From b4c522fabd0df7be08882d2207df8b2765026110 Mon Sep 17 00:00:00 2001 From: Iain Buclaw Date: Sun, 28 Oct 2018 19:51:47 +0000 Subject: Add D front-end, libphobos library, and D2 testsuite. ChangeLog: * Makefile.def (target_modules): Add libphobos. (flags_to_pass): Add GDC, GDCFLAGS, GDC_FOR_TARGET and GDCFLAGS_FOR_TARGET. (dependencies): Make libphobos depend on libatomic, libbacktrace configure, and zlib configure. (language): Add language d. * Makefile.in: Rebuild. * Makefile.tpl (BUILD_EXPORTS): Add GDC and GDCFLAGS. (HOST_EXPORTS): Add GDC. (POSTSTAGE1_HOST_EXPORTS): Add GDC and GDC_FOR_BUILD. (BASE_TARGET_EXPORTS): Add GDC. (GDC_FOR_BUILD, GDC, GDCFLAGS): New variables. (GDC_FOR_TARGET, GDC_FLAGS_FOR_TARGET): New variables. (EXTRA_HOST_FLAGS): Add GDC. (STAGE1_FLAGS_TO_PASS): Add GDC. (EXTRA_TARGET_FLAGS): Add GDC and GDCFLAGS. * config-ml.in: Treat GDC and GDCFLAGS like other compiler/flag environment variables. * configure: Rebuild. * configure.ac: Add target-libphobos to target_libraries. Set and substitute GDC_FOR_BUILD and GDC_FOR_TARGET. config/ChangeLog: * multi.m4: Set GDC. gcc/ChangeLog: * Makefile.in (tm_d_file_list, tm_d_include_list): New variables. (TM_D_H, D_TARGET_DEF, D_TARGET_H, D_TARGET_OBJS): New variables. (tm_d.h, cs-tm_d.h, default-d.o): New rules. (d/d-target-hooks-def.h, s-d-target-hooks-def-h): New rules. (s-tm-texi): Also check timestamp on d-target.def. (generated_files): Add TM_D_H and d-target-hooks-def.h. (build/genhooks.o): Also depend on D_TARGET_DEF. * config.gcc (tm_d_file, d_target_objs, target_has_targetdm): New variables. * config/aarch64/aarch64-d.c: New file. * config/aarch64/aarch64-linux.h (GNU_USER_TARGET_D_CRITSEC_SIZE): Define. * config/aarch64/aarch64-protos.h (aarch64_d_target_versions): New prototype. * config/aarch64/aarch64.h (TARGET_D_CPU_VERSIONS): Define. * config/aarch64/t-aarch64 (aarch64-d.o): New rule. * config/arm/arm-d.c: New file. * config/arm/arm-protos.h (arm_d_target_versions): New prototype. * config/arm/arm.h (TARGET_D_CPU_VERSIONS): Define. * config/arm/linux-eabi.h (EXTRA_TARGET_D_OS_VERSIONS): Define. * config/arm/t-arm (arm-d.o): New rule. * config/default-d.c: New file. * config/glibc-d.c: New file. * config/gnu.h (GNU_USER_TARGET_D_OS_VERSIONS): Define. * config/i386/i386-d.c: New file. * config/i386/i386-protos.h (ix86_d_target_versions): New prototype. * config/i386/i386.h (TARGET_D_CPU_VERSIONS): Define. * config/i386/linux-common.h (EXTRA_TARGET_D_OS_VERSIONS): Define. (GNU_USER_TARGET_D_CRITSEC_SIZE): Define. * config/i386/t-i386 (i386-d.o): New rule. * config/kfreebsd-gnu.h (GNU_USER_TARGET_D_OS_VERSIONS): Define. * config/kopensolaris-gnu.h (GNU_USER_TARGET_D_OS_VERSIONS): Define. * config/linux-android.h (ANDROID_TARGET_D_OS_VERSIONS): Define. * config/linux.h (GNU_USER_TARGET_D_OS_VERSIONS): Define. * config/mips/linux-common.h (EXTRA_TARGET_D_OS_VERSIONS): Define. * config/mips/mips-d.c: New file. * config/mips/mips-protos.h (mips_d_target_versions): New prototype. * config/mips/mips.h (TARGET_D_CPU_VERSIONS): Define. * config/mips/t-mips (mips-d.o): New rule. * config/powerpcspe/linux.h (GNU_USER_TARGET_D_OS_VERSIONS): Define. * config/powerpcspe/linux64.h (GNU_USER_TARGET_D_OS_VERSIONS): Define. * config/powerpcspe/powerpcspe-d.c: New file. * config/powerpcspe/powerpcspe-protos.h (rs6000_d_target_versions): New prototype. * config/powerpcspe/powerpcspe.c (rs6000_output_function_epilogue): Support GNU D by using 0 as the language type. * config/powerpcspe/powerpcspe.h (TARGET_D_CPU_VERSIONS): Define. * config/powerpcspe/t-powerpcspe (powerpcspe-d.o): New rule. * config/riscv/riscv-d.c: New file. * config/riscv/riscv-protos.h (riscv_d_target_versions): New prototype. * config/riscv/riscv.h (TARGET_D_CPU_VERSIONS): Define. * config/riscv/t-riscv (riscv-d.o): New rule. * config/rs6000/linux.h (GNU_USER_TARGET_D_OS_VERSIONS): Define. * config/rs6000/linux64.h (GNU_USER_TARGET_D_OS_VERSIONS): Define. * config/rs6000/rs6000-d.c: New file. * config/rs6000/rs6000-protos.h (rs6000_d_target_versions): New prototype. * config/rs6000/rs6000.c (rs6000_output_function_epilogue): Support GNU D by using 0 as the language type. * config/rs6000/rs6000.h (TARGET_D_CPU_VERSIONS): Define. * config/rs6000/t-rs6000 (rs6000-d.o): New rule. * config/s390/s390-d.c: New file. * config/s390/s390-protos.h (s390_d_target_versions): New prototype. * config/s390/s390.h (TARGET_D_CPU_VERSIONS): Define. * config/s390/t-s390 (s390-d.o): New rule. * config/sparc/sparc-d.c: New file. * config/sparc/sparc-protos.h (sparc_d_target_versions): New prototype. * config/sparc/sparc.h (TARGET_D_CPU_VERSIONS): Define. * config/sparc/t-sparc (sparc-d.o): New rule. * config/t-glibc (glibc-d.o): New rule. * configure: Regenerated. * configure.ac (tm_d_file): New variable. (tm_d_file_list, tm_d_include_list, d_target_objs): Add substitutes. * doc/contrib.texi (Contributors): Add self for the D frontend. * doc/frontends.texi (G++ and GCC): Mention D as a supported language. * doc/install.texi (Configuration): Mention libphobos as an option for --enable-shared. Mention d as an option for --enable-languages. (Testing): Mention check-d as a target. * doc/invoke.texi (Overall Options): Mention .d, .dd, and .di as file name suffixes. Mention d as a -x option. * doc/sourcebuild.texi (Top Level): Mention libphobos. * doc/standards.texi (Standards): Add section on D language. * doc/tm.texi: Regenerated. * doc/tm.texi.in: Add @node for D language and ABI, and @hook for TARGET_CPU_VERSIONS, TARGET_D_OS_VERSIONS, and TARGET_D_CRITSEC_SIZE. * dwarf2out.c (is_dlang): New function. (gen_compile_unit_die): Use DW_LANG_D for D. (declare_in_namespace): Return module die for D, instead of adding extra declarations into the namespace. (gen_namespace_die): Generate DW_TAG_module for D. (gen_decl_die): Handle CONST_DECLSs for D. (dwarf2out_decl): Likewise. (prune_unused_types_walk_local_classes): Handle DW_tag_interface_type. (prune_unused_types_walk): Handle DW_tag_interface_type same as other kinds of aggregates. * gcc.c (default_compilers): Add entries for .d, .dd and .di. * genhooks.c: Include d/d-target.def. gcc/po/ChangeLog: * EXCLUDES: Add sources from d/dmd. gcc/testsuite/ChangeLog: * gcc.misc-tests/help.exp: Add D to option descriptions check. * gdc.dg/asan/asan.exp: New file. * gdc.dg/asan/gdc272.d: New test. * gdc.dg/compilable.d: New test. * gdc.dg/dg.exp: New file. * gdc.dg/gdc254.d: New test. * gdc.dg/gdc260.d: New test. * gdc.dg/gdc270a.d: New test. * gdc.dg/gdc270b.d: New test. * gdc.dg/gdc282.d: New test. * gdc.dg/gdc283.d: New test. * gdc.dg/imports/gdc170.d: New test. * gdc.dg/imports/gdc231.d: New test. * gdc.dg/imports/gdc239.d: New test. * gdc.dg/imports/gdc241a.d: New test. * gdc.dg/imports/gdc241b.d: New test. * gdc.dg/imports/gdc251a.d: New test. * gdc.dg/imports/gdc251b.d: New test. * gdc.dg/imports/gdc253.d: New test. * gdc.dg/imports/gdc254a.d: New test. * gdc.dg/imports/gdc256.d: New test. * gdc.dg/imports/gdc27.d: New test. * gdc.dg/imports/gdcpkg256/package.d: New test. * gdc.dg/imports/runnable.d: New test. * gdc.dg/link.d: New test. * gdc.dg/lto/lto.exp: New file. * gdc.dg/lto/ltotests_0.d: New test. * gdc.dg/lto/ltotests_1.d: New test. * gdc.dg/runnable.d: New test. * gdc.dg/simd.d: New test. * gdc.test/gdc-test.exp: New file. * lib/gdc-dg.exp: New file. * lib/gdc.exp: New file. libphobos/ChangeLog: * Makefile.am: New file. * Makefile.in: New file. * acinclude.m4: New file. * aclocal.m4: New file. * config.h.in: New file. * configure: New file. * configure.ac: New file. * d_rules.am: New file. * libdruntime/Makefile.am: New file. * libdruntime/Makefile.in: New file. * libdruntime/__entrypoint.di: New file. * libdruntime/__main.di: New file. * libdruntime/gcc/attribute.d: New file. * libdruntime/gcc/backtrace.d: New file. * libdruntime/gcc/builtins.d: New file. * libdruntime/gcc/config.d.in: New file. * libdruntime/gcc/deh.d: New file. * libdruntime/gcc/libbacktrace.d.in: New file. * libdruntime/gcc/unwind/arm.d: New file. * libdruntime/gcc/unwind/arm_common.d: New file. * libdruntime/gcc/unwind/c6x.d: New file. * libdruntime/gcc/unwind/generic.d: New file. * libdruntime/gcc/unwind/package.d: New file. * libdruntime/gcc/unwind/pe.d: New file. * m4/autoconf.m4: New file. * m4/druntime.m4: New file. * m4/druntime/cpu.m4: New file. * m4/druntime/libraries.m4: New file. * m4/druntime/os.m4: New file. * m4/gcc_support.m4: New file. * m4/gdc.m4: New file. * m4/libtool.m4: New file. * src/Makefile.am: New file. * src/Makefile.in: New file. * src/libgphobos.spec.in: New file. * testsuite/Makefile.am: New file. * testsuite/Makefile.in: New file. * testsuite/config/default.exp: New file. * testsuite/lib/libphobos-dg.exp: New file. * testsuite/lib/libphobos.exp: New file. * testsuite/testsuite_flags.in: New file. From-SVN: r265573 --- libphobos/src/LICENSE_1_0.txt | 23 + libphobos/src/Makefile.am | 180 + libphobos/src/Makefile.in | 1824 +++ libphobos/src/etc/c/curl.d | 2336 ++++ libphobos/src/etc/c/sqlite3.d | 2126 ++++ libphobos/src/etc/c/zlib.d | 1788 +++ libphobos/src/index.d | 526 + libphobos/src/libgphobos.spec.in | 8 + libphobos/src/std/algorithm/comparison.d | 2159 ++++ libphobos/src/std/algorithm/internal.d | 77 + libphobos/src/std/algorithm/iteration.d | 5187 ++++++++ libphobos/src/std/algorithm/mutation.d | 2909 +++++ libphobos/src/std/algorithm/package.d | 198 + libphobos/src/std/algorithm/searching.d | 4600 +++++++ libphobos/src/std/algorithm/setops.d | 1521 +++ libphobos/src/std/algorithm/sorting.d | 4468 +++++++ libphobos/src/std/array.d | 3775 ++++++ libphobos/src/std/ascii.d | 729 ++ libphobos/src/std/base64.d | 2099 ++++ libphobos/src/std/bigint.d | 1705 +++ libphobos/src/std/bitmanip.d | 4009 +++++++ libphobos/src/std/compiler.d | 58 + libphobos/src/std/complex.d | 994 ++ libphobos/src/std/concurrency.d | 2531 ++++ libphobos/src/std/container/array.d | 2419 ++++ libphobos/src/std/container/binaryheap.d | 595 + libphobos/src/std/container/dlist.d | 1039 ++ libphobos/src/std/container/package.d | 1156 ++ libphobos/src/std/container/rbtree.d | 2065 ++++ libphobos/src/std/container/slist.d | 846 ++ libphobos/src/std/container/util.d | 189 + libphobos/src/std/conv.d | 6290 ++++++++++ libphobos/src/std/csv.d | 1701 +++ libphobos/src/std/datetime/date.d | 10580 ++++++++++++++++ libphobos/src/std/datetime/interval.d | 9131 ++++++++++++++ libphobos/src/std/datetime/package.d | 733 ++ libphobos/src/std/datetime/stopwatch.d | 428 + libphobos/src/std/datetime/systime.d | 11151 +++++++++++++++++ libphobos/src/std/datetime/timezone.d | 4235 +++++++ libphobos/src/std/demangle.d | 89 + libphobos/src/std/digest/crc.d | 705 ++ libphobos/src/std/digest/digest.d | 21 + libphobos/src/std/digest/hmac.d | 336 + libphobos/src/std/digest/md.d | 590 + libphobos/src/std/digest/murmurhash.d | 755 ++ libphobos/src/std/digest/package.d | 1171 ++ libphobos/src/std/digest/ripemd.d | 762 ++ libphobos/src/std/digest/sha.d | 1291 ++ libphobos/src/std/encoding.d | 3662 ++++++ libphobos/src/std/exception.d | 2316 ++++ .../allocator/building_blocks/affix_allocator.d | 441 + .../allocator/building_blocks/allocator_list.d | 640 + .../allocator/building_blocks/bitmapped_block.d | 1423 +++ .../allocator/building_blocks/bucketizer.d | 241 + .../allocator/building_blocks/fallback_allocator.d | 355 + .../allocator/building_blocks/free_list.d | 1205 ++ .../allocator/building_blocks/free_tree.d | 487 + .../allocator/building_blocks/kernighan_ritchie.d | 882 ++ .../allocator/building_blocks/null_allocator.d | 85 + .../allocator/building_blocks/package.d | 313 + .../allocator/building_blocks/quantizer.d | 234 + .../allocator/building_blocks/region.d | 784 ++ .../allocator/building_blocks/scoped_allocator.d | 221 + .../allocator/building_blocks/segregator.d | 361 + .../allocator/building_blocks/stats_collector.d | 735 ++ libphobos/src/std/experimental/allocator/common.d | 683 ++ .../src/std/experimental/allocator/gc_allocator.d | 167 + .../src/std/experimental/allocator/mallocator.d | 387 + .../std/experimental/allocator/mmap_allocator.d | 79 + libphobos/src/std/experimental/allocator/package.d | 3028 +++++ .../src/std/experimental/allocator/showcase.d | 92 + libphobos/src/std/experimental/allocator/typed.d | 423 + libphobos/src/std/experimental/checkedint.d | 3063 +++++ libphobos/src/std/experimental/logger/core.d | 3187 +++++ libphobos/src/std/experimental/logger/filelogger.d | 265 + .../src/std/experimental/logger/multilogger.d | 197 + libphobos/src/std/experimental/logger/nulllogger.d | 39 + libphobos/src/std/experimental/logger/package.d | 185 + libphobos/src/std/experimental/note.md | 1 + libphobos/src/std/experimental/typecons.d | 1078 ++ libphobos/src/std/file.d | 4325 +++++++ libphobos/src/std/format.d | 6028 ++++++++++ libphobos/src/std/functional.d | 1564 +++ libphobos/src/std/getopt.d | 1857 +++ libphobos/src/std/internal/cstring.d | 267 + libphobos/src/std/internal/digest/sha_SSSE3.d | 729 ++ libphobos/src/std/internal/math/biguintcore.d | 2571 ++++ libphobos/src/std/internal/math/biguintnoasm.d | 370 + libphobos/src/std/internal/math/biguintx86.d | 1353 +++ libphobos/src/std/internal/math/errorfunction.d | 1145 ++ libphobos/src/std/internal/math/gammafunction.d | 1834 +++ libphobos/src/std/internal/scopebuffer.d | 398 + libphobos/src/std/internal/test/dummyrange.d | 565 + libphobos/src/std/internal/test/range.d | 25 + libphobos/src/std/internal/test/uda.d | 16 + libphobos/src/std/internal/unicode_comp.d | 2984 +++++ libphobos/src/std/internal/unicode_decomp.d | 5301 ++++++++ libphobos/src/std/internal/unicode_grapheme.d | 293 + libphobos/src/std/internal/unicode_norm.d | 548 + libphobos/src/std/internal/unicode_tables.d | 11081 +++++++++++++++++ libphobos/src/std/internal/windows/advapi32.d | 69 + libphobos/src/std/json.d | 1859 +++ libphobos/src/std/math.d | 8413 +++++++++++++ libphobos/src/std/mathspecial.d | 361 + libphobos/src/std/meta.d | 1679 +++ libphobos/src/std/mmfile.d | 721 ++ libphobos/src/std/net/curl.d | 5109 ++++++++ libphobos/src/std/net/isemail.d | 1864 +++ libphobos/src/std/numeric.d | 3467 ++++++ libphobos/src/std/outbuffer.d | 418 + libphobos/src/std/parallelism.d | 4636 +++++++ libphobos/src/std/path.d | 4115 +++++++ libphobos/src/std/process.d | 4047 +++++++ libphobos/src/std/random.d | 3344 ++++++ libphobos/src/std/range/interfaces.d | 567 + libphobos/src/std/range/package.d | 12019 +++++++++++++++++++ libphobos/src/std/range/primitives.d | 2320 ++++ libphobos/src/std/regex/internal/backtracking.d | 1495 +++ libphobos/src/std/regex/internal/generator.d | 187 + libphobos/src/std/regex/internal/ir.d | 788 ++ libphobos/src/std/regex/internal/kickstart.d | 579 + libphobos/src/std/regex/internal/parser.d | 1751 +++ libphobos/src/std/regex/internal/tests.d | 1120 ++ libphobos/src/std/regex/internal/thompson.d | 1188 ++ libphobos/src/std/regex/package.d | 1735 +++ libphobos/src/std/signals.d | 708 ++ libphobos/src/std/socket.d | 3670 ++++++ libphobos/src/std/stdint.d | 131 + libphobos/src/std/stdio.d | 5159 ++++++++ libphobos/src/std/string.d | 6952 +++++++++++ libphobos/src/std/system.d | 74 + libphobos/src/std/traits.d | 8058 +++++++++++++ libphobos/src/std/typecons.d | 8029 +++++++++++++ libphobos/src/std/typetuple.d | 40 + libphobos/src/std/uni.d | 9756 +++++++++++++++ libphobos/src/std/uri.d | 592 + libphobos/src/std/utf.d | 4058 +++++++ libphobos/src/std/uuid.d | 1731 +++ libphobos/src/std/variant.d | 2771 +++++ libphobos/src/std/windows/charset.d | 122 + libphobos/src/std/windows/registry.d | 1868 +++ libphobos/src/std/windows/syserror.d | 201 + libphobos/src/std/xml.d | 3103 +++++ libphobos/src/std/zip.d | 990 ++ libphobos/src/std/zlib.d | 760 ++ 145 files changed, 297245 insertions(+) create mode 100644 libphobos/src/LICENSE_1_0.txt create mode 100644 libphobos/src/Makefile.am create mode 100644 libphobos/src/Makefile.in create mode 100644 libphobos/src/etc/c/curl.d create mode 100644 libphobos/src/etc/c/sqlite3.d create mode 100644 libphobos/src/etc/c/zlib.d create mode 100644 libphobos/src/index.d create mode 100644 libphobos/src/libgphobos.spec.in create mode 100644 libphobos/src/std/algorithm/comparison.d create mode 100644 libphobos/src/std/algorithm/internal.d create mode 100644 libphobos/src/std/algorithm/iteration.d create mode 100644 libphobos/src/std/algorithm/mutation.d create mode 100644 libphobos/src/std/algorithm/package.d create mode 100644 libphobos/src/std/algorithm/searching.d create mode 100644 libphobos/src/std/algorithm/setops.d create mode 100644 libphobos/src/std/algorithm/sorting.d create mode 100644 libphobos/src/std/array.d create mode 100644 libphobos/src/std/ascii.d create mode 100644 libphobos/src/std/base64.d create mode 100644 libphobos/src/std/bigint.d create mode 100644 libphobos/src/std/bitmanip.d create mode 100644 libphobos/src/std/compiler.d create mode 100644 libphobos/src/std/complex.d create mode 100644 libphobos/src/std/concurrency.d create mode 100644 libphobos/src/std/container/array.d create mode 100644 libphobos/src/std/container/binaryheap.d create mode 100644 libphobos/src/std/container/dlist.d create mode 100644 libphobos/src/std/container/package.d create mode 100644 libphobos/src/std/container/rbtree.d create mode 100644 libphobos/src/std/container/slist.d create mode 100644 libphobos/src/std/container/util.d create mode 100644 libphobos/src/std/conv.d create mode 100644 libphobos/src/std/csv.d create mode 100644 libphobos/src/std/datetime/date.d create mode 100644 libphobos/src/std/datetime/interval.d create mode 100644 libphobos/src/std/datetime/package.d create mode 100644 libphobos/src/std/datetime/stopwatch.d create mode 100644 libphobos/src/std/datetime/systime.d create mode 100644 libphobos/src/std/datetime/timezone.d create mode 100644 libphobos/src/std/demangle.d create mode 100644 libphobos/src/std/digest/crc.d create mode 100644 libphobos/src/std/digest/digest.d create mode 100644 libphobos/src/std/digest/hmac.d create mode 100644 libphobos/src/std/digest/md.d create mode 100644 libphobos/src/std/digest/murmurhash.d create mode 100644 libphobos/src/std/digest/package.d create mode 100644 libphobos/src/std/digest/ripemd.d create mode 100644 libphobos/src/std/digest/sha.d create mode 100644 libphobos/src/std/encoding.d create mode 100644 libphobos/src/std/exception.d create mode 100644 libphobos/src/std/experimental/allocator/building_blocks/affix_allocator.d create mode 100644 libphobos/src/std/experimental/allocator/building_blocks/allocator_list.d create mode 100644 libphobos/src/std/experimental/allocator/building_blocks/bitmapped_block.d create mode 100644 libphobos/src/std/experimental/allocator/building_blocks/bucketizer.d create mode 100644 libphobos/src/std/experimental/allocator/building_blocks/fallback_allocator.d create mode 100644 libphobos/src/std/experimental/allocator/building_blocks/free_list.d create mode 100644 libphobos/src/std/experimental/allocator/building_blocks/free_tree.d create mode 100644 libphobos/src/std/experimental/allocator/building_blocks/kernighan_ritchie.d create mode 100644 libphobos/src/std/experimental/allocator/building_blocks/null_allocator.d create mode 100644 libphobos/src/std/experimental/allocator/building_blocks/package.d create mode 100644 libphobos/src/std/experimental/allocator/building_blocks/quantizer.d create mode 100644 libphobos/src/std/experimental/allocator/building_blocks/region.d create mode 100644 libphobos/src/std/experimental/allocator/building_blocks/scoped_allocator.d create mode 100644 libphobos/src/std/experimental/allocator/building_blocks/segregator.d create mode 100644 libphobos/src/std/experimental/allocator/building_blocks/stats_collector.d create mode 100644 libphobos/src/std/experimental/allocator/common.d create mode 100644 libphobos/src/std/experimental/allocator/gc_allocator.d create mode 100644 libphobos/src/std/experimental/allocator/mallocator.d create mode 100644 libphobos/src/std/experimental/allocator/mmap_allocator.d create mode 100644 libphobos/src/std/experimental/allocator/package.d create mode 100644 libphobos/src/std/experimental/allocator/showcase.d create mode 100644 libphobos/src/std/experimental/allocator/typed.d create mode 100644 libphobos/src/std/experimental/checkedint.d create mode 100644 libphobos/src/std/experimental/logger/core.d create mode 100644 libphobos/src/std/experimental/logger/filelogger.d create mode 100644 libphobos/src/std/experimental/logger/multilogger.d create mode 100644 libphobos/src/std/experimental/logger/nulllogger.d create mode 100644 libphobos/src/std/experimental/logger/package.d create mode 100644 libphobos/src/std/experimental/note.md create mode 100644 libphobos/src/std/experimental/typecons.d create mode 100644 libphobos/src/std/file.d create mode 100644 libphobos/src/std/format.d create mode 100644 libphobos/src/std/functional.d create mode 100644 libphobos/src/std/getopt.d create mode 100644 libphobos/src/std/internal/cstring.d create mode 100644 libphobos/src/std/internal/digest/sha_SSSE3.d create mode 100644 libphobos/src/std/internal/math/biguintcore.d create mode 100644 libphobos/src/std/internal/math/biguintnoasm.d create mode 100644 libphobos/src/std/internal/math/biguintx86.d create mode 100644 libphobos/src/std/internal/math/errorfunction.d create mode 100644 libphobos/src/std/internal/math/gammafunction.d create mode 100644 libphobos/src/std/internal/scopebuffer.d create mode 100644 libphobos/src/std/internal/test/dummyrange.d create mode 100644 libphobos/src/std/internal/test/range.d create mode 100644 libphobos/src/std/internal/test/uda.d create mode 100644 libphobos/src/std/internal/unicode_comp.d create mode 100644 libphobos/src/std/internal/unicode_decomp.d create mode 100644 libphobos/src/std/internal/unicode_grapheme.d create mode 100644 libphobos/src/std/internal/unicode_norm.d create mode 100644 libphobos/src/std/internal/unicode_tables.d create mode 100644 libphobos/src/std/internal/windows/advapi32.d create mode 100644 libphobos/src/std/json.d create mode 100644 libphobos/src/std/math.d create mode 100644 libphobos/src/std/mathspecial.d create mode 100644 libphobos/src/std/meta.d create mode 100644 libphobos/src/std/mmfile.d create mode 100644 libphobos/src/std/net/curl.d create mode 100644 libphobos/src/std/net/isemail.d create mode 100644 libphobos/src/std/numeric.d create mode 100644 libphobos/src/std/outbuffer.d create mode 100644 libphobos/src/std/parallelism.d create mode 100644 libphobos/src/std/path.d create mode 100644 libphobos/src/std/process.d create mode 100644 libphobos/src/std/random.d create mode 100644 libphobos/src/std/range/interfaces.d create mode 100644 libphobos/src/std/range/package.d create mode 100644 libphobos/src/std/range/primitives.d create mode 100644 libphobos/src/std/regex/internal/backtracking.d create mode 100644 libphobos/src/std/regex/internal/generator.d create mode 100644 libphobos/src/std/regex/internal/ir.d create mode 100644 libphobos/src/std/regex/internal/kickstart.d create mode 100644 libphobos/src/std/regex/internal/parser.d create mode 100644 libphobos/src/std/regex/internal/tests.d create mode 100644 libphobos/src/std/regex/internal/thompson.d create mode 100644 libphobos/src/std/regex/package.d create mode 100644 libphobos/src/std/signals.d create mode 100644 libphobos/src/std/socket.d create mode 100644 libphobos/src/std/stdint.d create mode 100644 libphobos/src/std/stdio.d create mode 100644 libphobos/src/std/string.d create mode 100644 libphobos/src/std/system.d create mode 100644 libphobos/src/std/traits.d create mode 100644 libphobos/src/std/typecons.d create mode 100644 libphobos/src/std/typetuple.d create mode 100644 libphobos/src/std/uni.d create mode 100644 libphobos/src/std/uri.d create mode 100644 libphobos/src/std/utf.d create mode 100644 libphobos/src/std/uuid.d create mode 100644 libphobos/src/std/variant.d create mode 100644 libphobos/src/std/windows/charset.d create mode 100644 libphobos/src/std/windows/registry.d create mode 100644 libphobos/src/std/windows/syserror.d create mode 100644 libphobos/src/std/xml.d create mode 100644 libphobos/src/std/zip.d create mode 100644 libphobos/src/std/zlib.d (limited to 'libphobos/src') diff --git a/libphobos/src/LICENSE_1_0.txt b/libphobos/src/LICENSE_1_0.txt new file mode 100644 index 0000000..36b7cd9 --- /dev/null +++ b/libphobos/src/LICENSE_1_0.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/libphobos/src/Makefile.am b/libphobos/src/Makefile.am new file mode 100644 index 0000000..51ebf4e --- /dev/null +++ b/libphobos/src/Makefile.am @@ -0,0 +1,180 @@ +## Makefile for the Phobos standard library. +## Copyright (C) 2012-2018 Free Software Foundation, Inc. +## +## GCC is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3, or (at your option) +## any later version. +## +## GCC is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with GCC; see the file COPYING3. If not see +## . + +# Include D build rules +include $(top_srcdir)/d_rules.am + +# Make sure GDC can find libdruntime and libphobos include files +D_EXTRA_DFLAGS=-nostdinc -I $(srcdir) \ + -I $(top_srcdir)/libdruntime -I ../libdruntime -I . + +# C flags for zlib compilation +AM_CFLAGS=@DEFS@ -I. -I$(srcdir)/../libdruntime/gcc -I$(top_srcdir)/../zlib + +# D flags for compilation +AM_DFLAGS=$(phobos_compiler_pic_flag) + +# Install all D files +ALL_PHOBOS_INSTALL_DSOURCES = $(PHOBOS_DSOURCES) + +# Setup source files depending on configure +ALL_PHOBOS_COMPILE_DSOURCES = $(PHOBOS_DSOURCES) + +ALL_PHOBOS_SOURCES = $(ALL_PHOBOS_COMPILE_DSOURCES) +PHOBOS_TEST_LOBJECTS = $(ALL_PHOBOS_COMPILE_DSOURCES:.d=.t.lo) +PHOBOS_TEST_OBJECTS = $(ALL_PHOBOS_COMPILE_DSOURCES:.d=.t.o) + +# Main library build definitions +if DRUNTIME_ZLIB_SYSTEM + ZLIB_SRC = +else + ZLIB_SRC = $(ZLIB_CSOURCES) +endif +check_PROGRAMS = +if ENABLE_SHARED + check_LTLIBRARIES = libgphobos_t.la + check_PROGRAMS += unittest +endif +if ENABLE_STATIC + check_PROGRAMS += unittest_static +endif + +toolexeclib_DATA = libgphobos.spec +toolexeclib_LTLIBRARIES = libgphobos.la +libgphobos_la_SOURCES = $(ALL_PHOBOS_SOURCES) $(ZLIB_SRC) +libgphobos_la_LIBTOOLFLAGS = +libgphobos_la_LDFLAGS = -Xcompiler -nophoboslib -version-info $(PHOBOS_SOVERSION) +libgphobos_la_LIBADD = ../libdruntime/libgdruntime.la +libgphobos_la_DEPENDENCIES = libgphobos.spec + +# For static unittest, link objects directly +unittest_static_SOURCES = ../testsuite/test_runner.d $(ZLIB_SRC) +unittest_static_LIBTOOLFLAGS = +unittest_static_LDFLAGS = -Xcompiler -nophoboslib -static-libtool-libs +unittest_static_LDADD = $(PHOBOS_TEST_OBJECTS) \ + ../libdruntime/libgdruntime.la +EXTRA_unittest_static_DEPENDENCIES = $(PHOBOS_TEST_OBJECTS) + +# For unittest with dynamic library +libgphobos_t_la_SOURCES = $(ZLIB_SRC) +libgphobos_t_la_LIBTOOLFLAGS = +libgphobos_t_la_LDFLAGS = -Xcompiler -nophoboslib -rpath /foo -shared +libgphobos_t_la_LIBADD = $(PHOBOS_TEST_LOBJECTS) \ + ../libdruntime/libgdruntime.la +EXTRA_libgphobos_t_la_DEPENDENCIES = $(PHOBOS_TEST_LOBJECTS) + +# For unittest +unittest_SOURCES = ../testsuite/test_runner.d +unittest_LIBTOOLFLAGS = +unittest_LDFLAGS = -Xcompiler -nophoboslib -shared +unittest_LDADD = libgphobos_t.la ../libdruntime/libgdruntime.la + +# Extra install and clean rules. +# This does not delete the .libs/.t.o files, but deleting +# the .lo is good enough to rerun the rules +clean-local: + rm -f $(PHOBOS_TEST_LOBJECTS) + rm -f $(PHOBOS_TEST_OBJECTS) + +# Handles generated files as well +install-data-local: + for file in $(ALL_PHOBOS_INSTALL_DSOURCES); do \ + if test -f $$file; then \ + $(INSTALL_HEADER) -D $$file $(DESTDIR)$(gdc_include_dir)/$$file ; \ + else \ + $(INSTALL_HEADER) -D $(srcdir)/$$file \ + $(DESTDIR)$(gdc_include_dir)/$$file ; \ + fi ; \ + done + +# Zlib sources when not using system libz +ZLIB_CSOURCES=$(top_srcdir)/../zlib/adler32.c $(top_srcdir)/../zlib/compress.c \ + $(top_srcdir)/../zlib/crc32.c $(top_srcdir)/../zlib/deflate.c \ + $(top_srcdir)/../zlib/gzclose.c $(top_srcdir)/../zlib/gzlib.c \ + $(top_srcdir)/../zlib/gzread.c $(top_srcdir)/../zlib/gzwrite.c \ + $(top_srcdir)/../zlib/infback.c $(top_srcdir)/../zlib/inffast.c \ + $(top_srcdir)/../zlib/inflate.c $(top_srcdir)/../zlib/inftrees.c \ + $(top_srcdir)/../zlib/trees.c $(top_srcdir)/../zlib/uncompr.c \ + $(top_srcdir)/../zlib/zutil.c + +# Source file definitions. Boring stuff, auto-generated with +# https://gist.github.com/jpf91/8744acebc9dcf1e9d1a35cdff20afbb2 +# Can't use wildcards here: +# https://www.gnu.org/software/automake/manual/html_node/Wildcards.html +PHOBOS_DSOURCES = etc/c/curl.d etc/c/sqlite3.d etc/c/zlib.d \ + std/algorithm/comparison.d std/algorithm/internal.d \ + std/algorithm/iteration.d std/algorithm/mutation.d \ + std/algorithm/package.d std/algorithm/searching.d \ + std/algorithm/setops.d std/algorithm/sorting.d std/array.d std/ascii.d \ + std/base64.d std/bigint.d std/bitmanip.d std/compiler.d std/complex.d \ + std/concurrency.d std/container/array.d std/container/binaryheap.d \ + std/container/dlist.d std/container/package.d std/container/rbtree.d \ + std/container/slist.d std/container/util.d std/conv.d std/csv.d \ + std/datetime/date.d std/datetime/interval.d std/datetime/package.d \ + std/datetime/stopwatch.d std/datetime/systime.d \ + std/datetime/timezone.d std/demangle.d std/digest/crc.d \ + std/digest/digest.d std/digest/hmac.d std/digest/md.d \ + std/digest/murmurhash.d std/digest/package.d std/digest/ripemd.d \ + std/digest/sha.d std/encoding.d std/exception.d \ + std/experimental/allocator/building_blocks/affix_allocator.d \ + std/experimental/allocator/building_blocks/allocator_list.d \ + std/experimental/allocator/building_blocks/bitmapped_block.d \ + std/experimental/allocator/building_blocks/bucketizer.d \ + std/experimental/allocator/building_blocks/fallback_allocator.d \ + std/experimental/allocator/building_blocks/free_list.d \ + std/experimental/allocator/building_blocks/free_tree.d \ + std/experimental/allocator/building_blocks/kernighan_ritchie.d \ + std/experimental/allocator/building_blocks/null_allocator.d \ + std/experimental/allocator/building_blocks/package.d \ + std/experimental/allocator/building_blocks/quantizer.d \ + std/experimental/allocator/building_blocks/region.d \ + std/experimental/allocator/building_blocks/scoped_allocator.d \ + std/experimental/allocator/building_blocks/segregator.d \ + std/experimental/allocator/building_blocks/stats_collector.d \ + std/experimental/allocator/common.d \ + std/experimental/allocator/gc_allocator.d \ + std/experimental/allocator/mallocator.d \ + std/experimental/allocator/mmap_allocator.d \ + std/experimental/allocator/package.d \ + std/experimental/allocator/showcase.d \ + std/experimental/allocator/typed.d std/experimental/checkedint.d \ + std/experimental/logger/core.d std/experimental/logger/filelogger.d \ + std/experimental/logger/multilogger.d \ + std/experimental/logger/nulllogger.d std/experimental/logger/package.d \ + std/experimental/typecons.d std/file.d std/format.d std/functional.d \ + std/getopt.d std/internal/cstring.d std/internal/digest/sha_SSSE3.d \ + std/internal/math/biguintcore.d std/internal/math/biguintnoasm.d \ + std/internal/math/biguintx86.d std/internal/math/errorfunction.d \ + std/internal/math/gammafunction.d std/internal/scopebuffer.d \ + std/internal/test/dummyrange.d std/internal/test/range.d \ + std/internal/test/uda.d std/internal/unicode_comp.d \ + std/internal/unicode_decomp.d std/internal/unicode_grapheme.d \ + std/internal/unicode_norm.d std/internal/unicode_tables.d \ + std/internal/windows/advapi32.d std/json.d std/math.d \ + std/mathspecial.d std/meta.d std/mmfile.d std/net/curl.d \ + std/net/isemail.d std/numeric.d std/outbuffer.d std/parallelism.d \ + std/path.d std/process.d std/random.d std/range/interfaces.d \ + std/range/package.d std/range/primitives.d \ + std/regex/internal/backtracking.d std/regex/internal/generator.d \ + std/regex/internal/ir.d std/regex/internal/kickstart.d \ + std/regex/internal/parser.d std/regex/internal/tests.d \ + std/regex/internal/thompson.d std/regex/package.d std/signals.d \ + std/socket.d std/stdint.d std/stdio.d std/string.d std/system.d \ + std/traits.d std/typecons.d std/typetuple.d std/uni.d std/uri.d \ + std/utf.d std/uuid.d std/variant.d std/windows/charset.d \ + std/windows/registry.d std/windows/syserror.d std/xml.d std/zip.d \ + std/zlib.d diff --git a/libphobos/src/Makefile.in b/libphobos/src/Makefile.in new file mode 100644 index 0000000..6e8e90e --- /dev/null +++ b/libphobos/src/Makefile.in @@ -0,0 +1,1824 @@ +# Makefile.in generated by automake 1.11.6 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software +# Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__make_dryrun = \ + { \ + am__dry=no; \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + echo 'am--echo: ; @echo "AM" OK' | $(MAKE) -f - 2>/dev/null \ + | grep '^AM OK$$' >/dev/null || am__dry=yes;; \ + *) \ + for am__flg in $$MAKEFLAGS; do \ + case $$am__flg in \ + *=*|--*) ;; \ + *n*) am__dry=yes; break;; \ + esac; \ + done;; \ + esac; \ + test $$am__dry = yes; \ + } +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +DIST_COMMON = $(top_srcdir)/d_rules.am $(srcdir)/Makefile.in \ + $(srcdir)/Makefile.am $(srcdir)/libgphobos.spec.in +check_PROGRAMS = $(am__EXEEXT_1) $(am__EXEEXT_2) +@ENABLE_SHARED_TRUE@am__append_1 = unittest +@ENABLE_STATIC_TRUE@am__append_2 = unittest_static +subdir = src +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/../config/acx.m4 \ + $(top_srcdir)/../config/lead-dot.m4 \ + $(top_srcdir)/../config/multi.m4 \ + $(top_srcdir)/../config/override.m4 \ + $(top_srcdir)/../libtool.m4 $(top_srcdir)/../ltoptions.m4 \ + $(top_srcdir)/../ltsugar.m4 $(top_srcdir)/../ltversion.m4 \ + $(top_srcdir)/../lt~obsolete.m4 $(top_srcdir)/acinclude.m4 \ + $(top_srcdir)/m4/gcc_support.m4 $(top_srcdir)/m4/autoconf.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/gdc.m4 \ + $(top_srcdir)/m4/druntime.m4 $(top_srcdir)/m4/druntime/cpu.m4 \ + $(top_srcdir)/m4/druntime/os.m4 \ + $(top_srcdir)/m4/druntime/libraries.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(SHELL) $(top_srcdir)/../mkinstalldirs +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = libgphobos.spec +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(toolexeclibdir)" \ + "$(DESTDIR)$(toolexeclibdir)" +LTLIBRARIES = $(toolexeclib_LTLIBRARIES) +am__dirstamp = $(am__leading_dot)dirstamp +am__objects_1 = etc/c/curl.lo etc/c/sqlite3.lo etc/c/zlib.lo \ + std/algorithm/comparison.lo std/algorithm/internal.lo \ + std/algorithm/iteration.lo std/algorithm/mutation.lo \ + std/algorithm/package.lo std/algorithm/searching.lo \ + std/algorithm/setops.lo std/algorithm/sorting.lo std/array.lo \ + std/ascii.lo std/base64.lo std/bigint.lo std/bitmanip.lo \ + std/compiler.lo std/complex.lo std/concurrency.lo \ + std/container/array.lo std/container/binaryheap.lo \ + std/container/dlist.lo std/container/package.lo \ + std/container/rbtree.lo std/container/slist.lo \ + std/container/util.lo std/conv.lo std/csv.lo \ + std/datetime/date.lo std/datetime/interval.lo \ + std/datetime/package.lo std/datetime/stopwatch.lo \ + std/datetime/systime.lo std/datetime/timezone.lo \ + std/demangle.lo std/digest/crc.lo std/digest/digest.lo \ + std/digest/hmac.lo std/digest/md.lo std/digest/murmurhash.lo \ + std/digest/package.lo std/digest/ripemd.lo std/digest/sha.lo \ + std/encoding.lo std/exception.lo \ + std/experimental/allocator/building_blocks/affix_allocator.lo \ + std/experimental/allocator/building_blocks/allocator_list.lo \ + std/experimental/allocator/building_blocks/bitmapped_block.lo \ + std/experimental/allocator/building_blocks/bucketizer.lo \ + std/experimental/allocator/building_blocks/fallback_allocator.lo \ + std/experimental/allocator/building_blocks/free_list.lo \ + std/experimental/allocator/building_blocks/free_tree.lo \ + std/experimental/allocator/building_blocks/kernighan_ritchie.lo \ + std/experimental/allocator/building_blocks/null_allocator.lo \ + std/experimental/allocator/building_blocks/package.lo \ + std/experimental/allocator/building_blocks/quantizer.lo \ + std/experimental/allocator/building_blocks/region.lo \ + std/experimental/allocator/building_blocks/scoped_allocator.lo \ + std/experimental/allocator/building_blocks/segregator.lo \ + std/experimental/allocator/building_blocks/stats_collector.lo \ + std/experimental/allocator/common.lo \ + std/experimental/allocator/gc_allocator.lo \ + std/experimental/allocator/mallocator.lo \ + std/experimental/allocator/mmap_allocator.lo \ + std/experimental/allocator/package.lo \ + std/experimental/allocator/showcase.lo \ + std/experimental/allocator/typed.lo \ + std/experimental/checkedint.lo std/experimental/logger/core.lo \ + std/experimental/logger/filelogger.lo \ + std/experimental/logger/multilogger.lo \ + std/experimental/logger/nulllogger.lo \ + std/experimental/logger/package.lo \ + std/experimental/typecons.lo std/file.lo std/format.lo \ + std/functional.lo std/getopt.lo std/internal/cstring.lo \ + std/internal/digest/sha_SSSE3.lo \ + std/internal/math/biguintcore.lo \ + std/internal/math/biguintnoasm.lo \ + std/internal/math/biguintx86.lo \ + std/internal/math/errorfunction.lo \ + std/internal/math/gammafunction.lo std/internal/scopebuffer.lo \ + std/internal/test/dummyrange.lo std/internal/test/range.lo \ + std/internal/test/uda.lo std/internal/unicode_comp.lo \ + std/internal/unicode_decomp.lo \ + std/internal/unicode_grapheme.lo std/internal/unicode_norm.lo \ + std/internal/unicode_tables.lo \ + std/internal/windows/advapi32.lo std/json.lo std/math.lo \ + std/mathspecial.lo std/meta.lo std/mmfile.lo std/net/curl.lo \ + std/net/isemail.lo std/numeric.lo std/outbuffer.lo \ + std/parallelism.lo std/path.lo std/process.lo std/random.lo \ + std/range/interfaces.lo std/range/package.lo \ + std/range/primitives.lo std/regex/internal/backtracking.lo \ + std/regex/internal/generator.lo std/regex/internal/ir.lo \ + std/regex/internal/kickstart.lo std/regex/internal/parser.lo \ + std/regex/internal/tests.lo std/regex/internal/thompson.lo \ + std/regex/package.lo std/signals.lo std/socket.lo \ + std/stdint.lo std/stdio.lo std/string.lo std/system.lo \ + std/traits.lo std/typecons.lo std/typetuple.lo std/uni.lo \ + std/uri.lo std/utf.lo std/uuid.lo std/variant.lo \ + std/windows/charset.lo std/windows/registry.lo \ + std/windows/syserror.lo std/xml.lo std/zip.lo std/zlib.lo +am__objects_2 = $(am__objects_1) +am__objects_3 = $(am__objects_2) +am__objects_4 = libgphobos_la-adler32.lo libgphobos_la-compress.lo \ + libgphobos_la-crc32.lo libgphobos_la-deflate.lo \ + libgphobos_la-gzclose.lo libgphobos_la-gzlib.lo \ + libgphobos_la-gzread.lo libgphobos_la-gzwrite.lo \ + libgphobos_la-infback.lo libgphobos_la-inffast.lo \ + libgphobos_la-inflate.lo libgphobos_la-inftrees.lo \ + libgphobos_la-trees.lo libgphobos_la-uncompr.lo \ + libgphobos_la-zutil.lo +@DRUNTIME_ZLIB_SYSTEM_FALSE@am__objects_5 = $(am__objects_4) +am_libgphobos_la_OBJECTS = $(am__objects_3) $(am__objects_5) +libgphobos_la_OBJECTS = $(am_libgphobos_la_OBJECTS) +am__DEPENDENCIES_1 = etc/c/curl.t.lo etc/c/sqlite3.t.lo \ + etc/c/zlib.t.lo std/algorithm/comparison.t.lo \ + std/algorithm/internal.t.lo std/algorithm/iteration.t.lo \ + std/algorithm/mutation.t.lo std/algorithm/package.t.lo \ + std/algorithm/searching.t.lo std/algorithm/setops.t.lo \ + std/algorithm/sorting.t.lo std/array.t.lo std/ascii.t.lo \ + std/base64.t.lo std/bigint.t.lo std/bitmanip.t.lo \ + std/compiler.t.lo std/complex.t.lo std/concurrency.t.lo \ + std/container/array.t.lo std/container/binaryheap.t.lo \ + std/container/dlist.t.lo std/container/package.t.lo \ + std/container/rbtree.t.lo std/container/slist.t.lo \ + std/container/util.t.lo std/conv.t.lo std/csv.t.lo \ + std/datetime/date.t.lo std/datetime/interval.t.lo \ + std/datetime/package.t.lo std/datetime/stopwatch.t.lo \ + std/datetime/systime.t.lo std/datetime/timezone.t.lo \ + std/demangle.t.lo std/digest/crc.t.lo std/digest/digest.t.lo \ + std/digest/hmac.t.lo std/digest/md.t.lo \ + std/digest/murmurhash.t.lo std/digest/package.t.lo \ + std/digest/ripemd.t.lo std/digest/sha.t.lo std/encoding.t.lo \ + std/exception.t.lo \ + std/experimental/allocator/building_blocks/affix_allocator.t.lo \ + std/experimental/allocator/building_blocks/allocator_list.t.lo \ + std/experimental/allocator/building_blocks/bitmapped_block.t.lo \ + std/experimental/allocator/building_blocks/bucketizer.t.lo \ + std/experimental/allocator/building_blocks/fallback_allocator.t.lo \ + std/experimental/allocator/building_blocks/free_list.t.lo \ + std/experimental/allocator/building_blocks/free_tree.t.lo \ + std/experimental/allocator/building_blocks/kernighan_ritchie.t.lo \ + std/experimental/allocator/building_blocks/null_allocator.t.lo \ + std/experimental/allocator/building_blocks/package.t.lo \ + std/experimental/allocator/building_blocks/quantizer.t.lo \ + std/experimental/allocator/building_blocks/region.t.lo \ + std/experimental/allocator/building_blocks/scoped_allocator.t.lo \ + std/experimental/allocator/building_blocks/segregator.t.lo \ + std/experimental/allocator/building_blocks/stats_collector.t.lo \ + std/experimental/allocator/common.t.lo \ + std/experimental/allocator/gc_allocator.t.lo \ + std/experimental/allocator/mallocator.t.lo \ + std/experimental/allocator/mmap_allocator.t.lo \ + std/experimental/allocator/package.t.lo \ + std/experimental/allocator/showcase.t.lo \ + std/experimental/allocator/typed.t.lo \ + std/experimental/checkedint.t.lo \ + std/experimental/logger/core.t.lo \ + std/experimental/logger/filelogger.t.lo \ + std/experimental/logger/multilogger.t.lo \ + std/experimental/logger/nulllogger.t.lo \ + std/experimental/logger/package.t.lo \ + std/experimental/typecons.t.lo std/file.t.lo std/format.t.lo \ + std/functional.t.lo std/getopt.t.lo std/internal/cstring.t.lo \ + std/internal/digest/sha_SSSE3.t.lo \ + std/internal/math/biguintcore.t.lo \ + std/internal/math/biguintnoasm.t.lo \ + std/internal/math/biguintx86.t.lo \ + std/internal/math/errorfunction.t.lo \ + std/internal/math/gammafunction.t.lo \ + std/internal/scopebuffer.t.lo \ + std/internal/test/dummyrange.t.lo std/internal/test/range.t.lo \ + std/internal/test/uda.t.lo std/internal/unicode_comp.t.lo \ + std/internal/unicode_decomp.t.lo \ + std/internal/unicode_grapheme.t.lo \ + std/internal/unicode_norm.t.lo \ + std/internal/unicode_tables.t.lo \ + std/internal/windows/advapi32.t.lo std/json.t.lo std/math.t.lo \ + std/mathspecial.t.lo std/meta.t.lo std/mmfile.t.lo \ + std/net/curl.t.lo std/net/isemail.t.lo std/numeric.t.lo \ + std/outbuffer.t.lo std/parallelism.t.lo std/path.t.lo \ + std/process.t.lo std/random.t.lo std/range/interfaces.t.lo \ + std/range/package.t.lo std/range/primitives.t.lo \ + std/regex/internal/backtracking.t.lo \ + std/regex/internal/generator.t.lo std/regex/internal/ir.t.lo \ + std/regex/internal/kickstart.t.lo \ + std/regex/internal/parser.t.lo std/regex/internal/tests.t.lo \ + std/regex/internal/thompson.t.lo std/regex/package.t.lo \ + std/signals.t.lo std/socket.t.lo std/stdint.t.lo \ + std/stdio.t.lo std/string.t.lo std/system.t.lo std/traits.t.lo \ + std/typecons.t.lo std/typetuple.t.lo std/uni.t.lo std/uri.t.lo \ + std/utf.t.lo std/uuid.t.lo std/variant.t.lo \ + std/windows/charset.t.lo std/windows/registry.t.lo \ + std/windows/syserror.t.lo std/xml.t.lo std/zip.t.lo \ + std/zlib.t.lo +am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) +am__DEPENDENCIES_3 = $(am__DEPENDENCIES_2) +libgphobos_t_la_DEPENDENCIES = $(am__DEPENDENCIES_3) \ + ../libdruntime/libgdruntime.la +am__objects_6 = libgphobos_t_la-adler32.lo libgphobos_t_la-compress.lo \ + libgphobos_t_la-crc32.lo libgphobos_t_la-deflate.lo \ + libgphobos_t_la-gzclose.lo libgphobos_t_la-gzlib.lo \ + libgphobos_t_la-gzread.lo libgphobos_t_la-gzwrite.lo \ + libgphobos_t_la-infback.lo libgphobos_t_la-inffast.lo \ + libgphobos_t_la-inflate.lo libgphobos_t_la-inftrees.lo \ + libgphobos_t_la-trees.lo libgphobos_t_la-uncompr.lo \ + libgphobos_t_la-zutil.lo +@DRUNTIME_ZLIB_SYSTEM_FALSE@am__objects_7 = $(am__objects_6) +am_libgphobos_t_la_OBJECTS = $(am__objects_7) +libgphobos_t_la_OBJECTS = $(am_libgphobos_t_la_OBJECTS) +@ENABLE_SHARED_TRUE@am_libgphobos_t_la_rpath = +@ENABLE_SHARED_TRUE@am__EXEEXT_1 = unittest$(EXEEXT) +@ENABLE_STATIC_TRUE@am__EXEEXT_2 = unittest_static$(EXEEXT) +am_unittest_OBJECTS = ../testsuite/test_runner.$(OBJEXT) +unittest_OBJECTS = $(am_unittest_OBJECTS) +unittest_DEPENDENCIES = libgphobos_t.la ../libdruntime/libgdruntime.la +am__objects_8 = adler32.$(OBJEXT) compress.$(OBJEXT) crc32.$(OBJEXT) \ + deflate.$(OBJEXT) gzclose.$(OBJEXT) gzlib.$(OBJEXT) \ + gzread.$(OBJEXT) gzwrite.$(OBJEXT) infback.$(OBJEXT) \ + inffast.$(OBJEXT) inflate.$(OBJEXT) inftrees.$(OBJEXT) \ + trees.$(OBJEXT) uncompr.$(OBJEXT) zutil.$(OBJEXT) +@DRUNTIME_ZLIB_SYSTEM_FALSE@am__objects_9 = $(am__objects_8) +am_unittest_static_OBJECTS = ../testsuite/test_runner.$(OBJEXT) \ + $(am__objects_9) +unittest_static_OBJECTS = $(am_unittest_static_OBJECTS) +am__DEPENDENCIES_4 = etc/c/curl.t.o etc/c/sqlite3.t.o etc/c/zlib.t.o \ + std/algorithm/comparison.t.o std/algorithm/internal.t.o \ + std/algorithm/iteration.t.o std/algorithm/mutation.t.o \ + std/algorithm/package.t.o std/algorithm/searching.t.o \ + std/algorithm/setops.t.o std/algorithm/sorting.t.o \ + std/array.t.o std/ascii.t.o std/base64.t.o std/bigint.t.o \ + std/bitmanip.t.o std/compiler.t.o std/complex.t.o \ + std/concurrency.t.o std/container/array.t.o \ + std/container/binaryheap.t.o std/container/dlist.t.o \ + std/container/package.t.o std/container/rbtree.t.o \ + std/container/slist.t.o std/container/util.t.o std/conv.t.o \ + std/csv.t.o std/datetime/date.t.o std/datetime/interval.t.o \ + std/datetime/package.t.o std/datetime/stopwatch.t.o \ + std/datetime/systime.t.o std/datetime/timezone.t.o \ + std/demangle.t.o std/digest/crc.t.o std/digest/digest.t.o \ + std/digest/hmac.t.o std/digest/md.t.o \ + std/digest/murmurhash.t.o std/digest/package.t.o \ + std/digest/ripemd.t.o std/digest/sha.t.o std/encoding.t.o \ + std/exception.t.o \ + std/experimental/allocator/building_blocks/affix_allocator.t.o \ + std/experimental/allocator/building_blocks/allocator_list.t.o \ + std/experimental/allocator/building_blocks/bitmapped_block.t.o \ + std/experimental/allocator/building_blocks/bucketizer.t.o \ + std/experimental/allocator/building_blocks/fallback_allocator.t.o \ + std/experimental/allocator/building_blocks/free_list.t.o \ + std/experimental/allocator/building_blocks/free_tree.t.o \ + std/experimental/allocator/building_blocks/kernighan_ritchie.t.o \ + std/experimental/allocator/building_blocks/null_allocator.t.o \ + std/experimental/allocator/building_blocks/package.t.o \ + std/experimental/allocator/building_blocks/quantizer.t.o \ + std/experimental/allocator/building_blocks/region.t.o \ + std/experimental/allocator/building_blocks/scoped_allocator.t.o \ + std/experimental/allocator/building_blocks/segregator.t.o \ + std/experimental/allocator/building_blocks/stats_collector.t.o \ + std/experimental/allocator/common.t.o \ + std/experimental/allocator/gc_allocator.t.o \ + std/experimental/allocator/mallocator.t.o \ + std/experimental/allocator/mmap_allocator.t.o \ + std/experimental/allocator/package.t.o \ + std/experimental/allocator/showcase.t.o \ + std/experimental/allocator/typed.t.o \ + std/experimental/checkedint.t.o \ + std/experimental/logger/core.t.o \ + std/experimental/logger/filelogger.t.o \ + std/experimental/logger/multilogger.t.o \ + std/experimental/logger/nulllogger.t.o \ + std/experimental/logger/package.t.o \ + std/experimental/typecons.t.o std/file.t.o std/format.t.o \ + std/functional.t.o std/getopt.t.o std/internal/cstring.t.o \ + std/internal/digest/sha_SSSE3.t.o \ + std/internal/math/biguintcore.t.o \ + std/internal/math/biguintnoasm.t.o \ + std/internal/math/biguintx86.t.o \ + std/internal/math/errorfunction.t.o \ + std/internal/math/gammafunction.t.o \ + std/internal/scopebuffer.t.o std/internal/test/dummyrange.t.o \ + std/internal/test/range.t.o std/internal/test/uda.t.o \ + std/internal/unicode_comp.t.o std/internal/unicode_decomp.t.o \ + std/internal/unicode_grapheme.t.o \ + std/internal/unicode_norm.t.o std/internal/unicode_tables.t.o \ + std/internal/windows/advapi32.t.o std/json.t.o std/math.t.o \ + std/mathspecial.t.o std/meta.t.o std/mmfile.t.o \ + std/net/curl.t.o std/net/isemail.t.o std/numeric.t.o \ + std/outbuffer.t.o std/parallelism.t.o std/path.t.o \ + std/process.t.o std/random.t.o std/range/interfaces.t.o \ + std/range/package.t.o std/range/primitives.t.o \ + std/regex/internal/backtracking.t.o \ + std/regex/internal/generator.t.o std/regex/internal/ir.t.o \ + std/regex/internal/kickstart.t.o std/regex/internal/parser.t.o \ + std/regex/internal/tests.t.o std/regex/internal/thompson.t.o \ + std/regex/package.t.o std/signals.t.o std/socket.t.o \ + std/stdint.t.o std/stdio.t.o std/string.t.o std/system.t.o \ + std/traits.t.o std/typecons.t.o std/typetuple.t.o std/uni.t.o \ + std/uri.t.o std/utf.t.o std/uuid.t.o std/variant.t.o \ + std/windows/charset.t.o std/windows/registry.t.o \ + std/windows/syserror.t.o std/xml.t.o std/zip.t.o std/zlib.t.o +am__DEPENDENCIES_5 = $(am__DEPENDENCIES_4) +am__DEPENDENCIES_6 = $(am__DEPENDENCIES_5) +unittest_static_DEPENDENCIES = $(am__DEPENDENCIES_6) \ + ../libdruntime/libgdruntime.la +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = +am__depfiles_maybe = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ + $(LDFLAGS) -o $@ +SOURCES = $(libgphobos_la_SOURCES) $(libgphobos_t_la_SOURCES) \ + $(unittest_SOURCES) $(unittest_static_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +DATA = $(toolexeclib_DATA) +ETAGS = etags +CTAGS = ctags +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BACKTRACE_SUPPORTED = @BACKTRACE_SUPPORTED@ +BACKTRACE_SUPPORTS_THREADS = @BACKTRACE_SUPPORTS_THREADS@ +BACKTRACE_USES_MALLOC = @BACKTRACE_USES_MALLOC@ +CC = @CC@ +CCAS = @CCAS@ +CCASFLAGS = @CCASFLAGS@ +CC_FOR_BUILD = @CC_FOR_BUILD@ +CFLAGS = @CFLAGS@ +CFLAGS_FOR_BUILD = @CFLAGS_FOR_BUILD@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DCFG_ARM_EABI_UNWINDER = @DCFG_ARM_EABI_UNWINDER@ +DCFG_HAVE_64BIT_ATOMICS = @DCFG_HAVE_64BIT_ATOMICS@ +DCFG_HAVE_ATOMIC_BUILTINS = @DCFG_HAVE_ATOMIC_BUILTINS@ +DCFG_HAVE_LIBATOMIC = @DCFG_HAVE_LIBATOMIC@ +DCFG_MINFO_BRACKETING = @DCFG_MINFO_BRACKETING@ +DCFG_THREAD_MODEL = @DCFG_THREAD_MODEL@ +DEFS = @DEFS@ +DRUNTIME_SOVERSION = @DRUNTIME_SOVERSION@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GDC = @GDC@ +GDCFLAGS = @GDCFLAGS@ +GDCFLAGSX = @GDCFLAGSX@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBATOMIC = @LIBATOMIC@ +LIBBACKTRACE = @LIBBACKTRACE@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PHOBOS_SOVERSION = @PHOBOS_SOVERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPEC_PHOBOS_DEPS = @SPEC_PHOBOS_DEPS@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__leading_dot = @am__leading_dot@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +gcc_version = @gcc_version@ +gdc_include_dir = @gdc_include_dir@ +get_gcc_base_ver = @get_gcc_base_ver@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +libphobos_builddir = @libphobos_builddir@ +libphobos_srcdir = @libphobos_srcdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +multi_basedir = @multi_basedir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +phobos_compiler_pic_flag = @phobos_compiler_pic_flag@ +phobos_compiler_shared_flag = @phobos_compiler_shared_flag@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +toolexecdir = @toolexecdir@ +toolexeclibdir = @toolexeclibdir@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ + +# If there are no sources with known extension (i.e. only D sources) +# automake forgets to set this +CCLD = $(CC) +LTDCOMPILE = $(LIBTOOL) --tag=D $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=compile $(GDC) $(AM_DFLAGS) + + +# Override executable linking commands: We have to use GDC for linking +# to make sure we link pthreads and other dependencies +unittest_static_LINK = $(LIBTOOL) --tag=D \ + $(unittest_static_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(GDC) $(AM_CFLAGS) $(CFLAGS) $(unittest_static_LDFLAGS) \ + $(LDFLAGS) -o $@ + +unittest_LINK = $(LIBTOOL) --tag=D \ + $(unittest_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(GDC) $(AM_CFLAGS) $(CFLAGS) $(unittest_LDFLAGS) \ + $(LDFLAGS) -o $@ + + +# Also override library link commands: This is not strictly +# required, but we want to record additional dependencies such +# as pthread in the library +libgdruntime_la_LINK = $(LIBTOOL) --tag=D $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(GDC) $(AM_CFLAGS) $(CFLAGS) \ + $(libgdruntime_la_LDFLAGS) $(LDFLAGS) -o $@ + +libgdruntime_t_la_LINK = $(LIBTOOL) --tag=D $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(GDC) $(AM_CFLAGS) $(CFLAGS) \ + $(libgdruntime_t_la_LDFLAGS) $(LDFLAGS) -o $@ + +libgphobos_la_LINK = $(LIBTOOL) --tag=D $(libgphobos_la_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(GDC) $(AM_CFLAGS) $(CFLAGS) \ + $(libgphobos_la_LDFLAGS) $(LDFLAGS) -o $@ + +libgphobos_t_la_LINK = $(LIBTOOL) --tag=D \ + $(libgphobos_t_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(GDC) $(AM_CFLAGS) $(CFLAGS) $(libgphobos_t_la_LDFLAGS) \ + $(LDFLAGS) -o $@ + + +# Include D build rules + +# Make sure GDC can find libdruntime and libphobos include files +D_EXTRA_DFLAGS = -nostdinc -I $(srcdir) \ + -I $(top_srcdir)/libdruntime -I ../libdruntime -I . + + +# C flags for zlib compilation +AM_CFLAGS = @DEFS@ -I. -I$(srcdir)/../libdruntime/gcc -I$(top_srcdir)/../zlib + +# D flags for compilation +AM_DFLAGS = $(phobos_compiler_pic_flag) + +# Install all D files +ALL_PHOBOS_INSTALL_DSOURCES = $(PHOBOS_DSOURCES) + +# Setup source files depending on configure +ALL_PHOBOS_COMPILE_DSOURCES = $(PHOBOS_DSOURCES) +ALL_PHOBOS_SOURCES = $(ALL_PHOBOS_COMPILE_DSOURCES) +PHOBOS_TEST_LOBJECTS = $(ALL_PHOBOS_COMPILE_DSOURCES:.d=.t.lo) +PHOBOS_TEST_OBJECTS = $(ALL_PHOBOS_COMPILE_DSOURCES:.d=.t.o) +@DRUNTIME_ZLIB_SYSTEM_FALSE@ZLIB_SRC = $(ZLIB_CSOURCES) + +# Main library build definitions +@DRUNTIME_ZLIB_SYSTEM_TRUE@ZLIB_SRC = +@ENABLE_SHARED_TRUE@check_LTLIBRARIES = libgphobos_t.la +toolexeclib_DATA = libgphobos.spec +toolexeclib_LTLIBRARIES = libgphobos.la +libgphobos_la_SOURCES = $(ALL_PHOBOS_SOURCES) $(ZLIB_SRC) +libgphobos_la_LIBTOOLFLAGS = +libgphobos_la_LDFLAGS = -Xcompiler -nophoboslib -version-info $(PHOBOS_SOVERSION) +libgphobos_la_LIBADD = ../libdruntime/libgdruntime.la +libgphobos_la_DEPENDENCIES = libgphobos.spec + +# For static unittest, link objects directly +unittest_static_SOURCES = ../testsuite/test_runner.d $(ZLIB_SRC) +unittest_static_LIBTOOLFLAGS = +unittest_static_LDFLAGS = -Xcompiler -nophoboslib -static-libtool-libs +unittest_static_LDADD = $(PHOBOS_TEST_OBJECTS) \ + ../libdruntime/libgdruntime.la + +EXTRA_unittest_static_DEPENDENCIES = $(PHOBOS_TEST_OBJECTS) + +# For unittest with dynamic library +libgphobos_t_la_SOURCES = $(ZLIB_SRC) +libgphobos_t_la_LIBTOOLFLAGS = +libgphobos_t_la_LDFLAGS = -Xcompiler -nophoboslib -rpath /foo -shared +libgphobos_t_la_LIBADD = $(PHOBOS_TEST_LOBJECTS) \ + ../libdruntime/libgdruntime.la + +EXTRA_libgphobos_t_la_DEPENDENCIES = $(PHOBOS_TEST_LOBJECTS) + +# For unittest +unittest_SOURCES = ../testsuite/test_runner.d +unittest_LIBTOOLFLAGS = +unittest_LDFLAGS = -Xcompiler -nophoboslib -shared +unittest_LDADD = libgphobos_t.la ../libdruntime/libgdruntime.la + +# Zlib sources when not using system libz +ZLIB_CSOURCES = $(top_srcdir)/../zlib/adler32.c $(top_srcdir)/../zlib/compress.c \ + $(top_srcdir)/../zlib/crc32.c $(top_srcdir)/../zlib/deflate.c \ + $(top_srcdir)/../zlib/gzclose.c $(top_srcdir)/../zlib/gzlib.c \ + $(top_srcdir)/../zlib/gzread.c $(top_srcdir)/../zlib/gzwrite.c \ + $(top_srcdir)/../zlib/infback.c $(top_srcdir)/../zlib/inffast.c \ + $(top_srcdir)/../zlib/inflate.c $(top_srcdir)/../zlib/inftrees.c \ + $(top_srcdir)/../zlib/trees.c $(top_srcdir)/../zlib/uncompr.c \ + $(top_srcdir)/../zlib/zutil.c + + +# Source file definitions. Boring stuff, auto-generated with +# https://gist.github.com/jpf91/8744acebc9dcf1e9d1a35cdff20afbb2 +# Can't use wildcards here: +# https://www.gnu.org/software/automake/manual/html_node/Wildcards.html +PHOBOS_DSOURCES = etc/c/curl.d etc/c/sqlite3.d etc/c/zlib.d \ + std/algorithm/comparison.d std/algorithm/internal.d \ + std/algorithm/iteration.d std/algorithm/mutation.d \ + std/algorithm/package.d std/algorithm/searching.d \ + std/algorithm/setops.d std/algorithm/sorting.d std/array.d std/ascii.d \ + std/base64.d std/bigint.d std/bitmanip.d std/compiler.d std/complex.d \ + std/concurrency.d std/container/array.d std/container/binaryheap.d \ + std/container/dlist.d std/container/package.d std/container/rbtree.d \ + std/container/slist.d std/container/util.d std/conv.d std/csv.d \ + std/datetime/date.d std/datetime/interval.d std/datetime/package.d \ + std/datetime/stopwatch.d std/datetime/systime.d \ + std/datetime/timezone.d std/demangle.d std/digest/crc.d \ + std/digest/digest.d std/digest/hmac.d std/digest/md.d \ + std/digest/murmurhash.d std/digest/package.d std/digest/ripemd.d \ + std/digest/sha.d std/encoding.d std/exception.d \ + std/experimental/allocator/building_blocks/affix_allocator.d \ + std/experimental/allocator/building_blocks/allocator_list.d \ + std/experimental/allocator/building_blocks/bitmapped_block.d \ + std/experimental/allocator/building_blocks/bucketizer.d \ + std/experimental/allocator/building_blocks/fallback_allocator.d \ + std/experimental/allocator/building_blocks/free_list.d \ + std/experimental/allocator/building_blocks/free_tree.d \ + std/experimental/allocator/building_blocks/kernighan_ritchie.d \ + std/experimental/allocator/building_blocks/null_allocator.d \ + std/experimental/allocator/building_blocks/package.d \ + std/experimental/allocator/building_blocks/quantizer.d \ + std/experimental/allocator/building_blocks/region.d \ + std/experimental/allocator/building_blocks/scoped_allocator.d \ + std/experimental/allocator/building_blocks/segregator.d \ + std/experimental/allocator/building_blocks/stats_collector.d \ + std/experimental/allocator/common.d \ + std/experimental/allocator/gc_allocator.d \ + std/experimental/allocator/mallocator.d \ + std/experimental/allocator/mmap_allocator.d \ + std/experimental/allocator/package.d \ + std/experimental/allocator/showcase.d \ + std/experimental/allocator/typed.d std/experimental/checkedint.d \ + std/experimental/logger/core.d std/experimental/logger/filelogger.d \ + std/experimental/logger/multilogger.d \ + std/experimental/logger/nulllogger.d std/experimental/logger/package.d \ + std/experimental/typecons.d std/file.d std/format.d std/functional.d \ + std/getopt.d std/internal/cstring.d std/internal/digest/sha_SSSE3.d \ + std/internal/math/biguintcore.d std/internal/math/biguintnoasm.d \ + std/internal/math/biguintx86.d std/internal/math/errorfunction.d \ + std/internal/math/gammafunction.d std/internal/scopebuffer.d \ + std/internal/test/dummyrange.d std/internal/test/range.d \ + std/internal/test/uda.d std/internal/unicode_comp.d \ + std/internal/unicode_decomp.d std/internal/unicode_grapheme.d \ + std/internal/unicode_norm.d std/internal/unicode_tables.d \ + std/internal/windows/advapi32.d std/json.d std/math.d \ + std/mathspecial.d std/meta.d std/mmfile.d std/net/curl.d \ + std/net/isemail.d std/numeric.d std/outbuffer.d std/parallelism.d \ + std/path.d std/process.d std/random.d std/range/interfaces.d \ + std/range/package.d std/range/primitives.d \ + std/regex/internal/backtracking.d std/regex/internal/generator.d \ + std/regex/internal/ir.d std/regex/internal/kickstart.d \ + std/regex/internal/parser.d std/regex/internal/tests.d \ + std/regex/internal/thompson.d std/regex/package.d std/signals.d \ + std/socket.d std/stdint.d std/stdio.d std/string.d std/system.d \ + std/traits.d std/typecons.d std/typetuple.d std/uni.d std/uri.d \ + std/utf.d std/uuid.d std/variant.d std/windows/charset.d \ + std/windows/registry.d std/windows/syserror.d std/xml.d std/zip.d \ + std/zlib.d + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .d .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(top_srcdir)/d_rules.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign --ignore-deps src/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign --ignore-deps src/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; +$(top_srcdir)/d_rules.am: + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +libgphobos.spec: $(top_builddir)/config.status $(srcdir)/libgphobos.spec.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ + +clean-checkLTLIBRARIES: + -test -z "$(check_LTLIBRARIES)" || rm -f $(check_LTLIBRARIES) + @list='$(check_LTLIBRARIES)'; for p in $$list; do \ + dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \ + test "$$dir" != "$$p" || dir=.; \ + echo "rm -f \"$${dir}/so_locations\""; \ + rm -f "$${dir}/so_locations"; \ + done +install-toolexeclibLTLIBRARIES: $(toolexeclib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(toolexeclib_LTLIBRARIES)'; test -n "$(toolexeclibdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(toolexeclibdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(toolexeclibdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(toolexeclibdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(toolexeclibdir)"; \ + } + +uninstall-toolexeclibLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(toolexeclib_LTLIBRARIES)'; test -n "$(toolexeclibdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(toolexeclibdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(toolexeclibdir)/$$f"; \ + done + +clean-toolexeclibLTLIBRARIES: + -test -z "$(toolexeclib_LTLIBRARIES)" || rm -f $(toolexeclib_LTLIBRARIES) + @list='$(toolexeclib_LTLIBRARIES)'; for p in $$list; do \ + dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \ + test "$$dir" != "$$p" || dir=.; \ + echo "rm -f \"$${dir}/so_locations\""; \ + rm -f "$${dir}/so_locations"; \ + done +etc/c/$(am__dirstamp): + @$(MKDIR_P) etc/c + @: > etc/c/$(am__dirstamp) +etc/c/curl.lo: etc/c/$(am__dirstamp) +etc/c/sqlite3.lo: etc/c/$(am__dirstamp) +etc/c/zlib.lo: etc/c/$(am__dirstamp) +std/algorithm/$(am__dirstamp): + @$(MKDIR_P) std/algorithm + @: > std/algorithm/$(am__dirstamp) +std/algorithm/comparison.lo: std/algorithm/$(am__dirstamp) +std/algorithm/internal.lo: std/algorithm/$(am__dirstamp) +std/algorithm/iteration.lo: std/algorithm/$(am__dirstamp) +std/algorithm/mutation.lo: std/algorithm/$(am__dirstamp) +std/algorithm/package.lo: std/algorithm/$(am__dirstamp) +std/algorithm/searching.lo: std/algorithm/$(am__dirstamp) +std/algorithm/setops.lo: std/algorithm/$(am__dirstamp) +std/algorithm/sorting.lo: std/algorithm/$(am__dirstamp) +std/$(am__dirstamp): + @$(MKDIR_P) std + @: > std/$(am__dirstamp) +std/array.lo: std/$(am__dirstamp) +std/ascii.lo: std/$(am__dirstamp) +std/base64.lo: std/$(am__dirstamp) +std/bigint.lo: std/$(am__dirstamp) +std/bitmanip.lo: std/$(am__dirstamp) +std/compiler.lo: std/$(am__dirstamp) +std/complex.lo: std/$(am__dirstamp) +std/concurrency.lo: std/$(am__dirstamp) +std/container/$(am__dirstamp): + @$(MKDIR_P) std/container + @: > std/container/$(am__dirstamp) +std/container/array.lo: std/container/$(am__dirstamp) +std/container/binaryheap.lo: std/container/$(am__dirstamp) +std/container/dlist.lo: std/container/$(am__dirstamp) +std/container/package.lo: std/container/$(am__dirstamp) +std/container/rbtree.lo: std/container/$(am__dirstamp) +std/container/slist.lo: std/container/$(am__dirstamp) +std/container/util.lo: std/container/$(am__dirstamp) +std/conv.lo: std/$(am__dirstamp) +std/csv.lo: std/$(am__dirstamp) +std/datetime/$(am__dirstamp): + @$(MKDIR_P) std/datetime + @: > std/datetime/$(am__dirstamp) +std/datetime/date.lo: std/datetime/$(am__dirstamp) +std/datetime/interval.lo: std/datetime/$(am__dirstamp) +std/datetime/package.lo: std/datetime/$(am__dirstamp) +std/datetime/stopwatch.lo: std/datetime/$(am__dirstamp) +std/datetime/systime.lo: std/datetime/$(am__dirstamp) +std/datetime/timezone.lo: std/datetime/$(am__dirstamp) +std/demangle.lo: std/$(am__dirstamp) +std/digest/$(am__dirstamp): + @$(MKDIR_P) std/digest + @: > std/digest/$(am__dirstamp) +std/digest/crc.lo: std/digest/$(am__dirstamp) +std/digest/digest.lo: std/digest/$(am__dirstamp) +std/digest/hmac.lo: std/digest/$(am__dirstamp) +std/digest/md.lo: std/digest/$(am__dirstamp) +std/digest/murmurhash.lo: std/digest/$(am__dirstamp) +std/digest/package.lo: std/digest/$(am__dirstamp) +std/digest/ripemd.lo: std/digest/$(am__dirstamp) +std/digest/sha.lo: std/digest/$(am__dirstamp) +std/encoding.lo: std/$(am__dirstamp) +std/exception.lo: std/$(am__dirstamp) +std/experimental/allocator/building_blocks/$(am__dirstamp): + @$(MKDIR_P) std/experimental/allocator/building_blocks + @: > std/experimental/allocator/building_blocks/$(am__dirstamp) +std/experimental/allocator/building_blocks/affix_allocator.lo: \ + std/experimental/allocator/building_blocks/$(am__dirstamp) +std/experimental/allocator/building_blocks/allocator_list.lo: \ + std/experimental/allocator/building_blocks/$(am__dirstamp) +std/experimental/allocator/building_blocks/bitmapped_block.lo: \ + std/experimental/allocator/building_blocks/$(am__dirstamp) +std/experimental/allocator/building_blocks/bucketizer.lo: \ + std/experimental/allocator/building_blocks/$(am__dirstamp) +std/experimental/allocator/building_blocks/fallback_allocator.lo: \ + std/experimental/allocator/building_blocks/$(am__dirstamp) +std/experimental/allocator/building_blocks/free_list.lo: \ + std/experimental/allocator/building_blocks/$(am__dirstamp) +std/experimental/allocator/building_blocks/free_tree.lo: \ + std/experimental/allocator/building_blocks/$(am__dirstamp) +std/experimental/allocator/building_blocks/kernighan_ritchie.lo: \ + std/experimental/allocator/building_blocks/$(am__dirstamp) +std/experimental/allocator/building_blocks/null_allocator.lo: \ + std/experimental/allocator/building_blocks/$(am__dirstamp) +std/experimental/allocator/building_blocks/package.lo: \ + std/experimental/allocator/building_blocks/$(am__dirstamp) +std/experimental/allocator/building_blocks/quantizer.lo: \ + std/experimental/allocator/building_blocks/$(am__dirstamp) +std/experimental/allocator/building_blocks/region.lo: \ + std/experimental/allocator/building_blocks/$(am__dirstamp) +std/experimental/allocator/building_blocks/scoped_allocator.lo: \ + std/experimental/allocator/building_blocks/$(am__dirstamp) +std/experimental/allocator/building_blocks/segregator.lo: \ + std/experimental/allocator/building_blocks/$(am__dirstamp) +std/experimental/allocator/building_blocks/stats_collector.lo: \ + std/experimental/allocator/building_blocks/$(am__dirstamp) +std/experimental/allocator/$(am__dirstamp): + @$(MKDIR_P) std/experimental/allocator + @: > std/experimental/allocator/$(am__dirstamp) +std/experimental/allocator/common.lo: \ + std/experimental/allocator/$(am__dirstamp) +std/experimental/allocator/gc_allocator.lo: \ + std/experimental/allocator/$(am__dirstamp) +std/experimental/allocator/mallocator.lo: \ + std/experimental/allocator/$(am__dirstamp) +std/experimental/allocator/mmap_allocator.lo: \ + std/experimental/allocator/$(am__dirstamp) +std/experimental/allocator/package.lo: \ + std/experimental/allocator/$(am__dirstamp) +std/experimental/allocator/showcase.lo: \ + std/experimental/allocator/$(am__dirstamp) +std/experimental/allocator/typed.lo: \ + std/experimental/allocator/$(am__dirstamp) +std/experimental/$(am__dirstamp): + @$(MKDIR_P) std/experimental + @: > std/experimental/$(am__dirstamp) +std/experimental/checkedint.lo: std/experimental/$(am__dirstamp) +std/experimental/logger/$(am__dirstamp): + @$(MKDIR_P) std/experimental/logger + @: > std/experimental/logger/$(am__dirstamp) +std/experimental/logger/core.lo: \ + std/experimental/logger/$(am__dirstamp) +std/experimental/logger/filelogger.lo: \ + std/experimental/logger/$(am__dirstamp) +std/experimental/logger/multilogger.lo: \ + std/experimental/logger/$(am__dirstamp) +std/experimental/logger/nulllogger.lo: \ + std/experimental/logger/$(am__dirstamp) +std/experimental/logger/package.lo: \ + std/experimental/logger/$(am__dirstamp) +std/experimental/typecons.lo: std/experimental/$(am__dirstamp) +std/file.lo: std/$(am__dirstamp) +std/format.lo: std/$(am__dirstamp) +std/functional.lo: std/$(am__dirstamp) +std/getopt.lo: std/$(am__dirstamp) +std/internal/$(am__dirstamp): + @$(MKDIR_P) std/internal + @: > std/internal/$(am__dirstamp) +std/internal/cstring.lo: std/internal/$(am__dirstamp) +std/internal/digest/$(am__dirstamp): + @$(MKDIR_P) std/internal/digest + @: > std/internal/digest/$(am__dirstamp) +std/internal/digest/sha_SSSE3.lo: std/internal/digest/$(am__dirstamp) +std/internal/math/$(am__dirstamp): + @$(MKDIR_P) std/internal/math + @: > std/internal/math/$(am__dirstamp) +std/internal/math/biguintcore.lo: std/internal/math/$(am__dirstamp) +std/internal/math/biguintnoasm.lo: std/internal/math/$(am__dirstamp) +std/internal/math/biguintx86.lo: std/internal/math/$(am__dirstamp) +std/internal/math/errorfunction.lo: std/internal/math/$(am__dirstamp) +std/internal/math/gammafunction.lo: std/internal/math/$(am__dirstamp) +std/internal/scopebuffer.lo: std/internal/$(am__dirstamp) +std/internal/test/$(am__dirstamp): + @$(MKDIR_P) std/internal/test + @: > std/internal/test/$(am__dirstamp) +std/internal/test/dummyrange.lo: std/internal/test/$(am__dirstamp) +std/internal/test/range.lo: std/internal/test/$(am__dirstamp) +std/internal/test/uda.lo: std/internal/test/$(am__dirstamp) +std/internal/unicode_comp.lo: std/internal/$(am__dirstamp) +std/internal/unicode_decomp.lo: std/internal/$(am__dirstamp) +std/internal/unicode_grapheme.lo: std/internal/$(am__dirstamp) +std/internal/unicode_norm.lo: std/internal/$(am__dirstamp) +std/internal/unicode_tables.lo: std/internal/$(am__dirstamp) +std/internal/windows/$(am__dirstamp): + @$(MKDIR_P) std/internal/windows + @: > std/internal/windows/$(am__dirstamp) +std/internal/windows/advapi32.lo: \ + std/internal/windows/$(am__dirstamp) +std/json.lo: std/$(am__dirstamp) +std/math.lo: std/$(am__dirstamp) +std/mathspecial.lo: std/$(am__dirstamp) +std/meta.lo: std/$(am__dirstamp) +std/mmfile.lo: std/$(am__dirstamp) +std/net/$(am__dirstamp): + @$(MKDIR_P) std/net + @: > std/net/$(am__dirstamp) +std/net/curl.lo: std/net/$(am__dirstamp) +std/net/isemail.lo: std/net/$(am__dirstamp) +std/numeric.lo: std/$(am__dirstamp) +std/outbuffer.lo: std/$(am__dirstamp) +std/parallelism.lo: std/$(am__dirstamp) +std/path.lo: std/$(am__dirstamp) +std/process.lo: std/$(am__dirstamp) +std/random.lo: std/$(am__dirstamp) +std/range/$(am__dirstamp): + @$(MKDIR_P) std/range + @: > std/range/$(am__dirstamp) +std/range/interfaces.lo: std/range/$(am__dirstamp) +std/range/package.lo: std/range/$(am__dirstamp) +std/range/primitives.lo: std/range/$(am__dirstamp) +std/regex/internal/$(am__dirstamp): + @$(MKDIR_P) std/regex/internal + @: > std/regex/internal/$(am__dirstamp) +std/regex/internal/backtracking.lo: \ + std/regex/internal/$(am__dirstamp) +std/regex/internal/generator.lo: std/regex/internal/$(am__dirstamp) +std/regex/internal/ir.lo: std/regex/internal/$(am__dirstamp) +std/regex/internal/kickstart.lo: std/regex/internal/$(am__dirstamp) +std/regex/internal/parser.lo: std/regex/internal/$(am__dirstamp) +std/regex/internal/tests.lo: std/regex/internal/$(am__dirstamp) +std/regex/internal/thompson.lo: std/regex/internal/$(am__dirstamp) +std/regex/$(am__dirstamp): + @$(MKDIR_P) std/regex + @: > std/regex/$(am__dirstamp) +std/regex/package.lo: std/regex/$(am__dirstamp) +std/signals.lo: std/$(am__dirstamp) +std/socket.lo: std/$(am__dirstamp) +std/stdint.lo: std/$(am__dirstamp) +std/stdio.lo: std/$(am__dirstamp) +std/string.lo: std/$(am__dirstamp) +std/system.lo: std/$(am__dirstamp) +std/traits.lo: std/$(am__dirstamp) +std/typecons.lo: std/$(am__dirstamp) +std/typetuple.lo: std/$(am__dirstamp) +std/uni.lo: std/$(am__dirstamp) +std/uri.lo: std/$(am__dirstamp) +std/utf.lo: std/$(am__dirstamp) +std/uuid.lo: std/$(am__dirstamp) +std/variant.lo: std/$(am__dirstamp) +std/windows/$(am__dirstamp): + @$(MKDIR_P) std/windows + @: > std/windows/$(am__dirstamp) +std/windows/charset.lo: std/windows/$(am__dirstamp) +std/windows/registry.lo: std/windows/$(am__dirstamp) +std/windows/syserror.lo: std/windows/$(am__dirstamp) +std/xml.lo: std/$(am__dirstamp) +std/zip.lo: std/$(am__dirstamp) +std/zlib.lo: std/$(am__dirstamp) +libgphobos.la: $(libgphobos_la_OBJECTS) $(libgphobos_la_DEPENDENCIES) $(EXTRA_libgphobos_la_DEPENDENCIES) + $(libgphobos_la_LINK) -rpath $(toolexeclibdir) $(libgphobos_la_OBJECTS) $(libgphobos_la_LIBADD) $(LIBS) +libgphobos_t.la: $(libgphobos_t_la_OBJECTS) $(libgphobos_t_la_DEPENDENCIES) $(EXTRA_libgphobos_t_la_DEPENDENCIES) + $(libgphobos_t_la_LINK) $(am_libgphobos_t_la_rpath) $(libgphobos_t_la_OBJECTS) $(libgphobos_t_la_LIBADD) $(LIBS) + +clean-checkPROGRAMS: + @list='$(check_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +../testsuite/$(am__dirstamp): + @$(MKDIR_P) ../testsuite + @: > ../testsuite/$(am__dirstamp) +../testsuite/test_runner.$(OBJEXT): ../testsuite/$(am__dirstamp) +unittest$(EXEEXT): $(unittest_OBJECTS) $(unittest_DEPENDENCIES) $(EXTRA_unittest_DEPENDENCIES) + @rm -f unittest$(EXEEXT) + $(unittest_LINK) $(unittest_OBJECTS) $(unittest_LDADD) $(LIBS) +unittest_static$(EXEEXT): $(unittest_static_OBJECTS) $(unittest_static_DEPENDENCIES) $(EXTRA_unittest_static_DEPENDENCIES) + @rm -f unittest_static$(EXEEXT) + $(unittest_static_LINK) $(unittest_static_OBJECTS) $(unittest_static_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + -rm -f ../testsuite/test_runner.$(OBJEXT) + -rm -f etc/c/curl.$(OBJEXT) + -rm -f etc/c/curl.lo + -rm -f etc/c/sqlite3.$(OBJEXT) + -rm -f etc/c/sqlite3.lo + -rm -f etc/c/zlib.$(OBJEXT) + -rm -f etc/c/zlib.lo + -rm -f std/algorithm/comparison.$(OBJEXT) + -rm -f std/algorithm/comparison.lo + -rm -f std/algorithm/internal.$(OBJEXT) + -rm -f std/algorithm/internal.lo + -rm -f std/algorithm/iteration.$(OBJEXT) + -rm -f std/algorithm/iteration.lo + -rm -f std/algorithm/mutation.$(OBJEXT) + -rm -f std/algorithm/mutation.lo + -rm -f std/algorithm/package.$(OBJEXT) + -rm -f std/algorithm/package.lo + -rm -f std/algorithm/searching.$(OBJEXT) + -rm -f std/algorithm/searching.lo + -rm -f std/algorithm/setops.$(OBJEXT) + -rm -f std/algorithm/setops.lo + -rm -f std/algorithm/sorting.$(OBJEXT) + -rm -f std/algorithm/sorting.lo + -rm -f std/array.$(OBJEXT) + -rm -f std/array.lo + -rm -f std/ascii.$(OBJEXT) + -rm -f std/ascii.lo + -rm -f std/base64.$(OBJEXT) + -rm -f std/base64.lo + -rm -f std/bigint.$(OBJEXT) + -rm -f std/bigint.lo + -rm -f std/bitmanip.$(OBJEXT) + -rm -f std/bitmanip.lo + -rm -f std/compiler.$(OBJEXT) + -rm -f std/compiler.lo + -rm -f std/complex.$(OBJEXT) + -rm -f std/complex.lo + -rm -f std/concurrency.$(OBJEXT) + -rm -f std/concurrency.lo + -rm -f std/container/array.$(OBJEXT) + -rm -f std/container/array.lo + -rm -f std/container/binaryheap.$(OBJEXT) + -rm -f std/container/binaryheap.lo + -rm -f std/container/dlist.$(OBJEXT) + -rm -f std/container/dlist.lo + -rm -f std/container/package.$(OBJEXT) + -rm -f std/container/package.lo + -rm -f std/container/rbtree.$(OBJEXT) + -rm -f std/container/rbtree.lo + -rm -f std/container/slist.$(OBJEXT) + -rm -f std/container/slist.lo + -rm -f std/container/util.$(OBJEXT) + -rm -f std/container/util.lo + -rm -f std/conv.$(OBJEXT) + -rm -f std/conv.lo + -rm -f std/csv.$(OBJEXT) + -rm -f std/csv.lo + -rm -f std/datetime/date.$(OBJEXT) + -rm -f std/datetime/date.lo + -rm -f std/datetime/interval.$(OBJEXT) + -rm -f std/datetime/interval.lo + -rm -f std/datetime/package.$(OBJEXT) + -rm -f std/datetime/package.lo + -rm -f std/datetime/stopwatch.$(OBJEXT) + -rm -f std/datetime/stopwatch.lo + -rm -f std/datetime/systime.$(OBJEXT) + -rm -f std/datetime/systime.lo + -rm -f std/datetime/timezone.$(OBJEXT) + -rm -f std/datetime/timezone.lo + -rm -f std/demangle.$(OBJEXT) + -rm -f std/demangle.lo + -rm -f std/digest/crc.$(OBJEXT) + -rm -f std/digest/crc.lo + -rm -f std/digest/digest.$(OBJEXT) + -rm -f std/digest/digest.lo + -rm -f std/digest/hmac.$(OBJEXT) + -rm -f std/digest/hmac.lo + -rm -f std/digest/md.$(OBJEXT) + -rm -f std/digest/md.lo + -rm -f std/digest/murmurhash.$(OBJEXT) + -rm -f std/digest/murmurhash.lo + -rm -f std/digest/package.$(OBJEXT) + -rm -f std/digest/package.lo + -rm -f std/digest/ripemd.$(OBJEXT) + -rm -f std/digest/ripemd.lo + -rm -f std/digest/sha.$(OBJEXT) + -rm -f std/digest/sha.lo + -rm -f std/encoding.$(OBJEXT) + -rm -f std/encoding.lo + -rm -f std/exception.$(OBJEXT) + -rm -f std/exception.lo + -rm -f std/experimental/allocator/building_blocks/affix_allocator.$(OBJEXT) + -rm -f std/experimental/allocator/building_blocks/affix_allocator.lo + -rm -f std/experimental/allocator/building_blocks/allocator_list.$(OBJEXT) + -rm -f std/experimental/allocator/building_blocks/allocator_list.lo + -rm -f std/experimental/allocator/building_blocks/bitmapped_block.$(OBJEXT) + -rm -f std/experimental/allocator/building_blocks/bitmapped_block.lo + -rm -f std/experimental/allocator/building_blocks/bucketizer.$(OBJEXT) + -rm -f std/experimental/allocator/building_blocks/bucketizer.lo + -rm -f std/experimental/allocator/building_blocks/fallback_allocator.$(OBJEXT) + -rm -f std/experimental/allocator/building_blocks/fallback_allocator.lo + -rm -f std/experimental/allocator/building_blocks/free_list.$(OBJEXT) + -rm -f std/experimental/allocator/building_blocks/free_list.lo + -rm -f std/experimental/allocator/building_blocks/free_tree.$(OBJEXT) + -rm -f std/experimental/allocator/building_blocks/free_tree.lo + -rm -f std/experimental/allocator/building_blocks/kernighan_ritchie.$(OBJEXT) + -rm -f std/experimental/allocator/building_blocks/kernighan_ritchie.lo + -rm -f std/experimental/allocator/building_blocks/null_allocator.$(OBJEXT) + -rm -f std/experimental/allocator/building_blocks/null_allocator.lo + -rm -f std/experimental/allocator/building_blocks/package.$(OBJEXT) + -rm -f std/experimental/allocator/building_blocks/package.lo + -rm -f std/experimental/allocator/building_blocks/quantizer.$(OBJEXT) + -rm -f std/experimental/allocator/building_blocks/quantizer.lo + -rm -f std/experimental/allocator/building_blocks/region.$(OBJEXT) + -rm -f std/experimental/allocator/building_blocks/region.lo + -rm -f std/experimental/allocator/building_blocks/scoped_allocator.$(OBJEXT) + -rm -f std/experimental/allocator/building_blocks/scoped_allocator.lo + -rm -f std/experimental/allocator/building_blocks/segregator.$(OBJEXT) + -rm -f std/experimental/allocator/building_blocks/segregator.lo + -rm -f std/experimental/allocator/building_blocks/stats_collector.$(OBJEXT) + -rm -f std/experimental/allocator/building_blocks/stats_collector.lo + -rm -f std/experimental/allocator/common.$(OBJEXT) + -rm -f std/experimental/allocator/common.lo + -rm -f std/experimental/allocator/gc_allocator.$(OBJEXT) + -rm -f std/experimental/allocator/gc_allocator.lo + -rm -f std/experimental/allocator/mallocator.$(OBJEXT) + -rm -f std/experimental/allocator/mallocator.lo + -rm -f std/experimental/allocator/mmap_allocator.$(OBJEXT) + -rm -f std/experimental/allocator/mmap_allocator.lo + -rm -f std/experimental/allocator/package.$(OBJEXT) + -rm -f std/experimental/allocator/package.lo + -rm -f std/experimental/allocator/showcase.$(OBJEXT) + -rm -f std/experimental/allocator/showcase.lo + -rm -f std/experimental/allocator/typed.$(OBJEXT) + -rm -f std/experimental/allocator/typed.lo + -rm -f std/experimental/checkedint.$(OBJEXT) + -rm -f std/experimental/checkedint.lo + -rm -f std/experimental/logger/core.$(OBJEXT) + -rm -f std/experimental/logger/core.lo + -rm -f std/experimental/logger/filelogger.$(OBJEXT) + -rm -f std/experimental/logger/filelogger.lo + -rm -f std/experimental/logger/multilogger.$(OBJEXT) + -rm -f std/experimental/logger/multilogger.lo + -rm -f std/experimental/logger/nulllogger.$(OBJEXT) + -rm -f std/experimental/logger/nulllogger.lo + -rm -f std/experimental/logger/package.$(OBJEXT) + -rm -f std/experimental/logger/package.lo + -rm -f std/experimental/typecons.$(OBJEXT) + -rm -f std/experimental/typecons.lo + -rm -f std/file.$(OBJEXT) + -rm -f std/file.lo + -rm -f std/format.$(OBJEXT) + -rm -f std/format.lo + -rm -f std/functional.$(OBJEXT) + -rm -f std/functional.lo + -rm -f std/getopt.$(OBJEXT) + -rm -f std/getopt.lo + -rm -f std/internal/cstring.$(OBJEXT) + -rm -f std/internal/cstring.lo + -rm -f std/internal/digest/sha_SSSE3.$(OBJEXT) + -rm -f std/internal/digest/sha_SSSE3.lo + -rm -f std/internal/math/biguintcore.$(OBJEXT) + -rm -f std/internal/math/biguintcore.lo + -rm -f std/internal/math/biguintnoasm.$(OBJEXT) + -rm -f std/internal/math/biguintnoasm.lo + -rm -f std/internal/math/biguintx86.$(OBJEXT) + -rm -f std/internal/math/biguintx86.lo + -rm -f std/internal/math/errorfunction.$(OBJEXT) + -rm -f std/internal/math/errorfunction.lo + -rm -f std/internal/math/gammafunction.$(OBJEXT) + -rm -f std/internal/math/gammafunction.lo + -rm -f std/internal/scopebuffer.$(OBJEXT) + -rm -f std/internal/scopebuffer.lo + -rm -f std/internal/test/dummyrange.$(OBJEXT) + -rm -f std/internal/test/dummyrange.lo + -rm -f std/internal/test/range.$(OBJEXT) + -rm -f std/internal/test/range.lo + -rm -f std/internal/test/uda.$(OBJEXT) + -rm -f std/internal/test/uda.lo + -rm -f std/internal/unicode_comp.$(OBJEXT) + -rm -f std/internal/unicode_comp.lo + -rm -f std/internal/unicode_decomp.$(OBJEXT) + -rm -f std/internal/unicode_decomp.lo + -rm -f std/internal/unicode_grapheme.$(OBJEXT) + -rm -f std/internal/unicode_grapheme.lo + -rm -f std/internal/unicode_norm.$(OBJEXT) + -rm -f std/internal/unicode_norm.lo + -rm -f std/internal/unicode_tables.$(OBJEXT) + -rm -f std/internal/unicode_tables.lo + -rm -f std/internal/windows/advapi32.$(OBJEXT) + -rm -f std/internal/windows/advapi32.lo + -rm -f std/json.$(OBJEXT) + -rm -f std/json.lo + -rm -f std/math.$(OBJEXT) + -rm -f std/math.lo + -rm -f std/mathspecial.$(OBJEXT) + -rm -f std/mathspecial.lo + -rm -f std/meta.$(OBJEXT) + -rm -f std/meta.lo + -rm -f std/mmfile.$(OBJEXT) + -rm -f std/mmfile.lo + -rm -f std/net/curl.$(OBJEXT) + -rm -f std/net/curl.lo + -rm -f std/net/isemail.$(OBJEXT) + -rm -f std/net/isemail.lo + -rm -f std/numeric.$(OBJEXT) + -rm -f std/numeric.lo + -rm -f std/outbuffer.$(OBJEXT) + -rm -f std/outbuffer.lo + -rm -f std/parallelism.$(OBJEXT) + -rm -f std/parallelism.lo + -rm -f std/path.$(OBJEXT) + -rm -f std/path.lo + -rm -f std/process.$(OBJEXT) + -rm -f std/process.lo + -rm -f std/random.$(OBJEXT) + -rm -f std/random.lo + -rm -f std/range/interfaces.$(OBJEXT) + -rm -f std/range/interfaces.lo + -rm -f std/range/package.$(OBJEXT) + -rm -f std/range/package.lo + -rm -f std/range/primitives.$(OBJEXT) + -rm -f std/range/primitives.lo + -rm -f std/regex/internal/backtracking.$(OBJEXT) + -rm -f std/regex/internal/backtracking.lo + -rm -f std/regex/internal/generator.$(OBJEXT) + -rm -f std/regex/internal/generator.lo + -rm -f std/regex/internal/ir.$(OBJEXT) + -rm -f std/regex/internal/ir.lo + -rm -f std/regex/internal/kickstart.$(OBJEXT) + -rm -f std/regex/internal/kickstart.lo + -rm -f std/regex/internal/parser.$(OBJEXT) + -rm -f std/regex/internal/parser.lo + -rm -f std/regex/internal/tests.$(OBJEXT) + -rm -f std/regex/internal/tests.lo + -rm -f std/regex/internal/thompson.$(OBJEXT) + -rm -f std/regex/internal/thompson.lo + -rm -f std/regex/package.$(OBJEXT) + -rm -f std/regex/package.lo + -rm -f std/signals.$(OBJEXT) + -rm -f std/signals.lo + -rm -f std/socket.$(OBJEXT) + -rm -f std/socket.lo + -rm -f std/stdint.$(OBJEXT) + -rm -f std/stdint.lo + -rm -f std/stdio.$(OBJEXT) + -rm -f std/stdio.lo + -rm -f std/string.$(OBJEXT) + -rm -f std/string.lo + -rm -f std/system.$(OBJEXT) + -rm -f std/system.lo + -rm -f std/traits.$(OBJEXT) + -rm -f std/traits.lo + -rm -f std/typecons.$(OBJEXT) + -rm -f std/typecons.lo + -rm -f std/typetuple.$(OBJEXT) + -rm -f std/typetuple.lo + -rm -f std/uni.$(OBJEXT) + -rm -f std/uni.lo + -rm -f std/uri.$(OBJEXT) + -rm -f std/uri.lo + -rm -f std/utf.$(OBJEXT) + -rm -f std/utf.lo + -rm -f std/uuid.$(OBJEXT) + -rm -f std/uuid.lo + -rm -f std/variant.$(OBJEXT) + -rm -f std/variant.lo + -rm -f std/windows/charset.$(OBJEXT) + -rm -f std/windows/charset.lo + -rm -f std/windows/registry.$(OBJEXT) + -rm -f std/windows/registry.lo + -rm -f std/windows/syserror.$(OBJEXT) + -rm -f std/windows/syserror.lo + -rm -f std/xml.$(OBJEXT) + -rm -f std/xml.lo + -rm -f std/zip.$(OBJEXT) + -rm -f std/zip.lo + -rm -f std/zlib.$(OBJEXT) + -rm -f std/zlib.lo + +distclean-compile: + -rm -f *.tab.c + +.c.o: + $(COMPILE) -c $< + +.c.obj: + $(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: + $(LTCOMPILE) -c -o $@ $< + +libgphobos_la-adler32.lo: $(top_srcdir)/../zlib/adler32.c + $(LIBTOOL) --tag=CC $(libgphobos_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_la-adler32.lo `test -f '$(top_srcdir)/../zlib/adler32.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/adler32.c + +libgphobos_la-compress.lo: $(top_srcdir)/../zlib/compress.c + $(LIBTOOL) --tag=CC $(libgphobos_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_la-compress.lo `test -f '$(top_srcdir)/../zlib/compress.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/compress.c + +libgphobos_la-crc32.lo: $(top_srcdir)/../zlib/crc32.c + $(LIBTOOL) --tag=CC $(libgphobos_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_la-crc32.lo `test -f '$(top_srcdir)/../zlib/crc32.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/crc32.c + +libgphobos_la-deflate.lo: $(top_srcdir)/../zlib/deflate.c + $(LIBTOOL) --tag=CC $(libgphobos_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_la-deflate.lo `test -f '$(top_srcdir)/../zlib/deflate.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/deflate.c + +libgphobos_la-gzclose.lo: $(top_srcdir)/../zlib/gzclose.c + $(LIBTOOL) --tag=CC $(libgphobos_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_la-gzclose.lo `test -f '$(top_srcdir)/../zlib/gzclose.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/gzclose.c + +libgphobos_la-gzlib.lo: $(top_srcdir)/../zlib/gzlib.c + $(LIBTOOL) --tag=CC $(libgphobos_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_la-gzlib.lo `test -f '$(top_srcdir)/../zlib/gzlib.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/gzlib.c + +libgphobos_la-gzread.lo: $(top_srcdir)/../zlib/gzread.c + $(LIBTOOL) --tag=CC $(libgphobos_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_la-gzread.lo `test -f '$(top_srcdir)/../zlib/gzread.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/gzread.c + +libgphobos_la-gzwrite.lo: $(top_srcdir)/../zlib/gzwrite.c + $(LIBTOOL) --tag=CC $(libgphobos_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_la-gzwrite.lo `test -f '$(top_srcdir)/../zlib/gzwrite.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/gzwrite.c + +libgphobos_la-infback.lo: $(top_srcdir)/../zlib/infback.c + $(LIBTOOL) --tag=CC $(libgphobos_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_la-infback.lo `test -f '$(top_srcdir)/../zlib/infback.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/infback.c + +libgphobos_la-inffast.lo: $(top_srcdir)/../zlib/inffast.c + $(LIBTOOL) --tag=CC $(libgphobos_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_la-inffast.lo `test -f '$(top_srcdir)/../zlib/inffast.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/inffast.c + +libgphobos_la-inflate.lo: $(top_srcdir)/../zlib/inflate.c + $(LIBTOOL) --tag=CC $(libgphobos_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_la-inflate.lo `test -f '$(top_srcdir)/../zlib/inflate.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/inflate.c + +libgphobos_la-inftrees.lo: $(top_srcdir)/../zlib/inftrees.c + $(LIBTOOL) --tag=CC $(libgphobos_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_la-inftrees.lo `test -f '$(top_srcdir)/../zlib/inftrees.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/inftrees.c + +libgphobos_la-trees.lo: $(top_srcdir)/../zlib/trees.c + $(LIBTOOL) --tag=CC $(libgphobos_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_la-trees.lo `test -f '$(top_srcdir)/../zlib/trees.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/trees.c + +libgphobos_la-uncompr.lo: $(top_srcdir)/../zlib/uncompr.c + $(LIBTOOL) --tag=CC $(libgphobos_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_la-uncompr.lo `test -f '$(top_srcdir)/../zlib/uncompr.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/uncompr.c + +libgphobos_la-zutil.lo: $(top_srcdir)/../zlib/zutil.c + $(LIBTOOL) --tag=CC $(libgphobos_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_la-zutil.lo `test -f '$(top_srcdir)/../zlib/zutil.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/zutil.c + +libgphobos_t_la-adler32.lo: $(top_srcdir)/../zlib/adler32.c + $(LIBTOOL) --tag=CC $(libgphobos_t_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_t_la-adler32.lo `test -f '$(top_srcdir)/../zlib/adler32.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/adler32.c + +libgphobos_t_la-compress.lo: $(top_srcdir)/../zlib/compress.c + $(LIBTOOL) --tag=CC $(libgphobos_t_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_t_la-compress.lo `test -f '$(top_srcdir)/../zlib/compress.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/compress.c + +libgphobos_t_la-crc32.lo: $(top_srcdir)/../zlib/crc32.c + $(LIBTOOL) --tag=CC $(libgphobos_t_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_t_la-crc32.lo `test -f '$(top_srcdir)/../zlib/crc32.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/crc32.c + +libgphobos_t_la-deflate.lo: $(top_srcdir)/../zlib/deflate.c + $(LIBTOOL) --tag=CC $(libgphobos_t_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_t_la-deflate.lo `test -f '$(top_srcdir)/../zlib/deflate.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/deflate.c + +libgphobos_t_la-gzclose.lo: $(top_srcdir)/../zlib/gzclose.c + $(LIBTOOL) --tag=CC $(libgphobos_t_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_t_la-gzclose.lo `test -f '$(top_srcdir)/../zlib/gzclose.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/gzclose.c + +libgphobos_t_la-gzlib.lo: $(top_srcdir)/../zlib/gzlib.c + $(LIBTOOL) --tag=CC $(libgphobos_t_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_t_la-gzlib.lo `test -f '$(top_srcdir)/../zlib/gzlib.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/gzlib.c + +libgphobos_t_la-gzread.lo: $(top_srcdir)/../zlib/gzread.c + $(LIBTOOL) --tag=CC $(libgphobos_t_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_t_la-gzread.lo `test -f '$(top_srcdir)/../zlib/gzread.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/gzread.c + +libgphobos_t_la-gzwrite.lo: $(top_srcdir)/../zlib/gzwrite.c + $(LIBTOOL) --tag=CC $(libgphobos_t_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_t_la-gzwrite.lo `test -f '$(top_srcdir)/../zlib/gzwrite.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/gzwrite.c + +libgphobos_t_la-infback.lo: $(top_srcdir)/../zlib/infback.c + $(LIBTOOL) --tag=CC $(libgphobos_t_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_t_la-infback.lo `test -f '$(top_srcdir)/../zlib/infback.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/infback.c + +libgphobos_t_la-inffast.lo: $(top_srcdir)/../zlib/inffast.c + $(LIBTOOL) --tag=CC $(libgphobos_t_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_t_la-inffast.lo `test -f '$(top_srcdir)/../zlib/inffast.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/inffast.c + +libgphobos_t_la-inflate.lo: $(top_srcdir)/../zlib/inflate.c + $(LIBTOOL) --tag=CC $(libgphobos_t_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_t_la-inflate.lo `test -f '$(top_srcdir)/../zlib/inflate.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/inflate.c + +libgphobos_t_la-inftrees.lo: $(top_srcdir)/../zlib/inftrees.c + $(LIBTOOL) --tag=CC $(libgphobos_t_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_t_la-inftrees.lo `test -f '$(top_srcdir)/../zlib/inftrees.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/inftrees.c + +libgphobos_t_la-trees.lo: $(top_srcdir)/../zlib/trees.c + $(LIBTOOL) --tag=CC $(libgphobos_t_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_t_la-trees.lo `test -f '$(top_srcdir)/../zlib/trees.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/trees.c + +libgphobos_t_la-uncompr.lo: $(top_srcdir)/../zlib/uncompr.c + $(LIBTOOL) --tag=CC $(libgphobos_t_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_t_la-uncompr.lo `test -f '$(top_srcdir)/../zlib/uncompr.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/uncompr.c + +libgphobos_t_la-zutil.lo: $(top_srcdir)/../zlib/zutil.c + $(LIBTOOL) --tag=CC $(libgphobos_t_la_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgphobos_t_la-zutil.lo `test -f '$(top_srcdir)/../zlib/zutil.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/zutil.c + +adler32.o: $(top_srcdir)/../zlib/adler32.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o adler32.o `test -f '$(top_srcdir)/../zlib/adler32.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/adler32.c + +adler32.obj: $(top_srcdir)/../zlib/adler32.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o adler32.obj `if test -f '$(top_srcdir)/../zlib/adler32.c'; then $(CYGPATH_W) '$(top_srcdir)/../zlib/adler32.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/../zlib/adler32.c'; fi` + +compress.o: $(top_srcdir)/../zlib/compress.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o compress.o `test -f '$(top_srcdir)/../zlib/compress.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/compress.c + +compress.obj: $(top_srcdir)/../zlib/compress.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o compress.obj `if test -f '$(top_srcdir)/../zlib/compress.c'; then $(CYGPATH_W) '$(top_srcdir)/../zlib/compress.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/../zlib/compress.c'; fi` + +crc32.o: $(top_srcdir)/../zlib/crc32.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o crc32.o `test -f '$(top_srcdir)/../zlib/crc32.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/crc32.c + +crc32.obj: $(top_srcdir)/../zlib/crc32.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o crc32.obj `if test -f '$(top_srcdir)/../zlib/crc32.c'; then $(CYGPATH_W) '$(top_srcdir)/../zlib/crc32.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/../zlib/crc32.c'; fi` + +deflate.o: $(top_srcdir)/../zlib/deflate.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o deflate.o `test -f '$(top_srcdir)/../zlib/deflate.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/deflate.c + +deflate.obj: $(top_srcdir)/../zlib/deflate.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o deflate.obj `if test -f '$(top_srcdir)/../zlib/deflate.c'; then $(CYGPATH_W) '$(top_srcdir)/../zlib/deflate.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/../zlib/deflate.c'; fi` + +gzclose.o: $(top_srcdir)/../zlib/gzclose.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gzclose.o `test -f '$(top_srcdir)/../zlib/gzclose.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/gzclose.c + +gzclose.obj: $(top_srcdir)/../zlib/gzclose.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gzclose.obj `if test -f '$(top_srcdir)/../zlib/gzclose.c'; then $(CYGPATH_W) '$(top_srcdir)/../zlib/gzclose.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/../zlib/gzclose.c'; fi` + +gzlib.o: $(top_srcdir)/../zlib/gzlib.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gzlib.o `test -f '$(top_srcdir)/../zlib/gzlib.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/gzlib.c + +gzlib.obj: $(top_srcdir)/../zlib/gzlib.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gzlib.obj `if test -f '$(top_srcdir)/../zlib/gzlib.c'; then $(CYGPATH_W) '$(top_srcdir)/../zlib/gzlib.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/../zlib/gzlib.c'; fi` + +gzread.o: $(top_srcdir)/../zlib/gzread.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gzread.o `test -f '$(top_srcdir)/../zlib/gzread.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/gzread.c + +gzread.obj: $(top_srcdir)/../zlib/gzread.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gzread.obj `if test -f '$(top_srcdir)/../zlib/gzread.c'; then $(CYGPATH_W) '$(top_srcdir)/../zlib/gzread.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/../zlib/gzread.c'; fi` + +gzwrite.o: $(top_srcdir)/../zlib/gzwrite.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gzwrite.o `test -f '$(top_srcdir)/../zlib/gzwrite.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/gzwrite.c + +gzwrite.obj: $(top_srcdir)/../zlib/gzwrite.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gzwrite.obj `if test -f '$(top_srcdir)/../zlib/gzwrite.c'; then $(CYGPATH_W) '$(top_srcdir)/../zlib/gzwrite.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/../zlib/gzwrite.c'; fi` + +infback.o: $(top_srcdir)/../zlib/infback.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o infback.o `test -f '$(top_srcdir)/../zlib/infback.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/infback.c + +infback.obj: $(top_srcdir)/../zlib/infback.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o infback.obj `if test -f '$(top_srcdir)/../zlib/infback.c'; then $(CYGPATH_W) '$(top_srcdir)/../zlib/infback.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/../zlib/infback.c'; fi` + +inffast.o: $(top_srcdir)/../zlib/inffast.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o inffast.o `test -f '$(top_srcdir)/../zlib/inffast.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/inffast.c + +inffast.obj: $(top_srcdir)/../zlib/inffast.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o inffast.obj `if test -f '$(top_srcdir)/../zlib/inffast.c'; then $(CYGPATH_W) '$(top_srcdir)/../zlib/inffast.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/../zlib/inffast.c'; fi` + +inflate.o: $(top_srcdir)/../zlib/inflate.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o inflate.o `test -f '$(top_srcdir)/../zlib/inflate.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/inflate.c + +inflate.obj: $(top_srcdir)/../zlib/inflate.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o inflate.obj `if test -f '$(top_srcdir)/../zlib/inflate.c'; then $(CYGPATH_W) '$(top_srcdir)/../zlib/inflate.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/../zlib/inflate.c'; fi` + +inftrees.o: $(top_srcdir)/../zlib/inftrees.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o inftrees.o `test -f '$(top_srcdir)/../zlib/inftrees.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/inftrees.c + +inftrees.obj: $(top_srcdir)/../zlib/inftrees.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o inftrees.obj `if test -f '$(top_srcdir)/../zlib/inftrees.c'; then $(CYGPATH_W) '$(top_srcdir)/../zlib/inftrees.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/../zlib/inftrees.c'; fi` + +trees.o: $(top_srcdir)/../zlib/trees.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o trees.o `test -f '$(top_srcdir)/../zlib/trees.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/trees.c + +trees.obj: $(top_srcdir)/../zlib/trees.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o trees.obj `if test -f '$(top_srcdir)/../zlib/trees.c'; then $(CYGPATH_W) '$(top_srcdir)/../zlib/trees.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/../zlib/trees.c'; fi` + +uncompr.o: $(top_srcdir)/../zlib/uncompr.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o uncompr.o `test -f '$(top_srcdir)/../zlib/uncompr.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/uncompr.c + +uncompr.obj: $(top_srcdir)/../zlib/uncompr.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o uncompr.obj `if test -f '$(top_srcdir)/../zlib/uncompr.c'; then $(CYGPATH_W) '$(top_srcdir)/../zlib/uncompr.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/../zlib/uncompr.c'; fi` + +zutil.o: $(top_srcdir)/../zlib/zutil.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zutil.o `test -f '$(top_srcdir)/../zlib/zutil.c' || echo '$(srcdir)/'`$(top_srcdir)/../zlib/zutil.c + +zutil.obj: $(top_srcdir)/../zlib/zutil.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zutil.obj `if test -f '$(top_srcdir)/../zlib/zutil.c'; then $(CYGPATH_W) '$(top_srcdir)/../zlib/zutil.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/../zlib/zutil.c'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + -rm -rf etc/c/.libs etc/c/_libs + -rm -rf std/.libs std/_libs + -rm -rf std/algorithm/.libs std/algorithm/_libs + -rm -rf std/container/.libs std/container/_libs + -rm -rf std/datetime/.libs std/datetime/_libs + -rm -rf std/digest/.libs std/digest/_libs + -rm -rf std/experimental/.libs std/experimental/_libs + -rm -rf std/experimental/allocator/.libs std/experimental/allocator/_libs + -rm -rf std/experimental/allocator/building_blocks/.libs std/experimental/allocator/building_blocks/_libs + -rm -rf std/experimental/logger/.libs std/experimental/logger/_libs + -rm -rf std/internal/.libs std/internal/_libs + -rm -rf std/internal/digest/.libs std/internal/digest/_libs + -rm -rf std/internal/math/.libs std/internal/math/_libs + -rm -rf std/internal/test/.libs std/internal/test/_libs + -rm -rf std/internal/windows/.libs std/internal/windows/_libs + -rm -rf std/net/.libs std/net/_libs + -rm -rf std/range/.libs std/range/_libs + -rm -rf std/regex/.libs std/regex/_libs + -rm -rf std/regex/internal/.libs std/regex/internal/_libs + -rm -rf std/windows/.libs std/windows/_libs +install-toolexeclibDATA: $(toolexeclib_DATA) + @$(NORMAL_INSTALL) + @list='$(toolexeclib_DATA)'; test -n "$(toolexeclibdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(toolexeclibdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(toolexeclibdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(toolexeclibdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(toolexeclibdir)" || exit $$?; \ + done + +uninstall-toolexeclibDATA: + @$(NORMAL_UNINSTALL) + @list='$(toolexeclib_DATA)'; test -n "$(toolexeclibdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(toolexeclibdir)'; $(am__uninstall_files_from_dir) + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) $(check_LTLIBRARIES) $(check_PROGRAMS) +check: check-am +all-am: Makefile $(LTLIBRARIES) $(DATA) +installdirs: + for dir in "$(DESTDIR)$(toolexeclibdir)" "$(DESTDIR)$(toolexeclibdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + -rm -f ../testsuite/$(am__dirstamp) + -rm -f etc/c/$(am__dirstamp) + -rm -f std/$(am__dirstamp) + -rm -f std/algorithm/$(am__dirstamp) + -rm -f std/container/$(am__dirstamp) + -rm -f std/datetime/$(am__dirstamp) + -rm -f std/digest/$(am__dirstamp) + -rm -f std/experimental/$(am__dirstamp) + -rm -f std/experimental/allocator/$(am__dirstamp) + -rm -f std/experimental/allocator/building_blocks/$(am__dirstamp) + -rm -f std/experimental/logger/$(am__dirstamp) + -rm -f std/internal/$(am__dirstamp) + -rm -f std/internal/digest/$(am__dirstamp) + -rm -f std/internal/math/$(am__dirstamp) + -rm -f std/internal/test/$(am__dirstamp) + -rm -f std/internal/windows/$(am__dirstamp) + -rm -f std/net/$(am__dirstamp) + -rm -f std/range/$(am__dirstamp) + -rm -f std/regex/$(am__dirstamp) + -rm -f std/regex/internal/$(am__dirstamp) + -rm -f std/windows/$(am__dirstamp) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-checkLTLIBRARIES clean-checkPROGRAMS clean-generic \ + clean-libtool clean-local clean-toolexeclibLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-data-local + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-toolexeclibDATA \ + install-toolexeclibLTLIBRARIES + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-toolexeclibDATA \ + uninstall-toolexeclibLTLIBRARIES + +.MAKE: check-am install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean \ + clean-checkLTLIBRARIES clean-checkPROGRAMS clean-generic \ + clean-libtool clean-local clean-toolexeclibLTLIBRARIES ctags \ + distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags dvi dvi-am html html-am info \ + info-am install install-am install-data install-data-am \ + install-data-local install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip install-toolexeclibDATA \ + install-toolexeclibLTLIBRARIES installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags uninstall \ + uninstall-am uninstall-toolexeclibDATA \ + uninstall-toolexeclibLTLIBRARIES + + +# Compile D into normal object files +.d.o: + $(GDC) $(GDCFLAGS) $(MULTIFLAGS) $(D_EXTRA_DFLAGS) -c -o $@ $< + +# Compile D sources with libtool +.d.lo: + $(LTDCOMPILE) $(GDCFLAGS) $(MULTIFLAGS) $(D_EXTRA_DFLAGS) -c -o $@ $< + +# Unittest rules. Unfortunately we can't use _DFLAGS in automake without +# explicit D support, so use this hack. +# Compile D sources with libtool and test flags +%.t.lo : %.d + $(LTDCOMPILE) $(GDCFLAGSX) $(MULTIFLAGS) $(D_EXTRA_DFLAGS) -c -o $@ $< + +# Compile objects for static linking with test flags +# Automake breaks empty rules, so use the shell NOP : +%.t.o : %.t.lo + @: + +# Extra install and clean rules. +# This does not delete the .libs/.t.o files, but deleting +# the .lo is good enough to rerun the rules +clean-local: + rm -f $(PHOBOS_TEST_LOBJECTS) + rm -f $(PHOBOS_TEST_OBJECTS) + +# Handles generated files as well +install-data-local: + for file in $(ALL_PHOBOS_INSTALL_DSOURCES); do \ + if test -f $$file; then \ + $(INSTALL_HEADER) -D $$file $(DESTDIR)$(gdc_include_dir)/$$file ; \ + else \ + $(INSTALL_HEADER) -D $(srcdir)/$$file \ + $(DESTDIR)$(gdc_include_dir)/$$file ; \ + fi ; \ + done + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/libphobos/src/etc/c/curl.d b/libphobos/src/etc/c/curl.d new file mode 100644 index 0000000..2cc588c --- /dev/null +++ b/libphobos/src/etc/c/curl.d @@ -0,0 +1,2336 @@ +/** + This is an interface to the libcurl library. + + Converted to D from curl headers by $(LINK2 http://www.digitalmars.com/d/2.0/htod.html, htod) and + cleaned up by Jonas Drewsen (jdrewsen) + + Windows x86 note: + A DMD compatible libcurl static library can be downloaded from the dlang.org + $(LINK2 http://dlang.org/download.html, download page). +*/ + +/* ************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + */ + +/** + * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at $(LINK http://curl.haxx.se/docs/copyright.html). + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +module etc.c.curl; + +import core.stdc.config; +import core.stdc.time; +import std.socket; + +// linux +import core.sys.posix.sys.socket; + +// +// LICENSE FROM CURL HEADERS +// + +/** This is the global package copyright */ +enum LIBCURL_COPYRIGHT = "1996 - 2010 Daniel Stenberg, ."; + +/** This is the version number of the libcurl package from which this header + file origins: */ +enum LIBCURL_VERSION = "7.21.4"; + +/** The numeric version number is also available "in parts" by using these + constants */ +enum LIBCURL_VERSION_MAJOR = 7; +/// ditto +enum LIBCURL_VERSION_MINOR = 21; +/// ditto +enum LIBCURL_VERSION_PATCH = 4; + +/** This is the numeric version of the libcurl version number, meant for easier + parsing and comparions by programs. The LIBCURL_VERSION_NUM define will + always follow this syntax: + + 0xXXYYZZ + + Where XX, YY and ZZ are the main version, release and patch numbers in + hexadecimal (using 8 bits each). All three numbers are always represented + using two digits. 1.2 would appear as "0x010200" while version 9.11.7 + appears as "0x090b07". + + This 6-digit (24 bits) hexadecimal number does not show pre-release number, + and it is always a greater number in a more recent release. It makes + comparisons with greater than and less than work. +*/ + +enum LIBCURL_VERSION_NUM = 0x071504; + +/** + * This is the date and time when the full source package was created. The + * timestamp is not stored in git, as the timestamp is properly set in the + * tarballs by the maketgz script. + * + * The format of the date should follow this template: + * + * "Mon Feb 12 11:35:33 UTC 2007" + */ +enum LIBCURL_TIMESTAMP = "Thu Feb 17 12:19:40 UTC 2011"; + +/** Data type definition of curl_off_t. + * + * jdrewsen - Always 64bit signed and that is what long is in D. + * + * Comment below is from curlbuild.h: + * + * NOTE 2: + * + * For any given platform/compiler curl_off_t must be typedef'ed to a + * 64-bit wide signed integral data type. The width of this data type + * must remain constant and independent of any possible large file + * support settings. + * + * As an exception to the above, curl_off_t shall be typedef'ed to a + * 32-bit wide signed integral data type if there is no 64-bit type. + */ +alias curl_off_t = long; + +/// +alias CURL = void; + +/// jdrewsen - Get socket alias from std.socket +alias curl_socket_t = socket_t; + +/// jdrewsen - Would like to get socket error constant from std.socket by it is private atm. +version (Windows) +{ + import core.sys.windows.windows, core.sys.windows.winsock2; + enum CURL_SOCKET_BAD = SOCKET_ERROR; +} +version (Posix) enum CURL_SOCKET_BAD = -1; + +/// +extern (C) struct curl_httppost +{ + curl_httppost *next; /** next entry in the list */ + char *name; /** pointer to allocated name */ + c_long namelength; /** length of name length */ + char *contents; /** pointer to allocated data contents */ + c_long contentslength; /** length of contents field */ + char *buffer; /** pointer to allocated buffer contents */ + c_long bufferlength; /** length of buffer field */ + char *contenttype; /** Content-Type */ + curl_slist *contentheader; /** list of extra headers for this form */ + curl_httppost *more; /** if one field name has more than one + file, this link should link to following + files */ + c_long flags; /** as defined below */ + char *showfilename; /** The file name to show. If not set, the + actual file name will be used (if this + is a file part) */ + void *userp; /** custom pointer used for + HTTPPOST_CALLBACK posts */ +} + +enum HTTPPOST_FILENAME = 1; /** specified content is a file name */ +enum HTTPPOST_READFILE = 2; /** specified content is a file name */ +enum HTTPPOST_PTRNAME = 4; /** name is only stored pointer + do not free in formfree */ +enum HTTPPOST_PTRCONTENTS = 8; /** contents is only stored pointer + do not free in formfree */ +enum HTTPPOST_BUFFER = 16; /** upload file from buffer */ +enum HTTPPOST_PTRBUFFER = 32; /** upload file from pointer contents */ +enum HTTPPOST_CALLBACK = 64; /** upload file contents by using the + regular read callback to get the data + and pass the given pointer as custom + pointer */ + +/// +alias curl_progress_callback = int function(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow); + +/** Tests have proven that 20K is a very bad buffer size for uploads on + Windows, while 16K for some odd reason performed a lot better. + We do the ifndef check to allow this value to easier be changed at build + time for those who feel adventurous. The practical minimum is about + 400 bytes since libcurl uses a buffer of this size as a scratch area + (unrelated to network send operations). */ +enum CURL_MAX_WRITE_SIZE = 16_384; + +/** The only reason to have a max limit for this is to avoid the risk of a bad + server feeding libcurl with a never-ending header that will cause reallocs + infinitely */ +enum CURL_MAX_HTTP_HEADER = (100*1024); + + +/** This is a magic return code for the write callback that, when returned, + will signal libcurl to pause receiving on the current transfer. */ +enum CURL_WRITEFUNC_PAUSE = 0x10000001; + +/// +alias curl_write_callback = size_t function(char *buffer, size_t size, size_t nitems, void *outstream); + +/** enumeration of file types */ +enum CurlFileType { + file, /// + directory, /// + symlink, /// + device_block, /// + device_char, /// + namedpipe, /// + socket, /// + door, /// + unknown /** is possible only on Sun Solaris now */ +} + +/// +alias curlfiletype = int; + +/// +enum CurlFInfoFlagKnown { + filename = 1, /// + filetype = 2, /// + time = 4, /// + perm = 8, /// + uid = 16, /// + gid = 32, /// + size = 64, /// + hlinkcount = 128 /// +} + +/** Content of this structure depends on information which is known and is + achievable (e.g. by FTP LIST parsing). Please see the url_easy_setopt(3) man + page for callbacks returning this structure -- some fields are mandatory, + some others are optional. The FLAG field has special meaning. */ + + +/** If some of these fields is not NULL, it is a pointer to b_data. */ +extern (C) struct _N2 +{ + char *time; /// + char *perm; /// + char *user; /// + char *group; /// + char *target; /** pointer to the target filename of a symlink */ +} + +/** Content of this structure depends on information which is known and is + achievable (e.g. by FTP LIST parsing). Please see the url_easy_setopt(3) man + page for callbacks returning this structure -- some fields are mandatory, + some others are optional. The FLAG field has special meaning. */ +extern (C) struct curl_fileinfo +{ + char *filename; /// + curlfiletype filetype; /// + time_t time; /// + uint perm; /// + int uid; /// + int gid; /// + curl_off_t size; /// + c_long hardlinks; /// + _N2 strings; /// + uint flags; /// + char *b_data; /// + size_t b_size; /// + size_t b_used; /// +} + +/** return codes for CURLOPT_CHUNK_BGN_FUNCTION */ +enum CurlChunkBgnFunc { + ok = 0, /// + fail = 1, /** tell the lib to end the task */ + skip = 2 /** skip this chunk over */ +} + +/** if splitting of data transfer is enabled, this callback is called before + download of an individual chunk started. Note that parameter "remains" works + only for FTP wildcard downloading (for now), otherwise is not used */ +alias curl_chunk_bgn_callback = c_long function(void *transfer_info, void *ptr, int remains); + +/** return codes for CURLOPT_CHUNK_END_FUNCTION */ +enum CurlChunkEndFunc { + ok = 0, /// + fail = 1, /// +} +/** If splitting of data transfer is enabled this callback is called after + download of an individual chunk finished. + Note! After this callback was set then it have to be called FOR ALL chunks. + Even if downloading of this chunk was skipped in CHUNK_BGN_FUNC. + This is the reason why we don't need "transfer_info" parameter in this + callback and we are not interested in "remains" parameter too. */ +alias curl_chunk_end_callback = c_long function(void *ptr); + +/** return codes for FNMATCHFUNCTION */ +enum CurlFnMAtchFunc { + match = 0, /// + nomatch = 1, /// + fail = 2 /// +} + +/** callback type for wildcard downloading pattern matching. If the + string matches the pattern, return CURL_FNMATCHFUNC_MATCH value, etc. */ +alias curl_fnmatch_callback = int function(void *ptr, in char *pattern, in char *string); + +/// seek whence... +enum CurlSeekPos { + set, /// + current, /// + end /// +} + +/** These are the return codes for the seek callbacks */ +enum CurlSeek { + ok, /// + fail, /** fail the entire transfer */ + cantseek /** tell libcurl seeking can't be done, so + libcurl might try other means instead */ +} + +/// +alias curl_seek_callback = int function(void *instream, curl_off_t offset, int origin); + +/// +enum CurlReadFunc { + /** This is a return code for the read callback that, when returned, will + signal libcurl to immediately abort the current transfer. */ + abort = 0x10000000, + + /** This is a return code for the read callback that, when returned, + will const signal libcurl to pause sending data on the current + transfer. */ + pause = 0x10000001 +} + +/// +alias curl_read_callback = size_t function(char *buffer, size_t size, size_t nitems, void *instream); + +/// +enum CurlSockType { + ipcxn, /** socket created for a specific IP connection */ + last /** never use */ +} +/// +alias curlsocktype = int; + +/// +alias curl_sockopt_callback = int function(void *clientp, curl_socket_t curlfd, curlsocktype purpose); + +/** addrlen was a socklen_t type before 7.18.0 but it turned really + ugly and painful on the systems that lack this type */ +extern (C) struct curl_sockaddr +{ + int family; /// + int socktype; /// + int protocol; /// + uint addrlen; /** addrlen was a socklen_t type before 7.18.0 but it + turned really ugly and painful on the systems that + lack this type */ + sockaddr addr; /// +} + +/// +alias curl_opensocket_callback = curl_socket_t function(void *clientp, curlsocktype purpose, curl_sockaddr *address); + +/// +enum CurlIoError +{ + ok, /** I/O operation successful */ + unknowncmd, /** command was unknown to callback */ + failrestart, /** failed to restart the read */ + last /** never use */ +} +/// +alias curlioerr = int; + +/// +enum CurlIoCmd { + nop, /** command was unknown to callback */ + restartread, /** failed to restart the read */ + last, /** never use */ +} +/// +alias curliocmd = int; + +/// +alias curl_ioctl_callback = curlioerr function(CURL *handle, int cmd, void *clientp); + +/** + * The following typedef's are signatures of malloc, free, realloc, strdup and + * calloc respectively. Function pointers of these types can be passed to the + * curl_global_init_mem() function to set user defined memory management + * callback routines. + */ +alias curl_malloc_callback = void* function(size_t size); +/// ditto +alias curl_free_callback = void function(void *ptr); +/// ditto +alias curl_realloc_callback = void* function(void *ptr, size_t size); +/// ditto +alias curl_strdup_callback = char * function(in char *str); +/// ditto +alias curl_calloc_callback = void* function(size_t nmemb, size_t size); + +/** the kind of data that is passed to information_callback*/ +enum CurlCallbackInfo { + text, /// + header_in, /// + header_out, /// + data_in, /// + data_out, /// + ssl_data_in, /// + ssl_data_out, /// + end /// +} +/// +alias curl_infotype = int; + +/// +alias curl_debug_callback = + int function(CURL *handle, /** the handle/transfer this concerns */ + curl_infotype type, /** what kind of data */ + char *data, /** points to the data */ + size_t size, /** size of the data pointed to */ + void *userptr /** whatever the user please */ + ); + +/** All possible error codes from all sorts of curl functions. Future versions + may return other values, stay prepared. + + Always add new return codes last. Never *EVER* remove any. The return + codes must remain the same! + */ +enum CurlError +{ + ok, /// + unsupported_protocol, /** 1 */ + failed_init, /** 2 */ + url_malformat, /** 3 */ + not_built_in, /** 4 - [was obsoleted in August 2007 for + 7.17.0, reused in April 2011 for 7.21.5] */ + couldnt_resolve_proxy, /** 5 */ + couldnt_resolve_host, /** 6 */ + couldnt_connect, /** 7 */ + ftp_weird_server_reply, /** 8 */ + remote_access_denied, /** 9 a service was denied by the server + due to lack of access - when login fails + this is not returned. */ + obsolete10, /** 10 - NOT USED */ + ftp_weird_pass_reply, /** 11 */ + obsolete12, /** 12 - NOT USED */ + ftp_weird_pasv_reply, /** 13 */ + ftp_weird_227_format, /** 14 */ + ftp_cant_get_host, /** 15 */ + obsolete16, /** 16 - NOT USED */ + ftp_couldnt_set_type, /** 17 */ + partial_file, /** 18 */ + ftp_couldnt_retr_file, /** 19 */ + obsolete20, /** 20 - NOT USED */ + quote_error, /** 21 - quote command failure */ + http_returned_error, /** 22 */ + write_error, /** 23 */ + obsolete24, /** 24 - NOT USED */ + upload_failed, /** 25 - failed upload "command" */ + read_error, /** 26 - couldn't open/read from file */ + out_of_memory, /** 27 */ + /** Note: CURLE_OUT_OF_MEMORY may sometimes indicate a conversion error + instead of a memory allocation error if CURL_DOES_CONVERSIONS + is defined + */ + operation_timedout, /** 28 - the timeout time was reached */ + obsolete29, /** 29 - NOT USED */ + ftp_port_failed, /** 30 - FTP PORT operation failed */ + ftp_couldnt_use_rest, /** 31 - the REST command failed */ + obsolete32, /** 32 - NOT USED */ + range_error, /** 33 - RANGE "command" didn't work */ + http_post_error, /** 34 */ + ssl_connect_error, /** 35 - wrong when connecting with SSL */ + bad_download_resume, /** 36 - couldn't resume download */ + file_couldnt_read_file, /** 37 */ + ldap_cannot_bind, /** 38 */ + ldap_search_failed, /** 39 */ + obsolete40, /** 40 - NOT USED */ + function_not_found, /** 41 */ + aborted_by_callback, /** 42 */ + bad_function_argument, /** 43 */ + obsolete44, /** 44 - NOT USED */ + interface_failed, /** 45 - CURLOPT_INTERFACE failed */ + obsolete46, /** 46 - NOT USED */ + too_many_redirects, /** 47 - catch endless re-direct loops */ + unknown_option, /** 48 - User specified an unknown option */ + telnet_option_syntax, /** 49 - Malformed telnet option */ + obsolete50, /** 50 - NOT USED */ + peer_failed_verification, /** 51 - peer's certificate or fingerprint + wasn't verified fine */ + got_nothing, /** 52 - when this is a specific error */ + ssl_engine_notfound, /** 53 - SSL crypto engine not found */ + ssl_engine_setfailed, /** 54 - can not set SSL crypto engine as default */ + send_error, /** 55 - failed sending network data */ + recv_error, /** 56 - failure in receiving network data */ + obsolete57, /** 57 - NOT IN USE */ + ssl_certproblem, /** 58 - problem with the local certificate */ + ssl_cipher, /** 59 - couldn't use specified cipher */ + ssl_cacert, /** 60 - problem with the CA cert (path?) */ + bad_content_encoding, /** 61 - Unrecognized transfer encoding */ + ldap_invalid_url, /** 62 - Invalid LDAP URL */ + filesize_exceeded, /** 63 - Maximum file size exceeded */ + use_ssl_failed, /** 64 - Requested FTP SSL level failed */ + send_fail_rewind, /** 65 - Sending the data requires a rewind that failed */ + ssl_engine_initfailed, /** 66 - failed to initialise ENGINE */ + login_denied, /** 67 - user, password or similar was not accepted and we failed to login */ + tftp_notfound, /** 68 - file not found on server */ + tftp_perm, /** 69 - permission problem on server */ + remote_disk_full, /** 70 - out of disk space on server */ + tftp_illegal, /** 71 - Illegal TFTP operation */ + tftp_unknownid, /** 72 - Unknown transfer ID */ + remote_file_exists, /** 73 - File already exists */ + tftp_nosuchuser, /** 74 - No such user */ + conv_failed, /** 75 - conversion failed */ + conv_reqd, /** 76 - caller must register conversion + callbacks using curl_easy_setopt options + CURLOPT_CONV_FROM_NETWORK_FUNCTION, + CURLOPT_CONV_TO_NETWORK_FUNCTION, and + CURLOPT_CONV_FROM_UTF8_FUNCTION */ + ssl_cacert_badfile, /** 77 - could not load CACERT file, missing or wrong format */ + remote_file_not_found, /** 78 - remote file not found */ + ssh, /** 79 - error from the SSH layer, somewhat + generic so the error message will be of + interest when this has happened */ + ssl_shutdown_failed, /** 80 - Failed to shut down the SSL connection */ + again, /** 81 - socket is not ready for send/recv, + wait till it's ready and try again (Added + in 7.18.2) */ + ssl_crl_badfile, /** 82 - could not load CRL file, missing or wrong format (Added in 7.19.0) */ + ssl_issuer_error, /** 83 - Issuer check failed. (Added in 7.19.0) */ + ftp_pret_failed, /** 84 - a PRET command failed */ + rtsp_cseq_error, /** 85 - mismatch of RTSP CSeq numbers */ + rtsp_session_error, /** 86 - mismatch of RTSP Session Identifiers */ + ftp_bad_file_list, /** 87 - unable to parse FTP file list */ + chunk_failed, /** 88 - chunk callback reported error */ + curl_last /** never use! */ +} +/// +alias CURLcode = int; + +/** This prototype applies to all conversion callbacks */ +alias curl_conv_callback = CURLcode function(char *buffer, size_t length); + +/** actually an OpenSSL SSL_CTX */ +alias curl_ssl_ctx_callback = + CURLcode function(CURL *curl, /** easy handle */ + void *ssl_ctx, /** actually an OpenSSL SSL_CTX */ + void *userptr + ); + +/// +enum CurlProxy { + http, /** added in 7.10, new in 7.19.4 default is to use CONNECT HTTP/1.1 */ + http_1_0, /** added in 7.19.4, force to use CONNECT HTTP/1.0 */ + socks4 = 4, /** support added in 7.15.2, enum existed already in 7.10 */ + socks5 = 5, /** added in 7.10 */ + socks4a = 6, /** added in 7.18.0 */ + socks5_hostname =7 /** Use the SOCKS5 protocol but pass along the + host name rather than the IP address. added + in 7.18.0 */ +} +/// +alias curl_proxytype = int; + +/// +enum CurlAuth : long { + none = 0, + basic = 1, /** Basic (default) */ + digest = 2, /** Digest */ + gssnegotiate = 4, /** GSS-Negotiate */ + ntlm = 8, /** NTLM */ + digest_ie = 16, /** Digest with IE flavour */ + only = 2_147_483_648, /** used together with a single other + type to force no auth or just that + single type */ + any = -17, /* (~CURLAUTH_DIGEST_IE) */ /** all fine types set */ + anysafe = -18 /* (~(CURLAUTH_BASIC|CURLAUTH_DIGEST_IE)) */ /// +} + +/// +enum CurlSshAuth { + any = -1, /** all types supported by the server */ + none = 0, /** none allowed, silly but complete */ + publickey = 1, /** public/private key files */ + password = 2, /** password */ + host = 4, /** host key files */ + keyboard = 8, /** keyboard interactive */ + default_ = -1 // CURLSSH_AUTH_ANY; +} +/// +enum CURL_ERROR_SIZE = 256; +/** points to a zero-terminated string encoded with base64 + if len is zero, otherwise to the "raw" data */ +enum CurlKHType +{ + unknown, /// + rsa1, /// + rsa, /// + dss /// +} +/// +extern (C) struct curl_khkey +{ + const(char) *key; /** points to a zero-terminated string encoded with base64 + if len is zero, otherwise to the "raw" data */ + size_t len; /// + CurlKHType keytype; /// +} + +/** this is the set of return values expected from the curl_sshkeycallback + callback */ +enum CurlKHStat { + fine_add_to_file, /// + fine, /// + reject, /** reject the connection, return an error */ + defer, /** do not accept it, but we can't answer right now so + this causes a CURLE_DEFER error but otherwise the + connection will be left intact etc */ + last /** not for use, only a marker for last-in-list */ +} + +/** this is the set of status codes pass in to the callback */ +enum CurlKHMatch { + ok, /** match */ + mismatch, /** host found, key mismatch! */ + missing, /** no matching host/key found */ + last /** not for use, only a marker for last-in-list */ +} + +/// +alias curl_sshkeycallback = + int function(CURL *easy, /** easy handle */ + curl_khkey *knownkey, /** known */ + curl_khkey *foundkey, /** found */ + CurlKHMatch m, /** libcurl's view on the keys */ + void *clientp /** custom pointer passed from app */ + ); + +/** parameter for the CURLOPT_USE_SSL option */ +enum CurlUseSSL { + none, /** do not attempt to use SSL */ + tryssl, /** try using SSL, proceed anyway otherwise */ + control, /** SSL for the control connection or fail */ + all, /** SSL for all communication or fail */ + last /** not an option, never use */ +} +/// +alias curl_usessl = int; + +/** parameter for the CURLOPT_FTP_SSL_CCC option */ +enum CurlFtpSSL { + ccc_none, /** do not send CCC */ + ccc_passive, /** Let the server initiate the shutdown */ + ccc_active, /** Initiate the shutdown */ + ccc_last /** not an option, never use */ +} +/// +alias curl_ftpccc = int; + +/** parameter for the CURLOPT_FTPSSLAUTH option */ +enum CurlFtpAuth { + defaultauth, /** let libcurl decide */ + ssl, /** use "AUTH SSL" */ + tls, /** use "AUTH TLS" */ + last /** not an option, never use */ +} +/// +alias curl_ftpauth = int; + +/** parameter for the CURLOPT_FTP_CREATE_MISSING_DIRS option */ +enum CurlFtp { + create_dir_none, /** do NOT create missing dirs! */ + create_dir, /** (FTP/SFTP) if CWD fails, try MKD and then CWD again if MKD + succeeded, for SFTP this does similar magic */ + create_dir_retry, /** (FTP only) if CWD fails, try MKD and then CWD again even if MKD + failed! */ + create_dir_last /** not an option, never use */ +} +/// +alias curl_ftpcreatedir = int; + +/** parameter for the CURLOPT_FTP_FILEMETHOD option */ +enum CurlFtpMethod { + defaultmethod, /** let libcurl pick */ + multicwd, /** single CWD operation for each path part */ + nocwd, /** no CWD at all */ + singlecwd, /** one CWD to full dir, then work on file */ + last /** not an option, never use */ +} +/// +alias curl_ftpmethod = int; + +/** CURLPROTO_ defines are for the CURLOPT_*PROTOCOLS options */ +enum CurlProto { + http = 1, /// + https = 2, /// + ftp = 4, /// + ftps = 8, /// + scp = 16, /// + sftp = 32, /// + telnet = 64, /// + ldap = 128, /// + ldaps = 256, /// + dict = 512, /// + file = 1024, /// + tftp = 2048, /// + imap = 4096, /// + imaps = 8192, /// + pop3 = 16_384, /// + pop3s = 32_768, /// + smtp = 65_536, /// + smtps = 131_072, /// + rtsp = 262_144, /// + rtmp = 524_288, /// + rtmpt = 1_048_576, /// + rtmpe = 2_097_152, /// + rtmpte = 4_194_304, /// + rtmps = 8_388_608, /// + rtmpts = 16_777_216, /// + gopher = 33_554_432, /// + all = -1 /** enable everything */ +} + +/** long may be 32 or 64 bits, but we should never depend on anything else + but 32 */ +enum CURLOPTTYPE_LONG = 0; +/// ditto +enum CURLOPTTYPE_OBJECTPOINT = 10_000; +/// ditto +enum CURLOPTTYPE_FUNCTIONPOINT = 20_000; + +/// ditto +enum CURLOPTTYPE_OFF_T = 30_000; +/** name is uppercase CURLOPT_$(LT)name$(GT), + type is one of the defined CURLOPTTYPE_$(LT)type$(GT) + number is unique identifier */ + +/** The macro "##" is ISO C, we assume pre-ISO C doesn't support it. */ +alias LONG = CURLOPTTYPE_LONG; +/// ditto +alias OBJECTPOINT = CURLOPTTYPE_OBJECTPOINT; +/// ditto +alias FUNCTIONPOINT = CURLOPTTYPE_FUNCTIONPOINT; + +/// ditto +alias OFF_T = CURLOPTTYPE_OFF_T; + +/// +enum CurlOption { + /** This is the FILE * or void * the regular output should be written to. */ + file = 10_001, + /** The full URL to get/put */ + url, + /** Port number to connect to, if other than default. */ + port = 3, + /** Name of proxy to use. */ + proxy = 10_004, + /** "name:password" to use when fetching. */ + userpwd, + /** "name:password" to use with proxy. */ + proxyuserpwd, + /** Range to get, specified as an ASCII string. */ + range, + /** not used */ + + /** Specified file stream to upload from (use as input): */ + infile = 10_009, + /** Buffer to receive error messages in, must be at least CURL_ERROR_SIZE + * bytes big. If this is not used, error messages go to stderr instead: */ + errorbuffer, + /** Function that will be called to store the output (instead of fwrite). The + * parameters will use fwrite() syntax, make sure to follow them. */ + writefunction = 20_011, + /** Function that will be called to read the input (instead of fread). The + * parameters will use fread() syntax, make sure to follow them. */ + readfunction, + /** Time-out the read operation after this amount of seconds */ + timeout = 13, + /** If the CURLOPT_INFILE is used, this can be used to inform libcurl about + * how large the file being sent really is. That allows better error + * checking and better verifies that the upload was successful. -1 means + * unknown size. + * + * For large file support, there is also a _LARGE version of the key + * which takes an off_t type, allowing platforms with larger off_t + * sizes to handle larger files. See below for INFILESIZE_LARGE. + */ + infilesize, + /** POST static input fields. */ + postfields = 10_015, + /** Set the referrer page (needed by some CGIs) */ + referer, + /** Set the FTP PORT string (interface name, named or numerical IP address) + Use i.e '-' to use default address. */ + ftpport, + /** Set the User-Agent string (examined by some CGIs) */ + useragent, + /** If the download receives less than "low speed limit" bytes/second + * during "low speed time" seconds, the operations is aborted. + * You could i.e if you have a pretty high speed connection, abort if + * it is less than 2000 bytes/sec during 20 seconds. + */ + + /** Set the "low speed limit" */ + low_speed_limit = 19, + /** Set the "low speed time" */ + low_speed_time, + /** Set the continuation offset. + * + * Note there is also a _LARGE version of this key which uses + * off_t types, allowing for large file offsets on platforms which + * use larger-than-32-bit off_t's. Look below for RESUME_FROM_LARGE. + */ + resume_from, + /** Set cookie in request: */ + cookie = 10_022, + /** This points to a linked list of headers, struct curl_slist kind */ + httpheader, + /** This points to a linked list of post entries, struct curl_httppost */ + httppost, + /** name of the file keeping your private SSL-certificate */ + sslcert, + /** password for the SSL or SSH private key */ + keypasswd, + /** send TYPE parameter? */ + crlf = 27, + /** send linked-list of QUOTE commands */ + quote = 10_028, + /** send FILE * or void * to store headers to, if you use a callback it + is simply passed to the callback unmodified */ + writeheader, + /** point to a file to read the initial cookies from, also enables + "cookie awareness" */ + cookiefile = 10_031, + /** What version to specifically try to use. + See CURL_SSLVERSION defines below. */ + sslversion = 32, + /** What kind of HTTP time condition to use, see defines */ + timecondition, + /** Time to use with the above condition. Specified in number of seconds + since 1 Jan 1970 */ + timevalue, + /* 35 = OBSOLETE */ + + /** Custom request, for customizing the get command like + HTTP: DELETE, TRACE and others + FTP: to use a different list command + */ + customrequest = 10_036, + /** HTTP request, for odd commands like DELETE, TRACE and others */ + stderr, + /* 38 is not used */ + + /** send linked-list of post-transfer QUOTE commands */ + postquote = 10_039, + /** Pass a pointer to string of the output using full variable-replacement + as described elsewhere. */ + writeinfo, + verbose = 41, /** talk a lot */ + header, /** throw the header out too */ + noprogress, /** shut off the progress meter */ + nobody, /** use HEAD to get http document */ + failonerror, /** no output on http error codes >= 300 */ + upload, /** this is an upload */ + post, /** HTTP POST method */ + dirlistonly, /** return bare names when listing directories */ + append = 50, /** Append instead of overwrite on upload! */ + /** Specify whether to read the user+password from the .netrc or the URL. + * This must be one of the CURL_NETRC_* enums below. */ + netrc, + followlocation, /** use Location: Luke! */ + transfertext, /** transfer data in text/ASCII format */ + put, /** HTTP PUT */ + /* 55 = OBSOLETE */ + + /** Function that will be called instead of the internal progress display + * function. This function should be defined as the curl_progress_callback + * prototype defines. */ + progressfunction = 20_056, + /** Data passed to the progress callback */ + progressdata = 10_057, + /** We want the referrer field set automatically when following locations */ + autoreferer = 58, + /** Port of the proxy, can be set in the proxy string as well with: + "[host]:[port]" */ + proxyport, + /** size of the POST input data, if strlen() is not good to use */ + postfieldsize, + /** tunnel non-http operations through a HTTP proxy */ + httpproxytunnel, + /** Set the interface string to use as outgoing network interface */ + intrface = 10_062, + /** Set the krb4/5 security level, this also enables krb4/5 awareness. This + * is a string, 'clear', 'safe', 'confidential' or 'private'. If the string + * is set but doesn't match one of these, 'private' will be used. */ + krblevel, + /** Set if we should verify the peer in ssl handshake, set 1 to verify. */ + ssl_verifypeer = 64, + /** The CApath or CAfile used to validate the peer certificate + this option is used only if SSL_VERIFYPEER is true */ + cainfo = 10_065, + /* 66 = OBSOLETE */ + /* 67 = OBSOLETE */ + + /** Maximum number of http redirects to follow */ + maxredirs = 68, + /** Pass a long set to 1 to get the date of the requested document (if + possible)! Pass a zero to shut it off. */ + filetime, + /** This points to a linked list of telnet options */ + telnetoptions = 10_070, + /** Max amount of cached alive connections */ + maxconnects = 71, + /** What policy to use when closing connections when the cache is filled + up */ + closepolicy, + /* 73 = OBSOLETE */ + + /** Set to explicitly use a new connection for the upcoming transfer. + Do not use this unless you're absolutely sure of this, as it makes the + operation slower and is less friendly for the network. */ + fresh_connect = 74, + /** Set to explicitly forbid the upcoming transfer's connection to be re-used + when done. Do not use this unless you're absolutely sure of this, as it + makes the operation slower and is less friendly for the network. */ + forbid_reuse, + /** Set to a file name that contains random data for libcurl to use to + seed the random engine when doing SSL connects. */ + random_file = 10_076, + /** Set to the Entropy Gathering Daemon socket pathname */ + egdsocket, + /** Time-out connect operations after this amount of seconds, if connects + are OK within this time, then fine... This only aborts the connect + phase. [Only works on unix-style/SIGALRM operating systems] */ + connecttimeout = 78, + /** Function that will be called to store headers (instead of fwrite). The + * parameters will use fwrite() syntax, make sure to follow them. */ + headerfunction = 20_079, + /** Set this to force the HTTP request to get back to GET. Only really usable + if POST, PUT or a custom request have been used first. + */ + httpget = 80, + /** Set if we should verify the Common name from the peer certificate in ssl + * handshake, set 1 to check existence, 2 to ensure that it matches the + * provided hostname. */ + ssl_verifyhost, + /** Specify which file name to write all known cookies in after completed + operation. Set file name to "-" (dash) to make it go to stdout. */ + cookiejar = 10_082, + /** Specify which SSL ciphers to use */ + ssl_cipher_list, + /** Specify which HTTP version to use! This must be set to one of the + CURL_HTTP_VERSION* enums set below. */ + http_version = 84, + /** Specifically switch on or off the FTP engine's use of the EPSV command. By + default, that one will always be attempted before the more traditional + PASV command. */ + ftp_use_epsv, + /** type of the file keeping your SSL-certificate ("DER", "PEM", "ENG") */ + sslcerttype = 10_086, + /** name of the file keeping your private SSL-key */ + sslkey, + /** type of the file keeping your private SSL-key ("DER", "PEM", "ENG") */ + sslkeytype, + /** crypto engine for the SSL-sub system */ + sslengine, + /** set the crypto engine for the SSL-sub system as default + the param has no meaning... + */ + sslengine_default = 90, + /** Non-zero value means to use the global dns cache */ + dns_use_global_cache, + /** DNS cache timeout */ + dns_cache_timeout, + /** send linked-list of pre-transfer QUOTE commands */ + prequote = 10_093, + /** set the debug function */ + debugfunction = 20_094, + /** set the data for the debug function */ + debugdata = 10_095, + /** mark this as start of a cookie session */ + cookiesession = 96, + /** The CApath directory used to validate the peer certificate + this option is used only if SSL_VERIFYPEER is true */ + capath = 10_097, + /** Instruct libcurl to use a smaller receive buffer */ + buffersize = 98, + /** Instruct libcurl to not use any signal/alarm handlers, even when using + timeouts. This option is useful for multi-threaded applications. + See libcurl-the-guide for more background information. */ + nosignal, + /** Provide a CURLShare for mutexing non-ts data */ + share = 10_100, + /** indicates type of proxy. accepted values are CURLPROXY_HTTP (default), + CURLPROXY_SOCKS4, CURLPROXY_SOCKS4A and CURLPROXY_SOCKS5. */ + proxytype = 101, + /** Set the Accept-Encoding string. Use this to tell a server you would like + the response to be compressed. */ + encoding = 10_102, + /** Set pointer to private data */ + private_opt, + /** Set aliases for HTTP 200 in the HTTP Response header */ + http200aliases, + /** Continue to send authentication (user+password) when following locations, + even when hostname changed. This can potentially send off the name + and password to whatever host the server decides. */ + unrestricted_auth = 105, + /** Specifically switch on or off the FTP engine's use of the EPRT command ( it + also disables the LPRT attempt). By default, those ones will always be + attempted before the good old traditional PORT command. */ + ftp_use_eprt, + /** Set this to a bitmask value to enable the particular authentications + methods you like. Use this in combination with CURLOPT_USERPWD. + Note that setting multiple bits may cause extra network round-trips. */ + httpauth, + /** Set the ssl context callback function, currently only for OpenSSL ssl_ctx + in second argument. The function must be matching the + curl_ssl_ctx_callback proto. */ + ssl_ctx_function = 20_108, + /** Set the userdata for the ssl context callback function's third + argument */ + ssl_ctx_data = 10_109, + /** FTP Option that causes missing dirs to be created on the remote server. + In 7.19.4 we introduced the convenience enums for this option using the + CURLFTP_CREATE_DIR prefix. + */ + ftp_create_missing_dirs = 110, + /** Set this to a bitmask value to enable the particular authentications + methods you like. Use this in combination with CURLOPT_PROXYUSERPWD. + Note that setting multiple bits may cause extra network round-trips. */ + proxyauth, + /** FTP option that changes the timeout, in seconds, associated with + getting a response. This is different from transfer timeout time and + essentially places a demand on the FTP server to acknowledge commands + in a timely manner. */ + ftp_response_timeout, + /** Set this option to one of the CURL_IPRESOLVE_* defines (see below) to + tell libcurl to resolve names to those IP versions only. This only has + affect on systems with support for more than one, i.e IPv4 _and_ IPv6. */ + ipresolve, + /** Set this option to limit the size of a file that will be downloaded from + an HTTP or FTP server. + + Note there is also _LARGE version which adds large file support for + platforms which have larger off_t sizes. See MAXFILESIZE_LARGE below. */ + maxfilesize, + /** See the comment for INFILESIZE above, but in short, specifies + * the size of the file being uploaded. -1 means unknown. + */ + infilesize_large = 30_115, + /** Sets the continuation offset. There is also a LONG version of this; + * look above for RESUME_FROM. + */ + resume_from_large, + /** Sets the maximum size of data that will be downloaded from + * an HTTP or FTP server. See MAXFILESIZE above for the LONG version. + */ + maxfilesize_large, + /** Set this option to the file name of your .netrc file you want libcurl + to parse (using the CURLOPT_NETRC option). If not set, libcurl will do + a poor attempt to find the user's home directory and check for a .netrc + file in there. */ + netrc_file = 10_118, + /** Enable SSL/TLS for FTP, pick one of: + CURLFTPSSL_TRY - try using SSL, proceed anyway otherwise + CURLFTPSSL_CONTROL - SSL for the control connection or fail + CURLFTPSSL_ALL - SSL for all communication or fail + */ + use_ssl = 119, + /** The _LARGE version of the standard POSTFIELDSIZE option */ + postfieldsize_large = 30_120, + /** Enable/disable the TCP Nagle algorithm */ + tcp_nodelay = 121, + /* 122 OBSOLETE, used in 7.12.3. Gone in 7.13.0 */ + /* 123 OBSOLETE. Gone in 7.16.0 */ + /* 124 OBSOLETE, used in 7.12.3. Gone in 7.13.0 */ + /* 125 OBSOLETE, used in 7.12.3. Gone in 7.13.0 */ + /* 126 OBSOLETE, used in 7.12.3. Gone in 7.13.0 */ + /* 127 OBSOLETE. Gone in 7.16.0 */ + /* 128 OBSOLETE. Gone in 7.16.0 */ + + /** When FTP over SSL/TLS is selected (with CURLOPT_USE_SSL), this option + can be used to change libcurl's default action which is to first try + "AUTH SSL" and then "AUTH TLS" in this order, and proceed when a OK + response has been received. + + Available parameters are: + CURLFTPAUTH_DEFAULT - let libcurl decide + CURLFTPAUTH_SSL - try "AUTH SSL" first, then TLS + CURLFTPAUTH_TLS - try "AUTH TLS" first, then SSL + */ + ftpsslauth = 129, + ioctlfunction = 20_130, /// + ioctldata = 10_131, /// + /* 132 OBSOLETE. Gone in 7.16.0 */ + /* 133 OBSOLETE. Gone in 7.16.0 */ + + /** zero terminated string for pass on to the FTP server when asked for + "account" info */ + ftp_account = 10_134, + /** feed cookies into cookie engine */ + cookielist, + /** ignore Content-Length */ + ignore_content_length = 136, + /** Set to non-zero to skip the IP address received in a 227 PASV FTP server + response. Typically used for FTP-SSL purposes but is not restricted to + that. libcurl will then instead use the same IP address it used for the + control connection. */ + ftp_skip_pasv_ip, + /** Select "file method" to use when doing FTP, see the curl_ftpmethod + above. */ + ftp_filemethod, + /** Local port number to bind the socket to */ + localport, + /** Number of ports to try, including the first one set with LOCALPORT. + Thus, setting it to 1 will make no additional attempts but the first. + */ + localportrange, + /** no transfer, set up connection and let application use the socket by + extracting it with CURLINFO_LASTSOCKET */ + connect_only, + /** Function that will be called to convert from the + network encoding (instead of using the iconv calls in libcurl) */ + conv_from_network_function = 20_142, + /** Function that will be called to convert to the + network encoding (instead of using the iconv calls in libcurl) */ + conv_to_network_function, + /** Function that will be called to convert from UTF8 + (instead of using the iconv calls in libcurl) + Note that this is used only for SSL certificate processing */ + conv_from_utf8_function, + /** If the connection proceeds too quickly then need to slow it down */ + /** */ + /** limit-rate: maximum number of bytes per second to send or receive */ + max_send_speed_large = 30_145, + max_recv_speed_large, /// ditto + /** Pointer to command string to send if USER/PASS fails. */ + ftp_alternative_to_user = 10_147, + /** callback function for setting socket options */ + sockoptfunction = 20_148, + sockoptdata = 10_149, + /** set to 0 to disable session ID re-use for this transfer, default is + enabled (== 1) */ + ssl_sessionid_cache = 150, + /** allowed SSH authentication methods */ + ssh_auth_types, + /** Used by scp/sftp to do public/private key authentication */ + ssh_public_keyfile = 10_152, + ssh_private_keyfile, + /** Send CCC (Clear Command Channel) after authentication */ + ftp_ssl_ccc = 154, + /** Same as TIMEOUT and CONNECTTIMEOUT, but with ms resolution */ + timeout_ms, + connecttimeout_ms, + /** set to zero to disable the libcurl's decoding and thus pass the raw body + data to the application even when it is encoded/compressed */ + http_transfer_decoding, + http_content_decoding, /// ditto + /** Permission used when creating new files and directories on the remote + server for protocols that support it, SFTP/SCP/FILE */ + new_file_perms, + new_directory_perms, /// ditto + /** Set the behaviour of POST when redirecting. Values must be set to one + of CURL_REDIR* defines below. This used to be called CURLOPT_POST301 */ + postredir, + /** used by scp/sftp to verify the host's public key */ + ssh_host_public_key_md5 = 10_162, + /** Callback function for opening socket (instead of socket(2)). Optionally, + callback is able change the address or refuse to connect returning + CURL_SOCKET_BAD. The callback should have type + curl_opensocket_callback */ + opensocketfunction = 20_163, + opensocketdata = 10_164, /// ditto + /** POST volatile input fields. */ + copypostfields, + /** set transfer mode (;type=$(LT)a|i$(GT)) when doing FTP via an HTTP proxy */ + proxy_transfer_mode = 166, + /** Callback function for seeking in the input stream */ + seekfunction = 20_167, + seekdata = 10_168, /// ditto + /** CRL file */ + crlfile, + /** Issuer certificate */ + issuercert, + /** (IPv6) Address scope */ + address_scope = 171, + /** Collect certificate chain info and allow it to get retrievable with + CURLINFO_CERTINFO after the transfer is complete. (Unfortunately) only + working with OpenSSL-powered builds. */ + certinfo, + /** "name" and "pwd" to use when fetching. */ + username = 10_173, + password, /// ditto + /** "name" and "pwd" to use with Proxy when fetching. */ + proxyusername, + proxypassword, /// ditto + /** Comma separated list of hostnames defining no-proxy zones. These should + match both hostnames directly, and hostnames within a domain. For + example, local.com will match local.com and www.local.com, but NOT + notlocal.com or www.notlocal.com. For compatibility with other + implementations of this, .local.com will be considered to be the same as + local.com. A single * is the only valid wildcard, and effectively + disables the use of proxy. */ + noproxy, + /** block size for TFTP transfers */ + tftp_blksize = 178, + /** Socks Service */ + socks5_gssapi_service = 10_179, + /** Socks Service */ + socks5_gssapi_nec = 180, + /** set the bitmask for the protocols that are allowed to be used for the + transfer, which thus helps the app which takes URLs from users or other + external inputs and want to restrict what protocol(s) to deal + with. Defaults to CURLPROTO_ALL. */ + protocols, + /** set the bitmask for the protocols that libcurl is allowed to follow to, + as a subset of the CURLOPT_PROTOCOLS ones. That means the protocol needs + to be set in both bitmasks to be allowed to get redirected to. Defaults + to all protocols except FILE and SCP. */ + redir_protocols, + /** set the SSH knownhost file name to use */ + ssh_knownhosts = 10_183, + /** set the SSH host key callback, must point to a curl_sshkeycallback + function */ + ssh_keyfunction = 20_184, + /** set the SSH host key callback custom pointer */ + ssh_keydata = 10_185, + /** set the SMTP mail originator */ + mail_from, + /** set the SMTP mail receiver(s) */ + mail_rcpt, + /** FTP: send PRET before PASV */ + ftp_use_pret = 188, + /** RTSP request method (OPTIONS, SETUP, PLAY, etc...) */ + rtsp_request, + /** The RTSP session identifier */ + rtsp_session_id = 10_190, + /** The RTSP stream URI */ + rtsp_stream_uri, + /** The Transport: header to use in RTSP requests */ + rtsp_transport, + /** Manually initialize the client RTSP CSeq for this handle */ + rtsp_client_cseq = 193, + /** Manually initialize the server RTSP CSeq for this handle */ + rtsp_server_cseq, + /** The stream to pass to INTERLEAVEFUNCTION. */ + interleavedata = 10_195, + /** Let the application define a custom write method for RTP data */ + interleavefunction = 20_196, + /** Turn on wildcard matching */ + wildcardmatch = 197, + /** Directory matching callback called before downloading of an + individual file (chunk) started */ + chunk_bgn_function = 20_198, + /** Directory matching callback called after the file (chunk) + was downloaded, or skipped */ + chunk_end_function, + /** Change match (fnmatch-like) callback for wildcard matching */ + fnmatch_function, + /** Let the application define custom chunk data pointer */ + chunk_data = 10_201, + /** FNMATCH_FUNCTION user pointer */ + fnmatch_data, + /** send linked-list of name:port:address sets */ + resolve, + /** Set a username for authenticated TLS */ + tlsauth_username, + /** Set a password for authenticated TLS */ + tlsauth_password, + /** Set authentication type for authenticated TLS */ + tlsauth_type, + /** the last unused */ + lastentry, + + writedata = file, /// convenient alias + readdata = infile, /// ditto + headerdata = writeheader, /// ditto + rtspheader = httpheader, /// ditto +} +/// +alias CURLoption = int; +/// +enum CURLOPT_SERVER_RESPONSE_TIMEOUT = CurlOption.ftp_response_timeout; + +/** Below here follows defines for the CURLOPT_IPRESOLVE option. If a host + name resolves addresses using more than one IP protocol version, this + option might be handy to force libcurl to use a specific IP version. */ +enum CurlIpResolve { + whatever = 0, /** default, resolves addresses to all IP versions that your system allows */ + v4 = 1, /** resolve to ipv4 addresses */ + v6 = 2 /** resolve to ipv6 addresses */ +} + +/** three convenient "aliases" that follow the name scheme better */ +enum CURLOPT_WRITEDATA = CurlOption.file; +/// ditto +enum CURLOPT_READDATA = CurlOption.infile; +/// ditto +enum CURLOPT_HEADERDATA = CurlOption.writeheader; +/// ditto +enum CURLOPT_RTSPHEADER = CurlOption.httpheader; + +/** These enums are for use with the CURLOPT_HTTP_VERSION option. */ +enum CurlHttpVersion { + none, /** setting this means we don't care, and that we'd + like the library to choose the best possible + for us! */ + v1_0, /** please use HTTP 1.0 in the request */ + v1_1, /** please use HTTP 1.1 in the request */ + last /** *ILLEGAL* http version */ +} + +/** + * Public API enums for RTSP requests + */ +enum CurlRtspReq { + none, /// + options, /// + describe, /// + announce, /// + setup, /// + play, /// + pause, /// + teardown, /// + get_parameter, /// + set_parameter, /// + record, /// + receive, /// + last /// +} + + /** These enums are for use with the CURLOPT_NETRC option. */ +enum CurlNetRcOption { + ignored, /** The .netrc will never be read. This is the default. */ + optional /** A user:password in the URL will be preferred to one in the .netrc. */, + required, /** A user:password in the URL will be ignored. + * Unless one is set programmatically, the .netrc + * will be queried. */ + last /// +} + +/// +enum CurlSslVersion { + default_version, /// + tlsv1, /// + sslv2, /// + sslv3, /// + last /** never use */ +} + +/// +enum CurlTlsAuth { + none, /// + srp, /// + last /** never use */ +} + +/** symbols to use with CURLOPT_POSTREDIR. + CURL_REDIR_POST_301 and CURL_REDIR_POST_302 can be bitwise ORed so that + CURL_REDIR_POST_301 | CURL_REDIR_POST_302 == CURL_REDIR_POST_ALL */ +enum CurlRedir { + get_all = 0, /// + post_301 = 1, /// + post_302 = 2, /// + /// + post_all = (1 | 2) // (CURL_REDIR_POST_301|CURL_REDIR_POST_302); +} +/// +enum CurlTimeCond { + none, /// + ifmodsince, /// + ifunmodsince, /// + lastmod, /// + last /// +} +/// +alias curl_TimeCond = int; + + +/** curl_strequal() and curl_strnequal() are subject for removal in a future + libcurl, see lib/README.curlx for details */ +extern (C) { +int curl_strequal(in char *s1, in char *s2); +/// ditto +int curl_strnequal(in char *s1, in char *s2, size_t n); +} +enum CurlForm { + nothing, /********** the first one is unused ************/ + copyname, + ptrname, + namelength, + copycontents, + ptrcontents, + contentslength, + filecontent, + array, + obsolete, + file, + buffer, + bufferptr, + bufferlength, + contenttype, + contentheader, + filename, + end, + obsolete2, + stream, + lastentry /** the last unused */ +} +alias CURLformoption = int; + + +/** structure to be used as parameter for CURLFORM_ARRAY */ +extern (C) struct curl_forms +{ + CURLformoption option; /// + const(char) *value; /// +} + +/** Use this for multipart formpost building + * + * Returns code for curl_formadd() + * + * Returns: + * + * $(UL + * $(LI CURL_FORMADD_OK on success ) + * $(LI CURL_FORMADD_MEMORY if the FormInfo allocation fails ) + * $(LI CURL_FORMADD_OPTION_TWICE if one option is given twice for one Form ) + * $(LI CURL_FORMADD_NULL if a null pointer was given for a char ) + * $(LI CURL_FORMADD_MEMORY if the allocation of a FormInfo struct failed ) + * $(LI CURL_FORMADD_UNKNOWN_OPTION if an unknown option was used ) + * $(LI CURL_FORMADD_INCOMPLETE if the some FormInfo is not complete (or error) ) + * $(LI CURL_FORMADD_MEMORY if a curl_httppost struct cannot be allocated ) + * $(LI CURL_FORMADD_MEMORY if some allocation for string copying failed. ) + * $(LI CURL_FORMADD_ILLEGAL_ARRAY if an illegal option is used in an array ) + * ) + * + ***************************************************************************/ +enum CurlFormAdd { + ok, /** first, no error */ + memory, /// + option_twice, /// + null_ptr, /// + unknown_option, /// + incomplete, /// + illegal_array, /// + disabled, /** libcurl was built with this disabled */ + last /// +} +/// +alias CURLFORMcode = int; + +extern (C) { + +/** + * Name: curl_formadd() + * + * Description: + * + * Pretty advanced function for building multi-part formposts. Each invoke + * adds one part that together construct a full post. Then use + * CURLOPT_HTTPPOST to send it off to libcurl. + */ +CURLFORMcode curl_formadd(curl_httppost **httppost, curl_httppost **last_post,...); + +/** + * callback function for curl_formget() + * The void *arg pointer will be the one passed as second argument to + * curl_formget(). + * The character buffer passed to it must not be freed. + * Should return the buffer length passed to it as the argument "len" on + * success. + */ +alias curl_formget_callback = size_t function(void *arg, in char *buf, size_t len); + +/** + * Name: curl_formget() + * + * Description: + * + * Serialize a curl_httppost struct built with curl_formadd(). + * Accepts a void pointer as second argument which will be passed to + * the curl_formget_callback function. + * Returns 0 on success. + */ +int curl_formget(curl_httppost *form, void *arg, curl_formget_callback append); +/** + * Name: curl_formfree() + * + * Description: + * + * Free a multipart formpost previously built with curl_formadd(). + */ +void curl_formfree(curl_httppost *form); + +/** + * Name: curl_getenv() + * + * Description: + * + * Returns a malloc()'ed string that MUST be curl_free()ed after usage is + * complete. DEPRECATED - see lib/README.curlx + */ +char * curl_getenv(in char *variable); + +/** + * Name: curl_version() + * + * Description: + * + * Returns a static ascii string of the libcurl version. + */ +char * curl_version(); + +/** + * Name: curl_easy_escape() + * + * Description: + * + * Escapes URL strings (converts all letters consider illegal in URLs to their + * %XX versions). This function returns a new allocated string or NULL if an + * error occurred. + */ +char * curl_easy_escape(CURL *handle, in char *string, int length); + +/** the previous version: */ +char * curl_escape(in char *string, int length); + + +/** + * Name: curl_easy_unescape() + * + * Description: + * + * Unescapes URL encoding in strings (converts all %XX codes to their 8bit + * versions). This function returns a new allocated string or NULL if an error + * occurred. + * Conversion Note: On non-ASCII platforms the ASCII %XX codes are + * converted into the host encoding. + */ +char * curl_easy_unescape(CURL *handle, in char *string, int length, int *outlength); + +/** the previous version */ +char * curl_unescape(in char *string, int length); + +/** + * Name: curl_free() + * + * Description: + * + * Provided for de-allocation in the same translation unit that did the + * allocation. Added in libcurl 7.10 + */ +void curl_free(void *p); + +/** + * Name: curl_global_init() + * + * Description: + * + * curl_global_init() should be invoked exactly once for each application that + * uses libcurl and before any call of other libcurl functions. + * + * This function is not thread-safe! + */ +CURLcode curl_global_init(c_long flags); + +/** + * Name: curl_global_init_mem() + * + * Description: + * + * curl_global_init() or curl_global_init_mem() should be invoked exactly once + * for each application that uses libcurl. This function can be used to + * initialize libcurl and set user defined memory management callback + * functions. Users can implement memory management routines to check for + * memory leaks, check for mis-use of the curl library etc. User registered + * callback routines with be invoked by this library instead of the system + * memory management routines like malloc, free etc. + */ +CURLcode curl_global_init_mem( + c_long flags, + curl_malloc_callback m, + curl_free_callback f, + curl_realloc_callback r, + curl_strdup_callback s, + curl_calloc_callback c +); + +/** + * Name: curl_global_cleanup() + * + * Description: + * + * curl_global_cleanup() should be invoked exactly once for each application + * that uses libcurl + */ +void curl_global_cleanup(); +} + +/** linked-list structure for the CURLOPT_QUOTE option (and other) */ +extern (C) { + +struct curl_slist +{ + char *data; + curl_slist *next; +} + +/** + * Name: curl_slist_append() + * + * Description: + * + * Appends a string to a linked list. If no list exists, it will be created + * first. Returns the new list, after appending. + */ +curl_slist * curl_slist_append(curl_slist *, in char *); + +/** + * Name: curl_slist_free_all() + * + * Description: + * + * free a previously built curl_slist. + */ +void curl_slist_free_all(curl_slist *); + +/** + * Name: curl_getdate() + * + * Description: + * + * Returns the time, in seconds since 1 Jan 1970 of the time string given in + * the first argument. The time argument in the second parameter is unused + * and should be set to NULL. + */ +time_t curl_getdate(char *p, time_t *unused); + +/** info about the certificate chain, only for OpenSSL builds. Asked + for with CURLOPT_CERTINFO / CURLINFO_CERTINFO */ +struct curl_certinfo +{ + int num_of_certs; /** number of certificates with information */ + curl_slist **certinfo; /** for each index in this array, there's a + linked list with textual information in the + format "name: value" */ +} + +} // extern (C) end + +/// +enum CURLINFO_STRING = 0x100000; +/// +enum CURLINFO_LONG = 0x200000; +/// +enum CURLINFO_DOUBLE = 0x300000; +/// +enum CURLINFO_SLIST = 0x400000; +/// +enum CURLINFO_MASK = 0x0fffff; + +/// +enum CURLINFO_TYPEMASK = 0xf00000; + +/// +enum CurlInfo { + none, /// + effective_url = 1_048_577, /// + response_code = 2_097_154, /// + total_time = 3_145_731, /// + namelookup_time, /// + connect_time, /// + pretransfer_time, /// + size_upload, /// + size_download, /// + speed_download, /// + speed_upload, /// + header_size = 2_097_163, /// + request_size, /// + ssl_verifyresult, /// + filetime, /// + content_length_download = 3_145_743, /// + content_length_upload, /// + starttransfer_time, /// + content_type = 1_048_594, /// + redirect_time = 3_145_747, /// + redirect_count = 2_097_172, /// + private_info = 1_048_597, /// + http_connectcode = 2_097_174, /// + httpauth_avail, /// + proxyauth_avail, /// + os_errno, /// + num_connects, /// + ssl_engines = 4_194_331, /// + cookielist, /// + lastsocket = 2_097_181, /// + ftp_entry_path = 1_048_606, /// + redirect_url, /// + primary_ip, /// + appconnect_time = 3_145_761, /// + certinfo = 4_194_338, /// + condition_unmet = 2_097_187, /// + rtsp_session_id = 1_048_612, /// + rtsp_client_cseq = 2_097_189, /// + rtsp_server_cseq, /// + rtsp_cseq_recv, /// + primary_port, /// + local_ip = 1_048_617, /// + local_port = 2_097_194, /// + /** Fill in new entries below here! */ + lastone = 42 +} +/// +alias CURLINFO = int; + +/** CURLINFO_RESPONSE_CODE is the new name for the option previously known as + CURLINFO_HTTP_CODE */ +enum CURLINFO_HTTP_CODE = CurlInfo.response_code; + +/// +enum CurlClosePolicy { + none, /// + oldest, /// + least_recently_used, /// + least_traffic, /// + slowest, /// + callback, /// + last /// +} +/// +alias curl_closepolicy = int; + +/// +enum CurlGlobal { + ssl = 1, /// + win32 = 2, /// + /// + all = (1 | 2), // (CURL_GLOBAL_SSL|CURL_GLOBAL_WIN32); + nothing = 0, /// + default_ = (1 | 2) /// all +} + +/****************************************************************************** + * Setup defines, protos etc for the sharing stuff. + */ + +/** Different data locks for a single share */ +enum CurlLockData { + none, /// + /** CURL_LOCK_DATA_SHARE is used internally to say that + * the locking is just made to change the internal state of the share + * itself. + */ + share, + cookie, /// + dns, /// + ssl_session, /// + connect, /// + last /// +} +/// +alias curl_lock_data = int; + +/** Different lock access types */ +enum CurlLockAccess { + none, /** unspecified action */ + shared_access, /** for read perhaps */ + single, /** for write perhaps */ + last /** never use */ +} +/// +alias curl_lock_access = int; + +/// +alias curl_lock_function = void function(CURL *handle, curl_lock_data data, curl_lock_access locktype, void *userptr); +/// +alias curl_unlock_function = void function(CURL *handle, curl_lock_data data, void *userptr); + +/// +alias CURLSH = void; + +/// +enum CurlShError { + ok, /** all is fine */ + bad_option, /** 1 */ + in_use, /** 2 */ + invalid, /** 3 */ + nomem, /** out of memory */ + last /** never use */ +} +/// +alias CURLSHcode = int; + +/** pass in a user data pointer used in the lock/unlock callback + functions */ +enum CurlShOption { + none, /** don't use */ + share, /** specify a data type to share */ + unshare, /** specify which data type to stop sharing */ + lockfunc, /** pass in a 'curl_lock_function' pointer */ + unlockfunc, /** pass in a 'curl_unlock_function' pointer */ + userdata, /** pass in a user data pointer used in the lock/unlock + callback functions */ + last /** never use */ +} +/// +alias CURLSHoption = int; + +extern (C) { +/// +CURLSH * curl_share_init(); +/// +CURLSHcode curl_share_setopt(CURLSH *, CURLSHoption option,...); +/// +CURLSHcode curl_share_cleanup(CURLSH *); +} + +/***************************************************************************** + * Structures for querying information about the curl library at runtime. + */ + +// CURLVERSION_* +enum CurlVer { + first, /// + second, /// + third, /// + fourth, /// + last /// +} +/// +alias CURLversion = int; + +/** The 'CURLVERSION_NOW' is the symbolic name meant to be used by + basically all programs ever that want to get version information. It is + meant to be a built-in version number for what kind of struct the caller + expects. If the struct ever changes, we redefine the NOW to another enum + from above. */ +enum CURLVERSION_NOW = CurlVer.fourth; + +/// +extern (C) struct _N28 +{ + CURLversion age; /** age of the returned struct */ + const(char) *version_; /** LIBCURL_VERSION */ + uint version_num; /** LIBCURL_VERSION_NUM */ + const(char) *host; /** OS/host/cpu/machine when configured */ + int features; /** bitmask, see defines below */ + const(char) *ssl_version; /** human readable string */ + c_long ssl_version_num; /** not used anymore, always 0 */ + const(char) *libz_version; /** human readable string */ + /** protocols is terminated by an entry with a NULL protoname */ + const(char) **protocols; + /** The fields below this were added in CURLVERSION_SECOND */ + const(char) *ares; + int ares_num; + /** This field was added in CURLVERSION_THIRD */ + const(char) *libidn; + /** These field were added in CURLVERSION_FOURTH. */ + /** Same as '_libiconv_version' if built with HAVE_ICONV */ + int iconv_ver_num; + const(char) *libssh_version; /** human readable string */ +} +/// +alias curl_version_info_data = _N28; + +/// +// CURL_VERSION_* +enum CurlVersion { + ipv6 = 1, /** IPv6-enabled */ + kerberos4 = 2, /** kerberos auth is supported */ + ssl = 4, /** SSL options are present */ + libz = 8, /** libz features are present */ + ntlm = 16, /** NTLM auth is supported */ + gssnegotiate = 32, /** Negotiate auth support */ + dbg = 64, /** built with debug capabilities */ + asynchdns = 128, /** asynchronous dns resolves */ + spnego = 256, /** SPNEGO auth */ + largefile = 512, /** supports files bigger than 2GB */ + idn = 1024, /** International Domain Names support */ + sspi = 2048, /** SSPI is supported */ + conv = 4096, /** character conversions supported */ + curldebug = 8192, /** debug memory tracking supported */ + tlsauth_srp = 16_384 /** TLS-SRP auth is supported */ +} + +extern (C) { +/** + * Name: curl_version_info() + * + * Description: + * + * This function returns a pointer to a static copy of the version info + * struct. See above. + */ +curl_version_info_data * curl_version_info(CURLversion ); + +/** + * Name: curl_easy_strerror() + * + * Description: + * + * The curl_easy_strerror function may be used to turn a CURLcode value + * into the equivalent human readable error string. This is useful + * for printing meaningful error messages. + */ +const(char)* curl_easy_strerror(CURLcode ); + +/** + * Name: curl_share_strerror() + * + * Description: + * + * The curl_share_strerror function may be used to turn a CURLSHcode value + * into the equivalent human readable error string. This is useful + * for printing meaningful error messages. + */ +const(char)* curl_share_strerror(CURLSHcode ); + +/** + * Name: curl_easy_pause() + * + * Description: + * + * The curl_easy_pause function pauses or unpauses transfers. Select the new + * state by setting the bitmask, use the convenience defines below. + * + */ +CURLcode curl_easy_pause(CURL *handle, int bitmask); +} + + +/// +enum CurlPause { + recv = 1, /// + recv_cont = 0, /// + send = 4, /// + send_cont = 0, /// + /// + all = (1 | 4), // CURLPAUSE_RECV | CURLPAUSE_SEND + /// + cont = (0 | 0), // CURLPAUSE_RECV_CONT | CURLPAUSE_SEND_CONT +} + +/* unfortunately, the easy.h and multi.h include files need options and info + stuff before they can be included! */ +/* *************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2008, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +extern (C) { + /// + CURL * curl_easy_init(); + /// + CURLcode curl_easy_setopt(CURL *curl, CURLoption option,...); + /// + CURLcode curl_easy_perform(CURL *curl); + /// + void curl_easy_cleanup(CURL *curl); +} + +/** + * Name: curl_easy_getinfo() + * + * Description: + * + * Request internal information from the curl session with this function. The + * third argument MUST be a pointer to a long, a pointer to a char * or a + * pointer to a double (as the documentation describes elsewhere). The data + * pointed to will be filled in accordingly and can be relied upon only if the + * function returns CURLE_OK. This function is intended to get used *AFTER* a + * performed transfer, all results from this function are undefined until the + * transfer is completed. + */ +extern (C) CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info,...); + + +/** + * Name: curl_easy_duphandle() + * + * Description: + * + * Creates a new curl session handle with the same options set for the handle + * passed in. Duplicating a handle could only be a matter of cloning data and + * options, internal state info and things like persistant connections cannot + * be transfered. It is useful in multithreaded applications when you can run + * curl_easy_duphandle() for each new thread to avoid a series of identical + * curl_easy_setopt() invokes in every thread. + */ +extern (C) CURL * curl_easy_duphandle(CURL *curl); + +/** + * Name: curl_easy_reset() + * + * Description: + * + * Re-initializes a CURL handle to the default values. This puts back the + * handle to the same state as it was in when it was just created. + * + * It does keep: live connections, the Session ID cache, the DNS cache and the + * cookies. + */ +extern (C) void curl_easy_reset(CURL *curl); + +/** + * Name: curl_easy_recv() + * + * Description: + * + * Receives data from the connected socket. Use after successful + * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. + */ +extern (C) CURLcode curl_easy_recv(CURL *curl, void *buffer, size_t buflen, size_t *n); + +/** + * Name: curl_easy_send() + * + * Description: + * + * Sends data over the connected socket. Use after successful + * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. + */ +extern (C) CURLcode curl_easy_send(CURL *curl, void *buffer, size_t buflen, size_t *n); + + +/* + * This header file should not really need to include "curl.h" since curl.h + * itself includes this file and we expect user applications to do #include + * without the need for especially including multi.h. + * + * For some reason we added this include here at one point, and rather than to + * break existing (wrongly written) libcurl applications, we leave it as-is + * but with this warning attached. + */ +/* *************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2010, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +/// +alias CURLM = void; + +/// +enum CurlM { + call_multi_perform = -1, /** please call curl_multi_perform() or curl_multi_socket*() soon */ + ok, /// + bad_handle, /** the passed-in handle is not a valid CURLM handle */ + bad_easy_handle, /** an easy handle was not good/valid */ + out_of_memory, /** if you ever get this, you're in deep sh*t */ + internal_error, /** this is a libcurl bug */ + bad_socket, /** the passed in socket argument did not match */ + unknown_option, /** curl_multi_setopt() with unsupported option */ + last, /// +} +/// +alias CURLMcode = int; + +/** just to make code nicer when using curl_multi_socket() you can now check + for CURLM_CALL_MULTI_SOCKET too in the same style it works for + curl_multi_perform() and CURLM_CALL_MULTI_PERFORM */ +enum CURLM_CALL_MULTI_SOCKET = CurlM.call_multi_perform; + +/// +enum CurlMsg +{ + none, /// + done, /** This easy handle has completed. 'result' contains + the CURLcode of the transfer */ + last, /** no used */ +} +/// +alias CURLMSG = int; + +/// +extern (C) union _N31 +{ + void *whatever; /** message-specific data */ + CURLcode result; /** return code for transfer */ +} + +/// +extern (C) struct CURLMsg +{ + CURLMSG msg; /** what this message means */ + CURL *easy_handle; /** the handle it concerns */ + _N31 data; /// +} + +/** + * Name: curl_multi_init() + * + * Desc: inititalize multi-style curl usage + * + * Returns: a new CURLM handle to use in all 'curl_multi' functions. + */ +extern (C) CURLM * curl_multi_init(); + +/** + * Name: curl_multi_add_handle() + * + * Desc: add a standard curl handle to the multi stack + * + * Returns: CURLMcode type, general multi error code. + */ +extern (C) CURLMcode curl_multi_add_handle(CURLM *multi_handle, CURL *curl_handle); + + /** + * Name: curl_multi_remove_handle() + * + * Desc: removes a curl handle from the multi stack again + * + * Returns: CURLMcode type, general multi error code. + */ +extern (C) CURLMcode curl_multi_remove_handle(CURLM *multi_handle, CURL *curl_handle); + + /** + * Name: curl_multi_fdset() + * + * Desc: Ask curl for its fd_set sets. The app can use these to select() or + * poll() on. We want curl_multi_perform() called as soon as one of + * them are ready. + * + * Returns: CURLMcode type, general multi error code. + */ + +/** tmp decl */ +alias fd_set = int; +/// +extern (C) CURLMcode curl_multi_fdset( + CURLM *multi_handle, + fd_set *read_fd_set, + fd_set *write_fd_set, + fd_set *exc_fd_set, + int *max_fd +); + + /** + * Name: curl_multi_perform() + * + * Desc: When the app thinks there's data available for curl it calls this + * function to read/write whatever there is right now. This returns + * as soon as the reads and writes are done. This function does not + * require that there actually is data available for reading or that + * data can be written, it can be called just in case. It returns + * the number of handles that still transfer data in the second + * argument's integer-pointer. + * + * Returns: CURLMcode type, general multi error code. *NOTE* that this only + * returns errors etc regarding the whole multi stack. There might + * still have occurred problems on invidual transfers even when this + * returns OK. + */ +extern (C) CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles); + + /** + * Name: curl_multi_cleanup() + * + * Desc: Cleans up and removes a whole multi stack. It does not free or + * touch any individual easy handles in any way. We need to define + * in what state those handles will be if this function is called + * in the middle of a transfer. + * + * Returns: CURLMcode type, general multi error code. + */ +extern (C) CURLMcode curl_multi_cleanup(CURLM *multi_handle); + +/** + * Name: curl_multi_info_read() + * + * Desc: Ask the multi handle if there's any messages/informationals from + * the individual transfers. Messages include informationals such as + * error code from the transfer or just the fact that a transfer is + * completed. More details on these should be written down as well. + * + * Repeated calls to this function will return a new struct each + * time, until a special "end of msgs" struct is returned as a signal + * that there is no more to get at this point. + * + * The data the returned pointer points to will not survive calling + * curl_multi_cleanup(). + * + * The 'CURLMsg' struct is meant to be very simple and only contain + * very basic informations. If more involved information is wanted, + * we will provide the particular "transfer handle" in that struct + * and that should/could/would be used in subsequent + * curl_easy_getinfo() calls (or similar). The point being that we + * must never expose complex structs to applications, as then we'll + * undoubtably get backwards compatibility problems in the future. + * + * Returns: A pointer to a filled-in struct, or NULL if it failed or ran out + * of structs. It also writes the number of messages left in the + * queue (after this read) in the integer the second argument points + * to. + */ +extern (C) CURLMsg * curl_multi_info_read(CURLM *multi_handle, int *msgs_in_queue); + +/** + * Name: curl_multi_strerror() + * + * Desc: The curl_multi_strerror function may be used to turn a CURLMcode + * value into the equivalent human readable error string. This is + * useful for printing meaningful error messages. + * + * Returns: A pointer to a zero-terminated error message. + */ +extern (C) const(char)* curl_multi_strerror(CURLMcode ); + +/** + * Name: curl_multi_socket() and + * curl_multi_socket_all() + * + * Desc: An alternative version of curl_multi_perform() that allows the + * application to pass in one of the file descriptors that have been + * detected to have "action" on them and let libcurl perform. + * See man page for details. + */ +enum CurlPoll { + none_ = 0, /** jdrewsen - underscored in order not to clash with reserved D symbols */ + in_ = 1, /// + out_ = 2, /// + inout_ = 3, /// + remove_ = 4 /// +} + +/// +alias CURL_SOCKET_TIMEOUT = CURL_SOCKET_BAD; + +/// +enum CurlCSelect { + in_ = 0x01, /** jdrewsen - underscored in order not to clash with reserved D symbols */ + out_ = 0x02, /// + err_ = 0x04 /// +} + +extern (C) { + /// + alias curl_socket_callback = + int function(CURL *easy, /** easy handle */ + curl_socket_t s, /** socket */ + int what, /** see above */ + void *userp, /** private callback pointer */ + void *socketp); /** private socket pointer */ +} + +/** + * Name: curl_multi_timer_callback + * + * Desc: Called by libcurl whenever the library detects a change in the + * maximum number of milliseconds the app is allowed to wait before + * curl_multi_socket() or curl_multi_perform() must be called + * (to allow libcurl's timed events to take place). + * + * Returns: The callback should return zero. + */ + +extern (C) { + alias curl_multi_timer_callback = + int function(CURLM *multi, /** multi handle */ + c_long timeout_ms, /** see above */ + void *userp); /** private callback pointer */ + /// ditto + CURLMcode curl_multi_socket(CURLM *multi_handle, curl_socket_t s, int *running_handles); + /// ditto + CURLMcode curl_multi_socket_action(CURLM *multi_handle, curl_socket_t s, int ev_bitmask, int *running_handles); + /// ditto + CURLMcode curl_multi_socket_all(CURLM *multi_handle, int *running_handles); +} + +/** This macro below was added in 7.16.3 to push users who recompile to use + the new curl_multi_socket_action() instead of the old curl_multi_socket() +*/ + +/** + * Name: curl_multi_timeout() + * + * Desc: Returns the maximum number of milliseconds the app is allowed to + * wait before curl_multi_socket() or curl_multi_perform() must be + * called (to allow libcurl's timed events to take place). + * + * Returns: CURLM error code. + */ +extern (C) CURLMcode curl_multi_timeout(CURLM *multi_handle, c_long *milliseconds); + +/// +enum CurlMOption { + socketfunction = 20_001, /** This is the socket callback function pointer */ + socketdata = 10_002, /** This is the argument passed to the socket callback */ + pipelining = 3, /** set to 1 to enable pipelining for this multi handle */ + timerfunction = 20_004, /** This is the timer callback function pointer */ + timerdata = 10_005, /** This is the argument passed to the timer callback */ + maxconnects = 6, /** maximum number of entries in the connection cache */ + lastentry /// +} +/// +alias CURLMoption = int; + +/** + * Name: curl_multi_setopt() + * + * Desc: Sets options for the multi handle. + * + * Returns: CURLM error code. + */ +extern (C) CURLMcode curl_multi_setopt(CURLM *multi_handle, CURLMoption option,...); + +/** + * Name: curl_multi_assign() + * + * Desc: This function sets an association in the multi handle between the + * given socket and a private pointer of the application. This is + * (only) useful for curl_multi_socket uses. + * + * Returns: CURLM error code. + */ +extern (C) CURLMcode curl_multi_assign(CURLM *multi_handle, curl_socket_t sockfd, void *sockp); diff --git a/libphobos/src/etc/c/sqlite3.d b/libphobos/src/etc/c/sqlite3.d new file mode 100644 index 0000000..43a72f5 --- /dev/null +++ b/libphobos/src/etc/c/sqlite3.d @@ -0,0 +1,2126 @@ +module etc.c.sqlite3; +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the interface that the SQLite library +** presents to client programs. If a C-function, structure, datatype, +** or constant definition does not appear in this file, then it is +** not a published API of SQLite, is subject to change without +** notice, and should not be referenced by programs that use SQLite. +** +** Some of the definitions that are in this file are marked as +** "experimental". Experimental interfaces are normally new +** features recently added to SQLite. We do not anticipate changes +** to experimental interfaces but reserve the right to make minor changes +** if experience from use "in the wild" suggest such changes are prudent. +** +** The official C-language API documentation for SQLite is derived +** from comments in this file. This file is the authoritative source +** on how SQLite interfaces are suppose to operate. +** +** The name of this file under configuration management is "sqlite.h.in". +** The makefile makes some minor changes to this file (such as inserting +** the version number) and changes its name to "sqlite3.h" as +** part of the build process. +*/ + +import core.stdc.stdarg : va_list; + +extern (C) __gshared nothrow: + +/** +** CAPI3REF: Compile-Time Library Version Numbers +*/ +enum SQLITE_VERSION = "3.10.2"; +/// Ditto +enum SQLITE_VERSION_NUMBER = 3_010_002; +/// Ditto +enum SQLITE_SOURCE_ID = "2016-01-20 15:27:19 17efb4209f97fb4971656086b138599a91a75ff9"; + +/** +** CAPI3REF: Run-Time Library Version Numbers +*/ +extern immutable(char)* sqlite3_version; +/// Ditto +immutable(char)* sqlite3_libversion(); +/// Ditto +immutable(char)* sqlite3_sourceid(); +/// Ditto +int sqlite3_libversion_number(); + +/** +** CAPI3REF: Run-Time Library Compilation Options Diagnostics +*/ +int sqlite3_compileoption_used(const char *zOptName); +/// Ditto +immutable(char)* sqlite3_compileoption_get(int N); + +/** +** CAPI3REF: Test To See If The Library Is Threadsafe +*/ +int sqlite3_threadsafe(); + +/** +** CAPI3REF: Database Connection Handle +*/ +struct sqlite3; + +/// +alias sqlite3_int64 = long; +/// +alias sqlite3_uint64 = ulong; + +/** +** CAPI3REF: Closing A Database Connection +** +*/ +int sqlite3_close(sqlite3 *); +int sqlite3_close_v2(sqlite3*); + +/** +** The type for a callback function. +** This is legacy and deprecated. It is included for historical +** compatibility and is not documented. +*/ +alias sqlite3_callback = int function (void*,int,char**, char**); + +/** +** CAPI3REF: One-Step Query Execution Interface +*/ +int sqlite3_exec( + sqlite3*, /** An open database */ + const(char)*sql, /** SQL to be evaluated */ + int function (void*,int,char**,char**) callback, /** Callback function */ + void *, /** 1st argument to callback */ + char **errmsg /** Error msg written here */ +); + +/** +** CAPI3REF: Result Codes +*/ +enum +{ + SQLITE_OK = 0, /** Successful result */ +/* beginning-of-error-codes */ +/// Ditto + SQLITE_ERROR = 1, /** SQL error or missing database */ + SQLITE_INTERNAL = 2, /** Internal logic error in SQLite */ + SQLITE_PERM = 3, /** Access permission denied */ + SQLITE_ABORT = 4, /** Callback routine requested an abort */ + SQLITE_BUSY = 5, /** The database file is locked */ + SQLITE_LOCKED = 6, /** A table in the database is locked */ + SQLITE_NOMEM = 7, /** A malloc() failed */ + SQLITE_READONLY = 8, /** Attempt to write a readonly database */ + SQLITE_INTERRUPT = 9, /** Operation terminated by sqlite3_interrupt()*/ + SQLITE_IOERR = 10, /** Some kind of disk I/O error occurred */ + SQLITE_CORRUPT = 11, /** The database disk image is malformed */ + SQLITE_NOTFOUND = 12, /** Unknown opcode in sqlite3_file_control() */ + SQLITE_FULL = 13, /** Insertion failed because database is full */ + SQLITE_CANTOPEN = 14, /** Unable to open the database file */ + SQLITE_PROTOCOL = 15, /** Database lock protocol error */ + SQLITE_EMPTY = 16, /** Database is empty */ + SQLITE_SCHEMA = 17, /** The database schema changed */ + SQLITE_TOOBIG = 18, /** String or BLOB exceeds size limit */ + SQLITE_CONSTRAINT = 19, /** Abort due to constraint violation */ + SQLITE_MISMATCH = 20, /** Data type mismatch */ + SQLITE_MISUSE = 21, /** Library used incorrectly */ + SQLITE_NOLFS = 22, /** Uses OS features not supported on host */ + SQLITE_AUTH = 23, /** Authorization denied */ + SQLITE_FORMAT = 24, /** Auxiliary database format error */ + SQLITE_RANGE = 25, /** 2nd parameter to sqlite3_bind out of range */ + SQLITE_NOTADB = 26, /** File opened that is not a database file */ + SQLITE_NOTICE = 27, + SQLITE_WARNING = 28, + SQLITE_ROW = 100, /** sqlite3_step() has another row ready */ + SQLITE_DONE = 101 /** sqlite3_step() has finished executing */ +} +/* end-of-error-codes */ + +/** +** CAPI3REF: Extended Result Codes +*/ +enum +{ + SQLITE_IOERR_READ = (SQLITE_IOERR | (1 << 8)), + SQLITE_IOERR_SHORT_READ = (SQLITE_IOERR | (2 << 8)), + SQLITE_IOERR_WRITE = (SQLITE_IOERR | (3 << 8)), + SQLITE_IOERR_FSYNC = (SQLITE_IOERR | (4 << 8)), + SQLITE_IOERR_DIR_FSYNC = (SQLITE_IOERR | (5 << 8)), + SQLITE_IOERR_TRUNCATE = (SQLITE_IOERR | (6 << 8)), + SQLITE_IOERR_FSTAT = (SQLITE_IOERR | (7 << 8)), + SQLITE_IOERR_UNLOCK = (SQLITE_IOERR | (8 << 8)), + SQLITE_IOERR_RDLOCK = (SQLITE_IOERR | (9 << 8)), + SQLITE_IOERR_DELETE = (SQLITE_IOERR | (10 << 8)), + SQLITE_IOERR_BLOCKED = (SQLITE_IOERR | (11 << 8)), + SQLITE_IOERR_NOMEM = (SQLITE_IOERR | (12 << 8)), + SQLITE_IOERR_ACCESS = (SQLITE_IOERR | (13 << 8)), + SQLITE_IOERR_CHECKRESERVEDLOCK = (SQLITE_IOERR | (14 << 8)), + SQLITE_IOERR_LOCK = (SQLITE_IOERR | (15 << 8)), + SQLITE_IOERR_CLOSE = (SQLITE_IOERR | (16 << 8)), + SQLITE_IOERR_DIR_CLOSE = (SQLITE_IOERR | (17 << 8)), + SQLITE_IOERR_SHMOPEN = (SQLITE_IOERR | (18 << 8)), + SQLITE_IOERR_SHMSIZE = (SQLITE_IOERR | (19 << 8)), + SQLITE_IOERR_SHMLOCK = (SQLITE_IOERR | (20 << 8)), + SQLITE_IOERR_SHMMAP = (SQLITE_IOERR | (21 << 8)), + SQLITE_IOERR_SEEK = (SQLITE_IOERR | (22 << 8)), + SQLITE_IOERR_DELETE_NOENT = (SQLITE_IOERR | (23 << 8)), + SQLITE_IOERR_MMAP = (SQLITE_IOERR | (24 << 8)), + SQLITE_LOCKED_SHAREDCACHE = (SQLITE_LOCKED | (1 << 8)), + SQLITE_BUSY_RECOVERY = (SQLITE_BUSY | (1 << 8)), + SQLITE_CANTOPEN_NOTEMPDIR = (SQLITE_CANTOPEN | (1 << 8)), + SQLITE_IOERR_GETTEMPPATH = (SQLITE_IOERR | (25 << 8)), + SQLITE_IOERR_CONVPATH = (SQLITE_IOERR | (26 << 8)), + SQLITE_BUSY_SNAPSHOT = (SQLITE_BUSY | (2 << 8)), + SQLITE_CANTOPEN_ISDIR = (SQLITE_CANTOPEN | (2 << 8)), + SQLITE_CANTOPEN_FULLPATH = (SQLITE_CANTOPEN | (3 << 8)), + SQLITE_CANTOPEN_CONVPATH = (SQLITE_CANTOPEN | (4 << 8)), + SQLITE_CORRUPT_VTAB = (SQLITE_CORRUPT | (1 << 8)), + SQLITE_READONLY_RECOVERY = (SQLITE_READONLY | (1 << 8)), + SQLITE_READONLY_CANTLOCK = (SQLITE_READONLY | (2 << 8)), + SQLITE_READONLY_ROLLBACK = (SQLITE_READONLY | (3 << 8)), + SQLITE_READONLY_DBMOVED = (SQLITE_READONLY | (4 << 8)), + SQLITE_ABORT_ROLLBACK = (SQLITE_ABORT | (2 << 8)), + SQLITE_CONSTRAINT_CHECK = (SQLITE_CONSTRAINT | (1 << 8)), + SQLITE_CONSTRAINT_COMMITHOOK = (SQLITE_CONSTRAINT | (2 << 8)), + SQLITE_CONSTRAINT_FOREIGNKEY = (SQLITE_CONSTRAINT | (3 << 8)), + SQLITE_CONSTRAINT_FUNCTION = (SQLITE_CONSTRAINT | (4 << 8)), + SQLITE_CONSTRAINT_NOTNULL = (SQLITE_CONSTRAINT | (5 << 8)), + SQLITE_CONSTRAINT_PRIMARYKEY = (SQLITE_CONSTRAINT | (6 << 8)), + SQLITE_CONSTRAINT_TRIGGER = (SQLITE_CONSTRAINT | (7 << 8)), + SQLITE_CONSTRAINT_UNIQUE = (SQLITE_CONSTRAINT | (8 << 8)), + SQLITE_CONSTRAINT_VTAB = (SQLITE_CONSTRAINT | (9 << 8)), + SQLITE_CONSTRAINT_ROWID = (SQLITE_CONSTRAINT |(10 << 8)), + SQLITE_NOTICE_RECOVER_WAL = (SQLITE_NOTICE | (1 << 8)), + SQLITE_NOTICE_RECOVER_ROLLBACK = (SQLITE_NOTICE | (2 << 8)), + SQLITE_WARNING_AUTOINDEX = (SQLITE_WARNING | (1 << 8)), + SQLITE_AUTH_USER = (SQLITE_AUTH | (1 << 8)) +} + +/** +** CAPI3REF: Flags For File Open Operations +*/ +enum +{ + SQLITE_OPEN_READONLY = 0x00000001, /** Ok for sqlite3_open_v2() */ + SQLITE_OPEN_READWRITE = 0x00000002, /** Ok for sqlite3_open_v2() */ + SQLITE_OPEN_CREATE = 0x00000004, /** Ok for sqlite3_open_v2() */ + SQLITE_OPEN_DELETEONCLOSE = 0x00000008, /** VFS only */ + SQLITE_OPEN_EXCLUSIVE = 0x00000010, /** VFS only */ + SQLITE_OPEN_AUTOPROXY = 0x00000020, /** VFS only */ + SQLITE_OPEN_URI = 0x00000040, /** Ok for sqlite3_open_v2() */ + SQLITE_OPEN_MEMORY = 0x00000080, /** Ok for sqlite3_open_v2() */ + SQLITE_OPEN_MAIN_DB = 0x00000100, /** VFS only */ + SQLITE_OPEN_TEMP_DB = 0x00000200, /** VFS only */ + SQLITE_OPEN_TRANSIENT_DB = 0x00000400, /** VFS only */ + SQLITE_OPEN_MAIN_JOURNAL = 0x00000800, /** VFS only */ + SQLITE_OPEN_TEMP_JOURNAL = 0x00001000, /** VFS only */ + SQLITE_OPEN_SUBJOURNAL = 0x00002000, /** VFS only */ + SQLITE_OPEN_MASTER_JOURNAL = 0x00004000, /** VFS only */ + SQLITE_OPEN_NOMUTEX = 0x00008000, /** Ok for sqlite3_open_v2() */ + SQLITE_OPEN_FULLMUTEX = 0x00010000, /** Ok for sqlite3_open_v2() */ + SQLITE_OPEN_SHAREDCACHE = 0x00020000, /** Ok for sqlite3_open_v2() */ + SQLITE_OPEN_PRIVATECACHE = 0x00040000, /** Ok for sqlite3_open_v2() */ + SQLITE_OPEN_WAL = 0x00080000 /** VFS only */ +} + +/** +** CAPI3REF: Device Characteristics +*/ +enum +{ + SQLITE_IOCAP_ATOMIC = 0x00000001, + SQLITE_IOCAP_ATOMIC512 = 0x00000002, + SQLITE_IOCAP_ATOMIC1K = 0x00000004, + SQLITE_IOCAP_ATOMIC2K = 0x00000008, + SQLITE_IOCAP_ATOMIC4K = 0x00000010, + SQLITE_IOCAP_ATOMIC8K = 0x00000020, + SQLITE_IOCAP_ATOMIC16K = 0x00000040, + SQLITE_IOCAP_ATOMIC32K = 0x00000080, + SQLITE_IOCAP_ATOMIC64K = 0x00000100, + SQLITE_IOCAP_SAFE_APPEND = 0x00000200, + SQLITE_IOCAP_SEQUENTIAL = 0x00000400, + SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN = 0x00000800, + SQLITE_IOCAP_POWERSAFE_OVERWRITE = 0x00001000, + SQLITE_IOCAP_IMMUTABLE = 0x00002000 +} + +/** +** CAPI3REF: File Locking Levels +*/ +enum +{ + SQLITE_LOCK_NONE = 0, + SQLITE_LOCK_SHARED = 1, + SQLITE_LOCK_RESERVED = 2, + SQLITE_LOCK_PENDING = 3, + SQLITE_LOCK_EXCLUSIVE = 4 +} + +/** +** CAPI3REF: Synchronization Type Flags +*/ +enum +{ + SQLITE_SYNC_NORMAL = 0x00002, + SQLITE_SYNC_FULL = 0x00003, + SQLITE_SYNC_DATAONLY = 0x00010 +} + +/** +** CAPI3REF: OS Interface Open File Handle +*/ +struct sqlite3_file +{ + const(sqlite3_io_methods)*pMethods; /* Methods for an open file */ +} + +/** +** CAPI3REF: OS Interface File Virtual Methods Object +*/ + +struct sqlite3_io_methods +{ + int iVersion; + int function (sqlite3_file*) xClose; + int function (sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) xRead; + int function (sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) xWrite; + int function (sqlite3_file*, sqlite3_int64 size) xTruncate; + int function (sqlite3_file*, int flags) xSync; + int function (sqlite3_file*, sqlite3_int64 *pSize) xFileSize; + int function (sqlite3_file*, int) xLock; + int function (sqlite3_file*, int) xUnlock; + int function (sqlite3_file*, int *pResOut) xCheckReservedLock; + int function (sqlite3_file*, int op, void *pArg) xFileControl; + int function (sqlite3_file*) xSectorSize; + int function (sqlite3_file*) xDeviceCharacteristics; + /* Methods above are valid for version 1 */ + int function (sqlite3_file*, int iPg, int pgsz, int, void **) xShmMap; + int function (sqlite3_file*, int offset, int n, int flags) xShmLock; + void function (sqlite3_file*) xShmBarrier; + int function (sqlite3_file*, int deleteFlag) xShmUnmap; + /* Methods above are valid for version 2 */ + /* Additional methods may be added in future releases */ + int function (sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp) xFetch; + int function (sqlite3_file*, sqlite3_int64 iOfst, void *p) xUnfetch; +} + +/** +** CAPI3REF: Standard File Control Opcodes +*/ +enum +{ + SQLITE_FCNTL_LOCKSTATE = 1, + SQLITE_GET_LOCKPROXYFILE = 2, + SQLITE_SET_LOCKPROXYFILE = 3, + SQLITE_LAST_ERRNO = 4, + SQLITE_FCNTL_SIZE_HINT = 5, + SQLITE_FCNTL_CHUNK_SIZE = 6, + SQLITE_FCNTL_FILE_POINTER = 7, + SQLITE_FCNTL_SYNC_OMITTED = 8, + SQLITE_FCNTL_WIN32_AV_RETRY = 9, + SQLITE_FCNTL_PERSIST_WAL = 10, + SQLITE_FCNTL_OVERWRITE = 11, + SQLITE_FCNTL_VFSNAME = 12, + SQLITE_FCNTL_POWERSAFE_OVERWRITE = 13, + SQLITE_FCNTL_PRAGMA = 14, + SQLITE_FCNTL_BUSYHANDLER = 15, + SQLITE_FCNTL_TEMPFILENAME = 16, + SQLITE_FCNTL_MMAP_SIZE = 18, + SQLITE_FCNTL_TRACE = 19, + SQLITE_FCNTL_HAS_MOVED = 20, + SQLITE_FCNTL_SYNC = 21, + SQLITE_FCNTL_COMMIT_PHASETWO = 22, + SQLITE_FCNTL_WIN32_SET_HANDLE = 23, + SQLITE_FCNTL_WAL_BLOCK = 24, + SQLITE_FCNTL_ZIPVFS = 25, + SQLITE_FCNTL_RBU = 26, + SQLITE_FCNTL_VFS_POINTER = 27, +} + +/** +** CAPI3REF: Mutex Handle +*/ +struct sqlite3_mutex; + +/** +** CAPI3REF: OS Interface Object +*/ + +alias xDlSymReturn = void * function(); +/// Ditto +alias sqlite3_syscall_ptr = void function(); + +struct sqlite3_vfs +{ + int iVersion; /** Structure version number (currently 2) */ + int szOsFile; /** Size of subclassed sqlite3_file */ + int mxPathname; /** Maximum file pathname length */ + sqlite3_vfs *pNext; /** Next registered VFS */ + const(char)*zName; /** Name of this virtual file system */ + void *pAppData; /** Pointer to application-specific data */ + int function (sqlite3_vfs*, const char *zName, sqlite3_file*, + int flags, int *pOutFlags) xOpen; + int function (sqlite3_vfs*, const char *zName, int syncDir) xDelete; + int function (sqlite3_vfs*, const char *zName, int flags, int *pResOut) xAccess; + int function (sqlite3_vfs*, const char *zName, int nOut, char *zOut) xFullPathname; + void* function (sqlite3_vfs*, const char *zFilename) xDlOpen; + void function (sqlite3_vfs*, int nByte, char *zErrMsg) xDlError; + xDlSymReturn function (sqlite3_vfs*,void*, const char *zSymbol) *xDlSym; + void function (sqlite3_vfs*, void*) xDlClose; + int function (sqlite3_vfs*, int nByte, char *zOut) xRandomness; + int function (sqlite3_vfs*, int microseconds) xSleep; + int function (sqlite3_vfs*, double*) xCurrentTime; + int function (sqlite3_vfs*, int, char *) xGetLastError; + /* + ** The methods above are in version 1 of the sqlite_vfs object + ** definition. Those that follow are added in version 2 or later + */ + int function (sqlite3_vfs*, sqlite3_int64*) xCurrentTimeInt64; + /* + ** The methods above are in versions 1 and 2 of the sqlite_vfs object. + ** Those below are for version 3 and greater. + */ + int function(sqlite3_vfs*, const char * zName, sqlite3_syscall_ptr) xSetSystemCall; + sqlite3_syscall_ptr function(sqlite3_vfs*, const char * zName) xGetSystemCall; + const(char)* function(sqlite3_vfs*, const char * zName) xNextSystemCall; + /* + ** The methods above are in versions 1 through 3 of the sqlite_vfs object. + ** New fields may be appended in figure versions. The iVersion + ** value will increment whenever this happens. + */ +} + +/** +** CAPI3REF: Flags for the xAccess VFS method +*/ +enum +{ + SQLITE_ACCESS_EXISTS = 0, + + SQLITE_ACCESS_READWRITE = 1, /** Used by PRAGMA temp_store_directory */ + SQLITE_ACCESS_READ = 2 /** Unused */ +} + +/** +** CAPI3REF: Flags for the xShmLock VFS method +*/ +enum +{ + SQLITE_SHM_UNLOCK = 1, + SQLITE_SHM_LOCK = 2, + SQLITE_SHM_SHARED = 4, + SQLITE_SHM_EXCLUSIVE = 8 +} + +/** +** CAPI3REF: Maximum xShmLock index +*/ +enum SQLITE_SHM_NLOCK = 8; + + +/** +** CAPI3REF: Initialize The SQLite Library +*/ +int sqlite3_initialize(); +/// Ditto +int sqlite3_shutdown(); +/// Ditto +int sqlite3_os_init(); +/// Ditto +int sqlite3_os_end(); + +/** +** CAPI3REF: Configuring The SQLite Library +*/ +int sqlite3_config(int, ...); + +/** +** CAPI3REF: Configure database connections +*/ +int sqlite3_db_config(sqlite3*, int op, ...); + +/** +** CAPI3REF: Memory Allocation Routines +*/ +struct sqlite3_mem_methods +{ + void* function (int) xMalloc; /** Memory allocation function */ + void function (void*) xFree; /** Free a prior allocation */ + void* function (void*,int) xRealloc; /** Resize an allocation */ + int function (void*) xSize; /** Return the size of an allocation */ + int function (int) xRoundup; /** Round up request size to allocation size */ + int function (void*) xInit; /** Initialize the memory allocator */ + void function (void*) xShutdown; /** Deinitialize the memory allocator */ + void *pAppData; /** Argument to xInit() and xShutdown() */ +} + +/** +** CAPI3REF: Configuration Options +*/ +enum +{ + SQLITE_CONFIG_SINGLETHREAD = 1, /** nil */ + SQLITE_CONFIG_MULTITHREAD = 2, /** nil */ + SQLITE_CONFIG_SERIALIZED = 3, /** nil */ + SQLITE_CONFIG_MALLOC = 4, /** sqlite3_mem_methods* */ + SQLITE_CONFIG_GETMALLOC = 5, /** sqlite3_mem_methods* */ + SQLITE_CONFIG_SCRATCH = 6, /** void*, int sz, int N */ + SQLITE_CONFIG_PAGECACHE = 7, /** void*, int sz, int N */ + SQLITE_CONFIG_HEAP = 8, /** void*, int nByte, int min */ + SQLITE_CONFIG_MEMSTATUS = 9, /** boolean */ + SQLITE_CONFIG_MUTEX = 10, /** sqlite3_mutex_methods* */ + SQLITE_CONFIG_GETMUTEX = 11, /** sqlite3_mutex_methods* */ +/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ + SQLITE_CONFIG_LOOKASIDE = 13, /** int int */ + SQLITE_CONFIG_PCACHE = 14, /** sqlite3_pcache_methods* */ + SQLITE_CONFIG_GETPCACHE = 15, /** sqlite3_pcache_methods* */ + SQLITE_CONFIG_LOG = 16, /** xFunc, void* */ + SQLITE_CONFIG_URI = 17, + SQLITE_CONFIG_PCACHE2 = 18, + SQLITE_CONFIG_GETPCACHE2 = 19, + SQLITE_CONFIG_COVERING_INDEX_SCAN = 20, + SQLITE_CONFIG_SQLLOG = 21, + SQLITE_CONFIG_MMAP_SIZE = 22, + SQLITE_CONFIG_WIN32_HEAPSIZE = 23, + SQLITE_CONFIG_PCACHE_HDRSZ = 24, + SQLITE_CONFIG_PMASZ = 25, +} + +/** +** CAPI3REF: Database Connection Configuration Options +*/ +enum +{ + SQLITE_DBCONFIG_LOOKASIDE = 1001, /** void* int int */ + SQLITE_DBCONFIG_ENABLE_FKEY = 1002, /** int int* */ + SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003 /** int int* */ +} + + +/** +** CAPI3REF: Enable Or Disable Extended Result Codes +*/ +int sqlite3_extended_result_codes(sqlite3*, int onoff); + +/** +** CAPI3REF: Last Insert Rowid +*/ +sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*); + +/** +** CAPI3REF: Count The Number Of Rows Modified +*/ +int sqlite3_changes(sqlite3*); + +/** +** CAPI3REF: Total Number Of Rows Modified +*/ +int sqlite3_total_changes(sqlite3*); + +/** +** CAPI3REF: Interrupt A Long-Running Query +*/ +void sqlite3_interrupt(sqlite3*); + +/** +** CAPI3REF: Determine If An SQL Statement Is Complete +*/ +int sqlite3_complete(const char *sql); +/// Ditto +int sqlite3_complete16(const void *sql); + +/** +** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors +*/ +int sqlite3_busy_handler(sqlite3*, int function (void*,int), void*); + +/** +** CAPI3REF: Set A Busy Timeout +*/ +int sqlite3_busy_timeout(sqlite3*, int ms); + +/** +** CAPI3REF: Convenience Routines For Running Queries +*/ +int sqlite3_get_table( + sqlite3 *db, /** An open database */ + const(char)*zSql, /** SQL to be evaluated */ + char ***pazResult, /** Results of the query */ + int *pnRow, /** Number of result rows written here */ + int *pnColumn, /** Number of result columns written here */ + char **pzErrmsg /** Error msg written here */ +); +/// +void sqlite3_free_table(char **result); + +/** +** CAPI3REF: Formatted String Printing Functions +*/ +char *sqlite3_mprintf(const char*,...); +char *sqlite3_vmprintf(const char*, va_list); +char *sqlite3_snprintf(int,char*,const char*, ...); +char *sqlite3_vsnprintf(int,char*,const char*, va_list); + +/** +** CAPI3REF: Memory Allocation Subsystem +*/ +void *sqlite3_malloc(int); +/// Ditto +void *sqlite3_malloc64(sqlite3_uint64); +/// Ditto +void *sqlite3_realloc(void*, int); +/// Ditto +void *sqlite3_realloc64(void*, sqlite3_uint64); +/// Ditto +void sqlite3_free(void*); +/// Ditto +sqlite3_uint64 sqlite3_msize(void*); + +/** +** CAPI3REF: Memory Allocator Statistics +*/ +sqlite3_int64 sqlite3_memory_used(); +sqlite3_int64 sqlite3_memory_highwater(int resetFlag); + +/** +** CAPI3REF: Pseudo-Random Number Generator +*/ +void sqlite3_randomness(int N, void *P); + +/** +** CAPI3REF: Compile-Time Authorization Callbacks +*/ +int sqlite3_set_authorizer( + sqlite3*, + int function (void*,int,const char*,const char*,const char*,const char*) xAuth, + void *pUserData +); + +/** +** CAPI3REF: Authorizer Return Codes +*/ +enum +{ + SQLITE_DENY = 1, /** Abort the SQL statement with an error */ + SQLITE_IGNORE = 2 /** Don't allow access, but don't generate an error */ +} + +/** +** CAPI3REF: Authorizer Action Codes +*/ +/******************************************* 3rd ************ 4th ***********/ +enum +{ + SQLITE_CREATE_INDEX = 1, /** Index Name Table Name */ + SQLITE_CREATE_TABLE = 2, /** Table Name NULL */ + SQLITE_CREATE_TEMP_INDEX = 3, /** Index Name Table Name */ + SQLITE_CREATE_TEMP_TABLE = 4, /** Table Name NULL */ + SQLITE_CREATE_TEMP_TRIGGER = 5, /** Trigger Name Table Name */ + SQLITE_CREATE_TEMP_VIEW = 6, /** View Name NULL */ + SQLITE_CREATE_TRIGGER = 7, /** Trigger Name Table Name */ + SQLITE_CREATE_VIEW = 8, /** View Name NULL */ + SQLITE_DELETE = 9, /** Table Name NULL */ + SQLITE_DROP_INDEX = 10, /** Index Name Table Name */ + SQLITE_DROP_TABLE = 11, /** Table Name NULL */ + SQLITE_DROP_TEMP_INDEX = 12, /** Index Name Table Name */ + SQLITE_DROP_TEMP_TABLE = 13, /** Table Name NULL */ + SQLITE_DROP_TEMP_TRIGGER = 14, /** Trigger Name Table Name */ + SQLITE_DROP_TEMP_VIEW = 15, /** View Name NULL */ + SQLITE_DROP_TRIGGER = 16, /** Trigger Name Table Name */ + SQLITE_DROP_VIEW = 17, /** View Name NULL */ + SQLITE_INSERT = 18, /** Table Name NULL */ + SQLITE_PRAGMA = 19, /** Pragma Name 1st arg or NULL */ + SQLITE_READ = 20, /** Table Name Column Name */ + SQLITE_SELECT = 21, /** NULL NULL */ + SQLITE_TRANSACTION = 22, /** Operation NULL */ + SQLITE_UPDATE = 23, /** Table Name Column Name */ + SQLITE_ATTACH = 24, /** Filename NULL */ + SQLITE_DETACH = 25, /** Database Name NULL */ + SQLITE_ALTER_TABLE = 26, /** Database Name Table Name */ + SQLITE_REINDEX = 27, /** Index Name NULL */ + SQLITE_ANALYZE = 28, /** Table Name NULL */ + SQLITE_CREATE_VTABLE = 29, /** Table Name Module Name */ + SQLITE_DROP_VTABLE = 30, /** Table Name Module Name */ + SQLITE_FUNCTION = 31, /** NULL Function Name */ + SQLITE_SAVEPOINT = 32, /** Operation Savepoint Name */ + SQLITE_COPY = 0, /** No longer used */ + SQLITE_RECURSIVE = 33 +} + +/** +** CAPI3REF: Tracing And Profiling Functions +*/ +void *sqlite3_trace(sqlite3*, void function (void*,const char*) xTrace, void*); +/// Ditto +void *sqlite3_profile(sqlite3*, void function (void*,const char*,sqlite3_uint64) xProfile, void*); + +/** +** CAPI3REF: Query Progress Callbacks +*/ +void sqlite3_progress_handler(sqlite3*, int, int function (void*), void*); + +/** +** CAPI3REF: Opening A New Database Connection +*/ +int sqlite3_open( + const(char)*filename, /** Database filename (UTF-8) */ + sqlite3 **ppDb /** OUT: SQLite db handle */ +); +/// Ditto +int sqlite3_open16( + const(void)*filename, /** Database filename (UTF-16) */ + sqlite3 **ppDb /** OUT: SQLite db handle */ +); +/// Ditto +int sqlite3_open_v2( + const(char)*filename, /** Database filename (UTF-8) */ + sqlite3 **ppDb, /** OUT: SQLite db handle */ + int flags, /** Flags */ + const(char)*zVfs /** Name of VFS module to use */ +); + +/* +** CAPI3REF: Obtain Values For URI Parameters +*/ +const(char)* sqlite3_uri_parameter(const(char)* zFilename, const(char)* zParam); +/// Ditto +int sqlite3_uri_boolean(const(char)* zFile, const(char)* zParam, int bDefault); +/// Ditto +sqlite3_int64 sqlite3_uri_int64(const char*, const char*, sqlite3_int64); + +/** +** CAPI3REF: Error Codes And Messages +*/ +int sqlite3_errcode(sqlite3 *db); +/// Ditto +int sqlite3_extended_errcode(sqlite3 *db); +/// Ditto +const(char)* sqlite3_errmsg(sqlite3*); +/// Ditto +const(void)* sqlite3_errmsg16(sqlite3*); +/// Ditto +const(char)* sqlite3_errstr(int); + +/** +** CAPI3REF: SQL Statement Object +*/ +struct sqlite3_stmt; + +/** +** CAPI3REF: Run-time Limits +*/ +int sqlite3_limit(sqlite3*, int id, int newVal); + +/** +** CAPI3REF: Run-Time Limit Categories +*/ +enum +{ + SQLITE_LIMIT_LENGTH = 0, + SQLITE_LIMIT_SQL_LENGTH = 1, + SQLITE_LIMIT_COLUMN = 2, + SQLITE_LIMIT_EXPR_DEPTH = 3, + SQLITE_LIMIT_COMPOUND_SELECT = 4, + SQLITE_LIMIT_VDBE_OP = 5, + SQLITE_LIMIT_FUNCTION_ARG = 6, + SQLITE_LIMIT_ATTACHED = 7, + SQLITE_LIMIT_LIKE_PATTERN_LENGTH = 8, + SQLITE_LIMIT_VARIABLE_NUMBER = 9, + SQLITE_LIMIT_TRIGGER_DEPTH = 10, + SQLITE_LIMIT_WORKER_THREADS = 11, +} + +/** +** CAPI3REF: Compiling An SQL Statement +*/ +int sqlite3_prepare( + sqlite3 *db, /** Database handle */ + const(char)*zSql, /** SQL statement, UTF-8 encoded */ + int nByte, /** Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /** OUT: Statement handle */ + const(char*)*pzTail /** OUT: Pointer to unused portion of zSql */ +); +/// Ditto +int sqlite3_prepare_v2( + sqlite3 *db, /** Database handle */ + const(char)*zSql, /** SQL statement, UTF-8 encoded */ + int nByte, /** Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /** OUT: Statement handle */ + const(char*)*pzTail /** OUT: Pointer to unused portion of zSql */ +); +/// Ditto +int sqlite3_prepare16( + sqlite3 *db, /** Database handle */ + const(void)*zSql, /** SQL statement, UTF-16 encoded */ + int nByte, /** Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /** OUT: Statement handle */ + const(void*)*pzTail /** OUT: Pointer to unused portion of zSql */ +); +/// Ditto +int sqlite3_prepare16_v2( + sqlite3 *db, /** Database handle */ + const(void)*zSql, /** SQL statement, UTF-16 encoded */ + int nByte, /** Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /** OUT: Statement handle */ + const(void*)*pzTail /** OUT: Pointer to unused portion of zSql */ +); + +/** +** CAPI3REF: Retrieving Statement SQL +*/ +const(char)* sqlite3_sql(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Determine If An SQL Statement Writes The Database +*/ +int sqlite3_stmt_readonly(sqlite3_stmt *pStmt); + +/** +** CAPI3REF: Determine If A Prepared Statement Has Been Reset +*/ +int sqlite3_stmt_busy(sqlite3_stmt*); + + +/** +** CAPI3REF: Dynamically Typed Value Object +*/ +struct sqlite3_value; + +/** +** CAPI3REF: SQL Function Context Object +*/ +struct sqlite3_context; + +/** +** CAPI3REF: Binding Values To Prepared Statements +*/ +int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void function (void*)); +/// Ditto +int sqlite3_bind_blob64(sqlite3_stmt*, int, const void*, sqlite3_uint64,void function (void*)); +/// Ditto +int sqlite3_bind_double(sqlite3_stmt*, int, double); +/// Ditto +int sqlite3_bind_int(sqlite3_stmt*, int, int); +/// Ditto +int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64); +/// Ditto +int sqlite3_bind_null(sqlite3_stmt*, int); +/// Ditto +int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void function (void*)); +/// Ditto +int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void function (void*)); +/// Ditto +int sqlite3_bind_text64(sqlite3_stmt*, int, const char*, sqlite3_uint64,void function (void*), ubyte encoding); +/// Ditto +int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*); +/// Ditto +int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n); +/// Ditto +int sqlite3_bind_zeroblob64(sqlite3_stmt*, int, sqlite3_uint64 n); + +/** +** CAPI3REF: Number Of SQL Parameters +*/ +int sqlite3_bind_parameter_count(sqlite3_stmt*); + +/** +** CAPI3REF: Name Of A Host Parameter +*/ +const(char)* sqlite3_bind_parameter_name(sqlite3_stmt*, int); + +/** +** CAPI3REF: Index Of A Parameter With A Given Name +*/ +int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName); + +/** +** CAPI3REF: Reset All Bindings On A Prepared Statement +*/ +int sqlite3_clear_bindings(sqlite3_stmt*); + +/** +** CAPI3REF: Number Of Columns In A Result Set +*/ +int sqlite3_column_count(sqlite3_stmt *pStmt); + +/** +** CAPI3REF: Column Names In A Result Set +*/ +const(char)* sqlite3_column_name(sqlite3_stmt*, int N); +/// Ditto +const(void)* sqlite3_column_name16(sqlite3_stmt*, int N); + +/** +** CAPI3REF: Source Of Data In A Query Result +*/ +const(char)* sqlite3_column_database_name(sqlite3_stmt*,int); +/// Ditto +const(void)* sqlite3_column_database_name16(sqlite3_stmt*,int); +/// Ditto +const(char)* sqlite3_column_table_name(sqlite3_stmt*,int); +/// Ditto +const (void)* sqlite3_column_table_name16(sqlite3_stmt*,int); +/// Ditto +const (char)* sqlite3_column_origin_name(sqlite3_stmt*,int); +/// Ditto +const (void)* sqlite3_column_origin_name16(sqlite3_stmt*,int); + +/** +** CAPI3REF: Declared Datatype Of A Query Result +*/ +const (char)* sqlite3_column_decltype(sqlite3_stmt*,int); +/// Ditto +const (void)* sqlite3_column_decltype16(sqlite3_stmt*,int); + +/** +** CAPI3REF: Evaluate An SQL Statement +*/ +int sqlite3_step(sqlite3_stmt*); + +/** +** CAPI3REF: Number of columns in a result set +*/ +int sqlite3_data_count(sqlite3_stmt *pStmt); + +/** +** CAPI3REF: Fundamental Datatypes +*/ +enum +{ + SQLITE_INTEGER = 1, + SQLITE_FLOAT = 2, + SQLITE_BLOB = 4, + SQLITE_NULL = 5, + SQLITE3_TEXT = 3 +} + +/** +** CAPI3REF: Result Values From A Query +*/ +const (void)* sqlite3_column_blob(sqlite3_stmt*, int iCol); +/// Ditto +int sqlite3_column_bytes(sqlite3_stmt*, int iCol); +/// Ditto +int sqlite3_column_bytes16(sqlite3_stmt*, int iCol); +/// Ditto +double sqlite3_column_double(sqlite3_stmt*, int iCol); +/// Ditto +int sqlite3_column_int(sqlite3_stmt*, int iCol); +/// Ditto +sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol); +/// Ditto +const (char)* sqlite3_column_text(sqlite3_stmt*, int iCol); +/// Ditto +const (void)* sqlite3_column_text16(sqlite3_stmt*, int iCol); +/// Ditto +int sqlite3_column_type(sqlite3_stmt*, int iCol); +/// Ditto +sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol); + +/** +** CAPI3REF: Destroy A Prepared Statement Object +*/ +int sqlite3_finalize(sqlite3_stmt *pStmt); + +/** +** CAPI3REF: Reset A Prepared Statement Object +*/ +int sqlite3_reset(sqlite3_stmt *pStmt); + +/** +** CAPI3REF: Create Or Redefine SQL Functions +*/ +int sqlite3_create_function( + sqlite3 *db, + const(char)*zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void function (sqlite3_context*,int,sqlite3_value**) xFunc, + void function (sqlite3_context*,int,sqlite3_value**) xStep, + void function (sqlite3_context*) xFinal +); +/// Ditto +int sqlite3_create_function16( + sqlite3 *db, + const(void)*zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void function (sqlite3_context*,int,sqlite3_value**) xFunc, + void function (sqlite3_context*,int,sqlite3_value**) xStep, + void function (sqlite3_context*) xFinal +); +/// Ditto +int sqlite3_create_function_v2( + sqlite3 *db, + const(char)*zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void function (sqlite3_context*,int,sqlite3_value**) xFunc, + void function (sqlite3_context*,int,sqlite3_value**) xStep, + void function (sqlite3_context*) xFinal, + void function (void*) xDestroy +); + +/** +** CAPI3REF: Text Encodings +** +** These constant define integer codes that represent the various +** text encodings supported by SQLite. +*/ +enum +{ + SQLITE_UTF8 = 1, + SQLITE_UTF16LE = 2, + SQLITE_UTF16BE = 3 +} +/// Ditto +enum +{ + SQLITE_UTF16 = 4, /** Use native byte order */ + SQLITE_ANY = 5, /** sqlite3_create_function only */ + SQLITE_UTF16_ALIGNED = 8 /** sqlite3_create_collation only */ +} + +/** +** CAPI3REF: Function Flags +*/ +enum SQLITE_DETERMINISTIC = 0x800; + +/** +** CAPI3REF: Deprecated Functions +*/ +deprecated int sqlite3_aggregate_count(sqlite3_context*); +deprecated int sqlite3_expired(sqlite3_stmt*); +deprecated int sqlite3_transfer_bindings(sqlite3_stmt*, sqlite3_stmt*); +deprecated int sqlite3_global_recover(); +deprecated void sqlite3_thread_cleanup(); +deprecated int sqlite3_memory_alarm(void function(void*,sqlite3_int64,int),void*,sqlite3_int64); + +/** +** CAPI3REF: Obtaining SQL Function Parameter Values +*/ +const (void)* sqlite3_value_blob(sqlite3_value*); +/// Ditto +int sqlite3_value_bytes(sqlite3_value*); +/// Ditto +int sqlite3_value_bytes16(sqlite3_value*); +/// Ditto +double sqlite3_value_double(sqlite3_value*); +/// Ditto +int sqlite3_value_int(sqlite3_value*); +/// Ditto +sqlite3_int64 sqlite3_value_int64(sqlite3_value*); +/// Ditto +const (char)* sqlite3_value_text(sqlite3_value*); +/// Ditto +const (void)* sqlite3_value_text16(sqlite3_value*); +/// Ditto +const (void)* sqlite3_value_text16le(sqlite3_value*); +/// Ditto +const (void)* sqlite3_value_text16be(sqlite3_value*); +/// Ditto +int sqlite3_value_type(sqlite3_value*); +/// Ditto +int sqlite3_value_numeric_type(sqlite3_value*); + +/* +** CAPI3REF: Finding The Subtype Of SQL Values +*/ +uint sqlite3_value_subtype(sqlite3_value*); + +/* +** CAPI3REF: Copy And Free SQL Values +*/ +sqlite3_value* sqlite3_value_dup(const sqlite3_value*); +void sqlite3_value_free(sqlite3_value*); + +/** +** CAPI3REF: Obtain Aggregate Function Context +*/ +void *sqlite3_aggregate_context(sqlite3_context*, int nBytes); + +/** +** CAPI3REF: User Data For Functions +*/ +void *sqlite3_user_data(sqlite3_context*); + +/** +** CAPI3REF: Database Connection For Functions +*/ +sqlite3 *sqlite3_context_db_handle(sqlite3_context*); + +/** +** CAPI3REF: Function Auxiliary Data +*/ +void *sqlite3_get_auxdata(sqlite3_context*, int N); +/// Ditto +void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void function (void*)); + + +/** +** CAPI3REF: Constants Defining Special Destructor Behavior +*/ +alias sqlite3_destructor_type = void function (void*); +/// Ditto +enum +{ + SQLITE_STATIC = (cast(sqlite3_destructor_type) 0), + SQLITE_TRANSIENT = (cast (sqlite3_destructor_type) -1) +} + +/** +** CAPI3REF: Setting The Result Of An SQL Function +*/ +void sqlite3_result_blob(sqlite3_context*, const void*, int, void function(void*)); +/// Ditto +void sqlite3_result_blob64(sqlite3_context*,const void*,sqlite3_uint64,void function(void*)); +/// Ditto +void sqlite3_result_double(sqlite3_context*, double); +/// Ditto +void sqlite3_result_error(sqlite3_context*, const char*, int); +/// Ditto +void sqlite3_result_error16(sqlite3_context*, const void*, int); +/// Ditto +void sqlite3_result_error_toobig(sqlite3_context*); +/// Ditto +void sqlite3_result_error_nomem(sqlite3_context*); +/// Ditto +void sqlite3_result_error_code(sqlite3_context*, int); +/// Ditto +void sqlite3_result_int(sqlite3_context*, int); +/// Ditto +void sqlite3_result_int64(sqlite3_context*, sqlite3_int64); +/// Ditto +void sqlite3_result_null(sqlite3_context*); +/// Ditto +void sqlite3_result_text(sqlite3_context*, const char*, int, void function(void*)); +/// Ditto +void sqlite3_result_text64(sqlite3_context*, const char*,sqlite3_uint64,void function(void*), ubyte encoding); +/// Ditto +void sqlite3_result_text16(sqlite3_context*, const void*, int, void function(void*)); +/// Ditto +void sqlite3_result_text16le(sqlite3_context*, const void*, int, void function(void*)); +/// Ditto +void sqlite3_result_text16be(sqlite3_context*, const void*, int, void function(void*)); +/// Ditto +void sqlite3_result_value(sqlite3_context*, sqlite3_value*); +/// Ditto +void sqlite3_result_zeroblob(sqlite3_context*, int n); +/// Ditto +int sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n); + +/* +** CAPI3REF: Setting The Subtype Of An SQL Function +*/ +void sqlite3_result_subtype(sqlite3_context*,uint); + +/** +** CAPI3REF: Define New Collating Sequences +*/ +int sqlite3_create_collation( + sqlite3*, + const(char)*zName, + int eTextRep, + void *pArg, + int function (void*,int,const void*,int,const void*) xCompare +); +/// Ditto +int sqlite3_create_collation_v2( + sqlite3*, + const(char)*zName, + int eTextRep, + void *pArg, + int function (void*,int,const void*,int,const void*) xCompare, + void function (void*) xDestroy +); +/// Ditto +int sqlite3_create_collation16( + sqlite3*, + const(void)*zName, + int eTextRep, + void *pArg, + int function (void*,int,const void*,int,const void*) xCompare +); + +/** +** CAPI3REF: Collation Needed Callbacks +*/ +int sqlite3_collation_needed( + sqlite3*, + void*, + void function (void*,sqlite3*,int eTextRep,const char*) +); +/// Ditto +int sqlite3_collation_needed16( + sqlite3*, + void*, + void function (void*,sqlite3*,int eTextRep,const void*) +); + +/// +int sqlite3_key( + sqlite3 *db, /** Database to be rekeyed */ + const(void)*pKey, int nKey /** The key */ +); +/// Ditto +int sqlite3_key_v2( + sqlite3 *db, /* Database to be rekeyed */ + const(char)* zDbName, /* Name of the database */ + const(void)* pKey, int nKey /* The key */ +); + +/** +** Change the key on an open database. If the current database is not +** encrypted, this routine will encrypt it. If pNew == 0 or nNew == 0, the +** database is decrypted. +** +** The code to implement this API is not available in the public release +** of SQLite. +*/ +int sqlite3_rekey( + sqlite3 *db, /** Database to be rekeyed */ + const(void)*pKey, int nKey /** The new key */ +); +int sqlite3_rekey_v2( + sqlite3 *db, /* Database to be rekeyed */ + const(char)* zDbName, /* Name of the database */ + const(void)* pKey, int nKey /* The new key */ +); + +/** +** Specify the activation key for a SEE database. Unless +** activated, none of the SEE routines will work. +*/ +void sqlite3_activate_see( + const(char)*zPassPhrase /** Activation phrase */ +); + +/** +** Specify the activation key for a CEROD database. Unless +** activated, none of the CEROD routines will work. +*/ +void sqlite3_activate_cerod( + const(char)*zPassPhrase /** Activation phrase */ +); + +/** +** CAPI3REF: Suspend Execution For A Short Time +*/ +int sqlite3_sleep(int); + +/** +** CAPI3REF: Name Of The Folder Holding Temporary Files +*/ +extern char *sqlite3_temp_directory; + +/** +** CAPI3REF: Name Of The Folder Holding Database Files +*/ +extern char *sqlite3_data_directory; + +/** +** CAPI3REF: Test For Auto-Commit Mode +*/ +int sqlite3_get_autocommit(sqlite3*); + +/** +** CAPI3REF: Find The Database Handle Of A Prepared Statement +*/ +sqlite3 *sqlite3_db_handle(sqlite3_stmt*); + +/** +** CAPI3REF: Return The Filename For A Database Connection +*/ +const(char)* sqlite3_db_filename(sqlite3 *db, const char* zDbName); + +/** +** CAPI3REF: Determine if a database is read-only +*/ +int sqlite3_db_readonly(sqlite3 *db, const char * zDbName); + +/* +** CAPI3REF: Find the next prepared statement +*/ +sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt); + +/** +** CAPI3REF: Commit And Rollback Notification Callbacks +*/ +void *sqlite3_commit_hook(sqlite3*, int function (void*), void*); +/// Ditto +void *sqlite3_rollback_hook(sqlite3*, void function (void *), void*); + +/** +** CAPI3REF: Data Change Notification Callbacks +*/ +void *sqlite3_update_hook( + sqlite3*, + void function (void *,int ,char *, char *, sqlite3_int64), + void* +); + +/** +** CAPI3REF: Enable Or Disable Shared Pager Cache +*/ +int sqlite3_enable_shared_cache(int); + +/** +** CAPI3REF: Attempt To Free Heap Memory +*/ +int sqlite3_release_memory(int); + +/** +** CAPI3REF: Free Memory Used By A Database Connection +*/ +int sqlite3_db_release_memory(sqlite3*); + +/* +** CAPI3REF: Impose A Limit On Heap Size +*/ +sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N); + +/** +** CAPI3REF: Deprecated Soft Heap Limit Interface +*/ +deprecated void sqlite3_soft_heap_limit(int N); + +/** +** CAPI3REF: Extract Metadata About A Column Of A Table +*/ +int sqlite3_table_column_metadata( + sqlite3 *db, /** Connection handle */ + const(char)*zDbName, /** Database name or NULL */ + const(char)*zTableName, /** Table name */ + const(char)*zColumnName, /** Column name */ + char **pzDataType, /** OUTPUT: Declared data type */ + char **pzCollSeq, /** OUTPUT: Collation sequence name */ + int *pNotNull, /** OUTPUT: True if NOT NULL constraint exists */ + int *pPrimaryKey, /** OUTPUT: True if column part of PK */ + int *pAutoinc /** OUTPUT: True if column is auto-increment */ +); + +/** +** CAPI3REF: Load An Extension +*/ +int sqlite3_load_extension( + sqlite3 *db, /** Load the extension into this database connection */ + const(char)*zFile, /** Name of the shared library containing extension */ + const(char)*zProc, /** Entry point. Derived from zFile if 0 */ + char **pzErrMsg /** Put error message here if not 0 */ +); + +/** +** CAPI3REF: Enable Or Disable Extension Loading +*/ +int sqlite3_enable_load_extension(sqlite3 *db, int onoff); + +/** +** CAPI3REF: Automatically Load Statically Linked Extensions +*/ +int sqlite3_auto_extension(void function () xEntryPoint); + +/** +** CAPI3REF: Cancel Automatic Extension Loading +*/ +int sqlite3_cancel_auto_extension(void function() xEntryPoint); + +/** +** CAPI3REF: Reset Automatic Extension Loading +*/ +void sqlite3_reset_auto_extension(); + +/** +** The interface to the virtual-table mechanism is currently considered +** to be experimental. The interface might change in incompatible ways. +** If this is a problem for you, do not use the interface at this time. +** +** When the virtual-table mechanism stabilizes, we will declare the +** interface fixed, support it indefinitely, and remove this comment. +*/ + +/** +** CAPI3REF: Virtual Table Object +*/ + +alias mapFunction = void function (sqlite3_context*,int,sqlite3_value**); + +/// Ditto +struct sqlite3_module +{ + int iVersion; + int function (sqlite3*, void *pAux, + int argc, const char **argv, + sqlite3_vtab **ppVTab, char**) xCreate; + int function (sqlite3*, void *pAux, + int argc, const char **argv, + sqlite3_vtab **ppVTab, char**) xConnect; + int function (sqlite3_vtab *pVTab, sqlite3_index_info*) xBestIndex; + int function (sqlite3_vtab *pVTab) xDisconnect; + int function (sqlite3_vtab *pVTab) xDestroy; + int function (sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor) xOpen; + int function (sqlite3_vtab_cursor*) xClose; + int function (sqlite3_vtab_cursor*, int idxNum, const char *idxStr, + int argc, sqlite3_value **argv) xFilter; + int function (sqlite3_vtab_cursor*) xNext; + int function (sqlite3_vtab_cursor*) xEof; + int function (sqlite3_vtab_cursor*, sqlite3_context*, int) xColumn; + int function (sqlite3_vtab_cursor*, sqlite3_int64 *pRowid) xRowid; + int function (sqlite3_vtab *, int, sqlite3_value **, sqlite3_int64 *) xUpdate; + int function (sqlite3_vtab *pVTab) xBegin; + int function (sqlite3_vtab *pVTab) xSync; + int function (sqlite3_vtab *pVTab) xCommit; + int function (sqlite3_vtab *pVTab) xRollback; + int function (sqlite3_vtab *pVtab, int nArg, const char *zName, + mapFunction*, + void **ppArg) xFindFunction; + int function (sqlite3_vtab *pVtab, const char *zNew) xRename; + int function (sqlite3_vtab *pVTab, int) xSavepoint; + int function (sqlite3_vtab *pVTab, int) xRelease; + int function (sqlite3_vtab *pVTab, int) xRollbackTo; +} + +/** +** CAPI3REF: Virtual Table Indexing Information +*/ +struct sqlite3_index_info +{ + struct sqlite3_index_constraint + { + int iColumn; /** Column on left-hand side of constraint */ + char op; /** Constraint operator */ + char usable; /** True if this constraint is usable */ + int iTermOffset; /** Used internally - xBestIndex should ignore */ + } + struct sqlite3_index_orderby + { + int iColumn; /** Column number */ + char desc; /** True for DESC. False for ASC. */ + } + struct sqlite3_index_constraint_usage + { + int argvIndex; /** if >0, constraint is part of argv to xFilter */ + char omit; /** Do not code a test for this constraint */ + } + /* Inputs */ + int nConstraint; /** Number of entries in aConstraint */ + sqlite3_index_constraint* aConstraint; /** Table of WHERE clause constraints */ + int nOrderBy; /** Number of terms in the ORDER BY clause */ + sqlite3_index_orderby *aOrderBy; /** The ORDER BY clause */ + /* Outputs */ + sqlite3_index_constraint_usage *aConstraintUsage; + int idxNum; /** Number used to identify the index */ + char *idxStr; /** String, possibly obtained from sqlite3_malloc */ + int needToFreeIdxStr; /** Free idxStr using sqlite3_free() if true */ + int orderByConsumed; /** True if output is already ordered */ + double estimatedCost; /** Estimated cost of using this index */ + sqlite3_int64 estimatedRows; + int idxFlags; + sqlite3_uint64 colUsed; +} + +/** +** CAPI3REF: Virtual Table Constraint Operator Codes +*/ +enum +{ + SQLITE_INDEX_SCAN_UNIQUE = 1, + SQLITE_INDEX_CONSTRAINT_EQ = 2, + SQLITE_INDEX_CONSTRAINT_GT = 4, + SQLITE_INDEX_CONSTRAINT_LE = 8, + SQLITE_INDEX_CONSTRAINT_LT = 16, + SQLITE_INDEX_CONSTRAINT_GE = 32, + SQLITE_INDEX_CONSTRAINT_MATCH = 64, + SQLITE_INDEX_CONSTRAINT_LIKE = 65, + SQLITE_INDEX_CONSTRAINT_GLOB = 66, + SQLITE_INDEX_CONSTRAINT_REGEXP = 67, +} + +/** +** CAPI3REF: Register A Virtual Table Implementation +*/ +int sqlite3_create_module( + sqlite3 *db, /* SQLite connection to register module with */ + const(char)*zName, /* Name of the module */ + const(sqlite3_module)*p, /* Methods for the module */ + void *pClientData /* Client data for xCreate/xConnect */ +); +/// Ditto +int sqlite3_create_module_v2( + sqlite3 *db, /* SQLite connection to register module with */ + const(char)*zName, /* Name of the module */ + const(sqlite3_module)*p, /* Methods for the module */ + void *pClientData, /* Client data for xCreate/xConnect */ + void function (void*) xDestroy /* Module destructor function */ +); + +/** +** CAPI3REF: Virtual Table Instance Object +*/ +struct sqlite3_vtab +{ + const(sqlite3_module)*pModule; /** The module for this virtual table */ + int nRef; /** NO LONGER USED */ + char *zErrMsg; /** Error message from sqlite3_mprintf() */ + /* Virtual table implementations will typically add additional fields */ +} + +/** +** CAPI3REF: Virtual Table Cursor Object +*/ +struct sqlite3_vtab_cursor +{ + sqlite3_vtab *pVtab; /** Virtual table of this cursor */ + /* Virtual table implementations will typically add additional fields */ +} + +/** +** CAPI3REF: Declare The Schema Of A Virtual Table +*/ +int sqlite3_declare_vtab(sqlite3*, const char *zSQL); + +/** +** CAPI3REF: Overload A Function For A Virtual Table +*/ +int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg); + +/** +** The interface to the virtual-table mechanism defined above (back up +** to a comment remarkably similar to this one) is currently considered +** to be experimental. The interface might change in incompatible ways. +** If this is a problem for you, do not use the interface at this time. +** +** When the virtual-table mechanism stabilizes, we will declare the +** interface fixed, support it indefinitely, and remove this comment. +*/ + +/* +** CAPI3REF: A Handle To An Open BLOB +*/ +struct sqlite3_blob; + +/** +** CAPI3REF: Open A BLOB For Incremental I/O +*/ +int sqlite3_blob_open( + sqlite3*, + const(char)* zDb, + const(char)* zTable, + const(char)* zColumn, + sqlite3_int64 iRow, + int flags, + sqlite3_blob **ppBlob +); + +/** +** CAPI3REF: Move a BLOB Handle to a New Row +*/ +int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64); + +/** +** CAPI3REF: Close A BLOB Handle +*/ +int sqlite3_blob_close(sqlite3_blob *); + +/** +** CAPI3REF: Return The Size Of An Open BLOB +*/ +int sqlite3_blob_bytes(sqlite3_blob *); + +/** +** CAPI3REF: Read Data From A BLOB Incrementally +*/ +int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); + +/** +** CAPI3REF: Write Data Into A BLOB Incrementally +*/ +int sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOffset); + +/** +** CAPI3REF: Virtual File System Objects +*/ +sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName); +/// Ditto +int sqlite3_vfs_register(sqlite3_vfs*, int makeDflt); +/// Ditto +int sqlite3_vfs_unregister(sqlite3_vfs*); + +/** +** CAPI3REF: Mutexes +*/ +sqlite3_mutex *sqlite3_mutex_alloc(int); +/// Ditto +void sqlite3_mutex_free(sqlite3_mutex*); +/// Ditto +void sqlite3_mutex_enter(sqlite3_mutex*); +/// Ditto +int sqlite3_mutex_try(sqlite3_mutex*); +/// Ditto +void sqlite3_mutex_leave(sqlite3_mutex*); + +/** +** CAPI3REF: Mutex Methods Object +*/ +struct sqlite3_mutex_methods +{ + int function () xMutexInit; + int function () xMutexEnd; + sqlite3_mutex* function (int) xMutexAlloc; + void function (sqlite3_mutex *) xMutexFree; + void function (sqlite3_mutex *) xMutexEnter; + int function (sqlite3_mutex *) xMutexTry; + void function (sqlite3_mutex *) xMutexLeave; + int function (sqlite3_mutex *) xMutexHeld; + int function (sqlite3_mutex *) xMutexNotheld; +} + +/** +** CAPI3REF: Mutex Verification Routines +*/ + +//#ifndef NDEBUG +int sqlite3_mutex_held(sqlite3_mutex*); +/// Ditto +int sqlite3_mutex_notheld(sqlite3_mutex*); +//#endif + +/** +** CAPI3REF: Mutex Types +*/ +enum +{ + SQLITE_MUTEX_FAST = 0, + SQLITE_MUTEX_RECURSIVE = 1, + SQLITE_MUTEX_STATIC_MASTER = 2, + SQLITE_MUTEX_STATIC_MEM = 3, /** sqlite3_malloc() */ + SQLITE_MUTEX_STATIC_MEM2 = 4, /** NOT USED */ + SQLITE_MUTEX_STATIC_OPEN = 4, /** sqlite3BtreeOpen() */ + SQLITE_MUTEX_STATIC_PRNG = 5, /** sqlite3_random() */ + SQLITE_MUTEX_STATIC_LRU = 6, /** lru page list */ + SQLITE_MUTEX_STATIC_LRU2 = 7, /** NOT USED */ + SQLITE_MUTEX_STATIC_PMEM = 7, /** sqlite3PageMalloc() */ + SQLITE_MUTEX_STATIC_APP1 = 8, /** For use by application */ + SQLITE_MUTEX_STATIC_APP2 = 9, /** For use by application */ + SQLITE_MUTEX_STATIC_APP3 = 10, /** For use by application */ + SQLITE_MUTEX_STATIC_VFS1 = 11, /** For use by built-in VFS */ + SQLITE_MUTEX_STATIC_VFS2 = 12, /** For use by extension VFS */ + SQLITE_MUTEX_STATIC_VFS3 = 13, /** For use by application VFS */ +} + +/** +** CAPI3REF: Retrieve the mutex for a database connection +*/ +sqlite3_mutex *sqlite3_db_mutex(sqlite3*); + +/** +** CAPI3REF: Low-Level Control Of Database Files +*/ +int sqlite3_file_control(sqlite3*, const char *zDbName, int op, void*); + +/** +** CAPI3REF: Testing Interface +*/ +int sqlite3_test_control(int op, ...); + +/** +** CAPI3REF: Testing Interface Operation Codes +*/ +enum +{ + SQLITE_TESTCTRL_FIRST = 5, + SQLITE_TESTCTRL_PRNG_SAVE = 5, + SQLITE_TESTCTRL_PRNG_RESTORE = 6, + SQLITE_TESTCTRL_PRNG_RESET = 7, + SQLITE_TESTCTRL_BITVEC_TEST = 8, + SQLITE_TESTCTRL_FAULT_INSTALL = 9, + SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS = 10, + SQLITE_TESTCTRL_PENDING_BYTE = 11, + SQLITE_TESTCTRL_ASSERT = 12, + SQLITE_TESTCTRL_ALWAYS = 13, + SQLITE_TESTCTRL_RESERVE = 14, + SQLITE_TESTCTRL_OPTIMIZATIONS = 15, + SQLITE_TESTCTRL_ISKEYWORD = 16, + SQLITE_TESTCTRL_SCRATCHMALLOC = 17, + SQLITE_TESTCTRL_LOCALTIME_FAULT = 18, + SQLITE_TESTCTRL_EXPLAIN_STMT = 19, + SQLITE_TESTCTRL_NEVER_CORRUPT = 20, + SQLITE_TESTCTRL_VDBE_COVERAGE = 21, + SQLITE_TESTCTRL_BYTEORDER = 22, + SQLITE_TESTCTRL_ISINIT = 23, + SQLITE_TESTCTRL_SORTER_MMAP = 24, + SQLITE_TESTCTRL_IMPOSTER = 25, + SQLITE_TESTCTRL_LAST = 25, +} + +/** +** CAPI3REF: SQLite Runtime Status +*/ +int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag); +/// Ditto +int sqlite3_status64(int op, long *pCurrent, long *pHighwater, int resetFlag); + +/** +** CAPI3REF: Status Parameters +*/ +enum +{ + SQLITE_STATUS_MEMORY_USED = 0, + SQLITE_STATUS_PAGECACHE_USED = 1, + SQLITE_STATUS_PAGECACHE_OVERFLOW = 2, + SQLITE_STATUS_SCRATCH_USED = 3, + SQLITE_STATUS_SCRATCH_OVERFLOW = 4, + SQLITE_STATUS_MALLOC_SIZE = 5, + SQLITE_STATUS_PARSER_STACK = 6, + SQLITE_STATUS_PAGECACHE_SIZE = 7, + SQLITE_STATUS_SCRATCH_SIZE = 8, + SQLITE_STATUS_MALLOC_COUNT = 9 +} + +/** +** CAPI3REF: Database Connection Status +*/ +int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); + +/** +** CAPI3REF: Status Parameters for database connections +*/ +enum +{ + SQLITE_DBSTATUS_LOOKASIDE_USED = 0, + SQLITE_DBSTATUS_CACHE_USED = 1, + SQLITE_DBSTATUS_SCHEMA_USED = 2, + SQLITE_DBSTATUS_STMT_USED = 3, + SQLITE_DBSTATUS_LOOKASIDE_HIT = 4, + SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE = 5, + SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL = 6, + SQLITE_DBSTATUS_CACHE_HIT = 7, + SQLITE_DBSTATUS_CACHE_MISS = 8, + SQLITE_DBSTATUS_CACHE_WRITE = 9, + SQLITE_DBSTATUS_DEFERRED_FKS = 10, + SQLITE_DBSTATUS_MAX = 10 /** Largest defined DBSTATUS */ +} + +/** +** CAPI3REF: Prepared Statement Status +*/ +int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); + +/** +** CAPI3REF: Status Parameters for prepared statements +*/ +enum +{ + SQLITE_STMTSTATUS_FULLSCAN_STEP = 1, + SQLITE_STMTSTATUS_SORT = 2, + SQLITE_STMTSTATUS_AUTOINDEX = 3, + SQLITE_STMTSTATUS_VM_STEP = 4 +} + +/** +** CAPI3REF: Custom Page Cache Object +*/ +struct sqlite3_pcache; + +/** +** CAPI3REF: Custom Page Cache Object +*/ +struct sqlite3_pcache_page +{ + void *pBuf; /* The content of the page */ + void *pExtra; /* Extra information associated with the page */ +} + +/** +** CAPI3REF: Application Defined Page Cache. +*/ +struct sqlite3_pcache_methods2 +{ + int iVersion; + void *pArg; + int function(void*) xInit; + void function(void*) xShutdown; + sqlite3_pcache * function(int szPage, int szExtra, int bPurgeable) xCreate; + void function(sqlite3_pcache*, int nCachesize) xCachesize; + int function(sqlite3_pcache*) xPagecount; + sqlite3_pcache_page * function(sqlite3_pcache*, uint key, int createFlag) xFetch; + void function(sqlite3_pcache*, sqlite3_pcache_page*, int discard) xUnpin; + void function(sqlite3_pcache*, sqlite3_pcache_page*, + uint oldKey, uint newKey) xRekey; + void function(sqlite3_pcache*, uint iLimit) xTruncate; + void function(sqlite3_pcache*) xDestroy; + void function(sqlite3_pcache*) xShrink; +} + +struct sqlite3_pcache_methods +{ + void *pArg; + int function (void*) xInit; + void function (void*) xShutdown; + sqlite3_pcache* function (int szPage, int bPurgeable) xCreate; + void function (sqlite3_pcache*, int nCachesize) xCachesize; + int function (sqlite3_pcache*) xPagecount; + void* function (sqlite3_pcache*, uint key, int createFlag) xFetch; + void function (sqlite3_pcache*, void*, int discard) xUnpin; + void function (sqlite3_pcache*, void*, uint oldKey, uint newKey) xRekey; + void function (sqlite3_pcache*, uint iLimit) xTruncate; + void function (sqlite3_pcache*) xDestroy; +} + +/** +** CAPI3REF: Online Backup Object +*/ +struct sqlite3_backup; + +/** +** CAPI3REF: Online Backup API. +*/ +sqlite3_backup *sqlite3_backup_init( + sqlite3 *pDest, /** Destination database handle */ + const(char)*zDestName, /** Destination database name */ + sqlite3 *pSource, /** Source database handle */ + const(char)*zSourceName /** Source database name */ +); +/// Ditto +int sqlite3_backup_step(sqlite3_backup *p, int nPage); +/// Ditto +int sqlite3_backup_finish(sqlite3_backup *p); +/// Ditto +int sqlite3_backup_remaining(sqlite3_backup *p); +/// Ditto +int sqlite3_backup_pagecount(sqlite3_backup *p); + +/** +** CAPI3REF: Unlock Notification +*/ +int sqlite3_unlock_notify( + sqlite3 *pBlocked, /** Waiting connection */ + void function (void **apArg, int nArg) xNotify, /** Callback function to invoke */ + void *pNotifyArg /** Argument to pass to xNotify */ +); + +/** +** CAPI3REF: String Comparison +*/ +int sqlite3_stricmp(const char * , const char * ); +int sqlite3_strnicmp(const char * , const char * , int); + +/* +** CAPI3REF: String Globbing +* +*/ +int sqlite3_strglob(const(char)* zGlob, const(char)* zStr); + +/* +** CAPI3REF: String LIKE Matching +*/ +int sqlite3_strlike(const(char)* zGlob, const(char)* zStr, uint cEsc); + +/** +** CAPI3REF: Error Logging Interface +*/ +void sqlite3_log(int iErrCode, const char *zFormat, ...); + +/** +** CAPI3REF: Write-Ahead Log Commit Hook +*/ +void *sqlite3_wal_hook( + sqlite3*, + int function (void *,sqlite3*,const char*,int), + void* +); + +/** +** CAPI3REF: Configure an auto-checkpoint +*/ +int sqlite3_wal_autocheckpoint(sqlite3 *db, int N); + +/** +** CAPI3REF: Checkpoint a database +*/ +int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); + +/** +** CAPI3REF: Checkpoint a database +*/ +int sqlite3_wal_checkpoint_v2( + sqlite3 *db, /** Database handle */ + const(char)*zDb, /** Name of attached database (or NULL) */ + int eMode, /** SQLITE_CHECKPOINT_* value */ + int *pnLog, /** OUT: Size of WAL log in frames */ + int *pnCkpt /** OUT: Total number of frames checkpointed */ +); + +/** +** CAPI3REF: Checkpoint operation parameters +*/ +enum +{ + SQLITE_CHECKPOINT_PASSIVE = 0, + SQLITE_CHECKPOINT_FULL = 1, + SQLITE_CHECKPOINT_RESTART = 2, + SQLITE_CHECKPOINT_TRUNCATE = 3, +} + +/* +** CAPI3REF: Virtual Table Interface Configuration +*/ +int sqlite3_vtab_config(sqlite3*, int op, ...); + +/** +** CAPI3REF: Virtual Table Configuration Options +*/ +enum SQLITE_VTAB_CONSTRAINT_SUPPORT = 1; + +/* +** 2010 August 30 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +*/ + +//#ifndef _SQLITE3RTREE_H_ +//#define _SQLITE3RTREE_H_ + + +/* +** CAPI3REF: Determine The Virtual Table Conflict Policy +*/ +int sqlite3_vtab_on_conflict(sqlite3 *); + +/* +** CAPI3REF: Conflict resolution modes +*/ +enum +{ + SQLITE_ROLLBACK = 1, + SQLITE_FAIL = 3, + SQLITE_REPLACE = 5 +} + +/* +** CAPI3REF: Prepared Statement Scan Status Opcodes +*/ +enum +{ + SQLITE_SCANSTAT_NLOOP = 0, + SQLITE_SCANSTAT_NVISIT = 1, + SQLITE_SCANSTAT_EST = 2, + SQLITE_SCANSTAT_NAME = 3, + SQLITE_SCANSTAT_EXPLAIN = 4, + SQLITE_SCANSTAT_SELECTID = 5, +} + +/* +** CAPI3REF: Prepared Statement Scan Status +*/ +int sqlite3_stmt_scanstatus(sqlite3_stmt *pStmt, int idx, int iScanStatusOp, void *pOut); + +/* +** CAPI3REF: Zero Scan-Status Counters +*/ +void sqlite3_stmt_scanstatus_reset(sqlite3_stmt *); + +/* +** CAPI3REF: Flush caches to disk mid-transaction +*/ +int sqlite3_db_cacheflush(sqlite3 *); + +struct sqlite3_snapshot; + +/* +** CAPI3REF: Record A Database Snapshot +*/ +int sqlite3_snapshot_get(sqlite3 *db, char *zSchema, sqlite3_snapshot **ppSnapshot); + +/* +** CAPI3REF: Start a read transaction on an historical snapshot +*/ +int sqlite3_snapshot_open(sqlite3 *db, char *zSchema, sqlite3_snapshot *pSnapshot); + +/* +** CAPI3REF: Destroy a snapshot +*/ +void sqlite3_snapshot_free(sqlite3_snapshot *); + +/** +** Register a geometry callback named zGeom that can be used as part of an +** R-Tree geometry query as follows: +** +** SELECT ... FROM $(LT)rtree$(GT) WHERE $(LT)rtree col$(GT) MATCH $zGeom(... params ...) +*/ +int sqlite3_rtree_geometry_callback( + sqlite3 *db, + const(char)*zGeom, + int function (sqlite3_rtree_geometry *, int nCoord, double *aCoord, int *pRes) xGeom, + void *pContext +); + +/** +** A pointer to a structure of the following type is passed as the first +** argument to callbacks registered using rtree_geometry_callback(). +*/ +struct sqlite3_rtree_geometry +{ + void *pContext; /** Copy of pContext passed to s_r_g_c() */ + int nParam; /** Size of array aParam[] */ + double *aParam; /** Parameters passed to SQL geom function */ + void *pUser; /** Callback implementation user data */ + void function (void *) xDelUser; /** Called by SQLite to clean up pUser */ +} + +int sqlite3_rtree_query_callback( + sqlite3 *db, + const(char)* zQueryFunc, + int function(sqlite3_rtree_query_info*) xQueryFunc, + void *pContext, + void function(void*) xDestructor +); + +struct sqlite3_rtree_query_info +{ + void *pContext; /* pContext from when function registered */ + int nParam; /* Number of function parameters */ + double*aParam; /* value of function parameters */ + void *pUser; /* callback can use this, if desired */ + void function(void*) xDelUser; /* function to free pUser */ + double*aCoord; /* Coordinates of node or entry to check */ + uint *anQueue; /* Number of pending entries in the queue */ + int nCoord; /* Number of coordinates */ + int iLevel; /* Level of current node or entry */ + int mxLevel; /* The largest iLevel value in the tree */ + sqlite3_int64 iRowid; /* Rowid for current entry */ + double rParentScore; /* Score of parent node */ + int eParentWithin; /* Visibility of parent node */ + int eWithin; /* OUT: Visiblity */ + double rScore; /* OUT: Write the score here */ + sqlite3_value **apSqlParam; /* Original SQL values of parameters */ +} + +enum +{ + NOT_WITHIN = 0, + PARTLY_WITHIN = 1, + FULLY_WITHIN = 2 +} + +/****************************************************************************** +** Interfaces to extend FTS5. +*/ +struct Fts5Context; +/// Ditto +alias fts5_extension_function = void function( + const Fts5ExtensionApi *pApi, + Fts5Context *pFts, + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +); +/// Ditto +struct Fts5PhraseIter +{ + const(ubyte) *a; + const(ubyte) *b; +} +/// Ditto +struct Fts5ExtensionApi +{ + int iVersion; + void* function(Fts5Context*) xUserData; + int function(Fts5Context*) xColumnCount; + int function(Fts5Context*, sqlite3_int64 *pnRow) xRowCount; + int function(Fts5Context*, int iCol, sqlite3_int64 *pnToken) xColumnTotalSize; + int function(Fts5Context*, + const char *pText, int nText, + void *pCtx, + int function(void*, int, const char*, int, int, int) xToken + ) xTokenize; + int function(Fts5Context*) xPhraseCount; + int function(Fts5Context*, int iPhrase) xPhraseSize; + int function(Fts5Context*, int *pnInst) xInstCount; + int function(Fts5Context*, int iIdx, int *piPhrase, int *piCol, int *piOff) xInst; + sqlite3_int64 function(Fts5Context*) xRowid; + int function(Fts5Context*, int iCol, const char **pz, int *pn) xColumnText; + int function(Fts5Context*, int iCol, int *pnToken) xColumnSize; + int function(Fts5Context*, int iPhrase, void *pUserData, + int function(const Fts5ExtensionApi*,Fts5Context*,void*) + ) xQueryPhrase; + int function(Fts5Context*, void *pAux, void function(void*) xDelete) xSetAuxdata; + void* function(Fts5Context*, int bClear) xGetAuxdata; + void function(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*, int*) xPhraseFirst; + void function(Fts5Context*, Fts5PhraseIter*, int *piCol, int *piOff) xPhraseNext; +} +/// Ditto +struct Fts5Tokenizer; +struct fts5_tokenizer +{ + int function(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut) xCreate; + void function(Fts5Tokenizer*) xDelete; + int function(Fts5Tokenizer*, + void *pCtx, + int flags, + const char *pText, int nText, + int function( + void *pCtx, + int tflags, + const char *pToken, + int nToken, + int iStart, + int iEnd + ) xToken + ) xTokenize; +} +/// Ditto +enum FTS5_TOKENIZE_QUERY = 0x0001; +/// Ditto +enum FTS5_TOKENIZE_PREFIX = 0x0002; +/// Ditto +enum FTS5_TOKENIZE_DOCUMENT = 0x0004; +/// Ditto +enum FTS5_TOKENIZE_AUX = 0x0008; +/// Ditto +enum FTS5_TOKEN_COLOCATED = 0x0001; +/// Ditto +struct fts5_api +{ + int iVersion; + + int function( + fts5_api *pApi, + const char *zName, + void *pContext, + fts5_tokenizer *pTokenizer, + void function(void*) xDestroy + ) xCreateTokenizer; + + int function( + fts5_api *pApi, + const char *zName, + void **ppContext, + fts5_tokenizer *pTokenizer + ) xFindTokenizer; + + int function( + fts5_api *pApi, + const char *zName, + void *pContext, + fts5_extension_function xFunction, + void function(void*) xDestroy + ) xCreateFunction; +} diff --git a/libphobos/src/etc/c/zlib.d b/libphobos/src/etc/c/zlib.d new file mode 100644 index 0000000..b3e9783 --- /dev/null +++ b/libphobos/src/etc/c/zlib.d @@ -0,0 +1,1788 @@ +/* zlib.d: modified from zlib.h by Walter Bright */ +/* updated from 1.2.1 to 1.2.3 by Thomas Kuehne */ +/* updated from 1.2.3 to 1.2.8 by Dmitry Atamanov */ +/* updated from 1.2.8 to 1.2.11 by Iain Buclaw */ + +module etc.c.zlib; + +import core.stdc.config; + +/* zlib.h -- interface of the 'zlib' general purpose compression library + version 1.2.11, January 15th, 2017 + + Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + + + The data format used by the zlib library is described by RFCs (Request for + Comments) 1950 to 1952 in the files http://tools.ietf.org/html/rfc1950 + (zlib format), rfc1951 (deflate format) and rfc1952 (gzip format). +*/ + +nothrow: +extern (C): + +const char[] ZLIB_VERSION = "1.2.11"; +const ZLIB_VERNUM = 0x12b0; + +/* + The 'zlib' compression library provides in-memory compression and + decompression functions, including integrity checks of the uncompressed data. + This version of the library supports only one compression method (deflation) + but other algorithms will be added later and will have the same stream + interface. + + Compression can be done in a single step if the buffers are large enough, + or can be done by repeated calls of the compression function. In the latter + case, the application must provide more input and/or consume the output + (providing more output space) before each call. + + The compressed data format used by default by the in-memory functions is + the zlib format, which is a zlib wrapper documented in RFC 1950, wrapped + around a deflate stream, which is itself documented in RFC 1951. + + The library also supports reading and writing files in gzip (.gz) format + with an interface similar to that of stdio using the functions that start + with "gz". The gzip format is different from the zlib format. gzip is a + gzip wrapper, documented in RFC 1952, wrapped around a deflate stream. + + This library can optionally read and write gzip and raw deflate streams in + memory as well. + + The zlib format was designed to be compact and fast for use in memory + and on communications channels. The gzip format was designed for single- + file compression on file systems, has a larger header than zlib to maintain + directory information, and uses a different, slower check method than zlib. + + The library does not install any signal handler. The decoder checks + the consistency of the compressed data, so the library should never crash + even in the case of corrupted input. +*/ + +alias alloc_func = void* function (void* opaque, uint items, uint size); +alias free_func = void function (void* opaque, void* address); + +struct z_stream +{ + const(ubyte)* next_in; /* next input byte */ + uint avail_in; /* number of bytes available at next_in */ + c_ulong total_in; /* total nb of input bytes read so far */ + + ubyte* next_out; /* next output byte will go here */ + uint avail_out; /* remaining free space at next_out */ + c_ulong total_out; /* total nb of bytes output so far */ + + const(char)* msg; /* last error message, NULL if no error */ + void* state; /* not visible by applications */ + + alloc_func zalloc; /* used to allocate the internal state */ + free_func zfree; /* used to free the internal state */ + void* opaque; /* private data object passed to zalloc and zfree */ + + int data_type; /* best guess about the data type: binary or text + for deflate, or the decoding state for inflate */ + c_ulong adler; /* Adler-32 or CRC-32 value of the uncompressed data */ + c_ulong reserved; /* reserved for future use */ +} + +alias z_streamp = z_stream*; + +/* + gzip header information passed to and from zlib routines. See RFC 1952 + for more details on the meanings of these fields. +*/ +struct gz_header +{ + int text; /* true if compressed data believed to be text */ + c_ulong time; /* modification time */ + int xflags; /* extra flags (not used when writing a gzip file) */ + int os; /* operating system */ + byte *extra; /* pointer to extra field or Z_NULL if none */ + uint extra_len; /* extra field length (valid if extra != Z_NULL) */ + uint extra_max; /* space at extra (only when reading header) */ + byte* name; /* pointer to zero-terminated file name or Z_NULL */ + uint name_max; /* space at name (only when reading header) */ + byte* comment; /* pointer to zero-terminated comment or Z_NULL */ + uint comm_max; /* space at comment (only when reading header) */ + int hcrc; /* true if there was or will be a header crc */ + int done; /* true when done reading gzip header (not used + when writing a gzip file) */ +} + +alias gz_headerp = gz_header*; + +/* + The application must update next_in and avail_in when avail_in has dropped + to zero. It must update next_out and avail_out when avail_out has dropped + to zero. The application must initialize zalloc, zfree and opaque before + calling the init function. All other fields are set by the compression + library and must not be updated by the application. + + The opaque value provided by the application will be passed as the first + parameter for calls of zalloc and zfree. This can be useful for custom + memory management. The compression library attaches no meaning to the + opaque value. + + zalloc must return Z_NULL if there is not enough memory for the object. + If zlib is used in a multi-threaded application, zalloc and zfree must be + thread safe. In that case, zlib is thread-safe. When zalloc and zfree are + Z_NULL on entry to the initialization function, they are set to internal + routines that use the standard library functions malloc() and free(). + + On 16-bit systems, the functions zalloc and zfree must be able to allocate + exactly 65536 bytes, but will not be required to allocate more than this if + the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, pointers + returned by zalloc for objects of exactly 65536 bytes *must* have their + offset normalized to zero. The default allocation function provided by this + library ensures this (see zutil.c). To reduce memory requirements and avoid + any allocation of 64K objects, at the expense of compression ratio, compile + the library with -DMAX_WBITS=14 (see zconf.h). + + The fields total_in and total_out can be used for statistics or progress + reports. After compression, total_in holds the total size of the + uncompressed data and may be saved for use by the decompressor (particularly + if the decompressor wants to decompress everything in a single step). +*/ + + /* constants */ + +enum +{ + Z_NO_FLUSH = 0, + Z_PARTIAL_FLUSH = 1, /* will be removed, use Z_SYNC_FLUSH instead */ + Z_SYNC_FLUSH = 2, + Z_FULL_FLUSH = 3, + Z_FINISH = 4, + Z_BLOCK = 5, + Z_TREES = 6, +} +/* Allowed flush values; see deflate() and inflate() below for details */ + +enum +{ + Z_OK = 0, + Z_STREAM_END = 1, + Z_NEED_DICT = 2, + Z_ERRNO = -1, + Z_STREAM_ERROR = -2, + Z_DATA_ERROR = -3, + Z_MEM_ERROR = -4, + Z_BUF_ERROR = -5, + Z_VERSION_ERROR = -6, +} +/* Return codes for the compression/decompression functions. Negative + * values are errors, positive values are used for special but normal events. + */ + +enum +{ + Z_NO_COMPRESSION = 0, + Z_BEST_SPEED = 1, + Z_BEST_COMPRESSION = 9, + Z_DEFAULT_COMPRESSION = -1, +} +/* compression levels */ + +enum +{ + Z_FILTERED = 1, + Z_HUFFMAN_ONLY = 2, + Z_RLE = 3, + Z_FIXED = 4, + Z_DEFAULT_STRATEGY = 0, +} +/* compression strategy; see deflateInit2() below for details */ + +enum +{ + Z_BINARY = 0, + Z_TEXT = 1, + Z_UNKNOWN = 2, + + Z_ASCII = Z_TEXT +} +/* Possible values of the data_type field for deflate() */ + +enum +{ + Z_DEFLATED = 8, +} +/* The deflate compression method (the only one supported in this version) */ + +const int Z_NULL = 0; /* for initializing zalloc, zfree, opaque */ + + /* basic functions */ + +const(char)* zlibVersion(); +/* The application can compare zlibVersion and ZLIB_VERSION for consistency. + If the first character differs, the library code actually used is not + compatible with the zlib.h header file used by the application. This check + is automatically made by deflateInit and inflateInit. + */ + +int deflateInit(z_streamp strm, int level) +{ + return deflateInit_(strm, level, ZLIB_VERSION.ptr, z_stream.sizeof); +} +/* + Initializes the internal stream state for compression. The fields + zalloc, zfree and opaque must be initialized before by the caller. If + zalloc and zfree are set to Z_NULL, deflateInit updates them to use default + allocation functions. + + The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9: + 1 gives best speed, 9 gives best compression, 0 gives no compression at all + (the input data is simply copied a block at a time). Z_DEFAULT_COMPRESSION + requests a default compromise between speed and compression (currently + equivalent to level 6). + + deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if level is not a valid compression level, or + Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible + with the version assumed by the caller (ZLIB_VERSION). msg is set to null + if there is no error message. deflateInit does not perform any compression: + this will be done by deflate(). +*/ + + +int deflate(z_streamp strm, int flush); +/* + deflate compresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce + some output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. deflate performs one or both of the + following actions: + + - Compress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in and avail_in are updated and + processing will resume at this point for the next call of deflate(). + + - Generate more output starting at next_out and update next_out and avail_out + accordingly. This action is forced if the parameter flush is non zero. + Forcing flush frequently degrades the compression ratio, so this parameter + should be set only when necessary. Some output may be provided even if + flush is zero. + + Before the call of deflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming more + output, and updating avail_in or avail_out accordingly; avail_out should + never be zero before the call. The application can consume the compressed + output when it wants, for example when the output buffer is full (avail_out + == 0), or after each call of deflate(). If deflate returns Z_OK and with + zero avail_out, it must be called again after making room in the output + buffer because there might be more output pending. See deflatePending(), + which can be used if desired to determine whether or not there is more ouput + in that case. + + Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to + decide how much data to accumulate before producing output, in order to + maximize compression. + + If the parameter flush is set to Z_SYNC_FLUSH, all pending output is + flushed to the output buffer and the output is aligned on a byte boundary, so + that the decompressor can get all input data available so far. (In + particular avail_in is zero after the call if enough output space has been + provided before the call.) Flushing may degrade compression for some + compression algorithms and so it should be used only when necessary. This + completes the current deflate block and follows it with an empty stored block + that is three bits plus filler bits to the next byte, followed by four bytes + (00 00 ff ff). + + If flush is set to Z_PARTIAL_FLUSH, all pending output is flushed to the + output buffer, but the output is not aligned to a byte boundary. All of the + input data so far will be available to the decompressor, as for Z_SYNC_FLUSH. + This completes the current deflate block and follows it with an empty fixed + codes block that is 10 bits long. This assures that enough bytes are output + in order for the decompressor to finish the block before the empty fixed + codes block. + + If flush is set to Z_BLOCK, a deflate block is completed and emitted, as + for Z_SYNC_FLUSH, but the output is not aligned on a byte boundary, and up to + seven bits of the current block are held to be written as the next byte after + the next deflate block is completed. In this case, the decompressor may not + be provided enough bits at this point in order to complete decompression of + the data provided so far to the compressor. It may need to wait for the next + block to be emitted. This is for advanced applications that need to control + the emission of deflate blocks. + + If flush is set to Z_FULL_FLUSH, all output is flushed as with + Z_SYNC_FLUSH, and the compression state is reset so that decompression can + restart from this point if previous compressed data has been damaged or if + random access is desired. Using Z_FULL_FLUSH too often can seriously degrade + compression. + + If deflate returns with avail_out == 0, this function must be called again + with the same value of the flush parameter and more output space (updated + avail_out), until the flush is complete (deflate returns with non-zero + avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that + avail_out is greater than six to avoid repeated flush markers due to + avail_out == 0 on return. + + If the parameter flush is set to Z_FINISH, pending input is processed, + pending output is flushed and deflate returns with Z_STREAM_END if there was + enough output space. If deflate returns with Z_OK or Z_BUF_ERROR, this + function must be called again with Z_FINISH and more output space (updated + avail_out) but no more input data, until it returns with Z_STREAM_END or an + error. After deflate has returned Z_STREAM_END, the only possible operations + on the stream are deflateReset or deflateEnd. + + Z_FINISH can be used in the first deflate call after deflateInit if all the + compression is to be done in a single step. In order to complete in one + call, avail_out must be at least the value returned by deflateBound (see + below). Then deflate is guaranteed to return Z_STREAM_END. If not enough + output space is provided, deflate will not return Z_STREAM_END, and it must + be called again as described above. + + deflate() sets strm->adler to the Adler-32 checksum of all input read + so far (that is, total_in bytes). If a gzip stream is being generated, then + strm->adler will be the CRC-32 checksum of the input read so far. (See + deflateInit2 below.) + + deflate() may update strm->data_type if it can make a good guess about + the input data type (Z_BINARY or Z_TEXT). If in doubt, the data is + considered binary. This field is only for information purposes and does not + affect the compression algorithm in any manner. + + deflate() returns Z_OK if some progress has been made (more input + processed or more output produced), Z_STREAM_END if all input has been + consumed and all output has been produced (only when flush is set to + Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example + if next_in or next_out was Z_NULL or the state was inadvertently written over + by the application), or Z_BUF_ERROR if no progress is possible (for example + avail_in or avail_out was zero). Note that Z_BUF_ERROR is not fatal, and + deflate() can be called again with more input and more output space to + continue compressing. +*/ + + +int deflateEnd(z_streamp strm); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any pending + output. + + deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the + stream state was inconsistent, Z_DATA_ERROR if the stream was freed + prematurely (some input or output was discarded). In the error case, msg + may be set but then points to a static string (which must not be + deallocated). +*/ + + +int inflateInit(z_streamp strm) +{ + return inflateInit_(strm, ZLIB_VERSION.ptr, z_stream.sizeof); +} +/* + Initializes the internal stream state for decompression. The fields + next_in, avail_in, zalloc, zfree and opaque must be initialized before by + the caller. In the current version of inflate, the provided input is not + read or consumed. The allocation of a sliding window will be deferred to + the first call of inflate (if the decompression does not complete on the + first call). If zalloc and zfree are set to Z_NULL, inflateInit updates + them to use default allocation functions. + + inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_VERSION_ERROR if the zlib library version is incompatible with the + version assumed by the caller, or Z_STREAM_ERROR if the parameters are + invalid, such as a null pointer to the structure. msg is set to null if + there is no error message. inflateInit does not perform any decompression. + Actual decompression will be done by inflate(). So next_in, and avail_in, + next_out, and avail_out are unused and unchanged. The current + implementation of inflateInit() does not process any header information -- + that is deferred until inflate() is called. +*/ + + +int inflate(z_streamp strm, int flush); +/* + inflate decompresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce + some output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. inflate performs one or both of the + following actions: + + - Decompress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), then next_in and avail_in are updated + accordingly, and processing will resume at this point for the next call of + inflate(). + + - Generate more output starting at next_out and update next_out and avail_out + accordingly. inflate() provides as much output as possible, until there is + no more input data or no more space in the output buffer (see below about + the flush parameter). + + Before the call of inflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming more + output, and updating the next_* and avail_* values accordingly. If the + caller of inflate() does not provide both available input and available + output space, it is possible that there will be no progress made. The + application can consume the uncompressed output when it wants, for example + when the output buffer is full (avail_out == 0), or after each call of + inflate(). If inflate returns Z_OK and with zero avail_out, it must be + called again after making room in the output buffer because there might be + more output pending. + + The flush parameter of inflate() can be Z_NO_FLUSH, Z_SYNC_FLUSH, Z_FINISH, + Z_BLOCK, or Z_TREES. Z_SYNC_FLUSH requests that inflate() flush as much + output as possible to the output buffer. Z_BLOCK requests that inflate() + stop if and when it gets to the next deflate block boundary. When decoding + the zlib or gzip format, this will cause inflate() to return immediately + after the header and before the first block. When doing a raw inflate, + inflate() will go ahead and process the first block, and will return when it + gets to the end of that block, or when it runs out of data. + + The Z_BLOCK option assists in appending to or combining deflate streams. + To assist in this, on return inflate() always sets strm->data_type to the + number of unused bits in the last byte taken from strm->next_in, plus 64 if + inflate() is currently decoding the last block in the deflate stream, plus + 128 if inflate() returned immediately after decoding an end-of-block code or + decoding the complete header up to just before the first byte of the deflate + stream. The end-of-block will not be indicated until all of the uncompressed + data from that block has been written to strm->next_out. The number of + unused bits may in general be greater than seven, except when bit 7 of + data_type is set, in which case the number of unused bits will be less than + eight. data_type is set as noted here every time inflate() returns for all + flush options, and so can be used to determine the amount of currently + consumed input in bits. + + The Z_TREES option behaves as Z_BLOCK does, but it also returns when the + end of each deflate block header is reached, before any actual data in that + block is decoded. This allows the caller to determine the length of the + deflate block header for later use in random access within a deflate block. + 256 is added to the value of strm->data_type when inflate() returns + immediately after reaching the end of the deflate block header. + + inflate() should normally be called until it returns Z_STREAM_END or an + error. However if all decompression is to be performed in a single step (a + single call of inflate), the parameter flush should be set to Z_FINISH. In + this case all pending input is processed and all pending output is flushed; + avail_out must be large enough to hold all of the uncompressed data for the + operation to complete. (The size of the uncompressed data may have been + saved by the compressor for this purpose.) The use of Z_FINISH is not + required to perform an inflation in one step. However it may be used to + inform inflate that a faster approach can be used for the single inflate() + call. Z_FINISH also informs inflate to not maintain a sliding window if the + stream completes, which reduces inflate's memory footprint. If the stream + does not complete, either because not all of the stream is provided or not + enough output space is provided, then a sliding window will be allocated and + inflate() can be called again to continue the operation as if Z_NO_FLUSH had + been used. + + In this implementation, inflate() always flushes as much output as + possible to the output buffer, and always uses the faster approach on the + first call. So the effects of the flush parameter in this implementation are + on the return value of inflate() as noted below, when inflate() returns early + when Z_BLOCK or Z_TREES is used, and when inflate() avoids the allocation of + memory for a sliding window when Z_FINISH is used. + + If a preset dictionary is needed after this call (see inflateSetDictionary + below), inflate sets strm->adler to the Adler-32 checksum of the dictionary + chosen by the compressor and returns Z_NEED_DICT; otherwise it sets + strm->adler to the Adler-32 checksum of all output produced so far (that is, + total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described + below. At the end of the stream, inflate() checks that its computed Adler-32 + checksum is equal to that saved by the compressor and returns Z_STREAM_END + only if the checksum is correct. + + inflate() can decompress and check either zlib-wrapped or gzip-wrapped + deflate data. The header type is detected automatically, if requested when + initializing with inflateInit2(). Any information contained in the gzip + header is not retained unless inflateGetHeader() is used. When processing + gzip-wrapped deflate data, strm->adler32 is set to the CRC-32 of the output + produced so far. The CRC-32 is checked against the gzip trailer, as is the + uncompressed length, modulo 2^32. + + inflate() returns Z_OK if some progress has been made (more input processed + or more output produced), Z_STREAM_END if the end of the compressed data has + been reached and all uncompressed output has been produced, Z_NEED_DICT if a + preset dictionary is needed at this point, Z_DATA_ERROR if the input data was + corrupted (input stream not conforming to the zlib format or incorrect check + value, in which case strm->msg points to a string with a more specific + error), Z_STREAM_ERROR if the stream structure was inconsistent (for example + next_in or next_out was Z_NULL, or the state was inadvertently written over + by the application), Z_MEM_ERROR if there was not enough memory, Z_BUF_ERROR + if no progress was possible or if there was not enough room in the output + buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and + inflate() can be called again with more input and more output space to + continue decompressing. If Z_DATA_ERROR is returned, the application may + then call inflateSync() to look for a good compression block if a partial + recovery of the data is to be attempted. +*/ + + +int inflateEnd(z_streamp strm); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any pending + output. + + inflateEnd returns Z_OK if success, or Z_STREAM_ERROR if the stream state + was inconsistent. +*/ + + /* Advanced functions */ + +/* + The following functions are needed only in some special applications. +*/ + +int deflateInit2(z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, + int strategy) +{ + return deflateInit2_(strm, level, method, windowBits, memLevel, + strategy, ZLIB_VERSION.ptr, z_stream.sizeof); +} +/* + This is another version of deflateInit with more compression options. The + fields next_in, zalloc, zfree and opaque must be initialized before by the + caller. + + The method parameter is the compression method. It must be Z_DEFLATED in + this version of the library. + + The windowBits parameter is the base two logarithm of the window size + (the size of the history buffer). It should be in the range 8 .. 15 for this + version of the library. Larger values of this parameter result in better + compression at the expense of memory usage. The default value is 15 if + deflateInit is used instead. + + For the current implementation of deflate(), a windowBits value of 8 (a + window size of 256 bytes) is not supported. As a result, a request for 8 + will result in 9 (a 512-byte window). In that case, providing 8 to + inflateInit2() will result in an error when the zlib header with 9 is + checked against the initialization of inflate(). The remedy is to not use 8 + with deflateInit2() with this initialization, or at least in that case use 9 + with inflateInit2(). + + windowBits can also be -8..-15 for raw deflate. In this case, -windowBits + determines the window size. deflate() will then generate raw deflate data + with no zlib header or trailer, and will not compute a check value. + + windowBits can also be greater than 15 for optional gzip encoding. Add + 16 to windowBits to write a simple gzip header and trailer around the + compressed data instead of a zlib wrapper. The gzip header will have no + file name, no extra data, no comment, no modification time (set to zero), no + header crc, and the operating system will be set to the appropriate value, + if the operating system was determined at compile time. If a gzip stream is + being written, strm->adler is a CRC-32 instead of an Adler-32. + + For raw deflate or gzip encoding, a request for a 256-byte window is + rejected as invalid, since only the zlib header provides a means of + transmitting the window size to the decompressor. + + The memLevel parameter specifies how much memory should be allocated + for the internal compression state. memLevel=1 uses minimum memory but is + slow and reduces compression ratio; memLevel=9 uses maximum memory for + optimal speed. The default value is 8. See zconf.h for total memory usage + as a function of windowBits and memLevel. + + The strategy parameter is used to tune the compression algorithm. Use the + value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a + filter (or predictor), Z_HUFFMAN_ONLY to force Huffman encoding only (no + string match), or Z_RLE to limit match distances to one (run-length + encoding). Filtered data consists mostly of small values with a somewhat + random distribution. In this case, the compression algorithm is tuned to + compress them better. The effect of Z_FILTERED is to force more Huffman + coding and less string matching; it is somewhat intermediate between + Z_DEFAULT_STRATEGY and Z_HUFFMAN_ONLY. Z_RLE is designed to be almost as + fast as Z_HUFFMAN_ONLY, but give better compression for PNG image data. The + strategy parameter only affects the compression ratio but not the + correctness of the compressed output even if it is not set appropriately. + Z_FIXED prevents the use of dynamic Huffman codes, allowing for a simpler + decoder for special applications. + + deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if any parameter is invalid (such as an invalid + method), or Z_VERSION_ERROR if the zlib library version (zlib_version) is + incompatible with the version assumed by the caller (ZLIB_VERSION). msg is + set to null if there is no error message. deflateInit2 does not perform any + compression: this will be done by deflate(). +*/ + +int deflateSetDictionary(z_streamp strm, const(ubyte)* dictionary, uint dictLength); +/* + Initializes the compression dictionary from the given byte sequence + without producing any compressed output. When using the zlib format, this + function must be called immediately after deflateInit, deflateInit2 or + deflateReset, and before any call of deflate. When doing raw deflate, this + function must be called either before any call of deflate, or immediately + after the completion of a deflate block, i.e. after all input has been + consumed and all output has been delivered when using any of the flush + options Z_BLOCK, Z_PARTIAL_FLUSH, Z_SYNC_FLUSH, or Z_FULL_FLUSH. The + compressor and decompressor must use exactly the same dictionary (see + inflateSetDictionary). + + The dictionary should consist of strings (byte sequences) that are likely + to be encountered later in the data to be compressed, with the most commonly + used strings preferably put towards the end of the dictionary. Using a + dictionary is most useful when the data to be compressed is short and can be + predicted with good accuracy; the data can then be compressed better than + with the default empty dictionary. + + Depending on the size of the compression data structures selected by + deflateInit or deflateInit2, a part of the dictionary may in effect be + discarded, for example if the dictionary is larger than the window size + provided in deflateInit or deflateInit2. Thus the strings most likely to be + useful should be put at the end of the dictionary, not at the front. In + addition, the current implementation of deflate will use at most the window + size minus 262 bytes of the provided dictionary. + + Upon return of this function, strm->adler is set to the Adler-32 value + of the dictionary; the decompressor may later use this value to determine + which dictionary has been used by the compressor. (The Adler-32 value + applies to the whole dictionary even if only a subset of the dictionary is + actually used by the compressor.) If a raw deflate was requested, then the + Adler-32 value is not computed and strm->adler is not set. + + deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a + parameter is invalid (e.g. dictionary being Z_NULL) or the stream state is + inconsistent (for example if deflate has already been called for this stream + or if not at a block boundary for raw deflate). deflateSetDictionary does + not perform any compression: this will be done by deflate(). +*/ + +int deflateGetDictionary(z_streamp strm, ubyte *dictionary, uint dictLength); +/* + Returns the sliding dictionary being maintained by deflate. dictLength is + set to the number of bytes in the dictionary, and that many bytes are copied + to dictionary. dictionary must have enough space, where 32768 bytes is + always enough. If deflateGetDictionary() is called with dictionary equal to + Z_NULL, then only the dictionary length is returned, and nothing is copied. + Similary, if dictLength is Z_NULL, then it is not set. + + deflateGetDictionary() may return a length less than the window size, even + when more than the window size in input has been provided. It may return up + to 258 bytes less in that case, due to how zlib's implementation of deflate + manages the sliding window and lookahead for matches, where matches can be + up to 258 bytes long. If the application needs the last window-size bytes of + input, then that would need to be saved by the application outside of zlib. + + deflateGetDictionary returns Z_OK on success, or Z_STREAM_ERROR if the + stream state is inconsistent. +*/ + +int deflateCopy(z_streamp dest, z_streamp source); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when several compression strategies will be + tried, for example when there are several ways of pre-processing the input + data with a filter. The streams that will be discarded should then be freed + by calling deflateEnd. Note that deflateCopy duplicates the internal + compression state which can be quite large, so this strategy is slow and can + consume lots of memory. + + deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being Z_NULL). msg is left unchanged in both source and + destination. +*/ + +int deflateReset(z_streamp strm); +/* + This function is equivalent to deflateEnd followed by deflateInit, but + does not free and reallocate the internal compression state. The stream + will leave the compression level and any other attributes that may have been + set unchanged. + + deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being Z_NULL). +*/ + +int deflateParams(z_streamp strm, int level, int strategy); +/* + Dynamically update the compression level and compression strategy. The + interpretation of level and strategy is as in deflateInit2(). This can be + used to switch between compression and straight copy of the input data, or + to switch to a different kind of input data requiring a different strategy. + If the compression approach (which is a function of the level) or the + strategy is changed, and if any input has been consumed in a previous + deflate() call, then the input available so far is compressed with the old + level and strategy using deflate(strm, Z_BLOCK). There are three approaches + for the compression levels 0, 1 .. 3, and 4 .. 9 respectively. The new level + and strategy will take effect at the next call of deflate(). + + If a deflate(strm, Z_BLOCK) is performed by deflateParams(), and it does + not have enough output space to complete, then the parameter change will not + take effect. In this case, deflateParams() can be called again with the + same parameters and more output space to try again. + + In order to assure a change in the parameters on the first try, the + deflate stream should be flushed using deflate() with Z_BLOCK or other flush + request until strm.avail_out is not zero, before calling deflateParams(). + Then no more input data should be provided before the deflateParams() call. + If this is done, the old level and strategy will be applied to the data + compressed before deflateParams(), and the new level and strategy will be + applied to the the data compressed after deflateParams(). + + deflateParams returns Z_OK on success, Z_STREAM_ERROR if the source stream + state was inconsistent or if a parameter was invalid, or Z_BUF_ERROR if + there was not enough output space to complete the compression of the + available input data before a change in the strategy or approach. Note that + in the case of a Z_BUF_ERROR, the parameters are not changed. A return + value of Z_BUF_ERROR is not fatal, in which case deflateParams() can be + retried with more output space. +*/ + +int deflateTune(z_streamp strm, int good_length, int max_lazy, int nice_length, + int max_chain); +/* + Fine tune deflate's internal compression parameters. This should only be + used by someone who understands the algorithm used by zlib's deflate for + searching for the best matching string, and even then only by the most + fanatic optimizer trying to squeeze out the last compressed bit for their + specific input data. Read the deflate.c source code for the meaning of the + max_lazy, good_length, nice_length, and max_chain parameters. + + deflateTune() can be called after deflateInit() or deflateInit2(), and + returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream. + */ + +size_t deflateBound(z_streamp strm, size_t sourceLen); +/* + deflateBound() returns an upper bound on the compressed size after + deflation of sourceLen bytes. It must be called after deflateInit() or + deflateInit2(), and after deflateSetHeader(), if used. This would be used + to allocate an output buffer for deflation in a single pass, and so would be + called before deflate(). If that first deflate() call is provided the + sourceLen input bytes, an output buffer allocated to the size returned by + deflateBound(), and the flush value Z_FINISH, then deflate() is guaranteed + to return Z_STREAM_END. Note that it is possible for the compressed size to + be larger than the value returned by deflateBound() if flush options other + than Z_FINISH or Z_NO_FLUSH are used. +*/ + +int deflatePending(z_streamp strm, uint* pending, int* bits); +/* + deflatePending() returns the number of bytes and bits of output that have + been generated, but not yet provided in the available output. The bytes not + provided would be due to the available output space having being consumed. + The number of bits of output not provided are between 0 and 7, where they + await more bits to join them in order to fill out a full byte. If pending + or bits are Z_NULL, then those values are not set. + + deflatePending returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. + */ + +int deflatePrime(z_streamp strm, int bits, int value); +/* + deflatePrime() inserts bits in the deflate output stream. The intent + is that this function is used to start off the deflate output with the bits + leftover from a previous deflate stream when appending to it. As such, this + function can only be used for raw deflate, and must be used before the first + deflate() call after a deflateInit2() or deflateReset(). bits must be less + than or equal to 16, and that many of the least significant bits of value + will be inserted in the output. + + deflatePrime returns Z_OK if success, Z_BUF_ERROR if there was not enough + room in the internal buffer to insert the bits, or Z_STREAM_ERROR if the + source stream state was inconsistent. +*/ + +int deflateSetHeader(z_streamp strm, gz_headerp head); +/* + deflateSetHeader() provides gzip header information for when a gzip + stream is requested by deflateInit2(). deflateSetHeader() may be called + after deflateInit2() or deflateReset() and before the first call of + deflate(). The text, time, os, extra field, name, and comment information + in the provided gz_header structure are written to the gzip header (xflag is + ignored -- the extra flags are set according to the compression level). The + caller must assure that, if not Z_NULL, name and comment are terminated with + a zero byte, and that if extra is not Z_NULL, that extra_len bytes are + available there. If hcrc is true, a gzip header crc is included. Note that + the current versions of the command-line version of gzip (up through version + 1.3.x) do not support header crc's, and will report that it is a "multi-part + gzip file" and give up. + + If deflateSetHeader is not used, the default gzip header has text false, + the time set to zero, and os set to 255, with no extra, name, or comment + fields. The gzip header is returned to the default state by deflateReset(). + + deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +int inflateInit2(z_streamp strm, int windowBits) +{ + return inflateInit2_(strm, windowBits, ZLIB_VERSION.ptr, z_stream.sizeof); +} +/* + This is another version of inflateInit with an extra parameter. The + fields next_in, avail_in, zalloc, zfree and opaque must be initialized + before by the caller. + + The windowBits parameter is the base two logarithm of the maximum window + size (the size of the history buffer). It should be in the range 8 .. 15 for + this version of the library. The default value is 15 if inflateInit is used + instead. windowBits must be greater than or equal to the windowBits value + provided to deflateInit2() while compressing, or it must be equal to 15 if + deflateInit2() was not used. If a compressed stream with a larger window + size is given as input, inflate() will return with the error code + Z_DATA_ERROR instead of trying to allocate a larger window. + + windowBits can also be zero to request that inflate use the window size in + the zlib header of the compressed stream. + + windowBits can also be -8..-15 for raw inflate. In this case, -windowBits + determines the window size. inflate() will then process raw deflate data, + not looking for a zlib or gzip header, not generating a check value, and not + looking for any check values for comparison at the end of the stream. This + is for use with other formats that use the deflate compressed data format + such as zip. Those formats provide their own check values. If a custom + format is developed using the raw deflate format for compressed data, it is + recommended that a check value such as an Adler-32 or a CRC-32 be applied to + the uncompressed data as is done in the zlib, gzip, and zip formats. For + most applications, the zlib format should be used as is. Note that comments + above on the use in deflateInit2() applies to the magnitude of windowBits. + + windowBits can also be greater than 15 for optional gzip decoding. Add + 32 to windowBits to enable zlib and gzip decoding with automatic header + detection, or add 16 to decode only the gzip format (the zlib format will + return a Z_DATA_ERROR). If a gzip stream is being decoded, strm->adler is a + CRC-32 instead of an Adler-32. Unlike the gunzip utility and gzread() (see + below), inflate() will not automatically decode concatenated gzip streams. + inflate() will return Z_STREAM_END at the end of the gzip stream. The state + would need to be reset to continue decoding a subsequent gzip stream. + + inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_VERSION_ERROR if the zlib library version is incompatible with the + version assumed by the caller, or Z_STREAM_ERROR if the parameters are + invalid, such as a null pointer to the structure. msg is set to null if + there is no error message. inflateInit2 does not perform any decompression + apart from possibly reading the zlib header if present: actual decompression + will be done by inflate(). (So next_in and avail_in may be modified, but + next_out and avail_out are unused and unchanged.) The current implementation + of inflateInit2() does not process any header information -- that is + deferred until inflate() is called. +*/ + +int inflateSetDictionary(z_streamp strm, const(ubyte)* dictionary, uint dictLength); +/* + Initializes the decompression dictionary from the given uncompressed byte + sequence. This function must be called immediately after a call of inflate, + if that call returned Z_NEED_DICT. The dictionary chosen by the compressor + can be determined from the Adler-32 value returned by that call of inflate. + The compressor and decompressor must use exactly the same dictionary (see + deflateSetDictionary). For raw inflate, this function can be called at any + time to set the dictionary. If the provided dictionary is smaller than the + window and there is already data in the window, then the provided dictionary + will amend what's there. The application must insure that the dictionary + that was used for compression is provided. + + inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a + parameter is invalid (e.g. dictionary being Z_NULL) or the stream state is + inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the + expected one (incorrect Adler-32 value). inflateSetDictionary does not + perform any decompression: this will be done by subsequent calls of + inflate(). +*/ + +int inflateGetDictionary(z_streamp strm, ubyte* dictionary, uint* dictLength); +/* + Returns the sliding dictionary being maintained by inflate. dictLength is + set to the number of bytes in the dictionary, and that many bytes are copied + to dictionary. dictionary must have enough space, where 32768 bytes is + always enough. If inflateGetDictionary() is called with dictionary equal to + Z_NULL, then only the dictionary length is returned, and nothing is copied. + Similary, if dictLength is Z_NULL, then it is not set. + + inflateGetDictionary returns Z_OK on success, or Z_STREAM_ERROR if the + stream state is inconsistent. +*/ + +int inflateSync(z_streamp strm); +/* + Skips invalid compressed data until a possible full flush point (see above + for the description of deflate with Z_FULL_FLUSH) can be found, or until all + available input is skipped. No output is provided. + + inflateSync searches for a 00 00 FF FF pattern in the compressed data. + All full flush points have this pattern, but not all occurrences of this + pattern are full flush points. + + inflateSync returns Z_OK if a possible full flush point has been found, + Z_BUF_ERROR if no more input was provided, Z_DATA_ERROR if no flush point + has been found, or Z_STREAM_ERROR if the stream structure was inconsistent. + In the success case, the application may save the current current value of + total_in which indicates where valid compressed data was found. In the + error case, the application may repeatedly call inflateSync, providing more + input each time, until success or end of the input data. +*/ + +int inflateCopy(z_streamp dest, z_streamp source); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when randomly accessing a large stream. The + first pass through the stream can periodically record the inflate state, + allowing restarting inflate at those points when randomly accessing the + stream. + + inflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being Z_NULL). msg is left unchanged in both source and + destination. +*/ + +int inflateReset(z_streamp strm); +/* + This function is equivalent to inflateEnd followed by inflateInit, + but does not free and reallocate the internal decompression state. The + stream will keep attributes that may have been set by inflateInit2. + + inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being Z_NULL). +*/ + +int inflateReset2(z_streamp strm, int windowBits); +/* + This function is the same as inflateReset, but it also permits changing + the wrap and window size requests. The windowBits parameter is interpreted + the same as it is for inflateInit2. If the window size is changed, then the + memory allocated for the window is freed, and the window will be reallocated + by inflate() if needed. + + inflateReset2 returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being Z_NULL), or if + the windowBits parameter is invalid. +*/ + +int inflatePrime(z_streamp strm, int bits, int value); +/* + This function inserts bits in the inflate input stream. The intent is + that this function is used to start inflating at a bit position in the + middle of a byte. The provided bits will be used before any bytes are used + from next_in. This function should only be used with raw inflate, and + should be used before the first inflate() call after inflateInit2() or + inflateReset(). bits must be less than or equal to 16, and that many of the + least significant bits of value will be inserted in the input. + + If bits is negative, then the input stream bit buffer is emptied. Then + inflatePrime() can be called again to put bits in the buffer. This is used + to clear out bits leftover after feeding inflate a block description prior + to feeding inflate codes. + + inflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +c_long inflateMark(z_streamp strm); +/* + This function returns two values, one in the lower 16 bits of the return + value, and the other in the remaining upper bits, obtained by shifting the + return value down 16 bits. If the upper value is -1 and the lower value is + zero, then inflate() is currently decoding information outside of a block. + If the upper value is -1 and the lower value is non-zero, then inflate is in + the middle of a stored block, with the lower value equaling the number of + bytes from the input remaining to copy. If the upper value is not -1, then + it is the number of bits back from the current bit position in the input of + the code (literal or length/distance pair) currently being processed. In + that case the lower value is the number of bytes already emitted for that + code. + + A code is being processed if inflate is waiting for more input to complete + decoding of the code, or if it has completed decoding but is waiting for + more output space to write the literal or match data. + + inflateMark() is used to mark locations in the input data for random + access, which may be at bit positions, and to note those cases where the + output of a code may span boundaries of random access blocks. The current + location in the input stream can be determined from avail_in and data_type + as noted in the description for the Z_BLOCK flush parameter for inflate. + + inflateMark returns the value noted above, or -65536 if the provided + source stream state was inconsistent. +*/ + +int inflateGetHeader(z_streamp strm, gz_headerp head); +/* + inflateGetHeader() requests that gzip header information be stored in the + provided gz_header structure. inflateGetHeader() may be called after + inflateInit2() or inflateReset(), and before the first call of inflate(). + As inflate() processes the gzip stream, head->done is zero until the header + is completed, at which time head->done is set to one. If a zlib stream is + being decoded, then head->done is set to -1 to indicate that there will be + no gzip header information forthcoming. Note that Z_BLOCK or Z_TREES can be + used to force inflate() to return immediately after header processing is + complete and before any actual data is decompressed. + + The text, time, xflags, and os fields are filled in with the gzip header + contents. hcrc is set to true if there is a header CRC. (The header CRC + was valid if done is set to one.) If extra is not Z_NULL, then extra_max + contains the maximum number of bytes to write to extra. Once done is true, + extra_len contains the actual extra field length, and extra contains the + extra field, or that field truncated if extra_max is less than extra_len. + If name is not Z_NULL, then up to name_max characters are written there, + terminated with a zero unless the length is greater than name_max. If + comment is not Z_NULL, then up to comm_max characters are written there, + terminated with a zero unless the length is greater than comm_max. When any + of extra, name, or comment are not Z_NULL and the respective field is not + present in the header, then that field is set to Z_NULL to signal its + absence. This allows the use of deflateSetHeader() with the returned + structure to duplicate the header. However if those fields are set to + allocated memory, then the application will need to save those pointers + elsewhere so that they can be eventually freed. + + If inflateGetHeader is not used, then the header information is simply + discarded. The header is always checked for validity, including the header + CRC if present. inflateReset() will reset the process to discard the header + information. The application would need to call inflateGetHeader() again to + retrieve the header from the next gzip stream. + + inflateGetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + + +int inflateBackInit(z_stream* strm, int windowBits, ubyte* window) +{ + return inflateBackInit_(strm, windowBits, window, ZLIB_VERSION.ptr, z_stream.sizeof); +} +/* + Initialize the internal stream state for decompression using inflateBack() + calls. The fields zalloc, zfree and opaque in strm must be initialized + before the call. If zalloc and zfree are Z_NULL, then the default library- + derived memory allocation routines are used. windowBits is the base two + logarithm of the window size, in the range 8 .. 15. window is a caller + supplied buffer of that size. Except for special applications where it is + assured that deflate was used with small window sizes, windowBits must be 15 + and a 32K byte window must be supplied to be able to decompress general + deflate streams. + + See inflateBack() for the usage of these routines. + + inflateBackInit will return Z_OK on success, Z_STREAM_ERROR if any of + the parameters are invalid, Z_MEM_ERROR if the internal state could not be + allocated, or Z_VERSION_ERROR if the version of the library does not match + the version of the header file. +*/ + +alias in_func = uint function(void*, ubyte**); +alias out_func = int function(void*, ubyte*, uint); + +int inflateBack(z_stream* strm, + in_func f_in, + void* in_desc, + out_func f_out, + void* out_desc); +/* + inflateBack() does a raw inflate with a single call using a call-back + interface for input and output. This is potentially more efficient than + inflate() for file i/o applications, in that it avoids copying between the + output and the sliding window by simply making the window itself the output + buffer. inflate() can be faster on modern CPUs when used with large + buffers. inflateBack() trusts the application to not change the output + buffer passed by the output function, at least until inflateBack() returns. + + inflateBackInit() must be called first to allocate the internal state + and to initialize the state with the user-provided window buffer. + inflateBack() may then be used multiple times to inflate a complete, raw + deflate stream with each call. inflateBackEnd() is then called to free the + allocated state. + + A raw deflate stream is one with no zlib or gzip header or trailer. + This routine would normally be used in a utility that reads zip or gzip + files and writes out uncompressed files. The utility would decode the + header and process the trailer on its own, hence this routine expects only + the raw deflate stream to decompress. This is different from the default + behavior of inflate(), which expects a zlib header and trailer around the + deflate stream. + + inflateBack() uses two subroutines supplied by the caller that are then + called by inflateBack() for input and output. inflateBack() calls those + routines until it reads a complete deflate stream and writes out all of the + uncompressed data, or until it encounters an error. The function's + parameters and return types are defined above in the in_func and out_func + typedefs. inflateBack() will call in(in_desc, &buf) which should return the + number of bytes of provided input, and a pointer to that input in buf. If + there is no input available, in() must return zero -- buf is ignored in that + case -- and inflateBack() will return a buffer error. inflateBack() will + call out(out_desc, buf, len) to write the uncompressed data buf[0 .. len-1]. + out() should return zero on success, or non-zero on failure. If out() + returns non-zero, inflateBack() will return with an error. Neither in() nor + out() are permitted to change the contents of the window provided to + inflateBackInit(), which is also the buffer that out() uses to write from. + The length written by out() will be at most the window size. Any non-zero + amount of input may be provided by in(). + + For convenience, inflateBack() can be provided input on the first call by + setting strm->next_in and strm->avail_in. If that input is exhausted, then + in() will be called. Therefore strm->next_in must be initialized before + calling inflateBack(). If strm->next_in is Z_NULL, then in() will be called + immediately for input. If strm->next_in is not Z_NULL, then strm->avail_in + must also be initialized, and then if strm->avail_in is not zero, input will + initially be taken from strm->next_in[0 .. strm->avail_in - 1]. + + The in_desc and out_desc parameters of inflateBack() is passed as the + first parameter of in() and out() respectively when they are called. These + descriptors can be optionally used to pass any information that the caller- + supplied in() and out() functions need to do their job. + + On return, inflateBack() will set strm->next_in and strm->avail_in to + pass back any unused input that was provided by the last in() call. The + return values of inflateBack() can be Z_STREAM_END on success, Z_BUF_ERROR + if in() or out() returned an error, Z_DATA_ERROR if there was a format error + in the deflate stream (in which case strm->msg is set to indicate the nature + of the error), or Z_STREAM_ERROR if the stream was not properly initialized. + In the case of Z_BUF_ERROR, an input or output error can be distinguished + using strm->next_in which will be Z_NULL only if in() returned an error. If + strm->next_in is not Z_NULL, then the Z_BUF_ERROR was due to out() returning + non-zero. (in() will always be called before out(), so strm->next_in is + assured to be defined if out() returns non-zero.) Note that inflateBack() + cannot return Z_OK. +*/ + +int inflateBackEnd(z_stream* strm); +/* + All memory allocated by inflateBackInit() is freed. + + inflateBackEnd() returns Z_OK on success, or Z_STREAM_ERROR if the stream + state was inconsistent. +*/ + +uint zlibCompileFlags(); +/* Return flags indicating compile-time options. + + Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other: + 1.0: size of uInt + 3.2: size of uLong + 5.4: size of voidpf (pointer) + 7.6: size of z_off_t + + Compiler, assembler, and debug options: + 8: ZLIB_DEBUG + 9: ASMV or ASMINF -- use ASM code + 10: ZLIB_WINAPI -- exported functions use the WINAPI calling convention + 11: 0 (reserved) + + One-time table building (smaller code, but not thread-safe if true): + 12: BUILDFIXED -- build static block decoding tables when needed + 13: DYNAMIC_CRC_TABLE -- build CRC calculation tables when needed + 14,15: 0 (reserved) + + Library content (indicates missing functionality): + 16: NO_GZCOMPRESS -- gz* functions cannot compress (to avoid linking + deflate code when not needed) + 17: NO_GZIP -- deflate can't write gzip streams, and inflate can't detect + and decode gzip streams (to avoid linking crc code) + 18-19: 0 (reserved) + + Operation variations (changes in library functionality): + 20: PKZIP_BUG_WORKAROUND -- slightly more permissive inflate + 21: FASTEST -- deflate algorithm with only one, lowest compression level + 22,23: 0 (reserved) + + The sprintf variant used by gzprintf (zero is best): + 24: 0 = vs*, 1 = s* -- 1 means limited to 20 arguments after the format + 25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() not secure! + 26: 0 = returns value, 1 = void -- 1 means inferred string length returned + + Remainder: + 27-31: 0 (reserved) + */ + + /* utility functions */ + +/* + The following utility functions are implemented on top of the basic + stream-oriented functions. To simplify the interface, some default options + are assumed (compression level and memory usage, standard memory allocation + functions). The source code of these utility functions can be modified if + you need special options. +*/ + +int compress(ubyte* dest, + size_t* destLen, + const(ubyte)* source, + size_t sourceLen); +/* + Compresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total size + of the destination buffer, which must be at least the value returned by + compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed data. compress() is equivalent to compress2() with a level + parameter of Z_DEFAULT_COMPRESSION. + + compress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer. +*/ + +int compress2(ubyte* dest, + size_t* destLen, + const(ubyte)* source, + size_t sourceLen, + int level); +/* + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte + length of the source buffer. Upon entry, destLen is the total size of the + destination buffer, which must be at least the value returned by + compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed data. + + compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, + Z_STREAM_ERROR if the level parameter is invalid. +*/ + +size_t compressBound(size_t sourceLen); +/* + compressBound() returns an upper bound on the compressed size after + compress() or compress2() on sourceLen bytes. It would be used before a + compress() or compress2() call to allocate the destination buffer. +*/ + +int uncompress(ubyte* dest, + size_t* destLen, + const(ubyte)* source, + size_t sourceLen); +/* + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total size + of the destination buffer, which must be large enough to hold the entire + uncompressed data. (The size of the uncompressed data must have been saved + previously by the compressor and transmitted to the decompressor by some + mechanism outside the scope of this compression library.) Upon exit, destLen + is the actual size of the uncompressed data. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete. In + the case where there is not enough room, uncompress() will fill the output + buffer with the uncompressed data up to that point. +*/ + +int uncompress2(ubyte* dest, + size_t* destLen, + const(ubyte)* source, + size_t* sourceLen); +/* + Same as uncompress, except that sourceLen is a pointer, where the + length of the source is *sourceLen. On return, *sourceLen is the number of + source bytes consumed. +*/ + + /* gzip file access functions */ + +/* + This library supports reading and writing files in gzip (.gz) format with + an interface similar to that of stdio, using the functions that start with + "gz". The gzip format is different from the zlib format. gzip is a gzip + wrapper, documented in RFC 1952, wrapped around a deflate stream. +*/ + +alias gzFile = void*; +alias z_off_t = int; // file offset +alias z_size_t = size_t; + +gzFile gzopen(const(char)* path, const(char)* mode); +/* + Opens a gzip (.gz) file for reading or writing. The mode parameter is as + in fopen ("rb" or "wb") but can also include a compression level ("wb9") or + a strategy: 'f' for filtered data as in "wb6f", 'h' for Huffman-only + compression as in "wb1h", 'R' for run-length encoding as in "wb1R", or 'F' + for fixed code compression as in "wb9F". (See the description of + deflateInit2 for more information about the strategy parameter.) 'T' will + request transparent writing or appending with no compression and not using + the gzip format. + + "a" can be used instead of "w" to request that the gzip stream that will + be written be appended to the file. "+" will result in an error, since + reading and writing to the same gzip file is not supported. The addition of + "x" when writing will create the file exclusively, which fails if the file + already exists. On systems that support it, the addition of "e" when + reading or writing will set the flag to close the file on an execve() call. + + These functions, as well as gzip, will read and decode a sequence of gzip + streams in a file. The append function of gzopen() can be used to create + such a file. (Also see gzflush() for another way to do this.) When + appending, gzopen does not test whether the file begins with a gzip stream, + nor does it look for the end of the gzip streams to begin appending. gzopen + will simply append a gzip stream to the existing file. + + gzopen can be used to read a file which is not in gzip format; in this + case gzread will directly read from the file without decompression. When + reading, this will be detected automatically by looking for the magic two- + byte gzip header. + + gzopen returns NULL if the file could not be opened, if there was + insufficient memory to allocate the gzFile state, or if an invalid mode was + specified (an 'r', 'w', or 'a' was not provided, or '+' was provided). + errno can be checked to determine if the reason gzopen failed was that the + file could not be opened. +*/ + +gzFile gzdopen(int fd, const(char)* mode); +/* + gzdopen associates a gzFile with the file descriptor fd. File descriptors + are obtained from calls like open, dup, creat, pipe or fileno (if the file + has been previously opened with fopen). The mode parameter is as in gzopen. + + The next call of gzclose on the returned gzFile will also close the file + descriptor fd, just like fclose(fdopen(fd, mode)) closes the file descriptor + fd. If you want to keep fd open, use fd = dup(fd_keep); gz = gzdopen(fd, + mode);. The duplicated descriptor should be saved to avoid a leak, since + gzdopen does not close fd if it fails. If you are using fileno() to get the + file descriptor from a FILE *, then you will have to use dup() to avoid + double-close()ing the file descriptor. Both gzclose() and fclose() will + close the associated file descriptor, so they need to have different file + descriptors. + + gzdopen returns NULL if there was insufficient memory to allocate the + gzFile state, if an invalid mode was specified (an 'r', 'w', or 'a' was not + provided, or '+' was provided), or if fd is -1. The file descriptor is not + used until the next gz* read, write, seek, or close operation, so gzdopen + will not detect if fd is invalid (unless fd is -1). +*/ + +int gzbuffer(gzFile file, uint size); +/* + Set the internal buffer size used by this library's functions. The + default buffer size is 8192 bytes. This function must be called after + gzopen() or gzdopen(), and before any other calls that read or write the + file. The buffer memory allocation is always deferred to the first read or + write. Three times that size in buffer space is allocated. A larger buffer + size of, for example, 64K or 128K bytes will noticeably increase the speed + of decompression (reading). + + The new buffer size also affects the maximum length for gzprintf(). + + gzbuffer() returns 0 on success, or -1 on failure, such as being called + too late. +*/ + +int gzsetparams(gzFile file, int level, int strategy); +/* + Dynamically update the compression level or strategy. See the description + of deflateInit2 for the meaning of these parameters. Previously provided + data is flushed before the parameter change. + + gzsetparams returns Z_OK if success, Z_STREAM_ERROR if the file was not + opened for writing, Z_ERRNO if there is an error writing the flushed data, + or Z_MEM_ERROR if there is a memory allocation error. +*/ + +int gzread(gzFile file, void* buf, uint len); +/* + Reads the given number of uncompressed bytes from the compressed file. If + the input file is not in gzip format, gzread copies the given number of + bytes into the buffer directly from the file. + + After reaching the end of a gzip stream in the input, gzread will continue + to read, looking for another gzip stream. Any number of gzip streams may be + concatenated in the input file, and will all be decompressed by gzread(). + If something other than a gzip stream is encountered after a gzip stream, + that remaining trailing garbage is ignored (and no error is returned). + + gzread can be used to read a gzip file that is being concurrently written. + Upon reaching the end of the input, gzread will return with the available + data. If the error code returned by gzerror is Z_OK or Z_BUF_ERROR, then + gzclearerr can be used to clear the end of file indicator in order to permit + gzread to be tried again. Z_OK indicates that a gzip stream was completed + on the last gzread. Z_BUF_ERROR indicates that the input file ended in the + middle of a gzip stream. Note that gzread does not return -1 in the event + of an incomplete gzip stream. This error is deferred until gzclose(), which + will return Z_BUF_ERROR if the last gzread ended in the middle of a gzip + stream. Alternatively, gzerror can be used before gzclose to detect this + case. + + gzread returns the number of uncompressed bytes actually read, less than + len for end of file, or -1 for error. If len is too large to fit in an int, + then nothing is read, -1 is returned, and the error state is set to + Z_STREAM_ERROR. +*/ + +z_size_t gzfread(void* buf, z_size_t size, z_size_t nitems, gzFile file); +/* + Read up to nitems items of size size from file to buf, otherwise operating + as gzread() does. This duplicates the interface of stdio's fread(), with + size_t request and return types. If the library defines size_t, then + z_size_t is identical to size_t. If not, then z_size_t is an unsigned + integer type that can contain a pointer. + + gzfread() returns the number of full items read of size size, or zero if + the end of the file was reached and a full item could not be read, or if + there was an error. gzerror() must be consulted if zero is returned in + order to determine if there was an error. If the multiplication of size and + nitems overflows, i.e. the product does not fit in a z_size_t, then nothing + is read, zero is returned, and the error state is set to Z_STREAM_ERROR. + + In the event that the end of file is reached and only a partial item is + available at the end, i.e. the remaining uncompressed data length is not a + multiple of size, then the final partial item is nevetheless read into buf + and the end-of-file flag is set. The length of the partial item read is not + provided, but could be inferred from the result of gztell(). This behavior + is the same as the behavior of fread() implementations in common libraries, + but it prevents the direct use of gzfread() to read a concurrently written + file, reseting and retrying on end-of-file, when size is not 1. +*/ + +int gzwrite(gzFile file, void* buf, uint len); +/* + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of uncompressed bytes written or 0 in case of + error. +*/ + +z_size_t gzfwrite(void* buf, z_size_t size, z_size_t nitems, gzFile file); +/* + gzfwrite() writes nitems items of size size from buf to file, duplicating + the interface of stdio's fwrite(), with size_t request and return types. If + the library defines size_t, then z_size_t is identical to size_t. If not, + then z_size_t is an unsigned integer type that can contain a pointer. + + gzfwrite() returns the number of full items written of size size, or zero + if there was an error. If the multiplication of size and nitems overflows, + i.e. the product does not fit in a z_size_t, then nothing is written, zero + is returned, and the error state is set to Z_STREAM_ERROR. +*/ + +int gzprintf(gzFile file, const(char)* format, ...); +/* + Converts, formats, and writes the arguments to the compressed file under + control of the format string, as in fprintf. gzprintf returns the number of + uncompressed bytes actually written, or a negative zlib error code in case + of error. The number of uncompressed bytes written is limited to 8191, or + one less than the buffer size given to gzbuffer(). The caller should assure + that this limit is not exceeded. If it is exceeded, then gzprintf() will + return an error (0) with nothing written. In this case, there may also be a + buffer overflow with unpredictable consequences, which is possible only if + zlib was compiled with the insecure functions sprintf() or vsprintf() + because the secure snprintf() or vsnprintf() functions were not available. + This can be determined using zlibCompileFlags(). +*/ + +int gzputs(gzFile file, const(char)* s); +/* + Writes the given null-terminated string to the compressed file, excluding + the terminating null character. + + gzputs returns the number of characters written, or -1 in case of error. +*/ + +const(char)* gzgets(gzFile file, const(char)* buf, int len); +/* + Reads bytes from the compressed file until len-1 characters are read, or a + newline character is read and transferred to buf, or an end-of-file + condition is encountered. If any characters are read or if len == 1, the + string is terminated with a null character. If no characters are read due + to an end-of-file or len < 1, then the buffer is left untouched. + + gzgets returns buf which is a null-terminated string, or it returns NULL + for end-of-file or in case of error. If there was an error, the contents at + buf are indeterminate. +*/ + +int gzputc(gzFile file, int c); +/* + Writes c, converted to an unsigned char, into the compressed file. gzputc + returns the value that was written, or -1 in case of error. +*/ + +int gzgetc(gzFile file); +/* + Reads one byte from the compressed file. gzgetc returns this byte or -1 + in case of end of file or error. This is implemented as a macro for speed. + As such, it does not do all of the checking the other functions do. I.e. + it does not check to see if file is NULL, nor whether the structure file + points to has been clobbered or not. +*/ + +int gzungetc(int c, gzFile file); +/* + Push one character back onto the stream to be read as the first character + on the next read. At least one character of push-back is allowed. + gzungetc() returns the character pushed, or -1 on failure. gzungetc() will + fail if c is -1, and may fail if a character has been pushed but not read + yet. If gzungetc is used immediately after gzopen or gzdopen, at least the + output buffer size of pushed characters is allowed. (See gzbuffer above.) + The pushed character will be discarded if the stream is repositioned with + gzseek() or gzrewind(). +*/ + +int gzflush(gzFile file, int flush); +/* + Flushes all pending output into the compressed file. The parameter flush + is as in the deflate() function. The return value is the zlib error number + (see function gzerror below). gzflush is only permitted when writing. + + If the flush parameter is Z_FINISH, the remaining data is written and the + gzip stream is completed in the output. If gzwrite() is called again, a new + gzip stream will be started in the output. gzread() is able to read such + concatenated gzip streams. + + gzflush should be called only when strictly necessary because it will + degrade compression if called too often. +*/ + +z_off_t gzseek(gzFile file, z_off_t offset, int whence); +/* + Sets the starting position for the next gzread or gzwrite on the given + compressed file. The offset represents a number of bytes in the + uncompressed data stream. The whence parameter is defined as in lseek(2); + the value SEEK_END is not supported. + + If the file is opened for reading, this function is emulated but can be + extremely slow. If the file is opened for writing, only forward seeks are + supported; gzseek then compresses a sequence of zeroes up to the new + starting position. + + gzseek returns the resulting offset location as measured in bytes from + the beginning of the uncompressed stream, or -1 in case of error, in + particular if the file is opened for writing and the new starting position + would be before the current position. +*/ + +int gzrewind(gzFile file); +/* + Rewinds the given file. This function is supported only for reading. + + gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) +*/ + +z_off_t gztell(gzFile file); +/* + Returns the starting position for the next gzread or gzwrite on the given + compressed file. This position represents a number of bytes in the + uncompressed data stream, and is zero when starting, even if appending or + reading a gzip stream from the middle of a file using gzdopen(). + + gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) +*/ + +z_off_t gzoffset(gzFile file); +/* + Returns the current offset in the file being read or written. This offset + includes the count of bytes that precede the gzip stream, for example when + appending or when using gzdopen() for reading. When reading, the offset + does not include as yet unused buffered input. This information can be used + for a progress indicator. On error, gzoffset() returns -1. +*/ + +int gzeof(gzFile file); +/* + Returns true (1) if the end-of-file indicator has been set while reading, + false (0) otherwise. Note that the end-of-file indicator is set only if the + read tried to go past the end of the input, but came up short. Therefore, + just like feof(), gzeof() may return false even if there is no more data to + read, in the event that the last read request was for the exact number of + bytes remaining in the input file. This will happen if the input file size + is an exact multiple of the buffer size. + + If gzeof() returns true, then the read functions will return no more data, + unless the end-of-file indicator is reset by gzclearerr() and the input file + has grown since the previous end of file was detected. +*/ + +int gzdirect(gzFile file); +/* + Returns true (1) if file is being copied directly while reading, or false + (0) if file is a gzip stream being decompressed. + + If the input file is empty, gzdirect() will return true, since the input + does not contain a gzip stream. + + If gzdirect() is used immediately after gzopen() or gzdopen() it will + cause buffers to be allocated to allow reading the file to determine if it + is a gzip file. Therefore if gzbuffer() is used, it should be called before + gzdirect(). + + When writing, gzdirect() returns true (1) if transparent writing was + requested ("wT" for the gzopen() mode), or false (0) otherwise. (Note: + gzdirect() is not needed when writing. Transparent writing must be + explicitly requested, so the application already knows the answer. When + linking statically, using gzdirect() will include all of the zlib code for + gzip file reading and decompression, which may not be desired.) +*/ + +int gzclose(gzFile file); +/* + Flushes all pending output if necessary, closes the compressed file and + deallocates the (de)compression state. Note that once file is closed, you + cannot call gzerror with file, since its structures have been deallocated. + gzclose must not be called more than once on the same file, just as free + must not be called more than once on the same allocation. + + gzclose will return Z_STREAM_ERROR if file is not valid, Z_ERRNO on a + file operation error, Z_MEM_ERROR if out of memory, Z_BUF_ERROR if the + last read ended in the middle of a gzip stream, or Z_OK on success. +*/ + +int gzclose_r(gzFile file); +int gzclose_w(gzFile file); +/* + Same as gzclose(), but gzclose_r() is only for use when reading, and + gzclose_w() is only for use when writing or appending. The advantage to + using these instead of gzclose() is that they avoid linking in zlib + compression or decompression code that is not used when only reading or only + writing respectively. If gzclose() is used, then both compression and + decompression code will be included the application when linking to a static + zlib library. +*/ + +const(char)* gzerror(gzFile file, int* errnum); +/* + Returns the error message for the last error which occurred on the given + compressed file. errnum is set to zlib error number. If an error occurred + in the file system and not in the compression library, errnum is set to + Z_ERRNO and the application may consult errno to get the exact error code. + + The application must not modify the returned string. Future calls to + this function may invalidate the previously returned string. If file is + closed, then the string previously returned by gzerror will no longer be + available. + + gzerror() should be used to distinguish errors from end-of-file for those + functions above that do not distinguish those cases in their return values. +*/ + +void gzclearerr(gzFile file); +/* + Clears the error and end-of-file flags for file. This is analogous to the + clearerr() function in stdio. This is useful for continuing to read a gzip + file that is being written concurrently. +*/ + + /* checksum functions */ + +/* + These functions are not related to compression but are exported + anyway because they might be useful in applications using the compression + library. +*/ + +uint adler32(uint adler, const(ubyte)* buf, uint len); +/* + Update a running Adler-32 checksum with the bytes buf[0 .. len-1] and + return the updated checksum. If buf is Z_NULL, this function returns the + required initial value for the checksum. + + An Adler-32 checksum is almost as reliable as a CRC-32 but can be computed + much faster. + + Usage example: + + uLong adler = adler32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) + adler = adler32(adler, buffer, length); + + if (adler != original_adler) error(); +*/ + +uint adler32_z (uint adler, const(ubyte)* buf, z_size_t len); +/* + Same as adler32(), but with a size_t length. +*/ + +uint adler32_combine(uint adler1, uint adler2, z_off_t len2); +/* + Combine two Adler-32 checksums into one. For two sequences of bytes, seq1 + and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for + each, adler1 and adler2. adler32_combine() returns the Adler-32 checksum of + seq1 and seq2 concatenated, requiring only adler1, adler2, and len2. Note + that the z_off_t type (like off_t) is a signed integer. If len2 is + negative, the result has no meaning or utility. +*/ + +uint crc32(uint crc, const(ubyte)* buf, uint len); +/* + Update a running CRC-32 with the bytes buf[0 .. len-1] and return the + updated CRC-32. If buf is Z_NULL, this function returns the required + initial value for the crc. Pre- and post-conditioning (one's complement) is + performed within this function so it shouldn't be done by the application. + + Usage example: + + uLong crc = crc32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) + crc = crc32(crc, buffer, length); + + if (crc != original_crc) error(); +*/ + +uint crc32_z(uint adler, const(ubyte)* buf, z_size_t len); +/* + Same as crc32(), but with a size_t length. +*/ + +uint crc32_combine(uint crc1, uint crc2, z_off_t len2); + +/* + Combine two CRC-32 check values into one. For two sequences of bytes, + seq1 and seq2 with lengths len1 and len2, CRC-32 check values were + calculated for each, crc1 and crc2. crc32_combine() returns the CRC-32 + check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and + len2. +*/ + + + /* various hacks, don't look :) */ + +/* deflateInit and inflateInit are macros to allow checking the zlib version + * and the compiler's view of z_stream: + */ +int deflateInit_(z_streamp strm, + int level, + const(char)* versionx, + int stream_size); + +int inflateInit_(z_streamp strm, + const(char)* versionx, + int stream_size); + +int deflateInit2_(z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, + int strategy, + const(char)* versionx, + int stream_size); + +int inflateBackInit_(z_stream* strm, + int windowBits, + ubyte* window, + const(char)* z_version, + int stream_size); + +int inflateInit2_(z_streamp strm, + int windowBits, + const(char)* versionx, + int stream_size); + +const(char)* zError(int err); +int inflateSyncPoint(z_streamp z); +const(uint)* get_crc_table(); diff --git a/libphobos/src/index.d b/libphobos/src/index.d new file mode 100644 index 0000000..2792b7b --- /dev/null +++ b/libphobos/src/index.d @@ -0,0 +1,526 @@ +Ddoc + +$(P Phobos is the standard runtime library that comes with the D language +compiler.) + +$(P Generally, the $(D std) namespace is used for the main modules in the +Phobos standard library. The $(D etc) namespace is used for external C/C++ +library bindings. The $(D core) namespace is used for low-level D runtime +functions.) + +$(P The following table is a quick reference guide for which Phobos modules to +use for a given category of functionality. Note that some modules may appear in +more than one category, as some Phobos modules are quite generic and can be +applied in a variety of situations.) + +$(BOOKTABLE , + $(TR + $(TH Modules) + $(TH Description) + ) + $(LEADINGROW Algorithms & ranges) + $(TR + $(TDNW + $(MREF std,algorithm)$(BR) + $(MREF std,range)$(BR) + $(MREF std,range,primitives)$(BR) + $(MREF std,range,interfaces)$(BR) + ) + $(TD Generic algorithms that work with $(MREF_ALTTEXT ranges, std,range) + of any type, including strings, arrays, and other kinds of + sequentially-accessed data. Algorithms include searching, + comparison, iteration, sorting, set operations, and mutation. + ) + ) + $(LEADINGROW Array manipulation) + $(TR + $(TDNW + $(MREF std,array)$(BR) + $(MREF std,algorithm)$(BR) + ) + $(TD Convenient operations commonly used with built-in arrays. + Note that many common array operations are subsets of more generic + algorithms that work with arbitrary ranges, so they are found in + $(D std.algorithm). + ) + ) + $(LEADINGROW Containers) + $(TR + $(TDNW + $(MREF std,container,array)$(BR) + $(MREF std,container,binaryheap)$(BR) + $(MREF std,container,dlist)$(BR) + $(MREF std,container,rbtree)$(BR) + $(MREF std,container,slist)$(BR) + ) + $(TD See $(MREF_ALTTEXT std.container.*, std,container) for an + overview. + ) + ) + $(LEADINGROW Data formats) + $(TR + $(TDNW $(MREF std,base64)) + $(TD Encoding / decoding Base64 format.) + ) + $(TR + $(TDNW $(MREF std,csv)) + $(TD Read Comma Separated Values and its variants from an input range of $(CODE dchar).) + ) + $(TR + $(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.) + ) + $(TR + $(TDNW $(MREF std,zlib)) + $(TD Compress/decompress data using the zlib library.) + ) + $(LEADINGROW Data integrity) + $(TR + $(TDNW $(MREF std,experimental,checkedint)) + $(TD Checked integral types.) + ) + $(TR + $(TDNW $(MREF std,digest,crc)) + $(TD Cyclic Redundancy Check (32-bit) implementation.) + ) + $(TR + $(TDNW $(MREF std,digest,digest)) + $(TD Compute digests such as md5, sha1 and crc32.) + ) + $(TR + $(TDNW $(MREF std,digest,hmac)) + $(TD Compute HMAC digests of arbitrary data.) + ) + $(TR + $(TDNW $(MREF std,digest,md)) + $(TD Compute MD5 hash of arbitrary data.) + ) + $(TR + $(TDNW $(MREF std,digest,murmurhash)) + $(TD Compute MurmurHash of arbitrary data.) + ) + $(TR + $(TDNW $(MREF std,digest,ripemd)) + $(TD Compute RIPEMD-160 hash of arbitrary data.) + ) + $(TR + $(TDNW $(MREF std,digest,sha)) + $(TD Compute SHA1 and SHA2 hashes of arbitrary data.) + ) + $(LEADINGROW Date & time) + $(TR + $(TDNW $(MREF std,datetime)) + $(TD Provides convenient access to date and time representations.) + ) + $(TR + $(TDNW $(MREF core,time)) + $(TD Implements low-level time primitives.) + ) + $(LEADINGROW Exception handling) + $(TR + $(TDNW $(MREF std,exception)) + $(TD Implements routines related to exceptions.) + ) + $(TR + $(TDNW $(MREF core,exception)) + $(TD Defines built-in exception types and low-level + language hooks required by the compiler.) + ) + $(LEADINGROW External library bindings) + $(TR + $(TDNW $(MREF etc,c,curl)) + $(TD Interface to libcurl C library.) + ) + $(TR + $(TDNW $(MREF etc,c,odbc,sql)) + $(TD Interface to ODBC C library.) + ) + $(TR + $(TDNW $(MREF etc,c,odbc,sqlext)) + ) + $(TR + $(TDNW $(MREF etc,c,odbc,sqltypes)) + ) + $(TR + $(TDNW $(MREF etc,c,odbc,sqlucode)) + ) + $(TR + $(TDNW $(MREF etc,c,sqlite3)) + $(TD Interface to SQLite C library.) + ) + $(TR + $(TDNW $(MREF etc,c,zlib)) + $(TD Interface to zlib C library.) + ) + $(LEADINGROW I/O & File system) + $(TR + $(TDNW $(MREF std,file)) + $(TD Manipulate files and directories.) + ) + $(TR + $(TDNW $(MREF std,path)) + $(TD Manipulate strings that represent filesystem paths.) + ) + $(TR + $(TDNW $(MREF std,stdio)) + $(TD Perform buffered I/O.) + ) + $(LEADINGROW Interoperability) + $(TR + $(TDNW + $(MREF core,stdc,complex)$(BR) + $(MREF core,stdc,ctype)$(BR) + $(MREF core,stdc,errno)$(BR) + $(MREF core,stdc,fenv)$(BR) + $(MREF core,stdc,float_)$(BR) + $(MREF core,stdc,inttypes)$(BR) + $(MREF core,stdc,limits)$(BR) + $(MREF core,stdc,locale)$(BR) + $(MREF core,stdc,math)$(BR) + $(MREF core,stdc,signal)$(BR) + $(MREF core,stdc,stdarg)$(BR) + $(MREF core,stdc,stddef)$(BR) + $(MREF core,stdc,stdint)$(BR) + $(MREF core,stdc,stdio)$(BR) + $(MREF core,stdc,stdlib)$(BR) + $(MREF core,stdc,string)$(BR) + $(MREF core,stdc,tgmath)$(BR) + $(MREF core,stdc,time)$(BR) + $(MREF core,stdc,wchar_)$(BR) + $(MREF core,stdc,wctype)$(BR) + ) + $(TD + D bindings for standard C headers.$(BR)$(BR) + These are mostly undocumented, as documentation + for the functions these declarations provide + bindings to can be found on external resources. + ) + ) + $(LEADINGROW Memory management) + $(TR + $(TDNW $(MREF core,memory)) + $(TD Control the built-in garbage collector.) + ) + $(TR + $(TDNW $(MREF std,typecons)) + $(TD Build scoped variables and reference-counted types.) + ) + $(LEADINGROW Metaprogramming) + $(TR + $(TDNW $(MREF core,attribute)) + $(TD Definitions of special attributes recognized by the compiler.) + ) + $(TR + $(TDNW $(MREF core,demangle)) + $(TD Convert $(I mangled) D symbol identifiers to source representation.) + ) + $(TR + $(TDNW $(MREF std,demangle)) + $(TD A simple wrapper around core.demangle.) + ) + $(TR + $(TDNW $(MREF std,meta)) + $(TD Construct and manipulate template argument lists (aka type lists).) + ) + $(TR + $(TDNW $(MREF std,traits)) + $(TD Extract information about types and symbols at compile time.) + ) + $(TR + $(TDNW $(MREF std,typecons)) + $(TD Construct new, useful general purpose types.) + ) + $(LEADINGROW Multitasking) + $(TR + $(TDNW $(MREF std,concurrency)) + $(TD Low level messaging API for threads.) + ) + $(TR + $(TDNW $(MREF std,parallelism)) + $(TD High level primitives for SMP parallelism.) + ) + $(TR + $(TDNW $(MREF std,process)) + $(TD Starting and manipulating processes.) + ) + $(TR + $(TDNW $(MREF core,atomic)) + $(TD Basic support for lock-free concurrent programming.) + ) + $(TR + $(TDNW $(MREF core,sync,barrier)) + $(TD Synchronize the progress of a group of threads.) + ) + $(TR + $(TDNW $(MREF core,sync,condition)) + $(TD Synchronized condition checking.) + ) + $(TR + $(TDNW $(MREF core,sync,exception)) + $(TD Base class for synchronization exceptions.) + ) + $(TR + $(TDNW $(MREF core,sync,mutex)) + $(TD Mutex for mutually exclusive access.) + ) + $(TR + $(TDNW $(MREF core,sync,rwmutex)) + $(TD Shared read access and mutually exclusive write access.) + ) + $(TR + $(TDNW $(MREF core,sync,semaphore)) + $(TD General use synchronization semaphore.) + ) + $(TR + $(TDNW $(MREF core,thread)) + $(TD Thread creation and management.) + ) + $(LEADINGROW Networking) + $(TR + $(TDNW $(MREF std,socket)) + $(TD Socket primitives.) + ) + $(TR + $(TDNW $(MREF std,net,curl)) + $(TD Networking client functionality as provided by libcurl.) + ) + $(TR + $(TDNW $(MREF std,net,isemail)) + $(TD Validates an email address according to RFCs 5321, 5322 and others.) + ) + $(TR + $(TDNW $(MREF std,uri)) + $(TD Encode and decode Uniform Resource Identifiers (URIs).) + ) + $(TR + $(TDNW $(MREF std,uuid)) + $(TD Universally-unique identifiers for resources in distributed + systems.) + ) + $(LEADINGROW Numeric) + $(TR + $(TDNW $(MREF std,bigint)) + $(TD An arbitrary-precision integer type.) + ) + $(TR + $(TDNW $(MREF std,complex)) + $(TD A complex number type.) + ) + $(TR + $(TDNW $(MREF std,math)) + $(TD Elementary mathematical functions (powers, roots, trigonometry).) + ) + $(TR + $(TDNW $(MREF std,mathspecial)) + $(TD Families of transcendental functions.) + ) + $(TR + $(TDNW $(MREF std,numeric)) + $(TD Floating point numerics functions.) + ) + $(TR + $(TDNW $(MREF std,random)) + $(TD Pseudo-random number generators.) + ) + $(TR + $(TDNW $(MREF core,checkedint)) + $(TD Range-checking integral arithmetic primitives.) + ) + $(TR + $(TDNW $(MREF core,math)) + $(TD Built-in mathematical intrinsics.) + ) + $(LEADINGROW Paradigms) + $(TR + $(TDNW $(MREF std,functional)) + $(TD Functions that manipulate other functions.) + ) + $(TR + $(TDNW $(MREF std,algorithm)) + $(TD Generic algorithms for processing sequences.) + ) + $(TR + $(TDNW $(MREF std,signals)) + $(TD Signal-and-slots framework for event-driven programming.) + ) + $(LEADINGROW Runtime utilities) + $(TR + $(TDNW $(MREF1 object)) + $(TD Core language definitions. Automatically imported.) + ) + $(TR + $(TDNW $(MREF std,getopt)) + $(TD Parsing of command-line arguments.) + ) + $(TR + $(TDNW $(MREF std,compiler)) + $(TD Host compiler vendor string and language version.) + ) + $(TR + $(TDNW $(MREF std,system)) + $(TD Runtime environment, such as OS type and endianness.) + ) + $(TR + $(TDNW $(MREF core,cpuid)) + $(TD Capabilities of the CPU the program is running on.) + ) + $(TR + $(TDNW $(MREF core,memory)) + $(TD Control the built-in garbage collector.) + ) + $(TR + $(TDNW $(MREF core,runtime)) + $(TD Control and configure the D runtime.) + ) + $(LEADINGROW String manipulation) + $(TR + $(TDNW $(MREF std,string)) + $(TD Algorithms that work specifically with strings.) + ) + $(TR + $(TDNW $(MREF std,array)) + $(TD Manipulate builtin arrays.) + ) + $(TR + $(TDNW $(MREF std,algorithm)) + $(TD Generic algorithms for processing sequences.) + ) + $(TR + $(TDNW $(MREF std,uni)) + $(TD Fundamental Unicode algorithms and data structures.) + ) + $(TR + $(TDNW $(MREF std,utf)) + $(TD Encode and decode UTF-8, UTF-16 and UTF-32 strings.) + ) + $(TR + $(TDNW $(MREF std,format)) + $(TD Format data into strings.) + ) + $(TR + $(TDNW $(MREF std,path)) + $(TD Manipulate strings that represent filesystem paths.) + ) + $(TR + $(TDNW $(MREF std,regex)) + $(TD Regular expressions.) + ) + $(TR + $(TDNW $(MREF std,ascii)) + $(TD Routines specific to the ASCII subset of Unicode.) + ) + $(TR + $(TDNW $(MREF std,encoding)) + $(TD Handle and transcode between various text encodings.) + ) + $(TR + $(TDNW $(MREF std,windows,charset)) + $(TD Windows specific character set support.) + ) + $(TR + $(TDNW $(MREF std,outbuffer)) + $(TD Serialize data to $(CODE ubyte) arrays.) + ) + $(LEADINGROW Type manipulations) + $(TR + $(TDNW $(MREF std,conv)) + $(TD Convert types from one type to another.) + ) + $(TR + $(TDNW $(MREF std,typecons)) + $(TD Type constructors for scoped variables, ref counted types, etc.) + ) + $(TR + $(TDNW $(MREF std,bitmanip)) + $(TD High level bit level manipulation, bit arrays, bit fields.) + ) + $(TR + $(TDNW $(MREF std,variant)) + $(TD Discriminated unions and algebraic types.) + ) + $(TR + $(TDNW $(MREF core,bitop)) + $(TD Low level bit manipulation.) + ) + $(LEADINGROW Vector programming) + $(TR + $(TDNW $(MREF core,simd)) + $(TD SIMD intrinsics) + ) + +$(COMMENT + $(LEADINGROW Undocumented modules (intentionally omitted).) + $(TR + $(TDNW + $(MREF core,sync,config)$(BR) + $(MREF std,container,util)$(BR) + $(MREF std,regex,internal,backtracking)$(BR) + $(MREF std,regex,internal,generator)$(BR) + $(MREF std,regex,internal,ir)$(BR) + $(MREF std,regex,internal,kickstart)$(BR) + $(MREF std,regex,internal,parser)$(BR) + $(MREF std,regex,internal,tests)$(BR) + $(MREF std,regex,internal,thompson)$(BR) + ) + $(TD + Internal modules. + ) + ) + $(TR + $(TDNW + $(MREF core,vararg)$(BR) + $(MREF std,c,fenv)$(BR) + $(MREF std,c,linux,linux)$(BR) + $(MREF std,c,linux,socket)$(BR) + $(MREF std,c,locale)$(BR) + $(MREF std,c,math)$(BR) + $(MREF std,c,process)$(BR) + $(MREF std,c,stdarg)$(BR) + $(MREF std,c,stddef)$(BR) + $(MREF std,c,stdio)$(BR) + $(MREF std,c,stdlib)$(BR) + $(MREF std,c,string)$(BR) + $(MREF std,c,time)$(BR) + $(MREF std,c,wcharh)$(BR) + $(MREF std,stdint)$(BR) + ) + $(TDN + Redirect modules. + ) + ) + $(TR + $(TDNW + $(MREF std,mmfile)$(BR) + $(MREF std,typetuple)$(BR) + ) + $(TD + Deprecated modules. + ) + ) + $(TR + $(TDNW + $(MREF std,experimental,logger)$(BR) + $(MREF std,experimental,logger,core)$(BR) + $(MREF std,experimental,logger,filelogger)$(BR) + $(MREF std,experimental,logger,multilogger)$(BR) + $(MREF std,experimental,logger,nulllogger)$(BR) + ) + $(TD + Experimental modules. + ) + ) +) +) + +Macros: + TITLE=Phobos Runtime Library + DDOC_BLANKLINE= + _= diff --git a/libphobos/src/libgphobos.spec.in b/libphobos/src/libgphobos.spec.in new file mode 100644 index 0000000..0a33e83 --- /dev/null +++ b/libphobos/src/libgphobos.spec.in @@ -0,0 +1,8 @@ +# +# This spec file is read by gdc when linking. +# It is used to specify the libraries we need to link in, in the right +# order. +# + +%rename lib liborig_gdc_renamed +*lib: @SPEC_PHOBOS_DEPS@ %(liborig_gdc_renamed) diff --git a/libphobos/src/std/algorithm/comparison.d b/libphobos/src/std/algorithm/comparison.d new file mode 100644 index 0000000..faa4d44 --- /dev/null +++ b/libphobos/src/std/algorithm/comparison.d @@ -0,0 +1,2159 @@ +// Written in the D programming language. +/** +This is a submodule of $(MREF std, algorithm). +It contains generic _comparison algorithms. + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE Cheat Sheet, +$(TR $(TH Function Name) $(TH Description)) +$(T2 among, + Checks if a value is among a set of values, e.g. + $(D if (v.among(1, 2, 3)) // `v` is 1, 2 or 3)) +$(T2 castSwitch, + $(D (new A()).castSwitch((A a)=>1,(B b)=>2)) returns $(D 1).) +$(T2 clamp, + $(D clamp(1, 3, 6)) returns $(D 3). $(D clamp(4, 3, 6)) returns $(D 4).) +$(T2 cmp, + $(D cmp("abc", "abcd")) is $(D -1), $(D cmp("abc", "aba")) is $(D 1), + and $(D cmp("abc", "abc")) is $(D 0).) +$(T2 either, + Return first parameter $(D p) that passes an $(D if (p)) test, e.g. + $(D either(0, 42, 43)) returns $(D 42).) +$(T2 equal, + Compares ranges for element-by-element equality, e.g. + $(D equal([1, 2, 3], [1.0, 2.0, 3.0])) returns $(D true).) +$(T2 isPermutation, + $(D isPermutation([1, 2], [2, 1])) returns $(D true).) +$(T2 isSameLength, + $(D isSameLength([1, 2, 3], [4, 5, 6])) returns $(D true).) +$(T2 levenshteinDistance, + $(D levenshteinDistance("kitten", "sitting")) returns $(D 3) by using + the $(LINK2 https://en.wikipedia.org/wiki/Levenshtein_distance, + Levenshtein distance _algorithm).) +$(T2 levenshteinDistanceAndPath, + $(D levenshteinDistanceAndPath("kitten", "sitting")) returns + $(D tuple(3, "snnnsni")) by using the + $(LINK2 https://en.wikipedia.org/wiki/Levenshtein_distance, + Levenshtein distance _algorithm).) +$(T2 max, + $(D max(3, 4, 2)) returns $(D 4).) +$(T2 min, + $(D min(3, 4, 2)) returns $(D 2).) +$(T2 mismatch, + $(D mismatch("oh hi", "ohayo")) returns $(D tuple(" hi", "ayo")).) +$(T2 predSwitch, + $(D 2.predSwitch(1, "one", 2, "two", 3, "three")) returns $(D "two").) +) + +Copyright: Andrei Alexandrescu 2008-. + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(HTTP erdani.com, Andrei Alexandrescu) + +Source: $(PHOBOSSRC std/algorithm/_comparison.d) + +Macros: +T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) + */ +module std.algorithm.comparison; + +// FIXME +import std.functional; // : unaryFun, binaryFun; +import std.range.primitives; +import std.traits; +// FIXME +import std.meta : allSatisfy; +import std.typecons; // : tuple, Tuple, Flag, Yes; + +/** +Find $(D value) _among $(D values), returning the 1-based index +of the first matching value in $(D values), or $(D 0) if $(D value) +is not _among $(D values). The predicate $(D pred) is used to +compare values, and uses equality by default. + +Params: + pred = The predicate used to compare the values. + value = The value to search for. + values = The values to compare the value to. + +Returns: + 0 if value was not found among the values, otherwise the index of the + found value plus one is returned. + +See_Also: +$(REF_ALTTEXT find, find, std,algorithm,searching) and $(REF_ALTTEXT canFind, canFind, std,algorithm,searching) for finding a value in a +range. +*/ +uint among(alias pred = (a, b) => a == b, Value, Values...) + (Value value, Values values) +if (Values.length != 0) +{ + foreach (uint i, ref v; values) + { + import std.functional : binaryFun; + if (binaryFun!pred(value, v)) return i + 1; + } + return 0; +} + +/// Ditto +template among(values...) +if (isExpressionTuple!values) +{ + uint among(Value)(Value value) + if (!is(CommonType!(Value, values) == void)) + { + switch (value) + { + foreach (uint i, v; values) + case v: + return i + 1; + default: + return 0; + } + } +} + +/// +@safe unittest +{ + assert(3.among(1, 42, 24, 3, 2)); + + if (auto pos = "bar".among("foo", "bar", "baz")) + assert(pos == 2); + else + assert(false); + + // 42 is larger than 24 + assert(42.among!((lhs, rhs) => lhs > rhs)(43, 24, 100) == 2); +} + +/** +Alternatively, $(D values) can be passed at compile-time, allowing for a more +efficient search, but one that only supports matching on equality: +*/ +@safe unittest +{ + assert(3.among!(2, 3, 4)); + assert("bar".among!("foo", "bar", "baz") == 2); +} + +@safe unittest +{ + import std.meta : AliasSeq; + + if (auto pos = 3.among(1, 2, 3)) + assert(pos == 3); + else + assert(false); + assert(!4.among(1, 2, 3)); + + auto position = "hello".among("hello", "world"); + assert(position); + assert(position == 1); + + alias values = AliasSeq!("foo", "bar", "baz"); + auto arr = [values]; + assert(arr[0 .. "foo".among(values)] == ["foo"]); + assert(arr[0 .. "bar".among(values)] == ["foo", "bar"]); + assert(arr[0 .. "baz".among(values)] == arr); + assert("foobar".among(values) == 0); + + if (auto pos = 3.among!(1, 2, 3)) + assert(pos == 3); + else + assert(false); + assert(!4.among!(1, 2, 3)); + + position = "hello".among!("hello", "world"); + assert(position); + assert(position == 1); + + static assert(!__traits(compiles, "a".among!("a", 42))); + static assert(!__traits(compiles, (Object.init).among!(42, "a"))); +} + +// Used in castSwitch to find the first choice that overshadows the last choice +// in a tuple. +private template indexOfFirstOvershadowingChoiceOnLast(choices...) +{ + alias firstParameterTypes = Parameters!(choices[0]); + alias lastParameterTypes = Parameters!(choices[$ - 1]); + + static if (lastParameterTypes.length == 0) + { + // If the last is null-typed choice, check if the first is null-typed. + enum isOvershadowing = firstParameterTypes.length == 0; + } + else static if (firstParameterTypes.length == 1) + { + // If the both first and last are not null-typed, check for overshadowing. + enum isOvershadowing = + is(firstParameterTypes[0] == Object) // Object overshadows all other classes!(this is needed for interfaces) + || is(lastParameterTypes[0] : firstParameterTypes[0]); + } + else + { + // If the first is null typed and the last is not - the is no overshadowing. + enum isOvershadowing = false; + } + + static if (isOvershadowing) + { + enum indexOfFirstOvershadowingChoiceOnLast = 0; + } + else + { + enum indexOfFirstOvershadowingChoiceOnLast = + 1 + indexOfFirstOvershadowingChoiceOnLast!(choices[1..$]); + } +} + +/** +Executes and returns one of a collection of handlers based on the type of the +switch object. + +The first choice that $(D switchObject) can be casted to the type +of argument it accepts will be called with $(D switchObject) casted to that +type, and the value it'll return will be returned by $(D castSwitch). + +If a choice's return type is void, the choice must throw an exception, unless +all the choices are void. In that case, castSwitch itself will return void. + +Throws: If none of the choice matches, a $(D SwitchError) will be thrown. $(D +SwitchError) will also be thrown if not all the choices are void and a void +choice was executed without throwing anything. + +Params: + choices = The $(D choices) needs to be composed of function or delegate + handlers that accept one argument. There can also be a choice that + accepts zero arguments. That choice will be invoked if the $(D + switchObject) is null. + switchObject = the object against which the tests are being made. + +Returns: + The value of the selected choice. + +Note: $(D castSwitch) can only be used with object types. +*/ +auto castSwitch(choices...)(Object switchObject) +{ + import core.exception : SwitchError; + import std.format : format; + + // Check to see if all handlers return void. + enum areAllHandlersVoidResult = { + bool result = true; + foreach (index, choice; choices) + { + result &= is(ReturnType!choice == void); + } + return result; + }(); + + if (switchObject !is null) + { + + // Checking for exact matches: + const classInfo = typeid(switchObject); + foreach (index, choice; choices) + { + static assert(isCallable!choice, + "A choice handler must be callable"); + + alias choiceParameterTypes = Parameters!choice; + static assert(choiceParameterTypes.length <= 1, + "A choice handler can not have more than one argument."); + + static if (choiceParameterTypes.length == 1) + { + alias CastClass = choiceParameterTypes[0]; + static assert(is(CastClass == class) || is(CastClass == interface), + "A choice handler can have only class or interface typed argument."); + + // Check for overshadowing: + immutable indexOfOvershadowingChoice = + indexOfFirstOvershadowingChoiceOnLast!(choices[0 .. index + 1]); + static assert(indexOfOvershadowingChoice == index, + "choice number %d(type %s) is overshadowed by choice number %d(type %s)".format( + index + 1, CastClass.stringof, indexOfOvershadowingChoice + 1, + Parameters!(choices[indexOfOvershadowingChoice])[0].stringof)); + + if (classInfo == typeid(CastClass)) + { + static if (is(ReturnType!(choice) == void)) + { + choice(cast(CastClass) switchObject); + static if (areAllHandlersVoidResult) + { + return; + } + else + { + throw new SwitchError("Handlers that return void should throw"); + } + } + else + { + return choice(cast(CastClass) switchObject); + } + } + } + } + + // Checking for derived matches: + foreach (choice; choices) + { + alias choiceParameterTypes = Parameters!choice; + static if (choiceParameterTypes.length == 1) + { + if (auto castedObject = cast(choiceParameterTypes[0]) switchObject) + { + static if (is(ReturnType!(choice) == void)) + { + choice(castedObject); + static if (areAllHandlersVoidResult) + { + return; + } + else + { + throw new SwitchError("Handlers that return void should throw"); + } + } + else + { + return choice(castedObject); + } + } + } + } + } + else // If switchObject is null: + { + // Checking for null matches: + foreach (index, choice; choices) + { + static if (Parameters!(choice).length == 0) + { + immutable indexOfOvershadowingChoice = + indexOfFirstOvershadowingChoiceOnLast!(choices[0 .. index + 1]); + + // Check for overshadowing: + static assert(indexOfOvershadowingChoice == index, + "choice number %d(null reference) is overshadowed by choice number %d(null reference)".format( + index + 1, indexOfOvershadowingChoice + 1)); + + if (switchObject is null) + { + static if (is(ReturnType!(choice) == void)) + { + choice(); + static if (areAllHandlersVoidResult) + { + return; + } + else + { + throw new SwitchError("Handlers that return void should throw"); + } + } + else + { + return choice(); + } + } + } + } + } + + // In case nothing matched: + throw new SwitchError("Input not matched by any choice"); +} + +/// +@system unittest +{ + import std.algorithm.iteration : map; + import std.format : format; + + class A + { + int a; + this(int a) {this.a = a;} + @property int i() { return a; } + } + interface I { } + class B : I { } + + Object[] arr = [new A(1), new B(), null]; + + auto results = arr.map!(castSwitch!( + (A a) => "A with a value of %d".format(a.a), + (I i) => "derived from I", + () => "null reference", + ))(); + + // A is handled directly: + assert(results[0] == "A with a value of 1"); + // B has no handler - it is handled by the handler of I: + assert(results[1] == "derived from I"); + // null is handled by the null handler: + assert(results[2] == "null reference"); +} + +/// Using with void handlers: +@system unittest +{ + import std.exception : assertThrown; + + class A { } + class B { } + // Void handlers are allowed if they throw: + assertThrown!Exception( + new B().castSwitch!( + (A a) => 1, + (B d) { throw new Exception("B is not allowed!"); } + )() + ); + + // Void handlers are also allowed if all the handlers are void: + new A().castSwitch!( + (A a) { }, + (B b) { assert(false); }, + )(); +} + +@system unittest +{ + import core.exception : SwitchError; + import std.exception : assertThrown; + + interface I { } + class A : I { } + class B { } + + // Nothing matches: + assertThrown!SwitchError((new A()).castSwitch!( + (B b) => 1, + () => 2, + )()); + + // Choices with multiple arguments are not allowed: + static assert(!__traits(compiles, + (new A()).castSwitch!( + (A a, B b) => 0, + )())); + + // Only callable handlers allowed: + static assert(!__traits(compiles, + (new A()).castSwitch!( + 1234, + )())); + + // Only object arguments allowed: + static assert(!__traits(compiles, + (new A()).castSwitch!( + (int x) => 0, + )())); + + // Object overshadows regular classes: + static assert(!__traits(compiles, + (new A()).castSwitch!( + (Object o) => 0, + (A a) => 1, + )())); + + // Object overshadows interfaces: + static assert(!__traits(compiles, + (new A()).castSwitch!( + (Object o) => 0, + (I i) => 1, + )())); + + // No multiple null handlers allowed: + static assert(!__traits(compiles, + (new A()).castSwitch!( + () => 0, + () => 1, + )())); + + // No non-throwing void handlers allowed(when there are non-void handlers): + assertThrown!SwitchError((new A()).castSwitch!( + (A a) {}, + (B b) => 2, + )()); + + // All-void handlers work for the null case: + null.castSwitch!( + (Object o) { assert(false); }, + () { }, + )(); + + // Throwing void handlers work for the null case: + assertThrown!Exception(null.castSwitch!( + (Object o) => 1, + () { throw new Exception("null"); }, + )()); +} + +@system unittest +{ + interface I { } + class B : I { } + class C : I { } + + assert((new B()).castSwitch!( + (B b) => "class B", + (I i) => "derived from I", + ) == "class B"); + + assert((new C()).castSwitch!( + (B b) => "class B", + (I i) => "derived from I", + ) == "derived from I"); +} + +/** Clamps a value into the given bounds. + +This functions is equivalent to $(D max(lower, min(upper,val))). + +Params: + val = The value to _clamp. + lower = The _lower bound of the _clamp. + upper = The _upper bound of the _clamp. + +Returns: + Returns $(D val), if it is between $(D lower) and $(D upper). + Otherwise returns the nearest of the two. + +*/ +auto clamp(T1, T2, T3)(T1 val, T2 lower, T3 upper) +in +{ + import std.functional : greaterThan; + assert(!lower.greaterThan(upper)); +} +body +{ + return max(lower, min(upper, val)); +} + +/// +@safe unittest +{ + assert(clamp(2, 1, 3) == 2); + assert(clamp(0, 1, 3) == 1); + assert(clamp(4, 1, 3) == 3); + + assert(clamp(1, 1, 1) == 1); + + assert(clamp(5, -1, 2u) == 2); +} + +@safe unittest +{ + int a = 1; + short b = 6; + double c = 2; + static assert(is(typeof(clamp(c,a,b)) == double)); + assert(clamp(c, a, b) == c); + assert(clamp(a-c, a, b) == a); + assert(clamp(b+c, a, b) == b); + // mixed sign + a = -5; + uint f = 5; + static assert(is(typeof(clamp(f, a, b)) == int)); + assert(clamp(f, a, b) == f); + // similar type deduction for (u)long + static assert(is(typeof(clamp(-1L, -2L, 2UL)) == long)); + + // user-defined types + import std.datetime : Date; + assert(clamp(Date(1982, 1, 4), Date(1012, 12, 21), Date(2012, 12, 21)) == Date(1982, 1, 4)); + assert(clamp(Date(1982, 1, 4), Date.min, Date.max) == Date(1982, 1, 4)); + // UFCS style + assert(Date(1982, 1, 4).clamp(Date.min, Date.max) == Date(1982, 1, 4)); + +} + +// cmp +/********************************** +Performs three-way lexicographical comparison on two +$(REF_ALTTEXT input ranges, isInputRange, std,range,primitives) +according to predicate $(D pred). Iterating $(D r1) and $(D r2) in +lockstep, $(D cmp) compares each element $(D e1) of $(D r1) with the +corresponding element $(D e2) in $(D r2). If one of the ranges has been +finished, $(D cmp) returns a negative value if $(D r1) has fewer +elements than $(D r2), a positive value if $(D r1) has more elements +than $(D r2), and $(D 0) if the ranges have the same number of +elements. + +If the ranges are strings, $(D cmp) performs UTF decoding +appropriately and compares the ranges one code point at a time. + +Params: + pred = The predicate used for comparison. + r1 = The first range. + r2 = The second range. + +Returns: + 0 if both ranges compare equal. -1 if the first differing element of $(D + r1) is less than the corresponding element of $(D r2) according to $(D + pred). 1 if the first differing element of $(D r2) is less than the + corresponding element of $(D r1) according to $(D pred). + +*/ +int cmp(alias pred = "a < b", R1, R2)(R1 r1, R2 r2) +if (isInputRange!R1 && isInputRange!R2 && !(isSomeString!R1 && isSomeString!R2)) +{ + for (;; r1.popFront(), r2.popFront()) + { + if (r1.empty) return -cast(int)!r2.empty; + if (r2.empty) return !r1.empty; + auto a = r1.front, b = r2.front; + if (binaryFun!pred(a, b)) return -1; + if (binaryFun!pred(b, a)) return 1; + } +} + +/// ditto +int cmp(alias pred = "a < b", R1, R2)(R1 r1, R2 r2) +if (isSomeString!R1 && isSomeString!R2) +{ + import core.stdc.string : memcmp; + import std.utf : decode; + + static if (is(typeof(pred) : string)) + enum isLessThan = pred == "a < b"; + else + enum isLessThan = false; + + // For speed only + static int threeWay(size_t a, size_t b) + { + static if (size_t.sizeof == int.sizeof && isLessThan) + return a - b; + else + return binaryFun!pred(b, a) ? 1 : binaryFun!pred(a, b) ? -1 : 0; + } + // For speed only + // @@@BUG@@@ overloading should be allowed for nested functions + static int threeWayInt(int a, int b) + { + static if (isLessThan) + return a - b; + else + return binaryFun!pred(b, a) ? 1 : binaryFun!pred(a, b) ? -1 : 0; + } + + static if (typeof(r1[0]).sizeof == typeof(r2[0]).sizeof && isLessThan) + { + static if (typeof(r1[0]).sizeof == 1) + { + immutable len = min(r1.length, r2.length); + immutable result = __ctfe ? + { + foreach (i; 0 .. len) + { + if (r1[i] != r2[i]) + return threeWayInt(r1[i], r2[i]); + } + return 0; + }() + : () @trusted { return memcmp(r1.ptr, r2.ptr, len); }(); + if (result) return result; + } + else + { + auto p1 = r1.ptr, p2 = r2.ptr, + pEnd = p1 + min(r1.length, r2.length); + for (; p1 != pEnd; ++p1, ++p2) + { + if (*p1 != *p2) return threeWayInt(cast(int) *p1, cast(int) *p2); + } + } + return threeWay(r1.length, r2.length); + } + else + { + for (size_t i1, i2;;) + { + if (i1 == r1.length) return threeWay(i2, r2.length); + if (i2 == r2.length) return threeWay(r1.length, i1); + immutable c1 = decode(r1, i1), + c2 = decode(r2, i2); + if (c1 != c2) return threeWayInt(cast(int) c1, cast(int) c2); + } + } +} + +/// +@safe unittest +{ + int result; + + result = cmp("abc", "abc"); + assert(result == 0); + result = cmp("", ""); + assert(result == 0); + result = cmp("abc", "abcd"); + assert(result < 0); + result = cmp("abcd", "abc"); + assert(result > 0); + result = cmp("abc"d, "abd"); + assert(result < 0); + result = cmp("bbc", "abc"w); + assert(result > 0); + result = cmp("aaa", "aaaa"d); + assert(result < 0); + result = cmp("aaaa", "aaa"d); + assert(result > 0); + result = cmp("aaa", "aaa"d); + assert(result == 0); + result = cmp(cast(int[])[], cast(int[])[]); + assert(result == 0); + result = cmp([1, 2, 3], [1, 2, 3]); + assert(result == 0); + result = cmp([1, 3, 2], [1, 2, 3]); + assert(result > 0); + result = cmp([1, 2, 3], [1L, 2, 3, 4]); + assert(result < 0); + result = cmp([1L, 2, 3], [1, 2]); + assert(result > 0); +} + +// equal +/** +Compares two ranges for equality, as defined by predicate $(D pred) +(which is $(D ==) by default). +*/ +template equal(alias pred = "a == b") +{ + enum isEmptyRange(R) = + isInputRange!R && __traits(compiles, {static assert(R.empty);}); + + enum hasFixedLength(T) = hasLength!T || isNarrowString!T; + + /++ + Compares two ranges for equality. The ranges may have + different element types, as long as $(D pred(r1.front, r2.front)) + evaluates to $(D bool). + Performs $(BIGOH min(r1.length, r2.length)) evaluations of $(D pred). + + Params: + r1 = The first range to be compared. + r2 = The second range to be compared. + + Returns: + $(D true) if and only if the two ranges compare _equal element + for element, according to binary predicate $(D pred). + + See_Also: + $(HTTP sgi.com/tech/stl/_equal.html, STL's _equal) + +/ + bool equal(Range1, Range2)(Range1 r1, Range2 r2) + if (isInputRange!Range1 && isInputRange!Range2 && + is(typeof(binaryFun!pred(r1.front, r2.front)))) + { + static assert(!(isInfinite!Range1 && isInfinite!Range2), + "Both ranges are known to be infinite"); + + //No pred calls necessary + static if (isEmptyRange!Range1 || isEmptyRange!Range2) + { + return r1.empty && r2.empty; + } + else static if ((isInfinite!Range1 && hasFixedLength!Range2) || + (hasFixedLength!Range1 && isInfinite!Range2)) + { + return false; + } + //Detect default pred and compatible dynamic array + else static if (is(typeof(pred) == string) && pred == "a == b" && + isArray!Range1 && isArray!Range2 && is(typeof(r1 == r2))) + { + return r1 == r2; + } + // if one of the arguments is a string and the other isn't, then auto-decoding + // can be avoided if they have the same ElementEncodingType + else static if (is(typeof(pred) == string) && pred == "a == b" && + isAutodecodableString!Range1 != isAutodecodableString!Range2 && + is(ElementEncodingType!Range1 == ElementEncodingType!Range2)) + { + import std.utf : byCodeUnit; + + static if (isAutodecodableString!Range1) + { + return equal(r1.byCodeUnit, r2); + } + else + { + return equal(r2.byCodeUnit, r1); + } + } + //Try a fast implementation when the ranges have comparable lengths + else static if (hasLength!Range1 && hasLength!Range2 && is(typeof(r1.length == r2.length))) + { + immutable len1 = r1.length; + immutable len2 = r2.length; + if (len1 != len2) return false; //Short circuit return + + //Lengths are the same, so we need to do an actual comparison + //Good news is we can squeeze out a bit of performance by not checking if r2 is empty + for (; !r1.empty; r1.popFront(), r2.popFront()) + { + if (!binaryFun!(pred)(r1.front, r2.front)) return false; + } + return true; + } + else + { + //Generic case, we have to walk both ranges making sure neither is empty + for (; !r1.empty; r1.popFront(), r2.popFront()) + { + if (r2.empty) return false; + if (!binaryFun!(pred)(r1.front, r2.front)) return false; + } + static if (!isInfinite!Range1) + return r2.empty; + } + } +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.math : approxEqual; + + int[] a = [ 1, 2, 4, 3 ]; + assert(!equal(a, a[1..$])); + assert(equal(a, a)); + assert(equal!((a, b) => a == b)(a, a)); + + // different types + double[] b = [ 1.0, 2, 4, 3]; + assert(!equal(a, b[1..$])); + assert(equal(a, b)); + + // predicated: ensure that two vectors are approximately equal + double[] c = [ 1.005, 2, 4, 3]; + assert(equal!approxEqual(b, c)); +} + +/++ +Tip: $(D equal) can itself be used as a predicate to other functions. +This can be very useful when the element type of a range is itself a +range. In particular, $(D equal) can be its own predicate, allowing +range of range (of range...) comparisons. + +/ +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : iota, chunks; + assert(equal!(equal!equal)( + [[[0, 1], [2, 3]], [[4, 5], [6, 7]]], + iota(0, 8).chunks(2).chunks(2) + )); +} + +@safe unittest +{ + import std.algorithm.iteration : map; + import std.internal.test.dummyrange : ReferenceForwardRange, + ReferenceInputRange, ReferenceInfiniteForwardRange; + import std.math : approxEqual; + + // various strings + assert(equal("æøå", "æøå")); //UTF8 vs UTF8 + assert(!equal("???", "æøå")); //UTF8 vs UTF8 + assert(equal("æøå"w, "æøå"d)); //UTF16 vs UTF32 + assert(!equal("???"w, "æøå"d));//UTF16 vs UTF32 + assert(equal("æøå"d, "æøå"d)); //UTF32 vs UTF32 + assert(!equal("???"d, "æøå"d));//UTF32 vs UTF32 + assert(!equal("hello", "world")); + + // same strings, but "explicit non default" comparison (to test the non optimized array comparison) + assert( equal!("a == b")("æøå", "æøå")); //UTF8 vs UTF8 + assert(!equal!("a == b")("???", "æøå")); //UTF8 vs UTF8 + assert( equal!("a == b")("æøå"w, "æøå"d)); //UTF16 vs UTF32 + assert(!equal!("a == b")("???"w, "æøå"d));//UTF16 vs UTF32 + assert( equal!("a == b")("æøå"d, "æøå"d)); //UTF32 vs UTF32 + assert(!equal!("a == b")("???"d, "æøå"d));//UTF32 vs UTF32 + assert(!equal!("a == b")("hello", "world")); + + //Array of string + assert(equal(["hello", "world"], ["hello", "world"])); + assert(!equal(["hello", "world"], ["hello"])); + assert(!equal(["hello", "world"], ["hello", "Bob!"])); + + //Should not compile, because "string == dstring" is illegal + static assert(!is(typeof(equal(["hello", "world"], ["hello"d, "world"d])))); + //However, arrays of non-matching string can be compared using equal!equal. Neat-o! + equal!equal(["hello", "world"], ["hello"d, "world"d]); + + //Tests, with more fancy map ranges + int[] a = [ 1, 2, 4, 3 ]; + assert(equal([2, 4, 8, 6], map!"a*2"(a))); + double[] b = [ 1.0, 2, 4, 3]; + double[] c = [ 1.005, 2, 4, 3]; + assert(equal!approxEqual(map!"a*2"(b), map!"a*2"(c))); + assert(!equal([2, 4, 1, 3], map!"a*2"(a))); + assert(!equal([2, 4, 1], map!"a*2"(a))); + assert(!equal!approxEqual(map!"a*3"(b), map!"a*2"(c))); + + //Tests with some fancy reference ranges. + ReferenceInputRange!int cir = new ReferenceInputRange!int([1, 2, 4, 3]); + ReferenceForwardRange!int cfr = new ReferenceForwardRange!int([1, 2, 4, 3]); + assert(equal(cir, a)); + cir = new ReferenceInputRange!int([1, 2, 4, 3]); + assert(equal(cir, cfr.save)); + assert(equal(cfr.save, cfr.save)); + cir = new ReferenceInputRange!int([1, 2, 8, 1]); + assert(!equal(cir, cfr)); + + //Test with an infinite range + auto ifr = new ReferenceInfiniteForwardRange!int; + assert(!equal(a, ifr)); + assert(!equal(ifr, a)); + //Test InputRange without length + assert(!equal(ifr, cir)); + assert(!equal(cir, ifr)); +} + +@safe pure unittest +{ + import std.utf : byChar, byWchar, byDchar; + + assert(equal("æøå".byChar, "æøå")); + assert(equal("æøå", "æøå".byChar)); + assert(equal("æøå".byWchar, "æøå"w)); + assert(equal("æøå"w, "æøå".byWchar)); + assert(equal("æøå".byDchar, "æøå"d)); + assert(equal("æøå"d, "æøå".byDchar)); +} + +@safe pure unittest +{ + struct R(bool _empty) { + enum empty = _empty; + @property char front(){assert(0);} + void popFront(){assert(0);} + } + alias I = R!false; + static assert(!__traits(compiles, I().equal(I()))); + // strings have fixed length so don't need to compare elements + assert(!I().equal("foo")); + assert(!"bar".equal(I())); + + alias E = R!true; + assert(E().equal(E())); + assert(E().equal("")); + assert("".equal(E())); + assert(!E().equal("foo")); + assert(!"bar".equal(E())); +} + +// MaxType +private template MaxType(T...) +if (T.length >= 1) +{ + static if (T.length == 1) + { + alias MaxType = T[0]; + } + else static if (T.length == 2) + { + static if (!is(typeof(T[0].min))) + alias MaxType = CommonType!T; + else static if (T[1].max > T[0].max) + alias MaxType = T[1]; + else + alias MaxType = T[0]; + } + else + { + alias MaxType = MaxType!(MaxType!(T[0 .. ($+1)/2]), MaxType!(T[($+1)/2 .. $])); + } +} + +// levenshteinDistance +/** +Encodes $(HTTP realityinteractive.com/rgrzywinski/archives/000249.html, +edit operations) necessary to transform one sequence into +another. Given sequences $(D s) (source) and $(D t) (target), a +sequence of $(D EditOp) encodes the steps that need to be taken to +convert $(D s) into $(D t). For example, if $(D s = "cat") and $(D +"cars"), the minimal sequence that transforms $(D s) into $(D t) is: +skip two characters, replace 't' with 'r', and insert an 's'. Working +with edit operations is useful in applications such as spell-checkers +(to find the closest word to a given misspelled word), approximate +searches, diff-style programs that compute the difference between +files, efficient encoding of patches, DNA sequence analysis, and +plagiarism detection. +*/ + +enum EditOp : char +{ + /** Current items are equal; no editing is necessary. */ + none = 'n', + /** Substitute current item in target with current item in source. */ + substitute = 's', + /** Insert current item from the source into the target. */ + insert = 'i', + /** Remove current item from the target. */ + remove = 'r' +} + +/// +@safe unittest +{ + with(EditOp) + { + assert(levenshteinDistanceAndPath("foo", "foobar")[1] == [none, none, none, insert, insert, insert]); + assert(levenshteinDistanceAndPath("banana", "fazan")[1] == [substitute, none, substitute, none, none, remove]); + } +} + +private struct Levenshtein(Range, alias equals, CostType = size_t) +{ + EditOp[] path() + { + import std.algorithm.mutation : reverse; + + EditOp[] result; + size_t i = rows - 1, j = cols - 1; + // restore the path + while (i || j) + { + auto cIns = j == 0 ? CostType.max : matrix(i,j - 1); + auto cDel = i == 0 ? CostType.max : matrix(i - 1,j); + auto cSub = i == 0 || j == 0 + ? CostType.max + : matrix(i - 1,j - 1); + switch (min_index(cSub, cIns, cDel)) + { + case 0: + result ~= matrix(i - 1,j - 1) == matrix(i,j) + ? EditOp.none + : EditOp.substitute; + --i; + --j; + break; + case 1: + result ~= EditOp.insert; + --j; + break; + default: + result ~= EditOp.remove; + --i; + break; + } + } + reverse(result); + return result; + } + + ~this() { + FreeMatrix(); + } + +private: + CostType _deletionIncrement = 1, + _insertionIncrement = 1, + _substitutionIncrement = 1; + CostType[] _matrix; + size_t rows, cols; + + // Treat _matrix as a rectangular array + ref CostType matrix(size_t row, size_t col) { return _matrix[row * cols + col]; } + + void AllocMatrix(size_t r, size_t c) @trusted { + import core.checkedint : mulu; + bool overflow; + const rc = mulu(r, c, overflow); + if (overflow) assert(0); + rows = r; + cols = c; + if (_matrix.length < rc) + { + import core.exception : onOutOfMemoryError; + import core.stdc.stdlib : realloc; + const nbytes = mulu(rc, _matrix[0].sizeof, overflow); + if (overflow) assert(0); + auto m = cast(CostType *) realloc(_matrix.ptr, nbytes); + if (!m) + onOutOfMemoryError(); + _matrix = m[0 .. r * c]; + InitMatrix(); + } + } + + void FreeMatrix() @trusted { + import core.stdc.stdlib : free; + + free(_matrix.ptr); + _matrix = null; + } + + void InitMatrix() { + foreach (r; 0 .. rows) + matrix(r,0) = r * _deletionIncrement; + foreach (c; 0 .. cols) + matrix(0,c) = c * _insertionIncrement; + } + + static uint min_index(CostType i0, CostType i1, CostType i2) + { + if (i0 <= i1) + { + return i0 <= i2 ? 0 : 2; + } + else + { + return i1 <= i2 ? 1 : 2; + } + } + + CostType distanceWithPath(Range s, Range t) + { + auto slen = walkLength(s.save), tlen = walkLength(t.save); + AllocMatrix(slen + 1, tlen + 1); + foreach (i; 1 .. rows) + { + auto sfront = s.front; + auto tt = t.save; + foreach (j; 1 .. cols) + { + auto cSub = matrix(i - 1,j - 1) + + (equals(sfront, tt.front) ? 0 : _substitutionIncrement); + tt.popFront(); + auto cIns = matrix(i,j - 1) + _insertionIncrement; + auto cDel = matrix(i - 1,j) + _deletionIncrement; + switch (min_index(cSub, cIns, cDel)) + { + case 0: + matrix(i,j) = cSub; + break; + case 1: + matrix(i,j) = cIns; + break; + default: + matrix(i,j) = cDel; + break; + } + } + s.popFront(); + } + return matrix(slen,tlen); + } + + CostType distanceLowMem(Range s, Range t, CostType slen, CostType tlen) + { + CostType lastdiag, olddiag; + AllocMatrix(slen + 1, 1); + foreach (y; 1 .. slen + 1) + { + _matrix[y] = y; + } + foreach (x; 1 .. tlen + 1) + { + auto tfront = t.front; + auto ss = s.save; + _matrix[0] = x; + lastdiag = x - 1; + foreach (y; 1 .. rows) + { + olddiag = _matrix[y]; + auto cSub = lastdiag + (equals(ss.front, tfront) ? 0 : _substitutionIncrement); + ss.popFront(); + auto cIns = _matrix[y - 1] + _insertionIncrement; + auto cDel = _matrix[y] + _deletionIncrement; + switch (min_index(cSub, cIns, cDel)) + { + case 0: + _matrix[y] = cSub; + break; + case 1: + _matrix[y] = cIns; + break; + default: + _matrix[y] = cDel; + break; + } + lastdiag = olddiag; + } + t.popFront(); + } + return _matrix[slen]; + } +} + +/** +Returns the $(HTTP wikipedia.org/wiki/Levenshtein_distance, Levenshtein +distance) between $(D s) and $(D t). The Levenshtein distance computes +the minimal amount of edit operations necessary to transform $(D s) +into $(D t). Performs $(BIGOH s.length * t.length) evaluations of $(D +equals) and occupies $(BIGOH s.length * t.length) storage. + +Params: + equals = The binary predicate to compare the elements of the two ranges. + s = The original range. + t = The transformation target + +Returns: + The minimal number of edits to transform s into t. + +Does not allocate GC memory. +*/ +size_t levenshteinDistance(alias equals = (a,b) => a == b, Range1, Range2) + (Range1 s, Range2 t) +if (isForwardRange!(Range1) && isForwardRange!(Range2)) +{ + alias eq = binaryFun!(equals); + + for (;;) + { + if (s.empty) return t.walkLength; + if (t.empty) return s.walkLength; + if (eq(s.front, t.front)) + { + s.popFront(); + t.popFront(); + continue; + } + static if (isBidirectionalRange!(Range1) && isBidirectionalRange!(Range2)) + { + if (eq(s.back, t.back)) + { + s.popBack(); + t.popBack(); + continue; + } + } + break; + } + + auto slen = walkLength(s.save); + auto tlen = walkLength(t.save); + + if (slen == 1 && tlen == 1) + { + return eq(s.front, t.front) ? 0 : 1; + } + + if (slen > tlen) + { + Levenshtein!(Range1, eq, size_t) lev; + return lev.distanceLowMem(s, t, slen, tlen); + } + else + { + Levenshtein!(Range2, eq, size_t) lev; + return lev.distanceLowMem(t, s, tlen, slen); + } +} + +/// +@safe unittest +{ + import std.algorithm.iteration : filter; + import std.uni : toUpper; + + assert(levenshteinDistance("cat", "rat") == 1); + assert(levenshteinDistance("parks", "spark") == 2); + assert(levenshteinDistance("abcde", "abcde") == 0); + assert(levenshteinDistance("abcde", "abCde") == 1); + assert(levenshteinDistance("kitten", "sitting") == 3); + assert(levenshteinDistance!((a, b) => toUpper(a) == toUpper(b)) + ("parks", "SPARK") == 2); + assert(levenshteinDistance("parks".filter!"true", "spark".filter!"true") == 2); + assert(levenshteinDistance("ID", "I♥D") == 1); +} + +@safe @nogc nothrow unittest +{ + assert(levenshteinDistance("cat"d, "rat"d) == 1); +} + +/// ditto +size_t levenshteinDistance(alias equals = (a,b) => a == b, Range1, Range2) + (auto ref Range1 s, auto ref Range2 t) +if (isConvertibleToString!Range1 || isConvertibleToString!Range2) +{ + import std.meta : staticMap; + alias Types = staticMap!(convertToString, Range1, Range2); + return levenshteinDistance!(equals, Types)(s, t); +} + +@safe unittest +{ + static struct S { string s; alias s this; } + assert(levenshteinDistance(S("cat"), S("rat")) == 1); + assert(levenshteinDistance("cat", S("rat")) == 1); + assert(levenshteinDistance(S("cat"), "rat") == 1); +} + +@safe @nogc nothrow unittest +{ + static struct S { dstring s; alias s this; } + assert(levenshteinDistance(S("cat"d), S("rat"d)) == 1); + assert(levenshteinDistance("cat"d, S("rat"d)) == 1); + assert(levenshteinDistance(S("cat"d), "rat"d) == 1); +} + +/** +Returns the Levenshtein distance and the edit path between $(D s) and +$(D t). + +Params: + equals = The binary predicate to compare the elements of the two ranges. + s = The original range. + t = The transformation target + +Returns: + Tuple with the first element being the minimal amount of edits to transform s into t and + the second element being the sequence of edits to effect this transformation. + +Allocates GC memory for the returned EditOp[] array. +*/ +Tuple!(size_t, EditOp[]) +levenshteinDistanceAndPath(alias equals = (a,b) => a == b, Range1, Range2) + (Range1 s, Range2 t) +if (isForwardRange!(Range1) && isForwardRange!(Range2)) +{ + Levenshtein!(Range1, binaryFun!(equals)) lev; + auto d = lev.distanceWithPath(s, t); + return tuple(d, lev.path()); +} + +/// +@safe unittest +{ + string a = "Saturday", b = "Sundays"; + auto p = levenshteinDistanceAndPath(a, b); + assert(p[0] == 4); + assert(equal(p[1], "nrrnsnnni")); +} + +@safe unittest +{ + assert(levenshteinDistance("a", "a") == 0); + assert(levenshteinDistance("a", "b") == 1); + assert(levenshteinDistance("aa", "ab") == 1); + assert(levenshteinDistance("aa", "abc") == 2); + assert(levenshteinDistance("Saturday", "Sunday") == 3); + assert(levenshteinDistance("kitten", "sitting") == 3); +} + +/// ditto +Tuple!(size_t, EditOp[]) +levenshteinDistanceAndPath(alias equals = (a,b) => a == b, Range1, Range2) + (auto ref Range1 s, auto ref Range2 t) +if (isConvertibleToString!Range1 || isConvertibleToString!Range2) +{ + import std.meta : staticMap; + alias Types = staticMap!(convertToString, Range1, Range2); + return levenshteinDistanceAndPath!(equals, Types)(s, t); +} + +@safe unittest +{ + static struct S { string s; alias s this; } + assert(levenshteinDistanceAndPath(S("cat"), S("rat"))[0] == 1); + assert(levenshteinDistanceAndPath("cat", S("rat"))[0] == 1); + assert(levenshteinDistanceAndPath(S("cat"), "rat")[0] == 1); +} + +// max +/** +Iterates the passed arguments and return the maximum value. + +Params: + args = The values to select the maximum from. At least two arguments must + be passed. + +Returns: + The maximum of the passed-in args. The type of the returned value is + the type among the passed arguments that is able to store the largest value. + +See_Also: + $(REF maxElement, std,algorithm,searching) +*/ +MaxType!T max(T...)(T args) +if (T.length >= 2) +{ + //Get "a" + static if (T.length <= 2) + alias a = args[0]; + else + auto a = max(args[0 .. ($+1)/2]); + alias T0 = typeof(a); + + //Get "b" + static if (T.length <= 3) + alias b = args[$-1]; + else + auto b = max(args[($+1)/2 .. $]); + alias T1 = typeof(b); + + import std.algorithm.internal : algoFormat; + static assert(is(typeof(a < b)), + algoFormat("Invalid arguments: Cannot compare types %s and %s.", T0.stringof, T1.stringof)); + + //Do the "max" proper with a and b + import std.functional : lessThan; + immutable chooseB = lessThan!(T0, T1)(a, b); + return cast(typeof(return)) (chooseB ? b : a); +} + +/// +@safe unittest +{ + int a = 5; + short b = 6; + double c = 2; + auto d = max(a, b); + assert(is(typeof(d) == int)); + assert(d == 6); + auto e = min(a, b, c); + assert(is(typeof(e) == double)); + assert(e == 2); +} + +@safe unittest +{ + int a = 5; + short b = 6; + double c = 2; + auto d = max(a, b); + static assert(is(typeof(d) == int)); + assert(d == 6); + auto e = max(a, b, c); + static assert(is(typeof(e) == double)); + assert(e == 6); + // mixed sign + a = -5; + uint f = 5; + static assert(is(typeof(max(a, f)) == uint)); + assert(max(a, f) == 5); + + //Test user-defined types + import std.datetime : Date; + assert(max(Date(2012, 12, 21), Date(1982, 1, 4)) == Date(2012, 12, 21)); + assert(max(Date(1982, 1, 4), Date(2012, 12, 21)) == Date(2012, 12, 21)); + assert(max(Date(1982, 1, 4), Date.min) == Date(1982, 1, 4)); + assert(max(Date.min, Date(1982, 1, 4)) == Date(1982, 1, 4)); + assert(max(Date(1982, 1, 4), Date.max) == Date.max); + assert(max(Date.max, Date(1982, 1, 4)) == Date.max); + assert(max(Date.min, Date.max) == Date.max); + assert(max(Date.max, Date.min) == Date.max); +} + +// MinType +private template MinType(T...) +if (T.length >= 1) +{ + static if (T.length == 1) + { + alias MinType = T[0]; + } + else static if (T.length == 2) + { + static if (!is(typeof(T[0].min))) + alias MinType = CommonType!T; + else + { + enum hasMostNegative = is(typeof(mostNegative!(T[0]))) && + is(typeof(mostNegative!(T[1]))); + static if (hasMostNegative && mostNegative!(T[1]) < mostNegative!(T[0])) + alias MinType = T[1]; + else static if (hasMostNegative && mostNegative!(T[1]) > mostNegative!(T[0])) + alias MinType = T[0]; + else static if (T[1].max < T[0].max) + alias MinType = T[1]; + else + alias MinType = T[0]; + } + } + else + { + alias MinType = MinType!(MinType!(T[0 .. ($+1)/2]), MinType!(T[($+1)/2 .. $])); + } +} + +// min +/** +Iterates the passed arguments and returns the minimum value. + +Params: args = The values to select the minimum from. At least two arguments + must be passed, and they must be comparable with `<`. +Returns: The minimum of the passed-in values. +See_Also: + $(REF minElement, std,algorithm,searching) +*/ +MinType!T min(T...)(T args) +if (T.length >= 2) +{ + //Get "a" + static if (T.length <= 2) + alias a = args[0]; + else + auto a = min(args[0 .. ($+1)/2]); + alias T0 = typeof(a); + + //Get "b" + static if (T.length <= 3) + alias b = args[$-1]; + else + auto b = min(args[($+1)/2 .. $]); + alias T1 = typeof(b); + + import std.algorithm.internal : algoFormat; + static assert(is(typeof(a < b)), + algoFormat("Invalid arguments: Cannot compare types %s and %s.", T0.stringof, T1.stringof)); + + //Do the "min" proper with a and b + import std.functional : lessThan; + immutable chooseA = lessThan!(T0, T1)(a, b); + return cast(typeof(return)) (chooseA ? a : b); +} + +/// +@safe unittest +{ + int a = 5; + short b = 6; + double c = 2; + auto d = min(a, b); + static assert(is(typeof(d) == int)); + assert(d == 5); + auto e = min(a, b, c); + static assert(is(typeof(e) == double)); + assert(e == 2); + + // With arguments of mixed signedness, the return type is the one that can + // store the lowest values. + a = -10; + uint f = 10; + static assert(is(typeof(min(a, f)) == int)); + assert(min(a, f) == -10); + + // User-defined types that support comparison with < are supported. + import std.datetime; + assert(min(Date(2012, 12, 21), Date(1982, 1, 4)) == Date(1982, 1, 4)); + assert(min(Date(1982, 1, 4), Date(2012, 12, 21)) == Date(1982, 1, 4)); + assert(min(Date(1982, 1, 4), Date.min) == Date.min); + assert(min(Date.min, Date(1982, 1, 4)) == Date.min); + assert(min(Date(1982, 1, 4), Date.max) == Date(1982, 1, 4)); + assert(min(Date.max, Date(1982, 1, 4)) == Date(1982, 1, 4)); + assert(min(Date.min, Date.max) == Date.min); + assert(min(Date.max, Date.min) == Date.min); +} + +// mismatch +/** +Sequentially compares elements in $(D r1) and $(D r2) in lockstep, and +stops at the first mismatch (according to $(D pred), by default +equality). Returns a tuple with the reduced ranges that start with the +two mismatched values. Performs $(BIGOH min(r1.length, r2.length)) +evaluations of $(D pred). + +See_Also: + $(HTTP sgi.com/tech/stl/_mismatch.html, STL's _mismatch) +*/ +Tuple!(Range1, Range2) +mismatch(alias pred = "a == b", Range1, Range2)(Range1 r1, Range2 r2) +if (isInputRange!(Range1) && isInputRange!(Range2)) +{ + for (; !r1.empty && !r2.empty; r1.popFront(), r2.popFront()) + { + if (!binaryFun!(pred)(r1.front, r2.front)) break; + } + return tuple(r1, r2); +} + +/// +@safe unittest +{ + int[] x = [ 1, 5, 2, 7, 4, 3 ]; + double[] y = [ 1.0, 5, 2, 7.3, 4, 8 ]; + auto m = mismatch(x, y); + assert(m[0] == x[3 .. $]); + assert(m[1] == y[3 .. $]); +} + +@safe unittest +{ + int[] a = [ 1, 2, 3 ]; + int[] b = [ 1, 2, 4, 5 ]; + auto mm = mismatch(a, b); + assert(mm[0] == [3]); + assert(mm[1] == [4, 5]); +} + +/** +Returns one of a collection of expressions based on the value of the switch +expression. + +$(D choices) needs to be composed of pairs of test expressions and return +expressions. Each test-expression is compared with $(D switchExpression) using +$(D pred)($(D switchExpression) is the first argument) and if that yields true +- the return expression is returned. + +Both the test and the return expressions are lazily evaluated. + +Params: + +switchExpression = The first argument for the predicate. + +choices = Pairs of test expressions and return expressions. The test +expressions will be the second argument for the predicate, and the return +expression will be returned if the predicate yields true with $(D +switchExpression) and the test expression as arguments. May also have a +default return expression, that needs to be the last expression without a test +expression before it. A return expression may be of void type only if it +always throws. + +Returns: The return expression associated with the first test expression that +made the predicate yield true, or the default return expression if no test +expression matched. + +Throws: If there is no default return expression and the predicate does not +yield true with any test expression - $(D SwitchError) is thrown. $(D +SwitchError) is also thrown if a void return expression was executed without +throwing anything. +*/ +auto predSwitch(alias pred = "a == b", T, R ...)(T switchExpression, lazy R choices) +{ + import core.exception : SwitchError; + alias predicate = binaryFun!(pred); + + foreach (index, ChoiceType; R) + { + //The even places in `choices` are for the predicate. + static if (index % 2 == 1) + { + if (predicate(switchExpression, choices[index - 1]())) + { + static if (is(typeof(choices[index]()) == void)) + { + choices[index](); + throw new SwitchError("Choices that return void should throw"); + } + else + { + return choices[index](); + } + } + } + } + + //In case nothing matched: + static if (R.length % 2 == 1) //If there is a default return expression: + { + static if (is(typeof(choices[$ - 1]()) == void)) + { + choices[$ - 1](); + throw new SwitchError("Choices that return void should throw"); + } + else + { + return choices[$ - 1](); + } + } + else //If there is no default return expression: + { + throw new SwitchError("Input not matched by any pattern"); + } +} + +/// +@safe unittest +{ + string res = 2.predSwitch!"a < b"( + 1, "less than 1", + 5, "less than 5", + 10, "less than 10", + "greater or equal to 10"); + + assert(res == "less than 5"); + + //The arguments are lazy, which allows us to use predSwitch to create + //recursive functions: + int factorial(int n) + { + return n.predSwitch!"a <= b"( + -1, {throw new Exception("Can not calculate n! for n < 0");}(), + 0, 1, // 0! = 1 + n * factorial(n - 1) // n! = n * (n - 1)! for n >= 0 + ); + } + assert(factorial(3) == 6); + + //Void return expressions are allowed if they always throw: + import std.exception : assertThrown; + assertThrown!Exception(factorial(-9)); +} + +@system unittest +{ + import core.exception : SwitchError; + import std.exception : assertThrown; + + //Nothing matches - with default return expression: + assert(20.predSwitch!"a < b"( + 1, "less than 1", + 5, "less than 5", + 10, "less than 10", + "greater or equal to 10") == "greater or equal to 10"); + + //Nothing matches - without default return expression: + assertThrown!SwitchError(20.predSwitch!"a < b"( + 1, "less than 1", + 5, "less than 5", + 10, "less than 10", + )); + + //Using the default predicate: + assert(2.predSwitch( + 1, "one", + 2, "two", + 3, "three", + ) == "two"); + + //Void return expressions must always throw: + assertThrown!SwitchError(1.predSwitch( + 0, "zero", + 1, {}(), //A void return expression that doesn't throw + 2, "two", + )); +} + +/** +Checks if the two ranges have the same number of elements. This function is +optimized to always take advantage of the $(D length) member of either range +if it exists. + +If both ranges have a length member, this function is $(BIGOH 1). Otherwise, +this function is $(BIGOH min(r1.length, r2.length)). + +Params: + r1 = a finite $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + r2 = a finite $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + +Returns: + $(D true) if both ranges have the same length, $(D false) otherwise. +*/ +bool isSameLength(Range1, Range2)(Range1 r1, Range2 r2) +if (isInputRange!Range1 && + isInputRange!Range2 && + !isInfinite!Range1 && + !isInfinite!Range2) +{ + static if (hasLength!(Range1) && hasLength!(Range2)) + { + return r1.length == r2.length; + } + else static if (hasLength!(Range1) && !hasLength!(Range2)) + { + size_t length; + + while (!r2.empty) + { + r2.popFront; + + if (++length > r1.length) + { + return false; + } + } + + return !(length < r1.length); + } + else static if (!hasLength!(Range1) && hasLength!(Range2)) + { + size_t length; + + while (!r1.empty) + { + r1.popFront; + + if (++length > r2.length) + { + return false; + } + } + + return !(length < r2.length); + } + else + { + while (!r1.empty) + { + if (r2.empty) + { + return false; + } + + r1.popFront; + r2.popFront; + } + + return r2.empty; + } +} + +/// +@safe nothrow pure unittest +{ + assert(isSameLength([1, 2, 3], [4, 5, 6])); + assert(isSameLength([0.3, 90.4, 23.7, 119.2], [42.6, 23.6, 95.5, 6.3])); + assert(isSameLength("abc", "xyz")); + + int[] a; + int[] b; + assert(isSameLength(a, b)); + + assert(!isSameLength([1, 2, 3], [4, 5])); + assert(!isSameLength([0.3, 90.4, 23.7], [42.6, 23.6, 95.5, 6.3])); + assert(!isSameLength("abcd", "xyz")); +} + +// Test CTFE +@safe pure unittest +{ + enum result1 = isSameLength([1, 2, 3], [4, 5, 6]); + static assert(result1); + + enum result2 = isSameLength([0.3, 90.4, 23.7], [42.6, 23.6, 95.5, 6.3]); + static assert(!result2); +} + +@safe nothrow pure unittest +{ + import std.internal.test.dummyrange; + + auto r1 = new ReferenceInputRange!int([1, 2, 3]); + auto r2 = new ReferenceInputRange!int([4, 5, 6]); + assert(isSameLength(r1, r2)); + + auto r3 = new ReferenceInputRange!int([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Input) r4; + assert(isSameLength(r3, r4)); + + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Input) r5; + auto r6 = new ReferenceInputRange!int([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + assert(isSameLength(r5, r6)); + + auto r7 = new ReferenceInputRange!int([1, 2]); + auto r8 = new ReferenceInputRange!int([4, 5, 6]); + assert(!isSameLength(r7, r8)); + + auto r9 = new ReferenceInputRange!int([1, 2, 3, 4, 5, 6, 7, 8]); + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Input) r10; + assert(!isSameLength(r9, r10)); + + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Input) r11; + auto r12 = new ReferenceInputRange!int([1, 2, 3, 4, 5, 6, 7, 8]); + assert(!isSameLength(r11, r12)); +} + +/// For convenience +alias AllocateGC = Flag!"allocateGC"; + +/** +Checks if both ranges are permutations of each other. + +This function can allocate if the $(D Yes.allocateGC) flag is passed. This has +the benefit of have better complexity than the $(D Yes.allocateGC) option. However, +this option is only available for ranges whose equality can be determined via each +element's $(D toHash) method. If customized equality is needed, then the $(D pred) +template parameter can be passed, and the function will automatically switch to +the non-allocating algorithm. See $(REF binaryFun, std,functional) for more details on +how to define $(D pred). + +Non-allocating forward range option: $(BIGOH n^2) +Non-allocating forward range option with custom $(D pred): $(BIGOH n^2) +Allocating forward range option: amortized $(BIGOH r1.length) + $(BIGOH r2.length) + +Params: + pred = an optional parameter to change how equality is defined + allocate_gc = $(D Yes.allocateGC)/$(D No.allocateGC) + r1 = A finite $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) + r2 = A finite $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) + +Returns: + $(D true) if all of the elements in $(D r1) appear the same number of times in $(D r2). + Otherwise, returns $(D false). +*/ + +bool isPermutation(AllocateGC allocate_gc, Range1, Range2) +(Range1 r1, Range2 r2) +if (allocate_gc == Yes.allocateGC && + isForwardRange!Range1 && + isForwardRange!Range2 && + !isInfinite!Range1 && + !isInfinite!Range2) +{ + alias E1 = Unqual!(ElementType!Range1); + alias E2 = Unqual!(ElementType!Range2); + + if (!isSameLength(r1.save, r2.save)) + { + return false; + } + + // Skip the elements at the beginning where r1.front == r2.front, + // they are in the same order and don't need to be counted. + while (!r1.empty && !r2.empty && r1.front == r2.front) + { + r1.popFront(); + r2.popFront(); + } + + if (r1.empty && r2.empty) + { + return true; + } + + int[CommonType!(E1, E2)] counts; + + foreach (item; r1) + { + ++counts[item]; + } + + foreach (item; r2) + { + if (--counts[item] < 0) + { + return false; + } + } + + return true; +} + +/// ditto +bool isPermutation(alias pred = "a == b", Range1, Range2) +(Range1 r1, Range2 r2) +if (is(typeof(binaryFun!(pred))) && + isForwardRange!Range1 && + isForwardRange!Range2 && + !isInfinite!Range1 && + !isInfinite!Range2) +{ + import std.algorithm.searching : count; + + alias predEquals = binaryFun!(pred); + alias E1 = Unqual!(ElementType!Range1); + alias E2 = Unqual!(ElementType!Range2); + + if (!isSameLength(r1.save, r2.save)) + { + return false; + } + + // Skip the elements at the beginning where r1.front == r2.front, + // they are in the same order and don't need to be counted. + while (!r1.empty && !r2.empty && predEquals(r1.front, r2.front)) + { + r1.popFront(); + r2.popFront(); + } + + if (r1.empty && r2.empty) + { + return true; + } + + size_t r1_count; + size_t r2_count; + + // At each element item, when computing the count of item, scan it while + // also keeping track of the scanning index. If the first occurrence + // of item in the scanning loop has an index smaller than the current index, + // then you know that the element has been seen before + size_t index; + outloop: for (auto r1s1 = r1.save; !r1s1.empty; r1s1.popFront, index++) + { + auto item = r1s1.front; + r1_count = 0; + r2_count = 0; + + size_t i; + for (auto r1s2 = r1.save; !r1s2.empty; r1s2.popFront, i++) + { + auto e = r1s2.front; + if (predEquals(e, item) && i < index) + { + continue outloop; + } + else if (predEquals(e, item)) + { + ++r1_count; + } + } + + r2_count = r2.save.count!pred(item); + + if (r1_count != r2_count) + { + return false; + } + } + + return true; +} + +/// +@safe pure unittest +{ + import std.typecons : Yes; + + assert(isPermutation([1, 2, 3], [3, 2, 1])); + assert(isPermutation([1.1, 2.3, 3.5], [2.3, 3.5, 1.1])); + assert(isPermutation("abc", "bca")); + + assert(!isPermutation([1, 2], [3, 4])); + assert(!isPermutation([1, 1, 2, 3], [1, 2, 2, 3])); + assert(!isPermutation([1, 1], [1, 1, 1])); + + // Faster, but allocates GC handled memory + assert(isPermutation!(Yes.allocateGC)([1.1, 2.3, 3.5], [2.3, 3.5, 1.1])); + assert(!isPermutation!(Yes.allocateGC)([1, 2], [3, 4])); +} + +// Test @nogc inference +@safe @nogc pure unittest +{ + static immutable arr1 = [1, 2, 3]; + static immutable arr2 = [3, 2, 1]; + assert(isPermutation(arr1, arr2)); + + static immutable arr3 = [1, 1, 2, 3]; + static immutable arr4 = [1, 2, 2, 3]; + assert(!isPermutation(arr3, arr4)); +} + +@safe pure unittest +{ + import std.internal.test.dummyrange; + + auto r1 = new ReferenceForwardRange!int([1, 2, 3, 4]); + auto r2 = new ReferenceForwardRange!int([1, 2, 4, 3]); + assert(isPermutation(r1, r2)); + + auto r3 = new ReferenceForwardRange!int([1, 2, 3, 4]); + auto r4 = new ReferenceForwardRange!int([4, 2, 1, 3]); + assert(isPermutation!(Yes.allocateGC)(r3, r4)); + + auto r5 = new ReferenceForwardRange!int([1, 2, 3]); + auto r6 = new ReferenceForwardRange!int([4, 2, 1, 3]); + assert(!isPermutation(r5, r6)); + + auto r7 = new ReferenceForwardRange!int([4, 2, 1, 3]); + auto r8 = new ReferenceForwardRange!int([1, 2, 3]); + assert(!isPermutation!(Yes.allocateGC)(r7, r8)); + + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random) r9; + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random) r10; + assert(isPermutation(r9, r10)); + + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random) r11; + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random) r12; + assert(isPermutation!(Yes.allocateGC)(r11, r12)); + + alias mytuple = Tuple!(int, int); + + assert(isPermutation!"a[0] == b[0]"( + [mytuple(1, 4), mytuple(2, 5)], + [mytuple(2, 3), mytuple(1, 2)] + )); +} + +/** +Get the _first argument `a` that passes an `if (unaryFun!pred(a))` test. If +no argument passes the test, return the last argument. + +Similar to behaviour of the `or` operator in dynamic languages such as Lisp's +`(or ...)` and Python's `a or b or ...` except that the last argument is +returned upon no match. + +Simplifies logic, for instance, in parsing rules where a set of alternative +matchers are tried. The _first one that matches returns it match result, +typically as an abstract syntax tree (AST). + +Bugs: +Lazy parameters are currently, too restrictively, inferred by DMD to +always throw even though they don't need to be. This makes it impossible to +currently mark `either` as `nothrow`. See issue at $(BUGZILLA 12647). + +Returns: + The _first argument that passes the test `pred`. +*/ +CommonType!(T, Ts) either(alias pred = a => a, T, Ts...)(T first, lazy Ts alternatives) +if (alternatives.length >= 1 && + !is(CommonType!(T, Ts) == void) && + allSatisfy!(ifTestable, T, Ts)) +{ + alias predFun = unaryFun!pred; + + if (predFun(first)) return first; + + foreach (e; alternatives[0 .. $ - 1]) + if (predFun(e)) return e; + + return alternatives[$ - 1]; +} + +/// +@safe pure unittest +{ + const a = 1; + const b = 2; + auto ab = either(a, b); + static assert(is(typeof(ab) == const(int))); + assert(ab == a); + + auto c = 2; + const d = 3; + auto cd = either!(a => a == 3)(c, d); // use predicate + static assert(is(typeof(cd) == int)); + assert(cd == d); + + auto e = 0; + const f = 2; + auto ef = either(e, f); + static assert(is(typeof(ef) == int)); + assert(ef == f); + + immutable p = 1; + immutable q = 2; + auto pq = either(p, q); + static assert(is(typeof(pq) == immutable(int))); + assert(pq == p); + + assert(either(3, 4) == 3); + assert(either(0, 4) == 4); + assert(either(0, 0) == 0); + assert(either("", "a") == ""); + + string r = null; + assert(either(r, "a") == "a"); + assert(either("a", "") == "a"); + + immutable s = [1, 2]; + assert(either(s, s) == s); + + assert(either([0, 1], [1, 2]) == [0, 1]); + assert(either([0, 1], [1]) == [0, 1]); + assert(either("a", "b") == "a"); + + static assert(!__traits(compiles, either(1, "a"))); + static assert(!__traits(compiles, either(1.0, "a"))); + static assert(!__traits(compiles, either('a', "a"))); +} diff --git a/libphobos/src/std/algorithm/internal.d b/libphobos/src/std/algorithm/internal.d new file mode 100644 index 0000000..ca03fd7 --- /dev/null +++ b/libphobos/src/std/algorithm/internal.d @@ -0,0 +1,77 @@ +// Written in the D programming language. + +/// Helper functions for std.algorithm package. +module std.algorithm.internal; + + +// Same as std.string.format, but "self-importing". +// Helps reduce code and imports, particularly in static asserts. +// Also helps with missing imports errors. +package template algoFormat() +{ + import std.format : format; + alias algoFormat = format; +} + +// Internal random array generators +version (unittest) +{ + package enum size_t maxArraySize = 50; + package enum size_t minArraySize = maxArraySize - 1; + + package string[] rndstuff(T : string)() + { + import std.random : Random, unpredictableSeed, uniform; + + static Random rnd; + static bool first = true; + if (first) + { + rnd = Random(unpredictableSeed); + first = false; + } + string[] result = + new string[uniform(minArraySize, maxArraySize, rnd)]; + string alpha = "abcdefghijABCDEFGHIJ"; + foreach (ref s; result) + { + foreach (i; 0 .. uniform(0u, 20u, rnd)) + { + auto j = uniform(0, alpha.length - 1, rnd); + s ~= alpha[j]; + } + } + return result; + } + + package int[] rndstuff(T : int)() + { + import std.random : Random, unpredictableSeed, uniform; + + static Random rnd; + static bool first = true; + if (first) + { + rnd = Random(unpredictableSeed); + first = false; + } + int[] result = new int[uniform(minArraySize, maxArraySize, rnd)]; + foreach (ref i; result) + { + i = uniform(-100, 100, rnd); + } + return result; + } + + package double[] rndstuff(T : double)() + { + double[] result; + foreach (i; rndstuff!(int)()) + { + result ~= i / 50.0; + } + return result; + } +} + +package(std) T* addressOf(T)(ref T val) { return &val; } diff --git a/libphobos/src/std/algorithm/iteration.d b/libphobos/src/std/algorithm/iteration.d new file mode 100644 index 0000000..7e57824 --- /dev/null +++ b/libphobos/src/std/algorithm/iteration.d @@ -0,0 +1,5187 @@ +// Written in the D programming language. +/** +This is a submodule of $(MREF std, algorithm). +It contains generic _iteration algorithms. + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE Cheat Sheet, +$(TR $(TH Function Name) $(TH Description)) +$(T2 cache, + Eagerly evaluates and caches another range's $(D front).) +$(T2 cacheBidirectional, + As above, but also provides $(D back) and $(D popBack).) +$(T2 chunkBy, + $(D chunkBy!((a,b) => a[1] == b[1])([[1, 1], [1, 2], [2, 2], [2, 1]])) + returns a range containing 3 subranges: the first with just + $(D [1, 1]); the second with the elements $(D [1, 2]) and $(D [2, 2]); + and the third with just $(D [2, 1]).) +$(T2 cumulativeFold, + $(D cumulativeFold!((a, b) => a + b)([1, 2, 3, 4])) returns a + lazily-evaluated range containing the successive reduced values `1`, + `3`, `6`, `10`.) +$(T2 each, + $(D each!writeln([1, 2, 3])) eagerly prints the numbers $(D 1), $(D 2) + and $(D 3) on their own lines.) +$(T2 filter, + $(D filter!(a => a > 0)([1, -1, 2, 0, -3])) iterates over elements $(D 1) + and $(D 2).) +$(T2 filterBidirectional, + Similar to $(D filter), but also provides $(D back) and $(D popBack) at + a small increase in cost.) +$(T2 fold, + $(D fold!((a, b) => a + b)([1, 2, 3, 4])) returns $(D 10).) +$(T2 group, + $(D group([5, 2, 2, 3, 3])) returns a range containing the tuples + $(D tuple(5, 1)), $(D tuple(2, 2)), and $(D tuple(3, 2)).) +$(T2 joiner, + $(D joiner(["hello", "world!"], "; ")) returns a range that iterates + over the characters $(D "hello; world!"). No new string is created - + the existing inputs are iterated.) +$(T2 map, + $(D map!(a => a * 2)([1, 2, 3])) lazily returns a range with the numbers + $(D 2), $(D 4), $(D 6).) +$(T2 permutations, + Lazily computes all permutations using Heap's algorithm.) +$(T2 reduce, + $(D reduce!((a, b) => a + b)([1, 2, 3, 4])) returns $(D 10). + This is the old implementation of `fold`.) +$(T2 splitter, + Lazily splits a range by a separator.) +$(T2 sum, + Same as $(D fold), but specialized for accurate summation.) +$(T2 uniq, + Iterates over the unique elements in a range, which is assumed sorted.) +) + +Copyright: Andrei Alexandrescu 2008-. + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(HTTP erdani.com, Andrei Alexandrescu) + +Source: $(PHOBOSSRC std/algorithm/_iteration.d) + +Macros: +T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) + */ +module std.algorithm.iteration; + +// FIXME +import std.functional; // : unaryFun, binaryFun; +import std.range.primitives; +import std.traits; + +private template aggregate(fun...) +if (fun.length >= 1) +{ + /* --Intentionally not ddoc-- + * Aggregates elements in each subrange of the given range of ranges using + * the given aggregating function(s). + * Params: + * fun = One or more aggregating functions (binary functions that return a + * single _aggregate value of their arguments). + * ror = A range of ranges to be aggregated. + * + * Returns: + * A range representing the aggregated value(s) of each subrange + * of the original range. If only one aggregating function is specified, + * each element will be the aggregated value itself; if multiple functions + * are specified, each element will be a tuple of the aggregated values of + * each respective function. + */ + auto aggregate(RoR)(RoR ror) + if (isInputRange!RoR && isIterable!(ElementType!RoR)) + { + return ror.map!(reduce!fun); + } + + @safe unittest + { + import std.algorithm.comparison : equal, max, min; + + auto data = [[4, 2, 1, 3], [4, 9, -1, 3, 2], [3]]; + + // Single aggregating function + auto agg1 = data.aggregate!max; + assert(agg1.equal([4, 9, 3])); + + // Multiple aggregating functions + import std.typecons : tuple; + auto agg2 = data.aggregate!(max, min); + assert(agg2.equal([ + tuple(4, 1), + tuple(9, -1), + tuple(3, 3) + ])); + } +} + +/++ +$(D cache) eagerly evaluates $(D front) of $(D range) +on each construction or call to $(D popFront), +to store the result in a cache. +The result is then directly returned when $(D front) is called, +rather than re-evaluated. + +This can be a useful function to place in a chain, after functions +that have expensive evaluation, as a lazy alternative to $(REF array, std,array). +In particular, it can be placed after a call to $(D map), or before a call +to $(D filter). + +$(D cache) may provide +$(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives) +iteration if needed, but since this comes at an increased cost, it must be explicitly requested via the +call to $(D cacheBidirectional). Furthermore, a bidirectional cache will +evaluate the "center" element twice, when there is only one element left in +the range. + +$(D cache) does not provide random access primitives, +as $(D cache) would be unable to cache the random accesses. +If $(D Range) provides slicing primitives, +then $(D cache) will provide the same slicing primitives, +but $(D hasSlicing!Cache) will not yield true (as the $(REF hasSlicing, std,_range,primitives) +trait also checks for random access). + +Params: + range = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + +Returns: + an input range with the cached values of range ++/ +auto cache(Range)(Range range) +if (isInputRange!Range) +{ + return _Cache!(Range, false)(range); +} + +/// ditto +auto cacheBidirectional(Range)(Range range) +if (isBidirectionalRange!Range) +{ + return _Cache!(Range, true)(range); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range, std.stdio; + import std.typecons : tuple; + + ulong counter = 0; + double fun(int x) + { + ++counter; + // http://en.wikipedia.org/wiki/Quartic_function + return ( (x + 4.0) * (x + 1.0) * (x - 1.0) * (x - 3.0) ) / 14.0 + 0.5; + } + // Without cache, with array (greedy) + auto result1 = iota(-4, 5).map!(a =>tuple(a, fun(a)))() + .filter!(a => a[1] < 0)() + .map!(a => a[0])() + .array(); + + // the values of x that have a negative y are: + assert(equal(result1, [-3, -2, 2])); + + // Check how many times fun was evaluated. + // As many times as the number of items in both source and result. + assert(counter == iota(-4, 5).length + result1.length); + + counter = 0; + // Without array, with cache (lazy) + auto result2 = iota(-4, 5).map!(a =>tuple(a, fun(a)))() + .cache() + .filter!(a => a[1] < 0)() + .map!(a => a[0])(); + + // the values of x that have a negative y are: + assert(equal(result2, [-3, -2, 2])); + + // Check how many times fun was evaluated. + // Only as many times as the number of items in source. + assert(counter == iota(-4, 5).length); +} + +/++ +Tip: $(D cache) is eager when evaluating elements. If calling front on the +underlying _range has a side effect, it will be observable before calling +front on the actual cached _range. + +Furthermore, care should be taken composing $(D cache) with $(REF take, std,_range). +By placing $(D take) before $(D cache), then $(D cache) will be "aware" +of when the _range ends, and correctly stop caching elements when needed. +If calling front has no side effect though, placing $(D take) after $(D cache) +may yield a faster _range. + +Either way, the resulting ranges will be equivalent, but maybe not at the +same cost or side effects. ++/ +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range; + int i = 0; + + auto r = iota(0, 4).tee!((a){i = a;}, No.pipeOnPop); + auto r1 = r.take(3).cache(); + auto r2 = r.cache().take(3); + + assert(equal(r1, [0, 1, 2])); + assert(i == 2); //The last "seen" element was 2. The data in cache has been cleared. + + assert(equal(r2, [0, 1, 2])); + assert(i == 3); //cache has accessed 3. It is still stored internally by cache. +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range; + auto a = [1, 2, 3, 4]; + assert(equal(a.map!(a => (a - 1) * a)().cache(), [ 0, 2, 6, 12])); + assert(equal(a.map!(a => (a - 1) * a)().cacheBidirectional().retro(), [12, 6, 2, 0])); + auto r1 = [1, 2, 3, 4].cache() [1 .. $]; + auto r2 = [1, 2, 3, 4].cacheBidirectional()[1 .. $]; + assert(equal(r1, [2, 3, 4])); + assert(equal(r2, [2, 3, 4])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + //immutable test + static struct S + { + int i; + this(int i) + { + //this.i = i; + } + } + immutable(S)[] s = [S(1), S(2), S(3)]; + assert(equal(s.cache(), s)); + assert(equal(s.cacheBidirectional(), s)); +} + +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + + //safety etc + auto a = [1, 2, 3, 4]; + assert(equal(a.cache(), a)); + assert(equal(a.cacheBidirectional(), a)); +} + +@safe unittest +{ + char[][] stringbufs = ["hello".dup, "world".dup]; + auto strings = stringbufs.map!((a)=>a.idup)().cache(); + assert(strings.front is strings.front); +} + +@safe unittest +{ + import std.range : cycle; + import std.algorithm.comparison : equal; + + auto c = [1, 2, 3].cycle().cache(); + c = c[1 .. $]; + auto d = c[0 .. 1]; + assert(d.equal([2])); +} + +@safe unittest +{ + static struct Range + { + bool initialized = false; + bool front() @property {return initialized = true;} + void popFront() {initialized = false;} + enum empty = false; + } + auto r = Range().cache(); + assert(r.source.initialized == true); +} + +private struct _Cache(R, bool bidir) +{ + import core.exception : RangeError; + + private + { + import std.algorithm.internal : algoFormat; + import std.meta : AliasSeq; + + alias E = ElementType!R; + alias UE = Unqual!E; + + R source; + + static if (bidir) alias CacheTypes = AliasSeq!(UE, UE); + else alias CacheTypes = AliasSeq!UE; + CacheTypes caches; + + static assert(isAssignable!(UE, E) && is(UE : E), + algoFormat( + "Cannot instantiate range with %s because %s elements are not assignable to %s.", + R.stringof, + E.stringof, + UE.stringof + ) + ); + } + + this(R range) + { + source = range; + if (!range.empty) + { + caches[0] = source.front; + static if (bidir) + caches[1] = source.back; + } + } + + static if (isInfinite!R) + enum empty = false; + else + bool empty() @property + { + return source.empty; + } + + static if (hasLength!R) auto length() @property + { + return source.length; + } + + E front() @property + { + version (assert) if (empty) throw new RangeError(); + return caches[0]; + } + static if (bidir) E back() @property + { + version (assert) if (empty) throw new RangeError(); + return caches[1]; + } + + void popFront() + { + version (assert) if (empty) throw new RangeError(); + source.popFront(); + if (!source.empty) + caches[0] = source.front; + else + caches = CacheTypes.init; + } + static if (bidir) void popBack() + { + version (assert) if (empty) throw new RangeError(); + source.popBack(); + if (!source.empty) + caches[1] = source.back; + else + caches = CacheTypes.init; + } + + static if (isForwardRange!R) + { + private this(R source, ref CacheTypes caches) + { + this.source = source; + this.caches = caches; + } + typeof(this) save() @property + { + return typeof(this)(source.save, caches); + } + } + + static if (hasSlicing!R) + { + enum hasEndSlicing = is(typeof(source[size_t.max .. $])); + + static if (hasEndSlicing) + { + private static struct DollarToken{} + enum opDollar = DollarToken.init; + + auto opSlice(size_t low, DollarToken) + { + return typeof(this)(source[low .. $]); + } + } + + static if (!isInfinite!R) + { + typeof(this) opSlice(size_t low, size_t high) + { + return typeof(this)(source[low .. high]); + } + } + else static if (hasEndSlicing) + { + auto opSlice(size_t low, size_t high) + in + { + assert(low <= high, "Bounds error when slicing cache."); + } + body + { + import std.range : takeExactly; + return this[low .. $].takeExactly(high - low); + } + } + } +} + +/** +$(D auto map(Range)(Range r) if (isInputRange!(Unqual!Range));) + +Implements the homonym function (also known as $(D transform)) present +in many languages of functional flavor. The call $(D map!(fun)(range)) +returns a range of which elements are obtained by applying $(D fun(a)) +left to right for all elements $(D a) in $(D range). The original ranges are +not changed. Evaluation is done lazily. + +Params: + fun = one or more transformation functions + r = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + +Returns: + a range with each fun applied to all the elements. If there is more than one + fun, the element type will be $(D Tuple) containing one element for each fun. + +See_Also: + $(HTTP en.wikipedia.org/wiki/Map_(higher-order_function), Map (higher-order function)) +*/ +template map(fun...) +if (fun.length >= 1) +{ + auto map(Range)(Range r) if (isInputRange!(Unqual!Range)) + { + import std.meta : AliasSeq, staticMap; + + alias RE = ElementType!(Range); + static if (fun.length > 1) + { + import std.functional : adjoin; + import std.meta : staticIndexOf; + + alias _funs = staticMap!(unaryFun, fun); + alias _fun = adjoin!_funs; + + // Once DMD issue #5710 is fixed, this validation loop can be moved into a template. + foreach (f; _funs) + { + static assert(!is(typeof(f(RE.init)) == void), + "Mapping function(s) must not return void: " ~ _funs.stringof); + } + } + else + { + alias _fun = unaryFun!fun; + alias _funs = AliasSeq!(_fun); + + // Do the validation separately for single parameters due to DMD issue #15777. + static assert(!is(typeof(_fun(RE.init)) == void), + "Mapping function(s) must not return void: " ~ _funs.stringof); + } + + return MapResult!(_fun, Range)(r); + } +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : chain; + int[] arr1 = [ 1, 2, 3, 4 ]; + int[] arr2 = [ 5, 6 ]; + auto squares = map!(a => a * a)(chain(arr1, arr2)); + assert(equal(squares, [ 1, 4, 9, 16, 25, 36 ])); +} + +/** +Multiple functions can be passed to $(D map). In that case, the +element type of $(D map) is a tuple containing one element for each +function. +*/ +@safe unittest +{ + auto sums = [2, 4, 6, 8]; + auto products = [1, 4, 9, 16]; + + size_t i = 0; + foreach (result; [ 1, 2, 3, 4 ].map!("a + a", "a * a")) + { + assert(result[0] == sums[i]); + assert(result[1] == products[i]); + ++i; + } +} + +/** +You may alias $(D map) with some function(s) to a symbol and use +it separately: +*/ +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.conv : to; + + alias stringize = map!(to!string); + assert(equal(stringize([ 1, 2, 3, 4 ]), [ "1", "2", "3", "4" ])); +} + +@safe unittest +{ + // Verify workaround for DMD #15777 + + import std.algorithm.mutation, std.string; + auto foo(string[] args) + { + return args.map!strip; + } +} + +private struct MapResult(alias fun, Range) +{ + alias R = Unqual!Range; + R _input; + + static if (isBidirectionalRange!R) + { + @property auto ref back()() + { + assert(!empty, "Attempting to fetch the back of an empty map."); + return fun(_input.back); + } + + void popBack()() + { + assert(!empty, "Attempting to popBack an empty map."); + _input.popBack(); + } + } + + this(R input) + { + _input = input; + } + + static if (isInfinite!R) + { + // Propagate infinite-ness. + enum bool empty = false; + } + else + { + @property bool empty() + { + return _input.empty; + } + } + + void popFront() + { + assert(!empty, "Attempting to popFront an empty map."); + _input.popFront(); + } + + @property auto ref front() + { + assert(!empty, "Attempting to fetch the front of an empty map."); + return fun(_input.front); + } + + static if (isRandomAccessRange!R) + { + static if (is(typeof(_input[ulong.max]))) + private alias opIndex_t = ulong; + else + private alias opIndex_t = uint; + + auto ref opIndex(opIndex_t index) + { + return fun(_input[index]); + } + } + + static if (hasLength!R) + { + @property auto length() + { + return _input.length; + } + + alias opDollar = length; + } + + static if (hasSlicing!R) + { + static if (is(typeof(_input[ulong.max .. ulong.max]))) + private alias opSlice_t = ulong; + else + private alias opSlice_t = uint; + + static if (hasLength!R) + { + auto opSlice(opSlice_t low, opSlice_t high) + { + return typeof(this)(_input[low .. high]); + } + } + else static if (is(typeof(_input[opSlice_t.max .. $]))) + { + struct DollarToken{} + enum opDollar = DollarToken.init; + auto opSlice(opSlice_t low, DollarToken) + { + return typeof(this)(_input[low .. $]); + } + + auto opSlice(opSlice_t low, opSlice_t high) + { + import std.range : takeExactly; + return this[low .. $].takeExactly(high - low); + } + } + } + + static if (isForwardRange!R) + { + @property auto save() + { + return typeof(this)(_input.save); + } + } +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.conv : to; + import std.functional : adjoin; + + alias stringize = map!(to!string); + assert(equal(stringize([ 1, 2, 3, 4 ]), [ "1", "2", "3", "4" ])); + + uint counter; + alias count = map!((a) { return counter++; }); + assert(equal(count([ 10, 2, 30, 4 ]), [ 0, 1, 2, 3 ])); + + counter = 0; + adjoin!((a) { return counter++; }, (a) { return counter++; })(1); + alias countAndSquare = map!((a) { return counter++; }, (a) { return counter++; }); + //assert(equal(countAndSquare([ 10, 2 ]), [ tuple(0u, 100), tuple(1u, 4) ])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.ascii : toUpper; + import std.internal.test.dummyrange; + import std.range; + import std.typecons : tuple; + import std.random : unpredictableSeed, uniform, Random; + + int[] arr1 = [ 1, 2, 3, 4 ]; + const int[] arr1Const = arr1; + int[] arr2 = [ 5, 6 ]; + auto squares = map!("a * a")(arr1Const); + assert(squares[$ - 1] == 16); + assert(equal(squares, [ 1, 4, 9, 16 ][])); + assert(equal(map!("a * a")(chain(arr1, arr2)), [ 1, 4, 9, 16, 25, 36 ][])); + + // Test the caching stuff. + assert(squares.back == 16); + auto squares2 = squares.save; + assert(squares2.back == 16); + + assert(squares2.front == 1); + squares2.popFront(); + assert(squares2.front == 4); + squares2.popBack(); + assert(squares2.front == 4); + assert(squares2.back == 9); + + assert(equal(map!("a * a")(chain(arr1, arr2)), [ 1, 4, 9, 16, 25, 36 ][])); + + uint i; + foreach (e; map!("a", "a * a")(arr1)) + { + assert(e[0] == ++i); + assert(e[1] == i * i); + } + + // Test length. + assert(squares.length == 4); + assert(map!"a * a"(chain(arr1, arr2)).length == 6); + + // Test indexing. + assert(squares[0] == 1); + assert(squares[1] == 4); + assert(squares[2] == 9); + assert(squares[3] == 16); + + // Test slicing. + auto squareSlice = squares[1 .. squares.length - 1]; + assert(equal(squareSlice, [4, 9][])); + assert(squareSlice.back == 9); + assert(squareSlice[1] == 9); + + // Test on a forward range to make sure it compiles when all the fancy + // stuff is disabled. + auto fibsSquares = map!"a * a"(recurrence!("a[n-1] + a[n-2]")(1, 1)); + assert(fibsSquares.front == 1); + fibsSquares.popFront(); + fibsSquares.popFront(); + assert(fibsSquares.front == 4); + fibsSquares.popFront(); + assert(fibsSquares.front == 9); + + auto repeatMap = map!"a"(repeat(1)); + auto gen = Random(unpredictableSeed); + auto index = uniform(0, 1024, gen); + static assert(isInfinite!(typeof(repeatMap))); + assert(repeatMap[index] == 1); + + auto intRange = map!"a"([1,2,3]); + static assert(isRandomAccessRange!(typeof(intRange))); + assert(equal(intRange, [1, 2, 3])); + + foreach (DummyType; AllDummyRanges) + { + DummyType d; + auto m = map!"a * a"(d); + + static assert(propagatesRangeType!(typeof(m), DummyType)); + assert(equal(m, [1,4,9,16,25,36,49,64,81,100])); + } + + //Test string access + string s1 = "hello world!"; + dstring s2 = "日本語"; + dstring s3 = "hello world!"d; + auto ms1 = map!(std.ascii.toUpper)(s1); + auto ms2 = map!(std.ascii.toUpper)(s2); + auto ms3 = map!(std.ascii.toUpper)(s3); + static assert(!is(ms1[0])); //narrow strings can't be indexed + assert(ms2[0] == '日'); + assert(ms3[0] == 'H'); + static assert(!is(ms1[0 .. 1])); //narrow strings can't be sliced + assert(equal(ms2[0 .. 2], "日本"w)); + assert(equal(ms3[0 .. 2], "HE")); + + // Issue 5753 + static void voidFun(int) {} + static int nonvoidFun(int) { return 0; } + static assert(!__traits(compiles, map!voidFun([1]))); + static assert(!__traits(compiles, map!(voidFun, voidFun)([1]))); + static assert(!__traits(compiles, map!(nonvoidFun, voidFun)([1]))); + static assert(!__traits(compiles, map!(voidFun, nonvoidFun)([1]))); + static assert(!__traits(compiles, map!(a => voidFun(a))([1]))); + + // Phobos issue #15480 + auto dd = map!(z => z * z, c => c * c * c)([ 1, 2, 3, 4 ]); + assert(dd[0] == tuple(1, 1)); + assert(dd[1] == tuple(4, 8)); + assert(dd[2] == tuple(9, 27)); + assert(dd[3] == tuple(16, 64)); + assert(dd.length == 4); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range; + auto LL = iota(1L, 4L); + auto m = map!"a*a"(LL); + assert(equal(m, [1L, 4L, 9L])); +} + +@safe unittest +{ + import std.range : iota; + + // Issue #10130 - map of iota with const step. + const step = 2; + assert(map!(i => i)(iota(0, 10, step)).walkLength == 5); + + // Need these to all by const to repro the float case, due to the + // CommonType template used in the float specialization of iota. + const floatBegin = 0.0; + const floatEnd = 1.0; + const floatStep = 0.02; + assert(map!(i => i)(iota(floatBegin, floatEnd, floatStep)).walkLength == 50); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range; + //slicing infinites + auto rr = iota(0, 5).cycle().map!"a * a"(); + alias RR = typeof(rr); + static assert(hasSlicing!RR); + rr = rr[6 .. $]; //Advances 1 cycle and 1 unit + assert(equal(rr[0 .. 5], [1, 4, 9, 16, 0])); +} + +@safe unittest +{ + import std.range; + struct S {int* p;} + auto m = immutable(S).init.repeat().map!"a".save; + assert(m.front == immutable(S)(null)); +} + +// each +/** +Eagerly iterates over $(D r) and calls $(D pred) over _each element. + +If no predicate is specified, $(D each) will default to doing nothing +but consuming the entire range. $(D .front) will be evaluated, but this +can be avoided by explicitly specifying a predicate lambda with a +$(D lazy) parameter. + +$(D each) also supports $(D opApply)-based iterators, so it will work +with e.g. $(REF parallel, std,parallelism). + +Params: + pred = predicate to apply to each element of the range + r = range or iterable over which each iterates + +See_Also: $(REF tee, std,range) + + */ +template each(alias pred = "a") +{ + import std.meta : AliasSeq; + import std.traits : Parameters; + +private: + alias BinaryArgs = AliasSeq!(pred, "i", "a"); + + enum isRangeUnaryIterable(R) = + is(typeof(unaryFun!pred(R.init.front))); + + enum isRangeBinaryIterable(R) = + is(typeof(binaryFun!BinaryArgs(0, R.init.front))); + + enum isRangeIterable(R) = + isInputRange!R && + (isRangeUnaryIterable!R || isRangeBinaryIterable!R); + + enum isForeachUnaryIterable(R) = + is(typeof((R r) { + foreach (ref a; r) + cast(void) unaryFun!pred(a); + })); + + enum isForeachBinaryIterable(R) = + is(typeof((R r) { + foreach (ref i, ref a; r) + cast(void) binaryFun!BinaryArgs(i, a); + })); + + enum isForeachIterable(R) = + (!isForwardRange!R || isDynamicArray!R) && + (isForeachUnaryIterable!R || isForeachBinaryIterable!R); + +public: + void each(Range)(Range r) + if (!isForeachIterable!Range && ( + isRangeIterable!Range || + __traits(compiles, typeof(r.front).length))) + { + static if (isRangeIterable!Range) + { + debug(each) pragma(msg, "Using while for ", Range.stringof); + static if (isRangeUnaryIterable!Range) + { + while (!r.empty) + { + cast(void) unaryFun!pred(r.front); + r.popFront(); + } + } + else // if (isRangeBinaryIterable!Range) + { + size_t i = 0; + while (!r.empty) + { + cast(void) binaryFun!BinaryArgs(i, r.front); + r.popFront(); + i++; + } + } + } + else + { + // range interface with >2 parameters. + for (auto range = r; !range.empty; range.popFront()) + pred(range.front.expand); + } + } + + void each(Iterable)(auto ref Iterable r) + if (isForeachIterable!Iterable || + __traits(compiles, Parameters!(Parameters!(r.opApply)))) + { + static if (isForeachIterable!Iterable) + { + debug(each) pragma(msg, "Using foreach for ", Iterable.stringof); + static if (isForeachUnaryIterable!Iterable) + { + foreach (ref e; r) + cast(void) unaryFun!pred(e); + } + else // if (isForeachBinaryIterable!Iterable) + { + foreach (ref i, ref e; r) + cast(void) binaryFun!BinaryArgs(i, e); + } + } + else + { + // opApply with >2 parameters. count the delegate args. + // only works if it is not templated (otherwise we cannot count the args) + auto dg(Parameters!(Parameters!(r.opApply)) params) { + pred(params); + return 0; // tells opApply to continue iteration + } + r.opApply(&dg); + } + } +} + +/// +@system unittest +{ + import std.range : iota; + + long[] arr; + iota(5).each!(n => arr ~= n); + assert(arr == [0, 1, 2, 3, 4]); + + // If the range supports it, the value can be mutated in place + arr.each!((ref n) => n++); + assert(arr == [1, 2, 3, 4, 5]); + + arr.each!"a++"; + assert(arr == [2, 3, 4, 5, 6]); + + // by-ref lambdas are not allowed for non-ref ranges + static assert(!is(typeof(arr.map!(n => n).each!((ref n) => n++)))); + + // The default predicate consumes the range + auto m = arr.map!(n => n); + (&m).each(); + assert(m.empty); + + // Indexes are also available for in-place mutations + arr[] = 0; + arr.each!"a=i"(); + assert(arr == [0, 1, 2, 3, 4]); + + // opApply iterators work as well + static class S + { + int x; + int opApply(scope int delegate(ref int _x) dg) { return dg(x); } + } + + auto s = new S; + s.each!"a++"; + assert(s.x == 1); +} + +// binary foreach with two ref args +@system unittest +{ + import std.range : lockstep; + + auto a = [ 1, 2, 3 ]; + auto b = [ 2, 3, 4 ]; + + a.lockstep(b).each!((ref x, ref y) { ++x; ++y; }); + + assert(a == [ 2, 3, 4 ]); + assert(b == [ 3, 4, 5 ]); +} + +// #15358: application of `each` with >2 args (opApply) +@system unittest +{ + import std.range : lockstep; + auto a = [0,1,2]; + auto b = [3,4,5]; + auto c = [6,7,8]; + + lockstep(a, b, c).each!((ref x, ref y, ref z) { ++x; ++y; ++z; }); + + assert(a == [1,2,3]); + assert(b == [4,5,6]); + assert(c == [7,8,9]); +} + +// #15358: application of `each` with >2 args (range interface) +@safe unittest +{ + import std.range : zip; + auto a = [0,1,2]; + auto b = [3,4,5]; + auto c = [6,7,8]; + + int[] res; + + zip(a, b, c).each!((x, y, z) { res ~= x + y + z; }); + + assert(res == [9, 12, 15]); +} + +// #16255: `each` on opApply doesn't support ref +@safe unittest +{ + int[] dynamicArray = [1, 2, 3, 4, 5]; + int[5] staticArray = [1, 2, 3, 4, 5]; + + dynamicArray.each!((ref x) => x++); + assert(dynamicArray == [2, 3, 4, 5, 6]); + + staticArray.each!((ref x) => x++); + assert(staticArray == [2, 3, 4, 5, 6]); + + staticArray[].each!((ref x) => x++); + assert(staticArray == [3, 4, 5, 6, 7]); +} + +// #16255: `each` on opApply doesn't support ref +@system unittest +{ + struct S + { + int x; + int opApply(int delegate(ref int _x) dg) { return dg(x); } + } + + S s; + foreach (ref a; s) ++a; + assert(s.x == 1); + s.each!"++a"; + assert(s.x == 2); +} + +// filter +/** +$(D auto filter(Range)(Range rs) if (isInputRange!(Unqual!Range));) + +Implements the higher order _filter function. The predicate is passed to +$(REF unaryFun, std,functional), and can either accept a string, or any callable +that can be executed via $(D pred(element)). + +Params: + predicate = Function to apply to each element of range + range = Input range of elements + +Returns: + $(D filter!(predicate)(range)) returns a new range containing only elements $(D x) in $(D range) for + which $(D predicate(x)) returns $(D true). + +See_Also: + $(HTTP en.wikipedia.org/wiki/Filter_(higher-order_function), Filter (higher-order function)) + */ +template filter(alias predicate) +if (is(typeof(unaryFun!predicate))) +{ + auto filter(Range)(Range range) if (isInputRange!(Unqual!Range)) + { + return FilterResult!(unaryFun!predicate, Range)(range); + } +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.math : approxEqual; + import std.range; + + int[] arr = [ 1, 2, 3, 4, 5 ]; + + // Sum all elements + auto small = filter!(a => a < 3)(arr); + assert(equal(small, [ 1, 2 ])); + + // Sum again, but with Uniform Function Call Syntax (UFCS) + auto sum = arr.filter!(a => a < 3); + assert(equal(sum, [ 1, 2 ])); + + // In combination with chain() to span multiple ranges + int[] a = [ 3, -2, 400 ]; + int[] b = [ 100, -101, 102 ]; + auto r = chain(a, b).filter!(a => a > 0); + assert(equal(r, [ 3, 400, 100, 102 ])); + + // Mixing convertible types is fair game, too + double[] c = [ 2.5, 3.0 ]; + auto r1 = chain(c, a, b).filter!(a => cast(int) a != a); + assert(approxEqual(r1, [ 2.5 ])); +} + +private struct FilterResult(alias pred, Range) +{ + alias R = Unqual!Range; + R _input; + private bool _primed; + + private void prime() + { + if (_primed) return; + while (!_input.empty && !pred(_input.front)) + { + _input.popFront(); + } + _primed = true; + } + + this(R r) + { + _input = r; + } + + private this(R r, bool primed) + { + _input = r; + _primed = primed; + } + + auto opSlice() { return this; } + + static if (isInfinite!Range) + { + enum bool empty = false; + } + else + { + @property bool empty() { prime; return _input.empty; } + } + + void popFront() + { + do + { + _input.popFront(); + } while (!_input.empty && !pred(_input.front)); + _primed = true; + } + + @property auto ref front() + { + prime; + assert(!empty, "Attempting to fetch the front of an empty filter."); + return _input.front; + } + + static if (isForwardRange!R) + { + @property auto save() + { + return typeof(this)(_input.save, _primed); + } + } +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange; + import std.range; + + auto shouldNotLoop4ever = repeat(1).filter!(x => x % 2 == 0); + static assert(isInfinite!(typeof(shouldNotLoop4ever))); + assert(!shouldNotLoop4ever.empty); + + int[] a = [ 3, 4, 2 ]; + auto r = filter!("a > 3")(a); + static assert(isForwardRange!(typeof(r))); + assert(equal(r, [ 4 ][])); + + a = [ 1, 22, 3, 42, 5 ]; + auto under10 = filter!("a < 10")(a); + assert(equal(under10, [1, 3, 5][])); + static assert(isForwardRange!(typeof(under10))); + under10.front = 4; + assert(equal(under10, [4, 3, 5][])); + under10.front = 40; + assert(equal(under10, [40, 3, 5][])); + under10.front = 1; + + auto infinite = filter!"a > 2"(repeat(3)); + static assert(isInfinite!(typeof(infinite))); + static assert(isForwardRange!(typeof(infinite))); + assert(infinite.front == 3); + + foreach (DummyType; AllDummyRanges) + { + DummyType d; + auto f = filter!"a & 1"(d); + assert(equal(f, [1,3,5,7,9])); + + static if (isForwardRange!DummyType) + { + static assert(isForwardRange!(typeof(f))); + } + } + + // With delegates + int x = 10; + int overX(int a) { return a > x; } + typeof(filter!overX(a)) getFilter() + { + return filter!overX(a); + } + auto r1 = getFilter(); + assert(equal(r1, [22, 42])); + + // With chain + auto nums = [0,1,2,3,4]; + assert(equal(filter!overX(chain(a, nums)), [22, 42])); + + // With copying of inner struct Filter to Map + auto arr = [1,2,3,4,5]; + auto m = map!"a + 1"(filter!"a < 4"(arr)); + assert(equal(m, [2, 3, 4])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + int[] a = [ 3, 4 ]; + const aConst = a; + auto r = filter!("a > 3")(aConst); + assert(equal(r, [ 4 ][])); + + a = [ 1, 22, 3, 42, 5 ]; + auto under10 = filter!("a < 10")(a); + assert(equal(under10, [1, 3, 5][])); + assert(equal(under10.save, [1, 3, 5][])); + assert(equal(under10.save, under10)); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.functional : compose, pipe; + + assert(equal(compose!(map!"2 * a", filter!"a & 1")([1,2,3,4,5]), + [2,6,10])); + assert(equal(pipe!(filter!"a & 1", map!"2 * a")([1,2,3,4,5]), + [2,6,10])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + int x = 10; + int underX(int a) { return a < x; } + const(int)[] list = [ 1, 2, 10, 11, 3, 4 ]; + assert(equal(filter!underX(list), [ 1, 2, 3, 4 ])); +} + +/** + * $(D auto filterBidirectional(Range)(Range r) if (isBidirectionalRange!(Unqual!Range));) + * + * Similar to $(D filter), except it defines a + * $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives). + * There is a speed disadvantage - the constructor spends time + * finding the last element in the range that satisfies the filtering + * condition (in addition to finding the first one). The advantage is + * that the filtered range can be spanned from both directions. Also, + * $(REF retro, std,range) can be applied against the filtered range. + * + * The predicate is passed to $(REF unaryFun, std,functional), and can either + * accept a string, or any callable that can be executed via $(D pred(element)). + * + * Params: + * pred = Function to apply to each element of range + * r = Bidirectional range of elements + * + * Returns: + * a new range containing only the elements in r for which pred returns $(D true). + */ +template filterBidirectional(alias pred) +{ + auto filterBidirectional(Range)(Range r) if (isBidirectionalRange!(Unqual!Range)) + { + return FilterBidiResult!(unaryFun!pred, Range)(r); + } +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range; + + int[] arr = [ 1, 2, 3, 4, 5 ]; + auto small = filterBidirectional!("a < 3")(arr); + static assert(isBidirectionalRange!(typeof(small))); + assert(small.back == 2); + assert(equal(small, [ 1, 2 ])); + assert(equal(retro(small), [ 2, 1 ])); + // In combination with chain() to span multiple ranges + int[] a = [ 3, -2, 400 ]; + int[] b = [ 100, -101, 102 ]; + auto r = filterBidirectional!("a > 0")(chain(a, b)); + assert(r.back == 102); +} + +private struct FilterBidiResult(alias pred, Range) +{ + alias R = Unqual!Range; + R _input; + + this(R r) + { + _input = r; + while (!_input.empty && !pred(_input.front)) _input.popFront(); + while (!_input.empty && !pred(_input.back)) _input.popBack(); + } + + @property bool empty() { return _input.empty; } + + void popFront() + { + do + { + _input.popFront(); + } while (!_input.empty && !pred(_input.front)); + } + + @property auto ref front() + { + assert(!empty, "Attempting to fetch the front of an empty filterBidirectional."); + return _input.front; + } + + void popBack() + { + do + { + _input.popBack(); + } while (!_input.empty && !pred(_input.back)); + } + + @property auto ref back() + { + assert(!empty, "Attempting to fetch the back of an empty filterBidirectional."); + return _input.back; + } + + @property auto save() + { + return typeof(this)(_input.save); + } +} + +/** +Groups consecutively equivalent elements into a single tuple of the element and +the number of its repetitions. + +Similarly to $(D uniq), $(D group) produces a range that iterates over unique +consecutive elements of the given range. Each element of this range is a tuple +of the element and the number of times it is repeated in the original range. +Equivalence of elements is assessed by using the predicate $(D pred), which +defaults to $(D "a == b"). The predicate is passed to $(REF binaryFun, std,functional), +and can either accept a string, or any callable that can be executed via +$(D pred(element, element)). + +Params: + pred = Binary predicate for determining equivalence of two elements. + r = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to + iterate over. + +Returns: A range of elements of type $(D Tuple!(ElementType!R, uint)), +representing each consecutively unique element and its respective number of +occurrences in that run. This will be an input range if $(D R) is an input +range, and a forward range in all other cases. + +See_Also: $(LREF chunkBy), which chunks an input range into subranges + of equivalent adjacent elements. +*/ +Group!(pred, Range) group(alias pred = "a == b", Range)(Range r) +{ + return typeof(return)(r); +} + +/// ditto +struct Group(alias pred, R) +if (isInputRange!R) +{ + import std.typecons : Rebindable, tuple, Tuple; + + private alias comp = binaryFun!pred; + + private alias E = ElementType!R; + static if ((is(E == class) || is(E == interface)) && + (is(E == const) || is(E == immutable))) + { + private alias MutableE = Rebindable!E; + } + else static if (is(E : Unqual!E)) + { + private alias MutableE = Unqual!E; + } + else + { + private alias MutableE = E; + } + + private R _input; + private Tuple!(MutableE, uint) _current; + + /// + this(R input) + { + _input = input; + if (!_input.empty) popFront(); + } + + /// + void popFront() + { + if (_input.empty) + { + _current[1] = 0; + } + else + { + _current = tuple(_input.front, 1u); + _input.popFront(); + while (!_input.empty && comp(_current[0], _input.front)) + { + ++_current[1]; + _input.popFront(); + } + } + } + + static if (isInfinite!R) + { + /// + enum bool empty = false; // Propagate infiniteness. + } + else + { + /// + @property bool empty() + { + return _current[1] == 0; + } + } + + /// + @property auto ref front() + { + assert(!empty, "Attempting to fetch the front of an empty Group."); + return _current; + } + + static if (isForwardRange!R) + { + /// + @property typeof(this) save() { + typeof(this) ret = this; + ret._input = this._input.save; + ret._current = this._current; + return ret; + } + } +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : tuple, Tuple; + + int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; + assert(equal(group(arr), [ tuple(1, 1u), tuple(2, 4u), tuple(3, 1u), + tuple(4, 3u), tuple(5, 1u) ][])); +} + +/** + * Using group, an associative array can be easily generated with the count of each + * unique element in the range. + */ +@safe unittest +{ + import std.algorithm.sorting : sort; + import std.array : assocArray; + + uint[string] result; + auto range = ["a", "b", "a", "c", "b", "c", "c", "d", "e"]; + result = range.sort!((a, b) => a < b) + .group + .assocArray; + + assert(result == ["a": 2U, "b": 2U, "c": 3U, "d": 1U, "e": 1U]); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange; + import std.typecons : tuple, Tuple; + + int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; + assert(equal(group(arr), [ tuple(1, 1u), tuple(2, 4u), tuple(3, 1u), + tuple(4, 3u), tuple(5, 1u) ][])); + static assert(isForwardRange!(typeof(group(arr)))); + + foreach (DummyType; AllDummyRanges) + { + DummyType d; + auto g = group(d); + + static assert(d.rt == RangeType.Input || isForwardRange!(typeof(g))); + + assert(equal(g, [tuple(1, 1u), tuple(2, 1u), tuple(3, 1u), tuple(4, 1u), + tuple(5, 1u), tuple(6, 1u), tuple(7, 1u), tuple(8, 1u), + tuple(9, 1u), tuple(10, 1u)])); + } +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : tuple; + + // Issue 13857 + immutable(int)[] a1 = [1,1,2,2,2,3,4,4,5,6,6,7,8,9,9,9]; + auto g1 = group(a1); + assert(equal(g1, [ tuple(1, 2u), tuple(2, 3u), tuple(3, 1u), + tuple(4, 2u), tuple(5, 1u), tuple(6, 2u), + tuple(7, 1u), tuple(8, 1u), tuple(9, 3u) + ])); + + // Issue 13162 + immutable(ubyte)[] a2 = [1, 1, 1, 0, 0, 0]; + auto g2 = a2.group; + assert(equal(g2, [ tuple(1, 3u), tuple(0, 3u) ])); + + // Issue 10104 + const a3 = [1, 1, 2, 2]; + auto g3 = a3.group; + assert(equal(g3, [ tuple(1, 2u), tuple(2, 2u) ])); + + interface I {} + class C : I {} + const C[] a4 = [new const C()]; + auto g4 = a4.group!"a is b"; + assert(g4.front[1] == 1); + + immutable I[] a5 = [new immutable C()]; + auto g5 = a5.group!"a is b"; + assert(g5.front[1] == 1); + + const(int[][]) a6 = [[1], [1]]; + auto g6 = a6.group; + assert(equal(g6.front[0], [1])); +} + +// Used by implementation of chunkBy for non-forward input ranges. +private struct ChunkByChunkImpl(alias pred, Range) +if (isInputRange!Range && !isForwardRange!Range) +{ + alias fun = binaryFun!pred; + + private Range r; + private ElementType!Range prev; + + this(Range range, ElementType!Range _prev) + { + r = range; + prev = _prev; + } + + @property bool empty() + { + return r.empty || !fun(prev, r.front); + } + + @property ElementType!Range front() { return r.front; } + void popFront() { r.popFront(); } +} + +private template ChunkByImplIsUnary(alias pred, Range) +{ + static if (is(typeof(binaryFun!pred(ElementType!Range.init, + ElementType!Range.init)) : bool)) + enum ChunkByImplIsUnary = false; + else static if (is(typeof( + unaryFun!pred(ElementType!Range.init) == + unaryFun!pred(ElementType!Range.init)))) + enum ChunkByImplIsUnary = true; + else + static assert(0, "chunkBy expects either a binary predicate or "~ + "a unary predicate on range elements of type: "~ + ElementType!Range.stringof); +} + +// Implementation of chunkBy for non-forward input ranges. +private struct ChunkByImpl(alias pred, Range) +if (isInputRange!Range && !isForwardRange!Range) +{ + enum bool isUnary = ChunkByImplIsUnary!(pred, Range); + + static if (isUnary) + alias eq = binaryFun!((a, b) => unaryFun!pred(a) == unaryFun!pred(b)); + else + alias eq = binaryFun!pred; + + private Range r; + private ElementType!Range _prev; + + this(Range _r) + { + r = _r; + if (!empty) + { + // Check reflexivity if predicate is claimed to be an equivalence + // relation. + assert(eq(r.front, r.front), + "predicate is not reflexive"); + + // _prev's type may be a nested struct, so must be initialized + // directly in the constructor (cannot call savePred()). + _prev = r.front; + } + else + { + // We won't use _prev, but must be initialized. + _prev = typeof(_prev).init; + } + } + @property bool empty() { return r.empty; } + + @property auto front() + { + static if (isUnary) + { + import std.typecons : tuple; + return tuple(unaryFun!pred(_prev), + ChunkByChunkImpl!(eq, Range)(r, _prev)); + } + else + { + return ChunkByChunkImpl!(eq, Range)(r, _prev); + } + } + + void popFront() + { + while (!r.empty) + { + if (!eq(_prev, r.front)) + { + _prev = r.front; + break; + } + r.popFront(); + } + } +} + +// Single-pass implementation of chunkBy for forward ranges. +private struct ChunkByImpl(alias pred, Range) +if (isForwardRange!Range) +{ + import std.typecons : RefCounted; + + enum bool isUnary = ChunkByImplIsUnary!(pred, Range); + + static if (isUnary) + alias eq = binaryFun!((a, b) => unaryFun!pred(a) == unaryFun!pred(b)); + else + alias eq = binaryFun!pred; + + // Outer range + static struct Impl + { + size_t groupNum; + Range current; + Range next; + } + + // Inner range + static struct Group + { + private size_t groupNum; + private Range start; + private Range current; + + private RefCounted!Impl mothership; + + this(RefCounted!Impl origin) + { + groupNum = origin.groupNum; + + start = origin.current.save; + current = origin.current.save; + assert(!start.empty); + + mothership = origin; + + // Note: this requires reflexivity. + assert(eq(start.front, current.front), + "predicate is not reflexive"); + } + + @property bool empty() { return groupNum == size_t.max; } + @property auto ref front() { return current.front; } + + void popFront() + { + current.popFront(); + + // Note: this requires transitivity. + if (current.empty || !eq(start.front, current.front)) + { + if (groupNum == mothership.groupNum) + { + // If parent range hasn't moved on yet, help it along by + // saving location of start of next Group. + mothership.next = current.save; + } + + groupNum = size_t.max; + } + } + + @property auto save() + { + auto copy = this; + copy.current = current.save; + return copy; + } + } + static assert(isForwardRange!Group); + + private RefCounted!Impl impl; + + this(Range r) + { + impl = RefCounted!Impl(0, r, r.save); + } + + @property bool empty() { return impl.current.empty; } + + @property auto front() + { + static if (isUnary) + { + import std.typecons : tuple; + return tuple(unaryFun!pred(impl.current.front), Group(impl)); + } + else + { + return Group(impl); + } + } + + void popFront() + { + // Scan for next group. If we're lucky, one of our Groups would have + // already set .next to the start of the next group, in which case the + // loop is skipped. + while (!impl.next.empty && eq(impl.current.front, impl.next.front)) + { + impl.next.popFront(); + } + + impl.current = impl.next.save; + + // Indicate to any remaining Groups that we have moved on. + impl.groupNum++; + } + + @property auto save() + { + // Note: the new copy of the range will be detached from any existing + // satellite Groups, and will not benefit from the .next acceleration. + return typeof(this)(impl.current.save); + } + + static assert(isForwardRange!(typeof(this))); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + + size_t popCount = 0; + class RefFwdRange + { + int[] impl; + + @safe nothrow: + + this(int[] data) { impl = data; } + @property bool empty() { return impl.empty; } + @property auto ref front() { return impl.front; } + void popFront() + { + impl.popFront(); + popCount++; + } + @property auto save() { return new RefFwdRange(impl); } + } + static assert(isForwardRange!RefFwdRange); + + auto testdata = new RefFwdRange([1, 3, 5, 2, 4, 7, 6, 8, 9]); + auto groups = testdata.chunkBy!((a,b) => (a % 2) == (b % 2)); + auto outerSave1 = groups.save; + + // Sanity test + assert(groups.equal!equal([[1, 3, 5], [2, 4], [7], [6, 8], [9]])); + assert(groups.empty); + + // Performance test for single-traversal use case: popFront should not have + // been called more times than there are elements if we traversed the + // segmented range exactly once. + assert(popCount == 9); + + // Outer range .save test + groups = outerSave1.save; + assert(!groups.empty); + + // Inner range .save test + auto grp1 = groups.front.save; + auto grp1b = grp1.save; + assert(grp1b.equal([1, 3, 5])); + assert(grp1.save.equal([1, 3, 5])); + + // Inner range should remain consistent after outer range has moved on. + groups.popFront(); + assert(grp1.save.equal([1, 3, 5])); + + // Inner range should not be affected by subsequent inner ranges. + assert(groups.front.equal([2, 4])); + assert(grp1.save.equal([1, 3, 5])); +} + +/** + * Chunks an input range into subranges of equivalent adjacent elements. + * In other languages this is often called `partitionBy`, `groupBy` + * or `sliceWhen`. + * + * Equivalence is defined by the predicate $(D pred), which can be either + * binary, which is passed to $(REF binaryFun, std,functional), or unary, which is + * passed to $(REF unaryFun, std,functional). In the binary form, two _range elements + * $(D a) and $(D b) are considered equivalent if $(D pred(a,b)) is true. In + * unary form, two elements are considered equivalent if $(D pred(a) == pred(b)) + * is true. + * + * This predicate must be an equivalence relation, that is, it must be + * reflexive ($(D pred(x,x)) is always true), symmetric + * ($(D pred(x,y) == pred(y,x))), and transitive ($(D pred(x,y) && pred(y,z)) + * implies $(D pred(x,z))). If this is not the case, the range returned by + * chunkBy may assert at runtime or behave erratically. + * + * Params: + * pred = Predicate for determining equivalence. + * r = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to be chunked. + * + * Returns: With a binary predicate, a range of ranges is returned in which + * all elements in a given subrange are equivalent under the given predicate. + * With a unary predicate, a range of tuples is returned, with the tuple + * consisting of the result of the unary predicate for each subrange, and the + * subrange itself. + * + * Notes: + * + * Equivalent elements separated by an intervening non-equivalent element will + * appear in separate subranges; this function only considers adjacent + * equivalence. Elements in the subranges will always appear in the same order + * they appear in the original range. + * + * See_also: + * $(LREF group), which collapses adjacent equivalent elements into a single + * element. + */ +auto chunkBy(alias pred, Range)(Range r) +if (isInputRange!Range) +{ + return ChunkByImpl!(pred, Range)(r); +} + +/// Showing usage with binary predicate: +/*FIXME: @safe*/ @system unittest +{ + import std.algorithm.comparison : equal; + + // Grouping by particular attribute of each element: + auto data = [ + [1, 1], + [1, 2], + [2, 2], + [2, 3] + ]; + + auto r1 = data.chunkBy!((a,b) => a[0] == b[0]); + assert(r1.equal!equal([ + [[1, 1], [1, 2]], + [[2, 2], [2, 3]] + ])); + + auto r2 = data.chunkBy!((a,b) => a[1] == b[1]); + assert(r2.equal!equal([ + [[1, 1]], + [[1, 2], [2, 2]], + [[2, 3]] + ])); +} + +version (none) // this example requires support for non-equivalence relations +@safe unittest +{ + // Grouping by maximum adjacent difference: + import std.math : abs; + auto r3 = [1, 3, 2, 5, 4, 9, 10].chunkBy!((a, b) => abs(a-b) < 3); + assert(r3.equal!equal([ + [1, 3, 2], + [5, 4], + [9, 10] + ])); + +} + +/// Showing usage with unary predicate: +/* FIXME: pure @safe nothrow*/ @system unittest +{ + import std.algorithm.comparison : equal; + import std.range.primitives; + import std.typecons : tuple; + + // Grouping by particular attribute of each element: + auto range = + [ + [1, 1], + [1, 1], + [1, 2], + [2, 2], + [2, 3], + [2, 3], + [3, 3] + ]; + + auto byX = chunkBy!(a => a[0])(range); + auto expected1 = + [ + tuple(1, [[1, 1], [1, 1], [1, 2]]), + tuple(2, [[2, 2], [2, 3], [2, 3]]), + tuple(3, [[3, 3]]) + ]; + foreach (e; byX) + { + assert(!expected1.empty); + assert(e[0] == expected1.front[0]); + assert(e[1].equal(expected1.front[1])); + expected1.popFront(); + } + + auto byY = chunkBy!(a => a[1])(range); + auto expected2 = + [ + tuple(1, [[1, 1], [1, 1]]), + tuple(2, [[1, 2], [2, 2]]), + tuple(3, [[2, 3], [2, 3], [3, 3]]) + ]; + foreach (e; byY) + { + assert(!expected2.empty); + assert(e[0] == expected2.front[0]); + assert(e[1].equal(expected2.front[1])); + expected2.popFront(); + } +} + +/*FIXME: pure @safe nothrow*/ @system unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : tuple; + + struct Item { int x, y; } + + // Force R to have only an input range API with reference semantics, so + // that we're not unknowingly making use of array semantics outside of the + // range API. + class RefInputRange(R) + { + R data; + this(R _data) pure @safe nothrow { data = _data; } + @property bool empty() pure @safe nothrow { return data.empty; } + @property auto front() pure @safe nothrow { return data.front; } + void popFront() pure @safe nothrow { data.popFront(); } + } + auto refInputRange(R)(R range) { return new RefInputRange!R(range); } + + { + auto arr = [ Item(1,2), Item(1,3), Item(2,3) ]; + static assert(isForwardRange!(typeof(arr))); + + auto byX = chunkBy!(a => a.x)(arr); + static assert(isForwardRange!(typeof(byX))); + + auto byX_subrange1 = byX.front[1].save; + auto byX_subrange2 = byX.front[1].save; + static assert(isForwardRange!(typeof(byX_subrange1))); + static assert(isForwardRange!(typeof(byX_subrange2))); + + byX.popFront(); + assert(byX_subrange1.equal([ Item(1,2), Item(1,3) ])); + byX_subrange1.popFront(); + assert(byX_subrange1.equal([ Item(1,3) ])); + assert(byX_subrange2.equal([ Item(1,2), Item(1,3) ])); + + auto byY = chunkBy!(a => a.y)(arr); + static assert(isForwardRange!(typeof(byY))); + + auto byY2 = byY.save; + static assert(is(typeof(byY) == typeof(byY2))); + byY.popFront(); + assert(byY.front[0] == 3); + assert(byY.front[1].equal([ Item(1,3), Item(2,3) ])); + assert(byY2.front[0] == 2); + assert(byY2.front[1].equal([ Item(1,2) ])); + } + + // Test non-forward input ranges. + { + auto range = refInputRange([ Item(1,1), Item(1,2), Item(2,2) ]); + auto byX = chunkBy!(a => a.x)(range); + assert(byX.front[0] == 1); + assert(byX.front[1].equal([ Item(1,1), Item(1,2) ])); + byX.popFront(); + assert(byX.front[0] == 2); + assert(byX.front[1].equal([ Item(2,2) ])); + byX.popFront(); + assert(byX.empty); + assert(range.empty); + + range = refInputRange([ Item(1,1), Item(1,2), Item(2,2) ]); + auto byY = chunkBy!(a => a.y)(range); + assert(byY.front[0] == 1); + assert(byY.front[1].equal([ Item(1,1) ])); + byY.popFront(); + assert(byY.front[0] == 2); + assert(byY.front[1].equal([ Item(1,2), Item(2,2) ])); + byY.popFront(); + assert(byY.empty); + assert(range.empty); + } +} + +// Issue 13595 +version (none) // This requires support for non-equivalence relations +@system unittest +{ + import std.algorithm.comparison : equal; + auto r = [1, 2, 3, 4, 5, 6, 7, 8, 9].chunkBy!((x, y) => ((x*y) % 3) == 0); + assert(r.equal!equal([ + [1], + [2, 3, 4], + [5, 6, 7], + [8, 9] + ])); +} + +// Issue 13805 +@system unittest +{ + [""].map!((s) => s).chunkBy!((x, y) => true); +} + +// joiner +/** +Lazily joins a range of ranges with a separator. The separator itself +is a range. If a separator is not provided, then the ranges are +joined directly without anything in between them (often called `flatten` +in other languages). + +Params: + r = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of input + ranges to be joined. + sep = A $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) of + element(s) to serve as separators in the joined range. + +Returns: +A range of elements in the joined range. This will be a forward range if +both outer and inner ranges of $(D RoR) are forward ranges; otherwise it will +be only an input range. + +See_also: +$(REF chain, std,range), which chains a sequence of ranges with compatible elements +into a single range. + */ +auto joiner(RoR, Separator)(RoR r, Separator sep) +if (isInputRange!RoR && isInputRange!(ElementType!RoR) + && isForwardRange!Separator + && is(ElementType!Separator : ElementType!(ElementType!RoR))) +{ + static struct Result + { + private RoR _items; + private ElementType!RoR _current; + private Separator _sep, _currentSep; + + // This is a mixin instead of a function for the following reason (as + // explained by Kenji Hara): "This is necessary from 2.061. If a + // struct has a nested struct member, it must be directly initialized + // in its constructor to avoid leaving undefined state. If you change + // setItem to a function, the initialization of _current field is + // wrapped into private member function, then compiler could not detect + // that is correctly initialized while constructing. To avoid the + // compiler error check, string mixin is used." + private enum setItem = + q{ + if (!_items.empty) + { + // If we're exporting .save, we must not consume any of the + // subranges, since RoR.save does not guarantee that the states + // of the subranges are also saved. + static if (isForwardRange!RoR && + isForwardRange!(ElementType!RoR)) + _current = _items.front.save; + else + _current = _items.front; + } + }; + + private void useSeparator() + { + // Separator must always come after an item. + assert(_currentSep.empty && !_items.empty, + "joiner: internal error"); + _items.popFront(); + + // If there are no more items, we're done, since separators are not + // terminators. + if (_items.empty) return; + + if (_sep.empty) + { + // Advance to the next range in the + // input + while (_items.front.empty) + { + _items.popFront(); + if (_items.empty) return; + } + mixin(setItem); + } + else + { + _currentSep = _sep.save; + assert(!_currentSep.empty); + } + } + + private enum useItem = + q{ + // FIXME: this will crash if either _currentSep or _current are + // class objects, because .init is null when the ctor invokes this + // mixin. + //assert(_currentSep.empty && _current.empty, + // "joiner: internal error"); + + // Use the input + if (_items.empty) return; + mixin(setItem); + if (_current.empty) + { + // No data in the current item - toggle to use the separator + useSeparator(); + } + }; + + this(RoR items, Separator sep) + { + _items = items; + _sep = sep; + + //mixin(useItem); // _current should be initialized in place + if (_items.empty) + _current = _current.init; // set invalid state + else + { + // If we're exporting .save, we must not consume any of the + // subranges, since RoR.save does not guarantee that the states + // of the subranges are also saved. + static if (isForwardRange!RoR && + isForwardRange!(ElementType!RoR)) + _current = _items.front.save; + else + _current = _items.front; + + if (_current.empty) + { + // No data in the current item - toggle to use the separator + useSeparator(); + } + } + } + + @property auto empty() + { + return _items.empty; + } + + @property ElementType!(ElementType!RoR) front() + { + if (!_currentSep.empty) return _currentSep.front; + assert(!_current.empty, "Attempting to fetch the front of an empty joiner."); + return _current.front; + } + + void popFront() + { + assert(!_items.empty, "Attempting to popFront an empty joiner."); + // Using separator? + if (!_currentSep.empty) + { + _currentSep.popFront(); + if (!_currentSep.empty) return; + mixin(useItem); + } + else + { + // we're using the range + _current.popFront(); + if (!_current.empty) return; + useSeparator(); + } + } + + static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR)) + { + @property auto save() + { + Result copy = this; + copy._items = _items.save; + copy._current = _current.save; + copy._sep = _sep.save; + copy._currentSep = _currentSep.save; + return copy; + } + } + } + return Result(r, sep); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.conv : text; + + assert(["abc", "def"].joiner.equal("abcdef")); + assert(["Mary", "has", "a", "little", "lamb"] + .joiner("...") + .equal("Mary...has...a...little...lamb")); + assert(["", "abc"].joiner("xyz").equal("xyzabc")); + assert([""].joiner("xyz").equal("")); + assert(["", ""].joiner("xyz").equal("xyz")); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + import std.range.interfaces; + import std.range.primitives; + // joiner() should work for non-forward ranges too. + auto r = inputRangeObject(["abc", "def"]); + assert(equal(joiner(r, "xyz"), "abcxyzdef")); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + import std.range; + + // Related to issue 8061 + auto r = joiner([ + inputRangeObject("abc"), + inputRangeObject("def"), + ], "-*-"); + + assert(equal(r, "abc-*-def")); + + // Test case where separator is specified but is empty. + auto s = joiner([ + inputRangeObject("abc"), + inputRangeObject("def"), + ], ""); + + assert(equal(s, "abcdef")); + + // Test empty separator with some empty elements + auto t = joiner([ + inputRangeObject("abc"), + inputRangeObject(""), + inputRangeObject("def"), + inputRangeObject(""), + ], ""); + + assert(equal(t, "abcdef")); + + // Test empty elements with non-empty separator + auto u = joiner([ + inputRangeObject(""), + inputRangeObject("abc"), + inputRangeObject(""), + inputRangeObject("def"), + inputRangeObject(""), + ], "+-"); + + assert(equal(u, "+-abc+-+-def+-")); + + // Issue 13441: only(x) as separator + string[][] lines = [null]; + lines + .joiner(only("b")) + .array(); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + // Transience correctness test + struct TransientRange + { + @safe: + int[][] src; + int[] buf; + + this(int[][] _src) + { + src = _src; + buf.length = 100; + } + @property bool empty() { return src.empty; } + @property int[] front() + { + assert(src.front.length <= buf.length); + buf[0 .. src.front.length] = src.front[0..$]; + return buf[0 .. src.front.length]; + } + void popFront() { src.popFront(); } + } + + // Test embedded empty elements + auto tr1 = TransientRange([[], [1,2,3], [], [4]]); + assert(equal(joiner(tr1, [0]), [0,1,2,3,0,0,4])); + + // Test trailing empty elements + auto tr2 = TransientRange([[], [1,2,3], []]); + assert(equal(joiner(tr2, [0]), [0,1,2,3,0])); + + // Test no empty elements + auto tr3 = TransientRange([[1,2], [3,4]]); + assert(equal(joiner(tr3, [0,1]), [1,2,0,1,3,4])); + + // Test consecutive empty elements + auto tr4 = TransientRange([[1,2], [], [], [], [3,4]]); + assert(equal(joiner(tr4, [0,1]), [1,2,0,1,0,1,0,1,0,1,3,4])); + + // Test consecutive trailing empty elements + auto tr5 = TransientRange([[1,2], [3,4], [], []]); + assert(equal(joiner(tr5, [0,1]), [1,2,0,1,3,4,0,1,0,1])); +} + +@safe unittest +{ + static assert(isInputRange!(typeof(joiner([""], "")))); + static assert(isForwardRange!(typeof(joiner([""], "")))); +} + +/// Ditto +auto joiner(RoR)(RoR r) +if (isInputRange!RoR && isInputRange!(ElementType!RoR)) +{ + static struct Result + { + private: + RoR _items; + ElementType!RoR _current; + enum prepare = + q{ + // Skip over empty subranges. + if (_items.empty) return; + while (_items.front.empty) + { + _items.popFront(); + if (_items.empty) return; + } + // We cannot export .save method unless we ensure subranges are not + // consumed when a .save'd copy of ourselves is iterated over. So + // we need to .save each subrange we traverse. + static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR)) + _current = _items.front.save; + else + _current = _items.front; + }; + this(RoR items, ElementType!RoR current) + { + _items = items; + _current = current; + } + public: + this(RoR r) + { + _items = r; + //mixin(prepare); // _current should be initialized in place + + // Skip over empty subranges. + while (!_items.empty && _items.front.empty) + _items.popFront(); + + if (_items.empty) + _current = _current.init; // set invalid state + else + { + // We cannot export .save method unless we ensure subranges are not + // consumed when a .save'd copy of ourselves is iterated over. So + // we need to .save each subrange we traverse. + static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR)) + _current = _items.front.save; + else + _current = _items.front; + } + } + static if (isInfinite!RoR) + { + enum bool empty = false; + } + else + { + @property auto empty() + { + return _items.empty; + } + } + @property auto ref front() + { + assert(!empty, "Attempting to fetch the front of an empty joiner."); + return _current.front; + } + void popFront() + { + assert(!_current.empty, "Attempting to popFront an empty joiner."); + _current.popFront(); + if (_current.empty) + { + assert(!_items.empty); + _items.popFront(); + mixin(prepare); + } + } + static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR)) + { + @property auto save() + { + return Result(_items.save, _current.save); + } + } + + static if (hasAssignableElements!(ElementType!RoR)) + { + @property void front(ElementType!(ElementType!RoR) element) + { + assert(!empty, "Attempting to assign to front of an empty joiner."); + _current.front = element; + } + + @property void front(ref ElementType!(ElementType!RoR) element) + { + assert(!empty, "Attempting to assign to front of an empty joiner."); + _current.front = element; + } + } + } + return Result(r); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range.interfaces : inputRangeObject; + import std.range : repeat; + + static assert(isInputRange!(typeof(joiner([""])))); + static assert(isForwardRange!(typeof(joiner([""])))); + assert(equal(joiner([""]), "")); + assert(equal(joiner(["", ""]), "")); + assert(equal(joiner(["", "abc"]), "abc")); + assert(equal(joiner(["abc", ""]), "abc")); + assert(equal(joiner(["abc", "def"]), "abcdef")); + assert(equal(joiner(["Mary", "has", "a", "little", "lamb"]), + "Maryhasalittlelamb")); + assert(equal(joiner(repeat("abc", 3)), "abcabcabc")); + + // joiner allows in-place mutation! + auto a = [ [1, 2, 3], [42, 43] ]; + auto j = joiner(a); + j.front = 44; + assert(a == [ [44, 2, 3], [42, 43] ]); + assert(equal(j, [44, 2, 3, 42, 43])); +} + + +@system unittest +{ + import std.algorithm.comparison : equal; + import std.range.interfaces : inputRangeObject; + + // bugzilla 8240 + assert(equal(joiner([inputRangeObject("")]), "")); + + // issue 8792 + auto b = [[1], [2], [3]]; + auto jb = joiner(b); + auto js = jb.save; + assert(equal(jb, js)); + + auto js2 = jb.save; + jb.popFront(); + assert(!equal(jb, js)); + assert(equal(js2, js)); + js.popFront(); + assert(equal(jb, js)); + assert(!equal(js2, js)); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + struct TransientRange + { + @safe: + int[] _buf; + int[][] _values; + this(int[][] values) + { + _values = values; + _buf = new int[128]; + } + @property bool empty() + { + return _values.length == 0; + } + @property auto front() + { + foreach (i; 0 .. _values.front.length) + { + _buf[i] = _values[0][i]; + } + return _buf[0 .. _values.front.length]; + } + void popFront() + { + _values = _values[1 .. $]; + } + } + + auto rr = TransientRange([[1,2], [3,4,5], [], [6,7]]); + + // Can't use array() or equal() directly because they fail with transient + // .front. + int[] result; + foreach (c; rr.joiner()) + { + result ~= c; + } + + assert(equal(result, [1,2,3,4,5,6,7])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.internal : algoFormat; + + struct TransientRange + { + @safe: + dchar[] _buf; + dstring[] _values; + this(dstring[] values) + { + _buf.length = 128; + _values = values; + } + @property bool empty() + { + return _values.length == 0; + } + @property auto front() + { + foreach (i; 0 .. _values.front.length) + { + _buf[i] = _values[0][i]; + } + return _buf[0 .. _values.front.length]; + } + void popFront() + { + _values = _values[1 .. $]; + } + } + + auto rr = TransientRange(["abc"d, "12"d, "def"d, "34"d]); + + // Can't use array() or equal() directly because they fail with transient + // .front. + dchar[] result; + foreach (c; rr.joiner()) + { + result ~= c; + } + + import std.conv : to; + assert(equal(result, "abc12def34"d), + //Convert to string for assert's message + to!string("Unexpected result: '%s'"d.algoFormat(result))); +} + +// Issue 8061 +@system unittest +{ + import std.conv : to; + import std.range.interfaces; + + auto r = joiner([inputRangeObject("ab"), inputRangeObject("cd")]); + assert(isForwardRange!(typeof(r))); + + auto str = to!string(r); + assert(str == "abcd"); +} + +@safe unittest +{ + import std.range : repeat; + + class AssignableRange + { + @safe: + int element; + @property int front() + { + return element; + } + + enum empty = false; + + void popFront() + { + } + + @property void front(int newValue) + { + element = newValue; + } + } + + static assert(isInputRange!AssignableRange); + static assert(is(ElementType!AssignableRange == int)); + static assert(hasAssignableElements!AssignableRange); + static assert(!hasLvalueElements!AssignableRange); + + auto range = new AssignableRange(); + assert(range.element == 0); + + auto joined = joiner(repeat(range)); + joined.front = 5; + assert(range.element == 5); + assert(joined.front == 5); + + joined.popFront; + int byRef = 7; + joined.front = byRef; + assert(range.element == byRef); + assert(joined.front == byRef); +} + +/++ +Implements the homonym function (also known as $(D accumulate), $(D +compress), $(D inject), or $(D foldl)) present in various programming +languages of functional flavor. There is also $(LREF fold) which does +the same thing but with the opposite parameter order. +The call $(D reduce!(fun)(seed, range)) first assigns $(D seed) to +an internal variable $(D result), also called the accumulator. +Then, for each element $(D x) in $(D range), $(D result = fun(result, x)) +gets evaluated. Finally, $(D result) is returned. +The one-argument version $(D reduce!(fun)(range)) +works similarly, but it uses the first element of the range as the +seed (the range must be non-empty). + +Returns: + the accumulated $(D result) + +Params: + fun = one or more functions + +See_Also: + $(HTTP en.wikipedia.org/wiki/Fold_(higher-order_function), Fold (higher-order function)) + + $(LREF fold) is functionally equivalent to $(LREF reduce) with the argument order reversed, + and without the need to use $(LREF tuple) for multiple seeds. This makes it easier + to use in UFCS chains. + + $(LREF sum) is similar to $(D reduce!((a, b) => a + b)) that offers + pairwise summing of floating point numbers. ++/ +template reduce(fun...) +if (fun.length >= 1) +{ + import std.meta : staticMap; + + alias binfuns = staticMap!(binaryFun, fun); + static if (fun.length > 1) + import std.typecons : tuple, isTuple; + + /++ + No-seed version. The first element of $(D r) is used as the seed's value. + + For each function $(D f) in $(D fun), the corresponding + seed type $(D S) is $(D Unqual!(typeof(f(e, e)))), where $(D e) is an + element of $(D r): $(D ElementType!R) for ranges, + and $(D ForeachType!R) otherwise. + + Once S has been determined, then $(D S s = e;) and $(D s = f(s, e);) + must both be legal. + + If $(D r) is empty, an $(D Exception) is thrown. + + Params: + r = an iterable value as defined by $(D isIterable) + + Returns: + the final result of the accumulator applied to the iterable + +/ + auto reduce(R)(R r) + if (isIterable!R) + { + import std.exception : enforce; + alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R); + alias Args = staticMap!(ReduceSeedType!E, binfuns); + + static if (isInputRange!R) + { + enforce(!r.empty, "Cannot reduce an empty input range w/o an explicit seed value."); + Args result = r.front; + r.popFront(); + return reduceImpl!false(r, result); + } + else + { + auto result = Args.init; + return reduceImpl!true(r, result); + } + } + + /++ + Seed version. The seed should be a single value if $(D fun) is a + single function. If $(D fun) is multiple functions, then $(D seed) + should be a $(REF Tuple, std,typecons), with one field per function in $(D f). + + For convenience, if the seed is const, or has qualified fields, then + $(D reduce) will operate on an unqualified copy. If this happens + then the returned type will not perfectly match $(D S). + + Use $(D fold) instead of $(D reduce) to use the seed version in a UFCS chain. + + Params: + seed = the initial value of the accumulator + r = an iterable value as defined by $(D isIterable) + + Returns: + the final result of the accumulator applied to the iterable + +/ + auto reduce(S, R)(S seed, R r) + if (isIterable!R) + { + static if (fun.length == 1) + return reducePreImpl(r, seed); + else + { + import std.algorithm.internal : algoFormat; + static assert(isTuple!S, algoFormat("Seed %s should be a Tuple", S.stringof)); + return reducePreImpl(r, seed.expand); + } + } + + private auto reducePreImpl(R, Args...)(R r, ref Args args) + { + alias Result = staticMap!(Unqual, Args); + static if (is(Result == Args)) + alias result = args; + else + Result result = args; + return reduceImpl!false(r, result); + } + + private auto reduceImpl(bool mustInitialize, R, Args...)(R r, ref Args args) + if (isIterable!R) + { + import std.algorithm.internal : algoFormat; + static assert(Args.length == fun.length, + algoFormat("Seed %s does not have the correct amount of fields (should be %s)", Args.stringof, fun.length)); + alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R); + + static if (mustInitialize) bool initialized = false; + foreach (/+auto ref+/ E e; r) // @@@4707@@@ + { + foreach (i, f; binfuns) + { + static assert(!is(typeof(f(args[i], e))) || is(typeof(args[i] = f(args[i], e))), + algoFormat( + "Incompatible function/seed/element: %s/%s/%s", + fullyQualifiedName!f, + Args[i].stringof, + E.stringof + ) + ); + } + + static if (mustInitialize) if (initialized == false) + { + import std.conv : emplaceRef; + foreach (i, f; binfuns) + emplaceRef!(Args[i])(args[i], e); + initialized = true; + continue; + } + + foreach (i, f; binfuns) + args[i] = f(args[i], e); + } + static if (mustInitialize) + if (!initialized) + throw new Exception("Cannot reduce an empty iterable w/o an explicit seed value."); + + static if (Args.length == 1) + return args[0]; + else + return tuple(args); + } +} + +/** +Many aggregate range operations turn out to be solved with $(D reduce) +quickly and easily. The example below illustrates $(D reduce)'s +remarkable power and flexibility. +*/ +@safe unittest +{ + import std.algorithm.comparison : max, min; + import std.math : approxEqual; + import std.range; + + int[] arr = [ 1, 2, 3, 4, 5 ]; + // Sum all elements + auto sum = reduce!((a,b) => a + b)(0, arr); + assert(sum == 15); + + // Sum again, using a string predicate with "a" and "b" + sum = reduce!"a + b"(0, arr); + assert(sum == 15); + + // Compute the maximum of all elements + auto largest = reduce!(max)(arr); + assert(largest == 5); + + // Max again, but with Uniform Function Call Syntax (UFCS) + largest = arr.reduce!(max); + assert(largest == 5); + + // Compute the number of odd elements + auto odds = reduce!((a,b) => a + (b & 1))(0, arr); + assert(odds == 3); + + // Compute the sum of squares + auto ssquares = reduce!((a,b) => a + b * b)(0, arr); + assert(ssquares == 55); + + // Chain multiple ranges into seed + int[] a = [ 3, 4 ]; + int[] b = [ 100 ]; + auto r = reduce!("a + b")(chain(a, b)); + assert(r == 107); + + // Mixing convertible types is fair game, too + double[] c = [ 2.5, 3.0 ]; + auto r1 = reduce!("a + b")(chain(a, b, c)); + assert(approxEqual(r1, 112.5)); + + // To minimize nesting of parentheses, Uniform Function Call Syntax can be used + auto r2 = chain(a, b, c).reduce!("a + b"); + assert(approxEqual(r2, 112.5)); +} + +/** +Sometimes it is very useful to compute multiple aggregates in one pass. +One advantage is that the computation is faster because the looping overhead +is shared. That's why $(D reduce) accepts multiple functions. +If two or more functions are passed, $(D reduce) returns a +$(REF Tuple, std,typecons) object with one member per passed-in function. +The number of seeds must be correspondingly increased. +*/ +@safe unittest +{ + import std.algorithm.comparison : max, min; + import std.math : approxEqual, sqrt; + import std.typecons : tuple, Tuple; + + double[] a = [ 3.0, 4, 7, 11, 3, 2, 5 ]; + // Compute minimum and maximum in one pass + auto r = reduce!(min, max)(a); + // The type of r is Tuple!(int, int) + assert(approxEqual(r[0], 2)); // minimum + assert(approxEqual(r[1], 11)); // maximum + + // Compute sum and sum of squares in one pass + r = reduce!("a + b", "a + b * b")(tuple(0.0, 0.0), a); + assert(approxEqual(r[0], 35)); // sum + assert(approxEqual(r[1], 233)); // sum of squares + // Compute average and standard deviation from the above + auto avg = r[0] / a.length; + assert(avg == 5); + auto stdev = sqrt(r[1] / a.length - avg * avg); + assert(cast(int) stdev == 2); +} + +@safe unittest +{ + import std.algorithm.comparison : max, min; + import std.range : chain; + import std.typecons : tuple, Tuple; + + double[] a = [ 3, 4 ]; + auto r = reduce!("a + b")(0.0, a); + assert(r == 7); + r = reduce!("a + b")(a); + assert(r == 7); + r = reduce!(min)(a); + assert(r == 3); + double[] b = [ 100 ]; + auto r1 = reduce!("a + b")(chain(a, b)); + assert(r1 == 107); + + // two funs + auto r2 = reduce!("a + b", "a - b")(tuple(0.0, 0.0), a); + assert(r2[0] == 7 && r2[1] == -7); + auto r3 = reduce!("a + b", "a - b")(a); + assert(r3[0] == 7 && r3[1] == -1); + + a = [ 1, 2, 3, 4, 5 ]; + // Stringize with commas + string rep = reduce!("a ~ `, ` ~ to!(string)(b)")("", a); + assert(rep[2 .. $] == "1, 2, 3, 4, 5", "["~rep[2 .. $]~"]"); +} + +@system unittest +{ + import std.algorithm.comparison : max, min; + import std.exception : assertThrown; + import std.range : iota; + import std.typecons : tuple, Tuple; + + // Test the opApply case. + static struct OpApply + { + bool actEmpty; + + int opApply(scope int delegate(ref int) dg) + { + int res; + if (actEmpty) return res; + + foreach (i; 0 .. 100) + { + res = dg(i); + if (res) break; + } + return res; + } + } + + OpApply oa; + auto hundredSum = reduce!"a + b"(iota(100)); + assert(reduce!"a + b"(5, oa) == hundredSum + 5); + assert(reduce!"a + b"(oa) == hundredSum); + assert(reduce!("a + b", max)(oa) == tuple(hundredSum, 99)); + assert(reduce!("a + b", max)(tuple(5, 0), oa) == tuple(hundredSum + 5, 99)); + + // Test for throwing on empty range plus no seed. + assertThrown(reduce!"a + b"([1, 2][0 .. 0])); + + oa.actEmpty = true; + assertThrown(reduce!"a + b"(oa)); +} + +@safe unittest +{ + const float a = 0.0; + const float[] b = [ 1.2, 3, 3.3 ]; + float[] c = [ 1.2, 3, 3.3 ]; + auto r = reduce!"a + b"(a, b); + r = reduce!"a + b"(a, c); + assert(r == 7.5); +} + +@safe unittest +{ + // Issue #10408 - Two-function reduce of a const array. + import std.algorithm.comparison : max, min; + import std.typecons : tuple, Tuple; + + const numbers = [10, 30, 20]; + immutable m = reduce!(min)(numbers); + assert(m == 10); + immutable minmax = reduce!(min, max)(numbers); + assert(minmax == tuple(10, 30)); +} + +@safe unittest +{ + //10709 + import std.typecons : tuple, Tuple; + + enum foo = "a + 0.5 * b"; + auto r = [0, 1, 2, 3]; + auto r1 = reduce!foo(r); + auto r2 = reduce!(foo, foo)(r); + assert(r1 == 3); + assert(r2 == tuple(3, 3)); +} + +@system unittest +{ + static struct OpApply + { + int opApply(int delegate(ref int) dg) + { + int[] a = [1, 2, 3]; + + int res = 0; + foreach (ref e; a) + { + res = dg(e); + if (res) break; + } + return res; + } + } + //test CTFE and functions with context + int fun(int a, int b) @safe {return a + b + 1;} + auto foo() + { + import std.algorithm.comparison : max; + import std.typecons : tuple, Tuple; + + auto a = reduce!(fun)([1, 2, 3]); + auto b = reduce!(fun, fun)([1, 2, 3]); + auto c = reduce!(fun)(0, [1, 2, 3]); + auto d = reduce!(fun, fun)(tuple(0, 0), [1, 2, 3]); + auto e = reduce!(fun)(0, OpApply()); + auto f = reduce!(fun, fun)(tuple(0, 0), OpApply()); + + return max(a, b.expand, c, d.expand, e, f.expand); + } + auto a = foo(); + assert(a == 9); + enum b = foo(); + assert(b == 9); +} + +@safe unittest +{ + import std.algorithm.comparison : max, min; + import std.typecons : tuple, Tuple; + + //http://forum.dlang.org/post/oghtttkopzjshsuflelk@forum.dlang.org + //Seed is tuple of const. + static auto minmaxElement(alias F = min, alias G = max, R)(in R range) + @safe pure nothrow + if (isInputRange!R) + { + return reduce!(F, G)(tuple(ElementType!R.max, + ElementType!R.min), range); + } + assert(minmaxElement([1, 2, 3]) == tuple(1, 3)); +} + +@safe unittest //12569 +{ + import std.algorithm.comparison : max, min; + import std.typecons : tuple; + dchar c = 'a'; + reduce!(min, max)(tuple(c, c), "hello"); // OK + static assert(!is(typeof(reduce!(min, max)(tuple(c), "hello")))); + static assert(!is(typeof(reduce!(min, max)(tuple(c, c, c), "hello")))); + + + //"Seed dchar should be a Tuple" + static assert(!is(typeof(reduce!(min, max)(c, "hello")))); + //"Seed (dchar) does not have the correct amount of fields (should be 2)" + static assert(!is(typeof(reduce!(min, max)(tuple(c), "hello")))); + //"Seed (dchar, dchar, dchar) does not have the correct amount of fields (should be 2)" + static assert(!is(typeof(reduce!(min, max)(tuple(c, c, c), "hello")))); + //"Incompatable function/seed/element: all(alias pred = "a")/int/dchar" + static assert(!is(typeof(reduce!all(1, "hello")))); + static assert(!is(typeof(reduce!(all, all)(tuple(1, 1), "hello")))); +} + +@safe unittest //13304 +{ + int[] data; + static assert(is(typeof(reduce!((a, b) => a + b)(data)))); + assert(data.length == 0); +} + +//Helper for Reduce +private template ReduceSeedType(E) +{ + static template ReduceSeedType(alias fun) + { + import std.algorithm.internal : algoFormat; + + alias ReduceSeedType = Unqual!(typeof(fun(lvalueOf!E, lvalueOf!E))); + + //Check the Seed type is useable. + ReduceSeedType s = ReduceSeedType.init; + static assert(is(typeof({ReduceSeedType s = lvalueOf!E;})) && + is(typeof(lvalueOf!ReduceSeedType = fun(lvalueOf!ReduceSeedType, lvalueOf!E))), + algoFormat( + "Unable to deduce an acceptable seed type for %s with element type %s.", + fullyQualifiedName!fun, + E.stringof + ) + ); + } +} + + +/++ +Implements the homonym function (also known as $(D accumulate), $(D +compress), $(D inject), or $(D foldl)) present in various programming +languages of functional flavor. The call $(D fold!(fun)(range, seed)) +first assigns $(D seed) to an internal variable $(D result), +also called the accumulator. Then, for each element $(D x) in $(D +range), $(D result = fun(result, x)) gets evaluated. Finally, $(D +result) is returned. The one-argument version $(D fold!(fun)(range)) +works similarly, but it uses the first element of the range as the +seed (the range must be non-empty). + +Returns: + the accumulated $(D result) + +See_Also: + $(HTTP en.wikipedia.org/wiki/Fold_(higher-order_function), Fold (higher-order function)) + + $(LREF sum) is similar to $(D fold!((a, b) => a + b)) that offers + precise summing of floating point numbers. + + This is functionally equivalent to $(LREF reduce) with the argument order reversed, + and without the need to use $(LREF tuple) for multiple seeds. ++/ +template fold(fun...) +if (fun.length >= 1) +{ + auto fold(R, S...)(R r, S seed) + { + static if (S.length < 2) + { + return reduce!fun(seed, r); + } + else + { + import std.typecons : tuple; + return reduce!fun(tuple(seed), r); + } + } +} + +/// +@safe pure unittest +{ + immutable arr = [1, 2, 3, 4, 5]; + + // Sum all elements + assert(arr.fold!((a, b) => a + b) == 15); + + // Sum all elements with explicit seed + assert(arr.fold!((a, b) => a + b)(6) == 21); + + import std.algorithm.comparison : min, max; + import std.typecons : tuple; + + // Compute minimum and maximum at the same time + assert(arr.fold!(min, max) == tuple(1, 5)); + + // Compute minimum and maximum at the same time with seeds + assert(arr.fold!(min, max)(0, 7) == tuple(0, 7)); + + // Can be used in a UFCS chain + assert(arr.map!(a => a + 1).fold!((a, b) => a + b) == 20); + + // Return the last element of any range + assert(arr.fold!((a, b) => b) == 5); +} + +@safe @nogc pure nothrow unittest +{ + int[1] arr; + static assert(!is(typeof(arr.fold!()))); + static assert(!is(typeof(arr.fold!(a => a)))); + static assert(is(typeof(arr.fold!((a, b) => a)))); + static assert(is(typeof(arr.fold!((a, b) => a)(1)))); + assert(arr.length == 1); +} + +/++ +Similar to `fold`, but returns a range containing the successive reduced values. +The call $(D cumulativeFold!(fun)(range, seed)) first assigns `seed` to an +internal variable `result`, also called the accumulator. +The returned range contains the values $(D result = fun(result, x)) lazily +evaluated for each element `x` in `range`. Finally, the last element has the +same value as $(D fold!(fun)(seed, range)). +The one-argument version $(D cumulativeFold!(fun)(range)) works similarly, but +it returns the first element unchanged and uses it as seed for the next +elements. +This function is also known as + $(HTTP en.cppreference.com/w/cpp/algorithm/partial_sum, partial_sum), + $(HTTP docs.python.org/3/library/itertools.html#itertools.accumulate, accumulate), + $(HTTP hackage.haskell.org/package/base-4.8.2.0/docs/Prelude.html#v:scanl, scan), + $(HTTP mathworld.wolfram.com/CumulativeSum.html, Cumulative Sum). + +Params: + fun = one or more functions to use as fold operation + +Returns: + The function returns a range containing the consecutive reduced values. If + there is more than one `fun`, the element type will be $(REF Tuple, + std,typecons) containing one element for each `fun`. + +See_Also: + $(HTTP en.wikipedia.org/wiki/Prefix_sum, Prefix Sum) ++/ +template cumulativeFold(fun...) +if (fun.length >= 1) +{ + import std.meta : staticMap; + private alias binfuns = staticMap!(binaryFun, fun); + + /++ + No-seed version. The first element of `r` is used as the seed's value. + For each function `f` in `fun`, the corresponding seed type `S` is + $(D Unqual!(typeof(f(e, e)))), where `e` is an element of `r`: + `ElementType!R`. + Once `S` has been determined, then $(D S s = e;) and $(D s = f(s, e);) must + both be legal. + + Params: + range = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + Returns: + a range containing the consecutive reduced values. + +/ + auto cumulativeFold(R)(R range) + if (isInputRange!(Unqual!R)) + { + return cumulativeFoldImpl(range); + } + + /++ + Seed version. The seed should be a single value if `fun` is a single + function. If `fun` is multiple functions, then `seed` should be a + $(REF Tuple, std,typecons), with one field per function in `f`. + For convenience, if the seed is `const`, or has qualified fields, then + `cumulativeFold` will operate on an unqualified copy. If this happens + then the returned type will not perfectly match `S`. + + Params: + range = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + seed = the initial value of the accumulator + Returns: + a range containing the consecutive reduced values. + +/ + auto cumulativeFold(R, S)(R range, S seed) + if (isInputRange!(Unqual!R)) + { + static if (fun.length == 1) + return cumulativeFoldImpl(range, seed); + else + return cumulativeFoldImpl(range, seed.expand); + } + + private auto cumulativeFoldImpl(R, Args...)(R range, ref Args args) + { + import std.algorithm.internal : algoFormat; + + static assert(Args.length == 0 || Args.length == fun.length, + algoFormat("Seed %s does not have the correct amount of fields (should be %s)", + Args.stringof, fun.length)); + + static if (args.length) + alias State = staticMap!(Unqual, Args); + else + alias State = staticMap!(ReduceSeedType!(ElementType!R), binfuns); + + foreach (i, f; binfuns) + { + static assert(!__traits(compiles, f(args[i], e)) || __traits(compiles, + { args[i] = f(args[i], e); }()), + algoFormat("Incompatible function/seed/element: %s/%s/%s", + fullyQualifiedName!f, Args[i].stringof, E.stringof)); + } + + static struct Result + { + private: + R source; + State state; + + this(R range, ref Args args) + { + source = range; + if (source.empty) + return; + + foreach (i, f; binfuns) + { + static if (args.length) + state[i] = f(args[i], source.front); + else + state[i] = source.front; + } + } + + public: + @property bool empty() + { + return source.empty; + } + + @property auto front() + { + assert(!empty, "Attempting to fetch the front of an empty cumulativeFold."); + static if (fun.length > 1) + { + import std.typecons : tuple; + return tuple(state); + } + else + { + return state[0]; + } + } + + void popFront() + { + assert(!empty, "Attempting to popFront an empty cumulativeFold."); + source.popFront; + + if (source.empty) + return; + + foreach (i, f; binfuns) + state[i] = f(state[i], source.front); + } + + static if (isForwardRange!R) + { + @property auto save() + { + auto result = this; + result.source = source.save; + return result; + } + } + + static if (hasLength!R) + { + @property size_t length() + { + return source.length; + } + } + } + + return Result(range, args); + } +} + +/// +@safe unittest +{ + import std.algorithm.comparison : max, min; + import std.array : array; + import std.math : approxEqual; + import std.range : chain; + + int[] arr = [1, 2, 3, 4, 5]; + // Partial sum of all elements + auto sum = cumulativeFold!((a, b) => a + b)(arr, 0); + assert(sum.array == [1, 3, 6, 10, 15]); + + // Partial sum again, using a string predicate with "a" and "b" + auto sum2 = cumulativeFold!"a + b"(arr, 0); + assert(sum2.array == [1, 3, 6, 10, 15]); + + // Compute the partial maximum of all elements + auto largest = cumulativeFold!max(arr); + assert(largest.array == [1, 2, 3, 4, 5]); + + // Partial max again, but with Uniform Function Call Syntax (UFCS) + largest = arr.cumulativeFold!max; + assert(largest.array == [1, 2, 3, 4, 5]); + + // Partial count of odd elements + auto odds = arr.cumulativeFold!((a, b) => a + (b & 1))(0); + assert(odds.array == [1, 1, 2, 2, 3]); + + // Compute the partial sum of squares + auto ssquares = arr.cumulativeFold!((a, b) => a + b * b)(0); + assert(ssquares.array == [1, 5, 14, 30, 55]); + + // Chain multiple ranges into seed + int[] a = [3, 4]; + int[] b = [100]; + auto r = cumulativeFold!"a + b"(chain(a, b)); + assert(r.array == [3, 7, 107]); + + // Mixing convertible types is fair game, too + double[] c = [2.5, 3.0]; + auto r1 = cumulativeFold!"a + b"(chain(a, b, c)); + assert(approxEqual(r1, [3, 7, 107, 109.5, 112.5])); + + // To minimize nesting of parentheses, Uniform Function Call Syntax can be used + auto r2 = chain(a, b, c).cumulativeFold!"a + b"; + assert(approxEqual(r2, [3, 7, 107, 109.5, 112.5])); +} + +/** +Sometimes it is very useful to compute multiple aggregates in one pass. +One advantage is that the computation is faster because the looping overhead +is shared. That's why `cumulativeFold` accepts multiple functions. +If two or more functions are passed, `cumulativeFold` returns a $(REF Tuple, +std,typecons) object with one member per passed-in function. +The number of seeds must be correspondingly increased. +*/ +@safe unittest +{ + import std.algorithm.comparison : max, min; + import std.algorithm.iteration : map; + import std.math : approxEqual; + import std.typecons : tuple; + + double[] a = [3.0, 4, 7, 11, 3, 2, 5]; + // Compute minimum and maximum in one pass + auto r = a.cumulativeFold!(min, max); + // The type of r is Tuple!(int, int) + assert(approxEqual(r.map!"a[0]", [3, 3, 3, 3, 3, 2, 2])); // minimum + assert(approxEqual(r.map!"a[1]", [3, 4, 7, 11, 11, 11, 11])); // maximum + + // Compute sum and sum of squares in one pass + auto r2 = a.cumulativeFold!("a + b", "a + b * b")(tuple(0.0, 0.0)); + assert(approxEqual(r2.map!"a[0]", [3, 7, 14, 25, 28, 30, 35])); // sum + assert(approxEqual(r2.map!"a[1]", [9, 25, 74, 195, 204, 208, 233])); // sum of squares +} + +@safe unittest +{ + import std.algorithm.comparison : equal, max, min; + import std.conv : to; + import std.range : chain; + import std.typecons : tuple; + + double[] a = [3, 4]; + auto r = a.cumulativeFold!("a + b")(0.0); + assert(r.equal([3, 7])); + auto r2 = cumulativeFold!("a + b")(a); + assert(r2.equal([3, 7])); + auto r3 = cumulativeFold!(min)(a); + assert(r3.equal([3, 3])); + double[] b = [100]; + auto r4 = cumulativeFold!("a + b")(chain(a, b)); + assert(r4.equal([3, 7, 107])); + + // two funs + auto r5 = cumulativeFold!("a + b", "a - b")(a, tuple(0.0, 0.0)); + assert(r5.equal([tuple(3, -3), tuple(7, -7)])); + auto r6 = cumulativeFold!("a + b", "a - b")(a); + assert(r6.equal([tuple(3, 3), tuple(7, -1)])); + + a = [1, 2, 3, 4, 5]; + // Stringize with commas + auto rep = cumulativeFold!("a ~ `, ` ~ to!string(b)")(a, ""); + assert(rep.map!"a[2 .. $]".equal(["1", "1, 2", "1, 2, 3", "1, 2, 3, 4", "1, 2, 3, 4, 5"])); + + // Test for empty range + a = []; + assert(a.cumulativeFold!"a + b".empty); + assert(a.cumulativeFold!"a + b"(2.0).empty); +} + +@safe unittest +{ + import std.algorithm.comparison : max, min; + import std.array : array; + import std.math : approxEqual; + import std.typecons : tuple; + + const float a = 0.0; + const float[] b = [1.2, 3, 3.3]; + float[] c = [1.2, 3, 3.3]; + + auto r = cumulativeFold!"a + b"(b, a); + assert(approxEqual(r, [1.2, 4.2, 7.5])); + + auto r2 = cumulativeFold!"a + b"(c, a); + assert(approxEqual(r2, [1.2, 4.2, 7.5])); + + const numbers = [10, 30, 20]; + enum m = numbers.cumulativeFold!(min).array; + assert(m == [10, 10, 10]); + enum minmax = numbers.cumulativeFold!(min, max).array; + assert(minmax == [tuple(10, 10), tuple(10, 30), tuple(10, 30)]); +} + +@safe unittest +{ + import std.math : approxEqual; + import std.typecons : tuple; + + enum foo = "a + 0.5 * b"; + auto r = [0, 1, 2, 3]; + auto r1 = r.cumulativeFold!foo; + auto r2 = r.cumulativeFold!(foo, foo); + assert(approxEqual(r1, [0, 0.5, 1.5, 3])); + assert(approxEqual(r2.map!"a[0]", [0, 0.5, 1.5, 3])); + assert(approxEqual(r2.map!"a[1]", [0, 0.5, 1.5, 3])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal, max, min; + import std.array : array; + import std.typecons : tuple; + + //Seed is tuple of const. + static auto minmaxElement(alias F = min, alias G = max, R)(in R range) + @safe pure nothrow + if (isInputRange!R) + { + return range.cumulativeFold!(F, G)(tuple(ElementType!R.max, ElementType!R.min)); + } + + assert(minmaxElement([1, 2, 3]).equal([tuple(1, 1), tuple(1, 2), tuple(1, 3)])); +} + +@safe unittest //12569 +{ + import std.algorithm.comparison : equal, max, min; + import std.typecons : tuple; + + dchar c = 'a'; + + assert(cumulativeFold!(min, max)("hello", tuple(c, c)).equal([tuple('a', 'h'), + tuple('a', 'h'), tuple('a', 'l'), tuple('a', 'l'), tuple('a', 'o')])); + static assert(!__traits(compiles, cumulativeFold!(min, max)("hello", tuple(c)))); + static assert(!__traits(compiles, cumulativeFold!(min, max)("hello", tuple(c, c, c)))); + + //"Seed dchar should be a Tuple" + static assert(!__traits(compiles, cumulativeFold!(min, max)("hello", c))); + //"Seed (dchar) does not have the correct amount of fields (should be 2)" + static assert(!__traits(compiles, cumulativeFold!(min, max)("hello", tuple(c)))); + //"Seed (dchar, dchar, dchar) does not have the correct amount of fields (should be 2)" + static assert(!__traits(compiles, cumulativeFold!(min, max)("hello", tuple(c, c, c)))); + //"Incompatable function/seed/element: all(alias pred = "a")/int/dchar" + static assert(!__traits(compiles, cumulativeFold!all("hello", 1))); + static assert(!__traits(compiles, cumulativeFold!(all, all)("hello", tuple(1, 1)))); +} + +@safe unittest //13304 +{ + int[] data; + assert(data.cumulativeFold!((a, b) => a + b).empty); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : AllDummyRanges, propagatesLength, + propagatesRangeType, RangeType; + + foreach (DummyType; AllDummyRanges) + { + DummyType d; + auto m = d.cumulativeFold!"a * b"; + + static assert(propagatesLength!(typeof(m), DummyType)); + static if (DummyType.rt <= RangeType.Forward) + static assert(propagatesRangeType!(typeof(m), DummyType)); + + assert(m.equal([1, 2, 6, 24, 120, 720, 5040, 40_320, 362_880, 3_628_800])); + } +} + +// splitter +/** +Lazily splits a range using an element as a separator. This can be used with +any narrow string type or sliceable range type, but is most popular with string +types. + +Two adjacent separators are considered to surround an empty element in +the split range. Use $(D filter!(a => !a.empty)) on the result to compress +empty elements. + +The predicate is passed to $(REF binaryFun, std,functional), and can either accept +a string, or any callable that can be executed via $(D pred(element, s)). + +If the empty range is given, the result is a range with one empty +element. If a range with one separator is given, the result is a range +with two empty elements. + +If splitting a string on whitespace and token compression is desired, +consider using $(D splitter) without specifying a separator (see fourth overload +below). + +Params: + pred = The predicate for comparing each element with the separator, + defaulting to $(D "a == b"). + r = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to be + split. Must support slicing and $(D .length). + s = The element to be treated as the separator between range segments to be + split. + +Constraints: + The predicate $(D pred) needs to accept an element of $(D r) and the + separator $(D s). + +Returns: + An input range of the subranges of elements between separators. If $(D r) + is a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) + or $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives), + the returned range will be likewise. + +See_Also: + $(REF _splitter, std,regex) for a version that splits using a regular +expression defined separator. +*/ +auto splitter(alias pred = "a == b", Range, Separator)(Range r, Separator s) +if (is(typeof(binaryFun!pred(r.front, s)) : bool) + && ((hasSlicing!Range && hasLength!Range) || isNarrowString!Range)) +{ + import std.algorithm.searching : find; + import std.conv : unsigned; + + static struct Result + { + private: + Range _input; + Separator _separator; + // Do we need hasLength!Range? popFront uses _input.length... + enum size_t _unComputed = size_t.max - 1, _atEnd = size_t.max; + size_t _frontLength = _unComputed; + size_t _backLength = _unComputed; + + static if (isNarrowString!Range) + { + size_t _separatorLength; + } + else + { + enum _separatorLength = 1; + } + + static if (isBidirectionalRange!Range) + { + static size_t lastIndexOf(Range haystack, Separator needle) + { + import std.range : retro; + auto r = haystack.retro().find!pred(needle); + return r.retro().length - 1; + } + } + + public: + this(Range input, Separator separator) + { + _input = input; + _separator = separator; + + static if (isNarrowString!Range) + { + import std.utf : codeLength; + + _separatorLength = codeLength!(ElementEncodingType!Range)(separator); + } + if (_input.empty) + _frontLength = _atEnd; + } + + static if (isInfinite!Range) + { + enum bool empty = false; + } + else + { + @property bool empty() + { + return _frontLength == _atEnd; + } + } + + @property Range front() + { + assert(!empty, "Attempting to fetch the front of an empty splitter."); + if (_frontLength == _unComputed) + { + auto r = _input.find!pred(_separator); + _frontLength = _input.length - r.length; + } + return _input[0 .. _frontLength]; + } + + void popFront() + { + assert(!empty, "Attempting to popFront an empty splitter."); + if (_frontLength == _unComputed) + { + front; + } + assert(_frontLength <= _input.length); + if (_frontLength == _input.length) + { + // no more input and need to fetch => done + _frontLength = _atEnd; + + // Probably don't need this, but just for consistency: + _backLength = _atEnd; + } + else + { + _input = _input[_frontLength + _separatorLength .. _input.length]; + _frontLength = _unComputed; + } + } + + static if (isForwardRange!Range) + { + @property typeof(this) save() + { + auto ret = this; + ret._input = _input.save; + return ret; + } + } + + static if (isBidirectionalRange!Range) + { + @property Range back() + { + assert(!empty, "Attempting to fetch the back of an empty splitter."); + if (_backLength == _unComputed) + { + immutable lastIndex = lastIndexOf(_input, _separator); + if (lastIndex == -1) + { + _backLength = _input.length; + } + else + { + _backLength = _input.length - lastIndex - 1; + } + } + return _input[_input.length - _backLength .. _input.length]; + } + + void popBack() + { + assert(!empty, "Attempting to popBack an empty splitter."); + if (_backLength == _unComputed) + { + // evaluate back to make sure it's computed + back; + } + assert(_backLength <= _input.length); + if (_backLength == _input.length) + { + // no more input and need to fetch => done + _frontLength = _atEnd; + _backLength = _atEnd; + } + else + { + _input = _input[0 .. _input.length - _backLength - _separatorLength]; + _backLength = _unComputed; + } + } + } + } + + return Result(r, s); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + + assert(equal(splitter("hello world", ' '), [ "hello", "", "world" ])); + int[] a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ]; + int[][] w = [ [1, 2], [], [3], [4, 5], [] ]; + assert(equal(splitter(a, 0), w)); + a = [ 0 ]; + assert(equal(splitter(a, 0), [ (int[]).init, (int[]).init ])); + a = [ 0, 1 ]; + assert(equal(splitter(a, 0), [ [], [1] ])); + w = [ [0], [1], [2] ]; + assert(equal(splitter!"a.front == b"(w, 1), [ [[0]], [[2]] ])); +} + +@safe unittest +{ + import std.algorithm; + import std.array : array; + import std.internal.test.dummyrange; + import std.range : retro; + + assert(equal(splitter("hello world", ' '), [ "hello", "", "world" ])); + assert(equal(splitter("žlutoučkýřkůň", 'ř'), [ "žlutoučký", "kůň" ])); + int[] a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ]; + int[][] w = [ [1, 2], [], [3], [4, 5], [] ]; + static assert(isForwardRange!(typeof(splitter(a, 0)))); + + assert(equal(splitter(a, 0), w)); + a = null; + assert(equal(splitter(a, 0), (int[][]).init)); + a = [ 0 ]; + assert(equal(splitter(a, 0), [ (int[]).init, (int[]).init ][])); + a = [ 0, 1 ]; + assert(equal(splitter(a, 0), [ [], [1] ][])); + + // Thoroughly exercise the bidirectional stuff. + auto str = "abc abcd abcde ab abcdefg abcdefghij ab ac ar an at ada"; + assert(equal( + retro(splitter(str, 'a')), + retro(array(splitter(str, 'a'))) + )); + + // Test interleaving front and back. + auto split = splitter(str, 'a'); + assert(split.front == ""); + assert(split.back == ""); + split.popBack(); + assert(split.back == "d"); + split.popFront(); + assert(split.front == "bc "); + assert(split.back == "d"); + split.popFront(); + split.popBack(); + assert(split.back == "t "); + split.popBack(); + split.popBack(); + split.popFront(); + split.popFront(); + assert(split.front == "b "); + assert(split.back == "r "); + + foreach (DummyType; AllDummyRanges) { // Bug 4408 + static if (isRandomAccessRange!DummyType) + { + static assert(isBidirectionalRange!DummyType); + DummyType d; + auto s = splitter(d, 5); + assert(equal(s.front, [1,2,3,4])); + assert(equal(s.back, [6,7,8,9,10])); + + auto s2 = splitter(d, [4, 5]); + assert(equal(s2.front, [1,2,3])); + } + } +} +@safe unittest +{ + import std.algorithm; + import std.range; + auto L = retro(iota(1L, 10L)); + auto s = splitter(L, 5L); + assert(equal(s.front, [9L, 8L, 7L, 6L])); + s.popFront(); + assert(equal(s.front, [4L, 3L, 2L, 1L])); + s.popFront(); + assert(s.empty); +} + +/** +Similar to the previous overload of $(D splitter), except this one uses another +range as a separator. This can be used with any narrow string type or sliceable +range type, but is most popular with string types. The predicate is passed to +$(REF binaryFun, std,functional), and can either accept a string, or any callable +that can be executed via $(D pred(r.front, s.front)). + +Two adjacent separators are considered to surround an empty element in +the split range. Use $(D filter!(a => !a.empty)) on the result to compress +empty elements. + +Params: + pred = The predicate for comparing each element with the separator, + defaulting to $(D "a == b"). + r = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to be + split. + s = The $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) to + be treated as the separator between segments of $(D r) to be split. + +Constraints: + The predicate $(D pred) needs to accept an element of $(D r) and an + element of $(D s). + +Returns: + An input range of the subranges of elements between separators. If $(D r) + is a forward range or $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives), + the returned range will be likewise. + +See_Also: $(REF _splitter, std,regex) for a version that splits using a regular +expression defined separator. + */ +auto splitter(alias pred = "a == b", Range, Separator)(Range r, Separator s) +if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) + && (hasSlicing!Range || isNarrowString!Range) + && isForwardRange!Separator + && (hasLength!Separator || isNarrowString!Separator)) +{ + import std.algorithm.searching : find; + import std.conv : unsigned; + + static struct Result + { + private: + Range _input; + Separator _separator; + // _frontLength == size_t.max means empty + size_t _frontLength = size_t.max; + static if (isBidirectionalRange!Range) + size_t _backLength = size_t.max; + + @property auto separatorLength() { return _separator.length; } + + void ensureFrontLength() + { + if (_frontLength != _frontLength.max) return; + assert(!_input.empty); + // compute front length + _frontLength = (_separator.empty) ? 1 : + _input.length - find!pred(_input, _separator).length; + static if (isBidirectionalRange!Range) + if (_frontLength == _input.length) _backLength = _frontLength; + } + + void ensureBackLength() + { + static if (isBidirectionalRange!Range) + if (_backLength != _backLength.max) return; + assert(!_input.empty); + // compute back length + static if (isBidirectionalRange!Range && isBidirectionalRange!Separator) + { + import std.range : retro; + _backLength = _input.length - + find!pred(retro(_input), retro(_separator)).source.length; + } + } + + public: + this(Range input, Separator separator) + { + _input = input; + _separator = separator; + } + + @property Range front() + { + assert(!empty, "Attempting to fetch the front of an empty splitter."); + ensureFrontLength(); + return _input[0 .. _frontLength]; + } + + static if (isInfinite!Range) + { + enum bool empty = false; // Propagate infiniteness + } + else + { + @property bool empty() + { + return _frontLength == size_t.max && _input.empty; + } + } + + void popFront() + { + assert(!empty, "Attempting to popFront an empty splitter."); + ensureFrontLength(); + if (_frontLength == _input.length) + { + // done, there's no separator in sight + _input = _input[_frontLength .. _frontLength]; + _frontLength = _frontLength.max; + static if (isBidirectionalRange!Range) + _backLength = _backLength.max; + return; + } + if (_frontLength + separatorLength == _input.length) + { + // Special case: popping the first-to-last item; there is + // an empty item right after this. + _input = _input[_input.length .. _input.length]; + _frontLength = 0; + static if (isBidirectionalRange!Range) + _backLength = 0; + return; + } + // Normal case, pop one item and the separator, get ready for + // reading the next item + _input = _input[_frontLength + separatorLength .. _input.length]; + // mark _frontLength as uninitialized + _frontLength = _frontLength.max; + } + + static if (isForwardRange!Range) + { + @property typeof(this) save() + { + auto ret = this; + ret._input = _input.save; + return ret; + } + } + } + + return Result(r, s); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + + assert(equal(splitter("hello world", " "), [ "hello", "world" ])); + int[] a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ]; + int[][] w = [ [1, 2], [3, 0, 4, 5, 0] ]; + assert(equal(splitter(a, [0, 0]), w)); + a = [ 0, 0 ]; + assert(equal(splitter(a, [0, 0]), [ (int[]).init, (int[]).init ])); + a = [ 0, 0, 1 ]; + assert(equal(splitter(a, [0, 0]), [ [], [1] ])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : Tuple; + + alias C = Tuple!(int, "x", int, "y"); + auto a = [C(1,0), C(2,0), C(3,1), C(4,0)]; + assert(equal(splitter!"a.x == b"(a, [2, 3]), [ [C(1,0)], [C(4,0)] ])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.array : split; + import std.conv : text; + + auto s = ",abc, de, fg,hi,"; + auto sp0 = splitter(s, ','); + assert(equal(sp0, ["", "abc", " de", " fg", "hi", ""][])); + + auto s1 = ", abc, de, fg, hi, "; + auto sp1 = splitter(s1, ", "); + assert(equal(sp1, ["", "abc", "de", " fg", "hi", ""][])); + static assert(isForwardRange!(typeof(sp1))); + + int[] a = [ 1, 2, 0, 3, 0, 4, 5, 0 ]; + int[][] w = [ [1, 2], [3], [4, 5], [] ]; + uint i; + foreach (e; splitter(a, 0)) + { + assert(i < w.length); + assert(e == w[i++]); + } + assert(i == w.length); + // // Now go back + // auto s2 = splitter(a, 0); + + // foreach (e; retro(s2)) + // { + // assert(i > 0); + // assert(equal(e, w[--i]), text(e)); + // } + // assert(i == 0); + + wstring names = ",peter,paul,jerry,"; + auto words = split(names, ","); + assert(walkLength(words) == 5, text(walkLength(words))); +} + +@safe unittest +{ + int[][] a = [ [1], [2], [0], [3], [0], [4], [5], [0] ]; + int[][][] w = [ [[1], [2]], [[3]], [[4], [5]], [] ]; + uint i; + foreach (e; splitter!"a.front == 0"(a, 0)) + { + assert(i < w.length); + assert(e == w[i++]); + } + assert(i == w.length); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + auto s6 = ","; + auto sp6 = splitter(s6, ','); + foreach (e; sp6) {} + assert(equal(sp6, ["", ""][])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + // Issue 10773 + auto s = splitter("abc", ""); + assert(s.equal(["a", "b", "c"])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + // Test by-reference separator + class RefSep { + @safe: + string _impl; + this(string s) { _impl = s; } + @property empty() { return _impl.empty; } + @property auto front() { return _impl.front; } + void popFront() { _impl = _impl[1..$]; } + @property RefSep save() { return new RefSep(_impl); } + @property auto length() { return _impl.length; } + } + auto sep = new RefSep("->"); + auto data = "i->am->pointing"; + auto words = splitter(data, sep); + assert(words.equal([ "i", "am", "pointing" ])); +} + +/** + +Similar to the previous overload of $(D splitter), except this one does not use a separator. +Instead, the predicate is an unary function on the input range's element type. +The $(D isTerminator) predicate is passed to $(REF unaryFun, std,functional) and can +either accept a string, or any callable that can be executed via $(D pred(element, s)). + +Two adjacent separators are considered to surround an empty element in +the split range. Use $(D filter!(a => !a.empty)) on the result to compress +empty elements. + +Params: + isTerminator = The predicate for deciding where to split the range. + input = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to + be split. + +Constraints: + The predicate $(D isTerminator) needs to accept an element of $(D input). + +Returns: + An input range of the subranges of elements between separators. If $(D input) + is a forward range or $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives), + the returned range will be likewise. + +See_Also: $(REF _splitter, std,regex) for a version that splits using a regular +expression defined separator. + */ +auto splitter(alias isTerminator, Range)(Range input) +if (isForwardRange!Range && is(typeof(unaryFun!isTerminator(input.front)))) +{ + return SplitterResult!(unaryFun!isTerminator, Range)(input); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range.primitives : front; + + assert(equal(splitter!(a => a == ' ')("hello world"), [ "hello", "", "world" ])); + int[] a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ]; + int[][] w = [ [1, 2], [], [3], [4, 5], [] ]; + assert(equal(splitter!(a => a == 0)(a), w)); + a = [ 0 ]; + assert(equal(splitter!(a => a == 0)(a), [ (int[]).init, (int[]).init ])); + a = [ 0, 1 ]; + assert(equal(splitter!(a => a == 0)(a), [ [], [1] ])); + w = [ [0], [1], [2] ]; + assert(equal(splitter!(a => a.front == 1)(w), [ [[0]], [[2]] ])); +} + +private struct SplitterResult(alias isTerminator, Range) +{ + import std.algorithm.searching : find; + enum fullSlicing = (hasLength!Range && hasSlicing!Range) || isSomeString!Range; + + private Range _input; + private size_t _end = 0; + static if (!fullSlicing) + private Range _next; + + private void findTerminator() + { + static if (fullSlicing) + { + auto r = find!isTerminator(_input.save); + _end = _input.length - r.length; + } + else + for ( _end = 0; !_next.empty ; _next.popFront) + { + if (isTerminator(_next.front)) + break; + ++_end; + } + } + + this(Range input) + { + _input = input; + static if (!fullSlicing) + _next = _input.save; + + if (!_input.empty) + findTerminator(); + else + _end = size_t.max; + } + + static if (isInfinite!Range) + { + enum bool empty = false; // Propagate infiniteness. + } + else + { + @property bool empty() + { + return _end == size_t.max; + } + } + + @property auto front() + { + version (assert) + { + import core.exception : RangeError; + if (empty) + throw new RangeError(); + } + static if (fullSlicing) + return _input[0 .. _end]; + else + { + import std.range : takeExactly; + return _input.takeExactly(_end); + } + } + + void popFront() + { + version (assert) + { + import core.exception : RangeError; + if (empty) + throw new RangeError(); + } + + static if (fullSlicing) + { + _input = _input[_end .. _input.length]; + if (_input.empty) + { + _end = size_t.max; + return; + } + _input.popFront(); + } + else + { + if (_next.empty) + { + _input = _next; + _end = size_t.max; + return; + } + _next.popFront(); + _input = _next.save; + } + findTerminator(); + } + + @property typeof(this) save() + { + auto ret = this; + ret._input = _input.save; + static if (!fullSlicing) + ret._next = _next.save; + return ret; + } +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : iota; + + auto L = iota(1L, 10L); + auto s = splitter(L, [5L, 6L]); + assert(equal(s.front, [1L, 2L, 3L, 4L])); + s.popFront(); + assert(equal(s.front, [7L, 8L, 9L])); + s.popFront(); + assert(s.empty); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.internal : algoFormat; + import std.internal.test.dummyrange; + + void compare(string sentence, string[] witness) + { + auto r = splitter!"a == ' '"(sentence); + assert(equal(r.save, witness), algoFormat("got: %(%s, %) expected: %(%s, %)", r, witness)); + } + + compare(" Mary has a little lamb. ", + ["", "Mary", "", "has", "a", "little", "lamb.", "", "", ""]); + compare("Mary has a little lamb. ", + ["Mary", "", "has", "a", "little", "lamb.", "", "", ""]); + compare("Mary has a little lamb.", + ["Mary", "", "has", "a", "little", "lamb."]); + compare("", (string[]).init); + compare(" ", ["", ""]); + + static assert(isForwardRange!(typeof(splitter!"a == ' '"("ABC")))); + + foreach (DummyType; AllDummyRanges) + { + static if (isRandomAccessRange!DummyType) + { + auto rangeSplit = splitter!"a == 5"(DummyType.init); + assert(equal(rangeSplit.front, [1,2,3,4])); + rangeSplit.popFront(); + assert(equal(rangeSplit.front, [6,7,8,9,10])); + } + } +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.internal : algoFormat; + import std.range; + + struct Entry + { + int low; + int high; + int[][] result; + } + Entry[] entries = [ + Entry(0, 0, []), + Entry(0, 1, [[0]]), + Entry(1, 2, [[], []]), + Entry(2, 7, [[2], [4], [6]]), + Entry(1, 8, [[], [2], [4], [6], []]), + ]; + foreach ( entry ; entries ) + { + auto a = iota(entry.low, entry.high).filter!"true"(); + auto b = splitter!"a%2"(a); + assert(equal!equal(b.save, entry.result), algoFormat("got: %(%s, %) expected: %(%s, %)", b, entry.result)); + } +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.uni : isWhite; + + //@@@6791@@@ + assert(equal( + splitter("là dove terminava quella valle"), + ["là", "dove", "terminava", "quella", "valle"] + )); + assert(equal( + splitter!(std.uni.isWhite)("là dove terminava quella valle"), + ["là", "dove", "terminava", "quella", "valle"] + )); + assert(equal(splitter!"a=='本'"("日本語"), ["日", "語"])); +} + +/++ +Lazily splits the string $(D s) into words, using whitespace as the delimiter. + +This function is string specific and, contrary to +$(D splitter!(std.uni.isWhite)), runs of whitespace will be merged together +(no empty tokens will be produced). + +Params: + s = The string to be split. + +Returns: + An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of slices of + the original string split by whitespace. + +/ +auto splitter(C)(C[] s) +if (isSomeChar!C) +{ + import std.algorithm.searching : find; + static struct Result + { + private: + import core.exception : RangeError; + C[] _s; + size_t _frontLength; + + void getFirst() pure @safe + { + import std.uni : isWhite; + + auto r = find!(isWhite)(_s); + _frontLength = _s.length - r.length; + } + + public: + this(C[] s) pure @safe + { + import std.string : strip; + _s = s.strip(); + getFirst(); + } + + @property C[] front() pure @safe + { + version (assert) if (empty) throw new RangeError(); + return _s[0 .. _frontLength]; + } + + void popFront() pure @safe + { + import std.string : stripLeft; + version (assert) if (empty) throw new RangeError(); + _s = _s[_frontLength .. $].stripLeft(); + getFirst(); + } + + @property bool empty() const @safe pure nothrow + { + return _s.empty; + } + + @property inout(Result) save() inout @safe pure nothrow + { + return this; + } + } + return Result(s); +} + +/// +@safe pure unittest +{ + import std.algorithm.comparison : equal; + auto a = " a bcd ef gh "; + assert(equal(splitter(a), ["a", "bcd", "ef", "gh"][])); +} + +@safe pure unittest +{ + import std.algorithm.comparison : equal; + import std.meta : AliasSeq; + foreach (S; AliasSeq!(string, wstring, dstring)) + { + import std.conv : to; + S a = " a bcd ef gh "; + assert(equal(splitter(a), [to!S("a"), to!S("bcd"), to!S("ef"), to!S("gh")])); + a = ""; + assert(splitter(a).empty); + } + + immutable string s = " a bcd ef gh "; + assert(equal(splitter(s), ["a", "bcd", "ef", "gh"][])); +} + +@safe unittest +{ + import std.conv : to; + import std.string : strip; + + // TDPL example, page 8 + uint[string] dictionary; + char[][3] lines; + lines[0] = "line one".dup; + lines[1] = "line \ttwo".dup; + lines[2] = "yah last line\ryah".dup; + foreach (line; lines) + { + foreach (word; splitter(strip(line))) + { + if (word in dictionary) continue; // Nothing to do + auto newID = dictionary.length; + dictionary[to!string(word)] = cast(uint) newID; + } + } + assert(dictionary.length == 5); + assert(dictionary["line"]== 0); + assert(dictionary["one"]== 1); + assert(dictionary["two"]== 2); + assert(dictionary["yah"]== 3); + assert(dictionary["last"]== 4); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.internal : algoFormat; + import std.array : split; + import std.conv : text; + + // Check consistency: + // All flavors of split should produce the same results + foreach (input; [(int[]).init, + [0], + [0, 1, 0], + [1, 1, 0, 0, 1, 1], + ]) + { + foreach (s; [0, 1]) + { + auto result = split(input, s); + + assert(equal(result, split(input, [s])), algoFormat(`"[%(%s,%)]"`, split(input, [s]))); + //assert(equal(result, split(input, [s].filter!"true"()))); //Not yet implemented + assert(equal(result, split!((a) => a == s)(input)), text(split!((a) => a == s)(input))); + + //assert(equal!equal(result, split(input.filter!"true"(), s))); //Not yet implemented + //assert(equal!equal(result, split(input.filter!"true"(), [s]))); //Not yet implemented + //assert(equal!equal(result, split(input.filter!"true"(), [s].filter!"true"()))); //Not yet implemented + assert(equal!equal(result, split!((a) => a == s)(input.filter!"true"()))); + + assert(equal(result, splitter(input, s))); + assert(equal(result, splitter(input, [s]))); + //assert(equal(result, splitter(input, [s].filter!"true"()))); //Not yet implemented + assert(equal(result, splitter!((a) => a == s)(input))); + + //assert(equal!equal(result, splitter(input.filter!"true"(), s))); //Not yet implemented + //assert(equal!equal(result, splitter(input.filter!"true"(), [s]))); //Not yet implemented + //assert(equal!equal(result, splitter(input.filter!"true"(), [s].filter!"true"()))); //Not yet implemented + assert(equal!equal(result, splitter!((a) => a == s)(input.filter!"true"()))); + } + } + foreach (input; [string.init, + " ", + " hello ", + "hello hello", + " hello what heck this ? " + ]) + { + foreach (s; [' ', 'h']) + { + auto result = split(input, s); + + assert(equal(result, split(input, [s]))); + //assert(equal(result, split(input, [s].filter!"true"()))); //Not yet implemented + assert(equal(result, split!((a) => a == s)(input))); + + //assert(equal!equal(result, split(input.filter!"true"(), s))); //Not yet implemented + //assert(equal!equal(result, split(input.filter!"true"(), [s]))); //Not yet implemented + //assert(equal!equal(result, split(input.filter!"true"(), [s].filter!"true"()))); //Not yet implemented + assert(equal!equal(result, split!((a) => a == s)(input.filter!"true"()))); + + assert(equal(result, splitter(input, s))); + assert(equal(result, splitter(input, [s]))); + //assert(equal(result, splitter(input, [s].filter!"true"()))); //Not yet implemented + assert(equal(result, splitter!((a) => a == s)(input))); + + //assert(equal!equal(result, splitter(input.filter!"true"(), s))); //Not yet implemented + //assert(equal!equal(result, splitter(input.filter!"true"(), [s]))); //Not yet implemented + //assert(equal!equal(result, splitter(input.filter!"true"(), [s].filter!"true"()))); //Not yet implemented + assert(equal!equal(result, splitter!((a) => a == s)(input.filter!"true"()))); + } + } +} + +// sum +/** +Sums elements of $(D r), which must be a finite +$(REF_ALTTEXT input range, isInputRange, std,range,primitives). Although +conceptually $(D sum(r)) is equivalent to $(LREF fold)!((a, b) => a + +b)(r, 0), $(D sum) uses specialized algorithms to maximize accuracy, +as follows. + +$(UL +$(LI If $(D $(REF ElementType, std,range,primitives)!R) is a floating-point +type and $(D R) is a +$(REF_ALTTEXT random-access range, isRandomAccessRange, std,range,primitives) with +length and slicing, then $(D sum) uses the +$(HTTP en.wikipedia.org/wiki/Pairwise_summation, pairwise summation) +algorithm.) +$(LI If $(D ElementType!R) is a floating-point type and $(D R) is a +finite input range (but not a random-access range with slicing), then +$(D sum) uses the $(HTTP en.wikipedia.org/wiki/Kahan_summation, +Kahan summation) algorithm.) +$(LI In all other cases, a simple element by element addition is done.) +) + +For floating point inputs, calculations are made in +$(DDLINK spec/type, Types, $(D real)) +precision for $(D real) inputs and in $(D double) precision otherwise +(Note this is a special case that deviates from $(D fold)'s behavior, +which would have kept $(D float) precision for a $(D float) range). +For all other types, the calculations are done in the same type obtained +from from adding two elements of the range, which may be a different +type from the elements themselves (for example, in case of +$(DDSUBLINK spec/type,integer-promotions, integral promotion)). + +A seed may be passed to $(D sum). Not only will this seed be used as an initial +value, but its type will override all the above, and determine the algorithm +and precision used for summation. + +Note that these specialized summing algorithms execute more primitive operations +than vanilla summation. Therefore, if in certain cases maximum speed is required +at expense of precision, one can use $(D fold!((a, b) => a + b)(r, 0)), which +is not specialized for summation. + +Params: + seed = the initial value of the summation + r = a finite input range + +Returns: + The sum of all the elements in the range r. + */ +auto sum(R)(R r) +if (isInputRange!R && !isInfinite!R && is(typeof(r.front + r.front))) +{ + alias E = Unqual!(ElementType!R); + static if (isFloatingPoint!E) + alias Seed = typeof(E.init + 0.0); //biggest of double/real + else + alias Seed = typeof(r.front + r.front); + return sum(r, Unqual!Seed(0)); +} +/// ditto +auto sum(R, E)(R r, E seed) +if (isInputRange!R && !isInfinite!R && is(typeof(seed = seed + r.front))) +{ + static if (isFloatingPoint!E) + { + static if (hasLength!R && hasSlicing!R) + { + if (r.empty) return seed; + return seed + sumPairwise!E(r); + } + else + return sumKahan!E(seed, r); + } + else + { + return reduce!"a + b"(seed, r); + } +} + +// Pairwise summation http://en.wikipedia.org/wiki/Pairwise_summation +private auto sumPairwise(F, R)(R data) +if (isInputRange!R && !isInfinite!R) +{ + import core.bitop : bsf; + // Works for r with at least length < 2^^(64 + log2(16)), in keeping with the use of size_t + // elsewhere in std.algorithm and std.range on 64 bit platforms. The 16 in log2(16) comes + // from the manual unrolling in sumPairWise16 + F[64] store = void; + size_t idx = 0; + + void collapseStore(T)(T k) + { + auto lastToKeep = idx - cast(uint) bsf(k+1); + while (idx > lastToKeep) + { + store[idx - 1] += store[idx]; + --idx; + } + } + + static if (hasLength!R) + { + foreach (k; 0 .. data.length / 16) + { + static if (isRandomAccessRange!R && hasSlicing!R) + { + store[idx] = sumPairwise16!F(data); + data = data[16 .. data.length]; + } + else store[idx] = sumPairwiseN!(16, false, F)(data); + + collapseStore(k); + ++idx; + } + + size_t i = 0; + foreach (el; data) + { + store[idx] = el; + collapseStore(i); + ++idx; + ++i; + } + } + else + { + size_t k = 0; + while (!data.empty) + { + store[idx] = sumPairwiseN!(16, true, F)(data); + collapseStore(k); + ++idx; + ++k; + } + } + + F s = store[idx - 1]; + foreach_reverse (j; 0 .. idx - 1) + s += store[j]; + + return s; +} + +private auto sumPairwise16(F, R)(R r) +if (isRandomAccessRange!R) +{ + return (((cast(F) r[ 0] + r[ 1]) + (cast(F) r[ 2] + r[ 3])) + + ((cast(F) r[ 4] + r[ 5]) + (cast(F) r[ 6] + r[ 7]))) + + (((cast(F) r[ 8] + r[ 9]) + (cast(F) r[10] + r[11])) + + ((cast(F) r[12] + r[13]) + (cast(F) r[14] + r[15]))); +} + +private auto sumPair(bool needEmptyChecks, F, R)(ref R r) +if (isForwardRange!R && !isRandomAccessRange!R) +{ + static if (needEmptyChecks) if (r.empty) return F(0); + F s0 = r.front; + r.popFront(); + static if (needEmptyChecks) if (r.empty) return s0; + s0 += r.front; + r.popFront(); + return s0; +} + +private auto sumPairwiseN(size_t N, bool needEmptyChecks, F, R)(ref R r) +if (isForwardRange!R && !isRandomAccessRange!R) +{ + import std.math : isPowerOf2; + static assert(isPowerOf2(N)); + static if (N == 2) return sumPair!(needEmptyChecks, F)(r); + else return sumPairwiseN!(N/2, needEmptyChecks, F)(r) + + sumPairwiseN!(N/2, needEmptyChecks, F)(r); +} + +// Kahan algo http://en.wikipedia.org/wiki/Kahan_summation_algorithm +private auto sumKahan(Result, R)(Result result, R r) +{ + static assert(isFloatingPoint!Result && isMutable!Result); + Result c = 0; + for (; !r.empty; r.popFront()) + { + immutable y = r.front - c; + immutable t = result + y; + c = (t - result) - y; + result = t; + } + return result; +} + +/// Ditto +@safe pure nothrow unittest +{ + import std.range; + + //simple integral sumation + assert(sum([ 1, 2, 3, 4]) == 10); + + //with integral promotion + assert(sum([false, true, true, false, true]) == 3); + assert(sum(ubyte.max.repeat(100)) == 25500); + + //The result may overflow + assert(uint.max.repeat(3).sum() == 4294967293U ); + //But a seed can be used to change the sumation primitive + assert(uint.max.repeat(3).sum(ulong.init) == 12884901885UL); + + //Floating point sumation + assert(sum([1.0, 2.0, 3.0, 4.0]) == 10); + + //Floating point operations have double precision minimum + static assert(is(typeof(sum([1F, 2F, 3F, 4F])) == double)); + assert(sum([1F, 2, 3, 4]) == 10); + + //Force pair-wise floating point sumation on large integers + import std.math : approxEqual; + assert(iota(ulong.max / 2, ulong.max / 2 + 4096).sum(0.0) + .approxEqual((ulong.max / 2) * 4096.0 + 4096^^2 / 2)); +} + +@safe pure nothrow unittest +{ + static assert(is(typeof(sum([cast( byte) 1])) == int)); + static assert(is(typeof(sum([cast(ubyte) 1])) == int)); + static assert(is(typeof(sum([ 1, 2, 3, 4])) == int)); + static assert(is(typeof(sum([ 1U, 2U, 3U, 4U])) == uint)); + static assert(is(typeof(sum([ 1L, 2L, 3L, 4L])) == long)); + static assert(is(typeof(sum([1UL, 2UL, 3UL, 4UL])) == ulong)); + + int[] empty; + assert(sum(empty) == 0); + assert(sum([42]) == 42); + assert(sum([42, 43]) == 42 + 43); + assert(sum([42, 43, 44]) == 42 + 43 + 44); + assert(sum([42, 43, 44, 45]) == 42 + 43 + 44 + 45); +} + +@safe pure nothrow unittest +{ + static assert(is(typeof(sum([1.0, 2.0, 3.0, 4.0])) == double)); + static assert(is(typeof(sum([ 1F, 2F, 3F, 4F])) == double)); + const(float[]) a = [1F, 2F, 3F, 4F]; + assert(sum(a) == 10F); + static assert(is(typeof(sum(a)) == double)); + + double[] empty; + assert(sum(empty) == 0); + assert(sum([42.]) == 42); + assert(sum([42., 43.]) == 42 + 43); + assert(sum([42., 43., 44.]) == 42 + 43 + 44); + assert(sum([42., 43., 44., 45.5]) == 42 + 43 + 44 + 45.5); +} + +@safe pure nothrow unittest +{ + import std.container; + static assert(is(typeof(sum(SList!float()[])) == double)); + static assert(is(typeof(sum(SList!double()[])) == double)); + static assert(is(typeof(sum(SList!real()[])) == real)); + + assert(sum(SList!double()[]) == 0); + assert(sum(SList!double(1)[]) == 1); + assert(sum(SList!double(1, 2)[]) == 1 + 2); + assert(sum(SList!double(1, 2, 3)[]) == 1 + 2 + 3); + assert(sum(SList!double(1, 2, 3, 4)[]) == 10); +} + +@safe pure nothrow unittest // 12434 +{ + immutable a = [10, 20]; + auto s1 = sum(a); + assert(s1 == 30); + auto s2 = a.map!(x => x).sum; + assert(s2 == 30); +} + +@system unittest +{ + import std.bigint; + import std.range; + + immutable BigInt[] a = BigInt("1_000_000_000_000_000_000").repeat(10).array(); + immutable ulong[] b = (ulong.max/2).repeat(10).array(); + auto sa = a.sum(); + auto sb = b.sum(BigInt(0)); //reduce ulongs into bigint + assert(sa == BigInt("10_000_000_000_000_000_000")); + assert(sb == (BigInt(ulong.max/2) * 10)); +} + +@safe pure nothrow @nogc unittest +{ + import std.range; + foreach (n; iota(50)) + assert(repeat(1.0, n).sum == n); +} + +// uniq +/** +Lazily iterates unique consecutive elements of the given range (functionality +akin to the $(HTTP wikipedia.org/wiki/_Uniq, _uniq) system +utility). Equivalence of elements is assessed by using the predicate +$(D pred), by default $(D "a == b"). The predicate is passed to +$(REF binaryFun, std,functional), and can either accept a string, or any callable +that can be executed via $(D pred(element, element)). If the given range is +bidirectional, $(D uniq) also yields a +$(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives). + +Params: + pred = Predicate for determining equivalence between range elements. + r = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of + elements to filter. + +Returns: + An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of + consecutively unique elements in the original range. If $(D r) is also a + forward range or bidirectional range, the returned range will be likewise. +*/ +auto uniq(alias pred = "a == b", Range)(Range r) +if (isInputRange!Range && is(typeof(binaryFun!pred(r.front, r.front)) == bool)) +{ + return UniqResult!(binaryFun!pred, Range)(r); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.mutation : copy; + + int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; + assert(equal(uniq(arr), [ 1, 2, 3, 4, 5 ][])); + + // Filter duplicates in-place using copy + arr.length -= arr.uniq().copy(arr).length; + assert(arr == [ 1, 2, 3, 4, 5 ]); + + // Note that uniqueness is only determined consecutively; duplicated + // elements separated by an intervening different element will not be + // eliminated: + assert(equal(uniq([ 1, 1, 2, 1, 1, 3, 1]), [1, 2, 1, 3, 1])); +} + +private struct UniqResult(alias pred, Range) +{ + Range _input; + + this(Range input) + { + _input = input; + } + + auto opSlice() + { + return this; + } + + void popFront() + { + assert(!empty, "Attempting to popFront an empty uniq."); + auto last = _input.front; + do + { + _input.popFront(); + } + while (!_input.empty && pred(last, _input.front)); + } + + @property ElementType!Range front() + { + assert(!empty, "Attempting to fetch the front of an empty uniq."); + return _input.front; + } + + static if (isBidirectionalRange!Range) + { + void popBack() + { + assert(!empty, "Attempting to popBack an empty uniq."); + auto last = _input.back; + do + { + _input.popBack(); + } + while (!_input.empty && pred(last, _input.back)); + } + + @property ElementType!Range back() + { + assert(!empty, "Attempting to fetch the back of an empty uniq."); + return _input.back; + } + } + + static if (isInfinite!Range) + { + enum bool empty = false; // Propagate infiniteness. + } + else + { + @property bool empty() { return _input.empty; } + } + + static if (isForwardRange!Range) + { + @property typeof(this) save() { + return typeof(this)(_input.save); + } + } +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange; + import std.range; + + int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; + auto r = uniq(arr); + static assert(isForwardRange!(typeof(r))); + + assert(equal(r, [ 1, 2, 3, 4, 5 ][])); + assert(equal(retro(r), retro([ 1, 2, 3, 4, 5 ][]))); + + foreach (DummyType; AllDummyRanges) + { + DummyType d; + auto u = uniq(d); + assert(equal(u, [1,2,3,4,5,6,7,8,9,10])); + + static assert(d.rt == RangeType.Input || isForwardRange!(typeof(u))); + + static if (d.rt >= RangeType.Bidirectional) + { + assert(equal(retro(u), [10,9,8,7,6,5,4,3,2,1])); + } + } +} + +@safe unittest // https://issues.dlang.org/show_bug.cgi?id=17264 +{ + import std.algorithm.comparison : equal; + + const(int)[] var = [0, 1, 1, 2]; + assert(var.uniq.equal([0, 1, 2])); +} + +/** +Lazily computes all _permutations of $(D r) using $(HTTP +en.wikipedia.org/wiki/Heap%27s_algorithm, Heap's algorithm). + +Returns: +A $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) +the elements of which are an $(REF indexed, std,range) view into $(D r). + +See_Also: +$(REF nextPermutation, std,algorithm,sorting). +*/ +Permutations!Range permutations(Range)(Range r) +if (isRandomAccessRange!Range && hasLength!Range) +{ + return typeof(return)(r); +} + +/// ditto +struct Permutations(Range) +if (isRandomAccessRange!Range && hasLength!Range) +{ + private size_t[] _indices, _state; + private Range _r; + private bool _empty; + + /// + this(Range r) + { + import std.array : array; + import std.range : iota; + + this._r = r; + _state = r.length ? new size_t[r.length-1] : null; + _indices = iota(size_t(r.length)).array; + _empty = r.length == 0; + } + + /// + @property bool empty() const pure nothrow @safe @nogc + { + return _empty; + } + + /// + @property auto front() + { + import std.range : indexed; + return _r.indexed(_indices); + } + + /// + void popFront() + { + void next(int n) + { + import std.algorithm.mutation : swap; + + if (n > _indices.length) + { + _empty = true; + return; + } + + if (n % 2 == 1) + swap(_indices[0], _indices[n-1]); + else + swap(_indices[_state[n-2]], _indices[n-1]); + + if (++_state[n-2] == n) + { + _state[n-2] = 0; + next(n+1); + } + } + + next(2); + } +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : iota; + assert(equal!equal(iota(3).permutations, + [[0, 1, 2], + [1, 0, 2], + [2, 0, 1], + [0, 2, 1], + [1, 2, 0], + [2, 1, 0]])); +} diff --git a/libphobos/src/std/algorithm/mutation.d b/libphobos/src/std/algorithm/mutation.d new file mode 100644 index 0000000..2b708ad --- /dev/null +++ b/libphobos/src/std/algorithm/mutation.d @@ -0,0 +1,2909 @@ +// Written in the D programming language. +/** +This is a submodule of $(MREF std, algorithm). +It contains generic _mutation algorithms. + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE Cheat Sheet, +$(TR $(TH Function Name) $(TH Description)) +$(T2 bringToFront, + If $(D a = [1, 2, 3]) and $(D b = [4, 5, 6, 7]), + $(D bringToFront(a, b)) leaves $(D a = [4, 5, 6]) and + $(D b = [7, 1, 2, 3]).) +$(T2 copy, + Copies a range to another. If + $(D a = [1, 2, 3]) and $(D b = new int[5]), then $(D copy(a, b)) + leaves $(D b = [1, 2, 3, 0, 0]) and returns $(D b[3 .. $]).) +$(T2 fill, + Fills a range with a pattern, + e.g., if $(D a = new int[3]), then $(D fill(a, 4)) + leaves $(D a = [4, 4, 4]) and $(D fill(a, [3, 4])) leaves + $(D a = [3, 4, 3]).) +$(T2 initializeAll, + If $(D a = [1.2, 3.4]), then $(D initializeAll(a)) leaves + $(D a = [double.init, double.init]).) +$(T2 move, + $(D move(a, b)) moves $(D a) into $(D b). $(D move(a)) reads $(D a) + destructively when necessary.) +$(T2 moveEmplace, + Similar to $(D move) but assumes `target` is uninitialized.) +$(T2 moveAll, + Moves all elements from one range to another.) +$(T2 moveEmplaceAll, + Similar to $(D moveAll) but assumes all elements in `target` are uninitialized.) +$(T2 moveSome, + Moves as many elements as possible from one range to another.) +$(T2 moveEmplaceSome, + Similar to $(D moveSome) but assumes all elements in `target` are uninitialized.) +$(T2 remove, + Removes elements from a range in-place, and returns the shortened + range.) +$(T2 reverse, + If $(D a = [1, 2, 3]), $(D reverse(a)) changes it to $(D [3, 2, 1]).) +$(T2 strip, + Strips all leading and trailing elements equal to a value, or that + satisfy a predicate. + If $(D a = [1, 1, 0, 1, 1]), then $(D strip(a, 1)) and + $(D strip!(e => e == 1)(a)) returns $(D [0]).) +$(T2 stripLeft, + Strips all leading elements equal to a value, or that satisfy a + predicate. If $(D a = [1, 1, 0, 1, 1]), then $(D stripLeft(a, 1)) and + $(D stripLeft!(e => e == 1)(a)) returns $(D [0, 1, 1]).) +$(T2 stripRight, + Strips all trailing elements equal to a value, or that satisfy a + predicate. + If $(D a = [1, 1, 0, 1, 1]), then $(D stripRight(a, 1)) and + $(D stripRight!(e => e == 1)(a)) returns $(D [1, 1, 0]).) +$(T2 swap, + Swaps two values.) +$(T2 swapAt, + Swaps two values by indices.) +$(T2 swapRanges, + Swaps all elements of two ranges.) +$(T2 uninitializedFill, + Fills a range (assumed uninitialized) with a value.) +) + +Copyright: Andrei Alexandrescu 2008-. + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(HTTP erdani.com, Andrei Alexandrescu) + +Source: $(PHOBOSSRC std/algorithm/_mutation.d) + +Macros: +T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) + */ +module std.algorithm.mutation; + +import std.range.primitives; +import std.traits : isArray, isBlitAssignable, isNarrowString, Unqual, isSomeChar; +// FIXME +import std.typecons; // : tuple, Tuple; + +// bringToFront +/** +The $(D bringToFront) function has considerable flexibility and +usefulness. It can rotate elements in one buffer left or right, swap +buffers of equal length, and even move elements across disjoint +buffers of different types and different lengths. + +$(D bringToFront) takes two ranges $(D front) and $(D back), which may +be of different types. Considering the concatenation of $(D front) and +$(D back) one unified range, $(D bringToFront) rotates that unified +range such that all elements in $(D back) are brought to the beginning +of the unified range. The relative ordering of elements in $(D front) +and $(D back), respectively, remains unchanged. + +The $(D bringToFront) function treats strings at the code unit +level and it is not concerned with Unicode character integrity. +$(D bringToFront) is designed as a function for moving elements +in ranges, not as a string function. + +Performs $(BIGOH max(front.length, back.length)) evaluations of $(D +swap). + +Preconditions: + +Either $(D front) and $(D back) are disjoint, or $(D back) is +reachable from $(D front) and $(D front) is not reachable from $(D +back). + +Params: + front = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + back = a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) + +Returns: + The number of elements brought to the front, i.e., the length of $(D back). + +See_Also: + $(HTTP sgi.com/tech/stl/_rotate.html, STL's rotate) +*/ +size_t bringToFront(InputRange, ForwardRange)(InputRange front, ForwardRange back) +if (isInputRange!InputRange && isForwardRange!ForwardRange) +{ + import std.string : representation; + + static if (isNarrowString!InputRange) + { + auto frontW = representation(front); + } + else + { + alias frontW = front; + } + static if (isNarrowString!ForwardRange) + { + auto backW = representation(back); + } + else + { + alias backW = back; + } + + return bringToFrontImpl(frontW, backW); +} + +private size_t bringToFrontImpl(InputRange, ForwardRange)(InputRange front, ForwardRange back) +if (isInputRange!InputRange && isForwardRange!ForwardRange) +{ + import std.array : sameHead; + import std.range : take, Take; + enum bool sameHeadExists = is(typeof(front.sameHead(back))); + size_t result; + + for (bool semidone; !front.empty && !back.empty; ) + { + static if (sameHeadExists) + { + if (front.sameHead(back)) break; // shortcut + } + // Swap elements until front and/or back ends. + auto back0 = back.save; + size_t nswaps; + do + { + static if (sameHeadExists) + { + // Detect the stepping-over condition. + if (front.sameHead(back0)) back0 = back.save; + } + swapFront(front, back); + ++nswaps; + front.popFront(); + back.popFront(); + } + while (!front.empty && !back.empty); + + if (!semidone) result += nswaps; + + // Now deal with the remaining elements. + if (back.empty) + { + if (front.empty) break; + // Right side was shorter, which means that we've brought + // all the back elements to the front. + semidone = true; + // Next pass: bringToFront(front, back0) to adjust the rest. + back = back0; + } + else + { + assert(front.empty); + // Left side was shorter. Let's step into the back. + static if (is(InputRange == Take!ForwardRange)) + { + front = take(back0, nswaps); + } + else + { + immutable subresult = bringToFront(take(back0, nswaps), + back); + if (!semidone) result += subresult; + break; // done + } + } + } + return result; +} + +/** +The simplest use of $(D bringToFront) is for rotating elements in a +buffer. For example: +*/ +@safe unittest +{ + auto arr = [4, 5, 6, 7, 1, 2, 3]; + auto p = bringToFront(arr[0 .. 4], arr[4 .. $]); + assert(p == arr.length - 4); + assert(arr == [ 1, 2, 3, 4, 5, 6, 7 ]); +} + +/** +The $(D front) range may actually "step over" the $(D back) +range. This is very useful with forward ranges that cannot compute +comfortably right-bounded subranges like $(D arr[0 .. 4]) above. In +the example below, $(D r2) is a right subrange of $(D r1). +*/ +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.container : SList; + import std.range.primitives : popFrontN; + + auto list = SList!(int)(4, 5, 6, 7, 1, 2, 3); + auto r1 = list[]; + auto r2 = list[]; popFrontN(r2, 4); + assert(equal(r2, [ 1, 2, 3 ])); + bringToFront(r1, r2); + assert(equal(list[], [ 1, 2, 3, 4, 5, 6, 7 ])); +} + +/** +Elements can be swapped across ranges of different types: +*/ +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.container : SList; + + auto list = SList!(int)(4, 5, 6, 7); + auto vec = [ 1, 2, 3 ]; + bringToFront(list[], vec); + assert(equal(list[], [ 1, 2, 3, 4 ])); + assert(equal(vec, [ 5, 6, 7 ])); +} + +/** +Unicode integrity is not preserved: +*/ +@safe unittest +{ + import std.string : representation; + auto ar = representation("a".dup); + auto br = representation("ç".dup); + + bringToFront(ar, br); + + auto a = cast(char[]) ar; + auto b = cast(char[]) br; + + // Illegal UTF-8 + assert(a == "\303"); + // Illegal UTF-8 + assert(b == "\247a"); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.conv : text; + import std.random : Random, unpredictableSeed, uniform; + + // a more elaborate test + { + auto rnd = Random(unpredictableSeed); + int[] a = new int[uniform(100, 200, rnd)]; + int[] b = new int[uniform(100, 200, rnd)]; + foreach (ref e; a) e = uniform(-100, 100, rnd); + foreach (ref e; b) e = uniform(-100, 100, rnd); + int[] c = a ~ b; + // writeln("a= ", a); + // writeln("b= ", b); + auto n = bringToFront(c[0 .. a.length], c[a.length .. $]); + //writeln("c= ", c); + assert(n == b.length); + assert(c == b ~ a, text(c, "\n", a, "\n", b)); + } + // different types, moveFront, no sameHead + { + static struct R(T) + { + T[] data; + size_t i; + @property + { + R save() { return this; } + bool empty() { return i >= data.length; } + T front() { return data[i]; } + T front(real e) { return data[i] = cast(T) e; } + } + void popFront() { ++i; } + } + auto a = R!int([1, 2, 3, 4, 5]); + auto b = R!real([6, 7, 8, 9]); + auto n = bringToFront(a, b); + assert(n == 4); + assert(a.data == [6, 7, 8, 9, 1]); + assert(b.data == [2, 3, 4, 5]); + } + // front steps over back + { + int[] arr, r1, r2; + + // back is shorter + arr = [4, 5, 6, 7, 1, 2, 3]; + r1 = arr; + r2 = arr[4 .. $]; + bringToFront(r1, r2) == 3 || assert(0); + assert(equal(arr, [1, 2, 3, 4, 5, 6, 7])); + + // front is shorter + arr = [5, 6, 7, 1, 2, 3, 4]; + r1 = arr; + r2 = arr[3 .. $]; + bringToFront(r1, r2) == 4 || assert(0); + assert(equal(arr, [1, 2, 3, 4, 5, 6, 7])); + } + + // Bugzilla 16959 + auto arr = ['4', '5', '6', '7', '1', '2', '3']; + auto p = bringToFront(arr[0 .. 4], arr[4 .. $]); + + assert(p == arr.length - 4); + assert(arr == ['1', '2', '3', '4', '5', '6', '7']); +} + +// Tests if types are arrays and support slice assign. +private enum bool areCopyCompatibleArrays(T1, T2) = + isArray!T1 && isArray!T2 && is(typeof(T2.init[] = T1.init[])); + +// copy +/** +Copies the content of $(D source) into $(D target) and returns the +remaining (unfilled) part of $(D target). + +Preconditions: $(D target) shall have enough room to accommodate +the entirety of $(D source). + +Params: + source = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + target = an output range + +Returns: + The unfilled part of target + +See_Also: + $(HTTP sgi.com/tech/stl/_copy.html, STL's _copy) + */ +TargetRange copy(SourceRange, TargetRange)(SourceRange source, TargetRange target) +if (areCopyCompatibleArrays!(SourceRange, TargetRange)) +{ + const tlen = target.length; + const slen = source.length; + assert(tlen >= slen, + "Cannot copy a source range into a smaller target range."); + + immutable overlaps = __ctfe || () @trusted { + return source.ptr < target.ptr + tlen && + target.ptr < source.ptr + slen; }(); + + if (overlaps) + { + foreach (idx; 0 .. slen) + target[idx] = source[idx]; + return target[slen .. tlen]; + } + else + { + // Array specialization. This uses optimized memory copying + // routines under the hood and is about 10-20x faster than the + // generic implementation. + target[0 .. slen] = source[]; + return target[slen .. $]; + } +} + +/// ditto +TargetRange copy(SourceRange, TargetRange)(SourceRange source, TargetRange target) +if (!areCopyCompatibleArrays!(SourceRange, TargetRange) && + isInputRange!SourceRange && + isOutputRange!(TargetRange, ElementType!SourceRange)) +{ + // Specialize for 2 random access ranges. + // Typically 2 random access ranges are faster iterated by common + // index than by x.popFront(), y.popFront() pair + static if (isRandomAccessRange!SourceRange && + hasLength!SourceRange && + hasSlicing!TargetRange && + isRandomAccessRange!TargetRange && + hasLength!TargetRange) + { + auto len = source.length; + foreach (idx; 0 .. len) + target[idx] = source[idx]; + return target[len .. target.length]; + } + else + { + put(target, source); + return target; + } +} + +/// +@safe unittest +{ + int[] a = [ 1, 5 ]; + int[] b = [ 9, 8 ]; + int[] buf = new int[](a.length + b.length + 10); + auto rem = a.copy(buf); // copy a into buf + rem = b.copy(rem); // copy b into remainder of buf + assert(buf[0 .. a.length + b.length] == [1, 5, 9, 8]); + assert(rem.length == 10); // unused slots in buf +} + +/** +As long as the target range elements support assignment from source +range elements, different types of ranges are accepted: +*/ +@safe unittest +{ + float[] src = [ 1.0f, 5 ]; + double[] dest = new double[src.length]; + src.copy(dest); +} + +/** +To _copy at most $(D n) elements from a range, you may want to use +$(REF take, std,range): +*/ +@safe unittest +{ + import std.range; + int[] src = [ 1, 5, 8, 9, 10 ]; + auto dest = new int[](3); + src.take(dest.length).copy(dest); + assert(dest == [ 1, 5, 8 ]); +} + +/** +To _copy just those elements from a range that satisfy a predicate, +use $(LREF filter): +*/ +@safe unittest +{ + import std.algorithm.iteration : filter; + int[] src = [ 1, 5, 8, 9, 10, 1, 2, 0 ]; + auto dest = new int[src.length]; + auto rem = src + .filter!(a => (a & 1) == 1) + .copy(dest); + assert(dest[0 .. $ - rem.length] == [ 1, 5, 9, 1 ]); +} + +/** +$(REF retro, std,range) can be used to achieve behavior similar to +$(HTTP sgi.com/tech/stl/copy_backward.html, STL's copy_backward'): +*/ +@safe unittest +{ + import std.algorithm, std.range; + int[] src = [1, 2, 4]; + int[] dest = [0, 0, 0, 0, 0]; + src.retro.copy(dest.retro); + assert(dest == [0, 0, 1, 2, 4]); +} + +// Test CTFE copy. +@safe unittest +{ + enum c = copy([1,2,3], [4,5,6,7]); + assert(c == [7]); +} + + +@safe unittest +{ + import std.algorithm.iteration : filter; + + { + int[] a = [ 1, 5 ]; + int[] b = [ 9, 8 ]; + auto e = copy(filter!("a > 1")(a), b); + assert(b[0] == 5 && e.length == 1); + } + + { + int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + copy(a[5 .. 10], a[4 .. 9]); + assert(a[4 .. 9] == [6, 7, 8, 9, 10]); + } + + { // Test for bug 7898 + enum v = + { + import std.algorithm; + int[] arr1 = [10, 20, 30, 40, 50]; + int[] arr2 = arr1.dup; + copy(arr1, arr2); + return 35; + }(); + assert(v == 35); + } +} + +@safe unittest +{ + // Issue 13650 + import std.meta : AliasSeq; + foreach (Char; AliasSeq!(char, wchar, dchar)) + { + Char[3] a1 = "123"; + Char[6] a2 = "456789"; + assert(copy(a1[], a2[]) is a2[3..$]); + assert(a1[] == "123"); + assert(a2[] == "123789"); + } +} + +/** +Assigns $(D value) to each element of input _range $(D range). + +Params: + range = An + $(REF_ALTTEXT input _range, isInputRange, std,_range,primitives) + that exposes references to its elements and has assignable + elements + value = Assigned to each element of range + +See_Also: + $(LREF uninitializedFill) + $(LREF initializeAll) + */ +void fill(Range, Value)(auto ref Range range, auto ref Value value) +if ((isInputRange!Range && is(typeof(range.front = value)) || + isSomeChar!Value && is(typeof(range[] = value)))) +{ + alias T = ElementType!Range; + + static if (is(typeof(range[] = value))) + { + range[] = value; + } + else static if (is(typeof(range[] = T(value)))) + { + range[] = T(value); + } + else + { + for ( ; !range.empty; range.popFront() ) + { + range.front = value; + } + } +} + +/// +@safe unittest +{ + int[] a = [ 1, 2, 3, 4 ]; + fill(a, 5); + assert(a == [ 5, 5, 5, 5 ]); +} + +// issue 16342, test fallback on mutable narrow strings +@safe unittest +{ + char[] chars = ['a', 'b']; + fill(chars, 'c'); + assert(chars == "cc"); + + char[2] chars2 = ['a', 'b']; + fill(chars2, 'c'); + assert(chars2 == "cc"); + + wchar[] wchars = ['a', 'b']; + fill(wchars, wchar('c')); + assert(wchars == "cc"w); + + dchar[] dchars = ['a', 'b']; + fill(dchars, dchar('c')); + assert(dchars == "cc"d); +} + +@nogc @safe unittest +{ + const(char)[] chars; + assert(chars.length == 0); + static assert(!__traits(compiles, fill(chars, 'c'))); + wstring wchars; + assert(wchars.length == 0); + static assert(!__traits(compiles, fill(wchars, wchar('c')))); +} + +@nogc @safe unittest +{ + char[] chars; + fill(chars, 'c'); + assert(chars == ""c); +} + +@safe unittest +{ + shared(char)[] chrs = ['r']; + fill(chrs, 'c'); + assert(chrs == [shared(char)('c')]); +} + +@nogc @safe unittest +{ + struct Str(size_t len) + { + private char[len] _data; + void opIndexAssign(char value) @safe @nogc + {_data[] = value;} + } + Str!2 str; + str.fill(':'); + assert(str._data == "::"); +} + +@safe unittest +{ + char[] chars = ['a','b','c','d']; + chars[1 .. 3].fill(':'); + assert(chars == "a::d"); +} +// end issue 16342 + +@safe unittest +{ + import std.conv : text; + import std.internal.test.dummyrange; + + int[] a = [ 1, 2, 3 ]; + fill(a, 6); + assert(a == [ 6, 6, 6 ], text(a)); + + void fun0() + { + foreach (i; 0 .. 1000) + { + foreach (ref e; a) e = 6; + } + } + void fun1() { foreach (i; 0 .. 1000) fill(a, 6); } + + // fill should accept InputRange + alias InputRange = DummyRange!(ReturnBy.Reference, Length.No, RangeType.Input); + enum filler = uint.max; + InputRange range; + fill(range, filler); + foreach (value; range.arr) + assert(value == filler); +} + +@safe unittest +{ + //ER8638_1 IS_NOT self assignable + static struct ER8638_1 + { + void opAssign(int){} + } + + //ER8638_1 IS self assignable + static struct ER8638_2 + { + void opAssign(ER8638_2){} + void opAssign(int){} + } + + auto er8638_1 = new ER8638_1[](10); + auto er8638_2 = new ER8638_2[](10); + er8638_1.fill(5); //generic case + er8638_2.fill(5); //opSlice(T.init) case +} + +@safe unittest +{ + { + int[] a = [1, 2, 3]; + immutable(int) b = 0; + a.fill(b); + assert(a == [0, 0, 0]); + } + { + double[] a = [1, 2, 3]; + immutable(int) b = 0; + a.fill(b); + assert(a == [0, 0, 0]); + } +} + +/** +Fills $(D range) with a pattern copied from $(D filler). The length of +$(D range) does not have to be a multiple of the length of $(D +filler). If $(D filler) is empty, an exception is thrown. + +Params: + range = An $(REF_ALTTEXT input _range, isInputRange, std,_range,primitives) + that exposes references to its elements and has assignable elements. + filler = The + $(REF_ALTTEXT forward _range, isForwardRange, std,_range,primitives) + representing the _fill pattern. + */ +void fill(InputRange, ForwardRange)(InputRange range, ForwardRange filler) +if (isInputRange!InputRange + && (isForwardRange!ForwardRange + || (isInputRange!ForwardRange && isInfinite!ForwardRange)) + && is(typeof(InputRange.init.front = ForwardRange.init.front))) +{ + static if (isInfinite!ForwardRange) + { + //ForwardRange is infinite, no need for bounds checking or saving + static if (hasSlicing!ForwardRange && hasLength!InputRange + && is(typeof(filler[0 .. range.length]))) + { + copy(filler[0 .. range.length], range); + } + else + { + //manual feed + for ( ; !range.empty; range.popFront(), filler.popFront()) + { + range.front = filler.front; + } + } + } + else + { + import std.exception : enforce; + + enforce(!filler.empty, "Cannot fill range with an empty filler"); + + static if (hasLength!InputRange && hasLength!ForwardRange + && is(typeof(range.length > filler.length))) + { + //Case we have access to length + immutable len = filler.length; + //Start by bulk copies + while (range.length > len) + { + range = copy(filler.save, range); + } + + //and finally fill the partial range. No need to save here. + static if (hasSlicing!ForwardRange && is(typeof(filler[0 .. range.length]))) + { + //use a quick copy + auto len2 = range.length; + range = copy(filler[0 .. len2], range); + } + else + { + //iterate. No need to check filler, it's length is longer than range's + for (; !range.empty; range.popFront(), filler.popFront()) + { + range.front = filler.front; + } + } + } + else + { + //Most basic case. + auto bck = filler.save; + for (; !range.empty; range.popFront(), filler.popFront()) + { + if (filler.empty) filler = bck.save; + range.front = filler.front; + } + } + } +} + +/// +@safe unittest +{ + int[] a = [ 1, 2, 3, 4, 5 ]; + int[] b = [ 8, 9 ]; + fill(a, b); + assert(a == [ 8, 9, 8, 9, 8 ]); +} + +@safe unittest +{ + import std.exception : assertThrown; + import std.internal.test.dummyrange; + + int[] a = [ 1, 2, 3, 4, 5 ]; + int[] b = [1, 2]; + fill(a, b); + assert(a == [ 1, 2, 1, 2, 1 ]); + + // fill should accept InputRange + alias InputRange = DummyRange!(ReturnBy.Reference, Length.No, RangeType.Input); + InputRange range; + fill(range,[1,2]); + foreach (i,value;range.arr) + assert(value == (i%2 == 0?1:2)); + + //test with a input being a "reference forward" range + fill(a, new ReferenceForwardRange!int([8, 9])); + assert(a == [8, 9, 8, 9, 8]); + + //test with a input being an "infinite input" range + fill(a, new ReferenceInfiniteInputRange!int()); + assert(a == [0, 1, 2, 3, 4]); + + //empty filler test + assertThrown(fill(a, a[$..$])); +} + +/** +Initializes all elements of $(D range) with their $(D .init) value. +Assumes that the elements of the range are uninitialized. + +Params: + range = An + $(REF_ALTTEXT input _range, isInputRange, std,_range,primitives) + that exposes references to its elements and has assignable + elements + +See_Also: + $(LREF fill) + $(LREF uninitializeFill) + */ +void initializeAll(Range)(Range range) +if (isInputRange!Range && hasLvalueElements!Range && hasAssignableElements!Range) +{ + import core.stdc.string : memset, memcpy; + import std.traits : hasElaborateAssign, isDynamicArray; + + alias T = ElementType!Range; + static if (hasElaborateAssign!T) + { + import std.algorithm.internal : addressOf; + //Elaborate opAssign. Must go the memcpy road. + //We avoid calling emplace here, because our goal is to initialize to + //the static state of T.init, + //So we want to avoid any un-necassarilly CC'ing of T.init + auto p = typeid(T).initializer(); + if (p.ptr) + { + for ( ; !range.empty ; range.popFront() ) + { + static if (__traits(isStaticArray, T)) + { + // static array initializer only contains initialization + // for one element of the static array. + auto elemp = cast(void *) addressOf(range.front); + auto endp = elemp + T.sizeof; + while (elemp < endp) + { + memcpy(elemp, p.ptr, p.length); + elemp += p.length; + } + } + else + { + memcpy(addressOf(range.front), p.ptr, T.sizeof); + } + } + } + else + static if (isDynamicArray!Range) + memset(range.ptr, 0, range.length * T.sizeof); + else + for ( ; !range.empty ; range.popFront() ) + memset(addressOf(range.front), 0, T.sizeof); + } + else + fill(range, T.init); +} + +/// ditto +void initializeAll(Range)(Range range) +if (is(Range == char[]) || is(Range == wchar[])) +{ + alias T = ElementEncodingType!Range; + range[] = T.init; +} + +/// +@system unittest +{ + import core.stdc.stdlib : malloc, free; + + struct S + { + int a = 10; + } + + auto s = (cast(S*) malloc(5 * S.sizeof))[0 .. 5]; + initializeAll(s); + assert(s == [S(10), S(10), S(10), S(10), S(10)]); + + scope(exit) free(s.ptr); +} + +@system unittest +{ + import std.algorithm.iteration : filter; + import std.meta : AliasSeq; + import std.traits : hasElaborateAssign; + + //Test strings: + //Must work on narrow strings. + //Must reject const + char[3] a = void; + a[].initializeAll(); + assert(a[] == [char.init, char.init, char.init]); + string s; + assert(!__traits(compiles, s.initializeAll())); + assert(!__traits(compiles, s.initializeAll())); + assert(s.empty); + + //Note: Cannot call uninitializedFill on narrow strings + + enum e {e1, e2} + e[3] b1 = void; + b1[].initializeAll(); + assert(b1[] == [e.e1, e.e1, e.e1]); + e[3] b2 = void; + b2[].uninitializedFill(e.e2); + assert(b2[] == [e.e2, e.e2, e.e2]); + + static struct S1 + { + int i; + } + static struct S2 + { + int i = 1; + } + static struct S3 + { + int i; + this(this){} + } + static struct S4 + { + int i = 1; + this(this){} + } + static assert(!hasElaborateAssign!S1); + static assert(!hasElaborateAssign!S2); + static assert( hasElaborateAssign!S3); + static assert( hasElaborateAssign!S4); + assert(!typeid(S1).initializer().ptr); + assert( typeid(S2).initializer().ptr); + assert(!typeid(S3).initializer().ptr); + assert( typeid(S4).initializer().ptr); + + foreach (S; AliasSeq!(S1, S2, S3, S4)) + { + //initializeAll + { + //Array + S[3] ss1 = void; + ss1[].initializeAll(); + assert(ss1[] == [S.init, S.init, S.init]); + + //Not array + S[3] ss2 = void; + auto sf = ss2[].filter!"true"(); + + sf.initializeAll(); + assert(ss2[] == [S.init, S.init, S.init]); + } + //uninitializedFill + { + //Array + S[3] ss1 = void; + ss1[].uninitializedFill(S(2)); + assert(ss1[] == [S(2), S(2), S(2)]); + + //Not array + S[3] ss2 = void; + auto sf = ss2[].filter!"true"(); + sf.uninitializedFill(S(2)); + assert(ss2[] == [S(2), S(2), S(2)]); + } + } +} + +// test that initializeAll works for arrays of static arrays of structs with +// elaborate assigns. +@system unittest +{ + struct Int { + ~this() {} + int x = 3; + } + Int[2] xs = [Int(1), Int(2)]; + struct R { + bool done; + bool empty() { return done; } + ref Int[2] front() { return xs; } + void popFront() { done = true; } + } + initializeAll(R()); + assert(xs[0].x == 3); + assert(xs[1].x == 3); +} + +// move +/** +Moves `source` into `target`, via a destructive copy when necessary. + +If `T` is a struct with a destructor or postblit defined, source is reset +to its `.init` value after it is moved into target, otherwise it is +left unchanged. + +Preconditions: +If source has internal pointers that point to itself, it cannot be moved, and +will trigger an assertion failure. + +Params: + source = Data to copy. + target = Where to copy into. The destructor, if any, is invoked before the + copy is performed. +*/ +void move(T)(ref T source, ref T target) +{ + // test @safe destructible + static if (__traits(compiles, (T t) @safe {})) + trustedMoveImpl(source, target); + else + moveImpl(source, target); +} + +/// For non-struct types, `move` just performs `target = source`: +@safe unittest +{ + Object obj1 = new Object; + Object obj2 = obj1; + Object obj3; + + move(obj2, obj3); + assert(obj3 is obj1); + // obj2 unchanged + assert(obj2 is obj1); +} + +/// +pure nothrow @safe @nogc unittest +{ + // Structs without destructors are simply copied + struct S1 + { + int a = 1; + int b = 2; + } + S1 s11 = { 10, 11 }; + S1 s12; + + move(s11, s12); + + assert(s12 == S1(10, 11)); + assert(s11 == s12); + + // But structs with destructors or postblits are reset to their .init value + // after copying to the target. + struct S2 + { + int a = 1; + int b = 2; + + ~this() pure nothrow @safe @nogc { } + } + S2 s21 = { 3, 4 }; + S2 s22; + + move(s21, s22); + + assert(s21 == S2(1, 2)); + assert(s22 == S2(3, 4)); +} + +@safe unittest +{ + import std.exception : assertCTFEable; + import std.traits; + + assertCTFEable!((){ + Object obj1 = new Object; + Object obj2 = obj1; + Object obj3; + move(obj2, obj3); + assert(obj3 is obj1); + + static struct S1 { int a = 1, b = 2; } + S1 s11 = { 10, 11 }; + S1 s12; + move(s11, s12); + assert(s11.a == 10 && s11.b == 11 && s12.a == 10 && s12.b == 11); + + static struct S2 { int a = 1; int * b; } + S2 s21 = { 10, null }; + s21.b = new int; + S2 s22; + move(s21, s22); + assert(s21 == s22); + }); + // Issue 5661 test(1) + static struct S3 + { + static struct X { int n = 0; ~this(){n = 0;} } + X x; + } + static assert(hasElaborateDestructor!S3); + S3 s31, s32; + s31.x.n = 1; + move(s31, s32); + assert(s31.x.n == 0); + assert(s32.x.n == 1); + + // Issue 5661 test(2) + static struct S4 + { + static struct X { int n = 0; this(this){n = 0;} } + X x; + } + static assert(hasElaborateCopyConstructor!S4); + S4 s41, s42; + s41.x.n = 1; + move(s41, s42); + assert(s41.x.n == 0); + assert(s42.x.n == 1); + + // Issue 13990 test + class S5; + + S5 s51; + S5 s52 = s51; + S5 s53; + move(s52, s53); + assert(s53 is s51); +} + +/// Ditto +T move(T)(ref T source) +{ + // test @safe destructible + static if (__traits(compiles, (T t) @safe {})) + return trustedMoveImpl(source); + else + return moveImpl(source); +} + +/// Non-copyable structs can still be moved: +pure nothrow @safe @nogc unittest +{ + struct S + { + int a = 1; + @disable this(this); + ~this() pure nothrow @safe @nogc {} + } + S s1; + s1.a = 2; + S s2 = move(s1); + assert(s1.a == 1); + assert(s2.a == 2); +} + +private void trustedMoveImpl(T)(ref T source, ref T target) @trusted +{ + moveImpl(source, target); +} + +private void moveImpl(T)(ref T source, ref T target) +{ + import std.traits : hasElaborateDestructor; + + static if (is(T == struct)) + { + if (&source == &target) return; + // Destroy target before overwriting it + static if (hasElaborateDestructor!T) target.__xdtor(); + } + // move and emplace source into target + moveEmplace(source, target); +} + +private T trustedMoveImpl(T)(ref T source) @trusted +{ + return moveImpl(source); +} + +private T moveImpl(T)(ref T source) +{ + T result = void; + moveEmplace(source, result); + return result; +} + +@safe unittest +{ + import std.exception : assertCTFEable; + import std.traits; + + assertCTFEable!((){ + Object obj1 = new Object; + Object obj2 = obj1; + Object obj3 = move(obj2); + assert(obj3 is obj1); + + static struct S1 { int a = 1, b = 2; } + S1 s11 = { 10, 11 }; + S1 s12 = move(s11); + assert(s11.a == 10 && s11.b == 11 && s12.a == 10 && s12.b == 11); + + static struct S2 { int a = 1; int * b; } + S2 s21 = { 10, null }; + s21.b = new int; + S2 s22 = move(s21); + assert(s21 == s22); + }); + + // Issue 5661 test(1) + static struct S3 + { + static struct X { int n = 0; ~this(){n = 0;} } + X x; + } + static assert(hasElaborateDestructor!S3); + S3 s31; + s31.x.n = 1; + S3 s32 = move(s31); + assert(s31.x.n == 0); + assert(s32.x.n == 1); + + // Issue 5661 test(2) + static struct S4 + { + static struct X { int n = 0; this(this){n = 0;} } + X x; + } + static assert(hasElaborateCopyConstructor!S4); + S4 s41; + s41.x.n = 1; + S4 s42 = move(s41); + assert(s41.x.n == 0); + assert(s42.x.n == 1); + + // Issue 13990 test + class S5; + + S5 s51; + S5 s52 = s51; + S5 s53; + s53 = move(s52); + assert(s53 is s51); +} + +@system unittest +{ + static struct S { int n = 0; ~this() @system { n = 0; } } + S a, b; + static assert(!__traits(compiles, () @safe { move(a, b); })); + static assert(!__traits(compiles, () @safe { move(a); })); + a.n = 1; + () @trusted { move(a, b); }(); + assert(a.n == 0); + a.n = 1; + () @trusted { move(a); }(); + assert(a.n == 0); +} + +@safe unittest//Issue 6217 +{ + import std.algorithm.iteration : map; + auto x = map!"a"([1,2,3]); + x = move(x); +} + +@safe unittest// Issue 8055 +{ + static struct S + { + int x; + ~this() + { + assert(x == 0); + } + } + S foo(S s) + { + return move(s); + } + S a; + a.x = 0; + auto b = foo(a); + assert(b.x == 0); +} + +@system unittest// Issue 8057 +{ + int n = 10; + struct S + { + int x; + ~this() + { + // Access to enclosing scope + assert(n == 10); + } + } + S foo(S s) + { + // Move nested struct + return move(s); + } + S a; + a.x = 1; + auto b = foo(a); + assert(b.x == 1); + + // Regression 8171 + static struct Array(T) + { + // nested struct has no member + struct Payload + { + ~this() {} + } + } + Array!int.Payload x = void; + move(x); + move(x, x); +} + +/** + * Similar to $(LREF move) but assumes `target` is uninitialized. This + * is more efficient because `source` can be blitted over `target` + * without destroying or initializing it first. + * + * Params: + * source = value to be moved into target + * target = uninitialized value to be filled by source + */ +void moveEmplace(T)(ref T source, ref T target) @system +{ + import core.stdc.string : memcpy, memset; + import std.traits : hasAliasing, hasElaborateAssign, + hasElaborateCopyConstructor, hasElaborateDestructor, + isAssignable; + + static if (!is(T == class) && hasAliasing!T) if (!__ctfe) + { + import std.exception : doesPointTo; + assert(!doesPointTo(source, source), "Cannot move object with internal pointer."); + } + + static if (is(T == struct)) + { + assert(&source !is &target, "source and target must not be identical"); + + static if (hasElaborateAssign!T || !isAssignable!T) + memcpy(&target, &source, T.sizeof); + else + target = source; + + // If the source defines a destructor or a postblit hook, we must obliterate the + // object in order to avoid double freeing and undue aliasing + static if (hasElaborateDestructor!T || hasElaborateCopyConstructor!T) + { + // If T is nested struct, keep original context pointer + static if (__traits(isNested, T)) + enum sz = T.sizeof - (void*).sizeof; + else + enum sz = T.sizeof; + + auto init = typeid(T).initializer(); + if (init.ptr is null) // null ptr means initialize to 0s + memset(&source, 0, sz); + else + memcpy(&source, init.ptr, sz); + } + } + else + { + // Primitive data (including pointers and arrays) or class - + // assignment works great + target = source; + } +} + +/// +pure nothrow @nogc @system unittest +{ + static struct Foo + { + pure nothrow @nogc: + this(int* ptr) { _ptr = ptr; } + ~this() { if (_ptr) ++*_ptr; } + int* _ptr; + } + + int val; + Foo foo1 = void; // uninitialized + auto foo2 = Foo(&val); // initialized + assert(foo2._ptr is &val); + + // Using `move(foo2, foo1)` would have an undefined effect because it would destroy + // the uninitialized foo1. + // moveEmplace directly overwrites foo1 without destroying or initializing it first. + moveEmplace(foo2, foo1); + assert(foo1._ptr is &val); + assert(foo2._ptr is null); + assert(val == 0); +} + +// moveAll +/** +Calls `move(a, b)` for each element `a` in `src` and the corresponding +element `b` in `tgt`, in increasing order. + +Preconditions: +`walkLength(src) <= walkLength(tgt)`. +This precondition will be asserted. If you cannot ensure there is enough room in +`tgt` to accommodate all of `src` use $(LREF moveSome) instead. + +Params: + src = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with + movable elements. + tgt = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with + elements that elements from $(D src) can be moved into. + +Returns: The leftover portion of $(D tgt) after all elements from $(D src) have +been moved. + */ +InputRange2 moveAll(InputRange1, InputRange2)(InputRange1 src, InputRange2 tgt) +if (isInputRange!InputRange1 && isInputRange!InputRange2 + && is(typeof(move(src.front, tgt.front)))) +{ + return moveAllImpl!move(src, tgt); +} + +/// +pure nothrow @safe @nogc unittest +{ + int[3] a = [ 1, 2, 3 ]; + int[5] b; + assert(moveAll(a[], b[]) is b[3 .. $]); + assert(a[] == b[0 .. 3]); + int[3] cmp = [ 1, 2, 3 ]; + assert(a[] == cmp[]); +} + +/** + * Similar to $(LREF moveAll) but assumes all elements in `tgt` are + * uninitialized. Uses $(LREF moveEmplace) to move elements from + * `src` over elements from `tgt`. + */ +InputRange2 moveEmplaceAll(InputRange1, InputRange2)(InputRange1 src, InputRange2 tgt) @system +if (isInputRange!InputRange1 && isInputRange!InputRange2 + && is(typeof(moveEmplace(src.front, tgt.front)))) +{ + return moveAllImpl!moveEmplace(src, tgt); +} + +/// +pure nothrow @nogc @system unittest +{ + static struct Foo + { + ~this() pure nothrow @nogc { if (_ptr) ++*_ptr; } + int* _ptr; + } + int[3] refs = [0, 1, 2]; + Foo[3] src = [Foo(&refs[0]), Foo(&refs[1]), Foo(&refs[2])]; + Foo[5] dst = void; + + auto tail = moveEmplaceAll(src[], dst[]); // move 3 value from src over dst + assert(tail.length == 2); // returns remaining uninitialized values + initializeAll(tail); + + import std.algorithm.searching : all; + assert(src[].all!(e => e._ptr is null)); + assert(dst[0 .. 3].all!(e => e._ptr !is null)); +} + +@system unittest +{ + struct InputRange + { + ref int front() { return data[0]; } + void popFront() { data.popFront; } + bool empty() { return data.empty; } + int[] data; + } + auto a = InputRange([ 1, 2, 3 ]); + auto b = InputRange(new int[5]); + moveAll(a, b); + assert(a.data == b.data[0 .. 3]); + assert(a.data == [ 1, 2, 3 ]); +} + +private InputRange2 moveAllImpl(alias moveOp, InputRange1, InputRange2)( + ref InputRange1 src, ref InputRange2 tgt) +{ + import std.exception : enforce; + + static if (isRandomAccessRange!InputRange1 && hasLength!InputRange1 && hasLength!InputRange2 + && hasSlicing!InputRange2 && isRandomAccessRange!InputRange2) + { + auto toMove = src.length; + assert(toMove <= tgt.length); + foreach (idx; 0 .. toMove) + moveOp(src[idx], tgt[idx]); + return tgt[toMove .. tgt.length]; + } + else + { + for (; !src.empty; src.popFront(), tgt.popFront()) + { + assert(!tgt.empty); + moveOp(src.front, tgt.front); + } + return tgt; + } +} + +// moveSome +/** +Calls `move(a, b)` for each element `a` in `src` and the corresponding +element `b` in `tgt`, in increasing order, stopping when either range has been +exhausted. + +Params: + src = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with + movable elements. + tgt = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with + elements that elements from $(D src) can be moved into. + +Returns: The leftover portions of the two ranges after one or the other of the +ranges have been exhausted. + */ +Tuple!(InputRange1, InputRange2) moveSome(InputRange1, InputRange2)(InputRange1 src, InputRange2 tgt) +if (isInputRange!InputRange1 && isInputRange!InputRange2 + && is(typeof(move(src.front, tgt.front)))) +{ + return moveSomeImpl!move(src, tgt); +} + +/// +pure nothrow @safe @nogc unittest +{ + int[5] a = [ 1, 2, 3, 4, 5 ]; + int[3] b; + assert(moveSome(a[], b[])[0] is a[3 .. $]); + assert(a[0 .. 3] == b); + assert(a == [ 1, 2, 3, 4, 5 ]); +} + +/** + * Same as $(LREF moveSome) but assumes all elements in `tgt` are + * uninitialized. Uses $(LREF moveEmplace) to move elements from + * `src` over elements from `tgt`. + */ +Tuple!(InputRange1, InputRange2) moveEmplaceSome(InputRange1, InputRange2)(InputRange1 src, InputRange2 tgt) @system +if (isInputRange!InputRange1 && isInputRange!InputRange2 + && is(typeof(move(src.front, tgt.front)))) +{ + return moveSomeImpl!moveEmplace(src, tgt); +} + +/// +pure nothrow @nogc @system unittest +{ + static struct Foo + { + ~this() pure nothrow @nogc { if (_ptr) ++*_ptr; } + int* _ptr; + } + int[4] refs = [0, 1, 2, 3]; + Foo[4] src = [Foo(&refs[0]), Foo(&refs[1]), Foo(&refs[2]), Foo(&refs[3])]; + Foo[3] dst = void; + + auto res = moveEmplaceSome(src[], dst[]); + assert(res.length == 2); + + import std.algorithm.searching : all; + assert(src[0 .. 3].all!(e => e._ptr is null)); + assert(src[3]._ptr !is null); + assert(dst[].all!(e => e._ptr !is null)); +} + +private Tuple!(InputRange1, InputRange2) moveSomeImpl(alias moveOp, InputRange1, InputRange2)( + ref InputRange1 src, ref InputRange2 tgt) +{ + for (; !src.empty && !tgt.empty; src.popFront(), tgt.popFront()) + moveOp(src.front, tgt.front); + return tuple(src, tgt); + } + + +// SwapStrategy +/** +Defines the swapping strategy for algorithms that need to swap +elements in a range (such as partition and sort). The strategy +concerns the swapping of elements that are not the core concern of the +algorithm. For example, consider an algorithm that sorts $(D [ "abc", +"b", "aBc" ]) according to $(D toUpper(a) < toUpper(b)). That +algorithm might choose to swap the two equivalent strings $(D "abc") +and $(D "aBc"). That does not affect the sorting since both $(D [ +"abc", "aBc", "b" ]) and $(D [ "aBc", "abc", "b" ]) are valid +outcomes. + +Some situations require that the algorithm must NOT ever change the +relative ordering of equivalent elements (in the example above, only +$(D [ "abc", "aBc", "b" ]) would be the correct result). Such +algorithms are called $(B stable). If the ordering algorithm may swap +equivalent elements discretionarily, the ordering is called $(B +unstable). + +Yet another class of algorithms may choose an intermediate tradeoff by +being stable only on a well-defined subrange of the range. There is no +established terminology for such behavior; this library calls it $(B +semistable). + +Generally, the $(D stable) ordering strategy may be more costly in +time and/or space than the other two because it imposes additional +constraints. Similarly, $(D semistable) may be costlier than $(D +unstable). As (semi-)stability is not needed very often, the ordering +algorithms in this module parameterized by $(D SwapStrategy) all +choose $(D SwapStrategy.unstable) as the default. +*/ + +enum SwapStrategy +{ + /** + Allows freely swapping of elements as long as the output + satisfies the algorithm's requirements. + */ + unstable, + /** + In algorithms partitioning ranges in two, preserve relative + ordering of elements only to the left of the partition point. + */ + semistable, + /** + Preserve the relative ordering of elements to the largest + extent allowed by the algorithm's requirements. + */ + stable, +} + +/// +@safe unittest +{ + import std.stdio; + import std.algorithm.sorting : partition; + int[] a = [0, 1, 2, 3]; + assert(remove!(SwapStrategy.stable)(a, 1) == [0, 2, 3]); + a = [0, 1, 2, 3]; + assert(remove!(SwapStrategy.unstable)(a, 1) == [0, 3, 2]); +} + +/// +@safe unittest +{ + import std.algorithm.sorting : partition; + + // Put stuff greater than 3 on the left + auto arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + assert(partition!(a => a > 3, SwapStrategy.stable)(arr) == [1, 2, 3]); + assert(arr == [4, 5, 6, 7, 8, 9, 10, 1, 2, 3]); + + arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + assert(partition!(a => a > 3, SwapStrategy.semistable)(arr) == [2, 3, 1]); + assert(arr == [4, 5, 6, 7, 8, 9, 10, 2, 3, 1]); + + arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + assert(partition!(a => a > 3, SwapStrategy.unstable)(arr) == [3, 2, 1]); + assert(arr == [10, 9, 8, 4, 5, 6, 7, 3, 2, 1]); +} + +/** +Eliminates elements at given offsets from `range` and returns the shortened +range. + +For example, here is how to _remove a single element from an array: + +---- +string[] a = [ "a", "b", "c", "d" ]; +a = a.remove(1); // remove element at offset 1 +assert(a == [ "a", "c", "d"]); +---- + +Note that `remove` does not change the length of the original _range directly; +instead, it returns the shortened _range. If its return value is not assigned to +the original _range, the original _range will retain its original length, though +its contents will have changed: + +---- +int[] a = [ 3, 5, 7, 8 ]; +assert(remove(a, 1) == [ 3, 7, 8 ]); +assert(a == [ 3, 7, 8, 8 ]); +---- + +The element at _offset `1` has been removed and the rest of the elements have +shifted up to fill its place, however, the original array remains of the same +length. This is because all functions in `std.algorithm` only change $(I +content), not $(I topology). The value `8` is repeated because $(LREF move) was +invoked to rearrange elements, and on integers `move` simply copies the source +to the destination. To replace `a` with the effect of the removal, simply +assign the slice returned by `remove` to it, as shown in the first example. + +Multiple indices can be passed into $(D remove). In that case, +elements at the respective indices are all removed. The indices must +be passed in increasing order, otherwise an exception occurs. + +---- +int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; +assert(remove(a, 1, 3, 5) == + [ 0, 2, 4, 6, 7, 8, 9, 10 ]); +---- + +(Note that all indices refer to slots in the $(I original) array, not +in the array as it is being progressively shortened.) Finally, any +combination of integral offsets and tuples composed of two integral +offsets can be passed in. + +---- +int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; +assert(remove(a, 1, tuple(3, 5), 9) == [ 0, 2, 5, 6, 7, 8, 10 ]); +---- + +In this case, the slots at positions 1, 3, 4, and 9 are removed from +the array. The tuple passes in a range closed to the left and open to +the right (consistent with built-in slices), e.g. $(D tuple(3, 5)) +means indices $(D 3) and $(D 4) but not $(D 5). + +If the need is to remove some elements in the range but the order of +the remaining elements does not have to be preserved, you may want to +pass $(D SwapStrategy.unstable) to $(D remove). + +---- +int[] a = [ 0, 1, 2, 3 ]; +assert(remove!(SwapStrategy.unstable)(a, 1) == [ 0, 3, 2 ]); +---- + +In the case above, the element at slot $(D 1) is removed, but replaced +with the last element of the range. Taking advantage of the relaxation +of the stability requirement, $(D remove) moved elements from the end +of the array over the slots to be removed. This way there is less data +movement to be done which improves the execution time of the function. + +The function $(D remove) works on bidirectional ranges that have assignable +lvalue elements. The moving strategy is (listed from fastest to slowest): +$(UL $(LI If $(D s == SwapStrategy.unstable && isRandomAccessRange!Range && +hasLength!Range && hasLvalueElements!Range), then elements are moved from the +end of the range into the slots to be filled. In this case, the absolute +minimum of moves is performed.) $(LI Otherwise, if $(D s == +SwapStrategy.unstable && isBidirectionalRange!Range && hasLength!Range +&& hasLvalueElements!Range), then elements are still moved from the +end of the range, but time is spent on advancing between slots by repeated +calls to $(D range.popFront).) $(LI Otherwise, elements are moved +incrementally towards the front of $(D range); a given element is never +moved several times, but more elements are moved than in the previous +cases.)) + +Params: + s = a SwapStrategy to determine if the original order needs to be preserved + range = a $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,_range,primitives) + with a length member + offset = which element(s) to remove + +Returns: + a range containing all of the elements of range with offset removed + */ +Range remove +(SwapStrategy s = SwapStrategy.stable, Range, Offset...) +(Range range, Offset offset) +if (s != SwapStrategy.stable + && isBidirectionalRange!Range + && hasLvalueElements!Range + && hasLength!Range + && Offset.length >= 1) +{ + Tuple!(size_t, "pos", size_t, "len")[offset.length] blackouts; + foreach (i, v; offset) + { + static if (is(typeof(v[0]) : size_t) && is(typeof(v[1]) : size_t)) + { + blackouts[i].pos = v[0]; + blackouts[i].len = v[1] - v[0]; + } + else + { + static assert(is(typeof(v) : size_t), typeof(v).stringof); + blackouts[i].pos = v; + blackouts[i].len = 1; + } + static if (i > 0) + { + import std.exception : enforce; + + enforce(blackouts[i - 1].pos + blackouts[i - 1].len + <= blackouts[i].pos, + "remove(): incorrect ordering of elements to remove"); + } + } + + size_t left = 0, right = offset.length - 1; + auto tgt = range.save; + size_t tgtPos = 0; + + while (left <= right) + { + // Look for a blackout on the right + if (blackouts[right].pos + blackouts[right].len >= range.length) + { + range.popBackExactly(blackouts[right].len); + + // Since right is unsigned, we must check for this case, otherwise + // we might turn it into size_t.max and the loop condition will not + // fail when it should. + if (right > 0) + { + --right; + continue; + } + else + break; + } + // Advance to next blackout on the left + assert(blackouts[left].pos >= tgtPos); + tgt.popFrontExactly(blackouts[left].pos - tgtPos); + tgtPos = blackouts[left].pos; + + // Number of elements to the right of blackouts[right] + immutable tailLen = range.length - (blackouts[right].pos + blackouts[right].len); + size_t toMove = void; + if (tailLen < blackouts[left].len) + { + toMove = tailLen; + blackouts[left].pos += toMove; + blackouts[left].len -= toMove; + } + else + { + toMove = blackouts[left].len; + ++left; + } + tgtPos += toMove; + foreach (i; 0 .. toMove) + { + move(range.back, tgt.front); + range.popBack(); + tgt.popFront(); + } + } + + return range; +} + +/// Ditto +Range remove +(SwapStrategy s = SwapStrategy.stable, Range, Offset...) +(Range range, Offset offset) +if (s == SwapStrategy.stable + && isBidirectionalRange!Range + && hasLvalueElements!Range + && Offset.length >= 1) +{ + auto result = range; + auto src = range, tgt = range; + size_t pos; + foreach (pass, i; offset) + { + static if (is(typeof(i[0])) && is(typeof(i[1]))) + { + auto from = i[0], delta = i[1] - i[0]; + } + else + { + auto from = i; + enum delta = 1; + } + + static if (pass > 0) + { + import std.exception : enforce; + enforce(pos <= from, + "remove(): incorrect ordering of elements to remove"); + + for (; pos < from; ++pos, src.popFront(), tgt.popFront()) + { + move(src.front, tgt.front); + } + } + else + { + src.popFrontExactly(from); + tgt.popFrontExactly(from); + pos = from; + } + // now skip source to the "to" position + src.popFrontExactly(delta); + result.popBackExactly(delta); + pos += delta; + } + // leftover move + moveAll(src, tgt); + return result; +} + +/// +@safe pure unittest +{ + import std.typecons : tuple; + + auto a = [ 0, 1, 2, 3, 4, 5 ]; + assert(remove!(SwapStrategy.stable)(a, 1) == [ 0, 2, 3, 4, 5 ]); + a = [ 0, 1, 2, 3, 4, 5 ]; + assert(remove!(SwapStrategy.stable)(a, 1, 3) == [ 0, 2, 4, 5] ); + a = [ 0, 1, 2, 3, 4, 5 ]; + assert(remove!(SwapStrategy.stable)(a, 1, tuple(3, 6)) == [ 0, 2 ]); + + a = [ 0, 1, 2, 3, 4, 5 ]; + assert(remove!(SwapStrategy.unstable)(a, 1) == [0, 5, 2, 3, 4]); + a = [ 0, 1, 2, 3, 4, 5 ]; + assert(remove!(SwapStrategy.unstable)(a, tuple(1, 4)) == [0, 5, 4]); +} + +@safe unittest +{ + import std.exception : assertThrown; + import std.range; + + // http://d.puremagic.com/issues/show_bug.cgi?id=10173 + int[] test = iota(0, 10).array(); + assertThrown(remove!(SwapStrategy.stable)(test, tuple(2, 4), tuple(1, 3))); + assertThrown(remove!(SwapStrategy.unstable)(test, tuple(2, 4), tuple(1, 3))); + assertThrown(remove!(SwapStrategy.stable)(test, 2, 4, 1, 3)); + assertThrown(remove!(SwapStrategy.unstable)(test, 2, 4, 1, 3)); +} + +@safe unittest +{ + import std.range; + int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + assert(remove!(SwapStrategy.stable)(a, 1) == + [ 0, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]); + + a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + assert(remove!(SwapStrategy.unstable)(a, 0, 10) == + [ 9, 1, 2, 3, 4, 5, 6, 7, 8 ]); + + a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + assert(remove!(SwapStrategy.unstable)(a, 0, tuple(9, 11)) == + [ 8, 1, 2, 3, 4, 5, 6, 7 ]); + // http://d.puremagic.com/issues/show_bug.cgi?id=5224 + a = [ 1, 2, 3, 4 ]; + assert(remove!(SwapStrategy.unstable)(a, 2) == + [ 1, 2, 4 ]); + + a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + assert(remove!(SwapStrategy.stable)(a, 1, 5) == + [ 0, 2, 3, 4, 6, 7, 8, 9, 10 ]); + + a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + assert(remove!(SwapStrategy.stable)(a, 1, 3, 5) + == [ 0, 2, 4, 6, 7, 8, 9, 10]); + a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + assert(remove!(SwapStrategy.stable)(a, 1, tuple(3, 5)) + == [ 0, 2, 5, 6, 7, 8, 9, 10]); + + a = iota(0, 10).array(); + assert(remove!(SwapStrategy.unstable)(a, tuple(1, 4), tuple(6, 7)) + == [0, 9, 8, 7, 4, 5]); +} + +@safe unittest +{ + // Issue 11576 + auto arr = [1,2,3]; + arr = arr.remove!(SwapStrategy.unstable)(2); + assert(arr == [1,2]); + +} + +@safe unittest +{ + import std.range; + // Bug# 12889 + int[1][] arr = [[0], [1], [2], [3], [4], [5], [6]]; + auto orig = arr.dup; + foreach (i; iota(arr.length)) + { + assert(orig == arr.remove!(SwapStrategy.unstable)(tuple(i,i))); + assert(orig == arr.remove!(SwapStrategy.stable)(tuple(i,i))); + } +} + +/** +Reduces the length of the +$(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,_range,primitives) $(D range) by removing +elements that satisfy $(D pred). If $(D s = SwapStrategy.unstable), +elements are moved from the right end of the range over the elements +to eliminate. If $(D s = SwapStrategy.stable) (the default), +elements are moved progressively to front such that their relative +order is preserved. Returns the filtered range. + +Params: + range = a bidirectional ranges with lvalue elements + +Returns: + the range with all of the elements where $(D pred) is $(D true) + removed +*/ +Range remove(alias pred, SwapStrategy s = SwapStrategy.stable, Range) +(Range range) +if (isBidirectionalRange!Range + && hasLvalueElements!Range) +{ + import std.functional : unaryFun; + auto result = range; + static if (s != SwapStrategy.stable) + { + for (;!range.empty;) + { + if (!unaryFun!pred(range.front)) + { + range.popFront(); + continue; + } + move(range.back, range.front); + range.popBack(); + result.popBack(); + } + } + else + { + auto tgt = range; + for (; !range.empty; range.popFront()) + { + if (unaryFun!(pred)(range.front)) + { + // yank this guy + result.popBack(); + continue; + } + // keep this guy + move(range.front, tgt.front); + tgt.popFront(); + } + } + return result; +} + +/// +@safe unittest +{ + static immutable base = [1, 2, 3, 2, 4, 2, 5, 2]; + + int[] arr = base[].dup; + + // using a string-based predicate + assert(remove!("a == 2")(arr) == [ 1, 3, 4, 5 ]); + + // The original array contents have been modified, + // so we need to reset it to its original state. + // The length is unmodified however. + arr[] = base[]; + + // using a lambda predicate + assert(remove!(a => a == 2)(arr) == [ 1, 3, 4, 5 ]); +} + +@safe unittest +{ + int[] a = [ 1, 2, 3, 2, 3, 4, 5, 2, 5, 6 ]; + assert(remove!("a == 2", SwapStrategy.unstable)(a) == + [ 1, 6, 3, 5, 3, 4, 5 ]); + a = [ 1, 2, 3, 2, 3, 4, 5, 2, 5, 6 ]; + assert(remove!("a == 2", SwapStrategy.stable)(a) == + [ 1, 3, 3, 4, 5, 5, 6 ]); +} + +@nogc @system unittest +{ + // @nogc test + int[10] arr = [0,1,2,3,4,5,6,7,8,9]; + alias pred = e => e < 5; + + auto r = arr[].remove!(SwapStrategy.unstable)(0); + r = r.remove!(SwapStrategy.stable)(0); + r = r.remove!(pred, SwapStrategy.unstable); + r = r.remove!(pred, SwapStrategy.stable); +} + +@safe unittest +{ + import std.algorithm.comparison : min; + import std.algorithm.searching : all, any; + import std.algorithm.sorting : isStrictlyMonotonic; + import std.array : array; + import std.meta : AliasSeq; + import std.range : iota, only; + import std.typecons : Tuple; + alias S = Tuple!(int[2]); + S[] soffsets; + foreach (start; 0 .. 5) + foreach (end; min(start+1,5) .. 5) + soffsets ~= S([start,end]); + alias D = Tuple!(int[2],int[2]); + D[] doffsets; + foreach (start1; 0 .. 10) + foreach (end1; min(start1+1,10) .. 10) + foreach (start2; end1 .. 10) + foreach (end2; min(start2+1,10) .. 10) + doffsets ~= D([start1,end1],[start2,end2]); + alias T = Tuple!(int[2],int[2],int[2]); + T[] toffsets; + foreach (start1; 0 .. 15) + foreach (end1; min(start1+1,15) .. 15) + foreach (start2; end1 .. 15) + foreach (end2; min(start2+1,15) .. 15) + foreach (start3; end2 .. 15) + foreach (end3; min(start3+1,15) .. 15) + toffsets ~= T([start1,end1],[start2,end2],[start3,end3]); + + static void verify(O...)(int[] r, int len, int removed, bool stable, O offsets) + { + assert(r.length == len - removed); + assert(!stable || r.isStrictlyMonotonic); + assert(r.all!(e => all!(o => e < o[0] || e >= o[1])(offsets.only))); + } + + foreach (offsets; AliasSeq!(soffsets,doffsets,toffsets)) + foreach (os; offsets) + { + int len = 5*os.length; + auto w = iota(0, len).array; + auto x = w.dup; + auto y = w.dup; + auto z = w.dup; + alias pred = e => any!(o => o[0] <= e && e < o[1])(only(os.expand)); + w = w.remove!(SwapStrategy.unstable)(os.expand); + x = x.remove!(SwapStrategy.stable)(os.expand); + y = y.remove!(pred, SwapStrategy.unstable); + z = z.remove!(pred, SwapStrategy.stable); + int removed; + foreach (o; os) + removed += o[1] - o[0]; + verify(w, len, removed, false, os[]); + verify(x, len, removed, true, os[]); + verify(y, len, removed, false, os[]); + verify(z, len, removed, true, os[]); + assert(w == y); + assert(x == z); + } +} + +// reverse +/** +Reverses $(D r) in-place. Performs $(D r.length / 2) evaluations of $(D +swap). +Params: + r = a $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives) + with swappable elements or a random access range with a length member + +See_Also: + $(HTTP sgi.com/tech/stl/_reverse.html, STL's _reverse), $(REF retro, std,range) for a lazy reversed range view +*/ +void reverse(Range)(Range r) +if (isBidirectionalRange!Range && !isRandomAccessRange!Range + && hasSwappableElements!Range) +{ + while (!r.empty) + { + swap(r.front, r.back); + r.popFront(); + if (r.empty) break; + r.popBack(); + } +} + +/// +@safe unittest +{ + int[] arr = [ 1, 2, 3 ]; + reverse(arr); + assert(arr == [ 3, 2, 1 ]); +} + +///ditto +void reverse(Range)(Range r) +if (isRandomAccessRange!Range && hasLength!Range) +{ + //swapAt is in fact the only way to swap non lvalue ranges + immutable last = r.length-1; + immutable steps = r.length/2; + for (size_t i = 0; i < steps; i++) + { + r.swapAt(i, last-i); + } +} + +@safe unittest +{ + int[] range = null; + reverse(range); + range = [ 1 ]; + reverse(range); + assert(range == [1]); + range = [1, 2]; + reverse(range); + assert(range == [2, 1]); + range = [1, 2, 3]; + reverse(range); + assert(range == [3, 2, 1]); +} + +/** +Reverses $(D r) in-place, where $(D r) is a narrow string (having +elements of type $(D char) or $(D wchar)). UTF sequences consisting of +multiple code units are preserved properly. + +Params: + s = a narrow string + +Bugs: + When passing a sting with unicode modifiers on characters, such as $(D \u0301), + this function will not properly keep the position of the modifier. For example, + reversing $(D ba\u0301d) ("bád") will result in d\u0301ab ("d́ab") instead of + $(D da\u0301b) ("dáb"). +*/ +void reverse(Char)(Char[] s) +if (isNarrowString!(Char[]) && !is(Char == const) && !is(Char == immutable)) +{ + import std.string : representation; + import std.utf : stride; + + auto r = representation(s); + for (size_t i = 0; i < s.length; ) + { + immutable step = stride(s, i); + if (step > 1) + { + .reverse(r[i .. i + step]); + i += step; + } + else + { + ++i; + } + } + reverse(r); +} + +/// +@safe unittest +{ + char[] arr = "hello\U00010143\u0100\U00010143".dup; + reverse(arr); + assert(arr == "\U00010143\u0100\U00010143olleh"); +} + +@safe unittest +{ + void test(string a, string b) + { + auto c = a.dup; + reverse(c); + assert(c == b, c ~ " != " ~ b); + } + + test("a", "a"); + test(" ", " "); + test("\u2029", "\u2029"); + test("\u0100", "\u0100"); + test("\u0430", "\u0430"); + test("\U00010143", "\U00010143"); + test("abcdefcdef", "fedcfedcba"); + test("hello\U00010143\u0100\U00010143", "\U00010143\u0100\U00010143olleh"); +} + +/** + The strip group of functions allow stripping of either leading, trailing, + or both leading and trailing elements. + + The $(D stripLeft) function will strip the $(D front) of the range, + the $(D stripRight) function will strip the $(D back) of the range, + while the $(D strip) function will strip both the $(D front) and $(D back) + of the range. + + Note that the $(D strip) and $(D stripRight) functions require the range to + be a $(LREF BidirectionalRange) range. + + All of these functions come in two varieties: one takes a target element, + where the range will be stripped as long as this element can be found. + The other takes a lambda predicate, where the range will be stripped as + long as the predicate returns true. + + Params: + range = a $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives) + or $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + element = the elements to remove + + Returns: + a Range with all of range except element at the start and end +*/ +Range strip(Range, E)(Range range, E element) +if (isBidirectionalRange!Range && is(typeof(range.front == element) : bool)) +{ + return range.stripLeft(element).stripRight(element); +} + +/// ditto +Range strip(alias pred, Range)(Range range) +if (isBidirectionalRange!Range && is(typeof(pred(range.back)) : bool)) +{ + return range.stripLeft!pred().stripRight!pred(); +} + +/// ditto +Range stripLeft(Range, E)(Range range, E element) +if (isInputRange!Range && is(typeof(range.front == element) : bool)) +{ + import std.algorithm.searching : find; + return find!((auto ref a) => a != element)(range); +} + +/// ditto +Range stripLeft(alias pred, Range)(Range range) +if (isInputRange!Range && is(typeof(pred(range.front)) : bool)) +{ + import std.algorithm.searching : find; + import std.functional : not; + + return find!(not!pred)(range); +} + +/// ditto +Range stripRight(Range, E)(Range range, E element) +if (isBidirectionalRange!Range && is(typeof(range.back == element) : bool)) +{ + for (; !range.empty; range.popBack()) + { + if (range.back != element) + break; + } + return range; +} + +/// ditto +Range stripRight(alias pred, Range)(Range range) +if (isBidirectionalRange!Range && is(typeof(pred(range.back)) : bool)) +{ + for (; !range.empty; range.popBack()) + { + if (!pred(range.back)) + break; + } + return range; +} + +/// Strip leading and trailing elements equal to the target element. +@safe pure unittest +{ + assert(" foobar ".strip(' ') == "foobar"); + assert("00223.444500".strip('0') == "223.4445"); + assert("ëëêéüŗōpéêëë".strip('ë') == "êéüŗōpéê"); + assert([1, 1, 0, 1, 1].strip(1) == [0]); + assert([0.0, 0.01, 0.01, 0.0].strip(0).length == 2); +} + +/// Strip leading and trailing elements while the predicate returns true. +@safe pure unittest +{ + assert(" foobar ".strip!(a => a == ' ')() == "foobar"); + assert("00223.444500".strip!(a => a == '0')() == "223.4445"); + assert("ëëêéüŗōpéêëë".strip!(a => a == 'ë')() == "êéüŗōpéê"); + assert([1, 1, 0, 1, 1].strip!(a => a == 1)() == [0]); + assert([0.0, 0.01, 0.5, 0.6, 0.01, 0.0].strip!(a => a < 0.4)().length == 2); +} + +/// Strip leading elements equal to the target element. +@safe pure unittest +{ + assert(" foobar ".stripLeft(' ') == "foobar "); + assert("00223.444500".stripLeft('0') == "223.444500"); + assert("ůůűniçodêéé".stripLeft('ů') == "űniçodêéé"); + assert([1, 1, 0, 1, 1].stripLeft(1) == [0, 1, 1]); + assert([0.0, 0.01, 0.01, 0.0].stripLeft(0).length == 3); +} + +/// Strip leading elements while the predicate returns true. +@safe pure unittest +{ + assert(" foobar ".stripLeft!(a => a == ' ')() == "foobar "); + assert("00223.444500".stripLeft!(a => a == '0')() == "223.444500"); + assert("ůůűniçodêéé".stripLeft!(a => a == 'ů')() == "űniçodêéé"); + assert([1, 1, 0, 1, 1].stripLeft!(a => a == 1)() == [0, 1, 1]); + assert([0.0, 0.01, 0.10, 0.5, 0.6].stripLeft!(a => a < 0.4)().length == 2); +} + +/// Strip trailing elements equal to the target element. +@safe pure unittest +{ + assert(" foobar ".stripRight(' ') == " foobar"); + assert("00223.444500".stripRight('0') == "00223.4445"); + assert("ùniçodêéé".stripRight('é') == "ùniçodê"); + assert([1, 1, 0, 1, 1].stripRight(1) == [1, 1, 0]); + assert([0.0, 0.01, 0.01, 0.0].stripRight(0).length == 3); +} + +/// Strip trailing elements while the predicate returns true. +@safe pure unittest +{ + assert(" foobar ".stripRight!(a => a == ' ')() == " foobar"); + assert("00223.444500".stripRight!(a => a == '0')() == "00223.4445"); + assert("ùniçodêéé".stripRight!(a => a == 'é')() == "ùniçodê"); + assert([1, 1, 0, 1, 1].stripRight!(a => a == 1)() == [1, 1, 0]); + assert([0.0, 0.01, 0.10, 0.5, 0.6].stripRight!(a => a > 0.4)().length == 3); +} + +// swap +/** +Swaps $(D lhs) and $(D rhs). The instances $(D lhs) and $(D rhs) are moved in +memory, without ever calling $(D opAssign), nor any other function. $(D T) +need not be assignable at all to be swapped. + +If $(D lhs) and $(D rhs) reference the same instance, then nothing is done. + +$(D lhs) and $(D rhs) must be mutable. If $(D T) is a struct or union, then +its fields must also all be (recursively) mutable. + +Params: + lhs = Data to be swapped with $(D rhs). + rhs = Data to be swapped with $(D lhs). +*/ +void swap(T)(ref T lhs, ref T rhs) @trusted pure nothrow @nogc +if (isBlitAssignable!T && !is(typeof(lhs.proxySwap(rhs)))) +{ + import std.traits : hasAliasing, hasElaborateAssign, isAssignable, + isStaticArray; + static if (hasAliasing!T) if (!__ctfe) + { + import std.exception : doesPointTo; + assert(!doesPointTo(lhs, lhs), "Swap: lhs internal pointer."); + assert(!doesPointTo(rhs, rhs), "Swap: rhs internal pointer."); + assert(!doesPointTo(lhs, rhs), "Swap: lhs points to rhs."); + assert(!doesPointTo(rhs, lhs), "Swap: rhs points to lhs."); + } + + static if (hasElaborateAssign!T || !isAssignable!T) + { + if (&lhs != &rhs) + { + // For structs with non-trivial assignment, move memory directly + ubyte[T.sizeof] t = void; + auto a = (cast(ubyte*) &lhs)[0 .. T.sizeof]; + auto b = (cast(ubyte*) &rhs)[0 .. T.sizeof]; + t[] = a[]; + a[] = b[]; + b[] = t[]; + } + } + else + { + //Avoid assigning overlapping arrays. Dynamic arrays are fine, because + //it's their ptr and length properties which get assigned rather + //than their elements when assigning them, but static arrays are value + //types and therefore all of their elements get copied as part of + //assigning them, which would be assigning overlapping arrays if lhs + //and rhs were the same array. + static if (isStaticArray!T) + { + if (lhs.ptr == rhs.ptr) + return; + } + + // For non-struct types, suffice to do the classic swap + auto tmp = lhs; + lhs = rhs; + rhs = tmp; + } +} + +/// +@safe unittest +{ + // Swapping POD (plain old data) types: + int a = 42, b = 34; + swap(a, b); + assert(a == 34 && b == 42); + + // Swapping structs with indirection: + static struct S { int x; char c; int[] y; } + S s1 = { 0, 'z', [ 1, 2 ] }; + S s2 = { 42, 'a', [ 4, 6 ] }; + swap(s1, s2); + assert(s1.x == 42); + assert(s1.c == 'a'); + assert(s1.y == [ 4, 6 ]); + + assert(s2.x == 0); + assert(s2.c == 'z'); + assert(s2.y == [ 1, 2 ]); + + // Immutables cannot be swapped: + immutable int imm1 = 1, imm2 = 2; + static assert(!__traits(compiles, swap(imm1, imm2))); + + int c = imm1 + 0; + int d = imm2 + 0; + swap(c, d); + assert(c == 2); + assert(d == 1); +} + +/// +@safe unittest +{ + // Non-copyable types can still be swapped. + static struct NoCopy + { + this(this) { assert(0); } + int n; + string s; + } + NoCopy nc1, nc2; + nc1.n = 127; nc1.s = "abc"; + nc2.n = 513; nc2.s = "uvwxyz"; + + swap(nc1, nc2); + assert(nc1.n == 513 && nc1.s == "uvwxyz"); + assert(nc2.n == 127 && nc2.s == "abc"); + + swap(nc1, nc1); + swap(nc2, nc2); + assert(nc1.n == 513 && nc1.s == "uvwxyz"); + assert(nc2.n == 127 && nc2.s == "abc"); + + // Types containing non-copyable fields can also be swapped. + static struct NoCopyHolder + { + NoCopy noCopy; + } + NoCopyHolder h1, h2; + h1.noCopy.n = 31; h1.noCopy.s = "abc"; + h2.noCopy.n = 65; h2.noCopy.s = null; + + swap(h1, h2); + assert(h1.noCopy.n == 65 && h1.noCopy.s == null); + assert(h2.noCopy.n == 31 && h2.noCopy.s == "abc"); + + swap(h1, h1); + swap(h2, h2); + assert(h1.noCopy.n == 65 && h1.noCopy.s == null); + assert(h2.noCopy.n == 31 && h2.noCopy.s == "abc"); + + // Const types cannot be swapped. + const NoCopy const1, const2; + assert(const1.n == 0 && const2.n == 0); + static assert(!__traits(compiles, swap(const1, const2))); +} + +@safe unittest +{ + //Bug# 4789 + int[1] s = [1]; + swap(s, s); + + int[3] a = [1, 2, 3]; + swap(a[1], a[2]); + assert(a == [1, 3, 2]); +} + +@safe unittest +{ + static struct NoAssign + { + int i; + void opAssign(NoAssign) @disable; + } + auto s1 = NoAssign(1); + auto s2 = NoAssign(2); + swap(s1, s2); + assert(s1.i == 2); + assert(s2.i == 1); +} + +@safe unittest +{ + struct S + { + const int i; + int i2 = 2; + int i3 = 3; + } + S s; + static assert(!__traits(compiles, swap(s, s))); + swap(s.i2, s.i3); + assert(s.i2 == 3); + assert(s.i3 == 2); +} + +@safe unittest +{ + //11853 + import std.traits : isAssignable; + alias T = Tuple!(int, double); + static assert(isAssignable!T); +} + +@safe unittest +{ + // 12024 + import std.datetime; + SysTime a, b; + swap(a, b); +} + +@system unittest // 9975 +{ + import std.exception : doesPointTo, mayPointTo; + static struct S2 + { + union + { + size_t sz; + string s; + } + } + S2 a , b; + a.sz = -1; + assert(!doesPointTo(a, b)); + assert( mayPointTo(a, b)); + swap(a, b); + + //Note: we can catch an error here, because there is no RAII in this test + import std.exception : assertThrown; + void* p, pp; + p = &p; + assertThrown!Error(move(p)); + assertThrown!Error(move(p, pp)); + assertThrown!Error(swap(p, pp)); +} + +@system unittest +{ + static struct A + { + int* x; + this(this) { x = new int; } + } + A a1, a2; + swap(a1, a2); + + static struct B + { + int* x; + void opAssign(B) { x = new int; } + } + B b1, b2; + swap(b1, b2); +} + +/// ditto +void swap(T)(ref T lhs, ref T rhs) +if (is(typeof(lhs.proxySwap(rhs)))) +{ + lhs.proxySwap(rhs); +} + +/** +Swaps two elements in-place of a range `r`, +specified by their indices `i1` and `i2`. + +Params: + r = a range with swappable elements + i1 = first index + i2 = second index +*/ +void swapAt(R)(auto ref R r, size_t i1, size_t i2) +{ + static if (is(typeof(&r.swapAt))) + { + r.swapAt(i1, i2); + } + else static if (is(typeof(&r[i1]))) + { + swap(r[i1], r[i2]); + } + else + { + if (i1 == i2) return; + auto t1 = r.moveAt(i1); + auto t2 = r.moveAt(i2); + r[i2] = t1; + r[i1] = t2; + } +} + +/// +pure @safe nothrow unittest +{ + import std.algorithm.comparison : equal; + auto a = [1, 2, 3]; + a.swapAt(1, 2); + assert(a.equal([1, 3, 2])); +} + +pure @safe nothrow unittest +{ + import std.algorithm.comparison : equal; + auto a = [4, 5, 6]; + a.swapAt(1, 1); + assert(a.equal([4, 5, 6])); +} + +pure @safe nothrow unittest +{ + // test non random access ranges + import std.algorithm.comparison : equal; + import std.array : array; + + char[] b = ['a', 'b', 'c']; + b.swapAt(1, 2); + assert(b.equal(['a', 'c', 'b'])); + + int[3] c = [1, 2, 3]; + c.swapAt(1, 2); + assert(c.array.equal([1, 3, 2])); + + // opIndex returns lvalue + struct RandomIndexType(T) + { + T payload; + + @property ref auto opIndex(size_t i) + { + return payload[i]; + } + + } + auto d = RandomIndexType!(int[])([4, 5, 6]); + d.swapAt(1, 2); + assert(d.payload.equal([4, 6, 5])); + + // custom moveAt and opIndexAssign + struct RandomMoveAtType(T) + { + T payload; + + ElementType!T moveAt(size_t i) + { + return payload.moveAt(i); + } + + void opIndexAssign(ElementType!T val, size_t idx) + { + payload[idx] = val; + } + } + auto e = RandomMoveAtType!(int[])([7, 8, 9]); + e.swapAt(1, 2); + assert(e.payload.equal([7, 9, 8])); + + + // custom swapAt + struct RandomSwapAtType(T) + { + T payload; + + void swapAt(size_t i) + { + return payload.swapAt(i); + } + } + auto f = RandomMoveAtType!(int[])([10, 11, 12]); + swapAt(f, 1, 2); + assert(f.payload.equal([10, 12, 11])); +} + +private void swapFront(R1, R2)(R1 r1, R2 r2) +if (isInputRange!R1 && isInputRange!R2) +{ + static if (is(typeof(swap(r1.front, r2.front)))) + { + swap(r1.front, r2.front); + } + else + { + auto t1 = moveFront(r1), t2 = moveFront(r2); + r1.front = move(t2); + r2.front = move(t1); + } +} + +// swapRanges +/** +Swaps all elements of $(D r1) with successive elements in $(D r2). +Returns a tuple containing the remainder portions of $(D r1) and $(D +r2) that were not swapped (one of them will be empty). The ranges may +be of different types but must have the same element type and support +swapping. + +Params: + r1 = an $(REF_ALTTEXT input _range, isInputRange, std,_range,primitives) + with swappable elements + r2 = an $(REF_ALTTEXT input _range, isInputRange, std,_range,primitives) + with swappable elements + +Returns: + Tuple containing the remainder portions of r1 and r2 that were not swapped +*/ +Tuple!(InputRange1, InputRange2) +swapRanges(InputRange1, InputRange2)(InputRange1 r1, InputRange2 r2) +if (hasSwappableElements!InputRange1 && hasSwappableElements!InputRange2 + && is(ElementType!InputRange1 == ElementType!InputRange2)) +{ + for (; !r1.empty && !r2.empty; r1.popFront(), r2.popFront()) + { + swap(r1.front, r2.front); + } + return tuple(r1, r2); +} + +/// +@safe unittest +{ + import std.range : empty; + int[] a = [ 100, 101, 102, 103 ]; + int[] b = [ 0, 1, 2, 3 ]; + auto c = swapRanges(a[1 .. 3], b[2 .. 4]); + assert(c[0].empty && c[1].empty); + assert(a == [ 100, 2, 3, 103 ]); + assert(b == [ 0, 1, 101, 102 ]); +} + +/** +Initializes each element of $(D range) with $(D value). +Assumes that the elements of the range are uninitialized. +This is of interest for structs that +define copy constructors (for all other types, $(LREF fill) and +uninitializedFill are equivalent). + +Params: + range = An + $(REF_ALTTEXT input _range, isInputRange, std,_range,primitives) + that exposes references to its elements and has assignable + elements + value = Assigned to each element of range + +See_Also: + $(LREF fill) + $(LREF initializeAll) + */ +void uninitializedFill(Range, Value)(Range range, Value value) +if (isInputRange!Range && hasLvalueElements!Range && is(typeof(range.front = value))) +{ + import std.traits : hasElaborateAssign; + + alias T = ElementType!Range; + static if (hasElaborateAssign!T) + { + import std.conv : emplaceRef; + + // Must construct stuff by the book + for (; !range.empty; range.popFront()) + emplaceRef!T(range.front, value); + } + else + // Doesn't matter whether fill is initialized or not + return fill(range, value); +} + +/// +nothrow @system unittest +{ + import core.stdc.stdlib : malloc, free; + + auto s = (cast(int*) malloc(5 * int.sizeof))[0 .. 5]; + uninitializedFill(s, 42); + assert(s == [ 42, 42, 42, 42, 42 ]); + + scope(exit) free(s.ptr); +} diff --git a/libphobos/src/std/algorithm/package.d b/libphobos/src/std/algorithm/package.d new file mode 100644 index 0000000..4c9a72f --- /dev/null +++ b/libphobos/src/std/algorithm/package.d @@ -0,0 +1,198 @@ +// Written in the D programming language. + +/** +This package implements generic algorithms oriented towards the processing of +sequences. Sequences processed by these functions define range-based +interfaces. See also $(MREF_ALTTEXT Reference on ranges, std, range) and +$(HTTP ddili.org/ders/d.en/ranges.html, tutorial on ranges). + +$(SCRIPT inhibitQuickIndex = 1;) + +Algorithms are categorized into the following submodules: + +$(DIVC quickindex, +$(BOOKTABLE , +$(TR $(TH Submodule) $(TH Functions) +) +$(TR + $(TDNW $(SUBMODULE Searching, searching)) + $(TD + $(SUBREF searching, all) + $(SUBREF searching, any) + $(SUBREF searching, balancedParens) + $(SUBREF searching, boyerMooreFinder) + $(SUBREF searching, canFind) + $(SUBREF searching, commonPrefix) + $(SUBREF searching, count) + $(SUBREF searching, countUntil) + $(SUBREF searching, endsWith) + $(SUBREF searching, find) + $(SUBREF searching, findAdjacent) + $(SUBREF searching, findAmong) + $(SUBREF searching, findSkip) + $(SUBREF searching, findSplit) + $(SUBREF searching, findSplitAfter) + $(SUBREF searching, findSplitBefore) + $(SUBREF searching, minCount) + $(SUBREF searching, maxCount) + $(SUBREF searching, minElement) + $(SUBREF searching, maxElement) + $(SUBREF searching, minIndex) + $(SUBREF searching, maxIndex) + $(SUBREF searching, minPos) + $(SUBREF searching, maxPos) + $(SUBREF searching, skipOver) + $(SUBREF searching, startsWith) + $(SUBREF searching, until) + ) +) +$(TR + $(TDNW $(SUBMODULE Comparison, comparison)) + $(TD + $(SUBREF comparison, among) + $(SUBREF comparison, castSwitch) + $(SUBREF comparison, clamp) + $(SUBREF comparison, cmp) + $(SUBREF comparison, either) + $(SUBREF comparison, equal) + $(SUBREF comparison, isPermutation) + $(SUBREF comparison, isSameLength) + $(SUBREF comparison, levenshteinDistance) + $(SUBREF comparison, levenshteinDistanceAndPath) + $(SUBREF comparison, max) + $(SUBREF comparison, min) + $(SUBREF comparison, mismatch) + $(SUBREF comparison, predSwitch) + ) +) +$(TR + $(TDNW $(SUBMODULE Iteration, iteration)) + $(TD + $(SUBREF iteration, cache) + $(SUBREF iteration, cacheBidirectional) + $(SUBREF iteration, chunkBy) + $(SUBREF iteration, cumulativeFold) + $(SUBREF iteration, each) + $(SUBREF iteration, filter) + $(SUBREF iteration, filterBidirectional) + $(SUBREF iteration, fold) + $(SUBREF iteration, group) + $(SUBREF iteration, joiner) + $(SUBREF iteration, map) + $(SUBREF iteration, permutations) + $(SUBREF iteration, reduce) + $(SUBREF iteration, splitter) + $(SUBREF iteration, sum) + $(SUBREF iteration, uniq) + ) +) +$(TR + $(TDNW $(SUBMODULE Sorting, sorting)) + $(TD + $(SUBREF sorting, completeSort) + $(SUBREF sorting, isPartitioned) + $(SUBREF sorting, isSorted) + $(SUBREF sorting, isStrictlyMonotonic) + $(SUBREF sorting, ordered) + $(SUBREF sorting, strictlyOrdered) + $(SUBREF sorting, makeIndex) + $(SUBREF sorting, merge) + $(SUBREF sorting, multiSort) + $(SUBREF sorting, nextEvenPermutation) + $(SUBREF sorting, nextPermutation) + $(SUBREF sorting, partialSort) + $(SUBREF sorting, partition) + $(SUBREF sorting, partition3) + $(SUBREF sorting, schwartzSort) + $(SUBREF sorting, sort) + $(SUBREF sorting, topN) + $(SUBREF sorting, topNCopy) + $(SUBREF sorting, topNIndex) + ) +) +$(TR + $(TDNW Set operations $(BR)($(SUBMODULE setops, setops))) + $(TD + $(SUBREF setops, cartesianProduct) + $(SUBREF setops, largestPartialIntersection) + $(SUBREF setops, largestPartialIntersectionWeighted) + $(SUBREF setops, multiwayMerge) + $(SUBREF setops, multiwayUnion) + $(SUBREF setops, setDifference) + $(SUBREF setops, setIntersection) + $(SUBREF setops, setSymmetricDifference) + ) +) +$(TR + $(TDNW $(SUBMODULE Mutation, mutation)) + $(TD + $(SUBREF mutation, bringToFront) + $(SUBREF mutation, copy) + $(SUBREF mutation, fill) + $(SUBREF mutation, initializeAll) + $(SUBREF mutation, move) + $(SUBREF mutation, moveAll) + $(SUBREF mutation, moveSome) + $(SUBREF mutation, moveEmplace) + $(SUBREF mutation, moveEmplaceAll) + $(SUBREF mutation, moveEmplaceSome) + $(SUBREF mutation, remove) + $(SUBREF mutation, reverse) + $(SUBREF mutation, strip) + $(SUBREF mutation, stripLeft) + $(SUBREF mutation, stripRight) + $(SUBREF mutation, swap) + $(SUBREF mutation, swapRanges) + $(SUBREF mutation, uninitializedFill) + ) +) +)) + +Many functions in this package are parameterized with a $(GLOSSARY predicate). +The predicate may be any suitable callable type +(a function, a delegate, a $(GLOSSARY functor), or a lambda), or a +compile-time string. The string may consist of $(B any) legal D +expression that uses the symbol $(D a) (for unary functions) or the +symbols $(D a) and $(D b) (for binary functions). These names will NOT +interfere with other homonym symbols in user code because they are +evaluated in a different context. The default for all binary +comparison predicates is $(D "a == b") for unordered operations and +$(D "a < b") for ordered operations. + +Example: + +---- +int[] a = ...; +static bool greater(int a, int b) +{ + return a > b; +} +sort!greater(a); // predicate as alias +sort!((a, b) => a > b)(a); // predicate as a lambda. +sort!"a > b"(a); // predicate as string + // (no ambiguity with array name) +sort(a); // no predicate, "a < b" is implicit +---- + +Macros: +SUBMODULE = $(MREF_ALTTEXT $1, std, algorithm, $2) +SUBREF = $(REF_ALTTEXT $(TT $2), $2, std, algorithm, $1)$(NBSP) + +Copyright: Andrei Alexandrescu 2008-. + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(HTTP erdani.com, Andrei Alexandrescu) + +Source: $(PHOBOSSRC std/_algorithm/package.d) + */ +module std.algorithm; + +public import std.algorithm.comparison; +public import std.algorithm.iteration; +public import std.algorithm.mutation; +public import std.algorithm.searching; +public import std.algorithm.setops; +public import std.algorithm.sorting; + +static import std.functional; diff --git a/libphobos/src/std/algorithm/searching.d b/libphobos/src/std/algorithm/searching.d new file mode 100644 index 0000000..6468a87 --- /dev/null +++ b/libphobos/src/std/algorithm/searching.d @@ -0,0 +1,4600 @@ +// Written in the D programming language. +/** +This is a submodule of $(MREF std, algorithm). +It contains generic _searching algorithms. + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE Cheat Sheet, +$(TR $(TH Function Name) $(TH Description)) +$(T2 all, + $(D all!"a > 0"([1, 2, 3, 4])) returns $(D true) because all elements + are positive) +$(T2 any, + $(D any!"a > 0"([1, 2, -3, -4])) returns $(D true) because at least one + element is positive) +$(T2 balancedParens, + $(D balancedParens("((1 + 1) / 2)")) returns $(D true) because the + string has balanced parentheses.) +$(T2 boyerMooreFinder, + $(D find("hello world", boyerMooreFinder("or"))) returns $(D "orld") + using the $(LINK2 https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string_search_algorithm, + Boyer-Moore _algorithm).) +$(T2 canFind, + $(D canFind("hello world", "or")) returns $(D true).) +$(T2 count, + Counts elements that are equal to a specified value or satisfy a + predicate. $(D count([1, 2, 1], 1)) returns $(D 2) and + $(D count!"a < 0"([1, -3, 0])) returns $(D 1).) +$(T2 countUntil, + $(D countUntil(a, b)) returns the number of steps taken in $(D a) to + reach $(D b); for example, $(D countUntil("hello!", "o")) returns + $(D 4).) +$(T2 commonPrefix, + $(D commonPrefix("parakeet", "parachute")) returns $(D "para").) +$(T2 endsWith, + $(D endsWith("rocks", "ks")) returns $(D true).) +$(T2 find, + $(D find("hello world", "or")) returns $(D "orld") using linear search. + (For binary search refer to $(REF sortedRange, std,range).)) +$(T2 findAdjacent, + $(D findAdjacent([1, 2, 3, 3, 4])) returns the subrange starting with + two equal adjacent elements, i.e. $(D [3, 3, 4]).) +$(T2 findAmong, + $(D findAmong("abcd", "qcx")) returns $(D "cd") because $(D 'c') is + among $(D "qcx").) +$(T2 findSkip, + If $(D a = "abcde"), then $(D findSkip(a, "x")) returns $(D false) and + leaves $(D a) unchanged, whereas $(D findSkip(a, "c")) advances $(D a) + to $(D "de") and returns $(D true).) +$(T2 findSplit, + $(D findSplit("abcdefg", "de")) returns the three ranges $(D "abc"), + $(D "de"), and $(D "fg").) +$(T2 findSplitAfter, + $(D findSplitAfter("abcdefg", "de")) returns the two ranges + $(D "abcde") and $(D "fg").) +$(T2 findSplitBefore, + $(D findSplitBefore("abcdefg", "de")) returns the two ranges $(D "abc") + and $(D "defg").) +$(T2 minCount, + $(D minCount([2, 1, 1, 4, 1])) returns $(D tuple(1, 3)).) +$(T2 maxCount, + $(D maxCount([2, 4, 1, 4, 1])) returns $(D tuple(4, 2)).) +$(T2 minElement, + Selects the minimal element of a range. + `minElement([3, 4, 1, 2])` returns `1`.) +$(T2 maxElement, + Selects the maximal element of a range. + `maxElement([3, 4, 1, 2])` returns `4`.) +$(T2 minIndex, + Index of the minimal element of a range. + `minElement([3, 4, 1, 2])` returns `2`.) +$(T2 maxIndex, + Index of the maximal element of a range. + `maxElement([3, 4, 1, 2])` returns `1`.) +$(T2 minPos, + $(D minPos([2, 3, 1, 3, 4, 1])) returns the subrange $(D [1, 3, 4, 1]), + i.e., positions the range at the first occurrence of its minimal + element.) +$(T2 maxPos, + $(D maxPos([2, 3, 1, 3, 4, 1])) returns the subrange $(D [4, 1]), + i.e., positions the range at the first occurrence of its maximal + element.) +$(T2 mismatch, + $(D mismatch("parakeet", "parachute")) returns the two ranges + $(D "keet") and $(D "chute").) +$(T2 skipOver, + Assume $(D a = "blah"). Then $(D skipOver(a, "bi")) leaves $(D a) + unchanged and returns $(D false), whereas $(D skipOver(a, "bl")) + advances $(D a) to refer to $(D "ah") and returns $(D true).) +$(T2 startsWith, + $(D startsWith("hello, world", "hello")) returns $(D true).) +$(T2 until, + Lazily iterates a range until a specific value is found.) +) + +Copyright: Andrei Alexandrescu 2008-. + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(HTTP erdani.com, Andrei Alexandrescu) + +Source: $(PHOBOSSRC std/algorithm/_searching.d) + +Macros: +T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) + */ +module std.algorithm.searching; + +// FIXME +import std.functional; // : unaryFun, binaryFun; +import std.range.primitives; +import std.traits; +// FIXME +import std.typecons; // : Tuple, Flag, Yes, No; + +/++ +Checks if $(I _all) of the elements verify $(D pred). + +/ +template all(alias pred = "a") +{ + /++ + Returns $(D true) if and only if $(I _all) values $(D v) found in the + input _range $(D range) satisfy the predicate $(D pred). + Performs (at most) $(BIGOH range.length) evaluations of $(D pred). + +/ + bool all(Range)(Range range) + if (isInputRange!Range && is(typeof(unaryFun!pred(range.front)))) + { + import std.functional : not; + + return find!(not!(unaryFun!pred))(range).empty; + } +} + +/// +@safe unittest +{ + assert( all!"a & 1"([1, 3, 5, 7, 9])); + assert(!all!"a & 1"([1, 2, 3, 5, 7, 9])); +} + +/++ +$(D all) can also be used without a predicate, if its items can be +evaluated to true or false in a conditional statement. This can be a +convenient way to quickly evaluate that $(I _all) of the elements of a range +are true. + +/ +@safe unittest +{ + int[3] vals = [5, 3, 18]; + assert( all(vals[])); +} + +@safe unittest +{ + int x = 1; + assert(all!(a => a > x)([2, 3])); +} + +/++ +Checks if $(I _any) of the elements verifies $(D pred). +$(D !any) can be used to verify that $(I none) of the elements verify +$(D pred). +This is sometimes called `exists` in other languages. + +/ +template any(alias pred = "a") +{ + /++ + Returns $(D true) if and only if $(I _any) value $(D v) found in the + input _range $(D range) satisfies the predicate $(D pred). + Performs (at most) $(BIGOH range.length) evaluations of $(D pred). + +/ + bool any(Range)(Range range) + if (isInputRange!Range && is(typeof(unaryFun!pred(range.front)))) + { + return !find!pred(range).empty; + } +} + +/// +@safe unittest +{ + import std.ascii : isWhite; + assert( all!(any!isWhite)(["a a", "b b"])); + assert(!any!(all!isWhite)(["a a", "b b"])); +} + +/++ +$(D any) can also be used without a predicate, if its items can be +evaluated to true or false in a conditional statement. $(D !any) can be a +convenient way to quickly test that $(I none) of the elements of a range +evaluate to true. + +/ +@safe unittest +{ + int[3] vals1 = [0, 0, 0]; + assert(!any(vals1[])); //none of vals1 evaluate to true + + int[3] vals2 = [2, 0, 2]; + assert( any(vals2[])); + assert(!all(vals2[])); + + int[3] vals3 = [3, 3, 3]; + assert( any(vals3[])); + assert( all(vals3[])); +} + +@safe unittest +{ + auto a = [ 1, 2, 0, 4 ]; + assert(any!"a == 2"(a)); +} + +// balancedParens +/** +Checks whether $(D r) has "balanced parentheses", i.e. all instances +of $(D lPar) are closed by corresponding instances of $(D rPar). The +parameter $(D maxNestingLevel) controls the nesting level allowed. The +most common uses are the default or $(D 0). In the latter case, no +nesting is allowed. + +Params: + r = The range to check. + lPar = The element corresponding with a left (opening) parenthesis. + rPar = The element corresponding with a right (closing) parenthesis. + maxNestingLevel = The maximum allowed nesting level. + +Returns: + true if the given range has balanced parenthesis within the given maximum + nesting level; false otherwise. +*/ +bool balancedParens(Range, E)(Range r, E lPar, E rPar, + size_t maxNestingLevel = size_t.max) +if (isInputRange!(Range) && is(typeof(r.front == lPar))) +{ + size_t count; + for (; !r.empty; r.popFront()) + { + if (r.front == lPar) + { + if (count > maxNestingLevel) return false; + ++count; + } + else if (r.front == rPar) + { + if (!count) return false; + --count; + } + } + return count == 0; +} + +/// +@safe unittest +{ + auto s = "1 + (2 * (3 + 1 / 2)"; + assert(!balancedParens(s, '(', ')')); + s = "1 + (2 * (3 + 1) / 2)"; + assert(balancedParens(s, '(', ')')); + s = "1 + (2 * (3 + 1) / 2)"; + assert(!balancedParens(s, '(', ')', 0)); + s = "1 + (2 * 3 + 1) / (2 - 5)"; + assert(balancedParens(s, '(', ')', 0)); +} + +/** + * Sets up Boyer-Moore matching for use with $(D find) below. + * By default, elements are compared for equality. + * + * $(D BoyerMooreFinder) allocates GC memory. + * + * Params: + * pred = Predicate used to compare elements. + * needle = A random-access range with length and slicing. + * + * Returns: + * An instance of $(D BoyerMooreFinder) that can be used with $(D find()) to + * invoke the Boyer-Moore matching algorithm for finding of $(D needle) in a + * given haystack. + */ +struct BoyerMooreFinder(alias pred, Range) +{ +private: + size_t[] skip; // GC allocated + ptrdiff_t[ElementType!(Range)] occ; // GC allocated + Range needle; + + ptrdiff_t occurrence(ElementType!(Range) c) + { + auto p = c in occ; + return p ? *p : -1; + } + +/* +This helper function checks whether the last "portion" bytes of +"needle" (which is "nlen" bytes long) exist within the "needle" at +offset "offset" (counted from the end of the string), and whether the +character preceding "offset" is not a match. Notice that the range +being checked may reach beyond the beginning of the string. Such range +is ignored. + */ + static bool needlematch(R)(R needle, + size_t portion, size_t offset) + { + import std.algorithm.comparison : equal; + ptrdiff_t virtual_begin = needle.length - offset - portion; + ptrdiff_t ignore = 0; + if (virtual_begin < 0) + { + ignore = -virtual_begin; + virtual_begin = 0; + } + if (virtual_begin > 0 + && needle[virtual_begin - 1] == needle[$ - portion - 1]) + return 0; + + immutable delta = portion - ignore; + return equal(needle[needle.length - delta .. needle.length], + needle[virtual_begin .. virtual_begin + delta]); + } + +public: + /// + this(Range needle) + { + if (!needle.length) return; + this.needle = needle; + /* Populate table with the analysis of the needle */ + /* But ignoring the last letter */ + foreach (i, n ; needle[0 .. $ - 1]) + { + this.occ[n] = i; + } + /* Preprocess #2: init skip[] */ + /* Note: This step could be made a lot faster. + * A simple implementation is shown here. */ + this.skip = new size_t[needle.length]; + foreach (a; 0 .. needle.length) + { + size_t value = 0; + while (value < needle.length + && !needlematch(needle, a, value)) + { + ++value; + } + this.skip[needle.length - a - 1] = value; + } + } + + /// + Range beFound(Range haystack) + { + import std.algorithm.comparison : max; + + if (!needle.length) return haystack; + if (needle.length > haystack.length) return haystack[$ .. $]; + /* Search: */ + immutable limit = haystack.length - needle.length; + for (size_t hpos = 0; hpos <= limit; ) + { + size_t npos = needle.length - 1; + while (pred(needle[npos], haystack[npos+hpos])) + { + if (npos == 0) return haystack[hpos .. $]; + --npos; + } + hpos += max(skip[npos], cast(sizediff_t) npos - occurrence(haystack[npos+hpos])); + } + return haystack[$ .. $]; + } + + /// + @property size_t length() + { + return needle.length; + } + + /// + alias opDollar = length; +} + +/// Ditto +BoyerMooreFinder!(binaryFun!(pred), Range) boyerMooreFinder +(alias pred = "a == b", Range) +(Range needle) +if ((isRandomAccessRange!(Range) && hasSlicing!Range) || isSomeString!Range) +{ + return typeof(return)(needle); +} + +/// +@safe pure nothrow unittest +{ + auto bmFinder = boyerMooreFinder("TG"); + + string r = "TAGTGCCTGA"; + // search for the first match in the haystack r + r = bmFinder.beFound(r); + assert(r == "TGCCTGA"); + + // continue search in haystack + r = bmFinder.beFound(r[2 .. $]); + assert(r == "TGA"); +} + +/** +Returns the common prefix of two ranges. + +Params: + pred = The predicate to use in comparing elements for commonality. Defaults + to equality $(D "a == b"). + + r1 = A $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) of + elements. + + r2 = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of + elements. + +Returns: +A slice of $(D r1) which contains the characters that both ranges start with, +if the first argument is a string; otherwise, the same as the result of +$(D takeExactly(r1, n)), where $(D n) is the number of elements in the common +prefix of both ranges. + +See_Also: + $(REF takeExactly, std,range) + */ +auto commonPrefix(alias pred = "a == b", R1, R2)(R1 r1, R2 r2) +if (isForwardRange!R1 && isInputRange!R2 && + !isNarrowString!R1 && + is(typeof(binaryFun!pred(r1.front, r2.front)))) +{ + import std.algorithm.comparison : min; + static if (isRandomAccessRange!R1 && isRandomAccessRange!R2 && + hasLength!R1 && hasLength!R2 && + hasSlicing!R1) + { + immutable limit = min(r1.length, r2.length); + foreach (i; 0 .. limit) + { + if (!binaryFun!pred(r1[i], r2[i])) + { + return r1[0 .. i]; + } + } + return r1[0 .. limit]; + } + else + { + import std.range : takeExactly; + auto result = r1.save; + size_t i = 0; + for (; + !r1.empty && !r2.empty && binaryFun!pred(r1.front, r2.front); + ++i, r1.popFront(), r2.popFront()) + {} + return takeExactly(result, i); + } +} + +/// +@safe unittest +{ + assert(commonPrefix("hello, world", "hello, there") == "hello, "); +} + +/// ditto +auto commonPrefix(alias pred, R1, R2)(R1 r1, R2 r2) +if (isNarrowString!R1 && isInputRange!R2 && + is(typeof(binaryFun!pred(r1.front, r2.front)))) +{ + import std.utf : decode; + + auto result = r1.save; + immutable len = r1.length; + size_t i = 0; + + for (size_t j = 0; i < len && !r2.empty; r2.popFront(), i = j) + { + immutable f = decode(r1, j); + if (!binaryFun!pred(f, r2.front)) + break; + } + + return result[0 .. i]; +} + +/// ditto +auto commonPrefix(R1, R2)(R1 r1, R2 r2) +if (isNarrowString!R1 && isInputRange!R2 && !isNarrowString!R2 && + is(typeof(r1.front == r2.front))) +{ + return commonPrefix!"a == b"(r1, r2); +} + +/// ditto +auto commonPrefix(R1, R2)(R1 r1, R2 r2) +if (isNarrowString!R1 && isNarrowString!R2) +{ + import std.algorithm.comparison : min; + + static if (ElementEncodingType!R1.sizeof == ElementEncodingType!R2.sizeof) + { + import std.utf : stride, UTFException; + + immutable limit = min(r1.length, r2.length); + for (size_t i = 0; i < limit;) + { + immutable codeLen = stride(r1, i); + size_t j = 0; + + for (; j < codeLen && i < limit; ++i, ++j) + { + if (r1[i] != r2[i]) + return r1[0 .. i - j]; + } + + if (i == limit && j < codeLen) + throw new UTFException("Invalid UTF-8 sequence", i); + } + return r1[0 .. limit]; + } + else + return commonPrefix!"a == b"(r1, r2); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filter; + import std.conv : to; + import std.exception : assertThrown; + import std.meta : AliasSeq; + import std.range; + import std.utf : UTFException; + + assert(commonPrefix([1, 2, 3], [1, 2, 3, 4, 5]) == [1, 2, 3]); + assert(commonPrefix([1, 2, 3, 4, 5], [1, 2, 3]) == [1, 2, 3]); + assert(commonPrefix([1, 2, 3, 4], [1, 2, 3, 4]) == [1, 2, 3, 4]); + assert(commonPrefix([1, 2, 3], [7, 2, 3, 4, 5]).empty); + assert(commonPrefix([7, 2, 3, 4, 5], [1, 2, 3]).empty); + assert(commonPrefix([1, 2, 3], cast(int[]) null).empty); + assert(commonPrefix(cast(int[]) null, [1, 2, 3]).empty); + assert(commonPrefix(cast(int[]) null, cast(int[]) null).empty); + + foreach (S; AliasSeq!(char[], const(char)[], string, + wchar[], const(wchar)[], wstring, + dchar[], const(dchar)[], dstring)) + { + foreach (T; AliasSeq!(string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + assert(commonPrefix(to!S(""), to!T("")).empty); + assert(commonPrefix(to!S(""), to!T("hello")).empty); + assert(commonPrefix(to!S("hello"), to!T("")).empty); + assert(commonPrefix(to!S("hello, world"), to!T("hello, there")) == to!S("hello, ")); + assert(commonPrefix(to!S("hello, there"), to!T("hello, world")) == to!S("hello, ")); + assert(commonPrefix(to!S("hello, "), to!T("hello, world")) == to!S("hello, ")); + assert(commonPrefix(to!S("hello, world"), to!T("hello, ")) == to!S("hello, ")); + assert(commonPrefix(to!S("hello, world"), to!T("hello, world")) == to!S("hello, world")); + + //Bug# 8890 + assert(commonPrefix(to!S("Пиво"), to!T("Пони"))== to!S("П")); + assert(commonPrefix(to!S("Пони"), to!T("Пиво"))== to!S("П")); + assert(commonPrefix(to!S("Пиво"), to!T("Пиво"))== to!S("Пиво")); + assert(commonPrefix(to!S("\U0010FFFF\U0010FFFB\U0010FFFE"), + to!T("\U0010FFFF\U0010FFFB\U0010FFFC")) == to!S("\U0010FFFF\U0010FFFB")); + assert(commonPrefix(to!S("\U0010FFFF\U0010FFFB\U0010FFFC"), + to!T("\U0010FFFF\U0010FFFB\U0010FFFE")) == to!S("\U0010FFFF\U0010FFFB")); + assert(commonPrefix!"a != b"(to!S("Пиво"), to!T("онво")) == to!S("Пи")); + assert(commonPrefix!"a != b"(to!S("онво"), to!T("Пиво")) == to!S("он")); + }(); + + static assert(is(typeof(commonPrefix(to!S("Пиво"), filter!"true"("Пони"))) == S)); + assert(equal(commonPrefix(to!S("Пиво"), filter!"true"("Пони")), to!S("П"))); + + static assert(is(typeof(commonPrefix(filter!"true"("Пиво"), to!S("Пони"))) == + typeof(takeExactly(filter!"true"("П"), 1)))); + assert(equal(commonPrefix(filter!"true"("Пиво"), to!S("Пони")), takeExactly(filter!"true"("П"), 1))); + } + + assertThrown!UTFException(commonPrefix("\U0010FFFF\U0010FFFB", "\U0010FFFF\U0010FFFB"[0 .. $ - 1])); + + assert(commonPrefix("12345"d, [49, 50, 51, 60, 60]) == "123"d); + assert(commonPrefix([49, 50, 51, 60, 60], "12345" ) == [49, 50, 51]); + assert(commonPrefix([49, 50, 51, 60, 60], "12345"d) == [49, 50, 51]); + + assert(commonPrefix!"a == ('0' + b)"("12345" , [1, 2, 3, 9, 9]) == "123"); + assert(commonPrefix!"a == ('0' + b)"("12345"d, [1, 2, 3, 9, 9]) == "123"d); + assert(commonPrefix!"('0' + a) == b"([1, 2, 3, 9, 9], "12345" ) == [1, 2, 3]); + assert(commonPrefix!"('0' + a) == b"([1, 2, 3, 9, 9], "12345"d) == [1, 2, 3]); +} + +// count +/** +The first version counts the number of elements $(D x) in $(D r) for +which $(D pred(x, value)) is $(D true). $(D pred) defaults to +equality. Performs $(BIGOH haystack.length) evaluations of $(D pred). + +The second version returns the number of times $(D needle) occurs in +$(D haystack). Throws an exception if $(D needle.empty), as the _count +of the empty range in any range would be infinite. Overlapped counts +are not considered, for example $(D count("aaa", "aa")) is $(D 1), not +$(D 2). + +The third version counts the elements for which $(D pred(x)) is $(D +true). Performs $(BIGOH haystack.length) evaluations of $(D pred). + +The fourth version counts the number of elements in a range. It is +an optimization for the third version: if the given range has the +`length` property the count is returned right away, otherwise +performs $(BIGOH haystack.length) to walk the range. + +Note: Regardless of the overload, $(D count) will not accept +infinite ranges for $(D haystack). + +Params: + pred = The predicate to evaluate. + haystack = The range to _count. + needle = The element or sub-range to _count in the `haystack`. + +Returns: + The number of positions in the `haystack` for which `pred` returned true. +*/ +size_t count(alias pred = "a == b", Range, E)(Range haystack, E needle) +if (isInputRange!Range && !isInfinite!Range && + is(typeof(binaryFun!pred(haystack.front, needle)) : bool)) +{ + bool pred2(ElementType!Range a) { return binaryFun!pred(a, needle); } + return count!pred2(haystack); +} + +/// +@safe unittest +{ + import std.uni : toLower; + + // count elements in range + int[] a = [ 1, 2, 4, 3, 2, 5, 3, 2, 4 ]; + assert(count(a) == 9); + assert(count(a, 2) == 3); + assert(count!("a > b")(a, 2) == 5); + // count range in range + assert(count("abcadfabf", "ab") == 2); + assert(count("ababab", "abab") == 1); + assert(count("ababab", "abx") == 0); + // fuzzy count range in range + assert(count!((a, b) => toLower(a) == toLower(b))("AbcAdFaBf", "ab") == 2); + // count predicate in range + assert(count!("a > 1")(a) == 8); +} + +@safe unittest +{ + import std.conv : text; + + int[] a = [ 1, 2, 4, 3, 2, 5, 3, 2, 4 ]; + assert(count(a, 2) == 3, text(count(a, 2))); + assert(count!("a > b")(a, 2) == 5, text(count!("a > b")(a, 2))); + + // check strings + assert(count("日本語") == 3); + assert(count("日本語"w) == 3); + assert(count("日本語"d) == 3); + + assert(count!("a == '日'")("日本語") == 1); + assert(count!("a == '本'")("日本語"w) == 1); + assert(count!("a == '語'")("日本語"d) == 1); +} + +@safe unittest +{ + string s = "This is a fofofof list"; + string sub = "fof"; + assert(count(s, sub) == 2); +} + +/// Ditto +size_t count(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) +if (isForwardRange!R1 && !isInfinite!R1 && + isForwardRange!R2 && + is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool)) +{ + assert(!needle.empty, "Cannot count occurrences of an empty range"); + + static if (isInfinite!R2) + { + //Note: This is the special case of looking for an infinite inside a finite... + //"How many instances of the Fibonacci sequence can you count in [1, 2, 3]?" - "None." + return 0; + } + else + { + size_t result; + //Note: haystack is not saved, because findskip is designed to modify it + for ( ; findSkip!pred(haystack, needle.save) ; ++result) + {} + return result; + } +} + +/// Ditto +size_t count(alias pred, R)(R haystack) +if (isInputRange!R && !isInfinite!R && + is(typeof(unaryFun!pred(haystack.front)) : bool)) +{ + size_t result; + alias T = ElementType!R; //For narrow strings forces dchar iteration + foreach (T elem; haystack) + if (unaryFun!pred(elem)) ++result; + return result; +} + +/// Ditto +size_t count(R)(R haystack) +if (isInputRange!R && !isInfinite!R) +{ + return walkLength(haystack); +} + +@safe unittest +{ + int[] a = [ 1, 2, 4, 3, 2, 5, 3, 2, 4 ]; + assert(count!("a == 3")(a) == 2); + assert(count("日本語") == 3); +} + +// Issue 11253 +@safe nothrow unittest +{ + assert([1, 2, 3].count([2, 3]) == 1); +} + +/++ + Counts elements in the given + $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) + until the given predicate is true for one of the given $(D needles). + + Params: + pred = The predicate for determining when to stop counting. + haystack = The + $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to be + counted. + needles = Either a single element, or a + $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) + of elements, to be evaluated in turn against each + element in $(D haystack) under the given predicate. + + Returns: The number of elements which must be popped from the front of + $(D haystack) before reaching an element for which + $(D startsWith!pred(haystack, needles)) is $(D true). If + $(D startsWith!pred(haystack, needles)) is not $(D true) for any element in + $(D haystack), then $(D -1) is returned. + + See_Also: $(REF indexOf, std,string) + +/ +ptrdiff_t countUntil(alias pred = "a == b", R, Rs...)(R haystack, Rs needles) +if (isForwardRange!R + && Rs.length > 0 + && isForwardRange!(Rs[0]) == isInputRange!(Rs[0]) + && is(typeof(startsWith!pred(haystack, needles[0]))) + && (Rs.length == 1 + || is(typeof(countUntil!pred(haystack, needles[1 .. $]))))) +{ + typeof(return) result; + + static if (needles.length == 1) + { + static if (hasLength!R) //Note: Narrow strings don't have length. + { + //We delegate to find because find is very efficient. + //We store the length of the haystack so we don't have to save it. + auto len = haystack.length; + auto r2 = find!pred(haystack, needles[0]); + if (!r2.empty) + return cast(typeof(return)) (len - r2.length); + } + else + { + import std.range : dropOne; + + if (needles[0].empty) + return 0; + + //Default case, slower route doing startsWith iteration + for ( ; !haystack.empty ; ++result ) + { + //We compare the first elements of the ranges here before + //forwarding to startsWith. This avoids making useless saves to + //haystack/needle if they aren't even going to be mutated anyways. + //It also cuts down on the amount of pops on haystack. + if (binaryFun!pred(haystack.front, needles[0].front)) + { + //Here, we need to save the needle before popping it. + //haystack we pop in all paths, so we do that, and then save. + haystack.popFront(); + if (startsWith!pred(haystack.save, needles[0].save.dropOne())) + return result; + } + else + haystack.popFront(); + } + } + } + else + { + foreach (i, Ri; Rs) + { + static if (isForwardRange!Ri) + { + if (needles[i].empty) + return 0; + } + } + Tuple!Rs t; + foreach (i, Ri; Rs) + { + static if (!isForwardRange!Ri) + { + t[i] = needles[i]; + } + } + for (; !haystack.empty ; ++result, haystack.popFront()) + { + foreach (i, Ri; Rs) + { + static if (isForwardRange!Ri) + { + t[i] = needles[i].save; + } + } + if (startsWith!pred(haystack.save, t.expand)) + { + return result; + } + } + } + + //Because of @@@8804@@@: Avoids both "unreachable code" or "no return statement" + static if (isInfinite!R) assert(0); + else return -1; +} + +/// ditto +ptrdiff_t countUntil(alias pred = "a == b", R, N)(R haystack, N needle) +if (isInputRange!R && + is(typeof(binaryFun!pred(haystack.front, needle)) : bool)) +{ + bool pred2(ElementType!R a) { return binaryFun!pred(a, needle); } + return countUntil!pred2(haystack); +} + +/// +@safe unittest +{ + assert(countUntil("hello world", "world") == 6); + assert(countUntil("hello world", 'r') == 8); + assert(countUntil("hello world", "programming") == -1); + assert(countUntil("日本語", "本語") == 1); + assert(countUntil("日本語", '語') == 2); + assert(countUntil("日本語", "五") == -1); + assert(countUntil("日本語", '五') == -1); + assert(countUntil([0, 7, 12, 22, 9], [12, 22]) == 2); + assert(countUntil([0, 7, 12, 22, 9], 9) == 4); + assert(countUntil!"a > b"([0, 7, 12, 22, 9], 20) == 3); +} + +@safe unittest +{ + import std.algorithm.iteration : filter; + import std.internal.test.dummyrange; + + assert(countUntil("日本語", "") == 0); + assert(countUntil("日本語"d, "") == 0); + + assert(countUntil("", "") == 0); + assert(countUntil("".filter!"true"(), "") == 0); + + auto rf = [0, 20, 12, 22, 9].filter!"true"(); + assert(rf.countUntil!"a > b"((int[]).init) == 0); + assert(rf.countUntil!"a > b"(20) == 3); + assert(rf.countUntil!"a > b"([20, 8]) == 3); + assert(rf.countUntil!"a > b"([20, 10]) == -1); + assert(rf.countUntil!"a > b"([20, 8, 0]) == -1); + + auto r = new ReferenceForwardRange!int([0, 1, 2, 3, 4, 5, 6]); + auto r2 = new ReferenceForwardRange!int([3, 4]); + auto r3 = new ReferenceForwardRange!int([3, 5]); + assert(r.save.countUntil(3) == 3); + assert(r.save.countUntil(r2) == 3); + assert(r.save.countUntil(7) == -1); + assert(r.save.countUntil(r3) == -1); +} + +@safe unittest +{ + assert(countUntil("hello world", "world", "asd") == 6); + assert(countUntil("hello world", "world", "ello") == 1); + assert(countUntil("hello world", "world", "") == 0); + assert(countUntil("hello world", "world", 'l') == 2); +} + +/++ + Similar to the previous overload of $(D countUntil), except that this one + evaluates only the predicate $(D pred). + + Params: + pred = Predicate to when to stop counting. + haystack = An + $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of + elements to be counted. + Returns: The number of elements which must be popped from $(D haystack) + before $(D pred(haystack.front)) is $(D true). + +/ +ptrdiff_t countUntil(alias pred, R)(R haystack) +if (isInputRange!R && + is(typeof(unaryFun!pred(haystack.front)) : bool)) +{ + typeof(return) i; + static if (isRandomAccessRange!R) + { + //Optimized RA implementation. Since we want to count *and* iterate at + //the same time, it is more efficient this way. + static if (hasLength!R) + { + immutable len = cast(typeof(return)) haystack.length; + for ( ; i < len ; ++i ) + if (unaryFun!pred(haystack[i])) return i; + } + else //if (isInfinite!R) + { + for ( ; ; ++i ) + if (unaryFun!pred(haystack[i])) return i; + } + } + else static if (hasLength!R) + { + //For those odd ranges that have a length, but aren't RA. + //It is faster to quick find, and then compare the lengths + auto r2 = find!pred(haystack.save); + if (!r2.empty) return cast(typeof(return)) (haystack.length - r2.length); + } + else //Everything else + { + alias T = ElementType!R; //For narrow strings forces dchar iteration + foreach (T elem; haystack) + { + if (unaryFun!pred(elem)) return i; + ++i; + } + } + + //Because of @@@8804@@@: Avoids both "unreachable code" or "no return statement" + static if (isInfinite!R) assert(0); + else return -1; +} + +/// +@safe unittest +{ + import std.ascii : isDigit; + import std.uni : isWhite; + + assert(countUntil!(std.uni.isWhite)("hello world") == 5); + assert(countUntil!(std.ascii.isDigit)("hello world") == -1); + assert(countUntil!"a > 20"([0, 7, 12, 22, 9]) == 3); +} + +@safe unittest +{ + import std.internal.test.dummyrange; + + // References + { + // input + ReferenceInputRange!int r; + r = new ReferenceInputRange!int([0, 1, 2, 3, 4, 5, 6]); + assert(r.countUntil(3) == 3); + r = new ReferenceInputRange!int([0, 1, 2, 3, 4, 5, 6]); + assert(r.countUntil(7) == -1); + } + { + // forward + auto r = new ReferenceForwardRange!int([0, 1, 2, 3, 4, 5, 6]); + assert(r.save.countUntil([3, 4]) == 3); + assert(r.save.countUntil(3) == 3); + assert(r.save.countUntil([3, 7]) == -1); + assert(r.save.countUntil(7) == -1); + } + { + // infinite forward + auto r = new ReferenceInfiniteForwardRange!int(0); + assert(r.save.countUntil([3, 4]) == 3); + assert(r.save.countUntil(3) == 3); + } +} + +/** +Checks if the given range ends with (one of) the given needle(s). +The reciprocal of $(D startsWith). + +Params: + pred = The predicate to use for comparing elements between the range and + the needle(s). + + doesThisEnd = The + $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives) + to check. + + withOneOfThese = The needles to check against, which may be single + elements, or bidirectional ranges of elements. + + withThis = The single element to check. + +Returns: +0 if the needle(s) do not occur at the end of the given range; +otherwise the position of the matching needle, that is, 1 if the range ends +with $(D withOneOfThese[0]), 2 if it ends with $(D withOneOfThese[1]), and so +on. + +In the case when no needle parameters are given, return $(D true) iff back of +$(D doesThisStart) fulfils predicate $(D pred). +*/ +uint endsWith(alias pred = "a == b", Range, Needles...)(Range doesThisEnd, Needles withOneOfThese) +if (isBidirectionalRange!Range && Needles.length > 1 && + is(typeof(.endsWith!pred(doesThisEnd, withOneOfThese[0])) : bool) && + is(typeof(.endsWith!pred(doesThisEnd, withOneOfThese[1 .. $])) : uint)) +{ + alias haystack = doesThisEnd; + alias needles = withOneOfThese; + + // Make one pass looking for empty ranges in needles + foreach (i, Unused; Needles) + { + // Empty range matches everything + static if (!is(typeof(binaryFun!pred(haystack.back, needles[i])) : bool)) + { + if (needles[i].empty) return i + 1; + } + } + + for (; !haystack.empty; haystack.popBack()) + { + foreach (i, Unused; Needles) + { + static if (is(typeof(binaryFun!pred(haystack.back, needles[i])) : bool)) + { + // Single-element + if (binaryFun!pred(haystack.back, needles[i])) + { + // found, but continue to account for one-element + // range matches (consider endsWith("ab", "b", + // 'b') should return 1, not 2). + continue; + } + } + else + { + if (binaryFun!pred(haystack.back, needles[i].back)) + continue; + } + + // This code executed on failure to match + // Out with this guy, check for the others + uint result = endsWith!pred(haystack, needles[0 .. i], needles[i + 1 .. $]); + if (result > i) ++result; + return result; + } + + // If execution reaches this point, then the back matches for all + // needles ranges. What we need to do now is to lop off the back of + // all ranges involved and recurse. + foreach (i, Unused; Needles) + { + static if (is(typeof(binaryFun!pred(haystack.back, needles[i])) : bool)) + { + // Test has passed in the previous loop + return i + 1; + } + else + { + needles[i].popBack(); + if (needles[i].empty) return i + 1; + } + } + } + return 0; +} + +/// Ditto +bool endsWith(alias pred = "a == b", R1, R2)(R1 doesThisEnd, R2 withThis) +if (isBidirectionalRange!R1 && + isBidirectionalRange!R2 && + is(typeof(binaryFun!pred(doesThisEnd.back, withThis.back)) : bool)) +{ + alias haystack = doesThisEnd; + alias needle = withThis; + + static if (is(typeof(pred) : string)) + enum isDefaultPred = pred == "a == b"; + else + enum isDefaultPred = false; + + static if (isDefaultPred && isArray!R1 && isArray!R2 && + is(Unqual!(ElementEncodingType!R1) == Unqual!(ElementEncodingType!R2))) + { + if (haystack.length < needle.length) return false; + + return haystack[$ - needle.length .. $] == needle; + } + else + { + import std.range : retro; + return startsWith!pred(retro(doesThisEnd), retro(withThis)); + } +} + +/// Ditto +bool endsWith(alias pred = "a == b", R, E)(R doesThisEnd, E withThis) +if (isBidirectionalRange!R && + is(typeof(binaryFun!pred(doesThisEnd.back, withThis)) : bool)) +{ + if (doesThisEnd.empty) + return false; + + alias predFunc = binaryFun!pred; + + // auto-decoding special case + static if (isNarrowString!R) + { + // specialize for ASCII as to not change previous behavior + if (withThis <= 0x7F) + return predFunc(doesThisEnd[$ - 1], withThis); + else + return predFunc(doesThisEnd.back, withThis); + } + else + { + return predFunc(doesThisEnd.back, withThis); + } +} + +/// Ditto +bool endsWith(alias pred, R)(R doesThisEnd) +if (isInputRange!R && + ifTestable!(typeof(doesThisEnd.front), unaryFun!pred)) +{ + return !doesThisEnd.empty && unaryFun!pred(doesThisEnd.back); +} + +/// +@safe unittest +{ + import std.ascii : isAlpha; + assert("abc".endsWith!(a => a.isAlpha)); + assert("abc".endsWith!isAlpha); + + assert(!"ab1".endsWith!(a => a.isAlpha)); + + assert(!"ab1".endsWith!isAlpha); + assert(!"".endsWith!(a => a.isAlpha)); + + import std.algorithm.comparison : among; + assert("abc".endsWith!(a => a.among('c', 'd') != 0)); + assert(!"abc".endsWith!(a => a.among('a', 'b') != 0)); + + assert(endsWith("abc", "")); + assert(!endsWith("abc", "b")); + assert(endsWith("abc", "a", 'c') == 2); + assert(endsWith("abc", "c", "a") == 1); + assert(endsWith("abc", "c", "c") == 1); + assert(endsWith("abc", "bc", "c") == 2); + assert(endsWith("abc", "x", "c", "b") == 2); + assert(endsWith("abc", "x", "aa", "bc") == 3); + assert(endsWith("abc", "x", "aaa", "sab") == 0); + assert(endsWith("abc", "x", "aaa", 'c', "sab") == 3); +} + +@safe unittest +{ + import std.algorithm.iteration : filterBidirectional; + import std.conv : to; + import std.meta : AliasSeq; + + foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + { + assert(!endsWith(to!S("abc"), 'a')); + assert(endsWith(to!S("abc"), 'a', 'c') == 2); + assert(!endsWith(to!S("abc"), 'x', 'n', 'b')); + assert(endsWith(to!S("abc"), 'x', 'n', 'c') == 3); + assert(endsWith(to!S("abc\uFF28"), 'a', '\uFF28', 'c') == 2); + + foreach (T; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + //Lots of strings + assert(endsWith(to!S("abc"), to!T(""))); + assert(!endsWith(to!S("abc"), to!T("a"))); + assert(!endsWith(to!S("abc"), to!T("b"))); + assert(endsWith(to!S("abc"), to!T("bc"), 'c') == 2); + assert(endsWith(to!S("abc"), to!T("a"), "c") == 2); + assert(endsWith(to!S("abc"), to!T("c"), "a") == 1); + assert(endsWith(to!S("abc"), to!T("c"), "c") == 1); + assert(endsWith(to!S("abc"), to!T("x"), 'c', "b") == 2); + assert(endsWith(to!S("abc"), 'x', to!T("aa"), "bc") == 3); + assert(endsWith(to!S("abc"), to!T("x"), "aaa", "sab") == 0); + assert(endsWith(to!S("abc"), to!T("x"), "aaa", "c", "sab") == 3); + assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("l\uFF4co"))); + assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("lo"), to!T("l\uFF4co")) == 2); + + //Unicode + assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("l\uFF4co"))); + assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("lo"), to!T("l\uFF4co")) == 2); + assert(endsWith(to!S("日本語"), to!T("本語"))); + assert(endsWith(to!S("日本語"), to!T("日本語"))); + assert(!endsWith(to!S("本語"), to!T("日本語"))); + + //Empty + assert(endsWith(to!S(""), T.init)); + assert(!endsWith(to!S(""), 'a')); + assert(endsWith(to!S("a"), T.init)); + assert(endsWith(to!S("a"), T.init, "") == 1); + assert(endsWith(to!S("a"), T.init, 'a') == 1); + assert(endsWith(to!S("a"), 'a', T.init) == 2); + }(); + } + + foreach (T; AliasSeq!(int, short)) + { + immutable arr = cast(T[])[0, 1, 2, 3, 4, 5]; + + //RA range + assert(endsWith(arr, cast(int[]) null)); + assert(!endsWith(arr, 0)); + assert(!endsWith(arr, 4)); + assert(endsWith(arr, 5)); + assert(endsWith(arr, 0, 4, 5) == 3); + assert(endsWith(arr, [5])); + assert(endsWith(arr, [4, 5])); + assert(endsWith(arr, [4, 5], 7) == 1); + assert(!endsWith(arr, [2, 4, 5])); + assert(endsWith(arr, [2, 4, 5], [3, 4, 5]) == 2); + + //Normal input range + assert(!endsWith(filterBidirectional!"true"(arr), 4)); + assert(endsWith(filterBidirectional!"true"(arr), 5)); + assert(endsWith(filterBidirectional!"true"(arr), [5])); + assert(endsWith(filterBidirectional!"true"(arr), [4, 5])); + assert(endsWith(filterBidirectional!"true"(arr), [4, 5], 7) == 1); + assert(!endsWith(filterBidirectional!"true"(arr), [2, 4, 5])); + assert(endsWith(filterBidirectional!"true"(arr), [2, 4, 5], [3, 4, 5]) == 2); + assert(endsWith(arr, filterBidirectional!"true"([4, 5]))); + assert(endsWith(arr, filterBidirectional!"true"([4, 5]), 7) == 1); + assert(!endsWith(arr, filterBidirectional!"true"([2, 4, 5]))); + assert(endsWith(arr, [2, 4, 5], filterBidirectional!"true"([3, 4, 5])) == 2); + + //Non-default pred + assert(endsWith!("a%10 == b%10")(arr, [14, 15])); + assert(!endsWith!("a%10 == b%10")(arr, [15, 14])); + } +} + +/** +Iterates the passed range and selects the extreme element with `less`. +If the extreme element occurs multiple time, the first occurrence will be +returned. + +Params: + map = custom accessor for the comparison key + selector = custom mapping for the extrema selection + seed = custom seed to use as initial element + r = Range from which the extreme value will be selected + +Returns: + The extreme value according to `map` and `selector` of the passed-in values. +*/ +private auto extremum(alias map, alias selector = "a < b", Range)(Range r) +if (isInputRange!Range && !isInfinite!Range && + is(typeof(unaryFun!map(ElementType!(Range).init)))) +in +{ + assert(!r.empty, "r is an empty range"); +} +body +{ + alias Element = ElementType!Range; + Unqual!Element seed = r.front; + r.popFront(); + return extremum!(map, selector)(r, seed); +} + +private auto extremum(alias map, alias selector = "a < b", Range, + RangeElementType = ElementType!Range) + (Range r, RangeElementType seedElement) +if (isInputRange!Range && !isInfinite!Range && + !is(CommonType!(ElementType!Range, RangeElementType) == void) && + is(typeof(unaryFun!map(ElementType!(Range).init)))) +{ + alias mapFun = unaryFun!map; + alias selectorFun = binaryFun!selector; + + alias Element = ElementType!Range; + alias CommonElement = CommonType!(Element, RangeElementType); + Unqual!CommonElement extremeElement = seedElement; + + alias MapType = Unqual!(typeof(mapFun(CommonElement.init))); + MapType extremeElementMapped = mapFun(extremeElement); + + // direct access via a random access range is faster + static if (isRandomAccessRange!Range) + { + foreach (const i; 0 .. r.length) + { + MapType mapElement = mapFun(r[i]); + if (selectorFun(mapElement, extremeElementMapped)) + { + extremeElement = r[i]; + extremeElementMapped = mapElement; + } + } + } + else + { + while (!r.empty) + { + MapType mapElement = mapFun(r.front); + if (selectorFun(mapElement, extremeElementMapped)) + { + extremeElement = r.front; + extremeElementMapped = mapElement; + } + r.popFront(); + } + } + return extremeElement; +} + +private auto extremum(alias selector = "a < b", Range)(Range r) + if (isInputRange!Range && !isInfinite!Range && + !is(typeof(unaryFun!selector(ElementType!(Range).init)))) +{ + alias Element = ElementType!Range; + Unqual!Element seed = r.front; + r.popFront(); + return extremum!selector(r, seed); +} + +// if we only have one statement in the loop it can be optimized a lot better +private auto extremum(alias selector = "a < b", Range, + RangeElementType = ElementType!Range) + (Range r, RangeElementType seedElement) + if (isInputRange!Range && !isInfinite!Range && + !is(CommonType!(ElementType!Range, RangeElementType) == void) && + !is(typeof(unaryFun!selector(ElementType!(Range).init)))) +{ + alias Element = ElementType!Range; + alias CommonElement = CommonType!(Element, RangeElementType); + Unqual!CommonElement extremeElement = seedElement; + alias selectorFun = binaryFun!selector; + + // direct access via a random access range is faster + static if (isRandomAccessRange!Range) + { + foreach (const i; 0 .. r.length) + { + if (selectorFun(r[i], extremeElement)) + { + extremeElement = r[i]; + } + } + } + else + { + while (!r.empty) + { + if (selectorFun(r.front, extremeElement)) + { + extremeElement = r.front; + } + r.popFront(); + } + } + return extremeElement; +} + +@safe pure unittest +{ + // allows a custom map to select the extremum + assert([[0, 4], [1, 2]].extremum!"a[0]" == [0, 4]); + assert([[0, 4], [1, 2]].extremum!"a[1]" == [1, 2]); + + // allows a custom selector for comparison + assert([[0, 4], [1, 2]].extremum!("a[0]", "a > b") == [1, 2]); + assert([[0, 4], [1, 2]].extremum!("a[1]", "a > b") == [0, 4]); + + // use a custom comparator + import std.math : cmp; + assert([-2., 0, 5].extremum!cmp == 5.0); + assert([-2., 0, 2].extremum!`cmp(a, b) < 0` == -2.0); + + // combine with map + import std.range : enumerate; + assert([-3., 0, 5].enumerate.extremum!(`a.value`, cmp) == tuple(2, 5.0)); + assert([-2., 0, 2].enumerate.extremum!(`a.value`, `cmp(a, b) < 0`) == tuple(0, -2.0)); + + // seed with a custom value + int[] arr; + assert(arr.extremum(1) == 1); +} + +@safe pure nothrow unittest +{ + // 2d seeds + int[][] arr2d; + assert(arr2d.extremum([1]) == [1]); + + // allow seeds of different types (implicit casting) + assert(extremum([2, 3, 4], 1.5) == 1.5); +} + +@safe pure unittest +{ + import std.range : enumerate, iota; + + // forward ranges + assert(iota(1, 5).extremum() == 1); + assert(iota(2, 5).enumerate.extremum!"a.value" == tuple(0, 2)); + + // should work with const + const(int)[] immArr = [2, 1, 3]; + assert(immArr.extremum == 1); + + // should work with immutable + immutable(int)[] immArr2 = [2, 1, 3]; + assert(immArr2.extremum == 1); + + // with strings + assert(["b", "a", "c"].extremum == "a"); + + // with all dummy ranges + import std.internal.test.dummyrange; + foreach (DummyType; AllDummyRanges) + { + DummyType d; + assert(d.extremum == 1); + assert(d.extremum!(a => a) == 1); + assert(d.extremum!`a > b` == 10); + assert(d.extremum!(a => a, `a > b`) == 10); + } +} + +@nogc @safe nothrow pure unittest +{ + static immutable arr = [7, 3, 4, 2, 1, 8]; + assert(arr.extremum == 1); + + static immutable arr2d = [[1, 9], [3, 1], [4, 2]]; + assert(arr2d.extremum!"a[1]" == arr2d[1]); +} + +// find +/** +Finds an individual element in an input range. Elements of $(D +haystack) are compared with $(D needle) by using predicate $(D +pred). Performs $(BIGOH walkLength(haystack)) evaluations of $(D +pred). + +To _find the last occurrence of $(D needle) in $(D haystack), call $(D +find(retro(haystack), needle)). See $(REF retro, std,range). + +Params: + +pred = The predicate for comparing each element with the needle, defaulting to +$(D "a == b"). +The negated predicate $(D "a != b") can be used to search instead for the first +element $(I not) matching the needle. + +haystack = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) +searched in. + +needle = The element searched for. + +Constraints: + +$(D isInputRange!InputRange && is(typeof(binaryFun!pred(haystack.front, needle) +: bool))) + +Returns: + +$(D haystack) advanced such that the front element is the one searched for; +that is, until $(D binaryFun!pred(haystack.front, needle)) is $(D true). If no +such position exists, returns an empty $(D haystack). + +See_Also: + $(HTTP sgi.com/tech/stl/_find.html, STL's _find) + */ +InputRange find(alias pred = "a == b", InputRange, Element)(InputRange haystack, scope Element needle) +if (isInputRange!InputRange && + is (typeof(binaryFun!pred(haystack.front, needle)) : bool)) +{ + alias R = InputRange; + alias E = Element; + alias predFun = binaryFun!pred; + static if (is(typeof(pred == "a == b"))) + enum isDefaultPred = pred == "a == b"; + else + enum isDefaultPred = false; + enum isIntegralNeedle = isSomeChar!E || isIntegral!E || isBoolean!E; + + alias EType = ElementType!R; + + // If the haystack is a SortedRange we can use binary search to find the needle. + // Works only for the default find predicate and any SortedRange predicate. + // 8829 enhancement + import std.range : SortedRange; + static if (is(InputRange : SortedRange!TT, TT) && isDefaultPred) + { + auto lb = haystack.lowerBound(needle); + if (lb.length == haystack.length || haystack[lb.length] != needle) + return haystack[$ .. $]; + + return haystack[lb.length .. $]; + } + else static if (isNarrowString!R) + { + alias EEType = ElementEncodingType!R; + alias UEEType = Unqual!EEType; + + //These are two special cases which can search without decoding the UTF stream. + static if (isDefaultPred && isIntegralNeedle) + { + import std.utf : canSearchInCodeUnits; + + //This special case deals with UTF8 search, when the needle + //is represented by a single code point. + //Note: "needle <= 0x7F" properly handles sign via unsigned promotion + static if (is(UEEType == char)) + { + if (!__ctfe && canSearchInCodeUnits!char(needle)) + { + static R trustedMemchr(ref R haystack, ref E needle) @trusted nothrow pure + { + import core.stdc.string : memchr; + auto ptr = memchr(haystack.ptr, needle, haystack.length); + return ptr ? + haystack[cast(char*) ptr - haystack.ptr .. $] : + haystack[$ .. $]; + } + return trustedMemchr(haystack, needle); + } + } + + //Ditto, but for UTF16 + static if (is(UEEType == wchar)) + { + if (canSearchInCodeUnits!wchar(needle)) + { + foreach (i, ref EEType e; haystack) + { + if (e == needle) + return haystack[i .. $]; + } + return haystack[$ .. $]; + } + } + } + + //Previous conditonal optimizations did not succeed. Fallback to + //unconditional implementations + static if (isDefaultPred) + { + import std.utf : encode; + + //In case of default pred, it is faster to do string/string search. + UEEType[is(UEEType == char) ? 4 : 2] buf; + + size_t len = encode(buf, needle); + return find(haystack, buf[0 .. len]); + } + else + { + import std.utf : decode; + + //Explicit pred: we must test each character by the book. + //We choose a manual decoding approach, because it is faster than + //the built-in foreach, or doing a front/popFront for-loop. + immutable len = haystack.length; + size_t i = 0, next = 0; + while (next < len) + { + if (predFun(decode(haystack, next), needle)) + return haystack[i .. $]; + i = next; + } + return haystack[$ .. $]; + } + } + else static if (isArray!R) + { + //10403 optimization + static if (isDefaultPred && isIntegral!EType && EType.sizeof == 1 && isIntegralNeedle) + { + import std.algorithm.comparison : max, min; + + R findHelper(ref R haystack, ref E needle) @trusted nothrow pure + { + import core.stdc.string : memchr; + + EType* ptr = null; + //Note: we use "min/max" to handle sign mismatch. + if (min(EType.min, needle) == EType.min && + max(EType.max, needle) == EType.max) + { + ptr = cast(EType*) memchr(haystack.ptr, needle, + haystack.length); + } + + return ptr ? + haystack[ptr - haystack.ptr .. $] : + haystack[$ .. $]; + } + + if (!__ctfe) + return findHelper(haystack, needle); + } + + //Default implementation. + foreach (i, ref e; haystack) + if (predFun(e, needle)) + return haystack[i .. $]; + return haystack[$ .. $]; + } + else + { + //Everything else. Walk. + for ( ; !haystack.empty; haystack.popFront() ) + { + if (predFun(haystack.front, needle)) + break; + } + return haystack; + } +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.container : SList; + import std.range; + import std.range.primitives : empty; + + auto arr = assumeSorted!"a < b"([1, 2, 4, 4, 4, 4, 5, 6, 9]); + assert(find(arr, 4) == assumeSorted!"a < b"([4, 4, 4, 4, 5, 6, 9])); + assert(find(arr, 1) == arr); + assert(find(arr, 9) == assumeSorted!"a < b"([9])); + assert(find!"a > b"(arr, 4) == assumeSorted!"a < b"([5, 6, 9])); + assert(find!"a < b"(arr, 4) == arr); + assert(find(arr, 0).empty()); + assert(find(arr, 10).empty()); + assert(find(arr, 8).empty()); + + auto r = assumeSorted!"a > b"([10, 7, 3, 1, 0, 0]); + assert(find(r, 3) == assumeSorted!"a > b"([3, 1, 0, 0])); + assert(find!"a > b"(r, 8) == r); + assert(find!"a < b"(r, 5) == assumeSorted!"a > b"([3, 1, 0, 0])); + + assert(find("hello, world", ',') == ", world"); + assert(find([1, 2, 3, 5], 4) == []); + assert(equal(find(SList!int(1, 2, 3, 4, 5)[], 4), SList!int(4, 5)[])); + assert(find!"a > b"([1, 2, 3, 5], 2) == [3, 5]); + + auto a = [ 1, 2, 3 ]; + assert(find(a, 5).empty); // not found + assert(!find(a, 2).empty); // found + + // Case-insensitive find of a string + string[] s = [ "Hello", "world", "!" ]; + assert(!find!("toLower(a) == b")(s, "hello").empty); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.container : SList; + + auto lst = SList!int(1, 2, 5, 7, 3); + assert(lst.front == 1); + auto r = find(lst[], 5); + assert(equal(r, SList!int(5, 7, 3)[])); + assert(find([1, 2, 3, 5], 4).empty); + assert(equal(find!"a > b"("hello", 'k'), "llo")); +} + +@safe pure nothrow unittest +{ + assert(!find ([1, 2, 3], 2).empty); + assert(!find!((a,b)=>a == b)([1, 2, 3], 2).empty); + assert(!find ([1, 2, 3], 2).empty); + assert(!find!((a,b)=>a == b)([1, 2, 3], 2).empty); +} + +@safe pure unittest +{ + import std.meta : AliasSeq; + foreach (R; AliasSeq!(string, wstring, dstring)) + { + foreach (E; AliasSeq!(char, wchar, dchar)) + { + assert(find ("hello world", 'w') == "world"); + assert(find!((a,b)=>a == b)("hello world", 'w') == "world"); + assert(find ("日c語", 'c') == "c語"); + assert(find!((a,b)=>a == b)("日c語", 'c') == "c語"); + assert(find ("0123456789", 'A').empty); + static if (E.sizeof >= 2) + { + assert(find ("日本語", '本') == "本語"); + assert(find!((a,b)=>a == b)("日本語", '本') == "本語"); + } + } + } +} + +@safe unittest +{ + //CTFE + static assert(find("abc", 'b') == "bc"); + static assert(find("日b語", 'b') == "b語"); + static assert(find("日本語", '本') == "本語"); + static assert(find([1, 2, 3], 2) == [2, 3]); + + static assert(find ([1, 2, 3], 2)); + static assert(find!((a,b)=>a == b)([1, 2, 3], 2)); + static assert(find ([1, 2, 3], 2)); + static assert(find!((a,b)=>a == b)([1, 2, 3], 2)); +} + +@safe unittest +{ + import std.exception : assertCTFEable; + import std.meta : AliasSeq; + + void dg() @safe pure nothrow + { + byte[] sarr = [1, 2, 3, 4]; + ubyte[] uarr = [1, 2, 3, 4]; + foreach (arr; AliasSeq!(sarr, uarr)) + { + foreach (T; AliasSeq!(byte, ubyte, int, uint)) + { + assert(find(arr, cast(T) 3) == arr[2 .. $]); + assert(find(arr, cast(T) 9) == arr[$ .. $]); + } + assert(find(arr, 256) == arr[$ .. $]); + } + } + dg(); + assertCTFEable!dg; +} + +@safe unittest +{ + // Bugzilla 11603 + enum Foo : ubyte { A } + assert([Foo.A].find(Foo.A).empty == false); + + ubyte x = 0; + assert([x].find(x).empty == false); +} + +/** +Advances the input range $(D haystack) by calling $(D haystack.popFront) +until either $(D pred(haystack.front)), or $(D +haystack.empty). Performs $(BIGOH haystack.length) evaluations of $(D +pred). + +To _find the last element of a +$(REF_ALTTEXT bidirectional, isBidirectionalRange, std,range,primitives) $(D haystack) satisfying +$(D pred), call $(D find!(pred)(retro(haystack))). See $(REF retro, std,range). + +`find` behaves similar to `dropWhile` in other languages. + +Params: + +pred = The predicate for determining if a given element is the one being +searched for. + +haystack = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to +search in. + +Returns: + +$(D haystack) advanced such that the front element is the one searched for; +that is, until $(D binaryFun!pred(haystack.front, needle)) is $(D true). If no +such position exists, returns an empty $(D haystack). + +See_Also: + $(HTTP sgi.com/tech/stl/find_if.html, STL's find_if) +*/ +InputRange find(alias pred, InputRange)(InputRange haystack) +if (isInputRange!InputRange) +{ + alias R = InputRange; + alias predFun = unaryFun!pred; + static if (isNarrowString!R) + { + import std.utf : decode; + + immutable len = haystack.length; + size_t i = 0, next = 0; + while (next < len) + { + if (predFun(decode(haystack, next))) + return haystack[i .. $]; + i = next; + } + return haystack[$ .. $]; + } + else + { + //standard range + for ( ; !haystack.empty; haystack.popFront() ) + { + if (predFun(haystack.front)) + break; + } + return haystack; + } +} + +/// +@safe unittest +{ + auto arr = [ 1, 2, 3, 4, 1 ]; + assert(find!("a > 2")(arr) == [ 3, 4, 1 ]); + + // with predicate alias + bool pred(int x) { return x + 1 > 1.5; } + assert(find!(pred)(arr) == arr); +} + +@safe pure unittest +{ + int[] r = [ 1, 2, 3 ]; + assert(find!(a=>a > 2)(r) == [3]); + bool pred(int x) { return x + 1 > 1.5; } + assert(find!(pred)(r) == r); + + assert(find!(a=>a > 'v')("hello world") == "world"); + assert(find!(a=>a%4 == 0)("日本語") == "本語"); +} + +/** +Finds the first occurrence of a forward range in another forward range. + +Performs $(BIGOH walkLength(haystack) * walkLength(needle)) comparisons in the +worst case. There are specializations that improve performance by taking +advantage of $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives) +or random access in the given ranges (where possible), depending on the statistics +of the two ranges' content. + +Params: + +pred = The predicate to use for comparing respective elements from the haystack +and the needle. Defaults to simple equality $(D "a == b"). + +haystack = The $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) +searched in. + +needle = The $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) +searched for. + +Returns: + +$(D haystack) advanced such that $(D needle) is a prefix of it (if no +such position exists, returns $(D haystack) advanced to termination). + */ +R1 find(alias pred = "a == b", R1, R2)(R1 haystack, scope R2 needle) +if (isForwardRange!R1 && isForwardRange!R2 + && is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool)) +{ + static if (!isRandomAccessRange!R1) + { + static if (is(typeof(pred == "a == b")) && pred == "a == b" && isSomeString!R1 && isSomeString!R2 + && haystack[0].sizeof == needle[0].sizeof) + { + // return cast(R1) find(representation(haystack), representation(needle)); + // Specialization for simple string search + alias Representation = + Select!(haystack[0].sizeof == 1, ubyte[], + Select!(haystack[0].sizeof == 2, ushort[], uint[])); + // Will use the array specialization + static TO force(TO, T)(T r) @trusted { return cast(TO) r; } + return force!R1(.find!(pred, Representation, Representation) + (force!Representation(haystack), force!Representation(needle))); + } + else + { + return simpleMindedFind!pred(haystack, needle); + } + } + else static if (!isBidirectionalRange!R2 || !hasSlicing!R1) + { + static if (!is(ElementType!R1 == ElementType!R2)) + { + return simpleMindedFind!pred(haystack, needle); + } + else + { + // Prepare the search with needle's first element + if (needle.empty) + return haystack; + + haystack = .find!pred(haystack, needle.front); + + static if (hasLength!R1 && hasLength!R2 && is(typeof(takeNone(haystack)) == R1)) + { + if (needle.length > haystack.length) + return takeNone(haystack); + } + else + { + if (haystack.empty) + return haystack; + } + + needle.popFront(); + size_t matchLen = 1; + + // Loop invariant: haystack[0 .. matchLen] matches everything in + // the initial needle that was popped out of needle. + for (;;) + { + // Extend matchLength as much as possible + for (;;) + { + import std.range : takeNone; + + if (needle.empty || haystack.empty) + return haystack; + + static if (hasLength!R1 && is(typeof(takeNone(haystack)) == R1)) + { + if (matchLen == haystack.length) + return takeNone(haystack); + } + + if (!binaryFun!pred(haystack[matchLen], needle.front)) + break; + + ++matchLen; + needle.popFront(); + } + + auto bestMatch = haystack[0 .. matchLen]; + haystack.popFront(); + haystack = .find!pred(haystack, bestMatch); + } + } + } + else // static if (hasSlicing!R1 && isBidirectionalRange!R2) + { + if (needle.empty) return haystack; + + static if (hasLength!R2) + { + immutable needleLength = needle.length; + } + else + { + immutable needleLength = walkLength(needle.save); + } + if (needleLength > haystack.length) + { + return haystack[haystack.length .. haystack.length]; + } + // Optimization in case the ranges are both SortedRanges. + // Binary search can be used to find the first occurence + // of the first element of the needle in haystack. + // When it is found O(walklength(needle)) steps are performed. + // 8829 enhancement + import std.algorithm.comparison : mismatch; + import std.range : SortedRange; + static if (is(R1 == R2) + && is(R1 : SortedRange!TT, TT) + && pred == "a == b") + { + auto needleFirstElem = needle[0]; + auto partitions = haystack.trisect(needleFirstElem); + auto firstElemLen = partitions[1].length; + size_t count = 0; + + if (firstElemLen == 0) + return haystack[$ .. $]; + + while (needle.front() == needleFirstElem) + { + needle.popFront(); + ++count; + + if (count > firstElemLen) + return haystack[$ .. $]; + } + + auto m = mismatch(partitions[2], needle); + + if (m[1].empty) + return haystack[partitions[0].length + partitions[1].length - count .. $]; + } + else static if (isRandomAccessRange!R2) + { + immutable lastIndex = needleLength - 1; + auto last = needle[lastIndex]; + size_t j = lastIndex, skip = 0; + for (; j < haystack.length;) + { + if (!binaryFun!pred(haystack[j], last)) + { + ++j; + continue; + } + immutable k = j - lastIndex; + // last elements match + for (size_t i = 0;; ++i) + { + if (i == lastIndex) + return haystack[k .. haystack.length]; + if (!binaryFun!pred(haystack[k + i], needle[i])) + break; + } + if (skip == 0) + { + skip = 1; + while (skip < needleLength && needle[needleLength - 1 - skip] != needle[needleLength - 1]) + { + ++skip; + } + } + j += skip; + } + } + else + { + // @@@BUG@@@ + // auto needleBack = moveBack(needle); + // Stage 1: find the step + size_t step = 1; + auto needleBack = needle.back; + needle.popBack(); + for (auto i = needle.save; !i.empty && i.back != needleBack; + i.popBack(), ++step) + { + } + // Stage 2: linear find + size_t scout = needleLength - 1; + for (;;) + { + if (scout >= haystack.length) + break; + if (!binaryFun!pred(haystack[scout], needleBack)) + { + ++scout; + continue; + } + // Found a match with the last element in the needle + auto cand = haystack[scout + 1 - needleLength .. haystack.length]; + if (startsWith!pred(cand, needle)) + { + // found + return cand; + } + scout += step; + } + } + return haystack[haystack.length .. haystack.length]; + } +} + +/// +@safe unittest +{ + import std.container : SList; + import std.range.primitives : empty; + import std.typecons : Tuple; + + assert(find("hello, world", "World").empty); + assert(find("hello, world", "wo") == "world"); + assert([1, 2, 3, 4].find(SList!int(2, 3)[]) == [2, 3, 4]); + alias C = Tuple!(int, "x", int, "y"); + auto a = [C(1,0), C(2,0), C(3,1), C(4,0)]; + assert(a.find!"a.x == b"([2, 3]) == [C(2,0), C(3,1), C(4,0)]); + assert(a[1 .. $].find!"a.x == b"([2, 3]) == [C(2,0), C(3,1), C(4,0)]); +} + +@safe unittest +{ + import std.container : SList; + alias C = Tuple!(int, "x", int, "y"); + assert([C(1,0), C(2,0), C(3,1), C(4,0)].find!"a.x == b"(SList!int(2, 3)[]) == [C(2,0), C(3,1), C(4,0)]); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.container : SList; + + auto lst = SList!int(1, 2, 5, 7, 3); + static assert(isForwardRange!(int[])); + static assert(isForwardRange!(typeof(lst[]))); + auto r = find(lst[], [2, 5]); + assert(equal(r, SList!int(2, 5, 7, 3)[])); +} + +@safe unittest +{ + import std.range; + import std.stdio; + + auto r1 = assumeSorted([1, 2, 3, 3, 3, 4, 5, 6, 7, 8, 8, 8, 10]); + auto r2 = assumeSorted([3, 3, 4, 5, 6, 7, 8, 8]); + auto r3 = assumeSorted([3, 4, 5, 6, 7, 8]); + auto r4 = assumeSorted([4, 5, 6]); + auto r5 = assumeSorted([12, 13]); + auto r6 = assumeSorted([8, 8, 10, 11]); + auto r7 = assumeSorted([3, 3, 3, 3, 3, 3, 3]); + + assert(find(r1, r2) == assumeSorted([3, 3, 4, 5, 6, 7, 8, 8, 8, 10])); + assert(find(r1, r3) == assumeSorted([3, 4, 5, 6, 7, 8, 8, 8, 10])); + assert(find(r1, r4) == assumeSorted([4, 5, 6, 7, 8, 8, 8, 10])); + assert(find(r1, r5).empty()); + assert(find(r1, r6).empty()); + assert(find(r1, r7).empty()); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + // @@@BUG@@@ removing static below makes unittest fail + static struct BiRange + { + int[] payload; + @property bool empty() { return payload.empty; } + @property BiRange save() { return this; } + @property ref int front() { return payload[0]; } + @property ref int back() { return payload[$ - 1]; } + void popFront() { return payload.popFront(); } + void popBack() { return payload.popBack(); } + } + auto r = BiRange([1, 2, 3, 10, 11, 4]); + assert(equal(find(r, [10, 11]), [10, 11, 4])); +} + +@safe unittest +{ + import std.container : SList; + + assert(find([ 1, 2, 3 ], SList!int(2, 3)[]) == [ 2, 3 ]); + assert(find([ 1, 2, 1, 2, 3, 3 ], SList!int(2, 3)[]) == [ 2, 3, 3 ]); +} + +//Bug# 8334 +@safe unittest +{ + import std.algorithm.iteration : filter; + import std.range; + + auto haystack = [1, 2, 3, 4, 1, 9, 12, 42]; + auto needle = [12, 42, 27]; + + //different overload of find, but it's the base case. + assert(find(haystack, needle).empty); + + assert(find(haystack, takeExactly(filter!"true"(needle), 3)).empty); + assert(find(haystack, filter!"true"(needle)).empty); +} + +// Internally used by some find() overloads above +private R1 simpleMindedFind(alias pred, R1, R2)(R1 haystack, scope R2 needle) +{ + enum estimateNeedleLength = hasLength!R1 && !hasLength!R2; + + static if (hasLength!R1) + { + static if (!hasLength!R2) + size_t estimatedNeedleLength = 0; + else + immutable size_t estimatedNeedleLength = needle.length; + } + + bool haystackTooShort() + { + static if (estimateNeedleLength) + { + return haystack.length < estimatedNeedleLength; + } + else + { + return haystack.empty; + } + } + + searching: + for (;; haystack.popFront()) + { + if (haystackTooShort()) + { + // Failed search + static if (hasLength!R1) + { + static if (is(typeof(haystack[haystack.length .. + haystack.length]) : R1)) + return haystack[haystack.length .. haystack.length]; + else + return R1.init; + } + else + { + assert(haystack.empty); + return haystack; + } + } + static if (estimateNeedleLength) + size_t matchLength = 0; + for (auto h = haystack.save, n = needle.save; + !n.empty; + h.popFront(), n.popFront()) + { + if (h.empty || !binaryFun!pred(h.front, n.front)) + { + // Failed searching n in h + static if (estimateNeedleLength) + { + if (estimatedNeedleLength < matchLength) + estimatedNeedleLength = matchLength; + } + continue searching; + } + static if (estimateNeedleLength) + ++matchLength; + } + break; + } + return haystack; +} + +@safe unittest +{ + // Test simpleMindedFind for the case where both haystack and needle have + // length. + struct CustomString + { + @safe: + string _impl; + + // This is what triggers issue 7992. + @property size_t length() const { return _impl.length; } + @property void length(size_t len) { _impl.length = len; } + + // This is for conformance to the forward range API (we deliberately + // make it non-random access so that we will end up in + // simpleMindedFind). + @property bool empty() const { return _impl.empty; } + @property dchar front() const { return _impl.front; } + void popFront() { _impl.popFront(); } + @property CustomString save() { return this; } + } + + // If issue 7992 occurs, this will throw an exception from calling + // popFront() on an empty range. + auto r = find(CustomString("a"), CustomString("b")); + assert(r.empty); +} + +/** +Finds two or more $(D needles) into a $(D haystack). The predicate $(D +pred) is used throughout to compare elements. By default, elements are +compared for equality. + +Params: + +pred = The predicate to use for comparing elements. + +haystack = The target of the search. Must be an input range. +If any of $(D needles) is a range with elements comparable to +elements in $(D haystack), then $(D haystack) must be a +$(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) +such that the search can backtrack. + +needles = One or more items to search for. Each of $(D needles) must +be either comparable to one element in $(D haystack), or be itself a +forward range with elements comparable with elements in +$(D haystack). + +Returns: + +A tuple containing $(D haystack) positioned to match one of the +needles and also the 1-based index of the matching element in $(D +needles) (0 if none of $(D needles) matched, 1 if $(D needles[0]) +matched, 2 if $(D needles[1]) matched...). The first needle to be found +will be the one that matches. If multiple needles are found at the +same spot in the range, then the shortest one is the one which matches +(if multiple needles of the same length are found at the same spot (e.g +$(D "a") and $(D 'a')), then the left-most of them in the argument list +matches). + +The relationship between $(D haystack) and $(D needles) simply means +that one can e.g. search for individual $(D int)s or arrays of $(D +int)s in an array of $(D int)s. In addition, if elements are +individually comparable, searches of heterogeneous types are allowed +as well: a $(D double[]) can be searched for an $(D int) or a $(D +short[]), and conversely a $(D long) can be searched for a $(D float) +or a $(D double[]). This makes for efficient searches without the need +to coerce one side of the comparison into the other's side type. + +The complexity of the search is $(BIGOH haystack.length * +max(needles.length)). (For needles that are individual items, length +is considered to be 1.) The strategy used in searching several +subranges at once maximizes cache usage by moving in $(D haystack) as +few times as possible. + */ +Tuple!(Range, size_t) find(alias pred = "a == b", Range, Ranges...) +(Range haystack, Ranges needles) +if (Ranges.length > 1 && is(typeof(startsWith!pred(haystack, needles)))) +{ + for (;; haystack.popFront()) + { + size_t r = startsWith!pred(haystack, needles); + if (r || haystack.empty) + { + return tuple(haystack, r); + } + } +} + +/// +@safe unittest +{ + import std.typecons : tuple; + int[] a = [ 1, 4, 2, 3 ]; + assert(find(a, 4) == [ 4, 2, 3 ]); + assert(find(a, [ 1, 4 ]) == [ 1, 4, 2, 3 ]); + assert(find(a, [ 1, 3 ], 4) == tuple([ 4, 2, 3 ], 2)); + // Mixed types allowed if comparable + assert(find(a, 5, [ 1.2, 3.5 ], 2.0) == tuple([ 2, 3 ], 3)); +} + +@safe unittest +{ + auto s1 = "Mary has a little lamb"; + assert(find(s1, "has a", "has an") == tuple("has a little lamb", 1)); + assert(find(s1, 't', "has a", "has an") == tuple("has a little lamb", 2)); + assert(find(s1, 't', "has a", 'y', "has an") == tuple("y has a little lamb", 3)); + assert(find("abc", "bc").length == 2); +} + +@safe unittest +{ + import std.algorithm.internal : rndstuff; + import std.meta : AliasSeq; + import std.uni : toUpper; + + int[] a = [ 1, 2, 3 ]; + assert(find(a, 5).empty); + assert(find(a, 2) == [2, 3]); + + foreach (T; AliasSeq!(int, double)) + { + auto b = rndstuff!(T)(); + if (!b.length) continue; + b[$ / 2] = 200; + b[$ / 4] = 200; + assert(find(b, 200).length == b.length - b.length / 4); + } + + // Case-insensitive find of a string + string[] s = [ "Hello", "world", "!" ]; + assert(find!("toUpper(a) == toUpper(b)")(s, "hello").length == 3); + + static bool f(string a, string b) { return toUpper(a) == toUpper(b); } + assert(find!(f)(s, "hello").length == 3); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.internal : rndstuff; + import std.meta : AliasSeq; + import std.range : retro; + + int[] a = [ 1, 2, 3, 2, 6 ]; + assert(find(retro(a), 5).empty); + assert(equal(find(retro(a), 2), [ 2, 3, 2, 1 ][])); + + foreach (T; AliasSeq!(int, double)) + { + auto b = rndstuff!(T)(); + if (!b.length) continue; + b[$ / 2] = 200; + b[$ / 4] = 200; + assert(find(retro(b), 200).length == + b.length - (b.length - 1) / 2); + } +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange; + + int[] a = [ -1, 0, 1, 2, 3, 4, 5 ]; + int[] b = [ 1, 2, 3 ]; + assert(find(a, b) == [ 1, 2, 3, 4, 5 ]); + assert(find(b, a).empty); + + foreach (DummyType; AllDummyRanges) + { + DummyType d; + auto findRes = find(d, 5); + assert(equal(findRes, [5,6,7,8,9,10])); + } +} + +/** + * Finds $(D needle) in $(D haystack) efficiently using the + * $(LINK2 https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string_search_algorithm, + * Boyer-Moore) method. + * + * Params: + * haystack = A random-access range with length and slicing. + * needle = A $(LREF BoyerMooreFinder). + * + * Returns: + * $(D haystack) advanced such that $(D needle) is a prefix of it (if no + * such position exists, returns $(D haystack) advanced to termination). + */ +RandomAccessRange find(RandomAccessRange, alias pred, InputRange)( + RandomAccessRange haystack, scope BoyerMooreFinder!(pred, InputRange) needle) +{ + return needle.beFound(haystack); +} + +@safe unittest +{ + string h = "/homes/aalexand/d/dmd/bin/../lib/libphobos.a(dmain2.o)"~ + "(.gnu.linkonce.tmain+0x74): In function `main' undefined reference"~ + " to `_Dmain':"; + string[] ns = ["libphobos", "function", " undefined", "`", ":"]; + foreach (n ; ns) + { + auto p = find(h, boyerMooreFinder(n)); + assert(!p.empty); + } +} + +/// +@safe unittest +{ + import std.range.primitives : empty; + int[] a = [ -1, 0, 1, 2, 3, 4, 5 ]; + int[] b = [ 1, 2, 3 ]; + + assert(find(a, boyerMooreFinder(b)) == [ 1, 2, 3, 4, 5 ]); + assert(find(b, boyerMooreFinder(a)).empty); +} + +@safe unittest +{ + auto bm = boyerMooreFinder("for"); + auto match = find("Moor", bm); + assert(match.empty); +} + +// canFind +/++ +Convenience function. Like find, but only returns whether or not the search +was successful. + +See_Also: +$(LREF among) for checking a value against multiple possibilities. + +/ +template canFind(alias pred="a == b") +{ + import std.meta : allSatisfy; + + /++ + Returns $(D true) if and only if any value $(D v) found in the + input range $(D range) satisfies the predicate $(D pred). + Performs (at most) $(BIGOH haystack.length) evaluations of $(D pred). + +/ + bool canFind(Range)(Range haystack) + if (is(typeof(find!pred(haystack)))) + { + return any!pred(haystack); + } + + /++ + Returns $(D true) if and only if $(D needle) can be found in $(D + range). Performs $(BIGOH haystack.length) evaluations of $(D pred). + +/ + bool canFind(Range, Element)(Range haystack, scope Element needle) + if (is(typeof(find!pred(haystack, needle)))) + { + return !find!pred(haystack, needle).empty; + } + + /++ + Returns the 1-based index of the first needle found in $(D haystack). If no + needle is found, then $(D 0) is returned. + + So, if used directly in the condition of an if statement or loop, the result + will be $(D true) if one of the needles is found and $(D false) if none are + found, whereas if the result is used elsewhere, it can either be cast to + $(D bool) for the same effect or used to get which needle was found first + without having to deal with the tuple that $(D LREF find) returns for the + same operation. + +/ + size_t canFind(Range, Ranges...)(Range haystack, scope Ranges needles) + if (Ranges.length > 1 && + allSatisfy!(isForwardRange, Ranges) && + is(typeof(find!pred(haystack, needles)))) + { + return find!pred(haystack, needles)[1]; + } +} + +/// +@safe unittest +{ + assert(canFind([0, 1, 2, 3], 2) == true); + assert(canFind([0, 1, 2, 3], [1, 2], [2, 3])); + assert(canFind([0, 1, 2, 3], [1, 2], [2, 3]) == 1); + assert(canFind([0, 1, 2, 3], [1, 7], [2, 3])); + assert(canFind([0, 1, 2, 3], [1, 7], [2, 3]) == 2); + + assert(canFind([0, 1, 2, 3], 4) == false); + assert(!canFind([0, 1, 2, 3], [1, 3], [2, 4])); + assert(canFind([0, 1, 2, 3], [1, 3], [2, 4]) == 0); +} + +/** + * Example using a custom predicate. + * Note that the needle appears as the second argument of the predicate. + */ +@safe unittest +{ + auto words = [ + "apple", + "beeswax", + "cardboard" + ]; + assert(!canFind(words, "bees")); + assert( canFind!((string a, string b) => a.startsWith(b))(words, "bees")); +} + +@safe unittest +{ + import std.algorithm.internal : rndstuff; + + auto a = rndstuff!(int)(); + if (a.length) + { + auto b = a[a.length / 2]; + assert(canFind(a, b)); + } +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + assert(equal!(canFind!"a < b")([[1, 2, 3], [7, 8, 9]], [2, 8])); +} + +// findAdjacent +/** +Advances $(D r) until it finds the first two adjacent elements $(D a), +$(D b) that satisfy $(D pred(a, b)). Performs $(BIGOH r.length) +evaluations of $(D pred). + +Params: + pred = The predicate to satisfy. + r = A $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) to + search in. + +Returns: +$(D r) advanced to the first occurrence of two adjacent elements that satisfy +the given predicate. If there are no such two elements, returns $(D r) advanced +until empty. + +See_Also: + $(HTTP sgi.com/tech/stl/adjacent_find.html, STL's adjacent_find) +*/ +Range findAdjacent(alias pred = "a == b", Range)(Range r) +if (isForwardRange!(Range)) +{ + auto ahead = r.save; + if (!ahead.empty) + { + for (ahead.popFront(); !ahead.empty; r.popFront(), ahead.popFront()) + { + if (binaryFun!(pred)(r.front, ahead.front)) return r; + } + } + static if (!isInfinite!Range) + return ahead; +} + +/// +@safe unittest +{ + int[] a = [ 11, 10, 10, 9, 8, 8, 7, 8, 9 ]; + auto r = findAdjacent(a); + assert(r == [ 10, 10, 9, 8, 8, 7, 8, 9 ]); + auto p = findAdjacent!("a < b")(a); + assert(p == [ 7, 8, 9 ]); + +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange; + import std.range; + + int[] a = [ 11, 10, 10, 9, 8, 8, 7, 8, 9 ]; + auto p = findAdjacent(a); + assert(p == [10, 10, 9, 8, 8, 7, 8, 9 ]); + p = findAdjacent!("a < b")(a); + assert(p == [7, 8, 9]); + // empty + a = []; + p = findAdjacent(a); + assert(p.empty); + // not found + a = [ 1, 2, 3, 4, 5 ]; + p = findAdjacent(a); + assert(p.empty); + p = findAdjacent!"a > b"(a); + assert(p.empty); + ReferenceForwardRange!int rfr = new ReferenceForwardRange!int([1, 2, 3, 2, 2, 3]); + assert(equal(findAdjacent(rfr), [2, 2, 3])); + + // Issue 9350 + assert(!repeat(1).findAdjacent().empty); +} + +// findAmong +/** +Searches the given range for an element that matches one of the given choices. + +Advances $(D seq) by calling $(D seq.popFront) until either +$(D find!(pred)(choices, seq.front)) is $(D true), or $(D seq) becomes empty. +Performs $(BIGOH seq.length * choices.length) evaluations of $(D pred). + +Params: + pred = The predicate to use for determining a match. + seq = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to + search. + choices = A $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) + of possible choices. + +Returns: +$(D seq) advanced to the first matching element, or until empty if there are no +matching elements. + +See_Also: + $(HTTP sgi.com/tech/stl/find_first_of.html, STL's find_first_of) +*/ +InputRange findAmong(alias pred = "a == b", InputRange, ForwardRange)( + InputRange seq, ForwardRange choices) +if (isInputRange!InputRange && isForwardRange!ForwardRange) +{ + for (; !seq.empty && find!pred(choices, seq.front).empty; seq.popFront()) + { + } + return seq; +} + +/// +@safe unittest +{ + int[] a = [ -1, 0, 1, 2, 3, 4, 5 ]; + int[] b = [ 3, 1, 2 ]; + assert(findAmong(a, b) == a[2 .. $]); +} + +@safe unittest +{ + int[] a = [ -1, 0, 2, 1, 2, 3, 4, 5 ]; + int[] b = [ 1, 2, 3 ]; + assert(findAmong(a, b) == [2, 1, 2, 3, 4, 5 ]); + assert(findAmong(b, [ 4, 6, 7 ][]).empty); + assert(findAmong!("a == b")(a, b).length == a.length - 2); + assert(findAmong!("a == b")(b, [ 4, 6, 7 ][]).empty); +} + +// findSkip +/** + * Finds $(D needle) in $(D haystack) and positions $(D haystack) + * right after the first occurrence of $(D needle). + * + * Params: + * haystack = The + * $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) to search + * in. + * needle = The + * $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) to search + * for. + * + * Returns: $(D true) if the needle was found, in which case $(D haystack) is + * positioned after the end of the first occurrence of $(D needle); otherwise + * $(D false), leaving $(D haystack) untouched. + */ +bool findSkip(alias pred = "a == b", R1, R2)(ref R1 haystack, R2 needle) +if (isForwardRange!R1 && isForwardRange!R2 + && is(typeof(binaryFun!pred(haystack.front, needle.front)))) +{ + auto parts = findSplit!pred(haystack, needle); + if (parts[1].empty) return false; + // found + haystack = parts[2]; + return true; +} + +/// +@safe unittest +{ + import std.range.primitives : empty; + // Needle is found; s is replaced by the substring following the first + // occurrence of the needle. + string s = "abcdef"; + assert(findSkip(s, "cd") && s == "ef"); + + // Needle is not found; s is left untouched. + s = "abcdef"; + assert(!findSkip(s, "cxd") && s == "abcdef"); + + // If the needle occurs at the end of the range, the range is left empty. + s = "abcdef"; + assert(findSkip(s, "def") && s.empty); +} + +/** +These functions find the first occurrence of `needle` in `haystack` and then +split `haystack` as follows. + +`findSplit` returns a tuple `result` containing $(I three) ranges. `result[0]` +is the portion of `haystack` before `needle`, `result[1]` is the portion of +`haystack` that matches `needle`, and `result[2]` is the portion of `haystack` +after the match. If `needle` was not found, `result[0]` comprehends `haystack` +entirely and `result[1]` and `result[2]` are empty. + +`findSplitBefore` returns a tuple `result` containing two ranges. `result[0]` is +the portion of `haystack` before `needle`, and `result[1]` is the balance of +`haystack` starting with the match. If `needle` was not found, `result[0]` +comprehends `haystack` entirely and `result[1]` is empty. + +`findSplitAfter` returns a tuple `result` containing two ranges. +`result[0]` is the portion of `haystack` up to and including the +match, and `result[1]` is the balance of `haystack` starting +after the match. If `needle` was not found, `result[0]` is empty +and `result[1]` is `haystack`. + +In all cases, the concatenation of the returned ranges spans the +entire `haystack`. + +If `haystack` is a random-access range, all three components of the tuple have +the same type as `haystack`. Otherwise, `haystack` must be a +$(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) and +the type of `result[0]` and `result[1]` is the same as $(REF takeExactly, +std,range). + +Params: + pred = Predicate to use for comparing needle against haystack. + haystack = The range to search. + needle = What to look for. + +Returns: + +A sub-type of `Tuple!()` of the split portions of `haystack` (see above for +details). This sub-type of `Tuple!()` has `opCast` defined for `bool`. This +`opCast` returns `true` when the separating `needle` was found +(`!result[1].empty`) and `false` otherwise. This enables the convenient idiom +shown in the following example. + +Example: +--- +if (const split = haystack.findSplit(needle)) +{ + doSomethingWithSplit(split); +} +--- + */ +auto findSplit(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) +if (isForwardRange!R1 && isForwardRange!R2) +{ + static struct Result(S1, S2) if (isForwardRange!S1 && + isForwardRange!S2) + { + this(S1 pre, S1 separator, S2 post) + { + asTuple = typeof(asTuple)(pre, separator, post); + } + void opAssign(typeof(asTuple) rhs) + { + asTuple = rhs; + } + Tuple!(S1, S1, S2) asTuple; + bool opCast(T : bool)() + { + return !asTuple[1].empty; + } + alias asTuple this; + } + + static if (isSomeString!R1 && isSomeString!R2 + || (isRandomAccessRange!R1 && hasSlicing!R1 && hasLength!R1 && hasLength!R2)) + { + auto balance = find!pred(haystack, needle); + immutable pos1 = haystack.length - balance.length; + immutable pos2 = balance.empty ? pos1 : pos1 + needle.length; + return Result!(typeof(haystack[0 .. pos1]), + typeof(haystack[pos2 .. haystack.length]))(haystack[0 .. pos1], + haystack[pos1 .. pos2], + haystack[pos2 .. haystack.length]); + } + else + { + import std.range : takeExactly; + auto original = haystack.save; + auto h = haystack.save; + auto n = needle.save; + size_t pos1, pos2; + while (!n.empty && !h.empty) + { + if (binaryFun!pred(h.front, n.front)) + { + h.popFront(); + n.popFront(); + ++pos2; + } + else + { + haystack.popFront(); + n = needle.save; + h = haystack.save; + pos2 = ++pos1; + } + } + return Result!(typeof(takeExactly(original, pos1)), + typeof(h))(takeExactly(original, pos1), + takeExactly(haystack, pos2 - pos1), + h); + } +} + +/// Ditto +auto findSplitBefore(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) +if (isForwardRange!R1 && isForwardRange!R2) +{ + static struct Result(S1, S2) if (isForwardRange!S1 && + isForwardRange!S2) + { + this(S1 pre, S2 post) + { + asTuple = typeof(asTuple)(pre, post); + } + void opAssign(typeof(asTuple) rhs) + { + asTuple = rhs; + } + Tuple!(S1, S2) asTuple; + bool opCast(T : bool)() + { + return !asTuple[0].empty; + } + alias asTuple this; + } + + static if (isSomeString!R1 && isSomeString!R2 + || (isRandomAccessRange!R1 && hasLength!R1 && hasSlicing!R1 && hasLength!R2)) + { + auto balance = find!pred(haystack, needle); + immutable pos = haystack.length - balance.length; + return Result!(typeof(haystack[0 .. pos]), + typeof(haystack[pos .. haystack.length]))(haystack[0 .. pos], + haystack[pos .. haystack.length]); + } + else + { + import std.range : takeExactly; + auto original = haystack.save; + auto h = haystack.save; + auto n = needle.save; + size_t pos; + while (!n.empty && !h.empty) + { + if (binaryFun!pred(h.front, n.front)) + { + h.popFront(); + n.popFront(); + } + else + { + haystack.popFront(); + n = needle.save; + h = haystack.save; + ++pos; + } + } + return Result!(typeof(takeExactly(original, pos)), + typeof(haystack))(takeExactly(original, pos), + haystack); + } +} + +/// Ditto +auto findSplitAfter(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) +if (isForwardRange!R1 && isForwardRange!R2) +{ + static struct Result(S1, S2) if (isForwardRange!S1 && + isForwardRange!S2) + { + this(S1 pre, S2 post) + { + asTuple = typeof(asTuple)(pre, post); + } + void opAssign(typeof(asTuple) rhs) + { + asTuple = rhs; + } + Tuple!(S1, S2) asTuple; + bool opCast(T : bool)() + { + return !asTuple[1].empty; + } + alias asTuple this; + } + + static if (isSomeString!R1 && isSomeString!R2 + || isRandomAccessRange!R1 && hasLength!R1 && hasSlicing!R1 && hasLength!R2) + { + auto balance = find!pred(haystack, needle); + immutable pos = balance.empty ? 0 : haystack.length - balance.length + needle.length; + return Result!(typeof(haystack[0 .. pos]), + typeof(haystack[pos .. haystack.length]))(haystack[0 .. pos], + haystack[pos .. haystack.length]); + } + else + { + import std.range : takeExactly; + auto original = haystack.save; + auto h = haystack.save; + auto n = needle.save; + size_t pos1, pos2; + while (!n.empty) + { + if (h.empty) + { + // Failed search + return Result!(typeof(takeExactly(original, 0)), + typeof(original))(takeExactly(original, 0), + original); + } + if (binaryFun!pred(h.front, n.front)) + { + h.popFront(); + n.popFront(); + ++pos2; + } + else + { + haystack.popFront(); + n = needle.save; + h = haystack.save; + pos2 = ++pos1; + } + } + return Result!(typeof(takeExactly(original, pos2)), + typeof(h))(takeExactly(original, pos2), + h); + } +} + +/// +@safe pure nothrow unittest +{ + import std.range.primitives : empty; + + auto a = "Carl Sagan Memorial Station"; + auto r = findSplit(a, "Velikovsky"); + import std.typecons : isTuple; + static assert(isTuple!(typeof(r.asTuple))); + static assert(isTuple!(typeof(r))); + assert(!r); + assert(r[0] == a); + assert(r[1].empty); + assert(r[2].empty); + r = findSplit(a, " "); + assert(r[0] == "Carl"); + assert(r[1] == " "); + assert(r[2] == "Sagan Memorial Station"); + auto r1 = findSplitBefore(a, "Sagan"); + assert(r1); + assert(r1[0] == "Carl "); + assert(r1[1] == "Sagan Memorial Station"); + auto r2 = findSplitAfter(a, "Sagan"); + assert(r2); + assert(r2[0] == "Carl Sagan"); + assert(r2[1] == " Memorial Station"); +} + +/// Use $(REF only, std,range) to find single elements: +@safe pure nothrow unittest +{ + import std.range : only; + assert([1, 2, 3, 4].findSplitBefore(only(3))[0] == [1, 2]); +} + +@safe pure nothrow unittest +{ + import std.range.primitives : empty; + + auto a = [ 1, 2, 3, 4, 5, 6, 7, 8 ]; + auto r = findSplit(a, [9, 1]); + assert(!r); + assert(r[0] == a); + assert(r[1].empty); + assert(r[2].empty); + r = findSplit(a, [3]); + assert(r); + assert(r[0] == a[0 .. 2]); + assert(r[1] == a[2 .. 3]); + assert(r[2] == a[3 .. $]); + + auto r1 = findSplitBefore(a, [9, 1]); + assert(r1); + assert(r1[0] == a); + assert(r1[1].empty); + r1 = findSplitBefore(a, [3, 4]); + assert(r1); + assert(r1[0] == a[0 .. 2]); + assert(r1[1] == a[2 .. $]); + + auto r2 = findSplitAfter(a, [9, 1]); + assert(r2); + assert(r2[0].empty); + assert(r2[1] == a); + r2 = findSplitAfter(a, [3, 4]); + assert(r2); + assert(r2[0] == a[0 .. 4]); + assert(r2[1] == a[4 .. $]); +} + +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filter; + + auto a = [ 1, 2, 3, 4, 5, 6, 7, 8 ]; + auto fwd = filter!"a > 0"(a); + auto r = findSplit(fwd, [9, 1]); + assert(!r); + assert(equal(r[0], a)); + assert(r[1].empty); + assert(r[2].empty); + r = findSplit(fwd, [3]); + assert(r); + assert(equal(r[0], a[0 .. 2])); + assert(equal(r[1], a[2 .. 3])); + assert(equal(r[2], a[3 .. $])); + + auto r1 = findSplitBefore(fwd, [9, 1]); + assert(r1); + assert(equal(r1[0], a)); + assert(r1[1].empty); + r1 = findSplitBefore(fwd, [3, 4]); + assert(r1); + assert(equal(r1[0], a[0 .. 2])); + assert(equal(r1[1], a[2 .. $])); + + auto r2 = findSplitAfter(fwd, [9, 1]); + assert(r2); + assert(r2[0].empty); + assert(equal(r2[1], a)); + r2 = findSplitAfter(fwd, [3, 4]); + assert(r2); + assert(equal(r2[0], a[0 .. 4])); + assert(equal(r2[1], a[4 .. $])); +} + +@safe pure nothrow @nogc unittest +{ + auto str = "sep,one,sep,two"; + + auto split = str.findSplitAfter(","); + assert(split[0] == "sep,"); + + split = split[1].findSplitAfter(","); + assert(split[0] == "one,"); + + split = split[1].findSplitBefore(","); + assert(split[0] == "sep"); +} + +@safe pure nothrow @nogc unittest +{ + auto str = "sep,one,sep,two"; + + auto split = str.findSplitBefore(",two"); + assert(split[0] == "sep,one,sep"); + assert(split[1] == ",two"); + + split = split[0].findSplitBefore(",sep"); + assert(split[0] == "sep,one"); + assert(split[1] == ",sep"); + + split = split[0].findSplitAfter(","); + assert(split[0] == "sep,"); + assert(split[1] == "one"); +} + +// minCount +/** + +Computes the minimum (respectively maximum) of `range` along with its number of +occurrences. Formally, the minimum is a value `x` in `range` such that $(D +pred(a, x)) is `false` for all values `a` in `range`. Conversely, the maximum is +a value `x` in `range` such that $(D pred(x, a)) is `false` for all values `a` +in `range` (note the swapped arguments to `pred`). + +These functions may be used for computing arbitrary extrema by choosing `pred` +appropriately. For corrrect functioning, `pred` must be a strict partial order, +i.e. transitive (if $(D pred(a, b) && pred(b, c)) then $(D pred(a, c))) and +irreflexive ($(D pred(a, a)) is `false`). The $(LUCKY trichotomy property of +inequality) is not required: these algoritms consider elements `a` and `b` equal +(for the purpose of counting) if `pred` puts them in the same equivalence class, +i.e. $(D !pred(a, b) && !pred(b, a)). + +Params: + pred = The ordering predicate to use to determine the extremum (minimum + or maximum). + range = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to count. + +Returns: The minimum, respectively maximum element of a range together with the +number it occurs in the range. + +Throws: `Exception` if `range.empty`. + */ +Tuple!(ElementType!Range, size_t) +minCount(alias pred = "a < b", Range)(Range range) +if (isInputRange!Range && !isInfinite!Range && + is(typeof(binaryFun!pred(range.front, range.front)))) +{ + import std.algorithm.internal : algoFormat; + import std.exception : enforce; + + alias T = ElementType!Range; + alias UT = Unqual!T; + alias RetType = Tuple!(T, size_t); + + static assert(is(typeof(RetType(range.front, 1))), + algoFormat("Error: Cannot call minCount on a %s, because it is not possible "~ + "to copy the result value (a %s) into a Tuple.", Range.stringof, T.stringof)); + + enforce(!range.empty, "Can't count elements from an empty range"); + size_t occurrences = 1; + + static if (isForwardRange!Range) + { + Range least = range.save; + for (range.popFront(); !range.empty; range.popFront()) + { + if (binaryFun!pred(least.front, range.front)) + { + assert(!binaryFun!pred(range.front, least.front), + "min/maxPos: predicate must be a strict partial order."); + continue; + } + if (binaryFun!pred(range.front, least.front)) + { + // change the min + least = range.save; + occurrences = 1; + } + else + ++occurrences; + } + return RetType(least.front, occurrences); + } + else static if (isAssignable!(UT, T) || (!hasElaborateAssign!UT && isAssignable!UT)) + { + UT v = UT.init; + static if (isAssignable!(UT, T)) v = range.front; + else v = cast(UT) range.front; + + for (range.popFront(); !range.empty; range.popFront()) + { + if (binaryFun!pred(*cast(T*)&v, range.front)) continue; + if (binaryFun!pred(range.front, *cast(T*)&v)) + { + // change the min + static if (isAssignable!(UT, T)) v = range.front; + else v = cast(UT) range.front; //Safe because !hasElaborateAssign!UT + occurrences = 1; + } + else + ++occurrences; + } + return RetType(*cast(T*)&v, occurrences); + } + else static if (hasLvalueElements!Range) + { + import std.algorithm.internal : addressOf; + T* p = addressOf(range.front); + for (range.popFront(); !range.empty; range.popFront()) + { + if (binaryFun!pred(*p, range.front)) continue; + if (binaryFun!pred(range.front, *p)) + { + // change the min + p = addressOf(range.front); + occurrences = 1; + } + else + ++occurrences; + } + return RetType(*p, occurrences); + } + else + static assert(false, + algoFormat("Sorry, can't find the minCount of a %s: Don't know how "~ + "to keep track of the smallest %s element.", Range.stringof, T.stringof)); +} + +/// Ditto +Tuple!(ElementType!Range, size_t) +maxCount(alias pred = "a < b", Range)(Range range) +if (isInputRange!Range && !isInfinite!Range && + is(typeof(binaryFun!pred(range.front, range.front)))) +{ + return range.minCount!((a, b) => binaryFun!pred(b, a)); +} + +/// +@safe unittest +{ + import std.conv : text; + import std.typecons : tuple; + + int[] a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ]; + // Minimum is 1 and occurs 3 times + assert(a.minCount == tuple(1, 3)); + // Maximum is 4 and occurs 2 times + assert(a.maxCount == tuple(4, 2)); +} + +@system unittest +{ + import std.conv : text; + import std.exception : assertThrown; + import std.internal.test.dummyrange; + + int[][] b = [ [4], [2, 4], [4], [4] ]; + auto c = minCount!("a[0] < b[0]")(b); + assert(c == tuple([2, 4], 1), text(c[0])); + + //Test empty range + assertThrown(minCount(b[$..$])); + + //test with reference ranges. Test both input and forward. + assert(minCount(new ReferenceInputRange!int([1, 2, 1, 0, 2, 0])) == tuple(0, 2)); + assert(minCount(new ReferenceForwardRange!int([1, 2, 1, 0, 2, 0])) == tuple(0, 2)); +} + +@system unittest +{ + import std.conv : text; + import std.meta : AliasSeq; + + static struct R(T) //input range + { + T[] arr; + alias arr this; + } + + immutable a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ]; + R!(immutable int) b = R!(immutable int)(a); + + assert(minCount(a) == tuple(1, 3)); + assert(minCount(b) == tuple(1, 3)); + assert(minCount!((ref immutable int a, ref immutable int b) => (a > b))(a) == tuple(4, 2)); + assert(minCount!((ref immutable int a, ref immutable int b) => (a > b))(b) == tuple(4, 2)); + + immutable(int[])[] c = [ [4], [2, 4], [4], [4] ]; + assert(minCount!("a[0] < b[0]")(c) == tuple([2, 4], 1), text(c[0])); + + static struct S1 + { + int i; + } + alias IS1 = immutable(S1); + static assert( isAssignable!S1); + static assert( isAssignable!(S1, IS1)); + + static struct S2 + { + int* p; + this(ref immutable int i) immutable {p = &i;} + this(ref int i) {p = &i;} + @property ref inout(int) i() inout {return *p;} + bool opEquals(const S2 other) const {return i == other.i;} + } + alias IS2 = immutable(S2); + static assert( isAssignable!S2); + static assert(!isAssignable!(S2, IS2)); + static assert(!hasElaborateAssign!S2); + + static struct S3 + { + int i; + void opAssign(ref S3 other) @disable; + } + static assert(!isAssignable!S3); + + foreach (Type; AliasSeq!(S1, IS1, S2, IS2, S3)) + { + static if (is(Type == immutable)) alias V = immutable int; + else alias V = int; + V one = 1, two = 2; + auto r1 = [Type(two), Type(one), Type(one)]; + auto r2 = R!Type(r1); + assert(minCount!"a.i < b.i"(r1) == tuple(Type(one), 2)); + assert(minCount!"a.i < b.i"(r2) == tuple(Type(one), 2)); + assert(one == 1 && two == 2); + } +} + +/** +Iterates the passed range and returns the minimal element. +A custom mapping function can be passed to `map`. +In other languages this is sometimes called `argmin`. + +Complexity: O(n) + Exactly `n - 1` comparisons are needed. + +Params: + map = custom accessor for the comparison key + r = range from which the minimal element will be selected + seed = custom seed to use as initial element + +Returns: The minimal element of the passed-in range. + +See_Also: + $(REF min, std,algorithm,comparison) +*/ +auto minElement(alias map, Range)(Range r) +if (isInputRange!Range && !isInfinite!Range) +{ + return extremum!map(r); +} + +/// ditto +auto minElement(Range)(Range r) + if (isInputRange!Range && !isInfinite!Range) +{ + return extremum(r); +} + +/// ditto +auto minElement(alias map, Range, RangeElementType = ElementType!Range) + (Range r, RangeElementType seed) +if (isInputRange!Range && !isInfinite!Range && + !is(CommonType!(ElementType!Range, RangeElementType) == void)) +{ + return extremum!map(r, seed); +} + +/// ditto +auto minElement(Range, RangeElementType = ElementType!Range) + (Range r, RangeElementType seed) + if (isInputRange!Range && !isInfinite!Range && + !is(CommonType!(ElementType!Range, RangeElementType) == void)) +{ + return extremum(r, seed); +} + +/// +@safe pure unittest +{ + import std.range : enumerate; + import std.typecons : tuple; + + assert([2, 1, 4, 3].minElement == 1); + + // allows to get the index of an element too + assert([5, 3, 7, 9].enumerate.minElement!"a.value" == tuple(1, 3)); + + // any custom accessor can be passed + assert([[0, 4], [1, 2]].minElement!"a[1]" == [1, 2]); + + // can be seeded + int[] arr; + assert(arr.minElement(1) == 1); +} + +@safe pure unittest +{ + import std.range : enumerate, iota; + // supports mapping + assert([3, 4, 5, 1, 2].enumerate.minElement!"a.value" == tuple(3, 1)); + assert([5, 2, 4].enumerate.minElement!"a.value" == tuple(1, 2)); + + // forward ranges + assert(iota(1, 5).minElement() == 1); + assert(iota(2, 5).enumerate.minElement!"a.value" == tuple(0, 2)); + + // should work with const + const(int)[] immArr = [2, 1, 3]; + assert(immArr.minElement == 1); + + // should work with immutable + immutable(int)[] immArr2 = [2, 1, 3]; + assert(immArr2.minElement == 1); + + // with strings + assert(["b", "a", "c"].minElement == "a"); + + // with all dummy ranges + import std.internal.test.dummyrange; + foreach (DummyType; AllDummyRanges) + { + DummyType d; + assert(d.minElement == 1); + assert(d.minElement!(a => a) == 1); + } + + // with empty, but seeded ranges + int[] arr; + assert(arr.minElement(42) == 42); + assert(arr.minElement!(a => a)(42) == 42); +} + +@nogc @safe nothrow pure unittest +{ + static immutable arr = [7, 3, 4, 2, 1, 8]; + assert(arr.minElement == 1); + + static immutable arr2d = [[1, 9], [3, 1], [4, 2]]; + assert(arr2d.minElement!"a[1]" == arr2d[1]); +} + +/** +Iterates the passed range and returns the maximal element. +A custom mapping function can be passed to `map`. +In other languages this is sometimes called `argmax`. + +Complexity: + Exactly `n - 1` comparisons are needed. + +Params: + map = custom accessor for the comparison key + r = range from which the maximum will be selected + seed = custom seed to use as initial element + +Returns: The maximal element of the passed-in range. + +See_Also: + $(REF max, std,algorithm,comparison) +*/ +auto maxElement(alias map, Range)(Range r) +if (isInputRange!Range && !isInfinite!Range) +{ + return extremum!(map, "a > b")(r); +} + +/// ditto +auto maxElement(Range)(Range r) +if (isInputRange!Range && !isInfinite!Range) +{ + return extremum!`a > b`(r); +} + +/// ditto +auto maxElement(alias map, Range, RangeElementType = ElementType!Range) + (Range r, RangeElementType seed) +if (isInputRange!Range && !isInfinite!Range && + !is(CommonType!(ElementType!Range, RangeElementType) == void)) +{ + return extremum!(map, "a > b")(r, seed); +} + +/// ditto +auto maxElement(Range, RangeElementType = ElementType!Range) + (Range r, RangeElementType seed) +if (isInputRange!Range && !isInfinite!Range && + !is(CommonType!(ElementType!Range, RangeElementType) == void)) +{ + return extremum!`a > b`(r, seed); +} + +/// +@safe pure unittest +{ + import std.range : enumerate; + import std.typecons : tuple; + assert([2, 1, 4, 3].maxElement == 4); + + // allows to get the index of an element too + assert([2, 1, 4, 3].enumerate.maxElement!"a.value" == tuple(2, 4)); + + // any custom accessor can be passed + assert([[0, 4], [1, 2]].maxElement!"a[1]" == [0, 4]); + + // can be seeded + int[] arr; + assert(arr.minElement(1) == 1); +} + +@safe pure unittest +{ + import std.range : enumerate, iota; + + // supports mapping + assert([3, 4, 5, 1, 2].enumerate.maxElement!"a.value" == tuple(2, 5)); + assert([5, 2, 4].enumerate.maxElement!"a.value" == tuple(0, 5)); + + // forward ranges + assert(iota(1, 5).maxElement() == 4); + assert(iota(2, 5).enumerate.maxElement!"a.value" == tuple(2, 4)); + assert(iota(4, 14).enumerate.maxElement!"a.value" == tuple(9, 13)); + + // should work with const + const(int)[] immArr = [2, 3, 1]; + assert(immArr.maxElement == 3); + + // should work with immutable + immutable(int)[] immArr2 = [2, 3, 1]; + assert(immArr2.maxElement == 3); + + // with strings + assert(["a", "c", "b"].maxElement == "c"); + + // with all dummy ranges + import std.internal.test.dummyrange; + foreach (DummyType; AllDummyRanges) + { + DummyType d; + assert(d.maxElement == 10); + assert(d.maxElement!(a => a) == 10); + } + + // with empty, but seeded ranges + int[] arr; + assert(arr.maxElement(42) == 42); + assert(arr.maxElement!(a => a)(42) == 42); + +} + +@nogc @safe nothrow pure unittest +{ + static immutable arr = [7, 3, 8, 2, 1, 4]; + assert(arr.maxElement == 8); + + static immutable arr2d = [[1, 3], [3, 9], [4, 2]]; + assert(arr2d.maxElement!"a[1]" == arr2d[1]); +} + +// minPos +/** +Computes a subrange of `range` starting at the first occurrence of `range`'s +minimum (respectively maximum) and with the same ending as `range`, or the +empty range if `range` itself is empty. + +Formally, the minimum is a value `x` in `range` such that $(D pred(a, x)) is +`false` for all values `a` in `range`. Conversely, the maximum is a value `x` in +`range` such that $(D pred(x, a)) is `false` for all values `a` in `range` (note +the swapped arguments to `pred`). + +These functions may be used for computing arbitrary extrema by choosing `pred` +appropriately. For corrrect functioning, `pred` must be a strict partial order, +i.e. transitive (if $(D pred(a, b) && pred(b, c)) then $(D pred(a, c))) and +irreflexive ($(D pred(a, a)) is `false`). + +Params: + pred = The ordering predicate to use to determine the extremum (minimum or + maximum) element. + range = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to search. + +Returns: The position of the minimum (respectively maximum) element of forward +range `range`, i.e. a subrange of `range` starting at the position of its +smallest (respectively largest) element and with the same ending as `range`. + +*/ +Range minPos(alias pred = "a < b", Range)(Range range) +if (isForwardRange!Range && !isInfinite!Range && + is(typeof(binaryFun!pred(range.front, range.front)))) +{ + static if (hasSlicing!Range && isRandomAccessRange!Range && hasLength!Range) + { + // Prefer index-based access + size_t pos = 0; + foreach (i; 1 .. range.length) + { + if (binaryFun!pred(range[i], range[pos])) + { + pos = i; + } + } + return range[pos .. range.length]; + } + else + { + auto result = range.save; + if (range.empty) return result; + for (range.popFront(); !range.empty; range.popFront()) + { + // Note: Unlike minCount, we do not care to find equivalence, so a + // single pred call is enough. + if (binaryFun!pred(range.front, result.front)) + { + // change the min + result = range.save; + } + } + return result; + } +} + +/// Ditto +Range maxPos(alias pred = "a < b", Range)(Range range) +if (isForwardRange!Range && !isInfinite!Range && + is(typeof(binaryFun!pred(range.front, range.front)))) +{ + return range.minPos!((a, b) => binaryFun!pred(b, a)); +} + +/// +@safe unittest +{ + int[] a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ]; + // Minimum is 1 and first occurs in position 3 + assert(a.minPos == [ 1, 2, 4, 1, 1, 2 ]); + // Maximum is 4 and first occurs in position 2 + assert(a.maxPos == [ 4, 1, 2, 4, 1, 1, 2 ]); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange; + + int[] a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ]; + //Test that an empty range works + int[] b = a[$..$]; + assert(equal(minPos(b), b)); + + //test with reference range. + assert( equal( minPos(new ReferenceForwardRange!int([1, 2, 1, 0, 2, 0])), [0, 2, 0] ) ); +} + +@system unittest +{ + //Rvalue range + import std.algorithm.comparison : equal; + import std.container : Array; + + assert(Array!int(2, 3, 4, 1, 2, 4, 1, 1, 2) + [] + .minPos() + .equal([ 1, 2, 4, 1, 1, 2 ])); +} + +@safe unittest +{ + //BUG 9299 + immutable a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ]; + // Minimum is 1 and first occurs in position 3 + assert(minPos(a) == [ 1, 2, 4, 1, 1, 2 ]); + // Maximum is 4 and first occurs in position 5 + assert(minPos!("a > b")(a) == [ 4, 1, 2, 4, 1, 1, 2 ]); + + immutable(int[])[] b = [ [4], [2, 4], [4], [4] ]; + assert(minPos!("a[0] < b[0]")(b) == [ [2, 4], [4], [4] ]); +} + +/** +Computes the index of the first occurrence of `range`'s minimum element. + +Params: + pred = The ordering predicate to use to determine the minimum element. + range = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + to search. + +Complexity: O(n) + Exactly `n - 1` comparisons are needed. + +Returns: + The index of the first encounter of the minimum element in `range`. If the + `range` is empty, -1 is returned. + +See_Also: + $(REF min, std,algorithm,comparison), $(LREF minCount), $(LREF minElement), $(LREF minPos) + */ +sizediff_t minIndex(alias pred = "a < b", Range)(Range range) +if (isForwardRange!Range && !isInfinite!Range && + is(typeof(binaryFun!pred(range.front, range.front)))) +{ + if (range.empty) return -1; + + sizediff_t minPos = 0; + + static if (isRandomAccessRange!Range && hasLength!Range) + { + foreach (i; 1 .. range.length) + { + if (binaryFun!pred(range[i], range[minPos])) + { + minPos = i; + } + } + } + else + { + sizediff_t curPos = 0; + Unqual!(typeof(range.front)) min = range.front; + for (range.popFront(); !range.empty; range.popFront()) + { + ++curPos; + if (binaryFun!pred(range.front, min)) + { + min = range.front; + minPos = curPos; + } + } + } + return minPos; +} + +/// +@safe pure nothrow unittest +{ + int[] a = [2, 3, 4, 1, 2, 4, 1, 1, 2]; + + // Minimum is 1 and first occurs in position 3 + assert(a.minIndex == 3); + // Get maximum index with minIndex + assert(a.minIndex!"a > b" == 2); + + // Range is empty, so return value is -1 + int[] b; + assert(b.minIndex == -1); + + // Works with more custom types + struct Dog { int age; } + Dog[] dogs = [Dog(10), Dog(5), Dog(15)]; + assert(dogs.minIndex!"a.age < b.age" == 1); +} + +@safe pure unittest +{ + // should work with const + const(int)[] immArr = [2, 1, 3]; + assert(immArr.minIndex == 1); + + // Works for const ranges too + const int[] c = [2, 5, 4, 1, 2, 3]; + assert(c.minIndex == 3); + + // should work with immutable + immutable(int)[] immArr2 = [2, 1, 3]; + assert(immArr2.minIndex == 1); + + // with strings + assert(["b", "a", "c"].minIndex == 1); + + // infinite range + import std.range : cycle; + static assert(!__traits(compiles, cycle([1]).minIndex)); + + // with all dummy ranges + import std.internal.test.dummyrange : AllDummyRanges; + foreach (DummyType; AllDummyRanges) + { + static if (isForwardRange!DummyType && !isInfinite!DummyType) + { + DummyType d; + d.arr = [5, 3, 7, 2, 1, 4]; + assert(d.minIndex == 4); + + d.arr = []; + assert(d.minIndex == -1); + } + } +} + +@nogc @safe nothrow pure unittest +{ + static immutable arr = [7, 3, 8, 2, 1, 4]; + assert(arr.minIndex == 4); + + static immutable arr2d = [[1, 3], [3, 9], [4, 2]]; + assert(arr2d.minIndex!"a[1] < b[1]" == 2); +} + +/** +Computes the index of the first occurrence of `range`'s maximum element. + +Complexity: O(n) + Exactly `n - 1` comparisons are needed. + +Params: + pred = The ordering predicate to use to determine the maximum element. + range = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to search. + +Returns: + The index of the first encounter of the maximum in `range`. If the + `range` is empty, -1 is returned. + +See_Also: + $(REF max, std,algorithm,comparison), $(LREF maxCount), $(LREF maxElement), $(LREF maxPos) + */ +sizediff_t maxIndex(alias pred = "a < b", Range)(Range range) +if (isInputRange!Range && !isInfinite!Range && + is(typeof(binaryFun!pred(range.front, range.front)))) +{ + return range.minIndex!((a, b) => binaryFun!pred(b, a)); +} + +/// +@safe pure nothrow unittest +{ + // Maximum is 4 and first occurs in position 2 + int[] a = [2, 3, 4, 1, 2, 4, 1, 1, 2]; + assert(a.maxIndex == 2); + + // Empty range + int[] b; + assert(b.maxIndex == -1); + + // Works with more custom types + struct Dog { int age; } + Dog[] dogs = [Dog(10), Dog(15), Dog(5)]; + assert(dogs.maxIndex!"a.age < b.age" == 1); +} + +@safe pure unittest +{ + // should work with const + const(int)[] immArr = [5, 1, 3]; + assert(immArr.maxIndex == 0); + + // Works for const ranges too + const int[] c = [2, 5, 4, 1, 2, 3]; + assert(c.maxIndex == 1); + + + // should work with immutable + immutable(int)[] immArr2 = [2, 1, 3]; + assert(immArr2.maxIndex == 2); + + // with strings + assert(["b", "a", "c"].maxIndex == 2); + + // infinite range + import std.range : cycle; + static assert(!__traits(compiles, cycle([1]).maxIndex)); + + // with all dummy ranges + import std.internal.test.dummyrange : AllDummyRanges; + foreach (DummyType; AllDummyRanges) + { + static if (isForwardRange!DummyType && !isInfinite!DummyType) + { + DummyType d; + + d.arr = [5, 3, 7, 2, 1, 4]; + assert(d.maxIndex == 2); + + d.arr = []; + assert(d.maxIndex == -1); + } + } +} + +@nogc @safe nothrow pure unittest +{ + static immutable arr = [7, 3, 8, 2, 1, 4]; + assert(arr.maxIndex == 2); + + static immutable arr2d = [[1, 3], [3, 9], [4, 2]]; + assert(arr2d.maxIndex!"a[1] < b[1]" == 1); +} + +/** +Skip over the initial portion of the first given range that matches the second +range, or if no second range is given skip over the elements that fullfil pred. +Do nothing if there is no match. + +Params: + pred = The predicate that determines whether elements from each respective + range match. Defaults to equality $(D "a == b"). + r1 = The $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) to + move forward. + r2 = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + representing the initial segment of $(D r1) to skip over. + +Returns: +true if the initial segment of $(D r1) matches $(D r2) or $(D pred) evaluates to true, +and $(D r1) has been advanced to the point past this segment; otherwise false, and +$(D r1) is left in its original position. + */ +bool skipOver(R1, R2)(ref R1 r1, R2 r2) +if (isForwardRange!R1 && isInputRange!R2 + && is(typeof(r1.front == r2.front))) +{ + static if (is(typeof(r1[0 .. $] == r2) : bool) + && is(typeof(r2.length > r1.length) : bool) + && is(typeof(r1 = r1[r2.length .. $]))) + { + if (r2.length > r1.length || r1[0 .. r2.length] != r2) + { + return false; + } + r1 = r1[r2.length .. $]; + return true; + } + else + { + return skipOver!((a, b) => a == b)(r1, r2); + } +} + +/// Ditto +bool skipOver(alias pred, R1, R2)(ref R1 r1, R2 r2) +if (is(typeof(binaryFun!pred(r1.front, r2.front))) && + isForwardRange!R1 && + isInputRange!R2) +{ + static if (hasLength!R1 && hasLength!R2) + { + // Shortcut opportunity! + if (r2.length > r1.length) + return false; + } + auto r = r1.save; + while (!r2.empty && !r.empty && binaryFun!pred(r.front, r2.front)) + { + r.popFront(); + r2.popFront(); + } + if (r2.empty) + r1 = r; + return r2.empty; +} + +/// Ditto +bool skipOver(alias pred, R)(ref R r1) +if (isForwardRange!R && + ifTestable!(typeof(r1.front), unaryFun!pred)) +{ + if (r1.empty || !unaryFun!pred(r1.front)) + return false; + + do + r1.popFront(); + while (!r1.empty && unaryFun!pred(r1.front)); + return true; +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto s1 = "Hello world"; + assert(!skipOver(s1, "Ha")); + assert(s1 == "Hello world"); + assert(skipOver(s1, "Hell") && s1 == "o world"); + + string[] r1 = ["abc", "def", "hij"]; + dstring[] r2 = ["abc"d]; + assert(!skipOver!((a, b) => a.equal(b))(r1, ["def"d])); + assert(r1 == ["abc", "def", "hij"]); + assert(skipOver!((a, b) => a.equal(b))(r1, r2)); + assert(r1 == ["def", "hij"]); + + import std.ascii : isWhite; + import std.range.primitives : empty; + + auto s2 = "\t\tvalue"; + auto s3 = ""; + auto s4 = "\t\t\t"; + assert(s2.skipOver!isWhite && s2 == "value"); + assert(!s3.skipOver!isWhite); + assert(s4.skipOver!isWhite && s3.empty); +} + +/** +Skip over the first element of the given range if it matches the given element, +otherwise do nothing. + +Params: + pred = The predicate that determines whether an element from the range + matches the given element. + + r = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to skip + over. + + e = The element to match. + +Returns: +true if the first element matches the given element according to the given +predicate, and the range has been advanced by one element; otherwise false, and +the range is left untouched. + */ +bool skipOver(R, E)(ref R r, E e) +if (isInputRange!R && is(typeof(r.front == e) : bool)) +{ + return skipOver!((a, b) => a == b)(r, e); +} + +/// Ditto +bool skipOver(alias pred, R, E)(ref R r, E e) +if (is(typeof(binaryFun!pred(r.front, e))) && isInputRange!R) +{ + if (r.empty || !binaryFun!pred(r.front, e)) + return false; + r.popFront(); + return true; +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto s1 = "Hello world"; + assert(!skipOver(s1, 'a')); + assert(s1 == "Hello world"); + assert(skipOver(s1, 'H') && s1 == "ello world"); + + string[] r = ["abc", "def", "hij"]; + dstring e = "abc"d; + assert(!skipOver!((a, b) => a.equal(b))(r, "def"d)); + assert(r == ["abc", "def", "hij"]); + assert(skipOver!((a, b) => a.equal(b))(r, e)); + assert(r == ["def", "hij"]); + + auto s2 = ""; + assert(!s2.skipOver('a')); +} + +/** +Checks whether the given +$(REF_ALTTEXT input range, isInputRange, std,range,primitives) starts with (one +of) the given needle(s) or, if no needles are given, +if its front element fulfils predicate $(D pred). + +Params: + + pred = Predicate to use in comparing the elements of the haystack and the + needle(s). Mandatory if no needles are given. + + doesThisStart = The input range to check. + + withOneOfThese = The needles against which the range is to be checked, + which may be individual elements or input ranges of elements. + + withThis = The single needle to check, which may be either a single element + or an input range of elements. + +Returns: + +0 if the needle(s) do not occur at the beginning of the given range; +otherwise the position of the matching needle, that is, 1 if the range starts +with $(D withOneOfThese[0]), 2 if it starts with $(D withOneOfThese[1]), and so +on. + +In the case where $(D doesThisStart) starts with multiple of the ranges or +elements in $(D withOneOfThese), then the shortest one matches (if there are +two which match which are of the same length (e.g. $(D "a") and $(D 'a')), then +the left-most of them in the argument +list matches). + +In the case when no needle parameters are given, return $(D true) iff front of +$(D doesThisStart) fulfils predicate $(D pred). + */ +uint startsWith(alias pred = "a == b", Range, Needles...)(Range doesThisStart, Needles withOneOfThese) +if (isInputRange!Range && Needles.length > 1 && + is(typeof(.startsWith!pred(doesThisStart, withOneOfThese[0])) : bool ) && + is(typeof(.startsWith!pred(doesThisStart, withOneOfThese[1 .. $])) : uint)) +{ + alias haystack = doesThisStart; + alias needles = withOneOfThese; + + // Make one pass looking for empty ranges in needles + foreach (i, Unused; Needles) + { + // Empty range matches everything + static if (!is(typeof(binaryFun!pred(haystack.front, needles[i])) : bool)) + { + if (needles[i].empty) return i + 1; + } + } + + for (; !haystack.empty; haystack.popFront()) + { + foreach (i, Unused; Needles) + { + static if (is(typeof(binaryFun!pred(haystack.front, needles[i])) : bool)) + { + // Single-element + if (binaryFun!pred(haystack.front, needles[i])) + { + // found, but instead of returning, we just stop searching. + // This is to account for one-element + // range matches (consider startsWith("ab", "a", + // 'a') should return 1, not 2). + break; + } + } + else + { + if (binaryFun!pred(haystack.front, needles[i].front)) + { + continue; + } + } + + // This code executed on failure to match + // Out with this guy, check for the others + uint result = startsWith!pred(haystack, needles[0 .. i], needles[i + 1 .. $]); + if (result > i) ++result; + return result; + } + + // If execution reaches this point, then the front matches for all + // needle ranges, or a needle element has been matched. + // What we need to do now is iterate, lopping off the front of + // the range and checking if the result is empty, or finding an + // element needle and returning. + // If neither happens, we drop to the end and loop. + foreach (i, Unused; Needles) + { + static if (is(typeof(binaryFun!pred(haystack.front, needles[i])) : bool)) + { + // Test has passed in the previous loop + return i + 1; + } + else + { + needles[i].popFront(); + if (needles[i].empty) return i + 1; + } + } + } + return 0; +} + +/// Ditto +bool startsWith(alias pred = "a == b", R1, R2)(R1 doesThisStart, R2 withThis) +if (isInputRange!R1 && + isInputRange!R2 && + is(typeof(binaryFun!pred(doesThisStart.front, withThis.front)) : bool)) +{ + alias haystack = doesThisStart; + alias needle = withThis; + + static if (is(typeof(pred) : string)) + enum isDefaultPred = pred == "a == b"; + else + enum isDefaultPred = false; + + //Note: While narrow strings don't have a "true" length, for a narrow string to start with another + //narrow string *of the same type*, it must have *at least* as many code units. + static if ((hasLength!R1 && hasLength!R2) || + (isNarrowString!R1 && isNarrowString!R2 && ElementEncodingType!R1.sizeof == ElementEncodingType!R2.sizeof)) + { + if (haystack.length < needle.length) + return false; + } + + static if (isDefaultPred && isArray!R1 && isArray!R2 && + is(Unqual!(ElementEncodingType!R1) == Unqual!(ElementEncodingType!R2))) + { + //Array slice comparison mode + return haystack[0 .. needle.length] == needle; + } + else static if (isRandomAccessRange!R1 && isRandomAccessRange!R2 && hasLength!R2) + { + //RA dual indexing mode + foreach (j; 0 .. needle.length) + { + if (!binaryFun!pred(haystack[j], needle[j])) + // not found + return false; + } + // found! + return true; + } + else + { + //Standard input range mode + if (needle.empty) return true; + static if (hasLength!R1 && hasLength!R2) + { + //We have previously checked that haystack.length > needle.length, + //So no need to check haystack.empty during iteration + for ( ; ; haystack.popFront() ) + { + if (!binaryFun!pred(haystack.front, needle.front)) break; + needle.popFront(); + if (needle.empty) return true; + } + } + else + { + for ( ; !haystack.empty ; haystack.popFront() ) + { + if (!binaryFun!pred(haystack.front, needle.front)) break; + needle.popFront(); + if (needle.empty) return true; + } + } + return false; + } +} + +/// Ditto +bool startsWith(alias pred = "a == b", R, E)(R doesThisStart, E withThis) +if (isInputRange!R && + is(typeof(binaryFun!pred(doesThisStart.front, withThis)) : bool)) +{ + if (doesThisStart.empty) + return false; + + alias predFunc = binaryFun!pred; + + // auto-decoding special case + static if (isNarrowString!R) + { + // specialize for ASCII as to not change previous behavior + if (withThis <= 0x7F) + return predFunc(doesThisStart[0], withThis); + else + return predFunc(doesThisStart.front, withThis); + } + else + { + return predFunc(doesThisStart.front, withThis); + } +} + +/// Ditto +bool startsWith(alias pred, R)(R doesThisStart) +if (isInputRange!R && + ifTestable!(typeof(doesThisStart.front), unaryFun!pred)) +{ + return !doesThisStart.empty && unaryFun!pred(doesThisStart.front); +} + +/// +@safe unittest +{ + import std.ascii : isAlpha; + + assert("abc".startsWith!(a => a.isAlpha)); + assert("abc".startsWith!isAlpha); + assert(!"1ab".startsWith!(a => a.isAlpha)); + assert(!"".startsWith!(a => a.isAlpha)); + + import std.algorithm.comparison : among; + assert("abc".startsWith!(a => a.among('a', 'b') != 0)); + assert(!"abc".startsWith!(a => a.among('b', 'c') != 0)); + + assert(startsWith("abc", "")); + assert(startsWith("abc", "a")); + assert(!startsWith("abc", "b")); + assert(startsWith("abc", 'a', "b") == 1); + assert(startsWith("abc", "b", "a") == 2); + assert(startsWith("abc", "a", "a") == 1); + assert(startsWith("abc", "ab", "a") == 2); + assert(startsWith("abc", "x", "a", "b") == 2); + assert(startsWith("abc", "x", "aa", "ab") == 3); + assert(startsWith("abc", "x", "aaa", "sab") == 0); + assert(startsWith("abc", "x", "aaa", "a", "sab") == 3); + + import std.typecons : Tuple; + alias C = Tuple!(int, "x", int, "y"); + assert(startsWith!"a.x == b"([ C(1,1), C(1,2), C(2,2) ], [1, 1])); + assert(startsWith!"a.x == b"([ C(1,1), C(2,1), C(2,2) ], [1, 1], [1, 2], [1, 3]) == 2); +} + +@safe unittest +{ + import std.algorithm.iteration : filter; + import std.conv : to; + import std.meta : AliasSeq; + import std.range; + + foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + { + assert(!startsWith(to!S("abc"), 'c')); + assert(startsWith(to!S("abc"), 'a', 'c') == 1); + assert(!startsWith(to!S("abc"), 'x', 'n', 'b')); + assert(startsWith(to!S("abc"), 'x', 'n', 'a') == 3); + assert(startsWith(to!S("\uFF28abc"), 'a', '\uFF28', 'c') == 2); + + foreach (T; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + //Lots of strings + assert(startsWith(to!S("abc"), to!T(""))); + assert(startsWith(to!S("ab"), to!T("a"))); + assert(startsWith(to!S("abc"), to!T("a"))); + assert(!startsWith(to!S("abc"), to!T("b"))); + assert(!startsWith(to!S("abc"), to!T("b"), "bc", "abcd", "xyz")); + assert(startsWith(to!S("abc"), to!T("ab"), 'a') == 2); + assert(startsWith(to!S("abc"), to!T("a"), "b") == 1); + assert(startsWith(to!S("abc"), to!T("b"), "a") == 2); + assert(startsWith(to!S("abc"), to!T("a"), 'a') == 1); + assert(startsWith(to!S("abc"), 'a', to!T("a")) == 1); + assert(startsWith(to!S("abc"), to!T("x"), "a", "b") == 2); + assert(startsWith(to!S("abc"), to!T("x"), "aa", "ab") == 3); + assert(startsWith(to!S("abc"), to!T("x"), "aaa", "sab") == 0); + assert(startsWith(to!S("abc"), 'a')); + assert(!startsWith(to!S("abc"), to!T("sab"))); + assert(startsWith(to!S("abc"), 'x', to!T("aaa"), 'a', "sab") == 3); + + //Unicode + assert(startsWith(to!S("\uFF28el\uFF4co"), to!T("\uFF28el"))); + assert(startsWith(to!S("\uFF28el\uFF4co"), to!T("Hel"), to!T("\uFF28el")) == 2); + assert(startsWith(to!S("日本語"), to!T("日本"))); + assert(startsWith(to!S("日本語"), to!T("日本語"))); + assert(!startsWith(to!S("日本"), to!T("日本語"))); + + //Empty + assert(startsWith(to!S(""), T.init)); + assert(!startsWith(to!S(""), 'a')); + assert(startsWith(to!S("a"), T.init)); + assert(startsWith(to!S("a"), T.init, "") == 1); + assert(startsWith(to!S("a"), T.init, 'a') == 1); + assert(startsWith(to!S("a"), 'a', T.init) == 2); + }(); + } + + //Length but no RA + assert(!startsWith("abc".takeExactly(3), "abcd".takeExactly(4))); + assert(startsWith("abc".takeExactly(3), "abcd".takeExactly(3))); + assert(startsWith("abc".takeExactly(3), "abcd".takeExactly(1))); + + foreach (T; AliasSeq!(int, short)) + { + immutable arr = cast(T[])[0, 1, 2, 3, 4, 5]; + + //RA range + assert(startsWith(arr, cast(int[]) null)); + assert(!startsWith(arr, 5)); + assert(!startsWith(arr, 1)); + assert(startsWith(arr, 0)); + assert(startsWith(arr, 5, 0, 1) == 2); + assert(startsWith(arr, [0])); + assert(startsWith(arr, [0, 1])); + assert(startsWith(arr, [0, 1], 7) == 1); + assert(!startsWith(arr, [0, 1, 7])); + assert(startsWith(arr, [0, 1, 7], [0, 1, 2]) == 2); + + //Normal input range + assert(!startsWith(filter!"true"(arr), 1)); + assert(startsWith(filter!"true"(arr), 0)); + assert(startsWith(filter!"true"(arr), [0])); + assert(startsWith(filter!"true"(arr), [0, 1])); + assert(startsWith(filter!"true"(arr), [0, 1], 7) == 1); + assert(!startsWith(filter!"true"(arr), [0, 1, 7])); + assert(startsWith(filter!"true"(arr), [0, 1, 7], [0, 1, 2]) == 2); + assert(startsWith(arr, filter!"true"([0, 1]))); + assert(startsWith(arr, filter!"true"([0, 1]), 7) == 1); + assert(!startsWith(arr, filter!"true"([0, 1, 7]))); + assert(startsWith(arr, [0, 1, 7], filter!"true"([0, 1, 2])) == 2); + + //Non-default pred + assert(startsWith!("a%10 == b%10")(arr, [10, 11])); + assert(!startsWith!("a%10 == b%10")(arr, [10, 12])); + } +} + +/* (Not yet documented.) +Consume all elements from $(D r) that are equal to one of the elements +$(D es). + */ +private void skipAll(alias pred = "a == b", R, Es...)(ref R r, Es es) +//if (is(typeof(binaryFun!pred(r1.front, es[0])))) +{ + loop: + for (; !r.empty; r.popFront()) + { + foreach (i, E; Es) + { + if (binaryFun!pred(r.front, es[i])) + { + continue loop; + } + } + break; + } +} + +@safe unittest +{ + auto s1 = "Hello world"; + skipAll(s1, 'H', 'e'); + assert(s1 == "llo world"); +} + +/** +Interval option specifier for `until` (below) and others. + +If set to $(D OpenRight.yes), then the interval is open to the right +(last element is not included). + +Otherwise if set to $(D OpenRight.no), then the interval is closed to the right +(last element included). + */ +alias OpenRight = Flag!"openRight"; + +/** +Lazily iterates $(D range) _until the element $(D e) for which +$(D pred(e, sentinel)) is true. + +This is similar to `takeWhile` in other languages. + +Params: + pred = Predicate to determine when to stop. + range = The $(REF_ALTTEXT input _range, isInputRange, std,_range,primitives) + to iterate over. + sentinel = The element to stop at. + openRight = Determines whether the element for which the given predicate is + true should be included in the resulting range ($(D No.openRight)), or + not ($(D Yes.openRight)). + +Returns: + An $(REF_ALTTEXT input _range, isInputRange, std,_range,primitives) that + iterates over the original range's elements, but ends when the specified + predicate becomes true. If the original range is a + $(REF_ALTTEXT forward _range, isForwardRange, std,_range,primitives) or + higher, this range will be a forward range. + */ +Until!(pred, Range, Sentinel) +until(alias pred = "a == b", Range, Sentinel) +(Range range, Sentinel sentinel, OpenRight openRight = Yes.openRight) +if (!is(Sentinel == OpenRight)) +{ + return typeof(return)(range, sentinel, openRight); +} + +/// Ditto +Until!(pred, Range, void) +until(alias pred, Range) +(Range range, OpenRight openRight = Yes.openRight) +{ + return typeof(return)(range, openRight); +} + +/// ditto +struct Until(alias pred, Range, Sentinel) +if (isInputRange!Range) +{ + private Range _input; + static if (!is(Sentinel == void)) + private Sentinel _sentinel; + private OpenRight _openRight; + private bool _done; + + static if (!is(Sentinel == void)) + /// + this(Range input, Sentinel sentinel, + OpenRight openRight = Yes.openRight) + { + _input = input; + _sentinel = sentinel; + _openRight = openRight; + _done = _input.empty || openRight && predSatisfied(); + } + else + /// + this(Range input, OpenRight openRight = Yes.openRight) + { + _input = input; + _openRight = openRight; + _done = _input.empty || openRight && predSatisfied(); + } + + /// + @property bool empty() + { + return _done; + } + + /// + @property auto ref front() + { + assert(!empty); + return _input.front; + } + + private bool predSatisfied() + { + static if (is(Sentinel == void)) + return cast(bool) unaryFun!pred(_input.front); + else + return cast(bool) startsWith!pred(_input, _sentinel); + } + + /// + void popFront() + { + assert(!empty); + if (!_openRight) + { + _done = predSatisfied(); + _input.popFront(); + _done = _done || _input.empty; + } + else + { + _input.popFront(); + _done = _input.empty || predSatisfied(); + } + } + + static if (isForwardRange!Range) + { + static if (!is(Sentinel == void)) + /// + @property Until save() + { + Until result = this; + result._input = _input.save; + result._sentinel = _sentinel; + result._openRight = _openRight; + result._done = _done; + return result; + } + else + /// + @property Until save() + { + Until result = this; + result._input = _input.save; + result._openRight = _openRight; + result._done = _done; + return result; + } + } +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : No; + int[] a = [ 1, 2, 4, 7, 7, 2, 4, 7, 3, 5]; + assert(equal(a.until(7), [1, 2, 4])); + assert(equal(a.until(7, No.openRight), [1, 2, 4, 7])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + int[] a = [ 1, 2, 4, 7, 7, 2, 4, 7, 3, 5]; + + static assert(isForwardRange!(typeof(a.until(7)))); + static assert(isForwardRange!(typeof(until!"a == 2"(a, No.openRight)))); + + assert(equal(a.until(7), [1, 2, 4])); + assert(equal(a.until([7, 2]), [1, 2, 4, 7])); + assert(equal(a.until(7, No.openRight), [1, 2, 4, 7])); + assert(equal(until!"a == 2"(a, No.openRight), [1, 2])); +} + +@system unittest // bugzilla 13171 +{ + import std.algorithm.comparison : equal; + import std.range; + auto a = [1, 2, 3, 4]; + assert(equal(refRange(&a).until(3, No.openRight), [1, 2, 3])); + assert(a == [4]); +} + +@safe unittest // Issue 10460 +{ + import std.algorithm.comparison : equal; + auto a = [1, 2, 3, 4]; + foreach (ref e; a.until(3)) + e = 0; + assert(equal(a, [0, 0, 3, 4])); +} + +@safe unittest // Issue 13124 +{ + import std.algorithm.comparison : among, equal; + auto s = "hello how\nare you"; + assert(equal(s.until!(c => c.among!('\n', '\r')), "hello how")); +} diff --git a/libphobos/src/std/algorithm/setops.d b/libphobos/src/std/algorithm/setops.d new file mode 100644 index 0000000..05a6e7e --- /dev/null +++ b/libphobos/src/std/algorithm/setops.d @@ -0,0 +1,1521 @@ +// Written in the D programming language. +/** +This is a submodule of $(MREF std, algorithm). +It contains generic algorithms that implement set operations. + +The functions $(LREF multiwayMerge), $(LREF multiwayUnion), $(LREF setDifference), +$(LREF setIntersection), $(LREF setSymmetricDifference) expect a range of sorted +ranges as input. + +All algorithms are generalized to accept as input not only sets but also +$(HTTP https://en.wikipedia.org/wiki/Multiset, multisets). Each algorithm +documents behaviour in the presence of duplicated inputs. + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE Cheat Sheet, +$(TR $(TH Function Name) $(TH Description)) +$(T2 cartesianProduct, + Computes Cartesian product of two ranges.) +$(T2 largestPartialIntersection, + Copies out the values that occur most frequently in a range of ranges.) +$(T2 largestPartialIntersectionWeighted, + Copies out the values that occur most frequently (multiplied by + per-value weights) in a range of ranges.) +$(T2 multiwayMerge, + Merges a range of sorted ranges.) +$(T2 multiwayUnion, + Computes the union of a range of sorted ranges.) +$(T2 setDifference, + Lazily computes the set difference of two or more sorted ranges.) +$(T2 setIntersection, + Lazily computes the intersection of two or more sorted ranges.) +$(T2 setSymmetricDifference, + Lazily computes the symmetric set difference of two or more sorted + ranges.) +) + +Copyright: Andrei Alexandrescu 2008-. + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(HTTP erdani.com, Andrei Alexandrescu) + +Source: $(PHOBOSSRC std/algorithm/_setops.d) + +Macros: +T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) + */ +module std.algorithm.setops; + +import std.range.primitives; + +// FIXME +import std.functional; // : unaryFun, binaryFun; +import std.traits; +// FIXME +import std.meta; // : AliasSeq, staticMap, allSatisfy, anySatisfy; + +import std.algorithm.sorting; // : Merge; +import std.typecons : No; + +// cartesianProduct +/** +Lazily computes the Cartesian product of two or more ranges. The product is a +_range of tuples of elements from each respective range. + +The conditions for the two-range case are as follows: + +If both ranges are finite, then one must be (at least) a +$(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) and the +other an $(REF_ALTTEXT input range, isInputRange, std,range,primitives). + +If one _range is infinite and the other finite, then the finite _range must +be a forward _range, and the infinite range can be an input _range. + +If both ranges are infinite, then both must be forward ranges. + +When there are more than two ranges, the above conditions apply to each +adjacent pair of ranges. + +Params: + range1 = The first range + range2 = The second range + ranges = Two or more non-infinite forward ranges + otherRanges = Zero or more non-infinite forward ranges + +Returns: + A forward range of $(REF Tuple, std,typecons) representing elements of the + cartesian product of the given ranges. +*/ +auto cartesianProduct(R1, R2)(R1 range1, R2 range2) +if (!allSatisfy!(isForwardRange, R1, R2) || + anySatisfy!(isInfinite, R1, R2)) +{ + import std.algorithm.iteration : map, joiner; + + static if (isInfinite!R1 && isInfinite!R2) + { + static if (isForwardRange!R1 && isForwardRange!R2) + { + import std.range : zip, repeat, take, chain, sequence; + + // This algorithm traverses the cartesian product by alternately + // covering the right and bottom edges of an increasing square area + // over the infinite table of combinations. This schedule allows us + // to require only forward ranges. + return zip(sequence!"n"(cast(size_t) 0), range1.save, range2.save, + repeat(range1), repeat(range2)) + .map!(function(a) => chain( + zip(repeat(a[1]), take(a[4].save, a[0])), + zip(take(a[3].save, a[0]+1), repeat(a[2])) + ))() + .joiner(); + } + else static assert(0, "cartesianProduct of infinite ranges requires "~ + "forward ranges"); + } + else static if (isInputRange!R1 && isForwardRange!R2 && !isInfinite!R2) + { + import std.range : zip, repeat; + return joiner(map!((ElementType!R1 a) => zip(repeat(a), range2.save)) + (range1)); + } + else static if (isInputRange!R2 && isForwardRange!R1 && !isInfinite!R1) + { + import std.range : zip, repeat; + return joiner(map!((ElementType!R2 a) => zip(range1.save, repeat(a))) + (range2)); + } + else static assert(0, "cartesianProduct involving finite ranges must "~ + "have at least one finite forward range"); +} + +/// +@safe unittest +{ + import std.algorithm.searching : canFind; + import std.range; + import std.typecons : tuple; + + auto N = sequence!"n"(0); // the range of natural numbers + auto N2 = cartesianProduct(N, N); // the range of all pairs of natural numbers + + // Various arbitrary number pairs can be found in the range in finite time. + assert(canFind(N2, tuple(0, 0))); + assert(canFind(N2, tuple(123, 321))); + assert(canFind(N2, tuple(11, 35))); + assert(canFind(N2, tuple(279, 172))); +} + +/// +@safe unittest +{ + import std.algorithm.searching : canFind; + import std.typecons : tuple; + + auto B = [ 1, 2, 3 ]; + auto C = [ 4, 5, 6 ]; + auto BC = cartesianProduct(B, C); + + foreach (n; [[1, 4], [2, 4], [3, 4], [1, 5], [2, 5], [3, 5], [1, 6], + [2, 6], [3, 6]]) + { + assert(canFind(BC, tuple(n[0], n[1]))); + } +} + +@safe unittest +{ + // Test cartesian product of two infinite ranges + import std.algorithm.searching : canFind; + import std.range; + import std.typecons : tuple; + + auto Even = sequence!"2*n"(0); + auto Odd = sequence!"2*n+1"(0); + auto EvenOdd = cartesianProduct(Even, Odd); + + foreach (pair; [[0, 1], [2, 1], [0, 3], [2, 3], [4, 1], [4, 3], [0, 5], + [2, 5], [4, 5], [6, 1], [6, 3], [6, 5]]) + { + assert(canFind(EvenOdd, tuple(pair[0], pair[1]))); + } + + // This should terminate in finite time + assert(canFind(EvenOdd, tuple(124, 73))); + assert(canFind(EvenOdd, tuple(0, 97))); + assert(canFind(EvenOdd, tuple(42, 1))); +} + +@safe unittest +{ + // Test cartesian product of an infinite input range and a finite forward + // range. + import std.algorithm.searching : canFind; + import std.range; + import std.typecons : tuple; + + auto N = sequence!"n"(0); + auto M = [100, 200, 300]; + auto NM = cartesianProduct(N,M); + + foreach (pair; [[0, 100], [0, 200], [0, 300], [1, 100], [1, 200], [1, 300], + [2, 100], [2, 200], [2, 300], [3, 100], [3, 200], + [3, 300]]) + { + assert(canFind(NM, tuple(pair[0], pair[1]))); + } + + // We can't solve the halting problem, so we can only check a finite + // initial segment here. + assert(!canFind(NM.take(100), tuple(100, 0))); + assert(!canFind(NM.take(100), tuple(1, 1))); + assert(!canFind(NM.take(100), tuple(100, 200))); + + auto MN = cartesianProduct(M,N); + foreach (pair; [[100, 0], [200, 0], [300, 0], [100, 1], [200, 1], [300, 1], + [100, 2], [200, 2], [300, 2], [100, 3], [200, 3], + [300, 3]]) + { + assert(canFind(MN, tuple(pair[0], pair[1]))); + } + + // We can't solve the halting problem, so we can only check a finite + // initial segment here. + assert(!canFind(MN.take(100), tuple(0, 100))); + assert(!canFind(MN.take(100), tuple(0, 1))); + assert(!canFind(MN.take(100), tuple(100, 200))); +} + +@safe unittest +{ + import std.algorithm.searching : canFind; + import std.typecons : tuple; + + // Test cartesian product of two finite ranges. + auto X = [1, 2, 3]; + auto Y = [4, 5, 6]; + auto XY = cartesianProduct(X, Y); + auto Expected = [[1, 4], [1, 5], [1, 6], [2, 4], [2, 5], [2, 6], [3, 4], + [3, 5], [3, 6]]; + + // Verify Expected ⊆ XY + foreach (pair; Expected) + { + assert(canFind(XY, tuple(pair[0], pair[1]))); + } + + // Verify XY ⊆ Expected + foreach (pair; XY) + { + assert(canFind(Expected, [pair[0], pair[1]])); + } + + // And therefore, by set comprehension, XY == Expected +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map; + import std.algorithm.searching : canFind; + import std.typecons : tuple; + + import std.range; + auto N = sequence!"n"(0); + + // To force the template to fall to the second case, we wrap N in a struct + // that doesn't allow bidirectional access. + struct FwdRangeWrapper(R) + { + R impl; + + // Input range API + @property auto front() { return impl.front; } + void popFront() { impl.popFront(); } + static if (isInfinite!R) + enum empty = false; + else + @property bool empty() { return impl.empty; } + + // Forward range API + @property auto save() { return typeof(this)(impl.save); } + } + auto fwdWrap(R)(R range) { return FwdRangeWrapper!R(range); } + + // General test: two infinite bidirectional ranges + auto N2 = cartesianProduct(N, N); + + assert(canFind(N2, tuple(0, 0))); + assert(canFind(N2, tuple(123, 321))); + assert(canFind(N2, tuple(11, 35))); + assert(canFind(N2, tuple(279, 172))); + + // Test first case: forward range with bidirectional range + auto fwdN = fwdWrap(N); + auto N2_a = cartesianProduct(fwdN, N); + + assert(canFind(N2_a, tuple(0, 0))); + assert(canFind(N2_a, tuple(123, 321))); + assert(canFind(N2_a, tuple(11, 35))); + assert(canFind(N2_a, tuple(279, 172))); + + // Test second case: bidirectional range with forward range + auto N2_b = cartesianProduct(N, fwdN); + + assert(canFind(N2_b, tuple(0, 0))); + assert(canFind(N2_b, tuple(123, 321))); + assert(canFind(N2_b, tuple(11, 35))); + assert(canFind(N2_b, tuple(279, 172))); + + // Test third case: finite forward range with (infinite) input range + static struct InpRangeWrapper(R) + { + R impl; + + // Input range API + @property auto front() { return impl.front; } + void popFront() { impl.popFront(); } + static if (isInfinite!R) + enum empty = false; + else + @property bool empty() { return impl.empty; } + } + auto inpWrap(R)(R r) { return InpRangeWrapper!R(r); } + + auto inpN = inpWrap(N); + auto B = [ 1, 2, 3 ]; + auto fwdB = fwdWrap(B); + auto BN = cartesianProduct(fwdB, inpN); + + assert(equal(map!"[a[0],a[1]]"(BN.take(10)), [[1, 0], [2, 0], [3, 0], + [1, 1], [2, 1], [3, 1], [1, 2], [2, 2], [3, 2], [1, 3]])); + + // Test fourth case: (infinite) input range with finite forward range + auto NB = cartesianProduct(inpN, fwdB); + + assert(equal(map!"[a[0],a[1]]"(NB.take(10)), [[0, 1], [0, 2], [0, 3], + [1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3], [3, 1]])); + + // General finite range case + auto C = [ 4, 5, 6 ]; + auto BC = cartesianProduct(B, C); + + foreach (n; [[1, 4], [2, 4], [3, 4], [1, 5], [2, 5], [3, 5], [1, 6], + [2, 6], [3, 6]]) + { + assert(canFind(BC, tuple(n[0], n[1]))); + } +} + +// Issue 13091 +pure nothrow @safe @nogc unittest +{ + int[1] a = [1]; + foreach (t; cartesianProduct(a[], a[])) {} +} + +/// ditto +auto cartesianProduct(RR...)(RR ranges) +if (ranges.length >= 2 && + allSatisfy!(isForwardRange, RR) && + !anySatisfy!(isInfinite, RR)) +{ + // This overload uses a much less template-heavy implementation when + // all ranges are finite forward ranges, which is the most common use + // case, so that we don't run out of resources too quickly. + // + // For infinite ranges or non-forward ranges, we fall back to the old + // implementation which expands an exponential number of templates. + import std.typecons : tuple; + + static struct Result + { + RR ranges; + RR current; + bool empty = true; + + this(RR _ranges) + { + ranges = _ranges; + empty = false; + foreach (i, r; ranges) + { + current[i] = r.save; + if (current[i].empty) + empty = true; + } + } + @property auto front() + { + import std.algorithm.internal : algoFormat; + import std.range : iota; + return mixin(algoFormat("tuple(%(current[%d].front%|,%))", + iota(0, current.length))); + } + void popFront() + { + foreach_reverse (i, ref r; current) + { + r.popFront(); + if (!r.empty) break; + + static if (i == 0) + empty = true; + else + r = ranges[i].save; // rollover + } + } + @property Result save() + { + Result copy = this; + foreach (i, r; ranges) + { + copy.ranges[i] = r.save; + copy.current[i] = current[i].save; + } + return copy; + } + } + static assert(isForwardRange!Result); + + return Result(ranges); +} + +@safe unittest +{ + // Issue 10693: cartesian product of empty ranges should be empty. + int[] a, b, c, d, e; + auto cprod = cartesianProduct(a,b,c,d,e); + assert(cprod.empty); + foreach (_; cprod) {} // should not crash + + // Test case where only one of the ranges is empty: the result should still + // be empty. + int[] p=[1], q=[]; + auto cprod2 = cartesianProduct(p,p,p,q,p); + assert(cprod2.empty); + foreach (_; cprod2) {} // should not crash +} + +@safe unittest +{ + // .init value of cartesianProduct should be empty + auto cprod = cartesianProduct([0,0], [1,1], [2,2]); + assert(!cprod.empty); + assert(cprod.init.empty); +} + +@safe unittest +{ + // Issue 13393 + assert(!cartesianProduct([0],[0],[0]).save.empty); +} + +/// ditto +auto cartesianProduct(R1, R2, RR...)(R1 range1, R2 range2, RR otherRanges) +if (!allSatisfy!(isForwardRange, R1, R2, RR) || + anySatisfy!(isInfinite, R1, R2, RR)) +{ + /* We implement the n-ary cartesian product by recursively invoking the + * binary cartesian product. To make the resulting range nicer, we denest + * one level of tuples so that a ternary cartesian product, for example, + * returns 3-element tuples instead of nested 2-element tuples. + */ + import std.algorithm.internal : algoFormat; + import std.algorithm.iteration : map; + import std.range : iota; + + enum string denest = algoFormat("tuple(a[0], %(a[1][%d]%|,%))", + iota(0, otherRanges.length+1)); + return map!denest( + cartesianProduct(range1, cartesianProduct(range2, otherRanges)) + ); +} + +@safe unittest +{ + import std.algorithm.searching : canFind; + import std.range; + import std.typecons : tuple, Tuple; + + auto N = sequence!"n"(0); + auto N3 = cartesianProduct(N, N, N); + + // Check that tuples are properly denested + assert(is(ElementType!(typeof(N3)) == Tuple!(size_t,size_t,size_t))); + + assert(canFind(N3, tuple(0, 27, 7))); + assert(canFind(N3, tuple(50, 23, 71))); + assert(canFind(N3, tuple(9, 3, 0))); +} + +@safe unittest +{ + import std.algorithm.searching : canFind; + import std.range; + import std.typecons : tuple, Tuple; + + auto N = sequence!"n"(0); + auto N4 = cartesianProduct(N, N, N, N); + + // Check that tuples are properly denested + assert(is(ElementType!(typeof(N4)) == Tuple!(size_t,size_t,size_t,size_t))); + + assert(canFind(N4, tuple(1, 2, 3, 4))); + assert(canFind(N4, tuple(4, 3, 2, 1))); + assert(canFind(N4, tuple(10, 31, 7, 12))); +} + +// Issue 9878 +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : tuple; + + auto A = [ 1, 2, 3 ]; + auto B = [ 'a', 'b', 'c' ]; + auto C = [ "x", "y", "z" ]; + auto ABC = cartesianProduct(A, B, C); + + assert(ABC.equal([ + tuple(1, 'a', "x"), tuple(1, 'a', "y"), tuple(1, 'a', "z"), + tuple(1, 'b', "x"), tuple(1, 'b', "y"), tuple(1, 'b', "z"), + tuple(1, 'c', "x"), tuple(1, 'c', "y"), tuple(1, 'c', "z"), + tuple(2, 'a', "x"), tuple(2, 'a', "y"), tuple(2, 'a', "z"), + tuple(2, 'b', "x"), tuple(2, 'b', "y"), tuple(2, 'b', "z"), + tuple(2, 'c', "x"), tuple(2, 'c', "y"), tuple(2, 'c', "z"), + tuple(3, 'a', "x"), tuple(3, 'a', "y"), tuple(3, 'a', "z"), + tuple(3, 'b', "x"), tuple(3, 'b', "y"), tuple(3, 'b', "z"), + tuple(3, 'c', "x"), tuple(3, 'c', "y"), tuple(3, 'c', "z") + ])); +} + +pure @safe nothrow @nogc unittest +{ + import std.range.primitives : isForwardRange; + int[2] A = [1,2]; + auto C = cartesianProduct(A[], A[], A[]); + assert(isForwardRange!(typeof(C))); + + C.popFront(); + auto front1 = C.front; + auto D = C.save; + C.popFront(); + assert(D.front == front1); +} + +// Issue 13935 +@safe unittest +{ + import std.algorithm.iteration : map; + auto seq = [1, 2].map!(x => x); + foreach (pair; cartesianProduct(seq, seq)) {} +} + +// largestPartialIntersection +/** +Given a range of sorted $(REF_ALTTEXT forward ranges, isForwardRange, std,range,primitives) +$(D ror), copies to $(D tgt) the elements that are common to most ranges, along with their number +of occurrences. All ranges in $(D ror) are assumed to be sorted by $(D +less). Only the most frequent $(D tgt.length) elements are returned. + +Params: + less = The predicate the ranges are sorted by. + ror = A range of forward ranges sorted by `less`. + tgt = The target range to copy common elements to. + sorted = Whether the elements copied should be in sorted order. + +The function $(D largestPartialIntersection) is useful for +e.g. searching an $(LINK2 https://en.wikipedia.org/wiki/Inverted_index, +inverted index) for the documents most +likely to contain some terms of interest. The complexity of the search +is $(BIGOH n * log(tgt.length)), where $(D n) is the sum of lengths of +all input ranges. This approach is faster than keeping an associative +array of the occurrences and then selecting its top items, and also +requires less memory ($(D largestPartialIntersection) builds its +result directly in $(D tgt) and requires no extra memory). + +If at least one of the ranges is a multiset, then all occurences +of a duplicate element are taken into account. The result is +equivalent to merging all ranges and picking the most frequent +$(D tgt.length) elements. + +Warning: Because $(D largestPartialIntersection) does not allocate +extra memory, it will leave $(D ror) modified. Namely, $(D +largestPartialIntersection) assumes ownership of $(D ror) and +discretionarily swaps and advances elements of it. If you want $(D +ror) to preserve its contents after the call, you may want to pass a +duplicate to $(D largestPartialIntersection) (and perhaps cache the +duplicate in between calls). + */ +void largestPartialIntersection +(alias less = "a < b", RangeOfRanges, Range) +(RangeOfRanges ror, Range tgt, SortOutput sorted = No.sortOutput) +{ + struct UnitWeights + { + static int opIndex(ElementType!(ElementType!RangeOfRanges)) { return 1; } + } + return largestPartialIntersectionWeighted!less(ror, tgt, UnitWeights(), + sorted); +} + +/// +@system unittest +{ + import std.typecons : tuple, Tuple; + + // Figure which number can be found in most arrays of the set of + // arrays below. + double[][] a = + [ + [ 1, 4, 7, 8 ], + [ 1, 7 ], + [ 1, 7, 8], + [ 4 ], + [ 7 ], + ]; + auto b = new Tuple!(double, uint)[1]; + // it will modify the input range, hence we need to create a duplicate + largestPartialIntersection(a.dup, b); + // First member is the item, second is the occurrence count + assert(b[0] == tuple(7.0, 4u)); + // 7.0 occurs in 4 out of 5 inputs, more than any other number + + // If more of the top-frequent numbers are needed, just create a larger + // tgt range + auto c = new Tuple!(double, uint)[2]; + largestPartialIntersection(a, c); + assert(c[0] == tuple(1.0, 3u)); + // 1.0 occurs in 3 inputs + + // multiset + double[][] x = + [ + [1, 1, 1, 1, 4, 7, 8], + [1, 7], + [1, 7, 8], + [4, 7], + [7] + ]; + auto y = new Tuple!(double, uint)[2]; + largestPartialIntersection(x.dup, y); + // 7.0 occurs 5 times + assert(y[0] == tuple(7.0, 5u)); + // 1.0 occurs 6 times + assert(y[1] == tuple(1.0, 6u)); +} + +import std.algorithm.sorting : SortOutput; // FIXME + +// largestPartialIntersectionWeighted +/** +Similar to $(D largestPartialIntersection), but associates a weight +with each distinct element in the intersection. + +If at least one of the ranges is a multiset, then all occurences +of a duplicate element are taken into account. The result +is equivalent to merging all input ranges and picking the highest +$(D tgt.length), weight-based ranking elements. + +Params: + less = The predicate the ranges are sorted by. + ror = A range of $(REF_ALTTEXT forward ranges, isForwardRange, std,range,primitives) + sorted by `less`. + tgt = The target range to copy common elements to. + weights = An associative array mapping elements to weights. + sorted = Whether the elements copied should be in sorted order. + +*/ +void largestPartialIntersectionWeighted +(alias less = "a < b", RangeOfRanges, Range, WeightsAA) +(RangeOfRanges ror, Range tgt, WeightsAA weights, SortOutput sorted = No.sortOutput) +{ + import std.algorithm.iteration : group; + import std.algorithm.sorting : topNCopy; + + if (tgt.empty) return; + alias InfoType = ElementType!Range; + bool heapComp(InfoType a, InfoType b) + { + return weights[a[0]] * a[1] > weights[b[0]] * b[1]; + } + topNCopy!heapComp(group(multiwayMerge!less(ror)), tgt, sorted); +} + +/// +@system unittest +{ + import std.typecons : tuple, Tuple; + + // Figure which number can be found in most arrays of the set of + // arrays below, with specific per-element weights + double[][] a = + [ + [ 1, 4, 7, 8 ], + [ 1, 7 ], + [ 1, 7, 8], + [ 4 ], + [ 7 ], + ]; + auto b = new Tuple!(double, uint)[1]; + double[double] weights = [ 1:1.2, 4:2.3, 7:1.1, 8:1.1 ]; + largestPartialIntersectionWeighted(a, b, weights); + // First member is the item, second is the occurrence count + assert(b[0] == tuple(4.0, 2u)); + // 4.0 occurs 2 times -> 4.6 (2 * 2.3) + // 7.0 occurs 3 times -> 4.4 (3 * 1.1) + + // multiset + double[][] x = + [ + [ 1, 1, 1, 4, 7, 8 ], + [ 1, 7 ], + [ 1, 7, 8], + [ 4 ], + [ 7 ], + ]; + auto y = new Tuple!(double, uint)[1]; + largestPartialIntersectionWeighted(x, y, weights); + assert(y[0] == tuple(1.0, 5u)); + // 1.0 occurs 5 times -> 1.2 * 5 = 6 +} + +@system unittest +{ + import std.conv : text; + import std.typecons : tuple, Tuple, Yes; + + double[][] a = + [ + [ 1, 4, 7, 8 ], + [ 1, 7 ], + [ 1, 7, 8], + [ 4 ], + [ 7 ], + ]; + auto b = new Tuple!(double, uint)[2]; + largestPartialIntersection(a, b, Yes.sortOutput); + assert(b == [ tuple(7.0, 4u), tuple(1.0, 3u) ][], text(b)); + assert(a[0].empty); +} + +@system unittest +{ + import std.conv : text; + import std.typecons : tuple, Tuple, Yes; + + string[][] a = + [ + [ "1", "4", "7", "8" ], + [ "1", "7" ], + [ "1", "7", "8"], + [ "4" ], + [ "7" ], + ]; + auto b = new Tuple!(string, uint)[2]; + largestPartialIntersection(a, b, Yes.sortOutput); + assert(b == [ tuple("7", 4u), tuple("1", 3u) ][], text(b)); +} + +@system unittest +{ + import std.typecons : tuple, Tuple; + + // Figure which number can be found in most arrays of the set of + // arrays below, with specific per-element weights + double[][] a = + [ + [ 1, 4, 7, 8 ], + [ 1, 7 ], + [ 1, 7, 8], + [ 4 ], + [ 7 ], + ]; + auto b = new Tuple!(double, uint)[1]; + double[double] weights = [ 1:1.2, 4:2.3, 7:1.1, 8:1.1 ]; + largestPartialIntersectionWeighted(a, b, weights); + // First member is the item, second is the occurrence count + assert(b[0] == tuple(4.0, 2u)); +} + +@system unittest +{ + import std.container : Array; + import std.typecons : Tuple; + + alias T = Tuple!(uint, uint); + const Array!T arrayOne = Array!T( [ T(1,2), T(3,4) ] ); + const Array!T arrayTwo = Array!T([ T(1,2), T(3,4) ] ); + + assert(arrayOne == arrayTwo); +} + +// MultiwayMerge +/** +Merges multiple sets. The input sets are passed as a +range of ranges and each is assumed to be sorted by $(D +less). Computation is done lazily, one union element at a time. The +complexity of one $(D popFront) operation is $(BIGOH +log(ror.length)). However, the length of $(D ror) decreases as ranges +in it are exhausted, so the complexity of a full pass through $(D +MultiwayMerge) is dependent on the distribution of the lengths of ranges +contained within $(D ror). If all ranges have the same length $(D n) +(worst case scenario), the complexity of a full pass through $(D +MultiwayMerge) is $(BIGOH n * ror.length * log(ror.length)), i.e., $(D +log(ror.length)) times worse than just spanning all ranges in +turn. The output comes sorted (unstably) by $(D less). + +The length of the resulting range is the sum of all lengths of +the ranges passed as input. This means that all elements (duplicates +included) are transferred to the resulting range. + +For backward compatibility, `multiwayMerge` is available under +the name `nWayUnion` and `MultiwayMerge` under the name of `NWayUnion` . +Future code should use `multiwayMerge` and `MultiwayMerge` as `nWayUnion` +and `NWayUnion` will be deprecated. + +Params: + less = Predicate the given ranges are sorted by. + ror = A range of ranges sorted by `less` to compute the union for. + +Returns: + A range of the union of the ranges in `ror`. + +Warning: Because $(D MultiwayMerge) does not allocate extra memory, it +will leave $(D ror) modified. Namely, $(D MultiwayMerge) assumes ownership +of $(D ror) and discretionarily swaps and advances elements of it. If +you want $(D ror) to preserve its contents after the call, you may +want to pass a duplicate to $(D MultiwayMerge) (and perhaps cache the +duplicate in between calls). + */ +struct MultiwayMerge(alias less, RangeOfRanges) +{ + import std.container : BinaryHeap; + + private alias ElementType = .ElementType!(.ElementType!RangeOfRanges); + private alias comp = binaryFun!less; + private RangeOfRanges _ror; + + /// + static bool compFront(.ElementType!RangeOfRanges a, + .ElementType!RangeOfRanges b) + { + // revert comparison order so we get the smallest elements first + return comp(b.front, a.front); + } + private BinaryHeap!(RangeOfRanges, compFront) _heap; + + /// + this(RangeOfRanges ror) + { + import std.algorithm.mutation : remove, SwapStrategy; + + // Preemptively get rid of all empty ranges in the input + // No need for stability either + _ror = remove!("a.empty", SwapStrategy.unstable)(ror); + //Build the heap across the range + _heap.acquire(_ror); + } + + /// + @property bool empty() { return _ror.empty; } + + /// + @property auto ref front() + { + return _heap.front.front; + } + + /// + void popFront() + { + _heap.removeFront(); + // let's look at the guy just popped + _ror.back.popFront(); + if (_ror.back.empty) + { + _ror.popBack(); + // nothing else to do: the empty range is not in the + // heap and not in _ror + return; + } + // Put the popped range back in the heap + _heap.conditionalInsert(_ror.back) || assert(false); + } +} + +/// Ditto +MultiwayMerge!(less, RangeOfRanges) multiwayMerge +(alias less = "a < b", RangeOfRanges) +(RangeOfRanges ror) +{ + return typeof(return)(ror); +} + +/// +@system unittest +{ + import std.algorithm.comparison : equal; + + double[][] a = + [ + [ 1, 4, 7, 8 ], + [ 1, 7 ], + [ 1, 7, 8], + [ 4 ], + [ 7 ], + ]; + auto witness = [ + 1, 1, 1, 4, 4, 7, 7, 7, 7, 8, 8 + ]; + assert(equal(multiwayMerge(a), witness)); + + double[][] b = + [ + // range with duplicates + [ 1, 1, 4, 7, 8 ], + [ 7 ], + [ 1, 7, 8], + [ 4 ], + [ 7 ], + ]; + // duplicates are propagated to the resulting range + assert(equal(multiwayMerge(b), witness)); +} + +alias nWayUnion = multiwayMerge; +alias NWayUnion = MultiwayMerge; + +/** +Computes the union of multiple ranges. The input ranges are passed +as a range of ranges and each is assumed to be sorted by $(D +less). Computation is done lazily, one union element at a time. +`multiwayUnion(ror)` is functionally equivalent to `multiwayMerge(ror).uniq`. + +"The output of multiwayUnion has no duplicates even when its inputs contain duplicates." + +Params: + less = Predicate the given ranges are sorted by. + ror = A range of ranges sorted by `less` to compute the intersection for. + +Returns: + A range of the union of the ranges in `ror`. + +See also: $(LREF multiwayMerge) + */ +auto multiwayUnion(alias less = "a < b", RangeOfRanges)(RangeOfRanges ror) +{ + import std.algorithm.iteration : uniq; + return ror.multiwayMerge.uniq; +} + +/// +@system unittest +{ + import std.algorithm.comparison : equal; + + // sets + double[][] a = + [ + [ 1, 4, 7, 8 ], + [ 1, 7 ], + [ 1, 7, 8], + [ 4 ], + [ 7 ], + ]; + + auto witness = [1, 4, 7, 8]; + assert(equal(multiwayUnion(a), witness)); + + // multisets + double[][] b = + [ + [ 1, 1, 1, 4, 7, 8 ], + [ 1, 7 ], + [ 1, 7, 7, 8], + [ 4 ], + [ 7 ], + ]; + assert(equal(multiwayUnion(b), witness)); +} + +/** +Lazily computes the difference of $(D r1) and $(D r2). The two ranges +are assumed to be sorted by $(D less). The element types of the two +ranges must have a common type. + + +In the case of multisets, considering that element `a` appears `x` +times in $(D r1) and `y` times and $(D r2), the number of occurences +of `a` in the resulting range is going to be `x-y` if x > y or 0 othwerise. + +Params: + less = Predicate the given ranges are sorted by. + r1 = The first range. + r2 = The range to subtract from `r1`. + +Returns: + A range of the difference of `r1` and `r2`. + +See_also: $(LREF setSymmetricDifference) + */ +struct SetDifference(alias less = "a < b", R1, R2) +if (isInputRange!(R1) && isInputRange!(R2)) +{ +private: + R1 r1; + R2 r2; + alias comp = binaryFun!(less); + + void adjustPosition() + { + while (!r1.empty) + { + if (r2.empty || comp(r1.front, r2.front)) break; + if (comp(r2.front, r1.front)) + { + r2.popFront(); + } + else + { + // both are equal + r1.popFront(); + r2.popFront(); + } + } + } + +public: + /// + this(R1 r1, R2 r2) + { + this.r1 = r1; + this.r2 = r2; + // position to the first element + adjustPosition(); + } + + /// + void popFront() + { + r1.popFront(); + adjustPosition(); + } + + /// + @property auto ref front() + { + assert(!empty); + return r1.front; + } + + static if (isForwardRange!R1 && isForwardRange!R2) + { + /// + @property typeof(this) save() + { + auto ret = this; + ret.r1 = r1.save; + ret.r2 = r2.save; + return ret; + } + } + + /// + @property bool empty() { return r1.empty; } +} + +/// Ditto +SetDifference!(less, R1, R2) setDifference(alias less = "a < b", R1, R2) +(R1 r1, R2 r2) +{ + return typeof(return)(r1, r2); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range.primitives : isForwardRange; + + //sets + int[] a = [ 1, 2, 4, 5, 7, 9 ]; + int[] b = [ 0, 1, 2, 4, 7, 8 ]; + assert(equal(setDifference(a, b), [5, 9])); + static assert(isForwardRange!(typeof(setDifference(a, b)))); + + // multisets + int[] x = [1, 1, 1, 2, 3]; + int[] y = [1, 1, 2, 4, 5]; + auto r = setDifference(x, y); + assert(equal(r, [1, 3])); + assert(setDifference(r, x).empty); +} + +@safe unittest // Issue 10460 +{ + import std.algorithm.comparison : equal; + + int[] a = [1, 2, 3, 4, 5]; + int[] b = [2, 4]; + foreach (ref e; setDifference(a, b)) + e = 0; + assert(equal(a, [0, 2, 0, 4, 0])); +} + +/** +Lazily computes the intersection of two or more input ranges $(D +ranges). The ranges are assumed to be sorted by $(D less). The element +types of the ranges must have a common type. + +In the case of multisets, the range with the minimum number of +occurences of a given element, propagates the number of +occurences of this element to the resulting range. + +Params: + less = Predicate the given ranges are sorted by. + ranges = The ranges to compute the intersection for. + +Returns: + A range containing the intersection of the given ranges. + */ +struct SetIntersection(alias less = "a < b", Rs...) +if (Rs.length >= 2 && allSatisfy!(isInputRange, Rs) && + !is(CommonType!(staticMap!(ElementType, Rs)) == void)) +{ +private: + Rs _input; + alias comp = binaryFun!less; + alias ElementType = CommonType!(staticMap!(.ElementType, Rs)); + + // Positions to the first elements that are all equal + void adjustPosition() + { + if (empty) return; + + size_t done = Rs.length; + static if (Rs.length > 1) while (true) + { + foreach (i, ref r; _input) + { + alias next = _input[(i + 1) % Rs.length]; + + if (comp(next.front, r.front)) + { + do + { + next.popFront(); + if (next.empty) return; + } while (comp(next.front, r.front)); + done = Rs.length; + } + if (--done == 0) return; + } + } + } + +public: + /// + this(Rs input) + { + this._input = input; + // position to the first element + adjustPosition(); + } + + /// + @property bool empty() + { + foreach (ref r; _input) + { + if (r.empty) return true; + } + return false; + } + + /// + void popFront() + { + assert(!empty); + static if (Rs.length > 1) foreach (i, ref r; _input) + { + alias next = _input[(i + 1) % Rs.length]; + assert(!comp(r.front, next.front)); + } + + foreach (ref r; _input) + { + r.popFront(); + } + adjustPosition(); + } + + /// + @property ElementType front() + { + assert(!empty); + return _input[0].front; + } + + static if (allSatisfy!(isForwardRange, Rs)) + { + /// + @property SetIntersection save() + { + auto ret = this; + foreach (i, ref r; _input) + { + ret._input[i] = r.save; + } + return ret; + } + } +} + +/// Ditto +SetIntersection!(less, Rs) setIntersection(alias less = "a < b", Rs...)(Rs ranges) +if (Rs.length >= 2 && allSatisfy!(isInputRange, Rs) && + !is(CommonType!(staticMap!(ElementType, Rs)) == void)) +{ + return typeof(return)(ranges); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + + // sets + int[] a = [ 1, 2, 4, 5, 7, 9 ]; + int[] b = [ 0, 1, 2, 4, 7, 8 ]; + int[] c = [ 0, 1, 4, 5, 7, 8 ]; + assert(equal(setIntersection(a, a), a)); + assert(equal(setIntersection(a, b), [1, 2, 4, 7])); + assert(equal(setIntersection(a, b, c), [1, 4, 7])); + + // multisets + int[] d = [ 1, 1, 2, 2, 7, 7 ]; + int[] e = [ 1, 1, 1, 7]; + assert(equal(setIntersection(a, d), [1, 2, 7])); + assert(equal(setIntersection(d, e), [1, 1, 7])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filter; + + int[] a = [ 1, 2, 4, 5, 7, 9 ]; + int[] b = [ 0, 1, 2, 4, 7, 8 ]; + int[] c = [ 0, 1, 4, 5, 7, 8 ]; + int[] d = [ 1, 3, 4 ]; + int[] e = [ 4, 5 ]; + + assert(equal(setIntersection(a, a), a)); + assert(equal(setIntersection(a, a, a), a)); + assert(equal(setIntersection(a, b), [1, 2, 4, 7])); + assert(equal(setIntersection(a, b, c), [1, 4, 7])); + assert(equal(setIntersection(a, b, c, d), [1, 4])); + assert(equal(setIntersection(a, b, c, d, e), [4])); + + auto inpA = a.filter!(_ => true), inpB = b.filter!(_ => true); + auto inpC = c.filter!(_ => true), inpD = d.filter!(_ => true); + assert(equal(setIntersection(inpA, inpB, inpC, inpD), [1, 4])); + + assert(equal(setIntersection(a, b, b, a), [1, 2, 4, 7])); + assert(equal(setIntersection(a, c, b), [1, 4, 7])); + assert(equal(setIntersection(b, a, c), [1, 4, 7])); + assert(equal(setIntersection(b, c, a), [1, 4, 7])); + assert(equal(setIntersection(c, a, b), [1, 4, 7])); + assert(equal(setIntersection(c, b, a), [1, 4, 7])); +} + +/** +Lazily computes the symmetric difference of $(D r1) and $(D r2), +i.e. the elements that are present in exactly one of $(D r1) and $(D +r2). The two ranges are assumed to be sorted by $(D less), and the +output is also sorted by $(D less). The element types of the two +ranges must have a common type. + +If both ranges are sets (without duplicated elements), the resulting +range is going to be a set. If at least one of the ranges is a multiset, +the number of occurences of an element `x` in the resulting range is `abs(a-b)` +where `a` is the number of occurences of `x` in $(D r1), `b` is the number of +occurences of `x` in $(D r2), and `abs` is the absolute value. + +If both arguments are ranges of L-values of the same type then +$(D SetSymmetricDifference) will also be a range of L-values of +that type. + +Params: + less = Predicate the given ranges are sorted by. + r1 = The first range. + r2 = The second range. + +Returns: + A range of the symmetric difference between `r1` and `r2`. + +See_also: $(LREF setDifference) + */ +struct SetSymmetricDifference(alias less = "a < b", R1, R2) +if (isInputRange!(R1) && isInputRange!(R2)) +{ +private: + R1 r1; + R2 r2; + //bool usingR2; + alias comp = binaryFun!(less); + + void adjustPosition() + { + while (!r1.empty && !r2.empty) + { + if (comp(r1.front, r2.front) || comp(r2.front, r1.front)) + { + break; + } + // equal, pop both + r1.popFront(); + r2.popFront(); + } + } + +public: + /// + this(R1 r1, R2 r2) + { + this.r1 = r1; + this.r2 = r2; + // position to the first element + adjustPosition(); + } + + /// + void popFront() + { + assert(!empty); + if (r1.empty) r2.popFront(); + else if (r2.empty) r1.popFront(); + else + { + // neither is empty + if (comp(r1.front, r2.front)) + { + r1.popFront(); + } + else + { + assert(comp(r2.front, r1.front)); + r2.popFront(); + } + } + adjustPosition(); + } + + /// + @property auto ref front() + { + assert(!empty); + immutable chooseR1 = r2.empty || !r1.empty && comp(r1.front, r2.front); + assert(chooseR1 || r1.empty || comp(r2.front, r1.front)); + return chooseR1 ? r1.front : r2.front; + } + + static if (isForwardRange!R1 && isForwardRange!R2) + { + /// + @property typeof(this) save() + { + auto ret = this; + ret.r1 = r1.save; + ret.r2 = r2.save; + return ret; + } + } + + /// + ref auto opSlice() { return this; } + + /// + @property bool empty() { return r1.empty && r2.empty; } +} + +/// Ditto +SetSymmetricDifference!(less, R1, R2) +setSymmetricDifference(alias less = "a < b", R1, R2) +(R1 r1, R2 r2) +{ + return typeof(return)(r1, r2); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range.primitives : isForwardRange; + + // sets + int[] a = [ 1, 2, 4, 5, 7, 9 ]; + int[] b = [ 0, 1, 2, 4, 7, 8 ]; + assert(equal(setSymmetricDifference(a, b), [0, 5, 8, 9][])); + static assert(isForwardRange!(typeof(setSymmetricDifference(a, b)))); + + //mutisets + int[] c = [1, 1, 1, 1, 2, 2, 2, 4, 5, 6]; + int[] d = [1, 1, 2, 2, 2, 2, 4, 7, 9]; + assert(equal(setSymmetricDifference(c, d), setSymmetricDifference(d, c))); + assert(equal(setSymmetricDifference(c, d), [1, 1, 2, 5, 6, 7, 9])); +} + +@safe unittest // Issue 10460 +{ + import std.algorithm.comparison : equal; + + int[] a = [1, 2]; + double[] b = [2.0, 3.0]; + int[] c = [2, 3]; + + alias R1 = typeof(setSymmetricDifference(a, b)); + static assert(is(ElementType!R1 == double)); + static assert(!hasLvalueElements!R1); + + alias R2 = typeof(setSymmetricDifference(a, c)); + static assert(is(ElementType!R2 == int)); + static assert(hasLvalueElements!R2); + + assert(equal(setSymmetricDifference(a, b), [1.0, 3.0])); + assert(equal(setSymmetricDifference(a, c), [1, 3])); +} + +/++ +TODO: once SetUnion got deprecated we can provide the usual definition +(= merge + filter after uniqs) +See: https://github.com/dlang/phobos/pull/4249 +/** +Lazily computes the union of two or more ranges $(D rs). The ranges +are assumed to be sorted by $(D less). Elements in the output are +unique. The element types of all ranges must have a common type. + +Params: + less = Predicate the given ranges are sorted by. + rs = The ranges to compute the union for. + +Returns: + A range containing the unique union of the given ranges. + +See_Also: + $(REF merge, std,algorithm,sorting) + */ +auto setUnion(alias less = "a < b", Rs...) +(Rs rs) +{ + import std.algorithm.iteration : uniq; + import std.algorithm.sorting : merge; + return merge!(less, Rs)(rs).uniq; +} + +/// +@safe pure nothrow unittest + /// +{ + import std.algorithm.comparison : equal; + + int[] a = [1, 3, 5]; + int[] b = [2, 3, 4]; + assert(a.setUnion(b).equal([1, 2, 3, 4, 5])); +} + +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + + int[] a = [ 1, 2, 4, 5, 7, 9 ]; + int[] b = [ 0, 1, 2, 4, 7, 8 ]; + double[] c = [ 10.5 ]; + + assert(equal(setUnion(a, b), [0, 1, 2, 4, 5, 7, 8, 9][])); + assert(equal(setUnion(a, c, b), + [0, 1, 2, 4, 5, 7, 8, 9, 10.5][])); +} + +@safe unittest +{ + // save + import std.range : dropOne; + int[] a = [0, 1, 2]; + int[] b = [0, 3]; + auto arr = a.setUnion(b); + assert(arr.front == 0); + assert(arr.save.dropOne.front == 1); + assert(arr.front == 0); +} + +@nogc @safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + + static immutable a = [1, 3, 5]; + static immutable b = [2, 4]; + static immutable r = [1, 2, 3, 4, 5]; + assert(a.setUnion(b).equal(r)); +} + +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange; + import std.range : iota; + + auto dummyResult1 = [1, 1.5, 2, 3, 4, 5, 5.5, 6, 7, 8, 9, 10]; + auto dummyResult2 = iota(1, 11); + foreach (DummyType; AllDummyRanges) + { + DummyType d; + assert(d.setUnion([1, 1.5, 5.5]).equal(dummyResult1)); + assert(d.setUnion(d).equal(dummyResult2)); + } +} +++/ diff --git a/libphobos/src/std/algorithm/sorting.d b/libphobos/src/std/algorithm/sorting.d new file mode 100644 index 0000000..2400bca --- /dev/null +++ b/libphobos/src/std/algorithm/sorting.d @@ -0,0 +1,4468 @@ +// Written in the D programming language. +/** +This is a submodule of $(MREF std, algorithm). +It contains generic _sorting algorithms. + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE Cheat Sheet, +$(TR $(TH Function Name) $(TH Description)) +$(T2 completeSort, + If $(D a = [10, 20, 30]) and $(D b = [40, 6, 15]), then + $(D completeSort(a, b)) leaves $(D a = [6, 10, 15]) and $(D b = [20, + 30, 40]). + The range $(D a) must be sorted prior to the call, and as a result the + combination $(D $(REF chain, std,range)(a, b)) is sorted.) +$(T2 isPartitioned, + $(D isPartitioned!"a < 0"([-1, -2, 1, 0, 2])) returns $(D true) because + the predicate is $(D true) for a portion of the range and $(D false) + afterwards.) +$(T2 isSorted, + $(D isSorted([1, 1, 2, 3])) returns $(D true).) +$(T2 isStrictlyMonotonic, + $(D isStrictlyMonotonic([1, 1, 2, 3])) returns $(D false).) +$(T2 ordered, + $(D ordered(1, 1, 2, 3)) returns $(D true).) +$(T2 strictlyOrdered, + $(D strictlyOrdered(1, 1, 2, 3)) returns $(D false).) +$(T2 makeIndex, + Creates a separate index for a range.) +$(T2 merge, + Lazily merges two or more sorted ranges.) +$(T2 multiSort, + Sorts by multiple keys.) +$(T2 nextEvenPermutation, + Computes the next lexicographically greater even permutation of a range + in-place.) +$(T2 nextPermutation, + Computes the next lexicographically greater permutation of a range + in-place.) +$(T2 partialSort, + If $(D a = [5, 4, 3, 2, 1]), then $(D partialSort(a, 3)) leaves + $(D a[0 .. 3] = [1, 2, 3]). + The other elements of $(D a) are left in an unspecified order.) +$(T2 partition, + Partitions a range according to a unary predicate.) +$(T2 partition3, + Partitions a range according to a binary predicate in three parts (less + than, equal, greater than the given pivot). Pivot is not given as an + index, but instead as an element independent from the range's content.) +$(T2 pivotPartition, + Partitions a range according to a binary predicate in two parts: less + than or equal, and greater than or equal to the given pivot, passed as + an index in the range.) +$(T2 schwartzSort, + Sorts with the help of the $(LINK2 https://en.wikipedia.org/wiki/Schwartzian_transform, Schwartzian transform).) +$(T2 sort, + Sorts.) +$(T2 topN, + Separates the top elements in a range.) +$(T2 topNCopy, + Copies out the top elements of a range.) +$(T2 topNIndex, + Builds an index of the top elements of a range.) +) + +Copyright: Andrei Alexandrescu 2008-. + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(HTTP erdani.com, Andrei Alexandrescu) + +Source: $(PHOBOSSRC std/algorithm/_sorting.d) + +Macros: +T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) + */ +module std.algorithm.sorting; + +import std.algorithm.mutation : SwapStrategy; +import std.functional; // : unaryFun, binaryFun; +import std.range.primitives; +import std.typecons : Flag; +// FIXME +import std.meta; // : allSatisfy; +import std.range; // : SortedRange; +import std.traits; + +/** +Specifies whether the output of certain algorithm is desired in sorted +format. + +If set to $(D SortOutput.no), the output should not be sorted. + +Otherwise if set to $(D SortOutput.yes), the output should be sorted. + */ +alias SortOutput = Flag!"sortOutput"; + +// completeSort +/** +Sorts the random-access range $(D chain(lhs, rhs)) according to +predicate $(D less). The left-hand side of the range $(D lhs) is +assumed to be already sorted; $(D rhs) is assumed to be unsorted. The +exact strategy chosen depends on the relative sizes of $(D lhs) and +$(D rhs). Performs $(BIGOH lhs.length + rhs.length * log(rhs.length)) +(best case) to $(BIGOH (lhs.length + rhs.length) * log(lhs.length + +rhs.length)) (worst-case) evaluations of $(D swap). + +Params: + less = The predicate to sort by. + ss = The swapping strategy to use. + lhs = The sorted, left-hand side of the random access range to be sorted. + rhs = The unsorted, right-hand side of the random access range to be + sorted. +*/ +void completeSort(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, + RandomAccessRange1, RandomAccessRange2)(SortedRange!(RandomAccessRange1, less) lhs, RandomAccessRange2 rhs) +if (hasLength!(RandomAccessRange2) && hasSlicing!(RandomAccessRange2)) +{ + import std.algorithm.mutation : bringToFront; + import std.range : chain, assumeSorted; + // Probably this algorithm can be optimized by using in-place + // merge + auto lhsOriginal = lhs.release(); + foreach (i; 0 .. rhs.length) + { + auto sortedSoFar = chain(lhsOriginal, rhs[0 .. i]); + auto ub = assumeSorted!less(sortedSoFar).upperBound(rhs[i]); + if (!ub.length) continue; + bringToFront(ub.release(), rhs[i .. i + 1]); + } +} + +/// +@safe unittest +{ + import std.range : assumeSorted; + int[] a = [ 1, 2, 3 ]; + int[] b = [ 4, 0, 6, 5 ]; + completeSort(assumeSorted(a), b); + assert(a == [ 0, 1, 2 ]); + assert(b == [ 3, 4, 5, 6 ]); +} + +// isSorted +/** +Checks whether a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) +is sorted according to the comparison operation $(D less). Performs $(BIGOH r.length) +evaluations of $(D less). + +Unlike `isSorted`, `isStrictlyMonotonic` does not allow for equal values, +i.e. values for which both `less(a, b)` and `less(b, a)` are false. + +With either function, the predicate must be a strict ordering just like with +`isSorted`. For example, using `"a <= b"` instead of `"a < b"` is +incorrect and will cause failed assertions. + +Params: + less = Predicate the range should be sorted by. + r = Forward range to check for sortedness. + +Returns: + `true` if the range is sorted, false otherwise. `isSorted` allows + duplicates, `isStrictlyMonotonic` not. +*/ +bool isSorted(alias less = "a < b", Range)(Range r) +if (isForwardRange!(Range)) +{ + if (r.empty) return true; + + static if (isRandomAccessRange!Range && hasLength!Range) + { + immutable limit = r.length - 1; + foreach (i; 0 .. limit) + { + if (!binaryFun!less(r[i + 1], r[i])) continue; + assert( + !binaryFun!less(r[i], r[i + 1]), + "Predicate for isSorted is not antisymmetric. Both" ~ + " pred(a, b) and pred(b, a) are true for certain values."); + return false; + } + } + else + { + auto ahead = r.save; + ahead.popFront(); + size_t i; + + for (; !ahead.empty; ahead.popFront(), r.popFront(), ++i) + { + if (!binaryFun!less(ahead.front, r.front)) continue; + // Check for antisymmetric predicate + assert( + !binaryFun!less(r.front, ahead.front), + "Predicate for isSorted is not antisymmetric. Both" ~ + " pred(a, b) and pred(b, a) are true for certain values."); + return false; + } + } + return true; +} + +/// +@safe unittest +{ + assert([1, 1, 2].isSorted); + // strictly monotonic doesn't allow duplicates + assert(![1, 1, 2].isStrictlyMonotonic); + + int[] arr = [4, 3, 2, 1]; + assert(!isSorted(arr)); + assert(!isStrictlyMonotonic(arr)); + + assert(isSorted!"a > b"(arr)); + assert(isStrictlyMonotonic!"a > b"(arr)); + + sort(arr); + assert(isSorted(arr)); + assert(isStrictlyMonotonic(arr)); +} + +@safe unittest +{ + import std.conv : to; + + // Issue 9457 + auto x = "abcd"; + assert(isSorted(x)); + auto y = "acbd"; + assert(!isSorted(y)); + + int[] a = [1, 2, 3]; + assert(isSorted(a)); + int[] b = [1, 3, 2]; + assert(!isSorted(b)); + + // ignores duplicates + int[] c = [1, 1, 2]; + assert(isSorted(c)); + + dchar[] ds = "コーヒーが好きです"d.dup; + sort(ds); + string s = to!string(ds); + assert(isSorted(ds)); // random-access + assert(isSorted(s)); // bidirectional +} + +@nogc @safe nothrow pure unittest +{ + static immutable a = [1, 2, 3]; + assert(a.isSorted); +} + +/// ditto +bool isStrictlyMonotonic(alias less = "a < b", Range)(Range r) +if (isForwardRange!Range) +{ + import std.algorithm.searching : findAdjacent; + return findAdjacent!((a,b) => !binaryFun!less(a,b))(r).empty; +} + +@safe unittest +{ + import std.conv : to; + + assert("abcd".isStrictlyMonotonic); + assert(!"aacd".isStrictlyMonotonic); + assert(!"acb".isStrictlyMonotonic); + + assert([1, 2, 3].isStrictlyMonotonic); + assert(![1, 3, 2].isStrictlyMonotonic); + assert(![1, 1, 2].isStrictlyMonotonic); + + // ー occurs twice -> can't be strict + dchar[] ds = "コーヒーが好きです"d.dup; + sort(ds); + string s = to!string(ds); + assert(!isStrictlyMonotonic(ds)); // random-access + assert(!isStrictlyMonotonic(s)); // bidirectional + + dchar[] ds2 = "コーヒが好きです"d.dup; + sort(ds2); + string s2 = to!string(ds2); + assert(isStrictlyMonotonic(ds2)); // random-access + assert(isStrictlyMonotonic(s2)); // bidirectional +} + +@nogc @safe nothrow pure unittest +{ + static immutable a = [1, 2, 3]; + assert(a.isStrictlyMonotonic); +} + +/** +Like $(D isSorted), returns $(D true) if the given $(D values) are ordered +according to the comparison operation $(D less). Unlike $(D isSorted), takes values +directly instead of structured in a range. + +$(D ordered) allows repeated values, e.g. $(D ordered(1, 1, 2)) is $(D true). To verify +that the values are ordered strictly monotonically, use $(D strictlyOrdered); +$(D strictlyOrdered(1, 1, 2)) is $(D false). + +With either function, the predicate must be a strict ordering. For example, +using $(D "a <= b") instead of $(D "a < b") is incorrect and will cause failed +assertions. + +Params: + values = The tested value + less = The comparison predicate + +Returns: + $(D true) if the values are ordered; $(D ordered) allows for duplicates, + $(D strictlyOrdered) does not. +*/ + +bool ordered(alias less = "a < b", T...)(T values) +if ((T.length == 2 && is(typeof(binaryFun!less(values[1], values[0])) : bool)) + || + (T.length > 2 && is(typeof(ordered!less(values[0 .. 1 + $ / 2]))) + && is(typeof(ordered!less(values[$ / 2 .. $])))) + ) +{ + foreach (i, _; T[0 .. $ - 1]) + { + if (binaryFun!less(values[i + 1], values[i])) + { + assert(!binaryFun!less(values[i], values[i + 1]), + __FUNCTION__ ~ ": incorrect non-strict predicate."); + return false; + } + } + return true; +} + +/// ditto +bool strictlyOrdered(alias less = "a < b", T...)(T values) +if (is(typeof(ordered!less(values)))) +{ + foreach (i, _; T[0 .. $ - 1]) + { + if (!binaryFun!less(values[i], values[i + 1])) + { + return false; + } + assert(!binaryFun!less(values[i + 1], values[i]), + __FUNCTION__ ~ ": incorrect non-strict predicate."); + } + return true; +} + +/// +@safe unittest +{ + assert(ordered(42, 42, 43)); + assert(!strictlyOrdered(43, 42, 45)); + assert(ordered(42, 42, 43)); + assert(!strictlyOrdered(42, 42, 43)); + assert(!ordered(43, 42, 45)); + // Ordered lexicographically + assert(ordered("Jane", "Jim", "Joe")); + assert(strictlyOrdered("Jane", "Jim", "Joe")); + // Incidentally also ordered by length decreasing + assert(ordered!((a, b) => a.length > b.length)("Jane", "Jim", "Joe")); + // ... but not strictly so: "Jim" and "Joe" have the same length + assert(!strictlyOrdered!((a, b) => a.length > b.length)("Jane", "Jim", "Joe")); +} + +// partition +/** +Partitions a range in two using the given $(D predicate). +Specifically, reorders the range $(D r = [left, right$(RPAREN)) using $(D swap) +such that all elements $(D i) for which $(D predicate(i)) is $(D true) come +before all elements $(D j) for which $(D predicate(j)) returns $(D false). + +Performs $(BIGOH r.length) (if unstable or semistable) or $(BIGOH +r.length * log(r.length)) (if stable) evaluations of $(D less) and $(D +swap). The unstable version computes the minimum possible evaluations +of $(D swap) (roughly half of those performed by the semistable +version). + +Params: + predicate = The predicate to partition by. + ss = The swapping strategy to employ. + r = The random-access range to partition. + +Returns: + +The right part of $(D r) after partitioning. + +If $(D ss == SwapStrategy.stable), $(D partition) preserves the relative +ordering of all elements $(D a), $(D b) in $(D r) for which $(D predicate(a) == +predicate(b)). If $(D ss == SwapStrategy.semistable), $(D partition) preserves +the relative ordering of all elements $(D a), $(D b) in the left part of $(D r) +for which $(D predicate(a) == predicate(b)). + +See_Also: + STL's $(HTTP sgi.com/tech/stl/_partition.html, _partition)$(BR) + STL's $(HTTP sgi.com/tech/stl/stable_partition.html, stable_partition) +*/ +Range partition(alias predicate, SwapStrategy ss, Range)(Range r) +if (ss == SwapStrategy.stable && isRandomAccessRange!(Range) && hasLength!Range && hasSlicing!Range) +{ + import std.algorithm.mutation : bringToFront; + + alias pred = unaryFun!(predicate); + if (r.empty) return r; + + if (r.length == 1) + { + if (pred(r.front)) r.popFront(); + return r; + } + const middle = r.length / 2; + alias recurse = .partition!(pred, ss, Range); + auto lower = recurse(r[0 .. middle]); + auto upper = recurse(r[middle .. r.length]); + bringToFront(lower, r[middle .. r.length - upper.length]); + return r[r.length - lower.length - upper.length .. r.length]; +} + +///ditto +Range partition(alias predicate, SwapStrategy ss = SwapStrategy.unstable, Range)(Range r) +if (ss != SwapStrategy.stable && isInputRange!Range && hasSwappableElements!Range) +{ + import std.algorithm.mutation : swap; + alias pred = unaryFun!(predicate); + + static if (ss == SwapStrategy.semistable) + { + if (r.empty) return r; + + for (; !r.empty; r.popFront()) + { + // skip the initial portion of "correct" elements + if (pred(r.front)) continue; + // hit the first "bad" element + auto result = r; + for (r.popFront(); !r.empty; r.popFront()) + { + if (!pred(r.front)) continue; + swap(result.front, r.front); + result.popFront(); + } + return result; + } + + return r; + } + else + { + // Inspired from www.stepanovpapers.com/PAM3-partition_notes.pdf, + // section "Bidirectional Partition Algorithm (Hoare)" + static if (isDynamicArray!Range) + { + import std.algorithm.mutation : swapAt; + // For dynamic arrays prefer index-based manipulation + if (!r.length) return r; + size_t lo = 0, hi = r.length - 1; + for (;;) + { + for (;;) + { + if (lo > hi) return r[lo .. r.length]; + if (!pred(r[lo])) break; + ++lo; + } + // found the left bound + assert(lo <= hi); + for (;;) + { + if (lo == hi) return r[lo .. r.length]; + if (pred(r[hi])) break; + --hi; + } + // found the right bound, swap & make progress + r.swapAt(lo++, hi--); + } + } + else + { + import std.algorithm.mutation : swap; + auto result = r; + for (;;) + { + for (;;) + { + if (r.empty) return result; + if (!pred(r.front)) break; + r.popFront(); + result.popFront(); + } + // found the left bound + assert(!r.empty); + for (;;) + { + if (pred(r.back)) break; + r.popBack(); + if (r.empty) return result; + } + // found the right bound, swap & make progress + static if (is(typeof(swap(r.front, r.back)))) + { + swap(r.front, r.back); + } + else + { + auto t1 = r.moveFront(), t2 = r.moveBack(); + r.front = t2; + r.back = t1; + } + r.popFront(); + result.popFront(); + r.popBack(); + } + } + } +} + +/// +@safe unittest +{ + import std.algorithm.mutation : SwapStrategy; + import std.algorithm.searching : count, find; + import std.conv : text; + import std.range.primitives : empty; + + auto Arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + auto arr = Arr.dup; + static bool even(int a) { return (a & 1) == 0; } + // Partition arr such that even numbers come first + auto r = partition!(even)(arr); + // Now arr is separated in evens and odds. + // Numbers may have become shuffled due to instability + assert(r == arr[5 .. $]); + assert(count!(even)(arr[0 .. 5]) == 5); + assert(find!(even)(r).empty); + + // Can also specify the predicate as a string. + // Use 'a' as the predicate argument name + arr[] = Arr[]; + r = partition!(q{(a & 1) == 0})(arr); + assert(r == arr[5 .. $]); + + // Now for a stable partition: + arr[] = Arr[]; + r = partition!(q{(a & 1) == 0}, SwapStrategy.stable)(arr); + // Now arr is [2 4 6 8 10 1 3 5 7 9], and r points to 1 + assert(arr == [2, 4, 6, 8, 10, 1, 3, 5, 7, 9] && r == arr[5 .. $]); + + // In case the predicate needs to hold its own state, use a delegate: + arr[] = Arr[]; + int x = 3; + // Put stuff greater than 3 on the left + bool fun(int a) { return a > x; } + r = partition!(fun, SwapStrategy.semistable)(arr); + // Now arr is [4 5 6 7 8 9 10 2 3 1] and r points to 2 + assert(arr == [4, 5, 6, 7, 8, 9, 10, 2, 3, 1] && r == arr[7 .. $]); +} + +@safe unittest +{ + import std.algorithm.internal : rndstuff; + static bool even(int a) { return (a & 1) == 0; } + + // test with random data + auto a = rndstuff!int(); + partition!even(a); + assert(isPartitioned!even(a)); + auto b = rndstuff!string(); + partition!`a.length < 5`(b); + assert(isPartitioned!`a.length < 5`(b)); +} + +// pivotPartition +/** + +Partitions `r` around `pivot` using comparison function `less`, algorithm akin +to $(LINK2 https://en.wikipedia.org/wiki/Quicksort#Hoare_partition_scheme, +Hoare partition). Specifically, permutes elements of `r` and returns +an index $(D k < r.length) such that: + +$(UL + +$(LI `r[pivot]` is swapped to `r[k]`) + +$(LI All elements `e` in subrange $(D r[0 .. k]) satisfy $(D !less(r[k], e)) +(i.e. `r[k]` is greater than or equal to each element to its left according to +predicate `less`)) + +$(LI All elements `e` in subrange $(D r[0 .. k]) satisfy $(D !less(e, +r[k])) (i.e. `r[k]` is less than or equal to each element to its right +according to predicate `less`))) + +If `r` contains equivalent elements, multiple permutations of `r` satisfy these +constraints. In such cases, `pivotPartition` attempts to distribute equivalent +elements fairly to the left and right of `k` such that `k` stays close to $(D +r.length / 2). + +Params: +less = The predicate used for comparison, modeled as a + $(LINK2 https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings, + strict weak ordering) (irreflexive, antisymmetric, transitive, and implying a transitive + equivalence) +r = The range being partitioned +pivot = The index of the pivot for partitioning, must be less than `r.length` or +`0` is `r.length` is `0` + +Returns: +The new position of the pivot + +See_Also: +$(HTTP jgrcs.info/index.php/jgrcs/article/view/142, Engineering of a Quicksort +Partitioning Algorithm), D. Abhyankar, Journal of Global Research in Computer +Science, February 2011. $(HTTPS youtube.com/watch?v=AxnotgLql0k, ACCU 2016 +Keynote), Andrei Alexandrescu. +*/ +size_t pivotPartition(alias less = "a < b", Range) +(Range r, size_t pivot) +if (isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range) +{ + assert(pivot < r.length || r.length == 0 && pivot == 0); + if (r.length <= 1) return 0; + import std.algorithm.mutation : swapAt, move; + alias lt = binaryFun!less; + + // Pivot at the front + r.swapAt(pivot, 0); + + // Fork implementation depending on nothrow copy, assignment, and + // comparison. If all of these are nothrow, use the specialized + // implementation discussed at https://youtube.com/watch?v=AxnotgLql0k. + static if (is(typeof( + () nothrow { auto x = r.front; x = r.front; return lt(x, x); } + ))) + { + auto p = r[0]; + // Plant the pivot in the end as well as a sentinel + size_t lo = 0, hi = r.length - 1; + auto save = move(r[hi]); + r[hi] = p; // Vacancy is in r[$ - 1] now + // Start process + for (;;) + { + // Loop invariant + version (unittest) + { + import std.algorithm.searching : all; + assert(r[0 .. lo].all!(x => !lt(p, x))); + assert(r[hi + 1 .. r.length].all!(x => !lt(x, p))); + } + do ++lo; while (lt(r[lo], p)); + r[hi] = r[lo]; + // Vacancy is now in r[lo] + do --hi; while (lt(p, r[hi])); + if (lo >= hi) break; + r[lo] = r[hi]; + // Vacancy is not in r[hi] + } + // Fixup + assert(lo - hi <= 2); + assert(!lt(p, r[hi])); + if (lo == hi + 2) + { + assert(!lt(r[hi + 1], p)); + r[lo] = r[hi + 1]; + --lo; + } + r[lo] = save; + if (lt(p, save)) --lo; + assert(!lt(p, r[lo])); + } + else + { + size_t lo = 1, hi = r.length - 1; + loop: for (;; lo++, hi--) + { + for (;; ++lo) + { + if (lo > hi) break loop; + if (!lt(r[lo], r[0])) break; + } + // found the left bound: r[lo] >= r[0] + assert(lo <= hi); + for (;; --hi) + { + if (lo >= hi) break loop; + if (!lt(r[0], r[hi])) break; + } + // found the right bound: r[hi] <= r[0], swap & make progress + assert(!lt(r[lo], r[hi])); + r.swapAt(lo, hi); + } + --lo; + } + r.swapAt(lo, 0); + return lo; +} + +/// +@safe nothrow unittest +{ + int[] a = [5, 3, 2, 6, 4, 1, 3, 7]; + size_t pivot = pivotPartition(a, a.length / 2); + import std.algorithm.searching : all; + assert(a[0 .. pivot].all!(x => x <= a[pivot])); + assert(a[pivot .. $].all!(x => x >= a[pivot])); +} + +@safe unittest +{ + void test(alias less)() + { + int[] a; + size_t pivot; + + a = [-9, -4, -2, -2, 9]; + pivot = pivotPartition!less(a, a.length / 2); + import std.algorithm.searching : all; + assert(a[0 .. pivot].all!(x => x <= a[pivot])); + assert(a[pivot .. $].all!(x => x >= a[pivot])); + + a = [9, 2, 8, -5, 5, 4, -8, -4, 9]; + pivot = pivotPartition!less(a, a.length / 2); + assert(a[0 .. pivot].all!(x => x <= a[pivot])); + assert(a[pivot .. $].all!(x => x >= a[pivot])); + + a = [ 42 ]; + pivot = pivotPartition!less(a, a.length / 2); + assert(pivot == 0); + assert(a == [ 42 ]); + + a = [ 43, 42 ]; + pivot = pivotPartition!less(a, 0); + assert(pivot == 1); + assert(a == [ 42, 43 ]); + + a = [ 43, 42 ]; + pivot = pivotPartition!less(a, 1); + assert(pivot == 0); + assert(a == [ 42, 43 ]); + + a = [ 42, 42 ]; + pivot = pivotPartition!less(a, 0); + assert(pivot == 0 || pivot == 1); + assert(a == [ 42, 42 ]); + pivot = pivotPartition!less(a, 1); + assert(pivot == 0 || pivot == 1); + assert(a == [ 42, 42 ]); + + import std.algorithm.iteration : map; + import std.random; + import std.stdio; + auto s = unpredictableSeed; + auto g = Random(s); + a = iota(0, uniform(1, 1000, g)) + .map!(_ => uniform(-1000, 1000, g)) + .array; + scope(failure) writeln("RNG seed was ", s); + pivot = pivotPartition!less(a, a.length / 2); + assert(a[0 .. pivot].all!(x => x <= a[pivot])); + assert(a[pivot .. $].all!(x => x >= a[pivot])); + } + test!"a < b"; + static bool myLess(int a, int b) + { + static bool bogus; + if (bogus) throw new Exception(""); // just to make it no-nothrow + return a < b; + } + test!myLess; +} + +/** +Params: + pred = The predicate that the range should be partitioned by. + r = The range to check. +Returns: $(D true) if $(D r) is partitioned according to predicate $(D pred). + */ +bool isPartitioned(alias pred, Range)(Range r) +if (isForwardRange!(Range)) +{ + for (; !r.empty; r.popFront()) + { + if (unaryFun!(pred)(r.front)) continue; + for (r.popFront(); !r.empty; r.popFront()) + { + if (unaryFun!(pred)(r.front)) return false; + } + break; + } + return true; +} + +/// +@safe unittest +{ + int[] r = [ 1, 3, 5, 7, 8, 2, 4, ]; + assert(isPartitioned!"a & 1"(r)); +} + +// partition3 +/** +Rearranges elements in $(D r) in three adjacent ranges and returns +them. The first and leftmost range only contains elements in $(D r) +less than $(D pivot). The second and middle range only contains +elements in $(D r) that are equal to $(D pivot). Finally, the third +and rightmost range only contains elements in $(D r) that are greater +than $(D pivot). The less-than test is defined by the binary function +$(D less). + +Params: + less = The predicate to use for the rearrangement. + ss = The swapping strategy to use. + r = The random-access range to rearrange. + pivot = The pivot element. + +Returns: + A $(REF Tuple, std,typecons) of the three resulting ranges. These ranges are + slices of the original range. + +BUGS: stable $(D partition3) has not been implemented yet. + */ +auto partition3(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, Range, E) +(Range r, E pivot) +if (ss == SwapStrategy.unstable && isRandomAccessRange!Range + && hasSwappableElements!Range && hasLength!Range && hasSlicing!Range + && is(typeof(binaryFun!less(r.front, pivot)) == bool) + && is(typeof(binaryFun!less(pivot, r.front)) == bool) + && is(typeof(binaryFun!less(r.front, r.front)) == bool)) +{ + // The algorithm is described in "Engineering a sort function" by + // Jon Bentley et al, pp 1257. + + import std.algorithm.comparison : min; + import std.algorithm.mutation : swap, swapAt, swapRanges; + import std.typecons : tuple; + + alias lessFun = binaryFun!less; + size_t i, j, k = r.length, l = k; + + bigloop: + for (;;) + { + for (;; ++j) + { + if (j == k) break bigloop; + assert(j < r.length); + if (lessFun(r[j], pivot)) continue; + if (lessFun(pivot, r[j])) break; + r.swapAt(i++, j); + } + assert(j < k); + for (;;) + { + assert(k > 0); + if (!lessFun(pivot, r[--k])) + { + if (lessFun(r[k], pivot)) break; + r.swapAt(k, --l); + } + if (j == k) break bigloop; + } + // Here we know r[j] > pivot && r[k] < pivot + r.swapAt(j++, k); + } + + // Swap the equal ranges from the extremes into the middle + auto strictlyLess = j - i, strictlyGreater = l - k; + auto swapLen = min(i, strictlyLess); + swapRanges(r[0 .. swapLen], r[j - swapLen .. j]); + swapLen = min(r.length - l, strictlyGreater); + swapRanges(r[k .. k + swapLen], r[r.length - swapLen .. r.length]); + return tuple(r[0 .. strictlyLess], + r[strictlyLess .. r.length - strictlyGreater], + r[r.length - strictlyGreater .. r.length]); +} + +/// +@safe unittest +{ + auto a = [ 8, 3, 4, 1, 4, 7, 4 ]; + auto pieces = partition3(a, 4); + assert(pieces[0] == [ 1, 3 ]); + assert(pieces[1] == [ 4, 4, 4 ]); + assert(pieces[2] == [ 8, 7 ]); +} + +@safe unittest +{ + import std.random : Random, uniform, unpredictableSeed; + + immutable uint[] seeds = [3923355730, 1927035882, unpredictableSeed]; + foreach (s; seeds) + { + auto r = Random(s); + auto a = new int[](uniform(0, 100, r)); + foreach (ref e; a) + { + e = uniform(0, 50, r); + } + auto pieces = partition3(a, 25); + assert(pieces[0].length + pieces[1].length + pieces[2].length == a.length); + foreach (e; pieces[0]) + { + assert(e < 25); + } + foreach (e; pieces[1]) + { + assert(e == 25); + } + foreach (e; pieces[2]) + { + assert(e > 25); + } + } +} + +// makeIndex +/** +Computes an index for $(D r) based on the comparison $(D less). The +index is a sorted array of pointers or indices into the original +range. This technique is similar to sorting, but it is more flexible +because (1) it allows "sorting" of immutable collections, (2) allows +binary search even if the original collection does not offer random +access, (3) allows multiple indexes, each on a different predicate, +and (4) may be faster when dealing with large objects. However, using +an index may also be slower under certain circumstances due to the +extra indirection, and is always larger than a sorting-based solution +because it needs space for the index in addition to the original +collection. The complexity is the same as $(D sort)'s. + +The first overload of $(D makeIndex) writes to a range containing +pointers, and the second writes to a range containing offsets. The +first overload requires $(D Range) to be a +$(REF_ALTTEXT forward range, isForwardRange, std,range,primitives), and the +latter requires it to be a random-access range. + +$(D makeIndex) overwrites its second argument with the result, but +never reallocates it. + +Params: + less = The comparison to use. + ss = The swapping strategy. + r = The range to index. + index = The resulting index. + +Returns: The pointer-based version returns a $(D SortedRange) wrapper +over index, of type $(D SortedRange!(RangeIndex, (a, b) => +binaryFun!less(*a, *b))) thus reflecting the ordering of the +index. The index-based version returns $(D void) because the ordering +relation involves not only $(D index) but also $(D r). + +Throws: If the second argument's length is less than that of the range +indexed, an exception is thrown. +*/ +SortedRange!(RangeIndex, (a, b) => binaryFun!less(*a, *b)) +makeIndex( + alias less = "a < b", + SwapStrategy ss = SwapStrategy.unstable, + Range, + RangeIndex) +(Range r, RangeIndex index) +if (isForwardRange!(Range) && isRandomAccessRange!(RangeIndex) + && is(ElementType!(RangeIndex) : ElementType!(Range)*)) +{ + import std.algorithm.internal : addressOf; + import std.exception : enforce; + + // assume collection already ordered + size_t i; + for (; !r.empty; r.popFront(), ++i) + index[i] = addressOf(r.front); + enforce(index.length == i); + // sort the index + sort!((a, b) => binaryFun!less(*a, *b), ss)(index); + return typeof(return)(index); +} + +/// Ditto +void makeIndex( + alias less = "a < b", + SwapStrategy ss = SwapStrategy.unstable, + Range, + RangeIndex) +(Range r, RangeIndex index) +if (isRandomAccessRange!Range && !isInfinite!Range && + isRandomAccessRange!RangeIndex && !isInfinite!RangeIndex && + isIntegral!(ElementType!RangeIndex)) +{ + import std.conv : to; + import std.exception : enforce; + + alias IndexType = Unqual!(ElementType!RangeIndex); + enforce(r.length == index.length, + "r and index must be same length for makeIndex."); + static if (IndexType.sizeof < size_t.sizeof) + { + enforce(r.length <= size_t(1) + IndexType.max, "Cannot create an index with " ~ + "element type " ~ IndexType.stringof ~ " with length " ~ + to!string(r.length) ~ "."); + } + + // Use size_t as loop index to avoid overflow on ++i, + // e.g. when squeezing 256 elements into a ubyte index. + foreach (size_t i; 0 .. r.length) + index[i] = cast(IndexType) i; + + // sort the index + sort!((a, b) => binaryFun!less(r[cast(size_t) a], r[cast(size_t) b]), ss) + (index); +} + +/// +@system unittest +{ + immutable(int[]) arr = [ 2, 3, 1, 5, 0 ]; + // index using pointers + auto index1 = new immutable(int)*[arr.length]; + makeIndex!("a < b")(arr, index1); + assert(isSorted!("*a < *b")(index1)); + // index using offsets + auto index2 = new size_t[arr.length]; + makeIndex!("a < b")(arr, index2); + assert(isSorted! + ((size_t a, size_t b){ return arr[a] < arr[b];}) + (index2)); +} + +@system unittest +{ + immutable(int)[] arr = [ 2, 3, 1, 5, 0 ]; + // index using pointers + auto index1 = new immutable(int)*[arr.length]; + alias ImmRange = typeof(arr); + alias ImmIndex = typeof(index1); + static assert(isForwardRange!(ImmRange)); + static assert(isRandomAccessRange!(ImmIndex)); + static assert(!isIntegral!(ElementType!(ImmIndex))); + static assert(is(ElementType!(ImmIndex) : ElementType!(ImmRange)*)); + makeIndex!("a < b")(arr, index1); + assert(isSorted!("*a < *b")(index1)); + + // index using offsets + auto index2 = new long[arr.length]; + makeIndex(arr, index2); + assert(isSorted! + ((long a, long b){ + return arr[cast(size_t) a] < arr[cast(size_t) b]; + })(index2)); + + // index strings using offsets + string[] arr1 = ["I", "have", "no", "chocolate"]; + auto index3 = new byte[arr1.length]; + makeIndex(arr1, index3); + assert(isSorted! + ((byte a, byte b){ return arr1[a] < arr1[b];}) + (index3)); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + ubyte[256] index = void; + iota(256).makeIndex(index[]); + assert(index[].equal(iota(256))); + byte[128] sindex = void; + iota(128).makeIndex(sindex[]); + assert(sindex[].equal(iota(128))); + + auto index2 = new uint[10]; + 10.iota.makeIndex(index2); + assert(index2.equal(10.iota)); +} + +struct Merge(alias less = "a < b", Rs...) +if (Rs.length >= 2 && + allSatisfy!(isInputRange, Rs) && + !is(CommonType!(staticMap!(ElementType, Rs)) == void)) +{ + public Rs source; + private size_t _lastFrontIndex = size_t.max; + static if (isBidirectional) + { + private size_t _lastBackIndex = size_t.max; // `size_t.max` means uninitialized, + } + + import std.functional : binaryFun; + import std.traits : isCopyable; + import std.typetuple : anySatisfy; + + private alias comp = binaryFun!less; + private alias ElementType = CommonType!(staticMap!(.ElementType, Rs)); + private enum isBidirectional = allSatisfy!(isBidirectionalRange, staticMap!(Unqual, Rs)); + + debug private enum canCheckSortedness = isCopyable!ElementType && !hasAliasing!ElementType; + + this(Rs source) + { + this.source = source; + this._lastFrontIndex = frontIndex; + } + + static if (anySatisfy!(isInfinite, Rs)) + { + enum bool empty = false; // propagate infiniteness + } + else + { + @property bool empty() + { + return _lastFrontIndex == size_t.max; + } + } + + @property auto ref front() + { + final switch (_lastFrontIndex) + { + foreach (i, _; Rs) + { + case i: + assert(!source[i].empty); + return source[i].front; + } + } + } + + private size_t frontIndex() + { + size_t bestIndex = size_t.max; // indicate undefined + Unqual!ElementType bestElement; + foreach (i, _; Rs) + { + if (source[i].empty) continue; + if (bestIndex == size_t.max || // either this is the first or + comp(source[i].front, bestElement)) + { + bestIndex = i; + bestElement = source[i].front; + } + } + return bestIndex; + } + + void popFront() + { + sw: final switch (_lastFrontIndex) + { + foreach (i, R; Rs) + { + case i: + debug static if (canCheckSortedness) + { + ElementType previousFront = source[i].front(); + } + source[i].popFront(); + debug static if (canCheckSortedness) + { + if (!source[i].empty) + { + assert(previousFront == source[i].front || + comp(previousFront, source[i].front), + "Input " ~ i.stringof ~ " is unsorted"); // @nogc + } + } + break sw; + } + } + _lastFrontIndex = frontIndex; + } + + static if (isBidirectional) + { + @property auto ref back() + { + if (_lastBackIndex == size_t.max) + { + this._lastBackIndex = backIndex; // lazy initialization + } + final switch (_lastBackIndex) + { + foreach (i, _; Rs) + { + case i: + assert(!source[i].empty); + return source[i].back; + } + } + } + + private size_t backIndex() + { + size_t bestIndex = size_t.max; // indicate undefined + Unqual!ElementType bestElement; + foreach (i, _; Rs) + { + if (source[i].empty) continue; + if (bestIndex == size_t.max || // either this is the first or + comp(bestElement, source[i].back)) + { + bestIndex = i; + bestElement = source[i].back; + } + } + return bestIndex; + } + + void popBack() + { + if (_lastBackIndex == size_t.max) + { + this._lastBackIndex = backIndex; // lazy initialization + } + sw: final switch (_lastBackIndex) + { + foreach (i, R; Rs) + { + case i: + debug static if (canCheckSortedness) + { + ElementType previousBack = source[i].back(); + } + source[i].popBack(); + debug static if (canCheckSortedness) + { + if (!source[i].empty) + { + assert(previousBack == source[i].back || + comp(source[i].back, previousBack), + "Input " ~ i.stringof ~ " is unsorted"); // @nogc + } + } + break sw; + } + } + _lastBackIndex = backIndex; + if (_lastBackIndex == size_t.max) // if emptied + { + _lastFrontIndex = size_t.max; + } + } + } + + static if (allSatisfy!(isForwardRange, staticMap!(Unqual, Rs))) + { + @property auto save() + { + auto result = this; + foreach (i, _; Rs) + { + result.source[i] = result.source[i].save; + } + return result; + } + } + + static if (allSatisfy!(hasLength, Rs)) + { + @property size_t length() + { + size_t result; + foreach (i, _; Rs) + { + result += source[i].length; + } + return result; + } + + alias opDollar = length; + } +} + +/** + Merge multiple sorted ranges `rs` with less-than predicate function `pred` + into one single sorted output range containing the sorted union of the + elements of inputs. Duplicates are not eliminated, meaning that the total + number of elements in the output is the sum of all elements in the ranges + passed to it; the `length` member is offered if all inputs also have + `length`. The element types of all the inputs must have a common type + `CommonType`. + +Params: + less = Predicate the given ranges are sorted by. + rs = The ranges to compute the union for. + +Returns: + A range containing the union of the given ranges. + +Details: + +All of its inputs are assumed to be sorted. This can mean that inputs are + instances of $(REF SortedRange, std,range). Use the result of $(REF sort, + std,algorithm,sorting), or $(REF assumeSorted, std,range) to merge ranges + known to be sorted (show in the example below). Note that there is currently + no way of ensuring that two or more instances of $(REF SortedRange, + std,range) are sorted using a specific comparison function `pred`. Therefore + no checking is done here to assure that all inputs `rs` are instances of + $(REF SortedRange, std,range). + + This algorithm is lazy, doing work progressively as elements are pulled off + the result. + + Time complexity is proportional to the sum of element counts over all inputs. + + If all inputs have the same element type and offer it by `ref`, output + becomes a range with mutable `front` (and `back` where appropriate) that + reflects in the original inputs. + + If any of the inputs `rs` is infinite so is the result (`empty` being always + `false`). +*/ +Merge!(less, Rs) merge(alias less = "a < b", Rs...)(Rs rs) +if (Rs.length >= 2 && + allSatisfy!(isInputRange, Rs) && + !is(CommonType!(staticMap!(ElementType, Rs)) == void)) +{ + return typeof(return)(rs); +} + +/// +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.range : retro; + + int[] a = [1, 3, 5]; + int[] b = [2, 3, 4]; + + assert(a.merge(b).equal([1, 2, 3, 3, 4, 5])); + assert(a.merge(b).retro.equal([5, 4, 3, 3, 2, 1])); +} + +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + + int[] a = [ 1, 2, 4, 5, 7, 9 ]; + int[] b = [ 0, 1, 2, 4, 7, 8 ]; + double[] c = [ 10.5 ]; + + assert(merge(a, b).length == a.length + b.length); + assert(equal(merge(a, b), [0, 1, 1, 2, 2, 4, 4, 5, 7, 7, 8, 9][])); + assert(equal(merge(a, c, b), + [0, 1, 1, 2, 2, 4, 4, 5, 7, 7, 8, 9, 10.5][])); + auto u = merge(a, b); + u.front--; + assert(equal(u, [-1, 1, 1, 2, 2, 4, 4, 5, 7, 7, 8, 9][])); +} + +@safe pure nothrow unittest +{ + // save + import std.range : dropOne; + int[] a = [1, 2]; + int[] b = [0, 3]; + auto arr = a.merge(b); + assert(arr.front == 0); + assert(arr.save.dropOne.front == 1); + assert(arr.front == 0); +} + +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange; + + auto dummyResult1 = [1, 1, 1.5, 2, 3, 4, 5, 5.5, 6, 7, 8, 9, 10]; + auto dummyResult2 = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, + 6, 6, 7, 7, 8, 8, 9, 9, 10, 10]; + foreach (DummyType; AllDummyRanges) + { + DummyType d; + assert(d.merge([1, 1.5, 5.5]).equal(dummyResult1)); + assert(d.merge(d).equal(dummyResult2)); + } +} + +@nogc @safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + + static immutable a = [1, 3, 5]; + static immutable b = [2, 3, 4]; + static immutable r = [1, 2, 3, 3, 4, 5]; + assert(a.merge(b).equal(r)); +} + +/// test bi-directional access and common type +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.range : retro; + import std.traits : CommonType; + + alias S = short; + alias I = int; + alias D = double; + + S[] a = [1, 2, 3]; + I[] b = [50, 60]; + D[] c = [10, 20, 30, 40]; + + auto m = merge(a, b, c); + + static assert(is(typeof(m.front) == CommonType!(S, I, D))); + + assert(equal(m, [1, 2, 3, 10, 20, 30, 40, 50, 60])); + assert(equal(m.retro, [60, 50, 40, 30, 20, 10, 3, 2, 1])); + + m.popFront(); + assert(equal(m, [2, 3, 10, 20, 30, 40, 50, 60])); + m.popBack(); + assert(equal(m, [2, 3, 10, 20, 30, 40, 50])); + m.popFront(); + assert(equal(m, [3, 10, 20, 30, 40, 50])); + m.popBack(); + assert(equal(m, [3, 10, 20, 30, 40])); + m.popFront(); + assert(equal(m, [10, 20, 30, 40])); + m.popBack(); + assert(equal(m, [10, 20, 30])); + m.popFront(); + assert(equal(m, [20, 30])); + m.popBack(); + assert(equal(m, [20])); + m.popFront(); + assert(m.empty); +} + +private template validPredicates(E, less...) +{ + static if (less.length == 0) + enum validPredicates = true; + else static if (less.length == 1 && is(typeof(less[0]) == SwapStrategy)) + enum validPredicates = true; + else + enum validPredicates = + is(typeof((E a, E b){ bool r = binaryFun!(less[0])(a, b); })) + && validPredicates!(E, less[1 .. $]); +} + +/** +$(D auto multiSort(Range)(Range r) + if (validPredicates!(ElementType!Range, less));) + +Sorts a range by multiple keys. The call $(D multiSort!("a.id < b.id", +"a.date > b.date")(r)) sorts the range $(D r) by $(D id) ascending, +and sorts elements that have the same $(D id) by $(D date) +descending. Such a call is equivalent to $(D sort!"a.id != b.id ? a.id +< b.id : a.date > b.date"(r)), but $(D multiSort) is faster because it +does fewer comparisons (in addition to being more convenient). + +Returns: + The initial range wrapped as a $(D SortedRange) with its predicates + converted to an equivalent single predicate. + */ +template multiSort(less...) //if (less.length > 1) +{ + auto multiSort(Range)(Range r) + if (validPredicates!(ElementType!Range, less)) + { + import std.meta : AliasSeq; + import std.range : assumeSorted; + static if (is(typeof(less[$ - 1]) == SwapStrategy)) + { + enum ss = less[$ - 1]; + alias funs = less[0 .. $ - 1]; + } + else + { + enum ss = SwapStrategy.unstable; + alias funs = less; + } + + static if (funs.length == 0) + static assert(false, "No sorting predicate provided for multiSort"); + else + static if (funs.length == 1) + return sort!(funs[0], ss, Range)(r); + else + { + multiSortImpl!(Range, ss, funs)(r); + return assumeSorted!(multiSortPredFun!(Range, funs))(r); + } + } +} + +private bool multiSortPredFun(Range, funs...)(ElementType!Range a, ElementType!Range b) +{ + foreach (f; funs) + { + alias lessFun = binaryFun!(f); + if (lessFun(a, b)) return true; + if (lessFun(b, a)) return false; + } + return false; +} + +private void multiSortImpl(Range, SwapStrategy ss, funs...)(Range r) +{ + alias lessFun = binaryFun!(funs[0]); + + static if (funs.length > 1) + { + while (r.length > 1) + { + auto p = getPivot!lessFun(r); + auto t = partition3!(funs[0], ss)(r, r[p]); + if (t[0].length <= t[2].length) + { + multiSortImpl!(Range, ss, funs)(t[0]); + multiSortImpl!(Range, ss, funs[1 .. $])(t[1]); + r = t[2]; + } + else + { + multiSortImpl!(Range, ss, funs[1 .. $])(t[1]); + multiSortImpl!(Range, ss, funs)(t[2]); + r = t[0]; + } + } + } + else + { + sort!(lessFun, ss)(r); + } +} + +/// +@safe unittest +{ + import std.algorithm.mutation : SwapStrategy; + static struct Point { int x, y; } + auto pts1 = [ Point(0, 0), Point(5, 5), Point(0, 1), Point(0, 2) ]; + auto pts2 = [ Point(0, 0), Point(0, 1), Point(0, 2), Point(5, 5) ]; + multiSort!("a.x < b.x", "a.y < b.y", SwapStrategy.unstable)(pts1); + assert(pts1 == pts2); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range; + + static struct Point { int x, y; } + auto pts1 = [ Point(5, 6), Point(1, 0), Point(5, 7), Point(1, 1), Point(1, 2), Point(0, 1) ]; + auto pts2 = [ Point(0, 1), Point(1, 0), Point(1, 1), Point(1, 2), Point(5, 6), Point(5, 7) ]; + static assert(validPredicates!(Point, "a.x < b.x", "a.y < b.y")); + multiSort!("a.x < b.x", "a.y < b.y", SwapStrategy.unstable)(pts1); + assert(pts1 == pts2); + + auto pts3 = indexed(pts1, iota(pts1.length)); + assert(pts3.multiSort!("a.x < b.x", "a.y < b.y", SwapStrategy.unstable).release.equal(pts2)); + + auto pts4 = iota(10).array; + assert(pts4.multiSort!("a > b").release.equal(iota(10).retro)); +} + +@safe unittest //issue 9160 (L-value only comparators) +{ + static struct A + { + int x; + int y; + } + + static bool byX(const ref A lhs, const ref A rhs) + { + return lhs.x < rhs.x; + } + + static bool byY(const ref A lhs, const ref A rhs) + { + return lhs.y < rhs.y; + } + + auto points = [ A(4, 1), A(2, 4)]; + multiSort!(byX, byY)(points); + assert(points[0] == A(2, 4)); + assert(points[1] == A(4, 1)); +} + +@safe unittest // issue 16179 (cannot access frame of function) +{ + auto arr = [[1, 2], [2, 0], [1, 0], [1, 1]]; + int c = 3; + + arr.multiSort!( + (a, b) => a[0] < b[0], + (a, b) => c*a[1] < c*b[1] + ); + assert(arr == [[1, 0], [1, 1], [1, 2], [2, 0]]); +} + +@safe unittest //Issue 16413 - @system comparison function +{ + bool lt(int a, int b) { return a < b; } static @system + auto a = [2, 1]; + a.multiSort!(lt, lt); + assert(a == [1, 2]); +} + +private size_t getPivot(alias less, Range)(Range r) +{ + auto mid = r.length / 2; + if (r.length < 512) + { + if (r.length >= 32) + medianOf!less(r, size_t(0), mid, r.length - 1); + return mid; + } + + // The plan here is to take the median of five by taking five elements in + // the array, segregate around their median, and return the position of the + // third. We choose first, mid, last, and two more in between those. + + auto quarter = r.length / 4; + medianOf!less(r, + size_t(0), mid - quarter, mid, mid + quarter, r.length - 1); + return mid; +} + +/* +Sorting routine that is optimized for short ranges. Note: uses insertion sort +going downward. Benchmarked a similar routine that goes upward, for some reason +it's slower. +*/ +private void shortSort(alias less, Range)(Range r) +{ + import std.algorithm.mutation : swapAt; + alias pred = binaryFun!(less); + + switch (r.length) + { + case 0: case 1: + return; + case 2: + if (pred(r[1], r[0])) r.swapAt(0, 1); + return; + case 3: + if (pred(r[2], r[0])) + { + if (pred(r[0], r[1])) + { + r.swapAt(0, 1); + r.swapAt(0, 2); + } + else + { + r.swapAt(0, 2); + if (pred(r[1], r[0])) r.swapAt(0, 1); + } + } + else + { + if (pred(r[1], r[0])) + { + r.swapAt(0, 1); + } + else + { + if (pred(r[2], r[1])) r.swapAt(1, 2); + } + } + return; + case 4: + if (pred(r[1], r[0])) r.swapAt(0, 1); + if (pred(r[3], r[2])) r.swapAt(2, 3); + if (pred(r[2], r[0])) r.swapAt(0, 2); + if (pred(r[3], r[1])) r.swapAt(1, 3); + if (pred(r[2], r[1])) r.swapAt(1, 2); + return; + default: + sort5!pred(r[r.length - 5 .. r.length]); + if (r.length == 5) return; + break; + } + + assert(r.length >= 6); + /* The last 5 elements of the range are sorted. Proceed with expanding the + sorted portion downward. */ + immutable maxJ = r.length - 2; + for (size_t i = r.length - 6; ; --i) + { + static if (is(typeof(() nothrow + { + auto t = r[0]; if (pred(t, r[0])) r[0] = r[0]; + }))) // Can we afford to temporarily invalidate the array? + { + size_t j = i + 1; + auto temp = r[i]; + if (pred(r[j], temp)) + { + do + { + r[j - 1] = r[j]; + ++j; + } + while (j < r.length && pred(r[j], temp)); + r[j - 1] = temp; + } + } + else + { + size_t j = i; + while (pred(r[j + 1], r[j])) + { + r.swapAt(j, j + 1); + if (j == maxJ) break; + ++j; + } + } + if (i == 0) break; + } +} + +@safe unittest +{ + import std.random : Random, uniform; + + auto rnd = Random(1); + auto a = new int[uniform(100, 200, rnd)]; + foreach (ref e; a) + { + e = uniform(-100, 100, rnd); + } + + shortSort!(binaryFun!("a < b"), int[])(a); + assert(isSorted(a)); +} + +/* +Sorts the first 5 elements exactly of range r. +*/ +private void sort5(alias lt, Range)(Range r) +{ + assert(r.length >= 5); + + import std.algorithm.mutation : swapAt; + + // 1. Sort first two pairs + if (lt(r[1], r[0])) r.swapAt(0, 1); + if (lt(r[3], r[2])) r.swapAt(2, 3); + + // 2. Arrange first two pairs by the largest element + if (lt(r[3], r[1])) + { + r.swapAt(0, 2); + r.swapAt(1, 3); + } + assert(!lt(r[1], r[0]) && !lt(r[3], r[1]) && !lt(r[3], r[2])); + + // 3. Insert 4 into [0, 1, 3] + if (lt(r[4], r[1])) + { + r.swapAt(3, 4); + r.swapAt(1, 3); + if (lt(r[1], r[0])) + { + r.swapAt(0, 1); + } + } + else if (lt(r[4], r[3])) + { + r.swapAt(3, 4); + } + assert(!lt(r[1], r[0]) && !lt(r[3], r[1]) && !lt(r[4], r[3])); + + // 4. Insert 2 into [0, 1, 3, 4] (note: we already know the last is greater) + assert(!lt(r[4], r[2])); + if (lt(r[2], r[1])) + { + r.swapAt(1, 2); + if (lt(r[1], r[0])) + { + r.swapAt(0, 1); + } + } + else if (lt(r[3], r[2])) + { + r.swapAt(2, 3); + } + // 7 comparisons, 0-9 swaps +} + +@safe unittest +{ + import std.algorithm.iteration : permutations; + import std.algorithm.mutation : copy; + + int[5] buf; + foreach (per; iota(5).permutations) + { + per.copy(buf[]); + sort5!((a, b) => a < b)(buf[]); + assert(buf[].isSorted); + } +} + +// sort +/** +Sorts a random-access range according to the predicate $(D less). Performs +$(BIGOH r.length * log(r.length)) evaluations of $(D less). If `less` involves +expensive computations on the _sort key, it may be worthwhile to use +$(LREF schwartzSort) instead. + +Stable sorting requires $(D hasAssignableElements!Range) to be true. + +$(D sort) returns a $(REF SortedRange, std,range) over the original range, +allowing functions that can take advantage of sorted data to know that the +range is sorted and adjust accordingly. The $(REF SortedRange, std,range) is a +wrapper around the original range, so both it and the original range are sorted. +Other functions can't know that the original range has been sorted, but +they $(I can) know that $(REF SortedRange, std,range) has been sorted. + +Preconditions: + +The predicate is expected to satisfy certain rules in order for $(D sort) to +behave as expected - otherwise, the program may fail on certain inputs (but not +others) when not compiled in release mode, due to the cursory $(D assumeSorted) +check. Specifically, $(D sort) expects $(D less(a,b) && less(b,c)) to imply +$(D less(a,c)) (transitivity), and, conversely, $(D !less(a,b) && !less(b,c)) to +imply $(D !less(a,c)). Note that the default predicate ($(D "a < b")) does not +always satisfy these conditions for floating point types, because the expression +will always be $(D false) when either $(D a) or $(D b) is NaN. +Use $(REF cmp, std,math) instead. + +Params: + less = The predicate to sort by. + ss = The swapping strategy to use. + r = The range to sort. + +Returns: The initial range wrapped as a $(D SortedRange) with the predicate +$(D binaryFun!less). + +Algorithms: $(HTTP en.wikipedia.org/wiki/Introsort, Introsort) is used for unstable sorting and +$(HTTP en.wikipedia.org/wiki/Timsort, Timsort) is used for stable sorting. +Each algorithm has benefits beyond stability. Introsort is generally faster but +Timsort may achieve greater speeds on data with low entropy or if predicate calls +are expensive. Introsort performs no allocations whereas Timsort will perform one +or more allocations per call. Both algorithms have $(BIGOH n log n) worst-case +time complexity. + +See_Also: + $(REF assumeSorted, std,range)$(BR) + $(REF SortedRange, std,range)$(BR) + $(REF SwapStrategy, std,algorithm,mutation)$(BR) + $(REF binaryFun, std,functional) +*/ +SortedRange!(Range, less) +sort(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, + Range)(Range r) +if (((ss == SwapStrategy.unstable && (hasSwappableElements!Range || + hasAssignableElements!Range)) || + (ss != SwapStrategy.unstable && hasAssignableElements!Range)) && + isRandomAccessRange!Range && + hasSlicing!Range && + hasLength!Range) + /+ Unstable sorting uses the quicksort algorithm, which uses swapAt, + which either uses swap(...), requiring swappable elements, or just + swaps using assignment. + Stable sorting uses TimSort, which needs to copy elements into a buffer, + requiring assignable elements. +/ +{ + import std.range : assumeSorted; + alias lessFun = binaryFun!(less); + alias LessRet = typeof(lessFun(r.front, r.front)); // instantiate lessFun + static if (is(LessRet == bool)) + { + static if (ss == SwapStrategy.unstable) + quickSortImpl!(lessFun)(r, r.length); + else //use Tim Sort for semistable & stable + TimSortImpl!(lessFun, Range).sort(r, null); + + assert(isSorted!lessFun(r), "Failed to sort range of type " ~ Range.stringof); + } + else + { + static assert(false, "Invalid predicate passed to sort: " ~ less.stringof); + } + return assumeSorted!less(r); +} + +/// +@safe pure nothrow unittest +{ + int[] array = [ 1, 2, 3, 4 ]; + + // sort in descending order + array.sort!("a > b"); + assert(array == [ 4, 3, 2, 1 ]); + + // sort in ascending order + array.sort(); + assert(array == [ 1, 2, 3, 4 ]); + + // sort with reusable comparator and chain + alias myComp = (x, y) => x > y; + assert(array.sort!(myComp).release == [ 4, 3, 2, 1 ]); +} + +/// +@safe unittest +{ + // Showcase stable sorting + import std.algorithm.mutation : SwapStrategy; + string[] words = [ "aBc", "a", "abc", "b", "ABC", "c" ]; + sort!("toUpper(a) < toUpper(b)", SwapStrategy.stable)(words); + assert(words == [ "a", "aBc", "abc", "ABC", "b", "c" ]); +} + +/// +@safe unittest +{ + // Sorting floating-point numbers in presence of NaN + double[] numbers = [-0.0, 3.0, -2.0, double.nan, 0.0, -double.nan]; + + import std.algorithm.comparison : equal; + import std.math : cmp, isIdentical; + + sort!((a, b) => cmp(a, b) < 0)(numbers); + + double[] sorted = [-double.nan, -2.0, -0.0, 0.0, 3.0, double.nan]; + assert(numbers.equal!isIdentical(sorted)); +} + +@safe unittest +{ + // Simple regression benchmark + import std.algorithm.iteration, std.algorithm.mutation, std.random; + Random rng; + int[] a = iota(20148).map!(_ => uniform(-1000, 1000, rng)).array; + static uint comps; + static bool less(int a, int b) { ++comps; return a < b; } + sort!less(a); // random numbers + sort!less(a); // sorted ascending + a.reverse(); + sort!less(a); // sorted descending + a[] = 0; + sort!less(a); // all equal + + // This should get smaller with time. On occasion it may go larger, but only + // if there's thorough justification. + debug enum uint watermark = 1676280; + else enum uint watermark = 1676220; + + import std.conv; + assert(comps <= watermark, text("You seem to have pessimized sort! ", + watermark, " < ", comps)); + assert(comps >= watermark, text("You seem to have improved sort!", + " Please update watermark from ", watermark, " to ", comps)); +} + +@safe unittest +{ + import std.algorithm.internal : rndstuff; + import std.algorithm.mutation : swapRanges; + import std.random : Random, unpredictableSeed, uniform; + import std.uni : toUpper; + + // sort using delegate + auto a = new int[100]; + auto rnd = Random(unpredictableSeed); + foreach (ref e; a) + { + e = uniform(-100, 100, rnd); + } + + int i = 0; + bool greater2(int a, int b) @safe { return a + i > b + i; } + auto greater = &greater2; + sort!(greater)(a); + assert(isSorted!(greater)(a)); + + // sort using string + sort!("a < b")(a); + assert(isSorted!("a < b")(a)); + + // sort using function; all elements equal + foreach (ref e; a) + { + e = 5; + } + static bool less(int a, int b) { return a < b; } + sort!(less)(a); + assert(isSorted!(less)(a)); + + string[] words = [ "aBc", "a", "abc", "b", "ABC", "c" ]; + bool lessi(string a, string b) { return toUpper(a) < toUpper(b); } + sort!(lessi, SwapStrategy.stable)(words); + assert(words == [ "a", "aBc", "abc", "ABC", "b", "c" ]); + + // sort using ternary predicate + //sort!("b - a")(a); + //assert(isSorted!(less)(a)); + + a = rndstuff!(int)(); + sort(a); + assert(isSorted(a)); + auto b = rndstuff!(string)(); + sort!("toLower(a) < toLower(b)")(b); + assert(isSorted!("toUpper(a) < toUpper(b)")(b)); + + { + // Issue 10317 + enum E_10317 { a, b } + auto a_10317 = new E_10317[10]; + sort(a_10317); + } + + { + // Issue 7767 + // Unstable sort should complete without an excessive number of predicate calls + // This would suggest it's running in quadratic time + + // Compilation error if predicate is not static, i.e. a nested function + static uint comp; + static bool pred(size_t a, size_t b) + { + ++comp; + return a < b; + } + + size_t[] arr; + arr.length = 1024; + + foreach (k; 0 .. arr.length) arr[k] = k; + swapRanges(arr[0..$/2], arr[$/2..$]); + + sort!(pred, SwapStrategy.unstable)(arr); + assert(comp < 25_000); + } + + { + import std.algorithm.mutation : swap; + + bool proxySwapCalled; + struct S + { + int i; + alias i this; + void proxySwap(ref S other) { swap(i, other.i); proxySwapCalled = true; } + @disable void opAssign(S value); + } + + alias R = S[]; + R r = [S(3), S(2), S(1)]; + static assert(hasSwappableElements!R); + static assert(!hasAssignableElements!R); + r.sort(); + assert(proxySwapCalled); + } +} + +private void quickSortImpl(alias less, Range)(Range r, size_t depth) +{ + import std.algorithm.comparison : min, max; + import std.algorithm.mutation : swap, swapAt; + + alias Elem = ElementType!(Range); + enum size_t shortSortGetsBetter = max(32, 1024 / Elem.sizeof); + static assert(shortSortGetsBetter >= 1); + + // partition + while (r.length > shortSortGetsBetter) + { + if (depth == 0) + { + HeapOps!(less, Range).heapSort(r); + return; + } + depth = depth >= depth.max / 2 ? (depth / 3) * 2 : (depth * 2) / 3; + + const pivotIdx = getPivot!(less)(r); + auto pivot = r[pivotIdx]; + + // partition + r.swapAt(pivotIdx, r.length - 1); + size_t lessI = size_t.max, greaterI = r.length - 1; + + outer: for (;;) + { + alias pred = binaryFun!less; + while (pred(r[++lessI], pivot)) {} + assert(lessI <= greaterI, "sort: invalid comparison function."); + for (;;) + { + if (greaterI == lessI) break outer; + if (!pred(pivot, r[--greaterI])) break; + } + assert(lessI <= greaterI, "sort: invalid comparison function."); + if (lessI == greaterI) break; + r.swapAt(lessI, greaterI); + } + + r.swapAt(r.length - 1, lessI); + auto left = r[0 .. lessI], right = r[lessI + 1 .. r.length]; + if (right.length > left.length) + { + swap(left, right); + } + .quickSortImpl!(less, Range)(right, depth); + r = left; + } + // residual sort + static if (shortSortGetsBetter > 1) + { + shortSort!(less, Range)(r); + } +} + +// Heap operations for random-access ranges +package(std) template HeapOps(alias less, Range) +{ + import std.algorithm.mutation : swapAt; + + static assert(isRandomAccessRange!Range); + static assert(hasLength!Range); + static assert(hasSwappableElements!Range || hasAssignableElements!Range); + + alias lessFun = binaryFun!less; + + //template because of @@@12410@@@ + void heapSort()(Range r) + { + // If true, there is nothing to do + if (r.length < 2) return; + // Build Heap + buildHeap(r); + // Sort + for (size_t i = r.length - 1; i > 0; --i) + { + r.swapAt(0, i); + percolate(r, 0, i); + } + } + + //template because of @@@12410@@@ + void buildHeap()(Range r) + { + immutable n = r.length; + for (size_t i = n / 2; i-- > 0; ) + { + siftDown(r, i, n); + } + assert(isHeap(r)); + } + + bool isHeap()(Range r) + { + size_t parent = 0; + foreach (child; 1 .. r.length) + { + if (lessFun(r[parent], r[child])) return false; + // Increment parent every other pass + parent += !(child & 1); + } + return true; + } + + // Sifts down r[parent] (which is initially assumed to be messed up) so the + // heap property is restored for r[parent .. end]. + // template because of @@@12410@@@ + void siftDown()(Range r, size_t parent, immutable size_t end) + { + for (;;) + { + auto child = (parent + 1) * 2; + if (child >= end) + { + // Leftover left child? + if (child == end && lessFun(r[parent], r[--child])) + r.swapAt(parent, child); + break; + } + auto leftChild = child - 1; + if (lessFun(r[child], r[leftChild])) child = leftChild; + if (!lessFun(r[parent], r[child])) break; + r.swapAt(parent, child); + parent = child; + } + } + + // Alternate version of siftDown that performs fewer comparisons, see + // https://en.wikipedia.org/wiki/Heapsort#Bottom-up_heapsort. The percolate + // process first sifts the parent all the way down (without comparing it + // against the leaves), and then a bit up until the heap property is + // restored. So there are more swaps but fewer comparisons. Gains are made + // when the final position is likely to end toward the bottom of the heap, + // so not a lot of sifts back are performed. + //template because of @@@12410@@@ + void percolate()(Range r, size_t parent, immutable size_t end) + { + immutable root = parent; + + // Sift down + for (;;) + { + auto child = (parent + 1) * 2; + + if (child >= end) + { + if (child == end) + { + // Leftover left node. + --child; + r.swapAt(parent, child); + parent = child; + } + break; + } + + auto leftChild = child - 1; + if (lessFun(r[child], r[leftChild])) child = leftChild; + r.swapAt(parent, child); + parent = child; + } + + // Sift up + for (auto child = parent; child > root; child = parent) + { + parent = (child - 1) / 2; + if (!lessFun(r[parent], r[child])) break; + r.swapAt(parent, child); + } + } +} + +// Tim Sort implementation +private template TimSortImpl(alias pred, R) +{ + import core.bitop : bsr; + import std.array : uninitializedArray; + + static assert(isRandomAccessRange!R); + static assert(hasLength!R); + static assert(hasSlicing!R); + static assert(hasAssignableElements!R); + + alias T = ElementType!R; + + alias less = binaryFun!pred; + alias greater = (a, b) => less(b, a); + alias greaterEqual = (a, b) => !less(a, b); + alias lessEqual = (a, b) => !less(b, a); + + enum minimalMerge = 128; + enum minimalGallop = 7; + enum minimalStorage = 256; + enum stackSize = 40; + + struct Slice{ size_t base, length; } + + // Entry point for tim sort + void sort()(R range, T[] temp) + { + import std.algorithm.comparison : min; + + // Do insertion sort on small range + if (range.length <= minimalMerge) + { + binaryInsertionSort(range); + return; + } + + immutable minRun = minRunLength(range.length); + immutable minTemp = min(range.length / 2, minimalStorage); + size_t minGallop = minimalGallop; + Slice[stackSize] stack = void; + size_t stackLen = 0; + + // Allocate temporary memory if not provided by user + if (temp.length < minTemp) temp = () @trusted { return uninitializedArray!(T[])(minTemp); }(); + + for (size_t i = 0; i < range.length; ) + { + // Find length of first run in list + size_t runLen = firstRun(range[i .. range.length]); + + // If run has less than minRun elements, extend using insertion sort + if (runLen < minRun) + { + // Do not run farther than the length of the range + immutable force = range.length - i > minRun ? minRun : range.length - i; + binaryInsertionSort(range[i .. i + force], runLen); + runLen = force; + } + + // Push run onto stack + stack[stackLen++] = Slice(i, runLen); + i += runLen; + + // Collapse stack so that (e1 > e2 + e3 && e2 > e3) + // STACK is | ... e1 e2 e3 > + while (stackLen > 1) + { + immutable run4 = stackLen - 1; + immutable run3 = stackLen - 2; + immutable run2 = stackLen - 3; + immutable run1 = stackLen - 4; + + if ( (stackLen > 2 && stack[run2].length <= stack[run3].length + stack[run4].length) || + (stackLen > 3 && stack[run1].length <= stack[run3].length + stack[run2].length) ) + { + immutable at = stack[run2].length < stack[run4].length ? run2 : run3; + mergeAt(range, stack[0 .. stackLen], at, minGallop, temp); + } + else if (stack[run3].length > stack[run4].length) break; + else mergeAt(range, stack[0 .. stackLen], run3, minGallop, temp); + + stackLen -= 1; + } + + // Assert that the code above established the invariant correctly + version (assert) + { + if (stackLen == 2) assert(stack[0].length > stack[1].length); + else if (stackLen > 2) + { + foreach (k; 2 .. stackLen) + { + assert(stack[k - 2].length > stack[k - 1].length + stack[k].length); + assert(stack[k - 1].length > stack[k].length); + } + } + } + } + + // Force collapse stack until there is only one run left + while (stackLen > 1) + { + immutable run3 = stackLen - 1; + immutable run2 = stackLen - 2; + immutable run1 = stackLen - 3; + immutable at = stackLen >= 3 && stack[run1].length <= stack[run3].length + ? run1 : run2; + mergeAt(range, stack[0 .. stackLen], at, minGallop, temp); + --stackLen; + } + } + + // Calculates optimal value for minRun: + // take first 6 bits of n and add 1 if any lower bits are set + size_t minRunLength()(size_t n) + { + immutable shift = bsr(n)-5; + auto result = (n >> shift) + !!(n & ~((1 << shift)-1)); + return result; + } + + // Returns length of first run in range + size_t firstRun()(R range) + out(ret) + { + assert(ret <= range.length); + } + body + { + import std.algorithm.mutation : reverse; + + if (range.length < 2) return range.length; + + size_t i = 2; + if (lessEqual(range[0], range[1])) + { + while (i < range.length && lessEqual(range[i-1], range[i])) ++i; + } + else + { + while (i < range.length && greater(range[i-1], range[i])) ++i; + reverse(range[0 .. i]); + } + return i; + } + + // A binary insertion sort for building runs up to minRun length + void binaryInsertionSort()(R range, size_t sortedLen = 1) + out + { + if (!__ctfe) assert(isSorted!pred(range)); + } + body + { + import std.algorithm.mutation : move; + + for (; sortedLen < range.length; ++sortedLen) + { + T item = range.moveAt(sortedLen); + size_t lower = 0; + size_t upper = sortedLen; + while (upper != lower) + { + size_t center = (lower + upper) / 2; + if (less(item, range[center])) upper = center; + else lower = center + 1; + } + //Currently (DMD 2.061) moveAll+retro is slightly less + //efficient then stright 'for' loop + //11 instructions vs 7 in the innermost loop [checked on Win32] + //moveAll(retro(range[lower .. sortedLen]), + // retro(range[lower+1 .. sortedLen+1])); + for (upper=sortedLen; upper > lower; upper--) + range[upper] = range.moveAt(upper - 1); + range[lower] = move(item); + } + } + + // Merge two runs in stack (at, at + 1) + void mergeAt()(R range, Slice[] stack, immutable size_t at, ref size_t minGallop, ref T[] temp) + in + { + assert(stack.length >= 2); + assert(stack.length - at == 2 || stack.length - at == 3); + } + body + { + immutable base = stack[at].base; + immutable mid = stack[at].length; + immutable len = stack[at + 1].length + mid; + + // Pop run from stack + stack[at] = Slice(base, len); + if (stack.length - at == 3) stack[$ - 2] = stack[$ - 1]; + + // Merge runs (at, at + 1) + return merge(range[base .. base + len], mid, minGallop, temp); + } + + // Merge two runs in a range. Mid is the starting index of the second run. + // minGallop and temp are references; The calling function must receive the updated values. + void merge()(R range, size_t mid, ref size_t minGallop, ref T[] temp) + in + { + if (!__ctfe) + { + assert(isSorted!pred(range[0 .. mid])); + assert(isSorted!pred(range[mid .. range.length])); + } + } + body + { + assert(mid < range.length); + + // Reduce range of elements + immutable firstElement = gallopForwardUpper(range[0 .. mid], range[mid]); + immutable lastElement = gallopReverseLower(range[mid .. range.length], range[mid - 1]) + mid; + range = range[firstElement .. lastElement]; + mid -= firstElement; + + if (mid == 0 || mid == range.length) return; + + // Call function which will copy smaller run into temporary memory + if (mid <= range.length / 2) + { + temp = ensureCapacity(mid, temp); + minGallop = mergeLo(range, mid, minGallop, temp); + } + else + { + temp = ensureCapacity(range.length - mid, temp); + minGallop = mergeHi(range, mid, minGallop, temp); + } + } + + // Enlarge size of temporary memory if needed + T[] ensureCapacity()(size_t minCapacity, T[] temp) + out(ret) + { + assert(ret.length >= minCapacity); + } + body + { + if (temp.length < minCapacity) + { + size_t newSize = 1<<(bsr(minCapacity)+1); + //Test for overflow + if (newSize < minCapacity) newSize = minCapacity; + + if (__ctfe) temp.length = newSize; + else temp = () @trusted { return uninitializedArray!(T[])(newSize); }(); + } + return temp; + } + + // Merge front to back. Returns new value of minGallop. + // temp must be large enough to store range[0 .. mid] + size_t mergeLo()(R range, immutable size_t mid, size_t minGallop, T[] temp) + out + { + if (!__ctfe) assert(isSorted!pred(range)); + } + body + { + import std.algorithm.mutation : copy; + + assert(mid <= range.length); + assert(temp.length >= mid); + + // Copy run into temporary memory + temp = temp[0 .. mid]; + copy(range[0 .. mid], temp); + + // Move first element into place + range[0] = range[mid]; + + size_t i = 1, lef = 0, rig = mid + 1; + size_t count_lef, count_rig; + immutable lef_end = temp.length - 1; + + if (lef < lef_end && rig < range.length) + outer: while (true) + { + count_lef = 0; + count_rig = 0; + + // Linear merge + while ((count_lef | count_rig) < minGallop) + { + if (lessEqual(temp[lef], range[rig])) + { + range[i++] = temp[lef++]; + if (lef >= lef_end) break outer; + ++count_lef; + count_rig = 0; + } + else + { + range[i++] = range[rig++]; + if (rig >= range.length) break outer; + count_lef = 0; + ++count_rig; + } + } + + // Gallop merge + do + { + count_lef = gallopForwardUpper(temp[lef .. $], range[rig]); + foreach (j; 0 .. count_lef) range[i++] = temp[lef++]; + if (lef >= temp.length) break outer; + + count_rig = gallopForwardLower(range[rig .. range.length], temp[lef]); + foreach (j; 0 .. count_rig) range[i++] = range[rig++]; + if (rig >= range.length) while (true) + { + range[i++] = temp[lef++]; + if (lef >= temp.length) break outer; + } + + if (minGallop > 0) --minGallop; + } + while (count_lef >= minimalGallop || count_rig >= minimalGallop); + + minGallop += 2; + } + + // Move remaining elements from right + while (rig < range.length) + range[i++] = range[rig++]; + + // Move remaining elements from left + while (lef < temp.length) + range[i++] = temp[lef++]; + + return minGallop > 0 ? minGallop : 1; + } + + // Merge back to front. Returns new value of minGallop. + // temp must be large enough to store range[mid .. range.length] + size_t mergeHi()(R range, immutable size_t mid, size_t minGallop, T[] temp) + out + { + if (!__ctfe) assert(isSorted!pred(range)); + } + body + { + import std.algorithm.mutation : copy; + + assert(mid <= range.length); + assert(temp.length >= range.length - mid); + + // Copy run into temporary memory + temp = temp[0 .. range.length - mid]; + copy(range[mid .. range.length], temp); + + // Move first element into place + range[range.length - 1] = range[mid - 1]; + + size_t i = range.length - 2, lef = mid - 2, rig = temp.length - 1; + size_t count_lef, count_rig; + + outer: + while (true) + { + count_lef = 0; + count_rig = 0; + + // Linear merge + while ((count_lef | count_rig) < minGallop) + { + if (greaterEqual(temp[rig], range[lef])) + { + range[i--] = temp[rig]; + if (rig == 1) + { + // Move remaining elements from left + while (true) + { + range[i--] = range[lef]; + if (lef == 0) break; + --lef; + } + + // Move last element into place + range[i] = temp[0]; + + break outer; + } + --rig; + count_lef = 0; + ++count_rig; + } + else + { + range[i--] = range[lef]; + if (lef == 0) while (true) + { + range[i--] = temp[rig]; + if (rig == 0) break outer; + --rig; + } + --lef; + ++count_lef; + count_rig = 0; + } + } + + // Gallop merge + do + { + count_rig = rig - gallopReverseLower(temp[0 .. rig], range[lef]); + foreach (j; 0 .. count_rig) + { + range[i--] = temp[rig]; + if (rig == 0) break outer; + --rig; + } + + count_lef = lef - gallopReverseUpper(range[0 .. lef], temp[rig]); + foreach (j; 0 .. count_lef) + { + range[i--] = range[lef]; + if (lef == 0) while (true) + { + range[i--] = temp[rig]; + if (rig == 0) break outer; + --rig; + } + --lef; + } + + if (minGallop > 0) --minGallop; + } + while (count_lef >= minimalGallop || count_rig >= minimalGallop); + + minGallop += 2; + } + + return minGallop > 0 ? minGallop : 1; + } + + // false = forward / lower, true = reverse / upper + template gallopSearch(bool forwardReverse, bool lowerUpper) + { + // Gallop search on range according to attributes forwardReverse and lowerUpper + size_t gallopSearch(R)(R range, T value) + out(ret) + { + assert(ret <= range.length); + } + body + { + size_t lower = 0, center = 1, upper = range.length; + alias gap = center; + + static if (forwardReverse) + { + static if (!lowerUpper) alias comp = lessEqual; // reverse lower + static if (lowerUpper) alias comp = less; // reverse upper + + // Gallop Search Reverse + while (gap <= upper) + { + if (comp(value, range[upper - gap])) + { + upper -= gap; + gap *= 2; + } + else + { + lower = upper - gap; + break; + } + } + + // Binary Search Reverse + while (upper != lower) + { + center = lower + (upper - lower) / 2; + if (comp(value, range[center])) upper = center; + else lower = center + 1; + } + } + else + { + static if (!lowerUpper) alias comp = greater; // forward lower + static if (lowerUpper) alias comp = greaterEqual; // forward upper + + // Gallop Search Forward + while (lower + gap < upper) + { + if (comp(value, range[lower + gap])) + { + lower += gap; + gap *= 2; + } + else + { + upper = lower + gap; + break; + } + } + + // Binary Search Forward + while (lower != upper) + { + center = lower + (upper - lower) / 2; + if (comp(value, range[center])) lower = center + 1; + else upper = center; + } + } + + return lower; + } + } + + alias gallopForwardLower = gallopSearch!(false, false); + alias gallopForwardUpper = gallopSearch!(false, true); + alias gallopReverseLower = gallopSearch!( true, false); + alias gallopReverseUpper = gallopSearch!( true, true); +} + +@safe unittest +{ + import std.random : Random, uniform, randomShuffle; + + // Element type with two fields + static struct E + { + size_t value, index; + } + + // Generates data especially for testing sorting with Timsort + static E[] genSampleData(uint seed) @safe + { + import std.algorithm.mutation : swap, swapRanges; + + auto rnd = Random(seed); + + E[] arr; + arr.length = 64 * 64; + + // We want duplicate values for testing stability + foreach (i, ref v; arr) v.value = i / 64; + + // Swap ranges at random middle point (test large merge operation) + immutable mid = uniform(arr.length / 4, arr.length / 4 * 3, rnd); + swapRanges(arr[0 .. mid], arr[mid .. $]); + + // Shuffle last 1/8 of the array (test insertion sort and linear merge) + randomShuffle(arr[$ / 8 * 7 .. $], rnd); + + // Swap few random elements (test galloping mode) + foreach (i; 0 .. arr.length / 64) + { + immutable a = uniform(0, arr.length, rnd), b = uniform(0, arr.length, rnd); + swap(arr[a], arr[b]); + } + + // Now that our test array is prepped, store original index value + // This will allow us to confirm the array was sorted stably + foreach (i, ref v; arr) v.index = i; + + return arr; + } + + // Tests the Timsort function for correctness and stability + static bool testSort(uint seed) + { + auto arr = genSampleData(seed); + + // Now sort the array! + static bool comp(E a, E b) + { + return a.value < b.value; + } + + sort!(comp, SwapStrategy.stable)(arr); + + // Test that the array was sorted correctly + assert(isSorted!comp(arr)); + + // Test that the array was sorted stably + foreach (i; 0 .. arr.length - 1) + { + if (arr[i].value == arr[i + 1].value) assert(arr[i].index < arr[i + 1].index); + } + + return true; + } + + enum seed = 310614065; + testSort(seed); + + enum result = testSort(seed); + assert(result == true); +} + +@safe unittest +{//bugzilla 4584 + assert(isSorted!"a < b"(sort!("a < b", SwapStrategy.stable)( + [83, 42, 85, 86, 87, 22, 89, 30, 91, 46, 93, 94, 95, 6, + 97, 14, 33, 10, 101, 102, 103, 26, 105, 106, 107, 6] + ))); + +} + +@safe unittest +{ + //test stable sort + zip + import std.range; + auto x = [10, 50, 60, 60, 20]; + dchar[] y = "abcde"d.dup; + + sort!("a[0] < b[0]", SwapStrategy.stable)(zip(x, y)); + assert(x == [10, 20, 50, 60, 60]); + assert(y == "aebcd"d); +} + +@safe unittest +{ + // Issue 14223 + import std.array, std.range; + auto arr = chain(iota(0, 384), iota(0, 256), iota(0, 80), iota(0, 64), iota(0, 96)).array; + sort!("a < b", SwapStrategy.stable)(arr); +} + +// schwartzSort +/** +Alternative sorting method that should be used when comparing keys involves an +expensive computation. Instead of using `less(a, b)` for comparing elements, +`schwartzSort` uses `less(transform(a), transform(b))`. The values of the +`transform` function are precomputed in a temporary array, thus saving on +repeatedly computing it. Conversely, if the cost of `transform` is small +compared to the cost of allocating and filling the precomputed array, `sort` +may be faster and therefore preferable. + +This approach to sorting is akin to the $(HTTP +wikipedia.org/wiki/Schwartzian_transform, Schwartzian transform), also known as +the decorate-sort-undecorate pattern in Python and Lisp. The complexity is the +same as that of the corresponding `sort`, but `schwartzSort` evaluates +`transform` only `r.length` times (less than half when compared to regular +sorting). The usage can be best illustrated with an example. + +Example: +---- +uint hashFun(string) { ... expensive computation ... } +string[] array = ...; +// Sort strings by hash, slow +sort!((a, b) => hashFun(a) < hashFun(b))(array); +// Sort strings by hash, fast (only computes arr.length hashes): +schwartzSort!(hashFun, "a < b")(array); +---- + +The $(D schwartzSort) function might require less temporary data and +be faster than the Perl idiom or the decorate-sort-undecorate idiom +present in Python and Lisp. This is because sorting is done in-place +and only minimal extra data (one array of transformed elements) is +created. + +To check whether an array was sorted and benefit of the speedup of +Schwartz sorting, a function $(D schwartzIsSorted) is not provided +because the effect can be achieved by calling $(D +isSorted!less(map!transform(r))). + +Params: + transform = The transformation to apply. + less = The predicate to sort by. + ss = The swapping strategy to use. + r = The range to sort. + +Returns: The initial range wrapped as a $(D SortedRange) with the +predicate $(D (a, b) => binaryFun!less(transform(a), +transform(b))). + */ +SortedRange!(R, ((a, b) => binaryFun!less(unaryFun!transform(a), + unaryFun!transform(b)))) +schwartzSort(alias transform, alias less = "a < b", + SwapStrategy ss = SwapStrategy.unstable, R)(R r) +if (isRandomAccessRange!R && hasLength!R) +{ + import std.conv : emplace; + import std.range : zip, SortedRange; + import std.string : representation; + + alias T = typeof(unaryFun!transform(r.front)); + static trustedMalloc(size_t len) @trusted + { + import core.checkedint : mulu; + import core.stdc.stdlib : malloc; + bool overflow; + const nbytes = mulu(len, T.sizeof, overflow); + if (overflow) assert(0); + return (cast(T*) malloc(nbytes))[0 .. len]; + } + auto xform1 = trustedMalloc(r.length); + + size_t length; + scope(exit) + { + static if (hasElaborateDestructor!T) + { + foreach (i; 0 .. length) collectException(destroy(xform1[i])); + } + static void trustedFree(T[] p) @trusted + { + import core.stdc.stdlib : free; + free(p.ptr); + } + trustedFree(xform1); + } + for (; length != r.length; ++length) + { + emplace(&xform1[length], unaryFun!transform(r[length])); + } + // Make sure we use ubyte[] and ushort[], not char[] and wchar[] + // for the intermediate array, lest zip gets confused. + static if (isNarrowString!(typeof(xform1))) + { + auto xform = xform1.representation(); + } + else + { + alias xform = xform1; + } + zip(xform, r).sort!((a, b) => binaryFun!less(a[0], b[0]), ss)(); + return typeof(return)(r); +} + +/// +@safe unittest +{ + import std.algorithm.iteration : map; + import std.numeric : entropy; + + auto lowEnt = [ 1.0, 0, 0 ], + midEnt = [ 0.1, 0.1, 0.8 ], + highEnt = [ 0.31, 0.29, 0.4 ]; + auto arr = new double[][3]; + arr[0] = midEnt; + arr[1] = lowEnt; + arr[2] = highEnt; + + schwartzSort!(entropy, "a > b")(arr); + + assert(arr[0] == highEnt); + assert(arr[1] == midEnt); + assert(arr[2] == lowEnt); + assert(isSorted!("a > b")(map!(entropy)(arr))); +} + +@safe unittest +{ + import std.algorithm.iteration : map; + import std.numeric : entropy; + + auto lowEnt = [ 1.0, 0, 0 ], + midEnt = [ 0.1, 0.1, 0.8 ], + highEnt = [ 0.31, 0.29, 0.4 ]; + auto arr = new double[][3]; + arr[0] = midEnt; + arr[1] = lowEnt; + arr[2] = highEnt; + + schwartzSort!(entropy, "a < b")(arr); + + assert(arr[0] == lowEnt); + assert(arr[1] == midEnt); + assert(arr[2] == highEnt); + assert(isSorted!("a < b")(map!(entropy)(arr))); +} + +@safe unittest +{ + // issue 4909 + import std.typecons : Tuple; + Tuple!(char)[] chars; + schwartzSort!"a[0]"(chars); +} + +@safe unittest +{ + // issue 5924 + import std.typecons : Tuple; + Tuple!(char)[] chars; + schwartzSort!((Tuple!(char) c){ return c[0]; })(chars); +} + +// partialSort +/** +Reorders the random-access range $(D r) such that the range $(D r[0 +.. mid]) is the same as if the entire $(D r) were sorted, and leaves +the range $(D r[mid .. r.length]) in no particular order. Performs +$(BIGOH r.length * log(mid)) evaluations of $(D pred). The +implementation simply calls $(D topN!(less, ss)(r, n)) and then $(D +sort!(less, ss)(r[0 .. n])). + +Params: + less = The predicate to sort by. + ss = The swapping strategy to use. + r = The random-access range to reorder. + n = The length of the initial segment of `r` to sort. +*/ +void partialSort(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, + Range)(Range r, size_t n) +if (isRandomAccessRange!(Range) && hasLength!(Range) && hasSlicing!(Range)) +{ + partialSort!(less, ss)(r[0 .. n], r[n .. $]); +} + +/// +@system unittest +{ + int[] a = [ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ]; + partialSort(a, 5); + assert(a[0 .. 5] == [ 0, 1, 2, 3, 4 ]); +} + +/** +Stores the smallest elements of the two ranges in the left-hand range in sorted order. + +Params: + less = The predicate to sort by. + ss = The swapping strategy to use. + r1 = The first range. + r2 = The second range. + */ + +void partialSort(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, + Range1, Range2)(Range1 r1, Range2 r2) +if (isRandomAccessRange!(Range1) && hasLength!Range1 && + isInputRange!Range2 && is(ElementType!Range1 == ElementType!Range2) && + hasLvalueElements!Range1 && hasLvalueElements!Range2) +{ + topN!(less, ss)(r1, r2); + sort!(less, ss)(r1); +} +/// +@system unittest +{ + int[] a = [5, 7, 2, 6, 7]; + int[] b = [2, 1, 5, 6, 7, 3, 0]; + + partialSort(a, b); + assert(a == [0, 1, 2, 2, 3]); +} + +// topN +/** +Reorders the range $(D r) using $(D swap) such that $(D r[nth]) refers +to the element that would fall there if the range were fully +sorted. In addition, it also partitions $(D r) such that all elements +$(D e1) from $(D r[0]) to $(D r[nth]) satisfy $(D !less(r[nth], e1)), +and all elements $(D e2) from $(D r[nth]) to $(D r[r.length]) satisfy +$(D !less(e2, r[nth])). Effectively, it finds the nth smallest +(according to $(D less)) elements in $(D r). Performs an expected +$(BIGOH r.length) (if unstable) or $(BIGOH r.length * log(r.length)) +(if stable) evaluations of $(D less) and $(D swap). + +If $(D n >= r.length), the algorithm has no effect and returns +`r[0 .. r.length]`. + +Params: + less = The predicate to sort by. + ss = The swapping strategy to use. + r = The random-access range to reorder. + nth = The index of the element that should be in sorted position after the + function is done. + +See_Also: + $(LREF topNIndex), + $(HTTP sgi.com/tech/stl/nth_element.html, STL's nth_element) + +BUGS: + +Stable topN has not been implemented yet. +*/ +auto topN(alias less = "a < b", + SwapStrategy ss = SwapStrategy.unstable, + Range)(Range r, size_t nth) +if (isRandomAccessRange!(Range) && hasLength!Range && hasSlicing!Range) +{ + static assert(ss == SwapStrategy.unstable, + "Stable topN not yet implemented"); + if (nth >= r.length) return r[0 .. r.length]; + auto ret = r[0 .. nth]; + if (false) + { + // Workaround for https://issues.dlang.org/show_bug.cgi?id=16528 + // Safety checks: enumerate all potentially unsafe generic primitives + // then use a @trusted implementation. + binaryFun!less(r[0], r[r.length - 1]); + import std.algorithm.mutation : swapAt; + r.swapAt(size_t(0), size_t(0)); + static assert(is(typeof(r.length) == size_t)); + pivotPartition!less(r, 0); + } + bool useSampling = true; + topNImpl!(binaryFun!less)(r, nth, useSampling); + return ret; +} + +private @trusted +void topNImpl(alias less, R)(R r, size_t n, ref bool useSampling) +{ + for (;;) + { + import std.algorithm.mutation : swapAt; + assert(n < r.length); + size_t pivot = void; + + // Decide strategy for partitioning + if (n == 0) + { + pivot = 0; + foreach (i; 1 .. r.length) + if (less(r[i], r[pivot])) pivot = i; + r.swapAt(n, pivot); + return; + } + if (n + 1 == r.length) + { + pivot = 0; + foreach (i; 1 .. r.length) + if (less(r[pivot], r[i])) pivot = i; + r.swapAt(n, pivot); + return; + } + if (r.length <= 12) + { + pivot = pivotPartition!less(r, r.length / 2); + } + else if (n * 16 <= (r.length - 1) * 7) + { + pivot = topNPartitionOffMedian!(less, No.leanRight) + (r, n, useSampling); + // Quality check + if (useSampling) + { + if (pivot < n) + { + if (pivot * 4 < r.length) + { + useSampling = false; + } + } + else if ((r.length - pivot) * 8 < r.length * 3) + { + useSampling = false; + } + } + } + else if (n * 16 >= (r.length - 1) * 9) + { + pivot = topNPartitionOffMedian!(less, Yes.leanRight) + (r, n, useSampling); + // Quality check + if (useSampling) + { + if (pivot < n) + { + if (pivot * 8 < r.length * 3) + { + useSampling = false; + } + } + else if ((r.length - pivot) * 4 < r.length) + { + useSampling = false; + } + } + } + else + { + pivot = topNPartition!less(r, n, useSampling); + // Quality check + if (useSampling && + (pivot * 9 < r.length * 2 || pivot * 9 > r.length * 7)) + { + // Failed - abort sampling going forward + useSampling = false; + } + } + + assert(pivot != size_t.max); + // See how the pivot fares + if (pivot == n) + { + return; + } + if (pivot > n) + { + r = r[0 .. pivot]; + } + else + { + n -= pivot + 1; + r = r[pivot + 1 .. r.length]; + } + } +} + +/// +@safe unittest +{ + int[] v = [ 25, 7, 9, 2, 0, 5, 21 ]; + topN!"a < b"(v, 100); + assert(v == [ 25, 7, 9, 2, 0, 5, 21 ]); + auto n = 4; + topN!"a < b"(v, n); + assert(v[n] == 9); +} + +private size_t topNPartition(alias lp, R)(R r, size_t n, bool useSampling) +{ + assert(r.length >= 9 && n < r.length); + immutable ninth = r.length / 9; + auto pivot = ninth / 2; + // Position subrange r[lo .. hi] to have length equal to ninth and its upper + // median r[lo .. hi][$ / 2] in exactly the same place as the upper median + // of the entire range r[$ / 2]. This is to improve behavior for searching + // the median in already sorted ranges. + immutable lo = r.length / 2 - pivot, hi = lo + ninth; + // We have either one straggler on the left, one on the right, or none. + assert(lo - (r.length - hi) <= 1 || (r.length - hi) - lo <= 1); + assert(lo >= ninth * 4); + assert(r.length - hi >= ninth * 4); + + // Partition in groups of 3, and the mid tertile again in groups of 3 + if (!useSampling) + p3!lp(r, lo - ninth, hi + ninth); + p3!lp(r, lo, hi); + + // Get the median of medians of medians + // Map the full interval of n to the full interval of the ninth + pivot = (n * (ninth - 1)) / (r.length - 1); + topNImpl!lp(r[lo .. hi], pivot, useSampling); + return expandPartition!lp(r, lo, pivot + lo, hi); +} + +private void p3(alias less, Range)(Range r, size_t lo, immutable size_t hi) +{ + assert(lo <= hi && hi < r.length); + immutable ln = hi - lo; + for (; lo < hi; ++lo) + { + assert(lo >= ln); + assert(lo + ln < r.length); + medianOf!less(r, lo - ln, lo, lo + ln); + } +} + +private void p4(alias less, Flag!"leanRight" f, Range) + (Range r, size_t lo, immutable size_t hi) +{ + assert(lo <= hi && hi < r.length); + immutable ln = hi - lo, _2ln = ln * 2; + for (; lo < hi; ++lo) + { + assert(lo >= ln); + assert(lo + ln < r.length); + static if (f == Yes.leanRight) + medianOf!(less, f)(r, lo - _2ln, lo - ln, lo, lo + ln); + else + medianOf!(less, f)(r, lo - ln, lo, lo + ln, lo + _2ln); + } +} + +private size_t topNPartitionOffMedian(alias lp, Flag!"leanRight" f, R) + (R r, size_t n, bool useSampling) +{ + assert(r.length >= 12); + assert(n < r.length); + immutable _4 = r.length / 4; + static if (f == Yes.leanRight) + immutable leftLimit = 2 * _4; + else + immutable leftLimit = _4; + // Partition in groups of 4, and the left quartile again in groups of 3 + if (!useSampling) + { + p4!(lp, f)(r, leftLimit, leftLimit + _4); + } + immutable _12 = _4 / 3; + immutable lo = leftLimit + _12, hi = lo + _12; + p3!lp(r, lo, hi); + + // Get the median of medians of medians + // Map the full interval of n to the full interval of the ninth + immutable pivot = (n * (_12 - 1)) / (r.length - 1); + topNImpl!lp(r[lo .. hi], pivot, useSampling); + return expandPartition!lp(r, lo, pivot + lo, hi); +} + +/* +Params: +less = predicate +r = range to partition +pivot = pivot to partition around +lo = value such that r[lo .. pivot] already less than r[pivot] +hi = value such that r[pivot .. hi] already greater than r[pivot] + +Returns: new position of pivot +*/ +private +size_t expandPartition(alias lp, R)(R r, size_t lo, size_t pivot, size_t hi) +in +{ + import std.algorithm.searching : all; + assert(lo <= pivot); + assert(pivot < hi); + assert(hi <= r.length); + assert(r[lo .. pivot + 1].all!(x => !lp(r[pivot], x))); + assert(r[pivot + 1 .. hi].all!(x => !lp(x, r[pivot]))); + } +out +{ + import std.algorithm.searching : all; + assert(r[0 .. pivot + 1].all!(x => !lp(r[pivot], x))); + assert(r[pivot + 1 .. r.length].all!(x => !lp(x, r[pivot]))); +} +body +{ + import std.algorithm.mutation : swapAt; + import std.algorithm.searching : all; + // We work with closed intervals! + --hi; + + size_t left = 0, rite = r.length - 1; + loop: for (;; ++left, --rite) + { + for (;; ++left) + { + if (left == lo) break loop; + if (!lp(r[left], r[pivot])) break; + } + for (;; --rite) + { + if (rite == hi) break loop; + if (!lp(r[pivot], r[rite])) break; + } + r.swapAt(left, rite); + } + + assert(r[lo .. pivot + 1].all!(x => !lp(r[pivot], x))); + assert(r[pivot + 1 .. hi + 1].all!(x => !lp(x, r[pivot]))); + assert(r[0 .. left].all!(x => !lp(r[pivot], x))); + assert(r[rite + 1 .. r.length].all!(x => !lp(x, r[pivot]))); + + immutable oldPivot = pivot; + + if (left < lo) + { + // First loop: spend r[lo .. pivot] + for (; lo < pivot; ++left) + { + if (left == lo) goto done; + if (!lp(r[oldPivot], r[left])) continue; + --pivot; + assert(!lp(r[oldPivot], r[pivot])); + r.swapAt(left, pivot); + } + // Second loop: make left and pivot meet + for (;; ++left) + { + if (left == pivot) goto done; + if (!lp(r[oldPivot], r[left])) continue; + for (;;) + { + if (left == pivot) goto done; + --pivot; + if (lp(r[pivot], r[oldPivot])) + { + r.swapAt(left, pivot); + break; + } + } + } + } + + // First loop: spend r[lo .. pivot] + for (; hi != pivot; --rite) + { + if (rite == hi) goto done; + if (!lp(r[rite], r[oldPivot])) continue; + ++pivot; + assert(!lp(r[pivot], r[oldPivot])); + r.swapAt(rite, pivot); + } + // Second loop: make left and pivot meet + for (; rite > pivot; --rite) + { + if (!lp(r[rite], r[oldPivot])) continue; + while (rite > pivot) + { + ++pivot; + if (lp(r[oldPivot], r[pivot])) + { + r.swapAt(rite, pivot); + break; + } + } + } + +done: + r.swapAt(oldPivot, pivot); + return pivot; +} + +@safe unittest +{ + auto a = [ 10, 5, 3, 4, 8, 11, 13, 3, 9, 4, 10 ]; + assert(expandPartition!((a, b) => a < b)(a, 4, 5, 6) == 9); + a = randomArray; + if (a.length == 0) return; + expandPartition!((a, b) => a < b)(a, a.length / 2, a.length / 2, + a.length / 2 + 1); +} + +version (unittest) +private T[] randomArray(Flag!"exactSize" flag = No.exactSize, T = int)( + size_t maxSize = 1000, + T minValue = 0, T maxValue = 255) +{ + import std.algorithm.iteration : map; + import std.random : unpredictableSeed, Random, uniform; + auto size = flag == Yes.exactSize ? maxSize : uniform(1, maxSize); + return iota(0, size).map!(_ => uniform(minValue, maxValue)).array; +} + +@safe unittest +{ + import std.algorithm.comparison : max, min; + import std.algorithm.iteration : reduce; + + int[] v = [ 7, 6, 5, 4, 3, 2, 1, 0 ]; + ptrdiff_t n = 3; + topN!("a < b")(v, n); + assert(reduce!max(v[0 .. n]) <= v[n]); + assert(reduce!min(v[n + 1 .. $]) >= v[n]); + // + v = [3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]; + n = 3; + topN(v, n); + assert(reduce!max(v[0 .. n]) <= v[n]); + assert(reduce!min(v[n + 1 .. $]) >= v[n]); + // + v = [3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]; + n = 1; + topN(v, n); + assert(reduce!max(v[0 .. n]) <= v[n]); + assert(reduce!min(v[n + 1 .. $]) >= v[n]); + // + v = [3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]; + n = v.length - 1; + topN(v, n); + assert(v[n] == 7); + // + v = [3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]; + n = 0; + topN(v, n); + assert(v[n] == 1); + + double[][] v1 = [[-10, -5], [-10, -3], [-10, -5], [-10, -4], + [-10, -5], [-9, -5], [-9, -3], [-9, -5],]; + + // double[][] v1 = [ [-10, -5], [-10, -4], [-9, -5], [-9, -5], + // [-10, -5], [-10, -3], [-10, -5], [-9, -3],]; + double[]*[] idx = [ &v1[0], &v1[1], &v1[2], &v1[3], &v1[4], &v1[5], &v1[6], + &v1[7], ]; + + auto mid = v1.length / 2; + topN!((a, b){ return (*a)[1] < (*b)[1]; })(idx, mid); + foreach (e; idx[0 .. mid]) assert((*e)[1] <= (*idx[mid])[1]); + foreach (e; idx[mid .. $]) assert((*e)[1] >= (*idx[mid])[1]); +} + +@safe unittest +{ + import std.algorithm.comparison : max, min; + import std.algorithm.iteration : reduce; + import std.random : Random, uniform, unpredictableSeed; + + immutable uint[] seeds = [90027751, 2709791795, 1374631933, 995751648, 3541495258, 984840953, unpredictableSeed]; + foreach (s; seeds) + { + auto r = Random(s); + + int[] a = new int[uniform(1, 10000, r)]; + foreach (ref e; a) e = uniform(-1000, 1000, r); + + auto k = uniform(0, a.length, r); + topN(a, k); + if (k > 0) + { + auto left = reduce!max(a[0 .. k]); + assert(left <= a[k]); + } + if (k + 1 < a.length) + { + auto right = reduce!min(a[k + 1 .. $]); + assert(right >= a[k]); + } + } +} + +// bug 12987 +@safe unittest +{ + int[] a = [ 25, 7, 9, 2, 0, 5, 21 ]; + auto n = 4; + auto t = topN(a, n); + sort(t); + assert(t == [0, 2, 5, 7]); +} + +/** +Stores the smallest elements of the two ranges in the left-hand range. + +Params: + less = The predicate to sort by. + ss = The swapping strategy to use. + r1 = The first range. + r2 = The second range. + */ +auto topN(alias less = "a < b", + SwapStrategy ss = SwapStrategy.unstable, + Range1, Range2)(Range1 r1, Range2 r2) +if (isRandomAccessRange!(Range1) && hasLength!Range1 && + isInputRange!Range2 && is(ElementType!Range1 == ElementType!Range2) && + hasLvalueElements!Range1 && hasLvalueElements!Range2) +{ + import std.container : BinaryHeap; + + static assert(ss == SwapStrategy.unstable, + "Stable topN not yet implemented"); + + auto heap = BinaryHeap!(Range1, less)(r1); + foreach (ref e; r2) + { + heap.conditionalSwap(e); + } + + return r1; +} + +/// +@system unittest +{ + int[] a = [ 5, 7, 2, 6, 7 ]; + int[] b = [ 2, 1, 5, 6, 7, 3, 0 ]; + topN(a, b); + sort(a); + assert(a == [0, 1, 2, 2, 3]); +} + +// bug 15421 +@system unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange; + import std.meta : AliasSeq; + + alias RandomRanges = AliasSeq!( + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random) + ); + + alias ReferenceRanges = AliasSeq!( + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Forward), + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Bidirectional), + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random), + DummyRange!(ReturnBy.Reference, Length.No, RangeType.Forward), + DummyRange!(ReturnBy.Reference, Length.No, RangeType.Bidirectional)); + + foreach (T1; RandomRanges) + { + foreach (T2; ReferenceRanges) + { + import std.array; + + T1 A; + T2 B; + + A.reinit(); + B.reinit(); + + topN(A, B); + + // BUG(?): sort doesn't accept DummyRanges (needs Slicing and Length) + auto a = array(A); + auto b = array(B); + sort(a); + sort(b); + + assert(equal(a, [ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5 ])); + assert(equal(b, [ 6, 6, 7, 7, 8, 8, 9, 9, 10, 10 ])); + } + } +} + +// bug 15421 +@system unittest +{ + auto a = [ 9, 8, 0, 3, 5, 25, 43, 4, 2, 0, 7 ]; + auto b = [ 9, 8, 0, 3, 5, 25, 43, 4, 2, 0, 7 ]; + + topN(a, 4); + topN(b[0 .. 4], b[4 .. $]); + + sort(a[0 .. 4]); + sort(a[4 .. $]); + sort(b[0 .. 4]); + sort(b[4 .. $]); + + assert(a[0 .. 4] == b[0 .. 4]); + assert(a[4 .. $] == b[4 .. $]); + assert(a == b); +} + +// bug 12987 +@system unittest +{ + int[] a = [ 5, 7, 2, 6, 7 ]; + int[] b = [ 2, 1, 5, 6, 7, 3, 0 ]; + auto t = topN(a, b); + sort(t); + assert(t == [ 0, 1, 2, 2, 3 ]); +} + +// bug 15420 +@system unittest +{ + int[] a = [ 5, 7, 2, 6, 7 ]; + int[] b = [ 2, 1, 5, 6, 7, 3, 0 ]; + topN!"a > b"(a, b); + sort!"a > b"(a); + assert(a == [ 7, 7, 7, 6, 6 ]); +} + +/** +Copies the top $(D n) elements of the +$(REF_ALTTEXT input range, isInputRange, std,range,primitives) $(D source) into the +random-access range $(D target), where $(D n = +target.length). Elements of $(D source) are not touched. If $(D +sorted) is $(D true), the target is sorted. Otherwise, the target +respects the $(HTTP en.wikipedia.org/wiki/Binary_heap, heap property). + +Params: + less = The predicate to sort by. + source = The source range. + target = The target range. + sorted = Whether to sort the elements copied into `target`. + +Returns: The slice of `target` containing the copied elements. + */ +TRange topNCopy(alias less = "a < b", SRange, TRange) + (SRange source, TRange target, SortOutput sorted = No.sortOutput) +if (isInputRange!(SRange) && isRandomAccessRange!(TRange) + && hasLength!(TRange) && hasSlicing!(TRange)) +{ + import std.container : BinaryHeap; + + if (target.empty) return target; + auto heap = BinaryHeap!(TRange, less)(target, 0); + foreach (e; source) heap.conditionalInsert(e); + auto result = target[0 .. heap.length]; + if (sorted == Yes.sortOutput) + { + while (!heap.empty) heap.removeFront(); + } + return result; +} + +/// +@system unittest +{ + import std.typecons : Yes; + + int[] a = [ 10, 16, 2, 3, 1, 5, 0 ]; + int[] b = new int[3]; + topNCopy(a, b, Yes.sortOutput); + assert(b == [ 0, 1, 2 ]); +} + +@system unittest +{ + import std.random : Random, unpredictableSeed, uniform, randomShuffle; + import std.typecons : Yes; + + auto r = Random(unpredictableSeed); + ptrdiff_t[] a = new ptrdiff_t[uniform(1, 1000, r)]; + foreach (i, ref e; a) e = i; + randomShuffle(a, r); + auto n = uniform(0, a.length, r); + ptrdiff_t[] b = new ptrdiff_t[n]; + topNCopy!(binaryFun!("a < b"))(a, b, Yes.sortOutput); + assert(isSorted!(binaryFun!("a < b"))(b)); +} + +/** +Given a range of elements, constructs an index of its top $(I n) elements +(i.e., the first $(I n) elements if the range were sorted). + +Similar to $(LREF topN), except that the range is not modified. + +Params: + less = A binary predicate that defines the ordering of range elements. + Defaults to $(D a < b). + ss = $(RED (Not implemented yet.)) Specify the swapping strategy. + r = A + $(REF_ALTTEXT random-access range, isRandomAccessRange, std,range,primitives) + of elements to make an index for. + index = A + $(REF_ALTTEXT random-access range, isRandomAccessRange, std,range,primitives) + with assignable elements to build the index in. The length of this range + determines how many top elements to index in $(D r). + + This index range can either have integral elements, in which case the + constructed index will consist of zero-based numerical indices into + $(D r); or it can have pointers to the element type of $(D r), in which + case the constructed index will be pointers to the top elements in + $(D r). + sorted = Determines whether to sort the index by the elements they refer + to. + +See_also: $(LREF topN), $(LREF topNCopy). + +BUGS: +The swapping strategy parameter is not implemented yet; currently it is +ignored. +*/ +void topNIndex(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, + Range, RangeIndex) + (Range r, RangeIndex index, SortOutput sorted = No.sortOutput) +if (isRandomAccessRange!Range && + isRandomAccessRange!RangeIndex && + hasAssignableElements!RangeIndex) +{ + static assert(ss == SwapStrategy.unstable, + "Stable swap strategy not implemented yet."); + + import std.container.binaryheap : BinaryHeap; + if (index.empty) return; + + static if (isIntegral!(ElementType!(RangeIndex))) + { + import std.exception : enforce; + + enforce(ElementType!(RangeIndex).max >= index.length, + "Index type too small"); + bool indirectLess(ElementType!(RangeIndex) a, ElementType!(RangeIndex) b) + { + return binaryFun!(less)(r[a], r[b]); + } + auto heap = BinaryHeap!(RangeIndex, indirectLess)(index, 0); + foreach (i; 0 .. r.length) + { + heap.conditionalInsert(cast(ElementType!RangeIndex) i); + } + + } + else static if (is(ElementType!(RangeIndex) == ElementType!(Range)*)) + { + static bool indirectLess(const ElementType!(RangeIndex) a, + const ElementType!(RangeIndex) b) + { + return binaryFun!less(*a, *b); + } + auto heap = BinaryHeap!(RangeIndex, indirectLess)(index, 0); + foreach (i; 0 .. r.length) + { + heap.conditionalInsert(&r[i]); + } + } + else static assert(0, "Invalid ElementType"); + + if (sorted == Yes.sortOutput) + { + while (!heap.empty) heap.removeFront(); + } +} + +/// +@system unittest +{ + import std.typecons : Yes; + + // Construct index to top 3 elements using numerical indices: + int[] a = [ 10, 2, 7, 5, 8, 1 ]; + int[] index = new int[3]; + topNIndex(a, index, Yes.sortOutput); + assert(index == [5, 1, 3]); // because a[5]==1, a[1]==2, a[3]==5 + + // Construct index to top 3 elements using pointer indices: + int*[] ptrIndex = new int*[3]; + topNIndex(a, ptrIndex, Yes.sortOutput); + assert(ptrIndex == [ &a[5], &a[1], &a[3] ]); +} + +@system unittest +{ + import std.conv : text; + + { + int[] a = [ 10, 8, 9, 2, 4, 6, 7, 1, 3, 5 ]; + int*[] b = new int*[5]; + topNIndex!("a > b")(a, b, Yes.sortOutput); + assert(b == [ &a[0], &a[2], &a[1], &a[6], &a[5]]); + } + { + int[] a = [ 10, 8, 9, 2, 4, 6, 7, 1, 3, 5 ]; + auto b = new ubyte[5]; + topNIndex!("a > b")(a, b, Yes.sortOutput); + assert(b == [ cast(ubyte) 0, cast(ubyte) 2, cast(ubyte) 1, cast(ubyte) 6, cast(ubyte) 5], text(b)); + } +} + +// medianOf +/* +Private for the time being. + +Computes the median of 2 to 5 arbitrary indexes in random-access range `r` +using hand-written specialized algorithms. The indexes must be distinct (if not, +behavior is implementation-defined). The function also partitions the elements +involved around the median, e.g. $(D medianOf(r, a, b, c)) not only fills `r[b]` +with the median of `r[a]`, `r[b]`, and `r[c]`, but also puts the minimum in +`r[a]` and the maximum in `r[c]`. + +Params: +less = The comparison predicate used, modeled as a + $(LINK2 https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings, strict weak ordering) + (irreflexive, antisymmetric, transitive, and implying a transitive equivalence). +flag = Used only for even values of `T.length`. If `No.leanRight`, the median +"leans left", meaning $(D medianOf(r, a, b, c, d)) puts the lower median of the +four in `r[b]`, the minimum in `r[a]`, and the two others in `r[c]` and `r[d]`. +Conversely, $(D median!("a < b", Yes.leanRight)(r, a, b, c, d)) puts the upper +median of the four in `r[c]`, the maximum in `r[d]`, and the two others in +`r[a]` and `r[b]`. +r = The range containing the indexes. +i = Two to five indexes inside `r`. +*/ +private void medianOf( + alias less = "a < b", + Flag!"leanRight" flag = No.leanRight, + Range, + Indexes...) + (Range r, Indexes i) +if (isRandomAccessRange!Range && hasLength!Range && + Indexes.length >= 2 && Indexes.length <= 5 && + allSatisfy!(isUnsigned, Indexes)) +{ + assert(r.length >= Indexes.length); + import std.functional : binaryFun; + alias lt = binaryFun!less; + enum k = Indexes.length; + import std.algorithm.mutation : swapAt; + + alias a = i[0]; + static assert(is(typeof(a) == size_t)); + static if (k >= 2) + { + alias b = i[1]; + static assert(is(typeof(b) == size_t)); + assert(a != b); + } + static if (k >= 3) + { + alias c = i[2]; + static assert(is(typeof(c) == size_t)); + assert(a != c && b != c); + } + static if (k >= 4) + { + alias d = i[3]; + static assert(is(typeof(d) == size_t)); + assert(a != d && b != d && c != d); + } + static if (k >= 5) + { + alias e = i[4]; + static assert(is(typeof(e) == size_t)); + assert(a != e && b != e && c != e && d != e); + } + + static if (k == 2) + { + if (lt(r[b], r[a])) r.swapAt(a, b); + } + else static if (k == 3) + { + if (lt(r[c], r[a])) // c < a + { + if (lt(r[a], r[b])) // c < a < b + { + r.swapAt(a, b); + r.swapAt(a, c); + } + else // c < a, b <= a + { + r.swapAt(a, c); + if (lt(r[b], r[a])) r.swapAt(a, b); + } + } + else // a <= c + { + if (lt(r[b], r[a])) // b < a <= c + { + r.swapAt(a, b); + } + else // a <= c, a <= b + { + if (lt(r[c], r[b])) r.swapAt(b, c); + } + } + assert(!lt(r[b], r[a])); + assert(!lt(r[c], r[b])); + } + else static if (k == 4) + { + static if (flag == No.leanRight) + { + // Eliminate the rightmost from the competition + if (lt(r[d], r[c])) r.swapAt(c, d); // c <= d + if (lt(r[d], r[b])) r.swapAt(b, d); // b <= d + medianOf!lt(r, a, b, c); + } + else + { + // Eliminate the leftmost from the competition + if (lt(r[b], r[a])) r.swapAt(a, b); // a <= b + if (lt(r[c], r[a])) r.swapAt(a, c); // a <= c + medianOf!lt(r, b, c, d); + } + } + else static if (k == 5) + { + // Credit: Teppo Niinimäki + version (unittest) scope(success) + { + assert(!lt(r[c], r[a])); + assert(!lt(r[c], r[b])); + assert(!lt(r[d], r[c])); + assert(!lt(r[e], r[c])); + } + + if (lt(r[c], r[a])) r.swapAt(a, c); + if (lt(r[d], r[b])) r.swapAt(b, d); + if (lt(r[d], r[c])) + { + r.swapAt(c, d); + r.swapAt(a, b); + } + if (lt(r[e], r[b])) r.swapAt(b, e); + if (lt(r[e], r[c])) + { + r.swapAt(c, e); + if (lt(r[c], r[a])) r.swapAt(a, c); + } + else + { + if (lt(r[c], r[b])) r.swapAt(b, c); + } + } +} + +@safe unittest +{ + // Verify medianOf for all permutations of [1, 2, 2, 3, 4]. + int[5] data = [1, 2, 2, 3, 4]; + do + { + int[5] a = data; + medianOf(a[], size_t(0), size_t(1)); + assert(a[0] <= a[1]); + + a[] = data[]; + medianOf(a[], size_t(0), size_t(1), size_t(2)); + assert(ordered(a[0], a[1], a[2])); + + a[] = data[]; + medianOf(a[], size_t(0), size_t(1), size_t(2), size_t(3)); + assert(a[0] <= a[1] && a[1] <= a[2] && a[1] <= a[3]); + + a[] = data[]; + medianOf!("a < b", Yes.leanRight)(a[], size_t(0), size_t(1), + size_t(2), size_t(3)); + assert(a[0] <= a[2] && a[1] <= a[2] && a[2] <= a[3]); + + a[] = data[]; + medianOf(a[], size_t(0), size_t(1), size_t(2), size_t(3), size_t(4)); + assert(a[0] <= a[2] && a[1] <= a[2] && a[2] <= a[3] && a[2] <= a[4]); + } + while (nextPermutation(data[])); +} + +// nextPermutation +/** + * Permutes $(D range) in-place to the next lexicographically greater + * permutation. + * + * The predicate $(D less) defines the lexicographical ordering to be used on + * the range. + * + * If the range is currently the lexicographically greatest permutation, it is + * permuted back to the least permutation and false is returned. Otherwise, + * true is returned. One can thus generate all permutations of a range by + * sorting it according to $(D less), which produces the lexicographically + * least permutation, and then calling nextPermutation until it returns false. + * This is guaranteed to generate all distinct permutations of the range + * exactly once. If there are $(I N) elements in the range and all of them are + * unique, then $(I N)! permutations will be generated. Otherwise, if there are + * some duplicated elements, fewer permutations will be produced. +---- +// Enumerate all permutations +int[] a = [1,2,3,4,5]; +do +{ + // use the current permutation and + // proceed to the next permutation of the array. +} while (nextPermutation(a)); +---- + * Params: + * less = The ordering to be used to determine lexicographical ordering of the + * permutations. + * range = The range to permute. + * + * Returns: false if the range was lexicographically the greatest, in which + * case the range is reversed back to the lexicographically smallest + * permutation; otherwise returns true. + * See_Also: + * $(REF permutations, std,algorithm,iteration). + */ +bool nextPermutation(alias less="a < b", BidirectionalRange) + (BidirectionalRange range) +if (isBidirectionalRange!BidirectionalRange && + hasSwappableElements!BidirectionalRange) +{ + import std.algorithm.mutation : reverse, swap; + import std.algorithm.searching : find; + import std.range : retro, takeExactly; + // Ranges of 0 or 1 element have no distinct permutations. + if (range.empty) return false; + + auto i = retro(range); + auto last = i.save; + + // Find last occurring increasing pair of elements + size_t n = 1; + for (i.popFront(); !i.empty; i.popFront(), last.popFront(), n++) + { + if (binaryFun!less(i.front, last.front)) + break; + } + + if (i.empty) + { + // Entire range is decreasing: it's lexicographically the greatest. So + // wrap it around. + range.reverse(); + return false; + } + + // Find last element greater than i.front. + auto j = find!((a) => binaryFun!less(i.front, a))( + takeExactly(retro(range), n)); + + assert(!j.empty); // shouldn't happen since i.front < last.front + swap(i.front, j.front); + reverse(takeExactly(retro(range), n)); + + return true; +} + +/// +@safe unittest +{ + // Step through all permutations of a sorted array in lexicographic order + int[] a = [1,2,3]; + assert(nextPermutation(a) == true); + assert(a == [1,3,2]); + assert(nextPermutation(a) == true); + assert(a == [2,1,3]); + assert(nextPermutation(a) == true); + assert(a == [2,3,1]); + assert(nextPermutation(a) == true); + assert(a == [3,1,2]); + assert(nextPermutation(a) == true); + assert(a == [3,2,1]); + assert(nextPermutation(a) == false); + assert(a == [1,2,3]); +} + +/// +@safe unittest +{ + // Step through permutations of an array containing duplicate elements: + int[] a = [1,1,2]; + assert(nextPermutation(a) == true); + assert(a == [1,2,1]); + assert(nextPermutation(a) == true); + assert(a == [2,1,1]); + assert(nextPermutation(a) == false); + assert(a == [1,1,2]); +} + +@safe unittest +{ + // Boundary cases: arrays of 0 or 1 element. + int[] a1 = []; + assert(!nextPermutation(a1)); + assert(a1 == []); + + int[] a2 = [1]; + assert(!nextPermutation(a2)); + assert(a2 == [1]); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto a1 = [1, 2, 3, 4]; + + assert(nextPermutation(a1)); + assert(equal(a1, [1, 2, 4, 3])); + + assert(nextPermutation(a1)); + assert(equal(a1, [1, 3, 2, 4])); + + assert(nextPermutation(a1)); + assert(equal(a1, [1, 3, 4, 2])); + + assert(nextPermutation(a1)); + assert(equal(a1, [1, 4, 2, 3])); + + assert(nextPermutation(a1)); + assert(equal(a1, [1, 4, 3, 2])); + + assert(nextPermutation(a1)); + assert(equal(a1, [2, 1, 3, 4])); + + assert(nextPermutation(a1)); + assert(equal(a1, [2, 1, 4, 3])); + + assert(nextPermutation(a1)); + assert(equal(a1, [2, 3, 1, 4])); + + assert(nextPermutation(a1)); + assert(equal(a1, [2, 3, 4, 1])); + + assert(nextPermutation(a1)); + assert(equal(a1, [2, 4, 1, 3])); + + assert(nextPermutation(a1)); + assert(equal(a1, [2, 4, 3, 1])); + + assert(nextPermutation(a1)); + assert(equal(a1, [3, 1, 2, 4])); + + assert(nextPermutation(a1)); + assert(equal(a1, [3, 1, 4, 2])); + + assert(nextPermutation(a1)); + assert(equal(a1, [3, 2, 1, 4])); + + assert(nextPermutation(a1)); + assert(equal(a1, [3, 2, 4, 1])); + + assert(nextPermutation(a1)); + assert(equal(a1, [3, 4, 1, 2])); + + assert(nextPermutation(a1)); + assert(equal(a1, [3, 4, 2, 1])); + + assert(nextPermutation(a1)); + assert(equal(a1, [4, 1, 2, 3])); + + assert(nextPermutation(a1)); + assert(equal(a1, [4, 1, 3, 2])); + + assert(nextPermutation(a1)); + assert(equal(a1, [4, 2, 1, 3])); + + assert(nextPermutation(a1)); + assert(equal(a1, [4, 2, 3, 1])); + + assert(nextPermutation(a1)); + assert(equal(a1, [4, 3, 1, 2])); + + assert(nextPermutation(a1)); + assert(equal(a1, [4, 3, 2, 1])); + + assert(!nextPermutation(a1)); + assert(equal(a1, [1, 2, 3, 4])); +} + +@safe unittest +{ + // Test with non-default sorting order + int[] a = [3,2,1]; + assert(nextPermutation!"a > b"(a) == true); + assert(a == [3,1,2]); + assert(nextPermutation!"a > b"(a) == true); + assert(a == [2,3,1]); + assert(nextPermutation!"a > b"(a) == true); + assert(a == [2,1,3]); + assert(nextPermutation!"a > b"(a) == true); + assert(a == [1,3,2]); + assert(nextPermutation!"a > b"(a) == true); + assert(a == [1,2,3]); + assert(nextPermutation!"a > b"(a) == false); + assert(a == [3,2,1]); +} + +// Issue 13594 +@safe unittest +{ + int[3] a = [1,2,3]; + assert(nextPermutation(a[])); + assert(a == [1,3,2]); +} + +// nextEvenPermutation +/** + * Permutes $(D range) in-place to the next lexicographically greater $(I even) + * permutation. + * + * The predicate $(D less) defines the lexicographical ordering to be used on + * the range. + * + * An even permutation is one which is produced by swapping an even number of + * pairs of elements in the original range. The set of $(I even) permutations + * is distinct from the set of $(I all) permutations only when there are no + * duplicate elements in the range. If the range has $(I N) unique elements, + * then there are exactly $(I N)!/2 even permutations. + * + * If the range is already the lexicographically greatest even permutation, it + * is permuted back to the least even permutation and false is returned. + * Otherwise, true is returned, and the range is modified in-place to be the + * lexicographically next even permutation. + * + * One can thus generate the even permutations of a range with unique elements + * by starting with the lexicographically smallest permutation, and repeatedly + * calling nextEvenPermutation until it returns false. +---- +// Enumerate even permutations +int[] a = [1,2,3,4,5]; +do +{ + // use the current permutation and + // proceed to the next even permutation of the array. +} while (nextEvenPermutation(a)); +---- + * One can also generate the $(I odd) permutations of a range by noting that + * permutations obey the rule that even + even = even, and odd + even = odd. + * Thus, by swapping the last two elements of a lexicographically least range, + * it is turned into the first odd permutation. Then calling + * nextEvenPermutation on this first odd permutation will generate the next + * even permutation relative to this odd permutation, which is actually the + * next odd permutation of the original range. Thus, by repeatedly calling + * nextEvenPermutation until it returns false, one enumerates the odd + * permutations of the original range. +---- +// Enumerate odd permutations +int[] a = [1,2,3,4,5]; +swap(a[$-2], a[$-1]); // a is now the first odd permutation of [1,2,3,4,5] +do +{ + // use the current permutation and + // proceed to the next odd permutation of the original array + // (which is an even permutation of the first odd permutation). +} while (nextEvenPermutation(a)); +---- + * + * Warning: Since even permutations are only distinct from all permutations + * when the range elements are unique, this function assumes that there are no + * duplicate elements under the specified ordering. If this is not _true, some + * permutations may fail to be generated. When the range has non-unique + * elements, you should use $(MYREF nextPermutation) instead. + * + * Params: + * less = The ordering to be used to determine lexicographical ordering of the + * permutations. + * range = The range to permute. + * + * Returns: false if the range was lexicographically the greatest, in which + * case the range is reversed back to the lexicographically smallest + * permutation; otherwise returns true. + */ +bool nextEvenPermutation(alias less="a < b", BidirectionalRange) + (BidirectionalRange range) +if (isBidirectionalRange!BidirectionalRange && + hasSwappableElements!BidirectionalRange) +{ + import std.algorithm.mutation : reverse, swap; + import std.algorithm.searching : find; + import std.range : retro, takeExactly; + // Ranges of 0 or 1 element have no distinct permutations. + if (range.empty) return false; + + bool oddParity = false; + bool ret = true; + do + { + auto i = retro(range); + auto last = i.save; + + // Find last occurring increasing pair of elements + size_t n = 1; + for (i.popFront(); !i.empty; + i.popFront(), last.popFront(), n++) + { + if (binaryFun!less(i.front, last.front)) + break; + } + + if (!i.empty) + { + // Find last element greater than i.front. + auto j = find!((a) => binaryFun!less(i.front, a))( + takeExactly(retro(range), n)); + + // shouldn't happen since i.front < last.front + assert(!j.empty); + + swap(i.front, j.front); + oddParity = !oddParity; + } + else + { + // Entire range is decreasing: it's lexicographically + // the greatest. + ret = false; + } + + reverse(takeExactly(retro(range), n)); + if ((n / 2) % 2 == 1) + oddParity = !oddParity; + } while (oddParity); + + return ret; +} + +/// +@safe unittest +{ + // Step through even permutations of a sorted array in lexicographic order + int[] a = [1,2,3]; + assert(nextEvenPermutation(a) == true); + assert(a == [2,3,1]); + assert(nextEvenPermutation(a) == true); + assert(a == [3,1,2]); + assert(nextEvenPermutation(a) == false); + assert(a == [1,2,3]); +} + +@safe unittest +{ + auto a3 = [ 1, 2, 3, 4 ]; + int count = 1; + while (nextEvenPermutation(a3)) count++; + assert(count == 12); +} + +@safe unittest +{ + // Test with non-default sorting order + auto a = [ 3, 2, 1 ]; + + assert(nextEvenPermutation!"a > b"(a) == true); + assert(a == [ 2, 1, 3 ]); + assert(nextEvenPermutation!"a > b"(a) == true); + assert(a == [ 1, 3, 2 ]); + assert(nextEvenPermutation!"a > b"(a) == false); + assert(a == [ 3, 2, 1 ]); +} + +@safe unittest +{ + // Test various cases of rollover + auto a = [ 3, 1, 2 ]; + assert(nextEvenPermutation(a) == false); + assert(a == [ 1, 2, 3 ]); + + auto b = [ 3, 2, 1 ]; + assert(nextEvenPermutation(b) == false); + assert(b == [ 1, 3, 2 ]); +} + +@safe unittest +{ + // Issue 13594 + int[3] a = [1,2,3]; + assert(nextEvenPermutation(a[])); + assert(a == [2,3,1]); +} + +/** +Even permutations are useful for generating coordinates of certain geometric +shapes. Here's a non-trivial example: +*/ +@safe unittest +{ + import std.math : sqrt; + + // Print the 60 vertices of a uniform truncated icosahedron (soccer ball) + enum real Phi = (1.0 + sqrt(5.0)) / 2.0; // Golden ratio + real[][] seeds = [ + [0.0, 1.0, 3.0*Phi], + [1.0, 2.0+Phi, 2.0*Phi], + [Phi, 2.0, Phi^^3] + ]; + size_t n; + foreach (seed; seeds) + { + // Loop over even permutations of each seed + do + { + // Loop over all sign changes of each permutation + size_t i; + do + { + // Generate all possible sign changes + for (i=0; i < seed.length; i++) + { + if (seed[i] != 0.0) + { + seed[i] = -seed[i]; + if (seed[i] < 0.0) + break; + } + } + n++; + } while (i < seed.length); + } while (nextEvenPermutation(seed)); + } + assert(n == 60); +} diff --git a/libphobos/src/std/array.d b/libphobos/src/std/array.d new file mode 100644 index 0000000..13677a3 --- /dev/null +++ b/libphobos/src/std/array.d @@ -0,0 +1,3775 @@ +// Written in the D programming language. +/** +Functions and types that manipulate built-in arrays and associative arrays. + +This module provides all kinds of functions to create, manipulate or convert arrays: + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE , +$(TR $(TH Function Name) $(TH Description) +) + $(TR $(TD $(LREF _array)) + $(TD Returns a copy of the input in a newly allocated dynamic _array. + )) + $(TR $(TD $(LREF appender)) + $(TD Returns a new $(LREF Appender) or $(LREF RefAppender) initialized with a given _array. + )) + $(TR $(TD $(LREF assocArray)) + $(TD Returns a newly allocated associative _array from a range of key/value tuples. + )) + $(TR $(TD $(LREF byPair)) + $(TD Construct a range iterating over an associative _array by key/value tuples. + )) + $(TR $(TD $(LREF insertInPlace)) + $(TD Inserts into an existing _array at a given position. + )) + $(TR $(TD $(LREF join)) + $(TD Concatenates a range of ranges into one _array. + )) + $(TR $(TD $(LREF minimallyInitializedArray)) + $(TD Returns a new _array of type $(D T). + )) + $(TR $(TD $(LREF replace)) + $(TD Returns a new _array with all occurrences of a certain subrange replaced. + )) + $(TR $(TD $(LREF replaceFirst)) + $(TD Returns a new _array with the first occurrence of a certain subrange replaced. + )) + $(TR $(TD $(LREF replaceInPlace)) + $(TD Replaces all occurrences of a certain subrange and puts the result into a given _array. + )) + $(TR $(TD $(LREF replaceInto)) + $(TD Replaces all occurrences of a certain subrange and puts the result into an output range. + )) + $(TR $(TD $(LREF replaceLast)) + $(TD Returns a new _array with the last occurrence of a certain subrange replaced. + )) + $(TR $(TD $(LREF replaceSlice)) + $(TD Returns a new _array with a given slice replaced. + )) + $(TR $(TD $(LREF replicate)) + $(TD Creates a new _array out of several copies of an input _array or range. + )) + $(TR $(TD $(LREF sameHead)) + $(TD Checks if the initial segments of two arrays refer to the same + place in memory. + )) + $(TR $(TD $(LREF sameTail)) + $(TD Checks if the final segments of two arrays refer to the same place + in memory. + )) + $(TR $(TD $(LREF split)) + $(TD Eagerly split a range or string into an _array. + )) + $(TR $(TD $(LREF uninitializedArray)) + $(TD Returns a new _array of type $(D T) without initializing its elements. + )) +) + +Copyright: Copyright Andrei Alexandrescu 2008- and Jonathan M Davis 2011-. + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(HTTP erdani.org, Andrei Alexandrescu) and Jonathan M Davis + +Source: $(PHOBOSSRC std/_array.d) +*/ +module std.array; + +static import std.algorithm.iteration; // FIXME, remove with alias of splitter +import std.functional; +import std.meta; +import std.traits; + +import std.range.primitives; +public import std.range.primitives : save, empty, popFront, popBack, front, back; + +/** + * Allocates an array and initializes it with copies of the elements + * of range $(D r). + * + * Narrow strings are handled as a special case in an overload. + * + * Params: + * r = range (or aggregate with $(D opApply) function) whose elements are copied into the allocated array + * Returns: + * allocated and initialized array + */ +ForeachType!Range[] array(Range)(Range r) +if (isIterable!Range && !isNarrowString!Range && !isInfinite!Range) +{ + if (__ctfe) + { + // Compile-time version to avoid memcpy calls. + // Also used to infer attributes of array(). + typeof(return) result; + foreach (e; r) + result ~= e; + return result; + } + + alias E = ForeachType!Range; + static if (hasLength!Range) + { + auto length = r.length; + if (length == 0) + return null; + + import std.conv : emplaceRef; + + auto result = (() @trusted => uninitializedArray!(Unqual!E[])(length))(); + + // Every element of the uninitialized array must be initialized + size_t i; + foreach (e; r) + { + emplaceRef!E(result[i], e); + ++i; + } + return (() @trusted => cast(E[]) result)(); + } + else + { + auto a = appender!(E[])(); + foreach (e; r) + { + a.put(e); + } + return a.data; + } +} + +/// +@safe pure nothrow unittest +{ + auto a = array([1, 2, 3, 4, 5][]); + assert(a == [ 1, 2, 3, 4, 5 ]); +} + +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + struct Foo + { + int a; + } + auto a = array([Foo(1), Foo(2), Foo(3), Foo(4), Foo(5)][]); + assert(equal(a, [Foo(1), Foo(2), Foo(3), Foo(4), Foo(5)])); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + struct Foo + { + int a; + void opAssign(Foo foo) + { + assert(0); + } + auto opEquals(Foo foo) + { + return a == foo.a; + } + } + auto a = array([Foo(1), Foo(2), Foo(3), Foo(4), Foo(5)][]); + assert(equal(a, [Foo(1), Foo(2), Foo(3), Foo(4), Foo(5)])); +} + +@safe unittest +{ + // Issue 12315 + static struct Bug12315 { immutable int i; } + enum bug12315 = [Bug12315(123456789)].array(); + static assert(bug12315[0].i == 123456789); +} + +@safe unittest +{ + import std.range; + static struct S{int* p;} + auto a = array(immutable(S).init.repeat(5)); +} + +/** +Convert a narrow string to an array type that fully supports random access. +This is handled as a special case and always returns an array of `dchar` + +Params: + str = `isNarrowString` to be converted to an array of `dchar` +Returns: + a $(D dchar[]), $(D const(dchar)[]), or $(D immutable(dchar)[]) depending on the constness of + the input. +*/ +@trusted ElementType!String[] array(String)(scope String str) +if (isNarrowString!String) +{ + import std.utf : toUTF32; + /* This function is @trusted because the following cast + * is unsafe - converting a dstring[] to a dchar[], for example. + * Our special knowledge of toUTF32 is needed to know the cast is safe. + */ + return cast(typeof(return)) str.toUTF32; +} + +/// +@safe unittest +{ + import std.range.primitives : isRandomAccessRange; + + assert("Hello D".array == "Hello D"d); + static assert(isRandomAccessRange!string == false); + + assert("Hello D"w.array == "Hello D"d); + static assert(isRandomAccessRange!dstring == true); +} + +@system unittest +{ + // @system due to array!string + import std.conv : to; + + static struct TestArray { int x; string toString() @safe { return to!string(x); } } + + static struct OpAssign + { + uint num; + this(uint num) { this.num = num; } + + // Templating opAssign to make sure the bugs with opAssign being + // templated are fixed. + void opAssign(T)(T rhs) { this.num = rhs.num; } + } + + static struct OpApply + { + int opApply(scope int delegate(ref int) dg) + { + int res; + foreach (i; 0 .. 10) + { + res = dg(i); + if (res) break; + } + + return res; + } + } + + auto a = array([1, 2, 3, 4, 5][]); + //writeln(a); + assert(a == [ 1, 2, 3, 4, 5 ]); + + auto b = array([TestArray(1), TestArray(2)][]); + //writeln(b); + + class C + { + int x; + this(int y) { x = y; } + override string toString() const @safe { return to!string(x); } + } + auto c = array([new C(1), new C(2)][]); + //writeln(c); + + auto d = array([1.0, 2.2, 3][]); + assert(is(typeof(d) == double[])); + //writeln(d); + + auto e = [OpAssign(1), OpAssign(2)]; + auto f = array(e); + assert(e == f); + + assert(array(OpApply.init) == [0,1,2,3,4,5,6,7,8,9]); + assert(array("ABC") == "ABC"d); + assert(array("ABC".dup) == "ABC"d.dup); +} + +//Bug# 8233 +@safe unittest +{ + assert(array("hello world"d) == "hello world"d); + immutable a = [1, 2, 3, 4, 5]; + assert(array(a) == a); + const b = a; + assert(array(b) == a); + + //To verify that the opAssign branch doesn't get screwed up by using Unqual. + //EDIT: array no longer calls opAssign. + struct S + { + ref S opAssign(S)(const ref S rhs) + { + assert(0); + } + + int i; + } + + foreach (T; AliasSeq!(S, const S, immutable S)) + { + auto arr = [T(1), T(2), T(3), T(4)]; + assert(array(arr) == arr); + } +} + +@safe unittest +{ + //9824 + static struct S + { + @disable void opAssign(S); + int i; + } + auto arr = [S(0), S(1), S(2)]; + arr.array(); +} + +// Bugzilla 10220 +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.exception; + import std.range : repeat; + + static struct S + { + int val; + + @disable this(); + this(int v) { val = v; } + } + assertCTFEable!( + { + auto r = S(1).repeat(2).array(); + assert(equal(r, [S(1), S(1)])); + }); +} + +@safe unittest +{ + //Turn down infinity: + static assert(!is(typeof( + repeat(1).array() + ))); +} + +/** +Returns a newly allocated associative _array from a range of key/value tuples. + +Params: + r = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + of tuples of keys and values. +Returns: A newly allocated associative array out of elements of the input +range, which must be a range of tuples (Key, Value). Returns a null associative +array reference when given an empty range. +Duplicates: Associative arrays have unique keys. If r contains duplicate keys, +then the result will contain the value of the last pair for that key in r. + +See_Also: $(REF Tuple, std,typecons), $(REF zip, std,range) + */ + +auto assocArray(Range)(Range r) +if (isInputRange!Range) +{ + import std.typecons : isTuple; + + alias E = ElementType!Range; + static assert(isTuple!E, "assocArray: argument must be a range of tuples"); + static assert(E.length == 2, "assocArray: tuple dimension must be 2"); + alias KeyType = E.Types[0]; + alias ValueType = E.Types[1]; + static assert(isMutable!ValueType, "assocArray: value type must be mutable"); + + ValueType[KeyType] aa; + foreach (t; r) + aa[t[0]] = t[1]; + return aa; +} + +/// +@safe pure /*nothrow*/ unittest +{ + import std.range; + import std.typecons; + auto a = assocArray(zip([0, 1, 2], ["a", "b", "c"])); // aka zipMap + assert(is(typeof(a) == string[int])); + assert(a == [0:"a", 1:"b", 2:"c"]); + + auto b = assocArray([ tuple("foo", "bar"), tuple("baz", "quux") ]); + assert(is(typeof(b) == string[string])); + assert(b == ["foo":"bar", "baz":"quux"]); +} + +// @@@11053@@@ - Cannot be version (unittest) - recursive instantiation error +@safe unittest +{ + import std.typecons; + static assert(!__traits(compiles, [ tuple("foo", "bar", "baz") ].assocArray())); + static assert(!__traits(compiles, [ tuple("foo") ].assocArray())); + assert([ tuple("foo", "bar") ].assocArray() == ["foo": "bar"]); +} + +// Issue 13909 +@safe unittest +{ + import std.typecons; + auto a = [tuple!(const string, string)("foo", "bar")]; + auto b = [tuple!(string, const string)("foo", "bar")]; + assert(assocArray(a) == [cast(const(string)) "foo": "bar"]); + static assert(!__traits(compiles, assocArray(b))); +} + +/** +Construct a range iterating over an associative array by key/value tuples. + +Params: aa = The associative array to iterate over. + +Returns: A $(REF_ALTTEXT forward range, isForwardRange, std,_range,primitives) +of Tuple's of key and value pairs from the given associative array. +*/ +auto byPair(AA : Value[Key], Value, Key)(AA aa) +{ + import std.algorithm.iteration : map; + import std.typecons : tuple; + + return aa.byKeyValue.map!(pair => tuple(pair.key, pair.value)); +} + +/// +@system unittest +{ + import std.algorithm.sorting : sort; + import std.typecons : tuple, Tuple; + + auto aa = ["a": 1, "b": 2, "c": 3]; + Tuple!(string, int)[] pairs; + + // Iteration over key/value pairs. + foreach (pair; aa.byPair) + { + pairs ~= pair; + } + + // Iteration order is implementation-dependent, so we should sort it to get + // a fixed order. + sort(pairs); + assert(pairs == [ + tuple("a", 1), + tuple("b", 2), + tuple("c", 3) + ]); +} + +@system unittest +{ + import std.typecons : tuple, Tuple; + + auto aa = ["a":2]; + auto pairs = aa.byPair(); + + static assert(is(typeof(pairs.front) == Tuple!(string,int))); + static assert(isForwardRange!(typeof(pairs))); + + assert(!pairs.empty); + assert(pairs.front == tuple("a", 2)); + + auto savedPairs = pairs.save; + + pairs.popFront(); + assert(pairs.empty); + assert(!savedPairs.empty); + assert(savedPairs.front == tuple("a", 2)); +} + +// Issue 17711 +@system unittest +{ + const(int[string]) aa = [ "abc": 123 ]; + + // Ensure that byKeyValue is usable with a const AA. + auto kv = aa.byKeyValue; + assert(!kv.empty); + assert(kv.front.key == "abc" && kv.front.value == 123); + kv.popFront(); + assert(kv.empty); + + // Ensure byPair is instantiable with const AA. + auto r = aa.byPair; + static assert(isInputRange!(typeof(r))); + assert(!r.empty && r.front[0] == "abc" && r.front[1] == 123); + r.popFront(); + assert(r.empty); +} + +private template blockAttribute(T) +{ + import core.memory; + static if (hasIndirections!(T) || is(T == void)) + { + enum blockAttribute = 0; + } + else + { + enum blockAttribute = GC.BlkAttr.NO_SCAN; + } +} +version (unittest) +{ + import core.memory : UGC = GC; + static assert(!(blockAttribute!void & UGC.BlkAttr.NO_SCAN)); +} + +// Returns the number of dimensions in an array T. +private template nDimensions(T) +{ + static if (isArray!T) + { + enum nDimensions = 1 + nDimensions!(typeof(T.init[0])); + } + else + { + enum nDimensions = 0; + } +} + +version (unittest) +{ + static assert(nDimensions!(uint[]) == 1); + static assert(nDimensions!(float[][]) == 2); +} + +/++ +Returns a new array of type $(D T) allocated on the garbage collected heap +without initializing its elements. This can be a useful optimization if every +element will be immediately initialized. $(D T) may be a multidimensional +array. In this case sizes may be specified for any number of dimensions from 0 +to the number in $(D T). + +uninitializedArray is nothrow and weakly pure. + +uninitializedArray is @system if the uninitialized element type has pointers. ++/ +auto uninitializedArray(T, I...)(I sizes) nothrow @system +if (isDynamicArray!T && allSatisfy!(isIntegral, I) && hasIndirections!(ElementEncodingType!T)) +{ + enum isSize_t(E) = is (E : size_t); + alias toSize_t(E) = size_t; + + static assert(allSatisfy!(isSize_t, I), + "Argument types in "~I.stringof~" are not all convertible to size_t: " + ~Filter!(templateNot!(isSize_t), I).stringof); + + //Eagerlly transform non-size_t into size_t to avoid template bloat + alias ST = staticMap!(toSize_t, I); + + return arrayAllocImpl!(false, T, ST)(sizes); +} + +/// ditto +auto uninitializedArray(T, I...)(I sizes) nothrow @trusted +if (isDynamicArray!T && allSatisfy!(isIntegral, I) && !hasIndirections!(ElementEncodingType!T)) +{ + enum isSize_t(E) = is (E : size_t); + alias toSize_t(E) = size_t; + + static assert(allSatisfy!(isSize_t, I), + "Argument types in "~I.stringof~" are not all convertible to size_t: " + ~Filter!(templateNot!(isSize_t), I).stringof); + + //Eagerlly transform non-size_t into size_t to avoid template bloat + alias ST = staticMap!(toSize_t, I); + + return arrayAllocImpl!(false, T, ST)(sizes); +} +/// +@system nothrow pure unittest +{ + double[] arr = uninitializedArray!(double[])(100); + assert(arr.length == 100); + + double[][] matrix = uninitializedArray!(double[][])(42, 31); + assert(matrix.length == 42); + assert(matrix[0].length == 31); + + char*[] ptrs = uninitializedArray!(char*[])(100); + assert(ptrs.length == 100); +} + +/++ +Returns a new array of type $(D T) allocated on the garbage collected heap. + +Partial initialization is done for types with indirections, for preservation +of memory safety. Note that elements will only be initialized to 0, but not +necessarily the element type's $(D .init). + +minimallyInitializedArray is nothrow and weakly pure. ++/ +auto minimallyInitializedArray(T, I...)(I sizes) nothrow @trusted +if (isDynamicArray!T && allSatisfy!(isIntegral, I)) +{ + enum isSize_t(E) = is (E : size_t); + alias toSize_t(E) = size_t; + + static assert(allSatisfy!(isSize_t, I), + "Argument types in "~I.stringof~" are not all convertible to size_t: " + ~Filter!(templateNot!(isSize_t), I).stringof); + //Eagerlly transform non-size_t into size_t to avoid template bloat + alias ST = staticMap!(toSize_t, I); + + return arrayAllocImpl!(true, T, ST)(sizes); +} + +/// +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.range : repeat; + + auto arr = minimallyInitializedArray!(int[])(42); + assert(arr.length == 42); + // Elements aren't necessarily initialized to 0 + assert(!arr.equal(0.repeat(42))); +} + +@safe pure nothrow unittest +{ + cast(void) minimallyInitializedArray!(int[][][][][])(); + double[] arr = minimallyInitializedArray!(double[])(100); + assert(arr.length == 100); + + double[][] matrix = minimallyInitializedArray!(double[][])(42); + assert(matrix.length == 42); + foreach (elem; matrix) + { + assert(elem.ptr is null); + } +} + +private auto arrayAllocImpl(bool minimallyInitialized, T, I...)(I sizes) nothrow +{ + static assert(I.length <= nDimensions!T, + I.length.stringof~"dimensions specified for a "~nDimensions!T.stringof~" dimensional array."); + + alias E = ElementEncodingType!T; + + E[] ret; + + static if (I.length != 0) + { + static assert(is(I[0] == size_t)); + alias size = sizes[0]; + } + + static if (I.length == 1) + { + if (__ctfe) + { + static if (__traits(compiles, new E[](size))) + ret = new E[](size); + else static if (__traits(compiles, ret ~= E.init)) + { + try + { + //Issue: if E has an impure postblit, then all of arrayAllocImpl + //Will be impure, even during non CTFE. + foreach (i; 0 .. size) + ret ~= E.init; + } + catch (Exception e) + throw new Error(e.msg); + } + else + assert(0, "No postblit nor default init on " ~ E.stringof ~ + ": At least one is required for CTFE."); + } + else + { + import core.memory : GC; + import core.stdc.string : memset; + + import core.checkedint : mulu; + bool overflow; + const nbytes = mulu(size, E.sizeof, overflow); + if (overflow) assert(0); + + auto ptr = cast(E*) GC.malloc(nbytes, blockAttribute!E); + static if (minimallyInitialized && hasIndirections!E) + memset(ptr, 0, nbytes); + ret = ptr[0 .. size]; + } + } + else static if (I.length > 1) + { + ret = arrayAllocImpl!(false, E[])(size); + foreach (ref elem; ret) + elem = arrayAllocImpl!(minimallyInitialized, E)(sizes[1..$]); + } + + return ret; +} + +@safe nothrow pure unittest +{ + auto s1 = uninitializedArray!(int[])(); + auto s2 = minimallyInitializedArray!(int[])(); + assert(s1.length == 0); + assert(s2.length == 0); +} + +@safe nothrow pure unittest //@@@9803@@@ +{ + auto a = minimallyInitializedArray!(int*[])(1); + assert(a[0] == null); + auto b = minimallyInitializedArray!(int[][])(1); + assert(b[0].empty); + auto c = minimallyInitializedArray!(int*[][])(1, 1); + assert(c[0][0] == null); +} + +@safe unittest //@@@10637@@@ +{ + static struct S + { + static struct I{int i; alias i this;} + int* p; + this() @disable; + this(int i) + { + p = &(new I(i)).i; + } + this(this) + { + p = &(new I(*p)).i; + } + ~this() + { + assert(p != null); + } + } + auto a = minimallyInitializedArray!(S[])(1); + assert(a[0].p == null); + enum b = minimallyInitializedArray!(S[])(1); +} + +@safe nothrow unittest +{ + static struct S1 + { + this() @disable; + this(this) @disable; + } + auto a1 = minimallyInitializedArray!(S1[][])(2, 2); + //enum b1 = minimallyInitializedArray!(S1[][])(2, 2); + static struct S2 + { + this() @disable; + //this(this) @disable; + } + auto a2 = minimallyInitializedArray!(S2[][])(2, 2); + enum b2 = minimallyInitializedArray!(S2[][])(2, 2); + static struct S3 + { + //this() @disable; + this(this) @disable; + } + auto a3 = minimallyInitializedArray!(S3[][])(2, 2); + enum b3 = minimallyInitializedArray!(S3[][])(2, 2); +} + +// overlap +/* +NOTE: Undocumented for now, overlap does not yet work with ctfe. +Returns the overlapping portion, if any, of two arrays. Unlike $(D +equal), $(D overlap) only compares the pointers in the ranges, not the +values referred by them. If $(D r1) and $(D r2) have an overlapping +slice, returns that slice. Otherwise, returns the null slice. +*/ +auto overlap(T, U)(T[] r1, U[] r2) @trusted pure nothrow +if (is(typeof(r1.ptr < r2.ptr) == bool)) +{ + import std.algorithm.comparison : min, max; + auto b = max(r1.ptr, r2.ptr); + auto e = min(r1.ptr + r1.length, r2.ptr + r2.length); + return b < e ? b[0 .. e - b] : null; +} + +/// +@safe pure /*nothrow*/ unittest +{ + int[] a = [ 10, 11, 12, 13, 14 ]; + int[] b = a[1 .. 3]; + assert(overlap(a, b) == [ 11, 12 ]); + b = b.dup; + // overlap disappears even though the content is the same + assert(overlap(a, b).empty); +} + +@safe /*nothrow*/ unittest +{ + static void test(L, R)(L l, R r) + { + import std.stdio; + scope(failure) writeln("Types: L %s R %s", L.stringof, R.stringof); + + assert(overlap(l, r) == [ 100, 12 ]); + + assert(overlap(l, l[0 .. 2]) is l[0 .. 2]); + assert(overlap(l, l[3 .. 5]) is l[3 .. 5]); + assert(overlap(l[0 .. 2], l) is l[0 .. 2]); + assert(overlap(l[3 .. 5], l) is l[3 .. 5]); + } + + int[] a = [ 10, 11, 12, 13, 14 ]; + int[] b = a[1 .. 3]; + a[1] = 100; + + immutable int[] c = a.idup; + immutable int[] d = c[1 .. 3]; + + test(a, b); + assert(overlap(a, b.dup).empty); + test(c, d); + assert(overlap(c, d.idup).empty); +} + +@safe pure nothrow unittest // bugzilla 9836 +{ + // range primitives for array should work with alias this types + struct Wrapper + { + int[] data; + alias data this; + + @property Wrapper save() { return this; } + } + auto w = Wrapper([1,2,3,4]); + std.array.popFront(w); // should work + + static assert(isInputRange!Wrapper); + static assert(isForwardRange!Wrapper); + static assert(isBidirectionalRange!Wrapper); + static assert(isRandomAccessRange!Wrapper); +} + +private void copyBackwards(T)(T[] src, T[] dest) +{ + import core.stdc.string : memmove; + + assert(src.length == dest.length); + + if (!__ctfe || hasElaborateCopyConstructor!T) + { + /* insertInPlace relies on dest being uninitialized, so no postblits allowed, + * as this is a MOVE that overwrites the destination, not a COPY. + * BUG: insertInPlace will not work with ctfe and postblits + */ + memmove(dest.ptr, src.ptr, src.length * T.sizeof); + } + else + { + immutable len = src.length; + for (size_t i = len; i-- > 0;) + { + dest[i] = src[i]; + } + } +} + +/++ + Inserts $(D stuff) (which must be an input range or any number of + implicitly convertible items) in $(D array) at position $(D pos). + + Params: + array = The array that $(D stuff) will be inserted into. + pos = The position in $(D array) to insert the $(D stuff). + stuff = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives), + or any number of implicitly convertible items to insert into $(D array). + +/ +void insertInPlace(T, U...)(ref T[] array, size_t pos, U stuff) +if (!isSomeString!(T[]) + && allSatisfy!(isInputRangeOrConvertible!T, U) && U.length > 0) +{ + static if (allSatisfy!(isInputRangeWithLengthOrConvertible!T, U)) + { + import std.conv : emplaceRef; + + immutable oldLen = array.length; + + size_t to_insert = 0; + foreach (i, E; U) + { + static if (is(E : T)) //a single convertible value, not a range + to_insert += 1; + else + to_insert += stuff[i].length; + } + if (to_insert) + { + array.length += to_insert; + + // Takes arguments array, pos, stuff + // Spread apart array[] at pos by moving elements + (() @trusted { copyBackwards(array[pos .. oldLen], array[pos+to_insert..$]); })(); + + // Initialize array[pos .. pos+to_insert] with stuff[] + auto j = 0; + foreach (i, E; U) + { + static if (is(E : T)) + { + emplaceRef!T(array[pos + j++], stuff[i]); + } + else + { + foreach (v; stuff[i]) + { + emplaceRef!T(array[pos + j++], v); + } + } + } + } + } + else + { + // stuff has some InputRanges in it that don't have length + // assume that stuff to be inserted is typically shorter + // then the array that can be arbitrary big + // TODO: needs a better implementation as there is no need to build an _array_ + // a singly-linked list of memory blocks (rope, etc.) will do + auto app = appender!(T[])(); + foreach (i, E; U) + app.put(stuff[i]); + insertInPlace(array, pos, app.data); + } +} + +/// Ditto +void insertInPlace(T, U...)(ref T[] array, size_t pos, U stuff) +if (isSomeString!(T[]) && allSatisfy!(isCharOrStringOrDcharRange, U)) +{ + static if (is(Unqual!T == T) + && allSatisfy!(isInputRangeWithLengthOrConvertible!dchar, U)) + { + import std.utf : codeLength; + // mutable, can do in place + //helper function: re-encode dchar to Ts and store at *ptr + static T* putDChar(T* ptr, dchar ch) + { + static if (is(T == dchar)) + { + *ptr++ = ch; + return ptr; + } + else + { + import std.utf : encode; + T[dchar.sizeof/T.sizeof] buf; + immutable len = encode(buf, ch); + final switch (len) + { + static if (T.sizeof == char.sizeof) + { + case 4: + ptr[3] = buf[3]; + goto case; + case 3: + ptr[2] = buf[2]; + goto case; + } + case 2: + ptr[1] = buf[1]; + goto case; + case 1: + ptr[0] = buf[0]; + } + ptr += len; + return ptr; + } + } + size_t to_insert = 0; + //count up the number of *codeunits* to insert + foreach (i, E; U) + to_insert += codeLength!T(stuff[i]); + array.length += to_insert; + + @trusted static void moveToRight(T[] arr, size_t gap) + { + static assert(!hasElaborateCopyConstructor!T); + import core.stdc.string : memmove; + if (__ctfe) + { + for (size_t i = arr.length - gap; i; --i) + arr[gap + i - 1] = arr[i - 1]; + } + else + memmove(arr.ptr + gap, arr.ptr, (arr.length - gap) * T.sizeof); + } + moveToRight(array[pos .. $], to_insert); + auto ptr = array.ptr + pos; + foreach (i, E; U) + { + static if (is(E : dchar)) + { + ptr = putDChar(ptr, stuff[i]); + } + else + { + foreach (dchar ch; stuff[i]) + ptr = putDChar(ptr, ch); + } + } + assert(ptr == array.ptr + pos + to_insert, "(ptr == array.ptr + pos + to_insert) is false"); + } + else + { + // immutable/const, just construct a new array + auto app = appender!(T[])(); + app.put(array[0 .. pos]); + foreach (i, E; U) + app.put(stuff[i]); + app.put(array[pos..$]); + array = app.data; + } +} + +/// +@safe pure unittest +{ + int[] a = [ 1, 2, 3, 4 ]; + a.insertInPlace(2, [ 1, 2 ]); + assert(a == [ 1, 2, 1, 2, 3, 4 ]); + a.insertInPlace(3, 10u, 11); + assert(a == [ 1, 2, 1, 10, 11, 2, 3, 4]); +} + +//constraint helpers +private template isInputRangeWithLengthOrConvertible(E) +{ + template isInputRangeWithLengthOrConvertible(R) + { + //hasLength not defined for char[], wchar[] and dchar[] + enum isInputRangeWithLengthOrConvertible = + (isInputRange!R && is(typeof(R.init.length)) + && is(ElementType!R : E)) || is(R : E); + } +} + +//ditto +private template isCharOrStringOrDcharRange(T) +{ + enum isCharOrStringOrDcharRange = isSomeString!T || isSomeChar!T || + (isInputRange!T && is(ElementType!T : dchar)); +} + +//ditto +private template isInputRangeOrConvertible(E) +{ + template isInputRangeOrConvertible(R) + { + enum isInputRangeOrConvertible = + (isInputRange!R && is(ElementType!R : E)) || is(R : E); + } +} + +@system unittest +{ + // @system due to insertInPlace + import core.exception; + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filter; + import std.conv : to; + import std.exception; + + + bool test(T, U, V)(T orig, size_t pos, U toInsert, V result, + string file = __FILE__, size_t line = __LINE__) + { + { + static if (is(T == typeof(T.init.dup))) + auto a = orig.dup; + else + auto a = orig.idup; + + a.insertInPlace(pos, toInsert); + if (!equal(a, result)) + return false; + } + + static if (isInputRange!U) + { + orig.insertInPlace(pos, filter!"true"(toInsert)); + return equal(orig, result); + } + else + return true; + } + + + assert(test([1, 2, 3, 4], 0, [6, 7], [6, 7, 1, 2, 3, 4])); + assert(test([1, 2, 3, 4], 2, [8, 9], [1, 2, 8, 9, 3, 4])); + assert(test([1, 2, 3, 4], 4, [10, 11], [1, 2, 3, 4, 10, 11])); + + assert(test([1, 2, 3, 4], 0, 22, [22, 1, 2, 3, 4])); + assert(test([1, 2, 3, 4], 2, 23, [1, 2, 23, 3, 4])); + assert(test([1, 2, 3, 4], 4, 24, [1, 2, 3, 4, 24])); + + void testStr(T, U)(string file = __FILE__, size_t line = __LINE__) + { + + auto l = to!T("hello"); + auto r = to!U(" વિશ્વ"); + + enforce(test(l, 0, r, " વિશ્વhello"), + new AssertError("testStr failure 1", file, line)); + enforce(test(l, 3, r, "hel વિશ્વlo"), + new AssertError("testStr failure 2", file, line)); + enforce(test(l, l.length, r, "hello વિશ્વ"), + new AssertError("testStr failure 3", file, line)); + } + + foreach (T; AliasSeq!(char, wchar, dchar, + immutable(char), immutable(wchar), immutable(dchar))) + { + foreach (U; AliasSeq!(char, wchar, dchar, + immutable(char), immutable(wchar), immutable(dchar))) + { + testStr!(T[], U[])(); + } + + } + + // variadic version + bool testVar(T, U...)(T orig, size_t pos, U args) + { + static if (is(T == typeof(T.init.dup))) + auto a = orig.dup; + else + auto a = orig.idup; + auto result = args[$-1]; + + a.insertInPlace(pos, args[0..$-1]); + if (!equal(a, result)) + return false; + return true; + } + assert(testVar([1, 2, 3, 4], 0, 6, 7u, [6, 7, 1, 2, 3, 4])); + assert(testVar([1L, 2, 3, 4], 2, 8, 9L, [1, 2, 8, 9, 3, 4])); + assert(testVar([1L, 2, 3, 4], 4, 10L, 11, [1, 2, 3, 4, 10, 11])); + assert(testVar([1L, 2, 3, 4], 4, [10, 11], 40L, 42L, + [1, 2, 3, 4, 10, 11, 40, 42])); + assert(testVar([1L, 2, 3, 4], 4, 10, 11, [40L, 42], + [1, 2, 3, 4, 10, 11, 40, 42])); + assert(testVar("t".idup, 1, 'e', 's', 't', "test")); + assert(testVar("!!"w.idup, 1, "\u00e9ll\u00f4", 'x', "TTT"w, 'y', + "!\u00e9ll\u00f4xTTTy!")); + assert(testVar("flipflop"d.idup, 4, '_', + "xyz"w, '\U00010143', '_', "abc"d, "__", + "flip_xyz\U00010143_abc__flop")); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + // insertInPlace interop with postblit + static struct Int + { + int* payload; + this(int k) + { + payload = new int; + *payload = k; + } + this(this) + { + int* np = new int; + *np = *payload; + payload = np; + } + ~this() + { + if (payload) + *payload = 0; //'destroy' it + } + @property int getPayload(){ return *payload; } + alias getPayload this; + } + + Int[] arr = [Int(1), Int(4), Int(5)]; + assert(arr[0] == 1); + insertInPlace(arr, 1, Int(2), Int(3)); + assert(equal(arr, [1, 2, 3, 4, 5])); //check it works with postblit + + version (none) // illustrates that insertInPlace() will not work with CTFE and postblit + { + static bool testctfe() + { + Int[] arr = [Int(1), Int(4), Int(5)]; + assert(arr[0] == 1); + insertInPlace(arr, 1, Int(2), Int(3)); + return equal(arr, [1, 2, 3, 4, 5]); //check it works with postblit + } + enum E = testctfe(); + } +} + +@safe unittest +{ + import std.exception; + assertCTFEable!( + { + int[] a = [1, 2]; + a.insertInPlace(2, 3); + a.insertInPlace(0, -1, 0); + return a == [-1, 0, 1, 2, 3]; + }); +} + +@system unittest // bugzilla 6874 +{ + import core.memory; + // allocate some space + byte[] a; + a.length = 1; + + // fill it + a.length = a.capacity; + + // write beyond + byte[] b = a[$ .. $]; + b.insertInPlace(0, a); + + // make sure that reallocation has happened + assert(GC.addrOf(&b[0]) == GC.addrOf(&b[$-1])); +} + + +/++ + Returns whether the $(D front)s of $(D lhs) and $(D rhs) both refer to the + same place in memory, making one of the arrays a slice of the other which + starts at index $(D 0). + +/ +@safe +pure nothrow bool sameHead(T)(in T[] lhs, in T[] rhs) +{ + return lhs.ptr == rhs.ptr; +} + +/// +@safe pure nothrow unittest +{ + auto a = [1, 2, 3, 4, 5]; + auto b = a[0 .. 2]; + + assert(a.sameHead(b)); +} + + +/++ + Returns whether the $(D back)s of $(D lhs) and $(D rhs) both refer to the + same place in memory, making one of the arrays a slice of the other which + end at index $(D $). + +/ +@trusted +pure nothrow bool sameTail(T)(in T[] lhs, in T[] rhs) +{ + return lhs.ptr + lhs.length == rhs.ptr + rhs.length; +} + +/// +@safe pure nothrow unittest +{ + auto a = [1, 2, 3, 4, 5]; + auto b = a[3..$]; + + assert(a.sameTail(b)); +} + +@safe pure nothrow unittest +{ + foreach (T; AliasSeq!(int[], const(int)[], immutable(int)[], const int[], immutable int[])) + { + T a = [1, 2, 3, 4, 5]; + T b = a; + T c = a[1 .. $]; + T d = a[0 .. 1]; + T e = null; + + assert(sameHead(a, a)); + assert(sameHead(a, b)); + assert(!sameHead(a, c)); + assert(sameHead(a, d)); + assert(!sameHead(a, e)); + + assert(sameTail(a, a)); + assert(sameTail(a, b)); + assert(sameTail(a, c)); + assert(!sameTail(a, d)); + assert(!sameTail(a, e)); + + //verifies R-value compatibilty + assert(a.sameHead(a[0 .. 0])); + assert(a.sameTail(a[$ .. $])); + } +} + +/** +Params: + s = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + or a dynamic array + n = number of times to repeat `s` + +Returns: + An array that consists of `s` repeated `n` times. This function allocates, fills, and + returns a new array. + +See_Also: + For a lazy version, refer to $(REF repeat, std,range). + */ +ElementEncodingType!S[] replicate(S)(S s, size_t n) +if (isDynamicArray!S) +{ + alias RetType = ElementEncodingType!S[]; + + // Optimization for return join(std.range.repeat(s, n)); + if (n == 0) + return RetType.init; + if (n == 1) + return cast(RetType) s; + auto r = new Unqual!(typeof(s[0]))[n * s.length]; + if (s.length == 1) + r[] = s[0]; + else + { + immutable len = s.length, nlen = n * len; + for (size_t i = 0; i < nlen; i += len) + { + r[i .. i + len] = s[]; + } + } + return r; +} + +/// ditto +ElementType!S[] replicate(S)(S s, size_t n) +if (isInputRange!S && !isDynamicArray!S) +{ + import std.range : repeat; + return join(std.range.repeat(s, n)); +} + + +/// +@safe unittest +{ + auto a = "abc"; + auto s = replicate(a, 3); + + assert(s == "abcabcabc"); + + auto b = [1, 2, 3]; + auto c = replicate(b, 3); + + assert(c == [1, 2, 3, 1, 2, 3, 1, 2, 3]); + + auto d = replicate(b, 0); + + assert(d == []); +} + +@safe unittest +{ + import std.conv : to; + + foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[])) + { + S s; + immutable S t = "abc"; + + assert(replicate(to!S("1234"), 0) is null); + assert(replicate(to!S("1234"), 0) is null); + assert(replicate(to!S("1234"), 1) == "1234"); + assert(replicate(to!S("1234"), 2) == "12341234"); + assert(replicate(to!S("1"), 4) == "1111"); + assert(replicate(t, 3) == "abcabcabc"); + assert(replicate(cast(S) null, 4) is null); + } +} + +/++ +Eagerly split the string $(D s) into an array of words, using whitespace as +delimiter. Runs of whitespace are merged together (no empty words are produced). + +$(D @safe), $(D pure) and $(D CTFE)-able. + +Params: + s = the string to split + +Returns: + An array of each word in `s` + +See_Also: +$(REF splitter, std,algorithm,iteration) for a version that splits using any +separator. + +$(REF splitter, std,regex) for a version that splits using a regular +expression defined separator. ++/ +S[] split(S)(S s) @safe pure +if (isSomeString!S) +{ + size_t istart; + bool inword = false; + S[] result; + + foreach (i, dchar c ; s) + { + import std.uni : isWhite; + if (isWhite(c)) + { + if (inword) + { + result ~= s[istart .. i]; + inword = false; + } + } + else + { + if (!inword) + { + istart = i; + inword = true; + } + } + } + if (inword) + result ~= s[istart .. $]; + return result; +} + +/// +@safe unittest +{ + string str = "Hello World!"; + assert(str.split == ["Hello", "World!"]); + + string str2 = "Hello\t\tWorld\t!"; + assert(str2.split == ["Hello", "World", "!"]); +} + +/** + * `split` allocates memory, so the same effect can be achieved lazily + * using $(REF splitter, std,algorithm,iteration). + */ +@safe unittest +{ + import std.ascii : isWhite; + import std.algorithm.comparison : equal; + + string str = "Hello World!"; + assert(str.splitter!(isWhite).equal(["Hello", "World!"])); +} + +@safe unittest +{ + import std.conv : to; + import std.format; + import std.typecons; + + static auto makeEntry(S)(string l, string[] r) + {return tuple(l.to!S(), r.to!(S[])());} + + foreach (S; AliasSeq!(string, wstring, dstring,)) + { + auto entries = + [ + makeEntry!S("", []), + makeEntry!S(" ", []), + makeEntry!S("hello", ["hello"]), + makeEntry!S(" hello ", ["hello"]), + makeEntry!S(" h e l l o ", ["h", "e", "l", "l", "o"]), + makeEntry!S("peter\t\npaul\rjerry", ["peter", "paul", "jerry"]), + makeEntry!S(" \t\npeter paul\tjerry \n", ["peter", "paul", "jerry"]), + makeEntry!S("\u2000日\u202F本\u205F語\u3000", ["日", "本", "語"]), + makeEntry!S("  哈・郎博尔德}    ___一个", ["哈・郎博尔德}", "___一个"]) + ]; + foreach (entry; entries) + assert(entry[0].split() == entry[1], format("got: %s, expected: %s.", entry[0].split(), entry[1])); + } + + //Just to test that an immutable is split-able + immutable string s = " \t\npeter paul\tjerry \n"; + assert(split(s) == ["peter", "paul", "jerry"]); +} + +@safe unittest //purity, ctfe ... +{ + import std.exception; + void dg() @safe pure { + assert(split("hello world"c) == ["hello"c, "world"c]); + assert(split("hello world"w) == ["hello"w, "world"w]); + assert(split("hello world"d) == ["hello"d, "world"d]); + } + dg(); + assertCTFEable!dg; +} + +/// +@safe unittest +{ + assert(split("hello world") == ["hello","world"]); + assert(split("192.168.0.1", ".") == ["192", "168", "0", "1"]); + + auto a = split([1, 2, 3, 4, 5, 1, 2, 3, 4, 5], [2, 3]); + assert(a == [[1], [4, 5, 1], [4, 5]]); +} + +// Explicitly undocumented. It will be removed in January 2018. @@@DEPRECATED_2018-01@@@ +deprecated("Please use std.algorithm.iteration.splitter instead.") +alias splitter = std.algorithm.iteration.splitter; + +/++ + Eagerly splits $(D range) into an array, using $(D sep) as the delimiter. + + The _range must be a + $(REF_ALTTEXT forward _range, isForwardRange, std,_range,primitives). + The separator can be a value of the same type as the elements in $(D range) + or it can be another forward _range. + + Example: + If $(D range) is a $(D string), $(D sep) can be a $(D char) or another + $(D string). The return type will be an array of strings. If $(D range) is + an $(D int) array, $(D sep) can be an $(D int) or another $(D int) array. + The return type will be an array of $(D int) arrays. + + Params: + range = a forward _range. + sep = a value of the same type as the elements of $(D range) or another + forward range. + + Returns: + An array containing the divided parts of $(D range). + + See_Also: + $(REF splitter, std,algorithm,iteration) for the lazy version of this + function. + +/ +auto split(Range, Separator)(Range range, Separator sep) +if (isForwardRange!Range && is(typeof(ElementType!Range.init == Separator.init))) +{ + import std.algorithm.iteration : splitter; + return range.splitter(sep).array; +} +///ditto +auto split(Range, Separator)(Range range, Separator sep) +if ( + isForwardRange!Range && isForwardRange!Separator + && is(typeof(ElementType!Range.init == ElementType!Separator.init))) +{ + import std.algorithm.iteration : splitter; + return range.splitter(sep).array; +} +///ditto +auto split(alias isTerminator, Range)(Range range) +if (isForwardRange!Range && is(typeof(unaryFun!isTerminator(range.front)))) +{ + import std.algorithm.iteration : splitter; + return range.splitter!isTerminator.array; +} + +/// +@safe unittest +{ + import std.uni : isWhite; + assert("Learning,D,is,fun".split(",") == ["Learning", "D", "is", "fun"]); + assert("Learning D is fun".split!isWhite == ["Learning", "D", "is", "fun"]); + assert("Learning D is fun".split(" D ") == ["Learning", "is fun"]); +} + +@safe unittest +{ + import std.algorithm.comparison : cmp; + import std.conv; + + foreach (S; AliasSeq!(string, wstring, dstring, + immutable(string), immutable(wstring), immutable(dstring), + char[], wchar[], dchar[], + const(char)[], const(wchar)[], const(dchar)[], + const(char[]), immutable(char[]))) + { + S s = to!S(",peter,paul,jerry,"); + + auto words = split(s, ","); + assert(words.length == 5, text(words.length)); + assert(cmp(words[0], "") == 0); + assert(cmp(words[1], "peter") == 0); + assert(cmp(words[2], "paul") == 0); + assert(cmp(words[3], "jerry") == 0); + assert(cmp(words[4], "") == 0); + + auto s1 = s[0 .. s.length - 1]; // lop off trailing ',' + words = split(s1, ","); + assert(words.length == 4); + assert(cmp(words[3], "jerry") == 0); + + auto s2 = s1[1 .. s1.length]; // lop off leading ',' + words = split(s2, ","); + assert(words.length == 3); + assert(cmp(words[0], "peter") == 0); + + auto s3 = to!S(",,peter,,paul,,jerry,,"); + + words = split(s3, ",,"); + assert(words.length == 5); + assert(cmp(words[0], "") == 0); + assert(cmp(words[1], "peter") == 0); + assert(cmp(words[2], "paul") == 0); + assert(cmp(words[3], "jerry") == 0); + assert(cmp(words[4], "") == 0); + + auto s4 = s3[0 .. s3.length - 2]; // lop off trailing ',,' + words = split(s4, ",,"); + assert(words.length == 4); + assert(cmp(words[3], "jerry") == 0); + + auto s5 = s4[2 .. s4.length]; // lop off leading ',,' + words = split(s5, ",,"); + assert(words.length == 3); + assert(cmp(words[0], "peter") == 0); + } +} + +/++ + Conservative heuristic to determine if a range can be iterated cheaply. + Used by $(D join) in decision to do an extra iteration of the range to + compute the resultant length. If iteration is not cheap then precomputing + length could be more expensive than using $(D Appender). + + For now, we only assume arrays are cheap to iterate. + +/ +private enum bool hasCheapIteration(R) = isArray!R; + +/++ + Eagerly concatenates all of the ranges in `ror` together (with the GC) + into one array using `sep` as the separator if present. + + Params: + ror = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + of input ranges + sep = An input range, or a single element, to join the ranges on + + Returns: + An array of elements + + See_Also: + For a lazy version, see $(REF joiner, std,algorithm,iteration) + +/ +ElementEncodingType!(ElementType!RoR)[] join(RoR, R)(RoR ror, scope R sep) +if (isInputRange!RoR && + isInputRange!(Unqual!(ElementType!RoR)) && + isInputRange!R && + is(Unqual!(ElementType!(ElementType!RoR)) == Unqual!(ElementType!R))) +{ + alias RetType = typeof(return); + alias RetTypeElement = Unqual!(ElementEncodingType!RetType); + alias RoRElem = ElementType!RoR; + + if (ror.empty) + return RetType.init; + + // Constraint only requires input range for sep. + // This converts sep to an array (forward range) if it isn't one, + // and makes sure it has the same string encoding for string types. + static if (isSomeString!RetType && + !is(RetTypeElement == Unqual!(ElementEncodingType!R))) + { + import std.conv : to; + auto sepArr = to!RetType(sep); + } + else static if (!isArray!R) + auto sepArr = array(sep); + else + alias sepArr = sep; + + static if (hasCheapIteration!RoR && (hasLength!RoRElem || isNarrowString!RoRElem)) + { + import std.conv : emplaceRef; + size_t length; // length of result array + size_t rorLength; // length of range ror + foreach (r; ror.save) + { + length += r.length; + ++rorLength; + } + if (!rorLength) + return null; + length += (rorLength - 1) * sepArr.length; + + auto result = (() @trusted => uninitializedArray!(RetTypeElement[])(length))(); + size_t len; + foreach (e; ror.front) + emplaceRef(result[len++], e); + ror.popFront(); + foreach (r; ror) + { + foreach (e; sepArr) + emplaceRef(result[len++], e); + foreach (e; r) + emplaceRef(result[len++], e); + } + assert(len == result.length); + return (() @trusted => cast(RetType) result)(); + } + else + { + auto result = appender!RetType(); + put(result, ror.front); + ror.popFront(); + for (; !ror.empty; ror.popFront()) + { + put(result, sep); + put(result, ror.front); + } + return result.data; + } +} + +@safe unittest // Issue 14230 +{ + string[] ary = ["","aa","bb","cc"]; // leaded by _empty_ element + assert(ary.join(" @") == " @aa @bb @cc"); // OK in 2.067b1 and olders +} + +/// Ditto +ElementEncodingType!(ElementType!RoR)[] join(RoR, E)(RoR ror, scope E sep) +if (isInputRange!RoR && + isInputRange!(Unqual!(ElementType!RoR)) && + is(E : ElementType!(ElementType!RoR))) +{ + alias RetType = typeof(return); + alias RetTypeElement = Unqual!(ElementEncodingType!RetType); + alias RoRElem = ElementType!RoR; + + if (ror.empty) + return RetType.init; + + static if (hasCheapIteration!RoR && (hasLength!RoRElem || isNarrowString!RoRElem)) + { + static if (isSomeChar!E && isSomeChar!RetTypeElement && E.sizeof > RetTypeElement.sizeof) + { + import std.utf : encode; + RetTypeElement[4 / RetTypeElement.sizeof] encodeSpace; + immutable size_t sepArrLength = encode(encodeSpace, sep); + return join(ror, encodeSpace[0 .. sepArrLength]); + } + else + { + import std.conv : emplaceRef; + size_t length; + size_t rorLength; + foreach (r; ror.save) + { + length += r.length; + ++rorLength; + } + if (!rorLength) + return null; + length += rorLength - 1; + auto result = uninitializedArray!(RetTypeElement[])(length); + + + size_t len; + foreach (e; ror.front) + emplaceRef(result[len++], e); + ror.popFront(); + foreach (r; ror) + { + emplaceRef(result[len++], sep); + foreach (e; r) + emplaceRef(result[len++], e); + } + assert(len == result.length); + return (() @trusted => cast(RetType) result)(); + } + } + else + { + auto result = appender!RetType(); + put(result, ror.front); + ror.popFront(); + for (; !ror.empty; ror.popFront()) + { + put(result, sep); + put(result, ror.front); + } + return result.data; + } +} + +@safe unittest // Issue 10895 +{ + class A + { + string name; + alias name this; + this(string name) { this.name = name; } + } + auto a = [new A(`foo`)]; + assert(a[0].length == 3); + auto temp = join(a, " "); + assert(a[0].length == 3); +} + +@safe unittest // Issue 14230 +{ + string[] ary = ["","aa","bb","cc"]; + assert(ary.join('@') == "@aa@bb@cc"); +} + +/// Ditto +ElementEncodingType!(ElementType!RoR)[] join(RoR)(RoR ror) +if (isInputRange!RoR && + isInputRange!(Unqual!(ElementType!RoR))) +{ + alias RetType = typeof(return); + alias RetTypeElement = Unqual!(ElementEncodingType!RetType); + alias RoRElem = ElementType!RoR; + + if (ror.empty) + return RetType.init; + + static if (hasCheapIteration!RoR && (hasLength!RoRElem || isNarrowString!RoRElem)) + { + import std.conv : emplaceRef; + size_t length; + foreach (r; ror.save) + length += r.length; + + auto result = (() @trusted => uninitializedArray!(RetTypeElement[])(length))(); + size_t len; + foreach (r; ror) + foreach (e; r) + emplaceRef(result[len++], e); + assert(len == result.length); + return (() @trusted => cast(RetType) result)(); + } + else + { + auto result = appender!RetType(); + for (; !ror.empty; ror.popFront()) + put(result, ror.front); + return result.data; + } +} + +/// +@safe pure nothrow unittest +{ + assert(join(["hello", "silly", "world"], " ") == "hello silly world"); + assert(join(["hello", "silly", "world"]) == "hellosillyworld"); + + assert(join([[1, 2, 3], [4, 5]], [72, 73]) == [1, 2, 3, 72, 73, 4, 5]); + assert(join([[1, 2, 3], [4, 5]]) == [1, 2, 3, 4, 5]); + + const string[] arr = ["apple", "banana"]; + assert(arr.join(",") == "apple,banana"); + assert(arr.join() == "applebanana"); +} + +@safe pure unittest +{ + import std.conv : to; + + foreach (T; AliasSeq!(string,wstring,dstring)) + { + auto arr2 = "Здравствуй Мир Unicode".to!(T); + auto arr = ["Здравствуй", "Мир", "Unicode"].to!(T[]); + assert(join(arr) == "ЗдравствуйМирUnicode"); + foreach (S; AliasSeq!(char,wchar,dchar)) + { + auto jarr = arr.join(to!S(' ')); + static assert(is(typeof(jarr) == T)); + assert(jarr == arr2); + } + foreach (S; AliasSeq!(string,wstring,dstring)) + { + auto jarr = arr.join(to!S(" ")); + static assert(is(typeof(jarr) == T)); + assert(jarr == arr2); + } + } + + foreach (T; AliasSeq!(string,wstring,dstring)) + { + auto arr2 = "Здравствуй\u047CМир\u047CUnicode".to!(T); + auto arr = ["Здравствуй", "Мир", "Unicode"].to!(T[]); + foreach (S; AliasSeq!(wchar,dchar)) + { + auto jarr = arr.join(to!S('\u047C')); + static assert(is(typeof(jarr) == T)); + assert(jarr == arr2); + } + } + + const string[] arr = ["apple", "banana"]; + assert(arr.join(',') == "apple,banana"); +} + +@system unittest +{ + import std.algorithm; + import std.conv : to; + import std.range; + + foreach (R; AliasSeq!(string, wstring, dstring)) + { + R word1 = "日本語"; + R word2 = "paul"; + R word3 = "jerry"; + R[] words = [word1, word2, word3]; + + auto filteredWord1 = filter!"true"(word1); + auto filteredLenWord1 = takeExactly(filteredWord1, word1.walkLength()); + auto filteredWord2 = filter!"true"(word2); + auto filteredLenWord2 = takeExactly(filteredWord2, word2.walkLength()); + auto filteredWord3 = filter!"true"(word3); + auto filteredLenWord3 = takeExactly(filteredWord3, word3.walkLength()); + auto filteredWordsArr = [filteredWord1, filteredWord2, filteredWord3]; + auto filteredLenWordsArr = [filteredLenWord1, filteredLenWord2, filteredLenWord3]; + auto filteredWords = filter!"true"(filteredWordsArr); + + foreach (S; AliasSeq!(string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + assert(join(filteredWords, to!S(", ")) == "日本語, paul, jerry"); + assert(join(filteredWords, to!(ElementType!S)(',')) == "日本語,paul,jerry"); + assert(join(filteredWordsArr, to!(ElementType!(S))(',')) == "日本語,paul,jerry"); + assert(join(filteredWordsArr, to!S(", ")) == "日本語, paul, jerry"); + assert(join(filteredWordsArr, to!(ElementType!(S))(',')) == "日本語,paul,jerry"); + assert(join(filteredLenWordsArr, to!S(", ")) == "日本語, paul, jerry"); + assert(join(filter!"true"(words), to!S(", ")) == "日本語, paul, jerry"); + assert(join(words, to!S(", ")) == "日本語, paul, jerry"); + + assert(join(filteredWords, to!S("")) == "日本語pauljerry"); + assert(join(filteredWordsArr, to!S("")) == "日本語pauljerry"); + assert(join(filteredLenWordsArr, to!S("")) == "日本語pauljerry"); + assert(join(filter!"true"(words), to!S("")) == "日本語pauljerry"); + assert(join(words, to!S("")) == "日本語pauljerry"); + + assert(join(filter!"true"([word1]), to!S(", ")) == "日本語"); + assert(join([filteredWord1], to!S(", ")) == "日本語"); + assert(join([filteredLenWord1], to!S(", ")) == "日本語"); + assert(join(filter!"true"([filteredWord1]), to!S(", ")) == "日本語"); + assert(join([word1], to!S(", ")) == "日本語"); + + assert(join(filteredWords, to!S(word1)) == "日本語日本語paul日本語jerry"); + assert(join(filteredWordsArr, to!S(word1)) == "日本語日本語paul日本語jerry"); + assert(join(filteredLenWordsArr, to!S(word1)) == "日本語日本語paul日本語jerry"); + assert(join(filter!"true"(words), to!S(word1)) == "日本語日本語paul日本語jerry"); + assert(join(words, to!S(word1)) == "日本語日本語paul日本語jerry"); + + auto filterComma = filter!"true"(to!S(", ")); + assert(join(filteredWords, filterComma) == "日本語, paul, jerry"); + assert(join(filteredWordsArr, filterComma) == "日本語, paul, jerry"); + assert(join(filteredLenWordsArr, filterComma) == "日本語, paul, jerry"); + assert(join(filter!"true"(words), filterComma) == "日本語, paul, jerry"); + assert(join(words, filterComma) == "日本語, paul, jerry"); + }(); + + assert(join(filteredWords) == "日本語pauljerry"); + assert(join(filteredWordsArr) == "日本語pauljerry"); + assert(join(filteredLenWordsArr) == "日本語pauljerry"); + assert(join(filter!"true"(words)) == "日本語pauljerry"); + assert(join(words) == "日本語pauljerry"); + + assert(join(filteredWords, filter!"true"(", ")) == "日本語, paul, jerry"); + assert(join(filteredWordsArr, filter!"true"(", ")) == "日本語, paul, jerry"); + assert(join(filteredLenWordsArr, filter!"true"(", ")) == "日本語, paul, jerry"); + assert(join(filter!"true"(words), filter!"true"(", ")) == "日本語, paul, jerry"); + assert(join(words, filter!"true"(", ")) == "日本語, paul, jerry"); + + assert(join(filter!"true"(cast(typeof(filteredWordsArr))[]), ", ").empty); + assert(join(cast(typeof(filteredWordsArr))[], ", ").empty); + assert(join(cast(typeof(filteredLenWordsArr))[], ", ").empty); + assert(join(filter!"true"(cast(R[])[]), ", ").empty); + assert(join(cast(R[])[], ", ").empty); + + assert(join(filter!"true"(cast(typeof(filteredWordsArr))[])).empty); + assert(join(cast(typeof(filteredWordsArr))[]).empty); + assert(join(cast(typeof(filteredLenWordsArr))[]).empty); + + assert(join(filter!"true"(cast(R[])[])).empty); + assert(join(cast(R[])[]).empty); + } + + assert(join([[1, 2], [41, 42]], [5, 6]) == [1, 2, 5, 6, 41, 42]); + assert(join([[1, 2], [41, 42]], cast(int[])[]) == [1, 2, 41, 42]); + assert(join([[1, 2]], [5, 6]) == [1, 2]); + assert(join(cast(int[][])[], [5, 6]).empty); + + assert(join([[1, 2], [41, 42]]) == [1, 2, 41, 42]); + assert(join(cast(int[][])[]).empty); + + alias f = filter!"true"; + assert(join([[1, 2], [41, 42]], [5, 6]) == [1, 2, 5, 6, 41, 42]); + assert(join(f([[1, 2], [41, 42]]), [5, 6]) == [1, 2, 5, 6, 41, 42]); + assert(join([f([1, 2]), f([41, 42])], [5, 6]) == [1, 2, 5, 6, 41, 42]); + assert(join(f([f([1, 2]), f([41, 42])]), [5, 6]) == [1, 2, 5, 6, 41, 42]); + assert(join([[1, 2], [41, 42]], f([5, 6])) == [1, 2, 5, 6, 41, 42]); + assert(join(f([[1, 2], [41, 42]]), f([5, 6])) == [1, 2, 5, 6, 41, 42]); + assert(join([f([1, 2]), f([41, 42])], f([5, 6])) == [1, 2, 5, 6, 41, 42]); + assert(join(f([f([1, 2]), f([41, 42])]), f([5, 6])) == [1, 2, 5, 6, 41, 42]); +} + +// Issue 10683 +@safe unittest +{ + import std.range : join; + import std.typecons : tuple; + assert([[tuple(1)]].join == [tuple(1)]); + assert([[tuple("x")]].join == [tuple("x")]); +} + +// Issue 13877 +@safe unittest +{ + // Test that the range is iterated only once. + import std.algorithm.iteration : map; + int c = 0; + auto j1 = [1, 2, 3].map!(_ => [c++]).join; + assert(c == 3); + assert(j1 == [0, 1, 2]); + + c = 0; + auto j2 = [1, 2, 3].map!(_ => [c++]).join(9); + assert(c == 3); + assert(j2 == [0, 9, 1, 9, 2]); + + c = 0; + auto j3 = [1, 2, 3].map!(_ => [c++]).join([9]); + assert(c == 3); + assert(j3 == [0, 9, 1, 9, 2]); +} + + +/++ + Replace occurrences of `from` with `to` in `subject` in a new + array. If `sink` is defined, then output the new array into + `sink`. + + Params: + sink = an $(REF_ALTTEXT output range, isOutputRange, std,range,primitives) + subject = the array to scan + from = the item to replace + to = the item to replace all instances of `from` with + + Returns: + If `sink` isn't defined, a new array without changing the + contents of `subject`, or the original array if no match + is found. + + See_Also: + $(REF map, std,algorithm,iteration) which can act as a lazy replace + +/ +E[] replace(E, R1, R2)(E[] subject, R1 from, R2 to) +if (isDynamicArray!(E[]) && isForwardRange!R1 && isForwardRange!R2 + && (hasLength!R2 || isSomeString!R2)) +{ + import std.algorithm.searching : find; + + if (from.empty) return subject; + + auto balance = find(subject, from.save); + if (balance.empty) + return subject; + + auto app = appender!(E[])(); + app.put(subject[0 .. subject.length - balance.length]); + app.put(to.save); + replaceInto(app, balance[from.length .. $], from, to); + + return app.data; +} + +/// +@safe unittest +{ + assert("Hello Wörld".replace("o Wö", "o Wo") == "Hello World"); + assert("Hello Wörld".replace("l", "h") == "Hehho Wörhd"); +} + +/// ditto +void replaceInto(E, Sink, R1, R2)(Sink sink, E[] subject, R1 from, R2 to) +if (isOutputRange!(Sink, E) && isDynamicArray!(E[]) + && isForwardRange!R1 && isForwardRange!R2 + && (hasLength!R2 || isSomeString!R2)) +{ + import std.algorithm.searching : find; + + if (from.empty) + { + sink.put(subject); + return; + } + for (;;) + { + auto balance = find(subject, from.save); + if (balance.empty) + { + sink.put(subject); + break; + } + sink.put(subject[0 .. subject.length - balance.length]); + sink.put(to.save); + subject = balance[from.length .. $]; + } +} + +/// +@safe unittest +{ + auto arr = [1, 2, 3, 4, 5]; + auto from = [2, 3]; + auto to = [4, 6]; + auto sink = appender!(int[])(); + + replaceInto(sink, arr, from, to); + + assert(sink.data == [1, 4, 6, 4, 5]); +} + +@safe unittest +{ + import std.algorithm.comparison : cmp; + import std.conv : to; + + foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[])) + { + foreach (T; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[])) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + auto s = to!S("This is a foo foo list"); + auto from = to!T("foo"); + auto into = to!S("silly"); + S r; + int i; + + r = replace(s, from, into); + i = cmp(r, "This is a silly silly list"); + assert(i == 0); + + r = replace(s, to!S(""), into); + i = cmp(r, "This is a foo foo list"); + assert(i == 0); + + assert(replace(r, to!S("won't find this"), to!S("whatever")) is r); + }(); + } + + immutable s = "This is a foo foo list"; + assert(replace(s, "foo", "silly") == "This is a silly silly list"); +} + +@safe unittest +{ + import std.algorithm.searching : skipOver; + import std.conv : to; + + struct CheckOutput(C) + { + C[] desired; + this(C[] arr){ desired = arr; } + void put(C[] part){ assert(skipOver(desired, part)); } + } + foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[])) + { + alias Char = ElementEncodingType!S; + S s = to!S("yet another dummy text, yet another ..."); + S from = to!S("yet another"); + S into = to!S("some"); + replaceInto(CheckOutput!(Char)(to!S("some dummy text, some ...")) + , s, from, into); + } +} + +/++ + Replaces elements from `array` with indices ranging from `from` + (inclusive) to `to` (exclusive) with the range `stuff`. + + Params: + subject = the array to scan + from = the starting index + to = the ending index + stuff = the items to replace in-between `from` and `to` + + Returns: + A new array without changing the contents of `subject`. + +/ +T[] replace(T, Range)(T[] subject, size_t from, size_t to, Range stuff) +if (isInputRange!Range && + (is(ElementType!Range : T) || + isSomeString!(T[]) && is(ElementType!Range : dchar))) +{ + static if (hasLength!Range && is(ElementEncodingType!Range : T)) + { + import std.algorithm.mutation : copy; + assert(from <= to); + immutable sliceLen = to - from; + auto retval = new Unqual!(T)[](subject.length - sliceLen + stuff.length); + retval[0 .. from] = subject[0 .. from]; + + if (!stuff.empty) + copy(stuff, retval[from .. from + stuff.length]); + + retval[from + stuff.length .. $] = subject[to .. $]; + return cast(T[]) retval; + } + else + { + auto app = appender!(T[])(); + app.put(subject[0 .. from]); + app.put(stuff); + app.put(subject[to .. $]); + return app.data; + } +} + +/// +@safe unittest +{ + auto a = [ 1, 2, 3, 4 ]; + auto b = a.replace(1, 3, [ 9, 9, 9 ]); + assert(a == [ 1, 2, 3, 4 ]); + assert(b == [ 1, 9, 9, 9, 4 ]); +} + +@system unittest +{ + import core.exception; + import std.algorithm.iteration : filter; + import std.conv : to; + import std.exception; + + + auto a = [ 1, 2, 3, 4 ]; + assert(replace(a, 0, 0, [5, 6, 7]) == [5, 6, 7, 1, 2, 3, 4]); + assert(replace(a, 0, 2, cast(int[])[]) == [3, 4]); + assert(replace(a, 0, 4, [5, 6, 7]) == [5, 6, 7]); + assert(replace(a, 0, 2, [5, 6, 7]) == [5, 6, 7, 3, 4]); + assert(replace(a, 2, 4, [5, 6, 7]) == [1, 2, 5, 6, 7]); + + assert(replace(a, 0, 0, filter!"true"([5, 6, 7])) == [5, 6, 7, 1, 2, 3, 4]); + assert(replace(a, 0, 2, filter!"true"(cast(int[])[])) == [3, 4]); + assert(replace(a, 0, 4, filter!"true"([5, 6, 7])) == [5, 6, 7]); + assert(replace(a, 0, 2, filter!"true"([5, 6, 7])) == [5, 6, 7, 3, 4]); + assert(replace(a, 2, 4, filter!"true"([5, 6, 7])) == [1, 2, 5, 6, 7]); + assert(a == [ 1, 2, 3, 4 ]); + + void testStr(T, U)(string file = __FILE__, size_t line = __LINE__) + { + + auto l = to!T("hello"); + auto r = to!U(" world"); + + enforce(replace(l, 0, 0, r) == " worldhello", + new AssertError("testStr failure 1", file, line)); + enforce(replace(l, 0, 3, r) == " worldlo", + new AssertError("testStr failure 2", file, line)); + enforce(replace(l, 3, l.length, r) == "hel world", + new AssertError("testStr failure 3", file, line)); + enforce(replace(l, 0, l.length, r) == " world", + new AssertError("testStr failure 4", file, line)); + enforce(replace(l, l.length, l.length, r) == "hello world", + new AssertError("testStr failure 5", file, line)); + } + + testStr!(string, string)(); + testStr!(string, wstring)(); + testStr!(string, dstring)(); + testStr!(wstring, string)(); + testStr!(wstring, wstring)(); + testStr!(wstring, dstring)(); + testStr!(dstring, string)(); + testStr!(dstring, wstring)(); + testStr!(dstring, dstring)(); + + enum s = "0123456789"; + enum w = "⁰¹²³⁴⁵⁶⁷⁸⁹"w; + enum d = "⁰¹²³⁴⁵⁶⁷⁸⁹"d; + + assert(replace(s, 0, 0, "***") == "***0123456789"); + assert(replace(s, 10, 10, "***") == "0123456789***"); + assert(replace(s, 3, 8, "1012") == "012101289"); + assert(replace(s, 0, 5, "43210") == "4321056789"); + assert(replace(s, 5, 10, "43210") == "0123443210"); + + assert(replace(w, 0, 0, "***"w) == "***⁰¹²³⁴⁵⁶⁷⁸⁹"w); + assert(replace(w, 10, 10, "***"w) == "⁰¹²³⁴⁵⁶⁷⁸⁹***"w); + assert(replace(w, 3, 8, "¹⁰¹²"w) == "⁰¹²¹⁰¹²⁸⁹"w); + assert(replace(w, 0, 5, "⁴³²¹⁰"w) == "⁴³²¹⁰⁵⁶⁷⁸⁹"w); + assert(replace(w, 5, 10, "⁴³²¹⁰"w) == "⁰¹²³⁴⁴³²¹⁰"w); + + assert(replace(d, 0, 0, "***"d) == "***⁰¹²³⁴⁵⁶⁷⁸⁹"d); + assert(replace(d, 10, 10, "***"d) == "⁰¹²³⁴⁵⁶⁷⁸⁹***"d); + assert(replace(d, 3, 8, "¹⁰¹²"d) == "⁰¹²¹⁰¹²⁸⁹"d); + assert(replace(d, 0, 5, "⁴³²¹⁰"d) == "⁴³²¹⁰⁵⁶⁷⁸⁹"d); + assert(replace(d, 5, 10, "⁴³²¹⁰"d) == "⁰¹²³⁴⁴³²¹⁰"d); +} + +/++ + Replaces elements from `array` with indices ranging from `from` + (inclusive) to `to` (exclusive) with the range `stuff`. Expands or + shrinks the array as needed. + + Params: + array = the _array to scan + from = the starting index + to = the ending index + stuff = the items to replace in-between `from` and `to` + +/ +void replaceInPlace(T, Range)(ref T[] array, size_t from, size_t to, Range stuff) +if (is(typeof(replace(array, from, to, stuff)))) +{ + static if (isDynamicArray!Range && + is(Unqual!(ElementEncodingType!Range) == T) && + !isNarrowString!(T[])) + { + // optimized for homogeneous arrays that can be overwritten. + import std.algorithm.mutation : remove; + import std.typecons : tuple; + + if (overlap(array, stuff).length) + { + // use slower/conservative method + array = array[0 .. from] ~ stuff ~ array[to .. $]; + } + else if (stuff.length <= to - from) + { + // replacement reduces length + immutable stuffEnd = from + stuff.length; + array[from .. stuffEnd] = stuff[]; + if (stuffEnd < to) + array = remove(array, tuple(stuffEnd, to)); + } + else + { + // replacement increases length + // @@@TODO@@@: optimize this + immutable replaceLen = to - from; + array[from .. to] = stuff[0 .. replaceLen]; + insertInPlace(array, to, stuff[replaceLen .. $]); + } + } + else + { + // default implementation, just do what replace does. + array = replace(array, from, to, stuff); + } +} + +/// +@safe unittest +{ + int[] a = [1, 4, 5]; + replaceInPlace(a, 1u, 2u, [2, 3, 4]); + assert(a == [1, 2, 3, 4, 5]); + replaceInPlace(a, 1u, 2u, cast(int[])[]); + assert(a == [1, 3, 4, 5]); + replaceInPlace(a, 1u, 3u, a[2 .. 4]); + assert(a == [1, 4, 5, 5]); +} + +@safe unittest +{ + // Bug# 12889 + int[1][] arr = [[0], [1], [2], [3], [4], [5], [6]]; + int[1][] stuff = [[0], [1]]; + replaceInPlace(arr, 4, 6, stuff); + assert(arr == [[0], [1], [2], [3], [0], [1], [6]]); +} + +@system unittest +{ + // Bug# 14925 + char[] a = "mon texte 1".dup; + char[] b = "abc".dup; + replaceInPlace(a, 4, 9, b); + assert(a == "mon abc 1"); + + // ensure we can replace in place with different encodings + string unicoded = "\U00010437"; + string unicodedLong = "\U00010437aaaaa"; + string base = "abcXXXxyz"; + string result = "abc\U00010437xyz"; + string resultLong = "abc\U00010437aaaaaxyz"; + size_t repstart = 3; + size_t repend = 3 + 3; + + void testStringReplaceInPlace(T, U)() + { + import std.algorithm.comparison : equal; + import std.conv; + auto a = unicoded.to!(U[]); + auto b = unicodedLong.to!(U[]); + + auto test = base.to!(T[]); + + test.replaceInPlace(repstart, repend, a); + assert(equal(test, result), "Failed for types " ~ T.stringof ~ " and " ~ U.stringof); + + test = base.to!(T[]); + + test.replaceInPlace(repstart, repend, b); + assert(equal(test, resultLong), "Failed for types " ~ T.stringof ~ " and " ~ U.stringof); + } + + import std.meta : AliasSeq; + alias allChars = AliasSeq!(char, immutable(char), const(char), + wchar, immutable(wchar), const(wchar), + dchar, immutable(dchar), const(dchar)); + foreach (T; allChars) + foreach (U; allChars) + testStringReplaceInPlace!(T, U)(); + + void testInout(inout(int)[] a) + { + // will be transferred to the 'replace' function + replaceInPlace(a, 1, 2, [1,2,3]); + } +} + +@safe unittest +{ + // the constraint for the first overload used to match this, which wouldn't compile. + import std.algorithm.comparison : equal; + long[] a = [1L, 2, 3]; + int[] b = [4, 5, 6]; + a.replaceInPlace(1, 2, b); + assert(equal(a, [1L, 4, 5, 6, 3])); +} + +@system unittest +{ + import core.exception; + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filter; + import std.conv : to; + import std.exception; + + + bool test(T, U, V)(T orig, size_t from, size_t to, U toReplace, V result, + string file = __FILE__, size_t line = __LINE__) + { + { + static if (is(T == typeof(T.init.dup))) + auto a = orig.dup; + else + auto a = orig.idup; + + a.replaceInPlace(from, to, toReplace); + if (!equal(a, result)) + return false; + } + + static if (isInputRange!U) + { + orig.replaceInPlace(from, to, filter!"true"(toReplace)); + return equal(orig, result); + } + else + return true; + } + + assert(test([1, 2, 3, 4], 0, 0, [5, 6, 7], [5, 6, 7, 1, 2, 3, 4])); + assert(test([1, 2, 3, 4], 0, 2, cast(int[])[], [3, 4])); + assert(test([1, 2, 3, 4], 0, 4, [5, 6, 7], [5, 6, 7])); + assert(test([1, 2, 3, 4], 0, 2, [5, 6, 7], [5, 6, 7, 3, 4])); + assert(test([1, 2, 3, 4], 2, 4, [5, 6, 7], [1, 2, 5, 6, 7])); + + assert(test([1, 2, 3, 4], 0, 0, filter!"true"([5, 6, 7]), [5, 6, 7, 1, 2, 3, 4])); + assert(test([1, 2, 3, 4], 0, 2, filter!"true"(cast(int[])[]), [3, 4])); + assert(test([1, 2, 3, 4], 0, 4, filter!"true"([5, 6, 7]), [5, 6, 7])); + assert(test([1, 2, 3, 4], 0, 2, filter!"true"([5, 6, 7]), [5, 6, 7, 3, 4])); + assert(test([1, 2, 3, 4], 2, 4, filter!"true"([5, 6, 7]), [1, 2, 5, 6, 7])); + + void testStr(T, U)(string file = __FILE__, size_t line = __LINE__) + { + + auto l = to!T("hello"); + auto r = to!U(" world"); + + enforce(test(l, 0, 0, r, " worldhello"), + new AssertError("testStr failure 1", file, line)); + enforce(test(l, 0, 3, r, " worldlo"), + new AssertError("testStr failure 2", file, line)); + enforce(test(l, 3, l.length, r, "hel world"), + new AssertError("testStr failure 3", file, line)); + enforce(test(l, 0, l.length, r, " world"), + new AssertError("testStr failure 4", file, line)); + enforce(test(l, l.length, l.length, r, "hello world"), + new AssertError("testStr failure 5", file, line)); + } + + testStr!(string, string)(); + testStr!(string, wstring)(); + testStr!(string, dstring)(); + testStr!(wstring, string)(); + testStr!(wstring, wstring)(); + testStr!(wstring, dstring)(); + testStr!(dstring, string)(); + testStr!(dstring, wstring)(); + testStr!(dstring, dstring)(); +} + +/++ + Replaces the first occurrence of `from` with `to` in `subject`. + + Params: + subject = the array to scan + from = the item to replace + to = the item to replace `from` with + + Returns: + A new array without changing the contents of $(D subject), or the original + array if no match is found. + +/ +E[] replaceFirst(E, R1, R2)(E[] subject, R1 from, R2 to) +if (isDynamicArray!(E[]) && + isForwardRange!R1 && is(typeof(appender!(E[])().put(from[0 .. 1]))) && + isForwardRange!R2 && is(typeof(appender!(E[])().put(to[0 .. 1])))) +{ + if (from.empty) return subject; + static if (isSomeString!(E[])) + { + import std.string : indexOf; + immutable idx = subject.indexOf(from); + } + else + { + import std.algorithm.searching : countUntil; + immutable idx = subject.countUntil(from); + } + if (idx == -1) + return subject; + + auto app = appender!(E[])(); + app.put(subject[0 .. idx]); + app.put(to); + + static if (isSomeString!(E[]) && isSomeString!R1) + { + import std.utf : codeLength; + immutable fromLength = codeLength!(Unqual!E, R1)(from); + } + else + immutable fromLength = from.length; + + app.put(subject[idx + fromLength .. $]); + + return app.data; +} + +/// +@safe unittest +{ + auto a = [1, 2, 2, 3, 4, 5]; + auto b = a.replaceFirst([2], [1337]); + assert(b == [1, 1337, 2, 3, 4, 5]); + + auto s = "This is a foo foo list"; + auto r = s.replaceFirst("foo", "silly"); + assert(r == "This is a silly foo list"); +} + +@safe unittest +{ + import std.algorithm.comparison : cmp; + import std.conv : to; + + foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[], + const(char[]), immutable(char[]))) + { + foreach (T; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[], + const(char[]), immutable(char[]))) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + auto s = to!S("This is a foo foo list"); + auto s2 = to!S("Thüs is a ßöö foo list"); + auto from = to!T("foo"); + auto from2 = to!T("ßöö"); + auto into = to!T("silly"); + auto into2 = to!T("sälly"); + + S r1 = replaceFirst(s, from, into); + assert(cmp(r1, "This is a silly foo list") == 0); + + S r11 = replaceFirst(s2, from2, into2); + assert(cmp(r11, "Thüs is a sälly foo list") == 0, + to!string(r11) ~ " : " ~ S.stringof ~ " " ~ T.stringof); + + S r2 = replaceFirst(r1, from, into); + assert(cmp(r2, "This is a silly silly list") == 0); + + S r3 = replaceFirst(s, to!T(""), into); + assert(cmp(r3, "This is a foo foo list") == 0); + + assert(replaceFirst(r3, to!T("won't find"), to!T("whatever")) is r3); + }(); + } +} + +//Bug# 8187 +@safe unittest +{ + auto res = ["a", "a"]; + assert(replace(res, "a", "b") == ["b", "b"]); + assert(replaceFirst(res, "a", "b") == ["b", "a"]); +} + +/++ + Replaces the last occurrence of `from` with `to` in `subject`. + + Params: + subject = the array to scan + from = the item to replace + to = the item to replace `from` with + + Returns: + A new array without changing the contents of $(D subject), or the original + array if no match is found. + +/ +E[] replaceLast(E, R1, R2)(E[] subject, R1 from , R2 to) +if (isDynamicArray!(E[]) && + isForwardRange!R1 && is(typeof(appender!(E[])().put(from[0 .. 1]))) && + isForwardRange!R2 && is(typeof(appender!(E[])().put(to[0 .. 1])))) +{ + import std.range : retro; + if (from.empty) return subject; + static if (isSomeString!(E[])) + { + import std.string : lastIndexOf; + auto idx = subject.lastIndexOf(from); + } + else + { + import std.algorithm.searching : countUntil; + auto idx = retro(subject).countUntil(retro(from)); + } + + if (idx == -1) + return subject; + + static if (isSomeString!(E[]) && isSomeString!R1) + { + import std.utf : codeLength; + auto fromLength = codeLength!(Unqual!E, R1)(from); + } + else + auto fromLength = from.length; + + auto app = appender!(E[])(); + static if (isSomeString!(E[])) + app.put(subject[0 .. idx]); + else + app.put(subject[0 .. $ - idx - fromLength]); + + app.put(to); + + static if (isSomeString!(E[])) + app.put(subject[idx+fromLength .. $]); + else + app.put(subject[$ - idx .. $]); + + return app.data; +} + +/// +@safe unittest +{ + auto a = [1, 2, 2, 3, 4, 5]; + auto b = a.replaceLast([2], [1337]); + assert(b == [1, 2, 1337, 3, 4, 5]); + + auto s = "This is a foo foo list"; + auto r = s.replaceLast("foo", "silly"); + assert(r == "This is a foo silly list", r); +} + +@safe unittest +{ + import std.algorithm.comparison : cmp; + import std.conv : to; + + foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[], + const(char[]), immutable(char[]))) + { + foreach (T; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[], + const(char[]), immutable(char[]))) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + auto s = to!S("This is a foo foo list"); + auto s2 = to!S("Thüs is a ßöö ßöö list"); + auto from = to!T("foo"); + auto from2 = to!T("ßöö"); + auto into = to!T("silly"); + auto into2 = to!T("sälly"); + + S r1 = replaceLast(s, from, into); + assert(cmp(r1, "This is a foo silly list") == 0, to!string(r1)); + + S r11 = replaceLast(s2, from2, into2); + assert(cmp(r11, "Thüs is a ßöö sälly list") == 0, + to!string(r11) ~ " : " ~ S.stringof ~ " " ~ T.stringof); + + S r2 = replaceLast(r1, from, into); + assert(cmp(r2, "This is a silly silly list") == 0); + + S r3 = replaceLast(s, to!T(""), into); + assert(cmp(r3, "This is a foo foo list") == 0); + + assert(replaceLast(r3, to!T("won't find"), to!T("whatever")) is r3); + }(); + } +} + +/++ + Creates a new array such that the items in `slice` are replaced with the + items in `replacement`. `slice` and `replacement` do not need to be the + same length. The result will grow or shrink based on the items given. + + Params: + s = the base of the new array + slice = the slice of `s` to be replaced + replacement = the items to replace `slice` with + + Returns: + A new array that is `s` with `slice` replaced by + `replacement[]`. + +/ +inout(T)[] replaceSlice(T)(inout(T)[] s, in T[] slice, in T[] replacement) +in +{ + // Verify that slice[] really is a slice of s[] + assert(overlap(s, slice) is slice); +} +body +{ + auto result = new T[s.length - slice.length + replacement.length]; + immutable so = slice.ptr - s.ptr; + result[0 .. so] = s[0 .. so]; + result[so .. so + replacement.length] = replacement[]; + result[so + replacement.length .. result.length] = + s[so + slice.length .. s.length]; + + return cast(inout(T)[]) result; +} + +/// +@system unittest +{ + auto a = [1, 2, 3, 4, 5]; + auto b = replaceSlice(a, a[1 .. 4], [0, 0, 0]); + + assert(b == [1, 0, 0, 0, 5]); +} + +@system unittest +{ + import std.algorithm.comparison : cmp; + + string s = "hello"; + string slice = s[2 .. 4]; + + auto r = replaceSlice(s, slice, "bar"); + int i; + i = cmp(r, "hebaro"); + assert(i == 0); +} + +/** +Implements an output range that appends data to an array. This is +recommended over $(D array ~= data) when appending many elements because it is more +efficient. `Appender` maintains its own array metadata locally, so it can avoid +global locking for each append where $(LREF capacity) is non-zero. +See_Also: $(LREF appender) + */ +struct Appender(A) +if (isDynamicArray!A) +{ + import core.memory : GC; + + private alias T = ElementEncodingType!A; + + private struct Data + { + size_t capacity; + Unqual!T[] arr; + bool canExtend = false; + } + + private Data* _data; + + /** + * Constructs an `Appender` with a given array. Note that this does not copy the + * data. If the array has a larger capacity as determined by `arr.capacity`, + * it will be used by the appender. After initializing an appender on an array, + * appending to the original array will reallocate. + */ + this(A arr) @trusted pure nothrow + { + // initialize to a given array. + _data = new Data; + _data.arr = cast(Unqual!T[]) arr; //trusted + + if (__ctfe) + return; + + // We want to use up as much of the block the array is in as possible. + // if we consume all the block that we can, then array appending is + // safe WRT built-in append, and we can use the entire block. + // We only do this for mutable types that can be extended. + static if (isMutable!T && is(typeof(arr.length = size_t.max))) + { + immutable cap = arr.capacity; //trusted + // Replace with "GC.setAttr( Not Appendable )" once pure (and fixed) + if (cap > arr.length) + arr.length = cap; + } + _data.capacity = arr.length; + } + + /** + * Reserve at least newCapacity elements for appending. Note that more elements + * may be reserved than requested. If `newCapacity <= capacity`, then nothing is + * done. + */ + void reserve(size_t newCapacity) @safe pure nothrow + { + if (_data) + { + if (newCapacity > _data.capacity) + ensureAddable(newCapacity - _data.arr.length); + } + else + { + ensureAddable(newCapacity); + } + } + + /** + * Returns: the capacity of the array (the maximum number of elements the + * managed array can accommodate before triggering a reallocation). If any + * appending will reallocate, `0` will be returned. + */ + @property size_t capacity() const @safe pure nothrow + { + return _data ? _data.capacity : 0; + } + + /** + * Returns: The managed array. + */ + @property inout(ElementEncodingType!A)[] data() inout @trusted pure nothrow + { + /* @trusted operation: + * casting Unqual!T[] to inout(T)[] + */ + return cast(typeof(return))(_data ? _data.arr : null); + } + + // ensure we can add nelems elements, resizing as necessary + private void ensureAddable(size_t nelems) @trusted pure nothrow + { + if (!_data) + _data = new Data; + immutable len = _data.arr.length; + immutable reqlen = len + nelems; + + if (_data.capacity >= reqlen) + return; + + // need to increase capacity + if (__ctfe) + { + static if (__traits(compiles, new Unqual!T[1])) + { + _data.arr.length = reqlen; + } + else + { + // avoid restriction of @disable this() + _data.arr = _data.arr[0 .. _data.capacity]; + foreach (i; _data.capacity .. reqlen) + _data.arr ~= Unqual!T.init; + } + _data.arr = _data.arr[0 .. len]; + _data.capacity = reqlen; + } + else + { + // Time to reallocate. + // We need to almost duplicate what's in druntime, except we + // have better access to the capacity field. + auto newlen = appenderNewCapacity!(T.sizeof)(_data.capacity, reqlen); + // first, try extending the current block + if (_data.canExtend) + { + immutable u = GC.extend(_data.arr.ptr, nelems * T.sizeof, (newlen - len) * T.sizeof); + if (u) + { + // extend worked, update the capacity + _data.capacity = u / T.sizeof; + return; + } + } + + + // didn't work, must reallocate + import core.checkedint : mulu; + bool overflow; + const nbytes = mulu(newlen, T.sizeof, overflow); + if (overflow) assert(0); + + auto bi = GC.qalloc(nbytes, blockAttribute!T); + _data.capacity = bi.size / T.sizeof; + import core.stdc.string : memcpy; + if (len) + memcpy(bi.base, _data.arr.ptr, len * T.sizeof); + _data.arr = (cast(Unqual!T*) bi.base)[0 .. len]; + _data.canExtend = true; + // leave the old data, for safety reasons + } + } + + private template canPutItem(U) + { + enum bool canPutItem = + isImplicitlyConvertible!(U, T) || + isSomeChar!T && isSomeChar!U; + } + private template canPutConstRange(Range) + { + enum bool canPutConstRange = + isInputRange!(Unqual!Range) && + !isInputRange!Range && + is(typeof(Appender.init.put(Range.init.front))); + } + private template canPutRange(Range) + { + enum bool canPutRange = + isInputRange!Range && + is(typeof(Appender.init.put(Range.init.front))); + } + + /** + * Appends `item` to the managed array. + */ + void put(U)(U item) if (canPutItem!U) + { + static if (isSomeChar!T && isSomeChar!U && T.sizeof < U.sizeof) + { + /* may throwable operation: + * - std.utf.encode + */ + // must do some transcoding around here + import std.utf : encode; + Unqual!T[T.sizeof == 1 ? 4 : 2] encoded; + auto len = encode(encoded, item); + put(encoded[0 .. len]); + } + else + { + import std.conv : emplaceRef; + + ensureAddable(1); + immutable len = _data.arr.length; + + auto bigData = (() @trusted => _data.arr.ptr[0 .. len + 1])(); + emplaceRef!(Unqual!T)(bigData[len], cast(Unqual!T) item); + //We do this at the end, in case of exceptions + _data.arr = bigData; + } + } + + // Const fixing hack. + void put(Range)(Range items) if (canPutConstRange!Range) + { + alias p = put!(Unqual!Range); + p(items); + } + + /** + * Appends an entire range to the managed array. + */ + void put(Range)(Range items) if (canPutRange!Range) + { + // note, we disable this branch for appending one type of char to + // another because we can't trust the length portion. + static if (!(isSomeChar!T && isSomeChar!(ElementType!Range) && + !is(immutable Range == immutable T[])) && + is(typeof(items.length) == size_t)) + { + // optimization -- if this type is something other than a string, + // and we are adding exactly one element, call the version for one + // element. + static if (!isSomeChar!T) + { + if (items.length == 1) + { + put(items.front); + return; + } + } + + // make sure we have enough space, then add the items + @trusted auto bigDataFun(size_t extra) + { + ensureAddable(extra); + return _data.arr.ptr[0 .. _data.arr.length + extra]; + } + auto bigData = bigDataFun(items.length); + + immutable len = _data.arr.length; + immutable newlen = bigData.length; + + alias UT = Unqual!T; + + static if (is(typeof(_data.arr[] = items[])) && + !hasElaborateAssign!UT && isAssignable!(UT, ElementEncodingType!Range)) + { + bigData[len .. newlen] = items[]; + } + else + { + import std.conv : emplaceRef; + foreach (ref it ; bigData[len .. newlen]) + { + emplaceRef!T(it, items.front); + items.popFront(); + } + } + + //We do this at the end, in case of exceptions + _data.arr = bigData; + } + else + { + //pragma(msg, Range.stringof); + // Generic input range + for (; !items.empty; items.popFront()) + { + put(items.front); + } + } + } + + /** + * Appends `rhs` to the managed array. + * Params: + * rhs = Element or range. + */ + void opOpAssign(string op : "~", U)(U rhs) + if (__traits(compiles, put(rhs))) + { + put(rhs); + } + + // only allow overwriting data on non-immutable and non-const data + static if (isMutable!T) + { + /** + * Clears the managed array. This allows the elements of the array to be reused + * for appending. + * + * Note: clear is disabled for immutable or const element types, due to the + * possibility that $(D Appender) might overwrite immutable data. + */ + void clear() @trusted pure nothrow + { + if (_data) + { + _data.arr = _data.arr.ptr[0 .. 0]; + } + } + + /** + * Shrinks the managed array to the given length. + * + * Throws: $(D Exception) if newlength is greater than the current array length. + * Note: shrinkTo is disabled for immutable or const element types. + */ + void shrinkTo(size_t newlength) @trusted pure + { + import std.exception : enforce; + if (_data) + { + enforce(newlength <= _data.arr.length, "Attempting to shrink Appender with newlength > length"); + _data.arr = _data.arr.ptr[0 .. newlength]; + } + else + enforce(newlength == 0, "Attempting to shrink empty Appender with non-zero newlength"); + } + } + + void toString(Writer)(scope Writer w) + { + import std.format : formattedWrite; + w.formattedWrite(typeof(this).stringof ~ "(%s)", data); + } +} + +/// +@safe unittest +{ + auto app = appender!string(); + string b = "abcdefg"; + foreach (char c; b) + app.put(c); + assert(app.data == "abcdefg"); + + int[] a = [ 1, 2 ]; + auto app2 = appender(a); + app2.put(3); + app2.put([ 4, 5, 6 ]); + assert(app2.data == [ 1, 2, 3, 4, 5, 6 ]); +} + +@safe unittest +{ + import std.format : format; + auto app = appender!(int[])(); + app.put(1); + app.put(2); + app.put(3); + assert("%s".format(app) == "Appender!(int[])(%s)".format([1,2,3])); +} + +@safe unittest // issue 17251 +{ + static struct R + { + int front() const { return 0; } + bool empty() const { return true; } + void popFront() {} + } + + auto app = appender!(R[]); + const(R)[1] r; + app.put(r[0]); + app.put(r[]); +} + +//Calculates an efficient growth scheme based on the old capacity +//of data, and the minimum requested capacity. +//arg curLen: The current length +//arg reqLen: The length as requested by the user +//ret sugLen: A suggested growth. +private size_t appenderNewCapacity(size_t TSizeOf)(size_t curLen, size_t reqLen) @safe pure nothrow +{ + import core.bitop : bsr; + import std.algorithm.comparison : max; + if (curLen == 0) + return max(reqLen,8); + ulong mult = 100 + (1000UL) / (bsr(curLen * TSizeOf) + 1); + // limit to doubling the length, we don't want to grow too much + if (mult > 200) + mult = 200; + auto sugLen = cast(size_t)((curLen * mult + 99) / 100); + return max(reqLen, sugLen); +} + +/** + * A version of $(LREF Appender) that can update an array in-place. + * It forwards all calls to an underlying appender implementation. + * Any calls made to the appender also update the pointer to the + * original array passed in. + * + * Tip: Use the `arrayPtr` overload of $(LREF appender) for construction with type-inference. + */ +struct RefAppender(A) +if (isDynamicArray!A) +{ + private + { + Appender!A impl; + A* arr; + } + + /** + * Constructs a `RefAppender` with a given array reference. This does not copy the + * data. If the array has a larger capacity as determined by `arr.capacity`, it + * will be used by the appender. + * + * Note: Do not use built-in appending (i.e. `~=`) on the original array + * until you are done with the appender, because subsequent calls to the appender + * will reallocate the array data without those appends. + * + * Params: + * arr = Pointer to an array. Must not be _null. + */ + this(A* arr) + { + impl = Appender!A(*arr); + this.arr = arr; + } + + /** Wraps remaining `Appender` methods such as $(LREF put). + * Params: + * fn = Method name to call. + * args = Arguments to pass to the method. + */ + void opDispatch(string fn, Args...)(Args args) + if (__traits(compiles, (Appender!A a) => mixin("a." ~ fn ~ "(args)"))) + { + // we do it this way because we can't cache a void return + scope(exit) *this.arr = impl.data; + mixin("return impl." ~ fn ~ "(args);"); + } + + /** + * Appends `rhs` to the managed array. + * Params: + * rhs = Element or range. + */ + void opOpAssign(string op : "~", U)(U rhs) + if (__traits(compiles, (Appender!A a){ a.put(rhs); })) + { + scope(exit) *this.arr = impl.data; + impl.put(rhs); + } + + /** + * Returns the capacity of the array (the maximum number of elements the + * managed array can accommodate before triggering a reallocation). If any + * appending will reallocate, $(D capacity) returns $(D 0). + */ + @property size_t capacity() const + { + return impl.capacity; + } + + /** + * Returns the managed array. + */ + @property inout(ElementEncodingType!A)[] data() inout + { + return impl.data; + } +} + +/// +@system pure nothrow +unittest +{ + int[] a = [1, 2]; + auto app2 = appender(&a); + assert(app2.data == [1, 2]); + assert(a == [1, 2]); + app2 ~= 3; + app2 ~= [4, 5, 6]; + assert(app2.data == [1, 2, 3, 4, 5, 6]); + assert(a == [1, 2, 3, 4, 5, 6]); + + app2.reserve(5); + assert(app2.capacity >= 5); +} + +/++ + Convenience function that returns an $(LREF Appender) instance, + optionally initialized with $(D array). + +/ +Appender!A appender(A)() +if (isDynamicArray!A) +{ + return Appender!A(null); +} +/// ditto +Appender!(E[]) appender(A : E[], E)(auto ref A array) +{ + static assert(!isStaticArray!A || __traits(isRef, array), + "Cannot create Appender from an rvalue static array"); + + return Appender!(E[])(array); +} + +@safe pure nothrow unittest +{ + import std.exception; + { + auto app = appender!(char[])(); + string b = "abcdefg"; + foreach (char c; b) app.put(c); + assert(app.data == "abcdefg"); + } + { + auto app = appender!(char[])(); + string b = "abcdefg"; + foreach (char c; b) app ~= c; + assert(app.data == "abcdefg"); + } + { + int[] a = [ 1, 2 ]; + auto app2 = appender(a); + assert(app2.data == [ 1, 2 ]); + app2.put(3); + app2.put([ 4, 5, 6 ][]); + assert(app2.data == [ 1, 2, 3, 4, 5, 6 ]); + app2.put([ 7 ]); + assert(app2.data == [ 1, 2, 3, 4, 5, 6, 7 ]); + } + + int[] a = [ 1, 2 ]; + auto app2 = appender(a); + assert(app2.data == [ 1, 2 ]); + app2 ~= 3; + app2 ~= [ 4, 5, 6 ][]; + assert(app2.data == [ 1, 2, 3, 4, 5, 6 ]); + app2 ~= [ 7 ]; + assert(app2.data == [ 1, 2, 3, 4, 5, 6, 7 ]); + + app2.reserve(5); + assert(app2.capacity >= 5); + + try // shrinkTo may throw + { + app2.shrinkTo(3); + } + catch (Exception) assert(0); + assert(app2.data == [ 1, 2, 3 ]); + assertThrown(app2.shrinkTo(5)); + + const app3 = app2; + assert(app3.capacity >= 3); + assert(app3.data == [1, 2, 3]); + + auto app4 = appender([]); + try // shrinkTo may throw + { + app4.shrinkTo(0); + } + catch (Exception) assert(0); + + // Issue 5663 & 9725 tests + foreach (S; AliasSeq!(char[], const(char)[], string)) + { + { + Appender!S app5663i; + assertNotThrown(app5663i.put("\xE3")); + assert(app5663i.data == "\xE3"); + + Appender!S app5663c; + assertNotThrown(app5663c.put(cast(const(char)[])"\xE3")); + assert(app5663c.data == "\xE3"); + + Appender!S app5663m; + assertNotThrown(app5663m.put("\xE3".dup)); + assert(app5663m.data == "\xE3"); + } + // ditto for ~= + { + Appender!S app5663i; + assertNotThrown(app5663i ~= "\xE3"); + assert(app5663i.data == "\xE3"); + + Appender!S app5663c; + assertNotThrown(app5663c ~= cast(const(char)[])"\xE3"); + assert(app5663c.data == "\xE3"); + + Appender!S app5663m; + assertNotThrown(app5663m ~= "\xE3".dup); + assert(app5663m.data == "\xE3"); + } + } + + static struct S10122 + { + int val; + + @disable this(); + this(int v) @safe pure nothrow { val = v; } + } + assertCTFEable!( + { + auto w = appender!(S10122[])(); + w.put(S10122(1)); + assert(w.data.length == 1 && w.data[0].val == 1); + }); +} + +/// +@safe pure nothrow +unittest +{ + auto w = appender!string; + // pre-allocate space for at least 10 elements (this avoids costly reallocations) + w.reserve(10); + assert(w.capacity >= 10); + + w.put('a'); // single elements + w.put("bc"); // multiple elements + + // use the append syntax + w ~= 'd'; + w ~= "ef"; + + assert(w.data == "abcdef"); +} + +@safe pure nothrow unittest +{ + { + auto w = appender!string(); + w.reserve(4); + cast(void) w.capacity; + cast(void) w.data; + try + { + wchar wc = 'a'; + dchar dc = 'a'; + w.put(wc); // decoding may throw + w.put(dc); // decoding may throw + } + catch (Exception) assert(0); + } + { + auto w = appender!(int[])(); + w.reserve(4); + cast(void) w.capacity; + cast(void) w.data; + w.put(10); + w.put([10]); + w.clear(); + try + { + w.shrinkTo(0); + } + catch (Exception) assert(0); + + struct N + { + int payload; + alias payload this; + } + w.put(N(1)); + w.put([N(2)]); + + struct S(T) + { + @property bool empty() { return true; } + @property T front() { return T.init; } + void popFront() {} + } + S!int r; + w.put(r); + } +} + +@safe unittest +{ + import std.algorithm; + import std.typecons; + //10690 + [tuple(1)].filter!(t => true).array; // No error + [tuple("A")].filter!(t => true).array; // error +} + +@system unittest +{ + import std.range; + //Coverage for put(Range) + struct S1 + { + } + struct S2 + { + void opAssign(S2){} + } + auto a1 = Appender!(S1[])(); + auto a2 = Appender!(S2[])(); + auto au1 = Appender!(const(S1)[])(); + auto au2 = Appender!(const(S2)[])(); + a1.put(S1().repeat().take(10)); + a2.put(S2().repeat().take(10)); + auto sc1 = const(S1)(); + auto sc2 = const(S2)(); + au1.put(sc1.repeat().take(10)); + au2.put(sc2.repeat().take(10)); +} + +@system unittest +{ + struct S + { + int* p; + } + + auto a0 = Appender!(S[])(); + auto a1 = Appender!(const(S)[])(); + auto a2 = Appender!(immutable(S)[])(); + auto s0 = S(null); + auto s1 = const(S)(null); + auto s2 = immutable(S)(null); + a1.put(s0); + a1.put(s1); + a1.put(s2); + a1.put([s0]); + a1.put([s1]); + a1.put([s2]); + a0.put(s0); + static assert(!is(typeof(a0.put(a1)))); + static assert(!is(typeof(a0.put(a2)))); + a0.put([s0]); + static assert(!is(typeof(a0.put([a1])))); + static assert(!is(typeof(a0.put([a2])))); + static assert(!is(typeof(a2.put(a0)))); + static assert(!is(typeof(a2.put(a1)))); + a2.put(s2); + static assert(!is(typeof(a2.put([a0])))); + static assert(!is(typeof(a2.put([a1])))); + a2.put([s2]); +} + +@safe unittest +{ + //9528 + const(E)[] fastCopy(E)(E[] src) { + auto app = appender!(const(E)[])(); + foreach (i, e; src) + app.put(e); + return app.data; + } + + class C {} + struct S { const(C) c; } + S[] s = [ S(new C) ]; + + auto t = fastCopy(s); // Does not compile +} + +@safe unittest +{ + import std.algorithm.iteration : map; + //10753 + struct Foo { + immutable dchar d; + } + struct Bar { + immutable int x; + } + "12".map!Foo.array; + [1, 2].map!Bar.array; +} + +@safe unittest +{ + //New appender signature tests + alias mutARR = int[]; + alias conARR = const(int)[]; + alias immARR = immutable(int)[]; + + mutARR mut; + conARR con; + immARR imm; + + {auto app = Appender!mutARR(mut);} //Always worked. Should work. Should not create a warning. + static assert(!is(typeof(Appender!mutARR(con)))); //Never worked. Should not work. + static assert(!is(typeof(Appender!mutARR(imm)))); //Never worked. Should not work. + + {auto app = Appender!conARR(mut);} //Always worked. Should work. Should not create a warning. + {auto app = Appender!conARR(con);} //Didn't work. Now works. Should not create a warning. + {auto app = Appender!conARR(imm);} //Didn't work. Now works. Should not create a warning. + + //{auto app = Appender!immARR(mut);} //Worked. Will cease to work. Creates warning. + //static assert(!is(typeof(Appender!immARR(mut)))); //Worked. Will cease to work. Uncomment me after full deprecation. + static assert(!is(typeof(Appender!immARR(con)))); //Never worked. Should not work. + {auto app = Appender!immARR(imm);} //Didn't work. Now works. Should not create a warning. + + //Deprecated. Please uncomment and make sure this doesn't work: + //char[] cc; + //static assert(!is(typeof(Appender!string(cc)))); + + //This should always work: + {auto app = appender!string(null);} + {auto app = appender!(const(char)[])(null);} + {auto app = appender!(char[])(null);} +} + +@safe unittest //Test large allocations (for GC.extend) +{ + import std.algorithm.comparison : equal; + import std.range; + Appender!(char[]) app; + app.reserve(1); //cover reserve on non-initialized + foreach (_; 0 .. 100_000) + app.put('a'); + assert(equal(app.data, 'a'.repeat(100_000))); +} + +@safe unittest +{ + auto reference = new ubyte[](2048 + 1); //a number big enough to have a full page (EG: the GC extends) + auto arr = reference.dup; + auto app = appender(arr[0 .. 0]); + app.reserve(1); //This should not trigger a call to extend + app.put(ubyte(1)); //Don't clobber arr + assert(reference[] == arr[]); +} + +@safe unittest // clear method is supported only for mutable element types +{ + Appender!string app; + static assert(!__traits(compiles, app.clear())); +} + +@safe unittest +{ + static struct D//dynamic + { + int[] i; + alias i this; + } + static struct S//static + { + int[5] i; + alias i this; + } + static assert(!is(Appender!(char[5]))); + static assert(!is(Appender!D)); + static assert(!is(Appender!S)); + + enum int[5] a = []; + int[5] b; + D d; + S s; + int[5] foo(){return a;} + + static assert(!is(typeof(appender(a)))); + static assert( is(typeof(appender(b)))); + static assert( is(typeof(appender(d)))); + static assert( is(typeof(appender(s)))); + static assert(!is(typeof(appender(foo())))); +} + +@system unittest +{ + // Issue 13077 + static class A {} + + // reduced case + auto w = appender!(shared(A)[])(); + w.put(new shared A()); + + // original case + import std.range; + InputRange!(shared A) foo() + { + return [new shared A].inputRangeObject; + } + auto res = foo.array; +} + +/++ + Convenience function that returns a $(LREF RefAppender) instance initialized + with `arrayPtr`. Don't use null for the array pointer, use the other + version of $(D appender) instead. + +/ +RefAppender!(E[]) appender(P : E[]*, E)(P arrayPtr) +{ + return RefAppender!(E[])(arrayPtr); +} + +/// +@system pure nothrow +unittest +{ + int[] a = [1, 2]; + auto app2 = appender(&a); + assert(app2.data == [1, 2]); + assert(a == [1, 2]); + app2 ~= 3; + app2 ~= [4, 5, 6]; + assert(app2.data == [1, 2, 3, 4, 5, 6]); + assert(a == [1, 2, 3, 4, 5, 6]); + + app2.reserve(5); + assert(app2.capacity >= 5); +} + +@system unittest +{ + import std.exception; + { + auto arr = new char[0]; + auto app = appender(&arr); + string b = "abcdefg"; + foreach (char c; b) app.put(c); + assert(app.data == "abcdefg"); + assert(arr == "abcdefg"); + } + { + auto arr = new char[0]; + auto app = appender(&arr); + string b = "abcdefg"; + foreach (char c; b) app ~= c; + assert(app.data == "abcdefg"); + assert(arr == "abcdefg"); + } + { + int[] a = [ 1, 2 ]; + auto app2 = appender(&a); + assert(app2.data == [ 1, 2 ]); + assert(a == [ 1, 2 ]); + app2.put(3); + app2.put([ 4, 5, 6 ][]); + assert(app2.data == [ 1, 2, 3, 4, 5, 6 ]); + assert(a == [ 1, 2, 3, 4, 5, 6 ]); + } + + int[] a = [ 1, 2 ]; + auto app2 = appender(&a); + assert(app2.data == [ 1, 2 ]); + assert(a == [ 1, 2 ]); + app2 ~= 3; + app2 ~= [ 4, 5, 6 ][]; + assert(app2.data == [ 1, 2, 3, 4, 5, 6 ]); + assert(a == [ 1, 2, 3, 4, 5, 6 ]); + + app2.reserve(5); + assert(app2.capacity >= 5); + + try // shrinkTo may throw + { + app2.shrinkTo(3); + } + catch (Exception) assert(0); + assert(app2.data == [ 1, 2, 3 ]); + assertThrown(app2.shrinkTo(5)); + + const app3 = app2; + assert(app3.capacity >= 3); + assert(app3.data == [1, 2, 3]); +} + +@safe unittest // issue 14605 +{ + static assert(isOutputRange!(Appender!(int[]), int)); + static assert(isOutputRange!(RefAppender!(int[]), int)); +} + +@safe unittest +{ + Appender!(int[]) app; + short[] range = [1, 2, 3]; + app.put(range); + assert(app.data == [1, 2, 3]); +} + +@safe unittest +{ + string s = "hello".idup; + char[] a = "hello".dup; + auto appS = appender(s); + auto appA = appender(a); + put(appS, 'w'); + put(appA, 'w'); + s ~= 'a'; //Clobbers here? + a ~= 'a'; //Clobbers here? + assert(appS.data == "hellow"); + assert(appA.data == "hellow"); +} diff --git a/libphobos/src/std/ascii.d b/libphobos/src/std/ascii.d new file mode 100644 index 0000000..b430114 --- /dev/null +++ b/libphobos/src/std/ascii.d @@ -0,0 +1,729 @@ +// Written in the D programming language. + +/++ + Functions which operate on ASCII characters. + + All of the functions in std._ascii accept Unicode characters but + effectively ignore them if they're not ASCII. All $(D isX) functions return + $(D false) for non-ASCII characters, and all $(D toX) functions do nothing + to non-ASCII characters. + + For functions which operate on Unicode characters, see + $(MREF std, uni). + +$(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, +$(BOOKTABLE, +$(TR $(TH Category) $(TH Functions)) +$(TR $(TD Validation) $(TD + $(LREF isAlpha) + $(LREF isAlphaNum) + $(LREF isASCII) + $(LREF isControl) + $(LREF isDigit) + $(LREF isGraphical) + $(LREF isHexDigit) + $(LREF isOctalDigit) + $(LREF isPrintable) + $(LREF isPunctuation) + $(LREF isUpper) + $(LREF isWhite) +)) +$(TR $(TD Conversions) $(TD + $(LREF toLower) + $(LREF toUpper) +)) +$(TR $(TD Constants) $(TD + $(LREF digits) + $(LREF fullHexDigits) + $(LREF hexDigits) + $(LREF letters) + $(LREF lowercase) + $(LREF lowerHexDigits) + $(LREF newline) + $(LREF octalDigits) + $(LREF uppercase) + $(LREF whitespace) +)) +$(TR $(TD Enums) $(TD + $(LREF LetterCase) +)) +)) + References: + $(LINK2 http://www.digitalmars.com/d/ascii-table.html, ASCII Table), + $(HTTP en.wikipedia.org/wiki/Ascii, Wikipedia) + + License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + Authors: $(HTTP digitalmars.com, Walter Bright) and Jonathan M Davis + Source: $(PHOBOSSRC std/_ascii.d) + +/ +module std.ascii; + +version (unittest) +{ + // FIXME: When dmd bug #314 is fixed, make these selective. + import std.meta; // : AliasSeq; + import std.range; // : chain; + import std.traits; // : functionAttributes, FunctionAttribute, isSafe; +} + + +immutable fullHexDigits = "0123456789ABCDEFabcdef"; /// 0 .. 9A .. Fa .. f +immutable hexDigits = fullHexDigits[0 .. 16]; /// 0 .. 9A .. F +immutable lowerHexDigits = "0123456789abcdef"; /// 0 .. 9a .. f +immutable digits = hexDigits[0 .. 10]; /// 0 .. 9 +immutable octalDigits = digits[0 .. 8]; /// 0 .. 7 +immutable letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; /// A .. Za .. z +immutable uppercase = letters[0 .. 26]; /// A .. Z +immutable lowercase = letters[26 .. 52]; /// a .. z +immutable whitespace = " \t\v\r\n\f"; /// ASCII _whitespace + +/++ + Letter case specifier. + +/ +enum LetterCase : bool +{ + upper, /// Upper case letters + lower /// Lower case letters +} + +/// +@safe unittest +{ + import std.conv : to; + + assert(42.to!string(16, LetterCase.upper) == "2A"); + assert(42.to!string(16, LetterCase.lower) == "2a"); +} + +/// +@system unittest +{ + import std.digest.hmac : hmac; + import std.digest.digest : toHexString; + import std.digest.sha : SHA1; + import std.string : representation; + + const sha1HMAC = "A very long phrase".representation + .hmac!SHA1("secret".representation) + .toHexString!(LetterCase.lower); + assert(sha1HMAC == "49f2073c7bf58577e8c9ae59fe8cfd37c9ab94e5"); +} + +/// Newline sequence for this system. +version (Windows) + immutable newline = "\r\n"; +else version (Posix) + immutable newline = "\n"; +else + static assert(0, "Unsupported OS"); + + +/++ + Params: c = The character to test. + Returns: Whether $(D c) is a letter or a number (0 .. 9, a .. z, A .. Z). + +/ +bool isAlphaNum(dchar c) @safe pure nothrow @nogc +{ + return c <= 'z' && c >= '0' && (c <= '9' || c >= 'a' || (c >= 'A' && c <= 'Z')); +} + +/// +@safe pure nothrow @nogc unittest +{ + assert( isAlphaNum('A')); + assert( isAlphaNum('1')); + assert(!isAlphaNum('#')); + + // N.B.: does not return true for non-ASCII Unicode alphanumerics: + assert(!isAlphaNum('á')); +} + +@safe unittest +{ + foreach (c; chain(digits, octalDigits, fullHexDigits, letters, lowercase, uppercase)) + assert(isAlphaNum(c)); + + foreach (c; whitespace) + assert(!isAlphaNum(c)); +} + + +/++ + Params: c = The character to test. + Returns: Whether $(D c) is an ASCII letter (A .. Z, a .. z). + +/ +bool isAlpha(dchar c) @safe pure nothrow @nogc +{ + // Optimizer can turn this into a bitmask operation on 64 bit code + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); +} + +/// +@safe pure nothrow @nogc unittest +{ + assert( isAlpha('A')); + assert(!isAlpha('1')); + assert(!isAlpha('#')); + + // N.B.: does not return true for non-ASCII Unicode alphabetic characters: + assert(!isAlpha('á')); +} + +@safe unittest +{ + foreach (c; chain(letters, lowercase, uppercase)) + assert(isAlpha(c)); + + foreach (c; chain(digits, octalDigits, whitespace)) + assert(!isAlpha(c)); +} + + +/++ + Params: c = The character to test. + Returns: Whether $(D c) is a lowercase ASCII letter (a .. z). + +/ +bool isLower(dchar c) @safe pure nothrow @nogc +{ + return c >= 'a' && c <= 'z'; +} + +/// +@safe pure nothrow @nogc unittest +{ + assert( isLower('a')); + assert(!isLower('A')); + assert(!isLower('#')); + + // N.B.: does not return true for non-ASCII Unicode lowercase letters + assert(!isLower('á')); + assert(!isLower('Á')); +} + +@safe unittest +{ + foreach (c; lowercase) + assert(isLower(c)); + + foreach (c; chain(digits, uppercase, whitespace)) + assert(!isLower(c)); +} + + +/++ + Params: c = The character to test. + Returns: Whether $(D c) is an uppercase ASCII letter (A .. Z). + +/ +bool isUpper(dchar c) @safe pure nothrow @nogc +{ + return c <= 'Z' && 'A' <= c; +} + +/// +@safe pure nothrow @nogc unittest +{ + assert( isUpper('A')); + assert(!isUpper('a')); + assert(!isUpper('#')); + + // N.B.: does not return true for non-ASCII Unicode uppercase letters + assert(!isUpper('á')); + assert(!isUpper('Á')); +} + +@safe unittest +{ + foreach (c; uppercase) + assert(isUpper(c)); + + foreach (c; chain(digits, lowercase, whitespace)) + assert(!isUpper(c)); +} + + +/++ + Params: c = The character to test. + Returns: Whether $(D c) is a digit (0 .. 9). + +/ +bool isDigit(dchar c) @safe pure nothrow @nogc +{ + return '0' <= c && c <= '9'; +} + +/// +@safe pure nothrow @nogc unittest +{ + assert( isDigit('3')); + assert( isDigit('8')); + assert(!isDigit('B')); + assert(!isDigit('#')); + + // N.B.: does not return true for non-ASCII Unicode numbers + assert(!isDigit('0')); // full-width digit zero (U+FF10) + assert(!isDigit('4')); // full-width digit four (U+FF14) +} + +@safe unittest +{ + foreach (c; digits) + assert(isDigit(c)); + + foreach (c; chain(letters, whitespace)) + assert(!isDigit(c)); +} + + +/++ + Params: c = The character to test. + Returns: Whether $(D c) is a digit in base 8 (0 .. 7). + +/ +bool isOctalDigit(dchar c) @safe pure nothrow @nogc +{ + return c >= '0' && c <= '7'; +} + +/// +@safe pure nothrow @nogc unittest +{ + assert( isOctalDigit('0')); + assert( isOctalDigit('7')); + assert(!isOctalDigit('8')); + assert(!isOctalDigit('A')); + assert(!isOctalDigit('#')); +} + +@safe unittest +{ + foreach (c; octalDigits) + assert(isOctalDigit(c)); + + foreach (c; chain(letters, ['8', '9'], whitespace)) + assert(!isOctalDigit(c)); +} + + +/++ + Params: c = The character to test. + Returns: Whether $(D c) is a digit in base 16 (0 .. 9, A .. F, a .. f). + +/ +bool isHexDigit(dchar c) @safe pure nothrow @nogc +{ + return c <= 'f' && c >= '0' && (c <= '9' || c >= 'a' || (c >= 'A' && c <= 'F')); +} + +/// +@safe pure nothrow @nogc unittest +{ + assert( isHexDigit('0')); + assert( isHexDigit('A')); + assert( isHexDigit('f')); // lowercase hex digits are accepted + assert(!isHexDigit('g')); + assert(!isHexDigit('G')); + assert(!isHexDigit('#')); +} + +@safe unittest +{ + foreach (c; fullHexDigits) + assert(isHexDigit(c)); + + foreach (c; chain(lowercase[6 .. $], uppercase[6 .. $], whitespace)) + assert(!isHexDigit(c)); +} + + +/++ + Params: c = The character to test. + Returns: Whether or not $(D c) is a whitespace character. That includes the + space, tab, vertical tab, form feed, carriage return, and linefeed + characters. + +/ +bool isWhite(dchar c) @safe pure nothrow @nogc +{ + return c == ' ' || (c >= 0x09 && c <= 0x0D); +} + +/// +@safe pure nothrow @nogc unittest +{ + assert( isWhite(' ')); + assert( isWhite('\t')); + assert( isWhite('\n')); + assert(!isWhite('1')); + assert(!isWhite('a')); + assert(!isWhite('#')); + + // N.B.: Does not return true for non-ASCII Unicode whitespace characters. + static import std.uni; + assert(std.uni.isWhite('\u00A0')); + assert(!isWhite('\u00A0')); // std.ascii.isWhite +} + +@safe unittest +{ + foreach (c; whitespace) + assert(isWhite(c)); + + foreach (c; chain(digits, letters)) + assert(!isWhite(c)); +} + + +/++ + Params: c = The character to test. + Returns: Whether $(D c) is a control character. + +/ +bool isControl(dchar c) @safe pure nothrow @nogc +{ + return c < 0x20 || c == 0x7F; +} + +/// +@safe pure nothrow @nogc unittest +{ + assert( isControl('\0')); + assert( isControl('\022')); + assert( isControl('\n')); // newline is both whitespace and control + assert(!isControl(' ')); + assert(!isControl('1')); + assert(!isControl('a')); + assert(!isControl('#')); + + // N.B.: non-ASCII Unicode control characters are not recognized: + assert(!isControl('\u0080')); + assert(!isControl('\u2028')); + assert(!isControl('\u2029')); +} + +@safe unittest +{ + foreach (dchar c; 0 .. 32) + assert(isControl(c)); + assert(isControl(127)); + + foreach (c; chain(digits, letters, [' '])) + assert(!isControl(c)); +} + + +/++ + Params: c = The character to test. + Returns: Whether or not $(D c) is a punctuation character. That includes + all ASCII characters which are not control characters, letters, digits, or + whitespace. + +/ +bool isPunctuation(dchar c) @safe pure nothrow @nogc +{ + return c <= '~' && c >= '!' && !isAlphaNum(c); +} + +/// +@safe pure nothrow @nogc unittest +{ + assert( isPunctuation('.')); + assert( isPunctuation(',')); + assert( isPunctuation(':')); + assert( isPunctuation('!')); + assert( isPunctuation('#')); + assert( isPunctuation('~')); + assert( isPunctuation('+')); + assert( isPunctuation('_')); + + assert(!isPunctuation('1')); + assert(!isPunctuation('a')); + assert(!isPunctuation(' ')); + assert(!isPunctuation('\n')); + assert(!isPunctuation('\0')); + + // N.B.: Non-ASCII Unicode punctuation characters are not recognized. + assert(!isPunctuation('\u2012')); // (U+2012 = en-dash) +} + +@safe unittest +{ + foreach (dchar c; 0 .. 128) + { + if (isControl(c) || isAlphaNum(c) || c == ' ') + assert(!isPunctuation(c)); + else + assert(isPunctuation(c)); + } +} + + +/++ + Params: c = The character to test. + Returns: Whether or not $(D c) is a printable character other than the + space character. + +/ +bool isGraphical(dchar c) @safe pure nothrow @nogc +{ + return '!' <= c && c <= '~'; +} + +/// +@safe pure nothrow @nogc unittest +{ + assert( isGraphical('1')); + assert( isGraphical('a')); + assert( isGraphical('#')); + assert(!isGraphical(' ')); // whitespace is not graphical + assert(!isGraphical('\n')); + assert(!isGraphical('\0')); + + // N.B.: Unicode graphical characters are not regarded as such. + assert(!isGraphical('á')); +} + +@safe unittest +{ + foreach (dchar c; 0 .. 128) + { + if (isControl(c) || c == ' ') + assert(!isGraphical(c)); + else + assert(isGraphical(c)); + } +} + + +/++ + Params: c = The character to test. + Returns: Whether or not $(D c) is a printable character - including the + space character. + +/ +bool isPrintable(dchar c) @safe pure nothrow @nogc +{ + return c >= ' ' && c <= '~'; +} + +/// +@safe pure nothrow @nogc unittest +{ + assert( isPrintable(' ')); // whitespace is printable + assert( isPrintable('1')); + assert( isPrintable('a')); + assert( isPrintable('#')); + assert(!isPrintable('\0')); // control characters are not printable + + // N.B.: Printable non-ASCII Unicode characters are not recognized. + assert(!isPrintable('á')); +} + +@safe unittest +{ + foreach (dchar c; 0 .. 128) + { + if (isControl(c)) + assert(!isPrintable(c)); + else + assert(isPrintable(c)); + } +} + + +/++ + Params: c = The character to test. + Returns: Whether or not $(D c) is in the ASCII character set - i.e. in the + range 0 .. 0x7F. + +/ +pragma(inline, true) +bool isASCII(dchar c) @safe pure nothrow @nogc +{ + return c <= 0x7F; +} + +/// +@safe pure nothrow @nogc unittest +{ + assert( isASCII('a')); + assert(!isASCII('á')); +} + +@safe unittest +{ + foreach (dchar c; 0 .. 128) + assert(isASCII(c)); + + assert(!isASCII(128)); +} + + +/++ + Converts an ASCII letter to lowercase. + + Params: c = A character of any type that implicitly converts to $(D dchar). + In the case where it's a built-in type, or an enum of a built-in type, + $(D Unqual!(OriginalType!C)) is returned, whereas if it's a user-defined + type, $(D dchar) is returned. + + Returns: The corresponding lowercase letter, if $(D c) is an uppercase + ASCII character, otherwise $(D c) itself. + +/ +auto toLower(C)(C c) +if (is(C : dchar)) +{ + import std.traits : isAggregateType, OriginalType, Unqual; + + alias OC = OriginalType!C; + static if (isAggregateType!OC) + alias R = dchar; + else + alias R = Unqual!OC; + + return isUpper(c) ? cast(R)(cast(R) c + 'a' - 'A') : cast(R) c; +} + +/// +@safe pure nothrow @nogc unittest +{ + assert(toLower('a') == 'a'); + assert(toLower('A') == 'a'); + assert(toLower('#') == '#'); + + // N.B.: Non-ASCII Unicode uppercase letters are not converted. + assert(toLower('Á') == 'Á'); +} + +@safe pure nothrow unittest +{ + + foreach (C; AliasSeq!(char, wchar, dchar, immutable char, ubyte)) + { + foreach (i, c; uppercase) + assert(toLower(cast(C) c) == lowercase[i]); + + foreach (C c; 0 .. 128) + { + if (c < 'A' || c > 'Z') + assert(toLower(c) == c); + else + assert(toLower(c) != c); + } + + foreach (C c; 128 .. C.max) + assert(toLower(c) == c); + + //CTFE + static assert(toLower(cast(C)'a') == 'a'); + static assert(toLower(cast(C)'A') == 'a'); + } +} + + +/++ + Converts an ASCII letter to uppercase. + + Params: c = Any type which implicitly converts to $(D dchar). In the case + where it's a built-in type, or an enum of a built-in type, + $(D Unqual!(OriginalType!C)) is returned, whereas if it's a user-defined + type, $(D dchar) is returned. + + Returns: The corresponding uppercase letter, if $(D c) is a lowercase ASCII + character, otherwise $(D c) itself. + +/ +auto toUpper(C)(C c) +if (is(C : dchar)) +{ + import std.traits : isAggregateType, OriginalType, Unqual; + + alias OC = OriginalType!C; + static if (isAggregateType!OC) + alias R = dchar; + else + alias R = Unqual!OC; + + return isLower(c) ? cast(R)(cast(R) c - ('a' - 'A')) : cast(R) c; +} + +/// +@safe pure nothrow @nogc unittest +{ + assert(toUpper('a') == 'A'); + assert(toUpper('A') == 'A'); + assert(toUpper('#') == '#'); + + // N.B.: Non-ASCII Unicode lowercase letters are not converted. + assert(toUpper('á') == 'á'); +} + +@safe pure nothrow unittest +{ + foreach (C; AliasSeq!(char, wchar, dchar, immutable char, ubyte)) + { + foreach (i, c; lowercase) + assert(toUpper(cast(C) c) == uppercase[i]); + + foreach (C c; 0 .. 128) + { + if (c < 'a' || c > 'z') + assert(toUpper(c) == c); + else + assert(toUpper(c) != c); + } + + foreach (C c; 128 .. C.max) + assert(toUpper(c) == c); + + //CTFE + static assert(toUpper(cast(C)'a') == 'A'); + static assert(toUpper(cast(C)'A') == 'A'); + } +} + + +@safe unittest //Test both toUpper and toLower with non-builtin +{ + //User Defined [Char|Wchar|Dchar] + static struct UDC { char c; alias c this; } + static struct UDW { wchar c; alias c this; } + static struct UDD { dchar c; alias c this; } + //[Char|Wchar|Dchar] Enum + enum CE : char {a = 'a', A = 'A'} + enum WE : wchar {a = 'a', A = 'A'} + enum DE : dchar {a = 'a', A = 'A'} + //User Defined [Char|Wchar|Dchar] Enum + enum UDCE : UDC {a = UDC('a'), A = UDC('A')} + enum UDWE : UDW {a = UDW('a'), A = UDW('A')} + enum UDDE : UDD {a = UDD('a'), A = UDD('A')} + + //User defined types with implicit cast to dchar test. + foreach (Char; AliasSeq!(UDC, UDW, UDD)) + { + assert(toLower(Char('a')) == 'a'); + assert(toLower(Char('A')) == 'a'); + static assert(toLower(Char('a')) == 'a'); + static assert(toLower(Char('A')) == 'a'); + static assert(toUpper(Char('a')) == 'A'); + static assert(toUpper(Char('A')) == 'A'); + } + + //Various enum tests. + foreach (Enum; AliasSeq!(CE, WE, DE, UDCE, UDWE, UDDE)) + { + assert(toLower(Enum.a) == 'a'); + assert(toLower(Enum.A) == 'a'); + assert(toUpper(Enum.a) == 'A'); + assert(toUpper(Enum.A) == 'A'); + static assert(toLower(Enum.a) == 'a'); + static assert(toLower(Enum.A) == 'a'); + static assert(toUpper(Enum.a) == 'A'); + static assert(toUpper(Enum.A) == 'A'); + } + + //Return value type tests for enum of non-UDT. These should be the original type. + foreach (T; AliasSeq!(CE, WE, DE)) + { + alias C = OriginalType!T; + static assert(is(typeof(toLower(T.init)) == C)); + static assert(is(typeof(toUpper(T.init)) == C)); + } + + //Return value tests for UDT and enum of UDT. These should be dchar + foreach (T; AliasSeq!(UDC, UDW, UDD, UDCE, UDWE, UDDE)) + { + static assert(is(typeof(toLower(T.init)) == dchar)); + static assert(is(typeof(toUpper(T.init)) == dchar)); + } +} diff --git a/libphobos/src/std/base64.d b/libphobos/src/std/base64.d new file mode 100644 index 0000000..6211d10 --- /dev/null +++ b/libphobos/src/std/base64.d @@ -0,0 +1,2099 @@ +// Written in the D programming language. + +/** + * Support for Base64 encoding and decoding. + * + * This module provides two default implementations of Base64 encoding, + * $(LREF Base64) with a standard encoding alphabet, and a variant + * $(LREF Base64URL) that has a modified encoding alphabet designed to be + * safe for embedding in URLs and filenames. + * + * Both variants are implemented as instantiations of the template + * $(LREF Base64Impl). Most users will not need to use this template + * directly; however, it can be used to create customized Base64 encodings, + * such as one that omits padding characters, or one that is safe to embed + * inside a regular expression. + * + * Example: + * ----- + * ubyte[] data = [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]; + * + * const(char)[] encoded = Base64.encode(data); + * assert(encoded == "FPucA9l+"); + * + * ubyte[] decoded = Base64.decode("FPucA9l+"); + * assert(decoded == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]); + * ----- + * + * The range API is supported for both encoding and decoding: + * + * Example: + * ----- + * // Create MIME Base64 with CRLF, per line 76. + * File f = File("./text.txt", "r"); + * scope(exit) f.close(); + * + * Appender!string mime64 = appender!string; + * + * foreach (encoded; Base64.encoder(f.byChunk(57))) + * { + * mime64.put(encoded); + * mime64.put("\r\n"); + * } + * + * writeln(mime64.data); + * ----- + * + * References: + * $(LINK2 https://tools.ietf.org/html/rfc4648, RFC 4648 - The Base16, Base32, and Base64 + * Data Encodings) + * + * Copyright: Masahiro Nakagawa 2010-. + * License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Masahiro Nakagawa, Daniel Murphy (Single value Encoder and Decoder) + * Source: $(PHOBOSSRC std/_base64.d) + * Macros: + * LREF2=$(D $2) + */ +module std.base64; + +import std.exception; // enforce +import std.range.primitives; // isInputRange, isOutputRange, isForwardRange, ElementType, hasLength +import std.traits; // isArray + +// Make sure module header code examples work correctly. +@safe unittest +{ + ubyte[] data = [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]; + + const(char)[] encoded = Base64.encode(data); + assert(encoded == "FPucA9l+"); + + ubyte[] decoded = Base64.decode("FPucA9l+"); + assert(decoded == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]); +} + +/** + * Implementation of standard _Base64 encoding. + * + * See $(LREF Base64Impl) for a description of available methods. + */ +alias Base64 = Base64Impl!('+', '/'); + +/// +@safe unittest +{ + ubyte[] data = [0x83, 0xd7, 0x30, 0x7a, 0x01, 0x3f]; + assert(Base64.encode(data) == "g9cwegE/"); + assert(Base64.decode("g9cwegE/") == data); +} + + +/** + * Variation of Base64 encoding that is safe for use in URLs and filenames. + * + * See $(LREF Base64Impl) for a description of available methods. + */ +alias Base64URL = Base64Impl!('-', '_'); + +/// +@safe unittest +{ + ubyte[] data = [0x83, 0xd7, 0x30, 0x7a, 0x01, 0x3f]; + assert(Base64URL.encode(data) == "g9cwegE_"); + assert(Base64URL.decode("g9cwegE_") == data); +} + +/** + * Unpadded variation of Base64 encoding that is safe for use in URLs and + * filenames, as used in RFCs 4648 and 7515 (JWS/JWT/JWE). + * + * See $(LREF Base64Impl) for a description of available methods. + */ +alias Base64URLNoPadding = Base64Impl!('-', '_', Base64.NoPadding); + +/// +@safe unittest +{ + ubyte[] data = [0x83, 0xd7, 0x30, 0x7b, 0xef]; + assert(Base64URLNoPadding.encode(data) == "g9cwe-8"); + assert(Base64URLNoPadding.decode("g9cwe-8") == data); +} + +/** + * Template for implementing Base64 encoding and decoding. + * + * For most purposes, direct usage of this template is not necessary; instead, + * this module provides default implementations: $(LREF Base64), implementing + * basic Base64 encoding, and $(LREF Base64URL) and $(LREF Base64URLNoPadding), + * that implement the Base64 variant for use in URLs and filenames, with + * and without padding, respectively. + * + * Customized Base64 encoding schemes can be implemented by instantiating this + * template with the appropriate arguments. For example: + * + * ----- + * // Non-standard Base64 format for embedding in regular expressions. + * alias Base64Re = Base64Impl!('!', '=', Base64.NoPadding); + * ----- + * + * NOTE: + * Encoded strings will not have any padding if the $(D Padding) parameter is + * set to $(D NoPadding). + */ +template Base64Impl(char Map62th, char Map63th, char Padding = '=') +{ + enum NoPadding = '\0'; /// represents no-padding encoding + + + // Verify Base64 characters + static assert(Map62th < 'A' || Map62th > 'Z', "Character '" ~ Map62th ~ "' cannot be used twice"); + static assert(Map63th < 'A' || Map63th > 'Z', "Character '" ~ Map63th ~ "' cannot be used twice"); + static assert(Padding < 'A' || Padding > 'Z', "Character '" ~ Padding ~ "' cannot be used twice"); + static assert(Map62th < 'a' || Map62th > 'z', "Character '" ~ Map62th ~ "' cannot be used twice"); + static assert(Map63th < 'a' || Map63th > 'z', "Character '" ~ Map63th ~ "' cannot be used twice"); + static assert(Padding < 'a' || Padding > 'z', "Character '" ~ Padding ~ "' cannot be used twice"); + static assert(Map62th < '0' || Map62th > '9', "Character '" ~ Map62th ~ "' cannot be used twice"); + static assert(Map63th < '0' || Map63th > '9', "Character '" ~ Map63th ~ "' cannot be used twice"); + static assert(Padding < '0' || Padding > '9', "Character '" ~ Padding ~ "' cannot be used twice"); + static assert(Map62th != Map63th, "Character '" ~ Map63th ~ "' cannot be used twice"); + static assert(Map62th != Padding, "Character '" ~ Padding ~ "' cannot be used twice"); + static assert(Map63th != Padding, "Character '" ~ Padding ~ "' cannot be used twice"); + static assert(Map62th != NoPadding, "'\\0' is not a valid Base64character"); + static assert(Map63th != NoPadding, "'\\0' is not a valid Base64character"); + + + /* Encode functions */ + + + private immutable EncodeMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ~ Map62th ~ Map63th; + + + /** + * Calculates the length needed to store the encoded string corresponding + * to an input of the given length. + * + * Params: + * sourceLength = Length of the source array. + * + * Returns: + * The length of a Base64 encoding of an array of the given length. + */ + @safe + pure nothrow size_t encodeLength(in size_t sourceLength) + { + static if (Padding == NoPadding) + return (sourceLength / 3) * 4 + (sourceLength % 3 == 0 ? 0 : sourceLength % 3 == 1 ? 2 : 3); + else + return (sourceLength / 3 + (sourceLength % 3 ? 1 : 0)) * 4; + } + + /// + @safe unittest + { + ubyte[] data = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]; + + // Allocate a buffer large enough to hold the encoded string. + auto buf = new char[Base64.encodeLength(data.length)]; + + Base64.encode(data, buf); + assert(buf == "Gis8TV1u"); + } + + + // ubyte[] to char[] + + + /** + * Encode $(D_PARAM source) into a $(D char[]) buffer using Base64 + * encoding. + * + * Params: + * source = The $(LINK2 std_range_primitives.html#isInputRange, input + * range) to _encode. + * buffer = The $(D char[]) buffer to store the encoded result. + * + * Returns: + * The slice of $(D_PARAM buffer) that contains the encoded string. + */ + @trusted + pure char[] encode(R1, R2)(in R1 source, R2 buffer) if (isArray!R1 && is(ElementType!R1 : ubyte) && + is(R2 == char[])) + in + { + assert(buffer.length >= encodeLength(source.length), "Insufficient buffer for encoding"); + } + out(result) + { + assert(result.length == encodeLength(source.length), "The length of result is different from Base64"); + } + body + { + immutable srcLen = source.length; + if (srcLen == 0) + return []; + + immutable blocks = srcLen / 3; + immutable remain = srcLen % 3; + auto bufptr = buffer.ptr; + auto srcptr = source.ptr; + + foreach (Unused; 0 .. blocks) + { + immutable val = srcptr[0] << 16 | srcptr[1] << 8 | srcptr[2]; + *bufptr++ = EncodeMap[val >> 18 ]; + *bufptr++ = EncodeMap[val >> 12 & 0x3f]; + *bufptr++ = EncodeMap[val >> 6 & 0x3f]; + *bufptr++ = EncodeMap[val & 0x3f]; + srcptr += 3; + } + + if (remain) + { + immutable val = srcptr[0] << 16 | (remain == 2 ? srcptr[1] << 8 : 0); + *bufptr++ = EncodeMap[val >> 18 ]; + *bufptr++ = EncodeMap[val >> 12 & 0x3f]; + + final switch (remain) + { + case 2: + *bufptr++ = EncodeMap[val >> 6 & 0x3f]; + static if (Padding != NoPadding) + *bufptr++ = Padding; + break; + case 1: + static if (Padding != NoPadding) + { + *bufptr++ = Padding; + *bufptr++ = Padding; + } + break; + } + } + + // encode method can't assume buffer length. So, slice needed. + return buffer[0 .. bufptr - buffer.ptr]; + } + + /// + @safe unittest + { + ubyte[] data = [0x83, 0xd7, 0x30, 0x7a, 0x01, 0x3f]; + char[32] buffer; // much bigger than necessary + + // Just to be sure... + auto encodedLength = Base64.encodeLength(data.length); + assert(buffer.length >= encodedLength); + + // encode() returns a slice to the provided buffer. + auto encoded = Base64.encode(data, buffer[]); + assert(encoded is buffer[0 .. encodedLength]); + assert(encoded == "g9cwegE/"); + } + + + // InputRange to char[] + + + /** + * ditto + */ + char[] encode(R1, R2)(R1 source, R2 buffer) if (!isArray!R1 && isInputRange!R1 && + is(ElementType!R1 : ubyte) && hasLength!R1 && + is(R2 == char[])) + in + { + assert(buffer.length >= encodeLength(source.length), "Insufficient buffer for encoding"); + } + out(result) + { + // @@@BUG@@@ D's DbC can't caputre an argument of function and store the result of precondition. + //assert(result.length == encodeLength(source.length), "The length of result is different from Base64"); + } + body + { + immutable srcLen = source.length; + if (srcLen == 0) + return []; + + immutable blocks = srcLen / 3; + immutable remain = srcLen % 3; + auto bufptr = buffer.ptr; + + foreach (Unused; 0 .. blocks) + { + immutable v1 = source.front; source.popFront(); + immutable v2 = source.front; source.popFront(); + immutable v3 = source.front; source.popFront(); + immutable val = v1 << 16 | v2 << 8 | v3; + *bufptr++ = EncodeMap[val >> 18 ]; + *bufptr++ = EncodeMap[val >> 12 & 0x3f]; + *bufptr++ = EncodeMap[val >> 6 & 0x3f]; + *bufptr++ = EncodeMap[val & 0x3f]; + } + + if (remain) + { + size_t val = source.front << 16; + if (remain == 2) + { + source.popFront(); + val |= source.front << 8; + } + + *bufptr++ = EncodeMap[val >> 18 ]; + *bufptr++ = EncodeMap[val >> 12 & 0x3f]; + + final switch (remain) + { + case 2: + *bufptr++ = EncodeMap[val >> 6 & 0x3f]; + static if (Padding != NoPadding) + *bufptr++ = Padding; + break; + case 1: + static if (Padding != NoPadding) + { + *bufptr++ = Padding; + *bufptr++ = Padding; + } + break; + } + } + + // @@@BUG@@@ Workaround for DbC problem. See comment on 'out'. + version (unittest) + assert( + bufptr - buffer.ptr == encodeLength(srcLen), + "The length of result is different from Base64" + ); + + // encode method can't assume buffer length. So, slice needed. + return buffer[0 .. bufptr - buffer.ptr]; + } + + + // ubyte[] to OutputRange + + + /** + * Encodes $(D_PARAM source) into an + * $(LINK2 std_range_primitives.html#isOutputRange, output range) using + * Base64 encoding. + * + * Params: + * source = The $(LINK2 std_range_primitives.html#isInputRange, input + * range) to _encode. + * range = The $(LINK2 std_range_primitives.html#isOutputRange, output + * range) to store the encoded result. + * + * Returns: + * The number of times the output range's $(D put) method was invoked. + */ + size_t encode(R1, R2)(in R1 source, auto ref R2 range) + if (isArray!R1 && is(ElementType!R1 : ubyte) && + !is(R2 == char[]) && isOutputRange!(R2, char)) + out(result) + { + assert(result == encodeLength(source.length), "The number of put is different from the length of Base64"); + } + body + { + immutable srcLen = source.length; + if (srcLen == 0) + return 0; + + immutable blocks = srcLen / 3; + immutable remain = srcLen % 3; + auto srcptr = source.ptr; + size_t pcount; + + foreach (Unused; 0 .. blocks) + { + immutable val = srcptr[0] << 16 | srcptr[1] << 8 | srcptr[2]; + put(range, EncodeMap[val >> 18 ]); + put(range, EncodeMap[val >> 12 & 0x3f]); + put(range, EncodeMap[val >> 6 & 0x3f]); + put(range, EncodeMap[val & 0x3f]); + srcptr += 3; + pcount += 4; + } + + if (remain) + { + immutable val = srcptr[0] << 16 | (remain == 2 ? srcptr[1] << 8 : 0); + put(range, EncodeMap[val >> 18 ]); + put(range, EncodeMap[val >> 12 & 0x3f]); + pcount += 2; + + final switch (remain) + { + case 2: + put(range, EncodeMap[val >> 6 & 0x3f]); + pcount++; + + static if (Padding != NoPadding) + { + put(range, Padding); + pcount++; + } + break; + case 1: + static if (Padding != NoPadding) + { + put(range, Padding); + put(range, Padding); + pcount += 2; + } + break; + } + } + + return pcount; + } + + /// + @system unittest + { + // @system because encode for OutputRange is @system + struct OutputRange + { + char[] result; + void put(const(char) ch) @safe { result ~= ch; } + } + + ubyte[] data = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]; + + // This overload of encode() returns the number of calls to the output + // range's put method. + OutputRange output; + assert(Base64.encode(data, output) == 8); + assert(output.result == "Gis8TV1u"); + } + + + // InputRange to OutputRange + + + /** + * ditto + */ + size_t encode(R1, R2)(R1 source, auto ref R2 range) + if (!isArray!R1 && isInputRange!R1 && is(ElementType!R1 : ubyte) && + hasLength!R1 && !is(R2 == char[]) && isOutputRange!(R2, char)) + out(result) + { + // @@@BUG@@@ Workaround for DbC problem. + //assert(result == encodeLength(source.length), "The number of put is different from the length of Base64"); + } + body + { + immutable srcLen = source.length; + if (srcLen == 0) + return 0; + + immutable blocks = srcLen / 3; + immutable remain = srcLen % 3; + size_t pcount; + + foreach (Unused; 0 .. blocks) + { + immutable v1 = source.front; source.popFront(); + immutable v2 = source.front; source.popFront(); + immutable v3 = source.front; source.popFront(); + immutable val = v1 << 16 | v2 << 8 | v3; + put(range, EncodeMap[val >> 18 ]); + put(range, EncodeMap[val >> 12 & 0x3f]); + put(range, EncodeMap[val >> 6 & 0x3f]); + put(range, EncodeMap[val & 0x3f]); + pcount += 4; + } + + if (remain) + { + size_t val = source.front << 16; + if (remain == 2) + { + source.popFront(); + val |= source.front << 8; + } + + put(range, EncodeMap[val >> 18 ]); + put(range, EncodeMap[val >> 12 & 0x3f]); + pcount += 2; + + final switch (remain) + { + case 2: + put(range, EncodeMap[val >> 6 & 0x3f]); + pcount++; + + static if (Padding != NoPadding) + { + put(range, Padding); + pcount++; + } + break; + case 1: + static if (Padding != NoPadding) + { + put(range, Padding); + put(range, Padding); + pcount += 2; + } + break; + } + } + + // @@@BUG@@@ Workaround for DbC problem. + version (unittest) + assert( + pcount == encodeLength(srcLen), + "The number of put is different from the length of Base64" + ); + + return pcount; + } + + + /** + * Encodes $(D_PARAM source) to newly-allocated buffer. + * + * This convenience method alleviates the need to manually manage output + * buffers. + * + * Params: + * source = The $(LINK2 std_range_primitives.html#isInputRange, input + * range) to _encode. + * + * Returns: + * A newly-allocated $(D char[]) buffer containing the encoded string. + */ + @safe + pure char[] encode(Range)(Range source) if (isArray!Range && is(ElementType!Range : ubyte)) + { + return encode(source, new char[encodeLength(source.length)]); + } + + /// + @safe unittest + { + ubyte[] data = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]; + assert(Base64.encode(data) == "Gis8TV1u"); + } + + + /** + * ditto + */ + char[] encode(Range)(Range source) if (!isArray!Range && isInputRange!Range && + is(ElementType!Range : ubyte) && hasLength!Range) + { + return encode(source, new char[encodeLength(source.length)]); + } + + + /** + * An $(LINK2 std_range_primitives.html#isInputRange, input range) that + * iterates over the respective Base64 encodings of a range of data items. + * + * This range will be a $(LINK2 std_range_primitives.html#isForwardRange, + * forward range) if the underlying data source is at least a forward + * range. + * + * Note: This struct is not intended to be created in user code directly; + * use the $(LREF encoder) function instead. + */ + struct Encoder(Range) if (isInputRange!Range && (is(ElementType!Range : const(ubyte)[]) || + is(ElementType!Range : const(char)[]))) + { + private: + Range range_; + char[] buffer_, encoded_; + + + public: + this(Range range) + { + range_ = range; + doEncoding(); + } + + + /** + * Returns: + * true if there is no more encoded data left. + */ + @property @trusted + bool empty() + { + return range_.empty; + } + + + /** + * Returns: The current chunk of encoded data. + */ + @property @safe + nothrow char[] front() + { + return encoded_; + } + + + /** + * Advance the range to the next chunk of encoded data. + * + * Throws: + * $(D Base64Exception) If invoked when + * $(LREF2 .Base64Impl.Encoder.empty, empty) returns $(D true). + */ + void popFront() + { + enforce(!empty, new Base64Exception("Cannot call popFront on Encoder with no data remaining")); + + range_.popFront(); + + /* + * This check is very ugly. I think this is a Range's flaw. + * I very strongly want the Range guideline for unified implementation. + * + * In this case, Encoder becomes a beautiful implementation if 'front' performs Base64 encoding. + */ + if (!empty) + doEncoding(); + } + + + static if (isForwardRange!Range) + { + /** + * Save the current iteration state of the range. + * + * This method is only available if the underlying range is a + * $(LINK2 std_range_primitives.html#isForwardRange, forward + * range). + * + * Returns: + * A copy of $(D this). + */ + @property + typeof(this) save() + { + typeof(return) encoder; + + encoder.range_ = range_.save; + encoder.buffer_ = buffer_.dup; + encoder.encoded_ = encoder.buffer_[0 .. encoded_.length]; + + return encoder; + } + } + + + private: + void doEncoding() + { + auto data = cast(const(ubyte)[])range_.front; + auto size = encodeLength(data.length); + if (size > buffer_.length) + buffer_.length = size; + + encoded_ = encode(data, buffer_); + } + } + + + /** + * An $(LINK2 std_range_primitives.html#isInputRange, input range) that + * iterates over the encoded bytes of the given source data. + * + * It will be a $(LINK2 std_range_primitives.html#isForwardRange, forward + * range) if the underlying data source is at least a forward range. + * + * Note: This struct is not intended to be created in user code directly; + * use the $(LREF encoder) function instead. + */ + struct Encoder(Range) if (isInputRange!Range && is(ElementType!Range : ubyte)) + { + private: + Range range_; + ubyte first; + int pos, padding; + + + public: + this(Range range) + { + range_ = range; + static if (isForwardRange!Range) + range_ = range_.save; + + if (range_.empty) + pos = -1; + else + popFront(); + } + + + /** + * Returns: + * true if there are no more encoded characters to be iterated. + */ + @property @safe + nothrow bool empty() const + { + static if (Padding == NoPadding) + return pos < 0; + else + return pos < 0 && !padding; + } + + + /** + * Returns: The current encoded character. + */ + @property @safe + nothrow ubyte front() + { + return first; + } + + + /** + * Advance to the next encoded character. + * + * Throws: + * $(D Base64Exception) If invoked when $(LREF2 .Base64Impl.Encoder.empty.2, + * empty) returns $(D true). + */ + void popFront() + { + enforce(!empty, new Base64Exception("Cannot call popFront on Encoder with no data remaining")); + + static if (Padding != NoPadding) + if (padding) + { + first = Padding; + pos = -1; + padding--; + return; + } + + if (range_.empty) + { + pos = -1; + return; + } + + final switch (pos) + { + case 0: + first = EncodeMap[range_.front >> 2]; + break; + case 1: + immutable t = (range_.front & 0b11) << 4; + range_.popFront(); + + if (range_.empty) + { + first = EncodeMap[t]; + padding = 3; + } + else + { + first = EncodeMap[t | (range_.front >> 4)]; + } + break; + case 2: + immutable t = (range_.front & 0b1111) << 2; + range_.popFront(); + + if (range_.empty) + { + first = EncodeMap[t]; + padding = 2; + } + else + { + first = EncodeMap[t | (range_.front >> 6)]; + } + break; + case 3: + first = EncodeMap[range_.front & 0b111111]; + range_.popFront(); + break; + } + + ++pos %= 4; + } + + + static if (isForwardRange!Range) + { + /** + * Save the current iteration state of the range. + * + * This method is only available if the underlying range is a + * $(LINK2 std_range_primitives.html#isForwardRange, forward + * range). + * + * Returns: + * A copy of $(D this). + */ + @property + typeof(this) save() + { + auto encoder = this; + encoder.range_ = encoder.range_.save; + return encoder; + } + } + } + + + /** + * Construct an $(D Encoder) that iterates over the Base64 encoding of the + * given $(LINK2 std_range_primitives.html#isInputRange, input range). + * + * Params: + * range = An $(LINK2 std_range_primitives.html#isInputRange, input + * range) over the data to be encoded. + * + * Returns: + * If $(D_PARAM range) is a range of bytes, an $(D Encoder) that iterates + * over the bytes of the corresponding Base64 encoding. + * + * If $(D_PARAM range) is a range of ranges of bytes, an $(D Encoder) that + * iterates over the Base64 encoded strings of each element of the range. + * + * In both cases, the returned $(D Encoder) will be a + * $(LINK2 std_range_primitives.html#isForwardRange, forward range) if the + * given $(D range) is at least a forward range, otherwise it will be only + * an input range. + * + * Example: + * This example encodes the input one line at a time. + * ----- + * File f = File("text.txt", "r"); + * scope(exit) f.close(); + * + * uint line = 0; + * foreach (encoded; Base64.encoder(f.byLine())) + * { + * writeln(++line, ". ", encoded); + * } + * ----- + * + * Example: + * This example encodes the input data one byte at a time. + * ----- + * ubyte[] data = cast(ubyte[]) "0123456789"; + * + * // The ElementType of data is not aggregation type + * foreach (encoded; Base64.encoder(data)) + * { + * writeln(encoded); + * } + * ----- + */ + Encoder!(Range) encoder(Range)(Range range) if (isInputRange!Range) + { + return typeof(return)(range); + } + + + /* Decode functions */ + + + private immutable int[char.max + 1] DecodeMap = [ + 'A':0b000000, 'B':0b000001, 'C':0b000010, 'D':0b000011, 'E':0b000100, + 'F':0b000101, 'G':0b000110, 'H':0b000111, 'I':0b001000, 'J':0b001001, + 'K':0b001010, 'L':0b001011, 'M':0b001100, 'N':0b001101, 'O':0b001110, + 'P':0b001111, 'Q':0b010000, 'R':0b010001, 'S':0b010010, 'T':0b010011, + 'U':0b010100, 'V':0b010101, 'W':0b010110, 'X':0b010111, 'Y':0b011000, + 'Z':0b011001, 'a':0b011010, 'b':0b011011, 'c':0b011100, 'd':0b011101, + 'e':0b011110, 'f':0b011111, 'g':0b100000, 'h':0b100001, 'i':0b100010, + 'j':0b100011, 'k':0b100100, 'l':0b100101, 'm':0b100110, 'n':0b100111, + 'o':0b101000, 'p':0b101001, 'q':0b101010, 'r':0b101011, 's':0b101100, + 't':0b101101, 'u':0b101110, 'v':0b101111, 'w':0b110000, 'x':0b110001, + 'y':0b110010, 'z':0b110011, '0':0b110100, '1':0b110101, '2':0b110110, + '3':0b110111, '4':0b111000, '5':0b111001, '6':0b111010, '7':0b111011, + '8':0b111100, '9':0b111101, Map62th:0b111110, Map63th:0b111111, Padding:-1 + ]; + + + /** + * Given a Base64 encoded string, calculates the length of the decoded + * string. + * + * Params: + * sourceLength = The length of the Base64 encoding. + * + * Returns: + * The length of the decoded string corresponding to a Base64 encoding of + * length $(D_PARAM sourceLength). + */ + @safe + pure 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); + else + return (sourceLength / 4) * 3; + } + + /// + @safe unittest + { + auto encoded = "Gis8TV1u"; + + // Allocate a sufficiently large buffer to hold to decoded result. + auto buffer = new ubyte[Base64.decodeLength(encoded.length)]; + + Base64.decode(encoded, buffer); + assert(buffer == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]); + } + + + // 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) + { + auto expect = decodeLength(source.length); + static if (Padding != NoPadding) + { + if (source.length % 4 == 0) + { + expect -= source.length == 0 ? 0 : + source[$ - 2] == Padding ? 2 : + source[$ - 1] == Padding ? 1 : 0; + } + } + return expect; + } + + + // char[] to ubyte[] + + + /** + * Decodes $(D_PARAM source) into the given buffer. + * + * Params: + * source = The $(LINK2 std_range_primitives.html#isInputRange, input + * range) to _decode. + * buffer = The buffer to store decoded result. + * + * Returns: + * The slice of $(D_PARAM buffer) containing the decoded result. + * + * Throws: + * $(D Base64Exception) if $(D_PARAM source) contains characters outside the + * base alphabet of the current Base64 encoding scheme. + */ + @trusted + pure ubyte[] decode(R1, R2)(in R1 source, R2 buffer) if (isArray!R1 && is(ElementType!R1 : dchar) && + is(R2 == ubyte[]) && isOutputRange!(R2, ubyte)) + in + { + assert(buffer.length >= realDecodeLength(source), "Insufficient buffer for decoding"); + } + out(result) + { + immutable expect = realDecodeLength(source); + assert(result.length == expect, "The length of result is different from the expected length"); + } + body + { + immutable srcLen = source.length; + if (srcLen == 0) + return []; + static if (Padding != NoPadding) + enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data")); + + immutable blocks = srcLen / 4; + auto srcptr = source.ptr; + auto bufptr = buffer.ptr; + + foreach (Unused; 0 .. blocks) + { + immutable v1 = decodeChar(*srcptr++); + immutable v2 = decodeChar(*srcptr++); + + *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4); + + immutable v3 = decodeChar(*srcptr++); + if (v3 == -1) + break; + + *bufptr++ = cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff); + + immutable v4 = decodeChar(*srcptr++); + if (v4 == -1) + break; + + *bufptr++ = cast(ubyte)((v3 << 6 | v4) & 0xff); + } + + static if (Padding == NoPadding) + { + immutable remain = srcLen % 4; + + if (remain) + { + immutable v1 = decodeChar(*srcptr++); + immutable v2 = decodeChar(*srcptr++); + + *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4); + + if (remain == 3) + *bufptr++ = cast(ubyte)((v2 << 4 | decodeChar(*srcptr++) >> 2) & 0xff); + } + } + + return buffer[0 .. bufptr - buffer.ptr]; + } + + /// + @safe unittest + { + auto encoded = "Gis8TV1u"; + ubyte[32] buffer; // much bigger than necessary + + // Just to be sure... + auto decodedLength = Base64.decodeLength(encoded.length); + assert(buffer.length >= decodedLength); + + // decode() returns a slice of the given buffer. + auto decoded = Base64.decode(encoded, buffer[]); + assert(decoded is buffer[0 .. decodedLength]); + assert(decoded == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]); + } + + // InputRange to ubyte[] + + + /** + * ditto + */ + ubyte[] decode(R1, R2)(R1 source, R2 buffer) if (!isArray!R1 && isInputRange!R1 && + is(ElementType!R1 : dchar) && hasLength!R1 && + is(R2 == ubyte[]) && isOutputRange!(R2, ubyte)) + in + { + assert(buffer.length >= decodeLength(source.length), "Insufficient buffer for decoding"); + } + out(result) + { + // @@@BUG@@@ Workaround for DbC problem. + //immutable expect = decodeLength(source.length) - 2; + //assert(result.length >= expect, "The length of result is smaller than expected length"); + } + body + { + immutable srcLen = source.length; + if (srcLen == 0) + return []; + static if (Padding != NoPadding) + enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data")); + + immutable blocks = srcLen / 4; + auto bufptr = buffer.ptr; + + foreach (Unused; 0 .. blocks) + { + immutable v1 = decodeChar(source.front); source.popFront(); + immutable v2 = decodeChar(source.front); source.popFront(); + + *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4); + + immutable v3 = decodeChar(source.front); + if (v3 == -1) + break; + + *bufptr++ = cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff); + source.popFront(); + + immutable v4 = decodeChar(source.front); + if (v4 == -1) + break; + + *bufptr++ = cast(ubyte)((v3 << 6 | v4) & 0xff); + source.popFront(); + } + + static if (Padding == NoPadding) + { + immutable remain = srcLen % 4; + + if (remain) + { + immutable v1 = decodeChar(source.front); source.popFront(); + immutable v2 = decodeChar(source.front); + + *bufptr++ = cast(ubyte)(v1 << 2 | v2 >> 4); + + if (remain == 3) + { + source.popFront(); + *bufptr++ = cast(ubyte)((v2 << 4 | decodeChar(source.front) >> 2) & 0xff); + } + } + } + + // @@@BUG@@@ Workaround for DbC problem. + version (unittest) + assert( + (bufptr - buffer.ptr) >= (decodeLength(srcLen) - 2), + "The length of result is smaller than expected length" + ); + + return buffer[0 .. bufptr - buffer.ptr]; + } + + + // char[] to OutputRange + + + /** + * Decodes $(D_PARAM source) into a given + * $(LINK2 std_range_primitives.html#isOutputRange, output range). + * + * Params: + * source = The $(LINK2 std_range_primitives.html#isInputRange, input + * range) to _decode. + * range = The $(LINK2 std_range_primitives.html#isOutputRange, output + * range) to store the decoded result. + * + * Returns: + * The number of times the output range's $(D put) method was invoked. + * + * Throws: + * $(D Base64Exception) if $(D_PARAM source) contains characters outside the + * base alphabet of the current Base64 encoding scheme. + */ + size_t decode(R1, R2)(in R1 source, auto ref R2 range) + if (isArray!R1 && is(ElementType!R1 : dchar) && + !is(R2 == ubyte[]) && isOutputRange!(R2, ubyte)) + out(result) + { + immutable expect = realDecodeLength(source); + assert(result == expect, "The result of decode is different from the expected"); + } + body + { + immutable srcLen = source.length; + if (srcLen == 0) + return 0; + static if (Padding != NoPadding) + enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data")); + + immutable blocks = srcLen / 4; + auto srcptr = source.ptr; + size_t pcount; + + foreach (Unused; 0 .. blocks) + { + immutable v1 = decodeChar(*srcptr++); + immutable v2 = decodeChar(*srcptr++); + + put(range, cast(ubyte)(v1 << 2 | v2 >> 4)); + pcount++; + + immutable v3 = decodeChar(*srcptr++); + if (v3 == -1) + break; + + put(range, cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff)); + pcount++; + + immutable v4 = decodeChar(*srcptr++); + if (v4 == -1) + break; + + put(range, cast(ubyte)((v3 << 6 | v4) & 0xff)); + pcount++; + } + + static if (Padding == NoPadding) + { + immutable remain = srcLen % 4; + + if (remain) + { + immutable v1 = decodeChar(*srcptr++); + immutable v2 = decodeChar(*srcptr++); + + put(range, cast(ubyte)(v1 << 2 | v2 >> 4)); + pcount++; + + if (remain == 3) + { + put(range, cast(ubyte)((v2 << 4 | decodeChar(*srcptr++) >> 2) & 0xff)); + pcount++; + } + } + } + + return pcount; + } + + /// + @system unittest + { + struct OutputRange + { + ubyte[] result; + void put(ubyte b) { result ~= b; } + } + OutputRange output; + + // This overload of decode() returns the number of calls to put(). + assert(Base64.decode("Gis8TV1u", output) == 6); + assert(output.result == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]); + } + + + // InputRange to OutputRange + + + /** + * ditto + */ + size_t decode(R1, R2)(R1 source, auto ref R2 range) + if (!isArray!R1 && isInputRange!R1 && is(ElementType!R1 : dchar) && + hasLength!R1 && !is(R2 == ubyte[]) && isOutputRange!(R2, ubyte)) + out(result) + { + // @@@BUG@@@ Workaround for DbC problem. + //immutable expect = decodeLength(source.length) - 2; + //assert(result >= expect, "The length of result is smaller than expected length"); + } + body + { + immutable srcLen = source.length; + if (srcLen == 0) + return 0; + static if (Padding != NoPadding) + enforce(srcLen % 4 == 0, new Base64Exception("Invalid length of encoded data")); + + immutable blocks = srcLen / 4; + size_t pcount; + + foreach (Unused; 0 .. blocks) + { + immutable v1 = decodeChar(source.front); source.popFront(); + immutable v2 = decodeChar(source.front); source.popFront(); + + put(range, cast(ubyte)(v1 << 2 | v2 >> 4)); + pcount++; + + immutable v3 = decodeChar(source.front); + if (v3 == -1) + break; + + put(range, cast(ubyte)((v2 << 4 | v3 >> 2) & 0xff)); + source.popFront(); + pcount++; + + immutable v4 = decodeChar(source.front); + if (v4 == -1) + break; + + put(range, cast(ubyte)((v3 << 6 | v4) & 0xff)); + source.popFront(); + pcount++; + } + + static if (Padding == NoPadding) + { + immutable remain = srcLen % 4; + + if (remain) + { + immutable v1 = decodeChar(source.front); source.popFront(); + immutable v2 = decodeChar(source.front); + + put(range, cast(ubyte)(v1 << 2 | v2 >> 4)); + pcount++; + + if (remain == 3) + { + source.popFront(); + put(range, cast(ubyte)((v2 << 4 | decodeChar(source.front) >> 2) & 0xff)); + pcount++; + } + } + } + + // @@@BUG@@@ Workaround for DbC problem. + version (unittest) + assert( + pcount >= (decodeLength(srcLen) - 2), + "The length of result is smaller than expected length" + ); + + return pcount; + } + + + /** + * Decodes $(D_PARAM source) into newly-allocated buffer. + * + * This convenience method alleviates the need to manually manage decoding + * buffers. + * + * Params: + * source = The $(LINK2 std_range_primitives.html#isInputRange, input + * range) to _decode. + * + * Returns: + * A newly-allocated $(D ubyte[]) buffer containing the decoded string. + */ + @safe + pure ubyte[] decode(Range)(Range source) if (isArray!Range && is(ElementType!Range : dchar)) + { + return decode(source, new ubyte[decodeLength(source.length)]); + } + + /// + @safe unittest + { + auto data = "Gis8TV1u"; + assert(Base64.decode(data) == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]); + } + + + /** + * ditto + */ + ubyte[] decode(Range)(Range source) if (!isArray!Range && isInputRange!Range && + is(ElementType!Range : dchar) && hasLength!Range) + { + return decode(source, new ubyte[decodeLength(source.length)]); + } + + + /** + * An $(LINK2 std_range_primitives.html#isInputRange, input range) that + * iterates over the decoded data of a range of Base64 encodings. + * + * This range will be a $(LINK2 std_range_primitives.html#isForwardRange, + * forward range) if the underlying data source is at least a forward + * range. + * + * Note: This struct is not intended to be created in user code directly; + * use the $(LREF decoder) function instead. + */ + struct Decoder(Range) if (isInputRange!Range && (is(ElementType!Range : const(char)[]) || + is(ElementType!Range : const(ubyte)[]))) + { + private: + Range range_; + ubyte[] buffer_, decoded_; + + + public: + this(Range range) + { + range_ = range; + doDecoding(); + } + + + /** + * Returns: + * true if there are no more elements to be iterated. + */ + @property @trusted + bool empty() + { + return range_.empty; + } + + + /** + * Returns: The decoding of the current element in the input. + */ + @property @safe + nothrow ubyte[] front() + { + return decoded_; + } + + + /** + * Advance to the next element in the input to be decoded. + * + * Throws: + * $(D Base64Exception) if invoked when $(LREF2 .Base64Impl.Decoder.empty, + * empty) returns $(D true). + */ + void popFront() + { + enforce(!empty, new Base64Exception("Cannot call popFront on Decoder with no data remaining.")); + + range_.popFront(); + + /* + * I mentioned Encoder's popFront. + */ + if (!empty) + doDecoding(); + } + + + static if (isForwardRange!Range) + { + /** + * Saves the current iteration state. + * + * This method is only available if the underlying range is a + * $(LINK2 std_range_primitives.html#isForwardRange, forward + * range). + * + * Returns: A copy of $(D this). + */ + @property + typeof(this) save() + { + typeof(return) decoder; + + decoder.range_ = range_.save; + decoder.buffer_ = buffer_.dup; + decoder.decoded_ = decoder.buffer_[0 .. decoded_.length]; + + return decoder; + } + } + + + private: + void doDecoding() + { + auto data = cast(const(char)[])range_.front; + + static if (Padding == NoPadding) + { + while (data.length % 4 == 1) + { + range_.popFront(); + data ~= cast(const(char)[])range_.front; + } + } + else + { + while (data.length % 4 != 0) + { + range_.popFront(); + data ~= cast(const(char)[])range_.front; + } + } + + auto size = decodeLength(data.length); + if (size > buffer_.length) + buffer_.length = size; + + decoded_ = decode(data, buffer_); + } + } + + + /** + * An $(LINK2 std_range_primitives.html#isInputRange, input range) that + * iterates over the bytes of data decoded from a Base64 encoded string. + * + * This range will be a $(LINK2 std_range_primitives.html#isForwardRange, + * forward range) if the underlying data source is at least a forward + * range. + * + * Note: This struct is not intended to be created in user code directly; + * use the $(LREF decoder) function instead. + */ + struct Decoder(Range) if (isInputRange!Range && is(ElementType!Range : char)) + { + private: + Range range_; + ubyte first; + int pos; + + + public: + this(Range range) + { + range_ = range; + static if (isForwardRange!Range) + range_ = range_.save; + + static if (Padding != NoPadding && hasLength!Range) + enforce(range_.length % 4 == 0, new Base64Exception("Invalid length of encoded data")); + + if (range_.empty) + pos = -1; + else + popFront(); + } + + + /** + * Returns: + * true if there are no more elements to be iterated. + */ + @property @safe + nothrow bool empty() const + { + return pos < 0; + } + + + /** + * Returns: The current decoded byte. + */ + @property @safe + nothrow ubyte front() + { + return first; + } + + + /** + * Advance to the next decoded byte. + * + * Throws: + * $(D Base64Exception) if invoked when $(LREF2 .Base64Impl.Decoder.empty, + * empty) returns $(D true). + */ + void popFront() + { + enforce(!empty, new Base64Exception("Cannot call popFront on Decoder with no data remaining")); + + static if (Padding == NoPadding) + { + bool endCondition() + { + return range_.empty; + } + } + else + { + bool endCondition() + { + enforce(!range_.empty, new Base64Exception("Missing padding")); + return range_.front == Padding; + } + } + + if (range_.empty || range_.front == Padding) + { + pos = -1; + return; + } + + final switch (pos) + { + case 0: + enforce(!endCondition(), new Base64Exception("Premature end of data found")); + + immutable t = DecodeMap[range_.front] << 2; + range_.popFront(); + + enforce(!endCondition(), new Base64Exception("Premature end of data found")); + first = cast(ubyte)(t | (DecodeMap[range_.front] >> 4)); + break; + case 1: + immutable t = (DecodeMap[range_.front] & 0b1111) << 4; + range_.popFront(); + + if (endCondition()) + { + pos = -1; + return; + } + else + { + first = cast(ubyte)(t | (DecodeMap[range_.front] >> 2)); + } + break; + case 2: + immutable t = (DecodeMap[range_.front] & 0b11) << 6; + range_.popFront(); + + if (endCondition()) + { + pos = -1; + return; + } + else + { + first = cast(ubyte)(t | DecodeMap[range_.front]); + } + + range_.popFront(); + break; + } + + ++pos %= 3; + } + + + static if (isForwardRange!Range) + { + /** + * Saves the current iteration state. + * + * This method is only available if the underlying range is a + * $(LINK2 std_range_primitives.html#isForwardRange, forward + * range). + * + * Returns: A copy of $(D this). + */ + @property + typeof(this) save() + { + auto decoder = this; + decoder.range_ = decoder.range_.save; + return decoder; + } + } + } + + + /** + * Construct a $(D Decoder) that iterates over the decoding of the given + * Base64 encoded data. + * + * Params: + * range = An $(LINK2 std_range_primitives.html#isInputRange, input + * range) over the data to be decoded. + * + * Returns: + * If $(D_PARAM range) is a range of characters, a $(D Decoder) that + * iterates over the bytes of the corresponding Base64 decoding. + * + * If $(D_PARAM range) is a range of ranges of characters, a $(D Decoder) + * that iterates over the decoded strings corresponding to each element of + * the range. In this case, the length of each subrange must be a multiple + * of 4; the returned _decoder does not keep track of Base64 decoding + * state across subrange boundaries. + * + * In both cases, the returned $(D Decoder) will be a + * $(LINK2 std_range_primitives.html#isForwardRange, forward range) if the + * given $(D range) is at least a forward range, otherwise it will be only + * an input range. + * + * If the input data contains characters not found in the base alphabet of + * the current Base64 encoding scheme, the returned range may throw a + * $(D Base64Exception). + * + * Example: + * This example shows decoding over a range of input data lines. + * ----- + * foreach (decoded; Base64.decoder(stdin.byLine())) + * { + * writeln(decoded); + * } + * ----- + * + * Example: + * This example shows decoding one byte at a time. + * ----- + * auto encoded = Base64.encoder(cast(ubyte[])"0123456789"); + * foreach (n; map!q{a - '0'}(Base64.decoder(encoded))) + * { + * writeln(n); + * } + * ----- + */ + Decoder!(Range) decoder(Range)(Range range) if (isInputRange!Range) + { + return typeof(return)(range); + } + + + private: + @safe + pure int decodeChar()(char chr) + { + immutable val = DecodeMap[chr]; + + // enforce can't be a pure function, so I use trivial check. + if (val == 0 && chr != 'A') + throw new Base64Exception("Invalid character: " ~ chr); + + return val; + } + + + @safe + pure int decodeChar()(dchar chr) + { + // See above comment. + if (chr > 0x7f) + throw new Base64Exception("Base64-encoded character must be a single byte"); + + return decodeChar(cast(char) chr); + } +} + +/// +@safe unittest +{ + import std.string : representation; + + // pre-defined: alias Base64 = Base64Impl!('+', '/'); + ubyte[] emptyArr; + assert(Base64.encode(emptyArr) == ""); + assert(Base64.encode("f".representation) == "Zg=="); + assert(Base64.encode("foo".representation) == "Zm9v"); + + alias Base64Re = Base64Impl!('!', '=', Base64.NoPadding); + assert(Base64Re.encode("f".representation) == "Zg"); + assert(Base64Re.encode("foo".representation) == "Zm9v"); +} + +/** + * Exception thrown upon encountering Base64 encoding or decoding errors. + */ +class Base64Exception : Exception +{ + @safe pure nothrow + this(string s, string fn = __FILE__, size_t ln = __LINE__) + { + super(s, fn, ln); + } +} + +/// +@system unittest +{ + import std.exception : assertThrown; + assertThrown!Base64Exception(Base64.decode("ab|c")); +} + + +@system unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.sorting : sort; + import std.conv; + import std.file; + import std.stdio; + + alias Base64Re = Base64Impl!('!', '=', Base64.NoPadding); + + // Test vectors from RFC 4648 + ubyte[][string] tv = [ + "" :cast(ubyte[])"", + "f" :cast(ubyte[])"f", + "fo" :cast(ubyte[])"fo", + "foo" :cast(ubyte[])"foo", + "foob" :cast(ubyte[])"foob", + "fooba" :cast(ubyte[])"fooba", + "foobar":cast(ubyte[])"foobar" + ]; + + { // Base64 + // encode + assert(Base64.encodeLength(tv[""].length) == 0); + assert(Base64.encodeLength(tv["f"].length) == 4); + assert(Base64.encodeLength(tv["fo"].length) == 4); + assert(Base64.encodeLength(tv["foo"].length) == 4); + assert(Base64.encodeLength(tv["foob"].length) == 8); + assert(Base64.encodeLength(tv["fooba"].length) == 8); + assert(Base64.encodeLength(tv["foobar"].length) == 8); + + assert(Base64.encode(tv[""]) == ""); + assert(Base64.encode(tv["f"]) == "Zg=="); + assert(Base64.encode(tv["fo"]) == "Zm8="); + assert(Base64.encode(tv["foo"]) == "Zm9v"); + assert(Base64.encode(tv["foob"]) == "Zm9vYg=="); + assert(Base64.encode(tv["fooba"]) == "Zm9vYmE="); + assert(Base64.encode(tv["foobar"]) == "Zm9vYmFy"); + + // decode + assert(Base64.decodeLength(Base64.encode(tv[""]).length) == 0); + assert(Base64.decodeLength(Base64.encode(tv["f"]).length) == 3); + assert(Base64.decodeLength(Base64.encode(tv["fo"]).length) == 3); + assert(Base64.decodeLength(Base64.encode(tv["foo"]).length) == 3); + assert(Base64.decodeLength(Base64.encode(tv["foob"]).length) == 6); + assert(Base64.decodeLength(Base64.encode(tv["fooba"]).length) == 6); + assert(Base64.decodeLength(Base64.encode(tv["foobar"]).length) == 6); + + assert(Base64.decode(Base64.encode(tv[""])) == tv[""]); + assert(Base64.decode(Base64.encode(tv["f"])) == tv["f"]); + assert(Base64.decode(Base64.encode(tv["fo"])) == tv["fo"]); + assert(Base64.decode(Base64.encode(tv["foo"])) == tv["foo"]); + assert(Base64.decode(Base64.encode(tv["foob"])) == tv["foob"]); + assert(Base64.decode(Base64.encode(tv["fooba"])) == tv["fooba"]); + assert(Base64.decode(Base64.encode(tv["foobar"])) == tv["foobar"]); + + assertThrown!Base64Exception(Base64.decode("ab|c")); + + // Test decoding incomplete strings. RFC does not specify the correct + // behavior, but the code should never throw Errors on invalid input. + + // decodeLength is nothrow + assert(Base64.decodeLength(1) == 0); + assert(Base64.decodeLength(2) <= 1); + assert(Base64.decodeLength(3) <= 2); + + // may throw Exceptions, may not throw Errors + assertThrown!Base64Exception(Base64.decode("Zg")); + assertThrown!Base64Exception(Base64.decode("Zg=")); + assertThrown!Base64Exception(Base64.decode("Zm8")); + assertThrown!Base64Exception(Base64.decode("Zg==;")); + } + + { // No padding + // encode + assert(Base64Re.encodeLength(tv[""].length) == 0); + assert(Base64Re.encodeLength(tv["f"].length) == 2); + assert(Base64Re.encodeLength(tv["fo"].length) == 3); + assert(Base64Re.encodeLength(tv["foo"].length) == 4); + assert(Base64Re.encodeLength(tv["foob"].length) == 6); + assert(Base64Re.encodeLength(tv["fooba"].length) == 7); + assert(Base64Re.encodeLength(tv["foobar"].length) == 8); + + assert(Base64Re.encode(tv[""]) == ""); + assert(Base64Re.encode(tv["f"]) == "Zg"); + assert(Base64Re.encode(tv["fo"]) == "Zm8"); + assert(Base64Re.encode(tv["foo"]) == "Zm9v"); + assert(Base64Re.encode(tv["foob"]) == "Zm9vYg"); + assert(Base64Re.encode(tv["fooba"]) == "Zm9vYmE"); + assert(Base64Re.encode(tv["foobar"]) == "Zm9vYmFy"); + + // decode + assert(Base64Re.decodeLength(Base64Re.encode(tv[""]).length) == 0); + assert(Base64Re.decodeLength(Base64Re.encode(tv["f"]).length) == 1); + assert(Base64Re.decodeLength(Base64Re.encode(tv["fo"]).length) == 2); + assert(Base64Re.decodeLength(Base64Re.encode(tv["foo"]).length) == 3); + assert(Base64Re.decodeLength(Base64Re.encode(tv["foob"]).length) == 4); + assert(Base64Re.decodeLength(Base64Re.encode(tv["fooba"]).length) == 5); + assert(Base64Re.decodeLength(Base64Re.encode(tv["foobar"]).length) == 6); + + assert(Base64Re.decode(Base64Re.encode(tv[""])) == tv[""]); + assert(Base64Re.decode(Base64Re.encode(tv["f"])) == tv["f"]); + assert(Base64Re.decode(Base64Re.encode(tv["fo"])) == tv["fo"]); + assert(Base64Re.decode(Base64Re.encode(tv["foo"])) == tv["foo"]); + assert(Base64Re.decode(Base64Re.encode(tv["foob"])) == tv["foob"]); + assert(Base64Re.decode(Base64Re.encode(tv["fooba"])) == tv["fooba"]); + assert(Base64Re.decode(Base64Re.encode(tv["foobar"])) == tv["foobar"]); + + // decodeLength is nothrow + assert(Base64.decodeLength(1) == 0); + } + + { // with OutputRange + import std.array; + + auto a = Appender!(char[])([]); + auto b = Appender!(ubyte[])([]); + + assert(Base64.encode(tv[""], a) == 0); + assert(Base64.decode(a.data, b) == 0); + assert(tv[""] == b.data); a.clear(); b.clear(); + + assert(Base64.encode(tv["f"], a) == 4); + assert(Base64.decode(a.data, b) == 1); + assert(tv["f"] == b.data); a.clear(); b.clear(); + + assert(Base64.encode(tv["fo"], a) == 4); + assert(Base64.decode(a.data, b) == 2); + assert(tv["fo"] == b.data); a.clear(); b.clear(); + + assert(Base64.encode(tv["foo"], a) == 4); + assert(Base64.decode(a.data, b) == 3); + assert(tv["foo"] == b.data); a.clear(); b.clear(); + + assert(Base64.encode(tv["foob"], a) == 8); + assert(Base64.decode(a.data, b) == 4); + assert(tv["foob"] == b.data); a.clear(); b.clear(); + + assert(Base64.encode(tv["fooba"], a) == 8); + assert(Base64.decode(a.data, b) == 5); + assert(tv["fooba"] == b.data); a.clear(); b.clear(); + + assert(Base64.encode(tv["foobar"], a) == 8); + assert(Base64.decode(a.data, b) == 6); + assert(tv["foobar"] == b.data); a.clear(); b.clear(); + } + + // @@@9543@@@ These tests were disabled because they actually relied on the input range having length. + // The implementation (currently) doesn't support encoding/decoding from a length-less source. + version (none) + { // with InputRange + // InputRange to ubyte[] or char[] + auto encoded = Base64.encode(map!(to!(ubyte))(["20", "251", "156", "3", "217", "126"])); + assert(encoded == "FPucA9l+"); + assert(Base64.decode(map!q{a}(encoded)) == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]); + + // InputRange to OutputRange + auto a = Appender!(char[])([]); + auto b = Appender!(ubyte[])([]); + assert(Base64.encode(map!(to!(ubyte))(["20", "251", "156", "3", "217", "126"]), a) == 8); + assert(a.data == "FPucA9l+"); + assert(Base64.decode(map!q{a}(a.data), b) == 6); + assert(b.data == [0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]); + } + + { // Encoder and Decoder + { + string encode_file = std.file.deleteme ~ "-testingEncoder"; + std.file.write(encode_file, "\nf\nfo\nfoo\nfoob\nfooba\nfoobar"); + + auto witness = ["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"]; + auto f = File(encode_file); + scope(exit) + { + f.close(); + assert(!f.isOpen); + std.file.remove(encode_file); + } + + size_t i; + foreach (encoded; Base64.encoder(f.byLine())) + assert(encoded == witness[i++]); + + assert(i == witness.length); + } + + { + string decode_file = std.file.deleteme ~ "-testingDecoder"; + std.file.write(decode_file, "\nZg==\nZm8=\nZm9v\nZm9vYg==\nZm9vYmE=\nZm9vYmFy"); + + auto witness = sort(tv.keys); + auto f = File(decode_file); + scope(exit) + { + f.close(); + assert(!f.isOpen); + std.file.remove(decode_file); + } + + size_t i; + foreach (decoded; Base64.decoder(f.byLine())) + assert(decoded == witness[i++]); + + assert(i == witness.length); + } + + { // ForwardRange + { + auto encoder = Base64.encoder(sort(tv.values)); + auto witness = ["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"]; + size_t i; + + assert(encoder.front == witness[i++]); encoder.popFront(); + assert(encoder.front == witness[i++]); encoder.popFront(); + assert(encoder.front == witness[i++]); encoder.popFront(); + + foreach (encoded; encoder.save) + assert(encoded == witness[i++]); + } + + { + auto decoder = Base64.decoder(["", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"]); + auto witness = sort(tv.values); + size_t i; + + assert(decoder.front == witness[i++]); decoder.popFront(); + assert(decoder.front == witness[i++]); decoder.popFront(); + assert(decoder.front == witness[i++]); decoder.popFront(); + + foreach (decoded; decoder.save) + assert(decoded == witness[i++]); + } + } + } + + { // Encoder and Decoder for single character encoding and decoding + alias Base64NoPadding = Base64Impl!('+', '/', Base64.NoPadding); + + auto tests = [ + "" : ["", "", "", ""], + "f" : ["Zg==", "Zg==", "Zg", "Zg"], + "fo" : ["Zm8=", "Zm8=", "Zm8", "Zm8"], + "foo" : ["Zm9v", "Zm9v", "Zm9v", "Zm9v"], + "foob" : ["Zm9vYg==", "Zm9vYg==", "Zm9vYg", "Zm9vYg"], + "fooba" : ["Zm9vYmE=", "Zm9vYmE=", "Zm9vYmE", "Zm9vYmE"], + "foobar" : ["Zm9vYmFy", "Zm9vYmFy", "Zm9vYmFy", "Zm9vYmFy"], + ]; + + foreach (u, e; tests) + { + assert(equal(Base64.encoder(cast(ubyte[]) u), e[0])); + assert(equal(Base64.decoder(Base64.encoder(cast(ubyte[]) u)), u)); + + assert(equal(Base64URL.encoder(cast(ubyte[]) u), e[1])); + assert(equal(Base64URL.decoder(Base64URL.encoder(cast(ubyte[]) u)), u)); + + assert(equal(Base64NoPadding.encoder(cast(ubyte[]) u), e[2])); + assert(equal(Base64NoPadding.decoder(Base64NoPadding.encoder(cast(ubyte[]) u)), u)); + + assert(equal(Base64Re.encoder(cast(ubyte[]) u), e[3])); + assert(equal(Base64Re.decoder(Base64Re.encoder(cast(ubyte[]) u)), u)); + } + } +} + +// Regression control for the output range ref bug in encode. +@system unittest +{ + struct InputRange + { + ubyte[] impl = [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]; + @property bool empty() { return impl.length == 0; } + @property ubyte front() { return impl[0]; } + void popFront() { impl = impl[1 .. $]; } + @property size_t length() { return impl.length; } + } + + struct OutputRange + { + char[] result; + void put(char b) { result ~= b; } + } + + InputRange ir; + OutputRange or; + assert(Base64.encode(ir, or) == 8); + assert(or.result == "Gis8TV1u"); + + // Verify that any existing workaround that uses & still works. + InputRange ir2; + OutputRange or2; + assert(Base64.encode(ir2, &or2) == 8); + assert(or2.result == "Gis8TV1u"); +} + +// Regression control for the output range ref bug in decode. +@system unittest +{ + struct InputRange + { + const(char)[] impl = "Gis8TV1u"; + @property bool empty() { return impl.length == 0; } + @property dchar front() { return impl[0]; } + void popFront() { impl = impl[1 .. $]; } + @property size_t length() { return impl.length; } + } + + struct OutputRange + { + ubyte[] result; + void put(ubyte b) { result ~= b; } + } + + InputRange ir; + OutputRange or; + assert(Base64.decode(ir, or) == 6); + assert(or.result == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]); + + // Verify that any existing workaround that uses & still works. + InputRange ir2; + OutputRange or2; + assert(Base64.decode(ir2, &or2) == 6); + assert(or2.result == [0x1a, 0x2b, 0x3c, 0x4d, 0x5d, 0x6e]); +} diff --git a/libphobos/src/std/bigint.d b/libphobos/src/std/bigint.d new file mode 100644 index 0000000..4c518f2 --- /dev/null +++ b/libphobos/src/std/bigint.d @@ -0,0 +1,1705 @@ +/** Arbitrary-precision ('bignum') arithmetic. + * + * Performance is optimized for numbers below ~1000 decimal digits. + * For X86 machines, highly optimised assembly routines are used. + * + * The following algorithms are currently implemented: + * $(UL + * $(LI Karatsuba multiplication) + * $(LI Squaring is optimized independently of multiplication) + * $(LI Divide-and-conquer division) + * $(LI Binary exponentiation) + * ) + * + * For very large numbers, consider using the $(HTTP gmplib.org, GMP library) instead. + * + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Don Clugston + * Source: $(PHOBOSSRC std/_bigint.d) + */ +/* Copyright Don Clugston 2008 - 2010. + * 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) + */ + +module std.bigint; + +import std.conv : ConvException; + +import std.format : FormatSpec, FormatException; +import std.internal.math.biguintcore; +import std.range.primitives; +import std.traits; + +/** A struct representing an arbitrary precision integer. + * + * All arithmetic operations are supported, except unsigned shift right (>>>). + * Bitwise operations (|, &, ^, ~) are supported, and behave as if BigInt was + * an infinite length 2's complement number. + * + * BigInt implements value semantics using copy-on-write. This means that + * assignment is cheap, but operations such as x++ will cause heap + * allocation. (But note that for most bigint operations, heap allocation is + * inevitable anyway.) + */ +struct BigInt +{ +private: + BigUint data; // BigInt adds signed arithmetic to BigUint. + bool sign = false; +public: + /** + * Construct a BigInt from a decimal or hexadecimal string. The number must + * be in the form of a decimal or hex literal. It may have a leading `+` + * or `-` sign, followed by `0x` or `0X` if hexadecimal. Underscores are + * permitted in any location after the `0x` and/or the sign of the number. + * + * Params: + * s = a finite bidirectional range of any character type + * + * Throws: + * $(REF ConvException, std,conv) if the string doesn't represent a valid number + */ + this(Range)(Range s) if ( + isBidirectionalRange!Range && + isSomeChar!(ElementType!Range) && + !isInfinite!Range && + !isSomeString!Range) + { + import std.algorithm.iteration : filterBidirectional; + import std.algorithm.searching : startsWith; + import std.conv : ConvException; + import std.exception : enforce; + import std.utf : byChar; + + enforce!ConvException(!s.empty, "Can't initialize BigInt with an empty range"); + + bool neg = false; + bool ok; + + data = 0UL; + + // check for signs and if the string is a hex value + if (s.front == '+') + { + s.popFront(); // skip '+' + } + else if (s.front == '-') + { + neg = true; + s.popFront(); + } + + if (s.save.startsWith("0x".byChar) || + s.save.startsWith("0X".byChar)) + { + s.popFront; + s.popFront; + + if (!s.empty) + ok = data.fromHexString(s.filterBidirectional!(a => a != '_')); + else + ok = false; + } + else + { + ok = data.fromDecimalString(s.filterBidirectional!(a => a != '_')); + } + + enforce!ConvException(ok, "Not a valid numerical string"); + + if (isZero()) + neg = false; + + sign = neg; + } + + /// ditto + this(Range)(Range s) pure if (isSomeString!Range) + { + import std.utf : byCodeUnit; + this(s.byCodeUnit); + } + + @system unittest + { + // system because of the dummy ranges eventually call std.array!string + import std.exception : assertThrown; + import std.internal.test.dummyrange; + + auto r1 = new ReferenceBidirectionalRange!dchar("101"); + auto big1 = BigInt(r1); + assert(big1 == BigInt(101)); + + auto r2 = new ReferenceBidirectionalRange!dchar("1_000"); + auto big2 = BigInt(r2); + assert(big2 == BigInt(1000)); + + auto r3 = new ReferenceBidirectionalRange!dchar("0x0"); + auto big3 = BigInt(r3); + assert(big3 == BigInt(0)); + + auto r4 = new ReferenceBidirectionalRange!dchar("0x"); + assertThrown!ConvException(BigInt(r4)); + } + + /// Construct a BigInt from a built-in integral type. + this(T)(T x) pure nothrow if (isIntegral!T) + { + data = data.init; // @@@: Workaround for compiler bug + opAssign(x); + } + + /// + @system unittest + { + // @system due to failure in FreeBSD32 + ulong data = 1_000_000_000_000; + auto bigData = BigInt(data); + assert(data == BigInt("1_000_000_000_000")); + } + + /// Construct a BigInt from another BigInt. + this(T)(T x) pure nothrow if (is(Unqual!T == BigInt)) + { + opAssign(x); + } + + /// + @system unittest + { + const(BigInt) b1 = BigInt("1_234_567_890"); + BigInt b2 = BigInt(b1); + assert(b2 == BigInt("1_234_567_890")); + } + + /// Assignment from built-in integer types. + BigInt opAssign(T)(T x) pure nothrow if (isIntegral!T) + { + data = cast(ulong) absUnsign(x); + sign = (x < 0); + return this; + } + + /// + @system unittest + { + auto b = BigInt("123"); + b = 456; + assert(b == BigInt("456")); + } + + /// Assignment from another BigInt. + BigInt opAssign(T:BigInt)(T x) pure @nogc + { + data = x.data; + sign = x.sign; + return this; + } + + /// + @system unittest + { + auto b1 = BigInt("123"); + auto b2 = BigInt("456"); + b2 = b1; + assert(b2 == BigInt("123")); + } + + /** + * Implements assignment operators from built-in integers of the form + * $(D BigInt op= integer). + */ + BigInt opOpAssign(string op, T)(T y) pure nothrow + if ((op=="+" || op=="-" || op=="*" || op=="/" || op=="%" + || op==">>" || op=="<<" || op=="^^" || op=="|" || op=="&" || op=="^") && isIntegral!T) + { + ulong u = absUnsign(y); + + static if (op=="+") + { + data = BigUint.addOrSubInt(data, u, sign != (y<0), sign); + } + else static if (op=="-") + { + data = BigUint.addOrSubInt(data, u, sign == (y<0), sign); + } + else static if (op=="*") + { + if (y == 0) + { + sign = false; + data = 0UL; + } + else + { + sign = ( sign != (y<0) ); + data = BigUint.mulInt(data, u); + } + } + else static if (op=="/") + { + assert(y != 0, "Division by zero"); + static if (T.sizeof <= uint.sizeof) + { + data = BigUint.divInt(data, cast(uint) u); + } + else + { + data = BigUint.divInt(data, u); + } + sign = data.isZero() ? false : sign ^ (y < 0); + } + else static if (op=="%") + { + assert(y != 0, "Division by zero"); + static if (is(immutable(T) == immutable(long)) || is( immutable(T) == immutable(ulong) )) + { + this %= BigInt(y); + } + else + { + data = cast(ulong) BigUint.modInt(data, cast(uint) u); + if (data.isZero()) + sign = false; + } + // x%y always has the same sign as x. + // This is not the same as mathematical mod. + } + else static if (op==">>" || op=="<<") + { + // Do a left shift if y>0 and <<, or + // if y<0 and >>; else do a right shift. + if (y == 0) + return this; + else if ((y > 0) == (op=="<<")) + { + // Sign never changes during left shift + data = data.opShl(u); + } else + { + data = data.opShr(u); + if (data.isZero()) + sign = false; + } + } + else static if (op=="^^") + { + sign = (y & 1) ? sign : false; + data = BigUint.pow(data, u); + } + else static if (op=="|" || op=="&" || op=="^") + { + BigInt b = y; + opOpAssign!op(b); + } + else static assert(0, "BigInt " ~ op[0..$-1] ~ "= " ~ T.stringof ~ " is not supported"); + return this; + } + + /// + @system unittest + { + //@system because opOpAssign is @system + auto b = BigInt("1_000_000_000"); + + b += 12345; + assert(b == BigInt("1_000_012_345")); + + b /= 5; + assert(b == BigInt("200_002_469")); + } + + /** + * Implements assignment operators of the form $(D BigInt op= BigInt). + */ + BigInt opOpAssign(string op, T)(T y) pure nothrow + if ((op=="+" || op== "-" || op=="*" || op=="|" || op=="&" || op=="^" || op=="/" || op=="%") + && is (T: BigInt)) + { + static if (op == "+") + { + data = BigUint.addOrSub(data, y.data, sign != y.sign, &sign); + } + else static if (op == "-") + { + data = BigUint.addOrSub(data, y.data, sign == y.sign, &sign); + } + else static if (op == "*") + { + data = BigUint.mul(data, y.data); + sign = isZero() ? false : sign ^ y.sign; + } + else static if (op == "/") + { + y.checkDivByZero(); + if (!isZero()) + { + data = BigUint.div(data, y.data); + sign = isZero() ? false : sign ^ y.sign; + } + } + else static if (op == "%") + { + y.checkDivByZero(); + if (!isZero()) + { + data = BigUint.mod(data, y.data); + // x%y always has the same sign as x. + if (isZero()) + sign = false; + } + } + else static if (op == "|" || op == "&" || op == "^") + { + data = BigUint.bitwiseOp!op(data, y.data, sign, y.sign, sign); + } + else static assert(0, "BigInt " ~ op[0..$-1] ~ "= " ~ + T.stringof ~ " is not supported"); + return this; + } + + /// + @system unittest + { + // @system because opOpAssign is @system + auto x = BigInt("123"); + auto y = BigInt("321"); + x += y; + assert(x == BigInt("444")); + } + + /** + * Implements binary operators between BigInts. + */ + BigInt opBinary(string op, T)(T y) pure nothrow const + if ((op=="+" || op == "*" || op=="-" || op=="|" || op=="&" || op=="^" || + op=="/" || op=="%") + && is (T: BigInt)) + { + BigInt r = this; + return r.opOpAssign!(op)(y); + } + + /// + @system unittest + { + auto x = BigInt("123"); + auto y = BigInt("456"); + BigInt z = x * y; + assert(z == BigInt("56088")); + } + + /** + * Implements binary operators between BigInt's and built-in integers. + */ + BigInt opBinary(string op, T)(T y) pure nothrow const + if ((op=="+" || op == "*" || op=="-" || op=="/" || op=="|" || op=="&" || + op=="^"|| op==">>" || op=="<<" || op=="^^") + && isIntegral!T) + { + BigInt r = this; + return r.opOpAssign!(op)(y); + } + + /// + @system unittest + { + auto x = BigInt("123"); + x *= 300; + assert(x == BigInt("36900")); + } + + /** + Implements a narrowing remainder operation with built-in integer types. + + This binary operator returns a narrower, built-in integer type + where applicable, according to the following table. + + $(TABLE , + $(TR $(TD `BigInt`) $(TD $(CODE_PERCENT)) $(TD `long`) $(TD $(RARR)) $(TD `long`)) + $(TR $(TD `BigInt`) $(TD $(CODE_PERCENT)) $(TD `ulong`) $(TD $(RARR)) $(TD `BigInt`)) + $(TR $(TD `BigInt`) $(TD $(CODE_PERCENT)) $(TD other type) $(TD $(RARR)) $(TD `int`)) + ) + */ + auto opBinary(string op, T)(T y) pure nothrow const + if (op == "%" && isIntegral!T) + { + assert(y != 0); + + // BigInt % long => long + // BigInt % ulong => BigInt + // BigInt % other_type => int + static if (is(Unqual!T == long) || is(Unqual!T == ulong)) + { + auto r = this % BigInt(y); + + static if (is(Unqual!T == long)) + { + return r.toLong(); + } + else + { + // return as-is to avoid overflow + return r; + } + } + else + { + immutable uint u = absUnsign(y); + int rem = BigUint.modInt(data, u); + // x%y always has the same sign as x. + // This is not the same as mathematical mod. + return sign ? -rem : rem; + } + } + + /// + @system unittest + { + auto x = BigInt("1_000_000_500"); + long l = 1_000_000L; + ulong ul = 2_000_000UL; + int i = 500_000; + short s = 30_000; + + assert(is(typeof(x % l) == long) && x % l == 500L); + assert(is(typeof(x % ul) == BigInt) && x % ul == BigInt(500)); + assert(is(typeof(x % i) == int) && x % i == 500); + assert(is(typeof(x % s) == int) && x % s == 10500); + } + + /** + Implements operators with built-in integers on the left-hand side and + BigInt on the right-hand side. + */ + BigInt opBinaryRight(string op, T)(T y) pure nothrow const + if ((op=="+" || op=="*" || op=="|" || op=="&" || op=="^") && isIntegral!T) + { + return opBinary!(op)(y); + } + + /// + @system unittest + { + auto x = BigInt("100"); + BigInt y = 123 + x; + assert(y == BigInt("223")); + + BigInt z = 123 - x; + assert(z == BigInt("23")); + + // Dividing a built-in integer type by BigInt always results in + // something that fits in a built-in type, so the built-in type is + // returned, not BigInt. + assert(is(typeof(1000 / x) == int)); + assert(1000 / x == 10); + } + + // BigInt = integer op BigInt + /// ditto + BigInt opBinaryRight(string op, T)(T y) pure nothrow const + if (op == "-" && isIntegral!T) + { + ulong u = absUnsign(y); + BigInt r; + static if (op == "-") + { + r.sign = sign; + r.data = BigUint.addOrSubInt(data, u, sign == (y<0), r.sign); + r.negate(); + } + return r; + } + + // integer = integer op BigInt + /// ditto + T opBinaryRight(string op, T)(T x) pure nothrow const + if ((op=="%" || op=="/") && isIntegral!T) + { + checkDivByZero(); + + static if (op == "%") + { + // x%y always has the same sign as x. + if (data.ulongLength > 1) + return x; + immutable u = absUnsign(x); + immutable rem = u % data.peekUlong(0); + // x%y always has the same sign as x. + return cast(T)((x<0) ? -rem : rem); + } + else static if (op == "/") + { + if (data.ulongLength > 1) + return 0; + return cast(T)(x / data.peekUlong(0)); + } + } + + // const unary operations + /** + Implements BigInt unary operators. + */ + BigInt opUnary(string op)() pure nothrow const if (op=="+" || op=="-" || op=="~") + { + static if (op=="-") + { + BigInt r = this; + r.negate(); + return r; + } + else static if (op=="~") + { + return -(this+1); + } + else static if (op=="+") + return this; + } + + // non-const unary operations + /// ditto + BigInt opUnary(string op)() pure nothrow if (op=="++" || op=="--") + { + static if (op=="++") + { + data = BigUint.addOrSubInt(data, 1UL, sign, sign); + return this; + } + else static if (op=="--") + { + data = BigUint.addOrSubInt(data, 1UL, !sign, sign); + return this; + } + } + + /// + @system unittest + { + auto x = BigInt("1234"); + assert(-x == BigInt("-1234")); + + ++x; + assert(x == BigInt("1235")); + } + + /** + Implements BigInt equality test with other BigInt's and built-in + integer types. + */ + bool opEquals()(auto ref const BigInt y) const pure @nogc + { + return sign == y.sign && y.data == data; + } + + /// ditto + bool opEquals(T)(T y) const pure nothrow @nogc if (isIntegral!T) + { + if (sign != (y<0)) + return 0; + return data.opEquals(cast(ulong) absUnsign(y)); + } + + /// + @system unittest + { + auto x = BigInt("12345"); + auto y = BigInt("12340"); + int z = 12345; + int w = 54321; + + assert(x == x); + assert(x != y); + assert(x == y + 5); + assert(x == z); + assert(x != w); + } + + /** + Implements casting to bool. + */ + T opCast(T:bool)() pure nothrow @nogc const + { + return !isZero(); + } + + /// + @system unittest + { + // Non-zero values are regarded as true + auto x = BigInt("1"); + auto y = BigInt("10"); + assert(x); + assert(y); + + // Zero value is regarded as false + auto z = BigInt("0"); + assert(!z); + } + + /** + Implements casting to integer types. + + Throws: $(REF ConvOverflowException, std,conv) if the number exceeds + the target type's range. + */ + T opCast(T:ulong)() /*pure*/ const + { + if (isUnsigned!T && sign) + { /* throw */ } + else + if (data.ulongLength == 1) + { + ulong l = data.peekUlong(0); + if (isUnsigned!T || !sign) + { + if (l <= T.max) + return cast(T) l; + } + else + { + if (l <= ulong(T.max)+1) + return cast(T)-long(l); // -long.min == long.min + } + } + + import std.conv : ConvOverflowException; + import std.string : format; + throw new ConvOverflowException( + "BigInt(%d) cannot be represented as a %s" + .format(this, T.stringof)); + } + + /// + @system unittest + { + import std.conv : to, ConvOverflowException; + import std.exception : assertThrown; + + assert(BigInt("0").to!int == 0); + + assert(BigInt("0").to!ubyte == 0); + assert(BigInt("255").to!ubyte == 255); + assertThrown!ConvOverflowException(BigInt("256").to!ubyte); + assertThrown!ConvOverflowException(BigInt("-1").to!ubyte); + } + + @system unittest + { + import std.conv : to, ConvOverflowException; + import std.exception : assertThrown; + + assert(BigInt("-1").to!byte == -1); + assert(BigInt("-128").to!byte == -128); + assert(BigInt("127").to!byte == 127); + assertThrown!ConvOverflowException(BigInt("-129").to!byte); + assertThrown!ConvOverflowException(BigInt("128").to!byte); + + assert(BigInt("0").to!uint == 0); + assert(BigInt("4294967295").to!uint == uint.max); + assertThrown!ConvOverflowException(BigInt("4294967296").to!uint); + assertThrown!ConvOverflowException(BigInt("-1").to!uint); + + assert(BigInt("-1").to!int == -1); + assert(BigInt("-2147483648").to!int == int.min); + assert(BigInt("2147483647").to!int == int.max); + assertThrown!ConvOverflowException(BigInt("-2147483649").to!int); + assertThrown!ConvOverflowException(BigInt("2147483648").to!int); + + assert(BigInt("0").to!ulong == 0); + assert(BigInt("18446744073709551615").to!ulong == ulong.max); + assertThrown!ConvOverflowException(BigInt("18446744073709551616").to!ulong); + assertThrown!ConvOverflowException(BigInt("-1").to!ulong); + + assert(BigInt("-1").to!long == -1); + assert(BigInt("-9223372036854775808").to!long == long.min); + assert(BigInt("9223372036854775807").to!long == long.max); + assertThrown!ConvOverflowException(BigInt("-9223372036854775809").to!long); + assertThrown!ConvOverflowException(BigInt("9223372036854775808").to!long); + } + + /** + Implements casting to/from qualified BigInt's. + + Warning: Casting to/from $(D const) or $(D immutable) may break type + system guarantees. Use with care. + */ + T opCast(T)() pure nothrow @nogc const + if (is(Unqual!T == BigInt)) + { + return this; + } + + /// + @system unittest + { + const(BigInt) x = BigInt("123"); + BigInt y = cast() x; // cast away const + assert(y == x); + } + + // Hack to make BigInt's typeinfo.compare work properly. + // Note that this must appear before the other opCmp overloads, otherwise + // DMD won't find it. + /** + Implements 3-way comparisons of BigInt with BigInt or BigInt with + built-in integers. + */ + int opCmp(ref const BigInt y) pure nothrow @nogc const + { + // Simply redirect to the "real" opCmp implementation. + return this.opCmp!BigInt(y); + } + + /// ditto + int opCmp(T)(T y) pure nothrow @nogc const if (isIntegral!T) + { + if (sign != (y<0) ) + return sign ? -1 : 1; + int cmp = data.opCmp(cast(ulong) absUnsign(y)); + return sign? -cmp: cmp; + } + /// ditto + int opCmp(T:BigInt)(const T y) pure nothrow @nogc const + { + if (sign != y.sign) + return sign ? -1 : 1; + immutable cmp = data.opCmp(y.data); + return sign? -cmp: cmp; + } + + /// + @system unittest + { + auto x = BigInt("100"); + auto y = BigInt("10"); + int z = 50; + const int w = 200; + + assert(y < x); + assert(x > z); + assert(z > y); + assert(x < w); + } + + /** + Returns: The value of this BigInt as a long, or +/- long.max if outside + the representable range. + */ + long toLong() @safe pure nothrow const @nogc + { + return (sign ? -1 : 1) * + (data.ulongLength == 1 && (data.peekUlong(0) <= sign+cast(ulong)(long.max)) // 1+long.max = |long.min| + ? cast(long)(data.peekUlong(0)) + : long.max); + } + + /// + @system unittest + { + auto b = BigInt("12345"); + long l = b.toLong(); + assert(l == 12345); + } + + /** + Returns: The value of this BigInt as an int, or +/- int.max if outside + the representable range. + */ + int toInt() @safe pure nothrow @nogc const + { + return (sign ? -1 : 1) * + (data.uintLength == 1 && (data.peekUint(0) <= sign+cast(uint)(int.max)) // 1+int.max = |int.min| + ? cast(int)(data.peekUint(0)) + : int.max); + } + + /// + @system unittest + { + auto big = BigInt("5_000_000"); + auto i = big.toInt(); + assert(i == 5_000_000); + + // Numbers that are too big to fit into an int will be clamped to int.max. + auto tooBig = BigInt("5_000_000_000"); + i = tooBig.toInt(); + assert(i == int.max); + } + + /// Number of significant uints which are used in storing this number. + /// The absolute value of this BigInt is always < 2$(SUPERSCRIPT 32*uintLength) + @property size_t uintLength() @safe pure nothrow @nogc const + { + return data.uintLength; + } + + /// Number of significant ulongs which are used in storing this number. + /// The absolute value of this BigInt is always < 2$(SUPERSCRIPT 64*ulongLength) + @property size_t ulongLength() @safe pure nothrow @nogc const + { + return data.ulongLength; + } + + /** Convert the BigInt to string, passing it to the given sink. + * + * Params: + * sink = A delegate for accepting possibly piecewise segments of the + * formatted string. + * formatString = A format string specifying the output format. + * + * $(TABLE Available output formats:, + * $(TR $(TD "d") $(TD Decimal)) + * $(TR $(TD "o") $(TD Octal)) + * $(TR $(TD "x") $(TD Hexadecimal, lower case)) + * $(TR $(TD "X") $(TD Hexadecimal, upper case)) + * $(TR $(TD "s") $(TD Default formatting (same as "d") )) + * $(TR $(TD null) $(TD Default formatting (same as "d") )) + * ) + */ + void toString(scope void delegate(const (char)[]) sink, string formatString) const + { + auto f = FormatSpec!char(formatString); + f.writeUpToNextSpec(sink); + toString(sink, f); + } + + /// ditto + void toString(scope void delegate(const(char)[]) sink, ref FormatSpec!char f) const + { + immutable hex = (f.spec == 'x' || f.spec == 'X'); + if (!(f.spec == 's' || f.spec == 'd' || f.spec =='o' || hex)) + throw new FormatException("Format specifier not understood: %" ~ f.spec); + + char[] buff; + if (f.spec == 'X') + { + buff = data.toHexString(0, '_', 0, f.flZero ? '0' : ' ', LetterCase.upper); + } + else if (f.spec == 'x') + { + buff = data.toHexString(0, '_', 0, f.flZero ? '0' : ' ', LetterCase.lower); + } + else if (f.spec == 'o') + { + buff = data.toOctalString(); + } + else + { + buff = data.toDecimalString(0); + } + assert(buff.length > 0); + + char signChar = isNegative() ? '-' : 0; + auto minw = buff.length + (signChar ? 1 : 0); + + if (!hex && !signChar && (f.width == 0 || minw < f.width)) + { + if (f.flPlus) + { + signChar = '+'; + ++minw; + } + else if (f.flSpace) + { + signChar = ' '; + ++minw; + } + } + + immutable maxw = minw < f.width ? f.width : minw; + immutable difw = maxw - minw; + + if (!f.flDash && !f.flZero) + foreach (i; 0 .. difw) + sink(" "); + + if (signChar) + sink((&signChar)[0 .. 1]); + + if (!f.flDash && f.flZero) + foreach (i; 0 .. difw) + sink("0"); + + sink(buff); + + if (f.flDash) + foreach (i; 0 .. difw) + sink(" "); + } + + /** + $(D toString) is rarely directly invoked; the usual way of using it is via + $(REF format, std, format): + */ + @system unittest + { + import std.format : format; + + auto x = BigInt("1_000_000"); + x *= 12345; + + assert(format("%d", x) == "12345000000"); + assert(format("%x", x) == "2_dfd1c040"); + assert(format("%X", x) == "2_DFD1C040"); + assert(format("%o", x) == "133764340100"); + } + + // Implement toHash so that BigInt works properly as an AA key. + /** + Returns: A unique hash of the BigInt's value suitable for use in a hash + table. + */ + size_t toHash() const @safe nothrow + { + return data.toHash() + sign; + } + + /** + $(D toHash) is rarely directly invoked; it is implicitly used when + BigInt is used as the key of an associative array. + */ + @safe unittest + { + string[BigInt] aa; + aa[BigInt(123)] = "abc"; + aa[BigInt(456)] = "def"; + + assert(aa[BigInt(123)] == "abc"); + assert(aa[BigInt(456)] == "def"); + } + +private: + void negate() @safe pure nothrow @nogc + { + if (!data.isZero()) + sign = !sign; + } + bool isZero() pure const nothrow @nogc @safe + { + return data.isZero(); + } + bool isNegative() pure const nothrow @nogc @safe + { + return sign; + } + + // Generate a runtime error if division by zero occurs + void checkDivByZero() pure const nothrow @safe + { + if (isZero()) + throw new Error("BigInt division by zero"); + } +} + +/// +@system unittest +{ + BigInt a = "9588669891916142"; + BigInt b = "7452469135154800"; + auto c = a * b; + assert(c == BigInt("71459266416693160362545788781600")); + auto d = b * a; + assert(d == BigInt("71459266416693160362545788781600")); + assert(d == c); + d = c * BigInt("794628672112"); + assert(d == BigInt("56783581982794522489042432639320434378739200")); + auto e = c + d; + assert(e == BigInt("56783581982865981755459125799682980167520800")); + auto f = d + c; + assert(f == e); + auto g = f - c; + assert(g == d); + g = f - d; + assert(g == c); + e = 12345678; + g = c + e; + auto h = g / b; + auto i = g % b; + assert(h == a); + assert(i == e); + BigInt j = "-0x9A56_57f4_7B83_AB78"; + j ^^= 11; +} + +/** +Params: + x = The $(D BigInt) to convert to a decimal $(D string). + +Returns: + A $(D string) that represents the $(D BigInt) as a decimal number. + +*/ +string toDecimalString(const(BigInt) x) +{ + string outbuff=""; + void sink(const(char)[] s) { outbuff ~= s; } + x.toString(&sink, "%d"); + return outbuff; +} + +/// +@system unittest +{ + auto x = BigInt("123"); + x *= 1000; + x += 456; + + auto xstr = x.toDecimalString(); + assert(xstr == "123456"); +} + +/** +Params: + x = The $(D BigInt) to convert to a hexadecimal $(D string). + +Returns: + A $(D string) that represents the $(D BigInt) as a hexadecimal (base 16) + number in upper case. + +*/ +string toHex(const(BigInt) x) +{ + string outbuff=""; + void sink(const(char)[] s) { outbuff ~= s; } + x.toString(&sink, "%X"); + return outbuff; +} + +/// +@system unittest +{ + auto x = BigInt("123"); + x *= 1000; + x += 456; + + auto xstr = x.toHex(); + assert(xstr == "1E240"); +} + +/** Returns the absolute value of x converted to the corresponding unsigned +type. + +Params: + x = The integral value to return the absolute value of. + +Returns: + The absolute value of x. + +*/ +Unsigned!T absUnsign(T)(T x) +if (isIntegral!T) +{ + static if (isSigned!T) + { + import std.conv : unsigned; + /* This returns the correct result even when x = T.min + * on two's complement machines because unsigned(T.min) = |T.min| + * even though -T.min = T.min. + */ + return unsigned((x < 0) ? cast(T)(0-x) : x); + } + else + { + return x; + } +} + +/// +nothrow pure @system +unittest +{ + assert((-1).absUnsign == 1); + assert(1.absUnsign == 1); +} + +nothrow pure @system +unittest +{ + BigInt a, b; + a = 1; + b = 2; + auto c = a + b; +} + +nothrow pure @system +unittest +{ + long a; + BigInt b; + auto c = a + b; + auto d = b + a; +} + +nothrow pure @system +unittest +{ + BigInt x = 1, y = 2; + assert(x < y); + assert(x <= y); + assert(y >= x); + assert(y > x); + assert(x != y); + + long r1 = x.toLong; + assert(r1 == 1); + + BigInt r2 = 10 % x; + assert(r2 == 0); + + BigInt r3 = 10 / y; + assert(r3 == 5); + + BigInt[] arr = [BigInt(1)]; + auto incr = arr[0]++; + assert(arr == [BigInt(2)]); + assert(incr == BigInt(1)); +} + +@system unittest +{ + // Radix conversion + assert( toDecimalString(BigInt("-1_234_567_890_123_456_789")) + == "-1234567890123456789"); + assert( toHex(BigInt("0x1234567890123456789")) == "123_45678901_23456789"); + assert( toHex(BigInt("0x00000000000000000000000000000000000A234567890123456789")) + == "A23_45678901_23456789"); + assert( toHex(BigInt("0x000_00_000000_000_000_000000000000_000000_")) == "0"); + + assert(BigInt(-0x12345678).toInt() == -0x12345678); + assert(BigInt(-0x12345678).toLong() == -0x12345678); + assert(BigInt(0x1234_5678_9ABC_5A5AL).ulongLength == 1); + assert(BigInt(0x1234_5678_9ABC_5A5AL).toLong() == 0x1234_5678_9ABC_5A5AL); + assert(BigInt(-0x1234_5678_9ABC_5A5AL).toLong() == -0x1234_5678_9ABC_5A5AL); + assert(BigInt(0xF234_5678_9ABC_5A5AL).toLong() == long.max); + assert(BigInt(-0x123456789ABCL).toInt() == -int.max); + char[] s1 = "123".dup; // bug 8164 + assert(BigInt(s1) == 123); + char[] s2 = "0xABC".dup; + assert(BigInt(s2) == 2748); + + assert((BigInt(-2) + BigInt(1)) == BigInt(-1)); + BigInt a = ulong.max - 5; + auto b = -long.max % a; + assert( b == -long.max % (ulong.max - 5)); + b = long.max / a; + assert( b == long.max /(ulong.max - 5)); + assert(BigInt(1) - 1 == 0); + assert((-4) % BigInt(5) == -4); // bug 5928 + assert(BigInt(-4) % BigInt(5) == -4); + assert(BigInt(2)/BigInt(-3) == BigInt(0)); // bug 8022 + assert(BigInt("-1") > long.min); // bug 9548 + + assert(toDecimalString(BigInt("0000000000000000000000000000000000000000001234567")) + == "1234567"); +} + +@system unittest // Minimum signed value bug tests. +{ + assert(BigInt("-0x8000000000000000") == BigInt(long.min)); + assert(BigInt("-0x8000000000000000")+1 > BigInt(long.min)); + assert(BigInt("-0x80000000") == BigInt(int.min)); + assert(BigInt("-0x80000000")+1 > BigInt(int.min)); + assert(BigInt(long.min).toLong() == long.min); // lossy toLong bug for long.min + assert(BigInt(int.min).toInt() == int.min); // lossy toInt bug for int.min + assert(BigInt(long.min).ulongLength == 1); + assert(BigInt(int.min).uintLength == 1); // cast/sign extend bug in opAssign + BigInt a; + a += int.min; + assert(a == BigInt(int.min)); + a = int.min - BigInt(int.min); + assert(a == 0); + a = int.min; + assert(a == BigInt(int.min)); + assert(int.min % (BigInt(int.min)-1) == int.min); + assert((BigInt(int.min)-1)%int.min == -1); +} + +@system unittest // Recursive division, bug 5568 +{ + enum Z = 4843; + BigInt m = (BigInt(1) << (Z*8) ) - 1; + m -= (BigInt(1) << (Z*6)) - 1; + BigInt oldm = m; + + BigInt a = (BigInt(1) << (Z*4) )-1; + BigInt b = m % a; + m /= a; + m *= a; + assert( m + b == oldm); + + m = (BigInt(1) << (4846 + 4843) ) - 1; + a = (BigInt(1) << 4846 ) - 1; + b = (BigInt(1) << (4846*2 + 4843)) - 1; + BigInt c = (BigInt(1) << (4846*2 + 4843*2)) - 1; + BigInt w = c - b + a; + assert(w % m == 0); + + // Bug 6819. ^^ + BigInt z1 = BigInt(10)^^64; + BigInt w1 = BigInt(10)^^128; + assert(z1^^2 == w1); + BigInt z2 = BigInt(1)<<64; + BigInt w2 = BigInt(1)<<128; + assert(z2^^2 == w2); + // Bug 7993 + BigInt n7793 = 10; + assert( n7793 / 1 == 10); + // Bug 7973 + auto a7973 = 10_000_000_000_000_000; + const c7973 = 10_000_000_000_000_000; + immutable i7973 = 10_000_000_000_000_000; + BigInt v7973 = 2551700137; + v7973 %= a7973; + assert(v7973 == 2551700137); + v7973 %= c7973; + assert(v7973 == 2551700137); + v7973 %= i7973; + assert(v7973 == 2551700137); + // 8165 + BigInt[2] a8165; + a8165[0] = a8165[1] = 1; +} + +@system unittest +{ + import std.array; + import std.format; + + immutable string[][] table = [ + /* fmt, +10 -10 */ + ["%d", "10", "-10"], + ["%+d", "+10", "-10"], + ["%-d", "10", "-10"], + ["%+-d", "+10", "-10"], + + ["%4d", " 10", " -10"], + ["%+4d", " +10", " -10"], + ["%-4d", "10 ", "-10 "], + ["%+-4d", "+10 ", "-10 "], + + ["%04d", "0010", "-010"], + ["%+04d", "+010", "-010"], + ["%-04d", "10 ", "-10 "], + ["%+-04d", "+10 ", "-10 "], + + ["% 04d", " 010", "-010"], + ["%+ 04d", "+010", "-010"], + ["%- 04d", " 10 ", "-10 "], + ["%+- 04d", "+10 ", "-10 "], + ]; + + auto w1 = appender!(char[])(); + auto w2 = appender!(char[])(); + + foreach (entry; table) + { + immutable fmt = entry[0]; + + formattedWrite(w1, fmt, BigInt(10)); + formattedWrite(w2, fmt, 10); + assert(w1.data == w2.data); + assert(w1.data == entry[1]); + w1.clear(); + w2.clear(); + + formattedWrite(w1, fmt, BigInt(-10)); + formattedWrite(w2, fmt, -10); + assert(w1.data == w2.data); + assert(w1.data == entry[2]); + w1.clear(); + w2.clear(); + } +} + +@system unittest +{ + import std.array; + import std.format; + + immutable string[][] table = [ + /* fmt, +10 -10 */ + ["%x", "a", "-a"], + ["%+x", "a", "-a"], + ["%-x", "a", "-a"], + ["%+-x", "a", "-a"], + + ["%4x", " a", " -a"], + ["%+4x", " a", " -a"], + ["%-4x", "a ", "-a "], + ["%+-4x", "a ", "-a "], + + ["%04x", "000a", "-00a"], + ["%+04x", "000a", "-00a"], + ["%-04x", "a ", "-a "], + ["%+-04x", "a ", "-a "], + + ["% 04x", "000a", "-00a"], + ["%+ 04x", "000a", "-00a"], + ["%- 04x", "a ", "-a "], + ["%+- 04x", "a ", "-a "], + ]; + + auto w1 = appender!(char[])(); + auto w2 = appender!(char[])(); + + foreach (entry; table) + { + immutable fmt = entry[0]; + + formattedWrite(w1, fmt, BigInt(10)); + formattedWrite(w2, fmt, 10); + assert(w1.data == w2.data); // Equal only positive BigInt + assert(w1.data == entry[1]); + w1.clear(); + w2.clear(); + + formattedWrite(w1, fmt, BigInt(-10)); + //formattedWrite(w2, fmt, -10); + //assert(w1.data == w2.data); + assert(w1.data == entry[2]); + w1.clear(); + //w2.clear(); + } +} + +@system unittest +{ + import std.array; + import std.format; + + immutable string[][] table = [ + /* fmt, +10 -10 */ + ["%X", "A", "-A"], + ["%+X", "A", "-A"], + ["%-X", "A", "-A"], + ["%+-X", "A", "-A"], + + ["%4X", " A", " -A"], + ["%+4X", " A", " -A"], + ["%-4X", "A ", "-A "], + ["%+-4X", "A ", "-A "], + + ["%04X", "000A", "-00A"], + ["%+04X", "000A", "-00A"], + ["%-04X", "A ", "-A "], + ["%+-04X", "A ", "-A "], + + ["% 04X", "000A", "-00A"], + ["%+ 04X", "000A", "-00A"], + ["%- 04X", "A ", "-A "], + ["%+- 04X", "A ", "-A "], + ]; + + auto w1 = appender!(char[])(); + auto w2 = appender!(char[])(); + + foreach (entry; table) + { + immutable fmt = entry[0]; + + formattedWrite(w1, fmt, BigInt(10)); + formattedWrite(w2, fmt, 10); + assert(w1.data == w2.data); // Equal only positive BigInt + assert(w1.data == entry[1]); + w1.clear(); + w2.clear(); + + formattedWrite(w1, fmt, BigInt(-10)); + //formattedWrite(w2, fmt, -10); + //assert(w1.data == w2.data); + assert(w1.data == entry[2]); + w1.clear(); + //w2.clear(); + } +} + +// 6448 +@system unittest +{ + import std.array; + import std.format; + + auto w1 = appender!string(); + auto w2 = appender!string(); + + int x = 100; + formattedWrite(w1, "%010d", x); + BigInt bx = x; + formattedWrite(w2, "%010d", bx); + assert(w1.data == w2.data); + //8011 + BigInt y = -3; + ++y; + assert(y.toLong() == -2); + y = 1; + --y; + assert(y.toLong() == 0); + --y; + assert(y.toLong() == -1); + --y; + assert(y.toLong() == -2); +} + +@safe unittest +{ + import std.math : abs; + auto r = abs(BigInt(-1000)); // 6486 + assert(r == 1000); + + auto r2 = abs(const(BigInt)(-500)); // 11188 + assert(r2 == 500); + auto r3 = abs(immutable(BigInt)(-733)); // 11188 + assert(r3 == 733); + + // opCast!bool + BigInt one = 1, zero; + assert(one && !zero); +} + +@system unittest // 6850 +{ + pure long pureTest() { + BigInt a = 1; + BigInt b = 1336; + a += b; + return a.toLong(); + } + + assert(pureTest() == 1337); +} + +@system unittest // 8435 & 10118 +{ + auto i = BigInt(100); + auto j = BigInt(100); + + // Two separate BigInt instances representing same value should have same + // hash. + assert(typeid(i).getHash(&i) == typeid(j).getHash(&j)); + assert(typeid(i).compare(&i, &j) == 0); + + // BigInt AA keys should behave consistently. + int[BigInt] aa; + aa[BigInt(123)] = 123; + assert(BigInt(123) in aa); + + aa[BigInt(123)] = 321; + assert(aa[BigInt(123)] == 321); + + auto keys = aa.byKey; + assert(keys.front == BigInt(123)); + keys.popFront(); + assert(keys.empty); +} + +@system unittest // 11148 +{ + void foo(BigInt) {} + const BigInt cbi = 3; + immutable BigInt ibi = 3; + + assert(__traits(compiles, foo(cbi))); + assert(__traits(compiles, foo(ibi))); + + import std.conv : to; + import std.meta : AliasSeq; + + foreach (T1; AliasSeq!(BigInt, const(BigInt), immutable(BigInt))) + { + foreach (T2; AliasSeq!(BigInt, const(BigInt), immutable(BigInt))) + { + T1 t1 = 2; + T2 t2 = t1; + + T2 t2_1 = to!T2(t1); + T2 t2_2 = cast(T2) t1; + + assert(t2 == t1); + assert(t2 == 2); + + assert(t2_1 == t1); + assert(t2_1 == 2); + + assert(t2_2 == t1); + assert(t2_2 == 2); + } + } + + BigInt n = 2; + n *= 2; +} + +@safe unittest // 8167 +{ + BigInt a = BigInt(3); + BigInt b = BigInt(a); +} + +@safe unittest // 9061 +{ + long l1 = 0x12345678_90ABCDEF; + long l2 = 0xFEDCBA09_87654321; + long l3 = l1 | l2; + long l4 = l1 & l2; + long l5 = l1 ^ l2; + + BigInt b1 = l1; + BigInt b2 = l2; + BigInt b3 = b1 | b2; + BigInt b4 = b1 & b2; + BigInt b5 = b1 ^ b2; + + assert(l3 == b3); + assert(l4 == b4); + assert(l5 == b5); +} + +@system unittest // 11600 +{ + import std.conv; + import std.exception : assertThrown; + + // Original bug report + assertThrown!ConvException(to!BigInt("avadakedavra")); + + // Digit string lookalikes that are actually invalid + assertThrown!ConvException(to!BigInt("0123hellothere")); + assertThrown!ConvException(to!BigInt("-hihomarylowe")); + assertThrown!ConvException(to!BigInt("__reallynow__")); + assertThrown!ConvException(to!BigInt("-123four")); +} + +@safe unittest // 11583 +{ + BigInt x = 0; + assert((x > 0) == false); +} + +@system unittest // 13391 +{ + BigInt x1 = "123456789"; + BigInt x2 = "123456789123456789"; + BigInt x3 = "123456789123456789123456789"; + + import std.meta : AliasSeq; + foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong)) + { + assert((x1 * T.max) / T.max == x1); + assert((x2 * T.max) / T.max == x2); + assert((x3 * T.max) / T.max == x3); + } + + assert(x1 / -123456789 == -1); + assert(x1 / 123456789U == 1); + assert(x1 / -123456789L == -1); + assert(x1 / 123456789UL == 1); + assert(x2 / -123456789123456789L == -1); + assert(x2 / 123456789123456789UL == 1); + + assert(x1 / uint.max == 0); + assert(x1 / ulong.max == 0); + assert(x2 / ulong.max == 0); + + x1 /= 123456789UL; + assert(x1 == 1); + x2 /= 123456789123456789UL; + assert(x2 == 1); +} + +@system unittest // 13963 +{ + BigInt x = 1; + import std.meta : AliasSeq; + foreach (Int; AliasSeq!(byte, ubyte, short, ushort, int, uint)) + { + assert(is(typeof(x % Int(1)) == int)); + } + assert(is(typeof(x % 1L) == long)); + assert(is(typeof(x % 1UL) == BigInt)); + + auto x1 = BigInt(8); + auto x2 = -BigInt(long.min) + 1; + + // long + assert(x1 % 2L == 0L); + assert(-x1 % 2L == 0L); + + assert(x1 % 3L == 2L); + assert(x1 % -3L == 2L); + assert(-x1 % 3L == -2L); + assert(-x1 % -3L == -2L); + + assert(x1 % 11L == 8L); + assert(x1 % -11L == 8L); + assert(-x1 % 11L == -8L); + assert(-x1 % -11L == -8L); + + // ulong + assert(x1 % 2UL == BigInt(0)); + assert(-x1 % 2UL == BigInt(0)); + + assert(x1 % 3UL == BigInt(2)); + assert(-x1 % 3UL == -BigInt(2)); + + assert(x1 % 11UL == BigInt(8)); + assert(-x1 % 11UL == -BigInt(8)); + + assert(x2 % ulong.max == x2); + assert(-x2 % ulong.max == -x2); +} + +@system unittest // 14124 +{ + auto x = BigInt(-3); + x %= 3; + assert(!x.isNegative()); + assert(x.isZero()); + + x = BigInt(-3); + x %= cast(ushort) 3; + assert(!x.isNegative()); + assert(x.isZero()); + + x = BigInt(-3); + x %= 3L; + assert(!x.isNegative()); + assert(x.isZero()); + + x = BigInt(3); + x %= -3; + assert(!x.isNegative()); + assert(x.isZero()); +} + +// issue 15678 +@system unittest +{ + import std.exception : assertThrown; + assertThrown!ConvException(BigInt("")); + assertThrown!ConvException(BigInt("0x1234BARF")); + assertThrown!ConvException(BigInt("1234PUKE")); +} + +// Issue 6447 +@system unittest +{ + import std.algorithm.comparison : equal; + import std.range : iota; + + auto s = BigInt(1_000_000_000_000); + auto e = BigInt(1_000_000_000_003); + auto r = iota(s, e); + assert(r.equal([ + BigInt(1_000_000_000_000), + BigInt(1_000_000_000_001), + BigInt(1_000_000_000_002) + ])); +} + +// Issue 17330 +@system unittest +{ + auto b = immutable BigInt("123"); +} diff --git a/libphobos/src/std/bitmanip.d b/libphobos/src/std/bitmanip.d new file mode 100644 index 0000000..7802dff --- /dev/null +++ b/libphobos/src/std/bitmanip.d @@ -0,0 +1,4009 @@ +// Written in the D programming language. + +/** +Bit-level manipulation facilities. + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE, +$(TR $(TH Category) $(TH Functions)) +$(TR $(TD Bit constructs) $(TD + $(LREF BitArray) + $(LREF bitfields) + $(LREF bitsSet) +)) +$(TR $(TD Endianness conversion) $(TD + $(LREF bigEndianToNative) + $(LREF littleEndianToNative) + $(LREF nativeToBigEndian) + $(LREF nativeToLittleEndian) + $(LREF swapEndian) +)) +$(TR $(TD Integral ranges) $(TD + $(LREF append) + $(LREF peek) + $(LREF read) + $(LREF write) +)) +$(TR $(TD Floating-Point manipulation) $(TD + $(LREF DoubleRep) + $(LREF FloatRep) +)) +$(TR $(TD Tagging) $(TD + $(LREF taggedClassRef) + $(LREF taggedPointer) +)) +) + +Copyright: Copyright Digital Mars 2007 - 2011. +License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). +Authors: $(HTTP digitalmars.com, Walter Bright), + $(HTTP erdani.org, Andrei Alexandrescu), + Jonathan M Davis, + Alex Rønne Petersen, + Damian Ziemba, + Amaury SECHET +Source: $(PHOBOSSRC std/_bitmanip.d) +*/ +/* + Copyright Digital Mars 2007 - 2012. +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) +*/ +module std.bitmanip; + +//debug = bitarray; // uncomment to turn on debugging printf's + +import std.range.primitives; +public import std.system : Endian; +import std.traits; + +version (unittest) +{ + import std.stdio; +} + + +private string myToString(ulong n) +{ + import core.internal.string : UnsignedStringBuf, unsignedToTempString; + UnsignedStringBuf buf; + auto s = unsignedToTempString(n, buf); + return cast(string) s ~ (n > uint.max ? "UL" : "U"); +} + +private template createAccessors( + string store, T, string name, size_t len, size_t offset) +{ + static if (!name.length) + { + // No need to create any accessor + enum result = ""; + } + else static if (len == 0) + { + // Fields of length 0 are always zero + enum result = "enum "~T.stringof~" "~name~" = 0;\n"; + } + else + { + enum ulong + maskAllElse = ((~0uL) >> (64 - len)) << offset, + signBitCheck = 1uL << (len - 1); + + static if (T.min < 0) + { + enum long minVal = -(1uL << (len - 1)); + enum ulong maxVal = (1uL << (len - 1)) - 1; + alias UT = Unsigned!(T); + enum UT extendSign = cast(UT)~((~0uL) >> (64 - len)); + } + else + { + enum ulong minVal = 0; + enum ulong maxVal = (~0uL) >> (64 - len); + enum extendSign = 0; + } + + static if (is(T == bool)) + { + static assert(len == 1); + enum result = + // getter + "@property bool " ~ name ~ "() @safe pure nothrow @nogc const { return " + ~"("~store~" & "~myToString(maskAllElse)~") != 0;}\n" + // setter + ~"@property void " ~ name ~ "(bool v) @safe pure nothrow @nogc { " + ~"if (v) "~store~" |= "~myToString(maskAllElse)~";" + ~"else "~store~" &= cast(typeof("~store~"))(-1-cast(typeof("~store~"))"~myToString(maskAllElse)~");}\n"; + } + else + { + // getter + enum result = "@property "~T.stringof~" "~name~"() @safe pure nothrow @nogc const { auto result = " + ~"("~store~" & " + ~ myToString(maskAllElse) ~ ") >>" + ~ myToString(offset) ~ ";" + ~ (T.min < 0 + ? "if (result >= " ~ myToString(signBitCheck) + ~ ") result |= " ~ myToString(extendSign) ~ ";" + : "") + ~ " return cast("~T.stringof~") result;}\n" + // setter + ~"@property void "~name~"("~T.stringof~" v) @safe pure nothrow @nogc { " + ~"assert(v >= "~name~`_min, "Value is smaller than the minimum value of bitfield '`~name~`'"); ` + ~"assert(v <= "~name~`_max, "Value is greater than the maximum value of bitfield '`~name~`'"); ` + ~store~" = cast(typeof("~store~"))" + ~" (("~store~" & (-1-cast(typeof("~store~"))"~myToString(maskAllElse)~"))" + ~" | ((cast(typeof("~store~")) v << "~myToString(offset)~")" + ~" & "~myToString(maskAllElse)~"));}\n" + // constants + ~"enum "~T.stringof~" "~name~"_min = cast("~T.stringof~")" + ~myToString(minVal)~"; " + ~" enum "~T.stringof~" "~name~"_max = cast("~T.stringof~")" + ~myToString(maxVal)~"; "; + } + } +} + +private template createStoreName(Ts...) +{ + static if (Ts.length < 2) + enum createStoreName = ""; + else + enum createStoreName = "_" ~ Ts[1] ~ createStoreName!(Ts[3 .. $]); +} + +private template createStorageAndFields(Ts...) +{ + enum Name = createStoreName!Ts; + enum Size = sizeOfBitField!Ts; + static if (Size == ubyte.sizeof * 8) + alias StoreType = ubyte; + else static if (Size == ushort.sizeof * 8) + alias StoreType = ushort; + else static if (Size == uint.sizeof * 8) + alias StoreType = uint; + else static if (Size == ulong.sizeof * 8) + alias StoreType = ulong; + else + { + static assert(false, "Field widths must sum to 8, 16, 32, or 64"); + alias StoreType = ulong; // just to avoid another error msg + } + enum result + = "private " ~ StoreType.stringof ~ " " ~ Name ~ ";" + ~ createFields!(Name, 0, Ts).result; +} + +private template createFields(string store, size_t offset, Ts...) +{ + static if (Ts.length > 0) + enum result + = createAccessors!(store, Ts[0], Ts[1], Ts[2], offset).result + ~ createFields!(store, offset + Ts[2], Ts[3 .. $]).result; + else + enum result = ""; +} + +private ulong getBitsForAlign(ulong a) +{ + ulong bits = 0; + while ((a & 0x01) == 0) + { + bits++; + a >>= 1; + } + + assert(a == 1, "alignment is not a power of 2"); + return bits; +} + +private template createReferenceAccessor(string store, T, ulong bits, string name) +{ + enum storage = "private void* " ~ store ~ "_ptr;\n"; + enum storage_accessor = "@property ref size_t " ~ store ~ "() return @trusted pure nothrow @nogc const { " + ~ "return *cast(size_t*) &" ~ store ~ "_ptr;}\n" + ~ "@property void " ~ store ~ "(size_t v) @trusted pure nothrow @nogc { " + ~ "" ~ store ~ "_ptr = cast(void*) v;}\n"; + + enum mask = (1UL << bits) - 1; + // getter + enum ref_accessor = "@property "~T.stringof~" "~name~"() @trusted pure nothrow @nogc const { auto result = " + ~ "("~store~" & "~myToString(~mask)~"); " + ~ "return cast("~T.stringof~") cast(void*) result;}\n" + // setter + ~"@property void "~name~"("~T.stringof~" v) @trusted pure nothrow @nogc { " + ~"assert(((cast(typeof("~store~")) cast(void*) v) & "~myToString(mask) + ~`) == 0, "Value not properly aligned for '`~name~`'"); ` + ~store~" = cast(typeof("~store~"))" + ~" (("~store~" & (cast(typeof("~store~")) "~myToString(mask)~"))" + ~" | ((cast(typeof("~store~")) cast(void*) v) & (cast(typeof("~store~")) "~myToString(~mask)~")));}\n"; + + enum result = storage ~ storage_accessor ~ ref_accessor; +} + +private template sizeOfBitField(T...) +{ + static if (T.length < 2) + enum sizeOfBitField = 0; + else + enum sizeOfBitField = T[2] + sizeOfBitField!(T[3 .. $]); +} + +private template createTaggedReference(T, ulong a, string name, Ts...) +{ + static assert( + sizeOfBitField!Ts <= getBitsForAlign(a), + "Fields must fit in the bits know to be zero because of alignment." + ); + enum StoreName = createStoreName!(T, name, 0, Ts); + enum result + = createReferenceAccessor!(StoreName, T, sizeOfBitField!Ts, name).result + ~ createFields!(StoreName, 0, Ts, size_t, "", T.sizeof * 8 - sizeOfBitField!Ts).result; +} + +/** +Allows creating bit fields inside $(D_PARAM struct)s and $(D_PARAM +class)es. + +Example: + +---- +struct A +{ + int a; + mixin(bitfields!( + uint, "x", 2, + int, "y", 3, + uint, "z", 2, + bool, "flag", 1)); +} +A obj; +obj.x = 2; +obj.z = obj.x; +---- + +The example above creates a bitfield pack of eight bits, which fit in +one $(D_PARAM ubyte). The bitfields are allocated starting from the +least significant bit, i.e. x occupies the two least significant bits +of the bitfields storage. + +The sum of all bit lengths in one $(D_PARAM bitfield) instantiation +must be exactly 8, 16, 32, or 64. If padding is needed, just allocate +one bitfield with an empty name. + +Example: + +---- +struct A +{ + mixin(bitfields!( + bool, "flag1", 1, + bool, "flag2", 1, + uint, "", 6)); +} +---- + +The type of a bit field can be any integral type or enumerated +type. The most efficient type to store in bitfields is $(D_PARAM +bool), followed by unsigned types, followed by signed types. +*/ + +template bitfields(T...) +{ + enum { bitfields = createStorageAndFields!T.result } +} + +/** +This string mixin generator allows one to create tagged pointers inside $(D_PARAM struct)s and $(D_PARAM class)es. + +A tagged pointer uses the bits known to be zero in a normal pointer or class reference to store extra information. +For example, a pointer to an integer must be 4-byte aligned, so there are 2 bits that are always known to be zero. +One can store a 2-bit integer there. + +The example above creates a tagged pointer in the struct A. The pointer is of type +$(D uint*) as specified by the first argument, and is named x, as specified by the second +argument. + +Following arguments works the same way as $(D bitfield)'s. The bitfield must fit into the +bits known to be zero because of the pointer alignment. +*/ + +template taggedPointer(T : T*, string name, Ts...) { + enum taggedPointer = createTaggedReference!(T*, T.alignof, name, Ts).result; +} + +/// +@safe unittest +{ + struct A + { + int a; + mixin(taggedPointer!( + uint*, "x", + bool, "b1", 1, + bool, "b2", 1)); + } + A obj; + obj.x = new uint; + obj.b1 = true; + obj.b2 = false; +} + +/** +This string mixin generator allows one to create tagged class reference inside $(D_PARAM struct)s and $(D_PARAM class)es. + +A tagged class reference uses the bits known to be zero in a normal class reference to store extra information. +For example, a pointer to an integer must be 4-byte aligned, so there are 2 bits that are always known to be zero. +One can store a 2-bit integer there. + +The example above creates a tagged reference to an Object in the struct A. This expects the same parameters +as $(D taggedPointer), except the first argument which must be a class type instead of a pointer type. +*/ + +template taggedClassRef(T, string name, Ts...) +if (is(T == class)) +{ + enum taggedClassRef = createTaggedReference!(T, 8, name, Ts).result; +} + +/// +@safe unittest +{ + struct A + { + int a; + mixin(taggedClassRef!( + Object, "o", + uint, "i", 2)); + } + A obj; + obj.o = new Object(); + obj.i = 3; +} + +@safe pure nothrow @nogc +unittest +{ + // Degenerate bitfields (#8474 / #11160) tests mixed with range tests + struct Test1 + { + mixin(bitfields!(uint, "a", 32, + uint, "b", 4, + uint, "c", 4, + uint, "d", 8, + uint, "e", 16,)); + + static assert(Test1.b_min == 0); + static assert(Test1.b_max == 15); + } + + struct Test2 + { + mixin(bitfields!(bool, "a", 0, + ulong, "b", 64)); + + static assert(Test2.b_min == ulong.min); + static assert(Test2.b_max == ulong.max); + } + + struct Test1b + { + mixin(bitfields!(bool, "a", 0, + int, "b", 8)); + } + + struct Test2b + { + mixin(bitfields!(int, "a", 32, + int, "b", 4, + int, "c", 4, + int, "d", 8, + int, "e", 16,)); + + static assert(Test2b.b_min == -8); + static assert(Test2b.b_max == 7); + } + + struct Test3b + { + mixin(bitfields!(bool, "a", 0, + long, "b", 64)); + + static assert(Test3b.b_min == long.min); + static assert(Test3b.b_max == long.max); + } + + struct Test4b + { + mixin(bitfields!(long, "a", 32, + int, "b", 32)); + } + + // Sign extension tests + Test2b t2b; + Test4b t4b; + t2b.b = -5; assert(t2b.b == -5); + t2b.d = -5; assert(t2b.d == -5); + t2b.e = -5; assert(t2b.e == -5); + t4b.a = -5; assert(t4b.a == -5L); +} + +@system unittest +{ + struct Test5 + { + mixin(taggedPointer!( + int*, "a", + uint, "b", 2)); + } + + Test5 t5; + t5.a = null; + t5.b = 3; + assert(t5.a is null); + assert(t5.b == 3); + + int myint = 42; + t5.a = &myint; + assert(t5.a is &myint); + assert(t5.b == 3); + + struct Test6 + { + mixin(taggedClassRef!( + Object, "o", + bool, "b", 1)); + } + + Test6 t6; + t6.o = null; + t6.b = false; + assert(t6.o is null); + assert(t6.b == false); + + auto o = new Object(); + t6.o = o; + t6.b = true; + assert(t6.o is o); + assert(t6.b == true); +} + +@safe unittest +{ + static assert(!__traits(compiles, + taggedPointer!( + int*, "a", + uint, "b", 3))); + + static assert(!__traits(compiles, + taggedClassRef!( + Object, "a", + uint, "b", 4))); + + struct S { + mixin(taggedClassRef!( + Object, "a", + bool, "b", 1)); + } + + const S s; + void bar(S s) {} + + static assert(!__traits(compiles, bar(s))); +} + +@safe unittest +{ + // Bug #6686 + union S { + ulong bits = ulong.max; + mixin (bitfields!( + ulong, "back", 31, + ulong, "front", 33) + ); + } + S num; + + num.bits = ulong.max; + num.back = 1; + assert(num.bits == 0xFFFF_FFFF_8000_0001uL); +} + +@safe unittest +{ + // Bug #5942 + struct S + { + mixin(bitfields!( + int, "a" , 32, + int, "b" , 32 + )); + } + + S data; + data.b = 42; + data.a = 1; + assert(data.b == 42); +} + +@safe unittest +{ + struct Test + { + mixin(bitfields!(bool, "a", 1, + uint, "b", 3, + short, "c", 4)); + } + + @safe void test() pure nothrow + { + Test t; + + t.a = true; + t.b = 5; + t.c = 2; + + assert(t.a); + assert(t.b == 5); + assert(t.c == 2); + } + + test(); +} + +@safe unittest +{ + { + static struct Integrals { + bool checkExpectations(bool eb, int ei, short es) { return b == eb && i == ei && s == es; } + + mixin(bitfields!( + bool, "b", 1, + uint, "i", 3, + short, "s", 4)); + } + Integrals i; + assert(i.checkExpectations(false, 0, 0)); + i.b = true; + assert(i.checkExpectations(true, 0, 0)); + i.i = 7; + assert(i.checkExpectations(true, 7, 0)); + i.s = -8; + assert(i.checkExpectations(true, 7, -8)); + i.s = 7; + assert(i.checkExpectations(true, 7, 7)); + } + + //Bug# 8876 + { + struct MoreIntegrals { + bool checkExpectations(uint eu, ushort es, uint ei) { return u == eu && s == es && i == ei; } + + mixin(bitfields!( + uint, "u", 24, + short, "s", 16, + int, "i", 24)); + } + + MoreIntegrals i; + assert(i.checkExpectations(0, 0, 0)); + i.s = 20; + assert(i.checkExpectations(0, 20, 0)); + i.i = 72; + assert(i.checkExpectations(0, 20, 72)); + i.u = 8; + assert(i.checkExpectations(8, 20, 72)); + i.s = 7; + assert(i.checkExpectations(8, 7, 72)); + } + + enum A { True, False } + enum B { One, Two, Three, Four } + static struct Enums { + bool checkExpectations(A ea, B eb) { return a == ea && b == eb; } + + mixin(bitfields!( + A, "a", 1, + B, "b", 2, + uint, "", 5)); + } + Enums e; + assert(e.checkExpectations(A.True, B.One)); + e.a = A.False; + assert(e.checkExpectations(A.False, B.One)); + e.b = B.Three; + assert(e.checkExpectations(A.False, B.Three)); + + static struct SingleMember { + bool checkExpectations(bool eb) { return b == eb; } + + mixin(bitfields!( + bool, "b", 1, + uint, "", 7)); + } + SingleMember f; + assert(f.checkExpectations(false)); + f.b = true; + assert(f.checkExpectations(true)); +} + +// Issue 12477 +@system unittest +{ + import core.exception : AssertError; + import std.algorithm.searching : canFind; + import std.bitmanip : bitfields; + + static struct S + { + mixin(bitfields!( + uint, "a", 6, + int, "b", 2)); + } + + S s; + + try { s.a = uint.max; assert(0); } + catch (AssertError ae) + { assert(ae.msg.canFind("Value is greater than the maximum value of bitfield 'a'"), ae.msg); } + + try { s.b = int.min; assert(0); } + catch (AssertError ae) + { assert(ae.msg.canFind("Value is smaller than the minimum value of bitfield 'b'"), ae.msg); } +} + +/** + Allows manipulating the fraction, exponent, and sign parts of a + $(D_PARAM float) separately. The definition is: + +---- +struct FloatRep +{ + union + { + float value; + mixin(bitfields!( + uint, "fraction", 23, + ubyte, "exponent", 8, + bool, "sign", 1)); + } + enum uint bias = 127, fractionBits = 23, exponentBits = 8, signBits = 1; +} +---- +*/ + +struct FloatRep +{ + union + { + float value; + mixin(bitfields!( + uint, "fraction", 23, + ubyte, "exponent", 8, + bool, "sign", 1)); + } + enum uint bias = 127, fractionBits = 23, exponentBits = 8, signBits = 1; +} + +/** + Allows manipulating the fraction, exponent, and sign parts of a + $(D_PARAM double) separately. The definition is: + +---- +struct DoubleRep +{ + union + { + double value; + mixin(bitfields!( + ulong, "fraction", 52, + ushort, "exponent", 11, + bool, "sign", 1)); + } + enum uint bias = 1023, signBits = 1, fractionBits = 52, exponentBits = 11; +} +---- +*/ + +struct DoubleRep +{ + union + { + double value; + mixin(bitfields!( + ulong, "fraction", 52, + ushort, "exponent", 11, + bool, "sign", 1)); + } + enum uint bias = 1023, signBits = 1, fractionBits = 52, exponentBits = 11; +} + +@safe unittest +{ + // test reading + DoubleRep x; + x.value = 1.0; + assert(x.fraction == 0 && x.exponent == 1023 && !x.sign); + x.value = -0.5; + assert(x.fraction == 0 && x.exponent == 1022 && x.sign); + x.value = 0.5; + assert(x.fraction == 0 && x.exponent == 1022 && !x.sign); + + // test writing + x.fraction = 1125899906842624; + x.exponent = 1025; + x.sign = true; + assert(x.value == -5.0); + + // test enums + enum ABC { A, B, C } + struct EnumTest + { + mixin(bitfields!( + ABC, "x", 2, + bool, "y", 1, + ubyte, "z", 5)); + } +} + +@safe unittest +{ + // Issue #15305 + struct S { + mixin(bitfields!( + bool, "alice", 1, + ulong, "bob", 63, + )); + } + + S s; + s.bob = long.max - 1; + s.alice = false; + assert(s.bob == long.max - 1); +} + +/** + * An array of bits. + */ + +struct BitArray +{ +private: + + import core.bitop : bts, btr, bsf, bt; + import std.format : FormatSpec; + + size_t _len; + size_t* _ptr; + enum bitsPerSizeT = size_t.sizeof * 8; + + @property size_t fullWords() const @nogc pure nothrow + { + return _len / bitsPerSizeT; + } + // Number of bits after the last full word + @property size_t endBits() const @nogc pure nothrow + { + return _len % bitsPerSizeT; + } + // Bit mask to extract the bits after the last full word + @property size_t endMask() const @nogc pure nothrow + { + return (size_t(1) << endBits) - 1; + } + static size_t lenToDim(size_t len) @nogc pure nothrow @safe + { + return (len + (bitsPerSizeT-1)) / bitsPerSizeT; + } + +public: + /********************************************** + * Gets the amount of native words backing this $(D BitArray). + */ + @property size_t dim() const @nogc pure nothrow @safe + { + return lenToDim(_len); + } + + /********************************************** + * Gets the amount of bits in the $(D BitArray). + */ + @property size_t length() const @nogc pure nothrow @safe + { + return _len; + } + + /********************************************** + * Sets the amount of bits in the $(D BitArray). + * $(RED Warning: increasing length may overwrite bits in + * final word up to the next word boundary. i.e. D dynamic + * array extension semantics are not followed.) + */ + @property size_t length(size_t newlen) pure nothrow @system + { + if (newlen != _len) + { + size_t olddim = dim; + immutable newdim = lenToDim(newlen); + + if (newdim != olddim) + { + // Create a fake array so we can use D's realloc machinery + auto b = _ptr[0 .. olddim]; + b.length = newdim; // realloc + _ptr = b.ptr; + } + + _len = newlen; + } + return _len; + } + + /********************************************** + * Gets the $(D i)'th bit in the $(D BitArray). + */ + bool opIndex(size_t i) const @nogc pure nothrow + in + { + assert(i < _len); + } + body + { + return cast(bool) bt(_ptr, i); + } + + @system unittest + { + debug(bitarray) printf("BitArray.opIndex.unittest\n"); + + void Fun(const BitArray arr) + { + auto x = arr[0]; + assert(x == 1); + } + BitArray a; + a.length = 3; + a[0] = 1; + Fun(a); + } + + /********************************************** + * Sets the $(D i)'th bit in the $(D BitArray). + */ + bool opIndexAssign(bool b, size_t i) @nogc pure nothrow + in + { + assert(i < _len); + } + body + { + if (b) + bts(_ptr, i); + else + btr(_ptr, i); + return b; + } + + /********************************************** + * Duplicates the $(D BitArray) and its contents. + */ + @property BitArray dup() const pure nothrow + { + BitArray ba; + + auto b = _ptr[0 .. dim].dup; + ba._len = _len; + ba._ptr = b.ptr; + return ba; + } + + @system unittest + { + BitArray a; + BitArray b; + int i; + + debug(bitarray) printf("BitArray.dup.unittest\n"); + + a.length = 3; + a[0] = 1; a[1] = 0; a[2] = 1; + b = a.dup; + assert(b.length == 3); + for (i = 0; i < 3; i++) + { debug(bitarray) printf("b[%d] = %d\n", i, b[i]); + assert(b[i] == (((i ^ 1) & 1) ? true : false)); + } + } + + /********************************************** + * Support for $(D foreach) loops for $(D BitArray). + */ + int opApply(scope int delegate(ref bool) dg) + { + int result; + + foreach (i; 0 .. _len) + { + bool b = opIndex(i); + result = dg(b); + this[i] = b; + if (result) + break; + } + return result; + } + + /** ditto */ + int opApply(scope int delegate(bool) dg) const + { + int result; + + foreach (i; 0 .. _len) + { + immutable b = opIndex(i); + result = dg(b); + if (result) + break; + } + return result; + } + + /** ditto */ + int opApply(scope int delegate(size_t, ref bool) dg) + { + int result; + + foreach (i; 0 .. _len) + { + bool b = opIndex(i); + result = dg(i, b); + this[i] = b; + if (result) + break; + } + return result; + } + + /** ditto */ + int opApply(scope int delegate(size_t, bool) dg) const + { + int result; + + foreach (i; 0 .. _len) + { + immutable b = opIndex(i); + result = dg(i, b); + if (result) + break; + } + return result; + } + + @system unittest + { + debug(bitarray) printf("BitArray.opApply unittest\n"); + + static bool[] ba = [1,0,1]; + + auto a = BitArray(ba); + + int i; + foreach (b;a) + { + switch (i) + { + case 0: assert(b == true); break; + case 1: assert(b == false); break; + case 2: assert(b == true); break; + default: assert(0); + } + i++; + } + + foreach (j,b;a) + { + switch (j) + { + case 0: assert(b == true); break; + case 1: assert(b == false); break; + case 2: assert(b == true); break; + default: assert(0); + } + } + } + + + /********************************************** + * Reverses the bits of the $(D BitArray). + */ + @property BitArray reverse() @nogc pure nothrow + out (result) + { + assert(result == this); + } + body + { + if (_len >= 2) + { + bool t; + size_t lo, hi; + + lo = 0; + hi = _len - 1; + for (; lo < hi; lo++, hi--) + { + t = this[lo]; + this[lo] = this[hi]; + this[hi] = t; + } + } + return this; + } + + @system unittest + { + debug(bitarray) printf("BitArray.reverse.unittest\n"); + + BitArray b; + static bool[5] data = [1,0,1,1,0]; + int i; + + b = BitArray(data); + b.reverse; + for (i = 0; i < data.length; i++) + { + assert(b[i] == data[4 - i]); + } + } + + + /********************************************** + * Sorts the $(D BitArray)'s elements. + */ + @property BitArray sort() @nogc pure nothrow + out (result) + { + assert(result == this); + } + body + { + if (_len >= 2) + { + size_t lo, hi; + + lo = 0; + hi = _len - 1; + while (1) + { + while (1) + { + if (lo >= hi) + goto Ldone; + if (this[lo] == true) + break; + lo++; + } + + while (1) + { + if (lo >= hi) + goto Ldone; + if (this[hi] == false) + break; + hi--; + } + + this[lo] = false; + this[hi] = true; + + lo++; + hi--; + } + } + Ldone: + return this; + } + + @system unittest + { + debug(bitarray) printf("BitArray.sort.unittest\n"); + + __gshared size_t x = 0b1100011000; + __gshared ba = BitArray(10, &x); + ba.sort; + for (size_t i = 0; i < 6; i++) + assert(ba[i] == false); + for (size_t i = 6; i < 10; i++) + assert(ba[i] == true); + } + + + /*************************************** + * Support for operators == and != for $(D BitArray). + */ + bool opEquals(const ref BitArray a2) const @nogc pure nothrow + { + if (this.length != a2.length) + return false; + auto p1 = this._ptr; + auto p2 = a2._ptr; + + if (p1[0 .. fullWords] != p2[0 .. fullWords]) + return false; + + if (!endBits) + return true; + + auto i = fullWords; + return (p1[i] & endMask) == (p2[i] & endMask); + } + + @system unittest + { + debug(bitarray) printf("BitArray.opEquals unittest\n"); + + static bool[] ba = [1,0,1,0,1]; + static bool[] bb = [1,0,1]; + static bool[] bc = [1,0,1,0,1,0,1]; + static bool[] bd = [1,0,1,1,1]; + static bool[] be = [1,0,1,0,1]; + static bool[] bf = [1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; + static bool[] bg = [1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]; + + auto a = BitArray(ba); + auto b = BitArray(bb); + auto c = BitArray(bc); + auto d = BitArray(bd); + auto e = BitArray(be); + auto f = BitArray(bf); + auto g = BitArray(bg); + + assert(a != b); + assert(a != c); + assert(a != d); + assert(a == e); + assert(f != g); + } + + /*************************************** + * Supports comparison operators for $(D BitArray). + */ + int opCmp(BitArray a2) const @nogc pure nothrow + { + const lesser = this.length < a2.length ? &this : &a2; + immutable fullWords = lesser.fullWords; + immutable endBits = lesser.endBits; + auto p1 = this._ptr; + auto p2 = a2._ptr; + + foreach (i; 0 .. fullWords) + { + if (p1[i] != p2[i]) + { + return p1[i] & (size_t(1) << bsf(p1[i] ^ p2[i])) ? 1 : -1; + } + } + + if (endBits) + { + immutable i = fullWords; + immutable diff = p1[i] ^ p2[i]; + if (diff) + { + immutable index = bsf(diff); + if (index < endBits) + { + return p1[i] & (size_t(1) << index) ? 1 : -1; + } + } + } + + // Standard: + // A bool value can be implicitly converted to any integral type, + // with false becoming 0 and true becoming 1 + return (this.length > a2.length) - (this.length < a2.length); + } + + @system unittest + { + debug(bitarray) printf("BitArray.opCmp unittest\n"); + + static bool[] ba = [1,0,1,0,1]; + static bool[] bb = [1,0,1]; + static bool[] bc = [1,0,1,0,1,0,1]; + static bool[] bd = [1,0,1,1,1]; + static bool[] be = [1,0,1,0,1]; + static bool[] bf = [1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]; + static bool[] bg = [1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0]; + + auto a = BitArray(ba); + auto b = BitArray(bb); + auto c = BitArray(bc); + auto d = BitArray(bd); + auto e = BitArray(be); + auto f = BitArray(bf); + auto g = BitArray(bg); + + assert(a > b); + assert(a >= b); + assert(a < c); + assert(a <= c); + assert(a < d); + assert(a <= d); + assert(a == e); + assert(a <= e); + assert(a >= e); + assert(f < g); + assert(g <= g); + + bool[] v; + foreach (i; 1 .. 256) + { + v.length = i; + v[] = false; + auto x = BitArray(v); + v[i-1] = true; + auto y = BitArray(v); + assert(x < y); + assert(x <= y); + } + + BitArray a1, a2; + + for (size_t len = 4; len <= 256; len <<= 1) + { + a1.length = a2.length = len; + a1[len-2] = a2[len-1] = true; + assert(a1 > a2); + a1[len-2] = a2[len-1] = false; + } + + foreach (j; 1 .. a1.length) + { + a1[j-1] = a2[j] = true; + assert(a1 > a2); + a1[j-1] = a2[j] = false; + } + } + + /*************************************** + * Support for hashing for $(D BitArray). + */ + size_t toHash() const @nogc pure nothrow + { + size_t hash = 3557; + auto fullBytes = _len / 8; + foreach (i; 0 .. fullBytes) + { + hash *= 3559; + hash += (cast(byte*) this._ptr)[i]; + } + foreach (i; 8*fullBytes .. _len) + { + hash *= 3571; + hash += this[i]; + } + return hash; + } + + /*************************************** + * Set this $(D BitArray) to the contents of $(D ba). + */ + this(bool[] ba) pure nothrow @system + { + length = ba.length; + foreach (i, b; ba) + { + this[i] = b; + } + } + + // Deliberately undocumented: raw initialization of bit array. + this(size_t len, size_t* ptr) + { + _len = len; + _ptr = ptr; + } + + /*************************************** + * Map the $(D BitArray) onto $(D v), with $(D numbits) being the number of bits + * in the array. Does not copy the data. $(D v.length) must be a multiple of + * $(D size_t.sizeof). If there are unmapped bits in the final mapped word then + * these will be set to 0. + * + * This is the inverse of $(D opCast). + */ + this(void[] v, size_t numbits) pure nothrow + in + { + assert(numbits <= v.length * 8); + assert(v.length % size_t.sizeof == 0); + } + body + { + _ptr = cast(size_t*) v.ptr; + _len = numbits; + if (endBits) + { + // Need to mask away extraneous bits from v. + _ptr[dim - 1] &= endMask; + } + } + + @system unittest + { + debug(bitarray) printf("BitArray.init unittest\n"); + + static bool[] ba = [1,0,1,0,1]; + + auto a = BitArray(ba); + void[] v; + + v = cast(void[]) a; + auto b = BitArray(v, a.length); + + assert(b[0] == 1); + assert(b[1] == 0); + assert(b[2] == 1); + assert(b[3] == 0); + assert(b[4] == 1); + + a[0] = 0; + assert(b[0] == 0); + + assert(a == b); + } + + /*************************************** + * Convert to $(D void[]). + */ + void[] opCast(T : void[])() @nogc pure nothrow + { + return cast(void[])_ptr[0 .. dim]; + } + + /*************************************** + * Convert to $(D size_t[]). + */ + size_t[] opCast(T : size_t[])() @nogc pure nothrow + { + return _ptr[0 .. dim]; + } + + @system unittest + { + debug(bitarray) printf("BitArray.opCast unittest\n"); + + static bool[] ba = [1,0,1,0,1]; + + auto a = BitArray(ba); + void[] v = cast(void[]) a; + + assert(v.length == a.dim * size_t.sizeof); + } + + /*************************************** + * Support for unary operator ~ for $(D BitArray). + */ + BitArray opCom() const pure nothrow + { + auto dim = this.dim; + + BitArray result; + result.length = _len; + + result._ptr[0 .. dim] = ~this._ptr[0 .. dim]; + + // Avoid putting garbage in extra bits + // Remove once we zero on length extension + if (endBits) + result._ptr[dim - 1] &= endMask; + + return result; + } + + @system unittest + { + debug(bitarray) printf("BitArray.opCom unittest\n"); + + static bool[] ba = [1,0,1,0,1]; + + auto a = BitArray(ba); + BitArray b = ~a; + + assert(b[0] == 0); + assert(b[1] == 1); + assert(b[2] == 0); + assert(b[3] == 1); + assert(b[4] == 0); + } + + + /*************************************** + * Support for binary bitwise operators for $(D BitArray). + */ + BitArray opBinary(string op)(const BitArray e2) const pure nothrow + if (op == "-" || op == "&" || op == "|" || op == "^") + in + { + assert(_len == e2.length); + } + body + { + auto dim = this.dim; + + BitArray result; + result.length = _len; + + static if (op == "-") + result._ptr[0 .. dim] = this._ptr[0 .. dim] & ~e2._ptr[0 .. dim]; + else + mixin("result._ptr[0 .. dim] = this._ptr[0 .. dim]"~op~" e2._ptr[0 .. dim];"); + + // Avoid putting garbage in extra bits + // Remove once we zero on length extension + if (endBits) + result._ptr[dim - 1] &= endMask; + + return result; + } + + @system unittest + { + debug(bitarray) printf("BitArray.opAnd unittest\n"); + + static bool[] ba = [1,0,1,0,1]; + static bool[] bb = [1,0,1,1,0]; + + auto a = BitArray(ba); + auto b = BitArray(bb); + + BitArray c = a & b; + + assert(c[0] == 1); + assert(c[1] == 0); + assert(c[2] == 1); + assert(c[3] == 0); + assert(c[4] == 0); + } + + @system unittest + { + debug(bitarray) printf("BitArray.opOr unittest\n"); + + static bool[] ba = [1,0,1,0,1]; + static bool[] bb = [1,0,1,1,0]; + + auto a = BitArray(ba); + auto b = BitArray(bb); + + BitArray c = a | b; + + assert(c[0] == 1); + assert(c[1] == 0); + assert(c[2] == 1); + assert(c[3] == 1); + assert(c[4] == 1); + } + + @system unittest + { + debug(bitarray) printf("BitArray.opXor unittest\n"); + + static bool[] ba = [1,0,1,0,1]; + static bool[] bb = [1,0,1,1,0]; + + auto a = BitArray(ba); + auto b = BitArray(bb); + + BitArray c = a ^ b; + + assert(c[0] == 0); + assert(c[1] == 0); + assert(c[2] == 0); + assert(c[3] == 1); + assert(c[4] == 1); + } + + @system unittest + { + debug(bitarray) printf("BitArray.opSub unittest\n"); + + static bool[] ba = [1,0,1,0,1]; + static bool[] bb = [1,0,1,1,0]; + + auto a = BitArray(ba); + auto b = BitArray(bb); + + BitArray c = a - b; + + assert(c[0] == 0); + assert(c[1] == 0); + assert(c[2] == 0); + assert(c[3] == 0); + assert(c[4] == 1); + } + + + /*************************************** + * Support for operator op= for $(D BitArray). + */ + BitArray opOpAssign(string op)(const BitArray e2) @nogc pure nothrow + if (op == "-" || op == "&" || op == "|" || op == "^") + in + { + assert(_len == e2.length); + } + body + { + foreach (i; 0 .. fullWords) + { + static if (op == "-") + _ptr[i] &= ~e2._ptr[i]; + else + mixin("_ptr[i] "~op~"= e2._ptr[i];"); + } + if (!endBits) + return this; + + size_t i = fullWords; + size_t endWord = _ptr[i]; + static if (op == "-") + endWord &= ~e2._ptr[i]; + else + mixin("endWord "~op~"= e2._ptr[i];"); + _ptr[i] = (_ptr[i] & ~endMask) | (endWord & endMask); + + return this; + } + + @system unittest + { + static bool[] ba = [1,0,1,0,1,1,0,1,0,1]; + static bool[] bb = [1,0,1,1,0]; + auto a = BitArray(ba); + auto b = BitArray(bb); + BitArray c = a; + c.length = 5; + c &= b; + assert(a[5] == 1); + assert(a[6] == 0); + assert(a[7] == 1); + assert(a[8] == 0); + assert(a[9] == 1); + } + + @system unittest + { + debug(bitarray) printf("BitArray.opAndAssign unittest\n"); + + static bool[] ba = [1,0,1,0,1]; + static bool[] bb = [1,0,1,1,0]; + + auto a = BitArray(ba); + auto b = BitArray(bb); + + a &= b; + assert(a[0] == 1); + assert(a[1] == 0); + assert(a[2] == 1); + assert(a[3] == 0); + assert(a[4] == 0); + } + + @system unittest + { + debug(bitarray) printf("BitArray.opOrAssign unittest\n"); + + static bool[] ba = [1,0,1,0,1]; + static bool[] bb = [1,0,1,1,0]; + + auto a = BitArray(ba); + auto b = BitArray(bb); + + a |= b; + assert(a[0] == 1); + assert(a[1] == 0); + assert(a[2] == 1); + assert(a[3] == 1); + assert(a[4] == 1); + } + + @system unittest + { + debug(bitarray) printf("BitArray.opXorAssign unittest\n"); + + static bool[] ba = [1,0,1,0,1]; + static bool[] bb = [1,0,1,1,0]; + + auto a = BitArray(ba); + auto b = BitArray(bb); + + a ^= b; + assert(a[0] == 0); + assert(a[1] == 0); + assert(a[2] == 0); + assert(a[3] == 1); + assert(a[4] == 1); + } + + @system unittest + { + debug(bitarray) printf("BitArray.opSubAssign unittest\n"); + + static bool[] ba = [1,0,1,0,1]; + static bool[] bb = [1,0,1,1,0]; + + auto a = BitArray(ba); + auto b = BitArray(bb); + + a -= b; + assert(a[0] == 0); + assert(a[1] == 0); + assert(a[2] == 0); + assert(a[3] == 0); + assert(a[4] == 1); + } + + /*************************************** + * Support for operator ~= for $(D BitArray). + * $(RED Warning: This will overwrite a bit in the final word + * of the current underlying data regardless of whether it is + * shared between BitArray objects. i.e. D dynamic array + * concatenation semantics are not followed) + */ + + BitArray opCatAssign(bool b) pure nothrow + { + length = _len + 1; + this[_len - 1] = b; + return this; + } + + @system unittest + { + debug(bitarray) printf("BitArray.opCatAssign unittest\n"); + + static bool[] ba = [1,0,1,0,1]; + + auto a = BitArray(ba); + BitArray b; + + b = (a ~= true); + assert(a[0] == 1); + assert(a[1] == 0); + assert(a[2] == 1); + assert(a[3] == 0); + assert(a[4] == 1); + assert(a[5] == 1); + + assert(b == a); + } + + /*************************************** + * ditto + */ + + BitArray opCatAssign(BitArray b) pure nothrow + { + auto istart = _len; + length = _len + b.length; + for (auto i = istart; i < _len; i++) + this[i] = b[i - istart]; + return this; + } + + @system unittest + { + debug(bitarray) printf("BitArray.opCatAssign unittest\n"); + + static bool[] ba = [1,0]; + static bool[] bb = [0,1,0]; + + auto a = BitArray(ba); + auto b = BitArray(bb); + BitArray c; + + c = (a ~= b); + assert(a.length == 5); + assert(a[0] == 1); + assert(a[1] == 0); + assert(a[2] == 0); + assert(a[3] == 1); + assert(a[4] == 0); + + assert(c == a); + } + + /*************************************** + * Support for binary operator ~ for $(D BitArray). + */ + BitArray opCat(bool b) const pure nothrow + { + BitArray r; + + r = this.dup; + r.length = _len + 1; + r[_len] = b; + return r; + } + + /** ditto */ + BitArray opCat_r(bool b) const pure nothrow + { + BitArray r; + + r.length = _len + 1; + r[0] = b; + foreach (i; 0 .. _len) + r[1 + i] = this[i]; + return r; + } + + /** ditto */ + BitArray opCat(BitArray b) const pure nothrow + { + BitArray r; + + r = this.dup; + r ~= b; + return r; + } + + @system unittest + { + debug(bitarray) printf("BitArray.opCat unittest\n"); + + static bool[] ba = [1,0]; + static bool[] bb = [0,1,0]; + + auto a = BitArray(ba); + auto b = BitArray(bb); + BitArray c; + + c = (a ~ b); + assert(c.length == 5); + assert(c[0] == 1); + assert(c[1] == 0); + assert(c[2] == 0); + assert(c[3] == 1); + assert(c[4] == 0); + + c = (a ~ true); + assert(c.length == 3); + assert(c[0] == 1); + assert(c[1] == 0); + assert(c[2] == 1); + + c = (false ~ a); + assert(c.length == 3); + assert(c[0] == 0); + assert(c[1] == 1); + assert(c[2] == 0); + } + + // Rolls double word (upper, lower) to the right by n bits and returns the + // lower word of the result. + private static size_t rollRight()(size_t upper, size_t lower, size_t nbits) + pure @safe nothrow @nogc + in + { + assert(nbits < bitsPerSizeT); + } + body + { + return (upper << (bitsPerSizeT - nbits)) | (lower >> nbits); + } + + @safe unittest + { + static if (size_t.sizeof == 8) + { + size_t x = 0x12345678_90ABCDEF; + size_t y = 0xFEDBCA09_87654321; + + assert(rollRight(x, y, 32) == 0x90ABCDEF_FEDBCA09); + assert(rollRight(y, x, 4) == 0x11234567_890ABCDE); + } + else static if (size_t.sizeof == 4) + { + size_t x = 0x12345678; + size_t y = 0x90ABCDEF; + + assert(rollRight(x, y, 16) == 0x567890AB); + assert(rollRight(y, x, 4) == 0xF1234567); + } + else + static assert(0, "Unsupported size_t width"); + } + + // Rolls double word (upper, lower) to the left by n bits and returns the + // upper word of the result. + private static size_t rollLeft()(size_t upper, size_t lower, size_t nbits) + pure @safe nothrow @nogc + in + { + assert(nbits < bitsPerSizeT); + } + body + { + return (upper << nbits) | (lower >> (bitsPerSizeT - nbits)); + } + + @safe unittest + { + static if (size_t.sizeof == 8) + { + size_t x = 0x12345678_90ABCDEF; + size_t y = 0xFEDBCA09_87654321; + + assert(rollLeft(x, y, 32) == 0x90ABCDEF_FEDBCA09); + assert(rollLeft(y, x, 4) == 0xEDBCA098_76543211); + } + else static if (size_t.sizeof == 4) + { + size_t x = 0x12345678; + size_t y = 0x90ABCDEF; + + assert(rollLeft(x, y, 16) == 0x567890AB); + assert(rollLeft(y, x, 4) == 0x0ABCDEF1); + } + } + + /** + * Operator $(D <<=) support. + * + * Shifts all the bits in the array to the left by the given number of + * bits. The leftmost bits are dropped, and 0's are appended to the end + * to fill up the vacant bits. + * + * $(RED Warning: unused bits in the final word up to the next word + * boundary may be overwritten by this operation. It does not attempt to + * preserve bits past the end of the array.) + */ + void opOpAssign(string op)(size_t nbits) @nogc pure nothrow + if (op == "<<") + { + size_t wordsToShift = nbits / bitsPerSizeT; + size_t bitsToShift = nbits % bitsPerSizeT; + + if (wordsToShift < dim) + { + foreach_reverse (i; 1 .. dim - wordsToShift) + { + _ptr[i + wordsToShift] = rollLeft(_ptr[i], _ptr[i-1], + bitsToShift); + } + _ptr[wordsToShift] = rollLeft(_ptr[0], 0, bitsToShift); + } + + import std.algorithm.comparison : min; + foreach (i; 0 .. min(wordsToShift, dim)) + { + _ptr[i] = 0; + } + } + + /** + * Operator $(D >>=) support. + * + * Shifts all the bits in the array to the right by the given number of + * bits. The rightmost bits are dropped, and 0's are inserted at the back + * to fill up the vacant bits. + * + * $(RED Warning: unused bits in the final word up to the next word + * boundary may be overwritten by this operation. It does not attempt to + * preserve bits past the end of the array.) + */ + void opOpAssign(string op)(size_t nbits) @nogc pure nothrow + if (op == ">>") + { + size_t wordsToShift = nbits / bitsPerSizeT; + size_t bitsToShift = nbits % bitsPerSizeT; + + if (wordsToShift + 1 < dim) + { + foreach (i; 0 .. dim - wordsToShift - 1) + { + _ptr[i] = rollRight(_ptr[i + wordsToShift + 1], + _ptr[i + wordsToShift], bitsToShift); + } + } + + // The last word needs some care, as it must shift in 0's from past the + // end of the array. + if (wordsToShift < dim) + { + _ptr[dim - wordsToShift - 1] = rollRight(0, _ptr[dim - 1] & endMask, + bitsToShift); + } + + import std.algorithm.comparison : min; + foreach (i; 0 .. min(wordsToShift, dim)) + { + _ptr[dim - i - 1] = 0; + } + } + + @system unittest + { + import std.format : format; + + auto b = BitArray([1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1]); + + b <<= 1; + assert(format("%b", b) == "01100_10101101"); + + b >>= 1; + assert(format("%b", b) == "11001_01011010"); + + b <<= 4; + assert(format("%b", b) == "00001_10010101"); + + b >>= 5; + assert(format("%b", b) == "10010_10100000"); + + b <<= 13; + assert(format("%b", b) == "00000_00000000"); + + b = BitArray([1, 0, 1, 1, 0, 1, 1, 1]); + b >>= 8; + assert(format("%b", b) == "00000000"); + + } + + // Test multi-word case + @system unittest + { + import std.format : format; + + // This has to be long enough to occupy more than one size_t. On 64-bit + // machines, this would be at least 64 bits. + auto b = BitArray([ + 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, + ]); + b <<= 8; + assert(format("%b", b) == + "00000000_10000000_"~ + "11000000_11100000_"~ + "11110000_11111000_"~ + "11111100_11111110_"~ + "11111111_10101010"); + + // Test right shift of more than one size_t's worth of bits + b <<= 68; + assert(format("%b", b) == + "00000000_00000000_"~ + "00000000_00000000_"~ + "00000000_00000000_"~ + "00000000_00000000_"~ + "00000000_00001000"); + + b = BitArray([ + 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, + ]); + b >>= 8; + assert(format("%b", b) == + "11000000_11100000_"~ + "11110000_11111000_"~ + "11111100_11111110_"~ + "11111111_10101010_"~ + "01010101_00000000"); + + // Test left shift of more than 1 size_t's worth of bits + b >>= 68; + assert(format("%b", b) == + "01010000_00000000_"~ + "00000000_00000000_"~ + "00000000_00000000_"~ + "00000000_00000000_"~ + "00000000_00000000"); + } + + /*************************************** + * Return a string representation of this BitArray. + * + * Two format specifiers are supported: + * $(LI $(B %s) which prints the bits as an array, and) + * $(LI $(B %b) which prints the bits as 8-bit byte packets) + * separated with an underscore. + */ + void toString(scope void delegate(const(char)[]) sink, + FormatSpec!char fmt) const + { + switch (fmt.spec) + { + case 'b': + return formatBitString(sink); + case 's': + return formatBitArray(sink); + default: + throw new Exception("Unknown format specifier: %" ~ fmt.spec); + } + } + + /// + @system unittest + { + import std.format : format; + + debug(bitarray) printf("BitArray.toString unittest\n"); + auto b = BitArray([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]); + + auto s1 = format("%s", b); + assert(s1 == "[0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]"); + + auto s2 = format("%b", b); + assert(s2 == "00001111_00001111"); + } + + /*************************************** + * Return a lazy range of the indices of set bits. + */ + @property auto bitsSet() const nothrow + { + import std.algorithm.iteration : filter, map, joiner; + import std.range : iota; + + return iota(dim). + filter!(i => _ptr[i])(). + map!(i => BitsSet!size_t(_ptr[i], i * bitsPerSizeT))(). + joiner(); + } + + /// + @system unittest + { + import std.algorithm.comparison : equal; + + auto b1 = BitArray([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]); + assert(b1.bitsSet.equal([4, 5, 6, 7, 12, 13, 14, 15])); + + BitArray b2; + b2.length = 1000; + b2[333] = true; + b2[666] = true; + b2[999] = true; + assert(b2.bitsSet.equal([333, 666, 999])); + } + + @system unittest + { + import std.algorithm.comparison : equal; + import std.range : iota; + + debug(bitarray) printf("BitArray.bitsSet unittest\n"); + BitArray b; + enum wordBits = size_t.sizeof * 8; + b = BitArray([size_t.max], 0); + assert(b.bitsSet.empty); + b = BitArray([size_t.max], 1); + assert(b.bitsSet.equal([0])); + b = BitArray([size_t.max], wordBits); + assert(b.bitsSet.equal(iota(wordBits))); + b = BitArray([size_t.max, size_t.max], wordBits); + assert(b.bitsSet.equal(iota(wordBits))); + b = BitArray([size_t.max, size_t.max], wordBits + 1); + assert(b.bitsSet.equal(iota(wordBits + 1))); + b = BitArray([size_t.max, size_t.max], wordBits * 2); + assert(b.bitsSet.equal(iota(wordBits * 2))); + } + + private void formatBitString(scope void delegate(const(char)[]) sink) const + { + if (!length) + return; + + auto leftover = _len % 8; + foreach (idx; 0 .. leftover) + { + char[1] res = cast(char)(this[idx] + '0'); + sink.put(res[]); + } + + if (leftover && _len > 8) + sink.put("_"); + + size_t count; + foreach (idx; leftover .. _len) + { + char[1] res = cast(char)(this[idx] + '0'); + sink.put(res[]); + if (++count == 8 && idx != _len - 1) + { + sink.put("_"); + count = 0; + } + } + } + + private void formatBitArray(scope void delegate(const(char)[]) sink) const + { + sink("["); + foreach (idx; 0 .. _len) + { + char[1] res = cast(char)(this[idx] + '0'); + sink(res[]); + if (idx+1 < _len) + sink(", "); + } + sink("]"); + } +} + +@system unittest +{ + import std.format : format; + + BitArray b; + + b = BitArray([]); + assert(format("%s", b) == "[]"); + assert(format("%b", b) is null); + + b = BitArray([1]); + assert(format("%s", b) == "[1]"); + assert(format("%b", b) == "1"); + + b = BitArray([0, 0, 0, 0]); + assert(format("%b", b) == "0000"); + + b = BitArray([0, 0, 0, 0, 1, 1, 1, 1]); + assert(format("%s", b) == "[0, 0, 0, 0, 1, 1, 1, 1]"); + assert(format("%b", b) == "00001111"); + + b = BitArray([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]); + assert(format("%s", b) == "[0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]"); + assert(format("%b", b) == "00001111_00001111"); + + b = BitArray([1, 0, 0, 0, 0, 1, 1, 1, 1]); + assert(format("%b", b) == "1_00001111"); + + b = BitArray([1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]); + assert(format("%b", b) == "1_00001111_00001111"); +} + +/++ + Swaps the endianness of the given integral value or character. + +/ +T swapEndian(T)(T val) @safe pure nothrow @nogc +if (isIntegral!T || isSomeChar!T || isBoolean!T) +{ + static if (val.sizeof == 1) + return val; + else static if (isUnsigned!T) + return swapEndianImpl(val); + else static if (isIntegral!T) + return cast(T) swapEndianImpl(cast(Unsigned!T) val); + else static if (is(Unqual!T == wchar)) + return cast(T) swapEndian(cast(ushort) val); + else static if (is(Unqual!T == dchar)) + return cast(T) swapEndian(cast(uint) val); + else + static assert(0, T.stringof ~ " unsupported by swapEndian."); +} + +private ushort swapEndianImpl(ushort val) @safe pure nothrow @nogc +{ + return ((val & 0xff00U) >> 8) | + ((val & 0x00ffU) << 8); +} + +private uint swapEndianImpl(uint val) @trusted pure nothrow @nogc +{ + import core.bitop : bswap; + return bswap(val); +} + +private ulong swapEndianImpl(ulong val) @trusted pure nothrow @nogc +{ + import core.bitop : bswap; + immutable ulong res = bswap(cast(uint) val); + return res << 32 | bswap(cast(uint)(val >> 32)); +} + +@safe unittest +{ + import std.meta; + foreach (T; AliasSeq!(bool, byte, ubyte, short, ushort, int, uint, long, ulong, char, wchar, dchar)) + { + scope(failure) writefln("Failed type: %s", T.stringof); + T val; + const T cval; + immutable T ival; + + assert(swapEndian(swapEndian(val)) == val); + assert(swapEndian(swapEndian(cval)) == cval); + assert(swapEndian(swapEndian(ival)) == ival); + assert(swapEndian(swapEndian(T.min)) == T.min); + assert(swapEndian(swapEndian(T.max)) == T.max); + + foreach (i; 2 .. 10) + { + immutable T maxI = cast(T)(T.max / i); + immutable T minI = cast(T)(T.min / i); + + assert(swapEndian(swapEndian(maxI)) == maxI); + + static if (isSigned!T) + assert(swapEndian(swapEndian(minI)) == minI); + } + + static if (isSigned!T) + assert(swapEndian(swapEndian(cast(T) 0)) == 0); + + // used to trigger BUG6354 + static if (T.sizeof > 1 && isUnsigned!T) + { + T left = 0xffU; + left <<= (T.sizeof - 1) * 8; + T right = 0xffU; + + for (size_t i = 1; i < T.sizeof; ++i) + { + assert(swapEndian(left) == right); + assert(swapEndian(right) == left); + left >>= 8; + right <<= 8; + } + } + } +} + + +private union EndianSwapper(T) +if (canSwapEndianness!T) +{ + Unqual!T value; + ubyte[T.sizeof] array; + + static if (is(FloatingPointTypeOf!T == float)) + uint intValue; + else static if (is(FloatingPointTypeOf!T == double)) + ulong intValue; + +} + + +/++ + Converts the given value from the native endianness to big endian and + returns it as a $(D ubyte[n]) where $(D n) is the size of the given type. + + Returning a $(D ubyte[n]) helps prevent accidentally using a swapped value + as a regular one (and in the case of floating point values, it's necessary, + because the FPU will mess up any swapped floating point values. So, you + can't actually have swapped floating point values as floating point values). + + $(D real) is not supported, because its size is implementation-dependent + and therefore could vary from machine to machine (which could make it + unusable if you tried to transfer it to another machine). + +/ +auto nativeToBigEndian(T)(T val) @safe pure nothrow @nogc +if (canSwapEndianness!T) +{ + return nativeToBigEndianImpl(val); +} + +/// +@safe unittest +{ + int i = 12345; + ubyte[4] swappedI = nativeToBigEndian(i); + assert(i == bigEndianToNative!int(swappedI)); + + double d = 123.45; + ubyte[8] swappedD = nativeToBigEndian(d); + assert(d == bigEndianToNative!double(swappedD)); +} + +private auto nativeToBigEndianImpl(T)(T val) @safe pure nothrow @nogc +if (isIntegral!T || isSomeChar!T || isBoolean!T) +{ + EndianSwapper!T es = void; + + version (LittleEndian) + es.value = swapEndian(val); + else + es.value = val; + + return es.array; +} + +private auto nativeToBigEndianImpl(T)(T val) @safe pure nothrow @nogc +if (isFloatOrDouble!T) +{ + version (LittleEndian) + return floatEndianImpl!(T, true)(val); + else + return floatEndianImpl!(T, false)(val); +} + +@safe unittest +{ + import std.meta; + foreach (T; AliasSeq!(bool, byte, ubyte, short, ushort, int, uint, long, ulong, + char, wchar, dchar + /* The trouble here is with floats and doubles being compared against nan + * using a bit compare. There are two kinds of nans, quiet and signaling. + * When a nan passes through the x87, it converts signaling to quiet. + * When a nan passes through the XMM, it does not convert signaling to quiet. + * float.init is a signaling nan. + * The binary API sometimes passes the data through the XMM, sometimes through + * the x87, meaning these will fail the 'is' bit compare under some circumstances. + * I cannot think of a fix for this that makes consistent sense. + */ + /*,float, double*/)) + { + scope(failure) writefln("Failed type: %s", T.stringof); + T val; + const T cval; + immutable T ival; + + //is instead of == because of NaN for floating point values. + assert(bigEndianToNative!T(nativeToBigEndian(val)) is val); + assert(bigEndianToNative!T(nativeToBigEndian(cval)) is cval); + assert(bigEndianToNative!T(nativeToBigEndian(ival)) is ival); + assert(bigEndianToNative!T(nativeToBigEndian(T.min)) == T.min); + assert(bigEndianToNative!T(nativeToBigEndian(T.max)) == T.max); + + static if (isSigned!T) + assert(bigEndianToNative!T(nativeToBigEndian(cast(T) 0)) == 0); + + static if (!is(T == bool)) + { + foreach (i; [2, 4, 6, 7, 9, 11]) + { + immutable T maxI = cast(T)(T.max / i); + immutable T minI = cast(T)(T.min / i); + + assert(bigEndianToNative!T(nativeToBigEndian(maxI)) == maxI); + + static if (T.sizeof > 1) + assert(nativeToBigEndian(maxI) != nativeToLittleEndian(maxI)); + else + assert(nativeToBigEndian(maxI) == nativeToLittleEndian(maxI)); + + static if (isSigned!T) + { + assert(bigEndianToNative!T(nativeToBigEndian(minI)) == minI); + + static if (T.sizeof > 1) + assert(nativeToBigEndian(minI) != nativeToLittleEndian(minI)); + else + assert(nativeToBigEndian(minI) == nativeToLittleEndian(minI)); + } + } + } + + static if (isUnsigned!T || T.sizeof == 1 || is(T == wchar)) + assert(nativeToBigEndian(T.max) == nativeToLittleEndian(T.max)); + else + assert(nativeToBigEndian(T.max) != nativeToLittleEndian(T.max)); + + static if (isUnsigned!T || T.sizeof == 1 || isSomeChar!T) + assert(nativeToBigEndian(T.min) == nativeToLittleEndian(T.min)); + else + assert(nativeToBigEndian(T.min) != nativeToLittleEndian(T.min)); + } +} + + +/++ + Converts the given value from big endian to the native endianness and + returns it. The value is given as a $(D ubyte[n]) where $(D n) is the size + of the target type. You must give the target type as a template argument, + because there are multiple types with the same size and so the type of the + argument is not enough to determine the return type. + + Taking a $(D ubyte[n]) helps prevent accidentally using a swapped value + as a regular one (and in the case of floating point values, it's necessary, + because the FPU will mess up any swapped floating point values. So, you + can't actually have swapped floating point values as floating point values). + +/ +T bigEndianToNative(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc +if (canSwapEndianness!T && n == T.sizeof) +{ + return bigEndianToNativeImpl!(T, n)(val); +} + +/// +@safe unittest +{ + ushort i = 12345; + ubyte[2] swappedI = nativeToBigEndian(i); + assert(i == bigEndianToNative!ushort(swappedI)); + + dchar c = 'D'; + ubyte[4] swappedC = nativeToBigEndian(c); + assert(c == bigEndianToNative!dchar(swappedC)); +} + +private T bigEndianToNativeImpl(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc +if ((isIntegral!T || isSomeChar!T || isBoolean!T) && + n == T.sizeof) +{ + EndianSwapper!T es = void; + es.array = val; + + version (LittleEndian) + immutable retval = swapEndian(es.value); + else + immutable retval = es.value; + + return retval; +} + +private T bigEndianToNativeImpl(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc +if (isFloatOrDouble!T && n == T.sizeof) +{ + version (LittleEndian) + return cast(T) floatEndianImpl!(n, true)(val); + else + return cast(T) floatEndianImpl!(n, false)(val); +} + + +/++ + Converts the given value from the native endianness to little endian and + returns it as a $(D ubyte[n]) where $(D n) is the size of the given type. + + Returning a $(D ubyte[n]) helps prevent accidentally using a swapped value + as a regular one (and in the case of floating point values, it's necessary, + because the FPU will mess up any swapped floating point values. So, you + can't actually have swapped floating point values as floating point values). + +/ +auto nativeToLittleEndian(T)(T val) @safe pure nothrow @nogc +if (canSwapEndianness!T) +{ + return nativeToLittleEndianImpl(val); +} + +/// +@safe unittest +{ + int i = 12345; + ubyte[4] swappedI = nativeToLittleEndian(i); + assert(i == littleEndianToNative!int(swappedI)); + + double d = 123.45; + ubyte[8] swappedD = nativeToLittleEndian(d); + assert(d == littleEndianToNative!double(swappedD)); +} + +private auto nativeToLittleEndianImpl(T)(T val) @safe pure nothrow @nogc +if (isIntegral!T || isSomeChar!T || isBoolean!T) +{ + EndianSwapper!T es = void; + + version (BigEndian) + es.value = swapEndian(val); + else + es.value = val; + + return es.array; +} + +private auto nativeToLittleEndianImpl(T)(T val) @safe pure nothrow @nogc +if (isFloatOrDouble!T) +{ + version (BigEndian) + return floatEndianImpl!(T, true)(val); + else + return floatEndianImpl!(T, false)(val); +} + +@safe unittest +{ + import std.meta; + foreach (T; AliasSeq!(bool, byte, ubyte, short, ushort, int, uint, long, ulong, + char, wchar, dchar/*, + float, double*/)) + { + scope(failure) writefln("Failed type: %s", T.stringof); + T val; + const T cval; + immutable T ival; + + //is instead of == because of NaN for floating point values. + assert(littleEndianToNative!T(nativeToLittleEndian(val)) is val); + assert(littleEndianToNative!T(nativeToLittleEndian(cval)) is cval); + assert(littleEndianToNative!T(nativeToLittleEndian(ival)) is ival); + assert(littleEndianToNative!T(nativeToLittleEndian(T.min)) == T.min); + assert(littleEndianToNative!T(nativeToLittleEndian(T.max)) == T.max); + + static if (isSigned!T) + assert(littleEndianToNative!T(nativeToLittleEndian(cast(T) 0)) == 0); + + static if (!is(T == bool)) + { + foreach (i; 2 .. 10) + { + immutable T maxI = cast(T)(T.max / i); + immutable T minI = cast(T)(T.min / i); + + assert(littleEndianToNative!T(nativeToLittleEndian(maxI)) == maxI); + + static if (isSigned!T) + assert(littleEndianToNative!T(nativeToLittleEndian(minI)) == minI); + } + } + } +} + + +/++ + Converts the given value from little endian to the native endianness and + returns it. The value is given as a $(D ubyte[n]) where $(D n) is the size + of the target type. You must give the target type as a template argument, + because there are multiple types with the same size and so the type of the + argument is not enough to determine the return type. + + Taking a $(D ubyte[n]) helps prevent accidentally using a swapped value + as a regular one (and in the case of floating point values, it's necessary, + because the FPU will mess up any swapped floating point values. So, you + can't actually have swapped floating point values as floating point values). + + $(D real) is not supported, because its size is implementation-dependent + and therefore could vary from machine to machine (which could make it + unusable if you tried to transfer it to another machine). + +/ +T littleEndianToNative(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc +if (canSwapEndianness!T && n == T.sizeof) +{ + return littleEndianToNativeImpl!T(val); +} + +/// +@safe unittest +{ + ushort i = 12345; + ubyte[2] swappedI = nativeToLittleEndian(i); + assert(i == littleEndianToNative!ushort(swappedI)); + + dchar c = 'D'; + ubyte[4] swappedC = nativeToLittleEndian(c); + assert(c == littleEndianToNative!dchar(swappedC)); +} + +private T littleEndianToNativeImpl(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc +if ((isIntegral!T || isSomeChar!T || isBoolean!T) && + n == T.sizeof) +{ + EndianSwapper!T es = void; + es.array = val; + + version (BigEndian) + immutable retval = swapEndian(es.value); + else + immutable retval = es.value; + + return retval; +} + +private T littleEndianToNativeImpl(T, size_t n)(ubyte[n] val) @safe pure nothrow @nogc +if (((isFloatOrDouble!T) && + n == T.sizeof)) +{ + version (BigEndian) + return floatEndianImpl!(n, true)(val); + else + return floatEndianImpl!(n, false)(val); +} + +private auto floatEndianImpl(T, bool swap)(T val) @safe pure nothrow @nogc +if (isFloatOrDouble!T) +{ + EndianSwapper!T es = void; + es.value = val; + + static if (swap) + es.intValue = swapEndian(es.intValue); + + return es.array; +} + +private auto floatEndianImpl(size_t n, bool swap)(ubyte[n] val) @safe pure nothrow @nogc +if (n == 4 || n == 8) +{ + static if (n == 4) EndianSwapper!float es = void; + else static if (n == 8) EndianSwapper!double es = void; + + es.array = val; + + static if (swap) + es.intValue = swapEndian(es.intValue); + + return es.value; +} + +private template isFloatOrDouble(T) +{ + enum isFloatOrDouble = isFloatingPoint!T && + !is(Unqual!(FloatingPointTypeOf!T) == real); +} + +@safe unittest +{ + import std.meta; + foreach (T; AliasSeq!(float, double)) + { + static assert(isFloatOrDouble!(T)); + static assert(isFloatOrDouble!(const T)); + static assert(isFloatOrDouble!(immutable T)); + static assert(isFloatOrDouble!(shared T)); + static assert(isFloatOrDouble!(shared(const T))); + static assert(isFloatOrDouble!(shared(immutable T))); + } + + static assert(!isFloatOrDouble!(real)); + static assert(!isFloatOrDouble!(const real)); + static assert(!isFloatOrDouble!(immutable real)); + static assert(!isFloatOrDouble!(shared real)); + static assert(!isFloatOrDouble!(shared(const real))); + static assert(!isFloatOrDouble!(shared(immutable real))); +} + +private template canSwapEndianness(T) +{ + enum canSwapEndianness = isIntegral!T || + isSomeChar!T || + isBoolean!T || + isFloatOrDouble!T; +} + +@safe unittest +{ + import std.meta; + foreach (T; AliasSeq!(bool, ubyte, byte, ushort, short, uint, int, ulong, + long, char, wchar, dchar, float, double)) + { + static assert(canSwapEndianness!(T)); + static assert(canSwapEndianness!(const T)); + static assert(canSwapEndianness!(immutable T)); + static assert(canSwapEndianness!(shared(T))); + static assert(canSwapEndianness!(shared(const T))); + static assert(canSwapEndianness!(shared(immutable T))); + } + + //! + foreach (T; AliasSeq!(real, string, wstring, dstring)) + { + static assert(!canSwapEndianness!(T)); + static assert(!canSwapEndianness!(const T)); + static assert(!canSwapEndianness!(immutable T)); + static assert(!canSwapEndianness!(shared(T))); + static assert(!canSwapEndianness!(shared(const T))); + static assert(!canSwapEndianness!(shared(immutable T))); + } +} + +/++ + Takes a range of $(D ubyte)s and converts the first $(D T.sizeof) bytes to + $(D T). The value returned is converted from the given endianness to the + native endianness. The range is not consumed. + + Params: + T = The integral type to convert the first $(D T.sizeof) bytes to. + endianness = The endianness that the bytes are assumed to be in. + range = The range to read from. + index = The index to start reading from (instead of starting at the + front). If index is a pointer, then it is updated to the index + after the bytes read. The overloads with index are only + available if $(D hasSlicing!R) is $(D true). + +/ + +T peek(T, Endian endianness = Endian.bigEndian, R)(R range) +if (canSwapEndianness!T && + isForwardRange!R && + is(ElementType!R : const ubyte)) +{ + static if (hasSlicing!R) + const ubyte[T.sizeof] bytes = range[0 .. T.sizeof]; + else + { + ubyte[T.sizeof] bytes; + //Make sure that range is not consumed, even if it's a class. + range = range.save; + + foreach (ref e; bytes) + { + e = range.front; + range.popFront(); + } + } + + static if (endianness == Endian.bigEndian) + return bigEndianToNative!T(bytes); + else + return littleEndianToNative!T(bytes); +} + +/++ Ditto +/ +T peek(T, Endian endianness = Endian.bigEndian, R)(R range, size_t index) +if (canSwapEndianness!T && + isForwardRange!R && + hasSlicing!R && + is(ElementType!R : const ubyte)) +{ + return peek!(T, endianness)(range, &index); +} + +/++ Ditto +/ +T peek(T, Endian endianness = Endian.bigEndian, R)(R range, size_t* index) +if (canSwapEndianness!T && + isForwardRange!R && + hasSlicing!R && + is(ElementType!R : const ubyte)) +{ + assert(index); + + immutable begin = *index; + immutable end = begin + T.sizeof; + const ubyte[T.sizeof] bytes = range[begin .. end]; + *index = end; + + static if (endianness == Endian.bigEndian) + return bigEndianToNative!T(bytes); + else + return littleEndianToNative!T(bytes); +} + +/// +@system unittest +{ + ubyte[] buffer = [1, 5, 22, 9, 44, 255, 8]; + assert(buffer.peek!uint() == 17110537); + assert(buffer.peek!ushort() == 261); + assert(buffer.peek!ubyte() == 1); + + assert(buffer.peek!uint(2) == 369700095); + assert(buffer.peek!ushort(2) == 5641); + assert(buffer.peek!ubyte(2) == 22); + + size_t index = 0; + assert(buffer.peek!ushort(&index) == 261); + assert(index == 2); + + assert(buffer.peek!uint(&index) == 369700095); + assert(index == 6); + + assert(buffer.peek!ubyte(&index) == 8); + assert(index == 7); +} + +@system unittest +{ + { + //bool + ubyte[] buffer = [0, 1]; + assert(buffer.peek!bool() == false); + assert(buffer.peek!bool(1) == true); + + size_t index = 0; + assert(buffer.peek!bool(&index) == false); + assert(index == 1); + + assert(buffer.peek!bool(&index) == true); + assert(index == 2); + } + + { + //char (8bit) + ubyte[] buffer = [97, 98, 99, 100]; + assert(buffer.peek!char() == 'a'); + assert(buffer.peek!char(1) == 'b'); + + size_t index = 0; + assert(buffer.peek!char(&index) == 'a'); + assert(index == 1); + + assert(buffer.peek!char(&index) == 'b'); + assert(index == 2); + } + + { + //wchar (16bit - 2x ubyte) + ubyte[] buffer = [1, 5, 32, 29, 1, 7]; + assert(buffer.peek!wchar() == 'ą'); + assert(buffer.peek!wchar(2) == '”'); + assert(buffer.peek!wchar(4) == 'ć'); + + size_t index = 0; + assert(buffer.peek!wchar(&index) == 'ą'); + assert(index == 2); + + assert(buffer.peek!wchar(&index) == '”'); + assert(index == 4); + + assert(buffer.peek!wchar(&index) == 'ć'); + assert(index == 6); + } + + { + //dchar (32bit - 4x ubyte) + ubyte[] buffer = [0, 0, 1, 5, 0, 0, 32, 29, 0, 0, 1, 7]; + assert(buffer.peek!dchar() == 'ą'); + assert(buffer.peek!dchar(4) == '”'); + assert(buffer.peek!dchar(8) == 'ć'); + + size_t index = 0; + assert(buffer.peek!dchar(&index) == 'ą'); + assert(index == 4); + + assert(buffer.peek!dchar(&index) == '”'); + assert(index == 8); + + assert(buffer.peek!dchar(&index) == 'ć'); + assert(index == 12); + } + + { + //float (32bit - 4x ubyte) + ubyte[] buffer = [66, 0, 0, 0, 65, 200, 0, 0]; + assert(buffer.peek!float()== 32.0); + assert(buffer.peek!float(4) == 25.0f); + + size_t index = 0; + assert(buffer.peek!float(&index) == 32.0f); + assert(index == 4); + + assert(buffer.peek!float(&index) == 25.0f); + assert(index == 8); + } + + { + //double (64bit - 8x ubyte) + ubyte[] buffer = [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0]; + assert(buffer.peek!double() == 32.0); + assert(buffer.peek!double(8) == 25.0); + + size_t index = 0; + assert(buffer.peek!double(&index) == 32.0); + assert(index == 8); + + assert(buffer.peek!double(&index) == 25.0); + assert(index == 16); + } + + { + //enum + ubyte[] buffer = [0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 30]; + + enum Foo + { + one = 10, + two = 20, + three = 30 + } + + assert(buffer.peek!Foo() == Foo.one); + assert(buffer.peek!Foo(0) == Foo.one); + assert(buffer.peek!Foo(4) == Foo.two); + assert(buffer.peek!Foo(8) == Foo.three); + + size_t index = 0; + assert(buffer.peek!Foo(&index) == Foo.one); + assert(index == 4); + + assert(buffer.peek!Foo(&index) == Foo.two); + assert(index == 8); + + assert(buffer.peek!Foo(&index) == Foo.three); + assert(index == 12); + } + + { + //enum - bool + ubyte[] buffer = [0, 1]; + + enum Bool: bool + { + bfalse = false, + btrue = true, + } + + assert(buffer.peek!Bool() == Bool.bfalse); + assert(buffer.peek!Bool(0) == Bool.bfalse); + assert(buffer.peek!Bool(1) == Bool.btrue); + + size_t index = 0; + assert(buffer.peek!Bool(&index) == Bool.bfalse); + assert(index == 1); + + assert(buffer.peek!Bool(&index) == Bool.btrue); + assert(index == 2); + } + + { + //enum - float + ubyte[] buffer = [66, 0, 0, 0, 65, 200, 0, 0]; + + enum Float: float + { + one = 32.0f, + two = 25.0f + } + + assert(buffer.peek!Float() == Float.one); + assert(buffer.peek!Float(0) == Float.one); + assert(buffer.peek!Float(4) == Float.two); + + size_t index = 0; + assert(buffer.peek!Float(&index) == Float.one); + assert(index == 4); + + assert(buffer.peek!Float(&index) == Float.two); + assert(index == 8); + } + + { + //enum - double + ubyte[] buffer = [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0]; + + enum Double: double + { + one = 32.0, + two = 25.0 + } + + assert(buffer.peek!Double() == Double.one); + assert(buffer.peek!Double(0) == Double.one); + assert(buffer.peek!Double(8) == Double.two); + + size_t index = 0; + assert(buffer.peek!Double(&index) == Double.one); + assert(index == 8); + + assert(buffer.peek!Double(&index) == Double.two); + assert(index == 16); + } + + { + //enum - real + ubyte[] buffer = [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0]; + + enum Real: real + { + one = 32.0, + two = 25.0 + } + + static assert(!__traits(compiles, buffer.peek!Real())); + } +} + +@safe unittest +{ + import std.algorithm.iteration : filter; + ubyte[] buffer = [1, 5, 22, 9, 44, 255, 7]; + auto range = filter!"true"(buffer); + assert(range.peek!uint() == 17110537); + assert(range.peek!ushort() == 261); + assert(range.peek!ubyte() == 1); +} + + +/++ + Takes a range of $(D ubyte)s and converts the first $(D T.sizeof) bytes to + $(D T). The value returned is converted from the given endianness to the + native endianness. The $(D T.sizeof) bytes which are read are consumed from + the range. + + Params: + T = The integral type to convert the first $(D T.sizeof) bytes to. + endianness = The endianness that the bytes are assumed to be in. + range = The range to read from. + +/ +T read(T, Endian endianness = Endian.bigEndian, R)(ref R range) +if (canSwapEndianness!T && isInputRange!R && is(ElementType!R : const ubyte)) +{ + static if (hasSlicing!R && is(typeof(R.init[0 .. 0]) : const(ubyte)[])) + { + const ubyte[T.sizeof] bytes = range[0 .. T.sizeof]; + range.popFrontN(T.sizeof); + } + else + { + ubyte[T.sizeof] bytes; + + foreach (ref e; bytes) + { + e = range.front; + range.popFront(); + } + } + + static if (endianness == Endian.bigEndian) + return bigEndianToNative!T(bytes); + else + return littleEndianToNative!T(bytes); +} + +/// +@safe unittest +{ + import std.range.primitives : empty; + ubyte[] buffer = [1, 5, 22, 9, 44, 255, 8]; + assert(buffer.length == 7); + + assert(buffer.read!ushort() == 261); + assert(buffer.length == 5); + + assert(buffer.read!uint() == 369700095); + assert(buffer.length == 1); + + assert(buffer.read!ubyte() == 8); + assert(buffer.empty); +} + +@safe unittest +{ + { + //bool + ubyte[] buffer = [0, 1]; + assert(buffer.length == 2); + + assert(buffer.read!bool() == false); + assert(buffer.length == 1); + + assert(buffer.read!bool() == true); + assert(buffer.empty); + } + + { + //char (8bit) + ubyte[] buffer = [97, 98, 99]; + assert(buffer.length == 3); + + assert(buffer.read!char() == 'a'); + assert(buffer.length == 2); + + assert(buffer.read!char() == 'b'); + assert(buffer.length == 1); + + assert(buffer.read!char() == 'c'); + assert(buffer.empty); + } + + { + //wchar (16bit - 2x ubyte) + ubyte[] buffer = [1, 5, 32, 29, 1, 7]; + assert(buffer.length == 6); + + assert(buffer.read!wchar() == 'ą'); + assert(buffer.length == 4); + + assert(buffer.read!wchar() == '”'); + assert(buffer.length == 2); + + assert(buffer.read!wchar() == 'ć'); + assert(buffer.empty); + } + + { + //dchar (32bit - 4x ubyte) + ubyte[] buffer = [0, 0, 1, 5, 0, 0, 32, 29, 0, 0, 1, 7]; + assert(buffer.length == 12); + + assert(buffer.read!dchar() == 'ą'); + assert(buffer.length == 8); + + assert(buffer.read!dchar() == '”'); + assert(buffer.length == 4); + + assert(buffer.read!dchar() == 'ć'); + assert(buffer.empty); + } + + { + //float (32bit - 4x ubyte) + ubyte[] buffer = [66, 0, 0, 0, 65, 200, 0, 0]; + assert(buffer.length == 8); + + assert(buffer.read!float()== 32.0); + assert(buffer.length == 4); + + assert(buffer.read!float() == 25.0f); + assert(buffer.empty); + } + + { + //double (64bit - 8x ubyte) + ubyte[] buffer = [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0]; + assert(buffer.length == 16); + + assert(buffer.read!double() == 32.0); + assert(buffer.length == 8); + + assert(buffer.read!double() == 25.0); + assert(buffer.empty); + } + + { + //enum - uint + ubyte[] buffer = [0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 30]; + assert(buffer.length == 12); + + enum Foo + { + one = 10, + two = 20, + three = 30 + } + + assert(buffer.read!Foo() == Foo.one); + assert(buffer.length == 8); + + assert(buffer.read!Foo() == Foo.two); + assert(buffer.length == 4); + + assert(buffer.read!Foo() == Foo.three); + assert(buffer.empty); + } + + { + //enum - bool + ubyte[] buffer = [0, 1]; + assert(buffer.length == 2); + + enum Bool: bool + { + bfalse = false, + btrue = true, + } + + assert(buffer.read!Bool() == Bool.bfalse); + assert(buffer.length == 1); + + assert(buffer.read!Bool() == Bool.btrue); + assert(buffer.empty); + } + + { + //enum - float + ubyte[] buffer = [66, 0, 0, 0, 65, 200, 0, 0]; + assert(buffer.length == 8); + + enum Float: float + { + one = 32.0f, + two = 25.0f + } + + assert(buffer.read!Float() == Float.one); + assert(buffer.length == 4); + + assert(buffer.read!Float() == Float.two); + assert(buffer.empty); + } + + { + //enum - double + ubyte[] buffer = [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0]; + assert(buffer.length == 16); + + enum Double: double + { + one = 32.0, + two = 25.0 + } + + assert(buffer.read!Double() == Double.one); + assert(buffer.length == 8); + + assert(buffer.read!Double() == Double.two); + assert(buffer.empty); + } + + { + //enum - real + ubyte[] buffer = [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0]; + + enum Real: real + { + one = 32.0, + two = 25.0 + } + + static assert(!__traits(compiles, buffer.read!Real())); + } +} + +@safe unittest +{ + import std.algorithm.iteration : filter; + ubyte[] buffer = [1, 5, 22, 9, 44, 255, 8]; + auto range = filter!"true"(buffer); + assert(walkLength(range) == 7); + + assert(range.read!ushort() == 261); + assert(walkLength(range) == 5); + + assert(range.read!uint() == 369700095); + assert(walkLength(range) == 1); + + assert(range.read!ubyte() == 8); + assert(range.empty); +} + +// issue 17247 +@safe unittest +{ + struct UbyteRange + { + ubyte[] impl; + @property bool empty() { return impl.empty; } + @property ubyte front() { return impl.front; } + void popFront() { impl.popFront(); } + @property UbyteRange save() { return this; } + + // N.B. support slicing but do not return ubyte[] slices. + UbyteRange opSlice(size_t start, size_t end) + { + return UbyteRange(impl[start .. end]); + } + @property size_t length() { return impl.length; } + size_t opDollar() { return impl.length; } + } + static assert(hasSlicing!UbyteRange); + + auto r = UbyteRange([0x01, 0x00, 0x00, 0x00]); + int x = r.read!(int, Endian.littleEndian)(); + assert(x == 1); +} + + +/++ + Takes an integral value, converts it to the given endianness, and writes it + to the given range of $(D ubyte)s as a sequence of $(D T.sizeof) $(D ubyte)s + starting at index. $(D hasSlicing!R) must be $(D true). + + Params: + T = The integral type to convert the first $(D T.sizeof) bytes to. + endianness = The endianness to _write the bytes in. + range = The range to _write to. + value = The value to _write. + index = The index to start writing to. If index is a pointer, then it + is updated to the index after the bytes read. + +/ +void write(T, Endian endianness = Endian.bigEndian, R)(R range, T value, size_t index) +if (canSwapEndianness!T && + isForwardRange!R && + hasSlicing!R && + is(ElementType!R : ubyte)) +{ + write!(T, endianness)(range, value, &index); +} + +/++ Ditto +/ +void write(T, Endian endianness = Endian.bigEndian, R)(R range, T value, size_t* index) +if (canSwapEndianness!T && + isForwardRange!R && + hasSlicing!R && + is(ElementType!R : ubyte)) +{ + assert(index); + + static if (endianness == Endian.bigEndian) + immutable bytes = nativeToBigEndian!T(value); + else + immutable bytes = nativeToLittleEndian!T(value); + + immutable begin = *index; + immutable end = begin + T.sizeof; + *index = end; + range[begin .. end] = bytes[0 .. T.sizeof]; +} + +/// +@system unittest +{ + { + ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0]; + buffer.write!uint(29110231u, 0); + assert(buffer == [1, 188, 47, 215, 0, 0, 0, 0]); + + buffer.write!ushort(927, 0); + assert(buffer == [3, 159, 47, 215, 0, 0, 0, 0]); + + buffer.write!ubyte(42, 0); + assert(buffer == [42, 159, 47, 215, 0, 0, 0, 0]); + } + + { + ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0]; + buffer.write!uint(142700095u, 2); + assert(buffer == [0, 0, 8, 129, 110, 63, 0, 0, 0]); + + buffer.write!ushort(19839, 2); + assert(buffer == [0, 0, 77, 127, 110, 63, 0, 0, 0]); + + buffer.write!ubyte(132, 2); + assert(buffer == [0, 0, 132, 127, 110, 63, 0, 0, 0]); + } + + { + ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0]; + size_t index = 0; + buffer.write!ushort(261, &index); + assert(buffer == [1, 5, 0, 0, 0, 0, 0, 0]); + assert(index == 2); + + buffer.write!uint(369700095u, &index); + assert(buffer == [1, 5, 22, 9, 44, 255, 0, 0]); + assert(index == 6); + + buffer.write!ubyte(8, &index); + assert(buffer == [1, 5, 22, 9, 44, 255, 8, 0]); + assert(index == 7); + } +} + +@system unittest +{ + { + //bool + ubyte[] buffer = [0, 0]; + + buffer.write!bool(false, 0); + assert(buffer == [0, 0]); + + buffer.write!bool(true, 0); + assert(buffer == [1, 0]); + + buffer.write!bool(true, 1); + assert(buffer == [1, 1]); + + buffer.write!bool(false, 1); + assert(buffer == [1, 0]); + + size_t index = 0; + buffer.write!bool(false, &index); + assert(buffer == [0, 0]); + assert(index == 1); + + buffer.write!bool(true, &index); + assert(buffer == [0, 1]); + assert(index == 2); + } + + { + //char (8bit) + ubyte[] buffer = [0, 0, 0]; + + buffer.write!char('a', 0); + assert(buffer == [97, 0, 0]); + + buffer.write!char('b', 1); + assert(buffer == [97, 98, 0]); + + size_t index = 0; + buffer.write!char('a', &index); + assert(buffer == [97, 98, 0]); + assert(index == 1); + + buffer.write!char('b', &index); + assert(buffer == [97, 98, 0]); + assert(index == 2); + + buffer.write!char('c', &index); + assert(buffer == [97, 98, 99]); + assert(index == 3); + } + + { + //wchar (16bit - 2x ubyte) + ubyte[] buffer = [0, 0, 0, 0]; + + buffer.write!wchar('ą', 0); + assert(buffer == [1, 5, 0, 0]); + + buffer.write!wchar('”', 2); + assert(buffer == [1, 5, 32, 29]); + + size_t index = 0; + buffer.write!wchar('ć', &index); + assert(buffer == [1, 7, 32, 29]); + assert(index == 2); + + buffer.write!wchar('ą', &index); + assert(buffer == [1, 7, 1, 5]); + assert(index == 4); + } + + { + //dchar (32bit - 4x ubyte) + ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0]; + + buffer.write!dchar('ą', 0); + assert(buffer == [0, 0, 1, 5, 0, 0, 0, 0]); + + buffer.write!dchar('”', 4); + assert(buffer == [0, 0, 1, 5, 0, 0, 32, 29]); + + size_t index = 0; + buffer.write!dchar('ć', &index); + assert(buffer == [0, 0, 1, 7, 0, 0, 32, 29]); + assert(index == 4); + + buffer.write!dchar('ą', &index); + assert(buffer == [0, 0, 1, 7, 0, 0, 1, 5]); + assert(index == 8); + } + + { + //float (32bit - 4x ubyte) + ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0]; + + buffer.write!float(32.0f, 0); + assert(buffer == [66, 0, 0, 0, 0, 0, 0, 0]); + + buffer.write!float(25.0f, 4); + assert(buffer == [66, 0, 0, 0, 65, 200, 0, 0]); + + size_t index = 0; + buffer.write!float(25.0f, &index); + assert(buffer == [65, 200, 0, 0, 65, 200, 0, 0]); + assert(index == 4); + + buffer.write!float(32.0f, &index); + assert(buffer == [65, 200, 0, 0, 66, 0, 0, 0]); + assert(index == 8); + } + + { + //double (64bit - 8x ubyte) + ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + buffer.write!double(32.0, 0); + assert(buffer == [64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + + buffer.write!double(25.0, 8); + assert(buffer == [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0]); + + size_t index = 0; + buffer.write!double(25.0, &index); + assert(buffer == [64, 57, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0]); + assert(index == 8); + + buffer.write!double(32.0, &index); + assert(buffer == [64, 57, 0, 0, 0, 0, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0]); + assert(index == 16); + } + + { + //enum + ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + enum Foo + { + one = 10, + two = 20, + three = 30 + } + + buffer.write!Foo(Foo.one, 0); + assert(buffer == [0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0]); + + buffer.write!Foo(Foo.two, 4); + assert(buffer == [0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 0]); + + buffer.write!Foo(Foo.three, 8); + assert(buffer == [0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 30]); + + size_t index = 0; + buffer.write!Foo(Foo.three, &index); + assert(buffer == [0, 0, 0, 30, 0, 0, 0, 20, 0, 0, 0, 30]); + assert(index == 4); + + buffer.write!Foo(Foo.one, &index); + assert(buffer == [0, 0, 0, 30, 0, 0, 0, 10, 0, 0, 0, 30]); + assert(index == 8); + + buffer.write!Foo(Foo.two, &index); + assert(buffer == [0, 0, 0, 30, 0, 0, 0, 10, 0, 0, 0, 20]); + assert(index == 12); + } + + { + //enum - bool + ubyte[] buffer = [0, 0]; + + enum Bool: bool + { + bfalse = false, + btrue = true, + } + + buffer.write!Bool(Bool.btrue, 0); + assert(buffer == [1, 0]); + + buffer.write!Bool(Bool.btrue, 1); + assert(buffer == [1, 1]); + + size_t index = 0; + buffer.write!Bool(Bool.bfalse, &index); + assert(buffer == [0, 1]); + assert(index == 1); + + buffer.write!Bool(Bool.bfalse, &index); + assert(buffer == [0, 0]); + assert(index == 2); + } + + { + //enum - float + ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0]; + + enum Float: float + { + one = 32.0f, + two = 25.0f + } + + buffer.write!Float(Float.one, 0); + assert(buffer == [66, 0, 0, 0, 0, 0, 0, 0]); + + buffer.write!Float(Float.two, 4); + assert(buffer == [66, 0, 0, 0, 65, 200, 0, 0]); + + size_t index = 0; + buffer.write!Float(Float.two, &index); + assert(buffer == [65, 200, 0, 0, 65, 200, 0, 0]); + assert(index == 4); + + buffer.write!Float(Float.one, &index); + assert(buffer == [65, 200, 0, 0, 66, 0, 0, 0]); + assert(index == 8); + } + + { + //enum - double + ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + enum Double: double + { + one = 32.0, + two = 25.0 + } + + buffer.write!Double(Double.one, 0); + assert(buffer == [64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + + buffer.write!Double(Double.two, 8); + assert(buffer == [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0]); + + size_t index = 0; + buffer.write!Double(Double.two, &index); + assert(buffer == [64, 57, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0]); + assert(index == 8); + + buffer.write!Double(Double.one, &index); + assert(buffer == [64, 57, 0, 0, 0, 0, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0]); + assert(index == 16); + } + + { + //enum - real + ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + enum Real: real + { + one = 32.0, + two = 25.0 + } + + static assert(!__traits(compiles, buffer.write!Real(Real.one))); + } +} + + +/++ + Takes an integral value, converts it to the given endianness, and appends + it to the given range of $(D ubyte)s (using $(D put)) as a sequence of + $(D T.sizeof) $(D ubyte)s starting at index. $(D hasSlicing!R) must be + $(D true). + + Params: + T = The integral type to convert the first $(D T.sizeof) bytes to. + endianness = The endianness to write the bytes in. + range = The range to _append to. + value = The value to _append. + +/ +void append(T, Endian endianness = Endian.bigEndian, R)(R range, T value) +if (canSwapEndianness!T && isOutputRange!(R, ubyte)) +{ + static if (endianness == Endian.bigEndian) + immutable bytes = nativeToBigEndian!T(value); + else + immutable bytes = nativeToLittleEndian!T(value); + + put(range, bytes[]); +} + +/// +@safe unittest +{ + import std.array; + auto buffer = appender!(const ubyte[])(); + buffer.append!ushort(261); + assert(buffer.data == [1, 5]); + + buffer.append!uint(369700095u); + assert(buffer.data == [1, 5, 22, 9, 44, 255]); + + buffer.append!ubyte(8); + assert(buffer.data == [1, 5, 22, 9, 44, 255, 8]); +} + +@safe unittest +{ + import std.array; + { + //bool + auto buffer = appender!(const ubyte[])(); + + buffer.append!bool(true); + assert(buffer.data == [1]); + + buffer.append!bool(false); + assert(buffer.data == [1, 0]); + } + + { + //char wchar dchar + auto buffer = appender!(const ubyte[])(); + + buffer.append!char('a'); + assert(buffer.data == [97]); + + buffer.append!char('b'); + assert(buffer.data == [97, 98]); + + buffer.append!wchar('ą'); + assert(buffer.data == [97, 98, 1, 5]); + + buffer.append!dchar('ą'); + assert(buffer.data == [97, 98, 1, 5, 0, 0, 1, 5]); + } + + { + //float double + auto buffer = appender!(const ubyte[])(); + + buffer.append!float(32.0f); + assert(buffer.data == [66, 0, 0, 0]); + + buffer.append!double(32.0); + assert(buffer.data == [66, 0, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0]); + } + + { + //enum + auto buffer = appender!(const ubyte[])(); + + enum Foo + { + one = 10, + two = 20, + three = 30 + } + + buffer.append!Foo(Foo.one); + assert(buffer.data == [0, 0, 0, 10]); + + buffer.append!Foo(Foo.two); + assert(buffer.data == [0, 0, 0, 10, 0, 0, 0, 20]); + + buffer.append!Foo(Foo.three); + assert(buffer.data == [0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 30]); + } + + { + //enum - bool + auto buffer = appender!(const ubyte[])(); + + enum Bool: bool + { + bfalse = false, + btrue = true, + } + + buffer.append!Bool(Bool.btrue); + assert(buffer.data == [1]); + + buffer.append!Bool(Bool.bfalse); + assert(buffer.data == [1, 0]); + + buffer.append!Bool(Bool.btrue); + assert(buffer.data == [1, 0, 1]); + } + + { + //enum - float + auto buffer = appender!(const ubyte[])(); + + enum Float: float + { + one = 32.0f, + two = 25.0f + } + + buffer.append!Float(Float.one); + assert(buffer.data == [66, 0, 0, 0]); + + buffer.append!Float(Float.two); + assert(buffer.data == [66, 0, 0, 0, 65, 200, 0, 0]); + } + + { + //enum - double + auto buffer = appender!(const ubyte[])(); + + enum Double: double + { + one = 32.0, + two = 25.0 + } + + buffer.append!Double(Double.one); + assert(buffer.data == [64, 64, 0, 0, 0, 0, 0, 0]); + + buffer.append!Double(Double.two); + assert(buffer.data == [64, 64, 0, 0, 0, 0, 0, 0, 64, 57, 0, 0, 0, 0, 0, 0]); + } + + { + //enum - real + auto buffer = appender!(const ubyte[])(); + + enum Real: real + { + one = 32.0, + two = 25.0 + } + + static assert(!__traits(compiles, buffer.append!Real(Real.one))); + } +} + +@system unittest +{ + import std.array; + import std.format : format; + import std.meta; + foreach (endianness; AliasSeq!(Endian.bigEndian, Endian.littleEndian)) + { + auto toWrite = appender!(ubyte[])(); + alias Types = AliasSeq!(uint, int, long, ulong, short, ubyte, ushort, byte, uint); + ulong[] values = [42, -11, long.max, 1098911981329L, 16, 255, 19012, 2, 17]; + assert(Types.length == values.length); + + size_t index = 0; + size_t length = 0; + foreach (T; Types) + { + toWrite.append!(T, endianness)(cast(T) values[index++]); + length += T.sizeof; + } + + auto toRead = toWrite.data; + assert(toRead.length == length); + + index = 0; + foreach (T; Types) + { + assert(toRead.peek!(T, endianness)() == values[index], format("Failed Index: %s", index)); + assert(toRead.peek!(T, endianness)(0) == values[index], format("Failed Index: %s", index)); + assert(toRead.length == length, + format("Failed Index [%s], Actual Length: %s", index, toRead.length)); + assert(toRead.read!(T, endianness)() == values[index], format("Failed Index: %s", index)); + length -= T.sizeof; + assert(toRead.length == length, + format("Failed Index [%s], Actual Length: %s", index, toRead.length)); + ++index; + } + assert(toRead.empty); + } +} + +/** +Counts the number of set bits in the binary representation of $(D value). +For signed integers, the sign bit is included in the count. +*/ +private uint countBitsSet(T)(T value) @nogc pure nothrow +if (isIntegral!T) +{ + // http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel + static if (T.sizeof == 8) + { + T c = value - ((value >> 1) & 0x55555555_55555555); + c = ((c >> 2) & 0x33333333_33333333) + (c & 0x33333333_33333333); + c = ((c >> 4) + c) & 0x0F0F0F0F_0F0F0F0F; + c = ((c >> 8) + c) & 0x00FF00FF_00FF00FF; + c = ((c >> 16) + c) & 0x0000FFFF_0000FFFF; + c = ((c >> 32) + c) & 0x00000000_FFFFFFFF; + } + else static if (T.sizeof == 4) + { + T c = value - ((value >> 1) & 0x55555555); + c = ((c >> 2) & 0x33333333) + (c & 0x33333333); + c = ((c >> 4) + c) & 0x0F0F0F0F; + c = ((c >> 8) + c) & 0x00FF00FF; + c = ((c >> 16) + c) & 0x0000FFFF; + } + else static if (T.sizeof == 2) + { + uint c = value - ((value >> 1) & 0x5555); + c = ((c >> 2) & 0x3333) + (c & 0X3333); + c = ((c >> 4) + c) & 0x0F0F; + c = ((c >> 8) + c) & 0x00FF; + } + else static if (T.sizeof == 1) + { + uint c = value - ((value >> 1) & 0x55); + c = ((c >> 2) & 0x33) + (c & 0X33); + c = ((c >> 4) + c) & 0x0F; + } + else + { + static assert(false, "countBitsSet only supports 1, 2, 4, or 8 byte sized integers."); + } + return cast(uint) c; +} + +@safe unittest +{ + assert(countBitsSet(1) == 1); + assert(countBitsSet(0) == 0); + assert(countBitsSet(int.min) == 1); + assert(countBitsSet(uint.max) == 32); +} + +@safe unittest +{ + import std.meta; + foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong)) + { + assert(countBitsSet(cast(T) 0) == 0); + assert(countBitsSet(cast(T) 1) == 1); + assert(countBitsSet(cast(T) 2) == 1); + assert(countBitsSet(cast(T) 3) == 2); + assert(countBitsSet(cast(T) 4) == 1); + assert(countBitsSet(cast(T) 5) == 2); + assert(countBitsSet(cast(T) 127) == 7); + static if (isSigned!T) + { + assert(countBitsSet(cast(T)-1) == 8 * T.sizeof); + assert(countBitsSet(T.min) == 1); + } + else + { + assert(countBitsSet(T.max) == 8 * T.sizeof); + } + } + assert(countBitsSet(1_000_000) == 7); + foreach (i; 0 .. 63) + assert(countBitsSet(1UL << i) == 1); +} + +private struct BitsSet(T) +{ + static assert(T.sizeof <= 8, "bitsSet assumes T is no more than 64-bit."); + +@nogc pure nothrow: + + this(T value, size_t startIndex = 0) + { + _value = value; + // Further calculation is only valid and needed when the range is non-empty. + if (!_value) + return; + + import core.bitop : bsf; + immutable trailingZerosCount = bsf(value); + _value >>>= trailingZerosCount; + _index = startIndex + trailingZerosCount; + } + + @property size_t front() + { + return _index; + } + + @property bool empty() const + { + return !_value; + } + + void popFront() + { + assert(_value, "Cannot call popFront on empty range."); + + _value >>>= 1; + // Further calculation is only valid and needed when the range is non-empty. + if (!_value) + return; + + import core.bitop : bsf; + immutable trailingZerosCount = bsf(_value); + _value >>>= trailingZerosCount; + _index += trailingZerosCount + 1; + } + + @property auto save() + { + return this; + } + + @property size_t length() + { + return countBitsSet(_value); + } + + private T _value; + private size_t _index; +} + +/** +Range that iterates the indices of the set bits in $(D value). +Index 0 corresponds to the least significant bit. +For signed integers, the highest index corresponds to the sign bit. +*/ +auto bitsSet(T)(T value) @nogc pure nothrow +if (isIntegral!T) +{ + return BitsSet!T(value); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : iota; + + assert(bitsSet(1).equal([0])); + assert(bitsSet(5).equal([0, 2])); + assert(bitsSet(-1).equal(iota(32))); + assert(bitsSet(int.min).equal([31])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : iota; + + import std.meta; + foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong)) + { + assert(bitsSet(cast(T) 0).empty); + assert(bitsSet(cast(T) 1).equal([0])); + assert(bitsSet(cast(T) 2).equal([1])); + assert(bitsSet(cast(T) 3).equal([0, 1])); + assert(bitsSet(cast(T) 4).equal([2])); + assert(bitsSet(cast(T) 5).equal([0, 2])); + assert(bitsSet(cast(T) 127).equal(iota(7))); + static if (isSigned!T) + { + assert(bitsSet(cast(T)-1).equal(iota(8 * T.sizeof))); + assert(bitsSet(T.min).equal([8 * T.sizeof - 1])); + } + else + { + assert(bitsSet(T.max).equal(iota(8 * T.sizeof))); + } + } + assert(bitsSet(1_000_000).equal([6, 9, 14, 16, 17, 18, 19])); + foreach (i; 0 .. 63) + assert(bitsSet(1UL << i).equal([i])); +} diff --git a/libphobos/src/std/compiler.d b/libphobos/src/std/compiler.d new file mode 100644 index 0000000..cb038f9 --- /dev/null +++ b/libphobos/src/std/compiler.d @@ -0,0 +1,58 @@ +// Written in the D programming language. + +/** + * Identify the compiler used and its various features. + * + * Copyright: Copyright Digital Mars 2000 - 2011. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: $(HTTP digitalmars.com, Walter Bright), Alex Rønne Petersen + * Source: $(PHOBOSSRC std/_compiler.d) + */ +/* Copyright Digital Mars 2000 - 2011. + * 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) + */ +module std.compiler; + +immutable +{ + /// Vendor specific string naming the compiler, for example: "Digital Mars D". + string name = __VENDOR__; + + /// Master list of D compiler vendors. + enum Vendor + { + unknown = 0, /// Compiler vendor could not be detected + digitalMars = 1, /// Digital Mars D (DMD) + gnu = 2, /// GNU D Compiler (GDC) + llvm = 3, /// LLVM D Compiler (LDC) + dotNET = 4, /// D.NET + sdc = 5, /// Stupid D Compiler (SDC) + } + + /// Which vendor produced this compiler. + version (StdDdoc) Vendor vendor; + else version (DigitalMars) Vendor vendor = Vendor.digitalMars; + else version (GNU) Vendor vendor = Vendor.gnu; + else version (LDC) Vendor vendor = Vendor.llvm; + else version (D_NET) Vendor vendor = Vendor.dotNET; + else version (SDC) Vendor vendor = Vendor.sdc; + else Vendor vendor = Vendor.unknown; + + + /** + * The vendor specific version number, as in + * version_major.version_minor + */ + uint version_major = __VERSION__ / 1000; + uint version_minor = __VERSION__ % 1000; /// ditto + + + /** + * The version of the D Programming Language Specification + * supported by the compiler. + */ + uint D_major = 2; + uint D_minor = 0; +} diff --git a/libphobos/src/std/complex.d b/libphobos/src/std/complex.d new file mode 100644 index 0000000..7763822 --- /dev/null +++ b/libphobos/src/std/complex.d @@ -0,0 +1,994 @@ +// Written in the D programming language. + +/** This module contains the $(LREF Complex) type, which is used to represent + _complex numbers, along with related mathematical operations and functions. + + $(LREF Complex) will eventually + $(DDLINK deprecate, Deprecated Features, replace) + the built-in types $(D cfloat), $(D cdouble), $(D creal), $(D ifloat), + $(D idouble), and $(D ireal). + + Authors: Lars Tandle Kyllingstad, Don Clugston + Copyright: Copyright (c) 2010, Lars T. Kyllingstad. + License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0) + Source: $(PHOBOSSRC std/_complex.d) +*/ +module std.complex; + +import std.traits; + +/** Helper function that returns a _complex number with the specified + real and imaginary parts. + + Params: + R = (template parameter) type of real part of complex number + I = (template parameter) type of imaginary part of complex number + + re = real part of complex number to be constructed + im = (optional) imaginary part of complex number, 0 if omitted. + + Returns: + $(D Complex) instance with real and imaginary parts set + to the values provided as input. If neither $(D re) nor + $(D im) are floating-point numbers, the return type will + be $(D Complex!double). Otherwise, the return type is + deduced using $(D std.traits.CommonType!(R, I)). +*/ +auto complex(R)(R re) @safe pure nothrow @nogc +if (is(R : double)) +{ + static if (isFloatingPoint!R) + return Complex!R(re, 0); + else + return Complex!double(re, 0); +} + +/// ditto +auto complex(R, I)(R re, I im) @safe pure nothrow @nogc +if (is(R : double) && is(I : double)) +{ + static if (isFloatingPoint!R || isFloatingPoint!I) + return Complex!(CommonType!(R, I))(re, im); + else + return Complex!double(re, im); +} + +/// +@safe pure nothrow unittest +{ + auto a = complex(1.0); + static assert(is(typeof(a) == Complex!double)); + assert(a.re == 1.0); + assert(a.im == 0.0); + + auto b = complex(2.0L); + static assert(is(typeof(b) == Complex!real)); + assert(b.re == 2.0L); + assert(b.im == 0.0L); + + auto c = complex(1.0, 2.0); + static assert(is(typeof(c) == Complex!double)); + assert(c.re == 1.0); + assert(c.im == 2.0); + + auto d = complex(3.0, 4.0L); + static assert(is(typeof(d) == Complex!real)); + assert(d.re == 3.0); + assert(d.im == 4.0L); + + auto e = complex(1); + static assert(is(typeof(e) == Complex!double)); + assert(e.re == 1); + assert(e.im == 0); + + auto f = complex(1L, 2); + static assert(is(typeof(f) == Complex!double)); + assert(f.re == 1L); + assert(f.im == 2); + + auto g = complex(3, 4.0L); + static assert(is(typeof(g) == Complex!real)); + assert(g.re == 3); + assert(g.im == 4.0L); +} + + +/** A complex number parametrised by a type $(D T), which must be either + $(D float), $(D double) or $(D real). +*/ +struct Complex(T) +if (isFloatingPoint!T) +{ + import std.format : FormatSpec; + import std.range.primitives : isOutputRange; + + /** The real part of the number. */ + T re; + + /** The imaginary part of the number. */ + T im; + + /** Converts the complex number to a string representation. + + The second form of this function is usually not called directly; + instead, it is used via $(REF format, std,string), as shown in the examples + below. Supported format characters are 'e', 'f', 'g', 'a', and 's'. + + See the $(MREF std, format) and $(REF format, std,string) + documentation for more information. + */ + string toString() const @safe /* TODO: pure nothrow */ + { + import std.exception : assumeUnique; + char[] buf; + buf.reserve(100); + auto fmt = FormatSpec!char("%s"); + toString((const(char)[] s) { buf ~= s; }, fmt); + static trustedAssumeUnique(T)(T t) @trusted { return assumeUnique(t); } + return trustedAssumeUnique(buf); + } + + static if (is(T == double)) + /// + @safe unittest + { + auto c = complex(1.2, 3.4); + + // Vanilla toString formatting: + assert(c.toString() == "1.2+3.4i"); + + // Formatting with std.string.format specs: the precision and width + // specifiers apply to both the real and imaginary parts of the + // complex number. + import std.format : format; + assert(format("%.2f", c) == "1.20+3.40i"); + assert(format("%4.1f", c) == " 1.2+ 3.4i"); + } + + /// ditto + void toString(Writer, Char)(scope Writer w, + FormatSpec!Char formatSpec) const + if (isOutputRange!(Writer, const(Char)[])) + { + import std.format : formatValue; + import std.math : signbit; + import std.range.primitives : put; + formatValue(w, re, formatSpec); + if (signbit(im) == 0) + put(w, "+"); + formatValue(w, im, formatSpec); + put(w, "i"); + } + +@safe pure nothrow @nogc: + + /** Construct a complex number with the specified real and + imaginary parts. In the case where a single argument is passed + that is not complex, the imaginary part of the result will be + zero. + */ + this(R : T)(Complex!R z) + { + re = z.re; + im = z.im; + } + + /// ditto + this(Rx : T, Ry : T)(Rx x, Ry y) + { + re = x; + im = y; + } + + /// ditto + this(R : T)(R r) + { + re = r; + im = 0; + } + + // ASSIGNMENT OPERATORS + + // this = complex + ref Complex opAssign(R : T)(Complex!R z) + { + re = z.re; + im = z.im; + return this; + } + + // this = numeric + ref Complex opAssign(R : T)(R r) + { + re = r; + im = 0; + return this; + } + + // COMPARISON OPERATORS + + // this == complex + bool opEquals(R : T)(Complex!R z) const + { + return re == z.re && im == z.im; + } + + // this == numeric + bool opEquals(R : T)(R r) const + { + return re == r && im == 0; + } + + // UNARY OPERATORS + + // +complex + Complex opUnary(string op)() const + if (op == "+") + { + return this; + } + + // -complex + Complex opUnary(string op)() const + if (op == "-") + { + return Complex(-re, -im); + } + + // BINARY OPERATORS + + // complex op complex + Complex!(CommonType!(T,R)) opBinary(string op, R)(Complex!R z) const + { + alias C = typeof(return); + auto w = C(this.re, this.im); + return w.opOpAssign!(op)(z); + } + + // complex op numeric + Complex!(CommonType!(T,R)) opBinary(string op, R)(R r) const + if (isNumeric!R) + { + alias C = typeof(return); + auto w = C(this.re, this.im); + return w.opOpAssign!(op)(r); + } + + // numeric + complex, numeric * complex + Complex!(CommonType!(T, R)) opBinaryRight(string op, R)(R r) const + if ((op == "+" || op == "*") && (isNumeric!R)) + { + return opBinary!(op)(r); + } + + // numeric - complex + Complex!(CommonType!(T, R)) opBinaryRight(string op, R)(R r) const + if (op == "-" && isNumeric!R) + { + return Complex(r - re, -im); + } + + // numeric / complex + Complex!(CommonType!(T, R)) opBinaryRight(string op, R)(R r) const + if (op == "/" && isNumeric!R) + { + import std.math : fabs; + typeof(return) w = void; + if (fabs(re) < fabs(im)) + { + immutable ratio = re/im; + immutable rdivd = r/(re*ratio + im); + + w.re = rdivd*ratio; + w.im = -rdivd; + } + else + { + immutable ratio = im/re; + immutable rdivd = r/(re + im*ratio); + + w.re = rdivd; + w.im = -rdivd*ratio; + } + + return w; + } + + // numeric ^^ complex + Complex!(CommonType!(T, R)) opBinaryRight(string op, R)(R lhs) const + if (op == "^^" && isNumeric!R) + { + import std.math : cos, exp, log, sin, PI; + Unqual!(CommonType!(T, R)) ab = void, ar = void; + + if (lhs >= 0) + { + // r = lhs + // theta = 0 + ab = lhs ^^ this.re; + ar = log(lhs) * this.im; + } + else + { + // r = -lhs + // theta = PI + ab = (-lhs) ^^ this.re * exp(-PI * this.im); + ar = PI * this.re + log(-lhs) * this.im; + } + + return typeof(return)(ab * cos(ar), ab * sin(ar)); + } + + // OP-ASSIGN OPERATORS + + // complex += complex, complex -= complex + ref Complex opOpAssign(string op, C)(C z) + if ((op == "+" || op == "-") && is(C R == Complex!R)) + { + mixin ("re "~op~"= z.re;"); + mixin ("im "~op~"= z.im;"); + return this; + } + + // complex *= complex + ref Complex opOpAssign(string op, C)(C z) + if (op == "*" && is(C R == Complex!R)) + { + auto temp = re*z.re - im*z.im; + im = im*z.re + re*z.im; + re = temp; + return this; + } + + // complex /= complex + ref Complex opOpAssign(string op, C)(C z) + if (op == "/" && is(C R == Complex!R)) + { + import std.math : fabs; + if (fabs(z.re) < fabs(z.im)) + { + immutable ratio = z.re/z.im; + immutable denom = z.re*ratio + z.im; + + immutable temp = (re*ratio + im)/denom; + im = (im*ratio - re)/denom; + re = temp; + } + else + { + immutable ratio = z.im/z.re; + immutable denom = z.re + z.im*ratio; + + immutable temp = (re + im*ratio)/denom; + im = (im - re*ratio)/denom; + re = temp; + } + return this; + } + + // complex ^^= complex + ref Complex opOpAssign(string op, C)(C z) + if (op == "^^" && is(C R == Complex!R)) + { + import std.math : exp, log, cos, sin; + immutable r = abs(this); + immutable t = arg(this); + immutable ab = r^^z.re * exp(-t*z.im); + immutable ar = t*z.re + log(r)*z.im; + + re = ab*cos(ar); + im = ab*sin(ar); + return this; + } + + // complex += numeric, complex -= numeric + ref Complex opOpAssign(string op, U : T)(U a) + if (op == "+" || op == "-") + { + mixin ("re "~op~"= a;"); + return this; + } + + // complex *= numeric, complex /= numeric + ref Complex opOpAssign(string op, U : T)(U a) + if (op == "*" || op == "/") + { + mixin ("re "~op~"= a;"); + mixin ("im "~op~"= a;"); + return this; + } + + // complex ^^= real + ref Complex opOpAssign(string op, R)(R r) + if (op == "^^" && isFloatingPoint!R) + { + import std.math : cos, sin; + immutable ab = abs(this)^^r; + immutable ar = arg(this)*r; + re = ab*cos(ar); + im = ab*sin(ar); + return this; + } + + // complex ^^= int + ref Complex opOpAssign(string op, U)(U i) + if (op == "^^" && isIntegral!U) + { + switch (i) + { + case 0: + re = 1.0; + im = 0.0; + break; + case 1: + // identity; do nothing + break; + case 2: + this *= this; + break; + case 3: + auto z = this; + this *= z; + this *= z; + break; + default: + this ^^= cast(real) i; + } + return this; + } +} + +@safe pure nothrow unittest +{ + import std.complex; + import std.math; + + enum EPS = double.epsilon; + auto c1 = complex(1.0, 1.0); + + // Check unary operations. + auto c2 = Complex!double(0.5, 2.0); + + assert(c2 == +c2); + + assert((-c2).re == -(c2.re)); + assert((-c2).im == -(c2.im)); + assert(c2 == -(-c2)); + + // Check complex-complex operations. + auto cpc = c1 + c2; + assert(cpc.re == c1.re + c2.re); + assert(cpc.im == c1.im + c2.im); + + auto cmc = c1 - c2; + assert(cmc.re == c1.re - c2.re); + assert(cmc.im == c1.im - c2.im); + + auto ctc = c1 * c2; + assert(approxEqual(abs(ctc), abs(c1)*abs(c2), EPS)); + assert(approxEqual(arg(ctc), arg(c1)+arg(c2), EPS)); + + auto cdc = c1 / c2; + assert(approxEqual(abs(cdc), abs(c1)/abs(c2), EPS)); + assert(approxEqual(arg(cdc), arg(c1)-arg(c2), EPS)); + + auto cec = c1^^c2; + assert(approxEqual(cec.re, 0.11524131979943839881, EPS)); + assert(approxEqual(cec.im, 0.21870790452746026696, EPS)); + + // Check complex-real operations. + double a = 123.456; + + auto cpr = c1 + a; + assert(cpr.re == c1.re + a); + assert(cpr.im == c1.im); + + auto cmr = c1 - a; + assert(cmr.re == c1.re - a); + assert(cmr.im == c1.im); + + auto ctr = c1 * a; + assert(ctr.re == c1.re*a); + assert(ctr.im == c1.im*a); + + auto cdr = c1 / a; + assert(approxEqual(abs(cdr), abs(c1)/a, EPS)); + assert(approxEqual(arg(cdr), arg(c1), EPS)); + + auto cer = c1^^3.0; + assert(approxEqual(abs(cer), abs(c1)^^3, EPS)); + assert(approxEqual(arg(cer), arg(c1)*3, EPS)); + + auto rpc = a + c1; + assert(rpc == cpr); + + auto rmc = a - c1; + assert(rmc.re == a-c1.re); + assert(rmc.im == -c1.im); + + auto rtc = a * c1; + assert(rtc == ctr); + + auto rdc = a / c1; + assert(approxEqual(abs(rdc), a/abs(c1), EPS)); + assert(approxEqual(arg(rdc), -arg(c1), EPS)); + + rdc = a / c2; + assert(approxEqual(abs(rdc), a/abs(c2), EPS)); + assert(approxEqual(arg(rdc), -arg(c2), EPS)); + + auto rec1a = 1.0 ^^ c1; + assert(rec1a.re == 1.0); + assert(rec1a.im == 0.0); + + auto rec2a = 1.0 ^^ c2; + assert(rec2a.re == 1.0); + assert(rec2a.im == 0.0); + + auto rec1b = (-1.0) ^^ c1; + assert(approxEqual(abs(rec1b), std.math.exp(-PI * c1.im), EPS)); + auto arg1b = arg(rec1b); + /* The argument _should_ be PI, but floating-point rounding error + * means that in fact the imaginary part is very slightly negative. + */ + assert(approxEqual(arg1b, PI, EPS) || approxEqual(arg1b, -PI, EPS)); + + auto rec2b = (-1.0) ^^ c2; + assert(approxEqual(abs(rec2b), std.math.exp(-2 * PI), EPS)); + assert(approxEqual(arg(rec2b), PI_2, EPS)); + + auto rec3a = 0.79 ^^ complex(6.8, 5.7); + auto rec3b = complex(0.79, 0.0) ^^ complex(6.8, 5.7); + assert(approxEqual(rec3a.re, rec3b.re, EPS)); + assert(approxEqual(rec3a.im, rec3b.im, EPS)); + + auto rec4a = (-0.79) ^^ complex(6.8, 5.7); + auto rec4b = complex(-0.79, 0.0) ^^ complex(6.8, 5.7); + assert(approxEqual(rec4a.re, rec4b.re, EPS)); + assert(approxEqual(rec4a.im, rec4b.im, EPS)); + + auto rer = a ^^ complex(2.0, 0.0); + auto rcheck = a ^^ 2.0; + static assert(is(typeof(rcheck) == double)); + assert(feqrel(rer.re, rcheck) == double.mant_dig); + assert(isIdentical(rer.re, rcheck)); + assert(rer.im == 0.0); + + auto rer2 = (-a) ^^ complex(2.0, 0.0); + rcheck = (-a) ^^ 2.0; + assert(feqrel(rer2.re, rcheck) == double.mant_dig); + assert(isIdentical(rer2.re, rcheck)); + assert(approxEqual(rer2.im, 0.0, EPS)); + + auto rer3 = (-a) ^^ complex(-2.0, 0.0); + rcheck = (-a) ^^ (-2.0); + assert(feqrel(rer3.re, rcheck) == double.mant_dig); + assert(isIdentical(rer3.re, rcheck)); + assert(approxEqual(rer3.im, 0.0, EPS)); + + auto rer4 = a ^^ complex(-2.0, 0.0); + rcheck = a ^^ (-2.0); + assert(feqrel(rer4.re, rcheck) == double.mant_dig); + assert(isIdentical(rer4.re, rcheck)); + assert(rer4.im == 0.0); + + // Check Complex-int operations. + foreach (i; 0 .. 6) + { + auto cei = c1^^i; + assert(approxEqual(abs(cei), abs(c1)^^i, EPS)); + // Use cos() here to deal with arguments that go outside + // the (-pi,pi] interval (only an issue for i>3). + assert(approxEqual(std.math.cos(arg(cei)), std.math.cos(arg(c1)*i), EPS)); + } + + // Check operations between different complex types. + auto cf = Complex!float(1.0, 1.0); + auto cr = Complex!real(1.0, 1.0); + auto c1pcf = c1 + cf; + auto c1pcr = c1 + cr; + static assert(is(typeof(c1pcf) == Complex!double)); + static assert(is(typeof(c1pcr) == Complex!real)); + assert(c1pcf.re == c1pcr.re); + assert(c1pcf.im == c1pcr.im); + + auto c1c = c1; + auto c2c = c2; + + c1c /= c1; + assert(approxEqual(c1c.re, 1.0, EPS)); + assert(approxEqual(c1c.im, 0.0, EPS)); + + c1c = c1; + c1c /= c2; + assert(approxEqual(c1c.re, 0.588235, EPS)); + assert(approxEqual(c1c.im, -0.352941, EPS)); + + c2c /= c1; + assert(approxEqual(c2c.re, 1.25, EPS)); + assert(approxEqual(c2c.im, 0.75, EPS)); + + c2c = c2; + c2c /= c2; + assert(approxEqual(c2c.re, 1.0, EPS)); + assert(approxEqual(c2c.im, 0.0, EPS)); +} + +@safe pure nothrow unittest +{ + // Initialization + Complex!double a = 1; + assert(a.re == 1 && a.im == 0); + Complex!double b = 1.0; + assert(b.re == 1.0 && b.im == 0); + Complex!double c = Complex!real(1.0, 2); + assert(c.re == 1.0 && c.im == 2); +} + +@safe pure nothrow unittest +{ + // Assignments and comparisons + Complex!double z; + + z = 1; + assert(z == 1); + assert(z.re == 1.0 && z.im == 0.0); + + z = 2.0; + assert(z == 2.0); + assert(z.re == 2.0 && z.im == 0.0); + + z = 1.0L; + assert(z == 1.0L); + assert(z.re == 1.0 && z.im == 0.0); + + auto w = Complex!real(1.0, 1.0); + z = w; + assert(z == w); + assert(z.re == 1.0 && z.im == 1.0); + + auto c = Complex!float(2.0, 2.0); + z = c; + assert(z == c); + assert(z.re == 2.0 && z.im == 2.0); +} + + +/* Makes Complex!(Complex!T) fold to Complex!T. + + The rationale for this is that just like the real line is a + subspace of the complex plane, the complex plane is a subspace + of itself. Example of usage: + --- + Complex!T addI(T)(T x) + { + return x + Complex!T(0.0, 1.0); + } + --- + The above will work if T is both real and complex. +*/ +template Complex(T) +if (is(T R == Complex!R)) +{ + alias Complex = T; +} + +@safe pure nothrow unittest +{ + static assert(is(Complex!(Complex!real) == Complex!real)); + + Complex!T addI(T)(T x) + { + return x + Complex!T(0.0, 1.0); + } + + auto z1 = addI(1.0); + assert(z1.re == 1.0 && z1.im == 1.0); + + enum one = Complex!double(1.0, 0.0); + auto z2 = addI(one); + assert(z1 == z2); +} + + +/** + Params: z = A complex number. + Returns: The absolute value (or modulus) of `z`. +*/ +T abs(T)(Complex!T z) @safe pure nothrow @nogc +{ + import std.math : hypot; + return hypot(z.re, z.im); +} + +/// +@safe pure nothrow unittest +{ + static import std.math; + assert(abs(complex(1.0)) == 1.0); + assert(abs(complex(0.0, 1.0)) == 1.0); + assert(abs(complex(1.0L, -2.0L)) == std.math.sqrt(5.0L)); +} + + +/++ + Params: + z = A complex number. + x = A real number. + Returns: The squared modulus of `z`. + For genericity, if called on a real number, returns its square. ++/ +T sqAbs(T)(Complex!T z) @safe pure nothrow @nogc +{ + return z.re*z.re + z.im*z.im; +} + +/// +@safe pure nothrow unittest +{ + import std.math; + assert(sqAbs(complex(0.0)) == 0.0); + assert(sqAbs(complex(1.0)) == 1.0); + assert(sqAbs(complex(0.0, 1.0)) == 1.0); + assert(approxEqual(sqAbs(complex(1.0L, -2.0L)), 5.0L)); + assert(approxEqual(sqAbs(complex(-3.0L, 1.0L)), 10.0L)); + assert(approxEqual(sqAbs(complex(1.0f,-1.0f)), 2.0f)); +} + +/// ditto +T sqAbs(T)(T x) @safe pure nothrow @nogc +if (isFloatingPoint!T) +{ + return x*x; +} + +@safe pure nothrow unittest +{ + import std.math; + assert(sqAbs(0.0) == 0.0); + assert(sqAbs(-1.0) == 1.0); + assert(approxEqual(sqAbs(-3.0L), 9.0L)); + assert(approxEqual(sqAbs(-5.0f), 25.0f)); +} + + +/** + Params: z = A complex number. + Returns: The argument (or phase) of `z`. + */ +T arg(T)(Complex!T z) @safe pure nothrow @nogc +{ + import std.math : atan2; + return atan2(z.im, z.re); +} + +/// +@safe pure nothrow unittest +{ + import std.math; + assert(arg(complex(1.0)) == 0.0); + assert(arg(complex(0.0L, 1.0L)) == PI_2); + assert(arg(complex(1.0L, 1.0L)) == PI_4); +} + + +/** + Params: z = A complex number. + Returns: The complex conjugate of `z`. +*/ +Complex!T conj(T)(Complex!T z) @safe pure nothrow @nogc +{ + return Complex!T(z.re, -z.im); +} + +/// +@safe pure nothrow unittest +{ + assert(conj(complex(1.0)) == complex(1.0)); + assert(conj(complex(1.0, 2.0)) == complex(1.0, -2.0)); +} + + +/** + Constructs a complex number given its absolute value and argument. + Params: + modulus = The modulus + argument = The argument + Returns: The complex number with the given modulus and argument. +*/ +Complex!(CommonType!(T, U)) fromPolar(T, U)(T modulus, U argument) + @safe pure nothrow @nogc +{ + import std.math : sin, cos; + return Complex!(CommonType!(T,U)) + (modulus*cos(argument), modulus*sin(argument)); +} + +/// +@safe pure nothrow unittest +{ + import std.math; + auto z = fromPolar(std.math.sqrt(2.0), PI_4); + assert(approxEqual(z.re, 1.0L, real.epsilon)); + assert(approxEqual(z.im, 1.0L, real.epsilon)); +} + + +/** + Trigonometric functions on complex numbers. + + Params: z = A complex number. + Returns: The sine and cosine of `z`, respectively. +*/ +Complex!T sin(T)(Complex!T z) @safe pure nothrow @nogc +{ + import std.math : expi, coshisinh; + auto cs = expi(z.re); + auto csh = coshisinh(z.im); + return typeof(return)(cs.im * csh.re, cs.re * csh.im); +} + +/// +@safe pure nothrow unittest +{ + static import std.math; + assert(sin(complex(0.0)) == 0.0); + assert(sin(complex(2.0L, 0)) == std.math.sin(2.0L)); +} + + +/// ditto +Complex!T cos(T)(Complex!T z) @safe pure nothrow @nogc +{ + import std.math : expi, coshisinh; + auto cs = expi(z.re); + auto csh = coshisinh(z.im); + return typeof(return)(cs.re * csh.re, - cs.im * csh.im); +} + +/// +@safe pure nothrow unittest +{ + import std.complex; + import std.math; + assert(cos(complex(0.0)) == 1.0); + assert(cos(complex(1.3L)) == std.math.cos(1.3L)); + assert(cos(complex(0, 5.2L)) == cosh(5.2L)); +} + + +/** + Params: y = A real number. + Returns: The value of cos(y) + i sin(y). + + Note: + $(D expi) is included here for convenience and for easy migration of code + that uses $(REF _expi, std,math). Unlike $(REF _expi, std,math), which uses the + x87 $(I fsincos) instruction when possible, this function is no faster + than calculating cos(y) and sin(y) separately. +*/ +Complex!real expi(real y) @trusted pure nothrow @nogc +{ + import std.math : cos, sin; + return Complex!real(cos(y), sin(y)); +} + +/// +@safe pure nothrow unittest +{ + static import std.math; + + assert(expi(1.3e5L) == complex(std.math.cos(1.3e5L), std.math.sin(1.3e5L))); + assert(expi(0.0L) == 1.0L); + auto z1 = expi(1.234); + auto z2 = std.math.expi(1.234); + assert(z1.re == z2.re && z1.im == z2.im); +} + + +/** + Params: z = A complex number. + Returns: The square root of `z`. +*/ +Complex!T sqrt(T)(Complex!T z) @safe pure nothrow @nogc +{ + static import std.math; + typeof(return) c; + real x,y,w,r; + + if (z == 0) + { + c = typeof(return)(0, 0); + } + else + { + real z_re = z.re; + real z_im = z.im; + + x = std.math.fabs(z_re); + y = std.math.fabs(z_im); + if (x >= y) + { + r = y / x; + w = std.math.sqrt(x) + * std.math.sqrt(0.5 * (1 + std.math.sqrt(1 + r * r))); + } + else + { + r = x / y; + w = std.math.sqrt(y) + * std.math.sqrt(0.5 * (r + std.math.sqrt(1 + r * r))); + } + + if (z_re >= 0) + { + c = typeof(return)(w, z_im / (w + w)); + } + else + { + if (z_im < 0) + w = -w; + c = typeof(return)(z_im / (w + w), w); + } + } + return c; +} + +/// +@safe pure nothrow unittest +{ + static import std.math; + assert(sqrt(complex(0.0)) == 0.0); + assert(sqrt(complex(1.0L, 0)) == std.math.sqrt(1.0L)); + assert(sqrt(complex(-1.0L, 0)) == complex(0, 1.0L)); +} + +@safe pure nothrow unittest +{ + import std.math : approxEqual; + + auto c1 = complex(1.0, 1.0); + auto c2 = Complex!double(0.5, 2.0); + + auto c1s = sqrt(c1); + assert(approxEqual(c1s.re, 1.09868411)); + assert(approxEqual(c1s.im, 0.45508986)); + + auto c2s = sqrt(c2); + assert(approxEqual(c2s.re, 1.1317134)); + assert(approxEqual(c2s.im, 0.8836155)); +} + +// Issue 10881: support %f formatting of complex numbers +@safe unittest +{ + import std.format : format; + + auto x = complex(1.2, 3.4); + assert(format("%.2f", x) == "1.20+3.40i"); + + auto y = complex(1.2, -3.4); + assert(format("%.2f", y) == "1.20-3.40i"); +} + +@safe unittest +{ + // Test wide string formatting + import std.format; + wstring wformat(T)(string format, Complex!T c) + { + import std.array : appender; + auto w = appender!wstring(); + auto n = formattedWrite(w, format, c); + return w.data; + } + + auto x = complex(1.2, 3.4); + assert(wformat("%.2f", x) == "1.20+3.40i"w); +} + +@safe unittest +{ + // Test ease of use (vanilla toString() should be supported) + assert(complex(1.2, 3.4).toString() == "1.2+3.4i"); +} diff --git a/libphobos/src/std/concurrency.d b/libphobos/src/std/concurrency.d new file mode 100644 index 0000000..cf77911 --- /dev/null +++ b/libphobos/src/std/concurrency.d @@ -0,0 +1,2531 @@ +/** + * This is a low-level messaging API upon which more structured or restrictive + * APIs may be built. The general idea is that every messageable entity is + * represented by a common handle type called a Tid, which allows messages to + * be sent to logical threads that are executing in both the current process + * and in external processes using the same interface. This is an important + * aspect of scalability because it allows the components of a program to be + * spread across available resources with few to no changes to the actual + * implementation. + * + * A logical thread is an execution context that has its own stack and which + * runs asynchronously to other logical threads. These may be preemptively + * scheduled kernel threads, fibers (cooperative user-space threads), or some + * other concept with similar behavior. + * + * The type of concurrency used when logical threads are created is determined + * by the Scheduler selected at initialization time. The default behavior is + * currently to create a new kernel thread per call to spawn, but other + * schedulers are available that multiplex fibers across the main thread or + * use some combination of the two approaches. + * + * Copyright: Copyright Sean Kelly 2009 - 2014. + * License: Boost License 1.0. + * Authors: Sean Kelly, Alex Rønne Petersen, Martin Nowak + * Source: $(PHOBOSSRC std/_concurrency.d) + */ +/* Copyright Sean Kelly 2009 - 2014. + * 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) + */ +module std.concurrency; + +public import std.variant; + +import core.atomic; +import core.sync.condition; +import core.sync.mutex; +import core.thread; +import std.range.primitives; +import std.range.interfaces : InputRange; +import std.traits; + +/// +@system unittest +{ + __gshared string received; + static void spawnedFunc(Tid ownerTid) + { + import std.conv : text; + // Receive a message from the owner thread. + receive((int i){ + received = text("Received the number ", i); + + // Send a message back to the owner thread + // indicating success. + send(ownerTid, true); + }); + } + + // Start spawnedFunc in a new thread. + auto childTid = spawn(&spawnedFunc, thisTid); + + // Send the number 42 to this new thread. + send(childTid, 42); + + // Receive the result code. + auto wasSuccessful = receiveOnly!(bool); + assert(wasSuccessful); + assert(received == "Received the number 42"); +} + +private +{ + template hasLocalAliasing(T...) + { + static if (!T.length) + enum hasLocalAliasing = false; + else + enum hasLocalAliasing = (std.traits.hasUnsharedAliasing!(T[0]) && !is(T[0] == Tid)) || + std.concurrency.hasLocalAliasing!(T[1 .. $]); + } + + enum MsgType + { + standard, + priority, + linkDead, + } + + struct Message + { + MsgType type; + Variant data; + + this(T...)(MsgType t, T vals) if (T.length > 0) + { + static if (T.length == 1) + { + type = t; + data = vals[0]; + } + else + { + import std.typecons : Tuple; + + type = t; + data = Tuple!(T)(vals); + } + } + + @property auto convertsTo(T...)() + { + static if (T.length == 1) + { + return is(T[0] == Variant) || data.convertsTo!(T); + } + else + { + import std.typecons : Tuple; + return data.convertsTo!(Tuple!(T)); + } + } + + @property auto get(T...)() + { + static if (T.length == 1) + { + static if (is(T[0] == Variant)) + return data; + else + return data.get!(T); + } + else + { + import std.typecons : Tuple; + return data.get!(Tuple!(T)); + } + } + + auto map(Op)(Op op) + { + alias Args = Parameters!(Op); + + static if (Args.length == 1) + { + static if (is(Args[0] == Variant)) + return op(data); + else + return op(data.get!(Args)); + } + else + { + import std.typecons : Tuple; + return op(data.get!(Tuple!(Args)).expand); + } + } + } + + void checkops(T...)(T ops) + { + foreach (i, t1; T) + { + static assert(isFunctionPointer!t1 || isDelegate!t1); + alias a1 = Parameters!(t1); + alias r1 = ReturnType!(t1); + + static if (i < T.length - 1 && is(r1 == void)) + { + static assert(a1.length != 1 || !is(a1[0] == Variant), + "function with arguments " ~ a1.stringof ~ + " occludes successive function"); + + foreach (t2; T[i + 1 .. $]) + { + static assert(isFunctionPointer!t2 || isDelegate!t2); + alias a2 = Parameters!(t2); + + static assert(!is(a1 == a2), + "function with arguments " ~ a1.stringof ~ " occludes successive function"); + } + } + } + } + + @property ref ThreadInfo thisInfo() nothrow + { + if (scheduler is null) + return ThreadInfo.thisInfo; + return scheduler.thisInfo; + } +} + +static ~this() +{ + thisInfo.cleanup(); +} + +// Exceptions + +/** + * Thrown on calls to $(D receiveOnly) if a message other than the type + * the receiving thread expected is sent. + */ +class MessageMismatch : Exception +{ + /// + this(string msg = "Unexpected message type") @safe pure nothrow @nogc + { + super(msg); + } +} + +/** + * Thrown on calls to $(D receive) if the thread that spawned the receiving + * thread has terminated and no more messages exist. + */ +class OwnerTerminated : Exception +{ + /// + this(Tid t, string msg = "Owner terminated") @safe pure nothrow @nogc + { + super(msg); + tid = t; + } + + Tid tid; +} + +/** + * Thrown if a linked thread has terminated. + */ +class LinkTerminated : Exception +{ + /// + this(Tid t, string msg = "Link terminated") @safe pure nothrow @nogc + { + super(msg); + tid = t; + } + + Tid tid; +} + +/** + * Thrown if a message was sent to a thread via + * $(REF prioritySend, std,concurrency) and the receiver does not have a handler + * for a message of this type. + */ +class PriorityMessageException : Exception +{ + /// + this(Variant vals) + { + super("Priority message"); + message = vals; + } + + /** + * The message that was sent. + */ + Variant message; +} + +/** + * Thrown on mailbox crowding if the mailbox is configured with + * $(D OnCrowding.throwException). + */ +class MailboxFull : Exception +{ + /// + this(Tid t, string msg = "Mailbox full") @safe pure nothrow @nogc + { + super(msg); + tid = t; + } + + Tid tid; +} + +/** + * Thrown when a Tid is missing, e.g. when $(D ownerTid) doesn't + * find an owner thread. + */ +class TidMissingException : Exception +{ + import std.exception : basicExceptionCtors; + /// + mixin basicExceptionCtors; +} + + +// Thread ID + + +/** + * An opaque type used to represent a logical thread. + */ +struct Tid +{ +private: + this(MessageBox m) @safe pure nothrow @nogc + { + mbox = m; + } + + MessageBox mbox; + +public: + + /** + * Generate a convenient string for identifying this Tid. This is only + * useful to see if Tid's that are currently executing are the same or + * different, e.g. for logging and debugging. It is potentially possible + * that a Tid executed in the future will have the same toString() output + * as another Tid that has already terminated. + */ + void toString(scope void delegate(const(char)[]) sink) + { + import std.format : formattedWrite; + formattedWrite(sink, "Tid(%x)", cast(void*) mbox); + } + +} + +@system unittest +{ + // text!Tid is @system + import std.conv : text; + Tid tid; + assert(text(tid) == "Tid(0)"); + auto tid2 = thisTid; + assert(text(tid2) != "Tid(0)"); + auto tid3 = tid2; + assert(text(tid2) == text(tid3)); +} + +/** + * Returns: The $(LREF Tid) of the caller's thread. + */ +@property Tid thisTid() @safe +{ + // TODO: remove when concurrency is safe + static auto trus() @trusted + { + if (thisInfo.ident != Tid.init) + return thisInfo.ident; + thisInfo.ident = Tid(new MessageBox); + return thisInfo.ident; + } + + return trus(); +} + +/** + * Return the Tid of the thread which spawned the caller's thread. + * + * Throws: A $(D TidMissingException) exception if + * there is no owner thread. + */ +@property Tid ownerTid() +{ + import std.exception : enforce; + + enforce!TidMissingException(thisInfo.owner.mbox !is null, "Error: Thread has no owner thread."); + return thisInfo.owner; +} + +@system unittest +{ + import std.exception : assertThrown; + + static void fun() + { + string res = receiveOnly!string(); + assert(res == "Main calling"); + ownerTid.send("Child responding"); + } + + assertThrown!TidMissingException(ownerTid); + auto child = spawn(&fun); + child.send("Main calling"); + string res = receiveOnly!string(); + assert(res == "Child responding"); +} + +// Thread Creation + +private template isSpawnable(F, T...) +{ + template isParamsImplicitlyConvertible(F1, F2, int i = 0) + { + alias param1 = Parameters!F1; + alias param2 = Parameters!F2; + static if (param1.length != param2.length) + enum isParamsImplicitlyConvertible = false; + else static if (param1.length == i) + enum isParamsImplicitlyConvertible = true; + else static if (isImplicitlyConvertible!(param2[i], param1[i])) + enum isParamsImplicitlyConvertible = isParamsImplicitlyConvertible!(F1, + F2, i + 1); + else + enum isParamsImplicitlyConvertible = false; + } + + enum isSpawnable = isCallable!F && is(ReturnType!F == void) + && isParamsImplicitlyConvertible!(F, void function(T)) + && (isFunctionPointer!F || !hasUnsharedAliasing!F); +} + +/** + * Starts fn(args) in a new logical thread. + * + * Executes the supplied function in a new logical thread represented by + * $(D Tid). The calling thread is designated as the owner of the new thread. + * When the owner thread terminates an $(D OwnerTerminated) message will be + * sent to the new thread, causing an $(D OwnerTerminated) exception to be + * thrown on $(D receive()). + * + * Params: + * fn = The function to execute. + * args = Arguments to the function. + * + * Returns: + * A Tid representing the new logical thread. + * + * Notes: + * $(D args) must not have unshared aliasing. In other words, all arguments + * to $(D fn) must either be $(D shared) or $(D immutable) or have no + * pointer indirection. This is necessary for enforcing isolation among + * threads. + * + * Example: + * --- + * import std.stdio, std.concurrency; + * + * void f1(string str) + * { + * writeln(str); + * } + * + * void f2(char[] str) + * { + * writeln(str); + * } + * + * void main() + * { + * auto str = "Hello, world"; + * + * // Works: string is immutable. + * auto tid1 = spawn(&f1, str); + * + * // Fails: char[] has mutable aliasing. + * auto tid2 = spawn(&f2, str.dup); + * + * // New thread with anonymous function + * spawn({ writeln("This is so great!"); }); + * } + * --- + */ +Tid spawn(F, T...)(F fn, T args) if (isSpawnable!(F, T)) +{ + static assert(!hasLocalAliasing!(T), "Aliases to mutable thread-local data not allowed."); + return _spawn(false, fn, args); +} + +/** + * Starts fn(args) in a logical thread and will receive a LinkTerminated + * message when the operation terminates. + * + * Executes the supplied function in a new logical thread represented by + * Tid. This new thread is linked to the calling thread so that if either + * it or the calling thread terminates a LinkTerminated message will be sent + * to the other, causing a LinkTerminated exception to be thrown on receive(). + * The owner relationship from spawn() is preserved as well, so if the link + * between threads is broken, owner termination will still result in an + * OwnerTerminated exception to be thrown on receive(). + * + * Params: + * fn = The function to execute. + * args = Arguments to the function. + * + * Returns: + * A Tid representing the new thread. + */ +Tid spawnLinked(F, T...)(F fn, T args) if (isSpawnable!(F, T)) +{ + static assert(!hasLocalAliasing!(T), "Aliases to mutable thread-local data not allowed."); + return _spawn(true, fn, args); +} + +/* + * + */ +private Tid _spawn(F, T...)(bool linked, F fn, T args) if (isSpawnable!(F, T)) +{ + // TODO: MessageList and &exec should be shared. + auto spawnTid = Tid(new MessageBox); + auto ownerTid = thisTid; + + void exec() + { + thisInfo.ident = spawnTid; + thisInfo.owner = ownerTid; + fn(args); + } + + // TODO: MessageList and &exec should be shared. + if (scheduler !is null) + scheduler.spawn(&exec); + else + { + auto t = new Thread(&exec); + t.start(); + } + thisInfo.links[spawnTid] = linked; + return spawnTid; +} + +@system unittest +{ + void function() fn1; + void function(int) fn2; + static assert(__traits(compiles, spawn(fn1))); + static assert(__traits(compiles, spawn(fn2, 2))); + static assert(!__traits(compiles, spawn(fn1, 1))); + static assert(!__traits(compiles, spawn(fn2))); + + void delegate(int) shared dg1; + shared(void delegate(int)) dg2; + shared(void delegate(long) shared) dg3; + shared(void delegate(real, int, long) shared) dg4; + void delegate(int) immutable dg5; + void delegate(int) dg6; + static assert(__traits(compiles, spawn(dg1, 1))); + static assert(__traits(compiles, spawn(dg2, 2))); + static assert(__traits(compiles, spawn(dg3, 3))); + static assert(__traits(compiles, spawn(dg4, 4, 4, 4))); + static assert(__traits(compiles, spawn(dg5, 5))); + static assert(!__traits(compiles, spawn(dg6, 6))); + + auto callable1 = new class{ void opCall(int) shared {} }; + auto callable2 = cast(shared) new class{ void opCall(int) shared {} }; + auto callable3 = new class{ void opCall(int) immutable {} }; + auto callable4 = cast(immutable) new class{ void opCall(int) immutable {} }; + auto callable5 = new class{ void opCall(int) {} }; + auto callable6 = cast(shared) new class{ void opCall(int) immutable {} }; + auto callable7 = cast(immutable) new class{ void opCall(int) shared {} }; + auto callable8 = cast(shared) new class{ void opCall(int) const shared {} }; + auto callable9 = cast(const shared) new class{ void opCall(int) shared {} }; + auto callable10 = cast(const shared) new class{ void opCall(int) const shared {} }; + auto callable11 = cast(immutable) new class{ void opCall(int) const shared {} }; + static assert(!__traits(compiles, spawn(callable1, 1))); + static assert( __traits(compiles, spawn(callable2, 2))); + static assert(!__traits(compiles, spawn(callable3, 3))); + static assert( __traits(compiles, spawn(callable4, 4))); + static assert(!__traits(compiles, spawn(callable5, 5))); + static assert(!__traits(compiles, spawn(callable6, 6))); + static assert(!__traits(compiles, spawn(callable7, 7))); + static assert( __traits(compiles, spawn(callable8, 8))); + static assert(!__traits(compiles, spawn(callable9, 9))); + static assert( __traits(compiles, spawn(callable10, 10))); + static assert( __traits(compiles, spawn(callable11, 11))); +} + +/** + * Places the values as a message at the back of tid's message queue. + * + * Sends the supplied value to the thread represented by tid. As with + * $(REF spawn, std,concurrency), $(D T) must not have unshared aliasing. + */ +void send(T...)(Tid tid, T vals) +{ + static assert(!hasLocalAliasing!(T), "Aliases to mutable thread-local data not allowed."); + _send(tid, vals); +} + +/** + * Places the values as a message on the front of tid's message queue. + * + * Send a message to $(D tid) but place it at the front of $(D tid)'s message + * queue instead of at the back. This function is typically used for + * out-of-band communication, to signal exceptional conditions, etc. + */ +void prioritySend(T...)(Tid tid, T vals) +{ + static assert(!hasLocalAliasing!(T), "Aliases to mutable thread-local data not allowed."); + _send(MsgType.priority, tid, vals); +} + +/* + * ditto + */ +private void _send(T...)(Tid tid, T vals) +{ + _send(MsgType.standard, tid, vals); +} + +/* + * Implementation of send. This allows parameter checking to be different for + * both Tid.send() and .send(). + */ +private void _send(T...)(MsgType type, Tid tid, T vals) +{ + auto msg = Message(type, vals); + tid.mbox.put(msg); +} + +/** + * Receives a message from another thread. + * + * Receive a message from another thread, or block if no messages of the + * specified types are available. This function works by pattern matching + * a message against a set of delegates and executing the first match found. + * + * If a delegate that accepts a $(REF Variant, std,variant) is included as + * the last argument to $(D receive), it will match any message that was not + * matched by an earlier delegate. If more than one argument is sent, + * the $(D Variant) will contain a $(REF Tuple, std,typecons) of all values + * sent. + * + * Example: + * --- + * import std.stdio; + * import std.variant; + * import std.concurrency; + * + * void spawnedFunction() + * { + * receive( + * (int i) { writeln("Received an int."); }, + * (float f) { writeln("Received a float."); }, + * (Variant v) { writeln("Received some other type."); } + * ); + * } + * + * void main() + * { + * auto tid = spawn(&spawnedFunction); + * send(tid, 42); + * } + * --- + */ +void receive(T...)( T ops ) +in +{ + assert(thisInfo.ident.mbox !is null, + "Cannot receive a message until a thread was spawned " + ~ "or thisTid was passed to a running thread."); +} +body +{ + checkops( ops ); + + thisInfo.ident.mbox.get( ops ); +} + + +@safe unittest +{ + static assert( __traits( compiles, + { + receive( (Variant x) {} ); + receive( (int x) {}, (Variant x) {} ); + } ) ); + + static assert( !__traits( compiles, + { + receive( (Variant x) {}, (int x) {} ); + } ) ); + + static assert( !__traits( compiles, + { + receive( (int x) {}, (int x) {} ); + } ) ); +} + +// Make sure receive() works with free functions as well. +version (unittest) +{ + private void receiveFunction(int x) {} +} +@safe unittest +{ + static assert( __traits( compiles, + { + receive( &receiveFunction ); + receive( &receiveFunction, (Variant x) {} ); + } ) ); +} + + +private template receiveOnlyRet(T...) +{ + static if ( T.length == 1 ) + { + alias receiveOnlyRet = T[0]; + } + else + { + import std.typecons : Tuple; + alias receiveOnlyRet = Tuple!(T); + } +} + +/** + * Receives only messages with arguments of types $(D T). + * + * Throws: $(D MessageMismatch) if a message of types other than $(D T) + * is received. + * + * Returns: The received message. If $(D T.length) is greater than one, + * the message will be packed into a $(REF Tuple, std,typecons). + * + * Example: + * --- + * import std.concurrency; + * + * void spawnedFunc() + * { + * auto msg = receiveOnly!(int, string)(); + * assert(msg[0] == 42); + * assert(msg[1] == "42"); + * } + * + * void main() + * { + * auto tid = spawn(&spawnedFunc); + * send(tid, 42, "42"); + * } + * --- + */ +receiveOnlyRet!(T) receiveOnly(T...)() +in +{ + assert(thisInfo.ident.mbox !is null, + "Cannot receive a message until a thread was spawned or thisTid was passed to a running thread."); +} +body +{ + import std.format : format; + import std.typecons : Tuple; + + Tuple!(T) ret; + + thisInfo.ident.mbox.get((T val) { + static if (T.length) + ret.field = val; + }, + (LinkTerminated e) { throw e; }, + (OwnerTerminated e) { throw e; }, + (Variant val) { + static if (T.length > 1) + string exp = T.stringof; + else + string exp = T[0].stringof; + + throw new MessageMismatch( + format("Unexpected message type: expected '%s', got '%s'", exp, val.type.toString())); + }); + static if (T.length == 1) + return ret[0]; + else + return ret; +} + +@system unittest +{ + static void t1(Tid mainTid) + { + try + { + receiveOnly!string(); + mainTid.send(""); + } + catch (Throwable th) + { + mainTid.send(th.msg); + } + } + + auto tid = spawn(&t1, thisTid); + tid.send(1); + string result = receiveOnly!string(); + assert(result == "Unexpected message type: expected 'string', got 'int'"); +} + +/** + * Tries to receive but will give up if no matches arrive within duration. + * Won't wait at all if provided $(REF Duration, core,time) is negative. + * + * Same as $(D receive) except that rather than wait forever for a message, + * it waits until either it receives a message or the given + * $(REF Duration, core,time) has passed. It returns $(D true) if it received a + * message and $(D false) if it timed out waiting for one. + */ +bool receiveTimeout(T...)(Duration duration, T ops) +in +{ + assert(thisInfo.ident.mbox !is null, + "Cannot receive a message until a thread was spawned or thisTid was passed to a running thread."); +} +body +{ + checkops(ops); + + return thisInfo.ident.mbox.get(duration, ops); +} + +@safe unittest +{ + static assert(__traits(compiles, { + receiveTimeout(msecs(0), (Variant x) {}); + receiveTimeout(msecs(0), (int x) {}, (Variant x) {}); + })); + + static assert(!__traits(compiles, { + receiveTimeout(msecs(0), (Variant x) {}, (int x) {}); + })); + + static assert(!__traits(compiles, { + receiveTimeout(msecs(0), (int x) {}, (int x) {}); + })); + + static assert(__traits(compiles, { + receiveTimeout(msecs(10), (int x) {}, (Variant x) {}); + })); +} + +// MessageBox Limits + +/** + * These behaviors may be specified when a mailbox is full. + */ +enum OnCrowding +{ + block, /// Wait until room is available. + throwException, /// Throw a MailboxFull exception. + ignore /// Abort the send and return. +} + +private +{ + bool onCrowdingBlock(Tid tid) @safe pure nothrow @nogc + { + return true; + } + + bool onCrowdingThrow(Tid tid) @safe pure + { + throw new MailboxFull(tid); + } + + bool onCrowdingIgnore(Tid tid) @safe pure nothrow @nogc + { + return false; + } +} + +/** + * Sets a maximum mailbox size. + * + * Sets a limit on the maximum number of user messages allowed in the mailbox. + * If this limit is reached, the caller attempting to add a new message will + * execute the behavior specified by doThis. If messages is zero, the mailbox + * is unbounded. + * + * Params: + * tid = The Tid of the thread for which this limit should be set. + * messages = The maximum number of messages or zero if no limit. + * doThis = The behavior executed when a message is sent to a full + * mailbox. + */ +void setMaxMailboxSize(Tid tid, size_t messages, OnCrowding doThis) @safe pure +{ + final switch (doThis) + { + case OnCrowding.block: + return tid.mbox.setMaxMsgs(messages, &onCrowdingBlock); + case OnCrowding.throwException: + return tid.mbox.setMaxMsgs(messages, &onCrowdingThrow); + case OnCrowding.ignore: + return tid.mbox.setMaxMsgs(messages, &onCrowdingIgnore); + } +} + +/** + * Sets a maximum mailbox size. + * + * Sets a limit on the maximum number of user messages allowed in the mailbox. + * If this limit is reached, the caller attempting to add a new message will + * execute onCrowdingDoThis. If messages is zero, the mailbox is unbounded. + * + * Params: + * tid = The Tid of the thread for which this limit should be set. + * messages = The maximum number of messages or zero if no limit. + * onCrowdingDoThis = The routine called when a message is sent to a full + * mailbox. + */ +void setMaxMailboxSize(Tid tid, size_t messages, bool function(Tid) onCrowdingDoThis) +{ + tid.mbox.setMaxMsgs(messages, onCrowdingDoThis); +} + +private +{ + __gshared Tid[string] tidByName; + __gshared string[][Tid] namesByTid; +} + +private @property Mutex registryLock() +{ + __gshared Mutex impl; + initOnce!impl(new Mutex); + return impl; +} + +private void unregisterMe() +{ + auto me = thisInfo.ident; + if (thisInfo.ident != Tid.init) + { + synchronized (registryLock) + { + if (auto allNames = me in namesByTid) + { + foreach (name; *allNames) + tidByName.remove(name); + namesByTid.remove(me); + } + } + } +} + +/** + * Associates name with tid. + * + * Associates name with tid in a process-local map. When the thread + * represented by tid terminates, any names associated with it will be + * automatically unregistered. + * + * Params: + * name = The name to associate with tid. + * tid = The tid register by name. + * + * Returns: + * true if the name is available and tid is not known to represent a + * defunct thread. + */ +bool register(string name, Tid tid) +{ + synchronized (registryLock) + { + if (name in tidByName) + return false; + if (tid.mbox.isClosed) + return false; + namesByTid[tid] ~= name; + tidByName[name] = tid; + return true; + } +} + +/** + * Removes the registered name associated with a tid. + * + * Params: + * name = The name to unregister. + * + * Returns: + * true if the name is registered, false if not. + */ +bool unregister(string name) +{ + import std.algorithm.mutation : remove, SwapStrategy; + import std.algorithm.searching : countUntil; + + synchronized (registryLock) + { + if (auto tid = name in tidByName) + { + auto allNames = *tid in namesByTid; + auto pos = countUntil(*allNames, name); + remove!(SwapStrategy.unstable)(*allNames, pos); + tidByName.remove(name); + return true; + } + return false; + } +} + +/** + * Gets the Tid associated with name. + * + * Params: + * name = The name to locate within the registry. + * + * Returns: + * The associated Tid or Tid.init if name is not registered. + */ +Tid locate(string name) +{ + synchronized (registryLock) + { + if (auto tid = name in tidByName) + return *tid; + return Tid.init; + } +} + +/** + * Encapsulates all implementation-level data needed for scheduling. + * + * When defining a Scheduler, an instance of this struct must be associated + * with each logical thread. It contains all implementation-level information + * needed by the internal API. + */ +struct ThreadInfo +{ + Tid ident; + bool[Tid] links; + Tid owner; + + /** + * Gets a thread-local instance of ThreadInfo. + * + * Gets a thread-local instance of ThreadInfo, which should be used as the + * default instance when info is requested for a thread not created by the + * Scheduler. + */ + static @property ref thisInfo() nothrow + { + static ThreadInfo val; + return val; + } + + /** + * Cleans up this ThreadInfo. + * + * This must be called when a scheduled thread terminates. It tears down + * the messaging system for the thread and notifies interested parties of + * the thread's termination. + */ + void cleanup() + { + if (ident.mbox !is null) + ident.mbox.close(); + foreach (tid; links.keys) + _send(MsgType.linkDead, tid, ident); + if (owner != Tid.init) + _send(MsgType.linkDead, owner, ident); + unregisterMe(); // clean up registry entries + } +} + +/** + * A Scheduler controls how threading is performed by spawn. + * + * Implementing a Scheduler allows the concurrency mechanism used by this + * module to be customized according to different needs. By default, a call + * to spawn will create a new kernel thread that executes the supplied routine + * and terminates when finished. But it is possible to create Schedulers that + * reuse threads, that multiplex Fibers (coroutines) across a single thread, + * or any number of other approaches. By making the choice of Scheduler a + * user-level option, std.concurrency may be used for far more types of + * application than if this behavior were predefined. + * + * Example: + * --- + * import std.concurrency; + * import std.stdio; + * + * void main() + * { + * scheduler = new FiberScheduler; + * scheduler.start( + * { + * writeln("the rest of main goes here"); + * }); + * } + * --- + * + * Some schedulers have a dispatching loop that must run if they are to work + * properly, so for the sake of consistency, when using a scheduler, start() + * must be called within main(). This yields control to the scheduler and + * will ensure that any spawned threads are executed in an expected manner. + */ +interface Scheduler +{ + /** + * Spawns the supplied op and starts the Scheduler. + * + * This is intended to be called at the start of the program to yield all + * scheduling to the active Scheduler instance. This is necessary for + * schedulers that explicitly dispatch threads rather than simply relying + * on the operating system to do so, and so start should always be called + * within main() to begin normal program execution. + * + * Params: + * op = A wrapper for whatever the main thread would have done in the + * absence of a custom scheduler. It will be automatically executed + * via a call to spawn by the Scheduler. + */ + void start(void delegate() op); + + /** + * Assigns a logical thread to execute the supplied op. + * + * This routine is called by spawn. It is expected to instantiate a new + * logical thread and run the supplied operation. This thread must call + * thisInfo.cleanup() when the thread terminates if the scheduled thread + * is not a kernel thread--all kernel threads will have their ThreadInfo + * cleaned up automatically by a thread-local destructor. + * + * Params: + * op = The function to execute. This may be the actual function passed + * by the user to spawn itself, or may be a wrapper function. + */ + void spawn(void delegate() op); + + /** + * Yields execution to another logical thread. + * + * This routine is called at various points within concurrency-aware APIs + * to provide a scheduler a chance to yield execution when using some sort + * of cooperative multithreading model. If this is not appropriate, such + * as when each logical thread is backed by a dedicated kernel thread, + * this routine may be a no-op. + */ + void yield() nothrow; + + /** + * Returns an appropriate ThreadInfo instance. + * + * Returns an instance of ThreadInfo specific to the logical thread that + * is calling this routine or, if the calling thread was not create by + * this scheduler, returns ThreadInfo.thisInfo instead. + */ + @property ref ThreadInfo thisInfo() nothrow; + + /** + * Creates a Condition variable analog for signaling. + * + * Creates a new Condition variable analog which is used to check for and + * to signal the addition of messages to a thread's message queue. Like + * yield, some schedulers may need to define custom behavior so that calls + * to Condition.wait() yield to another thread when no new messages are + * available instead of blocking. + * + * Params: + * m = The Mutex that will be associated with this condition. It will be + * locked prior to any operation on the condition, and so in some + * cases a Scheduler may need to hold this reference and unlock the + * mutex before yielding execution to another logical thread. + */ + Condition newCondition(Mutex m) nothrow; +} + +/** + * An example Scheduler using kernel threads. + * + * This is an example Scheduler that mirrors the default scheduling behavior + * of creating one kernel thread per call to spawn. It is fully functional + * and may be instantiated and used, but is not a necessary part of the + * default functioning of this module. + */ +class ThreadScheduler : Scheduler +{ + /** + * This simply runs op directly, since no real scheduling is needed by + * this approach. + */ + void start(void delegate() op) + { + op(); + } + + /** + * Creates a new kernel thread and assigns it to run the supplied op. + */ + void spawn(void delegate() op) + { + auto t = new Thread(op); + t.start(); + } + + /** + * This scheduler does no explicit multiplexing, so this is a no-op. + */ + void yield() nothrow + { + // no explicit yield needed + } + + /** + * Returns ThreadInfo.thisInfo, since it is a thread-local instance of + * ThreadInfo, which is the correct behavior for this scheduler. + */ + @property ref ThreadInfo thisInfo() nothrow + { + return ThreadInfo.thisInfo; + } + + /** + * Creates a new Condition variable. No custom behavior is needed here. + */ + Condition newCondition(Mutex m) nothrow + { + return new Condition(m); + } +} + +/** + * An example Scheduler using Fibers. + * + * This is an example scheduler that creates a new Fiber per call to spawn + * and multiplexes the execution of all fibers within the main thread. + */ +class FiberScheduler : Scheduler +{ + /** + * This creates a new Fiber for the supplied op and then starts the + * dispatcher. + */ + void start(void delegate() op) + { + create(op); + dispatch(); + } + + /** + * This created a new Fiber for the supplied op and adds it to the + * dispatch list. + */ + void spawn(void delegate() op) nothrow + { + create(op); + yield(); + } + + /** + * If the caller is a scheduled Fiber, this yields execution to another + * scheduled Fiber. + */ + void yield() nothrow + { + // NOTE: It's possible that we should test whether the calling Fiber + // is an InfoFiber before yielding, but I think it's reasonable + // that any (non-Generator) fiber should yield here. + if (Fiber.getThis()) + Fiber.yield(); + } + + /** + * Returns an appropriate ThreadInfo instance. + * + * Returns a ThreadInfo instance specific to the calling Fiber if the + * Fiber was created by this dispatcher, otherwise it returns + * ThreadInfo.thisInfo. + */ + @property ref ThreadInfo thisInfo() nothrow + { + auto f = cast(InfoFiber) Fiber.getThis(); + + if (f !is null) + return f.info; + return ThreadInfo.thisInfo; + } + + /** + * Returns a Condition analog that yields when wait or notify is called. + */ + Condition newCondition(Mutex m) nothrow + { + return new FiberCondition(m); + } + +private: + static class InfoFiber : Fiber + { + ThreadInfo info; + + this(void delegate() op) nothrow + { + super(op); + } + } + + class FiberCondition : Condition + { + this(Mutex m) nothrow + { + super(m); + notified = false; + } + + override void wait() nothrow + { + scope (exit) notified = false; + + while (!notified) + switchContext(); + } + + override bool wait(Duration period) nothrow + { + import core.time : MonoTime; + + scope (exit) notified = false; + + for (auto limit = MonoTime.currTime + period; + !notified && !period.isNegative; + period = limit - MonoTime.currTime) + { + yield(); + } + return notified; + } + + override void notify() nothrow + { + notified = true; + switchContext(); + } + + override void notifyAll() nothrow + { + notified = true; + switchContext(); + } + + private: + void switchContext() nothrow + { + mutex_nothrow.unlock_nothrow(); + scope (exit) mutex_nothrow.lock_nothrow(); + yield(); + } + + private bool notified; + } + +private: + void dispatch() + { + import std.algorithm.mutation : remove; + + while (m_fibers.length > 0) + { + auto t = m_fibers[m_pos].call(Fiber.Rethrow.no); + if (t !is null && !(cast(OwnerTerminated) t)) + { + throw t; + } + if (m_fibers[m_pos].state == Fiber.State.TERM) + { + if (m_pos >= (m_fibers = remove(m_fibers, m_pos)).length) + m_pos = 0; + } + else if (m_pos++ >= m_fibers.length - 1) + { + m_pos = 0; + } + } + } + + void create(void delegate() op) nothrow + { + void wrap() + { + scope (exit) + { + thisInfo.cleanup(); + } + op(); + } + + m_fibers ~= new InfoFiber(&wrap); + } + +private: + Fiber[] m_fibers; + size_t m_pos; +} + +@system unittest +{ + static void receive(Condition cond, ref size_t received) + { + while (true) + { + synchronized (cond.mutex) + { + cond.wait(); + ++received; + } + } + } + + static void send(Condition cond, ref size_t sent) + { + while (true) + { + synchronized (cond.mutex) + { + ++sent; + cond.notify(); + } + } + } + + auto fs = new FiberScheduler; + auto mtx = new Mutex; + auto cond = fs.newCondition(mtx); + + size_t received, sent; + auto waiter = new Fiber({ receive(cond, received); }), notifier = new Fiber({ send(cond, sent); }); + waiter.call(); + assert(received == 0); + notifier.call(); + assert(sent == 1); + assert(received == 0); + waiter.call(); + assert(received == 1); + waiter.call(); + assert(received == 1); +} + +/** + * Sets the Scheduler behavior within the program. + * + * This variable sets the Scheduler behavior within this program. Typically, + * when setting a Scheduler, scheduler.start() should be called in main. This + * routine will not return until program execution is complete. + */ +__gshared Scheduler scheduler; + +// Generator + +/** + * If the caller is a Fiber and is not a Generator, this function will call + * scheduler.yield() or Fiber.yield(), as appropriate. + */ +void yield() nothrow +{ + auto fiber = Fiber.getThis(); + if (!(cast(IsGenerator) fiber)) + { + if (scheduler is null) + { + if (fiber) + return Fiber.yield(); + } + else + scheduler.yield(); + } +} + +/// Used to determine whether a Generator is running. +private interface IsGenerator {} + + +/** + * A Generator is a Fiber that periodically returns values of type T to the + * caller via yield. This is represented as an InputRange. + * + * Example: + * --- + * import std.concurrency; + * import std.stdio; + * + * + * void main() + * { + * auto tid = spawn( + * { + * while (true) + * { + * writeln(receiveOnly!int()); + * } + * }); + * + * auto r = new Generator!int( + * { + * foreach (i; 1 .. 10) + * yield(i); + * }); + * + * foreach (e; r) + * { + * tid.send(e); + * } + * } + * --- + */ +class Generator(T) : + Fiber, IsGenerator, InputRange!T +{ + /** + * Initializes a generator object which is associated with a static + * D function. The function will be called once to prepare the range + * for iteration. + * + * Params: + * fn = The fiber function. + * + * In: + * fn must not be null. + */ + this(void function() fn) + { + super(fn); + call(); + } + + /** + * Initializes a generator object which is associated with a static + * D function. The function will be called once to prepare the range + * for iteration. + * + * Params: + * fn = The fiber function. + * sz = The stack size for this fiber. + * + * In: + * fn must not be null. + */ + this(void function() fn, size_t sz) + { + super(fn, sz); + call(); + } + + /** + * Initializes a generator object which is associated with a dynamic + * D function. The function will be called once to prepare the range + * for iteration. + * + * Params: + * dg = The fiber function. + * + * In: + * dg must not be null. + */ + this(void delegate() dg) + { + super(dg); + call(); + } + + /** + * Initializes a generator object which is associated with a dynamic + * D function. The function will be called once to prepare the range + * for iteration. + * + * Params: + * dg = The fiber function. + * sz = The stack size for this fiber. + * + * In: + * dg must not be null. + */ + this(void delegate() dg, size_t sz) + { + super(dg, sz); + call(); + } + + /** + * Returns true if the generator is empty. + */ + final bool empty() @property + { + return m_value is null || state == State.TERM; + } + + /** + * Obtains the next value from the underlying function. + */ + final void popFront() + { + call(); + } + + /** + * Returns the most recently generated value by shallow copy. + */ + final T front() @property + { + return *m_value; + } + + /** + * Returns the most recently generated value without executing a + * copy contructor. Will not compile for element types defining a + * postblit, because Generator does not return by reference. + */ + final T moveFront() + { + static if (!hasElaborateCopyConstructor!T) + { + return front; + } + else + { + static assert(0, + "Fiber front is always rvalue and thus cannot be moved since it defines a postblit."); + } + } + + final int opApply(scope int delegate(T) loopBody) + { + int broken; + for (; !empty; popFront()) + { + broken = loopBody(front); + if (broken) break; + } + return broken; + } + + final int opApply(scope int delegate(size_t, T) loopBody) + { + int broken; + for (size_t i; !empty; ++i, popFront()) + { + broken = loopBody(i, front); + if (broken) break; + } + return broken; + } +private: + T* m_value; +} + +/** + * Yields a value of type T to the caller of the currently executing + * generator. + * + * Params: + * value = The value to yield. + */ +void yield(T)(ref T value) +{ + Generator!T cur = cast(Generator!T) Fiber.getThis(); + if (cur !is null && cur.state == Fiber.State.EXEC) + { + cur.m_value = &value; + return Fiber.yield(); + } + throw new Exception("yield(T) called with no active generator for the supplied type"); +} + +/// ditto +void yield(T)(T value) +{ + yield(value); +} + +@system unittest +{ + import core.exception; + import std.exception; + + static void testScheduler(Scheduler s) + { + scheduler = s; + scheduler.start({ + auto tid = spawn({ + int i; + + try + { + for (i = 1; i < 10; i++) + { + assertNotThrown!AssertError(assert(receiveOnly!int() == i)); + } + } + catch (OwnerTerminated e) + { + + } + + // i will advance 1 past the last value expected + assert(i == 4); + }); + + auto r = new Generator!int({ + assertThrown!Exception(yield(2.0)); + yield(); // ensure this is a no-op + yield(1); + yield(); // also once something has been yielded + yield(2); + yield(3); + }); + + foreach (e; r) + { + tid.send(e); + } + }); + scheduler = null; + } + + testScheduler(new ThreadScheduler); + testScheduler(new FiberScheduler); +} +/// +@system unittest +{ + import std.range; + + InputRange!int myIota = iota(10).inputRangeObject; + + myIota.popFront(); + myIota.popFront(); + assert(myIota.moveFront == 2); + assert(myIota.front == 2); + myIota.popFront(); + assert(myIota.front == 3); + + //can be assigned to std.range.interfaces.InputRange directly + myIota = new Generator!int( + { + foreach (i; 0 .. 10) yield(i); + }); + + myIota.popFront(); + myIota.popFront(); + assert(myIota.moveFront == 2); + assert(myIota.front == 2); + myIota.popFront(); + assert(myIota.front == 3); + + size_t[2] counter = [0, 0]; + foreach (i, unused; myIota) counter[] += [1, i]; + + assert(myIota.empty); + assert(counter == [7, 21]); +} + +private +{ + /* + * A MessageBox is a message queue for one thread. Other threads may send + * messages to this owner by calling put(), and the owner receives them by + * calling get(). The put() call is therefore effectively shared and the + * get() call is effectively local. setMaxMsgs may be used by any thread + * to limit the size of the message queue. + */ + class MessageBox + { + this() @trusted nothrow /* TODO: make @safe after relevant druntime PR gets merged */ + { + m_lock = new Mutex; + m_closed = false; + + if (scheduler is null) + { + m_putMsg = new Condition(m_lock); + m_notFull = new Condition(m_lock); + } + else + { + m_putMsg = scheduler.newCondition(m_lock); + m_notFull = scheduler.newCondition(m_lock); + } + } + + /// + final @property bool isClosed() @safe @nogc pure + { + synchronized (m_lock) + { + return m_closed; + } + } + + /* + * Sets a limit on the maximum number of user messages allowed in the + * mailbox. If this limit is reached, the caller attempting to add + * a new message will execute call. If num is zero, there is no limit + * on the message queue. + * + * Params: + * num = The maximum size of the queue or zero if the queue is + * unbounded. + * call = The routine to call when the queue is full. + */ + final void setMaxMsgs(size_t num, bool function(Tid) call) @safe @nogc pure + { + synchronized (m_lock) + { + m_maxMsgs = num; + m_onMaxMsgs = call; + } + } + + /* + * If maxMsgs is not set, the message is added to the queue and the + * owner is notified. If the queue is full, the message will still be + * accepted if it is a control message, otherwise onCrowdingDoThis is + * called. If the routine returns true, this call will block until + * the owner has made space available in the queue. If it returns + * false, this call will abort. + * + * Params: + * msg = The message to put in the queue. + * + * Throws: + * An exception if the queue is full and onCrowdingDoThis throws. + */ + final void put(ref Message msg) + { + synchronized (m_lock) + { + // TODO: Generate an error here if m_closed is true, or maybe + // put a message in the caller's queue? + if (!m_closed) + { + while (true) + { + if (isPriorityMsg(msg)) + { + m_sharedPty.put(msg); + m_putMsg.notify(); + return; + } + if (!mboxFull() || isControlMsg(msg)) + { + m_sharedBox.put(msg); + m_putMsg.notify(); + return; + } + if (m_onMaxMsgs !is null && !m_onMaxMsgs(thisTid)) + { + return; + } + m_putQueue++; + m_notFull.wait(); + m_putQueue--; + } + } + } + } + + /* + * Matches ops against each message in turn until a match is found. + * + * Params: + * ops = The operations to match. Each may return a bool to indicate + * whether a message with a matching type is truly a match. + * + * Returns: + * true if a message was retrieved and false if not (such as if a + * timeout occurred). + * + * Throws: + * LinkTerminated if a linked thread terminated, or OwnerTerminated + * if the owner thread terminates and no existing messages match the + * supplied ops. + */ + bool get(T...)(scope T vals) + { + import std.meta : AliasSeq; + + static assert(T.length); + + static if (isImplicitlyConvertible!(T[0], Duration)) + { + alias Ops = AliasSeq!(T[1 .. $]); + alias ops = vals[1 .. $]; + enum timedWait = true; + Duration period = vals[0]; + } + else + { + alias Ops = AliasSeq!(T); + alias ops = vals[0 .. $]; + enum timedWait = false; + } + + bool onStandardMsg(ref Message msg) + { + foreach (i, t; Ops) + { + alias Args = Parameters!(t); + auto op = ops[i]; + + if (msg.convertsTo!(Args)) + { + static if (is(ReturnType!(t) == bool)) + { + return msg.map(op); + } + else + { + msg.map(op); + return true; + } + } + } + return false; + } + + bool onLinkDeadMsg(ref Message msg) + { + assert(msg.convertsTo!(Tid)); + auto tid = msg.get!(Tid); + + if (bool* pDepends = tid in thisInfo.links) + { + auto depends = *pDepends; + thisInfo.links.remove(tid); + // Give the owner relationship precedence. + if (depends && tid != thisInfo.owner) + { + auto e = new LinkTerminated(tid); + auto m = Message(MsgType.standard, e); + if (onStandardMsg(m)) + return true; + throw e; + } + } + if (tid == thisInfo.owner) + { + thisInfo.owner = Tid.init; + auto e = new OwnerTerminated(tid); + auto m = Message(MsgType.standard, e); + if (onStandardMsg(m)) + return true; + throw e; + } + return false; + } + + bool onControlMsg(ref Message msg) + { + switch (msg.type) + { + case MsgType.linkDead: + return onLinkDeadMsg(msg); + default: + return false; + } + } + + bool scan(ref ListT list) + { + for (auto range = list[]; !range.empty;) + { + // Only the message handler will throw, so if this occurs + // we can be certain that the message was handled. + scope (failure) + list.removeAt(range); + + if (isControlMsg(range.front)) + { + if (onControlMsg(range.front)) + { + // Although the linkDead message is a control message, + // it can be handled by the user. Since the linkDead + // message throws if not handled, if we get here then + // it has been handled and we can return from receive. + // This is a weird special case that will have to be + // handled in a more general way if more are added. + if (!isLinkDeadMsg(range.front)) + { + list.removeAt(range); + continue; + } + list.removeAt(range); + return true; + } + range.popFront(); + continue; + } + else + { + if (onStandardMsg(range.front)) + { + list.removeAt(range); + return true; + } + range.popFront(); + continue; + } + } + return false; + } + + bool pty(ref ListT list) + { + if (!list.empty) + { + auto range = list[]; + + if (onStandardMsg(range.front)) + { + list.removeAt(range); + return true; + } + if (range.front.convertsTo!(Throwable)) + throw range.front.get!(Throwable); + else if (range.front.convertsTo!(shared(Throwable))) + throw range.front.get!(shared(Throwable)); + else + throw new PriorityMessageException(range.front.data); + } + return false; + } + + static if (timedWait) + { + import core.time : MonoTime; + auto limit = MonoTime.currTime + period; + } + + while (true) + { + ListT arrived; + + if (pty(m_localPty) || scan(m_localBox)) + { + return true; + } + yield(); + synchronized (m_lock) + { + updateMsgCount(); + while (m_sharedPty.empty && m_sharedBox.empty) + { + // NOTE: We're notifying all waiters here instead of just + // a few because the onCrowding behavior may have + // changed and we don't want to block sender threads + // unnecessarily if the new behavior is not to block. + // This will admittedly result in spurious wakeups + // in other situations, but what can you do? + if (m_putQueue && !mboxFull()) + m_notFull.notifyAll(); + static if (timedWait) + { + if (period <= Duration.zero || !m_putMsg.wait(period)) + return false; + } + else + { + m_putMsg.wait(); + } + } + m_localPty.put(m_sharedPty); + arrived.put(m_sharedBox); + } + if (m_localPty.empty) + { + scope (exit) m_localBox.put(arrived); + if (scan(arrived)) + { + return true; + } + else + { + static if (timedWait) + { + period = limit - MonoTime.currTime; + } + continue; + } + } + m_localBox.put(arrived); + pty(m_localPty); + return true; + } + } + + /* + * Called on thread termination. This routine processes any remaining + * control messages, clears out message queues, and sets a flag to + * reject any future messages. + */ + final void close() + { + static void onLinkDeadMsg(ref Message msg) + { + assert(msg.convertsTo!(Tid)); + auto tid = msg.get!(Tid); + + thisInfo.links.remove(tid); + if (tid == thisInfo.owner) + thisInfo.owner = Tid.init; + } + + static void sweep(ref ListT list) + { + for (auto range = list[]; !range.empty; range.popFront()) + { + if (range.front.type == MsgType.linkDead) + onLinkDeadMsg(range.front); + } + } + + ListT arrived; + + sweep(m_localBox); + synchronized (m_lock) + { + arrived.put(m_sharedBox); + m_closed = true; + } + m_localBox.clear(); + sweep(arrived); + } + + private: + // Routines involving local data only, no lock needed. + + bool mboxFull() @safe @nogc pure nothrow + { + return m_maxMsgs && m_maxMsgs <= m_localMsgs + m_sharedBox.length; + } + + void updateMsgCount() @safe @nogc pure nothrow + { + m_localMsgs = m_localBox.length; + } + + bool isControlMsg(ref Message msg) @safe @nogc pure nothrow + { + return msg.type != MsgType.standard && msg.type != MsgType.priority; + } + + bool isPriorityMsg(ref Message msg) @safe @nogc pure nothrow + { + return msg.type == MsgType.priority; + } + + bool isLinkDeadMsg(ref Message msg) @safe @nogc pure nothrow + { + return msg.type == MsgType.linkDead; + } + + alias OnMaxFn = bool function(Tid); + alias ListT = List!(Message); + + ListT m_localBox; + ListT m_localPty; + + Mutex m_lock; + Condition m_putMsg; + Condition m_notFull; + size_t m_putQueue; + ListT m_sharedBox; + ListT m_sharedPty; + OnMaxFn m_onMaxMsgs; + size_t m_localMsgs; + size_t m_maxMsgs; + bool m_closed; + } + + /* + * + */ + struct List(T) + { + struct Range + { + import std.exception : enforce; + + @property bool empty() const + { + return !m_prev.next; + } + + @property ref T front() + { + enforce(m_prev.next, "invalid list node"); + return m_prev.next.val; + } + + @property void front(T val) + { + enforce(m_prev.next, "invalid list node"); + m_prev.next.val = val; + } + + void popFront() + { + enforce(m_prev.next, "invalid list node"); + m_prev = m_prev.next; + } + + private this(Node* p) + { + m_prev = p; + } + + private Node* m_prev; + } + + void put(T val) + { + put(newNode(val)); + } + + void put(ref List!(T) rhs) + { + if (!rhs.empty) + { + put(rhs.m_first); + while (m_last.next !is null) + { + m_last = m_last.next; + m_count++; + } + rhs.m_first = null; + rhs.m_last = null; + rhs.m_count = 0; + } + } + + Range opSlice() + { + return Range(cast(Node*)&m_first); + } + + void removeAt(Range r) + { + import std.exception : enforce; + + assert(m_count); + Node* n = r.m_prev; + enforce(n && n.next, "attempting to remove invalid list node"); + + if (m_last is m_first) + m_last = null; + else if (m_last is n.next) + m_last = n; // nocoverage + Node* to_free = n.next; + n.next = n.next.next; + freeNode(to_free); + m_count--; + } + + @property size_t length() + { + return m_count; + } + + void clear() + { + m_first = m_last = null; + m_count = 0; + } + + @property bool empty() + { + return m_first is null; + } + + private: + struct Node + { + Node* next; + T val; + + this(T v) + { + val = v; + } + } + + static shared struct SpinLock + { + void lock() { while (!cas(&locked, false, true)) { Thread.yield(); } } + void unlock() { atomicStore!(MemoryOrder.rel)(locked, false); } + bool locked; + } + + static shared SpinLock sm_lock; + static shared Node* sm_head; + + Node* newNode(T v) + { + Node* n; + { + sm_lock.lock(); + scope (exit) sm_lock.unlock(); + + if (sm_head) + { + n = cast(Node*) sm_head; + sm_head = sm_head.next; + } + } + if (n) + { + import std.conv : emplace; + emplace!Node(n, v); + } + else + { + n = new Node(v); + } + return n; + } + + void freeNode(Node* n) + { + // destroy val to free any owned GC memory + destroy(n.val); + + sm_lock.lock(); + scope (exit) sm_lock.unlock(); + + auto sn = cast(shared(Node)*) n; + sn.next = sm_head; + sm_head = sn; + } + + void put(Node* n) + { + m_count++; + if (!empty) + { + m_last.next = n; + m_last = n; + return; + } + m_first = n; + m_last = n; + } + + Node* m_first; + Node* m_last; + size_t m_count; + } +} + +version (unittest) +{ + import std.stdio; + import std.typecons : tuple, Tuple; + + void testfn(Tid tid) + { + receive((float val) { assert(0); }, (int val, int val2) { + assert(val == 42 && val2 == 86); + }); + receive((Tuple!(int, int) val) { assert(val[0] == 42 && val[1] == 86); }); + receive((Variant val) { }); + receive((string val) { + if ("the quick brown fox" != val) + return false; + return true; + }, (string val) { assert(false); }); + prioritySend(tid, "done"); + } + + void runTest(Tid tid) + { + send(tid, 42, 86); + send(tid, tuple(42, 86)); + send(tid, "hello", "there"); + send(tid, "the quick brown fox"); + receive((string val) { assert(val == "done"); }); + } + + void simpleTest() + { + auto tid = spawn(&testfn, thisTid); + runTest(tid); + + // Run the test again with a limited mailbox size. + tid = spawn(&testfn, thisTid); + setMaxMailboxSize(tid, 2, OnCrowding.block); + runTest(tid); + } + + @system unittest + { + simpleTest(); + } + + @system unittest + { + scheduler = new ThreadScheduler; + simpleTest(); + scheduler = null; + } +} + +private @property Mutex initOnceLock() +{ + __gshared Mutex lock; + if (auto mtx = cast() atomicLoad!(MemoryOrder.acq)(*cast(shared)&lock)) + return mtx; + auto mtx = new Mutex; + if (cas(cast(shared)&lock, cast(shared) null, cast(shared) mtx)) + return mtx; + return cast() atomicLoad!(MemoryOrder.acq)(*cast(shared)&lock); +} + +/** + * Initializes $(D_PARAM var) with the lazy $(D_PARAM init) value in a + * thread-safe manner. + * + * The implementation guarantees that all threads simultaneously calling + * initOnce with the same $(D_PARAM var) argument block until $(D_PARAM var) is + * fully initialized. All side-effects of $(D_PARAM init) are globally visible + * afterwards. + * + * Params: + * var = The variable to initialize + * init = The lazy initializer value + * + * Returns: + * A reference to the initialized variable + */ +auto ref initOnce(alias var)(lazy typeof(var) init) +{ + return initOnce!var(init, initOnceLock); +} + +/// A typical use-case is to perform lazy but thread-safe initialization. +@system unittest +{ + static class MySingleton + { + static MySingleton instance() + { + static __gshared MySingleton inst; + return initOnce!inst(new MySingleton); + } + } + + assert(MySingleton.instance !is null); +} + +@system unittest +{ + static class MySingleton + { + static MySingleton instance() + { + static __gshared MySingleton inst; + return initOnce!inst(new MySingleton); + } + + private: + this() { val = ++cnt; } + size_t val; + static __gshared size_t cnt; + } + + foreach (_; 0 .. 10) + spawn({ ownerTid.send(MySingleton.instance.val); }); + foreach (_; 0 .. 10) + assert(receiveOnly!size_t == MySingleton.instance.val); + assert(MySingleton.cnt == 1); +} + +/** + * Same as above, but takes a separate mutex instead of sharing one among + * all initOnce instances. + * + * This should be used to avoid dead-locks when the $(D_PARAM init) + * expression waits for the result of another thread that might also + * call initOnce. Use with care. + * + * Params: + * var = The variable to initialize + * init = The lazy initializer value + * mutex = A mutex to prevent race conditions + * + * Returns: + * A reference to the initialized variable + */ +auto ref initOnce(alias var)(lazy typeof(var) init, Mutex mutex) +{ + // check that var is global, can't take address of a TLS variable + static assert(is(typeof({ __gshared p = &var; })), + "var must be 'static shared' or '__gshared'."); + import core.atomic : atomicLoad, MemoryOrder, atomicStore; + + static shared bool flag; + if (!atomicLoad!(MemoryOrder.acq)(flag)) + { + synchronized (mutex) + { + if (!atomicLoad!(MemoryOrder.acq)(flag)) + { + var = init; + atomicStore!(MemoryOrder.rel)(flag, true); + } + } + } + return var; +} + +/// Use a separate mutex when init blocks on another thread that might also call initOnce. +@system unittest +{ + import core.sync.mutex : Mutex; + + static shared bool varA, varB; + __gshared Mutex m; + m = new Mutex; + + spawn({ + // use a different mutex for varB to avoid a dead-lock + initOnce!varB(true, m); + ownerTid.send(true); + }); + // init depends on the result of the spawned thread + initOnce!varA(receiveOnly!bool); + assert(varA == true); + assert(varB == true); +} + +@system unittest +{ + static shared bool a; + __gshared bool b; + static bool c; + bool d; + initOnce!a(true); + initOnce!b(true); + static assert(!__traits(compiles, initOnce!c(true))); // TLS + static assert(!__traits(compiles, initOnce!d(true))); // local variable +} diff --git a/libphobos/src/std/container/array.d b/libphobos/src/std/container/array.d new file mode 100644 index 0000000..eee8901 --- /dev/null +++ b/libphobos/src/std/container/array.d @@ -0,0 +1,2419 @@ +/** + * This module provides an `Array` type with deterministic memory usage not + * reliant on the GC, as an alternative to the built-in arrays. + * + * This module is a submodule of $(MREF std, container). + * + * Source: $(PHOBOSSRC std/container/_array.d) + * + * Copyright: 2010- Andrei Alexandrescu. All rights reserved by the respective holders. + * + * License: Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at $(HTTP + * boost.org/LICENSE_1_0.txt)). + * + * Authors: $(HTTP erdani.com, Andrei Alexandrescu) + * + * $(SCRIPT inhibitQuickIndex = 1;) + */ +module std.container.array; + +import core.exception : RangeError; +import std.range.primitives; +import std.traits; + +public import std.container.util; + +/// +@system unittest +{ + auto arr = Array!int(0, 2, 3); + assert(arr[0] == 0); + assert(arr.front == 0); + assert(arr.back == 3); + + // reserve space + arr.reserve(1000); + assert(arr.length == 3); + assert(arr.capacity >= 1000); + + // insertion + arr.insertBefore(arr[1..$], 1); + assert(arr.front == 0); + assert(arr.length == 4); + + arr.insertBack(4); + assert(arr.back == 4); + assert(arr.length == 5); + + // set elements + arr[1] *= 42; + assert(arr[1] == 42); +} + +/// +@system unittest +{ + import std.algorithm.comparison : equal; + auto arr = Array!int(1, 2, 3); + + // concat + auto b = Array!int(11, 12, 13); + arr ~= b; + assert(arr.length == 6); + + // slicing + assert(arr[1 .. 3].equal([2, 3])); + + // remove + arr.linearRemove(arr[1 .. 3]); + assert(arr[0 .. 2].equal([1, 11])); +} + +/// `Array!bool` packs together values efficiently by allocating one bit per element +@system unittest +{ + Array!bool arr; + arr.insert([true, true, false, true, false]); + assert(arr.length == 5); +} + +private struct RangeT(A) +{ + /* Workaround for Issue 13629 at https://issues.dlang.org/show_bug.cgi?id=13629 + See also: http://forum.dlang.org/post/vbmwhzvawhnkoxrhbnyb@forum.dlang.org + */ + private A[1] _outer_; + private @property ref inout(A) _outer() inout { return _outer_[0]; } + + private size_t _a, _b; + + /* E is different from T when A is more restrictively qualified than T: + immutable(Array!int) => T == int, E = immutable(int) */ + alias E = typeof(_outer_[0]._data._payload[0]); + + private this(ref A data, size_t a, size_t b) + { + _outer_ = data; + _a = a; + _b = b; + } + + @property RangeT save() + { + return this; + } + + @property bool empty() @safe pure nothrow const + { + return _a >= _b; + } + + @property size_t length() @safe pure nothrow const + { + return _b - _a; + } + alias opDollar = length; + + @property ref inout(E) front() inout + { + assert(!empty, "Attempting to access the front of an empty Array"); + return _outer[_a]; + } + @property ref inout(E) back() inout + { + assert(!empty, "Attempting to access the back of an empty Array"); + return _outer[_b - 1]; + } + + void popFront() @safe @nogc pure nothrow + { + assert(!empty, "Attempting to popFront an empty Array"); + ++_a; + } + + void popBack() @safe @nogc pure nothrow + { + assert(!empty, "Attempting to popBack an empty Array"); + --_b; + } + + static if (isMutable!A) + { + import std.algorithm.mutation : move; + + E moveFront() + { + assert(!empty && _a < _outer.length); + return move(_outer._data._payload[_a]); + } + + E moveBack() + { + assert(!empty && _b <= _outer.length); + return move(_outer._data._payload[_b - 1]); + } + + E moveAt(size_t i) + { + assert(_a + i < _b && _a + i < _outer.length); + return move(_outer._data._payload[_a + i]); + } + } + + ref inout(E) opIndex(size_t i) inout + { + assert(_a + i < _b); + return _outer[_a + i]; + } + + RangeT opSlice() + { + return typeof(return)(_outer, _a, _b); + } + + RangeT opSlice(size_t i, size_t j) + { + assert(i <= j && _a + j <= _b); + return typeof(return)(_outer, _a + i, _a + j); + } + + RangeT!(const(A)) opSlice() const + { + return typeof(return)(_outer, _a, _b); + } + + RangeT!(const(A)) opSlice(size_t i, size_t j) const + { + assert(i <= j && _a + j <= _b); + return typeof(return)(_outer, _a + i, _a + j); + } + + static if (isMutable!A) + { + void opSliceAssign(E value) + { + assert(_b <= _outer.length); + _outer[_a .. _b] = value; + } + + void opSliceAssign(E value, size_t i, size_t j) + { + assert(_a + j <= _b); + _outer[_a + i .. _a + j] = value; + } + + void opSliceUnary(string op)() + if (op == "++" || op == "--") + { + assert(_b <= _outer.length); + mixin(op~"_outer[_a .. _b];"); + } + + void opSliceUnary(string op)(size_t i, size_t j) + if (op == "++" || op == "--") + { + assert(_a + j <= _b); + mixin(op~"_outer[_a + i .. _a + j];"); + } + + void opSliceOpAssign(string op)(E value) + { + assert(_b <= _outer.length); + mixin("_outer[_a .. _b] "~op~"= value;"); + } + + void opSliceOpAssign(string op)(E value, size_t i, size_t j) + { + assert(_a + j <= _b); + mixin("_outer[_a + i .. _a + j] "~op~"= value;"); + } + } +} + +/** + * _Array type with deterministic control of memory. The memory allocated + * for the array is reclaimed as soon as possible; there is no reliance + * on the garbage collector. `Array` uses `malloc`, `realloc` and `free` + * for managing its own memory. + * + * This means that pointers to elements of an `Array` will become + * dangling as soon as the element is removed from the `Array`. On the other hand + * the memory allocated by an `Array` will be scanned by the GC and + * GC managed objects referenced from an `Array` will be kept alive. + * + * Note: + * + * When using `Array` with range-based functions like those in `std.algorithm`, + * `Array` must be sliced to get a range (for example, use `array[].map!` + * instead of `array.map!`). The container itself is not a range. + */ +struct Array(T) +if (!is(Unqual!T == bool)) +{ + import core.stdc.stdlib : malloc, realloc, free; + import core.stdc.string : memcpy, memmove, memset; + + import core.memory : GC; + + import std.exception : enforce; + import std.typecons : RefCounted, RefCountedAutoInitialize; + + // This structure is not copyable. + private struct Payload + { + size_t _capacity; + T[] _payload; + + this(T[] p) { _capacity = p.length; _payload = p; } + + // Destructor releases array memory + ~this() + { + // Warning: destroy would destroy also class instances. + // The hasElaborateDestructor protects us here. + static if (hasElaborateDestructor!T) + foreach (ref e; _payload) + .destroy(e); + + static if (hasIndirections!T) + GC.removeRange(_payload.ptr); + + free(_payload.ptr); + } + + this(this) @disable; + + void opAssign(Payload rhs) @disable; + + @property size_t length() const + { + return _payload.length; + } + + @property void length(size_t newLength) + { + import std.algorithm.mutation : initializeAll; + + if (length >= newLength) + { + // shorten + static if (hasElaborateDestructor!T) + foreach (ref e; _payload.ptr[newLength .. _payload.length]) + .destroy(e); + + _payload = _payload.ptr[0 .. newLength]; + return; + } + immutable startEmplace = length; + if (_capacity < newLength) + { + // enlarge + import core.checkedint : mulu; + + bool overflow; + const nbytes = mulu(newLength, T.sizeof, overflow); + if (overflow) + assert(0); + _payload = (cast(T*) realloc(_payload.ptr, nbytes))[0 .. newLength]; + _capacity = newLength; + } + else + { + _payload = _payload.ptr[0 .. newLength]; + } + initializeAll(_payload.ptr[startEmplace .. newLength]); + } + + @property size_t capacity() const + { + return _capacity; + } + + void reserve(size_t elements) + { + if (elements <= capacity) return; + import core.checkedint : mulu; + bool overflow; + const sz = mulu(elements, T.sizeof, overflow); + if (overflow) + assert(0); + static if (hasIndirections!T) + { + /* Because of the transactional nature of this + * relative to the garbage collector, ensure no + * threading bugs by using malloc/copy/free rather + * than realloc. + */ + immutable oldLength = length; + + auto newPayloadPtr = cast(T*) malloc(sz); + newPayloadPtr || assert(false, "std.container.Array.reserve failed to allocate memory"); + auto newPayload = newPayloadPtr[0 .. oldLength]; + + // copy old data over to new array + memcpy(newPayload.ptr, _payload.ptr, T.sizeof * oldLength); + // Zero out unused capacity to prevent gc from seeing false pointers + memset(newPayload.ptr + oldLength, + 0, + (elements - oldLength) * T.sizeof); + GC.addRange(newPayload.ptr, sz); + GC.removeRange(_payload.ptr); + free(_payload.ptr); + _payload = newPayload; + } + else + { + // These can't have pointers, so no need to zero unused region + auto newPayloadPtr = cast(T*) realloc(_payload.ptr, sz); + newPayloadPtr || assert(false, "std.container.Array.reserve failed to allocate memory"); + auto newPayload = newPayloadPtr[0 .. length]; + _payload = newPayload; + } + _capacity = elements; + } + + // Insert one item + size_t insertBack(Elem)(Elem elem) + if (isImplicitlyConvertible!(Elem, T)) + { + import std.conv : emplace; + if (_capacity == length) + { + reserve(1 + capacity * 3 / 2); + } + assert(capacity > length && _payload.ptr); + emplace(_payload.ptr + _payload.length, elem); + _payload = _payload.ptr[0 .. _payload.length + 1]; + return 1; + } + + // Insert a range of items + size_t insertBack(Range)(Range r) + if (isInputRange!Range && isImplicitlyConvertible!(ElementType!Range, T)) + { + static if (hasLength!Range) + { + immutable oldLength = length; + reserve(oldLength + r.length); + } + size_t result; + foreach (item; r) + { + insertBack(item); + ++result; + } + static if (hasLength!Range) + { + assert(length == oldLength + r.length); + } + return result; + } + } + private alias Data = RefCounted!(Payload, RefCountedAutoInitialize.no); + private Data _data; + + /** + * Constructor taking a number of items. + */ + this(U)(U[] values...) + if (isImplicitlyConvertible!(U, T)) + { + import core.checkedint : mulu; + import std.conv : emplace; + bool overflow; + const nbytes = mulu(values.length, T.sizeof, overflow); + if (overflow) assert(0); + auto p = cast(T*) malloc(nbytes); + static if (hasIndirections!T) + { + if (p) + GC.addRange(p, T.sizeof * values.length); + } + + foreach (i, e; values) + { + emplace(p + i, e); + } + _data = Data(p[0 .. values.length]); + } + + /** + * Constructor taking an input range + */ + this(Range)(Range r) + if (isInputRange!Range && isImplicitlyConvertible!(ElementType!Range, T) && !is(Range == T[])) + { + insertBack(r); + } + + /** + * Comparison for equality. + */ + bool opEquals(const Array rhs) const + { + return opEquals(rhs); + } + + /// ditto + bool opEquals(ref const Array rhs) const + { + if (empty) return rhs.empty; + if (rhs.empty) return false; + return _data._payload == rhs._data._payload; + } + + /** + * Defines the array's primary range, which is a random-access range. + * + * `ConstRange` is a variant with `const` elements. + * `ImmutableRange` is a variant with `immutable` elements. + */ + alias Range = RangeT!Array; + + /// ditto + alias ConstRange = RangeT!(const Array); + + /// ditto + alias ImmutableRange = RangeT!(immutable Array); + + /** + * Duplicates the array. The elements themselves are not transitively + * duplicated. + * + * Complexity: $(BIGOH length). + */ + @property Array dup() + { + if (!_data.refCountedStore.isInitialized) return this; + return Array(_data._payload); + } + + /** + * Returns: `true` if and only if the array has no elements. + * + * Complexity: $(BIGOH 1) + */ + @property bool empty() const + { + return !_data.refCountedStore.isInitialized || _data._payload.empty; + } + + /** + * Returns: The number of elements in the array. + * + * Complexity: $(BIGOH 1). + */ + @property size_t length() const + { + return _data.refCountedStore.isInitialized ? _data._payload.length : 0; + } + + /// ditto + size_t opDollar() const + { + return length; + } + + /** + * Returns: The maximum number of elements the array can store without + * reallocating memory and invalidating iterators upon insertion. + * + * Complexity: $(BIGOH 1) + */ + @property size_t capacity() + { + return _data.refCountedStore.isInitialized ? _data._capacity : 0; + } + + /** + * Ensures sufficient capacity to accommodate `e` _elements. + * If `e < capacity`, this method does nothing. + * + * Postcondition: `capacity >= e` + * + * Note: If the capacity is increased, one should assume that all + * iterators to the elements are invalidated. + * + * Complexity: at most $(BIGOH length) if `e > capacity`, otherwise $(BIGOH 1). + */ + void reserve(size_t elements) + { + if (!_data.refCountedStore.isInitialized) + { + if (!elements) return; + import core.checkedint : mulu; + bool overflow; + const sz = mulu(elements, T.sizeof, overflow); + if (overflow) assert(0); + auto p = malloc(sz); + p || assert(false, "std.container.Array.reserve failed to allocate memory"); + static if (hasIndirections!T) + { + GC.addRange(p, sz); + } + _data = Data(cast(T[]) p[0 .. 0]); + _data._capacity = elements; + } + else + { + _data.reserve(elements); + } + } + + /** + * Returns: A range that iterates over elements of the array in + * forward order. + * + * Complexity: $(BIGOH 1) + */ + Range opSlice() + { + return typeof(return)(this, 0, length); + } + + ConstRange opSlice() const + { + return typeof(return)(this, 0, length); + } + + ImmutableRange opSlice() immutable + { + return typeof(return)(this, 0, length); + } + + /** + * Returns: A range that iterates over elements of the array from + * index `i` up to (excluding) index `j`. + * + * Precondition: `i <= j && j <= length` + * + * Complexity: $(BIGOH 1) + */ + Range opSlice(size_t i, size_t j) + { + assert(i <= j && j <= length); + return typeof(return)(this, i, j); + } + + ConstRange opSlice(size_t i, size_t j) const + { + assert(i <= j && j <= length); + return typeof(return)(this, i, j); + } + + ImmutableRange opSlice(size_t i, size_t j) immutable + { + assert(i <= j && j <= length); + return typeof(return)(this, i, j); + } + + /** + * Returns: The first element of the array. + * + * Precondition: `empty == false` + * + * Complexity: $(BIGOH 1) + */ + @property ref inout(T) front() inout + { + assert(_data.refCountedStore.isInitialized); + return _data._payload[0]; + } + + /** + * Returns: The last element of the array. + * + * Precondition: `empty == false` + * + * Complexity: $(BIGOH 1) + */ + @property ref inout(T) back() inout + { + assert(_data.refCountedStore.isInitialized); + return _data._payload[$ - 1]; + } + + /** + * Returns: The element or a reference to the element at the specified index. + * + * Precondition: `i < length` + * + * Complexity: $(BIGOH 1) + */ + ref inout(T) opIndex(size_t i) inout + { + assert(_data.refCountedStore.isInitialized); + return _data._payload[i]; + } + + /** + * Slicing operators executing the specified operation on the entire slice. + * + * Precondition: `i < j && j < length` + * + * Complexity: $(BIGOH slice.length) + */ + void opSliceAssign(T value) + { + if (!_data.refCountedStore.isInitialized) return; + _data._payload[] = value; + } + + /// ditto + void opSliceAssign(T value, size_t i, size_t j) + { + auto slice = _data.refCountedStore.isInitialized ? + _data._payload : + T[].init; + slice[i .. j] = value; + } + + /// ditto + void opSliceUnary(string op)() + if (op == "++" || op == "--") + { + if (!_data.refCountedStore.isInitialized) return; + mixin(op~"_data._payload[];"); + } + + /// ditto + void opSliceUnary(string op)(size_t i, size_t j) + if (op == "++" || op == "--") + { + auto slice = _data.refCountedStore.isInitialized ? _data._payload : T[].init; + mixin(op~"slice[i .. j];"); + } + + /// ditto + void opSliceOpAssign(string op)(T value) + { + if (!_data.refCountedStore.isInitialized) return; + mixin("_data._payload[] "~op~"= value;"); + } + + /// ditto + void opSliceOpAssign(string op)(T value, size_t i, size_t j) + { + auto slice = _data.refCountedStore.isInitialized ? _data._payload : T[].init; + mixin("slice[i .. j] "~op~"= value;"); + } + + private enum hasSliceWithLength(T) = is(typeof({ T t = T.init; t[].length; })); + + /** + * Returns: A new array which is a concatenation of `this` and its argument. + * + * Complexity: + * $(BIGOH length + m), where `m` is the number of elements in `stuff`. + */ + Array opBinary(string op, Stuff)(Stuff stuff) + if (op == "~") + { + Array result; + + static if (hasLength!Stuff || isNarrowString!Stuff) + result.reserve(length + stuff.length); + else static if (hasSliceWithLength!Stuff) + result.reserve(length + stuff[].length); + else static if (isImplicitlyConvertible!(Stuff, T)) + result.reserve(length + 1); + + result.insertBack(this[]); + result ~= stuff; + return result; + } + + /** + * Forwards to `insertBack`. + */ + void opOpAssign(string op, Stuff)(auto ref Stuff stuff) + if (op == "~") + { + static if (is(typeof(stuff[])) && isImplicitlyConvertible!(typeof(stuff[0]), T)) + { + insertBack(stuff[]); + } + else + { + insertBack(stuff); + } + } + + /** + * Removes all the elements from the array and releases allocated memory. + * + * Postcondition: `empty == true && capacity == 0` + * + * Complexity: $(BIGOH length) + */ + void clear() + { + _data = Data.init; + } + + /** + * Sets the number of elements in the array to `newLength`. If `newLength` + * is greater than `length`, the new elements are added to the end of the + * array and initialized with `T.init`. + * + * Complexity: + * Guaranteed $(BIGOH abs(length - newLength)) if `capacity >= newLength`. + * If `capacity < newLength` the worst case is $(BIGOH newLength). + * + * Postcondition: `length == newLength` + */ + @property void length(size_t newLength) + { + _data.refCountedStore.ensureInitialized(); + _data.length = newLength; + } + + /** + * Removes the last element from the array and returns it. + * Both stable and non-stable versions behave the same and guarantee + * that ranges iterating over the array are never invalidated. + * + * Precondition: `empty == false` + * + * Returns: The element removed. + * + * Complexity: $(BIGOH 1). + * + * Throws: `Exception` if the array is empty. + */ + T removeAny() + { + auto result = back; + removeBack(); + return result; + } + + /// ditto + alias stableRemoveAny = removeAny; + + /** + * Inserts the specified elements at the back of the array. `stuff` can be + * a value convertible to `T` or a range of objects convertible to `T`. + * + * Returns: The number of elements inserted. + * + * Complexity: + * $(BIGOH length + m) if reallocation takes place, otherwise $(BIGOH m), + * where `m` is the number of elements in `stuff`. + */ + size_t insertBack(Stuff)(Stuff stuff) + if (isImplicitlyConvertible!(Stuff, T) || + isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) + { + _data.refCountedStore.ensureInitialized(); + return _data.insertBack(stuff); + } + + /// ditto + alias insert = insertBack; + + /** + * Removes the value from the back of the array. Both stable and non-stable + * versions behave the same and guarantee that ranges iterating over the + * array are never invalidated. + * + * Precondition: `empty == false` + * + * Complexity: $(BIGOH 1). + * + * Throws: `Exception` if the array is empty. + */ + void removeBack() + { + enforce(!empty); + static if (hasElaborateDestructor!T) + .destroy(_data._payload[$ - 1]); + + _data._payload = _data._payload[0 .. $ - 1]; + } + + /// ditto + alias stableRemoveBack = removeBack; + + /** + * Removes `howMany` values from the back of the array. + * Unlike the unparameterized versions above, these functions + * do not throw if they could not remove `howMany` elements. Instead, + * if `howMany > n`, all elements are removed. The returned value is + * the effective number of elements removed. Both stable and non-stable + * versions behave the same and guarantee that ranges iterating over + * the array are never invalidated. + * + * Returns: The number of elements removed. + * + * Complexity: $(BIGOH howMany). + */ + size_t removeBack(size_t howMany) + { + if (howMany > length) howMany = length; + static if (hasElaborateDestructor!T) + foreach (ref e; _data._payload[$ - howMany .. $]) + .destroy(e); + + _data._payload = _data._payload[0 .. $ - howMany]; + return howMany; + } + + /// ditto + alias stableRemoveBack = removeBack; + + /** + * Inserts `stuff` before, after, or instead range `r`, which must + * be a valid range previously extracted from this array. `stuff` + * can be a value convertible to `T` or a range of objects convertible + * to `T`. Both stable and non-stable version behave the same and + * guarantee that ranges iterating over the array are never invalidated. + * + * Returns: The number of values inserted. + * + * Complexity: $(BIGOH length + m), where `m` is the length of `stuff`. + * + * Throws: `Exception` if `r` is not a range extracted from this array. + */ + size_t insertBefore(Stuff)(Range r, Stuff stuff) + if (isImplicitlyConvertible!(Stuff, T)) + { + import std.conv : emplace; + enforce(r._outer._data is _data && r._a <= length); + reserve(length + 1); + assert(_data.refCountedStore.isInitialized); + // Move elements over by one slot + memmove(_data._payload.ptr + r._a + 1, + _data._payload.ptr + r._a, + T.sizeof * (length - r._a)); + emplace(_data._payload.ptr + r._a, stuff); + _data._payload = _data._payload.ptr[0 .. _data._payload.length + 1]; + return 1; + } + + /// ditto + size_t insertBefore(Stuff)(Range r, Stuff stuff) + if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) + { + import std.conv : emplace; + enforce(r._outer._data is _data && r._a <= length); + static if (isForwardRange!Stuff) + { + // Can find the length in advance + auto extra = walkLength(stuff); + if (!extra) return 0; + reserve(length + extra); + assert(_data.refCountedStore.isInitialized); + // Move elements over by extra slots + memmove(_data._payload.ptr + r._a + extra, + _data._payload.ptr + r._a, + T.sizeof * (length - r._a)); + foreach (p; _data._payload.ptr + r._a .. + _data._payload.ptr + r._a + extra) + { + emplace(p, stuff.front); + stuff.popFront(); + } + _data._payload = + _data._payload.ptr[0 .. _data._payload.length + extra]; + return extra; + } + else + { + import std.algorithm.mutation : bringToFront; + enforce(_data); + immutable offset = r._a; + enforce(offset <= length); + auto result = insertBack(stuff); + bringToFront(this[offset .. length - result], + this[length - result .. length]); + return result; + } + } + + /// ditto + alias stableInsertBefore = insertBefore; + + /// ditto + size_t insertAfter(Stuff)(Range r, Stuff stuff) + { + import std.algorithm.mutation : bringToFront; + enforce(r._outer._data is _data); + // TODO: optimize + immutable offset = r._b; + enforce(offset <= length); + auto result = insertBack(stuff); + bringToFront(this[offset .. length - result], + this[length - result .. length]); + return result; + } + + /// ditto + size_t replace(Stuff)(Range r, Stuff stuff) + if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) + { + enforce(r._outer._data is _data); + size_t result; + for (; !stuff.empty; stuff.popFront()) + { + if (r.empty) + { + // insert the rest + return result + insertBefore(r, stuff); + } + r.front = stuff.front; + r.popFront(); + ++result; + } + // Remove remaining stuff in r + linearRemove(r); + return result; + } + + /// ditto + size_t replace(Stuff)(Range r, Stuff stuff) + if (isImplicitlyConvertible!(Stuff, T)) + { + enforce(r._outer._data is _data); + if (r.empty) + { + insertBefore(r, stuff); + } + else + { + r.front = stuff; + r.popFront(); + linearRemove(r); + } + return 1; + } + + /** + * Removes all elements belonging to `r`, which must be a range + * obtained originally from this array. + * + * Returns: A range spanning the remaining elements in the array that + * initially were right after `r`. + * + * Complexity: $(BIGOH length) + * + * Throws: `Exception` if `r` is not a valid range extracted from this array. + */ + Range linearRemove(Range r) + { + import std.algorithm.mutation : copy; + + enforce(r._outer._data is _data); + enforce(_data.refCountedStore.isInitialized); + enforce(r._a <= r._b && r._b <= length); + immutable offset1 = r._a; + immutable offset2 = r._b; + immutable tailLength = length - offset2; + // Use copy here, not a[] = b[] because the ranges may overlap + copy(this[offset2 .. length], this[offset1 .. offset1 + tailLength]); + length = offset1 + tailLength; + return this[length - tailLength .. length]; + } +} + +@system unittest +{ + Array!int a; + assert(a.empty); +} + +@system unittest +{ + Array!int a; + a.length = 10; + assert(a.length == 10); + assert(a.capacity >= a.length); +} + +@system unittest +{ + struct Dumb { int x = 5; } + Array!Dumb a; + a.length = 10; + assert(a.length == 10); + assert(a.capacity >= a.length); + immutable cap = a.capacity; + foreach (ref e; a) + e.x = 10; + a.length = 5; + assert(a.length == 5); + // do not realloc if length decreases + assert(a.capacity == cap); + foreach (ref e; a) + assert(e.x == 10); + + a.length = 8; + assert(a.length == 8); + // do not realloc if capacity sufficient + assert(a.capacity == cap); + assert(Dumb.init.x == 5); + foreach (i; 0 .. 5) + assert(a[i].x == 10); + foreach (i; 5 .. a.length) + assert(a[i].x == Dumb.init.x); + + // realloc required, check if values properly copied + a[] = Dumb(1); + a.length = 20; + assert(a.capacity >= 20); + foreach (i; 0 .. 8) + assert(a[i].x == 1); + foreach (i; 8 .. a.length) + assert(a[i].x == Dumb.init.x); + + // check if overlapping elements properly initialized + a.length = 1; + a.length = 20; + assert(a[0].x == 1); + foreach (e; a[1 .. $]) + assert(e.x == Dumb.init.x); +} + +@system unittest +{ + Array!int a = Array!int(1, 2, 3); + //a._data._refCountedDebug = true; + auto b = a.dup; + assert(b == Array!int(1, 2, 3)); + b.front = 42; + assert(b == Array!int(42, 2, 3)); + assert(a == Array!int(1, 2, 3)); +} + +@system unittest +{ + auto a = Array!int(1, 2, 3); + assert(a.length == 3); +} + +@system unittest +{ + const Array!int a = [1, 2]; + + assert(a[0] == 1); + assert(a.front == 1); + assert(a.back == 2); + + static assert(!__traits(compiles, { a[0] = 1; })); + static assert(!__traits(compiles, { a.front = 1; })); + static assert(!__traits(compiles, { a.back = 1; })); + + auto r = a[]; + size_t i; + foreach (e; r) + { + assert(e == i + 1); + i++; + } +} + +@safe unittest +{ + // REG https://issues.dlang.org/show_bug.cgi?id=13621 + import std.container : Array, BinaryHeap; + alias Heap = BinaryHeap!(Array!int); +} + +@system unittest +{ + Array!int a; + a.reserve(1000); + assert(a.length == 0); + assert(a.empty); + assert(a.capacity >= 1000); + auto p = a._data._payload.ptr; + foreach (i; 0 .. 1000) + { + a.insertBack(i); + } + assert(p == a._data._payload.ptr); +} + +@system unittest +{ + auto a = Array!int(1, 2, 3); + a[1] *= 42; + assert(a[1] == 84); +} + +@system unittest +{ + auto a = Array!int(1, 2, 3); + auto b = Array!int(11, 12, 13); + auto c = a ~ b; + assert(c == Array!int(1, 2, 3, 11, 12, 13)); + assert(a ~ b[] == Array!int(1, 2, 3, 11, 12, 13)); + assert(a ~ [4,5] == Array!int(1,2,3,4,5)); +} + +@system unittest +{ + auto a = Array!int(1, 2, 3); + auto b = Array!int(11, 12, 13); + a ~= b; + assert(a == Array!int(1, 2, 3, 11, 12, 13)); +} + +@system unittest +{ + auto a = Array!int(1, 2, 3, 4); + assert(a.removeAny() == 4); + assert(a == Array!int(1, 2, 3)); +} + +@system unittest +{ + auto a = Array!int(1, 2, 3, 4, 5); + auto r = a[2 .. a.length]; + assert(a.insertBefore(r, 42) == 1); + assert(a == Array!int(1, 2, 42, 3, 4, 5)); + r = a[2 .. 2]; + assert(a.insertBefore(r, [8, 9]) == 2); + assert(a == Array!int(1, 2, 8, 9, 42, 3, 4, 5)); +} + +@system unittest +{ + auto a = Array!int(0, 1, 2, 3, 4, 5, 6, 7, 8); + a.linearRemove(a[4 .. 6]); + assert(a == Array!int(0, 1, 2, 3, 6, 7, 8)); +} + +// Give the Range object some testing. +@system unittest +{ + import std.algorithm.comparison : equal; + import std.range : retro; + auto a = Array!int(0, 1, 2, 3, 4, 5, 6)[]; + auto b = Array!int(6, 5, 4, 3, 2, 1, 0)[]; + alias A = typeof(a); + + static assert(isRandomAccessRange!A); + static assert(hasSlicing!A); + static assert(hasAssignableElements!A); + static assert(hasMobileElements!A); + + assert(equal(retro(b), a)); + assert(a.length == 7); + assert(equal(a[1 .. 4], [1, 2, 3])); +} +// Test issue 5920 +@system unittest +{ + struct structBug5920 + { + int order; + uint* pDestructionMask; + ~this() + { + if (pDestructionMask) + *pDestructionMask += 1 << order; + } + } + + alias S = structBug5920; + uint dMask; + + auto arr = Array!S(cast(S[])[]); + foreach (i; 0 .. 8) + arr.insertBack(S(i, &dMask)); + // don't check dMask now as S may be copied multiple times (it's ok?) + { + assert(arr.length == 8); + dMask = 0; + arr.length = 6; + assert(arr.length == 6); // make sure shrinking calls the d'tor + assert(dMask == 0b1100_0000); + arr.removeBack(); + assert(arr.length == 5); // make sure removeBack() calls the d'tor + assert(dMask == 0b1110_0000); + arr.removeBack(3); + assert(arr.length == 2); // ditto + assert(dMask == 0b1111_1100); + arr.clear(); + assert(arr.length == 0); // make sure clear() calls the d'tor + assert(dMask == 0b1111_1111); + } + assert(dMask == 0b1111_1111); // make sure the d'tor is called once only. +} +// Test issue 5792 (mainly just to check if this piece of code is compilable) +@system unittest +{ + auto a = Array!(int[])([[1,2],[3,4]]); + a.reserve(4); + assert(a.capacity >= 4); + assert(a.length == 2); + assert(a[0] == [1,2]); + assert(a[1] == [3,4]); + a.reserve(16); + assert(a.capacity >= 16); + assert(a.length == 2); + assert(a[0] == [1,2]); + assert(a[1] == [3,4]); +} + +// test replace!Stuff with range Stuff +@system unittest +{ + import std.algorithm.comparison : equal; + auto a = Array!int([1, 42, 5]); + a.replace(a[1 .. 2], [2, 3, 4]); + assert(equal(a[], [1, 2, 3, 4, 5])); +} + +// test insertBefore and replace with empty Arrays +@system unittest +{ + import std.algorithm.comparison : equal; + auto a = Array!int(); + a.insertBefore(a[], 1); + assert(equal(a[], [1])); +} +@system unittest +{ + import std.algorithm.comparison : equal; + auto a = Array!int(); + a.insertBefore(a[], [1, 2]); + assert(equal(a[], [1, 2])); +} +@system unittest +{ + import std.algorithm.comparison : equal; + auto a = Array!int(); + a.replace(a[], [1, 2]); + assert(equal(a[], [1, 2])); +} +@system unittest +{ + import std.algorithm.comparison : equal; + auto a = Array!int(); + a.replace(a[], 1); + assert(equal(a[], [1])); +} +// make sure that Array instances refuse ranges that don't belong to them +@system unittest +{ + import std.exception : assertThrown; + + Array!int a = [1, 2, 3]; + auto r = a.dup[]; + assertThrown(a.insertBefore(r, 42)); + assertThrown(a.insertBefore(r, [42])); + assertThrown(a.insertAfter(r, 42)); + assertThrown(a.replace(r, 42)); + assertThrown(a.replace(r, [42])); + assertThrown(a.linearRemove(r)); +} +@system unittest +{ + auto a = Array!int([1, 1]); + a[1] = 0; //Check Array.opIndexAssign + assert(a[1] == 0); + a[1] += 1; //Check Array.opIndexOpAssign + assert(a[1] == 1); + + //Check Array.opIndexUnary + ++a[0]; + //a[0]++ //op++ doesn't return, so this shouldn't work, even with 5044 fixed + assert(a[0] == 2); + assert(+a[0] == +2); + assert(-a[0] == -2); + assert(~a[0] == ~2); + + auto r = a[]; + r[1] = 0; //Check Array.Range.opIndexAssign + assert(r[1] == 0); + r[1] += 1; //Check Array.Range.opIndexOpAssign + assert(r[1] == 1); + + //Check Array.Range.opIndexUnary + ++r[0]; + //r[0]++ //op++ doesn't return, so this shouldn't work, even with 5044 fixed + assert(r[0] == 3); + assert(+r[0] == +3); + assert(-r[0] == -3); + assert(~r[0] == ~3); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + + //Test "array-wide" operations + auto a = Array!int([0, 1, 2]); //Array + a[] += 5; + assert(a[].equal([5, 6, 7])); + ++a[]; + assert(a[].equal([6, 7, 8])); + a[1 .. 3] *= 5; + assert(a[].equal([6, 35, 40])); + a[0 .. 2] = 0; + assert(a[].equal([0, 0, 40])); + + //Test empty array + auto a2 = Array!int.init; + ++a2[]; + ++a2[0 .. 0]; + a2[] = 0; + a2[0 .. 0] = 0; + a2[] += 0; + a2[0 .. 0] += 0; + + //Test "range-wide" operations + auto r = Array!int([0, 1, 2])[]; //Array.Range + r[] += 5; + assert(r.equal([5, 6, 7])); + ++r[]; + assert(r.equal([6, 7, 8])); + r[1 .. 3] *= 5; + assert(r.equal([6, 35, 40])); + r[0 .. 2] = 0; + assert(r.equal([0, 0, 40])); + + //Test empty Range + auto r2 = Array!int.init[]; + ++r2[]; + ++r2[0 .. 0]; + r2[] = 0; + r2[0 .. 0] = 0; + r2[] += 0; + r2[0 .. 0] += 0; +} + +// Test issue 11194 +@system unittest +{ + static struct S { + int i = 1337; + void* p; + this(this) { assert(i == 1337); } + ~this() { assert(i == 1337); } + } + Array!S arr; + S s; + arr ~= s; + arr ~= s; +} + +@safe unittest //11459 +{ + static struct S + { + bool b; + alias b this; + } + alias A = Array!S; + alias B = Array!(shared bool); +} + +@system unittest //11884 +{ + import std.algorithm.iteration : filter; + auto a = Array!int([1, 2, 2].filter!"true"()); +} + +@safe unittest //8282 +{ + auto arr = new Array!int; +} + +@system unittest //6998 +{ + static int i = 0; + class C + { + int dummy = 1; + this(){++i;} + ~this(){--i;} + } + + assert(i == 0); + auto c = new C(); + assert(i == 1); + + //scope + { + auto arr = Array!C(c); + assert(i == 1); + } + //Array should not have destroyed the class instance + assert(i == 1); + + //Just to make sure the GC doesn't collect before the above test. + assert(c.dummy == 1); +} +@system unittest //6998-2 +{ + static class C {int i;} + auto c = new C; + c.i = 42; + Array!C a; + a ~= c; + a.clear; + assert(c.i == 42); //fails +} + +@safe unittest +{ + static assert(is(Array!int.Range)); + static assert(is(Array!int.ConstRange)); +} + +@system unittest // const/immutable Array and Ranges +{ + static void test(A, R, E, S)() + { + A a; + R r = a[]; + assert(r.empty); + assert(r.length == 0); + static assert(is(typeof(r.front) == E)); + static assert(is(typeof(r.back) == E)); + static assert(is(typeof(r[0]) == E)); + static assert(is(typeof(r[]) == S)); + static assert(is(typeof(r[0 .. 0]) == S)); + } + + alias A = Array!int; + + test!(A, A.Range, int, A.Range); + test!(A, const A.Range, const int, A.ConstRange); + + test!(const A, A.ConstRange, const int, A.ConstRange); + test!(const A, const A.ConstRange, const int, A.ConstRange); + + test!(immutable A, A.ImmutableRange, immutable int, A.ImmutableRange); + test!(immutable A, const A.ImmutableRange, immutable int, A.ImmutableRange); + test!(immutable A, immutable A.ImmutableRange, immutable int, + A.ImmutableRange); +} + +// ensure @nogc +@nogc @system unittest +{ + Array!int ai; + ai ~= 1; + assert(ai.front == 1); + + ai.reserve(10); + assert(ai.capacity == 10); + + static immutable arr = [1, 2, 3]; + ai.insertBack(arr); +} + + +//////////////////////////////////////////////////////////////////////////////// +// Array!bool +//////////////////////////////////////////////////////////////////////////////// + +/** + * _Array specialized for `bool`. Packs together values efficiently by + * allocating one bit per element. + */ +struct Array(T) +if (is(Unqual!T == bool)) +{ + import std.exception : enforce; + import std.typecons : RefCounted, RefCountedAutoInitialize; + + static immutable uint bitsPerWord = size_t.sizeof * 8; + private static struct Data + { + Array!size_t.Payload _backend; + size_t _length; + } + private RefCounted!(Data, RefCountedAutoInitialize.no) _store; + + private @property ref size_t[] data() + { + assert(_store.refCountedStore.isInitialized); + return _store._backend._payload; + } + + /** + * Defines the array's primary range. + */ + struct Range + { + private Array _outer; + private size_t _a, _b; + /// Range primitives + @property Range save() + { + version (bug4437) + { + return this; + } + else + { + auto copy = this; + return copy; + } + } + /// Ditto + @property bool empty() + { + return _a >= _b || _outer.length < _b; + } + /// Ditto + @property T front() + { + enforce(!empty, "Attempting to access the front of an empty Array"); + return _outer[_a]; + } + /// Ditto + @property void front(bool value) + { + enforce(!empty, "Attempting to set the front of an empty Array"); + _outer[_a] = value; + } + /// Ditto + T moveFront() + { + enforce(!empty, "Attempting to move the front of an empty Array"); + return _outer.moveAt(_a); + } + /// Ditto + void popFront() + { + enforce(!empty, "Attempting to popFront an empty Array"); + ++_a; + } + /// Ditto + @property T back() + { + enforce(!empty, "Attempting to access the back of an empty Array"); + return _outer[_b - 1]; + } + /// Ditto + @property void back(bool value) + { + enforce(!empty, "Attempting to set the back of an empty Array"); + _outer[_b - 1] = value; + } + /// Ditto + T moveBack() + { + enforce(!empty, "Attempting to move the back of an empty Array"); + return _outer.moveAt(_b - 1); + } + /// Ditto + void popBack() + { + enforce(!empty, "Attempting to popBack an empty Array"); + --_b; + } + /// Ditto + T opIndex(size_t i) + { + return _outer[_a + i]; + } + /// Ditto + void opIndexAssign(T value, size_t i) + { + _outer[_a + i] = value; + } + /// Ditto + T moveAt(size_t i) + { + return _outer.moveAt(_a + i); + } + /// Ditto + @property size_t length() const + { + assert(_a <= _b); + return _b - _a; + } + alias opDollar = length; + /// ditto + Range opSlice(size_t low, size_t high) + { + assert( + _a <= low && low <= high && high <= _b, + "Using out of bounds indexes on an Array" + ); + return Range(_outer, _a + low, _a + high); + } + } + + /** + * Property returning `true` if and only if the array has + * no elements. + * + * Complexity: $(BIGOH 1) + */ + @property bool empty() + { + return !length; + } + + /** + * Returns: A duplicate of the array. + * + * Complexity: $(BIGOH length). + */ + @property Array dup() + { + Array result; + result.insertBack(this[]); + return result; + } + + /** + * Returns the number of elements in the array. + * + * Complexity: $(BIGOH 1). + */ + @property size_t length() const + { + return _store.refCountedStore.isInitialized ? _store._length : 0; + } + size_t opDollar() const + { + return length; + } + + /** + * Returns: The maximum number of elements the array can store without + * reallocating memory and invalidating iterators upon insertion. + * + * Complexity: $(BIGOH 1). + */ + @property size_t capacity() + { + return _store.refCountedStore.isInitialized + ? cast(size_t) bitsPerWord * _store._backend.capacity + : 0; + } + + /** + * Ensures sufficient capacity to accommodate `e` _elements. + * If `e < capacity`, this method does nothing. + * + * Postcondition: `capacity >= e` + * + * Note: If the capacity is increased, one should assume that all + * iterators to the elements are invalidated. + * + * Complexity: at most $(BIGOH length) if `e > capacity`, otherwise $(BIGOH 1). + */ + void reserve(size_t e) + { + import std.conv : to; + _store.refCountedStore.ensureInitialized(); + _store._backend.reserve(to!size_t((e + bitsPerWord - 1) / bitsPerWord)); + } + + /** + * Returns: A range that iterates over all elements of the array in forward order. + * + * Complexity: $(BIGOH 1) + */ + Range opSlice() + { + return Range(this, 0, length); + } + + + /** + * Returns: A range that iterates the array between two specified positions. + * + * Complexity: $(BIGOH 1) + */ + Range opSlice(size_t a, size_t b) + { + enforce(a <= b && b <= length); + return Range(this, a, b); + } + + /** + * Returns: The first element of the array. + * + * Precondition: `empty == false` + * + * Complexity: $(BIGOH 1) + * + * Throws: `Exception` if the array is empty. + */ + @property bool front() + { + enforce(!empty); + return data.ptr[0] & 1; + } + + /// Ditto + @property void front(bool value) + { + enforce(!empty); + if (value) data.ptr[0] |= 1; + else data.ptr[0] &= ~cast(size_t) 1; + } + + /** + * Returns: The last element of the array. + * + * Precondition: `empty == false` + * + * Complexity: $(BIGOH 1) + * + * Throws: `Exception` if the array is empty. + */ + @property bool back() + { + enforce(!empty); + return cast(bool)(data.back & (cast(size_t) 1 << ((_store._length - 1) % bitsPerWord))); + } + + /// Ditto + @property void back(bool value) + { + enforce(!empty); + if (value) + { + data.back |= (cast(size_t) 1 << ((_store._length - 1) % bitsPerWord)); + } + else + { + data.back &= + ~(cast(size_t) 1 << ((_store._length - 1) % bitsPerWord)); + } + } + + /** + * Indexing operators yielding or modifyng the value at the specified index. + * + * Precondition: `i < length` + * + * Complexity: $(BIGOH 1) + */ + bool opIndex(size_t i) + { + auto div = cast(size_t) (i / bitsPerWord); + auto rem = i % bitsPerWord; + enforce(div < data.length); + return cast(bool)(data.ptr[div] & (cast(size_t) 1 << rem)); + } + + /// ditto + void opIndexAssign(bool value, size_t i) + { + auto div = cast(size_t) (i / bitsPerWord); + auto rem = i % bitsPerWord; + enforce(div < data.length); + if (value) data.ptr[div] |= (cast(size_t) 1 << rem); + else data.ptr[div] &= ~(cast(size_t) 1 << rem); + } + + /// ditto + void opIndexOpAssign(string op)(bool value, size_t i) + { + auto div = cast(size_t) (i / bitsPerWord); + auto rem = i % bitsPerWord; + enforce(div < data.length); + auto oldValue = cast(bool) (data.ptr[div] & (cast(size_t) 1 << rem)); + // Do the deed + auto newValue = mixin("oldValue "~op~" value"); + // Write back the value + if (newValue != oldValue) + { + if (newValue) data.ptr[div] |= (cast(size_t) 1 << rem); + else data.ptr[div] &= ~(cast(size_t) 1 << rem); + } + } + + /// Ditto + T moveAt(size_t i) + { + return this[i]; + } + + /** + * Returns: A new array which is a concatenation of `this` and its argument. + * + * Complexity: + * $(BIGOH length + m), where `m` is the number of elements in `stuff`. + */ + Array!bool opBinary(string op, Stuff)(Stuff rhs) + if (op == "~") + { + Array!bool result; + + static if (hasLength!Stuff) + result.reserve(length + rhs.length); + else static if (is(typeof(rhs[])) && hasLength!(typeof(rhs[]))) + result.reserve(length + rhs[].length); + else static if (isImplicitlyConvertible!(Stuff, bool)) + result.reserve(length + 1); + + result.insertBack(this[]); + result ~= rhs; + return result; + } + + /** + * Forwards to `insertBack`. + */ + Array!bool opOpAssign(string op, Stuff)(Stuff stuff) + if (op == "~") + { + static if (is(typeof(stuff[]))) insertBack(stuff[]); + else insertBack(stuff); + return this; + } + + /** + * Removes all the elements from the array and releases allocated memory. + * + * Postcondition: `empty == true && capacity == 0` + * + * Complexity: $(BIGOH length) + */ + void clear() + { + this = Array(); + } + + /** + * Sets the number of elements in the array to `newLength`. If `newLength` + * is greater than `length`, the new elements are added to the end of the + * array and initialized with `false`. + * + * Complexity: + * Guaranteed $(BIGOH abs(length - newLength)) if `capacity >= newLength`. + * If `capacity < newLength` the worst case is $(BIGOH newLength). + * + * Postcondition: `length == newLength` + */ + @property void length(size_t newLength) + { + import std.conv : to; + _store.refCountedStore.ensureInitialized(); + auto newDataLength = + to!size_t((newLength + bitsPerWord - 1) / bitsPerWord); + _store._backend.length = newDataLength; + _store._length = newLength; + } + + /** + * Removes the last element from the array and returns it. + * Both stable and non-stable versions behave the same and guarantee + * that ranges iterating over the array are never invalidated. + * + * Precondition: `empty == false` + * + * Returns: The element removed. + * + * Complexity: $(BIGOH 1). + * + * Throws: `Exception` if the array is empty. + */ + T removeAny() + { + auto result = back; + removeBack(); + return result; + } + + /// ditto + alias stableRemoveAny = removeAny; + + /** + * Inserts the specified elements at the back of the array. `stuff` can be + * a value convertible to `bool` or a range of objects convertible to `bool`. + * + * Returns: The number of elements inserted. + * + * Complexity: + * $(BIGOH length + m) if reallocation takes place, otherwise $(BIGOH m), + * where `m` is the number of elements in `stuff`. + */ + size_t insertBack(Stuff)(Stuff stuff) + if (is(Stuff : bool)) + { + _store.refCountedStore.ensureInitialized(); + auto rem = _store._length % bitsPerWord; + if (rem) + { + // Fits within the current array + if (stuff) + { + data[$ - 1] |= (cast(size_t) 1 << rem); + } + else + { + data[$ - 1] &= ~(cast(size_t) 1 << rem); + } + } + else + { + // Need to add more data + _store._backend.insertBack(stuff); + } + ++_store._length; + return 1; + } + + /// ditto + size_t insertBack(Stuff)(Stuff stuff) + if (isInputRange!Stuff && is(ElementType!Stuff : bool)) + { + static if (!hasLength!Stuff) size_t result; + for (; !stuff.empty; stuff.popFront()) + { + insertBack(stuff.front); + static if (!hasLength!Stuff) ++result; + } + static if (!hasLength!Stuff) return result; + else return stuff.length; + } + + /// ditto + alias stableInsertBack = insertBack; + + /// ditto + alias insert = insertBack; + + /// ditto + alias stableInsert = insertBack; + + /// ditto + alias linearInsert = insertBack; + + /// ditto + alias stableLinearInsert = insertBack; + + /** + * Removes the value from the back of the array. Both stable and non-stable + * versions behave the same and guarantee that ranges iterating over the + * array are never invalidated. + * + * Precondition: `empty == false` + * + * Complexity: $(BIGOH 1). + * + * Throws: `Exception` if the array is empty. + */ + void removeBack() + { + enforce(_store._length); + if (_store._length % bitsPerWord) + { + // Cool, just decrease the length + --_store._length; + } + else + { + // Reduce the allocated space + --_store._length; + _store._backend.length = _store._backend.length - 1; + } + } + + /// ditto + alias stableRemoveBack = removeBack; + + /** + * Removes `howMany` values from the back of the array. Unlike the + * unparameterized versions above, these functions do not throw if + * they could not remove `howMany` elements. Instead, if `howMany > n`, + * all elements are removed. The returned value is the effective number + * of elements removed. Both stable and non-stable versions behave the same + * and guarantee that ranges iterating over the array are never invalidated. + * + * Returns: The number of elements removed. + * + * Complexity: $(BIGOH howMany). + */ + size_t removeBack(size_t howMany) + { + if (howMany >= length) + { + howMany = length; + clear(); + } + else + { + length = length - howMany; + } + return howMany; + } + + /// ditto + alias stableRemoveBack = removeBack; + + /** + * Inserts `stuff` before, after, or instead range `r`, which must + * be a valid range previously extracted from this array. `stuff` + * can be a value convertible to `bool` or a range of objects convertible + * to `bool`. Both stable and non-stable version behave the same and + * guarantee that ranges iterating over the array are never invalidated. + * + * Returns: The number of values inserted. + * + * Complexity: $(BIGOH length + m), where `m` is the length of `stuff`. + */ + size_t insertBefore(Stuff)(Range r, Stuff stuff) + { + import std.algorithm.mutation : bringToFront; + // TODO: make this faster, it moves one bit at a time + immutable inserted = stableInsertBack(stuff); + immutable tailLength = length - inserted; + bringToFront( + this[r._a .. tailLength], + this[tailLength .. length]); + return inserted; + } + + /// ditto + alias stableInsertBefore = insertBefore; + + /// ditto + size_t insertAfter(Stuff)(Range r, Stuff stuff) + { + import std.algorithm.mutation : bringToFront; + // TODO: make this faster, it moves one bit at a time + immutable inserted = stableInsertBack(stuff); + immutable tailLength = length - inserted; + bringToFront( + this[r._b .. tailLength], + this[tailLength .. length]); + return inserted; + } + + /// ditto + alias stableInsertAfter = insertAfter; + + /// ditto + size_t replace(Stuff)(Range r, Stuff stuff) + if (is(Stuff : bool)) + { + if (!r.empty) + { + // There is room + r.front = stuff; + r.popFront(); + linearRemove(r); + } + else + { + // No room, must insert + insertBefore(r, stuff); + } + return 1; + } + + /// ditto + alias stableReplace = replace; + + /** + * Removes all elements belonging to `r`, which must be a range + * obtained originally from this array. + * + * Returns: A range spanning the remaining elements in the array that + * initially were right after `r`. + * + * Complexity: $(BIGOH length) + */ + Range linearRemove(Range r) + { + import std.algorithm.mutation : copy; + copy(this[r._b .. length], this[r._a .. length]); + length = length - r.length; + return this[r._a .. length]; + } +} + +@system unittest +{ + Array!bool a; + assert(a.empty); +} + +@system unittest +{ + Array!bool arr; + arr.insert([false, false, false, false]); + assert(arr.front == false); + assert(arr.back == false); + assert(arr[1] == false); + auto slice = arr[]; + slice = arr[0 .. $]; + slice = slice[1 .. $]; + slice.front = true; + slice.back = true; + slice[1] = true; + assert(slice.front == true); + assert(slice.back == true); + assert(slice[1] == true); + assert(slice.moveFront == true); + assert(slice.moveBack == true); + assert(slice.moveAt(1) == true); +} + +// issue 16331 - uncomparable values are valid values for an array +@system unittest +{ + double[] values = [double.nan, double.nan]; + auto arr = Array!double(values); +} + +@nogc @system unittest +{ + auto a = Array!int(0, 1, 2); + int[3] b = [3, 4, 5]; + short[3] ci = [0, 1, 0]; + auto c = Array!short(ci); + assert(Array!int(0, 1, 2, 0, 1, 2) == a ~ a); + assert(Array!int(0, 1, 2, 3, 4, 5) == a ~ b); + assert(Array!int(0, 1, 2, 3) == a ~ 3); + assert(Array!int(0, 1, 2, 0, 1, 0) == a ~ c); +} + +@nogc @system unittest +{ + auto a = Array!char('a', 'b'); + assert(Array!char("abc") == a ~ 'c'); + import std.utf : byCodeUnit; + assert(Array!char("abcd") == a ~ "cd".byCodeUnit); +} + +@nogc @system unittest +{ + auto a = Array!dchar("ąćę"d); + assert(Array!dchar("ąćęϢϖ"d) == a ~ "Ϣϖ"d); + wchar x = 'Ϣ'; + assert(Array!dchar("ąćęϢz"d) == a ~ x ~ 'z'); +} + +@system unittest +{ + Array!bool a; + assert(a.empty); + a.insertBack(false); + assert(!a.empty); +} + +@system unittest +{ + Array!bool a; + assert(a.empty); + auto b = a.dup; + assert(b.empty); + a.insertBack(true); + assert(b.empty); +} + +@system unittest +{ + import std.conv : to; + Array!bool a; + assert(a.length == 0); + a.insert(true); + assert(a.length == 1, to!string(a.length)); +} + +@system unittest +{ + import std.conv : to; + Array!bool a; + assert(a.capacity == 0); + foreach (i; 0 .. 100) + { + a.insert(true); + assert(a.capacity >= a.length, to!string(a.capacity)); + } +} + +@system unittest +{ + Array!bool a; + assert(a.capacity == 0); + a.reserve(15657); + assert(a.capacity >= 15657); + a.reserve(100); + assert(a.capacity >= 15657); +} + +@system unittest +{ + Array!bool a; + a.insertBack([true, false, true, true]); + assert(a[0 .. 2].length == 2); +} + +@system unittest +{ + Array!bool a; + a.insertBack([true, false, true, true]); + assert(a[].length == 4); +} + +@system unittest +{ + Array!bool a; + a.insertBack([true, false, true, true]); + assert(a.front); + a.front = false; + assert(!a.front); +} + +@system unittest +{ + Array!bool a; + a.insertBack([true, false, true, true]); + assert(a[].length == 4); +} + +@system unittest +{ + Array!bool a; + a.insertBack([true, false, true, true]); + assert(a.back); + a.back = false; + assert(!a.back); +} + +@system unittest +{ + Array!bool a; + a.insertBack([true, false, true, true]); + assert(a[0] && !a[1]); + a[0] &= a[1]; + assert(!a[0]); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + Array!bool a; + a.insertBack([true, false, true, true]); + Array!bool b; + b.insertBack([true, true, false, true]); + assert(equal((a ~ b)[], + [true, false, true, true, true, true, false, true])); + assert((a ~ [true, false])[].equal([true, false, true, true, true, false])); + Array!bool c; + c.insertBack(true); + assert((c ~ false)[].equal([true, false])); +} +@system unittest +{ + import std.algorithm.comparison : equal; + Array!bool a; + a.insertBack([true, false, true, true]); + Array!bool b; + a.insertBack([false, true, false, true, true]); + a ~= b; + assert(equal( + a[], + [true, false, true, true, false, true, false, true, true])); +} + +@system unittest +{ + Array!bool a; + a.insertBack([true, false, true, true]); + a.clear(); + assert(a.capacity == 0); +} + +@system unittest +{ + Array!bool a; + a.length = 1057; + assert(a.length == 1057); + assert(a.capacity >= a.length); + foreach (e; a) + { + assert(!e); + } + immutable cap = a.capacity; + a.length = 100; + assert(a.length == 100); + // do not realloc if length decreases + assert(a.capacity == cap); +} + +@system unittest +{ + Array!bool a; + a.length = 1057; + assert(!a.removeAny()); + assert(a.length == 1056); + foreach (e; a) + { + assert(!e); + } +} + +@system unittest +{ + Array!bool a; + for (int i = 0; i < 100; ++i) + a.insertBack(true); + foreach (e; a) + assert(e); +} + +@system unittest +{ + Array!bool a; + a.length = 1057; + assert(a.removeBack(1000) == 1000); + assert(a.length == 57); + foreach (e; a) + { + assert(!e); + } +} + +@system unittest +{ + import std.conv : to; + Array!bool a; + version (bugxxxx) + { + a._store.refCountedDebug = true; + } + a.insertBefore(a[], true); + assert(a.length == 1, to!string(a.length)); + a.insertBefore(a[], false); + assert(a.length == 2, to!string(a.length)); + a.insertBefore(a[1 .. $], true); + import std.algorithm.comparison : equal; + assert(a[].equal([false, true, true])); +} + +@system unittest +{ + import std.conv : to; + Array!bool a; + a.length = 10; + a.insertAfter(a[0 .. 5], true); + assert(a.length == 11, to!string(a.length)); + assert(a[5]); +} +@system unittest +{ + alias V3 = int[3]; + V3 v = [1, 2, 3]; + Array!V3 arr; + arr ~= v; + assert(arr[0] == [1, 2, 3]); +} +@system unittest +{ + alias V3 = int[3]; + V3[2] v = [[1, 2, 3], [4, 5, 6]]; + Array!V3 arr; + arr ~= v; + assert(arr[0] == [1, 2, 3]); + assert(arr[1] == [4, 5, 6]); +} diff --git a/libphobos/src/std/container/binaryheap.d b/libphobos/src/std/container/binaryheap.d new file mode 100644 index 0000000..4adf604 --- /dev/null +++ b/libphobos/src/std/container/binaryheap.d @@ -0,0 +1,595 @@ +/** +This module provides a $(D BinaryHeap) (aka priority queue) +adaptor that makes a binary heap out of any user-provided random-access range. + +This module is a submodule of $(MREF std, container). + +Source: $(PHOBOSSRC std/container/_binaryheap.d) + +Copyright: 2010- Andrei Alexandrescu. All rights reserved by the respective holders. + +License: Distributed under the Boost Software License, Version 1.0. +(See accompanying file LICENSE_1_0.txt or copy at $(HTTP +boost.org/LICENSE_1_0.txt)). + +Authors: $(HTTP erdani.com, Andrei Alexandrescu) +*/ +module std.container.binaryheap; + +import std.range.primitives; +import std.traits; + +public import std.container.util; + +/// +@system unittest +{ + import std.algorithm.comparison : equal; + import std.range : take; + auto maxHeap = heapify([4, 7, 3, 1, 5]); + assert(maxHeap.take(3).equal([7, 5, 4])); + + auto minHeap = heapify!"a > b"([4, 7, 3, 1, 5]); + assert(minHeap.take(3).equal([1, 3, 4])); +} + +// BinaryHeap +/** +Implements a $(HTTP en.wikipedia.org/wiki/Binary_heap, binary heap) +container on top of a given random-access range type (usually $(D +T[])) or a random-access container type (usually $(D Array!T)). The +documentation of $(D BinaryHeap) will refer to the underlying range or +container as the $(I store) of the heap. + +The binary heap induces structure over the underlying store such that +accessing the largest element (by using the $(D front) property) is a +$(BIGOH 1) operation and extracting it (by using the $(D +removeFront()) method) is done fast in $(BIGOH log n) time. + +If $(D less) is the less-than operator, which is the default option, +then $(D BinaryHeap) defines a so-called max-heap that optimizes +extraction of the $(I largest) elements. To define a min-heap, +instantiate BinaryHeap with $(D "a > b") as its predicate. + +Simply extracting elements from a $(D BinaryHeap) container is +tantamount to lazily fetching elements of $(D Store) in descending +order. Extracting elements from the $(D BinaryHeap) to completion +leaves the underlying store sorted in ascending order but, again, +yields elements in descending order. + +If $(D Store) is a range, the $(D BinaryHeap) cannot grow beyond the +size of that range. If $(D Store) is a container that supports $(D +insertBack), the $(D BinaryHeap) may grow by adding elements to the +container. + */ +struct BinaryHeap(Store, alias less = "a < b") +if (isRandomAccessRange!(Store) || isRandomAccessRange!(typeof(Store.init[]))) +{ + import std.algorithm.comparison : min; + import std.algorithm.mutation : move, swapAt; + import std.algorithm.sorting : HeapOps; + import std.exception : enforce; + import std.functional : binaryFun; + import std.typecons : RefCounted, RefCountedAutoInitialize; + + static if (isRandomAccessRange!Store) + alias Range = Store; + else + alias Range = typeof(Store.init[]); + alias percolate = HeapOps!(less, Range).percolate; + alias buildHeap = HeapOps!(less, Range).buildHeap; + +// Really weird @@BUG@@: if you comment out the "private:" label below, +// std.algorithm can't unittest anymore +//private: + + // The payload includes the support store and the effective length + private static struct Data + { + Store _store; + size_t _length; + } + private RefCounted!(Data, RefCountedAutoInitialize.no) _payload; + // Comparison predicate + private alias comp = binaryFun!(less); + // Convenience accessors + private @property ref Store _store() + { + assert(_payload.refCountedStore.isInitialized); + return _payload._store; + } + private @property ref size_t _length() + { + assert(_payload.refCountedStore.isInitialized); + return _payload._length; + } + + // Asserts that the heap property is respected. + private void assertValid() + { + debug + { + import std.conv : to; + if (!_payload.refCountedStore.isInitialized) return; + if (_length < 2) return; + for (size_t n = _length - 1; n >= 1; --n) + { + auto parentIdx = (n - 1) / 2; + assert(!comp(_store[parentIdx], _store[n]), to!string(n)); + } + } + } + + // @@@BUG@@@: add private here, std.algorithm doesn't unittest anymore + /*private*/ void pop(Store store) + { + assert(!store.empty, "Cannot pop an empty store."); + if (store.length == 1) return; + auto t1 = store[].moveFront(); + auto t2 = store[].moveBack(); + store.front = move(t2); + store.back = move(t1); + percolate(store[], 0, store.length - 1); + } + +public: + + /** + Converts the store $(D s) into a heap. If $(D initialSize) is + specified, only the first $(D initialSize) elements in $(D s) + are transformed into a heap, after which the heap can grow up + to $(D r.length) (if $(D Store) is a range) or indefinitely (if + $(D Store) is a container with $(D insertBack)). Performs + $(BIGOH min(r.length, initialSize)) evaluations of $(D less). + */ + this(Store s, size_t initialSize = size_t.max) + { + acquire(s, initialSize); + } + +/** +Takes ownership of a store. After this, manipulating $(D s) may make +the heap work incorrectly. + */ + void acquire(Store s, size_t initialSize = size_t.max) + { + _payload.refCountedStore.ensureInitialized(); + _store = move(s); + _length = min(_store.length, initialSize); + if (_length < 2) return; + buildHeap(_store[]); + assertValid(); + } + +/** +Takes ownership of a store assuming it already was organized as a +heap. + */ + void assume(Store s, size_t initialSize = size_t.max) + { + _payload.refCountedStore.ensureInitialized(); + _store = s; + _length = min(_store.length, initialSize); + assertValid(); + } + +/** +Clears the heap. Returns the portion of the store from $(D 0) up to +$(D length), which satisfies the $(LINK2 https://en.wikipedia.org/wiki/Heap_(data_structure), +heap property). + */ + auto release() + { + if (!_payload.refCountedStore.isInitialized) + { + return typeof(_store[0 .. _length]).init; + } + assertValid(); + auto result = _store[0 .. _length]; + _payload = _payload.init; + return result; + } + +/** +Returns $(D true) if the heap is _empty, $(D false) otherwise. + */ + @property bool empty() + { + return !length; + } + +/** +Returns a duplicate of the heap. The $(D dup) method is available only if the +underlying store supports it. + */ + static if (is(typeof((Store s) { return s.dup; }(Store.init)) == Store)) + { + @property BinaryHeap dup() + { + BinaryHeap result; + if (!_payload.refCountedStore.isInitialized) return result; + result.assume(_store.dup, length); + return result; + } + } + +/** +Returns the _length of the heap. + */ + @property size_t length() + { + return _payload.refCountedStore.isInitialized ? _length : 0; + } + +/** +Returns the _capacity of the heap, which is the length of the +underlying store (if the store is a range) or the _capacity of the +underlying store (if the store is a container). + */ + @property size_t capacity() + { + if (!_payload.refCountedStore.isInitialized) return 0; + static if (is(typeof(_store.capacity) : size_t)) + { + return _store.capacity; + } + else + { + return _store.length; + } + } + +/** +Returns a copy of the _front of the heap, which is the largest element +according to $(D less). + */ + @property ElementType!Store front() + { + enforce(!empty, "Cannot call front on an empty heap."); + return _store.front; + } + +/** +Clears the heap by detaching it from the underlying store. + */ + void clear() + { + _payload = _payload.init; + } + +/** +Inserts $(D value) into the store. If the underlying store is a range +and $(D length == capacity), throws an exception. + */ + size_t insert(ElementType!Store value) + { + static if (is(typeof(_store.insertBack(value)))) + { + _payload.refCountedStore.ensureInitialized(); + if (length == _store.length) + { + // reallocate + _store.insertBack(value); + } + else + { + // no reallocation + _store[_length] = value; + } + } + else + { + import std.traits : isDynamicArray; + static if (isDynamicArray!Store) + { + if (length == _store.length) + _store.length = (length < 6 ? 8 : length * 3 / 2); + _store[_length] = value; + } + else + { + // can't grow + enforce(length < _store.length, + "Cannot grow a heap created over a range"); + } + } + + // sink down the element + for (size_t n = _length; n; ) + { + auto parentIdx = (n - 1) / 2; + if (!comp(_store[parentIdx], _store[n])) break; // done! + // must swap and continue + _store.swapAt(parentIdx, n); + n = parentIdx; + } + ++_length; + debug(BinaryHeap) assertValid(); + return 1; + } + +/** +Removes the largest element from the heap. + */ + void removeFront() + { + enforce(!empty, "Cannot call removeFront on an empty heap."); + if (_length > 1) + { + auto t1 = _store[].moveFront(); + auto t2 = _store[].moveAt(_length - 1); + _store.front = move(t2); + _store[_length - 1] = move(t1); + } + --_length; + percolate(_store[], 0, _length); + } + + /// ditto + alias popFront = removeFront; + +/** +Removes the largest element from the heap and returns a copy of +it. The element still resides in the heap's store. For performance +reasons you may want to use $(D removeFront) with heaps of objects +that are expensive to copy. + */ + ElementType!Store removeAny() + { + removeFront(); + return _store[_length]; + } + +/** +Replaces the largest element in the store with $(D value). + */ + void replaceFront(ElementType!Store value) + { + // must replace the top + assert(!empty, "Cannot call replaceFront on an empty heap."); + _store.front = value; + percolate(_store[], 0, _length); + debug(BinaryHeap) assertValid(); + } + +/** +If the heap has room to grow, inserts $(D value) into the store and +returns $(D true). Otherwise, if $(D less(value, front)), calls $(D +replaceFront(value)) and returns again $(D true). Otherwise, leaves +the heap unaffected and returns $(D false). This method is useful in +scenarios where the smallest $(D k) elements of a set of candidates +must be collected. + */ + bool conditionalInsert(ElementType!Store value) + { + _payload.refCountedStore.ensureInitialized(); + if (_length < _store.length) + { + insert(value); + return true; + } + + assert(!_store.empty, "Cannot replace front of an empty heap."); + if (!comp(value, _store.front)) return false; // value >= largest + _store.front = value; + + percolate(_store[], 0, _length); + debug(BinaryHeap) assertValid(); + return true; + } + +/** +Swapping is allowed if the heap is full. If $(D less(value, front)), the +method exchanges store.front and value and returns $(D true). Otherwise, it +leaves the heap unaffected and returns $(D false). + */ + bool conditionalSwap(ref ElementType!Store value) + { + _payload.refCountedStore.ensureInitialized(); + assert(_length == _store.length); + assert(!_store.empty, "Cannot swap front of an empty heap."); + + if (!comp(value, _store.front)) return false; // value >= largest + + import std.algorithm.mutation : swap; + swap(_store.front, value); + + percolate(_store[], 0, _length); + debug(BinaryHeap) assertValid(); + + return true; + } +} + +/// Example from "Introduction to Algorithms" Cormen et al, p 146 +@system unittest +{ + import std.algorithm.comparison : equal; + int[] a = [ 4, 1, 3, 2, 16, 9, 10, 14, 8, 7 ]; + auto h = heapify(a); + // largest element + assert(h.front == 16); + // a has the heap property + assert(equal(a, [ 16, 14, 10, 8, 7, 9, 3, 2, 4, 1 ])); +} + +/// $(D BinaryHeap) implements the standard input range interface, allowing +/// lazy iteration of the underlying range in descending order. +@system unittest +{ + import std.algorithm.comparison : equal; + import std.range : take; + int[] a = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]; + auto top5 = heapify(a).take(5); + assert(top5.equal([16, 14, 10, 9, 8])); +} + +/** +Convenience function that returns a $(D BinaryHeap!Store) object +initialized with $(D s) and $(D initialSize). + */ +BinaryHeap!(Store, less) heapify(alias less = "a < b", Store)(Store s, + size_t initialSize = size_t.max) +{ + + return BinaryHeap!(Store, less)(s, initialSize); +} + +/// +@system unittest +{ + import std.conv : to; + import std.range.primitives; + { + // example from "Introduction to Algorithms" Cormen et al., p 146 + int[] a = [ 4, 1, 3, 2, 16, 9, 10, 14, 8, 7 ]; + auto h = heapify(a); + h = heapify!"a < b"(a); + assert(h.front == 16); + assert(a == [ 16, 14, 10, 8, 7, 9, 3, 2, 4, 1 ]); + auto witness = [ 16, 14, 10, 9, 8, 7, 4, 3, 2, 1 ]; + for (; !h.empty; h.removeFront(), witness.popFront()) + { + assert(!witness.empty); + assert(witness.front == h.front); + } + assert(witness.empty); + } + { + int[] a = [ 4, 1, 3, 2, 16, 9, 10, 14, 8, 7 ]; + int[] b = new int[a.length]; + BinaryHeap!(int[]) h = BinaryHeap!(int[])(b, 0); + foreach (e; a) + { + h.insert(e); + } + assert(b == [ 16, 14, 10, 8, 7, 3, 9, 1, 4, 2 ], to!string(b)); + } +} + +@system unittest +{ + // Test range interface. + import std.algorithm.comparison : equal; + int[] a = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]; + auto h = heapify(a); + static assert(isInputRange!(typeof(h))); + assert(h.equal([16, 14, 10, 9, 8, 7, 4, 3, 2, 1])); +} + +@system unittest // 15675 +{ + import std.container.array : Array; + + Array!int elements = [1, 2, 10, 12]; + auto heap = heapify(elements); + assert(heap.front == 12); +} + +@system unittest // 16072 +{ + auto q = heapify!"a > b"([2, 4, 5]); + q.insert(1); + q.insert(6); + assert(q.front == 1); + + // test more multiple grows + int[] arr; + auto r = heapify!"a < b"(arr); + foreach (i; 0 .. 100) + r.insert(i); + + assert(r.front == 99); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + int[] a = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]; + auto heap = heapify(a); + auto dup = heap.dup(); + assert(dup.equal([16, 14, 10, 9, 8, 7, 4, 3, 2, 1])); +} + +@safe unittest +{ + static struct StructWithoutDup + { + int[] a; + @disable StructWithoutDup dup() + { + StructWithoutDup d; + return d; + } + alias a this; + } + + // Assert Binary heap can be created when Store doesn't have dup + // if dup is not used. + assert(__traits(compiles, () + { + auto s = StructWithoutDup([1,2]); + auto h = heapify(s); + })); + + // Assert dup can't be used on BinaryHeaps when Store doesn't have dup + assert(!__traits(compiles, () + { + auto s = StructWithoutDup([1,2]); + auto h = heapify(s); + h.dup(); + })); +} + +@safe unittest +{ + static struct StructWithDup + { + int[] a; + StructWithDup dup() + { + StructWithDup d; + return d; + } + alias a this; + } + + // Assert dup can be used on BinaryHeaps when Store has dup + assert(__traits(compiles, () + { + auto s = StructWithDup([1, 2]); + auto h = heapify(s); + h.dup(); + })); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange; + + alias RefRange = DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random); + + RefRange a; + RefRange b; + a.reinit(); + b.reinit(); + + auto heap = heapify(a); + foreach (ref elem; b) + { + heap.conditionalSwap(elem); + } + + assert(equal(heap, [ 5, 5, 4, 4, 3, 3, 2, 2, 1, 1])); + assert(equal(b, [10, 9, 8, 7, 6, 6, 7, 8, 9, 10])); +} + +@system unittest // Issue 17314 +{ + import std.algorithm.comparison : equal; + int[] a = [5]; + auto heap = heapify(a); + heap.insert(6); + assert(equal(heap, [6, 5])); +} diff --git a/libphobos/src/std/container/dlist.d b/libphobos/src/std/container/dlist.d new file mode 100644 index 0000000..633371f --- /dev/null +++ b/libphobos/src/std/container/dlist.d @@ -0,0 +1,1039 @@ +/** +This module implements a generic doubly-linked list container. +It can be used as a queue, dequeue or stack. + +This module is a submodule of $(MREF std, container). + +Source: $(PHOBOSSRC std/container/_dlist.d) + +Copyright: 2010- Andrei Alexandrescu. All rights reserved by the respective holders. + +License: Distributed under the Boost Software License, Version 1.0. +(See accompanying file LICENSE_1_0.txt or copy at $(HTTP +boost.org/LICENSE_1_0.txt)). + +Authors: $(HTTP erdani.com, Andrei Alexandrescu) + +$(SCRIPT inhibitQuickIndex = 1;) +*/ +module std.container.dlist; + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.container : DList; + + auto s = DList!int(1, 2, 3); + assert(equal(s[], [1, 2, 3])); + + s.removeFront(); + assert(equal(s[], [2, 3])); + s.removeBack(); + assert(equal(s[], [2])); + + s.insertFront([4, 5]); + assert(equal(s[], [4, 5, 2])); + s.insertBack([6, 7]); + assert(equal(s[], [4, 5, 2, 6, 7])); + + // If you want to apply range operations, simply slice it. + import std.algorithm.searching : countUntil; + import std.range : popFrontN, popBackN, walkLength; + + auto sl = DList!int([1, 2, 3, 4, 5]); + assert(countUntil(sl[], 2) == 1); + + auto r = sl[]; + popFrontN(r, 2); + popBackN(r, 2); + assert(r.equal([3])); + assert(walkLength(r) == 1); + + // DList.Range can be used to remove elements from the list it spans + auto nl = DList!int([1, 2, 3, 4, 5]); + for (auto rn = nl[]; !rn.empty;) + if (rn.front % 2 == 0) + nl.popFirstOf(rn); + else + rn.popFront(); + assert(equal(nl[], [1, 3, 5])); + auto rs = nl[]; + rs.popFront(); + nl.remove(rs); + assert(equal(nl[], [1])); +} + +import std.range.primitives; +import std.traits; + +public import std.container.util; + +/+ +A DList Node without payload. Used to handle the sentinel node (henceforth "sentinode"). + +Also used for parts of the code that don't depend on the payload type. + +/ +private struct BaseNode +{ + private BaseNode* _prev = null; + private BaseNode* _next = null; + + /+ + Gets the payload associated with this node. + This is trusted because all nodes are associated with a payload, even + the sentinel node. + It is also not possible to mix Nodes in DLists of different types. + This function is implemented as a member function here, as UFCS does not + work with pointers. + +/ + ref inout(T) getPayload(T)() inout @trusted + { + return (cast(inout(DList!T.PayNode)*)&this)._payload; + } + + // Helper: Given nodes p and n, connects them. + static void connect(BaseNode* p, BaseNode* n) @safe nothrow pure + { + p._next = n; + n._prev = p; + } +} + +/+ +The base DList Range. Contains Range primitives that don't depend on payload type. + +/ +private struct DRange +{ + @safe unittest + { + static assert(isBidirectionalRange!DRange); + static assert(is(ElementType!DRange == BaseNode*)); + } + +nothrow @safe pure: + private BaseNode* _first; + private BaseNode* _last; + + private this(BaseNode* first, BaseNode* last) + { + assert((first is null) == (last is null), "Dlist.Range.this: Invalid arguments"); + _first = first; + _last = last; + } + private this(BaseNode* n) + { + this(n, n); + } + + @property + bool empty() const + { + assert((_first is null) == (_last is null), "DList.Range: Invalidated state"); + return !_first; + } + + @property BaseNode* front() + { + assert(!empty, "DList.Range.front: Range is empty"); + return _first; + } + + void popFront() + { + assert(!empty, "DList.Range.popFront: Range is empty"); + if (_first is _last) + { + _first = _last = null; + } + else + { + assert(_first._next && _first is _first._next._prev, "DList.Range: Invalidated state"); + _first = _first._next; + } + } + + @property BaseNode* back() + { + assert(!empty, "DList.Range.front: Range is empty"); + return _last; + } + + void popBack() + { + assert(!empty, "DList.Range.popBack: Range is empty"); + if (_first is _last) + { + _first = _last = null; + } + else + { + assert(_last._prev && _last is _last._prev._next, "DList.Range: Invalidated state"); + _last = _last._prev; + } + } + + /// Forward range primitive. + @property DRange save() { return this; } +} + +/** +Implements a doubly-linked list. + +$(D DList) uses reference semantics. + */ +struct DList(T) +{ + import std.range : Take; + + /* + A Node with a Payload. A PayNode. + */ + struct PayNode + { + BaseNode _base; + alias _base this; + + T _payload = T.init; + + inout(BaseNode)* asBaseNode() inout @trusted + { + return &_base; + } + } + + //The sentinel node + private BaseNode* _root; + + private + { + //Construct as new PayNode, and returns it as a BaseNode. + static BaseNode* createNode(Stuff)(auto ref Stuff arg, BaseNode* prev = null, BaseNode* next = null) + { + return (new PayNode(BaseNode(prev, next), arg)).asBaseNode(); + } + + void initialize() nothrow @safe pure + { + if (_root) return; + //Note: We allocate a PayNode for safety reasons. + _root = (new PayNode()).asBaseNode(); + _root._next = _root._prev = _root; + } + ref inout(BaseNode*) _first() @property @safe nothrow pure inout + { + assert(_root); + return _root._next; + } + ref inout(BaseNode*) _last() @property @safe nothrow pure inout + { + assert(_root); + return _root._prev; + } + } //end private + +/** +Constructor taking a number of nodes + */ + this(U)(U[] values...) if (isImplicitlyConvertible!(U, T)) + { + insertBack(values); + } + +/** +Constructor taking an input range + */ + this(Stuff)(Stuff stuff) + if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) + { + insertBack(stuff); + } + +/** +Comparison for equality. + +Complexity: $(BIGOH min(n, n1)) where $(D n1) is the number of +elements in $(D rhs). + */ + bool opEquals()(ref const DList rhs) const + if (is(typeof(front == front))) + { + const lhs = this; + const lroot = lhs._root; + const rroot = rhs._root; + + if (lroot is rroot) return true; + if (lroot is null) return rroot is rroot._next; + if (rroot is null) return lroot is lroot._next; + + const(BaseNode)* pl = lhs._first; + const(BaseNode)* pr = rhs._first; + while (true) + { + if (pl is lroot) return pr is rroot; + if (pr is rroot) return false; + + // !== because of NaN + if (!(pl.getPayload!T() == pr.getPayload!T())) return false; + + pl = pl._next; + pr = pr._next; + } + } + + /** + Defines the container's primary range, which embodies a bidirectional range. + */ + struct Range + { + static assert(isBidirectionalRange!Range); + + DRange _base; + alias _base this; + + private this(BaseNode* first, BaseNode* last) + { + _base = DRange(first, last); + } + private this(BaseNode* n) + { + this(n, n); + } + + @property ref T front() + { + return _base.front.getPayload!T(); + } + + @property ref T back() + { + return _base.back.getPayload!T(); + } + + //Note: shadows base DRange.save. + //Necessary for static covariance. + @property Range save() { return this; } + } + +/** +Property returning $(D true) if and only if the container has no +elements. + +Complexity: $(BIGOH 1) + */ + bool empty() @property const nothrow + { + return _root is null || _root is _first; + } + +/** +Removes all contents from the $(D DList). + +Postcondition: $(D empty) + +Complexity: $(BIGOH 1) + */ + void clear() + { + //remove actual elements. + remove(this[]); + } + +/** +Duplicates the container. The elements themselves are not transitively +duplicated. + +Complexity: $(BIGOH n). + */ + @property DList dup() + { + return DList(this[]); + } + +/** +Returns a range that iterates over all elements of the container, in +forward order. + +Complexity: $(BIGOH 1) + */ + Range opSlice() + { + if (empty) + return Range(null, null); + else + return Range(_first, _last); + } + +/** +Forward to $(D opSlice().front). + +Complexity: $(BIGOH 1) + */ + @property ref inout(T) front() inout + { + assert(!empty, "DList.front: List is empty"); + return _first.getPayload!T(); + } + +/** +Forward to $(D opSlice().back). + +Complexity: $(BIGOH 1) + */ + @property ref inout(T) back() inout + { + assert(!empty, "DList.back: List is empty"); + return _last.getPayload!T(); + } + +/+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +/ +/+ BEGIN CONCAT FUNCTIONS HERE +/ +/+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +/ + +/** +Returns a new $(D DList) that's the concatenation of $(D this) and its +argument $(D rhs). + */ + DList opBinary(string op, Stuff)(Stuff rhs) + if (op == "~" && is(typeof(insertBack(rhs)))) + { + auto ret = this.dup; + ret.insertBack(rhs); + return ret; + } + +/** +Returns a new $(D DList) that's the concatenation of the argument $(D lhs) +and $(D this). + */ + DList opBinaryRight(string op, Stuff)(Stuff lhs) + if (op == "~" && is(typeof(insertFront(lhs)))) + { + auto ret = this.dup; + ret.insertFront(lhs); + return ret; + } + +/** +Appends the contents of the argument $(D rhs) into $(D this). + */ + DList opOpAssign(string op, Stuff)(Stuff rhs) + if (op == "~" && is(typeof(insertBack(rhs)))) + { + insertBack(rhs); + return this; + } + +/+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +/ +/+ BEGIN INSERT FUNCTIONS HERE +/ +/+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +/ + +/** +Inserts $(D stuff) to the front/back of the container. $(D stuff) can be a +value convertible to $(D T) or a range of objects convertible to $(D +T). The stable version behaves the same, but guarantees that ranges +iterating over the container are never invalidated. + +Returns: The number of elements inserted + +Complexity: $(BIGOH log(n)) + */ + size_t insertFront(Stuff)(Stuff stuff) + { + initialize(); + return insertAfterNode(_root, stuff); + } + + /// ditto + size_t insertBack(Stuff)(Stuff stuff) + { + initialize(); + return insertBeforeNode(_root, stuff); + } + + /// ditto + alias insert = insertBack; + + /// ditto + alias stableInsert = insert; + + /// ditto + alias stableInsertFront = insertFront; + + /// ditto + alias stableInsertBack = insertBack; + +/** +Inserts $(D stuff) after range $(D r), which must be a non-empty range +previously extracted from this container. + +$(D stuff) can be a value convertible to $(D T) or a range of objects +convertible to $(D T). The stable version behaves the same, but +guarantees that ranges iterating over the container are never +invalidated. + +Returns: The number of values inserted. + +Complexity: $(BIGOH k + m), where $(D k) is the number of elements in +$(D r) and $(D m) is the length of $(D stuff). + */ + size_t insertBefore(Stuff)(Range r, Stuff stuff) + { + if (r._first) + return insertBeforeNode(r._first, stuff); + else + { + initialize(); + return insertAfterNode(_root, stuff); + } + } + + /// ditto + alias stableInsertBefore = insertBefore; + + /// ditto + size_t insertAfter(Stuff)(Range r, Stuff stuff) + { + if (r._last) + return insertAfterNode(r._last, stuff); + else + { + initialize(); + return insertBeforeNode(_root, stuff); + } + } + + /// ditto + alias stableInsertAfter = insertAfter; + +/+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +/ +/+ BEGIN REMOVE FUNCTIONS HERE +/ +/+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +/ + +/** +Picks one value in an unspecified position in the container, removes +it from the container, and returns it. The stable version behaves the same, +but guarantees that ranges iterating over the container are never invalidated. + +Precondition: $(D !empty) + +Returns: The element removed. + +Complexity: $(BIGOH 1). + */ + T removeAny() + { + import std.algorithm.mutation : move; + + assert(!empty, "DList.removeAny: List is empty"); + auto result = move(back); + removeBack(); + return result; + } + /// ditto + alias stableRemoveAny = removeAny; + +/** +Removes the value at the front/back of the container. The stable version +behaves the same, but guarantees that ranges iterating over the +container are never invalidated. + +Precondition: $(D !empty) + +Complexity: $(BIGOH 1). + */ + void removeFront() + { + assert(!empty, "DList.removeFront: List is empty"); + assert(_root is _first._prev, "DList: Inconsistent state"); + BaseNode.connect(_root, _first._next); + } + + /// ditto + alias stableRemoveFront = removeFront; + + /// ditto + void removeBack() + { + assert(!empty, "DList.removeBack: List is empty"); + assert(_last._next is _root, "DList: Inconsistent state"); + BaseNode.connect(_last._prev, _root); + } + + /// ditto + alias stableRemoveBack = removeBack; + +/** +Removes $(D howMany) values at the front or back of the +container. Unlike the unparameterized versions above, these functions +do not throw if they could not remove $(D howMany) elements. Instead, +if $(D howMany > n), all elements are removed. The returned value is +the effective number of elements removed. The stable version behaves +the same, but guarantees that ranges iterating over the container are +never invalidated. + +Returns: The number of elements removed + +Complexity: $(BIGOH howMany). + */ + size_t removeFront(size_t howMany) + { + if (!_root) return 0; + size_t result; + auto p = _first; + while (p !is _root && result < howMany) + { + p = p._next; + ++result; + } + BaseNode.connect(_root, p); + return result; + } + + /// ditto + alias stableRemoveFront = removeFront; + + /// ditto + size_t removeBack(size_t howMany) + { + if (!_root) return 0; + size_t result; + auto p = _last; + while (p !is _root && result < howMany) + { + p = p._prev; + ++result; + } + BaseNode.connect(p, _root); + return result; + } + + /// ditto + alias stableRemoveBack = removeBack; + +/** +Removes all elements belonging to $(D r), which must be a range +obtained originally from this container. + +Returns: A range spanning the remaining elements in the container that +initially were right after $(D r). + +Complexity: $(BIGOH 1) + */ + Range remove(Range r) + { + if (r.empty) + return r; + + assert(_root !is null, "Cannot remove from an un-initialized List"); + assert(r._first, "Remove: Range is empty"); + + BaseNode.connect(r._first._prev, r._last._next); + auto after = r._last._next; + if (after is _root) + return Range(null, null); + else + return Range(after, _last); + } + + /// ditto + Range linearRemove(Range r) + { + return remove(r); + } + +/** +Removes first element of $(D r), wich must be a range obtained originally +from this container, from both DList instance and range $(D r). + +Compexity: $(BIGOH 1) + */ + void popFirstOf(ref Range r) + { + assert(_root !is null, "Cannot remove from an un-initialized List"); + assert(r._first, "popFirstOf: Range is empty"); + auto prev = r._first._prev; + auto next = r._first._next; + r.popFront(); + BaseNode.connect(prev, next); + } + +/** +Removes last element of $(D r), wich must be a range obtained originally +from this container, from both DList instance and range $(D r). + +Compexity: $(BIGOH 1) + */ + void popLastOf(ref Range r) + { + assert(_root !is null, "Cannot remove from an un-initialized List"); + assert(r._first, "popLastOf: Range is empty"); + auto prev = r._last._prev; + auto next = r._last._next; + r.popBack(); + BaseNode.connect(prev, next); + } + +/** +$(D linearRemove) functions as $(D remove), but also accepts ranges that are +result the of a $(D take) operation. This is a convenient way to remove a +fixed amount of elements from the range. + +Complexity: $(BIGOH r.walkLength) + */ + Range linearRemove(Take!Range r) + { + assert(_root !is null, "Cannot remove from an un-initialized List"); + assert(r.source._first, "Remove: Range is empty"); + + BaseNode* first = r.source._first; + BaseNode* last = null; + do + { + last = r.source._first; + r.popFront(); + } while ( !r.empty ); + + return remove(Range(first, last)); + } + + /// ditto + alias stableRemove = remove; + /// ditto + alias stableLinearRemove = linearRemove; + +private: + + // Helper: Inserts stuff before the node n. + size_t insertBeforeNode(Stuff)(BaseNode* n, ref Stuff stuff) + if (isImplicitlyConvertible!(Stuff, T)) + { + auto p = createNode(stuff, n._prev, n); + n._prev._next = p; + n._prev = p; + return 1; + } + // ditto + size_t insertBeforeNode(Stuff)(BaseNode* n, ref Stuff stuff) + if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) + { + if (stuff.empty) return 0; + size_t result; + Range r = createRange(stuff, result); + BaseNode.connect(n._prev, r._first); + BaseNode.connect(r._last, n); + return result; + } + + // Helper: Inserts stuff after the node n. + size_t insertAfterNode(Stuff)(BaseNode* n, ref Stuff stuff) + if (isImplicitlyConvertible!(Stuff, T)) + { + auto p = createNode(stuff, n, n._next); + n._next._prev = p; + n._next = p; + return 1; + } + // ditto + size_t insertAfterNode(Stuff)(BaseNode* n, ref Stuff stuff) + if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) + { + if (stuff.empty) return 0; + size_t result; + Range r = createRange(stuff, result); + BaseNode.connect(r._last, n._next); + BaseNode.connect(n, r._first); + return result; + } + + // Helper: Creates a chain of nodes from the range stuff. + Range createRange(Stuff)(ref Stuff stuff, ref size_t result) + { + BaseNode* first = createNode(stuff.front); + BaseNode* last = first; + ++result; + for ( stuff.popFront() ; !stuff.empty ; stuff.popFront() ) + { + auto p = createNode(stuff.front, last); + last = last._next = p; + ++result; + } + return Range(first, last); + } +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + //Tests construction signatures + alias IntList = DList!int; + auto a0 = IntList(); + auto a1 = IntList(0); + auto a2 = IntList(0, 1); + auto a3 = IntList([0]); + auto a4 = IntList([0, 1]); + + assert(a0[].empty); + assert(equal(a1[], [0])); + assert(equal(a2[], [0, 1])); + assert(equal(a3[], [0])); + assert(equal(a4[], [0, 1])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + alias IntList = DList!int; + IntList list = IntList([0,1,2,3]); + assert(equal(list[],[0,1,2,3])); + list.insertBack([4,5,6,7]); + assert(equal(list[],[0,1,2,3,4,5,6,7])); + + list = IntList(); + list.insertFront([0,1,2,3]); + assert(equal(list[],[0,1,2,3])); + list.insertFront([4,5,6,7]); + assert(equal(list[],[4,5,6,7,0,1,2,3])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : take; + + alias IntList = DList!int; + IntList list = IntList([0,1,2,3]); + auto range = list[]; + for ( ; !range.empty; range.popFront()) + { + int item = range.front; + if (item == 2) + { + list.stableLinearRemove(take(range, 1)); + break; + } + } + assert(equal(list[],[0,1,3])); + + list = IntList([0,1,2,3]); + range = list[]; + for ( ; !range.empty; range.popFront()) + { + int item = range.front; + if (item == 2) + { + list.stableLinearRemove(take(range,2)); + break; + } + } + assert(equal(list[],[0,1])); + + list = IntList([0,1,2,3]); + range = list[]; + for ( ; !range.empty; range.popFront()) + { + int item = range.front; + if (item == 0) + { + list.stableLinearRemove(take(range,2)); + break; + } + } + assert(equal(list[],[2,3])); + + list = IntList([0,1,2,3]); + range = list[]; + for ( ; !range.empty; range.popFront()) + { + int item = range.front; + if (item == 1) + { + list.stableLinearRemove(take(range,2)); + break; + } + } + assert(equal(list[],[0,3])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto dl = DList!int([1, 2, 3, 4, 5]); + auto r = dl[]; + r.popFront(); + dl.popFirstOf(r); + assert(equal(dl[], [1, 3, 4, 5])); + assert(equal(r, [3, 4, 5])); + r.popBack(); + dl.popLastOf(r); + assert(equal(dl[], [1, 3, 5])); + assert(equal(r, [3])); + dl = DList!int([0]); + r = dl[]; + dl.popFirstOf(r); + assert(dl.empty); + dl = DList!int([0]); + r = dl[]; + dl.popLastOf(r); + assert(dl.empty); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto dl = DList!string(["a", "b", "d"]); + dl.insertAfter(dl[], "e"); // insert at the end + assert(equal(dl[], ["a", "b", "d", "e"])); + auto dlr = dl[]; + dlr.popBack(); dlr.popBack(); + dl.insertAfter(dlr, "c"); // insert after "b" + assert(equal(dl[], ["a", "b", "c", "d", "e"])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto dl = DList!string(["a", "b", "d"]); + dl.insertBefore(dl[], "e"); // insert at the front + assert(equal(dl[], ["e", "a", "b", "d"])); + auto dlr = dl[]; + dlr.popFront(); dlr.popFront(); + dl.insertBefore(dlr, "c"); // insert before "b" + assert(equal(dl[], ["e", "a", "c", "b", "d"])); +} + +@safe unittest +{ + auto d = DList!int([1, 2, 3]); + d.front = 5; //test frontAssign + assert(d.front == 5); + auto r = d[]; + r.back = 1; + assert(r.back == 1); +} + +// Issue 8895 +@safe unittest +{ + auto a = make!(DList!int)(1,2,3,4); + auto b = make!(DList!int)(1,2,3,4); + auto c = make!(DList!int)(1,2,3,5); + auto d = make!(DList!int)(1,2,3,4,5); + assert(a == b); // this better terminate! + assert(!(a == c)); + assert(!(a == d)); +} + +@safe unittest +{ + auto d = DList!int([1, 2, 3]); + d.front = 5; //test frontAssign + assert(d.front == 5); + auto r = d[]; + r.back = 1; + assert(r.back == 1); +} + +@safe unittest +{ + auto a = DList!int(); + assert(a.removeFront(10) == 0); + a.insert([1, 2, 3]); + assert(a.removeFront(10) == 3); + assert(a[].empty); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + //Verify all flavors of ~ + auto a = DList!int(); + auto b = DList!int(); + auto c = DList!int([1, 2, 3]); + auto d = DList!int([4, 5, 6]); + + assert((a ~ b[])[].empty); + assert((c ~ d[])[].equal([1, 2, 3, 4, 5, 6])); + assert(c[].equal([1, 2, 3])); + assert(d[].equal([4, 5, 6])); + + assert((c[] ~ d)[].equal([1, 2, 3, 4, 5, 6])); + assert(c[].equal([1, 2, 3])); + assert(d[].equal([4, 5, 6])); + + a~=c[]; + assert(a[].equal([1, 2, 3])); + assert(c[].equal([1, 2, 3])); + + a~=d[]; + assert(a[].equal([1, 2, 3, 4, 5, 6])); + assert(d[].equal([4, 5, 6])); + + a~=[7, 8, 9]; + assert(a[].equal([1, 2, 3, 4, 5, 6, 7, 8, 9])); + + //trick test: + auto r = c[]; + c.removeFront(); + c.removeBack(); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + //8905 + auto a = DList!int([1, 2, 3, 4]); + auto r = a[]; + a.stableRemoveBack(); + a.stableInsertBack(7); + assert(a[].equal([1, 2, 3, 7])); +} + +@safe unittest //12566 +{ + auto dl2 = DList!int([2,7]); + dl2.removeFront(); + assert(dl2[].walkLength == 1); + dl2.removeBack(); + assert(dl2.empty, "not empty?!"); +} + +@safe unittest //13076 +{ + DList!int list; + assert(list.empty); + list.clear(); +} + +@safe unittest //13425 +{ + import std.range : drop, take; + auto list = DList!int([1,2,3,4,5]); + auto r = list[].drop(4); // r is a view of the last element of list + assert(r.front == 5 && r.walkLength == 1); + r = list.linearRemove(r.take(1)); + assert(r.empty); // fails +} + +@safe unittest //14300 +{ + interface ITest {} + static class Test : ITest {} + + DList!ITest().insertBack(new Test()); +} + +@safe unittest //15263 +{ + import std.range : iota; + auto a = DList!int(); + a.insertFront(iota(0, 5)); // can insert range with non-ref front + assert(a.front == 0 && a.back == 4); +} diff --git a/libphobos/src/std/container/package.d b/libphobos/src/std/container/package.d new file mode 100644 index 0000000..fa00505 --- /dev/null +++ b/libphobos/src/std/container/package.d @@ -0,0 +1,1156 @@ +// Written in the D programming language. + +/** +This module defines generic containers. + +Construction: + +To implement the different containers both struct and class based +approaches have been used. $(REF make, std,_container,util) allows for +uniform construction with either approach. + +--- +import std.container; +// Construct a red-black tree and an array both containing the values 1, 2, 3. +// RedBlackTree should typically be allocated using `new` +RedBlackTree!int rbTree = new RedBlackTree!int(1, 2, 3); +// But `new` should not be used with Array +Array!int array = Array!int(1, 2, 3); +// `make` hides the differences +RedBlackTree!int rbTree2 = make!(RedBlackTree!int)(1, 2, 3); +Array!int array2 = make!(Array!int)(1, 2, 3); +--- + +Note that $(D make) can infer the element type from the given arguments. + +--- +import std.container; +auto rbTree = make!RedBlackTree(1, 2, 3); // RedBlackTree!int +auto array = make!Array("1", "2", "3"); // Array!string +--- + +Reference_semantics: + +All containers have reference semantics, which means that after +assignment both variables refer to the same underlying data. + +To make a copy of a _container, use the $(D c._dup) _container primitive. +--- +import std.container, std.range; +Array!int originalArray = make!(Array!int)(1, 2, 3); +Array!int secondArray = originalArray; +assert(equal(originalArray[], secondArray[])); + +// changing one instance changes the other one as well! +originalArray[0] = 12; +assert(secondArray[0] == 12); + +// secondArray now refers to an independent copy of originalArray +secondArray = originalArray.dup; +secondArray[0] = 1; +// assert that originalArray has not been affected +assert(originalArray[0] == 12); +--- + +$(B Attention:) If the _container is implemented as a class, using an +uninitialized instance can cause a null pointer dereference. + +--- +import std.container; + +RedBlackTree!int rbTree; +rbTree.insert(5); // null pointer dereference +--- + +Using an uninitialized struct-based _container will work, because the struct +intializes itself upon use; however, up to this point the _container will not +have an identity and assignment does not create two references to the same +data. + +--- +import std.container; + +// create an uninitialized array +Array!int array1; +// array2 does _not_ refer to array1 +Array!int array2 = array1; +array2.insertBack(42); +// thus array1 will not be affected +assert(array1.empty); + +// after initialization reference semantics work as expected +array1 = array2; +// now affects array2 as well +array1.removeBack(); +assert(array2.empty); +--- +It is therefore recommended to always construct containers using +$(REF make, std,_container,util). + +This is in fact necessary to put containers into another _container. +For example, to construct an $(D Array) of ten empty $(D Array)s, use +the following that calls $(D make) ten times. + +--- +import std.container, std.range; + +auto arrOfArrs = make!Array(generate!(() => make!(Array!int)).take(10)); +--- + +Submodules: + +This module consists of the following submodules: + +$(UL + $(LI + The $(MREF std, _container, array) module provides + an array type with deterministic control of memory, not reliant on + the GC unlike built-in arrays. + ) + $(LI + The $(MREF std, _container, binaryheap) module + provides a binary heap implementation that can be applied to any + user-provided random-access range. + ) + $(LI + The $(MREF std, _container, dlist) module provides + a doubly-linked list implementation. + ) + $(LI + The $(MREF std, _container, rbtree) module + implements red-black trees. + ) + $(LI + The $(MREF std, _container, slist) module + implements singly-linked lists. + ) + $(LI + The $(MREF std, _container, util) module contains + some generic tools commonly used by _container implementations. + ) +) + +The_primary_range_of_a_container: + +While some _containers offer direct access to their elements e.g. via +$(D opIndex), $(D c.front) or $(D c.back), access +and modification of a _container's contents is generally done through +its primary $(MREF_ALTTEXT range, std, range) type, +which is aliased as $(D C.Range). For example, the primary range type of +$(D Array!int) is $(D Array!int.Range). + +If the documentation of a member function of a _container takes +a parameter of type $(D Range), then it refers to the primary range type of +this _container. Oftentimes $(D Take!Range) will be used, in which case +the range refers to a span of the elements in the _container. Arguments to +these parameters $(B must) be obtained from the same _container instance +as the one being worked with. It is important to note that many generic range +algorithms return the same range type as their input range. + +--- +import std.algorithm.comparison : equal; +import std.algorithm.iteration : find; +import std.container; +import std.range : take; + +auto array = make!Array(1, 2, 3); + +// `find` returns an Array!int.Range advanced to the element "2" +array.linearRemove(array[].find(2)); + +assert(array[].equal([1])); + +array = make!Array(1, 2, 3); + +// the range given to `linearRemove` is a Take!(Array!int.Range) +// spanning just the element "2" +array.linearRemove(array[].find(2).take(1)); + +assert(array[].equal([1, 3])); +--- + +When any $(MREF_ALTTEXT range, std, range) can be passed as an argument to +a member function, the documention usually refers to the parameter's templated +type as $(D Stuff). + +--- +import std.algorithm.comparison : equal; +import std.container; +import std.range : iota; + +auto array = make!Array(1, 2); + +// the range type returned by `iota` is completely unrelated to Array, +// which is fine for Array.insertBack: +array.insertBack(iota(3, 10)); + +assert(array[].equal([1, 2, 3, 4, 5, 6, 7, 8, 9])); +--- + +Container_primitives: + +Containers do not form a class hierarchy, instead they implement a +common set of primitives (see table below). These primitives each guarantee +a specific worst case complexity and thus allow generic code to be written +independently of the _container implementation. + +For example the primitives $(D c.remove(r)) and $(D c.linearRemove(r)) both +remove the sequence of elements in range $(D r) from the _container $(D c). +The primitive $(D c.remove(r)) guarantees +$(BIGOH n$(SUBSCRIPT r) log n$(SUBSCRIPT c)) complexity in the worst case and +$(D c.linearRemove(r)) relaxes this guarantee to $(BIGOH n$(SUBSCRIPT c)). + +Since a sequence of elements can be removed from a $(MREF_ALTTEXT doubly linked list,std,_container,dlist) +in constant time, $(D DList) provides the primitive $(D c.remove(r)) +as well as $(D c.linearRemove(r)). On the other hand +$(MREF_ALTTEXT Array, std,_container, array) only offers $(D c.linearRemove(r)). + +The following table describes the common set of primitives that containers +implement. A _container need not implement all primitives, but if a +primitive is implemented, it must support the syntax described in the $(B +syntax) column with the semantics described in the $(B description) column, and +it must not have a worst-case complexity worse than denoted in big-O notation in +the $(BIGOH ·) column. Below, $(D C) means a _container type, $(D c) is +a value of _container type, $(D n$(SUBSCRIPT x)) represents the effective length of +value $(D x), which could be a single element (in which case $(D n$(SUBSCRIPT x)) is +$(D 1)), a _container, or a range. + +$(BOOKTABLE Container primitives, +$(TR + $(TH Syntax) + $(TH $(BIGOH ·)) + $(TH Description) +) +$(TR + $(TDNW $(D C(x))) + $(TDNW $(D n$(SUBSCRIPT x))) + $(TD Creates a _container of type $(D C) from either another _container or a range. + The created _container must not be a null reference even if x is empty.) +) +$(TR + $(TDNW $(D c.dup)) + $(TDNW $(D n$(SUBSCRIPT c))) + $(TD Returns a duplicate of the _container.) +) +$(TR + $(TDNW $(D c ~ x)) + $(TDNW $(D n$(SUBSCRIPT c) + n$(SUBSCRIPT x))) + $(TD Returns the concatenation of $(D c) and $(D r). $(D x) may be a single + element or an input range.) +) +$(TR + $(TDNW $(D x ~ c)) + $(TDNW $(D n$(SUBSCRIPT c) + n$(SUBSCRIPT x))) + $(TD Returns the concatenation of $(D x) and $(D c). $(D x) may be a + single element or an input range type.) +) +$(LEADINGROWN 3, Iteration +) +$(TR + $(TD $(D c.Range)) + $(TD) + $(TD The primary range type associated with the _container.) +) +$(TR + $(TD $(D c[])) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Returns a range + iterating over the entire _container, in a _container-defined order.) +) +$(TR + $(TDNW $(D c[a .. b])) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Fetches a portion of the _container from key $(D a) to key $(D b).) +) +$(LEADINGROWN 3, Capacity +) +$(TR + $(TD $(D c.empty)) + $(TD $(D 1)) + $(TD Returns $(D true) if the _container has no elements, $(D false) otherwise.) +) +$(TR + $(TD $(D c.length)) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Returns the number of elements in the _container.) +) +$(TR + $(TDNW $(D c.length = n)) + $(TDNW $(D n$(SUBSCRIPT c) + n)) + $(TD Forces the number of elements in the _container to $(D n). + If the _container ends up growing, the added elements are initialized + in a _container-dependent manner (usually with $(D T.init)).) +) +$(TR + $(TD $(D c.capacity)) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Returns the maximum number of elements that can be stored in the + _container without triggering a reallocation.) +) +$(TR + $(TD $(D c.reserve(x))) + $(TD $(D n$(SUBSCRIPT c))) + $(TD Forces $(D capacity) to at least $(D x) without reducing it.) +) +$(LEADINGROWN 3, Access +) +$(TR + $(TDNW $(D c.front)) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Returns the first element of the _container, in a _container-defined order.) +) +$(TR + $(TDNW $(D c.moveFront)) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Destructively reads and returns the first element of the + _container. The slot is not removed from the _container; it is left + initialized with $(D T.init). This routine need not be defined if $(D + front) returns a $(D ref).) +) +$(TR + $(TDNW $(D c.front = v)) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Assigns $(D v) to the first element of the _container.) +) +$(TR + $(TDNW $(D c.back)) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Returns the last element of the _container, in a _container-defined order.) +) +$(TR + $(TDNW $(D c.moveBack)) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Destructively reads and returns the last element of the + _container. The slot is not removed from the _container; it is left + initialized with $(D T.init). This routine need not be defined if $(D + front) returns a $(D ref).) +) +$(TR + $(TDNW $(D c.back = v)) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Assigns $(D v) to the last element of the _container.) +) +$(TR + $(TDNW $(D c[x])) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Provides indexed access into the _container. The index type is + _container-defined. A _container may define several index types (and + consequently overloaded indexing).) +) +$(TR + $(TDNW $(D c.moveAt(x))) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Destructively reads and returns the value at position $(D x). The slot + is not removed from the _container; it is left initialized with $(D + T.init).) +) +$(TR + $(TDNW $(D c[x] = v)) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Sets element at specified index into the _container.) +) +$(TR + $(TDNW $(D c[x] $(I op)= v)) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Performs read-modify-write operation at specified index into the + _container.) +) +$(LEADINGROWN 3, Operations +) +$(TR + $(TDNW $(D e in c)) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Returns nonzero if e is found in $(D c).) +) +$(TR + $(TDNW $(D c.lowerBound(v))) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Returns a range of all elements strictly less than $(D v).) +) +$(TR + $(TDNW $(D c.upperBound(v))) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Returns a range of all elements strictly greater than $(D v).) +) +$(TR + $(TDNW $(D c.equalRange(v))) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Returns a range of all elements in $(D c) that are equal to $(D v).) +) +$(LEADINGROWN 3, Modifiers +) +$(TR + $(TDNW $(D c ~= x)) + $(TDNW $(D n$(SUBSCRIPT c) + n$(SUBSCRIPT x))) + $(TD Appends $(D x) to $(D c). $(D x) may be a single element or an input range type.) +) +$(TR + $(TDNW $(D c.clear())) + $(TDNW $(D n$(SUBSCRIPT c))) + $(TD Removes all elements in $(D c).) +) +$(TR + $(TDNW $(D c.insert(x))) + $(TDNW $(D n$(SUBSCRIPT x) * log n$(SUBSCRIPT c))) + $(TD Inserts $(D x) in $(D c) at a position (or positions) chosen by $(D c).) +) +$(TR + $(TDNW $(D c.stableInsert(x))) + $(TDNW $(D n$(SUBSCRIPT x) * log n$(SUBSCRIPT c))) + $(TD Same as $(D c.insert(x)), but is guaranteed to not invalidate any ranges.) +) +$(TR + $(TDNW $(D c.linearInsert(v))) + $(TDNW $(D n$(SUBSCRIPT c))) + $(TD Same as $(D c.insert(v)) but relaxes complexity to linear.) +) +$(TR + $(TDNW $(D c.stableLinearInsert(v))) + $(TDNW $(D n$(SUBSCRIPT c))) + $(TD Same as $(D c.stableInsert(v)) but relaxes complexity to linear.) +) +$(TR + $(TDNW $(D c.removeAny())) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Removes some element from $(D c) and returns it.) +) +$(TR + $(TDNW $(D c.stableRemoveAny())) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Same as $(D c.removeAny()), but is guaranteed to not invalidate any + iterators.) +) +$(TR + $(TDNW $(D c.insertFront(v))) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Inserts $(D v) at the front of $(D c).) +) +$(TR + $(TDNW $(D c.stableInsertFront(v))) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Same as $(D c.insertFront(v)), but guarantees no ranges will be + invalidated.) +) +$(TR + $(TDNW $(D c.insertBack(v))) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Inserts $(D v) at the back of $(D c).) +) +$(TR + $(TDNW $(D c.stableInsertBack(v))) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Same as $(D c.insertBack(v)), but guarantees no ranges will be + invalidated.) +) +$(TR + $(TDNW $(D c.removeFront())) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Removes the element at the front of $(D c).) +) +$(TR + $(TDNW $(D c.stableRemoveFront())) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Same as $(D c.removeFront()), but guarantees no ranges will be + invalidated.) +) +$(TR + $(TDNW $(D c.removeBack())) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Removes the value at the back of $(D c).) +) +$(TR + $(TDNW $(D c.stableRemoveBack())) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Same as $(D c.removeBack()), but guarantees no ranges will be + invalidated.) +) +$(TR + $(TDNW $(D c.remove(r))) + $(TDNW $(D n$(SUBSCRIPT r) * log n$(SUBSCRIPT c))) + $(TD Removes range $(D r) from $(D c).) +) +$(TR + $(TDNW $(D c.stableRemove(r))) + $(TDNW $(D n$(SUBSCRIPT r) * log n$(SUBSCRIPT c))) + $(TD Same as $(D c.remove(r)), but guarantees iterators are not + invalidated.) +) +$(TR + $(TDNW $(D c.linearRemove(r))) + $(TDNW $(D n$(SUBSCRIPT c))) + $(TD Removes range $(D r) from $(D c).) +) +$(TR + $(TDNW $(D c.stableLinearRemove(r))) + $(TDNW $(D n$(SUBSCRIPT c))) + $(TD Same as $(D c.linearRemove(r)), but guarantees iterators are not + invalidated.) +) +$(TR + $(TDNW $(D c.removeKey(k))) + $(TDNW $(D log n$(SUBSCRIPT c))) + $(TD Removes an element from $(D c) by using its key $(D k). + The key's type is defined by the _container.) +) +$(TR + $(TDNW $(D )) + $(TDNW $(D )) + $(TD ) +) +) + +Source: $(PHOBOSSRC std/_container/package.d) + +Copyright: Red-black tree code copyright (C) 2008- by Steven Schveighoffer. Other code +copyright 2010- Andrei Alexandrescu. All rights reserved by the respective holders. + +License: Distributed under the Boost Software License, Version 1.0. +(See accompanying file LICENSE_1_0.txt or copy at $(HTTP +boost.org/LICENSE_1_0.txt)). + +Authors: Steven Schveighoffer, $(HTTP erdani.com, Andrei Alexandrescu) + */ + +module std.container; + +public import std.container.array; +public import std.container.binaryheap; +public import std.container.dlist; +public import std.container.rbtree; +public import std.container.slist; + +import std.meta; + + +/* The following documentation and type $(D TotalContainer) are +intended for developers only. + +$(D TotalContainer) is an unimplemented container that illustrates a +host of primitives that a container may define. It is to some extent +the bottom of the conceptual container hierarchy. A given container +most often will choose to only implement a subset of these primitives, +and define its own additional ones. Adhering to the standard primitive +names below allows generic code to work independently of containers. + +Things to remember: any container must be a reference type, whether +implemented as a $(D class) or $(D struct). No primitive below +requires the container to escape addresses of elements, which means +that compliant containers can be defined to use reference counting or +other deterministic memory management techniques. + +A container may choose to define additional specific operations. The +only requirement is that those operations bear different names than +the ones below, lest user code gets confused. + +Complexity of operations should be interpreted as "at least as good +as". If an operation is required to have $(BIGOH n) complexity, it +could have anything lower than that, e.g. $(BIGOH log(n)). Unless +specified otherwise, $(D n) inside a $(BIGOH) expression stands for +the number of elements in the container. + */ +struct TotalContainer(T) +{ +/** +If the container has a notion of key-value mapping, $(D KeyType) +defines the type of the key of the container. + */ + alias KeyType = T; + +/** +If the container has a notion of multikey-value mapping, $(D +KeyTypes[k]), where $(D k) is a zero-based unsigned number, defines +the type of the $(D k)th key of the container. + +A container may define both $(D KeyType) and $(D KeyTypes), e.g. in +the case it has the notion of primary/preferred key. + */ + alias KeyTypes = AliasSeq!T; + +/** +If the container has a notion of key-value mapping, $(D ValueType) +defines the type of the value of the container. Typically, a map-style +container mapping values of type $(D K) to values of type $(D V) +defines $(D KeyType) to be $(D K) and $(D ValueType) to be $(D V). + */ + alias ValueType = T; + +/** +Defines the container's primary range, which embodies one of the +ranges defined in $(MREF std,range). + +Generally a container may define several types of ranges. + */ + struct Range + { + /++ + Range primitives. + +/ + @property bool empty() + { + assert(0); + } + /// Ditto + @property ref T front() //ref return optional + { + assert(0); + } + /// Ditto + @property void front(T value) //Only when front does not return by ref + { + assert(0); + } + /// Ditto + T moveFront() + { + assert(0); + } + /// Ditto + void popFront() + { + assert(0); + } + /// Ditto + @property ref T back() //ref return optional + { + assert(0); + } + /// Ditto + @property void back(T value) //Only when front does not return by ref + { + assert(0); + } + /// Ditto + T moveBack() + { + assert(0); + } + /// Ditto + void popBack() + { + assert(0); + } + /// Ditto + T opIndex(size_t i) //ref return optional + { + assert(0); + } + /// Ditto + void opIndexAssign(size_t i, T value) //Only when front does not return by ref + { + assert(0); + } + /// Ditto + T opIndexUnary(string op)(size_t i) //Only when front does not return by ref + { + assert(0); + } + /// Ditto + void opIndexOpAssign(string op)(size_t i, T value) //Only when front does not return by ref + { + assert(0); + } + /// Ditto + T moveAt(size_t i) + { + assert(0); + } + /// Ditto + @property size_t length() + { + assert(0); + } + } + +/** +Property returning $(D true) if and only if the container has no +elements. + +Complexity: $(BIGOH 1) + */ + @property bool empty() + { + assert(0); + } + +/** +Returns a duplicate of the container. The elements themselves are not +transitively duplicated. + +Complexity: $(BIGOH n). + */ + @property TotalContainer dup() + { + assert(0); + } + +/** +Returns the number of elements in the container. + +Complexity: $(BIGOH log(n)). +*/ + @property size_t length() + { + assert(0); + } + +/** +Returns the maximum number of elements the container can store without +(a) allocating memory, (b) invalidating iterators upon insertion. + +Complexity: $(BIGOH log(n)). + */ + @property size_t capacity() + { + assert(0); + } + +/** +Ensures sufficient capacity to accommodate $(D n) elements. + +Postcondition: $(D capacity >= n) + +Complexity: $(BIGOH log(e - capacity)) if $(D e > capacity), otherwise +$(BIGOH 1). + */ + void reserve(size_t e) + { + assert(0); + } + +/** +Returns a range that iterates over all elements of the container, in a +container-defined order. The container should choose the most +convenient and fast method of iteration for $(D opSlice()). + +Complexity: $(BIGOH log(n)) + */ + Range opSlice() + { + assert(0); + } + + /** + Returns a range that iterates the container between two + specified positions. + + Complexity: $(BIGOH log(n)) + */ + Range opSlice(size_t a, size_t b) + { + assert(0); + } + +/** +Forward to $(D opSlice().front) and $(D opSlice().back), respectively. + +Complexity: $(BIGOH log(n)) + */ + @property ref T front() //ref return optional + { + assert(0); + } + /// Ditto + @property void front(T value) //Only when front does not return by ref + { + assert(0); + } + /// Ditto + T moveFront() + { + assert(0); + } + /// Ditto + @property ref T back() //ref return optional + { + assert(0); + } + /// Ditto + @property void back(T value) //Only when front does not return by ref + { + assert(0); + } + /// Ditto + T moveBack() + { + assert(0); + } + +/** +Indexing operators yield or modify the value at a specified index. + */ + ref T opIndex(KeyType) //ref return optional + { + assert(0); + } + /// ditto + void opIndexAssign(KeyType i, T value) //Only when front does not return by ref + { + assert(0); + } + /// ditto + T opIndexUnary(string op)(KeyType i) //Only when front does not return by ref + { + assert(0); + } + /// ditto + void opIndexOpAssign(string op)(KeyType i, T value) //Only when front does not return by ref + { + assert(0); + } + /// ditto + T moveAt(KeyType i) + { + assert(0); + } + +/** +$(D k in container) returns true if the given key is in the container. + */ + bool opBinaryRight(string op)(KeyType k) if (op == "in") + { + assert(0); + } + +/** +Returns a range of all elements containing $(D k) (could be empty or a +singleton range). + */ + Range equalRange(KeyType k) + { + assert(0); + } + +/** +Returns a range of all elements with keys less than $(D k) (could be +empty or a singleton range). Only defined by containers that store +data sorted at all times. + */ + Range lowerBound(KeyType k) + { + assert(0); + } + +/** +Returns a range of all elements with keys larger than $(D k) (could be +empty or a singleton range). Only defined by containers that store +data sorted at all times. + */ + Range upperBound(KeyType k) + { + assert(0); + } + +/** +Returns a new container that's the concatenation of $(D this) and its +argument. $(D opBinaryRight) is only defined if $(D Stuff) does not +define $(D opBinary). + +Complexity: $(BIGOH n + m), where m is the number of elements in $(D +stuff) + */ + TotalContainer opBinary(string op)(Stuff rhs) if (op == "~") + { + assert(0); + } + + /// ditto + TotalContainer opBinaryRight(string op)(Stuff lhs) if (op == "~") + { + assert(0); + } + +/** +Forwards to $(D insertAfter(this[], stuff)). + */ + void opOpAssign(string op)(Stuff stuff) if (op == "~") + { + assert(0); + } + +/** +Removes all contents from the container. The container decides how $(D +capacity) is affected. + +Postcondition: $(D empty) + +Complexity: $(BIGOH n) + */ + void clear() + { + assert(0); + } + +/** +Sets the number of elements in the container to $(D newSize). If $(D +newSize) is greater than $(D length), the added elements are added to +unspecified positions in the container and initialized with $(D +.init). + +Complexity: $(BIGOH abs(n - newLength)) + +Postcondition: $(D _length == newLength) + */ + @property void length(size_t newLength) + { + assert(0); + } + +/** +Inserts $(D stuff) in an unspecified position in the +container. Implementations should choose whichever insertion means is +the most advantageous for the container, but document the exact +behavior. $(D stuff) can be a value convertible to the element type of +the container, or a range of values convertible to it. + +The $(D stable) version guarantees that ranges iterating over the +container are never invalidated. Client code that counts on +non-invalidating insertion should use $(D stableInsert). Such code would +not compile against containers that don't support it. + +Returns: The number of elements added. + +Complexity: $(BIGOH m * log(n)), where $(D m) is the number of +elements in $(D stuff) + */ + size_t insert(Stuff)(Stuff stuff) + { + assert(0); + } + ///ditto + size_t stableInsert(Stuff)(Stuff stuff) + { + assert(0); + } + +/** +Same as $(D insert(stuff)) and $(D stableInsert(stuff)) respectively, +but relax the complexity constraint to linear. + */ + size_t linearInsert(Stuff)(Stuff stuff) + { + assert(0); + } + ///ditto + size_t stableLinearInsert(Stuff)(Stuff stuff) + { + assert(0); + } + +/** +Picks one value in an unspecified position in the container, removes +it from the container, and returns it. Implementations should pick the +value that's the most advantageous for the container. The stable version +behaves the same, but guarantees that ranges iterating over the container +are never invalidated. + +Precondition: $(D !empty) + +Returns: The element removed. + +Complexity: $(BIGOH log(n)). + */ + T removeAny() + { + assert(0); + } + /// ditto + T stableRemoveAny() + { + assert(0); + } + +/** +Inserts $(D value) to the front or back of the container. $(D stuff) +can be a value convertible to the container's element type or a range +of values convertible to it. The stable version behaves the same, but +guarantees that ranges iterating over the container are never +invalidated. + +Returns: The number of elements inserted + +Complexity: $(BIGOH log(n)). + */ + size_t insertFront(Stuff)(Stuff stuff) + { + assert(0); + } + /// ditto + size_t stableInsertFront(Stuff)(Stuff stuff) + { + assert(0); + } + /// ditto + size_t insertBack(Stuff)(Stuff stuff) + { + assert(0); + } + /// ditto + size_t stableInsertBack(T value) + { + assert(0); + } + +/** +Removes the value at the front or back of the container. The stable +version behaves the same, but guarantees that ranges iterating over +the container are never invalidated. The optional parameter $(D +howMany) instructs removal of that many elements. If $(D howMany > n), +all elements are removed and no exception is thrown. + +Precondition: $(D !empty) + +Complexity: $(BIGOH log(n)). + */ + void removeFront() + { + assert(0); + } + /// ditto + void stableRemoveFront() + { + assert(0); + } + /// ditto + void removeBack() + { + assert(0); + } + /// ditto + void stableRemoveBack() + { + assert(0); + } + +/** +Removes $(D howMany) values at the front or back of the +container. Unlike the unparameterized versions above, these functions +do not throw if they could not remove $(D howMany) elements. Instead, +if $(D howMany > n), all elements are removed. The returned value is +the effective number of elements removed. The stable version behaves +the same, but guarantees that ranges iterating over the container are +never invalidated. + +Returns: The number of elements removed + +Complexity: $(BIGOH howMany * log(n)). + */ + size_t removeFront(size_t howMany) + { + assert(0); + } + /// ditto + size_t stableRemoveFront(size_t howMany) + { + assert(0); + } + /// ditto + size_t removeBack(size_t howMany) + { + assert(0); + } + /// ditto + size_t stableRemoveBack(size_t howMany) + { + assert(0); + } + +/** +Removes all values corresponding to key $(D k). + +Complexity: $(BIGOH m * log(n)), where $(D m) is the number of +elements with the same key. + +Returns: The number of elements removed. + */ + size_t removeKey(KeyType k) + { + assert(0); + } + +/** +Inserts $(D stuff) before, after, or instead range $(D r), which must +be a valid range previously extracted from this container. $(D stuff) +can be a value convertible to the container's element type or a range +of objects convertible to it. The stable version behaves the same, but +guarantees that ranges iterating over the container are never +invalidated. + +Returns: The number of values inserted. + +Complexity: $(BIGOH n + m), where $(D m) is the length of $(D stuff) + */ + size_t insertBefore(Stuff)(Range r, Stuff stuff) + { + assert(0); + } + /// ditto + size_t stableInsertBefore(Stuff)(Range r, Stuff stuff) + { + assert(0); + } + /// ditto + size_t insertAfter(Stuff)(Range r, Stuff stuff) + { + assert(0); + } + /// ditto + size_t stableInsertAfter(Stuff)(Range r, Stuff stuff) + { + assert(0); + } + /// ditto + size_t replace(Stuff)(Range r, Stuff stuff) + { + assert(0); + } + /// ditto + size_t stableReplace(Stuff)(Range r, Stuff stuff) + { + assert(0); + } + +/** +Removes all elements belonging to $(D r), which must be a range +obtained originally from this container. The stable version behaves the +same, but guarantees that ranges iterating over the container are +never invalidated. + +Returns: A range spanning the remaining elements in the container that +initially were right after $(D r). + +Complexity: $(BIGOH m * log(n)), where $(D m) is the number of +elements in $(D r) + */ + Range remove(Range r) + { + assert(0); + } + /// ditto + Range stableRemove(Range r) + { + assert(0); + } + +/** +Same as $(D remove) above, but has complexity relaxed to linear. + +Returns: A range spanning the remaining elements in the container that +initially were right after $(D r). + +Complexity: $(BIGOH n) + */ + Range linearRemove(Range r) + { + assert(0); + } + /// ditto + Range stableLinearRemove(Range r) + { + assert(0); + } +} + +@safe unittest +{ + TotalContainer!int test; +} diff --git a/libphobos/src/std/container/rbtree.d b/libphobos/src/std/container/rbtree.d new file mode 100644 index 0000000..861da5e --- /dev/null +++ b/libphobos/src/std/container/rbtree.d @@ -0,0 +1,2065 @@ +/** +This module implements a red-black tree container. + +This module is a submodule of $(MREF std, container). + +Source: $(PHOBOSSRC std/container/_rbtree.d) + +Copyright: Red-black tree code copyright (C) 2008- by Steven Schveighoffer. Other code +copyright 2010- Andrei Alexandrescu. All rights reserved by the respective holders. + +License: Distributed under the Boost Software License, Version 1.0. +(See accompanying file LICENSE_1_0.txt or copy at $(HTTP +boost.org/LICENSE_1_0.txt)). + +Authors: Steven Schveighoffer, $(HTTP erdani.com, Andrei Alexandrescu) +*/ +module std.container.rbtree; + +/// +@safe pure unittest +{ + import std.algorithm.comparison : equal; + import std.container.rbtree; + + auto rbt = redBlackTree(3, 1, 4, 2, 5); + assert(rbt.front == 1); + assert(equal(rbt[], [1, 2, 3, 4, 5])); + + rbt.removeKey(1, 4); + assert(equal(rbt[], [2, 3, 5])); + + rbt.removeFront(); + assert(equal(rbt[], [3, 5])); + + rbt.insert([1, 2, 4]); + assert(equal(rbt[], [1, 2, 3, 4, 5])); + + // Query bounds in O(log(n)) + assert(rbt.lowerBound(3).equal([1, 2])); + assert(rbt.equalRange(3).equal([3])); + assert(rbt.upperBound(3).equal([4, 5])); + + // A Red Black tree with the highest element at front: + import std.range : iota; + auto maxTree = redBlackTree!"a > b"(iota(5)); + assert(equal(maxTree[], [4, 3, 2, 1, 0])); + + // adding duplicates will not add them, but return 0 + auto rbt2 = redBlackTree(1, 3); + assert(rbt2.insert(1) == 0); + assert(equal(rbt2[], [1, 3])); + assert(rbt2.insert(2) == 1); + + // however you can allow duplicates + auto ubt = redBlackTree!true([0, 1, 0, 1]); + assert(equal(ubt[], [0, 0, 1, 1])); +} + +import std.format; +import std.functional : binaryFun; + +public import std.container.util; + +version (unittest) debug = RBDoChecks; + +//debug = RBDoChecks; + +/* + * Implementation for a Red Black node for use in a Red Black Tree (see below) + * + * this implementation assumes we have a marker Node that is the parent of the + * root Node. This marker Node is not a valid Node, but marks the end of the + * collection. The root is the left child of the marker Node, so it is always + * last in the collection. The marker Node is passed in to the setColor + * function, and the Node which has this Node as its parent is assumed to be + * the root Node. + * + * A Red Black tree should have O(lg(n)) insertion, removal, and search time. + */ +struct RBNode(V) +{ + /* + * Convenience alias + */ + alias Node = RBNode*; + + private Node _left; + private Node _right; + private Node _parent; + + /** + * The value held by this node + */ + V value; + + /** + * Enumeration determining what color the node is. Null nodes are assumed + * to be black. + */ + enum Color : byte + { + Red, + Black + } + + /** + * The color of the node. + */ + Color color; + + /** + * Get the left child + */ + @property inout(RBNode)* left() inout + { + return _left; + } + + /** + * Get the right child + */ + @property inout(RBNode)* right() inout + { + return _right; + } + + /** + * Get the parent + */ + @property inout(RBNode)* parent() inout + { + return _parent; + } + + /** + * Set the left child. Also updates the new child's parent node. This + * does not update the previous child. + * + * Returns newNode + */ + @property Node left(Node newNode) + { + _left = newNode; + if (newNode !is null) + newNode._parent = &this; + return newNode; + } + + /** + * Set the right child. Also updates the new child's parent node. This + * does not update the previous child. + * + * Returns newNode + */ + @property Node right(Node newNode) + { + _right = newNode; + if (newNode !is null) + newNode._parent = &this; + return newNode; + } + + // assume _left is not null + // + // performs rotate-right operation, where this is T, _right is R, _left is + // L, _parent is P: + // + // P P + // | -> | + // T L + // / \ / \ + // L R a T + // / \ / \ + // a b b R + // + /** + * Rotate right. This performs the following operations: + * - The left child becomes the parent of this node. + * - This node becomes the new parent's right child. + * - The old right child of the new parent becomes the left child of this + * node. + */ + Node rotateR() + in + { + assert(_left !is null); + } + body + { + // sets _left._parent also + if (isLeftNode) + parent.left = _left; + else + parent.right = _left; + Node tmp = _left._right; + + // sets _parent also + _left.right = &this; + + // sets tmp._parent also + left = tmp; + + return &this; + } + + // assumes _right is non null + // + // performs rotate-left operation, where this is T, _right is R, _left is + // L, _parent is P: + // + // P P + // | -> | + // T R + // / \ / \ + // L R T b + // / \ / \ + // a b L a + // + /** + * Rotate left. This performs the following operations: + * - The right child becomes the parent of this node. + * - This node becomes the new parent's left child. + * - The old left child of the new parent becomes the right child of this + * node. + */ + Node rotateL() + in + { + assert(_right !is null); + } + body + { + // sets _right._parent also + if (isLeftNode) + parent.left = _right; + else + parent.right = _right; + Node tmp = _right._left; + + // sets _parent also + _right.left = &this; + + // sets tmp._parent also + right = tmp; + return &this; + } + + + /** + * Returns true if this node is a left child. + * + * Note that this should always return a value because the root has a + * parent which is the marker node. + */ + @property bool isLeftNode() const + in + { + assert(_parent !is null); + } + body + { + return _parent._left is &this; + } + + /** + * Set the color of the node after it is inserted. This performs an + * update to the whole tree, possibly rotating nodes to keep the Red-Black + * properties correct. This is an O(lg(n)) operation, where n is the + * number of nodes in the tree. + * + * end is the marker node, which is the parent of the topmost valid node. + */ + void setColor(Node end) + { + // test against the marker node + if (_parent !is end) + { + if (_parent.color == Color.Red) + { + Node cur = &this; + while (true) + { + // because root is always black, _parent._parent always exists + if (cur._parent.isLeftNode) + { + // parent is left node, y is 'uncle', could be null + Node y = cur._parent._parent._right; + if (y !is null && y.color == Color.Red) + { + cur._parent.color = Color.Black; + y.color = Color.Black; + cur = cur._parent._parent; + if (cur._parent is end) + { + // root node + cur.color = Color.Black; + break; + } + else + { + // not root node + cur.color = Color.Red; + if (cur._parent.color == Color.Black) + // satisfied, exit the loop + break; + } + } + else + { + if (!cur.isLeftNode) + cur = cur._parent.rotateL(); + cur._parent.color = Color.Black; + cur = cur._parent._parent.rotateR(); + cur.color = Color.Red; + // tree should be satisfied now + break; + } + } + else + { + // parent is right node, y is 'uncle' + Node y = cur._parent._parent._left; + if (y !is null && y.color == Color.Red) + { + cur._parent.color = Color.Black; + y.color = Color.Black; + cur = cur._parent._parent; + if (cur._parent is end) + { + // root node + cur.color = Color.Black; + break; + } + else + { + // not root node + cur.color = Color.Red; + if (cur._parent.color == Color.Black) + // satisfied, exit the loop + break; + } + } + else + { + if (cur.isLeftNode) + cur = cur._parent.rotateR(); + cur._parent.color = Color.Black; + cur = cur._parent._parent.rotateL(); + cur.color = Color.Red; + // tree should be satisfied now + break; + } + } + } + + } + } + else + { + // + // this is the root node, color it black + // + color = Color.Black; + } + } + + /** + * Remove this node from the tree. The 'end' node is used as the marker + * which is root's parent. Note that this cannot be null! + * + * Returns the next highest valued node in the tree after this one, or end + * if this was the highest-valued node. + */ + Node remove(Node end) + { + // + // remove this node from the tree, fixing the color if necessary. + // + Node x; + Node ret = next; + + // if this node has 2 children + if (_left !is null && _right !is null) + { + // + // normally, we can just swap this node's and y's value, but + // because an iterator could be pointing to y and we don't want to + // disturb it, we swap this node and y's structure instead. This + // can also be a benefit if the value of the tree is a large + // struct, which takes a long time to copy. + // + Node yp, yl, yr; + Node y = ret; // y = next + yp = y._parent; + yl = y._left; + yr = y._right; + auto yc = y.color; + auto isyleft = y.isLeftNode; + + // + // replace y's structure with structure of this node. + // + if (isLeftNode) + _parent.left = y; + else + _parent.right = y; + // + // need special case so y doesn't point back to itself + // + y.left = _left; + if (_right is y) + y.right = &this; + else + y.right = _right; + y.color = color; + + // + // replace this node's structure with structure of y. + // + left = yl; + right = yr; + if (_parent !is y) + { + if (isyleft) + yp.left = &this; + else + yp.right = &this; + } + color = yc; + } + + // if this has less than 2 children, remove it + if (_left !is null) + x = _left; + else + x = _right; + + bool deferedUnlink = false; + if (x is null) + { + // pretend this is a null node, defer unlinking the node + x = &this; + deferedUnlink = true; + } + else if (isLeftNode) + _parent.left = x; + else + _parent.right = x; + + // if the color of this is black, then it needs to be fixed + if (color == color.Black) + { + // need to recolor the tree. + while (x._parent !is end && x.color == Node.Color.Black) + { + if (x.isLeftNode) + { + // left node + Node w = x._parent._right; + if (w.color == Node.Color.Red) + { + w.color = Node.Color.Black; + x._parent.color = Node.Color.Red; + x._parent.rotateL(); + w = x._parent._right; + } + Node wl = w.left; + Node wr = w.right; + if ((wl is null || wl.color == Node.Color.Black) && + (wr is null || wr.color == Node.Color.Black)) + { + w.color = Node.Color.Red; + x = x._parent; + } + else + { + if (wr is null || wr.color == Node.Color.Black) + { + // wl cannot be null here + wl.color = Node.Color.Black; + w.color = Node.Color.Red; + w.rotateR(); + w = x._parent._right; + } + + w.color = x._parent.color; + x._parent.color = Node.Color.Black; + w._right.color = Node.Color.Black; + x._parent.rotateL(); + x = end.left; // x = root + } + } + else + { + // right node + Node w = x._parent._left; + if (w.color == Node.Color.Red) + { + w.color = Node.Color.Black; + x._parent.color = Node.Color.Red; + x._parent.rotateR(); + w = x._parent._left; + } + Node wl = w.left; + Node wr = w.right; + if ((wl is null || wl.color == Node.Color.Black) && + (wr is null || wr.color == Node.Color.Black)) + { + w.color = Node.Color.Red; + x = x._parent; + } + else + { + if (wl is null || wl.color == Node.Color.Black) + { + // wr cannot be null here + wr.color = Node.Color.Black; + w.color = Node.Color.Red; + w.rotateL(); + w = x._parent._left; + } + + w.color = x._parent.color; + x._parent.color = Node.Color.Black; + w._left.color = Node.Color.Black; + x._parent.rotateR(); + x = end.left; // x = root + } + } + } + x.color = Node.Color.Black; + } + + if (deferedUnlink) + { + // + // unlink this node from the tree + // + if (isLeftNode) + _parent.left = null; + else + _parent.right = null; + } + + // clean references to help GC - Bugzilla 12915 + _left = _right = _parent = null; + + return ret; + } + + /** + * Return the leftmost descendant of this node. + */ + @property inout(RBNode)* leftmost() inout + { + inout(RBNode)* result = &this; + while (result._left !is null) + result = result._left; + return result; + } + + /** + * Return the rightmost descendant of this node + */ + @property inout(RBNode)* rightmost() inout + { + inout(RBNode)* result = &this; + while (result._right !is null) + result = result._right; + return result; + } + + /** + * Returns the next valued node in the tree. + * + * You should never call this on the marker node, as it is assumed that + * there is a valid next node. + */ + @property inout(RBNode)* next() inout + { + inout(RBNode)* n = &this; + if (n.right is null) + { + while (!n.isLeftNode) + n = n._parent; + return n._parent; + } + else + return n.right.leftmost; + } + + /** + * Returns the previous valued node in the tree. + * + * You should never call this on the leftmost node of the tree as it is + * assumed that there is a valid previous node. + */ + @property inout(RBNode)* prev() inout + { + inout(RBNode)* n = &this; + if (n.left is null) + { + while (n.isLeftNode) + n = n._parent; + return n._parent; + } + else + return n.left.rightmost; + } + + Node dup(scope Node delegate(V v) alloc) + { + // + // duplicate this and all child nodes + // + // The recursion should be lg(n), so we shouldn't have to worry about + // stack size. + // + Node copy = alloc(value); + copy.color = color; + if (_left !is null) + copy.left = _left.dup(alloc); + if (_right !is null) + copy.right = _right.dup(alloc); + return copy; + } + + Node dup() + { + Node copy = new RBNode!V(null, null, null, value); + copy.color = color; + if (_left !is null) + copy.left = _left.dup(); + if (_right !is null) + copy.right = _right.dup(); + return copy; + } +} + +//constness checks +@safe pure unittest +{ + const RBNode!int n; + static assert(is(typeof(n.leftmost))); + static assert(is(typeof(n.rightmost))); + static assert(is(typeof(n.next))); + static assert(is(typeof(n.prev))); +} + +private struct RBRange(N) +{ + alias Node = N; + alias Elem = typeof(Node.value); + + private Node _begin; + private Node _end; + + private this(Node b, Node e) + { + _begin = b; + _end = e; + } + + /** + * Returns $(D true) if the range is _empty + */ + @property bool empty() const + { + return _begin is _end; + } + + /** + * Returns the first element in the range + */ + @property Elem front() + { + return _begin.value; + } + + /** + * Returns the last element in the range + */ + @property Elem back() + { + return _end.prev.value; + } + + /** + * pop the front element from the range + * + * Complexity: amortized $(BIGOH 1) + */ + void popFront() + { + _begin = _begin.next; + } + + /** + * pop the back element from the range + * + * Complexity: amortized $(BIGOH 1) + */ + void popBack() + { + _end = _end.prev; + } + + /** + * Trivial _save implementation, needed for $(D isForwardRange). + */ + @property RBRange save() + { + return this; + } +} + +/** + * Implementation of a $(LINK2 https://en.wikipedia.org/wiki/Red%E2%80%93black_tree, + * red-black tree) container. + * + * All inserts, removes, searches, and any function in general has complexity + * of $(BIGOH lg(n)). + * + * To use a different comparison than $(D "a < b"), pass a different operator string + * that can be used by $(REF binaryFun, std,functional), or pass in a + * function, delegate, functor, or any type where $(D less(a, b)) results in a $(D bool) + * value. + * + * Note that less should produce a strict ordering. That is, for two unequal + * elements $(D a) and $(D b), $(D less(a, b) == !less(b, a)). $(D less(a, a)) should + * always equal $(D false). + * + * If $(D allowDuplicates) is set to $(D true), then inserting the same element more than + * once continues to add more elements. If it is $(D false), duplicate elements are + * ignored on insertion. If duplicates are allowed, then new elements are + * inserted after all existing duplicate elements. + */ +final class RedBlackTree(T, alias less = "a < b", bool allowDuplicates = false) +if (is(typeof(binaryFun!less(T.init, T.init)))) +{ + import std.meta : allSatisfy; + import std.range : Take; + import std.range.primitives : isInputRange, walkLength; + import std.traits : isIntegral, isDynamicArray, isImplicitlyConvertible; + + alias _less = binaryFun!less; + + version (unittest) + { + static if (is(typeof(less) == string)) + { + private enum doUnittest = isIntegral!T && (less == "a < b" || less == "a > b"); + } + else + enum doUnittest = false; + + // note, this must be final so it does not affect the vtable layout + bool arrayEqual(T[] arr) + { + if (walkLength(this[]) == arr.length) + { + foreach (v; arr) + { + if (!(v in this)) + return false; + } + return true; + } + return false; + } + } + else + { + private enum doUnittest = false; + } + + /** + * Element type for the tree + */ + alias Elem = T; + + // used for convenience + private alias RBNode = .RBNode!Elem; + private alias Node = RBNode.Node; + + private Node _end; + private Node _begin; + private size_t _length; + + private void _setup() + { + assert(!_end); //Make sure that _setup isn't run more than once. + _begin = _end = allocate(); + } + + static private Node allocate() + { + return new RBNode; + } + + static private Node allocate(Elem v) + { + return new RBNode(null, null, null, v); + } + + /** + * The range types for $(D RedBlackTree) + */ + alias Range = RBRange!(RBNode*); + alias ConstRange = RBRange!(const(RBNode)*); /// Ditto + alias ImmutableRange = RBRange!(immutable(RBNode)*); /// Ditto + + static if (doUnittest) @safe pure unittest + { + import std.algorithm.comparison : equal; + import std.range.primitives; + auto ts = new RedBlackTree(1, 2, 3, 4, 5); + assert(ts.length == 5); + auto r = ts[]; + + static if (less == "a < b") + auto vals = [1, 2, 3, 4, 5]; + else + auto vals = [5, 4, 3, 2, 1]; + assert(equal(r, vals)); + + assert(r.front == vals.front); + assert(r.back != r.front); + auto oldfront = r.front; + auto oldback = r.back; + r.popFront(); + r.popBack(); + assert(r.front != r.back); + assert(r.front != oldfront); + assert(r.back != oldback); + assert(ts.length == 5); + } + + // find a node based on an element value + private inout(RBNode)* _find(Elem e) inout + { + static if (allowDuplicates) + { + inout(RBNode)* cur = _end.left; + inout(RBNode)* result = null; + while (cur) + { + if (_less(cur.value, e)) + cur = cur.right; + else if (_less(e, cur.value)) + cur = cur.left; + else + { + // want to find the left-most element + result = cur; + cur = cur.left; + } + } + return result; + } + else + { + inout(RBNode)* cur = _end.left; + while (cur) + { + if (_less(cur.value, e)) + cur = cur.right; + else if (_less(e, cur.value)) + cur = cur.left; + else + return cur; + } + return null; + } + } + + // add an element to the tree, returns the node added, or the existing node + // if it has already been added and allowDuplicates is false + private auto _add(Elem n) + { + Node result; + static if (!allowDuplicates) + bool added = true; + + if (!_end.left) + { + _end.left = _begin = result = allocate(n); + } + else + { + Node newParent = _end.left; + Node nxt; + while (true) + { + if (_less(n, newParent.value)) + { + nxt = newParent.left; + if (nxt is null) + { + // + // add to right of new parent + // + newParent.left = result = allocate(n); + break; + } + } + else + { + static if (!allowDuplicates) + { + if (!_less(newParent.value, n)) + { + result = newParent; + added = false; + break; + } + } + nxt = newParent.right; + if (nxt is null) + { + // + // add to right of new parent + // + newParent.right = result = allocate(n); + break; + } + } + newParent = nxt; + } + if (_begin.left) + _begin = _begin.left; + } + + static if (allowDuplicates) + { + result.setColor(_end); + debug(RBDoChecks) + check(); + ++_length; + return result; + } + else + { + import std.typecons : Tuple; + + if (added) + { + ++_length; + result.setColor(_end); + } + debug(RBDoChecks) + check(); + return Tuple!(bool, "added", Node, "n")(added, result); + } + } + + + /** + * Check if any elements exist in the container. Returns $(D false) if at least + * one element exists. + */ + @property bool empty() + { + return _end.left is null; + } + + /++ + Returns the number of elements in the container. + + Complexity: $(BIGOH 1). + +/ + @property size_t length() const + { + return _length; + } + + /** + * Duplicate this container. The resulting container contains a shallow + * copy of the elements. + * + * Complexity: $(BIGOH n) + */ + @property RedBlackTree dup() + { + return new RedBlackTree(_end.dup(), _length); + } + + static if (doUnittest) @safe pure unittest + { + import std.algorithm.comparison : equal; + auto ts = new RedBlackTree(1, 2, 3, 4, 5); + assert(ts.length == 5); + auto ts2 = ts.dup; + assert(ts2.length == 5); + assert(equal(ts[], ts2[])); + ts2.insert(cast(Elem) 6); + assert(!equal(ts[], ts2[])); + assert(ts.length == 5 && ts2.length == 6); + } + + /** + * Fetch a range that spans all the elements in the container. + * + * Complexity: $(BIGOH 1) + */ + Range opSlice() + { + return Range(_begin, _end); + } + + /// Ditto + ConstRange opSlice() const + { + return ConstRange(_begin, _end); + } + + /// Ditto + ImmutableRange opSlice() immutable + { + return ImmutableRange(_begin, _end); + } + + /** + * The front element in the container + * + * Complexity: $(BIGOH 1) + */ + Elem front() + { + return _begin.value; + } + + /** + * The last element in the container + * + * Complexity: $(BIGOH log(n)) + */ + Elem back() + { + return _end.prev.value; + } + + /++ + $(D in) operator. Check to see if the given element exists in the + container. + + Complexity: $(BIGOH log(n)) + +/ + bool opBinaryRight(string op)(Elem e) const if (op == "in") + { + return _find(e) !is null; + } + + static if (doUnittest) @safe pure unittest + { + auto ts = new RedBlackTree(1, 2, 3, 4, 5); + assert(cast(Elem) 3 in ts); + assert(cast(Elem) 6 !in ts); + } + + /** + * Compares two trees for equality. + * + * Complexity: $(BIGOH n) + */ + override bool opEquals(Object rhs) + { + import std.algorithm.comparison : equal; + + RedBlackTree that = cast(RedBlackTree) rhs; + if (that is null) return false; + + // If there aren't the same number of nodes, we can't be equal. + if (this._length != that._length) return false; + + auto thisRange = this[]; + auto thatRange = that[]; + return equal!(function(Elem a, Elem b) => !_less(a,b) && !_less(b,a)) + (thisRange, thatRange); + } + + static if (doUnittest) @system unittest + { + auto t1 = new RedBlackTree(1,2,3,4); + auto t2 = new RedBlackTree(1,2,3,4); + auto t3 = new RedBlackTree(1,2,3,5); + auto t4 = new RedBlackTree(1,2,3,4,5); + auto o = new Object(); + + assert(t1 == t1); + assert(t1 == t2); + assert(t1 != t3); + assert(t1 != t4); + assert(t1 != o); // pathological case, must not crash + } + + /** + * Removes all elements from the container. + * + * Complexity: $(BIGOH 1) + */ + void clear() + { + _end.left = null; + _begin = _end; + _length = 0; + } + + static if (doUnittest) @safe pure unittest + { + auto ts = new RedBlackTree(1,2,3,4,5); + assert(ts.length == 5); + ts.clear(); + assert(ts.empty && ts.length == 0); + } + + /** + * Insert a single element in the container. Note that this does not + * invalidate any ranges currently iterating the container. + * + * Returns: The number of elements inserted. + * + * Complexity: $(BIGOH log(n)) + */ + size_t stableInsert(Stuff)(Stuff stuff) if (isImplicitlyConvertible!(Stuff, Elem)) + { + static if (allowDuplicates) + { + _add(stuff); + return 1; + } + else + { + return(_add(stuff).added ? 1 : 0); + } + } + + /** + * Insert a range of elements in the container. Note that this does not + * invalidate any ranges currently iterating the container. + * + * Returns: The number of elements inserted. + * + * Complexity: $(BIGOH m * log(n)) + */ + size_t stableInsert(Stuff)(Stuff stuff) if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, Elem)) + { + size_t result = 0; + static if (allowDuplicates) + { + foreach (e; stuff) + { + ++result; + _add(e); + } + } + else + { + foreach (e; stuff) + { + if (_add(e).added) + ++result; + } + } + return result; + } + + /// ditto + alias insert = stableInsert; + + static if (doUnittest) @safe pure unittest + { + auto ts = new RedBlackTree(2,1,3,4,5,2,5); + static if (allowDuplicates) + { + assert(ts.length == 7); + assert(ts.stableInsert(cast(Elem[])[7, 8, 6, 9, 10, 8]) == 6); + assert(ts.length == 13); + assert(ts.stableInsert(cast(Elem) 11) == 1 && ts.length == 14); + assert(ts.stableInsert(cast(Elem) 7) == 1 && ts.length == 15); + + static if (less == "a < b") + assert(ts.arrayEqual([1,2,2,3,4,5,5,6,7,7,8,8,9,10,11])); + else + assert(ts.arrayEqual([11,10,9,8,8,7,7,6,5,5,4,3,2,2,1])); + } + else + { + assert(ts.length == 5); + Elem[] elems = [7, 8, 6, 9, 10, 8]; + assert(ts.stableInsert(elems) == 5); + assert(ts.length == 10); + assert(ts.stableInsert(cast(Elem) 11) == 1 && ts.length == 11); + assert(ts.stableInsert(cast(Elem) 7) == 0 && ts.length == 11); + + static if (less == "a < b") + assert(ts.arrayEqual([1,2,3,4,5,6,7,8,9,10,11])); + else + assert(ts.arrayEqual([11,10,9,8,7,6,5,4,3,2,1])); + } + } + + /** + * Remove an element from the container and return its value. + * + * Complexity: $(BIGOH log(n)) + */ + Elem removeAny() + { + scope(success) + --_length; + auto n = _begin; + auto result = n.value; + _begin = n.remove(_end); + debug(RBDoChecks) + check(); + return result; + } + + static if (doUnittest) @safe pure unittest + { + auto ts = new RedBlackTree(1,2,3,4,5); + assert(ts.length == 5); + auto x = ts.removeAny(); + assert(ts.length == 4); + Elem[] arr; + foreach (Elem i; 1 .. 6) + if (i != x) arr ~= i; + assert(ts.arrayEqual(arr)); + } + + /** + * Remove the front element from the container. + * + * Complexity: $(BIGOH log(n)) + */ + void removeFront() + { + scope(success) + --_length; + _begin = _begin.remove(_end); + debug(RBDoChecks) + check(); + } + + /** + * Remove the back element from the container. + * + * Complexity: $(BIGOH log(n)) + */ + void removeBack() + { + scope(success) + --_length; + auto lastnode = _end.prev; + if (lastnode is _begin) + _begin = _begin.remove(_end); + else + lastnode.remove(_end); + debug(RBDoChecks) + check(); + } + + static if (doUnittest) @safe pure unittest + { + auto ts = new RedBlackTree(1,2,3,4,5); + assert(ts.length == 5); + ts.removeBack(); + assert(ts.length == 4); + + static if (less == "a < b") + assert(ts.arrayEqual([1,2,3,4])); + else + assert(ts.arrayEqual([2,3,4,5])); + + ts.removeFront(); + assert(ts.arrayEqual([2,3,4]) && ts.length == 3); + } + + /++ + Removes the given range from the container. + + Returns: A range containing all of the elements that were after the + given range. + + Complexity: $(BIGOH m * log(n)) (where m is the number of elements in + the range) + +/ + Range remove(Range r) + { + auto b = r._begin; + auto e = r._end; + if (_begin is b) + _begin = e; + while (b !is e) + { + b = b.remove(_end); + --_length; + } + debug(RBDoChecks) + check(); + return Range(e, _end); + } + + static if (doUnittest) @safe pure unittest + { + import std.algorithm.comparison : equal; + auto ts = new RedBlackTree(1,2,3,4,5); + assert(ts.length == 5); + auto r = ts[]; + r.popFront(); + r.popBack(); + assert(ts.length == 5); + auto r2 = ts.remove(r); + assert(ts.length == 2); + assert(ts.arrayEqual([1,5])); + + static if (less == "a < b") + assert(equal(r2, [5])); + else + assert(equal(r2, [1])); + } + + /++ + Removes the given $(D Take!Range) from the container + + Returns: A range containing all of the elements that were after the + given range. + + Complexity: $(BIGOH m * log(n)) (where m is the number of elements in + the range) + +/ + Range remove(Take!Range r) + { + immutable isBegin = (r.source._begin is _begin); + auto b = r.source._begin; + + while (!r.empty) + { + r.popFront(); + b = b.remove(_end); + --_length; + } + + if (isBegin) + _begin = b; + + return Range(b, _end); + } + + static if (doUnittest) @safe pure unittest + { + import std.algorithm.comparison : equal; + import std.range : take; + auto ts = new RedBlackTree(1,2,3,4,5); + auto r = ts[]; + r.popFront(); + assert(ts.length == 5); + auto r2 = ts.remove(take(r, 0)); + + static if (less == "a < b") + { + assert(equal(r2, [2,3,4,5])); + auto r3 = ts.remove(take(r, 2)); + assert(ts.arrayEqual([1,4,5]) && ts.length == 3); + assert(equal(r3, [4,5])); + } + else + { + assert(equal(r2, [4,3,2,1])); + auto r3 = ts.remove(take(r, 2)); + assert(ts.arrayEqual([5,2,1]) && ts.length == 3); + assert(equal(r3, [2,1])); + } + } + + /++ + Removes elements from the container that are equal to the given values + according to the less comparator. One element is removed for each value + given which is in the container. If $(D allowDuplicates) is true, + duplicates are removed only if duplicate values are given. + + Returns: The number of elements removed. + + Complexity: $(BIGOH m log(n)) (where m is the number of elements to remove) + + Example: +-------------------- +auto rbt = redBlackTree!true(0, 1, 1, 1, 4, 5, 7); +rbt.removeKey(1, 4, 7); +assert(equal(rbt[], [0, 1, 1, 5])); +rbt.removeKey(1, 1, 0); +assert(equal(rbt[], [5])); +-------------------- + +/ + size_t removeKey(U...)(U elems) + if (allSatisfy!(isImplicitlyConvertibleToElem, U)) + { + Elem[U.length] toRemove = [elems]; + return removeKey(toRemove[]); + } + + /++ Ditto +/ + size_t removeKey(U)(U[] elems) + if (isImplicitlyConvertible!(U, Elem)) + { + immutable lenBefore = length; + + foreach (e; elems) + { + auto beg = _firstGreaterEqual(e); + if (beg is _end || _less(e, beg.value)) + // no values are equal + continue; + immutable isBegin = (beg is _begin); + beg = beg.remove(_end); + if (isBegin) + _begin = beg; + --_length; + } + + return lenBefore - length; + } + + /++ Ditto +/ + size_t removeKey(Stuff)(Stuff stuff) + if (isInputRange!Stuff && + isImplicitlyConvertible!(ElementType!Stuff, Elem) && + !isDynamicArray!Stuff) + { + import std.array : array; + //We use array in case stuff is a Range from this RedBlackTree - either + //directly or indirectly. + return removeKey(array(stuff)); + } + + //Helper for removeKey. + private template isImplicitlyConvertibleToElem(U) + { + enum isImplicitlyConvertibleToElem = isImplicitlyConvertible!(U, Elem); + } + + static if (doUnittest) @safe pure unittest + { + import std.algorithm.comparison : equal; + import std.range : take; + auto rbt = new RedBlackTree(5, 4, 3, 7, 2, 1, 7, 6, 2, 19, 45); + + //The cast(Elem) is because these tests are instantiated with a variety + //of numeric types, and the literals are all int, which is not always + //implicitly convertible to Elem (e.g. short). + static if (allowDuplicates) + { + assert(rbt.length == 11); + assert(rbt.removeKey(cast(Elem) 4) == 1 && rbt.length == 10); + assert(rbt.arrayEqual([1,2,2,3,5,6,7,7,19,45]) && rbt.length == 10); + + assert(rbt.removeKey(cast(Elem) 6, cast(Elem) 2, cast(Elem) 1) == 3); + assert(rbt.arrayEqual([2,3,5,7,7,19,45]) && rbt.length == 7); + + assert(rbt.removeKey(cast(Elem)(42)) == 0 && rbt.length == 7); + assert(rbt.removeKey(take(rbt[], 3)) == 3 && rbt.length == 4); + + static if (less == "a < b") + assert(equal(rbt[], [7,7,19,45])); + else + assert(equal(rbt[], [7,5,3,2])); + } + else + { + assert(rbt.length == 9); + assert(rbt.removeKey(cast(Elem) 4) == 1 && rbt.length == 8); + assert(rbt.arrayEqual([1,2,3,5,6,7,19,45])); + + assert(rbt.removeKey(cast(Elem) 6, cast(Elem) 2, cast(Elem) 1) == 3); + assert(rbt.arrayEqual([3,5,7,19,45]) && rbt.length == 5); + + assert(rbt.removeKey(cast(Elem)(42)) == 0 && rbt.length == 5); + assert(rbt.removeKey(take(rbt[], 3)) == 3 && rbt.length == 2); + + static if (less == "a < b") + assert(equal(rbt[], [19,45])); + else + assert(equal(rbt[], [5,3])); + } + } + + // find the first node where the value is > e + private inout(RBNode)* _firstGreater(Elem e) inout + { + // can't use _find, because we cannot return null + auto cur = _end.left; + inout(RBNode)* result = _end; + while (cur) + { + if (_less(e, cur.value)) + { + result = cur; + cur = cur.left; + } + else + cur = cur.right; + } + return result; + } + + // find the first node where the value is >= e + private inout(RBNode)* _firstGreaterEqual(Elem e) inout + { + // can't use _find, because we cannot return null. + auto cur = _end.left; + inout(RBNode)* result = _end; + while (cur) + { + if (_less(cur.value, e)) + cur = cur.right; + else + { + result = cur; + cur = cur.left; + } + + } + return result; + } + + /** + * Get a range from the container with all elements that are > e according + * to the less comparator + * + * Complexity: $(BIGOH log(n)) + */ + Range upperBound(Elem e) + { + return Range(_firstGreater(e), _end); + } + + /// Ditto + ConstRange upperBound(Elem e) const + { + return ConstRange(_firstGreater(e), _end); + } + + /// Ditto + ImmutableRange upperBound(Elem e) immutable + { + return ImmutableRange(_firstGreater(e), _end); + } + + /** + * Get a range from the container with all elements that are < e according + * to the less comparator + * + * Complexity: $(BIGOH log(n)) + */ + Range lowerBound(Elem e) + { + return Range(_begin, _firstGreaterEqual(e)); + } + + /// Ditto + ConstRange lowerBound(Elem e) const + { + return ConstRange(_begin, _firstGreaterEqual(e)); + } + + /// Ditto + ImmutableRange lowerBound(Elem e) immutable + { + return ImmutableRange(_begin, _firstGreaterEqual(e)); + } + + /** + * Get a range from the container with all elements that are == e according + * to the less comparator + * + * Complexity: $(BIGOH log(n)) + */ + auto equalRange(this This)(Elem e) + { + auto beg = _firstGreaterEqual(e); + alias RangeType = RBRange!(typeof(beg)); + if (beg is _end || _less(e, beg.value)) + // no values are equal + return RangeType(beg, beg); + static if (allowDuplicates) + { + return RangeType(beg, _firstGreater(e)); + } + else + { + // no sense in doing a full search, no duplicates are allowed, + // so we just get the next node. + return RangeType(beg, beg.next); + } + } + + static if (doUnittest) @safe pure unittest + { + import std.algorithm.comparison : equal; + auto ts = new RedBlackTree(1, 2, 3, 4, 5); + auto rl = ts.lowerBound(3); + auto ru = ts.upperBound(3); + auto re = ts.equalRange(3); + + static if (less == "a < b") + { + assert(equal(rl, [1,2])); + assert(equal(ru, [4,5])); + } + else + { + assert(equal(rl, [5,4])); + assert(equal(ru, [2,1])); + } + + assert(equal(re, [3])); + } + + debug(RBDoChecks) + { + /* + * Print the tree. This prints a sideways view of the tree in ASCII form, + * with the number of indentations representing the level of the nodes. + * It does not print values, only the tree structure and color of nodes. + */ + void printTree(Node n, int indent = 0) + { + import std.stdio : write, writeln; + if (n !is null) + { + printTree(n.right, indent + 2); + for (int i = 0; i < indent; i++) + write("."); + writeln(n.color == n.color.Black ? "B" : "R"); + printTree(n.left, indent + 2); + } + else + { + for (int i = 0; i < indent; i++) + write("."); + writeln("N"); + } + if (indent is 0) + writeln(); + } + + /* + * Check the tree for validity. This is called after every add or remove. + * This should only be enabled to debug the implementation of the RB Tree. + */ + void check() + { + // + // check implementation of the tree + // + int recurse(Node n, string path) + { + import std.stdio : writeln; + if (n is null) + return 1; + if (n.parent.left !is n && n.parent.right !is n) + throw new Exception("Node at path " ~ path ~ " has inconsistent pointers"); + Node next = n.next; + static if (allowDuplicates) + { + if (next !is _end && _less(next.value, n.value)) + throw new Exception("ordering invalid at path " ~ path); + } + else + { + if (next !is _end && !_less(n.value, next.value)) + throw new Exception("ordering invalid at path " ~ path); + } + if (n.color == n.color.Red) + { + if ((n.left !is null && n.left.color == n.color.Red) || + (n.right !is null && n.right.color == n.color.Red)) + throw new Exception("Node at path " ~ path ~ " is red with a red child"); + } + + int l = recurse(n.left, path ~ "L"); + int r = recurse(n.right, path ~ "R"); + if (l != r) + { + writeln("bad tree at:"); + debug printTree(n); + throw new Exception( + "Node at path " ~ path ~ " has different number of black nodes on left and right paths" + ); + } + return l + (n.color == n.color.Black ? 1 : 0); + } + + try + { + recurse(_end.left, ""); + } + catch (Exception e) + { + debug printTree(_end.left, 0); + throw e; + } + } + } + + /** + Formats the RedBlackTree into a sink function. For more info see $(D + std.format.formatValue). Note that this only is available when the + element type can be formatted. Otherwise, the default toString from + Object is used. + */ + static if (is(typeof((){FormatSpec!(char) fmt; formatValue((const(char)[]) {}, ConstRange.init, fmt);}))) + { + void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const { + sink("RedBlackTree("); + sink.formatValue(this[], fmt); + sink(")"); + } + } + + /** + * Constructor. Pass in an array of elements, or individual elements to + * initialize the tree with. + */ + this(Elem[] elems...) + { + _setup(); + stableInsert(elems); + } + + /** + * Constructor. Pass in a range of elements to initialize the tree with. + */ + this(Stuff)(Stuff stuff) if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, Elem)) + { + _setup(); + stableInsert(stuff); + } + + /// + this() + { + _setup(); + } + + private this(Node end, size_t length) + { + _end = end; + _begin = end.leftmost; + _length = length; + } +} + +//Verify Example for removeKey. +@safe pure unittest +{ + import std.algorithm.comparison : equal; + auto rbt = redBlackTree!true(0, 1, 1, 1, 4, 5, 7); + rbt.removeKey(1, 4, 7); + assert(equal(rbt[], [0, 1, 1, 5])); + rbt.removeKey(1, 1, 0); + assert(equal(rbt[], [5])); +} + +//Tests for removeKey +@safe pure unittest +{ + import std.algorithm.comparison : equal; + { + auto rbt = redBlackTree(["hello", "world", "foo", "bar"]); + assert(equal(rbt[], ["bar", "foo", "hello", "world"])); + assert(rbt.removeKey("hello") == 1); + assert(equal(rbt[], ["bar", "foo", "world"])); + assert(rbt.removeKey("hello") == 0); + assert(equal(rbt[], ["bar", "foo", "world"])); + assert(rbt.removeKey("hello", "foo", "bar") == 2); + assert(equal(rbt[], ["world"])); + assert(rbt.removeKey(["", "world", "hello"]) == 1); + assert(rbt.empty); + } + + { + auto rbt = redBlackTree([1, 2, 12, 27, 4, 500]); + assert(equal(rbt[], [1, 2, 4, 12, 27, 500])); + assert(rbt.removeKey(1u) == 1); + assert(equal(rbt[], [2, 4, 12, 27, 500])); + assert(rbt.removeKey(cast(byte) 1) == 0); + assert(equal(rbt[], [2, 4, 12, 27, 500])); + assert(rbt.removeKey(1, 12u, cast(byte) 27) == 2); + assert(equal(rbt[], [2, 4, 500])); + assert(rbt.removeKey([cast(short) 0, cast(short) 500, cast(short) 1]) == 1); + assert(equal(rbt[], [2, 4])); + } +} + +@safe pure unittest +{ + void test(T)() + { + auto rt1 = new RedBlackTree!(T, "a < b", false)(); + auto rt2 = new RedBlackTree!(T, "a < b", true)(); + auto rt3 = new RedBlackTree!(T, "a > b", false)(); + auto rt4 = new RedBlackTree!(T, "a > b", true)(); + } + + test!long(); + test!ulong(); + test!int(); + test!uint(); + test!short(); + test!ushort(); + test!byte(); + test!byte(); +} + +import std.range.primitives : isInputRange, isSomeString, ElementType; +import std.traits : isArray; + +/++ + Convenience function for creating a $(D RedBlackTree!E) from a list of + values. + + Params: + allowDuplicates = Whether duplicates should be allowed (optional, default: false) + less = predicate to sort by (optional) + elems = elements to insert into the rbtree (variadic arguments) + range = range elements to insert into the rbtree (alternative to elems) + +/ +auto redBlackTree(E)(E[] elems...) +{ + return new RedBlackTree!E(elems); +} + +/++ Ditto +/ +auto redBlackTree(bool allowDuplicates, E)(E[] elems...) +{ + return new RedBlackTree!(E, "a < b", allowDuplicates)(elems); +} + +/++ Ditto +/ +auto redBlackTree(alias less, E)(E[] elems...) +if (is(typeof(binaryFun!less(E.init, E.init)))) +{ + return new RedBlackTree!(E, less)(elems); +} + +/++ Ditto +/ +auto redBlackTree(alias less, bool allowDuplicates, E)(E[] elems...) +if (is(typeof(binaryFun!less(E.init, E.init)))) +{ + //We shouldn't need to instantiate less here, but for some reason, + //dmd can't handle it if we don't (even though the template which + //takes less but not allowDuplicates works just fine). + return new RedBlackTree!(E, binaryFun!less, allowDuplicates)(elems); +} + +/++ Ditto +/ +auto redBlackTree(Stuff)(Stuff range) +if (isInputRange!Stuff && !isArray!(Stuff)) +{ + return new RedBlackTree!(ElementType!Stuff)(range); +} + +/++ Ditto +/ +auto redBlackTree(bool allowDuplicates, Stuff)(Stuff range) +if (isInputRange!Stuff && !isArray!(Stuff)) +{ + return new RedBlackTree!(ElementType!Stuff, "a < b", allowDuplicates)(range); +} + +/++ Ditto +/ +auto redBlackTree(alias less, Stuff)(Stuff range) +if ( is(typeof(binaryFun!less((ElementType!Stuff).init, (ElementType!Stuff).init))) + && isInputRange!Stuff && !isArray!(Stuff)) +{ + return new RedBlackTree!(ElementType!Stuff, less)(range); +} + +/++ Ditto +/ +auto redBlackTree(alias less, bool allowDuplicates, Stuff)(Stuff range) +if ( is(typeof(binaryFun!less((ElementType!Stuff).init, (ElementType!Stuff).init))) + && isInputRange!Stuff && !isArray!(Stuff)) +{ + //We shouldn't need to instantiate less here, but for some reason, + //dmd can't handle it if we don't (even though the template which + //takes less but not allowDuplicates works just fine). + return new RedBlackTree!(ElementType!Stuff, binaryFun!less, allowDuplicates)(range); +} + +/// +@safe pure unittest +{ + import std.range : iota; + + auto rbt1 = redBlackTree(0, 1, 5, 7); + auto rbt2 = redBlackTree!string("hello", "world"); + auto rbt3 = redBlackTree!true(0, 1, 5, 7, 5); + auto rbt4 = redBlackTree!"a > b"(0, 1, 5, 7); + auto rbt5 = redBlackTree!("a > b", true)(0.1, 1.3, 5.9, 7.2, 5.9); + + // also works with ranges + auto rbt6 = redBlackTree(iota(3)); + auto rbt7 = redBlackTree!true(iota(3)); + auto rbt8 = redBlackTree!"a > b"(iota(3)); + auto rbt9 = redBlackTree!("a > b", true)(iota(3)); +} + +//Combinations not in examples. +@safe pure unittest +{ + auto rbt1 = redBlackTree!(true, string)("hello", "hello"); + auto rbt2 = redBlackTree!((a, b){return a < b;}, double)(5.1, 2.3); + auto rbt3 = redBlackTree!("a > b", true, string)("hello", "world"); +} + +//Range construction. +@safe pure unittest +{ + import std.algorithm.comparison : equal; + import std.range : iota; + auto rbt = new RedBlackTree!(int, "a > b")(iota(5)); + assert(equal(rbt[], [4, 3, 2, 1, 0])); +} + + +// construction with arrays +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + auto rbt = redBlackTree!"a > b"([0, 1, 2, 3, 4]); + assert(equal(rbt[], [4, 3, 2, 1, 0])); + + auto rbt2 = redBlackTree!"a > b"(["a", "b"]); + assert(equal(rbt2[], ["b", "a"])); + + auto rbt3 = redBlackTree!"a > b"([1, 2]); + assert(equal(rbt3[], [2, 1])); + + auto rbt4 = redBlackTree([0, 1, 7, 5]); + assert(equal(rbt4[], [0, 1, 5, 7])); + + auto rbt5 = redBlackTree(["hello", "world"]); + assert(equal(rbt5[], ["hello", "world"])); + + auto rbt6 = redBlackTree!true([0, 1, 5, 7, 5]); + assert(equal(rbt6[], [0, 1, 5, 5, 7])); + + auto rbt7 = redBlackTree!"a > b"([0, 1, 5, 7]); + assert(equal(rbt7[], [7, 5, 1, 0])); + + auto rbt8 = redBlackTree!("a > b", true)([0.1, 1.3, 5.9, 7.2, 5.9]); + assert(equal(rbt8[], [7.2, 5.9, 5.9, 1.3, 0.1])); +} + +// convenience wrapper range construction +@safe pure unittest +{ + import std.algorithm.comparison : equal; + import std.range : chain, iota; + + auto rbt = redBlackTree(iota(3)); + assert(equal(rbt[], [0, 1, 2])); + + auto rbt2 = redBlackTree!"a > b"(iota(2)); + assert(equal(rbt2[], [1, 0])); + + auto rbt3 = redBlackTree(chain([0, 1], [7, 5])); + assert(equal(rbt3[], [0, 1, 5, 7])); + + auto rbt4 = redBlackTree(chain(["hello"], ["world"])); + assert(equal(rbt4[], ["hello", "world"])); + + auto rbt5 = redBlackTree!true(chain([0, 1], [5, 7, 5])); + assert(equal(rbt5[], [0, 1, 5, 5, 7])); + + auto rbt6 = redBlackTree!("a > b", true)(chain([0.1, 1.3], [5.9, 7.2, 5.9])); + assert(equal(rbt6[], [7.2, 5.9, 5.9, 1.3, 0.1])); +} + +@safe pure unittest +{ + import std.array : array; + + auto rt1 = redBlackTree(5, 4, 3, 2, 1); + assert(rt1.length == 5); + assert(array(rt1[]) == [1, 2, 3, 4, 5]); + + auto rt2 = redBlackTree!"a > b"(1.1, 2.1); + assert(rt2.length == 2); + assert(array(rt2[]) == [2.1, 1.1]); + + auto rt3 = redBlackTree!true(5, 5, 4); + assert(rt3.length == 3); + assert(array(rt3[]) == [4, 5, 5]); + + auto rt4 = redBlackTree!string("hello", "hello"); + assert(rt4.length == 1); + assert(array(rt4[]) == ["hello"]); +} + +@system unittest +{ + import std.conv : to; + + auto rt1 = redBlackTree!string(); + assert(rt1.to!string == "RedBlackTree([])"); + + auto rt2 = redBlackTree!string("hello"); + assert(rt2.to!string == "RedBlackTree([\"hello\"])"); + + auto rt3 = redBlackTree!string("hello", "world", "!"); + assert(rt3.to!string == "RedBlackTree([\"!\", \"hello\", \"world\"])"); + + // type deduction can be done automatically + auto rt4 = redBlackTree(["hello"]); + assert(rt4.to!string == "RedBlackTree([\"hello\"])"); +} + +//constness checks +@safe pure unittest +{ + const rt1 = redBlackTree(5,4,3,2,1); + static assert(is(typeof(rt1.length))); + static assert(is(typeof(5 in rt1))); + + static assert(is(typeof(rt1.upperBound(3).front) == const(int))); + import std.algorithm.comparison : equal; + assert(rt1.upperBound(3).equal([4, 5])); + assert(rt1.lowerBound(3).equal([1, 2])); + assert(rt1.equalRange(3).equal([3])); + assert(rt1[].equal([1, 2, 3, 4, 5])); +} + +//immutable checks +@safe pure unittest +{ + immutable rt1 = redBlackTree(5,4,3,2,1); + static assert(is(typeof(rt1.length))); + + static assert(is(typeof(rt1.upperBound(3).front) == immutable(int))); + import std.algorithm.comparison : equal; + assert(rt1.upperBound(2).equal([3, 4, 5])); +} + +// issue 15941 +@safe pure unittest +{ + class C {} + RedBlackTree!(C, "cast(void*)a < cast(void*) b") tree; +} + +@safe pure unittest // const/immutable elements (issue 17519) +{ + RedBlackTree!(immutable int) t1; + RedBlackTree!(const int) t2; + + import std.algorithm.iteration : map; + static struct S { int* p; } + auto t3 = new RedBlackTree!(immutable S, (a, b) => *a.p < *b.p); + t3.insert([1, 2, 3].map!(x => immutable S(new int(x)))); + static assert(!__traits(compiles, *t3.front.p = 4)); + assert(*t3.front.p == 1); +} diff --git a/libphobos/src/std/container/slist.d b/libphobos/src/std/container/slist.d new file mode 100644 index 0000000..820b0bb --- /dev/null +++ b/libphobos/src/std/container/slist.d @@ -0,0 +1,846 @@ +/** +This module implements a singly-linked list container. +It can be used as a stack. + +This module is a submodule of $(MREF std, container). + +Source: $(PHOBOSSRC std/container/_slist.d) + +Copyright: 2010- Andrei Alexandrescu. All rights reserved by the respective holders. + +License: Distributed under the Boost Software License, Version 1.0. +(See accompanying file LICENSE_1_0.txt or copy at $(HTTP +boost.org/LICENSE_1_0.txt)). + +Authors: $(HTTP erdani.com, Andrei Alexandrescu) + +$(SCRIPT inhibitQuickIndex = 1;) +*/ +module std.container.slist; + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.container : SList; + + auto s = SList!int(1, 2, 3); + assert(equal(s[], [1, 2, 3])); + + s.removeFront(); + assert(equal(s[], [2, 3])); + + s.insertFront([5, 6]); + assert(equal(s[], [5, 6, 2, 3])); + + // If you want to apply range operations, simply slice it. + import std.algorithm.searching : countUntil; + import std.range : popFrontN, walkLength; + + auto sl = SList!int(1, 2, 3, 4, 5); + assert(countUntil(sl[], 2) == 1); + + auto r = sl[]; + popFrontN(r, 2); + assert(walkLength(r) == 3); +} + +public import std.container.util; + +/** + Implements a simple and fast singly-linked list. + It can be used as a stack. + + $(D SList) uses reference semantics. + */ +struct SList(T) +{ + import std.exception : enforce; + import std.range : Take; + import std.range.primitives : isInputRange, isForwardRange, ElementType; + import std.traits : isImplicitlyConvertible; + + private struct Node + { + Node * _next; + T _payload; + } + private struct NodeWithoutPayload + { + Node* _next; + } + static assert(NodeWithoutPayload._next.offsetof == Node._next.offsetof); + + private Node * _root; + + private void initialize() @trusted nothrow pure + { + if (_root) return; + _root = cast (Node*) new NodeWithoutPayload(); + } + + private ref inout(Node*) _first() @property @safe nothrow pure inout + { + assert(_root); + return _root._next; + } + + private static Node * findLastNode(Node * n) + { + assert(n); + auto ahead = n._next; + while (ahead) + { + n = ahead; + ahead = n._next; + } + return n; + } + + private static Node * findLastNode(Node * n, size_t limit) + { + assert(n && limit); + auto ahead = n._next; + while (ahead) + { + if (!--limit) break; + n = ahead; + ahead = n._next; + } + return n; + } + + private static Node * findNode(Node * n, Node * findMe) + { + assert(n); + auto ahead = n._next; + while (ahead != findMe) + { + n = ahead; + enforce(n); + ahead = n._next; + } + return n; + } + +/** +Constructor taking a number of nodes + */ + this(U)(U[] values...) if (isImplicitlyConvertible!(U, T)) + { + insertFront(values); + } + +/** +Constructor taking an input range + */ + this(Stuff)(Stuff stuff) + if (isInputRange!Stuff + && isImplicitlyConvertible!(ElementType!Stuff, T) + && !is(Stuff == T[])) + { + insertFront(stuff); + } + +/** +Comparison for equality. + +Complexity: $(BIGOH min(n, n1)) where $(D n1) is the number of +elements in $(D rhs). + */ + bool opEquals(const SList rhs) const + { + return opEquals(rhs); + } + + /// ditto + bool opEquals(ref const SList rhs) const + { + if (_root is rhs._root) return true; + if (_root is null) return rhs._root is null || rhs._first is null; + if (rhs._root is null) return _root is null || _first is null; + + const(Node) * n1 = _first, n2 = rhs._first; + + for (;; n1 = n1._next, n2 = n2._next) + { + if (!n1) return !n2; + if (!n2 || n1._payload != n2._payload) return false; + } + } + +/** +Defines the container's primary range, which embodies a forward range. + */ + struct Range + { + private Node * _head; + private this(Node * p) { _head = p; } + + /// Input range primitives. + @property bool empty() const { return !_head; } + + /// ditto + @property ref T front() + { + assert(!empty, "SList.Range.front: Range is empty"); + return _head._payload; + } + + /// ditto + void popFront() + { + assert(!empty, "SList.Range.popFront: Range is empty"); + _head = _head._next; + } + + /// Forward range primitive. + @property Range save() { return this; } + + T moveFront() + { + import std.algorithm.mutation : move; + + assert(!empty, "SList.Range.moveFront: Range is empty"); + return move(_head._payload); + } + + bool sameHead(Range rhs) + { + return _head && _head == rhs._head; + } + } + + @safe unittest + { + static assert(isForwardRange!Range); + } + +/** +Property returning $(D true) if and only if the container has no +elements. + +Complexity: $(BIGOH 1) + */ + @property bool empty() const + { + return _root is null || _first is null; + } + +/** +Duplicates the container. The elements themselves are not transitively +duplicated. + +Complexity: $(BIGOH n). + */ + @property SList dup() + { + return SList(this[]); + } + +/** +Returns a range that iterates over all elements of the container, in +forward order. + +Complexity: $(BIGOH 1) + */ + Range opSlice() + { + if (empty) + return Range(null); + else + return Range(_first); + } + +/** +Forward to $(D opSlice().front). + +Complexity: $(BIGOH 1) + */ + @property ref T front() + { + assert(!empty, "SList.front: List is empty"); + return _first._payload; + } + + @safe unittest + { + auto s = SList!int(1, 2, 3); + s.front = 42; + assert(s == SList!int(42, 2, 3)); + } + +/** +Returns a new $(D SList) that's the concatenation of $(D this) and its +argument. $(D opBinaryRight) is only defined if $(D Stuff) does not +define $(D opBinary). + */ + SList opBinary(string op, Stuff)(Stuff rhs) + if (op == "~" && is(typeof(SList(rhs)))) + { + auto toAdd = SList(rhs); + if (empty) return toAdd; + // TODO: optimize + auto result = dup; + auto n = findLastNode(result._first); + n._next = toAdd._first; + return result; + } + + /// ditto + SList opBinaryRight(string op, Stuff)(Stuff lhs) + if (op == "~" && !is(typeof(lhs.opBinary!"~"(this))) && is(typeof(SList(lhs)))) + { + auto toAdd = SList(lhs); + if (empty) return toAdd; + auto result = dup; + result.insertFront(toAdd[]); + return result; + } + +/** +Removes all contents from the $(D SList). + +Postcondition: $(D empty) + +Complexity: $(BIGOH 1) + */ + void clear() + { + if (_root) + _first = null; + } + +/** +Reverses SList in-place. Performs no memory allocation. + +Complexity: $(BIGOH n) + */ + void reverse() + { + if (!empty) + { + Node* prev; + while (_first) + { + auto next = _first._next; + _first._next = prev; + prev = _first; + _first = next; + } + _first = prev; + } + } + +/** +Inserts $(D stuff) to the front of the container. $(D stuff) can be a +value convertible to $(D T) or a range of objects convertible to $(D +T). The stable version behaves the same, but guarantees that ranges +iterating over the container are never invalidated. + +Returns: The number of elements inserted + +Complexity: $(BIGOH m), where $(D m) is the length of $(D stuff) + */ + size_t insertFront(Stuff)(Stuff stuff) + if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T)) + { + initialize(); + size_t result; + Node * n, newRoot; + foreach (item; stuff) + { + auto newNode = new Node(null, item); + (newRoot ? n._next : newRoot) = newNode; + n = newNode; + ++result; + } + if (!n) return 0; + // Last node points to the old root + n._next = _first; + _first = newRoot; + return result; + } + + /// ditto + size_t insertFront(Stuff)(Stuff stuff) + if (isImplicitlyConvertible!(Stuff, T)) + { + initialize(); + auto newRoot = new Node(_first, stuff); + _first = newRoot; + return 1; + } + +/// ditto + alias insert = insertFront; + +/// ditto + alias stableInsert = insert; + + /// ditto + alias stableInsertFront = insertFront; + +/** +Picks one value in an unspecified position in the container, removes +it from the container, and returns it. The stable version behaves the same, +but guarantees that ranges iterating over the container are never invalidated. + +Precondition: $(D !empty) + +Returns: The element removed. + +Complexity: $(BIGOH 1). + */ + T removeAny() + { + import std.algorithm.mutation : move; + + assert(!empty, "SList.removeAny: List is empty"); + auto result = move(_first._payload); + _first = _first._next; + return result; + } + /// ditto + alias stableRemoveAny = removeAny; + +/** +Removes the value at the front of the container. The stable version +behaves the same, but guarantees that ranges iterating over the +container are never invalidated. + +Precondition: $(D !empty) + +Complexity: $(BIGOH 1). + */ + void removeFront() + { + assert(!empty, "SList.removeFront: List is empty"); + _first = _first._next; + } + + /// ditto + alias stableRemoveFront = removeFront; + +/** +Removes $(D howMany) values at the front or back of the +container. Unlike the unparameterized versions above, these functions +do not throw if they could not remove $(D howMany) elements. Instead, +if $(D howMany > n), all elements are removed. The returned value is +the effective number of elements removed. The stable version behaves +the same, but guarantees that ranges iterating over the container are +never invalidated. + +Returns: The number of elements removed + +Complexity: $(BIGOH howMany * log(n)). + */ + size_t removeFront(size_t howMany) + { + size_t result; + while (_first && result < howMany) + { + _first = _first._next; + ++result; + } + return result; + } + + /// ditto + alias stableRemoveFront = removeFront; + +/** +Inserts $(D stuff) after range $(D r), which must be a range +previously extracted from this container. Given that all ranges for a +list end at the end of the list, this function essentially appends to +the list and uses $(D r) as a potentially fast way to reach the last +node in the list. Ideally $(D r) is positioned near or at the last +element of the list. + +$(D stuff) can be a value convertible to $(D T) or a range of objects +convertible to $(D T). The stable version behaves the same, but +guarantees that ranges iterating over the container are never +invalidated. + +Returns: The number of values inserted. + +Complexity: $(BIGOH k + m), where $(D k) is the number of elements in +$(D r) and $(D m) is the length of $(D stuff). + +Example: +-------------------- +auto sl = SList!string(["a", "b", "d"]); +sl.insertAfter(sl[], "e"); // insert at the end (slowest) +assert(std.algorithm.equal(sl[], ["a", "b", "d", "e"])); +sl.insertAfter(std.range.take(sl[], 2), "c"); // insert after "b" +assert(std.algorithm.equal(sl[], ["a", "b", "c", "d", "e"])); +-------------------- + */ + + size_t insertAfter(Stuff)(Range r, Stuff stuff) + { + initialize(); + if (!_first) + { + enforce(!r._head); + return insertFront(stuff); + } + enforce(r._head); + auto n = findLastNode(r._head); + SList tmp; + auto result = tmp.insertFront(stuff); + n._next = tmp._first; + return result; + } + +/** +Similar to $(D insertAfter) above, but accepts a range bounded in +count. This is important for ensuring fast insertions in the middle of +the list. For fast insertions after a specified position $(D r), use +$(D insertAfter(take(r, 1), stuff)). The complexity of that operation +only depends on the number of elements in $(D stuff). + +Precondition: $(D r.original.empty || r.maxLength > 0) + +Returns: The number of values inserted. + +Complexity: $(BIGOH k + m), where $(D k) is the number of elements in +$(D r) and $(D m) is the length of $(D stuff). + */ + size_t insertAfter(Stuff)(Take!Range r, Stuff stuff) + { + auto orig = r.source; + if (!orig._head) + { + // Inserting after a null range counts as insertion to the + // front + return insertFront(stuff); + } + enforce(!r.empty); + // Find the last valid element in the range + foreach (i; 1 .. r.maxLength) + { + if (!orig._head._next) break; + orig.popFront(); + } + // insert here + SList tmp; + tmp.initialize(); + tmp._first = orig._head._next; + auto result = tmp.insertFront(stuff); + orig._head._next = tmp._first; + return result; + } + +/// ditto + alias stableInsertAfter = insertAfter; + +/** +Removes a range from the list in linear time. + +Returns: An empty range. + +Complexity: $(BIGOH n) + */ + Range linearRemove(Range r) + { + if (!_first) + { + enforce(!r._head); + return this[]; + } + auto n = findNode(_root, r._head); + n._next = null; + return Range(null); + } + +/** +Removes a $(D Take!Range) from the list in linear time. + +Returns: A range comprehending the elements after the removed range. + +Complexity: $(BIGOH n) + */ + Range linearRemove(Take!Range r) + { + auto orig = r.source; + // We have something to remove here + if (orig._head == _first) + { + // remove straight from the head of the list + for (; !r.empty; r.popFront()) + { + removeFront(); + } + return this[]; + } + if (!r.maxLength) + { + // Nothing to remove, return the range itself + return orig; + } + // Remove from somewhere in the middle of the list + enforce(_first); + auto n1 = findNode(_root, orig._head); + auto n2 = findLastNode(orig._head, r.maxLength); + n1._next = n2._next; + return Range(n1._next); + } + +/// ditto + alias stableLinearRemove = linearRemove; +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto a = SList!int(5); + auto b = a; + auto r = a[]; + a.insertFront(1); + b.insertFront(2); + assert(equal(a[], [2, 1, 5])); + assert(equal(b[], [2, 1, 5])); + r.front = 9; + assert(equal(a[], [2, 1, 9])); + assert(equal(b[], [2, 1, 9])); +} + +@safe unittest +{ + auto s = SList!int(1, 2, 3); + auto n = s.findLastNode(s._root); + assert(n && n._payload == 3); +} + +@safe unittest +{ + import std.range.primitives; + auto s = SList!int(1, 2, 5, 10); + assert(walkLength(s[]) == 4); +} + +@safe unittest +{ + import std.range : take; + auto src = take([0, 1, 2, 3], 3); + auto s = SList!int(src); + assert(s == SList!int(0, 1, 2)); +} + +@safe unittest +{ + auto a = SList!int(); + auto b = SList!int(); + auto c = a ~ b[]; + assert(c.empty); +} + +@safe unittest +{ + auto a = SList!int(1, 2, 3); + auto b = SList!int(4, 5, 6); + auto c = a ~ b[]; + assert(c == SList!int(1, 2, 3, 4, 5, 6)); +} + +@safe unittest +{ + auto a = SList!int(1, 2, 3); + auto b = [4, 5, 6]; + auto c = a ~ b; + assert(c == SList!int(1, 2, 3, 4, 5, 6)); +} + +@safe unittest +{ + auto a = SList!int(1, 2, 3); + auto c = a ~ 4; + assert(c == SList!int(1, 2, 3, 4)); +} + +@safe unittest +{ + auto a = SList!int(2, 3, 4); + auto b = 1 ~ a; + assert(b == SList!int(1, 2, 3, 4)); +} + +@safe unittest +{ + auto a = [1, 2, 3]; + auto b = SList!int(4, 5, 6); + auto c = a ~ b; + assert(c == SList!int(1, 2, 3, 4, 5, 6)); +} + +@safe unittest +{ + auto s = SList!int(1, 2, 3, 4); + s.insertFront([ 42, 43 ]); + assert(s == SList!int(42, 43, 1, 2, 3, 4)); +} + +@safe unittest +{ + auto s = SList!int(1, 2, 3); + assert(s.removeAny() == 1); + assert(s == SList!int(2, 3)); + assert(s.stableRemoveAny() == 2); + assert(s == SList!int(3)); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto s = SList!int(1, 2, 3); + s.removeFront(); + assert(equal(s[], [2, 3])); + s.stableRemoveFront(); + assert(equal(s[], [3])); +} + +@safe unittest +{ + auto s = SList!int(1, 2, 3, 4, 5, 6, 7); + assert(s.removeFront(3) == 3); + assert(s == SList!int(4, 5, 6, 7)); +} + +@safe unittest +{ + auto a = SList!int(1, 2, 3); + auto b = SList!int(1, 2, 3); + assert(a.insertAfter(a[], b[]) == 3); +} + +@safe unittest +{ + import std.range : take; + auto s = SList!int(1, 2, 3, 4); + auto r = take(s[], 2); + assert(s.insertAfter(r, 5) == 1); + assert(s == SList!int(1, 2, 5, 3, 4)); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : take; + + // insertAfter documentation example + auto sl = SList!string(["a", "b", "d"]); + sl.insertAfter(sl[], "e"); // insert at the end (slowest) + assert(equal(sl[], ["a", "b", "d", "e"])); + sl.insertAfter(take(sl[], 2), "c"); // insert after "b" + assert(equal(sl[], ["a", "b", "c", "d", "e"])); +} + +@safe unittest +{ + import std.range.primitives; + auto s = SList!int(1, 2, 3, 4, 5); + auto r = s[]; + popFrontN(r, 3); + auto r1 = s.linearRemove(r); + assert(s == SList!int(1, 2, 3)); + assert(r1.empty); +} + +@safe unittest +{ + auto s = SList!int(1, 2, 3, 4, 5); + auto r = s[]; + auto r1 = s.linearRemove(r); + assert(s == SList!int()); + assert(r1.empty); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range; + + auto s = SList!int(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + auto r = s[]; + popFrontN(r, 3); + auto r1 = take(r, 4); + assert(equal(r1, [4, 5, 6, 7])); + auto r2 = s.linearRemove(r1); + assert(s == SList!int(1, 2, 3, 8, 9, 10)); + assert(equal(r2, [8, 9, 10])); +} + +@safe unittest +{ + import std.range.primitives; + auto lst = SList!int(1, 5, 42, 9); + assert(!lst.empty); + assert(lst.front == 1); + assert(walkLength(lst[]) == 4); + + auto lst2 = lst ~ [ 1, 2, 3 ]; + assert(walkLength(lst2[]) == 7); + + auto lst3 = lst ~ [ 7 ]; + assert(walkLength(lst3[]) == 5); +} + +@safe unittest +{ + auto s = make!(SList!int)(1, 2, 3); +} + +@safe unittest +{ + // 5193 + static struct Data + { + const int val; + } + SList!Data list; +} + +@safe unittest +{ + auto s = SList!int([1, 2, 3]); + s.front = 5; //test frontAssign + assert(s.front == 5); + auto r = s[]; + r.front = 1; //test frontAssign + assert(r.front == 1); +} + +@safe unittest +{ + // issue 14920 + SList!int s; + s.insertAfter(s[], 1); + assert(s.front == 1); +} + +@safe unittest +{ + // issue 15659 + SList!int s; + s.clear(); +} + +@safe unittest +{ + SList!int s; + s.reverse(); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto s = SList!int([1, 2, 3]); + assert(s[].equal([1, 2, 3])); + + s.reverse(); + assert(s[].equal([3, 2, 1])); +} diff --git a/libphobos/src/std/container/util.d b/libphobos/src/std/container/util.d new file mode 100644 index 0000000..5be9e7d --- /dev/null +++ b/libphobos/src/std/container/util.d @@ -0,0 +1,189 @@ +/** +This module contains some common utilities used by containers. + +This module is a submodule of $(MREF std, container). + +Source: $(PHOBOSSRC std/container/_util.d) + +Copyright: 2010- Andrei Alexandrescu. All rights reserved by the respective holders. + +License: Distributed under the Boost Software License, Version 1.0. +(See accompanying file LICENSE_1_0.txt or copy at $(HTTP +boost.org/LICENSE_1_0.txt)). + +Authors: $(HTTP erdani.com, Andrei Alexandrescu) + +$(SCRIPT inhibitQuickIndex = 1;) +*/ +module std.container.util; + +/** +Returns an initialized object. This function is mainly for eliminating +construction differences between structs and classes. It allows code to not +worry about whether the type it's constructing is a struct or a class. + */ +template make(T) +if (is(T == struct) || is(T == class)) +{ + T make(Args...)(Args arguments) + if (is(T == struct) && __traits(compiles, T(arguments))) + { + // constructing an std.container.Array without arguments, + // does not initialize its payload and is equivalent + // to a null reference. We therefore construct an empty container + // by passing an empty array to its constructor. + // Issue #13872. + static if (arguments.length == 0) + { + import std.range.primitives : ElementType; + alias ET = ElementType!(T.Range); + return T(ET[].init); + } + else + return T(arguments); + } + + T make(Args...)(Args arguments) + if (is(T == class) && __traits(compiles, new T(arguments))) + { + return new T(arguments); + } +} + + +/// +@system unittest +{ + import std.algorithm.comparison : equal; + import std.container; + + auto arr = make!(Array!int)([4, 2, 3, 1]); + assert(equal(arr[], [4, 2, 3, 1])); + + auto rbt = make!(RedBlackTree!(int, "a > b"))([4, 2, 3, 1]); + assert(equal(rbt[], [4, 3, 2, 1])); + + alias makeList = make!(SList!int); + auto slist = makeList(1, 2, 3); + assert(equal(slist[], [1, 2, 3])); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + import std.container; + + auto arr1 = make!(Array!dchar)(); + assert(arr1.empty); + auto arr2 = make!(Array!dchar)("hello"d); + assert(equal(arr2[], "hello"d)); + + auto rtb1 = make!(RedBlackTree!dchar)(); + assert(rtb1.empty); + auto rtb2 = make!(RedBlackTree!dchar)('h', 'e', 'l', 'l', 'o'); + assert(equal(rtb2[], "ehlo"d)); +} + +// Issue 8895 +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.container; + + auto a = make!(DList!int)(1,2,3,4); + auto b = make!(DList!int)(1,2,3,4); + auto c = make!(DList!int)(1,2,3,5); + auto d = make!(DList!int)(1,2,3,4,5); + assert(a == b); // this better terminate! + assert(a != c); + assert(a != d); +} + +/** + * Convenience function for constructing a generic container. + */ +template make(alias Container, Args...) +if (!is(Container)) +{ + import std.range : isInputRange, isInfinite; + import std.traits : isDynamicArray; + + auto make(Range)(Range range) + if (!isDynamicArray!Range && isInputRange!Range && !isInfinite!Range) + { + import std.range : ElementType; + return .make!(Container!(ElementType!Range, Args))(range); + } + + auto make(T)(T[] items...) + if (!isInfinite!T) + { + return .make!(Container!(T, Args))(items); + } +} + +/// forbid construction from infinite range +@safe unittest +{ + import std.container.array : Array; + import std.range : only, repeat; + import std.range.primitives : isInfinite; + static assert(__traits(compiles, { auto arr = make!Array(only(5)); })); + static assert(!__traits(compiles, { auto arr = make!Array(repeat(5)); })); +} + +/// +@system unittest +{ + import std.algorithm.comparison : equal; + import std.container.array, std.container.rbtree, std.container.slist; + import std.range : iota; + + auto arr = make!Array(iota(5)); + assert(equal(arr[], [0, 1, 2, 3, 4])); + + auto rbtmax = make!(RedBlackTree, "a > b")(iota(5)); + assert(equal(rbtmax[], [4, 3, 2, 1, 0])); + + auto rbtmin = make!RedBlackTree(4, 1, 3, 2); + assert(equal(rbtmin[], [1, 2, 3, 4])); + + alias makeList = make!SList; + auto list = makeList(1, 7, 42); + assert(equal(list[], [1, 7, 42])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.container.rbtree; + + auto rbtmin = make!(RedBlackTree, "a < b", false)(3, 2, 2, 1); + assert(equal(rbtmin[], [1, 2, 3])); +} + +// Issue 13872 +@system unittest +{ + import std.container; + + auto tree1 = make!(RedBlackTree!int)(); + auto refToTree1 = tree1; + refToTree1.insert(1); + assert(1 in tree1); + + auto array1 = make!(Array!int)(); + auto refToArray1 = array1; + refToArray1.insertBack(1); + assert(!array1.empty); + + auto slist = make!(SList!int)(); + auto refToSlist = slist; + refToSlist.insert(1); + assert(!slist.empty); + + auto dlist = make!(DList!int)(); + auto refToDList = dlist; + refToDList.insert(1); + assert(!dlist.empty); +} diff --git a/libphobos/src/std/conv.d b/libphobos/src/std/conv.d new file mode 100644 index 0000000..127849d --- /dev/null +++ b/libphobos/src/std/conv.d @@ -0,0 +1,6290 @@ +// Written in the D programming language. + +/** +A one-stop shop for converting values from one type to another. + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE, +$(TR $(TH Category) $(TH Functions)) +$(TR $(TD Generic) $(TD + $(LREF asOriginalType) + $(LREF castFrom) + $(LREF emplace) + $(LREF parse) + $(LREF to) + $(LREF toChars) +)) +$(TR $(TD Strings) $(TD + $(LREF text) + $(LREF wtext) + $(LREF dtext) + $(LREF hexString) +)) +$(TR $(TD Numeric) $(TD + $(LREF octal) + $(LREF roundTo) + $(LREF signed) + $(LREF unsigned) +)) +$(TR $(TD Exceptions) $(TD + $(LREF ConvException) + $(LREF ConvOverflowException) +)) +) + +Copyright: Copyright Digital Mars 2007-. + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(HTTP digitalmars.com, Walter Bright), + $(HTTP erdani.org, Andrei Alexandrescu), + Shin Fujishiro, + Adam D. Ruppe, + Kenji Hara + +Source: $(PHOBOSSRC std/_conv.d) + +*/ +module std.conv; + +public import std.ascii : LetterCase; + +import std.meta; +import std.range.primitives; +import std.traits; + +// Same as std.string.format, but "self-importing". +// Helps reduce code and imports, particularly in static asserts. +// Also helps with missing imports errors. +package template convFormat() +{ + import std.format : format; + alias convFormat = format; +} + +/* ************* Exceptions *************** */ + +/** + * Thrown on conversion errors. + */ +class ConvException : Exception +{ + import std.exception : basicExceptionCtors; + /// + mixin basicExceptionCtors; +} + +private auto convError(S, T)(S source, string fn = __FILE__, size_t ln = __LINE__) +{ + string msg; + + if (source.empty) + msg = "Unexpected end of input when converting from type " ~ S.stringof ~ " to type " ~ T.stringof; + else + { + ElementType!S el = source.front; + + if (el == '\n') + msg = text("Unexpected '\\n' when converting from type " ~ S.stringof ~ " to type " ~ T.stringof); + else + msg = text("Unexpected '", el, + "' when converting from type " ~ S.stringof ~ " to type " ~ T.stringof); + } + + return new ConvException(msg, fn, ln); +} + +private auto convError(S, T)(S source, int radix, string fn = __FILE__, size_t ln = __LINE__) +{ + string msg; + + if (source.empty) + msg = text("Unexpected end of input when converting from type " ~ S.stringof ~ " base ", radix, + " to type " ~ T.stringof); + else + msg = text("Unexpected '", source.front, + "' when converting from type " ~ S.stringof ~ " base ", radix, + " to type " ~ T.stringof); + + return new ConvException(msg, fn, ln); +} + +@safe pure/* nothrow*/ // lazy parameter bug +private auto parseError(lazy string msg, string fn = __FILE__, size_t ln = __LINE__) +{ + return new ConvException(text("Can't parse string: ", msg), fn, ln); +} + +private void parseCheck(alias source)(dchar c, string fn = __FILE__, size_t ln = __LINE__) +{ + if (source.empty) + throw parseError(text("unexpected end of input when expecting", "\"", c, "\"")); + if (source.front != c) + throw parseError(text("\"", c, "\" is missing"), fn, ln); + source.popFront(); +} + +private +{ + T toStr(T, S)(S src) + if (isSomeString!T) + { + // workaround for Bugzilla 14198 + static if (is(S == bool) && is(typeof({ T s = "string"; }))) + { + return src ? "true" : "false"; + } + else + { + import std.array : appender; + import std.format : FormatSpec, formatValue; + + auto w = appender!T(); + FormatSpec!(ElementEncodingType!T) f; + formatValue(w, src, f); + return w.data; + } + } + + template isExactSomeString(T) + { + enum isExactSomeString = isSomeString!T && !is(T == enum); + } + + template isEnumStrToStr(S, T) + { + enum isEnumStrToStr = isImplicitlyConvertible!(S, T) && + is(S == enum) && isExactSomeString!T; + } + template isNullToStr(S, T) + { + enum isNullToStr = isImplicitlyConvertible!(S, T) && + (is(Unqual!S == typeof(null))) && isExactSomeString!T; + } +} + +/** + * Thrown on conversion overflow errors. + */ +class ConvOverflowException : ConvException +{ + @safe pure nothrow + this(string s, string fn = __FILE__, size_t ln = __LINE__) + { + super(s, fn, ln); + } +} + +/** +The `to` template converts a value from one type _to another. +The source type is deduced and the target type must be specified, for example the +expression `to!int(42.0)` converts the number 42 from +`double` _to `int`. The conversion is "safe", i.e., +it checks for overflow; `to!int(4.2e10)` would throw the +`ConvOverflowException` exception. Overflow checks are only +inserted when necessary, e.g., `to!double(42)` does not do +any checking because any `int` fits in a `double`. + +Conversions from string _to numeric types differ from the C equivalents +`atoi()` and `atol()` by checking for overflow and not allowing whitespace. + +For conversion of strings _to signed types, the grammar recognized is: +$(PRE $(I Integer): $(I Sign UnsignedInteger) +$(I UnsignedInteger) +$(I Sign): + $(B +) + $(B -)) + +For conversion _to unsigned types, the grammar recognized is: +$(PRE $(I UnsignedInteger): + $(I DecimalDigit) + $(I DecimalDigit) $(I UnsignedInteger)) + */ +template to(T) +{ + T to(A...)(A args) + if (A.length > 0) + { + return toImpl!T(args); + } + + // Fix issue 6175 + T to(S)(ref S arg) + if (isStaticArray!S) + { + return toImpl!T(arg); + } + + // Fix issue 16108 + T to(S)(ref S arg) + if (isAggregateType!S && !isCopyable!S) + { + return toImpl!T(arg); + } +} + +/** + * Converting a value _to its own type (useful mostly for generic code) + * simply returns its argument. + */ +@safe pure unittest +{ + int a = 42; + int b = to!int(a); + double c = to!double(3.14); // c is double with value 3.14 +} + +/** + * Converting among numeric types is a safe way _to cast them around. + * + * Conversions from floating-point types _to integral types allow loss of + * precision (the fractional part of a floating-point number). The + * conversion is truncating towards zero, the same way a cast would + * truncate. (_To round a floating point value when casting _to an + * integral, use `roundTo`.) + */ +@safe pure unittest +{ + import std.exception : assertThrown; + + int a = 420; + assert(to!long(a) == a); + assertThrown!ConvOverflowException(to!byte(a)); + + assert(to!int(4.2e6) == 4200000); + assertThrown!ConvOverflowException(to!uint(-3.14)); + assert(to!uint(3.14) == 3); + assert(to!uint(3.99) == 3); + assert(to!int(-3.99) == -3); +} + +/** + * When converting strings _to numeric types, note that the D hexadecimal and binary + * literals are not handled. Neither the prefixes that indicate the base, nor the + * horizontal bar used _to separate groups of digits are recognized. This also + * applies to the suffixes that indicate the type. + * + * _To work around this, you can specify a radix for conversions involving numbers. + */ +@safe pure unittest +{ + auto str = to!string(42, 16); + assert(str == "2A"); + auto i = to!int(str, 16); + assert(i == 42); +} + +/** + * Conversions from integral types _to floating-point types always + * succeed, but might lose accuracy. The largest integers with a + * predecessor representable in floating-point format are `2^24-1` for + * `float`, `2^53-1` for `double`, and `2^64-1` for `real` (when + * `real` is 80-bit, e.g. on Intel machines). + */ +@safe pure unittest +{ + // 2^24 - 1, largest proper integer representable as float + int a = 16_777_215; + assert(to!int(to!float(a)) == a); + assert(to!int(to!float(-a)) == -a); +} + +/** + * Converting an array _to another array type works by converting each + * element in turn. Associative arrays can be converted _to associative + * arrays as long as keys and values can in turn be converted. + */ +@safe pure unittest +{ + import std.string : split; + + int[] a = [1, 2, 3]; + auto b = to!(float[])(a); + assert(b == [1.0f, 2, 3]); + string str = "1 2 3 4 5 6"; + auto numbers = to!(double[])(split(str)); + assert(numbers == [1.0, 2, 3, 4, 5, 6]); + int[string] c; + c["a"] = 1; + c["b"] = 2; + auto d = to!(double[wstring])(c); + assert(d["a"w] == 1 && d["b"w] == 2); +} + +/** + * Conversions operate transitively, meaning that they work on arrays and + * associative arrays of any complexity. + * + * This conversion works because `to!short` applies _to an `int`, `to!wstring` + * applies _to a `string`, `to!string` applies _to a `double`, and + * `to!(double[])` applies _to an `int[]`. The conversion might throw an + * exception because `to!short` might fail the range check. + */ +@safe unittest +{ + int[string][double[int[]]] a; + auto b = to!(short[wstring][string[double[]]])(a); +} + +/** + * Object-to-object conversions by dynamic casting throw exception when + * the source is non-null and the target is null. + */ +@safe pure unittest +{ + import std.exception : assertThrown; + // Testing object conversions + class A {} + class B : A {} + class C : A {} + A a1 = new A, a2 = new B, a3 = new C; + assert(to!B(a2) is a2); + assert(to!C(a3) is a3); + assertThrown!ConvException(to!B(a3)); +} + +/** + * Stringize conversion from all types is supported. + * $(UL + * $(LI String _to string conversion works for any two string types having + * ($(D char), $(D wchar), $(D dchar)) character widths and any + * combination of qualifiers (mutable, $(D const), or $(D immutable)).) + * $(LI Converts array (other than strings) _to string. + * Each element is converted by calling $(D to!T).) + * $(LI Associative array _to string conversion. + * Each element is printed by calling $(D to!T).) + * $(LI Object _to string conversion calls $(D toString) against the object or + * returns $(D "null") if the object is null.) + * $(LI Struct _to string conversion calls $(D toString) against the struct if + * it is defined.) + * $(LI For structs that do not define $(D toString), the conversion _to string + * produces the list of fields.) + * $(LI Enumerated types are converted _to strings as their symbolic names.) + * $(LI Boolean values are printed as $(D "true") or $(D "false").) + * $(LI $(D char), $(D wchar), $(D dchar) _to a string type.) + * $(LI Unsigned or signed integers _to strings. + * $(DL $(DT [special case]) + * $(DD Convert integral value _to string in $(D_PARAM radix) radix. + * radix must be a value from 2 to 36. + * value is treated as a signed value only if radix is 10. + * The characters A through Z are used to represent values 10 through 36 + * and their case is determined by the $(D_PARAM letterCase) parameter.))) + * $(LI All floating point types _to all string types.) + * $(LI Pointer to string conversions prints the pointer as a $(D size_t) value. + * If pointer is $(D char*), treat it as C-style strings. + * In that case, this function is $(D @system).)) + */ +@system pure unittest // @system due to cast and ptr +{ + // Conversion representing dynamic/static array with string + long[] a = [ 1, 3, 5 ]; + assert(to!string(a) == "[1, 3, 5]"); + + // Conversion representing associative array with string + int[string] associativeArray = ["0":1, "1":2]; + assert(to!string(associativeArray) == `["0":1, "1":2]` || + to!string(associativeArray) == `["1":2, "0":1]`); + + // char* to string conversion + assert(to!string(cast(char*) null) == ""); + assert(to!string("foo\0".ptr) == "foo"); + + // Conversion reinterpreting void array to string + auto w = "abcx"w; + const(void)[] b = w; + assert(b.length == 8); + + auto c = to!(wchar[])(b); + assert(c == "abcx"); +} + +// Tests for issue 6175 +@safe pure nothrow unittest +{ + char[9] sarr = "blablabla"; + auto darr = to!(char[])(sarr); + assert(sarr.ptr == darr.ptr); + assert(sarr.length == darr.length); +} + +// Tests for issue 7348 +@safe pure /+nothrow+/ unittest +{ + assert(to!string(null) == "null"); + assert(text(null) == "null"); +} + +// Tests for issue 11390 +@safe pure /+nothrow+/ unittest +{ + const(typeof(null)) ctn; + immutable(typeof(null)) itn; + assert(to!string(ctn) == "null"); + assert(to!string(itn) == "null"); +} + +// Tests for issue 8729: do NOT skip leading WS +@safe pure unittest +{ + import std.exception; + foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong)) + { + assertThrown!ConvException(to!T(" 0")); + assertThrown!ConvException(to!T(" 0", 8)); + } + foreach (T; AliasSeq!(float, double, real)) + { + assertThrown!ConvException(to!T(" 0")); + } + + assertThrown!ConvException(to!bool(" true")); + + alias NullType = typeof(null); + assertThrown!ConvException(to!NullType(" null")); + + alias ARR = int[]; + assertThrown!ConvException(to!ARR(" [1]")); + + alias AA = int[int]; + assertThrown!ConvException(to!AA(" [1:1]")); +} + +/** +If the source type is implicitly convertible to the target type, $(D +to) simply performs the implicit conversion. + */ +private T toImpl(T, S)(S value) +if (isImplicitlyConvertible!(S, T) && + !isEnumStrToStr!(S, T) && !isNullToStr!(S, T)) +{ + template isSignedInt(T) + { + enum isSignedInt = isIntegral!T && isSigned!T; + } + alias isUnsignedInt = isUnsigned; + + // Conversion from integer to integer, and changing its sign + static if (isUnsignedInt!S && isSignedInt!T && S.sizeof == T.sizeof) + { // unsigned to signed & same size + import std.exception : enforce; + enforce(value <= cast(S) T.max, + new ConvOverflowException("Conversion positive overflow")); + } + else static if (isSignedInt!S && isUnsignedInt!T) + { // signed to unsigned + import std.exception : enforce; + enforce(0 <= value, + new ConvOverflowException("Conversion negative overflow")); + } + + return value; +} + +@safe pure nothrow unittest +{ + enum E { a } // Issue 9523 - Allow identity enum conversion + auto e = to!E(E.a); + assert(e == E.a); +} + +@safe pure nothrow unittest +{ + int a = 42; + auto b = to!long(a); + assert(a == b); +} + +// Tests for issue 6377 +@safe pure unittest +{ + import std.exception; + // Conversion between same size + foreach (S; AliasSeq!(byte, short, int, long)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + alias U = Unsigned!S; + + foreach (Sint; AliasSeq!(S, const S, immutable S)) + foreach (Uint; AliasSeq!(U, const U, immutable U)) + { + // positive overflow + Uint un = Uint.max; + assertThrown!ConvOverflowException(to!Sint(un), + text(Sint.stringof, ' ', Uint.stringof, ' ', un)); + + // negative overflow + Sint sn = -1; + assertThrown!ConvOverflowException(to!Uint(sn), + text(Sint.stringof, ' ', Uint.stringof, ' ', un)); + } + }(); + + // Conversion between different size + foreach (i, S1; AliasSeq!(byte, short, int, long)) + foreach ( S2; AliasSeq!(byte, short, int, long)[i+1..$]) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + alias U1 = Unsigned!S1; + alias U2 = Unsigned!S2; + + static assert(U1.sizeof < S2.sizeof); + + // small unsigned to big signed + foreach (Uint; AliasSeq!(U1, const U1, immutable U1)) + foreach (Sint; AliasSeq!(S2, const S2, immutable S2)) + { + Uint un = Uint.max; + assertNotThrown(to!Sint(un)); + assert(to!Sint(un) == un); + } + + // big unsigned to small signed + foreach (Uint; AliasSeq!(U2, const U2, immutable U2)) + foreach (Sint; AliasSeq!(S1, const S1, immutable S1)) + { + Uint un = Uint.max; + assertThrown(to!Sint(un)); + } + + static assert(S1.sizeof < U2.sizeof); + + // small signed to big unsigned + foreach (Sint; AliasSeq!(S1, const S1, immutable S1)) + foreach (Uint; AliasSeq!(U2, const U2, immutable U2)) + { + Sint sn = -1; + assertThrown!ConvOverflowException(to!Uint(sn)); + } + + // big signed to small unsigned + foreach (Sint; AliasSeq!(S2, const S2, immutable S2)) + foreach (Uint; AliasSeq!(U1, const U1, immutable U1)) + { + Sint sn = -1; + assertThrown!ConvOverflowException(to!Uint(sn)); + } + }(); +} + +/* + Converting static arrays forwards to their dynamic counterparts. + */ +private T toImpl(T, S)(ref S s) +if (isStaticArray!S) +{ + return toImpl!(T, typeof(s[0])[])(s); +} + +@safe pure nothrow unittest +{ + char[4] test = ['a', 'b', 'c', 'd']; + static assert(!isInputRange!(Unqual!(char[4]))); + assert(to!string(test) == test); +} + +/** +When source type supports member template function opCast, it is used. +*/ +private T toImpl(T, S)(S value) +if (!isImplicitlyConvertible!(S, T) && + is(typeof(S.init.opCast!T()) : T) && + !isExactSomeString!T && + !is(typeof(T(value)))) +{ + return value.opCast!T(); +} + +@safe pure unittest +{ + static struct Test + { + struct T + { + this(S s) @safe pure { } + } + struct S + { + T opCast(U)() @safe pure { assert(false); } + } + } + cast(void) to!(Test.T)(Test.S()); + + // make sure std.conv.to is doing the same thing as initialization + Test.S s; + Test.T t = s; +} + +@safe pure unittest +{ + class B + { + T opCast(T)() { return 43; } + } + auto b = new B; + assert(to!int(b) == 43); + + struct S + { + T opCast(T)() { return 43; } + } + auto s = S(); + assert(to!int(s) == 43); +} + +/** +When target type supports 'converting construction', it is used. +$(UL $(LI If target type is struct, $(D T(value)) is used.) + $(LI If target type is class, $(D new T(value)) is used.)) +*/ +private T toImpl(T, S)(S value) +if (!isImplicitlyConvertible!(S, T) && + is(T == struct) && is(typeof(T(value)))) +{ + return T(value); +} + +// Bugzilla 3961 +@safe pure unittest +{ + struct Int + { + int x; + } + Int i = to!Int(1); + + static struct Int2 + { + int x; + this(int x) @safe pure { this.x = x; } + } + Int2 i2 = to!Int2(1); + + static struct Int3 + { + int x; + static Int3 opCall(int x) @safe pure + { + Int3 i; + i.x = x; + return i; + } + } + Int3 i3 = to!Int3(1); +} + +// Bugzilla 6808 +@safe pure unittest +{ + static struct FakeBigInt + { + this(string s) @safe pure {} + } + + string s = "101"; + auto i3 = to!FakeBigInt(s); +} + +/// ditto +private T toImpl(T, S)(S value) +if (!isImplicitlyConvertible!(S, T) && + is(T == class) && is(typeof(new T(value)))) +{ + return new T(value); +} + +@safe pure unittest +{ + static struct S + { + int x; + } + static class C + { + int x; + this(int x) @safe pure { this.x = x; } + } + + static class B + { + int value; + this(S src) @safe pure { value = src.x; } + this(C src) @safe pure { value = src.x; } + } + + S s = S(1); + auto b1 = to!B(s); // == new B(s) + assert(b1.value == 1); + + C c = new C(2); + auto b2 = to!B(c); // == new B(c) + assert(b2.value == 2); + + auto c2 = to!C(3); // == new C(3) + assert(c2.x == 3); +} + +@safe pure unittest +{ + struct S + { + class A + { + this(B b) @safe pure {} + } + class B : A + { + this() @safe pure { super(this); } + } + } + + S.B b = new S.B(); + S.A a = to!(S.A)(b); // == cast(S.A) b + // (do not run construction conversion like new S.A(b)) + assert(b is a); + + static class C : Object + { + this() @safe pure {} + this(Object o) @safe pure {} + } + + Object oc = new C(); + C a2 = to!C(oc); // == new C(a) + // Construction conversion overrides down-casting conversion + assert(a2 !is a); // +} + +/** +Object-to-object conversions by dynamic casting throw exception when the source is +non-null and the target is null. + */ +private T toImpl(T, S)(S value) +if (!isImplicitlyConvertible!(S, T) && + (is(S == class) || is(S == interface)) && !is(typeof(value.opCast!T()) : T) && + (is(T == class) || is(T == interface)) && !is(typeof(new T(value)))) +{ + static if (is(T == immutable)) + { + // immutable <- immutable + enum isModConvertible = is(S == immutable); + } + else static if (is(T == const)) + { + static if (is(T == shared)) + { + // shared const <- shared + // shared const <- shared const + // shared const <- immutable + enum isModConvertible = is(S == shared) || is(S == immutable); + } + else + { + // const <- mutable + // const <- immutable + enum isModConvertible = !is(S == shared); + } + } + else + { + static if (is(T == shared)) + { + // shared <- shared mutable + enum isModConvertible = is(S == shared) && !is(S == const); + } + else + { + // (mutable) <- (mutable) + enum isModConvertible = is(Unqual!S == S); + } + } + static assert(isModConvertible, "Bad modifier conversion: "~S.stringof~" to "~T.stringof); + + auto result = ()@trusted{ return cast(T) value; }(); + if (!result && value) + { + throw new ConvException("Cannot convert object of static type " + ~S.classinfo.name~" and dynamic type "~value.classinfo.name + ~" to type "~T.classinfo.name); + } + return result; +} + +// Unittest for 6288 +@safe pure unittest +{ + import std.exception; + + alias Identity(T) = T; + alias toConst(T) = const T; + alias toShared(T) = shared T; + alias toSharedConst(T) = shared const T; + alias toImmutable(T) = immutable T; + template AddModifier(int n) + if (0 <= n && n < 5) + { + static if (n == 0) alias AddModifier = Identity; + else static if (n == 1) alias AddModifier = toConst; + else static if (n == 2) alias AddModifier = toShared; + else static if (n == 3) alias AddModifier = toSharedConst; + else static if (n == 4) alias AddModifier = toImmutable; + } + + interface I {} + interface J {} + + class A {} + class B : A {} + class C : B, I, J {} + class D : I {} + + foreach (m1; AliasSeq!(0,1,2,3,4)) // enumerate modifiers + foreach (m2; AliasSeq!(0,1,2,3,4)) // ditto + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + alias srcmod = AddModifier!m1; + alias tgtmod = AddModifier!m2; + + // Compile time convertible equals to modifier convertible. + static if (isImplicitlyConvertible!(srcmod!Object, tgtmod!Object)) + { + // Test runtime conversions: class to class, class to interface, + // interface to class, and interface to interface + + // Check that the runtime conversion to succeed + srcmod!A ac = new srcmod!C(); + srcmod!I ic = new srcmod!C(); + assert(to!(tgtmod!C)(ac) !is null); // A(c) to C + assert(to!(tgtmod!I)(ac) !is null); // A(c) to I + assert(to!(tgtmod!C)(ic) !is null); // I(c) to C + assert(to!(tgtmod!J)(ic) !is null); // I(c) to J + + // Check that the runtime conversion fails + srcmod!A ab = new srcmod!B(); + srcmod!I id = new srcmod!D(); + assertThrown(to!(tgtmod!C)(ab)); // A(b) to C + assertThrown(to!(tgtmod!I)(ab)); // A(b) to I + assertThrown(to!(tgtmod!C)(id)); // I(d) to C + assertThrown(to!(tgtmod!J)(id)); // I(d) to J + } + else + { + // Check that the conversion is rejected statically + static assert(!is(typeof(to!(tgtmod!C)(srcmod!A.init)))); // A to C + static assert(!is(typeof(to!(tgtmod!I)(srcmod!A.init)))); // A to I + static assert(!is(typeof(to!(tgtmod!C)(srcmod!I.init)))); // I to C + static assert(!is(typeof(to!(tgtmod!J)(srcmod!I.init)))); // I to J + } + }(); +} + +/** +Handles type _to string conversions +*/ +private T toImpl(T, S)(S value) +if (!(isImplicitlyConvertible!(S, T) && + !isEnumStrToStr!(S, T) && !isNullToStr!(S, T)) && + !isInfinite!S && isExactSomeString!T) +{ + static if (isExactSomeString!S && value[0].sizeof == ElementEncodingType!T.sizeof) + { + // string-to-string with incompatible qualifier conversion + static if (is(ElementEncodingType!T == immutable)) + { + // conversion (mutable|const) -> immutable + return value.idup; + } + else + { + // conversion (immutable|const) -> mutable + return value.dup; + } + } + else static if (isExactSomeString!S) + { + import std.array : appender; + // other string-to-string + //Use Appender directly instead of toStr, which also uses a formatedWrite + auto w = appender!T(); + w.put(value); + return w.data; + } + else static if (isIntegral!S && !is(S == enum)) + { + // other integral-to-string conversions with default radix + return toImpl!(T, S)(value, 10); + } + else static if (is(S == void[]) || is(S == const(void)[]) || is(S == immutable(void)[])) + { + import core.stdc.string : memcpy; + import std.exception : enforce; + // Converting void array to string + alias Char = Unqual!(ElementEncodingType!T); + auto raw = cast(const(ubyte)[]) value; + enforce(raw.length % Char.sizeof == 0, + new ConvException("Alignment mismatch in converting a " + ~ S.stringof ~ " to a " + ~ T.stringof)); + auto result = new Char[raw.length / Char.sizeof]; + ()@trusted{ memcpy(result.ptr, value.ptr, value.length); }(); + return cast(T) result; + } + else static if (isPointer!S && isSomeChar!(PointerTarget!S)) + { + // This is unsafe because we cannot guarantee that the pointer is null terminated. + return () @system { + static if (is(S : const(char)*)) + import core.stdc.string : strlen; + else + size_t strlen(S s) nothrow + { + S p = s; + while (*p++) {} + return p-s-1; + } + return toImpl!T(value ? value[0 .. strlen(value)].dup : null); + }(); + } + else static if (isSomeString!T && is(S == enum)) + { + static if (isSwitchable!(OriginalType!S) && EnumMembers!S.length <= 50) + { + switch (value) + { + foreach (member; NoDuplicates!(EnumMembers!S)) + { + case member: + return to!T(enumRep!(immutable(T), S, member)); + } + default: + } + } + else + { + foreach (member; EnumMembers!S) + { + if (value == member) + return to!T(enumRep!(immutable(T), S, member)); + } + } + + import std.array : appender; + import std.format : FormatSpec, formatValue; + + //Default case, delegate to format + //Note: we don't call toStr directly, to avoid duplicate work. + auto app = appender!T(); + app.put("cast(" ~ S.stringof ~ ")"); + FormatSpec!char f; + formatValue(app, cast(OriginalType!S) value, f); + return app.data; + } + else + { + // other non-string values runs formatting + return toStr!T(value); + } +} + +// Bugzilla 14042 +@system unittest +{ + immutable(char)* ptr = "hello".ptr; + auto result = ptr.to!(char[]); +} +// Bugzilla 8384 +@system unittest +{ + void test1(T)(T lp, string cmp) + { + foreach (e; AliasSeq!(char, wchar, dchar)) + { + test2!(e[])(lp, cmp); + test2!(const(e)[])(lp, cmp); + test2!(immutable(e)[])(lp, cmp); + } + } + + void test2(D, S)(S lp, string cmp) + { + assert(to!string(to!D(lp)) == cmp); + } + + foreach (e; AliasSeq!("Hello, world!", "Hello, world!"w, "Hello, world!"d)) + { + test1(e, "Hello, world!"); + test1(e.ptr, "Hello, world!"); + } + foreach (e; AliasSeq!("", ""w, ""d)) + { + test1(e, ""); + test1(e.ptr, ""); + } +} + +/* + To string conversion for non copy-able structs + */ +private T toImpl(T, S)(ref S value) +if (!(isImplicitlyConvertible!(S, T) && + !isEnumStrToStr!(S, T) && !isNullToStr!(S, T)) && + !isInfinite!S && isExactSomeString!T && !isCopyable!S) +{ + import std.array : appender; + import std.format : FormatSpec, formatValue; + + auto w = appender!T(); + FormatSpec!(ElementEncodingType!T) f; + formatValue(w, value, f); + return w.data; +} + +// Bugzilla 16108 +@system unittest +{ + static struct A + { + int val; + bool flag; + + string toString() { return text(val, ":", flag); } + + @disable this(this); + } + + auto a = A(); + assert(to!string(a) == "0:false"); + + static struct B + { + int val; + bool flag; + + @disable this(this); + } + + auto b = B(); + assert(to!string(b) == "B(0, false)"); +} + +/* + Check whether type $(D T) can be used in a switch statement. + This is useful for compile-time generation of switch case statements. +*/ +private template isSwitchable(E) +{ + enum bool isSwitchable = is(typeof({ + switch (E.init) { default: } + })); +} + +// +@safe unittest +{ + static assert(isSwitchable!int); + static assert(!isSwitchable!double); + static assert(!isSwitchable!real); +} + +//Static representation of the index I of the enum S, +//In representation T. +//T must be an immutable string (avoids un-necessary initializations). +private template enumRep(T, S, S value) +if (is (T == immutable) && isExactSomeString!T && is(S == enum)) +{ + static T enumRep = toStr!T(value); +} + +@safe pure unittest +{ + import std.exception; + void dg() + { + // string to string conversion + alias Chars = AliasSeq!(char, wchar, dchar); + foreach (LhsC; Chars) + { + alias LhStrings = AliasSeq!(LhsC[], const(LhsC)[], immutable(LhsC)[]); + foreach (Lhs; LhStrings) + { + foreach (RhsC; Chars) + { + alias RhStrings = AliasSeq!(RhsC[], const(RhsC)[], immutable(RhsC)[]); + foreach (Rhs; RhStrings) + { + Lhs s1 = to!Lhs("wyda"); + Rhs s2 = to!Rhs(s1); + //writeln(Lhs.stringof, " -> ", Rhs.stringof); + assert(s1 == to!Lhs(s2)); + } + } + } + } + + foreach (T; Chars) + { + foreach (U; Chars) + { + T[] s1 = to!(T[])("Hello, world!"); + auto s2 = to!(U[])(s1); + assert(s1 == to!(T[])(s2)); + auto s3 = to!(const(U)[])(s1); + assert(s1 == to!(T[])(s3)); + auto s4 = to!(immutable(U)[])(s1); + assert(s1 == to!(T[])(s4)); + } + } + } + dg(); + assertCTFEable!dg; +} + +@safe pure unittest +{ + // Conversion representing bool value with string + bool b; + assert(to!string(b) == "false"); + b = true; + assert(to!string(b) == "true"); +} + +@safe pure unittest +{ + // Conversion representing character value with string + alias AllChars = + AliasSeq!( char, const( char), immutable( char), + wchar, const(wchar), immutable(wchar), + dchar, const(dchar), immutable(dchar)); + foreach (Char1; AllChars) + { + foreach (Char2; AllChars) + { + Char1 c = 'a'; + assert(to!(Char2[])(c)[0] == c); + } + uint x = 4; + assert(to!(Char1[])(x) == "4"); + } + + string s = "foo"; + string s2; + foreach (char c; s) + { + s2 ~= to!string(c); + } + assert(s2 == "foo"); +} + +@safe pure nothrow unittest +{ + import std.exception; + // Conversion representing integer values with string + + foreach (Int; AliasSeq!(ubyte, ushort, uint, ulong)) + { + assert(to!string(Int(0)) == "0"); + assert(to!string(Int(9)) == "9"); + assert(to!string(Int(123)) == "123"); + } + + foreach (Int; AliasSeq!(byte, short, int, long)) + { + assert(to!string(Int(0)) == "0"); + assert(to!string(Int(9)) == "9"); + assert(to!string(Int(123)) == "123"); + assert(to!string(Int(-0)) == "0"); + assert(to!string(Int(-9)) == "-9"); + assert(to!string(Int(-123)) == "-123"); + assert(to!string(const(Int)(6)) == "6"); + } + + assert(wtext(int.max) == "2147483647"w); + assert(wtext(int.min) == "-2147483648"w); + assert(to!string(0L) == "0"); + + assertCTFEable!( + { + assert(to!string(1uL << 62) == "4611686018427387904"); + assert(to!string(0x100000000) == "4294967296"); + assert(to!string(-138L) == "-138"); + }); +} + +@safe unittest // sprintf issue +{ + double[2] a = [ 1.5, 2.5 ]; + assert(to!string(a) == "[1.5, 2.5]"); +} + +@system unittest +{ + // Conversion representing class object with string + class A + { + override string toString() const { return "an A"; } + } + A a; + assert(to!string(a) == "null"); + a = new A; + assert(to!string(a) == "an A"); + + // Bug 7660 + class C { override string toString() const { return "C"; } } + struct S { C c; alias c this; } + S s; s.c = new C(); + assert(to!string(s) == "C"); +} + +@safe unittest +{ + // Conversion representing struct object with string + struct S1 + { + string toString() { return "wyda"; } + } + assert(to!string(S1()) == "wyda"); + + struct S2 + { + int a = 42; + float b = 43.5; + } + S2 s2; + assert(to!string(s2) == "S2(42, 43.5)"); + + // Test for issue 8080 + struct S8080 + { + short[4] data; + alias data this; + string toString() { return ""; } + } + S8080 s8080; + assert(to!string(s8080) == ""); +} + +@safe unittest +{ + // Conversion representing enum value with string + enum EB : bool { a = true } + enum EU : uint { a = 0, b = 1, c = 2 } // base type is unsigned + enum EI : int { a = -1, b = 0, c = 1 } // base type is signed (bug 7909) + enum EF : real { a = 1.414, b = 1.732, c = 2.236 } + enum EC : char { a = 'x', b = 'y' } + enum ES : string { a = "aaa", b = "bbb" } + + foreach (E; AliasSeq!(EB, EU, EI, EF, EC, ES)) + { + assert(to! string(E.a) == "a"c); + assert(to!wstring(E.a) == "a"w); + assert(to!dstring(E.a) == "a"d); + } + + // Test an value not corresponding to an enum member. + auto o = cast(EU) 5; + assert(to! string(o) == "cast(EU)5"c); + assert(to!wstring(o) == "cast(EU)5"w); + assert(to!dstring(o) == "cast(EU)5"d); +} + +@safe unittest +{ + enum E + { + foo, + doo = foo, // check duplicate switch statements + bar, + } + + //Test regression 12494 + assert(to!string(E.foo) == "foo"); + assert(to!string(E.doo) == "foo"); + assert(to!string(E.bar) == "bar"); + + foreach (S; AliasSeq!(string, wstring, dstring, const(char[]), const(wchar[]), const(dchar[]))) + { + auto s1 = to!S(E.foo); + auto s2 = to!S(E.foo); + assert(s1 == s2); + // ensure we don't allocate when it's unnecessary + assert(s1 is s2); + } + + foreach (S; AliasSeq!(char[], wchar[], dchar[])) + { + auto s1 = to!S(E.foo); + auto s2 = to!S(E.foo); + assert(s1 == s2); + // ensure each mutable array is unique + assert(s1 !is s2); + } +} + +// ditto +@trusted pure private T toImpl(T, S)(S value, uint radix, LetterCase letterCase = LetterCase.upper) +if (isIntegral!S && + isExactSomeString!T) +in +{ + assert(radix >= 2 && radix <= 36); +} +body +{ + alias EEType = Unqual!(ElementEncodingType!T); + + T toStringRadixConvert(size_t bufLen)(uint runtimeRadix = 0) + { + Unsigned!(Unqual!S) div = void, mValue = unsigned(value); + + size_t index = bufLen; + EEType[bufLen] buffer = void; + char baseChar = letterCase == LetterCase.lower ? 'a' : 'A'; + char mod = void; + + do + { + div = cast(S)(mValue / runtimeRadix ); + mod = cast(ubyte)(mValue % runtimeRadix); + mod += mod < 10 ? '0' : baseChar - 10; + buffer[--index] = cast(char) mod; + mValue = div; + } while (mValue); + + return cast(T) buffer[index .. $].dup; + } + + import std.array : array; + switch (radix) + { + case 10: + // The (value+0) is so integral promotions happen to the type + return toChars!(10, EEType)(value + 0).array; + case 16: + // The unsigned(unsigned(value)+0) is so unsigned integral promotions happen to the type + if (letterCase == letterCase.upper) + return toChars!(16, EEType, LetterCase.upper)(unsigned(unsigned(value) + 0)).array; + else + return toChars!(16, EEType, LetterCase.lower)(unsigned(unsigned(value) + 0)).array; + case 2: + return toChars!(2, EEType)(unsigned(unsigned(value) + 0)).array; + case 8: + return toChars!(8, EEType)(unsigned(unsigned(value) + 0)).array; + + default: + return toStringRadixConvert!(S.sizeof * 6)(radix); + } +} + +@safe pure nothrow unittest +{ + foreach (Int; AliasSeq!(uint, ulong)) + { + assert(to!string(Int(16), 16) == "10"); + assert(to!string(Int(15), 2u) == "1111"); + assert(to!string(Int(1), 2u) == "1"); + assert(to!string(Int(0x1234AF), 16u) == "1234AF"); + assert(to!string(Int(0x1234BCD), 16u, LetterCase.upper) == "1234BCD"); + assert(to!string(Int(0x1234AF), 16u, LetterCase.lower) == "1234af"); + } + + foreach (Int; AliasSeq!(int, long)) + { + assert(to!string(Int(-10), 10u) == "-10"); + } + + assert(to!string(byte(-10), 16) == "F6"); + assert(to!string(long.min) == "-9223372036854775808"); + assert(to!string(long.max) == "9223372036854775807"); +} + +/** +Narrowing numeric-numeric conversions throw when the value does not +fit in the narrower type. + */ +private T toImpl(T, S)(S value) +if (!isImplicitlyConvertible!(S, T) && + (isNumeric!S || isSomeChar!S || isBoolean!S) && + (isNumeric!T || isSomeChar!T || isBoolean!T) && !is(T == enum)) +{ + enum sSmallest = mostNegative!S; + enum tSmallest = mostNegative!T; + static if (sSmallest < 0) + { + // possible underflow converting from a signed + static if (tSmallest == 0) + { + immutable good = value >= 0; + } + else + { + static assert(tSmallest < 0); + immutable good = value >= tSmallest; + } + if (!good) + throw new ConvOverflowException("Conversion negative overflow"); + } + static if (S.max > T.max) + { + // possible overflow + if (value > T.max) + throw new ConvOverflowException("Conversion positive overflow"); + } + return (ref value)@trusted{ return cast(T) value; }(value); +} + +@safe pure unittest +{ + import std.exception; + + dchar a = ' '; + assert(to!char(a) == ' '); + a = 300; + assert(collectException(to!char(a))); + + dchar from0 = 'A'; + char to0 = to!char(from0); + + wchar from1 = 'A'; + char to1 = to!char(from1); + + char from2 = 'A'; + char to2 = to!char(from2); + + char from3 = 'A'; + wchar to3 = to!wchar(from3); + + char from4 = 'A'; + dchar to4 = to!dchar(from4); +} + +@safe unittest +{ + import std.exception; + + // Narrowing conversions from enum -> integral should be allowed, but they + // should throw at runtime if the enum value doesn't fit in the target + // type. + enum E1 : ulong { A = 1, B = 1UL << 48, C = 0 } + assert(to!int(E1.A) == 1); + assert(to!bool(E1.A) == true); + assertThrown!ConvOverflowException(to!int(E1.B)); // E1.B overflows int + assertThrown!ConvOverflowException(to!bool(E1.B)); // E1.B overflows bool + assert(to!bool(E1.C) == false); + + enum E2 : long { A = -1L << 48, B = -1 << 31, C = 1 << 31 } + assertThrown!ConvOverflowException(to!int(E2.A)); // E2.A overflows int + assertThrown!ConvOverflowException(to!uint(E2.B)); // E2.B overflows uint + assert(to!int(E2.B) == -1 << 31); // but does not overflow int + assert(to!int(E2.C) == 1 << 31); // E2.C does not overflow int + + enum E3 : int { A = -1, B = 1, C = 255, D = 0 } + assertThrown!ConvOverflowException(to!ubyte(E3.A)); + assertThrown!ConvOverflowException(to!bool(E3.A)); + assert(to!byte(E3.A) == -1); + assert(to!byte(E3.B) == 1); + assert(to!ubyte(E3.C) == 255); + assert(to!bool(E3.B) == true); + assertThrown!ConvOverflowException(to!byte(E3.C)); + assertThrown!ConvOverflowException(to!bool(E3.C)); + assert(to!bool(E3.D) == false); + +} + +/** +Array-to-array conversion (except when target is a string type) +converts each element in turn by using $(D to). + */ +private T toImpl(T, S)(S value) +if (!isImplicitlyConvertible!(S, T) && + !isSomeString!S && isDynamicArray!S && + !isExactSomeString!T && isArray!T) +{ + alias E = typeof(T.init[0]); + + static if (isStaticArray!T) + { + import std.exception : enforce; + auto res = to!(E[])(value); + enforce!ConvException(T.length == res.length, + convFormat("Length mismatch when converting to static array: %s vs %s", T.length, res.length)); + return res[0 .. T.length]; + } + else + { + import std.array : appender; + auto w = appender!(E[])(); + w.reserve(value.length); + foreach (i, ref e; value) + { + w.put(to!E(e)); + } + return w.data; + } +} + +@safe pure unittest +{ + import std.exception; + + // array to array conversions + uint[] a = [ 1u, 2, 3 ]; + auto b = to!(float[])(a); + assert(b == [ 1.0f, 2, 3 ]); + + immutable(int)[3] d = [ 1, 2, 3 ]; + b = to!(float[])(d); + assert(b == [ 1.0f, 2, 3 ]); + + uint[][] e = [ a, a ]; + auto f = to!(float[][])(e); + assert(f[0] == b && f[1] == b); + + // Test for bug 8264 + struct Wrap + { + string wrap; + alias wrap this; + } + Wrap[] warr = to!(Wrap[])(["foo", "bar"]); // should work + + // Issue 12633 + import std.conv : to; + const s2 = ["10", "20"]; + + immutable int[2] a3 = s2.to!(int[2]); + assert(a3 == [10, 20]); + + // verify length mismatches are caught + immutable s4 = [1, 2, 3, 4]; + foreach (i; [1, 4]) + { + auto ex = collectException(s4[0 .. i].to!(int[2])); + assert(ex && ex.msg == "Length mismatch when converting to static array: 2 vs " ~ [cast(char)(i + '0')], + ex ? ex.msg : "Exception was not thrown!"); + } +} + +@safe unittest +{ + auto b = [ 1.0f, 2, 3 ]; + + auto c = to!(string[])(b); + assert(c[0] == "1" && c[1] == "2" && c[2] == "3"); +} + +/** +Associative array to associative array conversion converts each key +and each value in turn. + */ +private T toImpl(T, S)(S value) +if (isAssociativeArray!S && + isAssociativeArray!T && !is(T == enum)) +{ + /* This code is potentially unsafe. + */ + alias K2 = KeyType!T; + alias V2 = ValueType!T; + + // While we are "building" the AA, we need to unqualify its values, and only re-qualify at the end + Unqual!V2[K2] result; + + foreach (k1, v1; value) + { + // Cast values temporarily to Unqual!V2 to store them to result variable + result[to!K2(k1)] = cast(Unqual!V2) to!V2(v1); + } + // Cast back to original type + return cast(T) result; +} + +@safe unittest +{ + // hash to hash conversions + int[string] a; + a["0"] = 1; + a["1"] = 2; + auto b = to!(double[dstring])(a); + assert(b["0"d] == 1 && b["1"d] == 2); +} +@safe unittest // Bugzilla 8705, from doc +{ + import std.exception; + int[string][double[int[]]] a; + auto b = to!(short[wstring][string[double[]]])(a); + a = [null:["hello":int.max]]; + assertThrown!ConvOverflowException(to!(short[wstring][string[double[]]])(a)); +} +@system unittest // Extra cases for AA with qualifiers conversion +{ + int[][int[]] a;// = [[], []]; + auto b = to!(immutable(short[])[immutable short[]])(a); + + double[dstring][int[long[]]] c; + auto d = to!(immutable(short[immutable wstring])[immutable string[double[]]])(c); +} + +private void testIntegralToFloating(Integral, Floating)() +{ + Integral a = 42; + auto b = to!Floating(a); + assert(a == b); + assert(a == to!Integral(b)); +} + +private void testFloatingToIntegral(Floating, Integral)() +{ + bool convFails(Source, Target, E)(Source src) + { + try + auto t = to!Target(src); + catch (E) + return true; + return false; + } + + // convert some value + Floating a = 4.2e1; + auto b = to!Integral(a); + assert(is(typeof(b) == Integral) && b == 42); + // convert some negative value (if applicable) + a = -4.2e1; + static if (Integral.min < 0) + { + b = to!Integral(a); + assert(is(typeof(b) == Integral) && b == -42); + } + else + { + // no go for unsigned types + assert(convFails!(Floating, Integral, ConvOverflowException)(a)); + } + // convert to the smallest integral value + a = 0.0 + Integral.min; + static if (Integral.min < 0) + { + a = -a; // -Integral.min not representable as an Integral + assert(convFails!(Floating, Integral, ConvOverflowException)(a) + || Floating.sizeof <= Integral.sizeof); + } + a = 0.0 + Integral.min; + assert(to!Integral(a) == Integral.min); + --a; // no more representable as an Integral + assert(convFails!(Floating, Integral, ConvOverflowException)(a) + || Floating.sizeof <= Integral.sizeof); + a = 0.0 + Integral.max; + assert(to!Integral(a) == Integral.max || Floating.sizeof <= Integral.sizeof); + ++a; // no more representable as an Integral + assert(convFails!(Floating, Integral, ConvOverflowException)(a) + || Floating.sizeof <= Integral.sizeof); + // convert a value with a fractional part + a = 3.14; + assert(to!Integral(a) == 3); + a = 3.99; + assert(to!Integral(a) == 3); + static if (Integral.min < 0) + { + a = -3.14; + assert(to!Integral(a) == -3); + a = -3.99; + assert(to!Integral(a) == -3); + } +} + +@safe pure unittest +{ + alias AllInts = AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong); + alias AllFloats = AliasSeq!(float, double, real); + alias AllNumerics = AliasSeq!(AllInts, AllFloats); + // test with same type + { + foreach (T; AllNumerics) + { + T a = 42; + auto b = to!T(a); + assert(is(typeof(a) == typeof(b)) && a == b); + } + } + // test that floating-point numbers convert properly to largest ints + // see http://oregonstate.edu/~peterseb/mth351/docs/351s2001_fp80x87.html + // look for "largest fp integer with a predecessor" + { + // float + int a = 16_777_215; // 2^24 - 1 + assert(to!int(to!float(a)) == a); + assert(to!int(to!float(-a)) == -a); + // double + long b = 9_007_199_254_740_991; // 2^53 - 1 + assert(to!long(to!double(b)) == b); + assert(to!long(to!double(-b)) == -b); + // real + static if (real.mant_dig >= 64) + { + ulong c = 18_446_744_073_709_551_615UL; // 2^64 - 1 + assert(to!ulong(to!real(c)) == c); + } + } + // test conversions floating => integral + { + // AllInts[0 .. $ - 1] should be AllInts + // @@@ BUG IN COMPILER @@@ + foreach (Integral; AllInts[0 .. $ - 1]) + { + foreach (Floating; AllFloats) + { + testFloatingToIntegral!(Floating, Integral)(); + } + } + } + // test conversion integral => floating + { + foreach (Integral; AllInts[0 .. $ - 1]) + { + foreach (Floating; AllFloats) + { + testIntegralToFloating!(Integral, Floating)(); + } + } + } + // test parsing + { + foreach (T; AllNumerics) + { + // from type immutable(char)[2] + auto a = to!T("42"); + assert(a == 42); + // from type char[] + char[] s1 = "42".dup; + a = to!T(s1); + assert(a == 42); + // from type char[2] + char[2] s2; + s2[] = "42"; + a = to!T(s2); + assert(a == 42); + // from type immutable(wchar)[2] + a = to!T("42"w); + assert(a == 42); + } + } +} + +@safe unittest +{ + alias AllInts = AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong); + alias AllFloats = AliasSeq!(float, double, real); + alias AllNumerics = AliasSeq!(AllInts, AllFloats); + // test conversions to string + { + foreach (T; AllNumerics) + { + T a = 42; + assert(to!string(a) == "42"); + assert(to!wstring(a) == "42"w); + assert(to!dstring(a) == "42"d); + // array test + T[] b = new T[2]; + b[0] = 42; + b[1] = 33; + assert(to!string(b) == "[42, 33]"); + } + } + // test array to string conversion + foreach (T ; AllNumerics) + { + auto a = [to!T(1), 2, 3]; + assert(to!string(a) == "[1, 2, 3]"); + } + // test enum to int conversion + enum Testing { Test1, Test2 } + Testing t; + auto a = to!string(t); + assert(a == "Test1"); +} + + +/** +String, or string-like input range, to non-string conversion runs parsing. +$(UL + $(LI When the source is a wide string, it is first converted to a narrow + string and then parsed.) + $(LI When the source is a narrow string, normal text parsing occurs.)) +*/ +private T toImpl(T, S)(S value) +if (isInputRange!S && isSomeChar!(ElementEncodingType!S) && + !isExactSomeString!T && is(typeof(parse!T(value)))) +{ + scope(success) + { + if (!value.empty) + { + throw convError!(S, T)(value); + } + } + return parse!T(value); +} + +/// ditto +private T toImpl(T, S)(S value, uint radix) +if (isInputRange!S && !isInfinite!S && isSomeChar!(ElementEncodingType!S) && + isIntegral!T && is(typeof(parse!T(value, radix)))) +{ + scope(success) + { + if (!value.empty) + { + throw convError!(S, T)(value); + } + } + return parse!T(value, radix); +} + +@safe pure unittest +{ + // Issue 6668 - ensure no collaterals thrown + try { to!uint("-1"); } + catch (ConvException e) { assert(e.next is null); } +} + +@safe pure unittest +{ + foreach (Str; AliasSeq!(string, wstring, dstring)) + { + Str a = "123"; + assert(to!int(a) == 123); + assert(to!double(a) == 123); + } + + // 6255 + auto n = to!int("FF", 16); + assert(n == 255); +} + +// bugzilla 15800 +@safe unittest +{ + import std.utf : byCodeUnit, byChar, byWchar, byDchar; + + assert(to!int(byCodeUnit("10")) == 10); + assert(to!int(byCodeUnit("10"), 10) == 10); + assert(to!int(byCodeUnit("10"w)) == 10); + assert(to!int(byCodeUnit("10"w), 10) == 10); + + assert(to!int(byChar("10")) == 10); + assert(to!int(byChar("10"), 10) == 10); + assert(to!int(byWchar("10")) == 10); + assert(to!int(byWchar("10"), 10) == 10); + assert(to!int(byDchar("10")) == 10); + assert(to!int(byDchar("10"), 10) == 10); +} + +/** +Convert a value that is implicitly convertible to the enum base type +into an Enum value. If the value does not match any enum member values +a ConvException is thrown. +Enums with floating-point or string base types are not supported. +*/ +private T toImpl(T, S)(S value) +if (is(T == enum) && !is(S == enum) + && is(typeof(value == OriginalType!T.init)) + && !isFloatingPoint!(OriginalType!T) && !isSomeString!(OriginalType!T)) +{ + foreach (Member; EnumMembers!T) + { + if (Member == value) + return Member; + } + throw new ConvException(convFormat("Value (%s) does not match any member value of enum '%s'", value, T.stringof)); +} + +@safe pure unittest +{ + import std.exception; + enum En8143 : int { A = 10, B = 20, C = 30, D = 20 } + enum En8143[][] m3 = to!(En8143[][])([[10, 30], [30, 10]]); + static assert(m3 == [[En8143.A, En8143.C], [En8143.C, En8143.A]]); + + En8143 en1 = to!En8143(10); + assert(en1 == En8143.A); + assertThrown!ConvException(to!En8143(5)); // matches none + En8143[][] m1 = to!(En8143[][])([[10, 30], [30, 10]]); + assert(m1 == [[En8143.A, En8143.C], [En8143.C, En8143.A]]); +} + +/*************************************************************** + Rounded conversion from floating point to integral. + +Rounded conversions do not work with non-integral target types. + */ + +template roundTo(Target) +{ + Target roundTo(Source)(Source value) + { + import std.math : trunc; + + static assert(isFloatingPoint!Source); + static assert(isIntegral!Target); + return to!Target(trunc(value + (value < 0 ? -0.5L : 0.5L))); + } +} + +/// +@safe unittest +{ + assert(roundTo!int(3.14) == 3); + assert(roundTo!int(3.49) == 3); + assert(roundTo!int(3.5) == 4); + assert(roundTo!int(3.999) == 4); + assert(roundTo!int(-3.14) == -3); + assert(roundTo!int(-3.49) == -3); + assert(roundTo!int(-3.5) == -4); + assert(roundTo!int(-3.999) == -4); + assert(roundTo!(const int)(to!(const double)(-3.999)) == -4); +} + +@safe unittest +{ + import std.exception; + // boundary values + foreach (Int; AliasSeq!(byte, ubyte, short, ushort, int, uint)) + { + assert(roundTo!Int(Int.min - 0.4L) == Int.min); + assert(roundTo!Int(Int.max + 0.4L) == Int.max); + assertThrown!ConvOverflowException(roundTo!Int(Int.min - 0.5L)); + assertThrown!ConvOverflowException(roundTo!Int(Int.max + 0.5L)); + } +} + +/** +The $(D parse) family of functions works quite like the $(D to) +family, except that: +$(OL + $(LI It only works with character ranges as input.) + $(LI It takes the input by reference. (This means that rvalues - such + as string literals - are not accepted: use $(D to) instead.)) + $(LI It advances the input to the position following the conversion.) + $(LI It does not throw if it could not convert the entire input.)) + +This overload converts an character input range to a `bool`. + +Params: + Target = the type to convert to + source = the lvalue of an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + +Returns: + A `bool` + +Throws: + A $(LREF ConvException) if the range does not represent a `bool`. + +Note: + All character input range conversions using $(LREF to) are forwarded + to `parse` and do not require lvalues. +*/ +Target parse(Target, Source)(ref Source source) +if (isInputRange!Source && + isSomeChar!(ElementType!Source) && + is(Unqual!Target == bool)) +{ + import std.ascii : toLower; + + static if (isNarrowString!Source) + { + import std.string : representation; + auto s = source.representation; + } + else + { + alias s = source; + } + + if (!s.empty) + { + auto c1 = toLower(s.front); + bool result = c1 == 't'; + if (result || c1 == 'f') + { + s.popFront(); + foreach (c; result ? "rue" : "alse") + { + if (s.empty || toLower(s.front) != c) + goto Lerr; + s.popFront(); + } + + static if (isNarrowString!Source) + source = cast(Source) s; + + return result; + } + } +Lerr: + throw parseError("bool should be case-insensitive 'true' or 'false'"); +} + +/// +@safe unittest +{ + auto s = "true"; + bool b = parse!bool(s); + assert(b); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.exception; + struct InputString + { + string _s; + @property auto front() { return _s.front; } + @property bool empty() { return _s.empty; } + void popFront() { _s.popFront(); } + } + + auto s = InputString("trueFALSETrueFalsetRUEfALSE"); + assert(parse!bool(s) == true); + assert(s.equal("FALSETrueFalsetRUEfALSE")); + assert(parse!bool(s) == false); + assert(s.equal("TrueFalsetRUEfALSE")); + assert(parse!bool(s) == true); + assert(s.equal("FalsetRUEfALSE")); + assert(parse!bool(s) == false); + assert(s.equal("tRUEfALSE")); + assert(parse!bool(s) == true); + assert(s.equal("fALSE")); + assert(parse!bool(s) == false); + assert(s.empty); + + foreach (ss; ["tfalse", "ftrue", "t", "f", "tru", "fals", ""]) + { + s = InputString(ss); + assertThrown!ConvException(parse!bool(s)); + } +} + +/** +Parses a character $(REF_ALTTEXT input range, isInputRange, std,range,primitives) +to an integral value. + +Params: + Target = the integral type to convert to + s = the lvalue of an input range + +Returns: + A number of type `Target` + +Throws: + A $(LREF ConvException) If an overflow occurred during conversion or + if no character of the input was meaningfully converted. +*/ +Target parse(Target, Source)(ref Source s) +if (isSomeChar!(ElementType!Source) && + isIntegral!Target && !is(Target == enum)) +{ + static if (Target.sizeof < int.sizeof) + { + // smaller types are handled like integers + auto v = .parse!(Select!(Target.min < 0, int, uint))(s); + auto result = ()@trusted{ return cast(Target) v; }(); + if (result == v) + return result; + throw new ConvOverflowException("Overflow in integral conversion"); + } + else + { + // int or larger types + + static if (Target.min < 0) + bool sign = false; + else + enum bool sign = false; + + enum char maxLastDigit = Target.min < 0 ? 7 : 5; + uint c; + + static if (isNarrowString!Source) + { + import std.string : representation; + auto source = s.representation; + } + else + { + alias source = s; + } + + if (source.empty) + goto Lerr; + + c = source.front; + + static if (Target.min < 0) + { + switch (c) + { + case '-': + sign = true; + goto case '+'; + case '+': + source.popFront(); + + if (source.empty) + goto Lerr; + + c = source.front; + + break; + + default: + break; + } + } + c -= '0'; + if (c <= 9) + { + Target v = cast(Target) c; + + source.popFront(); + + while (!source.empty) + { + c = cast(typeof(c)) (source.front - '0'); + + if (c > 9) + break; + + if (v >= 0 && (v < Target.max/10 || + (v == Target.max/10 && c <= maxLastDigit + sign))) + { + // Note: `v` can become negative here in case of parsing + // the most negative value: + v = cast(Target) (v * 10 + c); + + source.popFront(); + } + else + throw new ConvOverflowException("Overflow in integral conversion"); + } + + if (sign) + v = -v; + + static if (isNarrowString!Source) + s = cast(Source) source; + + return v; + } +Lerr: + static if (isNarrowString!Source) + throw convError!(Source, Target)(cast(Source) source); + else + throw convError!(Source, Target)(source); + } +} + +/// +@safe pure unittest +{ + string s = "123"; + auto a = parse!int(s); + assert(a == 123); + + // parse only accepts lvalues + static assert(!__traits(compiles, parse!int("123"))); +} + +/// +@safe pure unittest +{ + import std.string : tr; + string test = "123 \t 76.14"; + auto a = parse!uint(test); + assert(a == 123); + assert(test == " \t 76.14"); // parse bumps string + test = tr(test, " \t\n\r", "", "d"); // skip ws + assert(test == "76.14"); + auto b = parse!double(test); + assert(b == 76.14); + assert(test == ""); +} + +@safe pure unittest +{ + foreach (Int; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong)) + { + { + assert(to!Int("0") == 0); + + static if (isSigned!Int) + { + assert(to!Int("+0") == 0); + assert(to!Int("-0") == 0); + } + } + + static if (Int.sizeof >= byte.sizeof) + { + assert(to!Int("6") == 6); + assert(to!Int("23") == 23); + assert(to!Int("68") == 68); + assert(to!Int("127") == 0x7F); + + static if (isUnsigned!Int) + { + assert(to!Int("255") == 0xFF); + } + static if (isSigned!Int) + { + assert(to!Int("+6") == 6); + assert(to!Int("+23") == 23); + assert(to!Int("+68") == 68); + assert(to!Int("+127") == 0x7F); + + assert(to!Int("-6") == -6); + assert(to!Int("-23") == -23); + assert(to!Int("-68") == -68); + assert(to!Int("-128") == -128); + } + } + + static if (Int.sizeof >= short.sizeof) + { + assert(to!Int("468") == 468); + assert(to!Int("32767") == 0x7FFF); + + static if (isUnsigned!Int) + { + assert(to!Int("65535") == 0xFFFF); + } + static if (isSigned!Int) + { + assert(to!Int("+468") == 468); + assert(to!Int("+32767") == 0x7FFF); + + assert(to!Int("-468") == -468); + assert(to!Int("-32768") == -32768); + } + } + + static if (Int.sizeof >= int.sizeof) + { + assert(to!Int("2147483647") == 0x7FFFFFFF); + + static if (isUnsigned!Int) + { + assert(to!Int("4294967295") == 0xFFFFFFFF); + } + + static if (isSigned!Int) + { + assert(to!Int("+2147483647") == 0x7FFFFFFF); + + assert(to!Int("-2147483648") == -2147483648); + } + } + + static if (Int.sizeof >= long.sizeof) + { + assert(to!Int("9223372036854775807") == 0x7FFFFFFFFFFFFFFF); + + static if (isUnsigned!Int) + { + assert(to!Int("18446744073709551615") == 0xFFFFFFFFFFFFFFFF); + } + + static if (isSigned!Int) + { + assert(to!Int("+9223372036854775807") == 0x7FFFFFFFFFFFFFFF); + + assert(to!Int("-9223372036854775808") == 0x8000000000000000); + } + } + } +} + +@safe pure unittest +{ + import std.exception; + // parsing error check + foreach (Int; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong)) + { + { + immutable string[] errors1 = + [ + "", + "-", + "+", + "-+", + " ", + " 0", + "0 ", + "- 0", + "1-", + "xx", + "123h", + "-+1", + "--1", + "+-1", + "++1", + ]; + foreach (j, s; errors1) + assertThrown!ConvException(to!Int(s)); + } + + // parse!SomeUnsigned cannot parse head sign. + static if (isUnsigned!Int) + { + immutable string[] errors2 = + [ + "+5", + "-78", + ]; + foreach (j, s; errors2) + assertThrown!ConvException(to!Int(s)); + } + } + + // positive overflow check + foreach (i, Int; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong)) + { + immutable string[] errors = + [ + "128", // > byte.max + "256", // > ubyte.max + "32768", // > short.max + "65536", // > ushort.max + "2147483648", // > int.max + "4294967296", // > uint.max + "9223372036854775808", // > long.max + "18446744073709551616", // > ulong.max + ]; + foreach (j, s; errors[i..$]) + assertThrown!ConvOverflowException(to!Int(s)); + } + + // negative overflow check + foreach (i, Int; AliasSeq!(byte, short, int, long)) + { + immutable string[] errors = + [ + "-129", // < byte.min + "-32769", // < short.min + "-2147483649", // < int.min + "-9223372036854775809", // < long.min + ]; + foreach (j, s; errors[i..$]) + assertThrown!ConvOverflowException(to!Int(s)); + } +} + +@safe pure unittest +{ + void checkErrMsg(string input, dchar charInMsg, dchar charNotInMsg) + { + try + { + int x = input.to!int(); + assert(false, "Invalid conversion did not throw"); + } + catch (ConvException e) + { + // Ensure error message contains failing character, not the character + // beyond. + import std.algorithm.searching : canFind; + assert( e.msg.canFind(charInMsg) && + !e.msg.canFind(charNotInMsg)); + } + catch (Exception e) + { + assert(false, "Did not throw ConvException"); + } + } + checkErrMsg("@$", '@', '$'); + checkErrMsg("@$123", '@', '$'); + checkErrMsg("1@$23", '@', '$'); + checkErrMsg("1@$", '@', '$'); + checkErrMsg("1@$2", '@', '$'); + checkErrMsg("12@$", '@', '$'); +} + +@safe pure unittest +{ + import std.exception; + assertCTFEable!({ string s = "1234abc"; assert(parse! int(s) == 1234 && s == "abc"); }); + assertCTFEable!({ string s = "-1234abc"; assert(parse! int(s) == -1234 && s == "abc"); }); + assertCTFEable!({ string s = "1234abc"; assert(parse!uint(s) == 1234 && s == "abc"); }); +} + +// Issue 13931 +@safe pure unittest +{ + import std.exception; + + assertThrown!ConvOverflowException("-21474836480".to!int()); + assertThrown!ConvOverflowException("-92233720368547758080".to!long()); +} + +// Issue 14396 +@safe pure unittest +{ + struct StrInputRange + { + this (string s) { str = s; } + char front() const @property { return str[front_index]; } + char popFront() { return str[front_index++]; } + bool empty() const @property { return str.length <= front_index; } + string str; + size_t front_index = 0; + } + auto input = StrInputRange("777"); + assert(parse!int(input) == 777); +} + +/// ditto +Target parse(Target, Source)(ref Source source, uint radix) +if (isSomeChar!(ElementType!Source) && + isIntegral!Target && !is(Target == enum)) +in +{ + assert(radix >= 2 && radix <= 36); +} +body +{ + import core.checkedint : mulu, addu; + import std.exception : enforce; + + if (radix == 10) + return parse!Target(source); + + enforce!ConvException(!source.empty, "s must not be empty in integral parse"); + + immutable uint beyond = (radix < 10 ? '0' : 'a'-10) + radix; + Target v = 0; + + static if (isNarrowString!Source) + { + import std.string : representation; + auto s = source.representation; + } + else + { + alias s = source; + } + + do + { + uint c = s.front; + if (c < '0') + break; + if (radix < 10) + { + if (c >= beyond) + break; + } + else + { + if (c > '9') + { + c |= 0x20;//poorman's tolower + if (c < 'a' || c >= beyond) + break; + c -= 'a'-10-'0'; + } + } + + bool overflow = false; + auto nextv = v.mulu(radix, overflow).addu(c - '0', overflow); + enforce!ConvOverflowException(!overflow && nextv <= Target.max, "Overflow in integral conversion"); + v = cast(Target) nextv; + s.popFront(); + } while (!s.empty); + + static if (isNarrowString!Source) + source = cast(Source) s; + + return v; +} + +@safe pure unittest +{ + string s; // parse doesn't accept rvalues + foreach (i; 2 .. 37) + { + assert(parse!int(s = "0", i) == 0); + assert(parse!int(s = "1", i) == 1); + assert(parse!byte(s = "10", i) == i); + } + + assert(parse!int(s = "0011001101101", 2) == 0b0011001101101); + assert(parse!int(s = "765", 8) == octal!765); + assert(parse!int(s = "fCDe", 16) == 0xfcde); + + // 6609 + assert(parse!int(s = "-42", 10) == -42); + + assert(parse!ubyte(s = "ff", 16) == 0xFF); +} + +@safe pure unittest // bugzilla 7302 +{ + import std.range : cycle; + auto r = cycle("2A!"); + auto u = parse!uint(r, 16); + assert(u == 42); + assert(r.front == '!'); +} + +@safe pure unittest // bugzilla 13163 +{ + import std.exception; + foreach (s; ["fff", "123"]) + assertThrown!ConvOverflowException(s.parse!ubyte(16)); +} + +@safe pure unittest // bugzilla 17282 +{ + auto str = "0=\x00\x02\x55\x40&\xff\xf0\n\x00\x04\x55\x40\xff\xf0~4+10\n"; + assert(parse!uint(str) == 0); +} + +/** + * Takes a string representing an `enum` type and returns that type. + * + * Params: + * Target = the `enum` type to convert to + * s = the lvalue of the range to _parse + * + * Returns: + * An `enum` of type `Target` + * + * Throws: + * A $(LREF ConvException) if type `Target` does not have a member + * represented by `s`. + */ +Target parse(Target, Source)(ref Source s) +if (isSomeString!Source && !is(Source == enum) && + is(Target == enum)) +{ + import std.algorithm.searching : startsWith; + Target result; + size_t longest_match = 0; + + foreach (i, e; EnumMembers!Target) + { + auto ident = __traits(allMembers, Target)[i]; + if (longest_match < ident.length && s.startsWith(ident)) + { + result = e; + longest_match = ident.length ; + } + } + + if (longest_match > 0) + { + s = s[longest_match .. $]; + return result ; + } + + throw new ConvException( + Target.stringof ~ " does not have a member named '" + ~ to!string(s) ~ "'"); +} + +/// +@safe unittest +{ + enum EnumType : bool { a = true, b = false, c = a } + + auto str = "a"; + assert(parse!EnumType(str) == EnumType.a); +} + +@safe unittest +{ + import std.exception; + + enum EB : bool { a = true, b = false, c = a } + enum EU { a, b, c } + enum EI { a = -1, b = 0, c = 1 } + enum EF : real { a = 1.414, b = 1.732, c = 2.236 } + enum EC : char { a = 'a', b = 'b', c = 'c' } + enum ES : string { a = "aaa", b = "bbb", c = "ccc" } + + foreach (E; AliasSeq!(EB, EU, EI, EF, EC, ES)) + { + assert(to!E("a"c) == E.a); + assert(to!E("b"w) == E.b); + assert(to!E("c"d) == E.c); + + assertThrown!ConvException(to!E("d")); + } +} + +@safe pure unittest // bugzilla 4744 +{ + enum A { member1, member11, member111 } + assert(to!A("member1" ) == A.member1 ); + assert(to!A("member11" ) == A.member11 ); + assert(to!A("member111") == A.member111); + auto s = "member1111"; + assert(parse!A(s) == A.member111 && s == "1"); +} + +/** + * Parses a character range to a floating point number. + * + * Params: + * Target = a floating point type + * source = the lvalue of the range to _parse + * + * Returns: + * A floating point number of type `Target` + * + * Throws: + * A $(LREF ConvException) if `p` is empty, if no number could be + * parsed, or if an overflow occurred. + */ +Target parse(Target, Source)(ref Source source) +if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum) && + isFloatingPoint!Target && !is(Target == enum)) +{ + import core.stdc.math : HUGE_VAL; + import std.ascii : isDigit, isAlpha, toLower, toUpper, isHexDigit; + import std.exception : enforce; + + static if (isNarrowString!Source) + { + import std.string : representation; + auto p = source.representation; + } + else + { + alias p = source; + } + + static immutable real[14] negtab = + [ 1e-4096L,1e-2048L,1e-1024L,1e-512L,1e-256L,1e-128L,1e-64L,1e-32L, + 1e-16L,1e-8L,1e-4L,1e-2L,1e-1L,1.0L ]; + static immutable real[13] postab = + [ 1e+4096L,1e+2048L,1e+1024L,1e+512L,1e+256L,1e+128L,1e+64L,1e+32L, + 1e+16L,1e+8L,1e+4L,1e+2L,1e+1L ]; + + ConvException bailOut()(string msg = null, string fn = __FILE__, size_t ln = __LINE__) + { + if (msg == null) + msg = "Floating point conversion error"; + return new ConvException(text(msg, " for input \"", p, "\"."), fn, ln); + } + + + enforce(!p.empty, bailOut()); + + bool sign = false; + switch (p.front) + { + case '-': + sign = true; + p.popFront(); + enforce(!p.empty, bailOut()); + if (toLower(p.front) == 'i') + goto case 'i'; + enforce(!p.empty, bailOut()); + break; + case '+': + p.popFront(); + enforce(!p.empty, bailOut()); + break; + case 'i': case 'I': + p.popFront(); + enforce(!p.empty, bailOut()); + if (toLower(p.front) == 'n') + { + p.popFront(); + enforce(!p.empty, bailOut()); + if (toLower(p.front) == 'f') + { + // 'inf' + p.popFront(); + static if (isNarrowString!Source) + source = cast(Source) p; + return sign ? -Target.infinity : Target.infinity; + } + } + goto default; + default: {} + } + + bool isHex = false; + bool startsWithZero = p.front == '0'; + if (startsWithZero) + { + p.popFront(); + if (p.empty) + { + static if (isNarrowString!Source) + source = cast(Source) p; + return sign ? -0.0 : 0.0; + } + + isHex = p.front == 'x' || p.front == 'X'; + } + + real ldval = 0.0; + char dot = 0; /* if decimal point has been seen */ + int exp = 0; + long msdec = 0, lsdec = 0; + ulong msscale = 1; + + if (isHex) + { + int guard = 0; + int anydigits = 0; + uint ndigits = 0; + + p.popFront(); + while (!p.empty) + { + int i = p.front; + while (isHexDigit(i)) + { + anydigits = 1; + i = isAlpha(i) ? ((i & ~0x20) - ('A' - 10)) : i - '0'; + if (ndigits < 16) + { + msdec = msdec * 16 + i; + if (msdec) + ndigits++; + } + else if (ndigits == 16) + { + while (msdec >= 0) + { + exp--; + msdec <<= 1; + i <<= 1; + if (i & 0x10) + msdec |= 1; + } + guard = i << 4; + ndigits++; + exp += 4; + } + else + { + guard |= i; + exp += 4; + } + exp -= dot; + p.popFront(); + if (p.empty) + break; + i = p.front; + if (i == '_') + { + p.popFront(); + if (p.empty) + break; + i = p.front; + } + } + if (i == '.' && !dot) + { + p.popFront(); + dot = 4; + } + else + break; + } + + // Round up if (guard && (sticky || odd)) + if (guard & 0x80 && (guard & 0x7F || msdec & 1)) + { + msdec++; + if (msdec == 0) // overflow + { + msdec = 0x8000000000000000L; + exp++; + } + } + + enforce(anydigits, bailOut()); + enforce(!p.empty && (p.front == 'p' || p.front == 'P'), + bailOut("Floating point parsing: exponent is required")); + char sexp; + int e; + + sexp = 0; + p.popFront(); + if (!p.empty) + { + switch (p.front) + { + case '-': sexp++; + goto case; + case '+': p.popFront(); enforce(!p.empty, + new ConvException("Error converting input"~ + " to floating point")); + break; + default: {} + } + } + ndigits = 0; + e = 0; + while (!p.empty && isDigit(p.front)) + { + if (e < 0x7FFFFFFF / 10 - 10) // prevent integer overflow + { + e = e * 10 + p.front - '0'; + } + p.popFront(); + ndigits = 1; + } + exp += (sexp) ? -e : e; + enforce(ndigits, new ConvException("Error converting input"~ + " to floating point")); + + static if (real.mant_dig == 64) + { + if (msdec) + { + int e2 = 0x3FFF + 63; + + // left justify mantissa + while (msdec >= 0) + { + msdec <<= 1; + e2--; + } + + // Stuff mantissa directly into real + ()@trusted{ *cast(long*)&ldval = msdec; }(); + ()@trusted{ (cast(ushort*)&ldval)[4] = cast(ushort) e2; }(); + + import std.math : ldexp; + + // Exponent is power of 2, not power of 10 + ldval = ldexp(ldval,exp); + } + } + else static if (real.mant_dig == 53) + { + if (msdec) + { + //Exponent bias + 52: + //After shifting 52 times left, exp must be 1 + int e2 = 0x3FF + 52; + + // right justify mantissa + // first 11 bits must be zero, rest is implied bit + mantissa + // shift one time less, do rounding, shift again + while ((msdec & 0xFFC0_0000_0000_0000) != 0) + { + msdec = ((cast(ulong) msdec) >> 1); + e2++; + } + + //Have to shift one more time + //and do rounding + if ((msdec & 0xFFE0_0000_0000_0000) != 0) + { + auto roundUp = (msdec & 0x1); + + msdec = ((cast(ulong) msdec) >> 1); + e2++; + if (roundUp) + { + msdec += 1; + //If mantissa was 0b1111... and we added +1 + //the mantissa should be 0b10000 (think of implicit bit) + //and the exponent increased + if ((msdec & 0x0020_0000_0000_0000) != 0) + { + msdec = 0x0010_0000_0000_0000; + e2++; + } + } + } + + + // left justify mantissa + // bit 11 must be 1 + while ((msdec & 0x0010_0000_0000_0000) == 0) + { + msdec <<= 1; + e2--; + } + + // Stuff mantissa directly into double + // (first including implicit bit) + ()@trusted{ *cast(long *)&ldval = msdec; }(); + //Store exponent, now overwriting implicit bit + ()@trusted{ *cast(long *)&ldval &= 0x000F_FFFF_FFFF_FFFF; }(); + ()@trusted{ *cast(long *)&ldval |= ((e2 & 0xFFFUL) << 52); }(); + + import std.math : ldexp; + + // Exponent is power of 2, not power of 10 + ldval = ldexp(ldval,exp); + } + } + else + static assert(false, "Floating point format of real type not supported"); + + goto L6; + } + else // not hex + { + if (toUpper(p.front) == 'N' && !startsWithZero) + { + // nan + p.popFront(); + enforce(!p.empty && toUpper(p.front) == 'A', + new ConvException("error converting input to floating point")); + p.popFront(); + enforce(!p.empty && toUpper(p.front) == 'N', + new ConvException("error converting input to floating point")); + // skip past the last 'n' + p.popFront(); + static if (isNarrowString!Source) + source = cast(Source) p; + return typeof(return).nan; + } + + bool sawDigits = startsWithZero; + + while (!p.empty) + { + int i = p.front; + while (isDigit(i)) + { + sawDigits = true; /* must have at least 1 digit */ + if (msdec < (0x7FFFFFFFFFFFL-10)/10) + msdec = msdec * 10 + (i - '0'); + else if (msscale < (0xFFFFFFFF-10)/10) + { + lsdec = lsdec * 10 + (i - '0'); + msscale *= 10; + } + else + { + exp++; + } + exp -= dot; + p.popFront(); + if (p.empty) + break; + i = p.front; + if (i == '_') + { + p.popFront(); + if (p.empty) + break; + i = p.front; + } + } + if (i == '.' && !dot) + { + p.popFront(); + dot++; + } + else + { + break; + } + } + enforce(sawDigits, new ConvException("no digits seen")); + } + if (!p.empty && (p.front == 'e' || p.front == 'E')) + { + char sexp; + int e; + + sexp = 0; + p.popFront(); + enforce(!p.empty, new ConvException("Unexpected end of input")); + switch (p.front) + { + case '-': sexp++; + goto case; + case '+': p.popFront(); + break; + default: {} + } + bool sawDigits = 0; + e = 0; + while (!p.empty && isDigit(p.front)) + { + if (e < 0x7FFFFFFF / 10 - 10) // prevent integer overflow + { + e = e * 10 + p.front - '0'; + } + p.popFront(); + sawDigits = 1; + } + exp += (sexp) ? -e : e; + enforce(sawDigits, new ConvException("No digits seen.")); + } + + ldval = msdec; + if (msscale != 1) /* if stuff was accumulated in lsdec */ + ldval = ldval * msscale + lsdec; + if (ldval) + { + uint u = 0; + int pow = 4096; + + while (exp > 0) + { + while (exp >= pow) + { + ldval *= postab[u]; + exp -= pow; + } + pow >>= 1; + u++; + } + while (exp < 0) + { + while (exp <= -pow) + { + ldval *= negtab[u]; + enforce(ldval != 0, new ConvException("Range error")); + exp += pow; + } + pow >>= 1; + u++; + } + } + L6: // if overflow occurred + enforce(ldval != HUGE_VAL, new ConvException("Range error")); + + L1: + static if (isNarrowString!Source) + source = cast(Source) p; + return sign ? -ldval : ldval; +} + +/// +@safe unittest +{ + import std.math : approxEqual; + auto str = "123.456"; + + assert(parse!double(str).approxEqual(123.456)); +} + +@safe unittest +{ + import std.exception; + import std.math : isNaN, fabs; + + // Compare reals with given precision + bool feq(in real rx, in real ry, in real precision = 0.000001L) + { + if (rx == ry) + return 1; + + if (isNaN(rx)) + return cast(bool) isNaN(ry); + + if (isNaN(ry)) + return 0; + + return cast(bool)(fabs(rx - ry) <= precision); + } + + // Make given typed literal + F Literal(F)(F f) + { + return f; + } + + foreach (Float; AliasSeq!(float, double, real)) + { + assert(to!Float("123") == Literal!Float(123)); + assert(to!Float("+123") == Literal!Float(+123)); + assert(to!Float("-123") == Literal!Float(-123)); + assert(to!Float("123e2") == Literal!Float(123e2)); + assert(to!Float("123e+2") == Literal!Float(123e+2)); + assert(to!Float("123e-2") == Literal!Float(123e-2)); + assert(to!Float("123.") == Literal!Float(123.0)); + assert(to!Float(".375") == Literal!Float(.375)); + + assert(to!Float("1.23375E+2") == Literal!Float(1.23375E+2)); + + assert(to!Float("0") is 0.0); + assert(to!Float("-0") is -0.0); + + assert(isNaN(to!Float("nan"))); + + assertThrown!ConvException(to!Float("\x00")); + } + + // min and max + float f = to!float("1.17549e-38"); + assert(feq(cast(real) f, cast(real) 1.17549e-38)); + assert(feq(cast(real) f, cast(real) float.min_normal)); + f = to!float("3.40282e+38"); + assert(to!string(f) == to!string(3.40282e+38)); + + // min and max + double d = to!double("2.22508e-308"); + assert(feq(cast(real) d, cast(real) 2.22508e-308)); + assert(feq(cast(real) d, cast(real) double.min_normal)); + d = to!double("1.79769e+308"); + assert(to!string(d) == to!string(1.79769e+308)); + assert(to!string(d) == to!string(double.max)); + + assert(to!string(to!real(to!string(real.max / 2L))) == to!string(real.max / 2L)); + + // min and max + real r = to!real(to!string(real.min_normal)); + version (NetBSD) + { + // NetBSD notice + // to!string returns 3.3621e-4932L. It is less than real.min_normal and it is subnormal value + // Simple C code + // long double rd = 3.3621e-4932L; + // printf("%Le\n", rd); + // has unexpected result: 1.681050e-4932 + // + // Bug report: http://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=50937 + } + else + { + assert(to!string(r) == to!string(real.min_normal)); + } + r = to!real(to!string(real.max)); + assert(to!string(r) == to!string(real.max)); +} + +// Tests for the double implementation +@system unittest +{ + // @system because strtod is not @safe. + static if (real.mant_dig == 53) + { + import core.stdc.stdlib, std.exception, std.math; + + //Should be parsed exactly: 53 bit mantissa + string s = "0x1A_BCDE_F012_3456p10"; + auto x = parse!real(s); + assert(x == 0x1A_BCDE_F012_3456p10L); + //1 bit is implicit + assert(((*cast(ulong*)&x) & 0x000F_FFFF_FFFF_FFFF) == 0xA_BCDE_F012_3456); + assert(strtod("0x1ABCDEF0123456p10", null) == x); + + //Should be parsed exactly: 10 bit mantissa + s = "0x3FFp10"; + x = parse!real(s); + assert(x == 0x03FFp10); + //1 bit is implicit + assert(((*cast(ulong*)&x) & 0x000F_FFFF_FFFF_FFFF) == 0x000F_F800_0000_0000); + assert(strtod("0x3FFp10", null) == x); + + //60 bit mantissa, round up + s = "0xFFF_FFFF_FFFF_FFFFp10"; + x = parse!real(s); + assert(approxEqual(x, 0xFFF_FFFF_FFFF_FFFFp10)); + //1 bit is implicit + assert(((*cast(ulong*)&x) & 0x000F_FFFF_FFFF_FFFF) == 0x0000_0000_0000_0000); + assert(strtod("0xFFFFFFFFFFFFFFFp10", null) == x); + + //60 bit mantissa, round down + s = "0xFFF_FFFF_FFFF_FF90p10"; + x = parse!real(s); + assert(approxEqual(x, 0xFFF_FFFF_FFFF_FF90p10)); + //1 bit is implicit + assert(((*cast(ulong*)&x) & 0x000F_FFFF_FFFF_FFFF) == 0x000F_FFFF_FFFF_FFFF); + assert(strtod("0xFFFFFFFFFFFFF90p10", null) == x); + + //61 bit mantissa, round up 2 + s = "0x1F0F_FFFF_FFFF_FFFFp10"; + x = parse!real(s); + assert(approxEqual(x, 0x1F0F_FFFF_FFFF_FFFFp10)); + //1 bit is implicit + assert(((*cast(ulong*)&x) & 0x000F_FFFF_FFFF_FFFF) == 0x000F_1000_0000_0000); + assert(strtod("0x1F0FFFFFFFFFFFFFp10", null) == x); + + //61 bit mantissa, round down 2 + s = "0x1F0F_FFFF_FFFF_FF10p10"; + x = parse!real(s); + assert(approxEqual(x, 0x1F0F_FFFF_FFFF_FF10p10)); + //1 bit is implicit + assert(((*cast(ulong*)&x) & 0x000F_FFFF_FFFF_FFFF) == 0x000F_0FFF_FFFF_FFFF); + assert(strtod("0x1F0FFFFFFFFFFF10p10", null) == x); + + //Huge exponent + s = "0x1F_FFFF_FFFF_FFFFp900"; + x = parse!real(s); + assert(strtod("0x1FFFFFFFFFFFFFp900", null) == x); + + //exponent too big -> converror + s = ""; + assertThrown!ConvException(x = parse!real(s)); + assert(strtod("0x1FFFFFFFFFFFFFp1024", null) == real.infinity); + + //-exponent too big -> 0 + s = "0x1FFFFFFFFFFFFFp-2000"; + x = parse!real(s); + assert(x == 0); + assert(strtod("0x1FFFFFFFFFFFFFp-2000", null) == x); + } +} + +@system unittest +{ + import core.stdc.errno; + import core.stdc.stdlib; + + errno = 0; // In case it was set by another unittest in a different module. + struct longdouble + { + static if (real.mant_dig == 64) + { + ushort[5] value; + } + else static if (real.mant_dig == 53) + { + ushort[4] value; + } + else + static assert(false, "Not implemented"); + } + + real ld; + longdouble x; + real ld1; + longdouble x1; + int i; + + static if (real.mant_dig == 64) + enum s = "0x1.FFFFFFFFFFFFFFFEp-16382"; + else static if (real.mant_dig == 53) + enum s = "0x1.FFFFFFFFFFFFFFFEp-1000"; + else + static assert(false, "Floating point format for real not supported"); + + auto s2 = s.idup; + ld = parse!real(s2); + assert(s2.empty); + x = *cast(longdouble *)&ld; + + static if (real.mant_dig == 64) + { + version (CRuntime_Microsoft) + ld1 = 0x1.FFFFFFFFFFFFFFFEp-16382L; // strtold currently mapped to strtod + else version (CRuntime_Bionic) + ld1 = 0x1.FFFFFFFFFFFFFFFEp-16382L; // strtold currently mapped to strtod + else + ld1 = strtold(s.ptr, null); + } + else + ld1 = strtold(s.ptr, null); + + x1 = *cast(longdouble *)&ld1; + assert(x1 == x && ld1 == ld); + + assert(!errno); + + s2 = "1.0e5"; + ld = parse!real(s2); + assert(s2.empty); + x = *cast(longdouble *)&ld; + ld1 = strtold("1.0e5", null); + x1 = *cast(longdouble *)&ld1; +} + +@safe pure unittest +{ + import std.exception; + + // Bugzilla 4959 + { + auto s = "0 "; + auto x = parse!double(s); + assert(s == " "); + assert(x == 0.0); + } + + // Bugzilla 3369 + assert(to!float("inf") == float.infinity); + assert(to!float("-inf") == -float.infinity); + + // Bugzilla 6160 + assert(6_5.536e3L == to!real("6_5.536e3")); // 2^16 + assert(0x1000_000_000_p10 == to!real("0x1000_000_000_p10")); // 7.03687e+13 + + // Bugzilla 6258 + assertThrown!ConvException(to!real("-")); + assertThrown!ConvException(to!real("in")); + + // Bugzilla 7055 + assertThrown!ConvException(to!float("INF2")); + + //extra stress testing + auto ssOK = ["1.", "1.1.1", "1.e5", "2e1e", "2a", "2e1_1", + "inf", "-inf", "infa", "-infa", "inf2e2", "-inf2e2"]; + auto ssKO = ["", " ", "2e", "2e+", "2e-", "2ee", "2e++1", "2e--1", "2e_1", "+inf"]; + foreach (s; ssOK) + parse!double(s); + foreach (s; ssKO) + assertThrown!ConvException(parse!double(s)); +} + +/** +Parsing one character off a range returns the first element and calls `popFront`. + +Params: + Target = the type to convert to + s = the lvalue of an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + +Returns: + A character of type `Target` + +Throws: + A $(LREF ConvException) if the range is empty. + */ +Target parse(Target, Source)(ref Source s) +if (isSomeString!Source && !is(Source == enum) && + staticIndexOf!(Unqual!Target, dchar, Unqual!(ElementEncodingType!Source)) >= 0) +{ + if (s.empty) + throw convError!(Source, Target)(s); + static if (is(Unqual!Target == dchar)) + { + Target result = s.front; + s.popFront(); + return result; + } + else + { + // Special case: okay so parse a Char off a Char[] + Target result = s[0]; + s = s[1 .. $]; + return result; + } +} + +@safe pure unittest +{ + foreach (Str; AliasSeq!(string, wstring, dstring)) + { + foreach (Char; AliasSeq!(char, wchar, dchar)) + { + static if (is(Unqual!Char == dchar) || + Char.sizeof == ElementEncodingType!Str.sizeof) + { + Str s = "aaa"; + assert(parse!Char(s) == 'a'); + assert(s == "aa"); + } + } + } +} + +/// ditto +Target parse(Target, Source)(ref Source s) +if (!isSomeString!Source && isInputRange!Source && isSomeChar!(ElementType!Source) && + isSomeChar!Target && Target.sizeof >= ElementType!Source.sizeof && !is(Target == enum)) +{ + if (s.empty) + throw convError!(Source, Target)(s); + Target result = s.front; + s.popFront(); + return result; +} + +/// +@safe pure unittest +{ + auto s = "Hello, World!"; + char first = parse!char(s); + assert(first == 'H'); + assert(s == "ello, World!"); +} + + +/* + Tests for to!bool and parse!bool +*/ +@safe pure unittest +{ + import std.exception; + + assert(to!bool("TruE") == true); + assert(to!bool("faLse"d) == false); + assertThrown!ConvException(to!bool("maybe")); + + auto t = "TrueType"; + assert(parse!bool(t) == true); + assert(t == "Type"); + + auto f = "False killer whale"d; + assert(parse!bool(f) == false); + assert(f == " killer whale"d); + + auto m = "maybe"; + assertThrown!ConvException(parse!bool(m)); + assert(m == "maybe"); // m shouldn't change on failure + + auto s = "true"; + auto b = parse!(const(bool))(s); + assert(b == true); +} + +/** +Parsing a character range to `typeof(null)` returns `null` if the range +spells `"null"`. This function is case insensitive. + +Params: + Target = the type to convert to + s = the lvalue of an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + +Returns: + `null` + +Throws: + A $(LREF ConvException) if the range doesn't represent `null`. + */ +Target parse(Target, Source)(ref Source s) +if (isInputRange!Source && + isSomeChar!(ElementType!Source) && + is(Unqual!Target == typeof(null))) +{ + import std.ascii : toLower; + foreach (c; "null") + { + if (s.empty || toLower(s.front) != c) + throw parseError("null should be case-insensitive 'null'"); + s.popFront(); + } + return null; +} + +/// +@safe pure unittest +{ + import std.exception : assertThrown; + + alias NullType = typeof(null); + auto s1 = "null"; + assert(parse!NullType(s1) is null); + assert(s1 == ""); + + auto s2 = "NUll"d; + assert(parse!NullType(s2) is null); + assert(s2 == ""); + + auto m = "maybe"; + assertThrown!ConvException(parse!NullType(m)); + assert(m == "maybe"); // m shouldn't change on failure + + auto s = "NULL"; + assert(parse!(const NullType)(s) is null); +} + +//Used internally by parse Array/AA, to remove ascii whites +package void skipWS(R)(ref R r) +{ + import std.ascii : isWhite; + static if (isSomeString!R) + { + //Implementation inspired from stripLeft. + foreach (i, c; r) + { + if (!isWhite(c)) + { + r = r[i .. $]; + return; + } + } + r = r[0 .. 0]; //Empty string with correct type. + return; + } + else + { + for (; !r.empty && isWhite(r.front); r.popFront()) + {} + } +} + +/** + * Parses an array from a string given the left bracket (default $(D + * '[')), right bracket (default $(D ']')), and element separator (by + * default $(D ',')). A trailing separator is allowed. + * + * Params: + * s = The string to parse + * lbracket = the character that starts the array + * rbracket = the character that ends the array + * comma = the character that separates the elements of the array + * + * Returns: + * An array of type `Target` + */ +Target parse(Target, Source)(ref Source s, dchar lbracket = '[', dchar rbracket = ']', dchar comma = ',') +if (isSomeString!Source && !is(Source == enum) && + isDynamicArray!Target && !is(Target == enum)) +{ + import std.array : appender; + + auto result = appender!Target(); + + parseCheck!s(lbracket); + skipWS(s); + if (s.empty) + throw convError!(Source, Target)(s); + if (s.front == rbracket) + { + s.popFront(); + return result.data; + } + for (;; s.popFront(), skipWS(s)) + { + if (!s.empty && s.front == rbracket) + break; + result ~= parseElement!(ElementType!Target)(s); + skipWS(s); + if (s.empty) + throw convError!(Source, Target)(s); + if (s.front != comma) + break; + } + parseCheck!s(rbracket); + + return result.data; +} + +/// +@safe pure unittest +{ + auto s1 = `[['h', 'e', 'l', 'l', 'o'], "world"]`; + auto a1 = parse!(string[])(s1); + assert(a1 == ["hello", "world"]); + + auto s2 = `["aaa", "bbb", "ccc"]`; + auto a2 = parse!(string[])(s2); + assert(a2 == ["aaa", "bbb", "ccc"]); +} + +@safe unittest // Bugzilla 9615 +{ + string s0 = "[1,2, ]"; + string s1 = "[1,2, \t\v\r\n]"; + string s2 = "[1,2]"; + assert(s0.parse!(int[]) == [1,2]); + assert(s1.parse!(int[]) == [1,2]); + assert(s2.parse!(int[]) == [1,2]); + + string s3 = `["a","b",]`; + string s4 = `["a","b"]`; + assert(s3.parse!(string[]) == ["a","b"]); + assert(s4.parse!(string[]) == ["a","b"]); + + import std.exception : assertThrown; + string s5 = "[,]"; + string s6 = "[, \t,]"; + assertThrown!ConvException(parse!(string[])(s5)); + assertThrown!ConvException(parse!(int[])(s6)); +} + +@safe unittest +{ + int[] a = [1, 2, 3, 4, 5]; + auto s = to!string(a); + assert(to!(int[])(s) == a); +} + +@safe unittest +{ + int[][] a = [ [1, 2] , [3], [4, 5] ]; + auto s = to!string(a); + assert(to!(int[][])(s) == a); +} + +@safe unittest +{ + int[][][] ia = [ [[1,2],[3,4],[5]] , [[6],[],[7,8,9]] , [[]] ]; + + char[] s = to!(char[])(ia); + int[][][] ia2; + + ia2 = to!(typeof(ia2))(s); + assert( ia == ia2); +} + +@safe pure unittest +{ + import std.exception; + + //Check proper failure + auto s = "[ 1 , 2 , 3 ]"; + foreach (i ; 0 .. s.length-1) + { + auto ss = s[0 .. i]; + assertThrown!ConvException(parse!(int[])(ss)); + } + int[] arr = parse!(int[])(s); +} + +@safe pure unittest +{ + //Checks parsing of strings with escaped characters + string s1 = `[ + "Contains a\0null!", + "tab\there", + "line\nbreak", + "backslash \\ slash / question \?", + "number \x35 five", + "unicode \u65E5 sun", + "very long \U000065E5 sun" + ]`; + + //Note: escaped characters purposefully replaced and isolated to guarantee + //there are no typos in the escape syntax + string[] s2 = [ + "Contains a" ~ '\0' ~ "null!", + "tab" ~ '\t' ~ "here", + "line" ~ '\n' ~ "break", + "backslash " ~ '\\' ~ " slash / question ?", + "number 5 five", + "unicode 日 sun", + "very long 日 sun" + ]; + assert(s2 == parse!(string[])(s1)); + assert(s1.empty); +} + +/// ditto +Target parse(Target, Source)(ref Source s, dchar lbracket = '[', dchar rbracket = ']', dchar comma = ',') +if (isExactSomeString!Source && + isStaticArray!Target && !is(Target == enum)) +{ + static if (hasIndirections!Target) + Target result = Target.init[0].init; + else + Target result = void; + + parseCheck!s(lbracket); + skipWS(s); + if (s.empty) + throw convError!(Source, Target)(s); + if (s.front == rbracket) + { + static if (result.length != 0) + goto Lmanyerr; + else + { + s.popFront(); + return result; + } + } + for (size_t i = 0; ; s.popFront(), skipWS(s)) + { + if (i == result.length) + goto Lmanyerr; + result[i++] = parseElement!(ElementType!Target)(s); + skipWS(s); + if (s.empty) + throw convError!(Source, Target)(s); + if (s.front != comma) + { + if (i != result.length) + goto Lfewerr; + break; + } + } + parseCheck!s(rbracket); + + return result; + +Lmanyerr: + throw parseError(text("Too many elements in input, ", result.length, " elements expected.")); + +Lfewerr: + throw parseError(text("Too few elements in input, ", result.length, " elements expected.")); +} + +@safe pure unittest +{ + import std.exception; + + auto s1 = "[1,2,3,4]"; + auto sa1 = parse!(int[4])(s1); + assert(sa1 == [1,2,3,4]); + + auto s2 = "[[1],[2,3],[4]]"; + auto sa2 = parse!(int[][3])(s2); + assert(sa2 == [[1],[2,3],[4]]); + + auto s3 = "[1,2,3]"; + assertThrown!ConvException(parse!(int[4])(s3)); + + auto s4 = "[1,2,3,4,5]"; + assertThrown!ConvException(parse!(int[4])(s4)); +} + +/** + * Parses an associative array from a string given the left bracket (default $(D + * '[')), right bracket (default $(D ']')), key-value separator (default $(D + * ':')), and element seprator (by default $(D ',')). + * + * Params: + * s = the string to parse + * lbracket = the character that starts the associative array + * rbracket = the character that ends the associative array + * keyval = the character that associates the key with the value + * comma = the character that separates the elements of the associative array + * + * Returns: + * An associative array of type `Target` + */ +Target parse(Target, Source)(ref Source s, dchar lbracket = '[', + dchar rbracket = ']', dchar keyval = ':', dchar comma = ',') +if (isSomeString!Source && !is(Source == enum) && + isAssociativeArray!Target && !is(Target == enum)) +{ + alias KeyType = typeof(Target.init.keys[0]); + alias ValType = typeof(Target.init.values[0]); + + Target result; + + parseCheck!s(lbracket); + skipWS(s); + if (s.empty) + throw convError!(Source, Target)(s); + if (s.front == rbracket) + { + s.popFront(); + return result; + } + for (;; s.popFront(), skipWS(s)) + { + auto key = parseElement!KeyType(s); + skipWS(s); + parseCheck!s(keyval); + skipWS(s); + auto val = parseElement!ValType(s); + skipWS(s); + result[key] = val; + if (s.empty) + throw convError!(Source, Target)(s); + if (s.front != comma) + break; + } + parseCheck!s(rbracket); + + return result; +} + +/// +@safe pure unittest +{ + auto s1 = "[1:10, 2:20, 3:30]"; + auto aa1 = parse!(int[int])(s1); + assert(aa1 == [1:10, 2:20, 3:30]); + + auto s2 = `["aaa":10, "bbb":20, "ccc":30]`; + auto aa2 = parse!(int[string])(s2); + assert(aa2 == ["aaa":10, "bbb":20, "ccc":30]); + + auto s3 = `["aaa":[1], "bbb":[2,3], "ccc":[4,5,6]]`; + auto aa3 = parse!(int[][string])(s3); + assert(aa3 == ["aaa":[1], "bbb":[2,3], "ccc":[4,5,6]]); +} + +@safe pure unittest +{ + import std.exception; + + //Check proper failure + auto s = "[1:10, 2:20, 3:30]"; + foreach (i ; 0 .. s.length-1) + { + auto ss = s[0 .. i]; + assertThrown!ConvException(parse!(int[int])(ss)); + } + int[int] aa = parse!(int[int])(s); +} + +private dchar parseEscape(Source)(ref Source s) +if (isInputRange!Source && isSomeChar!(ElementType!Source)) +{ + parseCheck!s('\\'); + if (s.empty) + throw parseError("Unterminated escape sequence"); + + dchar getHexDigit()(ref Source s_ = s) // workaround + { + import std.ascii : isAlpha, isHexDigit; + if (s_.empty) + throw parseError("Unterminated escape sequence"); + s_.popFront(); + if (s_.empty) + throw parseError("Unterminated escape sequence"); + dchar c = s_.front; + if (!isHexDigit(c)) + throw parseError("Hex digit is missing"); + return isAlpha(c) ? ((c & ~0x20) - ('A' - 10)) : c - '0'; + } + + dchar result; + + switch (s.front) + { + case '"': result = '\"'; break; + case '\'': result = '\''; break; + case '0': result = '\0'; break; + case '?': result = '\?'; break; + case '\\': result = '\\'; break; + case 'a': result = '\a'; break; + case 'b': result = '\b'; break; + case 'f': result = '\f'; break; + case 'n': result = '\n'; break; + case 'r': result = '\r'; break; + case 't': result = '\t'; break; + case 'v': result = '\v'; break; + case 'x': + result = getHexDigit() << 4; + result |= getHexDigit(); + break; + case 'u': + result = getHexDigit() << 12; + result |= getHexDigit() << 8; + result |= getHexDigit() << 4; + result |= getHexDigit(); + break; + case 'U': + result = getHexDigit() << 28; + result |= getHexDigit() << 24; + result |= getHexDigit() << 20; + result |= getHexDigit() << 16; + result |= getHexDigit() << 12; + result |= getHexDigit() << 8; + result |= getHexDigit() << 4; + result |= getHexDigit(); + break; + default: + throw parseError("Unknown escape character " ~ to!string(s.front)); + } + if (s.empty) + throw parseError("Unterminated escape sequence"); + + s.popFront(); + + return result; +} + +@safe pure unittest +{ + string[] s1 = [ + `\"`, `\'`, `\?`, `\\`, `\a`, `\b`, `\f`, `\n`, `\r`, `\t`, `\v`, //Normal escapes + //`\141`, //@@@9621@@@ Octal escapes. + `\x61`, + `\u65E5`, `\U00012456` + //`\&`, `\"`, //@@@9621@@@ Named Character Entities. + ]; + + const(dchar)[] s2 = [ + '\"', '\'', '\?', '\\', '\a', '\b', '\f', '\n', '\r', '\t', '\v', //Normal escapes + //'\141', //@@@9621@@@ Octal escapes. + '\x61', + '\u65E5', '\U00012456' + //'\&', '\"', //@@@9621@@@ Named Character Entities. + ]; + + foreach (i ; 0 .. s1.length) + { + assert(s2[i] == parseEscape(s1[i])); + assert(s1[i].empty); + } +} + +@safe pure unittest +{ + import std.exception; + + string[] ss = [ + `hello!`, //Not an escape + `\`, //Premature termination + `\/`, //Not an escape + `\gggg`, //Not an escape + `\xzz`, //Not an hex + `\x0`, //Premature hex end + `\XB9`, //Not legal hex syntax + `\u!!`, //Not a unicode hex + `\777`, //Octal is larger than a byte //Note: Throws, but simply because octals are unsupported + `\u123`, //Premature hex end + `\U123123` //Premature hex end + ]; + foreach (s ; ss) + assertThrown!ConvException(parseEscape(s)); +} + +// Undocumented +Target parseElement(Target, Source)(ref Source s) +if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum) && + isExactSomeString!Target) +{ + import std.array : appender; + auto result = appender!Target(); + + // parse array of chars + if (s.empty) + throw convError!(Source, Target)(s); + if (s.front == '[') + return parse!Target(s); + + parseCheck!s('\"'); + if (s.empty) + throw convError!(Source, Target)(s); + if (s.front == '\"') + { + s.popFront(); + return result.data; + } + while (true) + { + if (s.empty) + throw parseError("Unterminated quoted string"); + switch (s.front) + { + case '\"': + s.popFront(); + return result.data; + case '\\': + result.put(parseEscape(s)); + break; + default: + result.put(s.front); + s.popFront(); + break; + } + } + assert(0); +} + +// ditto +Target parseElement(Target, Source)(ref Source s) +if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum) && + isSomeChar!Target && !is(Target == enum)) +{ + Target c; + + parseCheck!s('\''); + if (s.empty) + throw convError!(Source, Target)(s); + if (s.front != '\\') + { + c = s.front; + s.popFront(); + } + else + c = parseEscape(s); + parseCheck!s('\''); + + return c; +} + +// ditto +Target parseElement(Target, Source)(ref Source s) +if (isInputRange!Source && isSomeChar!(ElementType!Source) && + !isSomeString!Target && !isSomeChar!Target) +{ + return parse!Target(s); +} + + +/*************************************************************** + * Convenience functions for converting one or more arguments + * of any type into _text (the three character widths). + */ +string text(T...)(T args) +if (T.length > 0) { return textImpl!string(args); } + +// @@@DEPRECATED_2018-06@@@ +deprecated("Calling `text` with 0 arguments is deprecated") +string text(T...)(T args) +if (T.length == 0) { return textImpl!string(args); } + +///ditto +wstring wtext(T...)(T args) +if (T.length > 0) { return textImpl!wstring(args); } + +// @@@DEPRECATED_2018-06@@@ +deprecated("Calling `wtext` with 0 arguments is deprecated") +wstring wtext(T...)(T args) +if (T.length == 0) { return textImpl!wstring(args); } + +///ditto +dstring dtext(T...)(T args) +if (T.length > 0) { return textImpl!dstring(args); } + +/// +@safe unittest +{ + assert( text(42, ' ', 1.5, ": xyz") == "42 1.5: xyz"c); + assert(wtext(42, ' ', 1.5, ": xyz") == "42 1.5: xyz"w); + assert(dtext(42, ' ', 1.5, ": xyz") == "42 1.5: xyz"d); +} + +// @@@DEPRECATED_2018-06@@@ +deprecated("Calling `dtext` with 0 arguments is deprecated") +dstring dtext(T...)(T args) +if (T.length == 0) { return textImpl!dstring(args); } + +private S textImpl(S, U...)(U args) +{ + static if (U.length == 0) + { + return null; + } + else static if (U.length == 1) + { + return to!S(args[0]); + } + else + { + import std.array : appender; + + auto app = appender!S(); + + foreach (arg; args) + app.put(to!S(arg)); + return app.data; + } +} + + +/*************************************************************** +The $(D octal) facility provides a means to declare a number in base 8. +Using $(D octal!177) or $(D octal!"177") for 127 represented in octal +(same as 0177 in C). + +The rules for strings are the usual for literals: If it can fit in an +$(D int), it is an $(D int). Otherwise, it is a $(D long). But, if the +user specifically asks for a $(D long) with the $(D L) suffix, always +give the $(D long). Give an unsigned iff it is asked for with the $(D +U) or $(D u) suffix. _Octals created from integers preserve the type +of the passed-in integral. + +See_Also: + $(LREF parse) for parsing octal strings at runtime. + */ +template octal(string num) +if (isOctalLiteral(num)) +{ + static if ((octalFitsInInt!num && !literalIsLong!num) && !literalIsUnsigned!num) + enum octal = octal!int(num); + else static if ((!octalFitsInInt!num || literalIsLong!num) && !literalIsUnsigned!num) + enum octal = octal!long(num); + else static if ((octalFitsInInt!num && !literalIsLong!num) && literalIsUnsigned!num) + enum octal = octal!uint(num); + else static if ((!octalFitsInInt!(num) || literalIsLong!(num)) && literalIsUnsigned!(num)) + enum octal = octal!ulong(num); + else + static assert(false); +} + +/// Ditto +template octal(alias decimalInteger) +if (isIntegral!(typeof(decimalInteger))) +{ + enum octal = octal!(typeof(decimalInteger))(to!string(decimalInteger)); +} + +/// +@safe unittest +{ + // same as 0177 + auto x = octal!177; + // octal is a compile-time device + enum y = octal!160; + // Create an unsigned octal + auto z = octal!"1_000_000u"; +} + +/* + Takes a string, num, which is an octal literal, and returns its + value, in the type T specified. +*/ +private T octal(T)(const string num) +{ + assert(isOctalLiteral(num)); + + T value = 0; + + foreach (const char s; num) + { + if (s < '0' || s > '7') // we only care about digits; skip the rest + // safe to skip - this is checked out in the assert so these + // are just suffixes + continue; + + value *= 8; + value += s - '0'; + } + + return value; +} + +@safe unittest +{ + int a = octal!int("10"); + assert(a == 8); +} + +/* +Take a look at int.max and int.max+1 in octal and the logic for this +function follows directly. + */ +private template octalFitsInInt(string octalNum) +{ + // note it is important to strip the literal of all + // non-numbers. kill the suffix and underscores lest they mess up + // the number of digits here that we depend on. + enum bool octalFitsInInt = strippedOctalLiteral(octalNum).length < 11 || + strippedOctalLiteral(octalNum).length == 11 && + strippedOctalLiteral(octalNum)[0] == '1'; +} + +private string strippedOctalLiteral(string original) +{ + string stripped = ""; + foreach (c; original) + if (c >= '0' && c <= '7') + stripped ~= c; + return stripped; +} + +private template literalIsLong(string num) +{ + static if (num.length > 1) + // can be xxL or xxLu according to spec + enum literalIsLong = (num[$-1] == 'L' || num[$-2] == 'L'); + else + enum literalIsLong = false; +} + +private template literalIsUnsigned(string num) +{ + static if (num.length > 1) + // can be xxU or xxUL according to spec + enum literalIsUnsigned = (num[$-1] == 'u' || num[$-2] == 'u') + // both cases are allowed too + || (num[$-1] == 'U' || num[$-2] == 'U'); + else + enum literalIsUnsigned = false; +} + +/* +Returns if the given string is a correctly formatted octal literal. + +The format is specified in spec/lex.html. The leading zero is allowed, but +not required. + */ +@safe pure nothrow @nogc +private bool isOctalLiteral(const string num) +{ + if (num.length == 0) + return false; + + // Must start with a number. To avoid confusion, literals that + // start with a '0' are not allowed + if (num[0] == '0' && num.length > 1) + return false; + if (num[0] < '0' || num[0] > '7') + return false; + + foreach (i, c; num) + { + if ((c < '0' || c > '7') && c != '_') // not a legal character + { + if (i < num.length - 2) + return false; + else // gotta check for those suffixes + { + if (c != 'U' && c != 'u' && c != 'L') + return false; + if (i != num.length - 1) + { + // if we're not the last one, the next one must + // also be a suffix to be valid + char c2 = num[$-1]; + if (c2 != 'U' && c2 != 'u' && c2 != 'L') + return false; // spam at the end of the string + if (c2 == c) + return false; // repeats are disallowed + } + } + } + } + + return true; +} + +@safe unittest +{ + // ensure that you get the right types, even with embedded underscores + auto w = octal!"100_000_000_000"; + static assert(!is(typeof(w) == int)); + auto w2 = octal!"1_000_000_000"; + static assert(is(typeof(w2) == int)); + + static assert(octal!"45" == 37); + static assert(octal!"0" == 0); + static assert(octal!"7" == 7); + static assert(octal!"10" == 8); + static assert(octal!"666" == 438); + + static assert(octal!45 == 37); + static assert(octal!0 == 0); + static assert(octal!7 == 7); + static assert(octal!10 == 8); + static assert(octal!666 == 438); + + static assert(octal!"66_6" == 438); + + static assert(octal!2520046213 == 356535435); + static assert(octal!"2520046213" == 356535435); + + static assert(octal!17777777777 == int.max); + + static assert(!__traits(compiles, octal!823)); + + static assert(!__traits(compiles, octal!"823")); + + static assert(!__traits(compiles, octal!"_823")); + static assert(!__traits(compiles, octal!"spam")); + static assert(!__traits(compiles, octal!"77%")); + + static assert(is(typeof(octal!"17777777777") == int)); + static assert(octal!"17777777777" == int.max); + + static assert(is(typeof(octal!"20000000000U") == ulong)); // Shouldn't this be uint? + static assert(octal!"20000000000" == uint(int.max) + 1); + + static assert(is(typeof(octal!"777777777777777777777") == long)); + static assert(octal!"777777777777777777777" == long.max); + + static assert(is(typeof(octal!"1000000000000000000000U") == ulong)); + static assert(octal!"1000000000000000000000" == ulong(long.max) + 1); + + int a; + long b; + + // biggest value that should fit in an it + a = octal!"17777777777"; + assert(a == int.max); + // should not fit in the int + static assert(!__traits(compiles, a = octal!"20000000000")); + // ... but should fit in a long + b = octal!"20000000000"; + assert(b == 1L + int.max); + + b = octal!"1L"; + assert(b == 1); + b = octal!1L; + assert(b == 1); +} + +/+ +emplaceRef is a package function for phobos internal use. It works like +emplace, but takes its argument by ref (as opposed to "by pointer"). + +This makes it easier to use, easier to be safe, and faster in a non-inline +build. + +Furthermore, emplaceRef optionally takes a type paremeter, which specifies +the type we want to build. This helps to build qualified objects on mutable +buffer, without breaking the type system with unsafe casts. ++/ +package void emplaceRef(T, UT, Args...)(ref UT chunk, auto ref Args args) +{ + static if (args.length == 0) + { + static assert(is(typeof({static T i;})), + convFormat("Cannot emplace a %1$s because %1$s.this() is annotated with @disable.", T.stringof)); + static if (is(T == class)) static assert(!isAbstractClass!T, + T.stringof ~ " is abstract and it can't be emplaced"); + emplaceInitializer(chunk); + } + else static if ( + !is(T == struct) && Args.length == 1 /* primitives, enums, arrays */ + || + Args.length == 1 && is(typeof({T t = args[0];})) /* conversions */ + || + is(typeof(T(args))) /* general constructors */) + { + static struct S + { + T payload; + this(ref Args x) + { + static if (Args.length == 1) + static if (is(typeof(payload = x[0]))) + payload = x[0]; + else + payload = T(x[0]); + else + payload = T(x); + } + } + if (__ctfe) + { + static if (is(typeof(chunk = T(args)))) + chunk = T(args); + else static if (args.length == 1 && is(typeof(chunk = args[0]))) + chunk = args[0]; + else assert(0, "CTFE emplace doesn't support " + ~ T.stringof ~ " from " ~ Args.stringof); + } + else + { + S* p = () @trusted { return cast(S*) &chunk; }(); + emplaceInitializer(*p); + p.__ctor(args); + } + } + else static if (is(typeof(chunk.__ctor(args)))) + { + // This catches the rare case of local types that keep a frame pointer + emplaceInitializer(chunk); + chunk.__ctor(args); + } + else + { + //We can't emplace. Try to diagnose a disabled postblit. + static assert(!(Args.length == 1 && is(Args[0] : T)), + convFormat("Cannot emplace a %1$s because %1$s.this(this) is annotated with @disable.", T.stringof)); + + //We can't emplace. + static assert(false, + convFormat("%s cannot be emplaced from %s.", T.stringof, Args[].stringof)); + } +} +// ditto +package void emplaceRef(UT, Args...)(ref UT chunk, auto ref Args args) +if (is(UT == Unqual!UT)) +{ + emplaceRef!(UT, UT)(chunk, args); +} + +//emplace helper functions +private void emplaceInitializer(T)(ref T chunk) @trusted pure nothrow +{ + static if (!hasElaborateAssign!T && isAssignable!T) + chunk = T.init; + else + { + import core.stdc.string : memcpy; + static immutable T init = T.init; + memcpy(&chunk, &init, T.sizeof); + } +} + +// emplace +/** +Given a pointer $(D chunk) to uninitialized memory (but already typed +as $(D T)), constructs an object of non-$(D class) type $(D T) at that +address. If `T` is a class, initializes the class reference to null. + +Returns: A pointer to the newly constructed object (which is the same +as $(D chunk)). + */ +T* emplace(T)(T* chunk) @safe pure nothrow +{ + emplaceRef!T(*chunk); + return chunk; +} + +/// +@system unittest +{ + static struct S + { + int i = 42; + } + S[2] s2 = void; + emplace(&s2); + assert(s2[0].i == 42 && s2[1].i == 42); +} + +/// +@system unittest +{ + interface I {} + class K : I {} + + K k = void; + emplace(&k); + assert(k is null); + + I i = void; + emplace(&i); + assert(i is null); +} + +/** +Given a pointer $(D chunk) to uninitialized memory (but already typed +as a non-class type $(D T)), constructs an object of type $(D T) at +that address from arguments $(D args). If `T` is a class, initializes +the class reference to `args[0]`. + +This function can be $(D @trusted) if the corresponding constructor of +$(D T) is $(D @safe). + +Returns: A pointer to the newly constructed object (which is the same +as $(D chunk)). + */ +T* emplace(T, Args...)(T* chunk, auto ref Args args) +if (is(T == struct) || Args.length == 1) +{ + emplaceRef!T(*chunk, args); + return chunk; +} + +/// +@system unittest +{ + int a; + int b = 42; + assert(*emplace!int(&a, b) == 42); +} + +@system unittest +{ + shared int i; + emplace(&i, 42); + assert(i == 42); +} + +private void testEmplaceChunk(void[] chunk, size_t typeSize, size_t typeAlignment, string typeName) @nogc pure nothrow +{ + assert(chunk.length >= typeSize, "emplace: Chunk size too small."); + assert((cast(size_t) chunk.ptr) % typeAlignment == 0, "emplace: Chunk is not aligned."); +} + +/** +Given a raw memory area $(D chunk), constructs an object of $(D class) +type $(D T) at that address. The constructor is passed the arguments +$(D Args). + +If `T` is an inner class whose `outer` field can be used to access an instance +of the enclosing class, then `Args` must not be empty, and the first member of it +must be a valid initializer for that `outer` field. Correct initialization of +this field is essential to access members of the outer class inside `T` methods. + +Preconditions: +$(D chunk) must be at least as large as $(D T) needs +and should have an alignment multiple of $(D T)'s alignment. (The size +of a $(D class) instance is obtained by using $(D +__traits(classInstanceSize, T))). + +Note: +This function can be $(D @trusted) if the corresponding constructor of +$(D T) is $(D @safe). + +Returns: The newly constructed object. + */ +T emplace(T, Args...)(void[] chunk, auto ref Args args) +if (is(T == class)) +{ + static assert(!isAbstractClass!T, T.stringof ~ + " is abstract and it can't be emplaced"); + + enum classSize = __traits(classInstanceSize, T); + testEmplaceChunk(chunk, classSize, classInstanceAlignment!T, T.stringof); + auto result = cast(T) chunk.ptr; + + // Initialize the object in its pre-ctor state + chunk[0 .. classSize] = typeid(T).initializer[]; + + static if (isInnerClass!T) + { + static assert(Args.length > 0, + "Initializing an inner class requires a pointer to the outer class"); + static assert(is(Args[0] : typeof(T.outer)), + "The first argument must be a pointer to the outer class"); + + result.outer = args[0]; + alias args1 = args[1..$]; + } + else alias args1 = args; + + // Call the ctor if any + static if (is(typeof(result.__ctor(args1)))) + { + // T defines a genuine constructor accepting args + // Go the classic route: write .init first, then call ctor + result.__ctor(args1); + } + else + { + static assert(args1.length == 0 && !is(typeof(&T.__ctor)), + "Don't know how to initialize an object of type " + ~ T.stringof ~ " with arguments " ~ typeof(args1).stringof); + } + return result; +} + +/// +@system unittest +{ + static class C + { + int i; + this(int i){this.i = i;} + } + auto buf = new void[__traits(classInstanceSize, C)]; + auto c = emplace!C(buf, 5); + assert(c.i == 5); +} + +@system unittest +{ + class Outer + { + int i = 3; + class Inner + { + auto getI() { return i; } + } + } + auto outerBuf = new void[__traits(classInstanceSize, Outer)]; + auto innerBuf = new void[__traits(classInstanceSize, Outer.Inner)]; + auto inner = innerBuf.emplace!(Outer.Inner)(outerBuf.emplace!Outer); + assert(inner.getI == 3); +} + +@nogc pure nothrow @system unittest +{ + int var = 6; + align(__conv_EmplaceTestClass.alignof) ubyte[__traits(classInstanceSize, __conv_EmplaceTestClass)] buf; + auto k = emplace!__conv_EmplaceTestClass(buf, 5, var); + assert(k.i == 5); + assert(var == 7); +} + +/** +Given a raw memory area $(D chunk), constructs an object of non-$(D +class) type $(D T) at that address. The constructor is passed the +arguments $(D args), if any. + +Preconditions: +$(D chunk) must be at least as large +as $(D T) needs and should have an alignment multiple of $(D T)'s +alignment. + +Note: +This function can be $(D @trusted) if the corresponding constructor of +$(D T) is $(D @safe). + +Returns: A pointer to the newly constructed object. + */ +T* emplace(T, Args...)(void[] chunk, auto ref Args args) +if (!is(T == class)) +{ + testEmplaceChunk(chunk, T.sizeof, T.alignof, T.stringof); + emplaceRef!(T, Unqual!T)(*cast(Unqual!T*) chunk.ptr, args); + return cast(T*) chunk.ptr; +} + +/// +@system unittest +{ + struct S + { + int a, b; + } + auto buf = new void[S.sizeof]; + S s; + s.a = 42; + s.b = 43; + auto s1 = emplace!S(buf, s); + assert(s1.a == 42 && s1.b == 43); +} + +// Bulk of emplace unittests starts here + +@system unittest /* unions */ +{ + static union U + { + string a; + int b; + struct + { + long c; + int[] d; + } + } + U u1 = void; + U u2 = { "hello" }; + emplace(&u1, u2); + assert(u1.a == "hello"); +} + +version (unittest) private struct __conv_EmplaceTest +{ + int i = 3; + this(int i) + { + assert(this.i == 3 && i == 5); + this.i = i; + } + this(int i, ref int j) + { + assert(i == 5 && j == 6); + this.i = i; + ++j; + } + +@disable: + this(); + this(this); + void opAssign(); +} + +version (unittest) private class __conv_EmplaceTestClass +{ + int i = 3; + this(int i) @nogc @safe pure nothrow + { + assert(this.i == 3 && i == 5); + this.i = i; + } + this(int i, ref int j) @nogc @safe pure nothrow + { + assert(i == 5 && j == 6); + this.i = i; + ++j; + } +} + +@system unittest // bugzilla 15772 +{ + abstract class Foo {} + class Bar: Foo {} + void[] memory; + // test in emplaceInitializer + static assert(!is(typeof(emplace!Foo(cast(Foo*) memory.ptr)))); + static assert( is(typeof(emplace!Bar(cast(Bar*) memory.ptr)))); + // test in the emplace overload that takes void[] + static assert(!is(typeof(emplace!Foo(memory)))); + static assert( is(typeof(emplace!Bar(memory)))); +} + +@system unittest +{ + struct S { @disable this(); } + S s = void; + static assert(!__traits(compiles, emplace(&s))); + emplace(&s, S.init); +} + +@system unittest +{ + struct S1 + {} + + struct S2 + { + void opAssign(S2); + } + + S1 s1 = void; + S2 s2 = void; + S1[2] as1 = void; + S2[2] as2 = void; + emplace(&s1); + emplace(&s2); + emplace(&as1); + emplace(&as2); +} + +@system unittest +{ + static struct S1 + { + this(this) @disable; + } + static struct S2 + { + this() @disable; + } + S1[2] ss1 = void; + S2[2] ss2 = void; + emplace(&ss1); + static assert(!__traits(compiles, emplace(&ss2))); + S1 s1 = S1.init; + S2 s2 = S2.init; + static assert(!__traits(compiles, emplace(&ss1, s1))); + emplace(&ss2, s2); +} + +@system unittest +{ + struct S + { + immutable int i; + } + S s = void; + S[2] ss1 = void; + S[2] ss2 = void; + emplace(&s, 5); + assert(s.i == 5); + emplace(&ss1, s); + assert(ss1[0].i == 5 && ss1[1].i == 5); + emplace(&ss2, ss1); + assert(ss2 == ss1); +} + +//Start testing emplace-args here + +@system unittest +{ + interface I {} + class K : I {} + + K k = null, k2 = new K; + assert(k !is k2); + emplace!K(&k, k2); + assert(k is k2); + + I i = null; + assert(i !is k); + emplace!I(&i, k); + assert(i is k); +} + +@system unittest +{ + static struct S + { + int i = 5; + void opAssign(S){assert(0);} + } + S[2] sa = void; + S[2] sb; + emplace(&sa, sb); + assert(sa[0].i == 5 && sa[1].i == 5); +} + +//Start testing emplace-struct here + +// Test constructor branch +@system unittest +{ + struct S + { + double x = 5, y = 6; + this(int a, int b) + { + assert(x == 5 && y == 6); + x = a; + y = b; + } + } + + auto s1 = new void[S.sizeof]; + auto s2 = S(42, 43); + assert(*emplace!S(cast(S*) s1.ptr, s2) == s2); + assert(*emplace!S(cast(S*) s1, 44, 45) == S(44, 45)); +} + +@system unittest +{ + __conv_EmplaceTest k = void; + emplace(&k, 5); + assert(k.i == 5); +} + +@system unittest +{ + int var = 6; + __conv_EmplaceTest k = void; + emplace(&k, 5, var); + assert(k.i == 5); + assert(var == 7); +} + +// Test matching fields branch +@system unittest +{ + struct S { uint n; } + S s; + emplace!S(&s, 2U); + assert(s.n == 2); +} + +@safe unittest +{ + struct S { int a, b; this(int){} } + S s; + static assert(!__traits(compiles, emplace!S(&s, 2, 3))); +} + +@system unittest +{ + struct S { int a, b = 7; } + S s1 = void, s2 = void; + + emplace!S(&s1, 2); + assert(s1.a == 2 && s1.b == 7); + + emplace!S(&s2, 2, 3); + assert(s2.a == 2 && s2.b == 3); +} + +//opAssign +@system unittest +{ + static struct S + { + int i = 5; + void opAssign(int){assert(0);} + void opAssign(S){assert(0);} + } + S sa1 = void; + S sa2 = void; + S sb1 = S(1); + emplace(&sa1, sb1); + emplace(&sa2, 2); + assert(sa1.i == 1); + assert(sa2.i == 2); +} + +//postblit precedence +@system unittest +{ + //Works, but breaks in "-w -O" because of @@@9332@@@. + //Uncomment test when 9332 is fixed. + static struct S + { + int i; + + this(S other){assert(false);} + this(int i){this.i = i;} + this(this){} + } + S a = void; + assert(is(typeof({S b = a;}))); //Postblit + assert(is(typeof({S b = S(a);}))); //Constructor + auto b = S(5); + emplace(&a, b); + assert(a.i == 5); + + static struct S2 + { + int* p; + this(const S2){} + } + static assert(!is(immutable S2 : S2)); + S2 s2 = void; + immutable is2 = (immutable S2).init; + emplace(&s2, is2); +} + +//nested structs and postblit +@system unittest +{ + static struct S + { + int* p; + this(int i){p = [i].ptr;} + this(this) + { + if (p) + p = [*p].ptr; + } + } + static struct SS + { + S s; + void opAssign(const SS) + { + assert(0); + } + } + SS ssa = void; + SS ssb = SS(S(5)); + emplace(&ssa, ssb); + assert(*ssa.s.p == 5); + assert(ssa.s.p != ssb.s.p); +} + +//disabled postblit +@system unittest +{ + static struct S1 + { + int i; + @disable this(this); + } + S1 s1 = void; + emplace(&s1, 1); + assert(s1.i == 1); + static assert(!__traits(compiles, emplace(&s1, S1.init))); + + static struct S2 + { + int i; + @disable this(this); + this(ref S2){} + } + S2 s2 = void; + static assert(!__traits(compiles, emplace(&s2, 1))); + emplace(&s2, S2.init); + + static struct SS1 + { + S1 s; + } + SS1 ss1 = void; + emplace(&ss1); + static assert(!__traits(compiles, emplace(&ss1, SS1.init))); + + static struct SS2 + { + S2 s; + } + SS2 ss2 = void; + emplace(&ss2); + static assert(!__traits(compiles, emplace(&ss2, SS2.init))); + + + // SS1 sss1 = s1; //This doesn't compile + // SS1 sss1 = SS1(s1); //This doesn't compile + // So emplace shouldn't compile either + static assert(!__traits(compiles, emplace(&sss1, s1))); + static assert(!__traits(compiles, emplace(&sss2, s2))); +} + +//Imutability +@system unittest +{ + //Castable immutability + { + static struct S1 + { + int i; + } + static assert(is( immutable(S1) : S1)); + S1 sa = void; + auto sb = immutable(S1)(5); + emplace(&sa, sb); + assert(sa.i == 5); + } + //Un-castable immutability + { + static struct S2 + { + int* p; + } + static assert(!is(immutable(S2) : S2)); + S2 sa = void; + auto sb = immutable(S2)(null); + assert(!__traits(compiles, emplace(&sa, sb))); + } +} + +@system unittest +{ + static struct S + { + immutable int i; + immutable(int)* j; + } + S s = void; + emplace(&s, 1, null); + emplace(&s, 2, &s.i); + assert(s is S(2, &s.i)); +} + +//Context pointer +@system unittest +{ + int i = 0; + { + struct S1 + { + void foo(){++i;} + } + S1 sa = void; + S1 sb; + emplace(&sa, sb); + sa.foo(); + assert(i == 1); + } + { + struct S2 + { + void foo(){++i;} + this(this){} + } + S2 sa = void; + S2 sb; + emplace(&sa, sb); + sa.foo(); + assert(i == 2); + } +} + +//Alias this +@system unittest +{ + static struct S + { + int i; + } + //By Ref + { + static struct SS1 + { + int j; + S s; + alias s this; + } + S s = void; + SS1 ss = SS1(1, S(2)); + emplace(&s, ss); + assert(s.i == 2); + } + //By Value + { + static struct SS2 + { + int j; + S s; + S foo() @property{return s;} + alias foo this; + } + S s = void; + SS2 ss = SS2(1, S(2)); + emplace(&s, ss); + assert(s.i == 2); + } +} +version (unittest) +{ + //Ambiguity + struct __std_conv_S + { + int i; + this(__std_conv_SS ss) {assert(0);} + static opCall(__std_conv_SS ss) + { + __std_conv_S s; s.i = ss.j; + return s; + } + } + struct __std_conv_SS + { + int j; + __std_conv_S s; + ref __std_conv_S foo() return @property {s.i = j; return s;} + alias foo this; + } + static assert(is(__std_conv_SS : __std_conv_S)); + @system unittest + { + __std_conv_S s = void; + __std_conv_SS ss = __std_conv_SS(1); + + __std_conv_S sTest1 = ss; //this calls "SS alias this" (and not "S.this(SS)") + emplace(&s, ss); //"alias this" should take precedence in emplace over "opCall" + assert(s.i == 1); + } +} + +//Nested classes +@system unittest +{ + class A{} + static struct S + { + A a; + } + S s1 = void; + S s2 = S(new A); + emplace(&s1, s2); + assert(s1.a is s2.a); +} + +//safety & nothrow & CTFE +@system unittest +{ + //emplace should be safe for anything with no elaborate opassign + static struct S1 + { + int i; + } + static struct S2 + { + int i; + this(int j)@safe nothrow{i = j;} + } + + int i; + S1 s1 = void; + S2 s2 = void; + + auto pi = &i; + auto ps1 = &s1; + auto ps2 = &s2; + + void foo() @safe nothrow + { + emplace(pi); + emplace(pi, 5); + emplace(ps1); + emplace(ps1, 5); + emplace(ps1, S1.init); + emplace(ps2); + emplace(ps2, 5); + emplace(ps2, S2.init); + } + foo(); + + T bar(T)() @property + { + T t/+ = void+/; //CTFE void illegal + emplace(&t, 5); + return t; + } + // CTFE + enum a = bar!int; + static assert(a == 5); + enum b = bar!S1; + static assert(b.i == 5); + enum c = bar!S2; + static assert(c.i == 5); + // runtime + auto aa = bar!int; + assert(aa == 5); + auto bb = bar!S1; + assert(bb.i == 5); + auto cc = bar!S2; + assert(cc.i == 5); +} + + +@system unittest +{ + struct S + { + int[2] get(){return [1, 2];} + alias get this; + } + struct SS + { + int[2] ii; + } + struct ISS + { + int[2] ii; + } + S s; + SS ss = void; + ISS iss = void; + emplace(&ss, s); + emplace(&iss, s); + assert(ss.ii == [1, 2]); + assert(iss.ii == [1, 2]); +} + +//disable opAssign +@system unittest +{ + static struct S + { + @disable void opAssign(S); + } + S s; + emplace(&s, S.init); +} + +//opCall +@system unittest +{ + int i; + //Without constructor + { + static struct S1 + { + int i; + static S1 opCall(int*){assert(0);} + } + S1 s = void; + static assert(!__traits(compiles, emplace(&s, 1))); + } + //With constructor + { + static struct S2 + { + int i = 0; + static S2 opCall(int*){assert(0);} + static S2 opCall(int){assert(0);} + this(int i){this.i = i;} + } + S2 s = void; + emplace(&s, 1); + assert(s.i == 1); + } + //With postblit ambiguity + { + static struct S3 + { + int i = 0; + static S3 opCall(ref S3){assert(0);} + } + S3 s = void; + emplace(&s, S3.init); + } +} + +@safe unittest //@@@9559@@@ +{ + import std.algorithm.iteration : map; + import std.array : array; + import std.typecons : Nullable; + alias I = Nullable!int; + auto ints = [0, 1, 2].map!(i => i & 1 ? I.init : I(i))(); + auto asArray = array(ints); +} + +@system unittest //http://forum.dlang.org/post/nxbdgtdlmwscocbiypjs@forum.dlang.org +{ + import std.array : array; + import std.datetime : SysTime, UTC; + import std.math : isNaN; + + static struct A + { + double i; + } + + static struct B + { + invariant() + { + if (j == 0) + assert(a.i.isNaN(), "why is 'j' zero?? and i is not NaN?"); + else + assert(!a.i.isNaN()); + } + SysTime when; // comment this line avoid the breakage + int j; + A a; + } + + B b1 = B.init; + assert(&b1); // verify that default eyes invariants are ok; + + auto b2 = B(SysTime(0, UTC()), 1, A(1)); + assert(&b2); + auto b3 = B(SysTime(0, UTC()), 1, A(1)); + assert(&b3); + + auto arr = [b2, b3]; + + assert(arr[0].j == 1); + assert(arr[1].j == 1); + auto a2 = arr.array(); // << bang, invariant is raised, also if b2 and b3 are good +} + +//static arrays +@system unittest +{ + static struct S + { + int[2] ii; + } + static struct IS + { + immutable int[2] ii; + } + int[2] ii; + S s = void; + IS ims = void; + ubyte ub = 2; + emplace(&s, ub); + emplace(&s, ii); + emplace(&ims, ub); + emplace(&ims, ii); + uint[2] uu; + static assert(!__traits(compiles, {S ss = S(uu);})); + static assert(!__traits(compiles, emplace(&s, uu))); +} + +@system unittest +{ + int[2] sii; + int[2] sii2; + uint[2] uii; + uint[2] uii2; + emplace(&sii, 1); + emplace(&sii, 1U); + emplace(&uii, 1); + emplace(&uii, 1U); + emplace(&sii, sii2); + //emplace(&sii, uii2); //Sorry, this implementation doesn't know how to... + //emplace(&uii, sii2); //Sorry, this implementation doesn't know how to... + emplace(&uii, uii2); + emplace(&sii, sii2[]); + //emplace(&sii, uii2[]); //Sorry, this implementation doesn't know how to... + //emplace(&uii, sii2[]); //Sorry, this implementation doesn't know how to... + emplace(&uii, uii2[]); +} + +@system unittest +{ + bool allowDestruction = false; + struct S + { + int i; + this(this){} + ~this(){assert(allowDestruction);} + } + S s = S(1); + S[2] ss1 = void; + S[2] ss2 = void; + S[2] ss3 = void; + emplace(&ss1, s); + emplace(&ss2, ss1); + emplace(&ss3, ss2[]); + assert(ss1[1] == s); + assert(ss2[1] == s); + assert(ss3[1] == s); + allowDestruction = true; +} + +@system unittest +{ + //Checks postblit, construction, and context pointer + int count = 0; + struct S + { + this(this) + { + ++count; + } + ~this() + { + --count; + } + } + + S s; + { + S[4] ss = void; + emplace(&ss, s); + assert(count == 4); + } + assert(count == 0); +} + +@system unittest +{ + struct S + { + int i; + } + S s; + S[2][2][2] sss = void; + emplace(&sss, s); +} + +@system unittest //Constness +{ + import std.stdio; + + int a = void; + emplaceRef!(const int)(a, 5); + + immutable i = 5; + const(int)* p = void; + emplaceRef!(const int*)(p, &i); + + struct S + { + int* p; + } + alias IS = immutable(S); + S s = void; + emplaceRef!IS(s, IS()); + S[2] ss = void; + emplaceRef!(IS[2])(ss, IS()); + + IS[2] iss = IS.init; + emplaceRef!(IS[2])(ss, iss); + emplaceRef!(IS[2])(ss, iss[]); +} + +pure nothrow @safe @nogc unittest +{ + int i; + emplaceRef(i); + emplaceRef!int(i); + emplaceRef(i, 5); + emplaceRef!int(i, 5); +} + +// Test attribute propagation for UDTs +pure nothrow @safe /* @nogc */ unittest +{ + static struct Safe + { + this(this) pure nothrow @safe @nogc {} + } + + Safe safe = void; + emplaceRef(safe, Safe()); + + Safe[1] safeArr = [Safe()]; + Safe[1] uninitializedSafeArr = void; + emplaceRef(uninitializedSafeArr, safe); + emplaceRef(uninitializedSafeArr, safeArr); + + static struct Unsafe + { + this(this) @system {} + } + + Unsafe unsafe = void; + static assert(!__traits(compiles, emplaceRef(unsafe, Unsafe()))); + + Unsafe[1] unsafeArr = [Unsafe()]; + Unsafe[1] uninitializedUnsafeArr = void; + static assert(!__traits(compiles, emplaceRef(uninitializedUnsafeArr, unsafe))); + static assert(!__traits(compiles, emplaceRef(uninitializedUnsafeArr, unsafeArr))); +} + +@system unittest +{ + // Issue 15313 + static struct Node + { + int payload; + Node* next; + uint refs; + } + + import core.stdc.stdlib : malloc; + void[] buf = malloc(Node.sizeof)[0 .. Node.sizeof]; + + import std.conv : emplace; + const Node* n = emplace!(const Node)(buf, 42, null, 10); + assert(n.payload == 42); + assert(n.next == null); + assert(n.refs == 10); +} + +@system unittest +{ + int var = 6; + auto k = emplace!__conv_EmplaceTest(new void[__conv_EmplaceTest.sizeof], 5, var); + assert(k.i == 5); + assert(var == 7); +} + +@system unittest +{ + class A + { + int x = 5; + int y = 42; + this(int z) + { + assert(x == 5 && y == 42); + x = y = z; + } + } + void[] buf; + + static align(A.alignof) byte[__traits(classInstanceSize, A)] sbuf; + buf = sbuf[]; + auto a = emplace!A(buf, 55); + assert(a.x == 55 && a.y == 55); + + // emplace in bigger buffer + buf = new byte[](__traits(classInstanceSize, A) + 10); + a = emplace!A(buf, 55); + assert(a.x == 55 && a.y == 55); + + // need ctor args + static assert(!is(typeof(emplace!A(buf)))); +} +// Bulk of emplace unittests ends here + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map; + // Check fix for http://d.puremagic.com/issues/show_bug.cgi?id=2971 + assert(equal(map!(to!int)(["42", "34", "345"]), [42, 34, 345])); +} + +// Undocumented for the time being +void toTextRange(T, W)(T value, W writer) +if (isIntegral!T && isOutputRange!(W, char)) +{ + import core.internal.string : SignedStringBuf, signedToTempString, + UnsignedStringBuf, unsignedToTempString; + + if (value < 0) + { + SignedStringBuf buf = void; + put(writer, signedToTempString(value, buf, 10)); + } + else + { + UnsignedStringBuf buf = void; + put(writer, unsignedToTempString(value, buf, 10)); + } +} + +@safe unittest +{ + import std.array : appender; + auto result = appender!(char[])(); + toTextRange(-1, result); + assert(result.data == "-1"); +} + + +/** + Returns the corresponding _unsigned value for $(D x) (e.g. if $(D x) has type + $(D int), it returns $(D cast(uint) x)). The advantage compared to the cast + is that you do not need to rewrite the cast if $(D x) later changes type + (e.g from $(D int) to $(D long)). + + Note that the result is always mutable even if the original type was const + or immutable. In order to retain the constness, use $(REF Unsigned, std,traits). + */ +auto unsigned(T)(T x) +if (isIntegral!T) +{ + return cast(Unqual!(Unsigned!T))x; +} + +/// +@safe unittest +{ + import std.traits : Unsigned; + immutable int s = 42; + auto u1 = unsigned(s); //not qualified + static assert(is(typeof(u1) == uint)); + Unsigned!(typeof(s)) u2 = unsigned(s); //same qualification + static assert(is(typeof(u2) == immutable uint)); + immutable u3 = unsigned(s); //explicitly qualified +} + +@safe unittest +{ + foreach (T; AliasSeq!(byte, ubyte)) + { + static assert(is(typeof(unsigned(cast(T) 1)) == ubyte)); + static assert(is(typeof(unsigned(cast(const T) 1)) == ubyte)); + static assert(is(typeof(unsigned(cast(immutable T) 1)) == ubyte)); + } + + foreach (T; AliasSeq!(short, ushort)) + { + static assert(is(typeof(unsigned(cast(T) 1)) == ushort)); + static assert(is(typeof(unsigned(cast(const T) 1)) == ushort)); + static assert(is(typeof(unsigned(cast(immutable T) 1)) == ushort)); + } + + foreach (T; AliasSeq!(int, uint)) + { + static assert(is(typeof(unsigned(cast(T) 1)) == uint)); + static assert(is(typeof(unsigned(cast(const T) 1)) == uint)); + static assert(is(typeof(unsigned(cast(immutable T) 1)) == uint)); + } + + foreach (T; AliasSeq!(long, ulong)) + { + static assert(is(typeof(unsigned(cast(T) 1)) == ulong)); + static assert(is(typeof(unsigned(cast(const T) 1)) == ulong)); + static assert(is(typeof(unsigned(cast(immutable T) 1)) == ulong)); + } +} + +auto unsigned(T)(T x) +if (isSomeChar!T) +{ + // All characters are unsigned + static assert(T.min == 0); + return cast(Unqual!T) x; +} + +@safe unittest +{ + foreach (T; AliasSeq!(char, wchar, dchar)) + { + static assert(is(typeof(unsigned(cast(T)'A')) == T)); + static assert(is(typeof(unsigned(cast(const T)'A')) == T)); + static assert(is(typeof(unsigned(cast(immutable T)'A')) == T)); + } +} + + +/** + Returns the corresponding _signed value for $(D x) (e.g. if $(D x) has type + $(D uint), it returns $(D cast(int) x)). The advantage compared to the cast + is that you do not need to rewrite the cast if $(D x) later changes type + (e.g from $(D uint) to $(D ulong)). + + Note that the result is always mutable even if the original type was const + or immutable. In order to retain the constness, use $(REF Signed, std,traits). + */ +auto signed(T)(T x) +if (isIntegral!T) +{ + return cast(Unqual!(Signed!T))x; +} + +/// +@safe unittest +{ + import std.traits : Signed; + + immutable uint u = 42; + auto s1 = signed(u); //not qualified + static assert(is(typeof(s1) == int)); + Signed!(typeof(u)) s2 = signed(u); //same qualification + static assert(is(typeof(s2) == immutable int)); + immutable s3 = signed(u); //explicitly qualified +} + +@system unittest +{ + foreach (T; AliasSeq!(byte, ubyte)) + { + static assert(is(typeof(signed(cast(T) 1)) == byte)); + static assert(is(typeof(signed(cast(const T) 1)) == byte)); + static assert(is(typeof(signed(cast(immutable T) 1)) == byte)); + } + + foreach (T; AliasSeq!(short, ushort)) + { + static assert(is(typeof(signed(cast(T) 1)) == short)); + static assert(is(typeof(signed(cast(const T) 1)) == short)); + static assert(is(typeof(signed(cast(immutable T) 1)) == short)); + } + + foreach (T; AliasSeq!(int, uint)) + { + static assert(is(typeof(signed(cast(T) 1)) == int)); + static assert(is(typeof(signed(cast(const T) 1)) == int)); + static assert(is(typeof(signed(cast(immutable T) 1)) == int)); + } + + foreach (T; AliasSeq!(long, ulong)) + { + static assert(is(typeof(signed(cast(T) 1)) == long)); + static assert(is(typeof(signed(cast(const T) 1)) == long)); + static assert(is(typeof(signed(cast(immutable T) 1)) == long)); + } +} + +@safe unittest +{ + // issue 10874 + enum Test { a = 0 } + ulong l = 0; + auto t = l.to!Test; +} + +// asOriginalType +/** +Returns the representation of an enumerated value, i.e. the value converted to +the base type of the enumeration. +*/ +OriginalType!E asOriginalType(E)(E value) if (is(E == enum)) +{ + return value; +} + +/// +@safe unittest +{ + enum A { a = 42 } + static assert(is(typeof(A.a.asOriginalType) == int)); + assert(A.a.asOriginalType == 42); + enum B : double { a = 43 } + static assert(is(typeof(B.a.asOriginalType) == double)); + assert(B.a.asOriginalType == 43); +} + +/** + A wrapper on top of the built-in cast operator that allows one to restrict + casting of the original type of the value. + + A common issue with using a raw cast is that it may silently continue to + compile even if the value's type has changed during refactoring, + which breaks the initial assumption about the cast. + + Params: + From = The type to cast from. The programmer must ensure it is legal + to make this cast. + */ +template castFrom(From) +{ + /** + Params: + To = The type _to cast _to. + value = The value _to cast. It must be of type $(D From), + otherwise a compile-time error is emitted. + + Returns: + the value after the cast, returned by reference if possible. + */ + auto ref to(To, T)(auto ref T value) @system + { + static assert( + is(From == T), + "the value to cast is not of specified type '" ~ From.stringof ~ + "', it is of type '" ~ T.stringof ~ "'" + ); + + static assert( + is(typeof(cast(To) value)), + "can't cast from '" ~ From.stringof ~ "' to '" ~ To.stringof ~ "'" + ); + + return cast(To) value; + } +} + +/// +@system unittest +{ + // Regular cast, which has been verified to be legal by the programmer: + { + long x; + auto y = cast(int) x; + } + + // However this will still compile if 'x' is changed to be a pointer: + { + long* x; + auto y = cast(int) x; + } + + // castFrom provides a more reliable alternative to casting: + { + long x; + auto y = castFrom!long.to!int(x); + } + + // Changing the type of 'x' will now issue a compiler error, + // allowing bad casts to be caught before it's too late: + { + long* x; + static assert( + !__traits(compiles, castFrom!long.to!int(x)) + ); + + // if cast is still needed, must be changed to: + auto y = castFrom!(long*).to!int(x); + } +} + +// https://issues.dlang.org/show_bug.cgi?id=16667 +@system unittest +{ + ubyte[] a = ['a', 'b', 'c']; + assert(castFrom!(ubyte[]).to!(string)(a) == "abc"); +} + +/** +Check the correctness of a string for $(D hexString). +The result is true if and only if the input string is composed of whitespace +characters (\f\n\r\t\v lineSep paraSep nelSep) and +an even number of hexadecimal digits (regardless of the case). +*/ +@safe pure @nogc +private bool isHexLiteral(String)(scope const String hexData) +{ + import std.ascii : isHexDigit; + import std.uni : lineSep, paraSep, nelSep; + size_t i; + foreach (const dchar c; hexData) + { + switch (c) + { + case ' ': + case '\t': + case '\v': + case '\f': + case '\r': + case '\n': + case lineSep: + case paraSep: + case nelSep: + continue; + + default: + break; + } + if (c.isHexDigit) + ++i; + else + return false; + } + return !(i & 1); +} + +@safe unittest +{ + // test all the hex digits + static assert( ("0123456789abcdefABCDEF").isHexLiteral); + // empty or white strings are not valid + static assert( "\r\n\t".isHexLiteral); + // but are accepted if the count of hex digits is even + static assert( "A\r\n\tB".isHexLiteral); +} + +@safe unittest +{ + import std.ascii; + // empty/whites + static assert( "".isHexLiteral); + static assert( " \r".isHexLiteral); + static assert( whitespace.isHexLiteral); + static assert( ""w.isHexLiteral); + static assert( " \r"w.isHexLiteral); + static assert( ""d.isHexLiteral); + static assert( " \r"d.isHexLiteral); + static assert( "\u2028\u2029\u0085"d.isHexLiteral); + // odd x strings + static assert( !("5" ~ whitespace).isHexLiteral); + static assert( !"123".isHexLiteral); + static assert( !"1A3".isHexLiteral); + static assert( !"1 23".isHexLiteral); + static assert( !"\r\n\tC".isHexLiteral); + static assert( !"123"w.isHexLiteral); + static assert( !"1A3"w.isHexLiteral); + static assert( !"1 23"w.isHexLiteral); + static assert( !"\r\n\tC"w.isHexLiteral); + static assert( !"123"d.isHexLiteral); + static assert( !"1A3"d.isHexLiteral); + static assert( !"1 23"d.isHexLiteral); + static assert( !"\r\n\tC"d.isHexLiteral); + // even x strings with invalid charset + static assert( !"12gG".isHexLiteral); + static assert( !"2A 3q".isHexLiteral); + static assert( !"12gG"w.isHexLiteral); + static assert( !"2A 3q"w.isHexLiteral); + static assert( !"12gG"d.isHexLiteral); + static assert( !"2A 3q"d.isHexLiteral); + // valid x strings + static assert( ("5A" ~ whitespace).isHexLiteral); + static assert( ("5A 01A C FF de 1b").isHexLiteral); + static assert( ("0123456789abcdefABCDEF").isHexLiteral); + static assert( (" 012 34 5 6789 abcd ef\rAB\nCDEF").isHexLiteral); + static assert( ("5A 01A C FF de 1b"w).isHexLiteral); + static assert( ("0123456789abcdefABCDEF"w).isHexLiteral); + static assert( (" 012 34 5 6789 abcd ef\rAB\nCDEF"w).isHexLiteral); + static assert( ("5A 01A C FF de 1b"d).isHexLiteral); + static assert( ("0123456789abcdefABCDEF"d).isHexLiteral); + static assert( (" 012 34 5 6789 abcd ef\rAB\nCDEF"d).isHexLiteral); + // library version allows what's pointed by issue 10454 + static assert( ("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").isHexLiteral); +} + +/** +Converts a hex literal to a string at compile time. + +Takes a string made of hexadecimal digits and returns +the matching string by converting each pair of digits to a character. +The input string can also include white characters, which can be used +to keep the literal string readable in the source code. + +The function is intended to replace the hexadecimal literal strings +starting with $(D 'x'), which could be removed to simplify the core language. + +Params: + hexData = string to be converted. + +Returns: + a $(D string), a $(D wstring) or a $(D dstring), according to the type of hexData. + */ +template hexString(string hexData) +if (hexData.isHexLiteral) +{ + immutable hexString = hexStrImpl(hexData); +} + +/// ditto +template hexString(wstring hexData) +if (hexData.isHexLiteral) +{ + immutable hexString = hexStrImpl(hexData); +} + +/// ditto +template hexString(dstring hexData) +if (hexData.isHexLiteral) +{ + immutable hexString = hexStrImpl(hexData); +} + +/// +@safe unittest +{ + // conversion at compile time + auto string1 = hexString!"304A314B"; + assert(string1 == "0J1K"); + auto string2 = hexString!"304A314B"w; + assert(string2 == "0J1K"w); + auto string3 = hexString!"304A314B"d; + assert(string3 == "0J1K"d); +} + +/* + Takes a hexadecimal string literal and returns its representation. + hexData is granted to be a valid string by the caller. + C is granted to be a valid char type by the caller. +*/ +@safe nothrow pure +private auto hexStrImpl(String)(scope String hexData) +{ + import std.ascii : isHexDigit; + alias C = Unqual!(ElementEncodingType!String); + C[] result; + result.length = hexData.length / 2; + size_t cnt; + ubyte v; + foreach (c; hexData) + { + if (c.isHexDigit) + { + ubyte x; + if (c >= '0' && c <= '9') + x = cast(ubyte)(c - '0'); + else if (c >= 'a' && c <= 'f') + x = cast(ubyte)(c - ('a' - 10)); + else if (c >= 'A' && c <= 'F') + x = cast(ubyte)(c - ('A' - 10)); + if (cnt & 1) + { + v = cast(ubyte)((v << 4) | x); + result[cnt / 2] = v; + } + else + v = x; + ++cnt; + } + } + result.length = cnt / 2; + return result; +} + +@safe unittest +{ + // compile time + assert(hexString!"46 47 48 49 4A 4B" == "FGHIJK"); + assert(hexString!"30\r\n\t\f\v31 32 33 32 31 30" == "0123210"); + assert(hexString!"ab cd" == hexString!"ABCD"); +} + + +/** + * Convert integer to a range of characters. + * Intended to be lightweight and fast. + * + * Params: + * radix = 2, 8, 10, 16 + * Char = character type for output + * letterCase = lower for deadbeef, upper for DEADBEEF + * value = integer to convert. Can be uint or ulong. If radix is 10, can also be + * int or long. + * Returns: + * Random access range with slicing and everything + */ + +auto toChars(ubyte radix = 10, Char = char, LetterCase letterCase = LetterCase.lower, T)(T value) + pure nothrow @nogc @safe +if ((radix == 2 || radix == 8 || radix == 10 || radix == 16) && + (is(Unqual!T == uint) || is(Unqual!T == ulong) || + radix == 10 && (is(Unqual!T == int) || is(Unqual!T == long)))) +{ + alias UT = Unqual!T; + + static if (radix == 10) + { + /* uint.max is 42_9496_7295 + * int.max is 21_4748_3647 + * ulong.max is 1844_6744_0737_0955_1615 + * long.max is 922_3372_0368_5477_5807 + */ + static struct Result + { + void initialize(UT value) + { + bool neg = false; + if (value < 10) + { + if (value >= 0) + { + lwr = 0; + upr = 1; + buf[0] = cast(char)(cast(uint) value + '0'); + return; + } + value = -value; + neg = true; + } + auto i = cast(uint) buf.length - 1; + while (cast(Unsigned!UT) value >= 10) + { + buf[i] = cast(ubyte)('0' + cast(Unsigned!UT) value % 10); + value = unsigned(value) / 10; + --i; + } + buf[i] = cast(char)(cast(uint) value + '0'); + if (neg) + { + buf[i - 1] = '-'; + --i; + } + lwr = i; + upr = cast(uint) buf.length; + } + + @property size_t length() { return upr - lwr; } + + alias opDollar = length; + + @property bool empty() { return upr == lwr; } + + @property Char front() { return buf[lwr]; } + + void popFront() { ++lwr; } + + @property Char back() { return buf[upr - 1]; } + + void popBack() { --upr; } + + @property Result save() { return this; } + + Char opIndex(size_t i) { return buf[lwr + i]; } + + Result opSlice(size_t lwr, size_t upr) + { + Result result = void; + result.buf = buf; + result.lwr = cast(uint)(this.lwr + lwr); + result.upr = cast(uint)(this.lwr + upr); + return result; + } + + private: + uint lwr = void, upr = void; + char[(UT.sizeof == 4) ? 10 + isSigned!T : 20] buf = void; + } + + Result result = void; + result.initialize(value); + return result; + } + else + { + static if (radix == 2) + enum SHIFT = 1; + else static if (radix == 8) + enum SHIFT = 3; + else static if (radix == 16) + enum SHIFT = 4; + else + static assert(0); + static struct Result + { + this(UT value) + { + this.value = value; + + ubyte len = 1; + while (value >>>= SHIFT) + ++len; + this.len = len; + } + + @property size_t length() { return len; } + + @property bool empty() { return len == 0; } + + @property Char front() { return opIndex(0); } + + void popFront() { --len; } + + @property Char back() { return opIndex(len - 1); } + + void popBack() + { + value >>>= SHIFT; + --len; + } + + @property Result save() { return this; } + + Char opIndex(size_t i) + { + Char c = (value >>> ((len - i - 1) * SHIFT)) & ((1 << SHIFT) - 1); + return cast(Char)((radix < 10 || c < 10) ? c + '0' + : (letterCase == LetterCase.upper ? c + 'A' - 10 + : c + 'a' - 10)); + } + + Result opSlice(size_t lwr, size_t upr) + { + Result result = void; + result.value = value >>> ((len - upr) * SHIFT); + result.len = cast(ubyte)(upr - lwr); + return result; + } + + private: + UT value; + ubyte len; + } + + return Result(value); + } +} + + +@safe unittest +{ + import std.array; + import std.range; + + { + assert(toChars!2(0u).array == "0"); + assert(toChars!2(0Lu).array == "0"); + assert(toChars!2(1u).array == "1"); + assert(toChars!2(1Lu).array == "1"); + + auto r = toChars!2(2u); + assert(r.length == 2); + assert(r[0] == '1'); + assert(r[1 .. 2].array == "0"); + auto s = r.save; + assert(r.array == "10"); + assert(s.retro.array == "01"); + } + { + assert(toChars!8(0u).array == "0"); + assert(toChars!8(0Lu).array == "0"); + assert(toChars!8(1u).array == "1"); + assert(toChars!8(1234567Lu).array == "4553207"); + + auto r = toChars!8(8u); + assert(r.length == 2); + assert(r[0] == '1'); + assert(r[1 .. 2].array == "0"); + auto s = r.save; + assert(r.array == "10"); + assert(s.retro.array == "01"); + } + { + assert(toChars!10(0u).array == "0"); + assert(toChars!10(0Lu).array == "0"); + assert(toChars!10(1u).array == "1"); + assert(toChars!10(1234567Lu).array == "1234567"); + assert(toChars!10(uint.max).array == "4294967295"); + assert(toChars!10(ulong.max).array == "18446744073709551615"); + + auto r = toChars(10u); + assert(r.length == 2); + assert(r[0] == '1'); + assert(r[1 .. 2].array == "0"); + auto s = r.save; + assert(r.array == "10"); + assert(s.retro.array == "01"); + } + { + assert(toChars!10(0).array == "0"); + assert(toChars!10(0L).array == "0"); + assert(toChars!10(1).array == "1"); + assert(toChars!10(1234567L).array == "1234567"); + assert(toChars!10(int.max).array == "2147483647"); + assert(toChars!10(long.max).array == "9223372036854775807"); + assert(toChars!10(-int.max).array == "-2147483647"); + assert(toChars!10(-long.max).array == "-9223372036854775807"); + assert(toChars!10(int.min).array == "-2147483648"); + assert(toChars!10(long.min).array == "-9223372036854775808"); + + auto r = toChars!10(10); + assert(r.length == 2); + assert(r[0] == '1'); + assert(r[1 .. 2].array == "0"); + auto s = r.save; + assert(r.array == "10"); + assert(s.retro.array == "01"); + } + { + assert(toChars!(16)(0u).array == "0"); + assert(toChars!(16)(0Lu).array == "0"); + assert(toChars!(16)(10u).array == "a"); + assert(toChars!(16, char, LetterCase.upper)(0x12AF34567Lu).array == "12AF34567"); + + auto r = toChars!(16)(16u); + assert(r.length == 2); + assert(r[0] == '1'); + assert(r[1 .. 2].array == "0"); + auto s = r.save; + assert(r.array == "10"); + assert(s.retro.array == "01"); + } +} + +@safe unittest // opSlice (issue 16192) +{ + import std.meta : AliasSeq; + + static struct Test { ubyte radix; uint number; } + + alias tests = AliasSeq!( + Test(2, 0b1_0110_0111u), + Test(2, 0b10_1100_1110u), + Test(8, octal!123456701u), + Test(8, octal!1234567012u), + Test(10, 123456789u), + Test(10, 1234567890u), + Test(16, 0x789ABCDu), + Test(16, 0x789ABCDEu), + ); + + foreach (test; tests) + { + enum ubyte radix = test.radix; + auto original = toChars!radix(test.number); + + // opSlice vs popFront + auto r = original.save; + size_t i = 0; + for (; !r.empty; r.popFront(), ++i) + { + assert(original[i .. original.length].tupleof == r.tupleof); + // tupleof is used to work around issue 16216. + } + + // opSlice vs popBack + r = original.save; + i = 0; + for (; !r.empty; r.popBack(), ++i) + { + assert(original[0 .. original.length - i].tupleof == r.tupleof); + } + + // opSlice vs both popFront and popBack + r = original.save; + i = 0; + for (; r.length >= 2; r.popFront(), r.popBack(), ++i) + { + assert(original[i .. original.length - i].tupleof == r.tupleof); + } + } +} diff --git a/libphobos/src/std/csv.d b/libphobos/src/std/csv.d new file mode 100644 index 0000000..b10f1e6 --- /dev/null +++ b/libphobos/src/std/csv.d @@ -0,0 +1,1701 @@ +//Written in the D programming language + +/** + * Implements functionality to read Comma Separated Values and its variants + * from an input range of $(D dchar). + * + * Comma Separated Values provide a simple means to transfer and store + * tabular data. It has been common for programs to use their own + * variant of the CSV format. This parser will loosely follow the + * $(HTTP tools.ietf.org/html/rfc4180, RFC-4180). CSV input should adhere + * to the following criteria (differences from RFC-4180 in parentheses): + * + * $(UL + * $(LI A record is separated by a new line (CRLF,LF,CR)) + * $(LI A final record may end with a new line) + * $(LI A header may be provided as the first record in input) + * $(LI A record has fields separated by a comma (customizable)) + * $(LI A field containing new lines, commas, or double quotes + * should be enclosed in double quotes (customizable)) + * $(LI Double quotes in a field are escaped with a double quote) + * $(LI Each record should contain the same number of fields) + * ) + * + * Example: + * + * ------- + * import std.algorithm; + * import std.array; + * import std.csv; + * import std.stdio; + * import std.typecons; + * + * void main() + * { + * auto text = "Joe,Carpenter,300000\nFred,Blacksmith,400000\r\n"; + * + * foreach (record; csvReader!(Tuple!(string, string, int))(text)) + * { + * writefln("%s works as a %s and earns $%d per year", + * record[0], record[1], record[2]); + * } + * + * // To read the same string from the file "filename.csv": + * + * auto file = File("filename.csv", "r"); + * foreach (record; + * file.byLine.joiner("\n").csvReader!(Tuple!(string, string, int))) + * { + * writefln("%s works as a %s and earns $%d per year", + * record[0], record[1], record[2]); + * } + } + * } + * ------- + * + * When an input contains a header the $(D Contents) can be specified as an + * associative array. Passing null to signify that a header is present. + * + * ------- + * auto text = "Name,Occupation,Salary\r" + * "Joe,Carpenter,300000\nFred,Blacksmith,400000\r\n"; + * + * foreach (record; csvReader!(string[string]) + * (text, null)) + * { + * writefln("%s works as a %s and earns $%s per year.", + * record["Name"], record["Occupation"], + * record["Salary"]); + * } + * ------- + * + * This module allows content to be iterated by record stored in a struct, + * class, associative array, or as a range of fields. Upon detection of an + * error an CSVException is thrown (can be disabled). csvNextToken has been + * made public to allow for attempted recovery. + * + * Disabling exceptions will lift many restrictions specified above. A quote + * can appear in a field if the field was not quoted. If in a quoted field any + * quote by itself, not at the end of a field, will end processing for that + * field. The field is ended when there is no input, even if the quote was not + * closed. + * + * See_Also: + * $(HTTP en.wikipedia.org/wiki/Comma-separated_values, Wikipedia + * Comma-separated values) + * + * Copyright: Copyright 2011 + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Jesse Phillips + * Source: $(PHOBOSSRC std/_csv.d) + */ +module std.csv; + +import std.conv; +import std.exception; // basicExceptionCtors +import std.range.primitives; +import std.traits; + +/** + * Exception containing the row and column for when an exception was thrown. + * + * Numbering of both row and col start at one and corresponds to the location + * in the file rather than any specified header. Special consideration should + * be made when there is failure to match the header see $(LREF + * HeaderMismatchException) for details. + * + * When performing type conversions, $(REF ConvException, std,conv) is stored in + * the $(D next) field. + */ +class CSVException : Exception +{ + /// + size_t row, col; + + // FIXME: Use std.exception.basicExceptionCtors here once bug #11500 is fixed + + this(string msg, string file = __FILE__, size_t line = __LINE__, + Throwable next = null) @nogc @safe pure nothrow + { + super(msg, file, line, next); + } + + this(string msg, Throwable next, string file = __FILE__, + size_t line = __LINE__) @nogc @safe pure nothrow + { + super(msg, file, line, next); + } + + this(string msg, size_t row, size_t col, Throwable next = null, + string file = __FILE__, size_t line = __LINE__) @nogc @safe pure nothrow + { + super(msg, next, file, line); + this.row = row; + this.col = col; + } + + override string toString() @safe pure const + { + return "(Row: " ~ to!string(row) ~ + ", Col: " ~ to!string(col) ~ ") " ~ msg; + } +} + +@safe pure unittest +{ + import std.string; + auto e1 = new Exception("Foobar"); + auto e2 = new CSVException("args", e1); + assert(e2.next is e1); + + size_t r = 13; + size_t c = 37; + + auto e3 = new CSVException("argv", r, c); + assert(e3.row == r); + assert(e3.col == c); + + auto em = e3.toString(); + assert(em.indexOf("13") != -1); + assert(em.indexOf("37") != -1); +} + +/** + * Exception thrown when a Token is identified to not be completed: a quote is + * found in an unquoted field, data continues after a closing quote, or the + * quoted field was not closed before data was empty. + */ +class IncompleteCellException : CSVException +{ + /** + * Data pulled from input before finding a problem + * + * This field is populated when using $(LREF csvReader) + * but not by $(LREF csvNextToken) as this data will have + * already been fed to the output range. + */ + dstring partialData; + + mixin basicExceptionCtors; +} + +@safe pure unittest +{ + auto e1 = new Exception("Foobar"); + auto e2 = new IncompleteCellException("args", e1); + assert(e2.next is e1); +} + +/** + * Exception thrown under different conditions based on the type of $(D + * Contents). + * + * Structure, Class, and Associative Array + * $(UL + * $(LI When a header is provided but a matching column is not found) + * ) + * + * Other + * $(UL + * $(LI When a header is provided but a matching column is not found) + * $(LI Order did not match that found in the input) + * ) + * + * Since a row and column is not meaningful when a column specified by the + * header is not found in the data, both row and col will be zero. Otherwise + * row is always one and col is the first instance found in header that + * occurred before the previous starting at one. + */ +class HeaderMismatchException : CSVException +{ + mixin basicExceptionCtors; +} + +@safe pure unittest +{ + auto e1 = new Exception("Foobar"); + auto e2 = new HeaderMismatchException("args", e1); + assert(e2.next is e1); +} + +/** + * Determines the behavior for when an error is detected. + * + * Disabling exception will follow these rules: + * $(UL + * $(LI A quote can appear in a field if the field was not quoted.) + * $(LI If in a quoted field any quote by itself, not at the end of a + * field, will end processing for that field.) + * $(LI The field is ended when there is no input, even if the quote was + * not closed.) + * $(LI If the given header does not match the order in the input, the + * content will return as it is found in the input.) + * $(LI If the given header contains columns not found in the input they + * will be ignored.) + * ) +*/ +enum Malformed +{ + ignore, /// No exceptions are thrown due to incorrect CSV. + throwException /// Use exceptions when input has incorrect CSV. +} + +/** + * Returns an input range for iterating over records found in $(D + * input). + * + * The $(D Contents) of the input can be provided if all the records are the + * same type such as all integer data: + * + * ------- + * string str = `76,26,22`; + * int[] ans = [76,26,22]; + * auto records = csvReader!int(str); + * + * foreach (record; records) + * { + * assert(equal(record, ans)); + * } + * ------- + * + * Example using a struct with modified delimiter: + * + * ------- + * string str = "Hello;65;63.63\nWorld;123;3673.562"; + * struct Layout + * { + * string name; + * int value; + * double other; + * } + * + * auto records = csvReader!Layout(str,';'); + * + * foreach (record; records) + * { + * writeln(record.name); + * writeln(record.value); + * writeln(record.other); + * } + * ------- + * + * Specifying $(D ErrorLevel) as Malformed.ignore will lift restrictions + * on the format. This example shows that an exception is not thrown when + * finding a quote in a field not quoted. + * + * ------- + * string str = "A \" is now part of the data"; + * auto records = csvReader!(string,Malformed.ignore)(str); + * auto record = records.front; + * + * assert(record.front == str); + * ------- + * + * Returns: + * An input range R as defined by + * $(REF isInputRange, std,range,primitives). When $(D Contents) is a + * struct, class, or an associative array, the element type of R is + * $(D Contents), otherwise the element type of R is itself a range with + * element type $(D Contents). + * + * Throws: + * $(LREF CSVException) When a quote is found in an unquoted field, + * data continues after a closing quote, the quoted field was not + * closed before data was empty, a conversion failed, or when the row's + * length does not match the previous length. + * + * $(LREF HeaderMismatchException) when a header is provided but a + * matching column is not found or the order did not match that found in + * the input. Read the exception documentation for specific details of + * when the exception is thrown for different types of $(D Contents). + */ +auto csvReader(Contents = string,Malformed ErrorLevel = Malformed.throwException, Range, Separator = char)(Range input, + Separator delimiter = ',', Separator quote = '"') +if (isInputRange!Range && is(Unqual!(ElementType!Range) == dchar) + && isSomeChar!(Separator) + && !is(Contents T : T[U], U : string)) +{ + return CsvReader!(Contents,ErrorLevel,Range, + Unqual!(ElementType!Range),string[]) + (input, delimiter, quote); +} + +/** + * An optional $(D header) can be provided. The first record will be read in + * as the header. If $(D Contents) is a struct then the header provided is + * expected to correspond to the fields in the struct. When $(D Contents) is + * not a type which can contain the entire record, the $(D header) must be + * provided in the same order as the input or an exception is thrown. + * + * Read only column "b": + * + * ------- + * string str = "a,b,c\nHello,65,63.63\nWorld,123,3673.562"; + * auto records = csvReader!int(str, ["b"]); + * + * auto ans = [[65],[123]]; + * foreach (record; records) + * { + * assert(equal(record, ans.front)); + * ans.popFront(); + * } + * ------- + * + * Read from header of different order: + * + * ------- + * string str = "a,b,c\nHello,65,63.63\nWorld,123,3673.562"; + * struct Layout + * { + * int value; + * double other; + * string name; + * } + * + * auto records = csvReader!Layout(str, ["b","c","a"]); + * ------- + * + * The header can also be left empty if the input contains a header but + * all columns should be iterated. The header from the input can always + * be accessed from the header field. + * + * ------- + * string str = "a,b,c\nHello,65,63.63\nWorld,123,3673.562"; + * auto records = csvReader(str, null); + * + * assert(records.header == ["a","b","c"]); + * ------- + * + * Returns: + * An input range R as defined by + * $(REF isInputRange, std,range,primitives). When $(D Contents) is a + * struct, class, or an associative array, the element type of R is + * $(D Contents), otherwise the element type of R is itself a range with + * element type $(D Contents). + * + * The returned range provides a header field for accessing the header + * from the input in array form. + * + * ------- + * string str = "a,b,c\nHello,65,63.63"; + * auto records = csvReader(str, ["a"]); + * + * assert(records.header == ["a","b","c"]); + * ------- + * + * Throws: + * $(LREF CSVException) When a quote is found in an unquoted field, + * data continues after a closing quote, the quoted field was not + * closed before data was empty, a conversion failed, or when the row's + * length does not match the previous length. + * + * $(LREF HeaderMismatchException) when a header is provided but a + * matching column is not found or the order did not match that found in + * the input. Read the exception documentation for specific details of + * when the exception is thrown for different types of $(D Contents). + */ +auto csvReader(Contents = string, + Malformed ErrorLevel = Malformed.throwException, + Range, Header, Separator = char) + (Range input, Header header, + Separator delimiter = ',', Separator quote = '"') +if (isInputRange!Range && is(Unqual!(ElementType!Range) == dchar) + && isSomeChar!(Separator) + && isForwardRange!Header + && isSomeString!(ElementType!Header)) +{ + return CsvReader!(Contents,ErrorLevel,Range, + Unqual!(ElementType!Range),Header) + (input, header, delimiter, quote); +} + +/// +auto csvReader(Contents = string, + Malformed ErrorLevel = Malformed.throwException, + Range, Header, Separator = char) + (Range input, Header header, + Separator delimiter = ',', Separator quote = '"') +if (isInputRange!Range && is(Unqual!(ElementType!Range) == dchar) + && isSomeChar!(Separator) + && is(Header : typeof(null))) +{ + return CsvReader!(Contents,ErrorLevel,Range, + Unqual!(ElementType!Range),string[]) + (input, cast(string[]) null, delimiter, quote); +} + +// Test standard iteration over input. +@safe pure unittest +{ + string str = `one,"two ""quoted"""` ~ "\n\"three\nnew line\",\nfive,six"; + auto records = csvReader(str); + + int count; + foreach (record; records) + { + foreach (cell; record) + { + count++; + } + } + assert(count == 6); +} + +// Test newline on last record +@safe pure unittest +{ + string str = "one,two\nthree,four\n"; + auto records = csvReader(str); + records.popFront(); + records.popFront(); + assert(records.empty); +} + +// Test shorter row length +@safe pure unittest +{ + wstring str = "one,1\ntwo\nthree"w; + struct Layout + { + string name; + int value; + } + + Layout[3] ans; + ans[0].name = "one"; + ans[0].value = 1; + ans[1].name = "two"; + ans[1].value = 0; + ans[2].name = "three"; + ans[2].value = 0; + + auto records = csvReader!(Layout,Malformed.ignore)(str); + + int count; + foreach (record; records) + { + assert(ans[count].name == record.name); + assert(ans[count].value == record.value); + count++; + } +} + +// Test shorter row length exception +@safe pure unittest +{ + import std.exception; + + struct A + { + string a,b,c; + } + + auto strs = ["one,1\ntwo", + "one\ntwo,2,二\nthree,3,三", + "one\ntwo,2\nthree,3", + "one,1\ntwo\nthree,3"]; + + foreach (str; strs) + { + auto records = csvReader!A(str); + assertThrown!CSVException((){foreach (record; records) { }}()); + } +} + + +// Test structure conversion interface with unicode. +@safe pure unittest +{ + import std.math : abs; + + wstring str = "\U00010143Hello,65,63.63\nWorld,123,3673.562"w; + struct Layout + { + string name; + int value; + double other; + } + + Layout[2] ans; + ans[0].name = "\U00010143Hello"; + ans[0].value = 65; + ans[0].other = 63.63; + ans[1].name = "World"; + ans[1].value = 123; + ans[1].other = 3673.562; + + auto records = csvReader!Layout(str); + + int count; + foreach (record; records) + { + assert(ans[count].name == record.name); + assert(ans[count].value == record.value); + assert(abs(ans[count].other - record.other) < 0.00001); + count++; + } + assert(count == ans.length); +} + +// Test input conversion interface +@safe pure unittest +{ + import std.algorithm; + string str = `76,26,22`; + int[] ans = [76,26,22]; + auto records = csvReader!int(str); + + foreach (record; records) + { + assert(equal(record, ans)); + } +} + +// Test struct & header interface and same unicode +@safe unittest +{ + import std.math : abs; + + string str = "a,b,c\nHello,65,63.63\n➊➋➂❹,123,3673.562"; + struct Layout + { + int value; + double other; + string name; + } + + auto records = csvReader!Layout(str, ["b","c","a"]); + + Layout[2] ans; + ans[0].name = "Hello"; + ans[0].value = 65; + ans[0].other = 63.63; + ans[1].name = "➊➋➂❹"; + ans[1].value = 123; + ans[1].other = 3673.562; + + int count; + foreach (record; records) + { + assert(ans[count].name == record.name); + assert(ans[count].value == record.value); + assert(abs(ans[count].other - record.other) < 0.00001); + count++; + } + assert(count == ans.length); + +} + +// Test header interface +@safe unittest +{ + import std.algorithm; + + string str = "a,b,c\nHello,65,63.63\nWorld,123,3673.562"; + auto records = csvReader!int(str, ["b"]); + + auto ans = [[65],[123]]; + foreach (record; records) + { + assert(equal(record, ans.front)); + ans.popFront(); + } + + try + { + csvReader(str, ["c","b"]); + assert(0); + } + catch (HeaderMismatchException e) + { + assert(e.col == 2); + } + auto records2 = csvReader!(string,Malformed.ignore) + (str, ["b","a"], ',', '"'); + + auto ans2 = [["Hello","65"],["World","123"]]; + foreach (record; records2) + { + assert(equal(record, ans2.front)); + ans2.popFront(); + } + + str = "a,c,e\nJoe,Carpenter,300000\nFred,Fly,4"; + records2 = csvReader!(string,Malformed.ignore) + (str, ["a","b","c","d"], ',', '"'); + + ans2 = [["Joe","Carpenter"],["Fred","Fly"]]; + foreach (record; records2) + { + assert(equal(record, ans2.front)); + ans2.popFront(); + } +} + +// Test null header interface +@safe unittest +{ + string str = "a,b,c\nHello,65,63.63\nWorld,123,3673.562"; + auto records = csvReader(str, ["a"]); + + assert(records.header == ["a","b","c"]); +} + +// Test unchecked read +@safe pure unittest +{ + string str = "one \"quoted\""; + foreach (record; csvReader!(string,Malformed.ignore)(str)) + { + foreach (cell; record) + { + assert(cell == "one \"quoted\""); + } + } + + str = "one \"quoted\",two \"quoted\" end"; + struct Ans + { + string a,b; + } + foreach (record; csvReader!(Ans,Malformed.ignore)(str)) + { + assert(record.a == "one \"quoted\""); + assert(record.b == "two \"quoted\" end"); + } +} + +// Test partial data returned +@safe pure unittest +{ + string str = "\"one\nnew line"; + + try + { + foreach (record; csvReader(str)) + {} + assert(0); + } + catch (IncompleteCellException ice) + { + assert(ice.partialData == "one\nnew line"); + } +} + +// Test Windows line break +@safe pure unittest +{ + string str = "one,two\r\nthree"; + + auto records = csvReader(str); + auto record = records.front; + assert(record.front == "one"); + record.popFront(); + assert(record.front == "two"); + records.popFront(); + record = records.front; + assert(record.front == "three"); +} + + +// Test associative array support with unicode separator +@safe unittest +{ + string str = "1❁2❁3\n34❁65❁63\n34❁65❁63"; + + auto records = csvReader!(string[string])(str,["3","1"],'❁'); + int count; + foreach (record; records) + { + count++; + assert(record["1"] == "34"); + assert(record["3"] == "63"); + } + assert(count == 2); +} + +// Test restricted range +@safe unittest +{ + import std.typecons; + struct InputRange + { + dstring text; + + this(dstring txt) + { + text = txt; + } + + @property auto empty() + { + return text.empty; + } + + void popFront() + { + text.popFront(); + } + + @property dchar front() + { + return text[0]; + } + } + auto ir = InputRange("Name,Occupation,Salary\r"d~ + "Joe,Carpenter,300000\nFred,Blacksmith,400000\r\n"d); + + foreach (record; csvReader(ir, cast(string[]) null)) + foreach (cell; record) {} + foreach (record; csvReader!(Tuple!(string, string, int)) + (ir,cast(string[]) null)) {} + foreach (record; csvReader!(string[string]) + (ir,cast(string[]) null)) {} +} + +@safe unittest // const/immutable dchars +{ + import std.algorithm.iteration : map; + import std.array : array; + const(dchar)[] c = "foo,bar\n"; + assert(csvReader(c).map!array.array == [["foo", "bar"]]); + immutable(dchar)[] i = "foo,bar\n"; + assert(csvReader(i).map!array.array == [["foo", "bar"]]); +} + +/* + * This struct is stored on the heap for when the structures + * are passed around. + */ +private pure struct Input(Range, Malformed ErrorLevel) +{ + Range range; + size_t row, col; + static if (ErrorLevel == Malformed.throwException) + size_t rowLength; +} + +/* + * Range for iterating CSV records. + * + * This range is returned by the $(LREF csvReader) functions. It can be + * created in a similar manner to allow $(D ErrorLevel) be set to $(LREF + * Malformed).ignore if best guess processing should take place. + */ +private struct CsvReader(Contents, Malformed ErrorLevel, Range, Separator, Header) +if (isSomeChar!Separator && isInputRange!Range + && is(Unqual!(ElementType!Range) == dchar) + && isForwardRange!Header && isSomeString!(ElementType!Header)) +{ +private: + Input!(Range, ErrorLevel)* _input; + Separator _separator; + Separator _quote; + size_t[] indices; + bool _empty; + static if (is(Contents == struct) || is(Contents == class)) + { + Contents recordContent; + CsvRecord!(string, ErrorLevel, Range, Separator) recordRange; + } + else static if (is(Contents T : T[U], U : string)) + { + Contents recordContent; + CsvRecord!(T, ErrorLevel, Range, Separator) recordRange; + } + else + CsvRecord!(Contents, ErrorLevel, Range, Separator) recordRange; +public: + /** + * Header from the input in array form. + * + * ------- + * string str = "a,b,c\nHello,65,63.63"; + * auto records = csvReader(str, ["a"]); + * + * assert(records.header == ["a","b","c"]); + * ------- + */ + string[] header; + + /** + * Constructor to initialize the input, delimiter and quote for input + * without a header. + * + * ------- + * string str = `76;^26^;22`; + * int[] ans = [76,26,22]; + * auto records = CsvReader!(int,Malformed.ignore,string,char,string[]) + * (str, ';', '^'); + * + * foreach (record; records) + * { + * assert(equal(record, ans)); + * } + * ------- + */ + this(Range input, Separator delimiter, Separator quote) + { + _input = new Input!(Range, ErrorLevel)(input); + _separator = delimiter; + _quote = quote; + + prime(); + } + + /** + * Constructor to initialize the input, delimiter and quote for input + * with a header. + * + * ------- + * string str = `high;mean;low\n76;^26^;22`; + * auto records = CsvReader!(int,Malformed.ignore,string,char,string[]) + * (str, ["high","low"], ';', '^'); + * + * int[] ans = [76,22]; + * foreach (record; records) + * { + * assert(equal(record, ans)); + * } + * ------- + * + * Throws: + * $(LREF HeaderMismatchException) when a header is provided but a + * matching column is not found or the order did not match that found + * in the input (non-struct). + */ + this(Range input, Header colHeaders, Separator delimiter, Separator quote) + { + _input = new Input!(Range, ErrorLevel)(input); + _separator = delimiter; + _quote = quote; + + size_t[string] colToIndex; + foreach (h; colHeaders) + { + colToIndex[h] = size_t.max; + } + + auto r = CsvRecord!(string, ErrorLevel, Range, Separator) + (_input, _separator, _quote, indices); + + size_t colIndex; + foreach (col; r) + { + header ~= col; + auto ptr = col in colToIndex; + if (ptr) + *ptr = colIndex; + colIndex++; + } + // The above loop empties the header row. + recordRange._empty = true; + + indices.length = colToIndex.length; + int i; + foreach (h; colHeaders) + { + immutable index = colToIndex[h]; + static if (ErrorLevel != Malformed.ignore) + if (index == size_t.max) + throw new HeaderMismatchException + ("Header not found: " ~ to!string(h)); + indices[i++] = index; + } + + static if (!is(Contents == struct) && !is(Contents == class)) + { + static if (is(Contents T : T[U], U : string)) + { + import std.algorithm.sorting : sort; + sort(indices); + } + else static if (ErrorLevel == Malformed.ignore) + { + import std.algorithm.sorting : sort; + sort(indices); + } + else + { + import std.algorithm.searching : findAdjacent; + import std.algorithm.sorting : isSorted; + if (!isSorted(indices)) + { + auto ex = new HeaderMismatchException + ("Header in input does not match specified header."); + findAdjacent!"a > b"(indices); + ex.row = 1; + ex.col = indices.front; + + throw ex; + } + } + } + + popFront(); + } + + /** + * Part of an input range as defined by + * $(REF isInputRange, std,range,primitives). + * + * Returns: + * If $(D Contents) is a struct, will be filled with record data. + * + * If $(D Contents) is a class, will be filled with record data. + * + * If $(D Contents) is a associative array, will be filled + * with record data. + * + * If $(D Contents) is non-struct, a $(LREF CsvRecord) will be + * returned. + */ + @property auto front() + { + assert(!empty); + static if (is(Contents == struct) || is(Contents == class)) + { + return recordContent; + } + else static if (is(Contents T : T[U], U : string)) + { + return recordContent; + } + else + { + return recordRange; + } + } + + /** + * Part of an input range as defined by + * $(REF isInputRange, std,range,primitives). + */ + @property bool empty() @safe @nogc pure nothrow const + { + return _empty; + } + + /** + * Part of an input range as defined by + * $(REF isInputRange, std,range,primitives). + * + * Throws: + * $(LREF CSVException) When a quote is found in an unquoted field, + * data continues after a closing quote, the quoted field was not + * closed before data was empty, a conversion failed, or when the + * row's length does not match the previous length. + */ + void popFront() + { + while (!recordRange.empty) + { + recordRange.popFront(); + } + + static if (ErrorLevel == Malformed.throwException) + if (_input.rowLength == 0) + _input.rowLength = _input.col; + + _input.col = 0; + + if (!_input.range.empty) + { + if (_input.range.front == '\r') + { + _input.range.popFront(); + if (!_input.range.empty && _input.range.front == '\n') + _input.range.popFront(); + } + else if (_input.range.front == '\n') + _input.range.popFront(); + } + + if (_input.range.empty) + { + _empty = true; + return; + } + + prime(); + } + + private void prime() + { + if (_empty) + return; + _input.row++; + static if (is(Contents == struct) || is(Contents == class)) + { + recordRange = typeof(recordRange) + (_input, _separator, _quote, null); + } + else + { + recordRange = typeof(recordRange) + (_input, _separator, _quote, indices); + } + + static if (is(Contents T : T[U], U : string)) + { + T[U] aa; + try + { + for (; !recordRange.empty; recordRange.popFront()) + { + aa[header[_input.col-1]] = recordRange.front; + } + } + catch (ConvException e) + { + throw new CSVException(e.msg, _input.row, _input.col, e); + } + + recordContent = aa; + } + else static if (is(Contents == struct) || is(Contents == class)) + { + static if (is(Contents == class)) + recordContent = new typeof(recordContent)(); + else + recordContent = typeof(recordContent).init; + size_t colIndex; + try + { + for (; !recordRange.empty;) + { + auto colData = recordRange.front; + scope(exit) colIndex++; + if (indices.length > 0) + { + foreach (ti, ToType; Fields!(Contents)) + { + if (indices[ti] == colIndex) + { + static if (!isSomeString!ToType) skipWS(colData); + recordContent.tupleof[ti] = to!ToType(colData); + } + } + } + else + { + foreach (ti, ToType; Fields!(Contents)) + { + if (ti == colIndex) + { + static if (!isSomeString!ToType) skipWS(colData); + recordContent.tupleof[ti] = to!ToType(colData); + } + } + } + recordRange.popFront(); + } + } + catch (ConvException e) + { + throw new CSVException(e.msg, _input.row, colIndex, e); + } + } + } +} + +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + string str = `76;^26^;22`; + int[] ans = [76,26,22]; + auto records = CsvReader!(int,Malformed.ignore,string,char,string[]) + (str, ';', '^'); + + foreach (record; records) + { + assert(equal(record, ans)); + } +} + +// Bugzilla 15545 +// @system due to the catch for Throwable +@system pure unittest +{ + import std.exception : assertNotThrown; + enum failData = + "name, surname, age + Joe, Joker, 99\r"; + auto r = csvReader(failData); + assertNotThrown((){foreach (entry; r){}}()); +} + +/* + * This input range is accessible through $(LREF CsvReader) when the + * requested $(D Contents) type is neither a structure or an associative array. + */ +private struct CsvRecord(Contents, Malformed ErrorLevel, Range, Separator) +if (!is(Contents == class) && !is(Contents == struct)) +{ + import std.array : appender; +private: + Input!(Range, ErrorLevel)* _input; + Separator _separator; + Separator _quote; + Contents curContentsoken; + typeof(appender!(dchar[])()) _front; + bool _empty; + size_t[] _popCount; +public: + /* + * Params: + * input = Pointer to a character input range + * delimiter = Separator for each column + * quote = Character used for quotation + * indices = An array containing which columns will be returned. + * If empty, all columns are returned. List must be in order. + */ + this(Input!(Range, ErrorLevel)* input, Separator delimiter, + Separator quote, size_t[] indices) + { + _input = input; + _separator = delimiter; + _quote = quote; + _front = appender!(dchar[])(); + _popCount = indices.dup; + + // If a header was given, each call to popFront will need + // to eliminate so many tokens. This calculates + // how many will be skipped to get to the next header column + size_t normalizer; + foreach (ref c; _popCount) + { + static if (ErrorLevel == Malformed.ignore) + { + // If we are not throwing exceptions + // a header may not exist, indices are sorted + // and will be size_t.max if not found. + if (c == size_t.max) + break; + } + c -= normalizer; + normalizer += c + 1; + } + + prime(); + } + + /** + * Part of an input range as defined by + * $(REF isInputRange, std,range,primitives). + */ + @property Contents front() @safe pure + { + assert(!empty); + return curContentsoken; + } + + /** + * Part of an input range as defined by + * $(REF isInputRange, std,range,primitives). + */ + @property bool empty() @safe pure nothrow @nogc const + { + return _empty; + } + + /* + * CsvRecord is complete when input + * is empty or starts with record break + */ + private bool recordEnd() + { + if (_input.range.empty + || _input.range.front == '\n' + || _input.range.front == '\r') + { + return true; + } + return false; + } + + + /** + * Part of an input range as defined by + * $(REF isInputRange, std,range,primitives). + * + * Throws: + * $(LREF CSVException) When a quote is found in an unquoted field, + * data continues after a closing quote, the quoted field was not + * closed before data was empty, a conversion failed, or when the + * row's length does not match the previous length. + */ + void popFront() + { + static if (ErrorLevel == Malformed.throwException) + import std.format : format; + // Skip last of record when header is depleted. + if (_popCount.ptr && _popCount.empty) + while (!recordEnd()) + { + prime(1); + } + + if (recordEnd()) + { + _empty = true; + static if (ErrorLevel == Malformed.throwException) + if (_input.rowLength != 0) + if (_input.col != _input.rowLength) + throw new CSVException( + format("Row %s's length %s does not match "~ + "previous length of %s.", _input.row, + _input.col, _input.rowLength)); + return; + } + else + { + static if (ErrorLevel == Malformed.throwException) + if (_input.rowLength != 0) + if (_input.col > _input.rowLength) + throw new CSVException( + format("Row %s's length %s does not match "~ + "previous length of %s.", _input.row, + _input.col, _input.rowLength)); + } + + // Separator is left on the end of input from the last call. + // This cannot be moved to after the call to csvNextToken as + // there may be an empty record after it. + if (_input.range.front == _separator) + _input.range.popFront(); + + _front.shrinkTo(0); + + prime(); + } + + /* + * Handles moving to the next skipNum token. + */ + private void prime(size_t skipNum) + { + foreach (i; 0 .. skipNum) + { + _input.col++; + _front.shrinkTo(0); + if (_input.range.front == _separator) + _input.range.popFront(); + + try + csvNextToken!(Range, ErrorLevel, Separator) + (_input.range, _front, _separator, _quote,false); + catch (IncompleteCellException ice) + { + ice.row = _input.row; + ice.col = _input.col; + ice.partialData = _front.data.idup; + throw ice; + } + catch (ConvException e) + { + throw new CSVException(e.msg, _input.row, _input.col, e); + } + } + } + + private void prime() + { + try + { + _input.col++; + csvNextToken!(Range, ErrorLevel, Separator) + (_input.range, _front, _separator, _quote,false); + } + catch (IncompleteCellException ice) + { + ice.row = _input.row; + ice.col = _input.col; + ice.partialData = _front.data.idup; + throw ice; + } + + auto skipNum = _popCount.empty ? 0 : _popCount.front; + if (!_popCount.empty) + _popCount.popFront(); + + if (skipNum == size_t.max) + { + while (!recordEnd()) + prime(1); + _empty = true; + return; + } + + if (skipNum) + prime(skipNum); + + auto data = _front.data; + static if (!isSomeString!Contents) skipWS(data); + try curContentsoken = to!Contents(data); + catch (ConvException e) + { + throw new CSVException(e.msg, _input.row, _input.col, e); + } + } +} + +/** + * Lower level control over parsing CSV + * + * This function consumes the input. After each call the input will + * start with either a delimiter or record break (\n, \r\n, \r) which + * must be removed for subsequent calls. + * + * Params: + * input = Any CSV input + * ans = The first field in the input + * sep = The character to represent a comma in the specification + * quote = The character to represent a quote in the specification + * startQuoted = Whether the input should be considered to already be in + * quotes + * + * Throws: + * $(LREF IncompleteCellException) When a quote is found in an unquoted + * field, data continues after a closing quote, or the quoted field was + * not closed before data was empty. + */ +void csvNextToken(Range, Malformed ErrorLevel = Malformed.throwException, + Separator, Output) + (ref Range input, ref Output ans, + Separator sep, Separator quote, + bool startQuoted = false) +if (isSomeChar!Separator && isInputRange!Range + && is(Unqual!(ElementType!Range) == dchar) + && isOutputRange!(Output, dchar)) +{ + bool quoted = startQuoted; + bool escQuote; + if (input.empty) + return; + + if (input.front == '\n') + return; + if (input.front == '\r') + return; + + if (input.front == quote) + { + quoted = true; + input.popFront(); + } + + while (!input.empty) + { + assert(!(quoted && escQuote)); + if (!quoted) + { + // When not quoted the token ends at sep + if (input.front == sep) + break; + if (input.front == '\r') + break; + if (input.front == '\n') + break; + } + if (!quoted && !escQuote) + { + if (input.front == quote) + { + // Not quoted, but quote found + static if (ErrorLevel == Malformed.throwException) + throw new IncompleteCellException( + "Quote located in unquoted token"); + else static if (ErrorLevel == Malformed.ignore) + ans.put(quote); + } + else + { + // Not quoted, non-quote character + ans.put(input.front); + } + } + else + { + if (input.front == quote) + { + // Quoted, quote found + // By turning off quoted and turning on escQuote + // I can tell when to add a quote to the string + // escQuote is turned to false when it escapes a + // quote or is followed by a non-quote (see outside else). + // They are mutually exclusive, but provide different + // information. + if (escQuote) + { + escQuote = false; + quoted = true; + ans.put(quote); + } else + { + escQuote = true; + quoted = false; + } + } + else + { + // Quoted, non-quote character + if (escQuote) + { + static if (ErrorLevel == Malformed.throwException) + throw new IncompleteCellException( + "Content continues after end quote, " ~ + "or needs to be escaped."); + else static if (ErrorLevel == Malformed.ignore) + break; + } + ans.put(input.front); + } + } + input.popFront(); + } + + static if (ErrorLevel == Malformed.throwException) + if (quoted && (input.empty || input.front == '\n' || input.front == '\r')) + throw new IncompleteCellException( + "Data continues on future lines or trailing quote"); + +} + +/// +@safe unittest +{ + import std.array : appender; + import std.range.primitives : popFront; + + string str = "65,63\n123,3673"; + + auto a = appender!(char[])(); + + csvNextToken(str,a,',','"'); + assert(a.data == "65"); + assert(str == ",63\n123,3673"); + + str.popFront(); + a.shrinkTo(0); + csvNextToken(str,a,',','"'); + assert(a.data == "63"); + assert(str == "\n123,3673"); + + str.popFront(); + a.shrinkTo(0); + csvNextToken(str,a,',','"'); + assert(a.data == "123"); + assert(str == ",3673"); +} + +// Test csvNextToken on simplest form and correct format. +@safe pure unittest +{ + import std.array; + + string str = "\U00010143Hello,65,63.63\nWorld,123,3673.562"; + + auto a = appender!(dchar[])(); + csvNextToken!string(str,a,',','"'); + assert(a.data == "\U00010143Hello"); + assert(str == ",65,63.63\nWorld,123,3673.562"); + + str.popFront(); + a.shrinkTo(0); + csvNextToken(str,a,',','"'); + assert(a.data == "65"); + assert(str == ",63.63\nWorld,123,3673.562"); + + str.popFront(); + a.shrinkTo(0); + csvNextToken(str,a,',','"'); + assert(a.data == "63.63"); + assert(str == "\nWorld,123,3673.562"); + + str.popFront(); + a.shrinkTo(0); + csvNextToken(str,a,',','"'); + assert(a.data == "World"); + assert(str == ",123,3673.562"); + + str.popFront(); + a.shrinkTo(0); + csvNextToken(str,a,',','"'); + assert(a.data == "123"); + assert(str == ",3673.562"); + + str.popFront(); + a.shrinkTo(0); + csvNextToken(str,a,',','"'); + assert(a.data == "3673.562"); + assert(str == ""); +} + +// Test quoted tokens +@safe pure unittest +{ + import std.array; + + string str = `one,two,"three ""quoted""","",` ~ "\"five\nnew line\"\nsix"; + + auto a = appender!(dchar[])(); + csvNextToken!string(str,a,',','"'); + assert(a.data == "one"); + assert(str == `,two,"three ""quoted""","",` ~ "\"five\nnew line\"\nsix"); + + str.popFront(); + a.shrinkTo(0); + csvNextToken(str,a,',','"'); + assert(a.data == "two"); + assert(str == `,"three ""quoted""","",` ~ "\"five\nnew line\"\nsix"); + + str.popFront(); + a.shrinkTo(0); + csvNextToken(str,a,',','"'); + assert(a.data == "three \"quoted\""); + assert(str == `,"",` ~ "\"five\nnew line\"\nsix"); + + str.popFront(); + a.shrinkTo(0); + csvNextToken(str,a,',','"'); + assert(a.data == ""); + assert(str == ",\"five\nnew line\"\nsix"); + + str.popFront(); + a.shrinkTo(0); + csvNextToken(str,a,',','"'); + assert(a.data == "five\nnew line"); + assert(str == "\nsix"); + + str.popFront(); + a.shrinkTo(0); + csvNextToken(str,a,',','"'); + assert(a.data == "six"); + assert(str == ""); +} + +// Test empty data is pulled at end of record. +@safe pure unittest +{ + import std.array; + + string str = "one,"; + auto a = appender!(dchar[])(); + csvNextToken(str,a,',','"'); + assert(a.data == "one"); + assert(str == ","); + + a.shrinkTo(0); + csvNextToken(str,a,',','"'); + assert(a.data == ""); +} + +// Test exceptions +@safe pure unittest +{ + import std.array; + + string str = "\"one\nnew line"; + + typeof(appender!(dchar[])()) a; + try + { + a = appender!(dchar[])(); + csvNextToken(str,a,',','"'); + assert(0); + } + catch (IncompleteCellException ice) + { + assert(a.data == "one\nnew line"); + assert(str == ""); + } + + str = "Hello world\""; + + try + { + a = appender!(dchar[])(); + csvNextToken(str,a,',','"'); + assert(0); + } + catch (IncompleteCellException ice) + { + assert(a.data == "Hello world"); + assert(str == "\""); + } + + str = "one, two \"quoted\" end"; + + a = appender!(dchar[])(); + csvNextToken!(string,Malformed.ignore)(str,a,',','"'); + assert(a.data == "one"); + str.popFront(); + a.shrinkTo(0); + csvNextToken!(string,Malformed.ignore)(str,a,',','"'); + assert(a.data == " two \"quoted\" end"); +} + +// Test modifying token delimiter +@safe pure unittest +{ + import std.array; + + string str = `one|two|/three "quoted"/|//`; + + auto a = appender!(dchar[])(); + csvNextToken(str,a, '|','/'); + assert(a.data == "one"d); + assert(str == `|two|/three "quoted"/|//`); + + str.popFront(); + a.shrinkTo(0); + csvNextToken(str,a, '|','/'); + assert(a.data == "two"d); + assert(str == `|/three "quoted"/|//`); + + str.popFront(); + a.shrinkTo(0); + csvNextToken(str,a, '|','/'); + assert(a.data == `three "quoted"`); + assert(str == `|//`); + + str.popFront(); + a.shrinkTo(0); + csvNextToken(str,a, '|','/'); + assert(a.data == ""d); +} + +// Bugzilla 8908 +@safe pure unittest +{ + string csv = ` 1.0, 2.0, 3.0 + 4.0, 5.0, 6.0`; + + static struct Data { real a, b, c; } + size_t i = 0; + foreach (data; csvReader!Data(csv)) with (data) + { + int[] row = [cast(int) a, cast(int) b, cast(int) c]; + if (i == 0) + assert(row == [1, 2, 3]); + else + assert(row == [4, 5, 6]); + ++i; + } + + i = 0; + foreach (data; csvReader!real(csv)) + { + auto a = data.front; data.popFront(); + auto b = data.front; data.popFront(); + auto c = data.front; + int[] row = [cast(int) a, cast(int) b, cast(int) c]; + if (i == 0) + assert(row == [1, 2, 3]); + else + assert(row == [4, 5, 6]); + ++i; + } +} diff --git a/libphobos/src/std/datetime/date.d b/libphobos/src/std/datetime/date.d new file mode 100644 index 0000000..38a8766 --- /dev/null +++ b/libphobos/src/std/datetime/date.d @@ -0,0 +1,10580 @@ +// Written in the D programming language + +/++ + License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + Authors: Jonathan M Davis + Source: $(PHOBOSSRC std/datetime/_date.d) ++/ +module std.datetime.date; + +import core.time; +import std.traits : isSomeString, Unqual; +import std.typecons : Flag; + +version (unittest) import std.exception : assertThrown; + + +@safe unittest +{ + initializeTests(); +} + + +/++ + Exception type used by std.datetime. It's an alias to + $(REF TimeException,core,time). Either can be caught without concern about + which module it came from. + +/ +alias DateTimeException = TimeException; + + +/++ + Represents the 12 months of the Gregorian year (January is 1). + +/ +enum Month : ubyte +{ + jan = 1, /// + feb, /// + mar, /// + apr, /// + may, /// + jun, /// + jul, /// + aug, /// + sep, /// + oct, /// + nov, /// + dec /// +} + + +/++ + Represents the 7 days of the Gregorian week (Sunday is 0). + +/ +enum DayOfWeek : ubyte +{ + sun = 0, /// + mon, /// + tue, /// + wed, /// + thu, /// + fri, /// + sat /// +} + + +/++ + In some date calculations, adding months or years can cause the date to fall + on a day of the month which is not valid (e.g. February 29th 2001 or + June 31st 2000). If overflow is allowed (as is the default), then the month + will be incremented accordingly (so, February 29th 2001 would become + March 1st 2001, and June 31st 2000 would become July 1st 2000). If overflow + is not allowed, then the day will be adjusted to the last valid day in that + month (so, February 29th 2001 would become February 28th 2001 and + June 31st 2000 would become June 30th 2000). + + AllowDayOverflow only applies to calculations involving months or years. + + If set to $(D AllowDayOverflow.no), then day overflow is not allowed. + + Otherwise, if set to $(D AllowDayOverflow.yes), then day overflow is + allowed. + +/ +alias AllowDayOverflow = Flag!"allowDayOverflow"; + + +/++ + Array of the strings representing time units, starting with the smallest + unit and going to the largest. It does not include $(D "nsecs"). + + Includes $(D "hnsecs") (hecto-nanoseconds (100 ns)), + $(D "usecs") (microseconds), $(D "msecs") (milliseconds), $(D "seconds"), + $(D "minutes"), $(D "hours"), $(D "days"), $(D "weeks"), $(D "months"), and + $(D "years") + +/ +immutable string[] timeStrings = ["hnsecs", "usecs", "msecs", "seconds", "minutes", + "hours", "days", "weeks", "months", "years"]; + + +/++ + Combines the $(REF Date,std,datetime,date) and + $(REF TimeOfDay,std,datetime,date) structs to give an object which holds + both the date and the time. It is optimized for calendar-based operations and + has no concept of time zone. For an object which is optimized for time + operations based on the system time, use $(REF SysTime,std,datetime,systime). + $(REF SysTime,std,datetime,systime) has a concept of time zone and has much + higher precision (hnsecs). $(D DateTime) is intended primarily for + calendar-based uses rather than precise time operations. + +/ +struct DateTime +{ +public: + + /++ + Params: + date = The date portion of $(LREF DateTime). + tod = The time portion of $(LREF DateTime). + +/ + this(in Date date, in TimeOfDay tod = TimeOfDay.init) @safe pure nothrow @nogc + { + _date = date; + _tod = tod; + } + + @safe unittest + { + { + auto dt = DateTime.init; + assert(dt._date == Date.init); + assert(dt._tod == TimeOfDay.init); + } + + { + auto dt = DateTime(Date(1999, 7 ,6)); + assert(dt._date == Date(1999, 7, 6)); + assert(dt._tod == TimeOfDay.init); + } + + { + auto dt = DateTime(Date(1999, 7 ,6), TimeOfDay(12, 30, 33)); + assert(dt._date == Date(1999, 7, 6)); + assert(dt._tod == TimeOfDay(12, 30, 33)); + } + } + + + /++ + Params: + year = The year portion of the date. + month = The month portion of the date (January is 1). + day = The day portion of the date. + hour = The hour portion of the time; + minute = The minute portion of the time; + second = The second portion of the time; + +/ + this(int year, int month, int day, int hour = 0, int minute = 0, int second = 0) @safe pure + { + _date = Date(year, month, day); + _tod = TimeOfDay(hour, minute, second); + } + + @safe unittest + { + { + auto dt = DateTime(1999, 7 ,6); + assert(dt._date == Date(1999, 7, 6)); + assert(dt._tod == TimeOfDay.init); + } + + { + auto dt = DateTime(1999, 7 ,6, 12, 30, 33); + assert(dt._date == Date(1999, 7, 6)); + assert(dt._tod == TimeOfDay(12, 30, 33)); + } + } + + + /++ + Compares this $(LREF DateTime) with the given $(D DateTime.). + + Returns: + $(BOOKTABLE, + $(TR $(TD this < rhs) $(TD < 0)) + $(TR $(TD this == rhs) $(TD 0)) + $(TR $(TD this > rhs) $(TD > 0)) + ) + +/ + int opCmp(in DateTime rhs) const @safe pure nothrow @nogc + { + immutable dateResult = _date.opCmp(rhs._date); + + if (dateResult != 0) + return dateResult; + + return _tod.opCmp(rhs._tod); + } + + @safe unittest + { + // Test A.D. + assert(DateTime(Date.init, TimeOfDay.init).opCmp(DateTime.init) == 0); + + assert(DateTime(Date(1999, 1, 1)).opCmp(DateTime(Date(1999, 1, 1))) == 0); + assert(DateTime(Date(1, 7, 1)).opCmp(DateTime(Date(1, 7, 1))) == 0); + assert(DateTime(Date(1, 1, 6)).opCmp(DateTime(Date(1, 1, 6))) == 0); + + assert(DateTime(Date(1999, 7, 1)).opCmp(DateTime(Date(1999, 7, 1))) == 0); + assert(DateTime(Date(1999, 7, 6)).opCmp(DateTime(Date(1999, 7, 6))) == 0); + + assert(DateTime(Date(1, 7, 6)).opCmp(DateTime(Date(1, 7, 6))) == 0); + + assert(DateTime(Date(1999, 7, 6)).opCmp(DateTime(Date(2000, 7, 6))) < 0); + assert(DateTime(Date(2000, 7, 6)).opCmp(DateTime(Date(1999, 7, 6))) > 0); + assert(DateTime(Date(1999, 7, 6)).opCmp(DateTime(Date(1999, 8, 6))) < 0); + assert(DateTime(Date(1999, 8, 6)).opCmp(DateTime(Date(1999, 7, 6))) > 0); + assert(DateTime(Date(1999, 7, 6)).opCmp(DateTime(Date(1999, 7, 7))) < 0); + assert(DateTime(Date(1999, 7, 7)).opCmp(DateTime(Date(1999, 7, 6))) > 0); + + assert(DateTime(Date(1999, 8, 7)).opCmp(DateTime(Date(2000, 7, 6))) < 0); + assert(DateTime(Date(2000, 8, 6)).opCmp(DateTime(Date(1999, 7, 7))) > 0); + assert(DateTime(Date(1999, 7, 7)).opCmp(DateTime(Date(2000, 7, 6))) < 0); + assert(DateTime(Date(2000, 7, 6)).opCmp(DateTime(Date(1999, 7, 7))) > 0); + assert(DateTime(Date(1999, 7, 7)).opCmp(DateTime(Date(1999, 8, 6))) < 0); + assert(DateTime(Date(1999, 8, 6)).opCmp(DateTime(Date(1999, 7, 7))) > 0); + + + assert(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 0)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 0))) == 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 0)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 0))) == 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 0)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 0))) == 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33))) == 0); + + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0))) == 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) == 0); + + assert(DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33))) == 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33))) == 0); + + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33))) < 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) > 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33))) < 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) > 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))) < 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) > 0); + + assert(DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))) > 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33))) < 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33))) > 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33))) < 0); + + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))) > 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33))) < 0); + + assert(DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)).opCmp( + DateTime(Date(2000, 7, 6), TimeOfDay(12, 30, 33))) < 0); + assert(DateTime(Date(2000, 7, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33))) > 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)).opCmp( + DateTime(Date(2000, 7, 6), TimeOfDay(12, 30, 33))) < 0); + assert(DateTime(Date(2000, 7, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33))) > 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)).opCmp( + DateTime(Date(2000, 7, 6), TimeOfDay(12, 30, 33))) < 0); + assert(DateTime(Date(2000, 7, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))) > 0); + + assert(DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)).opCmp( + DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33))) < 0); + assert(DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33))) > 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)).opCmp( + DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33))) < 0); + assert(DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33))) > 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)).opCmp( + DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33))) < 0); + assert(DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))) > 0); + + assert(DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33)).opCmp( + DateTime(Date(1999, 7, 7), TimeOfDay(12, 30, 33))) < 0); + assert(DateTime(Date(1999, 7, 7), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33))) > 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)).opCmp( + DateTime(Date(1999, 7, 7), TimeOfDay(12, 31, 33))) < 0); + assert(DateTime(Date(1999, 7, 7), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) > 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)).opCmp( + DateTime(Date(1999, 7, 7), TimeOfDay(12, 30, 33))) < 0); + assert(DateTime(Date(1999, 7, 7), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))) > 0); + + // Test B.C. + assert(DateTime(Date(-1, 1, 1), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-1, 1, 1), TimeOfDay(12, 30, 33))) == 0); + assert(DateTime(Date(-1, 7, 1), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-1, 7, 1), TimeOfDay(12, 30, 33))) == 0); + assert(DateTime(Date(-1, 1, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-1, 1, 6), TimeOfDay(12, 30, 33))) == 0); + + assert(DateTime(Date(-1999, 7, 1), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-1999, 7, 1), TimeOfDay(12, 30, 33))) == 0); + assert(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))) == 0); + + assert(DateTime(Date(-1, 7, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-1, 7, 6), TimeOfDay(12, 30, 33))) == 0); + + assert(DateTime(Date(-2000, 7, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))) < 0); + assert(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-2000, 7, 6), TimeOfDay(12, 30, 33))) > 0); + assert(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-1999, 8, 6), TimeOfDay(12, 30, 33))) < 0); + assert(DateTime(Date(-1999, 8, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))) > 0); + assert(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33))) < 0); + assert(DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))) > 0); + + assert(DateTime(Date(-2000, 8, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33))) < 0); + assert(DateTime(Date(-1999, 8, 7), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-2000, 7, 6), TimeOfDay(12, 30, 33))) > 0); + assert(DateTime(Date(-2000, 7, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33))) < 0); + assert(DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-2000, 7, 6), TimeOfDay(12, 30, 33))) > 0); + assert(DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-1999, 8, 6), TimeOfDay(12, 30, 33))) < 0); + assert(DateTime(Date(-1999, 8, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33))) > 0); + + // Test Both + assert(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) < 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))) > 0); + + assert(DateTime(Date(-1999, 8, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) < 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-1999, 8, 6), TimeOfDay(12, 30, 33))) > 0); + + assert(DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) < 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-1999, 7, 7), TimeOfDay(12, 30, 33))) > 0); + + assert(DateTime(Date(-1999, 8, 7), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))) < 0); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-1999, 8, 7), TimeOfDay(12, 30, 33))) > 0); + + assert(DateTime(Date(-1999, 8, 6), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(1999, 6, 6), TimeOfDay(12, 30, 33))) < 0); + assert(DateTime(Date(1999, 6, 8), TimeOfDay(12, 30, 33)).opCmp( + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))) > 0); + + auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 33, 30)); + const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 33, 30)); + immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 33, 30)); + assert(dt.opCmp(dt) == 0); + assert(dt.opCmp(cdt) == 0); + assert(dt.opCmp(idt) == 0); + assert(cdt.opCmp(dt) == 0); + assert(cdt.opCmp(cdt) == 0); + assert(cdt.opCmp(idt) == 0); + assert(idt.opCmp(dt) == 0); + assert(idt.opCmp(cdt) == 0); + assert(idt.opCmp(idt) == 0); + } + + + /++ + The date portion of $(LREF DateTime). + +/ + @property Date date() const @safe pure nothrow @nogc + { + return _date; + } + + @safe unittest + { + { + auto dt = DateTime.init; + assert(dt.date == Date.init); + } + + { + auto dt = DateTime(Date(1999, 7, 6)); + assert(dt.date == Date(1999, 7, 6)); + } + + const cdt = DateTime(1999, 7, 6); + immutable idt = DateTime(1999, 7, 6); + assert(cdt.date == Date(1999, 7, 6)); + assert(idt.date == Date(1999, 7, 6)); + } + + + /++ + The date portion of $(LREF DateTime). + + Params: + date = The Date to set this $(LREF DateTime)'s date portion to. + +/ + @property void date(in Date date) @safe pure nothrow @nogc + { + _date = date; + } + + @safe unittest + { + auto dt = DateTime.init; + dt.date = Date(1999, 7, 6); + assert(dt._date == Date(1999, 7, 6)); + assert(dt._tod == TimeOfDay.init); + + const cdt = DateTime(1999, 7, 6); + immutable idt = DateTime(1999, 7, 6); + static assert(!__traits(compiles, cdt.date = Date(2010, 1, 1))); + static assert(!__traits(compiles, idt.date = Date(2010, 1, 1))); + } + + + /++ + The time portion of $(LREF DateTime). + +/ + @property TimeOfDay timeOfDay() const @safe pure nothrow @nogc + { + return _tod; + } + + @safe unittest + { + { + auto dt = DateTime.init; + assert(dt.timeOfDay == TimeOfDay.init); + } + + { + auto dt = DateTime(Date.init, TimeOfDay(12, 30, 33)); + assert(dt.timeOfDay == TimeOfDay(12, 30, 33)); + } + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + assert(cdt.timeOfDay == TimeOfDay(12, 30, 33)); + assert(idt.timeOfDay == TimeOfDay(12, 30, 33)); + } + + + /++ + The time portion of $(LREF DateTime). + + Params: + tod = The $(REF TimeOfDay,std,datetime,date) to set this + $(LREF DateTime)'s time portion to. + +/ + @property void timeOfDay(in TimeOfDay tod) @safe pure nothrow @nogc + { + _tod = tod; + } + + @safe unittest + { + auto dt = DateTime.init; + dt.timeOfDay = TimeOfDay(12, 30, 33); + assert(dt._date == Date.init); + assert(dt._tod == TimeOfDay(12, 30, 33)); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + static assert(!__traits(compiles, cdt.timeOfDay = TimeOfDay(12, 30, 33))); + static assert(!__traits(compiles, idt.timeOfDay = TimeOfDay(12, 30, 33))); + } + + + /++ + Year of the Gregorian Calendar. Positive numbers are A.D. Non-positive + are B.C. + +/ + @property short year() const @safe pure nothrow @nogc + { + return _date.year; + } + + @safe unittest + { + assert(Date.init.year == 1); + assert(Date(1999, 7, 6).year == 1999); + assert(Date(-1999, 7, 6).year == -1999); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + assert(idt.year == 1999); + assert(idt.year == 1999); + } + + + /++ + Year of the Gregorian Calendar. Positive numbers are A.D. Non-positive + are B.C. + + Params: + year = The year to set this $(LREF DateTime)'s year to. + + Throws: + $(REF DateTimeException,std,datetime,date) if the new year is not + a leap year and if the resulting date would be on February 29th. + +/ + @property void year(int year) @safe pure + { + _date.year = year; + } + + /// + @safe unittest + { + assert(DateTime(Date(1999, 7, 6), TimeOfDay(9, 7, 5)).year == 1999); + assert(DateTime(Date(2010, 10, 4), TimeOfDay(0, 0, 30)).year == 2010); + assert(DateTime(Date(-7, 4, 5), TimeOfDay(7, 45, 2)).year == -7); + } + + @safe unittest + { + static void testDT(DateTime dt, int year, in DateTime expected, size_t line = __LINE__) + { + dt.year = year; + assert(dt == expected); + } + + testDT(DateTime(Date(1, 1, 1), TimeOfDay(12, 30, 33)), + 1999, + DateTime(Date(1999, 1, 1), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(1, 1, 1), TimeOfDay(12, 30, 33)), + 0, + DateTime(Date(0, 1, 1), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(1, 1, 1), TimeOfDay(12, 30, 33)), + -1999, + DateTime(Date(-1999, 1, 1), TimeOfDay(12, 30, 33))); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + static assert(!__traits(compiles, cdt.year = 7)); + static assert(!__traits(compiles, idt.year = 7)); + } + + + /++ + Year B.C. of the Gregorian Calendar counting year 0 as 1 B.C. + + Throws: + $(REF DateTimeException,std,datetime,date) if $(D isAD) is true. + +/ + @property short yearBC() const @safe pure + { + return _date.yearBC; + } + + /// + @safe unittest + { + assert(DateTime(Date(0, 1, 1), TimeOfDay(12, 30, 33)).yearBC == 1); + assert(DateTime(Date(-1, 1, 1), TimeOfDay(10, 7, 2)).yearBC == 2); + assert(DateTime(Date(-100, 1, 1), TimeOfDay(4, 59, 0)).yearBC == 101); + } + + @safe unittest + { + assertThrown!DateTimeException((in DateTime dt){dt.yearBC;}(DateTime(Date(1, 1, 1)))); + + auto dt = DateTime(1999, 7, 6, 12, 30, 33); + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + dt.yearBC = 12; + assert(dt.yearBC == 12); + static assert(!__traits(compiles, cdt.yearBC = 12)); + static assert(!__traits(compiles, idt.yearBC = 12)); + } + + + /++ + Year B.C. of the Gregorian Calendar counting year 0 as 1 B.C. + + Params: + year = The year B.C. to set this $(LREF DateTime)'s year to. + + Throws: + $(REF DateTimeException,std,datetime,date) if a non-positive value + is given. + +/ + @property void yearBC(int year) @safe pure + { + _date.yearBC = year; + } + + /// + @safe unittest + { + auto dt = DateTime(Date(2010, 1, 1), TimeOfDay(7, 30, 0)); + dt.yearBC = 1; + assert(dt == DateTime(Date(0, 1, 1), TimeOfDay(7, 30, 0))); + + dt.yearBC = 10; + assert(dt == DateTime(Date(-9, 1, 1), TimeOfDay(7, 30, 0))); + } + + @safe unittest + { + assertThrown!DateTimeException((DateTime dt){dt.yearBC = -1;}(DateTime(Date(1, 1, 1)))); + + auto dt = DateTime(1999, 7, 6, 12, 30, 33); + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + dt.yearBC = 12; + assert(dt.yearBC == 12); + static assert(!__traits(compiles, cdt.yearBC = 12)); + static assert(!__traits(compiles, idt.yearBC = 12)); + } + + + /++ + Month of a Gregorian Year. + +/ + @property Month month() const @safe pure nothrow @nogc + { + return _date.month; + } + + /// + @safe unittest + { + assert(DateTime(Date(1999, 7, 6), TimeOfDay(9, 7, 5)).month == 7); + assert(DateTime(Date(2010, 10, 4), TimeOfDay(0, 0, 30)).month == 10); + assert(DateTime(Date(-7, 4, 5), TimeOfDay(7, 45, 2)).month == 4); + } + + @safe unittest + { + assert(DateTime.init.month == 1); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)).month == 7); + assert(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)).month == 7); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + assert(cdt.month == 7); + assert(idt.month == 7); + } + + + /++ + Month of a Gregorian Year. + + Params: + month = The month to set this $(LREF DateTime)'s month to. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given month is + not a valid month. + +/ + @property void month(Month month) @safe pure + { + _date.month = month; + } + + @safe unittest + { + static void testDT(DateTime dt, Month month, in DateTime expected = DateTime.init, size_t line = __LINE__) + { + dt.month = month; + assert(expected != DateTime.init); + assert(dt == expected); + } + + assertThrown!DateTimeException(testDT(DateTime(Date(1, 1, 1), TimeOfDay(12, 30, 33)), cast(Month) 0)); + assertThrown!DateTimeException(testDT(DateTime(Date(1, 1, 1), TimeOfDay(12, 30, 33)), cast(Month) 13)); + + testDT(DateTime(Date(1, 1, 1), TimeOfDay(12, 30, 33)), + cast(Month) 7, + DateTime(Date(1, 7, 1), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(-1, 1, 1), TimeOfDay(12, 30, 33)), + cast(Month) 7, + DateTime(Date(-1, 7, 1), TimeOfDay(12, 30, 33))); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + static assert(!__traits(compiles, cdt.month = 12)); + static assert(!__traits(compiles, idt.month = 12)); + } + + + /++ + Day of a Gregorian Month. + +/ + @property ubyte day() const @safe pure nothrow @nogc + { + return _date.day; + } + + /// + @safe unittest + { + assert(DateTime(Date(1999, 7, 6), TimeOfDay(9, 7, 5)).day == 6); + assert(DateTime(Date(2010, 10, 4), TimeOfDay(0, 0, 30)).day == 4); + assert(DateTime(Date(-7, 4, 5), TimeOfDay(7, 45, 2)).day == 5); + } + + @safe unittest + { + import std.format : format; + import std.range : chain; + + static void test(DateTime dateTime, int expected) + { + assert(dateTime.day == expected, format("Value given: %s", dateTime)); + } + + foreach (year; chain(testYearsBC, testYearsAD)) + { + foreach (md; testMonthDays) + { + foreach (tod; testTODs) + test(DateTime(Date(year, md.month, md.day), tod), md.day); + } + } + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + assert(cdt.day == 6); + assert(idt.day == 6); + } + + + /++ + Day of a Gregorian Month. + + Params: + day = The day of the month to set this $(LREF DateTime)'s day to. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given day is not + a valid day of the current month. + +/ + @property void day(int day) @safe pure + { + _date.day = day; + } + + @safe unittest + { + import std.exception : assertNotThrown; + + static void testDT(DateTime dt, int day) + { + dt.day = day; + } + + // Test A.D. + assertThrown!DateTimeException(testDT(DateTime(Date(1, 1, 1)), 0)); + assertThrown!DateTimeException(testDT(DateTime(Date(1, 1, 1)), 32)); + assertThrown!DateTimeException(testDT(DateTime(Date(1, 2, 1)), 29)); + assertThrown!DateTimeException(testDT(DateTime(Date(4, 2, 1)), 30)); + assertThrown!DateTimeException(testDT(DateTime(Date(1, 3, 1)), 32)); + assertThrown!DateTimeException(testDT(DateTime(Date(1, 4, 1)), 31)); + assertThrown!DateTimeException(testDT(DateTime(Date(1, 5, 1)), 32)); + assertThrown!DateTimeException(testDT(DateTime(Date(1, 6, 1)), 31)); + assertThrown!DateTimeException(testDT(DateTime(Date(1, 7, 1)), 32)); + assertThrown!DateTimeException(testDT(DateTime(Date(1, 8, 1)), 32)); + assertThrown!DateTimeException(testDT(DateTime(Date(1, 9, 1)), 31)); + assertThrown!DateTimeException(testDT(DateTime(Date(1, 10, 1)), 32)); + assertThrown!DateTimeException(testDT(DateTime(Date(1, 11, 1)), 31)); + assertThrown!DateTimeException(testDT(DateTime(Date(1, 12, 1)), 32)); + + assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 1, 1)), 31)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 2, 1)), 28)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(4, 2, 1)), 29)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 3, 1)), 31)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 4, 1)), 30)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 5, 1)), 31)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 6, 1)), 30)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 7, 1)), 31)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 8, 1)), 31)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 9, 1)), 30)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 10, 1)), 31)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 11, 1)), 30)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(1, 12, 1)), 31)); + + { + auto dt = DateTime(Date(1, 1, 1), TimeOfDay(7, 12, 22)); + dt.day = 6; + assert(dt == DateTime(Date(1, 1, 6), TimeOfDay(7, 12, 22))); + } + + // Test B.C. + assertThrown!DateTimeException(testDT(DateTime(Date(-1, 1, 1)), 0)); + assertThrown!DateTimeException(testDT(DateTime(Date(-1, 1, 1)), 32)); + assertThrown!DateTimeException(testDT(DateTime(Date(-1, 2, 1)), 29)); + assertThrown!DateTimeException(testDT(DateTime(Date(0, 2, 1)), 30)); + assertThrown!DateTimeException(testDT(DateTime(Date(-1, 3, 1)), 32)); + assertThrown!DateTimeException(testDT(DateTime(Date(-1, 4, 1)), 31)); + assertThrown!DateTimeException(testDT(DateTime(Date(-1, 5, 1)), 32)); + assertThrown!DateTimeException(testDT(DateTime(Date(-1, 6, 1)), 31)); + assertThrown!DateTimeException(testDT(DateTime(Date(-1, 7, 1)), 32)); + assertThrown!DateTimeException(testDT(DateTime(Date(-1, 8, 1)), 32)); + assertThrown!DateTimeException(testDT(DateTime(Date(-1, 9, 1)), 31)); + assertThrown!DateTimeException(testDT(DateTime(Date(-1, 10, 1)), 32)); + assertThrown!DateTimeException(testDT(DateTime(Date(-1, 11, 1)), 31)); + assertThrown!DateTimeException(testDT(DateTime(Date(-1, 12, 1)), 32)); + + assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 1, 1)), 31)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 2, 1)), 28)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(0, 2, 1)), 29)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 3, 1)), 31)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 4, 1)), 30)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 5, 1)), 31)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 6, 1)), 30)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 7, 1)), 31)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 8, 1)), 31)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 9, 1)), 30)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 10, 1)), 31)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 11, 1)), 30)); + assertNotThrown!DateTimeException(testDT(DateTime(Date(-1, 12, 1)), 31)); + + auto dt = DateTime(Date(-1, 1, 1), TimeOfDay(7, 12, 22)); + dt.day = 6; + assert(dt == DateTime(Date(-1, 1, 6), TimeOfDay(7, 12, 22))); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + static assert(!__traits(compiles, cdt.day = 27)); + static assert(!__traits(compiles, idt.day = 27)); + } + + + /++ + Hours past midnight. + +/ + @property ubyte hour() const @safe pure nothrow @nogc + { + return _tod.hour; + } + + @safe unittest + { + assert(DateTime.init.hour == 0); + assert(DateTime(Date.init, TimeOfDay(12, 0, 0)).hour == 12); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + assert(cdt.hour == 12); + assert(idt.hour == 12); + } + + + /++ + Hours past midnight. + + Params: + hour = The hour of the day to set this $(LREF DateTime)'s hour to. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given hour would + result in an invalid $(LREF DateTime). + +/ + @property void hour(int hour) @safe pure + { + _tod.hour = hour; + } + + @safe unittest + { + assertThrown!DateTimeException((){DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 0)).hour = 24;}()); + + auto dt = DateTime.init; + dt.hour = 12; + assert(dt == DateTime(1, 1, 1, 12, 0, 0)); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + static assert(!__traits(compiles, cdt.hour = 27)); + static assert(!__traits(compiles, idt.hour = 27)); + } + + + /++ + Minutes past the hour. + +/ + @property ubyte minute() const @safe pure nothrow @nogc + { + return _tod.minute; + } + + @safe unittest + { + assert(DateTime.init.minute == 0); + assert(DateTime(1, 1, 1, 0, 30, 0).minute == 30); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + assert(cdt.minute == 30); + assert(idt.minute == 30); + } + + + /++ + Minutes past the hour. + + Params: + minute = The minute to set this $(LREF DateTime)'s minute to. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given minute + would result in an invalid $(LREF DateTime). + +/ + @property void minute(int minute) @safe pure + { + _tod.minute = minute; + } + + @safe unittest + { + assertThrown!DateTimeException((){DateTime.init.minute = 60;}()); + + auto dt = DateTime.init; + dt.minute = 30; + assert(dt == DateTime(1, 1, 1, 0, 30, 0)); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + static assert(!__traits(compiles, cdt.minute = 27)); + static assert(!__traits(compiles, idt.minute = 27)); + } + + + /++ + Seconds past the minute. + +/ + @property ubyte second() const @safe pure nothrow @nogc + { + return _tod.second; + } + + @safe unittest + { + assert(DateTime.init.second == 0); + assert(DateTime(1, 1, 1, 0, 0, 33).second == 33); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + assert(cdt.second == 33); + assert(idt.second == 33); + } + + + /++ + Seconds past the minute. + + Params: + second = The second to set this $(LREF DateTime)'s second to. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given seconds + would result in an invalid $(LREF DateTime). + +/ + @property void second(int second) @safe pure + { + _tod.second = second; + } + + @safe unittest + { + assertThrown!DateTimeException((){DateTime.init.second = 60;}()); + + auto dt = DateTime.init; + dt.second = 33; + assert(dt == DateTime(1, 1, 1, 0, 0, 33)); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + static assert(!__traits(compiles, cdt.second = 27)); + static assert(!__traits(compiles, idt.second = 27)); + } + + + /++ + Adds the given number of years or months to this $(LREF DateTime). A + negative number will subtract. + + Note that if day overflow is allowed, and the date with the adjusted + year/month overflows the number of days in the new month, then the month + will be incremented by one, and the day set to the number of days + overflowed. (e.g. if the day were 31 and the new month were June, then + the month would be incremented to July, and the new day would be 1). If + day overflow is not allowed, then the day will be set to the last valid + day in the month (e.g. June 31st would become June 30th). + + Params: + units = The type of units to add ("years" or "months"). + value = The number of months or years to add to this + $(LREF DateTime). + allowOverflow = Whether the days should be allowed to overflow, + causing the month to increment. + +/ + ref DateTime add(string units) + (long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe pure nothrow @nogc + if (units == "years" || units == "months") + { + _date.add!units(value, allowOverflow); + return this; + } + + /// + @safe unittest + { + auto dt1 = DateTime(2010, 1, 1, 12, 30, 33); + dt1.add!"months"(11); + assert(dt1 == DateTime(2010, 12, 1, 12, 30, 33)); + + auto dt2 = DateTime(2010, 1, 1, 12, 30, 33); + dt2.add!"months"(-11); + assert(dt2 == DateTime(2009, 2, 1, 12, 30, 33)); + + auto dt3 = DateTime(2000, 2, 29, 12, 30, 33); + dt3.add!"years"(1); + assert(dt3 == DateTime(2001, 3, 1, 12, 30, 33)); + + auto dt4 = DateTime(2000, 2, 29, 12, 30, 33); + dt4.add!"years"(1, AllowDayOverflow.no); + assert(dt4 == DateTime(2001, 2, 28, 12, 30, 33)); + } + + @safe unittest + { + auto dt = DateTime(2000, 1, 31); + dt.add!"years"(7).add!"months"(-4); + assert(dt == DateTime(2006, 10, 1)); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + static assert(!__traits(compiles, cdt.add!"years"(4))); + static assert(!__traits(compiles, idt.add!"years"(4))); + static assert(!__traits(compiles, cdt.add!"months"(4))); + static assert(!__traits(compiles, idt.add!"months"(4))); + } + + + /++ + Adds the given number of years or months to this $(LREF DateTime). A + negative number will subtract. + + The difference between rolling and adding is that rolling does not + affect larger units. Rolling a $(LREF DateTime) 12 months + gets the exact same $(LREF DateTime). However, the days can still be + affected due to the differing number of days in each month. + + Because there are no units larger than years, there is no difference + between adding and rolling years. + + Params: + units = The type of units to add ("years" or "months"). + value = The number of months or years to add to this + $(LREF DateTime). + allowOverflow = Whether the days should be allowed to overflow, + causing the month to increment. + +/ + ref DateTime roll(string units) + (long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe pure nothrow @nogc + if (units == "years" || units == "months") + { + _date.roll!units(value, allowOverflow); + return this; + } + + /// + @safe unittest + { + auto dt1 = DateTime(2010, 1, 1, 12, 33, 33); + dt1.roll!"months"(1); + assert(dt1 == DateTime(2010, 2, 1, 12, 33, 33)); + + auto dt2 = DateTime(2010, 1, 1, 12, 33, 33); + dt2.roll!"months"(-1); + assert(dt2 == DateTime(2010, 12, 1, 12, 33, 33)); + + auto dt3 = DateTime(1999, 1, 29, 12, 33, 33); + dt3.roll!"months"(1); + assert(dt3 == DateTime(1999, 3, 1, 12, 33, 33)); + + auto dt4 = DateTime(1999, 1, 29, 12, 33, 33); + dt4.roll!"months"(1, AllowDayOverflow.no); + assert(dt4 == DateTime(1999, 2, 28, 12, 33, 33)); + + auto dt5 = DateTime(2000, 2, 29, 12, 30, 33); + dt5.roll!"years"(1); + assert(dt5 == DateTime(2001, 3, 1, 12, 30, 33)); + + auto dt6 = DateTime(2000, 2, 29, 12, 30, 33); + dt6.roll!"years"(1, AllowDayOverflow.no); + assert(dt6 == DateTime(2001, 2, 28, 12, 30, 33)); + } + + @safe unittest + { + auto dt = DateTime(2000, 1, 31); + dt.roll!"years"(7).roll!"months"(-4); + assert(dt == DateTime(2007, 10, 1)); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + static assert(!__traits(compiles, cdt.roll!"years"(4))); + static assert(!__traits(compiles, idt.roll!"years"(4))); + static assert(!__traits(compiles, cdt.roll!"months"(4))); + static assert(!__traits(compiles, idt.roll!"months"(4))); + } + + + /++ + Adds the given number of units to this $(LREF DateTime). A negative + number will subtract. + + The difference between rolling and adding is that rolling does not + affect larger units. For instance, rolling a $(LREF DateTime) one + year's worth of days gets the exact same $(LREF DateTime). + + Accepted units are $(D "days"), $(D "minutes"), $(D "hours"), + $(D "minutes"), and $(D "seconds"). + + Params: + units = The units to add. + value = The number of $(D_PARAM units) to add to this + $(LREF DateTime). + +/ + ref DateTime roll(string units)(long value) @safe pure nothrow @nogc + if (units == "days") + { + _date.roll!"days"(value); + return this; + } + + /// + @safe unittest + { + auto dt1 = DateTime(2010, 1, 1, 11, 23, 12); + dt1.roll!"days"(1); + assert(dt1 == DateTime(2010, 1, 2, 11, 23, 12)); + dt1.roll!"days"(365); + assert(dt1 == DateTime(2010, 1, 26, 11, 23, 12)); + dt1.roll!"days"(-32); + assert(dt1 == DateTime(2010, 1, 25, 11, 23, 12)); + + auto dt2 = DateTime(2010, 7, 4, 12, 0, 0); + dt2.roll!"hours"(1); + assert(dt2 == DateTime(2010, 7, 4, 13, 0, 0)); + + auto dt3 = DateTime(2010, 1, 1, 0, 0, 0); + dt3.roll!"seconds"(-1); + assert(dt3 == DateTime(2010, 1, 1, 0, 0, 59)); + } + + @safe unittest + { + auto dt = DateTime(2000, 1, 31); + dt.roll!"days"(7).roll!"days"(-4); + assert(dt == DateTime(2000, 1, 3)); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + static assert(!__traits(compiles, cdt.roll!"days"(4))); + static assert(!__traits(compiles, idt.roll!"days"(4))); + } + + + // Shares documentation with "days" version. + ref DateTime roll(string units)(long value) @safe pure nothrow @nogc + if (units == "hours" || + units == "minutes" || + units == "seconds") + { + _tod.roll!units(value); + return this; + } + + // Test roll!"hours"(). + @safe unittest + { + static void testDT(DateTime orig, int hours, in DateTime expected, size_t line = __LINE__) + { + orig.roll!"hours"(hours); + assert(orig == expected); + } + + // Test A.D. + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 0, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1, + DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 2, + DateTime(Date(1999, 7, 6), TimeOfDay(14, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 3, + DateTime(Date(1999, 7, 6), TimeOfDay(15, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 4, + DateTime(Date(1999, 7, 6), TimeOfDay(16, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 5, + DateTime(Date(1999, 7, 6), TimeOfDay(17, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 6, + DateTime(Date(1999, 7, 6), TimeOfDay(18, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 7, + DateTime(Date(1999, 7, 6), TimeOfDay(19, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 8, + DateTime(Date(1999, 7, 6), TimeOfDay(20, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 9, + DateTime(Date(1999, 7, 6), TimeOfDay(21, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 10, + DateTime(Date(1999, 7, 6), TimeOfDay(22, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 11, + DateTime(Date(1999, 7, 6), TimeOfDay(23, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 12, + DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 13, + DateTime(Date(1999, 7, 6), TimeOfDay(1, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 14, + DateTime(Date(1999, 7, 6), TimeOfDay(2, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 15, + DateTime(Date(1999, 7, 6), TimeOfDay(3, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 16, + DateTime(Date(1999, 7, 6), TimeOfDay(4, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 17, + DateTime(Date(1999, 7, 6), TimeOfDay(5, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 18, + DateTime(Date(1999, 7, 6), TimeOfDay(6, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 19, + DateTime(Date(1999, 7, 6), TimeOfDay(7, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 20, + DateTime(Date(1999, 7, 6), TimeOfDay(8, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 21, + DateTime(Date(1999, 7, 6), TimeOfDay(9, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 22, + DateTime(Date(1999, 7, 6), TimeOfDay(10, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 23, + DateTime(Date(1999, 7, 6), TimeOfDay(11, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 24, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 25, + DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33))); + + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -1, + DateTime(Date(1999, 7, 6), TimeOfDay(11, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -2, + DateTime(Date(1999, 7, 6), TimeOfDay(10, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -3, + DateTime(Date(1999, 7, 6), TimeOfDay(9, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -4, + DateTime(Date(1999, 7, 6), TimeOfDay(8, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -5, + DateTime(Date(1999, 7, 6), TimeOfDay(7, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -6, + DateTime(Date(1999, 7, 6), TimeOfDay(6, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -7, + DateTime(Date(1999, 7, 6), TimeOfDay(5, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -8, + DateTime(Date(1999, 7, 6), TimeOfDay(4, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -9, + DateTime(Date(1999, 7, 6), TimeOfDay(3, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -10, + DateTime(Date(1999, 7, 6), TimeOfDay(2, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -11, + DateTime(Date(1999, 7, 6), TimeOfDay(1, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -12, + DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -13, + DateTime(Date(1999, 7, 6), TimeOfDay(23, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -14, + DateTime(Date(1999, 7, 6), TimeOfDay(22, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -15, + DateTime(Date(1999, 7, 6), TimeOfDay(21, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -16, + DateTime(Date(1999, 7, 6), TimeOfDay(20, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -17, + DateTime(Date(1999, 7, 6), TimeOfDay(19, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -18, + DateTime(Date(1999, 7, 6), TimeOfDay(18, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -19, + DateTime(Date(1999, 7, 6), TimeOfDay(17, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -20, + DateTime(Date(1999, 7, 6), TimeOfDay(16, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -21, + DateTime(Date(1999, 7, 6), TimeOfDay(15, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -22, + DateTime(Date(1999, 7, 6), TimeOfDay(14, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -23, + DateTime(Date(1999, 7, 6), TimeOfDay(13, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -24, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -25, + DateTime(Date(1999, 7, 6), TimeOfDay(11, 30, 33))); + + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33)), 1, + DateTime(Date(1999, 7, 6), TimeOfDay(1, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33)), 0, + DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33)), -1, + DateTime(Date(1999, 7, 6), TimeOfDay(23, 30, 33))); + + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(23, 30, 33)), 1, + DateTime(Date(1999, 7, 6), TimeOfDay(0, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(23, 30, 33)), 0, + DateTime(Date(1999, 7, 6), TimeOfDay(23, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(23, 30, 33)), -1, + DateTime(Date(1999, 7, 6), TimeOfDay(22, 30, 33))); + + testDT(DateTime(Date(1999, 7, 31), TimeOfDay(23, 30, 33)), 1, + DateTime(Date(1999, 7, 31), TimeOfDay(0, 30, 33))); + testDT(DateTime(Date(1999, 8, 1), TimeOfDay(0, 30, 33)), -1, + DateTime(Date(1999, 8, 1), TimeOfDay(23, 30, 33))); + + testDT(DateTime(Date(1999, 12, 31), TimeOfDay(23, 30, 33)), 1, + DateTime(Date(1999, 12, 31), TimeOfDay(0, 30, 33))); + testDT(DateTime(Date(2000, 1, 1), TimeOfDay(0, 30, 33)), -1, + DateTime(Date(2000, 1, 1), TimeOfDay(23, 30, 33))); + + testDT(DateTime(Date(1999, 2, 28), TimeOfDay(23, 30, 33)), 25, + DateTime(Date(1999, 2, 28), TimeOfDay(0, 30, 33))); + testDT(DateTime(Date(1999, 3, 2), TimeOfDay(0, 30, 33)), -25, + DateTime(Date(1999, 3, 2), TimeOfDay(23, 30, 33))); + + testDT(DateTime(Date(2000, 2, 28), TimeOfDay(23, 30, 33)), 25, + DateTime(Date(2000, 2, 28), TimeOfDay(0, 30, 33))); + testDT(DateTime(Date(2000, 3, 1), TimeOfDay(0, 30, 33)), -25, + DateTime(Date(2000, 3, 1), TimeOfDay(23, 30, 33))); + + // Test B.C. + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 0, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1, + DateTime(Date(-1999, 7, 6), TimeOfDay(13, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 2, + DateTime(Date(-1999, 7, 6), TimeOfDay(14, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 3, + DateTime(Date(-1999, 7, 6), TimeOfDay(15, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 4, + DateTime(Date(-1999, 7, 6), TimeOfDay(16, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 5, + DateTime(Date(-1999, 7, 6), TimeOfDay(17, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 6, + DateTime(Date(-1999, 7, 6), TimeOfDay(18, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 7, + DateTime(Date(-1999, 7, 6), TimeOfDay(19, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 8, + DateTime(Date(-1999, 7, 6), TimeOfDay(20, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 9, + DateTime(Date(-1999, 7, 6), TimeOfDay(21, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 10, + DateTime(Date(-1999, 7, 6), TimeOfDay(22, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 11, + DateTime(Date(-1999, 7, 6), TimeOfDay(23, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 12, + DateTime(Date(-1999, 7, 6), TimeOfDay(0, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 13, + DateTime(Date(-1999, 7, 6), TimeOfDay(1, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 14, + DateTime(Date(-1999, 7, 6), TimeOfDay(2, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 15, + DateTime(Date(-1999, 7, 6), TimeOfDay(3, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 16, + DateTime(Date(-1999, 7, 6), TimeOfDay(4, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 17, + DateTime(Date(-1999, 7, 6), TimeOfDay(5, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 18, + DateTime(Date(-1999, 7, 6), TimeOfDay(6, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 19, + DateTime(Date(-1999, 7, 6), TimeOfDay(7, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 20, + DateTime(Date(-1999, 7, 6), TimeOfDay(8, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 21, + DateTime(Date(-1999, 7, 6), TimeOfDay(9, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 22, + DateTime(Date(-1999, 7, 6), TimeOfDay(10, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 23, + DateTime(Date(-1999, 7, 6), TimeOfDay(11, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 24, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 25, + DateTime(Date(-1999, 7, 6), TimeOfDay(13, 30, 33))); + + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -1, + DateTime(Date(-1999, 7, 6), TimeOfDay(11, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -2, + DateTime(Date(-1999, 7, 6), TimeOfDay(10, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -3, + DateTime(Date(-1999, 7, 6), TimeOfDay(9, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -4, + DateTime(Date(-1999, 7, 6), TimeOfDay(8, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -5, + DateTime(Date(-1999, 7, 6), TimeOfDay(7, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -6, + DateTime(Date(-1999, 7, 6), TimeOfDay(6, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -7, + DateTime(Date(-1999, 7, 6), TimeOfDay(5, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -8, + DateTime(Date(-1999, 7, 6), TimeOfDay(4, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -9, + DateTime(Date(-1999, 7, 6), TimeOfDay(3, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -10, + DateTime(Date(-1999, 7, 6), TimeOfDay(2, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -11, + DateTime(Date(-1999, 7, 6), TimeOfDay(1, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -12, + DateTime(Date(-1999, 7, 6), TimeOfDay(0, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -13, + DateTime(Date(-1999, 7, 6), TimeOfDay(23, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -14, + DateTime(Date(-1999, 7, 6), TimeOfDay(22, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -15, + DateTime(Date(-1999, 7, 6), TimeOfDay(21, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -16, + DateTime(Date(-1999, 7, 6), TimeOfDay(20, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -17, + DateTime(Date(-1999, 7, 6), TimeOfDay(19, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -18, + DateTime(Date(-1999, 7, 6), TimeOfDay(18, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -19, + DateTime(Date(-1999, 7, 6), TimeOfDay(17, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -20, + DateTime(Date(-1999, 7, 6), TimeOfDay(16, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -21, + DateTime(Date(-1999, 7, 6), TimeOfDay(15, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -22, + DateTime(Date(-1999, 7, 6), TimeOfDay(14, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -23, + DateTime(Date(-1999, 7, 6), TimeOfDay(13, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -24, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -25, + DateTime(Date(-1999, 7, 6), TimeOfDay(11, 30, 33))); + + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 30, 33)), 1, + DateTime(Date(-1999, 7, 6), TimeOfDay(1, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 30, 33)), 0, + DateTime(Date(-1999, 7, 6), TimeOfDay(0, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 30, 33)), -1, + DateTime(Date(-1999, 7, 6), TimeOfDay(23, 30, 33))); + + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(23, 30, 33)), 1, + DateTime(Date(-1999, 7, 6), TimeOfDay(0, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(23, 30, 33)), 0, + DateTime(Date(-1999, 7, 6), TimeOfDay(23, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(23, 30, 33)), -1, + DateTime(Date(-1999, 7, 6), TimeOfDay(22, 30, 33))); + + testDT(DateTime(Date(-1999, 7, 31), TimeOfDay(23, 30, 33)), 1, + DateTime(Date(-1999, 7, 31), TimeOfDay(0, 30, 33))); + testDT(DateTime(Date(-1999, 8, 1), TimeOfDay(0, 30, 33)), -1, + DateTime(Date(-1999, 8, 1), TimeOfDay(23, 30, 33))); + + testDT(DateTime(Date(-2001, 12, 31), TimeOfDay(23, 30, 33)), 1, + DateTime(Date(-2001, 12, 31), TimeOfDay(0, 30, 33))); + testDT(DateTime(Date(-2000, 1, 1), TimeOfDay(0, 30, 33)), -1, + DateTime(Date(-2000, 1, 1), TimeOfDay(23, 30, 33))); + + testDT(DateTime(Date(-2001, 2, 28), TimeOfDay(23, 30, 33)), 25, + DateTime(Date(-2001, 2, 28), TimeOfDay(0, 30, 33))); + testDT(DateTime(Date(-2001, 3, 2), TimeOfDay(0, 30, 33)), -25, + DateTime(Date(-2001, 3, 2), TimeOfDay(23, 30, 33))); + + testDT(DateTime(Date(-2000, 2, 28), TimeOfDay(23, 30, 33)), 25, + DateTime(Date(-2000, 2, 28), TimeOfDay(0, 30, 33))); + testDT(DateTime(Date(-2000, 3, 1), TimeOfDay(0, 30, 33)), -25, + DateTime(Date(-2000, 3, 1), TimeOfDay(23, 30, 33))); + + // Test Both + testDT(DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 33)), 17_546, + DateTime(Date(-1, 1, 1), TimeOfDay(13, 30, 33))); + testDT(DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 33)), -17_546, + DateTime(Date(1, 1, 1), TimeOfDay(11, 30, 33))); + + auto dt = DateTime(2000, 1, 31, 9, 7, 6); + dt.roll!"hours"(27).roll!"hours"(-9); + assert(dt == DateTime(2000, 1, 31, 3, 7, 6)); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + static assert(!__traits(compiles, cdt.roll!"hours"(4))); + static assert(!__traits(compiles, idt.roll!"hours"(4))); + } + + // Test roll!"minutes"(). + @safe unittest + { + static void testDT(DateTime orig, int minutes, in DateTime expected, size_t line = __LINE__) + { + orig.roll!"minutes"(minutes); + assert(orig == expected); + } + + // Test A.D. + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 0, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 2, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 32, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 3, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 33, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 4, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 34, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 5, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 35, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 10, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 40, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 15, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 45, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 29, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 59, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 30, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 45, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 15, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 60, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 75, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 45, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 90, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 100, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 10, 33))); + + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 689, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 59, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 690, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 691, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 1, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 960, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1439, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 29, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1440, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1441, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 2880, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -1, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 29, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -2, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 28, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -3, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 27, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -4, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 26, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -5, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 25, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -10, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 20, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -15, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 15, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -29, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 1, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -30, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -45, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 45, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -60, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -75, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 15, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -90, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -100, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 50, 33))); + + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -749, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 1, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -750, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -751, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 59, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -960, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -1439, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -1440, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -1441, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 29, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -2880, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)), 1, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 1, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)), 0, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 33)), -1, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 59, 33))); + + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(11, 59, 33)), 1, + DateTime(Date(1999, 7, 6), TimeOfDay(11, 0, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(11, 59, 33)), 0, + DateTime(Date(1999, 7, 6), TimeOfDay(11, 59, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(11, 59, 33)), -1, + DateTime(Date(1999, 7, 6), TimeOfDay(11, 58, 33))); + + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33)), 1, + DateTime(Date(1999, 7, 6), TimeOfDay(0, 1, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33)), 0, + DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 33)), -1, + DateTime(Date(1999, 7, 6), TimeOfDay(0, 59, 33))); + + testDT(DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 33)), 1, + DateTime(Date(1999, 7, 5), TimeOfDay(23, 0, 33))); + testDT(DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 33)), 0, + DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 33))); + testDT(DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 33)), -1, + DateTime(Date(1999, 7, 5), TimeOfDay(23, 58, 33))); + + testDT(DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 33)), 1, + DateTime(Date(1998, 12, 31), TimeOfDay(23, 0, 33))); + testDT(DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 33)), 0, + DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 33))); + testDT(DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 33)), -1, + DateTime(Date(1998, 12, 31), TimeOfDay(23, 58, 33))); + + // Test B.C. + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 0, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 31, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 2, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 32, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 3, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 33, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 4, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 34, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 5, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 35, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 10, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 40, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 15, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 45, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 29, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 59, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 30, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 45, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 15, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 60, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 75, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 45, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 90, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 100, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 10, 33))); + + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 689, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 59, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 690, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 691, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 1, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 960, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1439, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 29, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1440, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1441, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 31, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 2880, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))); + + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -1, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 29, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -2, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 28, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -3, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 27, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -4, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 26, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -5, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 25, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -10, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 20, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -15, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 15, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -29, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 1, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -30, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -45, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 45, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -60, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -75, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 15, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -90, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -100, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 50, 33))); + + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -749, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 1, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -750, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -751, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 59, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -960, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -1439, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 31, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -1440, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -1441, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 29, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -2880, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))); + + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)), 1, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 1, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)), 0, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 33)), -1, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 59, 33))); + + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(11, 59, 33)), 1, + DateTime(Date(-1999, 7, 6), TimeOfDay(11, 0, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(11, 59, 33)), 0, + DateTime(Date(-1999, 7, 6), TimeOfDay(11, 59, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(11, 59, 33)), -1, + DateTime(Date(-1999, 7, 6), TimeOfDay(11, 58, 33))); + + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 33)), 1, + DateTime(Date(-1999, 7, 6), TimeOfDay(0, 1, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 33)), 0, + DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 33)), -1, + DateTime(Date(-1999, 7, 6), TimeOfDay(0, 59, 33))); + + testDT(DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 33)), 1, + DateTime(Date(-1999, 7, 5), TimeOfDay(23, 0, 33))); + testDT(DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 33)), 0, + DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 33))); + testDT(DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 33)), -1, + DateTime(Date(-1999, 7, 5), TimeOfDay(23, 58, 33))); + + testDT(DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 33)), 1, + DateTime(Date(-2000, 12, 31), TimeOfDay(23, 0, 33))); + testDT(DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 33)), 0, + DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 33))); + testDT(DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 33)), -1, + DateTime(Date(-2000, 12, 31), TimeOfDay(23, 58, 33))); + + // Test Both + testDT(DateTime(Date(1, 1, 1), TimeOfDay(0, 0, 0)), -1, + DateTime(Date(1, 1, 1), TimeOfDay(0, 59, 0))); + testDT(DateTime(Date(0, 12, 31), TimeOfDay(23, 59, 0)), 1, + DateTime(Date(0, 12, 31), TimeOfDay(23, 0, 0))); + + testDT(DateTime(Date(0, 1, 1), TimeOfDay(0, 0, 0)), -1, + DateTime(Date(0, 1, 1), TimeOfDay(0, 59, 0))); + testDT(DateTime(Date(-1, 12, 31), TimeOfDay(23, 59, 0)), 1, + DateTime(Date(-1, 12, 31), TimeOfDay(23, 0, 0))); + + testDT(DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 33)), 1_052_760, + DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 33))); + testDT(DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 33)), -1_052_760, + DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 33))); + + testDT(DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 33)), 1_052_782, + DateTime(Date(-1, 1, 1), TimeOfDay(11, 52, 33))); + testDT(DateTime(Date(1, 1, 1), TimeOfDay(13, 52, 33)), -1_052_782, + DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 33))); + + auto dt = DateTime(2000, 1, 31, 9, 7, 6); + dt.roll!"minutes"(92).roll!"minutes"(-292); + assert(dt == DateTime(2000, 1, 31, 9, 47, 6)); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + static assert(!__traits(compiles, cdt.roll!"minutes"(4))); + static assert(!__traits(compiles, idt.roll!"minutes"(4))); + } + + // Test roll!"seconds"(). + @safe unittest + { + static void testDT(DateTime orig, int seconds, in DateTime expected, size_t line = __LINE__) + { + orig.roll!"seconds"(seconds); + assert(orig == expected); + } + + // Test A.D. + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 0, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 2, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 35))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 3, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 36))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 4, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 37))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 5, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 38))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 10, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 43))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 15, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 48))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 26, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 59))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 27, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 30, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 3))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 59, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 32))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 60, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 61, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))); + + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1766, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 59))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1767, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 1768, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 1))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 2007, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 3599, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 32))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 3600, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 3601, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), 7200, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -1, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 32))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -2, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 31))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -3, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 30))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -4, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 29))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -5, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 28))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -10, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 23))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -15, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 18))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -33, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -34, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 59))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -35, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 58))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -59, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -60, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)), -61, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 32))); + + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)), 1, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 1))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)), 0, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 0)), -1, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 59))); + + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 0)), 1, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 1))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 0)), 0, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 0))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 0)), -1, + DateTime(Date(1999, 7, 6), TimeOfDay(12, 0, 59))); + + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 0)), 1, + DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 1))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 0)), 0, + DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 0))); + testDT(DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 0)), -1, + DateTime(Date(1999, 7, 6), TimeOfDay(0, 0, 59))); + + testDT(DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 59)), 1, + DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 0))); + testDT(DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 59)), 0, + DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 59))); + testDT(DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 59)), -1, + DateTime(Date(1999, 7, 5), TimeOfDay(23, 59, 58))); + + testDT(DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 59)), 1, + DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 0))); + testDT(DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 59)), 0, + DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 59))); + testDT(DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 59)), -1, + DateTime(Date(1998, 12, 31), TimeOfDay(23, 59, 58))); + + // Test B.C. + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 0, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 34))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 2, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 35))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 3, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 36))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 4, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 37))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 5, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 38))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 10, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 43))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 15, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 48))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 26, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 59))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 27, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 30, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 3))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 59, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 32))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 60, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 61, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 34))); + + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1766, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 59))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1767, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 1768, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 1))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 2007, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 3599, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 32))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 3600, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 3601, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 34))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), 7200, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))); + + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -1, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 32))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -2, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 31))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -3, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 30))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -4, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 29))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -5, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 28))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -10, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 23))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -15, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 18))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -33, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -34, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 59))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -35, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 58))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -59, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 34))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -60, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33)), -61, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 32))); + + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0)), 1, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 1))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0)), 0, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 0)), -1, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 59))); + + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 0)), 1, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 1))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 0)), 0, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 0))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 0)), -1, + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 0, 59))); + + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 0)), 1, + DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 1))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 0)), 0, + DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 0))); + testDT(DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 0)), -1, + DateTime(Date(-1999, 7, 6), TimeOfDay(0, 0, 59))); + + testDT(DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 59)), 1, + DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 0))); + testDT(DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 59)), 0, + DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 59))); + testDT(DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 59)), -1, + DateTime(Date(-1999, 7, 5), TimeOfDay(23, 59, 58))); + + testDT(DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 59)), 1, + DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 0))); + testDT(DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 59)), 0, + DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 59))); + testDT(DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 59)), -1, + DateTime(Date(-2000, 12, 31), TimeOfDay(23, 59, 58))); + + // Test Both + testDT(DateTime(Date(1, 1, 1), TimeOfDay(0, 0, 0)), -1, + DateTime(Date(1, 1, 1), TimeOfDay(0, 0, 59))); + testDT(DateTime(Date(0, 12, 31), TimeOfDay(23, 59, 59)), 1, + DateTime(Date(0, 12, 31), TimeOfDay(23, 59, 0))); + + testDT(DateTime(Date(0, 1, 1), TimeOfDay(0, 0, 0)), -1, + DateTime(Date(0, 1, 1), TimeOfDay(0, 0, 59))); + testDT(DateTime(Date(-1, 12, 31), TimeOfDay(23, 59, 59)), 1, + DateTime(Date(-1, 12, 31), TimeOfDay(23, 59, 0))); + + testDT(DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 33)), 63_165_600L, + DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 33))); + testDT(DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 33)), -63_165_600L, + DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 33))); + + testDT(DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 33)), 63_165_617L, + DateTime(Date(-1, 1, 1), TimeOfDay(11, 30, 50))); + testDT(DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 50)), -63_165_617L, + DateTime(Date(1, 1, 1), TimeOfDay(13, 30, 33))); + + auto dt = DateTime(2000, 1, 31, 9, 7, 6); + dt.roll!"seconds"(92).roll!"seconds"(-292); + assert(dt == DateTime(2000, 1, 31, 9, 7, 46)); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + static assert(!__traits(compiles, cdt.roll!"seconds"(4))); + static assert(!__traits(compiles, idt.roll!"seconds"(4))); + } + + + /++ + Gives the result of adding or subtracting a $(REF Duration, core,time) + from this $(LREF DateTime). + + The legal types of arithmetic for $(LREF DateTime) using this operator + are + + $(BOOKTABLE, + $(TR $(TD DateTime) $(TD +) $(TD Duration) $(TD -->) $(TD DateTime)) + $(TR $(TD DateTime) $(TD -) $(TD Duration) $(TD -->) $(TD DateTime)) + ) + + Params: + duration = The $(REF Duration, core,time) to add to or subtract from + this $(LREF DateTime). + +/ + DateTime opBinary(string op)(Duration duration) const @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + DateTime retval = this; + immutable seconds = duration.total!"seconds"; + mixin("return retval._addSeconds(" ~ op ~ "seconds);"); + } + + /// + @safe unittest + { + import core.time : hours, seconds; + + assert(DateTime(2015, 12, 31, 23, 59, 59) + seconds(1) == + DateTime(2016, 1, 1, 0, 0, 0)); + + assert(DateTime(2015, 12, 31, 23, 59, 59) + hours(1) == + DateTime(2016, 1, 1, 0, 59, 59)); + + assert(DateTime(2016, 1, 1, 0, 0, 0) - seconds(1) == + DateTime(2015, 12, 31, 23, 59, 59)); + + assert(DateTime(2016, 1, 1, 0, 59, 59) - hours(1) == + DateTime(2015, 12, 31, 23, 59, 59)); + } + + @safe unittest + { + auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + + assert(dt + dur!"weeks"(7) == DateTime(Date(1999, 8, 24), TimeOfDay(12, 30, 33))); + assert(dt + dur!"weeks"(-7) == DateTime(Date(1999, 5, 18), TimeOfDay(12, 30, 33))); + assert(dt + dur!"days"(7) == DateTime(Date(1999, 7, 13), TimeOfDay(12, 30, 33))); + assert(dt + dur!"days"(-7) == DateTime(Date(1999, 6, 29), TimeOfDay(12, 30, 33))); + + assert(dt + dur!"hours"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(19, 30, 33))); + assert(dt + dur!"hours"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(5, 30, 33))); + assert(dt + dur!"minutes"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 37, 33))); + assert(dt + dur!"minutes"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 23, 33))); + assert(dt + dur!"seconds"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40))); + assert(dt + dur!"seconds"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26))); + assert(dt + dur!"msecs"(7_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40))); + assert(dt + dur!"msecs"(-7_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26))); + assert(dt + dur!"usecs"(7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40))); + assert(dt + dur!"usecs"(-7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26))); + assert(dt + dur!"hnsecs"(70_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40))); + assert(dt + dur!"hnsecs"(-70_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26))); + + assert(dt - dur!"weeks"(-7) == DateTime(Date(1999, 8, 24), TimeOfDay(12, 30, 33))); + assert(dt - dur!"weeks"(7) == DateTime(Date(1999, 5, 18), TimeOfDay(12, 30, 33))); + assert(dt - dur!"days"(-7) == DateTime(Date(1999, 7, 13), TimeOfDay(12, 30, 33))); + assert(dt - dur!"days"(7) == DateTime(Date(1999, 6, 29), TimeOfDay(12, 30, 33))); + + assert(dt - dur!"hours"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(19, 30, 33))); + assert(dt - dur!"hours"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(5, 30, 33))); + assert(dt - dur!"minutes"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 37, 33))); + assert(dt - dur!"minutes"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 23, 33))); + assert(dt - dur!"seconds"(-7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40))); + assert(dt - dur!"seconds"(7) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26))); + assert(dt - dur!"msecs"(-7_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40))); + assert(dt - dur!"msecs"(7_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26))); + assert(dt - dur!"usecs"(-7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40))); + assert(dt - dur!"usecs"(7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26))); + assert(dt - dur!"hnsecs"(-70_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40))); + assert(dt - dur!"hnsecs"(70_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26))); + + auto duration = dur!"seconds"(12); + const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + assert(cdt + duration == DateTime(1999, 7, 6, 12, 30, 45)); + assert(idt + duration == DateTime(1999, 7, 6, 12, 30, 45)); + assert(cdt - duration == DateTime(1999, 7, 6, 12, 30, 21)); + assert(idt - duration == DateTime(1999, 7, 6, 12, 30, 21)); + } + + // Explicitly undocumented. It will be removed in January 2018. @@@DEPRECATED_2018-01@@@ + deprecated("Use Duration instead of TickDuration.") + DateTime opBinary(string op)(in TickDuration td) const @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + DateTime retval = this; + immutable seconds = td.seconds; + mixin("return retval._addSeconds(" ~ op ~ "seconds);"); + } + + deprecated @safe unittest + { + // This probably only runs in cases where gettimeofday() is used, but it's + // hard to do this test correctly with variable ticksPerSec. + if (TickDuration.ticksPerSec == 1_000_000) + { + auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + + assert(dt + TickDuration.from!"usecs"(7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40))); + assert(dt + TickDuration.from!"usecs"(-7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26))); + + assert(dt - TickDuration.from!"usecs"(-7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40))); + assert(dt - TickDuration.from!"usecs"(7_000_000) == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26))); + } + } + + + /++ + Gives the result of adding or subtracting a duration from this + $(LREF DateTime), as well as assigning the result to this + $(LREF DateTime). + + The legal types of arithmetic for $(LREF DateTime) using this operator + are + + $(BOOKTABLE, + $(TR $(TD DateTime) $(TD +) $(TD duration) $(TD -->) $(TD DateTime)) + $(TR $(TD DateTime) $(TD -) $(TD duration) $(TD -->) $(TD DateTime)) + ) + + Params: + duration = The duration to add to or subtract from this + $(LREF DateTime). + +/ + ref DateTime opOpAssign(string op, D)(in D duration) @safe pure nothrow @nogc + if ((op == "+" || op == "-") && + (is(Unqual!D == Duration) || + is(Unqual!D == TickDuration))) + { + import std.format : format; + + DateTime retval = this; + + static if (is(Unqual!D == Duration)) + immutable hnsecs = duration.total!"hnsecs"; + else static if (is(Unqual!D == TickDuration)) + immutable hnsecs = duration.hnsecs; + + mixin(format(`return _addSeconds(convert!("hnsecs", "seconds")(%shnsecs));`, op)); + } + + @safe unittest + { + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"weeks"(7) == + DateTime(Date(1999, 8, 24), TimeOfDay(12, 30, 33))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"weeks"(-7) == + DateTime(Date(1999, 5, 18), TimeOfDay(12, 30, 33))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"days"(7) == + DateTime(Date(1999, 7, 13), TimeOfDay(12, 30, 33))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"days"(-7) == + DateTime(Date(1999, 6, 29), TimeOfDay(12, 30, 33))); + + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"hours"(7) == + DateTime(Date(1999, 7, 6), TimeOfDay(19, 30, 33))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"hours"(-7) == + DateTime(Date(1999, 7, 6), TimeOfDay(5, 30, 33))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"minutes"(7) == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 37, 33))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"minutes"(-7) == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 23, 33))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"seconds"(7) == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"seconds"(-7) == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"msecs"(7_000) == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"msecs"(-7_000) == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"usecs"(7_000_000) == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"usecs"(-7_000_000) == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"hnsecs"(70_000_000) == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) + dur!"hnsecs"(-70_000_000) == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26))); + + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"weeks"(-7) == + DateTime(Date(1999, 8, 24), TimeOfDay(12, 30, 33))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"weeks"(7) == + DateTime(Date(1999, 5, 18), TimeOfDay(12, 30, 33))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"days"(-7) == + DateTime(Date(1999, 7, 13), TimeOfDay(12, 30, 33))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"days"(7) == + DateTime(Date(1999, 6, 29), TimeOfDay(12, 30, 33))); + + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"hours"(-7) == + DateTime(Date(1999, 7, 6), TimeOfDay(19, 30, 33))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"hours"(7) == + DateTime(Date(1999, 7, 6), TimeOfDay(5, 30, 33))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"minutes"(-7) == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 37, 33))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"minutes"(7) == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 23, 33))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"seconds"(-7) == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"seconds"(7) == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"msecs"(-7_000) == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"msecs"(7_000) == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"usecs"(-7_000_000) == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"usecs"(7_000_000) == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"hnsecs"(-70_000_000) == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40))); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - dur!"hnsecs"(70_000_000) == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26))); + + auto dt = DateTime(2000, 1, 31, 9, 7, 6); + (dt += dur!"seconds"(92)) -= dur!"days"(-500); + assert(dt == DateTime(2001, 6, 14, 9, 8, 38)); + + auto duration = dur!"seconds"(12); + const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + static assert(!__traits(compiles, cdt += duration)); + static assert(!__traits(compiles, idt += duration)); + static assert(!__traits(compiles, cdt -= duration)); + static assert(!__traits(compiles, idt -= duration)); + } + + // Explicitly undocumented. It will be removed in January 2018. @@@DEPRECATED_2018-01@@@ + deprecated("Use Duration instead of TickDuration.") + ref DateTime opOpAssign(string op)(TickDuration td) @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + DateTime retval = this; + immutable seconds = td.seconds; + mixin("return _addSeconds(" ~ op ~ "seconds);"); + } + + deprecated @safe unittest + { + // This probably only runs in cases where gettimeofday() is used, but it's + // hard to do this test correctly with variable ticksPerSec. + if (TickDuration.ticksPerSec == 1_000_000) + { + { + auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + dt += TickDuration.from!"usecs"(7_000_000); + assert(dt == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40))); + } + + { + auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + dt += TickDuration.from!"usecs"(-7_000_000); + assert(dt == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26))); + } + + { + auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + dt -= TickDuration.from!"usecs"(-7_000_000); + assert(dt == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 40))); + } + + { + auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + dt -= TickDuration.from!"usecs"(7_000_000); + assert(dt == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 26))); + } + } + } + + + /++ + Gives the difference between two $(LREF DateTime)s. + + The legal types of arithmetic for $(LREF DateTime) using this operator are + + $(BOOKTABLE, + $(TR $(TD DateTime) $(TD -) $(TD DateTime) $(TD -->) $(TD duration)) + ) + +/ + Duration opBinary(string op)(in DateTime rhs) const @safe pure nothrow @nogc + if (op == "-") + { + immutable dateResult = _date - rhs.date; + immutable todResult = _tod - rhs._tod; + + return dur!"hnsecs"(dateResult.total!"hnsecs" + todResult.total!"hnsecs"); + } + + @safe unittest + { + auto dt = DateTime(1999, 7, 6, 12, 30, 33); + + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - DateTime(Date(1998, 7, 6), TimeOfDay(12, 30, 33)) == + dur!"seconds"(31_536_000)); + assert(DateTime(Date(1998, 7, 6), TimeOfDay(12, 30, 33)) - DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) == + dur!"seconds"(-31_536_000)); + + assert(DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33)) - DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) == + dur!"seconds"(26_78_400)); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - DateTime(Date(1999, 8, 6), TimeOfDay(12, 30, 33)) == + dur!"seconds"(-26_78_400)); + + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - DateTime(Date(1999, 7, 5), TimeOfDay(12, 30, 33)) == + dur!"seconds"(86_400)); + assert(DateTime(Date(1999, 7, 5), TimeOfDay(12, 30, 33)) - DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) == + dur!"seconds"(-86_400)); + + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - DateTime(Date(1999, 7, 6), TimeOfDay(11, 30, 33)) == + dur!"seconds"(3600)); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(11, 30, 33)) - DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) == + dur!"seconds"(-3600)); + + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)) - DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) == + dur!"seconds"(60)); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - DateTime(Date(1999, 7, 6), TimeOfDay(12, 31, 33)) == + dur!"seconds"(-60)); + + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)) - DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) == + dur!"seconds"(1)); + assert(DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)) - DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 34)) == + dur!"seconds"(-1)); + + assert(DateTime(1, 1, 1, 12, 30, 33) - DateTime(1, 1, 1, 0, 0, 0) == dur!"seconds"(45033)); + assert(DateTime(1, 1, 1, 0, 0, 0) - DateTime(1, 1, 1, 12, 30, 33) == dur!"seconds"(-45033)); + assert(DateTime(0, 12, 31, 12, 30, 33) - DateTime(1, 1, 1, 0, 0, 0) == dur!"seconds"(-41367)); + assert(DateTime(1, 1, 1, 0, 0, 0) - DateTime(0, 12, 31, 12, 30, 33) == dur!"seconds"(41367)); + + const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + assert(dt - dt == Duration.zero); + assert(cdt - dt == Duration.zero); + assert(idt - dt == Duration.zero); + + assert(dt - cdt == Duration.zero); + assert(cdt - cdt == Duration.zero); + assert(idt - cdt == Duration.zero); + + assert(dt - idt == Duration.zero); + assert(cdt - idt == Duration.zero); + assert(idt - idt == Duration.zero); + } + + + /++ + Returns the difference between the two $(LREF DateTime)s in months. + + To get the difference in years, subtract the year property + of two $(LREF DateTime)s. To get the difference in days or weeks, + subtract the $(LREF DateTime)s themselves and use the + $(REF Duration, core,time) that results. Because converting between + months and smaller units requires a specific date (which + $(REF Duration, core,time)s don't have), getting the difference in + months requires some math using both the year and month properties, so + this is a convenience function for getting the difference in months. + + Note that the number of days in the months or how far into the month + either date is is irrelevant. It is the difference in the month property + combined with the difference in years * 12. So, for instance, + December 31st and January 1st are one month apart just as December 1st + and January 31st are one month apart. + + Params: + rhs = The $(LREF DateTime) to subtract from this one. + +/ + int diffMonths(in DateTime rhs) const @safe pure nothrow @nogc + { + return _date.diffMonths(rhs._date); + } + + /// + @safe unittest + { + assert(DateTime(1999, 2, 1, 12, 2, 3).diffMonths( + DateTime(1999, 1, 31, 23, 59, 59)) == 1); + + assert(DateTime(1999, 1, 31, 0, 0, 0).diffMonths( + DateTime(1999, 2, 1, 12, 3, 42)) == -1); + + assert(DateTime(1999, 3, 1, 5, 30, 0).diffMonths( + DateTime(1999, 1, 1, 2, 4, 7)) == 2); + + assert(DateTime(1999, 1, 1, 7, 2, 4).diffMonths( + DateTime(1999, 3, 31, 0, 30, 58)) == -2); + } + + @safe unittest + { + auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + assert(dt.diffMonths(dt) == 0); + assert(cdt.diffMonths(dt) == 0); + assert(idt.diffMonths(dt) == 0); + + assert(dt.diffMonths(cdt) == 0); + assert(cdt.diffMonths(cdt) == 0); + assert(idt.diffMonths(cdt) == 0); + + assert(dt.diffMonths(idt) == 0); + assert(cdt.diffMonths(idt) == 0); + assert(idt.diffMonths(idt) == 0); + } + + + /++ + Whether this $(LREF DateTime) is in a leap year. + +/ + @property bool isLeapYear() const @safe pure nothrow @nogc + { + return _date.isLeapYear; + } + + @safe unittest + { + auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + assert(!dt.isLeapYear); + assert(!cdt.isLeapYear); + assert(!idt.isLeapYear); + } + + + /++ + Day of the week this $(LREF DateTime) is on. + +/ + @property DayOfWeek dayOfWeek() const @safe pure nothrow @nogc + { + return _date.dayOfWeek; + } + + @safe unittest + { + auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + assert(dt.dayOfWeek == DayOfWeek.tue); + assert(cdt.dayOfWeek == DayOfWeek.tue); + assert(idt.dayOfWeek == DayOfWeek.tue); + } + + + /++ + Day of the year this $(LREF DateTime) is on. + +/ + @property ushort dayOfYear() const @safe pure nothrow @nogc + { + return _date.dayOfYear; + } + + /// + @safe unittest + { + assert(DateTime(Date(1999, 1, 1), TimeOfDay(12, 22, 7)).dayOfYear == 1); + assert(DateTime(Date(1999, 12, 31), TimeOfDay(7, 2, 59)).dayOfYear == 365); + assert(DateTime(Date(2000, 12, 31), TimeOfDay(21, 20, 0)).dayOfYear == 366); + } + + @safe unittest + { + auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + assert(dt.dayOfYear == 187); + assert(cdt.dayOfYear == 187); + assert(idt.dayOfYear == 187); + } + + + /++ + Day of the year. + + Params: + day = The day of the year to set which day of the year this + $(LREF DateTime) is on. + +/ + @property void dayOfYear(int day) @safe pure + { + _date.dayOfYear = day; + } + + @safe unittest + { + auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + dt.dayOfYear = 12; + assert(dt.dayOfYear == 12); + static assert(!__traits(compiles, cdt.dayOfYear = 12)); + static assert(!__traits(compiles, idt.dayOfYear = 12)); + } + + + /++ + The Xth day of the Gregorian Calendar that this $(LREF DateTime) is on. + +/ + @property int dayOfGregorianCal() const @safe pure nothrow @nogc + { + return _date.dayOfGregorianCal; + } + + /// + @safe unittest + { + assert(DateTime(Date(1, 1, 1), TimeOfDay(0, 0, 0)).dayOfGregorianCal == 1); + assert(DateTime(Date(1, 12, 31), TimeOfDay(23, 59, 59)).dayOfGregorianCal == 365); + assert(DateTime(Date(2, 1, 1), TimeOfDay(2, 2, 2)).dayOfGregorianCal == 366); + + assert(DateTime(Date(0, 12, 31), TimeOfDay(7, 7, 7)).dayOfGregorianCal == 0); + assert(DateTime(Date(0, 1, 1), TimeOfDay(19, 30, 0)).dayOfGregorianCal == -365); + assert(DateTime(Date(-1, 12, 31), TimeOfDay(4, 7, 0)).dayOfGregorianCal == -366); + + assert(DateTime(Date(2000, 1, 1), TimeOfDay(9, 30, 20)).dayOfGregorianCal == 730_120); + assert(DateTime(Date(2010, 12, 31), TimeOfDay(15, 45, 50)).dayOfGregorianCal == 734_137); + } + + @safe unittest + { + const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + assert(cdt.dayOfGregorianCal == 729_941); + assert(idt.dayOfGregorianCal == 729_941); + } + + + /++ + The Xth day of the Gregorian Calendar that this $(LREF DateTime) is on. + Setting this property does not affect the time portion of + $(LREF DateTime). + + Params: + days = The day of the Gregorian Calendar to set this $(LREF DateTime) + to. + +/ + @property void dayOfGregorianCal(int days) @safe pure nothrow @nogc + { + _date.dayOfGregorianCal = days; + } + + /// + @safe unittest + { + auto dt = DateTime(Date.init, TimeOfDay(12, 0, 0)); + dt.dayOfGregorianCal = 1; + assert(dt == DateTime(Date(1, 1, 1), TimeOfDay(12, 0, 0))); + + dt.dayOfGregorianCal = 365; + assert(dt == DateTime(Date(1, 12, 31), TimeOfDay(12, 0, 0))); + + dt.dayOfGregorianCal = 366; + assert(dt == DateTime(Date(2, 1, 1), TimeOfDay(12, 0, 0))); + + dt.dayOfGregorianCal = 0; + assert(dt == DateTime(Date(0, 12, 31), TimeOfDay(12, 0, 0))); + + dt.dayOfGregorianCal = -365; + assert(dt == DateTime(Date(-0, 1, 1), TimeOfDay(12, 0, 0))); + + dt.dayOfGregorianCal = -366; + assert(dt == DateTime(Date(-1, 12, 31), TimeOfDay(12, 0, 0))); + + dt.dayOfGregorianCal = 730_120; + assert(dt == DateTime(Date(2000, 1, 1), TimeOfDay(12, 0, 0))); + + dt.dayOfGregorianCal = 734_137; + assert(dt == DateTime(Date(2010, 12, 31), TimeOfDay(12, 0, 0))); + } + + @safe unittest + { + const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + static assert(!__traits(compiles, cdt.dayOfGregorianCal = 7)); + static assert(!__traits(compiles, idt.dayOfGregorianCal = 7)); + } + + + /++ + The ISO 8601 week of the year that this $(LREF DateTime) is in. + + See_Also: + $(HTTP en.wikipedia.org/wiki/ISO_week_date, ISO Week Date) + +/ + @property ubyte isoWeek() const @safe pure nothrow + { + return _date.isoWeek; + } + + @safe unittest + { + auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + assert(dt.isoWeek == 27); + assert(cdt.isoWeek == 27); + assert(idt.isoWeek == 27); + } + + + /++ + $(LREF DateTime) for the last day in the month that this + $(LREF DateTime) is in. The time portion of endOfMonth is always + 23:59:59. + +/ + @property DateTime endOfMonth() const @safe pure nothrow + { + try + return DateTime(_date.endOfMonth, TimeOfDay(23, 59, 59)); + catch (Exception e) + assert(0, "DateTime constructor threw."); + } + + /// + @safe unittest + { + assert(DateTime(Date(1999, 1, 6), TimeOfDay(0, 0, 0)).endOfMonth == + DateTime(Date(1999, 1, 31), TimeOfDay(23, 59, 59))); + + assert(DateTime(Date(1999, 2, 7), TimeOfDay(19, 30, 0)).endOfMonth == + DateTime(Date(1999, 2, 28), TimeOfDay(23, 59, 59))); + + assert(DateTime(Date(2000, 2, 7), TimeOfDay(5, 12, 27)).endOfMonth == + DateTime(Date(2000, 2, 29), TimeOfDay(23, 59, 59))); + + assert(DateTime(Date(2000, 6, 4), TimeOfDay(12, 22, 9)).endOfMonth == + DateTime(Date(2000, 6, 30), TimeOfDay(23, 59, 59))); + } + + @safe unittest + { + // Test A.D. + assert(DateTime(1999, 1, 1, 0, 13, 26).endOfMonth == DateTime(1999, 1, 31, 23, 59, 59)); + assert(DateTime(1999, 2, 1, 1, 14, 27).endOfMonth == DateTime(1999, 2, 28, 23, 59, 59)); + assert(DateTime(2000, 2, 1, 2, 15, 28).endOfMonth == DateTime(2000, 2, 29, 23, 59, 59)); + assert(DateTime(1999, 3, 1, 3, 16, 29).endOfMonth == DateTime(1999, 3, 31, 23, 59, 59)); + assert(DateTime(1999, 4, 1, 4, 17, 30).endOfMonth == DateTime(1999, 4, 30, 23, 59, 59)); + assert(DateTime(1999, 5, 1, 5, 18, 31).endOfMonth == DateTime(1999, 5, 31, 23, 59, 59)); + assert(DateTime(1999, 6, 1, 6, 19, 32).endOfMonth == DateTime(1999, 6, 30, 23, 59, 59)); + assert(DateTime(1999, 7, 1, 7, 20, 33).endOfMonth == DateTime(1999, 7, 31, 23, 59, 59)); + assert(DateTime(1999, 8, 1, 8, 21, 34).endOfMonth == DateTime(1999, 8, 31, 23, 59, 59)); + assert(DateTime(1999, 9, 1, 9, 22, 35).endOfMonth == DateTime(1999, 9, 30, 23, 59, 59)); + assert(DateTime(1999, 10, 1, 10, 23, 36).endOfMonth == DateTime(1999, 10, 31, 23, 59, 59)); + assert(DateTime(1999, 11, 1, 11, 24, 37).endOfMonth == DateTime(1999, 11, 30, 23, 59, 59)); + assert(DateTime(1999, 12, 1, 12, 25, 38).endOfMonth == DateTime(1999, 12, 31, 23, 59, 59)); + + // Test B.C. + assert(DateTime(-1999, 1, 1, 0, 13, 26).endOfMonth == DateTime(-1999, 1, 31, 23, 59, 59)); + assert(DateTime(-1999, 2, 1, 1, 14, 27).endOfMonth == DateTime(-1999, 2, 28, 23, 59, 59)); + assert(DateTime(-2000, 2, 1, 2, 15, 28).endOfMonth == DateTime(-2000, 2, 29, 23, 59, 59)); + assert(DateTime(-1999, 3, 1, 3, 16, 29).endOfMonth == DateTime(-1999, 3, 31, 23, 59, 59)); + assert(DateTime(-1999, 4, 1, 4, 17, 30).endOfMonth == DateTime(-1999, 4, 30, 23, 59, 59)); + assert(DateTime(-1999, 5, 1, 5, 18, 31).endOfMonth == DateTime(-1999, 5, 31, 23, 59, 59)); + assert(DateTime(-1999, 6, 1, 6, 19, 32).endOfMonth == DateTime(-1999, 6, 30, 23, 59, 59)); + assert(DateTime(-1999, 7, 1, 7, 20, 33).endOfMonth == DateTime(-1999, 7, 31, 23, 59, 59)); + assert(DateTime(-1999, 8, 1, 8, 21, 34).endOfMonth == DateTime(-1999, 8, 31, 23, 59, 59)); + assert(DateTime(-1999, 9, 1, 9, 22, 35).endOfMonth == DateTime(-1999, 9, 30, 23, 59, 59)); + assert(DateTime(-1999, 10, 1, 10, 23, 36).endOfMonth == DateTime(-1999, 10, 31, 23, 59, 59)); + assert(DateTime(-1999, 11, 1, 11, 24, 37).endOfMonth == DateTime(-1999, 11, 30, 23, 59, 59)); + assert(DateTime(-1999, 12, 1, 12, 25, 38).endOfMonth == DateTime(-1999, 12, 31, 23, 59, 59)); + + const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + assert(cdt.endOfMonth == DateTime(1999, 7, 31, 23, 59, 59)); + assert(idt.endOfMonth == DateTime(1999, 7, 31, 23, 59, 59)); + } + + + /++ + The last day in the month that this $(LREF DateTime) is in. + +/ + @property ubyte daysInMonth() const @safe pure nothrow @nogc + { + return _date.daysInMonth; + } + + /// + @safe unittest + { + assert(DateTime(Date(1999, 1, 6), TimeOfDay(0, 0, 0)).daysInMonth == 31); + assert(DateTime(Date(1999, 2, 7), TimeOfDay(19, 30, 0)).daysInMonth == 28); + assert(DateTime(Date(2000, 2, 7), TimeOfDay(5, 12, 27)).daysInMonth == 29); + assert(DateTime(Date(2000, 6, 4), TimeOfDay(12, 22, 9)).daysInMonth == 30); + } + + @safe unittest + { + const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + assert(cdt.daysInMonth == 31); + assert(idt.daysInMonth == 31); + } + + + /++ + Whether the current year is a date in A.D. + +/ + @property bool isAD() const @safe pure nothrow @nogc + { + return _date.isAD; + } + + /// + @safe unittest + { + assert(DateTime(Date(1, 1, 1), TimeOfDay(12, 7, 0)).isAD); + assert(DateTime(Date(2010, 12, 31), TimeOfDay(0, 0, 0)).isAD); + assert(!DateTime(Date(0, 12, 31), TimeOfDay(23, 59, 59)).isAD); + assert(!DateTime(Date(-2010, 1, 1), TimeOfDay(2, 2, 2)).isAD); + } + + @safe unittest + { + const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + assert(cdt.isAD); + assert(idt.isAD); + } + + + /++ + The $(HTTP en.wikipedia.org/wiki/Julian_day, Julian day) for this + $(LREF DateTime) at the given time. For example, prior to noon, + 1996-03-31 would be the Julian day number 2_450_173, so this function + returns 2_450_173, while from noon onward, the julian day number would + be 2_450_174, so this function returns 2_450_174. + +/ + @property long julianDay() const @safe pure nothrow @nogc + { + if (_tod._hour < 12) + return _date.julianDay - 1; + else + return _date.julianDay; + } + + @safe unittest + { + assert(DateTime(Date(-4713, 11, 24), TimeOfDay(0, 0, 0)).julianDay == -1); + assert(DateTime(Date(-4713, 11, 24), TimeOfDay(12, 0, 0)).julianDay == 0); + + assert(DateTime(Date(0, 12, 31), TimeOfDay(0, 0, 0)).julianDay == 1_721_424); + assert(DateTime(Date(0, 12, 31), TimeOfDay(12, 0, 0)).julianDay == 1_721_425); + + assert(DateTime(Date(1, 1, 1), TimeOfDay(0, 0, 0)).julianDay == 1_721_425); + assert(DateTime(Date(1, 1, 1), TimeOfDay(12, 0, 0)).julianDay == 1_721_426); + + assert(DateTime(Date(1582, 10, 15), TimeOfDay(0, 0, 0)).julianDay == 2_299_160); + assert(DateTime(Date(1582, 10, 15), TimeOfDay(12, 0, 0)).julianDay == 2_299_161); + + assert(DateTime(Date(1858, 11, 17), TimeOfDay(0, 0, 0)).julianDay == 2_400_000); + assert(DateTime(Date(1858, 11, 17), TimeOfDay(12, 0, 0)).julianDay == 2_400_001); + + assert(DateTime(Date(1982, 1, 4), TimeOfDay(0, 0, 0)).julianDay == 2_444_973); + assert(DateTime(Date(1982, 1, 4), TimeOfDay(12, 0, 0)).julianDay == 2_444_974); + + assert(DateTime(Date(1996, 3, 31), TimeOfDay(0, 0, 0)).julianDay == 2_450_173); + assert(DateTime(Date(1996, 3, 31), TimeOfDay(12, 0, 0)).julianDay == 2_450_174); + + assert(DateTime(Date(2010, 8, 24), TimeOfDay(0, 0, 0)).julianDay == 2_455_432); + assert(DateTime(Date(2010, 8, 24), TimeOfDay(12, 0, 0)).julianDay == 2_455_433); + + const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + assert(cdt.julianDay == 2_451_366); + assert(idt.julianDay == 2_451_366); + } + + + /++ + The modified $(HTTP en.wikipedia.org/wiki/Julian_day, Julian day) for any + time on this date (since, the modified Julian day changes at midnight). + +/ + @property long modJulianDay() const @safe pure nothrow @nogc + { + return _date.modJulianDay; + } + + @safe unittest + { + assert(DateTime(Date(1858, 11, 17), TimeOfDay(0, 0, 0)).modJulianDay == 0); + assert(DateTime(Date(1858, 11, 17), TimeOfDay(12, 0, 0)).modJulianDay == 0); + + assert(DateTime(Date(2010, 8, 24), TimeOfDay(0, 0, 0)).modJulianDay == 55_432); + assert(DateTime(Date(2010, 8, 24), TimeOfDay(12, 0, 0)).modJulianDay == 55_432); + + const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + assert(cdt.modJulianDay == 51_365); + assert(idt.modJulianDay == 51_365); + } + + + /++ + Converts this $(LREF DateTime) to a string with the format YYYYMMDDTHHMMSS. + +/ + string toISOString() const @safe pure nothrow + { + import std.format : format; + try + { + return format!("%sT%02d%02d%02d")( + _date.toISOString(), + _tod._hour, + _tod._minute, + _tod._second + ); + } + catch (Exception e) + { + assert(0, "format() threw."); + } + } + + /// + @safe unittest + { + assert(DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12)).toISOString() == + "20100704T070612"); + + assert(DateTime(Date(1998, 12, 25), TimeOfDay(2, 15, 0)).toISOString() == + "19981225T021500"); + + assert(DateTime(Date(0, 1, 5), TimeOfDay(23, 9, 59)).toISOString() == + "00000105T230959"); + + assert(DateTime(Date(-4, 1, 5), TimeOfDay(0, 0, 2)).toISOString() == + "-00040105T000002"); + } + + @safe unittest + { + // Test A.D. + assert(DateTime(Date(9, 12, 4), TimeOfDay(0, 0, 0)).toISOString() == "00091204T000000"); + assert(DateTime(Date(99, 12, 4), TimeOfDay(5, 6, 12)).toISOString() == "00991204T050612"); + assert(DateTime(Date(999, 12, 4), TimeOfDay(13, 44, 59)).toISOString() == "09991204T134459"); + assert(DateTime(Date(9999, 7, 4), TimeOfDay(23, 59, 59)).toISOString() == "99990704T235959"); + assert(DateTime(Date(10000, 10, 20), TimeOfDay(1, 1, 1)).toISOString() == "+100001020T010101"); + + // Test B.C. + assert(DateTime(Date(0, 12, 4), TimeOfDay(0, 12, 4)).toISOString() == "00001204T001204"); + assert(DateTime(Date(-9, 12, 4), TimeOfDay(0, 0, 0)).toISOString() == "-00091204T000000"); + assert(DateTime(Date(-99, 12, 4), TimeOfDay(5, 6, 12)).toISOString() == "-00991204T050612"); + assert(DateTime(Date(-999, 12, 4), TimeOfDay(13, 44, 59)).toISOString() == "-09991204T134459"); + assert(DateTime(Date(-9999, 7, 4), TimeOfDay(23, 59, 59)).toISOString() == "-99990704T235959"); + assert(DateTime(Date(-10000, 10, 20), TimeOfDay(1, 1, 1)).toISOString() == "-100001020T010101"); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + assert(cdt.toISOString() == "19990706T123033"); + assert(idt.toISOString() == "19990706T123033"); + } + + + /++ + Converts this $(LREF DateTime) to a string with the format + YYYY-MM-DDTHH:MM:SS. + +/ + string toISOExtString() const @safe pure nothrow + { + import std.format : format; + try + { + return format!("%sT%02d:%02d:%02d")( + _date.toISOExtString(), + _tod._hour, + _tod._minute, + _tod._second + ); + } + catch (Exception e) + { + assert(0, "format() threw."); + } + } + + /// + @safe unittest + { + assert(DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12)).toISOExtString() == + "2010-07-04T07:06:12"); + + assert(DateTime(Date(1998, 12, 25), TimeOfDay(2, 15, 0)).toISOExtString() == + "1998-12-25T02:15:00"); + + assert(DateTime(Date(0, 1, 5), TimeOfDay(23, 9, 59)).toISOExtString() == + "0000-01-05T23:09:59"); + + assert(DateTime(Date(-4, 1, 5), TimeOfDay(0, 0, 2)).toISOExtString() == + "-0004-01-05T00:00:02"); + } + + @safe unittest + { + // Test A.D. + assert(DateTime(Date(9, 12, 4), TimeOfDay(0, 0, 0)).toISOExtString() == "0009-12-04T00:00:00"); + assert(DateTime(Date(99, 12, 4), TimeOfDay(5, 6, 12)).toISOExtString() == "0099-12-04T05:06:12"); + assert(DateTime(Date(999, 12, 4), TimeOfDay(13, 44, 59)).toISOExtString() == "0999-12-04T13:44:59"); + assert(DateTime(Date(9999, 7, 4), TimeOfDay(23, 59, 59)).toISOExtString() == "9999-07-04T23:59:59"); + assert(DateTime(Date(10000, 10, 20), TimeOfDay(1, 1, 1)).toISOExtString() == "+10000-10-20T01:01:01"); + + // Test B.C. + assert(DateTime(Date(0, 12, 4), TimeOfDay(0, 12, 4)).toISOExtString() == "0000-12-04T00:12:04"); + assert(DateTime(Date(-9, 12, 4), TimeOfDay(0, 0, 0)).toISOExtString() == "-0009-12-04T00:00:00"); + assert(DateTime(Date(-99, 12, 4), TimeOfDay(5, 6, 12)).toISOExtString() == "-0099-12-04T05:06:12"); + assert(DateTime(Date(-999, 12, 4), TimeOfDay(13, 44, 59)).toISOExtString() == "-0999-12-04T13:44:59"); + assert(DateTime(Date(-9999, 7, 4), TimeOfDay(23, 59, 59)).toISOExtString() == "-9999-07-04T23:59:59"); + assert(DateTime(Date(-10000, 10, 20), TimeOfDay(1, 1, 1)).toISOExtString() == "-10000-10-20T01:01:01"); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + assert(cdt.toISOExtString() == "1999-07-06T12:30:33"); + assert(idt.toISOExtString() == "1999-07-06T12:30:33"); + } + + /++ + Converts this $(LREF DateTime) to a string with the format + YYYY-Mon-DD HH:MM:SS. + +/ + string toSimpleString() const @safe pure nothrow + { + import std.format : format; + try + { + return format!("%s %02d:%02d:%02d")( + _date.toSimpleString(), + _tod._hour, + _tod._minute, + _tod._second + ); + } + catch (Exception e) + { + assert(0, "format() threw."); + } + } + + /// + @safe unittest + { + assert(DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12)).toSimpleString() == + "2010-Jul-04 07:06:12"); + + assert(DateTime(Date(1998, 12, 25), TimeOfDay(2, 15, 0)).toSimpleString() == + "1998-Dec-25 02:15:00"); + + assert(DateTime(Date(0, 1, 5), TimeOfDay(23, 9, 59)).toSimpleString() == + "0000-Jan-05 23:09:59"); + + assert(DateTime(Date(-4, 1, 5), TimeOfDay(0, 0, 2)).toSimpleString() == + "-0004-Jan-05 00:00:02"); + } + + @safe unittest + { + // Test A.D. + assert(DateTime(Date(9, 12, 4), TimeOfDay(0, 0, 0)).toSimpleString() == "0009-Dec-04 00:00:00"); + assert(DateTime(Date(99, 12, 4), TimeOfDay(5, 6, 12)).toSimpleString() == "0099-Dec-04 05:06:12"); + assert(DateTime(Date(999, 12, 4), TimeOfDay(13, 44, 59)).toSimpleString() == "0999-Dec-04 13:44:59"); + assert(DateTime(Date(9999, 7, 4), TimeOfDay(23, 59, 59)).toSimpleString() == "9999-Jul-04 23:59:59"); + assert(DateTime(Date(10000, 10, 20), TimeOfDay(1, 1, 1)).toSimpleString() == "+10000-Oct-20 01:01:01"); + + // Test B.C. + assert(DateTime(Date(0, 12, 4), TimeOfDay(0, 12, 4)).toSimpleString() == "0000-Dec-04 00:12:04"); + assert(DateTime(Date(-9, 12, 4), TimeOfDay(0, 0, 0)).toSimpleString() == "-0009-Dec-04 00:00:00"); + assert(DateTime(Date(-99, 12, 4), TimeOfDay(5, 6, 12)).toSimpleString() == "-0099-Dec-04 05:06:12"); + assert(DateTime(Date(-999, 12, 4), TimeOfDay(13, 44, 59)).toSimpleString() == "-0999-Dec-04 13:44:59"); + assert(DateTime(Date(-9999, 7, 4), TimeOfDay(23, 59, 59)).toSimpleString() == "-9999-Jul-04 23:59:59"); + assert(DateTime(Date(-10000, 10, 20), TimeOfDay(1, 1, 1)).toSimpleString() == "-10000-Oct-20 01:01:01"); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + assert(cdt.toSimpleString() == "1999-Jul-06 12:30:33"); + assert(idt.toSimpleString() == "1999-Jul-06 12:30:33"); + } + + + /++ + Converts this $(LREF DateTime) to a string. + + This function exists to make it easy to convert a $(LREF DateTime) to a + string for code that does not care what the exact format is - just that + it presents the information in a clear manner. It also makes it easy to + simply convert a $(LREF DateTime) to a string when using functions such + as `to!string`, `format`, or `writeln` which use toString to convert + user-defined types. So, it is unlikely that much code will call + toString directly. + + The format of the string is purposefully unspecified, and code that + cares about the format of the string should use `toISOString`, + `toISOExtString`, `toSimpleString`, or some other custom formatting + function that explicitly generates the format that the code needs. The + reason is that the code is then clear about what format it's using, + making it less error-prone to maintain the code and interact with other + software that consumes the generated strings. It's for this same reason + that $(LREF DateTime) has no `fromString` function, whereas it does have + `fromISOString`, `fromISOExtString`, and `fromSimpleString`. + + The format returned by toString may or may not change in the future. + +/ + string toString() const @safe pure nothrow + { + return toSimpleString(); + } + + @safe unittest + { + auto dt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + const cdt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + immutable idt = DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33)); + assert(dt.toString()); + assert(cdt.toString()); + assert(idt.toString()); + } + + + + /++ + Creates a $(LREF DateTime) from a string with the format YYYYMMDDTHHMMSS. + Whitespace is stripped from the given string. + + Params: + isoString = A string formatted in the ISO format for dates and times. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given string is + not in the ISO format or if the resulting $(LREF DateTime) would not + be valid. + +/ + static DateTime fromISOString(S)(in S isoString) @safe pure + if (isSomeString!S) + { + import std.algorithm.searching : countUntil; + import std.exception : enforce; + import std.format : format; + import std.string : strip; + + auto str = strip(isoString); + + enforce(str.length >= 15, new DateTimeException(format("Invalid ISO String: %s", isoString))); + auto t = str.countUntil('T'); + + enforce(t != -1, new DateTimeException(format("Invalid ISO String: %s", isoString))); + + immutable date = Date.fromISOString(str[0 .. t]); + immutable tod = TimeOfDay.fromISOString(str[t+1 .. $]); + + return DateTime(date, tod); + } + + /// + @safe unittest + { + assert(DateTime.fromISOString("20100704T070612") == + DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12))); + + assert(DateTime.fromISOString("19981225T021500") == + DateTime(Date(1998, 12, 25), TimeOfDay(2, 15, 0))); + + assert(DateTime.fromISOString("00000105T230959") == + DateTime(Date(0, 1, 5), TimeOfDay(23, 9, 59))); + + assert(DateTime.fromISOString("-00040105T000002") == + DateTime(Date(-4, 1, 5), TimeOfDay(0, 0, 2))); + + assert(DateTime.fromISOString(" 20100704T070612 ") == + DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12))); + } + + @safe unittest + { + assertThrown!DateTimeException(DateTime.fromISOString("")); + assertThrown!DateTimeException(DateTime.fromISOString("20100704000000")); + assertThrown!DateTimeException(DateTime.fromISOString("20100704 000000")); + assertThrown!DateTimeException(DateTime.fromISOString("20100704t000000")); + assertThrown!DateTimeException(DateTime.fromISOString("20100704T000000.")); + assertThrown!DateTimeException(DateTime.fromISOString("20100704T000000.0")); + + assertThrown!DateTimeException(DateTime.fromISOString("2010-07-0400:00:00")); + assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04 00:00:00")); + assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04t00:00:00")); + assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04T00:00:00.")); + assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04T00:00:00.0")); + + assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-0400:00:00")); + assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04 00:00:00")); + assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04t00:00:00")); + assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04T00:00:00")); + assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04 00:00:00.")); + assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04 00:00:00.0")); + + assertThrown!DateTimeException(DateTime.fromISOString("2010-12-22T172201")); + assertThrown!DateTimeException(DateTime.fromISOString("2010-Dec-22 17:22:01")); + + assert(DateTime.fromISOString("20101222T172201") == DateTime(Date(2010, 12, 22), TimeOfDay(17, 22, 01))); + assert(DateTime.fromISOString("19990706T123033") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + assert(DateTime.fromISOString("-19990706T123033") == DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))); + assert(DateTime.fromISOString("+019990706T123033") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + assert(DateTime.fromISOString("19990706T123033 ") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + assert(DateTime.fromISOString(" 19990706T123033") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + assert(DateTime.fromISOString(" 19990706T123033 ") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + } + + // bug# 17801 + @safe unittest + { + import std.conv : to; + import std.meta : AliasSeq; + foreach (C; AliasSeq!(char, wchar, dchar)) + { + foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + assert(DateTime.fromISOString(to!S("20121221T141516")) == DateTime(2012, 12, 21, 14, 15, 16)); + } + } + + + /++ + Creates a $(LREF DateTime) from a string with the format + YYYY-MM-DDTHH:MM:SS. Whitespace is stripped from the given string. + + Params: + isoExtString = A string formatted in the ISO Extended format for dates + and times. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given string is + not in the ISO Extended format or if the resulting $(LREF DateTime) + would not be valid. + +/ + static DateTime fromISOExtString(S)(in S isoExtString) @safe pure + if (isSomeString!(S)) + { + import std.algorithm.searching : countUntil; + import std.exception : enforce; + import std.format : format; + import std.string : strip; + + auto str = strip(isoExtString); + + enforce(str.length >= 15, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); + auto t = str.countUntil('T'); + + enforce(t != -1, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); + + immutable date = Date.fromISOExtString(str[0 .. t]); + immutable tod = TimeOfDay.fromISOExtString(str[t+1 .. $]); + + return DateTime(date, tod); + } + + /// + @safe unittest + { + assert(DateTime.fromISOExtString("2010-07-04T07:06:12") == + DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12))); + + assert(DateTime.fromISOExtString("1998-12-25T02:15:00") == + DateTime(Date(1998, 12, 25), TimeOfDay(2, 15, 0))); + + assert(DateTime.fromISOExtString("0000-01-05T23:09:59") == + DateTime(Date(0, 1, 5), TimeOfDay(23, 9, 59))); + + assert(DateTime.fromISOExtString("-0004-01-05T00:00:02") == + DateTime(Date(-4, 1, 5), TimeOfDay(0, 0, 2))); + + assert(DateTime.fromISOExtString(" 2010-07-04T07:06:12 ") == + DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12))); + } + + @safe unittest + { + assertThrown!DateTimeException(DateTime.fromISOExtString("")); + assertThrown!DateTimeException(DateTime.fromISOExtString("20100704000000")); + assertThrown!DateTimeException(DateTime.fromISOExtString("20100704 000000")); + assertThrown!DateTimeException(DateTime.fromISOExtString("20100704t000000")); + assertThrown!DateTimeException(DateTime.fromISOExtString("20100704T000000.")); + assertThrown!DateTimeException(DateTime.fromISOExtString("20100704T000000.0")); + + assertThrown!DateTimeException(DateTime.fromISOExtString("2010-07:0400:00:00")); + assertThrown!DateTimeException(DateTime.fromISOExtString("2010-07-04 00:00:00")); + assertThrown!DateTimeException(DateTime.fromISOExtString("2010-07-04 00:00:00")); + assertThrown!DateTimeException(DateTime.fromISOExtString("2010-07-04t00:00:00")); + assertThrown!DateTimeException(DateTime.fromISOExtString("2010-07-04T00:00:00.")); + assertThrown!DateTimeException(DateTime.fromISOExtString("2010-07-04T00:00:00.0")); + + assertThrown!DateTimeException(DateTime.fromISOExtString("2010-Jul-0400:00:00")); + assertThrown!DateTimeException(DateTime.fromISOExtString("2010-Jul-04t00:00:00")); + assertThrown!DateTimeException(DateTime.fromISOExtString("2010-Jul-04 00:00:00.")); + assertThrown!DateTimeException(DateTime.fromISOExtString("2010-Jul-04 00:00:00.0")); + + assertThrown!DateTimeException(DateTime.fromISOExtString("20101222T172201")); + assertThrown!DateTimeException(DateTime.fromISOExtString("2010-Dec-22 17:22:01")); + + assert(DateTime.fromISOExtString("2010-12-22T17:22:01") == DateTime(Date(2010, 12, 22), TimeOfDay(17, 22, 01))); + assert(DateTime.fromISOExtString("1999-07-06T12:30:33") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + assert(DateTime.fromISOExtString("-1999-07-06T12:30:33") == DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))); + assert(DateTime.fromISOExtString("+01999-07-06T12:30:33") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + assert(DateTime.fromISOExtString("1999-07-06T12:30:33 ") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + assert(DateTime.fromISOExtString(" 1999-07-06T12:30:33") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + assert(DateTime.fromISOExtString(" 1999-07-06T12:30:33 ") == DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + } + + // bug# 17801 + @safe unittest + { + import std.conv : to; + import std.meta : AliasSeq; + foreach (C; AliasSeq!(char, wchar, dchar)) + { + foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + assert(DateTime.fromISOExtString(to!S("2012-12-21T14:15:16")) == DateTime(2012, 12, 21, 14, 15, 16)); + } + } + + + /++ + Creates a $(LREF DateTime) from a string with the format + YYYY-Mon-DD HH:MM:SS. Whitespace is stripped from the given string. + + Params: + simpleString = A string formatted in the way that toSimpleString + formats dates and times. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given string is + not in the correct format or if the resulting $(LREF DateTime) + would not be valid. + +/ + static DateTime fromSimpleString(S)(in S simpleString) @safe pure + if (isSomeString!(S)) + { + import std.algorithm.searching : countUntil; + import std.exception : enforce; + import std.format : format; + import std.string : strip; + + auto str = strip(simpleString); + + enforce(str.length >= 15, new DateTimeException(format("Invalid string format: %s", simpleString))); + auto t = str.countUntil(' '); + + enforce(t != -1, new DateTimeException(format("Invalid string format: %s", simpleString))); + + immutable date = Date.fromSimpleString(str[0 .. t]); + immutable tod = TimeOfDay.fromISOExtString(str[t+1 .. $]); + + return DateTime(date, tod); + } + + /// + @safe unittest + { + assert(DateTime.fromSimpleString("2010-Jul-04 07:06:12") == + DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12))); + assert(DateTime.fromSimpleString("1998-Dec-25 02:15:00") == + DateTime(Date(1998, 12, 25), TimeOfDay(2, 15, 0))); + assert(DateTime.fromSimpleString("0000-Jan-05 23:09:59") == + DateTime(Date(0, 1, 5), TimeOfDay(23, 9, 59))); + assert(DateTime.fromSimpleString("-0004-Jan-05 00:00:02") == + DateTime(Date(-4, 1, 5), TimeOfDay(0, 0, 2))); + assert(DateTime.fromSimpleString(" 2010-Jul-04 07:06:12 ") == + DateTime(Date(2010, 7, 4), TimeOfDay(7, 6, 12))); + } + + @safe unittest + { + assertThrown!DateTimeException(DateTime.fromISOString("")); + assertThrown!DateTimeException(DateTime.fromISOString("20100704000000")); + assertThrown!DateTimeException(DateTime.fromISOString("20100704 000000")); + assertThrown!DateTimeException(DateTime.fromISOString("20100704t000000")); + assertThrown!DateTimeException(DateTime.fromISOString("20100704T000000.")); + assertThrown!DateTimeException(DateTime.fromISOString("20100704T000000.0")); + + assertThrown!DateTimeException(DateTime.fromISOString("2010-07-0400:00:00")); + assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04 00:00:00")); + assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04t00:00:00")); + assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04T00:00:00.")); + assertThrown!DateTimeException(DateTime.fromISOString("2010-07-04T00:00:00.0")); + + assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-0400:00:00")); + assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04 00:00:00")); + assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04t00:00:00")); + assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04T00:00:00")); + assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04 00:00:00.")); + assertThrown!DateTimeException(DateTime.fromISOString("2010-Jul-04 00:00:00.0")); + + assertThrown!DateTimeException(DateTime.fromSimpleString("20101222T172201")); + assertThrown!DateTimeException(DateTime.fromSimpleString("2010-12-22T172201")); + + assert(DateTime.fromSimpleString("2010-Dec-22 17:22:01") == + DateTime(Date(2010, 12, 22), TimeOfDay(17, 22, 01))); + assert(DateTime.fromSimpleString("1999-Jul-06 12:30:33") == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + assert(DateTime.fromSimpleString("-1999-Jul-06 12:30:33") == + DateTime(Date(-1999, 7, 6), TimeOfDay(12, 30, 33))); + assert(DateTime.fromSimpleString("+01999-Jul-06 12:30:33") == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + assert(DateTime.fromSimpleString("1999-Jul-06 12:30:33 ") == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + assert(DateTime.fromSimpleString(" 1999-Jul-06 12:30:33") == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + assert(DateTime.fromSimpleString(" 1999-Jul-06 12:30:33 ") == + DateTime(Date(1999, 7, 6), TimeOfDay(12, 30, 33))); + } + + // bug# 17801 + @safe unittest + { + import std.conv : to; + import std.meta : AliasSeq; + foreach (C; AliasSeq!(char, wchar, dchar)) + { + foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + assert(DateTime.fromSimpleString(to!S("2012-Dec-21 14:15:16")) == DateTime(2012, 12, 21, 14, 15, 16)); + } + } + + + /++ + Returns the $(LREF DateTime) farthest in the past which is representable + by $(LREF DateTime). + +/ + @property static DateTime min() @safe pure nothrow @nogc + out(result) + { + assert(result._date == Date.min); + assert(result._tod == TimeOfDay.min); + } + body + { + auto dt = DateTime.init; + dt._date._year = short.min; + dt._date._month = Month.jan; + dt._date._day = 1; + + return dt; + } + + @safe unittest + { + assert(DateTime.min.year < 0); + assert(DateTime.min < DateTime.max); + } + + + /++ + Returns the $(LREF DateTime) farthest in the future which is + representable by $(LREF DateTime). + +/ + @property static DateTime max() @safe pure nothrow @nogc + out(result) + { + assert(result._date == Date.max); + assert(result._tod == TimeOfDay.max); + } + body + { + auto dt = DateTime.init; + dt._date._year = short.max; + dt._date._month = Month.dec; + dt._date._day = 31; + dt._tod._hour = TimeOfDay.maxHour; + dt._tod._minute = TimeOfDay.maxMinute; + dt._tod._second = TimeOfDay.maxSecond; + + return dt; + } + + @safe unittest + { + assert(DateTime.max.year > 0); + assert(DateTime.max > DateTime.min); + } + + +private: + + /+ + Add seconds to the time of day. Negative values will subtract. If the + number of seconds overflows (or underflows), then the seconds will wrap, + increasing (or decreasing) the number of minutes accordingly. The + same goes for any larger units. + + Params: + seconds = The number of seconds to add to this $(LREF DateTime). + +/ + ref DateTime _addSeconds(long seconds) return @safe pure nothrow @nogc + { + long hnsecs = convert!("seconds", "hnsecs")(seconds); + hnsecs += convert!("hours", "hnsecs")(_tod._hour); + hnsecs += convert!("minutes", "hnsecs")(_tod._minute); + hnsecs += convert!("seconds", "hnsecs")(_tod._second); + + auto days = splitUnitsFromHNSecs!"days"(hnsecs); + + if (hnsecs < 0) + { + hnsecs += convert!("days", "hnsecs")(1); + --days; + } + + _date._addDays(days); + + immutable newHours = splitUnitsFromHNSecs!"hours"(hnsecs); + immutable newMinutes = splitUnitsFromHNSecs!"minutes"(hnsecs); + immutable newSeconds = splitUnitsFromHNSecs!"seconds"(hnsecs); + + _tod._hour = cast(ubyte) newHours; + _tod._minute = cast(ubyte) newMinutes; + _tod._second = cast(ubyte) newSeconds; + + return this; + } + + @safe unittest + { + static void testDT(DateTime orig, int seconds, in DateTime expected, size_t line = __LINE__) + { + orig._addSeconds(seconds); + assert(orig == expected); + } + + // Test A.D. + testDT(DateTime(1999, 7, 6, 12, 30, 33), 0, DateTime(1999, 7, 6, 12, 30, 33)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), 1, DateTime(1999, 7, 6, 12, 30, 34)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), 2, DateTime(1999, 7, 6, 12, 30, 35)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), 3, DateTime(1999, 7, 6, 12, 30, 36)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), 4, DateTime(1999, 7, 6, 12, 30, 37)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), 5, DateTime(1999, 7, 6, 12, 30, 38)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), 10, DateTime(1999, 7, 6, 12, 30, 43)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), 15, DateTime(1999, 7, 6, 12, 30, 48)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), 26, DateTime(1999, 7, 6, 12, 30, 59)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), 27, DateTime(1999, 7, 6, 12, 31, 0)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), 30, DateTime(1999, 7, 6, 12, 31, 3)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), 59, DateTime(1999, 7, 6, 12, 31, 32)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), 60, DateTime(1999, 7, 6, 12, 31, 33)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), 61, DateTime(1999, 7, 6, 12, 31, 34)); + + testDT(DateTime(1999, 7, 6, 12, 30, 33), 1766, DateTime(1999, 7, 6, 12, 59, 59)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), 1767, DateTime(1999, 7, 6, 13, 0, 0)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), 1768, DateTime(1999, 7, 6, 13, 0, 1)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), 2007, DateTime(1999, 7, 6, 13, 4, 0)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), 3599, DateTime(1999, 7, 6, 13, 30, 32)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), 3600, DateTime(1999, 7, 6, 13, 30, 33)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), 3601, DateTime(1999, 7, 6, 13, 30, 34)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), 7200, DateTime(1999, 7, 6, 14, 30, 33)); + testDT(DateTime(1999, 7, 6, 23, 0, 0), 432_123, DateTime(1999, 7, 11, 23, 2, 3)); + + testDT(DateTime(1999, 7, 6, 12, 30, 33), -1, DateTime(1999, 7, 6, 12, 30, 32)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), -2, DateTime(1999, 7, 6, 12, 30, 31)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), -3, DateTime(1999, 7, 6, 12, 30, 30)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), -4, DateTime(1999, 7, 6, 12, 30, 29)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), -5, DateTime(1999, 7, 6, 12, 30, 28)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), -10, DateTime(1999, 7, 6, 12, 30, 23)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), -15, DateTime(1999, 7, 6, 12, 30, 18)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), -33, DateTime(1999, 7, 6, 12, 30, 0)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), -34, DateTime(1999, 7, 6, 12, 29, 59)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), -35, DateTime(1999, 7, 6, 12, 29, 58)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), -59, DateTime(1999, 7, 6, 12, 29, 34)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), -60, DateTime(1999, 7, 6, 12, 29, 33)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), -61, DateTime(1999, 7, 6, 12, 29, 32)); + + testDT(DateTime(1999, 7, 6, 12, 30, 33), -1833, DateTime(1999, 7, 6, 12, 0, 0)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), -1834, DateTime(1999, 7, 6, 11, 59, 59)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), -3600, DateTime(1999, 7, 6, 11, 30, 33)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), -3601, DateTime(1999, 7, 6, 11, 30, 32)); + testDT(DateTime(1999, 7, 6, 12, 30, 33), -5134, DateTime(1999, 7, 6, 11, 4, 59)); + testDT(DateTime(1999, 7, 6, 23, 0, 0), -432_123, DateTime(1999, 7, 1, 22, 57, 57)); + + testDT(DateTime(1999, 7, 6, 12, 30, 0), 1, DateTime(1999, 7, 6, 12, 30, 1)); + testDT(DateTime(1999, 7, 6, 12, 30, 0), 0, DateTime(1999, 7, 6, 12, 30, 0)); + testDT(DateTime(1999, 7, 6, 12, 30, 0), -1, DateTime(1999, 7, 6, 12, 29, 59)); + + testDT(DateTime(1999, 7, 6, 12, 0, 0), 1, DateTime(1999, 7, 6, 12, 0, 1)); + testDT(DateTime(1999, 7, 6, 12, 0, 0), 0, DateTime(1999, 7, 6, 12, 0, 0)); + testDT(DateTime(1999, 7, 6, 12, 0, 0), -1, DateTime(1999, 7, 6, 11, 59, 59)); + + testDT(DateTime(1999, 7, 6, 0, 0, 0), 1, DateTime(1999, 7, 6, 0, 0, 1)); + testDT(DateTime(1999, 7, 6, 0, 0, 0), 0, DateTime(1999, 7, 6, 0, 0, 0)); + testDT(DateTime(1999, 7, 6, 0, 0, 0), -1, DateTime(1999, 7, 5, 23, 59, 59)); + + testDT(DateTime(1999, 7, 5, 23, 59, 59), 1, DateTime(1999, 7, 6, 0, 0, 0)); + testDT(DateTime(1999, 7, 5, 23, 59, 59), 0, DateTime(1999, 7, 5, 23, 59, 59)); + testDT(DateTime(1999, 7, 5, 23, 59, 59), -1, DateTime(1999, 7, 5, 23, 59, 58)); + + testDT(DateTime(1998, 12, 31, 23, 59, 59), 1, DateTime(1999, 1, 1, 0, 0, 0)); + testDT(DateTime(1998, 12, 31, 23, 59, 59), 0, DateTime(1998, 12, 31, 23, 59, 59)); + testDT(DateTime(1998, 12, 31, 23, 59, 59), -1, DateTime(1998, 12, 31, 23, 59, 58)); + + testDT(DateTime(1998, 1, 1, 0, 0, 0), 1, DateTime(1998, 1, 1, 0, 0, 1)); + testDT(DateTime(1998, 1, 1, 0, 0, 0), 0, DateTime(1998, 1, 1, 0, 0, 0)); + testDT(DateTime(1998, 1, 1, 0, 0, 0), -1, DateTime(1997, 12, 31, 23, 59, 59)); + + // Test B.C. + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 0, DateTime(-1999, 7, 6, 12, 30, 33)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 1, DateTime(-1999, 7, 6, 12, 30, 34)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 2, DateTime(-1999, 7, 6, 12, 30, 35)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 3, DateTime(-1999, 7, 6, 12, 30, 36)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 4, DateTime(-1999, 7, 6, 12, 30, 37)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 5, DateTime(-1999, 7, 6, 12, 30, 38)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 10, DateTime(-1999, 7, 6, 12, 30, 43)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 15, DateTime(-1999, 7, 6, 12, 30, 48)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 26, DateTime(-1999, 7, 6, 12, 30, 59)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 27, DateTime(-1999, 7, 6, 12, 31, 0)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 30, DateTime(-1999, 7, 6, 12, 31, 3)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 59, DateTime(-1999, 7, 6, 12, 31, 32)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 60, DateTime(-1999, 7, 6, 12, 31, 33)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 61, DateTime(-1999, 7, 6, 12, 31, 34)); + + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 1766, DateTime(-1999, 7, 6, 12, 59, 59)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 1767, DateTime(-1999, 7, 6, 13, 0, 0)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 1768, DateTime(-1999, 7, 6, 13, 0, 1)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 2007, DateTime(-1999, 7, 6, 13, 4, 0)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 3599, DateTime(-1999, 7, 6, 13, 30, 32)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 3600, DateTime(-1999, 7, 6, 13, 30, 33)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 3601, DateTime(-1999, 7, 6, 13, 30, 34)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), 7200, DateTime(-1999, 7, 6, 14, 30, 33)); + testDT(DateTime(-1999, 7, 6, 23, 0, 0), 432_123, DateTime(-1999, 7, 11, 23, 2, 3)); + + testDT(DateTime(-1999, 7, 6, 12, 30, 33), -1, DateTime(-1999, 7, 6, 12, 30, 32)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), -2, DateTime(-1999, 7, 6, 12, 30, 31)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), -3, DateTime(-1999, 7, 6, 12, 30, 30)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), -4, DateTime(-1999, 7, 6, 12, 30, 29)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), -5, DateTime(-1999, 7, 6, 12, 30, 28)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), -10, DateTime(-1999, 7, 6, 12, 30, 23)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), -15, DateTime(-1999, 7, 6, 12, 30, 18)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), -33, DateTime(-1999, 7, 6, 12, 30, 0)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), -34, DateTime(-1999, 7, 6, 12, 29, 59)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), -35, DateTime(-1999, 7, 6, 12, 29, 58)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), -59, DateTime(-1999, 7, 6, 12, 29, 34)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), -60, DateTime(-1999, 7, 6, 12, 29, 33)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), -61, DateTime(-1999, 7, 6, 12, 29, 32)); + + testDT(DateTime(-1999, 7, 6, 12, 30, 33), -1833, DateTime(-1999, 7, 6, 12, 0, 0)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), -1834, DateTime(-1999, 7, 6, 11, 59, 59)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), -3600, DateTime(-1999, 7, 6, 11, 30, 33)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), -3601, DateTime(-1999, 7, 6, 11, 30, 32)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), -5134, DateTime(-1999, 7, 6, 11, 4, 59)); + testDT(DateTime(-1999, 7, 6, 12, 30, 33), -7200, DateTime(-1999, 7, 6, 10, 30, 33)); + testDT(DateTime(-1999, 7, 6, 23, 0, 0), -432_123, DateTime(-1999, 7, 1, 22, 57, 57)); + + testDT(DateTime(-1999, 7, 6, 12, 30, 0), 1, DateTime(-1999, 7, 6, 12, 30, 1)); + testDT(DateTime(-1999, 7, 6, 12, 30, 0), 0, DateTime(-1999, 7, 6, 12, 30, 0)); + testDT(DateTime(-1999, 7, 6, 12, 30, 0), -1, DateTime(-1999, 7, 6, 12, 29, 59)); + + testDT(DateTime(-1999, 7, 6, 12, 0, 0), 1, DateTime(-1999, 7, 6, 12, 0, 1)); + testDT(DateTime(-1999, 7, 6, 12, 0, 0), 0, DateTime(-1999, 7, 6, 12, 0, 0)); + testDT(DateTime(-1999, 7, 6, 12, 0, 0), -1, DateTime(-1999, 7, 6, 11, 59, 59)); + + testDT(DateTime(-1999, 7, 6, 0, 0, 0), 1, DateTime(-1999, 7, 6, 0, 0, 1)); + testDT(DateTime(-1999, 7, 6, 0, 0, 0), 0, DateTime(-1999, 7, 6, 0, 0, 0)); + testDT(DateTime(-1999, 7, 6, 0, 0, 0), -1, DateTime(-1999, 7, 5, 23, 59, 59)); + + testDT(DateTime(-1999, 7, 5, 23, 59, 59), 1, DateTime(-1999, 7, 6, 0, 0, 0)); + testDT(DateTime(-1999, 7, 5, 23, 59, 59), 0, DateTime(-1999, 7, 5, 23, 59, 59)); + testDT(DateTime(-1999, 7, 5, 23, 59, 59), -1, DateTime(-1999, 7, 5, 23, 59, 58)); + + testDT(DateTime(-2000, 12, 31, 23, 59, 59), 1, DateTime(-1999, 1, 1, 0, 0, 0)); + testDT(DateTime(-2000, 12, 31, 23, 59, 59), 0, DateTime(-2000, 12, 31, 23, 59, 59)); + testDT(DateTime(-2000, 12, 31, 23, 59, 59), -1, DateTime(-2000, 12, 31, 23, 59, 58)); + + testDT(DateTime(-2000, 1, 1, 0, 0, 0), 1, DateTime(-2000, 1, 1, 0, 0, 1)); + testDT(DateTime(-2000, 1, 1, 0, 0, 0), 0, DateTime(-2000, 1, 1, 0, 0, 0)); + testDT(DateTime(-2000, 1, 1, 0, 0, 0), -1, DateTime(-2001, 12, 31, 23, 59, 59)); + + // Test Both + testDT(DateTime(1, 1, 1, 0, 0, 0), -1, DateTime(0, 12, 31, 23, 59, 59)); + testDT(DateTime(0, 12, 31, 23, 59, 59), 1, DateTime(1, 1, 1, 0, 0, 0)); + + testDT(DateTime(0, 1, 1, 0, 0, 0), -1, DateTime(-1, 12, 31, 23, 59, 59)); + testDT(DateTime(-1, 12, 31, 23, 59, 59), 1, DateTime(0, 1, 1, 0, 0, 0)); + + testDT(DateTime(-1, 1, 1, 11, 30, 33), 63_165_600L, DateTime(1, 1, 1, 13, 30, 33)); + testDT(DateTime(1, 1, 1, 13, 30, 33), -63_165_600L, DateTime(-1, 1, 1, 11, 30, 33)); + + testDT(DateTime(-1, 1, 1, 11, 30, 33), 63_165_617L, DateTime(1, 1, 1, 13, 30, 50)); + testDT(DateTime(1, 1, 1, 13, 30, 50), -63_165_617L, DateTime(-1, 1, 1, 11, 30, 33)); + + const cdt = DateTime(1999, 7, 6, 12, 30, 33); + immutable idt = DateTime(1999, 7, 6, 12, 30, 33); + static assert(!__traits(compiles, cdt._addSeconds(4))); + static assert(!__traits(compiles, idt._addSeconds(4))); + } + + + Date _date; + TimeOfDay _tod; +} + + +/++ + Represents a date in the + $(HTTP en.wikipedia.org/wiki/Proleptic_Gregorian_calendar, Proleptic + Gregorian Calendar) ranging from 32,768 B.C. to 32,767 A.D. Positive years + are A.D. Non-positive years are B.C. + + Year, month, and day are kept separately internally so that $(D Date) is + optimized for calendar-based operations. + + $(D Date) uses the Proleptic Gregorian Calendar, so it assumes the Gregorian + leap year calculations for its entire length. As per + $(HTTP en.wikipedia.org/wiki/ISO_8601, ISO 8601), it treats 1 B.C. as + year 0, i.e. 1 B.C. is 0, 2 B.C. is -1, etc. Use $(LREF yearBC) to use B.C. + as a positive integer with 1 B.C. being the year prior to 1 A.D. + + Year 0 is a leap year. + +/ +struct Date +{ +public: + + /++ + Throws: + $(REF DateTimeException,std,datetime,date) if the resulting + $(LREF Date) would not be valid. + + Params: + year = Year of the Gregorian Calendar. Positive values are A.D. + Non-positive values are B.C. with year 0 being the year + prior to 1 A.D. + month = Month of the year (January is 1). + day = Day of the month. + +/ + this(int year, int month, int day) @safe pure + { + enforceValid!"months"(cast(Month) month); + enforceValid!"days"(year, cast(Month) month, day); + + _year = cast(short) year; + _month = cast(Month) month; + _day = cast(ubyte) day; + } + + @safe unittest + { + import std.exception : assertNotThrown; + assert(Date(1, 1, 1) == Date.init); + + static void testDate(in Date date, int year, int month, int day) + { + assert(date._year == year); + assert(date._month == month); + assert(date._day == day); + } + + testDate(Date(1999, 1 , 1), 1999, Month.jan, 1); + testDate(Date(1999, 7 , 1), 1999, Month.jul, 1); + testDate(Date(1999, 7 , 6), 1999, Month.jul, 6); + + // Test A.D. + assertThrown!DateTimeException(Date(1, 0, 1)); + assertThrown!DateTimeException(Date(1, 1, 0)); + assertThrown!DateTimeException(Date(1999, 13, 1)); + assertThrown!DateTimeException(Date(1999, 1, 32)); + assertThrown!DateTimeException(Date(1999, 2, 29)); + assertThrown!DateTimeException(Date(2000, 2, 30)); + assertThrown!DateTimeException(Date(1999, 3, 32)); + assertThrown!DateTimeException(Date(1999, 4, 31)); + assertThrown!DateTimeException(Date(1999, 5, 32)); + assertThrown!DateTimeException(Date(1999, 6, 31)); + assertThrown!DateTimeException(Date(1999, 7, 32)); + assertThrown!DateTimeException(Date(1999, 8, 32)); + assertThrown!DateTimeException(Date(1999, 9, 31)); + assertThrown!DateTimeException(Date(1999, 10, 32)); + assertThrown!DateTimeException(Date(1999, 11, 31)); + assertThrown!DateTimeException(Date(1999, 12, 32)); + + assertNotThrown!DateTimeException(Date(1999, 1, 31)); + assertNotThrown!DateTimeException(Date(1999, 2, 28)); + assertNotThrown!DateTimeException(Date(2000, 2, 29)); + assertNotThrown!DateTimeException(Date(1999, 3, 31)); + assertNotThrown!DateTimeException(Date(1999, 4, 30)); + assertNotThrown!DateTimeException(Date(1999, 5, 31)); + assertNotThrown!DateTimeException(Date(1999, 6, 30)); + assertNotThrown!DateTimeException(Date(1999, 7, 31)); + assertNotThrown!DateTimeException(Date(1999, 8, 31)); + assertNotThrown!DateTimeException(Date(1999, 9, 30)); + assertNotThrown!DateTimeException(Date(1999, 10, 31)); + assertNotThrown!DateTimeException(Date(1999, 11, 30)); + assertNotThrown!DateTimeException(Date(1999, 12, 31)); + + // Test B.C. + assertNotThrown!DateTimeException(Date(0, 1, 1)); + assertNotThrown!DateTimeException(Date(-1, 1, 1)); + assertNotThrown!DateTimeException(Date(-1, 12, 31)); + assertNotThrown!DateTimeException(Date(-1, 2, 28)); + assertNotThrown!DateTimeException(Date(-4, 2, 29)); + + assertThrown!DateTimeException(Date(-1, 2, 29)); + assertThrown!DateTimeException(Date(-2, 2, 29)); + assertThrown!DateTimeException(Date(-3, 2, 29)); + } + + + /++ + Params: + day = The Xth day of the Gregorian Calendar that the constructed + $(LREF Date) will be for. + +/ + this(int day) @safe pure nothrow @nogc + { + if (day > 0) + { + int years = (day / daysIn400Years) * 400 + 1; + day %= daysIn400Years; + + { + immutable tempYears = day / daysIn100Years; + + if (tempYears == 4) + { + years += 300; + day -= daysIn100Years * 3; + } + else + { + years += tempYears * 100; + day %= daysIn100Years; + } + } + + years += (day / daysIn4Years) * 4; + day %= daysIn4Years; + + { + immutable tempYears = day / daysInYear; + + if (tempYears == 4) + { + years += 3; + day -= daysInYear * 3; + } + else + { + years += tempYears; + day %= daysInYear; + } + } + + if (day == 0) + { + _year = cast(short)(years - 1); + _month = Month.dec; + _day = 31; + } + else + { + _year = cast(short) years; + + setDayOfYear(day); + } + } + else if (day <= 0 && -day < daysInLeapYear) + { + _year = 0; + + setDayOfYear(daysInLeapYear + day); + } + else + { + day += daysInLeapYear - 1; + int years = (day / daysIn400Years) * 400 - 1; + day %= daysIn400Years; + + { + immutable tempYears = day / daysIn100Years; + + if (tempYears == -4) + { + years -= 300; + day += daysIn100Years * 3; + } + else + { + years += tempYears * 100; + day %= daysIn100Years; + } + } + + years += (day / daysIn4Years) * 4; + day %= daysIn4Years; + + { + immutable tempYears = day / daysInYear; + + if (tempYears == -4) + { + years -= 3; + day += daysInYear * 3; + } + else + { + years += tempYears; + day %= daysInYear; + } + } + + if (day == 0) + { + _year = cast(short)(years + 1); + _month = Month.jan; + _day = 1; + } + else + { + _year = cast(short) years; + immutable newDoY = (yearIsLeapYear(_year) ? daysInLeapYear : daysInYear) + day + 1; + + setDayOfYear(newDoY); + } + } + } + + @safe unittest + { + import std.range : chain; + + // Test A.D. + foreach (gd; chain(testGregDaysBC, testGregDaysAD)) + assert(Date(gd.day) == gd.date); + } + + + /++ + Compares this $(LREF Date) with the given $(LREF Date). + + Returns: + $(BOOKTABLE, + $(TR $(TD this < rhs) $(TD < 0)) + $(TR $(TD this == rhs) $(TD 0)) + $(TR $(TD this > rhs) $(TD > 0)) + ) + +/ + int opCmp(in Date rhs) const @safe pure nothrow @nogc + { + if (_year < rhs._year) + return -1; + if (_year > rhs._year) + return 1; + + if (_month < rhs._month) + return -1; + if (_month > rhs._month) + return 1; + + if (_day < rhs._day) + return -1; + if (_day > rhs._day) + return 1; + + return 0; + } + + @safe unittest + { + // Test A.D. + assert(Date(1, 1, 1).opCmp(Date.init) == 0); + + assert(Date(1999, 1, 1).opCmp(Date(1999, 1, 1)) == 0); + assert(Date(1, 7, 1).opCmp(Date(1, 7, 1)) == 0); + assert(Date(1, 1, 6).opCmp(Date(1, 1, 6)) == 0); + + assert(Date(1999, 7, 1).opCmp(Date(1999, 7, 1)) == 0); + assert(Date(1999, 7, 6).opCmp(Date(1999, 7, 6)) == 0); + + assert(Date(1, 7, 6).opCmp(Date(1, 7, 6)) == 0); + + assert(Date(1999, 7, 6).opCmp(Date(2000, 7, 6)) < 0); + assert(Date(2000, 7, 6).opCmp(Date(1999, 7, 6)) > 0); + assert(Date(1999, 7, 6).opCmp(Date(1999, 8, 6)) < 0); + assert(Date(1999, 8, 6).opCmp(Date(1999, 7, 6)) > 0); + assert(Date(1999, 7, 6).opCmp(Date(1999, 7, 7)) < 0); + assert(Date(1999, 7, 7).opCmp(Date(1999, 7, 6)) > 0); + + assert(Date(1999, 8, 7).opCmp(Date(2000, 7, 6)) < 0); + assert(Date(2000, 8, 6).opCmp(Date(1999, 7, 7)) > 0); + assert(Date(1999, 7, 7).opCmp(Date(2000, 7, 6)) < 0); + assert(Date(2000, 7, 6).opCmp(Date(1999, 7, 7)) > 0); + assert(Date(1999, 7, 7).opCmp(Date(1999, 8, 6)) < 0); + assert(Date(1999, 8, 6).opCmp(Date(1999, 7, 7)) > 0); + + // Test B.C. + assert(Date(0, 1, 1).opCmp(Date(0, 1, 1)) == 0); + assert(Date(-1, 1, 1).opCmp(Date(-1, 1, 1)) == 0); + assert(Date(-1, 7, 1).opCmp(Date(-1, 7, 1)) == 0); + assert(Date(-1, 1, 6).opCmp(Date(-1, 1, 6)) == 0); + + assert(Date(-1999, 7, 1).opCmp(Date(-1999, 7, 1)) == 0); + assert(Date(-1999, 7, 6).opCmp(Date(-1999, 7, 6)) == 0); + + assert(Date(-1, 7, 6).opCmp(Date(-1, 7, 6)) == 0); + + assert(Date(-2000, 7, 6).opCmp(Date(-1999, 7, 6)) < 0); + assert(Date(-1999, 7, 6).opCmp(Date(-2000, 7, 6)) > 0); + assert(Date(-1999, 7, 6).opCmp(Date(-1999, 8, 6)) < 0); + assert(Date(-1999, 8, 6).opCmp(Date(-1999, 7, 6)) > 0); + assert(Date(-1999, 7, 6).opCmp(Date(-1999, 7, 7)) < 0); + assert(Date(-1999, 7, 7).opCmp(Date(-1999, 7, 6)) > 0); + + assert(Date(-2000, 8, 6).opCmp(Date(-1999, 7, 7)) < 0); + assert(Date(-1999, 8, 7).opCmp(Date(-2000, 7, 6)) > 0); + assert(Date(-2000, 7, 6).opCmp(Date(-1999, 7, 7)) < 0); + assert(Date(-1999, 7, 7).opCmp(Date(-2000, 7, 6)) > 0); + assert(Date(-1999, 7, 7).opCmp(Date(-1999, 8, 6)) < 0); + assert(Date(-1999, 8, 6).opCmp(Date(-1999, 7, 7)) > 0); + + // Test Both + assert(Date(-1999, 7, 6).opCmp(Date(1999, 7, 6)) < 0); + assert(Date(1999, 7, 6).opCmp(Date(-1999, 7, 6)) > 0); + + assert(Date(-1999, 8, 6).opCmp(Date(1999, 7, 6)) < 0); + assert(Date(1999, 7, 6).opCmp(Date(-1999, 8, 6)) > 0); + + assert(Date(-1999, 7, 7).opCmp(Date(1999, 7, 6)) < 0); + assert(Date(1999, 7, 6).opCmp(Date(-1999, 7, 7)) > 0); + + assert(Date(-1999, 8, 7).opCmp(Date(1999, 7, 6)) < 0); + assert(Date(1999, 7, 6).opCmp(Date(-1999, 8, 7)) > 0); + + assert(Date(-1999, 8, 6).opCmp(Date(1999, 6, 6)) < 0); + assert(Date(1999, 6, 8).opCmp(Date(-1999, 7, 6)) > 0); + + auto date = Date(1999, 7, 6); + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(date.opCmp(date) == 0); + assert(date.opCmp(cdate) == 0); + assert(date.opCmp(idate) == 0); + assert(cdate.opCmp(date) == 0); + assert(cdate.opCmp(cdate) == 0); + assert(cdate.opCmp(idate) == 0); + assert(idate.opCmp(date) == 0); + assert(idate.opCmp(cdate) == 0); + assert(idate.opCmp(idate) == 0); + } + + + /++ + Year of the Gregorian Calendar. Positive numbers are A.D. Non-positive + are B.C. + +/ + @property short year() const @safe pure nothrow @nogc + { + return _year; + } + + /// + @safe unittest + { + assert(Date(1999, 7, 6).year == 1999); + assert(Date(2010, 10, 4).year == 2010); + assert(Date(-7, 4, 5).year == -7); + } + + @safe unittest + { + assert(Date.init.year == 1); + assert(Date(1999, 7, 6).year == 1999); + assert(Date(-1999, 7, 6).year == -1999); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(cdate.year == 1999); + assert(idate.year == 1999); + } + + /++ + Year of the Gregorian Calendar. Positive numbers are A.D. Non-positive + are B.C. + + Params: + year = The year to set this Date's year to. + + Throws: + $(REF DateTimeException,std,datetime,date) if the new year is not + a leap year and the resulting date would be on February 29th. + +/ + @property void year(int year) @safe pure + { + enforceValid!"days"(year, _month, _day); + _year = cast(short) year; + } + + /// + @safe unittest + { + assert(Date(1999, 7, 6).year == 1999); + assert(Date(2010, 10, 4).year == 2010); + assert(Date(-7, 4, 5).year == -7); + } + + @safe unittest + { + static void testDateInvalid(Date date, int year) + { + date.year = year; + } + + static void testDate(Date date, int year, in Date expected) + { + date.year = year; + assert(date == expected); + } + + assertThrown!DateTimeException(testDateInvalid(Date(4, 2, 29), 1)); + + testDate(Date(1, 1, 1), 1999, Date(1999, 1, 1)); + testDate(Date(1, 1, 1), 0, Date(0, 1, 1)); + testDate(Date(1, 1, 1), -1999, Date(-1999, 1, 1)); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + static assert(!__traits(compiles, cdate.year = 1999)); + static assert(!__traits(compiles, idate.year = 1999)); + } + + + /++ + Year B.C. of the Gregorian Calendar counting year 0 as 1 B.C. + + Throws: + $(REF DateTimeException,std,datetime,date) if $(D isAD) is true. + +/ + @property ushort yearBC() const @safe pure + { + import std.format : format; + + if (isAD) + throw new DateTimeException(format("Year %s is A.D.", _year)); + return cast(ushort)((_year * -1) + 1); + } + + /// + @safe unittest + { + assert(Date(0, 1, 1).yearBC == 1); + assert(Date(-1, 1, 1).yearBC == 2); + assert(Date(-100, 1, 1).yearBC == 101); + } + + @safe unittest + { + assertThrown!DateTimeException((in Date date){date.yearBC;}(Date(1, 1, 1))); + + auto date = Date(0, 7, 6); + const cdate = Date(0, 7, 6); + immutable idate = Date(0, 7, 6); + assert(date.yearBC == 1); + assert(cdate.yearBC == 1); + assert(idate.yearBC == 1); + } + + + /++ + Year B.C. of the Gregorian Calendar counting year 0 as 1 B.C. + + Params: + year = The year B.C. to set this $(LREF Date)'s year to. + + Throws: + $(REF DateTimeException,std,datetime,date) if a non-positive value + is given. + +/ + @property void yearBC(int year) @safe pure + { + if (year <= 0) + throw new DateTimeException("The given year is not a year B.C."); + _year = cast(short)((year - 1) * -1); + } + + /// + @safe unittest + { + auto date = Date(2010, 1, 1); + date.yearBC = 1; + assert(date == Date(0, 1, 1)); + + date.yearBC = 10; + assert(date == Date(-9, 1, 1)); + } + + @safe unittest + { + assertThrown!DateTimeException((Date date){date.yearBC = -1;}(Date(1, 1, 1))); + + auto date = Date(0, 7, 6); + const cdate = Date(0, 7, 6); + immutable idate = Date(0, 7, 6); + date.yearBC = 7; + assert(date.yearBC == 7); + static assert(!__traits(compiles, cdate.yearBC = 7)); + static assert(!__traits(compiles, idate.yearBC = 7)); + } + + + /++ + Month of a Gregorian Year. + +/ + @property Month month() const @safe pure nothrow @nogc + { + return _month; + } + + /// + @safe unittest + { + assert(Date(1999, 7, 6).month == 7); + assert(Date(2010, 10, 4).month == 10); + assert(Date(-7, 4, 5).month == 4); + } + + @safe unittest + { + assert(Date.init.month == 1); + assert(Date(1999, 7, 6).month == 7); + assert(Date(-1999, 7, 6).month == 7); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(cdate.month == 7); + assert(idate.month == 7); + } + + /++ + Month of a Gregorian Year. + + Params: + month = The month to set this $(LREF Date)'s month to. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given month is + not a valid month or if the current day would not be valid in the + given month. + +/ + @property void month(Month month) @safe pure + { + enforceValid!"months"(month); + enforceValid!"days"(_year, month, _day); + _month = cast(Month) month; + } + + @safe unittest + { + static void testDate(Date date, Month month, in Date expected = Date.init) + { + date.month = month; + assert(expected != Date.init); + assert(date == expected); + } + + assertThrown!DateTimeException(testDate(Date(1, 1, 1), cast(Month) 0)); + assertThrown!DateTimeException(testDate(Date(1, 1, 1), cast(Month) 13)); + assertThrown!DateTimeException(testDate(Date(1, 1, 29), cast(Month) 2)); + assertThrown!DateTimeException(testDate(Date(0, 1, 30), cast(Month) 2)); + + testDate(Date(1, 1, 1), cast(Month) 7, Date(1, 7, 1)); + testDate(Date(-1, 1, 1), cast(Month) 7, Date(-1, 7, 1)); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + static assert(!__traits(compiles, cdate.month = 7)); + static assert(!__traits(compiles, idate.month = 7)); + } + + + /++ + Day of a Gregorian Month. + +/ + @property ubyte day() const @safe pure nothrow @nogc + { + return _day; + } + + /// + @safe unittest + { + assert(Date(1999, 7, 6).day == 6); + assert(Date(2010, 10, 4).day == 4); + assert(Date(-7, 4, 5).day == 5); + } + + @safe unittest + { + import std.format : format; + import std.range : chain; + + static void test(Date date, int expected) + { + assert(date.day == expected, format("Value given: %s", date)); + } + + foreach (year; chain(testYearsBC, testYearsAD)) + { + foreach (md; testMonthDays) + test(Date(year, md.month, md.day), md.day); + } + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(cdate.day == 6); + assert(idate.day == 6); + } + + /++ + Day of a Gregorian Month. + + Params: + day = The day of the month to set this $(LREF Date)'s day to. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given day is not + a valid day of the current month. + +/ + @property void day(int day) @safe pure + { + enforceValid!"days"(_year, _month, day); + _day = cast(ubyte) day; + } + + @safe unittest + { + import std.exception : assertNotThrown; + + static void testDate(Date date, int day) + { + date.day = day; + } + + // Test A.D. + assertThrown!DateTimeException(testDate(Date(1, 1, 1), 0)); + assertThrown!DateTimeException(testDate(Date(1, 1, 1), 32)); + assertThrown!DateTimeException(testDate(Date(1, 2, 1), 29)); + assertThrown!DateTimeException(testDate(Date(4, 2, 1), 30)); + assertThrown!DateTimeException(testDate(Date(1, 3, 1), 32)); + assertThrown!DateTimeException(testDate(Date(1, 4, 1), 31)); + assertThrown!DateTimeException(testDate(Date(1, 5, 1), 32)); + assertThrown!DateTimeException(testDate(Date(1, 6, 1), 31)); + assertThrown!DateTimeException(testDate(Date(1, 7, 1), 32)); + assertThrown!DateTimeException(testDate(Date(1, 8, 1), 32)); + assertThrown!DateTimeException(testDate(Date(1, 9, 1), 31)); + assertThrown!DateTimeException(testDate(Date(1, 10, 1), 32)); + assertThrown!DateTimeException(testDate(Date(1, 11, 1), 31)); + assertThrown!DateTimeException(testDate(Date(1, 12, 1), 32)); + + assertNotThrown!DateTimeException(testDate(Date(1, 1, 1), 31)); + assertNotThrown!DateTimeException(testDate(Date(1, 2, 1), 28)); + assertNotThrown!DateTimeException(testDate(Date(4, 2, 1), 29)); + assertNotThrown!DateTimeException(testDate(Date(1, 3, 1), 31)); + assertNotThrown!DateTimeException(testDate(Date(1, 4, 1), 30)); + assertNotThrown!DateTimeException(testDate(Date(1, 5, 1), 31)); + assertNotThrown!DateTimeException(testDate(Date(1, 6, 1), 30)); + assertNotThrown!DateTimeException(testDate(Date(1, 7, 1), 31)); + assertNotThrown!DateTimeException(testDate(Date(1, 8, 1), 31)); + assertNotThrown!DateTimeException(testDate(Date(1, 9, 1), 30)); + assertNotThrown!DateTimeException(testDate(Date(1, 10, 1), 31)); + assertNotThrown!DateTimeException(testDate(Date(1, 11, 1), 30)); + assertNotThrown!DateTimeException(testDate(Date(1, 12, 1), 31)); + + { + auto date = Date(1, 1, 1); + date.day = 6; + assert(date == Date(1, 1, 6)); + } + + // Test B.C. + assertThrown!DateTimeException(testDate(Date(-1, 1, 1), 0)); + assertThrown!DateTimeException(testDate(Date(-1, 1, 1), 32)); + assertThrown!DateTimeException(testDate(Date(-1, 2, 1), 29)); + assertThrown!DateTimeException(testDate(Date(0, 2, 1), 30)); + assertThrown!DateTimeException(testDate(Date(-1, 3, 1), 32)); + assertThrown!DateTimeException(testDate(Date(-1, 4, 1), 31)); + assertThrown!DateTimeException(testDate(Date(-1, 5, 1), 32)); + assertThrown!DateTimeException(testDate(Date(-1, 6, 1), 31)); + assertThrown!DateTimeException(testDate(Date(-1, 7, 1), 32)); + assertThrown!DateTimeException(testDate(Date(-1, 8, 1), 32)); + assertThrown!DateTimeException(testDate(Date(-1, 9, 1), 31)); + assertThrown!DateTimeException(testDate(Date(-1, 10, 1), 32)); + assertThrown!DateTimeException(testDate(Date(-1, 11, 1), 31)); + assertThrown!DateTimeException(testDate(Date(-1, 12, 1), 32)); + + assertNotThrown!DateTimeException(testDate(Date(-1, 1, 1), 31)); + assertNotThrown!DateTimeException(testDate(Date(-1, 2, 1), 28)); + assertNotThrown!DateTimeException(testDate(Date(0, 2, 1), 29)); + assertNotThrown!DateTimeException(testDate(Date(-1, 3, 1), 31)); + assertNotThrown!DateTimeException(testDate(Date(-1, 4, 1), 30)); + assertNotThrown!DateTimeException(testDate(Date(-1, 5, 1), 31)); + assertNotThrown!DateTimeException(testDate(Date(-1, 6, 1), 30)); + assertNotThrown!DateTimeException(testDate(Date(-1, 7, 1), 31)); + assertNotThrown!DateTimeException(testDate(Date(-1, 8, 1), 31)); + assertNotThrown!DateTimeException(testDate(Date(-1, 9, 1), 30)); + assertNotThrown!DateTimeException(testDate(Date(-1, 10, 1), 31)); + assertNotThrown!DateTimeException(testDate(Date(-1, 11, 1), 30)); + assertNotThrown!DateTimeException(testDate(Date(-1, 12, 1), 31)); + + { + auto date = Date(-1, 1, 1); + date.day = 6; + assert(date == Date(-1, 1, 6)); + } + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + static assert(!__traits(compiles, cdate.day = 6)); + static assert(!__traits(compiles, idate.day = 6)); + } + + + /++ + Adds the given number of years or months to this $(LREF Date). A + negative number will subtract. + + Note that if day overflow is allowed, and the date with the adjusted + year/month overflows the number of days in the new month, then the month + will be incremented by one, and the day set to the number of days + overflowed. (e.g. if the day were 31 and the new month were June, then + the month would be incremented to July, and the new day would be 1). If + day overflow is not allowed, then the day will be set to the last valid + day in the month (e.g. June 31st would become June 30th). + + Params: + units = The type of units to add ("years" or "months"). + value = The number of months or years to add to this + $(LREF Date). + allowOverflow = Whether the day should be allowed to overflow, + causing the month to increment. + +/ + @safe pure nothrow @nogc + ref Date add(string units)(long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) + if (units == "years") + { + _year += value; + + if (_month == Month.feb && _day == 29 && !yearIsLeapYear(_year)) + { + if (allowOverflow == AllowDayOverflow.yes) + { + _month = Month.mar; + _day = 1; + } + else + _day = 28; + } + + return this; + } + + /// + @safe unittest + { + auto d1 = Date(2010, 1, 1); + d1.add!"months"(11); + assert(d1 == Date(2010, 12, 1)); + + auto d2 = Date(2010, 1, 1); + d2.add!"months"(-11); + assert(d2 == Date(2009, 2, 1)); + + auto d3 = Date(2000, 2, 29); + d3.add!"years"(1); + assert(d3 == Date(2001, 3, 1)); + + auto d4 = Date(2000, 2, 29); + d4.add!"years"(1, AllowDayOverflow.no); + assert(d4 == Date(2001, 2, 28)); + } + + // Test add!"years"() with AllowDayOverflow.yes + @safe unittest + { + // Test A.D. + { + auto date = Date(1999, 7, 6); + date.add!"years"(7); + assert(date == Date(2006, 7, 6)); + date.add!"years"(-9); + assert(date == Date(1997, 7, 6)); + } + + { + auto date = Date(1999, 2, 28); + date.add!"years"(1); + assert(date == Date(2000, 2, 28)); + } + + { + auto date = Date(2000, 2, 29); + date.add!"years"(-1); + assert(date == Date(1999, 3, 1)); + } + + // Test B.C. + { + auto date = Date(-1999, 7, 6); + date.add!"years"(-7); + assert(date == Date(-2006, 7, 6)); + date.add!"years"(9); + assert(date == Date(-1997, 7, 6)); + } + + { + auto date = Date(-1999, 2, 28); + date.add!"years"(-1); + assert(date == Date(-2000, 2, 28)); + } + + { + auto date = Date(-2000, 2, 29); + date.add!"years"(1); + assert(date == Date(-1999, 3, 1)); + } + + // Test Both + { + auto date = Date(4, 7, 6); + date.add!"years"(-5); + assert(date == Date(-1, 7, 6)); + date.add!"years"(5); + assert(date == Date(4, 7, 6)); + } + + { + auto date = Date(-4, 7, 6); + date.add!"years"(5); + assert(date == Date(1, 7, 6)); + date.add!"years"(-5); + assert(date == Date(-4, 7, 6)); + } + + { + auto date = Date(4, 7, 6); + date.add!"years"(-8); + assert(date == Date(-4, 7, 6)); + date.add!"years"(8); + assert(date == Date(4, 7, 6)); + } + + { + auto date = Date(-4, 7, 6); + date.add!"years"(8); + assert(date == Date(4, 7, 6)); + date.add!"years"(-8); + assert(date == Date(-4, 7, 6)); + } + + { + auto date = Date(-4, 2, 29); + date.add!"years"(5); + assert(date == Date(1, 3, 1)); + } + + { + auto date = Date(4, 2, 29); + date.add!"years"(-5); + assert(date == Date(-1, 3, 1)); + } + + { + auto date = Date(4, 2, 29); + date.add!"years"(-5).add!"years"(7); + assert(date == Date(6, 3, 1)); + } + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + static assert(!__traits(compiles, cdate.add!"years"(7))); + static assert(!__traits(compiles, idate.add!"years"(7))); + } + + // Test add!"years"() with AllowDayOverflow.no + @safe unittest + { + // Test A.D. + { + auto date = Date(1999, 7, 6); + date.add!"years"(7, AllowDayOverflow.no); + assert(date == Date(2006, 7, 6)); + date.add!"years"(-9, AllowDayOverflow.no); + assert(date == Date(1997, 7, 6)); + } + + { + auto date = Date(1999, 2, 28); + date.add!"years"(1, AllowDayOverflow.no); + assert(date == Date(2000, 2, 28)); + } + + { + auto date = Date(2000, 2, 29); + date.add!"years"(-1, AllowDayOverflow.no); + assert(date == Date(1999, 2, 28)); + } + + // Test B.C. + { + auto date = Date(-1999, 7, 6); + date.add!"years"(-7, AllowDayOverflow.no); + assert(date == Date(-2006, 7, 6)); + date.add!"years"(9, AllowDayOverflow.no); + assert(date == Date(-1997, 7, 6)); + } + + { + auto date = Date(-1999, 2, 28); + date.add!"years"(-1, AllowDayOverflow.no); + assert(date == Date(-2000, 2, 28)); + } + + { + auto date = Date(-2000, 2, 29); + date.add!"years"(1, AllowDayOverflow.no); + assert(date == Date(-1999, 2, 28)); + } + + // Test Both + { + auto date = Date(4, 7, 6); + date.add!"years"(-5, AllowDayOverflow.no); + assert(date == Date(-1, 7, 6)); + date.add!"years"(5, AllowDayOverflow.no); + assert(date == Date(4, 7, 6)); + } + + { + auto date = Date(-4, 7, 6); + date.add!"years"(5, AllowDayOverflow.no); + assert(date == Date(1, 7, 6)); + date.add!"years"(-5, AllowDayOverflow.no); + assert(date == Date(-4, 7, 6)); + } + + { + auto date = Date(4, 7, 6); + date.add!"years"(-8, AllowDayOverflow.no); + assert(date == Date(-4, 7, 6)); + date.add!"years"(8, AllowDayOverflow.no); + assert(date == Date(4, 7, 6)); + } + + { + auto date = Date(-4, 7, 6); + date.add!"years"(8, AllowDayOverflow.no); + assert(date == Date(4, 7, 6)); + date.add!"years"(-8, AllowDayOverflow.no); + assert(date == Date(-4, 7, 6)); + } + + { + auto date = Date(-4, 2, 29); + date.add!"years"(5, AllowDayOverflow.no); + assert(date == Date(1, 2, 28)); + } + + { + auto date = Date(4, 2, 29); + date.add!"years"(-5, AllowDayOverflow.no); + assert(date == Date(-1, 2, 28)); + } + + { + auto date = Date(4, 2, 29); + date.add!"years"(-5, AllowDayOverflow.no).add!"years"(7, AllowDayOverflow.no); + assert(date == Date(6, 2, 28)); + } + } + + + // Shares documentation with "years" version. + @safe pure nothrow @nogc + ref Date add(string units)(long months, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) + if (units == "months") + { + auto years = months / 12; + months %= 12; + auto newMonth = _month + months; + + if (months < 0) + { + if (newMonth < 1) + { + newMonth += 12; + --years; + } + } + else if (newMonth > 12) + { + newMonth -= 12; + ++years; + } + + _year += years; + _month = cast(Month) newMonth; + + immutable currMaxDay = maxDay(_year, _month); + immutable overflow = _day - currMaxDay; + + if (overflow > 0) + { + if (allowOverflow == AllowDayOverflow.yes) + { + ++_month; + _day = cast(ubyte) overflow; + } + else + _day = cast(ubyte) currMaxDay; + } + + return this; + } + + // Test add!"months"() with AllowDayOverflow.yes + @safe unittest + { + // Test A.D. + { + auto date = Date(1999, 7, 6); + date.add!"months"(3); + assert(date == Date(1999, 10, 6)); + date.add!"months"(-4); + assert(date == Date(1999, 6, 6)); + } + + { + auto date = Date(1999, 7, 6); + date.add!"months"(6); + assert(date == Date(2000, 1, 6)); + date.add!"months"(-6); + assert(date == Date(1999, 7, 6)); + } + + { + auto date = Date(1999, 7, 6); + date.add!"months"(27); + assert(date == Date(2001, 10, 6)); + date.add!"months"(-28); + assert(date == Date(1999, 6, 6)); + } + + { + auto date = Date(1999, 5, 31); + date.add!"months"(1); + assert(date == Date(1999, 7, 1)); + } + + { + auto date = Date(1999, 5, 31); + date.add!"months"(-1); + assert(date == Date(1999, 5, 1)); + } + + { + auto date = Date(1999, 2, 28); + date.add!"months"(12); + assert(date == Date(2000, 2, 28)); + } + + { + auto date = Date(2000, 2, 29); + date.add!"months"(12); + assert(date == Date(2001, 3, 1)); + } + + { + auto date = Date(1999, 7, 31); + date.add!"months"(1); + assert(date == Date(1999, 8, 31)); + date.add!"months"(1); + assert(date == Date(1999, 10, 1)); + } + + { + auto date = Date(1998, 8, 31); + date.add!"months"(13); + assert(date == Date(1999, 10, 1)); + date.add!"months"(-13); + assert(date == Date(1998, 9, 1)); + } + + { + auto date = Date(1997, 12, 31); + date.add!"months"(13); + assert(date == Date(1999, 1, 31)); + date.add!"months"(-13); + assert(date == Date(1997, 12, 31)); + } + + { + auto date = Date(1997, 12, 31); + date.add!"months"(14); + assert(date == Date(1999, 3, 3)); + date.add!"months"(-14); + assert(date == Date(1998, 1, 3)); + } + + { + auto date = Date(1998, 12, 31); + date.add!"months"(14); + assert(date == Date(2000, 3, 2)); + date.add!"months"(-14); + assert(date == Date(1999, 1, 2)); + } + + { + auto date = Date(1999, 12, 31); + date.add!"months"(14); + assert(date == Date(2001, 3, 3)); + date.add!"months"(-14); + assert(date == Date(2000, 1, 3)); + } + + // Test B.C. + { + auto date = Date(-1999, 7, 6); + date.add!"months"(3); + assert(date == Date(-1999, 10, 6)); + date.add!"months"(-4); + assert(date == Date(-1999, 6, 6)); + } + + { + auto date = Date(-1999, 7, 6); + date.add!"months"(6); + assert(date == Date(-1998, 1, 6)); + date.add!"months"(-6); + assert(date == Date(-1999, 7, 6)); + } + + { + auto date = Date(-1999, 7, 6); + date.add!"months"(-27); + assert(date == Date(-2001, 4, 6)); + date.add!"months"(28); + assert(date == Date(-1999, 8, 6)); + } + + { + auto date = Date(-1999, 5, 31); + date.add!"months"(1); + assert(date == Date(-1999, 7, 1)); + } + + { + auto date = Date(-1999, 5, 31); + date.add!"months"(-1); + assert(date == Date(-1999, 5, 1)); + } + + { + auto date = Date(-1999, 2, 28); + date.add!"months"(-12); + assert(date == Date(-2000, 2, 28)); + } + + { + auto date = Date(-2000, 2, 29); + date.add!"months"(-12); + assert(date == Date(-2001, 3, 1)); + } + + { + auto date = Date(-1999, 7, 31); + date.add!"months"(1); + assert(date == Date(-1999, 8, 31)); + date.add!"months"(1); + assert(date == Date(-1999, 10, 1)); + } + + { + auto date = Date(-1998, 8, 31); + date.add!"months"(13); + assert(date == Date(-1997, 10, 1)); + date.add!"months"(-13); + assert(date == Date(-1998, 9, 1)); + } + + { + auto date = Date(-1997, 12, 31); + date.add!"months"(13); + assert(date == Date(-1995, 1, 31)); + date.add!"months"(-13); + assert(date == Date(-1997, 12, 31)); + } + + { + auto date = Date(-1997, 12, 31); + date.add!"months"(14); + assert(date == Date(-1995, 3, 3)); + date.add!"months"(-14); + assert(date == Date(-1996, 1, 3)); + } + + { + auto date = Date(-2002, 12, 31); + date.add!"months"(14); + assert(date == Date(-2000, 3, 2)); + date.add!"months"(-14); + assert(date == Date(-2001, 1, 2)); + } + + { + auto date = Date(-2001, 12, 31); + date.add!"months"(14); + assert(date == Date(-1999, 3, 3)); + date.add!"months"(-14); + assert(date == Date(-2000, 1, 3)); + } + + // Test Both + { + auto date = Date(1, 1, 1); + date.add!"months"(-1); + assert(date == Date(0, 12, 1)); + date.add!"months"(1); + assert(date == Date(1, 1, 1)); + } + + { + auto date = Date(4, 1, 1); + date.add!"months"(-48); + assert(date == Date(0, 1, 1)); + date.add!"months"(48); + assert(date == Date(4, 1, 1)); + } + + { + auto date = Date(4, 3, 31); + date.add!"months"(-49); + assert(date == Date(0, 3, 2)); + date.add!"months"(49); + assert(date == Date(4, 4, 2)); + } + + { + auto date = Date(4, 3, 31); + date.add!"months"(-85); + assert(date == Date(-3, 3, 3)); + date.add!"months"(85); + assert(date == Date(4, 4, 3)); + } + + { + auto date = Date(-3, 3, 31); + date.add!"months"(85).add!"months"(-83); + assert(date == Date(-3, 6, 1)); + } + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + static assert(!__traits(compiles, cdate.add!"months"(3))); + static assert(!__traits(compiles, idate.add!"months"(3))); + } + + // Test add!"months"() with AllowDayOverflow.no + @safe unittest + { + // Test A.D. + { + auto date = Date(1999, 7, 6); + date.add!"months"(3, AllowDayOverflow.no); + assert(date == Date(1999, 10, 6)); + date.add!"months"(-4, AllowDayOverflow.no); + assert(date == Date(1999, 6, 6)); + } + + { + auto date = Date(1999, 7, 6); + date.add!"months"(6, AllowDayOverflow.no); + assert(date == Date(2000, 1, 6)); + date.add!"months"(-6, AllowDayOverflow.no); + assert(date == Date(1999, 7, 6)); + } + + { + auto date = Date(1999, 7, 6); + date.add!"months"(27, AllowDayOverflow.no); + assert(date == Date(2001, 10, 6)); + date.add!"months"(-28, AllowDayOverflow.no); + assert(date == Date(1999, 6, 6)); + } + + { + auto date = Date(1999, 5, 31); + date.add!"months"(1, AllowDayOverflow.no); + assert(date == Date(1999, 6, 30)); + } + + { + auto date = Date(1999, 5, 31); + date.add!"months"(-1, AllowDayOverflow.no); + assert(date == Date(1999, 4, 30)); + } + + { + auto date = Date(1999, 2, 28); + date.add!"months"(12, AllowDayOverflow.no); + assert(date == Date(2000, 2, 28)); + } + + { + auto date = Date(2000, 2, 29); + date.add!"months"(12, AllowDayOverflow.no); + assert(date == Date(2001, 2, 28)); + } + + { + auto date = Date(1999, 7, 31); + date.add!"months"(1, AllowDayOverflow.no); + assert(date == Date(1999, 8, 31)); + date.add!"months"(1, AllowDayOverflow.no); + assert(date == Date(1999, 9, 30)); + } + + { + auto date = Date(1998, 8, 31); + date.add!"months"(13, AllowDayOverflow.no); + assert(date == Date(1999, 9, 30)); + date.add!"months"(-13, AllowDayOverflow.no); + assert(date == Date(1998, 8, 30)); + } + + { + auto date = Date(1997, 12, 31); + date.add!"months"(13, AllowDayOverflow.no); + assert(date == Date(1999, 1, 31)); + date.add!"months"(-13, AllowDayOverflow.no); + assert(date == Date(1997, 12, 31)); + } + + { + auto date = Date(1997, 12, 31); + date.add!"months"(14, AllowDayOverflow.no); + assert(date == Date(1999, 2, 28)); + date.add!"months"(-14, AllowDayOverflow.no); + assert(date == Date(1997, 12, 28)); + } + + { + auto date = Date(1998, 12, 31); + date.add!"months"(14, AllowDayOverflow.no); + assert(date == Date(2000, 2, 29)); + date.add!"months"(-14, AllowDayOverflow.no); + assert(date == Date(1998, 12, 29)); + } + + { + auto date = Date(1999, 12, 31); + date.add!"months"(14, AllowDayOverflow.no); + assert(date == Date(2001, 2, 28)); + date.add!"months"(-14, AllowDayOverflow.no); + assert(date == Date(1999, 12, 28)); + } + + // Test B.C. + { + auto date = Date(-1999, 7, 6); + date.add!"months"(3, AllowDayOverflow.no); + assert(date == Date(-1999, 10, 6)); + date.add!"months"(-4, AllowDayOverflow.no); + assert(date == Date(-1999, 6, 6)); + } + + { + auto date = Date(-1999, 7, 6); + date.add!"months"(6, AllowDayOverflow.no); + assert(date == Date(-1998, 1, 6)); + date.add!"months"(-6, AllowDayOverflow.no); + assert(date == Date(-1999, 7, 6)); + } + + { + auto date = Date(-1999, 7, 6); + date.add!"months"(-27, AllowDayOverflow.no); + assert(date == Date(-2001, 4, 6)); + date.add!"months"(28, AllowDayOverflow.no); + assert(date == Date(-1999, 8, 6)); + } + + { + auto date = Date(-1999, 5, 31); + date.add!"months"(1, AllowDayOverflow.no); + assert(date == Date(-1999, 6, 30)); + } + + { + auto date = Date(-1999, 5, 31); + date.add!"months"(-1, AllowDayOverflow.no); + assert(date == Date(-1999, 4, 30)); + } + + { + auto date = Date(-1999, 2, 28); + date.add!"months"(-12, AllowDayOverflow.no); + assert(date == Date(-2000, 2, 28)); + } + + { + auto date = Date(-2000, 2, 29); + date.add!"months"(-12, AllowDayOverflow.no); + assert(date == Date(-2001, 2, 28)); + } + + { + auto date = Date(-1999, 7, 31); + date.add!"months"(1, AllowDayOverflow.no); + assert(date == Date(-1999, 8, 31)); + date.add!"months"(1, AllowDayOverflow.no); + assert(date == Date(-1999, 9, 30)); + } + + { + auto date = Date(-1998, 8, 31); + date.add!"months"(13, AllowDayOverflow.no); + assert(date == Date(-1997, 9, 30)); + date.add!"months"(-13, AllowDayOverflow.no); + assert(date == Date(-1998, 8, 30)); + } + + { + auto date = Date(-1997, 12, 31); + date.add!"months"(13, AllowDayOverflow.no); + assert(date == Date(-1995, 1, 31)); + date.add!"months"(-13, AllowDayOverflow.no); + assert(date == Date(-1997, 12, 31)); + } + + { + auto date = Date(-1997, 12, 31); + date.add!"months"(14, AllowDayOverflow.no); + assert(date == Date(-1995, 2, 28)); + date.add!"months"(-14, AllowDayOverflow.no); + assert(date == Date(-1997, 12, 28)); + } + + { + auto date = Date(-2002, 12, 31); + date.add!"months"(14, AllowDayOverflow.no); + assert(date == Date(-2000, 2, 29)); + date.add!"months"(-14, AllowDayOverflow.no); + assert(date == Date(-2002, 12, 29)); + } + + { + auto date = Date(-2001, 12, 31); + date.add!"months"(14, AllowDayOverflow.no); + assert(date == Date(-1999, 2, 28)); + date.add!"months"(-14, AllowDayOverflow.no); + assert(date == Date(-2001, 12, 28)); + } + + // Test Both + { + auto date = Date(1, 1, 1); + date.add!"months"(-1, AllowDayOverflow.no); + assert(date == Date(0, 12, 1)); + date.add!"months"(1, AllowDayOverflow.no); + assert(date == Date(1, 1, 1)); + } + + { + auto date = Date(4, 1, 1); + date.add!"months"(-48, AllowDayOverflow.no); + assert(date == Date(0, 1, 1)); + date.add!"months"(48, AllowDayOverflow.no); + assert(date == Date(4, 1, 1)); + } + + { + auto date = Date(4, 3, 31); + date.add!"months"(-49, AllowDayOverflow.no); + assert(date == Date(0, 2, 29)); + date.add!"months"(49, AllowDayOverflow.no); + assert(date == Date(4, 3, 29)); + } + + { + auto date = Date(4, 3, 31); + date.add!"months"(-85, AllowDayOverflow.no); + assert(date == Date(-3, 2, 28)); + date.add!"months"(85, AllowDayOverflow.no); + assert(date == Date(4, 3, 28)); + } + + { + auto date = Date(-3, 3, 31); + date.add!"months"(85, AllowDayOverflow.no).add!"months"(-83, AllowDayOverflow.no); + assert(date == Date(-3, 5, 30)); + } + } + + + /++ + Adds the given number of years or months to this $(LREF Date). A negative + number will subtract. + + The difference between rolling and adding is that rolling does not + affect larger units. Rolling a $(LREF Date) 12 months gets + the exact same $(LREF Date). However, the days can still be affected due + to the differing number of days in each month. + + Because there are no units larger than years, there is no difference + between adding and rolling years. + + Params: + units = The type of units to add ("years" or "months"). + value = The number of months or years to add to this + $(LREF Date). + allowOverflow = Whether the day should be allowed to overflow, + causing the month to increment. + +/ + @safe pure nothrow @nogc + ref Date roll(string units)(long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) + if (units == "years") + { + return add!"years"(value, allowOverflow); + } + + /// + @safe unittest + { + auto d1 = Date(2010, 1, 1); + d1.roll!"months"(1); + assert(d1 == Date(2010, 2, 1)); + + auto d2 = Date(2010, 1, 1); + d2.roll!"months"(-1); + assert(d2 == Date(2010, 12, 1)); + + auto d3 = Date(1999, 1, 29); + d3.roll!"months"(1); + assert(d3 == Date(1999, 3, 1)); + + auto d4 = Date(1999, 1, 29); + d4.roll!"months"(1, AllowDayOverflow.no); + assert(d4 == Date(1999, 2, 28)); + + auto d5 = Date(2000, 2, 29); + d5.roll!"years"(1); + assert(d5 == Date(2001, 3, 1)); + + auto d6 = Date(2000, 2, 29); + d6.roll!"years"(1, AllowDayOverflow.no); + assert(d6 == Date(2001, 2, 28)); + } + + @safe unittest + { + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + static assert(!__traits(compiles, cdate.roll!"years"(3))); + static assert(!__traits(compiles, idate.rolYears(3))); + } + + + // Shares documentation with "years" version. + @safe pure nothrow @nogc + ref Date roll(string units)(long months, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) + if (units == "months") + { + months %= 12; + auto newMonth = _month + months; + + if (months < 0) + { + if (newMonth < 1) + newMonth += 12; + } + else + { + if (newMonth > 12) + newMonth -= 12; + } + + _month = cast(Month) newMonth; + + immutable currMaxDay = maxDay(_year, _month); + immutable overflow = _day - currMaxDay; + + if (overflow > 0) + { + if (allowOverflow == AllowDayOverflow.yes) + { + ++_month; + _day = cast(ubyte) overflow; + } + else + _day = cast(ubyte) currMaxDay; + } + + return this; + } + + // Test roll!"months"() with AllowDayOverflow.yes + @safe unittest + { + // Test A.D. + { + auto date = Date(1999, 7, 6); + date.roll!"months"(3); + assert(date == Date(1999, 10, 6)); + date.roll!"months"(-4); + assert(date == Date(1999, 6, 6)); + } + + { + auto date = Date(1999, 7, 6); + date.roll!"months"(6); + assert(date == Date(1999, 1, 6)); + date.roll!"months"(-6); + assert(date == Date(1999, 7, 6)); + } + + { + auto date = Date(1999, 7, 6); + date.roll!"months"(27); + assert(date == Date(1999, 10, 6)); + date.roll!"months"(-28); + assert(date == Date(1999, 6, 6)); + } + + { + auto date = Date(1999, 5, 31); + date.roll!"months"(1); + assert(date == Date(1999, 7, 1)); + } + + { + auto date = Date(1999, 5, 31); + date.roll!"months"(-1); + assert(date == Date(1999, 5, 1)); + } + + { + auto date = Date(1999, 2, 28); + date.roll!"months"(12); + assert(date == Date(1999, 2, 28)); + } + + { + auto date = Date(2000, 2, 29); + date.roll!"months"(12); + assert(date == Date(2000, 2, 29)); + } + + { + auto date = Date(1999, 7, 31); + date.roll!"months"(1); + assert(date == Date(1999, 8, 31)); + date.roll!"months"(1); + assert(date == Date(1999, 10, 1)); + } + + { + auto date = Date(1998, 8, 31); + date.roll!"months"(13); + assert(date == Date(1998, 10, 1)); + date.roll!"months"(-13); + assert(date == Date(1998, 9, 1)); + } + + { + auto date = Date(1997, 12, 31); + date.roll!"months"(13); + assert(date == Date(1997, 1, 31)); + date.roll!"months"(-13); + assert(date == Date(1997, 12, 31)); + } + + { + auto date = Date(1997, 12, 31); + date.roll!"months"(14); + assert(date == Date(1997, 3, 3)); + date.roll!"months"(-14); + assert(date == Date(1997, 1, 3)); + } + + { + auto date = Date(1998, 12, 31); + date.roll!"months"(14); + assert(date == Date(1998, 3, 3)); + date.roll!"months"(-14); + assert(date == Date(1998, 1, 3)); + } + + { + auto date = Date(1999, 12, 31); + date.roll!"months"(14); + assert(date == Date(1999, 3, 3)); + date.roll!"months"(-14); + assert(date == Date(1999, 1, 3)); + } + + // Test B.C. + { + auto date = Date(-1999, 7, 6); + date.roll!"months"(3); + assert(date == Date(-1999, 10, 6)); + date.roll!"months"(-4); + assert(date == Date(-1999, 6, 6)); + } + + { + auto date = Date(-1999, 7, 6); + date.roll!"months"(6); + assert(date == Date(-1999, 1, 6)); + date.roll!"months"(-6); + assert(date == Date(-1999, 7, 6)); + } + + { + auto date = Date(-1999, 7, 6); + date.roll!"months"(-27); + assert(date == Date(-1999, 4, 6)); + date.roll!"months"(28); + assert(date == Date(-1999, 8, 6)); + } + + { + auto date = Date(-1999, 5, 31); + date.roll!"months"(1); + assert(date == Date(-1999, 7, 1)); + } + + { + auto date = Date(-1999, 5, 31); + date.roll!"months"(-1); + assert(date == Date(-1999, 5, 1)); + } + + { + auto date = Date(-1999, 2, 28); + date.roll!"months"(-12); + assert(date == Date(-1999, 2, 28)); + } + + { + auto date = Date(-2000, 2, 29); + date.roll!"months"(-12); + assert(date == Date(-2000, 2, 29)); + } + + { + auto date = Date(-1999, 7, 31); + date.roll!"months"(1); + assert(date == Date(-1999, 8, 31)); + date.roll!"months"(1); + assert(date == Date(-1999, 10, 1)); + } + + { + auto date = Date(-1998, 8, 31); + date.roll!"months"(13); + assert(date == Date(-1998, 10, 1)); + date.roll!"months"(-13); + assert(date == Date(-1998, 9, 1)); + } + + { + auto date = Date(-1997, 12, 31); + date.roll!"months"(13); + assert(date == Date(-1997, 1, 31)); + date.roll!"months"(-13); + assert(date == Date(-1997, 12, 31)); + } + + { + auto date = Date(-1997, 12, 31); + date.roll!"months"(14); + assert(date == Date(-1997, 3, 3)); + date.roll!"months"(-14); + assert(date == Date(-1997, 1, 3)); + } + + { + auto date = Date(-2002, 12, 31); + date.roll!"months"(14); + assert(date == Date(-2002, 3, 3)); + date.roll!"months"(-14); + assert(date == Date(-2002, 1, 3)); + } + + { + auto date = Date(-2001, 12, 31); + date.roll!"months"(14); + assert(date == Date(-2001, 3, 3)); + date.roll!"months"(-14); + assert(date == Date(-2001, 1, 3)); + } + + // Test Both + { + auto date = Date(1, 1, 1); + date.roll!"months"(-1); + assert(date == Date(1, 12, 1)); + date.roll!"months"(1); + assert(date == Date(1, 1, 1)); + } + + { + auto date = Date(4, 1, 1); + date.roll!"months"(-48); + assert(date == Date(4, 1, 1)); + date.roll!"months"(48); + assert(date == Date(4, 1, 1)); + } + + { + auto date = Date(4, 3, 31); + date.roll!"months"(-49); + assert(date == Date(4, 3, 2)); + date.roll!"months"(49); + assert(date == Date(4, 4, 2)); + } + + { + auto date = Date(4, 3, 31); + date.roll!"months"(-85); + assert(date == Date(4, 3, 2)); + date.roll!"months"(85); + assert(date == Date(4, 4, 2)); + } + + { + auto date = Date(-1, 1, 1); + date.roll!"months"(-1); + assert(date == Date(-1, 12, 1)); + date.roll!"months"(1); + assert(date == Date(-1, 1, 1)); + } + + { + auto date = Date(-4, 1, 1); + date.roll!"months"(-48); + assert(date == Date(-4, 1, 1)); + date.roll!"months"(48); + assert(date == Date(-4, 1, 1)); + } + + { + auto date = Date(-4, 3, 31); + date.roll!"months"(-49); + assert(date == Date(-4, 3, 2)); + date.roll!"months"(49); + assert(date == Date(-4, 4, 2)); + } + + { + auto date = Date(-4, 3, 31); + date.roll!"months"(-85); + assert(date == Date(-4, 3, 2)); + date.roll!"months"(85); + assert(date == Date(-4, 4, 2)); + } + + { + auto date = Date(-3, 3, 31); + date.roll!"months"(85).roll!"months"(-83); + assert(date == Date(-3, 6, 1)); + } + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + static assert(!__traits(compiles, cdate.roll!"months"(3))); + static assert(!__traits(compiles, idate.roll!"months"(3))); + } + + // Test roll!"months"() with AllowDayOverflow.no + @safe unittest + { + // Test A.D. + { + auto date = Date(1999, 7, 6); + date.roll!"months"(3, AllowDayOverflow.no); + assert(date == Date(1999, 10, 6)); + date.roll!"months"(-4, AllowDayOverflow.no); + assert(date == Date(1999, 6, 6)); + } + + { + auto date = Date(1999, 7, 6); + date.roll!"months"(6, AllowDayOverflow.no); + assert(date == Date(1999, 1, 6)); + date.roll!"months"(-6, AllowDayOverflow.no); + assert(date == Date(1999, 7, 6)); + } + + { + auto date = Date(1999, 7, 6); + date.roll!"months"(27, AllowDayOverflow.no); + assert(date == Date(1999, 10, 6)); + date.roll!"months"(-28, AllowDayOverflow.no); + assert(date == Date(1999, 6, 6)); + } + + { + auto date = Date(1999, 5, 31); + date.roll!"months"(1, AllowDayOverflow.no); + assert(date == Date(1999, 6, 30)); + } + + { + auto date = Date(1999, 5, 31); + date.roll!"months"(-1, AllowDayOverflow.no); + assert(date == Date(1999, 4, 30)); + } + + { + auto date = Date(1999, 2, 28); + date.roll!"months"(12, AllowDayOverflow.no); + assert(date == Date(1999, 2, 28)); + } + + { + auto date = Date(2000, 2, 29); + date.roll!"months"(12, AllowDayOverflow.no); + assert(date == Date(2000, 2, 29)); + } + + { + auto date = Date(1999, 7, 31); + date.roll!"months"(1, AllowDayOverflow.no); + assert(date == Date(1999, 8, 31)); + date.roll!"months"(1, AllowDayOverflow.no); + assert(date == Date(1999, 9, 30)); + } + + { + auto date = Date(1998, 8, 31); + date.roll!"months"(13, AllowDayOverflow.no); + assert(date == Date(1998, 9, 30)); + date.roll!"months"(-13, AllowDayOverflow.no); + assert(date == Date(1998, 8, 30)); + } + + { + auto date = Date(1997, 12, 31); + date.roll!"months"(13, AllowDayOverflow.no); + assert(date == Date(1997, 1, 31)); + date.roll!"months"(-13, AllowDayOverflow.no); + assert(date == Date(1997, 12, 31)); + } + + { + auto date = Date(1997, 12, 31); + date.roll!"months"(14, AllowDayOverflow.no); + assert(date == Date(1997, 2, 28)); + date.roll!"months"(-14, AllowDayOverflow.no); + assert(date == Date(1997, 12, 28)); + } + + { + auto date = Date(1998, 12, 31); + date.roll!"months"(14, AllowDayOverflow.no); + assert(date == Date(1998, 2, 28)); + date.roll!"months"(-14, AllowDayOverflow.no); + assert(date == Date(1998, 12, 28)); + } + + { + auto date = Date(1999, 12, 31); + date.roll!"months"(14, AllowDayOverflow.no); + assert(date == Date(1999, 2, 28)); + date.roll!"months"(-14, AllowDayOverflow.no); + assert(date == Date(1999, 12, 28)); + } + + // Test B.C. + { + auto date = Date(-1999, 7, 6); + date.roll!"months"(3, AllowDayOverflow.no); + assert(date == Date(-1999, 10, 6)); + date.roll!"months"(-4, AllowDayOverflow.no); + assert(date == Date(-1999, 6, 6)); + } + + { + auto date = Date(-1999, 7, 6); + date.roll!"months"(6, AllowDayOverflow.no); + assert(date == Date(-1999, 1, 6)); + date.roll!"months"(-6, AllowDayOverflow.no); + assert(date == Date(-1999, 7, 6)); + } + + { + auto date = Date(-1999, 7, 6); + date.roll!"months"(-27, AllowDayOverflow.no); + assert(date == Date(-1999, 4, 6)); + date.roll!"months"(28, AllowDayOverflow.no); + assert(date == Date(-1999, 8, 6)); + } + + { + auto date = Date(-1999, 5, 31); + date.roll!"months"(1, AllowDayOverflow.no); + assert(date == Date(-1999, 6, 30)); + } + + { + auto date = Date(-1999, 5, 31); + date.roll!"months"(-1, AllowDayOverflow.no); + assert(date == Date(-1999, 4, 30)); + } + + { + auto date = Date(-1999, 2, 28); + date.roll!"months"(-12, AllowDayOverflow.no); + assert(date == Date(-1999, 2, 28)); + } + + { + auto date = Date(-2000, 2, 29); + date.roll!"months"(-12, AllowDayOverflow.no); + assert(date == Date(-2000, 2, 29)); + } + + { + auto date = Date(-1999, 7, 31); + date.roll!"months"(1, AllowDayOverflow.no); + assert(date == Date(-1999, 8, 31)); + date.roll!"months"(1, AllowDayOverflow.no); + assert(date == Date(-1999, 9, 30)); + } + + { + auto date = Date(-1998, 8, 31); + date.roll!"months"(13, AllowDayOverflow.no); + assert(date == Date(-1998, 9, 30)); + date.roll!"months"(-13, AllowDayOverflow.no); + assert(date == Date(-1998, 8, 30)); + } + + { + auto date = Date(-1997, 12, 31); + date.roll!"months"(13, AllowDayOverflow.no); + assert(date == Date(-1997, 1, 31)); + date.roll!"months"(-13, AllowDayOverflow.no); + assert(date == Date(-1997, 12, 31)); + } + + { + auto date = Date(-1997, 12, 31); + date.roll!"months"(14, AllowDayOverflow.no); + assert(date == Date(-1997, 2, 28)); + date.roll!"months"(-14, AllowDayOverflow.no); + assert(date == Date(-1997, 12, 28)); + } + + { + auto date = Date(-2002, 12, 31); + date.roll!"months"(14, AllowDayOverflow.no); + assert(date == Date(-2002, 2, 28)); + date.roll!"months"(-14, AllowDayOverflow.no); + assert(date == Date(-2002, 12, 28)); + } + + { + auto date = Date(-2001, 12, 31); + date.roll!"months"(14, AllowDayOverflow.no); + assert(date == Date(-2001, 2, 28)); + date.roll!"months"(-14, AllowDayOverflow.no); + assert(date == Date(-2001, 12, 28)); + } + + // Test Both + { + auto date = Date(1, 1, 1); + date.roll!"months"(-1, AllowDayOverflow.no); + assert(date == Date(1, 12, 1)); + date.roll!"months"(1, AllowDayOverflow.no); + assert(date == Date(1, 1, 1)); + } + + { + auto date = Date(4, 1, 1); + date.roll!"months"(-48, AllowDayOverflow.no); + assert(date == Date(4, 1, 1)); + date.roll!"months"(48, AllowDayOverflow.no); + assert(date == Date(4, 1, 1)); + } + + { + auto date = Date(4, 3, 31); + date.roll!"months"(-49, AllowDayOverflow.no); + assert(date == Date(4, 2, 29)); + date.roll!"months"(49, AllowDayOverflow.no); + assert(date == Date(4, 3, 29)); + } + + { + auto date = Date(4, 3, 31); + date.roll!"months"(-85, AllowDayOverflow.no); + assert(date == Date(4, 2, 29)); + date.roll!"months"(85, AllowDayOverflow.no); + assert(date == Date(4, 3, 29)); + } + + { + auto date = Date(-1, 1, 1); + date.roll!"months"(-1, AllowDayOverflow.no); + assert(date == Date(-1, 12, 1)); + date.roll!"months"(1, AllowDayOverflow.no); + assert(date == Date(-1, 1, 1)); + } + + { + auto date = Date(-4, 1, 1); + date.roll!"months"(-48, AllowDayOverflow.no); + assert(date == Date(-4, 1, 1)); + date.roll!"months"(48, AllowDayOverflow.no); + assert(date == Date(-4, 1, 1)); + } + + { + auto date = Date(-4, 3, 31); + date.roll!"months"(-49, AllowDayOverflow.no); + assert(date == Date(-4, 2, 29)); + date.roll!"months"(49, AllowDayOverflow.no); + assert(date == Date(-4, 3, 29)); + } + + { + auto date = Date(-4, 3, 31); + date.roll!"months"(-85, AllowDayOverflow.no); + assert(date == Date(-4, 2, 29)); + date.roll!"months"(85, AllowDayOverflow.no); + assert(date == Date(-4, 3, 29)); + } + + { + auto date = Date(-3, 3, 31); + date.roll!"months"(85, AllowDayOverflow.no).roll!"months"(-83, AllowDayOverflow.no); + assert(date == Date(-3, 5, 30)); + } + } + + + /++ + Adds the given number of units to this $(LREF Date). A negative number + will subtract. + + The difference between rolling and adding is that rolling does not + affect larger units. For instance, rolling a $(LREF Date) one + year's worth of days gets the exact same $(LREF Date). + + The only accepted units are $(D "days"). + + Params: + units = The units to add. Must be $(D "days"). + days = The number of days to add to this $(LREF Date). + +/ + ref Date roll(string units)(long days) @safe pure nothrow @nogc + if (units == "days") + { + immutable limit = maxDay(_year, _month); + days %= limit; + auto newDay = _day + days; + + if (days < 0) + { + if (newDay < 1) + newDay += limit; + } + else if (newDay > limit) + newDay -= limit; + + _day = cast(ubyte) newDay; + return this; + } + + /// + @safe unittest + { + auto d = Date(2010, 1, 1); + d.roll!"days"(1); + assert(d == Date(2010, 1, 2)); + d.roll!"days"(365); + assert(d == Date(2010, 1, 26)); + d.roll!"days"(-32); + assert(d == Date(2010, 1, 25)); + } + + @safe unittest + { + // Test A.D. + { + auto date = Date(1999, 2, 28); + date.roll!"days"(1); + assert(date == Date(1999, 2, 1)); + date.roll!"days"(-1); + assert(date == Date(1999, 2, 28)); + } + + { + auto date = Date(2000, 2, 28); + date.roll!"days"(1); + assert(date == Date(2000, 2, 29)); + date.roll!"days"(1); + assert(date == Date(2000, 2, 1)); + date.roll!"days"(-1); + assert(date == Date(2000, 2, 29)); + } + + { + auto date = Date(1999, 6, 30); + date.roll!"days"(1); + assert(date == Date(1999, 6, 1)); + date.roll!"days"(-1); + assert(date == Date(1999, 6, 30)); + } + + { + auto date = Date(1999, 7, 31); + date.roll!"days"(1); + assert(date == Date(1999, 7, 1)); + date.roll!"days"(-1); + assert(date == Date(1999, 7, 31)); + } + + { + auto date = Date(1999, 1, 1); + date.roll!"days"(-1); + assert(date == Date(1999, 1, 31)); + date.roll!"days"(1); + assert(date == Date(1999, 1, 1)); + } + + { + auto date = Date(1999, 7, 6); + date.roll!"days"(9); + assert(date == Date(1999, 7, 15)); + date.roll!"days"(-11); + assert(date == Date(1999, 7, 4)); + date.roll!"days"(30); + assert(date == Date(1999, 7, 3)); + date.roll!"days"(-3); + assert(date == Date(1999, 7, 31)); + } + + { + auto date = Date(1999, 7, 6); + date.roll!"days"(365); + assert(date == Date(1999, 7, 30)); + date.roll!"days"(-365); + assert(date == Date(1999, 7, 6)); + date.roll!"days"(366); + assert(date == Date(1999, 7, 31)); + date.roll!"days"(730); + assert(date == Date(1999, 7, 17)); + date.roll!"days"(-1096); + assert(date == Date(1999, 7, 6)); + } + + { + auto date = Date(1999, 2, 6); + date.roll!"days"(365); + assert(date == Date(1999, 2, 7)); + date.roll!"days"(-365); + assert(date == Date(1999, 2, 6)); + date.roll!"days"(366); + assert(date == Date(1999, 2, 8)); + date.roll!"days"(730); + assert(date == Date(1999, 2, 10)); + date.roll!"days"(-1096); + assert(date == Date(1999, 2, 6)); + } + + // Test B.C. + { + auto date = Date(-1999, 2, 28); + date.roll!"days"(1); + assert(date == Date(-1999, 2, 1)); + date.roll!"days"(-1); + assert(date == Date(-1999, 2, 28)); + } + + { + auto date = Date(-2000, 2, 28); + date.roll!"days"(1); + assert(date == Date(-2000, 2, 29)); + date.roll!"days"(1); + assert(date == Date(-2000, 2, 1)); + date.roll!"days"(-1); + assert(date == Date(-2000, 2, 29)); + } + + { + auto date = Date(-1999, 6, 30); + date.roll!"days"(1); + assert(date == Date(-1999, 6, 1)); + date.roll!"days"(-1); + assert(date == Date(-1999, 6, 30)); + } + + { + auto date = Date(-1999, 7, 31); + date.roll!"days"(1); + assert(date == Date(-1999, 7, 1)); + date.roll!"days"(-1); + assert(date == Date(-1999, 7, 31)); + } + + { + auto date = Date(-1999, 1, 1); + date.roll!"days"(-1); + assert(date == Date(-1999, 1, 31)); + date.roll!"days"(1); + assert(date == Date(-1999, 1, 1)); + } + + { + auto date = Date(-1999, 7, 6); + date.roll!"days"(9); + assert(date == Date(-1999, 7, 15)); + date.roll!"days"(-11); + assert(date == Date(-1999, 7, 4)); + date.roll!"days"(30); + assert(date == Date(-1999, 7, 3)); + date.roll!"days"(-3); + assert(date == Date(-1999, 7, 31)); + } + + { + auto date = Date(-1999, 7, 6); + date.roll!"days"(365); + assert(date == Date(-1999, 7, 30)); + date.roll!"days"(-365); + assert(date == Date(-1999, 7, 6)); + date.roll!"days"(366); + assert(date == Date(-1999, 7, 31)); + date.roll!"days"(730); + assert(date == Date(-1999, 7, 17)); + date.roll!"days"(-1096); + assert(date == Date(-1999, 7, 6)); + } + + // Test Both + { + auto date = Date(1, 7, 6); + date.roll!"days"(-365); + assert(date == Date(1, 7, 13)); + date.roll!"days"(365); + assert(date == Date(1, 7, 6)); + date.roll!"days"(-731); + assert(date == Date(1, 7, 19)); + date.roll!"days"(730); + assert(date == Date(1, 7, 5)); + } + + { + auto date = Date(0, 7, 6); + date.roll!"days"(-365); + assert(date == Date(0, 7, 13)); + date.roll!"days"(365); + assert(date == Date(0, 7, 6)); + date.roll!"days"(-731); + assert(date == Date(0, 7, 19)); + date.roll!"days"(730); + assert(date == Date(0, 7, 5)); + } + + { + auto date = Date(0, 7, 6); + date.roll!"days"(-365).roll!"days"(362).roll!"days"(-12).roll!"days"(730); + assert(date == Date(0, 7, 8)); + } + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + static assert(!__traits(compiles, cdate.roll!"days"(12))); + static assert(!__traits(compiles, idate.roll!"days"(12))); + } + + + /++ + Gives the result of adding or subtracting a $(REF Duration, core,time) + from + + The legal types of arithmetic for $(LREF Date) using this operator are + + $(BOOKTABLE, + $(TR $(TD Date) $(TD +) $(TD Duration) $(TD -->) $(TD Date)) + $(TR $(TD Date) $(TD -) $(TD Duration) $(TD -->) $(TD Date)) + ) + + Params: + duration = The $(REF Duration, core,time) to add to or subtract from + this $(LREF Date). + +/ + Date opBinary(string op)(Duration duration) const @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + Date retval = this; + immutable days = duration.total!"days"; + mixin("return retval._addDays(" ~ op ~ "days);"); + } + + /// + @safe unittest + { + import core.time : days; + + assert(Date(2015, 12, 31) + days(1) == Date(2016, 1, 1)); + assert(Date(2004, 2, 26) + days(4) == Date(2004, 3, 1)); + + assert(Date(2016, 1, 1) - days(1) == Date(2015, 12, 31)); + assert(Date(2004, 3, 1) - days(4) == Date(2004, 2, 26)); + } + + @safe unittest + { + auto date = Date(1999, 7, 6); + + assert(date + dur!"weeks"(7) == Date(1999, 8, 24)); + assert(date + dur!"weeks"(-7) == Date(1999, 5, 18)); + assert(date + dur!"days"(7) == Date(1999, 7, 13)); + assert(date + dur!"days"(-7) == Date(1999, 6, 29)); + + assert(date + dur!"hours"(24) == Date(1999, 7, 7)); + assert(date + dur!"hours"(-24) == Date(1999, 7, 5)); + assert(date + dur!"minutes"(1440) == Date(1999, 7, 7)); + assert(date + dur!"minutes"(-1440) == Date(1999, 7, 5)); + assert(date + dur!"seconds"(86_400) == Date(1999, 7, 7)); + assert(date + dur!"seconds"(-86_400) == Date(1999, 7, 5)); + assert(date + dur!"msecs"(86_400_000) == Date(1999, 7, 7)); + assert(date + dur!"msecs"(-86_400_000) == Date(1999, 7, 5)); + assert(date + dur!"usecs"(86_400_000_000) == Date(1999, 7, 7)); + assert(date + dur!"usecs"(-86_400_000_000) == Date(1999, 7, 5)); + assert(date + dur!"hnsecs"(864_000_000_000) == Date(1999, 7, 7)); + assert(date + dur!"hnsecs"(-864_000_000_000) == Date(1999, 7, 5)); + + assert(date - dur!"weeks"(-7) == Date(1999, 8, 24)); + assert(date - dur!"weeks"(7) == Date(1999, 5, 18)); + assert(date - dur!"days"(-7) == Date(1999, 7, 13)); + assert(date - dur!"days"(7) == Date(1999, 6, 29)); + + assert(date - dur!"hours"(-24) == Date(1999, 7, 7)); + assert(date - dur!"hours"(24) == Date(1999, 7, 5)); + assert(date - dur!"minutes"(-1440) == Date(1999, 7, 7)); + assert(date - dur!"minutes"(1440) == Date(1999, 7, 5)); + assert(date - dur!"seconds"(-86_400) == Date(1999, 7, 7)); + assert(date - dur!"seconds"(86_400) == Date(1999, 7, 5)); + assert(date - dur!"msecs"(-86_400_000) == Date(1999, 7, 7)); + assert(date - dur!"msecs"(86_400_000) == Date(1999, 7, 5)); + assert(date - dur!"usecs"(-86_400_000_000) == Date(1999, 7, 7)); + assert(date - dur!"usecs"(86_400_000_000) == Date(1999, 7, 5)); + assert(date - dur!"hnsecs"(-864_000_000_000) == Date(1999, 7, 7)); + assert(date - dur!"hnsecs"(864_000_000_000) == Date(1999, 7, 5)); + + auto duration = dur!"days"(12); + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(date + duration == Date(1999, 7, 18)); + assert(cdate + duration == Date(1999, 7, 18)); + assert(idate + duration == Date(1999, 7, 18)); + + assert(date - duration == Date(1999, 6, 24)); + assert(cdate - duration == Date(1999, 6, 24)); + assert(idate - duration == Date(1999, 6, 24)); + } + + // Explicitly undocumented. It will be removed in January 2018. @@@DEPRECATED_2018-01@@@ + deprecated("Use Duration instead of TickDuration.") + Date opBinary(string op)(TickDuration td) const @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + Date retval = this; + immutable days = convert!("hnsecs", "days")(td.hnsecs); + mixin("return retval._addDays(" ~ op ~ "days);"); + } + + deprecated @safe unittest + { + // This probably only runs in cases where gettimeofday() is used, but it's + // hard to do this test correctly with variable ticksPerSec. + if (TickDuration.ticksPerSec == 1_000_000) + { + auto date = Date(1999, 7, 6); + + assert(date + TickDuration.from!"usecs"(86_400_000_000) == Date(1999, 7, 7)); + assert(date + TickDuration.from!"usecs"(-86_400_000_000) == Date(1999, 7, 5)); + + assert(date - TickDuration.from!"usecs"(-86_400_000_000) == Date(1999, 7, 7)); + assert(date - TickDuration.from!"usecs"(86_400_000_000) == Date(1999, 7, 5)); + } + } + + + /++ + Gives the result of adding or subtracting a $(REF Duration, core,time) + from this $(LREF Date), as well as assigning the result to this + $(LREF Date). + + The legal types of arithmetic for $(LREF Date) using this operator are + + $(BOOKTABLE, + $(TR $(TD Date) $(TD +) $(TD Duration) $(TD -->) $(TD Date)) + $(TR $(TD Date) $(TD -) $(TD Duration) $(TD -->) $(TD Date)) + ) + + Params: + duration = The $(REF Duration, core,time) to add to or subtract from + this $(LREF Date). + +/ + ref Date opOpAssign(string op)(Duration duration) @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + immutable days = duration.total!"days"; + mixin("return _addDays(" ~ op ~ "days);"); + } + + @safe unittest + { + assert(Date(1999, 7, 6) + dur!"weeks"(7) == Date(1999, 8, 24)); + assert(Date(1999, 7, 6) + dur!"weeks"(-7) == Date(1999, 5, 18)); + assert(Date(1999, 7, 6) + dur!"days"(7) == Date(1999, 7, 13)); + assert(Date(1999, 7, 6) + dur!"days"(-7) == Date(1999, 6, 29)); + + assert(Date(1999, 7, 6) + dur!"hours"(24) == Date(1999, 7, 7)); + assert(Date(1999, 7, 6) + dur!"hours"(-24) == Date(1999, 7, 5)); + assert(Date(1999, 7, 6) + dur!"minutes"(1440) == Date(1999, 7, 7)); + assert(Date(1999, 7, 6) + dur!"minutes"(-1440) == Date(1999, 7, 5)); + assert(Date(1999, 7, 6) + dur!"seconds"(86_400) == Date(1999, 7, 7)); + assert(Date(1999, 7, 6) + dur!"seconds"(-86_400) == Date(1999, 7, 5)); + assert(Date(1999, 7, 6) + dur!"msecs"(86_400_000) == Date(1999, 7, 7)); + assert(Date(1999, 7, 6) + dur!"msecs"(-86_400_000) == Date(1999, 7, 5)); + assert(Date(1999, 7, 6) + dur!"usecs"(86_400_000_000) == Date(1999, 7, 7)); + assert(Date(1999, 7, 6) + dur!"usecs"(-86_400_000_000) == Date(1999, 7, 5)); + assert(Date(1999, 7, 6) + dur!"hnsecs"(864_000_000_000) == Date(1999, 7, 7)); + assert(Date(1999, 7, 6) + dur!"hnsecs"(-864_000_000_000) == Date(1999, 7, 5)); + + assert(Date(1999, 7, 6) - dur!"weeks"(-7) == Date(1999, 8, 24)); + assert(Date(1999, 7, 6) - dur!"weeks"(7) == Date(1999, 5, 18)); + assert(Date(1999, 7, 6) - dur!"days"(-7) == Date(1999, 7, 13)); + assert(Date(1999, 7, 6) - dur!"days"(7) == Date(1999, 6, 29)); + + assert(Date(1999, 7, 6) - dur!"hours"(-24) == Date(1999, 7, 7)); + assert(Date(1999, 7, 6) - dur!"hours"(24) == Date(1999, 7, 5)); + assert(Date(1999, 7, 6) - dur!"minutes"(-1440) == Date(1999, 7, 7)); + assert(Date(1999, 7, 6) - dur!"minutes"(1440) == Date(1999, 7, 5)); + assert(Date(1999, 7, 6) - dur!"seconds"(-86_400) == Date(1999, 7, 7)); + assert(Date(1999, 7, 6) - dur!"seconds"(86_400) == Date(1999, 7, 5)); + assert(Date(1999, 7, 6) - dur!"msecs"(-86_400_000) == Date(1999, 7, 7)); + assert(Date(1999, 7, 6) - dur!"msecs"(86_400_000) == Date(1999, 7, 5)); + assert(Date(1999, 7, 6) - dur!"usecs"(-86_400_000_000) == Date(1999, 7, 7)); + assert(Date(1999, 7, 6) - dur!"usecs"(86_400_000_000) == Date(1999, 7, 5)); + assert(Date(1999, 7, 6) - dur!"hnsecs"(-864_000_000_000) == Date(1999, 7, 7)); + assert(Date(1999, 7, 6) - dur!"hnsecs"(864_000_000_000) == Date(1999, 7, 5)); + + { + auto date = Date(0, 1, 31); + (date += dur!"days"(507)) += dur!"days"(-2); + assert(date == Date(1, 6, 19)); + } + + auto duration = dur!"days"(12); + auto date = Date(1999, 7, 6); + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + date += duration; + static assert(!__traits(compiles, cdate += duration)); + static assert(!__traits(compiles, idate += duration)); + + date -= duration; + static assert(!__traits(compiles, cdate -= duration)); + static assert(!__traits(compiles, idate -= duration)); + } + + // Explicitly undocumented. It will be removed in January 2018. @@@DEPRECATED_2018-01@@@ + deprecated("Use Duration instead of TickDuration.") + ref Date opOpAssign(string op)(TickDuration td) @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + immutable days = convert!("seconds", "days")(td.seconds); + mixin("return _addDays(" ~ op ~ "days);"); + } + + deprecated @safe unittest + { + // This probably only runs in cases where gettimeofday() is used, but it's + // hard to do this test correctly with variable ticksPerSec. + if (TickDuration.ticksPerSec == 1_000_000) + { + { + auto date = Date(1999, 7, 6); + date += TickDuration.from!"usecs"(86_400_000_000); + assert(date == Date(1999, 7, 7)); + } + + { + auto date = Date(1999, 7, 6); + date += TickDuration.from!"usecs"(-86_400_000_000); + assert(date == Date(1999, 7, 5)); + } + + { + auto date = Date(1999, 7, 6); + date -= TickDuration.from!"usecs"(-86_400_000_000); + assert(date == Date(1999, 7, 7)); + } + + { + auto date = Date(1999, 7, 6); + date -= TickDuration.from!"usecs"(86_400_000_000); + assert(date == Date(1999, 7, 5)); + } + } + } + + + /++ + Gives the difference between two $(LREF Date)s. + + The legal types of arithmetic for $(LREF Date) using this operator are + + $(BOOKTABLE, + $(TR $(TD Date) $(TD -) $(TD Date) $(TD -->) $(TD duration)) + ) + +/ + Duration opBinary(string op)(in Date rhs) const @safe pure nothrow @nogc + if (op == "-") + { + return dur!"days"(this.dayOfGregorianCal - rhs.dayOfGregorianCal); + } + + @safe unittest + { + auto date = Date(1999, 7, 6); + + assert(Date(1999, 7, 6) - Date(1998, 7, 6) == dur!"days"(365)); + assert(Date(1998, 7, 6) - Date(1999, 7, 6) == dur!"days"(-365)); + assert(Date(1999, 6, 6) - Date(1999, 5, 6) == dur!"days"(31)); + assert(Date(1999, 5, 6) - Date(1999, 6, 6) == dur!"days"(-31)); + assert(Date(1999, 1, 1) - Date(1998, 12, 31) == dur!"days"(1)); + assert(Date(1998, 12, 31) - Date(1999, 1, 1) == dur!"days"(-1)); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(date - date == Duration.zero); + assert(cdate - date == Duration.zero); + assert(idate - date == Duration.zero); + + assert(date - cdate == Duration.zero); + assert(cdate - cdate == Duration.zero); + assert(idate - cdate == Duration.zero); + + assert(date - idate == Duration.zero); + assert(cdate - idate == Duration.zero); + assert(idate - idate == Duration.zero); + } + + + /++ + Returns the difference between the two $(LREF Date)s in months. + + To get the difference in years, subtract the year property + of two $(LREF Date)s. To get the difference in days or weeks, + subtract the $(LREF Date)s themselves and use the + $(REF Duration, core,time) that results. Because converting between + months and smaller units requires a specific date (which + $(REF Duration, core,time)s don't have), getting the difference in + months requires some math using both the year and month properties, so + this is a convenience function for getting the difference in months. + + Note that the number of days in the months or how far into the month + either $(LREF Date) is is irrelevant. It is the difference in the month + property combined with the difference in years * 12. So, for instance, + December 31st and January 1st are one month apart just as December 1st + and January 31st are one month apart. + + Params: + rhs = The $(LREF Date) to subtract from this one. + +/ + int diffMonths(in Date rhs) const @safe pure nothrow @nogc + { + immutable yearDiff = _year - rhs._year; + immutable monthDiff = _month - rhs._month; + + return yearDiff * 12 + monthDiff; + } + + /// + @safe unittest + { + assert(Date(1999, 2, 1).diffMonths(Date(1999, 1, 31)) == 1); + assert(Date(1999, 1, 31).diffMonths(Date(1999, 2, 1)) == -1); + assert(Date(1999, 3, 1).diffMonths(Date(1999, 1, 1)) == 2); + assert(Date(1999, 1, 1).diffMonths(Date(1999, 3, 31)) == -2); + } + + @safe unittest + { + auto date = Date(1999, 7, 6); + + // Test A.D. + assert(date.diffMonths(Date(1998, 6, 5)) == 13); + assert(date.diffMonths(Date(1998, 7, 5)) == 12); + assert(date.diffMonths(Date(1998, 8, 5)) == 11); + assert(date.diffMonths(Date(1998, 9, 5)) == 10); + assert(date.diffMonths(Date(1998, 10, 5)) == 9); + assert(date.diffMonths(Date(1998, 11, 5)) == 8); + assert(date.diffMonths(Date(1998, 12, 5)) == 7); + assert(date.diffMonths(Date(1999, 1, 5)) == 6); + assert(date.diffMonths(Date(1999, 2, 6)) == 5); + assert(date.diffMonths(Date(1999, 3, 6)) == 4); + assert(date.diffMonths(Date(1999, 4, 6)) == 3); + assert(date.diffMonths(Date(1999, 5, 6)) == 2); + assert(date.diffMonths(Date(1999, 6, 6)) == 1); + assert(date.diffMonths(date) == 0); + assert(date.diffMonths(Date(1999, 8, 6)) == -1); + assert(date.diffMonths(Date(1999, 9, 6)) == -2); + assert(date.diffMonths(Date(1999, 10, 6)) == -3); + assert(date.diffMonths(Date(1999, 11, 6)) == -4); + assert(date.diffMonths(Date(1999, 12, 6)) == -5); + assert(date.diffMonths(Date(2000, 1, 6)) == -6); + assert(date.diffMonths(Date(2000, 2, 6)) == -7); + assert(date.diffMonths(Date(2000, 3, 6)) == -8); + assert(date.diffMonths(Date(2000, 4, 6)) == -9); + assert(date.diffMonths(Date(2000, 5, 6)) == -10); + assert(date.diffMonths(Date(2000, 6, 6)) == -11); + assert(date.diffMonths(Date(2000, 7, 6)) == -12); + assert(date.diffMonths(Date(2000, 8, 6)) == -13); + + assert(Date(1998, 6, 5).diffMonths(date) == -13); + assert(Date(1998, 7, 5).diffMonths(date) == -12); + assert(Date(1998, 8, 5).diffMonths(date) == -11); + assert(Date(1998, 9, 5).diffMonths(date) == -10); + assert(Date(1998, 10, 5).diffMonths(date) == -9); + assert(Date(1998, 11, 5).diffMonths(date) == -8); + assert(Date(1998, 12, 5).diffMonths(date) == -7); + assert(Date(1999, 1, 5).diffMonths(date) == -6); + assert(Date(1999, 2, 6).diffMonths(date) == -5); + assert(Date(1999, 3, 6).diffMonths(date) == -4); + assert(Date(1999, 4, 6).diffMonths(date) == -3); + assert(Date(1999, 5, 6).diffMonths(date) == -2); + assert(Date(1999, 6, 6).diffMonths(date) == -1); + assert(Date(1999, 8, 6).diffMonths(date) == 1); + assert(Date(1999, 9, 6).diffMonths(date) == 2); + assert(Date(1999, 10, 6).diffMonths(date) == 3); + assert(Date(1999, 11, 6).diffMonths(date) == 4); + assert(Date(1999, 12, 6).diffMonths(date) == 5); + assert(Date(2000, 1, 6).diffMonths(date) == 6); + assert(Date(2000, 2, 6).diffMonths(date) == 7); + assert(Date(2000, 3, 6).diffMonths(date) == 8); + assert(Date(2000, 4, 6).diffMonths(date) == 9); + assert(Date(2000, 5, 6).diffMonths(date) == 10); + assert(Date(2000, 6, 6).diffMonths(date) == 11); + assert(Date(2000, 7, 6).diffMonths(date) == 12); + assert(Date(2000, 8, 6).diffMonths(date) == 13); + + assert(date.diffMonths(Date(1999, 6, 30)) == 1); + assert(date.diffMonths(Date(1999, 7, 1)) == 0); + assert(date.diffMonths(Date(1999, 7, 6)) == 0); + assert(date.diffMonths(Date(1999, 7, 11)) == 0); + assert(date.diffMonths(Date(1999, 7, 16)) == 0); + assert(date.diffMonths(Date(1999, 7, 21)) == 0); + assert(date.diffMonths(Date(1999, 7, 26)) == 0); + assert(date.diffMonths(Date(1999, 7, 31)) == 0); + assert(date.diffMonths(Date(1999, 8, 1)) == -1); + + assert(date.diffMonths(Date(1990, 6, 30)) == 109); + assert(date.diffMonths(Date(1990, 7, 1)) == 108); + assert(date.diffMonths(Date(1990, 7, 6)) == 108); + assert(date.diffMonths(Date(1990, 7, 11)) == 108); + assert(date.diffMonths(Date(1990, 7, 16)) == 108); + assert(date.diffMonths(Date(1990, 7, 21)) == 108); + assert(date.diffMonths(Date(1990, 7, 26)) == 108); + assert(date.diffMonths(Date(1990, 7, 31)) == 108); + assert(date.diffMonths(Date(1990, 8, 1)) == 107); + + assert(Date(1999, 6, 30).diffMonths(date) == -1); + assert(Date(1999, 7, 1).diffMonths(date) == 0); + assert(Date(1999, 7, 6).diffMonths(date) == 0); + assert(Date(1999, 7, 11).diffMonths(date) == 0); + assert(Date(1999, 7, 16).diffMonths(date) == 0); + assert(Date(1999, 7, 21).diffMonths(date) == 0); + assert(Date(1999, 7, 26).diffMonths(date) == 0); + assert(Date(1999, 7, 31).diffMonths(date) == 0); + assert(Date(1999, 8, 1).diffMonths(date) == 1); + + assert(Date(1990, 6, 30).diffMonths(date) == -109); + assert(Date(1990, 7, 1).diffMonths(date) == -108); + assert(Date(1990, 7, 6).diffMonths(date) == -108); + assert(Date(1990, 7, 11).diffMonths(date) == -108); + assert(Date(1990, 7, 16).diffMonths(date) == -108); + assert(Date(1990, 7, 21).diffMonths(date) == -108); + assert(Date(1990, 7, 26).diffMonths(date) == -108); + assert(Date(1990, 7, 31).diffMonths(date) == -108); + assert(Date(1990, 8, 1).diffMonths(date) == -107); + + // Test B.C. + auto dateBC = Date(-1999, 7, 6); + + assert(dateBC.diffMonths(Date(-2000, 6, 5)) == 13); + assert(dateBC.diffMonths(Date(-2000, 7, 5)) == 12); + assert(dateBC.diffMonths(Date(-2000, 8, 5)) == 11); + assert(dateBC.diffMonths(Date(-2000, 9, 5)) == 10); + assert(dateBC.diffMonths(Date(-2000, 10, 5)) == 9); + assert(dateBC.diffMonths(Date(-2000, 11, 5)) == 8); + assert(dateBC.diffMonths(Date(-2000, 12, 5)) == 7); + assert(dateBC.diffMonths(Date(-1999, 1, 5)) == 6); + assert(dateBC.diffMonths(Date(-1999, 2, 6)) == 5); + assert(dateBC.diffMonths(Date(-1999, 3, 6)) == 4); + assert(dateBC.diffMonths(Date(-1999, 4, 6)) == 3); + assert(dateBC.diffMonths(Date(-1999, 5, 6)) == 2); + assert(dateBC.diffMonths(Date(-1999, 6, 6)) == 1); + assert(dateBC.diffMonths(dateBC) == 0); + assert(dateBC.diffMonths(Date(-1999, 8, 6)) == -1); + assert(dateBC.diffMonths(Date(-1999, 9, 6)) == -2); + assert(dateBC.diffMonths(Date(-1999, 10, 6)) == -3); + assert(dateBC.diffMonths(Date(-1999, 11, 6)) == -4); + assert(dateBC.diffMonths(Date(-1999, 12, 6)) == -5); + assert(dateBC.diffMonths(Date(-1998, 1, 6)) == -6); + assert(dateBC.diffMonths(Date(-1998, 2, 6)) == -7); + assert(dateBC.diffMonths(Date(-1998, 3, 6)) == -8); + assert(dateBC.diffMonths(Date(-1998, 4, 6)) == -9); + assert(dateBC.diffMonths(Date(-1998, 5, 6)) == -10); + assert(dateBC.diffMonths(Date(-1998, 6, 6)) == -11); + assert(dateBC.diffMonths(Date(-1998, 7, 6)) == -12); + assert(dateBC.diffMonths(Date(-1998, 8, 6)) == -13); + + assert(Date(-2000, 6, 5).diffMonths(dateBC) == -13); + assert(Date(-2000, 7, 5).diffMonths(dateBC) == -12); + assert(Date(-2000, 8, 5).diffMonths(dateBC) == -11); + assert(Date(-2000, 9, 5).diffMonths(dateBC) == -10); + assert(Date(-2000, 10, 5).diffMonths(dateBC) == -9); + assert(Date(-2000, 11, 5).diffMonths(dateBC) == -8); + assert(Date(-2000, 12, 5).diffMonths(dateBC) == -7); + assert(Date(-1999, 1, 5).diffMonths(dateBC) == -6); + assert(Date(-1999, 2, 6).diffMonths(dateBC) == -5); + assert(Date(-1999, 3, 6).diffMonths(dateBC) == -4); + assert(Date(-1999, 4, 6).diffMonths(dateBC) == -3); + assert(Date(-1999, 5, 6).diffMonths(dateBC) == -2); + assert(Date(-1999, 6, 6).diffMonths(dateBC) == -1); + assert(Date(-1999, 8, 6).diffMonths(dateBC) == 1); + assert(Date(-1999, 9, 6).diffMonths(dateBC) == 2); + assert(Date(-1999, 10, 6).diffMonths(dateBC) == 3); + assert(Date(-1999, 11, 6).diffMonths(dateBC) == 4); + assert(Date(-1999, 12, 6).diffMonths(dateBC) == 5); + assert(Date(-1998, 1, 6).diffMonths(dateBC) == 6); + assert(Date(-1998, 2, 6).diffMonths(dateBC) == 7); + assert(Date(-1998, 3, 6).diffMonths(dateBC) == 8); + assert(Date(-1998, 4, 6).diffMonths(dateBC) == 9); + assert(Date(-1998, 5, 6).diffMonths(dateBC) == 10); + assert(Date(-1998, 6, 6).diffMonths(dateBC) == 11); + assert(Date(-1998, 7, 6).diffMonths(dateBC) == 12); + assert(Date(-1998, 8, 6).diffMonths(dateBC) == 13); + + assert(dateBC.diffMonths(Date(-1999, 6, 30)) == 1); + assert(dateBC.diffMonths(Date(-1999, 7, 1)) == 0); + assert(dateBC.diffMonths(Date(-1999, 7, 6)) == 0); + assert(dateBC.diffMonths(Date(-1999, 7, 11)) == 0); + assert(dateBC.diffMonths(Date(-1999, 7, 16)) == 0); + assert(dateBC.diffMonths(Date(-1999, 7, 21)) == 0); + assert(dateBC.diffMonths(Date(-1999, 7, 26)) == 0); + assert(dateBC.diffMonths(Date(-1999, 7, 31)) == 0); + assert(dateBC.diffMonths(Date(-1999, 8, 1)) == -1); + + assert(dateBC.diffMonths(Date(-2008, 6, 30)) == 109); + assert(dateBC.diffMonths(Date(-2008, 7, 1)) == 108); + assert(dateBC.diffMonths(Date(-2008, 7, 6)) == 108); + assert(dateBC.diffMonths(Date(-2008, 7, 11)) == 108); + assert(dateBC.diffMonths(Date(-2008, 7, 16)) == 108); + assert(dateBC.diffMonths(Date(-2008, 7, 21)) == 108); + assert(dateBC.diffMonths(Date(-2008, 7, 26)) == 108); + assert(dateBC.diffMonths(Date(-2008, 7, 31)) == 108); + assert(dateBC.diffMonths(Date(-2008, 8, 1)) == 107); + + assert(Date(-1999, 6, 30).diffMonths(dateBC) == -1); + assert(Date(-1999, 7, 1).diffMonths(dateBC) == 0); + assert(Date(-1999, 7, 6).diffMonths(dateBC) == 0); + assert(Date(-1999, 7, 11).diffMonths(dateBC) == 0); + assert(Date(-1999, 7, 16).diffMonths(dateBC) == 0); + assert(Date(-1999, 7, 21).diffMonths(dateBC) == 0); + assert(Date(-1999, 7, 26).diffMonths(dateBC) == 0); + assert(Date(-1999, 7, 31).diffMonths(dateBC) == 0); + assert(Date(-1999, 8, 1).diffMonths(dateBC) == 1); + + assert(Date(-2008, 6, 30).diffMonths(dateBC) == -109); + assert(Date(-2008, 7, 1).diffMonths(dateBC) == -108); + assert(Date(-2008, 7, 6).diffMonths(dateBC) == -108); + assert(Date(-2008, 7, 11).diffMonths(dateBC) == -108); + assert(Date(-2008, 7, 16).diffMonths(dateBC) == -108); + assert(Date(-2008, 7, 21).diffMonths(dateBC) == -108); + assert(Date(-2008, 7, 26).diffMonths(dateBC) == -108); + assert(Date(-2008, 7, 31).diffMonths(dateBC) == -108); + assert(Date(-2008, 8, 1).diffMonths(dateBC) == -107); + + // Test Both + assert(Date(3, 3, 3).diffMonths(Date(-5, 5, 5)) == 94); + assert(Date(-5, 5, 5).diffMonths(Date(3, 3, 3)) == -94); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(date.diffMonths(date) == 0); + assert(cdate.diffMonths(date) == 0); + assert(idate.diffMonths(date) == 0); + + assert(date.diffMonths(cdate) == 0); + assert(cdate.diffMonths(cdate) == 0); + assert(idate.diffMonths(cdate) == 0); + + assert(date.diffMonths(idate) == 0); + assert(cdate.diffMonths(idate) == 0); + assert(idate.diffMonths(idate) == 0); + } + + + /++ + Whether this $(LREF Date) is in a leap year. + +/ + @property bool isLeapYear() const @safe pure nothrow @nogc + { + return yearIsLeapYear(_year); + } + + @safe unittest + { + auto date = Date(1999, 7, 6); + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + static assert(!__traits(compiles, date.isLeapYear = true)); + static assert(!__traits(compiles, cdate.isLeapYear = true)); + static assert(!__traits(compiles, idate.isLeapYear = true)); + } + + + /++ + Day of the week this $(LREF Date) is on. + +/ + @property DayOfWeek dayOfWeek() const @safe pure nothrow @nogc + { + return getDayOfWeek(dayOfGregorianCal); + } + + @safe unittest + { + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(cdate.dayOfWeek == DayOfWeek.tue); + static assert(!__traits(compiles, cdate.dayOfWeek = DayOfWeek.sun)); + assert(idate.dayOfWeek == DayOfWeek.tue); + static assert(!__traits(compiles, idate.dayOfWeek = DayOfWeek.sun)); + } + + + /++ + Day of the year this $(LREF Date) is on. + +/ + @property ushort dayOfYear() const @safe pure nothrow @nogc + { + if (_month >= Month.jan && _month <= Month.dec) + { + immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap; + auto monthIndex = _month - Month.jan; + + return cast(ushort)(lastDay[monthIndex] + _day); + } + assert(0, "Invalid month."); + } + + /// + @safe unittest + { + assert(Date(1999, 1, 1).dayOfYear == 1); + assert(Date(1999, 12, 31).dayOfYear == 365); + assert(Date(2000, 12, 31).dayOfYear == 366); + } + + @safe unittest + { + import std.algorithm.iteration : filter; + import std.range : chain; + + foreach (year; filter!((a){return !yearIsLeapYear(a);})(chain(testYearsBC, testYearsAD))) + { + foreach (doy; testDaysOfYear) + assert(Date(year, doy.md.month, doy.md.day).dayOfYear == doy.day); + } + + foreach (year; filter!((a){return yearIsLeapYear(a);})(chain(testYearsBC, testYearsAD))) + { + foreach (doy; testDaysOfLeapYear) + assert(Date(year, doy.md.month, doy.md.day).dayOfYear == doy.day); + } + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(cdate.dayOfYear == 187); + assert(idate.dayOfYear == 187); + } + + /++ + Day of the year. + + Params: + day = The day of the year to set which day of the year this + $(LREF Date) is on. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given day is an + invalid day of the year. + +/ + @property void dayOfYear(int day) @safe pure + { + setDayOfYear!true(day); + } + + private void setDayOfYear(bool useExceptions = false)(int day) + { + immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap; + + bool dayOutOfRange = day <= 0 || day > (isLeapYear ? daysInLeapYear : daysInYear); + enum errorMsg = "Invalid day of the year."; + + static if (useExceptions) + { + if (dayOutOfRange) throw new DateTimeException(errorMsg); + } + else + { + assert(!dayOutOfRange, errorMsg); + } + + foreach (i; 1 .. lastDay.length) + { + if (day <= lastDay[i]) + { + _month = cast(Month)(cast(int) Month.jan + i - 1); + _day = cast(ubyte)(day - lastDay[i - 1]); + return; + } + } + assert(0, "Invalid day of the year."); + } + + @safe unittest + { + static void test(Date date, int day, MonthDay expected, size_t line = __LINE__) + { + date.dayOfYear = day; + assert(date.month == expected.month); + assert(date.day == expected.day); + } + + foreach (doy; testDaysOfYear) + { + test(Date(1999, 1, 1), doy.day, doy.md); + test(Date(-1, 1, 1), doy.day, doy.md); + } + + foreach (doy; testDaysOfLeapYear) + { + test(Date(2000, 1, 1), doy.day, doy.md); + test(Date(-4, 1, 1), doy.day, doy.md); + } + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + static assert(!__traits(compiles, cdate.dayOfYear = 187)); + static assert(!__traits(compiles, idate.dayOfYear = 187)); + } + + + /++ + The Xth day of the Gregorian Calendar that this $(LREF Date) is on. + +/ + @property int dayOfGregorianCal() const @safe pure nothrow @nogc + { + if (isAD) + { + if (_year == 1) + return dayOfYear; + + int years = _year - 1; + auto days = (years / 400) * daysIn400Years; + years %= 400; + + days += (years / 100) * daysIn100Years; + years %= 100; + + days += (years / 4) * daysIn4Years; + years %= 4; + + days += years * daysInYear; + + days += dayOfYear; + + return days; + } + else if (_year == 0) + return dayOfYear - daysInLeapYear; + else + { + int years = _year; + auto days = (years / 400) * daysIn400Years; + years %= 400; + + days += (years / 100) * daysIn100Years; + years %= 100; + + days += (years / 4) * daysIn4Years; + years %= 4; + + if (years < 0) + { + days -= daysInLeapYear; + ++years; + + days += years * daysInYear; + + days -= daysInYear - dayOfYear; + } + else + days -= daysInLeapYear - dayOfYear; + + return days; + } + } + + /// + @safe unittest + { + assert(Date(1, 1, 1).dayOfGregorianCal == 1); + assert(Date(1, 12, 31).dayOfGregorianCal == 365); + assert(Date(2, 1, 1).dayOfGregorianCal == 366); + + assert(Date(0, 12, 31).dayOfGregorianCal == 0); + assert(Date(0, 1, 1).dayOfGregorianCal == -365); + assert(Date(-1, 12, 31).dayOfGregorianCal == -366); + + assert(Date(2000, 1, 1).dayOfGregorianCal == 730_120); + assert(Date(2010, 12, 31).dayOfGregorianCal == 734_137); + } + + @safe unittest + { + import std.range : chain; + + foreach (gd; chain(testGregDaysBC, testGregDaysAD)) + assert(gd.date.dayOfGregorianCal == gd.day); + + auto date = Date(1999, 7, 6); + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(date.dayOfGregorianCal == 729_941); + assert(cdate.dayOfGregorianCal == 729_941); + assert(idate.dayOfGregorianCal == 729_941); + } + + /++ + The Xth day of the Gregorian Calendar that this $(LREF Date) is on. + + Params: + day = The day of the Gregorian Calendar to set this $(LREF Date) to. + +/ + @property void dayOfGregorianCal(int day) @safe pure nothrow @nogc + { + this = Date(day); + } + + /// + @safe unittest + { + auto date = Date.init; + date.dayOfGregorianCal = 1; + assert(date == Date(1, 1, 1)); + + date.dayOfGregorianCal = 365; + assert(date == Date(1, 12, 31)); + + date.dayOfGregorianCal = 366; + assert(date == Date(2, 1, 1)); + + date.dayOfGregorianCal = 0; + assert(date == Date(0, 12, 31)); + + date.dayOfGregorianCal = -365; + assert(date == Date(-0, 1, 1)); + + date.dayOfGregorianCal = -366; + assert(date == Date(-1, 12, 31)); + + date.dayOfGregorianCal = 730_120; + assert(date == Date(2000, 1, 1)); + + date.dayOfGregorianCal = 734_137; + assert(date == Date(2010, 12, 31)); + } + + @safe unittest + { + auto date = Date(1999, 7, 6); + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + date.dayOfGregorianCal = 187; + assert(date.dayOfGregorianCal == 187); + static assert(!__traits(compiles, cdate.dayOfGregorianCal = 187)); + static assert(!__traits(compiles, idate.dayOfGregorianCal = 187)); + } + + + /++ + The ISO 8601 week of the year that this $(LREF Date) is in. + + See_Also: + $(HTTP en.wikipedia.org/wiki/ISO_week_date, ISO Week Date) + +/ + @property ubyte isoWeek() const @safe pure nothrow + { + immutable weekday = dayOfWeek; + immutable adjustedWeekday = weekday == DayOfWeek.sun ? 7 : weekday; + immutable week = (dayOfYear - adjustedWeekday + 10) / 7; + + try + { + if (week == 53) + { + switch (Date(_year + 1, 1, 1).dayOfWeek) + { + case DayOfWeek.mon: + case DayOfWeek.tue: + case DayOfWeek.wed: + case DayOfWeek.thu: + return 1; + case DayOfWeek.fri: + case DayOfWeek.sat: + case DayOfWeek.sun: + return 53; + default: + assert(0, "Invalid ISO Week"); + } + } + else if (week > 0) + return cast(ubyte) week; + else + return Date(_year - 1, 12, 31).isoWeek; + } + catch (Exception e) + assert(0, "Date's constructor threw."); + } + + @safe unittest + { + // Test A.D. + assert(Date(2009, 12, 28).isoWeek == 53); + assert(Date(2009, 12, 29).isoWeek == 53); + assert(Date(2009, 12, 30).isoWeek == 53); + assert(Date(2009, 12, 31).isoWeek == 53); + assert(Date(2010, 1, 1).isoWeek == 53); + assert(Date(2010, 1, 2).isoWeek == 53); + assert(Date(2010, 1, 3).isoWeek == 53); + assert(Date(2010, 1, 4).isoWeek == 1); + assert(Date(2010, 1, 5).isoWeek == 1); + assert(Date(2010, 1, 6).isoWeek == 1); + assert(Date(2010, 1, 7).isoWeek == 1); + assert(Date(2010, 1, 8).isoWeek == 1); + assert(Date(2010, 1, 9).isoWeek == 1); + assert(Date(2010, 1, 10).isoWeek == 1); + assert(Date(2010, 1, 11).isoWeek == 2); + assert(Date(2010, 12, 31).isoWeek == 52); + + assert(Date(2004, 12, 26).isoWeek == 52); + assert(Date(2004, 12, 27).isoWeek == 53); + assert(Date(2004, 12, 28).isoWeek == 53); + assert(Date(2004, 12, 29).isoWeek == 53); + assert(Date(2004, 12, 30).isoWeek == 53); + assert(Date(2004, 12, 31).isoWeek == 53); + assert(Date(2005, 1, 1).isoWeek == 53); + assert(Date(2005, 1, 2).isoWeek == 53); + + assert(Date(2005, 12, 31).isoWeek == 52); + assert(Date(2007, 1, 1).isoWeek == 1); + + assert(Date(2007, 12, 30).isoWeek == 52); + assert(Date(2007, 12, 31).isoWeek == 1); + assert(Date(2008, 1, 1).isoWeek == 1); + + assert(Date(2008, 12, 28).isoWeek == 52); + assert(Date(2008, 12, 29).isoWeek == 1); + assert(Date(2008, 12, 30).isoWeek == 1); + assert(Date(2008, 12, 31).isoWeek == 1); + assert(Date(2009, 1, 1).isoWeek == 1); + assert(Date(2009, 1, 2).isoWeek == 1); + assert(Date(2009, 1, 3).isoWeek == 1); + assert(Date(2009, 1, 4).isoWeek == 1); + + // Test B.C. + // The algorithm should work identically for both A.D. and B.C. since + // it doesn't really take the year into account, so B.C. testing + // probably isn't really needed. + assert(Date(0, 12, 31).isoWeek == 52); + assert(Date(0, 1, 4).isoWeek == 1); + assert(Date(0, 1, 1).isoWeek == 52); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(cdate.isoWeek == 27); + static assert(!__traits(compiles, cdate.isoWeek = 3)); + assert(idate.isoWeek == 27); + static assert(!__traits(compiles, idate.isoWeek = 3)); + } + + + /++ + $(LREF Date) for the last day in the month that this $(LREF Date) is in. + +/ + @property Date endOfMonth() const @safe pure nothrow + { + try + return Date(_year, _month, maxDay(_year, _month)); + catch (Exception e) + assert(0, "Date's constructor threw."); + } + + /// + @safe unittest + { + assert(Date(1999, 1, 6).endOfMonth == Date(1999, 1, 31)); + assert(Date(1999, 2, 7).endOfMonth == Date(1999, 2, 28)); + assert(Date(2000, 2, 7).endOfMonth == Date(2000, 2, 29)); + assert(Date(2000, 6, 4).endOfMonth == Date(2000, 6, 30)); + } + + @safe unittest + { + // Test A.D. + assert(Date(1999, 1, 1).endOfMonth == Date(1999, 1, 31)); + assert(Date(1999, 2, 1).endOfMonth == Date(1999, 2, 28)); + assert(Date(2000, 2, 1).endOfMonth == Date(2000, 2, 29)); + assert(Date(1999, 3, 1).endOfMonth == Date(1999, 3, 31)); + assert(Date(1999, 4, 1).endOfMonth == Date(1999, 4, 30)); + assert(Date(1999, 5, 1).endOfMonth == Date(1999, 5, 31)); + assert(Date(1999, 6, 1).endOfMonth == Date(1999, 6, 30)); + assert(Date(1999, 7, 1).endOfMonth == Date(1999, 7, 31)); + assert(Date(1999, 8, 1).endOfMonth == Date(1999, 8, 31)); + assert(Date(1999, 9, 1).endOfMonth == Date(1999, 9, 30)); + assert(Date(1999, 10, 1).endOfMonth == Date(1999, 10, 31)); + assert(Date(1999, 11, 1).endOfMonth == Date(1999, 11, 30)); + assert(Date(1999, 12, 1).endOfMonth == Date(1999, 12, 31)); + + // Test B.C. + assert(Date(-1999, 1, 1).endOfMonth == Date(-1999, 1, 31)); + assert(Date(-1999, 2, 1).endOfMonth == Date(-1999, 2, 28)); + assert(Date(-2000, 2, 1).endOfMonth == Date(-2000, 2, 29)); + assert(Date(-1999, 3, 1).endOfMonth == Date(-1999, 3, 31)); + assert(Date(-1999, 4, 1).endOfMonth == Date(-1999, 4, 30)); + assert(Date(-1999, 5, 1).endOfMonth == Date(-1999, 5, 31)); + assert(Date(-1999, 6, 1).endOfMonth == Date(-1999, 6, 30)); + assert(Date(-1999, 7, 1).endOfMonth == Date(-1999, 7, 31)); + assert(Date(-1999, 8, 1).endOfMonth == Date(-1999, 8, 31)); + assert(Date(-1999, 9, 1).endOfMonth == Date(-1999, 9, 30)); + assert(Date(-1999, 10, 1).endOfMonth == Date(-1999, 10, 31)); + assert(Date(-1999, 11, 1).endOfMonth == Date(-1999, 11, 30)); + assert(Date(-1999, 12, 1).endOfMonth == Date(-1999, 12, 31)); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + static assert(!__traits(compiles, cdate.endOfMonth = Date(1999, 7, 30))); + static assert(!__traits(compiles, idate.endOfMonth = Date(1999, 7, 30))); + } + + + /++ + The last day in the month that this $(LREF Date) is in. + +/ + @property ubyte daysInMonth() const @safe pure nothrow @nogc + { + return maxDay(_year, _month); + } + + /// + @safe unittest + { + assert(Date(1999, 1, 6).daysInMonth == 31); + assert(Date(1999, 2, 7).daysInMonth == 28); + assert(Date(2000, 2, 7).daysInMonth == 29); + assert(Date(2000, 6, 4).daysInMonth == 30); + } + + @safe unittest + { + // Test A.D. + assert(Date(1999, 1, 1).daysInMonth == 31); + assert(Date(1999, 2, 1).daysInMonth == 28); + assert(Date(2000, 2, 1).daysInMonth == 29); + assert(Date(1999, 3, 1).daysInMonth == 31); + assert(Date(1999, 4, 1).daysInMonth == 30); + assert(Date(1999, 5, 1).daysInMonth == 31); + assert(Date(1999, 6, 1).daysInMonth == 30); + assert(Date(1999, 7, 1).daysInMonth == 31); + assert(Date(1999, 8, 1).daysInMonth == 31); + assert(Date(1999, 9, 1).daysInMonth == 30); + assert(Date(1999, 10, 1).daysInMonth == 31); + assert(Date(1999, 11, 1).daysInMonth == 30); + assert(Date(1999, 12, 1).daysInMonth == 31); + + // Test B.C. + assert(Date(-1999, 1, 1).daysInMonth == 31); + assert(Date(-1999, 2, 1).daysInMonth == 28); + assert(Date(-2000, 2, 1).daysInMonth == 29); + assert(Date(-1999, 3, 1).daysInMonth == 31); + assert(Date(-1999, 4, 1).daysInMonth == 30); + assert(Date(-1999, 5, 1).daysInMonth == 31); + assert(Date(-1999, 6, 1).daysInMonth == 30); + assert(Date(-1999, 7, 1).daysInMonth == 31); + assert(Date(-1999, 8, 1).daysInMonth == 31); + assert(Date(-1999, 9, 1).daysInMonth == 30); + assert(Date(-1999, 10, 1).daysInMonth == 31); + assert(Date(-1999, 11, 1).daysInMonth == 30); + assert(Date(-1999, 12, 1).daysInMonth == 31); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + static assert(!__traits(compiles, cdate.daysInMonth = 30)); + static assert(!__traits(compiles, idate.daysInMonth = 30)); + } + + + /++ + Whether the current year is a date in A.D. + +/ + @property bool isAD() const @safe pure nothrow @nogc + { + return _year > 0; + } + + /// + @safe unittest + { + assert(Date(1, 1, 1).isAD); + assert(Date(2010, 12, 31).isAD); + assert(!Date(0, 12, 31).isAD); + assert(!Date(-2010, 1, 1).isAD); + } + + @safe unittest + { + assert(Date(2010, 7, 4).isAD); + assert(Date(1, 1, 1).isAD); + assert(!Date(0, 1, 1).isAD); + assert(!Date(-1, 1, 1).isAD); + assert(!Date(-2010, 7, 4).isAD); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(cdate.isAD); + assert(idate.isAD); + } + + + /++ + The $(HTTP en.wikipedia.org/wiki/Julian_day, Julian day) for this + $(LREF Date) at noon (since the Julian day changes at noon). + +/ + @property long julianDay() const @safe pure nothrow @nogc + { + return dayOfGregorianCal + 1_721_425; + } + + @safe unittest + { + assert(Date(-4713, 11, 24).julianDay == 0); + assert(Date(0, 12, 31).julianDay == 1_721_425); + assert(Date(1, 1, 1).julianDay == 1_721_426); + assert(Date(1582, 10, 15).julianDay == 2_299_161); + assert(Date(1858, 11, 17).julianDay == 2_400_001); + assert(Date(1982, 1, 4).julianDay == 2_444_974); + assert(Date(1996, 3, 31).julianDay == 2_450_174); + assert(Date(2010, 8, 24).julianDay == 2_455_433); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(cdate.julianDay == 2_451_366); + assert(idate.julianDay == 2_451_366); + } + + + /++ + The modified $(HTTP en.wikipedia.org/wiki/Julian_day, Julian day) for + any time on this date (since, the modified Julian day changes at + midnight). + +/ + @property long modJulianDay() const @safe pure nothrow @nogc + { + return julianDay - 2_400_001; + } + + @safe unittest + { + assert(Date(1858, 11, 17).modJulianDay == 0); + assert(Date(2010, 8, 24).modJulianDay == 55_432); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(cdate.modJulianDay == 51_365); + assert(idate.modJulianDay == 51_365); + } + + + /++ + Converts this $(LREF Date) to a string with the format YYYYMMDD. + +/ + string toISOString() const @safe pure nothrow + { + import std.format : format; + try + { + if (_year >= 0) + { + if (_year < 10_000) + return format("%04d%02d%02d", _year, _month, _day); + else + return format("+%05d%02d%02d", _year, _month, _day); + } + else if (_year > -10_000) + return format("%05d%02d%02d", _year, _month, _day); + else + return format("%06d%02d%02d", _year, _month, _day); + } + catch (Exception e) + assert(0, "format() threw."); + } + + /// + @safe unittest + { + assert(Date(2010, 7, 4).toISOString() == "20100704"); + assert(Date(1998, 12, 25).toISOString() == "19981225"); + assert(Date(0, 1, 5).toISOString() == "00000105"); + assert(Date(-4, 1, 5).toISOString() == "-00040105"); + } + + @safe unittest + { + // Test A.D. + assert(Date(9, 12, 4).toISOString() == "00091204"); + assert(Date(99, 12, 4).toISOString() == "00991204"); + assert(Date(999, 12, 4).toISOString() == "09991204"); + assert(Date(9999, 7, 4).toISOString() == "99990704"); + assert(Date(10000, 10, 20).toISOString() == "+100001020"); + + // Test B.C. + assert(Date(0, 12, 4).toISOString() == "00001204"); + assert(Date(-9, 12, 4).toISOString() == "-00091204"); + assert(Date(-99, 12, 4).toISOString() == "-00991204"); + assert(Date(-999, 12, 4).toISOString() == "-09991204"); + assert(Date(-9999, 7, 4).toISOString() == "-99990704"); + assert(Date(-10000, 10, 20).toISOString() == "-100001020"); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(cdate.toISOString() == "19990706"); + assert(idate.toISOString() == "19990706"); + } + + /++ + Converts this $(LREF Date) to a string with the format YYYY-MM-DD. + +/ + string toISOExtString() const @safe pure nothrow + { + import std.format : format; + try + { + if (_year >= 0) + { + if (_year < 10_000) + return format("%04d-%02d-%02d", _year, _month, _day); + else + return format("+%05d-%02d-%02d", _year, _month, _day); + } + else if (_year > -10_000) + return format("%05d-%02d-%02d", _year, _month, _day); + else + return format("%06d-%02d-%02d", _year, _month, _day); + } + catch (Exception e) + assert(0, "format() threw."); + } + + /// + @safe unittest + { + assert(Date(2010, 7, 4).toISOExtString() == "2010-07-04"); + assert(Date(1998, 12, 25).toISOExtString() == "1998-12-25"); + assert(Date(0, 1, 5).toISOExtString() == "0000-01-05"); + assert(Date(-4, 1, 5).toISOExtString() == "-0004-01-05"); + } + + @safe unittest + { + // Test A.D. + assert(Date(9, 12, 4).toISOExtString() == "0009-12-04"); + assert(Date(99, 12, 4).toISOExtString() == "0099-12-04"); + assert(Date(999, 12, 4).toISOExtString() == "0999-12-04"); + assert(Date(9999, 7, 4).toISOExtString() == "9999-07-04"); + assert(Date(10000, 10, 20).toISOExtString() == "+10000-10-20"); + + // Test B.C. + assert(Date(0, 12, 4).toISOExtString() == "0000-12-04"); + assert(Date(-9, 12, 4).toISOExtString() == "-0009-12-04"); + assert(Date(-99, 12, 4).toISOExtString() == "-0099-12-04"); + assert(Date(-999, 12, 4).toISOExtString() == "-0999-12-04"); + assert(Date(-9999, 7, 4).toISOExtString() == "-9999-07-04"); + assert(Date(-10000, 10, 20).toISOExtString() == "-10000-10-20"); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(cdate.toISOExtString() == "1999-07-06"); + assert(idate.toISOExtString() == "1999-07-06"); + } + + /++ + Converts this $(LREF Date) to a string with the format YYYY-Mon-DD. + +/ + string toSimpleString() const @safe pure nothrow + { + import std.format : format; + try + { + if (_year >= 0) + { + if (_year < 10_000) + return format("%04d-%s-%02d", _year, monthToString(_month), _day); + else + return format("+%05d-%s-%02d", _year, monthToString(_month), _day); + } + else if (_year > -10_000) + return format("%05d-%s-%02d", _year, monthToString(_month), _day); + else + return format("%06d-%s-%02d", _year, monthToString(_month), _day); + } + catch (Exception e) + assert(0, "format() threw."); + } + + /// + @safe unittest + { + assert(Date(2010, 7, 4).toSimpleString() == "2010-Jul-04"); + assert(Date(1998, 12, 25).toSimpleString() == "1998-Dec-25"); + assert(Date(0, 1, 5).toSimpleString() == "0000-Jan-05"); + assert(Date(-4, 1, 5).toSimpleString() == "-0004-Jan-05"); + } + + @safe unittest + { + // Test A.D. + assert(Date(9, 12, 4).toSimpleString() == "0009-Dec-04"); + assert(Date(99, 12, 4).toSimpleString() == "0099-Dec-04"); + assert(Date(999, 12, 4).toSimpleString() == "0999-Dec-04"); + assert(Date(9999, 7, 4).toSimpleString() == "9999-Jul-04"); + assert(Date(10000, 10, 20).toSimpleString() == "+10000-Oct-20"); + + // Test B.C. + assert(Date(0, 12, 4).toSimpleString() == "0000-Dec-04"); + assert(Date(-9, 12, 4).toSimpleString() == "-0009-Dec-04"); + assert(Date(-99, 12, 4).toSimpleString() == "-0099-Dec-04"); + assert(Date(-999, 12, 4).toSimpleString() == "-0999-Dec-04"); + assert(Date(-9999, 7, 4).toSimpleString() == "-9999-Jul-04"); + assert(Date(-10000, 10, 20).toSimpleString() == "-10000-Oct-20"); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(cdate.toSimpleString() == "1999-Jul-06"); + assert(idate.toSimpleString() == "1999-Jul-06"); + } + + + /++ + Converts this $(LREF Date) to a string. + + This function exists to make it easy to convert a $(LREF Date) to a + string for code that does not care what the exact format is - just that + it presents the information in a clear manner. It also makes it easy to + simply convert a $(LREF Date) to a string when using functions such as + `to!string`, `format`, or `writeln` which use toString to convert + user-defined types. So, it is unlikely that much code will call + toString directly. + + The format of the string is purposefully unspecified, and code that + cares about the format of the string should use `toISOString`, + `toISOExtString`, `toSimpleString`, or some other custom formatting + function that explicitly generates the format that the code needs. The + reason is that the code is then clear about what format it's using, + making it less error-prone to maintain the code and interact with other + software that consumes the generated strings. It's for this same reason + $(LREF Date) has no `fromString` function, whereas it does have + `fromISOString`, `fromISOExtString`, and `fromSimpleString`. + + The format returned by toString may or may not change in the future. + +/ + string toString() const @safe pure nothrow + { + return toSimpleString(); + } + + @safe unittest + { + auto date = Date(1999, 7, 6); + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(date.toString()); + assert(cdate.toString()); + assert(idate.toString()); + } + + + /++ + Creates a $(LREF Date) from a string with the format YYYYMMDD. Whitespace + is stripped from the given string. + + Params: + isoString = A string formatted in the ISO format for dates. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given string is + not in the ISO format or if the resulting $(LREF Date) would not be + valid. + +/ + static Date fromISOString(S)(in S isoString) @safe pure + if (isSomeString!S) + { + import std.algorithm.searching : startsWith; + import std.conv : to, text, ConvException; + import std.exception : enforce; + import std.string : strip; + + auto str = isoString.strip; + + enforce!DateTimeException(str.length >= 8, text("Invalid ISO String: ", isoString)); + + int day, month, year; + auto yearStr = str[0 .. $ - 4]; + + try + { + // using conversion to uint plus cast because it checks for +/- + // for us quickly while throwing ConvException + day = cast(int) to!uint(str[$ - 2 .. $]); + month = cast(int) to!uint(str[$ - 4 .. $ - 2]); + + if (yearStr.length > 4) + { + enforce!DateTimeException(yearStr.startsWith('-', '+'), + text("Invalid ISO String: ", isoString)); + year = to!int(yearStr); + } + else + { + year = cast(int) to!uint(yearStr); + } + } + catch (ConvException) + { + throw new DateTimeException(text("Invalid ISO String: ", isoString)); + } + + return Date(year, month, day); + } + + /// + @safe unittest + { + assert(Date.fromISOString("20100704") == Date(2010, 7, 4)); + assert(Date.fromISOString("19981225") == Date(1998, 12, 25)); + assert(Date.fromISOString("00000105") == Date(0, 1, 5)); + assert(Date.fromISOString("-00040105") == Date(-4, 1, 5)); + assert(Date.fromISOString(" 20100704 ") == Date(2010, 7, 4)); + } + + @safe unittest + { + assertThrown!DateTimeException(Date.fromISOString("")); + assertThrown!DateTimeException(Date.fromISOString("990704")); + assertThrown!DateTimeException(Date.fromISOString("0100704")); + assertThrown!DateTimeException(Date.fromISOString("2010070")); + assertThrown!DateTimeException(Date.fromISOString("2010070 ")); + assertThrown!DateTimeException(Date.fromISOString("120100704")); + assertThrown!DateTimeException(Date.fromISOString("-0100704")); + assertThrown!DateTimeException(Date.fromISOString("+0100704")); + assertThrown!DateTimeException(Date.fromISOString("2010070a")); + assertThrown!DateTimeException(Date.fromISOString("20100a04")); + assertThrown!DateTimeException(Date.fromISOString("2010a704")); + + assertThrown!DateTimeException(Date.fromISOString("99-07-04")); + assertThrown!DateTimeException(Date.fromISOString("010-07-04")); + assertThrown!DateTimeException(Date.fromISOString("2010-07-0")); + assertThrown!DateTimeException(Date.fromISOString("2010-07-0 ")); + assertThrown!DateTimeException(Date.fromISOString("12010-07-04")); + assertThrown!DateTimeException(Date.fromISOString("-010-07-04")); + assertThrown!DateTimeException(Date.fromISOString("+010-07-04")); + assertThrown!DateTimeException(Date.fromISOString("2010-07-0a")); + assertThrown!DateTimeException(Date.fromISOString("2010-0a-04")); + assertThrown!DateTimeException(Date.fromISOString("2010-a7-04")); + assertThrown!DateTimeException(Date.fromISOString("2010/07/04")); + assertThrown!DateTimeException(Date.fromISOString("2010/7/04")); + assertThrown!DateTimeException(Date.fromISOString("2010/7/4")); + assertThrown!DateTimeException(Date.fromISOString("2010/07/4")); + assertThrown!DateTimeException(Date.fromISOString("2010-7-04")); + assertThrown!DateTimeException(Date.fromISOString("2010-7-4")); + assertThrown!DateTimeException(Date.fromISOString("2010-07-4")); + + assertThrown!DateTimeException(Date.fromISOString("99Jul04")); + assertThrown!DateTimeException(Date.fromISOString("010Jul04")); + assertThrown!DateTimeException(Date.fromISOString("2010Jul0")); + assertThrown!DateTimeException(Date.fromISOString("2010Jul0 ")); + assertThrown!DateTimeException(Date.fromISOString("12010Jul04")); + assertThrown!DateTimeException(Date.fromISOString("-010Jul04")); + assertThrown!DateTimeException(Date.fromISOString("+010Jul04")); + assertThrown!DateTimeException(Date.fromISOString("2010Jul0a")); + assertThrown!DateTimeException(Date.fromISOString("2010Jua04")); + assertThrown!DateTimeException(Date.fromISOString("2010aul04")); + + assertThrown!DateTimeException(Date.fromISOString("99-Jul-04")); + assertThrown!DateTimeException(Date.fromISOString("010-Jul-04")); + assertThrown!DateTimeException(Date.fromISOString("2010-Jul-0")); + assertThrown!DateTimeException(Date.fromISOString("2010-Jul-0 ")); + assertThrown!DateTimeException(Date.fromISOString("12010-Jul-04")); + assertThrown!DateTimeException(Date.fromISOString("-010-Jul-04")); + assertThrown!DateTimeException(Date.fromISOString("+010-Jul-04")); + assertThrown!DateTimeException(Date.fromISOString("2010-Jul-0a")); + assertThrown!DateTimeException(Date.fromISOString("2010-Jua-04")); + assertThrown!DateTimeException(Date.fromISOString("2010-Jal-04")); + assertThrown!DateTimeException(Date.fromISOString("2010-aul-04")); + + assertThrown!DateTimeException(Date.fromISOString("2010-07-04")); + assertThrown!DateTimeException(Date.fromISOString("2010-Jul-04")); + + assert(Date.fromISOString("19990706") == Date(1999, 7, 6)); + assert(Date.fromISOString("-19990706") == Date(-1999, 7, 6)); + assert(Date.fromISOString("+019990706") == Date(1999, 7, 6)); + assert(Date.fromISOString("19990706 ") == Date(1999, 7, 6)); + assert(Date.fromISOString(" 19990706") == Date(1999, 7, 6)); + assert(Date.fromISOString(" 19990706 ") == Date(1999, 7, 6)); + } + + // bug# 17801 + @safe unittest + { + import std.conv : to; + import std.meta : AliasSeq; + foreach (C; AliasSeq!(char, wchar, dchar)) + { + foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + assert(Date.fromISOString(to!S("20121221")) == Date(2012, 12, 21)); + } + } + + + /++ + Creates a $(LREF Date) from a string with the format YYYY-MM-DD. + Whitespace is stripped from the given string. + + Params: + isoExtString = A string formatted in the ISO Extended format for + dates. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given string is + not in the ISO Extended format or if the resulting $(LREF Date) + would not be valid. + +/ + static Date fromISOExtString(S)(in S isoExtString) @safe pure + if (isSomeString!(S)) + { + import std.algorithm.searching : all, startsWith; + import std.ascii : isDigit; + import std.conv : to; + import std.exception : enforce; + import std.format : format; + import std.string : strip; + + auto dstr = to!dstring(strip(isoExtString)); + + enforce(dstr.length >= 10, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); + + auto day = dstr[$-2 .. $]; + auto month = dstr[$-5 .. $-3]; + auto year = dstr[0 .. $-6]; + + enforce(dstr[$-3] == '-', new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); + enforce(dstr[$-6] == '-', new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); + enforce(all!isDigit(day), + new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); + enforce(all!isDigit(month), + new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); + + if (year.length > 4) + { + enforce(year.startsWith('-', '+'), + new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); + enforce(all!isDigit(year[1..$]), + new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); + } + else + enforce(all!isDigit(year), + new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); + + return Date(to!short(year), to!ubyte(month), to!ubyte(day)); + } + + /// + @safe unittest + { + assert(Date.fromISOExtString("2010-07-04") == Date(2010, 7, 4)); + assert(Date.fromISOExtString("1998-12-25") == Date(1998, 12, 25)); + assert(Date.fromISOExtString("0000-01-05") == Date(0, 1, 5)); + assert(Date.fromISOExtString("-0004-01-05") == Date(-4, 1, 5)); + assert(Date.fromISOExtString(" 2010-07-04 ") == Date(2010, 7, 4)); + } + + @safe unittest + { + assertThrown!DateTimeException(Date.fromISOExtString("")); + assertThrown!DateTimeException(Date.fromISOExtString("990704")); + assertThrown!DateTimeException(Date.fromISOExtString("0100704")); + assertThrown!DateTimeException(Date.fromISOExtString("2010070")); + assertThrown!DateTimeException(Date.fromISOExtString("2010070 ")); + assertThrown!DateTimeException(Date.fromISOExtString("120100704")); + assertThrown!DateTimeException(Date.fromISOExtString("-0100704")); + assertThrown!DateTimeException(Date.fromISOExtString("+0100704")); + assertThrown!DateTimeException(Date.fromISOExtString("2010070a")); + assertThrown!DateTimeException(Date.fromISOExtString("20100a04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010a704")); + + assertThrown!DateTimeException(Date.fromISOExtString("99-07-04")); + assertThrown!DateTimeException(Date.fromISOExtString("010-07-04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-07-0")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-07-0 ")); + assertThrown!DateTimeException(Date.fromISOExtString("12010-07-04")); + assertThrown!DateTimeException(Date.fromISOExtString("-010-07-04")); + assertThrown!DateTimeException(Date.fromISOExtString("+010-07-04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-07-0a")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-0a-04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-a7-04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010/07/04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010/7/04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010/7/4")); + assertThrown!DateTimeException(Date.fromISOExtString("2010/07/4")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-7-04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-7-4")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-07-4")); + + assertThrown!DateTimeException(Date.fromISOExtString("99Jul04")); + assertThrown!DateTimeException(Date.fromISOExtString("010Jul04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010Jul0")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-0 ")); + assertThrown!DateTimeException(Date.fromISOExtString("12010Jul04")); + assertThrown!DateTimeException(Date.fromISOExtString("-010Jul04")); + assertThrown!DateTimeException(Date.fromISOExtString("+010Jul04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010Jul0a")); + assertThrown!DateTimeException(Date.fromISOExtString("2010Jua04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010aul04")); + + assertThrown!DateTimeException(Date.fromISOExtString("99-Jul-04")); + assertThrown!DateTimeException(Date.fromISOExtString("010-Jul-04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-0")); + assertThrown!DateTimeException(Date.fromISOExtString("2010Jul0 ")); + assertThrown!DateTimeException(Date.fromISOExtString("12010-Jul-04")); + assertThrown!DateTimeException(Date.fromISOExtString("-010-Jul-04")); + assertThrown!DateTimeException(Date.fromISOExtString("+010-Jul-04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-0a")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-Jua-04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-Jal-04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-aul-04")); + + assertThrown!DateTimeException(Date.fromISOExtString("20100704")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-04")); + + assert(Date.fromISOExtString("1999-07-06") == Date(1999, 7, 6)); + assert(Date.fromISOExtString("-1999-07-06") == Date(-1999, 7, 6)); + assert(Date.fromISOExtString("+01999-07-06") == Date(1999, 7, 6)); + assert(Date.fromISOExtString("1999-07-06 ") == Date(1999, 7, 6)); + assert(Date.fromISOExtString(" 1999-07-06") == Date(1999, 7, 6)); + assert(Date.fromISOExtString(" 1999-07-06 ") == Date(1999, 7, 6)); + } + + // bug# 17801 + @safe unittest + { + import std.conv : to; + import std.meta : AliasSeq; + foreach (C; AliasSeq!(char, wchar, dchar)) + { + foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + assert(Date.fromISOExtString(to!S("2012-12-21")) == Date(2012, 12, 21)); + } + } + + + /++ + Creates a $(LREF Date) from a string with the format YYYY-Mon-DD. + Whitespace is stripped from the given string. + + Params: + simpleString = A string formatted in the way that toSimpleString + formats dates. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given string is + not in the correct format or if the resulting $(LREF Date) would not + be valid. + +/ + static Date fromSimpleString(S)(in S simpleString) @safe pure + if (isSomeString!(S)) + { + import std.algorithm.searching : all, startsWith; + import std.ascii : isDigit; + import std.conv : to; + import std.exception : enforce; + import std.format : format; + import std.string : strip; + + auto dstr = to!dstring(strip(simpleString)); + + enforce(dstr.length >= 11, new DateTimeException(format("Invalid string format: %s", simpleString))); + + auto day = dstr[$-2 .. $]; + auto month = monthFromString(to!string(dstr[$-6 .. $-3])); + auto year = dstr[0 .. $-7]; + + enforce(dstr[$-3] == '-', new DateTimeException(format("Invalid string format: %s", simpleString))); + enforce(dstr[$-7] == '-', new DateTimeException(format("Invalid string format: %s", simpleString))); + enforce(all!isDigit(day), new DateTimeException(format("Invalid string format: %s", simpleString))); + + if (year.length > 4) + { + enforce(year.startsWith('-', '+'), + new DateTimeException(format("Invalid string format: %s", simpleString))); + enforce(all!isDigit(year[1..$]), + new DateTimeException(format("Invalid string format: %s", simpleString))); + } + else + enforce(all!isDigit(year), + new DateTimeException(format("Invalid string format: %s", simpleString))); + + return Date(to!short(year), month, to!ubyte(day)); + } + + /// + @safe unittest + { + assert(Date.fromSimpleString("2010-Jul-04") == Date(2010, 7, 4)); + assert(Date.fromSimpleString("1998-Dec-25") == Date(1998, 12, 25)); + assert(Date.fromSimpleString("0000-Jan-05") == Date(0, 1, 5)); + assert(Date.fromSimpleString("-0004-Jan-05") == Date(-4, 1, 5)); + assert(Date.fromSimpleString(" 2010-Jul-04 ") == Date(2010, 7, 4)); + } + + @safe unittest + { + assertThrown!DateTimeException(Date.fromSimpleString("")); + assertThrown!DateTimeException(Date.fromSimpleString("990704")); + assertThrown!DateTimeException(Date.fromSimpleString("0100704")); + assertThrown!DateTimeException(Date.fromSimpleString("2010070")); + assertThrown!DateTimeException(Date.fromSimpleString("2010070 ")); + assertThrown!DateTimeException(Date.fromSimpleString("120100704")); + assertThrown!DateTimeException(Date.fromSimpleString("-0100704")); + assertThrown!DateTimeException(Date.fromSimpleString("+0100704")); + assertThrown!DateTimeException(Date.fromSimpleString("2010070a")); + assertThrown!DateTimeException(Date.fromSimpleString("20100a04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010a704")); + + assertThrown!DateTimeException(Date.fromSimpleString("99-07-04")); + assertThrown!DateTimeException(Date.fromSimpleString("010-07-04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-07-0")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-07-0 ")); + assertThrown!DateTimeException(Date.fromSimpleString("12010-07-04")); + assertThrown!DateTimeException(Date.fromSimpleString("-010-07-04")); + assertThrown!DateTimeException(Date.fromSimpleString("+010-07-04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-07-0a")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-0a-04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-a7-04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010/07/04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010/7/04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010/7/4")); + assertThrown!DateTimeException(Date.fromSimpleString("2010/07/4")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-7-04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-7-4")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-07-4")); + + assertThrown!DateTimeException(Date.fromSimpleString("99Jul04")); + assertThrown!DateTimeException(Date.fromSimpleString("010Jul04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010Jul0")); + assertThrown!DateTimeException(Date.fromSimpleString("2010Jul0 ")); + assertThrown!DateTimeException(Date.fromSimpleString("12010Jul04")); + assertThrown!DateTimeException(Date.fromSimpleString("-010Jul04")); + assertThrown!DateTimeException(Date.fromSimpleString("+010Jul04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010Jul0a")); + assertThrown!DateTimeException(Date.fromSimpleString("2010Jua04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010aul04")); + + assertThrown!DateTimeException(Date.fromSimpleString("99-Jul-04")); + assertThrown!DateTimeException(Date.fromSimpleString("010-Jul-04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-Jul-0")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-Jul-0 ")); + assertThrown!DateTimeException(Date.fromSimpleString("12010-Jul-04")); + assertThrown!DateTimeException(Date.fromSimpleString("-010-Jul-04")); + assertThrown!DateTimeException(Date.fromSimpleString("+010-Jul-04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-Jul-0a")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-Jua-04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-Jal-04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-aul-04")); + + assertThrown!DateTimeException(Date.fromSimpleString("20100704")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-07-04")); + + assert(Date.fromSimpleString("1999-Jul-06") == Date(1999, 7, 6)); + assert(Date.fromSimpleString("-1999-Jul-06") == Date(-1999, 7, 6)); + assert(Date.fromSimpleString("+01999-Jul-06") == Date(1999, 7, 6)); + assert(Date.fromSimpleString("1999-Jul-06 ") == Date(1999, 7, 6)); + assert(Date.fromSimpleString(" 1999-Jul-06") == Date(1999, 7, 6)); + assert(Date.fromSimpleString(" 1999-Jul-06 ") == Date(1999, 7, 6)); + } + + // bug# 17801 + @safe unittest + { + import std.conv : to; + import std.meta : AliasSeq; + foreach (C; AliasSeq!(char, wchar, dchar)) + { + foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + assert(Date.fromSimpleString(to!S("2012-Dec-21")) == Date(2012, 12, 21)); + } + } + + + /++ + Returns the $(LREF Date) farthest in the past which is representable by + $(LREF Date). + +/ + @property static Date min() @safe pure nothrow @nogc + { + auto date = Date.init; + date._year = short.min; + date._month = Month.jan; + date._day = 1; + + return date; + } + + @safe unittest + { + assert(Date.min.year < 0); + assert(Date.min < Date.max); + } + + + /++ + Returns the $(LREF Date) farthest in the future which is representable + by $(LREF Date). + +/ + @property static Date max() @safe pure nothrow @nogc + { + auto date = Date.init; + date._year = short.max; + date._month = Month.dec; + date._day = 31; + + return date; + } + + @safe unittest + { + assert(Date.max.year > 0); + assert(Date.max > Date.min); + } + + +private: + + /+ + Whether the given values form a valid date. + + Params: + year = The year to test. + month = The month of the Gregorian Calendar to test. + day = The day of the month to test. + +/ + static bool _valid(int year, int month, int day) @safe pure nothrow @nogc + { + if (!valid!"months"(month)) + return false; + return valid!"days"(year, month, day); + } + + +package: + + /+ + Adds the given number of days to this $(LREF Date). A negative number + will subtract. + + The month will be adjusted along with the day if the number of days + added (or subtracted) would overflow (or underflow) the current month. + The year will be adjusted along with the month if the increase (or + decrease) to the month would cause it to overflow (or underflow) the + current year. + + $(D _addDays(numDays)) is effectively equivalent to + $(D date.dayOfGregorianCal = date.dayOfGregorianCal + days). + + Params: + days = The number of days to add to this Date. + +/ + ref Date _addDays(long days) return @safe pure nothrow @nogc + { + dayOfGregorianCal = cast(int)(dayOfGregorianCal + days); + return this; + } + + @safe unittest + { + // Test A.D. + { + auto date = Date(1999, 2, 28); + date._addDays(1); + assert(date == Date(1999, 3, 1)); + date._addDays(-1); + assert(date == Date(1999, 2, 28)); + } + + { + auto date = Date(2000, 2, 28); + date._addDays(1); + assert(date == Date(2000, 2, 29)); + date._addDays(1); + assert(date == Date(2000, 3, 1)); + date._addDays(-1); + assert(date == Date(2000, 2, 29)); + } + + { + auto date = Date(1999, 6, 30); + date._addDays(1); + assert(date == Date(1999, 7, 1)); + date._addDays(-1); + assert(date == Date(1999, 6, 30)); + } + + { + auto date = Date(1999, 7, 31); + date._addDays(1); + assert(date == Date(1999, 8, 1)); + date._addDays(-1); + assert(date == Date(1999, 7, 31)); + } + + { + auto date = Date(1999, 1, 1); + date._addDays(-1); + assert(date == Date(1998, 12, 31)); + date._addDays(1); + assert(date == Date(1999, 1, 1)); + } + + { + auto date = Date(1999, 7, 6); + date._addDays(9); + assert(date == Date(1999, 7, 15)); + date._addDays(-11); + assert(date == Date(1999, 7, 4)); + date._addDays(30); + assert(date == Date(1999, 8, 3)); + date._addDays(-3); + assert(date == Date(1999, 7, 31)); + } + + { + auto date = Date(1999, 7, 6); + date._addDays(365); + assert(date == Date(2000, 7, 5)); + date._addDays(-365); + assert(date == Date(1999, 7, 6)); + date._addDays(366); + assert(date == Date(2000, 7, 6)); + date._addDays(730); + assert(date == Date(2002, 7, 6)); + date._addDays(-1096); + assert(date == Date(1999, 7, 6)); + } + + // Test B.C. + { + auto date = Date(-1999, 2, 28); + date._addDays(1); + assert(date == Date(-1999, 3, 1)); + date._addDays(-1); + assert(date == Date(-1999, 2, 28)); + } + + { + auto date = Date(-2000, 2, 28); + date._addDays(1); + assert(date == Date(-2000, 2, 29)); + date._addDays(1); + assert(date == Date(-2000, 3, 1)); + date._addDays(-1); + assert(date == Date(-2000, 2, 29)); + } + + { + auto date = Date(-1999, 6, 30); + date._addDays(1); + assert(date == Date(-1999, 7, 1)); + date._addDays(-1); + assert(date == Date(-1999, 6, 30)); + } + + { + auto date = Date(-1999, 7, 31); + date._addDays(1); + assert(date == Date(-1999, 8, 1)); + date._addDays(-1); + assert(date == Date(-1999, 7, 31)); + } + + { + auto date = Date(-1999, 1, 1); + date._addDays(-1); + assert(date == Date(-2000, 12, 31)); + date._addDays(1); + assert(date == Date(-1999, 1, 1)); + } + + { + auto date = Date(-1999, 7, 6); + date._addDays(9); + assert(date == Date(-1999, 7, 15)); + date._addDays(-11); + assert(date == Date(-1999, 7, 4)); + date._addDays(30); + assert(date == Date(-1999, 8, 3)); + date._addDays(-3); + } + + { + auto date = Date(-1999, 7, 6); + date._addDays(365); + assert(date == Date(-1998, 7, 6)); + date._addDays(-365); + assert(date == Date(-1999, 7, 6)); + date._addDays(366); + assert(date == Date(-1998, 7, 7)); + date._addDays(730); + assert(date == Date(-1996, 7, 6)); + date._addDays(-1096); + assert(date == Date(-1999, 7, 6)); + } + + // Test Both + { + auto date = Date(1, 7, 6); + date._addDays(-365); + assert(date == Date(0, 7, 6)); + date._addDays(365); + assert(date == Date(1, 7, 6)); + date._addDays(-731); + assert(date == Date(-1, 7, 6)); + date._addDays(730); + assert(date == Date(1, 7, 5)); + } + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + static assert(!__traits(compiles, cdate._addDays(12))); + static assert(!__traits(compiles, idate._addDays(12))); + } + + + @safe pure invariant() + { + import std.format : format; + assert(valid!"months"(_month), + format("Invariant Failure: year [%s] month [%s] day [%s]", _year, _month, _day)); + assert(valid!"days"(_year, _month, _day), + format("Invariant Failure: year [%s] month [%s] day [%s]", _year, _month, _day)); + } + + short _year = 1; + Month _month = Month.jan; + ubyte _day = 1; +} + + +/++ + Represents a time of day with hours, minutes, and seconds. It uses 24 hour + time. ++/ +struct TimeOfDay +{ +public: + + /++ + Params: + hour = Hour of the day [0 - 24$(RPAREN). + minute = Minute of the hour [0 - 60$(RPAREN). + second = Second of the minute [0 - 60$(RPAREN). + + Throws: + $(REF DateTimeException,std,datetime,date) if the resulting + $(LREF TimeOfDay) would be not be valid. + +/ + this(int hour, int minute, int second = 0) @safe pure + { + enforceValid!"hours"(hour); + enforceValid!"minutes"(minute); + enforceValid!"seconds"(second); + + _hour = cast(ubyte) hour; + _minute = cast(ubyte) minute; + _second = cast(ubyte) second; + } + + @safe unittest + { + assert(TimeOfDay(0, 0) == TimeOfDay.init); + + { + auto tod = TimeOfDay(0, 0); + assert(tod._hour == 0); + assert(tod._minute == 0); + assert(tod._second == 0); + } + + { + auto tod = TimeOfDay(12, 30, 33); + assert(tod._hour == 12); + assert(tod._minute == 30); + assert(tod._second == 33); + } + + { + auto tod = TimeOfDay(23, 59, 59); + assert(tod._hour == 23); + assert(tod._minute == 59); + assert(tod._second == 59); + } + + assertThrown!DateTimeException(TimeOfDay(24, 0, 0)); + assertThrown!DateTimeException(TimeOfDay(0, 60, 0)); + assertThrown!DateTimeException(TimeOfDay(0, 0, 60)); + } + + + /++ + Compares this $(LREF TimeOfDay) with the given $(LREF TimeOfDay). + + Returns: + $(BOOKTABLE, + $(TR $(TD this < rhs) $(TD < 0)) + $(TR $(TD this == rhs) $(TD 0)) + $(TR $(TD this > rhs) $(TD > 0)) + ) + +/ + int opCmp(in TimeOfDay rhs) const @safe pure nothrow @nogc + { + if (_hour < rhs._hour) + return -1; + if (_hour > rhs._hour) + return 1; + + if (_minute < rhs._minute) + return -1; + if (_minute > rhs._minute) + return 1; + + if (_second < rhs._second) + return -1; + if (_second > rhs._second) + return 1; + + return 0; + } + + @safe unittest + { + assert(TimeOfDay(0, 0, 0).opCmp(TimeOfDay.init) == 0); + + assert(TimeOfDay(0, 0, 0).opCmp(TimeOfDay(0, 0, 0)) == 0); + assert(TimeOfDay(12, 0, 0).opCmp(TimeOfDay(12, 0, 0)) == 0); + assert(TimeOfDay(0, 30, 0).opCmp(TimeOfDay(0, 30, 0)) == 0); + assert(TimeOfDay(0, 0, 33).opCmp(TimeOfDay(0, 0, 33)) == 0); + + assert(TimeOfDay(12, 30, 0).opCmp(TimeOfDay(12, 30, 0)) == 0); + assert(TimeOfDay(12, 30, 33).opCmp(TimeOfDay(12, 30, 33)) == 0); + + assert(TimeOfDay(0, 30, 33).opCmp(TimeOfDay(0, 30, 33)) == 0); + assert(TimeOfDay(0, 0, 33).opCmp(TimeOfDay(0, 0, 33)) == 0); + + assert(TimeOfDay(12, 30, 33).opCmp(TimeOfDay(13, 30, 33)) < 0); + assert(TimeOfDay(13, 30, 33).opCmp(TimeOfDay(12, 30, 33)) > 0); + assert(TimeOfDay(12, 30, 33).opCmp(TimeOfDay(12, 31, 33)) < 0); + assert(TimeOfDay(12, 31, 33).opCmp(TimeOfDay(12, 30, 33)) > 0); + assert(TimeOfDay(12, 30, 33).opCmp(TimeOfDay(12, 30, 34)) < 0); + assert(TimeOfDay(12, 30, 34).opCmp(TimeOfDay(12, 30, 33)) > 0); + + assert(TimeOfDay(13, 30, 33).opCmp(TimeOfDay(12, 30, 34)) > 0); + assert(TimeOfDay(12, 30, 34).opCmp(TimeOfDay(13, 30, 33)) < 0); + assert(TimeOfDay(13, 30, 33).opCmp(TimeOfDay(12, 31, 33)) > 0); + assert(TimeOfDay(12, 31, 33).opCmp(TimeOfDay(13, 30, 33)) < 0); + + assert(TimeOfDay(12, 31, 33).opCmp(TimeOfDay(12, 30, 34)) > 0); + assert(TimeOfDay(12, 30, 34).opCmp(TimeOfDay(12, 31, 33)) < 0); + + const ctod = TimeOfDay(12, 30, 33); + immutable itod = TimeOfDay(12, 30, 33); + assert(ctod.opCmp(itod) == 0); + assert(itod.opCmp(ctod) == 0); + } + + + /++ + Hours past midnight. + +/ + @property ubyte hour() const @safe pure nothrow @nogc + { + return _hour; + } + + @safe unittest + { + assert(TimeOfDay.init.hour == 0); + assert(TimeOfDay(12, 0, 0).hour == 12); + + const ctod = TimeOfDay(12, 0, 0); + immutable itod = TimeOfDay(12, 0, 0); + assert(ctod.hour == 12); + assert(itod.hour == 12); + } + + + /++ + Hours past midnight. + + Params: + hour = The hour of the day to set this $(LREF TimeOfDay)'s hour to. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given hour would + result in an invalid $(LREF TimeOfDay). + +/ + @property void hour(int hour) @safe pure + { + enforceValid!"hours"(hour); + _hour = cast(ubyte) hour; + } + + @safe unittest + { + assertThrown!DateTimeException((){TimeOfDay(0, 0, 0).hour = 24;}()); + + auto tod = TimeOfDay(0, 0, 0); + tod.hour = 12; + assert(tod == TimeOfDay(12, 0, 0)); + + const ctod = TimeOfDay(0, 0, 0); + immutable itod = TimeOfDay(0, 0, 0); + static assert(!__traits(compiles, ctod.hour = 12)); + static assert(!__traits(compiles, itod.hour = 12)); + } + + + /++ + Minutes past the hour. + +/ + @property ubyte minute() const @safe pure nothrow @nogc + { + return _minute; + } + + @safe unittest + { + assert(TimeOfDay.init.minute == 0); + assert(TimeOfDay(0, 30, 0).minute == 30); + + const ctod = TimeOfDay(0, 30, 0); + immutable itod = TimeOfDay(0, 30, 0); + assert(ctod.minute == 30); + assert(itod.minute == 30); + } + + + /++ + Minutes past the hour. + + Params: + minute = The minute to set this $(LREF TimeOfDay)'s minute to. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given minute + would result in an invalid $(LREF TimeOfDay). + +/ + @property void minute(int minute) @safe pure + { + enforceValid!"minutes"(minute); + _minute = cast(ubyte) minute; + } + + @safe unittest + { + assertThrown!DateTimeException((){TimeOfDay(0, 0, 0).minute = 60;}()); + + auto tod = TimeOfDay(0, 0, 0); + tod.minute = 30; + assert(tod == TimeOfDay(0, 30, 0)); + + const ctod = TimeOfDay(0, 0, 0); + immutable itod = TimeOfDay(0, 0, 0); + static assert(!__traits(compiles, ctod.minute = 30)); + static assert(!__traits(compiles, itod.minute = 30)); + } + + + /++ + Seconds past the minute. + +/ + @property ubyte second() const @safe pure nothrow @nogc + { + return _second; + } + + @safe unittest + { + assert(TimeOfDay.init.second == 0); + assert(TimeOfDay(0, 0, 33).second == 33); + + const ctod = TimeOfDay(0, 0, 33); + immutable itod = TimeOfDay(0, 0, 33); + assert(ctod.second == 33); + assert(itod.second == 33); + } + + + /++ + Seconds past the minute. + + Params: + second = The second to set this $(LREF TimeOfDay)'s second to. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given second + would result in an invalid $(LREF TimeOfDay). + +/ + @property void second(int second) @safe pure + { + enforceValid!"seconds"(second); + _second = cast(ubyte) second; + } + + @safe unittest + { + assertThrown!DateTimeException((){TimeOfDay(0, 0, 0).second = 60;}()); + + auto tod = TimeOfDay(0, 0, 0); + tod.second = 33; + assert(tod == TimeOfDay(0, 0, 33)); + + const ctod = TimeOfDay(0, 0, 0); + immutable itod = TimeOfDay(0, 0, 0); + static assert(!__traits(compiles, ctod.second = 33)); + static assert(!__traits(compiles, itod.second = 33)); + } + + + /++ + Adds the given number of units to this $(LREF TimeOfDay). A negative + number will subtract. + + The difference between rolling and adding is that rolling does not + affect larger units. For instance, rolling a $(LREF TimeOfDay) + one hours's worth of minutes gets the exact same + $(LREF TimeOfDay). + + Accepted units are $(D "hours"), $(D "minutes"), and $(D "seconds"). + + Params: + units = The units to add. + value = The number of $(D_PARAM units) to add to this + $(LREF TimeOfDay). + +/ + ref TimeOfDay roll(string units)(long value) @safe pure nothrow @nogc + if (units == "hours") + { + return this += dur!"hours"(value); + } + + /// + @safe unittest + { + auto tod1 = TimeOfDay(7, 12, 0); + tod1.roll!"hours"(1); + assert(tod1 == TimeOfDay(8, 12, 0)); + + auto tod2 = TimeOfDay(7, 12, 0); + tod2.roll!"hours"(-1); + assert(tod2 == TimeOfDay(6, 12, 0)); + + auto tod3 = TimeOfDay(23, 59, 0); + tod3.roll!"minutes"(1); + assert(tod3 == TimeOfDay(23, 0, 0)); + + auto tod4 = TimeOfDay(0, 0, 0); + tod4.roll!"minutes"(-1); + assert(tod4 == TimeOfDay(0, 59, 0)); + + auto tod5 = TimeOfDay(23, 59, 59); + tod5.roll!"seconds"(1); + assert(tod5 == TimeOfDay(23, 59, 0)); + + auto tod6 = TimeOfDay(0, 0, 0); + tod6.roll!"seconds"(-1); + assert(tod6 == TimeOfDay(0, 0, 59)); + } + + @safe unittest + { + auto tod = TimeOfDay(12, 27, 2); + tod.roll!"hours"(22).roll!"hours"(-7); + assert(tod == TimeOfDay(3, 27, 2)); + + const ctod = TimeOfDay(0, 0, 0); + immutable itod = TimeOfDay(0, 0, 0); + static assert(!__traits(compiles, ctod.roll!"hours"(53))); + static assert(!__traits(compiles, itod.roll!"hours"(53))); + } + + + // Shares documentation with "hours" version. + ref TimeOfDay roll(string units)(long value) @safe pure nothrow @nogc + if (units == "minutes" || units == "seconds") + { + import std.format : format; + + enum memberVarStr = units[0 .. $ - 1]; + value %= 60; + mixin(format("auto newVal = cast(ubyte)(_%s) + value;", memberVarStr)); + + if (value < 0) + { + if (newVal < 0) + newVal += 60; + } + else if (newVal >= 60) + newVal -= 60; + + mixin(format("_%s = cast(ubyte) newVal;", memberVarStr)); + return this; + } + + // Test roll!"minutes"(). + @safe unittest + { + static void testTOD(TimeOfDay orig, int minutes, in TimeOfDay expected, size_t line = __LINE__) + { + orig.roll!"minutes"(minutes); + assert(orig == expected); + } + + testTOD(TimeOfDay(12, 30, 33), 0, TimeOfDay(12, 30, 33)); + testTOD(TimeOfDay(12, 30, 33), 1, TimeOfDay(12, 31, 33)); + testTOD(TimeOfDay(12, 30, 33), 2, TimeOfDay(12, 32, 33)); + testTOD(TimeOfDay(12, 30, 33), 3, TimeOfDay(12, 33, 33)); + testTOD(TimeOfDay(12, 30, 33), 4, TimeOfDay(12, 34, 33)); + testTOD(TimeOfDay(12, 30, 33), 5, TimeOfDay(12, 35, 33)); + testTOD(TimeOfDay(12, 30, 33), 10, TimeOfDay(12, 40, 33)); + testTOD(TimeOfDay(12, 30, 33), 15, TimeOfDay(12, 45, 33)); + testTOD(TimeOfDay(12, 30, 33), 29, TimeOfDay(12, 59, 33)); + testTOD(TimeOfDay(12, 30, 33), 30, TimeOfDay(12, 0, 33)); + testTOD(TimeOfDay(12, 30, 33), 45, TimeOfDay(12, 15, 33)); + testTOD(TimeOfDay(12, 30, 33), 60, TimeOfDay(12, 30, 33)); + testTOD(TimeOfDay(12, 30, 33), 75, TimeOfDay(12, 45, 33)); + testTOD(TimeOfDay(12, 30, 33), 90, TimeOfDay(12, 0, 33)); + testTOD(TimeOfDay(12, 30, 33), 100, TimeOfDay(12, 10, 33)); + + testTOD(TimeOfDay(12, 30, 33), 689, TimeOfDay(12, 59, 33)); + testTOD(TimeOfDay(12, 30, 33), 690, TimeOfDay(12, 0, 33)); + testTOD(TimeOfDay(12, 30, 33), 691, TimeOfDay(12, 1, 33)); + testTOD(TimeOfDay(12, 30, 33), 960, TimeOfDay(12, 30, 33)); + testTOD(TimeOfDay(12, 30, 33), 1439, TimeOfDay(12, 29, 33)); + testTOD(TimeOfDay(12, 30, 33), 1440, TimeOfDay(12, 30, 33)); + testTOD(TimeOfDay(12, 30, 33), 1441, TimeOfDay(12, 31, 33)); + testTOD(TimeOfDay(12, 30, 33), 2880, TimeOfDay(12, 30, 33)); + + testTOD(TimeOfDay(12, 30, 33), -1, TimeOfDay(12, 29, 33)); + testTOD(TimeOfDay(12, 30, 33), -2, TimeOfDay(12, 28, 33)); + testTOD(TimeOfDay(12, 30, 33), -3, TimeOfDay(12, 27, 33)); + testTOD(TimeOfDay(12, 30, 33), -4, TimeOfDay(12, 26, 33)); + testTOD(TimeOfDay(12, 30, 33), -5, TimeOfDay(12, 25, 33)); + testTOD(TimeOfDay(12, 30, 33), -10, TimeOfDay(12, 20, 33)); + testTOD(TimeOfDay(12, 30, 33), -15, TimeOfDay(12, 15, 33)); + testTOD(TimeOfDay(12, 30, 33), -29, TimeOfDay(12, 1, 33)); + testTOD(TimeOfDay(12, 30, 33), -30, TimeOfDay(12, 0, 33)); + testTOD(TimeOfDay(12, 30, 33), -45, TimeOfDay(12, 45, 33)); + testTOD(TimeOfDay(12, 30, 33), -60, TimeOfDay(12, 30, 33)); + testTOD(TimeOfDay(12, 30, 33), -75, TimeOfDay(12, 15, 33)); + testTOD(TimeOfDay(12, 30, 33), -90, TimeOfDay(12, 0, 33)); + testTOD(TimeOfDay(12, 30, 33), -100, TimeOfDay(12, 50, 33)); + + testTOD(TimeOfDay(12, 30, 33), -749, TimeOfDay(12, 1, 33)); + testTOD(TimeOfDay(12, 30, 33), -750, TimeOfDay(12, 0, 33)); + testTOD(TimeOfDay(12, 30, 33), -751, TimeOfDay(12, 59, 33)); + testTOD(TimeOfDay(12, 30, 33), -960, TimeOfDay(12, 30, 33)); + testTOD(TimeOfDay(12, 30, 33), -1439, TimeOfDay(12, 31, 33)); + testTOD(TimeOfDay(12, 30, 33), -1440, TimeOfDay(12, 30, 33)); + testTOD(TimeOfDay(12, 30, 33), -1441, TimeOfDay(12, 29, 33)); + testTOD(TimeOfDay(12, 30, 33), -2880, TimeOfDay(12, 30, 33)); + + testTOD(TimeOfDay(12, 0, 33), 1, TimeOfDay(12, 1, 33)); + testTOD(TimeOfDay(12, 0, 33), 0, TimeOfDay(12, 0, 33)); + testTOD(TimeOfDay(12, 0, 33), -1, TimeOfDay(12, 59, 33)); + + testTOD(TimeOfDay(11, 59, 33), 1, TimeOfDay(11, 0, 33)); + testTOD(TimeOfDay(11, 59, 33), 0, TimeOfDay(11, 59, 33)); + testTOD(TimeOfDay(11, 59, 33), -1, TimeOfDay(11, 58, 33)); + + testTOD(TimeOfDay(0, 0, 33), 1, TimeOfDay(0, 1, 33)); + testTOD(TimeOfDay(0, 0, 33), 0, TimeOfDay(0, 0, 33)); + testTOD(TimeOfDay(0, 0, 33), -1, TimeOfDay(0, 59, 33)); + + testTOD(TimeOfDay(23, 59, 33), 1, TimeOfDay(23, 0, 33)); + testTOD(TimeOfDay(23, 59, 33), 0, TimeOfDay(23, 59, 33)); + testTOD(TimeOfDay(23, 59, 33), -1, TimeOfDay(23, 58, 33)); + + auto tod = TimeOfDay(12, 27, 2); + tod.roll!"minutes"(97).roll!"minutes"(-102); + assert(tod == TimeOfDay(12, 22, 2)); + + const ctod = TimeOfDay(0, 0, 0); + immutable itod = TimeOfDay(0, 0, 0); + static assert(!__traits(compiles, ctod.roll!"minutes"(7))); + static assert(!__traits(compiles, itod.roll!"minutes"(7))); + } + + // Test roll!"seconds"(). + @safe unittest + { + static void testTOD(TimeOfDay orig, int seconds, in TimeOfDay expected, size_t line = __LINE__) + { + orig.roll!"seconds"(seconds); + assert(orig == expected); + } + + testTOD(TimeOfDay(12, 30, 33), 0, TimeOfDay(12, 30, 33)); + testTOD(TimeOfDay(12, 30, 33), 1, TimeOfDay(12, 30, 34)); + testTOD(TimeOfDay(12, 30, 33), 2, TimeOfDay(12, 30, 35)); + testTOD(TimeOfDay(12, 30, 33), 3, TimeOfDay(12, 30, 36)); + testTOD(TimeOfDay(12, 30, 33), 4, TimeOfDay(12, 30, 37)); + testTOD(TimeOfDay(12, 30, 33), 5, TimeOfDay(12, 30, 38)); + testTOD(TimeOfDay(12, 30, 33), 10, TimeOfDay(12, 30, 43)); + testTOD(TimeOfDay(12, 30, 33), 15, TimeOfDay(12, 30, 48)); + testTOD(TimeOfDay(12, 30, 33), 26, TimeOfDay(12, 30, 59)); + testTOD(TimeOfDay(12, 30, 33), 27, TimeOfDay(12, 30, 0)); + testTOD(TimeOfDay(12, 30, 33), 30, TimeOfDay(12, 30, 3)); + testTOD(TimeOfDay(12, 30, 33), 59, TimeOfDay(12, 30, 32)); + testTOD(TimeOfDay(12, 30, 33), 60, TimeOfDay(12, 30, 33)); + testTOD(TimeOfDay(12, 30, 33), 61, TimeOfDay(12, 30, 34)); + + testTOD(TimeOfDay(12, 30, 33), 1766, TimeOfDay(12, 30, 59)); + testTOD(TimeOfDay(12, 30, 33), 1767, TimeOfDay(12, 30, 0)); + testTOD(TimeOfDay(12, 30, 33), 1768, TimeOfDay(12, 30, 1)); + testTOD(TimeOfDay(12, 30, 33), 2007, TimeOfDay(12, 30, 0)); + testTOD(TimeOfDay(12, 30, 33), 3599, TimeOfDay(12, 30, 32)); + testTOD(TimeOfDay(12, 30, 33), 3600, TimeOfDay(12, 30, 33)); + testTOD(TimeOfDay(12, 30, 33), 3601, TimeOfDay(12, 30, 34)); + testTOD(TimeOfDay(12, 30, 33), 7200, TimeOfDay(12, 30, 33)); + + testTOD(TimeOfDay(12, 30, 33), -1, TimeOfDay(12, 30, 32)); + testTOD(TimeOfDay(12, 30, 33), -2, TimeOfDay(12, 30, 31)); + testTOD(TimeOfDay(12, 30, 33), -3, TimeOfDay(12, 30, 30)); + testTOD(TimeOfDay(12, 30, 33), -4, TimeOfDay(12, 30, 29)); + testTOD(TimeOfDay(12, 30, 33), -5, TimeOfDay(12, 30, 28)); + testTOD(TimeOfDay(12, 30, 33), -10, TimeOfDay(12, 30, 23)); + testTOD(TimeOfDay(12, 30, 33), -15, TimeOfDay(12, 30, 18)); + testTOD(TimeOfDay(12, 30, 33), -33, TimeOfDay(12, 30, 0)); + testTOD(TimeOfDay(12, 30, 33), -34, TimeOfDay(12, 30, 59)); + testTOD(TimeOfDay(12, 30, 33), -35, TimeOfDay(12, 30, 58)); + testTOD(TimeOfDay(12, 30, 33), -59, TimeOfDay(12, 30, 34)); + testTOD(TimeOfDay(12, 30, 33), -60, TimeOfDay(12, 30, 33)); + testTOD(TimeOfDay(12, 30, 33), -61, TimeOfDay(12, 30, 32)); + + testTOD(TimeOfDay(12, 30, 0), 1, TimeOfDay(12, 30, 1)); + testTOD(TimeOfDay(12, 30, 0), 0, TimeOfDay(12, 30, 0)); + testTOD(TimeOfDay(12, 30, 0), -1, TimeOfDay(12, 30, 59)); + + testTOD(TimeOfDay(12, 0, 0), 1, TimeOfDay(12, 0, 1)); + testTOD(TimeOfDay(12, 0, 0), 0, TimeOfDay(12, 0, 0)); + testTOD(TimeOfDay(12, 0, 0), -1, TimeOfDay(12, 0, 59)); + + testTOD(TimeOfDay(0, 0, 0), 1, TimeOfDay(0, 0, 1)); + testTOD(TimeOfDay(0, 0, 0), 0, TimeOfDay(0, 0, 0)); + testTOD(TimeOfDay(0, 0, 0), -1, TimeOfDay(0, 0, 59)); + + testTOD(TimeOfDay(23, 59, 59), 1, TimeOfDay(23, 59, 0)); + testTOD(TimeOfDay(23, 59, 59), 0, TimeOfDay(23, 59, 59)); + testTOD(TimeOfDay(23, 59, 59), -1, TimeOfDay(23, 59, 58)); + + auto tod = TimeOfDay(12, 27, 2); + tod.roll!"seconds"(105).roll!"seconds"(-77); + assert(tod == TimeOfDay(12, 27, 30)); + + const ctod = TimeOfDay(0, 0, 0); + immutable itod = TimeOfDay(0, 0, 0); + static assert(!__traits(compiles, ctod.roll!"seconds"(7))); + static assert(!__traits(compiles, itod.roll!"seconds"(7))); + } + + + /++ + Gives the result of adding or subtracting a $(REF Duration, core,time) + from this $(LREF TimeOfDay). + + The legal types of arithmetic for $(LREF TimeOfDay) using this operator + are + + $(BOOKTABLE, + $(TR $(TD TimeOfDay) $(TD +) $(TD Duration) $(TD -->) $(TD TimeOfDay)) + $(TR $(TD TimeOfDay) $(TD -) $(TD Duration) $(TD -->) $(TD TimeOfDay)) + ) + + Params: + duration = The $(REF Duration, core,time) to add to or subtract from + this $(LREF TimeOfDay). + +/ + TimeOfDay opBinary(string op)(Duration duration) const @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + TimeOfDay retval = this; + immutable seconds = duration.total!"seconds"; + mixin("return retval._addSeconds(" ~ op ~ "seconds);"); + } + + /// + @safe unittest + { + import core.time : hours, minutes, seconds; + + assert(TimeOfDay(12, 12, 12) + seconds(1) == TimeOfDay(12, 12, 13)); + assert(TimeOfDay(12, 12, 12) + minutes(1) == TimeOfDay(12, 13, 12)); + assert(TimeOfDay(12, 12, 12) + hours(1) == TimeOfDay(13, 12, 12)); + assert(TimeOfDay(23, 59, 59) + seconds(1) == TimeOfDay(0, 0, 0)); + + assert(TimeOfDay(12, 12, 12) - seconds(1) == TimeOfDay(12, 12, 11)); + assert(TimeOfDay(12, 12, 12) - minutes(1) == TimeOfDay(12, 11, 12)); + assert(TimeOfDay(12, 12, 12) - hours(1) == TimeOfDay(11, 12, 12)); + assert(TimeOfDay(0, 0, 0) - seconds(1) == TimeOfDay(23, 59, 59)); + } + + @safe unittest + { + auto tod = TimeOfDay(12, 30, 33); + + assert(tod + dur!"hours"(7) == TimeOfDay(19, 30, 33)); + assert(tod + dur!"hours"(-7) == TimeOfDay(5, 30, 33)); + assert(tod + dur!"minutes"(7) == TimeOfDay(12, 37, 33)); + assert(tod + dur!"minutes"(-7) == TimeOfDay(12, 23, 33)); + assert(tod + dur!"seconds"(7) == TimeOfDay(12, 30, 40)); + assert(tod + dur!"seconds"(-7) == TimeOfDay(12, 30, 26)); + + assert(tod + dur!"msecs"(7000) == TimeOfDay(12, 30, 40)); + assert(tod + dur!"msecs"(-7000) == TimeOfDay(12, 30, 26)); + assert(tod + dur!"usecs"(7_000_000) == TimeOfDay(12, 30, 40)); + assert(tod + dur!"usecs"(-7_000_000) == TimeOfDay(12, 30, 26)); + assert(tod + dur!"hnsecs"(70_000_000) == TimeOfDay(12, 30, 40)); + assert(tod + dur!"hnsecs"(-70_000_000) == TimeOfDay(12, 30, 26)); + + assert(tod - dur!"hours"(-7) == TimeOfDay(19, 30, 33)); + assert(tod - dur!"hours"(7) == TimeOfDay(5, 30, 33)); + assert(tod - dur!"minutes"(-7) == TimeOfDay(12, 37, 33)); + assert(tod - dur!"minutes"(7) == TimeOfDay(12, 23, 33)); + assert(tod - dur!"seconds"(-7) == TimeOfDay(12, 30, 40)); + assert(tod - dur!"seconds"(7) == TimeOfDay(12, 30, 26)); + + assert(tod - dur!"msecs"(-7000) == TimeOfDay(12, 30, 40)); + assert(tod - dur!"msecs"(7000) == TimeOfDay(12, 30, 26)); + assert(tod - dur!"usecs"(-7_000_000) == TimeOfDay(12, 30, 40)); + assert(tod - dur!"usecs"(7_000_000) == TimeOfDay(12, 30, 26)); + assert(tod - dur!"hnsecs"(-70_000_000) == TimeOfDay(12, 30, 40)); + assert(tod - dur!"hnsecs"(70_000_000) == TimeOfDay(12, 30, 26)); + + auto duration = dur!"hours"(11); + const ctod = TimeOfDay(12, 30, 33); + immutable itod = TimeOfDay(12, 30, 33); + assert(tod + duration == TimeOfDay(23, 30, 33)); + assert(ctod + duration == TimeOfDay(23, 30, 33)); + assert(itod + duration == TimeOfDay(23, 30, 33)); + + assert(tod - duration == TimeOfDay(1, 30, 33)); + assert(ctod - duration == TimeOfDay(1, 30, 33)); + assert(itod - duration == TimeOfDay(1, 30, 33)); + } + + // Explicitly undocumented. It will be removed in January 2018. @@@DEPRECATED_2018-01@@@ + deprecated("Use Duration instead of TickDuration.") + TimeOfDay opBinary(string op)(TickDuration td) const @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + TimeOfDay retval = this; + immutable seconds = td.seconds; + mixin("return retval._addSeconds(" ~ op ~ "seconds);"); + } + + deprecated @safe unittest + { + // This probably only runs in cases where gettimeofday() is used, but it's + // hard to do this test correctly with variable ticksPerSec. + if (TickDuration.ticksPerSec == 1_000_000) + { + auto tod = TimeOfDay(12, 30, 33); + + assert(tod + TickDuration.from!"usecs"(7_000_000) == TimeOfDay(12, 30, 40)); + assert(tod + TickDuration.from!"usecs"(-7_000_000) == TimeOfDay(12, 30, 26)); + + assert(tod - TickDuration.from!"usecs"(-7_000_000) == TimeOfDay(12, 30, 40)); + assert(tod - TickDuration.from!"usecs"(7_000_000) == TimeOfDay(12, 30, 26)); + } + } + + + /++ + Gives the result of adding or subtracting a $(REF Duration, core,time) + from this $(LREF TimeOfDay), as well as assigning the result to this + $(LREF TimeOfDay). + + The legal types of arithmetic for $(LREF TimeOfDay) using this operator + are + + $(BOOKTABLE, + $(TR $(TD TimeOfDay) $(TD +) $(TD Duration) $(TD -->) $(TD TimeOfDay)) + $(TR $(TD TimeOfDay) $(TD -) $(TD Duration) $(TD -->) $(TD TimeOfDay)) + ) + + Params: + duration = The $(REF Duration, core,time) to add to or subtract from + this $(LREF TimeOfDay). + +/ + ref TimeOfDay opOpAssign(string op)(Duration duration) @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + immutable seconds = duration.total!"seconds"; + mixin("return _addSeconds(" ~ op ~ "seconds);"); + } + + @safe unittest + { + auto duration = dur!"hours"(12); + + assert(TimeOfDay(12, 30, 33) + dur!"hours"(7) == TimeOfDay(19, 30, 33)); + assert(TimeOfDay(12, 30, 33) + dur!"hours"(-7) == TimeOfDay(5, 30, 33)); + assert(TimeOfDay(12, 30, 33) + dur!"minutes"(7) == TimeOfDay(12, 37, 33)); + assert(TimeOfDay(12, 30, 33) + dur!"minutes"(-7) == TimeOfDay(12, 23, 33)); + assert(TimeOfDay(12, 30, 33) + dur!"seconds"(7) == TimeOfDay(12, 30, 40)); + assert(TimeOfDay(12, 30, 33) + dur!"seconds"(-7) == TimeOfDay(12, 30, 26)); + + assert(TimeOfDay(12, 30, 33) + dur!"msecs"(7000) == TimeOfDay(12, 30, 40)); + assert(TimeOfDay(12, 30, 33) + dur!"msecs"(-7000) == TimeOfDay(12, 30, 26)); + assert(TimeOfDay(12, 30, 33) + dur!"usecs"(7_000_000) == TimeOfDay(12, 30, 40)); + assert(TimeOfDay(12, 30, 33) + dur!"usecs"(-7_000_000) == TimeOfDay(12, 30, 26)); + assert(TimeOfDay(12, 30, 33) + dur!"hnsecs"(70_000_000) == TimeOfDay(12, 30, 40)); + assert(TimeOfDay(12, 30, 33) + dur!"hnsecs"(-70_000_000) == TimeOfDay(12, 30, 26)); + + assert(TimeOfDay(12, 30, 33) - dur!"hours"(-7) == TimeOfDay(19, 30, 33)); + assert(TimeOfDay(12, 30, 33) - dur!"hours"(7) == TimeOfDay(5, 30, 33)); + assert(TimeOfDay(12, 30, 33) - dur!"minutes"(-7) == TimeOfDay(12, 37, 33)); + assert(TimeOfDay(12, 30, 33) - dur!"minutes"(7) == TimeOfDay(12, 23, 33)); + assert(TimeOfDay(12, 30, 33) - dur!"seconds"(-7) == TimeOfDay(12, 30, 40)); + assert(TimeOfDay(12, 30, 33) - dur!"seconds"(7) == TimeOfDay(12, 30, 26)); + + assert(TimeOfDay(12, 30, 33) - dur!"msecs"(-7000) == TimeOfDay(12, 30, 40)); + assert(TimeOfDay(12, 30, 33) - dur!"msecs"(7000) == TimeOfDay(12, 30, 26)); + assert(TimeOfDay(12, 30, 33) - dur!"usecs"(-7_000_000) == TimeOfDay(12, 30, 40)); + assert(TimeOfDay(12, 30, 33) - dur!"usecs"(7_000_000) == TimeOfDay(12, 30, 26)); + assert(TimeOfDay(12, 30, 33) - dur!"hnsecs"(-70_000_000) == TimeOfDay(12, 30, 40)); + assert(TimeOfDay(12, 30, 33) - dur!"hnsecs"(70_000_000) == TimeOfDay(12, 30, 26)); + + auto tod = TimeOfDay(19, 17, 22); + (tod += dur!"seconds"(9)) += dur!"seconds"(-7292); + assert(tod == TimeOfDay(17, 15, 59)); + + const ctod = TimeOfDay(12, 33, 30); + immutable itod = TimeOfDay(12, 33, 30); + static assert(!__traits(compiles, ctod += duration)); + static assert(!__traits(compiles, itod += duration)); + static assert(!__traits(compiles, ctod -= duration)); + static assert(!__traits(compiles, itod -= duration)); + } + + // Explicitly undocumented. It will be removed in January 2018. @@@DEPRECATED_2018-01@@@ + deprecated("Use Duration instead of TickDuration.") + ref TimeOfDay opOpAssign(string op)(TickDuration td) @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + immutable seconds = td.seconds; + mixin("return _addSeconds(" ~ op ~ "seconds);"); + } + + deprecated @safe unittest + { + // This probably only runs in cases where gettimeofday() is used, but it's + // hard to do this test correctly with variable ticksPerSec. + if (TickDuration.ticksPerSec == 1_000_000) + { + { + auto tod = TimeOfDay(12, 30, 33); + tod += TickDuration.from!"usecs"(7_000_000); + assert(tod == TimeOfDay(12, 30, 40)); + } + + { + auto tod = TimeOfDay(12, 30, 33); + tod += TickDuration.from!"usecs"(-7_000_000); + assert(tod == TimeOfDay(12, 30, 26)); + } + + { + auto tod = TimeOfDay(12, 30, 33); + tod -= TickDuration.from!"usecs"(-7_000_000); + assert(tod == TimeOfDay(12, 30, 40)); + } + + { + auto tod = TimeOfDay(12, 30, 33); + tod -= TickDuration.from!"usecs"(7_000_000); + assert(tod == TimeOfDay(12, 30, 26)); + } + } + } + + + /++ + Gives the difference between two $(LREF TimeOfDay)s. + + The legal types of arithmetic for $(LREF TimeOfDay) using this operator + are + + $(BOOKTABLE, + $(TR $(TD TimeOfDay) $(TD -) $(TD TimeOfDay) $(TD -->) $(TD duration)) + ) + + Params: + rhs = The $(LREF TimeOfDay) to subtract from this one. + +/ + Duration opBinary(string op)(in TimeOfDay rhs) const @safe pure nothrow @nogc + if (op == "-") + { + immutable lhsSec = _hour * 3600 + _minute * 60 + _second; + immutable rhsSec = rhs._hour * 3600 + rhs._minute * 60 + rhs._second; + + return dur!"seconds"(lhsSec - rhsSec); + } + + @safe unittest + { + auto tod = TimeOfDay(12, 30, 33); + + assert(TimeOfDay(7, 12, 52) - TimeOfDay(12, 30, 33) == dur!"seconds"(-19_061)); + assert(TimeOfDay(12, 30, 33) - TimeOfDay(7, 12, 52) == dur!"seconds"(19_061)); + assert(TimeOfDay(12, 30, 33) - TimeOfDay(14, 30, 33) == dur!"seconds"(-7200)); + assert(TimeOfDay(14, 30, 33) - TimeOfDay(12, 30, 33) == dur!"seconds"(7200)); + assert(TimeOfDay(12, 30, 33) - TimeOfDay(12, 34, 33) == dur!"seconds"(-240)); + assert(TimeOfDay(12, 34, 33) - TimeOfDay(12, 30, 33) == dur!"seconds"(240)); + assert(TimeOfDay(12, 30, 33) - TimeOfDay(12, 30, 34) == dur!"seconds"(-1)); + assert(TimeOfDay(12, 30, 34) - TimeOfDay(12, 30, 33) == dur!"seconds"(1)); + + const ctod = TimeOfDay(12, 30, 33); + immutable itod = TimeOfDay(12, 30, 33); + assert(tod - tod == Duration.zero); + assert(ctod - tod == Duration.zero); + assert(itod - tod == Duration.zero); + + assert(tod - ctod == Duration.zero); + assert(ctod - ctod == Duration.zero); + assert(itod - ctod == Duration.zero); + + assert(tod - itod == Duration.zero); + assert(ctod - itod == Duration.zero); + assert(itod - itod == Duration.zero); + } + + + /++ + Converts this $(LREF TimeOfDay) to a string with the format HHMMSS. + +/ + string toISOString() const @safe pure nothrow + { + import std.format : format; + try + return format("%02d%02d%02d", _hour, _minute, _second); + catch (Exception e) + assert(0, "format() threw."); + } + + /// + @safe unittest + { + assert(TimeOfDay(0, 0, 0).toISOString() == "000000"); + assert(TimeOfDay(12, 30, 33).toISOString() == "123033"); + } + + @safe unittest + { + auto tod = TimeOfDay(12, 30, 33); + const ctod = TimeOfDay(12, 30, 33); + immutable itod = TimeOfDay(12, 30, 33); + assert(tod.toISOString() == "123033"); + assert(ctod.toISOString() == "123033"); + assert(itod.toISOString() == "123033"); + } + + + /++ + Converts this $(LREF TimeOfDay) to a string with the format HH:MM:SS. + +/ + string toISOExtString() const @safe pure nothrow + { + import std.format : format; + try + return format("%02d:%02d:%02d", _hour, _minute, _second); + catch (Exception e) + assert(0, "format() threw."); + } + + /// + @safe unittest + { + assert(TimeOfDay(0, 0, 0).toISOExtString() == "00:00:00"); + assert(TimeOfDay(12, 30, 33).toISOExtString() == "12:30:33"); + } + + @safe unittest + { + auto tod = TimeOfDay(12, 30, 33); + const ctod = TimeOfDay(12, 30, 33); + immutable itod = TimeOfDay(12, 30, 33); + assert(tod.toISOExtString() == "12:30:33"); + assert(ctod.toISOExtString() == "12:30:33"); + assert(itod.toISOExtString() == "12:30:33"); + } + + + /++ + Converts this TimeOfDay to a string. + + This function exists to make it easy to convert a $(LREF TimeOfDay) to a + string for code that does not care what the exact format is - just that + it presents the information in a clear manner. It also makes it easy to + simply convert a $(LREF TimeOfDay) to a string when using functions such + as `to!string`, `format`, or `writeln` which use toString to convert + user-defined types. So, it is unlikely that much code will call + toString directly. + + The format of the string is purposefully unspecified, and code that + cares about the format of the string should use `toISOString`, + `toISOExtString`, or some other custom formatting function that + explicitly generates the format that the code needs. The reason is that + the code is then clear about what format it's using, making it less + error-prone to maintain the code and interact with other software that + consumes the generated strings. It's for this same reason that + $(LREF TimeOfDay) has no `fromString` function, whereas it does have + `fromISOString` and `fromISOExtString`. + + The format returned by toString may or may not change in the future. + +/ + string toString() const @safe pure nothrow + { + return toISOExtString(); + } + + @safe unittest + { + auto tod = TimeOfDay(12, 30, 33); + const ctod = TimeOfDay(12, 30, 33); + immutable itod = TimeOfDay(12, 30, 33); + assert(tod.toString()); + assert(ctod.toString()); + assert(itod.toString()); + } + + + /++ + Creates a $(LREF TimeOfDay) from a string with the format HHMMSS. + Whitespace is stripped from the given string. + + Params: + isoString = A string formatted in the ISO format for times. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given string is + not in the ISO format or if the resulting $(LREF TimeOfDay) would + not be valid. + +/ + static TimeOfDay fromISOString(S)(in S isoString) @safe pure + if (isSomeString!S) + { + import std.conv : to, text, ConvException; + import std.exception : enforce; + import std.string : strip; + + int hours, minutes, seconds; + auto str = strip(isoString); + + enforce!DateTimeException(str.length == 6, text("Invalid ISO String: ", isoString)); + + try + { + // cast to int from uint is used because it checks for + // non digits without extra loops + hours = cast(int) to!uint(str[0 .. 2]); + minutes = cast(int) to!uint(str[2 .. 4]); + seconds = cast(int) to!uint(str[4 .. $]); + } + catch (ConvException) + { + throw new DateTimeException(text("Invalid ISO String: ", isoString)); + } + + return TimeOfDay(hours, minutes, seconds); + } + + /// + @safe unittest + { + assert(TimeOfDay.fromISOString("000000") == TimeOfDay(0, 0, 0)); + assert(TimeOfDay.fromISOString("123033") == TimeOfDay(12, 30, 33)); + assert(TimeOfDay.fromISOString(" 123033 ") == TimeOfDay(12, 30, 33)); + } + + @safe unittest + { + assertThrown!DateTimeException(TimeOfDay.fromISOString("")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("0")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("00")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("000")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("0000")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("00000")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("13033")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("1277")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("12707")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("12070")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("12303a")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("1230a3")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("123a33")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("12a033")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("1a0033")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("a20033")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("1200330")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("0120033")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("-120033")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("+120033")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("120033am")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("120033pm")); + + assertThrown!DateTimeException(TimeOfDay.fromISOString("0::")); + assertThrown!DateTimeException(TimeOfDay.fromISOString(":0:")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("::0")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("0:0:0")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("0:0:00")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("0:00:0")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("00:0:0")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("00:00:0")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("00:0:00")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("13:0:33")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("12:7:7")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("12:7:07")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("12:07:0")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("12:30:3a")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("12:30:a3")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("12:3a:33")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("12:a0:33")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("1a:00:33")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("a2:00:33")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("12:003:30")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("120:03:30")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("012:00:33")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("01:200:33")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("-12:00:33")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("+12:00:33")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("12:00:33am")); + assertThrown!DateTimeException(TimeOfDay.fromISOString("12:00:33pm")); + + assertThrown!DateTimeException(TimeOfDay.fromISOString("12:00:33")); + + assert(TimeOfDay.fromISOString("011217") == TimeOfDay(1, 12, 17)); + assert(TimeOfDay.fromISOString("001412") == TimeOfDay(0, 14, 12)); + assert(TimeOfDay.fromISOString("000007") == TimeOfDay(0, 0, 7)); + assert(TimeOfDay.fromISOString("011217 ") == TimeOfDay(1, 12, 17)); + assert(TimeOfDay.fromISOString(" 011217") == TimeOfDay(1, 12, 17)); + assert(TimeOfDay.fromISOString(" 011217 ") == TimeOfDay(1, 12, 17)); + } + + // bug# 17801 + @safe unittest + { + import std.conv : to; + import std.meta : AliasSeq; + foreach (C; AliasSeq!(char, wchar, dchar)) + { + foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + assert(TimeOfDay.fromISOString(to!S("141516")) == TimeOfDay(14, 15, 16)); + } + } + + + /++ + Creates a $(LREF TimeOfDay) from a string with the format HH:MM:SS. + Whitespace is stripped from the given string. + + Params: + isoExtString = A string formatted in the ISO Extended format for + times. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given string is + not in the ISO Extended format or if the resulting $(LREF TimeOfDay) + would not be valid. + +/ + static TimeOfDay fromISOExtString(S)(in S isoExtString) @safe pure + if (isSomeString!S) + { + import std.algorithm.searching : all; + import std.ascii : isDigit; + import std.conv : to; + import std.exception : enforce; + import std.format : format; + import std.string : strip; + + auto dstr = to!dstring(strip(isoExtString)); + + enforce(dstr.length == 8, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); + + auto hours = dstr[0 .. 2]; + auto minutes = dstr[3 .. 5]; + auto seconds = dstr[6 .. $]; + + enforce(dstr[2] == ':', new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); + enforce(dstr[5] == ':', new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); + enforce(all!isDigit(hours), + new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); + enforce(all!isDigit(minutes), + new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); + enforce(all!isDigit(seconds), + new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); + + return TimeOfDay(to!int(hours), to!int(minutes), to!int(seconds)); + } + + /// + @safe unittest + { + assert(TimeOfDay.fromISOExtString("00:00:00") == TimeOfDay(0, 0, 0)); + assert(TimeOfDay.fromISOExtString("12:30:33") == TimeOfDay(12, 30, 33)); + assert(TimeOfDay.fromISOExtString(" 12:30:33 ") == TimeOfDay(12, 30, 33)); + } + + @safe unittest + { + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("0")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("00")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("000")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("0000")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("00000")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("13033")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("1277")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12707")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12070")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12303a")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("1230a3")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("123a33")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12a033")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("1a0033")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("a20033")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("1200330")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("0120033")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("-120033")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("+120033")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("120033am")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("120033pm")); + + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("0::")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString(":0:")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("::0")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("0:0:0")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("0:0:00")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("0:00:0")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("00:0:0")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("00:00:0")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("00:0:00")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("13:0:33")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:7:7")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:7:07")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:07:0")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:30:3a")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:30:a3")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:3a:33")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:a0:33")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("1a:00:33")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("a2:00:33")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:003:30")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("120:03:30")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("012:00:33")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("01:200:33")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("-12:00:33")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("+12:00:33")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:00:33am")); + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("12:00:33pm")); + + assertThrown!DateTimeException(TimeOfDay.fromISOExtString("120033")); + + assert(TimeOfDay.fromISOExtString("01:12:17") == TimeOfDay(1, 12, 17)); + assert(TimeOfDay.fromISOExtString("00:14:12") == TimeOfDay(0, 14, 12)); + assert(TimeOfDay.fromISOExtString("00:00:07") == TimeOfDay(0, 0, 7)); + assert(TimeOfDay.fromISOExtString("01:12:17 ") == TimeOfDay(1, 12, 17)); + assert(TimeOfDay.fromISOExtString(" 01:12:17") == TimeOfDay(1, 12, 17)); + assert(TimeOfDay.fromISOExtString(" 01:12:17 ") == TimeOfDay(1, 12, 17)); + } + + // bug# 17801 + @safe unittest + { + import std.conv : to; + import std.meta : AliasSeq; + foreach (C; AliasSeq!(char, wchar, dchar)) + { + foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + assert(TimeOfDay.fromISOExtString(to!S("14:15:16")) == TimeOfDay(14, 15, 16)); + } + } + + + /++ + Returns midnight. + +/ + @property static TimeOfDay min() @safe pure nothrow @nogc + { + return TimeOfDay.init; + } + + @safe unittest + { + assert(TimeOfDay.min.hour == 0); + assert(TimeOfDay.min.minute == 0); + assert(TimeOfDay.min.second == 0); + assert(TimeOfDay.min < TimeOfDay.max); + } + + + /++ + Returns one second short of midnight. + +/ + @property static TimeOfDay max() @safe pure nothrow @nogc + { + auto tod = TimeOfDay.init; + tod._hour = maxHour; + tod._minute = maxMinute; + tod._second = maxSecond; + + return tod; + } + + @safe unittest + { + assert(TimeOfDay.max.hour == 23); + assert(TimeOfDay.max.minute == 59); + assert(TimeOfDay.max.second == 59); + assert(TimeOfDay.max > TimeOfDay.min); + } + + +private: + + /+ + Add seconds to the time of day. Negative values will subtract. If the + number of seconds overflows (or underflows), then the seconds will wrap, + increasing (or decreasing) the number of minutes accordingly. If the + number of minutes overflows (or underflows), then the minutes will wrap. + If the number of minutes overflows(or underflows), then the hour will + wrap. (e.g. adding 90 seconds to 23:59:00 would result in 00:00:30). + + Params: + seconds = The number of seconds to add to this TimeOfDay. + +/ + ref TimeOfDay _addSeconds(long seconds) return @safe pure nothrow @nogc + { + long hnsecs = convert!("seconds", "hnsecs")(seconds); + hnsecs += convert!("hours", "hnsecs")(_hour); + hnsecs += convert!("minutes", "hnsecs")(_minute); + hnsecs += convert!("seconds", "hnsecs")(_second); + + hnsecs %= convert!("days", "hnsecs")(1); + + if (hnsecs < 0) + hnsecs += convert!("days", "hnsecs")(1); + + immutable newHours = splitUnitsFromHNSecs!"hours"(hnsecs); + immutable newMinutes = splitUnitsFromHNSecs!"minutes"(hnsecs); + immutable newSeconds = splitUnitsFromHNSecs!"seconds"(hnsecs); + + _hour = cast(ubyte) newHours; + _minute = cast(ubyte) newMinutes; + _second = cast(ubyte) newSeconds; + + return this; + } + + @safe unittest + { + static void testTOD(TimeOfDay orig, int seconds, in TimeOfDay expected, size_t line = __LINE__) + { + orig._addSeconds(seconds); + assert(orig == expected); + } + + testTOD(TimeOfDay(12, 30, 33), 0, TimeOfDay(12, 30, 33)); + testTOD(TimeOfDay(12, 30, 33), 1, TimeOfDay(12, 30, 34)); + testTOD(TimeOfDay(12, 30, 33), 2, TimeOfDay(12, 30, 35)); + testTOD(TimeOfDay(12, 30, 33), 3, TimeOfDay(12, 30, 36)); + testTOD(TimeOfDay(12, 30, 33), 4, TimeOfDay(12, 30, 37)); + testTOD(TimeOfDay(12, 30, 33), 5, TimeOfDay(12, 30, 38)); + testTOD(TimeOfDay(12, 30, 33), 10, TimeOfDay(12, 30, 43)); + testTOD(TimeOfDay(12, 30, 33), 15, TimeOfDay(12, 30, 48)); + testTOD(TimeOfDay(12, 30, 33), 26, TimeOfDay(12, 30, 59)); + testTOD(TimeOfDay(12, 30, 33), 27, TimeOfDay(12, 31, 0)); + testTOD(TimeOfDay(12, 30, 33), 30, TimeOfDay(12, 31, 3)); + testTOD(TimeOfDay(12, 30, 33), 59, TimeOfDay(12, 31, 32)); + testTOD(TimeOfDay(12, 30, 33), 60, TimeOfDay(12, 31, 33)); + testTOD(TimeOfDay(12, 30, 33), 61, TimeOfDay(12, 31, 34)); + + testTOD(TimeOfDay(12, 30, 33), 1766, TimeOfDay(12, 59, 59)); + testTOD(TimeOfDay(12, 30, 33), 1767, TimeOfDay(13, 0, 0)); + testTOD(TimeOfDay(12, 30, 33), 1768, TimeOfDay(13, 0, 1)); + testTOD(TimeOfDay(12, 30, 33), 2007, TimeOfDay(13, 4, 0)); + testTOD(TimeOfDay(12, 30, 33), 3599, TimeOfDay(13, 30, 32)); + testTOD(TimeOfDay(12, 30, 33), 3600, TimeOfDay(13, 30, 33)); + testTOD(TimeOfDay(12, 30, 33), 3601, TimeOfDay(13, 30, 34)); + testTOD(TimeOfDay(12, 30, 33), 7200, TimeOfDay(14, 30, 33)); + + testTOD(TimeOfDay(12, 30, 33), -1, TimeOfDay(12, 30, 32)); + testTOD(TimeOfDay(12, 30, 33), -2, TimeOfDay(12, 30, 31)); + testTOD(TimeOfDay(12, 30, 33), -3, TimeOfDay(12, 30, 30)); + testTOD(TimeOfDay(12, 30, 33), -4, TimeOfDay(12, 30, 29)); + testTOD(TimeOfDay(12, 30, 33), -5, TimeOfDay(12, 30, 28)); + testTOD(TimeOfDay(12, 30, 33), -10, TimeOfDay(12, 30, 23)); + testTOD(TimeOfDay(12, 30, 33), -15, TimeOfDay(12, 30, 18)); + testTOD(TimeOfDay(12, 30, 33), -33, TimeOfDay(12, 30, 0)); + testTOD(TimeOfDay(12, 30, 33), -34, TimeOfDay(12, 29, 59)); + testTOD(TimeOfDay(12, 30, 33), -35, TimeOfDay(12, 29, 58)); + testTOD(TimeOfDay(12, 30, 33), -59, TimeOfDay(12, 29, 34)); + testTOD(TimeOfDay(12, 30, 33), -60, TimeOfDay(12, 29, 33)); + testTOD(TimeOfDay(12, 30, 33), -61, TimeOfDay(12, 29, 32)); + + testTOD(TimeOfDay(12, 30, 33), -1833, TimeOfDay(12, 0, 0)); + testTOD(TimeOfDay(12, 30, 33), -1834, TimeOfDay(11, 59, 59)); + testTOD(TimeOfDay(12, 30, 33), -3600, TimeOfDay(11, 30, 33)); + testTOD(TimeOfDay(12, 30, 33), -3601, TimeOfDay(11, 30, 32)); + testTOD(TimeOfDay(12, 30, 33), -5134, TimeOfDay(11, 4, 59)); + testTOD(TimeOfDay(12, 30, 33), -7200, TimeOfDay(10, 30, 33)); + + testTOD(TimeOfDay(12, 30, 0), 1, TimeOfDay(12, 30, 1)); + testTOD(TimeOfDay(12, 30, 0), 0, TimeOfDay(12, 30, 0)); + testTOD(TimeOfDay(12, 30, 0), -1, TimeOfDay(12, 29, 59)); + + testTOD(TimeOfDay(12, 0, 0), 1, TimeOfDay(12, 0, 1)); + testTOD(TimeOfDay(12, 0, 0), 0, TimeOfDay(12, 0, 0)); + testTOD(TimeOfDay(12, 0, 0), -1, TimeOfDay(11, 59, 59)); + + testTOD(TimeOfDay(0, 0, 0), 1, TimeOfDay(0, 0, 1)); + testTOD(TimeOfDay(0, 0, 0), 0, TimeOfDay(0, 0, 0)); + testTOD(TimeOfDay(0, 0, 0), -1, TimeOfDay(23, 59, 59)); + + testTOD(TimeOfDay(23, 59, 59), 1, TimeOfDay(0, 0, 0)); + testTOD(TimeOfDay(23, 59, 59), 0, TimeOfDay(23, 59, 59)); + testTOD(TimeOfDay(23, 59, 59), -1, TimeOfDay(23, 59, 58)); + + const ctod = TimeOfDay(0, 0, 0); + immutable itod = TimeOfDay(0, 0, 0); + static assert(!__traits(compiles, ctod._addSeconds(7))); + static assert(!__traits(compiles, itod._addSeconds(7))); + } + + + /+ + Whether the given values form a valid $(LREF TimeOfDay). + +/ + static bool _valid(int hour, int minute, int second) @safe pure nothrow @nogc + { + return valid!"hours"(hour) && valid!"minutes"(minute) && valid!"seconds"(second); + } + + + @safe pure invariant() + { + import std.format : format; + assert(_valid(_hour, _minute, _second), + format("Invariant Failure: hour [%s] minute [%s] second [%s]", _hour, _minute, _second)); + } + + +package: + + ubyte _hour; + ubyte _minute; + ubyte _second; + + enum ubyte maxHour = 24 - 1; + enum ubyte maxMinute = 60 - 1; + enum ubyte maxSecond = 60 - 1; +} + + +/++ + Returns whether the given value is valid for the given unit type when in a + time point. Naturally, a duration is not held to a particular range, but + the values in a time point are (e.g. a month must be in the range of + 1 - 12 inclusive). + + Params: + units = The units of time to validate. + value = The number to validate. + +/ +bool valid(string units)(int value) @safe pure nothrow @nogc +if (units == "months" || + units == "hours" || + units == "minutes" || + units == "seconds") +{ + static if (units == "months") + return value >= Month.jan && value <= Month.dec; + else static if (units == "hours") + return value >= 0 && value <= 23; + else static if (units == "minutes") + return value >= 0 && value <= 59; + else static if (units == "seconds") + return value >= 0 && value <= 59; +} + +/// +@safe unittest +{ + assert(valid!"hours"(12)); + assert(!valid!"hours"(32)); + assert(valid!"months"(12)); + assert(!valid!"months"(13)); +} + +/++ + Returns whether the given day is valid for the given year and month. + + Params: + units = The units of time to validate. + year = The year of the day to validate. + month = The month of the day to validate (January is 1). + day = The day to validate. + +/ +bool valid(string units)(int year, int month, int day) @safe pure nothrow @nogc +if (units == "days") +{ + return day > 0 && day <= maxDay(year, month); +} + +/// +@safe pure nothrow @nogc unittest +{ + assert(valid!"days"(2016, 2, 29)); + assert(!valid!"days"(2016, 2, 30)); + assert(valid!"days"(2017, 2, 20)); + assert(!valid!"days"(2017, 2, 29)); +} + + +/++ + Params: + units = The units of time to validate. + value = The number to validate. + file = The file that the $(LREF DateTimeException) will list if thrown. + line = The line number that the $(LREF DateTimeException) will list if + thrown. + + Throws: + $(LREF DateTimeException) if $(D valid!units(value)) is false. + +/ +void enforceValid(string units)(int value, string file = __FILE__, size_t line = __LINE__) @safe pure +if (units == "months" || + units == "hours" || + units == "minutes" || + units == "seconds") +{ + import std.format : format; + + static if (units == "months") + { + if (!valid!units(value)) + throw new DateTimeException(format("%s is not a valid month of the year.", value), file, line); + } + else static if (units == "hours") + { + if (!valid!units(value)) + throw new DateTimeException(format("%s is not a valid hour of the day.", value), file, line); + } + else static if (units == "minutes") + { + if (!valid!units(value)) + throw new DateTimeException(format("%s is not a valid minute of an hour.", value), file, line); + } + else static if (units == "seconds") + { + if (!valid!units(value)) + throw new DateTimeException(format("%s is not a valid second of a minute.", value), file, line); + } +} + + +/++ + Params: + units = The units of time to validate. + year = The year of the day to validate. + month = The month of the day to validate. + day = The day to validate. + file = The file that the $(LREF DateTimeException) will list if thrown. + line = The line number that the $(LREF DateTimeException) will list if + thrown. + + Throws: + $(LREF DateTimeException) if $(D valid!"days"(year, month, day)) is false. + +/ +void enforceValid(string units) + (int year, Month month, int day, string file = __FILE__, size_t line = __LINE__) @safe pure +if (units == "days") +{ + import std.format : format; + if (!valid!"days"(year, month, day)) + throw new DateTimeException(format("%s is not a valid day in %s in %s", day, month, year), file, line); +} + + +/++ + Returns the number of days from the current day of the week to the given + day of the week. If they are the same, then the result is 0. + + Params: + currDoW = The current day of the week. + dow = The day of the week to get the number of days to. + +/ +int daysToDayOfWeek(DayOfWeek currDoW, DayOfWeek dow) @safe pure nothrow @nogc +{ + if (currDoW == dow) + return 0; + if (currDoW < dow) + return dow - currDoW; + return DayOfWeek.sat - currDoW + dow + 1; +} + +/// +@safe pure nothrow @nogc unittest +{ + assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.mon) == 0); + assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.sun) == 6); + assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.wed) == 2); +} + +@safe unittest +{ + assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.sun) == 0); + assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.mon) == 1); + assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.tue) == 2); + assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.wed) == 3); + assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.thu) == 4); + assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.fri) == 5); + assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.sat) == 6); + + assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.sun) == 6); + assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.mon) == 0); + assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.tue) == 1); + assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.wed) == 2); + assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.thu) == 3); + assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.fri) == 4); + assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.sat) == 5); + + assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.sun) == 5); + assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.mon) == 6); + assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.tue) == 0); + assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.wed) == 1); + assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.thu) == 2); + assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.fri) == 3); + assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.sat) == 4); + + assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.sun) == 4); + assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.mon) == 5); + assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.tue) == 6); + assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.wed) == 0); + assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.thu) == 1); + assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.fri) == 2); + assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.sat) == 3); + + assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.sun) == 3); + assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.mon) == 4); + assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.tue) == 5); + assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.wed) == 6); + assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.thu) == 0); + assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.fri) == 1); + assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.sat) == 2); + + assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.sun) == 2); + assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.mon) == 3); + assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.tue) == 4); + assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.wed) == 5); + assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.thu) == 6); + assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.fri) == 0); + assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.sat) == 1); + + assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.sun) == 1); + assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.mon) == 2); + assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.tue) == 3); + assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.wed) == 4); + assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.thu) == 5); + assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.fri) == 6); + assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.sat) == 0); +} + + +/++ + Returns the number of months from the current months of the year to the + given month of the year. If they are the same, then the result is 0. + + Params: + currMonth = The current month of the year. + month = The month of the year to get the number of months to. + +/ +int monthsToMonth(int currMonth, int month) @safe pure +{ + enforceValid!"months"(currMonth); + enforceValid!"months"(month); + + if (currMonth == month) + return 0; + if (currMonth < month) + return month - currMonth; + return Month.dec - currMonth + month; +} + +/// +@safe pure unittest +{ + assert(monthsToMonth(Month.jan, Month.jan) == 0); + assert(monthsToMonth(Month.jan, Month.dec) == 11); + assert(monthsToMonth(Month.jul, Month.oct) == 3); +} + +@safe unittest +{ + assert(monthsToMonth(Month.jan, Month.jan) == 0); + assert(monthsToMonth(Month.jan, Month.feb) == 1); + assert(monthsToMonth(Month.jan, Month.mar) == 2); + assert(monthsToMonth(Month.jan, Month.apr) == 3); + assert(monthsToMonth(Month.jan, Month.may) == 4); + assert(monthsToMonth(Month.jan, Month.jun) == 5); + assert(monthsToMonth(Month.jan, Month.jul) == 6); + assert(monthsToMonth(Month.jan, Month.aug) == 7); + assert(monthsToMonth(Month.jan, Month.sep) == 8); + assert(monthsToMonth(Month.jan, Month.oct) == 9); + assert(monthsToMonth(Month.jan, Month.nov) == 10); + assert(monthsToMonth(Month.jan, Month.dec) == 11); + + assert(monthsToMonth(Month.may, Month.jan) == 8); + assert(monthsToMonth(Month.may, Month.feb) == 9); + assert(monthsToMonth(Month.may, Month.mar) == 10); + assert(monthsToMonth(Month.may, Month.apr) == 11); + assert(monthsToMonth(Month.may, Month.may) == 0); + assert(monthsToMonth(Month.may, Month.jun) == 1); + assert(monthsToMonth(Month.may, Month.jul) == 2); + assert(monthsToMonth(Month.may, Month.aug) == 3); + assert(monthsToMonth(Month.may, Month.sep) == 4); + assert(monthsToMonth(Month.may, Month.oct) == 5); + assert(monthsToMonth(Month.may, Month.nov) == 6); + assert(monthsToMonth(Month.may, Month.dec) == 7); + + assert(monthsToMonth(Month.oct, Month.jan) == 3); + assert(monthsToMonth(Month.oct, Month.feb) == 4); + assert(monthsToMonth(Month.oct, Month.mar) == 5); + assert(monthsToMonth(Month.oct, Month.apr) == 6); + assert(monthsToMonth(Month.oct, Month.may) == 7); + assert(monthsToMonth(Month.oct, Month.jun) == 8); + assert(monthsToMonth(Month.oct, Month.jul) == 9); + assert(monthsToMonth(Month.oct, Month.aug) == 10); + assert(monthsToMonth(Month.oct, Month.sep) == 11); + assert(monthsToMonth(Month.oct, Month.oct) == 0); + assert(monthsToMonth(Month.oct, Month.nov) == 1); + assert(monthsToMonth(Month.oct, Month.dec) == 2); + + assert(monthsToMonth(Month.dec, Month.jan) == 1); + assert(monthsToMonth(Month.dec, Month.feb) == 2); + assert(monthsToMonth(Month.dec, Month.mar) == 3); + assert(monthsToMonth(Month.dec, Month.apr) == 4); + assert(monthsToMonth(Month.dec, Month.may) == 5); + assert(monthsToMonth(Month.dec, Month.jun) == 6); + assert(monthsToMonth(Month.dec, Month.jul) == 7); + assert(monthsToMonth(Month.dec, Month.aug) == 8); + assert(monthsToMonth(Month.dec, Month.sep) == 9); + assert(monthsToMonth(Month.dec, Month.oct) == 10); + assert(monthsToMonth(Month.dec, Month.nov) == 11); + assert(monthsToMonth(Month.dec, Month.dec) == 0); +} + + +/++ + Whether the given Gregorian Year is a leap year. + + Params: + year = The year to to be tested. + +/ +bool yearIsLeapYear(int year) @safe pure nothrow @nogc +{ + if (year % 400 == 0) + return true; + if (year % 100 == 0) + return false; + return year % 4 == 0; +} + +/// +@safe unittest +{ + foreach (year; [1, 2, 100, 2001, 2002, 2003, 2005, 2006, 2007, 2009, 2010]) + { + assert(!yearIsLeapYear(year)); + assert(!yearIsLeapYear(-year)); + } + + foreach (year; [0, 4, 8, 400, 800, 1600, 1996, 2000, 2004, 2008, 2012]) + { + assert(yearIsLeapYear(year)); + assert(yearIsLeapYear(-year)); + } +} + +@safe unittest +{ + import std.format : format; + foreach (year; [1, 2, 3, 5, 6, 7, 100, 200, 300, 500, 600, 700, 1998, 1999, + 2001, 2002, 2003, 2005, 2006, 2007, 2009, 2010, 2011]) + { + assert(!yearIsLeapYear(year), format("year: %s.", year)); + assert(!yearIsLeapYear(-year), format("year: %s.", year)); + } + + foreach (year; [0, 4, 8, 400, 800, 1600, 1996, 2000, 2004, 2008, 2012]) + { + assert(yearIsLeapYear(year), format("year: %s.", year)); + assert(yearIsLeapYear(-year), format("year: %s.", year)); + } +} + + +/++ + Whether the given type defines all of the necessary functions for it to + function as a time point. + + 1. $(D T) must define a static property named $(D min) which is the smallest + value of $(D T) as $(D Unqual!T). + + 2. $(D T) must define a static property named $(D max) which is the largest + value of $(D T) as $(D Unqual!T). + + 3. $(D T) must define an $(D opBinary) for addition and subtraction that + accepts $(REF Duration, core,time) and returns $(D Unqual!T). + + 4. $(D T) must define an $(D opOpAssign) for addition and subtraction that + accepts $(REF Duration, core,time) and returns $(D ref Unqual!T). + + 5. $(D T) must define a $(D opBinary) for subtraction which accepts $(D T) + and returns returns $(REF Duration, core,time). + +/ +template isTimePoint(T) +{ + import core.time : Duration; + import std.traits : FunctionAttribute, functionAttributes, Unqual; + + enum isTimePoint = hasMin && + hasMax && + hasOverloadedOpBinaryWithDuration && + hasOverloadedOpAssignWithDuration && + hasOverloadedOpBinaryWithSelf && + !is(U == Duration); + +private: + + alias U = Unqual!T; + + enum hasMin = __traits(hasMember, T, "min") && + is(typeof(T.min) == U) && + is(typeof({static assert(__traits(isStaticFunction, T.min));})); + + enum hasMax = __traits(hasMember, T, "max") && + is(typeof(T.max) == U) && + is(typeof({static assert(__traits(isStaticFunction, T.max));})); + + enum hasOverloadedOpBinaryWithDuration = is(typeof(T.init + Duration.init) == U) && + is(typeof(T.init - Duration.init) == U); + + enum hasOverloadedOpAssignWithDuration = is(typeof(U.init += Duration.init) == U) && + is(typeof(U.init -= Duration.init) == U) && + is(typeof( + { + // Until the overload with TickDuration is removed, this is ambiguous. + //alias add = U.opOpAssign!"+"; + //alias sub = U.opOpAssign!"-"; + U u; + auto ref add() { return u += Duration.init; } + auto ref sub() { return u -= Duration.init; } + alias FA = FunctionAttribute; + static assert((functionAttributes!add & FA.ref_) != 0); + static assert((functionAttributes!sub & FA.ref_) != 0); + })); + + enum hasOverloadedOpBinaryWithSelf = is(typeof(T.init - T.init) == Duration); +} + +/// +@safe unittest +{ + import core.time : Duration; + import std.datetime.interval : Interval; + import std.datetime.systime : SysTime; + + static assert(isTimePoint!Date); + static assert(isTimePoint!DateTime); + static assert(isTimePoint!SysTime); + static assert(isTimePoint!TimeOfDay); + + static assert(!isTimePoint!int); + static assert(!isTimePoint!Duration); + static assert(!isTimePoint!(Interval!SysTime)); +} + +@safe unittest +{ + import core.time; + import std.datetime.interval; + import std.datetime.systime; + import std.meta : AliasSeq; + + foreach (TP; AliasSeq!(Date, DateTime, SysTime, TimeOfDay)) + { + static assert(isTimePoint!(const TP), TP.stringof); + static assert(isTimePoint!(immutable TP), TP.stringof); + } + + foreach (T; AliasSeq!(float, string, Duration, Interval!Date, PosInfInterval!Date, NegInfInterval!Date)) + static assert(!isTimePoint!T, T.stringof); +} + + +/++ + Whether all of the given strings are valid units of time. + + $(D "nsecs") is not considered a valid unit of time. Nothing in std.datetime + can handle precision greater than hnsecs, and the few functions in core.time + which deal with "nsecs" deal with it explicitly. + +/ +bool validTimeUnits(string[] units...) @safe pure nothrow @nogc +{ + import std.algorithm.searching : canFind; + foreach (str; units) + { + if (!canFind(timeStrings[], str)) + return false; + } + return true; +} + +/// +@safe @nogc nothrow unittest +{ + assert(validTimeUnits("msecs", "seconds", "minutes")); + assert(validTimeUnits("days", "weeks", "months")); + assert(!validTimeUnits("ms", "seconds", "minutes")); +} + + +/++ + Compares two time unit strings. $(D "years") are the largest units and + $(D "hnsecs") are the smallest. + + Returns: + $(BOOKTABLE, + $(TR $(TD this < rhs) $(TD < 0)) + $(TR $(TD this == rhs) $(TD 0)) + $(TR $(TD this > rhs) $(TD > 0)) + ) + + Throws: + $(LREF DateTimeException) if either of the given strings is not a valid + time unit string. + +/ +int cmpTimeUnits(string lhs, string rhs) @safe pure +{ + import std.algorithm.searching : countUntil; + import std.exception : enforce; + import std.format : format; + + auto tstrings = timeStrings; + immutable indexOfLHS = countUntil(tstrings, lhs); + immutable indexOfRHS = countUntil(tstrings, rhs); + + enforce(indexOfLHS != -1, format("%s is not a valid TimeString", lhs)); + enforce(indexOfRHS != -1, format("%s is not a valid TimeString", rhs)); + + if (indexOfLHS < indexOfRHS) + return -1; + if (indexOfLHS > indexOfRHS) + return 1; + + return 0; +} + +/// +@safe pure unittest +{ + assert(cmpTimeUnits("hours", "hours") == 0); + assert(cmpTimeUnits("hours", "weeks") < 0); + assert(cmpTimeUnits("months", "seconds") > 0); +} + +@safe unittest +{ + foreach (i, outerUnits; timeStrings) + { + assert(cmpTimeUnits(outerUnits, outerUnits) == 0); + + // For some reason, $ won't compile. + foreach (innerUnits; timeStrings[i + 1 .. timeStrings.length]) + assert(cmpTimeUnits(outerUnits, innerUnits) == -1); + } + + foreach (i, outerUnits; timeStrings) + { + foreach (innerUnits; timeStrings[0 .. i]) + assert(cmpTimeUnits(outerUnits, innerUnits) == 1); + } +} + + +/++ + Compares two time unit strings at compile time. $(D "years") are the largest + units and $(D "hnsecs") are the smallest. + + This template is used instead of $(D cmpTimeUnits) because exceptions + can't be thrown at compile time and $(D cmpTimeUnits) must enforce that + the strings it's given are valid time unit strings. This template uses a + template constraint instead. + + Returns: + $(BOOKTABLE, + $(TR $(TD this < rhs) $(TD < 0)) + $(TR $(TD this == rhs) $(TD 0)) + $(TR $(TD this > rhs) $(TD > 0)) + ) + +/ +template CmpTimeUnits(string lhs, string rhs) +if (validTimeUnits(lhs, rhs)) +{ + enum CmpTimeUnits = cmpTimeUnitsCTFE(lhs, rhs); +} + + +// Helper function for CmpTimeUnits. +private int cmpTimeUnitsCTFE(string lhs, string rhs) @safe pure nothrow @nogc +{ + import std.algorithm.searching : countUntil; + auto tstrings = timeStrings; + immutable indexOfLHS = countUntil(tstrings, lhs); + immutable indexOfRHS = countUntil(tstrings, rhs); + + if (indexOfLHS < indexOfRHS) + return -1; + if (indexOfLHS > indexOfRHS) + return 1; + + return 0; +} + +@safe unittest +{ + import std.format : format; + import std.meta : AliasSeq; + + static string genTest(size_t index) + { + auto currUnits = timeStrings[index]; + auto test = format(`assert(CmpTimeUnits!("%s", "%s") == 0);`, currUnits, currUnits); + + foreach (units; timeStrings[index + 1 .. $]) + test ~= format(`assert(CmpTimeUnits!("%s", "%s") == -1);`, currUnits, units); + + foreach (units; timeStrings[0 .. index]) + test ~= format(`assert(CmpTimeUnits!("%s", "%s") == 1);`, currUnits, units); + + return test; + } + + static assert(timeStrings.length == 10); + foreach (n; AliasSeq!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) + mixin(genTest(n)); +} + + +package: + + +/+ + Array of the short (three letter) names of each month. + +/ +immutable string[12] _monthNames = ["Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec"]; + +/+ + The maximum valid Day in the given month in the given year. + + Params: + year = The year to get the day for. + month = The month of the Gregorian Calendar to get the day for. + +/ +ubyte maxDay(int year, int month) @safe pure nothrow @nogc +in +{ + assert(valid!"months"(month)); +} +body +{ + switch (month) + { + case Month.jan, Month.mar, Month.may, Month.jul, Month.aug, Month.oct, Month.dec: + return 31; + case Month.feb: + return yearIsLeapYear(year) ? 29 : 28; + case Month.apr, Month.jun, Month.sep, Month.nov: + return 30; + default: + assert(0, "Invalid month."); + } +} + +@safe unittest +{ + // Test A.D. + assert(maxDay(1999, 1) == 31); + assert(maxDay(1999, 2) == 28); + assert(maxDay(1999, 3) == 31); + assert(maxDay(1999, 4) == 30); + assert(maxDay(1999, 5) == 31); + assert(maxDay(1999, 6) == 30); + assert(maxDay(1999, 7) == 31); + assert(maxDay(1999, 8) == 31); + assert(maxDay(1999, 9) == 30); + assert(maxDay(1999, 10) == 31); + assert(maxDay(1999, 11) == 30); + assert(maxDay(1999, 12) == 31); + + assert(maxDay(2000, 1) == 31); + assert(maxDay(2000, 2) == 29); + assert(maxDay(2000, 3) == 31); + assert(maxDay(2000, 4) == 30); + assert(maxDay(2000, 5) == 31); + assert(maxDay(2000, 6) == 30); + assert(maxDay(2000, 7) == 31); + assert(maxDay(2000, 8) == 31); + assert(maxDay(2000, 9) == 30); + assert(maxDay(2000, 10) == 31); + assert(maxDay(2000, 11) == 30); + assert(maxDay(2000, 12) == 31); + + // Test B.C. + assert(maxDay(-1999, 1) == 31); + assert(maxDay(-1999, 2) == 28); + assert(maxDay(-1999, 3) == 31); + assert(maxDay(-1999, 4) == 30); + assert(maxDay(-1999, 5) == 31); + assert(maxDay(-1999, 6) == 30); + assert(maxDay(-1999, 7) == 31); + assert(maxDay(-1999, 8) == 31); + assert(maxDay(-1999, 9) == 30); + assert(maxDay(-1999, 10) == 31); + assert(maxDay(-1999, 11) == 30); + assert(maxDay(-1999, 12) == 31); + + assert(maxDay(-2000, 1) == 31); + assert(maxDay(-2000, 2) == 29); + assert(maxDay(-2000, 3) == 31); + assert(maxDay(-2000, 4) == 30); + assert(maxDay(-2000, 5) == 31); + assert(maxDay(-2000, 6) == 30); + assert(maxDay(-2000, 7) == 31); + assert(maxDay(-2000, 8) == 31); + assert(maxDay(-2000, 9) == 30); + assert(maxDay(-2000, 10) == 31); + assert(maxDay(-2000, 11) == 30); + assert(maxDay(-2000, 12) == 31); +} + +/+ + Splits out a particular unit from hnsecs and gives the value for that + unit and the remaining hnsecs. It really shouldn't be used unless unless + all units larger than the given units have already been split out. + + Params: + units = The units to split out. + hnsecs = The current total hnsecs. Upon returning, it is the hnsecs left + after splitting out the given units. + + Returns: + The number of the given units from converting hnsecs to those units. + +/ +long splitUnitsFromHNSecs(string units)(ref long hnsecs) @safe pure nothrow @nogc +if (validTimeUnits(units) && CmpTimeUnits!(units, "months") < 0) +{ + import core.time : convert; + immutable value = convert!("hnsecs", units)(hnsecs); + hnsecs -= convert!(units, "hnsecs")(value); + return value; +} + +@safe unittest +{ + auto hnsecs = 2595000000007L; + immutable days = splitUnitsFromHNSecs!"days"(hnsecs); + assert(days == 3); + assert(hnsecs == 3000000007); + + immutable minutes = splitUnitsFromHNSecs!"minutes"(hnsecs); + assert(minutes == 5); + assert(hnsecs == 7); +} + + +/+ + Returns the day of the week for the given day of the Gregorian Calendar. + + Params: + day = The day of the Gregorian Calendar for which to get the day of + the week. + +/ +DayOfWeek getDayOfWeek(int day) @safe pure nothrow @nogc +{ + // January 1st, 1 A.D. was a Monday + if (day >= 0) + return cast(DayOfWeek)(day % 7); + else + { + immutable dow = cast(DayOfWeek)((day % 7) + 7); + + if (dow == 7) + return DayOfWeek.sun; + else + return dow; + } +} + +@safe unittest +{ + import std.datetime.systime : SysTime; + + // Test A.D. + assert(getDayOfWeek(SysTime(Date(1, 1, 1)).dayOfGregorianCal) == DayOfWeek.mon); + assert(getDayOfWeek(SysTime(Date(1, 1, 2)).dayOfGregorianCal) == DayOfWeek.tue); + assert(getDayOfWeek(SysTime(Date(1, 1, 3)).dayOfGregorianCal) == DayOfWeek.wed); + assert(getDayOfWeek(SysTime(Date(1, 1, 4)).dayOfGregorianCal) == DayOfWeek.thu); + assert(getDayOfWeek(SysTime(Date(1, 1, 5)).dayOfGregorianCal) == DayOfWeek.fri); + assert(getDayOfWeek(SysTime(Date(1, 1, 6)).dayOfGregorianCal) == DayOfWeek.sat); + assert(getDayOfWeek(SysTime(Date(1, 1, 7)).dayOfGregorianCal) == DayOfWeek.sun); + assert(getDayOfWeek(SysTime(Date(1, 1, 8)).dayOfGregorianCal) == DayOfWeek.mon); + assert(getDayOfWeek(SysTime(Date(1, 1, 9)).dayOfGregorianCal) == DayOfWeek.tue); + assert(getDayOfWeek(SysTime(Date(2, 1, 1)).dayOfGregorianCal) == DayOfWeek.tue); + assert(getDayOfWeek(SysTime(Date(3, 1, 1)).dayOfGregorianCal) == DayOfWeek.wed); + assert(getDayOfWeek(SysTime(Date(4, 1, 1)).dayOfGregorianCal) == DayOfWeek.thu); + assert(getDayOfWeek(SysTime(Date(5, 1, 1)).dayOfGregorianCal) == DayOfWeek.sat); + assert(getDayOfWeek(SysTime(Date(2000, 1, 1)).dayOfGregorianCal) == DayOfWeek.sat); + assert(getDayOfWeek(SysTime(Date(2010, 8, 22)).dayOfGregorianCal) == DayOfWeek.sun); + assert(getDayOfWeek(SysTime(Date(2010, 8, 23)).dayOfGregorianCal) == DayOfWeek.mon); + assert(getDayOfWeek(SysTime(Date(2010, 8, 24)).dayOfGregorianCal) == DayOfWeek.tue); + assert(getDayOfWeek(SysTime(Date(2010, 8, 25)).dayOfGregorianCal) == DayOfWeek.wed); + assert(getDayOfWeek(SysTime(Date(2010, 8, 26)).dayOfGregorianCal) == DayOfWeek.thu); + assert(getDayOfWeek(SysTime(Date(2010, 8, 27)).dayOfGregorianCal) == DayOfWeek.fri); + assert(getDayOfWeek(SysTime(Date(2010, 8, 28)).dayOfGregorianCal) == DayOfWeek.sat); + assert(getDayOfWeek(SysTime(Date(2010, 8, 29)).dayOfGregorianCal) == DayOfWeek.sun); + + // Test B.C. + assert(getDayOfWeek(SysTime(Date(0, 12, 31)).dayOfGregorianCal) == DayOfWeek.sun); + assert(getDayOfWeek(SysTime(Date(0, 12, 30)).dayOfGregorianCal) == DayOfWeek.sat); + assert(getDayOfWeek(SysTime(Date(0, 12, 29)).dayOfGregorianCal) == DayOfWeek.fri); + assert(getDayOfWeek(SysTime(Date(0, 12, 28)).dayOfGregorianCal) == DayOfWeek.thu); + assert(getDayOfWeek(SysTime(Date(0, 12, 27)).dayOfGregorianCal) == DayOfWeek.wed); + assert(getDayOfWeek(SysTime(Date(0, 12, 26)).dayOfGregorianCal) == DayOfWeek.tue); + assert(getDayOfWeek(SysTime(Date(0, 12, 25)).dayOfGregorianCal) == DayOfWeek.mon); + assert(getDayOfWeek(SysTime(Date(0, 12, 24)).dayOfGregorianCal) == DayOfWeek.sun); + assert(getDayOfWeek(SysTime(Date(0, 12, 23)).dayOfGregorianCal) == DayOfWeek.sat); +} + + +private: + +enum daysInYear = 365; // The number of days in a non-leap year. +enum daysInLeapYear = 366; // The numbef or days in a leap year. +enum daysIn4Years = daysInYear * 3 + daysInLeapYear; // Number of days in 4 years. +enum daysIn100Years = daysIn4Years * 25 - 1; // The number of days in 100 years. +enum daysIn400Years = daysIn100Years * 4 + 1; // The number of days in 400 years. + +/+ + Array of integers representing the last days of each month in a year. + +/ +immutable int[13] lastDayNonLeap = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; + +/+ + Array of integers representing the last days of each month in a leap year. + +/ +immutable int[13] lastDayLeap = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]; + + +/+ + Returns the string representation of the given month. + +/ +string monthToString(Month month) @safe pure +{ + import std.format : format; + assert(month >= Month.jan && month <= Month.dec, format("Invalid month: %s", month)); + return _monthNames[month - Month.jan]; +} + +@safe unittest +{ + assert(monthToString(Month.jan) == "Jan"); + assert(monthToString(Month.feb) == "Feb"); + assert(monthToString(Month.mar) == "Mar"); + assert(monthToString(Month.apr) == "Apr"); + assert(monthToString(Month.may) == "May"); + assert(monthToString(Month.jun) == "Jun"); + assert(monthToString(Month.jul) == "Jul"); + assert(monthToString(Month.aug) == "Aug"); + assert(monthToString(Month.sep) == "Sep"); + assert(monthToString(Month.oct) == "Oct"); + assert(monthToString(Month.nov) == "Nov"); + assert(monthToString(Month.dec) == "Dec"); +} + + +/+ + Returns the Month corresponding to the given string. + + Params: + monthStr = The string representation of the month to get the Month for. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given month is not a + valid month string. + +/ +Month monthFromString(string monthStr) @safe pure +{ + import std.format : format; + switch (monthStr) + { + case "Jan": + return Month.jan; + case "Feb": + return Month.feb; + case "Mar": + return Month.mar; + case "Apr": + return Month.apr; + case "May": + return Month.may; + case "Jun": + return Month.jun; + case "Jul": + return Month.jul; + case "Aug": + return Month.aug; + case "Sep": + return Month.sep; + case "Oct": + return Month.oct; + case "Nov": + return Month.nov; + case "Dec": + return Month.dec; + default: + throw new DateTimeException(format("Invalid month %s", monthStr)); + } +} + +@safe unittest +{ + import std.stdio : writeln; + import std.traits : EnumMembers; + foreach (badStr; ["Ja", "Janu", "Januar", "Januarys", "JJanuary", "JANUARY", + "JAN", "january", "jaNuary", "jaN", "jaNuaRy", "jAn"]) + { + scope(failure) writeln(badStr); + assertThrown!DateTimeException(monthFromString(badStr)); + } + + foreach (month; EnumMembers!Month) + { + scope(failure) writeln(month); + assert(monthFromString(monthToString(month)) == month); + } +} + + +version (unittest) +{ + // All of these helper arrays are sorted in ascending order. + auto testYearsBC = [-1999, -1200, -600, -4, -1, 0]; + auto testYearsAD = [1, 4, 1000, 1999, 2000, 2012]; + + // I'd use a Tuple, but I get forward reference errors if I try. + struct MonthDay + { + Month month; + short day; + + this(int m, short d) + { + month = cast(Month) m; + day = d; + } + } + + MonthDay[] testMonthDays = [MonthDay(1, 1), + MonthDay(1, 2), + MonthDay(3, 17), + MonthDay(7, 4), + MonthDay(10, 27), + MonthDay(12, 30), + MonthDay(12, 31)]; + + auto testDays = [1, 2, 9, 10, 16, 20, 25, 28, 29, 30, 31]; + + auto testTODs = [TimeOfDay(0, 0, 0), + TimeOfDay(0, 0, 1), + TimeOfDay(0, 1, 0), + TimeOfDay(1, 0, 0), + TimeOfDay(13, 13, 13), + TimeOfDay(23, 59, 59)]; + + auto testHours = [0, 1, 12, 22, 23]; + auto testMinSecs = [0, 1, 30, 58, 59]; + + // Throwing exceptions is incredibly expensive, so we want to use a smaller + // set of values for tests using assertThrown. + auto testTODsThrown = [TimeOfDay(0, 0, 0), + TimeOfDay(13, 13, 13), + TimeOfDay(23, 59, 59)]; + + Date[] testDatesBC; + Date[] testDatesAD; + + DateTime[] testDateTimesBC; + DateTime[] testDateTimesAD; + + // I'd use a Tuple, but I get forward reference errors if I try. + struct GregDay { int day; Date date; } + auto testGregDaysBC = [GregDay(-1_373_427, Date(-3760, 9, 7)), // Start of the Hebrew Calendar + GregDay(-735_233, Date(-2012, 1, 1)), + GregDay(-735_202, Date(-2012, 2, 1)), + GregDay(-735_175, Date(-2012, 2, 28)), + GregDay(-735_174, Date(-2012, 2, 29)), + GregDay(-735_173, Date(-2012, 3, 1)), + GregDay(-734_502, Date(-2010, 1, 1)), + GregDay(-734_472, Date(-2010, 1, 31)), + GregDay(-734_471, Date(-2010, 2, 1)), + GregDay(-734_444, Date(-2010, 2, 28)), + GregDay(-734_443, Date(-2010, 3, 1)), + GregDay(-734_413, Date(-2010, 3, 31)), + GregDay(-734_412, Date(-2010, 4, 1)), + GregDay(-734_383, Date(-2010, 4, 30)), + GregDay(-734_382, Date(-2010, 5, 1)), + GregDay(-734_352, Date(-2010, 5, 31)), + GregDay(-734_351, Date(-2010, 6, 1)), + GregDay(-734_322, Date(-2010, 6, 30)), + GregDay(-734_321, Date(-2010, 7, 1)), + GregDay(-734_291, Date(-2010, 7, 31)), + GregDay(-734_290, Date(-2010, 8, 1)), + GregDay(-734_260, Date(-2010, 8, 31)), + GregDay(-734_259, Date(-2010, 9, 1)), + GregDay(-734_230, Date(-2010, 9, 30)), + GregDay(-734_229, Date(-2010, 10, 1)), + GregDay(-734_199, Date(-2010, 10, 31)), + GregDay(-734_198, Date(-2010, 11, 1)), + GregDay(-734_169, Date(-2010, 11, 30)), + GregDay(-734_168, Date(-2010, 12, 1)), + GregDay(-734_139, Date(-2010, 12, 30)), + GregDay(-734_138, Date(-2010, 12, 31)), + GregDay(-731_215, Date(-2001, 1, 1)), + GregDay(-730_850, Date(-2000, 1, 1)), + GregDay(-730_849, Date(-2000, 1, 2)), + GregDay(-730_486, Date(-2000, 12, 30)), + GregDay(-730_485, Date(-2000, 12, 31)), + GregDay(-730_484, Date(-1999, 1, 1)), + GregDay(-694_690, Date(-1901, 1, 1)), + GregDay(-694_325, Date(-1900, 1, 1)), + GregDay(-585_118, Date(-1601, 1, 1)), + GregDay(-584_753, Date(-1600, 1, 1)), + GregDay(-584_388, Date(-1600, 12, 31)), + GregDay(-584_387, Date(-1599, 1, 1)), + GregDay(-365_972, Date(-1001, 1, 1)), + GregDay(-365_607, Date(-1000, 1, 1)), + GregDay(-183_351, Date(-501, 1, 1)), + GregDay(-182_986, Date(-500, 1, 1)), + GregDay(-182_621, Date(-499, 1, 1)), + GregDay(-146_827, Date(-401, 1, 1)), + GregDay(-146_462, Date(-400, 1, 1)), + GregDay(-146_097, Date(-400, 12, 31)), + GregDay(-110_302, Date(-301, 1, 1)), + GregDay(-109_937, Date(-300, 1, 1)), + GregDay(-73_778, Date(-201, 1, 1)), + GregDay(-73_413, Date(-200, 1, 1)), + GregDay(-38_715, Date(-105, 1, 1)), + GregDay(-37_254, Date(-101, 1, 1)), + GregDay(-36_889, Date(-100, 1, 1)), + GregDay(-36_524, Date(-99, 1, 1)), + GregDay(-36_160, Date(-99, 12, 31)), + GregDay(-35_794, Date(-97, 1, 1)), + GregDay(-18_627, Date(-50, 1, 1)), + GregDay(-18_262, Date(-49, 1, 1)), + GregDay(-3652, Date(-9, 1, 1)), + GregDay(-2191, Date(-5, 1, 1)), + GregDay(-1827, Date(-5, 12, 31)), + GregDay(-1826, Date(-4, 1, 1)), + GregDay(-1825, Date(-4, 1, 2)), + GregDay(-1462, Date(-4, 12, 30)), + GregDay(-1461, Date(-4, 12, 31)), + GregDay(-1460, Date(-3, 1, 1)), + GregDay(-1096, Date(-3, 12, 31)), + GregDay(-1095, Date(-2, 1, 1)), + GregDay(-731, Date(-2, 12, 31)), + GregDay(-730, Date(-1, 1, 1)), + GregDay(-367, Date(-1, 12, 30)), + GregDay(-366, Date(-1, 12, 31)), + GregDay(-365, Date(0, 1, 1)), + GregDay(-31, Date(0, 11, 30)), + GregDay(-30, Date(0, 12, 1)), + GregDay(-1, Date(0, 12, 30)), + GregDay(0, Date(0, 12, 31))]; + + auto testGregDaysAD = [GregDay(1, Date(1, 1, 1)), + GregDay(2, Date(1, 1, 2)), + GregDay(32, Date(1, 2, 1)), + GregDay(365, Date(1, 12, 31)), + GregDay(366, Date(2, 1, 1)), + GregDay(731, Date(3, 1, 1)), + GregDay(1096, Date(4, 1, 1)), + GregDay(1097, Date(4, 1, 2)), + GregDay(1460, Date(4, 12, 30)), + GregDay(1461, Date(4, 12, 31)), + GregDay(1462, Date(5, 1, 1)), + GregDay(17_898, Date(50, 1, 1)), + GregDay(35_065, Date(97, 1, 1)), + GregDay(36_160, Date(100, 1, 1)), + GregDay(36_525, Date(101, 1, 1)), + GregDay(37_986, Date(105, 1, 1)), + GregDay(72_684, Date(200, 1, 1)), + GregDay(73_049, Date(201, 1, 1)), + GregDay(109_208, Date(300, 1, 1)), + GregDay(109_573, Date(301, 1, 1)), + GregDay(145_732, Date(400, 1, 1)), + GregDay(146_098, Date(401, 1, 1)), + GregDay(182_257, Date(500, 1, 1)), + GregDay(182_622, Date(501, 1, 1)), + GregDay(364_878, Date(1000, 1, 1)), + GregDay(365_243, Date(1001, 1, 1)), + GregDay(584_023, Date(1600, 1, 1)), + GregDay(584_389, Date(1601, 1, 1)), + GregDay(693_596, Date(1900, 1, 1)), + GregDay(693_961, Date(1901, 1, 1)), + GregDay(729_755, Date(1999, 1, 1)), + GregDay(730_120, Date(2000, 1, 1)), + GregDay(730_121, Date(2000, 1, 2)), + GregDay(730_484, Date(2000, 12, 30)), + GregDay(730_485, Date(2000, 12, 31)), + GregDay(730_486, Date(2001, 1, 1)), + GregDay(733_773, Date(2010, 1, 1)), + GregDay(733_774, Date(2010, 1, 2)), + GregDay(733_803, Date(2010, 1, 31)), + GregDay(733_804, Date(2010, 2, 1)), + GregDay(733_831, Date(2010, 2, 28)), + GregDay(733_832, Date(2010, 3, 1)), + GregDay(733_862, Date(2010, 3, 31)), + GregDay(733_863, Date(2010, 4, 1)), + GregDay(733_892, Date(2010, 4, 30)), + GregDay(733_893, Date(2010, 5, 1)), + GregDay(733_923, Date(2010, 5, 31)), + GregDay(733_924, Date(2010, 6, 1)), + GregDay(733_953, Date(2010, 6, 30)), + GregDay(733_954, Date(2010, 7, 1)), + GregDay(733_984, Date(2010, 7, 31)), + GregDay(733_985, Date(2010, 8, 1)), + GregDay(734_015, Date(2010, 8, 31)), + GregDay(734_016, Date(2010, 9, 1)), + GregDay(734_045, Date(2010, 9, 30)), + GregDay(734_046, Date(2010, 10, 1)), + GregDay(734_076, Date(2010, 10, 31)), + GregDay(734_077, Date(2010, 11, 1)), + GregDay(734_106, Date(2010, 11, 30)), + GregDay(734_107, Date(2010, 12, 1)), + GregDay(734_136, Date(2010, 12, 30)), + GregDay(734_137, Date(2010, 12, 31)), + GregDay(734_503, Date(2012, 1, 1)), + GregDay(734_534, Date(2012, 2, 1)), + GregDay(734_561, Date(2012, 2, 28)), + GregDay(734_562, Date(2012, 2, 29)), + GregDay(734_563, Date(2012, 3, 1)), + GregDay(734_858, Date(2012, 12, 21))]; + + // I'd use a Tuple, but I get forward reference errors if I try. + struct DayOfYear { int day; MonthDay md; } + auto testDaysOfYear = [DayOfYear(1, MonthDay(1, 1)), + DayOfYear(2, MonthDay(1, 2)), + DayOfYear(3, MonthDay(1, 3)), + DayOfYear(31, MonthDay(1, 31)), + DayOfYear(32, MonthDay(2, 1)), + DayOfYear(59, MonthDay(2, 28)), + DayOfYear(60, MonthDay(3, 1)), + DayOfYear(90, MonthDay(3, 31)), + DayOfYear(91, MonthDay(4, 1)), + DayOfYear(120, MonthDay(4, 30)), + DayOfYear(121, MonthDay(5, 1)), + DayOfYear(151, MonthDay(5, 31)), + DayOfYear(152, MonthDay(6, 1)), + DayOfYear(181, MonthDay(6, 30)), + DayOfYear(182, MonthDay(7, 1)), + DayOfYear(212, MonthDay(7, 31)), + DayOfYear(213, MonthDay(8, 1)), + DayOfYear(243, MonthDay(8, 31)), + DayOfYear(244, MonthDay(9, 1)), + DayOfYear(273, MonthDay(9, 30)), + DayOfYear(274, MonthDay(10, 1)), + DayOfYear(304, MonthDay(10, 31)), + DayOfYear(305, MonthDay(11, 1)), + DayOfYear(334, MonthDay(11, 30)), + DayOfYear(335, MonthDay(12, 1)), + DayOfYear(363, MonthDay(12, 29)), + DayOfYear(364, MonthDay(12, 30)), + DayOfYear(365, MonthDay(12, 31))]; + + auto testDaysOfLeapYear = [DayOfYear(1, MonthDay(1, 1)), + DayOfYear(2, MonthDay(1, 2)), + DayOfYear(3, MonthDay(1, 3)), + DayOfYear(31, MonthDay(1, 31)), + DayOfYear(32, MonthDay(2, 1)), + DayOfYear(59, MonthDay(2, 28)), + DayOfYear(60, MonthDay(2, 29)), + DayOfYear(61, MonthDay(3, 1)), + DayOfYear(91, MonthDay(3, 31)), + DayOfYear(92, MonthDay(4, 1)), + DayOfYear(121, MonthDay(4, 30)), + DayOfYear(122, MonthDay(5, 1)), + DayOfYear(152, MonthDay(5, 31)), + DayOfYear(153, MonthDay(6, 1)), + DayOfYear(182, MonthDay(6, 30)), + DayOfYear(183, MonthDay(7, 1)), + DayOfYear(213, MonthDay(7, 31)), + DayOfYear(214, MonthDay(8, 1)), + DayOfYear(244, MonthDay(8, 31)), + DayOfYear(245, MonthDay(9, 1)), + DayOfYear(274, MonthDay(9, 30)), + DayOfYear(275, MonthDay(10, 1)), + DayOfYear(305, MonthDay(10, 31)), + DayOfYear(306, MonthDay(11, 1)), + DayOfYear(335, MonthDay(11, 30)), + DayOfYear(336, MonthDay(12, 1)), + DayOfYear(364, MonthDay(12, 29)), + DayOfYear(365, MonthDay(12, 30)), + DayOfYear(366, MonthDay(12, 31))]; + + void initializeTests() @safe + { + foreach (year; testYearsBC) + { + foreach (md; testMonthDays) + testDatesBC ~= Date(year, md.month, md.day); + } + + foreach (year; testYearsAD) + { + foreach (md; testMonthDays) + testDatesAD ~= Date(year, md.month, md.day); + } + + foreach (dt; testDatesBC) + { + foreach (tod; testTODs) + testDateTimesBC ~= DateTime(dt, tod); + } + + foreach (dt; testDatesAD) + { + foreach (tod; testTODs) + testDateTimesAD ~= DateTime(dt, tod); + } + } +} diff --git a/libphobos/src/std/datetime/interval.d b/libphobos/src/std/datetime/interval.d new file mode 100644 index 0000000..d1eec45 --- /dev/null +++ b/libphobos/src/std/datetime/interval.d @@ -0,0 +1,9131 @@ +// Written in the D programming language + +/++ + License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + Authors: Jonathan M Davis + Source: $(PHOBOSSRC std/datetime/_interval.d) ++/ +module std.datetime.interval; + +import core.time : Duration, dur; +import std.datetime.date : AllowDayOverflow, DateTimeException, daysToDayOfWeek, + DayOfWeek, isTimePoint, Month; +import std.exception : enforce; +import std.traits : isIntegral, Unqual; +import std.typecons : Flag; + +version (unittest) import std.exception : assertThrown; + + +/++ + Indicates a direction in time. One example of its use is $(LREF Interval)'s + $(LREF expand) function which uses it to indicate whether the interval + should be expanded backwards (into the past), forwards (into the future), or + both. + +/ +enum Direction +{ + /// Backward. + bwd, + + /// Forward. + fwd, + + /// Both backward and forward. + both +} + + +/++ + Used to indicate whether $(D popFront) should be called immediately upon + creating a range. The idea is that for some functions used to generate a + range for an interval, $(D front) is not necessarily a time point which + would ever be generated by the range (e.g. if the range were every Sunday + within an interval, but the interval started on a Monday), so there needs + to be a way to deal with that. To get the first time point in the range to + match what the function generates, then use $(D PopFirst.yes) to indicate + that the range should have $(D popFront) called on it before the range is + returned so that $(D front) is a time point which the function would + generate. To let the first time point not match the generator function, + use $(D PopFront.no). + + For instance, if the function used to generate a range of time points + generated successive Easters (i.e. you're iterating over all of the Easters + within the interval), the initial date probably isn't an Easter. Using + $(D PopFirst.yes) would tell the function which returned the range that + $(D popFront) was to be called so that front would then be an Easter - the + next one generated by the function (which when iterating forward would be + the Easter following the original $(D front), while when iterating backward, + it would be the Easter prior to the original $(D front)). If + $(D PopFirst.no) were used, then $(D front) would remain the original time + point and it would not necessarily be a time point which would be generated + by the range-generating function (which in many cases is exactly what is + desired - e.g. if iterating over every day starting at the beginning of the + interval). + + If set to $(D PopFirst.no), then popFront is not called before returning + the range. + + Otherwise, if set to $(D PopFirst.yes), then popFront is called before + returning the range. + +/ +alias PopFirst = Flag!"popFirst"; + + +/++ + Represents an interval of time. + + An $(D Interval) has a starting point and an end point. The interval of time + is therefore the time starting at the starting point up to, but not + including, the end point. e.g. + + $(BOOKTABLE, + $(TR $(TD [January 5th, 2010 - March 10th, 2010$(RPAREN))) + $(TR $(TD [05:00:30 - 12:00:00$(RPAREN))) + $(TR $(TD [1982-01-04T08:59:00 - 2010-07-04T12:00:00$(RPAREN))) + ) + + A range can be obtained from an $(D Interval), allowing iteration over + that interval, with the exact time points which are iterated over depending + on the function which generates the range. + +/ +struct Interval(TP) +{ +public: + + /++ + Params: + begin = The time point which begins the interval. + end = The time point which ends (but is not included in) the + interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if $(D_PARAM end) is + before $(D_PARAM begin). + + Example: + -------------------- + Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)); + -------------------- + +/ + this(U)(in TP begin, in U end) pure + if (is(Unqual!TP == Unqual!U)) + { + if (!_valid(begin, end)) + throw new DateTimeException("Arguments would result in an invalid Interval."); + _begin = cast(TP) begin; + _end = cast(TP) end; + } + + + /++ + Params: + begin = The time point which begins the interval. + duration = The duration from the starting point to the end point. + + Throws: + $(REF DateTimeException,std,datetime,date) if the resulting + $(D end) is before $(D begin). + + Example: + -------------------- + assert(Interval!Date(Date(1996, 1, 2), dur!"days"(3)) == + Interval!Date(Date(1996, 1, 2), Date(1996, 1, 5))); + -------------------- + +/ + this(D)(in TP begin, in D duration) pure + if (__traits(compiles, begin + duration)) + { + _begin = cast(TP) begin; + _end = begin + duration; + if (!_valid(_begin, _end)) + throw new DateTimeException("Arguments would result in an invalid Interval."); + } + + + /++ + Params: + rhs = The $(LREF Interval) to assign to this one. + +/ + ref Interval opAssign(const ref Interval rhs) pure nothrow + { + _begin = cast(TP) rhs._begin; + _end = cast(TP) rhs._end; + return this; + } + + + /++ + Params: + rhs = The $(LREF Interval) to assign to this one. + +/ + ref Interval opAssign(Interval rhs) pure nothrow + { + _begin = cast(TP) rhs._begin; + _end = cast(TP) rhs._end; + return this; + } + + + /++ + The starting point of the interval. It is included in the interval. + + Example: + -------------------- + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).begin == + Date(1996, 1, 2)); + -------------------- + +/ + @property TP begin() const pure nothrow + { + return cast(TP) _begin; + } + + + /++ + The starting point of the interval. It is included in the interval. + + Params: + timePoint = The time point to set $(D begin) to. + + Throws: + $(REF DateTimeException,std,datetime,date) if the resulting + interval would be invalid. + +/ + @property void begin(TP timePoint) pure + { + if (!_valid(timePoint, _end)) + throw new DateTimeException("Arguments would result in an invalid Interval."); + _begin = timePoint; + } + + + /++ + The end point of the interval. It is excluded from the interval. + + Example: + -------------------- + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).end == + Date(2012, 3, 1)); + -------------------- + +/ + @property TP end() const pure nothrow + { + return cast(TP) _end; + } + + + /++ + The end point of the interval. It is excluded from the interval. + + Params: + timePoint = The time point to set end to. + + Throws: + $(REF DateTimeException,std,datetime,date) if the resulting + interval would be invalid. + +/ + @property void end(TP timePoint) pure + { + if (!_valid(_begin, timePoint)) + throw new DateTimeException("Arguments would result in an invalid Interval."); + _end = timePoint; + } + + + /++ + Returns the duration between $(D begin) and $(D end). + + Example: + -------------------- + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).length == + dur!"days"(5903)); + -------------------- + +/ + @property auto length() const pure nothrow + { + return _end - _begin; + } + + + /++ + Whether the interval's length is 0, that is, whether $(D begin == end). + + Example: + -------------------- + assert(Interval!Date(Date(1996, 1, 2), Date(1996, 1, 2)).empty); + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).empty); + -------------------- + +/ + @property bool empty() const pure nothrow + { + return _begin == _end; + } + + + /++ + Whether the given time point is within this interval. + + Params: + timePoint = The time point to check for inclusion in this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty. + + Example: + -------------------- + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains( + Date(1994, 12, 24))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains( + Date(2000, 1, 5))); + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains( + Date(2012, 3, 1))); + -------------------- + +/ + bool contains(in TP timePoint) const pure + { + _enforceNotEmpty(); + return timePoint >= _begin && timePoint < _end; + } + + + /++ + Whether the given interval is completely within this interval. + + Params: + interval = The interval to check for inclusion in this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if either interval is + empty. + + Example: + -------------------- + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains( + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains( + Interval!Date(Date(1998, 2, 28), Date(2013, 5, 1)))); + -------------------- + +/ + bool contains(in Interval interval) const pure + { + _enforceNotEmpty(); + interval._enforceNotEmpty(); + return interval._begin >= _begin && + interval._begin < _end && + interval._end <= _end; + } + + + /++ + Whether the given interval is completely within this interval. + + Always returns false (unless this interval is empty), because an + interval going to positive infinity can never be contained in a finite + interval. + + Params: + interval = The interval to check for inclusion in this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty. + + Example: + -------------------- + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains( + PosInfInterval!Date(Date(1999, 5, 4)))); + -------------------- + +/ + bool contains(in PosInfInterval!TP interval) const pure + { + _enforceNotEmpty(); + return false; + } + + + /++ + Whether the given interval is completely within this interval. + + Always returns false (unless this interval is empty), because an + interval beginning at negative infinity can never be contained in a + finite interval. + + Params: + interval = The interval to check for inclusion in this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty. + + Example: + -------------------- + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains( + NegInfInterval!Date(Date(1996, 5, 4)))); + -------------------- + +/ + bool contains(in NegInfInterval!TP interval) const pure + { + _enforceNotEmpty(); + return false; + } + + + /++ + Whether this interval is before the given time point. + + Params: + timePoint = The time point to check whether this interval is before + it. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty. + + Example: + -------------------- + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore( + Date(1994, 12, 24))); + + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore( + Date(2000, 1, 5))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore( + Date(2012, 3, 1))); + -------------------- + +/ + bool isBefore(in TP timePoint) const pure + { + _enforceNotEmpty(); + return _end <= timePoint; + } + + + /++ + Whether this interval is before the given interval and does not + intersect with it. + + Params: + interval = The interval to check for against this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if either interval is + empty. + + Example: + -------------------- + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore( + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore( + Interval!Date(Date(2012, 3, 1), Date(2013, 5, 1)))); + -------------------- + +/ + bool isBefore(in Interval interval) const pure + { + _enforceNotEmpty(); + interval._enforceNotEmpty(); + return _end <= interval._begin; + } + + + /++ + Whether this interval is before the given interval and does not + intersect with it. + + Params: + interval = The interval to check for against this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty. + + Example: + -------------------- + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore( + PosInfInterval!Date(Date(1999, 5, 4)))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore( + PosInfInterval!Date(Date(2013, 3, 7)))); + -------------------- + +/ + bool isBefore(in PosInfInterval!TP interval) const pure + { + _enforceNotEmpty(); + return _end <= interval._begin; + } + + + /++ + Whether this interval is before the given interval and does not + intersect with it. + + Always returns false (unless this interval is empty) because a finite + interval can never be before an interval beginning at negative infinity. + + Params: + interval = The interval to check for against this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty. + + Example: + -------------------- + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore( + NegInfInterval!Date(Date(1996, 5, 4)))); + -------------------- + +/ + bool isBefore(in NegInfInterval!TP interval) const pure + { + _enforceNotEmpty(); + return false; + } + + + /++ + Whether this interval is after the given time point. + + Params: + timePoint = The time point to check whether this interval is after + it. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty. + + Example: + -------------------- + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter( + Date(1994, 12, 24))); + + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter( + Date(2000, 1, 5))); + + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter( + Date(2012, 3, 1))); + -------------------- + +/ + bool isAfter(in TP timePoint) const pure + { + _enforceNotEmpty(); + return timePoint < _begin; + } + + + /++ + Whether this interval is after the given interval and does not intersect + it. + + Params: + interval = The interval to check against this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if either interval is + empty. + + Example: + -------------------- + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter( + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter( + Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2)))); + -------------------- + +/ + bool isAfter(in Interval interval) const pure + { + _enforceNotEmpty(); + interval._enforceNotEmpty(); + return _begin >= interval._end; + } + + + /++ + Whether this interval is after the given interval and does not intersect + it. + + Always returns false (unless this interval is empty) because a finite + interval can never be after an interval going to positive infinity. + + Params: + interval = The interval to check against this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty. + + Example: + -------------------- + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter( + PosInfInterval!Date(Date(1999, 5, 4)))); + -------------------- + +/ + bool isAfter(in PosInfInterval!TP interval) const pure + { + _enforceNotEmpty(); + return false; + } + + + /++ + Whether this interval is after the given interval and does not intersect + it. + + Params: + interval = The interval to check against this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty. + + Example: + -------------------- + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter( + NegInfInterval!Date(Date(1996, 1, 2)))); + -------------------- + +/ + bool isAfter(in NegInfInterval!TP interval) const pure + { + _enforceNotEmpty(); + return _begin >= interval._end; + } + + + /++ + Whether the given interval overlaps this interval. + + Params: + interval = The interval to check for intersection with this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if either interval is + empty. + + Example: + -------------------- + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects( + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects( + Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2)))); + -------------------- + +/ + bool intersects(in Interval interval) const pure + { + _enforceNotEmpty(); + interval._enforceNotEmpty(); + return interval._begin < _end && interval._end > _begin; + } + + + /++ + Whether the given interval overlaps this interval. + + Params: + interval = The interval to check for intersection with this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty. + + Example: + -------------------- + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects( + PosInfInterval!Date(Date(1999, 5, 4)))); + + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects( + PosInfInterval!Date(Date(2012, 3, 1)))); + -------------------- + +/ + bool intersects(in PosInfInterval!TP interval) const pure + { + _enforceNotEmpty(); + return _end > interval._begin; + } + + + /++ + Whether the given interval overlaps this interval. + + Params: + interval = The interval to check for intersection with this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty. + + Example: + -------------------- + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects( + NegInfInterval!Date(Date(1996, 1, 2)))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects( + NegInfInterval!Date(Date(2000, 1, 2)))); + -------------------- + +/ + bool intersects(in NegInfInterval!TP interval) const pure + { + _enforceNotEmpty(); + return _begin < interval._end; + } + + + /++ + Returns the intersection of two intervals + + Params: + interval = The interval to intersect with this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the two intervals do + not intersect or if either interval is empty. + + Example: + -------------------- + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == + Interval!Date(Date(1996, 1 , 2), Date(2000, 8, 2))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection( + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) == + Interval!Date(Date(1999, 1 , 12), Date(2011, 9, 17))); + -------------------- + +/ + Interval intersection(in Interval interval) const + { + import std.format : format; + + enforce(this.intersects(interval), + new DateTimeException(format("%s and %s do not intersect.", this, interval))); + + auto begin = _begin > interval._begin ? _begin : interval._begin; + auto end = _end < interval._end ? _end : interval._end; + + return Interval(begin, end); + } + + + /++ + Returns the intersection of two intervals + + Params: + interval = The interval to intersect with this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the two intervals do + not intersect or if this interval is empty. + + Example: + -------------------- + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection( + PosInfInterval!Date(Date(1990, 7, 6))) == + Interval!Date(Date(1996, 1 , 2), Date(2012, 3, 1))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection( + PosInfInterval!Date(Date(1999, 1, 12))) == + Interval!Date(Date(1999, 1 , 12), Date(2012, 3, 1))); + -------------------- + +/ + Interval intersection(in PosInfInterval!TP interval) const + { + import std.format : format; + + enforce(this.intersects(interval), + new DateTimeException(format("%s and %s do not intersect.", this, interval))); + + return Interval(_begin > interval._begin ? _begin : interval._begin, _end); + } + + + /++ + Returns the intersection of two intervals + + Params: + interval = The interval to intersect with this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the two intervals do + not intersect or if this interval is empty. + + Example: + -------------------- + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection( + NegInfInterval!Date(Date(1999, 7, 6))) == + Interval!Date(Date(1996, 1 , 2), Date(1999, 7, 6))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection( + NegInfInterval!Date(Date(2013, 1, 12))) == + Interval!Date(Date(1996, 1 , 2), Date(2012, 3, 1))); + -------------------- + +/ + Interval intersection(in NegInfInterval!TP interval) const + { + import std.format : format; + + enforce(this.intersects(interval), + new DateTimeException(format("%s and %s do not intersect.", this, interval))); + + return Interval(_begin, _end < interval._end ? _end : interval._end); + } + + + /++ + Whether the given interval is adjacent to this interval. + + Params: + interval = The interval to check whether its adjecent to this + interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if either interval is + empty. + + Example: + -------------------- + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent( + Interval!Date(Date(1990, 7, 6), Date(1996, 1, 2)))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent( + Interval!Date(Date(2012, 3, 1), Date(2013, 9, 17)))); + + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent( + Interval!Date(Date(1989, 3, 1), Date(2012, 3, 1)))); + -------------------- + +/ + bool isAdjacent(in Interval interval) const pure + { + _enforceNotEmpty(); + interval._enforceNotEmpty(); + return _begin == interval._end || _end == interval._begin; + } + + + /++ + Whether the given interval is adjacent to this interval. + + Params: + interval = The interval to check whether its adjecent to this + interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty. + + Example: + -------------------- + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent( + PosInfInterval!Date(Date(1999, 5, 4)))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent( + PosInfInterval!Date(Date(2012, 3, 1)))); + -------------------- + +/ + bool isAdjacent(in PosInfInterval!TP interval) const pure + { + _enforceNotEmpty(); + return _end == interval._begin; + } + + + /++ + Whether the given interval is adjacent to this interval. + + Params: + interval = The interval to check whether its adjecent to this + interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty. + + Example: + -------------------- + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent( + NegInfInterval!Date(Date(1996, 1, 2)))); + + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent( + NegInfInterval!Date(Date(2000, 1, 2)))); + -------------------- + +/ + bool isAdjacent(in NegInfInterval!TP interval) const pure + { + _enforceNotEmpty(); + return _begin == interval._end; + } + + + /++ + Returns the union of two intervals + + Params: + interval = The interval to merge with this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the two intervals do + not intersect and are not adjacent or if either interval is empty. + + Example: + -------------------- + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == + Interval!Date(Date(1990, 7 , 6), Date(2012, 3, 1))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge( + Interval!Date(Date(2012, 3, 1), Date(2013, 5, 7))) == + Interval!Date(Date(1996, 1 , 2), Date(2013, 5, 7))); + -------------------- + +/ + Interval merge(in Interval interval) const + { + import std.format : format; + + enforce(this.isAdjacent(interval) || this.intersects(interval), + new DateTimeException(format("%s and %s are not adjacent and do not intersect.", this, interval))); + + auto begin = _begin < interval._begin ? _begin : interval._begin; + auto end = _end > interval._end ? _end : interval._end; + + return Interval(begin, end); + } + + + /++ + Returns the union of two intervals + + Params: + interval = The interval to merge with this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the two intervals do + not intersect and are not adjacent or if this interval is empty. + + Example: + -------------------- + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge( + PosInfInterval!Date(Date(1990, 7, 6))) == + PosInfInterval!Date(Date(1990, 7 , 6))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge( + PosInfInterval!Date(Date(2012, 3, 1))) == + PosInfInterval!Date(Date(1996, 1 , 2))); + -------------------- + +/ + PosInfInterval!TP merge(in PosInfInterval!TP interval) const + { + import std.format : format; + + enforce(this.isAdjacent(interval) || this.intersects(interval), + new DateTimeException(format("%s and %s are not adjacent and do not intersect.", this, interval))); + + return PosInfInterval!TP(_begin < interval._begin ? _begin : interval._begin); + } + + + /++ + Returns the union of two intervals + + Params: + interval = The interval to merge with this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the two intervals do + not intersect and are not adjacent or if this interval is empty. + + Example: + -------------------- + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge( + NegInfInterval!Date(Date(1996, 1, 2))) == + NegInfInterval!Date(Date(2012, 3 , 1))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge( + NegInfInterval!Date(Date(2013, 1, 12))) == + NegInfInterval!Date(Date(2013, 1 , 12))); + -------------------- + +/ + NegInfInterval!TP merge(in NegInfInterval!TP interval) const + { + import std.format : format; + + enforce(this.isAdjacent(interval) || this.intersects(interval), + new DateTimeException(format("%s and %s are not adjacent and do not intersect.", this, interval))); + + return NegInfInterval!TP(_end > interval._end ? _end : interval._end); + } + + + /++ + Returns an interval that covers from the earliest time point of two + intervals up to (but not including) the latest time point of two + intervals. + + Params: + interval = The interval to create a span together with this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if either interval is + empty. + + Example: + -------------------- + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span( + Interval!Date(Date(1990, 7, 6), Date(1991, 1, 8))) == + Interval!Date(Date(1990, 7 , 6), Date(2012, 3, 1))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span( + Interval!Date(Date(2012, 3, 1), Date(2013, 5, 7))) == + Interval!Date(Date(1996, 1 , 2), Date(2013, 5, 7))); + -------------------- + +/ + Interval span(in Interval interval) const pure + { + _enforceNotEmpty(); + interval._enforceNotEmpty(); + + auto begin = _begin < interval._begin ? _begin : interval._begin; + auto end = _end > interval._end ? _end : interval._end; + + return Interval(begin, end); + } + + + /++ + Returns an interval that covers from the earliest time point of two + intervals up to (but not including) the latest time point of two + intervals. + + Params: + interval = The interval to create a span together with this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty. + + Example: + -------------------- + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span( + PosInfInterval!Date(Date(1990, 7, 6))) == + PosInfInterval!Date(Date(1990, 7 , 6))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span( + PosInfInterval!Date(Date(2050, 1, 1))) == + PosInfInterval!Date(Date(1996, 1 , 2))); + -------------------- + +/ + PosInfInterval!TP span(in PosInfInterval!TP interval) const pure + { + _enforceNotEmpty(); + return PosInfInterval!TP(_begin < interval._begin ? _begin : interval._begin); + } + + + /++ + Returns an interval that covers from the earliest time point of two + intervals up to (but not including) the latest time point of two + intervals. + + Params: + interval = The interval to create a span together with this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty. + + Example: + -------------------- + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span( + NegInfInterval!Date(Date(1602, 5, 21))) == + NegInfInterval!Date(Date(2012, 3 , 1))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span( + NegInfInterval!Date(Date(2013, 1, 12))) == + NegInfInterval!Date(Date(2013, 1 , 12))); + -------------------- + +/ + NegInfInterval!TP span(in NegInfInterval!TP interval) const pure + { + _enforceNotEmpty(); + return NegInfInterval!TP(_end > interval._end ? _end : interval._end); + } + + + /++ + Shifts the interval forward or backwards in time by the given duration + (a positive duration shifts the interval forward; a negative duration + shifts it backward). Effectively, it does $(D begin += duration) and + $(D end += duration). + + Params: + duration = The duration to shift the interval by. + + Throws: + $(REF DateTimeException,std,datetime,date) this interval is empty + or if the resulting interval would be invalid. + + Example: + -------------------- + auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 4, 5)); + auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 4, 5)); + + interval1.shift(dur!"days"(50)); + assert(interval1 == Interval!Date(Date(1996, 2, 21), Date(2012, 5, 25))); + + interval2.shift(dur!"days"(-50)); + assert(interval2 == Interval!Date(Date(1995, 11, 13), Date(2012, 2, 15))); + -------------------- + +/ + void shift(D)(D duration) pure + if (__traits(compiles, begin + duration)) + { + _enforceNotEmpty(); + + auto begin = _begin + duration; + auto end = _end + duration; + + if (!_valid(begin, end)) + throw new DateTimeException("Argument would result in an invalid Interval."); + + _begin = begin; + _end = end; + } + + + static if (__traits(compiles, begin.add!"months"(1)) && + __traits(compiles, begin.add!"years"(1))) + { + /++ + Shifts the interval forward or backwards in time by the given number + of years and/or months (a positive number of years and months shifts + the interval forward; a negative number shifts it backward). + It adds the years the given years and months to both begin and end. + It effectively calls $(D add!"years"()) and then $(D add!"months"()) + on begin and end with the given number of years and months. + + Params: + years = The number of years to shift the interval by. + months = The number of months to shift the interval by. + allowOverflow = Whether the days should be allowed to overflow + on $(D begin) and $(D end), causing their month + to increment. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty or if the resulting interval would be invalid. + + Example: + -------------------- + auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)); + auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)); + + interval1.shift(2); + assert(interval1 == Interval!Date(Date(1998, 1, 2), Date(2014, 3, 1))); + + interval2.shift(-2); + assert(interval2 == Interval!Date(Date(1994, 1, 2), Date(2010, 3, 1))); + -------------------- + +/ + void shift(T)(T years, T months = 0, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) + if (isIntegral!T) + { + _enforceNotEmpty(); + + auto begin = _begin; + auto end = _end; + + begin.add!"years"(years, allowOverflow); + begin.add!"months"(months, allowOverflow); + end.add!"years"(years, allowOverflow); + end.add!"months"(months, allowOverflow); + + enforce(_valid(begin, end), new DateTimeException("Argument would result in an invalid Interval.")); + + _begin = begin; + _end = end; + } + } + + + /++ + Expands the interval forwards and/or backwards in time. Effectively, + it does $(D begin -= duration) and/or $(D end += duration). Whether + it expands forwards and/or backwards in time is determined by + $(D_PARAM dir). + + Params: + duration = The duration to expand the interval by. + dir = The direction in time to expand the interval. + + Throws: + $(REF DateTimeException,std,datetime,date) this interval is empty + or if the resulting interval would be invalid. + + Example: + -------------------- + auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)); + auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)); + + interval1.expand(2); + assert(interval1 == Interval!Date(Date(1994, 1, 2), Date(2014, 3, 1))); + + interval2.expand(-2); + assert(interval2 == Interval!Date(Date(1998, 1, 2), Date(2010, 3, 1))); + -------------------- + +/ + void expand(D)(D duration, Direction dir = Direction.both) pure + if (__traits(compiles, begin + duration)) + { + _enforceNotEmpty(); + + switch (dir) + { + case Direction.both: + { + auto begin = _begin - duration; + auto end = _end + duration; + + if (!_valid(begin, end)) + throw new DateTimeException("Argument would result in an invalid Interval."); + + _begin = begin; + _end = end; + + return; + } + case Direction.fwd: + { + auto end = _end + duration; + + if (!_valid(_begin, end)) + throw new DateTimeException("Argument would result in an invalid Interval."); + _end = end; + + return; + } + case Direction.bwd: + { + auto begin = _begin - duration; + + if (!_valid(begin, _end)) + throw new DateTimeException("Argument would result in an invalid Interval."); + _begin = begin; + + return; + } + default: + assert(0, "Invalid Direction."); + } + } + + static if (__traits(compiles, begin.add!"months"(1)) && + __traits(compiles, begin.add!"years"(1))) + { + /++ + Expands the interval forwards and/or backwards in time. Effectively, + it subtracts the given number of months/years from $(D begin) and + adds them to $(D end). Whether it expands forwards and/or backwards + in time is determined by $(D_PARAM dir). + + Params: + years = The number of years to expand the interval by. + months = The number of months to expand the interval by. + allowOverflow = Whether the days should be allowed to overflow + on $(D begin) and $(D end), causing their month + to increment. + dir = The direction in time to expand the interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty or if the resulting interval would be invalid. + + Example: + -------------------- + auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)); + auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)); + + interval1.expand(2); + assert(interval1 == Interval!Date(Date(1994, 1, 2), Date(2014, 3, 1))); + + interval2.expand(-2); + assert(interval2 == Interval!Date(Date(1998, 1, 2), Date(2010, 3, 1))); + -------------------- + +/ + void expand(T)(T years, + T months = 0, + AllowDayOverflow allowOverflow = AllowDayOverflow.yes, + Direction dir = Direction.both) + if (isIntegral!T) + { + _enforceNotEmpty(); + + switch (dir) + { + case Direction.both: + { + auto begin = _begin; + auto end = _end; + + begin.add!"years"(-years, allowOverflow); + begin.add!"months"(-months, allowOverflow); + end.add!"years"(years, allowOverflow); + end.add!"months"(months, allowOverflow); + + enforce(_valid(begin, end), new DateTimeException("Argument would result in an invalid Interval.")); + _begin = begin; + _end = end; + + return; + } + case Direction.fwd: + { + auto end = _end; + + end.add!"years"(years, allowOverflow); + end.add!"months"(months, allowOverflow); + + enforce(_valid(_begin, end), + new DateTimeException("Argument would result in an invalid Interval.")); + _end = end; + + return; + } + case Direction.bwd: + { + auto begin = _begin; + + begin.add!"years"(-years, allowOverflow); + begin.add!"months"(-months, allowOverflow); + + enforce(_valid(begin, _end), + new DateTimeException("Argument would result in an invalid Interval.")); + _begin = begin; + + return; + } + default: + assert(0, "Invalid Direction."); + } + } + } + + + /++ + Returns a range which iterates forward over the interval, starting + at $(D begin), using $(D_PARAM func) to generate each successive time + point. + + The range's $(D front) is the interval's $(D begin). $(D_PARAM func) is + used to generate the next $(D front) when $(D popFront) is called. If + $(D_PARAM popFirst) is $(D PopFirst.yes), then $(D popFront) is called + before the range is returned (so that $(D front) is a time point which + $(D_PARAM func) would generate). + + If $(D_PARAM func) ever generates a time point less than or equal to the + current $(D front) of the range, then a + $(REF DateTimeException,std,datetime,date) will be thrown. The range + will be empty and iteration complete when $(D_PARAM func) generates a + time point equal to or beyond the $(D end) of the interval. + + There are helper functions in this module which generate common + delegates to pass to $(D fwdRange). Their documentation starts with + "Range-generating function," making them easily searchable. + + Params: + func = The function used to generate the time points of the + range over the interval. + popFirst = Whether $(D popFront) should be called on the range + before returning it. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty. + + Warning: + $(D_PARAM func) must be logically pure. Ideally, $(D_PARAM func) + would be a function pointer to a pure function, but forcing + $(D_PARAM func) to be pure is far too restrictive to be useful, and + in order to have the ease of use of having functions which generate + functions to pass to $(D fwdRange), $(D_PARAM func) must be a + delegate. + + If $(D_PARAM func) retains state which changes as it is called, then + some algorithms will not work correctly, because the range's + $(D save) will have failed to have really saved the range's state. + To avoid such bugs, don't pass a delegate which is + not logically pure to $(D fwdRange). If $(D_PARAM func) is given the + same time point with two different calls, it must return the same + result both times. + + Of course, none of the functions in this module have this problem, + so it's only relevant if when creating a custom delegate. + + Example: + -------------------- + auto interval = Interval!Date(Date(2010, 9, 1), Date(2010, 9, 9)); + auto func = delegate (in Date date) // For iterating over even-numbered days. + { + if ((date.day & 1) == 0) + return date + dur!"days"(2); + + return date + dur!"days"(1); + }; + auto range = interval.fwdRange(func); + + // An odd day. Using PopFirst.yes would have made this Date(2010, 9, 2). + assert(range.front == Date(2010, 9, 1)); + + range.popFront(); + assert(range.front == Date(2010, 9, 2)); + + range.popFront(); + assert(range.front == Date(2010, 9, 4)); + + range.popFront(); + assert(range.front == Date(2010, 9, 6)); + + range.popFront(); + assert(range.front == Date(2010, 9, 8)); + + range.popFront(); + assert(range.empty); + -------------------- + +/ + IntervalRange!(TP, Direction.fwd) fwdRange(TP delegate(in TP) func, PopFirst popFirst = PopFirst.no) const + { + _enforceNotEmpty(); + + auto range = IntervalRange!(TP, Direction.fwd)(this, func); + + if (popFirst == PopFirst.yes) + range.popFront(); + + return range; + } + + + /++ + Returns a range which iterates backwards over the interval, starting + at $(D end), using $(D_PARAM func) to generate each successive time + point. + + The range's $(D front) is the interval's $(D end). $(D_PARAM func) is + used to generate the next $(D front) when $(D popFront) is called. If + $(D_PARAM popFirst) is $(D PopFirst.yes), then $(D popFront) is called + before the range is returned (so that $(D front) is a time point which + $(D_PARAM func) would generate). + + If $(D_PARAM func) ever generates a time point greater than or equal to + the current $(D front) of the range, then a + $(REF DateTimeException,std,datetime,date) will be thrown. The range + will be empty and iteration complete when $(D_PARAM func) generates a + time point equal to or less than the $(D begin) of the interval. + + There are helper functions in this module which generate common + delegates to pass to $(D bwdRange). Their documentation starts with + "Range-generating function," making them easily searchable. + + Params: + func = The function used to generate the time points of the + range over the interval. + popFirst = Whether $(D popFront) should be called on the range + before returning it. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty. + + Warning: + $(D_PARAM func) must be logically pure. Ideally, $(D_PARAM func) + would be a function pointer to a pure function, but forcing + $(D_PARAM func) to be pure is far too restrictive to be useful, and + in order to have the ease of use of having functions which generate + functions to pass to $(D fwdRange), $(D_PARAM func) must be a + delegate. + + If $(D_PARAM func) retains state which changes as it is called, then + some algorithms will not work correctly, because the range's + $(D save) will have failed to have really saved the range's state. + To avoid such bugs, don't pass a delegate which is + not logically pure to $(D fwdRange). If $(D_PARAM func) is given the + same time point with two different calls, it must return the same + result both times. + + Of course, none of the functions in this module have this problem, + so it's only relevant for custom delegates. + + Example: + -------------------- + auto interval = Interval!Date(Date(2010, 9, 1), Date(2010, 9, 9)); + auto func = delegate (in Date date) // For iterating over even-numbered days. + { + if ((date.day & 1) == 0) + return date - dur!"days"(2); + + return date - dur!"days"(1); + }; + auto range = interval.bwdRange(func); + + // An odd day. Using PopFirst.yes would have made this Date(2010, 9, 8). + assert(range.front == Date(2010, 9, 9)); + + range.popFront(); + assert(range.front == Date(2010, 9, 8)); + + range.popFront(); + assert(range.front == Date(2010, 9, 6)); + + range.popFront(); + assert(range.front == Date(2010, 9, 4)); + + range.popFront(); + assert(range.front == Date(2010, 9, 2)); + + range.popFront(); + assert(range.empty); + -------------------- + +/ + IntervalRange!(TP, Direction.bwd) bwdRange(TP delegate(in TP) func, PopFirst popFirst = PopFirst.no) const + { + _enforceNotEmpty(); + + auto range = IntervalRange!(TP, Direction.bwd)(this, func); + + if (popFirst == PopFirst.yes) + range.popFront(); + + return range; + } + + + /+ + Converts this interval to a string. + +/ + // Due to bug http://d.puremagic.com/issues/show_bug.cgi?id=3715 , we can't + // have versions of toString() with extra modifiers, so we define one version + // with modifiers and one without. + string toString() + { + return _toStringImpl(); + } + + + /++ + Converts this interval to a string. + +/ + // Due to bug http://d.puremagic.com/issues/show_bug.cgi?id=3715 , we can't + // have versions of toString() with extra modifiers, so we define one version + // with modifiers and one without. + string toString() const nothrow + { + return _toStringImpl(); + } + + +private: + + /+ + Since we have two versions of toString, we have _toStringImpl + so that they can share implementations. + +/ + string _toStringImpl() const nothrow + { + import std.format : format; + try + return format("[%s - %s)", _begin, _end); + catch (Exception e) + assert(0, "format() threw."); + } + + + /+ + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty. + +/ + void _enforceNotEmpty(size_t line = __LINE__) const pure + { + if (empty) + throw new DateTimeException("Invalid operation for an empty Interval.", __FILE__, line); + } + + + /+ + Whether the given values form a valid time interval. + + Params: + begin = The starting point of the interval. + end = The end point of the interval. + +/ + static bool _valid(in TP begin, in TP end) pure nothrow + { + return begin <= end; + } + + + pure invariant() + { + assert(_valid(_begin, _end), "Invariant Failure: begin is not before or equal to end."); + } + + + TP _begin; + TP _end; +} + +// Test Interval's constructors. +@safe unittest +{ + import std.datetime.date; + import std.datetime.systime; + + assertThrown!DateTimeException(Interval!Date(Date(2010, 1, 1), Date(1, 1, 1))); + + Interval!Date(Date.init, Date.init); + Interval!TimeOfDay(TimeOfDay.init, TimeOfDay.init); + Interval!DateTime(DateTime.init, DateTime.init); + Interval!SysTime(SysTime(0), SysTime(0)); + + Interval!DateTime(DateTime.init, dur!"days"(7)); + + // Verify Examples. + Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)); + assert(Interval!Date(Date(1996, 1, 2), dur!"weeks"(3)) == Interval!Date(Date(1996, 1, 2), Date(1996, 1, 23))); + assert(Interval!Date(Date(1996, 1, 2), dur!"days"(3)) == Interval!Date(Date(1996, 1, 2), Date(1996, 1, 5))); + assert(Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), dur!"hours"(3)) == + Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), DateTime(1996, 1, 2, 15, 0, 0))); + assert(Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), dur!"minutes"(3)) == + Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), DateTime(1996, 1, 2, 12, 3, 0))); + assert(Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), dur!"seconds"(3)) == + Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), DateTime(1996, 1, 2, 12, 0, 3))); + assert(Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), dur!"msecs"(3000)) == + Interval!DateTime(DateTime(1996, 1, 2, 12, 0, 0), DateTime(1996, 1, 2, 12, 0, 3))); +} + +// Test Interval's begin. +@safe unittest +{ + import std.datetime.date; + + assert(Interval!Date(Date(1, 1, 1), Date(2010, 1, 1)).begin == Date(1, 1, 1)); + assert(Interval!Date(Date(2010, 1, 1), Date(2010, 1, 1)).begin == Date(2010, 1, 1)); + assert(Interval!Date(Date(1997, 12, 31), Date(1998, 1, 1)).begin == Date(1997, 12, 31)); + + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + assert(cInterval.begin == Date(2010, 7, 4)); + assert(iInterval.begin == Date(2010, 7, 4)); + + // Verify Examples. + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).begin == Date(1996, 1, 2)); +} + +// Test Interval's end. +@safe unittest +{ + import std.datetime.date; + + assert(Interval!Date(Date(1, 1, 1), Date(2010, 1, 1)).end == Date(2010, 1, 1)); + assert(Interval!Date(Date(2010, 1, 1), Date(2010, 1, 1)).end == Date(2010, 1, 1)); + assert(Interval!Date(Date(1997, 12, 31), Date(1998, 1, 1)).end == Date(1998, 1, 1)); + + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + assert(cInterval.end == Date(2012, 1, 7)); + assert(iInterval.end == Date(2012, 1, 7)); + + // Verify Examples. + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).end == Date(2012, 3, 1)); +} + +// Test Interval's length. +@safe unittest +{ + import std.datetime.date; + import std.datetime.systime; + + assert(Interval!Date(Date(2010, 1, 1), Date(2010, 1, 1)).length == dur!"days"(0)); + assert(Interval!Date(Date(2010, 1, 1), Date(2010, 4, 1)).length == dur!"days"(90)); + assert(Interval!TimeOfDay(TimeOfDay(0, 30, 0), TimeOfDay(12, 22, 7)).length == dur!"seconds"(42_727)); + assert(Interval!DateTime(DateTime(2010, 1, 1, 0, 30, 0), DateTime(2010, 1, 2, 12, 22, 7)).length == + dur!"seconds"(129_127)); + assert(Interval!SysTime(SysTime(DateTime(2010, 1, 1, 0, 30, 0)),SysTime(DateTime(2010, 1, 2, 12, 22, 7))).length == + dur!"seconds"(129_127)); + + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + assert(cInterval.length != Duration.zero); + assert(iInterval.length != Duration.zero); + + // Verify Examples. + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).length == dur!"days"(5903)); +} + +// Test Interval's empty. +@safe unittest +{ + import std.datetime.date; + import std.datetime.systime; + + assert(Interval!Date(Date(2010, 1, 1), Date(2010, 1, 1)).empty); + assert(!Interval!Date(Date(2010, 1, 1), Date(2010, 4, 1)).empty); + assert(!Interval!TimeOfDay(TimeOfDay(0, 30, 0), TimeOfDay(12, 22, 7)).empty); + assert(!Interval!DateTime(DateTime(2010, 1, 1, 0, 30, 0), DateTime(2010, 1, 2, 12, 22, 7)).empty); + assert(!Interval!SysTime(SysTime(DateTime(2010, 1, 1, 0, 30, 0)), SysTime(DateTime(2010, 1, 2, 12, 22, 7))).empty); + + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + assert(!cInterval.empty); + assert(!iInterval.empty); + + // Verify Examples. + assert(Interval!Date(Date(1996, 1, 2), Date(1996, 1, 2)).empty); + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).empty); +} + +// Test Interval's contains(time point). +@safe unittest +{ + import std.datetime.date; + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + + assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).contains(Date(2010, 7, 4))); + + assert(!interval.contains(Date(2009, 7, 4))); + assert(!interval.contains(Date(2010, 7, 3))); + assert(interval.contains(Date(2010, 7, 4))); + assert(interval.contains(Date(2010, 7, 5))); + assert(interval.contains(Date(2011, 7, 1))); + assert(interval.contains(Date(2012, 1, 6))); + assert(!interval.contains(Date(2012, 1, 7))); + assert(!interval.contains(Date(2012, 1, 8))); + assert(!interval.contains(Date(2013, 1, 7))); + + const cdate = Date(2010, 7, 6); + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + assert(interval.contains(cdate)); + assert(cInterval.contains(cdate)); + assert(iInterval.contains(cdate)); + + // Verify Examples. + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(Date(1994, 12, 24))); + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(Date(2000, 1, 5))); + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(Date(2012, 3, 1))); +} + +// Test Interval's contains(Interval). +@safe unittest +{ + import std.datetime.date; + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + + assertThrown!DateTimeException(interval.contains(Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).contains(interval)); + assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4),dur!"days"(0)).contains( + Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assert(interval.contains(interval)); + assert(!interval.contains(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)))); + assert(!interval.contains(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)))); + assert(!interval.contains(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)))); + assert(!interval.contains(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)))); + assert(!interval.contains(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)))); + assert(!interval.contains(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)))); + assert(interval.contains(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)))); + assert(interval.contains(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)))); + assert(interval.contains(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)))); + assert(!interval.contains(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)))); + assert(!interval.contains(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)))); + assert(!interval.contains(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)))); + + assert(!Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)).contains(interval)); + assert(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).contains(interval)); + assert(!Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).contains(interval)); + assert(!Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).contains(interval)); + assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).contains(interval)); + assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).contains(interval)); + assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).contains(interval)); + assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).contains(interval)); + assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).contains(interval)); + assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).contains(interval)); + assert(!Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).contains(interval)); + assert(!Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)).contains(interval)); + + assert(!interval.contains(PosInfInterval!Date(Date(2010, 7, 3)))); + assert(!interval.contains(PosInfInterval!Date(Date(2010, 7, 4)))); + assert(!interval.contains(PosInfInterval!Date(Date(2010, 7, 5)))); + assert(!interval.contains(PosInfInterval!Date(Date(2012, 1, 6)))); + assert(!interval.contains(PosInfInterval!Date(Date(2012, 1, 7)))); + assert(!interval.contains(PosInfInterval!Date(Date(2012, 1, 8)))); + + assert(!interval.contains(NegInfInterval!Date(Date(2010, 7, 3)))); + assert(!interval.contains(NegInfInterval!Date(Date(2010, 7, 4)))); + assert(!interval.contains(NegInfInterval!Date(Date(2010, 7, 5)))); + assert(!interval.contains(NegInfInterval!Date(Date(2012, 1, 6)))); + assert(!interval.contains(NegInfInterval!Date(Date(2012, 1, 7)))); + assert(!interval.contains(NegInfInterval!Date(Date(2012, 1, 8)))); + + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(interval.contains(interval)); + assert(interval.contains(cInterval)); + assert(interval.contains(iInterval)); + assert(!interval.contains(posInfInterval)); + assert(!interval.contains(cPosInfInterval)); + assert(!interval.contains(iPosInfInterval)); + assert(!interval.contains(negInfInterval)); + assert(!interval.contains(cNegInfInterval)); + assert(!interval.contains(iNegInfInterval)); + assert(cInterval.contains(interval)); + assert(cInterval.contains(cInterval)); + assert(cInterval.contains(iInterval)); + assert(!cInterval.contains(posInfInterval)); + assert(!cInterval.contains(cPosInfInterval)); + assert(!cInterval.contains(iPosInfInterval)); + assert(!cInterval.contains(negInfInterval)); + assert(!cInterval.contains(cNegInfInterval)); + assert(!cInterval.contains(iNegInfInterval)); + assert(iInterval.contains(interval)); + assert(iInterval.contains(cInterval)); + assert(iInterval.contains(iInterval)); + assert(!iInterval.contains(posInfInterval)); + assert(!iInterval.contains(cPosInfInterval)); + assert(!iInterval.contains(iPosInfInterval)); + assert(!iInterval.contains(negInfInterval)); + assert(!iInterval.contains(cNegInfInterval)); + assert(!iInterval.contains(iNegInfInterval)); + + // Verify Examples. + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains( + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains( + Interval!Date(Date(1998, 2, 28), Date(2013, 5, 1)))); + + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(PosInfInterval!Date(Date(1999, 5, 4)))); + + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).contains(NegInfInterval!Date(Date(1996, 5, 4)))); +} + +// Test Interval's isBefore(time point). +@safe unittest +{ + import std.datetime.date; + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + + assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).isBefore(Date(2010, 7, 4))); + + assert(!interval.isBefore(Date(2009, 7, 3))); + assert(!interval.isBefore(Date(2010, 7, 3))); + assert(!interval.isBefore(Date(2010, 7, 4))); + assert(!interval.isBefore(Date(2010, 7, 5))); + assert(!interval.isBefore(Date(2011, 7, 1))); + assert(!interval.isBefore(Date(2012, 1, 6))); + assert(interval.isBefore(Date(2012, 1, 7))); + assert(interval.isBefore(Date(2012, 1, 8))); + assert(interval.isBefore(Date(2013, 1, 7))); + + const cdate = Date(2010, 7, 6); + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + assert(!interval.isBefore(cdate)); + assert(!cInterval.isBefore(cdate)); + assert(!iInterval.isBefore(cdate)); + + // Verify Examples. + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(Date(1994, 12, 24))); + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(Date(2000, 1, 5))); + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(Date(2012, 3, 1))); +} + +// Test Interval's isBefore(Interval). +@safe unittest +{ + import std.datetime.date; + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + + assertThrown!DateTimeException(interval.isBefore(Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).isBefore(interval)); + assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).isBefore( + Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assert(!interval.isBefore(interval)); + assert(!interval.isBefore(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)))); + assert(!interval.isBefore(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)))); + assert(!interval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)))); + assert(!interval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)))); + assert(!interval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)))); + assert(!interval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)))); + assert(!interval.isBefore(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)))); + assert(!interval.isBefore(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)))); + assert(!interval.isBefore(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)))); + assert(!interval.isBefore(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)))); + assert(interval.isBefore(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)))); + assert(interval.isBefore(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)))); + + assert(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)).isBefore(interval)); + assert(!Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).isBefore(interval)); + assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).isBefore(interval)); + assert(!Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).isBefore(interval)); + assert(!Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).isBefore(interval)); + assert(!Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).isBefore(interval)); + assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).isBefore(interval)); + assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).isBefore(interval)); + assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).isBefore(interval)); + assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).isBefore(interval)); + assert(!Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).isBefore(interval)); + assert(!Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)).isBefore(interval)); + + assert(!interval.isBefore(PosInfInterval!Date(Date(2010, 7, 3)))); + assert(!interval.isBefore(PosInfInterval!Date(Date(2010, 7, 4)))); + assert(!interval.isBefore(PosInfInterval!Date(Date(2010, 7, 5)))); + assert(!interval.isBefore(PosInfInterval!Date(Date(2012, 1, 6)))); + assert(interval.isBefore(PosInfInterval!Date(Date(2012, 1, 7)))); + assert(interval.isBefore(PosInfInterval!Date(Date(2012, 1, 8)))); + + assert(!interval.isBefore(NegInfInterval!Date(Date(2010, 7, 3)))); + assert(!interval.isBefore(NegInfInterval!Date(Date(2010, 7, 4)))); + assert(!interval.isBefore(NegInfInterval!Date(Date(2010, 7, 5)))); + assert(!interval.isBefore(NegInfInterval!Date(Date(2012, 1, 6)))); + assert(!interval.isBefore(NegInfInterval!Date(Date(2012, 1, 7)))); + assert(!interval.isBefore(NegInfInterval!Date(Date(2012, 1, 8)))); + + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!interval.isBefore(interval)); + assert(!interval.isBefore(cInterval)); + assert(!interval.isBefore(iInterval)); + assert(!interval.isBefore(posInfInterval)); + assert(!interval.isBefore(cPosInfInterval)); + assert(!interval.isBefore(iPosInfInterval)); + assert(!interval.isBefore(negInfInterval)); + assert(!interval.isBefore(cNegInfInterval)); + assert(!interval.isBefore(iNegInfInterval)); + assert(!cInterval.isBefore(interval)); + assert(!cInterval.isBefore(cInterval)); + assert(!cInterval.isBefore(iInterval)); + assert(!cInterval.isBefore(posInfInterval)); + assert(!cInterval.isBefore(cPosInfInterval)); + assert(!cInterval.isBefore(iPosInfInterval)); + assert(!cInterval.isBefore(negInfInterval)); + assert(!cInterval.isBefore(cNegInfInterval)); + assert(!cInterval.isBefore(iNegInfInterval)); + assert(!iInterval.isBefore(interval)); + assert(!iInterval.isBefore(cInterval)); + assert(!iInterval.isBefore(iInterval)); + assert(!iInterval.isBefore(posInfInterval)); + assert(!iInterval.isBefore(cPosInfInterval)); + assert(!iInterval.isBefore(iPosInfInterval)); + assert(!iInterval.isBefore(negInfInterval)); + assert(!iInterval.isBefore(cNegInfInterval)); + assert(!iInterval.isBefore(iNegInfInterval)); + + // Verify Examples. + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore( + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore( + Interval!Date(Date(2012, 3, 1), Date(2013, 5, 1)))); + + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(PosInfInterval!Date(Date(1999, 5, 4)))); + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(PosInfInterval!Date(Date(2013, 3, 7)))); + + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isBefore(NegInfInterval!Date(Date(1996, 5, 4)))); +} + +// Test Interval's isAfter(time point). +@safe unittest +{ + import std.datetime.date; + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + + assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).isAfter(Date(2010, 7, 4))); + + assert(interval.isAfter(Date(2009, 7, 4))); + assert(interval.isAfter(Date(2010, 7, 3))); + assert(!interval.isAfter(Date(2010, 7, 4))); + assert(!interval.isAfter(Date(2010, 7, 5))); + assert(!interval.isAfter(Date(2011, 7, 1))); + assert(!interval.isAfter(Date(2012, 1, 6))); + assert(!interval.isAfter(Date(2012, 1, 7))); + assert(!interval.isAfter(Date(2012, 1, 8))); + assert(!interval.isAfter(Date(2013, 1, 7))); + + const cdate = Date(2010, 7, 6); + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + assert(!interval.isAfter(cdate)); + assert(!cInterval.isAfter(cdate)); + assert(!iInterval.isAfter(cdate)); + + // Verify Examples. + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(Date(1994, 12, 24))); + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(Date(2000, 1, 5))); + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(Date(2012, 3, 1))); +} + +// Test Interval's isAfter(Interval). +@safe unittest +{ + import std.datetime.date; + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + + assertThrown!DateTimeException(interval.isAfter(Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).isAfter(interval)); + assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).isAfter( + Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assert(!interval.isAfter(interval)); + assert(interval.isAfter(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)))); + assert(!interval.isAfter(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)))); + assert(interval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)))); + assert(!interval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)))); + assert(!interval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)))); + assert(!interval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)))); + assert(!interval.isAfter(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)))); + assert(!interval.isAfter(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)))); + assert(!interval.isAfter(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)))); + assert(!interval.isAfter(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)))); + assert(!interval.isAfter(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)))); + assert(!interval.isAfter(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)))); + + assert(!Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)).isAfter(interval)); + assert(!Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).isAfter(interval)); + assert(!Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).isAfter(interval)); + assert(!Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).isAfter(interval)); + assert(!Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).isAfter(interval)); + assert(!Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).isAfter(interval)); + assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).isAfter(interval)); + assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).isAfter(interval)); + assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).isAfter(interval)); + assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).isAfter(interval)); + assert(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).isAfter(interval)); + assert(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)).isAfter(interval)); + + assert(!interval.isAfter(PosInfInterval!Date(Date(2010, 7, 3)))); + assert(!interval.isAfter(PosInfInterval!Date(Date(2010, 7, 4)))); + assert(!interval.isAfter(PosInfInterval!Date(Date(2010, 7, 5)))); + assert(!interval.isAfter(PosInfInterval!Date(Date(2012, 1, 6)))); + assert(!interval.isAfter(PosInfInterval!Date(Date(2012, 1, 7)))); + assert(!interval.isAfter(PosInfInterval!Date(Date(2012, 1, 8)))); + + assert(interval.isAfter(NegInfInterval!Date(Date(2010, 7, 3)))); + assert(interval.isAfter(NegInfInterval!Date(Date(2010, 7, 4)))); + assert(!interval.isAfter(NegInfInterval!Date(Date(2010, 7, 5)))); + assert(!interval.isAfter(NegInfInterval!Date(Date(2012, 1, 6)))); + assert(!interval.isAfter(NegInfInterval!Date(Date(2012, 1, 7)))); + assert(!interval.isAfter(NegInfInterval!Date(Date(2012, 1, 8)))); + + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!interval.isAfter(interval)); + assert(!interval.isAfter(cInterval)); + assert(!interval.isAfter(iInterval)); + assert(!interval.isAfter(posInfInterval)); + assert(!interval.isAfter(cPosInfInterval)); + assert(!interval.isAfter(iPosInfInterval)); + assert(!interval.isAfter(negInfInterval)); + assert(!interval.isAfter(cNegInfInterval)); + assert(!interval.isAfter(iNegInfInterval)); + assert(!cInterval.isAfter(interval)); + assert(!cInterval.isAfter(cInterval)); + assert(!cInterval.isAfter(iInterval)); + assert(!cInterval.isAfter(posInfInterval)); + assert(!cInterval.isAfter(cPosInfInterval)); + assert(!cInterval.isAfter(iPosInfInterval)); + assert(!cInterval.isAfter(negInfInterval)); + assert(!cInterval.isAfter(cNegInfInterval)); + assert(!cInterval.isAfter(iNegInfInterval)); + assert(!iInterval.isAfter(interval)); + assert(!iInterval.isAfter(cInterval)); + assert(!iInterval.isAfter(iInterval)); + assert(!iInterval.isAfter(posInfInterval)); + assert(!iInterval.isAfter(cPosInfInterval)); + assert(!iInterval.isAfter(iPosInfInterval)); + assert(!iInterval.isAfter(negInfInterval)); + assert(!iInterval.isAfter(cNegInfInterval)); + assert(!iInterval.isAfter(iNegInfInterval)); + + // Verify Examples. + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter( + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter( + Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2)))); + + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(PosInfInterval!Date(Date(1999, 5, 4)))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAfter(NegInfInterval!Date(Date(1996, 1, 2)))); +} + +// Test Interval's intersects(). +@safe unittest +{ + import std.datetime.date; + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + + assertThrown!DateTimeException(interval.intersects(Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).intersects(interval)); + assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).intersects( + Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assert(interval.intersects(interval)); + assert(!interval.intersects(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)))); + assert(interval.intersects(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)))); + assert(!interval.intersects(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)))); + assert(interval.intersects(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)))); + assert(interval.intersects(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)))); + assert(interval.intersects(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)))); + assert(interval.intersects(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)))); + assert(interval.intersects(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)))); + assert(interval.intersects(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)))); + assert(interval.intersects(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)))); + assert(!interval.intersects(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)))); + assert(!interval.intersects(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)))); + + assert(!Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)).intersects(interval)); + assert(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).intersects(interval)); + assert(!Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).intersects(interval)); + assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).intersects(interval)); + assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).intersects(interval)); + assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).intersects(interval)); + assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).intersects(interval)); + assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).intersects(interval)); + assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).intersects(interval)); + assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).intersects(interval)); + assert(!Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).intersects(interval)); + assert(!Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)).intersects(interval)); + + assert(interval.intersects(PosInfInterval!Date(Date(2010, 7, 3)))); + assert(interval.intersects(PosInfInterval!Date(Date(2010, 7, 4)))); + assert(interval.intersects(PosInfInterval!Date(Date(2010, 7, 5)))); + assert(interval.intersects(PosInfInterval!Date(Date(2012, 1, 6)))); + assert(!interval.intersects(PosInfInterval!Date(Date(2012, 1, 7)))); + assert(!interval.intersects(PosInfInterval!Date(Date(2012, 1, 8)))); + + assert(!interval.intersects(NegInfInterval!Date(Date(2010, 7, 3)))); + assert(!interval.intersects(NegInfInterval!Date(Date(2010, 7, 4)))); + assert(interval.intersects(NegInfInterval!Date(Date(2010, 7, 5)))); + assert(interval.intersects(NegInfInterval!Date(Date(2012, 1, 6)))); + assert(interval.intersects(NegInfInterval!Date(Date(2012, 1, 7)))); + assert(interval.intersects(NegInfInterval!Date(Date(2012, 1, 8)))); + + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(interval.intersects(interval)); + assert(interval.intersects(cInterval)); + assert(interval.intersects(iInterval)); + assert(interval.intersects(posInfInterval)); + assert(interval.intersects(cPosInfInterval)); + assert(interval.intersects(iPosInfInterval)); + assert(interval.intersects(negInfInterval)); + assert(interval.intersects(cNegInfInterval)); + assert(interval.intersects(iNegInfInterval)); + assert(cInterval.intersects(interval)); + assert(cInterval.intersects(cInterval)); + assert(cInterval.intersects(iInterval)); + assert(cInterval.intersects(posInfInterval)); + assert(cInterval.intersects(cPosInfInterval)); + assert(cInterval.intersects(iPosInfInterval)); + assert(cInterval.intersects(negInfInterval)); + assert(cInterval.intersects(cNegInfInterval)); + assert(cInterval.intersects(iNegInfInterval)); + assert(iInterval.intersects(interval)); + assert(iInterval.intersects(cInterval)); + assert(iInterval.intersects(iInterval)); + assert(iInterval.intersects(posInfInterval)); + assert(iInterval.intersects(cPosInfInterval)); + assert(iInterval.intersects(iPosInfInterval)); + assert(iInterval.intersects(negInfInterval)); + assert(iInterval.intersects(cNegInfInterval)); + assert(iInterval.intersects(iNegInfInterval)); + + // Verify Examples. + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects( + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects( + Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2)))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(PosInfInterval!Date(Date(1999, 5, 4)))); + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(PosInfInterval!Date(Date(2012, 3, 1)))); + + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(NegInfInterval!Date(Date(1996, 1, 2)))); + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersects(NegInfInterval!Date(Date(2000, 1, 2)))); +} + +// Test Interval's intersection(). +@safe unittest +{ + import std.datetime.date; + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + + assertThrown!DateTimeException(interval.intersection(Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).intersection(interval)); + assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 4), dur!"days"(0)).intersection( + Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assertThrown!DateTimeException(interval.intersection(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)))); + assertThrown!DateTimeException(interval.intersection(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)))); + assertThrown!DateTimeException(interval.intersection(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)))); + assertThrown!DateTimeException(interval.intersection(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)))); + + assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)).intersection(interval)); + assertThrown!DateTimeException(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).intersection(interval)); + assertThrown!DateTimeException(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).intersection(interval)); + assertThrown!DateTimeException(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)).intersection(interval)); + + assertThrown!DateTimeException(interval.intersection(PosInfInterval!Date(Date(2012, 1, 7)))); + assertThrown!DateTimeException(interval.intersection(PosInfInterval!Date(Date(2012, 1, 8)))); + + assertThrown!DateTimeException(interval.intersection(NegInfInterval!Date(Date(2010, 7, 3)))); + assertThrown!DateTimeException(interval.intersection(NegInfInterval!Date(Date(2010, 7, 4)))); + + assert(interval.intersection(interval) == interval); + assert(interval.intersection(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(interval.intersection(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) == + Interval!Date(Date(2010, 7, 4), Date(2010, 7, 5))); + assert(interval.intersection(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(interval.intersection(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(interval.intersection(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) == + Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))); + assert(interval.intersection(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) == + Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))); + assert(interval.intersection(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) == + Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))); + assert(interval.intersection(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) == + Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))); + + assert(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).intersection(interval) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).intersection(interval) == + Interval!Date(Date(2010, 7, 4), Date(2010, 7, 5))); + assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).intersection(interval) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).intersection(interval) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).intersection(interval) == + Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))); + assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).intersection(interval) == + Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))); + assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).intersection(interval) == + Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))); + assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).intersection(interval) == + Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))); + + assert(interval.intersection(PosInfInterval!Date(Date(2010, 7, 3))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(interval.intersection(PosInfInterval!Date(Date(2010, 7, 4))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(interval.intersection(PosInfInterval!Date(Date(2010, 7, 5))) == + Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))); + assert(interval.intersection(PosInfInterval!Date(Date(2012, 1, 6))) == + Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))); + + assert(interval.intersection(NegInfInterval!Date(Date(2010, 7, 5))) == + Interval!Date(Date(2010, 7, 4), Date(2010, 7, 5))); + assert(interval.intersection(NegInfInterval!Date(Date(2012, 1, 6))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 6))); + assert(interval.intersection(NegInfInterval!Date(Date(2012, 1, 7))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(interval.intersection(NegInfInterval!Date(Date(2012, 1, 8))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!interval.intersection(interval).empty); + assert(!interval.intersection(cInterval).empty); + assert(!interval.intersection(iInterval).empty); + assert(!interval.intersection(posInfInterval).empty); + assert(!interval.intersection(cPosInfInterval).empty); + assert(!interval.intersection(iPosInfInterval).empty); + assert(!interval.intersection(negInfInterval).empty); + assert(!interval.intersection(cNegInfInterval).empty); + assert(!interval.intersection(iNegInfInterval).empty); + assert(!cInterval.intersection(interval).empty); + assert(!cInterval.intersection(cInterval).empty); + assert(!cInterval.intersection(iInterval).empty); + assert(!cInterval.intersection(posInfInterval).empty); + assert(!cInterval.intersection(cPosInfInterval).empty); + assert(!cInterval.intersection(iPosInfInterval).empty); + assert(!cInterval.intersection(negInfInterval).empty); + assert(!cInterval.intersection(cNegInfInterval).empty); + assert(!cInterval.intersection(iNegInfInterval).empty); + assert(!iInterval.intersection(interval).empty); + assert(!iInterval.intersection(cInterval).empty); + assert(!iInterval.intersection(iInterval).empty); + assert(!iInterval.intersection(posInfInterval).empty); + assert(!iInterval.intersection(cPosInfInterval).empty); + assert(!iInterval.intersection(iPosInfInterval).empty); + assert(!iInterval.intersection(negInfInterval).empty); + assert(!iInterval.intersection(cNegInfInterval).empty); + assert(!iInterval.intersection(iNegInfInterval).empty); + + // Verify Examples. + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == + Interval!Date(Date(1996, 1 , 2), Date(2000, 8, 2))); + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection( + Interval!Date(Date(1999, 1, 12),Date(2011, 9, 17))) == + Interval!Date(Date(1999, 1 , 12), Date(2011, 9, 17))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection( + PosInfInterval!Date(Date(1990, 7, 6))) == + Interval!Date(Date(1996, 1 , 2), Date(2012, 3, 1))); + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection( + PosInfInterval!Date(Date(1999, 1, 12))) == + Interval!Date(Date(1999, 1 , 12), Date(2012, 3, 1))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection( + NegInfInterval!Date(Date(1999, 7, 6))) == + Interval!Date(Date(1996, 1 , 2), Date(1999, 7, 6))); + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).intersection( + NegInfInterval!Date(Date(2013, 1, 12))) == + Interval!Date(Date(1996, 1 , 2), Date(2012, 3, 1))); +} + +// Test Interval's isAdjacent(). +@safe unittest +{ + import std.datetime.date; + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + + static void testInterval(in Interval!Date interval1, in Interval!Date interval2) + { + interval1.isAdjacent(interval2); + } + + assertThrown!DateTimeException(testInterval(interval, Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + assertThrown!DateTimeException(testInterval(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), interval)); + assertThrown!DateTimeException(testInterval(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), + Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assert(!interval.isAdjacent(interval)); + assert(!interval.isAdjacent(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)))); + assert(!interval.isAdjacent(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)))); + assert(interval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)))); + assert(!interval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)))); + assert(!interval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)))); + assert(!interval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)))); + assert(!interval.isAdjacent(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)))); + assert(!interval.isAdjacent(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)))); + assert(!interval.isAdjacent(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)))); + assert(!interval.isAdjacent(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)))); + assert(interval.isAdjacent(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)))); + assert(!interval.isAdjacent(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)))); + + assert(!Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)).isAdjacent(interval)); + assert(!Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).isAdjacent(interval)); + assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).isAdjacent(interval)); + assert(!Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).isAdjacent(interval)); + assert(!Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).isAdjacent(interval)); + assert(!Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).isAdjacent(interval)); + assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).isAdjacent(interval)); + assert(!Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).isAdjacent(interval)); + assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).isAdjacent(interval)); + assert(!Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).isAdjacent(interval)); + assert(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).isAdjacent(interval)); + assert(!Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)).isAdjacent(interval)); + + assert(!interval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 3)))); + assert(!interval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 4)))); + assert(!interval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 5)))); + assert(!interval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 6)))); + assert(interval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 7)))); + assert(!interval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 8)))); + + assert(!interval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 3)))); + assert(interval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 4)))); + assert(!interval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 5)))); + assert(!interval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 6)))); + assert(!interval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 7)))); + assert(!interval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 8)))); + + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!interval.isAdjacent(interval)); + assert(!interval.isAdjacent(cInterval)); + assert(!interval.isAdjacent(iInterval)); + assert(!interval.isAdjacent(posInfInterval)); + assert(!interval.isAdjacent(cPosInfInterval)); + assert(!interval.isAdjacent(iPosInfInterval)); + assert(!interval.isAdjacent(negInfInterval)); + assert(!interval.isAdjacent(cNegInfInterval)); + assert(!interval.isAdjacent(iNegInfInterval)); + assert(!cInterval.isAdjacent(interval)); + assert(!cInterval.isAdjacent(cInterval)); + assert(!cInterval.isAdjacent(iInterval)); + assert(!cInterval.isAdjacent(posInfInterval)); + assert(!cInterval.isAdjacent(cPosInfInterval)); + assert(!cInterval.isAdjacent(iPosInfInterval)); + assert(!cInterval.isAdjacent(negInfInterval)); + assert(!cInterval.isAdjacent(cNegInfInterval)); + assert(!cInterval.isAdjacent(iNegInfInterval)); + assert(!iInterval.isAdjacent(interval)); + assert(!iInterval.isAdjacent(cInterval)); + assert(!iInterval.isAdjacent(iInterval)); + assert(!iInterval.isAdjacent(posInfInterval)); + assert(!iInterval.isAdjacent(cPosInfInterval)); + assert(!iInterval.isAdjacent(iPosInfInterval)); + assert(!iInterval.isAdjacent(negInfInterval)); + assert(!iInterval.isAdjacent(cNegInfInterval)); + assert(!iInterval.isAdjacent(iNegInfInterval)); + + // Verify Examples. + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent( + Interval!Date(Date(1990, 7, 6), Date(1996, 1, 2)))); + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent( + Interval!Date(Date(2012, 3, 1), Date(2013, 9, 17)))); + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent( + Interval!Date(Date(1989, 3, 1), Date(2012, 3, 1)))); + + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(PosInfInterval!Date(Date(1999, 5, 4)))); + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(PosInfInterval!Date(Date(2012, 3, 1)))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).isAdjacent(NegInfInterval!Date(Date(1996, 1, 2)))); + assert(!Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)) .isAdjacent(NegInfInterval!Date(Date(2000, 1, 2)))); +} + +// Test Interval's merge(). +@safe unittest +{ + import std.datetime.date; + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + + static void testInterval(I)(in Interval!Date interval1, in I interval2) + { + interval1.merge(interval2); + } + + assertThrown!DateTimeException(testInterval(interval, Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + assertThrown!DateTimeException(testInterval(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), interval)); + assertThrown!DateTimeException(testInterval(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), + Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assertThrown!DateTimeException(testInterval(interval, Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)))); + assertThrown!DateTimeException(testInterval(interval, Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)))); + + assertThrown!DateTimeException(testInterval(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)), interval)); + assertThrown!DateTimeException(testInterval(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)), interval)); + + assertThrown!DateTimeException(testInterval(interval, PosInfInterval!Date(Date(2012, 1, 8)))); + + assertThrown!DateTimeException(testInterval(interval, NegInfInterval!Date(Date(2010, 7, 3)))); + + assert(interval.merge(interval) == interval); + assert(interval.merge(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) == + Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))); + assert(interval.merge(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))) == + Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))); + assert(interval.merge(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) == + Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))); + assert(interval.merge(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) == + Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))); + assert(interval.merge(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) == + Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))); + assert(interval.merge(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(interval.merge(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(interval.merge(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(interval.merge(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8))); + assert(interval.merge(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8))); + + assert(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).merge(interval) == + Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))); + assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).merge(interval) == + Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))); + assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).merge(interval) == + Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))); + assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).merge(interval) == + Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))); + assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).merge(interval) == + Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))); + assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).merge(interval) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).merge(interval) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).merge(interval) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).merge(interval) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8))); + assert(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).merge(interval) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8))); + + assert(interval.merge(PosInfInterval!Date(Date(2010, 7, 3))) == + PosInfInterval!Date(Date(2010, 7, 3))); + assert(interval.merge(PosInfInterval!Date(Date(2010, 7, 4))) == + PosInfInterval!Date(Date(2010, 7, 4))); + assert(interval.merge(PosInfInterval!Date(Date(2010, 7, 5))) == + PosInfInterval!Date(Date(2010, 7, 4))); + assert(interval.merge(PosInfInterval!Date(Date(2012, 1, 6))) == + PosInfInterval!Date(Date(2010, 7, 4))); + assert(interval.merge(PosInfInterval!Date(Date(2012, 1, 7))) == + PosInfInterval!Date(Date(2010, 7, 4))); + + assert(interval.merge(NegInfInterval!Date(Date(2010, 7, 4))) == + NegInfInterval!Date(Date(2012, 1, 7))); + assert(interval.merge(NegInfInterval!Date(Date(2010, 7, 5))) == + NegInfInterval!Date(Date(2012, 1, 7))); + assert(interval.merge(NegInfInterval!Date(Date(2012, 1, 6))) == + NegInfInterval!Date(Date(2012, 1, 7))); + assert(interval.merge(NegInfInterval!Date(Date(2012, 1, 7))) == + NegInfInterval!Date(Date(2012, 1, 7))); + assert(interval.merge(NegInfInterval!Date(Date(2012, 1, 8))) == + NegInfInterval!Date(Date(2012, 1, 8))); + + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!interval.merge(interval).empty); + assert(!interval.merge(cInterval).empty); + assert(!interval.merge(iInterval).empty); + assert(!interval.merge(posInfInterval).empty); + assert(!interval.merge(cPosInfInterval).empty); + assert(!interval.merge(iPosInfInterval).empty); + assert(!interval.merge(negInfInterval).empty); + assert(!interval.merge(cNegInfInterval).empty); + assert(!interval.merge(iNegInfInterval).empty); + assert(!cInterval.merge(interval).empty); + assert(!cInterval.merge(cInterval).empty); + assert(!cInterval.merge(iInterval).empty); + assert(!cInterval.merge(posInfInterval).empty); + assert(!cInterval.merge(cPosInfInterval).empty); + assert(!cInterval.merge(iPosInfInterval).empty); + assert(!cInterval.merge(negInfInterval).empty); + assert(!cInterval.merge(cNegInfInterval).empty); + assert(!cInterval.merge(iNegInfInterval).empty); + assert(!iInterval.merge(interval).empty); + assert(!iInterval.merge(cInterval).empty); + assert(!iInterval.merge(iInterval).empty); + assert(!iInterval.merge(posInfInterval).empty); + assert(!iInterval.merge(cPosInfInterval).empty); + assert(!iInterval.merge(iPosInfInterval).empty); + assert(!iInterval.merge(negInfInterval).empty); + assert(!iInterval.merge(cNegInfInterval).empty); + assert(!iInterval.merge(iNegInfInterval).empty); + + // Verify Examples. + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == + Interval!Date(Date(1990, 7 , 6), Date(2012, 3, 1))); + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(Interval!Date(Date(2012, 3, 1), Date(2013, 5, 7))) == + Interval!Date(Date(1996, 1 , 2), Date(2013, 5, 7))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(PosInfInterval!Date(Date(1990, 7, 6))) == + PosInfInterval!Date(Date(1990, 7 , 6))); + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(PosInfInterval!Date(Date(2012, 3, 1))) == + PosInfInterval!Date(Date(1996, 1 , 2))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(NegInfInterval!Date(Date(1996, 1, 2))) == + NegInfInterval!Date(Date(2012, 3 , 1))); + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).merge(NegInfInterval!Date(Date(2013, 1, 12))) == + NegInfInterval!Date(Date(2013, 1 , 12))); +} + +// Test Interval's span(). +@safe unittest +{ + import std.datetime.date; + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + + static void testInterval(in Interval!Date interval1, in Interval!Date interval2) + { + interval1.span(interval2); + } + + assertThrown!DateTimeException(testInterval(interval, Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + assertThrown!DateTimeException(testInterval(Interval!Date(Date(2010, 7, 4), dur!"days"(0)),interval)); + assertThrown!DateTimeException(testInterval(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), + Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assert(interval.span(interval) == interval); + assert(interval.span(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))) == + Interval!Date(Date(2010, 7, 1), Date(2012, 1, 7))); + assert(interval.span(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) == + Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))); + assert(interval.span(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))) == + Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))); + assert(interval.span(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) == + Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))); + assert(interval.span(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) == + Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))); + assert(interval.span(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) == + Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))); + assert(interval.span(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(interval.span(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(interval.span(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(interval.span(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8))); + assert(interval.span(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8))); + assert(interval.span(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 9))); + + assert(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)).span(interval) == + Interval!Date(Date(2010, 7, 1), Date(2012, 1, 7))); + assert(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)).span(interval) == + Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))); + assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)).span(interval) == + Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))); + assert(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)).span(interval) == + Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))); + assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)).span(interval) == + Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))); + assert(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)).span(interval) == + Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))); + assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)).span(interval) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)).span(interval) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)).span(interval) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)).span(interval) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8))); + assert(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)).span(interval) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8))); + assert(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)).span(interval) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 9))); + + assert(interval.span(PosInfInterval!Date(Date(2010, 7, 3))) == PosInfInterval!Date(Date(2010, 7, 3))); + assert(interval.span(PosInfInterval!Date(Date(2010, 7, 4))) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(interval.span(PosInfInterval!Date(Date(2010, 7, 5))) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(interval.span(PosInfInterval!Date(Date(2012, 1, 6))) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(interval.span(PosInfInterval!Date(Date(2012, 1, 7))) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(interval.span(PosInfInterval!Date(Date(2012, 1, 8))) == PosInfInterval!Date(Date(2010, 7, 4))); + + assert(interval.span(NegInfInterval!Date(Date(2010, 7, 3))) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(interval.span(NegInfInterval!Date(Date(2010, 7, 4))) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(interval.span(NegInfInterval!Date(Date(2010, 7, 5))) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(interval.span(NegInfInterval!Date(Date(2012, 1, 6))) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(interval.span(NegInfInterval!Date(Date(2012, 1, 7))) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(interval.span(NegInfInterval!Date(Date(2012, 1, 8))) == NegInfInterval!Date(Date(2012, 1, 8))); + + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!interval.span(interval).empty); + assert(!interval.span(cInterval).empty); + assert(!interval.span(iInterval).empty); + assert(!interval.span(posInfInterval).empty); + assert(!interval.span(cPosInfInterval).empty); + assert(!interval.span(iPosInfInterval).empty); + assert(!interval.span(negInfInterval).empty); + assert(!interval.span(cNegInfInterval).empty); + assert(!interval.span(iNegInfInterval).empty); + assert(!cInterval.span(interval).empty); + assert(!cInterval.span(cInterval).empty); + assert(!cInterval.span(iInterval).empty); + assert(!cInterval.span(posInfInterval).empty); + assert(!cInterval.span(cPosInfInterval).empty); + assert(!cInterval.span(iPosInfInterval).empty); + assert(!cInterval.span(negInfInterval).empty); + assert(!cInterval.span(cNegInfInterval).empty); + assert(!cInterval.span(iNegInfInterval).empty); + assert(!iInterval.span(interval).empty); + assert(!iInterval.span(cInterval).empty); + assert(!iInterval.span(iInterval).empty); + assert(!iInterval.span(posInfInterval).empty); + assert(!iInterval.span(cPosInfInterval).empty); + assert(!iInterval.span(iPosInfInterval).empty); + assert(!iInterval.span(negInfInterval).empty); + assert(!iInterval.span(cNegInfInterval).empty); + assert(!iInterval.span(iNegInfInterval).empty); + + // Verify Examples. + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(Interval!Date(Date(1990, 7, 6), Date(1991, 1, 8))) == + Interval!Date(Date(1990, 7 , 6), Date(2012, 3, 1))); + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(Interval!Date(Date(2012, 3, 1), Date(2013, 5, 7))) == + Interval!Date(Date(1996, 1 , 2), Date(2013, 5, 7))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(PosInfInterval!Date(Date(1990, 7, 6))) == + PosInfInterval!Date(Date(1990, 7 , 6))); + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(PosInfInterval!Date(Date(2050, 1, 1))) == + PosInfInterval!Date(Date(1996, 1 , 2))); + + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(NegInfInterval!Date(Date(1602, 5, 21))) == + NegInfInterval!Date(Date(2012, 3 , 1))); + assert(Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)).span(NegInfInterval!Date(Date(2013, 1, 12))) == + NegInfInterval!Date(Date(2013, 1 , 12))); +} + +// Test Interval's shift(duration). +@safe unittest +{ + import std.datetime.date; + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + + static void testIntervalFail(Interval!Date interval, in Duration duration) + { + interval.shift(duration); + } + + assertThrown!DateTimeException(testIntervalFail(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), dur!"days"(1))); + + static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__) + { + interval.shift(duration); + assert(interval == expected); + } + + testInterval(interval, dur!"days"(22), Interval!Date(Date(2010, 7, 26), Date(2012, 1, 29))); + testInterval(interval, dur!"days"(-22), Interval!Date(Date(2010, 6, 12), Date(2011, 12, 16))); + + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + static assert(!__traits(compiles, cInterval.shift(dur!"days"(5)))); + static assert(!__traits(compiles, iInterval.shift(dur!"days"(5)))); + + // Verify Examples. + auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 4, 5)); + auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 4, 5)); + + interval1.shift(dur!"days"(50)); + assert(interval1 == Interval!Date(Date(1996, 2, 21), Date(2012, 5, 25))); + + interval2.shift(dur!"days"(-50)); + assert(interval2 == Interval!Date(Date(1995, 11, 13), Date(2012, 2, 15))); +} + +// Test Interval's shift(int, int, AllowDayOverflow). +@safe unittest +{ + import std.datetime.date; + + { + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + + static void testIntervalFail(Interval!Date interval, int years, int months) + { + interval.shift(years, months); + } + + assertThrown!DateTimeException(testIntervalFail(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), 1, 0)); + + static void testInterval(I)(I interval, int years, int months, AllowDayOverflow allow, + in I expected, size_t line = __LINE__) + { + interval.shift(years, months, allow); + assert(interval == expected); + } + + testInterval(interval, 5, 0, AllowDayOverflow.yes, Interval!Date(Date(2015, 7, 4), Date(2017, 1, 7))); + testInterval(interval, -5, 0, AllowDayOverflow.yes, Interval!Date(Date(2005, 7, 4), Date(2007, 1, 7))); + + auto interval2 = Interval!Date(Date(2000, 1, 29), Date(2010, 5, 31)); + + testInterval(interval2, 1, 1, AllowDayOverflow.yes, Interval!Date(Date(2001, 3, 1), Date(2011, 7, 1))); + testInterval(interval2, 1, -1, AllowDayOverflow.yes, Interval!Date(Date(2000, 12, 29), Date(2011, 5, 1))); + testInterval(interval2, -1, -1, AllowDayOverflow.yes, Interval!Date(Date(1998, 12, 29), Date(2009, 5, 1))); + testInterval(interval2, -1, 1, AllowDayOverflow.yes, Interval!Date(Date(1999, 3, 1), Date(2009, 7, 1))); + + testInterval(interval2, 1, 1, AllowDayOverflow.no, Interval!Date(Date(2001, 2, 28), Date(2011, 6, 30))); + testInterval(interval2, 1, -1, AllowDayOverflow.no, Interval!Date(Date(2000, 12, 29), Date(2011, 4, 30))); + testInterval(interval2, -1, -1, AllowDayOverflow.no, Interval!Date(Date(1998, 12, 29), Date(2009, 4, 30))); + testInterval(interval2, -1, 1, AllowDayOverflow.no, Interval!Date(Date(1999, 2, 28), Date(2009, 6, 30))); + } + + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + static assert(!__traits(compiles, cInterval.shift(5))); + static assert(!__traits(compiles, iInterval.shift(5))); + + // Verify Examples. + auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)); + auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)); + + interval1.shift(2); + assert(interval1 == Interval!Date(Date(1998, 1, 2), Date(2014, 3, 1))); + + interval2.shift(-2); + assert(interval2 == Interval!Date(Date(1994, 1, 2), Date(2010, 3, 1))); +} + +// Test Interval's expand(Duration). +@safe unittest +{ + import std.datetime.date; + + auto interval = Interval!Date(Date(2000, 7, 4), Date(2012, 1, 7)); + + static void testIntervalFail(I)(I interval, in Duration duration) + { + interval.expand(duration); + } + + assertThrown!DateTimeException(testIntervalFail(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), dur!"days"(1))); + assertThrown!DateTimeException(testIntervalFail(Interval!Date(Date(2010, 7, 4), Date(2010, 7, 5)), dur!"days"(-5))); + + static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__) + { + interval.expand(duration); + assert(interval == expected); + } + + testInterval(interval, dur!"days"(22), Interval!Date(Date(2000, 6, 12), Date(2012, 1, 29))); + testInterval(interval, dur!"days"(-22), Interval!Date(Date(2000, 7, 26), Date(2011, 12, 16))); + + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + static assert(!__traits(compiles, cInterval.expand(dur!"days"(5)))); + static assert(!__traits(compiles, iInterval.expand(dur!"days"(5)))); + + // Verify Examples. + auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)); + auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)); + + interval1.expand(dur!"days"(2)); + assert(interval1 == Interval!Date(Date(1995, 12, 31), Date(2012, 3, 3))); + + interval2.expand(dur!"days"(-2)); + assert(interval2 == Interval!Date(Date(1996, 1, 4), Date(2012, 2, 28))); +} + +// Test Interval's expand(int, int, AllowDayOverflow, Direction) +@safe unittest +{ + import std.datetime.date; + + { + auto interval = Interval!Date(Date(2000, 7, 4), Date(2012, 1, 7)); + + static void testIntervalFail(Interval!Date interval, int years, int months) + { + interval.expand(years, months); + } + + assertThrown!DateTimeException(testIntervalFail(Interval!Date(Date(2010, 7, 4), dur!"days"(0)), 1, 0)); + assertThrown!DateTimeException(testIntervalFail(Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)), -5, 0)); + + static void testInterval(I)(I interval, int years, int months, AllowDayOverflow allow, Direction dir, + in I expected, size_t line = __LINE__) + { + interval.expand(years, months, allow, dir); + assert(interval == expected); + } + + testInterval(interval, 5, 0, AllowDayOverflow.yes, Direction.both, + Interval!Date(Date(1995, 7, 4), Date(2017, 1, 7))); + testInterval(interval, -5, 0, AllowDayOverflow.yes, Direction.both, + Interval!Date(Date(2005, 7, 4), Date(2007, 1, 7))); + + testInterval(interval, 5, 0, AllowDayOverflow.yes, Direction.fwd, + Interval!Date(Date(2000, 7, 4), Date(2017, 1, 7))); + testInterval(interval, -5, 0, AllowDayOverflow.yes, Direction.fwd, + Interval!Date(Date(2000, 7, 4), Date(2007, 1, 7))); + + testInterval(interval, 5, 0, AllowDayOverflow.yes, Direction.bwd, + Interval!Date(Date(1995, 7, 4), Date(2012, 1, 7))); + testInterval(interval, -5, 0, AllowDayOverflow.yes, Direction.bwd, + Interval!Date(Date(2005, 7, 4), Date(2012, 1, 7))); + + auto interval2 = Interval!Date(Date(2000, 1, 29), Date(2010, 5, 31)); + + testInterval(interval2, 1, 1, AllowDayOverflow.yes, Direction.both, + Interval!Date(Date(1998, 12, 29), Date(2011, 7, 1))); + testInterval(interval2, 1, -1, AllowDayOverflow.yes, Direction.both, + Interval!Date(Date(1999, 3, 1), Date(2011, 5, 1))); + testInterval(interval2, -1, -1, AllowDayOverflow.yes, Direction.both, + Interval!Date(Date(2001, 3, 1), Date(2009, 5, 1))); + testInterval(interval2, -1, 1, AllowDayOverflow.yes, Direction.both, + Interval!Date(Date(2000, 12, 29), Date(2009, 7, 1))); + + testInterval(interval2, 1, 1, AllowDayOverflow.no, Direction.both, + Interval!Date(Date(1998, 12, 29), Date(2011, 6, 30))); + testInterval(interval2, 1, -1, AllowDayOverflow.no, Direction.both, + Interval!Date(Date(1999, 2, 28), Date(2011, 4, 30))); + testInterval(interval2, -1, -1, AllowDayOverflow.no, Direction.both, + Interval!Date(Date(2001, 2, 28), Date(2009, 4, 30))); + testInterval(interval2, -1, 1, AllowDayOverflow.no, Direction.both, + Interval!Date(Date(2000, 12, 29), Date(2009, 6, 30))); + + testInterval(interval2, 1, 1, AllowDayOverflow.yes, Direction.fwd, + Interval!Date(Date(2000, 1, 29), Date(2011, 7, 1))); + testInterval(interval2, 1, -1, AllowDayOverflow.yes, Direction.fwd, + Interval!Date(Date(2000, 1, 29), Date(2011, 5, 1))); + testInterval(interval2, -1, -1, AllowDayOverflow.yes, Direction.fwd, + Interval!Date(Date(2000, 1, 29), Date(2009, 5, 1))); + testInterval(interval2, -1, 1, AllowDayOverflow.yes, Direction.fwd, + Interval!Date(Date(2000, 1, 29), Date(2009, 7, 1))); + + testInterval(interval2, 1, 1, AllowDayOverflow.no, Direction.fwd, + Interval!Date(Date(2000, 1, 29), Date(2011, 6, 30))); + testInterval(interval2, 1, -1, AllowDayOverflow.no, Direction.fwd, + Interval!Date(Date(2000, 1, 29), Date(2011, 4, 30))); + testInterval(interval2, -1, -1, AllowDayOverflow.no, Direction.fwd, + Interval!Date(Date(2000, 1, 29), Date(2009, 4, 30))); + testInterval(interval2, -1, 1, AllowDayOverflow.no, Direction.fwd, + Interval!Date(Date(2000, 1, 29), Date(2009, 6, 30))); + + testInterval(interval2, 1, 1, AllowDayOverflow.yes, Direction.bwd, + Interval!Date(Date(1998, 12, 29), Date(2010, 5, 31))); + testInterval(interval2, 1, -1, AllowDayOverflow.yes, Direction.bwd, + Interval!Date(Date(1999, 3, 1), Date(2010, 5, 31))); + testInterval(interval2, -1, -1, AllowDayOverflow.yes, Direction.bwd, + Interval!Date(Date(2001, 3, 1), Date(2010, 5, 31))); + testInterval(interval2, -1, 1, AllowDayOverflow.yes, Direction.bwd, + Interval!Date(Date(2000, 12, 29), Date(2010, 5, 31))); + + testInterval(interval2, 1, 1, AllowDayOverflow.no, Direction.bwd, + Interval!Date(Date(1998, 12, 29), Date(2010, 5, 31))); + testInterval(interval2, 1, -1, AllowDayOverflow.no, Direction.bwd, + Interval!Date(Date(1999, 2, 28), Date(2010, 5, 31))); + testInterval(interval2, -1, -1, AllowDayOverflow.no, Direction.bwd, + Interval!Date(Date(2001, 2, 28), Date(2010, 5, 31))); + testInterval(interval2, -1, 1, AllowDayOverflow.no, Direction.bwd, + Interval!Date(Date(2000, 12, 29), Date(2010, 5, 31))); + } + + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + static assert(!__traits(compiles, cInterval.expand(5))); + static assert(!__traits(compiles, iInterval.expand(5))); + + // Verify Examples. + auto interval1 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)); + auto interval2 = Interval!Date(Date(1996, 1, 2), Date(2012, 3, 1)); + + interval1.expand(2); + assert(interval1 == Interval!Date(Date(1994, 1, 2), Date(2014, 3, 1))); + + interval2.expand(-2); + assert(interval2 == Interval!Date(Date(1998, 1, 2), Date(2010, 3, 1))); +} + +// Test Interval's fwdRange. +@system unittest +{ + import std.datetime.date; + + { + auto interval = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 21)); + + static void testInterval1(Interval!Date interval) + { + interval.fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)); + } + + assertThrown!DateTimeException(testInterval1(Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + static void testInterval2(Interval!Date interval) + { + interval.fwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)).popFront(); + } + + assertThrown!DateTimeException(testInterval2(interval)); + + assert(!interval.fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).empty); + assert(interval.fwdRange(everyDayOfWeek!Date(DayOfWeek.fri), PopFirst.yes).empty); + + assert(Interval!Date(Date(2010, 9, 12), Date(2010, 10, 1)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).front == + Date(2010, 9, 12)); + + assert(Interval!Date(Date(2010, 9, 12), Date(2010, 10, 1)).fwdRange( + everyDayOfWeek!Date(DayOfWeek.fri), PopFirst.yes).front == Date(2010, 9, 17)); + } + + // Verify Examples. + { + auto interval = Interval!Date(Date(2010, 9, 1), Date(2010, 9, 9)); + auto func = delegate (in Date date) + { + if ((date.day & 1) == 0) + return date + dur!"days"(2); + return date + dur!"days"(1); + }; + auto range = interval.fwdRange(func); + + // An odd day. Using PopFirst.yes would have made this Date(2010, 9, 2). + assert(range.front == Date(2010, 9, 1)); + + range.popFront(); + assert(range.front == Date(2010, 9, 2)); + + range.popFront(); + assert(range.front == Date(2010, 9, 4)); + + range.popFront(); + assert(range.front == Date(2010, 9, 6)); + + range.popFront(); + assert(range.front == Date(2010, 9, 8)); + + range.popFront(); + assert(range.empty); + } + + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + assert(!cInterval.fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).empty); + assert(!iInterval.fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).empty); +} + +// Test Interval's bwdRange. +@system unittest +{ + import std.datetime.date; + + { + auto interval = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 21)); + + static void testInterval1(Interval!Date interval) + { + interval.bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)); + } + + assertThrown!DateTimeException(testInterval1(Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + static void testInterval2(Interval!Date interval) + { + interval.bwdRange(everyDayOfWeek!(Date, Direction.fwd)(DayOfWeek.fri)).popFront(); + } + + assertThrown!DateTimeException(testInterval2(interval)); + + assert(!interval.bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)).empty); + assert(interval.bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri), PopFirst.yes).empty); + + assert(Interval!Date(Date(2010, 9, 19), Date(2010, 10, 1)).bwdRange( + everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)).front == Date(2010, 10, 1)); + + assert(Interval!Date(Date(2010, 9, 19), Date(2010, 10, 1)).bwdRange( + everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri), PopFirst.yes).front == Date(2010, 9, 24)); + } + + // Verify Examples. + { + auto interval = Interval!Date(Date(2010, 9, 1), Date(2010, 9, 9)); + auto func = delegate (in Date date) + { + if ((date.day & 1) == 0) + return date - dur!"days"(2); + return date - dur!"days"(1); + }; + auto range = interval.bwdRange(func); + + // An odd day. Using PopFirst.yes would have made this Date(2010, 9, 8). + assert(range.front == Date(2010, 9, 9)); + + range.popFront(); + assert(range.front == Date(2010, 9, 8)); + + range.popFront(); + assert(range.front == Date(2010, 9, 6)); + + range.popFront(); + assert(range.front == Date(2010, 9, 4)); + + range.popFront(); + assert(range.front == Date(2010, 9, 2)); + + range.popFront(); + assert(range.empty); + } + + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + assert(!cInterval.bwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).empty); + assert(!iInterval.bwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).empty); +} + +// Test Interval's toString(). +@safe unittest +{ + import std.datetime.date; + + assert(Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).toString() == "[2010-Jul-04 - 2012-Jan-07)"); + + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + assert(cInterval.toString()); + assert(iInterval.toString()); +} + + +/++ + Represents an interval of time which has positive infinity as its end point. + + Any ranges which iterate over a $(D PosInfInterval) are infinite. So, the + main purpose of using $(D PosInfInterval) is to create an infinite range + which starts at a fixed point in time and goes to positive infinity. + +/ +struct PosInfInterval(TP) +{ +public: + + /++ + Params: + begin = The time point which begins the interval. + + Example: +-------------------- +auto interval = PosInfInterval!Date(Date(1996, 1, 2)); +-------------------- + +/ + this(in TP begin) pure nothrow + { + _begin = cast(TP) begin; + } + + + /++ + Params: + rhs = The $(D PosInfInterval) to assign to this one. + +/ + ref PosInfInterval opAssign(const ref PosInfInterval rhs) pure nothrow + { + _begin = cast(TP) rhs._begin; + return this; + } + + + /++ + Params: + rhs = The $(D PosInfInterval) to assign to this one. + +/ + ref PosInfInterval opAssign(PosInfInterval rhs) pure nothrow + { + _begin = cast(TP) rhs._begin; + return this; + } + + + /++ + The starting point of the interval. It is included in the interval. + + Example: +-------------------- +assert(PosInfInterval!Date(Date(1996, 1, 2)).begin == Date(1996, 1, 2)); +-------------------- + +/ + @property TP begin() const pure nothrow + { + return cast(TP)_begin; + } + + + /++ + The starting point of the interval. It is included in the interval. + + Params: + timePoint = The time point to set $(D begin) to. + +/ + @property void begin(TP timePoint) pure nothrow + { + _begin = timePoint; + } + + + /++ + Whether the interval's length is 0. Always returns false. + + Example: +-------------------- +assert(!PosInfInterval!Date(Date(1996, 1, 2)).empty); +-------------------- + +/ + enum bool empty = false; + + + /++ + Whether the given time point is within this interval. + + Params: + timePoint = The time point to check for inclusion in this interval. + + Example: +-------------------- +assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains(Date(1994, 12, 24))); +assert(PosInfInterval!Date(Date(1996, 1, 2)).contains(Date(2000, 1, 5))); +-------------------- + +/ + bool contains(TP timePoint) const pure nothrow + { + return timePoint >= _begin; + } + + + /++ + Whether the given interval is completely within this interval. + + Params: + interval = The interval to check for inclusion in this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given interval + is empty. + + Example: +-------------------- +assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + +assert(PosInfInterval!Date(Date(1996, 1, 2)).contains( + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + +assert(PosInfInterval!Date(Date(1996, 1, 2)).contains( + Interval!Date(Date(1998, 2, 28), Date(2013, 5, 1)))); +-------------------- + +/ + bool contains(in Interval!TP interval) const pure + { + interval._enforceNotEmpty(); + return interval._begin >= _begin; + } + + + /++ + Whether the given interval is completely within this interval. + + Params: + interval = The interval to check for inclusion in this interval. + + Example: +-------------------- +assert(PosInfInterval!Date(Date(1996, 1, 2)).contains( + PosInfInterval!Date(Date(1999, 5, 4)))); + +assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains( + PosInfInterval!Date(Date(1995, 7, 2)))); +-------------------- + +/ + bool contains(in PosInfInterval interval) const pure nothrow + { + return interval._begin >= _begin; + } + + + /++ + Whether the given interval is completely within this interval. + + Always returns false because an interval going to positive infinity + can never contain an interval beginning at negative infinity. + + Params: + interval = The interval to check for inclusion in this interval. + + Example: +-------------------- +assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains( + NegInfInterval!Date(Date(1996, 5, 4)))); +-------------------- + +/ + bool contains(in NegInfInterval!TP interval) const pure nothrow + { + return false; + } + + + /++ + Whether this interval is before the given time point. + + Always returns false because an interval going to positive infinity + can never be before any time point. + + Params: + timePoint = The time point to check whether this interval is before + it. + + Example: +-------------------- +assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(Date(1994, 12, 24))); +assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(Date(2000, 1, 5))); +-------------------- + +/ + bool isBefore(in TP timePoint) const pure nothrow + { + return false; + } + + + /++ + Whether this interval is before the given interval and does not + intersect it. + + Always returns false (unless the given interval is empty) because an + interval going to positive infinity can never be before any other + interval. + + Params: + interval = The interval to check for against this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given interval + is empty. + + Example: +-------------------- +assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + +assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore( + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); +-------------------- + +/ + bool isBefore(in Interval!TP interval) const pure + { + interval._enforceNotEmpty(); + return false; + } + + + /++ + Whether this interval is before the given interval and does not + intersect it. + + Always returns false because an interval going to positive infinity can + never be before any other interval. + + Params: + interval = The interval to check for against this interval. + + Example: +-------------------- +assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore( + PosInfInterval!Date(Date(1992, 5, 4)))); + +assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore( + PosInfInterval!Date(Date(2013, 3, 7)))); +-------------------- + +/ + bool isBefore(in PosInfInterval interval) const pure nothrow + { + return false; + } + + + /++ + Whether this interval is before the given interval and does not + intersect it. + + Always returns false because an interval going to positive infinity can + never be before any other interval. + + Params: + interval = The interval to check for against this interval. + + Example: +-------------------- +assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore( + NegInfInterval!Date(Date(1996, 5, 4)))); +-------------------- + +/ + bool isBefore(in NegInfInterval!TP interval) const pure nothrow + { + return false; + } + + + /++ + Whether this interval is after the given time point. + + Params: + timePoint = The time point to check whether this interval is after + it. + + Example: +-------------------- +assert(PosInfInterval!Date(Date(1996, 1, 2)).isAfter(Date(1994, 12, 24))); +assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(Date(2000, 1, 5))); +-------------------- + +/ + bool isAfter(in TP timePoint) const pure nothrow + { + return timePoint < _begin; + } + + + /++ + Whether this interval is after the given interval and does not intersect + it. + + Params: + interval = The interval to check against this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given interval + is empty. + + Example: +-------------------- +assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + +assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter( + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + +assert(PosInfInterval!Date(Date(1996, 1, 2)).isAfter( + Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2)))); +-------------------- + +/ + bool isAfter(in Interval!TP interval) const pure + { + interval._enforceNotEmpty(); + return _begin >= interval._end; + } + + + /++ + Whether this interval is after the given interval and does not intersect + it. + + Always returns false because an interval going to positive infinity can + never be after another interval going to positive infinity. + + Params: + interval = The interval to check against this interval. + + Example: +-------------------- +assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter( + PosInfInterval!Date(Date(1990, 1, 7)))); + +assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter( + PosInfInterval!Date(Date(1999, 5, 4)))); +-------------------- + +/ + bool isAfter(in PosInfInterval interval) const pure nothrow + { + return false; + } + + + /++ + Whether this interval is after the given interval and does not intersect + it. + + Params: + interval = The interval to check against this interval. + + Example: +-------------------- +assert(PosInfInterval!Date(Date(1996, 1, 2)).isAfter( + NegInfInterval!Date(Date(1996, 1, 2)))); + +assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter( + NegInfInterval!Date(Date(2000, 7, 1)))); +-------------------- + +/ + bool isAfter(in NegInfInterval!TP interval) const pure nothrow + { + return _begin >= interval._end; + } + + + /++ + Whether the given interval overlaps this interval. + + Params: + interval = The interval to check for intersection with this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given interval + is empty. + + Example: +-------------------- +assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + +assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects( + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + +assert(!PosInfInterval!Date(Date(1996, 1, 2)).intersects( + Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2)))); +-------------------- + +/ + bool intersects(in Interval!TP interval) const pure + { + interval._enforceNotEmpty(); + return interval._end > _begin; + } + + + /++ + Whether the given interval overlaps this interval. + + Always returns true because two intervals going to positive infinity + always overlap. + + Params: + interval = The interval to check for intersection with this + interval. + + Example: +-------------------- +assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects( + PosInfInterval!Date(Date(1990, 1, 7)))); + +assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects( + PosInfInterval!Date(Date(1999, 5, 4)))); +-------------------- + +/ + bool intersects(in PosInfInterval interval) const pure nothrow + { + return true; + } + + + /++ + Whether the given interval overlaps this interval. + + Params: + interval = The interval to check for intersection with this + interval. + + Example: +-------------------- +assert(!PosInfInterval!Date(Date(1996, 1, 2)).intersects( + NegInfInterval!Date(Date(1996, 1, 2)))); + +assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects( + NegInfInterval!Date(Date(2000, 7, 1)))); +-------------------- + +/ + bool intersects(in NegInfInterval!TP interval) const pure nothrow + { + return _begin < interval._end; + } + + + /++ + Returns the intersection of two intervals + + Params: + interval = The interval to intersect with this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the two intervals do + not intersect or if the given interval is empty. + + Example: +-------------------- +assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == + Interval!Date(Date(1996, 1 , 2), Date(2000, 8, 2))); + +assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection( + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) == + Interval!Date(Date(1999, 1 , 12), Date(2011, 9, 17))); +-------------------- + +/ + Interval!TP intersection(in Interval!TP interval) const + { + import std.format : format; + + enforce(this.intersects(interval), + new DateTimeException(format("%s and %s do not intersect.", this, interval))); + + auto begin = _begin > interval._begin ? _begin : interval._begin; + + return Interval!TP(begin, interval._end); + } + + + /++ + Returns the intersection of two intervals + + Params: + interval = The interval to intersect with this interval. + + Example: +-------------------- +assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection( + PosInfInterval!Date(Date(1990, 7, 6))) == + PosInfInterval!Date(Date(1996, 1 , 2))); + +assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection( + PosInfInterval!Date(Date(1999, 1, 12))) == + PosInfInterval!Date(Date(1999, 1 , 12))); +-------------------- + +/ + PosInfInterval intersection(in PosInfInterval interval) const pure nothrow + { + return PosInfInterval(_begin < interval._begin ? interval._begin : _begin); + } + + + /++ + Returns the intersection of two intervals + + Params: + interval = The interval to intersect with this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the two intervals do + not intersect. + + Example: +-------------------- +assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection( + NegInfInterval!Date(Date(1999, 7, 6))) == + Interval!Date(Date(1996, 1 , 2), Date(1999, 7, 6))); + +assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection( + NegInfInterval!Date(Date(2013, 1, 12))) == + Interval!Date(Date(1996, 1 , 2), Date(2013, 1, 12))); +-------------------- + +/ + Interval!TP intersection(in NegInfInterval!TP interval) const + { + import std.format : format; + + enforce(this.intersects(interval), + new DateTimeException(format("%s and %s do not intersect.", this, interval))); + + return Interval!TP(_begin, interval._end); + } + + + /++ + Whether the given interval is adjacent to this interval. + + Params: + interval = The interval to check whether its adjecent to this + interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given interval + is empty. + + Example: +-------------------- +assert(PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent( + Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2)))); + +assert(!PosInfInterval!Date(Date(1999, 1, 12)).isAdjacent( + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); +-------------------- + +/ + bool isAdjacent(in Interval!TP interval) const pure + { + interval._enforceNotEmpty(); + return _begin == interval._end; + } + + + /++ + Whether the given interval is adjacent to this interval. + + Always returns false because two intervals going to positive infinity + can never be adjacent to one another. + + Params: + interval = The interval to check whether its adjecent to this + interval. + + Example: +-------------------- +assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent( + PosInfInterval!Date(Date(1990, 1, 7)))); + +assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent( + PosInfInterval!Date(Date(1996, 1, 2)))); +-------------------- + +/ + bool isAdjacent(in PosInfInterval interval) const pure nothrow + { + return false; + } + + + /++ + Whether the given interval is adjacent to this interval. + + Params: + interval = The interval to check whether its adjecent to this + interval. + + Example: +-------------------- +assert(PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent( + NegInfInterval!Date(Date(1996, 1, 2)))); + +assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent( + NegInfInterval!Date(Date(2000, 7, 1)))); +-------------------- + +/ + bool isAdjacent(in NegInfInterval!TP interval) const pure nothrow + { + return _begin == interval._end; + } + + + /++ + Returns the union of two intervals + + Params: + interval = The interval to merge with this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the two intervals do + not intersect and are not adjacent or if the given interval is + empty. + + Note: + There is no overload for $(D merge) which takes a + $(D NegInfInterval), because an interval + going from negative infinity to positive infinity + is not possible. + + Example: +-------------------- +assert(PosInfInterval!Date(Date(1996, 1, 2)).merge( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == + PosInfInterval!Date(Date(1990, 7 , 6))); + +assert(PosInfInterval!Date(Date(1996, 1, 2)).merge( + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) == + PosInfInterval!Date(Date(1996, 1 , 2))); +-------------------- + +/ + PosInfInterval merge(in Interval!TP interval) const + { + import std.format : format; + + enforce(this.isAdjacent(interval) || this.intersects(interval), + new DateTimeException(format("%s and %s are not adjacent and do not intersect.", this, interval))); + + return PosInfInterval(_begin < interval._begin ? _begin : interval._begin); + } + + + /++ + Returns the union of two intervals + + Params: + interval = The interval to merge with this interval. + + Note: + There is no overload for $(D merge) which takes a + $(D NegInfInterval), because an interval + going from negative infinity to positive infinity + is not possible. + + Example: +-------------------- +assert(PosInfInterval!Date(Date(1996, 1, 2)).merge( + PosInfInterval!Date(Date(1990, 7, 6))) == + PosInfInterval!Date(Date(1990, 7 , 6))); + +assert(PosInfInterval!Date(Date(1996, 1, 2)).merge( + PosInfInterval!Date(Date(1999, 1, 12))) == + PosInfInterval!Date(Date(1996, 1 , 2))); +-------------------- + +/ + PosInfInterval merge(in PosInfInterval interval) const pure nothrow + { + return PosInfInterval(_begin < interval._begin ? _begin : interval._begin); + } + + + /++ + Returns an interval that covers from the earliest time point of two + intervals up to (but not including) the latest time point of two + intervals. + + Params: + interval = The interval to create a span together with this + interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given interval + is empty. + + Note: + There is no overload for $(D span) which takes a + $(D NegInfInterval), because an interval + going from negative infinity to positive infinity + is not possible. + + Example: +-------------------- +assert(PosInfInterval!Date(Date(1996, 1, 2)).span( + Interval!Date(Date(500, 8, 9), Date(1602, 1, 31))) == + PosInfInterval!Date(Date(500, 8, 9))); + +assert(PosInfInterval!Date(Date(1996, 1, 2)).span( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == + PosInfInterval!Date(Date(1990, 7 , 6))); + +assert(PosInfInterval!Date(Date(1996, 1, 2)).span( + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) == + PosInfInterval!Date(Date(1996, 1 , 2))); +-------------------- + +/ + PosInfInterval span(in Interval!TP interval) const pure + { + interval._enforceNotEmpty(); + return PosInfInterval(_begin < interval._begin ? _begin : interval._begin); + } + + + /++ + Returns an interval that covers from the earliest time point of two + intervals up to (but not including) the latest time point of two + intervals. + + Params: + interval = The interval to create a span together with this + interval. + + Note: + There is no overload for $(D span) which takes a + $(D NegInfInterval), because an interval + going from negative infinity to positive infinity + is not possible. + + Example: +-------------------- +assert(PosInfInterval!Date(Date(1996, 1, 2)).span( + PosInfInterval!Date(Date(1990, 7, 6))) == + PosInfInterval!Date(Date(1990, 7 , 6))); + +assert(PosInfInterval!Date(Date(1996, 1, 2)).span( + PosInfInterval!Date(Date(1999, 1, 12))) == + PosInfInterval!Date(Date(1996, 1 , 2))); +-------------------- + +/ + PosInfInterval span(in PosInfInterval interval) const pure nothrow + { + return PosInfInterval(_begin < interval._begin ? _begin : interval._begin); + } + + + /++ + Shifts the $(D begin) of this interval forward or backwards in time by + the given duration (a positive duration shifts the interval forward; a + negative duration shifts it backward). Effectively, it does + $(D begin += duration). + + Params: + duration = The duration to shift the interval by. + + Example: +-------------------- +auto interval1 = PosInfInterval!Date(Date(1996, 1, 2)); +auto interval2 = PosInfInterval!Date(Date(1996, 1, 2)); + +interval1.shift(dur!"days"(50)); +assert(interval1 == PosInfInterval!Date(Date(1996, 2, 21))); + +interval2.shift(dur!"days"(-50)); +assert(interval2 == PosInfInterval!Date(Date(1995, 11, 13))); +-------------------- + +/ + void shift(D)(D duration) pure nothrow + if (__traits(compiles, begin + duration)) + { + _begin += duration; + } + + + static if (__traits(compiles, begin.add!"months"(1)) && + __traits(compiles, begin.add!"years"(1))) + { + /++ + Shifts the $(D begin) of this interval forward or backwards in time + by the given number of years and/or months (a positive number of + years and months shifts the interval forward; a negative number + shifts it backward). It adds the years the given years and months to + $(D begin). It effectively calls $(D add!"years"()) and then + $(D add!"months"()) on $(D begin) with the given number of years and + months. + + Params: + years = The number of years to shift the interval by. + months = The number of months to shift the interval by. + allowOverflow = Whether the days should be allowed to overflow + on $(D begin), causing its month to increment. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty or if the resulting interval would be invalid. + + Example: +-------------------- +auto interval1 = PosInfInterval!Date(Date(1996, 1, 2)); +auto interval2 = PosInfInterval!Date(Date(1996, 1, 2)); + +interval1.shift(dur!"days"(50)); +assert(interval1 == PosInfInterval!Date(Date(1996, 2, 21))); + +interval2.shift(dur!"days"(-50)); +assert(interval2 == PosInfInterval!Date(Date(1995, 11, 13))); +-------------------- + +/ + void shift(T)(T years, T months = 0, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) + if (isIntegral!T) + { + auto begin = _begin; + + begin.add!"years"(years, allowOverflow); + begin.add!"months"(months, allowOverflow); + + _begin = begin; + } + } + + + /++ + Expands the interval backwards in time. Effectively, it does + $(D begin -= duration). + + Params: + duration = The duration to expand the interval by. + + Example: +-------------------- +auto interval1 = PosInfInterval!Date(Date(1996, 1, 2)); +auto interval2 = PosInfInterval!Date(Date(1996, 1, 2)); + +interval1.expand(dur!"days"(2)); +assert(interval1 == PosInfInterval!Date(Date(1995, 12, 31))); + +interval2.expand(dur!"days"(-2)); +assert(interval2 == PosInfInterval!Date(Date(1996, 1, 4))); +-------------------- + +/ + void expand(D)(D duration) pure nothrow + if (__traits(compiles, begin + duration)) + { + _begin -= duration; + } + + + static if (__traits(compiles, begin.add!"months"(1)) && + __traits(compiles, begin.add!"years"(1))) + { + /++ + Expands the interval forwards and/or backwards in time. Effectively, + it subtracts the given number of months/years from $(D begin). + + Params: + years = The number of years to expand the interval by. + months = The number of months to expand the interval by. + allowOverflow = Whether the days should be allowed to overflow + on $(D begin), causing its month to increment. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty or if the resulting interval would be invalid. + + Example: +-------------------- +auto interval1 = PosInfInterval!Date(Date(1996, 1, 2)); +auto interval2 = PosInfInterval!Date(Date(1996, 1, 2)); + +interval1.expand(2); +assert(interval1 == PosInfInterval!Date(Date(1994, 1, 2))); + +interval2.expand(-2); +assert(interval2 == PosInfInterval!Date(Date(1998, 1, 2))); +-------------------- + +/ + void expand(T)(T years, T months = 0, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) + if (isIntegral!T) + { + auto begin = _begin; + + begin.add!"years"(-years, allowOverflow); + begin.add!"months"(-months, allowOverflow); + + _begin = begin; + } + } + + + /++ + Returns a range which iterates forward over the interval, starting + at $(D begin), using $(D_PARAM func) to generate each successive time + point. + + The range's $(D front) is the interval's $(D begin). $(D_PARAM func) is + used to generate the next $(D front) when $(D popFront) is called. If + $(D_PARAM popFirst) is $(D PopFirst.yes), then $(D popFront) is called + before the range is returned (so that $(D front) is a time point which + $(D_PARAM func) would generate). + + If $(D_PARAM func) ever generates a time point less than or equal to the + current $(D front) of the range, then a + $(REF DateTimeException,std,datetime,date) will be thrown. + + There are helper functions in this module which generate common + delegates to pass to $(D fwdRange). Their documentation starts with + "Range-generating function," to make them easily searchable. + + Params: + func = The function used to generate the time points of the + range over the interval. + popFirst = Whether $(D popFront) should be called on the range + before returning it. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty. + + Warning: + $(D_PARAM func) must be logically pure. Ideally, $(D_PARAM func) + would be a function pointer to a pure function, but forcing + $(D_PARAM func) to be pure is far too restrictive to be useful, and + in order to have the ease of use of having functions which generate + functions to pass to $(D fwdRange), $(D_PARAM func) must be a + delegate. + + If $(D_PARAM func) retains state which changes as it is called, then + some algorithms will not work correctly, because the range's + $(D save) will have failed to have really saved the range's state. + To avoid such bugs, don't pass a delegate which is + not logically pure to $(D fwdRange). If $(D_PARAM func) is given the + same time point with two different calls, it must return the same + result both times. + + Of course, none of the functions in this module have this problem, + so it's only relevant for custom delegates. + + Example: +-------------------- +auto interval = PosInfInterval!Date(Date(2010, 9, 1)); +auto func = delegate (in Date date) //For iterating over even-numbered days. + { + if ((date.day & 1) == 0) + return date + dur!"days"(2); + + return date + dur!"days"(1); + }; +auto range = interval.fwdRange(func); + +//An odd day. Using PopFirst.yes would have made this Date(2010, 9, 2). +assert(range.front == Date(2010, 9, 1)); + +range.popFront(); +assert(range.front == Date(2010, 9, 2)); + +range.popFront(); +assert(range.front == Date(2010, 9, 4)); + +range.popFront(); +assert(range.front == Date(2010, 9, 6)); + +range.popFront(); +assert(range.front == Date(2010, 9, 8)); + +range.popFront(); +assert(!range.empty); +-------------------- + +/ + PosInfIntervalRange!(TP) fwdRange(TP delegate(in TP) func, PopFirst popFirst = PopFirst.no) const + { + auto range = PosInfIntervalRange!(TP)(this, func); + + if (popFirst == PopFirst.yes) + range.popFront(); + + return range; + } + + + /+ + Converts this interval to a string. + +/ + //Due to bug http://d.puremagic.com/issues/show_bug.cgi?id=3715 , we can't + //have versions of toString() with extra modifiers, so we define one version + //with modifiers and one without. + string toString() + { + return _toStringImpl(); + } + + + /++ + Converts this interval to a string. + +/ + //Due to bug http://d.puremagic.com/issues/show_bug.cgi?id=3715 , we can't + //have versions of toString() with extra modifiers, so we define one version + //with modifiers and one without. + string toString() const nothrow + { + return _toStringImpl(); + } + +private: + + /+ + Since we have two versions of toString(), we have _toStringImpl() + so that they can share implementations. + +/ + string _toStringImpl() const nothrow + { + import std.format : format; + try + return format("[%s - ∞)", _begin); + catch (Exception e) + assert(0, "format() threw."); + } + + + TP _begin; +} + +//Test PosInfInterval's constructor. +@safe unittest +{ + import std.datetime.date; + import std.datetime.systime; + + PosInfInterval!Date(Date.init); + PosInfInterval!TimeOfDay(TimeOfDay.init); + PosInfInterval!DateTime(DateTime.init); + PosInfInterval!SysTime(SysTime(0)); + + //Verify Examples. + auto interval = PosInfInterval!Date(Date(1996, 1, 2)); +} + +//Test PosInfInterval's begin. +@safe unittest +{ + import std.datetime.date; + + assert(PosInfInterval!Date(Date(1, 1, 1)).begin == Date(1, 1, 1)); + assert(PosInfInterval!Date(Date(2010, 1, 1)).begin == Date(2010, 1, 1)); + assert(PosInfInterval!Date(Date(1997, 12, 31)).begin == Date(1997, 12, 31)); + + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + assert(cPosInfInterval.begin != Date.init); + assert(iPosInfInterval.begin != Date.init); + + //Verify Examples. + assert(PosInfInterval!Date(Date(1996, 1, 2)).begin == Date(1996, 1, 2)); +} + +//Test PosInfInterval's empty. +@safe unittest +{ + import std.datetime.date; + import std.datetime.systime; + + assert(!PosInfInterval!Date(Date(2010, 1, 1)).empty); + assert(!PosInfInterval!TimeOfDay(TimeOfDay(0, 30, 0)).empty); + assert(!PosInfInterval!DateTime(DateTime(2010, 1, 1, 0, 30, 0)).empty); + assert(!PosInfInterval!SysTime(SysTime(DateTime(2010, 1, 1, 0, 30, 0))).empty); + + const cPosInfInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iPosInfInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + assert(!cPosInfInterval.empty); + assert(!iPosInfInterval.empty); + + //Verify Examples. + assert(!PosInfInterval!Date(Date(1996, 1, 2)).empty); +} + +//Test PosInfInterval's contains(time point). +@safe unittest +{ + import std.datetime.date; + + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + + assert(!posInfInterval.contains(Date(2009, 7, 4))); + assert(!posInfInterval.contains(Date(2010, 7, 3))); + assert(posInfInterval.contains(Date(2010, 7, 4))); + assert(posInfInterval.contains(Date(2010, 7, 5))); + assert(posInfInterval.contains(Date(2011, 7, 1))); + assert(posInfInterval.contains(Date(2012, 1, 6))); + assert(posInfInterval.contains(Date(2012, 1, 7))); + assert(posInfInterval.contains(Date(2012, 1, 8))); + assert(posInfInterval.contains(Date(2013, 1, 7))); + + const cdate = Date(2010, 7, 6); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + assert(posInfInterval.contains(cdate)); + assert(cPosInfInterval.contains(cdate)); + assert(iPosInfInterval.contains(cdate)); + + //Verify Examples. + assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains(Date(1994, 12, 24))); + assert(PosInfInterval!Date(Date(1996, 1, 2)).contains(Date(2000, 1, 5))); +} + +//Test PosInfInterval's contains(Interval). +@safe unittest +{ + import std.datetime.date; + + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + + static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval) + { + posInfInterval.contains(interval); + } + + assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assert(posInfInterval.contains(posInfInterval)); + assert(!posInfInterval.contains(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)))); + assert(!posInfInterval.contains(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)))); + assert(!posInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)))); + assert(!posInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)))); + assert(!posInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)))); + assert(!posInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)))); + assert(posInfInterval.contains(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)))); + assert(posInfInterval.contains(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)))); + assert(posInfInterval.contains(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)))); + assert(posInfInterval.contains(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)))); + assert(posInfInterval.contains(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)))); + assert(posInfInterval.contains(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)))); + + assert(!posInfInterval.contains(PosInfInterval!Date(Date(2010, 7, 3)))); + assert(posInfInterval.contains(PosInfInterval!Date(Date(2010, 7, 4)))); + assert(posInfInterval.contains(PosInfInterval!Date(Date(2010, 7, 5)))); + assert(posInfInterval.contains(PosInfInterval!Date(Date(2012, 1, 6)))); + assert(posInfInterval.contains(PosInfInterval!Date(Date(2012, 1, 7)))); + assert(posInfInterval.contains(PosInfInterval!Date(Date(2012, 1, 8)))); + + assert(PosInfInterval!Date(Date(2010, 7, 3)).contains(posInfInterval)); + assert(PosInfInterval!Date(Date(2010, 7, 4)).contains(posInfInterval)); + assert(!PosInfInterval!Date(Date(2010, 7, 5)).contains(posInfInterval)); + assert(!PosInfInterval!Date(Date(2012, 1, 6)).contains(posInfInterval)); + assert(!PosInfInterval!Date(Date(2012, 1, 7)).contains(posInfInterval)); + assert(!PosInfInterval!Date(Date(2012, 1, 8)).contains(posInfInterval)); + + assert(!posInfInterval.contains(NegInfInterval!Date(Date(2010, 7, 3)))); + assert(!posInfInterval.contains(NegInfInterval!Date(Date(2010, 7, 4)))); + assert(!posInfInterval.contains(NegInfInterval!Date(Date(2010, 7, 5)))); + assert(!posInfInterval.contains(NegInfInterval!Date(Date(2012, 1, 6)))); + assert(!posInfInterval.contains(NegInfInterval!Date(Date(2012, 1, 7)))); + assert(!posInfInterval.contains(NegInfInterval!Date(Date(2012, 1, 8)))); + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(posInfInterval.contains(interval)); + assert(posInfInterval.contains(cInterval)); + assert(posInfInterval.contains(iInterval)); + assert(posInfInterval.contains(posInfInterval)); + assert(posInfInterval.contains(cPosInfInterval)); + assert(posInfInterval.contains(iPosInfInterval)); + assert(!posInfInterval.contains(negInfInterval)); + assert(!posInfInterval.contains(cNegInfInterval)); + assert(!posInfInterval.contains(iNegInfInterval)); + assert(cPosInfInterval.contains(interval)); + assert(cPosInfInterval.contains(cInterval)); + assert(cPosInfInterval.contains(iInterval)); + assert(cPosInfInterval.contains(posInfInterval)); + assert(cPosInfInterval.contains(cPosInfInterval)); + assert(cPosInfInterval.contains(iPosInfInterval)); + assert(!cPosInfInterval.contains(negInfInterval)); + assert(!cPosInfInterval.contains(cNegInfInterval)); + assert(!cPosInfInterval.contains(iNegInfInterval)); + assert(iPosInfInterval.contains(interval)); + assert(iPosInfInterval.contains(cInterval)); + assert(iPosInfInterval.contains(iInterval)); + assert(iPosInfInterval.contains(posInfInterval)); + assert(iPosInfInterval.contains(cPosInfInterval)); + assert(iPosInfInterval.contains(iPosInfInterval)); + assert(!iPosInfInterval.contains(negInfInterval)); + assert(!iPosInfInterval.contains(cNegInfInterval)); + assert(!iPosInfInterval.contains(iNegInfInterval)); + + //Verify Examples. + assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + assert(PosInfInterval!Date(Date(1996, 1, 2)).contains(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + assert(PosInfInterval!Date(Date(1996, 1, 2)).contains(Interval!Date(Date(1998, 2, 28), Date(2013, 5, 1)))); + + assert(PosInfInterval!Date(Date(1996, 1, 2)).contains(PosInfInterval!Date(Date(1999, 5, 4)))); + assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains(PosInfInterval!Date(Date(1995, 7, 2)))); + + assert(!PosInfInterval!Date(Date(1996, 1, 2)).contains(NegInfInterval!Date(Date(1996, 5, 4)))); +} + +//Test PosInfInterval's isBefore(time point). +@safe unittest +{ + import std.datetime.date; + + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + + assert(!posInfInterval.isBefore(Date(2009, 7, 3))); + assert(!posInfInterval.isBefore(Date(2010, 7, 3))); + assert(!posInfInterval.isBefore(Date(2010, 7, 4))); + assert(!posInfInterval.isBefore(Date(2010, 7, 5))); + assert(!posInfInterval.isBefore(Date(2011, 7, 1))); + assert(!posInfInterval.isBefore(Date(2012, 1, 6))); + assert(!posInfInterval.isBefore(Date(2012, 1, 7))); + assert(!posInfInterval.isBefore(Date(2012, 1, 8))); + assert(!posInfInterval.isBefore(Date(2013, 1, 7))); + + const cdate = Date(2010, 7, 6); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + assert(!posInfInterval.isBefore(cdate)); + assert(!cPosInfInterval.isBefore(cdate)); + assert(!iPosInfInterval.isBefore(cdate)); + + //Verify Examples. + assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(Date(1994, 12, 24))); + assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(Date(2000, 1, 5))); +} + +//Test PosInfInterval's isBefore(Interval). +@safe unittest +{ + import std.datetime.date; + + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + + static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval) + { + posInfInterval.isBefore(interval); + } + + assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assert(!posInfInterval.isBefore(posInfInterval)); + assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)))); + assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)))); + assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)))); + assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)))); + assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)))); + assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)))); + assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)))); + assert(!posInfInterval.isBefore(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)))); + assert(!posInfInterval.isBefore(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)))); + assert(!posInfInterval.isBefore(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)))); + assert(!posInfInterval.isBefore(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)))); + assert(!posInfInterval.isBefore(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)))); + + assert(!posInfInterval.isBefore(PosInfInterval!Date(Date(2010, 7, 3)))); + assert(!posInfInterval.isBefore(PosInfInterval!Date(Date(2010, 7, 4)))); + assert(!posInfInterval.isBefore(PosInfInterval!Date(Date(2010, 7, 5)))); + assert(!posInfInterval.isBefore(PosInfInterval!Date(Date(2012, 1, 6)))); + assert(!posInfInterval.isBefore(PosInfInterval!Date(Date(2012, 1, 7)))); + assert(!posInfInterval.isBefore(PosInfInterval!Date(Date(2012, 1, 8)))); + + assert(!PosInfInterval!Date(Date(2010, 7, 3)).isBefore(posInfInterval)); + assert(!PosInfInterval!Date(Date(2010, 7, 4)).isBefore(posInfInterval)); + assert(!PosInfInterval!Date(Date(2010, 7, 5)).isBefore(posInfInterval)); + assert(!PosInfInterval!Date(Date(2012, 1, 6)).isBefore(posInfInterval)); + assert(!PosInfInterval!Date(Date(2012, 1, 7)).isBefore(posInfInterval)); + assert(!PosInfInterval!Date(Date(2012, 1, 8)).isBefore(posInfInterval)); + + assert(!posInfInterval.isBefore(NegInfInterval!Date(Date(2010, 7, 3)))); + assert(!posInfInterval.isBefore(NegInfInterval!Date(Date(2010, 7, 4)))); + assert(!posInfInterval.isBefore(NegInfInterval!Date(Date(2010, 7, 5)))); + assert(!posInfInterval.isBefore(NegInfInterval!Date(Date(2012, 1, 6)))); + assert(!posInfInterval.isBefore(NegInfInterval!Date(Date(2012, 1, 7)))); + assert(!posInfInterval.isBefore(NegInfInterval!Date(Date(2012, 1, 8)))); + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!posInfInterval.isBefore(interval)); + assert(!posInfInterval.isBefore(cInterval)); + assert(!posInfInterval.isBefore(iInterval)); + assert(!posInfInterval.isBefore(posInfInterval)); + assert(!posInfInterval.isBefore(cPosInfInterval)); + assert(!posInfInterval.isBefore(iPosInfInterval)); + assert(!posInfInterval.isBefore(negInfInterval)); + assert(!posInfInterval.isBefore(cNegInfInterval)); + assert(!posInfInterval.isBefore(iNegInfInterval)); + assert(!cPosInfInterval.isBefore(interval)); + assert(!cPosInfInterval.isBefore(cInterval)); + assert(!cPosInfInterval.isBefore(iInterval)); + assert(!cPosInfInterval.isBefore(posInfInterval)); + assert(!cPosInfInterval.isBefore(cPosInfInterval)); + assert(!cPosInfInterval.isBefore(iPosInfInterval)); + assert(!cPosInfInterval.isBefore(negInfInterval)); + assert(!cPosInfInterval.isBefore(cNegInfInterval)); + assert(!cPosInfInterval.isBefore(iNegInfInterval)); + assert(!iPosInfInterval.isBefore(interval)); + assert(!iPosInfInterval.isBefore(cInterval)); + assert(!iPosInfInterval.isBefore(iInterval)); + assert(!iPosInfInterval.isBefore(posInfInterval)); + assert(!iPosInfInterval.isBefore(cPosInfInterval)); + assert(!iPosInfInterval.isBefore(iPosInfInterval)); + assert(!iPosInfInterval.isBefore(negInfInterval)); + assert(!iPosInfInterval.isBefore(cNegInfInterval)); + assert(!iPosInfInterval.isBefore(iNegInfInterval)); + + //Verify Examples. + assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + + assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(PosInfInterval!Date(Date(1992, 5, 4)))); + assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(PosInfInterval!Date(Date(2013, 3, 7)))); + + assert(!PosInfInterval!Date(Date(1996, 1, 2)).isBefore(NegInfInterval!Date(Date(1996, 5, 4)))); +} + +//Test PosInfInterval's isAfter(time point). +@safe unittest +{ + import std.datetime.date; + + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + + assert(posInfInterval.isAfter(Date(2009, 7, 3))); + assert(posInfInterval.isAfter(Date(2010, 7, 3))); + assert(!posInfInterval.isAfter(Date(2010, 7, 4))); + assert(!posInfInterval.isAfter(Date(2010, 7, 5))); + assert(!posInfInterval.isAfter(Date(2011, 7, 1))); + assert(!posInfInterval.isAfter(Date(2012, 1, 6))); + assert(!posInfInterval.isAfter(Date(2012, 1, 7))); + assert(!posInfInterval.isAfter(Date(2012, 1, 8))); + assert(!posInfInterval.isAfter(Date(2013, 1, 7))); + + const cdate = Date(2010, 7, 6); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + assert(!posInfInterval.isAfter(cdate)); + assert(!cPosInfInterval.isAfter(cdate)); + assert(!iPosInfInterval.isAfter(cdate)); + + //Verify Examples. + assert(PosInfInterval!Date(Date(1996, 1, 2)).isAfter(Date(1994, 12, 24))); + assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(Date(2000, 1, 5))); +} + +//Test PosInfInterval's isAfter(Interval). +@safe unittest +{ + import std.datetime.date; + + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + + static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval) + { + posInfInterval.isAfter(interval); + } + + assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assert(!posInfInterval.isAfter(posInfInterval)); + assert(posInfInterval.isAfter(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)))); + assert(!posInfInterval.isAfter(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)))); + assert(posInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)))); + assert(!posInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)))); + assert(!posInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)))); + assert(!posInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)))); + assert(!posInfInterval.isAfter(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)))); + assert(!posInfInterval.isAfter(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)))); + assert(!posInfInterval.isAfter(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)))); + assert(!posInfInterval.isAfter(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)))); + assert(!posInfInterval.isAfter(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)))); + assert(!posInfInterval.isAfter(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)))); + + assert(!posInfInterval.isAfter(PosInfInterval!Date(Date(2010, 7, 3)))); + assert(!posInfInterval.isAfter(PosInfInterval!Date(Date(2010, 7, 4)))); + assert(!posInfInterval.isAfter(PosInfInterval!Date(Date(2010, 7, 5)))); + assert(!posInfInterval.isAfter(PosInfInterval!Date(Date(2012, 1, 6)))); + assert(!posInfInterval.isAfter(PosInfInterval!Date(Date(2012, 1, 7)))); + assert(!posInfInterval.isAfter(PosInfInterval!Date(Date(2012, 1, 8)))); + + assert(!PosInfInterval!Date(Date(2010, 7, 3)).isAfter(posInfInterval)); + assert(!PosInfInterval!Date(Date(2010, 7, 4)).isAfter(posInfInterval)); + assert(!PosInfInterval!Date(Date(2010, 7, 5)).isAfter(posInfInterval)); + assert(!PosInfInterval!Date(Date(2012, 1, 6)).isAfter(posInfInterval)); + assert(!PosInfInterval!Date(Date(2012, 1, 7)).isAfter(posInfInterval)); + assert(!PosInfInterval!Date(Date(2012, 1, 8)).isAfter(posInfInterval)); + + assert(posInfInterval.isAfter(NegInfInterval!Date(Date(2010, 7, 3)))); + assert(posInfInterval.isAfter(NegInfInterval!Date(Date(2010, 7, 4)))); + assert(!posInfInterval.isAfter(NegInfInterval!Date(Date(2010, 7, 5)))); + assert(!posInfInterval.isAfter(NegInfInterval!Date(Date(2012, 1, 6)))); + assert(!posInfInterval.isAfter(NegInfInterval!Date(Date(2012, 1, 7)))); + assert(!posInfInterval.isAfter(NegInfInterval!Date(Date(2012, 1, 8)))); + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!posInfInterval.isAfter(interval)); + assert(!posInfInterval.isAfter(cInterval)); + assert(!posInfInterval.isAfter(iInterval)); + assert(!posInfInterval.isAfter(posInfInterval)); + assert(!posInfInterval.isAfter(cPosInfInterval)); + assert(!posInfInterval.isAfter(iPosInfInterval)); + assert(!posInfInterval.isAfter(negInfInterval)); + assert(!posInfInterval.isAfter(cNegInfInterval)); + assert(!posInfInterval.isAfter(iNegInfInterval)); + assert(!cPosInfInterval.isAfter(interval)); + assert(!cPosInfInterval.isAfter(cInterval)); + assert(!cPosInfInterval.isAfter(iInterval)); + assert(!cPosInfInterval.isAfter(posInfInterval)); + assert(!cPosInfInterval.isAfter(cPosInfInterval)); + assert(!cPosInfInterval.isAfter(iPosInfInterval)); + assert(!cPosInfInterval.isAfter(negInfInterval)); + assert(!cPosInfInterval.isAfter(cNegInfInterval)); + assert(!cPosInfInterval.isAfter(iNegInfInterval)); + assert(!iPosInfInterval.isAfter(interval)); + assert(!iPosInfInterval.isAfter(cInterval)); + assert(!iPosInfInterval.isAfter(iInterval)); + assert(!iPosInfInterval.isAfter(posInfInterval)); + assert(!iPosInfInterval.isAfter(cPosInfInterval)); + assert(!iPosInfInterval.isAfter(iPosInfInterval)); + assert(!iPosInfInterval.isAfter(negInfInterval)); + assert(!iPosInfInterval.isAfter(cNegInfInterval)); + assert(!iPosInfInterval.isAfter(iNegInfInterval)); + + //Verify Examples. + assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + assert(PosInfInterval!Date(Date(1996, 1, 2)).isAfter(Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2)))); + + assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(PosInfInterval!Date(Date(1990, 1, 7)))); + assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(PosInfInterval!Date(Date(1999, 5, 4)))); + + assert(PosInfInterval!Date(Date(1996, 1, 2)).isAfter(NegInfInterval!Date(Date(1996, 1, 2)))); + assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAfter(NegInfInterval!Date(Date(2000, 7, 1)))); +} + +//Test PosInfInterval's intersects(). +@safe unittest +{ + import std.datetime.date; + + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + + static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval) + { + posInfInterval.intersects(interval); + } + + assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assert(posInfInterval.intersects(posInfInterval)); + assert(!posInfInterval.intersects(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)))); + assert(posInfInterval.intersects(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)))); + assert(!posInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)))); + assert(posInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)))); + assert(posInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)))); + assert(posInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)))); + assert(posInfInterval.intersects(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)))); + assert(posInfInterval.intersects(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)))); + assert(posInfInterval.intersects(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)))); + assert(posInfInterval.intersects(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)))); + assert(posInfInterval.intersects(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)))); + assert(posInfInterval.intersects(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)))); + + assert(posInfInterval.intersects(PosInfInterval!Date(Date(2010, 7, 3)))); + assert(posInfInterval.intersects(PosInfInterval!Date(Date(2010, 7, 4)))); + assert(posInfInterval.intersects(PosInfInterval!Date(Date(2010, 7, 5)))); + assert(posInfInterval.intersects(PosInfInterval!Date(Date(2012, 1, 6)))); + assert(posInfInterval.intersects(PosInfInterval!Date(Date(2012, 1, 7)))); + assert(posInfInterval.intersects(PosInfInterval!Date(Date(2012, 1, 8)))); + + assert(PosInfInterval!Date(Date(2010, 7, 3)).intersects(posInfInterval)); + assert(PosInfInterval!Date(Date(2010, 7, 4)).intersects(posInfInterval)); + assert(PosInfInterval!Date(Date(2010, 7, 5)).intersects(posInfInterval)); + assert(PosInfInterval!Date(Date(2012, 1, 6)).intersects(posInfInterval)); + assert(PosInfInterval!Date(Date(2012, 1, 7)).intersects(posInfInterval)); + assert(PosInfInterval!Date(Date(2012, 1, 8)).intersects(posInfInterval)); + + assert(!posInfInterval.intersects(NegInfInterval!Date(Date(2010, 7, 3)))); + assert(!posInfInterval.intersects(NegInfInterval!Date(Date(2010, 7, 4)))); + assert(posInfInterval.intersects(NegInfInterval!Date(Date(2010, 7, 5)))); + assert(posInfInterval.intersects(NegInfInterval!Date(Date(2012, 1, 6)))); + assert(posInfInterval.intersects(NegInfInterval!Date(Date(2012, 1, 7)))); + assert(posInfInterval.intersects(NegInfInterval!Date(Date(2012, 1, 8)))); + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(posInfInterval.intersects(interval)); + assert(posInfInterval.intersects(cInterval)); + assert(posInfInterval.intersects(iInterval)); + assert(posInfInterval.intersects(posInfInterval)); + assert(posInfInterval.intersects(cPosInfInterval)); + assert(posInfInterval.intersects(iPosInfInterval)); + assert(posInfInterval.intersects(negInfInterval)); + assert(posInfInterval.intersects(cNegInfInterval)); + assert(posInfInterval.intersects(iNegInfInterval)); + assert(cPosInfInterval.intersects(interval)); + assert(cPosInfInterval.intersects(cInterval)); + assert(cPosInfInterval.intersects(iInterval)); + assert(cPosInfInterval.intersects(posInfInterval)); + assert(cPosInfInterval.intersects(cPosInfInterval)); + assert(cPosInfInterval.intersects(iPosInfInterval)); + assert(cPosInfInterval.intersects(negInfInterval)); + assert(cPosInfInterval.intersects(cNegInfInterval)); + assert(cPosInfInterval.intersects(iNegInfInterval)); + assert(iPosInfInterval.intersects(interval)); + assert(iPosInfInterval.intersects(cInterval)); + assert(iPosInfInterval.intersects(iInterval)); + assert(iPosInfInterval.intersects(posInfInterval)); + assert(iPosInfInterval.intersects(cPosInfInterval)); + assert(iPosInfInterval.intersects(iPosInfInterval)); + assert(iPosInfInterval.intersects(negInfInterval)); + assert(iPosInfInterval.intersects(cNegInfInterval)); + assert(iPosInfInterval.intersects(iNegInfInterval)); + + //Verify Examples. + assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + assert(!PosInfInterval!Date(Date(1996, 1, 2)).intersects(Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2)))); + + assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(PosInfInterval!Date(Date(1990, 1, 7)))); + assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(PosInfInterval!Date(Date(1999, 5, 4)))); + + assert(!PosInfInterval!Date(Date(1996, 1, 2)).intersects(NegInfInterval!Date(Date(1996, 1, 2)))); + assert(PosInfInterval!Date(Date(1996, 1, 2)).intersects(NegInfInterval!Date(Date(2000, 7, 1)))); +} + +//Test PosInfInterval's intersection(). +@safe unittest +{ + import std.datetime.date; + + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + + static void testInterval(I, J)(in I interval1, in J interval2) + { + interval1.intersection(interval2); + } + + assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)))); + assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)))); + + assertThrown!DateTimeException(testInterval(posInfInterval, NegInfInterval!Date(Date(2010, 7, 3)))); + assertThrown!DateTimeException(testInterval(posInfInterval, NegInfInterval!Date(Date(2010, 7, 4)))); + + assert(posInfInterval.intersection(posInfInterval) == posInfInterval); + assert(posInfInterval.intersection(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) == + Interval!Date(Date(2010, 7, 4), Date(2013, 7, 3))); + assert(posInfInterval.intersection(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) == + Interval!Date(Date(2010, 7, 4), Date(2010, 7, 5))); + assert(posInfInterval.intersection(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(posInfInterval.intersection(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8))); + assert(posInfInterval.intersection(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) == + Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))); + assert(posInfInterval.intersection(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) == + Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))); + assert(posInfInterval.intersection(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) == + Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))); + assert(posInfInterval.intersection(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) == + Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))); + assert(posInfInterval.intersection(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))) == + Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))); + assert(posInfInterval.intersection(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))) == + Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))); + + assert(posInfInterval.intersection(PosInfInterval!Date(Date(2010, 7, 3))) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(posInfInterval.intersection(PosInfInterval!Date(Date(2010, 7, 4))) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(posInfInterval.intersection(PosInfInterval!Date(Date(2010, 7, 5))) == PosInfInterval!Date(Date(2010, 7, 5))); + assert(posInfInterval.intersection(PosInfInterval!Date(Date(2012, 1, 6))) == PosInfInterval!Date(Date(2012, 1, 6))); + assert(posInfInterval.intersection(PosInfInterval!Date(Date(2012, 1, 7))) == PosInfInterval!Date(Date(2012, 1, 7))); + assert(posInfInterval.intersection(PosInfInterval!Date(Date(2012, 1, 8))) == PosInfInterval!Date(Date(2012, 1, 8))); + + assert(PosInfInterval!Date(Date(2010, 7, 3)).intersection(posInfInterval) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(PosInfInterval!Date(Date(2010, 7, 4)).intersection(posInfInterval) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(PosInfInterval!Date(Date(2010, 7, 5)).intersection(posInfInterval) == PosInfInterval!Date(Date(2010, 7, 5))); + assert(PosInfInterval!Date(Date(2012, 1, 6)).intersection(posInfInterval) == PosInfInterval!Date(Date(2012, 1, 6))); + assert(PosInfInterval!Date(Date(2012, 1, 7)).intersection(posInfInterval) == PosInfInterval!Date(Date(2012, 1, 7))); + assert(PosInfInterval!Date(Date(2012, 1, 8)).intersection(posInfInterval) == PosInfInterval!Date(Date(2012, 1, 8))); + + assert(posInfInterval.intersection(NegInfInterval!Date(Date(2010, 7, 5))) == + Interval!Date(Date(2010, 7, 4), Date(2010, 7, 5))); + assert(posInfInterval.intersection(NegInfInterval!Date(Date(2012, 1, 6))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 6))); + assert(posInfInterval.intersection(NegInfInterval!Date(Date(2012, 1, 7))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7))); + assert(posInfInterval.intersection(NegInfInterval!Date(Date(2012, 1, 8))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1, 8))); + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!posInfInterval.intersection(interval).empty); + assert(!posInfInterval.intersection(cInterval).empty); + assert(!posInfInterval.intersection(iInterval).empty); + assert(!posInfInterval.intersection(posInfInterval).empty); + assert(!posInfInterval.intersection(cPosInfInterval).empty); + assert(!posInfInterval.intersection(iPosInfInterval).empty); + assert(!posInfInterval.intersection(negInfInterval).empty); + assert(!posInfInterval.intersection(cNegInfInterval).empty); + assert(!posInfInterval.intersection(iNegInfInterval).empty); + assert(!cPosInfInterval.intersection(interval).empty); + assert(!cPosInfInterval.intersection(cInterval).empty); + assert(!cPosInfInterval.intersection(iInterval).empty); + assert(!cPosInfInterval.intersection(posInfInterval).empty); + assert(!cPosInfInterval.intersection(cPosInfInterval).empty); + assert(!cPosInfInterval.intersection(iPosInfInterval).empty); + assert(!cPosInfInterval.intersection(negInfInterval).empty); + assert(!cPosInfInterval.intersection(cNegInfInterval).empty); + assert(!cPosInfInterval.intersection(iNegInfInterval).empty); + assert(!iPosInfInterval.intersection(interval).empty); + assert(!iPosInfInterval.intersection(cInterval).empty); + assert(!iPosInfInterval.intersection(iInterval).empty); + assert(!iPosInfInterval.intersection(posInfInterval).empty); + assert(!iPosInfInterval.intersection(cPosInfInterval).empty); + assert(!iPosInfInterval.intersection(iPosInfInterval).empty); + assert(!iPosInfInterval.intersection(negInfInterval).empty); + assert(!iPosInfInterval.intersection(cNegInfInterval).empty); + assert(!iPosInfInterval.intersection(iNegInfInterval).empty); + + //Verify Examples. + assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == + Interval!Date(Date(1996, 1, 2), Date(2000, 8, 2))); + assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) == + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))); + + assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(PosInfInterval!Date(Date(1990, 7, 6))) == + PosInfInterval!Date(Date(1996, 1, 2))); + assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(PosInfInterval!Date(Date(1999, 1, 12))) == + PosInfInterval!Date(Date(1999, 1, 12))); + + assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(NegInfInterval!Date(Date(1999, 7, 6))) == + Interval!Date(Date(1996, 1, 2), Date(1999, 7, 6))); + assert(PosInfInterval!Date(Date(1996, 1, 2)).intersection(NegInfInterval!Date(Date(2013, 1, 12))) == + Interval!Date(Date(1996, 1, 2), Date(2013, 1, 12))); +} + +//Test PosInfInterval's isAdjacent(). +@safe unittest +{ + import std.datetime.date; + + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + + static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval) + { + posInfInterval.isAdjacent(interval); + } + + assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assert(!posInfInterval.isAdjacent(posInfInterval)); + assert(!posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)))); + assert(!posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)))); + assert(posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)))); + assert(!posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)))); + assert(!posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)))); + assert(!posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)))); + assert(!posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)))); + assert(!posInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)))); + assert(!posInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)))); + assert(!posInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)))); + assert(!posInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)))); + assert(!posInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)))); + + assert(!posInfInterval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 3)))); + assert(!posInfInterval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 4)))); + assert(!posInfInterval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 5)))); + assert(!posInfInterval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 6)))); + assert(!posInfInterval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 7)))); + assert(!posInfInterval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 8)))); + + assert(!PosInfInterval!Date(Date(2010, 7, 3)).isAdjacent(posInfInterval)); + assert(!PosInfInterval!Date(Date(2010, 7, 4)).isAdjacent(posInfInterval)); + assert(!PosInfInterval!Date(Date(2010, 7, 5)).isAdjacent(posInfInterval)); + assert(!PosInfInterval!Date(Date(2012, 1, 6)).isAdjacent(posInfInterval)); + assert(!PosInfInterval!Date(Date(2012, 1, 7)).isAdjacent(posInfInterval)); + assert(!PosInfInterval!Date(Date(2012, 1, 8)).isAdjacent(posInfInterval)); + + assert(!posInfInterval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 3)))); + assert(posInfInterval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 4)))); + assert(!posInfInterval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 5)))); + assert(!posInfInterval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 6)))); + assert(!posInfInterval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 7)))); + assert(!posInfInterval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 8)))); + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!posInfInterval.isAdjacent(interval)); + assert(!posInfInterval.isAdjacent(cInterval)); + assert(!posInfInterval.isAdjacent(iInterval)); + assert(!posInfInterval.isAdjacent(posInfInterval)); + assert(!posInfInterval.isAdjacent(cPosInfInterval)); + assert(!posInfInterval.isAdjacent(iPosInfInterval)); + assert(!posInfInterval.isAdjacent(negInfInterval)); + assert(!posInfInterval.isAdjacent(cNegInfInterval)); + assert(!posInfInterval.isAdjacent(iNegInfInterval)); + assert(!cPosInfInterval.isAdjacent(interval)); + assert(!cPosInfInterval.isAdjacent(cInterval)); + assert(!cPosInfInterval.isAdjacent(iInterval)); + assert(!cPosInfInterval.isAdjacent(posInfInterval)); + assert(!cPosInfInterval.isAdjacent(cPosInfInterval)); + assert(!cPosInfInterval.isAdjacent(iPosInfInterval)); + assert(!cPosInfInterval.isAdjacent(negInfInterval)); + assert(!cPosInfInterval.isAdjacent(cNegInfInterval)); + assert(!cPosInfInterval.isAdjacent(iNegInfInterval)); + assert(!iPosInfInterval.isAdjacent(interval)); + assert(!iPosInfInterval.isAdjacent(cInterval)); + assert(!iPosInfInterval.isAdjacent(iInterval)); + assert(!iPosInfInterval.isAdjacent(posInfInterval)); + assert(!iPosInfInterval.isAdjacent(cPosInfInterval)); + assert(!iPosInfInterval.isAdjacent(iPosInfInterval)); + assert(!iPosInfInterval.isAdjacent(negInfInterval)); + assert(!iPosInfInterval.isAdjacent(cNegInfInterval)); + assert(!iPosInfInterval.isAdjacent(iNegInfInterval)); + + //Verify Examples. + assert(PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(Interval!Date(Date(1989, 3, 1), Date(1996, 1, 2)))); + assert(!PosInfInterval!Date(Date(1999, 1, 12)).isAdjacent(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + + assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(PosInfInterval!Date(Date(1990, 1, 7)))); + assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(PosInfInterval!Date(Date(1996, 1, 2)))); + + assert(PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(NegInfInterval!Date(Date(1996, 1, 2)))); + assert(!PosInfInterval!Date(Date(1996, 1, 2)).isAdjacent(NegInfInterval!Date(Date(2000, 7, 1)))); +} + +//Test PosInfInterval's merge(). +@safe unittest +{ + import std.datetime.date; + + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + + static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval) + { + posInfInterval.merge(interval); + } + + assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)))); + + assert(posInfInterval.merge(posInfInterval) == posInfInterval); + assert(posInfInterval.merge(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) == + PosInfInterval!Date(Date(2010, 7, 1))); + assert(posInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))) == + PosInfInterval!Date(Date(2010, 7, 3))); + assert(posInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) == + PosInfInterval!Date(Date(2010, 7, 3))); + assert(posInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) == + PosInfInterval!Date(Date(2010, 7, 3))); + assert(posInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) == + PosInfInterval!Date(Date(2010, 7, 3))); + assert(posInfInterval.merge(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) == + PosInfInterval!Date(Date(2010, 7, 4))); + assert(posInfInterval.merge(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) == + PosInfInterval!Date(Date(2010, 7, 4))); + assert(posInfInterval.merge(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) == + PosInfInterval!Date(Date(2010, 7, 4))); + assert(posInfInterval.merge(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) == + PosInfInterval!Date(Date(2010, 7, 4))); + assert(posInfInterval.merge(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))) == + PosInfInterval!Date(Date(2010, 7, 4))); + assert(posInfInterval.merge(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))) == + PosInfInterval!Date(Date(2010, 7, 4))); + + assert(posInfInterval.merge(PosInfInterval!Date(Date(2010, 7, 3))) == PosInfInterval!Date(Date(2010, 7, 3))); + assert(posInfInterval.merge(PosInfInterval!Date(Date(2010, 7, 4))) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(posInfInterval.merge(PosInfInterval!Date(Date(2010, 7, 5))) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(posInfInterval.merge(PosInfInterval!Date(Date(2012, 1, 6))) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(posInfInterval.merge(PosInfInterval!Date(Date(2012, 1, 7))) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(posInfInterval.merge(PosInfInterval!Date(Date(2012, 1, 8))) == PosInfInterval!Date(Date(2010, 7, 4))); + + assert(PosInfInterval!Date(Date(2010, 7, 3)).merge(posInfInterval) == PosInfInterval!Date(Date(2010, 7, 3))); + assert(PosInfInterval!Date(Date(2010, 7, 4)).merge(posInfInterval) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(PosInfInterval!Date(Date(2010, 7, 5)).merge(posInfInterval) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(PosInfInterval!Date(Date(2012, 1, 6)).merge(posInfInterval) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(PosInfInterval!Date(Date(2012, 1, 7)).merge(posInfInterval) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(PosInfInterval!Date(Date(2012, 1, 8)).merge(posInfInterval) == PosInfInterval!Date(Date(2010, 7, 4))); + + static assert(!__traits(compiles, posInfInterval.merge(NegInfInterval!Date(Date(2010, 7, 3))))); + static assert(!__traits(compiles, posInfInterval.merge(NegInfInterval!Date(Date(2010, 7, 4))))); + static assert(!__traits(compiles, posInfInterval.merge(NegInfInterval!Date(Date(2010, 7, 5))))); + static assert(!__traits(compiles, posInfInterval.merge(NegInfInterval!Date(Date(2012, 1, 6))))); + static assert(!__traits(compiles, posInfInterval.merge(NegInfInterval!Date(Date(2012, 1, 7))))); + static assert(!__traits(compiles, posInfInterval.merge(NegInfInterval!Date(Date(2012, 1, 8))))); + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!posInfInterval.merge(interval).empty); + assert(!posInfInterval.merge(cInterval).empty); + assert(!posInfInterval.merge(iInterval).empty); + assert(!posInfInterval.merge(posInfInterval).empty); + assert(!posInfInterval.merge(cPosInfInterval).empty); + assert(!posInfInterval.merge(iPosInfInterval).empty); + static assert(!__traits(compiles, posInfInterval.merge(negInfInterval))); + static assert(!__traits(compiles, posInfInterval.merge(cNegInfInterval))); + static assert(!__traits(compiles, posInfInterval.merge(iNegInfInterval))); + assert(!cPosInfInterval.merge(interval).empty); + assert(!cPosInfInterval.merge(cInterval).empty); + assert(!cPosInfInterval.merge(iInterval).empty); + assert(!cPosInfInterval.merge(posInfInterval).empty); + assert(!cPosInfInterval.merge(cPosInfInterval).empty); + assert(!cPosInfInterval.merge(iPosInfInterval).empty); + static assert(!__traits(compiles, cPosInfInterval.merge(negInfInterval))); + static assert(!__traits(compiles, cPosInfInterval.merge(cNegInfInterval))); + static assert(!__traits(compiles, cPosInfInterval.merge(iNegInfInterval))); + assert(!iPosInfInterval.merge(interval).empty); + assert(!iPosInfInterval.merge(cInterval).empty); + assert(!iPosInfInterval.merge(iInterval).empty); + assert(!iPosInfInterval.merge(posInfInterval).empty); + assert(!iPosInfInterval.merge(cPosInfInterval).empty); + assert(!iPosInfInterval.merge(iPosInfInterval).empty); + static assert(!__traits(compiles, iPosInfInterval.merge(negInfInterval))); + static assert(!__traits(compiles, iPosInfInterval.merge(cNegInfInterval))); + static assert(!__traits(compiles, iPosInfInterval.merge(iNegInfInterval))); + + //Verify Examples. + assert(PosInfInterval!Date(Date(1996, 1, 2)).merge(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == + PosInfInterval!Date(Date(1990, 7, 6))); + assert(PosInfInterval!Date(Date(1996, 1, 2)).merge(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) == + PosInfInterval!Date(Date(1996, 1, 2))); + + assert(PosInfInterval!Date(Date(1996, 1, 2)).merge(PosInfInterval!Date(Date(1990, 7, 6))) == + PosInfInterval!Date(Date(1990, 7, 6))); + assert(PosInfInterval!Date(Date(1996, 1, 2)).merge(PosInfInterval!Date(Date(1999, 1, 12))) == + PosInfInterval!Date(Date(1996, 1, 2))); +} + +//Test PosInfInterval's span(). +@safe unittest +{ + import std.datetime.date; + + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + + static void testInterval(in PosInfInterval!Date posInfInterval, in Interval!Date interval) + { + posInfInterval.span(interval); + } + + assertThrown!DateTimeException(testInterval(posInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assert(posInfInterval.span(posInfInterval) == posInfInterval); + assert(posInfInterval.span(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))) == + PosInfInterval!Date(Date(2010, 7, 1))); + assert(posInfInterval.span(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) == + PosInfInterval!Date(Date(2010, 7, 1))); + assert(posInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))) == + PosInfInterval!Date(Date(2010, 7, 3))); + assert(posInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) == + PosInfInterval!Date(Date(2010, 7, 3))); + assert(posInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) == + PosInfInterval!Date(Date(2010, 7, 3))); + assert(posInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) == + PosInfInterval!Date(Date(2010, 7, 3))); + assert(posInfInterval.span(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) == + PosInfInterval!Date(Date(2010, 7, 4))); + assert(posInfInterval.span(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) == + PosInfInterval!Date(Date(2010, 7, 4))); + assert(posInfInterval.span(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) == + PosInfInterval!Date(Date(2010, 7, 4))); + assert(posInfInterval.span(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) == + PosInfInterval!Date(Date(2010, 7, 4))); + assert(posInfInterval.span(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))) == + PosInfInterval!Date(Date(2010, 7, 4))); + assert(posInfInterval.span(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))) == + PosInfInterval!Date(Date(2010, 7, 4))); + + assert(posInfInterval.span(PosInfInterval!Date(Date(2010, 7, 3))) == PosInfInterval!Date(Date(2010, 7, 3))); + assert(posInfInterval.span(PosInfInterval!Date(Date(2010, 7, 4))) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(posInfInterval.span(PosInfInterval!Date(Date(2010, 7, 5))) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(posInfInterval.span(PosInfInterval!Date(Date(2012, 1, 6))) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(posInfInterval.span(PosInfInterval!Date(Date(2012, 1, 7))) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(posInfInterval.span(PosInfInterval!Date(Date(2012, 1, 8))) == PosInfInterval!Date(Date(2010, 7, 4))); + + assert(PosInfInterval!Date(Date(2010, 7, 3)).span(posInfInterval) == PosInfInterval!Date(Date(2010, 7, 3))); + assert(PosInfInterval!Date(Date(2010, 7, 4)).span(posInfInterval) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(PosInfInterval!Date(Date(2010, 7, 5)).span(posInfInterval) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(PosInfInterval!Date(Date(2012, 1, 6)).span(posInfInterval) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(PosInfInterval!Date(Date(2012, 1, 7)).span(posInfInterval) == PosInfInterval!Date(Date(2010, 7, 4))); + assert(PosInfInterval!Date(Date(2012, 1, 8)).span(posInfInterval) == PosInfInterval!Date(Date(2010, 7, 4))); + + static assert(!__traits(compiles, posInfInterval.span(NegInfInterval!Date(Date(2010, 7, 3))))); + static assert(!__traits(compiles, posInfInterval.span(NegInfInterval!Date(Date(2010, 7, 4))))); + static assert(!__traits(compiles, posInfInterval.span(NegInfInterval!Date(Date(2010, 7, 5))))); + static assert(!__traits(compiles, posInfInterval.span(NegInfInterval!Date(Date(2012, 1, 6))))); + static assert(!__traits(compiles, posInfInterval.span(NegInfInterval!Date(Date(2012, 1, 7))))); + static assert(!__traits(compiles, posInfInterval.span(NegInfInterval!Date(Date(2012, 1, 8))))); + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!posInfInterval.span(interval).empty); + assert(!posInfInterval.span(cInterval).empty); + assert(!posInfInterval.span(iInterval).empty); + assert(!posInfInterval.span(posInfInterval).empty); + assert(!posInfInterval.span(cPosInfInterval).empty); + assert(!posInfInterval.span(iPosInfInterval).empty); + static assert(!__traits(compiles, posInfInterval.span(negInfInterval))); + static assert(!__traits(compiles, posInfInterval.span(cNegInfInterval))); + static assert(!__traits(compiles, posInfInterval.span(iNegInfInterval))); + assert(!cPosInfInterval.span(interval).empty); + assert(!cPosInfInterval.span(cInterval).empty); + assert(!cPosInfInterval.span(iInterval).empty); + assert(!cPosInfInterval.span(posInfInterval).empty); + assert(!cPosInfInterval.span(cPosInfInterval).empty); + assert(!cPosInfInterval.span(iPosInfInterval).empty); + static assert(!__traits(compiles, cPosInfInterval.span(negInfInterval))); + static assert(!__traits(compiles, cPosInfInterval.span(cNegInfInterval))); + static assert(!__traits(compiles, cPosInfInterval.span(iNegInfInterval))); + assert(!iPosInfInterval.span(interval).empty); + assert(!iPosInfInterval.span(cInterval).empty); + assert(!iPosInfInterval.span(iInterval).empty); + assert(!iPosInfInterval.span(posInfInterval).empty); + assert(!iPosInfInterval.span(cPosInfInterval).empty); + assert(!iPosInfInterval.span(iPosInfInterval).empty); + static assert(!__traits(compiles, iPosInfInterval.span(negInfInterval))); + static assert(!__traits(compiles, iPosInfInterval.span(cNegInfInterval))); + static assert(!__traits(compiles, iPosInfInterval.span(iNegInfInterval))); + + //Verify Examples. + assert(PosInfInterval!Date(Date(1996, 1, 2)).span(Interval!Date(Date(500, 8, 9), Date(1602, 1, 31))) == + PosInfInterval!Date(Date(500, 8, 9))); + assert(PosInfInterval!Date(Date(1996, 1, 2)).span(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == + PosInfInterval!Date(Date(1990, 7, 6))); + assert(PosInfInterval!Date(Date(1996, 1, 2)).span(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17))) == + PosInfInterval!Date(Date(1996, 1, 2))); + + assert(PosInfInterval!Date(Date(1996, 1, 2)).span(PosInfInterval!Date(Date(1990, 7, 6))) == + PosInfInterval!Date(Date(1990, 7, 6))); + assert(PosInfInterval!Date(Date(1996, 1, 2)).span(PosInfInterval!Date(Date(1999, 1, 12))) == + PosInfInterval!Date(Date(1996, 1, 2))); +} + +//Test PosInfInterval's shift(). +@safe unittest +{ + import std.datetime.date; + + auto interval = PosInfInterval!Date(Date(2010, 7, 4)); + + static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__) + { + interval.shift(duration); + assert(interval == expected); + } + + testInterval(interval, dur!"days"(22), PosInfInterval!Date(Date(2010, 7, 26))); + testInterval(interval, dur!"days"(-22), PosInfInterval!Date(Date(2010, 6, 12))); + + const cInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iInterval = PosInfInterval!Date(Date(2010, 7, 4)); + static assert(!__traits(compiles, cInterval.shift(dur!"days"(5)))); + static assert(!__traits(compiles, iInterval.shift(dur!"days"(5)))); + + //Verify Examples. + auto interval1 = PosInfInterval!Date(Date(1996, 1, 2)); + auto interval2 = PosInfInterval!Date(Date(1996, 1, 2)); + + interval1.shift(dur!"days"(50)); + assert(interval1 == PosInfInterval!Date(Date(1996, 2, 21))); + + interval2.shift(dur!"days"(-50)); + assert(interval2 == PosInfInterval!Date(Date(1995, 11, 13))); +} + +//Test PosInfInterval's shift(int, int, AllowDayOverflow). +@safe unittest +{ + import std.datetime.date; + + { + auto interval = PosInfInterval!Date(Date(2010, 7, 4)); + + static void testInterval(I)(I interval, int years, int months, AllowDayOverflow allow, + in I expected, size_t line = __LINE__) + { + interval.shift(years, months, allow); + assert(interval == expected); + } + + testInterval(interval, 5, 0, AllowDayOverflow.yes, PosInfInterval!Date(Date(2015, 7, 4))); + testInterval(interval, -5, 0, AllowDayOverflow.yes, PosInfInterval!Date(Date(2005, 7, 4))); + + auto interval2 = PosInfInterval!Date(Date(2000, 1, 29)); + + testInterval(interval2, 1, 1, AllowDayOverflow.yes, PosInfInterval!Date(Date(2001, 3, 1))); + testInterval(interval2, 1, -1, AllowDayOverflow.yes, PosInfInterval!Date(Date(2000, 12, 29))); + testInterval(interval2, -1, -1, AllowDayOverflow.yes, PosInfInterval!Date(Date(1998, 12, 29))); + testInterval(interval2, -1, 1, AllowDayOverflow.yes, PosInfInterval!Date(Date(1999, 3, 1))); + + testInterval(interval2, 1, 1, AllowDayOverflow.no, PosInfInterval!Date(Date(2001, 2, 28))); + testInterval(interval2, 1, -1, AllowDayOverflow.no, PosInfInterval!Date(Date(2000, 12, 29))); + testInterval(interval2, -1, -1, AllowDayOverflow.no, PosInfInterval!Date(Date(1998, 12, 29))); + testInterval(interval2, -1, 1, AllowDayOverflow.no, PosInfInterval!Date(Date(1999, 2, 28))); + } + + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + static assert(!__traits(compiles, cPosInfInterval.shift(1))); + static assert(!__traits(compiles, iPosInfInterval.shift(1))); + + //Verify Examples. + auto interval1 = PosInfInterval!Date(Date(1996, 1, 2)); + auto interval2 = PosInfInterval!Date(Date(1996, 1, 2)); + + interval1.shift(2); + assert(interval1 == PosInfInterval!Date(Date(1998, 1, 2))); + + interval2.shift(-2); + assert(interval2 == PosInfInterval!Date(Date(1994, 1, 2))); +} + +//Test PosInfInterval's expand(). +@safe unittest +{ + import std.datetime.date; + + auto interval = PosInfInterval!Date(Date(2000, 7, 4)); + + static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__) + { + interval.expand(duration); + assert(interval == expected); + } + + testInterval(interval, dur!"days"(22), PosInfInterval!Date(Date(2000, 6, 12))); + testInterval(interval, dur!"days"(-22), PosInfInterval!Date(Date(2000, 7, 26))); + + const cInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iInterval = PosInfInterval!Date(Date(2010, 7, 4)); + static assert(!__traits(compiles, cInterval.expand(dur!"days"(5)))); + static assert(!__traits(compiles, iInterval.expand(dur!"days"(5)))); + + //Verify Examples. + auto interval1 = PosInfInterval!Date(Date(1996, 1, 2)); + auto interval2 = PosInfInterval!Date(Date(1996, 1, 2)); + + interval1.expand(dur!"days"(2)); + assert(interval1 == PosInfInterval!Date(Date(1995, 12, 31))); + + interval2.expand(dur!"days"(-2)); + assert(interval2 == PosInfInterval!Date(Date(1996, 1, 4))); +} + +//Test PosInfInterval's expand(int, int, AllowDayOverflow). +@safe unittest +{ + import std.datetime.date; + + { + auto interval = PosInfInterval!Date(Date(2000, 7, 4)); + + static void testInterval(I)(I interval, int years, int months, AllowDayOverflow allow, + in I expected, size_t line = __LINE__) + { + interval.expand(years, months, allow); + assert(interval == expected); + } + + testInterval(interval, 5, 0, AllowDayOverflow.yes, PosInfInterval!Date(Date(1995, 7, 4))); + testInterval(interval, -5, 0, AllowDayOverflow.yes, PosInfInterval!Date(Date(2005, 7, 4))); + + auto interval2 = PosInfInterval!Date(Date(2000, 1, 29)); + + testInterval(interval2, 1, 1, AllowDayOverflow.yes, PosInfInterval!Date(Date(1998, 12, 29))); + testInterval(interval2, 1, -1, AllowDayOverflow.yes, PosInfInterval!Date(Date(1999, 3, 1))); + testInterval(interval2, -1, -1, AllowDayOverflow.yes, PosInfInterval!Date(Date(2001, 3, 1))); + testInterval(interval2, -1, 1, AllowDayOverflow.yes, PosInfInterval!Date(Date(2000, 12, 29))); + + testInterval(interval2, 1, 1, AllowDayOverflow.no, PosInfInterval!Date(Date(1998, 12, 29))); + testInterval(interval2, 1, -1, AllowDayOverflow.no, PosInfInterval!Date(Date(1999, 2, 28))); + testInterval(interval2, -1, -1, AllowDayOverflow.no, PosInfInterval!Date(Date(2001, 2, 28))); + testInterval(interval2, -1, 1, AllowDayOverflow.no, PosInfInterval!Date(Date(2000, 12, 29))); + } + + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + static assert(!__traits(compiles, cPosInfInterval.expand(1))); + static assert(!__traits(compiles, iPosInfInterval.expand(1))); + + //Verify Examples. + auto interval1 = PosInfInterval!Date(Date(1996, 1, 2)); + auto interval2 = PosInfInterval!Date(Date(1996, 1, 2)); + + interval1.expand(2); + assert(interval1 == PosInfInterval!Date(Date(1994, 1, 2))); + + interval2.expand(-2); + assert(interval2 == PosInfInterval!Date(Date(1998, 1, 2))); +} + +//Test PosInfInterval's fwdRange(). +@system unittest +{ + import std.datetime.date; + + auto posInfInterval = PosInfInterval!Date(Date(2010, 9, 19)); + + static void testInterval(PosInfInterval!Date posInfInterval) + { + posInfInterval.fwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)).popFront(); + } + + assertThrown!DateTimeException(testInterval(posInfInterval)); + + assert(PosInfInterval!Date(Date(2010, 9, 12)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).front == + Date(2010, 9, 12)); + + assert(PosInfInterval!Date(Date(2010, 9, 12)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri), PopFirst.yes).front == + Date(2010, 9, 17)); + + //Verify Examples. + auto interval = PosInfInterval!Date(Date(2010, 9, 1)); + auto func = delegate (in Date date) + { + if ((date.day & 1) == 0) + return date + dur!"days"(2); + return date + dur!"days"(1); + }; + auto range = interval.fwdRange(func); + + assert(range.front == Date(2010, 9, 1)); //An odd day. Using PopFirst.yes would have made this Date(2010, 9, 2). + + range.popFront(); + assert(range.front == Date(2010, 9, 2)); + + range.popFront(); + assert(range.front == Date(2010, 9, 4)); + + range.popFront(); + assert(range.front == Date(2010, 9, 6)); + + range.popFront(); + assert(range.front == Date(2010, 9, 8)); + + range.popFront(); + assert(!range.empty); + + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + assert(!cPosInfInterval.fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).empty); + assert(!iPosInfInterval.fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)).empty); +} + +//Test PosInfInterval's toString(). +@safe unittest +{ + import std.datetime.date; + assert(PosInfInterval!Date(Date(2010, 7, 4)).toString() == "[2010-Jul-04 - ∞)"); + + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + assert(cPosInfInterval.toString()); + assert(iPosInfInterval.toString()); +} + + +/++ + Represents an interval of time which has negative infinity as its starting + point. + + Any ranges which iterate over a $(D NegInfInterval) are infinite. So, the + main purpose of using $(D NegInfInterval) is to create an infinite range + which starts at negative infinity and goes to a fixed end point. + Iterate over it in reverse. + +/ +struct NegInfInterval(TP) +{ +public: + + /++ + Params: + end = The time point which ends the interval. + + Example: +-------------------- +auto interval = PosInfInterval!Date(Date(1996, 1, 2)); +-------------------- + +/ + this(in TP end) pure nothrow + { + _end = cast(TP) end; + } + + + /++ + Params: + rhs = The $(D NegInfInterval) to assign to this one. + +/ + ref NegInfInterval opAssign(const ref NegInfInterval rhs) pure nothrow + { + _end = cast(TP) rhs._end; + return this; + } + + + /++ + Params: + rhs = The $(D NegInfInterval) to assign to this one. + +/ + ref NegInfInterval opAssign(NegInfInterval rhs) pure nothrow + { + _end = cast(TP) rhs._end; + return this; + } + + + /++ + The end point of the interval. It is excluded from the interval. + + Example: +-------------------- +assert(NegInfInterval!Date(Date(2012, 3, 1)).end == Date(2012, 3, 1)); +-------------------- + +/ + @property TP end() const pure nothrow + { + return cast(TP)_end; + } + + + /++ + The end point of the interval. It is excluded from the interval. + + Params: + timePoint = The time point to set end to. + +/ + @property void end(TP timePoint) pure nothrow + { + _end = timePoint; + } + + + /++ + Whether the interval's length is 0. Always returns false. + + Example: +-------------------- +assert(!NegInfInterval!Date(Date(1996, 1, 2)).empty); +-------------------- + +/ + enum bool empty = false; + + + /++ + Whether the given time point is within this interval. + + Params: + timePoint = The time point to check for inclusion in this interval. + + Example: +-------------------- +assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(Date(1994, 12, 24))); +assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(Date(2000, 1, 5))); +assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains(Date(2012, 3, 1))); +-------------------- + +/ + bool contains(TP timePoint) const pure nothrow + { + return timePoint < _end; + } + + + /++ + Whether the given interval is completely within this interval. + + Params: + interval = The interval to check for inclusion in this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given interval + is empty. + + Example: +-------------------- +assert(NegInfInterval!Date(Date(2012, 3, 1)).contains( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + +assert(NegInfInterval!Date(Date(2012, 3, 1)).contains( + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + +assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains( + Interval!Date(Date(1998, 2, 28), Date(2013, 5, 1)))); +-------------------- + +/ + bool contains(in Interval!TP interval) const pure + { + interval._enforceNotEmpty(); + return interval._end <= _end; + } + + + /++ + Whether the given interval is completely within this interval. + + Always returns false because an interval beginning at negative + infinity can never contain an interval going to positive infinity. + + Params: + interval = The interval to check for inclusion in this interval. + + Example: +-------------------- +assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains( + PosInfInterval!Date(Date(1999, 5, 4)))); +-------------------- + +/ + bool contains(in PosInfInterval!TP interval) const pure nothrow + { + return false; + } + + + /++ + Whether the given interval is completely within this interval. + + Params: + interval = The interval to check for inclusion in this interval. + + Example: +-------------------- +assert(NegInfInterval!Date(Date(2012, 3, 1)).contains( + NegInfInterval!Date(Date(1996, 5, 4)))); + +assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains( + NegInfInterval!Date(Date(2013, 7, 9)))); +-------------------- + +/ + bool contains(in NegInfInterval interval) const pure nothrow + { + return interval._end <= _end; + } + + + /++ + Whether this interval is before the given time point. + + Params: + timePoint = The time point to check whether this interval is + before it. + + Example: +-------------------- +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Date(1994, 12, 24))); +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Date(2000, 1, 5))); +assert(NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Date(2012, 3, 1))); +-------------------- + +/ + bool isBefore(in TP timePoint) const pure nothrow + { + return timePoint >= _end; + } + + + /++ + Whether this interval is before the given interval and does not + intersect it. + + Params: + interval = The interval to check for against this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given interval + is empty + + Example: +-------------------- +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore( + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + +assert(NegInfInterval!Date(Date(2012, 3, 1)).isBefore( + Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3)))); +-------------------- + +/ + bool isBefore(in Interval!TP interval) const pure + { + interval._enforceNotEmpty(); + return _end <= interval._begin; + } + + + /++ + Whether this interval is before the given interval and does not + intersect it. + + Params: + interval = The interval to check for against this interval. + + Example: +-------------------- +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore( + PosInfInterval!Date(Date(1999, 5, 4)))); + +assert(NegInfInterval!Date(Date(2012, 3, 1)).isBefore( + PosInfInterval!Date(Date(2012, 3, 1)))); +-------------------- + +/ + bool isBefore(in PosInfInterval!TP interval) const pure nothrow + { + return _end <= interval._begin; + } + + + /++ + Whether this interval is before the given interval and does not + intersect it. + + Always returns false because an interval beginning at negative + infinity can never be before another interval beginning at negative + infinity. + + Params: + interval = The interval to check for against this interval. + + Example: +-------------------- +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore( + NegInfInterval!Date(Date(1996, 5, 4)))); + +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore( + NegInfInterval!Date(Date(2013, 7, 9)))); +-------------------- + +/ + bool isBefore(in NegInfInterval interval) const pure nothrow + { + return false; + } + + + /++ + Whether this interval is after the given time point. + + Always returns false because an interval beginning at negative infinity + can never be after any time point. + + Params: + timePoint = The time point to check whether this interval is after + it. + + Example: +-------------------- +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Date(1994, 12, 24))); +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Date(2000, 1, 5))); +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Date(2012, 3, 1))); +-------------------- + +/ + bool isAfter(in TP timePoint) const pure nothrow + { + return false; + } + + + /++ + Whether this interval is after the given interval and does not + intersect it. + + Always returns false (unless the given interval is empty) because an + interval beginning at negative infinity can never be after any other + interval. + + Params: + interval = The interval to check against this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given interval + is empty. + + Example: +-------------------- +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter( + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter( + Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3)))); +-------------------- + +/ + bool isAfter(in Interval!TP interval) const pure + { + interval._enforceNotEmpty(); + return false; + } + + + /++ + Whether this interval is after the given interval and does not intersect + it. + + Always returns false because an interval beginning at negative infinity + can never be after any other interval. + + Params: + interval = The interval to check against this interval. + + Example: +-------------------- +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter( + PosInfInterval!Date(Date(1999, 5, 4)))); + +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter( + PosInfInterval!Date(Date(2012, 3, 1)))); +-------------------- + +/ + bool isAfter(in PosInfInterval!TP interval) const pure nothrow + { + return false; + } + + + /++ + Whether this interval is after the given interval and does not intersect + it. + + Always returns false because an interval beginning at negative infinity + can never be after any other interval. + + Params: + interval = The interval to check against this interval. + + Example: +-------------------- +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter( + NegInfInterval!Date(Date(1996, 5, 4)))); + +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter( + NegInfInterval!Date(Date(2013, 7, 9)))); +-------------------- + +/ + bool isAfter(in NegInfInterval interval) const pure nothrow + { + return false; + } + + + /++ + Whether the given interval overlaps this interval. + + Params: + interval = The interval to check for intersection with this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given interval + is empty. + + Example: +-------------------- +assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + +assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects( + Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + +assert(!NegInfInterval!Date(Date(2012, 3, 1)).intersects( + Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3)))); +-------------------- + +/ + bool intersects(in Interval!TP interval) const pure + { + interval._enforceNotEmpty(); + return interval._begin < _end; + } + + + /++ + Whether the given interval overlaps this interval. + + Params: + interval = The interval to check for intersection with this + interval. + + Example: +-------------------- +assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects( + PosInfInterval!Date(Date(1999, 5, 4)))); + +assert(!NegInfInterval!Date(Date(2012, 3, 1)).intersects( + PosInfInterval!Date(Date(2012, 3, 1)))); +-------------------- + +/ + bool intersects(in PosInfInterval!TP interval) const pure nothrow + { + return interval._begin < _end; + } + + + /++ + Whether the given interval overlaps this interval. + + Always returns true because two intervals beginning at negative infinity + always overlap. + + Params: + interval = The interval to check for intersection with this interval. + + Example: +-------------------- +assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects( + NegInfInterval!Date(Date(1996, 5, 4)))); + +assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects( + NegInfInterval!Date(Date(2013, 7, 9)))); +-------------------- + +/ + bool intersects(in NegInfInterval!TP interval) const pure nothrow + { + return true; + } + + + /++ + Returns the intersection of two intervals + + Params: + interval = The interval to intersect with this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the two intervals do + not intersect or if the given interval is empty. + + Example: +-------------------- +assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == + Interval!Date(Date(1990, 7 , 6), Date(2000, 8, 2))); + +assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection( + Interval!Date(Date(1999, 1, 12), Date(2015, 9, 2))) == + Interval!Date(Date(1999, 1 , 12), Date(2012, 3, 1))); +-------------------- + +/ + Interval!TP intersection(in Interval!TP interval) const + { + import std.format : format; + + enforce(this.intersects(interval), + new DateTimeException(format("%s and %s do not intersect.", this, interval))); + + auto end = _end < interval._end ? _end : interval._end; + + return Interval!TP(interval._begin, end); + } + + + /++ + Returns the intersection of two intervals + + Params: + interval = The interval to intersect with this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the two intervals do + not intersect. + + Example: +-------------------- +assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection( + PosInfInterval!Date(Date(1990, 7, 6))) == + Interval!Date(Date(1990, 7 , 6), Date(2012, 3, 1))); + +assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection( + PosInfInterval!Date(Date(1999, 1, 12))) == + Interval!Date(Date(1999, 1 , 12), Date(2012, 3, 1))); +-------------------- + +/ + Interval!TP intersection(in PosInfInterval!TP interval) const + { + import std.format : format; + + enforce(this.intersects(interval), + new DateTimeException(format("%s and %s do not intersect.", this, interval))); + + return Interval!TP(interval._begin, _end); + } + + + /++ + Returns the intersection of two intervals + + Params: + interval = The interval to intersect with this interval. + + Example: +-------------------- +assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection( + NegInfInterval!Date(Date(1999, 7, 6))) == + NegInfInterval!Date(Date(1999, 7 , 6))); + +assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection( + NegInfInterval!Date(Date(2013, 1, 12))) == + NegInfInterval!Date(Date(2012, 3 , 1))); +-------------------- + +/ + NegInfInterval intersection(in NegInfInterval interval) const nothrow + { + return NegInfInterval(_end < interval._end ? _end : interval._end); + } + + + /++ + Whether the given interval is adjacent to this interval. + + Params: + interval = The interval to check whether its adjecent to this + interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given interval + is empty. + + Example: +-------------------- +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent( + Interval!Date(Date(1999, 1, 12), Date(2012, 3, 1)))); + +assert(NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent( + Interval!Date(Date(2012, 3, 1), Date(2019, 2, 2)))); + +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent( + Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3)))); +-------------------- + +/ + bool isAdjacent(in Interval!TP interval) const pure + { + interval._enforceNotEmpty(); + return interval._begin == _end; + } + + + /++ + Whether the given interval is adjacent to this interval. + + Params: + interval = The interval to check whether its adjecent to this + interval. + + Example: +-------------------- +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent( + PosInfInterval!Date(Date(1999, 5, 4)))); + +assert(NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent( + PosInfInterval!Date(Date(2012, 3, 1)))); +-------------------- + +/ + bool isAdjacent(in PosInfInterval!TP interval) const pure nothrow + { + return interval._begin == _end; + } + + + /++ + Whether the given interval is adjacent to this interval. + + Always returns false because two intervals beginning at negative + infinity can never be adjacent to one another. + + Params: + interval = The interval to check whether its adjecent to this + interval. + + Example: +-------------------- +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent( + NegInfInterval!Date(Date(1996, 5, 4)))); + +assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent( + NegInfInterval!Date(Date(2012, 3, 1)))); +-------------------- + +/ + bool isAdjacent(in NegInfInterval interval) const pure nothrow + { + return false; + } + + + /++ + Returns the union of two intervals + + Params: + interval = The interval to merge with this interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the two intervals do + not intersect and are not adjacent or if the given interval is empty. + + Note: + There is no overload for $(D merge) which takes a + $(D PosInfInterval), because an interval + going from negative infinity to positive infinity + is not possible. + + Example: +-------------------- +assert(NegInfInterval!Date(Date(2012, 3, 1)).merge( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == + NegInfInterval!Date(Date(2012, 3 , 1))); + +assert(NegInfInterval!Date(Date(2012, 3, 1)).merge( + Interval!Date(Date(1999, 1, 12), Date(2015, 9, 2))) == + NegInfInterval!Date(Date(2015, 9 , 2))); +-------------------- + +/ + NegInfInterval merge(in Interval!TP interval) const + { + import std.format : format; + + enforce(this.isAdjacent(interval) || this.intersects(interval), + new DateTimeException(format("%s and %s are not adjacent and do not intersect.", this, interval))); + + return NegInfInterval(_end > interval._end ? _end : interval._end); + } + + + /++ + Returns the union of two intervals + + Params: + interval = The interval to merge with this interval. + + Note: + There is no overload for $(D merge) which takes a + $(D PosInfInterval), because an interval + going from negative infinity to positive infinity + is not possible. + + Example: +-------------------- +assert(NegInfInterval!Date(Date(2012, 3, 1)).merge( + NegInfInterval!Date(Date(1999, 7, 6))) == + NegInfInterval!Date(Date(2012, 3 , 1))); + +assert(NegInfInterval!Date(Date(2012, 3, 1)).merge( + NegInfInterval!Date(Date(2013, 1, 12))) == + NegInfInterval!Date(Date(2013, 1 , 12))); +-------------------- + +/ + NegInfInterval merge(in NegInfInterval interval) const pure nothrow + { + return NegInfInterval(_end > interval._end ? _end : interval._end); + } + + + /++ + Returns an interval that covers from the earliest time point of two + intervals up to (but not including) the latest time point of two + intervals. + + Params: + interval = The interval to create a span together with this + interval. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given interval + is empty. + + Note: + There is no overload for $(D span) which takes a + $(D PosInfInterval), because an interval + going from negative infinity to positive infinity + is not possible. + + Example: +-------------------- +assert(NegInfInterval!Date(Date(2012, 3, 1)).span( + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == + NegInfInterval!Date(Date(2012, 3 , 1))); + +assert(NegInfInterval!Date(Date(2012, 3, 1)).span( + Interval!Date(Date(1999, 1, 12), Date(2015, 9, 2))) == + NegInfInterval!Date(Date(2015, 9 , 2))); + +assert(NegInfInterval!Date(Date(1600, 1, 7)).span( + Interval!Date(Date(2012, 3, 11), Date(2017, 7, 1))) == + NegInfInterval!Date(Date(2017, 7 , 1))); +-------------------- + +/ + NegInfInterval span(in Interval!TP interval) const pure + { + interval._enforceNotEmpty(); + return NegInfInterval(_end > interval._end ? _end : interval._end); + } + + + /++ + Returns an interval that covers from the earliest time point of two + intervals up to (but not including) the latest time point of two + intervals. + + Params: + interval = The interval to create a span together with this + interval. + + Note: + There is no overload for $(D span) which takes a + $(D PosInfInterval), because an interval + going from negative infinity to positive infinity + is not possible. + + Example: +-------------------- +assert(NegInfInterval!Date(Date(2012, 3, 1)).span( + NegInfInterval!Date(Date(1999, 7, 6))) == + NegInfInterval!Date(Date(2012, 3 , 1))); + +assert(NegInfInterval!Date(Date(2012, 3, 1)).span( + NegInfInterval!Date(Date(2013, 1, 12))) == + NegInfInterval!Date(Date(2013, 1 , 12))); +-------------------- + +/ + NegInfInterval span(in NegInfInterval interval) const pure nothrow + { + return NegInfInterval(_end > interval._end ? _end : interval._end); + } + + + /++ + Shifts the $(D end) of this interval forward or backwards in time by the + given duration (a positive duration shifts the interval forward; a + negative duration shifts it backward). Effectively, it does + $(D end += duration). + + Params: + duration = The duration to shift the interval by. + + Example: +-------------------- +auto interval1 = NegInfInterval!Date(Date(2012, 4, 5)); +auto interval2 = NegInfInterval!Date(Date(2012, 4, 5)); + +interval1.shift(dur!"days"(50)); +assert(interval1 == NegInfInterval!Date(Date(2012, 5, 25))); + +interval2.shift(dur!"days"(-50)); +assert(interval2 == NegInfInterval!Date( Date(2012, 2, 15))); +-------------------- + +/ + void shift(D)(D duration) pure nothrow + if (__traits(compiles, end + duration)) + { + _end += duration; + } + + + static if (__traits(compiles, end.add!"months"(1)) && + __traits(compiles, end.add!"years"(1))) + { + /++ + Shifts the $(D end) of this interval forward or backwards in time by + the given number of years and/or months (a positive number of years + and months shifts the interval forward; a negative number shifts it + backward). It adds the years the given years and months to end. It + effectively calls $(D add!"years"()) and then $(D add!"months"()) + on end with the given number of years and months. + + Params: + years = The number of years to shift the interval by. + months = The number of months to shift the interval by. + allowOverflow = Whether the days should be allowed to overflow + on $(D end), causing its month to increment. + + Throws: + $(REF DateTimeException,std,datetime,date) if empty is true or + if the resulting interval would be invalid. + + Example: +-------------------- +auto interval1 = NegInfInterval!Date(Date(2012, 3, 1)); +auto interval2 = NegInfInterval!Date(Date(2012, 3, 1)); + +interval1.shift(2); +assert(interval1 == NegInfInterval!Date(Date(2014, 3, 1))); + +interval2.shift(-2); +assert(interval2 == NegInfInterval!Date(Date(2010, 3, 1))); +-------------------- + +/ + void shift(T)(T years, T months = 0, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) + if (isIntegral!T) + { + auto end = _end; + + end.add!"years"(years, allowOverflow); + end.add!"months"(months, allowOverflow); + + _end = end; + } + } + + + /++ + Expands the interval forwards in time. Effectively, it does + $(D end += duration). + + Params: + duration = The duration to expand the interval by. + + Example: +-------------------- +auto interval1 = NegInfInterval!Date(Date(2012, 3, 1)); +auto interval2 = NegInfInterval!Date(Date(2012, 3, 1)); + +interval1.expand(dur!"days"(2)); +assert(interval1 == NegInfInterval!Date(Date(2012, 3, 3))); + +interval2.expand(dur!"days"(-2)); +assert(interval2 == NegInfInterval!Date(Date(2012, 2, 28))); +-------------------- + +/ + void expand(D)(D duration) pure nothrow + if (__traits(compiles, end + duration)) + { + _end += duration; + } + + + static if (__traits(compiles, end.add!"months"(1)) && + __traits(compiles, end.add!"years"(1))) + { + /++ + Expands the interval forwards and/or backwards in time. Effectively, + it adds the given number of months/years to end. + + Params: + years = The number of years to expand the interval by. + months = The number of months to expand the interval by. + allowOverflow = Whether the days should be allowed to overflow + on $(D end), causing their month to increment. + + Throws: + $(REF DateTimeException,std,datetime,date) if empty is true or + if the resulting interval would be invalid. + + Example: +-------------------- +auto interval1 = NegInfInterval!Date(Date(2012, 3, 1)); +auto interval2 = NegInfInterval!Date(Date(2012, 3, 1)); + +interval1.expand(2); +assert(interval1 == NegInfInterval!Date(Date(2014, 3, 1))); + +interval2.expand(-2); +assert(interval2 == NegInfInterval!Date(Date(2010, 3, 1))); +-------------------- + +/ + void expand(T)(T years, T months = 0, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) + if (isIntegral!T) + { + auto end = _end; + + end.add!"years"(years, allowOverflow); + end.add!"months"(months, allowOverflow); + + _end = end; + } + } + + + /++ + Returns a range which iterates backwards over the interval, starting + at $(D end), using $(D_PARAM func) to generate each successive time + point. + + The range's $(D front) is the interval's $(D end). $(D_PARAM func) is + used to generate the next $(D front) when $(D popFront) is called. If + $(D_PARAM popFirst) is $(D PopFirst.yes), then $(D popFront) is called + before the range is returned (so that $(D front) is a time point which + $(D_PARAM func) would generate). + + If $(D_PARAM func) ever generates a time point greater than or equal to + the current $(D front) of the range, then a + $(REF DateTimeException,std,datetime,date) will be thrown. + + There are helper functions in this module which generate common + delegates to pass to $(D bwdRange). Their documentation starts with + "Range-generating function," to make them easily searchable. + + Params: + func = The function used to generate the time points of the + range over the interval. + popFirst = Whether $(D popFront) should be called on the range + before returning it. + + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty. + + Warning: + $(D_PARAM func) must be logically pure. Ideally, $(D_PARAM func) + would be a function pointer to a pure function, but forcing + $(D_PARAM func) to be pure is far too restrictive to be useful, and + in order to have the ease of use of having functions which generate + functions to pass to $(D fwdRange), $(D_PARAM func) must be a + delegate. + + If $(D_PARAM func) retains state which changes as it is called, then + some algorithms will not work correctly, because the range's + $(D save) will have failed to have really saved the range's state. + To avoid such bugs, don't pass a delegate which is + not logically pure to $(D fwdRange). If $(D_PARAM func) is given the + same time point with two different calls, it must return the same + result both times. + + Of course, none of the functions in this module have this problem, + so it's only relevant for custom delegates. + + Example: +-------------------- +auto interval = NegInfInterval!Date(Date(2010, 9, 9)); +auto func = delegate (in Date date) //For iterating over even-numbered days. + { + if ((date.day & 1) == 0) + return date - dur!"days"(2); + + return date - dur!"days"(1); + }; +auto range = interval.bwdRange(func); + +assert(range.front == Date(2010, 9, 9)); //An odd day. Using PopFirst.yes would have made this Date(2010, 9, 8). + +range.popFront(); +assert(range.front == Date(2010, 9, 8)); + +range.popFront(); +assert(range.front == Date(2010, 9, 6)); + +range.popFront(); +assert(range.front == Date(2010, 9, 4)); + +range.popFront(); +assert(range.front == Date(2010, 9, 2)); + +range.popFront(); +assert(!range.empty); +-------------------- + +/ + NegInfIntervalRange!(TP) bwdRange(TP delegate(in TP) func, PopFirst popFirst = PopFirst.no) const + { + auto range = NegInfIntervalRange!(TP)(this, func); + + if (popFirst == PopFirst.yes) + range.popFront(); + + return range; + } + + + /+ + Converts this interval to a string. + +/ + //Due to bug http://d.puremagic.com/issues/show_bug.cgi?id=3715 , we can't + //have versions of toString() with extra modifiers, so we define one version + //with modifiers and one without. + string toString() + { + return _toStringImpl(); + } + + + /++ + Converts this interval to a string. + +/ + //Due to bug http://d.puremagic.com/issues/show_bug.cgi?id=3715 , we can't + //have versions of toString() with extra modifiers, so we define one version + //with modifiers and one without. + string toString() const nothrow + { + return _toStringImpl(); + } + +private: + + /+ + Since we have two versions of toString(), we have _toStringImpl() + so that they can share implementations. + +/ + string _toStringImpl() const nothrow + { + import std.format : format; + try + return format("[-∞ - %s)", _end); + catch (Exception e) + assert(0, "format() threw."); + } + + + TP _end; +} + +//Test NegInfInterval's constructor. +@safe unittest +{ + import std.datetime.date; + import std.datetime.systime; + + NegInfInterval!Date(Date.init); + NegInfInterval!TimeOfDay(TimeOfDay.init); + NegInfInterval!DateTime(DateTime.init); + NegInfInterval!SysTime(SysTime(0)); +} + +//Test NegInfInterval's end. +@safe unittest +{ + import std.datetime.date; + + assert(NegInfInterval!Date(Date(2010, 1, 1)).end == Date(2010, 1, 1)); + assert(NegInfInterval!Date(Date(2010, 1, 1)).end == Date(2010, 1, 1)); + assert(NegInfInterval!Date(Date(1998, 1, 1)).end == Date(1998, 1, 1)); + + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(cNegInfInterval.end != Date.init); + assert(iNegInfInterval.end != Date.init); + + //Verify Examples. + assert(NegInfInterval!Date(Date(2012, 3, 1)).end == Date(2012, 3, 1)); +} + +//Test NegInfInterval's empty. +@safe unittest +{ + import std.datetime.date; + import std.datetime.systime; + + assert(!NegInfInterval!Date(Date(2010, 1, 1)).empty); + assert(!NegInfInterval!TimeOfDay(TimeOfDay(0, 30, 0)).empty); + assert(!NegInfInterval!DateTime(DateTime(2010, 1, 1, 0, 30, 0)).empty); + assert(!NegInfInterval!SysTime(SysTime(DateTime(2010, 1, 1, 0, 30, 0))).empty); + + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!cNegInfInterval.empty); + assert(!iNegInfInterval.empty); + + //Verify Examples. + assert(!NegInfInterval!Date(Date(1996, 1, 2)).empty); +} + +//Test NegInfInterval's contains(time point). +@safe unittest +{ + import std.datetime.date; + + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + + assert(negInfInterval.contains(Date(2009, 7, 4))); + assert(negInfInterval.contains(Date(2010, 7, 3))); + assert(negInfInterval.contains(Date(2010, 7, 4))); + assert(negInfInterval.contains(Date(2010, 7, 5))); + assert(negInfInterval.contains(Date(2011, 7, 1))); + assert(negInfInterval.contains(Date(2012, 1, 6))); + assert(!negInfInterval.contains(Date(2012, 1, 7))); + assert(!negInfInterval.contains(Date(2012, 1, 8))); + assert(!negInfInterval.contains(Date(2013, 1, 7))); + + const cdate = Date(2010, 7, 6); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(negInfInterval.contains(cdate)); + assert(cNegInfInterval.contains(cdate)); + assert(iNegInfInterval.contains(cdate)); + + //Verify Examples. + assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(Date(1994, 12, 24))); + assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(Date(2000, 1, 5))); + assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains(Date(2012, 3, 1))); +} + +//Test NegInfInterval's contains(Interval). +@safe unittest +{ + import std.datetime.date; + + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + + static void testInterval(in NegInfInterval!Date negInfInterval, in Interval!Date interval) + { + negInfInterval.contains(interval); + } + + assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assert(negInfInterval.contains(negInfInterval)); + assert(negInfInterval.contains(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)))); + assert(!negInfInterval.contains(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)))); + assert(negInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)))); + assert(negInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)))); + assert(negInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)))); + assert(!negInfInterval.contains(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)))); + assert(negInfInterval.contains(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)))); + assert(negInfInterval.contains(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)))); + assert(negInfInterval.contains(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)))); + assert(!negInfInterval.contains(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)))); + assert(!negInfInterval.contains(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)))); + assert(!negInfInterval.contains(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)))); + + assert(negInfInterval.contains(NegInfInterval!Date(Date(2010, 7, 3)))); + assert(negInfInterval.contains(NegInfInterval!Date(Date(2010, 7, 4)))); + assert(negInfInterval.contains(NegInfInterval!Date(Date(2010, 7, 5)))); + assert(negInfInterval.contains(NegInfInterval!Date(Date(2012, 1, 6)))); + assert(negInfInterval.contains(NegInfInterval!Date(Date(2012, 1, 7)))); + assert(!negInfInterval.contains(NegInfInterval!Date(Date(2012, 1, 8)))); + + assert(!NegInfInterval!Date(Date(2010, 7, 3)).contains(negInfInterval)); + assert(!NegInfInterval!Date(Date(2010, 7, 4)).contains(negInfInterval)); + assert(!NegInfInterval!Date(Date(2010, 7, 5)).contains(negInfInterval)); + assert(!NegInfInterval!Date(Date(2012, 1, 6)).contains(negInfInterval)); + assert(NegInfInterval!Date(Date(2012, 1, 7)).contains(negInfInterval)); + assert(NegInfInterval!Date(Date(2012, 1, 8)).contains(negInfInterval)); + + assert(!negInfInterval.contains(PosInfInterval!Date(Date(2010, 7, 3)))); + assert(!negInfInterval.contains(PosInfInterval!Date(Date(2010, 7, 4)))); + assert(!negInfInterval.contains(PosInfInterval!Date(Date(2010, 7, 5)))); + assert(!negInfInterval.contains(PosInfInterval!Date(Date(2012, 1, 6)))); + assert(!negInfInterval.contains(PosInfInterval!Date(Date(2012, 1, 7)))); + assert(!negInfInterval.contains(PosInfInterval!Date(Date(2012, 1, 8)))); + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(negInfInterval.contains(interval)); + assert(negInfInterval.contains(cInterval)); + assert(negInfInterval.contains(iInterval)); + assert(!negInfInterval.contains(posInfInterval)); + assert(!negInfInterval.contains(cPosInfInterval)); + assert(!negInfInterval.contains(iPosInfInterval)); + assert(negInfInterval.contains(negInfInterval)); + assert(negInfInterval.contains(cNegInfInterval)); + assert(negInfInterval.contains(iNegInfInterval)); + assert(cNegInfInterval.contains(interval)); + assert(cNegInfInterval.contains(cInterval)); + assert(cNegInfInterval.contains(iInterval)); + assert(!cNegInfInterval.contains(posInfInterval)); + assert(!cNegInfInterval.contains(cPosInfInterval)); + assert(!cNegInfInterval.contains(iPosInfInterval)); + assert(cNegInfInterval.contains(negInfInterval)); + assert(cNegInfInterval.contains(cNegInfInterval)); + assert(cNegInfInterval.contains(iNegInfInterval)); + assert(iNegInfInterval.contains(interval)); + assert(iNegInfInterval.contains(cInterval)); + assert(iNegInfInterval.contains(iInterval)); + assert(!iNegInfInterval.contains(posInfInterval)); + assert(!iNegInfInterval.contains(cPosInfInterval)); + assert(!iNegInfInterval.contains(iPosInfInterval)); + assert(iNegInfInterval.contains(negInfInterval)); + assert(iNegInfInterval.contains(cNegInfInterval)); + assert(iNegInfInterval.contains(iNegInfInterval)); + + //Verify Examples. + assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains(Interval!Date(Date(1998, 2, 28), Date(2013, 5, 1)))); + + assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains(PosInfInterval!Date(Date(1999, 5, 4)))); + + assert(NegInfInterval!Date(Date(2012, 3, 1)).contains(NegInfInterval!Date(Date(1996, 5, 4)))); + assert(!NegInfInterval!Date(Date(2012, 3, 1)).contains(NegInfInterval!Date(Date(2013, 7, 9)))); +} + +//Test NegInfInterval's isBefore(time point). +@safe unittest +{ + import std.datetime.date; + + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + + assert(!negInfInterval.isBefore(Date(2009, 7, 4))); + assert(!negInfInterval.isBefore(Date(2010, 7, 3))); + assert(!negInfInterval.isBefore(Date(2010, 7, 4))); + assert(!negInfInterval.isBefore(Date(2010, 7, 5))); + assert(!negInfInterval.isBefore(Date(2011, 7, 1))); + assert(!negInfInterval.isBefore(Date(2012, 1, 6))); + assert(negInfInterval.isBefore(Date(2012, 1, 7))); + assert(negInfInterval.isBefore(Date(2012, 1, 8))); + assert(negInfInterval.isBefore(Date(2013, 1, 7))); + + const cdate = Date(2010, 7, 6); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!negInfInterval.isBefore(cdate)); + assert(!cNegInfInterval.isBefore(cdate)); + assert(!iNegInfInterval.isBefore(cdate)); + + //Verify Examples. + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Date(1994, 12, 24))); + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Date(2000, 1, 5))); + assert(NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Date(2012, 3, 1))); +} + +//Test NegInfInterval's isBefore(Interval). +@safe unittest +{ + import std.datetime.date; + + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + + static void testInterval(in NegInfInterval!Date negInfInterval, in Interval!Date interval) + { + negInfInterval.isBefore(interval); + } + + assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assert(!negInfInterval.isBefore(negInfInterval)); + assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)))); + assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)))); + assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)))); + assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)))); + assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)))); + assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)))); + assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)))); + assert(!negInfInterval.isBefore(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)))); + assert(!negInfInterval.isBefore(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)))); + assert(!negInfInterval.isBefore(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)))); + assert(negInfInterval.isBefore(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)))); + assert(negInfInterval.isBefore(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)))); + + assert(!negInfInterval.isBefore(NegInfInterval!Date(Date(2010, 7, 3)))); + assert(!negInfInterval.isBefore(NegInfInterval!Date(Date(2010, 7, 4)))); + assert(!negInfInterval.isBefore(NegInfInterval!Date(Date(2010, 7, 5)))); + assert(!negInfInterval.isBefore(NegInfInterval!Date(Date(2012, 1, 6)))); + assert(!negInfInterval.isBefore(NegInfInterval!Date(Date(2012, 1, 7)))); + assert(!negInfInterval.isBefore(NegInfInterval!Date(Date(2012, 1, 8)))); + + assert(!NegInfInterval!Date(Date(2010, 7, 3)).isBefore(negInfInterval)); + assert(!NegInfInterval!Date(Date(2010, 7, 4)).isBefore(negInfInterval)); + assert(!NegInfInterval!Date(Date(2010, 7, 5)).isBefore(negInfInterval)); + assert(!NegInfInterval!Date(Date(2012, 1, 6)).isBefore(negInfInterval)); + assert(!NegInfInterval!Date(Date(2012, 1, 7)).isBefore(negInfInterval)); + assert(!NegInfInterval!Date(Date(2012, 1, 8)).isBefore(negInfInterval)); + + assert(!negInfInterval.isBefore(PosInfInterval!Date(Date(2010, 7, 3)))); + assert(!negInfInterval.isBefore(PosInfInterval!Date(Date(2010, 7, 4)))); + assert(!negInfInterval.isBefore(PosInfInterval!Date(Date(2010, 7, 5)))); + assert(!negInfInterval.isBefore(PosInfInterval!Date(Date(2012, 1, 6)))); + assert(negInfInterval.isBefore(PosInfInterval!Date(Date(2012, 1, 7)))); + assert(negInfInterval.isBefore(PosInfInterval!Date(Date(2012, 1, 8)))); + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!negInfInterval.isBefore(interval)); + assert(!negInfInterval.isBefore(cInterval)); + assert(!negInfInterval.isBefore(iInterval)); + assert(!negInfInterval.isBefore(posInfInterval)); + assert(!negInfInterval.isBefore(cPosInfInterval)); + assert(!negInfInterval.isBefore(iPosInfInterval)); + assert(!negInfInterval.isBefore(negInfInterval)); + assert(!negInfInterval.isBefore(cNegInfInterval)); + assert(!negInfInterval.isBefore(iNegInfInterval)); + assert(!cNegInfInterval.isBefore(interval)); + assert(!cNegInfInterval.isBefore(cInterval)); + assert(!cNegInfInterval.isBefore(iInterval)); + assert(!cNegInfInterval.isBefore(posInfInterval)); + assert(!cNegInfInterval.isBefore(cPosInfInterval)); + assert(!cNegInfInterval.isBefore(iPosInfInterval)); + assert(!cNegInfInterval.isBefore(negInfInterval)); + assert(!cNegInfInterval.isBefore(cNegInfInterval)); + assert(!cNegInfInterval.isBefore(iNegInfInterval)); + assert(!iNegInfInterval.isBefore(interval)); + assert(!iNegInfInterval.isBefore(cInterval)); + assert(!iNegInfInterval.isBefore(iInterval)); + assert(!iNegInfInterval.isBefore(posInfInterval)); + assert(!iNegInfInterval.isBefore(cPosInfInterval)); + assert(!iNegInfInterval.isBefore(iPosInfInterval)); + assert(!iNegInfInterval.isBefore(negInfInterval)); + assert(!iNegInfInterval.isBefore(cNegInfInterval)); + assert(!iNegInfInterval.isBefore(iNegInfInterval)); + + //Verify Examples. + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + assert(NegInfInterval!Date(Date(2012, 3, 1)).isBefore(Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3)))); + + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(PosInfInterval!Date(Date(1999, 5, 4)))); + assert(NegInfInterval!Date(Date(2012, 3, 1)).isBefore(PosInfInterval!Date(Date(2012, 3, 1)))); + + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(NegInfInterval!Date(Date(1996, 5, 4)))); + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isBefore(NegInfInterval!Date(Date(2013, 7, 9)))); +} + +//Test NegInfInterval's isAfter(time point). +@safe unittest +{ + import std.datetime.date; + + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + + assert(!negInfInterval.isAfter(Date(2009, 7, 4))); + assert(!negInfInterval.isAfter(Date(2010, 7, 3))); + assert(!negInfInterval.isAfter(Date(2010, 7, 4))); + assert(!negInfInterval.isAfter(Date(2010, 7, 5))); + assert(!negInfInterval.isAfter(Date(2011, 7, 1))); + assert(!negInfInterval.isAfter(Date(2012, 1, 6))); + assert(!negInfInterval.isAfter(Date(2012, 1, 7))); + assert(!negInfInterval.isAfter(Date(2012, 1, 8))); + assert(!negInfInterval.isAfter(Date(2013, 1, 7))); + + const cdate = Date(2010, 7, 6); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!negInfInterval.isAfter(cdate)); + assert(!cNegInfInterval.isAfter(cdate)); + assert(!iNegInfInterval.isAfter(cdate)); +} + +//Test NegInfInterval's isAfter(Interval). +@safe unittest +{ + import std.datetime.date; + + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + + static void testInterval(in NegInfInterval!Date negInfInterval, in Interval!Date interval) + { + negInfInterval.isAfter(interval); + } + + assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assert(!negInfInterval.isAfter(negInfInterval)); + assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)))); + assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)))); + assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)))); + assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)))); + assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)))); + assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)))); + assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)))); + assert(!negInfInterval.isAfter(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)))); + assert(!negInfInterval.isAfter(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)))); + assert(!negInfInterval.isAfter(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)))); + assert(!negInfInterval.isAfter(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)))); + assert(!negInfInterval.isAfter(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)))); + + assert(!negInfInterval.isAfter(NegInfInterval!Date(Date(2010, 7, 3)))); + assert(!negInfInterval.isAfter(NegInfInterval!Date(Date(2010, 7, 4)))); + assert(!negInfInterval.isAfter(NegInfInterval!Date(Date(2010, 7, 5)))); + assert(!negInfInterval.isAfter(NegInfInterval!Date(Date(2012, 1, 6)))); + assert(!negInfInterval.isAfter(NegInfInterval!Date(Date(2012, 1, 7)))); + assert(!negInfInterval.isAfter(NegInfInterval!Date(Date(2012, 1, 8)))); + + assert(!NegInfInterval!Date(Date(2010, 7, 3)).isAfter(negInfInterval)); + assert(!NegInfInterval!Date(Date(2010, 7, 4)).isAfter(negInfInterval)); + assert(!NegInfInterval!Date(Date(2010, 7, 5)).isAfter(negInfInterval)); + assert(!NegInfInterval!Date(Date(2012, 1, 6)).isAfter(negInfInterval)); + assert(!NegInfInterval!Date(Date(2012, 1, 7)).isAfter(negInfInterval)); + assert(!NegInfInterval!Date(Date(2012, 1, 8)).isAfter(negInfInterval)); + + assert(!negInfInterval.isAfter(PosInfInterval!Date(Date(2010, 7, 3)))); + assert(!negInfInterval.isAfter(PosInfInterval!Date(Date(2010, 7, 4)))); + assert(!negInfInterval.isAfter(PosInfInterval!Date(Date(2010, 7, 5)))); + assert(!negInfInterval.isAfter(PosInfInterval!Date(Date(2012, 1, 6)))); + assert(!negInfInterval.isAfter(PosInfInterval!Date(Date(2012, 1, 7)))); + assert(!negInfInterval.isAfter(PosInfInterval!Date(Date(2012, 1, 8)))); + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!negInfInterval.isAfter(interval)); + assert(!negInfInterval.isAfter(cInterval)); + assert(!negInfInterval.isAfter(iInterval)); + assert(!negInfInterval.isAfter(posInfInterval)); + assert(!negInfInterval.isAfter(cPosInfInterval)); + assert(!negInfInterval.isAfter(iPosInfInterval)); + assert(!negInfInterval.isAfter(negInfInterval)); + assert(!negInfInterval.isAfter(cNegInfInterval)); + assert(!negInfInterval.isAfter(iNegInfInterval)); + assert(!cNegInfInterval.isAfter(interval)); + assert(!cNegInfInterval.isAfter(cInterval)); + assert(!cNegInfInterval.isAfter(iInterval)); + assert(!cNegInfInterval.isAfter(posInfInterval)); + assert(!cNegInfInterval.isAfter(cPosInfInterval)); + assert(!cNegInfInterval.isAfter(iPosInfInterval)); + assert(!cNegInfInterval.isAfter(negInfInterval)); + assert(!cNegInfInterval.isAfter(cNegInfInterval)); + assert(!cNegInfInterval.isAfter(iNegInfInterval)); + assert(!iNegInfInterval.isAfter(interval)); + assert(!iNegInfInterval.isAfter(cInterval)); + assert(!iNegInfInterval.isAfter(iInterval)); + assert(!iNegInfInterval.isAfter(posInfInterval)); + assert(!iNegInfInterval.isAfter(cPosInfInterval)); + assert(!iNegInfInterval.isAfter(iPosInfInterval)); + assert(!iNegInfInterval.isAfter(negInfInterval)); + assert(!iNegInfInterval.isAfter(cNegInfInterval)); + assert(!iNegInfInterval.isAfter(iNegInfInterval)); + + //Verify Examples. + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Date(1994, 12, 24))); + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Date(2000, 1, 5))); + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Date(2012, 3, 1))); + + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3)))); + + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(PosInfInterval!Date(Date(1999, 5, 4)))); + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(PosInfInterval!Date(Date(2012, 3, 1)))); + + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(NegInfInterval!Date(Date(1996, 5, 4)))); + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAfter(NegInfInterval!Date(Date(2013, 7, 9)))); +} + +//Test NegInfInterval's intersects(). +@safe unittest +{ + import std.datetime.date; + + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + + static void testInterval(in NegInfInterval!Date negInfInterval, in Interval!Date interval) + { + negInfInterval.intersects(interval); + } + + assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assert(negInfInterval.intersects(negInfInterval)); + assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)))); + assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)))); + assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)))); + assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)))); + assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)))); + assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)))); + assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)))); + assert(negInfInterval.intersects(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)))); + assert(negInfInterval.intersects(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)))); + assert(negInfInterval.intersects(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)))); + assert(!negInfInterval.intersects(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)))); + assert(!negInfInterval.intersects(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)))); + + assert(negInfInterval.intersects(NegInfInterval!Date(Date(2010, 7, 3)))); + assert(negInfInterval.intersects(NegInfInterval!Date(Date(2010, 7, 4)))); + assert(negInfInterval.intersects(NegInfInterval!Date(Date(2010, 7, 5)))); + assert(negInfInterval.intersects(NegInfInterval!Date(Date(2012, 1, 6)))); + assert(negInfInterval.intersects(NegInfInterval!Date(Date(2012, 1, 7)))); + assert(negInfInterval.intersects(NegInfInterval!Date(Date(2012, 1, 8)))); + + assert(NegInfInterval!Date(Date(2010, 7, 3)).intersects(negInfInterval)); + assert(NegInfInterval!Date(Date(2010, 7, 4)).intersects(negInfInterval)); + assert(NegInfInterval!Date(Date(2010, 7, 5)).intersects(negInfInterval)); + assert(NegInfInterval!Date(Date(2012, 1, 6)).intersects(negInfInterval)); + assert(NegInfInterval!Date(Date(2012, 1, 7)).intersects(negInfInterval)); + assert(NegInfInterval!Date(Date(2012, 1, 8)).intersects(negInfInterval)); + + assert(negInfInterval.intersects(PosInfInterval!Date(Date(2010, 7, 3)))); + assert(negInfInterval.intersects(PosInfInterval!Date(Date(2010, 7, 4)))); + assert(negInfInterval.intersects(PosInfInterval!Date(Date(2010, 7, 5)))); + assert(negInfInterval.intersects(PosInfInterval!Date(Date(2012, 1, 6)))); + assert(!negInfInterval.intersects(PosInfInterval!Date(Date(2012, 1, 7)))); + assert(!negInfInterval.intersects(PosInfInterval!Date(Date(2012, 1, 8)))); + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(negInfInterval.intersects(interval)); + assert(negInfInterval.intersects(cInterval)); + assert(negInfInterval.intersects(iInterval)); + assert(negInfInterval.intersects(posInfInterval)); + assert(negInfInterval.intersects(cPosInfInterval)); + assert(negInfInterval.intersects(iPosInfInterval)); + assert(negInfInterval.intersects(negInfInterval)); + assert(negInfInterval.intersects(cNegInfInterval)); + assert(negInfInterval.intersects(iNegInfInterval)); + assert(cNegInfInterval.intersects(interval)); + assert(cNegInfInterval.intersects(cInterval)); + assert(cNegInfInterval.intersects(iInterval)); + assert(cNegInfInterval.intersects(posInfInterval)); + assert(cNegInfInterval.intersects(cPosInfInterval)); + assert(cNegInfInterval.intersects(iPosInfInterval)); + assert(cNegInfInterval.intersects(negInfInterval)); + assert(cNegInfInterval.intersects(cNegInfInterval)); + assert(cNegInfInterval.intersects(iNegInfInterval)); + assert(iNegInfInterval.intersects(interval)); + assert(iNegInfInterval.intersects(cInterval)); + assert(iNegInfInterval.intersects(iInterval)); + assert(iNegInfInterval.intersects(posInfInterval)); + assert(iNegInfInterval.intersects(cPosInfInterval)); + assert(iNegInfInterval.intersects(iPosInfInterval)); + assert(iNegInfInterval.intersects(negInfInterval)); + assert(iNegInfInterval.intersects(cNegInfInterval)); + assert(iNegInfInterval.intersects(iNegInfInterval)); + + //Verify Examples. + assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(Interval!Date(Date(1999, 1, 12), Date(2011, 9, 17)))); + assert(!NegInfInterval!Date(Date(2012, 3, 1)).intersects(Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3)))); + + assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(PosInfInterval!Date(Date(1999, 5, 4)))); + assert(!NegInfInterval!Date(Date(2012, 3, 1)).intersects(PosInfInterval!Date(Date(2012, 3, 1)))); + + assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(NegInfInterval!Date(Date(1996, 5, 4)))); + assert(NegInfInterval!Date(Date(2012, 3, 1)).intersects(NegInfInterval!Date(Date(2013, 7, 9)))); +} + +//Test NegInfInterval's intersection(). +@safe unittest +{ + import std.datetime.date; + + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + + static void testInterval(I, J)(in I interval1, in J interval2) + { + interval1.intersection(interval2); + } + + assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)))); + assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)))); + + assertThrown!DateTimeException(testInterval(negInfInterval, PosInfInterval!Date(Date(2012, 1, 7)))); + assertThrown!DateTimeException(testInterval(negInfInterval, PosInfInterval!Date(Date(2012, 1, 8)))); + + assert(negInfInterval.intersection(negInfInterval) == negInfInterval); + assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))) == + Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))); + assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) == + Interval!Date(Date(2010, 7, 1), Date(2012, 1, 7))); + assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))) == + Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))); + assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) == + Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))); + assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) == + Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))); + assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) == + Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))); + assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) == + Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))); + assert(negInfInterval.intersection(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) == + Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))); + assert(negInfInterval.intersection(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) == + Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))); + assert(negInfInterval.intersection(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) == + Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))); + + assert(negInfInterval.intersection(NegInfInterval!Date(Date(2010, 7, 3))) == NegInfInterval!Date(Date(2010, 7, 3))); + assert(negInfInterval.intersection(NegInfInterval!Date(Date(2010, 7, 4))) == NegInfInterval!Date(Date(2010, 7, 4))); + assert(negInfInterval.intersection(NegInfInterval!Date(Date(2010, 7, 5))) == NegInfInterval!Date(Date(2010, 7, 5))); + assert(negInfInterval.intersection(NegInfInterval!Date(Date(2012, 1, 6))) == NegInfInterval!Date(Date(2012, 1, 6))); + assert(negInfInterval.intersection(NegInfInterval!Date(Date(2012, 1, 7))) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.intersection(NegInfInterval!Date(Date(2012, 1, 8))) == NegInfInterval!Date(Date(2012, 1, 7))); + + assert(NegInfInterval!Date(Date(2010, 7, 3)).intersection(negInfInterval) == NegInfInterval!Date(Date(2010, 7, 3))); + assert(NegInfInterval!Date(Date(2010, 7, 4)).intersection(negInfInterval) == NegInfInterval!Date(Date(2010, 7, 4))); + assert(NegInfInterval!Date(Date(2010, 7, 5)).intersection(negInfInterval) == NegInfInterval!Date(Date(2010, 7, 5))); + assert(NegInfInterval!Date(Date(2012, 1, 6)).intersection(negInfInterval) == NegInfInterval!Date(Date(2012, 1, 6))); + assert(NegInfInterval!Date(Date(2012, 1, 7)).intersection(negInfInterval) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(NegInfInterval!Date(Date(2012, 1, 8)).intersection(negInfInterval) == NegInfInterval!Date(Date(2012, 1, 7))); + + assert(negInfInterval.intersection(PosInfInterval!Date(Date(2010, 7, 3))) == + Interval!Date(Date(2010, 7, 3), Date(2012, 1 ,7))); + assert(negInfInterval.intersection(PosInfInterval!Date(Date(2010, 7, 4))) == + Interval!Date(Date(2010, 7, 4), Date(2012, 1 ,7))); + assert(negInfInterval.intersection(PosInfInterval!Date(Date(2010, 7, 5))) == + Interval!Date(Date(2010, 7, 5), Date(2012, 1 ,7))); + assert(negInfInterval.intersection(PosInfInterval!Date(Date(2012, 1, 6))) == + Interval!Date(Date(2012, 1, 6), Date(2012, 1 ,7))); + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!negInfInterval.intersection(interval).empty); + assert(!negInfInterval.intersection(cInterval).empty); + assert(!negInfInterval.intersection(iInterval).empty); + assert(!negInfInterval.intersection(posInfInterval).empty); + assert(!negInfInterval.intersection(cPosInfInterval).empty); + assert(!negInfInterval.intersection(iPosInfInterval).empty); + assert(!negInfInterval.intersection(negInfInterval).empty); + assert(!negInfInterval.intersection(cNegInfInterval).empty); + assert(!negInfInterval.intersection(iNegInfInterval).empty); + assert(!cNegInfInterval.intersection(interval).empty); + assert(!cNegInfInterval.intersection(cInterval).empty); + assert(!cNegInfInterval.intersection(iInterval).empty); + assert(!cNegInfInterval.intersection(posInfInterval).empty); + assert(!cNegInfInterval.intersection(cPosInfInterval).empty); + assert(!cNegInfInterval.intersection(iPosInfInterval).empty); + assert(!cNegInfInterval.intersection(negInfInterval).empty); + assert(!cNegInfInterval.intersection(cNegInfInterval).empty); + assert(!cNegInfInterval.intersection(iNegInfInterval).empty); + assert(!iNegInfInterval.intersection(interval).empty); + assert(!iNegInfInterval.intersection(cInterval).empty); + assert(!iNegInfInterval.intersection(iInterval).empty); + assert(!iNegInfInterval.intersection(posInfInterval).empty); + assert(!iNegInfInterval.intersection(cPosInfInterval).empty); + assert(!iNegInfInterval.intersection(iPosInfInterval).empty); + assert(!iNegInfInterval.intersection(negInfInterval).empty); + assert(!iNegInfInterval.intersection(cNegInfInterval).empty); + assert(!iNegInfInterval.intersection(iNegInfInterval).empty); + + //Verify Examples. + assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == + Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))); + assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(Interval!Date(Date(1999, 1, 12), Date(2015, 9, 2))) == + Interval!Date(Date(1999, 1, 12), Date(2012, 3, 1))); + + assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(PosInfInterval!Date(Date(1990, 7, 6))) == + Interval!Date(Date(1990, 7, 6), Date(2012, 3, 1))); + assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(PosInfInterval!Date(Date(1999, 1, 12))) == + Interval!Date(Date(1999, 1, 12), Date(2012, 3, 1))); + + assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(NegInfInterval!Date(Date(1999, 7, 6))) == + NegInfInterval!Date(Date(1999, 7, 6))); + assert(NegInfInterval!Date(Date(2012, 3, 1)).intersection(NegInfInterval!Date(Date(2013, 1, 12))) == + NegInfInterval!Date(Date(2012, 3, 1))); +} + +//Test NegInfInterval's isAdjacent(). +@safe unittest +{ + import std.datetime.date; + + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + + static void testInterval(in NegInfInterval!Date negInfInterval, in Interval!Date interval) + { + negInfInterval.isAdjacent(interval); + } + + assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assert(!negInfInterval.isAdjacent(negInfInterval)); + assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3)))); + assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3)))); + assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4)))); + assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5)))); + assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7)))); + assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8)))); + assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6)))); + assert(!negInfInterval.isAdjacent(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7)))); + assert(!negInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7)))); + assert(!negInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8)))); + assert(negInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8)))); + assert(!negInfInterval.isAdjacent(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)))); + + assert(!negInfInterval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 3)))); + assert(!negInfInterval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 4)))); + assert(!negInfInterval.isAdjacent(NegInfInterval!Date(Date(2010, 7, 5)))); + assert(!negInfInterval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 6)))); + assert(!negInfInterval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 7)))); + assert(!negInfInterval.isAdjacent(NegInfInterval!Date(Date(2012, 1, 8)))); + + assert(!NegInfInterval!Date(Date(2010, 7, 3)).isAdjacent(negInfInterval)); + assert(!NegInfInterval!Date(Date(2010, 7, 4)).isAdjacent(negInfInterval)); + assert(!NegInfInterval!Date(Date(2010, 7, 5)).isAdjacent(negInfInterval)); + assert(!NegInfInterval!Date(Date(2012, 1, 6)).isAdjacent(negInfInterval)); + assert(!NegInfInterval!Date(Date(2012, 1, 7)).isAdjacent(negInfInterval)); + assert(!NegInfInterval!Date(Date(2012, 1, 8)).isAdjacent(negInfInterval)); + + assert(!negInfInterval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 3)))); + assert(!negInfInterval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 4)))); + assert(!negInfInterval.isAdjacent(PosInfInterval!Date(Date(2010, 7, 5)))); + assert(!negInfInterval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 6)))); + assert(negInfInterval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 7)))); + assert(!negInfInterval.isAdjacent(PosInfInterval!Date(Date(2012, 1, 8)))); + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!negInfInterval.isAdjacent(interval)); + assert(!negInfInterval.isAdjacent(cInterval)); + assert(!negInfInterval.isAdjacent(iInterval)); + assert(!negInfInterval.isAdjacent(posInfInterval)); + assert(!negInfInterval.isAdjacent(cPosInfInterval)); + assert(!negInfInterval.isAdjacent(iPosInfInterval)); + assert(!negInfInterval.isAdjacent(negInfInterval)); + assert(!negInfInterval.isAdjacent(cNegInfInterval)); + assert(!negInfInterval.isAdjacent(iNegInfInterval)); + assert(!cNegInfInterval.isAdjacent(interval)); + assert(!cNegInfInterval.isAdjacent(cInterval)); + assert(!cNegInfInterval.isAdjacent(iInterval)); + assert(!cNegInfInterval.isAdjacent(posInfInterval)); + assert(!cNegInfInterval.isAdjacent(cPosInfInterval)); + assert(!cNegInfInterval.isAdjacent(iPosInfInterval)); + assert(!cNegInfInterval.isAdjacent(negInfInterval)); + assert(!cNegInfInterval.isAdjacent(cNegInfInterval)); + assert(!cNegInfInterval.isAdjacent(iNegInfInterval)); + assert(!iNegInfInterval.isAdjacent(interval)); + assert(!iNegInfInterval.isAdjacent(cInterval)); + assert(!iNegInfInterval.isAdjacent(iInterval)); + assert(!iNegInfInterval.isAdjacent(posInfInterval)); + assert(!iNegInfInterval.isAdjacent(cPosInfInterval)); + assert(!iNegInfInterval.isAdjacent(iPosInfInterval)); + assert(!iNegInfInterval.isAdjacent(negInfInterval)); + assert(!iNegInfInterval.isAdjacent(cNegInfInterval)); + assert(!iNegInfInterval.isAdjacent(iNegInfInterval)); + + //Verify Examples. + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2)))); + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(Interval!Date(Date(1999, 1, 12), Date(2012, 3, 1)))); + assert(NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(Interval!Date(Date(2012, 3, 1), Date(2019, 2, 2)))); + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(Interval!Date(Date(2022, 10, 19), Date(2027, 6, 3)))); + + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(PosInfInterval!Date(Date(1999, 5, 4)))); + assert(NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(PosInfInterval!Date(Date(2012, 3, 1)))); + + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(NegInfInterval!Date(Date(1996, 5, 4)))); + assert(!NegInfInterval!Date(Date(2012, 3, 1)).isAdjacent(NegInfInterval!Date(Date(2012, 3, 1)))); +} + +//Test NegInfInterval's merge(). +@safe unittest +{ + import std.datetime.date; + + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + + static void testInterval(I, J)(in I interval1, in J interval2) + { + interval1.merge(interval2); + } + + assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9)))); + + assert(negInfInterval.merge(negInfInterval) == negInfInterval); + assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))) == + NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) == + NegInfInterval!Date(Date(2013, 7, 3))); + assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))) == + NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) == + NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) == + NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) == + NegInfInterval!Date(Date(2012, 1, 8))); + assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) == + NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.merge(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) == + NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.merge(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) == + NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.merge(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) == + NegInfInterval!Date(Date(2012, 1, 8))); + assert(negInfInterval.merge(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))) == + NegInfInterval!Date(Date(2012, 1, 8))); + + assert(negInfInterval.merge(NegInfInterval!Date(Date(2010, 7, 3))) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.merge(NegInfInterval!Date(Date(2010, 7, 4))) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.merge(NegInfInterval!Date(Date(2010, 7, 5))) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.merge(NegInfInterval!Date(Date(2012, 1, 6))) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.merge(NegInfInterval!Date(Date(2012, 1, 7))) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.merge(NegInfInterval!Date(Date(2012, 1, 8))) == NegInfInterval!Date(Date(2012, 1, 8))); + + assert(NegInfInterval!Date(Date(2010, 7, 3)).merge(negInfInterval) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(NegInfInterval!Date(Date(2010, 7, 4)).merge(negInfInterval) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(NegInfInterval!Date(Date(2010, 7, 5)).merge(negInfInterval) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(NegInfInterval!Date(Date(2012, 1, 6)).merge(negInfInterval) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(NegInfInterval!Date(Date(2012, 1, 7)).merge(negInfInterval) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(NegInfInterval!Date(Date(2012, 1, 8)).merge(negInfInterval) == NegInfInterval!Date(Date(2012, 1, 8))); + + static assert(!__traits(compiles, negInfInterval.merge(PosInfInterval!Date(Date(2010, 7, 3))))); + static assert(!__traits(compiles, negInfInterval.merge(PosInfInterval!Date(Date(2010, 7, 4))))); + static assert(!__traits(compiles, negInfInterval.merge(PosInfInterval!Date(Date(2010, 7, 5))))); + static assert(!__traits(compiles, negInfInterval.merge(PosInfInterval!Date(Date(2012, 1, 6))))); + static assert(!__traits(compiles, negInfInterval.merge(PosInfInterval!Date(Date(2012, 1, 7))))); + static assert(!__traits(compiles, negInfInterval.merge(PosInfInterval!Date(Date(2012, 1, 8))))); + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!negInfInterval.merge(interval).empty); + assert(!negInfInterval.merge(cInterval).empty); + assert(!negInfInterval.merge(iInterval).empty); + static assert(!__traits(compiles, negInfInterval.merge(posInfInterval))); + static assert(!__traits(compiles, negInfInterval.merge(cPosInfInterval))); + static assert(!__traits(compiles, negInfInterval.merge(iPosInfInterval))); + assert(!negInfInterval.merge(negInfInterval).empty); + assert(!negInfInterval.merge(cNegInfInterval).empty); + assert(!negInfInterval.merge(iNegInfInterval).empty); + assert(!cNegInfInterval.merge(interval).empty); + assert(!cNegInfInterval.merge(cInterval).empty); + assert(!cNegInfInterval.merge(iInterval).empty); + static assert(!__traits(compiles, cNegInfInterval.merge(posInfInterval))); + static assert(!__traits(compiles, cNegInfInterval.merge(cPosInfInterval))); + static assert(!__traits(compiles, cNegInfInterval.merge(iPosInfInterval))); + assert(!cNegInfInterval.merge(negInfInterval).empty); + assert(!cNegInfInterval.merge(cNegInfInterval).empty); + assert(!cNegInfInterval.merge(iNegInfInterval).empty); + assert(!iNegInfInterval.merge(interval).empty); + assert(!iNegInfInterval.merge(cInterval).empty); + assert(!iNegInfInterval.merge(iInterval).empty); + static assert(!__traits(compiles, iNegInfInterval.merge(posInfInterval))); + static assert(!__traits(compiles, iNegInfInterval.merge(cPosInfInterval))); + static assert(!__traits(compiles, iNegInfInterval.merge(iPosInfInterval))); + assert(!iNegInfInterval.merge(negInfInterval).empty); + assert(!iNegInfInterval.merge(cNegInfInterval).empty); + assert(!iNegInfInterval.merge(iNegInfInterval).empty); + + //Verify Examples. + assert(NegInfInterval!Date(Date(2012, 3, 1)).merge(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == + NegInfInterval!Date(Date(2012, 3, 1))); + assert(NegInfInterval!Date(Date(2012, 3, 1)).merge(Interval!Date(Date(1999, 1, 12), Date(2015, 9, 2))) == + NegInfInterval!Date(Date(2015, 9, 2))); + + assert(NegInfInterval!Date(Date(2012, 3, 1)).merge(NegInfInterval!Date(Date(1999, 7, 6))) == + NegInfInterval!Date(Date(2012, 3, 1))); + assert(NegInfInterval!Date(Date(2012, 3, 1)).merge(NegInfInterval!Date(Date(2013, 1, 12))) == + NegInfInterval!Date(Date(2013, 1, 12))); +} + +//Test NegInfInterval's span(). +@safe unittest +{ + import std.datetime.date; + + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + + static void testInterval(I, J)(in I interval1, in J interval2) + { + interval1.span(interval2); + } + + assertThrown!DateTimeException(testInterval(negInfInterval, Interval!Date(Date(2010, 7, 4), dur!"days"(0)))); + + assert(negInfInterval.span(negInfInterval) == negInfInterval); + assert(negInfInterval.span(Interval!Date(Date(2010, 7, 1), Date(2010, 7, 3))) == + NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.span(Interval!Date(Date(2010, 7, 1), Date(2013, 7, 3))) == + NegInfInterval!Date(Date(2013, 7, 3))); + assert(negInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 4))) == + NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2010, 7, 5))) == + NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 7))) == + NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.span(Interval!Date(Date(2010, 7, 3), Date(2012, 1, 8))) == + NegInfInterval!Date(Date(2012, 1, 8))); + assert(negInfInterval.span(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 6))) == + NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.span(Interval!Date(Date(2010, 7, 5), Date(2012, 1, 7))) == + NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.span(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 7))) == + NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.span(Interval!Date(Date(2012, 1, 6), Date(2012, 1, 8))) == + NegInfInterval!Date(Date(2012, 1, 8))); + assert(negInfInterval.span(Interval!Date(Date(2012, 1, 7), Date(2012, 1, 8))) == + NegInfInterval!Date(Date(2012, 1, 8))); + assert(negInfInterval.span(Interval!Date(Date(2012, 1, 8), Date(2012, 1, 9))) == + NegInfInterval!Date(Date(2012, 1, 9))); + + assert(negInfInterval.span(NegInfInterval!Date(Date(2010, 7, 3))) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.span(NegInfInterval!Date(Date(2010, 7, 4))) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.span(NegInfInterval!Date(Date(2010, 7, 5))) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.span(NegInfInterval!Date(Date(2012, 1, 6))) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.span(NegInfInterval!Date(Date(2012, 1, 7))) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(negInfInterval.span(NegInfInterval!Date(Date(2012, 1, 8))) == NegInfInterval!Date(Date(2012, 1, 8))); + + assert(NegInfInterval!Date(Date(2010, 7, 3)).span(negInfInterval) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(NegInfInterval!Date(Date(2010, 7, 4)).span(negInfInterval) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(NegInfInterval!Date(Date(2010, 7, 5)).span(negInfInterval) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(NegInfInterval!Date(Date(2012, 1, 6)).span(negInfInterval) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(NegInfInterval!Date(Date(2012, 1, 7)).span(negInfInterval) == NegInfInterval!Date(Date(2012, 1, 7))); + assert(NegInfInterval!Date(Date(2012, 1, 8)).span(negInfInterval) == NegInfInterval!Date(Date(2012, 1, 8))); + + static assert(!__traits(compiles, negInfInterval.span(PosInfInterval!Date(Date(2010, 7, 3))))); + static assert(!__traits(compiles, negInfInterval.span(PosInfInterval!Date(Date(2010, 7, 4))))); + static assert(!__traits(compiles, negInfInterval.span(PosInfInterval!Date(Date(2010, 7, 5))))); + static assert(!__traits(compiles, negInfInterval.span(PosInfInterval!Date(Date(2012, 1, 6))))); + static assert(!__traits(compiles, negInfInterval.span(PosInfInterval!Date(Date(2012, 1, 7))))); + static assert(!__traits(compiles, negInfInterval.span(PosInfInterval!Date(Date(2012, 1, 8))))); + + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + const cInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + immutable iInterval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + immutable iPosInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!negInfInterval.span(interval).empty); + assert(!negInfInterval.span(cInterval).empty); + assert(!negInfInterval.span(iInterval).empty); + static assert(!__traits(compiles, negInfInterval.span(posInfInterval))); + static assert(!__traits(compiles, negInfInterval.span(cPosInfInterval))); + static assert(!__traits(compiles, negInfInterval.span(iPosInfInterval))); + assert(!negInfInterval.span(negInfInterval).empty); + assert(!negInfInterval.span(cNegInfInterval).empty); + assert(!negInfInterval.span(iNegInfInterval).empty); + assert(!cNegInfInterval.span(interval).empty); + assert(!cNegInfInterval.span(cInterval).empty); + assert(!cNegInfInterval.span(iInterval).empty); + static assert(!__traits(compiles, cNegInfInterval.span(posInfInterval))); + static assert(!__traits(compiles, cNegInfInterval.span(cPosInfInterval))); + static assert(!__traits(compiles, cNegInfInterval.span(iPosInfInterval))); + assert(!cNegInfInterval.span(negInfInterval).empty); + assert(!cNegInfInterval.span(cNegInfInterval).empty); + assert(!cNegInfInterval.span(iNegInfInterval).empty); + assert(!iNegInfInterval.span(interval).empty); + assert(!iNegInfInterval.span(cInterval).empty); + assert(!iNegInfInterval.span(iInterval).empty); + static assert(!__traits(compiles, iNegInfInterval.span(posInfInterval))); + static assert(!__traits(compiles, iNegInfInterval.span(cPosInfInterval))); + static assert(!__traits(compiles, iNegInfInterval.span(iPosInfInterval))); + assert(!iNegInfInterval.span(negInfInterval).empty); + assert(!iNegInfInterval.span(cNegInfInterval).empty); + assert(!iNegInfInterval.span(iNegInfInterval).empty); + + //Verify Examples. + assert(NegInfInterval!Date(Date(2012, 3, 1)).span(Interval!Date(Date(1990, 7, 6), Date(2000, 8, 2))) == + NegInfInterval!Date(Date(2012, 3, 1))); + assert(NegInfInterval!Date(Date(2012, 3, 1)).span(Interval!Date(Date(1999, 1, 12), Date(2015, 9, 2))) == + NegInfInterval!Date(Date(2015, 9, 2))); + assert(NegInfInterval!Date(Date(1600, 1, 7)).span(Interval!Date(Date(2012, 3, 11), Date(2017, 7, 1))) == + NegInfInterval!Date(Date(2017, 7, 1))); + + assert(NegInfInterval!Date(Date(2012, 3, 1)).span(NegInfInterval!Date(Date(1999, 7, 6))) == + NegInfInterval!Date(Date(2012, 3, 1))); + assert(NegInfInterval!Date(Date(2012, 3, 1)).span(NegInfInterval!Date(Date(2013, 1, 12))) == + NegInfInterval!Date(Date(2013, 1, 12))); +} + +//Test NegInfInterval's shift(). +@safe unittest +{ + import std.datetime.date; + + auto interval = NegInfInterval!Date(Date(2012, 1, 7)); + + static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__) + { + interval.shift(duration); + assert(interval == expected); + } + + testInterval(interval, dur!"days"(22), NegInfInterval!Date(Date(2012, 1, 29))); + testInterval(interval, dur!"days"(-22), NegInfInterval!Date(Date(2011, 12, 16))); + + const cInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iInterval = NegInfInterval!Date(Date(2012, 1, 7)); + static assert(!__traits(compiles, cInterval.shift(dur!"days"(5)))); + static assert(!__traits(compiles, iInterval.shift(dur!"days"(5)))); + + //Verify Examples. + auto interval1 = NegInfInterval!Date(Date(2012, 4, 5)); + auto interval2 = NegInfInterval!Date(Date(2012, 4, 5)); + + interval1.shift(dur!"days"(50)); + assert(interval1 == NegInfInterval!Date(Date(2012, 5, 25))); + + interval2.shift(dur!"days"(-50)); + assert(interval2 == NegInfInterval!Date( Date(2012, 2, 15))); +} + +//Test NegInfInterval's shift(int, int, AllowDayOverflow). +@safe unittest +{ + import std.datetime.date; + + { + auto interval = NegInfInterval!Date(Date(2012, 1, 7)); + + static void testIntervalFail(I)(I interval, int years, int months) + { + interval.shift(years, months); + } + + static void testInterval(I)(I interval, int years, int months, AllowDayOverflow allow, + in I expected, size_t line = __LINE__) + { + interval.shift(years, months, allow); + assert(interval == expected); + } + + testInterval(interval, 5, 0, AllowDayOverflow.yes, NegInfInterval!Date(Date(2017, 1, 7))); + testInterval(interval, -5, 0, AllowDayOverflow.yes, NegInfInterval!Date(Date(2007, 1, 7))); + + auto interval2 = NegInfInterval!Date(Date(2010, 5, 31)); + + testInterval(interval2, 1, 1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2011, 7, 1))); + testInterval(interval2, 1, -1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2011, 5, 1))); + testInterval(interval2, -1, -1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2009, 5, 1))); + testInterval(interval2, -1, 1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2009, 7, 1))); + + testInterval(interval2, 1, 1, AllowDayOverflow.no, NegInfInterval!Date(Date(2011, 6, 30))); + testInterval(interval2, 1, -1, AllowDayOverflow.no, NegInfInterval!Date(Date(2011, 4, 30))); + testInterval(interval2, -1, -1, AllowDayOverflow.no, NegInfInterval!Date(Date(2009, 4, 30))); + testInterval(interval2, -1, 1, AllowDayOverflow.no, NegInfInterval!Date(Date(2009, 6, 30))); + } + + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + static assert(!__traits(compiles, cNegInfInterval.shift(1))); + static assert(!__traits(compiles, iNegInfInterval.shift(1))); + + //Verify Examples. + auto interval1 = NegInfInterval!Date(Date(2012, 3, 1)); + auto interval2 = NegInfInterval!Date(Date(2012, 3, 1)); + + interval1.shift(2); + assert(interval1 == NegInfInterval!Date(Date(2014, 3, 1))); + + interval2.shift(-2); + assert(interval2 == NegInfInterval!Date(Date(2010, 3, 1))); +} + +//Test NegInfInterval's expand(). +@safe unittest +{ + import std.datetime.date; + + auto interval = NegInfInterval!Date(Date(2012, 1, 7)); + + static void testInterval(I)(I interval, in Duration duration, in I expected, size_t line = __LINE__) + { + interval.expand(duration); + assert(interval == expected); + } + + testInterval(interval, dur!"days"(22), NegInfInterval!Date(Date(2012, 1, 29))); + testInterval(interval, dur!"days"(-22), NegInfInterval!Date(Date(2011, 12, 16))); + + const cInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iInterval = NegInfInterval!Date(Date(2012, 1, 7)); + static assert(!__traits(compiles, cInterval.expand(dur!"days"(5)))); + static assert(!__traits(compiles, iInterval.expand(dur!"days"(5)))); + + //Verify Examples. + auto interval1 = NegInfInterval!Date(Date(2012, 3, 1)); + auto interval2 = NegInfInterval!Date(Date(2012, 3, 1)); + + interval1.expand(dur!"days"(2)); + assert(interval1 == NegInfInterval!Date(Date(2012, 3, 3))); + + interval2.expand(dur!"days"(-2)); + assert(interval2 == NegInfInterval!Date(Date(2012, 2, 28))); +} + +//Test NegInfInterval's expand(int, int, AllowDayOverflow). +@safe unittest +{ + import std.datetime.date; + + { + auto interval = NegInfInterval!Date(Date(2012, 1, 7)); + + static void testInterval(I)(I interval, int years, int months, AllowDayOverflow allow, + in I expected, size_t line = __LINE__) + { + interval.expand(years, months, allow); + assert(interval == expected); + } + + testInterval(interval, 5, 0, AllowDayOverflow.yes, NegInfInterval!Date(Date(2017, 1, 7))); + testInterval(interval, -5, 0, AllowDayOverflow.yes, NegInfInterval!Date(Date(2007, 1, 7))); + + auto interval2 = NegInfInterval!Date(Date(2010, 5, 31)); + + testInterval(interval2, 1, 1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2011, 7, 1))); + testInterval(interval2, 1, -1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2011, 5, 1))); + testInterval(interval2, -1, -1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2009, 5, 1))); + testInterval(interval2, -1, 1, AllowDayOverflow.yes, NegInfInterval!Date(Date(2009, 7, 1))); + + testInterval(interval2, 1, 1, AllowDayOverflow.no, NegInfInterval!Date(Date(2011, 6, 30))); + testInterval(interval2, 1, -1, AllowDayOverflow.no, NegInfInterval!Date(Date(2011, 4, 30))); + testInterval(interval2, -1, -1, AllowDayOverflow.no, NegInfInterval!Date(Date(2009, 4, 30))); + testInterval(interval2, -1, 1, AllowDayOverflow.no, NegInfInterval!Date( Date(2009, 6, 30))); + } + + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + static assert(!__traits(compiles, cNegInfInterval.expand(1))); + static assert(!__traits(compiles, iNegInfInterval.expand(1))); + + //Verify Examples. + auto interval1 = NegInfInterval!Date(Date(2012, 3, 1)); + auto interval2 = NegInfInterval!Date(Date(2012, 3, 1)); + + interval1.expand(2); + assert(interval1 == NegInfInterval!Date(Date(2014, 3, 1))); + + interval2.expand(-2); + assert(interval2 == NegInfInterval!Date(Date(2010, 3, 1))); +} + +//Test NegInfInterval's bwdRange(). +@system unittest +{ + import std.datetime.date; + + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + + static void testInterval(NegInfInterval!Date negInfInterval) + { + negInfInterval.bwdRange(everyDayOfWeek!(Date, Direction.fwd)(DayOfWeek.fri)).popFront(); + } + + assertThrown!DateTimeException(testInterval(negInfInterval)); + + assert(NegInfInterval!Date(Date(2010, 10, 1)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)).front == + Date(2010, 10, 1)); + + assert(NegInfInterval!Date(Date(2010, 10, 1)).bwdRange( + everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri), PopFirst.yes).front == Date(2010, 9, 24)); + + //Verify Examples. + auto interval = NegInfInterval!Date(Date(2010, 9, 9)); + auto func = delegate (in Date date) + { + if ((date.day & 1) == 0) + return date - dur!"days"(2); + return date - dur!"days"(1); + }; + auto range = interval.bwdRange(func); + + //An odd day. Using PopFirst.yes would have made this Date(2010, 9, 8). + assert(range.front == Date(2010, 9, 9)); + + range.popFront(); + assert(range.front == Date(2010, 9, 8)); + + range.popFront(); + assert(range.front == Date(2010, 9, 6)); + + range.popFront(); + assert(range.front == Date(2010, 9, 4)); + + range.popFront(); + assert(range.front == Date(2010, 9, 2)); + + range.popFront(); + assert(!range.empty); + + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(!cNegInfInterval.bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)).empty); + assert(!iNegInfInterval.bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)).empty); +} + +//Test NegInfInterval's toString(). +@safe unittest +{ + import std.datetime.date; + + assert(NegInfInterval!Date(Date(2012, 1, 7)).toString() == "[-∞ - 2012-Jan-07)"); + + const cNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + immutable iNegInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + assert(cNegInfInterval.toString()); + assert(iNegInfInterval.toString()); +} + + +/++ + Range-generating function. + + Returns a delegate which returns the next time point with the given + $(D DayOfWeek) in a range. + + Using this delegate allows iteration over successive time points which + are all the same day of the week. e.g. passing $(D DayOfWeek.mon) to + $(D everyDayOfWeek) would result in a delegate which could be used to + iterate over all of the Mondays in a range. + + Params: + dir = The direction to iterate in. If passing the return value to + $(D fwdRange), use $(D Direction.fwd). If passing it to + $(D bwdRange), use $(D Direction.bwd). + dayOfWeek = The week that each time point in the range will be. + +/ +TP delegate(in TP) everyDayOfWeek(TP, Direction dir = Direction.fwd)(DayOfWeek dayOfWeek) nothrow +if (isTimePoint!TP && + (dir == Direction.fwd || dir == Direction.bwd) && + __traits(hasMember, TP, "dayOfWeek") && + !__traits(isStaticFunction, TP.dayOfWeek) && + is(typeof(TP.dayOfWeek) == DayOfWeek)) +{ + TP func(in TP tp) + { + TP retval = cast(TP) tp; + immutable days = daysToDayOfWeek(retval.dayOfWeek, dayOfWeek); + + static if (dir == Direction.fwd) + immutable adjustedDays = days == 0 ? 7 : days; + else + immutable adjustedDays = days == 0 ? -7 : days - 7; + + return retval += dur!"days"(adjustedDays); + } + + return &func; +} + +/// +@system unittest +{ + import std.datetime.date : Date, DayOfWeek; + + auto interval = Interval!Date(Date(2010, 9, 2), Date(2010, 9, 27)); + auto func = everyDayOfWeek!Date(DayOfWeek.mon); + auto range = interval.fwdRange(func); + + // A Thursday. Using PopFirst.yes would have made this Date(2010, 9, 6). + assert(range.front == Date(2010, 9, 2)); + + range.popFront(); + assert(range.front == Date(2010, 9, 6)); + + range.popFront(); + assert(range.front == Date(2010, 9, 13)); + + range.popFront(); + assert(range.front == Date(2010, 9, 20)); + + range.popFront(); + assert(range.empty); +} + +@system unittest +{ + import std.datetime.date; + import std.datetime.systime; + + auto funcFwd = everyDayOfWeek!Date(DayOfWeek.mon); + auto funcBwd = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.mon); + + assert(funcFwd(Date(2010, 8, 28)) == Date(2010, 8, 30)); + assert(funcFwd(Date(2010, 8, 29)) == Date(2010, 8, 30)); + assert(funcFwd(Date(2010, 8, 30)) == Date(2010, 9, 6)); + assert(funcFwd(Date(2010, 8, 31)) == Date(2010, 9, 6)); + assert(funcFwd(Date(2010, 9, 1)) == Date(2010, 9, 6)); + assert(funcFwd(Date(2010, 9, 2)) == Date(2010, 9, 6)); + assert(funcFwd(Date(2010, 9, 3)) == Date(2010, 9, 6)); + assert(funcFwd(Date(2010, 9, 4)) == Date(2010, 9, 6)); + assert(funcFwd(Date(2010, 9, 5)) == Date(2010, 9, 6)); + assert(funcFwd(Date(2010, 9, 6)) == Date(2010, 9, 13)); + assert(funcFwd(Date(2010, 9, 7)) == Date(2010, 9, 13)); + + assert(funcBwd(Date(2010, 8, 28)) == Date(2010, 8, 23)); + assert(funcBwd(Date(2010, 8, 29)) == Date(2010, 8, 23)); + assert(funcBwd(Date(2010, 8, 30)) == Date(2010, 8, 23)); + assert(funcBwd(Date(2010, 8, 31)) == Date(2010, 8, 30)); + assert(funcBwd(Date(2010, 9, 1)) == Date(2010, 8, 30)); + assert(funcBwd(Date(2010, 9, 2)) == Date(2010, 8, 30)); + assert(funcBwd(Date(2010, 9, 3)) == Date(2010, 8, 30)); + assert(funcBwd(Date(2010, 9, 4)) == Date(2010, 8, 30)); + assert(funcBwd(Date(2010, 9, 5)) == Date(2010, 8, 30)); + assert(funcBwd(Date(2010, 9, 6)) == Date(2010, 8, 30)); + assert(funcBwd(Date(2010, 9, 7)) == Date(2010, 9, 6)); + + static assert(!__traits(compiles, everyDayOfWeek!TimeOfDay(DayOfWeek.mon))); + assert(everyDayOfWeek!DateTime(DayOfWeek.mon) !is null); + assert(everyDayOfWeek!SysTime(DayOfWeek.mon) !is null); +} + + +/++ + Range-generating function. + + Returns a delegate which returns the next time point with the given month + which would be reached by adding months to the given time point. + + So, using this delegate allows iteration over successive time points + which are in the same month but different years. For example, + iterate over each successive December 25th in an interval by starting with a + date which had the 25th as its day and passed $(D Month.dec) to + $(D everyMonth) to create the delegate. + + Since it wouldn't really make sense to be iterating over a specific month + and end up with some of the time points in the succeeding month or two years + after the previous time point, $(D AllowDayOverflow.no) is always used when + calculating the next time point. + + Params: + dir = The direction to iterate in. If passing the return value to + $(D fwdRange), use $(D Direction.fwd). If passing it to + $(D bwdRange), use $(D Direction.bwd). + month = The month that each time point in the range will be in + (January is 1). + +/ +TP delegate(in TP) everyMonth(TP, Direction dir = Direction.fwd)(int month) +if (isTimePoint!TP && + (dir == Direction.fwd || dir == Direction.bwd) && + __traits(hasMember, TP, "month") && + !__traits(isStaticFunction, TP.month) && + is(typeof(TP.month) == Month)) +{ + import std.datetime.date : enforceValid, monthsToMonth; + + enforceValid!"months"(month); + + TP func(in TP tp) + { + TP retval = cast(TP) tp; + immutable months = monthsToMonth(retval.month, month); + + static if (dir == Direction.fwd) + immutable adjustedMonths = months == 0 ? 12 : months; + else + immutable adjustedMonths = months == 0 ? -12 : months - 12; + + retval.add!"months"(adjustedMonths, AllowDayOverflow.no); + + if (retval.month != month) + { + retval.add!"months"(-1); + assert(retval.month == month); + } + + return retval; + } + + return &func; +} + +/// +@system unittest +{ + import std.datetime.date : Date, Month; + + auto interval = Interval!Date(Date(2000, 1, 30), Date(2004, 8, 5)); + auto func = everyMonth!Date(Month.feb); + auto range = interval.fwdRange(func); + + // Using PopFirst.yes would have made this Date(2010, 2, 29). + assert(range.front == Date(2000, 1, 30)); + + range.popFront(); + assert(range.front == Date(2000, 2, 29)); + + range.popFront(); + assert(range.front == Date(2001, 2, 28)); + + range.popFront(); + assert(range.front == Date(2002, 2, 28)); + + range.popFront(); + assert(range.front == Date(2003, 2, 28)); + + range.popFront(); + assert(range.front == Date(2004, 2, 28)); + + range.popFront(); + assert(range.empty); +} + +@system unittest +{ + import std.datetime.date; + import std.datetime.systime; + + auto funcFwd = everyMonth!Date(Month.jun); + auto funcBwd = everyMonth!(Date, Direction.bwd)(Month.jun); + + assert(funcFwd(Date(2010, 5, 31)) == Date(2010, 6, 30)); + assert(funcFwd(Date(2010, 6, 30)) == Date(2011, 6, 30)); + assert(funcFwd(Date(2010, 7, 31)) == Date(2011, 6, 30)); + assert(funcFwd(Date(2010, 8, 31)) == Date(2011, 6, 30)); + assert(funcFwd(Date(2010, 9, 30)) == Date(2011, 6, 30)); + assert(funcFwd(Date(2010, 10, 31)) == Date(2011, 6, 30)); + assert(funcFwd(Date(2010, 11, 30)) == Date(2011, 6, 30)); + assert(funcFwd(Date(2010, 12, 31)) == Date(2011, 6, 30)); + assert(funcFwd(Date(2011, 1, 31)) == Date(2011, 6, 30)); + assert(funcFwd(Date(2011, 2, 28)) == Date(2011, 6, 28)); + assert(funcFwd(Date(2011, 3, 31)) == Date(2011, 6, 30)); + assert(funcFwd(Date(2011, 4, 30)) == Date(2011, 6, 30)); + assert(funcFwd(Date(2011, 5, 31)) == Date(2011, 6, 30)); + assert(funcFwd(Date(2011, 6, 30)) == Date(2012, 6, 30)); + assert(funcFwd(Date(2011, 7, 31)) == Date(2012, 6, 30)); + + assert(funcBwd(Date(2010, 5, 31)) == Date(2009, 6, 30)); + assert(funcBwd(Date(2010, 6, 30)) == Date(2009, 6, 30)); + assert(funcBwd(Date(2010, 7, 31)) == Date(2010, 6, 30)); + assert(funcBwd(Date(2010, 8, 31)) == Date(2010, 6, 30)); + assert(funcBwd(Date(2010, 9, 30)) == Date(2010, 6, 30)); + assert(funcBwd(Date(2010, 10, 31)) == Date(2010, 6, 30)); + assert(funcBwd(Date(2010, 11, 30)) == Date(2010, 6, 30)); + assert(funcBwd(Date(2010, 12, 31)) == Date(2010, 6, 30)); + assert(funcBwd(Date(2011, 1, 31)) == Date(2010, 6, 30)); + assert(funcBwd(Date(2011, 2, 28)) == Date(2010, 6, 28)); + assert(funcBwd(Date(2011, 3, 31)) == Date(2010, 6, 30)); + assert(funcBwd(Date(2011, 4, 30)) == Date(2010, 6, 30)); + assert(funcBwd(Date(2011, 5, 31)) == Date(2010, 6, 30)); + assert(funcBwd(Date(2011, 6, 30)) == Date(2010, 6, 30)); + assert(funcBwd(Date(2011, 7, 30)) == Date(2011, 6, 30)); + + static assert(!__traits(compiles, everyMonth!TimeOfDay(Month.jan))); + assert(everyMonth!DateTime(Month.jan) !is null); + assert(everyMonth!SysTime(Month.jan) !is null); +} + + +/++ + Range-generating function. + + Returns a delegate which returns the next time point which is the given + duration later. + + Using this delegate allows iteration over successive time points which + are apart by the given duration e.g. passing $(D dur!"days"(3)) to + $(D everyDuration) would result in a delegate which could be used to iterate + over a range of days which are each 3 days apart. + + Params: + dir = The direction to iterate in. If passing the return value to + $(D fwdRange), use $(D Direction.fwd). If passing it to + $(D bwdRange), use $(D Direction.bwd). + duration = The duration which separates each successive time point in + the range. + +/ +TP delegate(in TP) everyDuration(TP, Direction dir = Direction.fwd, D)(D duration) nothrow +if (isTimePoint!TP && + __traits(compiles, TP.init + duration) && + (dir == Direction.fwd || dir == Direction.bwd)) +{ + TP func(in TP tp) + { + static if (dir == Direction.fwd) + return tp + duration; + else + return tp - duration; + } + + return &func; +} + +/// +@system unittest +{ + import core.time : dur; + import std.datetime.date : Date; + + auto interval = Interval!Date(Date(2010, 9, 2), Date(2010, 9, 27)); + auto func = everyDuration!Date(dur!"days"(8)); + auto range = interval.fwdRange(func); + + // Using PopFirst.yes would have made this Date(2010, 9, 10). + assert(range.front == Date(2010, 9, 2)); + + range.popFront(); + assert(range.front == Date(2010, 9, 10)); + + range.popFront(); + assert(range.front == Date(2010, 9, 18)); + + range.popFront(); + assert(range.front == Date(2010, 9, 26)); + + range.popFront(); + assert(range.empty); +} + +@system unittest +{ + import std.datetime.date; + import std.datetime.systime; + + auto funcFwd = everyDuration!Date(dur!"days"(27)); + auto funcBwd = everyDuration!(Date, Direction.bwd)(dur!"days"(27)); + + assert(funcFwd(Date(2009, 12, 25)) == Date(2010, 1, 21)); + assert(funcFwd(Date(2009, 12, 26)) == Date(2010, 1, 22)); + assert(funcFwd(Date(2009, 12, 27)) == Date(2010, 1, 23)); + assert(funcFwd(Date(2009, 12, 28)) == Date(2010, 1, 24)); + + assert(funcBwd(Date(2010, 1, 21)) == Date(2009, 12, 25)); + assert(funcBwd(Date(2010, 1, 22)) == Date(2009, 12, 26)); + assert(funcBwd(Date(2010, 1, 23)) == Date(2009, 12, 27)); + assert(funcBwd(Date(2010, 1, 24)) == Date(2009, 12, 28)); + + assert(everyDuration!Date(dur!"hnsecs"(1)) !is null); + assert(everyDuration!TimeOfDay(dur!"hnsecs"(1)) !is null); + assert(everyDuration!DateTime(dur!"hnsecs"(1)) !is null); + assert(everyDuration!SysTime(dur!"hnsecs"(1)) !is null); +} + + +/++ + Range-generating function. + + Returns a delegate which returns the next time point which is the given + number of years, month, and duration later. + + The difference between this version of $(D everyDuration) and the version + which just takes a $(REF Duration, core,time) is that this one also takes + the number of years and months (along with an $(D AllowDayOverflow) to + indicate whether adding years and months should allow the days to overflow). + + Note that if iterating forward, $(D add!"years"()) is called on the given + time point, then $(D add!"months"()), and finally the duration is added + to it. However, if iterating backwards, the duration is added first, then + $(D add!"months"()) is called, and finally $(D add!"years"()) is called. + That way, going backwards generates close to the same time points that + iterating forward does, but since adding years and months is not entirely + reversible (due to possible day overflow, regardless of whether + $(D AllowDayOverflow.yes) or $(D AllowDayOverflow.no) is used), it can't be + guaranteed that iterating backwards will give the same time points as + iterating forward would have (even assuming that the end of the range is a + time point which would be returned by the delegate when iterating forward + from $(D begin)). + + Params: + dir = The direction to iterate in. If passing the return + value to $(D fwdRange), use $(D Direction.fwd). If + passing it to $(D bwdRange), use $(D Direction.bwd). + years = The number of years to add to the time point passed to + the delegate. + months = The number of months to add to the time point passed to + the delegate. + allowOverflow = Whether the days should be allowed to overflow on + $(D begin) and $(D end), causing their month to + increment. + duration = The duration to add to the time point passed to the + delegate. + +/ +TP delegate(in TP) everyDuration(TP, Direction dir = Direction.fwd, D) + (int years, + int months = 0, + AllowDayOverflow allowOverflow = AllowDayOverflow.yes, + D duration = dur!"days"(0)) nothrow +if (isTimePoint!TP && + __traits(compiles, TP.init + duration) && + __traits(compiles, TP.init.add!"years"(years)) && + __traits(compiles, TP.init.add!"months"(months)) && + (dir == Direction.fwd || dir == Direction.bwd)) +{ + TP func(in TP tp) + { + static if (dir == Direction.fwd) + { + TP retval = cast(TP) tp; + + retval.add!"years"(years, allowOverflow); + retval.add!"months"(months, allowOverflow); + + return retval + duration; + } + else + { + TP retval = tp - duration; + + retval.add!"months"(-months, allowOverflow); + retval.add!"years"(-years, allowOverflow); + + return retval; + } + } + + return &func; +} + +/// +@system unittest +{ + import core.time : dur; + import std.datetime.date : AllowDayOverflow, Date; + + auto interval = Interval!Date(Date(2010, 9, 2), Date(2025, 9, 27)); + auto func = everyDuration!Date(4, 1, AllowDayOverflow.yes, dur!"days"(2)); + auto range = interval.fwdRange(func); + + // Using PopFirst.yes would have made this Date(2014, 10, 12). + assert(range.front == Date(2010, 9, 2)); + + range.popFront(); + assert(range.front == Date(2014, 10, 4)); + + range.popFront(); + assert(range.front == Date(2018, 11, 6)); + + range.popFront(); + assert(range.front == Date(2022, 12, 8)); + + range.popFront(); + assert(range.empty); +} + +@system unittest +{ + import std.datetime.date; + import std.datetime.systime; + + { + auto funcFwd = everyDuration!Date(1, 2, AllowDayOverflow.yes, dur!"days"(3)); + auto funcBwd = everyDuration!(Date, Direction.bwd)(1, 2, AllowDayOverflow.yes, dur!"days"(3)); + + assert(funcFwd(Date(2009, 12, 25)) == Date(2011, 2, 28)); + assert(funcFwd(Date(2009, 12, 26)) == Date(2011, 3, 1)); + assert(funcFwd(Date(2009, 12, 27)) == Date(2011, 3, 2)); + assert(funcFwd(Date(2009, 12, 28)) == Date(2011, 3, 3)); + assert(funcFwd(Date(2009, 12, 29)) == Date(2011, 3, 4)); + + assert(funcBwd(Date(2011, 2, 28)) == Date(2009, 12, 25)); + assert(funcBwd(Date(2011, 3, 1)) == Date(2009, 12, 26)); + assert(funcBwd(Date(2011, 3, 2)) == Date(2009, 12, 27)); + assert(funcBwd(Date(2011, 3, 3)) == Date(2009, 12, 28)); + assert(funcBwd(Date(2011, 3, 4)) == Date(2010, 1, 1)); + } + + { + auto funcFwd = everyDuration!Date(1, 2, AllowDayOverflow.no, dur!"days"(3)); + auto funcBwd = everyDuration!(Date, Direction.bwd)(1, 2, AllowDayOverflow.yes, dur!"days"(3)); + + assert(funcFwd(Date(2009, 12, 25)) == Date(2011, 2, 28)); + assert(funcFwd(Date(2009, 12, 26)) == Date(2011, 3, 1)); + assert(funcFwd(Date(2009, 12, 27)) == Date(2011, 3, 2)); + assert(funcFwd(Date(2009, 12, 28)) == Date(2011, 3, 3)); + assert(funcFwd(Date(2009, 12, 29)) == Date(2011, 3, 3)); + + assert(funcBwd(Date(2011, 2, 28)) == Date(2009, 12, 25)); + assert(funcBwd(Date(2011, 3, 1)) == Date(2009, 12, 26)); + assert(funcBwd(Date(2011, 3, 2)) == Date(2009, 12, 27)); + assert(funcBwd(Date(2011, 3, 3)) == Date(2009, 12, 28)); + assert(funcBwd(Date(2011, 3, 4)) == Date(2010, 1, 1)); + } + + assert(everyDuration!Date(1, 2, AllowDayOverflow.yes, dur!"hnsecs"(1)) !is null); + static assert(!__traits(compiles, everyDuration!TimeOfDay(1, 2, AllowDayOverflow.yes, dur!"hnsecs"(1)))); + assert(everyDuration!DateTime(1, 2, AllowDayOverflow.yes, dur!"hnsecs"(1)) !is null); + assert(everyDuration!SysTime(1, 2, AllowDayOverflow.yes, dur!"hnsecs"(1)) !is null); +} + + +/++ + A range over an $(LREF Interval). + + $(D IntervalRange) is only ever constructed by $(LREF Interval). However, when + it is constructed, it is given a function, $(D func), which is used to + generate the time points which are iterated over. $(D func) takes a time + point and returns a time point of the same type. For instance, + to iterate over all of the days in + the interval $(D Interval!Date), pass a function to $(LREF Interval)'s + $(D fwdRange) where that function took a $(REF Date,std,datetime,date) and + returned a $(REF Date,std,datetime,date) which was one day later. That + function would then be used by $(D IntervalRange)'s $(D popFront) to iterate + over the $(REF Date,std,datetime,date)s in the interval. + + If $(D dir == Direction.fwd), then a range iterates forward in time, whereas + if $(D dir == Direction.bwd), then it iterates backwards in time. So, if + $(D dir == Direction.fwd) then $(D front == interval.begin), whereas if + $(D dir == Direction.bwd) then $(D front == interval.end). $(D func) must + generate a time point going in the proper direction of iteration, or a + $(REF DateTimeException,std,datetime,date) will be thrown. So, to iterate + forward in time, the time point that $(D func) generates must be later in + time than the one passed to it. If it's either identical or earlier in time, + then a $(REF DateTimeException,std,datetime,date) will be thrown. To + iterate backwards, then the generated time point must be before the time + point which was passed in. + + If the generated time point is ever passed the edge of the range in the + proper direction, then the edge of that range will be used instead. So, if + iterating forward, and the generated time point is past the interval's + $(D end), then $(D front) becomes $(D end). If iterating backwards, and the + generated time point is before $(D begin), then $(D front) becomes + $(D begin). In either case, the range would then be empty. + + Also note that while normally the $(D begin) of an interval is included in + it and its $(D end) is excluded from it, if $(D dir == Direction.bwd), then + $(D begin) is treated as excluded and $(D end) is treated as included. This + allows for the same behavior in both directions. This works because none of + $(LREF Interval)'s functions which care about whether $(D begin) or $(D end) + is included or excluded are ever called by $(D IntervalRange). $(D interval) + returns a normal interval, regardless of whether $(D dir == Direction.fwd) + or if $(D dir == Direction.bwd), so any $(LREF Interval) functions which are + called on it which care about whether $(D begin) or $(D end) are included or + excluded will treat $(D begin) as included and $(D end) as excluded. + +/ +struct IntervalRange(TP, Direction dir) +if (isTimePoint!TP && dir != Direction.both) +{ +public: + + /++ + Params: + rhs = The $(D IntervalRange) to assign to this one. + +/ + ref IntervalRange opAssign(ref IntervalRange rhs) pure nothrow + { + _interval = rhs._interval; + _func = rhs._func; + return this; + } + + + /++ Ditto +/ + ref IntervalRange opAssign(IntervalRange rhs) pure nothrow + { + return this = rhs; + } + + + /++ + Whether this $(D IntervalRange) is empty. + +/ + @property bool empty() const pure nothrow + { + return _interval.empty; + } + + + /++ + The first time point in the range. + + Throws: + $(REF DateTimeException,std,datetime,date) if the range is empty. + +/ + @property TP front() const pure + { + _enforceNotEmpty(); + + static if (dir == Direction.fwd) + return _interval.begin; + else + return _interval.end; + } + + + /++ + Pops $(D front) from the range, using $(D func) to generate the next + time point in the range. If the generated time point is beyond the edge + of the range, then $(D front) is set to that edge, and the range is then + empty. So, if iterating forwards, and the generated time point is + greater than the interval's $(D end), then $(D front) is set to + $(D end). If iterating backwards, and the generated time point is less + than the interval's $(D begin), then $(D front) is set to $(D begin). + + Throws: + $(REF DateTimeException,std,datetime,date) if the range is empty + or if the generated time point is in the wrong direction (i.e. if + iterating forward and the generated time point is before $(D front), + or if iterating backwards and the generated time point is after + $(D front)). + +/ + void popFront() + { + _enforceNotEmpty(); + + static if (dir == Direction.fwd) + { + auto begin = _func(_interval.begin); + + if (begin > _interval.end) + begin = _interval.end; + + _enforceCorrectDirection(begin); + + _interval.begin = begin; + } + else + { + auto end = _func(_interval.end); + + if (end < _interval.begin) + end = _interval.begin; + + _enforceCorrectDirection(end); + + _interval.end = end; + } + } + + + /++ + Returns a copy of $(D this). + +/ + @property IntervalRange save() pure nothrow + { + return this; + } + + + /++ + The interval that this $(D IntervalRange) currently covers. + +/ + @property Interval!TP interval() const pure nothrow + { + return cast(Interval!TP)_interval; + } + + + /++ + The function used to generate the next time point in the range. + +/ + TP delegate(in TP) func() pure nothrow @property + { + return _func; + } + + + /++ + The $(D Direction) that this range iterates in. + +/ + @property Direction direction() const pure nothrow + { + return dir; + } + + +private: + + /+ + Params: + interval = The interval that this range covers. + func = The function used to generate the time points which are + iterated over. + +/ + this(in Interval!TP interval, TP delegate(in TP) func) pure nothrow + { + _func = func; + _interval = interval; + } + + + /+ + Throws: + $(REF DateTimeException,std,datetime,date) if this interval is + empty. + +/ + void _enforceNotEmpty(size_t line = __LINE__) const pure + { + if (empty) + throw new DateTimeException("Invalid operation for an empty IntervalRange.", __FILE__, line); + } + + + /+ + Throws: + $(REF DateTimeException,std,datetime,date) if $(D_PARAM newTP) is + in the wrong direction. + +/ + void _enforceCorrectDirection(in TP newTP, size_t line = __LINE__) const + { + import std.format : format; + + static if (dir == Direction.fwd) + { + enforce(newTP > _interval._begin, + new DateTimeException(format("Generated time point is before previous begin: prev [%s] new [%s]", + interval._begin, + newTP), + __FILE__, + line)); + } + else + { + enforce(newTP < _interval._end, + new DateTimeException(format("Generated time point is after previous end: prev [%s] new [%s]", + interval._end, + newTP), + __FILE__, + line)); + } + } + + + Interval!TP _interval; + TP delegate(in TP) _func; +} + +//Test that IntervalRange satisfies the range predicates that it's supposed to satisfy. +@safe unittest +{ + import std.datetime.date; + import std.datetime.systime; + import std.range.primitives; + + static assert(isInputRange!(IntervalRange!(Date, Direction.fwd))); + static assert(isForwardRange!(IntervalRange!(Date, Direction.fwd))); + + //Commented out due to bug http://d.puremagic.com/issues/show_bug.cgi?id=4895 + //static assert(!isOutputRange!(IntervalRange!(Date, Direction.fwd), Date)); + + static assert(!isBidirectionalRange!(IntervalRange!(Date, Direction.fwd))); + static assert(!isRandomAccessRange!(IntervalRange!(Date, Direction.fwd))); + static assert(!hasSwappableElements!(IntervalRange!(Date, Direction.fwd))); + static assert(!hasAssignableElements!(IntervalRange!(Date, Direction.fwd))); + static assert(!hasLength!(IntervalRange!(Date, Direction.fwd))); + static assert(!isInfinite!(IntervalRange!(Date, Direction.fwd))); + static assert(!hasSlicing!(IntervalRange!(Date, Direction.fwd))); + + static assert(is(ElementType!(IntervalRange!(Date, Direction.fwd)) == Date)); + static assert(is(ElementType!(IntervalRange!(TimeOfDay, Direction.fwd)) == TimeOfDay)); + static assert(is(ElementType!(IntervalRange!(DateTime, Direction.fwd)) == DateTime)); + static assert(is(ElementType!(IntervalRange!(SysTime, Direction.fwd)) == SysTime)); +} + +//Test construction of IntervalRange. +@safe unittest +{ + import std.datetime.date; + import std.datetime.systime; + + { + Date dateFunc(in Date date) { return date; } + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto ir = IntervalRange!(Date, Direction.fwd)(interval, &dateFunc); + } + + { + TimeOfDay todFunc(in TimeOfDay tod) { return tod; } + auto interval = Interval!TimeOfDay(TimeOfDay(12, 1, 7), TimeOfDay(14, 0, 0)); + auto ir = IntervalRange!(TimeOfDay, Direction.fwd)(interval, &todFunc); + } + + { + DateTime dtFunc(in DateTime dt) { return dt; } + auto interval = Interval!DateTime(DateTime(2010, 7, 4, 12, 1, 7), DateTime(2012, 1, 7, 14, 0, 0)); + auto ir = IntervalRange!(DateTime, Direction.fwd)(interval, &dtFunc); + } + + { + SysTime stFunc(in SysTime st) { return cast(SysTime) st; } + auto interval = Interval!SysTime(SysTime(DateTime(2010, 7, 4, 12, 1, 7)), + SysTime(DateTime(2012, 1, 7, 14, 0, 0))); + auto ir = IntervalRange!(SysTime, Direction.fwd)(interval, &stFunc); + } +} + +//Test IntervalRange's empty(). +@system unittest +{ + import std.datetime.date; + + //fwd + { + auto range = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 21)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)); + + assert(!range.empty); + range.popFront(); + assert(range.empty); + + const cRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)); + assert(!cRange.empty); + + //Apparently, creating an immutable IntervalRange!Date doesn't work, so we can't test if + //empty works with it. However, since an immutable range is pretty useless, it's no great loss. + } + + //bwd + { + auto range = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 21)).bwdRange( + everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)); + + assert(!range.empty); + range.popFront(); + assert(range.empty); + + const cRange = Interval!Date( Date(2010, 7, 4), Date(2012, 1, 7)).bwdRange( + everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)); + assert(!cRange.empty); + + //Apparently, creating an immutable IntervalRange!Date doesn't work, so we can't test if + //empty works with it. However, since an immutable range is pretty useless, it's no great loss. + } +} + +//Test IntervalRange's front. +@system unittest +{ + import std.datetime.date; + + //fwd + { + auto emptyRange = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 20)).fwdRange( + everyDayOfWeek!Date(DayOfWeek.wed), PopFirst.yes); + assertThrown!DateTimeException((in IntervalRange!(Date, Direction.fwd) range){range.front;}(emptyRange)); + + auto range = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).fwdRange(everyDayOfWeek!Date(DayOfWeek.wed)); + assert(range.front == Date(2010, 7, 4)); + + auto poppedRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).fwdRange( + everyDayOfWeek!Date(DayOfWeek.wed), PopFirst.yes); + assert(poppedRange.front == Date(2010, 7, 7)); + + const cRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)); + assert(cRange.front != Date.init); + } + + //bwd + { + auto emptyRange = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 20)).bwdRange( + everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed), PopFirst.yes); + assertThrown!DateTimeException((in IntervalRange!(Date, Direction.bwd) range){range.front;}(emptyRange)); + + auto range = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).bwdRange( + everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed)); + assert(range.front == Date(2012, 1, 7)); + + auto poppedRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).bwdRange( + everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed), PopFirst.yes); + assert(poppedRange.front == Date(2012, 1, 4)); + + const cRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).bwdRange( + everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)); + assert(cRange.front != Date.init); + } +} + +//Test IntervalRange's popFront(). +@system unittest +{ + import std.datetime.date; + import std.range.primitives : walkLength; + + //fwd + { + auto emptyRange = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 20)).fwdRange( + everyDayOfWeek!Date(DayOfWeek.wed), PopFirst.yes); + assertThrown!DateTimeException((IntervalRange!(Date, Direction.fwd) range){range.popFront();}(emptyRange)); + + auto range = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).fwdRange( + everyDayOfWeek!Date(DayOfWeek.wed), PopFirst.yes); + auto expected = range.front; + + foreach (date; range) + { + assert(date == expected); + expected += dur!"days"(7); + } + + assert(walkLength(range) == 79); + + const cRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)); + assert(cRange.front != Date.init); + } + + //bwd + { + auto emptyRange = Interval!Date(Date(2010, 9, 19), Date(2010, 9, 20)).bwdRange( + everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed), PopFirst.yes); + assertThrown!DateTimeException((IntervalRange!(Date, Direction.bwd) range){range.popFront();}(emptyRange)); + + auto range = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).bwdRange( + everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed), PopFirst.yes); + auto expected = range.front; + + foreach (date; range) + { + assert(date == expected); + expected += dur!"days"(-7); + } + + assert(walkLength(range) == 79); + + const cRange = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)).bwdRange( + everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)); + static assert(!__traits(compiles, cRange.popFront())); + } +} + +//Test IntervalRange's save. +@system unittest +{ + import std.datetime.date; + + //fwd + { + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto func = everyDayOfWeek!Date(DayOfWeek.fri); + auto range = interval.fwdRange(func); + + assert(range.save == range); + } + + //bwd + { + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto func = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri); + auto range = interval.bwdRange(func); + + assert(range.save == range); + } +} + +//Test IntervalRange's interval. +@system unittest +{ + import std.datetime.date; + + //fwd + { + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto func = everyDayOfWeek!Date(DayOfWeek.fri); + auto range = interval.fwdRange(func); + + assert(range.interval == interval); + + const cRange = range; + assert(!cRange.interval.empty); + } + + //bwd + { + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto func = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri); + auto range = interval.bwdRange(func); + + assert(range.interval == interval); + + const cRange = range; + assert(!cRange.interval.empty); + } +} + +//Test IntervalRange's func. +@system unittest +{ + import std.datetime.date; + + //fwd + { + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto func = everyDayOfWeek!Date(DayOfWeek.fri); + auto range = interval.fwdRange(func); + + assert(range.func == func); + } + + //bwd + { + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto func = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri); + auto range = interval.bwdRange(func); + + assert(range.func == func); + } +} + +//Test IntervalRange's direction. +@system unittest +{ + import std.datetime.date; + + //fwd + { + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto func = everyDayOfWeek!Date(DayOfWeek.fri); + auto range = interval.fwdRange(func); + + assert(range.direction == Direction.fwd); + + const cRange = range; + assert(cRange.direction == Direction.fwd); + } + + //bwd + { + auto interval = Interval!Date(Date(2010, 7, 4), Date(2012, 1, 7)); + auto func = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri); + auto range = interval.bwdRange(func); + + assert(range.direction == Direction.bwd); + + const cRange = range; + assert(cRange.direction == Direction.bwd); + } +} + + +/++ + A range over a $(D PosInfInterval). It is an infinite range. + + $(D PosInfIntervalRange) is only ever constructed by $(D PosInfInterval). + However, when it is constructed, it is given a function, $(D func), which + is used to generate the time points which are iterated over. $(D func) + takes a time point and returns a time point of the same type. For + instance, to iterate + over all of the days in the interval $(D PosInfInterval!Date), pass a + function to $(D PosInfInterval)'s $(D fwdRange) where that function took a + $(REF Date,std,datetime,date) and returned a $(REF Date,std,datetime,date) + which was one day later. That function would then be used by + $(D PosInfIntervalRange)'s $(D popFront) to iterate over the + $(REF Date,std,datetime,date)s in the interval - though obviously, since the + range is infinite, use a function such as $(D std.range.take) with it rather + than iterating over $(I all) of the dates. + + As the interval goes to positive infinity, the range is always iterated over + forwards, never backwards. $(D func) must generate a time point going in + the proper direction of iteration, or a + $(REF DateTimeException,std,datetime,date) will be thrown. So, the time + points that $(D func) generates must be later in time than the one passed to + it. If it's either identical or earlier in time, then a + $(REF DateTimeException,std,datetime,date) will be thrown. + +/ +struct PosInfIntervalRange(TP) +if (isTimePoint!TP) +{ +public: + + /++ + Params: + rhs = The $(D PosInfIntervalRange) to assign to this one. + +/ + ref PosInfIntervalRange opAssign(ref PosInfIntervalRange rhs) pure nothrow + { + _interval = rhs._interval; + _func = rhs._func; + return this; + } + + + /++ Ditto +/ + ref PosInfIntervalRange opAssign(PosInfIntervalRange rhs) pure nothrow + { + return this = rhs; + } + + + /++ + This is an infinite range, so it is never empty. + +/ + enum bool empty = false; + + + /++ + The first time point in the range. + +/ + @property TP front() const pure nothrow + { + return _interval.begin; + } + + + /++ + Pops $(D front) from the range, using $(D func) to generate the next + time point in the range. + + Throws: + $(REF DateTimeException,std,datetime,date) if the generated time + point is less than $(D front). + +/ + void popFront() + { + auto begin = _func(_interval.begin); + _enforceCorrectDirection(begin); + _interval.begin = begin; + } + + + /++ + Returns a copy of $(D this). + +/ + @property PosInfIntervalRange save() pure nothrow + { + return this; + } + + + /++ + The interval that this range currently covers. + +/ + @property PosInfInterval!TP interval() const pure nothrow + { + return cast(PosInfInterval!TP)_interval; + } + + + /++ + The function used to generate the next time point in the range. + +/ + TP delegate(in TP) func() pure nothrow @property + { + return _func; + } + + +private: + + /+ + Params: + interval = The interval that this range covers. + func = The function used to generate the time points which are + iterated over. + +/ + this(in PosInfInterval!TP interval, TP delegate(in TP) func) pure nothrow + { + _func = func; + _interval = interval; + } + + + /+ + Throws: + $(REF DateTimeException,std,datetime,date) if $(D_PARAM newTP) is + in the wrong direction. + +/ + void _enforceCorrectDirection(in TP newTP, size_t line = __LINE__) const + { + import std.format : format; + + enforce(newTP > _interval._begin, + new DateTimeException(format("Generated time point is before previous begin: prev [%s] new [%s]", + interval._begin, + newTP), + __FILE__, + line)); + } + + + PosInfInterval!TP _interval; + TP delegate(in TP) _func; +} + +//Test that PosInfIntervalRange satisfies the range predicates that it's supposed to satisfy. +@safe unittest +{ + import std.datetime.date; + import std.datetime.systime; + import std.range.primitives; + + static assert(isInputRange!(PosInfIntervalRange!Date)); + static assert(isForwardRange!(PosInfIntervalRange!Date)); + static assert(isInfinite!(PosInfIntervalRange!Date)); + + //Commented out due to bug http://d.puremagic.com/issues/show_bug.cgi?id=4895 + //static assert(!isOutputRange!(PosInfIntervalRange!Date, Date)); + static assert(!isBidirectionalRange!(PosInfIntervalRange!Date)); + static assert(!isRandomAccessRange!(PosInfIntervalRange!Date)); + static assert(!hasSwappableElements!(PosInfIntervalRange!Date)); + static assert(!hasAssignableElements!(PosInfIntervalRange!Date)); + static assert(!hasLength!(PosInfIntervalRange!Date)); + static assert(!hasSlicing!(PosInfIntervalRange!Date)); + + static assert(is(ElementType!(PosInfIntervalRange!Date) == Date)); + static assert(is(ElementType!(PosInfIntervalRange!TimeOfDay) == TimeOfDay)); + static assert(is(ElementType!(PosInfIntervalRange!DateTime) == DateTime)); + static assert(is(ElementType!(PosInfIntervalRange!SysTime) == SysTime)); +} + +//Test construction of PosInfIntervalRange. +@safe unittest +{ + import std.datetime.date; + import std.datetime.systime; + + { + Date dateFunc(in Date date) { return date; } + auto posInfInterval = PosInfInterval!Date(Date(2010, 7, 4)); + auto ir = PosInfIntervalRange!Date(posInfInterval, &dateFunc); + } + + { + TimeOfDay todFunc(in TimeOfDay tod) { return tod; } + auto posInfInterval = PosInfInterval!TimeOfDay(TimeOfDay(12, 1, 7)); + auto ir = PosInfIntervalRange!(TimeOfDay)(posInfInterval, &todFunc); + } + + { + DateTime dtFunc(in DateTime dt) { return dt; } + auto posInfInterval = PosInfInterval!DateTime(DateTime(2010, 7, 4, 12, 1, 7)); + auto ir = PosInfIntervalRange!(DateTime)(posInfInterval, &dtFunc); + } + + { + SysTime stFunc(in SysTime st) { return cast(SysTime) st; } + auto posInfInterval = PosInfInterval!SysTime(SysTime(DateTime(2010, 7, 4, 12, 1, 7))); + auto ir = PosInfIntervalRange!SysTime(posInfInterval, &stFunc); + } +} + +//Test PosInfIntervalRange's front. +@system unittest +{ + import std.datetime.date; + + auto range = PosInfInterval!Date(Date(2010, 7, 4)).fwdRange(everyDayOfWeek!Date(DayOfWeek.wed)); + assert(range.front == Date(2010, 7, 4)); + + auto poppedRange = PosInfInterval!Date(Date(2010, 7, 4)).fwdRange(everyDayOfWeek!Date(DayOfWeek.wed), PopFirst.yes); + assert(poppedRange.front == Date(2010, 7, 7)); + + const cRange = PosInfInterval!Date(Date(2010, 7, 4)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)); + assert(cRange.front != Date.init); +} + +//Test PosInfIntervalRange's popFront(). +@system unittest +{ + import std.datetime.date; + import std.range : take; + + auto range = PosInfInterval!Date(Date(2010, 7, 4)).fwdRange(everyDayOfWeek!Date(DayOfWeek.wed), PopFirst.yes); + auto expected = range.front; + + foreach (date; take(range, 79)) + { + assert(date == expected); + expected += dur!"days"(7); + } + + const cRange = PosInfInterval!Date(Date(2010, 7, 4)).fwdRange(everyDayOfWeek!Date(DayOfWeek.fri)); + static assert(!__traits(compiles, cRange.popFront())); +} + +//Test PosInfIntervalRange's save. +@system unittest +{ + import std.datetime.date; + + auto interval = PosInfInterval!Date(Date(2010, 7, 4)); + auto func = everyDayOfWeek!Date(DayOfWeek.fri); + auto range = interval.fwdRange(func); + + assert(range.save == range); +} + +//Test PosInfIntervalRange's interval. +@system unittest +{ + import std.datetime.date; + + auto interval = PosInfInterval!Date(Date(2010, 7, 4)); + auto func = everyDayOfWeek!Date(DayOfWeek.fri); + auto range = interval.fwdRange(func); + + assert(range.interval == interval); + + const cRange = range; + assert(!cRange.interval.empty); +} + +//Test PosInfIntervalRange's func. +@system unittest +{ + import std.datetime.date; + + auto interval = PosInfInterval!Date(Date(2010, 7, 4)); + auto func = everyDayOfWeek!Date(DayOfWeek.fri); + auto range = interval.fwdRange(func); + + assert(range.func == func); +} + + +/++ + A range over a $(D NegInfInterval). It is an infinite range. + + $(D NegInfIntervalRange) is only ever constructed by $(D NegInfInterval). + However, when it is constructed, it is given a function, $(D func), which + is used to generate the time points which are iterated over. $(D func) + takes a time point and returns a time point of the same type. For + instance, to iterate over all of the days in the interval + $(D NegInfInterval!Date), pass a function to $(D NegInfInterval)'s + $(D bwdRange) where that function took a $(REF Date,std,datetime,date) and + returned a $(REF Date,std,datetime,date) which was one day earlier. That + function would then be used by $(D NegInfIntervalRange)'s $(D popFront) to + iterate over the $(REF Date,std,datetime,date)s in the interval - though + obviously, since the range is infinite, use a function such as + $(D std.range.take) with it rather than iterating over $(I all) of the dates. + + As the interval goes to negative infinity, the range is always iterated over + backwards, never forwards. $(D func) must generate a time point going in + the proper direction of iteration, or a + $(REF DateTimeException,std,datetime,date) will be thrown. So, the time + points that $(D func) generates must be earlier in time than the one passed + to it. If it's either identical or later in time, then a + $(REF DateTimeException,std,datetime,date) will be thrown. + + Also note that while normally the $(D end) of an interval is excluded from + it, $(D NegInfIntervalRange) treats it as if it were included. This allows + for the same behavior as with $(D PosInfIntervalRange). This works + because none of $(D NegInfInterval)'s functions which care about whether + $(D end) is included or excluded are ever called by + $(D NegInfIntervalRange). $(D interval) returns a normal interval, so any + $(D NegInfInterval) functions which are called on it which care about + whether $(D end) is included or excluded will treat $(D end) as excluded. + +/ +struct NegInfIntervalRange(TP) +if (isTimePoint!TP) +{ +public: + + /++ + Params: + rhs = The $(D NegInfIntervalRange) to assign to this one. + +/ + ref NegInfIntervalRange opAssign(ref NegInfIntervalRange rhs) pure nothrow + { + _interval = rhs._interval; + _func = rhs._func; + + return this; + } + + + /++ Ditto +/ + ref NegInfIntervalRange opAssign(NegInfIntervalRange rhs) pure nothrow + { + return this = rhs; + } + + + /++ + This is an infinite range, so it is never empty. + +/ + enum bool empty = false; + + + /++ + The first time point in the range. + +/ + @property TP front() const pure nothrow + { + return _interval.end; + } + + + /++ + Pops $(D front) from the range, using $(D func) to generate the next + time point in the range. + + Throws: + $(REF DateTimeException,std,datetime,date) if the generated time + point is greater than $(D front). + +/ + void popFront() + { + auto end = _func(_interval.end); + _enforceCorrectDirection(end); + _interval.end = end; + } + + + /++ + Returns a copy of $(D this). + +/ + @property NegInfIntervalRange save() pure nothrow + { + return this; + } + + + /++ + The interval that this range currently covers. + +/ + @property NegInfInterval!TP interval() const pure nothrow + { + return cast(NegInfInterval!TP)_interval; + } + + + /++ + The function used to generate the next time point in the range. + +/ + TP delegate(in TP) func() pure nothrow @property + { + return _func; + } + + +private: + + /+ + Params: + interval = The interval that this range covers. + func = The function used to generate the time points which are + iterated over. + +/ + this(in NegInfInterval!TP interval, TP delegate(in TP) func) pure nothrow + { + _func = func; + _interval = interval; + } + + + /+ + Throws: + $(REF DateTimeException,std,datetime,date) if $(D_PARAM newTP) is + in the wrong direction. + +/ + void _enforceCorrectDirection(in TP newTP, size_t line = __LINE__) const + { + import std.format : format; + + enforce(newTP < _interval._end, + new DateTimeException(format("Generated time point is before previous end: prev [%s] new [%s]", + interval._end, + newTP), + __FILE__, + line)); + } + + + NegInfInterval!TP _interval; + TP delegate(in TP) _func; +} + +//Test that NegInfIntervalRange satisfies the range predicates that it's supposed to satisfy. +@safe unittest +{ + import std.datetime.date; + import std.range.primitives; + + static assert(isInputRange!(NegInfIntervalRange!Date)); + static assert(isForwardRange!(NegInfIntervalRange!Date)); + static assert(isInfinite!(NegInfIntervalRange!Date)); + + //Commented out due to bug http://d.puremagic.com/issues/show_bug.cgi?id=4895 + //static assert(!isOutputRange!(NegInfIntervalRange!Date, Date)); + static assert(!isBidirectionalRange!(NegInfIntervalRange!Date)); + static assert(!isRandomAccessRange!(NegInfIntervalRange!Date)); + static assert(!hasSwappableElements!(NegInfIntervalRange!Date)); + static assert(!hasAssignableElements!(NegInfIntervalRange!Date)); + static assert(!hasLength!(NegInfIntervalRange!Date)); + static assert(!hasSlicing!(NegInfIntervalRange!Date)); + + static assert(is(ElementType!(NegInfIntervalRange!Date) == Date)); + static assert(is(ElementType!(NegInfIntervalRange!TimeOfDay) == TimeOfDay)); + static assert(is(ElementType!(NegInfIntervalRange!DateTime) == DateTime)); +} + +//Test construction of NegInfIntervalRange. +@safe unittest +{ + import std.datetime.date; + import std.datetime.systime; + + { + Date dateFunc(in Date date) { return date; } + auto negInfInterval = NegInfInterval!Date(Date(2012, 1, 7)); + auto ir = NegInfIntervalRange!Date(negInfInterval, &dateFunc); + } + + { + TimeOfDay todFunc(in TimeOfDay tod) { return tod; } + auto negInfInterval = NegInfInterval!TimeOfDay(TimeOfDay(14, 0, 0)); + auto ir = NegInfIntervalRange!(TimeOfDay)(negInfInterval, &todFunc); + } + + { + DateTime dtFunc(in DateTime dt) { return dt; } + auto negInfInterval = NegInfInterval!DateTime(DateTime(2012, 1, 7, 14, 0, 0)); + auto ir = NegInfIntervalRange!(DateTime)(negInfInterval, &dtFunc); + } + + { + SysTime stFunc(in SysTime st) { return cast(SysTime)(st); } + auto negInfInterval = NegInfInterval!SysTime(SysTime(DateTime(2012, 1, 7, 14, 0, 0))); + auto ir = NegInfIntervalRange!(SysTime)(negInfInterval, &stFunc); + } +} + +//Test NegInfIntervalRange's front. +@system unittest +{ + import std.datetime.date; + + auto range = NegInfInterval!Date(Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed)); + assert(range.front == Date(2012, 1, 7)); + + auto poppedRange = NegInfInterval!Date(Date(2012, 1, 7)).bwdRange( + everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed), PopFirst.yes); + assert(poppedRange.front == Date(2012, 1, 4)); + + const cRange = NegInfInterval!Date(Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)); + assert(cRange.front != Date.init); +} + +//Test NegInfIntervalRange's popFront(). +@system unittest +{ + import std.datetime.date; + import std.range : take; + + auto range = NegInfInterval!Date(Date(2012, 1, 7)).bwdRange( + everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.wed), PopFirst.yes); + auto expected = range.front; + + foreach (date; take(range, 79)) + { + assert(date == expected); + expected += dur!"days"(-7); + } + + const cRange = NegInfInterval!Date(Date(2012, 1, 7)).bwdRange(everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri)); + static assert(!__traits(compiles, cRange.popFront())); +} + +//Test NegInfIntervalRange's save. +@system unittest +{ + import std.datetime.date; + + auto interval = NegInfInterval!Date(Date(2012, 1, 7)); + auto func = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri); + auto range = interval.bwdRange(func); + + assert(range.save == range); +} + +//Test NegInfIntervalRange's interval. +@system unittest +{ + import std.datetime.date; + + auto interval = NegInfInterval!Date(Date(2012, 1, 7)); + auto func = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri); + auto range = interval.bwdRange(func); + + assert(range.interval == interval); + + const cRange = range; + assert(!cRange.interval.empty); +} + +//Test NegInfIntervalRange's func. +@system unittest +{ + import std.datetime.date; + + auto interval = NegInfInterval!Date(Date(2012, 1, 7)); + auto func = everyDayOfWeek!(Date, Direction.bwd)(DayOfWeek.fri); + auto range = interval.bwdRange(func); + + assert(range.func == func); +} diff --git a/libphobos/src/std/datetime/package.d b/libphobos/src/std/datetime/package.d new file mode 100644 index 0000000..976d06d --- /dev/null +++ b/libphobos/src/std/datetime/package.d @@ -0,0 +1,733 @@ +// Written in the D programming language + +/++ + Module containing Date/Time functionality. + + This module provides: + $(UL + $(LI Types to represent points in time: + $(REF SysTime,std,_datetime,systime), + $(REF Date,std,_datetime,date), + $(REF TimeOfDay,std,_datetime,date), + $(REF DateTime,std,_datetime,date).) + $(LI Types to represent intervals of time.) + $(LI Types to represent ranges over intervals of time.) + $(LI Types to represent time zones (used by + $(REF SysTime,std,_datetime,systime)).) + $(LI A platform-independent, high precision stopwatch type: + $(LREF StopWatch)) + $(LI Benchmarking functions.) + $(LI Various helper functions.) + ) + + Closely related to std.datetime is $(D core.time), + and some of the time types used in std.datetime come from there - such as + $(REF Duration, core,time), $(REF TickDuration, core,time), and + $(REF FracSec, core,time). + core.time is publically imported into std.datetime, it isn't necessary + to import it separately. + + Three of the main concepts used in this module are time points, time + durations, and time intervals. + + A time point is a specific point in time. e.g. January 5th, 2010 + or 5:00. + + A time duration is a length of time with units. e.g. 5 days or 231 seconds. + + A time interval indicates a period of time associated with a fixed point in + time. It is either two time points associated with each other, + indicating the time starting at the first point up to, but not including, + the second point - e.g. [January 5th, 2010 - March 10th, 2010$(RPAREN) - or + it is a time point and a time duration associated with one another. e.g. + January 5th, 2010 and 5 days, indicating [January 5th, 2010 - + January 10th, 2010$(RPAREN). + + Various arithmetic operations are supported between time points and + durations (e.g. the difference between two time points is a time duration), + and ranges can be gotten from time intervals, so range-based operations may + be done on a series of time points. + + The types that the typical user is most likely to be interested in are + $(REF Date,std,_datetime,date) (if they want dates but don't care about + time), $(REF DateTime,std,_datetime,date) (if they want dates and times + but don't care about time zones), $(REF SysTime,std,_datetime,systime) (if + they want the date and time from the OS and/or do care about time zones), + and StopWatch (a platform-independent, high precision stop watch). + $(REF Date,std,_datetime,date) and $(REF DateTime,std,_datetime,date) are + optimized for calendar-based operations, while + $(REF SysTime,std,_datetime,systime) is designed for dealing with time from + the OS. Check out their specific documentation for more details. + + To get the current time, use $(REF Clock.currTime,std,_datetime,systime). + It will return the current time as a $(REF SysTime,std,_datetime,systime). To + print it, $(D toString) is sufficient, but if using $(D toISOString), + $(D toISOExtString), or $(D toSimpleString), use the corresponding + $(D fromISOString), $(D fromISOExtString), or $(D fromSimpleString) to + create a $(REF SysTime,std,_datetime,systime) from the string. + +-------------------- +auto currentTime = Clock.currTime(); +auto timeString = currentTime.toISOExtString(); +auto restoredTime = SysTime.fromISOExtString(timeString); +-------------------- + + Various functions take a string (or strings) to represent a unit of time + (e.g. $(D convert!("days", "hours")(numDays))). The valid strings to use + with such functions are $(D "years"), $(D "months"), $(D "weeks"), + $(D "days"), $(D "hours"), $(D "minutes"), $(D "seconds"), + $(D "msecs") (milliseconds), $(D "usecs") (microseconds), + $(D "hnsecs") (hecto-nanoseconds - i.e. 100 ns), or some subset thereof. + There are a few functions in core.time which take $(D "nsecs"), but because + nothing in std.datetime has precision greater than hnsecs, and very little + in core.time does, no functions in std.datetime accept $(D "nsecs"). + To remember which units are abbreviated and which aren't, + all units seconds and greater use their full names, and all + sub-second units are abbreviated (since they'd be rather long if they + weren't). + + Note: + $(REF DateTimeException,std,_datetime,date) is an alias for + $(REF TimeException, core,time), so you don't need to worry about + core.time functions and std.datetime functions throwing different + exception types (except in the rare case that they throw something other + than $(REF TimeException, core,time) or + $(REF DateTimeException,std,_datetime,date)). + + See_Also: + $(DDLINK intro-to-_datetime, Introduction to std.datetime, + Introduction to std._datetime)
+ $(HTTP en.wikipedia.org/wiki/ISO_8601, ISO 8601)
+ $(HTTP en.wikipedia.org/wiki/Tz_database, + Wikipedia entry on TZ Database)
+ $(HTTP en.wikipedia.org/wiki/List_of_tz_database_time_zones, + List of Time Zones)
+ + License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + Authors: Jonathan M Davis and Kato Shoichi + Source: $(PHOBOSSRC std/_datetime/package.d) ++/ +module std.datetime; + +public import core.time; +public import std.datetime.date; +public import std.datetime.interval; +public import std.datetime.systime; +public import std.datetime.timezone; + +import core.exception : AssertError; +import std.functional : unaryFun; +import std.traits; +import std.typecons : Flag, Yes, No; + + +// Verify module example. +@safe unittest +{ + auto currentTime = Clock.currTime(); + auto timeString = currentTime.toISOExtString(); + auto restoredTime = SysTime.fromISOExtString(timeString); +} + +// Verify Examples for core.time.Duration which couldn't be in core.time. +@safe unittest +{ + assert(std.datetime.Date(2010, 9, 7) + dur!"days"(5) == + std.datetime.Date(2010, 9, 12)); + + assert(std.datetime.Date(2010, 9, 7) - std.datetime.Date(2010, 10, 3) == + dur!"days"(-26)); +} + +@safe unittest +{ + import std.traits : hasUnsharedAliasing; + /* Issue 6642 */ + static assert(!hasUnsharedAliasing!Date); + static assert(!hasUnsharedAliasing!TimeOfDay); + static assert(!hasUnsharedAliasing!DateTime); + static assert(!hasUnsharedAliasing!SysTime); +} + + +//============================================================================== +// Everything after here will be deprecated after we have replacements which +// use MonoTime and Duration. +//============================================================================== + + +/++ + Used by StopWatch to indicate whether it should start immediately upon + construction. + + If set to $(D AutoStart.no), then the stopwatch is not started when it is + constructed. + + Otherwise, if set to $(D AutoStart.yes), then the stopwatch is started when + it is constructed. + +/ +alias AutoStart = Flag!"autoStart"; + + +/++ + $(RED This will be deprecated in 2.076. Please use + $(REF StopWatch,std,datetime,stopwatch) instead. It uses + $(REF Monotime,core,time) and $(REF Duration,core,time) rather + than $(REF TickDuration,core,time), which will also be deprecated in + 2.076.) + + $(D StopWatch) measures time as precisely as possible. + + This class uses a high-performance counter. On Windows systems, it uses + $(D QueryPerformanceCounter), and on Posix systems, it uses + $(D clock_gettime) if available, and $(D gettimeofday) otherwise. + + But the precision of $(D StopWatch) differs from system to system. It is + impossible to for it to be the same from system to system since the precision + of the system clock varies from system to system, and other system-dependent + and situation-dependent stuff (such as the overhead of a context switch + between threads) can also affect $(D StopWatch)'s accuracy. + +/ +@safe struct StopWatch +{ +public: + + /++ + Auto start with constructor. + +/ + this(AutoStart autostart) @nogc + { + if (autostart) + start(); + } + + @nogc @safe unittest + { + auto sw = StopWatch(Yes.autoStart); + sw.stop(); + } + + + /// + bool opEquals(const StopWatch rhs) const pure nothrow @nogc + { + return opEquals(rhs); + } + + /// ditto + bool opEquals(const ref StopWatch rhs) const pure nothrow @nogc + { + return _timeStart == rhs._timeStart && + _timeMeasured == rhs._timeMeasured; + } + + + /++ + Resets the stop watch. + +/ + void reset() @nogc + { + if (_flagStarted) + { + // Set current system time if StopWatch is measuring. + _timeStart = TickDuration.currSystemTick; + } + else + { + // Set zero if StopWatch is not measuring. + _timeStart.length = 0; + } + + _timeMeasured.length = 0; + } + + /// + @nogc @safe unittest + { + StopWatch sw; + sw.start(); + sw.stop(); + sw.reset(); + assert(sw.peek().to!("seconds", real)() == 0); + } + + + /++ + Starts the stop watch. + +/ + void start() @nogc + { + assert(!_flagStarted); + _flagStarted = true; + _timeStart = TickDuration.currSystemTick; + } + + @nogc @system unittest + { + StopWatch sw; + sw.start(); + auto t1 = sw.peek(); + bool doublestart = true; + try + sw.start(); + catch (AssertError e) + doublestart = false; + assert(!doublestart); + sw.stop(); + assert((t1 - sw.peek()).to!("seconds", real)() <= 0); + } + + + /++ + Stops the stop watch. + +/ + void stop() @nogc + { + assert(_flagStarted); + _flagStarted = false; + _timeMeasured += TickDuration.currSystemTick - _timeStart; + } + + @nogc @system unittest + { + StopWatch sw; + sw.start(); + sw.stop(); + auto t1 = sw.peek(); + bool doublestop = true; + try + sw.stop(); + catch (AssertError e) + doublestop = false; + assert(!doublestop); + assert((t1 - sw.peek()).to!("seconds", real)() == 0); + } + + + /++ + Peek at the amount of time which has passed since the stop watch was + started. + +/ + TickDuration peek() const @nogc + { + if (_flagStarted) + return TickDuration.currSystemTick - _timeStart + _timeMeasured; + + return _timeMeasured; + } + + @nogc @safe unittest + { + StopWatch sw; + sw.start(); + auto t1 = sw.peek(); + sw.stop(); + auto t2 = sw.peek(); + auto t3 = sw.peek(); + assert(t1 <= t2); + assert(t2 == t3); + } + + + /++ + Set the amount of time which has been measured since the stop watch was + started. + +/ + void setMeasured(TickDuration d) @nogc + { + reset(); + _timeMeasured = d; + } + + @nogc @safe unittest + { + StopWatch sw; + TickDuration t0; + t0.length = 100; + sw.setMeasured(t0); + auto t1 = sw.peek(); + assert(t0 == t1); + } + + + /++ + Confirm whether this stopwatch is measuring time. + +/ + bool running() @property const pure nothrow @nogc + { + return _flagStarted; + } + + @nogc @safe unittest + { + StopWatch sw1; + assert(!sw1.running); + sw1.start(); + assert(sw1.running); + sw1.stop(); + assert(!sw1.running); + StopWatch sw2 = Yes.autoStart; + assert(sw2.running); + sw2.stop(); + assert(!sw2.running); + sw2.start(); + assert(sw2.running); + } + + + + +private: + + // true if observing. + bool _flagStarted = false; + + // TickDuration at the time of StopWatch starting measurement. + TickDuration _timeStart; + + // Total time that StopWatch ran. + TickDuration _timeMeasured; +} + +/// +@safe unittest +{ + void writeln(S...)(S args){} + static void bar() {} + + StopWatch sw; + enum n = 100; + TickDuration[n] times; + TickDuration last = TickDuration.from!"seconds"(0); + foreach (i; 0 .. n) + { + sw.start(); //start/resume mesuring. + foreach (unused; 0 .. 1_000_000) + bar(); + sw.stop(); //stop/pause measuring. + //Return value of peek() after having stopped are the always same. + writeln((i + 1) * 1_000_000, " times done, lap time: ", + sw.peek().msecs, "[ms]"); + times[i] = sw.peek() - last; + last = sw.peek(); + } + real sum = 0; + // To get the number of seconds, + // use properties of TickDuration. + // (seconds, msecs, usecs, hnsecs) + foreach (t; times) + sum += t.hnsecs; + writeln("Average time: ", sum/n, " hnsecs"); +} + + +/++ + $(RED This will be deprecated in 2.076. Please use + $(REF benchmark,std,datetime,stopwatch) instead. It uses + $(REF Monotime,core,time) and $(REF Duration,core,time) rather + than $(REF TickDuration,core,time), which will also be deprecated in + 2.076.) + + Benchmarks code for speed assessment and comparison. + + Params: + fun = aliases of callable objects (e.g. function names). Each should + take no arguments. + n = The number of times each function is to be executed. + + Returns: + The amount of time (as a $(REF TickDuration, core,time)) that it took to + call each function $(D n) times. The first value is the length of time + that it took to call $(D fun[0]) $(D n) times. The second value is the + length of time it took to call $(D fun[1]) $(D n) times. Etc. + + Note that casting the TickDurations to $(REF Duration, core,time)s will make + the results easier to deal with (and it may change in the future that + benchmark will return an array of Durations rather than TickDurations). + + See_Also: + $(LREF measureTime) + +/ +TickDuration[fun.length] benchmark(fun...)(uint n) +{ + TickDuration[fun.length] result; + StopWatch sw; + sw.start(); + + foreach (i, unused; fun) + { + sw.reset(); + foreach (j; 0 .. n) + fun[i](); + result[i] = sw.peek(); + } + + return result; +} + +/// +@safe unittest +{ + import std.conv : to; + int a; + void f0() {} + void f1() {auto b = a;} + void f2() {auto b = to!string(a);} + auto r = benchmark!(f0, f1, f2)(10_000); + auto f0Result = to!Duration(r[0]); // time f0 took to run 10,000 times + auto f1Result = to!Duration(r[1]); // time f1 took to run 10,000 times + auto f2Result = to!Duration(r[2]); // time f2 took to run 10,000 times +} + +@safe unittest +{ + int a; + void f0() {} + //void f1() {auto b = to!(string)(a);} + void f2() {auto b = (a);} + auto r = benchmark!(f0, f2)(100); +} + + +/++ + Return value of benchmark with two functions comparing. + +/ +@safe struct ComparingBenchmarkResult +{ + /++ + Evaluation value + + This returns the evaluation value of performance as the ratio of + baseFunc's time over targetFunc's time. If performance is high, this + returns a high value. + +/ + @property real point() const pure nothrow + { + return _baseTime.length / cast(const real)_targetTime.length; + } + + + /++ + The time required of the base function + +/ + @property public TickDuration baseTime() const pure nothrow + { + return _baseTime; + } + + + /++ + The time required of the target function + +/ + @property public TickDuration targetTime() const pure nothrow + { + return _targetTime; + } + +private: + + this(TickDuration baseTime, TickDuration targetTime) pure nothrow + { + _baseTime = baseTime; + _targetTime = targetTime; + } + + TickDuration _baseTime; + TickDuration _targetTime; +} + + +/++ + $(RED This will be deprecated in 2.076. Please use + $(REF benchmark,std,datetime,stopwatch) instead. This function has + not been ported to $(REF Monotime,core,time) and + $(REF Duration,core,time), because it is a trivial wrapper around + benchmark.) + + Benchmark with two functions comparing. + + Params: + baseFunc = The function to become the base of the speed. + targetFunc = The function that wants to measure speed. + times = The number of times each function is to be executed. + +/ +ComparingBenchmarkResult comparingBenchmark(alias baseFunc, + alias targetFunc, + int times = 0xfff)() +{ + auto t = benchmark!(baseFunc, targetFunc)(times); + return ComparingBenchmarkResult(t[0], t[1]); +} + +/// +@safe unittest +{ + void f1x() {} + void f2x() {} + @safe void f1o() {} + @safe void f2o() {} + auto b1 = comparingBenchmark!(f1o, f2o, 1)(); // OK + //writeln(b1.point); +} + +//Bug# 8450 +@system unittest +{ + @safe void safeFunc() {} + @trusted void trustFunc() {} + @system void sysFunc() {} + auto safeResult = comparingBenchmark!((){safeFunc();}, (){safeFunc();})(); + auto trustResult = comparingBenchmark!((){trustFunc();}, (){trustFunc();})(); + auto sysResult = comparingBenchmark!((){sysFunc();}, (){sysFunc();})(); + auto mixedResult1 = comparingBenchmark!((){safeFunc();}, (){trustFunc();})(); + auto mixedResult2 = comparingBenchmark!((){trustFunc();}, (){sysFunc();})(); + auto mixedResult3 = comparingBenchmark!((){safeFunc();}, (){sysFunc();})(); +} + + +/++ + $(RED This will be deprecated in 2.076. Please use + $(REF StopWatch,std,datetime,stopwatch) instead. This function has + not been ported to $(REF Monotime,core,time) and + $(REF Duration,core,time), because it is a trivial wrapper around + StopWatch.) + + Function for starting to a stop watch time when the function is called + and stopping it when its return value goes out of scope and is destroyed. + + When the value that is returned by this function is destroyed, + $(D func) will run. $(D func) is a unary function that takes a + $(REF TickDuration, core,time). + + Example: +-------------------- +{ + auto mt = measureTime!((TickDuration a) + { /+ do something when the scope is exited +/ }); + // do something that needs to be timed +} +-------------------- + + which is functionally equivalent to + +-------------------- +{ + auto sw = StopWatch(Yes.autoStart); + scope(exit) + { + TickDuration a = sw.peek(); + /+ do something when the scope is exited +/ + } + // do something that needs to be timed +} +-------------------- + + See_Also: + $(LREF benchmark) ++/ +@safe auto measureTime(alias func)() +if (isSafe!((){StopWatch sw; unaryFun!func(sw.peek());})) +{ + struct Result + { + private StopWatch _sw = void; + this(AutoStart as) + { + _sw = StopWatch(as); + } + ~this() + { + unaryFun!(func)(_sw.peek()); + } + } + return Result(Yes.autoStart); +} + +auto measureTime(alias func)() +if (!isSafe!((){StopWatch sw; unaryFun!func(sw.peek());})) +{ + struct Result + { + private StopWatch _sw = void; + this(AutoStart as) + { + _sw = StopWatch(as); + } + ~this() + { + unaryFun!(func)(_sw.peek()); + } + } + return Result(Yes.autoStart); +} + +// Verify Example. +@safe unittest +{ + { + auto mt = measureTime!((TickDuration a) + { /+ do something when the scope is exited +/ }); + // do something that needs to be timed + } + + { + auto sw = StopWatch(Yes.autoStart); + scope(exit) + { + TickDuration a = sw.peek(); + /+ do something when the scope is exited +/ + } + // do something that needs to be timed + } +} + +@safe unittest +{ + import std.math : isNaN; + + @safe static void func(TickDuration td) + { + assert(!td.to!("seconds", real)().isNaN()); + } + + auto mt = measureTime!(func)(); + + /+ + with (measureTime!((a){assert(a.seconds);})) + { + // doSomething(); + // @@@BUG@@@ doesn't work yet. + } + +/ +} + +@safe unittest +{ + import std.math : isNaN; + + static void func(TickDuration td) + { + assert(!td.to!("seconds", real)().isNaN()); + } + + auto mt = measureTime!(func)(); + + /+ + with (measureTime!((a){assert(a.seconds);})) + { + // doSomething(); + // @@@BUG@@@ doesn't work yet. + } + +/ +} + +//Bug# 8450 +@system unittest +{ + @safe void safeFunc() {} + @trusted void trustFunc() {} + @system void sysFunc() {} + auto safeResult = measureTime!((a){safeFunc();})(); + auto trustResult = measureTime!((a){trustFunc();})(); + auto sysResult = measureTime!((a){sysFunc();})(); +} diff --git a/libphobos/src/std/datetime/stopwatch.d b/libphobos/src/std/datetime/stopwatch.d new file mode 100644 index 0000000..48e025b --- /dev/null +++ b/libphobos/src/std/datetime/stopwatch.d @@ -0,0 +1,428 @@ +// Written in the D programming language + +/++ + Module containing some basic benchmarking and timing functionality. + + For convenience, this module publicly imports $(MREF core,time). + + $(RED Unlike the other modules in std.datetime, this module is not currently + publicly imported in std.datetime.package, because the old + versions of this functionality which use + $(REF TickDuration,core,time) are in std.datetime.package and would + conflict with the symbols in this module. After the old symbols have + gone through the deprecation cycle and have been removed, then this + module will be publicly imported in std.datetime.package.) + + License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + Authors: Jonathan M Davis and Kato Shoichi + Source: $(PHOBOSSRC std/datetime/_stopwatch.d) ++/ +module std.datetime.stopwatch; + +public import core.time; +import std.typecons : Flag; + +/++ + Used by StopWatch to indicate whether it should start immediately upon + construction. + + If set to $(D AutoStart.no), then the StopWatch is not started when it is + constructed. + + Otherwise, if set to $(D AutoStart.yes), then the StopWatch is started when + it is constructed. + +/ +alias AutoStart = Flag!"autoStart"; + + +/++ + StopWatch is used to measure time just like one would do with a physical + stopwatch, including stopping, restarting, and/or resetting it. + + $(REF MonoTime,core,time) is used to hold the time, and it uses the system's + monotonic clock, which is high precision and never counts backwards (unlike + the wall clock time, which $(I can) count backwards, which is why + $(REF SysTime,std,datetime,systime) should not be used for timing). + + Note that the precision of StopWatch differs from system to system. It is + impossible for it to be the same for all systems, since the precision of the + system clock and other system-dependent and situation-dependent factors + (such as the overhead of a context switch between threads) varies from system + to system and can affect StopWatch's accuracy. + +/ +struct StopWatch +{ +public: + + /// + @system nothrow @nogc unittest + { + import core.thread : Thread; + + auto sw = StopWatch(AutoStart.yes); + + Duration t1 = sw.peek(); + Thread.sleep(usecs(1)); + Duration t2 = sw.peek(); + assert(t2 > t1); + + Thread.sleep(usecs(1)); + sw.stop(); + + Duration t3 = sw.peek(); + assert(t3 > t2); + Duration t4 = sw.peek(); + assert(t3 == t4); + + sw.start(); + Thread.sleep(usecs(1)); + + Duration t5 = sw.peek(); + assert(t5 > t4); + + // If stopping or resetting the StopWatch is not required, then + // MonoTime can easily be used by itself without StopWatch. + auto before = MonoTime.currTime; + // do stuff... + auto timeElapsed = MonoTime.currTime - before; + } + + /++ + Constructs a StopWatch. Whether it starts immediately depends on the + $(LREF AutoStart) argument. + + If $(D StopWatch.init) is used, then the constructed StopWatch isn't + running (and can't be, since no constructor ran). + +/ + this(AutoStart autostart) @safe nothrow @nogc + { + if (autostart) + start(); + } + + /// + @system nothrow @nogc unittest + { + import core.thread : Thread; + + { + auto sw = StopWatch(AutoStart.yes); + assert(sw.running); + Thread.sleep(usecs(1)); + assert(sw.peek() > Duration.zero); + } + { + auto sw = StopWatch(AutoStart.no); + assert(!sw.running); + Thread.sleep(usecs(1)); + assert(sw.peek() == Duration.zero); + } + { + StopWatch sw; + assert(!sw.running); + Thread.sleep(usecs(1)); + assert(sw.peek() == Duration.zero); + } + + assert(StopWatch.init == StopWatch(AutoStart.no)); + assert(StopWatch.init != StopWatch(AutoStart.yes)); + } + + + /++ + Resets the StopWatch. + + The StopWatch can be reset while it's running, and resetting it while + it's running will not cause it to stop. + +/ + void reset() @safe nothrow @nogc + { + if (_running) + _timeStarted = MonoTime.currTime; + _ticksElapsed = 0; + } + + /// + @system nothrow @nogc unittest + { + import core.thread : Thread; + + auto sw = StopWatch(AutoStart.yes); + Thread.sleep(usecs(1)); + sw.stop(); + assert(sw.peek() > Duration.zero); + sw.reset(); + assert(sw.peek() == Duration.zero); + } + + @system nothrow @nogc unittest + { + import core.thread : Thread; + + auto sw = StopWatch(AutoStart.yes); + Thread.sleep(msecs(1)); + assert(sw.peek() > msecs(1)); + immutable before = MonoTime.currTime; + + // Just in case the system clock is slow enough or the system is fast + // enough for the call to MonoTime.currTime inside of reset to get + // the same that we just got by calling MonoTime.currTime. + Thread.sleep(usecs(1)); + + sw.reset(); + assert(sw.peek() < msecs(1)); + assert(sw._timeStarted > before); + assert(sw._timeStarted <= MonoTime.currTime); + } + + + /++ + Starts the StopWatch. + + start should not be called if the StopWatch is already running. + +/ + void start() @safe nothrow @nogc + in { assert(!_running, "start was called when the StopWatch was already running."); } + body + { + _running = true; + _timeStarted = MonoTime.currTime; + } + + /// + @system nothrow @nogc unittest + { + import core.thread : Thread; + + StopWatch sw; + assert(!sw.running); + assert(sw.peek() == Duration.zero); + sw.start(); + assert(sw.running); + Thread.sleep(usecs(1)); + assert(sw.peek() > Duration.zero); + } + + + /++ + Stops the StopWatch. + + stop should not be called if the StopWatch is not running. + +/ + void stop() @safe nothrow @nogc + in { assert(_running, "stop was called when the StopWatch was not running."); } + body + { + _running = false; + _ticksElapsed += MonoTime.currTime.ticks - _timeStarted.ticks; + } + + /// + @system nothrow @nogc unittest + { + import core.thread : Thread; + + auto sw = StopWatch(AutoStart.yes); + assert(sw.running); + Thread.sleep(usecs(1)); + immutable t1 = sw.peek(); + assert(t1 > Duration.zero); + + sw.stop(); + assert(!sw.running); + immutable t2 = sw.peek(); + assert(t2 >= t1); + immutable t3 = sw.peek(); + assert(t2 == t3); + } + + + /++ + Peek at the amount of time that the 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 + time since it was started last. + + Calling $(LREF reset) will reset this to $(D Duration.zero). + +/ + Duration peek() @safe const nothrow @nogc + { + enum hnsecsPerSecond = convert!("seconds", "hnsecs")(1); + immutable hnsecsMeasured = convClockFreq(_ticksElapsed, MonoTime.ticksPerSecond, hnsecsPerSecond); + return _running ? MonoTime.currTime - _timeStarted + hnsecs(hnsecsMeasured) + : hnsecs(hnsecsMeasured); + } + + /// + @system nothrow @nogc unittest + { + import core.thread : Thread; + + auto sw = StopWatch(AutoStart.no); + assert(sw.peek() == Duration.zero); + sw.start(); + + Thread.sleep(usecs(1)); + assert(sw.peek() >= usecs(1)); + + Thread.sleep(usecs(1)); + assert(sw.peek() >= usecs(2)); + + sw.stop(); + immutable stopped = sw.peek(); + Thread.sleep(usecs(1)); + assert(sw.peek() == stopped); + + sw.start(); + Thread.sleep(usecs(1)); + assert(sw.peek() > stopped); + } + + @safe nothrow @nogc unittest + { + assert(StopWatch.init.peek() == Duration.zero); + } + + + /++ + Sets the total time which the StopWatch has been running (i.e. what peek + returns). + + The StopWatch does not have to be stopped for setTimeElapsed to be + called, nor will calling it cause the StopWatch to stop. + +/ + void setTimeElapsed(Duration timeElapsed) @safe nothrow @nogc + { + enum hnsecsPerSecond = convert!("seconds", "hnsecs")(1); + _ticksElapsed = convClockFreq(timeElapsed.total!"hnsecs", hnsecsPerSecond, MonoTime.ticksPerSecond); + _timeStarted = MonoTime.currTime; + } + + /// + @system nothrow @nogc unittest + { + import core.thread : Thread; + + StopWatch sw; + sw.setTimeElapsed(hours(1)); + + // As discussed in MonoTime's documentation, converting between + // Duration and ticks is not exact, though it will be close. + // How exact it is depends on the frequency/resolution of the + // system's monotonic clock. + assert(abs(sw.peek() - hours(1)) < usecs(1)); + + sw.start(); + Thread.sleep(usecs(1)); + assert(sw.peek() > hours(1) + usecs(1)); + } + + + /++ + Returns whether this StopWatch is currently running. + +/ + @property bool running() @safe const pure nothrow @nogc + { + return _running; + } + + /// + @safe nothrow @nogc unittest + { + StopWatch sw; + assert(!sw.running); + sw.start(); + assert(sw.running); + sw.stop(); + assert(!sw.running); + } + + +private: + + // We track the ticks for the elapsed time rather than a Duration so that we + // don't lose any precision. + + bool _running = false; // Whether the StopWatch is currently running + MonoTime _timeStarted; // The time the StopWatch started measuring (i.e. when it was started or reset). + long _ticksElapsed; // Total time that the StopWatch ran before it was stopped last. +} + + +/++ + Benchmarks code for speed assessment and comparison. + + Params: + fun = aliases of callable objects (e.g. function names). Each callable + object should take no arguments. + n = The number of times each function is to be executed. + + Returns: + The amount of time (as a $(REF Duration,core,time)) that it took to call + each function $(D n) times. The first value is the length of time that + it took to call $(D fun[0]) $(D n) times. The second value is the length + of time it took to call $(D fun[1]) $(D n) times. Etc. + +/ +Duration[fun.length] benchmark(fun...)(uint n) +{ + Duration[fun.length] result; + auto sw = StopWatch(AutoStart.yes); + + foreach (i, unused; fun) + { + sw.reset(); + foreach (_; 0 .. n) + fun[i](); + result[i] = sw.peek(); + } + + return result; +} + +/// +@safe unittest +{ + import std.conv : to; + + int a; + void f0() {} + void f1() { auto b = a; } + void f2() { auto b = to!string(a); } + auto r = benchmark!(f0, f1, f2)(10_000); + Duration f0Result = r[0]; // time f0 took to run 10,000 times + Duration f1Result = r[1]; // time f1 took to run 10,000 times + Duration f2Result = r[2]; // time f2 took to run 10,000 times +} + +@safe nothrow unittest +{ + import std.conv : to; + + int a; + void f0() nothrow {} + void f1() nothrow { auto b = to!string(a); } + auto r = benchmark!(f0, f1)(1000); + version (GNU) + assert(r[0] >= Duration.zero); + else + assert(r[0] > Duration.zero); + assert(r[1] > Duration.zero); + assert(r[1] > r[0]); + assert(r[0] < seconds(1)); + assert(r[1] < seconds(1)); +} + +@safe nothrow @nogc unittest +{ + int f0Count; + int f1Count; + int f2Count; + void f0() nothrow @nogc { ++f0Count; } + void f1() nothrow @nogc { ++f1Count; } + void f2() nothrow @nogc { ++f2Count; } + auto r = benchmark!(f0, f1, f2)(552); + assert(f0Count == 552); + assert(f1Count == 552); + assert(f2Count == 552); +} diff --git a/libphobos/src/std/datetime/systime.d b/libphobos/src/std/datetime/systime.d new file mode 100644 index 0000000..a15c125 --- /dev/null +++ b/libphobos/src/std/datetime/systime.d @@ -0,0 +1,11151 @@ +// Written in the D programming language + +/++ + License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + Authors: Jonathan M Davis + Source: $(PHOBOSSRC std/datetime/_systime.d) ++/ +module std.datetime.systime; + +import core.time; +import std.datetime.date; +import std.datetime.timezone; +import std.exception : enforce; +import std.format : format; +import std.range.primitives; +import std.traits : isIntegral, isSigned, isSomeString, Unqual; + +version (Windows) +{ + import core.stdc.time : time_t; + import core.sys.windows.windows; + import core.sys.windows.winsock2; +} +else version (Posix) +{ + import core.sys.posix.signal : timespec; + import core.sys.posix.sys.types : time_t; +} + +version (unittest) +{ + import core.exception : AssertError; + import std.exception : assertThrown; +} + + +@safe unittest +{ + initializeTests(); +} + + +/++ + Effectively a namespace to make it clear that the methods it contains are + getting the time from the system clock. It cannot be instantiated. + +/ +final class Clock +{ +public: + + /++ + Returns the current time in the given time zone. + + Params: + clockType = The $(REF ClockType, core,time) indicates which system + clock to use to get the current time. Very few programs + need to use anything other than the default. + tz = The time zone for the SysTime that's returned. + + Throws: + $(REF DateTimeException,std,datetime,date) if it fails to get the + time. + +/ + static SysTime currTime(ClockType clockType = ClockType.normal)(immutable TimeZone tz = LocalTime()) @safe + { + return SysTime(currStdTime!clockType, tz); + } + + @safe unittest + { + import std.format : format; + import std.stdio : writefln; + assert(currTime().timezone is LocalTime()); + assert(currTime(UTC()).timezone is UTC()); + + // core.stdc.time.time does not always use unix time on Windows systems. + // In particular, dmc does not use unix time. If we can guarantee that + // the MS runtime uses unix time, then we may be able run this test + // then, but for now, we're just not going to run this test on Windows. + version (Posix) + { + static import core.stdc.time; + static import std.math; + immutable unixTimeD = currTime().toUnixTime(); + immutable unixTimeC = core.stdc.time.time(null); + assert(std.math.abs(unixTimeC - unixTimeD) <= 2); + } + + auto norm1 = Clock.currTime; + auto norm2 = Clock.currTime(UTC()); + assert(norm1 <= norm2, format("%s %s", norm1, norm2)); + assert(abs(norm1 - norm2) <= seconds(2)); + + import std.meta : AliasSeq; + foreach (ct; AliasSeq!(ClockType.coarse, ClockType.precise, ClockType.second)) + { + scope(failure) writefln("ClockType.%s", ct); + auto value1 = Clock.currTime!ct; + auto value2 = Clock.currTime!ct(UTC()); + assert(value1 <= value2, format("%s %s", value1, value2)); + assert(abs(value1 - value2) <= seconds(2)); + } + } + + + /++ + Returns the number of hnsecs since midnight, January 1st, 1 A.D. for the + current time. + + Params: + clockType = The $(REF ClockType, core,time) indicates which system + clock to use to get the current time. Very few programs + need to use anything other than the default. + + Throws: + $(REF DateTimeException,std,datetime,date) if it fails to get the + time. + +/ + static @property long currStdTime(ClockType clockType = ClockType.normal)() @trusted + { + static if (clockType != ClockType.coarse && + clockType != ClockType.normal && + clockType != ClockType.precise && + clockType != ClockType.second) + { + static assert(0, format("ClockType.%s is not supported by Clock.currTime or Clock.currStdTime", clockType)); + } + + version (Windows) + { + FILETIME fileTime; + GetSystemTimeAsFileTime(&fileTime); + immutable result = FILETIMEToStdTime(&fileTime); + static if (clockType == ClockType.second) + { + // Ideally, this would use core.std.time.time, but the C runtime + // has to be using unix time for that to work, and that's not + // guaranteed on Windows. Digital Mars does not use unix time. + // MS may or may not. If it does, then this can be made to use + // core.stdc.time for MS, but for now, we'll leave it like this. + return convert!("seconds", "hnsecs")(convert!("hnsecs", "seconds")(result)); + } + else + return result; + } + else version (Posix) + { + static import core.stdc.time; + enum hnsecsToUnixEpoch = unixTimeToStdTime(0); + + version (OSX) + { + static if (clockType == ClockType.second) + return unixTimeToStdTime(core.stdc.time.time(null)); + else + { + import core.sys.posix.sys.time : gettimeofday, timeval; + timeval tv; + if (gettimeofday(&tv, null) != 0) + throw new TimeException("Call to gettimeofday() failed"); + return convert!("seconds", "hnsecs")(tv.tv_sec) + + convert!("usecs", "hnsecs")(tv.tv_usec) + + hnsecsToUnixEpoch; + } + } + else version (linux) + { + static if (clockType == ClockType.second) + return unixTimeToStdTime(core.stdc.time.time(null)); + else + { + import core.sys.linux.time : CLOCK_REALTIME_COARSE; + import core.sys.posix.time : clock_gettime, CLOCK_REALTIME; + static if (clockType == ClockType.coarse) alias clockArg = CLOCK_REALTIME_COARSE; + else static if (clockType == ClockType.normal) alias clockArg = CLOCK_REALTIME; + else static if (clockType == ClockType.precise) alias clockArg = CLOCK_REALTIME; + else static assert(0, "Previous static if is wrong."); + timespec ts; + if (clock_gettime(clockArg, &ts) != 0) + throw new TimeException("Call to clock_gettime() failed"); + return convert!("seconds", "hnsecs")(ts.tv_sec) + + ts.tv_nsec / 100 + + hnsecsToUnixEpoch; + } + } + else version (FreeBSD) + { + import core.sys.freebsd.time : clock_gettime, CLOCK_REALTIME, + CLOCK_REALTIME_FAST, CLOCK_REALTIME_PRECISE, CLOCK_SECOND; + static if (clockType == ClockType.coarse) alias clockArg = CLOCK_REALTIME_FAST; + else static if (clockType == ClockType.normal) alias clockArg = CLOCK_REALTIME; + else static if (clockType == ClockType.precise) alias clockArg = CLOCK_REALTIME_PRECISE; + else static if (clockType == ClockType.second) alias clockArg = CLOCK_SECOND; + else static assert(0, "Previous static if is wrong."); + timespec ts; + if (clock_gettime(clockArg, &ts) != 0) + throw new TimeException("Call to clock_gettime() failed"); + return convert!("seconds", "hnsecs")(ts.tv_sec) + + ts.tv_nsec / 100 + + hnsecsToUnixEpoch; + } + else version (NetBSD) + { + static if (clockType == ClockType.second) + return unixTimeToStdTime(core.stdc.time.time(null)); + else + { + import core.sys.posix.sys.time : gettimeofday, timeval; + timeval tv; + if (gettimeofday(&tv, null) != 0) + throw new TimeException("Call to gettimeofday() failed"); + return convert!("seconds", "hnsecs")(tv.tv_sec) + + convert!("usecs", "hnsecs")(tv.tv_usec) + + hnsecsToUnixEpoch; + } + } + else version (Solaris) + { + static if (clockType == ClockType.second) + return unixTimeToStdTime(core.stdc.time.time(null)); + else + { + import core.sys.solaris.time : CLOCK_REALTIME; + static if (clockType == ClockType.coarse) alias clockArg = CLOCK_REALTIME; + else static if (clockType == ClockType.normal) alias clockArg = CLOCK_REALTIME; + else static if (clockType == ClockType.precise) alias clockArg = CLOCK_REALTIME; + else static assert(0, "Previous static if is wrong."); + timespec ts; + if (clock_gettime(clockArg, &ts) != 0) + throw new TimeException("Call to clock_gettime() failed"); + return convert!("seconds", "hnsecs")(ts.tv_sec) + + ts.tv_nsec / 100 + + hnsecsToUnixEpoch; + } + } + else static assert(0, "Unsupported OS"); + } + else static assert(0, "Unsupported OS"); + } + + @safe unittest + { + import std.format : format; + import std.math : abs; + import std.meta : AliasSeq; + import std.stdio : writefln; + enum limit = convert!("seconds", "hnsecs")(2); + + auto norm1 = Clock.currStdTime; + auto norm2 = Clock.currStdTime; + assert(norm1 <= norm2, format("%s %s", norm1, norm2)); + assert(abs(norm1 - norm2) <= limit); + + foreach (ct; AliasSeq!(ClockType.coarse, ClockType.precise, ClockType.second)) + { + scope(failure) writefln("ClockType.%s", ct); + auto value1 = Clock.currStdTime!ct; + auto value2 = Clock.currStdTime!ct; + assert(value1 <= value2, format("%s %s", value1, value2)); + assert(abs(value1 - value2) <= limit); + } + } + + + // Explicitly undocumented. It will be removed in January 2018. @@@DEPRECATED_2018-01@@@ + deprecated("Use core.time.MonoTime.currTime instead") + static @property TickDuration currSystemTick() @safe nothrow + { + return TickDuration.currSystemTick; + } + + deprecated @safe unittest + { + assert(Clock.currSystemTick.length > 0); + } + + // Explicitly undocumented. It will be removed in January 2018. @@@DEPRECATED_2018-01@@@ + deprecated("Use core.time.MonoTime instead. See currAppTick's documentation for details.") + static @property TickDuration currAppTick() @safe + { + return currSystemTick - TickDuration.appOrigin; + } + + deprecated @safe unittest + { + auto a = Clock.currSystemTick; + auto b = Clock.currAppTick; + assert(a.length); + assert(b.length); + assert(a > b); + } + +private: + + @disable this() {} +} + + +/++ + $(D SysTime) is the type used to get the current time from the + system or doing anything that involves time zones. Unlike + $(REF DateTime,std,datetime,date), the time zone is an integral part of + $(D SysTime) (though for local time applications, time zones can be ignored + and it will work, since it defaults to using the local time zone). It holds + its internal time in std time (hnsecs since midnight, January 1st, 1 A.D. + UTC), so it interfaces well with the system time. However, that means that, + unlike $(REF DateTime,std,datetime,date), it is not optimized for + calendar-based operations, and getting individual units from it such as + years or days is going to involve conversions and be less efficient. + + For calendar-based operations that don't + care about time zones, then $(REF DateTime,std,datetime,date) would be + the type to use. For system time, use $(D SysTime). + + $(LREF Clock.currTime) will return the current time as a $(D SysTime). + To convert a $(D SysTime) to a $(REF Date,std,datetime,date) or + $(REF DateTime,std,datetime,date), simply cast it. To convert a + $(REF Date,std,datetime,date) or $(REF DateTime,std,datetime,date) to a + $(D SysTime), use $(D SysTime)'s constructor, and pass in the ntended time + zone with it (or don't pass in a $(REF TimeZone,std,datetime,timezone), and + the local time zone will be used). Be aware, however, that converting from a + $(REF DateTime,std,datetime,date) to a $(D SysTime) will not necessarily + be 100% accurate due to DST (one hour of the year doesn't exist and another + occurs twice). To not risk any conversion errors, keep times as + $(D SysTime)s. Aside from DST though, there shouldn't be any conversion + problems. + + For using time zones other than local time or UTC, use + $(REF PosixTimeZone,std,datetime,timezone) on Posix systems (or on Windows, + if providing the TZ Database files), and use + $(REF WindowsTimeZone,std,datetime,timezone) on Windows systems. The time in + $(D SysTime) is kept internally in hnsecs from midnight, January 1st, 1 A.D. + UTC. Conversion error cannot happen when changing the time zone of a + $(D SysTime). $(REF LocalTime,std,datetime,timezone) is the + $(REF TimeZone,std,datetime,timezone) class which represents the local time, + and $(D UTC) is the $(REF TimeZone,std,datetime,timezone) class which + represents UTC. $(D SysTime) uses $(REF LocalTime,std,datetime,timezone) if + no $(REF TimeZone,std,datetime,timezone) is provided. For more details on + time zones, see the documentation for $(REF TimeZone,std,datetime,timezone), + $(REF PosixTimeZone,std,datetime,timezone), and + $(REF WindowsTimeZone,std,datetime,timezone). + + $(D SysTime)'s range is from approximately 29,000 B.C. to approximately + 29,000 A.D. + +/ +struct SysTime +{ + import core.stdc.time : tm; + version (Posix) import core.sys.posix.sys.time : timeval; + import std.typecons : Rebindable; + +public: + + /++ + Params: + dateTime = The $(REF DateTime,std,datetime,date) to use to set + this $(LREF SysTime)'s internal std time. As + $(REF DateTime,std,datetime,date) has no concept of + time zone, tz is used as its time zone. + tz = The $(REF TimeZone,std,datetime,timezone) to use for this + $(LREF SysTime). If null, + $(REF LocalTime,std,datetime,timezone) will be used. The + given $(REF DateTime,std,datetime,date) is assumed to + be in the given time zone. + +/ + this(in DateTime dateTime, immutable TimeZone tz = null) @safe nothrow + { + try + this(dateTime, Duration.zero, tz); + catch (Exception e) + assert(0, "SysTime's constructor threw when it shouldn't have."); + } + + @safe unittest + { + static void test(DateTime dt, immutable TimeZone tz, long expected) + { + auto sysTime = SysTime(dt, tz); + assert(sysTime._stdTime == expected); + assert(sysTime._timezone is (tz is null ? LocalTime() : tz), format("Given DateTime: %s", dt)); + } + + test(DateTime.init, UTC(), 0); + test(DateTime(1, 1, 1, 12, 30, 33), UTC(), 450_330_000_000L); + test(DateTime(0, 12, 31, 12, 30, 33), UTC(), -413_670_000_000L); + test(DateTime(1, 1, 1, 0, 0, 0), UTC(), 0); + test(DateTime(1, 1, 1, 0, 0, 1), UTC(), 10_000_000L); + test(DateTime(0, 12, 31, 23, 59, 59), UTC(), -10_000_000L); + + test(DateTime(1, 1, 1, 0, 0, 0), new immutable SimpleTimeZone(dur!"minutes"(-60)), 36_000_000_000L); + test(DateTime(1, 1, 1, 0, 0, 0), new immutable SimpleTimeZone(Duration.zero), 0); + test(DateTime(1, 1, 1, 0, 0, 0), new immutable SimpleTimeZone(dur!"minutes"(60)), -36_000_000_000L); + } + + /++ + Params: + dateTime = The $(REF DateTime,std,datetime,date) to use to set + this $(LREF SysTime)'s internal std time. As + $(REF DateTime,std,datetime,date) has no concept of + time zone, tz is used as its time zone. + fracSecs = The fractional seconds portion of the time. + tz = The $(REF TimeZone,std,datetime,timezone) to use for this + $(LREF SysTime). If null, + $(REF LocalTime,std,datetime,timezone) will be used. The + given $(REF DateTime,std,datetime,date) is assumed to + be in the given time zone. + + Throws: + $(REF DateTimeException,std,datetime,date) if $(D fracSecs) is negative or if it's + greater than or equal to one second. + +/ + this(in DateTime dateTime, in Duration fracSecs, immutable TimeZone tz = null) @safe + { + enforce(fracSecs >= Duration.zero, new DateTimeException("A SysTime cannot have negative fractional seconds.")); + enforce(fracSecs < seconds(1), new DateTimeException("Fractional seconds must be less than one second.")); + auto nonNullTZ = tz is null ? LocalTime() : tz; + + immutable dateDiff = dateTime.date - Date.init; + immutable todDiff = dateTime.timeOfDay - TimeOfDay.init; + + immutable adjustedTime = dateDiff + todDiff + fracSecs; + immutable standardTime = nonNullTZ.tzToUTC(adjustedTime.total!"hnsecs"); + + this(standardTime, nonNullTZ); + } + + @safe unittest + { + static void test(DateTime dt, Duration fracSecs, immutable TimeZone tz, long expected) + { + auto sysTime = SysTime(dt, fracSecs, tz); + assert(sysTime._stdTime == expected); + assert(sysTime._timezone is (tz is null ? LocalTime() : tz), + format("Given DateTime: %s, Given Duration: %s", dt, fracSecs)); + } + + test(DateTime.init, Duration.zero, UTC(), 0); + test(DateTime(1, 1, 1, 12, 30, 33), Duration.zero, UTC(), 450_330_000_000L); + test(DateTime(0, 12, 31, 12, 30, 33), Duration.zero, UTC(), -413_670_000_000L); + test(DateTime(1, 1, 1, 0, 0, 0), msecs(1), UTC(), 10_000L); + test(DateTime(0, 12, 31, 23, 59, 59), msecs(999), UTC(), -10_000L); + + test(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC(), -1); + test(DateTime(0, 12, 31, 23, 59, 59), hnsecs(1), UTC(), -9_999_999); + test(DateTime(0, 12, 31, 23, 59, 59), Duration.zero, UTC(), -10_000_000); + + assertThrown!DateTimeException(SysTime(DateTime.init, hnsecs(-1), UTC())); + assertThrown!DateTimeException(SysTime(DateTime.init, seconds(1), UTC())); + } + + // Explicitly undocumented. It will be removed in August 2017. @@@DEPRECATED_2017-08@@@ + deprecated("Please use the overload which takes a Duration instead of a FracSec.") + this(in DateTime dateTime, in FracSec fracSec, immutable TimeZone tz = null) @safe + { + immutable fracHNSecs = fracSec.hnsecs; + enforce(fracHNSecs >= 0, new DateTimeException("A SysTime cannot have negative fractional seconds.")); + _timezone = tz is null ? LocalTime() : tz; + + try + { + immutable dateDiff = (dateTime.date - Date(1, 1, 1)).total!"hnsecs"; + immutable todDiff = (dateTime.timeOfDay - TimeOfDay(0, 0, 0)).total!"hnsecs"; + + immutable adjustedTime = dateDiff + todDiff + fracHNSecs; + immutable standardTime = _timezone.tzToUTC(adjustedTime); + + this(standardTime, _timezone); + } + catch (Exception e) + assert(0, "Date, TimeOfDay, or DateTime's constructor threw when it shouldn't have."); + } + + deprecated @safe unittest + { + static void test(DateTime dt, FracSec fracSec, immutable TimeZone tz, long expected) + { + auto sysTime = SysTime(dt, fracSec, tz); + assert(sysTime._stdTime == expected); + assert(sysTime._timezone is (tz is null ? LocalTime() : tz), + format("Given DateTime: %s, Given FracSec: %s", dt, fracSec)); + } + + test(DateTime.init, FracSec.init, UTC(), 0); + test(DateTime(1, 1, 1, 12, 30, 33), FracSec.init, UTC(), 450_330_000_000L); + test(DateTime(0, 12, 31, 12, 30, 33), FracSec.init, UTC(), -413_670_000_000L); + test(DateTime(1, 1, 1, 0, 0, 0), FracSec.from!"msecs"(1), UTC(), 10_000L); + test(DateTime(0, 12, 31, 23, 59, 59), FracSec.from!"msecs"(999), UTC(), -10_000L); + + test(DateTime(0, 12, 31, 23, 59, 59), FracSec.from!"hnsecs"(9_999_999), UTC(), -1); + test(DateTime(0, 12, 31, 23, 59, 59), FracSec.from!"hnsecs"(1), UTC(), -9_999_999); + test(DateTime(0, 12, 31, 23, 59, 59), FracSec.from!"hnsecs"(0), UTC(), -10_000_000); + + assertThrown!DateTimeException(SysTime(DateTime.init, FracSec.from!"hnsecs"(-1), UTC())); + } + + /++ + Params: + date = The $(REF Date,std,datetime,date) to use to set this + $(LREF SysTime)'s internal std time. As + $(REF Date,std,datetime,date) has no concept of time zone, tz + is used as its time zone. + tz = The $(REF TimeZone,std,datetime,timezone) to use for this + $(LREF SysTime). If null, + $(REF LocalTime,std,datetime,timezone) will be used. The + given $(REF Date,std,datetime,date) is assumed to be in the + given time zone. + +/ + this(in Date date, immutable TimeZone tz = null) @safe nothrow + { + _timezone = tz is null ? LocalTime() : tz; + + try + { + immutable adjustedTime = (date - Date(1, 1, 1)).total!"hnsecs"; + immutable standardTime = _timezone.tzToUTC(adjustedTime); + + this(standardTime, _timezone); + } + catch (Exception e) + assert(0, "Date's constructor through when it shouldn't have."); + } + + @safe unittest + { + static void test(Date d, immutable TimeZone tz, long expected) + { + auto sysTime = SysTime(d, tz); + assert(sysTime._stdTime == expected); + assert(sysTime._timezone is (tz is null ? LocalTime() : tz), format("Given Date: %s", d)); + } + + test(Date.init, UTC(), 0); + test(Date(1, 1, 1), UTC(), 0); + test(Date(1, 1, 2), UTC(), 864000000000); + test(Date(0, 12, 31), UTC(), -864000000000); + } + + /++ + Note: + Whereas the other constructors take in the given date/time, assume + that it's in the given time zone, and convert it to hnsecs in UTC + since midnight, January 1st, 1 A.D. UTC - i.e. std time - this + constructor takes a std time, which is specifically already in UTC, + so no conversion takes place. Of course, the various getter + properties and functions will use the given time zone's conversion + function to convert the results to that time zone, but no conversion + of the arguments to this constructor takes place. + + Params: + stdTime = The number of hnsecs since midnight, January 1st, 1 A.D. + UTC. + tz = The $(REF TimeZone,std,datetime,timezone) to use for this + $(LREF SysTime). If null, + $(REF LocalTime,std,datetime,timezone) will be used. + +/ + this(long stdTime, immutable TimeZone tz = null) @safe pure nothrow + { + _stdTime = stdTime; + _timezone = tz is null ? LocalTime() : tz; + } + + @safe unittest + { + static void test(long stdTime, immutable TimeZone tz) + { + auto sysTime = SysTime(stdTime, tz); + assert(sysTime._stdTime == stdTime); + assert(sysTime._timezone is (tz is null ? LocalTime() : tz), format("Given stdTime: %s", stdTime)); + } + + foreach (stdTime; [-1234567890L, -250, 0, 250, 1235657390L]) + { + foreach (tz; testTZs) + test(stdTime, tz); + } + } + + /++ + Params: + rhs = The $(LREF SysTime) to assign to this one. + +/ + ref SysTime opAssign(const ref SysTime rhs) return @safe pure nothrow + { + _stdTime = rhs._stdTime; + _timezone = rhs._timezone; + return this; + } + + /++ + Params: + rhs = The $(LREF SysTime) to assign to this one. + +/ + ref SysTime opAssign(SysTime rhs) scope return @safe pure nothrow + { + _stdTime = rhs._stdTime; + _timezone = rhs._timezone; + return this; + } + + /++ + Checks for equality between this $(LREF SysTime) and the given + $(LREF SysTime). + + Note that the time zone is ignored. Only the internal + std times (which are in UTC) are compared. + +/ + bool opEquals(const SysTime rhs) @safe const pure nothrow + { + return opEquals(rhs); + } + + /// ditto + bool opEquals(const ref SysTime rhs) @safe const pure nothrow + { + return _stdTime == rhs._stdTime; + } + + @safe unittest + { + import std.range : chain; + + assert(SysTime(DateTime.init, UTC()) == SysTime(0, UTC())); + assert(SysTime(DateTime.init, UTC()) == SysTime(0)); + assert(SysTime(Date.init, UTC()) == SysTime(0)); + assert(SysTime(0) == SysTime(0)); + + static void test(DateTime dt, immutable TimeZone tz1, immutable TimeZone tz2) + { + auto st1 = SysTime(dt); + st1.timezone = tz1; + + auto st2 = SysTime(dt); + st2.timezone = tz2; + + assert(st1 == st2); + } + + foreach (tz1; testTZs) + { + foreach (tz2; testTZs) + { + foreach (dt; chain(testDateTimesBC, testDateTimesAD)) + test(dt, tz1, tz2); + } + } + + auto st = SysTime(DateTime(1999, 7, 6, 12, 33, 30)); + const cst = SysTime(DateTime(1999, 7, 6, 12, 33, 30)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 33, 30)); + assert(st == st); + assert(st == cst); + //assert(st == ist); + assert(cst == st); + assert(cst == cst); + //assert(cst == ist); + //assert(ist == st); + //assert(ist == cst); + //assert(ist == ist); + } + + /++ + Compares this $(LREF SysTime) with the given $(LREF SysTime). + + Time zone is irrelevant when comparing $(LREF SysTime)s. + + Returns: + $(BOOKTABLE, + $(TR $(TD this < rhs) $(TD < 0)) + $(TR $(TD this == rhs) $(TD 0)) + $(TR $(TD this > rhs) $(TD > 0)) + ) + +/ + int opCmp(in SysTime rhs) @safe const pure nothrow + { + if (_stdTime < rhs._stdTime) + return -1; + if (_stdTime > rhs._stdTime) + return 1; + return 0; + } + + @safe unittest + { + import std.algorithm.iteration : map; + import std.array : array; + import std.range : chain; + + assert(SysTime(DateTime.init, UTC()).opCmp(SysTime(0, UTC())) == 0); + assert(SysTime(DateTime.init, UTC()).opCmp(SysTime(0)) == 0); + assert(SysTime(Date.init, UTC()).opCmp(SysTime(0)) == 0); + assert(SysTime(0).opCmp(SysTime(0)) == 0); + + static void testEqual(SysTime st, immutable TimeZone tz1, immutable TimeZone tz2) + { + auto st1 = st; + st1.timezone = tz1; + + auto st2 = st; + st2.timezone = tz2; + + assert(st1.opCmp(st2) == 0); + } + + auto sts = array(map!SysTime(chain(testDateTimesBC, testDateTimesAD))); + + foreach (st; sts) + { + foreach (tz1; testTZs) + { + foreach (tz2; testTZs) + testEqual(st, tz1, tz2); + } + } + + static void testCmp(SysTime st1, immutable TimeZone tz1, SysTime st2, immutable TimeZone tz2) + { + st1.timezone = tz1; + st2.timezone = tz2; + assert(st1.opCmp(st2) < 0); + assert(st2.opCmp(st1) > 0); + } + + foreach (si, st1; sts) + { + foreach (st2; sts[si + 1 .. $]) + { + foreach (tz1; testTZs) + { + foreach (tz2; testTZs) + testCmp(st1, tz1, st2, tz2); + } + } + } + + auto st = SysTime(DateTime(1999, 7, 6, 12, 33, 30)); + const cst = SysTime(DateTime(1999, 7, 6, 12, 33, 30)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 33, 30)); + assert(st.opCmp(st) == 0); + assert(st.opCmp(cst) == 0); + //assert(st.opCmp(ist) == 0); + assert(cst.opCmp(st) == 0); + assert(cst.opCmp(cst) == 0); + //assert(cst.opCmp(ist) == 0); + //assert(ist.opCmp(st) == 0); + //assert(ist.opCmp(cst) == 0); + //assert(ist.opCmp(ist) == 0); + } + + /** + * Returns: A hash of the $(LREF SysTime) + */ + size_t toHash() const @nogc pure nothrow @safe + { + static if (is(size_t == ulong)) + return _stdTime; + else + { + // MurmurHash2 + enum ulong m = 0xc6a4a7935bd1e995UL; + enum ulong n = m * 16; + enum uint r = 47; + + ulong k = _stdTime; + k *= m; + k ^= k >> r; + k *= m; + + ulong h = n; + h ^= k; + h *= m; + + return cast(size_t) h; + } + } + + @safe unittest + { + assert(SysTime(0).toHash == SysTime(0).toHash); + assert(SysTime(DateTime(2000, 1, 1)).toHash == SysTime(DateTime(2000, 1, 1)).toHash); + assert(SysTime(DateTime(2000, 1, 1)).toHash != SysTime(DateTime(2000, 1, 2)).toHash); + + // test that timezones aren't taken into account + assert(SysTime(0, LocalTime()).toHash == SysTime(0, LocalTime()).toHash); + assert(SysTime(0, LocalTime()).toHash == SysTime(0, UTC()).toHash); + assert(SysTime(DateTime(2000, 1, 1), LocalTime()).toHash == SysTime(DateTime(2000, 1, 1), LocalTime()).toHash); + immutable zone = new SimpleTimeZone(dur!"minutes"(60)); + assert(SysTime(DateTime(2000, 1, 1, 1), zone).toHash == SysTime(DateTime(2000, 1, 1), UTC()).toHash); + assert(SysTime(DateTime(2000, 1, 1), zone).toHash != SysTime(DateTime(2000, 1, 1), UTC()).toHash); + } + + /++ + Year of the Gregorian Calendar. Positive numbers are A.D. Non-positive + are B.C. + +/ + @property short year() @safe const nothrow + { + return (cast(Date) this).year; + } + + @safe unittest + { + import std.range : chain; + static void test(SysTime sysTime, long expected) + { + assert(sysTime.year == expected, format("Value given: %s", sysTime)); + } + + test(SysTime(0, UTC()), 1); + test(SysTime(1, UTC()), 1); + test(SysTime(-1, UTC()), 0); + + foreach (year; chain(testYearsBC, testYearsAD)) + { + foreach (md; testMonthDays) + { + foreach (tod; testTODs) + { + auto dt = DateTime(Date(year, md.month, md.day), tod); + foreach (tz; testTZs) + { + foreach (fs; testFracSecs) + test(SysTime(dt, fs, tz), year); + } + } + } + } + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cst.year == 1999); + //assert(ist.year == 1999); + } + + /++ + Year of the Gregorian Calendar. Positive numbers are A.D. Non-positive + are B.C. + + Params: + year = The year to set this $(LREF SysTime)'s year to. + + Throws: + $(REF DateTimeException,std,datetime,date) if the new year is not + a leap year and the resulting date would be on February 29th. + +/ + @property void year(int year) @safe + { + auto hnsecs = adjTime; + auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; + + if (hnsecs < 0) + { + hnsecs += convert!("hours", "hnsecs")(24); + --days; + } + + auto date = Date(cast(int) days); + date.year = year; + + immutable newDaysHNSecs = convert!("days", "hnsecs")(date.dayOfGregorianCal - 1); + adjTime = newDaysHNSecs + hnsecs; + } + + /// + @safe unittest + { + import std.datetime.date : DateTime; + + assert(SysTime(DateTime(1999, 7, 6, 9, 7, 5)).year == 1999); + assert(SysTime(DateTime(2010, 10, 4, 0, 0, 30)).year == 2010); + assert(SysTime(DateTime(-7, 4, 5, 7, 45, 2)).year == -7); + } + + @safe unittest + { + import std.range : chain; + + static void test(SysTime st, int year, in SysTime expected) + { + st.year = year; + assert(st == expected); + } + + foreach (st; chain(testSysTimesBC, testSysTimesAD)) + { + auto dt = cast(DateTime) st; + + foreach (year; chain(testYearsBC, testYearsAD)) + { + auto e = SysTime(DateTime(year, dt.month, dt.day, dt.hour, dt.minute, dt.second), + st.fracSecs, + st.timezone); + test(st, year, e); + } + } + + foreach (fs; testFracSecs) + { + foreach (tz; testTZs) + { + foreach (tod; testTODs) + { + test(SysTime(DateTime(Date(1999, 2, 28), tod), fs, tz), 2000, + SysTime(DateTime(Date(2000, 2, 28), tod), fs, tz)); + test(SysTime(DateTime(Date(2000, 2, 28), tod), fs, tz), 1999, + SysTime(DateTime(Date(1999, 2, 28), tod), fs, tz)); + } + + foreach (tod; testTODsThrown) + { + auto st = SysTime(DateTime(Date(2000, 2, 29), tod), fs, tz); + assertThrown!DateTimeException(st.year = 1999); + } + } + } + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.year = 7)); + //static assert(!__traits(compiles, ist.year = 7)); + } + + /++ + Year B.C. of the Gregorian Calendar counting year 0 as 1 B.C. + + Throws: + $(REF DateTimeException,std,datetime,date) if $(D isAD) is true. + +/ + @property ushort yearBC() @safe const + { + return (cast(Date) this).yearBC; + } + + /// + @safe unittest + { + import std.datetime.date : DateTime; + + assert(SysTime(DateTime(0, 1, 1, 12, 30, 33)).yearBC == 1); + assert(SysTime(DateTime(-1, 1, 1, 10, 7, 2)).yearBC == 2); + assert(SysTime(DateTime(-100, 1, 1, 4, 59, 0)).yearBC == 101); + } + + @safe unittest + { + import std.exception : assertNotThrown; + foreach (st; testSysTimesBC) + { + auto msg = format("SysTime: %s", st); + assertNotThrown!DateTimeException(st.yearBC, msg); + assert(st.yearBC == (st.year * -1) + 1, msg); + } + + foreach (st; [testSysTimesAD[0], testSysTimesAD[$/2], testSysTimesAD[$-1]]) + assertThrown!DateTimeException(st.yearBC, format("SysTime: %s", st)); + + auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + st.year = 12; + assert(st.year == 12); + static assert(!__traits(compiles, cst.year = 12)); + //static assert(!__traits(compiles, ist.year = 12)); + } + + + /++ + Year B.C. of the Gregorian Calendar counting year 0 as 1 B.C. + + Params: + year = The year B.C. to set this $(LREF SysTime)'s year to. + + Throws: + $(REF DateTimeException,std,datetime,date) if a non-positive value + is given. + +/ + @property void yearBC(int year) @safe + { + auto hnsecs = adjTime; + auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; + + if (hnsecs < 0) + { + hnsecs += convert!("hours", "hnsecs")(24); + --days; + } + + auto date = Date(cast(int) days); + date.yearBC = year; + + immutable newDaysHNSecs = convert!("days", "hnsecs")(date.dayOfGregorianCal - 1); + adjTime = newDaysHNSecs + hnsecs; + } + + @safe unittest + { + auto st = SysTime(DateTime(2010, 1, 1, 7, 30, 0)); + st.yearBC = 1; + assert(st == SysTime(DateTime(0, 1, 1, 7, 30, 0))); + + st.yearBC = 10; + assert(st == SysTime(DateTime(-9, 1, 1, 7, 30, 0))); + } + + @safe unittest + { + import std.range : chain; + static void test(SysTime st, int year, in SysTime expected) + { + st.yearBC = year; + assert(st == expected, format("SysTime: %s", st)); + } + + foreach (st; chain(testSysTimesBC, testSysTimesAD)) + { + auto dt = cast(DateTime) st; + + foreach (year; testYearsBC) + { + auto e = SysTime(DateTime(year, dt.month, dt.day, dt.hour, dt.minute, dt.second), + st.fracSecs, + st.timezone); + test(st, (year * -1) + 1, e); + } + } + + foreach (st; [testSysTimesBC[0], testSysTimesBC[$ - 1], testSysTimesAD[0], testSysTimesAD[$ - 1]]) + { + foreach (year; testYearsBC) + assertThrown!DateTimeException(st.yearBC = year); + } + + foreach (fs; testFracSecs) + { + foreach (tz; testTZs) + { + foreach (tod; testTODs) + { + test(SysTime(DateTime(Date(-1999, 2, 28), tod), fs, tz), 2001, + SysTime(DateTime(Date(-2000, 2, 28), tod), fs, tz)); + test(SysTime(DateTime(Date(-2000, 2, 28), tod), fs, tz), 2000, + SysTime(DateTime(Date(-1999, 2, 28), tod), fs, tz)); + } + + foreach (tod; testTODsThrown) + { + auto st = SysTime(DateTime(Date(-2000, 2, 29), tod), fs, tz); + assertThrown!DateTimeException(st.year = -1999); + } + } + } + + auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + st.yearBC = 12; + assert(st.yearBC == 12); + static assert(!__traits(compiles, cst.yearBC = 12)); + //static assert(!__traits(compiles, ist.yearBC = 12)); + } + + /++ + Month of a Gregorian Year. + +/ + @property Month month() @safe const nothrow + { + return (cast(Date) this).month; + } + + /// + @safe unittest + { + import std.datetime.date : DateTime; + + assert(SysTime(DateTime(1999, 7, 6, 9, 7, 5)).month == 7); + assert(SysTime(DateTime(2010, 10, 4, 0, 0, 30)).month == 10); + assert(SysTime(DateTime(-7, 4, 5, 7, 45, 2)).month == 4); + } + + @safe unittest + { + import std.range : chain; + + static void test(SysTime sysTime, Month expected) + { + assert(sysTime.month == expected, format("Value given: %s", sysTime)); + } + + test(SysTime(0, UTC()), Month.jan); + test(SysTime(1, UTC()), Month.jan); + test(SysTime(-1, UTC()), Month.dec); + + foreach (year; chain(testYearsBC, testYearsAD)) + { + foreach (md; testMonthDays) + { + foreach (tod; testTODs) + { + auto dt = DateTime(Date(year, md.month, md.day), tod); + foreach (fs; testFracSecs) + { + foreach (tz; testTZs) + test(SysTime(dt, fs, tz), md.month); + } + } + } + } + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cst.month == 7); + //assert(ist.month == 7); + } + + + /++ + Month of a Gregorian Year. + + Params: + month = The month to set this $(LREF SysTime)'s month to. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given month is + not a valid month. + +/ + @property void month(Month month) @safe + { + auto hnsecs = adjTime; + auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; + + if (hnsecs < 0) + { + hnsecs += convert!("hours", "hnsecs")(24); + --days; + } + + auto date = Date(cast(int) days); + date.month = month; + + immutable newDaysHNSecs = convert!("days", "hnsecs")(date.dayOfGregorianCal - 1); + adjTime = newDaysHNSecs + hnsecs; + } + + @safe unittest + { + import std.algorithm.iteration : filter; + import std.range : chain; + + static void test(SysTime st, Month month, in SysTime expected) + { + st.month = cast(Month) month; + assert(st == expected); + } + + foreach (st; chain(testSysTimesBC, testSysTimesAD)) + { + auto dt = cast(DateTime) st; + + foreach (md; testMonthDays) + { + if (st.day > maxDay(dt.year, md.month)) + continue; + auto e = SysTime(DateTime(dt.year, md.month, dt.day, dt.hour, dt.minute, dt.second), + st.fracSecs, + st.timezone); + test(st, md.month, e); + } + } + + foreach (fs; testFracSecs) + { + foreach (tz; testTZs) + { + foreach (tod; testTODs) + { + foreach (year; filter!((a){return yearIsLeapYear(a);}) (chain(testYearsBC, testYearsAD))) + { + test(SysTime(DateTime(Date(year, 1, 29), tod), fs, tz), + Month.feb, + SysTime(DateTime(Date(year, 2, 29), tod), fs, tz)); + } + + foreach (year; chain(testYearsBC, testYearsAD)) + { + test(SysTime(DateTime(Date(year, 1, 28), tod), fs, tz), + Month.feb, + SysTime(DateTime(Date(year, 2, 28), tod), fs, tz)); + test(SysTime(DateTime(Date(year, 7, 30), tod), fs, tz), + Month.jun, + SysTime(DateTime(Date(year, 6, 30), tod), fs, tz)); + } + } + } + } + + foreach (fs; [testFracSecs[0], testFracSecs[$-1]]) + { + foreach (tz; testTZs) + { + foreach (tod; testTODsThrown) + { + foreach (year; [testYearsBC[$-3], testYearsBC[$-2], + testYearsBC[$-2], testYearsAD[0], + testYearsAD[$-2], testYearsAD[$-1]]) + { + auto day = yearIsLeapYear(year) ? 30 : 29; + auto st1 = SysTime(DateTime(Date(year, 1, day), tod), fs, tz); + assertThrown!DateTimeException(st1.month = Month.feb); + + auto st2 = SysTime(DateTime(Date(year, 7, 31), tod), fs, tz); + assertThrown!DateTimeException(st2.month = Month.jun); + } + } + } + } + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.month = 12)); + //static assert(!__traits(compiles, ist.month = 12)); + } + + /++ + Day of a Gregorian Month. + +/ + @property ubyte day() @safe const nothrow + { + return (cast(Date) this).day; + } + + /// + @safe unittest + { + import std.datetime.date : DateTime; + + assert(SysTime(DateTime(1999, 7, 6, 9, 7, 5)).day == 6); + assert(SysTime(DateTime(2010, 10, 4, 0, 0, 30)).day == 4); + assert(SysTime(DateTime(-7, 4, 5, 7, 45, 2)).day == 5); + } + + @safe unittest + { + import std.range : chain; + + static void test(SysTime sysTime, int expected) + { + assert(sysTime.day == expected, format("Value given: %s", sysTime)); + } + + test(SysTime(0, UTC()), 1); + test(SysTime(1, UTC()), 1); + test(SysTime(-1, UTC()), 31); + + foreach (year; chain(testYearsBC, testYearsAD)) + { + foreach (md; testMonthDays) + { + foreach (tod; testTODs) + { + auto dt = DateTime(Date(year, md.month, md.day), tod); + + foreach (tz; testTZs) + { + foreach (fs; testFracSecs) + test(SysTime(dt, fs, tz), md.day); + } + } + } + } + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cst.day == 6); + //assert(ist.day == 6); + } + + + /++ + Day of a Gregorian Month. + + Params: + day = The day of the month to set this $(LREF SysTime)'s day to. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given day is not + a valid day of the current month. + +/ + @property void day(int day) @safe + { + auto hnsecs = adjTime; + auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; + + if (hnsecs < 0) + { + hnsecs += convert!("hours", "hnsecs")(24); + --days; + } + + auto date = Date(cast(int) days); + date.day = day; + + immutable newDaysHNSecs = convert!("days", "hnsecs")(date.dayOfGregorianCal - 1); + adjTime = newDaysHNSecs + hnsecs; + } + + @safe unittest + { + import std.range : chain; + import std.traits : EnumMembers; + + foreach (day; chain(testDays)) + { + foreach (st; chain(testSysTimesBC, testSysTimesAD)) + { + auto dt = cast(DateTime) st; + + if (day > maxDay(dt.year, dt.month)) + continue; + auto expected = SysTime(DateTime(dt.year, dt.month, day, dt.hour, dt.minute, dt.second), + st.fracSecs, + st.timezone); + st.day = day; + assert(st == expected, format("[%s] [%s]", st, expected)); + } + } + + foreach (tz; testTZs) + { + foreach (tod; testTODs) + { + foreach (fs; testFracSecs) + { + foreach (year; chain(testYearsBC, testYearsAD)) + { + foreach (month; EnumMembers!Month) + { + auto st = SysTime(DateTime(Date(year, month, 1), tod), fs, tz); + immutable max = maxDay(year, month); + auto expected = SysTime(DateTime(Date(year, month, max), tod), fs, tz); + + st.day = max; + assert(st == expected, format("[%s] [%s]", st, expected)); + } + } + } + } + } + + foreach (tz; testTZs) + { + foreach (tod; testTODsThrown) + { + foreach (fs; [testFracSecs[0], testFracSecs[$-1]]) + { + foreach (year; [testYearsBC[$-3], testYearsBC[$-2], + testYearsBC[$-2], testYearsAD[0], + testYearsAD[$-2], testYearsAD[$-1]]) + { + foreach (month; EnumMembers!Month) + { + auto st = SysTime(DateTime(Date(year, month, 1), tod), fs, tz); + immutable max = maxDay(year, month); + + assertThrown!DateTimeException(st.day = max + 1); + } + } + } + } + } + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.day = 27)); + //static assert(!__traits(compiles, ist.day = 27)); + } + + + /++ + Hours past midnight. + +/ + @property ubyte hour() @safe const nothrow + { + auto hnsecs = adjTime; + auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; + + if (hnsecs < 0) + { + hnsecs += convert!("hours", "hnsecs")(24); + --days; + } + + return cast(ubyte) getUnitsFromHNSecs!"hours"(hnsecs); + } + + @safe unittest + { + import std.range : chain; + + static void test(SysTime sysTime, int expected) + { + assert(sysTime.hour == expected, format("Value given: %s", sysTime)); + } + + test(SysTime(0, UTC()), 0); + test(SysTime(1, UTC()), 0); + test(SysTime(-1, UTC()), 23); + + foreach (tz; testTZs) + { + foreach (year; chain(testYearsBC, testYearsAD)) + { + foreach (md; testMonthDays) + { + foreach (hour; testHours) + { + foreach (minute; testMinSecs) + { + foreach (second; testMinSecs) + { + auto dt = DateTime(Date(year, md.month, md.day), TimeOfDay(hour, minute, second)); + foreach (fs; testFracSecs) + test(SysTime(dt, fs, tz), hour); + } + } + } + } + } + } + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cst.hour == 12); + //assert(ist.hour == 12); + } + + + /++ + Hours past midnight. + + Params: + hour = The hours to set this $(LREF SysTime)'s hour to. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given hour are + not a valid hour of the day. + +/ + @property void hour(int hour) @safe + { + enforceValid!"hours"(hour); + + auto hnsecs = adjTime; + auto days = splitUnitsFromHNSecs!"days"(hnsecs); + immutable daysHNSecs = convert!("days", "hnsecs")(days); + immutable negative = hnsecs < 0; + + if (negative) + hnsecs += convert!("hours", "hnsecs")(24); + + hnsecs = removeUnitsFromHNSecs!"hours"(hnsecs); + hnsecs += convert!("hours", "hnsecs")(hour); + + if (negative) + hnsecs -= convert!("hours", "hnsecs")(24); + + adjTime = daysHNSecs + hnsecs; + } + + @safe unittest + { + import std.range : chain; + + foreach (hour; chain(testHours)) + { + foreach (st; chain(testSysTimesBC, testSysTimesAD)) + { + auto dt = cast(DateTime) st; + auto expected = SysTime(DateTime(dt.year, dt.month, dt.day, hour, dt.minute, dt.second), + st.fracSecs, + st.timezone); + st.hour = hour; + assert(st == expected, format("[%s] [%s]", st, expected)); + } + } + + auto st = testSysTimesAD[0]; + assertThrown!DateTimeException(st.hour = -1); + assertThrown!DateTimeException(st.hour = 60); + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.hour = 27)); + //static assert(!__traits(compiles, ist.hour = 27)); + } + + + /++ + Minutes past the current hour. + +/ + @property ubyte minute() @safe const nothrow + { + auto hnsecs = adjTime; + auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; + + if (hnsecs < 0) + { + hnsecs += convert!("hours", "hnsecs")(24); + --days; + } + + hnsecs = removeUnitsFromHNSecs!"hours"(hnsecs); + + return cast(ubyte) getUnitsFromHNSecs!"minutes"(hnsecs); + } + + @safe unittest + { + import std.range : chain; + + static void test(SysTime sysTime, int expected) + { + assert(sysTime.minute == expected, format("Value given: %s", sysTime)); + } + + test(SysTime(0, UTC()), 0); + test(SysTime(1, UTC()), 0); + test(SysTime(-1, UTC()), 59); + + foreach (tz; testTZs) + { + foreach (year; chain(testYearsBC, testYearsAD)) + { + foreach (md; testMonthDays) + { + foreach (hour; testHours) + { + foreach (minute; testMinSecs) + { + foreach (second; testMinSecs) + { + auto dt = DateTime(Date(year, md.month, md.day), TimeOfDay(hour, minute, second)); + foreach (fs; testFracSecs) + test(SysTime(dt, fs, tz), minute); + } + } + } + } + } + } + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cst.minute == 30); + //assert(ist.minute == 30); + } + + + /++ + Minutes past the current hour. + + Params: + minute = The minute to set this $(LREF SysTime)'s minute to. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given minute are + not a valid minute of an hour. + +/ + @property void minute(int minute) @safe + { + enforceValid!"minutes"(minute); + + auto hnsecs = adjTime; + auto days = splitUnitsFromHNSecs!"days"(hnsecs); + immutable daysHNSecs = convert!("days", "hnsecs")(days); + immutable negative = hnsecs < 0; + + if (negative) + hnsecs += convert!("hours", "hnsecs")(24); + + immutable hour = splitUnitsFromHNSecs!"hours"(hnsecs); + hnsecs = removeUnitsFromHNSecs!"minutes"(hnsecs); + + hnsecs += convert!("hours", "hnsecs")(hour); + hnsecs += convert!("minutes", "hnsecs")(minute); + + if (negative) + hnsecs -= convert!("hours", "hnsecs")(24); + + adjTime = daysHNSecs + hnsecs; + } + + @safe unittest + { + import std.range : chain; + + foreach (minute; testMinSecs) + { + foreach (st; chain(testSysTimesBC, testSysTimesAD)) + { + auto dt = cast(DateTime) st; + auto expected = SysTime(DateTime(dt.year, dt.month, dt.day, dt.hour, minute, dt.second), + st.fracSecs, + st.timezone); + st.minute = minute; + assert(st == expected, format("[%s] [%s]", st, expected)); + } + } + + auto st = testSysTimesAD[0]; + assertThrown!DateTimeException(st.minute = -1); + assertThrown!DateTimeException(st.minute = 60); + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.minute = 27)); + //static assert(!__traits(compiles, ist.minute = 27)); + } + + + /++ + Seconds past the current minute. + +/ + @property ubyte second() @safe const nothrow + { + auto hnsecs = adjTime; + auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; + + if (hnsecs < 0) + { + hnsecs += convert!("hours", "hnsecs")(24); + --days; + } + + hnsecs = removeUnitsFromHNSecs!"hours"(hnsecs); + hnsecs = removeUnitsFromHNSecs!"minutes"(hnsecs); + + return cast(ubyte) getUnitsFromHNSecs!"seconds"(hnsecs); + } + + @safe unittest + { + import std.range : chain; + + static void test(SysTime sysTime, int expected) + { + assert(sysTime.second == expected, format("Value given: %s", sysTime)); + } + + test(SysTime(0, UTC()), 0); + test(SysTime(1, UTC()), 0); + test(SysTime(-1, UTC()), 59); + + foreach (tz; testTZs) + { + foreach (year; chain(testYearsBC, testYearsAD)) + { + foreach (md; testMonthDays) + { + foreach (hour; testHours) + { + foreach (minute; testMinSecs) + { + foreach (second; testMinSecs) + { + auto dt = DateTime(Date(year, md.month, md.day), TimeOfDay(hour, minute, second)); + foreach (fs; testFracSecs) + test(SysTime(dt, fs, tz), second); + } + } + } + } + } + } + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cst.second == 33); + //assert(ist.second == 33); + } + + + /++ + Seconds past the current minute. + + Params: + second = The second to set this $(LREF SysTime)'s second to. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given second are + not a valid second of a minute. + +/ + @property void second(int second) @safe + { + enforceValid!"seconds"(second); + + auto hnsecs = adjTime; + auto days = splitUnitsFromHNSecs!"days"(hnsecs); + immutable daysHNSecs = convert!("days", "hnsecs")(days); + immutable negative = hnsecs < 0; + + if (negative) + hnsecs += convert!("hours", "hnsecs")(24); + + immutable hour = splitUnitsFromHNSecs!"hours"(hnsecs); + immutable minute = splitUnitsFromHNSecs!"minutes"(hnsecs); + hnsecs = removeUnitsFromHNSecs!"seconds"(hnsecs); + + hnsecs += convert!("hours", "hnsecs")(hour); + hnsecs += convert!("minutes", "hnsecs")(minute); + hnsecs += convert!("seconds", "hnsecs")(second); + + if (negative) + hnsecs -= convert!("hours", "hnsecs")(24); + + adjTime = daysHNSecs + hnsecs; + } + + @safe unittest + { + import std.range : chain; + + foreach (second; testMinSecs) + { + foreach (st; chain(testSysTimesBC, testSysTimesAD)) + { + auto dt = cast(DateTime) st; + auto expected = SysTime(DateTime(dt.year, dt.month, dt.day, dt.hour, dt.minute, second), + st.fracSecs, + st.timezone); + st.second = second; + assert(st == expected, format("[%s] [%s]", st, expected)); + } + } + + auto st = testSysTimesAD[0]; + assertThrown!DateTimeException(st.second = -1); + assertThrown!DateTimeException(st.second = 60); + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.seconds = 27)); + //static assert(!__traits(compiles, ist.seconds = 27)); + } + + + /++ + Fractional seconds past the second (i.e. the portion of a + $(LREF SysTime) which is less than a second). + +/ + @property Duration fracSecs() @safe const nothrow + { + auto hnsecs = removeUnitsFromHNSecs!"days"(adjTime); + + if (hnsecs < 0) + hnsecs += convert!("hours", "hnsecs")(24); + + return dur!"hnsecs"(removeUnitsFromHNSecs!"seconds"(hnsecs)); + } + + /// + @safe unittest + { + import core.time : msecs, usecs, hnsecs, nsecs; + import std.datetime.date : DateTime; + + auto dt = DateTime(1982, 4, 1, 20, 59, 22); + assert(SysTime(dt, msecs(213)).fracSecs == msecs(213)); + assert(SysTime(dt, usecs(5202)).fracSecs == usecs(5202)); + assert(SysTime(dt, hnsecs(1234567)).fracSecs == hnsecs(1234567)); + + // SysTime and Duration both have a precision of hnsecs (100 ns), + // so nsecs are going to be truncated. + assert(SysTime(dt, nsecs(123456789)).fracSecs == nsecs(123456700)); + } + + @safe unittest + { + import std.range : chain; + + assert(SysTime(0, UTC()).fracSecs == Duration.zero); + assert(SysTime(1, UTC()).fracSecs == hnsecs(1)); + assert(SysTime(-1, UTC()).fracSecs == hnsecs(9_999_999)); + + foreach (tz; testTZs) + { + foreach (year; chain(testYearsBC, testYearsAD)) + { + foreach (md; testMonthDays) + { + foreach (hour; testHours) + { + foreach (minute; testMinSecs) + { + foreach (second; testMinSecs) + { + auto dt = DateTime(Date(year, md.month, md.day), TimeOfDay(hour, minute, second)); + foreach (fs; testFracSecs) + assert(SysTime(dt, fs, tz).fracSecs == fs); + } + } + } + } + } + } + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cst.fracSecs == Duration.zero); + //assert(ist.fracSecs == Duration.zero); + } + + + /++ + Fractional seconds past the second (i.e. the portion of a + $(LREF SysTime) which is less than a second). + + Params: + fracSecs = The duration to set this $(LREF SysTime)'s fractional + seconds to. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given duration + is negative or if it's greater than or equal to one second. + +/ + @property void fracSecs(Duration fracSecs) @safe + { + enforce(fracSecs >= Duration.zero, new DateTimeException("A SysTime cannot have negative fractional seconds.")); + enforce(fracSecs < seconds(1), new DateTimeException("Fractional seconds must be less than one second.")); + + auto oldHNSecs = adjTime; + auto days = splitUnitsFromHNSecs!"days"(oldHNSecs); + immutable daysHNSecs = convert!("days", "hnsecs")(days); + immutable negative = oldHNSecs < 0; + + if (negative) + oldHNSecs += convert!("hours", "hnsecs")(24); + + immutable seconds = splitUnitsFromHNSecs!"seconds"(oldHNSecs); + immutable secondsHNSecs = convert!("seconds", "hnsecs")(seconds); + auto newHNSecs = fracSecs.total!"hnsecs" + secondsHNSecs; + + if (negative) + newHNSecs -= convert!("hours", "hnsecs")(24); + + adjTime = daysHNSecs + newHNSecs; + } + + /// + @safe unittest + { + import core.time : Duration, msecs, hnsecs, nsecs; + import std.datetime.date : DateTime; + + auto st = SysTime(DateTime(1982, 4, 1, 20, 59, 22)); + assert(st.fracSecs == Duration.zero); + + st.fracSecs = msecs(213); + assert(st.fracSecs == msecs(213)); + + st.fracSecs = hnsecs(1234567); + assert(st.fracSecs == hnsecs(1234567)); + + // SysTime has a precision of hnsecs (100 ns), so nsecs are + // going to be truncated. + st.fracSecs = nsecs(123456789); + assert(st.fracSecs == hnsecs(1234567)); + } + + @safe unittest + { + import std.range : chain; + + foreach (fracSec; testFracSecs) + { + foreach (st; chain(testSysTimesBC, testSysTimesAD)) + { + auto dt = cast(DateTime) st; + auto expected = SysTime(dt, fracSec, st.timezone); + st.fracSecs = fracSec; + assert(st == expected, format("[%s] [%s]", st, expected)); + } + } + + auto st = testSysTimesAD[0]; + assertThrown!DateTimeException(st.fracSecs = hnsecs(-1)); + assertThrown!DateTimeException(st.fracSecs = seconds(1)); + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.fracSecs = msecs(7))); + //static assert(!__traits(compiles, ist.fracSecs = msecs(7))); + } + + + // Explicitly undocumented. It will be removed in August 2017. @@@DEPRECATED_2017-08@@@ + deprecated("Please use fracSecs (with an s) rather than fracSec (without an s). " ~ + "It returns a Duration instead of a FracSec, as FracSec is being deprecated.") + @property FracSec fracSec() @safe const nothrow + { + try + { + auto hnsecs = removeUnitsFromHNSecs!"days"(adjTime); + + if (hnsecs < 0) + hnsecs += convert!("hours", "hnsecs")(24); + + hnsecs = removeUnitsFromHNSecs!"seconds"(hnsecs); + + return FracSec.from!"hnsecs"(cast(int) hnsecs); + } + catch (Exception e) + assert(0, "FracSec.from!\"hnsecs\"() threw."); + } + + deprecated @safe unittest + { + import std.range; + + static void test(SysTime sysTime, FracSec expected, size_t line = __LINE__) + { + if (sysTime.fracSec != expected) + throw new AssertError(format("Value given: %s", sysTime.fracSec), __FILE__, line); + } + + test(SysTime(0, UTC()), FracSec.from!"hnsecs"(0)); + test(SysTime(1, UTC()), FracSec.from!"hnsecs"(1)); + test(SysTime(-1, UTC()), FracSec.from!"hnsecs"(9_999_999)); + + foreach (tz; testTZs) + { + foreach (year; chain(testYearsBC, testYearsAD)) + { + foreach (md; testMonthDays) + { + foreach (hour; testHours) + { + foreach (minute; testMinSecs) + { + foreach (second; testMinSecs) + { + auto dt = DateTime(Date(year, md.month, md.day), TimeOfDay(hour, minute, second)); + foreach (fs; testFracSecs) + test(SysTime(dt, fs, tz), FracSec.from!"hnsecs"(fs.total!"hnsecs")); + } + } + } + } + } + } + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cst.fracSec == FracSec.zero); + //assert(ist.fracSec == FracSec.zero); + } + + + // Explicitly undocumented. It will be removed in August 2017. @@@DEPRECATED_2017-08@@@ + deprecated("Please use fracSecs (with an s) rather than fracSec (without an s). " ~ + "It takes a Duration instead of a FracSec, as FracSec is being deprecated.") + @property void fracSec(FracSec fracSec) @safe + { + immutable fracHNSecs = fracSec.hnsecs; + enforce(fracHNSecs >= 0, new DateTimeException("A SysTime cannot have negative fractional seconds.")); + + auto hnsecs = adjTime; + auto days = splitUnitsFromHNSecs!"days"(hnsecs); + immutable daysHNSecs = convert!("days", "hnsecs")(days); + immutable negative = hnsecs < 0; + + if (negative) + hnsecs += convert!("hours", "hnsecs")(24); + + immutable hour = splitUnitsFromHNSecs!"hours"(hnsecs); + immutable minute = splitUnitsFromHNSecs!"minutes"(hnsecs); + immutable second = getUnitsFromHNSecs!"seconds"(hnsecs); + + hnsecs = fracHNSecs; + hnsecs += convert!("hours", "hnsecs")(hour); + hnsecs += convert!("minutes", "hnsecs")(minute); + hnsecs += convert!("seconds", "hnsecs")(second); + + if (negative) + hnsecs -= convert!("hours", "hnsecs")(24); + + adjTime = daysHNSecs + hnsecs; + } + + deprecated @safe unittest + { + import std.range; + + foreach (fracSec; testFracSecs) + { + foreach (st; chain(testSysTimesBC, testSysTimesAD)) + { + auto dt = cast(DateTime) st; + auto expected = SysTime(dt, fracSec, st.timezone); + st.fracSec = FracSec.from!"hnsecs"(fracSec.total!"hnsecs"); + assert(st == expected, format("[%s] [%s]", st, expected)); + } + } + + auto st = testSysTimesAD[0]; + assertThrown!DateTimeException(st.fracSec = FracSec.from!"hnsecs"(-1)); + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.fracSec = FracSec.from!"msecs"(7))); + //static assert(!__traits(compiles, ist.fracSec = FracSec.from!"msecs"(7))); + } + + + /++ + The total hnsecs from midnight, January 1st, 1 A.D. UTC. This is the + internal representation of $(LREF SysTime). + +/ + @property long stdTime() @safe const pure nothrow + { + return _stdTime; + } + + @safe unittest + { + assert(SysTime(0).stdTime == 0); + assert(SysTime(1).stdTime == 1); + assert(SysTime(-1).stdTime == -1); + assert(SysTime(DateTime(1, 1, 1, 0, 0, 33), hnsecs(502), UTC()).stdTime == 330_000_502L); + assert(SysTime(DateTime(1970, 1, 1, 0, 0, 0), UTC()).stdTime == 621_355_968_000_000_000L); + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cst.stdTime > 0); + //assert(ist.stdTime > 0); + } + + + /++ + The total hnsecs from midnight, January 1st, 1 A.D. UTC. This is the + internal representation of $(LREF SysTime). + + Params: + stdTime = The number of hnsecs since January 1st, 1 A.D. UTC. + +/ + @property void stdTime(long stdTime) @safe pure nothrow + { + _stdTime = stdTime; + } + + @safe unittest + { + static void test(long stdTime, in SysTime expected, size_t line = __LINE__) + { + auto st = SysTime(0, UTC()); + st.stdTime = stdTime; + assert(st == expected); + } + + test(0, SysTime(Date(1, 1, 1), UTC())); + test(1, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1), UTC())); + test(-1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC())); + test(330_000_502L, SysTime(DateTime(1, 1, 1, 0, 0, 33), hnsecs(502), UTC())); + test(621_355_968_000_000_000L, SysTime(DateTime(1970, 1, 1, 0, 0, 0), UTC())); + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.stdTime = 27)); + //static assert(!__traits(compiles, ist.stdTime = 27)); + } + + + /++ + The current time zone of this $(LREF SysTime). Its internal time is + always kept in UTC, so there are no conversion issues between time zones + due to DST. Functions which return all or part of the time - such as + hours - adjust the time to this $(LREF SysTime)'s time zone before + returning. + +/ + @property immutable(TimeZone) timezone() @safe const pure nothrow + { + return _timezone; + } + + + /++ + The current time zone of this $(LREF SysTime). It's internal time is + always kept in UTC, so there are no conversion issues between time zones + due to DST. Functions which return all or part of the time - such as + hours - adjust the time to this $(LREF SysTime)'s time zone before + returning. + + Params: + timezone = The $(REF _TimeZone,std,datetime,_timezone) to set this + $(LREF SysTime)'s time zone to. + +/ + @property void timezone(immutable TimeZone timezone) @safe pure nothrow + { + if (timezone is null) + _timezone = LocalTime(); + else + _timezone = timezone; + } + + + /++ + Returns whether DST is in effect for this $(LREF SysTime). + +/ + @property bool dstInEffect() @safe const nothrow + { + return _timezone.dstInEffect(_stdTime); + // This function's unit testing is done in the time zone classes. + } + + + /++ + Returns what the offset from UTC is for this $(LREF SysTime). + It includes the DST offset in effect at that time (if any). + +/ + @property Duration utcOffset() @safe const nothrow + { + return _timezone.utcOffsetAt(_stdTime); + } + + + /++ + Returns a $(LREF SysTime) with the same std time as this one, but with + $(REF LocalTime,std,datetime,timezone) as its time zone. + +/ + SysTime toLocalTime() @safe const pure nothrow + { + return SysTime(_stdTime, LocalTime()); + } + + @safe unittest + { + { + auto sysTime = SysTime(DateTime(1982, 1, 4, 8, 59, 7), hnsecs(27)); + assert(sysTime == sysTime.toLocalTime()); + assert(sysTime._stdTime == sysTime.toLocalTime()._stdTime); + assert(sysTime.toLocalTime().timezone is LocalTime()); + assert(sysTime.toLocalTime().timezone is sysTime.timezone); + assert(sysTime.toLocalTime().timezone !is UTC()); + } + + { + auto stz = new immutable SimpleTimeZone(dur!"minutes"(-3 * 60)); + auto sysTime = SysTime(DateTime(1982, 1, 4, 8, 59, 7), hnsecs(27), stz); + assert(sysTime == sysTime.toLocalTime()); + assert(sysTime._stdTime == sysTime.toLocalTime()._stdTime); + assert(sysTime.toLocalTime().timezone is LocalTime()); + assert(sysTime.toLocalTime().timezone !is UTC()); + assert(sysTime.toLocalTime().timezone !is stz); + } + } + + + /++ + Returns a $(LREF SysTime) with the same std time as this one, but with + $(D UTC) as its time zone. + +/ + SysTime toUTC() @safe const pure nothrow + { + return SysTime(_stdTime, UTC()); + } + + @safe unittest + { + auto sysTime = SysTime(DateTime(1982, 1, 4, 8, 59, 7), hnsecs(27)); + assert(sysTime == sysTime.toUTC()); + assert(sysTime._stdTime == sysTime.toUTC()._stdTime); + assert(sysTime.toUTC().timezone is UTC()); + assert(sysTime.toUTC().timezone !is LocalTime()); + assert(sysTime.toUTC().timezone !is sysTime.timezone); + } + + + /++ + Returns a $(LREF SysTime) with the same std time as this one, but with + given time zone as its time zone. + +/ + SysTime toOtherTZ(immutable TimeZone tz) @safe const pure nothrow + { + if (tz is null) + return SysTime(_stdTime, LocalTime()); + else + return SysTime(_stdTime, tz); + } + + @safe unittest + { + auto stz = new immutable SimpleTimeZone(dur!"minutes"(11 * 60)); + auto sysTime = SysTime(DateTime(1982, 1, 4, 8, 59, 7), hnsecs(27)); + assert(sysTime == sysTime.toOtherTZ(stz)); + assert(sysTime._stdTime == sysTime.toOtherTZ(stz)._stdTime); + assert(sysTime.toOtherTZ(stz).timezone is stz); + assert(sysTime.toOtherTZ(stz).timezone !is LocalTime()); + assert(sysTime.toOtherTZ(stz).timezone !is UTC()); + } + + + /++ + Converts this $(LREF SysTime) to unix time (i.e. seconds from midnight, + January 1st, 1970 in UTC). + + The C standard does not specify the representation of time_t, so it is + implementation defined. On POSIX systems, unix time is equivalent to + time_t, but that's not necessarily true on other systems (e.g. it is + not true for the Digital Mars C runtime). So, be careful when using unix + time with C functions on non-POSIX systems. + + By default, the return type is time_t (which is normally an alias for + int on 32-bit systems and long on 64-bit systems), but if a different + size is required than either int or long can be passed as a template + argument to get the desired size. + + If the return type is int, and the result can't fit in an int, then the + closest value that can be held in 32 bits will be used (so $(D int.max) + if it goes over and $(D int.min) if it goes under). However, no attempt + is made to deal with integer overflow if the return type is long. + + Params: + T = The return type (int or long). It defaults to time_t, which is + normally 32 bits on a 32-bit system and 64 bits on a 64-bit + system. + + Returns: + A signed integer representing the unix time which is equivalent to + this SysTime. + +/ + T toUnixTime(T = time_t)() @safe const pure nothrow + if (is(T == int) || is(T == long)) + { + return stdTimeToUnixTime!T(_stdTime); + } + + /// + @safe unittest + { + import core.time : hours; + import std.datetime.date : DateTime; + import std.datetime.timezone : SimpleTimeZone, UTC; + + assert(SysTime(DateTime(1970, 1, 1), UTC()).toUnixTime() == 0); + + auto pst = new immutable SimpleTimeZone(hours(-8)); + assert(SysTime(DateTime(1970, 1, 1), pst).toUnixTime() == 28800); + + auto utc = SysTime(DateTime(2007, 12, 22, 8, 14, 45), UTC()); + assert(utc.toUnixTime() == 1_198_311_285); + + auto ca = SysTime(DateTime(2007, 12, 22, 8, 14, 45), pst); + assert(ca.toUnixTime() == 1_198_340_085); + } + + @safe unittest + { + import std.meta : AliasSeq; + assert(SysTime(DateTime(1970, 1, 1), UTC()).toUnixTime() == 0); + foreach (units; AliasSeq!("hnsecs", "usecs", "msecs")) + assert(SysTime(DateTime(1970, 1, 1, 0, 0, 0), dur!units(1), UTC()).toUnixTime() == 0); + assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), UTC()).toUnixTime() == 1); + assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC()).toUnixTime() == 0); + assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), usecs(999_999), UTC()).toUnixTime() == 0); + assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), msecs(999), UTC()).toUnixTime() == 0); + assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), UTC()).toUnixTime() == -1); + } + + + /++ + Converts from unix time (i.e. seconds from midnight, January 1st, 1970 + in UTC) to a $(LREF SysTime). + + The C standard does not specify the representation of time_t, so it is + implementation defined. On POSIX systems, unix time is equivalent to + time_t, but that's not necessarily true on other systems (e.g. it is + not true for the Digital Mars C runtime). So, be careful when using unix + time with C functions on non-POSIX systems. + + Params: + unixTime = Seconds from midnight, January 1st, 1970 in UTC. + tz = The time zone for the SysTime that's returned. + +/ + static SysTime fromUnixTime(long unixTime, immutable TimeZone tz = LocalTime()) @safe pure nothrow + { + return SysTime(unixTimeToStdTime(unixTime), tz); + } + + /// + @safe unittest + { + import core.time : hours; + import std.datetime.date : DateTime; + import std.datetime.timezone : SimpleTimeZone, UTC; + + assert(SysTime.fromUnixTime(0) == + SysTime(DateTime(1970, 1, 1), UTC())); + + auto pst = new immutable SimpleTimeZone(hours(-8)); + assert(SysTime.fromUnixTime(28800) == + SysTime(DateTime(1970, 1, 1), pst)); + + auto st1 = SysTime.fromUnixTime(1_198_311_285, UTC()); + assert(st1 == SysTime(DateTime(2007, 12, 22, 8, 14, 45), UTC())); + assert(st1.timezone is UTC()); + assert(st1 == SysTime(DateTime(2007, 12, 22, 0, 14, 45), pst)); + + auto st2 = SysTime.fromUnixTime(1_198_311_285, pst); + assert(st2 == SysTime(DateTime(2007, 12, 22, 8, 14, 45), UTC())); + assert(st2.timezone is pst); + assert(st2 == SysTime(DateTime(2007, 12, 22, 0, 14, 45), pst)); + } + + @safe unittest + { + assert(SysTime.fromUnixTime(0) == SysTime(DateTime(1970, 1, 1), UTC())); + assert(SysTime.fromUnixTime(1) == SysTime(DateTime(1970, 1, 1, 0, 0, 1), UTC())); + assert(SysTime.fromUnixTime(-1) == SysTime(DateTime(1969, 12, 31, 23, 59, 59), UTC())); + + auto st = SysTime.fromUnixTime(0); + auto dt = cast(DateTime) st; + assert(dt <= DateTime(1970, 2, 1) && dt >= DateTime(1969, 12, 31)); + assert(st.timezone is LocalTime()); + + auto aest = new immutable SimpleTimeZone(hours(10)); + assert(SysTime.fromUnixTime(-36000) == SysTime(DateTime(1970, 1, 1), aest)); + } + + + /++ + Returns a $(D timeval) which represents this $(LREF SysTime). + + Note that like all conversions in std.datetime, this is a truncating + conversion. + + If $(D timeval.tv_sec) is int, and the result can't fit in an int, then + the closest value that can be held in 32 bits will be used for + $(D tv_sec). (so $(D int.max) if it goes over and $(D int.min) if it + goes under). + +/ + timeval toTimeVal() @safe const pure nothrow + { + immutable tv_sec = toUnixTime!(typeof(timeval.tv_sec))(); + immutable fracHNSecs = removeUnitsFromHNSecs!"seconds"(_stdTime - 621_355_968_000_000_000L); + immutable tv_usec = cast(typeof(timeval.tv_usec))convert!("hnsecs", "usecs")(fracHNSecs); + return timeval(tv_sec, tv_usec); + } + + @safe unittest + { + assert(SysTime(DateTime(1970, 1, 1), UTC()).toTimeVal() == timeval(0, 0)); + assert(SysTime(DateTime(1970, 1, 1), hnsecs(9), UTC()).toTimeVal() == timeval(0, 0)); + assert(SysTime(DateTime(1970, 1, 1), hnsecs(10), UTC()).toTimeVal() == timeval(0, 1)); + assert(SysTime(DateTime(1970, 1, 1), usecs(7), UTC()).toTimeVal() == timeval(0, 7)); + + assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), UTC()).toTimeVal() == timeval(1, 0)); + assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), hnsecs(9), UTC()).toTimeVal() == timeval(1, 0)); + assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), hnsecs(10), UTC()).toTimeVal() == timeval(1, 1)); + assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), usecs(7), UTC()).toTimeVal() == timeval(1, 7)); + + assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC()).toTimeVal() == timeval(0, 0)); + assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), hnsecs(9_999_990), UTC()).toTimeVal() == timeval(0, -1)); + + assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), usecs(999_999), UTC()).toTimeVal() == timeval(0, -1)); + assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), usecs(999), UTC()).toTimeVal() == timeval(0, -999_001)); + assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), msecs(999), UTC()).toTimeVal() == timeval(0, -1000)); + assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), UTC()).toTimeVal() == timeval(-1, 0)); + assert(SysTime(DateTime(1969, 12, 31, 23, 59, 58), usecs(17), UTC()).toTimeVal() == timeval(-1, -999_983)); + } + + + version (StdDdoc) + { + private struct timespec {} + /++ + Returns a $(D timespec) which represents this $(LREF SysTime). + + $(BLUE This function is Posix-Only.) + +/ + timespec toTimeSpec() @safe const pure nothrow; + } + else version (Posix) + { + timespec toTimeSpec() @safe const pure nothrow + { + immutable tv_sec = toUnixTime!(typeof(timespec.tv_sec))(); + immutable fracHNSecs = removeUnitsFromHNSecs!"seconds"(_stdTime - 621_355_968_000_000_000L); + immutable tv_nsec = cast(typeof(timespec.tv_nsec))convert!("hnsecs", "nsecs")(fracHNSecs); + return timespec(tv_sec, tv_nsec); + } + + @safe unittest + { + assert(SysTime(DateTime(1970, 1, 1), UTC()).toTimeSpec() == timespec(0, 0)); + assert(SysTime(DateTime(1970, 1, 1), hnsecs(9), UTC()).toTimeSpec() == timespec(0, 900)); + assert(SysTime(DateTime(1970, 1, 1), hnsecs(10), UTC()).toTimeSpec() == timespec(0, 1000)); + assert(SysTime(DateTime(1970, 1, 1), usecs(7), UTC()).toTimeSpec() == timespec(0, 7000)); + + assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), UTC()).toTimeSpec() == timespec(1, 0)); + assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), hnsecs(9), UTC()).toTimeSpec() == timespec(1, 900)); + assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), hnsecs(10), UTC()).toTimeSpec() == timespec(1, 1000)); + assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), usecs(7), UTC()).toTimeSpec() == timespec(1, 7000)); + + assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC()).toTimeSpec() == + timespec(0, -100)); + assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), hnsecs(9_999_990), UTC()).toTimeSpec() == + timespec(0, -1000)); + + assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), usecs(999_999), UTC()).toTimeSpec() == + timespec(0, -1_000)); + assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), usecs(999), UTC()).toTimeSpec() == + timespec(0, -999_001_000)); + assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), msecs(999), UTC()).toTimeSpec() == + timespec(0, -1_000_000)); + assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), UTC()).toTimeSpec() == + timespec(-1, 0)); + assert(SysTime(DateTime(1969, 12, 31, 23, 59, 58), usecs(17), UTC()).toTimeSpec() == + timespec(-1, -999_983_000)); + } + } + + /++ + Returns a $(D tm) which represents this $(LREF SysTime). + +/ + tm toTM() @safe const nothrow + { + auto dateTime = cast(DateTime) this; + tm timeInfo; + + timeInfo.tm_sec = dateTime.second; + timeInfo.tm_min = dateTime.minute; + timeInfo.tm_hour = dateTime.hour; + timeInfo.tm_mday = dateTime.day; + timeInfo.tm_mon = dateTime.month - 1; + timeInfo.tm_year = dateTime.year - 1900; + timeInfo.tm_wday = dateTime.dayOfWeek; + timeInfo.tm_yday = dateTime.dayOfYear - 1; + timeInfo.tm_isdst = _timezone.dstInEffect(_stdTime); + + version (Posix) + { + import std.utf : toUTFz; + timeInfo.tm_gmtoff = cast(int) convert!("hnsecs", "seconds")(adjTime - _stdTime); + auto zone = (timeInfo.tm_isdst ? _timezone.dstName : _timezone.stdName); + timeInfo.tm_zone = zone.toUTFz!(char*)(); + } + + return timeInfo; + } + + @system unittest + { + import std.conv : to; + + version (Posix) + { + scope(exit) clearTZEnvVar(); + setTZEnvVar("America/Los_Angeles"); + } + + { + auto timeInfo = SysTime(DateTime(1970, 1, 1)).toTM(); + + assert(timeInfo.tm_sec == 0); + assert(timeInfo.tm_min == 0); + assert(timeInfo.tm_hour == 0); + assert(timeInfo.tm_mday == 1); + assert(timeInfo.tm_mon == 0); + assert(timeInfo.tm_year == 70); + assert(timeInfo.tm_wday == 4); + assert(timeInfo.tm_yday == 0); + + version (Posix) + assert(timeInfo.tm_isdst == 0); + else version (Windows) + assert(timeInfo.tm_isdst == 0 || timeInfo.tm_isdst == 1); + + version (Posix) + { + assert(timeInfo.tm_gmtoff == -8 * 60 * 60); + assert(to!string(timeInfo.tm_zone) == "PST"); + } + } + + { + auto timeInfo = SysTime(DateTime(2010, 7, 4, 12, 15, 7), hnsecs(15)).toTM(); + + assert(timeInfo.tm_sec == 7); + assert(timeInfo.tm_min == 15); + assert(timeInfo.tm_hour == 12); + assert(timeInfo.tm_mday == 4); + assert(timeInfo.tm_mon == 6); + assert(timeInfo.tm_year == 110); + assert(timeInfo.tm_wday == 0); + assert(timeInfo.tm_yday == 184); + + version (Posix) + assert(timeInfo.tm_isdst == 1); + else version (Windows) + assert(timeInfo.tm_isdst == 0 || timeInfo.tm_isdst == 1); + + version (Posix) + { + assert(timeInfo.tm_gmtoff == -7 * 60 * 60); + assert(to!string(timeInfo.tm_zone) == "PDT"); + } + } + } + + + /++ + Adds the given number of years or months to this $(LREF SysTime). A + negative number will subtract. + + Note that if day overflow is allowed, and the date with the adjusted + year/month overflows the number of days in the new month, then the month + will be incremented by one, and the day set to the number of days + overflowed. (e.g. if the day were 31 and the new month were June, then + the month would be incremented to July, and the new day would be 1). If + day overflow is not allowed, then the day will be set to the last valid + day in the month (e.g. June 31st would become June 30th). + + Params: + units = The type of units to add ("years" or "months"). + value = The number of months or years to add to this + $(LREF SysTime). + allowOverflow = Whether the days should be allowed to overflow, + causing the month to increment. + +/ + ref SysTime add(string units)(long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe nothrow + if (units == "years" || units == "months") + { + auto hnsecs = adjTime; + auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; + + if (hnsecs < 0) + { + hnsecs += convert!("hours", "hnsecs")(24); + --days; + } + + auto date = Date(cast(int) days); + date.add!units(value, allowOverflow); + days = date.dayOfGregorianCal - 1; + + if (days < 0) + { + hnsecs -= convert!("hours", "hnsecs")(24); + ++days; + } + + immutable newDaysHNSecs = convert!("days", "hnsecs")(days); + + adjTime = newDaysHNSecs + hnsecs; + + return this; + } + + @safe unittest + { + auto st1 = SysTime(DateTime(2010, 1, 1, 12, 30, 33)); + st1.add!"months"(11); + assert(st1 == SysTime(DateTime(2010, 12, 1, 12, 30, 33))); + + auto st2 = SysTime(DateTime(2010, 1, 1, 12, 30, 33)); + st2.add!"months"(-11); + assert(st2 == SysTime(DateTime(2009, 2, 1, 12, 30, 33))); + + auto st3 = SysTime(DateTime(2000, 2, 29, 12, 30, 33)); + st3.add!"years"(1); + assert(st3 == SysTime(DateTime(2001, 3, 1, 12, 30, 33))); + + auto st4 = SysTime(DateTime(2000, 2, 29, 12, 30, 33)); + st4.add!"years"(1, AllowDayOverflow.no); + assert(st4 == SysTime(DateTime(2001, 2, 28, 12, 30, 33))); + } + + // Test add!"years"() with AllowDayOverflow.yes + @safe unittest + { + // Test A.D. + { + auto sysTime = SysTime(Date(1999, 7, 6)); + sysTime.add!"years"(7); + assert(sysTime == SysTime(Date(2006, 7, 6))); + sysTime.add!"years"(-9); + assert(sysTime == SysTime(Date(1997, 7, 6))); + } + + { + auto sysTime = SysTime(Date(1999, 2, 28)); + sysTime.add!"years"(1); + assert(sysTime == SysTime(Date(2000, 2, 28))); + } + + { + auto sysTime = SysTime(Date(2000, 2, 29)); + sysTime.add!"years"(-1); + assert(sysTime == SysTime(Date(1999, 3, 1))); + } + + { + auto sysTime = SysTime(DateTime(1999, 7, 6, 12, 7, 3), msecs(234)); + sysTime.add!"years"(7); + assert(sysTime == SysTime(DateTime(2006, 7, 6, 12, 7, 3), msecs(234))); + sysTime.add!"years"(-9); + assert(sysTime == SysTime(DateTime(1997, 7, 6, 12, 7, 3), msecs(234))); + } + + { + auto sysTime = SysTime(DateTime(1999, 2, 28, 0, 7, 2), usecs(1207)); + sysTime.add!"years"(1); + assert(sysTime == SysTime(DateTime(2000, 2, 28, 0, 7, 2), usecs(1207))); + } + + { + auto sysTime = SysTime(DateTime(2000, 2, 29, 0, 7, 2), usecs(1207)); + sysTime.add!"years"(-1); + assert(sysTime == SysTime(DateTime(1999, 3, 1, 0, 7, 2), usecs(1207))); + } + + // Test B.C. + { + auto sysTime = SysTime(Date(-1999, 7, 6)); + sysTime.add!"years"(-7); + assert(sysTime == SysTime(Date(-2006, 7, 6))); + sysTime.add!"years"(9); + assert(sysTime == SysTime(Date(-1997, 7, 6))); + } + + { + auto sysTime = SysTime(Date(-1999, 2, 28)); + sysTime.add!"years"(-1); + assert(sysTime == SysTime(Date(-2000, 2, 28))); + } + + { + auto sysTime = SysTime(Date(-2000, 2, 29)); + sysTime.add!"years"(1); + assert(sysTime == SysTime(Date(-1999, 3, 1))); + } + + { + auto sysTime = SysTime(DateTime(-1999, 7, 6, 12, 7, 3), msecs(234)); + sysTime.add!"years"(-7); + assert(sysTime == SysTime(DateTime(-2006, 7, 6, 12, 7, 3), msecs(234))); + sysTime.add!"years"(9); + assert(sysTime == SysTime(DateTime(-1997, 7, 6, 12, 7, 3), msecs(234))); + } + + { + auto sysTime = SysTime(DateTime(-1999, 2, 28, 3, 3, 3), hnsecs(3)); + sysTime.add!"years"(-1); + assert(sysTime == SysTime(DateTime(-2000, 2, 28, 3, 3, 3), hnsecs(3))); + } + + { + auto sysTime = SysTime(DateTime(-2000, 2, 29, 3, 3, 3), hnsecs(3)); + sysTime.add!"years"(1); + assert(sysTime == SysTime(DateTime(-1999, 3, 1, 3, 3, 3), hnsecs(3))); + } + + // Test Both + { + auto sysTime = SysTime(Date(4, 7, 6)); + sysTime.add!"years"(-5); + assert(sysTime == SysTime(Date(-1, 7, 6))); + sysTime.add!"years"(5); + assert(sysTime == SysTime(Date(4, 7, 6))); + } + + { + auto sysTime = SysTime(Date(-4, 7, 6)); + sysTime.add!"years"(5); + assert(sysTime == SysTime(Date(1, 7, 6))); + sysTime.add!"years"(-5); + assert(sysTime == SysTime(Date(-4, 7, 6))); + } + + { + auto sysTime = SysTime(Date(4, 7, 6)); + sysTime.add!"years"(-8); + assert(sysTime == SysTime(Date(-4, 7, 6))); + sysTime.add!"years"(8); + assert(sysTime == SysTime(Date(4, 7, 6))); + } + + { + auto sysTime = SysTime(Date(-4, 7, 6)); + sysTime.add!"years"(8); + assert(sysTime == SysTime(Date(4, 7, 6))); + sysTime.add!"years"(-8); + assert(sysTime == SysTime(Date(-4, 7, 6))); + } + + { + auto sysTime = SysTime(Date(-4, 2, 29)); + sysTime.add!"years"(5); + assert(sysTime == SysTime(Date(1, 3, 1))); + } + + { + auto sysTime = SysTime(Date(4, 2, 29)); + sysTime.add!"years"(-5); + assert(sysTime == SysTime(Date(-1, 3, 1))); + } + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0)); + sysTime.add!"years"(-1); + assert(sysTime == SysTime(DateTime(0, 1, 1, 0, 0, 0))); + sysTime.add!"years"(1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0))); + } + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)); + sysTime.add!"years"(-1); + assert(sysTime == SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + sysTime.add!"years"(1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + } + + { + auto sysTime = SysTime(DateTime(0, 1, 1, 0, 0, 0)); + sysTime.add!"years"(1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0))); + sysTime.add!"years"(-1); + assert(sysTime == SysTime(DateTime(0, 1, 1, 0, 0, 0))); + } + + { + auto sysTime = SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999)); + sysTime.add!"years"(1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + sysTime.add!"years"(-1); + assert(sysTime == SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + } + + { + auto sysTime = SysTime(DateTime(4, 7, 6, 14, 7, 1), usecs(54329)); + sysTime.add!"years"(-5); + assert(sysTime == SysTime(DateTime(-1, 7, 6, 14, 7, 1), usecs(54329))); + sysTime.add!"years"(5); + assert(sysTime == SysTime(DateTime(4, 7, 6, 14, 7, 1), usecs(54329))); + } + + { + auto sysTime = SysTime(DateTime(-4, 7, 6, 14, 7, 1), usecs(54329)); + sysTime.add!"years"(5); + assert(sysTime == SysTime(DateTime(1, 7, 6, 14, 7, 1), usecs(54329))); + sysTime.add!"years"(-5); + assert(sysTime == SysTime(DateTime(-4, 7, 6, 14, 7, 1), usecs(54329))); + } + + { + auto sysTime = SysTime(DateTime(-4, 2, 29, 5, 5, 5), msecs(555)); + sysTime.add!"years"(5); + assert(sysTime == SysTime(DateTime(1, 3, 1, 5, 5, 5), msecs(555))); + } + + { + auto sysTime = SysTime(DateTime(4, 2, 29, 5, 5, 5), msecs(555)); + sysTime.add!"years"(-5); + assert(sysTime == SysTime(DateTime(-1, 3, 1, 5, 5, 5), msecs(555))); + } + + { + auto sysTime = SysTime(DateTime(4, 2, 29, 5, 5, 5), msecs(555)); + sysTime.add!"years"(-5).add!"years"(7); + assert(sysTime == SysTime(DateTime(6, 3, 1, 5, 5, 5), msecs(555))); + } + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.add!"years"(4))); + //static assert(!__traits(compiles, ist.add!"years"(4))); + } + + // Test add!"years"() with AllowDayOverflow.no + @safe unittest + { + // Test A.D. + { + auto sysTime = SysTime(Date(1999, 7, 6)); + sysTime.add!"years"(7, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(2006, 7, 6))); + sysTime.add!"years"(-9, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1997, 7, 6))); + } + + { + auto sysTime = SysTime(Date(1999, 2, 28)); + sysTime.add!"years"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(2000, 2, 28))); + } + + { + auto sysTime = SysTime(Date(2000, 2, 29)); + sysTime.add!"years"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 2, 28))); + } + + { + auto sysTime = SysTime(DateTime(1999, 7, 6, 12, 7, 3), msecs(234)); + sysTime.add!"years"(7, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(2006, 7, 6, 12, 7, 3), msecs(234))); + sysTime.add!"years"(-9, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1997, 7, 6, 12, 7, 3), msecs(234))); + } + + { + auto sysTime = SysTime(DateTime(1999, 2, 28, 0, 7, 2), usecs(1207)); + sysTime.add!"years"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(2000, 2, 28, 0, 7, 2), usecs(1207))); + } + + { + auto sysTime = SysTime(DateTime(2000, 2, 29, 0, 7, 2), usecs(1207)); + sysTime.add!"years"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1999, 2, 28, 0, 7, 2), usecs(1207))); + } + + // Test B.C. + { + auto sysTime = SysTime(Date(-1999, 7, 6)); + sysTime.add!"years"(-7, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-2006, 7, 6))); + sysTime.add!"years"(9, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1997, 7, 6))); + } + + { + auto sysTime = SysTime(Date(-1999, 2, 28)); + sysTime.add!"years"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-2000, 2, 28))); + } + + { + auto sysTime = SysTime(Date(-2000, 2, 29)); + sysTime.add!"years"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1999, 2, 28))); + } + + { + auto sysTime = SysTime(DateTime(-1999, 7, 6, 12, 7, 3), msecs(234)); + sysTime.add!"years"(-7, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-2006, 7, 6, 12, 7, 3), msecs(234))); + sysTime.add!"years"(9, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-1997, 7, 6, 12, 7, 3), msecs(234))); + } + + { + auto sysTime = SysTime(DateTime(-1999, 2, 28, 3, 3, 3), hnsecs(3)); + sysTime.add!"years"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-2000, 2, 28, 3, 3, 3), hnsecs(3))); + } + + { + auto sysTime = SysTime(DateTime(-2000, 2, 29, 3, 3, 3), hnsecs(3)); + sysTime.add!"years"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-1999, 2, 28, 3, 3, 3), hnsecs(3))); + } + + // Test Both + { + auto sysTime = SysTime(Date(4, 7, 6)); + sysTime.add!"years"(-5, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1, 7, 6))); + sysTime.add!"years"(5, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(4, 7, 6))); + } + + { + auto sysTime = SysTime(Date(-4, 7, 6)); + sysTime.add!"years"(5, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1, 7, 6))); + sysTime.add!"years"(-5, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-4, 7, 6))); + } + + { + auto sysTime = SysTime(Date(4, 7, 6)); + sysTime.add!"years"(-8, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-4, 7, 6))); + sysTime.add!"years"(8, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(4, 7, 6))); + } + + { + auto sysTime = SysTime(Date(-4, 7, 6)); + sysTime.add!"years"(8, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(4, 7, 6))); + sysTime.add!"years"(-8, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-4, 7, 6))); + } + + { + auto sysTime = SysTime(Date(-4, 2, 29)); + sysTime.add!"years"(5, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1, 2, 28))); + } + + { + auto sysTime = SysTime(Date(4, 2, 29)); + sysTime.add!"years"(-5, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1, 2, 28))); + } + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0)); + sysTime.add!"years"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(0, 1, 1, 0, 0, 0))); + sysTime.add!"years"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0))); + } + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)); + sysTime.add!"years"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + sysTime.add!"years"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + } + + { + auto sysTime = SysTime(DateTime(0, 1, 1, 0, 0, 0)); + sysTime.add!"years"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0))); + sysTime.add!"years"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(0, 1, 1, 0, 0, 0))); + } + + { + auto sysTime = SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999)); + sysTime.add!"years"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + sysTime.add!"years"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + } + + { + auto sysTime = SysTime(DateTime(4, 7, 6, 14, 7, 1), usecs(54329)); + sysTime.add!"years"(-5); + assert(sysTime == SysTime(DateTime(-1, 7, 6, 14, 7, 1), usecs(54329))); + sysTime.add!"years"(5); + assert(sysTime == SysTime(DateTime(4, 7, 6, 14, 7, 1), usecs(54329))); + } + + { + auto sysTime = SysTime(DateTime(4, 7, 6, 14, 7, 1), usecs(54329)); + sysTime.add!"years"(-5, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-1, 7, 6, 14, 7, 1), usecs(54329))); + sysTime.add!"years"(5, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(4, 7, 6, 14, 7, 1), usecs(54329))); + } + + { + auto sysTime = SysTime(DateTime(-4, 7, 6, 14, 7, 1), usecs(54329)); + sysTime.add!"years"(5, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1, 7, 6, 14, 7, 1), usecs(54329))); + sysTime.add!"years"(-5, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-4, 7, 6, 14, 7, 1), usecs(54329))); + } + + { + auto sysTime = SysTime(DateTime(-4, 2, 29, 5, 5, 5), msecs(555)); + sysTime.add!"years"(5, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1, 2, 28, 5, 5, 5), msecs(555))); + } + + { + auto sysTime = SysTime(DateTime(4, 2, 29, 5, 5, 5), msecs(555)); + sysTime.add!"years"(-5, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-1, 2, 28, 5, 5, 5), msecs(555))); + } + + { + auto sysTime = SysTime(DateTime(4, 2, 29, 5, 5, 5), msecs(555)); + sysTime.add!"years"(-5, AllowDayOverflow.no).add!"years"(7, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(6, 2, 28, 5, 5, 5), msecs(555))); + } + } + + // Test add!"months"() with AllowDayOverflow.yes + @safe unittest + { + // Test A.D. + { + auto sysTime = SysTime(Date(1999, 7, 6)); + sysTime.add!"months"(3); + assert(sysTime == SysTime(Date(1999, 10, 6))); + sysTime.add!"months"(-4); + assert(sysTime == SysTime(Date(1999, 6, 6))); + } + + { + auto sysTime = SysTime(Date(1999, 7, 6)); + sysTime.add!"months"(6); + assert(sysTime == SysTime(Date(2000, 1, 6))); + sysTime.add!"months"(-6); + assert(sysTime == SysTime(Date(1999, 7, 6))); + } + + { + auto sysTime = SysTime(Date(1999, 7, 6)); + sysTime.add!"months"(27); + assert(sysTime == SysTime(Date(2001, 10, 6))); + sysTime.add!"months"(-28); + assert(sysTime == SysTime(Date(1999, 6, 6))); + } + + { + auto sysTime = SysTime(Date(1999, 5, 31)); + sysTime.add!"months"(1); + assert(sysTime == SysTime(Date(1999, 7, 1))); + } + + { + auto sysTime = SysTime(Date(1999, 5, 31)); + sysTime.add!"months"(-1); + assert(sysTime == SysTime(Date(1999, 5, 1))); + } + + { + auto sysTime = SysTime(Date(1999, 2, 28)); + sysTime.add!"months"(12); + assert(sysTime == SysTime(Date(2000, 2, 28))); + } + + { + auto sysTime = SysTime(Date(2000, 2, 29)); + sysTime.add!"months"(12); + assert(sysTime == SysTime(Date(2001, 3, 1))); + } + + { + auto sysTime = SysTime(Date(1999, 7, 31)); + sysTime.add!"months"(1); + assert(sysTime == SysTime(Date(1999, 8, 31))); + sysTime.add!"months"(1); + assert(sysTime == SysTime(Date(1999, 10, 1))); + } + + { + auto sysTime = SysTime(Date(1998, 8, 31)); + sysTime.add!"months"(13); + assert(sysTime == SysTime(Date(1999, 10, 1))); + sysTime.add!"months"(-13); + assert(sysTime == SysTime(Date(1998, 9, 1))); + } + + { + auto sysTime = SysTime(Date(1997, 12, 31)); + sysTime.add!"months"(13); + assert(sysTime == SysTime(Date(1999, 1, 31))); + sysTime.add!"months"(-13); + assert(sysTime == SysTime(Date(1997, 12, 31))); + } + + { + auto sysTime = SysTime(Date(1997, 12, 31)); + sysTime.add!"months"(14); + assert(sysTime == SysTime(Date(1999, 3, 3))); + sysTime.add!"months"(-14); + assert(sysTime == SysTime(Date(1998, 1, 3))); + } + + { + auto sysTime = SysTime(Date(1998, 12, 31)); + sysTime.add!"months"(14); + assert(sysTime == SysTime(Date(2000, 3, 2))); + sysTime.add!"months"(-14); + assert(sysTime == SysTime(Date(1999, 1, 2))); + } + + { + auto sysTime = SysTime(Date(1999, 12, 31)); + sysTime.add!"months"(14); + assert(sysTime == SysTime(Date(2001, 3, 3))); + sysTime.add!"months"(-14); + assert(sysTime == SysTime(Date(2000, 1, 3))); + } + + { + auto sysTime = SysTime(DateTime(1999, 7, 6, 12, 2, 7), usecs(5007)); + sysTime.add!"months"(3); + assert(sysTime == SysTime(DateTime(1999, 10, 6, 12, 2, 7), usecs(5007))); + sysTime.add!"months"(-4); + assert(sysTime == SysTime(DateTime(1999, 6, 6, 12, 2, 7), usecs(5007))); + } + + { + auto sysTime = SysTime(DateTime(1998, 12, 31, 7, 7, 7), hnsecs(422202)); + sysTime.add!"months"(14); + assert(sysTime == SysTime(DateTime(2000, 3, 2, 7, 7, 7), hnsecs(422202))); + sysTime.add!"months"(-14); + assert(sysTime == SysTime(DateTime(1999, 1, 2, 7, 7, 7), hnsecs(422202))); + } + + { + auto sysTime = SysTime(DateTime(1999, 12, 31, 7, 7, 7), hnsecs(422202)); + sysTime.add!"months"(14); + assert(sysTime == SysTime(DateTime(2001, 3, 3, 7, 7, 7), hnsecs(422202))); + sysTime.add!"months"(-14); + assert(sysTime == SysTime(DateTime(2000, 1, 3, 7, 7, 7), hnsecs(422202))); + } + + // Test B.C. + { + auto sysTime = SysTime(Date(-1999, 7, 6)); + sysTime.add!"months"(3); + assert(sysTime == SysTime(Date(-1999, 10, 6))); + sysTime.add!"months"(-4); + assert(sysTime == SysTime(Date(-1999, 6, 6))); + } + + { + auto sysTime = SysTime(Date(-1999, 7, 6)); + sysTime.add!"months"(6); + assert(sysTime == SysTime(Date(-1998, 1, 6))); + sysTime.add!"months"(-6); + assert(sysTime == SysTime(Date(-1999, 7, 6))); + } + + { + auto sysTime = SysTime(Date(-1999, 7, 6)); + sysTime.add!"months"(-27); + assert(sysTime == SysTime(Date(-2001, 4, 6))); + sysTime.add!"months"(28); + assert(sysTime == SysTime(Date(-1999, 8, 6))); + } + + { + auto sysTime = SysTime(Date(-1999, 5, 31)); + sysTime.add!"months"(1); + assert(sysTime == SysTime(Date(-1999, 7, 1))); + } + + { + auto sysTime = SysTime(Date(-1999, 5, 31)); + sysTime.add!"months"(-1); + assert(sysTime == SysTime(Date(-1999, 5, 1))); + } + + { + auto sysTime = SysTime(Date(-1999, 2, 28)); + sysTime.add!"months"(-12); + assert(sysTime == SysTime(Date(-2000, 2, 28))); + } + + { + auto sysTime = SysTime(Date(-2000, 2, 29)); + sysTime.add!"months"(-12); + assert(sysTime == SysTime(Date(-2001, 3, 1))); + } + + { + auto sysTime = SysTime(Date(-1999, 7, 31)); + sysTime.add!"months"(1); + assert(sysTime == SysTime(Date(-1999, 8, 31))); + sysTime.add!"months"(1); + assert(sysTime == SysTime(Date(-1999, 10, 1))); + } + + { + auto sysTime = SysTime(Date(-1998, 8, 31)); + sysTime.add!"months"(13); + assert(sysTime == SysTime(Date(-1997, 10, 1))); + sysTime.add!"months"(-13); + assert(sysTime == SysTime(Date(-1998, 9, 1))); + } + + { + auto sysTime = SysTime(Date(-1997, 12, 31)); + sysTime.add!"months"(13); + assert(sysTime == SysTime(Date(-1995, 1, 31))); + sysTime.add!"months"(-13); + assert(sysTime == SysTime(Date(-1997, 12, 31))); + } + + { + auto sysTime = SysTime(Date(-1997, 12, 31)); + sysTime.add!"months"(14); + assert(sysTime == SysTime(Date(-1995, 3, 3))); + sysTime.add!"months"(-14); + assert(sysTime == SysTime(Date(-1996, 1, 3))); + } + + { + auto sysTime = SysTime(Date(-2002, 12, 31)); + sysTime.add!"months"(14); + assert(sysTime == SysTime(Date(-2000, 3, 2))); + sysTime.add!"months"(-14); + assert(sysTime == SysTime(Date(-2001, 1, 2))); + } + + { + auto sysTime = SysTime(Date(-2001, 12, 31)); + sysTime.add!"months"(14); + assert(sysTime == SysTime(Date(-1999, 3, 3))); + sysTime.add!"months"(-14); + assert(sysTime == SysTime(Date(-2000, 1, 3))); + } + + { + auto sysTime = SysTime(DateTime(-1999, 7, 6, 12, 2, 7), usecs(5007)); + sysTime.add!"months"(3); + assert(sysTime == SysTime(DateTime(-1999, 10, 6, 12, 2, 7), usecs(5007))); + sysTime.add!"months"(-4); + assert(sysTime == SysTime(DateTime(-1999, 6, 6, 12, 2, 7), usecs(5007))); + } + + { + auto sysTime = SysTime(DateTime(-2002, 12, 31, 7, 7, 7), hnsecs(422202)); + sysTime.add!"months"(14); + assert(sysTime == SysTime(DateTime(-2000, 3, 2, 7, 7, 7), hnsecs(422202))); + sysTime.add!"months"(-14); + assert(sysTime == SysTime(DateTime(-2001, 1, 2, 7, 7, 7), hnsecs(422202))); + } + + { + auto sysTime = SysTime(DateTime(-2001, 12, 31, 7, 7, 7), hnsecs(422202)); + sysTime.add!"months"(14); + assert(sysTime == SysTime(DateTime(-1999, 3, 3, 7, 7, 7), hnsecs(422202))); + sysTime.add!"months"(-14); + assert(sysTime == SysTime(DateTime(-2000, 1, 3, 7, 7, 7), hnsecs(422202))); + } + + // Test Both + { + auto sysTime = SysTime(Date(1, 1, 1)); + sysTime.add!"months"(-1); + assert(sysTime == SysTime(Date(0, 12, 1))); + sysTime.add!"months"(1); + assert(sysTime == SysTime(Date(1, 1, 1))); + } + + { + auto sysTime = SysTime(Date(4, 1, 1)); + sysTime.add!"months"(-48); + assert(sysTime == SysTime(Date(0, 1, 1))); + sysTime.add!"months"(48); + assert(sysTime == SysTime(Date(4, 1, 1))); + } + + { + auto sysTime = SysTime(Date(4, 3, 31)); + sysTime.add!"months"(-49); + assert(sysTime == SysTime(Date(0, 3, 2))); + sysTime.add!"months"(49); + assert(sysTime == SysTime(Date(4, 4, 2))); + } + + { + auto sysTime = SysTime(Date(4, 3, 31)); + sysTime.add!"months"(-85); + assert(sysTime == SysTime(Date(-3, 3, 3))); + sysTime.add!"months"(85); + assert(sysTime == SysTime(Date(4, 4, 3))); + } + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0)); + sysTime.add!"months"(-1); + assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 0, 0))); + sysTime.add!"months"(1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0))); + } + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)); + sysTime.add!"months"(-1); + assert(sysTime == SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999))); + sysTime.add!"months"(1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + } + + { + auto sysTime = SysTime(DateTime(0, 12, 1, 0, 0, 0)); + sysTime.add!"months"(1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0))); + sysTime.add!"months"(-1); + assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 0, 0))); + } + + { + auto sysTime = SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999)); + sysTime.add!"months"(1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + sysTime.add!"months"(-1); + assert(sysTime == SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999))); + } + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17)); + sysTime.add!"months"(-1); + assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 7, 9), hnsecs(17))); + sysTime.add!"months"(1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17))); + } + + { + auto sysTime = SysTime(DateTime(4, 3, 31, 12, 11, 10), msecs(9)); + sysTime.add!"months"(-85); + assert(sysTime == SysTime(DateTime(-3, 3, 3, 12, 11, 10), msecs(9))); + sysTime.add!"months"(85); + assert(sysTime == SysTime(DateTime(4, 4, 3, 12, 11, 10), msecs(9))); + } + + { + auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9)); + sysTime.add!"months"(85); + assert(sysTime == SysTime(DateTime(4, 5, 1, 12, 11, 10), msecs(9))); + sysTime.add!"months"(-85); + assert(sysTime == SysTime(DateTime(-3, 4, 1, 12, 11, 10), msecs(9))); + } + + { + auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9)); + sysTime.add!"months"(85).add!"months"(-83); + assert(sysTime == SysTime(DateTime(-3, 6, 1, 12, 11, 10), msecs(9))); + } + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.add!"months"(4))); + //static assert(!__traits(compiles, ist.add!"months"(4))); + } + + // Test add!"months"() with AllowDayOverflow.no + @safe unittest + { + // Test A.D. + { + auto sysTime = SysTime(Date(1999, 7, 6)); + sysTime.add!"months"(3, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 10, 6))); + sysTime.add!"months"(-4, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 6, 6))); + } + + { + auto sysTime = SysTime(Date(1999, 7, 6)); + sysTime.add!"months"(6, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(2000, 1, 6))); + sysTime.add!"months"(-6, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 7, 6))); + } + + { + auto sysTime = SysTime(Date(1999, 7, 6)); + sysTime.add!"months"(27, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(2001, 10, 6))); + sysTime.add!"months"(-28, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 6, 6))); + } + + { + auto sysTime = SysTime(Date(1999, 5, 31)); + sysTime.add!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 6, 30))); + } + + { + auto sysTime = SysTime(Date(1999, 5, 31)); + sysTime.add!"months"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 4, 30))); + } + + { + auto sysTime = SysTime(Date(1999, 2, 28)); + sysTime.add!"months"(12, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(2000, 2, 28))); + } + + { + auto sysTime = SysTime(Date(2000, 2, 29)); + sysTime.add!"months"(12, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(2001, 2, 28))); + } + + { + auto sysTime = SysTime(Date(1999, 7, 31)); + sysTime.add!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 8, 31))); + sysTime.add!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 9, 30))); + } + + { + auto sysTime = SysTime(Date(1998, 8, 31)); + sysTime.add!"months"(13, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 9, 30))); + sysTime.add!"months"(-13, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1998, 8, 30))); + } + + { + auto sysTime = SysTime(Date(1997, 12, 31)); + sysTime.add!"months"(13, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 1, 31))); + sysTime.add!"months"(-13, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1997, 12, 31))); + } + + { + auto sysTime = SysTime(Date(1997, 12, 31)); + sysTime.add!"months"(14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 2, 28))); + sysTime.add!"months"(-14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1997, 12, 28))); + } + + { + auto sysTime = SysTime(Date(1998, 12, 31)); + sysTime.add!"months"(14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(2000, 2, 29))); + sysTime.add!"months"(-14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1998, 12, 29))); + } + + { + auto sysTime = SysTime(Date(1999, 12, 31)); + sysTime.add!"months"(14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(2001, 2, 28))); + sysTime.add!"months"(-14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 12, 28))); + } + + { + auto sysTime = SysTime(DateTime(1999, 7, 6, 12, 2, 7), usecs(5007)); + sysTime.add!"months"(3, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1999, 10, 6, 12, 2, 7), usecs(5007))); + sysTime.add!"months"(-4, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1999, 6, 6, 12, 2, 7), usecs(5007))); + } + + { + auto sysTime = SysTime(DateTime(1998, 12, 31, 7, 7, 7), hnsecs(422202)); + sysTime.add!"months"(14, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(2000, 2, 29, 7, 7, 7), hnsecs(422202))); + sysTime.add!"months"(-14, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1998, 12, 29, 7, 7, 7), hnsecs(422202))); + } + + { + auto sysTime = SysTime(DateTime(1999, 12, 31, 7, 7, 7), hnsecs(422202)); + sysTime.add!"months"(14, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(2001, 2, 28, 7, 7, 7), hnsecs(422202))); + sysTime.add!"months"(-14, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1999, 12, 28, 7, 7, 7), hnsecs(422202))); + } + + // Test B.C. + { + auto sysTime = SysTime(Date(-1999, 7, 6)); + sysTime.add!"months"(3, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1999, 10, 6))); + sysTime.add!"months"(-4, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1999, 6, 6))); + } + + { + auto sysTime = SysTime(Date(-1999, 7, 6)); + sysTime.add!"months"(6, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1998, 1, 6))); + sysTime.add!"months"(-6, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1999, 7, 6))); + } + + { + auto sysTime = SysTime(Date(-1999, 7, 6)); + sysTime.add!"months"(-27, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-2001, 4, 6))); + sysTime.add!"months"(28, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1999, 8, 6))); + } + + { + auto sysTime = SysTime(Date(-1999, 5, 31)); + sysTime.add!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1999, 6, 30))); + } + + { + auto sysTime = SysTime(Date(-1999, 5, 31)); + sysTime.add!"months"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1999, 4, 30))); + } + + { + auto sysTime = SysTime(Date(-1999, 2, 28)); + sysTime.add!"months"(-12, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-2000, 2, 28))); + } + + { + auto sysTime = SysTime(Date(-2000, 2, 29)); + sysTime.add!"months"(-12, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-2001, 2, 28))); + } + + { + auto sysTime = SysTime(Date(-1999, 7, 31)); + sysTime.add!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1999, 8, 31))); + sysTime.add!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1999, 9, 30))); + } + + { + auto sysTime = SysTime(Date(-1998, 8, 31)); + sysTime.add!"months"(13, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1997, 9, 30))); + sysTime.add!"months"(-13, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1998, 8, 30))); + } + + { + auto sysTime = SysTime(Date(-1997, 12, 31)); + sysTime.add!"months"(13, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1995, 1, 31))); + sysTime.add!"months"(-13, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1997, 12, 31))); + } + + { + auto sysTime = SysTime(Date(-1997, 12, 31)); + sysTime.add!"months"(14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1995, 2, 28))); + sysTime.add!"months"(-14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1997, 12, 28))); + } + + { + auto sysTime = SysTime(Date(-2002, 12, 31)); + sysTime.add!"months"(14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-2000, 2, 29))); + sysTime.add!"months"(-14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-2002, 12, 29))); + } + + { + auto sysTime = SysTime(Date(-2001, 12, 31)); + sysTime.add!"months"(14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1999, 2, 28))); + sysTime.add!"months"(-14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-2001, 12, 28))); + } + + { + auto sysTime = SysTime(DateTime(-1999, 7, 6, 12, 2, 7), usecs(5007)); + sysTime.add!"months"(3, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-1999, 10, 6, 12, 2, 7), usecs(5007))); + sysTime.add!"months"(-4, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-1999, 6, 6, 12, 2, 7), usecs(5007))); + } + + { + auto sysTime = SysTime(DateTime(-2002, 12, 31, 7, 7, 7), hnsecs(422202)); + sysTime.add!"months"(14, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-2000, 2, 29, 7, 7, 7), hnsecs(422202))); + sysTime.add!"months"(-14, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-2002, 12, 29, 7, 7, 7), hnsecs(422202))); + } + + { + auto sysTime = SysTime(DateTime(-2001, 12, 31, 7, 7, 7), hnsecs(422202)); + sysTime.add!"months"(14, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-1999, 2, 28, 7, 7, 7), hnsecs(422202))); + sysTime.add!"months"(-14, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-2001, 12, 28, 7, 7, 7), hnsecs(422202))); + } + + // Test Both + { + auto sysTime = SysTime(Date(1, 1, 1)); + sysTime.add!"months"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(0, 12, 1))); + sysTime.add!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1, 1, 1))); + } + + { + auto sysTime = SysTime(Date(4, 1, 1)); + sysTime.add!"months"(-48, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(0, 1, 1))); + sysTime.add!"months"(48, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(4, 1, 1))); + } + + { + auto sysTime = SysTime(Date(4, 3, 31)); + sysTime.add!"months"(-49, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(0, 2, 29))); + sysTime.add!"months"(49, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(4, 3, 29))); + } + + { + auto sysTime = SysTime(Date(4, 3, 31)); + sysTime.add!"months"(-85, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-3, 2, 28))); + sysTime.add!"months"(85, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(4, 3, 28))); + } + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0)); + sysTime.add!"months"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 0, 0))); + sysTime.add!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0))); + } + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)); + sysTime.add!"months"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999))); + sysTime.add!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + } + + { + auto sysTime = SysTime(DateTime(0, 12, 1, 0, 0, 0)); + sysTime.add!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0))); + sysTime.add!"months"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 0, 0))); + } + + { + auto sysTime = SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999)); + sysTime.add!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + sysTime.add!"months"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999))); + } + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17)); + sysTime.add!"months"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 7, 9), hnsecs(17))); + sysTime.add!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17))); + } + + { + auto sysTime = SysTime(DateTime(4, 3, 31, 12, 11, 10), msecs(9)); + sysTime.add!"months"(-85, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-3, 2, 28, 12, 11, 10), msecs(9))); + sysTime.add!"months"(85, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(4, 3, 28, 12, 11, 10), msecs(9))); + } + + { + auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9)); + sysTime.add!"months"(85, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(4, 4, 30, 12, 11, 10), msecs(9))); + sysTime.add!"months"(-85, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-3, 3, 30, 12, 11, 10), msecs(9))); + } + + { + auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9)); + sysTime.add!"months"(85, AllowDayOverflow.no).add!"months"(-83, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-3, 5, 30, 12, 11, 10), msecs(9))); + } + } + + + /++ + Adds the given number of years or months to this $(LREF SysTime). A + negative number will subtract. + + The difference between rolling and adding is that rolling does not + affect larger units. Rolling a $(LREF SysTime) 12 months + gets the exact same $(LREF SysTime). However, the days can still be + affected due to the differing number of days in each month. + + Because there are no units larger than years, there is no difference + between adding and rolling years. + + Params: + units = The type of units to add ("years" or "months"). + value = The number of months or years to add to this + $(LREF SysTime). + allowOverflow = Whether the days should be allowed to overflow, + causing the month to increment. + +/ + ref SysTime roll(string units)(long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe nothrow + if (units == "years") + { + return add!"years"(value, allowOverflow); + } + + /// + @safe unittest + { + import std.datetime.date : AllowDayOverflow, DateTime; + + auto st1 = SysTime(DateTime(2010, 1, 1, 12, 33, 33)); + st1.roll!"months"(1); + assert(st1 == SysTime(DateTime(2010, 2, 1, 12, 33, 33))); + + auto st2 = SysTime(DateTime(2010, 1, 1, 12, 33, 33)); + st2.roll!"months"(-1); + assert(st2 == SysTime(DateTime(2010, 12, 1, 12, 33, 33))); + + auto st3 = SysTime(DateTime(1999, 1, 29, 12, 33, 33)); + st3.roll!"months"(1); + assert(st3 == SysTime(DateTime(1999, 3, 1, 12, 33, 33))); + + auto st4 = SysTime(DateTime(1999, 1, 29, 12, 33, 33)); + st4.roll!"months"(1, AllowDayOverflow.no); + assert(st4 == SysTime(DateTime(1999, 2, 28, 12, 33, 33))); + + auto st5 = SysTime(DateTime(2000, 2, 29, 12, 30, 33)); + st5.roll!"years"(1); + assert(st5 == SysTime(DateTime(2001, 3, 1, 12, 30, 33))); + + auto st6 = SysTime(DateTime(2000, 2, 29, 12, 30, 33)); + st6.roll!"years"(1, AllowDayOverflow.no); + assert(st6 == SysTime(DateTime(2001, 2, 28, 12, 30, 33))); + } + + @safe unittest + { + auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + st.roll!"years"(4); + static assert(!__traits(compiles, cst.roll!"years"(4))); + //static assert(!__traits(compiles, ist.roll!"years"(4))); + } + + + // Shares documentation with "years" overload. + ref SysTime roll(string units)(long value, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) @safe nothrow + if (units == "months") + { + auto hnsecs = adjTime; + auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; + + if (hnsecs < 0) + { + hnsecs += convert!("hours", "hnsecs")(24); + --days; + } + + auto date = Date(cast(int) days); + date.roll!"months"(value, allowOverflow); + days = date.dayOfGregorianCal - 1; + + if (days < 0) + { + hnsecs -= convert!("hours", "hnsecs")(24); + ++days; + } + + immutable newDaysHNSecs = convert!("days", "hnsecs")(days); + adjTime = newDaysHNSecs + hnsecs; + return this; + } + + // Test roll!"months"() with AllowDayOverflow.yes + @safe unittest + { + // Test A.D. + { + auto sysTime = SysTime(Date(1999, 7, 6)); + sysTime.roll!"months"(3); + assert(sysTime == SysTime(Date(1999, 10, 6))); + sysTime.roll!"months"(-4); + assert(sysTime == SysTime(Date(1999, 6, 6))); + } + + { + auto sysTime = SysTime(Date(1999, 7, 6)); + sysTime.roll!"months"(6); + assert(sysTime == SysTime(Date(1999, 1, 6))); + sysTime.roll!"months"(-6); + assert(sysTime == SysTime(Date(1999, 7, 6))); + } + + { + auto sysTime = SysTime(Date(1999, 7, 6)); + sysTime.roll!"months"(27); + assert(sysTime == SysTime(Date(1999, 10, 6))); + sysTime.roll!"months"(-28); + assert(sysTime == SysTime(Date(1999, 6, 6))); + } + + { + auto sysTime = SysTime(Date(1999, 5, 31)); + sysTime.roll!"months"(1); + assert(sysTime == SysTime(Date(1999, 7, 1))); + } + + { + auto sysTime = SysTime(Date(1999, 5, 31)); + sysTime.roll!"months"(-1); + assert(sysTime == SysTime(Date(1999, 5, 1))); + } + + { + auto sysTime = SysTime(Date(1999, 2, 28)); + sysTime.roll!"months"(12); + assert(sysTime == SysTime(Date(1999, 2, 28))); + } + + { + auto sysTime = SysTime(Date(2000, 2, 29)); + sysTime.roll!"months"(12); + assert(sysTime == SysTime(Date(2000, 2, 29))); + } + + { + auto sysTime = SysTime(Date(1999, 7, 31)); + sysTime.roll!"months"(1); + assert(sysTime == SysTime(Date(1999, 8, 31))); + sysTime.roll!"months"(1); + assert(sysTime == SysTime(Date(1999, 10, 1))); + } + + { + auto sysTime = SysTime(Date(1998, 8, 31)); + sysTime.roll!"months"(13); + assert(sysTime == SysTime(Date(1998, 10, 1))); + sysTime.roll!"months"(-13); + assert(sysTime == SysTime(Date(1998, 9, 1))); + } + + { + auto sysTime = SysTime(Date(1997, 12, 31)); + sysTime.roll!"months"(13); + assert(sysTime == SysTime(Date(1997, 1, 31))); + sysTime.roll!"months"(-13); + assert(sysTime == SysTime(Date(1997, 12, 31))); + } + + { + auto sysTime = SysTime(Date(1997, 12, 31)); + sysTime.roll!"months"(14); + assert(sysTime == SysTime(Date(1997, 3, 3))); + sysTime.roll!"months"(-14); + assert(sysTime == SysTime(Date(1997, 1, 3))); + } + + { + auto sysTime = SysTime(Date(1998, 12, 31)); + sysTime.roll!"months"(14); + assert(sysTime == SysTime(Date(1998, 3, 3))); + sysTime.roll!"months"(-14); + assert(sysTime == SysTime(Date(1998, 1, 3))); + } + + { + auto sysTime = SysTime(Date(1999, 12, 31)); + sysTime.roll!"months"(14); + assert(sysTime == SysTime(Date(1999, 3, 3))); + sysTime.roll!"months"(-14); + assert(sysTime == SysTime(Date(1999, 1, 3))); + } + + { + auto sysTime = SysTime(DateTime(1999, 7, 6, 12, 2, 7), usecs(5007)); + sysTime.roll!"months"(3); + assert(sysTime == SysTime(DateTime(1999, 10, 6, 12, 2, 7), usecs(5007))); + sysTime.roll!"months"(-4); + assert(sysTime == SysTime(DateTime(1999, 6, 6, 12, 2, 7), usecs(5007))); + } + + { + auto sysTime = SysTime(DateTime(1998, 12, 31, 7, 7, 7), hnsecs(422202)); + sysTime.roll!"months"(14); + assert(sysTime == SysTime(DateTime(1998, 3, 3, 7, 7, 7), hnsecs(422202))); + sysTime.roll!"months"(-14); + assert(sysTime == SysTime(DateTime(1998, 1, 3, 7, 7, 7), hnsecs(422202))); + } + + { + auto sysTime = SysTime(DateTime(1999, 12, 31, 7, 7, 7), hnsecs(422202)); + sysTime.roll!"months"(14); + assert(sysTime == SysTime(DateTime(1999, 3, 3, 7, 7, 7), hnsecs(422202))); + sysTime.roll!"months"(-14); + assert(sysTime == SysTime(DateTime(1999, 1, 3, 7, 7, 7), hnsecs(422202))); + } + + // Test B.C. + { + auto sysTime = SysTime(Date(-1999, 7, 6)); + sysTime.roll!"months"(3); + assert(sysTime == SysTime(Date(-1999, 10, 6))); + sysTime.roll!"months"(-4); + assert(sysTime == SysTime(Date(-1999, 6, 6))); + } + + { + auto sysTime = SysTime(Date(-1999, 7, 6)); + sysTime.roll!"months"(6); + assert(sysTime == SysTime(Date(-1999, 1, 6))); + sysTime.roll!"months"(-6); + assert(sysTime == SysTime(Date(-1999, 7, 6))); + } + + { + auto sysTime = SysTime(Date(-1999, 7, 6)); + sysTime.roll!"months"(-27); + assert(sysTime == SysTime(Date(-1999, 4, 6))); + sysTime.roll!"months"(28); + assert(sysTime == SysTime(Date(-1999, 8, 6))); + } + + { + auto sysTime = SysTime(Date(-1999, 5, 31)); + sysTime.roll!"months"(1); + assert(sysTime == SysTime(Date(-1999, 7, 1))); + } + + { + auto sysTime = SysTime(Date(-1999, 5, 31)); + sysTime.roll!"months"(-1); + assert(sysTime == SysTime(Date(-1999, 5, 1))); + } + + { + auto sysTime = SysTime(Date(-1999, 2, 28)); + sysTime.roll!"months"(-12); + assert(sysTime == SysTime(Date(-1999, 2, 28))); + } + + { + auto sysTime = SysTime(Date(-2000, 2, 29)); + sysTime.roll!"months"(-12); + assert(sysTime == SysTime(Date(-2000, 2, 29))); + } + + { + auto sysTime = SysTime(Date(-1999, 7, 31)); + sysTime.roll!"months"(1); + assert(sysTime == SysTime(Date(-1999, 8, 31))); + sysTime.roll!"months"(1); + assert(sysTime == SysTime(Date(-1999, 10, 1))); + } + + { + auto sysTime = SysTime(Date(-1998, 8, 31)); + sysTime.roll!"months"(13); + assert(sysTime == SysTime(Date(-1998, 10, 1))); + sysTime.roll!"months"(-13); + assert(sysTime == SysTime(Date(-1998, 9, 1))); + } + + { + auto sysTime = SysTime(Date(-1997, 12, 31)); + sysTime.roll!"months"(13); + assert(sysTime == SysTime(Date(-1997, 1, 31))); + sysTime.roll!"months"(-13); + assert(sysTime == SysTime(Date(-1997, 12, 31))); + } + + { + auto sysTime = SysTime(Date(-1997, 12, 31)); + sysTime.roll!"months"(14); + assert(sysTime == SysTime(Date(-1997, 3, 3))); + sysTime.roll!"months"(-14); + assert(sysTime == SysTime(Date(-1997, 1, 3))); + } + + { + auto sysTime = SysTime(Date(-2002, 12, 31)); + sysTime.roll!"months"(14); + assert(sysTime == SysTime(Date(-2002, 3, 3))); + sysTime.roll!"months"(-14); + assert(sysTime == SysTime(Date(-2002, 1, 3))); + } + + { + auto sysTime = SysTime(Date(-2001, 12, 31)); + sysTime.roll!"months"(14); + assert(sysTime == SysTime(Date(-2001, 3, 3))); + sysTime.roll!"months"(-14); + assert(sysTime == SysTime(Date(-2001, 1, 3))); + } + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0)); + sysTime.roll!"months"(-1); + assert(sysTime == SysTime(DateTime(1, 12, 1, 0, 0, 0))); + sysTime.roll!"months"(1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0))); + } + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)); + sysTime.roll!"months"(-1); + assert(sysTime == SysTime(DateTime(1, 12, 1, 23, 59, 59), hnsecs(9_999_999))); + sysTime.roll!"months"(1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + } + + { + auto sysTime = SysTime(DateTime(0, 12, 1, 0, 0, 0)); + sysTime.roll!"months"(1); + assert(sysTime == SysTime(DateTime(0, 1, 1, 0, 0, 0))); + sysTime.roll!"months"(-1); + assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 0, 0))); + } + + { + auto sysTime = SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999)); + sysTime.roll!"months"(1); + assert(sysTime == SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + sysTime.roll!"months"(-1); + assert(sysTime == SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999))); + } + + { + auto sysTime = SysTime(DateTime(-1999, 7, 6, 12, 2, 7), hnsecs(5007)); + sysTime.roll!"months"(3); + assert(sysTime == SysTime(DateTime(-1999, 10, 6, 12, 2, 7), hnsecs(5007))); + sysTime.roll!"months"(-4); + assert(sysTime == SysTime(DateTime(-1999, 6, 6, 12, 2, 7), hnsecs(5007))); + } + + { + auto sysTime = SysTime(DateTime(-2002, 12, 31, 7, 7, 7), hnsecs(422202)); + sysTime.roll!"months"(14); + assert(sysTime == SysTime(DateTime(-2002, 3, 3, 7, 7, 7), hnsecs(422202))); + sysTime.roll!"months"(-14); + assert(sysTime == SysTime(DateTime(-2002, 1, 3, 7, 7, 7), hnsecs(422202))); + } + + { + auto sysTime = SysTime(DateTime(-2001, 12, 31, 7, 7, 7), hnsecs(422202)); + sysTime.roll!"months"(14); + assert(sysTime == SysTime(DateTime(-2001, 3, 3, 7, 7, 7), hnsecs(422202))); + sysTime.roll!"months"(-14); + assert(sysTime == SysTime(DateTime(-2001, 1, 3, 7, 7, 7), hnsecs(422202))); + } + + // Test Both + { + auto sysTime = SysTime(Date(1, 1, 1)); + sysTime.roll!"months"(-1); + assert(sysTime == SysTime(Date(1, 12, 1))); + sysTime.roll!"months"(1); + assert(sysTime == SysTime(Date(1, 1, 1))); + } + + { + auto sysTime = SysTime(Date(4, 1, 1)); + sysTime.roll!"months"(-48); + assert(sysTime == SysTime(Date(4, 1, 1))); + sysTime.roll!"months"(48); + assert(sysTime == SysTime(Date(4, 1, 1))); + } + + { + auto sysTime = SysTime(Date(4, 3, 31)); + sysTime.roll!"months"(-49); + assert(sysTime == SysTime(Date(4, 3, 2))); + sysTime.roll!"months"(49); + assert(sysTime == SysTime(Date(4, 4, 2))); + } + + { + auto sysTime = SysTime(Date(4, 3, 31)); + sysTime.roll!"months"(-85); + assert(sysTime == SysTime(Date(4, 3, 2))); + sysTime.roll!"months"(85); + assert(sysTime == SysTime(Date(4, 4, 2))); + } + + { + auto sysTime = SysTime(Date(-1, 1, 1)); + sysTime.roll!"months"(-1); + assert(sysTime == SysTime(Date(-1, 12, 1))); + sysTime.roll!"months"(1); + assert(sysTime == SysTime(Date(-1, 1, 1))); + } + + { + auto sysTime = SysTime(Date(-4, 1, 1)); + sysTime.roll!"months"(-48); + assert(sysTime == SysTime(Date(-4, 1, 1))); + sysTime.roll!"months"(48); + assert(sysTime == SysTime(Date(-4, 1, 1))); + } + + { + auto sysTime = SysTime(Date(-4, 3, 31)); + sysTime.roll!"months"(-49); + assert(sysTime == SysTime(Date(-4, 3, 2))); + sysTime.roll!"months"(49); + assert(sysTime == SysTime(Date(-4, 4, 2))); + } + + { + auto sysTime = SysTime(Date(-4, 3, 31)); + sysTime.roll!"months"(-85); + assert(sysTime == SysTime(Date(-4, 3, 2))); + sysTime.roll!"months"(85); + assert(sysTime == SysTime(Date(-4, 4, 2))); + } + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17)); + sysTime.roll!"months"(-1); + assert(sysTime == SysTime(DateTime(1, 12, 1, 0, 7, 9), hnsecs(17))); + sysTime.roll!"months"(1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17))); + } + + { + auto sysTime = SysTime(DateTime(4, 3, 31, 12, 11, 10), msecs(9)); + sysTime.roll!"months"(-85); + assert(sysTime == SysTime(DateTime(4, 3, 2, 12, 11, 10), msecs(9))); + sysTime.roll!"months"(85); + assert(sysTime == SysTime(DateTime(4, 4, 2, 12, 11, 10), msecs(9))); + } + + { + auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9)); + sysTime.roll!"months"(85); + assert(sysTime == SysTime(DateTime(-3, 5, 1, 12, 11, 10), msecs(9))); + sysTime.roll!"months"(-85); + assert(sysTime == SysTime(DateTime(-3, 4, 1, 12, 11, 10), msecs(9))); + } + + { + auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9)); + sysTime.roll!"months"(85).roll!"months"(-83); + assert(sysTime == SysTime(DateTime(-3, 6, 1, 12, 11, 10), msecs(9))); + } + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.roll!"months"(4))); + //static assert(!__traits(compiles, ist.roll!"months"(4))); + } + + // Test roll!"months"() with AllowDayOverflow.no + @safe unittest + { + // Test A.D. + { + auto sysTime = SysTime(Date(1999, 7, 6)); + sysTime.roll!"months"(3, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 10, 6))); + sysTime.roll!"months"(-4, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 6, 6))); + } + + { + auto sysTime = SysTime(Date(1999, 7, 6)); + sysTime.roll!"months"(6, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 1, 6))); + sysTime.roll!"months"(-6, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 7, 6))); + } + + { + auto sysTime = SysTime(Date(1999, 7, 6)); + sysTime.roll!"months"(27, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 10, 6))); + sysTime.roll!"months"(-28, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 6, 6))); + } + + { + auto sysTime = SysTime(Date(1999, 5, 31)); + sysTime.roll!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 6, 30))); + } + + { + auto sysTime = SysTime(Date(1999, 5, 31)); + sysTime.roll!"months"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 4, 30))); + } + + { + auto sysTime = SysTime(Date(1999, 2, 28)); + sysTime.roll!"months"(12, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 2, 28))); + } + + { + auto sysTime = SysTime(Date(2000, 2, 29)); + sysTime.roll!"months"(12, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(2000, 2, 29))); + } + + { + auto sysTime = SysTime(Date(1999, 7, 31)); + sysTime.roll!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 8, 31))); + sysTime.roll!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 9, 30))); + } + + { + auto sysTime = SysTime(Date(1998, 8, 31)); + sysTime.roll!"months"(13, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1998, 9, 30))); + sysTime.roll!"months"(-13, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1998, 8, 30))); + } + + { + auto sysTime = SysTime(Date(1997, 12, 31)); + sysTime.roll!"months"(13, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1997, 1, 31))); + sysTime.roll!"months"(-13, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1997, 12, 31))); + } + + { + auto sysTime = SysTime(Date(1997, 12, 31)); + sysTime.roll!"months"(14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1997, 2, 28))); + sysTime.roll!"months"(-14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1997, 12, 28))); + } + + { + auto sysTime = SysTime(Date(1998, 12, 31)); + sysTime.roll!"months"(14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1998, 2, 28))); + sysTime.roll!"months"(-14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1998, 12, 28))); + } + + { + auto sysTime = SysTime(Date(1999, 12, 31)); + sysTime.roll!"months"(14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 2, 28))); + sysTime.roll!"months"(-14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1999, 12, 28))); + } + + { + auto sysTime = SysTime(DateTime(1999, 7, 6, 12, 2, 7), usecs(5007)); + sysTime.roll!"months"(3, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1999, 10, 6, 12, 2, 7), usecs(5007))); + sysTime.roll!"months"(-4, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1999, 6, 6, 12, 2, 7), usecs(5007))); + } + + { + auto sysTime = SysTime(DateTime(1998, 12, 31, 7, 7, 7), hnsecs(422202)); + sysTime.roll!"months"(14, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1998, 2, 28, 7, 7, 7), hnsecs(422202))); + sysTime.roll!"months"(-14, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1998, 12, 28, 7, 7, 7), hnsecs(422202))); + } + + { + auto sysTime = SysTime(DateTime(1999, 12, 31, 7, 7, 7), hnsecs(422202)); + sysTime.roll!"months"(14, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1999, 2, 28, 7, 7, 7), hnsecs(422202))); + sysTime.roll!"months"(-14, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1999, 12, 28, 7, 7, 7), hnsecs(422202))); + } + + // Test B.C. + { + auto sysTime = SysTime(Date(-1999, 7, 6)); + sysTime.roll!"months"(3, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1999, 10, 6))); + sysTime.roll!"months"(-4, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1999, 6, 6))); + } + + { + auto sysTime = SysTime(Date(-1999, 7, 6)); + sysTime.roll!"months"(6, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1999, 1, 6))); + sysTime.roll!"months"(-6, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1999, 7, 6))); + } + + { + auto sysTime = SysTime(Date(-1999, 7, 6)); + sysTime.roll!"months"(-27, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1999, 4, 6))); + sysTime.roll!"months"(28, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1999, 8, 6))); + } + + { + auto sysTime = SysTime(Date(-1999, 5, 31)); + sysTime.roll!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1999, 6, 30))); + } + + { + auto sysTime = SysTime(Date(-1999, 5, 31)); + sysTime.roll!"months"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1999, 4, 30))); + } + + { + auto sysTime = SysTime(Date(-1999, 2, 28)); + sysTime.roll!"months"(-12, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1999, 2, 28))); + } + + { + auto sysTime = SysTime(Date(-2000, 2, 29)); + sysTime.roll!"months"(-12, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-2000, 2, 29))); + } + + { + auto sysTime = SysTime(Date(-1999, 7, 31)); + sysTime.roll!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1999, 8, 31))); + sysTime.roll!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1999, 9, 30))); + } + + { + auto sysTime = SysTime(Date(-1998, 8, 31)); + sysTime.roll!"months"(13, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1998, 9, 30))); + sysTime.roll!"months"(-13, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1998, 8, 30))); + } + + { + auto sysTime = SysTime(Date(-1997, 12, 31)); + sysTime.roll!"months"(13, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1997, 1, 31))); + sysTime.roll!"months"(-13, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1997, 12, 31))); + } + + { + auto sysTime = SysTime(Date(-1997, 12, 31)); + sysTime.roll!"months"(14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1997, 2, 28))); + sysTime.roll!"months"(-14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1997, 12, 28))); + } + + { + auto sysTime = SysTime(Date(-2002, 12, 31)); + sysTime.roll!"months"(14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-2002, 2, 28))); + sysTime.roll!"months"(-14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-2002, 12, 28))); + } + + { + auto sysTime = SysTime(Date(-2001, 12, 31)); + sysTime.roll!"months"(14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-2001, 2, 28))); + sysTime.roll!"months"(-14, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-2001, 12, 28))); + } + + { + auto sysTime = SysTime(DateTime(-1999, 7, 6, 12, 2, 7), usecs(5007)); + sysTime.roll!"months"(3, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-1999, 10, 6, 12, 2, 7), usecs(5007))); + sysTime.roll!"months"(-4, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-1999, 6, 6, 12, 2, 7), usecs(5007))); + } + + { + auto sysTime = SysTime(DateTime(-2002, 12, 31, 7, 7, 7), hnsecs(422202)); + sysTime.roll!"months"(14, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-2002, 2, 28, 7, 7, 7), hnsecs(422202))); + sysTime.roll!"months"(-14, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-2002, 12, 28, 7, 7, 7), hnsecs(422202))); + } + + { + auto sysTime = SysTime(DateTime(-2001, 12, 31, 7, 7, 7), hnsecs(422202)); + sysTime.roll!"months"(14, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-2001, 2, 28, 7, 7, 7), hnsecs(422202))); + sysTime.roll!"months"(-14, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-2001, 12, 28, 7, 7, 7), hnsecs(422202))); + } + + // Test Both + { + auto sysTime = SysTime(Date(1, 1, 1)); + sysTime.roll!"months"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1, 12, 1))); + sysTime.roll!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(1, 1, 1))); + } + + { + auto sysTime = SysTime(Date(4, 1, 1)); + sysTime.roll!"months"(-48, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(4, 1, 1))); + sysTime.roll!"months"(48, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(4, 1, 1))); + } + + { + auto sysTime = SysTime(Date(4, 3, 31)); + sysTime.roll!"months"(-49, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(4, 2, 29))); + sysTime.roll!"months"(49, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(4, 3, 29))); + } + + { + auto sysTime = SysTime(Date(4, 3, 31)); + sysTime.roll!"months"(-85, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(4, 2, 29))); + sysTime.roll!"months"(85, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(4, 3, 29))); + } + + { + auto sysTime = SysTime(Date(-1, 1, 1)); + sysTime.roll!"months"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1, 12, 1))); + sysTime.roll!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-1, 1, 1))); + } + + { + auto sysTime = SysTime(Date(-4, 1, 1)); + sysTime.roll!"months"(-48, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-4, 1, 1))); + sysTime.roll!"months"(48, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-4, 1, 1))); + } + + { + auto sysTime = SysTime(Date(-4, 3, 31)); + sysTime.roll!"months"(-49, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-4, 2, 29))); + sysTime.roll!"months"(49, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-4, 3, 29))); + } + + { + auto sysTime = SysTime(Date(-4, 3, 31)); + sysTime.roll!"months"(-85, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-4, 2, 29))); + sysTime.roll!"months"(85, AllowDayOverflow.no); + assert(sysTime == SysTime(Date(-4, 3, 29))); + } + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0)); + sysTime.roll!"months"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1, 12, 1, 0, 0, 0))); + sysTime.roll!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0))); + } + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)); + sysTime.roll!"months"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1, 12, 1, 23, 59, 59), hnsecs(9_999_999))); + sysTime.roll!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + } + + { + auto sysTime = SysTime(DateTime(0, 12, 1, 0, 0, 0)); + sysTime.roll!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(0, 1, 1, 0, 0, 0))); + sysTime.roll!"months"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 0, 0))); + } + + { + auto sysTime = SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999)); + sysTime.roll!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + sysTime.roll!"months"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999))); + } + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17)); + sysTime.roll!"months"(-1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1, 12, 1, 0, 7, 9), hnsecs(17))); + sysTime.roll!"months"(1, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 7, 9), hnsecs(17))); + } + + { + auto sysTime = SysTime(DateTime(4, 3, 31, 12, 11, 10), msecs(9)); + sysTime.roll!"months"(-85, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(4, 2, 29, 12, 11, 10), msecs(9))); + sysTime.roll!"months"(85, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(4, 3, 29, 12, 11, 10), msecs(9))); + } + + { + auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9)); + sysTime.roll!"months"(85, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-3, 4, 30, 12, 11, 10), msecs(9))); + sysTime.roll!"months"(-85, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-3, 3, 30, 12, 11, 10), msecs(9))); + } + + { + auto sysTime = SysTime(DateTime(-3, 3, 31, 12, 11, 10), msecs(9)); + sysTime.roll!"months"(85, AllowDayOverflow.no).roll!"months"(-83, AllowDayOverflow.no); + assert(sysTime == SysTime(DateTime(-3, 5, 30, 12, 11, 10), msecs(9))); + } + } + + + /++ + Adds the given number of units to this $(LREF SysTime). A negative number + will subtract. + + The difference between rolling and adding is that rolling does not + affect larger units. For instance, rolling a $(LREF SysTime) one + year's worth of days gets the exact same $(LREF SysTime). + + Accepted units are $(D "days"), $(D "minutes"), $(D "hours"), + $(D "minutes"), $(D "seconds"), $(D "msecs"), $(D "usecs"), and + $(D "hnsecs"). + + Note that when rolling msecs, usecs or hnsecs, they all add up to a + second. So, for example, rolling 1000 msecs is exactly the same as + rolling 100,000 usecs. + + Params: + units = The units to add. + value = The number of $(D_PARAM units) to add to this + $(LREF SysTime). + +/ + ref SysTime roll(string units)(long value) @safe nothrow + if (units == "days") + { + auto hnsecs = adjTime; + auto gdays = splitUnitsFromHNSecs!"days"(hnsecs) + 1; + + if (hnsecs < 0) + { + hnsecs += convert!("hours", "hnsecs")(24); + --gdays; + } + + auto date = Date(cast(int) gdays); + date.roll!"days"(value); + gdays = date.dayOfGregorianCal - 1; + + if (gdays < 0) + { + hnsecs -= convert!("hours", "hnsecs")(24); + ++gdays; + } + + immutable newDaysHNSecs = convert!("days", "hnsecs")(gdays); + adjTime = newDaysHNSecs + hnsecs; + return this; + } + + /// + @safe unittest + { + import core.time : msecs, hnsecs; + import std.datetime.date : DateTime; + + auto st1 = SysTime(DateTime(2010, 1, 1, 11, 23, 12)); + st1.roll!"days"(1); + assert(st1 == SysTime(DateTime(2010, 1, 2, 11, 23, 12))); + st1.roll!"days"(365); + assert(st1 == SysTime(DateTime(2010, 1, 26, 11, 23, 12))); + st1.roll!"days"(-32); + assert(st1 == SysTime(DateTime(2010, 1, 25, 11, 23, 12))); + + auto st2 = SysTime(DateTime(2010, 7, 4, 12, 0, 0)); + st2.roll!"hours"(1); + assert(st2 == SysTime(DateTime(2010, 7, 4, 13, 0, 0))); + + auto st3 = SysTime(DateTime(2010, 2, 12, 12, 0, 0)); + st3.roll!"hours"(-1); + assert(st3 == SysTime(DateTime(2010, 2, 12, 11, 0, 0))); + + auto st4 = SysTime(DateTime(2009, 12, 31, 0, 0, 0)); + st4.roll!"minutes"(1); + assert(st4 == SysTime(DateTime(2009, 12, 31, 0, 1, 0))); + + auto st5 = SysTime(DateTime(2010, 1, 1, 0, 0, 0)); + st5.roll!"minutes"(-1); + assert(st5 == SysTime(DateTime(2010, 1, 1, 0, 59, 0))); + + auto st6 = SysTime(DateTime(2009, 12, 31, 0, 0, 0)); + st6.roll!"seconds"(1); + assert(st6 == SysTime(DateTime(2009, 12, 31, 0, 0, 1))); + + auto st7 = SysTime(DateTime(2010, 1, 1, 0, 0, 0)); + st7.roll!"seconds"(-1); + assert(st7 == SysTime(DateTime(2010, 1, 1, 0, 0, 59))); + + auto dt = DateTime(2010, 1, 1, 0, 0, 0); + auto st8 = SysTime(dt); + st8.roll!"msecs"(1); + assert(st8 == SysTime(dt, msecs(1))); + + auto st9 = SysTime(dt); + st9.roll!"msecs"(-1); + assert(st9 == SysTime(dt, msecs(999))); + + auto st10 = SysTime(dt); + st10.roll!"hnsecs"(1); + assert(st10 == SysTime(dt, hnsecs(1))); + + auto st11 = SysTime(dt); + st11.roll!"hnsecs"(-1); + assert(st11 == SysTime(dt, hnsecs(9_999_999))); + } + + @safe unittest + { + // Test A.D. + { + auto sysTime = SysTime(Date(1999, 2, 28)); + sysTime.roll!"days"(1); + assert(sysTime == SysTime(Date(1999, 2, 1))); + sysTime.roll!"days"(-1); + assert(sysTime == SysTime(Date(1999, 2, 28))); + } + + { + auto sysTime = SysTime(Date(2000, 2, 28)); + sysTime.roll!"days"(1); + assert(sysTime == SysTime(Date(2000, 2, 29))); + sysTime.roll!"days"(1); + assert(sysTime == SysTime(Date(2000, 2, 1))); + sysTime.roll!"days"(-1); + assert(sysTime == SysTime(Date(2000, 2, 29))); + } + + { + auto sysTime = SysTime(Date(1999, 6, 30)); + sysTime.roll!"days"(1); + assert(sysTime == SysTime(Date(1999, 6, 1))); + sysTime.roll!"days"(-1); + assert(sysTime == SysTime(Date(1999, 6, 30))); + } + + { + auto sysTime = SysTime(Date(1999, 7, 31)); + sysTime.roll!"days"(1); + assert(sysTime == SysTime(Date(1999, 7, 1))); + sysTime.roll!"days"(-1); + assert(sysTime == SysTime(Date(1999, 7, 31))); + } + + { + auto sysTime = SysTime(Date(1999, 1, 1)); + sysTime.roll!"days"(-1); + assert(sysTime == SysTime(Date(1999, 1, 31))); + sysTime.roll!"days"(1); + assert(sysTime == SysTime(Date(1999, 1, 1))); + } + + { + auto sysTime = SysTime(Date(1999, 7, 6)); + sysTime.roll!"days"(9); + assert(sysTime == SysTime(Date(1999, 7, 15))); + sysTime.roll!"days"(-11); + assert(sysTime == SysTime(Date(1999, 7, 4))); + sysTime.roll!"days"(30); + assert(sysTime == SysTime(Date(1999, 7, 3))); + sysTime.roll!"days"(-3); + assert(sysTime == SysTime(Date(1999, 7, 31))); + } + + { + auto sysTime = SysTime(Date(1999, 7, 6)); + sysTime.roll!"days"(365); + assert(sysTime == SysTime(Date(1999, 7, 30))); + sysTime.roll!"days"(-365); + assert(sysTime == SysTime(Date(1999, 7, 6))); + sysTime.roll!"days"(366); + assert(sysTime == SysTime(Date(1999, 7, 31))); + sysTime.roll!"days"(730); + assert(sysTime == SysTime(Date(1999, 7, 17))); + sysTime.roll!"days"(-1096); + assert(sysTime == SysTime(Date(1999, 7, 6))); + } + + { + auto sysTime = SysTime(Date(1999, 2, 6)); + sysTime.roll!"days"(365); + assert(sysTime == SysTime(Date(1999, 2, 7))); + sysTime.roll!"days"(-365); + assert(sysTime == SysTime(Date(1999, 2, 6))); + sysTime.roll!"days"(366); + assert(sysTime == SysTime(Date(1999, 2, 8))); + sysTime.roll!"days"(730); + assert(sysTime == SysTime(Date(1999, 2, 10))); + sysTime.roll!"days"(-1096); + assert(sysTime == SysTime(Date(1999, 2, 6))); + } + + { + auto sysTime = SysTime(DateTime(1999, 2, 28, 7, 9, 2), usecs(234578)); + sysTime.roll!"days"(1); + assert(sysTime == SysTime(DateTime(1999, 2, 1, 7, 9, 2), usecs(234578))); + sysTime.roll!"days"(-1); + assert(sysTime == SysTime(DateTime(1999, 2, 28, 7, 9, 2), usecs(234578))); + } + + { + auto sysTime = SysTime(DateTime(1999, 7, 6, 7, 9, 2), usecs(234578)); + sysTime.roll!"days"(9); + assert(sysTime == SysTime(DateTime(1999, 7, 15, 7, 9, 2), usecs(234578))); + sysTime.roll!"days"(-11); + assert(sysTime == SysTime(DateTime(1999, 7, 4, 7, 9, 2), usecs(234578))); + sysTime.roll!"days"(30); + assert(sysTime == SysTime(DateTime(1999, 7, 3, 7, 9, 2), usecs(234578))); + sysTime.roll!"days"(-3); + assert(sysTime == SysTime(DateTime(1999, 7, 31, 7, 9, 2), usecs(234578))); + } + + // Test B.C. + { + auto sysTime = SysTime(Date(-1999, 2, 28)); + sysTime.roll!"days"(1); + assert(sysTime == SysTime(Date(-1999, 2, 1))); + sysTime.roll!"days"(-1); + assert(sysTime == SysTime(Date(-1999, 2, 28))); + } + + { + auto sysTime = SysTime(Date(-2000, 2, 28)); + sysTime.roll!"days"(1); + assert(sysTime == SysTime(Date(-2000, 2, 29))); + sysTime.roll!"days"(1); + assert(sysTime == SysTime(Date(-2000, 2, 1))); + sysTime.roll!"days"(-1); + assert(sysTime == SysTime(Date(-2000, 2, 29))); + } + + { + auto sysTime = SysTime(Date(-1999, 6, 30)); + sysTime.roll!"days"(1); + assert(sysTime == SysTime(Date(-1999, 6, 1))); + sysTime.roll!"days"(-1); + assert(sysTime == SysTime(Date(-1999, 6, 30))); + } + + { + auto sysTime = SysTime(Date(-1999, 7, 31)); + sysTime.roll!"days"(1); + assert(sysTime == SysTime(Date(-1999, 7, 1))); + sysTime.roll!"days"(-1); + assert(sysTime == SysTime(Date(-1999, 7, 31))); + } + + { + auto sysTime = SysTime(Date(-1999, 1, 1)); + sysTime.roll!"days"(-1); + assert(sysTime == SysTime(Date(-1999, 1, 31))); + sysTime.roll!"days"(1); + assert(sysTime == SysTime(Date(-1999, 1, 1))); + } + + { + auto sysTime = SysTime(Date(-1999, 7, 6)); + sysTime.roll!"days"(9); + assert(sysTime == SysTime(Date(-1999, 7, 15))); + sysTime.roll!"days"(-11); + assert(sysTime == SysTime(Date(-1999, 7, 4))); + sysTime.roll!"days"(30); + assert(sysTime == SysTime(Date(-1999, 7, 3))); + sysTime.roll!"days"(-3); + assert(sysTime == SysTime(Date(-1999, 7, 31))); + } + + { + auto sysTime = SysTime(Date(-1999, 7, 6)); + sysTime.roll!"days"(365); + assert(sysTime == SysTime(Date(-1999, 7, 30))); + sysTime.roll!"days"(-365); + assert(sysTime == SysTime(Date(-1999, 7, 6))); + sysTime.roll!"days"(366); + assert(sysTime == SysTime(Date(-1999, 7, 31))); + sysTime.roll!"days"(730); + assert(sysTime == SysTime(Date(-1999, 7, 17))); + sysTime.roll!"days"(-1096); + assert(sysTime == SysTime(Date(-1999, 7, 6))); + } + + { + auto sysTime = SysTime(DateTime(-1999, 2, 28, 7, 9, 2), usecs(234578)); + sysTime.roll!"days"(1); + assert(sysTime == SysTime(DateTime(-1999, 2, 1, 7, 9, 2), usecs(234578))); + sysTime.roll!"days"(-1); + assert(sysTime == SysTime(DateTime(-1999, 2, 28, 7, 9, 2), usecs(234578))); + } + + { + auto sysTime = SysTime(DateTime(-1999, 7, 6, 7, 9, 2), usecs(234578)); + sysTime.roll!"days"(9); + assert(sysTime == SysTime(DateTime(-1999, 7, 15, 7, 9, 2), usecs(234578))); + sysTime.roll!"days"(-11); + assert(sysTime == SysTime(DateTime(-1999, 7, 4, 7, 9, 2), usecs(234578))); + sysTime.roll!"days"(30); + assert(sysTime == SysTime(DateTime(-1999, 7, 3, 7, 9, 2), usecs(234578))); + sysTime.roll!"days"(-3); + } + + // Test Both + { + auto sysTime = SysTime(Date(1, 7, 6)); + sysTime.roll!"days"(-365); + assert(sysTime == SysTime(Date(1, 7, 13))); + sysTime.roll!"days"(365); + assert(sysTime == SysTime(Date(1, 7, 6))); + sysTime.roll!"days"(-731); + assert(sysTime == SysTime(Date(1, 7, 19))); + sysTime.roll!"days"(730); + assert(sysTime == SysTime(Date(1, 7, 5))); + } + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0)); + sysTime.roll!"days"(-1); + assert(sysTime == SysTime(DateTime(1, 1, 31, 0, 0, 0))); + sysTime.roll!"days"(1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0))); + } + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)); + sysTime.roll!"days"(-1); + assert(sysTime == SysTime(DateTime(1, 1, 31, 23, 59, 59), hnsecs(9_999_999))); + sysTime.roll!"days"(1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + } + + { + auto sysTime = SysTime(DateTime(0, 12, 31, 0, 0, 0)); + sysTime.roll!"days"(1); + assert(sysTime == SysTime(DateTime(0, 12, 1, 0, 0, 0))); + sysTime.roll!"days"(-1); + assert(sysTime == SysTime(DateTime(0, 12, 31, 0, 0, 0))); + } + + { + auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)); + sysTime.roll!"days"(1); + assert(sysTime == SysTime(DateTime(0, 12, 1, 23, 59, 59), hnsecs(9_999_999))); + sysTime.roll!"days"(-1); + assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999))); + } + + { + auto sysTime = SysTime(DateTime(1, 7, 6, 13, 13, 9), msecs(22)); + sysTime.roll!"days"(-365); + assert(sysTime == SysTime(DateTime(1, 7, 13, 13, 13, 9), msecs(22))); + sysTime.roll!"days"(365); + assert(sysTime == SysTime(DateTime(1, 7, 6, 13, 13, 9), msecs(22))); + sysTime.roll!"days"(-731); + assert(sysTime == SysTime(DateTime(1, 7, 19, 13, 13, 9), msecs(22))); + sysTime.roll!"days"(730); + assert(sysTime == SysTime(DateTime(1, 7, 5, 13, 13, 9), msecs(22))); + } + + { + auto sysTime = SysTime(DateTime(0, 7, 6, 13, 13, 9), msecs(22)); + sysTime.roll!"days"(-365); + assert(sysTime == SysTime(DateTime(0, 7, 13, 13, 13, 9), msecs(22))); + sysTime.roll!"days"(365); + assert(sysTime == SysTime(DateTime(0, 7, 6, 13, 13, 9), msecs(22))); + sysTime.roll!"days"(-731); + assert(sysTime == SysTime(DateTime(0, 7, 19, 13, 13, 9), msecs(22))); + sysTime.roll!"days"(730); + assert(sysTime == SysTime(DateTime(0, 7, 5, 13, 13, 9), msecs(22))); + } + + { + auto sysTime = SysTime(DateTime(0, 7, 6, 13, 13, 9), msecs(22)); + sysTime.roll!"days"(-365).roll!"days"(362).roll!"days"(-12).roll!"days"(730); + assert(sysTime == SysTime(DateTime(0, 7, 8, 13, 13, 9), msecs(22))); + } + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.roll!"days"(4))); + //static assert(!__traits(compiles, ist.roll!"days"(4))); + } + + + // Shares documentation with "days" version. + ref SysTime roll(string units)(long value) @safe nothrow + if (units == "hours" || units == "minutes" || units == "seconds") + { + try + { + auto hnsecs = adjTime; + auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; + + if (hnsecs < 0) + { + hnsecs += convert!("hours", "hnsecs")(24); + --days; + } + + immutable hour = splitUnitsFromHNSecs!"hours"(hnsecs); + immutable minute = splitUnitsFromHNSecs!"minutes"(hnsecs); + immutable second = splitUnitsFromHNSecs!"seconds"(hnsecs); + + auto dateTime = DateTime(Date(cast(int) days), TimeOfDay(cast(int) hour, + cast(int) minute, cast(int) second)); + dateTime.roll!units(value); + --days; + + hnsecs += convert!("hours", "hnsecs")(dateTime.hour); + hnsecs += convert!("minutes", "hnsecs")(dateTime.minute); + hnsecs += convert!("seconds", "hnsecs")(dateTime.second); + + if (days < 0) + { + hnsecs -= convert!("hours", "hnsecs")(24); + ++days; + } + + immutable newDaysHNSecs = convert!("days", "hnsecs")(days); + adjTime = newDaysHNSecs + hnsecs; + return this; + } + catch (Exception e) + assert(0, "Either DateTime's constructor or TimeOfDay's constructor threw."); + } + + // Test roll!"hours"(). + @safe unittest + { + static void testST(SysTime orig, int hours, in SysTime expected, size_t line = __LINE__) + { + orig.roll!"hours"(hours); + if (orig != expected) + throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line); + } + + // Test A.D. + immutable d = msecs(45); + auto beforeAD = SysTime(DateTime(1999, 7, 6, 12, 30, 33), d); + testST(beforeAD, 0, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d)); + testST(beforeAD, 1, SysTime(DateTime(1999, 7, 6, 13, 30, 33), d)); + testST(beforeAD, 2, SysTime(DateTime(1999, 7, 6, 14, 30, 33), d)); + testST(beforeAD, 3, SysTime(DateTime(1999, 7, 6, 15, 30, 33), d)); + testST(beforeAD, 4, SysTime(DateTime(1999, 7, 6, 16, 30, 33), d)); + testST(beforeAD, 5, SysTime(DateTime(1999, 7, 6, 17, 30, 33), d)); + testST(beforeAD, 6, SysTime(DateTime(1999, 7, 6, 18, 30, 33), d)); + testST(beforeAD, 7, SysTime(DateTime(1999, 7, 6, 19, 30, 33), d)); + testST(beforeAD, 8, SysTime(DateTime(1999, 7, 6, 20, 30, 33), d)); + testST(beforeAD, 9, SysTime(DateTime(1999, 7, 6, 21, 30, 33), d)); + testST(beforeAD, 10, SysTime(DateTime(1999, 7, 6, 22, 30, 33), d)); + testST(beforeAD, 11, SysTime(DateTime(1999, 7, 6, 23, 30, 33), d)); + testST(beforeAD, 12, SysTime(DateTime(1999, 7, 6, 0, 30, 33), d)); + testST(beforeAD, 13, SysTime(DateTime(1999, 7, 6, 1, 30, 33), d)); + testST(beforeAD, 14, SysTime(DateTime(1999, 7, 6, 2, 30, 33), d)); + testST(beforeAD, 15, SysTime(DateTime(1999, 7, 6, 3, 30, 33), d)); + testST(beforeAD, 16, SysTime(DateTime(1999, 7, 6, 4, 30, 33), d)); + testST(beforeAD, 17, SysTime(DateTime(1999, 7, 6, 5, 30, 33), d)); + testST(beforeAD, 18, SysTime(DateTime(1999, 7, 6, 6, 30, 33), d)); + testST(beforeAD, 19, SysTime(DateTime(1999, 7, 6, 7, 30, 33), d)); + testST(beforeAD, 20, SysTime(DateTime(1999, 7, 6, 8, 30, 33), d)); + testST(beforeAD, 21, SysTime(DateTime(1999, 7, 6, 9, 30, 33), d)); + testST(beforeAD, 22, SysTime(DateTime(1999, 7, 6, 10, 30, 33), d)); + testST(beforeAD, 23, SysTime(DateTime(1999, 7, 6, 11, 30, 33), d)); + testST(beforeAD, 24, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d)); + testST(beforeAD, 25, SysTime(DateTime(1999, 7, 6, 13, 30, 33), d)); + testST(beforeAD, 50, SysTime(DateTime(1999, 7, 6, 14, 30, 33), d)); + testST(beforeAD, 10_000, SysTime(DateTime(1999, 7, 6, 4, 30, 33), d)); + + testST(beforeAD, -1, SysTime(DateTime(1999, 7, 6, 11, 30, 33), d)); + testST(beforeAD, -2, SysTime(DateTime(1999, 7, 6, 10, 30, 33), d)); + testST(beforeAD, -3, SysTime(DateTime(1999, 7, 6, 9, 30, 33), d)); + testST(beforeAD, -4, SysTime(DateTime(1999, 7, 6, 8, 30, 33), d)); + testST(beforeAD, -5, SysTime(DateTime(1999, 7, 6, 7, 30, 33), d)); + testST(beforeAD, -6, SysTime(DateTime(1999, 7, 6, 6, 30, 33), d)); + testST(beforeAD, -7, SysTime(DateTime(1999, 7, 6, 5, 30, 33), d)); + testST(beforeAD, -8, SysTime(DateTime(1999, 7, 6, 4, 30, 33), d)); + testST(beforeAD, -9, SysTime(DateTime(1999, 7, 6, 3, 30, 33), d)); + testST(beforeAD, -10, SysTime(DateTime(1999, 7, 6, 2, 30, 33), d)); + testST(beforeAD, -11, SysTime(DateTime(1999, 7, 6, 1, 30, 33), d)); + testST(beforeAD, -12, SysTime(DateTime(1999, 7, 6, 0, 30, 33), d)); + testST(beforeAD, -13, SysTime(DateTime(1999, 7, 6, 23, 30, 33), d)); + testST(beforeAD, -14, SysTime(DateTime(1999, 7, 6, 22, 30, 33), d)); + testST(beforeAD, -15, SysTime(DateTime(1999, 7, 6, 21, 30, 33), d)); + testST(beforeAD, -16, SysTime(DateTime(1999, 7, 6, 20, 30, 33), d)); + testST(beforeAD, -17, SysTime(DateTime(1999, 7, 6, 19, 30, 33), d)); + testST(beforeAD, -18, SysTime(DateTime(1999, 7, 6, 18, 30, 33), d)); + testST(beforeAD, -19, SysTime(DateTime(1999, 7, 6, 17, 30, 33), d)); + testST(beforeAD, -20, SysTime(DateTime(1999, 7, 6, 16, 30, 33), d)); + testST(beforeAD, -21, SysTime(DateTime(1999, 7, 6, 15, 30, 33), d)); + testST(beforeAD, -22, SysTime(DateTime(1999, 7, 6, 14, 30, 33), d)); + testST(beforeAD, -23, SysTime(DateTime(1999, 7, 6, 13, 30, 33), d)); + testST(beforeAD, -24, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d)); + testST(beforeAD, -25, SysTime(DateTime(1999, 7, 6, 11, 30, 33), d)); + testST(beforeAD, -50, SysTime(DateTime(1999, 7, 6, 10, 30, 33), d)); + testST(beforeAD, -10_000, SysTime(DateTime(1999, 7, 6, 20, 30, 33), d)); + + testST(SysTime(DateTime(1999, 7, 6, 0, 30, 33), d), 1, SysTime(DateTime(1999, 7, 6, 1, 30, 33), d)); + testST(SysTime(DateTime(1999, 7, 6, 0, 30, 33), d), 0, SysTime(DateTime(1999, 7, 6, 0, 30, 33), d)); + testST(SysTime(DateTime(1999, 7, 6, 0, 30, 33), d), -1, SysTime(DateTime(1999, 7, 6, 23, 30, 33), d)); + + testST(SysTime(DateTime(1999, 7, 6, 23, 30, 33), d), 1, SysTime(DateTime(1999, 7, 6, 0, 30, 33), d)); + testST(SysTime(DateTime(1999, 7, 6, 23, 30, 33), d), 0, SysTime(DateTime(1999, 7, 6, 23, 30, 33), d)); + testST(SysTime(DateTime(1999, 7, 6, 23, 30, 33), d), -1, SysTime(DateTime(1999, 7, 6, 22, 30, 33), d)); + + testST(SysTime(DateTime(1999, 7, 31, 23, 30, 33), d), 1, SysTime(DateTime(1999, 7, 31, 0, 30, 33), d)); + testST(SysTime(DateTime(1999, 8, 1, 0, 30, 33), d), -1, SysTime(DateTime(1999, 8, 1, 23, 30, 33), d)); + + testST(SysTime(DateTime(1999, 12, 31, 23, 30, 33), d), 1, SysTime(DateTime(1999, 12, 31, 0, 30, 33), d)); + testST(SysTime(DateTime(2000, 1, 1, 0, 30, 33), d), -1, SysTime(DateTime(2000, 1, 1, 23, 30, 33), d)); + + testST(SysTime(DateTime(1999, 2, 28, 23, 30, 33), d), 25, SysTime(DateTime(1999, 2, 28, 0, 30, 33), d)); + testST(SysTime(DateTime(1999, 3, 2, 0, 30, 33), d), -25, SysTime(DateTime(1999, 3, 2, 23, 30, 33), d)); + + testST(SysTime(DateTime(2000, 2, 28, 23, 30, 33), d), 25, SysTime(DateTime(2000, 2, 28, 0, 30, 33), d)); + testST(SysTime(DateTime(2000, 3, 1, 0, 30, 33), d), -25, SysTime(DateTime(2000, 3, 1, 23, 30, 33), d)); + + // Test B.C. + auto beforeBC = SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d); + testST(beforeBC, 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d)); + testST(beforeBC, 1, SysTime(DateTime(-1999, 7, 6, 13, 30, 33), d)); + testST(beforeBC, 2, SysTime(DateTime(-1999, 7, 6, 14, 30, 33), d)); + testST(beforeBC, 3, SysTime(DateTime(-1999, 7, 6, 15, 30, 33), d)); + testST(beforeBC, 4, SysTime(DateTime(-1999, 7, 6, 16, 30, 33), d)); + testST(beforeBC, 5, SysTime(DateTime(-1999, 7, 6, 17, 30, 33), d)); + testST(beforeBC, 6, SysTime(DateTime(-1999, 7, 6, 18, 30, 33), d)); + testST(beforeBC, 7, SysTime(DateTime(-1999, 7, 6, 19, 30, 33), d)); + testST(beforeBC, 8, SysTime(DateTime(-1999, 7, 6, 20, 30, 33), d)); + testST(beforeBC, 9, SysTime(DateTime(-1999, 7, 6, 21, 30, 33), d)); + testST(beforeBC, 10, SysTime(DateTime(-1999, 7, 6, 22, 30, 33), d)); + testST(beforeBC, 11, SysTime(DateTime(-1999, 7, 6, 23, 30, 33), d)); + testST(beforeBC, 12, SysTime(DateTime(-1999, 7, 6, 0, 30, 33), d)); + testST(beforeBC, 13, SysTime(DateTime(-1999, 7, 6, 1, 30, 33), d)); + testST(beforeBC, 14, SysTime(DateTime(-1999, 7, 6, 2, 30, 33), d)); + testST(beforeBC, 15, SysTime(DateTime(-1999, 7, 6, 3, 30, 33), d)); + testST(beforeBC, 16, SysTime(DateTime(-1999, 7, 6, 4, 30, 33), d)); + testST(beforeBC, 17, SysTime(DateTime(-1999, 7, 6, 5, 30, 33), d)); + testST(beforeBC, 18, SysTime(DateTime(-1999, 7, 6, 6, 30, 33), d)); + testST(beforeBC, 19, SysTime(DateTime(-1999, 7, 6, 7, 30, 33), d)); + testST(beforeBC, 20, SysTime(DateTime(-1999, 7, 6, 8, 30, 33), d)); + testST(beforeBC, 21, SysTime(DateTime(-1999, 7, 6, 9, 30, 33), d)); + testST(beforeBC, 22, SysTime(DateTime(-1999, 7, 6, 10, 30, 33), d)); + testST(beforeBC, 23, SysTime(DateTime(-1999, 7, 6, 11, 30, 33), d)); + testST(beforeBC, 24, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d)); + testST(beforeBC, 25, SysTime(DateTime(-1999, 7, 6, 13, 30, 33), d)); + testST(beforeBC, 50, SysTime(DateTime(-1999, 7, 6, 14, 30, 33), d)); + testST(beforeBC, 10_000, SysTime(DateTime(-1999, 7, 6, 4, 30, 33), d)); + + testST(beforeBC, -1, SysTime(DateTime(-1999, 7, 6, 11, 30, 33), d)); + testST(beforeBC, -2, SysTime(DateTime(-1999, 7, 6, 10, 30, 33), d)); + testST(beforeBC, -3, SysTime(DateTime(-1999, 7, 6, 9, 30, 33), d)); + testST(beforeBC, -4, SysTime(DateTime(-1999, 7, 6, 8, 30, 33), d)); + testST(beforeBC, -5, SysTime(DateTime(-1999, 7, 6, 7, 30, 33), d)); + testST(beforeBC, -6, SysTime(DateTime(-1999, 7, 6, 6, 30, 33), d)); + testST(beforeBC, -7, SysTime(DateTime(-1999, 7, 6, 5, 30, 33), d)); + testST(beforeBC, -8, SysTime(DateTime(-1999, 7, 6, 4, 30, 33), d)); + testST(beforeBC, -9, SysTime(DateTime(-1999, 7, 6, 3, 30, 33), d)); + testST(beforeBC, -10, SysTime(DateTime(-1999, 7, 6, 2, 30, 33), d)); + testST(beforeBC, -11, SysTime(DateTime(-1999, 7, 6, 1, 30, 33), d)); + testST(beforeBC, -12, SysTime(DateTime(-1999, 7, 6, 0, 30, 33), d)); + testST(beforeBC, -13, SysTime(DateTime(-1999, 7, 6, 23, 30, 33), d)); + testST(beforeBC, -14, SysTime(DateTime(-1999, 7, 6, 22, 30, 33), d)); + testST(beforeBC, -15, SysTime(DateTime(-1999, 7, 6, 21, 30, 33), d)); + testST(beforeBC, -16, SysTime(DateTime(-1999, 7, 6, 20, 30, 33), d)); + testST(beforeBC, -17, SysTime(DateTime(-1999, 7, 6, 19, 30, 33), d)); + testST(beforeBC, -18, SysTime(DateTime(-1999, 7, 6, 18, 30, 33), d)); + testST(beforeBC, -19, SysTime(DateTime(-1999, 7, 6, 17, 30, 33), d)); + testST(beforeBC, -20, SysTime(DateTime(-1999, 7, 6, 16, 30, 33), d)); + testST(beforeBC, -21, SysTime(DateTime(-1999, 7, 6, 15, 30, 33), d)); + testST(beforeBC, -22, SysTime(DateTime(-1999, 7, 6, 14, 30, 33), d)); + testST(beforeBC, -23, SysTime(DateTime(-1999, 7, 6, 13, 30, 33), d)); + testST(beforeBC, -24, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d)); + testST(beforeBC, -25, SysTime(DateTime(-1999, 7, 6, 11, 30, 33), d)); + testST(beforeBC, -50, SysTime(DateTime(-1999, 7, 6, 10, 30, 33), d)); + testST(beforeBC, -10_000, SysTime(DateTime(-1999, 7, 6, 20, 30, 33), d)); + + testST(SysTime(DateTime(-1999, 7, 6, 0, 30, 33), d), 1, SysTime(DateTime(-1999, 7, 6, 1, 30, 33), d)); + testST(SysTime(DateTime(-1999, 7, 6, 0, 30, 33), d), 0, SysTime(DateTime(-1999, 7, 6, 0, 30, 33), d)); + testST(SysTime(DateTime(-1999, 7, 6, 0, 30, 33), d), -1, SysTime(DateTime(-1999, 7, 6, 23, 30, 33), d)); + + testST(SysTime(DateTime(-1999, 7, 6, 23, 30, 33), d), 1, SysTime(DateTime(-1999, 7, 6, 0, 30, 33), d)); + testST(SysTime(DateTime(-1999, 7, 6, 23, 30, 33), d), 0, SysTime(DateTime(-1999, 7, 6, 23, 30, 33), d)); + testST(SysTime(DateTime(-1999, 7, 6, 23, 30, 33), d), -1, SysTime(DateTime(-1999, 7, 6, 22, 30, 33), d)); + + testST(SysTime(DateTime(-1999, 7, 31, 23, 30, 33), d), 1, SysTime(DateTime(-1999, 7, 31, 0, 30, 33), d)); + testST(SysTime(DateTime(-1999, 8, 1, 0, 30, 33), d), -1, SysTime(DateTime(-1999, 8, 1, 23, 30, 33), d)); + + testST(SysTime(DateTime(-2001, 12, 31, 23, 30, 33), d), 1, SysTime(DateTime(-2001, 12, 31, 0, 30, 33), d)); + testST(SysTime(DateTime(-2000, 1, 1, 0, 30, 33), d), -1, SysTime(DateTime(-2000, 1, 1, 23, 30, 33), d)); + + testST(SysTime(DateTime(-2001, 2, 28, 23, 30, 33), d), 25, SysTime(DateTime(-2001, 2, 28, 0, 30, 33), d)); + testST(SysTime(DateTime(-2001, 3, 2, 0, 30, 33), d), -25, SysTime(DateTime(-2001, 3, 2, 23, 30, 33), d)); + + testST(SysTime(DateTime(-2000, 2, 28, 23, 30, 33), d), 25, SysTime(DateTime(-2000, 2, 28, 0, 30, 33), d)); + testST(SysTime(DateTime(-2000, 3, 1, 0, 30, 33), d), -25, SysTime(DateTime(-2000, 3, 1, 23, 30, 33), d)); + + // Test Both + testST(SysTime(DateTime(-1, 1, 1, 11, 30, 33), d), 17_546, SysTime(DateTime(-1, 1, 1, 13, 30, 33), d)); + testST(SysTime(DateTime(1, 1, 1, 13, 30, 33), d), -17_546, SysTime(DateTime(1, 1, 1, 11, 30, 33), d)); + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0)); + sysTime.roll!"hours"(-1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 0, 0))); + sysTime.roll!"hours"(1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0))); + } + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 0, 59, 59), hnsecs(9_999_999)); + sysTime.roll!"hours"(-1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + sysTime.roll!"hours"(1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 59, 59), hnsecs(9_999_999))); + } + + { + auto sysTime = SysTime(DateTime(0, 12, 31, 23, 0, 0)); + sysTime.roll!"hours"(1); + assert(sysTime == SysTime(DateTime(0, 12, 31, 0, 0, 0))); + sysTime.roll!"hours"(-1); + assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 0, 0))); + } + + { + auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)); + sysTime.roll!"hours"(1); + assert(sysTime == SysTime(DateTime(0, 12, 31, 0, 59, 59), hnsecs(9_999_999))); + sysTime.roll!"hours"(-1); + assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999))); + } + + { + auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)); + sysTime.roll!"hours"(1).roll!"hours"(-67); + assert(sysTime == SysTime(DateTime(0, 12, 31, 5, 59, 59), hnsecs(9_999_999))); + } + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.roll!"hours"(4))); + //static assert(!__traits(compiles, ist.roll!"hours"(4))); + } + + // Test roll!"minutes"(). + @safe unittest + { + static void testST(SysTime orig, int minutes, in SysTime expected, size_t line = __LINE__) + { + orig.roll!"minutes"(minutes); + if (orig != expected) + throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line); + } + + // Test A.D. + immutable d = usecs(7203); + auto beforeAD = SysTime(DateTime(1999, 7, 6, 12, 30, 33), d); + testST(beforeAD, 0, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d)); + testST(beforeAD, 1, SysTime(DateTime(1999, 7, 6, 12, 31, 33), d)); + testST(beforeAD, 2, SysTime(DateTime(1999, 7, 6, 12, 32, 33), d)); + testST(beforeAD, 3, SysTime(DateTime(1999, 7, 6, 12, 33, 33), d)); + testST(beforeAD, 4, SysTime(DateTime(1999, 7, 6, 12, 34, 33), d)); + testST(beforeAD, 5, SysTime(DateTime(1999, 7, 6, 12, 35, 33), d)); + testST(beforeAD, 10, SysTime(DateTime(1999, 7, 6, 12, 40, 33), d)); + testST(beforeAD, 15, SysTime(DateTime(1999, 7, 6, 12, 45, 33), d)); + testST(beforeAD, 29, SysTime(DateTime(1999, 7, 6, 12, 59, 33), d)); + testST(beforeAD, 30, SysTime(DateTime(1999, 7, 6, 12, 0, 33), d)); + testST(beforeAD, 45, SysTime(DateTime(1999, 7, 6, 12, 15, 33), d)); + testST(beforeAD, 60, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d)); + testST(beforeAD, 75, SysTime(DateTime(1999, 7, 6, 12, 45, 33), d)); + testST(beforeAD, 90, SysTime(DateTime(1999, 7, 6, 12, 0, 33), d)); + testST(beforeAD, 100, SysTime(DateTime(1999, 7, 6, 12, 10, 33), d)); + + testST(beforeAD, 689, SysTime(DateTime(1999, 7, 6, 12, 59, 33), d)); + testST(beforeAD, 690, SysTime(DateTime(1999, 7, 6, 12, 0, 33), d)); + testST(beforeAD, 691, SysTime(DateTime(1999, 7, 6, 12, 1, 33), d)); + testST(beforeAD, 960, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d)); + testST(beforeAD, 1439, SysTime(DateTime(1999, 7, 6, 12, 29, 33), d)); + testST(beforeAD, 1440, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d)); + testST(beforeAD, 1441, SysTime(DateTime(1999, 7, 6, 12, 31, 33), d)); + testST(beforeAD, 2880, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d)); + + testST(beforeAD, -1, SysTime(DateTime(1999, 7, 6, 12, 29, 33), d)); + testST(beforeAD, -2, SysTime(DateTime(1999, 7, 6, 12, 28, 33), d)); + testST(beforeAD, -3, SysTime(DateTime(1999, 7, 6, 12, 27, 33), d)); + testST(beforeAD, -4, SysTime(DateTime(1999, 7, 6, 12, 26, 33), d)); + testST(beforeAD, -5, SysTime(DateTime(1999, 7, 6, 12, 25, 33), d)); + testST(beforeAD, -10, SysTime(DateTime(1999, 7, 6, 12, 20, 33), d)); + testST(beforeAD, -15, SysTime(DateTime(1999, 7, 6, 12, 15, 33), d)); + testST(beforeAD, -29, SysTime(DateTime(1999, 7, 6, 12, 1, 33), d)); + testST(beforeAD, -30, SysTime(DateTime(1999, 7, 6, 12, 0, 33), d)); + testST(beforeAD, -45, SysTime(DateTime(1999, 7, 6, 12, 45, 33), d)); + testST(beforeAD, -60, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d)); + testST(beforeAD, -75, SysTime(DateTime(1999, 7, 6, 12, 15, 33), d)); + testST(beforeAD, -90, SysTime(DateTime(1999, 7, 6, 12, 0, 33), d)); + testST(beforeAD, -100, SysTime(DateTime(1999, 7, 6, 12, 50, 33), d)); + + testST(beforeAD, -749, SysTime(DateTime(1999, 7, 6, 12, 1, 33), d)); + testST(beforeAD, -750, SysTime(DateTime(1999, 7, 6, 12, 0, 33), d)); + testST(beforeAD, -751, SysTime(DateTime(1999, 7, 6, 12, 59, 33), d)); + testST(beforeAD, -960, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d)); + testST(beforeAD, -1439, SysTime(DateTime(1999, 7, 6, 12, 31, 33), d)); + testST(beforeAD, -1440, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d)); + testST(beforeAD, -1441, SysTime(DateTime(1999, 7, 6, 12, 29, 33), d)); + testST(beforeAD, -2880, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d)); + + testST(SysTime(DateTime(1999, 7, 6, 12, 0, 33), d), 1, SysTime(DateTime(1999, 7, 6, 12, 1, 33), d)); + testST(SysTime(DateTime(1999, 7, 6, 12, 0, 33), d), 0, SysTime(DateTime(1999, 7, 6, 12, 0, 33), d)); + testST(SysTime(DateTime(1999, 7, 6, 12, 0, 33), d), -1, SysTime(DateTime(1999, 7, 6, 12, 59, 33), d)); + + testST(SysTime(DateTime(1999, 7, 6, 11, 59, 33), d), 1, SysTime(DateTime(1999, 7, 6, 11, 0, 33), d)); + testST(SysTime(DateTime(1999, 7, 6, 11, 59, 33), d), 0, SysTime(DateTime(1999, 7, 6, 11, 59, 33), d)); + testST(SysTime(DateTime(1999, 7, 6, 11, 59, 33), d), -1, SysTime(DateTime(1999, 7, 6, 11, 58, 33), d)); + + testST(SysTime(DateTime(1999, 7, 6, 0, 0, 33), d), 1, SysTime(DateTime(1999, 7, 6, 0, 1, 33), d)); + testST(SysTime(DateTime(1999, 7, 6, 0, 0, 33), d), 0, SysTime(DateTime(1999, 7, 6, 0, 0, 33), d)); + testST(SysTime(DateTime(1999, 7, 6, 0, 0, 33), d), -1, SysTime(DateTime(1999, 7, 6, 0, 59, 33), d)); + + testST(SysTime(DateTime(1999, 7, 5, 23, 59, 33), d), 1, SysTime(DateTime(1999, 7, 5, 23, 0, 33), d)); + testST(SysTime(DateTime(1999, 7, 5, 23, 59, 33), d), 0, SysTime(DateTime(1999, 7, 5, 23, 59, 33), d)); + testST(SysTime(DateTime(1999, 7, 5, 23, 59, 33), d), -1, SysTime(DateTime(1999, 7, 5, 23, 58, 33), d)); + + testST(SysTime(DateTime(1998, 12, 31, 23, 59, 33), d), 1, SysTime(DateTime(1998, 12, 31, 23, 0, 33), d)); + testST(SysTime(DateTime(1998, 12, 31, 23, 59, 33), d), 0, SysTime(DateTime(1998, 12, 31, 23, 59, 33), d)); + testST(SysTime(DateTime(1998, 12, 31, 23, 59, 33), d), -1, SysTime(DateTime(1998, 12, 31, 23, 58, 33), d)); + + // Test B.C. + auto beforeBC = SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d); + testST(beforeBC, 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d)); + testST(beforeBC, 1, SysTime(DateTime(-1999, 7, 6, 12, 31, 33), d)); + testST(beforeBC, 2, SysTime(DateTime(-1999, 7, 6, 12, 32, 33), d)); + testST(beforeBC, 3, SysTime(DateTime(-1999, 7, 6, 12, 33, 33), d)); + testST(beforeBC, 4, SysTime(DateTime(-1999, 7, 6, 12, 34, 33), d)); + testST(beforeBC, 5, SysTime(DateTime(-1999, 7, 6, 12, 35, 33), d)); + testST(beforeBC, 10, SysTime(DateTime(-1999, 7, 6, 12, 40, 33), d)); + testST(beforeBC, 15, SysTime(DateTime(-1999, 7, 6, 12, 45, 33), d)); + testST(beforeBC, 29, SysTime(DateTime(-1999, 7, 6, 12, 59, 33), d)); + testST(beforeBC, 30, SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d)); + testST(beforeBC, 45, SysTime(DateTime(-1999, 7, 6, 12, 15, 33), d)); + testST(beforeBC, 60, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d)); + testST(beforeBC, 75, SysTime(DateTime(-1999, 7, 6, 12, 45, 33), d)); + testST(beforeBC, 90, SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d)); + testST(beforeBC, 100, SysTime(DateTime(-1999, 7, 6, 12, 10, 33), d)); + + testST(beforeBC, 689, SysTime(DateTime(-1999, 7, 6, 12, 59, 33), d)); + testST(beforeBC, 690, SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d)); + testST(beforeBC, 691, SysTime(DateTime(-1999, 7, 6, 12, 1, 33), d)); + testST(beforeBC, 960, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d)); + testST(beforeBC, 1439, SysTime(DateTime(-1999, 7, 6, 12, 29, 33), d)); + testST(beforeBC, 1440, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d)); + testST(beforeBC, 1441, SysTime(DateTime(-1999, 7, 6, 12, 31, 33), d)); + testST(beforeBC, 2880, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d)); + + testST(beforeBC, -1, SysTime(DateTime(-1999, 7, 6, 12, 29, 33), d)); + testST(beforeBC, -2, SysTime(DateTime(-1999, 7, 6, 12, 28, 33), d)); + testST(beforeBC, -3, SysTime(DateTime(-1999, 7, 6, 12, 27, 33), d)); + testST(beforeBC, -4, SysTime(DateTime(-1999, 7, 6, 12, 26, 33), d)); + testST(beforeBC, -5, SysTime(DateTime(-1999, 7, 6, 12, 25, 33), d)); + testST(beforeBC, -10, SysTime(DateTime(-1999, 7, 6, 12, 20, 33), d)); + testST(beforeBC, -15, SysTime(DateTime(-1999, 7, 6, 12, 15, 33), d)); + testST(beforeBC, -29, SysTime(DateTime(-1999, 7, 6, 12, 1, 33), d)); + testST(beforeBC, -30, SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d)); + testST(beforeBC, -45, SysTime(DateTime(-1999, 7, 6, 12, 45, 33), d)); + testST(beforeBC, -60, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d)); + testST(beforeBC, -75, SysTime(DateTime(-1999, 7, 6, 12, 15, 33), d)); + testST(beforeBC, -90, SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d)); + testST(beforeBC, -100, SysTime(DateTime(-1999, 7, 6, 12, 50, 33), d)); + + testST(beforeBC, -749, SysTime(DateTime(-1999, 7, 6, 12, 1, 33), d)); + testST(beforeBC, -750, SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d)); + testST(beforeBC, -751, SysTime(DateTime(-1999, 7, 6, 12, 59, 33), d)); + testST(beforeBC, -960, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d)); + testST(beforeBC, -1439, SysTime(DateTime(-1999, 7, 6, 12, 31, 33), d)); + testST(beforeBC, -1440, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d)); + testST(beforeBC, -1441, SysTime(DateTime(-1999, 7, 6, 12, 29, 33), d)); + testST(beforeBC, -2880, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d)); + + testST(SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d), 1, SysTime(DateTime(-1999, 7, 6, 12, 1, 33), d)); + testST(SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d), 0, SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d)); + testST(SysTime(DateTime(-1999, 7, 6, 12, 0, 33), d), -1, SysTime(DateTime(-1999, 7, 6, 12, 59, 33), d)); + + testST(SysTime(DateTime(-1999, 7, 6, 11, 59, 33), d), 1, SysTime(DateTime(-1999, 7, 6, 11, 0, 33), d)); + testST(SysTime(DateTime(-1999, 7, 6, 11, 59, 33), d), 0, SysTime(DateTime(-1999, 7, 6, 11, 59, 33), d)); + testST(SysTime(DateTime(-1999, 7, 6, 11, 59, 33), d), -1, SysTime(DateTime(-1999, 7, 6, 11, 58, 33), d)); + + testST(SysTime(DateTime(-1999, 7, 6, 0, 0, 33), d), 1, SysTime(DateTime(-1999, 7, 6, 0, 1, 33), d)); + testST(SysTime(DateTime(-1999, 7, 6, 0, 0, 33), d), 0, SysTime(DateTime(-1999, 7, 6, 0, 0, 33), d)); + testST(SysTime(DateTime(-1999, 7, 6, 0, 0, 33), d), -1, SysTime(DateTime(-1999, 7, 6, 0, 59, 33), d)); + + testST(SysTime(DateTime(-1999, 7, 5, 23, 59, 33), d), 1, SysTime(DateTime(-1999, 7, 5, 23, 0, 33), d)); + testST(SysTime(DateTime(-1999, 7, 5, 23, 59, 33), d), 0, SysTime(DateTime(-1999, 7, 5, 23, 59, 33), d)); + testST(SysTime(DateTime(-1999, 7, 5, 23, 59, 33), d), -1, SysTime(DateTime(-1999, 7, 5, 23, 58, 33), d)); + + testST(SysTime(DateTime(-2000, 12, 31, 23, 59, 33), d), 1, SysTime(DateTime(-2000, 12, 31, 23, 0, 33), d)); + testST(SysTime(DateTime(-2000, 12, 31, 23, 59, 33), d), 0, SysTime(DateTime(-2000, 12, 31, 23, 59, 33), d)); + testST(SysTime(DateTime(-2000, 12, 31, 23, 59, 33), d), -1, SysTime(DateTime(-2000, 12, 31, 23, 58, 33), d)); + + // Test Both + testST(SysTime(DateTime(1, 1, 1, 0, 0, 0)), -1, SysTime(DateTime(1, 1, 1, 0, 59, 0))); + testST(SysTime(DateTime(0, 12, 31, 23, 59, 0)), 1, SysTime(DateTime(0, 12, 31, 23, 0, 0))); + + testST(SysTime(DateTime(0, 1, 1, 0, 0, 0)), -1, SysTime(DateTime(0, 1, 1, 0, 59, 0))); + testST(SysTime(DateTime(-1, 12, 31, 23, 59, 0)), 1, SysTime(DateTime(-1, 12, 31, 23, 0, 0))); + + testST(SysTime(DateTime(-1, 1, 1, 11, 30, 33), d), 1_052_760, SysTime(DateTime(-1, 1, 1, 11, 30, 33), d)); + testST(SysTime(DateTime(1, 1, 1, 13, 30, 33), d), -1_052_760, SysTime(DateTime(1, 1, 1, 13, 30, 33), d)); + + testST(SysTime(DateTime(-1, 1, 1, 11, 30, 33), d), 1_052_782, SysTime(DateTime(-1, 1, 1, 11, 52, 33), d)); + testST(SysTime(DateTime(1, 1, 1, 13, 52, 33), d), -1_052_782, SysTime(DateTime(1, 1, 1, 13, 30, 33), d)); + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0)); + sysTime.roll!"minutes"(-1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 59, 0))); + sysTime.roll!"minutes"(1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0))); + } + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 59), hnsecs(9_999_999)); + sysTime.roll!"minutes"(-1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 59, 59), hnsecs(9_999_999))); + sysTime.roll!"minutes"(1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 59), hnsecs(9_999_999))); + } + + { + auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 0)); + sysTime.roll!"minutes"(1); + assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 0, 0))); + sysTime.roll!"minutes"(-1); + assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 0))); + } + + { + auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)); + sysTime.roll!"minutes"(1); + assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 0, 59), hnsecs(9_999_999))); + sysTime.roll!"minutes"(-1); + assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999))); + } + + { + auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)); + sysTime.roll!"minutes"(1).roll!"minutes"(-79); + assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 41, 59), hnsecs(9_999_999))); + } + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.roll!"minutes"(4))); + //static assert(!__traits(compiles, ist.roll!"minutes"(4))); + } + + // Test roll!"seconds"(). + @safe unittest + { + static void testST(SysTime orig, int seconds, in SysTime expected, size_t line = __LINE__) + { + orig.roll!"seconds"(seconds); + if (orig != expected) + throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line); + } + + // Test A.D. + immutable d = msecs(274); + auto beforeAD = SysTime(DateTime(1999, 7, 6, 12, 30, 33), d); + testST(beforeAD, 0, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d)); + testST(beforeAD, 1, SysTime(DateTime(1999, 7, 6, 12, 30, 34), d)); + testST(beforeAD, 2, SysTime(DateTime(1999, 7, 6, 12, 30, 35), d)); + testST(beforeAD, 3, SysTime(DateTime(1999, 7, 6, 12, 30, 36), d)); + testST(beforeAD, 4, SysTime(DateTime(1999, 7, 6, 12, 30, 37), d)); + testST(beforeAD, 5, SysTime(DateTime(1999, 7, 6, 12, 30, 38), d)); + testST(beforeAD, 10, SysTime(DateTime(1999, 7, 6, 12, 30, 43), d)); + testST(beforeAD, 15, SysTime(DateTime(1999, 7, 6, 12, 30, 48), d)); + testST(beforeAD, 26, SysTime(DateTime(1999, 7, 6, 12, 30, 59), d)); + testST(beforeAD, 27, SysTime(DateTime(1999, 7, 6, 12, 30, 0), d)); + testST(beforeAD, 30, SysTime(DateTime(1999, 7, 6, 12, 30, 3), d)); + testST(beforeAD, 59, SysTime(DateTime(1999, 7, 6, 12, 30, 32), d)); + testST(beforeAD, 60, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d)); + testST(beforeAD, 61, SysTime(DateTime(1999, 7, 6, 12, 30, 34), d)); + + testST(beforeAD, 1766, SysTime(DateTime(1999, 7, 6, 12, 30, 59), d)); + testST(beforeAD, 1767, SysTime(DateTime(1999, 7, 6, 12, 30, 0), d)); + testST(beforeAD, 1768, SysTime(DateTime(1999, 7, 6, 12, 30, 1), d)); + testST(beforeAD, 2007, SysTime(DateTime(1999, 7, 6, 12, 30, 0), d)); + testST(beforeAD, 3599, SysTime(DateTime(1999, 7, 6, 12, 30, 32), d)); + testST(beforeAD, 3600, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d)); + testST(beforeAD, 3601, SysTime(DateTime(1999, 7, 6, 12, 30, 34), d)); + testST(beforeAD, 7200, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d)); + + testST(beforeAD, -1, SysTime(DateTime(1999, 7, 6, 12, 30, 32), d)); + testST(beforeAD, -2, SysTime(DateTime(1999, 7, 6, 12, 30, 31), d)); + testST(beforeAD, -3, SysTime(DateTime(1999, 7, 6, 12, 30, 30), d)); + testST(beforeAD, -4, SysTime(DateTime(1999, 7, 6, 12, 30, 29), d)); + testST(beforeAD, -5, SysTime(DateTime(1999, 7, 6, 12, 30, 28), d)); + testST(beforeAD, -10, SysTime(DateTime(1999, 7, 6, 12, 30, 23), d)); + testST(beforeAD, -15, SysTime(DateTime(1999, 7, 6, 12, 30, 18), d)); + testST(beforeAD, -33, SysTime(DateTime(1999, 7, 6, 12, 30, 0), d)); + testST(beforeAD, -34, SysTime(DateTime(1999, 7, 6, 12, 30, 59), d)); + testST(beforeAD, -35, SysTime(DateTime(1999, 7, 6, 12, 30, 58), d)); + testST(beforeAD, -59, SysTime(DateTime(1999, 7, 6, 12, 30, 34), d)); + testST(beforeAD, -60, SysTime(DateTime(1999, 7, 6, 12, 30, 33), d)); + testST(beforeAD, -61, SysTime(DateTime(1999, 7, 6, 12, 30, 32), d)); + + testST(SysTime(DateTime(1999, 7, 6, 12, 30, 0), d), 1, SysTime(DateTime(1999, 7, 6, 12, 30, 1), d)); + testST(SysTime(DateTime(1999, 7, 6, 12, 30, 0), d), 0, SysTime(DateTime(1999, 7, 6, 12, 30, 0), d)); + testST(SysTime(DateTime(1999, 7, 6, 12, 30, 0), d), -1, SysTime(DateTime(1999, 7, 6, 12, 30, 59), d)); + + testST(SysTime(DateTime(1999, 7, 6, 12, 0, 0), d), 1, SysTime(DateTime(1999, 7, 6, 12, 0, 1), d)); + testST(SysTime(DateTime(1999, 7, 6, 12, 0, 0), d), 0, SysTime(DateTime(1999, 7, 6, 12, 0, 0), d)); + testST(SysTime(DateTime(1999, 7, 6, 12, 0, 0), d), -1, SysTime(DateTime(1999, 7, 6, 12, 0, 59), d)); + + testST(SysTime(DateTime(1999, 7, 6, 0, 0, 0), d), 1, SysTime(DateTime(1999, 7, 6, 0, 0, 1), d)); + testST(SysTime(DateTime(1999, 7, 6, 0, 0, 0), d), 0, SysTime(DateTime(1999, 7, 6, 0, 0, 0), d)); + testST(SysTime(DateTime(1999, 7, 6, 0, 0, 0), d), -1, SysTime(DateTime(1999, 7, 6, 0, 0, 59), d)); + + testST(SysTime(DateTime(1999, 7, 5, 23, 59, 59), d), 1, SysTime(DateTime(1999, 7, 5, 23, 59, 0), d)); + testST(SysTime(DateTime(1999, 7, 5, 23, 59, 59), d), 0, SysTime(DateTime(1999, 7, 5, 23, 59, 59), d)); + testST(SysTime(DateTime(1999, 7, 5, 23, 59, 59), d), -1, SysTime(DateTime(1999, 7, 5, 23, 59, 58), d)); + + testST(SysTime(DateTime(1998, 12, 31, 23, 59, 59), d), 1, SysTime(DateTime(1998, 12, 31, 23, 59, 0), d)); + testST(SysTime(DateTime(1998, 12, 31, 23, 59, 59), d), 0, SysTime(DateTime(1998, 12, 31, 23, 59, 59), d)); + testST(SysTime(DateTime(1998, 12, 31, 23, 59, 59), d), -1, SysTime(DateTime(1998, 12, 31, 23, 59, 58), d)); + + // Test B.C. + auto beforeBC = SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d); + testST(beforeBC, 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d)); + testST(beforeBC, 1, SysTime(DateTime(-1999, 7, 6, 12, 30, 34), d)); + testST(beforeBC, 2, SysTime(DateTime(-1999, 7, 6, 12, 30, 35), d)); + testST(beforeBC, 3, SysTime(DateTime(-1999, 7, 6, 12, 30, 36), d)); + testST(beforeBC, 4, SysTime(DateTime(-1999, 7, 6, 12, 30, 37), d)); + testST(beforeBC, 5, SysTime(DateTime(-1999, 7, 6, 12, 30, 38), d)); + testST(beforeBC, 10, SysTime(DateTime(-1999, 7, 6, 12, 30, 43), d)); + testST(beforeBC, 15, SysTime(DateTime(-1999, 7, 6, 12, 30, 48), d)); + testST(beforeBC, 26, SysTime(DateTime(-1999, 7, 6, 12, 30, 59), d)); + testST(beforeBC, 27, SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d)); + testST(beforeBC, 30, SysTime(DateTime(-1999, 7, 6, 12, 30, 3), d)); + testST(beforeBC, 59, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), d)); + testST(beforeBC, 60, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d)); + testST(beforeBC, 61, SysTime(DateTime(-1999, 7, 6, 12, 30, 34), d)); + + testST(beforeBC, 1766, SysTime(DateTime(-1999, 7, 6, 12, 30, 59), d)); + testST(beforeBC, 1767, SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d)); + testST(beforeBC, 1768, SysTime(DateTime(-1999, 7, 6, 12, 30, 1), d)); + testST(beforeBC, 2007, SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d)); + testST(beforeBC, 3599, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), d)); + testST(beforeBC, 3600, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d)); + testST(beforeBC, 3601, SysTime(DateTime(-1999, 7, 6, 12, 30, 34), d)); + testST(beforeBC, 7200, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d)); + + testST(beforeBC, -1, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), d)); + testST(beforeBC, -2, SysTime(DateTime(-1999, 7, 6, 12, 30, 31), d)); + testST(beforeBC, -3, SysTime(DateTime(-1999, 7, 6, 12, 30, 30), d)); + testST(beforeBC, -4, SysTime(DateTime(-1999, 7, 6, 12, 30, 29), d)); + testST(beforeBC, -5, SysTime(DateTime(-1999, 7, 6, 12, 30, 28), d)); + testST(beforeBC, -10, SysTime(DateTime(-1999, 7, 6, 12, 30, 23), d)); + testST(beforeBC, -15, SysTime(DateTime(-1999, 7, 6, 12, 30, 18), d)); + testST(beforeBC, -33, SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d)); + testST(beforeBC, -34, SysTime(DateTime(-1999, 7, 6, 12, 30, 59), d)); + testST(beforeBC, -35, SysTime(DateTime(-1999, 7, 6, 12, 30, 58), d)); + testST(beforeBC, -59, SysTime(DateTime(-1999, 7, 6, 12, 30, 34), d)); + testST(beforeBC, -60, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), d)); + testST(beforeBC, -61, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), d)); + + testST(SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d), 1, SysTime(DateTime(-1999, 7, 6, 12, 30, 1), d)); + testST(SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d), 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d)); + testST(SysTime(DateTime(-1999, 7, 6, 12, 30, 0), d), -1, SysTime(DateTime(-1999, 7, 6, 12, 30, 59), d)); + + testST(SysTime(DateTime(-1999, 7, 6, 12, 0, 0), d), 1, SysTime(DateTime(-1999, 7, 6, 12, 0, 1), d)); + testST(SysTime(DateTime(-1999, 7, 6, 12, 0, 0), d), 0, SysTime(DateTime(-1999, 7, 6, 12, 0, 0), d)); + testST(SysTime(DateTime(-1999, 7, 6, 12, 0, 0), d), -1, SysTime(DateTime(-1999, 7, 6, 12, 0, 59), d)); + + testST(SysTime(DateTime(-1999, 7, 6, 0, 0, 0), d), 1, SysTime(DateTime(-1999, 7, 6, 0, 0, 1), d)); + testST(SysTime(DateTime(-1999, 7, 6, 0, 0, 0), d), 0, SysTime(DateTime(-1999, 7, 6, 0, 0, 0), d)); + testST(SysTime(DateTime(-1999, 7, 6, 0, 0, 0), d), -1, SysTime(DateTime(-1999, 7, 6, 0, 0, 59), d)); + + testST(SysTime(DateTime(-1999, 7, 5, 23, 59, 59), d), 1, SysTime(DateTime(-1999, 7, 5, 23, 59, 0), d)); + testST(SysTime(DateTime(-1999, 7, 5, 23, 59, 59), d), 0, SysTime(DateTime(-1999, 7, 5, 23, 59, 59), d)); + testST(SysTime(DateTime(-1999, 7, 5, 23, 59, 59), d), -1, SysTime(DateTime(-1999, 7, 5, 23, 59, 58), d)); + + testST(SysTime(DateTime(-2000, 12, 31, 23, 59, 59), d), 1, SysTime(DateTime(-2000, 12, 31, 23, 59, 0), d)); + testST(SysTime(DateTime(-2000, 12, 31, 23, 59, 59), d), 0, SysTime(DateTime(-2000, 12, 31, 23, 59, 59), d)); + testST(SysTime(DateTime(-2000, 12, 31, 23, 59, 59), d), -1, SysTime(DateTime(-2000, 12, 31, 23, 59, 58), d)); + + // Test Both + testST(SysTime(DateTime(1, 1, 1, 0, 0, 0), d), -1, SysTime(DateTime(1, 1, 1, 0, 0, 59), d)); + testST(SysTime(DateTime(0, 12, 31, 23, 59, 59), d), 1, SysTime(DateTime(0, 12, 31, 23, 59, 0), d)); + + testST(SysTime(DateTime(0, 1, 1, 0, 0, 0), d), -1, SysTime(DateTime(0, 1, 1, 0, 0, 59), d)); + testST(SysTime(DateTime(-1, 12, 31, 23, 59, 59), d), 1, SysTime(DateTime(-1, 12, 31, 23, 59, 0), d)); + + testST(SysTime(DateTime(-1, 1, 1, 11, 30, 33), d), 63_165_600L, SysTime(DateTime(-1, 1, 1, 11, 30, 33), d)); + testST(SysTime(DateTime(1, 1, 1, 13, 30, 33), d), -63_165_600L, SysTime(DateTime(1, 1, 1, 13, 30, 33), d)); + + testST(SysTime(DateTime(-1, 1, 1, 11, 30, 33), d), 63_165_617L, SysTime(DateTime(-1, 1, 1, 11, 30, 50), d)); + testST(SysTime(DateTime(1, 1, 1, 13, 30, 50), d), -63_165_617L, SysTime(DateTime(1, 1, 1, 13, 30, 33), d)); + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0)); + sysTime.roll!"seconds"(-1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 59))); + sysTime.roll!"seconds"(1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0))); + } + + { + auto sysTime = SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(9_999_999)); + sysTime.roll!"seconds"(-1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 59), hnsecs(9_999_999))); + sysTime.roll!"seconds"(1); + assert(sysTime == SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(9_999_999))); + } + + { + auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59)); + sysTime.roll!"seconds"(1); + assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 0))); + sysTime.roll!"seconds"(-1); + assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 59))); + } + + { + auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)); + sysTime.roll!"seconds"(1); + assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 0), hnsecs(9_999_999))); + sysTime.roll!"seconds"(-1); + assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999))); + } + + { + auto sysTime = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)); + sysTime.roll!"seconds"(1).roll!"seconds"(-102); + assert(sysTime == SysTime(DateTime(0, 12, 31, 23, 59, 18), hnsecs(9_999_999))); + } + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.roll!"seconds"(4))); + //static assert(!__traits(compiles, ist.roll!"seconds"(4))); + } + + + // Shares documentation with "days" version. + ref SysTime roll(string units)(long value) @safe nothrow + if (units == "msecs" || units == "usecs" || units == "hnsecs") + { + auto hnsecs = adjTime; + immutable days = splitUnitsFromHNSecs!"days"(hnsecs); + immutable negative = hnsecs < 0; + + if (negative) + hnsecs += convert!("hours", "hnsecs")(24); + + immutable seconds = splitUnitsFromHNSecs!"seconds"(hnsecs); + hnsecs += convert!(units, "hnsecs")(value); + hnsecs %= convert!("seconds", "hnsecs")(1); + + if (hnsecs < 0) + hnsecs += convert!("seconds", "hnsecs")(1); + hnsecs += convert!("seconds", "hnsecs")(seconds); + + if (negative) + hnsecs -= convert!("hours", "hnsecs")(24); + + immutable newDaysHNSecs = convert!("days", "hnsecs")(days); + adjTime = newDaysHNSecs + hnsecs; + return this; + } + + + // Test roll!"msecs"(). + @safe unittest + { + static void testST(SysTime orig, int milliseconds, in SysTime expected, size_t line = __LINE__) + { + orig.roll!"msecs"(milliseconds); + if (orig != expected) + throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line); + } + + // Test A.D. + auto beforeAD = SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(274)); + testST(beforeAD, 0, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(274))); + testST(beforeAD, 1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(275))); + testST(beforeAD, 2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(276))); + testST(beforeAD, 10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(284))); + testST(beforeAD, 100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(374))); + testST(beforeAD, 725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(999))); + testST(beforeAD, 726, SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + testST(beforeAD, 1000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(274))); + testST(beforeAD, 1001, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(275))); + testST(beforeAD, 2000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(274))); + testST(beforeAD, 26_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(999))); + testST(beforeAD, 26_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + testST(beforeAD, 26_727, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(1))); + testST(beforeAD, 1_766_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(999))); + testST(beforeAD, 1_766_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + + testST(beforeAD, -1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(273))); + testST(beforeAD, -2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(272))); + testST(beforeAD, -10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(264))); + testST(beforeAD, -100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(174))); + testST(beforeAD, -274, SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + testST(beforeAD, -275, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(999))); + testST(beforeAD, -1000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(274))); + testST(beforeAD, -1001, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(273))); + testST(beforeAD, -2000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(274))); + testST(beforeAD, -33_274, SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + testST(beforeAD, -33_275, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(999))); + testST(beforeAD, -1_833_274, SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + testST(beforeAD, -1_833_275, SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(999))); + + // Test B.C. + auto beforeBC = SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(274)); + testST(beforeBC, 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(274))); + testST(beforeBC, 1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(275))); + testST(beforeBC, 2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(276))); + testST(beforeBC, 10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(284))); + testST(beforeBC, 100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(374))); + testST(beforeBC, 725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(999))); + testST(beforeBC, 726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33))); + testST(beforeBC, 1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(274))); + testST(beforeBC, 1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(275))); + testST(beforeBC, 2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(274))); + testST(beforeBC, 26_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(999))); + testST(beforeBC, 26_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33))); + testST(beforeBC, 26_727, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(1))); + testST(beforeBC, 1_766_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(999))); + testST(beforeBC, 1_766_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33))); + + testST(beforeBC, -1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(273))); + testST(beforeBC, -2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(272))); + testST(beforeBC, -10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(264))); + testST(beforeBC, -100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(174))); + testST(beforeBC, -274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33))); + testST(beforeBC, -275, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(999))); + testST(beforeBC, -1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(274))); + testST(beforeBC, -1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(273))); + testST(beforeBC, -2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(274))); + testST(beforeBC, -33_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33))); + testST(beforeBC, -33_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(999))); + testST(beforeBC, -1_833_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33))); + testST(beforeBC, -1_833_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), msecs(999))); + + // Test Both + auto beforeBoth1 = SysTime(DateTime(1, 1, 1, 0, 0, 0)); + testST(beforeBoth1, 1, SysTime(DateTime(1, 1, 1, 0, 0, 0), msecs(1))); + testST(beforeBoth1, 0, SysTime(DateTime(1, 1, 1, 0, 0, 0))); + testST(beforeBoth1, -1, SysTime(DateTime(1, 1, 1, 0, 0, 0), msecs(999))); + testST(beforeBoth1, -2, SysTime(DateTime(1, 1, 1, 0, 0, 0), msecs(998))); + testST(beforeBoth1, -1000, SysTime(DateTime(1, 1, 1, 0, 0, 0))); + testST(beforeBoth1, -2000, SysTime(DateTime(1, 1, 1, 0, 0, 0))); + testST(beforeBoth1, -2555, SysTime(DateTime(1, 1, 1, 0, 0, 0), msecs(445))); + + auto beforeBoth2 = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)); + testST(beforeBoth2, -1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_989_999))); + testST(beforeBoth2, 0, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999))); + testST(beforeBoth2, 1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9999))); + testST(beforeBoth2, 2, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(19_999))); + testST(beforeBoth2, 1000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999))); + testST(beforeBoth2, 2000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999))); + testST(beforeBoth2, 2555, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(5_549_999))); + + { + auto st = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)); + st.roll!"msecs"(1202).roll!"msecs"(-703); + assert(st == SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(4_989_999))); + } + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.addMSecs(4))); + //static assert(!__traits(compiles, ist.addMSecs(4))); + } + + // Test roll!"usecs"(). + @safe unittest + { + static void testST(SysTime orig, long microseconds, in SysTime expected, size_t line = __LINE__) + { + orig.roll!"usecs"(microseconds); + if (orig != expected) + throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line); + } + + // Test A.D. + auto beforeAD = SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274)); + testST(beforeAD, 0, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274))); + testST(beforeAD, 1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(275))); + testST(beforeAD, 2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(276))); + testST(beforeAD, 10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(284))); + testST(beforeAD, 100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(374))); + testST(beforeAD, 725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(999))); + testST(beforeAD, 726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(1000))); + testST(beforeAD, 1000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(1274))); + testST(beforeAD, 1001, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(1275))); + testST(beforeAD, 2000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(2274))); + testST(beforeAD, 26_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(26_999))); + testST(beforeAD, 26_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(27_000))); + testST(beforeAD, 26_727, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(27_001))); + testST(beforeAD, 1_766_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(766_999))); + testST(beforeAD, 1_766_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(767_000))); + testST(beforeAD, 1_000_000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274))); + testST(beforeAD, 60_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274))); + testST(beforeAD, 3_600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274))); + + testST(beforeAD, -1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(273))); + testST(beforeAD, -2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(272))); + testST(beforeAD, -10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(264))); + testST(beforeAD, -100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(174))); + testST(beforeAD, -274, SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + testST(beforeAD, -275, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(999_999))); + testST(beforeAD, -1000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(999_274))); + testST(beforeAD, -1001, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(999_273))); + testST(beforeAD, -2000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(998_274))); + testST(beforeAD, -33_274, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(967_000))); + testST(beforeAD, -33_275, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(966_999))); + testST(beforeAD, -1_833_274, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(167_000))); + testST(beforeAD, -1_833_275, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(166_999))); + testST(beforeAD, -1_000_000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274))); + testST(beforeAD, -60_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274))); + testST(beforeAD, -3_600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(274))); + + // Test B.C. + auto beforeBC = SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274)); + testST(beforeBC, 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274))); + testST(beforeBC, 1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(275))); + testST(beforeBC, 2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(276))); + testST(beforeBC, 10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(284))); + testST(beforeBC, 100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(374))); + testST(beforeBC, 725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(999))); + testST(beforeBC, 726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(1000))); + testST(beforeBC, 1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(1274))); + testST(beforeBC, 1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(1275))); + testST(beforeBC, 2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(2274))); + testST(beforeBC, 26_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(26_999))); + testST(beforeBC, 26_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(27_000))); + testST(beforeBC, 26_727, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(27_001))); + testST(beforeBC, 1_766_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(766_999))); + testST(beforeBC, 1_766_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(767_000))); + testST(beforeBC, 1_000_000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274))); + testST(beforeBC, 60_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274))); + testST(beforeBC, 3_600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274))); + + testST(beforeBC, -1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(273))); + testST(beforeBC, -2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(272))); + testST(beforeBC, -10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(264))); + testST(beforeBC, -100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(174))); + testST(beforeBC, -274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33))); + testST(beforeBC, -275, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(999_999))); + testST(beforeBC, -1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(999_274))); + testST(beforeBC, -1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(999_273))); + testST(beforeBC, -2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(998_274))); + testST(beforeBC, -33_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(967_000))); + testST(beforeBC, -33_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(966_999))); + testST(beforeBC, -1_833_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(167_000))); + testST(beforeBC, -1_833_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(166_999))); + testST(beforeBC, -1_000_000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274))); + testST(beforeBC, -60_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274))); + testST(beforeBC, -3_600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), usecs(274))); + + // Test Both + auto beforeBoth1 = SysTime(DateTime(1, 1, 1, 0, 0, 0)); + testST(beforeBoth1, 1, SysTime(DateTime(1, 1, 1, 0, 0, 0), usecs(1))); + testST(beforeBoth1, 0, SysTime(DateTime(1, 1, 1, 0, 0, 0))); + testST(beforeBoth1, -1, SysTime(DateTime(1, 1, 1, 0, 0, 0), usecs(999_999))); + testST(beforeBoth1, -2, SysTime(DateTime(1, 1, 1, 0, 0, 0), usecs(999_998))); + testST(beforeBoth1, -1000, SysTime(DateTime(1, 1, 1, 0, 0, 0), usecs(999_000))); + testST(beforeBoth1, -2000, SysTime(DateTime(1, 1, 1, 0, 0, 0), usecs(998_000))); + testST(beforeBoth1, -2555, SysTime(DateTime(1, 1, 1, 0, 0, 0), usecs(997_445))); + testST(beforeBoth1, -1_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0))); + testST(beforeBoth1, -2_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0))); + testST(beforeBoth1, -2_333_333, SysTime(DateTime(1, 1, 1, 0, 0, 0), usecs(666_667))); + + auto beforeBoth2 = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)); + testST(beforeBoth2, -1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_989))); + testST(beforeBoth2, 0, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999))); + testST(beforeBoth2, 1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9))); + testST(beforeBoth2, 2, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(19))); + testST(beforeBoth2, 1000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9999))); + testST(beforeBoth2, 2000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(19_999))); + testST(beforeBoth2, 2555, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(25_549))); + testST(beforeBoth2, 1_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999))); + testST(beforeBoth2, 2_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999))); + testST(beforeBoth2, 2_333_333, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(3_333_329))); + + { + auto st = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)); + st.roll!"usecs"(9_020_027); + assert(st == SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(200_269))); + } + + { + auto st = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)); + st.roll!"usecs"(9_020_027).roll!"usecs"(-70_034); + assert(st == SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_499_929))); + } + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.roll!"usecs"(4))); + //static assert(!__traits(compiles, ist.roll!"usecs"(4))); + } + + // Test roll!"hnsecs"(). + @safe unittest + { + static void testST(SysTime orig, long hnsecs, in SysTime expected, size_t line = __LINE__) + { + orig.roll!"hnsecs"(hnsecs); + if (orig != expected) + throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line); + } + + // Test A.D. + auto dtAD = DateTime(1999, 7, 6, 12, 30, 33); + auto beforeAD = SysTime(dtAD, hnsecs(274)); + testST(beforeAD, 0, SysTime(dtAD, hnsecs(274))); + testST(beforeAD, 1, SysTime(dtAD, hnsecs(275))); + testST(beforeAD, 2, SysTime(dtAD, hnsecs(276))); + testST(beforeAD, 10, SysTime(dtAD, hnsecs(284))); + testST(beforeAD, 100, SysTime(dtAD, hnsecs(374))); + testST(beforeAD, 725, SysTime(dtAD, hnsecs(999))); + testST(beforeAD, 726, SysTime(dtAD, hnsecs(1000))); + testST(beforeAD, 1000, SysTime(dtAD, hnsecs(1274))); + testST(beforeAD, 1001, SysTime(dtAD, hnsecs(1275))); + testST(beforeAD, 2000, SysTime(dtAD, hnsecs(2274))); + testST(beforeAD, 26_725, SysTime(dtAD, hnsecs(26_999))); + testST(beforeAD, 26_726, SysTime(dtAD, hnsecs(27_000))); + testST(beforeAD, 26_727, SysTime(dtAD, hnsecs(27_001))); + testST(beforeAD, 1_766_725, SysTime(dtAD, hnsecs(1_766_999))); + testST(beforeAD, 1_766_726, SysTime(dtAD, hnsecs(1_767_000))); + testST(beforeAD, 1_000_000, SysTime(dtAD, hnsecs(1_000_274))); + testST(beforeAD, 60_000_000L, SysTime(dtAD, hnsecs(274))); + testST(beforeAD, 3_600_000_000L, SysTime(dtAD, hnsecs(274))); + testST(beforeAD, 600_000_000L, SysTime(dtAD, hnsecs(274))); + testST(beforeAD, 36_000_000_000L, SysTime(dtAD, hnsecs(274))); + + testST(beforeAD, -1, SysTime(dtAD, hnsecs(273))); + testST(beforeAD, -2, SysTime(dtAD, hnsecs(272))); + testST(beforeAD, -10, SysTime(dtAD, hnsecs(264))); + testST(beforeAD, -100, SysTime(dtAD, hnsecs(174))); + testST(beforeAD, -274, SysTime(dtAD)); + testST(beforeAD, -275, SysTime(dtAD, hnsecs(9_999_999))); + testST(beforeAD, -1000, SysTime(dtAD, hnsecs(9_999_274))); + testST(beforeAD, -1001, SysTime(dtAD, hnsecs(9_999_273))); + testST(beforeAD, -2000, SysTime(dtAD, hnsecs(9_998_274))); + testST(beforeAD, -33_274, SysTime(dtAD, hnsecs(9_967_000))); + testST(beforeAD, -33_275, SysTime(dtAD, hnsecs(9_966_999))); + testST(beforeAD, -1_833_274, SysTime(dtAD, hnsecs(8_167_000))); + testST(beforeAD, -1_833_275, SysTime(dtAD, hnsecs(8_166_999))); + testST(beforeAD, -1_000_000, SysTime(dtAD, hnsecs(9_000_274))); + testST(beforeAD, -60_000_000L, SysTime(dtAD, hnsecs(274))); + testST(beforeAD, -3_600_000_000L, SysTime(dtAD, hnsecs(274))); + testST(beforeAD, -600_000_000L, SysTime(dtAD, hnsecs(274))); + testST(beforeAD, -36_000_000_000L, SysTime(dtAD, hnsecs(274))); + + // Test B.C. + auto dtBC = DateTime(-1999, 7, 6, 12, 30, 33); + auto beforeBC = SysTime(dtBC, hnsecs(274)); + testST(beforeBC, 0, SysTime(dtBC, hnsecs(274))); + testST(beforeBC, 1, SysTime(dtBC, hnsecs(275))); + testST(beforeBC, 2, SysTime(dtBC, hnsecs(276))); + testST(beforeBC, 10, SysTime(dtBC, hnsecs(284))); + testST(beforeBC, 100, SysTime(dtBC, hnsecs(374))); + testST(beforeBC, 725, SysTime(dtBC, hnsecs(999))); + testST(beforeBC, 726, SysTime(dtBC, hnsecs(1000))); + testST(beforeBC, 1000, SysTime(dtBC, hnsecs(1274))); + testST(beforeBC, 1001, SysTime(dtBC, hnsecs(1275))); + testST(beforeBC, 2000, SysTime(dtBC, hnsecs(2274))); + testST(beforeBC, 26_725, SysTime(dtBC, hnsecs(26_999))); + testST(beforeBC, 26_726, SysTime(dtBC, hnsecs(27_000))); + testST(beforeBC, 26_727, SysTime(dtBC, hnsecs(27_001))); + testST(beforeBC, 1_766_725, SysTime(dtBC, hnsecs(1_766_999))); + testST(beforeBC, 1_766_726, SysTime(dtBC, hnsecs(1_767_000))); + testST(beforeBC, 1_000_000, SysTime(dtBC, hnsecs(1_000_274))); + testST(beforeBC, 60_000_000L, SysTime(dtBC, hnsecs(274))); + testST(beforeBC, 3_600_000_000L, SysTime(dtBC, hnsecs(274))); + testST(beforeBC, 600_000_000L, SysTime(dtBC, hnsecs(274))); + testST(beforeBC, 36_000_000_000L, SysTime(dtBC, hnsecs(274))); + + testST(beforeBC, -1, SysTime(dtBC, hnsecs(273))); + testST(beforeBC, -2, SysTime(dtBC, hnsecs(272))); + testST(beforeBC, -10, SysTime(dtBC, hnsecs(264))); + testST(beforeBC, -100, SysTime(dtBC, hnsecs(174))); + testST(beforeBC, -274, SysTime(dtBC)); + testST(beforeBC, -275, SysTime(dtBC, hnsecs(9_999_999))); + testST(beforeBC, -1000, SysTime(dtBC, hnsecs(9_999_274))); + testST(beforeBC, -1001, SysTime(dtBC, hnsecs(9_999_273))); + testST(beforeBC, -2000, SysTime(dtBC, hnsecs(9_998_274))); + testST(beforeBC, -33_274, SysTime(dtBC, hnsecs(9_967_000))); + testST(beforeBC, -33_275, SysTime(dtBC, hnsecs(9_966_999))); + testST(beforeBC, -1_833_274, SysTime(dtBC, hnsecs(8_167_000))); + testST(beforeBC, -1_833_275, SysTime(dtBC, hnsecs(8_166_999))); + testST(beforeBC, -1_000_000, SysTime(dtBC, hnsecs(9_000_274))); + testST(beforeBC, -60_000_000L, SysTime(dtBC, hnsecs(274))); + testST(beforeBC, -3_600_000_000L, SysTime(dtBC, hnsecs(274))); + testST(beforeBC, -600_000_000L, SysTime(dtBC, hnsecs(274))); + testST(beforeBC, -36_000_000_000L, SysTime(dtBC, hnsecs(274))); + + // Test Both + auto dtBoth1 = DateTime(1, 1, 1, 0, 0, 0); + auto beforeBoth1 = SysTime(dtBoth1); + testST(beforeBoth1, 1, SysTime(dtBoth1, hnsecs(1))); + testST(beforeBoth1, 0, SysTime(dtBoth1)); + testST(beforeBoth1, -1, SysTime(dtBoth1, hnsecs(9_999_999))); + testST(beforeBoth1, -2, SysTime(dtBoth1, hnsecs(9_999_998))); + testST(beforeBoth1, -1000, SysTime(dtBoth1, hnsecs(9_999_000))); + testST(beforeBoth1, -2000, SysTime(dtBoth1, hnsecs(9_998_000))); + testST(beforeBoth1, -2555, SysTime(dtBoth1, hnsecs(9_997_445))); + testST(beforeBoth1, -1_000_000, SysTime(dtBoth1, hnsecs(9_000_000))); + testST(beforeBoth1, -2_000_000, SysTime(dtBoth1, hnsecs(8_000_000))); + testST(beforeBoth1, -2_333_333, SysTime(dtBoth1, hnsecs(7_666_667))); + testST(beforeBoth1, -10_000_000, SysTime(dtBoth1)); + testST(beforeBoth1, -20_000_000, SysTime(dtBoth1)); + testST(beforeBoth1, -20_888_888, SysTime(dtBoth1, hnsecs(9_111_112))); + + auto dtBoth2 = DateTime(0, 12, 31, 23, 59, 59); + auto beforeBoth2 = SysTime(dtBoth2, hnsecs(9_999_999)); + testST(beforeBoth2, -1, SysTime(dtBoth2, hnsecs(9_999_998))); + testST(beforeBoth2, 0, SysTime(dtBoth2, hnsecs(9_999_999))); + testST(beforeBoth2, 1, SysTime(dtBoth2)); + testST(beforeBoth2, 2, SysTime(dtBoth2, hnsecs(1))); + testST(beforeBoth2, 1000, SysTime(dtBoth2, hnsecs(999))); + testST(beforeBoth2, 2000, SysTime(dtBoth2, hnsecs(1999))); + testST(beforeBoth2, 2555, SysTime(dtBoth2, hnsecs(2554))); + testST(beforeBoth2, 1_000_000, SysTime(dtBoth2, hnsecs(999_999))); + testST(beforeBoth2, 2_000_000, SysTime(dtBoth2, hnsecs(1_999_999))); + testST(beforeBoth2, 2_333_333, SysTime(dtBoth2, hnsecs(2_333_332))); + testST(beforeBoth2, 10_000_000, SysTime(dtBoth2, hnsecs(9_999_999))); + testST(beforeBoth2, 20_000_000, SysTime(dtBoth2, hnsecs(9_999_999))); + testST(beforeBoth2, 20_888_888, SysTime(dtBoth2, hnsecs(888_887))); + + { + auto st = SysTime(dtBoth2, hnsecs(9_999_999)); + st.roll!"hnsecs"(70_777_222).roll!"hnsecs"(-222_555_292); + assert(st == SysTime(dtBoth2, hnsecs(8_221_929))); + } + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.roll!"hnsecs"(4))); + //static assert(!__traits(compiles, ist.roll!"hnsecs"(4))); + } + + + /++ + Gives the result of adding or subtracting a $(REF Duration, core,time) + from this $(LREF SysTime). + + The legal types of arithmetic for $(LREF SysTime) using this operator + are + + $(BOOKTABLE, + $(TR $(TD SysTime) $(TD +) $(TD Duration) $(TD -->) $(TD SysTime)) + $(TR $(TD SysTime) $(TD -) $(TD Duration) $(TD -->) $(TD SysTime)) + ) + + Params: + duration = The $(REF Duration, core,time) to add to or subtract from + this $(LREF SysTime). + +/ + SysTime opBinary(string op)(Duration duration) @safe const pure nothrow + if (op == "+" || op == "-") + { + SysTime retval = SysTime(this._stdTime, this._timezone); + immutable hnsecs = duration.total!"hnsecs"; + mixin("retval._stdTime " ~ op ~ "= hnsecs;"); + return retval; + } + + /// + @safe unittest + { + import core.time : hours, seconds; + import std.datetime.date : DateTime; + + assert(SysTime(DateTime(2015, 12, 31, 23, 59, 59)) + seconds(1) == + SysTime(DateTime(2016, 1, 1, 0, 0, 0))); + + assert(SysTime(DateTime(2015, 12, 31, 23, 59, 59)) + hours(1) == + SysTime(DateTime(2016, 1, 1, 0, 59, 59))); + + assert(SysTime(DateTime(2016, 1, 1, 0, 0, 0)) - seconds(1) == + SysTime(DateTime(2015, 12, 31, 23, 59, 59))); + + assert(SysTime(DateTime(2016, 1, 1, 0, 59, 59)) - hours(1) == + SysTime(DateTime(2015, 12, 31, 23, 59, 59))); + } + + @safe unittest + { + auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_678)); + + assert(st + dur!"weeks"(7) == SysTime(DateTime(1999, 8, 24, 12, 30, 33), hnsecs(2_345_678))); + assert(st + dur!"weeks"(-7) == SysTime(DateTime(1999, 5, 18, 12, 30, 33), hnsecs(2_345_678))); + assert(st + dur!"days"(7) == SysTime(DateTime(1999, 7, 13, 12, 30, 33), hnsecs(2_345_678))); + assert(st + dur!"days"(-7) == SysTime(DateTime(1999, 6, 29, 12, 30, 33), hnsecs(2_345_678))); + assert(st + dur!"hours"(7) == SysTime(DateTime(1999, 7, 6, 19, 30, 33), hnsecs(2_345_678))); + assert(st + dur!"hours"(-7) == SysTime(DateTime(1999, 7, 6, 5, 30, 33), hnsecs(2_345_678))); + assert(st + dur!"minutes"(7) == SysTime(DateTime(1999, 7, 6, 12, 37, 33), hnsecs(2_345_678))); + assert(st + dur!"minutes"(-7) == SysTime(DateTime(1999, 7, 6, 12, 23, 33), hnsecs(2_345_678))); + assert(st + dur!"seconds"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 40), hnsecs(2_345_678))); + assert(st + dur!"seconds"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 26), hnsecs(2_345_678))); + assert(st + dur!"msecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_415_678))); + assert(st + dur!"msecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_275_678))); + assert(st + dur!"usecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_748))); + assert(st + dur!"usecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_608))); + assert(st + dur!"hnsecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_685))); + assert(st + dur!"hnsecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_671))); + + assert(st - dur!"weeks"(-7) == SysTime(DateTime(1999, 8, 24, 12, 30, 33), hnsecs(2_345_678))); + assert(st - dur!"weeks"(7) == SysTime(DateTime(1999, 5, 18, 12, 30, 33), hnsecs(2_345_678))); + assert(st - dur!"days"(-7) == SysTime(DateTime(1999, 7, 13, 12, 30, 33), hnsecs(2_345_678))); + assert(st - dur!"days"(7) == SysTime(DateTime(1999, 6, 29, 12, 30, 33), hnsecs(2_345_678))); + assert(st - dur!"hours"(-7) == SysTime(DateTime(1999, 7, 6, 19, 30, 33), hnsecs(2_345_678))); + assert(st - dur!"hours"(7) == SysTime(DateTime(1999, 7, 6, 5, 30, 33), hnsecs(2_345_678))); + assert(st - dur!"minutes"(-7) == SysTime(DateTime(1999, 7, 6, 12, 37, 33), hnsecs(2_345_678))); + assert(st - dur!"minutes"(7) == SysTime(DateTime(1999, 7, 6, 12, 23, 33), hnsecs(2_345_678))); + assert(st - dur!"seconds"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 40), hnsecs(2_345_678))); + assert(st - dur!"seconds"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 26), hnsecs(2_345_678))); + assert(st - dur!"msecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_415_678))); + assert(st - dur!"msecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_275_678))); + assert(st - dur!"usecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_748))); + assert(st - dur!"usecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_608))); + assert(st - dur!"hnsecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_685))); + assert(st - dur!"hnsecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_671))); + + static void testST(in SysTime orig, long hnsecs, in SysTime expected, size_t line = __LINE__) + { + auto result = orig + dur!"hnsecs"(hnsecs); + if (result != expected) + throw new AssertError(format("Failed. actual [%s] != expected [%s]", result, expected), __FILE__, line); + } + + // Test A.D. + auto beforeAD = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(274)); + testST(beforeAD, 0, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(274))); + testST(beforeAD, 1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(275))); + testST(beforeAD, 2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(276))); + testST(beforeAD, 10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(284))); + testST(beforeAD, 100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(374))); + testST(beforeAD, 725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(999))); + testST(beforeAD, 726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1000))); + testST(beforeAD, 1000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1274))); + testST(beforeAD, 1001, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1275))); + testST(beforeAD, 2000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2274))); + testST(beforeAD, 26_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(26_999))); + testST(beforeAD, 26_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(27_000))); + testST(beforeAD, 26_727, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(27_001))); + testST(beforeAD, 1_766_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1_766_999))); + testST(beforeAD, 1_766_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1_767_000))); + testST(beforeAD, 1_000_000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1_000_274))); + testST(beforeAD, 60_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 39), hnsecs(274))); + testST(beforeAD, 3_600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 36, 33), hnsecs(274))); + testST(beforeAD, 600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 31, 33), hnsecs(274))); + testST(beforeAD, 36_000_000_000L, SysTime(DateTime(1999, 7, 6, 13, 30, 33), hnsecs(274))); + + testST(beforeAD, -1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(273))); + testST(beforeAD, -2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(272))); + testST(beforeAD, -10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(264))); + testST(beforeAD, -100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(174))); + testST(beforeAD, -274, SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + testST(beforeAD, -275, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_999))); + testST(beforeAD, -1000, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_274))); + testST(beforeAD, -1001, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_273))); + testST(beforeAD, -2000, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_998_274))); + testST(beforeAD, -33_274, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_967_000))); + testST(beforeAD, -33_275, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_966_999))); + testST(beforeAD, -1_833_274, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(8_167_000))); + testST(beforeAD, -1_833_275, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(8_166_999))); + testST(beforeAD, -1_000_000, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_000_274))); + testST(beforeAD, -60_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 27), hnsecs(274))); + testST(beforeAD, -3_600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 24, 33), hnsecs(274))); + testST(beforeAD, -600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 29, 33), hnsecs(274))); + testST(beforeAD, -36_000_000_000L, SysTime(DateTime(1999, 7, 6, 11, 30, 33), hnsecs(274))); + + // Test B.C. + auto beforeBC = SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(274)); + testST(beforeBC, 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(274))); + testST(beforeBC, 1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(275))); + testST(beforeBC, 2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(276))); + testST(beforeBC, 10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(284))); + testST(beforeBC, 100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(374))); + testST(beforeBC, 725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(999))); + testST(beforeBC, 726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1000))); + testST(beforeBC, 1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1274))); + testST(beforeBC, 1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1275))); + testST(beforeBC, 2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(2274))); + testST(beforeBC, 26_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(26_999))); + testST(beforeBC, 26_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(27_000))); + testST(beforeBC, 26_727, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(27_001))); + testST(beforeBC, 1_766_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1_766_999))); + testST(beforeBC, 1_766_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1_767_000))); + testST(beforeBC, 1_000_000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1_000_274))); + testST(beforeBC, 60_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 39), hnsecs(274))); + testST(beforeBC, 3_600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 36, 33), hnsecs(274))); + testST(beforeBC, 600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 31, 33), hnsecs(274))); + testST(beforeBC, 36_000_000_000L, SysTime(DateTime(-1999, 7, 6, 13, 30, 33), hnsecs(274))); + + testST(beforeBC, -1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(273))); + testST(beforeBC, -2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(272))); + testST(beforeBC, -10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(264))); + testST(beforeBC, -100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(174))); + testST(beforeBC, -274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33))); + testST(beforeBC, -275, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_999_999))); + testST(beforeBC, -1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_999_274))); + testST(beforeBC, -1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_999_273))); + testST(beforeBC, -2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_998_274))); + testST(beforeBC, -33_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_967_000))); + testST(beforeBC, -33_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_966_999))); + testST(beforeBC, -1_833_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(8_167_000))); + testST(beforeBC, -1_833_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(8_166_999))); + testST(beforeBC, -1_000_000, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_000_274))); + testST(beforeBC, -60_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 27), hnsecs(274))); + testST(beforeBC, -3_600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 24, 33), hnsecs(274))); + testST(beforeBC, -600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 29, 33), hnsecs(274))); + testST(beforeBC, -36_000_000_000L, SysTime(DateTime(-1999, 7, 6, 11, 30, 33), hnsecs(274))); + + // Test Both + auto beforeBoth1 = SysTime(DateTime(1, 1, 1, 0, 0, 0)); + testST(beforeBoth1, 1, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1))); + testST(beforeBoth1, 0, SysTime(DateTime(1, 1, 1, 0, 0, 0))); + testST(beforeBoth1, -1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999))); + testST(beforeBoth1, -2, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_998))); + testST(beforeBoth1, -1000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_000))); + testST(beforeBoth1, -2000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_998_000))); + testST(beforeBoth1, -2555, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_997_445))); + testST(beforeBoth1, -1_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_000_000))); + testST(beforeBoth1, -2_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(8_000_000))); + testST(beforeBoth1, -2_333_333, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(7_666_667))); + testST(beforeBoth1, -10_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59))); + testST(beforeBoth1, -20_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 58))); + testST(beforeBoth1, -20_888_888, SysTime(DateTime(0, 12, 31, 23, 59, 57), hnsecs(9_111_112))); + + auto beforeBoth2 = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)); + testST(beforeBoth2, -1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_998))); + testST(beforeBoth2, 0, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999))); + testST(beforeBoth2, 1, SysTime(DateTime(1, 1, 1, 0, 0, 0))); + testST(beforeBoth2, 2, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1))); + testST(beforeBoth2, 1000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(999))); + testST(beforeBoth2, 2000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1999))); + testST(beforeBoth2, 2555, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(2554))); + testST(beforeBoth2, 1_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(999_999))); + testST(beforeBoth2, 2_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1_999_999))); + testST(beforeBoth2, 2_333_333, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(2_333_332))); + testST(beforeBoth2, 10_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(9_999_999))); + testST(beforeBoth2, 20_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 1), hnsecs(9_999_999))); + testST(beforeBoth2, 20_888_888, SysTime(DateTime(1, 1, 1, 0, 0, 2), hnsecs(888_887))); + + auto duration = dur!"seconds"(12); + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cst + duration == SysTime(DateTime(1999, 7, 6, 12, 30, 45))); + //assert(ist + duration == SysTime(DateTime(1999, 7, 6, 12, 30, 45))); + assert(cst - duration == SysTime(DateTime(1999, 7, 6, 12, 30, 21))); + //assert(ist - duration == SysTime(DateTime(1999, 7, 6, 12, 30, 21))); + } + + // Explicitly undocumented. It will be removed in January 2018. @@@DEPRECATED_2018-01@@@ + deprecated("Use Duration instead of TickDuration.") + SysTime opBinary(string op)(TickDuration td) @safe const pure nothrow + if (op == "+" || op == "-") + { + SysTime retval = SysTime(this._stdTime, this._timezone); + immutable hnsecs = td.hnsecs; + mixin("retval._stdTime " ~ op ~ "= hnsecs;"); + return retval; + } + + deprecated @safe unittest + { + // This probably only runs in cases where gettimeofday() is used, but it's + // hard to do this test correctly with variable ticksPerSec. + if (TickDuration.ticksPerSec == 1_000_000) + { + auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_678)); + + assert(st + TickDuration.from!"usecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_748))); + assert(st + TickDuration.from!"usecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_608))); + + assert(st - TickDuration.from!"usecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_748))); + assert(st - TickDuration.from!"usecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_608))); + } + } + + + /++ + Gives the result of adding or subtracting a $(REF Duration, core,time) from + this $(LREF SysTime), as well as assigning the result to this + $(LREF SysTime). + + The legal types of arithmetic for $(LREF SysTime) using this operator are + + $(BOOKTABLE, + $(TR $(TD SysTime) $(TD +) $(TD Duration) $(TD -->) $(TD SysTime)) + $(TR $(TD SysTime) $(TD -) $(TD Duration) $(TD -->) $(TD SysTime)) + ) + + Params: + duration = The $(REF Duration, core,time) to add to or subtract from + this $(LREF SysTime). + +/ + ref SysTime opOpAssign(string op)(Duration duration) @safe pure nothrow + if (op == "+" || op == "-") + { + immutable hnsecs = duration.total!"hnsecs"; + mixin("_stdTime " ~ op ~ "= hnsecs;"); + return this; + } + + @safe unittest + { + auto before = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(before + dur!"weeks"(7) == SysTime(DateTime(1999, 8, 24, 12, 30, 33))); + assert(before + dur!"weeks"(-7) == SysTime(DateTime(1999, 5, 18, 12, 30, 33))); + assert(before + dur!"days"(7) == SysTime(DateTime(1999, 7, 13, 12, 30, 33))); + assert(before + dur!"days"(-7) == SysTime(DateTime(1999, 6, 29, 12, 30, 33))); + + assert(before + dur!"hours"(7) == SysTime(DateTime(1999, 7, 6, 19, 30, 33))); + assert(before + dur!"hours"(-7) == SysTime(DateTime(1999, 7, 6, 5, 30, 33))); + assert(before + dur!"minutes"(7) == SysTime(DateTime(1999, 7, 6, 12, 37, 33))); + assert(before + dur!"minutes"(-7) == SysTime(DateTime(1999, 7, 6, 12, 23, 33))); + assert(before + dur!"seconds"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 40))); + assert(before + dur!"seconds"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 26))); + assert(before + dur!"msecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(7))); + assert(before + dur!"msecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 32), msecs(993))); + assert(before + dur!"usecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(7))); + assert(before + dur!"usecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 32), usecs(999_993))); + assert(before + dur!"hnsecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(7))); + assert(before + dur!"hnsecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_993))); + + assert(before - dur!"weeks"(-7) == SysTime(DateTime(1999, 8, 24, 12, 30, 33))); + assert(before - dur!"weeks"(7) == SysTime(DateTime(1999, 5, 18, 12, 30, 33))); + assert(before - dur!"days"(-7) == SysTime(DateTime(1999, 7, 13, 12, 30, 33))); + assert(before - dur!"days"(7) == SysTime(DateTime(1999, 6, 29, 12, 30, 33))); + + assert(before - dur!"hours"(-7) == SysTime(DateTime(1999, 7, 6, 19, 30, 33))); + assert(before - dur!"hours"(7) == SysTime(DateTime(1999, 7, 6, 5, 30, 33))); + assert(before - dur!"minutes"(-7) == SysTime(DateTime(1999, 7, 6, 12, 37, 33))); + assert(before - dur!"minutes"(7) == SysTime(DateTime(1999, 7, 6, 12, 23, 33))); + assert(before - dur!"seconds"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 40))); + assert(before - dur!"seconds"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 26))); + assert(before - dur!"msecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), msecs(7))); + assert(before - dur!"msecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 32), msecs(993))); + assert(before - dur!"usecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), usecs(7))); + assert(before - dur!"usecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 32), usecs(999_993))); + assert(before - dur!"hnsecs"(-7) == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(7))); + assert(before - dur!"hnsecs"(7) == SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_993))); + + static void testST(SysTime orig, long hnsecs, in SysTime expected, size_t line = __LINE__) + { + auto r = orig += dur!"hnsecs"(hnsecs); + if (orig != expected) + throw new AssertError(format("Failed 1. actual [%s] != expected [%s]", orig, expected), __FILE__, line); + if (r != expected) + throw new AssertError(format("Failed 2. actual [%s] != expected [%s]", r, expected), __FILE__, line); + } + + // Test A.D. + auto beforeAD = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(274)); + testST(beforeAD, 0, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(274))); + testST(beforeAD, 1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(275))); + testST(beforeAD, 2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(276))); + testST(beforeAD, 10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(284))); + testST(beforeAD, 100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(374))); + testST(beforeAD, 725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(999))); + testST(beforeAD, 726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1000))); + testST(beforeAD, 1000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1274))); + testST(beforeAD, 1001, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1275))); + testST(beforeAD, 2000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2274))); + testST(beforeAD, 26_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(26_999))); + testST(beforeAD, 26_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(27_000))); + testST(beforeAD, 26_727, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(27_001))); + testST(beforeAD, 1_766_725, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1_766_999))); + testST(beforeAD, 1_766_726, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1_767_000))); + testST(beforeAD, 1_000_000, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(1_000_274))); + testST(beforeAD, 60_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 39), hnsecs(274))); + testST(beforeAD, 3_600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 36, 33), hnsecs(274))); + testST(beforeAD, 600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 31, 33), hnsecs(274))); + testST(beforeAD, 36_000_000_000L, SysTime(DateTime(1999, 7, 6, 13, 30, 33), hnsecs(274))); + + testST(beforeAD, -1, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(273))); + testST(beforeAD, -2, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(272))); + testST(beforeAD, -10, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(264))); + testST(beforeAD, -100, SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(174))); + testST(beforeAD, -274, SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + testST(beforeAD, -275, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_999))); + testST(beforeAD, -1000, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_274))); + testST(beforeAD, -1001, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_999_273))); + testST(beforeAD, -2000, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_998_274))); + testST(beforeAD, -33_274, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_967_000))); + testST(beforeAD, -33_275, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_966_999))); + testST(beforeAD, -1_833_274, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(8_167_000))); + testST(beforeAD, -1_833_275, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(8_166_999))); + testST(beforeAD, -1_000_000, SysTime(DateTime(1999, 7, 6, 12, 30, 32), hnsecs(9_000_274))); + testST(beforeAD, -60_000_000L, SysTime(DateTime(1999, 7, 6, 12, 30, 27), hnsecs(274))); + testST(beforeAD, -3_600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 24, 33), hnsecs(274))); + testST(beforeAD, -600_000_000L, SysTime(DateTime(1999, 7, 6, 12, 29, 33), hnsecs(274))); + testST(beforeAD, -36_000_000_000L, SysTime(DateTime(1999, 7, 6, 11, 30, 33), hnsecs(274))); + + // Test B.C. + auto beforeBC = SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(274)); + testST(beforeBC, 0, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(274))); + testST(beforeBC, 1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(275))); + testST(beforeBC, 2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(276))); + testST(beforeBC, 10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(284))); + testST(beforeBC, 100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(374))); + testST(beforeBC, 725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(999))); + testST(beforeBC, 726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1000))); + testST(beforeBC, 1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1274))); + testST(beforeBC, 1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1275))); + testST(beforeBC, 2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(2274))); + testST(beforeBC, 26_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(26_999))); + testST(beforeBC, 26_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(27_000))); + testST(beforeBC, 26_727, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(27_001))); + testST(beforeBC, 1_766_725, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1_766_999))); + testST(beforeBC, 1_766_726, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1_767_000))); + testST(beforeBC, 1_000_000, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(1_000_274))); + testST(beforeBC, 60_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 39), hnsecs(274))); + testST(beforeBC, 3_600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 36, 33), hnsecs(274))); + testST(beforeBC, 600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 31, 33), hnsecs(274))); + testST(beforeBC, 36_000_000_000L, SysTime(DateTime(-1999, 7, 6, 13, 30, 33), hnsecs(274))); + + testST(beforeBC, -1, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(273))); + testST(beforeBC, -2, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(272))); + testST(beforeBC, -10, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(264))); + testST(beforeBC, -100, SysTime(DateTime(-1999, 7, 6, 12, 30, 33), hnsecs(174))); + testST(beforeBC, -274, SysTime(DateTime(-1999, 7, 6, 12, 30, 33))); + testST(beforeBC, -275, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_999_999))); + testST(beforeBC, -1000, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_999_274))); + testST(beforeBC, -1001, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_999_273))); + testST(beforeBC, -2000, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_998_274))); + testST(beforeBC, -33_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_967_000))); + testST(beforeBC, -33_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_966_999))); + testST(beforeBC, -1_833_274, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(8_167_000))); + testST(beforeBC, -1_833_275, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(8_166_999))); + testST(beforeBC, -1_000_000, SysTime(DateTime(-1999, 7, 6, 12, 30, 32), hnsecs(9_000_274))); + testST(beforeBC, -60_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 30, 27), hnsecs(274))); + testST(beforeBC, -3_600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 24, 33), hnsecs(274))); + testST(beforeBC, -600_000_000L, SysTime(DateTime(-1999, 7, 6, 12, 29, 33), hnsecs(274))); + testST(beforeBC, -36_000_000_000L, SysTime(DateTime(-1999, 7, 6, 11, 30, 33), hnsecs(274))); + + // Test Both + auto beforeBoth1 = SysTime(DateTime(1, 1, 1, 0, 0, 0)); + testST(beforeBoth1, 1, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1))); + testST(beforeBoth1, 0, SysTime(DateTime(1, 1, 1, 0, 0, 0))); + testST(beforeBoth1, -1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999))); + testST(beforeBoth1, -2, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_998))); + testST(beforeBoth1, -1000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_000))); + testST(beforeBoth1, -2000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_998_000))); + testST(beforeBoth1, -2555, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_997_445))); + testST(beforeBoth1, -1_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_000_000))); + testST(beforeBoth1, -2_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(8_000_000))); + testST(beforeBoth1, -2_333_333, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(7_666_667))); + testST(beforeBoth1, -10_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 59))); + testST(beforeBoth1, -20_000_000, SysTime(DateTime(0, 12, 31, 23, 59, 58))); + testST(beforeBoth1, -20_888_888, SysTime(DateTime(0, 12, 31, 23, 59, 57), hnsecs(9_111_112))); + + auto beforeBoth2 = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)); + testST(beforeBoth2, -1, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_998))); + testST(beforeBoth2, 0, SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999))); + testST(beforeBoth2, 1, SysTime(DateTime(1, 1, 1, 0, 0, 0))); + testST(beforeBoth2, 2, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1))); + testST(beforeBoth2, 1000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(999))); + testST(beforeBoth2, 2000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1999))); + testST(beforeBoth2, 2555, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(2554))); + testST(beforeBoth2, 1_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(999_999))); + testST(beforeBoth2, 2_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1_999_999))); + testST(beforeBoth2, 2_333_333, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(2_333_332))); + testST(beforeBoth2, 10_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(9_999_999))); + testST(beforeBoth2, 20_000_000, SysTime(DateTime(1, 1, 1, 0, 0, 1), hnsecs(9_999_999))); + testST(beforeBoth2, 20_888_888, SysTime(DateTime(1, 1, 1, 0, 0, 2), hnsecs(888_887))); + + { + auto st = SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)); + (st += dur!"hnsecs"(52)) += dur!"seconds"(-907); + assert(st == SysTime(DateTime(0, 12, 31, 23, 44, 53), hnsecs(51))); + } + + auto duration = dur!"seconds"(12); + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst += duration)); + //static assert(!__traits(compiles, ist += duration)); + static assert(!__traits(compiles, cst -= duration)); + //static assert(!__traits(compiles, ist -= duration)); + } + + // Explicitly undocumented. It will be removed in January 2018. @@@DEPRECATED_2018-01@@@ + deprecated("Use Duration instead of TickDuration.") + ref SysTime opOpAssign(string op)(TickDuration td) @safe pure nothrow + if (op == "+" || op == "-") + { + immutable hnsecs = td.hnsecs; + mixin("_stdTime " ~ op ~ "= hnsecs;"); + return this; + } + + deprecated @safe unittest + { + // This probably only runs in cases where gettimeofday() is used, but it's + // hard to do this test correctly with variable ticksPerSec. + if (TickDuration.ticksPerSec == 1_000_000) + { + { + auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_678)); + st += TickDuration.from!"usecs"(7); + assert(st == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_748))); + } + { + auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_678)); + st += TickDuration.from!"usecs"(-7); + assert(st == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_608))); + } + + { + auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_678)); + st -= TickDuration.from!"usecs"(-7); + assert(st == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_748))); + } + { + auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_678)); + st -= TickDuration.from!"usecs"(7); + assert(st == SysTime(DateTime(1999, 7, 6, 12, 30, 33), hnsecs(2_345_608))); + } + } + } + + + /++ + Gives the difference between two $(LREF SysTime)s. + + The legal types of arithmetic for $(LREF SysTime) using this operator + are + + $(BOOKTABLE, + $(TR $(TD SysTime) $(TD -) $(TD SysTime) $(TD -->) $(TD duration)) + ) + +/ + Duration opBinary(string op)(in SysTime rhs) @safe const pure nothrow + if (op == "-") + { + return dur!"hnsecs"(_stdTime - rhs._stdTime); + } + + @safe unittest + { + assert(SysTime(DateTime(1999, 7, 6, 12, 30, 33)) - SysTime(DateTime(1998, 7, 6, 12, 30, 33)) == + dur!"seconds"(31_536_000)); + assert(SysTime(DateTime(1998, 7, 6, 12, 30, 33)) - SysTime(DateTime(1999, 7, 6, 12, 30, 33)) == + dur!"seconds"(-31_536_000)); + + assert(SysTime(DateTime(1999, 8, 6, 12, 30, 33)) - SysTime(DateTime(1999, 7, 6, 12, 30, 33)) == + dur!"seconds"(26_78_400)); + assert(SysTime(DateTime(1999, 7, 6, 12, 30, 33)) - SysTime(DateTime(1999, 8, 6, 12, 30, 33)) == + dur!"seconds"(-26_78_400)); + + assert(SysTime(DateTime(1999, 7, 6, 12, 30, 33)) - SysTime(DateTime(1999, 7, 5, 12, 30, 33)) == + dur!"seconds"(86_400)); + assert(SysTime(DateTime(1999, 7, 5, 12, 30, 33)) - SysTime(DateTime(1999, 7, 6, 12, 30, 33)) == + dur!"seconds"(-86_400)); + + assert(SysTime(DateTime(1999, 7, 6, 12, 30, 33)) - SysTime(DateTime(1999, 7, 6, 11, 30, 33)) == + dur!"seconds"(3600)); + assert(SysTime(DateTime(1999, 7, 6, 11, 30, 33)) - SysTime(DateTime(1999, 7, 6, 12, 30, 33)) == + dur!"seconds"(-3600)); + + assert(SysTime(DateTime(1999, 7, 6, 12, 31, 33)) - SysTime(DateTime(1999, 7, 6, 12, 30, 33)) == + dur!"seconds"(60)); + assert(SysTime(DateTime(1999, 7, 6, 12, 30, 33)) - SysTime(DateTime(1999, 7, 6, 12, 31, 33)) == + dur!"seconds"(-60)); + + assert(SysTime(DateTime(1999, 7, 6, 12, 30, 34)) - SysTime(DateTime(1999, 7, 6, 12, 30, 33)) == + dur!"seconds"(1)); + assert(SysTime(DateTime(1999, 7, 6, 12, 30, 33)) - SysTime(DateTime(1999, 7, 6, 12, 30, 34)) == + dur!"seconds"(-1)); + + { + auto dt = DateTime(1999, 7, 6, 12, 30, 33); + assert(SysTime(dt, msecs(532)) - SysTime(dt) == msecs(532)); + assert(SysTime(dt) - SysTime(dt, msecs(532)) == msecs(-532)); + + assert(SysTime(dt, usecs(333_347)) - SysTime(dt) == usecs(333_347)); + assert(SysTime(dt) - SysTime(dt, usecs(333_347)) == usecs(-333_347)); + + assert(SysTime(dt, hnsecs(1_234_567)) - SysTime(dt) == hnsecs(1_234_567)); + assert(SysTime(dt) - SysTime(dt, hnsecs(1_234_567)) == hnsecs(-1_234_567)); + } + + assert(SysTime(DateTime(1, 1, 1, 12, 30, 33)) - SysTime(DateTime(1, 1, 1, 0, 0, 0)) == dur!"seconds"(45033)); + assert(SysTime(DateTime(1, 1, 1, 0, 0, 0)) - SysTime(DateTime(1, 1, 1, 12, 30, 33)) == dur!"seconds"(-45033)); + assert(SysTime(DateTime(0, 12, 31, 12, 30, 33)) - SysTime(DateTime(1, 1, 1, 0, 0, 0)) == dur!"seconds"(-41367)); + assert(SysTime(DateTime(1, 1, 1, 0, 0, 0)) - SysTime(DateTime(0, 12, 31, 12, 30, 33)) == dur!"seconds"(41367)); + + assert(SysTime(DateTime(1, 1, 1, 0, 0, 0)) - SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)) == + dur!"hnsecs"(1)); + assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)) - SysTime(DateTime(1, 1, 1, 0, 0, 0)) == + dur!"hnsecs"(-1)); + + version (Posix) + immutable tz = PosixTimeZone.getTimeZone("America/Los_Angeles"); + else version (Windows) + immutable tz = WindowsTimeZone.getTimeZone("Pacific Standard Time"); + + { + auto dt = DateTime(2011, 1, 13, 8, 17, 2); + auto d = msecs(296); + assert(SysTime(dt, d, tz) - SysTime(dt, d, tz) == Duration.zero); + assert(SysTime(dt, d, tz) - SysTime(dt, d, UTC()) == hours(8)); + assert(SysTime(dt, d, UTC()) - SysTime(dt, d, tz) == hours(-8)); + } + + auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(st - st == Duration.zero); + assert(cst - st == Duration.zero); + //assert(ist - st == Duration.zero); + + assert(st - cst == Duration.zero); + assert(cst - cst == Duration.zero); + //assert(ist - cst == Duration.zero); + + //assert(st - ist == Duration.zero); + //assert(cst - ist == Duration.zero); + //assert(ist - ist == Duration.zero); + } + + + /++ + Returns the difference between the two $(LREF SysTime)s in months. + + To get the difference in years, subtract the year property + of two $(LREF SysTime)s. To get the difference in days or weeks, + subtract the $(LREF SysTime)s themselves and use the + $(REF Duration, core,time) that results. Because converting between + months and smaller units requires a specific date (which + $(REF Duration, core,time)s don't have), getting the difference in + months requires some math using both the year and month properties, so + this is a convenience function for getting the difference in months. + + Note that the number of days in the months or how far into the month + either date is is irrelevant. It is the difference in the month property + combined with the difference in years * 12. So, for instance, + December 31st and January 1st are one month apart just as December 1st + and January 31st are one month apart. + + Params: + rhs = The $(LREF SysTime) to subtract from this one. + +/ + int diffMonths(in SysTime rhs) @safe const nothrow + { + return (cast(Date) this).diffMonths(cast(Date) rhs); + } + + /// + @safe unittest + { + import std.datetime.date : Date; + + assert(SysTime(Date(1999, 2, 1)).diffMonths( + SysTime(Date(1999, 1, 31))) == 1); + + assert(SysTime(Date(1999, 1, 31)).diffMonths( + SysTime(Date(1999, 2, 1))) == -1); + + assert(SysTime(Date(1999, 3, 1)).diffMonths( + SysTime(Date(1999, 1, 1))) == 2); + + assert(SysTime(Date(1999, 1, 1)).diffMonths( + SysTime(Date(1999, 3, 31))) == -2); + } + + @safe unittest + { + auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(st.diffMonths(st) == 0); + assert(cst.diffMonths(st) == 0); + //assert(ist.diffMonths(st) == 0); + + assert(st.diffMonths(cst) == 0); + assert(cst.diffMonths(cst) == 0); + //assert(ist.diffMonths(cst) == 0); + + //assert(st.diffMonths(ist) == 0); + //assert(cst.diffMonths(ist) == 0); + //assert(ist.diffMonths(ist) == 0); + } + + + /++ + Whether this $(LREF SysTime) is in a leap year. + +/ + @property bool isLeapYear() @safe const nothrow + { + return (cast(Date) this).isLeapYear; + } + + @safe unittest + { + auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(!st.isLeapYear); + assert(!cst.isLeapYear); + //assert(!ist.isLeapYear); + } + + + /++ + Day of the week this $(LREF SysTime) is on. + +/ + @property DayOfWeek dayOfWeek() @safe const nothrow + { + return getDayOfWeek(dayOfGregorianCal); + } + + @safe unittest + { + auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(st.dayOfWeek == DayOfWeek.tue); + assert(cst.dayOfWeek == DayOfWeek.tue); + //assert(ist.dayOfWeek == DayOfWeek.tue); + } + + + /++ + Day of the year this $(LREF SysTime) is on. + +/ + @property ushort dayOfYear() @safe const nothrow + { + return (cast(Date) this).dayOfYear; + } + + /// + @safe unittest + { + import std.datetime.date : DateTime; + + assert(SysTime(DateTime(1999, 1, 1, 12, 22, 7)).dayOfYear == 1); + assert(SysTime(DateTime(1999, 12, 31, 7, 2, 59)).dayOfYear == 365); + assert(SysTime(DateTime(2000, 12, 31, 21, 20, 0)).dayOfYear == 366); + } + + @safe unittest + { + auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(st.dayOfYear == 187); + assert(cst.dayOfYear == 187); + //assert(ist.dayOfYear == 187); + } + + + /++ + Day of the year. + + Params: + day = The day of the year to set which day of the year this + $(LREF SysTime) is on. + +/ + @property void dayOfYear(int day) @safe + { + immutable hnsecs = adjTime; + immutable days = convert!("hnsecs", "days")(hnsecs); + immutable theRest = hnsecs - convert!("days", "hnsecs")(days); + + auto date = Date(cast(int) days); + date.dayOfYear = day; + + immutable newDaysHNSecs = convert!("days", "hnsecs")(date.dayOfGregorianCal - 1); + + adjTime = newDaysHNSecs + theRest; + } + + @safe unittest + { + auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + st.dayOfYear = 12; + assert(st.dayOfYear == 12); + static assert(!__traits(compiles, cst.dayOfYear = 12)); + //static assert(!__traits(compiles, ist.dayOfYear = 12)); + } + + + /++ + The Xth day of the Gregorian Calendar that this $(LREF SysTime) is on. + +/ + @property int dayOfGregorianCal() @safe const nothrow + { + immutable adjustedTime = adjTime; + + // We have to add one because 0 would be midnight, January 1st, 1 A.D., + // which would be the 1st day of the Gregorian Calendar, not the 0th. So, + // simply casting to days is one day off. + if (adjustedTime > 0) + return cast(int) getUnitsFromHNSecs!"days"(adjustedTime) + 1; + + long hnsecs = adjustedTime; + immutable days = cast(int) splitUnitsFromHNSecs!"days"(hnsecs); + + return hnsecs == 0 ? days + 1 : days; + } + + /// + @safe unittest + { + import std.datetime.date : DateTime; + + assert(SysTime(DateTime(1, 1, 1, 0, 0, 0)).dayOfGregorianCal == 1); + assert(SysTime(DateTime(1, 12, 31, 23, 59, 59)).dayOfGregorianCal == 365); + assert(SysTime(DateTime(2, 1, 1, 2, 2, 2)).dayOfGregorianCal == 366); + + assert(SysTime(DateTime(0, 12, 31, 7, 7, 7)).dayOfGregorianCal == 0); + assert(SysTime(DateTime(0, 1, 1, 19, 30, 0)).dayOfGregorianCal == -365); + assert(SysTime(DateTime(-1, 12, 31, 4, 7, 0)).dayOfGregorianCal == -366); + + assert(SysTime(DateTime(2000, 1, 1, 9, 30, 20)).dayOfGregorianCal == 730_120); + assert(SysTime(DateTime(2010, 12, 31, 15, 45, 50)).dayOfGregorianCal == 734_137); + } + + @safe unittest + { + // Test A.D. + assert(SysTime(DateTime(1, 1, 1, 0, 0, 0)).dayOfGregorianCal == 1); + assert(SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1)).dayOfGregorianCal == 1); + assert(SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)).dayOfGregorianCal == 1); + + assert(SysTime(DateTime(1, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 1); + assert(SysTime(DateTime(1, 1, 2, 12, 2, 9), msecs(212)).dayOfGregorianCal == 2); + assert(SysTime(DateTime(1, 2, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 32); + assert(SysTime(DateTime(2, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 366); + assert(SysTime(DateTime(3, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 731); + assert(SysTime(DateTime(4, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 1096); + assert(SysTime(DateTime(5, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 1462); + assert(SysTime(DateTime(50, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 17_898); + assert(SysTime(DateTime(97, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 35_065); + assert(SysTime(DateTime(100, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 36_160); + assert(SysTime(DateTime(101, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 36_525); + assert(SysTime(DateTime(105, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 37_986); + assert(SysTime(DateTime(200, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 72_684); + assert(SysTime(DateTime(201, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 73_049); + assert(SysTime(DateTime(300, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 109_208); + assert(SysTime(DateTime(301, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 109_573); + assert(SysTime(DateTime(400, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 145_732); + assert(SysTime(DateTime(401, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 146_098); + assert(SysTime(DateTime(500, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 182_257); + assert(SysTime(DateTime(501, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 182_622); + assert(SysTime(DateTime(1000, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 364_878); + assert(SysTime(DateTime(1001, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 365_243); + assert(SysTime(DateTime(1600, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 584_023); + assert(SysTime(DateTime(1601, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 584_389); + assert(SysTime(DateTime(1900, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 693_596); + assert(SysTime(DateTime(1901, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 693_961); + assert(SysTime(DateTime(1945, 11, 12, 12, 2, 9), msecs(212)).dayOfGregorianCal == 710_347); + assert(SysTime(DateTime(1999, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 729_755); + assert(SysTime(DateTime(2000, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 730_120); + assert(SysTime(DateTime(2001, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == 730_486); + + assert(SysTime(DateTime(2010, 1, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_773); + assert(SysTime(DateTime(2010, 1, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_803); + assert(SysTime(DateTime(2010, 2, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_804); + assert(SysTime(DateTime(2010, 2, 28, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_831); + assert(SysTime(DateTime(2010, 3, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_832); + assert(SysTime(DateTime(2010, 3, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_862); + assert(SysTime(DateTime(2010, 4, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_863); + assert(SysTime(DateTime(2010, 4, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_892); + assert(SysTime(DateTime(2010, 5, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_893); + assert(SysTime(DateTime(2010, 5, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_923); + assert(SysTime(DateTime(2010, 6, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_924); + assert(SysTime(DateTime(2010, 6, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_953); + assert(SysTime(DateTime(2010, 7, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_954); + assert(SysTime(DateTime(2010, 7, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_984); + assert(SysTime(DateTime(2010, 8, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 733_985); + assert(SysTime(DateTime(2010, 8, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_015); + assert(SysTime(DateTime(2010, 9, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_016); + assert(SysTime(DateTime(2010, 9, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_045); + assert(SysTime(DateTime(2010, 10, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_046); + assert(SysTime(DateTime(2010, 10, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_076); + assert(SysTime(DateTime(2010, 11, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_077); + assert(SysTime(DateTime(2010, 11, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_106); + assert(SysTime(DateTime(2010, 12, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_107); + assert(SysTime(DateTime(2010, 12, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == 734_137); + + assert(SysTime(DateTime(2012, 2, 1, 0, 0, 0)).dayOfGregorianCal == 734_534); + assert(SysTime(DateTime(2012, 2, 28, 0, 0, 0)).dayOfGregorianCal == 734_561); + assert(SysTime(DateTime(2012, 2, 29, 0, 0, 0)).dayOfGregorianCal == 734_562); + assert(SysTime(DateTime(2012, 3, 1, 0, 0, 0)).dayOfGregorianCal == 734_563); + + // Test B.C. + assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999)).dayOfGregorianCal == 0); + assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_998)).dayOfGregorianCal == 0); + assert(SysTime(DateTime(0, 12, 31, 23, 59, 59)).dayOfGregorianCal == 0); + assert(SysTime(DateTime(0, 12, 31, 0, 0, 0), hnsecs(1)).dayOfGregorianCal == 0); + assert(SysTime(DateTime(0, 12, 31, 0, 0, 0)).dayOfGregorianCal == 0); + + assert(SysTime(DateTime(-1, 12, 31, 23, 59, 59), hnsecs(9_999_999)).dayOfGregorianCal == -366); + assert(SysTime(DateTime(-1, 12, 31, 23, 59, 59), hnsecs(9_999_998)).dayOfGregorianCal == -366); + assert(SysTime(DateTime(-1, 12, 31, 23, 59, 59)).dayOfGregorianCal == -366); + assert(SysTime(DateTime(-1, 12, 31, 0, 0, 0)).dayOfGregorianCal == -366); + + assert(SysTime(DateTime(0, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == 0); + assert(SysTime(DateTime(0, 12, 30, 12, 2, 9), msecs(212)).dayOfGregorianCal == -1); + assert(SysTime(DateTime(0, 12, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -30); + assert(SysTime(DateTime(0, 11, 30, 12, 2, 9), msecs(212)).dayOfGregorianCal == -31); + + assert(SysTime(DateTime(-1, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -366); + assert(SysTime(DateTime(-1, 12, 30, 12, 2, 9), msecs(212)).dayOfGregorianCal == -367); + assert(SysTime(DateTime(-1, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -730); + assert(SysTime(DateTime(-2, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -731); + assert(SysTime(DateTime(-2, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -1095); + assert(SysTime(DateTime(-3, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -1096); + assert(SysTime(DateTime(-3, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -1460); + assert(SysTime(DateTime(-4, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -1461); + assert(SysTime(DateTime(-4, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -1826); + assert(SysTime(DateTime(-5, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -1827); + assert(SysTime(DateTime(-5, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -2191); + assert(SysTime(DateTime(-9, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -3652); + + assert(SysTime(DateTime(-49, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -18_262); + assert(SysTime(DateTime(-50, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -18_627); + assert(SysTime(DateTime(-97, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -35_794); + assert(SysTime(DateTime(-99, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -36_160); + assert(SysTime(DateTime(-99, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -36_524); + assert(SysTime(DateTime(-100, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -36_889); + assert(SysTime(DateTime(-101, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -37_254); + assert(SysTime(DateTime(-105, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -38_715); + assert(SysTime(DateTime(-200, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -73_413); + assert(SysTime(DateTime(-201, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -73_778); + assert(SysTime(DateTime(-300, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -109_937); + assert(SysTime(DateTime(-301, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -110_302); + assert(SysTime(DateTime(-400, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -146_097); + assert(SysTime(DateTime(-400, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -146_462); + assert(SysTime(DateTime(-401, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -146_827); + assert(SysTime(DateTime(-499, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -182_621); + assert(SysTime(DateTime(-500, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -182_986); + assert(SysTime(DateTime(-501, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -183_351); + assert(SysTime(DateTime(-1000, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -365_607); + assert(SysTime(DateTime(-1001, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -365_972); + assert(SysTime(DateTime(-1599, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -584_387); + assert(SysTime(DateTime(-1600, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -584_388); + assert(SysTime(DateTime(-1600, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -584_753); + assert(SysTime(DateTime(-1601, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -585_118); + assert(SysTime(DateTime(-1900, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -694_325); + assert(SysTime(DateTime(-1901, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -694_690); + assert(SysTime(DateTime(-1999, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -730_484); + assert(SysTime(DateTime(-2000, 12, 31, 12, 2, 9), msecs(212)).dayOfGregorianCal == -730_485); + assert(SysTime(DateTime(-2000, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -730_850); + assert(SysTime(DateTime(-2001, 1, 1, 12, 2, 9), msecs(212)).dayOfGregorianCal == -731_215); + + assert(SysTime(DateTime(-2010, 1, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_502); + assert(SysTime(DateTime(-2010, 1, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_472); + assert(SysTime(DateTime(-2010, 2, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_471); + assert(SysTime(DateTime(-2010, 2, 28, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_444); + assert(SysTime(DateTime(-2010, 3, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_443); + assert(SysTime(DateTime(-2010, 3, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_413); + assert(SysTime(DateTime(-2010, 4, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_412); + assert(SysTime(DateTime(-2010, 4, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_383); + assert(SysTime(DateTime(-2010, 5, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_382); + assert(SysTime(DateTime(-2010, 5, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_352); + assert(SysTime(DateTime(-2010, 6, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_351); + assert(SysTime(DateTime(-2010, 6, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_322); + assert(SysTime(DateTime(-2010, 7, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_321); + assert(SysTime(DateTime(-2010, 7, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_291); + assert(SysTime(DateTime(-2010, 8, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_290); + assert(SysTime(DateTime(-2010, 8, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_260); + assert(SysTime(DateTime(-2010, 9, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_259); + assert(SysTime(DateTime(-2010, 9, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_230); + assert(SysTime(DateTime(-2010, 10, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_229); + assert(SysTime(DateTime(-2010, 10, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_199); + assert(SysTime(DateTime(-2010, 11, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_198); + assert(SysTime(DateTime(-2010, 11, 30, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_169); + assert(SysTime(DateTime(-2010, 12, 1, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_168); + assert(SysTime(DateTime(-2010, 12, 31, 23, 59, 59), msecs(999)).dayOfGregorianCal == -734_138); + + assert(SysTime(DateTime(-2012, 2, 1, 0, 0, 0)).dayOfGregorianCal == -735_202); + assert(SysTime(DateTime(-2012, 2, 28, 0, 0, 0)).dayOfGregorianCal == -735_175); + assert(SysTime(DateTime(-2012, 2, 29, 0, 0, 0)).dayOfGregorianCal == -735_174); + assert(SysTime(DateTime(-2012, 3, 1, 0, 0, 0)).dayOfGregorianCal == -735_173); + + // Start of Hebrew Calendar + assert(SysTime(DateTime(-3760, 9, 7, 0, 0, 0)).dayOfGregorianCal == -1_373_427); + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cst.dayOfGregorianCal == 729_941); + //assert(ist.dayOfGregorianCal == 729_941); + } + + + // Test that the logic for the day of the Gregorian Calendar is consistent + // between Date and SysTime. + @safe unittest + { + void test(Date date, SysTime st, size_t line = __LINE__) + { + if (date.dayOfGregorianCal != st.dayOfGregorianCal) + { + throw new AssertError(format("Date [%s] SysTime [%s]", date.dayOfGregorianCal, st.dayOfGregorianCal), + __FILE__, line); + } + } + + // Test A.D. + test(Date(1, 1, 1), SysTime(DateTime(1, 1, 1, 0, 0, 0))); + test(Date(1, 1, 2), SysTime(DateTime(1, 1, 2, 0, 0, 0), hnsecs(500))); + test(Date(1, 2, 1), SysTime(DateTime(1, 2, 1, 0, 0, 0), hnsecs(50_000))); + test(Date(2, 1, 1), SysTime(DateTime(2, 1, 1, 0, 0, 0), hnsecs(9_999_999))); + test(Date(3, 1, 1), SysTime(DateTime(3, 1, 1, 12, 13, 14))); + test(Date(4, 1, 1), SysTime(DateTime(4, 1, 1, 12, 13, 14), hnsecs(500))); + test(Date(5, 1, 1), SysTime(DateTime(5, 1, 1, 12, 13, 14), hnsecs(50_000))); + test(Date(50, 1, 1), SysTime(DateTime(50, 1, 1, 12, 13, 14), hnsecs(9_999_999))); + test(Date(97, 1, 1), SysTime(DateTime(97, 1, 1, 23, 59, 59))); + test(Date(100, 1, 1), SysTime(DateTime(100, 1, 1, 23, 59, 59), hnsecs(500))); + test(Date(101, 1, 1), SysTime(DateTime(101, 1, 1, 23, 59, 59), hnsecs(50_000))); + test(Date(105, 1, 1), SysTime(DateTime(105, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + test(Date(200, 1, 1), SysTime(DateTime(200, 1, 1, 0, 0, 0))); + test(Date(201, 1, 1), SysTime(DateTime(201, 1, 1, 0, 0, 0), hnsecs(500))); + test(Date(300, 1, 1), SysTime(DateTime(300, 1, 1, 0, 0, 0), hnsecs(50_000))); + test(Date(301, 1, 1), SysTime(DateTime(301, 1, 1, 0, 0, 0), hnsecs(9_999_999))); + test(Date(400, 1, 1), SysTime(DateTime(400, 1, 1, 12, 13, 14))); + test(Date(401, 1, 1), SysTime(DateTime(401, 1, 1, 12, 13, 14), hnsecs(500))); + test(Date(500, 1, 1), SysTime(DateTime(500, 1, 1, 12, 13, 14), hnsecs(50_000))); + test(Date(501, 1, 1), SysTime(DateTime(501, 1, 1, 12, 13, 14), hnsecs(9_999_999))); + test(Date(1000, 1, 1), SysTime(DateTime(1000, 1, 1, 23, 59, 59))); + test(Date(1001, 1, 1), SysTime(DateTime(1001, 1, 1, 23, 59, 59), hnsecs(500))); + test(Date(1600, 1, 1), SysTime(DateTime(1600, 1, 1, 23, 59, 59), hnsecs(50_000))); + test(Date(1601, 1, 1), SysTime(DateTime(1601, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + test(Date(1900, 1, 1), SysTime(DateTime(1900, 1, 1, 0, 0, 0))); + test(Date(1901, 1, 1), SysTime(DateTime(1901, 1, 1, 0, 0, 0), hnsecs(500))); + test(Date(1945, 11, 12), SysTime(DateTime(1945, 11, 12, 0, 0, 0), hnsecs(50_000))); + test(Date(1999, 1, 1), SysTime(DateTime(1999, 1, 1, 0, 0, 0), hnsecs(9_999_999))); + test(Date(1999, 7, 6), SysTime(DateTime(1999, 7, 6, 12, 13, 14))); + test(Date(2000, 1, 1), SysTime(DateTime(2000, 1, 1, 12, 13, 14), hnsecs(500))); + test(Date(2001, 1, 1), SysTime(DateTime(2001, 1, 1, 12, 13, 14), hnsecs(50_000))); + + test(Date(2010, 1, 1), SysTime(DateTime(2010, 1, 1, 12, 13, 14), hnsecs(9_999_999))); + test(Date(2010, 1, 31), SysTime(DateTime(2010, 1, 31, 23, 0, 0))); + test(Date(2010, 2, 1), SysTime(DateTime(2010, 2, 1, 23, 59, 59), hnsecs(500))); + test(Date(2010, 2, 28), SysTime(DateTime(2010, 2, 28, 23, 59, 59), hnsecs(50_000))); + test(Date(2010, 3, 1), SysTime(DateTime(2010, 3, 1, 23, 59, 59), hnsecs(9_999_999))); + test(Date(2010, 3, 31), SysTime(DateTime(2010, 3, 31, 0, 0, 0))); + test(Date(2010, 4, 1), SysTime(DateTime(2010, 4, 1, 0, 0, 0), hnsecs(500))); + test(Date(2010, 4, 30), SysTime(DateTime(2010, 4, 30, 0, 0, 0), hnsecs(50_000))); + test(Date(2010, 5, 1), SysTime(DateTime(2010, 5, 1, 0, 0, 0), hnsecs(9_999_999))); + test(Date(2010, 5, 31), SysTime(DateTime(2010, 5, 31, 12, 13, 14))); + test(Date(2010, 6, 1), SysTime(DateTime(2010, 6, 1, 12, 13, 14), hnsecs(500))); + test(Date(2010, 6, 30), SysTime(DateTime(2010, 6, 30, 12, 13, 14), hnsecs(50_000))); + test(Date(2010, 7, 1), SysTime(DateTime(2010, 7, 1, 12, 13, 14), hnsecs(9_999_999))); + test(Date(2010, 7, 31), SysTime(DateTime(2010, 7, 31, 23, 59, 59))); + test(Date(2010, 8, 1), SysTime(DateTime(2010, 8, 1, 23, 59, 59), hnsecs(500))); + test(Date(2010, 8, 31), SysTime(DateTime(2010, 8, 31, 23, 59, 59), hnsecs(50_000))); + test(Date(2010, 9, 1), SysTime(DateTime(2010, 9, 1, 23, 59, 59), hnsecs(9_999_999))); + test(Date(2010, 9, 30), SysTime(DateTime(2010, 9, 30, 12, 0, 0))); + test(Date(2010, 10, 1), SysTime(DateTime(2010, 10, 1, 0, 12, 0), hnsecs(500))); + test(Date(2010, 10, 31), SysTime(DateTime(2010, 10, 31, 0, 0, 12), hnsecs(50_000))); + test(Date(2010, 11, 1), SysTime(DateTime(2010, 11, 1, 23, 0, 0), hnsecs(9_999_999))); + test(Date(2010, 11, 30), SysTime(DateTime(2010, 11, 30, 0, 59, 0))); + test(Date(2010, 12, 1), SysTime(DateTime(2010, 12, 1, 0, 0, 59), hnsecs(500))); + test(Date(2010, 12, 31), SysTime(DateTime(2010, 12, 31, 0, 59, 59), hnsecs(50_000))); + + test(Date(2012, 2, 1), SysTime(DateTime(2012, 2, 1, 23, 0, 59), hnsecs(9_999_999))); + test(Date(2012, 2, 28), SysTime(DateTime(2012, 2, 28, 23, 59, 0))); + test(Date(2012, 2, 29), SysTime(DateTime(2012, 2, 29, 7, 7, 7), hnsecs(7))); + test(Date(2012, 3, 1), SysTime(DateTime(2012, 3, 1, 7, 7, 7), hnsecs(7))); + + // Test B.C. + test(Date(0, 12, 31), SysTime(DateTime(0, 12, 31, 0, 0, 0))); + test(Date(0, 12, 30), SysTime(DateTime(0, 12, 30, 0, 0, 0), hnsecs(500))); + test(Date(0, 12, 1), SysTime(DateTime(0, 12, 1, 0, 0, 0), hnsecs(50_000))); + test(Date(0, 11, 30), SysTime(DateTime(0, 11, 30, 0, 0, 0), hnsecs(9_999_999))); + + test(Date(-1, 12, 31), SysTime(DateTime(-1, 12, 31, 12, 13, 14))); + test(Date(-1, 12, 30), SysTime(DateTime(-1, 12, 30, 12, 13, 14), hnsecs(500))); + test(Date(-1, 1, 1), SysTime(DateTime(-1, 1, 1, 12, 13, 14), hnsecs(50_000))); + test(Date(-2, 12, 31), SysTime(DateTime(-2, 12, 31, 12, 13, 14), hnsecs(9_999_999))); + test(Date(-2, 1, 1), SysTime(DateTime(-2, 1, 1, 23, 59, 59))); + test(Date(-3, 12, 31), SysTime(DateTime(-3, 12, 31, 23, 59, 59), hnsecs(500))); + test(Date(-3, 1, 1), SysTime(DateTime(-3, 1, 1, 23, 59, 59), hnsecs(50_000))); + test(Date(-4, 12, 31), SysTime(DateTime(-4, 12, 31, 23, 59, 59), hnsecs(9_999_999))); + test(Date(-4, 1, 1), SysTime(DateTime(-4, 1, 1, 0, 0, 0))); + test(Date(-5, 12, 31), SysTime(DateTime(-5, 12, 31, 0, 0, 0), hnsecs(500))); + test(Date(-5, 1, 1), SysTime(DateTime(-5, 1, 1, 0, 0, 0), hnsecs(50_000))); + test(Date(-9, 1, 1), SysTime(DateTime(-9, 1, 1, 0, 0, 0), hnsecs(9_999_999))); + + test(Date(-49, 1, 1), SysTime(DateTime(-49, 1, 1, 12, 13, 14))); + test(Date(-50, 1, 1), SysTime(DateTime(-50, 1, 1, 12, 13, 14), hnsecs(500))); + test(Date(-97, 1, 1), SysTime(DateTime(-97, 1, 1, 12, 13, 14), hnsecs(50_000))); + test(Date(-99, 12, 31), SysTime(DateTime(-99, 12, 31, 12, 13, 14), hnsecs(9_999_999))); + test(Date(-99, 1, 1), SysTime(DateTime(-99, 1, 1, 23, 59, 59))); + test(Date(-100, 1, 1), SysTime(DateTime(-100, 1, 1, 23, 59, 59), hnsecs(500))); + test(Date(-101, 1, 1), SysTime(DateTime(-101, 1, 1, 23, 59, 59), hnsecs(50_000))); + test(Date(-105, 1, 1), SysTime(DateTime(-105, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + test(Date(-200, 1, 1), SysTime(DateTime(-200, 1, 1, 0, 0, 0))); + test(Date(-201, 1, 1), SysTime(DateTime(-201, 1, 1, 0, 0, 0), hnsecs(500))); + test(Date(-300, 1, 1), SysTime(DateTime(-300, 1, 1, 0, 0, 0), hnsecs(50_000))); + test(Date(-301, 1, 1), SysTime(DateTime(-301, 1, 1, 0, 0, 0), hnsecs(9_999_999))); + test(Date(-400, 12, 31), SysTime(DateTime(-400, 12, 31, 12, 13, 14))); + test(Date(-400, 1, 1), SysTime(DateTime(-400, 1, 1, 12, 13, 14), hnsecs(500))); + test(Date(-401, 1, 1), SysTime(DateTime(-401, 1, 1, 12, 13, 14), hnsecs(50_000))); + test(Date(-499, 1, 1), SysTime(DateTime(-499, 1, 1, 12, 13, 14), hnsecs(9_999_999))); + test(Date(-500, 1, 1), SysTime(DateTime(-500, 1, 1, 23, 59, 59))); + test(Date(-501, 1, 1), SysTime(DateTime(-501, 1, 1, 23, 59, 59), hnsecs(500))); + test(Date(-1000, 1, 1), SysTime(DateTime(-1000, 1, 1, 23, 59, 59), hnsecs(50_000))); + test(Date(-1001, 1, 1), SysTime(DateTime(-1001, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + test(Date(-1599, 1, 1), SysTime(DateTime(-1599, 1, 1, 0, 0, 0))); + test(Date(-1600, 12, 31), SysTime(DateTime(-1600, 12, 31, 0, 0, 0), hnsecs(500))); + test(Date(-1600, 1, 1), SysTime(DateTime(-1600, 1, 1, 0, 0, 0), hnsecs(50_000))); + test(Date(-1601, 1, 1), SysTime(DateTime(-1601, 1, 1, 0, 0, 0), hnsecs(9_999_999))); + test(Date(-1900, 1, 1), SysTime(DateTime(-1900, 1, 1, 12, 13, 14))); + test(Date(-1901, 1, 1), SysTime(DateTime(-1901, 1, 1, 12, 13, 14), hnsecs(500))); + test(Date(-1999, 1, 1), SysTime(DateTime(-1999, 1, 1, 12, 13, 14), hnsecs(50_000))); + test(Date(-1999, 7, 6), SysTime(DateTime(-1999, 7, 6, 12, 13, 14), hnsecs(9_999_999))); + test(Date(-2000, 12, 31), SysTime(DateTime(-2000, 12, 31, 23, 59, 59))); + test(Date(-2000, 1, 1), SysTime(DateTime(-2000, 1, 1, 23, 59, 59), hnsecs(500))); + test(Date(-2001, 1, 1), SysTime(DateTime(-2001, 1, 1, 23, 59, 59), hnsecs(50_000))); + + test(Date(-2010, 1, 1), SysTime(DateTime(-2010, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + test(Date(-2010, 1, 31), SysTime(DateTime(-2010, 1, 31, 0, 0, 0))); + test(Date(-2010, 2, 1), SysTime(DateTime(-2010, 2, 1, 0, 0, 0), hnsecs(500))); + test(Date(-2010, 2, 28), SysTime(DateTime(-2010, 2, 28, 0, 0, 0), hnsecs(50_000))); + test(Date(-2010, 3, 1), SysTime(DateTime(-2010, 3, 1, 0, 0, 0), hnsecs(9_999_999))); + test(Date(-2010, 3, 31), SysTime(DateTime(-2010, 3, 31, 12, 13, 14))); + test(Date(-2010, 4, 1), SysTime(DateTime(-2010, 4, 1, 12, 13, 14), hnsecs(500))); + test(Date(-2010, 4, 30), SysTime(DateTime(-2010, 4, 30, 12, 13, 14), hnsecs(50_000))); + test(Date(-2010, 5, 1), SysTime(DateTime(-2010, 5, 1, 12, 13, 14), hnsecs(9_999_999))); + test(Date(-2010, 5, 31), SysTime(DateTime(-2010, 5, 31, 23, 59, 59))); + test(Date(-2010, 6, 1), SysTime(DateTime(-2010, 6, 1, 23, 59, 59), hnsecs(500))); + test(Date(-2010, 6, 30), SysTime(DateTime(-2010, 6, 30, 23, 59, 59), hnsecs(50_000))); + test(Date(-2010, 7, 1), SysTime(DateTime(-2010, 7, 1, 23, 59, 59), hnsecs(9_999_999))); + test(Date(-2010, 7, 31), SysTime(DateTime(-2010, 7, 31, 0, 0, 0))); + test(Date(-2010, 8, 1), SysTime(DateTime(-2010, 8, 1, 0, 0, 0), hnsecs(500))); + test(Date(-2010, 8, 31), SysTime(DateTime(-2010, 8, 31, 0, 0, 0), hnsecs(50_000))); + test(Date(-2010, 9, 1), SysTime(DateTime(-2010, 9, 1, 0, 0, 0), hnsecs(9_999_999))); + test(Date(-2010, 9, 30), SysTime(DateTime(-2010, 9, 30, 12, 0, 0))); + test(Date(-2010, 10, 1), SysTime(DateTime(-2010, 10, 1, 0, 12, 0), hnsecs(500))); + test(Date(-2010, 10, 31), SysTime(DateTime(-2010, 10, 31, 0, 0, 12), hnsecs(50_000))); + test(Date(-2010, 11, 1), SysTime(DateTime(-2010, 11, 1, 23, 0, 0), hnsecs(9_999_999))); + test(Date(-2010, 11, 30), SysTime(DateTime(-2010, 11, 30, 0, 59, 0))); + test(Date(-2010, 12, 1), SysTime(DateTime(-2010, 12, 1, 0, 0, 59), hnsecs(500))); + test(Date(-2010, 12, 31), SysTime(DateTime(-2010, 12, 31, 0, 59, 59), hnsecs(50_000))); + + test(Date(-2012, 2, 1), SysTime(DateTime(-2012, 2, 1, 23, 0, 59), hnsecs(9_999_999))); + test(Date(-2012, 2, 28), SysTime(DateTime(-2012, 2, 28, 23, 59, 0))); + test(Date(-2012, 2, 29), SysTime(DateTime(-2012, 2, 29, 7, 7, 7), hnsecs(7))); + test(Date(-2012, 3, 1), SysTime(DateTime(-2012, 3, 1, 7, 7, 7), hnsecs(7))); + + test(Date(-3760, 9, 7), SysTime(DateTime(-3760, 9, 7, 0, 0, 0))); + } + + + /++ + The Xth day of the Gregorian Calendar that this $(LREF SysTime) is on. + Setting this property does not affect the time portion of $(LREF SysTime). + + Params: + days = The day of the Gregorian Calendar to set this $(LREF SysTime) + to. + +/ + @property void dayOfGregorianCal(int days) @safe nothrow + { + auto hnsecs = adjTime; + hnsecs = removeUnitsFromHNSecs!"days"(hnsecs); + + if (hnsecs < 0) + hnsecs += convert!("hours", "hnsecs")(24); + + if (--days < 0) + { + hnsecs -= convert!("hours", "hnsecs")(24); + ++days; + } + + immutable newDaysHNSecs = convert!("days", "hnsecs")(days); + + adjTime = newDaysHNSecs + hnsecs; + } + + /// + @safe unittest + { + import std.datetime.date : DateTime; + + auto st = SysTime(DateTime(0, 1, 1, 12, 0, 0)); + st.dayOfGregorianCal = 1; + assert(st == SysTime(DateTime(1, 1, 1, 12, 0, 0))); + + st.dayOfGregorianCal = 365; + assert(st == SysTime(DateTime(1, 12, 31, 12, 0, 0))); + + st.dayOfGregorianCal = 366; + assert(st == SysTime(DateTime(2, 1, 1, 12, 0, 0))); + + st.dayOfGregorianCal = 0; + assert(st == SysTime(DateTime(0, 12, 31, 12, 0, 0))); + + st.dayOfGregorianCal = -365; + assert(st == SysTime(DateTime(-0, 1, 1, 12, 0, 0))); + + st.dayOfGregorianCal = -366; + assert(st == SysTime(DateTime(-1, 12, 31, 12, 0, 0))); + + st.dayOfGregorianCal = 730_120; + assert(st == SysTime(DateTime(2000, 1, 1, 12, 0, 0))); + + st.dayOfGregorianCal = 734_137; + assert(st == SysTime(DateTime(2010, 12, 31, 12, 0, 0))); + } + + @safe unittest + { + void testST(SysTime orig, int day, in SysTime expected, size_t line = __LINE__) + { + orig.dayOfGregorianCal = day; + if (orig != expected) + throw new AssertError(format("Failed. actual [%s] != expected [%s]", orig, expected), __FILE__, line); + } + + // Test A.D. + testST(SysTime(DateTime(1, 1, 1, 0, 0, 0)), 1, SysTime(DateTime(1, 1, 1, 0, 0, 0))); + testST(SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1)), 1, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1))); + testST(SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999)), 1, + SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + + // Test B.C. + testST(SysTime(DateTime(0, 1, 1, 0, 0, 0)), 0, SysTime(DateTime(0, 12, 31, 0, 0, 0))); + testST(SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(9_999_999)), 0, + SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999))); + testST(SysTime(DateTime(0, 1, 1, 23, 59, 59), hnsecs(1)), 0, + SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(1))); + testST(SysTime(DateTime(0, 1, 1, 23, 59, 59)), 0, SysTime(DateTime(0, 12, 31, 23, 59, 59))); + + // Test Both. + testST(SysTime(DateTime(-512, 7, 20, 0, 0, 0)), 1, SysTime(DateTime(1, 1, 1, 0, 0, 0))); + testST(SysTime(DateTime(-513, 6, 6, 0, 0, 0), hnsecs(1)), 1, SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1))); + testST(SysTime(DateTime(-511, 5, 7, 23, 59, 59), hnsecs(9_999_999)), 1, + SysTime(DateTime(1, 1, 1, 23, 59, 59), hnsecs(9_999_999))); + + testST(SysTime(DateTime(1607, 4, 8, 0, 0, 0)), 0, SysTime(DateTime(0, 12, 31, 0, 0, 0))); + testST(SysTime(DateTime(1500, 3, 9, 23, 59, 59), hnsecs(9_999_999)), 0, + SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999))); + testST(SysTime(DateTime(999, 2, 10, 23, 59, 59), hnsecs(1)), 0, + SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(1))); + testST(SysTime(DateTime(2007, 12, 11, 23, 59, 59)), 0, SysTime(DateTime(0, 12, 31, 23, 59, 59))); + + + auto st = SysTime(DateTime(1, 1, 1, 12, 2, 9), msecs(212)); + + void testST2(int day, in SysTime expected, size_t line = __LINE__) + { + st.dayOfGregorianCal = day; + if (st != expected) + throw new AssertError(format("Failed. actual [%s] != expected [%s]", st, expected), __FILE__, line); + } + + // Test A.D. + testST2(1, SysTime(DateTime(1, 1, 1, 12, 2, 9), msecs(212))); + testST2(2, SysTime(DateTime(1, 1, 2, 12, 2, 9), msecs(212))); + testST2(32, SysTime(DateTime(1, 2, 1, 12, 2, 9), msecs(212))); + testST2(366, SysTime(DateTime(2, 1, 1, 12, 2, 9), msecs(212))); + testST2(731, SysTime(DateTime(3, 1, 1, 12, 2, 9), msecs(212))); + testST2(1096, SysTime(DateTime(4, 1, 1, 12, 2, 9), msecs(212))); + testST2(1462, SysTime(DateTime(5, 1, 1, 12, 2, 9), msecs(212))); + testST2(17_898, SysTime(DateTime(50, 1, 1, 12, 2, 9), msecs(212))); + testST2(35_065, SysTime(DateTime(97, 1, 1, 12, 2, 9), msecs(212))); + testST2(36_160, SysTime(DateTime(100, 1, 1, 12, 2, 9), msecs(212))); + testST2(36_525, SysTime(DateTime(101, 1, 1, 12, 2, 9), msecs(212))); + testST2(37_986, SysTime(DateTime(105, 1, 1, 12, 2, 9), msecs(212))); + testST2(72_684, SysTime(DateTime(200, 1, 1, 12, 2, 9), msecs(212))); + testST2(73_049, SysTime(DateTime(201, 1, 1, 12, 2, 9), msecs(212))); + testST2(109_208, SysTime(DateTime(300, 1, 1, 12, 2, 9), msecs(212))); + testST2(109_573, SysTime(DateTime(301, 1, 1, 12, 2, 9), msecs(212))); + testST2(145_732, SysTime(DateTime(400, 1, 1, 12, 2, 9), msecs(212))); + testST2(146_098, SysTime(DateTime(401, 1, 1, 12, 2, 9), msecs(212))); + testST2(182_257, SysTime(DateTime(500, 1, 1, 12, 2, 9), msecs(212))); + testST2(182_622, SysTime(DateTime(501, 1, 1, 12, 2, 9), msecs(212))); + testST2(364_878, SysTime(DateTime(1000, 1, 1, 12, 2, 9), msecs(212))); + testST2(365_243, SysTime(DateTime(1001, 1, 1, 12, 2, 9), msecs(212))); + testST2(584_023, SysTime(DateTime(1600, 1, 1, 12, 2, 9), msecs(212))); + testST2(584_389, SysTime(DateTime(1601, 1, 1, 12, 2, 9), msecs(212))); + testST2(693_596, SysTime(DateTime(1900, 1, 1, 12, 2, 9), msecs(212))); + testST2(693_961, SysTime(DateTime(1901, 1, 1, 12, 2, 9), msecs(212))); + testST2(729_755, SysTime(DateTime(1999, 1, 1, 12, 2, 9), msecs(212))); + testST2(730_120, SysTime(DateTime(2000, 1, 1, 12, 2, 9), msecs(212))); + testST2(730_486, SysTime(DateTime(2001, 1, 1, 12, 2, 9), msecs(212))); + + testST2(733_773, SysTime(DateTime(2010, 1, 1, 12, 2, 9), msecs(212))); + testST2(733_803, SysTime(DateTime(2010, 1, 31, 12, 2, 9), msecs(212))); + testST2(733_804, SysTime(DateTime(2010, 2, 1, 12, 2, 9), msecs(212))); + testST2(733_831, SysTime(DateTime(2010, 2, 28, 12, 2, 9), msecs(212))); + testST2(733_832, SysTime(DateTime(2010, 3, 1, 12, 2, 9), msecs(212))); + testST2(733_862, SysTime(DateTime(2010, 3, 31, 12, 2, 9), msecs(212))); + testST2(733_863, SysTime(DateTime(2010, 4, 1, 12, 2, 9), msecs(212))); + testST2(733_892, SysTime(DateTime(2010, 4, 30, 12, 2, 9), msecs(212))); + testST2(733_893, SysTime(DateTime(2010, 5, 1, 12, 2, 9), msecs(212))); + testST2(733_923, SysTime(DateTime(2010, 5, 31, 12, 2, 9), msecs(212))); + testST2(733_924, SysTime(DateTime(2010, 6, 1, 12, 2, 9), msecs(212))); + testST2(733_953, SysTime(DateTime(2010, 6, 30, 12, 2, 9), msecs(212))); + testST2(733_954, SysTime(DateTime(2010, 7, 1, 12, 2, 9), msecs(212))); + testST2(733_984, SysTime(DateTime(2010, 7, 31, 12, 2, 9), msecs(212))); + testST2(733_985, SysTime(DateTime(2010, 8, 1, 12, 2, 9), msecs(212))); + testST2(734_015, SysTime(DateTime(2010, 8, 31, 12, 2, 9), msecs(212))); + testST2(734_016, SysTime(DateTime(2010, 9, 1, 12, 2, 9), msecs(212))); + testST2(734_045, SysTime(DateTime(2010, 9, 30, 12, 2, 9), msecs(212))); + testST2(734_046, SysTime(DateTime(2010, 10, 1, 12, 2, 9), msecs(212))); + testST2(734_076, SysTime(DateTime(2010, 10, 31, 12, 2, 9), msecs(212))); + testST2(734_077, SysTime(DateTime(2010, 11, 1, 12, 2, 9), msecs(212))); + testST2(734_106, SysTime(DateTime(2010, 11, 30, 12, 2, 9), msecs(212))); + testST2(734_107, SysTime(DateTime(2010, 12, 1, 12, 2, 9), msecs(212))); + testST2(734_137, SysTime(DateTime(2010, 12, 31, 12, 2, 9), msecs(212))); + + testST2(734_534, SysTime(DateTime(2012, 2, 1, 12, 2, 9), msecs(212))); + testST2(734_561, SysTime(DateTime(2012, 2, 28, 12, 2, 9), msecs(212))); + testST2(734_562, SysTime(DateTime(2012, 2, 29, 12, 2, 9), msecs(212))); + testST2(734_563, SysTime(DateTime(2012, 3, 1, 12, 2, 9), msecs(212))); + + testST2(734_534, SysTime(DateTime(2012, 2, 1, 12, 2, 9), msecs(212))); + + testST2(734_561, SysTime(DateTime(2012, 2, 28, 12, 2, 9), msecs(212))); + testST2(734_562, SysTime(DateTime(2012, 2, 29, 12, 2, 9), msecs(212))); + testST2(734_563, SysTime(DateTime(2012, 3, 1, 12, 2, 9), msecs(212))); + + // Test B.C. + testST2(0, SysTime(DateTime(0, 12, 31, 12, 2, 9), msecs(212))); + testST2(-1, SysTime(DateTime(0, 12, 30, 12, 2, 9), msecs(212))); + testST2(-30, SysTime(DateTime(0, 12, 1, 12, 2, 9), msecs(212))); + testST2(-31, SysTime(DateTime(0, 11, 30, 12, 2, 9), msecs(212))); + + testST2(-366, SysTime(DateTime(-1, 12, 31, 12, 2, 9), msecs(212))); + testST2(-367, SysTime(DateTime(-1, 12, 30, 12, 2, 9), msecs(212))); + testST2(-730, SysTime(DateTime(-1, 1, 1, 12, 2, 9), msecs(212))); + testST2(-731, SysTime(DateTime(-2, 12, 31, 12, 2, 9), msecs(212))); + testST2(-1095, SysTime(DateTime(-2, 1, 1, 12, 2, 9), msecs(212))); + testST2(-1096, SysTime(DateTime(-3, 12, 31, 12, 2, 9), msecs(212))); + testST2(-1460, SysTime(DateTime(-3, 1, 1, 12, 2, 9), msecs(212))); + testST2(-1461, SysTime(DateTime(-4, 12, 31, 12, 2, 9), msecs(212))); + testST2(-1826, SysTime(DateTime(-4, 1, 1, 12, 2, 9), msecs(212))); + testST2(-1827, SysTime(DateTime(-5, 12, 31, 12, 2, 9), msecs(212))); + testST2(-2191, SysTime(DateTime(-5, 1, 1, 12, 2, 9), msecs(212))); + testST2(-3652, SysTime(DateTime(-9, 1, 1, 12, 2, 9), msecs(212))); + + testST2(-18_262, SysTime(DateTime(-49, 1, 1, 12, 2, 9), msecs(212))); + testST2(-18_627, SysTime(DateTime(-50, 1, 1, 12, 2, 9), msecs(212))); + testST2(-35_794, SysTime(DateTime(-97, 1, 1, 12, 2, 9), msecs(212))); + testST2(-36_160, SysTime(DateTime(-99, 12, 31, 12, 2, 9), msecs(212))); + testST2(-36_524, SysTime(DateTime(-99, 1, 1, 12, 2, 9), msecs(212))); + testST2(-36_889, SysTime(DateTime(-100, 1, 1, 12, 2, 9), msecs(212))); + testST2(-37_254, SysTime(DateTime(-101, 1, 1, 12, 2, 9), msecs(212))); + testST2(-38_715, SysTime(DateTime(-105, 1, 1, 12, 2, 9), msecs(212))); + testST2(-73_413, SysTime(DateTime(-200, 1, 1, 12, 2, 9), msecs(212))); + testST2(-73_778, SysTime(DateTime(-201, 1, 1, 12, 2, 9), msecs(212))); + testST2(-109_937, SysTime(DateTime(-300, 1, 1, 12, 2, 9), msecs(212))); + testST2(-110_302, SysTime(DateTime(-301, 1, 1, 12, 2, 9), msecs(212))); + testST2(-146_097, SysTime(DateTime(-400, 12, 31, 12, 2, 9), msecs(212))); + testST2(-146_462, SysTime(DateTime(-400, 1, 1, 12, 2, 9), msecs(212))); + testST2(-146_827, SysTime(DateTime(-401, 1, 1, 12, 2, 9), msecs(212))); + testST2(-182_621, SysTime(DateTime(-499, 1, 1, 12, 2, 9), msecs(212))); + testST2(-182_986, SysTime(DateTime(-500, 1, 1, 12, 2, 9), msecs(212))); + testST2(-183_351, SysTime(DateTime(-501, 1, 1, 12, 2, 9), msecs(212))); + testST2(-365_607, SysTime(DateTime(-1000, 1, 1, 12, 2, 9), msecs(212))); + testST2(-365_972, SysTime(DateTime(-1001, 1, 1, 12, 2, 9), msecs(212))); + testST2(-584_387, SysTime(DateTime(-1599, 1, 1, 12, 2, 9), msecs(212))); + testST2(-584_388, SysTime(DateTime(-1600, 12, 31, 12, 2, 9), msecs(212))); + testST2(-584_753, SysTime(DateTime(-1600, 1, 1, 12, 2, 9), msecs(212))); + testST2(-585_118, SysTime(DateTime(-1601, 1, 1, 12, 2, 9), msecs(212))); + testST2(-694_325, SysTime(DateTime(-1900, 1, 1, 12, 2, 9), msecs(212))); + testST2(-694_690, SysTime(DateTime(-1901, 1, 1, 12, 2, 9), msecs(212))); + testST2(-730_484, SysTime(DateTime(-1999, 1, 1, 12, 2, 9), msecs(212))); + testST2(-730_485, SysTime(DateTime(-2000, 12, 31, 12, 2, 9), msecs(212))); + testST2(-730_850, SysTime(DateTime(-2000, 1, 1, 12, 2, 9), msecs(212))); + testST2(-731_215, SysTime(DateTime(-2001, 1, 1, 12, 2, 9), msecs(212))); + + testST2(-734_502, SysTime(DateTime(-2010, 1, 1, 12, 2, 9), msecs(212))); + testST2(-734_472, SysTime(DateTime(-2010, 1, 31, 12, 2, 9), msecs(212))); + testST2(-734_471, SysTime(DateTime(-2010, 2, 1, 12, 2, 9), msecs(212))); + testST2(-734_444, SysTime(DateTime(-2010, 2, 28, 12, 2, 9), msecs(212))); + testST2(-734_443, SysTime(DateTime(-2010, 3, 1, 12, 2, 9), msecs(212))); + testST2(-734_413, SysTime(DateTime(-2010, 3, 31, 12, 2, 9), msecs(212))); + testST2(-734_412, SysTime(DateTime(-2010, 4, 1, 12, 2, 9), msecs(212))); + testST2(-734_383, SysTime(DateTime(-2010, 4, 30, 12, 2, 9), msecs(212))); + testST2(-734_382, SysTime(DateTime(-2010, 5, 1, 12, 2, 9), msecs(212))); + testST2(-734_352, SysTime(DateTime(-2010, 5, 31, 12, 2, 9), msecs(212))); + testST2(-734_351, SysTime(DateTime(-2010, 6, 1, 12, 2, 9), msecs(212))); + testST2(-734_322, SysTime(DateTime(-2010, 6, 30, 12, 2, 9), msecs(212))); + testST2(-734_321, SysTime(DateTime(-2010, 7, 1, 12, 2, 9), msecs(212))); + testST2(-734_291, SysTime(DateTime(-2010, 7, 31, 12, 2, 9), msecs(212))); + testST2(-734_290, SysTime(DateTime(-2010, 8, 1, 12, 2, 9), msecs(212))); + testST2(-734_260, SysTime(DateTime(-2010, 8, 31, 12, 2, 9), msecs(212))); + testST2(-734_259, SysTime(DateTime(-2010, 9, 1, 12, 2, 9), msecs(212))); + testST2(-734_230, SysTime(DateTime(-2010, 9, 30, 12, 2, 9), msecs(212))); + testST2(-734_229, SysTime(DateTime(-2010, 10, 1, 12, 2, 9), msecs(212))); + testST2(-734_199, SysTime(DateTime(-2010, 10, 31, 12, 2, 9), msecs(212))); + testST2(-734_198, SysTime(DateTime(-2010, 11, 1, 12, 2, 9), msecs(212))); + testST2(-734_169, SysTime(DateTime(-2010, 11, 30, 12, 2, 9), msecs(212))); + testST2(-734_168, SysTime(DateTime(-2010, 12, 1, 12, 2, 9), msecs(212))); + testST2(-734_138, SysTime(DateTime(-2010, 12, 31, 12, 2, 9), msecs(212))); + + testST2(-735_202, SysTime(DateTime(-2012, 2, 1, 12, 2, 9), msecs(212))); + testST2(-735_175, SysTime(DateTime(-2012, 2, 28, 12, 2, 9), msecs(212))); + testST2(-735_174, SysTime(DateTime(-2012, 2, 29, 12, 2, 9), msecs(212))); + testST2(-735_173, SysTime(DateTime(-2012, 3, 1, 12, 2, 9), msecs(212))); + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + static assert(!__traits(compiles, cst.dayOfGregorianCal = 7)); + //static assert(!__traits(compiles, ist.dayOfGregorianCal = 7)); + } + + + /++ + The ISO 8601 week of the year that this $(LREF SysTime) is in. + + See_Also: + $(HTTP en.wikipedia.org/wiki/ISO_week_date, ISO Week Date). + +/ + @property ubyte isoWeek() @safe const nothrow + { + return (cast(Date) this).isoWeek; + } + + /// + @safe unittest + { + import std.datetime.date : Date; + + auto st = SysTime(Date(1999, 7, 6)); + const cst = SysTime(Date(2010, 5, 1)); + immutable ist = SysTime(Date(2015, 10, 10)); + + assert(st.isoWeek == 27); + assert(cst.isoWeek == 17); + assert(ist.isoWeek == 41); + } + + + /++ + $(LREF SysTime) for the last day in the month that this Date is in. + The time portion of endOfMonth is always 23:59:59.9999999. + +/ + @property SysTime endOfMonth() @safe const nothrow + { + immutable hnsecs = adjTime; + immutable days = getUnitsFromHNSecs!"days"(hnsecs); + + auto date = Date(cast(int) days + 1).endOfMonth; + auto newDays = date.dayOfGregorianCal - 1; + long theTimeHNSecs; + + if (newDays < 0) + { + theTimeHNSecs = -1; + ++newDays; + } + else + theTimeHNSecs = convert!("days", "hnsecs")(1) - 1; + + immutable newDaysHNSecs = convert!("days", "hnsecs")(newDays); + + auto retval = SysTime(this._stdTime, this._timezone); + retval.adjTime = newDaysHNSecs + theTimeHNSecs; + + return retval; + } + + /// + @safe unittest + { + import core.time : msecs, usecs, hnsecs; + import std.datetime.date : DateTime; + + assert(SysTime(DateTime(1999, 1, 6, 0, 0, 0)).endOfMonth == + SysTime(DateTime(1999, 1, 31, 23, 59, 59), hnsecs(9_999_999))); + + assert(SysTime(DateTime(1999, 2, 7, 19, 30, 0), msecs(24)).endOfMonth == + SysTime(DateTime(1999, 2, 28, 23, 59, 59), hnsecs(9_999_999))); + + assert(SysTime(DateTime(2000, 2, 7, 5, 12, 27), usecs(5203)).endOfMonth == + SysTime(DateTime(2000, 2, 29, 23, 59, 59), hnsecs(9_999_999))); + + assert(SysTime(DateTime(2000, 6, 4, 12, 22, 9), hnsecs(12345)).endOfMonth == + SysTime(DateTime(2000, 6, 30, 23, 59, 59), hnsecs(9_999_999))); + } + + @safe unittest + { + // Test A.D. + assert(SysTime(Date(1999, 1, 1)).endOfMonth == SysTime(DateTime(1999, 1, 31, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(1999, 2, 1)).endOfMonth == SysTime(DateTime(1999, 2, 28, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(2000, 2, 1)).endOfMonth == SysTime(DateTime(2000, 2, 29, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(1999, 3, 1)).endOfMonth == SysTime(DateTime(1999, 3, 31, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(1999, 4, 1)).endOfMonth == SysTime(DateTime(1999, 4, 30, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(1999, 5, 1)).endOfMonth == SysTime(DateTime(1999, 5, 31, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(1999, 6, 1)).endOfMonth == SysTime(DateTime(1999, 6, 30, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(1999, 7, 1)).endOfMonth == SysTime(DateTime(1999, 7, 31, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(1999, 8, 1)).endOfMonth == SysTime(DateTime(1999, 8, 31, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(1999, 9, 1)).endOfMonth == SysTime(DateTime(1999, 9, 30, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(1999, 10, 1)).endOfMonth == SysTime(DateTime(1999, 10, 31, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(1999, 11, 1)).endOfMonth == SysTime(DateTime(1999, 11, 30, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(1999, 12, 1)).endOfMonth == SysTime(DateTime(1999, 12, 31, 23, 59, 59), hnsecs(9_999_999))); + + // Test B.C. + assert(SysTime(Date(-1999, 1, 1)).endOfMonth == SysTime(DateTime(-1999, 1, 31, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(-1999, 2, 1)).endOfMonth == SysTime(DateTime(-1999, 2, 28, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(-2000, 2, 1)).endOfMonth == SysTime(DateTime(-2000, 2, 29, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(-1999, 3, 1)).endOfMonth == SysTime(DateTime(-1999, 3, 31, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(-1999, 4, 1)).endOfMonth == SysTime(DateTime(-1999, 4, 30, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(-1999, 5, 1)).endOfMonth == SysTime(DateTime(-1999, 5, 31, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(-1999, 6, 1)).endOfMonth == SysTime(DateTime(-1999, 6, 30, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(-1999, 7, 1)).endOfMonth == SysTime(DateTime(-1999, 7, 31, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(-1999, 8, 1)).endOfMonth == SysTime(DateTime(-1999, 8, 31, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(-1999, 9, 1)).endOfMonth == SysTime(DateTime(-1999, 9, 30, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(-1999, 10, 1)).endOfMonth == + SysTime(DateTime(-1999, 10, 31, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(-1999, 11, 1)).endOfMonth == + SysTime(DateTime(-1999, 11, 30, 23, 59, 59), hnsecs(9_999_999))); + assert(SysTime(Date(-1999, 12, 1)).endOfMonth == + SysTime(DateTime(-1999, 12, 31, 23, 59, 59), hnsecs(9_999_999))); + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cst.endOfMonth == SysTime(DateTime(1999, 7, 31, 23, 59, 59), hnsecs(9_999_999))); + //assert(ist.endOfMonth == SysTime(DateTime(1999, 7, 31, 23, 59, 59), hnsecs(9_999_999))); + } + + + /++ + The last day in the month that this $(LREF SysTime) is in. + +/ + @property ubyte daysInMonth() @safe const nothrow + { + return Date(dayOfGregorianCal).daysInMonth; + } + + /// + @safe unittest + { + import std.datetime.date : DateTime; + + assert(SysTime(DateTime(1999, 1, 6, 0, 0, 0)).daysInMonth == 31); + assert(SysTime(DateTime(1999, 2, 7, 19, 30, 0)).daysInMonth == 28); + assert(SysTime(DateTime(2000, 2, 7, 5, 12, 27)).daysInMonth == 29); + assert(SysTime(DateTime(2000, 6, 4, 12, 22, 9)).daysInMonth == 30); + } + + @safe unittest + { + // Test A.D. + assert(SysTime(DateTime(1999, 1, 1, 12, 1, 13)).daysInMonth == 31); + assert(SysTime(DateTime(1999, 2, 1, 17, 13, 12)).daysInMonth == 28); + assert(SysTime(DateTime(2000, 2, 1, 13, 2, 12)).daysInMonth == 29); + assert(SysTime(DateTime(1999, 3, 1, 12, 13, 12)).daysInMonth == 31); + assert(SysTime(DateTime(1999, 4, 1, 12, 6, 13)).daysInMonth == 30); + assert(SysTime(DateTime(1999, 5, 1, 15, 13, 12)).daysInMonth == 31); + assert(SysTime(DateTime(1999, 6, 1, 13, 7, 12)).daysInMonth == 30); + assert(SysTime(DateTime(1999, 7, 1, 12, 13, 17)).daysInMonth == 31); + assert(SysTime(DateTime(1999, 8, 1, 12, 3, 13)).daysInMonth == 31); + assert(SysTime(DateTime(1999, 9, 1, 12, 13, 12)).daysInMonth == 30); + assert(SysTime(DateTime(1999, 10, 1, 13, 19, 12)).daysInMonth == 31); + assert(SysTime(DateTime(1999, 11, 1, 12, 13, 17)).daysInMonth == 30); + assert(SysTime(DateTime(1999, 12, 1, 12, 52, 13)).daysInMonth == 31); + + // Test B.C. + assert(SysTime(DateTime(-1999, 1, 1, 12, 1, 13)).daysInMonth == 31); + assert(SysTime(DateTime(-1999, 2, 1, 7, 13, 12)).daysInMonth == 28); + assert(SysTime(DateTime(-2000, 2, 1, 13, 2, 12)).daysInMonth == 29); + assert(SysTime(DateTime(-1999, 3, 1, 12, 13, 12)).daysInMonth == 31); + assert(SysTime(DateTime(-1999, 4, 1, 12, 6, 13)).daysInMonth == 30); + assert(SysTime(DateTime(-1999, 5, 1, 5, 13, 12)).daysInMonth == 31); + assert(SysTime(DateTime(-1999, 6, 1, 13, 7, 12)).daysInMonth == 30); + assert(SysTime(DateTime(-1999, 7, 1, 12, 13, 17)).daysInMonth == 31); + assert(SysTime(DateTime(-1999, 8, 1, 12, 3, 13)).daysInMonth == 31); + assert(SysTime(DateTime(-1999, 9, 1, 12, 13, 12)).daysInMonth == 30); + assert(SysTime(DateTime(-1999, 10, 1, 13, 19, 12)).daysInMonth == 31); + assert(SysTime(DateTime(-1999, 11, 1, 12, 13, 17)).daysInMonth == 30); + assert(SysTime(DateTime(-1999, 12, 1, 12, 52, 13)).daysInMonth == 31); + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cst.daysInMonth == 31); + //assert(ist.daysInMonth == 31); + } + + + /++ + Whether the current year is a date in A.D. + +/ + @property bool isAD() @safe const nothrow + { + return adjTime >= 0; + } + + /// + @safe unittest + { + import std.datetime.date : DateTime; + + assert(SysTime(DateTime(1, 1, 1, 12, 7, 0)).isAD); + assert(SysTime(DateTime(2010, 12, 31, 0, 0, 0)).isAD); + assert(!SysTime(DateTime(0, 12, 31, 23, 59, 59)).isAD); + assert(!SysTime(DateTime(-2010, 1, 1, 2, 2, 2)).isAD); + } + + @safe unittest + { + assert(SysTime(DateTime(2010, 7, 4, 12, 0, 9)).isAD); + assert(SysTime(DateTime(1, 1, 1, 0, 0, 0)).isAD); + assert(!SysTime(DateTime(0, 12, 31, 23, 59, 59)).isAD); + assert(!SysTime(DateTime(0, 1, 1, 23, 59, 59)).isAD); + assert(!SysTime(DateTime(-1, 1, 1, 23 ,59 ,59)).isAD); + assert(!SysTime(DateTime(-2010, 7, 4, 12, 2, 2)).isAD); + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cst.isAD); + //assert(ist.isAD); + } + + + /++ + The $(HTTP en.wikipedia.org/wiki/Julian_day, Julian day) + for this $(LREF SysTime) at the given time. For example, + prior to noon, 1996-03-31 would be the Julian day number 2_450_173, so + this function returns 2_450_173, while from noon onward, the Julian + day number would be 2_450_174, so this function returns 2_450_174. + +/ + @property long julianDay() @safe const nothrow + { + immutable jd = dayOfGregorianCal + 1_721_425; + return hour < 12 ? jd - 1 : jd; + } + + @safe unittest + { + assert(SysTime(DateTime(-4713, 11, 24, 0, 0, 0)).julianDay == -1); + assert(SysTime(DateTime(-4713, 11, 24, 12, 0, 0)).julianDay == 0); + + assert(SysTime(DateTime(0, 12, 31, 0, 0, 0)).julianDay == 1_721_424); + assert(SysTime(DateTime(0, 12, 31, 12, 0, 0)).julianDay == 1_721_425); + + assert(SysTime(DateTime(1, 1, 1, 0, 0, 0)).julianDay == 1_721_425); + assert(SysTime(DateTime(1, 1, 1, 12, 0, 0)).julianDay == 1_721_426); + + assert(SysTime(DateTime(1582, 10, 15, 0, 0, 0)).julianDay == 2_299_160); + assert(SysTime(DateTime(1582, 10, 15, 12, 0, 0)).julianDay == 2_299_161); + + assert(SysTime(DateTime(1858, 11, 17, 0, 0, 0)).julianDay == 2_400_000); + assert(SysTime(DateTime(1858, 11, 17, 12, 0, 0)).julianDay == 2_400_001); + + assert(SysTime(DateTime(1982, 1, 4, 0, 0, 0)).julianDay == 2_444_973); + assert(SysTime(DateTime(1982, 1, 4, 12, 0, 0)).julianDay == 2_444_974); + + assert(SysTime(DateTime(1996, 3, 31, 0, 0, 0)).julianDay == 2_450_173); + assert(SysTime(DateTime(1996, 3, 31, 12, 0, 0)).julianDay == 2_450_174); + + assert(SysTime(DateTime(2010, 8, 24, 0, 0, 0)).julianDay == 2_455_432); + assert(SysTime(DateTime(2010, 8, 24, 12, 0, 0)).julianDay == 2_455_433); + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cst.julianDay == 2_451_366); + //assert(ist.julianDay == 2_451_366); + } + + + /++ + The modified $(HTTP en.wikipedia.org/wiki/Julian_day, Julian day) for + any time on this date (since, the modified Julian day changes at + midnight). + +/ + @property long modJulianDay() @safe const nothrow + { + return dayOfGregorianCal + 1_721_425 - 2_400_001; + } + + @safe unittest + { + assert(SysTime(DateTime(1858, 11, 17, 0, 0, 0)).modJulianDay == 0); + assert(SysTime(DateTime(1858, 11, 17, 12, 0, 0)).modJulianDay == 0); + + assert(SysTime(DateTime(2010, 8, 24, 0, 0, 0)).modJulianDay == 55_432); + assert(SysTime(DateTime(2010, 8, 24, 12, 0, 0)).modJulianDay == 55_432); + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cst.modJulianDay == 51_365); + //assert(ist.modJulianDay == 51_365); + } + + + /++ + Returns a $(REF Date,std,datetime,date) equivalent to this $(LREF SysTime). + +/ + Date opCast(T)() @safe const nothrow + if (is(Unqual!T == Date)) + { + return Date(dayOfGregorianCal); + } + + @safe unittest + { + assert(cast(Date) SysTime(Date(1999, 7, 6)) == Date(1999, 7, 6)); + assert(cast(Date) SysTime(Date(2000, 12, 31)) == Date(2000, 12, 31)); + assert(cast(Date) SysTime(Date(2001, 1, 1)) == Date(2001, 1, 1)); + + assert(cast(Date) SysTime(DateTime(1999, 7, 6, 12, 10, 9)) == Date(1999, 7, 6)); + assert(cast(Date) SysTime(DateTime(2000, 12, 31, 13, 11, 10)) == Date(2000, 12, 31)); + assert(cast(Date) SysTime(DateTime(2001, 1, 1, 14, 12, 11)) == Date(2001, 1, 1)); + + assert(cast(Date) SysTime(Date(-1999, 7, 6)) == Date(-1999, 7, 6)); + assert(cast(Date) SysTime(Date(-2000, 12, 31)) == Date(-2000, 12, 31)); + assert(cast(Date) SysTime(Date(-2001, 1, 1)) == Date(-2001, 1, 1)); + + assert(cast(Date) SysTime(DateTime(-1999, 7, 6, 12, 10, 9)) == Date(-1999, 7, 6)); + assert(cast(Date) SysTime(DateTime(-2000, 12, 31, 13, 11, 10)) == Date(-2000, 12, 31)); + assert(cast(Date) SysTime(DateTime(-2001, 1, 1, 14, 12, 11)) == Date(-2001, 1, 1)); + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cast(Date) cst != Date.init); + //assert(cast(Date) ist != Date.init); + } + + + /++ + Returns a $(REF DateTime,std,datetime,date) equivalent to this + $(LREF SysTime). + +/ + DateTime opCast(T)() @safe const nothrow + if (is(Unqual!T == DateTime)) + { + try + { + auto hnsecs = adjTime; + auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; + + if (hnsecs < 0) + { + hnsecs += convert!("hours", "hnsecs")(24); + --days; + } + + immutable hour = splitUnitsFromHNSecs!"hours"(hnsecs); + immutable minute = splitUnitsFromHNSecs!"minutes"(hnsecs); + immutable second = getUnitsFromHNSecs!"seconds"(hnsecs); + + return DateTime(Date(cast(int) days), TimeOfDay(cast(int) hour, cast(int) minute, cast(int) second)); + } + catch (Exception e) + assert(0, "Either DateTime's constructor or TimeOfDay's constructor threw."); + } + + @safe unittest + { + assert(cast(DateTime) SysTime(DateTime(1, 1, 6, 7, 12, 22)) == DateTime(1, 1, 6, 7, 12, 22)); + assert(cast(DateTime) SysTime(DateTime(1, 1, 6, 7, 12, 22), msecs(22)) == DateTime(1, 1, 6, 7, 12, 22)); + assert(cast(DateTime) SysTime(Date(1999, 7, 6)) == DateTime(1999, 7, 6, 0, 0, 0)); + assert(cast(DateTime) SysTime(Date(2000, 12, 31)) == DateTime(2000, 12, 31, 0, 0, 0)); + assert(cast(DateTime) SysTime(Date(2001, 1, 1)) == DateTime(2001, 1, 1, 0, 0, 0)); + + assert(cast(DateTime) SysTime(DateTime(1999, 7, 6, 12, 10, 9)) == DateTime(1999, 7, 6, 12, 10, 9)); + assert(cast(DateTime) SysTime(DateTime(2000, 12, 31, 13, 11, 10)) == DateTime(2000, 12, 31, 13, 11, 10)); + assert(cast(DateTime) SysTime(DateTime(2001, 1, 1, 14, 12, 11)) == DateTime(2001, 1, 1, 14, 12, 11)); + + assert(cast(DateTime) SysTime(DateTime(-1, 1, 6, 7, 12, 22)) == DateTime(-1, 1, 6, 7, 12, 22)); + assert(cast(DateTime) SysTime(DateTime(-1, 1, 6, 7, 12, 22), msecs(22)) == DateTime(-1, 1, 6, 7, 12, 22)); + assert(cast(DateTime) SysTime(Date(-1999, 7, 6)) == DateTime(-1999, 7, 6, 0, 0, 0)); + assert(cast(DateTime) SysTime(Date(-2000, 12, 31)) == DateTime(-2000, 12, 31, 0, 0, 0)); + assert(cast(DateTime) SysTime(Date(-2001, 1, 1)) == DateTime(-2001, 1, 1, 0, 0, 0)); + + assert(cast(DateTime) SysTime(DateTime(-1999, 7, 6, 12, 10, 9)) == DateTime(-1999, 7, 6, 12, 10, 9)); + assert(cast(DateTime) SysTime(DateTime(-2000, 12, 31, 13, 11, 10)) == DateTime(-2000, 12, 31, 13, 11, 10)); + assert(cast(DateTime) SysTime(DateTime(-2001, 1, 1, 14, 12, 11)) == DateTime(-2001, 1, 1, 14, 12, 11)); + + assert(cast(DateTime) SysTime(DateTime(2011, 1, 13, 8, 17, 2), msecs(296), LocalTime()) == + DateTime(2011, 1, 13, 8, 17, 2)); + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cast(DateTime) cst != DateTime.init); + //assert(cast(DateTime) ist != DateTime.init); + } + + + /++ + Returns a $(REF TimeOfDay,std,datetime,date) equivalent to this + $(LREF SysTime). + +/ + TimeOfDay opCast(T)() @safe const nothrow + if (is(Unqual!T == TimeOfDay)) + { + try + { + auto hnsecs = adjTime; + hnsecs = removeUnitsFromHNSecs!"days"(hnsecs); + + if (hnsecs < 0) + hnsecs += convert!("hours", "hnsecs")(24); + + immutable hour = splitUnitsFromHNSecs!"hours"(hnsecs); + immutable minute = splitUnitsFromHNSecs!"minutes"(hnsecs); + immutable second = getUnitsFromHNSecs!"seconds"(hnsecs); + + return TimeOfDay(cast(int) hour, cast(int) minute, cast(int) second); + } + catch (Exception e) + assert(0, "TimeOfDay's constructor threw."); + } + + @safe unittest + { + assert(cast(TimeOfDay) SysTime(Date(1999, 7, 6)) == TimeOfDay(0, 0, 0)); + assert(cast(TimeOfDay) SysTime(Date(2000, 12, 31)) == TimeOfDay(0, 0, 0)); + assert(cast(TimeOfDay) SysTime(Date(2001, 1, 1)) == TimeOfDay(0, 0, 0)); + + assert(cast(TimeOfDay) SysTime(DateTime(1999, 7, 6, 12, 10, 9)) == TimeOfDay(12, 10, 9)); + assert(cast(TimeOfDay) SysTime(DateTime(2000, 12, 31, 13, 11, 10)) == TimeOfDay(13, 11, 10)); + assert(cast(TimeOfDay) SysTime(DateTime(2001, 1, 1, 14, 12, 11)) == TimeOfDay(14, 12, 11)); + + assert(cast(TimeOfDay) SysTime(Date(-1999, 7, 6)) == TimeOfDay(0, 0, 0)); + assert(cast(TimeOfDay) SysTime(Date(-2000, 12, 31)) == TimeOfDay(0, 0, 0)); + assert(cast(TimeOfDay) SysTime(Date(-2001, 1, 1)) == TimeOfDay(0, 0, 0)); + + assert(cast(TimeOfDay) SysTime(DateTime(-1999, 7, 6, 12, 10, 9)) == TimeOfDay(12, 10, 9)); + assert(cast(TimeOfDay) SysTime(DateTime(-2000, 12, 31, 13, 11, 10)) == TimeOfDay(13, 11, 10)); + assert(cast(TimeOfDay) SysTime(DateTime(-2001, 1, 1, 14, 12, 11)) == TimeOfDay(14, 12, 11)); + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cast(TimeOfDay) cst != TimeOfDay.init); + //assert(cast(TimeOfDay) ist != TimeOfDay.init); + } + + + // Temporary hack until bug http://d.puremagic.com/issues/show_bug.cgi?id=4867 is fixed. + // This allows assignment from const(SysTime) to SysTime. + // It may be a good idea to keep it though, since casting from a type to itself + // should be allowed, and it doesn't work without this opCast() since opCast() + // has already been defined for other types. + SysTime opCast(T)() @safe const pure nothrow + if (is(Unqual!T == SysTime)) + { + return SysTime(_stdTime, _timezone); + } + + + /++ + Converts this $(LREF SysTime) to a string with the format + YYYYMMDDTHHMMSS.FFFFFFFTZ (where F is fractional seconds and TZ is time + zone). + + Note that the number of digits in the fractional seconds varies with the + number of fractional seconds. It's a maximum of 7 (which would be + hnsecs), but only has as many as are necessary to hold the correct value + (so no trailing zeroes), and if there are no fractional seconds, then + there is no decimal point. + + If this $(LREF SysTime)'s time zone is + $(REF LocalTime,std,datetime,timezone), then TZ is empty. If its time + zone is $(D UTC), then it is "Z". Otherwise, it is the offset from UTC + (e.g. +0100 or -0700). Note that the offset from UTC is $(I not) enough + to uniquely identify the time zone. + + Time zone offsets will be in the form +HHMM or -HHMM. + + $(RED Warning: + Previously, toISOString did the same as $(LREF toISOExtString) and + generated +HH:MM or -HH:MM for the time zone when it was not + $(REF LocalTime,std,datetime,timezone) or + $(REF UTC,std,datetime,timezone), which is not in conformance with + ISO 8601 for the non-extended string format. This has now been + fixed. However, for now, fromISOString will continue to accept the + extended format for the time zone so that any code which has been + writing out the result of toISOString to read in later will continue + to work. The current behavior will be kept until July 2019 at which + point, fromISOString will be fixed to be standards compliant.) + +/ + string toISOString() @safe const nothrow + { + try + { + immutable adjustedTime = adjTime; + long hnsecs = adjustedTime; + + auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; + + if (hnsecs < 0) + { + hnsecs += convert!("hours", "hnsecs")(24); + --days; + } + + auto hour = splitUnitsFromHNSecs!"hours"(hnsecs); + auto minute = splitUnitsFromHNSecs!"minutes"(hnsecs); + auto second = splitUnitsFromHNSecs!"seconds"(hnsecs); + + auto dateTime = DateTime(Date(cast(int) days), TimeOfDay(cast(int) hour, + cast(int) minute, cast(int) second)); + auto fracSecStr = fracSecsToISOString(cast(int) hnsecs); + + if (_timezone is LocalTime()) + return dateTime.toISOString() ~ fracSecStr; + + if (_timezone is UTC()) + return dateTime.toISOString() ~ fracSecStr ~ "Z"; + + immutable utcOffset = dur!"hnsecs"(adjustedTime - stdTime); + + return format("%s%s%s", + dateTime.toISOString(), + fracSecStr, + SimpleTimeZone.toISOExtString(utcOffset)); + } + catch (Exception e) + assert(0, "format() threw."); + } + + /// + @safe unittest + { + import core.time : msecs, hnsecs; + import std.datetime.date : DateTime; + + assert(SysTime(DateTime(2010, 7, 4, 7, 6, 12)).toISOString() == + "20100704T070612"); + + assert(SysTime(DateTime(1998, 12, 25, 2, 15, 0), msecs(24)).toISOString() == + "19981225T021500.024"); + + assert(SysTime(DateTime(0, 1, 5, 23, 9, 59)).toISOString() == + "00000105T230959"); + + assert(SysTime(DateTime(-4, 1, 5, 0, 0, 2), hnsecs(520_920)).toISOString() == + "-00040105T000002.052092"); + } + + @safe unittest + { + // Test A.D. + assert(SysTime(DateTime.init, UTC()).toISOString() == "00010101T000000Z"); + assert(SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1), UTC()).toISOString() == "00010101T000000.0000001Z"); + + assert(SysTime(DateTime(9, 12, 4, 0, 0, 0)).toISOString() == "00091204T000000"); + assert(SysTime(DateTime(99, 12, 4, 5, 6, 12)).toISOString() == "00991204T050612"); + assert(SysTime(DateTime(999, 12, 4, 13, 44, 59)).toISOString() == "09991204T134459"); + assert(SysTime(DateTime(9999, 7, 4, 23, 59, 59)).toISOString() == "99990704T235959"); + assert(SysTime(DateTime(10000, 10, 20, 1, 1, 1)).toISOString() == "+100001020T010101"); + + assert(SysTime(DateTime(9, 12, 4, 0, 0, 0), msecs(42)).toISOString() == "00091204T000000.042"); + assert(SysTime(DateTime(99, 12, 4, 5, 6, 12), msecs(100)).toISOString() == "00991204T050612.1"); + assert(SysTime(DateTime(999, 12, 4, 13, 44, 59), usecs(45020)).toISOString() == "09991204T134459.04502"); + assert(SysTime(DateTime(9999, 7, 4, 23, 59, 59), hnsecs(12)).toISOString() == "99990704T235959.0000012"); + assert(SysTime(DateTime(10000, 10, 20, 1, 1, 1), hnsecs(507890)).toISOString() == "+100001020T010101.050789"); + + assert(SysTime(DateTime(2012, 12, 21, 12, 12, 12), + new immutable SimpleTimeZone(dur!"minutes"(-360))).toISOString() == + "20121221T121212-06:00"); + + assert(SysTime(DateTime(2012, 12, 21, 12, 12, 12), + new immutable SimpleTimeZone(dur!"minutes"(420))).toISOString() == + "20121221T121212+07:00"); + + // Test B.C. + assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC()).toISOString() == + "00001231T235959.9999999Z"); + assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(1), UTC()).toISOString() == "00001231T235959.0000001Z"); + assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), UTC()).toISOString() == "00001231T235959Z"); + + assert(SysTime(DateTime(0, 12, 4, 0, 12, 4)).toISOString() == "00001204T001204"); + assert(SysTime(DateTime(-9, 12, 4, 0, 0, 0)).toISOString() == "-00091204T000000"); + assert(SysTime(DateTime(-99, 12, 4, 5, 6, 12)).toISOString() == "-00991204T050612"); + assert(SysTime(DateTime(-999, 12, 4, 13, 44, 59)).toISOString() == "-09991204T134459"); + assert(SysTime(DateTime(-9999, 7, 4, 23, 59, 59)).toISOString() == "-99990704T235959"); + assert(SysTime(DateTime(-10000, 10, 20, 1, 1, 1)).toISOString() == "-100001020T010101"); + + assert(SysTime(DateTime(0, 12, 4, 0, 0, 0), msecs(7)).toISOString() == "00001204T000000.007"); + assert(SysTime(DateTime(-9, 12, 4, 0, 0, 0), msecs(42)).toISOString() == "-00091204T000000.042"); + assert(SysTime(DateTime(-99, 12, 4, 5, 6, 12), msecs(100)).toISOString() == "-00991204T050612.1"); + assert(SysTime(DateTime(-999, 12, 4, 13, 44, 59), usecs(45020)).toISOString() == "-09991204T134459.04502"); + assert(SysTime(DateTime(-9999, 7, 4, 23, 59, 59), hnsecs(12)).toISOString() == "-99990704T235959.0000012"); + assert(SysTime(DateTime(-10000, 10, 20, 1, 1, 1), hnsecs(507890)).toISOString() == "-100001020T010101.050789"); + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cast(TimeOfDay) cst != TimeOfDay.init); + //assert(cast(TimeOfDay) ist != TimeOfDay.init); + } + + + + /++ + Converts this $(LREF SysTime) to a string with the format + YYYY-MM-DDTHH:MM:SS.FFFFFFFTZ (where F is fractional seconds and TZ + is the time zone). + + Note that the number of digits in the fractional seconds varies with the + number of fractional seconds. It's a maximum of 7 (which would be + hnsecs), but only has as many as are necessary to hold the correct value + (so no trailing zeroes), and if there are no fractional seconds, then + there is no decimal point. + + If this $(LREF SysTime)'s time zone is + $(REF LocalTime,std,datetime,timezone), then TZ is empty. If its time + zone is $(D UTC), then it is "Z". Otherwise, it is the offset from UTC + (e.g. +01:00 or -07:00). Note that the offset from UTC is $(I not) + enough to uniquely identify the time zone. + + Time zone offsets will be in the form +HH:MM or -HH:MM. + +/ + string toISOExtString() @safe const nothrow + { + try + { + immutable adjustedTime = adjTime; + long hnsecs = adjustedTime; + + auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; + + if (hnsecs < 0) + { + hnsecs += convert!("hours", "hnsecs")(24); + --days; + } + + auto hour = splitUnitsFromHNSecs!"hours"(hnsecs); + auto minute = splitUnitsFromHNSecs!"minutes"(hnsecs); + auto second = splitUnitsFromHNSecs!"seconds"(hnsecs); + + auto dateTime = DateTime(Date(cast(int) days), TimeOfDay(cast(int) hour, + cast(int) minute, cast(int) second)); + auto fracSecStr = fracSecsToISOString(cast(int) hnsecs); + + if (_timezone is LocalTime()) + return dateTime.toISOExtString() ~ fracSecStr; + + if (_timezone is UTC()) + return dateTime.toISOExtString() ~ fracSecStr ~ "Z"; + + immutable utcOffset = dur!"hnsecs"(adjustedTime - stdTime); + + return format("%s%s%s", + dateTime.toISOExtString(), + fracSecStr, + SimpleTimeZone.toISOExtString(utcOffset)); + } + catch (Exception e) + assert(0, "format() threw."); + } + + /// + @safe unittest + { + import core.time : msecs, hnsecs; + import std.datetime.date : DateTime; + + assert(SysTime(DateTime(2010, 7, 4, 7, 6, 12)).toISOExtString() == + "2010-07-04T07:06:12"); + + assert(SysTime(DateTime(1998, 12, 25, 2, 15, 0), msecs(24)).toISOExtString() == + "1998-12-25T02:15:00.024"); + + assert(SysTime(DateTime(0, 1, 5, 23, 9, 59)).toISOExtString() == + "0000-01-05T23:09:59"); + + assert(SysTime(DateTime(-4, 1, 5, 0, 0, 2), hnsecs(520_920)).toISOExtString() == + "-0004-01-05T00:00:02.052092"); + } + + @safe unittest + { + // Test A.D. + assert(SysTime(DateTime.init, UTC()).toISOExtString() == "0001-01-01T00:00:00Z"); + assert(SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1), UTC()).toISOExtString() == + "0001-01-01T00:00:00.0000001Z"); + + assert(SysTime(DateTime(9, 12, 4, 0, 0, 0)).toISOExtString() == "0009-12-04T00:00:00"); + assert(SysTime(DateTime(99, 12, 4, 5, 6, 12)).toISOExtString() == "0099-12-04T05:06:12"); + assert(SysTime(DateTime(999, 12, 4, 13, 44, 59)).toISOExtString() == "0999-12-04T13:44:59"); + assert(SysTime(DateTime(9999, 7, 4, 23, 59, 59)).toISOExtString() == "9999-07-04T23:59:59"); + assert(SysTime(DateTime(10000, 10, 20, 1, 1, 1)).toISOExtString() == "+10000-10-20T01:01:01"); + + assert(SysTime(DateTime(9, 12, 4, 0, 0, 0), msecs(42)).toISOExtString() == "0009-12-04T00:00:00.042"); + assert(SysTime(DateTime(99, 12, 4, 5, 6, 12), msecs(100)).toISOExtString() == "0099-12-04T05:06:12.1"); + assert(SysTime(DateTime(999, 12, 4, 13, 44, 59), usecs(45020)).toISOExtString() == "0999-12-04T13:44:59.04502"); + assert(SysTime(DateTime(9999, 7, 4, 23, 59, 59), hnsecs(12)).toISOExtString() == "9999-07-04T23:59:59.0000012"); + assert(SysTime(DateTime(10000, 10, 20, 1, 1, 1), hnsecs(507890)).toISOExtString() == + "+10000-10-20T01:01:01.050789"); + + assert(SysTime(DateTime(2012, 12, 21, 12, 12, 12), + new immutable SimpleTimeZone(dur!"minutes"(-360))).toISOExtString() == + "2012-12-21T12:12:12-06:00"); + + assert(SysTime(DateTime(2012, 12, 21, 12, 12, 12), + new immutable SimpleTimeZone(dur!"minutes"(420))).toISOExtString() == + "2012-12-21T12:12:12+07:00"); + + // Test B.C. + assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC()).toISOExtString() == + "0000-12-31T23:59:59.9999999Z"); + assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(1), UTC()).toISOExtString() == + "0000-12-31T23:59:59.0000001Z"); + assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), UTC()).toISOExtString() == "0000-12-31T23:59:59Z"); + + assert(SysTime(DateTime(0, 12, 4, 0, 12, 4)).toISOExtString() == "0000-12-04T00:12:04"); + assert(SysTime(DateTime(-9, 12, 4, 0, 0, 0)).toISOExtString() == "-0009-12-04T00:00:00"); + assert(SysTime(DateTime(-99, 12, 4, 5, 6, 12)).toISOExtString() == "-0099-12-04T05:06:12"); + assert(SysTime(DateTime(-999, 12, 4, 13, 44, 59)).toISOExtString() == "-0999-12-04T13:44:59"); + assert(SysTime(DateTime(-9999, 7, 4, 23, 59, 59)).toISOExtString() == "-9999-07-04T23:59:59"); + assert(SysTime(DateTime(-10000, 10, 20, 1, 1, 1)).toISOExtString() == "-10000-10-20T01:01:01"); + + assert(SysTime(DateTime(0, 12, 4, 0, 0, 0), msecs(7)).toISOExtString() == "0000-12-04T00:00:00.007"); + assert(SysTime(DateTime(-9, 12, 4, 0, 0, 0), msecs(42)).toISOExtString() == "-0009-12-04T00:00:00.042"); + assert(SysTime(DateTime(-99, 12, 4, 5, 6, 12), msecs(100)).toISOExtString() == "-0099-12-04T05:06:12.1"); + assert(SysTime(DateTime(-999, 12, 4, 13, 44, 59), usecs(45020)).toISOExtString() == + "-0999-12-04T13:44:59.04502"); + assert(SysTime(DateTime(-9999, 7, 4, 23, 59, 59), hnsecs(12)).toISOExtString() == + "-9999-07-04T23:59:59.0000012"); + assert(SysTime(DateTime(-10000, 10, 20, 1, 1, 1), hnsecs(507890)).toISOExtString() == + "-10000-10-20T01:01:01.050789"); + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cast(TimeOfDay) cst != TimeOfDay.init); + //assert(cast(TimeOfDay) ist != TimeOfDay.init); + } + + /++ + Converts this $(LREF SysTime) to a string with the format + YYYY-Mon-DD HH:MM:SS.FFFFFFFTZ (where F is fractional seconds and TZ + is the time zone). + + Note that the number of digits in the fractional seconds varies with the + number of fractional seconds. It's a maximum of 7 (which would be + hnsecs), but only has as many as are necessary to hold the correct value + (so no trailing zeroes), and if there are no fractional seconds, then + there is no decimal point. + + If this $(LREF SysTime)'s time zone is + $(REF LocalTime,std,datetime,timezone), then TZ is empty. If its time + zone is $(D UTC), then it is "Z". Otherwise, it is the offset from UTC + (e.g. +01:00 or -07:00). Note that the offset from UTC is $(I not) + enough to uniquely identify the time zone. + + Time zone offsets will be in the form +HH:MM or -HH:MM. + +/ + string toSimpleString() @safe const nothrow + { + try + { + immutable adjustedTime = adjTime; + long hnsecs = adjustedTime; + + auto days = splitUnitsFromHNSecs!"days"(hnsecs) + 1; + + if (hnsecs < 0) + { + hnsecs += convert!("hours", "hnsecs")(24); + --days; + } + + auto hour = splitUnitsFromHNSecs!"hours"(hnsecs); + auto minute = splitUnitsFromHNSecs!"minutes"(hnsecs); + auto second = splitUnitsFromHNSecs!"seconds"(hnsecs); + + auto dateTime = DateTime(Date(cast(int) days), TimeOfDay(cast(int) hour, + cast(int) minute, cast(int) second)); + auto fracSecStr = fracSecsToISOString(cast(int) hnsecs); + + if (_timezone is LocalTime()) + return dateTime.toSimpleString() ~ fracSecStr; + + if (_timezone is UTC()) + return dateTime.toSimpleString() ~ fracSecStr ~ "Z"; + + immutable utcOffset = dur!"hnsecs"(adjustedTime - stdTime); + + return format("%s%s%s", + dateTime.toSimpleString(), + fracSecStr, + SimpleTimeZone.toISOExtString(utcOffset)); + } + catch (Exception e) + assert(0, "format() threw."); + } + + /// + @safe unittest + { + import core.time : msecs, hnsecs; + import std.datetime.date : DateTime; + + assert(SysTime(DateTime(2010, 7, 4, 7, 6, 12)).toSimpleString() == + "2010-Jul-04 07:06:12"); + + assert(SysTime(DateTime(1998, 12, 25, 2, 15, 0), msecs(24)).toSimpleString() == + "1998-Dec-25 02:15:00.024"); + + assert(SysTime(DateTime(0, 1, 5, 23, 9, 59)).toSimpleString() == + "0000-Jan-05 23:09:59"); + + assert(SysTime(DateTime(-4, 1, 5, 0, 0, 2), hnsecs(520_920)).toSimpleString() == + "-0004-Jan-05 00:00:02.052092"); + } + + @safe unittest + { + // Test A.D. + assert(SysTime(DateTime.init, UTC()).toString() == "0001-Jan-01 00:00:00Z"); + assert(SysTime(DateTime(1, 1, 1, 0, 0, 0), hnsecs(1), UTC()).toString() == "0001-Jan-01 00:00:00.0000001Z"); + + assert(SysTime(DateTime(9, 12, 4, 0, 0, 0)).toSimpleString() == "0009-Dec-04 00:00:00"); + assert(SysTime(DateTime(99, 12, 4, 5, 6, 12)).toSimpleString() == "0099-Dec-04 05:06:12"); + assert(SysTime(DateTime(999, 12, 4, 13, 44, 59)).toSimpleString() == "0999-Dec-04 13:44:59"); + assert(SysTime(DateTime(9999, 7, 4, 23, 59, 59)).toSimpleString() == "9999-Jul-04 23:59:59"); + assert(SysTime(DateTime(10000, 10, 20, 1, 1, 1)).toSimpleString() == "+10000-Oct-20 01:01:01"); + + assert(SysTime(DateTime(9, 12, 4, 0, 0, 0), msecs(42)).toSimpleString() == "0009-Dec-04 00:00:00.042"); + assert(SysTime(DateTime(99, 12, 4, 5, 6, 12), msecs(100)).toSimpleString() == "0099-Dec-04 05:06:12.1"); + assert(SysTime(DateTime(999, 12, 4, 13, 44, 59), usecs(45020)).toSimpleString() == + "0999-Dec-04 13:44:59.04502"); + assert(SysTime(DateTime(9999, 7, 4, 23, 59, 59), hnsecs(12)).toSimpleString() == + "9999-Jul-04 23:59:59.0000012"); + assert(SysTime(DateTime(10000, 10, 20, 1, 1, 1), hnsecs(507890)).toSimpleString() == + "+10000-Oct-20 01:01:01.050789"); + + assert(SysTime(DateTime(2012, 12, 21, 12, 12, 12), + new immutable SimpleTimeZone(dur!"minutes"(-360))).toSimpleString() == + "2012-Dec-21 12:12:12-06:00"); + + assert(SysTime(DateTime(2012, 12, 21, 12, 12, 12), + new immutable SimpleTimeZone(dur!"minutes"(420))).toSimpleString() == + "2012-Dec-21 12:12:12+07:00"); + + // Test B.C. + assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC()).toSimpleString() == + "0000-Dec-31 23:59:59.9999999Z"); + assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), hnsecs(1), UTC()).toSimpleString() == + "0000-Dec-31 23:59:59.0000001Z"); + assert(SysTime(DateTime(0, 12, 31, 23, 59, 59), UTC()).toSimpleString() == "0000-Dec-31 23:59:59Z"); + + assert(SysTime(DateTime(0, 12, 4, 0, 12, 4)).toSimpleString() == "0000-Dec-04 00:12:04"); + assert(SysTime(DateTime(-9, 12, 4, 0, 0, 0)).toSimpleString() == "-0009-Dec-04 00:00:00"); + assert(SysTime(DateTime(-99, 12, 4, 5, 6, 12)).toSimpleString() == "-0099-Dec-04 05:06:12"); + assert(SysTime(DateTime(-999, 12, 4, 13, 44, 59)).toSimpleString() == "-0999-Dec-04 13:44:59"); + assert(SysTime(DateTime(-9999, 7, 4, 23, 59, 59)).toSimpleString() == "-9999-Jul-04 23:59:59"); + assert(SysTime(DateTime(-10000, 10, 20, 1, 1, 1)).toSimpleString() == "-10000-Oct-20 01:01:01"); + + assert(SysTime(DateTime(0, 12, 4, 0, 0, 0), msecs(7)).toSimpleString() == "0000-Dec-04 00:00:00.007"); + assert(SysTime(DateTime(-9, 12, 4, 0, 0, 0), msecs(42)).toSimpleString() == "-0009-Dec-04 00:00:00.042"); + assert(SysTime(DateTime(-99, 12, 4, 5, 6, 12), msecs(100)).toSimpleString() == "-0099-Dec-04 05:06:12.1"); + assert(SysTime(DateTime(-999, 12, 4, 13, 44, 59), usecs(45020)).toSimpleString() == + "-0999-Dec-04 13:44:59.04502"); + assert(SysTime(DateTime(-9999, 7, 4, 23, 59, 59), hnsecs(12)).toSimpleString() == + "-9999-Jul-04 23:59:59.0000012"); + assert(SysTime(DateTime(-10000, 10, 20, 1, 1, 1), hnsecs(507890)).toSimpleString() == + "-10000-Oct-20 01:01:01.050789"); + + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(cast(TimeOfDay) cst != TimeOfDay.init); + //assert(cast(TimeOfDay) ist != TimeOfDay.init); + } + + + /++ + Converts this $(LREF SysTime) to a string. + + This function exists to make it easy to convert a $(LREF SysTime) to a + string for code that does not care what the exact format is - just that + it presents the information in a clear manner. It also makes it easy to + simply convert a $(LREF SysTime) to a string when using functions such + as `to!string`, `format`, or `writeln` which use toString to convert + user-defined types. So, it is unlikely that much code will call + toString directly. + + The format of the string is purposefully unspecified, and code that + cares about the format of the string should use `toISOString`, + `toISOExtString`, `toSimpleString`, or some other custom formatting + function that explicitly generates the format that the code needs. The + reason is that the code is then clear about what format it's using, + making it less error-prone to maintain the code and interact with other + software that consumes the generated strings. It's for this same reason + that $(LREF SysTime) has no `fromString` function, whereas it does have + `fromISOString`, `fromISOExtString`, and `fromSimpleString`. + + The format returned by toString may or may not change in the future. + +/ + string toString() @safe const nothrow + { + return toSimpleString(); + } + + @safe unittest + { + auto st = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + const cst = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + //immutable ist = SysTime(DateTime(1999, 7, 6, 12, 30, 33)); + assert(st.toString()); + assert(cst.toString()); + //assert(ist.toString()); + } + + + /++ + Creates a $(LREF SysTime) from a string with the format + YYYYMMDDTHHMMSS.FFFFFFFTZ (where F is fractional seconds is the time + zone). Whitespace is stripped from the given string. + + The exact format is exactly as described in $(D toISOString) except that + trailing zeroes are permitted - including having fractional seconds with + all zeroes. However, a decimal point with nothing following it is + invalid. Also, while $(LREF toISOString) will never generate a string + with more than 7 digits in the fractional seconds (because that's the + limit with hecto-nanosecond precision), it will allow more than 7 digits + in order to read strings from other sources that have higher precision + (however, any digits beyond 7 will be truncated). + + If there is no time zone in the string, then + $(REF LocalTime,std,datetime,timezone) is used. If the time zone is "Z", + then $(D UTC) is used. Otherwise, a + $(REF SimpleTimeZone,std,datetime,timezone) which corresponds to the + given offset from UTC is used. To get the returned $(LREF SysTime) to be + a particular time zone, pass in that time zone and the $(LREF SysTime) + to be returned will be converted to that time zone (though it will still + be read in as whatever time zone is in its string). + + The accepted formats for time zone offsets are +HH, -HH, +HHMM, and + -HHMM. + + $(RED Warning: + Previously, $(LREF toISOString) did the same as + $(LREF toISOExtString) and generated +HH:MM or -HH:MM for the time + zone when it was not $(REF LocalTime,std,datetime,timezone) or + $(REF UTC,std,datetime,timezone), which is not in conformance with + ISO 8601 for the non-extended string format. This has now been + fixed. However, for now, fromISOString will continue to accept the + extended format for the time zone so that any code which has been + writing out the result of toISOString to read in later will continue + to work. The current behavior will be kept until July 2019 at which + point, fromISOString will be fixed to be standards compliant.) + + Params: + isoString = A string formatted in the ISO format for dates and times. + tz = The time zone to convert the given time to (no + conversion occurs if null). + + Throws: + $(REF DateTimeException,std,datetime,date) if the given string is + not in the ISO format or if the resulting $(LREF SysTime) would not + be valid. + +/ + static SysTime fromISOString(S)(in S isoString, immutable TimeZone tz = null) @safe + if (isSomeString!S) + { + import std.algorithm.searching : startsWith, find; + import std.conv : to; + import std.string : strip; + + auto dstr = to!dstring(strip(isoString)); + immutable skipFirst = dstr.startsWith('+', '-') != 0; + + auto found = (skipFirst ? dstr[1..$] : dstr).find('.', 'Z', '+', '-'); + auto dateTimeStr = dstr[0 .. $ - found[0].length]; + + dstring fracSecStr; + dstring zoneStr; + + if (found[1] != 0) + { + if (found[1] == 1) + { + auto foundTZ = found[0].find('Z', '+', '-'); + + if (foundTZ[1] != 0) + { + fracSecStr = found[0][0 .. $ - foundTZ[0].length]; + zoneStr = foundTZ[0]; + } + else + fracSecStr = found[0]; + } + else + zoneStr = found[0]; + } + + try + { + auto dateTime = DateTime.fromISOString(dateTimeStr); + auto fracSec = fracSecsFromISOString(fracSecStr); + Rebindable!(immutable TimeZone) parsedZone; + + if (zoneStr.empty) + parsedZone = LocalTime(); + else if (zoneStr == "Z") + parsedZone = UTC(); + else + { + try + parsedZone = SimpleTimeZone.fromISOString(zoneStr); + catch (DateTimeException dte) + parsedZone = SimpleTimeZone.fromISOExtString(zoneStr); + } + + auto retval = SysTime(dateTime, fracSec, parsedZone); + + if (tz !is null) + retval.timezone = tz; + + return retval; + } + catch (DateTimeException dte) + throw new DateTimeException(format("Invalid ISO String: %s", isoString)); + } + + /// + @safe unittest + { + import core.time : hours, msecs, usecs, hnsecs; + import std.datetime.date : DateTime; + import std.datetime.timezone : SimpleTimeZone, UTC; + + assert(SysTime.fromISOString("20100704T070612") == + SysTime(DateTime(2010, 7, 4, 7, 6, 12))); + + assert(SysTime.fromISOString("19981225T021500.007") == + SysTime(DateTime(1998, 12, 25, 2, 15, 0), msecs(7))); + + assert(SysTime.fromISOString("00000105T230959.00002") == + SysTime(DateTime(0, 1, 5, 23, 9, 59), usecs(20))); + + assert(SysTime.fromISOString("20130207T043937.000050392") == + SysTime(DateTime(2013, 2, 7, 4, 39, 37), hnsecs(503))); + + assert(SysTime.fromISOString("-00040105T000002") == + SysTime(DateTime(-4, 1, 5, 0, 0, 2))); + + assert(SysTime.fromISOString(" 20100704T070612 ") == + SysTime(DateTime(2010, 7, 4, 7, 6, 12))); + + assert(SysTime.fromISOString("20100704T070612Z") == + SysTime(DateTime(2010, 7, 4, 7, 6, 12), UTC())); + + assert(SysTime.fromISOString("20100704T070612-0800") == + SysTime(DateTime(2010, 7, 4, 7, 6, 12), + new immutable SimpleTimeZone(hours(-8)))); + + assert(SysTime.fromISOString("20100704T070612+0800") == + SysTime(DateTime(2010, 7, 4, 7, 6, 12), + new immutable SimpleTimeZone(hours(8)))); + } + + @safe unittest + { + foreach (str; ["", "20100704000000", "20100704 000000", "20100704t000000", + "20100704T000000.", "20100704T000000.A", "20100704T000000.Z", + "20100704T000000.0000000A", "20100704T000000.00000000A", + "20100704T000000+", "20100704T000000-", "20100704T000000:", + "20100704T000000-:", "20100704T000000+:", "20100704T000000-1:", + "20100704T000000+1:", "20100704T000000+1:0", + "20100704T000000-12.00", "20100704T000000+12.00", + "20100704T000000-8", "20100704T000000+8", + "20100704T000000-800", "20100704T000000+800", + "20100704T000000-080", "20100704T000000+080", + "20100704T000000-2400", "20100704T000000+2400", + "20100704T000000-1260", "20100704T000000+1260", + "20100704T000000.0-8", "20100704T000000.0+8", + "20100704T000000.0-800", "20100704T000000.0+800", + "20100704T000000.0-080", "20100704T000000.0+080", + "20100704T000000.0-2400", "20100704T000000.0+2400", + "20100704T000000.0-1260", "20100704T000000.0+1260", + "20100704T000000-8:00", "20100704T000000+8:00", + "20100704T000000-08:0", "20100704T000000+08:0", + "20100704T000000-24:00", "20100704T000000+24:00", + "20100704T000000-12:60", "20100704T000000+12:60", + "20100704T000000.0-8:00", "20100704T000000.0+8:00", + "20100704T000000.0-08:0", "20100704T000000.0+08:0", + "20100704T000000.0-24:00", "20100704T000000.0+24:00", + "20100704T000000.0-12:60", "20100704T000000.0+12:60", + "2010-07-0400:00:00", "2010-07-04 00:00:00", + "2010-07-04t00:00:00", "2010-07-04T00:00:00.", + "2010-Jul-0400:00:00", "2010-Jul-04 00:00:00", "2010-Jul-04t00:00:00", + "2010-Jul-04T00:00:00", "2010-Jul-04 00:00:00.", + "2010-12-22T172201", "2010-Dec-22 17:22:01"]) + { + assertThrown!DateTimeException(SysTime.fromISOString(str), format("[%s]", str)); + } + + static void test(string str, SysTime st, size_t line = __LINE__) + { + if (SysTime.fromISOString(str) != st) + throw new AssertError("unittest failure", __FILE__, line); + } + + test("20101222T172201", SysTime(DateTime(2010, 12, 22, 17, 22, 01))); + test("19990706T123033", SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + test("-19990706T123033", SysTime(DateTime(-1999, 7, 6, 12, 30, 33))); + test("+019990706T123033", SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + test("19990706T123033 ", SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + test(" 19990706T123033", SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + test(" 19990706T123033 ", SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + + test("19070707T121212.0", SysTime(DateTime(1907, 07, 07, 12, 12, 12))); + test("19070707T121212.0000000", SysTime(DateTime(1907, 07, 07, 12, 12, 12))); + test("19070707T121212.0000001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), hnsecs(1))); + test("20100704T000000.00000000", SysTime(Date(2010, 07, 04))); + test("20100704T000000.00000009", SysTime(Date(2010, 07, 04))); + test("20100704T000000.00000019", SysTime(DateTime(2010, 07, 04), hnsecs(1))); + test("19070707T121212.000001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), usecs(1))); + test("19070707T121212.0000010", SysTime(DateTime(1907, 07, 07, 12, 12, 12), usecs(1))); + test("19070707T121212.001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), msecs(1))); + test("19070707T121212.0010000", SysTime(DateTime(1907, 07, 07, 12, 12, 12), msecs(1))); + + auto west60 = new immutable SimpleTimeZone(hours(-1)); + auto west90 = new immutable SimpleTimeZone(minutes(-90)); + auto west480 = new immutable SimpleTimeZone(hours(-8)); + auto east60 = new immutable SimpleTimeZone(hours(1)); + auto east90 = new immutable SimpleTimeZone(minutes(90)); + auto east480 = new immutable SimpleTimeZone(hours(8)); + + test("20101222T172201Z", SysTime(DateTime(2010, 12, 22, 17, 22, 01), UTC())); + test("20101222T172201-0100", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west60)); + test("20101222T172201-01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west60)); + test("20101222T172201-0130", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west90)); + test("20101222T172201-0800", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west480)); + test("20101222T172201+0100", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60)); + test("20101222T172201+01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60)); + test("20101222T172201+0130", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90)); + test("20101222T172201+0800", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east480)); + + test("20101103T065106.57159Z", SysTime(DateTime(2010, 11, 3, 6, 51, 6), hnsecs(5715900), UTC())); + test("20101222T172201.23412Z", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(2_341_200), UTC())); + test("20101222T172201.23112-0100", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(2_311_200), west60)); + test("20101222T172201.45-01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(4_500_000), west60)); + test("20101222T172201.1-0130", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_000_000), west90)); + test("20101222T172201.55-0800", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(5_500_000), west480)); + test("20101222T172201.1234567+0100", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_234_567), east60)); + test("20101222T172201.0+01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60)); + test("20101222T172201.0000000+0130", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90)); + test("20101222T172201.45+0800", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(4_500_000), east480)); + + // @@@DEPRECATED_2019-07@@@ + // This isn't deprecated per se, but that text will make it so that it + // pops up when deprecations are moved along around July 2019. At that + // time, we will update fromISOString so that it is conformant with ISO + // 8601, and it will no longer accept ISO extended time zones (it does + // currently because of issue #15654 - toISOString used to incorrectly + // use the ISO extended time zone format). These tests will then start + // failing will need to be updated accordingly. Also, the notes about + // this issue in toISOString and fromISOString's documentation will need + // to be removed. + test("20101222T172201-01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west60)); + test("20101222T172201-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west90)); + test("20101222T172201-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west480)); + test("20101222T172201+01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60)); + test("20101222T172201+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90)); + test("20101222T172201+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east480)); + + test("20101222T172201.23112-01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(2_311_200), west60)); + test("20101222T172201.1-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_000_000), west90)); + test("20101222T172201.55-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(5_500_000), west480)); + test("20101222T172201.1234567+01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_234_567), east60)); + test("20101222T172201.0000000+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90)); + test("20101222T172201.45+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(4_500_000), east480)); + } + + // bug# 17801 + @safe unittest + { + import std.conv : to; + import std.meta : AliasSeq; + foreach (C; AliasSeq!(char, wchar, dchar)) + { + foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + { + assert(SysTime.fromISOString(to!S("20121221T141516Z")) == + SysTime(DateTime(2012, 12, 21, 14, 15, 16), UTC())); + } + } + } + + + /++ + Creates a $(LREF SysTime) from a string with the format + YYYY-MM-DDTHH:MM:SS.FFFFFFFTZ (where F is fractional seconds is the + time zone). Whitespace is stripped from the given string. + + The exact format is exactly as described in $(D toISOExtString) + except that trailing zeroes are permitted - including having fractional + seconds with all zeroes. However, a decimal point with nothing following + it is invalid. Also, while $(LREF toISOExtString) will never generate a + string with more than 7 digits in the fractional seconds (because that's + the limit with hecto-nanosecond precision), it will allow more than 7 + digits in order to read strings from other sources that have higher + precision (however, any digits beyond 7 will be truncated). + + If there is no time zone in the string, then + $(REF LocalTime,std,datetime,timezone) is used. If the time zone is "Z", + then $(D UTC) is used. Otherwise, a + $(REF SimpleTimeZone,std,datetime,timezone) which corresponds to the + given offset from UTC is used. To get the returned $(LREF SysTime) to be + a particular time zone, pass in that time zone and the $(LREF SysTime) + to be returned will be converted to that time zone (though it will still + be read in as whatever time zone is in its string). + + The accepted formats for time zone offsets are +HH, -HH, +HH:MM, and + -HH:MM. + + Params: + isoExtString = A string formatted in the ISO Extended format for + dates and times. + tz = The time zone to convert the given time to (no + conversion occurs if null). + + Throws: + $(REF DateTimeException,std,datetime,date) if the given string is + not in the ISO format or if the resulting $(LREF SysTime) would not + be valid. + +/ + static SysTime fromISOExtString(S)(in S isoExtString, immutable TimeZone tz = null) @safe + if (isSomeString!(S)) + { + import std.algorithm.searching : countUntil, find; + import std.conv : to; + import std.string : strip; + + auto dstr = to!dstring(strip(isoExtString)); + + auto tIndex = dstr.countUntil('T'); + enforce(tIndex != -1, new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString))); + + auto found = dstr[tIndex + 1 .. $].find('.', 'Z', '+', '-'); + auto dateTimeStr = dstr[0 .. $ - found[0].length]; + + dstring fracSecStr; + dstring zoneStr; + + if (found[1] != 0) + { + if (found[1] == 1) + { + auto foundTZ = found[0].find('Z', '+', '-'); + + if (foundTZ[1] != 0) + { + fracSecStr = found[0][0 .. $ - foundTZ[0].length]; + zoneStr = foundTZ[0]; + } + else + fracSecStr = found[0]; + } + else + zoneStr = found[0]; + } + + try + { + auto dateTime = DateTime.fromISOExtString(dateTimeStr); + auto fracSec = fracSecsFromISOString(fracSecStr); + Rebindable!(immutable TimeZone) parsedZone; + + if (zoneStr.empty) + parsedZone = LocalTime(); + else if (zoneStr == "Z") + parsedZone = UTC(); + else + parsedZone = SimpleTimeZone.fromISOExtString(zoneStr); + + auto retval = SysTime(dateTime, fracSec, parsedZone); + + if (tz !is null) + retval.timezone = tz; + + return retval; + } + catch (DateTimeException dte) + throw new DateTimeException(format("Invalid ISO Extended String: %s", isoExtString)); + } + + /// + @safe unittest + { + import core.time : hours, msecs, usecs, hnsecs; + import std.datetime.date : DateTime; + import std.datetime.timezone : SimpleTimeZone, UTC; + + assert(SysTime.fromISOExtString("2010-07-04T07:06:12") == + SysTime(DateTime(2010, 7, 4, 7, 6, 12))); + + assert(SysTime.fromISOExtString("1998-12-25T02:15:00.007") == + SysTime(DateTime(1998, 12, 25, 2, 15, 0), msecs(7))); + + assert(SysTime.fromISOExtString("0000-01-05T23:09:59.00002") == + SysTime(DateTime(0, 1, 5, 23, 9, 59), usecs(20))); + + assert(SysTime.fromISOExtString("2013-02-07T04:39:37.000050392") == + SysTime(DateTime(2013, 2, 7, 4, 39, 37), hnsecs(503))); + + assert(SysTime.fromISOExtString("-0004-01-05T00:00:02") == + SysTime(DateTime(-4, 1, 5, 0, 0, 2))); + + assert(SysTime.fromISOExtString(" 2010-07-04T07:06:12 ") == + SysTime(DateTime(2010, 7, 4, 7, 6, 12))); + + assert(SysTime.fromISOExtString("2010-07-04T07:06:12Z") == + SysTime(DateTime(2010, 7, 4, 7, 6, 12), UTC())); + + assert(SysTime.fromISOExtString("2010-07-04T07:06:12-08:00") == + SysTime(DateTime(2010, 7, 4, 7, 6, 12), + new immutable SimpleTimeZone(hours(-8)))); + assert(SysTime.fromISOExtString("2010-07-04T07:06:12+08:00") == + SysTime(DateTime(2010, 7, 4, 7, 6, 12), + new immutable SimpleTimeZone(hours(8)))); + } + + @safe unittest + { + foreach (str; ["", "20100704000000", "20100704 000000", + "20100704t000000", "20100704T000000.", "20100704T000000.0", + "2010-07:0400:00:00", "2010-07-04 00:00:00", + "2010-07-04 00:00:00", "2010-07-04t00:00:00", + "2010-07-04T00:00:00.", "2010-07-04T00:00:00.A", "2010-07-04T00:00:00.Z", + "2010-07-04T00:00:00.0000000A", "2010-07-04T00:00:00.00000000A", + "2010-07-04T00:00:00+", "2010-07-04T00:00:00-", + "2010-07-04T00:00:00:", "2010-07-04T00:00:00-:", "2010-07-04T00:00:00+:", + "2010-07-04T00:00:00-1:", "2010-07-04T00:00:00+1:", "2010-07-04T00:00:00+1:0", + "2010-07-04T00:00:00-12.00", "2010-07-04T00:00:00+12.00", + "2010-07-04T00:00:00-8", "2010-07-04T00:00:00+8", + "20100704T000000-800", "20100704T000000+800", + "20100704T000000-080", "20100704T000000+080", + "20100704T000000-2400", "20100704T000000+2400", + "20100704T000000-1260", "20100704T000000+1260", + "20100704T000000.0-800", "20100704T000000.0+800", + "20100704T000000.0-8", "20100704T000000.0+8", + "20100704T000000.0-080", "20100704T000000.0+080", + "20100704T000000.0-2400", "20100704T000000.0+2400", + "20100704T000000.0-1260", "20100704T000000.0+1260", + "2010-07-04T00:00:00-8:00", "2010-07-04T00:00:00+8:00", + "2010-07-04T00:00:00-24:00", "2010-07-04T00:00:00+24:00", + "2010-07-04T00:00:00-12:60", "2010-07-04T00:00:00+12:60", + "2010-07-04T00:00:00.0-8:00", "2010-07-04T00:00:00.0+8:00", + "2010-07-04T00:00:00.0-8", "2010-07-04T00:00:00.0+8", + "2010-07-04T00:00:00.0-24:00", "2010-07-04T00:00:00.0+24:00", + "2010-07-04T00:00:00.0-12:60", "2010-07-04T00:00:00.0+12:60", + "2010-Jul-0400:00:00", "2010-Jul-04t00:00:00", + "2010-Jul-04 00:00:00.", "2010-Jul-04 00:00:00.0", + "20101222T172201", "2010-Dec-22 17:22:01"]) + { + assertThrown!DateTimeException(SysTime.fromISOExtString(str), format("[%s]", str)); + } + + static void test(string str, SysTime st, size_t line = __LINE__) + { + if (SysTime.fromISOExtString(str) != st) + throw new AssertError("unittest failure", __FILE__, line); + } + + test("2010-12-22T17:22:01", SysTime(DateTime(2010, 12, 22, 17, 22, 01))); + test("1999-07-06T12:30:33", SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + test("-1999-07-06T12:30:33", SysTime(DateTime(-1999, 7, 6, 12, 30, 33))); + test("+01999-07-06T12:30:33", SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + test("1999-07-06T12:30:33 ", SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + test(" 1999-07-06T12:30:33", SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + test(" 1999-07-06T12:30:33 ", SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + + test("1907-07-07T12:12:12.0", SysTime(DateTime(1907, 07, 07, 12, 12, 12))); + test("1907-07-07T12:12:12.0000000", SysTime(DateTime(1907, 07, 07, 12, 12, 12))); + test("1907-07-07T12:12:12.0000001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), hnsecs(1))); + test("2010-07-04T00:00:00.00000000", SysTime(Date(2010, 07, 04))); + test("2010-07-04T00:00:00.00000009", SysTime(Date(2010, 07, 04))); + test("2010-07-04T00:00:00.00000019", SysTime(DateTime(2010, 07, 04), hnsecs(1))); + test("1907-07-07T12:12:12.000001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), usecs(1))); + test("1907-07-07T12:12:12.0000010", SysTime(DateTime(1907, 07, 07, 12, 12, 12), usecs(1))); + test("1907-07-07T12:12:12.001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), msecs(1))); + test("1907-07-07T12:12:12.0010000", SysTime(DateTime(1907, 07, 07, 12, 12, 12), msecs(1))); + + auto west60 = new immutable SimpleTimeZone(hours(-1)); + auto west90 = new immutable SimpleTimeZone(minutes(-90)); + auto west480 = new immutable SimpleTimeZone(hours(-8)); + auto east60 = new immutable SimpleTimeZone(hours(1)); + auto east90 = new immutable SimpleTimeZone(minutes(90)); + auto east480 = new immutable SimpleTimeZone(hours(8)); + + test("2010-12-22T17:22:01Z", SysTime(DateTime(2010, 12, 22, 17, 22, 01), UTC())); + test("2010-12-22T17:22:01-01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west60)); + test("2010-12-22T17:22:01-01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west60)); + test("2010-12-22T17:22:01-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west90)); + test("2010-12-22T17:22:01-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west480)); + test("2010-12-22T17:22:01+01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60)); + test("2010-12-22T17:22:01+01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60)); + test("2010-12-22T17:22:01+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90)); + test("2010-12-22T17:22:01+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east480)); + + test("2010-11-03T06:51:06.57159Z", SysTime(DateTime(2010, 11, 3, 6, 51, 6), hnsecs(5715900), UTC())); + test("2010-12-22T17:22:01.23412Z", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(2_341_200), UTC())); + test("2010-12-22T17:22:01.23112-01:00", + SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(2_311_200), west60)); + test("2010-12-22T17:22:01.45-01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(4_500_000), west60)); + test("2010-12-22T17:22:01.1-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_000_000), west90)); + test("2010-12-22T17:22:01.55-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(5_500_000), west480)); + test("2010-12-22T17:22:01.1234567+01:00", + SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_234_567), east60)); + test("2010-12-22T17:22:01.0+01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60)); + test("2010-12-22T17:22:01.0000000+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90)); + test("2010-12-22T17:22:01.45+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(4_500_000), east480)); + } + + // bug# 17801 + @safe unittest + { + import std.conv : to; + import std.meta : AliasSeq; + foreach (C; AliasSeq!(char, wchar, dchar)) + { + foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + { + assert(SysTime.fromISOExtString(to!S("2012-12-21T14:15:16Z")) == + SysTime(DateTime(2012, 12, 21, 14, 15, 16), UTC())); + } + } + } + + + /++ + Creates a $(LREF SysTime) from a string with the format + YYYY-MM-DD HH:MM:SS.FFFFFFFTZ (where F is fractional seconds is the + time zone). Whitespace is stripped from the given string. + + The exact format is exactly as described in $(D toSimpleString) except + that trailing zeroes are permitted - including having fractional seconds + with all zeroes. However, a decimal point with nothing following it is + invalid. Also, while $(LREF toSimpleString) will never generate a + string with more than 7 digits in the fractional seconds (because that's + the limit with hecto-nanosecond precision), it will allow more than 7 + digits in order to read strings from other sources that have higher + precision (however, any digits beyond 7 will be truncated). + + If there is no time zone in the string, then + $(REF LocalTime,std,datetime,timezone) is used. If the time zone is "Z", + then $(D UTC) is used. Otherwise, a + $(REF SimpleTimeZone,std,datetime,timezone) which corresponds to the + given offset from UTC is used. To get the returned $(LREF SysTime) to be + a particular time zone, pass in that time zone and the $(LREF SysTime) + to be returned will be converted to that time zone (though it will still + be read in as whatever time zone is in its string). + + The accepted formats for time zone offsets are +HH, -HH, +HH:MM, and + -HH:MM. + + Params: + simpleString = A string formatted in the way that + $(D toSimpleString) formats dates and times. + tz = The time zone to convert the given time to (no + conversion occurs if null). + + Throws: + $(REF DateTimeException,std,datetime,date) if the given string is + not in the ISO format or if the resulting $(LREF SysTime) would not + be valid. + +/ + static SysTime fromSimpleString(S)(in S simpleString, immutable TimeZone tz = null) @safe + if (isSomeString!(S)) + { + import std.algorithm.searching : countUntil, find; + import std.conv : to; + import std.string : strip; + + auto dstr = to!dstring(strip(simpleString)); + + auto spaceIndex = dstr.countUntil(' '); + enforce(spaceIndex != -1, new DateTimeException(format("Invalid Simple String: %s", simpleString))); + + auto found = dstr[spaceIndex + 1 .. $].find('.', 'Z', '+', '-'); + auto dateTimeStr = dstr[0 .. $ - found[0].length]; + + dstring fracSecStr; + dstring zoneStr; + + if (found[1] != 0) + { + if (found[1] == 1) + { + auto foundTZ = found[0].find('Z', '+', '-'); + + if (foundTZ[1] != 0) + { + fracSecStr = found[0][0 .. $ - foundTZ[0].length]; + zoneStr = foundTZ[0]; + } + else + fracSecStr = found[0]; + } + else + zoneStr = found[0]; + } + + try + { + auto dateTime = DateTime.fromSimpleString(dateTimeStr); + auto fracSec = fracSecsFromISOString(fracSecStr); + Rebindable!(immutable TimeZone) parsedZone; + + if (zoneStr.empty) + parsedZone = LocalTime(); + else if (zoneStr == "Z") + parsedZone = UTC(); + else + parsedZone = SimpleTimeZone.fromISOExtString(zoneStr); + + auto retval = SysTime(dateTime, fracSec, parsedZone); + + if (tz !is null) + retval.timezone = tz; + + return retval; + } + catch (DateTimeException dte) + throw new DateTimeException(format("Invalid Simple String: %s", simpleString)); + } + + /// + @safe unittest + { + import core.time : hours, msecs, usecs, hnsecs; + import std.datetime.date : DateTime; + import std.datetime.timezone : SimpleTimeZone, UTC; + + assert(SysTime.fromSimpleString("2010-Jul-04 07:06:12") == + SysTime(DateTime(2010, 7, 4, 7, 6, 12))); + + assert(SysTime.fromSimpleString("1998-Dec-25 02:15:00.007") == + SysTime(DateTime(1998, 12, 25, 2, 15, 0), msecs(7))); + + assert(SysTime.fromSimpleString("0000-Jan-05 23:09:59.00002") == + SysTime(DateTime(0, 1, 5, 23, 9, 59), usecs(20))); + + assert(SysTime.fromSimpleString("2013-Feb-07 04:39:37.000050392") == + SysTime(DateTime(2013, 2, 7, 4, 39, 37), hnsecs(503))); + + assert(SysTime.fromSimpleString("-0004-Jan-05 00:00:02") == + SysTime(DateTime(-4, 1, 5, 0, 0, 2))); + + assert(SysTime.fromSimpleString(" 2010-Jul-04 07:06:12 ") == + SysTime(DateTime(2010, 7, 4, 7, 6, 12))); + + assert(SysTime.fromSimpleString("2010-Jul-04 07:06:12Z") == + SysTime(DateTime(2010, 7, 4, 7, 6, 12), UTC())); + + assert(SysTime.fromSimpleString("2010-Jul-04 07:06:12-08:00") == + SysTime(DateTime(2010, 7, 4, 7, 6, 12), + new immutable SimpleTimeZone(hours(-8)))); + + assert(SysTime.fromSimpleString("2010-Jul-04 07:06:12+08:00") == + SysTime(DateTime(2010, 7, 4, 7, 6, 12), + new immutable SimpleTimeZone(hours(8)))); + } + + @safe unittest + { + foreach (str; ["", "20100704000000", "20100704 000000", + "20100704t000000", "20100704T000000.", "20100704T000000.0", + "2010-07-0400:00:00", "2010-07-04 00:00:00", "2010-07-04t00:00:00", + "2010-07-04T00:00:00.", "2010-07-04T00:00:00.0", + "2010-Jul-0400:00:00", "2010-Jul-04t00:00:00", "2010-Jul-04T00:00:00", + "2010-Jul-04 00:00:00.", "2010-Jul-04 00:00:00.A", "2010-Jul-04 00:00:00.Z", + "2010-Jul-04 00:00:00.0000000A", "2010-Jul-04 00:00:00.00000000A", + "2010-Jul-04 00:00:00+", "2010-Jul-04 00:00:00-", + "2010-Jul-04 00:00:00:", "2010-Jul-04 00:00:00-:", + "2010-Jul-04 00:00:00+:", "2010-Jul-04 00:00:00-1:", + "2010-Jul-04 00:00:00+1:", "2010-Jul-04 00:00:00+1:0", + "2010-Jul-04 00:00:00-12.00", "2010-Jul-04 00:00:00+12.00", + "2010-Jul-04 00:00:00-8", "2010-Jul-04 00:00:00+8", + "20100704T000000-800", "20100704T000000+800", + "20100704T000000-080", "20100704T000000+080", + "20100704T000000-2400", "20100704T000000+2400", + "20100704T000000-1260", "20100704T000000+1260", + "20100704T000000.0-800", "20100704T000000.0+800", + "20100704T000000.0-8", "20100704T000000.0+8", + "20100704T000000.0-080", "20100704T000000.0+080", + "20100704T000000.0-2400", "20100704T000000.0+2400", + "20100704T000000.0-1260", "20100704T000000.0+1260", + "2010-Jul-04 00:00:00-8:00", "2010-Jul-04 00:00:00+8:00", + "2010-Jul-04 00:00:00-08:0", "2010-Jul-04 00:00:00+08:0", + "2010-Jul-04 00:00:00-24:00", "2010-Jul-04 00:00:00+24:00", + "2010-Jul-04 00:00:00-12:60", "2010-Jul-04 00:00:00+24:60", + "2010-Jul-04 00:00:00.0-8:00", "2010-Jul-04 00:00:00+8:00", + "2010-Jul-04 00:00:00.0-8", "2010-Jul-04 00:00:00.0+8", + "2010-Jul-04 00:00:00.0-08:0", "2010-Jul-04 00:00:00.0+08:0", + "2010-Jul-04 00:00:00.0-24:00", "2010-Jul-04 00:00:00.0+24:00", + "2010-Jul-04 00:00:00.0-12:60", "2010-Jul-04 00:00:00.0+24:60", + "20101222T172201", "2010-12-22T172201"]) + { + assertThrown!DateTimeException(SysTime.fromSimpleString(str), format("[%s]", str)); + } + + static void test(string str, SysTime st, size_t line = __LINE__) + { + if (SysTime.fromSimpleString(str) != st) + throw new AssertError("unittest failure", __FILE__, line); + } + + test("2010-Dec-22 17:22:01", SysTime(DateTime(2010, 12, 22, 17, 22, 01))); + test("1999-Jul-06 12:30:33", SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + test("-1999-Jul-06 12:30:33", SysTime(DateTime(-1999, 7, 6, 12, 30, 33))); + test("+01999-Jul-06 12:30:33", SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + test("1999-Jul-06 12:30:33 ", SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + test(" 1999-Jul-06 12:30:33", SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + test(" 1999-Jul-06 12:30:33 ", SysTime(DateTime(1999, 7, 6, 12, 30, 33))); + + test("1907-Jul-07 12:12:12.0", SysTime(DateTime(1907, 07, 07, 12, 12, 12))); + test("1907-Jul-07 12:12:12.0000000", SysTime(DateTime(1907, 07, 07, 12, 12, 12))); + test("2010-Jul-04 00:00:00.00000000", SysTime(Date(2010, 07, 04))); + test("2010-Jul-04 00:00:00.00000009", SysTime(Date(2010, 07, 04))); + test("2010-Jul-04 00:00:00.00000019", SysTime(DateTime(2010, 07, 04), hnsecs(1))); + test("1907-Jul-07 12:12:12.0000001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), hnsecs(1))); + test("1907-Jul-07 12:12:12.000001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), usecs(1))); + test("1907-Jul-07 12:12:12.0000010", SysTime(DateTime(1907, 07, 07, 12, 12, 12), usecs(1))); + test("1907-Jul-07 12:12:12.001", SysTime(DateTime(1907, 07, 07, 12, 12, 12), msecs(1))); + test("1907-Jul-07 12:12:12.0010000", SysTime(DateTime(1907, 07, 07, 12, 12, 12), msecs(1))); + + auto west60 = new immutable SimpleTimeZone(hours(-1)); + auto west90 = new immutable SimpleTimeZone(minutes(-90)); + auto west480 = new immutable SimpleTimeZone(hours(-8)); + auto east60 = new immutable SimpleTimeZone(hours(1)); + auto east90 = new immutable SimpleTimeZone(minutes(90)); + auto east480 = new immutable SimpleTimeZone(hours(8)); + + test("2010-Dec-22 17:22:01Z", SysTime(DateTime(2010, 12, 22, 17, 22, 01), UTC())); + test("2010-Dec-22 17:22:01-01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west60)); + test("2010-Dec-22 17:22:01-01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west60)); + test("2010-Dec-22 17:22:01-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west90)); + test("2010-Dec-22 17:22:01-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), west480)); + test("2010-Dec-22 17:22:01+01:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60)); + test("2010-Dec-22 17:22:01+01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60)); + test("2010-Dec-22 17:22:01+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90)); + test("2010-Dec-22 17:22:01+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east480)); + + test("2010-Nov-03 06:51:06.57159Z", SysTime(DateTime(2010, 11, 3, 6, 51, 6), hnsecs(5715900), UTC())); + test("2010-Dec-22 17:22:01.23412Z", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(2_341_200), UTC())); + test("2010-Dec-22 17:22:01.23112-01:00", + SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(2_311_200), west60)); + test("2010-Dec-22 17:22:01.45-01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(4_500_000), west60)); + test("2010-Dec-22 17:22:01.1-01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_000_000), west90)); + test("2010-Dec-22 17:22:01.55-08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(5_500_000), west480)); + test("2010-Dec-22 17:22:01.1234567+01:00", + SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(1_234_567), east60)); + test("2010-Dec-22 17:22:01.0+01", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east60)); + test("2010-Dec-22 17:22:01.0000000+01:30", SysTime(DateTime(2010, 12, 22, 17, 22, 01), east90)); + test("2010-Dec-22 17:22:01.45+08:00", SysTime(DateTime(2010, 12, 22, 17, 22, 01), hnsecs(4_500_000), east480)); + } + + // bug# 17801 + @safe unittest + { + import std.conv : to; + import std.meta : AliasSeq; + foreach (C; AliasSeq!(char, wchar, dchar)) + { + foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + { + assert(SysTime.fromSimpleString(to!S("2012-Dec-21 14:15:16Z")) == + SysTime(DateTime(2012, 12, 21, 14, 15, 16), UTC())); + } + } + } + + + /++ + Returns the $(LREF SysTime) farthest in the past which is representable + by $(LREF SysTime). + + The $(LREF SysTime) which is returned is in UTC. + +/ + @property static SysTime min() @safe pure nothrow + { + return SysTime(long.min, UTC()); + } + + @safe unittest + { + assert(SysTime.min.year < 0); + assert(SysTime.min < SysTime.max); + } + + + /++ + Returns the $(LREF SysTime) farthest in the future which is representable + by $(LREF SysTime). + + The $(LREF SysTime) which is returned is in UTC. + +/ + @property static SysTime max() @safe pure nothrow + { + return SysTime(long.max, UTC()); + } + + @safe unittest + { + assert(SysTime.max.year > 0); + assert(SysTime.max > SysTime.min); + } + + +private: + + /+ + Returns $(D stdTime) converted to $(LREF SysTime)'s time zone. + +/ + @property long adjTime() @safe const nothrow + { + return _timezone.utcToTZ(_stdTime); + } + + + /+ + Converts the given hnsecs from $(LREF SysTime)'s time zone to std time. + +/ + @property void adjTime(long adjTime) @safe nothrow + { + _stdTime = _timezone.tzToUTC(adjTime); + } + + + // Commented out due to bug http://d.puremagic.com/issues/show_bug.cgi?id=5058 + /+ + invariant() + { + assert(_timezone !is null, "Invariant Failure: timezone is null. Were you foolish enough to use " ~ + "SysTime.init? (since timezone for SysTime.init can't be set at compile time)."); + } + +/ + + + long _stdTime; + Rebindable!(immutable TimeZone) _timezone; +} + + +/++ + Converts from unix time (which uses midnight, January 1st, 1970 UTC as its + epoch and seconds as its units) to "std time" (which uses midnight, + January 1st, 1 A.D. UTC and hnsecs as its units). + + The C standard does not specify the representation of time_t, so it is + implementation defined. On POSIX systems, unix time is equivalent to + time_t, but that's not necessarily true on other systems (e.g. it is + not true for the Digital Mars C runtime). So, be careful when using unix + time with C functions on non-POSIX systems. + + "std time"'s epoch is based on the Proleptic Gregorian Calendar per ISO + 8601 and is what $(LREF SysTime) uses internally. However, holding the time + as an integer in hnescs since that epoch technically isn't actually part of + the standard, much as it's based on it, so the name "std time" isn't + particularly good, but there isn't an official name for it. C# uses "ticks" + for the same thing, but they aren't actually clock ticks, and the term + "ticks" $(I is) used for actual clock ticks for $(REF MonoTime, core,time), + so it didn't make sense to use the term ticks here. So, for better or worse, + std.datetime uses the term "std time" for this. + + Params: + unixTime = The unix time to convert. + + See_Also: + SysTime.fromUnixTime + +/ +long unixTimeToStdTime(long unixTime) @safe pure nothrow +{ + return 621_355_968_000_000_000L + convert!("seconds", "hnsecs")(unixTime); +} + +/// +@safe unittest +{ + import std.datetime.date : DateTime; + import std.datetime.timezone : UTC; + + // Midnight, January 1st, 1970 + assert(unixTimeToStdTime(0) == 621_355_968_000_000_000L); + assert(SysTime(unixTimeToStdTime(0)) == + SysTime(DateTime(1970, 1, 1), UTC())); + + assert(unixTimeToStdTime(int.max) == 642_830_804_470_000_000L); + assert(SysTime(unixTimeToStdTime(int.max)) == + SysTime(DateTime(2038, 1, 19, 3, 14, 07), UTC())); + + assert(unixTimeToStdTime(-127_127) == 621_354_696_730_000_000L); + assert(SysTime(unixTimeToStdTime(-127_127)) == + SysTime(DateTime(1969, 12, 30, 12, 41, 13), UTC())); +} + +@safe unittest +{ + // Midnight, January 2nd, 1970 + assert(unixTimeToStdTime(86_400) == 621_355_968_000_000_000L + 864_000_000_000L); + // Midnight, December 31st, 1969 + assert(unixTimeToStdTime(-86_400) == 621_355_968_000_000_000L - 864_000_000_000L); + + assert(unixTimeToStdTime(0) == (Date(1970, 1, 1) - Date(1, 1, 1)).total!"hnsecs"); + assert(unixTimeToStdTime(0) == (DateTime(1970, 1, 1) - DateTime(1, 1, 1)).total!"hnsecs"); + + foreach (dt; [DateTime(2010, 11, 1, 19, 5, 22), DateTime(1952, 7, 6, 2, 17, 9)]) + assert(unixTimeToStdTime((dt - DateTime(1970, 1, 1)).total!"seconds") == (dt - DateTime.init).total!"hnsecs"); +} + + +/++ + Converts std time (which uses midnight, January 1st, 1 A.D. UTC as its epoch + and hnsecs as its units) to unix time (which uses midnight, January 1st, + 1970 UTC as its epoch and seconds as its units). + + The C standard does not specify the representation of time_t, so it is + implementation defined. On POSIX systems, unix time is equivalent to + time_t, but that's not necessarily true on other systems (e.g. it is + not true for the Digital Mars C runtime). So, be careful when using unix + time with C functions on non-POSIX systems. + + "std time"'s epoch is based on the Proleptic Gregorian Calendar per ISO + 8601 and is what $(LREF SysTime) uses internally. However, holding the time + as an integer in hnescs since that epoch technically isn't actually part of + the standard, much as it's based on it, so the name "std time" isn't + particularly good, but there isn't an official name for it. C# uses "ticks" + for the same thing, but they aren't actually clock ticks, and the term + "ticks" $(I is) used for actual clock ticks for $(REF MonoTime, core,time), + so it didn't make sense to use the term ticks here. So, for better or worse, + std.datetime uses the term "std time" for this. + + By default, the return type is time_t (which is normally an alias for + int on 32-bit systems and long on 64-bit systems), but if a different + size is required than either int or long can be passed as a template + argument to get the desired size. + + If the return type is int, and the result can't fit in an int, then the + closest value that can be held in 32 bits will be used (so $(D int.max) + if it goes over and $(D int.min) if it goes under). However, no attempt + is made to deal with integer overflow if the return type is long. + + Params: + T = The return type (int or long). It defaults to time_t, which is + normally 32 bits on a 32-bit system and 64 bits on a 64-bit + system. + stdTime = The std time to convert. + + Returns: + A signed integer representing the unix time which is equivalent to + the given std time. + + See_Also: + SysTime.toUnixTime + +/ +T stdTimeToUnixTime(T = time_t)(long stdTime) @safe pure nothrow +if (is(T == int) || is(T == long)) +{ + immutable unixTime = convert!("hnsecs", "seconds")(stdTime - 621_355_968_000_000_000L); + + static assert(is(time_t == int) || is(time_t == long), + "Currently, std.datetime only supports systems where time_t is int or long"); + + static if (is(T == long)) + return unixTime; + else static if (is(T == int)) + { + if (unixTime > int.max) + return int.max; + return unixTime < int.min ? int.min : cast(int) unixTime; + } + else + static assert(0, "Bug in template constraint. Only int and long allowed."); +} + +/// +@safe unittest +{ + // Midnight, January 1st, 1970 UTC + assert(stdTimeToUnixTime(621_355_968_000_000_000L) == 0); + + // 2038-01-19 03:14:07 UTC + assert(stdTimeToUnixTime(642_830_804_470_000_000L) == int.max); +} + +@safe unittest +{ + enum unixEpochAsStdTime = (Date(1970, 1, 1) - Date.init).total!"hnsecs"; + + assert(stdTimeToUnixTime(unixEpochAsStdTime) == 0); // Midnight, January 1st, 1970 + assert(stdTimeToUnixTime(unixEpochAsStdTime + 864_000_000_000L) == 86_400); // Midnight, January 2nd, 1970 + assert(stdTimeToUnixTime(unixEpochAsStdTime - 864_000_000_000L) == -86_400); // Midnight, December 31st, 1969 + + assert(stdTimeToUnixTime((Date(1970, 1, 1) - Date(1, 1, 1)).total!"hnsecs") == 0); + assert(stdTimeToUnixTime((DateTime(1970, 1, 1) - DateTime(1, 1, 1)).total!"hnsecs") == 0); + + foreach (dt; [DateTime(2010, 11, 1, 19, 5, 22), DateTime(1952, 7, 6, 2, 17, 9)]) + assert(stdTimeToUnixTime((dt - DateTime.init).total!"hnsecs") == (dt - DateTime(1970, 1, 1)).total!"seconds"); + + enum max = convert!("seconds", "hnsecs")(int.max); + enum min = convert!("seconds", "hnsecs")(int.min); + enum one = convert!("seconds", "hnsecs")(1); + + assert(stdTimeToUnixTime!long(unixEpochAsStdTime + max) == int.max); + assert(stdTimeToUnixTime!int(unixEpochAsStdTime + max) == int.max); + + assert(stdTimeToUnixTime!long(unixEpochAsStdTime + max + one) == int.max + 1L); + assert(stdTimeToUnixTime!int(unixEpochAsStdTime + max + one) == int.max); + assert(stdTimeToUnixTime!long(unixEpochAsStdTime + max + 9_999_999) == int.max); + assert(stdTimeToUnixTime!int(unixEpochAsStdTime + max + 9_999_999) == int.max); + + assert(stdTimeToUnixTime!long(unixEpochAsStdTime + min) == int.min); + assert(stdTimeToUnixTime!int(unixEpochAsStdTime + min) == int.min); + + assert(stdTimeToUnixTime!long(unixEpochAsStdTime + min - one) == int.min - 1L); + assert(stdTimeToUnixTime!int(unixEpochAsStdTime + min - one) == int.min); + assert(stdTimeToUnixTime!long(unixEpochAsStdTime + min - 9_999_999) == int.min); + assert(stdTimeToUnixTime!int(unixEpochAsStdTime + min - 9_999_999) == int.min); +} + + +version (StdDdoc) +{ + version (Windows) + {} + else + { + alias SYSTEMTIME = void*; + alias FILETIME = void*; + } + + /++ + $(BLUE This function is Windows-Only.) + + Converts a $(D SYSTEMTIME) struct to a $(LREF SysTime). + + Params: + st = The $(D SYSTEMTIME) struct to convert. + tz = The time zone that the time in the $(D SYSTEMTIME) struct is + assumed to be (if the $(D SYSTEMTIME) was supplied by a Windows + system call, the $(D SYSTEMTIME) will either be in local time + or UTC, depending on the call). + + Throws: + $(REF DateTimeException,std,datetime,date) if the given + $(D SYSTEMTIME) will not fit in a $(LREF SysTime), which is highly + unlikely to happen given that $(D SysTime.max) is in 29,228 A.D. and + the maximum $(D SYSTEMTIME) is in 30,827 A.D. + +/ + SysTime SYSTEMTIMEToSysTime(const SYSTEMTIME* st, immutable TimeZone tz = LocalTime()) @safe; + + + /++ + $(BLUE This function is Windows-Only.) + + Converts a $(LREF SysTime) to a $(D SYSTEMTIME) struct. + + The $(D SYSTEMTIME) which is returned will be set using the given + $(LREF SysTime)'s time zone, so to get the $(D SYSTEMTIME) in + UTC, set the $(LREF SysTime)'s time zone to UTC. + + Params: + sysTime = The $(LREF SysTime) to convert. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given + $(LREF SysTime) will not fit in a $(D SYSTEMTIME). This will only + happen if the $(LREF SysTime)'s date is prior to 1601 A.D. + +/ + SYSTEMTIME SysTimeToSYSTEMTIME(in SysTime sysTime) @safe; + + + /++ + $(BLUE This function is Windows-Only.) + + Converts a $(D FILETIME) struct to the number of hnsecs since midnight, + January 1st, 1 A.D. + + Params: + ft = The $(D FILETIME) struct to convert. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given + $(D FILETIME) cannot be represented as the return value. + +/ + long FILETIMEToStdTime(scope const FILETIME* ft) @safe; + + + /++ + $(BLUE This function is Windows-Only.) + + Converts a $(D FILETIME) struct to a $(LREF SysTime). + + Params: + ft = The $(D FILETIME) struct to convert. + tz = The time zone that the $(LREF SysTime) will be in + ($(D FILETIME)s are in UTC). + + Throws: + $(REF DateTimeException,std,datetime,date) if the given + $(D FILETIME) will not fit in a $(LREF SysTime). + +/ + SysTime FILETIMEToSysTime(scope const FILETIME* ft, immutable TimeZone tz = LocalTime()) @safe; + + + /++ + $(BLUE This function is Windows-Only.) + + Converts a number of hnsecs since midnight, January 1st, 1 A.D. to a + $(D FILETIME) struct. + + Params: + stdTime = The number of hnsecs since midnight, January 1st, 1 A.D. + UTC. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given value will + not fit in a $(D FILETIME). + +/ + FILETIME stdTimeToFILETIME(long stdTime) @safe; + + + /++ + $(BLUE This function is Windows-Only.) + + Converts a $(LREF SysTime) to a $(D FILETIME) struct. + + $(D FILETIME)s are always in UTC. + + Params: + sysTime = The $(LREF SysTime) to convert. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given + $(LREF SysTime) will not fit in a $(D FILETIME). + +/ + FILETIME SysTimeToFILETIME(SysTime sysTime) @safe; +} +else version (Windows) +{ + SysTime SYSTEMTIMEToSysTime(const SYSTEMTIME* st, immutable TimeZone tz = LocalTime()) @safe + { + const max = SysTime.max; + + static void throwLaterThanMax() + { + throw new DateTimeException("The given SYSTEMTIME is for a date greater than SysTime.max."); + } + + if (st.wYear > max.year) + throwLaterThanMax(); + else if (st.wYear == max.year) + { + if (st.wMonth > max.month) + throwLaterThanMax(); + else if (st.wMonth == max.month) + { + if (st.wDay > max.day) + throwLaterThanMax(); + else if (st.wDay == max.day) + { + if (st.wHour > max.hour) + throwLaterThanMax(); + else if (st.wHour == max.hour) + { + if (st.wMinute > max.minute) + throwLaterThanMax(); + else if (st.wMinute == max.minute) + { + if (st.wSecond > max.second) + throwLaterThanMax(); + else if (st.wSecond == max.second) + { + if (st.wMilliseconds > max.fracSecs.total!"msecs") + throwLaterThanMax(); + } + } + } + } + } + } + + auto dt = DateTime(st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); + + return SysTime(dt, msecs(st.wMilliseconds), tz); + } + + @system unittest + { + auto sysTime = Clock.currTime(UTC()); + SYSTEMTIME st = void; + GetSystemTime(&st); + auto converted = SYSTEMTIMEToSysTime(&st, UTC()); + + assert(abs((converted - sysTime)) <= dur!"seconds"(2)); + } + + + SYSTEMTIME SysTimeToSYSTEMTIME(in SysTime sysTime) @safe + { + immutable dt = cast(DateTime) sysTime; + + if (dt.year < 1601) + throw new DateTimeException("SYSTEMTIME cannot hold dates prior to the year 1601."); + + SYSTEMTIME st; + + st.wYear = dt.year; + st.wMonth = dt.month; + st.wDayOfWeek = dt.dayOfWeek; + st.wDay = dt.day; + st.wHour = dt.hour; + st.wMinute = dt.minute; + st.wSecond = dt.second; + st.wMilliseconds = cast(ushort) sysTime.fracSecs.total!"msecs"; + + return st; + } + + @system unittest + { + SYSTEMTIME st = void; + GetSystemTime(&st); + auto sysTime = SYSTEMTIMEToSysTime(&st, UTC()); + + SYSTEMTIME result = SysTimeToSYSTEMTIME(sysTime); + + assert(st.wYear == result.wYear); + assert(st.wMonth == result.wMonth); + assert(st.wDayOfWeek == result.wDayOfWeek); + assert(st.wDay == result.wDay); + assert(st.wHour == result.wHour); + assert(st.wMinute == result.wMinute); + assert(st.wSecond == result.wSecond); + assert(st.wMilliseconds == result.wMilliseconds); + } + + private enum hnsecsFrom1601 = 504_911_232_000_000_000L; + + long FILETIMEToStdTime(scope const FILETIME* ft) @safe + { + ULARGE_INTEGER ul; + ul.HighPart = ft.dwHighDateTime; + ul.LowPart = ft.dwLowDateTime; + ulong tempHNSecs = ul.QuadPart; + + if (tempHNSecs > long.max - hnsecsFrom1601) + throw new DateTimeException("The given FILETIME cannot be represented as a stdTime value."); + + return cast(long) tempHNSecs + hnsecsFrom1601; + } + + SysTime FILETIMEToSysTime(scope const FILETIME* ft, immutable TimeZone tz = LocalTime()) @safe + { + auto sysTime = SysTime(FILETIMEToStdTime(ft), UTC()); + sysTime.timezone = tz; + return sysTime; + } + + @system unittest + { + auto sysTime = Clock.currTime(UTC()); + SYSTEMTIME st = void; + GetSystemTime(&st); + + FILETIME ft = void; + SystemTimeToFileTime(&st, &ft); + + auto converted = FILETIMEToSysTime(&ft); + + assert(abs((converted - sysTime)) <= dur!"seconds"(2)); + } + + + FILETIME stdTimeToFILETIME(long stdTime) @safe + { + if (stdTime < hnsecsFrom1601) + throw new DateTimeException("The given stdTime value cannot be represented as a FILETIME."); + + ULARGE_INTEGER ul; + ul.QuadPart = cast(ulong) stdTime - hnsecsFrom1601; + + FILETIME ft; + ft.dwHighDateTime = ul.HighPart; + ft.dwLowDateTime = ul.LowPart; + + return ft; + } + + FILETIME SysTimeToFILETIME(SysTime sysTime) @safe + { + return stdTimeToFILETIME(sysTime.stdTime); + } + + @system unittest + { + SYSTEMTIME st = void; + GetSystemTime(&st); + + FILETIME ft = void; + SystemTimeToFileTime(&st, &ft); + auto sysTime = FILETIMEToSysTime(&ft, UTC()); + + FILETIME result = SysTimeToFILETIME(sysTime); + + assert(ft.dwLowDateTime == result.dwLowDateTime); + assert(ft.dwHighDateTime == result.dwHighDateTime); + } +} + + +/++ + Type representing the DOS file date/time format. + +/ +alias DosFileTime = uint; + +/++ + Converts from DOS file date/time to $(LREF SysTime). + + Params: + dft = The DOS file time to convert. + tz = The time zone which the DOS file time is assumed to be in. + + Throws: + $(REF DateTimeException,std,datetime,date) if the $(D DosFileTime) is + invalid. + +/ +SysTime DosFileTimeToSysTime(DosFileTime dft, immutable TimeZone tz = LocalTime()) @safe +{ + uint dt = cast(uint) dft; + + if (dt == 0) + throw new DateTimeException("Invalid DosFileTime."); + + int year = ((dt >> 25) & 0x7F) + 1980; + int month = ((dt >> 21) & 0x0F); // 1 .. 12 + int dayOfMonth = ((dt >> 16) & 0x1F); // 1 .. 31 + int hour = (dt >> 11) & 0x1F; // 0 .. 23 + int minute = (dt >> 5) & 0x3F; // 0 .. 59 + int second = (dt << 1) & 0x3E; // 0 .. 58 (in 2 second increments) + + try + return SysTime(DateTime(year, month, dayOfMonth, hour, minute, second), tz); + catch (DateTimeException dte) + throw new DateTimeException("Invalid DosFileTime", __FILE__, __LINE__, dte); +} + +@safe unittest +{ + assert(DosFileTimeToSysTime(0b00000000001000010000000000000000) == SysTime(DateTime(1980, 1, 1, 0, 0, 0))); + assert(DosFileTimeToSysTime(0b11111111100111111011111101111101) == SysTime(DateTime(2107, 12, 31, 23, 59, 58))); + assert(DosFileTimeToSysTime(0x3E3F8456) == SysTime(DateTime(2011, 1, 31, 16, 34, 44))); +} + + +/++ + Converts from $(LREF SysTime) to DOS file date/time. + + Params: + sysTime = The $(LREF SysTime) to convert. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given + $(LREF SysTime) cannot be converted to a $(D DosFileTime). + +/ +DosFileTime SysTimeToDosFileTime(SysTime sysTime) @safe +{ + auto dateTime = cast(DateTime) sysTime; + + if (dateTime.year < 1980) + throw new DateTimeException("DOS File Times cannot hold dates prior to 1980."); + + if (dateTime.year > 2107) + throw new DateTimeException("DOS File Times cannot hold dates past 2107."); + + uint retval = 0; + retval = (dateTime.year - 1980) << 25; + retval |= (dateTime.month & 0x0F) << 21; + retval |= (dateTime.day & 0x1F) << 16; + retval |= (dateTime.hour & 0x1F) << 11; + retval |= (dateTime.minute & 0x3F) << 5; + retval |= (dateTime.second >> 1) & 0x1F; + + return cast(DosFileTime) retval; +} + +@safe unittest +{ + assert(SysTimeToDosFileTime(SysTime(DateTime(1980, 1, 1, 0, 0, 0))) == 0b00000000001000010000000000000000); + assert(SysTimeToDosFileTime(SysTime(DateTime(2107, 12, 31, 23, 59, 58))) == 0b11111111100111111011111101111101); + assert(SysTimeToDosFileTime(SysTime(DateTime(2011, 1, 31, 16, 34, 44))) == 0x3E3F8456); +} + + +/++ + The given array of $(D char) or random-access range of $(D char) or + $(D ubyte) is expected to be in the format specified in + $(HTTP tools.ietf.org/html/rfc5322, RFC 5322) section 3.3 with the + grammar rule $(I date-time). It is the date-time format commonly used in + internet messages such as e-mail and HTTP. The corresponding + $(LREF SysTime) will be returned. + + RFC 822 was the original spec (hence the function's name), whereas RFC 5322 + is the current spec. + + The day of the week is ignored beyond verifying that it's a valid day of the + week, as the day of the week can be inferred from the date. It is not + checked whether the given day of the week matches the actual day of the week + of the given date (though it is technically invalid per the spec if the + day of the week doesn't match the actual day of the week of the given date). + + If the time zone is $(D "-0000") (or considered to be equivalent to + $(D "-0000") by section 4.3 of the spec), a + $(REF SimpleTimeZone,std,datetime,timezone) with a utc offset of $(D 0) is + used rather than $(REF UTC,std,datetime,timezone), whereas $(D "+0000") uses + $(REF UTC,std,datetime,timezone). + + Note that because $(LREF SysTime) does not currently support having a second + value of 60 (as is sometimes done for leap seconds), if the date-time value + does have a value of 60 for the seconds, it is treated as 59. + + The one area in which this function violates RFC 5322 is that it accepts + $(D "\n") in folding whitespace in the place of $(D "\r\n"), because the + HTTP spec requires it. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given string doesn't + follow the grammar for a date-time field or if the resulting + $(LREF SysTime) is invalid. + +/ +SysTime parseRFC822DateTime()(in char[] value) @safe +{ + import std.string : representation; + return parseRFC822DateTime(value.representation); +} + +/++ Ditto +/ +SysTime parseRFC822DateTime(R)(R value) @safe +if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && + (is(Unqual!(ElementType!R) == char) || is(Unqual!(ElementType!R) == ubyte))) +{ + import std.algorithm.searching : find, all; + import std.ascii : isDigit, isAlpha, isPrintable; + import std.conv : to; + import std.functional : not; + import std.string : capitalize, format; + import std.traits : EnumMembers, isArray; + import std.typecons : Rebindable; + + void stripAndCheckLen(R valueBefore, size_t minLen, size_t line = __LINE__) + { + value = _stripCFWS(valueBefore); + if (value.length < minLen) + throw new DateTimeException("date-time value too short", __FILE__, line); + } + stripAndCheckLen(value, "7Dec1200:00A".length); + + static if (isArray!R && (is(ElementEncodingType!R == char) || is(ElementEncodingType!R == ubyte))) + { + static string sliceAsString(R str) @trusted + { + return cast(string) str; + } + } + else + { + char[4] temp; + char[] sliceAsString(R str) @trusted + { + size_t i = 0; + foreach (c; str) + temp[i++] = cast(char) c; + return temp[0 .. str.length]; + } + } + + // day-of-week + if (isAlpha(value[0])) + { + auto dowStr = sliceAsString(value[0 .. 3]); + switch (dowStr) + { + foreach (dow; EnumMembers!DayOfWeek) + { + enum dowC = capitalize(to!string(dow)); + case dowC: + goto afterDoW; + } + default: throw new DateTimeException(format("Invalid day-of-week: %s", dowStr)); + } +afterDoW: stripAndCheckLen(value[3 .. value.length], ",7Dec1200:00A".length); + if (value[0] != ',') + throw new DateTimeException("day-of-week missing comma"); + stripAndCheckLen(value[1 .. value.length], "7Dec1200:00A".length); + } + + // day + immutable digits = isDigit(value[1]) ? 2 : 1; + immutable day = _convDigits!short(value[0 .. digits]); + if (day == -1) + throw new DateTimeException("Invalid day"); + stripAndCheckLen(value[digits .. value.length], "Dec1200:00A".length); + + // month + Month month; + { + auto monStr = sliceAsString(value[0 .. 3]); + switch (monStr) + { + foreach (mon; EnumMembers!Month) + { + enum monC = capitalize(to!string(mon)); + case monC: + { + month = mon; + goto afterMon; + } + } + default: throw new DateTimeException(format("Invalid month: %s", monStr)); + } +afterMon: stripAndCheckLen(value[3 .. value.length], "1200:00A".length); + } + + // year + auto found = value[2 .. value.length].find!(not!(std.ascii.isDigit))(); + size_t yearLen = value.length - found.length; + if (found.length == 0) + throw new DateTimeException("Invalid year"); + if (found[0] == ':') + yearLen -= 2; + auto year = _convDigits!short(value[0 .. yearLen]); + if (year < 1900) + { + if (year == -1) + throw new DateTimeException("Invalid year"); + if (yearLen < 4) + { + if (yearLen == 3) + year += 1900; + else if (yearLen == 2) + year += year < 50 ? 2000 : 1900; + else + throw new DateTimeException("Invalid year. Too few digits."); + } + else + throw new DateTimeException("Invalid year. Cannot be earlier than 1900."); + } + stripAndCheckLen(value[yearLen .. value.length], "00:00A".length); + + // hour + immutable hour = _convDigits!short(value[0 .. 2]); + stripAndCheckLen(value[2 .. value.length], ":00A".length); + if (value[0] != ':') + throw new DateTimeException("Invalid hour"); + stripAndCheckLen(value[1 .. value.length], "00A".length); + + // minute + immutable minute = _convDigits!short(value[0 .. 2]); + stripAndCheckLen(value[2 .. value.length], "A".length); + + // second + short second; + if (value[0] == ':') + { + stripAndCheckLen(value[1 .. value.length], "00A".length); + second = _convDigits!short(value[0 .. 2]); + // this is just if/until SysTime is sorted out to fully support leap seconds + if (second == 60) + second = 59; + stripAndCheckLen(value[2 .. value.length], "A".length); + } + + immutable(TimeZone) parseTZ(int sign) + { + if (value.length < 5) + throw new DateTimeException("Invalid timezone"); + immutable zoneHours = _convDigits!short(value[1 .. 3]); + immutable zoneMinutes = _convDigits!short(value[3 .. 5]); + if (zoneHours == -1 || zoneMinutes == -1 || zoneMinutes > 59) + throw new DateTimeException("Invalid timezone"); + value = value[5 .. value.length]; + immutable utcOffset = (dur!"hours"(zoneHours) + dur!"minutes"(zoneMinutes)) * sign; + if (utcOffset == Duration.zero) + { + return sign == 1 ? cast(immutable(TimeZone))UTC() + : cast(immutable(TimeZone))new immutable SimpleTimeZone(Duration.zero); + } + return new immutable(SimpleTimeZone)(utcOffset); + } + + // zone + Rebindable!(immutable TimeZone) tz; + if (value[0] == '-') + tz = parseTZ(-1); + else if (value[0] == '+') + tz = parseTZ(1); + else + { + // obs-zone + immutable tzLen = value.length - find(value, ' ', '\t', '(')[0].length; + switch (sliceAsString(value[0 .. tzLen <= 4 ? tzLen : 4])) + { + case "UT": case "GMT": tz = UTC(); break; + case "EST": tz = new immutable SimpleTimeZone(dur!"hours"(-5)); break; + case "EDT": tz = new immutable SimpleTimeZone(dur!"hours"(-4)); break; + case "CST": tz = new immutable SimpleTimeZone(dur!"hours"(-6)); break; + case "CDT": tz = new immutable SimpleTimeZone(dur!"hours"(-5)); break; + case "MST": tz = new immutable SimpleTimeZone(dur!"hours"(-7)); break; + case "MDT": tz = new immutable SimpleTimeZone(dur!"hours"(-6)); break; + case "PST": tz = new immutable SimpleTimeZone(dur!"hours"(-8)); break; + case "PDT": tz = new immutable SimpleTimeZone(dur!"hours"(-7)); break; + case "J": case "j": throw new DateTimeException("Invalid timezone"); + default: + { + if (all!(std.ascii.isAlpha)(value[0 .. tzLen])) + { + tz = new immutable SimpleTimeZone(Duration.zero); + break; + } + throw new DateTimeException("Invalid timezone"); + } + } + value = value[tzLen .. value.length]; + } + + // This is kind of arbitrary. Technically, nothing but CFWS is legal past + // the end of the timezone, but we don't want to be picky about that in a + // function that's just parsing rather than validating. So, the idea here is + // that if the next character is printable (and not part of CFWS), then it + // might be part of the timezone and thus affect what the timezone was + // supposed to be, so we'll throw, but otherwise, we'll just ignore it. + if (!value.empty && isPrintable(value[0]) && value[0] != ' ' && value[0] != '(') + throw new DateTimeException("Invalid timezone"); + + try + return SysTime(DateTime(year, month, day, hour, minute, second), tz); + catch (DateTimeException dte) + throw new DateTimeException("date-time format is correct, but the resulting SysTime is invalid.", dte); +} + +/// +@safe unittest +{ + import core.time : hours; + import std.datetime.date : DateTime, DateTimeException; + import std.datetime.timezone : SimpleTimeZone, UTC; + import std.exception : assertThrown; + + auto tz = new immutable SimpleTimeZone(hours(-8)); + assert(parseRFC822DateTime("Sat, 6 Jan 1990 12:14:19 -0800") == + SysTime(DateTime(1990, 1, 6, 12, 14, 19), tz)); + + assert(parseRFC822DateTime("9 Jul 2002 13:11 +0000") == + SysTime(DateTime(2002, 7, 9, 13, 11, 0), UTC())); + + auto badStr = "29 Feb 2001 12:17:16 +0200"; + assertThrown!DateTimeException(parseRFC822DateTime(badStr)); +} + +version (unittest) void testParse822(alias cr)(string str, SysTime expected, size_t line = __LINE__) +{ + import std.format : format; + auto value = cr(str); + auto result = parseRFC822DateTime(value); + if (result != expected) + throw new AssertError(format("wrong result. expected [%s], actual[%s]", expected, result), __FILE__, line); +} + +version (unittest) void testBadParse822(alias cr)(string str, size_t line = __LINE__) +{ + try + parseRFC822DateTime(cr(str)); + catch (DateTimeException) + return; + throw new AssertError("No DateTimeException was thrown", __FILE__, line); +} + +@system unittest +{ + import std.algorithm.iteration : filter, map; + import std.algorithm.searching : canFind; + import std.array : array; + import std.ascii : letters; + import std.format : format; + import std.meta : AliasSeq; + import std.range : chain, iota, take; + import std.stdio : writefln, writeln; + import std.string : representation; + + static struct Rand3Letters + { + enum empty = false; + @property auto front() { return _mon; } + void popFront() + { + import std.exception : assumeUnique; + import std.random : rndGen; + _mon = rndGen.map!(a => letters[a % letters.length])().take(3).array().assumeUnique(); + } + string _mon; + static auto start() { Rand3Letters retval; retval.popFront(); return retval; } + } + + foreach (cr; AliasSeq!(function(string a){return cast(char[]) a;}, + function(string a){return cast(ubyte[]) a;}, + function(string a){return a;}, + function(string a){return map!(b => cast(char) b)(a.representation);})) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + scope(failure) writeln(typeof(cr).stringof); + alias test = testParse822!cr; + alias testBad = testBadParse822!cr; + + immutable std1 = DateTime(2012, 12, 21, 13, 14, 15); + immutable std2 = DateTime(2012, 12, 21, 13, 14, 0); + immutable dst1 = DateTime(1976, 7, 4, 5, 4, 22); + immutable dst2 = DateTime(1976, 7, 4, 5, 4, 0); + + test("21 Dec 2012 13:14:15 +0000", SysTime(std1, UTC())); + test("21 Dec 2012 13:14 +0000", SysTime(std2, UTC())); + test("Fri, 21 Dec 2012 13:14 +0000", SysTime(std2, UTC())); + test("Fri, 21 Dec 2012 13:14:15 +0000", SysTime(std1, UTC())); + + test("04 Jul 1976 05:04:22 +0000", SysTime(dst1, UTC())); + test("04 Jul 1976 05:04 +0000", SysTime(dst2, UTC())); + test("Sun, 04 Jul 1976 05:04 +0000", SysTime(dst2, UTC())); + test("Sun, 04 Jul 1976 05:04:22 +0000", SysTime(dst1, UTC())); + + test("4 Jul 1976 05:04:22 +0000", SysTime(dst1, UTC())); + test("4 Jul 1976 05:04 +0000", SysTime(dst2, UTC())); + test("Sun, 4 Jul 1976 05:04 +0000", SysTime(dst2, UTC())); + test("Sun, 4 Jul 1976 05:04:22 +0000", SysTime(dst1, UTC())); + + auto badTZ = new immutable SimpleTimeZone(Duration.zero); + test("21 Dec 2012 13:14:15 -0000", SysTime(std1, badTZ)); + test("21 Dec 2012 13:14 -0000", SysTime(std2, badTZ)); + test("Fri, 21 Dec 2012 13:14 -0000", SysTime(std2, badTZ)); + test("Fri, 21 Dec 2012 13:14:15 -0000", SysTime(std1, badTZ)); + + test("04 Jul 1976 05:04:22 -0000", SysTime(dst1, badTZ)); + test("04 Jul 1976 05:04 -0000", SysTime(dst2, badTZ)); + test("Sun, 04 Jul 1976 05:04 -0000", SysTime(dst2, badTZ)); + test("Sun, 04 Jul 1976 05:04:22 -0000", SysTime(dst1, badTZ)); + + test("4 Jul 1976 05:04:22 -0000", SysTime(dst1, badTZ)); + test("4 Jul 1976 05:04 -0000", SysTime(dst2, badTZ)); + test("Sun, 4 Jul 1976 05:04 -0000", SysTime(dst2, badTZ)); + test("Sun, 4 Jul 1976 05:04:22 -0000", SysTime(dst1, badTZ)); + + auto pst = new immutable SimpleTimeZone(dur!"hours"(-8)); + auto pdt = new immutable SimpleTimeZone(dur!"hours"(-7)); + test("21 Dec 2012 13:14:15 -0800", SysTime(std1, pst)); + test("21 Dec 2012 13:14 -0800", SysTime(std2, pst)); + test("Fri, 21 Dec 2012 13:14 -0800", SysTime(std2, pst)); + test("Fri, 21 Dec 2012 13:14:15 -0800", SysTime(std1, pst)); + + test("04 Jul 1976 05:04:22 -0700", SysTime(dst1, pdt)); + test("04 Jul 1976 05:04 -0700", SysTime(dst2, pdt)); + test("Sun, 04 Jul 1976 05:04 -0700", SysTime(dst2, pdt)); + test("Sun, 04 Jul 1976 05:04:22 -0700", SysTime(dst1, pdt)); + + test("4 Jul 1976 05:04:22 -0700", SysTime(dst1, pdt)); + test("4 Jul 1976 05:04 -0700", SysTime(dst2, pdt)); + test("Sun, 4 Jul 1976 05:04 -0700", SysTime(dst2, pdt)); + test("Sun, 4 Jul 1976 05:04:22 -0700", SysTime(dst1, pdt)); + + auto cet = new immutable SimpleTimeZone(dur!"hours"(1)); + auto cest = new immutable SimpleTimeZone(dur!"hours"(2)); + test("21 Dec 2012 13:14:15 +0100", SysTime(std1, cet)); + test("21 Dec 2012 13:14 +0100", SysTime(std2, cet)); + test("Fri, 21 Dec 2012 13:14 +0100", SysTime(std2, cet)); + test("Fri, 21 Dec 2012 13:14:15 +0100", SysTime(std1, cet)); + + test("04 Jul 1976 05:04:22 +0200", SysTime(dst1, cest)); + test("04 Jul 1976 05:04 +0200", SysTime(dst2, cest)); + test("Sun, 04 Jul 1976 05:04 +0200", SysTime(dst2, cest)); + test("Sun, 04 Jul 1976 05:04:22 +0200", SysTime(dst1, cest)); + + test("4 Jul 1976 05:04:22 +0200", SysTime(dst1, cest)); + test("4 Jul 1976 05:04 +0200", SysTime(dst2, cest)); + test("Sun, 4 Jul 1976 05:04 +0200", SysTime(dst2, cest)); + test("Sun, 4 Jul 1976 05:04:22 +0200", SysTime(dst1, cest)); + + // dst and std times are switched in the Southern Hemisphere which is why the + // time zone names and DateTime variables don't match. + auto cstStd = new immutable SimpleTimeZone(dur!"hours"(9) + dur!"minutes"(30)); + auto cstDST = new immutable SimpleTimeZone(dur!"hours"(10) + dur!"minutes"(30)); + test("21 Dec 2012 13:14:15 +1030", SysTime(std1, cstDST)); + test("21 Dec 2012 13:14 +1030", SysTime(std2, cstDST)); + test("Fri, 21 Dec 2012 13:14 +1030", SysTime(std2, cstDST)); + test("Fri, 21 Dec 2012 13:14:15 +1030", SysTime(std1, cstDST)); + + test("04 Jul 1976 05:04:22 +0930", SysTime(dst1, cstStd)); + test("04 Jul 1976 05:04 +0930", SysTime(dst2, cstStd)); + test("Sun, 04 Jul 1976 05:04 +0930", SysTime(dst2, cstStd)); + test("Sun, 04 Jul 1976 05:04:22 +0930", SysTime(dst1, cstStd)); + + test("4 Jul 1976 05:04:22 +0930", SysTime(dst1, cstStd)); + test("4 Jul 1976 05:04 +0930", SysTime(dst2, cstStd)); + test("Sun, 4 Jul 1976 05:04 +0930", SysTime(dst2, cstStd)); + test("Sun, 4 Jul 1976 05:04:22 +0930", SysTime(dst1, cstStd)); + + foreach (int i, mon; _monthNames) + { + test(format("17 %s 2012 00:05:02 +0000", mon), SysTime(DateTime(2012, i + 1, 17, 0, 5, 2), UTC())); + test(format("17 %s 2012 00:05 +0000", mon), SysTime(DateTime(2012, i + 1, 17, 0, 5, 0), UTC())); + } + + import std.uni : toLower, toUpper; + foreach (mon; chain(_monthNames[].map!(a => toLower(a))(), + _monthNames[].map!(a => toUpper(a))(), + ["Jam", "Jen", "Fec", "Fdb", "Mas", "Mbr", "Aps", "Aqr", "Mai", "Miy", + "Jum", "Jbn", "Jup", "Jal", "Aur", "Apg", "Sem", "Sap", "Ocm", "Odt", + "Nom", "Nav", "Dem", "Dac"], + Rand3Letters.start().filter!(a => !_monthNames[].canFind(a)).take(20))) + { + scope(failure) writefln("Month: %s", mon); + testBad(format("17 %s 2012 00:05:02 +0000", mon)); + testBad(format("17 %s 2012 00:05 +0000", mon)); + } + + immutable string[7] daysOfWeekNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + + { + auto start = SysTime(DateTime(2012, 11, 11, 9, 42, 0), UTC()); + int day = 11; + + foreach (int i, dow; daysOfWeekNames) + { + auto curr = start + dur!"days"(i); + test(format("%s, %s Nov 2012 09:42:00 +0000", dow, day), curr); + test(format("%s, %s Nov 2012 09:42 +0000", dow, day++), curr); + + // Whether the day of the week matches the date is ignored. + test(format("%s, 11 Nov 2012 09:42:00 +0000", dow), start); + test(format("%s, 11 Nov 2012 09:42 +0000", dow), start); + } + } + + foreach (dow; chain(daysOfWeekNames[].map!(a => toLower(a))(), + daysOfWeekNames[].map!(a => toUpper(a))(), + ["Sum", "Spn", "Mom", "Man", "Tuf", "Tae", "Wem", "Wdd", "The", "Tur", + "Fro", "Fai", "San", "Sut"], + Rand3Letters.start().filter!(a => !daysOfWeekNames[].canFind(a)).take(20))) + { + scope(failure) writefln("Day of Week: %s", dow); + testBad(format("%s, 11 Nov 2012 09:42:00 +0000", dow)); + testBad(format("%s, 11 Nov 2012 09:42 +0000", dow)); + } + + testBad("31 Dec 1899 23:59:59 +0000"); + test("01 Jan 1900 00:00:00 +0000", SysTime(Date(1900, 1, 1), UTC())); + test("01 Jan 1900 00:00:00 -0000", SysTime(Date(1900, 1, 1), + new immutable SimpleTimeZone(Duration.zero))); + test("01 Jan 1900 00:00:00 -0700", SysTime(Date(1900, 1, 1), + new immutable SimpleTimeZone(dur!"hours"(-7)))); + + { + auto st1 = SysTime(Date(1900, 1, 1), UTC()); + auto st2 = SysTime(Date(1900, 1, 1), new immutable SimpleTimeZone(dur!"hours"(-11))); + foreach (i; 1900 .. 2102) + { + test(format("1 Jan %05d 00:00 +0000", i), st1); + test(format("1 Jan %05d 00:00 -1100", i), st2); + st1.add!"years"(1); + st2.add!"years"(1); + } + st1.year = 9998; + st2.year = 9998; + foreach (i; 9998 .. 11_002) + { + test(format("1 Jan %05d 00:00 +0000", i), st1); + test(format("1 Jan %05d 00:00 -1100", i), st2); + st1.add!"years"(1); + st2.add!"years"(1); + } + } + + testBad("12 Feb 1907 23:17:09 0000"); + testBad("12 Feb 1907 23:17:09 +000"); + testBad("12 Feb 1907 23:17:09 -000"); + testBad("12 Feb 1907 23:17:09 +00000"); + testBad("12 Feb 1907 23:17:09 -00000"); + testBad("12 Feb 1907 23:17:09 +A"); + testBad("12 Feb 1907 23:17:09 +PST"); + testBad("12 Feb 1907 23:17:09 -A"); + testBad("12 Feb 1907 23:17:09 -PST"); + + // test trailing stuff that gets ignored + { + foreach (c; chain(iota(0, 33), ['('], iota(127, ubyte.max + 1))) + { + scope(failure) writefln("c: %d", c); + test(format("21 Dec 2012 13:14:15 +0000%c", cast(char) c), SysTime(std1, UTC())); + test(format("21 Dec 2012 13:14:15 +0000%c ", cast(char) c), SysTime(std1, UTC())); + test(format("21 Dec 2012 13:14:15 +0000%chello", cast(char) c), SysTime(std1, UTC())); + } + } + + // test trailing stuff that doesn't get ignored + { + foreach (c; chain(iota(33, '('), iota('(' + 1, 127))) + { + scope(failure) writefln("c: %d", c); + testBad(format("21 Dec 2012 13:14:15 +0000%c", cast(char) c)); + testBad(format("21 Dec 2012 13:14:15 +0000%c ", cast(char) c)); + testBad(format("21 Dec 2012 13:14:15 +0000%chello", cast(char) c)); + } + } + + testBad("32 Jan 2012 12:13:14 -0800"); + testBad("31 Jan 2012 24:13:14 -0800"); + testBad("31 Jan 2012 12:60:14 -0800"); + testBad("31 Jan 2012 12:13:61 -0800"); + testBad("31 Jan 2012 12:13:14 -0860"); + test("31 Jan 2012 12:13:14 -0859", + SysTime(DateTime(2012, 1, 31, 12, 13, 14), + new immutable SimpleTimeZone(dur!"hours"(-8) + dur!"minutes"(-59)))); + + // leap-seconds + test("21 Dec 2012 15:59:60 -0800", SysTime(DateTime(2012, 12, 21, 15, 59, 59), pst)); + + // FWS + test("Sun,4 Jul 1976 05:04 +0930", SysTime(dst2, cstStd)); + test("Sun,4 Jul 1976 05:04:22 +0930", SysTime(dst1, cstStd)); + test("Sun,4 Jul 1976 05:04 +0930 (foo)", SysTime(dst2, cstStd)); + test("Sun,4 Jul 1976 05:04:22 +0930 (foo)", SysTime(dst1, cstStd)); + test("Sun,4 \r\n Jul \r\n 1976 \r\n 05:04 \r\n +0930 \r\n (foo)", SysTime(dst2, cstStd)); + test("Sun,4 \r\n Jul \r\n 1976 \r\n 05:04:22 \r\n +0930 \r\n (foo)", SysTime(dst1, cstStd)); + + auto str = "01 Jan 2012 12:13:14 -0800 "; + test(str, SysTime(DateTime(2012, 1, 1, 12, 13, 14), new immutable SimpleTimeZone(hours(-8)))); + foreach (i; 0 .. str.length) + { + auto currStr = str.dup; + currStr[i] = 'x'; + scope(failure) writefln("failed: %s", currStr); + testBad(cast(string) currStr); + } + foreach (i; 2 .. str.length) + { + auto currStr = str[0 .. $ - i]; + scope(failure) writefln("failed: %s", currStr); + testBad(cast(string) currStr); + testBad((cast(string) currStr) ~ " "); + } + }(); +} + +// Obsolete Format per section 4.3 of RFC 5322. +@system unittest +{ + import std.algorithm.iteration : filter, map; + import std.ascii : letters; + import std.exception : collectExceptionMsg; + import std.format : format; + import std.meta : AliasSeq; + import std.range : chain, iota; + import std.stdio : writefln, writeln; + import std.string : representation; + + auto std1 = SysTime(DateTime(2012, 12, 21, 13, 14, 15), UTC()); + auto std2 = SysTime(DateTime(2012, 12, 21, 13, 14, 0), UTC()); + auto std3 = SysTime(DateTime(1912, 12, 21, 13, 14, 15), UTC()); + auto std4 = SysTime(DateTime(1912, 12, 21, 13, 14, 0), UTC()); + auto dst1 = SysTime(DateTime(1976, 7, 4, 5, 4, 22), UTC()); + auto dst2 = SysTime(DateTime(1976, 7, 4, 5, 4, 0), UTC()); + auto tooLate1 = SysTime(Date(10_000, 1, 1), UTC()); + auto tooLate2 = SysTime(DateTime(12_007, 12, 31, 12, 22, 19), UTC()); + + foreach (cr; AliasSeq!(function(string a){return cast(char[]) a;}, + function(string a){return cast(ubyte[]) a;}, + function(string a){return a;}, + function(string a){return map!(b => cast(char) b)(a.representation);})) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + scope(failure) writeln(typeof(cr).stringof); + alias test = testParse822!cr; + { + auto list = ["", " ", " \r\n\t", "\t\r\n (hello world( frien(dog)) silly \r\n ) \t\t \r\n ()", + " \n ", "\t\n\t", " \n\t (foo) \n (bar) \r\n (baz) \n "]; + + foreach (i, cfws; list) + { + scope(failure) writefln("i: %s", i); + + test(format("%1$s21%1$sDec%1$s2012%1$s13:14:15%1$s+0000%1$s", cfws), std1); + test(format("%1$s21%1$sDec%1$s2012%1$s13:14%1$s+0000%1$s", cfws), std2); + test(format("%1$sFri%1$s,%1$s21%1$sDec%1$s2012%1$s13:14%1$s+0000%1$s", cfws), std2); + test(format("%1$sFri%1$s,%1$s21%1$sDec%1$s2012%1$s13:14:15%1$s+0000%1$s", cfws), std1); + + test(format("%1$s04%1$sJul%1$s1976%1$s05:04:22%1$s+0000%1$s", cfws), dst1); + test(format("%1$s04%1$sJul%1$s1976%1$s05:04%1$s+0000%1$s", cfws), dst2); + test(format("%1$sSun%1$s,%1$s04%1$sJul%1$s1976%1$s05:04%1$s+0000%1$s", cfws), dst2); + test(format("%1$sSun%1$s,%1$s04%1$sJul%1$s1976%1$s05:04:22 +0000%1$s", cfws), dst1); + + test(format("%1$s4%1$sJul%1$s1976%1$s05:04:22%1$s+0000%1$s", cfws), dst1); + test(format("%1$s4%1$sJul%1$s1976%1$s05:04%1$s+0000%1$s", cfws), dst2); + test(format("%1$sSun%1$s,%1$s4%1$sJul%1$s1976%1$s05:04%1$s+0000%1$s", cfws), dst2); + test(format("%1$sSun%1$s,%1$s4%1$sJul%1$s1976%1$s05:04:22%1$s+0000%1$s", cfws), dst1); + + test(format("%1$s21%1$sDec%1$s12%1$s13:14:15%1$s+0000%1$s", cfws), std1); + test(format("%1$s21%1$sDec%1$s12%1$s13:14%1$s+0000%1$s", cfws), std2); + test(format("%1$sFri%1$s,%1$s21%1$sDec%1$s12%1$s13:14%1$s+0000%1$s", cfws), std2); + test(format("%1$sFri%1$s,%1$s21%1$sDec%1$s12%1$s13:14:15%1$s+0000%1$s", cfws), std1); + + test(format("%1$s04%1$sJul%1$s76%1$s05:04:22%1$s+0000%1$s", cfws), dst1); + test(format("%1$s04%1$sJul%1$s76%1$s05:04%1$s+0000%1$s", cfws), dst2); + test(format("%1$sSun%1$s,%1$s04%1$sJul%1$s76%1$s05:04%1$s+0000%1$s", cfws), dst2); + test(format("%1$sSun%1$s,%1$s04%1$sJul%1$s76%1$s05:04:22%1$s+0000%1$s", cfws), dst1); + + test(format("%1$s4%1$sJul%1$s76 05:04:22%1$s+0000%1$s", cfws), dst1); + test(format("%1$s4%1$sJul%1$s76 05:04%1$s+0000%1$s", cfws), dst2); + test(format("%1$sSun%1$s,%1$s4%1$sJul%1$s76%1$s05:04%1$s+0000%1$s", cfws), dst2); + test(format("%1$sSun%1$s,%1$s4%1$sJul%1$s76%1$s05:04:22%1$s+0000%1$s", cfws), dst1); + + test(format("%1$s21%1$sDec%1$s012%1$s13:14:15%1$s+0000%1$s", cfws), std3); + test(format("%1$s21%1$sDec%1$s012%1$s13:14%1$s+0000%1$s", cfws), std4); + test(format("%1$sFri%1$s,%1$s21%1$sDec%1$s012%1$s13:14%1$s+0000%1$s", cfws), std4); + test(format("%1$sFri%1$s,%1$s21%1$sDec%1$s012%1$s13:14:15%1$s+0000%1$s", cfws), std3); + + test(format("%1$s04%1$sJul%1$s076%1$s05:04:22%1$s+0000%1$s", cfws), dst1); + test(format("%1$s04%1$sJul%1$s076%1$s05:04%1$s+0000%1$s", cfws), dst2); + test(format("%1$sSun%1$s,%1$s04%1$sJul%1$s076%1$s05:04%1$s+0000%1$s", cfws), dst2); + test(format("%1$sSun%1$s,%1$s04%1$sJul%1$s076%1$s05:04:22%1$s+0000%1$s", cfws), dst1); + + test(format("%1$s4%1$sJul%1$s076%1$s05:04:22%1$s+0000%1$s", cfws), dst1); + test(format("%1$s4%1$sJul%1$s076%1$s05:04%1$s+0000%1$s", cfws), dst2); + test(format("%1$sSun%1$s,%1$s4%1$sJul%1$s076%1$s05:04%1$s+0000%1$s", cfws), dst2); + test(format("%1$sSun%1$s,%1$s4%1$sJul%1$s076%1$s05:04:22%1$s+0000%1$s", cfws), dst1); + + test(format("%1$s1%1$sJan%1$s10000%1$s00:00:00%1$s+0000%1$s", cfws), tooLate1); + test(format("%1$s31%1$sDec%1$s12007%1$s12:22:19%1$s+0000%1$s", cfws), tooLate2); + test(format("%1$sSat%1$s,%1$s1%1$sJan%1$s10000%1$s00:00:00%1$s+0000%1$s", cfws), tooLate1); + test(format("%1$sSun%1$s,%1$s31%1$sDec%1$s12007%1$s12:22:19%1$s+0000%1$s", cfws), tooLate2); + } + } + + // test years of 1, 2, and 3 digits. + { + auto st1 = SysTime(Date(2000, 1, 1), UTC()); + auto st2 = SysTime(Date(2000, 1, 1), new immutable SimpleTimeZone(dur!"hours"(-12))); + foreach (i; 0 .. 50) + { + test(format("1 Jan %02d 00:00 GMT", i), st1); + test(format("1 Jan %02d 00:00 -1200", i), st2); + st1.add!"years"(1); + st2.add!"years"(1); + } + } + + { + auto st1 = SysTime(Date(1950, 1, 1), UTC()); + auto st2 = SysTime(Date(1950, 1, 1), new immutable SimpleTimeZone(dur!"hours"(-12))); + foreach (i; 50 .. 100) + { + test(format("1 Jan %02d 00:00 GMT", i), st1); + test(format("1 Jan %02d 00:00 -1200", i), st2); + st1.add!"years"(1); + st2.add!"years"(1); + } + } + + { + auto st1 = SysTime(Date(1900, 1, 1), UTC()); + auto st2 = SysTime(Date(1900, 1, 1), new immutable SimpleTimeZone(dur!"hours"(-11))); + foreach (i; 0 .. 1000) + { + test(format("1 Jan %03d 00:00 GMT", i), st1); + test(format("1 Jan %03d 00:00 -1100", i), st2); + st1.add!"years"(1); + st2.add!"years"(1); + } + } + + foreach (i; 0 .. 10) + { + auto str1 = cr(format("1 Jan %d 00:00 GMT", i)); + auto str2 = cr(format("1 Jan %d 00:00 -1200", i)); + assertThrown!DateTimeException(parseRFC822DateTime(str1)); + assertThrown!DateTimeException(parseRFC822DateTime(str1)); + } + + // test time zones + { + auto dt = DateTime(1982, 05, 03, 12, 22, 04); + test("Wed, 03 May 1982 12:22:04 UT", SysTime(dt, UTC())); + test("Wed, 03 May 1982 12:22:04 GMT", SysTime(dt, UTC())); + test("Wed, 03 May 1982 12:22:04 EST", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-5)))); + test("Wed, 03 May 1982 12:22:04 EDT", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-4)))); + test("Wed, 03 May 1982 12:22:04 CST", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-6)))); + test("Wed, 03 May 1982 12:22:04 CDT", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-5)))); + test("Wed, 03 May 1982 12:22:04 MST", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-7)))); + test("Wed, 03 May 1982 12:22:04 MDT", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-6)))); + test("Wed, 03 May 1982 12:22:04 PST", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-8)))); + test("Wed, 03 May 1982 12:22:04 PDT", SysTime(dt, new immutable SimpleTimeZone(dur!"hours"(-7)))); + + auto badTZ = new immutable SimpleTimeZone(Duration.zero); + foreach (dchar c; filter!(a => a != 'j' && a != 'J')(letters)) + { + scope(failure) writefln("c: %s", c); + test(format("Wed, 03 May 1982 12:22:04 %s", c), SysTime(dt, badTZ)); + test(format("Wed, 03 May 1982 12:22:04%s", c), SysTime(dt, badTZ)); + } + + foreach (dchar c; ['j', 'J']) + { + scope(failure) writefln("c: %s", c); + assertThrown!DateTimeException(parseRFC822DateTime(cr(format("Wed, 03 May 1982 12:22:04 %s", c)))); + assertThrown!DateTimeException(parseRFC822DateTime(cr(format("Wed, 03 May 1982 12:22:04%s", c)))); + } + + foreach (string s; ["AAA", "GQW", "DDT", "PDA", "GT", "GM"]) + { + scope(failure) writefln("s: %s", s); + test(format("Wed, 03 May 1982 12:22:04 %s", s), SysTime(dt, badTZ)); + } + + // test trailing stuff that gets ignored + { + foreach (c; chain(iota(0, 33), ['('], iota(127, ubyte.max + 1))) + { + scope(failure) writefln("c: %d", c); + test(format("21Dec1213:14:15+0000%c", cast(char) c), std1); + test(format("21Dec1213:14:15+0000%c ", cast(char) c), std1); + test(format("21Dec1213:14:15+0000%chello", cast(char) c), std1); + } + } + + // test trailing stuff that doesn't get ignored + { + foreach (c; chain(iota(33, '('), iota('(' + 1, 127))) + { + scope(failure) writefln("c: %d", c); + assertThrown!DateTimeException( + parseRFC822DateTime(cr(format("21Dec1213:14:15+0000%c", cast(char) c)))); + assertThrown!DateTimeException( + parseRFC822DateTime(cr(format("21Dec1213:14:15+0000%c ", cast(char) c)))); + assertThrown!DateTimeException( + parseRFC822DateTime(cr(format("21Dec1213:14:15+0000%chello", cast(char) c)))); + } + } + } + + // test that the checks for minimum length work correctly and avoid + // any RangeErrors. + test("7Dec1200:00A", SysTime(DateTime(2012, 12, 7, 00, 00, 00), + new immutable SimpleTimeZone(Duration.zero))); + test("Fri,7Dec1200:00A", SysTime(DateTime(2012, 12, 7, 00, 00, 00), + new immutable SimpleTimeZone(Duration.zero))); + test("7Dec1200:00:00A", SysTime(DateTime(2012, 12, 7, 00, 00, 00), + new immutable SimpleTimeZone(Duration.zero))); + test("Fri,7Dec1200:00:00A", SysTime(DateTime(2012, 12, 7, 00, 00, 00), + new immutable SimpleTimeZone(Duration.zero))); + + auto tooShortMsg = collectExceptionMsg!DateTimeException(parseRFC822DateTime("")); + foreach (str; ["Fri,7Dec1200:00:00", "7Dec1200:00:00"]) + { + foreach (i; 0 .. str.length) + { + auto value = str[0 .. $ - i]; + scope(failure) writeln(value); + assert(collectExceptionMsg!DateTimeException(parseRFC822DateTime(value)) == tooShortMsg); + } + } + }(); +} + + +private: + +/+ + Returns the given hnsecs as an ISO string of fractional seconds. + +/ +static string fracSecsToISOString(int hnsecs) @safe pure nothrow +{ + assert(hnsecs >= 0); + + try + { + if (hnsecs == 0) + return ""; + + string isoString = format(".%07d", hnsecs); + + while (isoString[$ - 1] == '0') + isoString.popBack(); + + return isoString; + } + catch (Exception e) + assert(0, "format() threw."); +} + +@safe unittest +{ + assert(fracSecsToISOString(0) == ""); + assert(fracSecsToISOString(1) == ".0000001"); + assert(fracSecsToISOString(10) == ".000001"); + assert(fracSecsToISOString(100) == ".00001"); + assert(fracSecsToISOString(1000) == ".0001"); + assert(fracSecsToISOString(10_000) == ".001"); + assert(fracSecsToISOString(100_000) == ".01"); + assert(fracSecsToISOString(1_000_000) == ".1"); + assert(fracSecsToISOString(1_000_001) == ".1000001"); + assert(fracSecsToISOString(1_001_001) == ".1001001"); + assert(fracSecsToISOString(1_071_601) == ".1071601"); + assert(fracSecsToISOString(1_271_641) == ".1271641"); + assert(fracSecsToISOString(9_999_999) == ".9999999"); + assert(fracSecsToISOString(9_999_990) == ".999999"); + assert(fracSecsToISOString(9_999_900) == ".99999"); + assert(fracSecsToISOString(9_999_000) == ".9999"); + assert(fracSecsToISOString(9_990_000) == ".999"); + assert(fracSecsToISOString(9_900_000) == ".99"); + assert(fracSecsToISOString(9_000_000) == ".9"); + assert(fracSecsToISOString(999) == ".0000999"); + assert(fracSecsToISOString(9990) == ".000999"); + assert(fracSecsToISOString(99_900) == ".00999"); + assert(fracSecsToISOString(999_000) == ".0999"); +} + + +/+ + Returns a Duration corresponding to to the given ISO string of + fractional seconds. + +/ +static Duration fracSecsFromISOString(S)(in S isoString) @trusted pure +if (isSomeString!S) +{ + import std.algorithm.searching : all; + import std.ascii : isDigit; + import std.conv : to; + import std.string : representation; + + if (isoString.empty) + return Duration.zero; + + auto str = isoString.representation; + + enforce(str[0] == '.', new DateTimeException("Invalid ISO String")); + str.popFront(); + + enforce(!str.empty && all!isDigit(str), new DateTimeException("Invalid ISO String")); + + dchar[7] fullISOString = void; + foreach (i, ref dchar c; fullISOString) + { + if (i < str.length) + c = str[i]; + else + c = '0'; + } + + return hnsecs(to!int(fullISOString[])); +} + +@safe unittest +{ + static void testFSInvalid(string isoString) + { + fracSecsFromISOString(isoString); + } + + assertThrown!DateTimeException(testFSInvalid(".")); + assertThrown!DateTimeException(testFSInvalid("0.")); + assertThrown!DateTimeException(testFSInvalid("0")); + assertThrown!DateTimeException(testFSInvalid("0000000")); + assertThrown!DateTimeException(testFSInvalid("T")); + assertThrown!DateTimeException(testFSInvalid("T.")); + assertThrown!DateTimeException(testFSInvalid(".T")); + assertThrown!DateTimeException(testFSInvalid(".00000Q0")); + assertThrown!DateTimeException(testFSInvalid(".000000Q")); + assertThrown!DateTimeException(testFSInvalid(".0000000Q")); + assertThrown!DateTimeException(testFSInvalid(".0000000000Q")); + + assert(fracSecsFromISOString("") == Duration.zero); + assert(fracSecsFromISOString(".0000001") == hnsecs(1)); + assert(fracSecsFromISOString(".000001") == hnsecs(10)); + assert(fracSecsFromISOString(".00001") == hnsecs(100)); + assert(fracSecsFromISOString(".0001") == hnsecs(1000)); + assert(fracSecsFromISOString(".001") == hnsecs(10_000)); + assert(fracSecsFromISOString(".01") == hnsecs(100_000)); + assert(fracSecsFromISOString(".1") == hnsecs(1_000_000)); + assert(fracSecsFromISOString(".1000001") == hnsecs(1_000_001)); + assert(fracSecsFromISOString(".1001001") == hnsecs(1_001_001)); + assert(fracSecsFromISOString(".1071601") == hnsecs(1_071_601)); + assert(fracSecsFromISOString(".1271641") == hnsecs(1_271_641)); + assert(fracSecsFromISOString(".9999999") == hnsecs(9_999_999)); + assert(fracSecsFromISOString(".9999990") == hnsecs(9_999_990)); + assert(fracSecsFromISOString(".999999") == hnsecs(9_999_990)); + assert(fracSecsFromISOString(".9999900") == hnsecs(9_999_900)); + assert(fracSecsFromISOString(".99999") == hnsecs(9_999_900)); + assert(fracSecsFromISOString(".9999000") == hnsecs(9_999_000)); + assert(fracSecsFromISOString(".9999") == hnsecs(9_999_000)); + assert(fracSecsFromISOString(".9990000") == hnsecs(9_990_000)); + assert(fracSecsFromISOString(".999") == hnsecs(9_990_000)); + assert(fracSecsFromISOString(".9900000") == hnsecs(9_900_000)); + assert(fracSecsFromISOString(".9900") == hnsecs(9_900_000)); + assert(fracSecsFromISOString(".99") == hnsecs(9_900_000)); + assert(fracSecsFromISOString(".9000000") == hnsecs(9_000_000)); + assert(fracSecsFromISOString(".9") == hnsecs(9_000_000)); + assert(fracSecsFromISOString(".0000999") == hnsecs(999)); + assert(fracSecsFromISOString(".0009990") == hnsecs(9990)); + assert(fracSecsFromISOString(".000999") == hnsecs(9990)); + assert(fracSecsFromISOString(".0099900") == hnsecs(99_900)); + assert(fracSecsFromISOString(".00999") == hnsecs(99_900)); + assert(fracSecsFromISOString(".0999000") == hnsecs(999_000)); + assert(fracSecsFromISOString(".0999") == hnsecs(999_000)); + assert(fracSecsFromISOString(".00000000") == Duration.zero); + assert(fracSecsFromISOString(".00000001") == Duration.zero); + assert(fracSecsFromISOString(".00000009") == Duration.zero); + assert(fracSecsFromISOString(".1234567890") == hnsecs(1_234_567)); + assert(fracSecsFromISOString(".12345678901234567890") == hnsecs(1_234_567)); +} + + +/+ + This function is used to split out the units without getting the remaining + hnsecs. + + Params: + units = The units to split out. + hnsecs = The current total hnsecs. + + Returns: + The split out value. + +/ +long getUnitsFromHNSecs(string units)(long hnsecs) @safe pure nothrow +if (validTimeUnits(units) && + CmpTimeUnits!(units, "months") < 0) +{ + return convert!("hnsecs", units)(hnsecs); +} + +@safe unittest +{ + auto hnsecs = 2595000000007L; + immutable days = getUnitsFromHNSecs!"days"(hnsecs); + assert(days == 3); + assert(hnsecs == 2595000000007L); +} + + +/+ + This function is used to split out the units without getting the units but + just the remaining hnsecs. + + Params: + units = The units to split out. + hnsecs = The current total hnsecs. + + Returns: + The remaining hnsecs. + +/ +long removeUnitsFromHNSecs(string units)(long hnsecs) @safe pure nothrow +if (validTimeUnits(units) && + CmpTimeUnits!(units, "months") < 0) +{ + immutable value = convert!("hnsecs", units)(hnsecs); + return hnsecs - convert!(units, "hnsecs")(value); +} + +@safe unittest +{ + auto hnsecs = 2595000000007L; + auto returned = removeUnitsFromHNSecs!"days"(hnsecs); + assert(returned == 3000000007); + assert(hnsecs == 2595000000007L); +} + + +/+ + Strips what RFC 5322, section 3.2.2 refers to as CFWS from the left-hand + side of the given range (it strips comments delimited by $(D '(') and + $(D ')') as well as folding whitespace). + + It is assumed that the given range contains the value of a header field and + no terminating CRLF for the line (though the CRLF for folding whitespace is + of course expected and stripped) and thus that the only case of CR or LF is + in folding whitespace. + + If a comment does not terminate correctly (e.g. mismatched parens) or if the + the FWS is malformed, then the range will be empty when stripCWFS is done. + However, only minimal validation of the content is done (e.g. quoted pairs + within a comment aren't validated beyond \$LPAREN or \$RPAREN, because + they're inside a comment, and thus their value doesn't matter anyway). It's + only when the content does not conform to the grammar rules for FWS and thus + literally cannot be parsed that content is considered invalid, and an empty + range is returned. + + Note that _stripCFWS is eager, not lazy. It does not create a new range. + Rather, it pops off the CFWS from the range and returns it. + +/ +R _stripCFWS(R)(R range) +if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && + (is(Unqual!(ElementType!R) == char) || is(Unqual!(ElementType!R) == ubyte))) +{ + immutable e = range.length; + outer: for (size_t i = 0; i < e; ) + { + switch (range[i]) + { + case ' ': case '\t': + { + ++i; + break; + } + case '\r': + { + if (i + 2 < e && range[i + 1] == '\n' && (range[i + 2] == ' ' || range[i + 2] == '\t')) + { + i += 3; + break; + } + break outer; + } + case '\n': + { + if (i + 1 < e && (range[i + 1] == ' ' || range[i + 1] == '\t')) + { + i += 2; + break; + } + break outer; + } + case '(': + { + ++i; + size_t commentLevel = 1; + while (i < e) + { + if (range[i] == '(') + ++commentLevel; + else if (range[i] == ')') + { + ++i; + if (--commentLevel == 0) + continue outer; + continue; + } + else if (range[i] == '\\') + { + if (++i == e) + break outer; + } + ++i; + } + break outer; + } + default: return range[i .. e]; + } + } + return range[e .. e]; +} + +@system unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map; + import std.meta : AliasSeq; + import std.stdio : writeln; + import std.string : representation; + + foreach (cr; AliasSeq!(function(string a){return cast(ubyte[]) a;}, + function(string a){return map!(b => cast(char) b)(a.representation);})) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + scope(failure) writeln(typeof(cr).stringof); + + assert(_stripCFWS(cr("")).empty); + assert(_stripCFWS(cr("\r")).empty); + assert(_stripCFWS(cr("\r\n")).empty); + assert(_stripCFWS(cr("\r\n ")).empty); + assert(_stripCFWS(cr(" \t\r\n")).empty); + assert(equal(_stripCFWS(cr(" \t\r\n hello")), cr("hello"))); + assert(_stripCFWS(cr(" \t\r\nhello")).empty); + assert(_stripCFWS(cr(" \t\r\n\v")).empty); + assert(equal(_stripCFWS(cr("\v \t\r\n\v")), cr("\v \t\r\n\v"))); + assert(_stripCFWS(cr("()")).empty); + assert(_stripCFWS(cr("(hello world)")).empty); + assert(_stripCFWS(cr("(hello world)(hello world)")).empty); + assert(_stripCFWS(cr("(hello world\r\n foo\r where's\nwaldo)")).empty); + assert(_stripCFWS(cr(" \t (hello \tworld\r\n foo\r where's\nwaldo)\t\t ")).empty); + assert(_stripCFWS(cr(" ")).empty); + assert(_stripCFWS(cr("\t\t\t")).empty); + assert(_stripCFWS(cr("\t \r\n\r \n")).empty); + assert(_stripCFWS(cr("(hello world) (can't find waldo) (he's lost)")).empty); + assert(_stripCFWS(cr("(hello\\) world) (can't \\(find waldo) (he's \\(\\)lost)")).empty); + assert(_stripCFWS(cr("(((((")).empty); + assert(_stripCFWS(cr("(((()))")).empty); + assert(_stripCFWS(cr("(((())))")).empty); + assert(equal(_stripCFWS(cr("(((()))))")), cr(")"))); + assert(equal(_stripCFWS(cr(")))))")), cr(")))))"))); + assert(equal(_stripCFWS(cr("()))))")), cr("))))"))); + assert(equal(_stripCFWS(cr(" hello hello ")), cr("hello hello "))); + assert(equal(_stripCFWS(cr("\thello (world)")), cr("hello (world)"))); + assert(equal(_stripCFWS(cr(" \r\n \\((\\)) foo")), cr("\\((\\)) foo"))); + assert(equal(_stripCFWS(cr(" \r\n (\\((\\))) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" \r\n (\\(())) foo")), cr(") foo"))); + assert(_stripCFWS(cr(" \r\n (((\\))) foo")).empty); + + assert(_stripCFWS(cr("(hello)(hello)")).empty); + assert(_stripCFWS(cr(" \r\n (hello)\r\n (hello)")).empty); + assert(_stripCFWS(cr(" \r\n (hello) \r\n (hello) \r\n ")).empty); + assert(_stripCFWS(cr("\t\t\t\t(hello)\t\t\t\t(hello)\t\t\t\t")).empty); + assert(equal(_stripCFWS(cr(" \r\n (hello)\r\n (hello) \r\n hello")), cr("hello"))); + assert(equal(_stripCFWS(cr(" \r\n (hello) \r\n (hello) \r\n hello")), cr("hello"))); + assert(equal(_stripCFWS(cr("\t\r\n\t(hello)\r\n\t(hello)\t\r\n hello")), cr("hello"))); + assert(equal(_stripCFWS(cr("\t\r\n\t(hello)\t\r\n\t(hello)\t\r\n hello")), cr("hello"))); + assert(equal(_stripCFWS(cr(" \r\n (hello) \r\n \r\n (hello) \r\n hello")), cr("hello"))); + assert(equal(_stripCFWS(cr(" \r\n (hello) \r\n (hello) \r\n \r\n hello")), cr("hello"))); + assert(equal(_stripCFWS(cr(" \r\n \r\n (hello)\t\r\n (hello) \r\n hello")), cr("hello"))); + assert(equal(_stripCFWS(cr(" \r\n\t\r\n\t(hello)\t\r\n (hello) \r\n hello")), cr("hello"))); + + assert(equal(_stripCFWS(cr(" (\r\n ( \r\n ) \r\n ) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n ) \r\n ) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" (\t\r\n ( \r\n ) \r\n ) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" (\r\n\t( \r\n ) \r\n ) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" ( \r\n (\t\r\n ) \r\n ) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" ( \r\n (\r\n ) \r\n ) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" ( \r\n (\r\n\t) \r\n ) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n) \r\n ) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n )\t\r\n ) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n )\r\n ) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n ) \r\n) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n ) \r\n\t) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n ) \r\n ) \r\n foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n ) \r\n )\t\r\n foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" ( \r\n ( \r\n ) \r\n )\r\n foo")), cr("foo"))); + + assert(equal(_stripCFWS(cr(" ( \r\n \r\n ( \r\n \r\n ) \r\n ) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" ( \r\n \r\n ( \r\n \r\n ) \r\n ) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" (\t\r\n \r\n ( \r\n \r\n ) \r\n ) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" (\r\n \r\n\t( \r\n \r\n ) \r\n ) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" (\r\n \r\n( \r\n \r\n ) \r\n ) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" (\r\n \r\n ( \r\n \r\n\t) \r\n ) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" (\r\n \r\n ( \r\n \r\n )\t\r\n ) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" (\r\n \r\n ( \r\n \r\n )\r\n ) foo")), cr("foo"))); + + assert(equal(_stripCFWS(cr(" ( \r\n bar \r\n ( \r\n bar \r\n ) \r\n ) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" ( \r\n () \r\n ( \r\n () \r\n ) \r\n ) foo")), cr("foo"))); + assert(equal(_stripCFWS(cr(" ( \r\n \\\\ \r\n ( \r\n \\\\ \r\n ) \r\n ) foo")), cr("foo"))); + + assert(_stripCFWS(cr("(hello)(hello)")).empty); + assert(_stripCFWS(cr(" \n (hello)\n (hello) \n ")).empty); + assert(_stripCFWS(cr(" \n (hello) \n (hello) \n ")).empty); + assert(equal(_stripCFWS(cr(" \n (hello)\n (hello) \n hello")), cr("hello"))); + assert(equal(_stripCFWS(cr(" \n (hello) \n (hello) \n hello")), cr("hello"))); + assert(equal(_stripCFWS(cr("\t\n\t(hello)\n\t(hello)\t\n hello")), cr("hello"))); + assert(equal(_stripCFWS(cr("\t\n\t(hello)\t\n\t(hello)\t\n hello")), cr("hello"))); + assert(equal(_stripCFWS(cr(" \n (hello) \n \n (hello) \n hello")), cr("hello"))); + assert(equal(_stripCFWS(cr(" \n (hello) \n (hello) \n \n hello")), cr("hello"))); + assert(equal(_stripCFWS(cr(" \n \n (hello)\t\n (hello) \n hello")), cr("hello"))); + assert(equal(_stripCFWS(cr(" \n\t\n\t(hello)\t\n (hello) \n hello")), cr("hello"))); + }(); +} + +// This is so that we don't have to worry about std.conv.to throwing. It also +// doesn't have to worry about quite as many cases as std.conv.to, since it +// doesn't have to worry about a sign on the value or about whether it fits. +T _convDigits(T, R)(R str) +if (isIntegral!T && isSigned!T) // The constraints on R were already covered by parseRFC822DateTime. +{ + import std.ascii : isDigit; + + assert(!str.empty); + T num = 0; + foreach (i; 0 .. str.length) + { + if (i != 0) + num *= 10; + if (!isDigit(str[i])) + return -1; + num += str[i] - '0'; + } + return num; +} + +@safe unittest +{ + import std.conv : to; + import std.range : chain, iota; + import std.stdio : writeln; + foreach (i; chain(iota(0, 101), [250, 999, 1000, 1001, 2345, 9999])) + { + scope(failure) writeln(i); + assert(_convDigits!int(to!string(i)) == i); + } + foreach (str; ["-42", "+42", "1a", "1 ", " ", " 42 "]) + { + scope(failure) writeln(str); + assert(_convDigits!int(str) == -1); + } +} + + +version (unittest) +{ + // Variables to help in testing. + Duration currLocalDiffFromUTC; + immutable (TimeZone)[] testTZs; + + // All of these helper arrays are sorted in ascending order. + auto testYearsBC = [-1999, -1200, -600, -4, -1, 0]; + auto testYearsAD = [1, 4, 1000, 1999, 2000, 2012]; + + // I'd use a Tuple, but I get forward reference errors if I try. + struct MonthDay + { + Month month; + short day; + + this(int m, short d) + { + month = cast(Month) m; + day = d; + } + } + + MonthDay[] testMonthDays = [MonthDay(1, 1), + MonthDay(1, 2), + MonthDay(3, 17), + MonthDay(7, 4), + MonthDay(10, 27), + MonthDay(12, 30), + MonthDay(12, 31)]; + + auto testDays = [1, 2, 9, 10, 16, 20, 25, 28, 29, 30, 31]; + + auto testTODs = [TimeOfDay(0, 0, 0), + TimeOfDay(0, 0, 1), + TimeOfDay(0, 1, 0), + TimeOfDay(1, 0, 0), + TimeOfDay(13, 13, 13), + TimeOfDay(23, 59, 59)]; + + auto testHours = [0, 1, 12, 22, 23]; + auto testMinSecs = [0, 1, 30, 58, 59]; + + // Throwing exceptions is incredibly expensive, so we want to use a smaller + // set of values for tests using assertThrown. + auto testTODsThrown = [TimeOfDay(0, 0, 0), + TimeOfDay(13, 13, 13), + TimeOfDay(23, 59, 59)]; + + Date[] testDatesBC; + Date[] testDatesAD; + + DateTime[] testDateTimesBC; + DateTime[] testDateTimesAD; + + Duration[] testFracSecs; + + SysTime[] testSysTimesBC; + SysTime[] testSysTimesAD; + + // I'd use a Tuple, but I get forward reference errors if I try. + struct GregDay { int day; Date date; } + auto testGregDaysBC = [GregDay(-1_373_427, Date(-3760, 9, 7)), // Start of the Hebrew Calendar + GregDay(-735_233, Date(-2012, 1, 1)), + GregDay(-735_202, Date(-2012, 2, 1)), + GregDay(-735_175, Date(-2012, 2, 28)), + GregDay(-735_174, Date(-2012, 2, 29)), + GregDay(-735_173, Date(-2012, 3, 1)), + GregDay(-734_502, Date(-2010, 1, 1)), + GregDay(-734_472, Date(-2010, 1, 31)), + GregDay(-734_471, Date(-2010, 2, 1)), + GregDay(-734_444, Date(-2010, 2, 28)), + GregDay(-734_443, Date(-2010, 3, 1)), + GregDay(-734_413, Date(-2010, 3, 31)), + GregDay(-734_412, Date(-2010, 4, 1)), + GregDay(-734_383, Date(-2010, 4, 30)), + GregDay(-734_382, Date(-2010, 5, 1)), + GregDay(-734_352, Date(-2010, 5, 31)), + GregDay(-734_351, Date(-2010, 6, 1)), + GregDay(-734_322, Date(-2010, 6, 30)), + GregDay(-734_321, Date(-2010, 7, 1)), + GregDay(-734_291, Date(-2010, 7, 31)), + GregDay(-734_290, Date(-2010, 8, 1)), + GregDay(-734_260, Date(-2010, 8, 31)), + GregDay(-734_259, Date(-2010, 9, 1)), + GregDay(-734_230, Date(-2010, 9, 30)), + GregDay(-734_229, Date(-2010, 10, 1)), + GregDay(-734_199, Date(-2010, 10, 31)), + GregDay(-734_198, Date(-2010, 11, 1)), + GregDay(-734_169, Date(-2010, 11, 30)), + GregDay(-734_168, Date(-2010, 12, 1)), + GregDay(-734_139, Date(-2010, 12, 30)), + GregDay(-734_138, Date(-2010, 12, 31)), + GregDay(-731_215, Date(-2001, 1, 1)), + GregDay(-730_850, Date(-2000, 1, 1)), + GregDay(-730_849, Date(-2000, 1, 2)), + GregDay(-730_486, Date(-2000, 12, 30)), + GregDay(-730_485, Date(-2000, 12, 31)), + GregDay(-730_484, Date(-1999, 1, 1)), + GregDay(-694_690, Date(-1901, 1, 1)), + GregDay(-694_325, Date(-1900, 1, 1)), + GregDay(-585_118, Date(-1601, 1, 1)), + GregDay(-584_753, Date(-1600, 1, 1)), + GregDay(-584_388, Date(-1600, 12, 31)), + GregDay(-584_387, Date(-1599, 1, 1)), + GregDay(-365_972, Date(-1001, 1, 1)), + GregDay(-365_607, Date(-1000, 1, 1)), + GregDay(-183_351, Date(-501, 1, 1)), + GregDay(-182_986, Date(-500, 1, 1)), + GregDay(-182_621, Date(-499, 1, 1)), + GregDay(-146_827, Date(-401, 1, 1)), + GregDay(-146_462, Date(-400, 1, 1)), + GregDay(-146_097, Date(-400, 12, 31)), + GregDay(-110_302, Date(-301, 1, 1)), + GregDay(-109_937, Date(-300, 1, 1)), + GregDay(-73_778, Date(-201, 1, 1)), + GregDay(-73_413, Date(-200, 1, 1)), + GregDay(-38_715, Date(-105, 1, 1)), + GregDay(-37_254, Date(-101, 1, 1)), + GregDay(-36_889, Date(-100, 1, 1)), + GregDay(-36_524, Date(-99, 1, 1)), + GregDay(-36_160, Date(-99, 12, 31)), + GregDay(-35_794, Date(-97, 1, 1)), + GregDay(-18_627, Date(-50, 1, 1)), + GregDay(-18_262, Date(-49, 1, 1)), + GregDay(-3652, Date(-9, 1, 1)), + GregDay(-2191, Date(-5, 1, 1)), + GregDay(-1827, Date(-5, 12, 31)), + GregDay(-1826, Date(-4, 1, 1)), + GregDay(-1825, Date(-4, 1, 2)), + GregDay(-1462, Date(-4, 12, 30)), + GregDay(-1461, Date(-4, 12, 31)), + GregDay(-1460, Date(-3, 1, 1)), + GregDay(-1096, Date(-3, 12, 31)), + GregDay(-1095, Date(-2, 1, 1)), + GregDay(-731, Date(-2, 12, 31)), + GregDay(-730, Date(-1, 1, 1)), + GregDay(-367, Date(-1, 12, 30)), + GregDay(-366, Date(-1, 12, 31)), + GregDay(-365, Date(0, 1, 1)), + GregDay(-31, Date(0, 11, 30)), + GregDay(-30, Date(0, 12, 1)), + GregDay(-1, Date(0, 12, 30)), + GregDay(0, Date(0, 12, 31))]; + + auto testGregDaysAD = [GregDay(1, Date(1, 1, 1)), + GregDay(2, Date(1, 1, 2)), + GregDay(32, Date(1, 2, 1)), + GregDay(365, Date(1, 12, 31)), + GregDay(366, Date(2, 1, 1)), + GregDay(731, Date(3, 1, 1)), + GregDay(1096, Date(4, 1, 1)), + GregDay(1097, Date(4, 1, 2)), + GregDay(1460, Date(4, 12, 30)), + GregDay(1461, Date(4, 12, 31)), + GregDay(1462, Date(5, 1, 1)), + GregDay(17_898, Date(50, 1, 1)), + GregDay(35_065, Date(97, 1, 1)), + GregDay(36_160, Date(100, 1, 1)), + GregDay(36_525, Date(101, 1, 1)), + GregDay(37_986, Date(105, 1, 1)), + GregDay(72_684, Date(200, 1, 1)), + GregDay(73_049, Date(201, 1, 1)), + GregDay(109_208, Date(300, 1, 1)), + GregDay(109_573, Date(301, 1, 1)), + GregDay(145_732, Date(400, 1, 1)), + GregDay(146_098, Date(401, 1, 1)), + GregDay(182_257, Date(500, 1, 1)), + GregDay(182_622, Date(501, 1, 1)), + GregDay(364_878, Date(1000, 1, 1)), + GregDay(365_243, Date(1001, 1, 1)), + GregDay(584_023, Date(1600, 1, 1)), + GregDay(584_389, Date(1601, 1, 1)), + GregDay(693_596, Date(1900, 1, 1)), + GregDay(693_961, Date(1901, 1, 1)), + GregDay(729_755, Date(1999, 1, 1)), + GregDay(730_120, Date(2000, 1, 1)), + GregDay(730_121, Date(2000, 1, 2)), + GregDay(730_484, Date(2000, 12, 30)), + GregDay(730_485, Date(2000, 12, 31)), + GregDay(730_486, Date(2001, 1, 1)), + GregDay(733_773, Date(2010, 1, 1)), + GregDay(733_774, Date(2010, 1, 2)), + GregDay(733_803, Date(2010, 1, 31)), + GregDay(733_804, Date(2010, 2, 1)), + GregDay(733_831, Date(2010, 2, 28)), + GregDay(733_832, Date(2010, 3, 1)), + GregDay(733_862, Date(2010, 3, 31)), + GregDay(733_863, Date(2010, 4, 1)), + GregDay(733_892, Date(2010, 4, 30)), + GregDay(733_893, Date(2010, 5, 1)), + GregDay(733_923, Date(2010, 5, 31)), + GregDay(733_924, Date(2010, 6, 1)), + GregDay(733_953, Date(2010, 6, 30)), + GregDay(733_954, Date(2010, 7, 1)), + GregDay(733_984, Date(2010, 7, 31)), + GregDay(733_985, Date(2010, 8, 1)), + GregDay(734_015, Date(2010, 8, 31)), + GregDay(734_016, Date(2010, 9, 1)), + GregDay(734_045, Date(2010, 9, 30)), + GregDay(734_046, Date(2010, 10, 1)), + GregDay(734_076, Date(2010, 10, 31)), + GregDay(734_077, Date(2010, 11, 1)), + GregDay(734_106, Date(2010, 11, 30)), + GregDay(734_107, Date(2010, 12, 1)), + GregDay(734_136, Date(2010, 12, 30)), + GregDay(734_137, Date(2010, 12, 31)), + GregDay(734_503, Date(2012, 1, 1)), + GregDay(734_534, Date(2012, 2, 1)), + GregDay(734_561, Date(2012, 2, 28)), + GregDay(734_562, Date(2012, 2, 29)), + GregDay(734_563, Date(2012, 3, 1)), + GregDay(734_858, Date(2012, 12, 21))]; + + // I'd use a Tuple, but I get forward reference errors if I try. + struct DayOfYear { int day; MonthDay md; } + auto testDaysOfYear = [DayOfYear(1, MonthDay(1, 1)), + DayOfYear(2, MonthDay(1, 2)), + DayOfYear(3, MonthDay(1, 3)), + DayOfYear(31, MonthDay(1, 31)), + DayOfYear(32, MonthDay(2, 1)), + DayOfYear(59, MonthDay(2, 28)), + DayOfYear(60, MonthDay(3, 1)), + DayOfYear(90, MonthDay(3, 31)), + DayOfYear(91, MonthDay(4, 1)), + DayOfYear(120, MonthDay(4, 30)), + DayOfYear(121, MonthDay(5, 1)), + DayOfYear(151, MonthDay(5, 31)), + DayOfYear(152, MonthDay(6, 1)), + DayOfYear(181, MonthDay(6, 30)), + DayOfYear(182, MonthDay(7, 1)), + DayOfYear(212, MonthDay(7, 31)), + DayOfYear(213, MonthDay(8, 1)), + DayOfYear(243, MonthDay(8, 31)), + DayOfYear(244, MonthDay(9, 1)), + DayOfYear(273, MonthDay(9, 30)), + DayOfYear(274, MonthDay(10, 1)), + DayOfYear(304, MonthDay(10, 31)), + DayOfYear(305, MonthDay(11, 1)), + DayOfYear(334, MonthDay(11, 30)), + DayOfYear(335, MonthDay(12, 1)), + DayOfYear(363, MonthDay(12, 29)), + DayOfYear(364, MonthDay(12, 30)), + DayOfYear(365, MonthDay(12, 31))]; + + auto testDaysOfLeapYear = [DayOfYear(1, MonthDay(1, 1)), + DayOfYear(2, MonthDay(1, 2)), + DayOfYear(3, MonthDay(1, 3)), + DayOfYear(31, MonthDay(1, 31)), + DayOfYear(32, MonthDay(2, 1)), + DayOfYear(59, MonthDay(2, 28)), + DayOfYear(60, MonthDay(2, 29)), + DayOfYear(61, MonthDay(3, 1)), + DayOfYear(91, MonthDay(3, 31)), + DayOfYear(92, MonthDay(4, 1)), + DayOfYear(121, MonthDay(4, 30)), + DayOfYear(122, MonthDay(5, 1)), + DayOfYear(152, MonthDay(5, 31)), + DayOfYear(153, MonthDay(6, 1)), + DayOfYear(182, MonthDay(6, 30)), + DayOfYear(183, MonthDay(7, 1)), + DayOfYear(213, MonthDay(7, 31)), + DayOfYear(214, MonthDay(8, 1)), + DayOfYear(244, MonthDay(8, 31)), + DayOfYear(245, MonthDay(9, 1)), + DayOfYear(274, MonthDay(9, 30)), + DayOfYear(275, MonthDay(10, 1)), + DayOfYear(305, MonthDay(10, 31)), + DayOfYear(306, MonthDay(11, 1)), + DayOfYear(335, MonthDay(11, 30)), + DayOfYear(336, MonthDay(12, 1)), + DayOfYear(364, MonthDay(12, 29)), + DayOfYear(365, MonthDay(12, 30)), + DayOfYear(366, MonthDay(12, 31))]; + + void initializeTests() @safe + { + import std.algorithm.sorting : sort; + import std.typecons : Rebindable; + immutable lt = LocalTime().utcToTZ(0); + currLocalDiffFromUTC = dur!"hnsecs"(lt); + + version (Posix) + { + immutable otherTZ = lt < 0 ? PosixTimeZone.getTimeZone("Australia/Sydney") + : PosixTimeZone.getTimeZone("America/Denver"); + } + else version (Windows) + { + immutable otherTZ = lt < 0 ? WindowsTimeZone.getTimeZone("AUS Eastern Standard Time") + : WindowsTimeZone.getTimeZone("Mountain Standard Time"); + } + + immutable ot = otherTZ.utcToTZ(0); + + auto diffs = [0L, lt, ot]; + auto diffAA = [0L : Rebindable!(immutable TimeZone)(UTC())]; + diffAA[lt] = Rebindable!(immutable TimeZone)(LocalTime()); + diffAA[ot] = Rebindable!(immutable TimeZone)(otherTZ); + + sort(diffs); + testTZs = [diffAA[diffs[0]], diffAA[diffs[1]], diffAA[diffs[2]]]; + + testFracSecs = [Duration.zero, hnsecs(1), hnsecs(5007), hnsecs(9_999_999)]; + + foreach (year; testYearsBC) + { + foreach (md; testMonthDays) + testDatesBC ~= Date(year, md.month, md.day); + } + + foreach (year; testYearsAD) + { + foreach (md; testMonthDays) + testDatesAD ~= Date(year, md.month, md.day); + } + + foreach (dt; testDatesBC) + { + foreach (tod; testTODs) + testDateTimesBC ~= DateTime(dt, tod); + } + + foreach (dt; testDatesAD) + { + foreach (tod; testTODs) + testDateTimesAD ~= DateTime(dt, tod); + } + + foreach (dt; testDateTimesBC) + { + foreach (tz; testTZs) + { + foreach (fs; testFracSecs) + testSysTimesBC ~= SysTime(dt, fs, tz); + } + } + + foreach (dt; testDateTimesAD) + { + foreach (tz; testTZs) + { + foreach (fs; testFracSecs) + testSysTimesAD ~= SysTime(dt, fs, tz); + } + } + } +} diff --git a/libphobos/src/std/datetime/timezone.d b/libphobos/src/std/datetime/timezone.d new file mode 100644 index 0000000..fb06262 --- /dev/null +++ b/libphobos/src/std/datetime/timezone.d @@ -0,0 +1,4235 @@ +// Written in the D programming language + +/++ + License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + Authors: Jonathan M Davis + Source: $(PHOBOSSRC std/datetime/_timezone.d) ++/ +module std.datetime.timezone; + +import core.time; +import std.datetime.date; +import std.datetime.systime; +import std.exception : enforce; +import std.range.primitives; +import std.traits : isIntegral, isSomeString, Unqual; + +version (Windows) +{ + import core.stdc.time : time_t; + import core.sys.windows.windows; + import core.sys.windows.winsock2; + import std.windows.registry; + + // Uncomment and run unittests to print missing Windows TZ translations. + // Please subscribe to Microsoft Daylight Saving Time & Time Zone Blog + // (https://blogs.technet.microsoft.com/dst2007/) if you feel responsible + // for updating the translations. + // version = UpdateWindowsTZTranslations; +} +else version (Posix) +{ + import core.sys.posix.signal : timespec; + import core.sys.posix.sys.types : time_t; +} + +version (unittest) import std.exception : assertThrown; + + +/++ + Represents a time zone. It is used with $(REF SysTime,std,datetime,systime) + to indicate the time zone of a $(REF SysTime,std,datetime,systime). + +/ +abstract class TimeZone +{ +public: + + /++ + The name of the time zone per the TZ Database. This is the name used to + get a $(LREF TimeZone) by name with $(D TimeZone.getTimeZone). + + See_Also: + $(HTTP en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ + Database)
+ $(HTTP en.wikipedia.org/wiki/List_of_tz_database_time_zones, List of + Time Zones) + +/ + @property string name() @safe const nothrow + { + return _name; + } + + + /++ + Typically, the abbreviation (generally 3 or 4 letters) for the time zone + when DST is $(I not) in effect (e.g. PST). It is not necessarily unique. + + However, on Windows, it may be the unabbreviated name (e.g. Pacific + Standard Time). Regardless, it is not the same as name. + +/ + @property string stdName() @safe const nothrow + { + return _stdName; + } + + + /++ + Typically, the abbreviation (generally 3 or 4 letters) for the time zone + when DST $(I is) in effect (e.g. PDT). It is not necessarily unique. + + However, on Windows, it may be the unabbreviated name (e.g. Pacific + Daylight Time). Regardless, it is not the same as name. + +/ + @property string dstName() @safe const nothrow + { + return _dstName; + } + + + /++ + Whether this time zone has Daylight Savings Time at any point in time. + Note that for some time zone types it may not have DST for current dates + but will still return true for $(D hasDST) because the time zone did at + some point have DST. + +/ + @property abstract bool hasDST() @safe const nothrow; + + + /++ + Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D. + in UTC time (i.e. std time) and returns whether DST is effect in this + time zone at the given point in time. + + Params: + stdTime = The UTC time that needs to be checked for DST in this time + zone. + +/ + abstract bool dstInEffect(long stdTime) @safe const nothrow; + + + /++ + Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D. + in UTC time (i.e. std time) and converts it to this time zone's time. + + Params: + stdTime = The UTC time that needs to be adjusted to this time zone's + time. + +/ + abstract long utcToTZ(long stdTime) @safe const nothrow; + + + /++ + Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D. + in this time zone's time and converts it to UTC (i.e. std time). + + Params: + adjTime = The time in this time zone that needs to be adjusted to + UTC time. + +/ + abstract long tzToUTC(long adjTime) @safe const nothrow; + + + /++ + Returns what the offset from UTC is at the given std time. + It includes the DST offset in effect at that time (if any). + + Params: + stdTime = The UTC time for which to get the offset from UTC for this + time zone. + +/ + Duration utcOffsetAt(long stdTime) @safe const nothrow + { + return dur!"hnsecs"(utcToTZ(stdTime) - stdTime); + } + + // Explicitly undocumented. It will be removed in June 2018. @@@DEPRECATED_2018-07@@@ + deprecated("Use PosixTimeZone.getTimeZone or WindowsTimeZone.getTimeZone instead") + static immutable(TimeZone) getTimeZone(string name) @safe + { + version (Posix) + return PosixTimeZone.getTimeZone(name); + else version (Windows) + { + import std.format : format; + auto windowsTZName = tzDatabaseNameToWindowsTZName(name); + if (windowsTZName != null) + { + try + return WindowsTimeZone.getTimeZone(windowsTZName); + catch (DateTimeException dte) + { + auto oldName = _getOldName(windowsTZName); + if (oldName != null) + return WindowsTimeZone.getTimeZone(oldName); + throw dte; + } + } + else + throw new DateTimeException(format("%s does not have an equivalent Windows time zone.", name)); + } + } + + /// + deprecated @safe unittest + { + auto tz = TimeZone.getTimeZone("America/Los_Angeles"); + } + + // The purpose of this is to handle the case where a Windows time zone is + // new and exists on an up-to-date Windows box but does not exist on Windows + // boxes which have not been properly updated. The "date added" is included + // on the theory that we'll be able to remove them at some point in the + // the future once enough time has passed, and that way, we know how much + // time has passed. + private static string _getOldName(string windowsTZName) @safe pure nothrow + { + switch (windowsTZName) + { + case "Belarus Standard Time": return "Kaliningrad Standard Time"; // Added 2014-10-08 + case "Russia Time Zone 10": return "Magadan Standard Time"; // Added 2014-10-08 + case "Russia Time Zone 11": return "Magadan Standard Time"; // Added 2014-10-08 + case "Russia Time Zone 3": return "Russian Standard Time"; // Added 2014-10-08 + default: return null; + } + } + + // Since reading in the time zone files could be expensive, most unit tests + // are consolidated into this one unittest block which minimizes how often + // it reads a time zone file. + @system unittest + { + import core.exception : AssertError; + import std.conv : to; + import std.file : exists, isFile; + import std.format : format; + import std.path : chainPath; + import std.stdio : writefln; + import std.typecons : tuple; + + version (Posix) alias getTimeZone = PosixTimeZone.getTimeZone; + else version (Windows) alias getTimeZone = WindowsTimeZone.getTimeZone; + + version (Posix) scope(exit) clearTZEnvVar(); + + static immutable(TimeZone) testTZ(string tzName, + string stdName, + string dstName, + Duration utcOffset, + Duration dstOffset, + bool north = true) + { + scope(failure) writefln("Failed time zone: %s", tzName); + + version (Posix) + { + immutable tz = PosixTimeZone.getTimeZone(tzName); + assert(tz.name == tzName); + } + else version (Windows) + { + immutable tz = WindowsTimeZone.getTimeZone(tzName); + assert(tz.name == stdName); + } + + immutable hasDST = dstOffset != Duration.zero; + + //assert(tz.stdName == stdName); //Locale-dependent + //assert(tz.dstName == dstName); //Locale-dependent + assert(tz.hasDST == hasDST); + + immutable stdDate = DateTime(2010, north ? 1 : 7, 1, 6, 0, 0); + immutable dstDate = DateTime(2010, north ? 7 : 1, 1, 6, 0, 0); + auto std = SysTime(stdDate, tz); + auto dst = SysTime(dstDate, tz); + auto stdUTC = SysTime(stdDate - utcOffset, UTC()); + auto dstUTC = SysTime(stdDate - utcOffset + dstOffset, UTC()); + + assert(!std.dstInEffect); + assert(dst.dstInEffect == hasDST); + assert(tz.utcOffsetAt(std.stdTime) == utcOffset); + assert(tz.utcOffsetAt(dst.stdTime) == utcOffset + dstOffset); + + assert(cast(DateTime) std == stdDate); + assert(cast(DateTime) dst == dstDate); + assert(std == stdUTC); + + version (Posix) + { + setTZEnvVar(tzName); + + static void testTM(in SysTime st) + { + import core.stdc.time : localtime, tm; + time_t unixTime = st.toUnixTime(); + tm* osTimeInfo = localtime(&unixTime); + tm ourTimeInfo = st.toTM(); + + assert(ourTimeInfo.tm_sec == osTimeInfo.tm_sec); + assert(ourTimeInfo.tm_min == osTimeInfo.tm_min); + assert(ourTimeInfo.tm_hour == osTimeInfo.tm_hour); + assert(ourTimeInfo.tm_mday == osTimeInfo.tm_mday); + assert(ourTimeInfo.tm_mon == osTimeInfo.tm_mon); + assert(ourTimeInfo.tm_year == osTimeInfo.tm_year); + assert(ourTimeInfo.tm_wday == osTimeInfo.tm_wday); + assert(ourTimeInfo.tm_yday == osTimeInfo.tm_yday); + assert(ourTimeInfo.tm_isdst == osTimeInfo.tm_isdst); + assert(ourTimeInfo.tm_gmtoff == osTimeInfo.tm_gmtoff); + assert(to!string(ourTimeInfo.tm_zone) == to!string(osTimeInfo.tm_zone)); + } + + testTM(std); + testTM(dst); + + // Apparently, right/ does not exist on Mac OS X. I don't know + // whether or not it exists on FreeBSD. It's rather pointless + // normally, since the Posix standard requires that leap seconds + // be ignored, so it does make some sense that right/ wouldn't + // be there, but since PosixTimeZone _does_ use leap seconds if + // the time zone file does, we'll test that functionality if the + // appropriate files exist. + if (chainPath(PosixTimeZone.defaultTZDatabaseDir, "right", tzName).exists) + { + auto leapTZ = PosixTimeZone.getTimeZone("right/" ~ tzName); + + assert(leapTZ.name == "right/" ~ tzName); + //assert(leapTZ.stdName == stdName); //Locale-dependent + //assert(leapTZ.dstName == dstName); //Locale-dependent + assert(leapTZ.hasDST == hasDST); + + auto leapSTD = SysTime(std.stdTime, leapTZ); + auto leapDST = SysTime(dst.stdTime, leapTZ); + + assert(!leapSTD.dstInEffect); + assert(leapDST.dstInEffect == hasDST); + + assert(leapSTD.stdTime == std.stdTime); + assert(leapDST.stdTime == dst.stdTime); + + // Whenever a leap second is added/removed, + // this will have to be adjusted. + //enum leapDiff = convert!("seconds", "hnsecs")(25); + //assert(leapSTD.adjTime - leapDiff == std.adjTime); + //assert(leapDST.adjTime - leapDiff == dst.adjTime); + } + } + + return tz; + } + + auto dstSwitches = [/+America/Los_Angeles+/ tuple(DateTime(2012, 3, 11), DateTime(2012, 11, 4), 2, 2), + /+America/New_York+/ tuple(DateTime(2012, 3, 11), DateTime(2012, 11, 4), 2, 2), + ///+America/Santiago+/ tuple(DateTime(2011, 8, 21), DateTime(2011, 5, 8), 0, 0), + /+Europe/London+/ tuple(DateTime(2012, 3, 25), DateTime(2012, 10, 28), 1, 2), + /+Europe/Paris+/ tuple(DateTime(2012, 3, 25), DateTime(2012, 10, 28), 2, 3), + /+Australia/Adelaide+/ tuple(DateTime(2012, 10, 7), DateTime(2012, 4, 1), 2, 3)]; + + version (Posix) + { + version (FreeBSD) enum utcZone = "Etc/UTC"; + else version (NetBSD) enum utcZone = "UTC"; + else version (linux) enum utcZone = "UTC"; + else version (OSX) enum utcZone = "UTC"; + else static assert(0, "The location of the UTC timezone file on this Posix platform must be set."); + + auto tzs = [testTZ("America/Los_Angeles", "PST", "PDT", dur!"hours"(-8), dur!"hours"(1)), + testTZ("America/New_York", "EST", "EDT", dur!"hours"(-5), dur!"hours"(1)), + //testTZ("America/Santiago", "CLT", "CLST", dur!"hours"(-4), dur!"hours"(1), false), + testTZ("Europe/London", "GMT", "BST", dur!"hours"(0), dur!"hours"(1)), + testTZ("Europe/Paris", "CET", "CEST", dur!"hours"(1), dur!"hours"(1)), + // Per www.timeanddate.com, it should be "CST" and "CDT", + // but the OS insists that it's "CST" for both. We should + // probably figure out how to report an error in the TZ + // database and report it. + testTZ("Australia/Adelaide", "CST", "CST", + dur!"hours"(9) + dur!"minutes"(30), dur!"hours"(1), false)]; + + testTZ(utcZone, "UTC", "UTC", dur!"hours"(0), dur!"hours"(0)); + assertThrown!DateTimeException(PosixTimeZone.getTimeZone("hello_world")); + } + else version (Windows) + { + auto tzs = [testTZ("Pacific Standard Time", "Pacific Standard Time", + "Pacific Daylight Time", dur!"hours"(-8), dur!"hours"(1)), + testTZ("Eastern Standard Time", "Eastern Standard Time", + "Eastern Daylight Time", dur!"hours"(-5), dur!"hours"(1)), + //testTZ("Pacific SA Standard Time", "Pacific SA Standard Time", + //"Pacific SA Daylight Time", dur!"hours"(-4), dur!"hours"(1), false), + testTZ("GMT Standard Time", "GMT Standard Time", + "GMT Daylight Time", dur!"hours"(0), dur!"hours"(1)), + testTZ("Romance Standard Time", "Romance Standard Time", + "Romance Daylight Time", dur!"hours"(1), dur!"hours"(1)), + testTZ("Cen. Australia Standard Time", "Cen. Australia Standard Time", + "Cen. Australia Daylight Time", + dur!"hours"(9) + dur!"minutes"(30), dur!"hours"(1), false)]; + + testTZ("Greenwich Standard Time", "Greenwich Standard Time", + "Greenwich Daylight Time", dur!"hours"(0), dur!"hours"(0)); + assertThrown!DateTimeException(WindowsTimeZone.getTimeZone("hello_world")); + } + else + assert(0, "OS not supported."); + + foreach (i; 0 .. tzs.length) + { + auto tz = tzs[i]; + immutable spring = dstSwitches[i][2]; + immutable fall = dstSwitches[i][3]; + auto stdOffset = SysTime(dstSwitches[i][0] + dur!"days"(-1), tz).utcOffset; + auto dstOffset = stdOffset + dur!"hours"(1); + + // Verify that creating a SysTime in the given time zone results + // in a SysTime with the correct std time during and surrounding + // a DST switch. + foreach (hour; -12 .. 13) + { + auto st = SysTime(dstSwitches[i][0] + dur!"hours"(hour), tz); + immutable targetHour = hour < 0 ? hour + 24 : hour; + + static void testHour(SysTime st, int hour, string tzName, size_t line = __LINE__) + { + enforce(st.hour == hour, + new AssertError(format("[%s] [%s]: [%s] [%s]", st, tzName, st.hour, hour), + __FILE__, line)); + } + + void testOffset1(Duration offset, bool dstInEffect, size_t line = __LINE__) + { + AssertError msg(string tag) + { + return new AssertError(format("%s [%s] [%s]: [%s] [%s] [%s]", + tag, st, tz.name, st.utcOffset, stdOffset, dstOffset), + __FILE__, line); + } + + enforce(st.dstInEffect == dstInEffect, msg("1")); + enforce(st.utcOffset == offset, msg("2")); + enforce((st + dur!"minutes"(1)).utcOffset == offset, msg("3")); + } + + if (hour == spring) + { + testHour(st, spring + 1, tz.name); + testHour(st + dur!"minutes"(1), spring + 1, tz.name); + } + else + { + testHour(st, targetHour, tz.name); + testHour(st + dur!"minutes"(1), targetHour, tz.name); + } + + if (hour < spring) + testOffset1(stdOffset, false); + else + testOffset1(dstOffset, true); + + st = SysTime(dstSwitches[i][1] + dur!"hours"(hour), tz); + testHour(st, targetHour, tz.name); + + // Verify that 01:00 is the first 01:00 (or whatever hour before the switch is). + if (hour == fall - 1) + testHour(st + dur!"hours"(1), targetHour, tz.name); + + if (hour < fall) + testOffset1(dstOffset, true); + else + testOffset1(stdOffset, false); + } + + // Verify that converting a time in UTC to a time in another + // time zone results in the correct time during and surrounding + // a DST switch. + bool first = true; + auto springSwitch = SysTime(dstSwitches[i][0] + dur!"hours"(spring), UTC()) - stdOffset; + auto fallSwitch = SysTime(dstSwitches[i][1] + dur!"hours"(fall), UTC()) - dstOffset; + // @@@BUG@@@ 3659 makes this necessary. + auto fallSwitchMinus1 = fallSwitch - dur!"hours"(1); + + foreach (hour; -24 .. 25) + { + auto utc = SysTime(dstSwitches[i][0] + dur!"hours"(hour), UTC()); + auto local = utc.toOtherTZ(tz); + + void testOffset2(Duration offset, size_t line = __LINE__) + { + AssertError msg(string tag) + { + return new AssertError(format("%s [%s] [%s]: [%s] [%s]", tag, hour, tz.name, utc, local), + __FILE__, line); + } + + enforce((utc + offset).hour == local.hour, msg("1")); + enforce((utc + offset + dur!"minutes"(1)).hour == local.hour, msg("2")); + } + + if (utc < springSwitch) + testOffset2(stdOffset); + else + testOffset2(dstOffset); + + utc = SysTime(dstSwitches[i][1] + dur!"hours"(hour), UTC()); + local = utc.toOtherTZ(tz); + + if (utc == fallSwitch || utc == fallSwitchMinus1) + { + if (first) + { + testOffset2(dstOffset); + first = false; + } + else + testOffset2(stdOffset); + } + else if (utc > fallSwitch) + testOffset2(stdOffset); + else + testOffset2(dstOffset); + } + } + } + + + // Explicitly undocumented. It will be removed in June 2018. @@@DEPRECATED_2018-07@@@ + deprecated("Use PosixTimeZone.getInstalledTZNames or WindowsTimeZone.getInstalledTZNames instead") + static string[] getInstalledTZNames(string subName = "") @safe + { + version (Posix) + return PosixTimeZone.getInstalledTZNames(subName); + else version (Windows) + { + import std.algorithm.searching : startsWith; + import std.algorithm.sorting : sort; + import std.array : appender; + + auto windowsNames = WindowsTimeZone.getInstalledTZNames(); + auto retval = appender!(string[])(); + + foreach (winName; windowsNames) + { + auto tzName = windowsTZNameToTZDatabaseName(winName); + if (tzName !is null && tzName.startsWith(subName)) + retval.put(tzName); + } + + sort(retval.data); + return retval.data; + } + } + + deprecated @safe unittest + { + import std.exception : assertNotThrown; + import std.stdio : writefln; + static void testPZSuccess(string tzName) + { + scope(failure) writefln("TZName which threw: %s", tzName); + TimeZone.getTimeZone(tzName); + } + + auto tzNames = getInstalledTZNames(); + // This was not previously tested, and it's currently failing, so I'm + // leaving it commented out until I can sort it out. + //assert(equal(tzNames, tzNames.uniq())); + + foreach (tzName; tzNames) + assertNotThrown!DateTimeException(testPZSuccess(tzName)); + } + + +protected: + + /++ + Params: + name = The name of the time zone. + stdName = The abbreviation for the time zone during std time. + dstName = The abbreviation for the time zone during DST. + +/ + this(string name, string stdName, string dstName) @safe immutable pure + { + _name = name; + _stdName = stdName; + _dstName = dstName; + } + + +private: + + immutable string _name; + immutable string _stdName; + immutable string _dstName; +} + + +/++ + A TimeZone which represents the current local time zone on + the system running your program. + + This uses the underlying C calls to adjust the time rather than using + specific D code based off of system settings to calculate the time such as + $(LREF PosixTimeZone) and $(LREF WindowsTimeZone) do. That also means that + it will use whatever the current time zone is on the system, even if the + system's time zone changes while the program is running. + +/ +final class LocalTime : TimeZone +{ +public: + + /++ + $(LREF LocalTime) is a singleton class. $(LREF LocalTime) returns its + only instance. + +/ + static immutable(LocalTime) opCall() @trusted pure nothrow + { + alias FuncType = @safe pure nothrow immutable(LocalTime) function(); + return (cast(FuncType)&singleton)(); + } + + + version (StdDdoc) + { + /++ + The name of the time zone per the TZ Database. This is the name used + to get a $(LREF TimeZone) by name with $(D TimeZone.getTimeZone). + + Note that this always returns the empty string. This is because time + zones cannot be uniquely identified by the attributes given by the + OS (such as the $(D stdName) and $(D dstName)), and neither Posix + systems nor Windows systems provide an easy way to get the TZ + Database name of the local time zone. + + See_Also: + $(HTTP en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ + Database)
+ $(HTTP en.wikipedia.org/wiki/List_of_tz_database_time_zones, List + of Time Zones) + +/ + @property override string name() @safe const nothrow; + } + + + /++ + Typically, the abbreviation (generally 3 or 4 letters) for the time zone + when DST is $(I not) in effect (e.g. PST). It is not necessarily unique. + + However, on Windows, it may be the unabbreviated name (e.g. Pacific + Standard Time). Regardless, it is not the same as name. + + This property is overridden because the local time of the system could + change while the program is running and we need to determine it + dynamically rather than it being fixed like it would be with most time + zones. + +/ + @property override string stdName() @trusted const nothrow + { + version (Posix) + { + import core.stdc.time : tzname; + import std.conv : to; + try + return to!string(tzname[0]); + catch (Exception e) + assert(0, "to!string(tzname[0]) failed."); + } + else version (Windows) + { + TIME_ZONE_INFORMATION tzInfo; + GetTimeZoneInformation(&tzInfo); + + // Cannot use to!string() like this should, probably due to bug + // http://d.puremagic.com/issues/show_bug.cgi?id=5016 + //return to!string(tzInfo.StandardName); + + wchar[32] str; + + foreach (i, ref wchar c; str) + c = tzInfo.StandardName[i]; + + string retval; + + try + { + foreach (dchar c; str) + { + if (c == '\0') + break; + + retval ~= c; + } + + return retval; + } + catch (Exception e) + assert(0, "GetTimeZoneInformation() returned invalid UTF-16."); + } + } + + @safe unittest + { + version (FreeBSD) + { + // A bug on FreeBSD 9+ makes it so that this test fails. + // https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=168862 + } + else version (NetBSD) + { + // The same bug on NetBSD 7+ + } + else + { + assert(LocalTime().stdName !is null); + + version (Posix) + { + scope(exit) clearTZEnvVar(); + + setTZEnvVar("America/Los_Angeles"); + assert(LocalTime().stdName == "PST"); + + setTZEnvVar("America/New_York"); + assert(LocalTime().stdName == "EST"); + } + } + } + + + /++ + Typically, the abbreviation (generally 3 or 4 letters) for the time zone + when DST $(I is) in effect (e.g. PDT). It is not necessarily unique. + + However, on Windows, it may be the unabbreviated name (e.g. Pacific + Daylight Time). Regardless, it is not the same as name. + + This property is overridden because the local time of the system could + change while the program is running and we need to determine it + dynamically rather than it being fixed like it would be with most time + zones. + +/ + @property override string dstName() @trusted const nothrow + { + version (Posix) + { + import core.stdc.time : tzname; + import std.conv : to; + try + return to!string(tzname[1]); + catch (Exception e) + assert(0, "to!string(tzname[1]) failed."); + } + else version (Windows) + { + TIME_ZONE_INFORMATION tzInfo; + GetTimeZoneInformation(&tzInfo); + + // Cannot use to!string() like this should, probably due to bug + // http://d.puremagic.com/issues/show_bug.cgi?id=5016 + //return to!string(tzInfo.DaylightName); + + wchar[32] str; + + foreach (i, ref wchar c; str) + c = tzInfo.DaylightName[i]; + + string retval; + + try + { + foreach (dchar c; str) + { + if (c == '\0') + break; + + retval ~= c; + } + + return retval; + } + catch (Exception e) + assert(0, "GetTimeZoneInformation() returned invalid UTF-16."); + } + } + + @safe unittest + { + assert(LocalTime().dstName !is null); + + version (Posix) + { + scope(exit) clearTZEnvVar(); + + version (FreeBSD) + { + // A bug on FreeBSD 9+ makes it so that this test fails. + // https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=168862 + } + else version (NetBSD) + { + // The same bug on NetBSD 7+ + } + else + { + setTZEnvVar("America/Los_Angeles"); + assert(LocalTime().dstName == "PDT"); + + setTZEnvVar("America/New_York"); + assert(LocalTime().dstName == "EDT"); + } + } + } + + + /++ + Whether this time zone has Daylight Savings Time at any point in time. + Note that for some time zone types it may not have DST for current + dates but will still return true for $(D hasDST) because the time zone + did at some point have DST. + +/ + @property override bool hasDST() @trusted const nothrow + { + version (Posix) + { + static if (is(typeof(daylight))) + return cast(bool)(daylight); + else + { + try + { + auto currYear = (cast(Date) Clock.currTime()).year; + auto janOffset = SysTime(Date(currYear, 1, 4), cast(immutable) this).stdTime - + SysTime(Date(currYear, 1, 4), UTC()).stdTime; + auto julyOffset = SysTime(Date(currYear, 7, 4), cast(immutable) this).stdTime - + SysTime(Date(currYear, 7, 4), UTC()).stdTime; + + return janOffset != julyOffset; + } + catch (Exception e) + assert(0, "Clock.currTime() threw."); + } + } + else version (Windows) + { + TIME_ZONE_INFORMATION tzInfo; + GetTimeZoneInformation(&tzInfo); + + return tzInfo.DaylightDate.wMonth != 0; + } + } + + @safe unittest + { + LocalTime().hasDST; + + version (Posix) + { + scope(exit) clearTZEnvVar(); + + setTZEnvVar("America/Los_Angeles"); + assert(LocalTime().hasDST); + + setTZEnvVar("America/New_York"); + assert(LocalTime().hasDST); + + setTZEnvVar("UTC"); + assert(!LocalTime().hasDST); + } + } + + + /++ + Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D. + in UTC time (i.e. std time) and returns whether DST is in effect in this + time zone at the given point in time. + + Params: + stdTime = The UTC time that needs to be checked for DST in this time + zone. + +/ + override bool dstInEffect(long stdTime) @trusted const nothrow + { + import core.stdc.time : localtime, tm; + time_t unixTime = stdTimeToUnixTime(stdTime); + + version (Posix) + { + tm* timeInfo = localtime(&unixTime); + + return cast(bool)(timeInfo.tm_isdst); + } + else version (Windows) + { + // Apparently Windows isn't smart enough to deal with negative time_t. + if (unixTime >= 0) + { + tm* timeInfo = localtime(&unixTime); + + if (timeInfo) + return cast(bool)(timeInfo.tm_isdst); + } + + TIME_ZONE_INFORMATION tzInfo; + GetTimeZoneInformation(&tzInfo); + + return WindowsTimeZone._dstInEffect(&tzInfo, stdTime); + } + } + + @safe unittest + { + auto currTime = Clock.currStdTime; + LocalTime().dstInEffect(currTime); + } + + + /++ + Returns hnsecs in the local time zone using the standard C function + calls on Posix systems and the standard Windows system calls on Windows + systems to adjust the time to the appropriate time zone from std time. + + Params: + stdTime = The UTC time that needs to be adjusted to this time zone's + time. + + See_Also: + $(D TimeZone.utcToTZ) + +/ + override long utcToTZ(long stdTime) @trusted const nothrow + { + version (Solaris) + return stdTime + convert!("seconds", "hnsecs")(tm_gmtoff(stdTime)); + else version (Posix) + { + import core.stdc.time : localtime, tm; + time_t unixTime = stdTimeToUnixTime(stdTime); + tm* timeInfo = localtime(&unixTime); + + return stdTime + convert!("seconds", "hnsecs")(timeInfo.tm_gmtoff); + } + else version (Windows) + { + TIME_ZONE_INFORMATION tzInfo; + GetTimeZoneInformation(&tzInfo); + + return WindowsTimeZone._utcToTZ(&tzInfo, stdTime, hasDST); + } + } + + @safe unittest + { + LocalTime().utcToTZ(0); + } + + + /++ + Returns std time using the standard C function calls on Posix systems + and the standard Windows system calls on Windows systems to adjust the + time to UTC from the appropriate time zone. + + See_Also: + $(D TimeZone.tzToUTC) + + Params: + adjTime = The time in this time zone that needs to be adjusted to + UTC time. + +/ + override long tzToUTC(long adjTime) @trusted const nothrow + { + version (Posix) + { + import core.stdc.time : localtime, tm; + time_t unixTime = stdTimeToUnixTime(adjTime); + + immutable past = unixTime - cast(time_t) convert!("days", "seconds")(1); + tm* timeInfo = localtime(past < unixTime ? &past : &unixTime); + immutable pastOffset = timeInfo.tm_gmtoff; + + immutable future = unixTime + cast(time_t) convert!("days", "seconds")(1); + timeInfo = localtime(future > unixTime ? &future : &unixTime); + immutable futureOffset = timeInfo.tm_gmtoff; + + if (pastOffset == futureOffset) + return adjTime - convert!("seconds", "hnsecs")(pastOffset); + + if (pastOffset < futureOffset) + unixTime -= cast(time_t) convert!("hours", "seconds")(1); + + unixTime -= pastOffset; + timeInfo = localtime(&unixTime); + + return adjTime - convert!("seconds", "hnsecs")(timeInfo.tm_gmtoff); + } + else version (Windows) + { + TIME_ZONE_INFORMATION tzInfo; + GetTimeZoneInformation(&tzInfo); + + return WindowsTimeZone._tzToUTC(&tzInfo, adjTime, hasDST); + } + } + + @safe unittest + { + import core.exception : AssertError; + import std.format : format; + import std.typecons : tuple; + + assert(LocalTime().tzToUTC(LocalTime().utcToTZ(0)) == 0); + assert(LocalTime().utcToTZ(LocalTime().tzToUTC(0)) == 0); + + assert(LocalTime().tzToUTC(LocalTime().utcToTZ(0)) == 0); + assert(LocalTime().utcToTZ(LocalTime().tzToUTC(0)) == 0); + + version (Posix) + { + scope(exit) clearTZEnvVar(); + + auto tzInfos = [tuple("America/Los_Angeles", DateTime(2012, 3, 11), DateTime(2012, 11, 4), 2, 2), + tuple("America/New_York", DateTime(2012, 3, 11), DateTime(2012, 11, 4), 2, 2), + //tuple("America/Santiago", DateTime(2011, 8, 21), DateTime(2011, 5, 8), 0, 0), + tuple("Atlantic/Azores", DateTime(2011, 3, 27), DateTime(2011, 10, 30), 0, 1), + tuple("Europe/London", DateTime(2012, 3, 25), DateTime(2012, 10, 28), 1, 2), + tuple("Europe/Paris", DateTime(2012, 3, 25), DateTime(2012, 10, 28), 2, 3), + tuple("Australia/Adelaide", DateTime(2012, 10, 7), DateTime(2012, 4, 1), 2, 3)]; + + foreach (i; 0 .. tzInfos.length) + { + auto tzName = tzInfos[i][0]; + setTZEnvVar(tzName); + immutable spring = tzInfos[i][3]; + immutable fall = tzInfos[i][4]; + auto stdOffset = SysTime(tzInfos[i][1] + dur!"hours"(-12)).utcOffset; + auto dstOffset = stdOffset + dur!"hours"(1); + + // Verify that creating a SysTime in the given time zone results + // in a SysTime with the correct std time during and surrounding + // a DST switch. + foreach (hour; -12 .. 13) + { + auto st = SysTime(tzInfos[i][1] + dur!"hours"(hour)); + immutable targetHour = hour < 0 ? hour + 24 : hour; + + static void testHour(SysTime st, int hour, string tzName, size_t line = __LINE__) + { + enforce(st.hour == hour, + new AssertError(format("[%s] [%s]: [%s] [%s]", st, tzName, st.hour, hour), + __FILE__, line)); + } + + void testOffset1(Duration offset, bool dstInEffect, size_t line = __LINE__) + { + AssertError msg(string tag) + { + return new AssertError(format("%s [%s] [%s]: [%s] [%s] [%s]", + tag, st, tzName, st.utcOffset, stdOffset, dstOffset), + __FILE__, line); + } + + enforce(st.dstInEffect == dstInEffect, msg("1")); + enforce(st.utcOffset == offset, msg("2")); + enforce((st + dur!"minutes"(1)).utcOffset == offset, msg("3")); + } + + if (hour == spring) + { + testHour(st, spring + 1, tzName); + testHour(st + dur!"minutes"(1), spring + 1, tzName); + } + else + { + testHour(st, targetHour, tzName); + testHour(st + dur!"minutes"(1), targetHour, tzName); + } + + if (hour < spring) + testOffset1(stdOffset, false); + else + testOffset1(dstOffset, true); + + st = SysTime(tzInfos[i][2] + dur!"hours"(hour)); + testHour(st, targetHour, tzName); + + // Verify that 01:00 is the first 01:00 (or whatever hour before the switch is). + if (hour == fall - 1) + testHour(st + dur!"hours"(1), targetHour, tzName); + + if (hour < fall) + testOffset1(dstOffset, true); + else + testOffset1(stdOffset, false); + } + + // Verify that converting a time in UTC to a time in another + // time zone results in the correct time during and surrounding + // a DST switch. + bool first = true; + auto springSwitch = SysTime(tzInfos[i][1] + dur!"hours"(spring), UTC()) - stdOffset; + auto fallSwitch = SysTime(tzInfos[i][2] + dur!"hours"(fall), UTC()) - dstOffset; + // @@@BUG@@@ 3659 makes this necessary. + auto fallSwitchMinus1 = fallSwitch - dur!"hours"(1); + + foreach (hour; -24 .. 25) + { + auto utc = SysTime(tzInfos[i][1] + dur!"hours"(hour), UTC()); + auto local = utc.toLocalTime(); + + void testOffset2(Duration offset, size_t line = __LINE__) + { + AssertError msg(string tag) + { + return new AssertError(format("%s [%s] [%s]: [%s] [%s]", tag, hour, tzName, utc, local), + __FILE__, line); + } + + enforce((utc + offset).hour == local.hour, msg("1")); + enforce((utc + offset + dur!"minutes"(1)).hour == local.hour, msg("2")); + } + + if (utc < springSwitch) + testOffset2(stdOffset); + else + testOffset2(dstOffset); + + utc = SysTime(tzInfos[i][2] + dur!"hours"(hour), UTC()); + local = utc.toLocalTime(); + + if (utc == fallSwitch || utc == fallSwitchMinus1) + { + if (first) + { + testOffset2(dstOffset); + first = false; + } + else + testOffset2(stdOffset); + } + else if (utc > fallSwitch) + testOffset2(stdOffset); + else + testOffset2(dstOffset); + } + } + } + } + + +private: + + this() @safe immutable pure + { + super("", "", ""); + } + + + // This is done so that we can maintain purity in spite of doing an impure + // operation the first time that LocalTime() is called. + static immutable(LocalTime) singleton() @trusted + { + import core.stdc.time : tzset; + import std.concurrency : initOnce; + static instance = new immutable(LocalTime)(); + static shared bool guard; + initOnce!guard({tzset(); return true;}()); + return instance; + } + + + // The Solaris version of struct tm has no tm_gmtoff field, so do it here + version (Solaris) + { + long tm_gmtoff(long stdTime) @trusted const nothrow + { + import core.stdc.time : localtime, gmtime, tm; + + time_t unixTime = stdTimeToUnixTime(stdTime); + tm* buf = localtime(&unixTime); + tm timeInfo = *buf; + buf = gmtime(&unixTime); + tm timeInfoGmt = *buf; + + return timeInfo.tm_sec - timeInfoGmt.tm_sec + + convert!("minutes", "seconds")(timeInfo.tm_min - timeInfoGmt.tm_min) + + convert!("hours", "seconds")(timeInfo.tm_hour - timeInfoGmt.tm_hour); + } + } +} + + +/++ + A $(LREF TimeZone) which represents UTC. + +/ +final class UTC : TimeZone +{ +public: + + /++ + $(D UTC) is a singleton class. $(D UTC) returns its only instance. + +/ + static immutable(UTC) opCall() @safe pure nothrow + { + return _utc; + } + + + /++ + Always returns false. + +/ + @property override bool hasDST() @safe const nothrow + { + return false; + } + + + /++ + Always returns false. + +/ + override bool dstInEffect(long stdTime) @safe const nothrow + { + return false; + } + + + /++ + Returns the given hnsecs without changing them at all. + + Params: + stdTime = The UTC time that needs to be adjusted to this time zone's + time. + + See_Also: + $(D TimeZone.utcToTZ) + +/ + override long utcToTZ(long stdTime) @safe const nothrow + { + return stdTime; + } + + @safe unittest + { + assert(UTC().utcToTZ(0) == 0); + + version (Posix) + { + scope(exit) clearTZEnvVar(); + + setTZEnvVar("UTC"); + auto std = SysTime(Date(2010, 1, 1)); + auto dst = SysTime(Date(2010, 7, 1)); + assert(UTC().utcToTZ(std.stdTime) == std.stdTime); + assert(UTC().utcToTZ(dst.stdTime) == dst.stdTime); + } + } + + + /++ + Returns the given hnsecs without changing them at all. + + See_Also: + $(D TimeZone.tzToUTC) + + Params: + adjTime = The time in this time zone that needs to be adjusted to + UTC time. + +/ + override long tzToUTC(long adjTime) @safe const nothrow + { + return adjTime; + } + + @safe unittest + { + assert(UTC().tzToUTC(0) == 0); + + version (Posix) + { + scope(exit) clearTZEnvVar(); + + setTZEnvVar("UTC"); + auto std = SysTime(Date(2010, 1, 1)); + auto dst = SysTime(Date(2010, 7, 1)); + assert(UTC().tzToUTC(std.stdTime) == std.stdTime); + assert(UTC().tzToUTC(dst.stdTime) == dst.stdTime); + } + } + + + /++ + Returns a $(REF Duration, core,time) of 0. + + Params: + stdTime = The UTC time for which to get the offset from UTC for this + time zone. + +/ + override Duration utcOffsetAt(long stdTime) @safe const nothrow + { + return dur!"hnsecs"(0); + } + + +private: + + this() @safe immutable pure + { + super("UTC", "UTC", "UTC"); + } + + + static immutable UTC _utc = new immutable(UTC)(); +} + + +/++ + Represents a time zone with an offset (in minutes, west is negative) from + UTC but no DST. + + It's primarily used as the time zone in the result of + $(REF SysTime,std,datetime,systime)'s $(D fromISOString), + $(D fromISOExtString), and $(D fromSimpleString). + + $(D name) and $(D dstName) are always the empty string since this time zone + has no DST, and while it may be meant to represent a time zone which is in + the TZ Database, obviously it's not likely to be following the exact rules + of any of the time zones in the TZ Database, so it makes no sense to set it. + +/ +final class SimpleTimeZone : TimeZone +{ +public: + + /++ + Always returns false. + +/ + @property override bool hasDST() @safe const nothrow + { + return false; + } + + + /++ + Always returns false. + +/ + override bool dstInEffect(long stdTime) @safe const nothrow + { + return false; + } + + + /++ + Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D. + in UTC time (i.e. std time) and converts it to this time zone's time. + + Params: + stdTime = The UTC time that needs to be adjusted to this time zone's + time. + +/ + override long utcToTZ(long stdTime) @safe const nothrow + { + return stdTime + _utcOffset.total!"hnsecs"; + } + + @safe unittest + { + auto west = new immutable SimpleTimeZone(dur!"hours"(-8)); + auto east = new immutable SimpleTimeZone(dur!"hours"(8)); + + assert(west.utcToTZ(0) == -288_000_000_000L); + assert(east.utcToTZ(0) == 288_000_000_000L); + assert(west.utcToTZ(54_321_234_567_890L) == 54_033_234_567_890L); + + const cstz = west; + assert(cstz.utcToTZ(50002) == west.utcToTZ(50002)); + } + + + /++ + Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D. + in this time zone's time and converts it to UTC (i.e. std time). + + Params: + adjTime = The time in this time zone that needs to be adjusted to + UTC time. + +/ + override long tzToUTC(long adjTime) @safe const nothrow + { + return adjTime - _utcOffset.total!"hnsecs"; + } + + @safe unittest + { + auto west = new immutable SimpleTimeZone(dur!"hours"(-8)); + auto east = new immutable SimpleTimeZone(dur!"hours"(8)); + + assert(west.tzToUTC(-288_000_000_000L) == 0); + assert(east.tzToUTC(288_000_000_000L) == 0); + assert(west.tzToUTC(54_033_234_567_890L) == 54_321_234_567_890L); + + const cstz = west; + assert(cstz.tzToUTC(20005) == west.tzToUTC(20005)); + } + + + /++ + Returns utcOffset as a $(REF Duration, core,time). + + Params: + stdTime = The UTC time for which to get the offset from UTC for this + time zone. + +/ + override Duration utcOffsetAt(long stdTime) @safe const nothrow + { + return _utcOffset; + } + + + /++ + Params: + utcOffset = This time zone's offset from UTC with west of UTC being + negative (it is added to UTC to get the adjusted time). + stdName = The $(D stdName) for this time zone. + +/ + this(Duration utcOffset, string stdName = "") @safe immutable pure + { + // FIXME This probably needs to be changed to something like (-12 - 13). + enforce!DateTimeException(abs(utcOffset) < dur!"minutes"(1440), + "Offset from UTC must be within range (-24:00 - 24:00)."); + super("", stdName, ""); + this._utcOffset = utcOffset; + } + + @safe unittest + { + auto stz = new immutable SimpleTimeZone(dur!"hours"(-8), "PST"); + assert(stz.name == ""); + assert(stz.stdName == "PST"); + assert(stz.dstName == ""); + assert(stz.utcOffset == dur!"hours"(-8)); + } + + + /++ + The amount of time the offset from UTC is (negative is west of UTC, + positive is east). + +/ + @property Duration utcOffset() @safe const pure nothrow + { + return _utcOffset; + } + + +package: + + /+ + Returns a time zone as a string with an offset from UTC. + + Time zone offsets will be in the form +HHMM or -HHMM. + + Params: + utcOffset = The number of minutes offset from UTC (negative means + west). + +/ + static string toISOString(Duration utcOffset) @safe pure + { + import std.format : format; + immutable absOffset = abs(utcOffset); + enforce!DateTimeException(absOffset < dur!"minutes"(1440), + "Offset from UTC must be within range (-24:00 - 24:00)."); + int hours; + int minutes; + absOffset.split!("hours", "minutes")(hours, minutes); + return format(utcOffset < Duration.zero ? "-%02d%02d" : "+%02d%02d", hours, minutes); + } + + @safe unittest + { + static string testSTZInvalid(Duration offset) + { + return SimpleTimeZone.toISOString(offset); + } + + assertThrown!DateTimeException(testSTZInvalid(dur!"minutes"(1440))); + assertThrown!DateTimeException(testSTZInvalid(dur!"minutes"(-1440))); + + assert(toISOString(dur!"minutes"(0)) == "+0000"); + assert(toISOString(dur!"minutes"(1)) == "+0001"); + assert(toISOString(dur!"minutes"(10)) == "+0010"); + assert(toISOString(dur!"minutes"(59)) == "+0059"); + assert(toISOString(dur!"minutes"(60)) == "+0100"); + assert(toISOString(dur!"minutes"(90)) == "+0130"); + assert(toISOString(dur!"minutes"(120)) == "+0200"); + assert(toISOString(dur!"minutes"(480)) == "+0800"); + assert(toISOString(dur!"minutes"(1439)) == "+2359"); + + assert(toISOString(dur!"minutes"(-1)) == "-0001"); + assert(toISOString(dur!"minutes"(-10)) == "-0010"); + assert(toISOString(dur!"minutes"(-59)) == "-0059"); + assert(toISOString(dur!"minutes"(-60)) == "-0100"); + assert(toISOString(dur!"minutes"(-90)) == "-0130"); + assert(toISOString(dur!"minutes"(-120)) == "-0200"); + assert(toISOString(dur!"minutes"(-480)) == "-0800"); + assert(toISOString(dur!"minutes"(-1439)) == "-2359"); + } + + + /+ + Returns a time zone as a string with an offset from UTC. + + Time zone offsets will be in the form +HH:MM or -HH:MM. + + Params: + utcOffset = The number of minutes offset from UTC (negative means + west). + +/ + static string toISOExtString(Duration utcOffset) @safe pure + { + import std.format : format; + + immutable absOffset = abs(utcOffset); + enforce!DateTimeException(absOffset < dur!"minutes"(1440), + "Offset from UTC must be within range (-24:00 - 24:00)."); + int hours; + int minutes; + absOffset.split!("hours", "minutes")(hours, minutes); + return format(utcOffset < Duration.zero ? "-%02d:%02d" : "+%02d:%02d", hours, minutes); + } + + @safe unittest + { + static string testSTZInvalid(Duration offset) + { + return SimpleTimeZone.toISOExtString(offset); + } + + assertThrown!DateTimeException(testSTZInvalid(dur!"minutes"(1440))); + assertThrown!DateTimeException(testSTZInvalid(dur!"minutes"(-1440))); + + assert(toISOExtString(dur!"minutes"(0)) == "+00:00"); + assert(toISOExtString(dur!"minutes"(1)) == "+00:01"); + assert(toISOExtString(dur!"minutes"(10)) == "+00:10"); + assert(toISOExtString(dur!"minutes"(59)) == "+00:59"); + assert(toISOExtString(dur!"minutes"(60)) == "+01:00"); + assert(toISOExtString(dur!"minutes"(90)) == "+01:30"); + assert(toISOExtString(dur!"minutes"(120)) == "+02:00"); + assert(toISOExtString(dur!"minutes"(480)) == "+08:00"); + assert(toISOExtString(dur!"minutes"(1439)) == "+23:59"); + + assert(toISOExtString(dur!"minutes"(-1)) == "-00:01"); + assert(toISOExtString(dur!"minutes"(-10)) == "-00:10"); + assert(toISOExtString(dur!"minutes"(-59)) == "-00:59"); + assert(toISOExtString(dur!"minutes"(-60)) == "-01:00"); + assert(toISOExtString(dur!"minutes"(-90)) == "-01:30"); + assert(toISOExtString(dur!"minutes"(-120)) == "-02:00"); + assert(toISOExtString(dur!"minutes"(-480)) == "-08:00"); + assert(toISOExtString(dur!"minutes"(-1439)) == "-23:59"); + } + + + /+ + Takes a time zone as a string with an offset from UTC and returns a + $(LREF SimpleTimeZone) which matches. + + The accepted formats for time zone offsets are +HH, -HH, +HHMM, and + -HHMM. + + Params: + isoString = A string which represents a time zone in the ISO format. + +/ + static immutable(SimpleTimeZone) fromISOString(S)(S isoString) @safe pure + if (isSomeString!S) + { + import std.algorithm.searching : startsWith, countUntil, all; + import std.ascii : isDigit; + import std.conv : to; + import std.format : format; + + auto dstr = to!dstring(isoString); + + enforce!DateTimeException(dstr.startsWith('-', '+'), "Invalid ISO String"); + + auto sign = dstr.startsWith('-') ? -1 : 1; + + dstr.popFront(); + enforce!DateTimeException(all!isDigit(dstr), format("Invalid ISO String: %s", dstr)); + + int hours; + int minutes; + + if (dstr.length == 2) + hours = to!int(dstr); + else if (dstr.length == 4) + { + hours = to!int(dstr[0 .. 2]); + minutes = to!int(dstr[2 .. 4]); + } + else + throw new DateTimeException(format("Invalid ISO String: %s", dstr)); + + enforce!DateTimeException(hours < 24 && minutes < 60, format("Invalid ISO String: %s", dstr)); + + return new immutable SimpleTimeZone(sign * (dur!"hours"(hours) + dur!"minutes"(minutes))); + } + + @safe unittest + { + import core.exception : AssertError; + import std.format : format; + + foreach (str; ["", "Z", "-", "+", "-:", "+:", "-1:", "+1:", "+1", "-1", + "-24:00", "+24:00", "-24", "+24", "-2400", "+2400", + "1", "+1", "-1", "+9", "-9", + "+1:0", "+01:0", "+1:00", "+01:000", "+01:60", + "-1:0", "-01:0", "-1:00", "-01:000", "-01:60", + "000", "00000", "0160", "-0160", + " +08:00", "+ 08:00", "+08 :00", "+08: 00", "+08:00 ", + " -08:00", "- 08:00", "-08 :00", "-08: 00", "-08:00 ", + " +0800", "+ 0800", "+08 00", "+08 00", "+0800 ", + " -0800", "- 0800", "-08 00", "-08 00", "-0800 ", + "+ab:cd", "+abcd", "+0Z:00", "+Z", "+00Z", + "-ab:cd", "+abcd", "-0Z:00", "-Z", "-00Z", + "01:00", "12:00", "23:59"]) + { + assertThrown!DateTimeException(SimpleTimeZone.fromISOString(str), format("[%s]", str)); + } + + static void test(string str, Duration utcOffset, size_t line = __LINE__) + { + if (SimpleTimeZone.fromISOString(str).utcOffset != (new immutable SimpleTimeZone(utcOffset)).utcOffset) + throw new AssertError("unittest failure", __FILE__, line); + } + + test("+0000", Duration.zero); + test("+0001", minutes(1)); + test("+0010", minutes(10)); + test("+0059", minutes(59)); + test("+0100", hours(1)); + test("+0130", hours(1) + minutes(30)); + test("+0200", hours(2)); + test("+0800", hours(8)); + test("+2359", hours(23) + minutes(59)); + + test("-0001", minutes(-1)); + test("-0010", minutes(-10)); + test("-0059", minutes(-59)); + test("-0100", hours(-1)); + test("-0130", hours(-1) - minutes(30)); + test("-0200", hours(-2)); + test("-0800", hours(-8)); + test("-2359", hours(-23) - minutes(59)); + + test("+00", Duration.zero); + test("+01", hours(1)); + test("+02", hours(2)); + test("+12", hours(12)); + test("+23", hours(23)); + + test("-00", Duration.zero); + test("-01", hours(-1)); + test("-02", hours(-2)); + test("-12", hours(-12)); + test("-23", hours(-23)); + } + + @safe unittest + { + import core.exception : AssertError; + import std.format : format; + + static void test(in string isoString, int expectedOffset, size_t line = __LINE__) + { + auto stz = SimpleTimeZone.fromISOExtString(isoString); + if (stz.utcOffset != dur!"minutes"(expectedOffset)) + throw new AssertError(format("unittest failure: wrong offset [%s]", stz.utcOffset), __FILE__, line); + + auto result = SimpleTimeZone.toISOExtString(stz.utcOffset); + if (result != isoString) + throw new AssertError(format("unittest failure: [%s] != [%s]", result, isoString), __FILE__, line); + } + + test("+00:00", 0); + test("+00:01", 1); + test("+00:10", 10); + test("+00:59", 59); + test("+01:00", 60); + test("+01:30", 90); + test("+02:00", 120); + test("+08:00", 480); + test("+08:00", 480); + test("+23:59", 1439); + + test("-00:01", -1); + test("-00:10", -10); + test("-00:59", -59); + test("-01:00", -60); + test("-01:30", -90); + test("-02:00", -120); + test("-08:00", -480); + test("-08:00", -480); + test("-23:59", -1439); + } + + + /+ + Takes a time zone as a string with an offset from UTC and returns a + $(LREF SimpleTimeZone) which matches. + + The accepted formats for time zone offsets are +HH, -HH, +HH:MM, and + -HH:MM. + + Params: + isoExtString = A string which represents a time zone in the ISO format. + +/ + static immutable(SimpleTimeZone) fromISOExtString(S)(S isoExtString) @safe pure + if (isSomeString!S) + { + import std.algorithm.searching : startsWith, countUntil, all; + import std.ascii : isDigit; + import std.conv : to; + import std.format : format; + + auto dstr = to!dstring(isoExtString); + + enforce!DateTimeException(dstr.startsWith('-', '+'), "Invalid ISO String"); + + auto sign = dstr.startsWith('-') ? -1 : 1; + + dstr.popFront(); + enforce!DateTimeException(!dstr.empty, "Invalid ISO String"); + + immutable colon = dstr.countUntil(':'); + + dstring hoursStr; + dstring minutesStr; + + if (colon != -1) + { + hoursStr = dstr[0 .. colon]; + minutesStr = dstr[colon + 1 .. $]; + enforce!DateTimeException(minutesStr.length == 2, format("Invalid ISO String: %s", dstr)); + } + else + hoursStr = dstr; + + enforce!DateTimeException(hoursStr.length == 2, format("Invalid ISO String: %s", dstr)); + enforce!DateTimeException(all!isDigit(hoursStr), format("Invalid ISO String: %s", dstr)); + enforce!DateTimeException(all!isDigit(minutesStr), format("Invalid ISO String: %s", dstr)); + + immutable hours = to!int(hoursStr); + immutable minutes = minutesStr.empty ? 0 : to!int(minutesStr); + enforce!DateTimeException(hours < 24 && minutes < 60, format("Invalid ISO String: %s", dstr)); + + return new immutable SimpleTimeZone(sign * (dur!"hours"(hours) + dur!"minutes"(minutes))); + } + + @safe unittest + { + import core.exception : AssertError; + import std.format : format; + + foreach (str; ["", "Z", "-", "+", "-:", "+:", "-1:", "+1:", "+1", "-1", + "-24:00", "+24:00", "-24", "+24", "-2400", "-2400", + "1", "+1", "-1", "+9", "-9", + "+1:0", "+01:0", "+1:00", "+01:000", "+01:60", + "-1:0", "-01:0", "-1:00", "-01:000", "-01:60", + "000", "00000", "0160", "-0160", + " +08:00", "+ 08:00", "+08 :00", "+08: 00", "+08:00 ", + " -08:00", "- 08:00", "-08 :00", "-08: 00", "-08:00 ", + " +0800", "+ 0800", "+08 00", "+08 00", "+0800 ", + " -0800", "- 0800", "-08 00", "-08 00", "-0800 ", + "+ab:cd", "abcd", "+0Z:00", "+Z", "+00Z", + "-ab:cd", "abcd", "-0Z:00", "-Z", "-00Z", + "0100", "1200", "2359"]) + { + assertThrown!DateTimeException(SimpleTimeZone.fromISOExtString(str), format("[%s]", str)); + } + + static void test(string str, Duration utcOffset, size_t line = __LINE__) + { + if (SimpleTimeZone.fromISOExtString(str).utcOffset != (new immutable SimpleTimeZone(utcOffset)).utcOffset) + throw new AssertError("unittest failure", __FILE__, line); + } + + test("+00:00", Duration.zero); + test("+00:01", minutes(1)); + test("+00:10", minutes(10)); + test("+00:59", minutes(59)); + test("+01:00", hours(1)); + test("+01:30", hours(1) + minutes(30)); + test("+02:00", hours(2)); + test("+08:00", hours(8)); + test("+23:59", hours(23) + minutes(59)); + + test("-00:01", minutes(-1)); + test("-00:10", minutes(-10)); + test("-00:59", minutes(-59)); + test("-01:00", hours(-1)); + test("-01:30", hours(-1) - minutes(30)); + test("-02:00", hours(-2)); + test("-08:00", hours(-8)); + test("-23:59", hours(-23) - minutes(59)); + + test("+00", Duration.zero); + test("+01", hours(1)); + test("+02", hours(2)); + test("+12", hours(12)); + test("+23", hours(23)); + + test("-00", Duration.zero); + test("-01", hours(-1)); + test("-02", hours(-2)); + test("-12", hours(-12)); + test("-23", hours(-23)); + } + + @safe unittest + { + import core.exception : AssertError; + import std.format : format; + + static void test(in string isoExtString, int expectedOffset, size_t line = __LINE__) + { + auto stz = SimpleTimeZone.fromISOExtString(isoExtString); + if (stz.utcOffset != dur!"minutes"(expectedOffset)) + throw new AssertError(format("unittest failure: wrong offset [%s]", stz.utcOffset), __FILE__, line); + + auto result = SimpleTimeZone.toISOExtString(stz.utcOffset); + if (result != isoExtString) + throw new AssertError(format("unittest failure: [%s] != [%s]", result, isoExtString), __FILE__, line); + } + + test("+00:00", 0); + test("+00:01", 1); + test("+00:10", 10); + test("+00:59", 59); + test("+01:00", 60); + test("+01:30", 90); + test("+02:00", 120); + test("+08:00", 480); + test("+08:00", 480); + test("+23:59", 1439); + + test("-00:01", -1); + test("-00:10", -10); + test("-00:59", -59); + test("-01:00", -60); + test("-01:30", -90); + test("-02:00", -120); + test("-08:00", -480); + test("-08:00", -480); + test("-23:59", -1439); + } + + +private: + + immutable Duration _utcOffset; +} + + +/++ + Represents a time zone from a TZ Database time zone file. Files from the TZ + Database are how Posix systems hold their time zone information. + Unfortunately, Windows does not use the TZ Database. To use the TZ Database, + use $(D PosixTimeZone) (which reads its information from the TZ Database + files on disk) on Windows by providing the TZ Database files and telling + $(D PosixTimeZone.getTimeZone) where the directory holding them is. + + To get a $(D PosixTimeZone), either call $(D PosixTimeZone.getTimeZone) + (which allows specifying the location the time zone files) or call + $(D TimeZone.getTimeZone) (which will give a $(D PosixTimeZone) on Posix + systems and a $(LREF WindowsTimeZone) on Windows systems). + + Note: + Unless your system's local time zone deals with leap seconds (which is + highly unlikely), then the only way to get a time zone which + takes leap seconds into account is to use $(D PosixTimeZone) with a + time zone whose name starts with "right/". Those time zone files do + include leap seconds, and $(D PosixTimeZone) will take them into account + (though posix systems which use a "right/" time zone as their local time + zone will $(I not) take leap seconds into account even though they're + in the file). + + See_Also: + $(HTTP www.iana.org/time-zones, Home of the TZ Database files)
+ $(HTTP en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ Database)
+ $(HTTP en.wikipedia.org/wiki/List_of_tz_database_time_zones, List of Time + Zones) + +/ +final class PosixTimeZone : TimeZone +{ + import std.algorithm.searching : countUntil, canFind, startsWith; + import std.file : isDir, isFile, exists, dirEntries, SpanMode, DirEntry; + import std.path : extension; + import std.stdio : File; + import std.string : strip, representation; + import std.traits : isArray, isSomeChar; +public: + + /++ + Whether this time zone has Daylight Savings Time at any point in time. + Note that for some time zone types it may not have DST for current + dates but will still return true for $(D hasDST) because the time zone + did at some point have DST. + +/ + @property override bool hasDST() @safe const nothrow + { + return _hasDST; + } + + + /++ + Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D. + in UTC time (i.e. std time) and returns whether DST is in effect in this + time zone at the given point in time. + + Params: + stdTime = The UTC time that needs to be checked for DST in this time + zone. + +/ + override bool dstInEffect(long stdTime) @safe const nothrow + { + assert(!_transitions.empty); + + immutable unixTime = stdTimeToUnixTime(stdTime); + immutable found = countUntil!"b < a.timeT"(_transitions, unixTime); + + if (found == -1) + return _transitions.back.ttInfo.isDST; + + immutable transition = found == 0 ? _transitions[0] : _transitions[found - 1]; + + return transition.ttInfo.isDST; + } + + + /++ + Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D. + in UTC time (i.e. std time) and converts it to this time zone's time. + + Params: + stdTime = The UTC time that needs to be adjusted to this time zone's + time. + +/ + override long utcToTZ(long stdTime) @safe const nothrow + { + assert(!_transitions.empty); + + immutable leapSecs = calculateLeapSeconds(stdTime); + immutable unixTime = stdTimeToUnixTime(stdTime); + immutable found = countUntil!"b < a.timeT"(_transitions, unixTime); + + if (found == -1) + return stdTime + convert!("seconds", "hnsecs")(_transitions.back.ttInfo.utcOffset + leapSecs); + + immutable transition = found == 0 ? _transitions[0] : _transitions[found - 1]; + + return stdTime + convert!("seconds", "hnsecs")(transition.ttInfo.utcOffset + leapSecs); + } + + + /++ + Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D. + in this time zone's time and converts it to UTC (i.e. std time). + + Params: + adjTime = The time in this time zone that needs to be adjusted to + UTC time. + +/ + override long tzToUTC(long adjTime) @safe const nothrow + { + assert(!_transitions.empty); + + immutable leapSecs = calculateLeapSeconds(adjTime); + time_t unixTime = stdTimeToUnixTime(adjTime); + immutable past = unixTime - convert!("days", "seconds")(1); + immutable future = unixTime + convert!("days", "seconds")(1); + + immutable pastFound = countUntil!"b < a.timeT"(_transitions, past); + + if (pastFound == -1) + return adjTime - convert!("seconds", "hnsecs")(_transitions.back.ttInfo.utcOffset + leapSecs); + + immutable futureFound = countUntil!"b < a.timeT"(_transitions[pastFound .. $], future); + immutable pastTrans = pastFound == 0 ? _transitions[0] : _transitions[pastFound - 1]; + + if (futureFound == 0) + return adjTime - convert!("seconds", "hnsecs")(pastTrans.ttInfo.utcOffset + leapSecs); + + immutable futureTrans = futureFound == -1 ? _transitions.back + : _transitions[pastFound + futureFound - 1]; + immutable pastOffset = pastTrans.ttInfo.utcOffset; + + if (pastOffset < futureTrans.ttInfo.utcOffset) + unixTime -= convert!("hours", "seconds")(1); + + immutable found = countUntil!"b < a.timeT"(_transitions[pastFound .. $], unixTime - pastOffset); + + if (found == -1) + return adjTime - convert!("seconds", "hnsecs")(_transitions.back.ttInfo.utcOffset + leapSecs); + + immutable transition = found == 0 ? pastTrans : _transitions[pastFound + found - 1]; + + return adjTime - convert!("seconds", "hnsecs")(transition.ttInfo.utcOffset + leapSecs); + } + + + version (Android) + { + // Android concatenates all time zone data into a single file and stores it here. + enum defaultTZDatabaseDir = "/system/usr/share/zoneinfo/"; + } + else version (Posix) + { + /++ + The default directory where the TZ Database files are. It's empty + for Windows, since Windows doesn't have them. + +/ + enum defaultTZDatabaseDir = "/usr/share/zoneinfo/"; + } + else version (Windows) + { + /++ The default directory where the TZ Database files are. It's empty + for Windows, since Windows doesn't have them. + +/ + enum defaultTZDatabaseDir = ""; + } + + + /++ + Returns a $(LREF TimeZone) with the give name per the TZ Database. The + time zone information is fetched from the TZ Database time zone files in + the given directory. + + See_Also: + $(HTTP en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ + Database)
+ $(HTTP en.wikipedia.org/wiki/List_of_tz_database_time_zones, List of + Time Zones) + + Params: + name = The TZ Database name of the desired time zone + tzDatabaseDir = The directory where the TZ Database files are + located. Because these files are not located on + Windows systems, provide them + and give their location here to + use $(LREF PosixTimeZone)s. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given time zone + could not be found or $(D FileException) if the TZ Database file + could not be opened. + +/ + // TODO make it possible for tzDatabaseDir to be gzipped tar file rather than an uncompressed + // directory. + static immutable(PosixTimeZone) getTimeZone(string name, string tzDatabaseDir = defaultTZDatabaseDir) @trusted + { + import std.algorithm.sorting : sort; + import std.conv : to; + import std.format : format; + import std.path : asNormalizedPath, chainPath; + import std.range : retro; + + name = strip(name); + + enforce(tzDatabaseDir.exists(), new DateTimeException(format("Directory %s does not exist.", tzDatabaseDir))); + enforce(tzDatabaseDir.isDir, new DateTimeException(format("%s is not a directory.", tzDatabaseDir))); + + version (Android) + { + auto tzfileOffset = name in tzdataIndex(tzDatabaseDir); + enforce(tzfileOffset, new DateTimeException(format("The time zone %s is not listed.", name))); + string tzFilename = separate_index ? "zoneinfo.dat" : "tzdata"; + const file = asNormalizedPath(chainPath(tzDatabaseDir, tzFilename)).to!string; + } + else + const file = asNormalizedPath(chainPath(tzDatabaseDir, name)).to!string; + + enforce(file.exists(), new DateTimeException(format("File %s does not exist.", file))); + enforce(file.isFile, new DateTimeException(format("%s is not a file.", file))); + + auto tzFile = File(file); + version (Android) tzFile.seek(*tzfileOffset); + immutable gmtZone = name.representation().canFind("GMT"); + + try + { + _enforceValidTZFile(readVal!(char[])(tzFile, 4) == "TZif"); + + immutable char tzFileVersion = readVal!char(tzFile); + _enforceValidTZFile(tzFileVersion == '\0' || tzFileVersion == '2' || tzFileVersion == '3'); + + { + auto zeroBlock = readVal!(ubyte[])(tzFile, 15); + bool allZeroes = true; + + foreach (val; zeroBlock) + { + if (val != 0) + { + allZeroes = false; + break; + } + } + + _enforceValidTZFile(allZeroes); + } + + + // The number of UTC/local indicators stored in the file. + auto tzh_ttisgmtcnt = readVal!int(tzFile); + + // The number of standard/wall indicators stored in the file. + auto tzh_ttisstdcnt = readVal!int(tzFile); + + // The number of leap seconds for which data is stored in the file. + auto tzh_leapcnt = readVal!int(tzFile); + + // The number of "transition times" for which data is stored in the file. + auto tzh_timecnt = readVal!int(tzFile); + + // The number of "local time types" for which data is stored in the file (must not be zero). + auto tzh_typecnt = readVal!int(tzFile); + _enforceValidTZFile(tzh_typecnt != 0); + + // The number of characters of "timezone abbreviation strings" stored in the file. + auto tzh_charcnt = readVal!int(tzFile); + + // time_ts where DST transitions occur. + auto transitionTimeTs = new long[](tzh_timecnt); + foreach (ref transition; transitionTimeTs) + transition = readVal!int(tzFile); + + // Indices into ttinfo structs indicating the changes + // to be made at the corresponding DST transition. + auto ttInfoIndices = new ubyte[](tzh_timecnt); + foreach (ref ttInfoIndex; ttInfoIndices) + ttInfoIndex = readVal!ubyte(tzFile); + + // ttinfos which give info on DST transitions. + auto tempTTInfos = new TempTTInfo[](tzh_typecnt); + foreach (ref ttInfo; tempTTInfos) + ttInfo = readVal!TempTTInfo(tzFile); + + // The array of time zone abbreviation characters. + auto tzAbbrevChars = readVal!(char[])(tzFile, tzh_charcnt); + + auto leapSeconds = new LeapSecond[](tzh_leapcnt); + foreach (ref leapSecond; leapSeconds) + { + // The time_t when the leap second occurs. + auto timeT = readVal!int(tzFile); + + // The total number of leap seconds to be applied after + // the corresponding leap second. + auto total = readVal!int(tzFile); + + leapSecond = LeapSecond(timeT, total); + } + + // Indicate whether each corresponding DST transition were specified + // in standard time or wall clock time. + auto transitionIsStd = new bool[](tzh_ttisstdcnt); + foreach (ref isStd; transitionIsStd) + isStd = readVal!bool(tzFile); + + // Indicate whether each corresponding DST transition associated with + // local time types are specified in UTC or local time. + auto transitionInUTC = new bool[](tzh_ttisgmtcnt); + foreach (ref inUTC; transitionInUTC) + inUTC = readVal!bool(tzFile); + + _enforceValidTZFile(!tzFile.eof); + + // If version 2 or 3, the information is duplicated in 64-bit. + if (tzFileVersion == '2' || tzFileVersion == '3') + { + _enforceValidTZFile(readVal!(char[])(tzFile, 4) == "TZif"); + + immutable char tzFileVersion2 = readVal!(char)(tzFile); + _enforceValidTZFile(tzFileVersion2 == '2' || tzFileVersion2 == '3'); + + { + auto zeroBlock = readVal!(ubyte[])(tzFile, 15); + bool allZeroes = true; + + foreach (val; zeroBlock) + { + if (val != 0) + { + allZeroes = false; + break; + } + } + + _enforceValidTZFile(allZeroes); + } + + + // The number of UTC/local indicators stored in the file. + tzh_ttisgmtcnt = readVal!int(tzFile); + + // The number of standard/wall indicators stored in the file. + tzh_ttisstdcnt = readVal!int(tzFile); + + // The number of leap seconds for which data is stored in the file. + tzh_leapcnt = readVal!int(tzFile); + + // The number of "transition times" for which data is stored in the file. + tzh_timecnt = readVal!int(tzFile); + + // The number of "local time types" for which data is stored in the file (must not be zero). + tzh_typecnt = readVal!int(tzFile); + _enforceValidTZFile(tzh_typecnt != 0); + + // The number of characters of "timezone abbreviation strings" stored in the file. + tzh_charcnt = readVal!int(tzFile); + + // time_ts where DST transitions occur. + transitionTimeTs = new long[](tzh_timecnt); + foreach (ref transition; transitionTimeTs) + transition = readVal!long(tzFile); + + // Indices into ttinfo structs indicating the changes + // to be made at the corresponding DST transition. + ttInfoIndices = new ubyte[](tzh_timecnt); + foreach (ref ttInfoIndex; ttInfoIndices) + ttInfoIndex = readVal!ubyte(tzFile); + + // ttinfos which give info on DST transitions. + tempTTInfos = new TempTTInfo[](tzh_typecnt); + foreach (ref ttInfo; tempTTInfos) + ttInfo = readVal!TempTTInfo(tzFile); + + // The array of time zone abbreviation characters. + tzAbbrevChars = readVal!(char[])(tzFile, tzh_charcnt); + + leapSeconds = new LeapSecond[](tzh_leapcnt); + foreach (ref leapSecond; leapSeconds) + { + // The time_t when the leap second occurs. + auto timeT = readVal!long(tzFile); + + // The total number of leap seconds to be applied after + // the corresponding leap second. + auto total = readVal!int(tzFile); + + leapSecond = LeapSecond(timeT, total); + } + + // Indicate whether each corresponding DST transition were specified + // in standard time or wall clock time. + transitionIsStd = new bool[](tzh_ttisstdcnt); + foreach (ref isStd; transitionIsStd) + isStd = readVal!bool(tzFile); + + // Indicate whether each corresponding DST transition associated with + // local time types are specified in UTC or local time. + transitionInUTC = new bool[](tzh_ttisgmtcnt); + foreach (ref inUTC; transitionInUTC) + inUTC = readVal!bool(tzFile); + } + + _enforceValidTZFile(tzFile.readln().strip().empty); + + cast(void) tzFile.readln(); + + version (Android) + { + // Android uses a single file for all timezone data, so the file + // doesn't end here. + } + else + { + _enforceValidTZFile(tzFile.readln().strip().empty); + _enforceValidTZFile(tzFile.eof); + } + + + auto transitionTypes = new TransitionType*[](tempTTInfos.length); + + foreach (i, ref ttype; transitionTypes) + { + bool isStd = false; + + if (i < transitionIsStd.length && !transitionIsStd.empty) + isStd = transitionIsStd[i]; + + bool inUTC = false; + + if (i < transitionInUTC.length && !transitionInUTC.empty) + inUTC = transitionInUTC[i]; + + ttype = new TransitionType(isStd, inUTC); + } + + auto ttInfos = new immutable(TTInfo)*[](tempTTInfos.length); + foreach (i, ref ttInfo; ttInfos) + { + auto tempTTInfo = tempTTInfos[i]; + + if (gmtZone) + tempTTInfo.tt_gmtoff = -tempTTInfo.tt_gmtoff; + + auto abbrevChars = tzAbbrevChars[tempTTInfo.tt_abbrind .. $]; + string abbrev = abbrevChars[0 .. abbrevChars.countUntil('\0')].idup; + + ttInfo = new immutable(TTInfo)(tempTTInfos[i], abbrev); + } + + auto tempTransitions = new TempTransition[](transitionTimeTs.length); + foreach (i, ref tempTransition; tempTransitions) + { + immutable ttiIndex = ttInfoIndices[i]; + auto transitionTimeT = transitionTimeTs[i]; + auto ttype = transitionTypes[ttiIndex]; + auto ttInfo = ttInfos[ttiIndex]; + + tempTransition = TempTransition(transitionTimeT, ttInfo, ttype); + } + + if (tempTransitions.empty) + { + _enforceValidTZFile(ttInfos.length == 1 && transitionTypes.length == 1); + tempTransitions ~= TempTransition(0, ttInfos[0], transitionTypes[0]); + } + + sort!"a.timeT < b.timeT"(tempTransitions); + sort!"a.timeT < b.timeT"(leapSeconds); + + auto transitions = new Transition[](tempTransitions.length); + foreach (i, ref transition; transitions) + { + auto tempTransition = tempTransitions[i]; + auto transitionTimeT = tempTransition.timeT; + auto ttInfo = tempTransition.ttInfo; + + _enforceValidTZFile(i == 0 || transitionTimeT > tempTransitions[i - 1].timeT); + + transition = Transition(transitionTimeT, ttInfo); + } + + string stdName; + string dstName; + bool hasDST = false; + + foreach (transition; retro(transitions)) + { + auto ttInfo = transition.ttInfo; + + if (ttInfo.isDST) + { + if (dstName.empty) + dstName = ttInfo.abbrev; + hasDST = true; + } + else + { + if (stdName.empty) + stdName = ttInfo.abbrev; + } + + if (!stdName.empty && !dstName.empty) + break; + } + + return new immutable PosixTimeZone(transitions.idup, leapSeconds.idup, name, stdName, dstName, hasDST); + } + catch (DateTimeException dte) + throw dte; + catch (Exception e) + throw new DateTimeException("Not a valid TZ data file", __FILE__, __LINE__, e); + } + + /// + @safe unittest + { + version (Posix) + { + auto tz = PosixTimeZone.getTimeZone("America/Los_Angeles"); + + assert(tz.name == "America/Los_Angeles"); + assert(tz.stdName == "PST"); + assert(tz.dstName == "PDT"); + } + } + + /++ + Returns a list of the names of the time zones installed on the system. + + Providing a sub-name narrows down the list of time zones (which + can number in the thousands). For example, + passing in "America" as the sub-name returns only the time zones which + begin with "America". + + Params: + subName = The first part of the desired time zones. + tzDatabaseDir = The directory where the TZ Database files are + located. + + Throws: + $(D FileException) if it fails to read from disk. + +/ + static string[] getInstalledTZNames(string subName = "", string tzDatabaseDir = defaultTZDatabaseDir) @trusted + { + import std.algorithm.sorting : sort; + import std.array : appender; + import std.format : format; + + version (Posix) + subName = strip(subName); + else version (Windows) + { + import std.array : replace; + import std.path : dirSeparator; + subName = replace(strip(subName), "/", dirSeparator); + } + + enforce(tzDatabaseDir.exists(), new DateTimeException(format("Directory %s does not exist.", tzDatabaseDir))); + enforce(tzDatabaseDir.isDir, new DateTimeException(format("%s is not a directory.", tzDatabaseDir))); + + auto timezones = appender!(string[])(); + + version (Android) + { + import std.algorithm.iteration : filter; + import std.algorithm.mutation : copy; + tzdataIndex(tzDatabaseDir).byKey.filter!(a => a.startsWith(subName)).copy(timezones); + } + else + { + foreach (DirEntry de; dirEntries(tzDatabaseDir, SpanMode.depth)) + { + if (de.isFile) + { + auto tzName = de.name[tzDatabaseDir.length .. $]; + + if (!tzName.extension().empty || + !tzName.startsWith(subName) || + tzName == "leapseconds" || + tzName == "+VERSION") + { + continue; + } + + timezones.put(tzName); + } + } + } + + sort(timezones.data); + + return timezones.data; + } + + version (Posix) @system unittest + { + import std.exception : assertNotThrown; + import std.stdio : writefln; + static void testPTZSuccess(string tzName) + { + scope(failure) writefln("TZName which threw: %s", tzName); + + PosixTimeZone.getTimeZone(tzName); + } + + static void testPTZFailure(string tzName) + { + scope(success) writefln("TZName which was supposed to throw: %s", tzName); + + PosixTimeZone.getTimeZone(tzName); + } + + auto tzNames = getInstalledTZNames(); + + foreach (tzName; tzNames) + assertNotThrown!DateTimeException(testPTZSuccess(tzName)); + + // No timezone directories on Android, just a single tzdata file + version (Android) + {} + else + { + foreach (DirEntry de; dirEntries(defaultTZDatabaseDir, SpanMode.depth)) + { + if (de.isFile) + { + auto tzName = de.name[defaultTZDatabaseDir.length .. $]; + + if (!canFind(tzNames, tzName)) + assertThrown!DateTimeException(testPTZFailure(tzName)); + } + } + } + } + + +private: + + /+ + Holds information on when a time transition occures (usually a + transition to or from DST) as well as a pointer to the $(D TTInfo) which + holds information on the utc offset past the transition. + +/ + struct Transition + { + this(long timeT, immutable (TTInfo)* ttInfo) @safe pure + { + this.timeT = timeT; + this.ttInfo = ttInfo; + } + + long timeT; + immutable (TTInfo)* ttInfo; + } + + + /+ + Holds information on when a leap second occurs. + +/ + struct LeapSecond + { + this(long timeT, int total) @safe pure + { + this.timeT = timeT; + this.total = total; + } + + long timeT; + int total; + } + + /+ + Holds information on the utc offset after a transition as well as + whether DST is in effect after that transition. + +/ + struct TTInfo + { + this(in TempTTInfo tempTTInfo, string abbrev) @safe immutable pure + { + utcOffset = tempTTInfo.tt_gmtoff; + isDST = tempTTInfo.tt_isdst; + this.abbrev = abbrev; + } + + immutable int utcOffset; // Offset from UTC. + immutable bool isDST; // Whether DST is in effect. + immutable string abbrev; // The current abbreviation for the time zone. + } + + + /+ + Struct used to hold information relating to $(D TTInfo) while organizing + the time zone information prior to putting it in its final form. + +/ + struct TempTTInfo + { + this(int gmtOff, bool isDST, ubyte abbrInd) @safe pure + { + tt_gmtoff = gmtOff; + tt_isdst = isDST; + tt_abbrind = abbrInd; + } + + int tt_gmtoff; + bool tt_isdst; + ubyte tt_abbrind; + } + + + /+ + Struct used to hold information relating to $(D Transition) while + organizing the time zone information prior to putting it in its final + form. + +/ + struct TempTransition + { + this(long timeT, immutable (TTInfo)* ttInfo, TransitionType* ttype) @safe pure + { + this.timeT = timeT; + this.ttInfo = ttInfo; + this.ttype = ttype; + } + + long timeT; + immutable (TTInfo)* ttInfo; + TransitionType* ttype; + } + + + /+ + Struct used to hold information relating to $(D Transition) and + $(D TTInfo) while organizing the time zone information prior to putting + it in its final form. + +/ + struct TransitionType + { + this(bool isStd, bool inUTC) @safe pure + { + this.isStd = isStd; + this.inUTC = inUTC; + } + + // Whether the transition is in std time (as opposed to wall clock time). + bool isStd; + + // Whether the transition is in UTC (as opposed to local time). + bool inUTC; + } + + + /+ + Reads an int from a TZ file. + +/ + static T readVal(T)(ref File tzFile) @trusted + if ((isIntegral!T || isSomeChar!T) || is(Unqual!T == bool)) + { + import std.bitmanip : bigEndianToNative; + T[1] buff; + + _enforceValidTZFile(!tzFile.eof); + tzFile.rawRead(buff); + + return bigEndianToNative!T(cast(ubyte[T.sizeof]) buff); + } + + /+ + Reads an array of values from a TZ file. + +/ + static T readVal(T)(ref File tzFile, size_t length) @trusted + if (isArray!T) + { + auto buff = new T(length); + + _enforceValidTZFile(!tzFile.eof); + tzFile.rawRead(buff); + + return buff; + } + + + /+ + Reads a $(D TempTTInfo) from a TZ file. + +/ + static T readVal(T)(ref File tzFile) @safe + if (is(T == TempTTInfo)) + { + return TempTTInfo(readVal!int(tzFile), + readVal!bool(tzFile), + readVal!ubyte(tzFile)); + } + + + /+ + Throws: + $(REF DateTimeException,std,datetime,date) if $(D result) is false. + +/ + static void _enforceValidTZFile(bool result, size_t line = __LINE__) @safe pure + { + if (!result) + throw new DateTimeException("Not a valid tzdata file.", __FILE__, line); + } + + + int calculateLeapSeconds(long stdTime) @safe const pure nothrow + { + if (_leapSeconds.empty) + return 0; + + immutable unixTime = stdTimeToUnixTime(stdTime); + + if (_leapSeconds.front.timeT >= unixTime) + return 0; + + immutable found = countUntil!"b < a.timeT"(_leapSeconds, unixTime); + + if (found == -1) + return _leapSeconds.back.total; + + immutable leapSecond = found == 0 ? _leapSeconds[0] : _leapSeconds[found - 1]; + + return leapSecond.total; + } + + + this(immutable Transition[] transitions, + immutable LeapSecond[] leapSeconds, + string name, + string stdName, + string dstName, + bool hasDST) @safe immutable pure + { + if (dstName.empty && !stdName.empty) + dstName = stdName; + else if (stdName.empty && !dstName.empty) + stdName = dstName; + + super(name, stdName, dstName); + + if (!transitions.empty) + { + foreach (i, transition; transitions[0 .. $-1]) + _enforceValidTZFile(transition.timeT < transitions[i + 1].timeT); + } + + foreach (i, leapSecond; leapSeconds) + _enforceValidTZFile(i == leapSeconds.length - 1 || leapSecond.timeT < leapSeconds[i + 1].timeT); + + _transitions = transitions; + _leapSeconds = leapSeconds; + _hasDST = hasDST; + } + + // Android concatenates the usual timezone directories into a single file, + // tzdata, along with an index to jump to each timezone's offset. In older + // versions of Android, the index was stored in a separate file, zoneinfo.idx, + // whereas now it's stored at the beginning of tzdata. + version (Android) + { + // Keep track of whether there's a separate index, zoneinfo.idx. Only + // check this after calling tzdataIndex, as it's initialized there. + static shared bool separate_index; + + // Extracts the name of each time zone and the offset where its data is + // located in the tzdata file from the index and caches it for later. + static const(uint[string]) tzdataIndex(string tzDir) + { + import std.concurrency : initOnce; + + static __gshared uint[string] _tzIndex; + + // _tzIndex is initialized once and then shared across all threads. + initOnce!_tzIndex( + { + import std.conv : to; + import std.format : format; + import std.path : asNormalizedPath, chainPath; + + enum indexEntrySize = 52; + const combinedFile = asNormalizedPath(chainPath(tzDir, "tzdata")).to!string; + const indexFile = asNormalizedPath(chainPath(tzDir, "zoneinfo.idx")).to!string; + File tzFile; + uint indexEntries, dataOffset; + uint[string] initIndex; + + // Check for the combined file tzdata, which stores the index + // and the time zone data together. + if (combinedFile.exists() && combinedFile.isFile) + { + tzFile = File(combinedFile); + _enforceValidTZFile(readVal!(char[])(tzFile, 6) == "tzdata"); + auto tzDataVersion = readVal!(char[])(tzFile, 6); + _enforceValidTZFile(tzDataVersion[5] == '\0'); + + uint indexOffset = readVal!uint(tzFile); + dataOffset = readVal!uint(tzFile); + readVal!uint(tzFile); + + indexEntries = (dataOffset - indexOffset) / indexEntrySize; + separate_index = false; + } + else if (indexFile.exists() && indexFile.isFile) + { + tzFile = File(indexFile); + indexEntries = to!uint(tzFile.size/indexEntrySize); + separate_index = true; + } + else + { + throw new DateTimeException(format("Both timezone files %s and %s do not exist.", + combinedFile, indexFile)); + } + + foreach (_; 0 .. indexEntries) + { + string tzName = to!string(readVal!(char[])(tzFile, 40).ptr); + uint tzOffset = readVal!uint(tzFile); + readVal!(uint[])(tzFile, 2); + initIndex[tzName] = dataOffset + tzOffset; + } + initIndex.rehash; + return initIndex; + }()); + return _tzIndex; + } + } + + // List of times when the utc offset changes. + immutable Transition[] _transitions; + + // List of leap second occurrences. + immutable LeapSecond[] _leapSeconds; + + // Whether DST is in effect for this time zone at any point in time. + immutable bool _hasDST; +} + + +version (StdDdoc) +{ + /++ + $(BLUE This class is Windows-Only.) + + Represents a time zone from the Windows registry. Unfortunately, Windows + does not use the TZ Database. To use the TZ Database, use + $(LREF PosixTimeZone) (which reads its information from the TZ Database + files on disk) on Windows by providing the TZ Database files and telling + $(D PosixTimeZone.getTimeZone) where the directory holding them is. + + The TZ Database files and Windows' time zone information frequently + do not match. Windows has many errors with regards to when DST switches + occur (especially for historical dates). Also, the TZ Database files + include far more time zones than Windows does. So, for accurate + time zone information, use the TZ Database files with + $(LREF PosixTimeZone) rather than $(D WindowsTimeZone). However, because + $(D WindowsTimeZone) uses Windows system calls to deal with the time, + it's far more likely to match the behavior of other Windows programs. + Be aware of the differences when selecting a method. + + $(D WindowsTimeZone) does not exist on Posix systems. + + To get a $(D WindowsTimeZone), either call + $(D WindowsTimeZone.getTimeZone) or call $(D TimeZone.getTimeZone) + (which will give a $(LREF PosixTimeZone) on Posix systems and a + $(D WindowsTimeZone) on Windows systems). + + See_Also: + $(HTTP www.iana.org/time-zones, Home of the TZ Database files) + +/ + final class WindowsTimeZone : TimeZone + { + public: + + /++ + Whether this time zone has Daylight Savings Time at any point in + time. Note that for some time zone types it may not have DST for + current dates but will still return true for $(D hasDST) because the + time zone did at some point have DST. + +/ + @property override bool hasDST() @safe const nothrow; + + + /++ + Takes the number of hnsecs (100 ns) since midnight, January 1st, + 1 A.D. in UTC time (i.e. std time) and returns whether DST is in + effect in this time zone at the given point in time. + + Params: + stdTime = The UTC time that needs to be checked for DST in this + time zone. + +/ + override bool dstInEffect(long stdTime) @safe const nothrow; + + + /++ + Takes the number of hnsecs (100 ns) since midnight, January 1st, + 1 A.D. in UTC time (i.e. std time) and converts it to this time + zone's time. + + Params: + stdTime = The UTC time that needs to be adjusted to this time + zone's time. + +/ + override long utcToTZ(long stdTime) @safe const nothrow; + + + /++ + Takes the number of hnsecs (100 ns) since midnight, January 1st, + 1 A.D. in this time zone's time and converts it to UTC (i.e. std + time). + + Params: + adjTime = The time in this time zone that needs to be adjusted + to UTC time. + +/ + override long tzToUTC(long adjTime) @safe const nothrow; + + + /++ + Returns a $(LREF TimeZone) with the given name per the Windows time + zone names. The time zone information is fetched from the Windows + registry. + + See_Also: + $(HTTP en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ + Database)
+ $(HTTP en.wikipedia.org/wiki/List_of_tz_database_time_zones, List + of Time Zones) + + Params: + name = The TZ Database name of the desired time zone. + + Throws: + $(REF DateTimeException,std,datetime,date) if the given time + zone could not be found. + + Example: + -------------------- + auto tz = WindowsTimeZone.getTimeZone("Pacific Standard Time"); + -------------------- + +/ + static immutable(WindowsTimeZone) getTimeZone(string name) @safe; + + + /++ + Returns a list of the names of the time zones installed on the + system. The list returned by WindowsTimeZone contains the Windows + TZ names, not the TZ Database names. However, + $(D TimeZone.getinstalledTZNames) will return the TZ Database names + which are equivalent to the Windows TZ names. + +/ + static string[] getInstalledTZNames() @safe; + + private: + + version (Windows) + {} + else + alias TIME_ZONE_INFORMATION = void*; + + static bool _dstInEffect(const TIME_ZONE_INFORMATION* tzInfo, long stdTime) nothrow; + static long _utcToTZ(const TIME_ZONE_INFORMATION* tzInfo, long stdTime, bool hasDST) nothrow; + static long _tzToUTC(const TIME_ZONE_INFORMATION* tzInfo, long adjTime, bool hasDST) nothrow; + + this() immutable pure + { + super("", "", ""); + } + } + +} +else version (Windows) +{ + final class WindowsTimeZone : TimeZone + { + import std.algorithm.sorting : sort; + import std.array : appender; + import std.conv : to; + import std.format : format; + + public: + + @property override bool hasDST() @safe const nothrow + { + return _tzInfo.DaylightDate.wMonth != 0; + } + + + override bool dstInEffect(long stdTime) @safe const nothrow + { + return _dstInEffect(&_tzInfo, stdTime); + } + + + override long utcToTZ(long stdTime) @safe const nothrow + { + return _utcToTZ(&_tzInfo, stdTime, hasDST); + } + + + override long tzToUTC(long adjTime) @safe const nothrow + { + return _tzToUTC(&_tzInfo, adjTime, hasDST); + } + + + static immutable(WindowsTimeZone) getTimeZone(string name) @trusted + { + scope baseKey = Registry.localMachine.getKey(`Software\Microsoft\Windows NT\CurrentVersion\Time Zones`); + + foreach (tzKeyName; baseKey.keyNames) + { + if (tzKeyName != name) + continue; + + scope tzKey = baseKey.getKey(tzKeyName); + + scope stdVal = tzKey.getValue("Std"); + auto stdName = stdVal.value_SZ; + + scope dstVal = tzKey.getValue("Dlt"); + auto dstName = dstVal.value_SZ; + + scope tziVal = tzKey.getValue("TZI"); + auto binVal = tziVal.value_BINARY; + assert(binVal.length == REG_TZI_FORMAT.sizeof); + auto tziFmt = cast(REG_TZI_FORMAT*) binVal.ptr; + + TIME_ZONE_INFORMATION tzInfo; + + auto wstdName = stdName.to!wstring; + auto wdstName = dstName.to!wstring; + auto wstdNameLen = wstdName.length > 32 ? 32 : wstdName.length; + auto wdstNameLen = wdstName.length > 32 ? 32 : wdstName.length; + + tzInfo.Bias = tziFmt.Bias; + tzInfo.StandardName[0 .. wstdNameLen] = wstdName[0 .. wstdNameLen]; + tzInfo.StandardName[wstdNameLen .. $] = '\0'; + tzInfo.StandardDate = tziFmt.StandardDate; + tzInfo.StandardBias = tziFmt.StandardBias; + tzInfo.DaylightName[0 .. wdstNameLen] = wdstName[0 .. wdstNameLen]; + tzInfo.DaylightName[wdstNameLen .. $] = '\0'; + tzInfo.DaylightDate = tziFmt.DaylightDate; + tzInfo.DaylightBias = tziFmt.DaylightBias; + + return new immutable WindowsTimeZone(name, tzInfo); + } + throw new DateTimeException(format("Failed to find time zone: %s", name)); + } + + static string[] getInstalledTZNames() @trusted + { + auto timezones = appender!(string[])(); + + scope baseKey = Registry.localMachine.getKey(`Software\Microsoft\Windows NT\CurrentVersion\Time Zones`); + + foreach (tzKeyName; baseKey.keyNames) + timezones.put(tzKeyName); + sort(timezones.data); + + return timezones.data; + } + + @safe unittest + { + import std.exception : assertNotThrown; + import std.stdio : writefln; + static void testWTZSuccess(string tzName) + { + scope(failure) writefln("TZName which threw: %s", tzName); + + WindowsTimeZone.getTimeZone(tzName); + } + + auto tzNames = getInstalledTZNames(); + + foreach (tzName; tzNames) + assertNotThrown!DateTimeException(testWTZSuccess(tzName)); + } + + + private: + + static bool _dstInEffect(const TIME_ZONE_INFORMATION* tzInfo, long stdTime) @trusted nothrow + { + try + { + if (tzInfo.DaylightDate.wMonth == 0) + return false; + + auto utcDateTime = cast(DateTime) SysTime(stdTime, UTC()); + + //The limits of what SystemTimeToTzSpecificLocalTime will accept. + if (utcDateTime.year < 1601) + { + if (utcDateTime.month == Month.feb && utcDateTime.day == 29) + utcDateTime.day = 28; + utcDateTime.year = 1601; + } + else if (utcDateTime.year > 30_827) + { + if (utcDateTime.month == Month.feb && utcDateTime.day == 29) + utcDateTime.day = 28; + utcDateTime.year = 30_827; + } + + //SystemTimeToTzSpecificLocalTime doesn't act correctly at the + //beginning or end of the year (bleh). Unless some bizarre time + //zone changes DST on January 1st or December 31st, this should + //fix the problem. + if (utcDateTime.month == Month.jan) + { + if (utcDateTime.day == 1) + utcDateTime.day = 2; + } + else if (utcDateTime.month == Month.dec && utcDateTime.day == 31) + utcDateTime.day = 30; + + SYSTEMTIME utcTime = void; + SYSTEMTIME otherTime = void; + + utcTime.wYear = utcDateTime.year; + utcTime.wMonth = utcDateTime.month; + utcTime.wDay = utcDateTime.day; + utcTime.wHour = utcDateTime.hour; + utcTime.wMinute = utcDateTime.minute; + utcTime.wSecond = utcDateTime.second; + utcTime.wMilliseconds = 0; + + immutable result = SystemTimeToTzSpecificLocalTime(cast(TIME_ZONE_INFORMATION*) tzInfo, + &utcTime, + &otherTime); + assert(result); + + immutable otherDateTime = DateTime(otherTime.wYear, + otherTime.wMonth, + otherTime.wDay, + otherTime.wHour, + otherTime.wMinute, + otherTime.wSecond); + immutable diff = utcDateTime - otherDateTime; + immutable minutes = diff.total!"minutes" - tzInfo.Bias; + + if (minutes == tzInfo.DaylightBias) + return true; + + assert(minutes == tzInfo.StandardBias); + + return false; + } + catch (Exception e) + assert(0, "DateTime's constructor threw."); + } + + @system unittest + { + TIME_ZONE_INFORMATION tzInfo; + GetTimeZoneInformation(&tzInfo); + + foreach (year; [1600, 1601, 30_827, 30_828]) + WindowsTimeZone._dstInEffect(&tzInfo, SysTime(DateTime(year, 1, 1)).stdTime); + } + + + static long _utcToTZ(const TIME_ZONE_INFORMATION* tzInfo, long stdTime, bool hasDST) @safe nothrow + { + if (hasDST && WindowsTimeZone._dstInEffect(tzInfo, stdTime)) + return stdTime - convert!("minutes", "hnsecs")(tzInfo.Bias + tzInfo.DaylightBias); + + return stdTime - convert!("minutes", "hnsecs")(tzInfo.Bias + tzInfo.StandardBias); + } + + + static long _tzToUTC(const TIME_ZONE_INFORMATION* tzInfo, long adjTime, bool hasDST) @trusted nothrow + { + if (hasDST) + { + try + { + bool dstInEffectForLocalDateTime(DateTime localDateTime) + { + // The limits of what SystemTimeToTzSpecificLocalTime will accept. + if (localDateTime.year < 1601) + { + if (localDateTime.month == Month.feb && localDateTime.day == 29) + localDateTime.day = 28; + + localDateTime.year = 1601; + } + else if (localDateTime.year > 30_827) + { + if (localDateTime.month == Month.feb && localDateTime.day == 29) + localDateTime.day = 28; + + localDateTime.year = 30_827; + } + + // SystemTimeToTzSpecificLocalTime doesn't act correctly at the + // beginning or end of the year (bleh). Unless some bizarre time + // zone changes DST on January 1st or December 31st, this should + // fix the problem. + if (localDateTime.month == Month.jan) + { + if (localDateTime.day == 1) + localDateTime.day = 2; + } + else if (localDateTime.month == Month.dec && localDateTime.day == 31) + localDateTime.day = 30; + + SYSTEMTIME utcTime = void; + SYSTEMTIME localTime = void; + + localTime.wYear = localDateTime.year; + localTime.wMonth = localDateTime.month; + localTime.wDay = localDateTime.day; + localTime.wHour = localDateTime.hour; + localTime.wMinute = localDateTime.minute; + localTime.wSecond = localDateTime.second; + localTime.wMilliseconds = 0; + + immutable result = TzSpecificLocalTimeToSystemTime(cast(TIME_ZONE_INFORMATION*) tzInfo, + &localTime, + &utcTime); + assert(result); + + immutable utcDateTime = DateTime(utcTime.wYear, + utcTime.wMonth, + utcTime.wDay, + utcTime.wHour, + utcTime.wMinute, + utcTime.wSecond); + + immutable diff = localDateTime - utcDateTime; + immutable minutes = -tzInfo.Bias - diff.total!"minutes"; + + if (minutes == tzInfo.DaylightBias) + return true; + + assert(minutes == tzInfo.StandardBias); + + return false; + } + + auto localDateTime = cast(DateTime) SysTime(adjTime, UTC()); + auto localDateTimeBefore = localDateTime - dur!"hours"(1); + auto localDateTimeAfter = localDateTime + dur!"hours"(1); + + auto dstInEffectNow = dstInEffectForLocalDateTime(localDateTime); + auto dstInEffectBefore = dstInEffectForLocalDateTime(localDateTimeBefore); + auto dstInEffectAfter = dstInEffectForLocalDateTime(localDateTimeAfter); + + bool isDST; + + if (dstInEffectBefore && dstInEffectNow && dstInEffectAfter) + isDST = true; + else if (!dstInEffectBefore && !dstInEffectNow && !dstInEffectAfter) + isDST = false; + else if (!dstInEffectBefore && dstInEffectAfter) + isDST = false; + else if (dstInEffectBefore && !dstInEffectAfter) + isDST = dstInEffectNow; + else + assert(0, "Bad Logic."); + + if (isDST) + return adjTime + convert!("minutes", "hnsecs")(tzInfo.Bias + tzInfo.DaylightBias); + } + catch (Exception e) + assert(0, "SysTime's constructor threw."); + } + + return adjTime + convert!("minutes", "hnsecs")(tzInfo.Bias + tzInfo.StandardBias); + } + + + this(string name, TIME_ZONE_INFORMATION tzInfo) @trusted immutable pure + { + super(name, to!string(tzInfo.StandardName.ptr), to!string(tzInfo.DaylightName.ptr)); + _tzInfo = tzInfo; + } + + + TIME_ZONE_INFORMATION _tzInfo; + } +} + + +version (StdDdoc) +{ + /++ + $(BLUE This function is Posix-Only.) + + Sets the local time zone on Posix systems with the TZ + Database name by setting the TZ environment variable. + + Unfortunately, there is no way to do it on Windows using the TZ + Database name, so this function only exists on Posix systems. + +/ + void setTZEnvVar(string tzDatabaseName) @safe nothrow; + + + /++ + $(BLUE This function is Posix-Only.) + + Clears the TZ environment variable. + +/ + void clearTZEnvVar() @safe nothrow; +} +else version (Posix) +{ + void setTZEnvVar(string tzDatabaseName) @trusted nothrow + { + import core.stdc.time : tzset; + import core.sys.posix.stdlib : setenv; + import std.internal.cstring : tempCString; + import std.path : asNormalizedPath, chainPath; + + version (Android) + auto value = asNormalizedPath(tzDatabaseName); + else + auto value = asNormalizedPath(chainPath(PosixTimeZone.defaultTZDatabaseDir, tzDatabaseName)); + setenv("TZ", value.tempCString(), 1); + tzset(); + } + + + void clearTZEnvVar() @trusted nothrow + { + import core.stdc.time : tzset; + import core.sys.posix.stdlib : unsetenv; + + unsetenv("TZ"); + tzset(); + } +} + + +/++ + Provides the conversions between the IANA time zone database time zone names + (which POSIX systems use) and the time zone names that Windows uses. + + Windows uses a different set of time zone names than the IANA time zone + database does, and how they correspond to one another changes over time + (particularly when Microsoft updates Windows). + $(HTTP unicode.org/cldr/data/common/supplemental/windowsZones.xml, windowsZones.xml) + provides the current conversions (which may or may not match up with what's + on a particular Windows box depending on how up-to-date it is), and + parseTZConversions reads in those conversions from windowsZones.xml so that + a D program can use those conversions. + + However, it should be noted that the time zone information on Windows is + frequently less accurate than that in the IANA time zone database, and if + someone really wants accurate time zone information, they should use the + IANA time zone database files with $(LREF PosixTimeZone) on Windows rather + than $(LREF WindowsTimeZone), whereas $(LREF WindowsTimeZone) makes more + sense when trying to match what Windows will think the time is in a specific + time zone. + + Also, the IANA time zone database has a lot more time zones than Windows + does. + + Params: + windowsZonesXMLText = The text from + $(HTTP unicode.org/cldr/data/common/supplemental/windowsZones.xml, windowsZones.xml) + + Throws: + Exception if there is an error while parsing the given XML. + +-------------------- + // Parse the conversions from a local file. + auto text = std.file.readText("path/to/windowsZones.xml"); + auto conversions = parseTZConversions(text); + + // Alternatively, grab the XML file from the web at runtime + // and parse it so that it's guaranteed to be up-to-date, though + // that has the downside that the code needs to worry about the + // site being down or unicode.org changing the URL. + auto url = "http://unicode.org/cldr/data/common/supplemental/windowsZones.xml"; + auto conversions2 = parseTZConversions(std.net.curl.get(url)); +-------------------- + +/ +struct TZConversions +{ + /++ + The key is the Windows time zone name, and the value is a list of + IANA TZ database names which are close (currently only ever one, but + it allows for multiple in case it's ever necessary). + +/ + string[][string] toWindows; + + /++ + The key is the IANA time zone database name, and the value is a list of + Windows time zone names which are close (usually only one, but it could + be multiple). + +/ + string[][string] fromWindows; +} + +/++ ditto +/ +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). + import std.algorithm.iteration : uniq; + import std.algorithm.searching : find; + import std.algorithm.sorting : sort; + import std.array : array, split; + import std.string : lineSplitter; + + string[][string] win2Nix; + string[][string] nix2Win; + + immutable f1 = ` + + line = line.find(f1); + if (line.empty) + continue; + line = line[f1.length .. $]; + auto next = line.find('"'); + enforce(!next.empty, "Error parsing. Text does not appear to be from windowsZones.xml"); + auto win = line[0 .. $ - next.length]; + line = next.find(f2); + enforce(!line.empty, "Error parsing. Text does not appear to be from windowsZones.xml"); + line = line[f2.length .. $]; + next = line.find('"'); + enforce(!next.empty, "Error parsing. Text does not appear to be from windowsZones.xml"); + auto nixes = line[0 .. $ - next.length].split(); + + if (auto n = win in win2Nix) + *n ~= nixes; + else + win2Nix[win] = nixes; + + foreach (nix; nixes) + { + if (auto w = nix in nix2Win) + *w ~= win; + else + nix2Win[nix] = [win]; + } + } + + foreach (key, ref value; nix2Win) + value = value.sort().uniq().array(); + foreach (key, ref value; win2Nix) + value = value.sort().uniq().array(); + + return TZConversions(nix2Win, win2Nix); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : uniq; + import std.algorithm.sorting : isSorted; + + // Reduced text from http://unicode.org/cldr/data/common/supplemental/windowsZones.xml + auto sampleFileText = +` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + + auto tzConversions = parseTZConversions(sampleFileText); + assert(tzConversions.toWindows.length == 15); + assert(tzConversions.toWindows["America/Anchorage"] == ["Alaskan Standard Time"]); + assert(tzConversions.toWindows["America/Juneau"] == ["Alaskan Standard Time"]); + assert(tzConversions.toWindows["America/Nome"] == ["Alaskan Standard Time"]); + assert(tzConversions.toWindows["America/Sitka"] == ["Alaskan Standard Time"]); + assert(tzConversions.toWindows["America/Yakutat"] == ["Alaskan Standard Time"]); + assert(tzConversions.toWindows["Etc/GMT+10"] == ["Hawaiian Standard Time"]); + assert(tzConversions.toWindows["Etc/GMT+11"] == ["UTC-11"]); + assert(tzConversions.toWindows["Etc/GMT+12"] == ["Dateline Standard Time"]); + assert(tzConversions.toWindows["Pacific/Honolulu"] == ["Hawaiian Standard Time"]); + assert(tzConversions.toWindows["Pacific/Johnston"] == ["Hawaiian Standard Time"]); + assert(tzConversions.toWindows["Pacific/Midway"] == ["UTC-11"]); + assert(tzConversions.toWindows["Pacific/Niue"] == ["UTC-11"]); + assert(tzConversions.toWindows["Pacific/Pago_Pago"] == ["UTC-11"]); + assert(tzConversions.toWindows["Pacific/Rarotonga"] == ["Hawaiian Standard Time"]); + assert(tzConversions.toWindows["Pacific/Tahiti"] == ["Hawaiian Standard Time"]); + + assert(tzConversions.fromWindows.length == 4); + assert(tzConversions.fromWindows["Alaskan Standard Time"] == + ["America/Anchorage", "America/Juneau", "America/Nome", "America/Sitka", "America/Yakutat"]); + assert(tzConversions.fromWindows["Dateline Standard Time"] == ["Etc/GMT+12"]); + assert(tzConversions.fromWindows["Hawaiian Standard Time"] == + ["Etc/GMT+10", "Pacific/Honolulu", "Pacific/Johnston", "Pacific/Rarotonga", "Pacific/Tahiti"]); + assert(tzConversions.fromWindows["UTC-11"] == + ["Etc/GMT+11", "Pacific/Midway", "Pacific/Niue", "Pacific/Pago_Pago"]); + + foreach (key, value; tzConversions.fromWindows) + { + assert(value.isSorted, key); + assert(equal(value.uniq(), value), key); + } +} + + +// Explicitly undocumented. It will be removed in June 2018. @@@DEPRECATED_2018-07@@@ +deprecated("Use parseTZConversions instead") +string tzDatabaseNameToWindowsTZName(string tzName) @safe pure nothrow @nogc +{ + switch (tzName) + { + case "Africa/Abidjan": return "Greenwich Standard Time"; + case "Africa/Accra": return "Greenwich Standard Time"; + case "Africa/Addis_Ababa": return "E. Africa Standard Time"; + case "Africa/Algiers": return "W. Central Africa Standard Time"; + case "Africa/Asmera": return "E. Africa Standard Time"; + case "Africa/Bamako": return "Greenwich Standard Time"; + case "Africa/Bangui": return "W. Central Africa Standard Time"; + case "Africa/Banjul": return "Greenwich Standard Time"; + case "Africa/Bissau": return "Greenwich Standard Time"; + case "Africa/Blantyre": return "South Africa Standard Time"; + case "Africa/Brazzaville": return "W. Central Africa Standard Time"; + case "Africa/Bujumbura": return "South Africa Standard Time"; + case "Africa/Cairo": return "Egypt Standard Time"; + case "Africa/Casablanca": return "Morocco Standard Time"; + case "Africa/Ceuta": return "Romance Standard Time"; + case "Africa/Conakry": return "Greenwich Standard Time"; + case "Africa/Dakar": return "Greenwich Standard Time"; + case "Africa/Dar_es_Salaam": return "E. Africa Standard Time"; + case "Africa/Djibouti": return "E. Africa Standard Time"; + case "Africa/Douala": return "W. Central Africa Standard Time"; + case "Africa/El_Aaiun": return "Morocco Standard Time"; + case "Africa/Freetown": return "Greenwich Standard Time"; + case "Africa/Gaborone": return "South Africa Standard Time"; + case "Africa/Harare": return "South Africa Standard Time"; + case "Africa/Johannesburg": return "South Africa Standard Time"; + case "Africa/Juba": return "E. Africa Standard Time"; + case "Africa/Kampala": return "E. Africa Standard Time"; + case "Africa/Khartoum": return "E. Africa Standard Time"; + case "Africa/Kigali": return "South Africa Standard Time"; + case "Africa/Kinshasa": return "W. Central Africa Standard Time"; + case "Africa/Lagos": return "W. Central Africa Standard Time"; + case "Africa/Libreville": return "W. Central Africa Standard Time"; + case "Africa/Lome": return "Greenwich Standard Time"; + case "Africa/Luanda": return "W. Central Africa Standard Time"; + case "Africa/Lubumbashi": return "South Africa Standard Time"; + case "Africa/Lusaka": return "South Africa Standard Time"; + case "Africa/Malabo": return "W. Central Africa Standard Time"; + case "Africa/Maputo": return "South Africa Standard Time"; + case "Africa/Maseru": return "South Africa Standard Time"; + case "Africa/Mbabane": return "South Africa Standard Time"; + case "Africa/Mogadishu": return "E. Africa Standard Time"; + case "Africa/Monrovia": return "Greenwich Standard Time"; + case "Africa/Nairobi": return "E. Africa Standard Time"; + case "Africa/Ndjamena": return "W. Central Africa Standard Time"; + case "Africa/Niamey": return "W. Central Africa Standard Time"; + case "Africa/Nouakchott": return "Greenwich Standard Time"; + case "Africa/Ouagadougou": return "Greenwich Standard Time"; + case "Africa/Porto-Novo": return "W. Central Africa Standard Time"; + case "Africa/Sao_Tome": return "Greenwich Standard Time"; + case "Africa/Tripoli": return "Libya Standard Time"; + case "Africa/Tunis": return "W. Central Africa Standard Time"; + case "Africa/Windhoek": return "Namibia Standard Time"; + case "America/Adak": return "Aleutian Standard Time"; + case "America/Anchorage": return "Alaskan Standard Time"; + case "America/Anguilla": return "SA Western Standard Time"; + case "America/Antigua": return "SA Western Standard Time"; + case "America/Araguaina": return "SA Eastern Standard Time"; + case "America/Argentina/La_Rioja": return "Argentina Standard Time"; + case "America/Argentina/Rio_Gallegos": return "Argentina Standard Time"; + case "America/Argentina/Salta": return "Argentina Standard Time"; + case "America/Argentina/San_Juan": return "Argentina Standard Time"; + case "America/Argentina/San_Luis": return "Argentina Standard Time"; + case "America/Argentina/Tucuman": return "Argentina Standard Time"; + case "America/Argentina/Ushuaia": return "Argentina Standard Time"; + case "America/Arguaina": return "Tocantins Standard Time"; + case "America/Aruba": return "SA Western Standard Time"; + case "America/Asuncion": return "Paraguay Standard Time"; + case "America/Bahia": return "Bahia Standard Time"; + case "America/Bahia_Banderas": return "Central Standard Time (Mexico)"; + case "America/Barbados": return "SA Western Standard Time"; + case "America/Belem": return "SA Eastern Standard Time"; + case "America/Belize": return "Central America Standard Time"; + case "America/Blanc-Sablon": return "SA Western Standard Time"; + case "America/Boa_Vista": return "SA Western Standard Time"; + case "America/Bogota": return "SA Pacific Standard Time"; + case "America/Boise": return "Mountain Standard Time"; + case "America/Buenos_Aires": return "Argentina Standard Time"; + case "America/Cambridge_Bay": return "Mountain Standard Time"; + case "America/Campo_Grande": return "Central Brazilian Standard Time"; + case "America/Cancun": return "Eastern Standard Time (Mexico)"; + case "America/Caracas": return "Venezuela Standard Time"; + case "America/Catamarca": return "Argentina Standard Time"; + case "America/Cayenne": return "SA Eastern Standard Time"; + case "America/Cayman": return "SA Pacific Standard Time"; + case "America/Chicago": return "Central Standard Time"; + case "America/Chihuahua": return "Mountain Standard Time (Mexico)"; + case "America/Coral_Harbour": return "SA Pacific Standard Time"; + case "America/Cordoba": return "Argentina Standard Time"; + case "America/Costa_Rica": return "Central America Standard Time"; + case "America/Creston": return "US Mountain Standard Time"; + case "America/Cuiaba": return "Central Brazilian Standard Time"; + case "America/Curacao": return "SA Western Standard Time"; + case "America/Danmarkshavn": return "UTC"; + case "America/Dawson": return "Pacific Standard Time"; + case "America/Dawson_Creek": return "US Mountain Standard Time"; + case "America/Denver": return "Mountain Standard Time"; + case "America/Detroit": return "Eastern Standard Time"; + case "America/Dominica": return "SA Western Standard Time"; + case "America/Edmonton": return "Mountain Standard Time"; + case "America/Eirunepe": return "SA Pacific Standard Time"; + case "America/El_Salvador": return "Central America Standard Time"; + case "America/Fortaleza": return "SA Eastern Standard Time"; + case "America/Glace_Bay": return "Atlantic Standard Time"; + case "America/Godthab": return "Greenland Standard Time"; + case "America/Goose_Bay": return "Atlantic Standard Time"; + case "America/Grand_Turk": return "Turks And Caicos Standard Time"; + case "America/Grenada": return "SA Western Standard Time"; + case "America/Guadeloupe": return "SA Western Standard Time"; + case "America/Guatemala": return "Central America Standard Time"; + case "America/Guayaquil": return "SA Pacific Standard Time"; + case "America/Guyana": return "SA Western Standard Time"; + case "America/Halifax": return "Atlantic Standard Time"; + case "America/Havana": return "Cuba Standard Time"; + case "America/Hermosillo": return "US Mountain Standard Time"; + case "America/Indiana/Knox": return "Central Standard Time"; + case "America/Indiana/Marengo": return "US Eastern Standard Time"; + case "America/Indiana/Petersburg": return "Eastern Standard Time"; + case "America/Indiana/Tell_City": return "Central Standard Time"; + case "America/Indiana/Vevay": return "US Eastern Standard Time"; + case "America/Indiana/Vincennes": return "Eastern Standard Time"; + case "America/Indiana/Winamac": return "Eastern Standard Time"; + case "America/Indianapolis": return "US Eastern Standard Time"; + case "America/Inuvik": return "Mountain Standard Time"; + case "America/Iqaluit": return "Eastern Standard Time"; + case "America/Jamaica": return "SA Pacific Standard Time"; + case "America/Jujuy": return "Argentina Standard Time"; + case "America/Juneau": return "Alaskan Standard Time"; + case "America/Kentucky/Monticello": return "Eastern Standard Time"; + case "America/Kralendijk": return "SA Western Standard Time"; + case "America/La_Paz": return "SA Western Standard Time"; + case "America/Lima": return "SA Pacific Standard Time"; + case "America/Los_Angeles": return "Pacific Standard Time"; + case "America/Louisville": return "Eastern Standard Time"; + case "America/Lower_Princes": return "SA Western Standard Time"; + case "America/Maceio": return "SA Eastern Standard Time"; + case "America/Managua": return "Central America Standard Time"; + case "America/Manaus": return "SA Western Standard Time"; + case "America/Marigot": return "SA Western Standard Time"; + case "America/Martinique": return "SA Western Standard Time"; + case "America/Matamoros": return "Central Standard Time"; + case "America/Mazatlan": return "Mountain Standard Time (Mexico)"; + case "America/Mendoza": return "Argentina Standard Time"; + case "America/Menominee": return "Central Standard Time"; + case "America/Merida": return "Central Standard Time (Mexico)"; + case "America/Mexico_City": return "Central Standard Time (Mexico)"; + case "America/Miquelon": return "Saint Pierre Standard Time"; + case "America/Moncton": return "Atlantic Standard Time"; + case "America/Monterrey": return "Central Standard Time (Mexico)"; + case "America/Montevideo": return "Montevideo Standard Time"; + case "America/Montreal": return "Eastern Standard Time"; + case "America/Montserrat": return "SA Western Standard Time"; + case "America/Nassau": return "Eastern Standard Time"; + case "America/New_York": return "Eastern Standard Time"; + case "America/Nipigon": return "Eastern Standard Time"; + case "America/Nome": return "Alaskan Standard Time"; + case "America/Noronha": return "UTC-02"; + case "America/North_Dakota/Beulah": return "Central Standard Time"; + case "America/North_Dakota/Center": return "Central Standard Time"; + case "America/North_Dakota/New_Salem": return "Central Standard Time"; + case "America/Ojinaga": return "Mountain Standard Time"; + case "America/Panama": return "SA Pacific Standard Time"; + case "America/Pangnirtung": return "Eastern Standard Time"; + case "America/Paramaribo": return "SA Eastern Standard Time"; + case "America/Phoenix": return "US Mountain Standard Time"; + case "America/Port-au-Prince": return "Haiti Standard Time"; + case "America/Port_of_Spain": return "SA Western Standard Time"; + case "America/Porto_Velho": return "SA Western Standard Time"; + case "America/Puerto_Rico": return "SA Western Standard Time"; + case "America/Rainy_River": return "Central Standard Time"; + case "America/Rankin_Inlet": return "Central Standard Time"; + case "America/Recife": return "SA Eastern Standard Time"; + case "America/Regina": return "Canada Central Standard Time"; + case "America/Resolute": return "Central Standard Time"; + case "America/Rio_Branco": return "SA Pacific Standard Time"; + case "America/Santa_Isabel": return "Pacific Standard Time (Mexico)"; + case "America/Santarem": return "SA Eastern Standard Time"; + case "America/Santiago": return "Pacific SA Standard Time"; + case "America/Santo_Domingo": return "SA Western Standard Time"; + case "America/Sao_Paulo": return "E. South America Standard Time"; + case "America/Scoresbysund": return "Azores Standard Time"; + case "America/Sitka": return "Alaskan Standard Time"; + case "America/St_Barthelemy": return "SA Western Standard Time"; + case "America/St_Johns": return "Newfoundland Standard Time"; + case "America/St_Kitts": return "SA Western Standard Time"; + case "America/St_Lucia": return "SA Western Standard Time"; + case "America/St_Thomas": return "SA Western Standard Time"; + case "America/St_Vincent": return "SA Western Standard Time"; + case "America/Swift_Current": return "Canada Central Standard Time"; + case "America/Tegucigalpa": return "Central America Standard Time"; + case "America/Thule": return "Atlantic Standard Time"; + case "America/Thunder_Bay": return "Eastern Standard Time"; + case "America/Tijuana": return "Pacific Standard Time"; + case "America/Toronto": return "Eastern Standard Time"; + case "America/Tortola": return "SA Western Standard Time"; + case "America/Vancouver": return "Pacific Standard Time"; + case "America/Whitehorse": return "Pacific Standard Time"; + case "America/Winnipeg": return "Central Standard Time"; + case "America/Yakutat": return "Alaskan Standard Time"; + case "America/Yellowknife": return "Mountain Standard Time"; + case "Antarctica/Casey": return "W. Australia Standard Time"; + case "Antarctica/Davis": return "SE Asia Standard Time"; + case "Antarctica/DumontDUrville": return "West Pacific Standard Time"; + case "Antarctica/Macquarie": return "Central Pacific Standard Time"; + case "Antarctica/Mawson": return "West Asia Standard Time"; + case "Antarctica/McMurdo": return "New Zealand Standard Time"; + case "Antarctica/Palmer": return "Pacific SA Standard Time"; + case "Antarctica/Rothera": return "SA Eastern Standard Time"; + case "Antarctica/Syowa": return "E. Africa Standard Time"; + case "Antarctica/Vostok": return "Central Asia Standard Time"; + case "Arctic/Longyearbyen": return "W. Europe Standard Time"; + case "Asia/Aden": return "Arab Standard Time"; + case "Asia/Almaty": return "Central Asia Standard Time"; + case "Asia/Amman": return "Jordan Standard Time"; + case "Asia/Anadyr": return "Russia Time Zone 11"; + case "Asia/Aqtau": return "West Asia Standard Time"; + case "Asia/Aqtobe": return "West Asia Standard Time"; + case "Asia/Ashgabat": return "West Asia Standard Time"; + case "Asia/Baghdad": return "Arabic Standard Time"; + case "Asia/Bahrain": return "Arab Standard Time"; + case "Asia/Baku": return "Azerbaijan Standard Time"; + case "Asia/Bangkok": return "SE Asia Standard Time"; + case "Asia/Barnaul": return "Altai Standard Time"; + case "Asia/Beirut": return "Middle East Standard Time"; + case "Asia/Bishkek": return "Central Asia Standard Time"; + case "Asia/Brunei": return "Singapore Standard Time"; + case "Asia/Calcutta": return "India Standard Time"; + case "Asia/Chita": return "Transbaikal Standard Time"; + case "Asia/Choibalsan": return "Ulaanbaatar Standard Time"; + case "Asia/Colombo": return "Sri Lanka Standard Time"; + case "Asia/Damascus": return "Syria Standard Time"; + case "Asia/Dhaka": return "Bangladesh Standard Time"; + case "Asia/Dili": return "Tokyo Standard Time"; + case "Asia/Dubai": return "Arabian Standard Time"; + case "Asia/Dushanbe": return "West Asia Standard Time"; + case "Asia/Hebron": return "West Bank Standard Time"; + case "Asia/Hong_Kong": return "China Standard Time"; + case "Asia/Hovd": return "W. Mongolia Standard Time"; + case "Asia/Irkutsk": return "North Asia East Standard Time"; + case "Asia/Jakarta": return "SE Asia Standard Time"; + case "Asia/Jayapura": return "Tokyo Standard Time"; + case "Asia/Jerusalem": return "Israel Standard Time"; + case "Asia/Kabul": return "Afghanistan Standard Time"; + case "Asia/Kamchatka": return "Russia Time Zone 11"; + case "Asia/Karachi": return "Pakistan Standard Time"; + case "Asia/Katmandu": return "Nepal Standard Time"; + case "Asia/Khandyga": return "Yakutsk Standard Time"; + case "Asia/Krasnoyarsk": return "North Asia Standard Time"; + case "Asia/Kuala_Lumpur": return "Singapore Standard Time"; + case "Asia/Kuching": return "Singapore Standard Time"; + case "Asia/Kuwait": return "Arab Standard Time"; + case "Asia/Macau": return "China Standard Time"; + case "Asia/Magadan": return "Magadan Standard Time"; + case "Asia/Makassar": return "Singapore Standard Time"; + case "Asia/Manila": return "Singapore Standard Time"; + case "Asia/Muscat": return "Arabian Standard Time"; + case "Asia/Nicosia": return "GTB Standard Time"; + case "Asia/Novokuznetsk": return "North Asia Standard Time"; + case "Asia/Novosibirsk": return "N. Central Asia Standard Time"; + case "Asia/Omsk": return "N. Central Asia Standard Time"; + case "Asia/Oral": return "West Asia Standard Time"; + case "Asia/Phnom_Penh": return "SE Asia Standard Time"; + case "Asia/Pontianak": return "SE Asia Standard Time"; + case "Asia/Pyongyang": return "North Korea Standard Time"; + case "Asia/Qatar": return "Arab Standard Time"; + case "Asia/Qyzylorda": return "Central Asia Standard Time"; + case "Asia/Rangoon": return "Myanmar Standard Time"; + case "Asia/Riyadh": return "Arab Standard Time"; + case "Asia/Saigon": return "SE Asia Standard Time"; + case "Asia/Sakhalin": return "Sakhalin Standard Time"; + case "Asia/Samarkand": return "West Asia Standard Time"; + case "Asia/Seoul": return "Korea Standard Time"; + case "Asia/Shanghai": return "China Standard Time"; + case "Asia/Singapore": return "Singapore Standard Time"; + case "Asia/Srednekolymsk": return "Russia Time Zone 10"; + case "Asia/Taipei": return "Taipei Standard Time"; + case "Asia/Tashkent": return "West Asia Standard Time"; + case "Asia/Tbilisi": return "Georgian Standard Time"; + case "Asia/Tehran": return "Iran Standard Time"; + case "Asia/Thimphu": return "Bangladesh Standard Time"; + case "Asia/Tokyo": return "Tokyo Standard Time"; + case "Asia/Tomsk": return "Tomsk Standard Time"; + case "Asia/Ulaanbaatar": return "Ulaanbaatar Standard Time"; + case "Asia/Urumqi": return "Central Asia Standard Time"; + case "Asia/Ust-Nera": return "Vladivostok Standard Time"; + case "Asia/Vientiane": return "SE Asia Standard Time"; + case "Asia/Vladivostok": return "Vladivostok Standard Time"; + case "Asia/Yakutsk": return "Yakutsk Standard Time"; + case "Asia/Yekaterinburg": return "Ekaterinburg Standard Time"; + case "Asia/Yerevan": return "Caucasus Standard Time"; + case "Atlantic/Azores": return "Azores Standard Time"; + case "Atlantic/Bermuda": return "Atlantic Standard Time"; + case "Atlantic/Canary": return "GMT Standard Time"; + case "Atlantic/Cape_Verde": return "Cape Verde Standard Time"; + case "Atlantic/Faeroe": return "GMT Standard Time"; + case "Atlantic/Madeira": return "GMT Standard Time"; + case "Atlantic/Reykjavik": return "Greenwich Standard Time"; + case "Atlantic/South_Georgia": return "UTC-02"; + case "Atlantic/St_Helena": return "Greenwich Standard Time"; + case "Atlantic/Stanley": return "SA Eastern Standard Time"; + case "Australia/Adelaide": return "Cen. Australia Standard Time"; + case "Australia/Brisbane": return "E. Australia Standard Time"; + case "Australia/Broken_Hill": return "Cen. Australia Standard Time"; + case "Australia/Currie": return "Tasmania Standard Time"; + case "Australia/Darwin": return "AUS Central Standard Time"; + case "Australia/Eucla": return "Aus Central W. Standard Time"; + case "Australia/Hobart": return "Tasmania Standard Time"; + case "Australia/Lindeman": return "E. Australia Standard Time"; + case "Australia/Lord_Howe": return "Lord Howe Standard Time"; + case "Australia/Melbourne": return "AUS Eastern Standard Time"; + case "Australia/Perth": return "W. Australia Standard Time"; + case "Australia/Sydney": return "AUS Eastern Standard Time"; + case "CST6CDT": return "Central Standard Time"; + case "EST5EDT": return "Eastern Standard Time"; + case "Etc/GMT": return "UTC"; + case "Etc/GMT+1": return "Cape Verde Standard Time"; + case "Etc/GMT+10": return "Hawaiian Standard Time"; + case "Etc/GMT+11": return "UTC-11"; + case "Etc/GMT+12": return "Dateline Standard Time"; + case "Etc/GMT+2": return "UTC-02"; + case "Etc/GMT+3": return "SA Eastern Standard Time"; + case "Etc/GMT+4": return "SA Western Standard Time"; + case "Etc/GMT+5": return "SA Pacific Standard Time"; + case "Etc/GMT+6": return "Central America Standard Time"; + case "Etc/GMT+7": return "US Mountain Standard Time"; + case "Etc/GMT+8": return "UTC-08"; + case "Etc/GMT+9": return "UTC-09"; + case "Etc/GMT-1": return "W. Central Africa Standard Time"; + case "Etc/GMT-10": return "West Pacific Standard Time"; + case "Etc/GMT-11": return "Central Pacific Standard Time"; + case "Etc/GMT-12": return "UTC+12"; + case "Etc/GMT-13": return "Tonga Standard Time"; + case "Etc/GMT-14": return "Line Islands Standard Time"; + case "Etc/GMT-2": return "South Africa Standard Time"; + case "Etc/GMT-3": return "E. Africa Standard Time"; + case "Etc/GMT-4": return "Arabian Standard Time"; + case "Etc/GMT-5": return "West Asia Standard Time"; + case "Etc/GMT-6": return "Central Asia Standard Time"; + case "Etc/GMT-7": return "SE Asia Standard Time"; + case "Etc/GMT-8": return "Singapore Standard Time"; + case "Etc/GMT-9": return "Tokyo Standard Time"; + case "Europe/Amsterdam": return "W. Europe Standard Time"; + case "Europe/Andorra": return "W. Europe Standard Time"; + case "Europe/Astrakhan": return "Astrakhan Standard Time"; + case "Europe/Athens": return "GTB Standard Time"; + case "Europe/Belgrade": return "Central Europe Standard Time"; + case "Europe/Berlin": return "W. Europe Standard Time"; + case "Europe/Bratislava": return "Central Europe Standard Time"; + case "Europe/Brussels": return "Romance Standard Time"; + case "Europe/Bucharest": return "GTB Standard Time"; + case "Europe/Budapest": return "Central Europe Standard Time"; + case "Europe/Busingen": return "W. Europe Standard Time"; + case "Europe/Chisinau": return "GTB Standard Time"; + case "Europe/Copenhagen": return "Romance Standard Time"; + case "Europe/Dublin": return "GMT Standard Time"; + case "Europe/Gibraltar": return "W. Europe Standard Time"; + case "Europe/Guernsey": return "GMT Standard Time"; + case "Europe/Helsinki": return "FLE Standard Time"; + case "Europe/Isle_of_Man": return "GMT Standard Time"; + case "Europe/Istanbul": return "Turkey Standard Time"; + case "Europe/Jersey": return "GMT Standard Time"; + case "Europe/Kaliningrad": return "Kaliningrad Standard Time"; + case "Europe/Kiev": return "FLE Standard Time"; + case "Europe/Lisbon": return "GMT Standard Time"; + case "Europe/Ljubljana": return "Central Europe Standard Time"; + case "Europe/London": return "GMT Standard Time"; + case "Europe/Luxembourg": return "W. Europe Standard Time"; + case "Europe/Madrid": return "Romance Standard Time"; + case "Europe/Malta": return "W. Europe Standard Time"; + case "Europe/Mariehamn": return "FLE Standard Time"; + case "Europe/Minsk": return "Belarus Standard Time"; + case "Europe/Monaco": return "W. Europe Standard Time"; + case "Europe/Moscow": return "Russian Standard Time"; + case "Europe/Oslo": return "W. Europe Standard Time"; + case "Europe/Paris": return "Romance Standard Time"; + case "Europe/Podgorica": return "Central Europe Standard Time"; + case "Europe/Prague": return "Central Europe Standard Time"; + case "Europe/Riga": return "FLE Standard Time"; + case "Europe/Rome": return "W. Europe Standard Time"; + case "Europe/Samara": return "Russia Time Zone 3"; + case "Europe/San_Marino": return "W. Europe Standard Time"; + case "Europe/Sarajevo": return "Central European Standard Time"; + case "Europe/Simferopol": return "Russian Standard Time"; + case "Europe/Skopje": return "Central European Standard Time"; + case "Europe/Sofia": return "FLE Standard Time"; + case "Europe/Stockholm": return "W. Europe Standard Time"; + case "Europe/Tallinn": return "FLE Standard Time"; + case "Europe/Tirane": return "Central Europe Standard Time"; + case "Europe/Uzhgorod": return "FLE Standard Time"; + case "Europe/Vaduz": return "W. Europe Standard Time"; + case "Europe/Vatican": return "W. Europe Standard Time"; + case "Europe/Vienna": return "W. Europe Standard Time"; + case "Europe/Vilnius": return "FLE Standard Time"; + case "Europe/Volgograd": return "Russian Standard Time"; + case "Europe/Warsaw": return "Central European Standard Time"; + case "Europe/Zagreb": return "Central European Standard Time"; + case "Europe/Zaporozhye": return "FLE Standard Time"; + case "Europe/Zurich": return "W. Europe Standard Time"; + case "Indian/Antananarivo": return "E. Africa Standard Time"; + case "Indian/Chagos": return "Central Asia Standard Time"; + case "Indian/Christmas": return "SE Asia Standard Time"; + case "Indian/Cocos": return "Myanmar Standard Time"; + case "Indian/Comoro": return "E. Africa Standard Time"; + case "Indian/Kerguelen": return "West Asia Standard Time"; + case "Indian/Mahe": return "Mauritius Standard Time"; + case "Indian/Maldives": return "West Asia Standard Time"; + case "Indian/Mauritius": return "Mauritius Standard Time"; + case "Indian/Mayotte": return "E. Africa Standard Time"; + case "Indian/Reunion": return "Mauritius Standard Time"; + case "MST7MDT": return "Mountain Standard Time"; + case "PST8PDT": return "Pacific Standard Time"; + case "Pacific/Apia": return "Samoa Standard Time"; + case "Pacific/Auckland": return "New Zealand Standard Time"; + case "Pacific/Bougainville": return "Bougainville Standard Time"; + case "Pacific/Chatham": return "Chatham Islands Standard Time"; + case "Pacific/Easter": return "Easter Island Standard Time"; + case "Pacific/Efate": return "Central Pacific Standard Time"; + case "Pacific/Enderbury": return "Tonga Standard Time"; + case "Pacific/Fakaofo": return "Tonga Standard Time"; + case "Pacific/Fiji": return "Fiji Standard Time"; + case "Pacific/Funafuti": return "UTC+12"; + case "Pacific/Galapagos": return "Central America Standard Time"; + case "Pacific/Guadalcanal": return "Central Pacific Standard Time"; + case "Pacific/Guam": return "West Pacific Standard Time"; + case "Pacific/Honolulu": return "Hawaiian Standard Time"; + case "Pacific/Johnston": return "Hawaiian Standard Time"; + case "Pacific/Kiritimati": return "Line Islands Standard Time"; + case "Pacific/Kosrae": return "Central Pacific Standard Time"; + case "Pacific/Kwajalein": return "UTC+12"; + case "Pacific/Majuro": return "UTC+12"; + case "Pacific/Marquesas": return "Marquesas Standard Time"; + case "Pacific/Midway": return "UTC-11"; + case "Pacific/Nauru": return "UTC+12"; + case "Pacific/Niue": return "UTC-11"; + case "Pacific/Noumea": return "Central Pacific Standard Time"; + case "Pacific/Norfolk": return "Norfolk Standard Time"; + case "Pacific/Pago_Pago": return "UTC-11"; + case "Pacific/Palau": return "Tokyo Standard Time"; + case "Pacific/Ponape": return "Central Pacific Standard Time"; + case "Pacific/Port_Moresby": return "West Pacific Standard Time"; + case "Pacific/Rarotonga": return "Hawaiian Standard Time"; + case "Pacific/Saipan": return "West Pacific Standard Time"; + case "Pacific/Tahiti": return "Hawaiian Standard Time"; + case "Pacific/Tarawa": return "UTC+12"; + case "Pacific/Tongatapu": return "Tonga Standard Time"; + case "Pacific/Truk": return "West Pacific Standard Time"; + case "Pacific/Wake": return "UTC+12"; + case "Pacific/Wallis": return "UTC+12"; + default: return null; + } +} + +version (Windows) version (UpdateWindowsTZTranslations) deprecated @system unittest +{ + import std.stdio : stderr; + + foreach (tzName; TimeZone.getInstalledTZNames()) + { + if (tzDatabaseNameToWindowsTZName(tzName) is null) + stderr.writeln("Missing TZName to Windows translation: ", tzName); + } +} + + +// Explicitly undocumented. It will be removed in June 2018. @@@DEPRECATED_2018-07@@@ +deprecated("Use parseTZConversions instead") +string windowsTZNameToTZDatabaseName(string tzName) @safe pure nothrow @nogc +{ + switch (tzName) + { + case "AUS Central Standard Time": return "Australia/Darwin"; + case "AUS Eastern Standard Time": return "Australia/Sydney"; + case "Aus Central W. Standard Time": return "Australia/Eucla"; + case "Afghanistan Standard Time": return "Asia/Kabul"; + case "Haiti Standard Time": return "America/Port-au-Prince"; + case "Alaskan Standard Time": return "America/Anchorage"; + case "Aleutian Standard Time": return "America/Adak"; + case "Altai Standard Time": return "Asia/Barnaul"; + case "Arab Standard Time": return "Asia/Riyadh"; + case "Arabian Standard Time": return "Asia/Dubai"; + case "Arabic Standard Time": return "Asia/Baghdad"; + case "Argentina Standard Time": return "America/Buenos_Aires"; + case "Astrakhan Standard Time": return "Europe/Astrakhan"; + case "Atlantic Standard Time": return "America/Halifax"; + case "Azerbaijan Standard Time": return "Asia/Baku"; + case "Azores Standard Time": return "Atlantic/Azores"; + case "Bahia Standard Time": return "America/Bahia"; + case "Bangladesh Standard Time": return "Asia/Dhaka"; + case "Belarus Standard Time": return "Europe/Minsk"; + case "Bougainville Standard Time": return "Pacific/Bougainville"; + case "Canada Central Standard Time": return "America/Regina"; + case "Cape Verde Standard Time": return "Atlantic/Cape_Verde"; + case "Caucasus Standard Time": return "Asia/Yerevan"; + case "Cen. Australia Standard Time": return "Australia/Adelaide"; + case "Central America Standard Time": return "America/Guatemala"; + case "Central Asia Standard Time": return "Asia/Almaty"; + case "Central Brazilian Standard Time": return "America/Cuiaba"; + case "Central Europe Standard Time": return "Europe/Budapest"; + case "Central European Standard Time": return "Europe/Warsaw"; + case "Central Pacific Standard Time": return "Pacific/Guadalcanal"; + case "Central Standard Time": return "America/Chicago"; + case "Central Standard Time (Mexico)": return "America/Mexico_City"; + case "Chatham Islands Standard Time": return "Pacific/Chatham"; + case "China Standard Time": return "Asia/Shanghai"; + case "Cuba Standard Time": return "America/Havana"; + case "Dateline Standard Time": return "Etc/GMT+12"; + case "E. Africa Standard Time": return "Africa/Nairobi"; + case "E. Australia Standard Time": return "Australia/Brisbane"; + // This doesn't appear to be in the current stuff from MS, but the autotester + // is failing without it (probably because its time zone data hasn't been + // updated recently enough). + case "E. Europe Standard Time": return "Europe/Minsk"; + case "E. South America Standard Time": return "America/Sao_Paulo"; + case "Easter Island Standard Time": return "Pacific/Easter"; + case "Eastern Standard Time": return "America/New_York"; + case "Eastern Standard Time (Mexico)": return "America/Cancun"; + case "Egypt Standard Time": return "Africa/Cairo"; + case "Ekaterinburg Standard Time": return "Asia/Yekaterinburg"; + case "FLE Standard Time": return "Europe/Kiev"; + case "Fiji Standard Time": return "Pacific/Fiji"; + case "GMT Standard Time": return "Europe/London"; + case "GTB Standard Time": return "Europe/Athens"; + case "Georgian Standard Time": return "Asia/Tbilisi"; + case "Greenland Standard Time": return "America/Godthab"; + case "Greenwich Standard Time": return "Atlantic/Reykjavik"; + case "Hawaiian Standard Time": return "Pacific/Honolulu"; + case "India Standard Time": return "Asia/Calcutta"; + case "Iran Standard Time": return "Asia/Tehran"; + case "Israel Standard Time": return "Asia/Jerusalem"; + case "Jordan Standard Time": return "Asia/Amman"; + case "Kaliningrad Standard Time": return "Europe/Kaliningrad"; + // Same as with E. Europe Standard Time. + case "Kamchatka Standard Time": return "Asia/Kamchatka"; + case "Korea Standard Time": return "Asia/Seoul"; + case "Libya Standard Time": return "Africa/Tripoli"; + case "Line Islands Standard Time": return "Pacific/Kiritimati"; + case "Lord Howe Standard Time": return "Australia/Lord_Howe"; + case "Magadan Standard Time": return "Asia/Magadan"; + case "Marquesas Standard Time": return "Pacific/Marquesas"; + case "Mauritius Standard Time": return "Indian/Mauritius"; + // Same as with E. Europe Standard Time. + case "Mexico Standard Time": return "America/Mexico_City"; + // Same as with E. Europe Standard Time. + case "Mexico Standard Time 2": return "America/Chihuahua"; + // Same as with E. Europe Standard Time. + case "Mid-Atlantic Standard Time": return "Etc/GMT+2"; + case "Middle East Standard Time": return "Asia/Beirut"; + case "Montevideo Standard Time": return "America/Montevideo"; + case "Morocco Standard Time": return "Africa/Casablanca"; + case "Mountain Standard Time": return "America/Denver"; + case "Mountain Standard Time (Mexico)": return "America/Chihuahua"; + case "Myanmar Standard Time": return "Asia/Rangoon"; + case "N. Central Asia Standard Time": return "Asia/Novosibirsk"; + case "Namibia Standard Time": return "Africa/Windhoek"; + case "Nepal Standard Time": return "Asia/Katmandu"; + case "New Zealand Standard Time": return "Pacific/Auckland"; + case "Newfoundland Standard Time": return "America/St_Johns"; + case "Norfolk Standard Time": return "Pacific/Norfolk"; + case "North Asia East Standard Time": return "Asia/Irkutsk"; + case "North Asia Standard Time": return "Asia/Krasnoyarsk"; + case "North Korea Standard Time": return "Asia/Pyongyang"; + case "Pacific SA Standard Time": return "America/Santiago"; + case "Pacific Standard Time": return "America/Los_Angeles"; + case "Pacific Standard Time (Mexico)": return "America/Santa_Isabel"; + case "Pakistan Standard Time": return "Asia/Karachi"; + case "Paraguay Standard Time": return "America/Asuncion"; + case "Romance Standard Time": return "Europe/Paris"; + case "Russia Time Zone 10": return "Asia/Srednekolymsk"; + case "Russia Time Zone 11": return "Asia/Anadyr"; + case "Russia Time Zone 3": return "Europe/Samara"; + case "Russian Standard Time": return "Europe/Moscow"; + case "SA Eastern Standard Time": return "America/Cayenne"; + case "SA Pacific Standard Time": return "America/Bogota"; + case "SA Western Standard Time": return "America/La_Paz"; + case "SE Asia Standard Time": return "Asia/Bangkok"; + case "Sakhalin Standard Time": return "Asia/Sakhalin"; + case "Saint Pierre Standard Time": return "America/Miquelon"; + case "Samoa Standard Time": return "Pacific/Apia"; + case "Singapore Standard Time": return "Asia/Singapore"; + case "South Africa Standard Time": return "Africa/Johannesburg"; + case "Sri Lanka Standard Time": return "Asia/Colombo"; + case "Syria Standard Time": return "Asia/Damascus"; + case "Taipei Standard Time": return "Asia/Taipei"; + case "Tasmania Standard Time": return "Australia/Hobart"; + case "Tocantins Standard Time": return "America/Arguaina"; + case "Tokyo Standard Time": return "Asia/Tokyo"; + case "Tomsk Standard Time": return "Asia/Tomsk"; + case "Tonga Standard Time": return "Pacific/Tongatapu"; + case "Transbaikal Standard Time": return "Asia/Chita"; + case "Turkey Standard Time": return "Europe/Istanbul"; + case "Turks And Caicos Standard Time": return "America/Grand_Turk"; + case "US Eastern Standard Time": return "America/Indianapolis"; + case "US Mountain Standard Time": return "America/Phoenix"; + case "UTC": return "Etc/GMT"; + case "UTC+12": return "Etc/GMT-12"; + case "UTC-02": return "Etc/GMT+2"; + case "UTC-08": return "Etc/GMT+8"; + case "UTC-09": return "Etc/GMT+9"; + case "UTC-11": return "Etc/GMT+11"; + case "Ulaanbaatar Standard Time": return "Asia/Ulaanbaatar"; + case "Venezuela Standard Time": return "America/Caracas"; + case "Vladivostok Standard Time": return "Asia/Vladivostok"; + case "W. Australia Standard Time": return "Australia/Perth"; + case "W. Central Africa Standard Time": return "Africa/Lagos"; + case "W. Europe Standard Time": return "Europe/Berlin"; + case "W. Mongolia Standard Time": return "Asia/Hovd"; + case "West Asia Standard Time": return "Asia/Tashkent"; + case "West Bank Standard Time": return "Asia/Hebron"; + case "West Pacific Standard Time": return "Pacific/Port_Moresby"; + case "Yakutsk Standard Time": return "Asia/Yakutsk"; + default: return null; + } +} + +version (Windows) version (UpdateWindowsTZTranslations) deprecated @system unittest +{ + import std.stdio : stderr; + + foreach (winName; WindowsTimeZone.getInstalledTZNames()) + { + if (windowsTZNameToTZDatabaseName(winName) is null) + stderr.writeln("Missing Windows to TZName translation: ", winName); + } +} + + +// This script is for regenerating tzDatabaseNameToWindowsTZName and +// windowsTZNameToTZDatabaseName from +// http://unicode.org/cldr/data/common/supplemental/windowsZones.xml + +/+ +#!/bin/rdmd + +import std.algorithm; +import std.array; +import std.conv; +import std.datetime; +import std.exception; +import std.path; +import std.stdio; +import std.string; + +int main(string[] args) +{ + if (args.length != 4 || args[1].baseName != "windowsZones.xml") + { + stderr.writeln("genTZs.d windowsZones.xml "); + return -1; + } + + string[][string] win2Nix; + string[][string] nix2Win; + immutable f1 = ` %s", nix, wins)); + + // We'll try to eliminate multiples by favoring a conversion if it's already + // in Phobos, but if it's new, then the correct one will have to be chosen + // manually from the results. + string[] haveMultiple; + foreach (win, nixes; win2Nix) + { + if (nixes.length > 1) + haveMultiple ~= win; + } + bool[string] haveConflicts; + foreach (win; haveMultiple) + { + if (auto curr = windowsTZNameToTZDatabaseName(win)) + { + if (auto other = curr in nix2Win) + { + if ((*other)[0] == win) + { + win2Nix[win] = [curr]; + continue; + } + } + } + haveConflicts[win] = true; + writefln("Warning: %s -> %s", win, win2Nix[win]); + } + + + string[] nix2WinLines = [ + `string tzDatabaseNameToWindowsTZName(string tzName) @safe pure nothrow @nogc`, + `{`, + ` switch (tzName)`, + ` {`]; + + foreach (nix; nix2Win.keys.sort()) + nix2WinLines ~= format(` case "%s": return "%s";`, nix, nix2Win[nix][0]); + + nix2WinLines ~= [ + ` default: return null;`, + ` }`, + `}`]; + + + string[] win2NixLines = [ + `string windowsTZNameToTZDatabaseName(string tzName) @safe pure nothrow @nogc`, + `{`, + ` switch (tzName)`, + ` {`]; + foreach (win; win2Nix.keys.sort()) + { + immutable hasMultiple = cast(bool)(win in haveConflicts); + foreach (nix; win2Nix[win]) + win2NixLines ~= format(` case "%s": return "%s";%s`, win, nix, hasMultiple ? " FIXME" : ""); + } + + win2NixLines ~= [ + ` default: return null;`, + ` }`, + `}`]; + + + auto nix2WinFile = args[2]; + std.file.write(nix2WinFile, nix2WinLines.join("\n")); + + auto win2NixFile = args[3]; + std.file.write(win2NixFile, win2NixLines.join("\n")); + + return 0; +} ++/ diff --git a/libphobos/src/std/demangle.d b/libphobos/src/std/demangle.d new file mode 100644 index 0000000..e49bb9f --- /dev/null +++ b/libphobos/src/std/demangle.d @@ -0,0 +1,89 @@ +// Written in the D programming language. + +/** + * Demangle D mangled names. + * + * Copyright: Copyright Digital Mars 2000 - 2009. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: $(HTTP digitalmars.com, Walter Bright), + * Thomas K$(UUML)hne, Frits van Bommel + * Source: $(PHOBOSSRC std/_demangle.d) + * $(SCRIPT inhibitQuickIndex = 1;) + */ +/* + * Copyright Digital Mars 2000 - 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) + */ +module std.demangle; + +/+ +private class MangleException : Exception +{ + this() + { + super("MangleException"); + } +} ++/ + +/***************************** + * Demangle D mangled names. + * + * If it is not a D mangled name, it returns its argument name. + * Example: + * This program reads standard in and writes it to standard out, + * pretty-printing any found D mangled names. +------------------- +import core.stdc.stdio : stdin; +import std.stdio; +import std.ascii; +import std.demangle; + +void test(int x, float y) { } + +int main() +{ + string buffer; + bool inword; + int c; + + writefln("Try typing in: %s", test.mangleof); + while ((c = fgetc(stdin)) != EOF) + { + if (inword) + { + if (c == '_' || isAlphaNum(c)) + buffer ~= cast(char) c; + else + { + inword = false; + write(demangle(buffer), cast(char) c); + } + } + else + { if (c == '_' || isAlpha(c)) + { + inword = true; + buffer.length = 0; + buffer ~= cast(char) c; + } + else + write(cast(char) c); + } + } + if (inword) + write(demangle(buffer)); + return 0; +} +------------------- + */ + +string demangle(string name) +{ + import core.demangle : demangle; + import std.exception : assumeUnique; + auto ret = demangle(name); + return assumeUnique(ret); +} diff --git a/libphobos/src/std/digest/crc.d b/libphobos/src/std/digest/crc.d new file mode 100644 index 0000000..c606f4c --- /dev/null +++ b/libphobos/src/std/digest/crc.d @@ -0,0 +1,705 @@ +/** +Cyclic Redundancy Check (32-bit) implementation. + +$(SCRIPT inhibitQuickIndex = 1;) + +$(DIVC quickindex, +$(BOOKTABLE , +$(TR $(TH Category) $(TH Functions) +) +$(TR $(TDNW Template API) $(TD $(MYREF CRC) $(MYREF CRC32) $(MYREF CRC64ECMA) $(MYREF CRC64ISO) +) +) +$(TR $(TDNW OOP API) $(TD $(MYREF CRC32Digest) $(MYREF CRC64ECMADigest) $(MYREF CRC64ISODigest)) +) +$(TR $(TDNW Helpers) $(TD $(MYREF crcHexString) $(MYREF crc32Of) $(MYREF crc64ECMAOf) $(MYREF crc64ISOOf)) +) +) +) + + * + * This module conforms to the APIs defined in $(D std.digest). To understand the + * differences between the template and the OOP API, see $(MREF std, digest). + * + * This module publicly imports $(MREF std, digest) and can be used as a stand-alone + * module. + * + * Note: + * CRCs are usually printed with the MSB first. When using + * $(REF toHexString, std,digest) the result will be in an unexpected + * order. Use $(REF toHexString, std,digest)'s optional order parameter + * to specify decreasing order for the correct result. The $(LREF crcHexString) + * alias can also be used for this purpose. + * + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * + * Authors: Pavel "EvilOne" Minayev, Alex Rønne Petersen, Johannes Pfau + * + * References: + * $(LINK2 http://en.wikipedia.org/wiki/Cyclic_redundancy_check, Wikipedia on CRC) + * + * Source: $(PHOBOSSRC std/digest/_crc.d) + * + * Standards: + * Implements the 'common' IEEE CRC32 variant + * (LSB-first order, Initial value uint.max, complement result) + * + * CTFE: + * Digests do not work in CTFE + */ +/* + * Copyright (c) 2001 - 2002 + * Pavel "EvilOne" Minayev + * Copyright (c) 2012 + * Alex Rønne Petersen + * 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) + */ +module std.digest.crc; + +public import std.digest; + +version (unittest) + import std.exception; + + +/// +@safe unittest +{ + //Template API + import std.digest.crc; + + ubyte[4] hash = crc32Of("The quick brown fox jumps over the lazy dog"); + assert(crcHexString(hash) == "414FA339"); + + //Feeding data + ubyte[1024] data; + CRC32 crc; + crc.put(data[]); + crc.start(); //Start again + crc.put(data[]); + hash = crc.finish(); +} + +/// +@safe unittest +{ + //OOP API + import std.digest.crc; + + auto crc = new CRC32Digest(); + ubyte[] hash = crc.digest("The quick brown fox jumps over the lazy dog"); + assert(crcHexString(hash) == "414FA339"); //352441c2 + + //Feeding data + ubyte[1024] data; + crc.put(data[]); + crc.reset(); //Start again + crc.put(data[]); + hash = crc.finish(); +} + +private T[256][8] genTables(T)(T polynomial) +{ + T[256][8] res = void; + + foreach (i; 0 .. 0x100) + { + T crc = i; + foreach (_; 0 .. 8) + crc = (crc >> 1) ^ (-int(crc & 1) & polynomial); + res[0][i] = crc; + } + + foreach (i; 0 .. 0x100) + { + res[1][i] = (res[0][i] >> 8) ^ res[0][res[0][i] & 0xFF]; + res[2][i] = (res[1][i] >> 8) ^ res[0][res[1][i] & 0xFF]; + res[3][i] = (res[2][i] >> 8) ^ res[0][res[2][i] & 0xFF]; + res[4][i] = (res[3][i] >> 8) ^ res[0][res[3][i] & 0xFF]; + res[5][i] = (res[4][i] >> 8) ^ res[0][res[4][i] & 0xFF]; + res[6][i] = (res[5][i] >> 8) ^ res[0][res[5][i] & 0xFF]; + res[7][i] = (res[6][i] >> 8) ^ res[0][res[6][i] & 0xFF]; + } + return res; +} + +@system unittest +{ + auto tables = genTables(0xEDB88320); + assert(tables[0][0] == 0x00000000 && tables[0][$ - 1] == 0x2d02ef8d && tables[7][$ - 1] == 0x264b06e6); +} + +/** + * Template API CRC32 implementation. + * See $(D std.digest) for differences between template and OOP API. + */ +alias CRC32 = CRC!(32, 0xEDB88320); + +/** + * Template API CRC64-ECMA implementation. + * See $(D std.digest.digest) for differences between template and OOP API. + */ +alias CRC64ECMA = CRC!(64, 0xC96C5795D7870F42); + +/** + * Template API CRC64-ISO implementation. + * See $(D std.digest.digest) for differences between template and OOP API. + */ +alias CRC64ISO = CRC!(64, 0xD800000000000000); + +/** + * Generic Template API used for CRC32 and CRC64 implementations. + * + * The N parameter indicate the size of the hash in bits. + * The parameter P specify the polynomial to be used for reduction. + * + * You may want to use the CRC32, CRC65ECMA and CRC64ISO aliases + * for convenience. + * + * See $(D std.digest.digest) for differences between template and OOP API. + */ +struct CRC(uint N, ulong P) if (N == 32 || N == 64) +{ + private: + static if (N == 32) + { + alias T = uint; + } + else + { + alias T = ulong; + } + + static immutable T[256][8] tables = genTables!T(P); + + /** + * Type of the finished CRC hash. + * ubyte[4] if N is 32, ubyte[8] if N is 64. + */ + alias R = ubyte[T.sizeof]; + + // magic initialization constants + T _state = T.max; + + public: + /** + * Use this to feed the digest with data. + * Also implements the $(REF isOutputRange, std,range,primitives) + * interface for $(D ubyte) and $(D const(ubyte)[]). + */ + void put(scope const(ubyte)[] data...) @trusted pure nothrow @nogc + { + T crc = _state; + // process eight bytes at once + while (data.length >= 8) + { + // Use byte-wise reads to support architectures without HW support + // for unaligned reads. This can be optimized by compilers to a single + // 32-bit read if unaligned reads are supported. + // DMD is not able to do this optimization though, so explicitly + // do unaligned reads for DMD's architectures. + version (X86) + enum hasLittleEndianUnalignedReads = true; + else version (X86_64) + enum hasLittleEndianUnalignedReads = true; + else + enum hasLittleEndianUnalignedReads = false; // leave decision to optimizer + static if (hasLittleEndianUnalignedReads) + { + uint one = (cast(uint*) data.ptr)[0]; + uint two = (cast(uint*) data.ptr)[1]; + } + else + { + uint one = (data.ptr[3] << 24 | data.ptr[2] << 16 | data.ptr[1] << 8 | data.ptr[0]); + uint two = (data.ptr[7] << 24 | data.ptr[6] << 16 | data.ptr[5] << 8 | data.ptr[4]); + } + + static if (N == 32) + { + one ^= crc; + } + else + { + one ^= (crc & 0xffffffff); + two ^= (crc >> 32); + } + + crc = + tables[0][two >> 24] ^ + tables[1][(two >> 16) & 0xFF] ^ + tables[2][(two >> 8) & 0xFF] ^ + tables[3][two & 0xFF] ^ + tables[4][one >> 24] ^ + tables[5][(one >> 16) & 0xFF] ^ + tables[6][(one >> 8) & 0xFF] ^ + tables[7][one & 0xFF]; + + data = data[8 .. $]; + } + // remaining 1 to 7 bytes + foreach (d; data) + crc = (crc >> 8) ^ tables[0][(crc & 0xFF) ^ d]; + _state = crc; + } + + /** + * Used to initialize the CRC32 digest. + * + * Note: + * For this CRC32 Digest implementation calling start after default construction + * is not necessary. Calling start is only necessary to reset the Digest. + * + * Generic code which deals with different Digest types should always call start though. + */ + void start() @safe pure nothrow @nogc + { + this = CRC.init; + } + + /** + * Returns the finished CRC hash. This also calls $(LREF start) to + * reset the internal state. + */ + R finish() @safe pure nothrow @nogc + { + auto tmp = peek(); + start(); + return tmp; + } + + /** + * Works like $(D finish) but does not reset the internal state, so it's possible + * to continue putting data into this CRC after a call to peek. + */ + R peek() const @safe pure nothrow @nogc + { + import std.bitmanip : nativeToLittleEndian; + //Complement, LSB first / Little Endian, see http://rosettacode.org/wiki/CRC-32 + return nativeToLittleEndian(~_state); + } +} + +/// +@safe unittest +{ + //Simple example, hashing a string using crc32Of helper function + ubyte[4] hash32 = crc32Of("abc"); + //Let's get a hash string + assert(crcHexString(hash32) == "352441C2"); + // Repeat for CRC64 + ubyte[8] hash64ecma = crc64ECMAOf("abc"); + assert(crcHexString(hash64ecma) == "2CD8094A1A277627"); + ubyte[8] hash64iso = crc64ISOOf("abc"); + assert(crcHexString(hash64iso) == "3776C42000000000"); +} + +/// +@safe unittest +{ + ubyte[1024] data; + //Using the basic API + CRC32 hash32; + CRC64ECMA hash64ecma; + CRC64ISO hash64iso; + //Initialize data here... + hash32.put(data); + ubyte[4] result32 = hash32.finish(); + hash64ecma.put(data); + ubyte[8] result64ecma = hash64ecma.finish(); + hash64iso.put(data); + ubyte[8] result64iso = hash64iso.finish(); +} + +/// +@safe unittest +{ + //Let's use the template features: + //Note: When passing a CRC32 to a function, it must be passed by reference! + void doSomething(T)(ref T hash) + if (isDigest!T) + { + hash.put(cast(ubyte) 0); + } + CRC32 crc32; + crc32.start(); + doSomething(crc32); + assert(crcHexString(crc32.finish()) == "D202EF8D"); + // repeat for CRC64 + CRC64ECMA crc64ecma; + crc64ecma.start(); + doSomething(crc64ecma); + assert(crcHexString(crc64ecma.finish()) == "1FADA17364673F59"); + CRC64ISO crc64iso; + crc64iso.start(); + doSomething(crc64iso); + assert(crcHexString(crc64iso.finish()) == "6F90000000000000"); +} + +@safe unittest +{ + assert(isDigest!CRC32); + assert(isDigest!CRC64ECMA); + assert(isDigest!CRC64ISO); +} + +@system unittest +{ + ubyte[4] digest; + + CRC32 crc; + crc.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz"); + assert(crc.peek() == cast(ubyte[]) x"bd50274c"); + crc.start(); + crc.put(cast(ubyte[])""); + assert(crc.finish() == cast(ubyte[]) x"00000000"); + + digest = crc32Of(""); + assert(digest == cast(ubyte[]) x"00000000"); + + //Test vector from http://rosettacode.org/wiki/CRC-32 + assert(crcHexString(crc32Of("The quick brown fox jumps over the lazy dog")) == "414FA339"); + + digest = crc32Of("a"); + assert(digest == cast(ubyte[]) x"43beb7e8"); + + digest = crc32Of("abc"); + assert(digest == cast(ubyte[]) x"c2412435"); + + digest = crc32Of("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + assert(digest == cast(ubyte[]) x"5f3f1a17"); + + digest = crc32Of("message digest"); + assert(digest == cast(ubyte[]) x"7f9d1520"); + + digest = crc32Of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + assert(digest == cast(ubyte[]) x"d2e6c21f"); + + digest = crc32Of("1234567890123456789012345678901234567890"~ + "1234567890123456789012345678901234567890"); + assert(digest == cast(ubyte[]) x"724aa97c"); + + assert(crcHexString(cast(ubyte[4]) x"c3fcd3d7") == "D7D3FCC3"); +} + +@system unittest +{ + ubyte[8] digest; + + CRC64ECMA crc; + crc.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz"); + assert(crc.peek() == cast(ubyte[]) x"2f121b7575789626"); + crc.start(); + crc.put(cast(ubyte[])""); + assert(crc.finish() == cast(ubyte[]) x"0000000000000000"); + digest = crc64ECMAOf(""); + assert(digest == cast(ubyte[]) x"0000000000000000"); + + //Test vector from http://rosettacode.org/wiki/CRC-32 + assert(crcHexString(crc64ECMAOf("The quick brown fox jumps over the lazy dog")) == "5B5EB8C2E54AA1C4"); + + digest = crc64ECMAOf("a"); + assert(digest == cast(ubyte[]) x"052b652e77840233"); + + digest = crc64ECMAOf("abc"); + assert(digest == cast(ubyte[]) x"2776271a4a09d82c"); + + digest = crc64ECMAOf("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + assert(digest == cast(ubyte[]) x"4b7cdce3746c449f"); + + digest = crc64ECMAOf("message digest"); + assert(digest == cast(ubyte[]) x"6f9b8a3156c9bc5d"); + + digest = crc64ECMAOf("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + assert(digest == cast(ubyte[]) x"2656b716e1bf0503"); + + digest = crc64ECMAOf("1234567890123456789012345678901234567890"~ + "1234567890123456789012345678901234567890"); + assert(digest == cast(ubyte[]) x"bd3eb7765d0a22ae"); + + assert(crcHexString(cast(ubyte[8]) x"c3fcd3d7efbeadde") == "DEADBEEFD7D3FCC3"); +} + +@system unittest +{ + ubyte[8] digest; + + CRC64ISO crc; + crc.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz"); + assert(crc.peek() == cast(ubyte[]) x"f0494ab780989b42"); + crc.start(); + crc.put(cast(ubyte[])""); + assert(crc.finish() == cast(ubyte[]) x"0000000000000000"); + digest = crc64ISOOf(""); + assert(digest == cast(ubyte[]) x"0000000000000000"); + + //Test vector from http://rosettacode.org/wiki/CRC-32 + assert(crcHexString(crc64ISOOf("The quick brown fox jumps over the lazy dog")) == "4EF14E19F4C6E28E"); + + digest = crc64ISOOf("a"); + assert(digest == cast(ubyte[]) x"0000000000002034"); + + digest = crc64ISOOf("abc"); + assert(digest == cast(ubyte[]) x"0000000020c47637"); + + digest = crc64ISOOf("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + assert(digest == cast(ubyte[]) x"5173f717971365e5"); + + digest = crc64ISOOf("message digest"); + assert(digest == cast(ubyte[]) x"a2c355bbc0b93f86"); + + digest = crc64ISOOf("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + assert(digest == cast(ubyte[]) x"598B258292E40084"); + + digest = crc64ISOOf("1234567890123456789012345678901234567890"~ + "1234567890123456789012345678901234567890"); + assert(digest == cast(ubyte[]) x"760cd2d3588bf809"); + + assert(crcHexString(cast(ubyte[8]) x"c3fcd3d7efbeadde") == "DEADBEEFD7D3FCC3"); +} + +/** + * This is a convenience alias for $(REF digest, std,digest) using the + * CRC32 implementation. + * + * Params: + * data = $(D InputRange) of $(D ElementType) implicitly convertible to + * $(D ubyte), $(D ubyte[]) or $(D ubyte[num]) or one or more arrays + * of any type. + * + * Returns: + * CRC32 of data + */ +//simple alias doesn't work here, hope this gets inlined... +ubyte[4] crc32Of(T...)(T data) +{ + return digest!(CRC32, T)(data); +} + +/// +@system unittest +{ + ubyte[] data = [4,5,7,25]; + assert(data.crc32Of == [167, 180, 199, 131]); + + import std.utf : byChar; + assert("hello"d.byChar.crc32Of == [134, 166, 16, 54]); + + ubyte[4] hash = "abc".crc32Of(); + assert(hash == digest!CRC32("ab", "c")); + + import std.range : iota; + enum ubyte S = 5, F = 66; + assert(iota(S, F).crc32Of == [59, 140, 234, 154]); +} + +/** + * This is a convenience alias for $(REF digest, std,digest) using the + * CRC64-ECMA implementation. + * + * Params: + * data = $(D InputRange) of $(D ElementType) implicitly convertible to + * $(D ubyte), $(D ubyte[]) or $(D ubyte[num]) or one or more arrays + * of any type. + * + * Returns: + * CRC64-ECMA of data + */ +//simple alias doesn't work here, hope this gets inlined... +ubyte[8] crc64ECMAOf(T...)(T data) +{ + return digest!(CRC64ECMA, T)(data); +} + +/// +@system unittest +{ + ubyte[] data = [4,5,7,25]; + assert(data.crc64ECMAOf == [58, 142, 220, 214, 118, 98, 105, 69]); + + import std.utf : byChar; + assert("hello"d.byChar.crc64ECMAOf == [177, 55, 185, 219, 229, 218, 30, 155]); + + ubyte[8] hash = "abc".crc64ECMAOf(); + assert("abc".crc64ECMAOf == [39, 118, 39, 26, 74, 9, 216, 44]); + assert(hash == digest!CRC64ECMA("ab", "c")); + + import std.range : iota; + enum ubyte S = 5, F = 66; + assert(iota(S, F).crc64ECMAOf == [6, 184, 91, 238, 46, 213, 127, 188]); +} + +/** + * This is a convenience alias for $(REF digest, std,digest,digest) using the + * CRC64-ISO implementation. + * + * Params: + * data = $(D InputRange) of $(D ElementType) implicitly convertible to + * $(D ubyte), $(D ubyte[]) or $(D ubyte[num]) or one or more arrays + * of any type. + * + * Returns: + * CRC64-ISO of data + */ +//simple alias doesn't work here, hope this gets inlined... +ubyte[8] crc64ISOOf(T...)(T data) +{ + return digest!(CRC64ISO, T)(data); +} + +/// +@system unittest +{ + ubyte[] data = [4,5,7,25]; + assert(data.crc64ISOOf == [0, 0, 0, 80, 137, 232, 203, 120]); + + import std.utf : byChar; + assert("hello"d.byChar.crc64ISOOf == [0, 0, 16, 216, 226, 238, 62, 60]); + + ubyte[8] hash = "abc".crc64ISOOf(); + assert("abc".crc64ISOOf == [0, 0, 0, 0, 32, 196, 118, 55]); + assert(hash == digest!CRC64ISO("ab", "c")); + + import std.range : iota; + enum ubyte S = 5, F = 66; + + assert(iota(S, F).crc64ISOOf == [21, 185, 116, 95, 219, 11, 54, 7]); +} + +/** + * producing the usual CRC32 string output. + */ +public alias crcHexString = toHexString!(Order.decreasing); +///ditto +public alias crcHexString = toHexString!(Order.decreasing, 16); + +/** + * OOP API CRC32 implementation. + * See $(D std.digest) for differences between template and OOP API. + * + * This is an alias for $(D $(REF WrapperDigest, std,digest)!CRC32), see + * there for more information. + */ +alias CRC32Digest = WrapperDigest!CRC32; + +/** + * OOP API CRC64-ECMA implementation. + * See $(D std.digest.digest) for differences between template and OOP API. + * + * This is an alias for $(D $(REF WrapperDigest, std,digest,digest)!CRC64ECMA), + * see there for more information. + */ +alias CRC64ECMADigest = WrapperDigest!CRC64ECMA; + +/** + * OOP API CRC64-ISO implementation. + * See $(D std.digest.digest) for differences between template and OOP API. + * + * This is an alias for $(D $(REF WrapperDigest, std,digest,digest)!CRC64ISO), + * see there for more information. + */ +alias CRC64ISODigest = WrapperDigest!CRC64ISO; + +/// +@safe unittest +{ + //Simple example, hashing a string using Digest.digest helper function + auto crc = new CRC32Digest(); + ubyte[] hash = crc.digest("abc"); + //Let's get a hash string + assert(crcHexString(hash) == "352441C2"); +} + +/// +@system unittest +{ + //Let's use the OOP features: + void test(Digest dig) + { + dig.put(cast(ubyte) 0); + } + auto crc = new CRC32Digest(); + test(crc); + + //Let's use a custom buffer: + ubyte[4] buf; + ubyte[] result = crc.finish(buf[]); + assert(crcHexString(result) == "D202EF8D"); +} + +/// +@safe unittest +{ + //Simple example + auto hash = new CRC32Digest(); + hash.put(cast(ubyte) 0); + ubyte[] result = hash.finish(); +} + +/// +@system unittest +{ + //using a supplied buffer + ubyte[4] buf; + auto hash = new CRC32Digest(); + hash.put(cast(ubyte) 0); + ubyte[] result = hash.finish(buf[]); + //The result is now in result (and in buf. If you pass a buffer which is bigger than + //necessary, result will have the correct length, but buf will still have it's original + //length) +} + +@system unittest +{ + import std.range; + + auto crc = new CRC32Digest(); + + crc.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz"); + assert(crc.peek() == cast(ubyte[]) x"bd50274c"); + crc.reset(); + crc.put(cast(ubyte[])""); + assert(crc.finish() == cast(ubyte[]) x"00000000"); + + crc.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz"); + ubyte[20] result; + auto result2 = crc.finish(result[]); + assert(result[0 .. 4] == result2 && result2 == cast(ubyte[]) x"bd50274c"); + + debug + assertThrown!Error(crc.finish(result[0 .. 3])); + + assert(crc.length == 4); + + assert(crc.digest("") == cast(ubyte[]) x"00000000"); + + assert(crc.digest("a") == cast(ubyte[]) x"43beb7e8"); + + assert(crc.digest("abc") == cast(ubyte[]) x"c2412435"); + + assert(crc.digest("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") + == cast(ubyte[]) x"5f3f1a17"); + + assert(crc.digest("message digest") == cast(ubyte[]) x"7f9d1520"); + + assert(crc.digest("abcdefghijklmnopqrstuvwxyz") + == cast(ubyte[]) x"bd50274c"); + + assert(crc.digest("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") + == cast(ubyte[]) x"d2e6c21f"); + + assert(crc.digest("1234567890123456789012345678901234567890", + "1234567890123456789012345678901234567890") + == cast(ubyte[]) x"724aa97c"); + + ubyte[] onemilliona = new ubyte[1000000]; + onemilliona[] = 'a'; + auto digest = crc32Of(onemilliona); + assert(digest == cast(ubyte[]) x"BCBF25DC"); + + auto oneMillionRange = repeat!ubyte(cast(ubyte)'a', 1000000); + digest = crc32Of(oneMillionRange); + assert(digest == cast(ubyte[]) x"BCBF25DC"); +} diff --git a/libphobos/src/std/digest/digest.d b/libphobos/src/std/digest/digest.d new file mode 100644 index 0000000..325afd4 --- /dev/null +++ b/libphobos/src/std/digest/digest.d @@ -0,0 +1,21 @@ +module std.digest.digest; + +import _newDigest = std.digest; + +// scheduled for deprecation in 2.077 +// See also: https://github.com/dlang/phobos/pull/5013#issuecomment-313987845 +alias isDigest = _newDigest.isDigest; +alias DigestType = _newDigest.DigestType; +alias hasPeek = _newDigest.hasPeek; +alias hasBlockSize = _newDigest.hasBlockSize; +alias digest = _newDigest.digest; +alias hexDigest = _newDigest.hexDigest; +alias makeDigest = _newDigest.makeDigest; +alias Digest = _newDigest.Digest; +alias Order = _newDigest.Order; +alias toHexString = _newDigest.toHexString; +alias asArray = _newDigest.asArray; +alias digestLength = _newDigest.digestLength; +alias WrapperDigest = _newDigest.WrapperDigest; +alias secureEqual = _newDigest.secureEqual; +alias LetterCase = _newDigest.LetterCase; diff --git a/libphobos/src/std/digest/hmac.d b/libphobos/src/std/digest/hmac.d new file mode 100644 index 0000000..bf0cbf3 --- /dev/null +++ b/libphobos/src/std/digest/hmac.d @@ -0,0 +1,336 @@ +// Written in the D programming language. + +/** +This package implements the hash-based message authentication code (_HMAC) +algorithm as defined in $(HTTP tools.ietf.org/html/rfc2104, RFC2104). See also +the corresponding $(HTTP en.wikipedia.org/wiki/Hash-based_message_authentication_code, Wikipedia article). + +$(SCRIPT inhibitQuickIndex = 1;) + +Macros: + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Source: $(PHOBOSSRC std/digest/_hmac.d) + */ + +module std.digest.hmac; + +import std.digest : isDigest, hasBlockSize, isDigestibleRange, DigestType; +import std.meta : allSatisfy; + +@safe: + +/** + * Template API HMAC implementation. + * + * This implements an _HMAC over the digest H. If H doesn't provide + * information about the block size, it can be supplied explicitly using + * the second overload. + * + * This type conforms to $(REF isDigest, std,digest). + */ + +/// Compute HMAC over an input string +@safe unittest +{ + import std.ascii : LetterCase; + import std.digest : toHexString; + import std.digest.sha : SHA1; + import std.string : representation; + + auto secret = "secret".representation; + assert("The quick brown fox jumps over the lazy dog" + .representation + .hmac!SHA1(secret) + .toHexString!(LetterCase.lower) == "198ea1ea04c435c1246b586a06d5cf11c3ffcda6"); +} + +template HMAC(H) +if (isDigest!H && hasBlockSize!H) +{ + alias HMAC = HMAC!(H, H.blockSize); +} + +/** + * Overload of HMAC to be used if H doesn't provide information about its + * block size. + */ + +struct HMAC(H, size_t hashBlockSize) +if (hashBlockSize % 8 == 0) +{ + enum blockSize = hashBlockSize; + + private H digest; + private ubyte[blockSize / 8] key; + + /** + * Constructs the HMAC digest using the specified secret. + */ + + this(scope const(ubyte)[] secret) + { + // if secret is too long, shorten it by computing its hash + typeof(digest.finish()) buffer = void; + if (secret.length > blockSize / 8) + { + digest.start(); + digest.put(secret); + buffer = digest.finish(); + secret = buffer[]; + } + + // if secret is too short, it will be padded with zeroes + // (the key buffer is already zero-initialized) + import std.algorithm.mutation : copy; + secret.copy(key[]); + + start(); + } + + /// + @safe pure nothrow @nogc unittest + { + import std.digest.hmac, std.digest.sha; + import std.string : representation; + auto hmac = HMAC!SHA1("My s3cR3T keY".representation); + hmac.put("Hello, world".representation); + static immutable expected = [ + 130, 32, 235, 44, 208, 141, + 150, 232, 211, 214, 162, 195, + 188, 127, 52, 89, 100, 68, 90, 216]; + assert(hmac.finish() == expected); + } + + /** + * Reinitializes the digest, making it ready for reuse. + * + * Note: + * The constructor leaves the digest in an initialized state, so that this + * method only needs to be called if an unfinished digest is to be reused. + * + * Returns: + * A reference to the digest for convenient chaining. + */ + + ref HMAC!(H, blockSize) start() return + { + ubyte[blockSize / 8] ipad = void; + foreach (immutable i; 0 .. blockSize / 8) + ipad[i] = key[i] ^ 0x36; + + digest.start(); + digest.put(ipad[]); + + return this; + } + + /// + @safe pure nothrow @nogc unittest + { + import std.digest.hmac, std.digest.sha; + import std.string : representation; + string data1 = "Hello, world", data2 = "Hola mundo"; + auto hmac = HMAC!SHA1("My s3cR3T keY".representation); + hmac.put(data1.representation); + hmac.start(); // reset digest + hmac.put(data2.representation); // start over + static immutable expected = [ + 122, 151, 232, 240, 249, 80, + 19, 178, 186, 77, 110, 23, 208, + 52, 11, 88, 34, 151, 192, 255]; + assert(hmac.finish() == expected); + } + + /** + * Feeds a piece of data into the hash computation. This method allows the + * type to be used as an $(REF OutputRange, std,range). + * + * Returns: + * A reference to the digest for convenient chaining. + */ + + ref HMAC!(H, blockSize) put(in ubyte[] data...) return + { + digest.put(data); + return this; + } + + /// + @safe pure nothrow @nogc unittest + { + import std.digest.hmac, std.digest.sha; + import std.string : representation; + string data1 = "Hello, world", data2 = "Hola mundo"; + auto hmac = HMAC!SHA1("My s3cR3T keY".representation); + hmac.put(data1.representation) + .put(data2.representation); + static immutable expected = [ + 197, 57, 52, 3, 13, 194, 13, + 36, 117, 228, 8, 11, 111, 51, + 165, 3, 123, 31, 251, 113]; + assert(hmac.finish() == expected); + } + + /** + * Resets the digest and returns the finished hash. + */ + + DigestType!H finish() + { + ubyte[blockSize / 8] opad = void; + foreach (immutable i; 0 .. blockSize / 8) + opad[i] = key[i] ^ 0x5c; + + auto tmp = digest.finish(); + + digest.start(); + digest.put(opad[]); + digest.put(tmp); + auto result = digest.finish(); + start(); // reset the digest + return result; + } + + /// + @safe pure nothrow @nogc unittest + { + import std.digest.hmac, std.digest.sha; + import std.string : representation; + string data1 = "Hello, world", data2 = "Hola mundo"; + auto hmac = HMAC!SHA1("My s3cR3T keY".representation); + auto digest = hmac.put(data1.representation) + .put(data2.representation) + .finish(); + static immutable expected = [ + 197, 57, 52, 3, 13, 194, 13, + 36, 117, 228, 8, 11, 111, 51, + 165, 3, 123, 31, 251, 113]; + assert(digest == expected); + } +} + +/// Convenience constructor for $(LREF HMAC). +template hmac(H) +if (isDigest!H && hasBlockSize!H) +{ + alias hmac = hmac!(H, H.blockSize); +} + +/// ditto +template hmac(H, size_t blockSize) +if (isDigest!H) +{ + /** + * Constructs an HMAC digest with the specified secret. + * + * Returns: + * An instance of HMAC that can be fed data as desired, and finished + * to compute the final hash when done. + */ + auto hmac(scope const(ubyte)[] secret) + { + return HMAC!(H, blockSize)(secret); + } + + /// + @safe pure nothrow @nogc unittest + { + import std.digest.hmac, std.digest.sha; + import std.string : representation; + string data1 = "Hello, world", data2 = "Hola mundo"; + auto digest = hmac!SHA1("My s3cR3T keY".representation) + .put(data1.representation) + .put(data2.representation) + .finish(); + static immutable expected = [ + 197, 57, 52, 3, 13, 194, 13, 36, + 117, 228, 8, 11, 111, 51, 165, + 3, 123, 31, 251, 113]; + assert(digest == expected); + } + + /** + * Computes an _HMAC digest over the given range of data with the + * specified secret. + * + * Returns: + * The final _HMAC hash. + */ + DigestType!H hmac(T...)(scope T data, scope const(ubyte)[] secret) + if (allSatisfy!(isDigestibleRange, typeof(data))) + { + import std.range.primitives : put; + auto hash = HMAC!(H, blockSize)(secret); + foreach (datum; data) + put(hash, datum); + return hash.finish(); + } + + /// + @safe pure nothrow @nogc unittest + { + import std.algorithm.iteration : map; + import std.digest.hmac, std.digest.sha; + import std.string : representation; + string data = "Hello, world"; + auto digest = data.representation + .map!(a => cast(ubyte)(a+1)) + .hmac!SHA1("My s3cR3T keY".representation); + static assert(is(typeof(digest) == ubyte[20])); + static immutable expected = [ + 163, 208, 118, 179, 216, 93, + 17, 10, 84, 200, 87, 104, 244, + 111, 136, 214, 167, 210, 58, 10]; + assert(digest == expected); + } +} + +version (unittest) +{ + import std.digest : toHexString, LetterCase; + alias hex = toHexString!(LetterCase.lower); +} + +@safe pure nothrow @nogc +unittest +{ + import std.digest.md : MD5; + import std.range : isOutputRange; + static assert(isOutputRange!(HMAC!MD5, ubyte)); + static assert(isDigest!(HMAC!MD5)); + static assert(hasBlockSize!(HMAC!MD5) && HMAC!MD5.blockSize == MD5.blockSize); +} + +@safe pure nothrow +unittest +{ + import std.digest.md : MD5; + import std.digest.sha : SHA1, SHA256; + + ubyte[] nada; + assert(hmac!MD5 (nada, nada).hex == "74e6f7298a9c2d168935f58c001bad88"); + assert(hmac!SHA1 (nada, nada).hex == "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d"); + assert(hmac!SHA256(nada, nada).hex == "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"); + + import std.string : representation; + auto key = "key".representation, + long_key = ("012345678901234567890123456789012345678901" + ~"234567890123456789012345678901234567890123456789").representation, + data1 = "The quick brown fox ".representation, + data2 = "jumps over the lazy dog".representation, + data = data1 ~ data2; + + assert(data.hmac!MD5 (key).hex == "80070713463e7749b90c2dc24911e275"); + assert(data.hmac!SHA1 (key).hex == "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9"); + assert(data.hmac!SHA256(key).hex == "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8"); + + assert(data.hmac!MD5 (long_key).hex == "e1728d68e05beae186ea768561963778"); + assert(data.hmac!SHA1 (long_key).hex == "560d3cd77316e57ab4bba0c186966200d2b37ba3"); + assert(data.hmac!SHA256(long_key).hex == "a1b0065a5d1edd93152c677e1bc1b1e3bc70d3a76619842e7f733f02b8135c04"); + + assert(hmac!MD5 (key).put(data1).put(data2).finish == data.hmac!MD5 (key)); + assert(hmac!SHA1 (key).put(data1).put(data2).finish == data.hmac!SHA1 (key)); + assert(hmac!SHA256(key).put(data1).put(data2).finish == data.hmac!SHA256(key)); +} diff --git a/libphobos/src/std/digest/md.d b/libphobos/src/std/digest/md.d new file mode 100644 index 0000000..1b621cf --- /dev/null +++ b/libphobos/src/std/digest/md.d @@ -0,0 +1,590 @@ +/** + * Computes MD5 hashes of arbitrary data. MD5 hashes are 16 byte quantities that are like a + * checksum or CRC, but are more robust. + * +$(SCRIPT inhibitQuickIndex = 1;) + +$(DIVC quickindex, +$(BOOKTABLE , +$(TR $(TH Category) $(TH Functions) +) +$(TR $(TDNW Template API) $(TD $(MYREF MD5) +) +) +$(TR $(TDNW OOP API) $(TD $(MYREF MD5Digest)) +) +$(TR $(TDNW Helpers) $(TD $(MYREF md5Of)) +) +) +) + + * This module conforms to the APIs defined in $(D std.digest). To understand the + * differences between the template and the OOP API, see $(MREF std, digest). + * + * This module publicly imports $(MREF std, digest) and can be used as a stand-alone + * module. + * + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * + * CTFE: + * Digests do not work in CTFE + * + * Authors: + * Piotr Szturmaj, Kai Nacke, Johannes Pfau $(BR) + * The routines and algorithms are derived from the $(I RSA Data Security, Inc. MD5 Message-Digest Algorithm). + * + * References: + * $(LINK2 http://en.wikipedia.org/wiki/Md5, Wikipedia on MD5) + * + * Source: $(PHOBOSSRC std/digest/_md.d) + * + */ + +/* md5.d - RSA Data Security, Inc., MD5 message-digest algorithm + * Derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm. + */ +module std.digest.md; + +public import std.digest; + +/// +@safe unittest +{ + //Template API + import std.digest.md; + + //Feeding data + ubyte[1024] data; + MD5 md5; + md5.start(); + md5.put(data[]); + md5.start(); //Start again + md5.put(data[]); + auto hash = md5.finish(); +} + +/// +@safe unittest +{ + //OOP API + import std.digest.md; + + auto md5 = new MD5Digest(); + ubyte[] hash = md5.digest("abc"); + assert(toHexString(hash) == "900150983CD24FB0D6963F7D28E17F72"); + + //Feeding data + ubyte[1024] data; + md5.put(data[]); + md5.reset(); //Start again + md5.put(data[]); + hash = md5.finish(); +} + +//rotateLeft rotates x left n bits +private uint rotateLeft(uint x, uint n) @safe pure nothrow @nogc +{ + // With recently added optimization to DMD (commit 32ea0206 at 07/28/11), this is translated to rol. + // No assembler required. + return (x << n) | (x >> (32-n)); +} + +/** + * Template API MD5 implementation. + * See $(D std.digest) for differences between template and OOP API. + */ +struct MD5 +{ + private: + // magic initialization constants + uint[4] _state = [0x67452301,0xefcdab89,0x98badcfe,0x10325476]; // state (ABCD) + ulong _count; //number of bits, modulo 2^64 + ubyte[64] _buffer; // input buffer + + static immutable ubyte[64] _padding = + [ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ]; + + // F, G, H and I are basic MD5 functions + static @safe pure nothrow @nogc + { + uint F(uint x, uint y, uint z) { return (x & y) | (~x & z); } + uint G(uint x, uint y, uint z) { return (x & z) | (y & ~z); } + uint H(uint x, uint y, uint z) { return x ^ y ^ z; } + uint I(uint x, uint y, uint z) { return y ^ (x | ~z); } + } + + + /* + * FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. + * Rotation is separate from addition to prevent recomputation. + */ + static void FF(ref uint a, uint b, uint c, uint d, uint x, uint s, uint ac) + @safe pure nothrow @nogc + { + a += F (b, c, d) + x + ac; + a = rotateLeft(a, s); + a += b; + } + + static void GG(ref uint a, uint b, uint c, uint d, uint x, uint s, uint ac) + @safe pure nothrow @nogc + { + a += G (b, c, d) + x + ac; + a = rotateLeft(a, s); + a += b; + } + + static void HH(ref uint a, uint b, uint c, uint d, uint x, uint s, uint ac) + @safe pure nothrow @nogc + { + a += H (b, c, d) + x + ac; + a = rotateLeft(a, s); + a += b; + } + + static void II(ref uint a, uint b, uint c, uint d, uint x, uint s, uint ac) + @safe pure nothrow @nogc + { + a += I (b, c, d) + x + ac; + a = rotateLeft(a, s); + a += b; + } + + /* + * MD5 basic transformation. Transforms state based on block. + */ + + //Constants for MD5Transform routine. + enum + { + S11 = 7, + S12 = 12, + S13 = 17, + S14 = 22, + S21 = 5, + S22 = 9, + S23 = 14, + S24 = 20, + S31 = 4, + S32 = 11, + S33 = 16, + S34 = 23, + S41 = 6, + S42 = 10, + S43 = 15, + S44 = 21, + } + + private void transform(const(ubyte[64])* block) pure nothrow @nogc + { + uint a = _state[0], + b = _state[1], + c = _state[2], + d = _state[3]; + + uint[16] x = void; + + version (BigEndian) + { + import std.bitmanip : littleEndianToNative; + + for (size_t i = 0; i < 16; i++) + { + x[i] = littleEndianToNative!uint(*cast(ubyte[4]*)&(*block)[i*4]); + } + } + else + { + (cast(ubyte*) x.ptr)[0 .. 64] = (cast(ubyte*) block)[0 .. 64]; + } + + //Round 1 + FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ + FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ + FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ + FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ + FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ + FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ + FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ + FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ + FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ + FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ + FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + //Round 2 + GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ + GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ + GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ + GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ + GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ + GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ + GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ + GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ + GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ + GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ + GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + //Round 3 + HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ + HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ + HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ + HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ + HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ + HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ + HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ + HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ + HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ + HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ + + //Round 4 + II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ + II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ + II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ + II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ + II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ + II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ + II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ + II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ + II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ + II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ + + _state[0] += a; + _state[1] += b; + _state[2] += c; + _state[3] += d; + + //Zeroize sensitive information. + x[] = 0; + } + + public: + enum blockSize = 512; + + /** + * Use this to feed the digest with data. + * Also implements the $(REF isOutputRange, std,range,primitives) + * interface for $(D ubyte) and $(D const(ubyte)[]). + * + * Example: + * ---- + * MD5 dig; + * dig.put(cast(ubyte) 0); //single ubyte + * dig.put(cast(ubyte) 0, cast(ubyte) 0); //variadic + * ubyte[10] buf; + * dig.put(buf); //buffer + * ---- + */ + void put(scope const(ubyte)[] data...) @trusted pure nothrow @nogc + { + uint i, index, partLen; + auto inputLen = data.length; + + //Compute number of bytes mod 64 + index = (cast(uint)_count >> 3) & (64 - 1); + + //Update number of bits + _count += inputLen * 8; + + partLen = 64 - index; + + //Transform as many times as possible + if (inputLen >= partLen) + { + (&_buffer[index])[0 .. partLen] = data.ptr[0 .. partLen]; + transform(&_buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + { + transform(cast(const(ubyte[64])*)(data[i .. i + 64].ptr)); + } + + index = 0; + } + else + { + i = 0; + } + + /* Buffer remaining input */ + if (inputLen - i) + (&_buffer[index])[0 .. inputLen-i] = (&data[i])[0 .. inputLen-i]; + } + + /** + * Used to (re)initialize the MD5 digest. + * + * Note: + * For this MD5 Digest implementation calling start after default construction + * is not necessary. Calling start is only necessary to reset the Digest. + * + * Generic code which deals with different Digest types should always call start though. + * + * Example: + * -------- + * MD5 digest; + * //digest.start(); //Not necessary + * digest.put(0); + * -------- + */ + void start() @safe pure nothrow @nogc + { + this = MD5.init; + } + + /** + * Returns the finished MD5 hash. This also calls $(LREF start) to + * reset the internal state. + */ + ubyte[16] finish() @trusted pure nothrow @nogc + { + import std.bitmanip : nativeToLittleEndian; + + ubyte[16] data = void; + ubyte[8] bits = void; + uint index, padLen; + + //Save number of bits + bits[0 .. 8] = nativeToLittleEndian(_count)[]; + + //Pad out to 56 mod 64 + index = (cast(uint)_count >> 3) & (64 - 1); + padLen = (index < 56) ? (56 - index) : (120 - index); + put(_padding[0 .. padLen]); + + //Append length (before padding) + put(bits); + + //Store state in digest + data[0 .. 4] = nativeToLittleEndian(_state[0])[]; + data[4 .. 8] = nativeToLittleEndian(_state[1])[]; + data[8 .. 12] = nativeToLittleEndian(_state[2])[]; + data[12 .. 16] = nativeToLittleEndian(_state[3])[]; + + /* Zeroize sensitive information. */ + start(); + return data; + } + /// + @safe unittest + { + //Simple example + MD5 hash; + hash.start(); + hash.put(cast(ubyte) 0); + ubyte[16] result = hash.finish(); + } +} + +/// +@safe unittest +{ + //Simple example, hashing a string using md5Of helper function + ubyte[16] hash = md5Of("abc"); + //Let's get a hash string + assert(toHexString(hash) == "900150983CD24FB0D6963F7D28E17F72"); +} + +/// +@safe unittest +{ + //Using the basic API + MD5 hash; + hash.start(); + ubyte[1024] data; + //Initialize data here... + hash.put(data); + ubyte[16] result = hash.finish(); +} + +/// +@safe unittest +{ + //Let's use the template features: + void doSomething(T)(ref T hash) + if (isDigest!T) + { + hash.put(cast(ubyte) 0); + } + MD5 md5; + md5.start(); + doSomething(md5); + assert(toHexString(md5.finish()) == "93B885ADFE0DA089CDF634904FD59F71"); +} + +@safe unittest +{ + assert(isDigest!MD5); +} + +@system unittest +{ + import std.range; + + ubyte[16] digest; + + MD5 md5; + md5.put(cast(ubyte[])"abcdef"); + md5.start(); + md5.put(cast(ubyte[])""); + assert(md5.finish() == cast(ubyte[]) x"d41d8cd98f00b204e9800998ecf8427e"); + + digest = md5Of(""); + assert(digest == cast(ubyte[]) x"d41d8cd98f00b204e9800998ecf8427e"); + + digest = md5Of("a"); + assert(digest == cast(ubyte[]) x"0cc175b9c0f1b6a831c399e269772661"); + + digest = md5Of("abc"); + assert(digest == cast(ubyte[]) x"900150983cd24fb0d6963f7d28e17f72"); + + digest = md5Of("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + assert(digest == cast(ubyte[]) x"8215ef0796a20bcaaae116d3876c664a"); + + digest = md5Of("message digest"); + assert(digest == cast(ubyte[]) x"f96b697d7cb7938d525a2f31aaf161d0"); + + digest = md5Of("abcdefghijklmnopqrstuvwxyz"); + assert(digest == cast(ubyte[]) x"c3fcd3d76192e4007dfb496cca67e13b"); + + digest = md5Of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + assert(digest == cast(ubyte[]) x"d174ab98d277d9f5a5611c2c9f419d9f"); + + digest = md5Of("1234567890123456789012345678901234567890"~ + "1234567890123456789012345678901234567890"); + assert(digest == cast(ubyte[]) x"57edf4a22be3c955ac49da2e2107b67a"); + + assert(toHexString(cast(ubyte[16]) x"c3fcd3d76192e4007dfb496cca67e13b") + == "C3FCD3D76192E4007DFB496CCA67E13B"); + + ubyte[] onemilliona = new ubyte[1000000]; + onemilliona[] = 'a'; + digest = md5Of(onemilliona); + assert(digest == cast(ubyte[]) x"7707D6AE4E027C70EEA2A935C2296F21"); + + auto oneMillionRange = repeat!ubyte(cast(ubyte)'a', 1000000); + digest = md5Of(oneMillionRange); + assert(digest == cast(ubyte[]) x"7707D6AE4E027C70EEA2A935C2296F21"); +} + +/** + * This is a convenience alias for $(REF digest, std,digest) using the + * MD5 implementation. + */ +//simple alias doesn't work here, hope this gets inlined... +auto md5Of(T...)(T data) +{ + return digest!(MD5, T)(data); +} + +/// +@safe unittest +{ + ubyte[16] hash = md5Of("abc"); + assert(hash == digest!MD5("abc")); +} + +/** + * OOP API MD5 implementation. + * See $(D std.digest) for differences between template and OOP API. + * + * This is an alias for $(D $(REF WrapperDigest, std,digest)!MD5), see + * there for more information. + */ +alias MD5Digest = WrapperDigest!MD5; + +/// +@safe unittest +{ + //Simple example, hashing a string using Digest.digest helper function + auto md5 = new MD5Digest(); + ubyte[] hash = md5.digest("abc"); + //Let's get a hash string + assert(toHexString(hash) == "900150983CD24FB0D6963F7D28E17F72"); +} + +/// +@system unittest +{ + //Let's use the OOP features: + void test(Digest dig) + { + dig.put(cast(ubyte) 0); + } + auto md5 = new MD5Digest(); + test(md5); + + //Let's use a custom buffer: + ubyte[16] buf; + ubyte[] result = md5.finish(buf[]); + assert(toHexString(result) == "93B885ADFE0DA089CDF634904FD59F71"); +} + +@system unittest +{ + auto md5 = new MD5Digest(); + + md5.put(cast(ubyte[])"abcdef"); + md5.reset(); + md5.put(cast(ubyte[])""); + assert(md5.finish() == cast(ubyte[]) x"d41d8cd98f00b204e9800998ecf8427e"); + + md5.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz"); + ubyte[20] result; + auto result2 = md5.finish(result[]); + assert(result[0 .. 16] == result2 && result2 == cast(ubyte[]) x"c3fcd3d76192e4007dfb496cca67e13b"); + + debug + { + import std.exception; + assertThrown!Error(md5.finish(result[0 .. 15])); + } + + assert(md5.length == 16); + + assert(md5.digest("") == cast(ubyte[]) x"d41d8cd98f00b204e9800998ecf8427e"); + + assert(md5.digest("a") == cast(ubyte[]) x"0cc175b9c0f1b6a831c399e269772661"); + + assert(md5.digest("abc") == cast(ubyte[]) x"900150983cd24fb0d6963f7d28e17f72"); + + assert(md5.digest("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") + == cast(ubyte[]) x"8215ef0796a20bcaaae116d3876c664a"); + + assert(md5.digest("message digest") == cast(ubyte[]) x"f96b697d7cb7938d525a2f31aaf161d0"); + + assert(md5.digest("abcdefghijklmnopqrstuvwxyz") + == cast(ubyte[]) x"c3fcd3d76192e4007dfb496cca67e13b"); + + assert(md5.digest("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") + == cast(ubyte[]) x"d174ab98d277d9f5a5611c2c9f419d9f"); + + assert(md5.digest("1234567890123456789012345678901234567890", + "1234567890123456789012345678901234567890") + == cast(ubyte[]) x"57edf4a22be3c955ac49da2e2107b67a"); +} diff --git a/libphobos/src/std/digest/murmurhash.d b/libphobos/src/std/digest/murmurhash.d new file mode 100644 index 0000000..74efed5 --- /dev/null +++ b/libphobos/src/std/digest/murmurhash.d @@ -0,0 +1,755 @@ +/** +Computes $(LINK2 https://en.wikipedia.org/wiki/MurmurHash, MurmurHash) hashes +of arbitrary data. MurmurHash is a non-cryptographic hash function suitable +for general hash-based lookup. It is optimized for x86 but can be used on +all architectures. + +The current version is MurmurHash3, which yields a 32-bit or 128-bit hash value. +The older MurmurHash 1 and 2 are currently not supported. + +MurmurHash3 comes in three flavors, listed in increasing order of throughput: +$(UL +$(LI $(D MurmurHash3!32) produces a 32-bit value and is optimized for 32-bit architectures) +$(LI $(D MurmurHash3!(128, 32)) produces a 128-bit value and is optimized for 32-bit architectures) +$(LI $(D MurmurHash3!(128, 64)) produces a 128-bit value and is optimized for 64-bit architectures) +) + +Note: +$(UL +$(LI $(D MurmurHash3!(128, 32)) and $(D MurmurHash3!(128, 64)) produce different values.) +$(LI The current implementation is optimized for little endian architectures. + It will exhibit different results on big endian architectures and a slightly + less uniform distribution.) +) + +This module conforms to the APIs defined in $(MREF std, digest). + +This module publicly imports $(MREF std, digest) and can be used as a stand-alone module. + +Source: $(PHOBOSSRC std/digest/_murmurhash.d) +License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). +Authors: Guillaume Chatelet +References: $(LINK2 https://github.com/aappleby/smhasher, Reference implementation) +$(BR) $(LINK2 https://en.wikipedia.org/wiki/MurmurHash, Wikipedia) +*/ +/* Copyright Guillaume Chatelet 2016. + * Distributed under the Boost Software License, Version 1.0. + * (See LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +module std.digest.murmurhash; + +/// +@safe unittest +{ + // MurmurHash3!32, MurmurHash3!(128, 32) and MurmurHash3!(128, 64) implement + // the std.digest Template API. + static assert(isDigest!(MurmurHash3!32)); + // The convenient digest template allows for quick hashing of any data. + ubyte[4] hashed = digest!(MurmurHash3!32)([1, 2, 3, 4]); + assert(hashed == [0, 173, 69, 68]); +} + +/// +@safe unittest +{ + // One can also hash ubyte data piecewise by instanciating a hasher and call + // the 'put' method. + const(ubyte)[] data1 = [1, 2, 3]; + const(ubyte)[] data2 = [4, 5, 6, 7]; + // The incoming data will be buffered and hashed element by element. + MurmurHash3!32 hasher; + hasher.put(data1); + hasher.put(data2); + // The call to 'finish' ensures: + // - the remaining bits are processed + // - the hash gets finalized + auto hashed = hasher.finish(); + assert(hashed == [181, 151, 88, 252]); +} + +/// +@safe unittest +{ + // Using `putElements`, `putRemainder` and `finalize` you gain full + // control over which part of the algorithm to run. + // This allows for maximum throughput but needs extra care. + + // Data type must be the same as the hasher's element type: + // - uint for MurmurHash3!32 + // - uint[4] for MurmurHash3!(128, 32) + // - ulong[2] for MurmurHash3!(128, 64) + const(uint)[] data = [1, 2, 3, 4]; + // Note the hasher starts with 'Fast'. + MurmurHash3!32 hasher; + // Push as many array of elements as you need. The less calls the better. + hasher.putElements(data); + // Put remainder bytes if needed. This method can be called only once. + hasher.putRemainder(ubyte(1), ubyte(1), ubyte(1)); + // Call finalize to incorporate data length in the hash. + hasher.finalize(); + // Finally get the hashed value. + auto hashed = hasher.getBytes(); + assert(hashed == [188, 165, 108, 2]); +} + +public import std.digest; + +@safe: + +/* +Performance notes: + - To help a bit with the performance when compiling with DMD some + functions have been rewritten to pass by value instead of by reference. + - GDC and LDC are on par with their C++ counterpart. + - DMD is typically between 20% to 50% of the GCC version. +*/ + +/++ + + Implements the MurmurHash3 functions. You can specify the `size` of the + + hash in bit. For 128 bit hashes you can specify whether to optimize for 32 + + or 64 bit architectures. If you don't specify the `opt` value it will select + + the fastest version of the host platform. + + + + This hasher is compatible with the `Digest` API: + + $(UL + + $(LI `void start()`) + + $(LI `void put(scope const(ubyte)[] data...)`) + + $(LI `ubyte[Element.sizeof] finish()`) + + ) + + + + It also provides a faster, low level API working with data of size + + `Element.sizeof`: + + $(UL + + $(LI `void putElements(scope const(Element[]) elements...)`) + + $(LI `void putRemainder(scope const(ubyte[]) data...)`) + + $(LI `void finalize()`) + + $(LI `Element get()`) + + $(LI `ubyte[Element.sizeof] getBytes()`) + + ) + +/ +struct MurmurHash3(uint size /* 32 or 128 */ , uint opt = size_t.sizeof == 8 ? 64 : 32) +{ + enum blockSize = size; // Number of bits of the hashed value. + size_t element_count; // The number of full elements pushed, this is used for finalization. + + static if (size == 32) + { + private enum uint c1 = 0xcc9e2d51; + private enum uint c2 = 0x1b873593; + private uint h1; + alias Element = uint; /// The element type for 32-bit implementation. + + this(uint seed) + { + h1 = seed; + } + /++ + Adds a single Element of data without increasing `element_count`. + Make sure to increase `element_count` by `Element.sizeof` for each call to `putElement`. + +/ + void putElement(uint block) pure nothrow @nogc + { + h1 = update(h1, block, 0, c1, c2, 15, 13, 0xe6546b64U); + } + + /// Put remainder bytes. This must be called only once after `putElement` and before `finalize`. + void putRemainder(scope const(ubyte[]) data...) pure nothrow @nogc + { + assert(data.length < Element.sizeof); + assert(data.length >= 0); + element_count += data.length; + uint k1 = 0; + final switch (data.length & 3) + { + case 3: + k1 ^= data[2] << 16; + goto case; + case 2: + k1 ^= data[1] << 8; + goto case; + case 1: + k1 ^= data[0]; + h1 ^= shuffle(k1, c1, c2, 15); + goto case; + case 0: + } + } + + /// Incorporate `element_count` and finalizes the hash. + void finalize() pure nothrow @nogc + { + h1 ^= element_count; + h1 = fmix(h1); + } + + /// Returns the hash as an uint value. + Element get() pure nothrow @nogc + { + return h1; + } + + /// Returns the current hashed value as an ubyte array. + ubyte[4] getBytes() pure nothrow @nogc + { + return cast(typeof(return)) cast(uint[1])[get()]; + } + } + else static if (size == 128 && opt == 32) + { + private enum uint c1 = 0x239b961b; + private enum uint c2 = 0xab0e9789; + private enum uint c3 = 0x38b34ae5; + private enum uint c4 = 0xa1e38b93; + private uint h4, h3, h2, h1; + + alias Element = uint[4]; /// The element type for 128-bit implementation. + + this(uint seed4, uint seed3, uint seed2, uint seed1) pure nothrow @nogc + { + h4 = seed4; + h3 = seed3; + h2 = seed2; + h1 = seed1; + } + + this(uint seed) pure nothrow @nogc + { + h4 = h3 = h2 = h1 = seed; + } + + /++ + Adds a single Element of data without increasing element_count. + Make sure to increase `element_count` by `Element.sizeof` for each call to `putElement`. + +/ + void putElement(Element block) pure nothrow @nogc + { + h1 = update(h1, block[0], h2, c1, c2, 15, 19, 0x561ccd1bU); + h2 = update(h2, block[1], h3, c2, c3, 16, 17, 0x0bcaa747U); + h3 = update(h3, block[2], h4, c3, c4, 17, 15, 0x96cd1c35U); + h4 = update(h4, block[3], h1, c4, c1, 18, 13, 0x32ac3b17U); + } + + /// Put remainder bytes. This must be called only once after `putElement` and before `finalize`. + void putRemainder(scope const(ubyte[]) data...) pure nothrow @nogc + { + assert(data.length < Element.sizeof); + assert(data.length >= 0); + element_count += data.length; + uint k1 = 0; + uint k2 = 0; + uint k3 = 0; + uint k4 = 0; + + final switch (data.length & 15) + { + case 15: + k4 ^= data[14] << 16; + goto case; + case 14: + k4 ^= data[13] << 8; + goto case; + case 13: + k4 ^= data[12] << 0; + h4 ^= shuffle(k4, c4, c1, 18); + goto case; + case 12: + k3 ^= data[11] << 24; + goto case; + case 11: + k3 ^= data[10] << 16; + goto case; + case 10: + k3 ^= data[9] << 8; + goto case; + case 9: + k3 ^= data[8] << 0; + h3 ^= shuffle(k3, c3, c4, 17); + goto case; + case 8: + k2 ^= data[7] << 24; + goto case; + case 7: + k2 ^= data[6] << 16; + goto case; + case 6: + k2 ^= data[5] << 8; + goto case; + case 5: + k2 ^= data[4] << 0; + h2 ^= shuffle(k2, c2, c3, 16); + goto case; + case 4: + k1 ^= data[3] << 24; + goto case; + case 3: + k1 ^= data[2] << 16; + goto case; + case 2: + k1 ^= data[1] << 8; + goto case; + case 1: + k1 ^= data[0] << 0; + h1 ^= shuffle(k1, c1, c2, 15); + goto case; + case 0: + } + } + + /// Incorporate `element_count` and finalizes the hash. + void finalize() pure nothrow @nogc + { + h1 ^= element_count; + h2 ^= element_count; + h3 ^= element_count; + h4 ^= element_count; + + h1 += h2; + h1 += h3; + h1 += h4; + h2 += h1; + h3 += h1; + h4 += h1; + + h1 = fmix(h1); + h2 = fmix(h2); + h3 = fmix(h3); + h4 = fmix(h4); + + h1 += h2; + h1 += h3; + h1 += h4; + h2 += h1; + h3 += h1; + h4 += h1; + } + + /// Returns the hash as an uint[4] value. + Element get() pure nothrow @nogc + { + return [h1, h2, h3, h4]; + } + + /// Returns the current hashed value as an ubyte array. + ubyte[16] getBytes() pure nothrow @nogc + { + return cast(typeof(return)) get(); + } + } + else static if (size == 128 && opt == 64) + { + private enum ulong c1 = 0x87c37b91114253d5; + private enum ulong c2 = 0x4cf5ad432745937f; + private ulong h2, h1; + + alias Element = ulong[2]; /// The element type for 128-bit implementation. + + this(ulong seed) pure nothrow @nogc + { + h2 = h1 = seed; + } + + this(ulong seed2, ulong seed1) pure nothrow @nogc + { + h2 = seed2; + h1 = seed1; + } + + /++ + Adds a single Element of data without increasing `element_count`. + Make sure to increase `element_count` by `Element.sizeof` for each call to `putElement`. + +/ + void putElement(Element block) pure nothrow @nogc + { + h1 = update(h1, block[0], h2, c1, c2, 31, 27, 0x52dce729U); + h2 = update(h2, block[1], h1, c2, c1, 33, 31, 0x38495ab5U); + } + + /// Put remainder bytes. This must be called only once after `putElement` and before `finalize`. + void putRemainder(scope const(ubyte[]) data...) pure nothrow @nogc + { + assert(data.length < Element.sizeof); + assert(data.length >= 0); + element_count += data.length; + ulong k1 = 0; + ulong k2 = 0; + final switch (data.length & 15) + { + case 15: + k2 ^= ulong(data[14]) << 48; + goto case; + case 14: + k2 ^= ulong(data[13]) << 40; + goto case; + case 13: + k2 ^= ulong(data[12]) << 32; + goto case; + case 12: + k2 ^= ulong(data[11]) << 24; + goto case; + case 11: + k2 ^= ulong(data[10]) << 16; + goto case; + case 10: + k2 ^= ulong(data[9]) << 8; + goto case; + case 9: + k2 ^= ulong(data[8]) << 0; + h2 ^= shuffle(k2, c2, c1, 33); + goto case; + case 8: + k1 ^= ulong(data[7]) << 56; + goto case; + case 7: + k1 ^= ulong(data[6]) << 48; + goto case; + case 6: + k1 ^= ulong(data[5]) << 40; + goto case; + case 5: + k1 ^= ulong(data[4]) << 32; + goto case; + case 4: + k1 ^= ulong(data[3]) << 24; + goto case; + case 3: + k1 ^= ulong(data[2]) << 16; + goto case; + case 2: + k1 ^= ulong(data[1]) << 8; + goto case; + case 1: + k1 ^= ulong(data[0]) << 0; + h1 ^= shuffle(k1, c1, c2, 31); + goto case; + case 0: + } + } + + /// Incorporate `element_count` and finalizes the hash. + void finalize() pure nothrow @nogc + { + h1 ^= element_count; + h2 ^= element_count; + + h1 += h2; + h2 += h1; + h1 = fmix(h1); + h2 = fmix(h2); + h1 += h2; + h2 += h1; + } + + /// Returns the hash as an ulong[2] value. + Element get() pure nothrow @nogc + { + return [h1, h2]; + } + + /// Returns the current hashed value as an ubyte array. + ubyte[16] getBytes() pure nothrow @nogc + { + return cast(typeof(return)) get(); + } + } + else + { + alias Element = char; // This is needed to trigger the following error message. + static assert(false, "MurmurHash3(" ~ size.stringof ~ ", " ~ opt.stringof ~ ") is not implemented"); + } + + /++ + Pushes an array of elements at once. It is more efficient to push as much data as possible in a single call. + On platforms that do not support unaligned reads (MIPS or old ARM chips), the compiler may produce slower code to ensure correctness. + +/ + void putElements(scope const(Element[]) elements...) pure nothrow @nogc + { + foreach (const block; elements) + { + putElement(block); + } + element_count += elements.length * Element.sizeof; + } + + //------------------------------------------------------------------------- + // Implementation of the Digest API. + //------------------------------------------------------------------------- + + private union BufferUnion + { + Element block; + ubyte[Element.sizeof] data; + } + + private BufferUnion buffer; + private size_t bufferSize; + + @disable this(this); + + // Initialize + void start() + { + this = this.init; + } + + /++ + Adds data to the digester. This function can be called many times in a row + after start but before finish. + +/ + void put(scope const(ubyte)[] data...) pure nothrow + { + // Buffer should never be full while entering this function. + assert(bufferSize < Element.sizeof); + + // Check if we have some leftover data in the buffer. Then fill the first block buffer. + if (bufferSize + data.length < Element.sizeof) + { + buffer.data[bufferSize .. bufferSize + data.length] = data[]; + bufferSize += data.length; + return; + } + const bufferLeeway = Element.sizeof - bufferSize; + assert(bufferLeeway <= Element.sizeof); + buffer.data[bufferSize .. $] = data[0 .. bufferLeeway]; + putElement(buffer.block); + data = data[bufferLeeway .. $]; + + // Do main work: process chunks of `Element.sizeof` bytes. + const numElements = data.length / Element.sizeof; + const remainderStart = numElements * Element.sizeof; + foreach (ref const Element block; cast(const(Element[]))(data[0 .. remainderStart])) + { + putElement(block); + } + // +1 for bufferLeeway Element. + element_count += (numElements + 1) * Element.sizeof; + data = data[remainderStart .. $]; + + // Now add remaining data to buffer. + assert(data.length < Element.sizeof); + bufferSize = data.length; + buffer.data[0 .. data.length] = data[]; + } + + /++ + Finalizes the computation of the hash and returns the computed value. + Note that $(D finish) can be called only once and that no subsequent calls + to $(D put) is allowed. + +/ + ubyte[Element.sizeof] finish() pure nothrow + { + auto tail = buffer.data[0 .. bufferSize]; + if (tail.length > 0) + { + putRemainder(tail); + } + finalize(); + return getBytes(); + } + + //------------------------------------------------------------------------- + // MurmurHash3 utils + //------------------------------------------------------------------------- + + private T rotl(T)(T x, uint y) + in + { + import std.traits : isUnsigned; + + static assert(isUnsigned!T); + debug assert(y >= 0 && y <= (T.sizeof * 8)); + } + body + { + return ((x << y) | (x >> ((T.sizeof * 8) - y))); + } + + private T shuffle(T)(T k, T c1, T c2, ubyte r1) + { + import std.traits : isUnsigned; + + static assert(isUnsigned!T); + k *= c1; + k = rotl(k, r1); + k *= c2; + return k; + } + + private T update(T)(ref T h, T k, T mixWith, T c1, T c2, ubyte r1, ubyte r2, T n) + { + import std.traits : isUnsigned; + + static assert(isUnsigned!T); + h ^= shuffle(k, c1, c2, r1); + h = rotl(h, r2); + h += mixWith; + return h * 5 + n; + } + + private uint fmix(uint h) pure nothrow @nogc + { + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + return h; + } + + private ulong fmix(ulong k) pure nothrow @nogc + { + k ^= k >> 33; + k *= 0xff51afd7ed558ccd; + k ^= k >> 33; + k *= 0xc4ceb9fe1a85ec53; + k ^= k >> 33; + return k; + } +} + +version (unittest) +{ + import std.string : representation; + + private auto hash(H, Element = H.Element)(string data) + { + H hasher; + immutable elements = data.length / Element.sizeof; + hasher.putElements(cast(const(Element)[]) data[0 .. elements * Element.sizeof]); + hasher.putRemainder(cast(const(ubyte)[]) data[elements * Element.sizeof .. $]); + hasher.finalize(); + return hasher.getBytes(); + } + + private void checkResult(H)(in string[string] groundtruth) + { + foreach (data, expectedHash; groundtruth) + { + assert(data.digest!H.toHexString() == expectedHash); + assert(data.hash!H.toHexString() == expectedHash); + H hasher; + foreach (element; data) + { + hasher.put(element); + } + assert(hasher.finish.toHexString() == expectedHash); + } + } +} + +@safe unittest +{ + // dfmt off + checkResult!(MurmurHash3!32)([ + "" : "00000000", + "a" : "B269253C", + "ab" : "5FD7BF9B", + "abc" : "FA93DDB3", + "abcd" : "6A67ED43", + "abcde" : "F69A9BE8", + "abcdef" : "85C08161", + "abcdefg" : "069B3C88", + "abcdefgh" : "C4CCDD49", + "abcdefghi" : "F0061442", + "abcdefghij" : "91779288", + "abcdefghijk" : "DF253B5F", + "abcdefghijkl" : "273D6FA3", + "abcdefghijklm" : "1B1612F2", + "abcdefghijklmn" : "F06D52F8", + "abcdefghijklmno" : "D2F7099D", + "abcdefghijklmnop" : "ED9162E7", + "abcdefghijklmnopq" : "4A5E65B6", + "abcdefghijklmnopqr" : "94A819C2", + "abcdefghijklmnopqrs" : "C15BBF85", + "abcdefghijklmnopqrst" : "9A711CBE", + "abcdefghijklmnopqrstu" : "ABE7195A", + "abcdefghijklmnopqrstuv" : "C73CB670", + "abcdefghijklmnopqrstuvw" : "1C4D1EA5", + "abcdefghijklmnopqrstuvwx" : "3939F9B0", + "abcdefghijklmnopqrstuvwxy" : "1A568338", + "abcdefghijklmnopqrstuvwxyz" : "6D034EA3"]); + // dfmt on +} + +@safe unittest +{ + // dfmt off + checkResult!(MurmurHash3!(128,32))([ + "" : "00000000000000000000000000000000", + "a" : "3C9394A71BB056551BB056551BB05655", + "ab" : "DF5184151030BE251030BE251030BE25", + "abc" : "D1C6CD75A506B0A2A506B0A2A506B0A2", + "abcd" : "AACCB6962EC6AF452EC6AF452EC6AF45", + "abcde" : "FB2E40C5BCC5245D7701725A7701725A", + "abcdef" : "0AB97CE12127AFA1F9DFBEA9F9DFBEA9", + "abcdefg" : "D941B590DE3A86092869774A2869774A", + "abcdefgh" : "3611F4AE8714B1AD92806CFA92806CFA", + "abcdefghi" : "1C8C05AD6F590622107DD2147C4194DD", + "abcdefghij" : "A72ED9F50E90379A2AAA92C77FF12F69", + "abcdefghijk" : "DDC9C8A01E111FCA2DF1FE8257975EBD", + "abcdefghijkl" : "FE038573C02482F4ADDFD42753E58CD2", + "abcdefghijklm" : "15A23AC1ECA1AEDB66351CF470DE2CD9", + "abcdefghijklmn" : "8E11EC75D71F5D60F4456F944D89D4F1", + "abcdefghijklmno" : "691D6DEEAED51A4A5714CE84A861A7AD", + "abcdefghijklmnop" : "2776D29F5612B990218BCEE445BA93D1", + "abcdefghijklmnopq" : "D3A445046F5C51642ADC6DD99D07111D", + "abcdefghijklmnopqr" : "AA5493A0DA291D966A9E7128585841D9", + "abcdefghijklmnopqrs" : "281B6A4F9C45B9BFC3B77850930F2C20", + "abcdefghijklmnopqrst" : "19342546A8216DB62873B49E545DCB1F", + "abcdefghijklmnopqrstu" : "A6C0F30D6C738620E7B9590D2E088D99", + "abcdefghijklmnopqrstuv" : "A7D421D9095CDCEA393CBBA908342384", + "abcdefghijklmnopqrstuvw" : "C3A93D572B014949317BAD7EE809158F", + "abcdefghijklmnopqrstuvwx" : "802381D77956833791F87149326E4801", + "abcdefghijklmnopqrstuvwxy" : "0AC619A5302315755A80D74ADEFAA842", + "abcdefghijklmnopqrstuvwxyz" : "1306343E662F6F666E56F6172C3DE344"]); + // dfmt on +} + +@safe unittest +{ + // dfmt off + checkResult!(MurmurHash3!(128,64))([ + "" : "00000000000000000000000000000000", + "a" : "897859F6655555855A890E51483AB5E6", + "ab" : "2E1BED16EA118B93ADD4529B01A75EE6", + "abc" : "6778AD3F3F3F96B4522DCA264174A23B", + "abcd" : "4FCD5646D6B77BB875E87360883E00F2", + "abcde" : "B8BB96F491D036208CECCF4BA0EEC7C5", + "abcdef" : "55BFA3ACBF867DE45C842133990971B0", + "abcdefg" : "99E49EC09F2FCDA6B6BB55B13AA23A1C", + "abcdefgh" : "028CEF37B00A8ACCA14069EB600D8948", + "abcdefghi" : "64793CF1CFC0470533E041B7F53DB579", + "abcdefghij" : "998C2F770D5BC1B6C91A658CDC854DA2", + "abcdefghijk" : "029D78DFB8D095A871E75A45E2317CBB", + "abcdefghijkl" : "94E17AE6B19BF38E1C62FF7232309E1F", + "abcdefghijklm" : "73FAC0A78D2848167FCCE70DFF7B652E", + "abcdefghijklmn" : "E075C3F5A794D09124336AD2276009EE", + "abcdefghijklmno" : "FB2F0C895124BE8A612A969C2D8C546A", + "abcdefghijklmnop" : "23B74C22A33CCAC41AEB31B395D63343", + "abcdefghijklmnopq" : "57A6BD887F746475E40D11A19D49DAEC", + "abcdefghijklmnopqr" : "508A7F90EC8CF0776BC7005A29A8D471", + "abcdefghijklmnopqrs" : "886D9EDE23BC901574946FB62A4D8AA6", + "abcdefghijklmnopqrst" : "F1E237F926370B314BD016572AF40996", + "abcdefghijklmnopqrstu" : "3CC9FF79E268D5C9FB3C9BE9C148CCD7", + "abcdefghijklmnopqrstuv" : "56F8ABF430E388956DA9F4A8741FDB46", + "abcdefghijklmnopqrstuvw" : "8E234F9DBA0A4840FFE9541CEBB7BE83", + "abcdefghijklmnopqrstuvwx" : "F72CDED40F96946408F22153A3CF0F79", + "abcdefghijklmnopqrstuvwxy" : "0F96072FA4CBE771DBBD9E398115EEED", + "abcdefghijklmnopqrstuvwxyz" : "A94A6F517E9D9C7429D5A7B6899CADE9"]); + // dfmt on +} + +@safe unittest +{ + // Pushing unaligned data and making sure the result is still coherent. + void testUnalignedHash(H)() + { + immutable ubyte[1025] data = 0xAC; + immutable alignedHash = digest!H(data[0 .. $ - 1]); // 0 .. 1023 + immutable unalignedHash = digest!H(data[1 .. $]); // 1 .. 1024 + assert(alignedHash == unalignedHash); + } + + testUnalignedHash!(MurmurHash3!32)(); + testUnalignedHash!(MurmurHash3!(128, 32))(); + testUnalignedHash!(MurmurHash3!(128, 64))(); +} diff --git a/libphobos/src/std/digest/package.d b/libphobos/src/std/digest/package.d new file mode 100644 index 0000000..f4646ae --- /dev/null +++ b/libphobos/src/std/digest/package.d @@ -0,0 +1,1171 @@ +/** + * This module describes the _digest APIs used in Phobos. All digests follow + * these APIs. Additionally, this module contains useful helper methods which + * can be used with every _digest type. + * +$(SCRIPT inhibitQuickIndex = 1;) + +$(DIVC quickindex, +$(BOOKTABLE , +$(TR $(TH Category) $(TH Functions) +) +$(TR $(TDNW Template API) $(TD $(MYREF isDigest) $(MYREF DigestType) $(MYREF hasPeek) + $(MYREF hasBlockSize) + $(MYREF ExampleDigest) $(MYREF _digest) $(MYREF hexDigest) $(MYREF makeDigest) +) +) +$(TR $(TDNW OOP API) $(TD $(MYREF Digest) +) +) +$(TR $(TDNW Helper functions) $(TD $(MYREF toHexString)) +) +$(TR $(TDNW Implementation helpers) $(TD $(MYREF digestLength) $(MYREF WrapperDigest)) +) +) +) + + * APIs: + * There are two APIs for digests: The template API and the OOP API. The template API uses structs + * and template helpers like $(LREF isDigest). The OOP API implements digests as classes inheriting + * the $(LREF Digest) interface. All digests are named so that the template API struct is called "$(B x)" + * and the OOP API class is called "$(B x)Digest". For example we have $(D MD5) <--> $(D MD5Digest), + * $(D CRC32) <--> $(D CRC32Digest), etc. + * + * The template API is slightly more efficient. It does not have to allocate memory dynamically, + * all memory is allocated on the stack. The OOP API has to allocate in the finish method if no + * buffer was provided. If you provide a buffer to the OOP APIs finish function, it doesn't allocate, + * but the $(LREF Digest) classes still have to be created using $(D new) which allocates them using the GC. + * + * The OOP API is useful to change the _digest function and/or _digest backend at 'runtime'. The benefit here + * is that switching e.g. Phobos MD5Digest and an OpenSSLMD5Digest implementation is ABI compatible. + * + * If just one specific _digest type and backend is needed, the template API is usually a good fit. + * In this simplest case, the template API can even be used without templates: Just use the "$(B x)" structs + * directly. + * + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: + * Johannes Pfau + * + * Source: $(PHOBOSSRC std/_digest/_package.d) + * + * CTFE: + * Digests do not work in CTFE + * + * TODO: + * Digesting single bits (as opposed to bytes) is not implemented. This will be done as another + * template constraint helper (hasBitDigesting!T) and an additional interface (BitDigest) + */ +/* Copyright Johannes Pfau 2012. + * 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) + */ +module std.digest; + +public import std.ascii : LetterCase; +import std.meta : allSatisfy; +import std.range.primitives; +import std.traits; + + +/// +@system unittest +{ + import std.digest.crc; + + //Simple example + char[8] hexHash = hexDigest!CRC32("The quick brown fox jumps over the lazy dog"); + assert(hexHash == "39A34F41"); + + //Simple example, using the API manually + CRC32 context = makeDigest!CRC32(); + context.put(cast(ubyte[])"The quick brown fox jumps over the lazy dog"); + ubyte[4] hash = context.finish(); + assert(toHexString(hash) == "39A34F41"); +} + +/// +@system unittest +{ + //Generating the hashes of a file, idiomatic D way + import std.digest.crc, std.digest.md, std.digest.sha; + import std.stdio; + + // Digests a file and prints the result. + void digestFile(Hash)(string filename) + if (isDigest!Hash) + { + auto file = File(filename); + auto result = digest!Hash(file.byChunk(4096 * 1024)); + writefln("%s (%s) = %s", Hash.stringof, filename, toHexString(result)); + } + + void main(string[] args) + { + foreach (name; args[1 .. $]) + { + digestFile!MD5(name); + digestFile!SHA1(name); + digestFile!CRC32(name); + } + } +} +/// +@system unittest +{ + //Generating the hashes of a file using the template API + import std.digest.crc, std.digest.md, std.digest.sha; + import std.stdio; + // Digests a file and prints the result. + void digestFile(Hash)(ref Hash hash, string filename) + if (isDigest!Hash) + { + File file = File(filename); + + //As digests imlement OutputRange, we could use std.algorithm.copy + //Let's do it manually for now + foreach (buffer; file.byChunk(4096 * 1024)) + hash.put(buffer); + + auto result = hash.finish(); + writefln("%s (%s) = %s", Hash.stringof, filename, toHexString(result)); + } + + void uMain(string[] args) + { + MD5 md5; + SHA1 sha1; + CRC32 crc32; + + md5.start(); + sha1.start(); + crc32.start(); + + foreach (arg; args[1 .. $]) + { + digestFile(md5, arg); + digestFile(sha1, arg); + digestFile(crc32, arg); + } + } +} + +/// +@system unittest +{ + import std.digest.crc, std.digest.md, std.digest.sha; + import std.stdio; + + // Digests a file and prints the result. + void digestFile(Digest hash, string filename) + { + File file = File(filename); + + //As digests implement OutputRange, we could use std.algorithm.copy + //Let's do it manually for now + foreach (buffer; file.byChunk(4096 * 1024)) + hash.put(buffer); + + ubyte[] result = hash.finish(); + writefln("%s (%s) = %s", typeid(hash).toString(), filename, toHexString(result)); + } + + void umain(string[] args) + { + auto md5 = new MD5Digest(); + auto sha1 = new SHA1Digest(); + auto crc32 = new CRC32Digest(); + + foreach (arg; args[1 .. $]) + { + digestFile(md5, arg); + digestFile(sha1, arg); + digestFile(crc32, arg); + } + } +} + +version (StdDdoc) + version = ExampleDigest; + +version (ExampleDigest) +{ + /** + * This documents the general structure of a Digest in the template API. + * All digest implementations should implement the following members and therefore pass + * the $(LREF isDigest) test. + * + * Note: + * $(UL + * $(LI A digest must be a struct (value type) to pass the $(LREF isDigest) test.) + * $(LI A digest passing the $(LREF isDigest) test is always an $(D OutputRange)) + * ) + */ + struct ExampleDigest + { + public: + /** + * Use this to feed the digest with data. + * Also implements the $(REF isOutputRange, std,range,primitives) + * interface for $(D ubyte) and $(D const(ubyte)[]). + * The following usages of $(D put) must work for any type which + * passes $(LREF isDigest): + * Example: + * ---- + * ExampleDigest dig; + * dig.put(cast(ubyte) 0); //single ubyte + * dig.put(cast(ubyte) 0, cast(ubyte) 0); //variadic + * ubyte[10] buf; + * dig.put(buf); //buffer + * ---- + */ + @trusted void put(scope const(ubyte)[] data...) + { + + } + + /** + * This function is used to (re)initialize the digest. + * It must be called before using the digest and it also works as a 'reset' function + * if the digest has already processed data. + */ + @trusted void start() + { + + } + + /** + * The finish function returns the final hash sum and resets the Digest. + * + * Note: + * The actual type returned by finish depends on the digest implementation. + * $(D ubyte[16]) is just used as an example. It is guaranteed that the type is a + * static array of ubytes. + * + * $(UL + * $(LI Use $(LREF DigestType) to obtain the actual return type.) + * $(LI Use $(LREF digestLength) to obtain the length of the ubyte array.) + * ) + */ + @trusted ubyte[16] finish() + { + return (ubyte[16]).init; + } + } +} + +/// +@system unittest +{ + //Using the OutputRange feature + import std.algorithm.mutation : copy; + import std.digest.md; + import std.range : repeat; + + auto oneMillionRange = repeat!ubyte(cast(ubyte)'a', 1000000); + auto ctx = makeDigest!MD5(); + copy(oneMillionRange, &ctx); //Note: You must pass a pointer to copy! + assert(ctx.finish().toHexString() == "7707D6AE4E027C70EEA2A935C2296F21"); +} + +/** + * Use this to check if a type is a digest. See $(LREF ExampleDigest) to see what + * a type must provide to pass this check. + * + * Note: + * This is very useful as a template constraint (see examples) + * + * BUGS: + * $(UL + * $(LI Does not yet verify that put takes scope parameters.) + * $(LI Should check that finish() returns a ubyte[num] array) + * ) + */ +template isDigest(T) +{ + import std.range : isOutputRange; + enum bool isDigest = isOutputRange!(T, const(ubyte)[]) && isOutputRange!(T, ubyte) && + is(T == struct) && + is(typeof( + { + T dig = void; //Can define + dig.put(cast(ubyte) 0, cast(ubyte) 0); //varags + dig.start(); //has start + auto value = dig.finish(); //has finish + })); +} + +/// +@system unittest +{ + import std.digest.crc; + static assert(isDigest!CRC32); +} +/// +@system unittest +{ + import std.digest.crc; + void myFunction(T)() + if (isDigest!T) + { + T dig; + dig.start(); + auto result = dig.finish(); + } + myFunction!CRC32(); +} + +/** + * Use this template to get the type which is returned by a digest's $(LREF finish) method. + */ +template DigestType(T) +{ + static if (isDigest!T) + { + alias DigestType = + ReturnType!(typeof( + { + T dig = void; + return dig.finish(); + })); + } + else + static assert(false, T.stringof ~ " is not a digest! (fails isDigest!T)"); +} + +/// +@system unittest +{ + import std.digest.crc; + assert(is(DigestType!(CRC32) == ubyte[4])); +} +/// +@system unittest +{ + import std.digest.crc; + CRC32 dig; + dig.start(); + DigestType!CRC32 result = dig.finish(); +} + +/** + * Used to check if a digest supports the $(D peek) method. + * Peek has exactly the same function signatures as finish, but it doesn't reset + * the digest's internal state. + * + * Note: + * $(UL + * $(LI This is very useful as a template constraint (see examples)) + * $(LI This also checks if T passes $(LREF isDigest)) + * ) + */ +template hasPeek(T) +{ + enum bool hasPeek = isDigest!T && + is(typeof( + { + T dig = void; //Can define + DigestType!T val = dig.peek(); + })); +} + +/// +@system unittest +{ + import std.digest.crc, std.digest.md; + assert(!hasPeek!(MD5)); + assert(hasPeek!CRC32); +} +/// +@system unittest +{ + import std.digest.crc; + void myFunction(T)() + if (hasPeek!T) + { + T dig; + dig.start(); + auto result = dig.peek(); + } + myFunction!CRC32(); +} + +/** + * Checks whether the digest has a $(D blockSize) member, which contains the + * digest's internal block size in bits. It is primarily used by $(REF HMAC, std,digest,hmac). + */ + +template hasBlockSize(T) +if (isDigest!T) +{ + enum bool hasBlockSize = __traits(compiles, { size_t blockSize = T.blockSize; }); +} + +/// +@system unittest +{ + import std.digest.hmac, std.digest.md; + static assert(hasBlockSize!MD5 && MD5.blockSize == 512); + static assert(hasBlockSize!(HMAC!MD5) && HMAC!MD5.blockSize == 512); +} + +package template isDigestibleRange(Range) +{ + import std.digest.md; + import std.range : isInputRange, ElementType; + enum bool isDigestibleRange = isInputRange!Range && is(typeof( + { + MD5 ha; //Could use any conformant hash + ElementType!Range val; + ha.put(val); + })); +} + +/** + * This is a convenience function to calculate a hash using the template API. + * Every digest passing the $(LREF isDigest) test can be used with this function. + * + * Params: + * range= an $(D InputRange) with $(D ElementType) $(D ubyte), $(D ubyte[]) or $(D ubyte[num]) + */ +DigestType!Hash digest(Hash, Range)(auto ref Range range) +if (!isArray!Range + && isDigestibleRange!Range) +{ + import std.algorithm.mutation : copy; + Hash hash; + hash.start(); + copy(range, &hash); + return hash.finish(); +} + +/// +@system unittest +{ + import std.digest.md; + import std.range : repeat; + auto testRange = repeat!ubyte(cast(ubyte)'a', 100); + auto md5 = digest!MD5(testRange); +} + +/** + * This overload of the digest function handles arrays. + * + * Params: + * data= one or more arrays of any type + */ +DigestType!Hash digest(Hash, T...)(scope const T data) +if (allSatisfy!(isArray, typeof(data))) +{ + Hash hash; + hash.start(); + foreach (datum; data) + hash.put(cast(const(ubyte[]))datum); + return hash.finish(); +} + +/// +@system unittest +{ + import std.digest.crc, std.digest.md, std.digest.sha; + auto md5 = digest!MD5( "The quick brown fox jumps over the lazy dog"); + auto sha1 = digest!SHA1( "The quick brown fox jumps over the lazy dog"); + auto crc32 = digest!CRC32("The quick brown fox jumps over the lazy dog"); + assert(toHexString(crc32) == "39A34F41"); +} + +/// +@system unittest +{ + import std.digest.crc; + auto crc32 = digest!CRC32("The quick ", "brown ", "fox jumps over the lazy dog"); + assert(toHexString(crc32) == "39A34F41"); +} + +/** + * This is a convenience function similar to $(LREF digest), but it returns the string + * representation of the hash. Every digest passing the $(LREF isDigest) test can be used with this + * function. + * + * Params: + * order= the order in which the bytes are processed (see $(LREF toHexString)) + * range= an $(D InputRange) with $(D ElementType) $(D ubyte), $(D ubyte[]) or $(D ubyte[num]) + */ +char[digestLength!(Hash)*2] hexDigest(Hash, Order order = Order.increasing, Range)(ref Range range) +if (!isArray!Range && isDigestibleRange!Range) +{ + return toHexString!order(digest!Hash(range)); +} + +/// +@system unittest +{ + import std.digest.md; + import std.range : repeat; + auto testRange = repeat!ubyte(cast(ubyte)'a', 100); + assert(hexDigest!MD5(testRange) == "36A92CC94A9E0FA21F625F8BFB007ADF"); +} + +/** + * This overload of the hexDigest function handles arrays. + * + * Params: + * order= the order in which the bytes are processed (see $(LREF toHexString)) + * data= one or more arrays of any type + */ +char[digestLength!(Hash)*2] hexDigest(Hash, Order order = Order.increasing, T...)(scope const T data) +if (allSatisfy!(isArray, typeof(data))) +{ + return toHexString!order(digest!Hash(data)); +} + +/// +@system unittest +{ + import std.digest.crc; + assert(hexDigest!(CRC32, Order.decreasing)("The quick brown fox jumps over the lazy dog") == "414FA339"); +} +/// +@system unittest +{ + import std.digest.crc; + assert(hexDigest!(CRC32, Order.decreasing)("The quick ", "brown ", "fox jumps over the lazy dog") == "414FA339"); +} + +/** + * This is a convenience function which returns an initialized digest, so it's not necessary to call + * start manually. + */ +Hash makeDigest(Hash)() +{ + Hash hash; + hash.start(); + return hash; +} + +/// +@system unittest +{ + import std.digest.md; + auto md5 = makeDigest!MD5(); + md5.put(0); + assert(toHexString(md5.finish()) == "93B885ADFE0DA089CDF634904FD59F71"); +} + +/*+*************************** End of template part, welcome to OOP land **************************/ + +/** + * This describes the OOP API. To understand when to use the template API and when to use the OOP API, + * see the module documentation at the top of this page. + * + * The Digest interface is the base interface which is implemented by all digests. + * + * Note: + * A Digest implementation is always an $(D OutputRange) + */ +interface Digest +{ + public: + /** + * Use this to feed the digest with data. + * Also implements the $(REF isOutputRange, std,range,primitives) + * interface for $(D ubyte) and $(D const(ubyte)[]). + * + * Example: + * ---- + * void test(Digest dig) + * { + * dig.put(cast(ubyte) 0); //single ubyte + * dig.put(cast(ubyte) 0, cast(ubyte) 0); //variadic + * ubyte[10] buf; + * dig.put(buf); //buffer + * } + * ---- + */ + @trusted nothrow void put(scope const(ubyte)[] data...); + + /** + * Resets the internal state of the digest. + * Note: + * $(LREF finish) calls this internally, so it's not necessary to call + * $(D reset) manually after a call to $(LREF finish). + */ + @trusted nothrow void reset(); + + /** + * This is the length in bytes of the hash value which is returned by $(LREF finish). + * It's also the required size of a buffer passed to $(LREF finish). + */ + @trusted nothrow @property size_t length() const; + + /** + * The finish function returns the hash value. It takes an optional buffer to copy the data + * into. If a buffer is passed, it must be at least $(LREF length) bytes big. + */ + @trusted nothrow ubyte[] finish(); + ///ditto + nothrow ubyte[] finish(ubyte[] buf); + //@@@BUG@@@ http://d.puremagic.com/issues/show_bug.cgi?id=6549 + /*in + { + assert(buf.length >= this.length); + }*/ + + /** + * This is a convenience function to calculate the hash of a value using the OOP API. + */ + final @trusted nothrow ubyte[] digest(scope const(void[])[] data...) + { + this.reset(); + foreach (datum; data) + this.put(cast(ubyte[]) datum); + return this.finish(); + } +} + +/// +@system unittest +{ + //Using the OutputRange feature + import std.algorithm.mutation : copy; + import std.digest.md; + import std.range : repeat; + + auto oneMillionRange = repeat!ubyte(cast(ubyte)'a', 1000000); + auto ctx = new MD5Digest(); + copy(oneMillionRange, ctx); + assert(ctx.finish().toHexString() == "7707D6AE4E027C70EEA2A935C2296F21"); +} + +/// +@system unittest +{ + import std.digest.crc, std.digest.md, std.digest.sha; + ubyte[] md5 = (new MD5Digest()).digest("The quick brown fox jumps over the lazy dog"); + ubyte[] sha1 = (new SHA1Digest()).digest("The quick brown fox jumps over the lazy dog"); + ubyte[] crc32 = (new CRC32Digest()).digest("The quick brown fox jumps over the lazy dog"); + assert(crcHexString(crc32) == "414FA339"); +} + +/// +@system unittest +{ + import std.digest.crc; + ubyte[] crc32 = (new CRC32Digest()).digest("The quick ", "brown ", "fox jumps over the lazy dog"); + assert(crcHexString(crc32) == "414FA339"); +} + +@system unittest +{ + import std.range : isOutputRange; + assert(!isDigest!(Digest)); + assert(isOutputRange!(Digest, ubyte)); +} + +/// +@system unittest +{ + void test(Digest dig) + { + dig.put(cast(ubyte) 0); //single ubyte + dig.put(cast(ubyte) 0, cast(ubyte) 0); //variadic + ubyte[10] buf; + dig.put(buf); //buffer + } +} + +/*+*************************** End of OOP part, helper functions follow ***************************/ + +/** + * See $(LREF toHexString) + */ +enum Order : bool +{ + increasing, /// + decreasing /// +} + + +/** + * Used to convert a hash value (a static or dynamic array of ubytes) to a string. + * Can be used with the OOP and with the template API. + * + * The additional order parameter can be used to specify the order of the input data. + * By default the data is processed in increasing order, starting at index 0. To process it in the + * opposite order, pass Order.decreasing as a parameter. + * + * The additional letterCase parameter can be used to specify the case of the output data. + * By default the output is in upper case. To change it to the lower case + * pass LetterCase.lower as a parameter. + * + * Note: + * The function overloads returning a string allocate their return values + * using the GC. The versions returning static arrays use pass-by-value for + * the return value, effectively avoiding dynamic allocation. + */ +char[num*2] toHexString(Order order = Order.increasing, size_t num, LetterCase letterCase = LetterCase.upper) +(in ubyte[num] digest) +{ + static if (letterCase == LetterCase.upper) + { + import std.ascii : hexDigits = hexDigits; + } + else + { + import std.ascii : hexDigits = lowerHexDigits; + } + + + char[num*2] result; + size_t i; + + static if (order == Order.increasing) + { + foreach (u; digest) + { + result[i++] = hexDigits[u >> 4]; + result[i++] = hexDigits[u & 15]; + } + } + else + { + size_t j = num - 1; + while (i < num*2) + { + result[i++] = hexDigits[digest[j] >> 4]; + result[i++] = hexDigits[digest[j] & 15]; + j--; + } + } + return result; +} + +///ditto +char[num*2] toHexString(LetterCase letterCase, Order order = Order.increasing, size_t num)(in ubyte[num] digest) +{ + return toHexString!(order, num, letterCase)(digest); +} + +///ditto +string toHexString(Order order = Order.increasing, LetterCase letterCase = LetterCase.upper) +(in ubyte[] digest) +{ + static if (letterCase == LetterCase.upper) + { + import std.ascii : hexDigits = hexDigits; + } + else + { + import std.ascii : hexDigits = lowerHexDigits; + } + + auto result = new char[digest.length*2]; + size_t i; + + static if (order == Order.increasing) + { + foreach (u; digest) + { + result[i++] = hexDigits[u >> 4]; + result[i++] = hexDigits[u & 15]; + } + } + else + { + import std.range : retro; + foreach (u; retro(digest)) + { + result[i++] = hexDigits[u >> 4]; + result[i++] = hexDigits[u & 15]; + } + } + import std.exception : assumeUnique; + // memory was just created, so casting to immutable is safe + return () @trusted { return assumeUnique(result); }(); +} + +///ditto +string toHexString(LetterCase letterCase, Order order = Order.increasing)(in ubyte[] digest) +{ + return toHexString!(order, letterCase)(digest); +} + +//For more example unittests, see Digest.digest, digest + +/// +@safe unittest +{ + import std.digest.crc; + //Test with template API: + auto crc32 = digest!CRC32("The quick ", "brown ", "fox jumps over the lazy dog"); + //Lower case variant: + assert(toHexString!(LetterCase.lower)(crc32) == "39a34f41"); + //Usually CRCs are printed in this order, though: + assert(toHexString!(Order.decreasing)(crc32) == "414FA339"); + assert(toHexString!(LetterCase.lower, Order.decreasing)(crc32) == "414fa339"); +} + +/// +@safe unittest +{ + import std.digest.crc; + // With OOP API + auto crc32 = (new CRC32Digest()).digest("The quick ", "brown ", "fox jumps over the lazy dog"); + //Usually CRCs are printed in this order, though: + assert(toHexString!(Order.decreasing)(crc32) == "414FA339"); +} + +@safe unittest +{ + ubyte[16] data; + assert(toHexString(data) == "00000000000000000000000000000000"); + + assert(toHexString(cast(ubyte[4])[42, 43, 44, 45]) == "2A2B2C2D"); + assert(toHexString(cast(ubyte[])[42, 43, 44, 45]) == "2A2B2C2D"); + assert(toHexString!(Order.decreasing)(cast(ubyte[4])[42, 43, 44, 45]) == "2D2C2B2A"); + assert(toHexString!(Order.decreasing, LetterCase.lower)(cast(ubyte[4])[42, 43, 44, 45]) == "2d2c2b2a"); + assert(toHexString!(Order.decreasing)(cast(ubyte[])[42, 43, 44, 45]) == "2D2C2B2A"); +} + +/*+*********************** End of public helper part, private helpers follow ***********************/ + +/* + * Used to convert from a ubyte[] slice to a ref ubyte[N]. + * This helper is used internally in the WrapperDigest template to wrap the template API's + * finish function. + */ +ref T[N] asArray(size_t N, T)(ref T[] source, string errorMsg = "") +{ + assert(source.length >= N, errorMsg); + return *cast(T[N]*) source.ptr; +} + +/* + * Returns the length (in bytes) of the hash value produced by T. + */ +template digestLength(T) +if (isDigest!T) +{ + enum size_t digestLength = (ReturnType!(T.finish)).length; +} + +@safe pure nothrow @nogc +unittest +{ + import std.digest.md : MD5; + import std.digest.sha : SHA1, SHA256, SHA512; + assert(digestLength!MD5 == 16); + assert(digestLength!SHA1 == 20); + assert(digestLength!SHA256 == 32); + assert(digestLength!SHA512 == 64); +} + +/** + * Wraps a template API hash struct into a Digest interface. + * Modules providing digest implementations will usually provide + * an alias for this template (e.g. MD5Digest, SHA1Digest, ...). + */ +class WrapperDigest(T) +if (isDigest!T) : Digest +{ + protected: + T _digest; + + public final: + /** + * Initializes the digest. + */ + this() + { + _digest.start(); + } + + /** + * Use this to feed the digest with data. + * Also implements the $(REF isOutputRange, std,range,primitives) + * interface for $(D ubyte) and $(D const(ubyte)[]). + */ + @trusted nothrow void put(scope const(ubyte)[] data...) + { + _digest.put(data); + } + + /** + * Resets the internal state of the digest. + * Note: + * $(LREF finish) calls this internally, so it's not necessary to call + * $(D reset) manually after a call to $(LREF finish). + */ + @trusted nothrow void reset() + { + _digest.start(); + } + + /** + * This is the length in bytes of the hash value which is returned by $(LREF finish). + * It's also the required size of a buffer passed to $(LREF finish). + */ + @trusted nothrow @property size_t length() const pure + { + return digestLength!T; + } + + /** + * The finish function returns the hash value. It takes an optional buffer to copy the data + * into. If a buffer is passed, it must have a length at least $(LREF length) bytes. + * + * Example: + * -------- + * + * import std.digest.md; + * ubyte[16] buf; + * auto hash = new WrapperDigest!MD5(); + * hash.put(cast(ubyte) 0); + * auto result = hash.finish(buf[]); + * //The result is now in result (and in buf). If you pass a buffer which is bigger than + * //necessary, result will have the correct length, but buf will still have it's original + * //length + * -------- + */ + nothrow ubyte[] finish(ubyte[] buf) + in + { + assert(buf.length >= this.length); + } + body + { + enum string msg = "Buffer needs to be at least " ~ digestLength!(T).stringof ~ " bytes " ~ + "big, check " ~ typeof(this).stringof ~ ".length!"; + asArray!(digestLength!T)(buf, msg) = _digest.finish(); + return buf[0 .. digestLength!T]; + } + + ///ditto + @trusted nothrow ubyte[] finish() + { + enum len = digestLength!T; + auto buf = new ubyte[len]; + asArray!(digestLength!T)(buf) = _digest.finish(); + return buf; + } + + version (StdDdoc) + { + /** + * Works like $(D finish) but does not reset the internal state, so it's possible + * to continue putting data into this WrapperDigest after a call to peek. + * + * These functions are only available if $(D hasPeek!T) is true. + */ + @trusted ubyte[] peek(ubyte[] buf) const; + ///ditto + @trusted ubyte[] peek() const; + } + else static if (hasPeek!T) + { + @trusted ubyte[] peek(ubyte[] buf) const + in + { + assert(buf.length >= this.length); + } + body + { + enum string msg = "Buffer needs to be at least " ~ digestLength!(T).stringof ~ " bytes " ~ + "big, check " ~ typeof(this).stringof ~ ".length!"; + asArray!(digestLength!T)(buf, msg) = _digest.peek(); + return buf[0 .. digestLength!T]; + } + + @trusted ubyte[] peek() const + { + enum len = digestLength!T; + auto buf = new ubyte[len]; + asArray!(digestLength!T)(buf) = _digest.peek(); + return buf; + } + } +} + +/// +@system unittest +{ + import std.digest.md; + //Simple example + auto hash = new WrapperDigest!MD5(); + hash.put(cast(ubyte) 0); + auto result = hash.finish(); +} + +/// +@system unittest +{ + //using a supplied buffer + import std.digest.md; + ubyte[16] buf; + auto hash = new WrapperDigest!MD5(); + hash.put(cast(ubyte) 0); + auto result = hash.finish(buf[]); + //The result is now in result (and in buf). If you pass a buffer which is bigger than + //necessary, result will have the correct length, but buf will still have it's original + //length +} + +@safe unittest +{ + // Test peek & length + import std.digest.crc; + auto hash = new WrapperDigest!CRC32(); + assert(hash.length == 4); + hash.put(cast(const(ubyte[]))"The quick brown fox jumps over the lazy dog"); + assert(hash.peek().toHexString() == "39A34F41"); + ubyte[5] buf; + assert(hash.peek(buf).toHexString() == "39A34F41"); +} + +/** + * Securely compares two digest representations while protecting against timing + * attacks. Do not use `==` to compare digest representations. + * + * The attack happens as follows: + * + * $(OL + * $(LI An attacker wants to send harmful data to your server, which + * requires a integrity HMAC SHA1 token signed with a secret.) + * $(LI The length of the token is known to be 40 characters long due to its format, + * so the attacker first sends `"0000000000000000000000000000000000000000"`, + * then `"1000000000000000000000000000000000000000"`, and so on.) + * $(LI The given HMAC token is compared with the expected token using the + * `==` string comparison, which returns `false` as soon as the first wrong + * element is found. If a wrong element is found, then a rejection is sent + * back to the sender.) + * $(LI Eventually, the attacker is able to determine the first character in + * the correct token because the sever takes slightly longer to return a + * rejection. This is due to the comparison moving on to second item in + * the two arrays, seeing they are different, and then sending the rejection.) + * $(LI It may seem like too small of a difference in time for the attacker + * to notice, but security researchers have shown that differences as + * small as $(LINK2 http://www.cs.rice.edu/~dwallach/pub/crosby-timing2009.pdf, + * 20µs can be reliably distinguished) even with network inconsistencies.) + * $(LI Repeat the process for each character until the attacker has the whole + * correct token and the server accepts the harmful data. This can be done + * in a week with the attacker pacing the attack to 10 requests per second + * with only one client.) + * ) + * + * This function defends against this attack by always comparing every single + * item in the array if the two arrays are the same length. Therefore, this + * function is always $(BIGOH n) for ranges of the same length. + * + * This attack can also be mitigated via rate limiting and banning IPs which have too + * many rejected requests. However, this does not completely solve the problem, + * as the attacker could be in control of a bot net. To fully defend against + * the timing attack, rate limiting, banning IPs, and using this function + * should be used together. + * + * Params: + * r1 = A digest representation + * r2 = A digest representation + * Returns: + * `true` if both representations are equal, `false` otherwise + * See_Also: + * $(LINK2 https://en.wikipedia.org/wiki/Timing_attack, The Wikipedia article + * on timing attacks). + */ +bool secureEqual(R1, R2)(R1 r1, R2 r2) +if (isInputRange!R1 && isInputRange!R2 && !isInfinite!R1 && !isInfinite!R2 && + (isIntegral!(ElementEncodingType!R1) || isSomeChar!(ElementEncodingType!R1)) && + !is(CommonType!(ElementEncodingType!R1, ElementEncodingType!R2) == void)) +{ + static if (hasLength!R1 && hasLength!R2) + if (r1.length != r2.length) + return false; + + int result; + + static if (isRandomAccessRange!R1 && isRandomAccessRange!R2 && + hasLength!R1 && hasLength!R2) + { + foreach (i; 0 .. r1.length) + result |= r1[i] ^ r2[i]; + } + else static if (hasLength!R1 && hasLength!R2) + { + // Lengths are the same so we can squeeze out a bit of performance + // by not checking if r2 is empty + for (; !r1.empty; r1.popFront(), r2.popFront()) + { + result |= r1.front ^ r2.front; + } + } + else + { + // Generic case, walk both ranges + for (; !r1.empty; r1.popFront(), r2.popFront()) + { + if (r2.empty) return false; + result |= r1.front ^ r2.front; + } + if (!r2.empty) return false; + } + + return result == 0; +} + +/// +@system pure unittest +{ + import std.digest.hmac : hmac; + import std.digest.sha : SHA1; + import std.string : representation; + + // a typical HMAC data integrity verification + auto secret = "A7GZIP6TAQA6OHM7KZ42KB9303CEY0MOV5DD6NTV".representation; + auto data = "data".representation; + + string hex1 = data.hmac!SHA1(secret).toHexString; + string hex2 = data.hmac!SHA1(secret).toHexString; + string hex3 = "data1".representation.hmac!SHA1(secret).toHexString; + + assert( secureEqual(hex1, hex2)); + assert(!secureEqual(hex1, hex3)); +} + +@system pure unittest +{ + import std.internal.test.dummyrange : ReferenceInputRange; + import std.range : takeExactly; + import std.string : representation; + import std.utf : byWchar, byDchar; + + { + auto hex1 = "02CA3484C375EDD3C0F08D3F50D119E61077".representation; + auto hex2 = "02CA3484C375EDD3C0F08D3F50D119E610779018".representation; + assert(!secureEqual(hex1, hex2)); + } + { + auto hex1 = "02CA3484C375EDD3C0F08D3F50D119E610779018"w.representation; + auto hex2 = "02CA3484C375EDD3C0F08D3F50D119E610779018"d.representation; + assert(secureEqual(hex1, hex2)); + } + { + auto hex1 = "02CA3484C375EDD3C0F08D3F50D119E610779018".byWchar; + auto hex2 = "02CA3484C375EDD3C0F08D3F50D119E610779018".byDchar; + assert(secureEqual(hex1, hex2)); + } + { + auto hex1 = "02CA3484C375EDD3C0F08D3F50D119E61077".byWchar; + auto hex2 = "02CA3484C375EDD3C0F08D3F50D119E610779018".byDchar; + assert(!secureEqual(hex1, hex2)); + } + { + auto hex1 = new ReferenceInputRange!int([0, 1, 2, 3, 4, 5, 6, 7, 8]).takeExactly(9); + auto hex2 = new ReferenceInputRange!int([0, 1, 2, 3, 4, 5, 6, 7, 8]).takeExactly(9); + assert(secureEqual(hex1, hex2)); + } + { + auto hex1 = new ReferenceInputRange!int([0, 1, 2, 3, 4, 5, 6, 7, 8]).takeExactly(9); + auto hex2 = new ReferenceInputRange!int([0, 1, 2, 3, 4, 5, 6, 7, 9]).takeExactly(9); + assert(!secureEqual(hex1, hex2)); + } +} diff --git a/libphobos/src/std/digest/ripemd.d b/libphobos/src/std/digest/ripemd.d new file mode 100644 index 0000000..c47a741 --- /dev/null +++ b/libphobos/src/std/digest/ripemd.d @@ -0,0 +1,762 @@ +/** + * Computes RIPEMD-160 hashes of arbitrary data. RIPEMD-160 hashes are 20 byte quantities + * that are like a checksum or CRC, but are more robust. + * +$(SCRIPT inhibitQuickIndex = 1;) + +$(DIVC quickindex, +$(BOOKTABLE , +$(TR $(TH Category) $(TH Functions) +) +$(TR $(TDNW Template API) $(TD $(MYREF RIPEMD160) +) +) +$(TR $(TDNW OOP API) $(TD $(MYREF RIPEMD160Digest)) +) +$(TR $(TDNW Helpers) $(TD $(MYREF ripemd160Of)) +) +) +) + + * This module conforms to the APIs defined in $(MREF std, digest). To understand the + * differences between the template and the OOP API, see $(MREF std, digest). + * + * This module publicly imports $(D std.digest) and can be used as a stand-alone + * module. + * + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * + * CTFE: + * Digests do not work in CTFE + * + * Authors: + * Kai Nacke $(BR) + * The algorithm was designed by Hans Dobbertin, Antoon Bosselaers, and Bart Preneel. $(BR) + * The D implementation is a direct translation of the ANSI C implementation by Antoon Bosselaers. + * + * References: + * $(UL + * $(LI $(LINK2 http://homes.esat.kuleuven.be/~bosselae/ripemd160.html, The hash function RIPEMD-160)) + * $(LI $(LINK2 http://en.wikipedia.org/wiki/RIPEMD-160, Wikipedia on RIPEMD-160)) + * ) + * + * Source: $(PHOBOSSRC std/digest/_ripemd.d) + * + */ + +module std.digest.ripemd; + +public import std.digest; + +/// +@safe unittest +{ + //Template API + import std.digest.md; + + ubyte[20] hash = ripemd160Of("abc"); + assert(toHexString(hash) == "8EB208F7E05D987A9B044A8E98C6B087F15A0BFC"); + + //Feeding data + ubyte[1024] data; + RIPEMD160 md; + md.start(); + md.put(data[]); + md.start(); //Start again + md.put(data[]); + hash = md.finish(); +} + +/// +@safe unittest +{ + //OOP API + import std.digest.md; + + auto md = new RIPEMD160Digest(); + ubyte[] hash = md.digest("abc"); + assert(toHexString(hash) == "8EB208F7E05D987A9B044A8E98C6B087F15A0BFC"); + + //Feeding data + ubyte[1024] data; + md.put(data[]); + md.reset(); //Start again + md.put(data[]); + hash = md.finish(); +} + +//rotateLeft rotates x left n bits +private uint rotateLeft(uint x, uint n) @safe pure nothrow @nogc +{ + // With recently added optimization to DMD (commit 32ea0206 at 07/28/11), this is translated to rol. + // No assembler required. + return (x << n) | (x >> (32-n)); +} + +/** + * Template API RIPEMD160 implementation. + * See $(D std.digest) for differences between template and OOP API. + */ +struct RIPEMD160 +{ + private: + // magic initialization constants + uint[5] _state = [0x67452301,0xefcdab89,0x98badcfe,0x10325476,0xc3d2e1f0]; // state (ABCDE) + ulong _count; //number of bits, modulo 2^64 + ubyte[64] _buffer; // input buffer + + static immutable ubyte[64] _padding = + [ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ]; + + // F, G, H, I and J are basic RIPEMD160 functions + static @safe pure nothrow @nogc + { + uint F(uint x, uint y, uint z) { return x ^ y ^ z; } + uint G(uint x, uint y, uint z) { return (x & y) | (~x & z); } + uint H(uint x, uint y, uint z) { return (x | ~y) ^ z; } + uint I(uint x, uint y, uint z) { return (x & z) | (y & ~z); } + uint J(uint x, uint y, uint z) { return x ^ (y | ~z); } + } + + /* + * FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. + * Rotation is separate from addition to prevent recomputation. + */ + + /* the ten basic operations FF() through III() */ + static void FF(ref uint a, uint b, ref uint c, uint d, uint e, uint x, uint s) + @safe pure nothrow @nogc + { + a += F(b, c, d) + x; + a = rotateLeft(a, s) + e; + c = rotateLeft(c, 10); + } + + static void GG(ref uint a, uint b, ref uint c, uint d, uint e, uint x, uint s) + @safe pure nothrow @nogc + { + a += G(b, c, d) + x + 0x5a827999UL; + a = rotateLeft(a, s) + e; + c = rotateLeft(c, 10); + } + + static void HH(ref uint a, uint b, ref uint c, uint d, uint e, uint x, uint s) + @safe pure nothrow @nogc + { + a += H(b, c, d) + x + 0x6ed9eba1UL; + a = rotateLeft(a, s) + e; + c = rotateLeft(c, 10); + } + + static void II(ref uint a, uint b, ref uint c, uint d, uint e, uint x, uint s) + @safe pure nothrow @nogc + { + a += I(b, c, d) + x + 0x8f1bbcdcUL; + a = rotateLeft(a, s) + e; + c = rotateLeft(c, 10); + } + + static void JJ(ref uint a, uint b, ref uint c, uint d, uint e, uint x, uint s) + @safe pure nothrow @nogc + { + a += J(b, c, d) + x + 0xa953fd4eUL; + a = rotateLeft(a, s) + e; + c = rotateLeft(c, 10); + } + + /* + * FFF, GGG, HHH, and III transformations for parallel rounds 1, 2, 3, and 4. + * Rotation is separate from addition to prevent recomputation. + */ + + static void FFF(ref uint a, uint b, ref uint c, uint d, uint e, uint x, uint s) + @safe pure nothrow @nogc + { + a += F(b, c, d) + x; + a = rotateLeft(a, s) + e; + c = rotateLeft(c, 10); + } + + static void GGG(ref uint a, uint b, ref uint c, uint d, uint e, uint x, uint s) + @safe pure nothrow @nogc + { + a += G(b, c, d) + x + 0x7a6d76e9UL; + a = rotateLeft(a, s) + e; + c = rotateLeft(c, 10); + } + + static void HHH(ref uint a, uint b, ref uint c, uint d, uint e, uint x, uint s) + @safe pure nothrow @nogc + { + a += H(b, c, d) + x + 0x6d703ef3UL; + a = rotateLeft(a, s) + e; + c = rotateLeft(c, 10); + } + + static void III(ref uint a, uint b, ref uint c, uint d, uint e, uint x, uint s) + @safe pure nothrow @nogc + { + a += I(b, c, d) + x + 0x5c4dd124UL; + a = rotateLeft(a, s) + e; + c = rotateLeft(c, 10); + } + + static void JJJ(ref uint a, uint b, ref uint c, uint d, uint e, uint x, uint s) + @safe pure nothrow @nogc + { + a += J(b, c, d) + x + 0x50a28be6UL; + a = rotateLeft(a, s) + e; + c = rotateLeft(c, 10); + } + + /* + * RIPEMD160 basic transformation. Transforms state based on block. + */ + + private void transform(const(ubyte[64])* block) + pure nothrow @nogc + { + uint aa = _state[0], + bb = _state[1], + cc = _state[2], + dd = _state[3], + ee = _state[4]; + uint aaa = _state[0], + bbb = _state[1], + ccc = _state[2], + ddd = _state[3], + eee = _state[4]; + + uint[16] x = void; + + version (BigEndian) + { + import std.bitmanip : littleEndianToNative; + + for (size_t i = 0; i < 16; i++) + { + x[i] = littleEndianToNative!uint(*cast(ubyte[4]*)&(*block)[i*4]); + } + } + else + { + (cast(ubyte*) x.ptr)[0 .. 64] = (cast(ubyte*) block)[0 .. 64]; + } + + /* round 1 */ + FF(aa, bb, cc, dd, ee, x[ 0], 11); + FF(ee, aa, bb, cc, dd, x[ 1], 14); + FF(dd, ee, aa, bb, cc, x[ 2], 15); + FF(cc, dd, ee, aa, bb, x[ 3], 12); + FF(bb, cc, dd, ee, aa, x[ 4], 5); + FF(aa, bb, cc, dd, ee, x[ 5], 8); + FF(ee, aa, bb, cc, dd, x[ 6], 7); + FF(dd, ee, aa, bb, cc, x[ 7], 9); + FF(cc, dd, ee, aa, bb, x[ 8], 11); + FF(bb, cc, dd, ee, aa, x[ 9], 13); + FF(aa, bb, cc, dd, ee, x[10], 14); + FF(ee, aa, bb, cc, dd, x[11], 15); + FF(dd, ee, aa, bb, cc, x[12], 6); + FF(cc, dd, ee, aa, bb, x[13], 7); + FF(bb, cc, dd, ee, aa, x[14], 9); + FF(aa, bb, cc, dd, ee, x[15], 8); + + /* round 2 */ + GG(ee, aa, bb, cc, dd, x[ 7], 7); + GG(dd, ee, aa, bb, cc, x[ 4], 6); + GG(cc, dd, ee, aa, bb, x[13], 8); + GG(bb, cc, dd, ee, aa, x[ 1], 13); + GG(aa, bb, cc, dd, ee, x[10], 11); + GG(ee, aa, bb, cc, dd, x[ 6], 9); + GG(dd, ee, aa, bb, cc, x[15], 7); + GG(cc, dd, ee, aa, bb, x[ 3], 15); + GG(bb, cc, dd, ee, aa, x[12], 7); + GG(aa, bb, cc, dd, ee, x[ 0], 12); + GG(ee, aa, bb, cc, dd, x[ 9], 15); + GG(dd, ee, aa, bb, cc, x[ 5], 9); + GG(cc, dd, ee, aa, bb, x[ 2], 11); + GG(bb, cc, dd, ee, aa, x[14], 7); + GG(aa, bb, cc, dd, ee, x[11], 13); + GG(ee, aa, bb, cc, dd, x[ 8], 12); + + /* round 3 */ + HH(dd, ee, aa, bb, cc, x[ 3], 11); + HH(cc, dd, ee, aa, bb, x[10], 13); + HH(bb, cc, dd, ee, aa, x[14], 6); + HH(aa, bb, cc, dd, ee, x[ 4], 7); + HH(ee, aa, bb, cc, dd, x[ 9], 14); + HH(dd, ee, aa, bb, cc, x[15], 9); + HH(cc, dd, ee, aa, bb, x[ 8], 13); + HH(bb, cc, dd, ee, aa, x[ 1], 15); + HH(aa, bb, cc, dd, ee, x[ 2], 14); + HH(ee, aa, bb, cc, dd, x[ 7], 8); + HH(dd, ee, aa, bb, cc, x[ 0], 13); + HH(cc, dd, ee, aa, bb, x[ 6], 6); + HH(bb, cc, dd, ee, aa, x[13], 5); + HH(aa, bb, cc, dd, ee, x[11], 12); + HH(ee, aa, bb, cc, dd, x[ 5], 7); + HH(dd, ee, aa, bb, cc, x[12], 5); + + /* round 4 */ + II(cc, dd, ee, aa, bb, x[ 1], 11); + II(bb, cc, dd, ee, aa, x[ 9], 12); + II(aa, bb, cc, dd, ee, x[11], 14); + II(ee, aa, bb, cc, dd, x[10], 15); + II(dd, ee, aa, bb, cc, x[ 0], 14); + II(cc, dd, ee, aa, bb, x[ 8], 15); + II(bb, cc, dd, ee, aa, x[12], 9); + II(aa, bb, cc, dd, ee, x[ 4], 8); + II(ee, aa, bb, cc, dd, x[13], 9); + II(dd, ee, aa, bb, cc, x[ 3], 14); + II(cc, dd, ee, aa, bb, x[ 7], 5); + II(bb, cc, dd, ee, aa, x[15], 6); + II(aa, bb, cc, dd, ee, x[14], 8); + II(ee, aa, bb, cc, dd, x[ 5], 6); + II(dd, ee, aa, bb, cc, x[ 6], 5); + II(cc, dd, ee, aa, bb, x[ 2], 12); + + /* round 5 */ + JJ(bb, cc, dd, ee, aa, x[ 4], 9); + JJ(aa, bb, cc, dd, ee, x[ 0], 15); + JJ(ee, aa, bb, cc, dd, x[ 5], 5); + JJ(dd, ee, aa, bb, cc, x[ 9], 11); + JJ(cc, dd, ee, aa, bb, x[ 7], 6); + JJ(bb, cc, dd, ee, aa, x[12], 8); + JJ(aa, bb, cc, dd, ee, x[ 2], 13); + JJ(ee, aa, bb, cc, dd, x[10], 12); + JJ(dd, ee, aa, bb, cc, x[14], 5); + JJ(cc, dd, ee, aa, bb, x[ 1], 12); + JJ(bb, cc, dd, ee, aa, x[ 3], 13); + JJ(aa, bb, cc, dd, ee, x[ 8], 14); + JJ(ee, aa, bb, cc, dd, x[11], 11); + JJ(dd, ee, aa, bb, cc, x[ 6], 8); + JJ(cc, dd, ee, aa, bb, x[15], 5); + JJ(bb, cc, dd, ee, aa, x[13], 6); + + /* parallel round 1 */ + JJJ(aaa, bbb, ccc, ddd, eee, x[ 5], 8); + JJJ(eee, aaa, bbb, ccc, ddd, x[14], 9); + JJJ(ddd, eee, aaa, bbb, ccc, x[ 7], 9); + JJJ(ccc, ddd, eee, aaa, bbb, x[ 0], 11); + JJJ(bbb, ccc, ddd, eee, aaa, x[ 9], 13); + JJJ(aaa, bbb, ccc, ddd, eee, x[ 2], 15); + JJJ(eee, aaa, bbb, ccc, ddd, x[11], 15); + JJJ(ddd, eee, aaa, bbb, ccc, x[ 4], 5); + JJJ(ccc, ddd, eee, aaa, bbb, x[13], 7); + JJJ(bbb, ccc, ddd, eee, aaa, x[ 6], 7); + JJJ(aaa, bbb, ccc, ddd, eee, x[15], 8); + JJJ(eee, aaa, bbb, ccc, ddd, x[ 8], 11); + JJJ(ddd, eee, aaa, bbb, ccc, x[ 1], 14); + JJJ(ccc, ddd, eee, aaa, bbb, x[10], 14); + JJJ(bbb, ccc, ddd, eee, aaa, x[ 3], 12); + JJJ(aaa, bbb, ccc, ddd, eee, x[12], 6); + + /* parallel round 2 */ + III(eee, aaa, bbb, ccc, ddd, x[ 6], 9); + III(ddd, eee, aaa, bbb, ccc, x[11], 13); + III(ccc, ddd, eee, aaa, bbb, x[ 3], 15); + III(bbb, ccc, ddd, eee, aaa, x[ 7], 7); + III(aaa, bbb, ccc, ddd, eee, x[ 0], 12); + III(eee, aaa, bbb, ccc, ddd, x[13], 8); + III(ddd, eee, aaa, bbb, ccc, x[ 5], 9); + III(ccc, ddd, eee, aaa, bbb, x[10], 11); + III(bbb, ccc, ddd, eee, aaa, x[14], 7); + III(aaa, bbb, ccc, ddd, eee, x[15], 7); + III(eee, aaa, bbb, ccc, ddd, x[ 8], 12); + III(ddd, eee, aaa, bbb, ccc, x[12], 7); + III(ccc, ddd, eee, aaa, bbb, x[ 4], 6); + III(bbb, ccc, ddd, eee, aaa, x[ 9], 15); + III(aaa, bbb, ccc, ddd, eee, x[ 1], 13); + III(eee, aaa, bbb, ccc, ddd, x[ 2], 11); + + /* parallel round 3 */ + HHH(ddd, eee, aaa, bbb, ccc, x[15], 9); + HHH(ccc, ddd, eee, aaa, bbb, x[ 5], 7); + HHH(bbb, ccc, ddd, eee, aaa, x[ 1], 15); + HHH(aaa, bbb, ccc, ddd, eee, x[ 3], 11); + HHH(eee, aaa, bbb, ccc, ddd, x[ 7], 8); + HHH(ddd, eee, aaa, bbb, ccc, x[14], 6); + HHH(ccc, ddd, eee, aaa, bbb, x[ 6], 6); + HHH(bbb, ccc, ddd, eee, aaa, x[ 9], 14); + HHH(aaa, bbb, ccc, ddd, eee, x[11], 12); + HHH(eee, aaa, bbb, ccc, ddd, x[ 8], 13); + HHH(ddd, eee, aaa, bbb, ccc, x[12], 5); + HHH(ccc, ddd, eee, aaa, bbb, x[ 2], 14); + HHH(bbb, ccc, ddd, eee, aaa, x[10], 13); + HHH(aaa, bbb, ccc, ddd, eee, x[ 0], 13); + HHH(eee, aaa, bbb, ccc, ddd, x[ 4], 7); + HHH(ddd, eee, aaa, bbb, ccc, x[13], 5); + + /* parallel round 4 */ + GGG(ccc, ddd, eee, aaa, bbb, x[ 8], 15); + GGG(bbb, ccc, ddd, eee, aaa, x[ 6], 5); + GGG(aaa, bbb, ccc, ddd, eee, x[ 4], 8); + GGG(eee, aaa, bbb, ccc, ddd, x[ 1], 11); + GGG(ddd, eee, aaa, bbb, ccc, x[ 3], 14); + GGG(ccc, ddd, eee, aaa, bbb, x[11], 14); + GGG(bbb, ccc, ddd, eee, aaa, x[15], 6); + GGG(aaa, bbb, ccc, ddd, eee, x[ 0], 14); + GGG(eee, aaa, bbb, ccc, ddd, x[ 5], 6); + GGG(ddd, eee, aaa, bbb, ccc, x[12], 9); + GGG(ccc, ddd, eee, aaa, bbb, x[ 2], 12); + GGG(bbb, ccc, ddd, eee, aaa, x[13], 9); + GGG(aaa, bbb, ccc, ddd, eee, x[ 9], 12); + GGG(eee, aaa, bbb, ccc, ddd, x[ 7], 5); + GGG(ddd, eee, aaa, bbb, ccc, x[10], 15); + GGG(ccc, ddd, eee, aaa, bbb, x[14], 8); + + /* parallel round 5 */ + FFF(bbb, ccc, ddd, eee, aaa, x[12] , 8); + FFF(aaa, bbb, ccc, ddd, eee, x[15] , 5); + FFF(eee, aaa, bbb, ccc, ddd, x[10] , 12); + FFF(ddd, eee, aaa, bbb, ccc, x[ 4] , 9); + FFF(ccc, ddd, eee, aaa, bbb, x[ 1] , 12); + FFF(bbb, ccc, ddd, eee, aaa, x[ 5] , 5); + FFF(aaa, bbb, ccc, ddd, eee, x[ 8] , 14); + FFF(eee, aaa, bbb, ccc, ddd, x[ 7] , 6); + FFF(ddd, eee, aaa, bbb, ccc, x[ 6] , 8); + FFF(ccc, ddd, eee, aaa, bbb, x[ 2] , 13); + FFF(bbb, ccc, ddd, eee, aaa, x[13] , 6); + FFF(aaa, bbb, ccc, ddd, eee, x[14] , 5); + FFF(eee, aaa, bbb, ccc, ddd, x[ 0] , 15); + FFF(ddd, eee, aaa, bbb, ccc, x[ 3] , 13); + FFF(ccc, ddd, eee, aaa, bbb, x[ 9] , 11); + FFF(bbb, ccc, ddd, eee, aaa, x[11] , 11); + + /* combine results */ + ddd += cc + _state[1]; /* final result for _state[0] */ + _state[1] = _state[2] + dd + eee; + _state[2] = _state[3] + ee + aaa; + _state[3] = _state[4] + aa + bbb; + _state[4] = _state[0] + bb + ccc; + _state[0] = ddd; + + //Zeroize sensitive information. + x[] = 0; + } + + public: + enum blockSize = 512; + + /** + * Use this to feed the digest with data. + * Also implements the $(REF isOutputRange, std,range,primitives) + * interface for $(D ubyte) and $(D const(ubyte)[]). + * + * Example: + * ---- + * RIPEMD160 dig; + * dig.put(cast(ubyte) 0); //single ubyte + * dig.put(cast(ubyte) 0, cast(ubyte) 0); //variadic + * ubyte[10] buf; + * dig.put(buf); //buffer + * ---- + */ + void put(scope const(ubyte)[] data...) @trusted pure nothrow @nogc + { + uint i, index, partLen; + auto inputLen = data.length; + + //Compute number of bytes mod 64 + index = (cast(uint)_count >> 3) & (64 - 1); + + //Update number of bits + _count += inputLen * 8; + + partLen = 64 - index; + + //Transform as many times as possible + if (inputLen >= partLen) + { + (&_buffer[index])[0 .. partLen] = data.ptr[0 .. partLen]; + transform(&_buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + { + transform(cast(const(ubyte[64])*)(data[i .. i + 64].ptr)); + } + + index = 0; + } + else + { + i = 0; + } + + /* Buffer remaining input */ + if (inputLen - i) + (&_buffer[index])[0 .. inputLen-i] = (&data[i])[0 .. inputLen-i]; + } + + /** + * Used to (re)initialize the RIPEMD160 digest. + * + * Note: + * For this RIPEMD160 Digest implementation calling start after default construction + * is not necessary. Calling start is only necessary to reset the Digest. + * + * Generic code which deals with different Digest types should always call start though. + * + * Example: + * -------- + * RIPEMD160 digest; + * //digest.start(); //Not necessary + * digest.put(0); + * -------- + */ + void start() @safe pure nothrow @nogc + { + this = RIPEMD160.init; + } + + /** + * Returns the finished RIPEMD160 hash. This also calls $(LREF start) to + * reset the internal state. + * + * Example: + * -------- + * //Simple example + * RIPEMD160 hash; + * hash.start(); + * hash.put(cast(ubyte) 0); + * ubyte[20] result = hash.finish(); + * assert(toHexString(result) == "C81B94933420221A7AC004A90242D8B1D3E5070D"); + * -------- + */ + ubyte[20] finish() @trusted pure nothrow @nogc + { + import std.bitmanip : nativeToLittleEndian; + + ubyte[20] data = void; + ubyte[8] bits = void; + uint index, padLen; + + //Save number of bits + bits[0 .. 8] = nativeToLittleEndian(_count)[]; + + //Pad out to 56 mod 64 + index = (cast(uint)_count >> 3) & (64 - 1); + padLen = (index < 56) ? (56 - index) : (120 - index); + put(_padding[0 .. padLen]); + + //Append length (before padding) + put(bits); + + //Store state in digest + data[0 .. 4] = nativeToLittleEndian(_state[0])[]; + data[4 .. 8] = nativeToLittleEndian(_state[1])[]; + data[8 .. 12] = nativeToLittleEndian(_state[2])[]; + data[12 .. 16] = nativeToLittleEndian(_state[3])[]; + data[16 .. 20] = nativeToLittleEndian(_state[4])[]; + + /* Zeroize sensitive information. */ + start(); + return data; + } +} + +/// +@safe unittest +{ + //Simple example, hashing a string using ripemd160Of helper function + ubyte[20] hash = ripemd160Of("abc"); + //Let's get a hash string + assert(toHexString(hash) == "8EB208F7E05D987A9B044A8E98C6B087F15A0BFC"); +} + +/// +@safe unittest +{ + //Using the basic API + RIPEMD160 hash; + hash.start(); + ubyte[1024] data; + //Initialize data here... + hash.put(data); + ubyte[20] result = hash.finish(); +} + +/// +@safe unittest +{ + //Let's use the template features: + void doSomething(T)(ref T hash) + if (isDigest!T) + { + hash.put(cast(ubyte) 0); + } + RIPEMD160 md; + md.start(); + doSomething(md); + assert(toHexString(md.finish()) == "C81B94933420221A7AC004A90242D8B1D3E5070D"); +} + +/// +@safe unittest +{ + //Simple example + RIPEMD160 hash; + hash.start(); + hash.put(cast(ubyte) 0); + ubyte[20] result = hash.finish(); + assert(toHexString(result) == "C81B94933420221A7AC004A90242D8B1D3E5070D"); +} + +@safe unittest +{ + assert(isDigest!RIPEMD160); +} + +@system unittest +{ + import std.range; + + ubyte[20] digest; + + RIPEMD160 md; + md.put(cast(ubyte[])"abcdef"); + md.start(); + md.put(cast(ubyte[])""); + assert(md.finish() == cast(ubyte[]) x"9c1185a5c5e9fc54612808977ee8f548b2258d31"); + + digest = ripemd160Of(""); + assert(digest == cast(ubyte[]) x"9c1185a5c5e9fc54612808977ee8f548b2258d31"); + + digest = ripemd160Of("a"); + assert(digest == cast(ubyte[]) x"0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"); + + digest = ripemd160Of("abc"); + assert(digest == cast(ubyte[]) x"8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"); + + digest = ripemd160Of("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + assert(digest == cast(ubyte[]) x"12a053384a9c0c88e405a06c27dcf49ada62eb2b"); + + digest = ripemd160Of("message digest"); + assert(digest == cast(ubyte[]) x"5d0689ef49d2fae572b881b123a85ffa21595f36"); + + digest = ripemd160Of("abcdefghijklmnopqrstuvwxyz"); + assert(digest == cast(ubyte[]) x"f71c27109c692c1b56bbdceb5b9d2865b3708dbc"); + + digest = ripemd160Of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + assert(digest == cast(ubyte[]) x"b0e20b6e3116640286ed3a87a5713079b21f5189"); + + digest = ripemd160Of("1234567890123456789012345678901234567890"~ + "1234567890123456789012345678901234567890"); + assert(digest == cast(ubyte[]) x"9b752e45573d4b39f4dbd3323cab82bf63326bfb"); + + assert(toHexString(cast(ubyte[20]) x"f71c27109c692c1b56bbdceb5b9d2865b3708dbc") + == "F71C27109C692C1B56BBDCEB5B9D2865B3708DBC"); + + ubyte[] onemilliona = new ubyte[1000000]; + onemilliona[] = 'a'; + digest = ripemd160Of(onemilliona); + assert(digest == cast(ubyte[]) x"52783243c1697bdbe16d37f97f68f08325dc1528"); + + auto oneMillionRange = repeat!ubyte(cast(ubyte)'a', 1000000); + digest = ripemd160Of(oneMillionRange); + assert(digest == cast(ubyte[]) x"52783243c1697bdbe16d37f97f68f08325dc1528"); +} + +/** + * This is a convenience alias for $(REF digest, std,digest) using the + * RIPEMD160 implementation. + */ +//simple alias doesn't work here, hope this gets inlined... +auto ripemd160Of(T...)(T data) +{ + return digest!(RIPEMD160, T)(data); +} + +/// +@safe unittest +{ + ubyte[20] hash = ripemd160Of("abc"); + assert(hash == digest!RIPEMD160("abc")); +} + +/** + * OOP API RIPEMD160 implementation. + * See $(D std.digest) for differences between template and OOP API. + * + * This is an alias for $(D $(REF WrapperDigest, std,digest)!RIPEMD160), + * see there for more information. + */ +alias RIPEMD160Digest = WrapperDigest!RIPEMD160; + +/// +@safe unittest +{ + //Simple example, hashing a string using Digest.digest helper function + auto md = new RIPEMD160Digest(); + ubyte[] hash = md.digest("abc"); + //Let's get a hash string + assert(toHexString(hash) == "8EB208F7E05D987A9B044A8E98C6B087F15A0BFC"); +} + +/// +@system unittest +{ + //Let's use the OOP features: + void test(Digest dig) + { + dig.put(cast(ubyte) 0); + } + auto md = new RIPEMD160Digest(); + test(md); + + //Let's use a custom buffer: + ubyte[20] buf; + ubyte[] result = md.finish(buf[]); + assert(toHexString(result) == "C81B94933420221A7AC004A90242D8B1D3E5070D"); +} + +@system unittest +{ + auto md = new RIPEMD160Digest(); + + md.put(cast(ubyte[])"abcdef"); + md.reset(); + md.put(cast(ubyte[])""); + assert(md.finish() == cast(ubyte[]) x"9c1185a5c5e9fc54612808977ee8f548b2258d31"); + + md.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz"); + ubyte[20] result; + auto result2 = md.finish(result[]); + assert(result[0 .. 20] == result2 && result2 == cast(ubyte[]) x"f71c27109c692c1b56bbdceb5b9d2865b3708dbc"); + + debug + { + import std.exception; + assertThrown!Error(md.finish(result[0 .. 19])); + } + + assert(md.length == 20); + + assert(md.digest("") == cast(ubyte[]) x"9c1185a5c5e9fc54612808977ee8f548b2258d31"); + + assert(md.digest("a") == cast(ubyte[]) x"0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"); + + assert(md.digest("abc") == cast(ubyte[]) x"8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"); + + assert(md.digest("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") + == cast(ubyte[]) x"12a053384a9c0c88e405a06c27dcf49ada62eb2b"); + + assert(md.digest("message digest") == cast(ubyte[]) x"5d0689ef49d2fae572b881b123a85ffa21595f36"); + + assert(md.digest("abcdefghijklmnopqrstuvwxyz") + == cast(ubyte[]) x"f71c27109c692c1b56bbdceb5b9d2865b3708dbc"); + + assert(md.digest("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") + == cast(ubyte[]) x"b0e20b6e3116640286ed3a87a5713079b21f5189"); + + assert(md.digest("1234567890123456789012345678901234567890", + "1234567890123456789012345678901234567890") + == cast(ubyte[]) x"9b752e45573d4b39f4dbd3323cab82bf63326bfb"); + + assert(md.digest(new ubyte[160/8]) // 160 zero bits + == cast(ubyte[]) x"5c00bd4aca04a9057c09b20b05f723f2e23deb65"); +} diff --git a/libphobos/src/std/digest/sha.d b/libphobos/src/std/digest/sha.d new file mode 100644 index 0000000..671e07b --- /dev/null +++ b/libphobos/src/std/digest/sha.d @@ -0,0 +1,1291 @@ +// Written in the D programming language. +/** + * Computes SHA1 and SHA2 hashes of arbitrary data. SHA hashes are 20 to 64 byte + * quantities (depending on the SHA algorithm) that are like a checksum or CRC, + * but are more robust. + * +$(SCRIPT inhibitQuickIndex = 1;) + +$(DIVC quickindex, +$(BOOKTABLE , +$(TR $(TH Category) $(TH Functions) +) +$(TR $(TDNW Template API) $(TD $(MYREF SHA1) +) +) +$(TR $(TDNW OOP API) $(TD $(MYREF SHA1Digest)) +) +$(TR $(TDNW Helpers) $(TD $(MYREF sha1Of)) +) +) +) + + * SHA2 comes in several different versions, all supported by this module: + * SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224 and SHA-512/256. + * + * This module conforms to the APIs defined in $(MREF std, digest). To understand the + * differences between the template and the OOP API, see $(MREF std, digest). + * + * This module publicly imports $(D std.digest) and can be used as a stand-alone + * module. + * + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * + * CTFE: + * Digests do not work in CTFE + * + * Authors: + * The routines and algorithms are derived from the + * $(I Secure Hash Signature Standard (SHS) (FIPS PUB 180-2)). $(BR ) + * Kai Nacke, Johannes Pfau, Nick Sabalausky + * + * References: + * $(UL + * $(LI $(LINK2 http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf, FIPS PUB180-2)) + * $(LI $(LINK2 http://software.intel.com/en-us/articles/improving-the-performance-of-the-secure-hash-algorithm-1/, Fast implementation of SHA1)) + * $(LI $(LINK2 http://en.wikipedia.org/wiki/Secure_Hash_Algorithm, Wikipedia article about SHA)) + * ) + * + * Source: $(PHOBOSSRC std/digest/_sha.d) + * + */ + +/* Copyright Kai Nacke 2012. + * 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) + */ +module std.digest.sha; + +/// +@safe unittest +{ + //Template API + import std.digest.sha; + + ubyte[20] hash1 = sha1Of("abc"); + assert(toHexString(hash1) == "A9993E364706816ABA3E25717850C26C9CD0D89D"); + + ubyte[28] hash224 = sha224Of("abc"); + assert(toHexString(hash224) == "23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7E36C9DA7"); + + //Feeding data + ubyte[1024] data; + SHA1 sha1; + sha1.start(); + sha1.put(data[]); + sha1.start(); //Start again + sha1.put(data[]); + hash1 = sha1.finish(); +} + +/// +@safe unittest +{ + //OOP API + import std.digest.sha; + + auto sha1 = new SHA1Digest(); + ubyte[] hash1 = sha1.digest("abc"); + assert(toHexString(hash1) == "A9993E364706816ABA3E25717850C26C9CD0D89D"); + + auto sha224 = new SHA224Digest(); + ubyte[] hash224 = sha224.digest("abc"); + assert(toHexString(hash224) == "23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7E36C9DA7"); + + //Feeding data + ubyte[1024] data; + sha1.put(data[]); + sha1.reset(); //Start again + sha1.put(data[]); + hash1 = sha1.finish(); +} + +version (Win64) +{ + // wrong calling convention +} +else version (D_InlineAsm_X86) +{ + version (D_PIC) {} // Bugzilla 9378 + else private version = USE_SSSE3; +} +else version (D_InlineAsm_X86_64) +{ + private version = USE_SSSE3; +} + +version (LittleEndian) import core.bitop : bswap; + + +version (unittest) +{ + import std.exception; +} + + +public import std.digest; + +/* + * Helper methods for encoding the buffer. + * Can be removed if the optimizer can inline the methods from std.bitmanip. + */ +private ubyte[8] nativeToBigEndian(ulong val) @trusted pure nothrow @nogc +{ + version (LittleEndian) + immutable ulong res = (cast(ulong) bswap(cast(uint) val)) << 32 | bswap(cast(uint) (val >> 32)); + else + immutable ulong res = val; + return *cast(ubyte[8]*) &res; +} + +private ubyte[4] nativeToBigEndian(uint val) @trusted pure nothrow @nogc +{ + version (LittleEndian) + immutable uint res = bswap(val); + else + immutable uint res = val; + return *cast(ubyte[4]*) &res; +} + +private ulong bigEndianToNative(ubyte[8] val) @trusted pure nothrow @nogc +{ + version (LittleEndian) + { + import std.bitmanip : bigEndianToNative; + return bigEndianToNative!ulong(val); + } + else + return *cast(ulong*) &val; +} + +private uint bigEndianToNative(ubyte[4] val) @trusted pure nothrow @nogc +{ + version (LittleEndian) + return bswap(*cast(uint*) &val); + else + return *cast(uint*) &val; +} + +//rotateLeft rotates x left n bits +private uint rotateLeft(uint x, uint n) @safe pure nothrow @nogc +{ + // With recently added optimization to DMD (commit 32ea0206 at 07/28/11), this is translated to rol. + // No assembler required. + return (x << n) | (x >> (32-n)); +} + +//rotateRight rotates x right n bits +private uint rotateRight(uint x, uint n) @safe pure nothrow @nogc +{ + return (x >> n) | (x << (32-n)); +} +private ulong rotateRight(ulong x, uint n) @safe pure nothrow @nogc +{ + return (x >> n) | (x << (64-n)); +} + +/** + * Template API SHA1/SHA2 implementation. Supports: SHA-1, SHA-224, SHA-256, + * SHA-384, SHA-512, SHA-512/224 and SHA-512/256. + * + * The hashBlockSize and digestSize are in bits. However, it's likely easier to + * simply use the convenience aliases: SHA1, SHA224, SHA256, SHA384, SHA512, + * SHA512_224 and SHA512_256. + * + * See $(D std.digest) for differences between template and OOP API. + */ +struct SHA(uint hashBlockSize, uint digestSize) +{ + enum blockSize = hashBlockSize; + + static assert(blockSize == 512 || blockSize == 1024, + "Invalid SHA blockSize, must be 512 or 1024"); + static assert(digestSize == 160 || digestSize == 224 || digestSize == 256 || digestSize == 384 || digestSize == 512, + "Invalid SHA digestSize, must be 224, 256, 384 or 512"); + static assert(!(blockSize == 512 && digestSize > 256), + "Invalid SHA digestSize for a blockSize of 512. The digestSize must be 160, 224 or 256."); + static assert(!(blockSize == 1024 && digestSize < 224), + "Invalid SHA digestSize for a blockSize of 1024. The digestSize must be 224, 256, 384 or 512."); + + static if (digestSize == 160) /* SHA-1 */ + { + version (USE_SSSE3) + { + import core.cpuid : ssse3; + import std.internal.digest.sha_SSSE3 : sse3_constants=constants, transformSSSE3; + + static void transform(uint[5]* state, const(ubyte[64])* block) pure nothrow @nogc + { + if (ssse3) + { + version (D_InlineAsm_X86_64) + // constants as extra argument for PIC, see Bugzilla 9378 + transformSSSE3(state, block, &sse3_constants); + else + transformSSSE3(state, block); + } + else + transformX86(state, block); + } + } + else + { + alias transform = transformX86; + } + } + else static if (blockSize == 512) /* SHA-224, SHA-256 */ + alias transform = transformSHA2!uint; + else static if (blockSize == 1024) /* SHA-384, SHA-512, SHA-512/224, SHA-512/256 */ + alias transform = transformSHA2!ulong; + else + static assert(0); + + private: + /* magic initialization constants - state (ABCDEFGH) */ + static if (blockSize == 512 && digestSize == 160) /* SHA-1 */ + { + uint[5] state = + [0x67452301,0xefcdab89,0x98badcfe,0x10325476,0xc3d2e1f0]; + } + else static if (blockSize == 512 && digestSize == 224) /* SHA-224 */ + { + uint[8] state = [ + 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, + 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4, + ]; + } + else static if (blockSize == 512 && digestSize == 256) /* SHA-256 */ + { + uint[8] state = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, + ]; + } + else static if (blockSize == 1024 && digestSize == 224) /* SHA-512/224 */ + { + ulong[8] state = [ + 0x8C3D37C8_19544DA2, 0x73E19966_89DCD4D6, + 0x1DFAB7AE_32FF9C82, 0x679DD514_582F9FCF, + 0x0F6D2B69_7BD44DA8, 0x77E36F73_04C48942, + 0x3F9D85A8_6A1D36C8, 0x1112E6AD_91D692A1, + ]; + } + else static if (blockSize == 1024 && digestSize == 256) /* SHA-512/256 */ + { + ulong[8] state = [ + 0x22312194_FC2BF72C, 0x9F555FA3_C84C64C2, + 0x2393B86B_6F53B151, 0x96387719_5940EABD, + 0x96283EE2_A88EFFE3, 0xBE5E1E25_53863992, + 0x2B0199FC_2C85B8AA, 0x0EB72DDC_81C52CA2, + ]; + } + else static if (blockSize == 1024 && digestSize == 384) /* SHA-384 */ + { + ulong[8] state = [ + 0xcbbb9d5d_c1059ed8, 0x629a292a_367cd507, + 0x9159015a_3070dd17, 0x152fecd8_f70e5939, + 0x67332667_ffc00b31, 0x8eb44a87_68581511, + 0xdb0c2e0d_64f98fa7, 0x47b5481d_befa4fa4, + ]; + } + else static if (blockSize == 1024 && digestSize == 512) /* SHA-512 */ + { + ulong[8] state = [ + 0x6a09e667_f3bcc908, 0xbb67ae85_84caa73b, + 0x3c6ef372_fe94f82b, 0xa54ff53a_5f1d36f1, + 0x510e527f_ade682d1, 0x9b05688c_2b3e6c1f, + 0x1f83d9ab_fb41bd6b, 0x5be0cd19_137e2179, + ]; + } + else + static assert(0); + + /* constants */ + static if (blockSize == 512) + { + static immutable uint[64] constants = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + ]; + } + else static if (blockSize == 1024) + { + static immutable ulong[80] constants = [ + 0x428a2f98_d728ae22, 0x71374491_23ef65cd, 0xb5c0fbcf_ec4d3b2f, 0xe9b5dba5_8189dbbc, + 0x3956c25b_f348b538, 0x59f111f1_b605d019, 0x923f82a4_af194f9b, 0xab1c5ed5_da6d8118, + 0xd807aa98_a3030242, 0x12835b01_45706fbe, 0x243185be_4ee4b28c, 0x550c7dc3_d5ffb4e2, + 0x72be5d74_f27b896f, 0x80deb1fe_3b1696b1, 0x9bdc06a7_25c71235, 0xc19bf174_cf692694, + 0xe49b69c1_9ef14ad2, 0xefbe4786_384f25e3, 0x0fc19dc6_8b8cd5b5, 0x240ca1cc_77ac9c65, + 0x2de92c6f_592b0275, 0x4a7484aa_6ea6e483, 0x5cb0a9dc_bd41fbd4, 0x76f988da_831153b5, + 0x983e5152_ee66dfab, 0xa831c66d_2db43210, 0xb00327c8_98fb213f, 0xbf597fc7_beef0ee4, + 0xc6e00bf3_3da88fc2, 0xd5a79147_930aa725, 0x06ca6351_e003826f, 0x14292967_0a0e6e70, + 0x27b70a85_46d22ffc, 0x2e1b2138_5c26c926, 0x4d2c6dfc_5ac42aed, 0x53380d13_9d95b3df, + 0x650a7354_8baf63de, 0x766a0abb_3c77b2a8, 0x81c2c92e_47edaee6, 0x92722c85_1482353b, + 0xa2bfe8a1_4cf10364, 0xa81a664b_bc423001, 0xc24b8b70_d0f89791, 0xc76c51a3_0654be30, + 0xd192e819_d6ef5218, 0xd6990624_5565a910, 0xf40e3585_5771202a, 0x106aa070_32bbd1b8, + 0x19a4c116_b8d2d0c8, 0x1e376c08_5141ab53, 0x2748774c_df8eeb99, 0x34b0bcb5_e19b48a8, + 0x391c0cb3_c5c95a63, 0x4ed8aa4a_e3418acb, 0x5b9cca4f_7763e373, 0x682e6ff3_d6b2b8a3, + 0x748f82ee_5defb2fc, 0x78a5636f_43172f60, 0x84c87814_a1f0ab72, 0x8cc70208_1a6439ec, + 0x90befffa_23631e28, 0xa4506ceb_de82bde9, 0xbef9a3f7_b2c67915, 0xc67178f2_e372532b, + 0xca273ece_ea26619c, 0xd186b8c7_21c0c207, 0xeada7dd6_cde0eb1e, 0xf57d4f7f_ee6ed178, + 0x06f067aa_72176fba, 0x0a637dc5_a2c898a6, 0x113f9804_bef90dae, 0x1b710b35_131c471b, + 0x28db77f5_23047d84, 0x32caab7b_40c72493, 0x3c9ebe0a_15c9bebc, 0x431d67c4_9c100d4c, + 0x4cc5d4be_cb3e42b6, 0x597f299c_fc657e2a, 0x5fcb6fab_3ad6faec, 0x6c44198c_4a475817, + ]; + } + else + static assert(0); + + /* + * number of bits, modulo 2^64 (ulong[1]) or 2^128 (ulong[2]), + * should just use ucent instead of ulong[2] once it's available + */ + ulong[blockSize/512] count; + ubyte[blockSize/8] buffer; /* input buffer */ + + static immutable ubyte[128] padding = + [ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ]; + + /* + * Basic SHA1/SHA2 functions. + */ + static @safe pure nothrow @nogc + { + /* All SHA1/SHA2 */ + T Ch(T)(T x, T y, T z) { return z ^ (x & (y ^ z)); } + T Maj(T)(T x, T y, T z) { return (x & y) | (z & (x ^ y)); } + + /* SHA-1 */ + uint Parity(uint x, uint y, uint z) { return x ^ y ^ z; } + + /* SHA-224, SHA-256 */ + uint BigSigma0(uint x) { return rotateRight(x, 2) ^ rotateRight(x, 13) ^ rotateRight(x, 22); } + uint BigSigma1(uint x) { return rotateRight(x, 6) ^ rotateRight(x, 11) ^ rotateRight(x, 25); } + uint SmSigma0(uint x) { return rotateRight(x, 7) ^ rotateRight(x, 18) ^ x >> 3; } + uint SmSigma1(uint x) { return rotateRight(x, 17) ^ rotateRight(x, 19) ^ x >> 10; } + + /* SHA-384, SHA-512, SHA-512/224, SHA-512/256 */ + ulong BigSigma0(ulong x) { return rotateRight(x, 28) ^ rotateRight(x, 34) ^ rotateRight(x, 39); } + ulong BigSigma1(ulong x) { return rotateRight(x, 14) ^ rotateRight(x, 18) ^ rotateRight(x, 41); } + ulong SmSigma0(ulong x) { return rotateRight(x, 1) ^ rotateRight(x, 8) ^ x >> 7; } + ulong SmSigma1(ulong x) { return rotateRight(x, 19) ^ rotateRight(x, 61) ^ x >> 6; } + } + + /* + * SHA1 basic transformation. Transforms state based on block. + */ + static void T_0_15(int i, const(ubyte[64])* input, ref uint[16] W, uint A, ref uint B, uint C, uint D, + uint E, ref uint T) pure nothrow @nogc + { + uint Wi = W[i] = bigEndianToNative(*cast(ubyte[4]*)&((*input)[i*4])); + T = Ch(B, C, D) + E + rotateLeft(A, 5) + Wi + 0x5a827999; + B = rotateLeft(B, 30); + } + + static void T_16_19(int i, ref uint[16] W, uint A, ref uint B, uint C, uint D, uint E, ref uint T) + pure nothrow @nogc + { + W[i&15] = rotateLeft(W[(i-3)&15] ^ W[(i-8)&15] ^ W[(i-14)&15] ^ W[(i-16)&15], 1); + T = Ch(B, C, D) + E + rotateLeft(A, 5) + W[i&15] + 0x5a827999; + B = rotateLeft(B, 30); + } + + static void T_20_39(int i, ref uint[16] W, uint A, ref uint B, uint C, uint D, uint E, + ref uint T) pure nothrow @nogc + { + W[i&15] = rotateLeft(W[(i-3)&15] ^ W[(i-8)&15] ^ W[(i-14)&15] ^ W[(i-16)&15], 1); + T = Parity(B, C, D) + E + rotateLeft(A, 5) + W[i&15] + 0x6ed9eba1; + B = rotateLeft(B, 30); + } + + static void T_40_59(int i, ref uint[16] W, uint A, ref uint B, uint C, uint D, uint E, + ref uint T) pure nothrow @nogc + { + W[i&15] = rotateLeft(W[(i-3)&15] ^ W[(i-8)&15] ^ W[(i-14)&15] ^ W[(i-16)&15], 1); + T = Maj(B, C, D) + E + rotateLeft(A, 5) + W[i&15] + 0x8f1bbcdc; + B = rotateLeft(B, 30); + } + + static void T_60_79(int i, ref uint[16] W, uint A, ref uint B, uint C, uint D, uint E, + ref uint T) pure nothrow @nogc + { + W[i&15] = rotateLeft(W[(i-3)&15] ^ W[(i-8)&15] ^ W[(i-14)&15] ^ W[(i-16)&15], 1); + T = Parity(B, C, D) + E + rotateLeft(A, 5) + W[i&15] + 0xca62c1d6; + B = rotateLeft(B, 30); + } + + private static void transformX86(uint[5]* state, const(ubyte[64])* block) pure nothrow @nogc + { + uint A, B, C, D, E, T; + uint[16] W = void; + + A = (*state)[0]; + B = (*state)[1]; + C = (*state)[2]; + D = (*state)[3]; + E = (*state)[4]; + + T_0_15 ( 0, block, W, A, B, C, D, E, T); + T_0_15 ( 1, block, W, T, A, B, C, D, E); + T_0_15 ( 2, block, W, E, T, A, B, C, D); + T_0_15 ( 3, block, W, D, E, T, A, B, C); + T_0_15 ( 4, block, W, C, D, E, T, A, B); + T_0_15 ( 5, block, W, B, C, D, E, T, A); + T_0_15 ( 6, block, W, A, B, C, D, E, T); + T_0_15 ( 7, block, W, T, A, B, C, D, E); + T_0_15 ( 8, block, W, E, T, A, B, C, D); + T_0_15 ( 9, block, W, D, E, T, A, B, C); + T_0_15 (10, block, W, C, D, E, T, A, B); + T_0_15 (11, block, W, B, C, D, E, T, A); + T_0_15 (12, block, W, A, B, C, D, E, T); + T_0_15 (13, block, W, T, A, B, C, D, E); + T_0_15 (14, block, W, E, T, A, B, C, D); + T_0_15 (15, block, W, D, E, T, A, B, C); + T_16_19(16, W, C, D, E, T, A, B); + T_16_19(17, W, B, C, D, E, T, A); + T_16_19(18, W, A, B, C, D, E, T); + T_16_19(19, W, T, A, B, C, D, E); + T_20_39(20, W, E, T, A, B, C, D); + T_20_39(21, W, D, E, T, A, B, C); + T_20_39(22, W, C, D, E, T, A, B); + T_20_39(23, W, B, C, D, E, T, A); + T_20_39(24, W, A, B, C, D, E, T); + T_20_39(25, W, T, A, B, C, D, E); + T_20_39(26, W, E, T, A, B, C, D); + T_20_39(27, W, D, E, T, A, B, C); + T_20_39(28, W, C, D, E, T, A, B); + T_20_39(29, W, B, C, D, E, T, A); + T_20_39(30, W, A, B, C, D, E, T); + T_20_39(31, W, T, A, B, C, D, E); + T_20_39(32, W, E, T, A, B, C, D); + T_20_39(33, W, D, E, T, A, B, C); + T_20_39(34, W, C, D, E, T, A, B); + T_20_39(35, W, B, C, D, E, T, A); + T_20_39(36, W, A, B, C, D, E, T); + T_20_39(37, W, T, A, B, C, D, E); + T_20_39(38, W, E, T, A, B, C, D); + T_20_39(39, W, D, E, T, A, B, C); + T_40_59(40, W, C, D, E, T, A, B); + T_40_59(41, W, B, C, D, E, T, A); + T_40_59(42, W, A, B, C, D, E, T); + T_40_59(43, W, T, A, B, C, D, E); + T_40_59(44, W, E, T, A, B, C, D); + T_40_59(45, W, D, E, T, A, B, C); + T_40_59(46, W, C, D, E, T, A, B); + T_40_59(47, W, B, C, D, E, T, A); + T_40_59(48, W, A, B, C, D, E, T); + T_40_59(49, W, T, A, B, C, D, E); + T_40_59(50, W, E, T, A, B, C, D); + T_40_59(51, W, D, E, T, A, B, C); + T_40_59(52, W, C, D, E, T, A, B); + T_40_59(53, W, B, C, D, E, T, A); + T_40_59(54, W, A, B, C, D, E, T); + T_40_59(55, W, T, A, B, C, D, E); + T_40_59(56, W, E, T, A, B, C, D); + T_40_59(57, W, D, E, T, A, B, C); + T_40_59(58, W, C, D, E, T, A, B); + T_40_59(59, W, B, C, D, E, T, A); + T_60_79(60, W, A, B, C, D, E, T); + T_60_79(61, W, T, A, B, C, D, E); + T_60_79(62, W, E, T, A, B, C, D); + T_60_79(63, W, D, E, T, A, B, C); + T_60_79(64, W, C, D, E, T, A, B); + T_60_79(65, W, B, C, D, E, T, A); + T_60_79(66, W, A, B, C, D, E, T); + T_60_79(67, W, T, A, B, C, D, E); + T_60_79(68, W, E, T, A, B, C, D); + T_60_79(69, W, D, E, T, A, B, C); + T_60_79(70, W, C, D, E, T, A, B); + T_60_79(71, W, B, C, D, E, T, A); + T_60_79(72, W, A, B, C, D, E, T); + T_60_79(73, W, T, A, B, C, D, E); + T_60_79(74, W, E, T, A, B, C, D); + T_60_79(75, W, D, E, T, A, B, C); + T_60_79(76, W, C, D, E, T, A, B); + T_60_79(77, W, B, C, D, E, T, A); + T_60_79(78, W, A, B, C, D, E, T); + T_60_79(79, W, T, A, B, C, D, E); + + (*state)[0] += E; + (*state)[1] += T; + (*state)[2] += A; + (*state)[3] += B; + (*state)[4] += C; + + /* Zeroize sensitive information. */ + W[] = 0; + } + + /* + * SHA2 basic transformation. Transforms state based on block. + */ + static void T_SHA2_0_15(Word)(int i, const(ubyte[blockSize/8])* input, ref Word[16] W, + Word A, Word B, Word C, ref Word D, Word E, Word F, Word G, ref Word H, Word K) + pure nothrow @nogc + { + Word Wi = W[i] = bigEndianToNative(*cast(ubyte[Word.sizeof]*)&((*input)[i*Word.sizeof])); + Word T1 = H + BigSigma1(E) + Ch(E, F, G) + K + Wi; + Word T2 = BigSigma0(A) + Maj(A, B, C); + D += T1; + H = T1 + T2; + } + + static void T_SHA2_16_79(Word)(int i, ref Word[16] W, + Word A, Word B, Word C, ref Word D, Word E, Word F, Word G, ref Word H, Word K) + pure nothrow @nogc + { + W[i&15] = SmSigma1(W[(i-2)&15]) + W[(i-7)&15] + SmSigma0(W[(i-15)&15]) + W[i&15]; + Word T1 = H + BigSigma1(E) + Ch(E, F, G) + K + W[i&15]; + Word T2 = BigSigma0(A) + Maj(A, B, C); + D += T1; + H = T1 + T2; + } + + private static void transformSHA2(Word)(Word[8]* state, const(ubyte[blockSize/8])* block) + pure nothrow @nogc + { + Word A, B, C, D, E, F, G, H; + Word[16] W = void; + + A = (*state)[0]; + B = (*state)[1]; + C = (*state)[2]; + D = (*state)[3]; + E = (*state)[4]; + F = (*state)[5]; + G = (*state)[6]; + H = (*state)[7]; + + T_SHA2_0_15!Word ( 0, block, W, A, B, C, D, E, F, G, H, constants[ 0]); + T_SHA2_0_15!Word ( 1, block, W, H, A, B, C, D, E, F, G, constants[ 1]); + T_SHA2_0_15!Word ( 2, block, W, G, H, A, B, C, D, E, F, constants[ 2]); + T_SHA2_0_15!Word ( 3, block, W, F, G, H, A, B, C, D, E, constants[ 3]); + T_SHA2_0_15!Word ( 4, block, W, E, F, G, H, A, B, C, D, constants[ 4]); + T_SHA2_0_15!Word ( 5, block, W, D, E, F, G, H, A, B, C, constants[ 5]); + T_SHA2_0_15!Word ( 6, block, W, C, D, E, F, G, H, A, B, constants[ 6]); + T_SHA2_0_15!Word ( 7, block, W, B, C, D, E, F, G, H, A, constants[ 7]); + T_SHA2_0_15!Word ( 8, block, W, A, B, C, D, E, F, G, H, constants[ 8]); + T_SHA2_0_15!Word ( 9, block, W, H, A, B, C, D, E, F, G, constants[ 9]); + T_SHA2_0_15!Word (10, block, W, G, H, A, B, C, D, E, F, constants[10]); + T_SHA2_0_15!Word (11, block, W, F, G, H, A, B, C, D, E, constants[11]); + T_SHA2_0_15!Word (12, block, W, E, F, G, H, A, B, C, D, constants[12]); + T_SHA2_0_15!Word (13, block, W, D, E, F, G, H, A, B, C, constants[13]); + T_SHA2_0_15!Word (14, block, W, C, D, E, F, G, H, A, B, constants[14]); + T_SHA2_0_15!Word (15, block, W, B, C, D, E, F, G, H, A, constants[15]); + T_SHA2_16_79!Word(16, W, A, B, C, D, E, F, G, H, constants[16]); + T_SHA2_16_79!Word(17, W, H, A, B, C, D, E, F, G, constants[17]); + T_SHA2_16_79!Word(18, W, G, H, A, B, C, D, E, F, constants[18]); + T_SHA2_16_79!Word(19, W, F, G, H, A, B, C, D, E, constants[19]); + T_SHA2_16_79!Word(20, W, E, F, G, H, A, B, C, D, constants[20]); + T_SHA2_16_79!Word(21, W, D, E, F, G, H, A, B, C, constants[21]); + T_SHA2_16_79!Word(22, W, C, D, E, F, G, H, A, B, constants[22]); + T_SHA2_16_79!Word(23, W, B, C, D, E, F, G, H, A, constants[23]); + T_SHA2_16_79!Word(24, W, A, B, C, D, E, F, G, H, constants[24]); + T_SHA2_16_79!Word(25, W, H, A, B, C, D, E, F, G, constants[25]); + T_SHA2_16_79!Word(26, W, G, H, A, B, C, D, E, F, constants[26]); + T_SHA2_16_79!Word(27, W, F, G, H, A, B, C, D, E, constants[27]); + T_SHA2_16_79!Word(28, W, E, F, G, H, A, B, C, D, constants[28]); + T_SHA2_16_79!Word(29, W, D, E, F, G, H, A, B, C, constants[29]); + T_SHA2_16_79!Word(30, W, C, D, E, F, G, H, A, B, constants[30]); + T_SHA2_16_79!Word(31, W, B, C, D, E, F, G, H, A, constants[31]); + T_SHA2_16_79!Word(32, W, A, B, C, D, E, F, G, H, constants[32]); + T_SHA2_16_79!Word(33, W, H, A, B, C, D, E, F, G, constants[33]); + T_SHA2_16_79!Word(34, W, G, H, A, B, C, D, E, F, constants[34]); + T_SHA2_16_79!Word(35, W, F, G, H, A, B, C, D, E, constants[35]); + T_SHA2_16_79!Word(36, W, E, F, G, H, A, B, C, D, constants[36]); + T_SHA2_16_79!Word(37, W, D, E, F, G, H, A, B, C, constants[37]); + T_SHA2_16_79!Word(38, W, C, D, E, F, G, H, A, B, constants[38]); + T_SHA2_16_79!Word(39, W, B, C, D, E, F, G, H, A, constants[39]); + T_SHA2_16_79!Word(40, W, A, B, C, D, E, F, G, H, constants[40]); + T_SHA2_16_79!Word(41, W, H, A, B, C, D, E, F, G, constants[41]); + T_SHA2_16_79!Word(42, W, G, H, A, B, C, D, E, F, constants[42]); + T_SHA2_16_79!Word(43, W, F, G, H, A, B, C, D, E, constants[43]); + T_SHA2_16_79!Word(44, W, E, F, G, H, A, B, C, D, constants[44]); + T_SHA2_16_79!Word(45, W, D, E, F, G, H, A, B, C, constants[45]); + T_SHA2_16_79!Word(46, W, C, D, E, F, G, H, A, B, constants[46]); + T_SHA2_16_79!Word(47, W, B, C, D, E, F, G, H, A, constants[47]); + T_SHA2_16_79!Word(48, W, A, B, C, D, E, F, G, H, constants[48]); + T_SHA2_16_79!Word(49, W, H, A, B, C, D, E, F, G, constants[49]); + T_SHA2_16_79!Word(50, W, G, H, A, B, C, D, E, F, constants[50]); + T_SHA2_16_79!Word(51, W, F, G, H, A, B, C, D, E, constants[51]); + T_SHA2_16_79!Word(52, W, E, F, G, H, A, B, C, D, constants[52]); + T_SHA2_16_79!Word(53, W, D, E, F, G, H, A, B, C, constants[53]); + T_SHA2_16_79!Word(54, W, C, D, E, F, G, H, A, B, constants[54]); + T_SHA2_16_79!Word(55, W, B, C, D, E, F, G, H, A, constants[55]); + T_SHA2_16_79!Word(56, W, A, B, C, D, E, F, G, H, constants[56]); + T_SHA2_16_79!Word(57, W, H, A, B, C, D, E, F, G, constants[57]); + T_SHA2_16_79!Word(58, W, G, H, A, B, C, D, E, F, constants[58]); + T_SHA2_16_79!Word(59, W, F, G, H, A, B, C, D, E, constants[59]); + T_SHA2_16_79!Word(60, W, E, F, G, H, A, B, C, D, constants[60]); + T_SHA2_16_79!Word(61, W, D, E, F, G, H, A, B, C, constants[61]); + T_SHA2_16_79!Word(62, W, C, D, E, F, G, H, A, B, constants[62]); + T_SHA2_16_79!Word(63, W, B, C, D, E, F, G, H, A, constants[63]); + + static if (is(Word == ulong)) + { + T_SHA2_16_79!Word(64, W, A, B, C, D, E, F, G, H, constants[64]); + T_SHA2_16_79!Word(65, W, H, A, B, C, D, E, F, G, constants[65]); + T_SHA2_16_79!Word(66, W, G, H, A, B, C, D, E, F, constants[66]); + T_SHA2_16_79!Word(67, W, F, G, H, A, B, C, D, E, constants[67]); + T_SHA2_16_79!Word(68, W, E, F, G, H, A, B, C, D, constants[68]); + T_SHA2_16_79!Word(69, W, D, E, F, G, H, A, B, C, constants[69]); + T_SHA2_16_79!Word(70, W, C, D, E, F, G, H, A, B, constants[70]); + T_SHA2_16_79!Word(71, W, B, C, D, E, F, G, H, A, constants[71]); + T_SHA2_16_79!Word(72, W, A, B, C, D, E, F, G, H, constants[72]); + T_SHA2_16_79!Word(73, W, H, A, B, C, D, E, F, G, constants[73]); + T_SHA2_16_79!Word(74, W, G, H, A, B, C, D, E, F, constants[74]); + T_SHA2_16_79!Word(75, W, F, G, H, A, B, C, D, E, constants[75]); + T_SHA2_16_79!Word(76, W, E, F, G, H, A, B, C, D, constants[76]); + T_SHA2_16_79!Word(77, W, D, E, F, G, H, A, B, C, constants[77]); + T_SHA2_16_79!Word(78, W, C, D, E, F, G, H, A, B, constants[78]); + T_SHA2_16_79!Word(79, W, B, C, D, E, F, G, H, A, constants[79]); + } + + (*state)[0] += A; + (*state)[1] += B; + (*state)[2] += C; + (*state)[3] += D; + (*state)[4] += E; + (*state)[5] += F; + (*state)[6] += G; + (*state)[7] += H; + + /* Zeroize sensitive information. */ + W[] = 0; + } + + public: + /** + * SHA initialization. Begins an SHA1/SHA2 operation. + * + * Note: + * For this SHA Digest implementation calling start after default construction + * is not necessary. Calling start is only necessary to reset the Digest. + * + * Generic code which deals with different Digest types should always call start though. + * + * Example: + * -------- + * SHA1 digest; + * //digest.start(); //Not necessary + * digest.put(0); + * -------- + */ + void start() @safe pure nothrow @nogc + { + this = typeof(this).init; + } + + /** + * Use this to feed the digest with data. + * Also implements the $(REF isOutputRange, std,range,primitives) + * interface for $(D ubyte) and $(D const(ubyte)[]). + */ + void put(scope const(ubyte)[] input...) @trusted pure nothrow @nogc + { + enum blockSizeInBytes = blockSize/8; + uint i, index, partLen; + auto inputLen = input.length; + + /* Compute number of bytes mod block size (64 or 128 bytes) */ + index = (cast(uint) count[0] >> 3) & (blockSizeInBytes - 1); + + /* Update number of bits */ + static if (blockSize == 512) + count[0] += inputLen * 8; + else static if (blockSize == 1024) + { + /* ugly hack to work around lack of ucent */ + auto oldCount0 = count[0]; + count[0] += inputLen * 8; + if (count[0] < oldCount0) + count[1]++; + } + else + static assert(0); + + partLen = blockSizeInBytes - index; + + /* Transform as many times as possible. */ + if (inputLen >= partLen) + { + (&buffer[index])[0 .. partLen] = input.ptr[0 .. partLen]; + transform (&state, &buffer); + + for (i = partLen; i + blockSizeInBytes-1 < inputLen; i += blockSizeInBytes) + transform(&state, cast(ubyte[blockSizeInBytes]*)(input.ptr + i)); + + index = 0; + } + else + i = 0; + + /* Buffer remaining input */ + if (inputLen - i) + (&buffer[index])[0 .. inputLen-i] = (&input[i])[0 .. inputLen-i]; + } + + @safe unittest + { + typeof(this) dig; + dig.put(cast(ubyte) 0); //single ubyte + dig.put(cast(ubyte) 0, cast(ubyte) 0); //variadic + ubyte[10] buf; + dig.put(buf); //buffer + } + + + /** + * Returns the finished SHA hash. This also calls $(LREF start) to + * reset the internal state. + */ + ubyte[digestSize/8] finish() @trusted pure nothrow @nogc + { + static if (blockSize == 512) + { + ubyte[32] data = void; + uint index, padLen; + + /* Save number of bits */ + ubyte[8] bits = nativeToBigEndian(count[0]); + + /* Pad out to 56 mod 64. */ + index = (cast(uint) count[0] >> 3) & (64 - 1); + padLen = (index < 56) ? (56 - index) : (120 - index); + put(padding[0 .. padLen]); + + /* Append length (before padding) */ + put(bits); + + /* Store state in digest */ + for (auto i = 0; i < ((digestSize == 160)? 5 : 8); i++) + data[i*4..(i+1)*4] = nativeToBigEndian(state[i])[]; + + /* Zeroize sensitive information. */ + start(); + return data[0 .. digestSize/8]; + } + else static if (blockSize == 1024) + { + ubyte[64] data = void; + uint index, padLen; + + /* Save number of bits */ + ubyte[16] bits; + bits[ 0 .. 8] = nativeToBigEndian(count[1]); + bits[8 .. 16] = nativeToBigEndian(count[0]); + + /* Pad out to 112 mod 128. */ + index = (cast(uint) count[0] >> 3) & (128 - 1); + padLen = (index < 112) ? (112 - index) : (240 - index); + put(padding[0 .. padLen]); + + /* Append length (before padding) */ + put(bits); + + /* Store state in digest */ + for (auto i = 0; i < 8; i++) + data[i*8..(i+1)*8] = nativeToBigEndian(state[i])[]; + + /* Zeroize sensitive information. */ + start(); + return data[0 .. digestSize/8]; + } + else + static assert(0); + } + /// + @safe unittest + { + //Simple example + SHA1 hash; + hash.start(); + hash.put(cast(ubyte) 0); + ubyte[20] result = hash.finish(); + } +} + +alias SHA1 = SHA!(512, 160); /// SHA alias for SHA-1, hash is ubyte[20] +alias SHA224 = SHA!(512, 224); /// SHA alias for SHA-224, hash is ubyte[28] +alias SHA256 = SHA!(512, 256); /// SHA alias for SHA-256, hash is ubyte[32] +alias SHA384 = SHA!(1024, 384); /// SHA alias for SHA-384, hash is ubyte[48] +alias SHA512 = SHA!(1024, 512); /// SHA alias for SHA-512, hash is ubyte[64] +alias SHA512_224 = SHA!(1024, 224); /// SHA alias for SHA-512/224, hash is ubyte[28] +alias SHA512_256 = SHA!(1024, 256); /// SHA alias for SHA-512/256, hash is ubyte[32] + +/// +@safe unittest +{ + //Simple example, hashing a string using sha1Of helper function + ubyte[20] hash = sha1Of("abc"); + //Let's get a hash string + assert(toHexString(hash) == "A9993E364706816ABA3E25717850C26C9CD0D89D"); + + //The same, but using SHA-224 + ubyte[28] hash224 = sha224Of("abc"); + assert(toHexString(hash224) == "23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7E36C9DA7"); +} + +/// +@safe unittest +{ + //Using the basic API + SHA1 hash; + hash.start(); + ubyte[1024] data; + //Initialize data here... + hash.put(data); + ubyte[20] result = hash.finish(); +} + +/// +@safe unittest +{ + //Let's use the template features: + //Note: When passing a SHA1 to a function, it must be passed by reference! + void doSomething(T)(ref T hash) + if (isDigest!T) + { + hash.put(cast(ubyte) 0); + } + SHA1 sha; + sha.start(); + doSomething(sha); + assert(toHexString(sha.finish()) == "5BA93C9DB0CFF93F52B521D7420E43F6EDA2784F"); +} + +@safe unittest +{ + assert(isDigest!SHA1); + assert(isDigest!SHA224); + assert(isDigest!SHA256); + assert(isDigest!SHA384); + assert(isDigest!SHA512); + assert(isDigest!SHA512_224); + assert(isDigest!SHA512_256); +} + +@system unittest +{ + import std.conv : hexString; + import std.range; + + ubyte[20] digest; + ubyte[28] digest224; + ubyte[32] digest256; + ubyte[48] digest384; + ubyte[64] digest512; + ubyte[28] digest512_224; + ubyte[32] digest512_256; + + SHA1 sha; + sha.put(cast(ubyte[])"abcdef"); + sha.start(); + sha.put(cast(ubyte[])""); + assert(sha.finish() == cast(ubyte[]) x"da39a3ee5e6b4b0d3255bfef95601890afd80709"); + + SHA224 sha224; + sha224.put(cast(ubyte[])"abcdef"); + sha224.start(); + sha224.put(cast(ubyte[])""); + assert(sha224.finish() == cast(ubyte[]) x"d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f"); + + SHA256 sha256; + sha256.put(cast(ubyte[])"abcdef"); + sha256.start(); + sha256.put(cast(ubyte[])""); + assert(sha256.finish() == cast(ubyte[]) x"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); + + SHA384 sha384; + sha384.put(cast(ubyte[])"abcdef"); + sha384.start(); + sha384.put(cast(ubyte[])""); + assert(sha384.finish() == cast(ubyte[]) hexString!("38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c" + ~"0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b")); + + SHA512 sha512; + sha512.put(cast(ubyte[])"abcdef"); + sha512.start(); + sha512.put(cast(ubyte[])""); + assert(sha512.finish() == cast(ubyte[]) hexString!("cf83e1357eefb8bdf1542850d66d8007d620e4050b571" + ~"5dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")); + + SHA512_224 sha512_224; + sha512_224.put(cast(ubyte[])"abcdef"); + sha512_224.start(); + sha512_224.put(cast(ubyte[])""); + assert(sha512_224.finish() == cast(ubyte[]) x"6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4"); + + SHA512_256 sha512_256; + sha512_256.put(cast(ubyte[])"abcdef"); + sha512_256.start(); + sha512_256.put(cast(ubyte[])""); + assert(sha512_256.finish() == cast(ubyte[]) x"c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a"); + + digest = sha1Of (""); + digest224 = sha224Of (""); + digest256 = sha256Of (""); + digest384 = sha384Of (""); + digest512 = sha512Of (""); + digest512_224 = sha512_224Of(""); + digest512_256 = sha512_256Of(""); + assert(digest == cast(ubyte[]) x"da39a3ee5e6b4b0d3255bfef95601890afd80709"); + assert(digest224 == cast(ubyte[]) x"d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f"); + assert(digest256 == cast(ubyte[]) x"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); + assert(digest384 == cast(ubyte[]) hexString!("38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c" + ~"0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b")); + assert(digest512 == cast(ubyte[]) hexString!("cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83" + ~"f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")); + assert(digest512_224 == cast(ubyte[]) x"6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4"); + assert(digest512_256 == cast(ubyte[]) x"c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a"); + + digest = sha1Of ("a"); + digest224 = sha224Of ("a"); + digest256 = sha256Of ("a"); + digest384 = sha384Of ("a"); + digest512 = sha512Of ("a"); + digest512_224 = sha512_224Of("a"); + digest512_256 = sha512_256Of("a"); + assert(digest == cast(ubyte[]) x"86f7e437faa5a7fce15d1ddcb9eaeaea377667b8"); + assert(digest224 == cast(ubyte[]) x"abd37534c7d9a2efb9465de931cd7055ffdb8879563ae98078d6d6d5"); + assert(digest256 == cast(ubyte[]) x"ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb"); + assert(digest384 == cast(ubyte[]) hexString!("54a59b9f22b0b80880d8427e548b7c23abd873486e1f035dce9" + ~"cd697e85175033caa88e6d57bc35efae0b5afd3145f31")); + assert(digest512 == cast(ubyte[]) hexString!("1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05ab" + ~"c54d0560e0f5302860c652bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75")); + assert(digest512_224 == cast(ubyte[]) x"d5cdb9ccc769a5121d4175f2bfdd13d6310e0d3d361ea75d82108327"); + assert(digest512_256 == cast(ubyte[]) x"455e518824bc0601f9fb858ff5c37d417d67c2f8e0df2babe4808858aea830f8"); + + digest = sha1Of ("abc"); + digest224 = sha224Of ("abc"); + digest256 = sha256Of ("abc"); + digest384 = sha384Of ("abc"); + digest512 = sha512Of ("abc"); + digest512_224 = sha512_224Of("abc"); + digest512_256 = sha512_256Of("abc"); + assert(digest == cast(ubyte[]) x"a9993e364706816aba3e25717850c26c9cd0d89d"); + assert(digest224 == cast(ubyte[]) x"23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7"); + assert(digest256 == cast(ubyte[]) x"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"); + assert(digest384 == cast(ubyte[]) hexString!("cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a" + ~"8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7")); + assert(digest512 == cast(ubyte[]) hexString!("ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9" + ~"eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f")); + assert(digest512_224 == cast(ubyte[]) x"4634270f707b6a54daae7530460842e20e37ed265ceee9a43e8924aa"); + assert(digest512_256 == cast(ubyte[]) x"53048e2681941ef99b2e29b76b4c7dabe4c2d0c634fc6d46e0e2f13107e7af23"); + + digest = sha1Of ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + digest224 = sha224Of ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + digest256 = sha256Of ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + digest384 = sha384Of ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + digest512 = sha512Of ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + digest512_224 = sha512_224Of("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + digest512_256 = sha512_256Of("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + assert(digest == cast(ubyte[]) x"84983e441c3bd26ebaae4aa1f95129e5e54670f1"); + assert(digest224 == cast(ubyte[]) x"75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525"); + assert(digest256 == cast(ubyte[]) x"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"); + assert(digest384 == cast(ubyte[]) hexString!("3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe" + ~"8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b")); + assert(digest512 == cast(ubyte[]) hexString!("204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a827" + ~"9be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445")); + assert(digest512_224 == cast(ubyte[]) x"e5302d6d54bb242275d1e7622d68df6eb02dedd13f564c13dbda2174"); + assert(digest512_256 == cast(ubyte[]) x"bde8e1f9f19bb9fd3406c90ec6bc47bd36d8ada9f11880dbc8a22a7078b6a461"); + + digest = sha1Of ("message digest"); + digest224 = sha224Of ("message digest"); + digest256 = sha256Of ("message digest"); + digest384 = sha384Of ("message digest"); + digest512 = sha512Of ("message digest"); + digest512_224 = sha512_224Of("message digest"); + digest512_256 = sha512_256Of("message digest"); + assert(digest == cast(ubyte[]) x"c12252ceda8be8994d5fa0290a47231c1d16aae3"); + assert(digest224 == cast(ubyte[]) x"2cb21c83ae2f004de7e81c3c7019cbcb65b71ab656b22d6d0c39b8eb"); + assert(digest256 == cast(ubyte[]) x"f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650"); + assert(digest384 == cast(ubyte[]) hexString!("473ed35167ec1f5d8e550368a3db39be54639f828868e9454c" + ~"239fc8b52e3c61dbd0d8b4de1390c256dcbb5d5fd99cd5")); + assert(digest512 == cast(ubyte[]) hexString!("107dbf389d9e9f71a3a95f6c055b9251bc5268c2be16d6c134" + ~"92ea45b0199f3309e16455ab1e96118e8a905d5597b72038ddb372a89826046de66687bb420e7c")); + assert(digest512_224 == cast(ubyte[]) x"ad1a4db188fe57064f4f24609d2a83cd0afb9b398eb2fcaeaae2c564"); + assert(digest512_256 == cast(ubyte[]) x"0cf471fd17ed69d990daf3433c89b16d63dec1bb9cb42a6094604ee5d7b4e9fb"); + + digest = sha1Of ("abcdefghijklmnopqrstuvwxyz"); + digest224 = sha224Of ("abcdefghijklmnopqrstuvwxyz"); + digest256 = sha256Of ("abcdefghijklmnopqrstuvwxyz"); + digest384 = sha384Of ("abcdefghijklmnopqrstuvwxyz"); + digest512 = sha512Of ("abcdefghijklmnopqrstuvwxyz"); + digest512_224 = sha512_224Of("abcdefghijklmnopqrstuvwxyz"); + digest512_256 = sha512_256Of("abcdefghijklmnopqrstuvwxyz"); + assert(digest == cast(ubyte[]) x"32d10c7b8cf96570ca04ce37f2a19d84240d3a89"); + assert(digest224 == cast(ubyte[]) x"45a5f72c39c5cff2522eb3429799e49e5f44b356ef926bcf390dccc2"); + assert(digest256 == cast(ubyte[]) x"71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73"); + assert(digest384 == cast(ubyte[]) hexString!("feb67349df3db6f5924815d6c3dc133f091809213731fe5c7b5" + ~"f4999e463479ff2877f5f2936fa63bb43784b12f3ebb4")); + assert(digest512 == cast(ubyte[]) hexString!("4dbff86cc2ca1bae1e16468a05cb9881c97f1753bce3619034" + ~"898faa1aabe429955a1bf8ec483d7421fe3c1646613a59ed5441fb0f321389f77f48a879c7b1f1")); + assert(digest512_224 == cast(ubyte[]) x"ff83148aa07ec30655c1b40aff86141c0215fe2a54f767d3f38743d8"); + assert(digest512_256 == cast(ubyte[]) x"fc3189443f9c268f626aea08a756abe7b726b05f701cb08222312ccfd6710a26"); + + digest = sha1Of ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + digest224 = sha224Of ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + digest256 = sha256Of ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + digest384 = sha384Of ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + digest512 = sha512Of ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + digest512_224 = sha512_224Of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + digest512_256 = sha512_256Of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + assert(digest == cast(ubyte[]) x"761c457bf73b14d27e9e9265c46f4b4dda11f940"); + assert(digest224 == cast(ubyte[]) x"bff72b4fcb7d75e5632900ac5f90d219e05e97a7bde72e740db393d9"); + assert(digest256 == cast(ubyte[]) x"db4bfcbd4da0cd85a60c3c37d3fbd8805c77f15fc6b1fdfe614ee0a7c8fdb4c0"); + assert(digest384 == cast(ubyte[]) hexString!("1761336e3f7cbfe51deb137f026f89e01a448e3b1fafa64039" + ~"c1464ee8732f11a5341a6f41e0c202294736ed64db1a84")); + assert(digest512 == cast(ubyte[]) hexString!("1e07be23c26a86ea37ea810c8ec7809352515a970e9253c26f" + ~"536cfc7a9996c45c8370583e0a78fa4a90041d71a4ceab7423f19c71b9d5a3e01249f0bebd5894")); + assert(digest512_224 == cast(ubyte[]) x"a8b4b9174b99ffc67d6f49be9981587b96441051e16e6dd036b140d3"); + assert(digest512_256 == cast(ubyte[]) x"cdf1cc0effe26ecc0c13758f7b4a48e000615df241284185c39eb05d355bb9c8"); + + digest = sha1Of ("1234567890123456789012345678901234567890"~ + "1234567890123456789012345678901234567890"); + digest224 = sha224Of ("1234567890123456789012345678901234567890"~ + "1234567890123456789012345678901234567890"); + digest256 = sha256Of ("1234567890123456789012345678901234567890"~ + "1234567890123456789012345678901234567890"); + digest384 = sha384Of ("1234567890123456789012345678901234567890"~ + "1234567890123456789012345678901234567890"); + digest512 = sha512Of ("1234567890123456789012345678901234567890"~ + "1234567890123456789012345678901234567890"); + digest512_224 = sha512_224Of("1234567890123456789012345678901234567890"~ + "1234567890123456789012345678901234567890"); + digest512_256 = sha512_256Of("1234567890123456789012345678901234567890"~ + "1234567890123456789012345678901234567890"); + assert(digest == cast(ubyte[]) x"50abf5706a150990a08b2c5ea40fa0e585554732"); + assert(digest224 == cast(ubyte[]) x"b50aecbe4e9bb0b57bc5f3ae760a8e01db24f203fb3cdcd13148046e"); + assert(digest256 == cast(ubyte[]) x"f371bc4a311f2b009eef952dd83ca80e2b60026c8e935592d0f9c308453c813e"); + assert(digest384 == cast(ubyte[]) hexString!("b12932b0627d1c060942f5447764155655bd4da0c9afa6dd9b" + ~"9ef53129af1b8fb0195996d2de9ca0df9d821ffee67026")); + assert(digest512 == cast(ubyte[]) hexString!("72ec1ef1124a45b047e8b7c75a932195135bb61de24ec0d191" + ~"4042246e0aec3a2354e093d76f3048b456764346900cb130d2a4fd5dd16abb5e30bcb850dee843")); + assert(digest512_224 == cast(ubyte[]) x"ae988faaa47e401a45f704d1272d99702458fea2ddc6582827556dd2"); + assert(digest512_256 == cast(ubyte[]) x"2c9fdbc0c90bdd87612ee8455474f9044850241dc105b1e8b94b8ddf5fac9148"); + + ubyte[] onemilliona = new ubyte[1000000]; + onemilliona[] = 'a'; + digest = sha1Of(onemilliona); + digest224 = sha224Of(onemilliona); + digest256 = sha256Of(onemilliona); + digest384 = sha384Of(onemilliona); + digest512 = sha512Of(onemilliona); + digest512_224 = sha512_224Of(onemilliona); + digest512_256 = sha512_256Of(onemilliona); + assert(digest == cast(ubyte[]) x"34aa973cd4c4daa4f61eeb2bdbad27316534016f"); + assert(digest224 == cast(ubyte[]) x"20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67"); + assert(digest256 == cast(ubyte[]) x"cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0"); + assert(digest384 == cast(ubyte[]) hexString!("9d0e1809716474cb086e834e310a4a1ced149e9c00f2485279" + ~"72cec5704c2a5b07b8b3dc38ecc4ebae97ddd87f3d8985")); + assert(digest512 == cast(ubyte[]) hexString!("e718483d0ce769644e2e42c7bc15b4638e1f98b13b20442856" + ~"32a803afa973ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b")); + assert(digest512_224 == cast(ubyte[]) x"37ab331d76f0d36de422bd0edeb22a28accd487b7a8453ae965dd287"); + assert(digest512_256 == cast(ubyte[]) x"9a59a052930187a97038cae692f30708aa6491923ef5194394dc68d56c74fb21"); + + auto oneMillionRange = repeat!ubyte(cast(ubyte)'a', 1000000); + digest = sha1Of(oneMillionRange); + digest224 = sha224Of(oneMillionRange); + digest256 = sha256Of(oneMillionRange); + digest384 = sha384Of(oneMillionRange); + digest512 = sha512Of(oneMillionRange); + digest512_224 = sha512_224Of(oneMillionRange); + digest512_256 = sha512_256Of(oneMillionRange); + assert(digest == cast(ubyte[]) x"34aa973cd4c4daa4f61eeb2bdbad27316534016f"); + assert(digest224 == cast(ubyte[]) x"20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67"); + assert(digest256 == cast(ubyte[]) x"cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0"); + assert(digest384 == cast(ubyte[]) hexString!("9d0e1809716474cb086e834e310a4a1ced149e9c00f2485279" + ~"72cec5704c2a5b07b8b3dc38ecc4ebae97ddd87f3d8985")); + assert(digest512 == cast(ubyte[]) hexString!("e718483d0ce769644e2e42c7bc15b4638e1f98b13b20442856" + ~"32a803afa973ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b")); + assert(digest512_224 == cast(ubyte[]) x"37ab331d76f0d36de422bd0edeb22a28accd487b7a8453ae965dd287"); + assert(digest512_256 == cast(ubyte[]) x"9a59a052930187a97038cae692f30708aa6491923ef5194394dc68d56c74fb21"); + + assert(toHexString(cast(ubyte[20]) x"a9993e364706816aba3e25717850c26c9cd0d89d") + == "A9993E364706816ABA3E25717850C26C9CD0D89D"); +} + +/** + * These are convenience aliases for $(REF digest, std,digest) using the + * SHA implementation. + */ +//simple alias doesn't work here, hope this gets inlined... +auto sha1Of(T...)(T data) +{ + return digest!(SHA1, T)(data); +} +///ditto +auto sha224Of(T...)(T data) +{ + return digest!(SHA224, T)(data); +} +///ditto +auto sha256Of(T...)(T data) +{ + return digest!(SHA256, T)(data); +} +///ditto +auto sha384Of(T...)(T data) +{ + return digest!(SHA384, T)(data); +} +///ditto +auto sha512Of(T...)(T data) +{ + return digest!(SHA512, T)(data); +} +///ditto +auto sha512_224Of(T...)(T data) +{ + return digest!(SHA512_224, T)(data); +} +///ditto +auto sha512_256Of(T...)(T data) +{ + return digest!(SHA512_256, T)(data); +} + +/// +@safe unittest +{ + ubyte[20] hash = sha1Of("abc"); + assert(hash == digest!SHA1("abc")); + + ubyte[28] hash224 = sha224Of("abc"); + assert(hash224 == digest!SHA224("abc")); + + ubyte[32] hash256 = sha256Of("abc"); + assert(hash256 == digest!SHA256("abc")); + + ubyte[48] hash384 = sha384Of("abc"); + assert(hash384 == digest!SHA384("abc")); + + ubyte[64] hash512 = sha512Of("abc"); + assert(hash512 == digest!SHA512("abc")); + + ubyte[28] hash512_224 = sha512_224Of("abc"); + assert(hash512_224 == digest!SHA512_224("abc")); + + ubyte[32] hash512_256 = sha512_256Of("abc"); + assert(hash512_256 == digest!SHA512_256("abc")); +} + +@safe unittest +{ + string a = "Mary has ", b = "a little lamb"; + int[] c = [ 1, 2, 3, 4, 5 ]; + string d = toHexString(sha1Of(a, b, c)); + version (LittleEndian) + assert(d == "CDBB611D00AC2387B642D3D7BDF4C3B342237110", d); + else + assert(d == "A0F1196C7A379C09390476D9CA4AA11B71FD11C8", d); +} + +/** + * OOP API SHA1 and SHA2 implementations. + * See $(D std.digest) for differences between template and OOP API. + * + * This is an alias for $(D $(REF WrapperDigest, std,digest)!SHA1), see + * there for more information. + */ +alias SHA1Digest = WrapperDigest!SHA1; +alias SHA224Digest = WrapperDigest!SHA224; ///ditto +alias SHA256Digest = WrapperDigest!SHA256; ///ditto +alias SHA384Digest = WrapperDigest!SHA384; ///ditto +alias SHA512Digest = WrapperDigest!SHA512; ///ditto +alias SHA512_224Digest = WrapperDigest!SHA512_224; ///ditto +alias SHA512_256Digest = WrapperDigest!SHA512_256; ///ditto + +/// +@safe unittest +{ + //Simple example, hashing a string using Digest.digest helper function + auto sha = new SHA1Digest(); + ubyte[] hash = sha.digest("abc"); + //Let's get a hash string + assert(toHexString(hash) == "A9993E364706816ABA3E25717850C26C9CD0D89D"); + + //The same, but using SHA-224 + auto sha224 = new SHA224Digest(); + ubyte[] hash224 = sha224.digest("abc"); + //Let's get a hash string + assert(toHexString(hash224) == "23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7E36C9DA7"); +} + +/// +@system unittest +{ + //Let's use the OOP features: + void test(Digest dig) + { + dig.put(cast(ubyte) 0); + } + auto sha = new SHA1Digest(); + test(sha); + + //Let's use a custom buffer: + ubyte[20] buf; + ubyte[] result = sha.finish(buf[]); + assert(toHexString(result) == "5BA93C9DB0CFF93F52B521D7420E43F6EDA2784F"); +} + +@system unittest +{ + auto sha = new SHA1Digest(); + + sha.put(cast(ubyte[])"abcdef"); + sha.reset(); + sha.put(cast(ubyte[])""); + assert(sha.finish() == cast(ubyte[]) x"da39a3ee5e6b4b0d3255bfef95601890afd80709"); + + sha.put(cast(ubyte[])"abcdefghijklmnopqrstuvwxyz"); + ubyte[22] result; + auto result2 = sha.finish(result[]); + assert(result[0 .. 20] == result2 && result2 == cast(ubyte[]) x"32d10c7b8cf96570ca04ce37f2a19d84240d3a89"); + + debug + assertThrown!Error(sha.finish(result[0 .. 15])); + + assert(sha.length == 20); + + assert(sha.digest("") == cast(ubyte[]) x"da39a3ee5e6b4b0d3255bfef95601890afd80709"); + + assert(sha.digest("a") == cast(ubyte[]) x"86f7e437faa5a7fce15d1ddcb9eaeaea377667b8"); + + assert(sha.digest("abc") == cast(ubyte[]) x"a9993e364706816aba3e25717850c26c9cd0d89d"); + + assert(sha.digest("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") + == cast(ubyte[]) x"84983e441c3bd26ebaae4aa1f95129e5e54670f1"); + + assert(sha.digest("message digest") == cast(ubyte[]) x"c12252ceda8be8994d5fa0290a47231c1d16aae3"); + + assert(sha.digest("abcdefghijklmnopqrstuvwxyz") + == cast(ubyte[]) x"32d10c7b8cf96570ca04ce37f2a19d84240d3a89"); + + assert(sha.digest("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") + == cast(ubyte[]) x"761c457bf73b14d27e9e9265c46f4b4dda11f940"); + + assert(sha.digest("1234567890123456789012345678901234567890", + "1234567890123456789012345678901234567890") + == cast(ubyte[]) x"50abf5706a150990a08b2c5ea40fa0e585554732"); + + ubyte[] onemilliona = new ubyte[1000000]; + onemilliona[] = 'a'; + assert(sha.digest(onemilliona) == cast(ubyte[]) x"34aa973cd4c4daa4f61eeb2bdbad27316534016f"); +} diff --git a/libphobos/src/std/encoding.d b/libphobos/src/std/encoding.d new file mode 100644 index 0000000..5bbd0d4 --- /dev/null +++ b/libphobos/src/std/encoding.d @@ -0,0 +1,3662 @@ +// Written in the D programming language. + +/** +Classes and functions for handling and transcoding between various encodings. + +For cases where the _encoding is known at compile-time, functions are provided +for arbitrary _encoding and decoding of characters, arbitrary transcoding +between strings of different type, as well as validation and sanitization. + +Encodings currently supported are UTF-8, UTF-16, UTF-32, ASCII, ISO-8859-1 +(also known as LATIN-1), ISO-8859-2 (LATIN-2), WINDOWS-1250 and WINDOWS-1252. + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE, +$(TR $(TH Category) $(TH Functions)) +$(TR $(TD Decode) $(TD + $(LREF codePoints) + $(LREF decode) + $(LREF decodeReverse) + $(LREF safeDecode) +)) +$(TR $(TD Conversion) $(TD + $(LREF codeUnits) + $(LREF sanitize) + $(LREF transcode) +)) +$(TR $(TD Classification) $(TD + $(LREF canEncode) + $(LREF isValid) + $(LREF isValidCodePoint) + $(LREF isValidCodeUnit) +)) +$(TR $(TD BOM) $(TD + $(LREF BOM) + $(LREF BOMSeq) + $(LREF getBOM) + $(LREF utfBOM) +)) +$(TR $(TD Length & Index) $(TD + $(LREF firstSequence) + $(LREF encodedLength) + $(LREF index) + $(LREF lastSequence) + $(LREF validLength) +)) +$(TR $(TD Encoding schemes) $(TD + $(LREF encodingName) + $(LREF EncodingScheme) + $(LREF EncodingSchemeASCII) + $(LREF EncodingSchemeLatin1) + $(LREF EncodingSchemeLatin2) + $(LREF EncodingSchemeUtf16Native) + $(LREF EncodingSchemeUtf32Native) + $(LREF EncodingSchemeUtf8) + $(LREF EncodingSchemeWindows1250) + $(LREF EncodingSchemeWindows1252) +)) +$(TR $(TD Representation) $(TD + $(LREF AsciiChar) + $(LREF AsciiString) + $(LREF Latin1Char) + $(LREF Latin1String) + $(LREF Latin2Char) + $(LREF Latin2String) + $(LREF Windows1250Char) + $(LREF Windows1250String) + $(LREF Windows1252Char) + $(LREF Windows1252String) +)) +$(TR $(TD Exceptions) $(TD + $(LREF INVALID_SEQUENCE) + $(LREF EncodingException) +)) +) + +For cases where the _encoding is not known at compile-time, but is +known at run-time, the abstract class $(LREF EncodingScheme) +and its subclasses is provided. To construct a run-time encoder/decoder, +one does e.g. + +---------------------------------------------------- +auto e = EncodingScheme.create("utf-8"); +---------------------------------------------------- + +This library supplies $(LREF EncodingScheme) subclasses for ASCII, +ISO-8859-1 (also known as LATIN-1), ISO-8859-2 (LATIN-2), WINDOWS-1250, +WINDOWS-1252, UTF-8, and (on little-endian architectures) UTF-16LE and +UTF-32LE; or (on big-endian architectures) UTF-16BE and UTF-32BE. + +This library provides a mechanism whereby other modules may add $(LREF +EncodingScheme) subclasses for any other _encoding. + +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/_encoding.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) +*/ +module std.encoding; + +import std.range.primitives; +import std.traits; +import std.typecons; + +@system unittest +{ + static ubyte[][] validStrings = + [ + // Plain ASCII + cast(ubyte[])"hello", + + // First possible sequence of a certain length + [ 0x00 ], // U+00000000 one byte + [ 0xC2, 0x80 ], // U+00000080 two bytes + [ 0xE0, 0xA0, 0x80 ], // U+00000800 three bytes + [ 0xF0, 0x90, 0x80, 0x80 ], // U+00010000 three bytes + + // Last possible sequence of a certain length + [ 0x7F ], // U+0000007F one byte + [ 0xDF, 0xBF ], // U+000007FF two bytes + [ 0xEF, 0xBF, 0xBF ], // U+0000FFFF three bytes + + // Other boundary conditions + [ 0xED, 0x9F, 0xBF ], + // U+0000D7FF Last character before surrogates + [ 0xEE, 0x80, 0x80 ], + // U+0000E000 First character after surrogates + [ 0xEF, 0xBF, 0xBD ], + // U+0000FFFD Unicode replacement character + [ 0xF4, 0x8F, 0xBF, 0xBF ], + // U+0010FFFF Very last character + + // Non-character code points + /* NOTE: These are legal in UTF, and may be converted from + one UTF to another, however they do not represent Unicode + characters. These code points have been reserved by + Unicode as non-character code points. They are permissible + for data exchange within an application, but they are are + not permitted to be used as characters. Since this module + deals with UTF, and not with Unicode per se, we choose to + accept them here. */ + [ 0xDF, 0xBE ], // U+0000FFFE + [ 0xDF, 0xBF ], // U+0000FFFF + ]; + + static ubyte[][] invalidStrings = + [ + // First possible sequence of a certain length, but greater + // than U+10FFFF + [ 0xF8, 0x88, 0x80, 0x80, 0x80 ], // U+00200000 five bytes + [ 0xFC, 0x84, 0x80, 0x80, 0x80, 0x80 ], // U+04000000 six bytes + + // Last possible sequence of a certain length, but greater than U+10FFFF + [ 0xF7, 0xBF, 0xBF, 0xBF ], // U+001FFFFF four bytes + [ 0xFB, 0xBF, 0xBF, 0xBF, 0xBF ], // U+03FFFFFF five bytes + [ 0xFD, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF ], // U+7FFFFFFF six bytes + + // Other boundary conditions + [ 0xF4, 0x90, 0x80, 0x80 ], // U+00110000 + // First code + // point after + // last character + + // Unexpected continuation bytes + [ 0x80 ], + [ 0xBF ], + [ 0x20, 0x80, 0x20 ], + [ 0x20, 0xBF, 0x20 ], + [ 0x80, 0x9F, 0xA0 ], + + // Lonely start bytes + [ 0xC0 ], + [ 0xCF ], + [ 0x20, 0xC0, 0x20 ], + [ 0x20, 0xCF, 0x20 ], + [ 0xD0 ], + [ 0xDF ], + [ 0x20, 0xD0, 0x20 ], + [ 0x20, 0xDF, 0x20 ], + [ 0xE0 ], + [ 0xEF ], + [ 0x20, 0xE0, 0x20 ], + [ 0x20, 0xEF, 0x20 ], + [ 0xF0 ], + [ 0xF1 ], + [ 0xF2 ], + [ 0xF3 ], + [ 0xF4 ], + [ 0xF5 ], // If this were legal it would start a character > U+10FFFF + [ 0xF6 ], // If this were legal it would start a character > U+10FFFF + [ 0xF7 ], // If this were legal it would start a character > U+10FFFF + + [ 0xEF, 0xBF ], // Three byte sequence with third byte missing + [ 0xF7, 0xBF, 0xBF ], // Four byte sequence with fourth byte missing + [ 0xEF, 0xBF, 0xF7, 0xBF, 0xBF ], // Concatenation of the above + + // Impossible bytes + [ 0xF8 ], + [ 0xF9 ], + [ 0xFA ], + [ 0xFB ], + [ 0xFC ], + [ 0xFD ], + [ 0xFE ], + [ 0xFF ], + [ 0x20, 0xF8, 0x20 ], + [ 0x20, 0xF9, 0x20 ], + [ 0x20, 0xFA, 0x20 ], + [ 0x20, 0xFB, 0x20 ], + [ 0x20, 0xFC, 0x20 ], + [ 0x20, 0xFD, 0x20 ], + [ 0x20, 0xFE, 0x20 ], + [ 0x20, 0xFF, 0x20 ], + + // Overlong sequences, all representing U+002F + /* With a safe UTF-8 decoder, all of the following five overlong + representations of the ASCII character slash ("/") should be + rejected like a malformed UTF-8 sequence */ + [ 0xC0, 0xAF ], + [ 0xE0, 0x80, 0xAF ], + [ 0xF0, 0x80, 0x80, 0xAF ], + [ 0xF8, 0x80, 0x80, 0x80, 0xAF ], + [ 0xFC, 0x80, 0x80, 0x80, 0x80, 0xAF ], + + // Maximum overlong sequences + /* Below you see the highest Unicode value that is still resulting in + an overlong sequence if represented with the given number of bytes. + This is a boundary test for safe UTF-8 decoders. All five + characters should be rejected like malformed UTF-8 sequences. */ + [ 0xC1, 0xBF ], // U+0000007F + [ 0xE0, 0x9F, 0xBF ], // U+000007FF + [ 0xF0, 0x8F, 0xBF, 0xBF ], // U+0000FFFF + [ 0xF8, 0x87, 0xBF, 0xBF, 0xBF ], // U+001FFFFF + [ 0xFC, 0x83, 0xBF, 0xBF, 0xBF, 0xBF ], // U+03FFFFFF + + // Overlong representation of the NUL character + /* The following five sequences should also be rejected like malformed + UTF-8 sequences and should not be treated like the ASCII NUL + character. */ + [ 0xC0, 0x80 ], + [ 0xE0, 0x80, 0x80 ], + [ 0xF0, 0x80, 0x80, 0x80 ], + [ 0xF8, 0x80, 0x80, 0x80, 0x80 ], + [ 0xFC, 0x80, 0x80, 0x80, 0x80, 0x80 ], + + // Illegal code positions + /* The following UTF-8 sequences should be rejected like malformed + sequences, because they never represent valid ISO 10646 characters + and a UTF-8 decoder that accepts them might introduce security + problems comparable to overlong UTF-8 sequences. */ + [ 0xED, 0xA0, 0x80 ], // U+D800 + [ 0xED, 0xAD, 0xBF ], // U+DB7F + [ 0xED, 0xAE, 0x80 ], // U+DB80 + [ 0xED, 0xAF, 0xBF ], // U+DBFF + [ 0xED, 0xB0, 0x80 ], // U+DC00 + [ 0xED, 0xBE, 0x80 ], // U+DF80 + [ 0xED, 0xBF, 0xBF ], // U+DFFF + ]; + + static string[] sanitizedStrings = + [ + "\uFFFD","\uFFFD", + "\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD"," \uFFFD ", + " \uFFFD ","\uFFFD\uFFFD\uFFFD","\uFFFD","\uFFFD"," \uFFFD "," \uFFFD ", + "\uFFFD","\uFFFD"," \uFFFD "," \uFFFD ","\uFFFD","\uFFFD"," \uFFFD ", + " \uFFFD ","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD", + "\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD\uFFFD","\uFFFD","\uFFFD", + "\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD"," \uFFFD ", + " \uFFFD "," \uFFFD "," \uFFFD "," \uFFFD "," \uFFFD "," \uFFFD ", + " \uFFFD ","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD", + "\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD", + "\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD","\uFFFD", + ]; + + // Make sure everything that should be valid, is + foreach (a;validStrings) + { + string s = cast(string) a; + assert(isValid(s),"Failed to validate: "~makeReadable(s)); + } + + // Make sure everything that shouldn't be valid, isn't + foreach (a;invalidStrings) + { + string s = cast(string) a; + assert(!isValid(s),"Incorrectly validated: "~makeReadable(s)); + } + + // Make sure we can sanitize everything bad + assert(invalidStrings.length == sanitizedStrings.length); + for (int i=0; i m_charMapEnd && c < 0x100)) return true; + if (c >= 0xFFFD) return false; + + auto idx = 0; + while (idx < bstMap.length) + { + if (bstMap[idx][0] == c) return true; + idx = bstMap[idx][0] > c ? 2 * idx + 1 : 2 * idx + 2; // next BST index + } + + return false; + } + + bool isValidCodeUnit(E c) @safe pure @nogc nothrow + { + if (c < m_charMapStart || c > m_charMapEnd) return true; + return charMap[c-m_charMapStart] != 0xFFFD; + } + + size_t encodedLength(dchar c) @safe pure @nogc nothrow + in + { + assert(canEncode(c)); + } + body + { + return 1; + } + + void encodeViaWrite()(dchar c) + { + if (c < m_charMapStart || (c > m_charMapEnd && c < 0x100)) {} + else if (c >= 0xFFFD) { c = '?'; } + else + { + auto idx = 0; + while (idx < bstMap.length) + { + if (bstMap[idx][0] == c) + { + write(cast(E) bstMap[idx][1]); + return; + } + idx = bstMap[idx][0] > c ? 2 * idx + 1 : 2 * idx + 2; // next BST index + } + c = '?'; + } + write(cast(E) c); + } + + void skipViaRead()() + { + read(); + } + + dchar decodeViaRead()() + { + E c = read(); + return (c >= m_charMapStart && c <= m_charMapEnd) ? charMap[c-m_charMapStart] : c; + } + + dchar safeDecodeViaRead()() + { + immutable E c = read(); + immutable d = (c >= m_charMapStart && c <= m_charMapEnd) ? charMap[c-m_charMapStart] : c; + return d == 0xFFFD ? INVALID_SEQUENCE : d; + } + + dchar decodeReverseViaRead()() + { + E c = read(); + return (c >= m_charMapStart && c <= m_charMapEnd) ? charMap[c-m_charMapStart] : c; + } + + @property EString replacementSequence() @safe pure @nogc nothrow + { + return cast(EString)("?"); + } + + mixin EncoderFunctions; +} + +//============================================================================= +// ASCII +//============================================================================= + +/** Defines various character sets. */ +enum AsciiChar : ubyte { init } +/// Ditto +alias AsciiString = immutable(AsciiChar)[]; + +template EncoderInstance(CharType : AsciiChar) +{ + alias E = AsciiChar; + alias EString = AsciiString; + + @property string encodingName() @safe pure nothrow @nogc + { + return "ASCII"; + } + + bool canEncode(dchar c) @safe pure nothrow @nogc + { + return c < 0x80; + } + + bool isValidCodeUnit(AsciiChar c) @safe pure nothrow @nogc + { + return c < 0x80; + } + + size_t encodedLength(dchar c) @safe pure nothrow @nogc + in + { + assert(canEncode(c)); + } + body + { + return 1; + } + + void encodeX(Range)(dchar c, Range r) + { + if (!canEncode(c)) c = '?'; + r.write(cast(AsciiChar) c); + } + + void encodeViaWrite()(dchar c) + { + if (!canEncode(c)) c = '?'; + write(cast(AsciiChar) c); + } + + void skipViaRead()() + { + read(); + } + + dchar decodeViaRead()() + { + return read(); + } + + dchar safeDecodeViaRead()() + { + immutable c = read(); + return canEncode(c) ? c : INVALID_SEQUENCE; + } + + dchar decodeReverseViaRead()() + { + return read(); + } + + @property EString replacementSequence() @safe pure nothrow @nogc + { + return cast(EString)("?"); + } + + mixin EncoderFunctions; +} + +//============================================================================= +// ISO-8859-1 +//============================================================================= + +/** Defines an Latin1-encoded character. */ +enum Latin1Char : ubyte { init } +/** +Defines an Latin1-encoded string (as an array of $(D +immutable(Latin1Char))). + */ +alias Latin1String = immutable(Latin1Char)[]; + +template EncoderInstance(CharType : Latin1Char) +{ + alias E = Latin1Char; + alias EString = Latin1String; + + @property string encodingName() @safe pure nothrow @nogc + { + return "ISO-8859-1"; + } + + bool canEncode(dchar c) @safe pure nothrow @nogc + { + return c < 0x100; + } + + bool isValidCodeUnit(Latin1Char c) @safe pure nothrow @nogc + { + return true; + } + + size_t encodedLength(dchar c) @safe pure nothrow @nogc + in + { + assert(canEncode(c)); + } + body + { + return 1; + } + + void encodeViaWrite()(dchar c) + { + if (!canEncode(c)) c = '?'; + write(cast(Latin1Char) c); + } + + void skipViaRead()() + { + read(); + } + + dchar decodeViaRead()() + { + return read(); + } + + dchar safeDecodeViaRead()() + { + return read(); + } + + dchar decodeReverseViaRead()() + { + return read(); + } + + @property EString replacementSequence() @safe pure nothrow @nogc + { + return cast(EString)("?"); + } + + mixin EncoderFunctions; +} + +//============================================================================= +// ISO-8859-2 +//============================================================================= + +/// Defines a Latin2-encoded character. +enum Latin2Char : ubyte { init } + +/** + * Defines an Latin2-encoded string (as an array of $(D + * immutable(Latin2Char))). + */ +alias Latin2String = immutable(Latin2Char)[]; + +private template EncoderInstance(CharType : Latin2Char) +{ + import std.typecons : Tuple, tuple; + + alias E = Latin2Char; + alias EString = Latin2String; + + @property string encodingName() @safe pure nothrow @nogc + { + return "ISO-8859-2"; + } + + private static immutable dchar m_charMapStart = 0xa1; + private static immutable dchar m_charMapEnd = 0xff; + + private immutable wstring charMap = + "\u0104\u02D8\u0141\u00A4\u013D\u015A\u00A7\u00A8"~ + "\u0160\u015E\u0164\u0179\u00AD\u017D\u017B\u00B0"~ + "\u0105\u02DB\u0142\u00B4\u013E\u015B\u02C7\u00B8"~ + "\u0161\u015F\u0165\u017A\u02DD\u017E\u017C\u0154"~ + "\u00C1\u00C2\u0102\u00C4\u0139\u0106\u00C7\u010C"~ + "\u00C9\u0118\u00CB\u011A\u00CD\u00CE\u010E\u0110"~ + "\u0143\u0147\u00D3\u00D4\u0150\u00D6\u00D7\u0158"~ + "\u016E\u00DA\u0170\u00DC\u00DD\u0162\u00DF\u0155"~ + "\u00E1\u00E2\u0103\u00E4\u013A\u0107\u00E7\u010D"~ + "\u00E9\u0119\u00EB\u011B\u00ED\u00EE\u010F\u0111"~ + "\u0144\u0148\u00F3\u00F4\u0151\u00F6\u00F7\u0159"~ + "\u016F\u00FA\u0171\u00FC\u00FD\u0163\u02D9"; + + private immutable Tuple!(wchar, char)[] bstMap = [ + tuple('\u0148','\xF2'), tuple('\u00F3','\xF3'), tuple('\u0165','\xBB'), + tuple('\u00D3','\xD3'), tuple('\u010F','\xEF'), tuple('\u015B','\xB6'), + tuple('\u017C','\xBF'), tuple('\u00C1','\xC1'), tuple('\u00E1','\xE1'), + tuple('\u0103','\xE3'), tuple('\u013A','\xE5'), tuple('\u0155','\xE0'), + tuple('\u0161','\xB9'), tuple('\u0171','\xFB'), tuple('\u02D8','\xA2'), + tuple('\u00AD','\xAD'), tuple('\u00C9','\xC9'), tuple('\u00DA','\xDA'), + tuple('\u00E9','\xE9'), tuple('\u00FA','\xFA'), tuple('\u0107','\xE6'), + tuple('\u0119','\xEA'), tuple('\u0142','\xB3'), tuple('\u0151','\xF5'), + tuple('\u0159','\xF8'), tuple('\u015F','\xBA'), tuple('\u0163','\xFE'), + tuple('\u016F','\xF9'), tuple('\u017A','\xBC'), tuple('\u017E','\xBE'), + tuple('\u02DB','\xB2'), tuple('\u00A7','\xA7'), tuple('\u00B4','\xB4'), + tuple('\u00C4','\xC4'), tuple('\u00CD','\xCD'), tuple('\u00D6','\xD6'), + tuple('\u00DD','\xDD'), tuple('\u00E4','\xE4'), tuple('\u00ED','\xED'), + tuple('\u00F6','\xF6'), tuple('\u00FD','\xFD'), tuple('\u0105','\xB1'), + tuple('\u010D','\xE8'), tuple('\u0111','\xF0'), tuple('\u011B','\xEC'), + tuple('\u013E','\xB5'), tuple('\u0144','\xF1'), tuple('\u0150','\xD5'), + tuple('\u0154','\xC0'), tuple('\u0158','\xD8'), tuple('\u015A','\xA6'), + tuple('\u015E','\xAA'), tuple('\u0160','\xA9'), tuple('\u0162','\xDE'), + tuple('\u0164','\xAB'), tuple('\u016E','\xD9'), tuple('\u0170','\xDB'), + tuple('\u0179','\xAC'), tuple('\u017B','\xAF'), tuple('\u017D','\xAE'), + tuple('\u02C7','\xB7'), tuple('\u02D9','\xFF'), tuple('\u02DD','\xBD'), + tuple('\u00A4','\xA4'), tuple('\u00A8','\xA8'), tuple('\u00B0','\xB0'), + tuple('\u00B8','\xB8'), tuple('\u00C2','\xC2'), tuple('\u00C7','\xC7'), + tuple('\u00CB','\xCB'), tuple('\u00CE','\xCE'), tuple('\u00D4','\xD4'), + tuple('\u00D7','\xD7'), tuple('\u00DC','\xDC'), tuple('\u00DF','\xDF'), + tuple('\u00E2','\xE2'), tuple('\u00E7','\xE7'), tuple('\u00EB','\xEB'), + tuple('\u00EE','\xEE'), tuple('\u00F4','\xF4'), tuple('\u00F7','\xF7'), + tuple('\u00FC','\xFC'), tuple('\u0102','\xC3'), tuple('\u0104','\xA1'), + tuple('\u0106','\xC6'), tuple('\u010C','\xC8'), tuple('\u010E','\xCF'), + tuple('\u0110','\xD0'), tuple('\u0118','\xCA'), tuple('\u011A','\xCC'), + tuple('\u0139','\xC5'), tuple('\u013D','\xA5'), tuple('\u0141','\xA3'), + tuple('\u0143','\xD1'), tuple('\u0147','\xD2') + ]; + + mixin GenericEncoder!(); +} + +//============================================================================= +// WINDOWS-1250 +//============================================================================= + +/// Defines a Windows1250-encoded character. +enum Windows1250Char : ubyte { init } + +/** + * Defines an Windows1250-encoded string (as an array of $(D + * immutable(Windows1250Char))). + */ +alias Windows1250String = immutable(Windows1250Char)[]; + +private template EncoderInstance(CharType : Windows1250Char) +{ + import std.typecons : Tuple, tuple; + + alias E = Windows1250Char; + alias EString = Windows1250String; + + @property string encodingName() @safe pure nothrow @nogc + { + return "windows-1250"; + } + + private static immutable dchar m_charMapStart = 0x80; + private static immutable dchar m_charMapEnd = 0xff; + + private immutable wstring charMap = + "\u20AC\uFFFD\u201A\uFFFD\u201E\u2026\u2020\u2021"~ + "\uFFFD\u2030\u0160\u2039\u015A\u0164\u017D\u0179"~ + "\uFFFD\u2018\u2019\u201C\u201D\u2022\u2013\u2014"~ + "\uFFFD\u2122\u0161\u203A\u015B\u0165\u017E\u017A"~ + "\u00A0\u02C7\u02D8\u0141\u00A4\u0104\u00A6\u00A7"~ + "\u00A8\u00A9\u015E\u00AB\u00AC\u00AD\u00AE\u017B"~ + "\u00B0\u00B1\u02DB\u0142\u00B4\u00B5\u00B6\u00B7"~ + "\u00B8\u0105\u015F\u00BB\u013D\u02DD\u013E\u017C"~ + "\u0154\u00C1\u00C2\u0102\u00C4\u0139\u0106\u00C7"~ + "\u010C\u00C9\u0118\u00CB\u011A\u00CD\u00CE\u010E"~ + "\u0110\u0143\u0147\u00D3\u00D4\u0150\u00D6\u00D7"~ + "\u0158\u016E\u00DA\u0170\u00DC\u00DD\u0162\u00DF"~ + "\u0155\u00E1\u00E2\u0103\u00E4\u013A\u0107\u00E7"~ + "\u010D\u00E9\u0119\u00EB\u011B\u00ED\u00EE\u010F"~ + "\u0111\u0144\u0148\u00F3\u00F4\u0151\u00F6\u00F7"~ + "\u0159\u016F\u00FA\u0171\u00FC\u00FD\u0163\u02D9"; + + private immutable Tuple!(wchar, char)[] bstMap = [ + tuple('\u011A','\xCC'), tuple('\u00DC','\xDC'), tuple('\u0179','\x8F'), + tuple('\u00B7','\xB7'), tuple('\u00FC','\xFC'), tuple('\u0158','\xD8'), + tuple('\u201C','\x93'), tuple('\u00AC','\xAC'), tuple('\u00CB','\xCB'), + tuple('\u00EB','\xEB'), tuple('\u010C','\xC8'), tuple('\u0143','\xD1'), + tuple('\u0162','\xDE'), tuple('\u02D9','\xFF'), tuple('\u2039','\x8B'), + tuple('\u00A7','\xA7'), tuple('\u00B1','\xB1'), tuple('\u00C2','\xC2'), + tuple('\u00D4','\xD4'), tuple('\u00E2','\xE2'), tuple('\u00F4','\xF4'), + tuple('\u0104','\xA5'), tuple('\u0110','\xD0'), tuple('\u013D','\xBC'), + tuple('\u0150','\xD5'), tuple('\u015E','\xAA'), tuple('\u016E','\xD9'), + tuple('\u017D','\x8E'), tuple('\u2014','\x97'), tuple('\u2021','\x87'), + tuple('\u20AC','\x80'), tuple('\u00A4','\xA4'), tuple('\u00A9','\xA9'), + tuple('\u00AE','\xAE'), tuple('\u00B5','\xB5'), tuple('\u00BB','\xBB'), + tuple('\u00C7','\xC7'), tuple('\u00CE','\xCE'), tuple('\u00D7','\xD7'), + tuple('\u00DF','\xDF'), tuple('\u00E7','\xE7'), tuple('\u00EE','\xEE'), + tuple('\u00F7','\xF7'), tuple('\u0102','\xC3'), tuple('\u0106','\xC6'), + tuple('\u010E','\xCF'), tuple('\u0118','\xCA'), tuple('\u0139','\xC5'), + tuple('\u0141','\xA3'), tuple('\u0147','\xD2'), tuple('\u0154','\xC0'), + tuple('\u015A','\x8C'), tuple('\u0160','\x8A'), tuple('\u0164','\x8D'), + tuple('\u0170','\xDB'), tuple('\u017B','\xAF'), tuple('\u02C7','\xA1'), + tuple('\u02DD','\xBD'), tuple('\u2019','\x92'), tuple('\u201E','\x84'), + tuple('\u2026','\x85'), tuple('\u203A','\x9B'), tuple('\u2122','\x99'), + tuple('\u00A0','\xA0'), tuple('\u00A6','\xA6'), tuple('\u00A8','\xA8'), + tuple('\u00AB','\xAB'), tuple('\u00AD','\xAD'), tuple('\u00B0','\xB0'), + tuple('\u00B4','\xB4'), tuple('\u00B6','\xB6'), tuple('\u00B8','\xB8'), + tuple('\u00C1','\xC1'), tuple('\u00C4','\xC4'), tuple('\u00C9','\xC9'), + tuple('\u00CD','\xCD'), tuple('\u00D3','\xD3'), tuple('\u00D6','\xD6'), + tuple('\u00DA','\xDA'), tuple('\u00DD','\xDD'), tuple('\u00E1','\xE1'), + tuple('\u00E4','\xE4'), tuple('\u00E9','\xE9'), tuple('\u00ED','\xED'), + tuple('\u00F3','\xF3'), tuple('\u00F6','\xF6'), tuple('\u00FA','\xFA'), + tuple('\u00FD','\xFD'), tuple('\u0103','\xE3'), tuple('\u0105','\xB9'), + tuple('\u0107','\xE6'), tuple('\u010D','\xE8'), tuple('\u010F','\xEF'), + tuple('\u0111','\xF0'), tuple('\u0119','\xEA'), tuple('\u011B','\xEC'), + tuple('\u013A','\xE5'), tuple('\u013E','\xBE'), tuple('\u0142','\xB3'), + tuple('\u0144','\xF1'), tuple('\u0148','\xF2'), tuple('\u0151','\xF5'), + tuple('\u0155','\xE0'), tuple('\u0159','\xF8'), tuple('\u015B','\x9C'), + tuple('\u015F','\xBA'), tuple('\u0161','\x9A'), tuple('\u0163','\xFE'), + tuple('\u0165','\x9D'), tuple('\u016F','\xF9'), tuple('\u0171','\xFB'), + tuple('\u017A','\x9F'), tuple('\u017C','\xBF'), tuple('\u017E','\x9E'), + tuple('\u02D8','\xA2'), tuple('\u02DB','\xB2'), tuple('\u2013','\x96'), + tuple('\u2018','\x91'), tuple('\u201A','\x82'), tuple('\u201D','\x94'), + tuple('\u2020','\x86'), tuple('\u2022','\x95'), tuple('\u2030','\x89') + ]; + + mixin GenericEncoder!(); +} + +//============================================================================= +// WINDOWS-1252 +//============================================================================= + +/// Defines a Windows1252-encoded character. +enum Windows1252Char : ubyte { init } + +/** + * Defines an Windows1252-encoded string (as an array of $(D + * immutable(Windows1252Char))). + */ +alias Windows1252String = immutable(Windows1252Char)[]; + +template EncoderInstance(CharType : Windows1252Char) +{ + import std.typecons : Tuple, tuple; + + alias E = Windows1252Char; + alias EString = Windows1252String; + + @property string encodingName() @safe pure nothrow @nogc + { + return "windows-1252"; + } + + private static immutable dchar m_charMapStart = 0x80; + private static immutable dchar m_charMapEnd = 0x9f; + + private immutable wstring charMap = + "\u20AC\uFFFD\u201A\u0192\u201E\u2026\u2020\u2021"~ + "\u02C6\u2030\u0160\u2039\u0152\uFFFD\u017D\uFFFD"~ + "\uFFFD\u2018\u2019\u201C\u201D\u2022\u2013\u2014"~ + "\u02DC\u2122\u0161\u203A\u0153\uFFFD\u017E\u0178"; + + private immutable Tuple!(wchar, char)[] bstMap = [ + tuple('\u201C','\x93'), tuple('\u0192','\x83'), tuple('\u2039','\x8B'), + tuple('\u0161','\x9A'), tuple('\u2014','\x97'), tuple('\u2021','\x87'), + tuple('\u20AC','\x80'), tuple('\u0153','\x9C'), tuple('\u017D','\x8E'), + tuple('\u02DC','\x98'), tuple('\u2019','\x92'), tuple('\u201E','\x84'), + tuple('\u2026','\x85'), tuple('\u203A','\x9B'), tuple('\u2122','\x99'), + tuple('\u0152','\x8C'), tuple('\u0160','\x8A'), tuple('\u0178','\x9F'), + tuple('\u017E','\x9E'), tuple('\u02C6','\x88'), tuple('\u2013','\x96'), + tuple('\u2018','\x91'), tuple('\u201A','\x82'), tuple('\u201D','\x94'), + tuple('\u2020','\x86'), tuple('\u2022','\x95'), tuple('\u2030','\x89') + ]; + + mixin GenericEncoder!(); +} + +//============================================================================= +// UTF-8 +//============================================================================= + +template EncoderInstance(CharType : char) +{ + alias E = char; + alias EString = immutable(char)[]; + + @property string encodingName() @safe pure nothrow @nogc + { + return "UTF-8"; + } + + bool canEncode(dchar c) @safe pure nothrow @nogc + { + return isValidCodePoint(c); + } + + bool isValidCodeUnit(char c) @safe pure nothrow @nogc + { + return (c < 0xC0 || (c >= 0xC2 && c < 0xF5)); + } + + immutable ubyte[128] tailTable = + [ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 3,3,3,3,3,3,3,3,4,4,4,4,5,5,6,0, + ]; + + private int tails(char c) @safe pure nothrow @nogc + in + { + assert(c >= 0x80); + } + body + { + return tailTable[c-0x80]; + } + + size_t encodedLength(dchar c) @safe pure nothrow @nogc + in + { + assert(canEncode(c)); + } + body + { + if (c < 0x80) return 1; + if (c < 0x800) return 2; + if (c < 0x10000) return 3; + return 4; + } + + void encodeViaWrite()(dchar c) + { + if (c < 0x80) + { + write(cast(char) c); + } + else if (c < 0x800) + { + write(cast(char)((c >> 6) + 0xC0)); + write(cast(char)((c & 0x3F) + 0x80)); + } + else if (c < 0x10000) + { + write(cast(char)((c >> 12) + 0xE0)); + write(cast(char)(((c >> 6) & 0x3F) + 0x80)); + write(cast(char)((c & 0x3F) + 0x80)); + } + else + { + write(cast(char)((c >> 18) + 0xF0)); + write(cast(char)(((c >> 12) & 0x3F) + 0x80)); + write(cast(char)(((c >> 6) & 0x3F) + 0x80)); + write(cast(char)((c & 0x3F) + 0x80)); + } + } + + void skipViaRead()() + { + auto c = read(); + if (c < 0xC0) return; + int n = tails(cast(char) c); + for (size_t i=0; i 0xF4) // fail overlong 4-6-byte sequences + || (c == 0xE0 && ((d & 0xE0) == 0x80)) // fail overlong 3-byte sequences + || (c == 0xED && ((d & 0xE0) == 0xA0)) // fail surrogates + || (c == 0xF0 && ((d & 0xF0) == 0x80)) // fail overlong 4-byte sequences + || (c == 0xF4 && ((d & 0xF0) >= 0x90)) // fail code points > 0x10FFFF + ); + + c &= (1 << (6 - n)) - 1; + for (size_t i=0; i> 10))); + write(cast(wchar)(0xDC00 + (n & 0x3FF))); + } + } + + void skipViaRead()() + { + immutable c = read(); + if (c < 0xD800 || c >= 0xE000) return; + read(); + } + + dchar decodeViaRead()() + { + wchar c = read(); + if (c < 0xD800 || c >= 0xE000) return cast(dchar) c; + wchar d = read(); + c &= 0x3FF; + d &= 0x3FF; + return 0x10000 + (c << 10) + d; + } + + dchar safeDecodeViaRead()() + { + wchar c = read(); + if (c < 0xD800 || c >= 0xE000) return cast(dchar) c; + if (c >= 0xDC00) return INVALID_SEQUENCE; + if (!canRead) return INVALID_SEQUENCE; + wchar d = peek(); + if (d < 0xDC00 || d >= 0xE000) return INVALID_SEQUENCE; + d = read(); + c &= 0x3FF; + d &= 0x3FF; + return 0x10000 + (c << 10) + d; + } + + dchar decodeReverseViaRead()() + { + wchar c = read(); + if (c < 0xD800 || c >= 0xE000) return cast(dchar) c; + wchar d = read(); + c &= 0x3FF; + d &= 0x3FF; + return 0x10000 + (d << 10) + c; + } + + @property EString replacementSequence() @safe pure nothrow @nogc + { + return "\uFFFD"w; + } + + mixin EncoderFunctions; +} + +//============================================================================= +// UTF-32 +//============================================================================= + +template EncoderInstance(CharType : dchar) +{ + alias E = dchar; + alias EString = immutable(dchar)[]; + + @property string encodingName() @safe pure nothrow @nogc + { + return "UTF-32"; + } + + bool canEncode(dchar c) @safe pure @nogc nothrow + { + return isValidCodePoint(c); + } + + bool isValidCodeUnit(dchar c) @safe pure @nogc nothrow + { + return isValidCodePoint(c); + } + + size_t encodedLength(dchar c) @safe pure @nogc nothrow + in + { + assert(canEncode(c)); + } + body + { + return 1; + } + + void encodeViaWrite()(dchar c) + { + write(c); + } + + void skipViaRead()() + { + read(); + } + + dchar decodeViaRead()() + { + return cast(dchar) read(); + } + + dchar safeDecodeViaRead()() + { + immutable c = read(); + return isValidCodePoint(c) ? c : INVALID_SEQUENCE; + } + + dchar decodeReverseViaRead()() + { + return cast(dchar) read(); + } + + @property EString replacementSequence() @safe pure nothrow @nogc + { + return "\uFFFD"d; + } + + mixin EncoderFunctions; +} + +//============================================================================= +// Below are forwarding functions which expose the function to the user + +/** +Returns true if c is a valid code point + + Note that this includes the non-character code points U+FFFE and U+FFFF, + since these are valid code points (even though they are not valid + characters). + + Supersedes: + This function supersedes $(D std.utf.startsValidDchar()). + + Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, + WINDOWS-1252 + + Params: + c = the code point to be tested + */ +bool isValidCodePoint(dchar c) @safe pure nothrow @nogc +{ + return c < 0xD800 || (c >= 0xE000 && c < 0x110000); +} + +/** + Returns the name of an encoding. + + The type of encoding cannot be deduced. Therefore, it is necessary to + explicitly specify the encoding type. + + Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, + WINDOWS-1252 + */ +@property string encodingName(T)() +{ + return EncoderInstance!(T).encodingName; +} + +/// +@safe unittest +{ + assert(encodingName!(char) == "UTF-8"); + assert(encodingName!(wchar) == "UTF-16"); + assert(encodingName!(dchar) == "UTF-32"); + assert(encodingName!(AsciiChar) == "ASCII"); + assert(encodingName!(Latin1Char) == "ISO-8859-1"); + assert(encodingName!(Latin2Char) == "ISO-8859-2"); + assert(encodingName!(Windows1250Char) == "windows-1250"); + assert(encodingName!(Windows1252Char) == "windows-1252"); +} + +/** + Returns true iff it is possible to represent the specified codepoint + in the encoding. + + The type of encoding cannot be deduced. Therefore, it is necessary to + explicitly specify the encoding type. + + Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, + WINDOWS-1252 + */ +bool canEncode(E)(dchar c) +{ + return EncoderInstance!(E).canEncode(c); +} + +/// +@safe pure unittest +{ + assert( canEncode!(Latin1Char)('A')); + assert( canEncode!(Latin2Char)('A')); + assert(!canEncode!(AsciiChar)('\u00A0')); + assert( canEncode!(Latin1Char)('\u00A0')); + assert( canEncode!(Latin2Char)('\u00A0')); + assert( canEncode!(Windows1250Char)('\u20AC')); + assert(!canEncode!(Windows1250Char)('\u20AD')); + assert(!canEncode!(Windows1250Char)('\uFFFD')); + assert( canEncode!(Windows1252Char)('\u20AC')); + assert(!canEncode!(Windows1252Char)('\u20AD')); + assert(!canEncode!(Windows1252Char)('\uFFFD')); + assert(!canEncode!(char)(cast(dchar) 0x110000)); +} + +/// How to check an entire string +@safe pure unittest +{ + import std.algorithm.searching : find; + import std.utf : byDchar; + + assert("The quick brown fox" + .byDchar + .find!(x => !canEncode!AsciiChar(x)) + .empty); +} + +/** + Returns true if the code unit is legal. For example, the byte 0x80 would + not be legal in ASCII, because ASCII code units must always be in the range + 0x00 to 0x7F. + + Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, + WINDOWS-1252 + + Params: + c = the code unit to be tested + */ +bool isValidCodeUnit(E)(E c) +{ + return EncoderInstance!(E).isValidCodeUnit(c); +} + +/// +@system pure unittest +{ + assert(!isValidCodeUnit(cast(char) 0xC0)); + assert(!isValidCodeUnit(cast(char) 0xFF)); + assert( isValidCodeUnit(cast(wchar) 0xD800)); + assert(!isValidCodeUnit(cast(dchar) 0xD800)); + assert(!isValidCodeUnit(cast(AsciiChar) 0xA0)); + assert( isValidCodeUnit(cast(Windows1250Char) 0x80)); + assert(!isValidCodeUnit(cast(Windows1250Char) 0x81)); + assert( isValidCodeUnit(cast(Windows1252Char) 0x80)); + assert(!isValidCodeUnit(cast(Windows1252Char) 0x81)); +} + +/** + Returns true if the string is encoded correctly + + Supersedes: + This function supersedes std.utf.validate(), however note that this + function returns a bool indicating whether the input was valid or not, + whereas the older function would throw an exception. + + Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, + WINDOWS-1252 + + Params: + s = the string to be tested + */ +bool isValid(E)(const(E)[] s) +{ + return s.length == validLength(s); +} + +/// +@system pure unittest +{ + assert( isValid("\u20AC100")); + assert(!isValid(cast(char[3])[167, 133, 175])); +} + +/** + Returns the length of the longest possible substring, starting from + the first code unit, which is validly encoded. + + Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, + WINDOWS-1252 + + Params: + s = the string to be tested + */ +size_t validLength(E)(const(E)[] s) +{ + size_t result, before = void; + while ((before = s.length) > 0) + { + if (EncoderInstance!(E).safeDecode(s) == INVALID_SEQUENCE) + break; + result += before - s.length; + } + return result; +} + +/** + Sanitizes a string by replacing malformed code unit sequences with valid + code unit sequences. The result is guaranteed to be valid for this encoding. + + If the input string is already valid, this function returns the original, + otherwise it constructs a new string by replacing all illegal code unit + sequences with the encoding's replacement character, Invalid sequences will + be replaced with the Unicode replacement character (U+FFFD) if the + character repertoire contains it, otherwise invalid sequences will be + replaced with '?'. + + Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, + WINDOWS-1252 + + Params: + s = the string to be sanitized + */ +immutable(E)[] sanitize(E)(immutable(E)[] s) +{ + size_t n = validLength(s); + if (n == s.length) return s; + + auto repSeq = EncoderInstance!(E).replacementSequence; + + // Count how long the string needs to be. + // Overestimating is not a problem + size_t len = s.length; + const(E)[] t = s[n..$]; + while (t.length != 0) + { + immutable c = EncoderInstance!(E).safeDecode(t); + assert(c == INVALID_SEQUENCE); + len += repSeq.length; + t = t[validLength(t)..$]; + } + + // Now do the write + E[] array = new E[len]; + array[0 .. n] = s[0 .. n]; + size_t offset = n; + + t = s[n..$]; + while (t.length != 0) + { + immutable c = EncoderInstance!(E).safeDecode(t); + assert(c == INVALID_SEQUENCE); + array[offset .. offset+repSeq.length] = repSeq[]; + offset += repSeq.length; + n = validLength(t); + array[offset .. offset+n] = t[0 .. n]; + offset += n; + t = t[n..$]; + } + return cast(immutable(E)[])array[0 .. offset]; +} + +/// +@system pure unittest +{ + assert(sanitize("hello \xF0\x80world") == "hello \xEF\xBF\xBDworld"); +} + +/** + Returns the length of the first encoded sequence. + + The input to this function MUST be validly encoded. + This is enforced by the function's in-contract. + + Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, + WINDOWS-1252 + + Params: + s = the string to be sliced + */ +size_t firstSequence(E)(const(E)[] s) +in +{ + assert(s.length != 0); + const(E)[] u = s; + assert(safeDecode(u) != INVALID_SEQUENCE); +} +body +{ + auto before = s.length; + EncoderInstance!(E).skip(s); + return before - s.length; +} + +/// +@system pure unittest +{ + assert(firstSequence("\u20AC1000") == "\u20AC".length); + assert(firstSequence("hel") == "h".length); +} + +/** + Returns the length of the last encoded sequence. + + The input to this function MUST be validly encoded. + This is enforced by the function's in-contract. + + Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, + WINDOWS-1252 + + Params: + s = the string to be sliced + */ +size_t lastSequence(E)(const(E)[] s) +in +{ + assert(s.length != 0); + assert(isValid(s)); +} +body +{ + const(E)[] t = s; + EncoderInstance!(E).decodeReverse(s); + return t.length - s.length; +} + +/// +@system pure unittest +{ + assert(lastSequence("1000\u20AC") == "\u20AC".length); + assert(lastSequence("hellö") == "ö".length); +} + +/** + Returns the array index at which the (n+1)th code point begins. + + The input to this function MUST be validly encoded. + This is enforced by the function's in-contract. + + Supersedes: + This function supersedes std.utf.toUTFindex(). + + Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, + WINDOWS-1252 + + Params: + s = the string to be counted + n = the current code point index + */ +ptrdiff_t index(E)(const(E)[] s,int n) +in +{ + assert(isValid(s)); + assert(n >= 0); +} +body +{ + const(E)[] t = s; + for (size_t i=0; i> 6))); + put(range, cast(char)(0x80 | (c & 0x3F))); + return 2; + } + if (c <= 0xFFFF) + { + put(range, cast(char)(0xE0 | (c >> 12))); + put(range, cast(char)(0x80 | ((c >> 6) & 0x3F))); + put(range, cast(char)(0x80 | (c & 0x3F))); + return 3; + } + if (c <= 0x10FFFF) + { + put(range, cast(char)(0xF0 | (c >> 18))); + put(range, cast(char)(0x80 | ((c >> 12) & 0x3F))); + put(range, cast(char)(0x80 | ((c >> 6) & 0x3F))); + put(range, cast(char)(0x80 | (c & 0x3F))); + return 4; + } + else + { + assert(0); + } + } + else static if (is(Unqual!E == wchar)) + { + if (c <= 0xFFFF) + { + range.put(cast(wchar) c); + return 1; + } + range.put(cast(wchar) ((((c - 0x10000) >> 10) & 0x3FF) + 0xD800)); + range.put(cast(wchar) (((c - 0x10000) & 0x3FF) + 0xDC00)); + return 2; + } + else static if (is(Unqual!E == dchar)) + { + range.put(c); + return 1; + } + else + { + static assert(0); + } +} + +@safe pure unittest +{ + import std.array; + Appender!(char[]) r; + assert(encode!(char)('T', r) == 1); + assert(encode!(wchar)('T', r) == 1); + assert(encode!(dchar)('T', r) == 1); +} + +/** + Encodes a single code point to a delegate. + + This function encodes a single code point into one or more code units. + The code units are passed one at a time to the supplied delegate. + + The input to this function MUST be a valid code point. + This is enforced by the function's in-contract. + + The type of the output cannot be deduced. Therefore, it is necessary to + explicitly specify the encoding as a template parameter. + + Supersedes: + This function supersedes std.utf.encode(), however, note that the + function codeUnits() supersedes it more conveniently. + + Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, + WINDOWS-1252 + + Params: + c = the code point to be encoded + dg = the delegate to invoke for each code unit + */ +void encode(E)(dchar c, void delegate(E) dg) +in +{ + assert(isValidCodePoint(c)); +} +body +{ + EncoderInstance!(E).encode(c,dg); +} + +/** +Encodes the contents of $(D s) in units of type $(D Tgt), writing the result to an +output range. + +Returns: The number of $(D Tgt) elements written. +Params: +Tgt = Element type of $(D range). +s = Input array. +range = Output range. + */ +size_t encode(Tgt, Src, R)(in Src[] s, R range) +{ + size_t result; + foreach (c; s) + { + result += encode!(Tgt)(c, range); + } + return result; +} + +/** + Returns a foreachable struct which can bidirectionally iterate over all + code points in a string. + + The input to this function MUST be validly encoded. + This is enforced by the function's in-contract. + + You can foreach either + with or without an index. If an index is specified, it will be initialized + at each iteration with the offset into the string at which the code point + begins. + + Supersedes: + This function supersedes std.utf.decode(). + + Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, + WINDOWS-1252 + + Params: + s = the string to be decoded + + Example: + -------------------------------------------------------- + string s = "hello world"; + foreach (c;codePoints(s)) + { + // do something with c (which will always be a dchar) + } + -------------------------------------------------------- + + Note that, currently, foreach (c:codePoints(s)) is superior to foreach (c;s) + in that the latter will fall over on encountering U+FFFF. + */ +CodePoints!(E) codePoints(E)(immutable(E)[] s) +in +{ + assert(isValid(s)); +} +body +{ + return CodePoints!(E)(s); +} + +/// +@system unittest +{ + string s = "hello"; + string t; + foreach (c;codePoints(s)) + { + t ~= cast(char) c; + } + assert(s == t); +} + +/** + Returns a foreachable struct which can bidirectionally iterate over all + code units in a code point. + + The input to this function MUST be a valid code point. + This is enforced by the function's in-contract. + + The type of the output cannot be deduced. Therefore, it is necessary to + explicitly specify the encoding type in the template parameter. + + Supersedes: + This function supersedes std.utf.encode(). + + Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, + WINDOWS-1252 + + Params: + c = the code point to be encoded + */ +CodeUnits!(E) codeUnits(E)(dchar c) +in +{ + assert(isValidCodePoint(c)); +} +body +{ + return CodeUnits!(E)(c); +} + +/// +@system unittest +{ + char[] a; + foreach (c;codeUnits!(char)(cast(dchar)'\u20AC')) + { + a ~= c; + } + assert(a.length == 3); + assert(a[0] == 0xE2); + assert(a[1] == 0x82); + assert(a[2] == 0xAC); +} + +/** + Convert a string from one encoding to another. + + Supersedes: + This function supersedes std.utf.toUTF8(), std.utf.toUTF16() and + std.utf.toUTF32() + (but note that to!() supersedes it more conveniently). + + Standards: Unicode 5.0, ASCII, ISO-8859-1, ISO-8859-2, WINDOWS-1250, + WINDOWS-1252 + + Params: + s = Source string. $(B Must) be validly encoded. + This is enforced by the function's in-contract. + r = Destination string + + See_Also: + $(REF to, std,conv) + */ +void transcode(Src, Dst)(Src[] s, out Dst[] r) +in +{ + assert(isValid(s)); +} +body +{ + static if (is(Src == Dst) && is(Src == immutable)) + { + r = s; + } + else static if (is(Unqual!Src == AsciiChar)) + { + transcode(cast(const(char)[])s, r); + } + else + { + static if (is(Unqual!Dst == wchar)) + { + immutable minReservePlace = 2; + } + else static if (is(Unqual!Dst == dchar)) + { + immutable minReservePlace = 1; + } + else + { + immutable minReservePlace = 6; + } + + auto buffer = new Unqual!Dst[s.length]; + auto tmpBuffer = buffer; + + while (s.length != 0) + { + if (tmpBuffer.length < minReservePlace) + { + size_t prevLength = buffer.length; + buffer.length += s.length + minReservePlace; + tmpBuffer = buffer[prevLength - tmpBuffer.length .. $]; + } + EncoderInstance!(Unqual!Dst).encode(decode(s), tmpBuffer); + } + + r = cast(Dst[]) buffer[0 .. buffer.length - tmpBuffer.length]; + } +} + +/// +@system pure unittest +{ + wstring ws; + // transcode from UTF-8 to UTF-16 + transcode("hello world",ws); + assert(ws == "hello world"w); + + Latin1String ls; + // transcode from UTF-16 to ISO-8859-1 + transcode(ws, ls); + assert(ws == "hello world"); +} + +@system pure unittest +{ + import std.meta; + import std.range; + { + import std.conv : to; + + string asciiCharString = to!string(iota(0, 128, 1)); + + alias Types = AliasSeq!(string, Latin1String, Latin2String, AsciiString, + Windows1250String, Windows1252String, dstring, wstring); + foreach (S; Types) + foreach (D; Types) + { + string str; + S sStr; + D dStr; + transcode(asciiCharString, sStr); + transcode(sStr, dStr); + transcode(dStr, str); + assert(asciiCharString == str); + } + } + { + string czechChars = "Příliš žluťoučký kůň úpěl ďábelské ódy."; + alias Types = AliasSeq!(string, dstring, wstring); + foreach (S; Types) + foreach (D; Types) + { + string str; + S sStr; + D dStr; + transcode(czechChars, sStr); + transcode(sStr, dStr); + transcode(dStr, str); + assert(czechChars == str); + } + } +} + +@system unittest // mutable/const input/output +{ + import std.meta : AliasSeq; + + foreach (O; AliasSeq!(Latin1Char, const Latin1Char, immutable Latin1Char)) + { + O[] output; + + char[] mutableInput = "äbc".dup; + transcode(mutableInput, output); + assert(output == [0xE4, 'b', 'c']); + + const char[] constInput = "öbc"; + transcode(constInput, output); + assert(output == [0xF6, 'b', 'c']); + + immutable char[] immutInput = "übc"; + transcode(immutInput, output); + assert(output == [0xFC, 'b', 'c']); + } + + // Make sure that const/mutable input is copied. + foreach (C; AliasSeq!(char, const char)) + { + C[] input = "foo".dup; + C[] output; + transcode(input, output); + assert(input == output); + assert(input !is output); + } + + // But immutable input should not be copied. + string input = "foo"; + string output; + transcode(input, output); + assert(input is output); +} + +//============================================================================= + +/** The base class for exceptions thrown by this module */ +class EncodingException : Exception { this(string msg) @safe pure { super(msg); } } + +class UnrecognizedEncodingException : EncodingException +{ + private this(string msg) @safe pure { super(msg); } +} + +/** Abstract base class of all encoding schemes */ +abstract class EncodingScheme +{ + import std.uni : toLower; + + /** + * Registers a subclass of EncodingScheme. + * + * This function allows user-defined subclasses of EncodingScheme to + * be declared in other modules. + * + * Params: + * Klass = The subclass of EncodingScheme to register. + * + * Example: + * ---------------------------------------------- + * class Amiga1251 : EncodingScheme + * { + * shared static this() + * { + * EncodingScheme.register!Amiga1251; + * } + * } + * ---------------------------------------------- + */ + static void register(Klass:EncodingScheme)() + { + scope scheme = new Klass(); + foreach (encodingName;scheme.names()) + { + supported[toLower(encodingName)] = () => new Klass(); + } + } + + deprecated("Please pass the EncodingScheme subclass as template argument instead.") + static void register(string className) + { + auto scheme = cast(EncodingScheme) ClassInfo.find(className).create(); + if (scheme is null) + throw new EncodingException("Unable to create class "~className); + foreach (encodingName;scheme.names()) + { + supportedFactories[toLower(encodingName)] = className; + } + } + + /** + * Obtains a subclass of EncodingScheme which is capable of encoding + * and decoding the named encoding scheme. + * + * This function is only aware of EncodingSchemes which have been + * registered with the register() function. + * + * Example: + * --------------------------------------------------- + * auto scheme = EncodingScheme.create("Amiga-1251"); + * --------------------------------------------------- + */ + static EncodingScheme create(string encodingName) + { + static bool registerDefaultEncodings() + { + EncodingScheme.register!EncodingSchemeASCII; + EncodingScheme.register!EncodingSchemeLatin1; + EncodingScheme.register!EncodingSchemeLatin2; + EncodingScheme.register!EncodingSchemeWindows1250; + EncodingScheme.register!EncodingSchemeWindows1252; + EncodingScheme.register!EncodingSchemeUtf8; + EncodingScheme.register!EncodingSchemeUtf16Native; + EncodingScheme.register!EncodingSchemeUtf32Native; + return true; + } + + static shared bool initialized; + import std.concurrency : initOnce; + initOnce!initialized(registerDefaultEncodings()); + encodingName = toLower(encodingName); + + if (auto p = encodingName in supported) + return (*p)(); + + auto p = encodingName in supportedFactories; + if (p is null) + throw new EncodingException("Unrecognized Encoding: "~encodingName); + string className = *p; + auto scheme = cast(EncodingScheme) ClassInfo.find(className).create(); + if (scheme is null) throw new EncodingException("Unable to create class "~className); + return scheme; + } + + const + { + /** + * Returns the standard name of the encoding scheme + */ + abstract override string toString(); + + /** + * Returns an array of all known names for this encoding scheme + */ + abstract string[] names(); + + /** + * Returns true if the character c can be represented + * in this encoding scheme. + */ + abstract bool canEncode(dchar c); + + /** + * Returns the number of ubytes required to encode this code point. + * + * The input to this function MUST be a valid code point. + * + * Params: + * c = the code point to be encoded + * + * Returns: + * the number of ubytes required. + */ + abstract size_t encodedLength(dchar c); + + /** + * Encodes a single code point into a user-supplied, fixed-size buffer. + * + * This function encodes a single code point into one or more ubytes. + * The supplied buffer must be code unit aligned. + * (For example, UTF-16LE or UTF-16BE must be wchar-aligned, + * UTF-32LE or UTF-32BE must be dchar-aligned, etc.) + * + * The input to this function MUST be a valid code point. + * + * Params: + * c = the code point to be encoded + * buffer = the destination array + * + * Returns: + * the number of ubytes written. + */ + abstract size_t encode(dchar c, ubyte[] buffer); + + /** + * Decodes a single code point. + * + * This function removes one or more ubytes from the start of an array, + * and returns the decoded code point which those ubytes represent. + * + * The input to this function MUST be validly encoded. + * + * Params: + * s = the array whose first code point is to be decoded + */ + abstract dchar decode(ref const(ubyte)[] s); + + /** + * Decodes a single code point. The input does not have to be valid. + * + * This function removes one or more ubytes from the start of an array, + * and returns the decoded code point which those ubytes represent. + * + * This function will accept an invalidly encoded array as input. + * If an invalid sequence is found at the start of the string, this + * function will remove it, and return the value INVALID_SEQUENCE. + * + * Params: + * s = the array whose first code point is to be decoded + */ + abstract dchar safeDecode(ref const(ubyte)[] s); + + /** + * Returns the sequence of ubytes to be used to represent + * any character which cannot be represented in the encoding scheme. + * + * Normally this will be a representation of some substitution + * character, such as U+FFFD or '?'. + */ + abstract @property immutable(ubyte)[] replacementSequence(); + } + + /** + * Returns true if the array is encoded correctly + * + * Params: + * s = the array to be tested + */ + bool isValid(const(ubyte)[] s) + { + while (s.length != 0) + { + if (safeDecode(s) == INVALID_SEQUENCE) + return false; + } + return true; + } + + /** + * Returns the length of the longest possible substring, starting from + * the first element, which is validly encoded. + * + * Params: + * s = the array to be tested + */ + size_t validLength()(const(ubyte)[] s) + { + const(ubyte)[] r = s; + const(ubyte)[] t = s; + while (s.length != 0) + { + if (safeDecode(s) == INVALID_SEQUENCE) break; + t = s; + } + return r.length - t.length; + } + + /** + * Sanitizes an array by replacing malformed ubyte sequences with valid + * ubyte sequences. The result is guaranteed to be valid for this + * encoding scheme. + * + * If the input array is already valid, this function returns the + * original, otherwise it constructs a new array by replacing all illegal + * sequences with the encoding scheme's replacement sequence. + * + * Params: + * s = the string to be sanitized + */ + immutable(ubyte)[] sanitize()(immutable(ubyte)[] s) + { + auto n = validLength(s); + if (n == s.length) return s; + + auto repSeq = replacementSequence; + + // Count how long the string needs to be. + // Overestimating is not a problem + auto len = s.length; + const(ubyte)[] t = s[n..$]; + while (t.length != 0) + { + immutable c = safeDecode(t); + assert(c == INVALID_SEQUENCE); + len += repSeq.length; + t = t[validLength(t)..$]; + } + + // Now do the write + ubyte[] array = new ubyte[len]; + array[0 .. n] = s[0 .. n]; + auto offset = n; + + t = s[n..$]; + while (t.length != 0) + { + immutable c = safeDecode(t); + assert(c == INVALID_SEQUENCE); + array[offset .. offset+repSeq.length] = repSeq[]; + offset += repSeq.length; + n = validLength(t); + array[offset .. offset+n] = t[0 .. n]; + offset += n; + t = t[n..$]; + } + return cast(immutable(ubyte)[])array[0 .. offset]; + } + + /** + * Returns the length of the first encoded sequence. + * + * The input to this function MUST be validly encoded. + * This is enforced by the function's in-contract. + * + * Params: + * s = the array to be sliced + */ + size_t firstSequence()(const(ubyte)[] s) + in + { + assert(s.length != 0); + const(ubyte)[] u = s; + assert(safeDecode(u) != INVALID_SEQUENCE); + } + body + { + const(ubyte)[] t = s; + decode(s); + return t.length - s.length; + } + + /** + * Returns the total number of code points encoded in a ubyte array. + * + * The input to this function MUST be validly encoded. + * This is enforced by the function's in-contract. + * + * Params: + * s = the string to be counted + */ + size_t count()(const(ubyte)[] s) + in + { + assert(isValid(s)); + } + body + { + size_t n = 0; + while (s.length != 0) + { + decode(s); + ++n; + } + return n; + } + + /** + * Returns the array index at which the (n+1)th code point begins. + * + * The input to this function MUST be validly encoded. + * This is enforced by the function's in-contract. + * + * Params: + * s = the string to be counted + * n = the current code point index + */ + ptrdiff_t index()(const(ubyte)[] s, size_t n) + in + { + assert(isValid(s)); + assert(n >= 0); + } + body + { + const(ubyte)[] t = s; + for (size_t i=0; i= 0x20 && c < 0x80) + { + r ~= c; + } + else + { + r ~= "\\x"; + r ~= toHexDigit(c >> 4); + r ~= toHexDigit(c); + } + } + r ~= "\""; + return r; + } + + string makeReadable(wstring s) + { + string r = "\""; + foreach (wchar c;s) + { + if (c >= 0x20 && c < 0x80) + { + r ~= cast(char) c; + } + else + { + r ~= "\\u"; + r ~= toHexDigit(c >> 12); + r ~= toHexDigit(c >> 8); + r ~= toHexDigit(c >> 4); + r ~= toHexDigit(c); + } + } + r ~= "\"w"; + return r; + } + + string makeReadable(dstring s) + { + string r = "\""; + foreach (dchar c; s) + { + if (c >= 0x20 && c < 0x80) + { + r ~= cast(char) c; + } + else if (c < 0x10000) + { + r ~= "\\u"; + r ~= toHexDigit(c >> 12); + r ~= toHexDigit(c >> 8); + r ~= toHexDigit(c >> 4); + r ~= toHexDigit(c); + } + else + { + r ~= "\\U00"; + r ~= toHexDigit(c >> 20); + r ~= toHexDigit(c >> 16); + r ~= toHexDigit(c >> 12); + r ~= toHexDigit(c >> 8); + r ~= toHexDigit(c >> 4); + r ~= toHexDigit(c); + } + } + r ~= "\"d"; + return r; + } + + char toHexDigit(int n) + { + return "0123456789ABCDEF"[n & 0xF]; + } +} + +/** Definitions of common Byte Order Marks. +The elements of the $(D enum) can used as indices into $(D bomTable) to get +matching $(D BOMSeq). +*/ +enum BOM +{ + none = 0, /// no BOM was found + utf32be = 1, /// [0x00, 0x00, 0xFE, 0xFF] + utf32le = 2, /// [0xFF, 0xFE, 0x00, 0x00] + utf7 = 3, /* [0x2B, 0x2F, 0x76, 0x38] + [0x2B, 0x2F, 0x76, 0x39], + [0x2B, 0x2F, 0x76, 0x2B], + [0x2B, 0x2F, 0x76, 0x2F], + [0x2B, 0x2F, 0x76, 0x38, 0x2D] + */ + utf1 = 8, /// [0xF7, 0x64, 0x4C] + utfebcdic = 9, /// [0xDD, 0x73, 0x66, 0x73] + scsu = 10, /// [0x0E, 0xFE, 0xFF] + bocu1 = 11, /// [0xFB, 0xEE, 0x28] + gb18030 = 12, /// [0x84, 0x31, 0x95, 0x33] + utf8 = 13, /// [0xEF, 0xBB, 0xBF] + utf16be = 14, /// [0xFE, 0xFF] + utf16le = 15 /// [0xFF, 0xFE] +} + +/// The type stored inside $(D bomTable). +alias BOMSeq = Tuple!(BOM, "schema", ubyte[], "sequence"); + +/** Mapping of a byte sequence to $(B Byte Order Mark (BOM)) +*/ +immutable bomTable = [ + BOMSeq(BOM.none, null), + BOMSeq(BOM.utf32be, cast(ubyte[])([0x00, 0x00, 0xFE, 0xFF])), + BOMSeq(BOM.utf32le, cast(ubyte[])([0xFF, 0xFE, 0x00, 0x00])), + BOMSeq(BOM.utf7, cast(ubyte[])([0x2B, 0x2F, 0x76, 0x39])), + BOMSeq(BOM.utf7, cast(ubyte[])([0x2B, 0x2F, 0x76, 0x2B])), + BOMSeq(BOM.utf7, cast(ubyte[])([0x2B, 0x2F, 0x76, 0x2F])), + BOMSeq(BOM.utf7, cast(ubyte[])([0x2B, 0x2F, 0x76, 0x38, 0x2D])), + BOMSeq(BOM.utf7, cast(ubyte[])([0x2B, 0x2F, 0x76, 0x38])), + BOMSeq(BOM.utf1, cast(ubyte[])([0xF7, 0x64, 0x4C])), + BOMSeq(BOM.utfebcdic, cast(ubyte[])([0xDD, 0x73, 0x66, 0x73])), + BOMSeq(BOM.scsu, cast(ubyte[])([0x0E, 0xFE, 0xFF])), + BOMSeq(BOM.bocu1, cast(ubyte[])([0xFB, 0xEE, 0x28])), + BOMSeq(BOM.gb18030, cast(ubyte[])([0x84, 0x31, 0x95, 0x33])), + BOMSeq(BOM.utf8, cast(ubyte[])([0xEF, 0xBB, 0xBF])), + BOMSeq(BOM.utf16be, cast(ubyte[])([0xFE, 0xFF])), + BOMSeq(BOM.utf16le, cast(ubyte[])([0xFF, 0xFE])) +]; + +/** Returns a $(D BOMSeq) for a given $(D input). +If no $(D BOM) is present the $(D BOMSeq) for $(D BOM.none) is +returned. The $(D BOM) sequence at the beginning of the range will +not be comsumed from the passed range. If you pass a reference type +range make sure that $(D save) creates a deep copy. + +Params: + input = The sequence to check for the $(D BOM) + +Returns: + the found $(D BOMSeq) corresponding to the passed $(D input). +*/ +immutable(BOMSeq) getBOM(Range)(Range input) +if (isForwardRange!Range && is(Unqual!(ElementType!Range) == ubyte)) +{ + import std.algorithm.searching : startsWith; + foreach (it; bomTable[1 .. $]) + { + if (startsWith(input.save, it.sequence)) + { + return it; + } + } + + return bomTable[0]; +} + +/// +@system unittest +{ + import std.format : format; + + auto ts = dchar(0x0000FEFF) ~ "Hello World"d; + + auto entry = getBOM(cast(ubyte[]) ts); + version (BigEndian) + { + assert(entry.schema == BOM.utf32be, format("%s", entry.schema)); + } + else + { + assert(entry.schema == BOM.utf32le, format("%s", entry.schema)); + } +} + +@system unittest +{ + import std.format : format; + + foreach (idx, it; bomTable) + { + auto s = it[1] ~ cast(ubyte[])"hello world"; + auto i = getBOM(s); + assert(i[0] == bomTable[idx][0]); + + if (idx < 4 || idx > 7) // get around the multiple utf7 bom's + { + assert(i[0] == BOM.init + idx); + assert(i[1] == it[1]); + } + } +} + +@safe pure unittest +{ + struct BOMInputRange + { + ubyte[] arr; + + @property ubyte front() + { + return this.arr.front; + } + + @property bool empty() + { + return this.arr.empty; + } + + void popFront() + { + this.arr = this.arr[1 .. $]; + } + + @property typeof(this) save() + { + return this; + } + } + + static assert( isInputRange!BOMInputRange); + static assert(!isArray!BOMInputRange); + + ubyte[] dummyEnd = [0,0,0,0]; + + foreach (idx, it; bomTable[1 .. $]) + { + { + auto ir = BOMInputRange(it.sequence.dup); + + auto b = getBOM(ir); + assert(b.schema == it.schema); + assert(ir.arr == it.sequence); + } + + { + auto noBom = it.sequence[0 .. 1].dup ~ dummyEnd; + size_t oldLen = noBom.length; + assert(oldLen - 4 < it.sequence.length); + + auto ir = BOMInputRange(noBom.dup); + auto b = getBOM(ir); + assert(b.schema == BOM.none); + assert(noBom.length == oldLen); + } + } +} + +/** Constant defining a fully decoded BOM */ +enum dchar utfBOM = 0xfeff; diff --git a/libphobos/src/std/exception.d b/libphobos/src/std/exception.d new file mode 100644 index 0000000..73afadc --- /dev/null +++ b/libphobos/src/std/exception.d @@ -0,0 +1,2316 @@ +// Written in the D programming language. + +/++ + This module defines functions related to exceptions and general error + handling. It also defines functions intended to aid in unit testing. + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE, +$(TR $(TH Category) $(TH Functions)) +$(TR $(TD Assumptions) $(TD + $(LREF assertNotThrown) + $(LREF assertThrown) + $(LREF assumeUnique) + $(LREF assumeWontThrow) + $(LREF mayPointTo) +)) +$(TR $(TD Enforce) $(TD + $(LREF doesPointTo) + $(LREF enforce) + $(LREF enforceEx) + $(LREF errnoEnforce) +)) +$(TR $(TD Handlers) $(TD + $(LREF collectException) + $(LREF collectExceptionMsg) + $(LREF ifThrown) + $(LREF handle) +)) +$(TR $(TD Other) $(TD + $(LREF basicExceptionCtors) + $(LREF emptyExceptionMsg) + $(LREF ErrnoException) + $(LREF RangePrimitive) +)) +) + + Synopsis of some of std.exception's functions: + -------------------- + string synopsis() + { + FILE* f = enforce(fopen("some/file")); + // f is not null from here on + FILE* g = enforce!WriteException(fopen("some/other/file", "w")); + // g is not null from here on + + Exception e = collectException(write(g, readln(f))); + if (e) + { + ... an exception occurred... + ... We have the exception to play around with... + } + + string msg = collectExceptionMsg(write(g, readln(f))); + if (msg) + { + ... an exception occurred... + ... We have the message from the exception but not the exception... + } + + char[] line; + enforce(readln(f, line)); + return assumeUnique(line); + } + -------------------- + + Copyright: Copyright Andrei Alexandrescu 2008-, Jonathan M Davis 2011-. + License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0) + Authors: $(HTTP erdani.org, Andrei Alexandrescu) and Jonathan M Davis + Source: $(PHOBOSSRC std/_exception.d) + + +/ +module std.exception; + +import std.range.primitives; +import std.traits; + +/++ + Asserts that the given expression does $(I not) throw the given type + of $(D Throwable). If a $(D Throwable) of the given type is thrown, + it is caught and does not escape assertNotThrown. Rather, an + $(D AssertError) is thrown. However, any other $(D Throwable)s will escape. + + Params: + T = The $(D Throwable) to test for. + expression = The expression to test. + msg = Optional message to output on test failure. + If msg is empty, and the thrown exception has a + non-empty msg field, the exception's msg field + will be output on test failure. + file = The file where the error occurred. + Defaults to $(D __FILE__). + line = The line where the error occurred. + Defaults to $(D __LINE__). + + Throws: + $(D AssertError) if the given $(D Throwable) is thrown. + + Returns: + the result of `expression`. + +/ +auto assertNotThrown(T : Throwable = Exception, E) + (lazy E expression, + string msg = null, + string file = __FILE__, + size_t line = __LINE__) +{ + import core.exception : AssertError; + try + { + return expression(); + } + catch (T t) + { + immutable message = msg.length == 0 ? t.msg : msg; + immutable tail = message.length == 0 ? "." : ": " ~ message; + throw new AssertError("assertNotThrown failed: " ~ T.stringof ~ " was thrown" ~ tail, file, line, t); + } +} +/// +@system unittest +{ + import core.exception : AssertError; + + import std.string; + assertNotThrown!StringException(enforce!StringException(true, "Error!")); + + //Exception is the default. + assertNotThrown(enforce!StringException(true, "Error!")); + + assert(collectExceptionMsg!AssertError(assertNotThrown!StringException( + enforce!StringException(false, "Error!"))) == + `assertNotThrown failed: StringException was thrown: Error!`); +} +@system unittest +{ + import core.exception : AssertError; + import std.string; + assert(collectExceptionMsg!AssertError(assertNotThrown!StringException( + enforce!StringException(false, ""), "Error!")) == + `assertNotThrown failed: StringException was thrown: Error!`); + + assert(collectExceptionMsg!AssertError(assertNotThrown!StringException( + enforce!StringException(false, ""))) == + `assertNotThrown failed: StringException was thrown.`); + + assert(collectExceptionMsg!AssertError(assertNotThrown!StringException( + enforce!StringException(false, ""), "")) == + `assertNotThrown failed: StringException was thrown.`); +} + +@system unittest +{ + import core.exception : AssertError; + + void throwEx(Throwable t) { throw t; } + bool nothrowEx() { return true; } + + try + { + assert(assertNotThrown!Exception(nothrowEx())); + } + catch (AssertError) assert(0); + + try + { + assert(assertNotThrown!Exception(nothrowEx(), "It's a message")); + } + catch (AssertError) assert(0); + + try + { + assert(assertNotThrown!AssertError(nothrowEx())); + } + catch (AssertError) assert(0); + + try + { + assert(assertNotThrown!AssertError(nothrowEx(), "It's a message")); + } + catch (AssertError) assert(0); + + { + bool thrown = false; + try + { + assertNotThrown!Exception( + throwEx(new Exception("It's an Exception"))); + } + catch (AssertError) thrown = true; + assert(thrown); + } + + { + bool thrown = false; + try + { + assertNotThrown!Exception( + throwEx(new Exception("It's an Exception")), "It's a message"); + } + catch (AssertError) thrown = true; + assert(thrown); + } + + { + bool thrown = false; + try + { + assertNotThrown!AssertError( + throwEx(new AssertError("It's an AssertError", __FILE__, __LINE__))); + } + catch (AssertError) thrown = true; + assert(thrown); + } + + { + bool thrown = false; + try + { + assertNotThrown!AssertError( + throwEx(new AssertError("It's an AssertError", __FILE__, __LINE__)), + "It's a message"); + } + catch (AssertError) thrown = true; + assert(thrown); + } +} + +/++ + Asserts that the given expression throws the given type of $(D Throwable). + The $(D Throwable) is caught and does not escape assertThrown. However, + any other $(D Throwable)s $(I will) escape, and if no $(D Throwable) + of the given type is thrown, then an $(D AssertError) is thrown. + + Params: + T = The $(D Throwable) to test for. + expression = The expression to test. + msg = Optional message to output on test failure. + file = The file where the error occurred. + Defaults to $(D __FILE__). + line = The line where the error occurred. + Defaults to $(D __LINE__). + + Throws: + $(D AssertError) if the given $(D Throwable) is not thrown. + +/ +void assertThrown(T : Throwable = Exception, E) + (lazy E expression, + string msg = null, + string file = __FILE__, + size_t line = __LINE__) +{ + import core.exception : AssertError; + + try + expression(); + catch (T) + return; + throw new AssertError("assertThrown failed: No " ~ T.stringof ~ " was thrown" + ~ (msg.length == 0 ? "." : ": ") ~ msg, + file, line); +} +/// +@system unittest +{ + import core.exception : AssertError; + import std.string; + + assertThrown!StringException(enforce!StringException(false, "Error!")); + + //Exception is the default. + assertThrown(enforce!StringException(false, "Error!")); + + assert(collectExceptionMsg!AssertError(assertThrown!StringException( + enforce!StringException(true, "Error!"))) == + `assertThrown failed: No StringException was thrown.`); +} + +@system unittest +{ + import core.exception : AssertError; + + void throwEx(Throwable t) { throw t; } + void nothrowEx() { } + + try + { + assertThrown!Exception(throwEx(new Exception("It's an Exception"))); + } + catch (AssertError) assert(0); + + try + { + assertThrown!Exception(throwEx(new Exception("It's an Exception")), + "It's a message"); + } + catch (AssertError) assert(0); + + try + { + assertThrown!AssertError(throwEx(new AssertError("It's an AssertError", + __FILE__, __LINE__))); + } + catch (AssertError) assert(0); + + try + { + assertThrown!AssertError(throwEx(new AssertError("It's an AssertError", + __FILE__, __LINE__)), + "It's a message"); + } + catch (AssertError) assert(0); + + + { + bool thrown = false; + try + assertThrown!Exception(nothrowEx()); + catch (AssertError) + thrown = true; + + assert(thrown); + } + + { + bool thrown = false; + try + assertThrown!Exception(nothrowEx(), "It's a message"); + catch (AssertError) + thrown = true; + + assert(thrown); + } + + { + bool thrown = false; + try + assertThrown!AssertError(nothrowEx()); + catch (AssertError) + thrown = true; + + assert(thrown); + } + + { + bool thrown = false; + try + assertThrown!AssertError(nothrowEx(), "It's a message"); + catch (AssertError) + thrown = true; + + assert(thrown); + } +} + + +/++ + Enforces that the given value is true. + + Params: + value = The value to test. + E = Exception type to throw if the value evalues to false. + msg = The error message to put in the exception if it is thrown. + file = The source file of the caller. + line = The line number of the caller. + + Returns: $(D value), if `cast(bool) value` is true. Otherwise, + $(D new Exception(msg)) is thrown. + + Note: + $(D enforce) is used to throw exceptions and is therefore intended to + aid in error handling. It is $(I not) intended for verifying the logic + of your program. That is what $(D assert) is for. Also, do not use + $(D enforce) inside of contracts (i.e. inside of $(D in) and $(D out) + blocks and $(D invariant)s), because they will be compiled out when + compiling with $(I -release). Use $(D assert) in contracts. + + Example: + -------------------- + auto f = enforce(fopen("data.txt")); + auto line = readln(f); + enforce(line.length, "Expected a non-empty line."); + -------------------- + +/ +T enforce(E : Throwable = Exception, T)(T value, lazy const(char)[] msg = null, +string file = __FILE__, size_t line = __LINE__) +if (is(typeof({ if (!value) {} }))) +{ + if (!value) bailOut!E(file, line, msg); + return value; +} + +/++ + Enforces that the given value is true. + + Params: + value = The value to test. + dg = The delegate to be called if the value evaluates to false. + file = The source file of the caller. + line = The line number of the caller. + + Returns: $(D value), if `cast(bool) value` is true. Otherwise, the given + delegate is called. + + The safety and purity of this function are inferred from $(D Dg)'s safety + and purity. + +/ +T enforce(T, Dg, string file = __FILE__, size_t line = __LINE__) + (T value, scope Dg dg) +if (isSomeFunction!Dg && is(typeof( dg() )) && + is(typeof({ if (!value) {} }))) +{ + if (!value) dg(); + return value; +} + +private void bailOut(E : Throwable = Exception)(string file, size_t line, in char[] msg) +{ + static if (is(typeof(new E(string.init, string.init, size_t.init)))) + { + throw new E(msg ? msg.idup : "Enforcement failed", file, line); + } + else static if (is(typeof(new E(string.init, size_t.init)))) + { + throw new E(file, line); + } + else + { + static assert(0, "Expected this(string, string, size_t) or this(string, size_t)" ~ + " constructor for " ~ __traits(identifier, E)); + } +} + +@safe unittest +{ + assert(enforce(123) == 123); + + try + { + enforce(false, "error"); + assert(false); + } + catch (Exception e) + { + assert(e.msg == "error"); + assert(e.file == __FILE__); + assert(e.line == __LINE__-7); + } +} + +@safe unittest +{ + // Issue 10510 + extern(C) void cFoo() { } + enforce(false, &cFoo); +} + +// purity and safety inference test +@system unittest +{ + import std.meta : AliasSeq; + + foreach (EncloseSafe; AliasSeq!(false, true)) + foreach (EnclosePure; AliasSeq!(false, true)) + { + foreach (BodySafe; AliasSeq!(false, true)) + foreach (BodyPure; AliasSeq!(false, true)) + { + enum code = + "delegate void() " ~ + (EncloseSafe ? "@safe " : "") ~ + (EnclosePure ? "pure " : "") ~ + "{ enforce(true, { " ~ + "int n; " ~ + (BodySafe ? "" : "auto p = &n + 10; " ) ~ // unsafe code + (BodyPure ? "" : "static int g; g = 10; ") ~ // impure code + "}); " ~ + "}"; + enum expect = + (BodySafe || !EncloseSafe) && (!EnclosePure || BodyPure); + + version (none) + pragma(msg, "safe = ", EncloseSafe?1:0, "/", BodySafe?1:0, ", ", + "pure = ", EnclosePure?1:0, "/", BodyPure?1:0, ", ", + "expect = ", expect?"OK":"NG", ", ", + "code = ", code); + + static assert(__traits(compiles, mixin(code)()) == expect); + } + } +} + +// Test for bugzilla 8637 +@system unittest +{ + struct S + { + static int g; + ~this() {} // impure & unsafe destructor + bool opCast(T:bool)() { + int* p = cast(int*) 0; // unsafe operation + int n = g; // impure operation + return true; + } + } + S s; + + enforce(s); + enforce(s, {}); + enforce(s, new Exception("")); + + errnoEnforce(s); + + alias E1 = Exception; + static class E2 : Exception + { + this(string fn, size_t ln) { super("", fn, ln); } + } + static class E3 : Exception + { + this(string msg) { super(msg, __FILE__, __LINE__); } + } + enforce!E1(s); + enforce!E2(s); +} + +@safe unittest +{ + // Issue 14685 + + class E : Exception + { + this() { super("Not found"); } + } + static assert(!__traits(compiles, { enforce!E(false); })); +} + +/++ + Enforces that the given value is true. + + Params: + value = The value to test. + ex = The exception to throw if the value evaluates to false. + + Returns: $(D value), if `cast(bool) value` is true. Otherwise, $(D ex) is + thrown. + + Example: + -------------------- + auto f = enforce(fopen("data.txt")); + auto line = readln(f); + enforce(line.length, new IOException); // expect a non-empty line + -------------------- + +/ +T enforce(T)(T value, lazy Throwable ex) +{ + if (!value) throw ex(); + return value; +} + +@safe unittest +{ + assertNotThrown(enforce(true, new Exception("this should not be thrown"))); + assertThrown(enforce(false, new Exception("this should be thrown"))); +} + +/++ + Enforces that the given value is true, throwing an `ErrnoException` if it + is not. + + Params: + value = The value to test. + msg = The message to include in the `ErrnoException` if it is thrown. + + Returns: $(D value), if `cast(bool) value` is true. Otherwise, + $(D new ErrnoException(msg)) is thrown. It is assumed that the last + operation set $(D errno) to an error code corresponding with the failed + condition. + + Example: + -------------------- + auto f = errnoEnforce(fopen("data.txt")); + auto line = readln(f); + enforce(line.length); // expect a non-empty line + -------------------- + +/ +T errnoEnforce(T, string file = __FILE__, size_t line = __LINE__) + (T value, lazy string msg = null) +{ + if (!value) throw new ErrnoException(msg, file, line); + return value; +} + + +/++ + If $(D !value) is $(D false), $(D value) is returned. Otherwise, + $(D new E(msg, file, line)) is thrown. Or if $(D E) doesn't take a message + and can be constructed with $(D new E(file, line)), then + $(D new E(file, line)) will be thrown. + + This is legacy name, it is recommended to use $(D enforce!E) instead. + + Example: + -------------------- + auto f = enforceEx!FileMissingException(fopen("data.txt")); + auto line = readln(f); + enforceEx!DataCorruptionException(line.length); + -------------------- + +/ +template enforceEx(E : Throwable) +if (is(typeof(new E("", __FILE__, __LINE__)))) +{ + /++ Ditto +/ + T enforceEx(T)(T value, lazy string msg = "", string file = __FILE__, size_t line = __LINE__) + { + if (!value) throw new E(msg, file, line); + return value; + } +} + +/++ Ditto +/ +template enforceEx(E : Throwable) +if (is(typeof(new E(__FILE__, __LINE__))) && !is(typeof(new E("", __FILE__, __LINE__)))) +{ + /++ Ditto +/ + T enforceEx(T)(T value, string file = __FILE__, size_t line = __LINE__) + { + if (!value) throw new E(file, line); + return value; + } +} + +@system unittest +{ + import core.exception : OutOfMemoryError; + import std.array : empty; + assertNotThrown(enforceEx!Exception(true)); + assertNotThrown(enforceEx!Exception(true, "blah")); + assertNotThrown(enforceEx!OutOfMemoryError(true)); + + { + auto e = collectException(enforceEx!Exception(false)); + assert(e !is null); + assert(e.msg.empty); + assert(e.file == __FILE__); + assert(e.line == __LINE__ - 4); + } + + { + auto e = collectException(enforceEx!Exception(false, "hello", "file", 42)); + assert(e !is null); + assert(e.msg == "hello"); + assert(e.file == "file"); + assert(e.line == 42); + } + + { + auto e = collectException!Error(enforceEx!OutOfMemoryError(false)); + assert(e !is null); + assert(e.msg == "Memory allocation failed"); + assert(e.file == __FILE__); + assert(e.line == __LINE__ - 4); + } + + { + auto e = collectException!Error(enforceEx!OutOfMemoryError(false, "file", 42)); + assert(e !is null); + assert(e.msg == "Memory allocation failed"); + assert(e.file == "file"); + assert(e.line == 42); + } + + static assert(!is(typeof(enforceEx!int(true)))); +} + +@safe unittest +{ + alias enf = enforceEx!Exception; + assertNotThrown(enf(true)); + assertThrown(enf(false, "blah")); +} + + +/++ + Catches and returns the exception thrown from the given expression. + If no exception is thrown, then null is returned and $(D result) is + set to the result of the expression. + + Note that while $(D collectException) $(I can) be used to collect any + $(D Throwable) and not just $(D Exception)s, it is generally ill-advised to + catch anything that is neither an $(D Exception) nor a type derived from + $(D Exception). So, do not use $(D collectException) to collect + non-$(D Exception)s unless you're sure that that's what you really want to + do. + + Params: + T = The type of exception to catch. + expression = The expression which may throw an exception. + result = The result of the expression if no exception is thrown. ++/ +T collectException(T = Exception, E)(lazy E expression, ref E result) +{ + try + { + result = expression(); + } + catch (T e) + { + return e; + } + return null; +} +/// +@system unittest +{ + int b; + int foo() { throw new Exception("blah"); } + assert(collectException(foo(), b)); + + int[] a = new int[3]; + import core.exception : RangeError; + assert(collectException!RangeError(a[4], b)); +} + +/++ + Catches and returns the exception thrown from the given expression. + If no exception is thrown, then null is returned. $(D E) can be + $(D void). + + Note that while $(D collectException) $(I can) be used to collect any + $(D Throwable) and not just $(D Exception)s, it is generally ill-advised to + catch anything that is neither an $(D Exception) nor a type derived from + $(D Exception). So, do not use $(D collectException) to collect + non-$(D Exception)s unless you're sure that that's what you really want to + do. + + Params: + T = The type of exception to catch. + expression = The expression which may throw an exception. ++/ +T collectException(T : Throwable = Exception, E)(lazy E expression) +{ + try + { + expression(); + } + catch (T t) + { + return t; + } + return null; +} + +@safe unittest +{ + int foo() { throw new Exception("blah"); } + assert(collectException(foo())); +} + +/++ + Catches the exception thrown from the given expression and returns the + msg property of that exception. If no exception is thrown, then null is + returned. $(D E) can be $(D void). + + If an exception is thrown but it has an empty message, then + $(D emptyExceptionMsg) is returned. + + Note that while $(D collectExceptionMsg) $(I can) be used to collect any + $(D Throwable) and not just $(D Exception)s, it is generally ill-advised to + catch anything that is neither an $(D Exception) nor a type derived from + $(D Exception). So, do not use $(D collectExceptionMsg) to collect + non-$(D Exception)s unless you're sure that that's what you really want to + do. + + Params: + T = The type of exception to catch. + expression = The expression which may throw an exception. ++/ +string collectExceptionMsg(T = Exception, E)(lazy E expression) +{ + import std.array : empty; + try + { + expression(); + + return cast(string) null; + } + catch (T e) + return e.msg.empty ? emptyExceptionMsg : e.msg; +} +/// +@safe unittest +{ + void throwFunc() { throw new Exception("My Message."); } + assert(collectExceptionMsg(throwFunc()) == "My Message."); + + void nothrowFunc() {} + assert(collectExceptionMsg(nothrowFunc()) is null); + + void throwEmptyFunc() { throw new Exception(""); } + assert(collectExceptionMsg(throwEmptyFunc()) == emptyExceptionMsg); +} + +/++ + Value that collectExceptionMsg returns when it catches an exception + with an empty exception message. + +/ +enum emptyExceptionMsg = ""; + +/** + * Casts a mutable array to an immutable array in an idiomatic + * manner. Technically, $(D assumeUnique) just inserts a cast, + * but its name documents assumptions on the part of the + * caller. $(D assumeUnique(arr)) should only be called when + * there are no more active mutable aliases to elements of $(D + * arr). To strengthen this assumption, $(D assumeUnique(arr)) + * also clears $(D arr) before returning. Essentially $(D + * assumeUnique(arr)) indicates commitment from the caller that there + * is no more mutable access to any of $(D arr)'s elements + * (transitively), and that all future accesses will be done through + * the immutable array returned by $(D assumeUnique). + * + * Typically, $(D assumeUnique) is used to return arrays from + * functions that have allocated and built them. + * + * Params: + * array = The array to cast to immutable. + * + * Returns: The immutable array. + * + * Example: + * + * ---- + * string letters() + * { + * char[] result = new char['z' - 'a' + 1]; + * foreach (i, ref e; result) + * { + * e = cast(char)('a' + i); + * } + * return assumeUnique(result); + * } + * ---- + * + * The use in the example above is correct because $(D result) + * was private to $(D letters) and is inaccessible in writing + * after the function returns. The following example shows an + * incorrect use of $(D assumeUnique). + * + * Bad: + * + * ---- + * private char[] buffer; + * string letters(char first, char last) + * { + * if (first >= last) return null; // fine + * auto sneaky = buffer; + * sneaky.length = last - first + 1; + * foreach (i, ref e; sneaky) + * { + * e = cast(char)('a' + i); + * } + * return assumeUnique(sneaky); // BAD + * } + * ---- + * + * The example above wreaks havoc on client code because it is + * modifying arrays that callers considered immutable. To obtain an + * immutable array from the writable array $(D buffer), replace + * the last line with: + * ---- + * return to!(string)(sneaky); // not that sneaky anymore + * ---- + * + * The call will duplicate the array appropriately. + * + * Note that checking for uniqueness during compilation is + * possible in certain cases, especially when a function is + * marked as a pure function. The following example does not + * need to call assumeUnique because the compiler can infer the + * uniqueness of the array in the pure function: + * ---- + * string letters() pure + * { + * char[] result = new char['z' - 'a' + 1]; + * foreach (i, ref e; result) + * { + * e = cast(char)('a' + i); + * } + * return result; + * } + * ---- + * + * For more on infering uniqueness see the $(B unique) and + * $(B lent) keywords in the + * $(HTTP archjava.fluid.cs.cmu.edu/papers/oopsla02.pdf, ArchJava) + * language. + * + * The downside of using $(D assumeUnique)'s + * convention-based usage is that at this time there is no + * formal checking of the correctness of the assumption; + * on the upside, the idiomatic use of $(D assumeUnique) is + * simple and rare enough to be tolerable. + * + */ +immutable(T)[] assumeUnique(T)(T[] array) pure nothrow +{ + return .assumeUnique(array); // call ref version +} +/// ditto +immutable(T)[] assumeUnique(T)(ref T[] array) pure nothrow +{ + auto result = cast(immutable(T)[]) array; + array = null; + return result; +} +/// ditto +immutable(T[U]) assumeUnique(T, U)(ref T[U] array) pure nothrow +{ + auto result = cast(immutable(T[U])) array; + array = null; + return result; +} + +@system unittest +{ + // @system due to assumeUnique + int[] arr = new int[1]; + auto arr1 = assumeUnique(arr); + assert(is(typeof(arr1) == immutable(int)[]) && arr == null); +} + +// @@@BUG@@@ +version (none) @system unittest +{ + int[string] arr = ["a":1]; + auto arr1 = assumeUnique(arr); + assert(is(typeof(arr1) == immutable(int[string])) && arr == null); +} + +/** + * Wraps a possibly-throwing expression in a $(D nothrow) wrapper so that it + * can be called by a $(D nothrow) function. + * + * This wrapper function documents commitment on the part of the caller that + * the appropriate steps have been taken to avoid whatever conditions may + * trigger an exception during the evaluation of $(D expr). If it turns out + * that the expression $(I does) throw at runtime, the wrapper will throw an + * $(D AssertError). + * + * (Note that $(D Throwable) objects such as $(D AssertError) that do not + * subclass $(D Exception) may be thrown even from $(D nothrow) functions, + * since they are considered to be serious runtime problems that cannot be + * recovered from.) + * + * Params: + * expr = The expression asserted not to throw. + * msg = The message to include in the `AssertError` if the assumption turns + * out to be false. + * file = The source file name of the caller. + * line = The line number of the caller. + * + * Returns: + * The value of `expr`, if any. + */ +T assumeWontThrow(T)(lazy T expr, + string msg = null, + string file = __FILE__, + size_t line = __LINE__) nothrow +{ + import core.exception : AssertError; + try + { + return expr; + } + catch (Exception e) + { + import std.range.primitives : empty; + immutable tail = msg.empty ? "." : ": " ~ msg; + throw new AssertError("assumeWontThrow failed: Expression did throw" ~ + tail, file, line); + } +} + +/// +@safe unittest +{ + import std.math : sqrt; + + // This function may throw. + int squareRoot(int x) + { + if (x < 0) + throw new Exception("Tried to take root of negative number"); + return cast(int) sqrt(cast(double) x); + } + + // This function never throws. + int computeLength(int x, int y) nothrow + { + // Since x*x + y*y is always positive, we can safely assume squareRoot + // won't throw, and use it to implement this nothrow function. If it + // does throw (e.g., if x*x + y*y overflows a 32-bit value), then the + // program will terminate. + return assumeWontThrow(squareRoot(x*x + y*y)); + } + + assert(computeLength(3, 4) == 5); +} + +@system unittest +{ + import core.exception : AssertError; + + void alwaysThrows() + { + throw new Exception("I threw up"); + } + void bad() nothrow + { + assumeWontThrow(alwaysThrows()); + } + assertThrown!AssertError(bad()); +} + +/** +Checks whether a given source object contains pointers or references to a given +target object. + +Params: + source = The source object + target = The target object + +Returns: $(D true) if $(D source)'s representation embeds a pointer +that points to $(D target)'s representation or somewhere inside +it. + +If $(D source) is or contains a dynamic array, then, then these functions will check +if there is overlap between the dynamic array and $(D target)'s representation. + +If $(D source) is a class, then it will be handled as a pointer. + +If $(D target) is a pointer, a dynamic array or a class, then these functions will only +check if $(D source) points to $(D target), $(I not) what $(D target) references. + +If $(D source) is or contains a union, then there may be either false positives or +false negatives: + +$(D doesPointTo) will return $(D true) if it is absolutely certain +$(D source) points to $(D target). It may produce false negatives, but never +false positives. This function should be prefered when trying to validate +input data. + +$(D mayPointTo) will return $(D false) if it is absolutely certain +$(D source) does not point to $(D target). It may produce false positives, but never +false negatives. This function should be prefered for defensively choosing a +code path. + +Note: Evaluating $(D doesPointTo(x, x)) checks whether $(D x) has +internal pointers. This should only be done as an assertive test, +as the language is free to assume objects don't have internal pointers +(TDPL 7.1.3.5). +*/ +bool doesPointTo(S, T, Tdummy=void)(auto ref const S source, ref const T target) @trusted pure nothrow +if (__traits(isRef, source) || isDynamicArray!S || + isPointer!S || is(S == class)) +{ + static if (isPointer!S || is(S == class) || is(S == interface)) + { + const m = *cast(void**) &source; + const b = cast(void*) ⌖ + const e = b + target.sizeof; + return b <= m && m < e; + } + else static if (is(S == struct) || is(S == union)) + { + foreach (i, Subobj; typeof(source.tupleof)) + static if (!isUnionAliased!(S, i)) + if (doesPointTo(source.tupleof[i], target)) return true; + return false; + } + else static if (isStaticArray!S) + { + foreach (size_t i; 0 .. S.length) + if (doesPointTo(source[i], target)) return true; + return false; + } + else static if (isDynamicArray!S) + { + import std.array : overlap; + return overlap(cast(void[]) source, cast(void[])(&target)[0 .. 1]).length != 0; + } + else + { + return false; + } +} + +// for shared objects +/// ditto +bool doesPointTo(S, T)(auto ref const shared S source, ref const shared T target) @trusted pure nothrow +{ + return doesPointTo!(shared S, shared T, void)(source, target); +} + +/// ditto +bool mayPointTo(S, T, Tdummy=void)(auto ref const S source, ref const T target) @trusted pure nothrow +if (__traits(isRef, source) || isDynamicArray!S || + isPointer!S || is(S == class)) +{ + static if (isPointer!S || is(S == class) || is(S == interface)) + { + const m = *cast(void**) &source; + const b = cast(void*) ⌖ + const e = b + target.sizeof; + return b <= m && m < e; + } + else static if (is(S == struct) || is(S == union)) + { + foreach (i, Subobj; typeof(source.tupleof)) + if (mayPointTo(source.tupleof[i], target)) return true; + return false; + } + else static if (isStaticArray!S) + { + foreach (size_t i; 0 .. S.length) + if (mayPointTo(source[i], target)) return true; + return false; + } + else static if (isDynamicArray!S) + { + import std.array : overlap; + return overlap(cast(void[]) source, cast(void[])(&target)[0 .. 1]).length != 0; + } + else + { + return false; + } +} + +// for shared objects +/// ditto +bool mayPointTo(S, T)(auto ref const shared S source, ref const shared T target) @trusted pure nothrow +{ + return mayPointTo!(shared S, shared T, void)(source, target); +} + +/// Pointers +@system unittest +{ + int i = 0; + int* p = null; + assert(!p.doesPointTo(i)); + p = &i; + assert( p.doesPointTo(i)); +} + +/// Structs and Unions +@system unittest +{ + struct S + { + int v; + int* p; + } + int i; + auto s = S(0, &i); + + // structs and unions "own" their members + // pointsTo will answer true if one of the members pointsTo. + assert(!s.doesPointTo(s.v)); //s.v is just v member of s, so not pointed. + assert( s.p.doesPointTo(i)); //i is pointed by s.p. + assert( s .doesPointTo(i)); //which means i is pointed by s itself. + + // Unions will behave exactly the same. Points to will check each "member" + // individually, even if they share the same memory +} + +/// Arrays (dynamic and static) +@system unittest +{ + int i; + int[] slice = [0, 1, 2, 3, 4]; + int[5] arr = [0, 1, 2, 3, 4]; + int*[] slicep = [&i]; + int*[1] arrp = [&i]; + + // A slice points to all of its members: + assert( slice.doesPointTo(slice[3])); + assert(!slice[0 .. 2].doesPointTo(slice[3])); // Object 3 is outside of the + // slice [0 .. 2] + + // Note that a slice will not take into account what its members point to. + assert( slicep[0].doesPointTo(i)); + assert(!slicep .doesPointTo(i)); + + // static arrays are objects that own their members, just like structs: + assert(!arr.doesPointTo(arr[0])); // arr[0] is just a member of arr, so not + // pointed. + assert( arrp[0].doesPointTo(i)); // i is pointed by arrp[0]. + assert( arrp .doesPointTo(i)); // which means i is pointed by arrp + // itself. + + // Notice the difference between static and dynamic arrays: + assert(!arr .doesPointTo(arr[0])); + assert( arr[].doesPointTo(arr[0])); + assert( arrp .doesPointTo(i)); + assert(!arrp[].doesPointTo(i)); +} + +/// Classes +@system unittest +{ + class C + { + this(int* p){this.p = p;} + int* p; + } + int i; + C a = new C(&i); + C b = a; + + // Classes are a bit particular, as they are treated like simple pointers + // to a class payload. + assert( a.p.doesPointTo(i)); // a.p points to i. + assert(!a .doesPointTo(i)); // Yet a itself does not point i. + + //To check the class payload itself, iterate on its members: + () + { + import std.traits : Fields; + + foreach (index, _; Fields!C) + if (doesPointTo(a.tupleof[index], i)) + return; + assert(0); + }(); + + // To check if a class points a specific payload, a direct memmory check + // can be done: + auto aLoc = cast(ubyte[__traits(classInstanceSize, C)]*) a; + assert(b.doesPointTo(*aLoc)); // b points to where a is pointing +} + +@system unittest +{ + struct S1 { int a; S1 * b; } + S1 a1; + S1 * p = &a1; + assert(doesPointTo(p, a1)); + + S1 a2; + a2.b = &a1; + assert(doesPointTo(a2, a1)); + + struct S3 { int[10] a; } + S3 a3; + auto a4 = a3.a[2 .. 3]; + assert(doesPointTo(a4, a3)); + + auto a5 = new double[4]; + auto a6 = a5[1 .. 2]; + assert(!doesPointTo(a5, a6)); + + auto a7 = new double[3]; + auto a8 = new double[][1]; + a8[0] = a7; + assert(!doesPointTo(a8[0], a8[0])); + + // don't invoke postblit on subobjects + { + static struct NoCopy { this(this) { assert(0); } } + static struct Holder { NoCopy a, b, c; } + Holder h; + cast(void) doesPointTo(h, h); + } + + shared S3 sh3; + shared sh3sub = sh3.a[]; + assert(doesPointTo(sh3sub, sh3)); + + int[] darr = [1, 2, 3, 4]; + + //dynamic arrays don't point to each other, or slices of themselves + assert(!doesPointTo(darr, darr)); + assert(!doesPointTo(darr[0 .. 1], darr)); + + //But they do point their elements + foreach (i; 0 .. 4) + assert(doesPointTo(darr, darr[i])); + assert(doesPointTo(darr[0 .. 3], darr[2])); + assert(!doesPointTo(darr[0 .. 3], darr[3])); +} + +@system unittest +{ + //tests with static arrays + //Static arrays themselves are just objects, and don't really *point* to anything. + //They aggregate their contents, much the same way a structure aggregates its attributes. + //*However* The elements inside the static array may themselves point to stuff. + + //Standard array + int[2] k; + assert(!doesPointTo(k, k)); //an array doesn't point to itself + //Technically, k doesn't point its elements, although it does alias them + assert(!doesPointTo(k, k[0])); + assert(!doesPointTo(k, k[1])); + //But an extracted slice will point to the same array. + assert(doesPointTo(k[], k)); + assert(doesPointTo(k[], k[1])); + + //An array of pointers + int*[2] pp; + int a; + int b; + pp[0] = &a; + assert( doesPointTo(pp, a)); //The array contains a pointer to a + assert(!doesPointTo(pp, b)); //The array does NOT contain a pointer to b + assert(!doesPointTo(pp, pp)); //The array does not point itslef + + //A struct containing a static array of pointers + static struct S + { + int*[2] p; + } + S s; + s.p[0] = &a; + assert( doesPointTo(s, a)); //The struct contains an array that points a + assert(!doesPointTo(s, b)); //But doesn't point b + assert(!doesPointTo(s, s)); //The struct doesn't actually point itslef. + + //An array containing structs that have pointers + static struct SS + { + int* p; + } + SS[2] ss = [SS(&a), SS(null)]; + assert( doesPointTo(ss, a)); //The array contains a struct that points to a + assert(!doesPointTo(ss, b)); //The array doesn't contains a struct that points to b + assert(!doesPointTo(ss, ss)); //The array doesn't point itself. +} + + +@system unittest //Unions +{ + int i; + union U //Named union + { + size_t asInt = 0; + int* asPointer; + } + struct S + { + union //Anonymous union + { + size_t asInt = 0; + int* asPointer; + } + } + + U u; + S s; + assert(!doesPointTo(u, i)); + assert(!doesPointTo(s, i)); + assert(!mayPointTo(u, i)); + assert(!mayPointTo(s, i)); + + u.asPointer = &i; + s.asPointer = &i; + assert(!doesPointTo(u, i)); + assert(!doesPointTo(s, i)); + assert( mayPointTo(u, i)); + assert( mayPointTo(s, i)); + + u.asInt = cast(size_t)&i; + s.asInt = cast(size_t)&i; + assert(!doesPointTo(u, i)); + assert(!doesPointTo(s, i)); + assert( mayPointTo(u, i)); + assert( mayPointTo(s, i)); +} + +@system unittest //Classes +{ + int i; + static class A + { + int* p; + } + A a = new A, b = a; + assert(!doesPointTo(a, b)); //a does not point to b + a.p = &i; + assert(!doesPointTo(a, i)); //a does not point to i +} +@safe unittest //alias this test +{ + static int i; + static int j; + struct S + { + int* p; + @property int* foo(){return &i;} + alias foo this; + } + assert(is(S : int*)); + S s = S(&j); + assert(!doesPointTo(s, i)); + assert( doesPointTo(s, j)); + assert( doesPointTo(cast(int*) s, i)); + assert(!doesPointTo(cast(int*) s, j)); +} +@safe unittest //more alias this opCast +{ + void* p; + class A + { + void* opCast(T)() if (is(T == void*)) + { + return p; + } + alias foo = opCast!(void*); + alias foo this; + } + assert(!doesPointTo(A.init, p)); + assert(!mayPointTo(A.init, p)); +} + +/+ +Returns true if the field at index $(D i) in ($D T) shares its address with another field. + +Note: This does not merelly check if the field is a member of an union, but also that +it is not a single child. ++/ +package enum isUnionAliased(T, size_t i) = isUnionAliasedImpl!T(T.tupleof[i].offsetof); +private bool isUnionAliasedImpl(T)(size_t offset) +{ + int count = 0; + foreach (i, U; typeof(T.tupleof)) + if (T.tupleof[i].offsetof == offset) + ++count; + return count >= 2; +} +// +@safe unittest +{ + static struct S + { + int a0; //Not aliased + union + { + int a1; //Not aliased + } + union + { + int a2; //Aliased + int a3; //Aliased + } + union A4 + { + int b0; //Not aliased + } + A4 a4; + union A5 + { + int b0; //Aliased + int b1; //Aliased + } + A5 a5; + } + + static assert(!isUnionAliased!(S, 0)); //a0; + static assert(!isUnionAliased!(S, 1)); //a1; + static assert( isUnionAliased!(S, 2)); //a2; + static assert( isUnionAliased!(S, 3)); //a3; + static assert(!isUnionAliased!(S, 4)); //a4; + static assert(!isUnionAliased!(S.A4, 0)); //a4.b0; + static assert(!isUnionAliased!(S, 5)); //a5; + static assert( isUnionAliased!(S.A5, 0)); //a5.b0; + static assert( isUnionAliased!(S.A5, 1)); //a5.b1; +} + +package string errnoString(int errno) nothrow @trusted +{ + import core.stdc.string : strlen; + version (CRuntime_Glibc) + { + import core.stdc.string : strerror_r; + char[1024] buf = void; + auto s = strerror_r(errno, buf.ptr, buf.length); + } + else version (Posix) + { + // XSI-compliant + import core.stdc.string : strerror_r; + char[1024] buf = void; + const(char)* s; + if (strerror_r(errno, buf.ptr, buf.length) == 0) + s = buf.ptr; + else + return "Unknown error"; + } + else + { + import core.stdc.string : strerror; + auto s = strerror(errno); + } + return s[0 .. s.strlen].idup; +} + +/********************* + * Thrown if errors that set $(D errno) occur. + */ +class ErrnoException : Exception +{ + final @property uint errno() { return _errno; } /// Operating system error code. + private uint _errno; + /// Constructor which takes an error message. The current global $(REF errno, core,stdc,errno) value is used as error code. + this(string msg, string file = null, size_t line = 0) @trusted + { + import core.stdc.errno : errno; + this(msg, errno, file, line); + } + /// Constructor which takes an error message and error code. + this(string msg, int errno, string file = null, size_t line = 0) @trusted + { + _errno = errno; + super(msg ~ " (" ~ errnoString(errno) ~ ")", file, line); + } + + @system unittest + { + import core.stdc.errno : errno, EAGAIN; + + auto old = errno; + scope(exit) errno = old; + + errno = EAGAIN; + auto ex = new ErrnoException("oh no"); + assert(ex.errno == EAGAIN); + } + + @system unittest + { + import core.stdc.errno : EAGAIN; + auto ex = new ErrnoException("oh no", EAGAIN); + assert(ex.errno == EAGAIN); + } +} + +/++ + ML-style functional exception handling. Runs the supplied expression and + returns its result. If the expression throws a $(D Throwable), runs the + supplied error handler instead and return its result. The error handler's + type must be the same as the expression's type. + + Params: + E = The type of $(D Throwable)s to catch. Defaults to $(D Exception) + T1 = The type of the expression. + T2 = The return type of the error handler. + expression = The expression to run and return its result. + errorHandler = The handler to run if the expression throwed. + + Returns: + expression, if it does not throw. Otherwise, returns the result of + errorHandler. + + Example: + -------------------- + //Revert to a default value upon an error: + assert("x".to!int().ifThrown(0) == 0); + -------------------- + + You can also chain multiple calls to ifThrown, each capturing errors from the + entire preceding expression. + + Example: + -------------------- + //Chaining multiple calls to ifThrown to attempt multiple things in a row: + string s="true"; + assert(s.to!int(). + ifThrown(cast(int) s.to!double()). + ifThrown(cast(int) s.to!bool()) + == 1); + + //Respond differently to different types of errors + assert(enforce("x".to!int() < 1).to!string() + .ifThrown!ConvException("not a number") + .ifThrown!Exception("number too small") + == "not a number"); + -------------------- + + The expression and the errorHandler must have a common type they can both + be implicitly casted to, and that type will be the type of the compound + expression. + + Example: + -------------------- + //null and new Object have a common type(Object). + static assert(is(typeof(null.ifThrown(new Object())) == Object)); + static assert(is(typeof((new Object()).ifThrown(null)) == Object)); + + //1 and new Object do not have a common type. + static assert(!__traits(compiles, 1.ifThrown(new Object()))); + static assert(!__traits(compiles, (new Object()).ifThrown(1))); + -------------------- + + If you need to use the actual thrown exception, you can use a delegate. + Example: + -------------------- + //Use a lambda to get the thrown object. + assert("%s".format().ifThrown!Exception(e => e.classinfo.name) == "std.format.FormatException"); + -------------------- + +/ +//lazy version +CommonType!(T1, T2) ifThrown(E : Throwable = Exception, T1, T2)(lazy scope T1 expression, lazy scope T2 errorHandler) +{ + static assert(!is(typeof(return) == void), + "The error handler's return value(" + ~ T2.stringof ~ + ") does not have a common type with the expression(" + ~ T1.stringof ~ + ")." + ); + try + { + return expression(); + } + catch (E) + { + return errorHandler(); + } +} + +///ditto +//delegate version +CommonType!(T1, T2) ifThrown(E : Throwable, T1, T2)(lazy scope T1 expression, scope T2 delegate(E) errorHandler) +{ + static assert(!is(typeof(return) == void), + "The error handler's return value(" + ~ T2.stringof ~ + ") does not have a common type with the expression(" + ~ T1.stringof ~ + ")." + ); + try + { + return expression(); + } + catch (E e) + { + return errorHandler(e); + } +} + +///ditto +//delegate version, general overload to catch any Exception +CommonType!(T1, T2) ifThrown(T1, T2)(lazy scope T1 expression, scope T2 delegate(Exception) errorHandler) +{ + static assert(!is(typeof(return) == void), + "The error handler's return value(" + ~ T2.stringof ~ + ") does not have a common type with the expression(" + ~ T1.stringof ~ + ")." + ); + try + { + return expression(); + } + catch (Exception e) + { + return errorHandler(e); + } +} + +//Verify Examples +@system unittest +{ + import std.conv; + import std.string; + //Revert to a default value upon an error: + assert("x".to!int().ifThrown(0) == 0); + + //Chaining multiple calls to ifThrown to attempt multiple things in a row: + string s="true"; + assert(s.to!int(). + ifThrown(cast(int) s.to!double()). + ifThrown(cast(int) s.to!bool()) + == 1); + + //Respond differently to different types of errors + assert(enforce("x".to!int() < 1).to!string() + .ifThrown!ConvException("not a number") + .ifThrown!Exception("number too small") + == "not a number"); + + //null and new Object have a common type(Object). + static assert(is(typeof(null.ifThrown(new Object())) == Object)); + static assert(is(typeof((new Object()).ifThrown(null)) == Object)); + + //1 and new Object do not have a common type. + static assert(!__traits(compiles, 1.ifThrown(new Object()))); + static assert(!__traits(compiles, (new Object()).ifThrown(1))); + + //Use a lambda to get the thrown object. + assert("%s".format().ifThrown(e => e.classinfo.name) == "std.format.FormatException"); +} + +@system unittest +{ + import core.exception; + import std.conv; + import std.string; + //Basic behaviour - all versions. + assert("1".to!int().ifThrown(0) == 1); + assert("x".to!int().ifThrown(0) == 0); + assert("1".to!int().ifThrown!ConvException(0) == 1); + assert("x".to!int().ifThrown!ConvException(0) == 0); + assert("1".to!int().ifThrown(e=>0) == 1); + assert("x".to!int().ifThrown(e=>0) == 0); + static if (__traits(compiles, 0.ifThrown!Exception(e => 0))) //This will only work with a fix that was not yet pulled + { + assert("1".to!int().ifThrown!ConvException(e=>0) == 1); + assert("x".to!int().ifThrown!ConvException(e=>0) == 0); + } + + //Exceptions other than stated not caught. + assert("x".to!int().ifThrown!StringException(0).collectException!ConvException() !is null); + static if (__traits(compiles, 0.ifThrown!Exception(e => 0))) //This will only work with a fix that was not yet pulled + { + assert("x".to!int().ifThrown!StringException(e=>0).collectException!ConvException() !is null); + } + + //Default does not include errors. + int throwRangeError() { throw new RangeError; } + assert(throwRangeError().ifThrown(0).collectException!RangeError() !is null); + assert(throwRangeError().ifThrown(e=>0).collectException!RangeError() !is null); + + //Incompatible types are not accepted. + static assert(!__traits(compiles, 1.ifThrown(new Object()))); + static assert(!__traits(compiles, (new Object()).ifThrown(1))); + static assert(!__traits(compiles, 1.ifThrown(e=>new Object()))); + static assert(!__traits(compiles, (new Object()).ifThrown(e=>1))); +} + +version (unittest) package +@property void assertCTFEable(alias dg)() +{ + static assert({ cast(void) dg(); return true; }()); + cast(void) dg(); +} + +/** This $(D enum) is used to select the primitives of the range to handle by the + $(LREF handle) range wrapper. The values of the $(D enum) can be $(D OR)'d to + select multiple primitives to be handled. + + $(D RangePrimitive.access) is a shortcut for the access primitives; $(D front), + $(D back) and $(D opIndex). + + $(D RangePrimitive.pop) is a shortcut for the mutating primitives; + $(D popFront) and $(D popBack). + */ +enum RangePrimitive +{ + front = 0b00_0000_0001, /// + back = 0b00_0000_0010, /// Ditto + popFront = 0b00_0000_0100, /// Ditto + popBack = 0b00_0000_1000, /// Ditto + empty = 0b00_0001_0000, /// Ditto + save = 0b00_0010_0000, /// Ditto + length = 0b00_0100_0000, /// Ditto + opDollar = 0b00_1000_0000, /// Ditto + opIndex = 0b01_0000_0000, /// Ditto + opSlice = 0b10_0000_0000, /// Ditto + access = front | back | opIndex, /// Ditto + pop = popFront | popBack, /// Ditto +} + +/** Handle exceptions thrown from range primitives. + +Use the $(LREF RangePrimitive) enum to specify which primitives to _handle. +Multiple range primitives can be handled at once by using the $(D OR) operator +or the pseudo-primitives $(D RangePrimitive.access) and $(D RangePrimitive.pop). +All handled primitives must have return types or values compatible with the +user-supplied handler. + +Params: + E = The type of $(D Throwable) to _handle. + primitivesToHandle = Set of range primitives to _handle. + handler = The callable that is called when a handled primitive throws a + $(D Throwable) of type $(D E). The handler must accept arguments of + the form $(D E, ref IRange) and its return value is used as the primitive's + return value whenever $(D E) is thrown. For $(D opIndex), the handler can + optionally recieve a third argument; the index that caused the exception. + input = The range to _handle. + +Returns: A wrapper $(D struct) that preserves the range interface of $(D input). + +Note: +Infinite ranges with slicing support must return an instance of +$(REF Take, std,range) when sliced with a specific lower and upper +bound (see $(REF hasSlicing, std,range,primitives)); $(D handle) deals with +this by $(D take)ing 0 from the return value of the handler function and +returning that when an exception is caught. +*/ +auto handle(E : Throwable, RangePrimitive primitivesToHandle, alias handler, Range)(Range input) +if (isInputRange!Range) +{ + static struct Handler + { + private Range range; + + static if (isForwardRange!Range) + { + @property typeof(this) save() + { + static if (primitivesToHandle & RangePrimitive.save) + { + try + { + return typeof(this)(range.save); + } + catch (E exception) + { + return typeof(this)(handler(exception, this.range)); + } + } + else + return typeof(this)(range.save); + } + } + + static if (isInfinite!Range) + { + enum bool empty = false; + } + else + { + @property bool empty() + { + static if (primitivesToHandle & RangePrimitive.empty) + { + try + { + return this.range.empty; + } + catch (E exception) + { + return handler(exception, this.range); + } + } + else + return this.range.empty; + } + } + + @property auto ref front() + { + static if (primitivesToHandle & RangePrimitive.front) + { + try + { + return this.range.front; + } + catch (E exception) + { + return handler(exception, this.range); + } + } + else + return this.range.front; + } + + void popFront() + { + static if (primitivesToHandle & RangePrimitive.popFront) + { + try + { + this.range.popFront(); + } + catch (E exception) + { + handler(exception, this.range); + } + } + else + this.range.popFront(); + } + + static if (isBidirectionalRange!Range) + { + @property auto ref back() + { + static if (primitivesToHandle & RangePrimitive.back) + { + try + { + return this.range.back; + } + catch (E exception) + { + return handler(exception, this.range); + } + } + else + return this.range.back; + } + + void popBack() + { + static if (primitivesToHandle & RangePrimitive.popBack) + { + try + { + this.range.popBack(); + } + catch (E exception) + { + handler(exception, this.range); + } + } + else + this.range.popBack(); + } + } + + static if (isRandomAccessRange!Range) + { + auto ref opIndex(size_t index) + { + static if (primitivesToHandle & RangePrimitive.opIndex) + { + try + { + return this.range[index]; + } + catch (E exception) + { + static if (__traits(compiles, handler(exception, this.range, index))) + return handler(exception, this.range, index); + else + return handler(exception, this.range); + } + } + else + return this.range[index]; + } + } + + static if (hasLength!Range) + { + @property auto length() + { + static if (primitivesToHandle & RangePrimitive.length) + { + try + { + return this.range.length; + } + catch (E exception) + { + return handler(exception, this.range); + } + } + else + return this.range.length; + } + } + + static if (hasSlicing!Range) + { + static if (hasLength!Range) + { + typeof(this) opSlice(size_t lower, size_t upper) + { + static if (primitivesToHandle & RangePrimitive.opSlice) + { + try + { + return typeof(this)(this.range[lower .. upper]); + } + catch (E exception) + { + return typeof(this)(handler(exception, this.range)); + } + } + else + return typeof(this)(this.range[lower .. upper]); + } + } + else static if (is(typeof(Range.init[size_t.init .. $]))) + { + import std.range : Take, takeExactly; + static struct DollarToken {} + enum opDollar = DollarToken.init; + + typeof(this) opSlice(size_t lower, DollarToken) + { + static if (primitivesToHandle & RangePrimitive.opSlice) + { + try + { + return typeof(this)(this.range[lower .. $]); + } + catch (E exception) + { + return typeof(this)(handler(exception, this.range)); + } + } + else + return typeof(this)(this.range[lower .. $]); + } + + Take!Handler opSlice(size_t lower, size_t upper) + { + static if (primitivesToHandle & RangePrimitive.opSlice) + { + try + { + return takeExactly(typeof(this)(this.range[lower .. $]), upper - 1); + } + catch (E exception) + { + return takeExactly(typeof(this)(handler(exception, this.range)), 0); + } + } + else + return takeExactly(typeof(this)(this.range[lower .. $]), upper - 1); + } + } + } + } + + return Handler(input); +} + +/// +pure @safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map, splitter; + import std.conv : to, ConvException; + + auto s = "12,1337z32,54,2,7,9,1z,6,8"; + + // The next line composition will throw when iterated + // as some elements of the input do not convert to integer + auto r = s.splitter(',').map!(a => to!int(a)); + + // Substitute 0 for cases of ConvException + auto h = r.handle!(ConvException, RangePrimitive.front, (e, r) => 0); + assert(h.equal([12, 0, 54, 2, 7, 9, 0, 6, 8])); +} + +/// +pure @safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : retro; + import std.utf : UTFException; + + auto str = "hello\xFFworld"; // 0xFF is an invalid UTF-8 code unit + + auto handled = str.handle!(UTFException, RangePrimitive.access, + (e, r) => ' '); // Replace invalid code points with spaces + + assert(handled.equal("hello world")); // `front` is handled, + assert(handled.retro.equal("dlrow olleh")); // as well as `back` +} + +pure nothrow @safe unittest +{ + static struct ThrowingRange + { + pure @safe: + @property bool empty() + { + throw new Exception("empty has thrown"); + } + + @property int front() + { + throw new Exception("front has thrown"); + } + + @property int back() + { + throw new Exception("back has thrown"); + } + + void popFront() + { + throw new Exception("popFront has thrown"); + } + + void popBack() + { + throw new Exception("popBack has thrown"); + } + + int opIndex(size_t) + { + throw new Exception("opIndex has thrown"); + } + + ThrowingRange opSlice(size_t, size_t) + { + throw new Exception("opSlice has thrown"); + } + + @property size_t length() + { + throw new Exception("length has thrown"); + } + + alias opDollar = length; + + @property ThrowingRange save() + { + throw new Exception("save has thrown"); + } + } + + static assert(isInputRange!ThrowingRange); + static assert(isForwardRange!ThrowingRange); + static assert(isBidirectionalRange!ThrowingRange); + static assert(hasSlicing!ThrowingRange); + static assert(hasLength!ThrowingRange); + + auto f = ThrowingRange(); + auto fb = f.handle!(Exception, RangePrimitive.front | RangePrimitive.back, + (e, r) => -1)(); + assert(fb.front == -1); + assert(fb.back == -1); + assertThrown(fb.popFront()); + assertThrown(fb.popBack()); + assertThrown(fb.empty); + assertThrown(fb.save); + assertThrown(fb[0]); + + auto accessRange = f.handle!(Exception, RangePrimitive.access, + (e, r) => -1); + assert(accessRange.front == -1); + assert(accessRange.back == -1); + assert(accessRange[0] == -1); + assertThrown(accessRange.popFront()); + assertThrown(accessRange.popBack()); + + auto pfb = f.handle!(Exception, RangePrimitive.pop, (e, r) => -1)(); + + pfb.popFront(); // this would throw otherwise + pfb.popBack(); // this would throw otherwise + + auto em = f.handle!(Exception, + RangePrimitive.empty, (e, r) => false)(); + + assert(!em.empty); + + auto arr = f.handle!(Exception, + RangePrimitive.opIndex, (e, r) => 1337)(); + + assert(arr[0] == 1337); + + auto arr2 = f.handle!(Exception, + RangePrimitive.opIndex, (e, r, i) => i)(); + + assert(arr2[0] == 0); + assert(arr2[1337] == 1337); + + auto save = f.handle!(Exception, + RangePrimitive.save, + function(Exception e, ref ThrowingRange r) { + return ThrowingRange(); + })(); + + save.save; + + auto slice = f.handle!(Exception, + RangePrimitive.opSlice, (e, r) => ThrowingRange())(); + + auto sliced = slice[0 .. 1337]; // this would throw otherwise + + static struct Infinite + { + import std.range : Take; + pure @safe: + enum bool empty = false; + int front() { assert(false); } + void popFront() { assert(false); } + Infinite save() @property { assert(false); } + static struct DollarToken {} + enum opDollar = DollarToken.init; + Take!Infinite opSlice(size_t, size_t) { assert(false); } + Infinite opSlice(size_t, DollarToken) + { + throw new Exception("opSlice has thrown"); + } + } + + static assert(isInputRange!Infinite); + static assert(isInfinite!Infinite); + static assert(hasSlicing!Infinite); + + assertThrown(Infinite()[0 .. $]); + + auto infinite = Infinite.init.handle!(Exception, + RangePrimitive.opSlice, (e, r) => Infinite())(); + + auto infSlice = infinite[0 .. $]; // this would throw otherwise +} + + +/++ + Convenience mixin for trivially sub-classing exceptions + + Even trivially sub-classing an exception involves writing boilerplate code + for the constructor to: 1$(RPAREN) correctly pass in the source file and line number + the exception was thrown from; 2$(RPAREN) be usable with $(LREF enforce) which + expects exception constructors to take arguments in a fixed order. This + mixin provides that boilerplate code. + + Note however that you need to mark the $(B mixin) line with at least a + minimal (i.e. just $(B ///)) DDoc comment if you want the mixed-in + constructors to be documented in the newly created Exception subclass. + + $(RED Current limitation): Due to + $(LINK2 https://issues.dlang.org/show_bug.cgi?id=11500, bug #11500), + currently the constructors specified in this mixin cannot be overloaded with + any other custom constructors. Thus this mixin can currently only be used + when no such custom constructors need to be explicitly specified. + +/ +mixin template basicExceptionCtors() +{ + /++ + Params: + msg = The message for the exception. + file = The file where the exception occurred. + line = The line number where the exception occurred. + next = The previous exception in the chain of exceptions, if any. + +/ + this(string msg, string file = __FILE__, size_t line = __LINE__, + Throwable next = null) @nogc @safe pure nothrow + { + super(msg, file, line, next); + } + + /++ + Params: + msg = The message for the exception. + next = The previous exception in the chain of exceptions. + file = The file where the exception occurred. + line = The line number where the exception occurred. + +/ + this(string msg, Throwable next, string file = __FILE__, + size_t line = __LINE__) @nogc @safe pure nothrow + { + super(msg, file, line, next); + } +} + +/// +@safe unittest +{ + class MeaCulpa: Exception + { + /// + mixin basicExceptionCtors; + } + + try + throw new MeaCulpa("test"); + catch (MeaCulpa e) + { + assert(e.msg == "test"); + assert(e.file == __FILE__); + assert(e.line == __LINE__ - 5); + } +} + +@safe pure nothrow unittest +{ + class TestException : Exception { mixin basicExceptionCtors; } + auto e = new Exception("msg"); + auto te1 = new TestException("foo"); + auto te2 = new TestException("foo", e); +} + +@safe unittest +{ + class TestException : Exception { mixin basicExceptionCtors; } + auto e = new Exception("!!!"); + + auto te1 = new TestException("message", "file", 42, e); + assert(te1.msg == "message"); + assert(te1.file == "file"); + assert(te1.line == 42); + assert(te1.next is e); + + auto te2 = new TestException("message", e, "file", 42); + assert(te2.msg == "message"); + assert(te2.file == "file"); + assert(te2.line == 42); + assert(te2.next is e); + + auto te3 = new TestException("foo"); + assert(te3.msg == "foo"); + assert(te3.file == __FILE__); + assert(te3.line == __LINE__ - 3); + assert(te3.next is null); + + auto te4 = new TestException("foo", e); + assert(te4.msg == "foo"); + assert(te4.file == __FILE__); + assert(te4.line == __LINE__ - 3); + assert(te4.next is e); +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/affix_allocator.d b/libphobos/src/std/experimental/allocator/building_blocks/affix_allocator.d new file mode 100644 index 0000000..cfeae0c --- /dev/null +++ b/libphobos/src/std/experimental/allocator/building_blocks/affix_allocator.d @@ -0,0 +1,441 @@ +/// +module std.experimental.allocator.building_blocks.affix_allocator; + +/** + +Allocator that adds some extra data before (of type $(D Prefix)) and/or after +(of type $(D Suffix)) any allocation made with its parent allocator. This is +useful for uses where additional allocation-related information is needed, such +as mutexes, reference counts, or walls for debugging memory corruption errors. + +If $(D Prefix) is not $(D void), $(D Allocator) must guarantee an alignment at +least as large as $(D Prefix.alignof). + +Suffixes are slower to get at because of alignment rounding, so prefixes should +be preferred. However, small prefixes blunt the alignment so if a large +alignment with a small affix is needed, suffixes should be chosen. + +The following methods are defined if $(D Allocator) defines them, and forward to it: $(D deallocateAll), $(D empty), $(D owns). + */ +struct AffixAllocator(Allocator, Prefix, Suffix = void) +{ + import std.algorithm.comparison : min; + import std.conv : emplace; + import std.experimental.allocator : IAllocator, theAllocator; + import std.experimental.allocator.common : stateSize, forwardToMember, + roundUpToMultipleOf, alignedAt, alignDownTo, roundUpToMultipleOf, + hasStaticallyKnownAlignment; + import std.math : isPowerOf2; + import std.traits : hasMember; + import std.typecons : Ternary; + + static if (hasStaticallyKnownAlignment!Allocator) + { + static assert( + !stateSize!Prefix || Allocator.alignment >= Prefix.alignof, + "AffixAllocator does not work with allocators offering a smaller" + ~ " alignment than the prefix alignment."); + } + static assert(alignment % Suffix.alignof == 0, + "This restriction could be relaxed in the future."); + + /** + If $(D Prefix) is $(D void), the alignment is that of the parent. Otherwise, the alignment is the same as the $(D Prefix)'s alignment. + */ + static if (hasStaticallyKnownAlignment!Allocator) + { + enum uint alignment = isPowerOf2(stateSize!Prefix) + ? min(stateSize!Prefix, Allocator.alignment) + : (stateSize!Prefix ? Prefix.alignof : Allocator.alignment); + } + else static if (is(Prefix == void)) + { + enum uint alignment = platformAlignment; + } + else + { + enum uint alignment = Prefix.alignof; + } + + /** + If the parent allocator $(D Allocator) is stateful, an instance of it is + stored as a member. Otherwise, $(D AffixAllocator) uses + `Allocator.instance`. In either case, the name $(D _parent) is uniformly + used for accessing the parent allocator. + */ + static if (stateSize!Allocator) + { + Allocator _parent; + static if (is(Allocator == IAllocator)) + { + Allocator parent() + { + if (_parent is null) _parent = theAllocator; + assert(alignment <= _parent.alignment); + return _parent; + } + } + else + { + alias parent = _parent; + } + } + else + { + alias parent = Allocator.instance; + } + + private template Impl() + { + + size_t goodAllocSize(size_t s) + { + import std.experimental.allocator.common : goodAllocSize; + auto a = actualAllocationSize(s); + return roundUpToMultipleOf(parent.goodAllocSize(a) + - stateSize!Prefix - stateSize!Suffix, + this.alignment); + } + + private size_t actualAllocationSize(size_t s) const + { + assert(s > 0); + static if (!stateSize!Suffix) + { + return s + stateSize!Prefix; + } + else + { + return + roundUpToMultipleOf(s + stateSize!Prefix, Suffix.alignof) + + stateSize!Suffix; + } + } + + private void[] actualAllocation(void[] b) const + { + assert(b !is null); + return (b.ptr - stateSize!Prefix) + [0 .. actualAllocationSize(b.length)]; + } + + void[] allocate(size_t bytes) + { + if (!bytes) return null; + auto result = parent.allocate(actualAllocationSize(bytes)); + if (result is null) return null; + static if (stateSize!Prefix) + { + assert(result.ptr.alignedAt(Prefix.alignof)); + emplace!Prefix(cast(Prefix*) result.ptr); + } + static if (stateSize!Suffix) + { + auto suffixP = result.ptr + result.length - Suffix.sizeof; + assert(suffixP.alignedAt(Suffix.alignof)); + emplace!Suffix(cast(Suffix*)(suffixP)); + } + return result[stateSize!Prefix .. stateSize!Prefix + bytes]; + } + + static if (hasMember!(Allocator, "allocateAll")) + void[] allocateAll() + { + auto result = parent.allocateAll(); + if (result is null) return null; + if (result.length < actualAllocationSize(1)) + { + deallocate(result); + return null; + } + static if (stateSize!Prefix) + { + assert(result.length > stateSize!Prefix); + emplace!Prefix(cast(Prefix*) result.ptr); + result = result[stateSize!Prefix .. $]; + } + static if (stateSize!Suffix) + { + assert(result.length > stateSize!Suffix); + // Ehm, find a properly aligned place for the suffix + auto p = (result.ptr + result.length - stateSize!Suffix) + .alignDownTo(Suffix.alignof); + assert(p > result.ptr); + emplace!Suffix(cast(Suffix*) p); + result = result[0 .. p - result.ptr]; + } + return result; + } + + static if (hasMember!(Allocator, "owns")) + Ternary owns(void[] b) + { + if (b is null) return Ternary.no; + return parent.owns(actualAllocation(b)); + } + + static if (hasMember!(Allocator, "resolveInternalPointer")) + Ternary resolveInternalPointer(const void* p, ref void[] result) + { + void[] p1; + Ternary r = parent.resolveInternalPointer(p, p1); + if (r != Ternary.yes || p1 is null) + return r; + p1 = p1[stateSize!Prefix .. $]; + auto p2 = (p1.ptr + p1.length - stateSize!Suffix) + .alignDownTo(Suffix.alignof); + result = p1[0 .. p2 - p1.ptr]; + return Ternary.yes; + } + + static if (!stateSize!Suffix && hasMember!(Allocator, "expand")) + bool expand(ref void[] b, size_t delta) + { + if (!b.ptr) return delta == 0; + auto t = actualAllocation(b); + const result = parent.expand(t, delta); + if (!result) return false; + b = b.ptr[0 .. b.length + delta]; + return true; + } + + static if (hasMember!(Allocator, "reallocate")) + bool reallocate(ref void[] b, size_t s) + { + if (b is null) + { + b = allocate(s); + return b.length == s; + } + auto t = actualAllocation(b); + const result = parent.reallocate(t, actualAllocationSize(s)); + if (!result) return false; // no harm done + b = t.ptr[stateSize!Prefix .. stateSize!Prefix + s]; + return true; + } + + static if (hasMember!(Allocator, "deallocate")) + bool deallocate(void[] b) + { + if (!b.ptr) return true; + return parent.deallocate(actualAllocation(b)); + } + + /* The following methods are defined if $(D ParentAllocator) defines + them, and forward to it: $(D deallocateAll), $(D empty).*/ + mixin(forwardToMember("parent", + "deallocateAll", "empty")); + + // Computes suffix type given buffer type + private template Payload2Affix(Payload, Affix) + { + static if (is(Payload[] : void[])) + alias Payload2Affix = Affix; + else static if (is(Payload[] : shared(void)[])) + alias Payload2Affix = shared Affix; + else static if (is(Payload[] : immutable(void)[])) + alias Payload2Affix = shared Affix; + else static if (is(Payload[] : const(shared(void))[])) + alias Payload2Affix = shared Affix; + else static if (is(Payload[] : const(void)[])) + alias Payload2Affix = const Affix; + else + static assert(0, "Internal error for type " ~ Payload.stringof); + } + + // Extra functions + static if (stateSize!Prefix) + { + static auto ref prefix(T)(T[] b) + { + assert(b.ptr && b.ptr.alignedAt(Prefix.alignof)); + return (cast(Payload2Affix!(T, Prefix)*) b.ptr)[-1]; + } + } + static if (stateSize!Suffix) + auto ref suffix(T)(T[] b) + { + assert(b.ptr); + auto p = b.ptr - stateSize!Prefix + + actualAllocationSize(b.length); + assert(p && p.alignedAt(Suffix.alignof)); + return (cast(Payload2Affix!(T, Suffix)*) p)[-1]; + } + } + + version (StdDdoc) + { + /** + Standard allocator methods. Each is defined if and only if the parent + allocator defines the homonym method (except for $(D goodAllocSize), + which may use the global default). Also, the methods will be $(D + shared) if the parent allocator defines them as such. + */ + size_t goodAllocSize(size_t); + /// Ditto + void[] allocate(size_t); + /// Ditto + Ternary owns(void[]); + /// Ditto + bool expand(ref void[] b, size_t delta); + /// Ditto + bool reallocate(ref void[] b, size_t s); + /// Ditto + bool deallocate(void[] b); + /// Ditto + bool deallocateAll(); + /// Ditto + Ternary empty(); + + /** + The `instance` singleton is defined if and only if the parent allocator + has no state and defines its own `it` object. + */ + static AffixAllocator instance; + + /** + Affix access functions offering references to the affixes of a + block `b` previously allocated with this allocator. `b` may not be null. + They are defined if and only if the corresponding affix is not `void`. + + The qualifiers of the affix are not always the same as the qualifiers + of the argument. This is because the affixes are not part of the data + itself, but instead are just $(I associated) with the data and known + to the allocator. The table below documents the type of `preffix(b)` and + `affix(b)` depending on the type of `b`. + + $(BOOKTABLE Result of `prefix`/`suffix` depending on argument (`U` is + any unqualified type, `Affix` is `Prefix` or `Suffix`), + $(TR $(TH Argument$(NBSP)Type) $(TH Return) $(TH Comments)) + + $(TR $(TD `shared(U)[]`) $(TD `ref shared Affix`) + $(TD Data is shared across threads and the affix follows suit.)) + + $(TR $(TD `immutable(U)[]`) $(TD `ref shared Affix`) + $(TD Although the data is immutable, the allocator "knows" the + underlying memory is mutable, so `immutable` is elided for the affix + which is independent from the data itself. However, the result is + `shared` because `immutable` is implicitly shareable so multiple + threads may access and manipulate the affix for the same data.)) + + $(TR $(TD `const(shared(U))[]`) $(TD `ref shared Affix`) + $(TD The data is always shareable across threads. Even if the data + is `const`, the affix is modifiable by the same reasoning as for + `immutable`.)) + + $(TR $(TD `const(U)[]`) $(TD `ref const Affix`) + $(TD The input may have originated from `U[]` or `immutable(U)[]`, + so it may be actually shared or not. Returning an unqualified affix + may result in race conditions, whereas returning a `shared` affix + may result in inadvertent sharing of mutable thread-local data + across multiple threads. So the returned type is conservatively + `ref const`.)) + + $(TR $(TD `U[]`) $(TD `ref Affix`) + $(TD Unqualified data has unqualified affixes.)) + ) + + Precondition: `b !is null` and `b` must have been allocated with + this allocator. + */ + static ref auto prefix(T)(T[] b); + /// Ditto + ref auto suffix(T)(T[] b); + } + else static if (is(typeof(Allocator.instance) == shared)) + { + static shared AffixAllocator instance; + shared { mixin Impl!(); } + } + else + { + mixin Impl!(); + static if (stateSize!Allocator == 0) + static __gshared AffixAllocator instance; + } +} + +/// +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + // One word before and after each allocation. + alias A = AffixAllocator!(Mallocator, size_t, size_t); + auto b = A.instance.allocate(11); + A.instance.prefix(b) = 0xCAFE_BABE; + A.instance.suffix(b) = 0xDEAD_BEEF; + assert(A.instance.prefix(b) == 0xCAFE_BABE + && A.instance.suffix(b) == 0xDEAD_BEEF); +} + +@system unittest +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.experimental.allocator : theAllocator, IAllocator; + + // One word before and after each allocation. + auto A = AffixAllocator!(IAllocator, size_t, size_t)(theAllocator); + auto a = A.allocate(11); + A.prefix(a) = 0xCAFE_BABE; + A.suffix(a) = 0xDEAD_BEEF; + assert(A.prefix(a) == 0xCAFE_BABE + && A.suffix(a) == 0xDEAD_BEEF); + + // One word before and after each allocation. + auto B = AffixAllocator!(IAllocator, size_t, size_t)(); + auto b = B.allocate(11); + B.prefix(b) = 0xCAFE_BABE; + B.suffix(b) = 0xDEAD_BEEF; + assert(B.prefix(b) == 0xCAFE_BABE + && B.suffix(b) == 0xDEAD_BEEF); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.bitmapped_block + : BitmappedBlock; + import std.experimental.allocator.common : testAllocator; + testAllocator!({ + auto a = AffixAllocator!(BitmappedBlock!128, ulong, ulong) + (BitmappedBlock!128(new ubyte[128 * 4096])); + return a; + }); +} + +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + alias A = AffixAllocator!(Mallocator, size_t); + auto b = A.instance.allocate(10); + A.instance.prefix(b) = 10; + assert(A.instance.prefix(b) == 10); + + import std.experimental.allocator.building_blocks.null_allocator + : NullAllocator; + alias B = AffixAllocator!(NullAllocator, size_t); + b = B.instance.allocate(100); + assert(b is null); +} + +@system unittest +{ + import std.experimental.allocator; + import std.experimental.allocator.gc_allocator; + import std.typecons : Ternary; + alias MyAllocator = AffixAllocator!(GCAllocator, uint); + auto a = MyAllocator.instance.makeArray!(shared int)(100); + static assert(is(typeof(&MyAllocator.instance.prefix(a)) == shared(uint)*)); + auto b = MyAllocator.instance.makeArray!(shared const int)(100); + static assert(is(typeof(&MyAllocator.instance.prefix(b)) == shared(uint)*)); + auto c = MyAllocator.instance.makeArray!(immutable int)(100); + static assert(is(typeof(&MyAllocator.instance.prefix(c)) == shared(uint)*)); + auto d = MyAllocator.instance.makeArray!(int)(100); + static assert(is(typeof(&MyAllocator.instance.prefix(d)) == uint*)); + auto e = MyAllocator.instance.makeArray!(const int)(100); + static assert(is(typeof(&MyAllocator.instance.prefix(e)) == const(uint)*)); + + void[] p; + assert(MyAllocator.instance.resolveInternalPointer(null, p) == Ternary.no); + Ternary r = MyAllocator.instance.resolveInternalPointer(d.ptr, p); + assert(p.ptr is d.ptr && p.length >= d.length); +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/allocator_list.d b/libphobos/src/std/experimental/allocator/building_blocks/allocator_list.d new file mode 100644 index 0000000..2d0e670 --- /dev/null +++ b/libphobos/src/std/experimental/allocator/building_blocks/allocator_list.d @@ -0,0 +1,640 @@ +/// +module std.experimental.allocator.building_blocks.allocator_list; + +import std.experimental.allocator.building_blocks.null_allocator; +import std.experimental.allocator.common; +import std.experimental.allocator.gc_allocator; +version (unittest) import std.stdio; + +// Turn this on for debugging +// debug = allocator_list; + +/** + +Given an $(LINK2 https://en.wikipedia.org/wiki/Factory_(object-oriented_programming), +object factory) of type `Factory` or a factory function +`factoryFunction`, and optionally also `BookkeepingAllocator` as a supplemental +allocator for bookkeeping, `AllocatorList` creates an allocator that lazily +creates as many allocators are needed for satisfying client allocation requests. + +An embedded list builds a most-recently-used strategy: the most recent +allocators used in calls to either `allocate`, `owns` (successful calls +only), or `deallocate` are tried for new allocations in order of their most +recent use. Thus, although core operations take in theory $(BIGOH k) time for +$(D k) allocators in current use, in many workloads the factor is sublinear. +Details of the actual strategy may change in future releases. + +`AllocatorList` is primarily intended for coarse-grained handling of +allocators, i.e. the number of allocators in the list is expected to be +relatively small compared to the number of allocations handled by each +allocator. However, the per-allocator overhead is small so using +`AllocatorList` with a large number of allocators should be satisfactory as long +as the most-recently-used strategy is fast enough for the application. + +`AllocatorList` makes an effort to return allocated memory back when no +longer used. It does so by destroying empty allocators. However, in order to +avoid thrashing (excessive creation/destruction of allocators under certain use +patterns), it keeps unused allocators for a while. + +Params: +factoryFunction = A function or template function (including function literals). +New allocators are created by calling `factoryFunction(n)` with strictly +positive numbers `n`. Delegates that capture their enviroment are not created +amid concerns regarding garbage creation for the environment. When the factory +needs state, a `Factory` object should be used. + +BookkeepingAllocator = Allocator used for storing bookkeeping data. The size of +bookkeeping data is proportional to the number of allocators. If $(D +BookkeepingAllocator) is $(D NullAllocator), then $(D AllocatorList) is +"ouroboros-style", i.e. it keeps the bookkeeping data in memory obtained from +the allocators themselves. Note that for ouroboros-style management, the size +$(D n) passed to $(D make) will be occasionally different from the size +requested by client code. + +Factory = Type of a factory object that returns new allocators on a need +basis. For an object $(D sweatshop) of type $(D Factory), `sweatshop(n)` should +return an allocator able to allocate at least `n` bytes (i.e. `Factory` must +define `opCall(size_t)` to return an allocator object). Usually the capacity of +allocators created should be much larger than $(D n) such that an allocator can +be used for many subsequent allocations. $(D n) is passed only to ensure the +minimum necessary for the next allocation. The factory object is allowed to hold +state, which will be stored inside `AllocatorList` as a direct `public` member +called `factory`. + +*/ +struct AllocatorList(Factory, BookkeepingAllocator = GCAllocator) +{ + import std.conv : emplace; + import std.experimental.allocator.building_blocks.stats_collector + : StatsCollector, Options; + import std.traits : hasMember; + import std.typecons : Ternary; + + private enum ouroboros = is(BookkeepingAllocator == NullAllocator); + + /** + Alias for `typeof(Factory()(1))`, i.e. the type of the individual + allocators. + */ + alias Allocator = typeof(Factory.init(1)); + // Allocator used internally + private alias SAllocator = StatsCollector!(Allocator, Options.bytesUsed); + + private static struct Node + { + // Allocator in this node + SAllocator a; + Node* next; + + @disable this(this); + + // Is this node unused? + void setUnused() { next = &this; } + bool unused() const { return next is &this; } + + // Just forward everything to the allocator + alias a this; + } + + /** + If $(D BookkeepingAllocator) is not $(D NullAllocator), $(D bkalloc) is + defined and accessible. + */ + + // State is stored in an array, but it has a list threaded through it by + // means of "nextIdx". + + // state + static if (!ouroboros) + { + static if (stateSize!BookkeepingAllocator) BookkeepingAllocator bkalloc; + else alias bkalloc = BookkeepingAllocator.instance; + } + static if (stateSize!Factory) + { + Factory factory; + } + private Node[] allocators; + private Node* root; + + static if (stateSize!Factory) + { + private auto make(size_t n) { return factory(n); } + } + else + { + private auto make(size_t n) { Factory f; return f(n); } + } + + /** + Constructs an `AllocatorList` given a factory object. This constructor is + defined only if `Factory` has state. + */ + static if (stateSize!Factory) + this(ref Factory plant) + { + factory = plant; + } + /// Ditto + static if (stateSize!Factory) + this(Factory plant) + { + factory = plant; + } + + static if (hasMember!(Allocator, "deallocateAll") + && hasMember!(Allocator, "owns")) + ~this() + { + deallocateAll; + } + + /** + The alignment offered. + */ + enum uint alignment = Allocator.alignment; + + /** + Allocate a block of size $(D s). First tries to allocate from the existing + list of already-created allocators. If neither can satisfy the request, + creates a new allocator by calling $(D make(s)) and delegates the request + to it. However, if the allocation fresh off a newly created allocator + fails, subsequent calls to $(D allocate) will not cause more calls to $(D + make). + */ + void[] allocate(size_t s) + { + for (auto p = &root, n = *p; n; p = &n.next, n = *p) + { + auto result = n.allocate(s); + if (result.length != s) continue; + // Bring to front if not already + if (root != n) + { + *p = n.next; + n.next = root; + root = n; + } + return result; + } + // Can't allocate from the current pool. Check if we just added a new + // allocator, in that case it won't do any good to add yet another. + if (root && root.empty == Ternary.yes) + { + // no can do + return null; + } + // Add a new allocator + if (auto a = addAllocator(s)) + { + auto result = a.allocate(s); + assert(owns(result) == Ternary.yes || !result.ptr); + return result; + } + return null; + } + + private void moveAllocators(void[] newPlace) + { + assert(newPlace.ptr.alignedAt(Node.alignof)); + assert(newPlace.length % Node.sizeof == 0); + auto newAllocators = cast(Node[]) newPlace; + assert(allocators.length <= newAllocators.length); + + // Move allocators + foreach (i, ref e; allocators) + { + if (e.unused) + { + newAllocators[i].setUnused; + continue; + } + import core.stdc.string : memcpy; + memcpy(&newAllocators[i].a, &e.a, e.a.sizeof); + if (e.next) + { + newAllocators[i].next = newAllocators.ptr + + (e.next - allocators.ptr); + } + else + { + newAllocators[i].next = null; + } + } + + // Mark the unused portion as unused + foreach (i; allocators.length .. newAllocators.length) + { + newAllocators[i].setUnused; + } + auto toFree = allocators; + + // Change state + root = newAllocators.ptr + (root - allocators.ptr); + allocators = newAllocators; + + // Free the olden buffer + static if (ouroboros) + { + static if (hasMember!(Allocator, "deallocate") + && hasMember!(Allocator, "owns")) + deallocate(toFree); + } + else + { + bkalloc.deallocate(toFree); + } + } + + static if (ouroboros) + private Node* addAllocator(size_t atLeastBytes) + { + void[] t = allocators; + static if (hasMember!(Allocator, "expand") + && hasMember!(Allocator, "owns")) + { + immutable bool expanded = t && this.expand(t, Node.sizeof); + } + else + { + enum expanded = false; + } + if (expanded) + { + import core.stdc.string : memcpy; + assert(t.length % Node.sizeof == 0); + assert(t.ptr.alignedAt(Node.alignof)); + allocators = cast(Node[]) t; + allocators[$ - 1].setUnused; + auto newAlloc = SAllocator(make(atLeastBytes)); + memcpy(&allocators[$ - 1].a, &newAlloc, newAlloc.sizeof); + emplace(&newAlloc); + } + else + { + immutable toAlloc = (allocators.length + 1) * Node.sizeof + + atLeastBytes + 128; + auto newAlloc = SAllocator(make(toAlloc)); + auto newPlace = newAlloc.allocate( + (allocators.length + 1) * Node.sizeof); + if (!newPlace) return null; + moveAllocators(newPlace); + import core.stdc.string : memcpy; + memcpy(&allocators[$ - 1].a, &newAlloc, newAlloc.sizeof); + emplace(&newAlloc); + assert(allocators[$ - 1].owns(allocators) == Ternary.yes); + } + // Insert as new root + if (root != &allocators[$ - 1]) + { + allocators[$ - 1].next = root; + root = &allocators[$ - 1]; + } + else + { + // This is the first one + root.next = null; + } + assert(!root.unused); + return root; + } + + static if (!ouroboros) + private Node* addAllocator(size_t atLeastBytes) + { + void[] t = allocators; + static if (hasMember!(BookkeepingAllocator, "expand")) + immutable bool expanded = bkalloc.expand(t, Node.sizeof); + else + immutable bool expanded = false; + if (expanded) + { + assert(t.length % Node.sizeof == 0); + assert(t.ptr.alignedAt(Node.alignof)); + allocators = cast(Node[]) t; + allocators[$ - 1].setUnused; + } + else + { + // Could not expand, create a new block + t = bkalloc.allocate((allocators.length + 1) * Node.sizeof); + assert(t.length % Node.sizeof == 0); + if (!t.ptr) return null; + moveAllocators(t); + } + assert(allocators[$ - 1].unused); + auto newAlloc = SAllocator(make(atLeastBytes)); + import core.stdc.string : memcpy; + memcpy(&allocators[$ - 1].a, &newAlloc, newAlloc.sizeof); + emplace(&newAlloc); + // Creation succeeded, insert as root + if (allocators.length == 1) + allocators[$ - 1].next = null; + else + allocators[$ - 1].next = root; + assert(allocators[$ - 1].a.bytesUsed == 0); + root = &allocators[$ - 1]; + return root; + } + + /** + Defined only if `Allocator` defines `owns`. Tries each allocator in + turn, in most-recently-used order. If the owner is found, it is moved to + the front of the list as a side effect under the assumption it will be used + soon. + + Returns: `Ternary.yes` if one allocator was found to return `Ternary.yes`, + `Ternary.no` if all component allocators returned `Ternary.no`, and + `Ternary.unknown` if no allocator returned `Ternary.yes` and at least one + returned `Ternary.unknown`. + */ + static if (hasMember!(Allocator, "owns")) + Ternary owns(void[] b) + { + auto result = Ternary.no; + for (auto p = &root, n = *p; n; p = &n.next, n = *p) + { + immutable t = n.owns(b); + if (t != Ternary.yes) + { + if (t == Ternary.unknown) result = t; + continue; + } + // Move the owner to front, speculating it'll be used + if (n != root) + { + *p = n.next; + n.next = root; + root = n; + } + return Ternary.yes; + } + return result; + } + + /** + Defined only if $(D Allocator.expand) is defined. Finds the owner of $(D b) + and calls $(D expand) for it. The owner is not brought to the head of the + list. + */ + static if (hasMember!(Allocator, "expand") + && hasMember!(Allocator, "owns")) + bool expand(ref void[] b, size_t delta) + { + if (!b.ptr) return delta == 0; + for (auto p = &root, n = *p; n; p = &n.next, n = *p) + { + if (n.owns(b) == Ternary.yes) return n.expand(b, delta); + } + return false; + } + + /** + Defined only if $(D Allocator.reallocate) is defined. Finds the owner of + $(D b) and calls $(D reallocate) for it. If that fails, calls the global + $(D reallocate), which allocates a new block and moves memory. + */ + static if (hasMember!(Allocator, "reallocate")) + bool reallocate(ref void[] b, size_t s) + { + // First attempt to reallocate within the existing node + if (!b.ptr) + { + b = allocate(s); + return b.length == s; + } + for (auto p = &root, n = *p; n; p = &n.next, n = *p) + { + if (n.owns(b) == Ternary.yes) return n.reallocate(b, s); + } + // Failed, but we may find new memory in a new node. + return .reallocate(this, b, s); + } + + /** + Defined if $(D Allocator.deallocate) and $(D Allocator.owns) are defined. + */ + static if (hasMember!(Allocator, "deallocate") + && hasMember!(Allocator, "owns")) + bool deallocate(void[] b) + { + if (!b.ptr) return true; + assert(allocators.length); + assert(owns(b) == Ternary.yes); + bool result; + for (auto p = &root, n = *p; ; p = &n.next, n = *p) + { + assert(n); + if (n.owns(b) != Ternary.yes) continue; + result = n.deallocate(b); + // Bring to front + if (n != root) + { + *p = n.next; + n.next = root; + root = n; + } + if (n.empty != Ternary.yes) return result; + break; + } + // Hmmm... should we return this allocator back to the wild? Let's + // decide if there are TWO empty allocators we can release ONE. This + // is to avoid thrashing. + // Note that loop starts from the second element. + for (auto p = &root.next, n = *p; n; p = &n.next, n = *p) + { + if (n.unused || n.empty != Ternary.yes) continue; + // Used and empty baby, nuke it! + n.a.destroy; + *p = n.next; + n.setUnused; + break; + } + return result; + } + + /** + Defined only if $(D Allocator.owns) and $(D Allocator.deallocateAll) are + defined. + */ + static if (ouroboros && hasMember!(Allocator, "deallocateAll") + && hasMember!(Allocator, "owns")) + bool deallocateAll() + { + Node* special; + foreach (ref n; allocators) + { + if (n.unused) continue; + if (n.owns(allocators) == Ternary.yes) + { + special = &n; + continue; + } + n.a.deallocateAll; + n.a.destroy; + } + assert(special || !allocators.ptr); + if (special) + { + special.deallocate(allocators); + } + allocators = null; + root = null; + return true; + } + + static if (!ouroboros && hasMember!(Allocator, "deallocateAll") + && hasMember!(Allocator, "owns")) + bool deallocateAll() + { + foreach (ref n; allocators) + { + if (n.unused) continue; + n.a.deallocateAll; + n.a.destroy; + } + bkalloc.deallocate(allocators); + allocators = null; + root = null; + return true; + } + + /** + Returns `Ternary.yes` if no allocators are currently active, + `Ternary.no` otherwise. This methods never returns `Ternary.unknown`. + */ + Ternary empty() const + { + return Ternary(!allocators.length); + } +} + +/// Ditto +template AllocatorList(alias factoryFunction, + BookkeepingAllocator = GCAllocator) +{ + alias A = typeof(factoryFunction(1)); + static assert( + // is a template function (including literals) + is(typeof({A function(size_t) @system x = factoryFunction!size_t;})) + || + // or a function (including literals) + is(typeof({A function(size_t) @system x = factoryFunction;})) + , + "Only function names and function literals that take size_t" + ~ " and return an allocator are accepted, not " + ~ typeof(factoryFunction).stringof + ); + static struct Factory + { + A opCall(size_t n) { return factoryFunction(n); } + } + alias AllocatorList = .AllocatorList!(Factory, BookkeepingAllocator); +} + +/// +version (Posix) @system unittest +{ + import std.algorithm.comparison : max; + import std.experimental.allocator.building_blocks.free_list : ContiguousFreeList; + import std.experimental.allocator.building_blocks.null_allocator : NullAllocator; + import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.building_blocks.segregator : Segregator; + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.experimental.allocator.mmap_allocator : MmapAllocator; + + // Ouroboros allocator list based upon 4MB regions, fetched directly from + // mmap. All memory is released upon destruction. + alias A1 = AllocatorList!((n) => Region!MmapAllocator(max(n, 1024 * 4096)), + NullAllocator); + + // Allocator list based upon 4MB regions, fetched from the garbage + // collector. All memory is released upon destruction. + alias A2 = AllocatorList!((n) => Region!GCAllocator(max(n, 1024 * 4096))); + + // Ouroboros allocator list based upon 4MB regions, fetched from the garbage + // collector. Memory is left to the collector. + alias A3 = AllocatorList!( + (n) => Region!NullAllocator(new ubyte[max(n, 1024 * 4096)]), + NullAllocator); + + // Allocator list that creates one freelist for all objects + alias A4 = + Segregator!( + 64, AllocatorList!( + (n) => ContiguousFreeList!(NullAllocator, 0, 64)( + cast(ubyte[])(GCAllocator.instance.allocate(4096)))), + GCAllocator); + + A4 a; + auto small = a.allocate(64); + assert(small); + a.deallocate(small); + auto b1 = a.allocate(1024 * 8192); + assert(b1 !is null); // still works due to overdimensioning + b1 = a.allocate(1024 * 10); + assert(b1.length == 1024 * 10); +} + +@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!GCAllocator(new ubyte[max(n, 1024 * 4096)]), + NullAllocator) a; + const b1 = a.allocate(1024 * 8192); + assert(b1 !is null); // still works due to overdimensioning + const b2 = a.allocate(1024 * 10); + assert(b2.length == 1024 * 10); + a.deallocateAll(); +} + +@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; + auto b1 = a.allocate(1024 * 8192); + assert(b1 !is null); // still works due to overdimensioning + b1 = a.allocate(1024 * 10); + assert(b1.length == 1024 * 10); + a.deallocateAll(); +} + +@system unittest +{ + import std.algorithm.comparison : max; + import std.experimental.allocator.building_blocks.region : Region; + import std.typecons : Ternary; + AllocatorList!((n) => Region!()(new ubyte[max(n, 1024 * 4096)])) a; + auto b1 = a.allocate(1024 * 8192); + assert(b1 !is null); + b1 = a.allocate(1024 * 10); + assert(b1.length == 1024 * 10); + a.allocate(1024 * 4095); + a.deallocateAll(); + assert(a.empty == Ternary.yes); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.region : Region; + enum bs = GCAllocator.alignment; + AllocatorList!((n) => Region!GCAllocator(256 * bs)) a; + auto b1 = a.allocate(192 * bs); + assert(b1.length == 192 * bs); + assert(a.allocators.length == 1); + auto b2 = a.allocate(64 * bs); + assert(b2.length == 64 * bs); + assert(a.allocators.length == 1); + auto b3 = a.allocate(192 * bs); + assert(b3.length == 192 * bs); + assert(a.allocators.length == 2); + a.deallocate(b1); + b1 = a.allocate(64 * bs); + assert(b1.length == 64 * bs); + assert(a.allocators.length == 2); + a.deallocateAll(); +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/bitmapped_block.d b/libphobos/src/std/experimental/allocator/building_blocks/bitmapped_block.d new file mode 100644 index 0000000..24e27a9 --- /dev/null +++ b/libphobos/src/std/experimental/allocator/building_blocks/bitmapped_block.d @@ -0,0 +1,1423 @@ +/// +module std.experimental.allocator.building_blocks.bitmapped_block; + +import std.experimental.allocator.building_blocks.null_allocator; +import std.experimental.allocator.common; + +/** + +$(D BitmappedBlock) implements a simple heap consisting of one contiguous area +of memory organized in blocks, each of size $(D theBlockSize). A block is a unit +of allocation. A bitmap serves as bookkeeping data, more precisely one bit per +block indicating whether that block is currently allocated or not. + +Passing $(D NullAllocator) as $(D ParentAllocator) (the default) means user code +manages allocation of the memory block from the outside; in that case +$(D BitmappedBlock) must be constructed with a $(D void[]) preallocated block and +has no responsibility regarding the lifetime of its support underlying storage. +If another allocator type is passed, $(D BitmappedBlock) defines a destructor that +uses the parent allocator to release the memory block. That makes the combination of $(D AllocatorList), $(D BitmappedBlock), and a back-end allocator such as $(D MmapAllocator) a simple and scalable solution for memory allocation. + +There are advantages to storing bookkeeping data separated from the payload +(as opposed to e.g. using $(D AffixAllocator) to store metadata together with +each allocation). The layout is more compact (overhead is one bit per block), +searching for a free block during allocation enjoys better cache locality, and +deallocation does not touch memory around the payload being deallocated (which +is often cold). + +Allocation requests are handled on a first-fit basis. Although linear in +complexity, allocation is in practice fast because of the compact bookkeeping +representation, use of simple and fast bitwise routines, and caching of the +first available block position. A known issue with this general approach is +fragmentation, partially mitigated by coalescing. Since $(D BitmappedBlock) does +not need to maintain the allocated size, freeing memory implicitly coalesces +free blocks together. Also, tuning $(D blockSize) has a considerable impact on +both internal and external fragmentation. + +The size of each block can be selected either during compilation or at run +time. Statically-known block sizes are frequent in practice and yield slightly +better performance. To choose a block size statically, pass it as the $(D +blockSize) parameter as in $(D BitmappedBlock!(Allocator, 4096)). To choose a block +size parameter, use $(D BitmappedBlock!(Allocator, chooseAtRuntime)) and pass the +block size to the constructor. + +*/ +struct BitmappedBlock(size_t theBlockSize, uint theAlignment = platformAlignment, + ParentAllocator = NullAllocator) +{ + import std.conv : text; + import std.traits : hasMember; + import std.typecons : Ternary; + import std.typecons : tuple, Tuple; + + @system unittest + { + import std.algorithm.comparison : max; + import std.experimental.allocator.mallocator : AlignedMallocator; + auto m = cast(ubyte[])(AlignedMallocator.instance.alignedAllocate(1024 * 64, + max(theAlignment, cast(uint) size_t.sizeof))); + scope(exit) AlignedMallocator.instance.deallocate(m); + testAllocator!(() => BitmappedBlock(m)); + } + static assert(theBlockSize > 0 && theAlignment.isGoodStaticAlignment); + static assert(theBlockSize == chooseAtRuntime + || theBlockSize % theAlignment == 0, + "Block size must be a multiple of the alignment"); + + /** + If $(D blockSize == chooseAtRuntime), $(D BitmappedBlock) offers a read/write + property $(D blockSize). It must be set before any use of the allocator. + Otherwise (i.e. $(D theBlockSize) is a legit constant), $(D blockSize) is + an alias for $(D theBlockSize). Whether constant or variable, must also be + a multiple of $(D alignment). This constraint is $(D assert)ed statically + and dynamically. + */ + static if (theBlockSize != chooseAtRuntime) + { + alias blockSize = theBlockSize; + } + else + { + @property uint blockSize() { return _blockSize; } + @property void blockSize(uint s) + { + assert(!_control && s % alignment == 0); + _blockSize = s; + } + private uint _blockSize; + } + + static if (is(ParentAllocator == NullAllocator)) + { + private enum parentAlignment = platformAlignment; + } + else + { + private alias parentAlignment = ParentAllocator.alignment; + static assert(parentAlignment >= ulong.alignof); + } + + /** + The _alignment offered is user-configurable statically through parameter + $(D theAlignment), defaulted to $(D platformAlignment). + */ + alias alignment = theAlignment; + + // state { + /** + The _parent allocator. Depending on whether $(D ParentAllocator) holds state + or not, this is a member variable or an alias for + `ParentAllocator.instance`. + */ + static if (stateSize!ParentAllocator) + { + ParentAllocator parent; + } + else + { + alias parent = ParentAllocator.instance; + } + private uint _blocks; + private BitVector _control; + private void[] _payload; + private size_t _startIdx; + // } + + private size_t totalAllocation(size_t capacity) + { + auto blocks = capacity.divideRoundUp(blockSize); + auto leadingUlongs = blocks.divideRoundUp(64); + import std.algorithm.comparison : min; + immutable initialAlignment = min(parentAlignment, + 1U << trailingZeros(leadingUlongs * 8)); + auto maxSlack = alignment <= initialAlignment + ? 0 + : alignment - initialAlignment; + //writeln(maxSlack); + return leadingUlongs * 8 + maxSlack + blockSize * blocks; + } + + /** + Constructs a block allocator given a hunk of memory, or a desired capacity + in bytes. + + $(UL + $(LI If $(D ParentAllocator) is $(D NullAllocator), only the constructor + taking $(D data) is defined and the user is responsible for freeing $(D + data) if desired.) + $(LI Otherwise, both constructors are defined. The $(D data)-based + constructor assumes memory has been allocated with the parent allocator. + The $(D capacity)-based constructor uses $(D ParentAllocator) to allocate + an appropriate contiguous hunk of memory. Regardless of the constructor + used, the destructor releases the memory by using $(D + ParentAllocator.deallocate).) + ) + */ + this(ubyte[] data) + { + immutable a = data.ptr.effectiveAlignment; + assert(a >= size_t.alignof || !data.ptr, + "Data must be aligned properly"); + + immutable ulong totalBits = data.length * 8; + immutable ulong bitsPerBlock = blockSize * 8 + 1; + // Get a first estimate + import std.conv : to; + _blocks = to!uint(totalBits / bitsPerBlock); + + // Reality is a bit more complicated, iterate until a good number of + // blocks found. + for (; _blocks; --_blocks) + { + immutable controlWords = _blocks.divideRoundUp(64); + auto payload = data[controlWords * 8 .. $].roundStartToMultipleOf( + alignment); + if (payload.length < _blocks * blockSize) + { + // Overestimated + continue; + } + _control = BitVector((cast(ulong*) data.ptr)[0 .. controlWords]); + _control[] = 0; + _payload = payload; + break; + } + } + + /// Ditto + static if (!is(ParentAllocator == NullAllocator)) + this(size_t capacity) + { + size_t toAllocate = totalAllocation(capacity); + auto data = cast(ubyte[])(parent.allocate(toAllocate)); + this(data); + assert(_blocks * blockSize >= capacity); + } + + /** + If $(D ParentAllocator) is not $(D NullAllocator) and defines $(D + deallocate), the destructor is defined to deallocate the block held. + */ + static if (!is(ParentAllocator == NullAllocator) + && hasMember!(ParentAllocator, "deallocate")) + ~this() + { + auto start = _control.rep.ptr, end = _payload.ptr + _payload.length; + parent.deallocate(start[0 .. end - start]); + } + + /* + Adjusts the memoized _startIdx to the leftmost control word that has at + least one zero bit. Assumes all control words to the left of $(D + _control[_startIdx]) are already occupied. + */ + private void adjustStartIdx() + { + while (_startIdx < _control.rep.length + && _control.rep[_startIdx] == ulong.max) + { + ++_startIdx; + } + } + + /* + Returns the blocks corresponding to the control bits starting at word index + wordIdx and bit index msbIdx (MSB=0) for a total of howManyBlocks. + */ + private void[] blocksFor(size_t wordIdx, uint msbIdx, size_t howManyBlocks) + { + assert(msbIdx <= 63); + const start = (wordIdx * 64 + msbIdx) * blockSize; + const end = start + blockSize * howManyBlocks; + if (end <= _payload.length) return _payload[start .. end]; + // This could happen if we have more control bits than available memory. + // That's possible because the control bits are rounded up to fit in + // 64-bit words. + return null; + } + + /** + Returns the actual bytes allocated when $(D n) bytes are requested, i.e. + $(D n.roundUpToMultipleOf(blockSize)). + */ + size_t goodAllocSize(size_t n) + { + return n.roundUpToMultipleOf(blockSize); + } + + /** + Allocates $(D s) bytes of memory and returns it, or $(D null) if memory + could not be allocated. + + The following information might be of help with choosing the appropriate + block size. Actual allocation occurs in sizes multiple of the block size. + Allocating one block is the fastest because only one 0 bit needs to be + found in the metadata. Allocating 2 through 64 blocks is the next cheapest + because it affects a maximum of two $(D ulong)s in the metadata. + Allocations greater than 64 blocks require a multiword search through the + metadata. + */ + @trusted void[] allocate(const size_t s) + { + const blocks = s.divideRoundUp(blockSize); + void[] result = void; + + switcharoo: + switch (blocks) + { + case 1: + // inline code here for speed + // find the next available block + foreach (i; _startIdx .. _control.rep.length) + { + const w = _control.rep[i]; + if (w == ulong.max) continue; + uint j = leadingOnes(w); + assert(j < 64); + assert((_control.rep[i] & ((1UL << 63) >> j)) == 0); + _control.rep[i] |= (1UL << 63) >> j; + if (i == _startIdx) + { + adjustStartIdx(); + } + result = blocksFor(i, j, 1); + break switcharoo; + } + goto case 0; // fall through + case 0: + return null; + case 2: .. case 64: + result = smallAlloc(cast(uint) blocks); + break; + default: + result = hugeAlloc(blocks); + break; + } + return result.ptr ? result.ptr[0 .. s] : null; + } + + /** + Allocates a block with specified alignment $(D a). The alignment must be a + power of 2. If $(D a <= alignment), function forwards to $(D allocate). + Otherwise, it attempts to overallocate and then adjust the result for + proper alignment. In the worst case the slack memory is around two blocks. + */ + void[] alignedAllocate(size_t n, uint a) + { + import std.math : isPowerOf2; + assert(a.isPowerOf2); + if (a <= alignment) return allocate(n); + + // Overallocate to make sure we can get an aligned block + auto b = allocate((n + a - alignment).roundUpToMultipleOf(blockSize)); + if (!b.ptr) return null; + auto result = b.roundStartToMultipleOf(a); + assert(result.length >= n); + result = result.ptr[0 .. n]; // final result + + // Free any blocks that might be slack at the beginning + auto slackHeadingBlocks = (result.ptr - b.ptr) / blockSize; + if (slackHeadingBlocks) + { + deallocate(b[0 .. slackHeadingBlocks * blockSize]); + } + + // Free any blocks that might be slack at the end + auto slackTrailingBlocks = ((b.ptr + b.length) + - (result.ptr + result.length)) / blockSize; + if (slackTrailingBlocks) + { + deallocate(b[$ - slackTrailingBlocks * blockSize .. $]); + } + + return result; + } + + /** + If the $(D BitmappedBlock) object is empty (has no active allocation), allocates + all memory within and returns a slice to it. Otherwise, returns $(D null) + (i.e. no attempt is made to allocate the largest available block). + */ + void[] allocateAll() + { + if (empty != Ternary.yes) return null; + _control[] = 1; + return _payload; + } + + /** + Returns `Ternary.yes` if `b` belongs to the `BitmappedBlock` object, + `Ternary.no` otherwise. Never returns `Ternary.unkown`. (This + method is somewhat tolerant in that accepts an interior slice.) + */ + Ternary owns(void[] b) const + { + //if (!b.ptr) return Ternary.no; + assert(b.ptr !is null || b.length == 0, "Corrupt block."); + return Ternary(b.ptr >= _payload.ptr + && b.ptr + b.length <= _payload.ptr + _payload.length); + } + + /* + Tries to allocate "blocks" blocks at the exact position indicated by the + position wordIdx/msbIdx (msbIdx counts from MSB, i.e. MSB has index 0). If + it succeeds, fills "result" with the result and returns tuple(size_t.max, + 0). Otherwise, returns a tuple with the next position to search. + */ + private Tuple!(size_t, uint) allocateAt(size_t wordIdx, uint msbIdx, + size_t blocks, ref void[] result) + { + assert(blocks > 0); + assert(wordIdx < _control.rep.length); + assert(msbIdx <= 63); + if (msbIdx + blocks <= 64) + { + // Allocation should fit this control word + if (setBitsIfZero(_control.rep[wordIdx], + cast(uint) (64 - msbIdx - blocks), 63 - msbIdx)) + { + // Success + result = blocksFor(wordIdx, msbIdx, blocks); + return tuple(size_t.max, 0u); + } + // Can't allocate, make a suggestion + return msbIdx + blocks == 64 + ? tuple(wordIdx + 1, 0u) + : tuple(wordIdx, cast(uint) (msbIdx + blocks)); + } + // Allocation spans two control words or more + immutable mask = ulong.max >> msbIdx; + if (_control.rep[wordIdx] & mask) + { + // We can't allocate the rest of this control word, + // return a suggestion. + return tuple(wordIdx + 1, 0u); + } + // We can allocate the rest of this control word, but we first need to + // make sure we can allocate the tail. + if (wordIdx + 1 == _control.rep.length) + { + // No more memory + return tuple(_control.rep.length, 0u); + } + auto hint = allocateAt(wordIdx + 1, 0, blocks - 64 + msbIdx, result); + if (hint[0] == size_t.max) + { + // We did it! + _control.rep[wordIdx] |= mask; + result = blocksFor(wordIdx, msbIdx, blocks); + return tuple(size_t.max, 0u); + } + // Failed, return a suggestion that skips this whole run. + return hint; + } + + /* Allocates as many blocks as possible at the end of the blocks indicated + by wordIdx. Returns the number of blocks allocated. */ + private uint allocateAtTail(size_t wordIdx) + { + assert(wordIdx < _control.rep.length); + const available = trailingZeros(_control.rep[wordIdx]); + _control.rep[wordIdx] |= ulong.max >> available; + return available; + } + + private void[] smallAlloc(uint blocks) + { + assert(blocks >= 2 && blocks <= 64, text(blocks)); + foreach (i; _startIdx .. _control.rep.length) + { + // Test within the current 64-bit word + const v = _control.rep[i]; + if (v == ulong.max) continue; + auto j = findContigOnes(~v, blocks); + if (j < 64) + { + // yay, found stuff + setBits(_control.rep[i], 64 - j - blocks, 63 - j); + return blocksFor(i, j, blocks); + } + // Next, try allocations that cross a word + auto available = trailingZeros(v); + if (available == 0) continue; + if (i + 1 >= _control.rep.length) break; + assert(available < blocks); // otherwise we should have found it + auto needed = blocks - available; + assert(needed > 0 && needed < 64); + if (allocateAtFront(i + 1, needed)) + { + // yay, found a block crossing two words + _control.rep[i] |= (1UL << available) - 1; + return blocksFor(i, 64 - available, blocks); + } + } + return null; + } + + private void[] hugeAlloc(size_t blocks) + { + assert(blocks > 64); + if (_startIdx == _control._rep.length) + { + assert(_control.allAre1); + return null; + } + auto i = _control.findZeros(blocks, _startIdx * 64); + if (i == i.max) return null; + // Allocate those bits + _control[i .. i + blocks] = 1; + return _payload[cast(size_t) (i * blockSize) + .. cast(size_t) ((i + blocks) * blockSize)]; + } + + // Rounds sizeInBytes to a multiple of blockSize. + private size_t bytes2blocks(size_t sizeInBytes) + { + return (sizeInBytes + blockSize - 1) / blockSize; + } + + /* Allocates given blocks at the beginning blocks indicated by wordIdx. + Returns true if allocation was possible, false otherwise. */ + private bool allocateAtFront(size_t wordIdx, uint blocks) + { + assert(wordIdx < _control.rep.length && blocks >= 1 && blocks <= 64); + const mask = (1UL << (64 - blocks)) - 1; + if (_control.rep[wordIdx] > mask) return false; + // yay, works + _control.rep[wordIdx] |= ~mask; + return true; + } + + /** + Expands an allocated block in place. + */ + @trusted bool expand(ref void[] b, immutable size_t delta) + { + // Dispose with trivial corner cases + if (delta == 0) return true; + if (b is null) return false; + + /* To simplify matters, refuse to expand buffers that don't start at a block start (this may be the case for blocks allocated with alignedAllocate). + */ + if ((b.ptr - _payload.ptr) % blockSize) return false; + + const blocksOld = bytes2blocks(b.length); + const blocksNew = bytes2blocks(b.length + delta); + assert(blocksOld <= blocksNew); + + // Possibly we have enough slack at the end of the block! + if (blocksOld == blocksNew) + { + b = b.ptr[0 .. b.length + delta]; + return true; + } + + assert((b.ptr - _payload.ptr) % blockSize == 0); + const blockIdx = (b.ptr - _payload.ptr) / blockSize; + const blockIdxAfter = blockIdx + blocksOld; + + // Try the maximum + const wordIdx = blockIdxAfter / 64, + msbIdx = cast(uint) (blockIdxAfter % 64); + void[] p; + auto hint = allocateAt(wordIdx, msbIdx, blocksNew - blocksOld, p); + if (hint[0] != size_t.max) + { + return false; + } + // Expansion successful + assert(p.ptr == b.ptr + blocksOld * blockSize, + text(p.ptr, " != ", b.ptr + blocksOld * blockSize)); + b = b.ptr[0 .. b.length + delta]; + return true; + } + + /** + Reallocates a previously-allocated block. Contractions occur in place. + */ + @system bool reallocate(ref void[] b, size_t newSize) + { + if (!b.ptr) + { + b = allocate(newSize); + return b.length == newSize; + } + if (newSize == 0) + { + deallocate(b); + b = null; + return true; + } + if (newSize < b.length) + { + // Shrink. Will shrink in place by deallocating the trailing part. + auto newCapacity = bytes2blocks(newSize) * blockSize; + deallocate(b[newCapacity .. $]); + b = b[0 .. newSize]; + return true; + } + // Go the slow route + return .reallocate(this, b, newSize); + } + + /** + Reallocates a block previously allocated with $(D alignedAllocate). Contractions do not occur in place. + */ + @system bool alignedReallocate(ref void[] b, size_t newSize, uint a) + { + if (newSize == 0) + { + deallocate(b); + b = null; + return true; + } + // Go the slow route + return .alignedReallocate(this, b, newSize, a); + } + + /** + Deallocates a block previously allocated with this allocator. + */ + bool deallocate(void[] b) + { + if (b is null) return true; + + // Locate position + immutable pos = b.ptr - _payload.ptr; + immutable blockIdx = pos / blockSize; + + // Adjust pointer, might be inside a block due to alignedAllocate + auto begin = _payload.ptr + blockIdx * blockSize, + end = b.ptr + b.length; + b = begin[0 .. end - begin]; + // Round up size to multiple of block size + auto blocks = b.length.divideRoundUp(blockSize); + + // Get into details + auto wordIdx = blockIdx / 64, msbIdx = cast(uint) (blockIdx % 64); + if (_startIdx > wordIdx) _startIdx = wordIdx; + + // Three stages: heading bits, full words, leftover bits + if (msbIdx) + { + if (blocks + msbIdx <= 64) + { + resetBits(_control.rep[wordIdx], + cast(uint) (64 - msbIdx - blocks), + 63 - msbIdx); + return true; + } + else + { + _control.rep[wordIdx] &= ulong.max << 64 - msbIdx; + blocks -= 64 - msbIdx; + ++wordIdx; + msbIdx = 0; + } + } + + // Stage 2: reset one word at a time + for (; blocks >= 64; blocks -= 64) + { + _control.rep[wordIdx++] = 0; + } + + // Stage 3: deal with leftover bits, if any + assert(wordIdx <= _control.rep.length); + if (blocks) + { + _control.rep[wordIdx] &= ulong.max >> blocks; + } + return true; + } + + /** + Forcibly deallocates all memory allocated by this allocator, making it + available for further allocations. Does not return memory to $(D + ParentAllocator). + */ + bool deallocateAll() + { + _control[] = 0; + _startIdx = 0; + return true; + } + + /** + Returns `Ternary.yes` if no memory is currently allocated with this + allocator, otherwise `Ternary.no`. This method never returns + `Ternary.unknown`. + */ + Ternary empty() + { + return Ternary(_control.allAre0()); + } + + void dump() + { + import std.stdio : writefln, writeln; + writefln("%s @ %s {", typeid(this), cast(void*) _control._rep.ptr); + scope(exit) writeln("}"); + assert(_payload.length == blockSize * _blocks); + assert(_control.length >= _blocks); + writefln(" _startIdx=%s; blockSize=%s; blocks=%s", + _startIdx, blockSize, _blocks); + if (!_control.length) return; + uint blockCount = 1; + bool inAllocatedStore = _control[0]; + void* start = _payload.ptr; + for (size_t i = 1;; ++i) + { + if (i >= _blocks || _control[i] != inAllocatedStore) + { + writefln(" %s block at 0x%s, length: %s (%s*%s)", + inAllocatedStore ? "Busy" : "Free", + cast(void*) start, + blockCount * blockSize, + blockCount, blockSize); + if (i >= _blocks) break; + assert(i < _control.length); + inAllocatedStore = _control[i]; + start = _payload.ptr + blockCount * blockSize; + blockCount = 1; + } + else + { + ++blockCount; + } + } + } +} + +/// +@system unittest +{ + // Create a block allocator on top of a 10KB stack region. + import std.experimental.allocator.building_blocks.region : InSituRegion; + import std.traits : hasMember; + InSituRegion!(10_240, 64) r; + auto a = BitmappedBlock!(64, 64)(cast(ubyte[])(r.allocateAll())); + static assert(hasMember!(InSituRegion!(10_240, 64), "allocateAll")); + const b = a.allocate(100); + assert(b.length == 100); +} + +@system unittest +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + testAllocator!(() => BitmappedBlock!(64, 8, GCAllocator)(1024 * 64)); +} + +@system unittest +{ + static void testAllocateAll(size_t bs)(uint blocks, uint blocksAtATime) + { + import std.algorithm.comparison : min; + assert(bs); + import std.experimental.allocator.gc_allocator : GCAllocator; + auto a = BitmappedBlock!(bs, min(bs, platformAlignment))( + cast(ubyte[])(GCAllocator.instance.allocate((blocks * bs * 8 + + blocks) / 8)) + ); + import std.conv : text; + assert(blocks >= a._blocks, text(blocks, " < ", a._blocks)); + blocks = a._blocks; + + // test allocation of 0 bytes + auto x = a.allocate(0); + assert(x is null); + // test allocation of 1 byte + x = a.allocate(1); + assert(x.length == 1 || blocks == 0, + text(x.ptr, " ", x.length, " ", a)); + a.deallocateAll(); + + bool twice = true; + + begin: + foreach (i; 0 .. blocks / blocksAtATime) + { + auto b = a.allocate(bs * blocksAtATime); + assert(b.length == bs * blocksAtATime, text(i, ": ", b.length)); + } + assert(a.allocate(bs * blocksAtATime) is null); + assert(a.allocate(1) is null); + + // Now deallocate all and do it again! + a.deallocateAll(); + + // Test deallocation + + auto v = new void[][blocks / blocksAtATime]; + foreach (i; 0 .. blocks / blocksAtATime) + { + auto b = a.allocate(bs * blocksAtATime); + assert(b.length == bs * blocksAtATime, text(i, ": ", b.length)); + v[i] = b; + } + assert(a.allocate(bs * blocksAtATime) is null); + assert(a.allocate(1) is null); + + foreach (i; 0 .. blocks / blocksAtATime) + { + a.deallocate(v[i]); + } + + foreach (i; 0 .. blocks / blocksAtATime) + { + auto b = a.allocate(bs * blocksAtATime); + assert(b.length == bs * blocksAtATime, text(i, ": ", b.length)); + v[i] = b; + } + + foreach (i; 0 .. v.length) + { + a.deallocate(v[i]); + } + + if (twice) + { + twice = false; + goto begin; + } + + a.deallocateAll; + + // test expansion + if (blocks >= blocksAtATime) + { + foreach (i; 0 .. blocks / blocksAtATime - 1) + { + auto b = a.allocate(bs * blocksAtATime); + assert(b.length == bs * blocksAtATime, text(i, ": ", b.length)); + (cast(ubyte[]) b)[] = 0xff; + a.expand(b, blocksAtATime * bs) + || assert(0, text(i)); + (cast(ubyte[]) b)[] = 0xfe; + assert(b.length == bs * blocksAtATime * 2, text(i, ": ", b.length)); + a.reallocate(b, blocksAtATime * bs) || assert(0); + assert(b.length == bs * blocksAtATime, text(i, ": ", b.length)); + } + } + } + + testAllocateAll!(1)(0, 1); + testAllocateAll!(1)(8, 1); + testAllocateAll!(4096)(128, 1); + + testAllocateAll!(1)(0, 2); + testAllocateAll!(1)(128, 2); + testAllocateAll!(4096)(128, 2); + + testAllocateAll!(1)(0, 4); + testAllocateAll!(1)(128, 4); + testAllocateAll!(4096)(128, 4); + + testAllocateAll!(1)(0, 3); + testAllocateAll!(1)(24, 3); + testAllocateAll!(3008)(100, 1); + testAllocateAll!(3008)(100, 3); + + testAllocateAll!(1)(0, 128); + testAllocateAll!(1)(128 * 1, 128); + testAllocateAll!(128 * 20)(13 * 128, 128); +} + +// Test totalAllocation +@safe unittest +{ + BitmappedBlock!(8, 8, NullAllocator) h1; + assert(h1.totalAllocation(1) >= 8); + assert(h1.totalAllocation(64) >= 64); + assert(h1.totalAllocation(8 * 64) >= 8 * 64); + assert(h1.totalAllocation(8 * 63) >= 8 * 63); + assert(h1.totalAllocation(8 * 64 + 1) >= 8 * 65); + + BitmappedBlock!(64, 8, NullAllocator) h2; + assert(h2.totalAllocation(1) >= 64); + assert(h2.totalAllocation(64 * 64) >= 64 * 64); + + BitmappedBlock!(4096, 4096, NullAllocator) h3; + assert(h3.totalAllocation(1) >= 4096); + assert(h3.totalAllocation(64 * 4096) >= 64 * 4096); + assert(h3.totalAllocation(64 * 4096 + 1) >= 65 * 4096); +} + +// BitmappedBlockWithInternalPointers +/** + +A $(D BitmappedBlock) with additional structure for supporting $(D +resolveInternalPointer). To that end, $(D BitmappedBlockWithInternalPointers) adds a +bitmap (one bit per block) that marks object starts. The bitmap itself has +variable size and is allocated together with regular allocations. + +The time complexity of $(D resolveInternalPointer) is $(BIGOH k), where $(D k) +is the size of the object within which the internal pointer is looked up. + +*/ +struct BitmappedBlockWithInternalPointers( + size_t theBlockSize, uint theAlignment = platformAlignment, + ParentAllocator = NullAllocator) +{ + import std.conv : text; + import std.typecons : Ternary; + @system unittest + { + import std.experimental.allocator.mallocator : AlignedMallocator; + auto m = cast(ubyte[])(AlignedMallocator.instance.alignedAllocate(1024 * 64, + theAlignment)); + scope(exit) AlignedMallocator.instance.deallocate(m); + testAllocator!(() => BitmappedBlockWithInternalPointers(m)); + } + + // state { + private BitmappedBlock!(theBlockSize, theAlignment, NullAllocator) _heap; + private BitVector _allocStart; + // } + + /** + Constructors accepting desired capacity or a preallocated buffer, similar + in semantics to those of $(D BitmappedBlock). + */ + this(ubyte[] data) + { + _heap = BitmappedBlock!(theBlockSize, theAlignment, ParentAllocator)(data); + } + + /// Ditto + static if (!is(ParentAllocator == NullAllocator)) + this(size_t capacity) + { + // Add room for the _allocStart vector + _heap = BitmappedBlock!(theBlockSize, theAlignment, ParentAllocator) + (capacity + capacity.divideRoundUp(64)); + } + + // Makes sure there's enough room for _allocStart + private bool ensureRoomForAllocStart(size_t len) + { + if (_allocStart.length >= len) return true; + // Must ensure there's room + immutable oldLength = _allocStart.rep.length; + immutable bits = len.roundUpToMultipleOf(64); + void[] b = _allocStart.rep; + if (!_heap.reallocate(b, bits / 8)) return false; + assert(b.length * 8 == bits, text(b.length * 8, " != ", bits)); + _allocStart = BitVector(cast(ulong[]) b); + assert(_allocStart.rep.length * 64 == bits); + _allocStart.rep[oldLength .. $] = ulong.max; + return true; + } + + /** + Allocator primitives. + */ + alias alignment = theAlignment; + + /// Ditto + size_t goodAllocSize(size_t n) + { + return n.roundUpToMultipleOf(_heap.blockSize); + } + + /// Ditto + void[] allocate(size_t bytes) + { + auto r = _heap.allocate(bytes); + if (!r.ptr) return r; + immutable block = (r.ptr - _heap._payload.ptr) / _heap.blockSize; + immutable blocks = + (r.length + _heap.blockSize - 1) / _heap.blockSize; + if (!ensureRoomForAllocStart(block + blocks)) + { + // Failed, free r and bailout + _heap.deallocate(r); + return null; + } + assert(block < _allocStart.length); + assert(block + blocks <= _allocStart.length); + // Mark the _allocStart bits + assert(blocks > 0); + _allocStart[block] = 1; + _allocStart[block + 1 .. block + blocks] = 0; + assert(block + blocks == _allocStart.length + || _allocStart[block + blocks] == 1); + return r; + } + + /// Ditto + void[] allocateAll() + { + auto r = _heap.allocateAll(); + if (!r.ptr) return r; + // Carve space at the end for _allocStart + auto p = alignDownTo(r.ptr + r.length - 8, ulong.alignof); + r = r[0 .. p - r.ptr]; + // Initialize _allocStart + _allocStart = BitVector(cast(ulong[]) p[0 .. 8]); + _allocStart[] = 0; + immutable block = (r.ptr - _heap._payload.ptr) / _heap.blockSize; + assert(block < _allocStart.length); + _allocStart[block] = 1; + return r; + } + + /// Ditto + bool expand(ref void[] b, size_t bytes) + { + if (!bytes) return true; + if (b is null) return false; + immutable oldBlocks = + (b.length + _heap.blockSize - 1) / _heap.blockSize; + assert(oldBlocks); + immutable newBlocks = + (b.length + bytes + _heap.blockSize - 1) / _heap.blockSize; + assert(newBlocks >= oldBlocks); + immutable block = (b.ptr - _heap._payload.ptr) / _heap.blockSize; + assert(_allocStart[block]); + if (!ensureRoomForAllocStart(block + newBlocks) + || !_heap.expand(b, bytes)) + { + return false; + } + // Zero only the expanded bits + _allocStart[block + oldBlocks .. block + newBlocks] = 0; + assert(_allocStart[block]); + return true; + } + + /// Ditto + bool deallocate(void[] b) + { + // No need to touch _allocStart here - except for the first bit, it's + // meaningless in freed memory. The first bit is already 1. + return _heap.deallocate(b); + // TODO: one smart thing to do is reduce memory occupied by + // _allocStart if we're freeing the rightmost block. + } + + /// Ditto + Ternary resolveInternalPointer(const void* p, ref void[] result) + { + if (p < _heap._payload.ptr + || p >= _heap._payload.ptr + _heap._payload.length) + { + return Ternary.no; + } + // Find block start + auto block = (p - _heap._payload.ptr) / _heap.blockSize; + if (block >= _allocStart.length) return Ternary.no; + // Within an allocation, must find the 1 just to the left of it + auto i = _allocStart.find1Backward(block); + if (i == i.max) return Ternary.no; + auto j = _allocStart.find1(i + 1); + result = _heap._payload.ptr[cast(size_t) (_heap.blockSize * i) + .. cast(size_t) (_heap.blockSize * j)]; + return Ternary.yes; + } + + /// Ditto + Ternary empty() + { + return _heap.empty; + } + + // Currently unused + private void markAllAsUnused() + { + // Mark all deallocated memory with 1 so we minimize damage created by + // false pointers. TODO: improve speed. + foreach (i, ref e; _allocStart.rep) + { + // Set to 1 all bits in _allocStart[i] that were 0 in control, and + // leave the others unchanged. + // (0, 0) => 1; (0, 1) => 0; (1, 0) => 1; (1, 1) => 1 + e |= ~_heap._control.rep[i]; + } + // Now zero all control bits + _heap._control[] = 0; + // EXCEPT for the _allocStart block itself + markAsUsed(_allocStart.rep); + } + + // Currently unused + private bool markAsUsed(void[] b) + { + // Locate position + immutable pos = b.ptr - _heap._payload.ptr; + assert(pos % _heap.blockSize == 0); + auto blockIdx = pos / _heap.blockSize; + if (_heap._control[blockIdx]) return false; + // Round up size to multiple of block size + auto blocks = b.length.divideRoundUp(_heap.blockSize); + _heap._control[blockIdx .. blockIdx + blocks] = 1; + return true; + } + + // Currently unused + private void doneMarking() + { + // Nothing to do, what's free stays free. + } +} + +@system unittest +{ + import std.typecons : Ternary; + + auto h = BitmappedBlockWithInternalPointers!(4096)(new ubyte[4096 * 1024]); + auto b = h.allocate(123); + assert(b.length == 123); + + void[] p; + Ternary r = h.resolveInternalPointer(b.ptr + 17, p); + assert(p.ptr is b.ptr); + assert(p.length >= b.length); + b = h.allocate(4096); + + h.resolveInternalPointer(b.ptr, p); + assert(p is b); + + h.resolveInternalPointer(b.ptr + 11, p); + assert(p is b); + + void[] unchanged = p; + h.resolveInternalPointer(b.ptr - 40_970, p); + assert(p is unchanged); + + assert(h.expand(b, 1)); + assert(b.length == 4097); + h.resolveInternalPointer(b.ptr + 4096, p); + assert(p.ptr is b.ptr); +} + +/** +Returns the number of most significant ones before a zero can be found in $(D +x). If $(D x) contains no zeros (i.e. is equal to $(D ulong.max)), returns 64. +*/ +private uint leadingOnes(ulong x) +{ + uint result = 0; + while (cast(long) x < 0) + { + ++result; + x <<= 1; + } + return result; +} + +@system unittest +{ + assert(leadingOnes(0) == 0); + assert(leadingOnes(~0UL) == 64); + assert(leadingOnes(0xF000_0000_0000_0000) == 4); + assert(leadingOnes(0xE400_0000_0000_0000) == 3); + assert(leadingOnes(0xC700_0200_0000_0000) == 2); + assert(leadingOnes(0x8000_0030_0000_0000) == 1); + assert(leadingOnes(0x2000_0000_0000_0000) == 0); +} + +/** +Finds a run of contiguous ones in $(D x) of length at least $(D n). +*/ +private uint findContigOnes(ulong x, uint n) +{ + while (n > 1) + { + immutable s = n >> 1; + x &= x << s; + n -= s; + } + return leadingOnes(~x); +} + +@system unittest +{ + assert(findContigOnes(0x0000_0000_0000_0300, 2) == 54); + + assert(findContigOnes(~0UL, 1) == 0); + assert(findContigOnes(~0UL, 2) == 0); + assert(findContigOnes(~0UL, 32) == 0); + assert(findContigOnes(~0UL, 64) == 0); + assert(findContigOnes(0UL, 1) == 64); + + assert(findContigOnes(0x4000_0000_0000_0000, 1) == 1); + assert(findContigOnes(0x0000_0F00_0000_0000, 4) == 20); +} + +/* +Unconditionally sets the bits from lsb through msb in w to zero. +*/ +private void setBits(ref ulong w, uint lsb, uint msb) +{ + assert(lsb <= msb && msb < 64); + const mask = (ulong.max << lsb) & (ulong.max >> (63 - msb)); + w |= mask; +} + +@system unittest +{ + ulong w; + w = 0; setBits(w, 0, 63); assert(w == ulong.max); + w = 0; setBits(w, 1, 63); assert(w == ulong.max - 1); + w = 6; setBits(w, 0, 1); assert(w == 7); + w = 6; setBits(w, 3, 3); assert(w == 14); +} + +/* Are bits from lsb through msb in w zero? If so, make then 1 +and return the resulting w. Otherwise, just return 0. +*/ +private bool setBitsIfZero(ref ulong w, uint lsb, uint msb) +{ + assert(lsb <= msb && msb < 64); + const mask = (ulong.max << lsb) & (ulong.max >> (63 - msb)); + if (w & mask) return false; + w |= mask; + return true; +} + +// Assigns bits in w from lsb through msb to zero. +private void resetBits(ref ulong w, uint lsb, uint msb) +{ + assert(lsb <= msb && msb < 64); + const mask = (ulong.max << lsb) & (ulong.max >> (63 - msb)); + w &= ~mask; +} + +/* +Bit disposition is MSB=0 (leftmost, big endian). +*/ +private struct BitVector +{ + ulong[] _rep; + + auto rep() { return _rep; } + + this(ulong[] data) { _rep = data; } + + void opSliceAssign(bool b) { _rep[] = b ? ulong.max : 0; } + + void opSliceAssign(bool b, ulong x, ulong y) + { + assert(x <= y && y <= _rep.length * 64); + if (x == y) return; + --y; + assert(x / 64 <= size_t.max); + immutable i1 = cast(size_t) (x / 64); + immutable uint b1 = 63 - x % 64; + assert(y / 64 <= size_t.max); + immutable i2 = cast(size_t) (y / 64); + immutable uint b2 = 63 - y % 64; + assert(i1 <= i2 && i2 < _rep.length); + if (i1 == i2) + { + // Inside the same word + assert(b1 >= b2); + if (b) setBits(_rep[i1], b2, b1); + else resetBits(_rep[i1], b2, b1); + } + else + { + // Spans multiple words + assert(i1 < i2); + if (b) setBits(_rep[i1], 0, b1); + else resetBits(_rep[i1], 0, b1); + _rep[i1 + 1 .. i2] = b; + if (b) setBits(_rep[i2], b2, 63); + else resetBits(_rep[i2], b2, 63); + } + } + + bool opIndex(ulong x) + { + assert(x < length); + return (_rep[cast(size_t) (x / 64)] + & (0x8000_0000_0000_0000UL >> (x % 64))) != 0; + } + + void opIndexAssign(bool b, ulong x) + { + assert(x / 64 <= size_t.max); + immutable i = cast(size_t) (x / 64); + immutable j = 0x8000_0000_0000_0000UL >> (x % 64); + if (b) _rep[i] |= j; + else _rep[i] &= ~j; + } + + ulong length() const + { + return _rep.length * 64; + } + + /* Returns the index of the first 1 to the right of i (including i itself), + or length if not found. + */ + ulong find1(ulong i) + { + assert(i < length); + assert(i / 64 <= size_t.max); + auto w = cast(size_t) (i / 64); + immutable b = i % 64; // 0 through 63, 0 when i == 0 + immutable mask = ulong.max >> b; + if (auto current = _rep[w] & mask) + { + // Great, found + return w * 64 + leadingOnes(~current); + } + // The current word doesn't have the solution, find the leftmost 1 + // going to the right. + for (++w; w < _rep.length; ++w) + { + if (auto current = _rep[w]) + { + return w * 64 + leadingOnes(~current); + } + } + return length; + } + + /* Returns the index of the first 1 to the left of i (including i itself), + or ulong.max if not found. + */ + ulong find1Backward(ulong i) + { + assert(i < length); + auto w = cast(size_t) (i / 64); + immutable b = 63 - (i % 64); // 0 through 63, 63 when i == 0 + immutable mask = ~((1UL << b) - 1); + assert(mask != 0); + // First, let's see if the current word has a bit larger than ours. + if (auto currentWord = _rep[w] & mask) + { + // Great, this word contains the result. + return w * 64 + 63 - currentWord.trailingZeros; + } + // The current word doesn't have the solution, find the rightmost 1 + // going to the left. + while (w >= 1) + { + --w; + if (auto currentWord = _rep[w]) + return w * 64 + (63 - currentWord.trailingZeros); + } + return ulong.max; + } + + /// Are all bits zero? + bool allAre0() const + { + foreach (w; _rep) if (w) return false; + return true; + } + + /// Are all bits one? + bool allAre1() const + { + foreach (w; _rep) if (w != ulong.max) return false; + return true; + } + + ulong findZeros(immutable size_t howMany, ulong start) + { + assert(start < length); + assert(howMany > 64); + auto i = cast(size_t) (start / 64); + while (_rep[i] & 1) + { + // No trailing zeros in this word, try the next one + if (++i == _rep.length) return ulong.max; + start = i * 64; + } + // Adjust start to have only trailing zeros after it + auto prefixLength = 64; + while (_rep[i] & (ulong.max >> (64 - prefixLength))) + { + assert(prefixLength > 0); + --prefixLength; + ++start; + } + + assert(howMany > prefixLength); + auto needed = howMany - prefixLength; + for (++i; needed >= 64; needed -= 64, ++i) + { + if (i >= _rep.length) return ulong.max; + if (_rep[i] != 0) return findZeros(howMany, i * 64); + } + // Leftover < 64 bits + assert(needed < 64); + if (!needed) return start; + if (i >= _rep.length) return ulong.max; + if (leadingOnes(~_rep[i]) >= needed) return start; + return findZeros(howMany, i * 64); + } +} + +@system unittest +{ + auto v = BitVector(new ulong[10]); + assert(v.length == 640); + + v[] = 0; + v[53] = 1; + assert(v[52] == 0); + assert(v[53] == 1); + assert(v[54] == 0); + + v[] = 0; + v[53 .. 55] = 1; + assert(v[52] == 0); + assert(v[53] == 1); + assert(v[54] == 1); + assert(v[55] == 0); + + v[] = 0; + v[2 .. 65] = 1; + assert(v.rep[0] == 0x3FFF_FFFF_FFFF_FFFF); + assert(v.rep[1] == 0x8000_0000_0000_0000); + assert(v.rep[2] == 0); + + v[] = 0; + assert(v.find1Backward(0) == ulong.max); + assert(v.find1Backward(43) == ulong.max); + assert(v.find1Backward(83) == ulong.max); + + v[0] = 1; + assert(v.find1Backward(0) == 0); + assert(v.find1Backward(43) == 0); + import std.conv : text; + assert(v.find1Backward(83) == 0, text(v.find1Backward(83))); + + v[0] = 0; + v[101] = 1; + assert(v.find1Backward(0) == ulong.max); + assert(v.find1Backward(43) == ulong.max); + assert(v.find1Backward(83) == ulong.max); + assert(v.find1Backward(100) == ulong.max); + assert(v.find1Backward(101) == 101); + assert(v.find1Backward(553) == 101); + + v[0 .. v.length] = 0; + v[v.length .. v.length] = 0; + v[0 .. 0] = 0; + + v[] = 0; + assert(v.find1(0) == v.length); + v[139] = 1; + assert(v.find1(0) == 139); + assert(v.find1(100) == 139); + assert(v.find1(138) == 139); + assert(v.find1(139) == 139); + assert(v.find1(140) == v.length); + + v[] = 0; + assert(v.findZeros(100, 0) == 0); + foreach (i; 0 .. 500) + assert(v.findZeros(100, i) == i, text(v.findZeros(100, i), " != ", i)); + assert(v.findZeros(540, 99) == 99); + assert(v.findZeros(99, 540) == 540); + assert(v.findZeros(540, 100) == 100); + assert(v.findZeros(640, 0) == 0); + assert(v.findZeros(641, 1) == ulong.max); + assert(v.findZeros(641, 100) == ulong.max); +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/bucketizer.d b/libphobos/src/std/experimental/allocator/building_blocks/bucketizer.d new file mode 100644 index 0000000..64067dd --- /dev/null +++ b/libphobos/src/std/experimental/allocator/building_blocks/bucketizer.d @@ -0,0 +1,241 @@ +/// +module std.experimental.allocator.building_blocks.bucketizer; + +/** + +A $(D Bucketizer) uses distinct allocators for handling allocations of sizes in +the intervals $(D [min, min + step - 1]), $(D [min + step, min + 2 * step - 1]), +$(D [min + 2 * step, min + 3 * step - 1]), $(D ...), $(D [max - step + 1, max]). + +$(D Bucketizer) holds a fixed-size array of allocators and dispatches calls to +them appropriately. The size of the array is $(D (max + 1 - min) / step), which +must be an exact division. + +Allocations for sizes smaller than $(D min) or larger than $(D max) are illegal +for $(D Bucketizer). To handle them separately, $(D Segregator) may be of use. + +*/ +struct Bucketizer(Allocator, size_t min, size_t max, size_t step) +{ + import common = std.experimental.allocator.common : roundUpToMultipleOf; + import std.traits : hasMember; + import std.typecons : Ternary; + + static assert((max - (min - 1)) % step == 0, + "Invalid limits when instantiating " ~ Bucketizer.stringof); + + // state + /** + The array of allocators is publicly available for e.g. initialization and + inspection. + */ + Allocator[(max + 1 - min) / step] buckets; + + private Allocator* allocatorFor(size_t n) + { + const i = (n - min) / step; + return i < buckets.length ? buckets.ptr + i : null; + } + + /** + The alignment offered is the same as $(D Allocator.alignment). + */ + enum uint alignment = Allocator.alignment; + + /** + Rounds up to the maximum size of the bucket in which $(D bytes) falls. + */ + size_t goodAllocSize(size_t bytes) const + { + // round up bytes such that bytes - min + 1 is a multiple of step + assert(bytes >= min); + const min_1 = min - 1; + return min_1 + roundUpToMultipleOf(bytes - min_1, step); + } + + /** + Directs the call to either one of the $(D buckets) allocators. + */ + void[] allocate(size_t bytes) + { + if (!bytes) return null; + if (auto a = allocatorFor(bytes)) + { + const actual = goodAllocSize(bytes); + auto result = a.allocate(actual); + return result.ptr ? result.ptr[0 .. bytes] : null; + } + return null; + } + + /** + Directs the call to either one of the $(D buckets) allocators. Defined only + if `Allocator` defines `alignedAllocate`. + */ + static if (hasMember!(Allocator, "alignedAllocate")) + void[] alignedAllocate(size_t bytes, uint a) + { + if (!bytes) return null; + if (auto a = allocatorFor(b.length)) + { + const actual = goodAllocSize(bytes); + auto result = a.alignedAllocate(actual); + return result.ptr ? result.ptr[0 .. bytes] : null; + } + return null; + } + + /** + This method allows expansion within the respective bucket range. It succeeds + if both $(D b.length) and $(D b.length + delta) fall in a range of the form + $(D [min + k * step, min + (k + 1) * step - 1]). + */ + bool expand(ref void[] b, size_t delta) + { + if (!b.ptr) return delta == 0; + assert(b.length >= min && b.length <= max); + const available = goodAllocSize(b.length); + const desired = b.length + delta; + if (available < desired) return false; + b = b.ptr[0 .. desired]; + return true; + } + + /** + This method allows reallocation within the respective bucket range. If both + $(D b.length) and $(D size) fall in a range of the form $(D [min + k * + step, min + (k + 1) * step - 1]), then reallocation is in place. Otherwise, + reallocation with moving is attempted. + */ + bool reallocate(ref void[] b, size_t size) + { + if (size == 0) + { + deallocate(b); + b = null; + return true; + } + if (size >= b.length) + { + return expand(b, size - b.length); + } + assert(b.length >= min && b.length <= max); + if (goodAllocSize(size) == goodAllocSize(b.length)) + { + b = b.ptr[0 .. size]; + return true; + } + // Move cross buckets + return common.reallocate(this, b, size); + } + + /** + Similar to `reallocate`, with alignment. Defined only if `Allocator` + defines `alignedReallocate`. + */ + static if (hasMember!(Allocator, "alignedReallocate")) + bool alignedReallocate(ref void[] b, size_t size, uint a) + { + if (size == 0) + { + deallocate(b); + b = null; + return true; + } + if (size >= b.length) + { + return expand(b, size - b.length); + } + assert(b.length >= min && b.length <= max); + if (goodAllocSize(size) == goodAllocSize(b.length)) + { + b = b.ptr[0 .. size]; + return true; + } + // Move cross buckets + return .alignedReallocate(this, b, size, a); + } + + /** + Defined only if `Allocator` defines `owns`. Finds the owner of `b` and forwards the call to it. + */ + static if (hasMember!(Allocator, "owns")) + Ternary owns(void[] b) + { + if (!b.ptr) return Ternary.no; + if (auto a = allocatorFor(b.length)) + { + const actual = goodAllocSize(b.length); + return a.owns(b.ptr[0 .. actual]); + } + return Ternary.no; + } + + /** + This method is only defined if $(D Allocator) defines $(D deallocate). + */ + static if (hasMember!(Allocator, "deallocate")) + bool deallocate(void[] b) + { + if (!b.ptr) return true; + if (auto a = allocatorFor(b.length)) + { + a.deallocate(b.ptr[0 .. goodAllocSize(b.length)]); + } + return true; + } + + /** + This method is only defined if all allocators involved define $(D + deallocateAll), and calls it for each bucket in turn. Returns `true` if all + allocators could deallocate all. + */ + static if (hasMember!(Allocator, "deallocateAll")) + bool deallocateAll() + { + bool result = true; + foreach (ref a; buckets) + { + if (!a.deallocateAll()) result = false; + } + return result; + } + + /** + This method is only defined if all allocators involved define $(D + resolveInternalPointer), and tries it for each bucket in turn. + */ + static if (hasMember!(Allocator, "resolveInternalPointer")) + Ternary resolveInternalPointer(const void* p, ref void[] result) + { + foreach (ref a; buckets) + { + Ternary r = a.resolveInternalPointer(p, result); + if (r == Ternary.yes) return r; + } + return Ternary.no; + } +} + +/// +@system unittest +{ + import std.algorithm.comparison : max; + import std.experimental.allocator.building_blocks.allocator_list : AllocatorList; + import std.experimental.allocator.building_blocks.free_list : FreeList; + import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.common : unbounded; + import std.experimental.allocator.mallocator : Mallocator; + import std.typecons : Ternary; + Bucketizer!( + FreeList!( + AllocatorList!( + (size_t n) => Region!Mallocator(max(n, 1024 * 1024))), + 0, unbounded), + 65, 512, 64) a; + auto b = a.allocate(400); + assert(b.length == 400); + assert(a.owns(b) == Ternary.yes); + void[] p; + a.deallocate(b); +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/fallback_allocator.d b/libphobos/src/std/experimental/allocator/building_blocks/fallback_allocator.d new file mode 100644 index 0000000..ca7961b --- /dev/null +++ b/libphobos/src/std/experimental/allocator/building_blocks/fallback_allocator.d @@ -0,0 +1,355 @@ +/// +module std.experimental.allocator.building_blocks.fallback_allocator; + +import std.experimental.allocator.common; + +/** +$(D FallbackAllocator) is the allocator equivalent of an "or" operator in +algebra. An allocation request is first attempted with the $(D Primary) +allocator. If that returns $(D null), the request is forwarded to the $(D +Fallback) allocator. All other requests are dispatched appropriately to one of +the two allocators. + +In order to work, $(D FallbackAllocator) requires that $(D Primary) defines the +$(D owns) method. This is needed in order to decide which allocator was +responsible for a given allocation. + +$(D FallbackAllocator) is useful for fast, special-purpose allocators backed up +by general-purpose allocators. The example below features a stack region backed +up by the $(D GCAllocator). +*/ +struct FallbackAllocator(Primary, Fallback) +{ + import std.algorithm.comparison : min; + import std.traits : hasMember; + import std.typecons : Ternary; + + @system unittest + { + testAllocator!(() => FallbackAllocator()); + } + + /// The primary allocator. + static if (stateSize!Primary) Primary primary; + else alias primary = Primary.instance; + + /// The fallback allocator. + static if (stateSize!Fallback) Fallback fallback; + else alias fallback = Fallback.instance; + + /** + If both $(D Primary) and $(D Fallback) are stateless, $(D FallbackAllocator) + defines a static instance called `instance`. + */ + static if (!stateSize!Primary && !stateSize!Fallback) + { + static FallbackAllocator instance; + } + + /** + The alignment offered is the minimum of the two allocators' alignment. + */ + enum uint alignment = min(Primary.alignment, Fallback.alignment); + + /** + Allocates memory trying the primary allocator first. If it returns $(D + null), the fallback allocator is tried. + */ + void[] allocate(size_t s) + { + auto result = primary.allocate(s); + return result.length == s ? result : fallback.allocate(s); + } + + /** + $(D FallbackAllocator) offers $(D alignedAllocate) iff at least one of the + allocators also offers it. It attempts to allocate using either or both. + */ + static if (hasMember!(Primary, "alignedAllocate") + || hasMember!(Fallback, "alignedAllocate")) + void[] alignedAllocate(size_t s, uint a) + { + static if (hasMember!(Primary, "alignedAllocate")) + {{ + auto result = primary.alignedAllocate(s, a); + if (result.length == s) return result; + }} + static if (hasMember!(Fallback, "alignedAllocate")) + {{ + auto result = fallback.alignedAllocate(s, a); + if (result.length == s) return result; + }} + return null; + } + + /** + + $(D expand) is defined if and only if at least one of the allocators + defines $(D expand). It works as follows. If $(D primary.owns(b)), then the + request is forwarded to $(D primary.expand) if it is defined, or fails + (returning $(D false)) otherwise. If $(D primary) does not own $(D b), then + the request is forwarded to $(D fallback.expand) if it is defined, or fails + (returning $(D false)) otherwise. + + */ + static if (hasMember!(Primary, "owns") + && (hasMember!(Primary, "expand") || hasMember!(Fallback, "expand"))) + bool expand(ref void[] b, size_t delta) + { + if (!delta) return true; + if (!b.ptr) return false; + if (primary.owns(b) == Ternary.yes) + { + static if (hasMember!(Primary, "expand")) + return primary.expand(b, delta); + else + return false; + } + static if (hasMember!(Fallback, "expand")) + return fallback.expand(b, delta); + else + return false; + } + + /** + + $(D reallocate) works as follows. If $(D primary.owns(b)), then $(D + primary.reallocate(b, newSize)) is attempted. If it fails, an attempt is + made to move the allocation from $(D primary) to $(D fallback). + + If $(D primary) does not own $(D b), then $(D fallback.reallocate(b, + newSize)) is attempted. If that fails, an attempt is made to move the + allocation from $(D fallback) to $(D primary). + + */ + static if (hasMember!(Primary, "owns")) + bool reallocate(ref void[] b, size_t newSize) + { + bool crossAllocatorMove(From, To)(ref From from, ref To to) + { + auto b1 = to.allocate(newSize); + if (b1.length != newSize) return false; + if (b.length < newSize) b1[0 .. b.length] = b[]; + else b1[] = b[0 .. newSize]; + static if (hasMember!(From, "deallocate")) + from.deallocate(b); + b = b1; + return true; + } + + if (b is null || primary.owns(b) == Ternary.yes) + { + return primary.reallocate(b, newSize) + // Move from primary to fallback + || crossAllocatorMove(primary, fallback); + } + return fallback.reallocate(b, newSize) + // Interesting. Move from fallback to primary. + || crossAllocatorMove(fallback, primary); + } + + static if (hasMember!(Primary, "owns") + && (hasMember!(Primary, "alignedAllocate") + || hasMember!(Fallback, "alignedAllocate"))) + bool alignedReallocate(ref void[] b, size_t newSize, uint a) + { + bool crossAllocatorMove(From, To)(ref From from, ref To to) + { + static if (!hasMember!(To, "alignedAllocate")) + { + return false; + } + else + { + auto b1 = to.alignedAllocate(newSize, a); + if (b1.length != newSize) return false; + if (b.length < newSize) b1[0 .. b.length] = b[]; + else b1[] = b[0 .. newSize]; + static if (hasMember!(From, "deallocate")) + from.deallocate(b); + b = b1; + return true; + } + } + + static if (hasMember!(Primary, "alignedAllocate")) + { + if (b is null || primary.owns(b) == Ternary.yes) + { + return primary.alignedReallocate(b, newSize, a) + || crossAllocatorMove(primary, fallback); + } + } + static if (hasMember!(Fallback, "alignedAllocate")) + { + return fallback.alignedReallocate(b, newSize, a) + || crossAllocatorMove(fallback, primary); + } + else + { + return false; + } + } + + /** + $(D owns) is defined if and only if both allocators define $(D owns). + Returns $(D primary.owns(b) | fallback.owns(b)). + */ + static if (hasMember!(Primary, "owns") && hasMember!(Fallback, "owns")) + Ternary owns(void[] b) + { + return primary.owns(b) | fallback.owns(b); + } + + /** + $(D resolveInternalPointer) is defined if and only if both allocators + define it. + */ + static if (hasMember!(Primary, "resolveInternalPointer") + && hasMember!(Fallback, "resolveInternalPointer")) + Ternary resolveInternalPointer(const void* p, ref void[] result) + { + Ternary r = primary.resolveInternalPointer(p, result); + return r == Ternary.no ? fallback.resolveInternalPointer(p, result) : r; + } + + /** + $(D deallocate) is defined if and only if at least one of the allocators + define $(D deallocate). It works as follows. If $(D primary.owns(b)), + then the request is forwarded to $(D primary.deallocate) if it is defined, + or is a no-op otherwise. If $(D primary) does not own $(D b), then the + request is forwarded to $(D fallback.deallocate) if it is defined, or is a + no-op otherwise. + */ + static if (hasMember!(Primary, "owns") && + (hasMember!(Primary, "deallocate") + || hasMember!(Fallback, "deallocate"))) + bool deallocate(void[] b) + { + if (primary.owns(b) == Ternary.yes) + { + static if (hasMember!(Primary, "deallocate")) + return primary.deallocate(b); + else + return false; + } + else + { + static if (hasMember!(Fallback, "deallocate")) + return fallback.deallocate(b); + else + return false; + } + } + + /** + $(D empty) is defined if both allocators also define it. + + Returns: $(D primary.empty & fallback.empty) + */ + static if (hasMember!(Primary, "empty") && hasMember!(Fallback, "empty")) + Ternary empty() + { + return primary.empty & fallback.empty; + } +} + +@system unittest +{ + import std.conv : text; + import std.experimental.allocator.building_blocks.region : InSituRegion; + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.typecons : Ternary; + FallbackAllocator!(InSituRegion!16_384, GCAllocator) a; + // This allocation uses the stack + auto b1 = a.allocate(1024); + assert(b1.length == 1024, text(b1.length)); + assert(a.primary.owns(b1) == Ternary.yes); + // This large allocation will go to the Mallocator + auto b2 = a.allocate(1024 * 1024); + assert(a.primary.owns(b2) == Ternary.no); + a.deallocate(b1); + a.deallocate(b2); +} + +/* +Forwards an argument from one function to another +*/ +private auto ref forward(alias arg)() +{ + static if (__traits(isRef, arg)) + { + return arg; + } + else + { + import std.algorithm.mutation : move; + return move(arg); + } +} + +@safe unittest +{ + void fun(T)(auto ref T, string) { /* ... */ } + void gun(T...)(auto ref T args) + { + fun(forward!(args[0]), forward!(args[1])); + } + gun(42, "hello"); + int x; + gun(x, "hello"); +} + +@safe unittest +{ + static void checkByRef(T)(auto ref T value) + { + static assert(__traits(isRef, value)); + } + + static void checkByVal(T)(auto ref T value) + { + static assert(!__traits(isRef, value)); + } + + static void test1(ref int a) { checkByRef(forward!a); } + static void test2(int a) { checkByVal(forward!a); } + static void test3() { int a; checkByVal(forward!a); } +} + +/** +Convenience function that uses type deduction to return the appropriate +$(D FallbackAllocator) instance. To initialize with allocators that don't have +state, use their $(D it) static member. +*/ +FallbackAllocator!(Primary, Fallback) +fallbackAllocator(Primary, Fallback)(auto ref Primary p, auto ref Fallback f) +{ + alias R = FallbackAllocator!(Primary, Fallback); + + static if (stateSize!Primary) + static if (stateSize!Fallback) + return R(forward!p, forward!f); + else + return R(forward!p); + else + static if (stateSize!Fallback) + return R(forward!f); + else + return R(); +} + +/// +@system unittest +{ + import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.typecons : Ternary; + auto a = fallbackAllocator(Region!GCAllocator(1024), GCAllocator.instance); + auto b1 = a.allocate(1020); + assert(b1.length == 1020); + assert(a.primary.owns(b1) == Ternary.yes); + auto b2 = a.allocate(10); + assert(b2.length == 10); + assert(a.primary.owns(b2) == Ternary.no); +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/free_list.d b/libphobos/src/std/experimental/allocator/building_blocks/free_list.d new file mode 100644 index 0000000..8860806 --- /dev/null +++ b/libphobos/src/std/experimental/allocator/building_blocks/free_list.d @@ -0,0 +1,1205 @@ +/// +module std.experimental.allocator.building_blocks.free_list; + +import std.experimental.allocator.common; +import std.typecons : Flag, Yes, No; + +/** + +$(HTTP en.wikipedia.org/wiki/Free_list, Free list allocator), stackable on top of +another allocator. Allocation requests between $(D min) and $(D max) bytes are +rounded up to $(D max) and served from a singly-linked list of buffers +deallocated in the past. All other allocations are directed to $(D +ParentAllocator). Due to the simplicity of free list management, allocations +from the free list are fast. + +One instantiation is of particular interest: $(D FreeList!(0, unbounded)) puts +every deallocation in the freelist, and subsequently serves any allocation from +the freelist (if not empty). There is no checking of size matching, which would +be incorrect for a freestanding allocator but is both correct and fast when an +owning allocator on top of the free list allocator (such as $(D Segregator)) is +already in charge of handling size checking. + +The following methods are defined if $(D ParentAllocator) defines them, and +forward to it: $(D expand), $(D owns), $(D reallocate). + +*/ +struct FreeList(ParentAllocator, + size_t minSize, size_t maxSize = minSize, + Flag!"adaptive" adaptive = No.adaptive) +{ + import std.conv : text; + import std.exception : enforce; + import std.traits : hasMember; + import std.typecons : Ternary; + + static assert(minSize != unbounded, "Use minSize = 0 for no low bound."); + static assert(maxSize >= (void*).sizeof, + "Maximum size must accommodate a pointer."); + + private enum unchecked = minSize == 0 && maxSize == unbounded; + + private enum hasTolerance = !unchecked && (minSize != maxSize + || maxSize == chooseAtRuntime); + + static if (minSize == chooseAtRuntime) + { + /** + Returns the smallest allocation size eligible for allocation from the + freelist. (If $(D minSize != chooseAtRuntime), this is simply an alias + for $(D minSize).) + */ + @property size_t min() const + { + assert(_min != chooseAtRuntime); + return _min; + } + /** + If $(D FreeList) has been instantiated with $(D minSize == + chooseAtRuntime), then the $(D min) property is writable. Setting it + must precede any allocation. + + Params: + low = new value for $(D min) + + Precondition: $(D low <= max), or $(D maxSize == chooseAtRuntime) and + $(D max) has not yet been initialized. Also, no allocation has been + yet done with this allocator. + + Postcondition: $(D min == low) + */ + @property void min(size_t low) + { + assert(low <= max || max == chooseAtRuntime); + minimize; + _min = low; + } + } + else + { + alias min = minSize; + } + + static if (maxSize == chooseAtRuntime) + { + /** + Returns the largest allocation size eligible for allocation from the + freelist. (If $(D maxSize != chooseAtRuntime), this is simply an alias + for $(D maxSize).) All allocation requests for sizes greater than or + equal to $(D min) and less than or equal to $(D max) are rounded to $(D + max) and forwarded to the parent allocator. When the block fitting the + same constraint gets deallocated, it is put in the freelist with the + allocated size assumed to be $(D max). + */ + @property size_t max() const { return _max; } + + /** + If $(D FreeList) has been instantiated with $(D maxSize == + chooseAtRuntime), then the $(D max) property is writable. Setting it + must precede any allocation. + + Params: + high = new value for $(D max) + + Precondition: $(D high >= min), or $(D minSize == chooseAtRuntime) and + $(D min) has not yet been initialized. Also $(D high >= (void*).sizeof). Also, no allocation has been yet done with this allocator. + + Postcondition: $(D max == high) + */ + @property void max(size_t high) + { + assert((high >= min || min == chooseAtRuntime) + && high >= (void*).sizeof); + minimize; + _max = high; + } + + /// + @safe unittest + { + import std.experimental.allocator.common : chooseAtRuntime; + import std.experimental.allocator.mallocator : Mallocator; + + FreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime) a; + a.min = 64; + a.max = 128; + assert(a.min == 64); + assert(a.max == 128); + } + } + else + { + alias max = maxSize; + } + + private bool tooSmall(size_t n) const + { + static if (minSize == 0) return false; + else return n < min; + } + + private bool tooLarge(size_t n) const + { + static if (maxSize == unbounded) return false; + else return n > max; + } + + private bool freeListEligible(size_t n) const + { + static if (unchecked) + { + return true; + } + else + { + static if (minSize == 0) + { + if (!n) return false; + } + static if (minSize == maxSize && minSize != chooseAtRuntime) + return n == maxSize; + else + return !tooSmall(n) && !tooLarge(n); + } + } + + static if (!unchecked) + private void[] blockFor(Node* p) + { + assert(p); + return (cast(void*) p)[0 .. max]; + } + + // statistics + static if (adaptive == Yes.adaptive) + { + private enum double windowLength = 1000.0; + private enum double tooFewMisses = 0.01; + private double probMiss = 1.0; // start with a high miss probability + private uint accumSamples, accumMisses; + + void updateStats() + { + assert(accumSamples >= accumMisses); + /* + Given that for the past windowLength samples we saw misses with + estimated probability probMiss, and assuming the new sample wasMiss or + not, what's the new estimated probMiss? + */ + probMiss = (probMiss * windowLength + accumMisses) + / (windowLength + accumSamples); + assert(probMiss <= 1.0); + accumSamples = 0; + accumMisses = 0; + // If probability to miss is under x%, yank one off the freelist + static if (!unchecked) + { + if (probMiss < tooFewMisses && _root) + { + auto b = blockFor(_root); + _root = _root.next; + parent.deallocate(b); + } + } + } + } + + private struct Node { Node* next; } + static assert(ParentAllocator.alignment >= Node.alignof); + + // state + /** + The parent allocator. Depending on whether $(D ParentAllocator) holds state + or not, this is a member variable or an alias for + `ParentAllocator.instance`. + */ + static if (stateSize!ParentAllocator) ParentAllocator parent; + else alias parent = ParentAllocator.instance; + private Node* root; + static if (minSize == chooseAtRuntime) private size_t _min = chooseAtRuntime; + static if (maxSize == chooseAtRuntime) private size_t _max = chooseAtRuntime; + + /** + Alignment offered. + */ + alias alignment = ParentAllocator.alignment; + + /** + If $(D maxSize == unbounded), returns $(D parent.goodAllocSize(bytes)). + Otherwise, returns $(D max) for sizes in the interval $(D [min, max]), and + $(D parent.goodAllocSize(bytes)) otherwise. + + Precondition: + If set at runtime, $(D min) and/or $(D max) must be initialized + appropriately. + + Postcondition: + $(D result >= bytes) + */ + size_t goodAllocSize(size_t bytes) + { + assert(minSize != chooseAtRuntime && maxSize != chooseAtRuntime); + static if (maxSize != unbounded) + { + if (freeListEligible(bytes)) + { + assert(parent.goodAllocSize(max) == max, + text("Wrongly configured freelist: maximum should be ", + parent.goodAllocSize(max), " instead of ", max)); + return max; + } + } + return parent.goodAllocSize(bytes); + } + + private void[] allocateEligible(size_t bytes) + { + assert(bytes); + if (root) + { + // faster + auto result = (cast(ubyte*) root)[0 .. bytes]; + root = root.next; + return result; + } + // slower + static if (hasTolerance) + { + immutable toAllocate = max; + } + else + { + alias toAllocate = bytes; + } + assert(toAllocate == max || max == unbounded); + auto result = parent.allocate(toAllocate); + static if (hasTolerance) + { + if (result) result = result.ptr[0 .. bytes]; + } + static if (adaptive == Yes.adaptive) + { + ++accumMisses; + updateStats; + } + return result; + } + + /** + Allocates memory either off of the free list or from the parent allocator. + If $(D n) is within $(D [min, max]) or if the free list is unchecked + ($(D minSize == 0 && maxSize == size_t.max)), then the free list is + consulted first. If not empty (hit), the block at the front of the free + list is removed from the list and returned. Otherwise (miss), a new block + of $(D max) bytes is allocated, truncated to $(D n) bytes, and returned. + + Params: + n = number of bytes to allocate + + Returns: + The allocated block, or $(D null). + + Precondition: + If set at runtime, $(D min) and/or $(D max) must be initialized + appropriately. + + Postcondition: $(D result.length == bytes || result is null) + */ + void[] allocate(size_t n) + { + static if (adaptive == Yes.adaptive) ++accumSamples; + assert(n < size_t.max / 2); + // fast path + if (freeListEligible(n)) + { + return allocateEligible(n); + } + // slower + static if (adaptive == Yes.adaptive) + { + updateStats; + } + return parent.allocate(n); + } + + // Forwarding methods + mixin(forwardToMember("parent", + "expand", "owns", "reallocate")); + + /** + If $(D block.length) is within $(D [min, max]) or if the free list is + unchecked ($(D minSize == 0 && maxSize == size_t.max)), then inserts the + block at the front of the free list. For all others, forwards to $(D + parent.deallocate) if $(D Parent.deallocate) is defined. + + Params: + block = Block to deallocate. + + Precondition: + If set at runtime, $(D min) and/or $(D max) must be initialized + appropriately. The block must have been allocated with this + freelist, and no dynamic changing of $(D min) or $(D max) is allowed to + occur between allocation and deallocation. + */ + bool deallocate(void[] block) + { + if (freeListEligible(block.length)) + { + if (min == 0) + { + // In this case a null pointer might have made it this far. + if (block is null) return true; + } + auto t = root; + root = cast(Node*) block.ptr; + root.next = t; + return true; + } + static if (hasMember!(ParentAllocator, "deallocate")) + return parent.deallocate(block); + else + return false; + } + + /** + Defined only if $(D ParentAllocator) defines $(D deallocateAll). If so, + forwards to it and resets the freelist. + */ + static if (hasMember!(ParentAllocator, "deallocateAll")) + bool deallocateAll() + { + root = null; + return parent.deallocateAll(); + } + + /** + Nonstandard function that minimizes the memory usage of the freelist by + freeing each element in turn. Defined only if $(D ParentAllocator) defines + $(D deallocate). + */ + static if (hasMember!(ParentAllocator, "deallocate") && !unchecked) + void minimize() + { + while (root) + { + auto nuke = blockFor(root); + root = root.next; + parent.deallocate(nuke); + } + } +} + +@system unittest +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + FreeList!(GCAllocator, 0, 8) fl; + assert(fl.root is null); + auto b1 = fl.allocate(7); + fl.allocate(8); + assert(fl.root is null); + fl.deallocate(b1); + assert(fl.root !is null); + fl.allocate(8); + assert(fl.root is null); +} + +/** +Free list built on top of exactly one contiguous block of memory. The block is +assumed to have been allocated with $(D ParentAllocator), and is released in +$(D ContiguousFreeList)'s destructor (unless $(D ParentAllocator) is $(D +NullAllocator)). + +$(D ContiguousFreeList) has most advantages of $(D FreeList) but fewer +disadvantages. It has better cache locality because items are closer to one +another. It imposes less fragmentation on its parent allocator. + +The disadvantages of $(D ContiguousFreeList) over $(D FreeList) are its pay +upfront model (as opposed to $(D FreeList)'s pay-as-you-go approach), and a +hard limit on the number of nodes in the list. Thus, a large number of long- +lived objects may occupy the entire block, making it unavailable for serving +allocations from the free list. However, an absolute cap on the free list size +may be beneficial. + +The options $(D minSize == unbounded) and $(D maxSize == unbounded) are not +available for $(D ContiguousFreeList). +*/ +struct ContiguousFreeList(ParentAllocator, + size_t minSize, size_t maxSize = minSize) +{ + import std.experimental.allocator.building_blocks.null_allocator + : NullAllocator; + import std.experimental.allocator.building_blocks.stats_collector + : StatsCollector, Options; + import std.traits : hasMember; + import std.typecons : Ternary; + + alias Impl = FreeList!(NullAllocator, minSize, maxSize); + enum unchecked = minSize == 0 && maxSize == unbounded; + alias Node = Impl.Node; + + alias SParent = StatsCollector!(ParentAllocator, Options.bytesUsed); + + // state + /** + The parent allocator. Depending on whether $(D ParentAllocator) holds state + or not, this is a member variable or an alias for + `ParentAllocator.instance`. + */ + SParent parent; + FreeList!(NullAllocator, minSize, maxSize) fl; + void[] support; + size_t allocated; + + /// Alignment offered. + enum uint alignment = (void*).alignof; + + private void initialize(ubyte[] buffer, size_t itemSize = fl.max) + { + assert(itemSize != unbounded && itemSize != chooseAtRuntime); + assert(buffer.ptr.alignedAt(alignment)); + immutable available = buffer.length / itemSize; + if (available == 0) return; + support = buffer; + fl.root = cast(Node*) buffer.ptr; + auto past = cast(Node*) (buffer.ptr + available * itemSize); + for (auto n = fl.root; ; ) + { + auto next = cast(Node*) (cast(ubyte*) n + itemSize); + if (next == past) + { + n.next = null; + break; + } + assert(next < past); + assert(n < next); + n.next = next; + n = next; + } + } + + /** + Constructors setting up the memory structured as a free list. + + Params: + buffer = Buffer to structure as a free list. If $(D ParentAllocator) is not + $(D NullAllocator), the buffer is assumed to be allocated by $(D parent) + and will be freed in the destructor. + parent = Parent allocator. For construction from stateless allocators, use + their `instance` static member. + bytes = Bytes (not items) to be allocated for the free list. Memory will be + allocated during construction and deallocated in the destructor. + max = Maximum size eligible for freelisting. Construction with this + parameter is defined only if $(D maxSize == chooseAtRuntime) or $(D maxSize + == unbounded). + min = Minimum size eligible for freelisting. Construction with this + parameter is defined only if $(D minSize == chooseAtRuntime). If this + condition is met and no $(D min) parameter is present, $(D min) is + initialized with $(D max). + */ + static if (!stateSize!ParentAllocator) + this(ubyte[] buffer) + { + initialize(buffer); + } + + /// ditto + static if (stateSize!ParentAllocator) + this(ParentAllocator parent, ubyte[] buffer) + { + initialize(buffer); + this.parent = SParent(parent); + } + + /// ditto + static if (!stateSize!ParentAllocator) + this(size_t bytes) + { + initialize(cast(ubyte[])(ParentAllocator.instance.allocate(bytes))); + } + + /// ditto + static if (stateSize!ParentAllocator) + this(ParentAllocator parent, size_t bytes) + { + initialize(cast(ubyte[])(parent.allocate(bytes))); + this.parent = SParent(parent); + } + + /// ditto + static if (!stateSize!ParentAllocator + && (maxSize == chooseAtRuntime || maxSize == unbounded)) + this(size_t bytes, size_t max) + { + static if (maxSize == chooseAtRuntime) fl.max = max; + static if (minSize == chooseAtRuntime) fl.min = max; + initialize(cast(ubyte[])(parent.allocate(bytes)), max); + } + + /// ditto + static if (stateSize!ParentAllocator + && (maxSize == chooseAtRuntime || maxSize == unbounded)) + this(ParentAllocator parent, size_t bytes, size_t max) + { + static if (maxSize == chooseAtRuntime) fl.max = max; + static if (minSize == chooseAtRuntime) fl.min = max; + initialize(cast(ubyte[])(parent.allocate(bytes)), max); + this.parent = SParent(parent); + } + + /// ditto + static if (!stateSize!ParentAllocator + && (maxSize == chooseAtRuntime || maxSize == unbounded) + && minSize == chooseAtRuntime) + this(size_t bytes, size_t min, size_t max) + { + static if (maxSize == chooseAtRuntime) fl.max = max; + fl.min = min; + initialize(cast(ubyte[])(parent.allocate(bytes)), max); + static if (stateSize!ParentAllocator) + this.parent = SParent(parent); + } + + /// ditto + static if (stateSize!ParentAllocator + && (maxSize == chooseAtRuntime || maxSize == unbounded) + && minSize == chooseAtRuntime) + this(ParentAllocator parent, size_t bytes, size_t min, size_t max) + { + static if (maxSize == chooseAtRuntime) fl.max = max; + fl.min = min; + initialize(cast(ubyte[])(parent.allocate(bytes)), max); + static if (stateSize!ParentAllocator) + this.parent = SParent(parent); + } + + /** + If $(D n) is eligible for freelisting, returns $(D max). Otherwise, returns + $(D parent.goodAllocSize(n)). + + Precondition: + If set at runtime, $(D min) and/or $(D max) must be initialized + appropriately. + + Postcondition: + $(D result >= bytes) + */ + size_t goodAllocSize(size_t n) + { + if (fl.freeListEligible(n)) return fl.max; + return parent.goodAllocSize(n); + } + + /** + Allocate $(D n) bytes of memory. If $(D n) is eligible for freelist and the + freelist is not empty, pops the memory off the free list. In all other + cases, uses the parent allocator. + */ + void[] allocate(size_t n) + { + auto result = fl.allocate(n); + if (result) + { + // Only case we care about: eligible sizes allocated from us + ++allocated; + return result; + } + // All others, allocate from parent + return parent.allocate(n); + } + + /** + Defined if `ParentAllocator` defines it. Checks whether the block + belongs to this allocator. + */ + static if (hasMember!(SParent, "owns") || unchecked) + Ternary owns(void[] b) + { + if (support.ptr <= b.ptr && b.ptr < support.ptr + support.length) + return Ternary.yes; + static if (unchecked) + return Ternary.no; + else + return parent.owns(b); + } + + /** + Deallocates $(D b). If it's of eligible size, it's put on the free list. + Otherwise, it's returned to $(D parent). + + Precondition: $(D b) has been allocated with this allocator, or is $(D + null). + */ + bool deallocate(void[] b) + { + if (support.ptr <= b.ptr && b.ptr < support.ptr + support.length) + { + // we own this guy + import std.conv : text; + assert(fl.freeListEligible(b.length), text(b.length)); + assert(allocated); + --allocated; + // Put manually in the freelist + auto t = fl.root; + fl.root = cast(Node*) b.ptr; + fl.root.next = t; + return true; + } + return parent.deallocate(b); + } + + /** + Deallocates everything from the parent. + */ + static if (hasMember!(ParentAllocator, "deallocateAll") + && stateSize!ParentAllocator) + bool deallocateAll() + { + bool result = fl.deallocateAll && parent.deallocateAll; + allocated = 0; + return result; + } + + /** + Returns `Ternary.yes` if no memory is currently allocated with this + allocator, `Ternary.no` otherwise. This method never returns + `Ternary.unknown`. + */ + Ternary empty() + { + return Ternary(allocated == 0 && parent.bytesUsed == 0); + } +} + +/// +@safe unittest +{ + import std.experimental.allocator.building_blocks.allocator_list + : AllocatorList; + import std.experimental.allocator.gc_allocator : GCAllocator; + + import std.experimental.allocator.common : unbounded; + + alias ScalableFreeList = AllocatorList!((n) => + ContiguousFreeList!(GCAllocator, 0, unbounded)(4096) + ); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.null_allocator + : NullAllocator; + import std.typecons : Ternary; + alias A = ContiguousFreeList!(NullAllocator, 0, 64); + auto a = A(new ubyte[1024]); + + assert(a.empty == Ternary.yes); + + assert(a.goodAllocSize(15) == 64); + assert(a.goodAllocSize(65) == NullAllocator.instance.goodAllocSize(65)); + + auto b = a.allocate(100); + assert(a.empty == Ternary.yes); + assert(b.length == 0); + a.deallocate(b); + b = a.allocate(64); + assert(a.empty == Ternary.no); + assert(b.length == 64); + assert(a.owns(b) == Ternary.yes); + assert(a.owns(null) == Ternary.no); + a.deallocate(b); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.typecons : Ternary; + alias A = ContiguousFreeList!(Region!GCAllocator, 0, 64); + auto a = A(Region!GCAllocator(1024 * 4), 1024); + + assert(a.empty == Ternary.yes); + + assert(a.goodAllocSize(15) == 64); + assert(a.goodAllocSize(65) == a.parent.goodAllocSize(65)); + + auto b = a.allocate(100); + assert(a.empty == Ternary.no); + assert(a.allocated == 0); + assert(b.length == 100); + a.deallocate(b); + assert(a.empty == Ternary.yes); + b = a.allocate(64); + assert(a.empty == Ternary.no); + assert(b.length == 64); + assert(a.owns(b) == Ternary.yes); + assert(a.owns(null) == Ternary.no); + a.deallocate(b); +} + +@system unittest +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + alias A = ContiguousFreeList!(GCAllocator, 64, 64); + auto a = A(1024); + const b = a.allocate(100); + assert(b.length == 100); +} + +/** +FreeList shared across threads. Allocation and deallocation are lock-free. The +parameters have the same semantics as for $(D FreeList). + +$(D expand) is defined to forward to $(D ParentAllocator.expand) +(it must be also $(D shared)). +*/ +struct SharedFreeList(ParentAllocator, + size_t minSize, size_t maxSize = minSize, size_t approxMaxNodes = unbounded) +{ + import std.conv : text; + import std.exception : enforce; + import std.traits : hasMember; + + static assert(approxMaxNodes, "approxMaxNodes must not be null."); + static assert(minSize != unbounded, "Use minSize = 0 for no low bound."); + static assert(maxSize >= (void*).sizeof, + "Maximum size must accommodate a pointer."); + + import core.atomic : atomicOp, cas; + import core.internal.spinlock : SpinLock; + + private enum unchecked = minSize == 0 && maxSize == unbounded; + + static if (minSize != chooseAtRuntime) + { + alias min = minSize; + } + else + { + private shared size_t _min = chooseAtRuntime; + @property size_t min() const shared + { + assert(_min != chooseAtRuntime); + return _min; + } + @property void min(size_t x) shared + { + enforce(x <= max); + enforce(cas(&_min, chooseAtRuntime, x), + "SharedFreeList.min must be initialized exactly once."); + } + static if (maxSize == chooseAtRuntime) + { + // Both bounds can be set, provide one function for setting both in + // one shot. + void setBounds(size_t low, size_t high) shared + { + enforce(low <= high && high >= (void*).sizeof); + enforce(cas(&_min, chooseAtRuntime, low), + "SharedFreeList.min must be initialized exactly once."); + enforce(cas(&_max, chooseAtRuntime, high), + "SharedFreeList.max must be initialized exactly once."); + } + } + } + + private bool tooSmall(size_t n) const shared + { + static if (minSize == 0) return false; + else static if (minSize == chooseAtRuntime) return n < _min; + else return n < minSize; + } + + static if (maxSize != chooseAtRuntime) + { + alias max = maxSize; + } + else + { + private shared size_t _max = chooseAtRuntime; + @property size_t max() const shared { return _max; } + @property void max(size_t x) shared + { + enforce(x >= min && x >= (void*).sizeof); + enforce(cas(&_max, chooseAtRuntime, x), + "SharedFreeList.max must be initialized exactly once."); + } + } + + private bool tooLarge(size_t n) const shared + { + static if (maxSize == unbounded) return false; + else static if (maxSize == chooseAtRuntime) return n > _max; + else return n > maxSize; + } + + private bool freeListEligible(size_t n) const shared + { + static if (minSize == maxSize && minSize != chooseAtRuntime) + return n == maxSize; + else return !tooSmall(n) && !tooLarge(n); + } + + static if (approxMaxNodes != chooseAtRuntime) + { + alias approxMaxLength = approxMaxNodes; + } + else + { + private shared size_t _approxMaxLength = chooseAtRuntime; + @property size_t approxMaxLength() const shared { return _approxMaxLength; } + @property void approxMaxLength(size_t x) shared { _approxMaxLength = enforce(x); } + } + + static if (approxMaxNodes != unbounded) + { + private shared size_t nodes; + private void incNodes() shared + { + atomicOp!("+=")(nodes, 1); + } + private void decNodes() shared + { + assert(nodes); + atomicOp!("-=")(nodes, 1); + } + private void resetNodes() shared + { + nodes = 0; + } + private bool nodesFull() shared + { + return nodes >= approxMaxLength; + } + } + else + { + private static void incNodes() { } + private static void decNodes() { } + private static void resetNodes() { } + private enum bool nodesFull = false; + } + + version (StdDdoc) + { + /** + Properties for getting (and possibly setting) the bounds. Setting bounds + is allowed only once , and before any allocation takes place. Otherwise, + the primitives have the same semantics as those of $(D FreeList). + */ + @property size_t min(); + /// Ditto + @property void min(size_t newMinSize); + /// Ditto + @property size_t max(); + /// Ditto + @property void max(size_t newMaxSize); + /// Ditto + void setBounds(size_t newMin, size_t newMax); + /// + @safe unittest + { + import std.experimental.allocator.common : chooseAtRuntime; + import std.experimental.allocator.mallocator : Mallocator; + + shared SharedFreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime) a; + // Set the maxSize first so setting the minSize doesn't throw + a.max = 128; + a.min = 64; + a.setBounds(64, 128); // equivalent + assert(a.max == 128); + assert(a.min == 64); + } + + /** + Properties for getting (and possibly setting) the approximate maximum length of a shared freelist. + */ + @property size_t approxMaxLength() const shared; + /// ditto + @property void approxMaxLength(size_t x) shared; + /// + @safe unittest + { + import std.experimental.allocator.common : chooseAtRuntime; + import std.experimental.allocator.mallocator : Mallocator; + + shared SharedFreeList!(Mallocator, 50, 50, chooseAtRuntime) a; + // Set the maxSize first so setting the minSize doesn't throw + a.approxMaxLength = 128; + assert(a.approxMaxLength == 128); + a.approxMaxLength = 1024; + assert(a.approxMaxLength == 1024); + a.approxMaxLength = 1; + assert(a.approxMaxLength == 1); + } + } + + /** + The parent allocator. Depending on whether $(D ParentAllocator) holds state + or not, this is a member variable or an alias for + `ParentAllocator.instance`. + */ + static if (stateSize!ParentAllocator) shared ParentAllocator parent; + else alias parent = ParentAllocator.instance; + + mixin(forwardToMember("parent", "expand")); + + private SpinLock lock; + + private struct Node { Node* next; } + static assert(ParentAllocator.alignment >= Node.alignof); + private Node* _root; + + /// Standard primitives. + enum uint alignment = ParentAllocator.alignment; + + /// Ditto + size_t goodAllocSize(size_t bytes) shared + { + if (freeListEligible(bytes)) return maxSize == unbounded ? bytes : max; + return parent.goodAllocSize(bytes); + } + + /// Ditto + static if (hasMember!(ParentAllocator, "owns")) + Ternary owns(void[] b) shared const + { + return parent.owns(b); + } + + /// Ditto + static if (hasMember!(ParentAllocator, "reallocate")) + bool reallocate(ref void[] b, size_t s) shared + { + return parent.reallocate(b, s); + } + + /// Ditto + void[] allocate(size_t bytes) shared + { + assert(bytes < size_t.max / 2); + if (!freeListEligible(bytes)) return parent.allocate(bytes); + if (maxSize != unbounded) bytes = max; + + // Try to pop off the freelist + lock.lock(); + if (!_root) + { + lock.unlock(); + return allocateFresh(bytes); + } + else + { + auto oldRoot = _root; + _root = _root.next; + decNodes(); + lock.unlock(); + return (cast(ubyte*) oldRoot)[0 .. bytes]; + } + } + + private void[] allocateFresh(const size_t bytes) shared + { + assert(bytes == max || max == unbounded); + return parent.allocate(bytes); + } + + /// Ditto + bool deallocate(void[] b) shared + { + if (!nodesFull && freeListEligible(b.length)) + { + auto newRoot = cast(shared Node*) b.ptr; + lock.lock(); + newRoot.next = _root; + _root = newRoot; + incNodes(); + lock.unlock(); + return true; + } + static if (hasMember!(ParentAllocator, "deallocate")) + return parent.deallocate(b); + else + return false; + } + + /// Ditto + bool deallocateAll() shared + { + bool result = false; + lock.lock(); + scope(exit) lock.unlock(); + static if (hasMember!(ParentAllocator, "deallocateAll")) + { + result = parent.deallocateAll(); + } + else static if (hasMember!(ParentAllocator, "deallocate")) + { + result = true; + for (auto n = _root; n;) + { + auto tmp = n.next; + if (!parent.deallocate((cast(ubyte*) n)[0 .. max])) + result = false; + n = tmp; + } + } + _root = null; + resetNodes(); + return result; + } + + /** + Nonstandard function that minimizes the memory usage of the freelist by + freeing each element in turn. Defined only if $(D ParentAllocator) defines + $(D deallocate). + */ + static if (hasMember!(ParentAllocator, "deallocate") && !unchecked) + void minimize() shared + { + lock.lock(); + scope(exit) lock.unlock(); + + for (auto n = _root; n;) + { + auto tmp = n.next; + parent.deallocate((cast(ubyte*) n)[0 .. max]); + n = tmp; + } + + _root = null; + resetNodes(); + } +} + +@system unittest +{ + import core.thread : ThreadGroup; + import std.algorithm.comparison : equal; + import std.experimental.allocator.mallocator : Mallocator; + import std.range : repeat; + + static shared SharedFreeList!(Mallocator, 64, 128, 10) a; + + assert(a.goodAllocSize(1) == platformAlignment); + + auto b = a.allocate(96); + a.deallocate(b); + + void fun() + { + auto b = cast(size_t[]) a.allocate(96); + b[] = cast(size_t) &b; + + assert(b.equal(repeat(cast(size_t) &b, b.length))); + a.deallocate(b); + } + + auto tg = new ThreadGroup; + foreach (i; 0 .. 20) + { + tg.create(&fun); + } + + tg.joinAll(); +} + +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + static shared SharedFreeList!(Mallocator, 64, 128, 10) a; + auto b = a.allocate(100); + a.deallocate(b); + assert(a.nodes == 1); + b = []; + a.deallocateAll(); + assert(a.nodes == 0); +} + +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + static shared SharedFreeList!(Mallocator, 64, 128, 10) a; + auto b = a.allocate(100); + auto c = a.allocate(100); + a.deallocate(c); + assert(a.nodes == 1); + c = []; + a.minimize(); + assert(a.nodes == 0); + a.deallocate(b); + assert(a.nodes == 1); + b = []; + a.minimize(); + assert(a.nodes == 0); +} + +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + static shared SharedFreeList!(Mallocator, 64, 128, 10) a; + auto b = a.allocate(100); + auto c = a.allocate(100); + assert(a.nodes == 0); + a.deallocate(b); + a.deallocate(c); + assert(a.nodes == 2); + b = []; + c = []; + a.minimize(); + assert(a.nodes == 0); +} + +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + shared SharedFreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime) a; + scope(exit) a.deallocateAll(); + auto c = a.allocate(64); + assert(a.reallocate(c, 96)); + assert(c.length == 96); + a.deallocate(c); +} + +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + shared SharedFreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime, chooseAtRuntime) a; + scope(exit) a.deallocateAll; + a.allocate(64); +} + +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + shared SharedFreeList!(Mallocator, 30, 40) a; + scope(exit) a.deallocateAll; + a.allocate(64); +} + +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + shared SharedFreeList!(Mallocator, 30, 40, chooseAtRuntime) a; + scope(exit) a.deallocateAll; + a.allocate(64); +} + +@system unittest +{ + // Pull request #5556 + import std.experimental.allocator.mallocator : Mallocator; + shared SharedFreeList!(Mallocator, 0, chooseAtRuntime) a; + scope(exit) a.deallocateAll; + a.max = 64; + a.allocate(64); +} + +@system unittest +{ + // Pull request #5556 + import std.experimental.allocator.mallocator : Mallocator; + shared SharedFreeList!(Mallocator, chooseAtRuntime, 64) a; + scope(exit) a.deallocateAll; + a.min = 32; + a.allocate(64); +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/free_tree.d b/libphobos/src/std/experimental/allocator/building_blocks/free_tree.d new file mode 100644 index 0000000..6b64659 --- /dev/null +++ b/libphobos/src/std/experimental/allocator/building_blocks/free_tree.d @@ -0,0 +1,487 @@ +/// +module std.experimental.allocator.building_blocks.free_tree; + +import std.experimental.allocator.common; + +//debug = std_experimental_allocator_free_tree; + +/** + +The Free Tree allocator, stackable on top of any other allocator, bears +similarity with the free list allocator. Instead of a singly-linked list of +previously freed blocks, it maintains a binary search tree. This allows the +Free Tree allocator to manage blocks of arbitrary lengths and search them +efficiently. + +Common uses of $(D FreeTree) include: + +$(UL +$(LI Adding $(D deallocate) capability to an allocator that lacks it (such as simple regions).) +$(LI Getting the benefits of multiple adaptable freelists that do not need to +be tuned for one specific size but insted automatically adapts itself to +frequently used sizes.) +) + +The free tree has special handling of duplicates (a singly-linked list per +node) in anticipation of large number of duplicates. Allocation time from the +free tree is expected to be $(BIGOH log n) where $(D n) is the number of +distinct sizes (not total nodes) kept in the free tree. + +Allocation requests first search the tree for a buffer of suitable size +deallocated in the past. If a match is found, the node is removed from the tree +and the memory is returned. Otherwise, the allocation is directed to $(D +ParentAllocator). If at this point $(D ParentAllocator) also fails to allocate, +$(D FreeTree) frees everything and then tries the parent allocator again. + +Upon deallocation, the deallocated block is inserted in the internally +maintained free tree (not returned to the parent). The free tree is not kept +balanced. Instead, it has a last-in-first-out flavor because newly inserted +blocks are rotated to the root of the tree. That way allocations are cache +friendly and also frequently used sizes are more likely to be found quickly, +whereas seldom used sizes migrate to the leaves of the tree. + +$(D FreeTree) rounds up small allocations to at least $(D 4 * size_t.sizeof), +which on 64-bit system is one cache line size. If very small objects need to +be efficiently allocated, the $(D FreeTree) should be fronted with an +appropriate small object allocator. + +The following methods are defined if $(D ParentAllocator) defines them, and forward to it: $(D allocateAll), $(D expand), $(D owns), $(D reallocate). +*/ +struct FreeTree(ParentAllocator) +{ + static assert(ParentAllocator.alignment % size_t.alignof == 0, + "FreeTree must be on top of a word-aligned allocator"); + + import std.algorithm.comparison : min, max; + import std.algorithm.mutation : swap; + import std.traits : hasMember; + + // State + static if (stateSize!ParentAllocator) private ParentAllocator parent; + else private alias parent = ParentAllocator.instance; + private Node* root; // that's the entire added state + + private struct Node + { + Node*[2] kid; + Node* sibling; + size_t size; + ref Node* left() { return kid[0]; } + ref Node* right() { return kid[1]; } + } + + // Removes "which" from the tree, returns the memory it occupied + private void[] remove(ref Node* which) + { + assert(which); + assert(!which.sibling); + auto result = (cast(ubyte*) which)[0 .. which.size]; + if (!which.right) which = which.left; + else if (!which.left) which = which.right; + else + { + // result has two kids + static bool toggler; + // Crude randomization: alternate left/right choices + toggler = !toggler; + auto newRoot = which.kid[toggler], orphan = which.kid[!toggler]; + which = newRoot; + for (Node* n = void; (n = newRoot.kid[!toggler]) !is null; ) + { + newRoot = n; + } + newRoot.kid[!toggler] = orphan; + } + return result; + } + + private void[] findAndRemove(ref Node* n, size_t s) + { + if (!n) return null; + if (s == n.size) + { + if (auto sis = n.sibling) + { + // Nice, give away one from the freelist + auto result = (cast(ubyte*) sis)[0 .. sis.size]; + n.sibling = sis.sibling; + return result; + } + return remove(n); + } + return findAndRemove(n.kid[s > n.size], s); + } + + debug(std_experimental_allocator_free_tree) + private void dump() + { + import std.stdio : writef, writefln, writeln; + writeln(typeof(this).stringof, "@", &this, " {"); + scope(exit) writeln("}"); + + if (!root) return; + + static void recurse(Node* n, uint indent = 4) + { + if (!n) + { + writefln("%*s(null)", indent, ""); + return; + } + for (auto sis = n; sis; sis = sis.sibling) + { + writef("%*s%x (%s bytes) ", indent, "", + cast(void*) n, n.size); + } + writeln; + if (!n.left && !n.right) return; + recurse(n.left, indent + 4); + recurse(n.right, indent + 4); + } + recurse(root); + } + + private string formatSizes() + { + string result = "("; + void recurse(Node* n) + { + if (!n) + { + result ~= "_"; + return; + } + import std.conv : to; + result ~= to!string(n.size); + for (auto sis = n.sibling; sis; sis = sis.sibling) + { + result ~= "+moar"; + } + if (n.left || n.right) + { + result ~= " ("; + recurse(n.left); + result ~= ' '; + recurse(n.right); + result ~= ")"; + } + } + recurse(root); + return result ~= ")"; + } + + private static void rotate(ref Node* parent, bool toRight) + { + assert(parent); + auto opposing = parent.kid[!toRight]; + if (!opposing) return; + parent.kid[!toRight] = opposing.kid[toRight]; + opposing.kid[toRight] = parent; + parent = opposing; + } + + // Inserts which into the tree, making it the new root + private void insertAsRoot(Node* which) + { + assert(which); + debug(std_experimental_allocator_free_tree) + { + assertValid; + scope(exit) assertValid; + } + + static void recurse(ref Node* where, Node* which) + { + if (!where) + { + where = which; + which.left = null; + which.right = null; + which.sibling = null; + return; + } + if (which.size == where.size) + { + // Special handling of duplicates + which.sibling = where.sibling; + where.sibling = which; + which.left = null; + which.right = null; + return; + } + bool goRight = which.size > where.size; + recurse(where.kid[goRight], which); + rotate(where, !goRight); + } + recurse(root, which); + } + + private void assertValid() + { + debug(std_experimental_allocator_free_tree) + { + static bool isBST(Node* n, size_t lb = 0, size_t ub = size_t.max) + { + if (!n) return true; + for (auto sis = n.sibling; sis; sis = sis.sibling) + { + assert(n.size == sis.size); + assert(sis.left is null); + assert(sis.right is null); + } + return lb < n.size && n.size <= ub + && isBST(n.left, lb, min(ub, n.size)) + && isBST(n.right, max(lb, n.size), ub); + } + if (isBST(root)) return; + dump; + assert(0); + } + } + + /** + The $(D FreeTree) is word aligned. + */ + enum uint alignment = size_t.alignof; + + /** + The $(D FreeTree) allocator is noncopyable. + */ + this(this) @disable; + + /** + The destructor of $(D FreeTree) releases all memory back to the parent + allocator. + */ + static if (hasMember!(ParentAllocator, "deallocate")) + ~this() + { + clear; + } + + /** + Returns $(D parent.goodAllocSize(max(Node.sizeof, s))). + */ + static if (stateSize!ParentAllocator) + size_t goodAllocSize(size_t s) + { + return parent.goodAllocSize(max(Node.sizeof, s)); + } + else + static size_t goodAllocSize(size_t s) + { + return parent.goodAllocSize(max(Node.sizeof, s)); + } + + /** + + Allocates $(D n) bytes of memory. First consults the free tree, and returns + from it if a suitably sized block is found. Otherwise, the parent allocator + is tried. If allocation from the parent succeeds, the allocated block is + returned. Otherwise, the free tree tries an alternate strategy: If $(D + ParentAllocator) defines $(D deallocate), $(D FreeTree) releases all of its + contents and tries again. + + TODO: Splitting and coalescing should be implemented if $(D ParentAllocator) does not defined $(D deallocate). + + */ + void[] allocate(size_t n) + { + assertValid; + if (n == 0) return null; + + immutable s = goodAllocSize(n); + + // Consult the free tree. + auto result = findAndRemove(root, s); + if (result.ptr) return result.ptr[0 .. n]; + + // No block found, try the parent allocator. + result = parent.allocate(s); + if (result.ptr) return result.ptr[0 .. n]; + + // Parent ran out of juice, desperation mode on + static if (hasMember!(ParentAllocator, "deallocate")) + { + clear; + // Try parent allocator again. + result = parent.allocate(s); + if (result.ptr) return result.ptr[0 .. n]; + return null; + } + else + { + // TODO: get smart here + return null; + } + } + + // Forwarding methods + mixin(forwardToMember("parent", + "allocateAll", "expand", "owns", "reallocate")); + + /** Places $(D b) into the free tree. */ + bool deallocate(void[] b) + { + if (!b.ptr) return true; + auto which = cast(Node*) b.ptr; + which.size = goodAllocSize(b.length); + // deliberately don't initialize which.left and which.right + assert(which.size >= Node.sizeof); + insertAsRoot(which); + return true; + } + + @system unittest // test a few simple configurations + { + import std.experimental.allocator.gc_allocator; + FreeTree!GCAllocator a; + auto b1 = a.allocate(10000); + auto b2 = a.allocate(20000); + auto b3 = a.allocate(30000); + assert(b1.ptr && b2.ptr && b3.ptr); + a.deallocate(b1); + a.deallocate(b3); + a.deallocate(b2); + assert(a.formatSizes == "(20480 (12288 32768))", a.formatSizes); + + b1 = a.allocate(10000); + assert(a.formatSizes == "(20480 (_ 32768))", a.formatSizes); + b1 = a.allocate(30000); + assert(a.formatSizes == "(20480)", a.formatSizes); + b1 = a.allocate(20000); + assert(a.formatSizes == "(_)", a.formatSizes); + } + + @system unittest // build a complex free tree + { + import std.experimental.allocator.gc_allocator, std.range; + FreeTree!GCAllocator a; + uint[] sizes = [3008,704,1856,576,1632,672,832,1856,1120,2656,1216,672, + 448,992,2400,1376,2688,2656,736,1440]; + void[][] allocs; + foreach (s; sizes) + allocs ~= a.allocate(s); + foreach_reverse (b; allocs) + { + assert(b.ptr); + a.deallocate(b); + } + a.assertValid; + allocs = null; + foreach (s; sizes) + allocs ~= a.allocate(s); + assert(a.root is null); + a.assertValid; + } + + /** Defined if $(D ParentAllocator.deallocate) exists, and returns to it + all memory held in the free tree. */ + static if (hasMember!(ParentAllocator, "deallocate")) + void clear() + { + void recurse(Node* n) + { + if (!n) return; + recurse(n.left); + recurse(n.right); + parent.deallocate((cast(ubyte*) n)[0 .. n.size]); + } + recurse(root); + root = null; + } + + /** + + Defined if $(D ParentAllocator.deallocateAll) exists, and forwards to it. + Also nullifies the free tree (it's assumed the parent frees all memory + stil managed by the free tree). + + */ + static if (hasMember!(ParentAllocator, "deallocateAll")) + bool deallocateAll() + { + // This is easy, just nuke the root and deallocate all from the + // parent + root = null; + return parent.deallocateAll; + } +} + +@system unittest +{ + import std.experimental.allocator.gc_allocator; + testAllocator!(() => FreeTree!GCAllocator()); +} + +@system unittest // issue 16506 +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.experimental.allocator.mallocator : Mallocator; + + static void f(ParentAllocator)(size_t sz) + { + static FreeTree!ParentAllocator myAlloc; + byte[] _payload = cast(byte[]) myAlloc.allocate(sz); + assert(_payload, "_payload is null"); + _payload[] = 0; + myAlloc.deallocate(_payload); + } + + f!Mallocator(33); + f!Mallocator(43); + f!GCAllocator(1); +} + +@system unittest // issue 16507 +{ + static struct MyAllocator + { + byte dummy; + static bool alive = true; + void[] allocate(size_t s) { return new byte[](s); } + bool deallocate(void[] ) { if (alive) assert(false); return true; } + enum alignment = size_t.sizeof; + } + + FreeTree!MyAllocator ft; + void[] x = ft.allocate(1); + ft.deallocate(x); + ft.allocate(1000); + MyAllocator.alive = false; +} + +@system unittest // "desperation mode" +{ + uint myDeallocCounter = 0; + + struct MyAllocator + { + byte[] allocation; + void[] allocate(size_t s) + { + if (allocation.ptr) return null; + allocation = new byte[](s); + return allocation; + } + bool deallocate(void[] ) + { + ++myDeallocCounter; + allocation = null; + return true; + } + enum alignment = size_t.sizeof; + } + + FreeTree!MyAllocator ft; + void[] x = ft.allocate(1); + ft.deallocate(x); + assert(myDeallocCounter == 0); + x = ft.allocate(1000); // Triggers "desperation mode". + assert(myDeallocCounter == 1); + assert(x.ptr); + void[] y = ft.allocate(1000); /* Triggers "desperation mode" but there's + nothing to deallocate so MyAllocator can't deliver. */ + assert(myDeallocCounter == 1); + assert(y.ptr is null); +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/kernighan_ritchie.d b/libphobos/src/std/experimental/allocator/building_blocks/kernighan_ritchie.d new file mode 100644 index 0000000..555daba --- /dev/null +++ b/libphobos/src/std/experimental/allocator/building_blocks/kernighan_ritchie.d @@ -0,0 +1,882 @@ +/// +module std.experimental.allocator.building_blocks.kernighan_ritchie; +import std.experimental.allocator.building_blocks.null_allocator; + +//debug = KRRegion; +version (unittest) import std.conv : text; +debug(KRRegion) import std.stdio; + +// KRRegion +/** +$(D KRRegion) draws inspiration from the $(MREF_ALTTEXT region allocation +strategy, std,experimental,allocator,building_blocks,region) and also the +$(HTTP stackoverflow.com/questions/13159564/explain-this-implementation-of-malloc-from-the-kr-book, +famed allocator) described by Brian Kernighan and Dennis Ritchie in section 8.7 +of the book $(HTTP amazon.com/exec/obidos/ASIN/0131103628/classicempire, "The C +Programming Language"), Second Edition, Prentice Hall, 1988. + +$(H4 `KRRegion` = `Region` + Kernighan-Ritchie Allocator) + +Initially, `KRRegion` starts in "region" mode: allocations are served from +the memory chunk in a region fashion. Thus, as long as there is enough memory +left, $(D KRRegion.allocate) has the performance profile of a region allocator. +Deallocation inserts (in $(BIGOH 1) time) the deallocated blocks in an +unstructured freelist, which is not read in region mode. + +Once the region cannot serve an $(D allocate) request, $(D KRRegion) switches +to "free list" mode. It sorts the list of previously deallocated blocks by +address and serves allocation requests off that free list. The allocation and +deallocation follow the pattern described by Kernighan and Ritchie. + +The recommended use of `KRRegion` is as a $(I region with deallocation). If the +`KRRegion` is dimensioned appropriately, it could often not enter free list +mode during its lifetime. Thus it is as fast as a simple region, whilst +offering deallocation at a small cost. When the region memory is exhausted, +the previously deallocated memory is still usable, at a performance cost. If +the region is not excessively large and fragmented, the linear allocation and +deallocation cost may still be compensated for by the good locality +characteristics. + +If the chunk of memory managed is large, it may be desirable to switch +management to free list from the beginning. That way, memory may be used in a +more compact manner than region mode. To force free list mode, call $(D +switchToFreeList) shortly after construction or when deemed appropriate. + +The smallest size that can be allocated is two words (16 bytes on 64-bit +systems, 8 bytes on 32-bit systems). This is because the free list management +needs two words (one for the length, the other for the next pointer in the +singly-linked list). + +The $(D ParentAllocator) type parameter is the type of the allocator used to +allocate the memory chunk underlying the $(D KRRegion) object. Choosing the +default ($(D NullAllocator)) means the user is responsible for passing a buffer +at construction (and for deallocating it if necessary). Otherwise, $(D KRRegion) +automatically deallocates the buffer during destruction. For that reason, if +$(D ParentAllocator) is not $(D NullAllocator), then $(D KRRegion) is not +copyable. + +$(H4 Implementation Details) + +In free list mode, $(D KRRegion) embeds a free blocks list onto the chunk of +memory. The free list is circular, coalesced, and sorted by address at all +times. Allocations and deallocations take time proportional to the number of +previously deallocated blocks. (In practice the cost may be lower, e.g. if +memory is deallocated in reverse order of allocation, all operations take +constant time.) Memory utilization is good (small control structure and no +per-allocation overhead). The disadvantages of freelist mode include proneness +to fragmentation, a minimum allocation size of two words, and linear worst-case +allocation and deallocation times. + +Similarities of `KRRegion` (in free list mode) with the +Kernighan-Ritchie allocator: + +$(UL +$(LI Free blocks have variable size and are linked in a singly-linked list.) +$(LI The freelist is maintained in increasing address order, which makes +coalescing easy.) +$(LI The strategy for finding the next available block is first fit.) +$(LI The free list is circular, with the last node pointing back to the first.) +$(LI Coalescing is carried during deallocation.) +) + +Differences from the Kernighan-Ritchie allocator: + +$(UL +$(LI Once the chunk is exhausted, the Kernighan-Ritchie allocator allocates +another chunk using operating system primitives. For better composability, $(D +KRRegion) just gets full (returns $(D null) on new allocation requests). The +decision to allocate more blocks is deferred to a higher-level entity. For an +example, see the example below using $(D AllocatorList) in conjunction with $(D +KRRegion).) +$(LI Allocated blocks do not hold a size prefix. This is because in D the size +information is available in client code at deallocation time.) +) + +*/ +struct KRRegion(ParentAllocator = NullAllocator) +{ + import std.experimental.allocator.common : stateSize, alignedAt; + import std.traits : hasMember; + import std.typecons : Ternary; + + private static struct Node + { + import std.typecons : tuple, Tuple; + + Node* next; + size_t size; + + this(this) @disable; + + void[] payload() inout + { + return (cast(ubyte*) &this)[0 .. size]; + } + + bool adjacent(in Node* right) const + { + assert(right); + auto p = payload; + return p.ptr < right && right < p.ptr + p.length + Node.sizeof; + } + + bool coalesce(void* memoryEnd = null) + { + // Coalesce the last node before the memory end with any possible gap + if (memoryEnd + && memoryEnd < payload.ptr + payload.length + Node.sizeof) + { + size += memoryEnd - (payload.ptr + payload.length); + return true; + } + + if (!adjacent(next)) return false; + size = (cast(ubyte*) next + next.size) - cast(ubyte*) &this; + next = next.next; + return true; + } + + Tuple!(void[], Node*) allocateHere(size_t bytes) + { + assert(bytes >= Node.sizeof); + assert(bytes % Node.alignof == 0); + assert(next); + assert(!adjacent(next)); + if (size < bytes) return typeof(return)(); + assert(size >= bytes); + immutable leftover = size - bytes; + + if (leftover >= Node.sizeof) + { + // There's room for another node + auto newNode = cast(Node*) ((cast(ubyte*) &this) + bytes); + newNode.size = leftover; + newNode.next = next == &this ? newNode : next; + assert(next); + return tuple(payload, newNode); + } + + // No slack space, just return next node + return tuple(payload, next == &this ? null : next); + } + } + + // state + /** + If $(D ParentAllocator) holds state, $(D parent) is a public member of type + $(D KRRegion). Otherwise, $(D parent) is an $(D alias) for + `ParentAllocator.instance`. + */ + static if (stateSize!ParentAllocator) ParentAllocator parent; + else alias parent = ParentAllocator.instance; + private void[] payload; + private Node* root; + private bool regionMode = true; + + auto byNodePtr() + { + static struct Range + { + Node* start, current; + @property bool empty() { return !current; } + @property Node* front() { return current; } + void popFront() + { + assert(current && current.next); + current = current.next; + if (current == start) current = null; + } + @property Range save() { return this; } + } + import std.range : isForwardRange; + static assert(isForwardRange!Range); + return Range(root, root); + } + + string toString() + { + import std.format : format; + string s = "KRRegion@"; + s ~= format("%s-%s(0x%s[%s] %s", &this, &this + 1, + payload.ptr, payload.length, + regionMode ? "(region)" : "(freelist)"); + + Node* lastNode = null; + if (!regionMode) + { + foreach (node; byNodePtr) + { + s ~= format(", %sfree(0x%s[%s])", + lastNode && lastNode.adjacent(node) ? "+" : "", + cast(void*) node, node.size); + lastNode = node; + } + } + else + { + for (auto node = root; node; node = node.next) + { + s ~= format(", %sfree(0x%s[%s])", + lastNode && lastNode.adjacent(node) ? "+" : "", + cast(void*) node, node.size); + lastNode = node; + } + } + + s ~= ')'; + return s; + } + + private void assertValid(string s) + { + assert(!regionMode); + if (!payload.ptr) + { + assert(!root, s); + return; + } + if (!root) + { + return; + } + assert(root >= payload.ptr, s); + assert(root < payload.ptr + payload.length, s); + + // Check that the list terminates + size_t n; + foreach (node; byNodePtr) + { + assert(node.next); + assert(!node.adjacent(node.next)); + assert(n++ < payload.length / Node.sizeof, s); + } + } + + private Node* sortFreelist(Node* root) + { + // Find a monotonic run + auto last = root; + for (;;) + { + if (!last.next) return root; + if (last > last.next) break; + assert(last < last.next); + last = last.next; + } + auto tail = last.next; + last.next = null; + tail = sortFreelist(tail); + return merge(root, tail); + } + + private Node* merge(Node* left, Node* right) + { + assert(left != right); + if (!left) return right; + if (!right) return left; + if (left < right) + { + auto result = left; + result.next = merge(left.next, right); + return result; + } + auto result = right; + result.next = merge(left, right.next); + return result; + } + + private void coalesceAndMakeCircular() + { + for (auto n = root;;) + { + assert(!n.next || n < n.next); + if (!n.next) + { + // Convert to circular + n.next = root; + break; + } + if (n.coalesce) continue; // possibly another coalesce + n = n.next; + } + } + + /** + Create a $(D KRRegion). If $(D ParentAllocator) is not $(D NullAllocator), + $(D KRRegion)'s destructor will call $(D parent.deallocate). + + Params: + b = Block of memory to serve as support for the allocator. Memory must be + larger than two words and word-aligned. + n = Capacity desired. This constructor is defined only if $(D + ParentAllocator) is not $(D NullAllocator). + */ + this(ubyte[] b) + { + if (b.length < Node.sizeof) + { + // Init as empty + assert(root is null); + assert(payload is null); + return; + } + assert(b.length >= Node.sizeof); + assert(b.ptr.alignedAt(Node.alignof)); + assert(b.length >= 2 * Node.sizeof); + payload = b; + root = cast(Node*) b.ptr; + // Initialize the free list with all list + assert(regionMode); + root.next = null; + root.size = b.length; + debug(KRRegion) writefln("KRRegion@%s: init with %s[%s]", &this, + b.ptr, b.length); + } + + /// Ditto + static if (!is(ParentAllocator == NullAllocator)) + this(size_t n) + { + assert(n > Node.sizeof); + this(cast(ubyte[])(parent.allocate(n))); + } + + /// Ditto + static if (!is(ParentAllocator == NullAllocator) + && hasMember!(ParentAllocator, "deallocate")) + ~this() + { + parent.deallocate(payload); + } + + /** + Forces free list mode. If already in free list mode, does nothing. + Otherwise, sorts the free list accumulated so far and switches strategy for + future allocations to KR style. + */ + void switchToFreeList() + { + if (!regionMode) return; + regionMode = false; + if (!root) return; + root = sortFreelist(root); + coalesceAndMakeCircular; + } + + /* + Noncopyable + */ + @disable this(this); + + /** + Word-level alignment. + */ + enum alignment = Node.alignof; + + /** + Allocates $(D n) bytes. Allocation searches the list of available blocks + until a free block with $(D n) or more bytes is found (first fit strategy). + The block is split (if larger) and returned. + + Params: n = number of bytes to _allocate + + Returns: A word-aligned buffer of $(D n) bytes, or $(D null). + */ + void[] allocate(size_t n) + { + if (!n || !root) return null; + const actualBytes = goodAllocSize(n); + + // Try the region first + if (regionMode) + { + // Only look at the head of the freelist + if (root.size >= actualBytes) + { + // Enough room for allocation + void* result = root; + immutable balance = root.size - actualBytes; + if (balance >= Node.sizeof) + { + auto newRoot = cast(Node*) (result + actualBytes); + newRoot.next = root.next; + newRoot.size = balance; + root = newRoot; + } + else + { + root = null; + switchToFreeList; + } + return result[0 .. n]; + } + + // Not enough memory, switch to freelist mode and fall through + switchToFreeList; + } + + // Try to allocate from next after the iterating node + for (auto pnode = root;;) + { + assert(!pnode.adjacent(pnode.next)); + auto k = pnode.next.allocateHere(actualBytes); + if (k[0] !is null) + { + // awes + assert(k[0].length >= n); + if (root == pnode.next) root = k[1]; + pnode.next = k[1]; + return k[0][0 .. n]; + } + + pnode = pnode.next; + if (pnode == root) break; + } + return null; + } + + /** + Deallocates $(D b), which is assumed to have been previously allocated with + this allocator. Deallocation performs a linear search in the free list to + preserve its sorting order. It follows that blocks with higher addresses in + allocators with many free blocks are slower to deallocate. + + Params: b = block to be deallocated + */ + bool deallocate(void[] b) + { + debug(KRRegion) writefln("KRRegion@%s: deallocate(%s[%s])", &this, + b.ptr, b.length); + if (!b.ptr) return true; + assert(owns(b) == Ternary.yes); + assert(b.ptr.alignedAt(Node.alignof)); + + // Insert back in the freelist, keeping it sorted by address. Do not + // coalesce at this time. Instead, do it lazily during allocation. + auto n = cast(Node*) b.ptr; + n.size = goodAllocSize(b.length); + auto memoryEnd = payload.ptr + payload.length; + + if (regionMode) + { + assert(root); + // Insert right after root + n.next = root.next; + root.next = n; + return true; + } + + if (!root) + { + // What a sight for sore eyes + root = n; + root.next = root; + + // If the first block freed is the last one allocated, + // maybe there's a gap after it. + root.coalesce(memoryEnd); + return true; + } + + version (assert) foreach (test; byNodePtr) + { + assert(test != n); + } + // Linear search + auto pnode = root; + do + { + assert(pnode && pnode.next); + assert(pnode != n); + assert(pnode.next != n); + if (pnode < pnode.next) + { + if (pnode >= n || n >= pnode.next) continue; + // Insert in between pnode and pnode.next + n.next = pnode.next; + pnode.next = n; + n.coalesce; + pnode.coalesce; + root = pnode; + return true; + } + else if (pnode < n) + { + // Insert at the end of the list + // Add any possible gap at the end of n to the length of n + n.next = pnode.next; + pnode.next = n; + n.coalesce(memoryEnd); + pnode.coalesce; + root = pnode; + return true; + } + else if (n < pnode.next) + { + // Insert at the front of the list + n.next = pnode.next; + pnode.next = n; + n.coalesce; + root = n; + return true; + } + } + while ((pnode = pnode.next) != root); + assert(0, "Wrong parameter passed to deallocate"); + } + + /** + Allocates all memory available to this allocator. If the allocator is empty, + returns the entire available block of memory. Otherwise, it still performs + a best-effort allocation: if there is no fragmentation (e.g. $(D allocate) + has been used but not $(D deallocate)), allocates and returns the only + available block of memory. + + The operation takes time proportional to the number of adjacent free blocks + at the front of the free list. These blocks get coalesced, whether + $(D allocateAll) succeeds or fails due to fragmentation. + */ + void[] allocateAll() + { + if (regionMode) switchToFreeList; + if (root && root.next == root) + return allocate(root.size); + return null; + } + + /// + @system unittest + { + import std.experimental.allocator.gc_allocator : GCAllocator; + auto alloc = KRRegion!GCAllocator(1024 * 64); + const b1 = alloc.allocate(2048); + assert(b1.length == 2048); + const b2 = alloc.allocateAll; + assert(b2.length == 1024 * 62); + } + + /** + Deallocates all memory currently allocated, making the allocator ready for + other allocations. This is a $(BIGOH 1) operation. + */ + bool deallocateAll() + { + debug(KRRegion) assertValid("deallocateAll"); + debug(KRRegion) scope(exit) assertValid("deallocateAll"); + root = cast(Node*) payload.ptr; + // Initialize the free list with all list + if (root) + { + root.next = root; + root.size = payload.length; + } + return true; + } + + /** + Checks whether the allocator is responsible for the allocation of $(D b). + It does a simple $(BIGOH 1) range check. $(D b) should be a buffer either + allocated with $(D this) or obtained through other means. + */ + Ternary owns(void[] b) + { + debug(KRRegion) assertValid("owns"); + debug(KRRegion) scope(exit) assertValid("owns"); + return Ternary(b.ptr >= payload.ptr + && b.ptr < payload.ptr + payload.length); + } + + /** + Adjusts $(D n) to a size suitable for allocation (two words or larger, + word-aligned). + */ + static size_t goodAllocSize(size_t n) + { + import std.experimental.allocator.common : roundUpToMultipleOf; + return n <= Node.sizeof + ? Node.sizeof : n.roundUpToMultipleOf(alignment); + } + + /** + Returns: `Ternary.yes` if the allocator is empty, `Ternary.no` otherwise. + Never returns `Ternary.unknown`. + */ + Ternary empty() + { + return Ternary(root && root.size == payload.length); + } +} + +/** +$(D KRRegion) is preferable to $(D Region) as a front for a general-purpose +allocator if $(D deallocate) is needed, yet the actual deallocation traffic is +relatively low. The example below shows a $(D KRRegion) using stack storage +fronting the GC allocator. +*/ +@system unittest +{ + import std.experimental.allocator.building_blocks.fallback_allocator + : fallbackAllocator; + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.typecons : Ternary; + // KRRegion fronting a general-purpose allocator + ubyte[1024 * 128] buf; + auto alloc = fallbackAllocator(KRRegion!()(buf), GCAllocator.instance); + auto b = alloc.allocate(100); + assert(b.length == 100); + assert(alloc.primary.owns(b) == Ternary.yes); +} + +/** +The code below defines a scalable allocator consisting of 1 MB (or larger) +blocks fetched from the garbage-collected heap. Each block is organized as a +KR-style heap. More blocks are allocated and freed on a need basis. + +This is the closest example to the allocator introduced in the K$(AMP)R book. +It should perform slightly better because instead of searching through one +large free list, it searches through several shorter lists in LRU order. Also, +it actually returns memory to the operating system when possible. +*/ +@system unittest +{ + import std.algorithm.comparison : max; + import std.experimental.allocator.building_blocks.allocator_list + : AllocatorList; + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.experimental.allocator.mmap_allocator : MmapAllocator; + AllocatorList!(n => KRRegion!MmapAllocator(max(n * 16, 1024 * 1024))) alloc; +} + +@system unittest +{ + import std.algorithm.comparison : max; + import std.experimental.allocator.building_blocks.allocator_list + : AllocatorList; + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.experimental.allocator.mallocator : Mallocator; + import std.typecons : Ternary; + /* + Create a scalable allocator consisting of 1 MB (or larger) blocks fetched + from the garbage-collected heap. Each block is organized as a KR-style + heap. More blocks are allocated and freed on a need basis. + */ + AllocatorList!(n => KRRegion!Mallocator(max(n * 16, 1024 * 1024)), + NullAllocator) alloc; + void[][50] array; + foreach (i; 0 .. array.length) + { + auto length = i * 10_000 + 1; + array[i] = alloc.allocate(length); + assert(array[i].ptr); + assert(array[i].length == length); + } + import std.random : randomShuffle; + randomShuffle(array[]); + foreach (i; 0 .. array.length) + { + assert(array[i].ptr); + assert(alloc.owns(array[i]) == Ternary.yes); + alloc.deallocate(array[i]); + } +} + +@system unittest +{ + import std.algorithm.comparison : max; + import std.experimental.allocator.building_blocks.allocator_list + : AllocatorList; + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.experimental.allocator.mmap_allocator : MmapAllocator; + import std.typecons : Ternary; + /* + Create a scalable allocator consisting of 1 MB (or larger) blocks fetched + from the garbage-collected heap. Each block is organized as a KR-style + heap. More blocks are allocated and freed on a need basis. + */ + AllocatorList!((n) { + auto result = KRRegion!MmapAllocator(max(n * 2, 1024 * 1024)); + return result; + }) alloc; + void[][99] array; + foreach (i; 0 .. array.length) + { + auto length = i * 10_000 + 1; + array[i] = alloc.allocate(length); + assert(array[i].ptr); + foreach (j; 0 .. i) + { + assert(array[i].ptr != array[j].ptr); + } + assert(array[i].length == length); + } + import std.random : randomShuffle; + randomShuffle(array[]); + foreach (i; 0 .. array.length) + { + assert(alloc.owns(array[i]) == Ternary.yes); + alloc.deallocate(array[i]); + } +} + +@system unittest +{ + import std.algorithm.comparison : max; + import std.experimental.allocator.building_blocks.allocator_list + : AllocatorList; + import std.experimental.allocator.common : testAllocator; + import std.experimental.allocator.gc_allocator : GCAllocator; + testAllocator!(() => AllocatorList!( + n => KRRegion!GCAllocator(max(n * 16, 1024 * 1024)))()); +} + +@system unittest +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + + auto alloc = KRRegion!GCAllocator(1024 * 1024); + + void[][] array; + foreach (i; 1 .. 4) + { + array ~= alloc.allocate(i); + assert(array[$ - 1].length == i); + } + alloc.deallocate(array[1]); + alloc.deallocate(array[0]); + alloc.deallocate(array[2]); + assert(alloc.allocateAll().length == 1024 * 1024); +} + +@system unittest +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.typecons : Ternary; + auto alloc = KRRegion!()( + cast(ubyte[])(GCAllocator.instance.allocate(1024 * 1024))); + const store = alloc.allocate(KRRegion!().sizeof); + auto p = cast(KRRegion!()* ) store.ptr; + import core.stdc.string : memcpy; + import std.algorithm.mutation : move; + import std.conv : emplace; + + memcpy(p, &alloc, alloc.sizeof); + emplace(&alloc); + + void[][100] array; + foreach (i; 0 .. array.length) + { + auto length = 100 * i + 1; + array[i] = p.allocate(length); + assert(array[i].length == length, text(array[i].length)); + assert(p.owns(array[i]) == Ternary.yes); + } + import std.random : randomShuffle; + randomShuffle(array[]); + foreach (i; 0 .. array.length) + { + assert(p.owns(array[i]) == Ternary.yes); + p.deallocate(array[i]); + } + auto b = p.allocateAll(); + assert(b.length == 1024 * 1024 - KRRegion!().sizeof, text(b.length)); +} + +@system unittest +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + auto alloc = KRRegion!()( + cast(ubyte[])(GCAllocator.instance.allocate(1024 * 1024))); + auto p = alloc.allocateAll(); + assert(p.length == 1024 * 1024); + alloc.deallocateAll(); + p = alloc.allocateAll(); + assert(p.length == 1024 * 1024); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks; + import std.random; + import std.typecons : Ternary; + + // Both sequences must work on either system + + // A sequence of allocs which generates the error described in issue 16564 + // that is a gap at the end of buf from the perspective of the allocator + + // for 64 bit systems (leftover balance = 8 bytes < 16) + int[] sizes64 = [18904, 2008, 74904, 224, 111904, 1904, 52288, 8]; + + // for 32 bit systems (leftover balance < 8) + int[] sizes32 = [81412, 107068, 49892, 23768]; + + + void test(int[] sizes) + { + align(size_t.sizeof) ubyte[256 * 1024] buf; + auto a = KRRegion!()(buf); + + void[][] bufs; + + foreach (size; sizes) + { + bufs ~= a.allocate(size); + } + + foreach (b; bufs.randomCover) + { + a.deallocate(b); + } + + assert(a.empty == Ternary.yes); + } + + test(sizes64); + test(sizes32); +} + +@system unittest +{ + import std.experimental.allocator.building_blocks; + import std.random; + import std.typecons : Ternary; + + // For 64 bits, we allocate in multiples of 8, but the minimum alloc size is 16. + // This can create gaps. + // This test is an example of such a case. The gap is formed between the block + // allocated for the second value in sizes and the third. There is also a gap + // at the very end. (total lost 2 * word) + + int[] sizes64 = [2008, 18904, 74904, 224, 111904, 1904, 52288, 8]; + int[] sizes32 = [81412, 107068, 49892, 23768]; + + int word64 = 8; + int word32 = 4; + + void test(int[] sizes, int word) + { + align(size_t.sizeof) ubyte[256 * 1024] buf; + auto a = KRRegion!()(buf); + + void[][] bufs; + + foreach (size; sizes) + { + bufs ~= a.allocate(size); + } + + a.deallocate(bufs[1]); + bufs ~= a.allocate(sizes[1] - word); + + a.deallocate(bufs[0]); + foreach (i; 2 .. bufs.length) + { + a.deallocate(bufs[i]); + } + + assert(a.empty == Ternary.yes); + } + + test(sizes64, word64); + test(sizes32, word32); +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/null_allocator.d b/libphobos/src/std/experimental/allocator/building_blocks/null_allocator.d new file mode 100644 index 0000000..68bab70 --- /dev/null +++ b/libphobos/src/std/experimental/allocator/building_blocks/null_allocator.d @@ -0,0 +1,85 @@ +/// +module std.experimental.allocator.building_blocks.null_allocator; + +/** +$(D NullAllocator) is an emphatically empty implementation of the allocator +interface. Although it has no direct use, it is useful as a "terminator" in +composite allocators. +*/ +struct NullAllocator +{ + import std.typecons : Ternary; + /** + $(D NullAllocator) advertises a relatively large _alignment equal to 64 KB. + This is because $(D NullAllocator) never actually needs to honor this + alignment and because composite allocators using $(D NullAllocator) + shouldn't be unnecessarily constrained. + */ + enum uint alignment = 64 * 1024; + // /// Returns $(D n). + //size_t goodAllocSize(size_t n) shared const + //{ return .goodAllocSize(this, n); } + /// Always returns $(D null). + void[] allocate(size_t) shared { return null; } + /// Always returns $(D null). + void[] alignedAllocate(size_t, uint) shared { return null; } + /// Always returns $(D null). + void[] allocateAll() shared { return null; } + /** + These methods return $(D false). + Precondition: $(D b is null). This is because there is no other possible + legitimate input. + */ + bool expand(ref void[] b, size_t s) shared + { assert(b is null); return s == 0; } + /// Ditto + bool reallocate(ref void[] b, size_t) shared + { assert(b is null); return false; } + /// Ditto + bool alignedReallocate(ref void[] b, size_t, uint) shared + { assert(b is null); return false; } + /// Returns $(D Ternary.no). + Ternary owns(void[]) shared const { return Ternary.no; } + /** + Returns $(D Ternary.no). + */ + Ternary resolveInternalPointer(const void*, ref void[]) shared const + { return Ternary.no; } + /** + No-op. + Precondition: $(D b is null) + */ + bool deallocate(void[] b) shared { assert(b is null); return true; } + /** + No-op. + */ + bool deallocateAll() shared { return true; } + /** + Returns $(D Ternary.yes). + */ + Ternary empty() shared const { return Ternary.yes; } + /** + Returns the $(D shared) global instance of the $(D NullAllocator). + */ + static shared NullAllocator instance; +} + +@system unittest +{ + assert(NullAllocator.instance.alignedAllocate(100, 0) is null); + assert(NullAllocator.instance.allocateAll() is null); + auto b = NullAllocator.instance.allocate(100); + assert(b is null); + assert(NullAllocator.instance.expand(b, 0)); + assert(!NullAllocator.instance.expand(b, 42)); + assert(!NullAllocator.instance.reallocate(b, 42)); + assert(!NullAllocator.instance.alignedReallocate(b, 42, 0)); + NullAllocator.instance.deallocate(b); + NullAllocator.instance.deallocateAll(); + + import std.typecons : Ternary; + assert(NullAllocator.instance.empty() == Ternary.yes); + assert(NullAllocator.instance.owns(null) == Ternary.no); + void[] p; + assert(NullAllocator.instance.resolveInternalPointer(null, p) == Ternary.no); +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/package.d b/libphobos/src/std/experimental/allocator/building_blocks/package.d new file mode 100644 index 0000000..d55a16b --- /dev/null +++ b/libphobos/src/std/experimental/allocator/building_blocks/package.d @@ -0,0 +1,313 @@ +/** +$(H2 Assembling Your Own Allocator) + +In addition to defining the interfaces above, this package also implements +untyped composable memory allocators. They are $(I untyped) because they deal +exclusively in $(D void[]) and have no notion of what type the memory allocated +would be destined for. They are $(I composable) because the included allocators +are building blocks that can be assembled in complex nontrivial allocators. + +$(P Unlike the allocators for the C and C++ programming languages, which manage +the allocated size internally, these allocators require that the client +maintains (or knows $(I a priori)) the allocation size for each piece of memory +allocated. Put simply, the client must pass the allocated size upon +deallocation. Storing the size in the _allocator has significant negative +performance implications, and is virtually always redundant because client code +needs knowledge of the allocated size in order to avoid buffer overruns. (See +more discussion in a $(HTTP open- +std.org/JTC1/SC22/WG21/docs/papers/2013/n3536.html, proposal) for sized +deallocation in C++.) For this reason, allocators herein traffic in $(D void[]) +as opposed to $(D void*).) + +$(P In order to be usable as an _allocator, a type should implement the +following methods with their respective semantics. Only $(D alignment) and $(D +allocate) are required. If any of the other methods is missing, the _allocator +is assumed to not have that capability (for example some allocators do not offer +manual deallocation of memory). Allocators should NOT implement +unsupported methods to always fail. For example, an allocator that lacks the +capability to implement `alignedAllocate` should not define it at all (as +opposed to defining it to always return `null` or throw an exception). The +missing implementation statically informs other components about the +allocator's capabilities and allows them to make design decisions accordingly.) + +$(BOOKTABLE , +$(TR $(TH Method name) $(TH Semantics)) + +$(TR $(TDC uint alignment;, $(POST $(RES) > 0)) $(TD Returns the minimum +alignment of all data returned by the allocator. An allocator may implement $(D +alignment) as a statically-known $(D enum) value only. Applications that need +dynamically-chosen alignment values should use the $(D alignedAllocate) and $(D +alignedReallocate) APIs.)) + +$(TR $(TDC size_t goodAllocSize(size_t n);, $(POST $(RES) >= n)) $(TD Allocators +customarily allocate memory in discretely-sized chunks. Therefore, a request for +$(D n) bytes may result in a larger allocation. The extra memory allocated goes +unused and adds to the so-called $(HTTP goo.gl/YoKffF,internal fragmentation). +The function $(D goodAllocSize(n)) returns the actual number of bytes that would +be allocated upon a request for $(D n) bytes. This module defines a default +implementation that returns $(D n) rounded up to a multiple of the allocator's +alignment.)) + +$(TR $(TDC void[] allocate(size_t s);, $(POST $(RES) is null || $(RES).length == +s)) $(TD If $(D s == 0), the call may return any empty slice (including $(D +null)). Otherwise, the call allocates $(D s) bytes of memory and returns the +allocated block, or $(D null) if the request could not be satisfied.)) + +$(TR $(TDC void[] alignedAllocate(size_t s, uint a);, $(POST $(RES) is null || +$(RES).length == s)) $(TD Similar to `allocate`, with the additional +guarantee that the memory returned is aligned to at least `a` bytes. `a` +must be a power of 2.)) + +$(TR $(TDC void[] allocateAll();) $(TD Offers all of allocator's memory to the +caller, so it's usually defined by fixed-size allocators. If the allocator is +currently NOT managing any memory, then $(D allocateAll()) shall allocate and +return all memory available to the allocator, and subsequent calls to all +allocation primitives should not succeed (e.g. $(D allocate) shall return $(D +null) etc). Otherwise, $(D allocateAll) only works on a best-effort basis, and +the allocator is allowed to return $(D null) even if does have available memory. +Memory allocated with $(D allocateAll) is not otherwise special (e.g. can be +reallocated or deallocated with the usual primitives, if defined).)) + +$(TR $(TDC bool expand(ref void[] b, size_t delta);, $(POST !$(RES) || b.length +== $(I old)(b).length + delta)) $(TD Expands $(D b) by $(D delta) bytes. If $(D +delta == 0), succeeds without changing $(D b). If $(D b is null), returns +`false` (the null pointer cannot be expanded in place). Otherwise, $(D +b) must be a buffer previously allocated with the same allocator. If expansion +was successful, $(D expand) changes $(D b)'s length to $(D b.length + delta) and +returns $(D true). Upon failure, the call effects no change upon the allocator +object, leaves $(D b) unchanged, and returns $(D false).)) + +$(TR $(TDC bool reallocate(ref void[] b, size_t s);, $(POST !$(RES) || b.length +== s)) $(TD Reallocates $(D b) to size $(D s), possibly moving memory around. +$(D b) must be $(D null) or a buffer allocated with the same allocator. If +reallocation was successful, $(D reallocate) changes $(D b) appropriately and +returns $(D true). Upon failure, the call effects no change upon the allocator +object, leaves $(D b) unchanged, and returns $(D false). An allocator should +implement $(D reallocate) if it can derive some advantage from doing so; +otherwise, this module defines a $(D reallocate) free function implemented in +terms of $(D expand), $(D allocate), and $(D deallocate).)) + +$(TR $(TDC bool alignedReallocate(ref void[] b,$(BR) size_t s, uint a);, $(POST +!$(RES) || b.length == s)) $(TD Similar to $(D reallocate), but guarantees the +reallocated memory is aligned at $(D a) bytes. The buffer must have been +originated with a call to $(D alignedAllocate). $(D a) must be a power of 2 +greater than $(D (void*).sizeof). An allocator should implement $(D +alignedReallocate) if it can derive some advantage from doing so; otherwise, +this module defines a $(D alignedReallocate) free function implemented in terms +of $(D expand), $(D alignedAllocate), and $(D deallocate).)) + +$(TR $(TDC Ternary owns(void[] b);) $(TD Returns `Ternary.yes` if `b` has been +allocated with this allocator. An allocator should define this method only if it +can decide on ownership precisely and fast (in constant time, logarithmic time, +or linear time with a low multiplication factor). Traditional allocators such as +the C heap do not define such functionality. If $(D b is null), the allocator +shall return `Ternary.no`, i.e. no allocator owns the `null` slice.)) + +$(TR $(TDC Ternary resolveInternalPointer(void* p, ref void[] result);) $(TD If +`p` is a pointer somewhere inside a block allocated with this allocator, +`result` holds a pointer to the beginning of the allocated block and returns +`Ternary.yes`. Otherwise, `result` holds `null` and returns `Ternary.no`. +If the pointer points immediately after an allocated block, the result is +implementation defined.)) + +$(TR $(TDC bool deallocate(void[] b);) $(TD If $(D b is null), does +nothing and returns `true`. Otherwise, deallocates memory previously allocated +with this allocator and returns `true` if successful, `false` otherwise. An +implementation that would not support deallocation (i.e. would always return +`false` should not define this primitive at all.))) + +$(TR $(TDC bool deallocateAll();, $(POST empty)) $(TD Deallocates all memory +allocated with this allocator. If an allocator implements this method, it must +specify whether its destructor calls it, too.)) + +$(TR $(TDC Ternary empty();) $(TD Returns `Ternary.yes` if and only if the +allocator holds no memory (i.e. no allocation has occurred, or all allocations +have been deallocated).)) + +$(TR $(TDC static Allocator instance;, $(POST instance $(I is a valid) +Allocator $(I object))) $(TD Some allocators are $(I monostate), i.e. have only +an instance and hold only global state. (Notable examples are C's own +`malloc`-based allocator and D's garbage-collected heap.) Such allocators must +define a static $(D instance) instance that serves as the symbolic placeholder +for the global instance of the allocator. An allocator should not hold state +and define `instance` simultaneously. Depending on whether the allocator is +thread-safe or not, this instance may be $(D shared).)) +) + +$(H2 Sample Assembly) + +The example below features an _allocator modeled after $(HTTP goo.gl/m7329l, +jemalloc), which uses a battery of free-list allocators spaced so as to keep +internal fragmentation to a minimum. The $(D FList) definitions specify no +bounds for the freelist because the $(D Segregator) does all size selection in +advance. + +Sizes through 3584 bytes are handled via freelists of staggered sizes. Sizes +from 3585 bytes through 4072 KB are handled by a $(D BitmappedBlock) with a +block size of 4 KB. Sizes above that are passed direct to the $(D GCAllocator). + +---- + alias FList = FreeList!(GCAllocator, 0, unbounded); + alias A = Segregator!( + 8, FreeList!(GCAllocator, 0, 8), + 128, Bucketizer!(FList, 1, 128, 16), + 256, Bucketizer!(FList, 129, 256, 32), + 512, Bucketizer!(FList, 257, 512, 64), + 1024, Bucketizer!(FList, 513, 1024, 128), + 2048, Bucketizer!(FList, 1025, 2048, 256), + 3584, Bucketizer!(FList, 2049, 3584, 512), + 4072 * 1024, AllocatorList!( + () => BitmappedBlock!(GCAllocator, 4096)(4072 * 1024)), + GCAllocator + ); + A tuMalloc; + auto b = tuMalloc.allocate(500); + assert(b.length == 500); + auto c = tuMalloc.allocate(113); + assert(c.length == 113); + assert(tuMalloc.expand(c, 14)); + tuMalloc.deallocate(b); + tuMalloc.deallocate(c); +---- + +$(H2 Allocating memory for sharing across threads) + +One allocation pattern used in multithreaded applications is to share memory +across threads, and to deallocate blocks in a different thread than the one that +allocated it. + +All allocators in this module accept and return $(D void[]) (as opposed to +$(D shared void[])). This is because at the time of allocation, deallocation, or +reallocation, the memory is effectively not $(D shared) (if it were, it would +reveal a bug at the application level). + +The issue remains of calling $(D a.deallocate(b)) from a different thread than +the one that allocated $(D b). It follows that both threads must have access to +the same instance $(D a) of the respective allocator type. By definition of D, +this is possible only if $(D a) has the $(D shared) qualifier. It follows that +the allocator type must implement $(D allocate) and $(D deallocate) as $(D +shared) methods. That way, the allocator commits to allowing usable $(D shared) +instances. + +Conversely, allocating memory with one non-$(D shared) allocator, passing it +across threads (by casting the obtained buffer to $(D shared)), and later +deallocating it in a different thread (either with a different allocator object +or with the same allocator object after casting it to $(D shared)) is illegal. + +$(H2 Building Blocks) + +$(P The table below gives a synopsis of predefined allocator building blocks, +with their respective modules. Either `import` the needed modules individually, +or `import` `std.experimental.building_blocks`, which imports them all +`public`ly. The building blocks can be assembled in unbounded ways and also +combined with your own. For a collection of typical and useful preassembled +allocators and for inspiration in defining more such assemblies, refer to +$(MREF std,experimental,allocator,showcase).) + +$(BOOKTABLE, +$(TR $(TH Allocator$(BR)) $(TH Description)) + +$(TR $(TDC2 NullAllocator, null_allocator) $(TD Very good at doing absolutely nothing. A good +starting point for defining other allocators or for studying the API.)) + +$(TR $(TDC3 GCAllocator, gc_allocator) $(TD The system-provided garbage-collector allocator. +This should be the default fallback allocator tapping into system memory. It +offers manual $(D free) and dutifully collects litter.)) + +$(TR $(TDC3 Mallocator, mallocator) $(TD The C heap _allocator, a.k.a. $(D +malloc)/$(D realloc)/$(D free). Use sparingly and only for code that is unlikely +to leak.)) + +$(TR $(TDC3 AlignedMallocator, mallocator) $(TD Interface to OS-specific _allocators that +support specifying alignment: +$(HTTP man7.org/linux/man-pages/man3/posix_memalign.3.html, $(D posix_memalign)) +on Posix and $(HTTP msdn.microsoft.com/en-us/library/fs9stz4e(v=vs.80).aspx, +$(D __aligned_xxx)) on Windows.)) + +$(TR $(TDC2 AffixAllocator, affix_allocator) $(TD Allocator that allows and manages allocating +extra prefix and/or a suffix bytes for each block allocated.)) + +$(TR $(TDC2 BitmappedBlock, bitmapped_block) $(TD Organizes one contiguous chunk of memory in +equal-size blocks and tracks allocation status at the cost of one bit per +block.)) + +$(TR $(TDC2 FallbackAllocator, fallback_allocator) $(TD Allocator that combines two other allocators + - primary and fallback. Allocation requests are first tried with primary, and + upon failure are passed to the fallback. Useful for small and fast allocators + fronting general-purpose ones.)) + +$(TR $(TDC2 FreeList, free_list) $(TD Allocator that implements a $(HTTP +wikipedia.org/wiki/Free_list, free list) on top of any other allocator. The +preferred size, tolerance, and maximum elements are configurable at compile- and +run time.)) + +$(TR $(TDC2 SharedFreeList, free_list) $(TD Same features as $(D FreeList), but packaged as +a $(D shared) structure that is accessible to several threads.)) + +$(TR $(TDC2 FreeTree, free_tree) $(TD Allocator similar to $(D FreeList) that uses a +binary search tree to adaptively store not one, but many free lists.)) + +$(TR $(TDC2 Region, region) $(TD Region allocator organizes a chunk of memory as a +simple bump-the-pointer allocator.)) + +$(TR $(TDC2 InSituRegion, region) $(TD Region holding its own allocation, most often on +the stack. Has statically-determined size.)) + +$(TR $(TDC2 SbrkRegion, region) $(TD Region using $(D $(LINK2 https://en.wikipedia.org/wiki/Sbrk, +sbrk)) for allocating memory.)) + +$(TR $(TDC3 MmapAllocator, mmap_allocator) $(TD Allocator using + $(D $(LINK2 https://en.wikipedia.org/wiki/Mmap, mmap)) directly.)) + +$(TR $(TDC2 StatsCollector, stats_collector) $(TD Collect statistics about any other +allocator.)) + +$(TR $(TDC2 Quantizer, quantizer) $(TD Allocates in coarse-grained quantas, thus +improving performance of reallocations by often reallocating in place. The drawback is higher memory consumption because of allocated and unused memory.)) + +$(TR $(TDC2 AllocatorList, allocator_list) $(TD Given an allocator factory, lazily creates as +many allocators as needed to satisfy allocation requests. The allocators are +stored in a linked list. Requests for allocation are satisfied by searching the +list in a linear manner.)) + +$(TR $(TDC2 Segregator, segregator) $(TD Segregates allocation requests by size +and dispatches them to distinct allocators.)) + +$(TR $(TDC2 Bucketizer, bucketizer) $(TD Divides allocation sizes in discrete buckets and +uses an array of allocators, one per bucket, to satisfy requests.)) + +$(COMMENT $(TR $(TDC2 InternalPointersTree) $(TD Adds support for resolving internal +pointers on top of another allocator.))) +) + +Macros: +MYREF2 = $(REF_SHORT $1, std,experimental,allocator,building_blocks,$2) +MYREF3 = $(REF_SHORT $1, std,experimental,allocator,$2) +TDC = $(TDNW $(D $1)$+) +TDC2 = $(TDNW $(D $(MYREF2 $1,$+))$(BR)$(SMALL +$(D std.experimental.allocator.building_blocks.$2))) +TDC3 = $(TDNW $(D $(MYREF3 $1,$+))$(BR)$(SMALL +$(D std.experimental.allocator.$2))) +RES = $(I result) +POST = $(BR)$(SMALL $(I Post:) $(BLUE $(D $0))) +*/ + +module std.experimental.allocator.building_blocks; + +public import + 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.gc_allocator, + std.experimental.allocator.building_blocks.bitmapped_block, + std.experimental.allocator.building_blocks.kernighan_ritchie, + std.experimental.allocator.mallocator, + std.experimental.allocator.mmap_allocator, + std.experimental.allocator.building_blocks.null_allocator, + 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; diff --git a/libphobos/src/std/experimental/allocator/building_blocks/quantizer.d b/libphobos/src/std/experimental/allocator/building_blocks/quantizer.d new file mode 100644 index 0000000..b3f205d --- /dev/null +++ b/libphobos/src/std/experimental/allocator/building_blocks/quantizer.d @@ -0,0 +1,234 @@ +/// +module std.experimental.allocator.building_blocks.quantizer; + +import std.experimental.allocator.common; + +/** +This allocator sits on top of $(D ParentAllocator) and quantizes allocation +sizes, usually from arbitrary positive numbers to a small set of round numbers +(e.g. powers of two, page sizes etc). This technique is commonly used to: + +$(UL +$(LI Preallocate more memory than requested such that later on, when +reallocation is needed (e.g. to grow an array), expansion can be done quickly +in place. Reallocation to smaller sizes is also fast (in-place) when the new +size requested is within the same quantum as the existing size. Code that's +reallocation-heavy can therefore benefit from fronting a generic allocator +with a $(D Quantizer). These advantages are present even if +$(D ParentAllocator) does not support reallocation at all.) +$(LI Improve behavior of allocators sensitive to allocation sizes, such as $(D +FreeList) and $(D FreeTree). Rounding allocation requests up makes for smaller +free lists/trees at the cost of slack memory (internal fragmentation).) +) + +The following methods are forwarded to the parent allocator if present: +$(D allocateAll), $(D owns), $(D deallocateAll), $(D empty). + +Preconditions: $(D roundingFunction) must satisfy three constraints. These are +not enforced (save for the use of $(D assert)) for the sake of efficiency. +$(OL +$(LI $(D roundingFunction(n) >= n) for all $(D n) of type $(D size_t);) +$(LI $(D roundingFunction) must be monotonically increasing, i.e. $(D +roundingFunction(n1) <= roundingFunction(n2)) for all $(D n1 < n2);) +$(LI $(D roundingFunction) must be $(D pure), i.e. always return the same +value for a given $(D n).) +) +*/ +struct Quantizer(ParentAllocator, alias roundingFunction) +{ + import std.traits : hasMember; + + /** + The parent allocator. Depending on whether $(D ParentAllocator) holds state + or not, this is a member variable or an alias for + `ParentAllocator.instance`. + */ + static if (stateSize!ParentAllocator) + { + ParentAllocator parent; + } + else + { + alias parent = ParentAllocator.instance; + static __gshared Quantizer instance; + } + + /** + Returns $(D roundingFunction(n)). + */ + size_t goodAllocSize(size_t n) + { + auto result = roundingFunction(n); + assert(result >= n); + return result; + } + + /** + Alignment is identical to that of the parent. + */ + enum alignment = ParentAllocator.alignment; + + /** + Gets a larger buffer $(D buf) by calling + $(D parent.allocate(goodAllocSize(n))). If $(D buf) is $(D null), returns + $(D null). Otherwise, returns $(D buf[0 .. n]). + */ + void[] allocate(size_t n) + { + auto result = parent.allocate(goodAllocSize(n)); + return result.ptr ? result.ptr[0 .. n] : null; + } + + /** + Defined only if $(D parent.alignedAllocate) exists and works similarly to + $(D allocate) by forwarding to + $(D parent.alignedAllocate(goodAllocSize(n), a)). + */ + static if (hasMember!(ParentAllocator, "alignedAllocate")) + void[] alignedAllocate(size_t n, uint) + { + auto result = parent.alignedAllocate(goodAllocSize(n)); + return result.ptr ? result.ptr[0 .. n] : null; + } + + /** + First checks whether there's enough slack memory preallocated for $(D b) + by evaluating $(D b.length + delta <= goodAllocSize(b.length)). If that's + the case, expands $(D b) in place. Otherwise, attempts to use + $(D parent.expand) appropriately if present. + */ + bool expand(ref void[] b, size_t delta) + { + if (!b.ptr) return delta == 0; + immutable allocated = goodAllocSize(b.length), + needed = b.length + delta, + neededAllocation = goodAllocSize(needed); + assert(b.length <= allocated); + assert(needed <= neededAllocation); + assert(allocated <= neededAllocation); + // Second test needed because expand must work for null pointers, too. + if (allocated == neededAllocation) + { + // Nice! + b = b.ptr[0 .. needed]; + return true; + } + // Hail Mary + static if (hasMember!(ParentAllocator, "expand")) + { + // Expand to the appropriate quantum + auto original = b.ptr[0 .. allocated]; + assert(goodAllocSize(needed) >= allocated); + if (!parent.expand(original, neededAllocation - allocated)) + return false; + // Dial back the size + b = original.ptr[0 .. needed]; + return true; + } + else + { + return false; + } + } + + /** + Expands or shrinks allocated block to an allocated size of $(D + goodAllocSize(s)). Expansion occurs in place under the conditions required + by $(D expand). Shrinking occurs in place if $(D goodAllocSize(b.length) + == goodAllocSize(s)). + */ + bool reallocate(ref void[] b, size_t s) + { + if (!b.ptr) + { + b = allocate(s); + return b.length == s; + } + if (s >= b.length && expand(b, s - b.length)) return true; + immutable toAllocate = goodAllocSize(s), + allocated = goodAllocSize(b.length); + // Are the lengths within the same quantum? + if (allocated == toAllocate) + { + // Reallocation (whether up or down) will be done in place + b = b.ptr[0 .. s]; + return true; + } + // Defer to parent (or global) with quantized size + auto original = b.ptr[0 .. allocated]; + if (!parent.reallocate(original, toAllocate)) return false; + b = original.ptr[0 .. s]; + return true; + } + + /** + Defined only if $(D ParentAllocator.alignedAllocate) exists. Expansion + occurs in place under the conditions required by $(D expand). Shrinking + occurs in place if $(D goodAllocSize(b.length) == goodAllocSize(s)). + */ + static if (hasMember!(ParentAllocator, "alignedAllocate")) + bool alignedReallocate(ref void[] b, size_t s, uint a) + { + if (!b.ptr) + { + b = alignedAllocate(s); + return b.length == s; + } + if (s >= b.length && expand(b, s - b.length)) return true; + immutable toAllocate = goodAllocSize(s), + allocated = goodAllocSize(b.length); + // Are the lengths within the same quantum? + if (allocated == toAllocate) + { + assert(b.ptr); // code above must have caught this + // Reallocation (whether up or down) will be done in place + b = b.ptr[0 .. s]; + return true; + } + // Defer to parent (or global) with quantized size + auto original = b.ptr[0 .. allocated]; + if (!parent.alignedReallocate(original, toAllocate, a)) return false; + b = original.ptr[0 .. s]; + return true; + } + + /** + Defined if $(D ParentAllocator.deallocate) exists and forwards to + $(D parent.deallocate(b.ptr[0 .. goodAllocSize(b.length)])). + */ + static if (hasMember!(ParentAllocator, "deallocate")) + bool deallocate(void[] b) + { + if (!b.ptr) return true; + return parent.deallocate(b.ptr[0 .. goodAllocSize(b.length)]); + } + + // Forwarding methods + mixin(forwardToMember("parent", + "allocateAll", "owns", "deallocateAll", "empty")); +} + +/// +@system unittest +{ + import std.experimental.allocator.building_blocks.free_tree : FreeTree; + import std.experimental.allocator.common : roundUpToMultipleOf; + import std.experimental.allocator.gc_allocator : GCAllocator; + + // Quantize small allocations to a multiple of cache line, large ones to a + // multiple of page size + alias MyAlloc = Quantizer!( + FreeTree!GCAllocator, + n => n.roundUpToMultipleOf(n <= 16_384 ? 64 : 4096)); + MyAlloc alloc; + const buf = alloc.allocate(256); + assert(buf.ptr); +} + +@system unittest +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + alias MyAlloc = Quantizer!(GCAllocator, + (size_t n) => n.roundUpToMultipleOf(64)); + testAllocator!(() => MyAlloc()); +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/region.d b/libphobos/src/std/experimental/allocator/building_blocks/region.d new file mode 100644 index 0000000..80157ae --- /dev/null +++ b/libphobos/src/std/experimental/allocator/building_blocks/region.d @@ -0,0 +1,784 @@ +/// +module std.experimental.allocator.building_blocks.region; + +import std.experimental.allocator.building_blocks.null_allocator; +import std.experimental.allocator.common; +import std.typecons : Flag, Yes, No; + +/** +A $(D Region) allocator allocates memory straight from one contiguous chunk. +There is no deallocation, and once the region is full, allocation requests +return $(D null). Therefore, $(D Region)s are often used (a) in conjunction with +more sophisticated allocators; or (b) for batch-style very fast allocations +that deallocate everything at once. + +The region only stores three pointers, corresponding to the current position in +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 $(D ParentAllocator) is different from $(D NullAllocator), $(D Region) +deallocates the chunk of memory during destruction. + +The $(D minAlign) parameter establishes alignment. If $(D minAlign > 1), the +sizes of all allocation requests are rounded up to a multiple of $(D minAlign). +Applications aiming at maximum speed may want to choose $(D minAlign = 1) and +control alignment externally. + +*/ +struct Region(ParentAllocator = NullAllocator, + uint minAlign = platformAlignment, + Flag!"growDownwards" growDownwards = No.growDownwards) +{ + static assert(minAlign.isGoodStaticAlignment); + static assert(ParentAllocator.alignment >= minAlign); + + import std.traits : hasMember; + import std.typecons : Ternary; + + // state + /** + The _parent allocator. Depending on whether $(D ParentAllocator) holds state + or not, this is a member variable or an alias for + `ParentAllocator.instance`. + */ + static if (stateSize!ParentAllocator) + { + ParentAllocator parent; + } + else + { + alias parent = ParentAllocator.instance; + } + private void* _current, _begin, _end; + + /** + Constructs a region backed by a user-provided store. Assumes $(D store) is + aligned at $(D minAlign). Also assumes the memory was allocated with $(D + ParentAllocator) (if different from $(D NullAllocator)). + + Params: + store = User-provided store backing up the region. $(D store) must be + aligned at $(D minAlign) (enforced with $(D assert)). If $(D + ParentAllocator) is different from $(D NullAllocator), memory is assumed to + have been allocated with $(D ParentAllocator). + n = Bytes to allocate using $(D ParentAllocator). This constructor is only + defined If $(D ParentAllocator) is different from $(D NullAllocator). If + $(D parent.allocate(n)) returns $(D null), the region will be initialized + as empty (correctly initialized but unable to allocate). + */ + this(ubyte[] store) + { + store = cast(ubyte[])(store.roundUpToAlignment(alignment)); + store = store[0 .. $.roundDownToAlignment(alignment)]; + assert(store.ptr.alignedAt(minAlign)); + assert(store.length % minAlign == 0); + _begin = store.ptr; + _end = store.ptr + store.length; + static if (growDownwards) + _current = _end; + else + _current = store.ptr; + } + + /// Ditto + static if (!is(ParentAllocator == NullAllocator)) + this(size_t n) + { + this(cast(ubyte[])(parent.allocate(n.roundUpToAlignment(alignment)))); + } + + /* + TODO: The postblit of $(D BasicRegion) should be disabled because such objects + should not be copied around naively. + */ + + /** + If `ParentAllocator` is not `NullAllocator` and defines `deallocate`, the region defines a destructor that uses `ParentAllocator.delete` to free the + memory chunk. + */ + static if (!is(ParentAllocator == NullAllocator) + && hasMember!(ParentAllocator, "deallocate")) + ~this() + { + parent.deallocate(_begin[0 .. _end - _begin]); + } + + + /** + Alignment offered. + */ + alias alignment = minAlign; + + /** + Allocates $(D 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 $(D n) or $(D null) if request could not + be satisfied. + */ + void[] allocate(size_t n) + { + static if (growDownwards) + { + if (available < n) return null; + static if (minAlign > 1) + const rounded = n.roundUpToAlignment(alignment); + else + alias rounded = n; + assert(available >= rounded); + auto result = (_current - rounded)[0 .. n]; + assert(result.ptr >= _begin); + _current = result.ptr; + assert(owns(result) == Ternary.yes); + return result; + } + else + { + auto result = _current[0 .. n]; + static if (minAlign > 1) + const rounded = n.roundUpToAlignment(alignment); + else + alias rounded = n; + _current += rounded; + if (_current <= _end) return result; + // Slow path, backtrack + _current -= rounded; + return null; + } + } + + /** + Allocates $(D n) bytes of memory aligned at alignment $(D a). + + Params: + n = number of bytes to allocate + a = alignment for the allocated block + + Returns: + Either a suitable block of $(D n) bytes aligned at $(D a), or $(D null). + */ + void[] alignedAllocate(size_t n, uint a) + { + import std.math : isPowerOf2; + assert(a.isPowerOf2); + static if (growDownwards) + { + const available = _current - _begin; + if (available < n) return null; + auto result = (_current - n).alignDownTo(a)[0 .. n]; + if (result.ptr >= _begin) + { + _current = result.ptr; + return result; + } + } + else + { + // Just bump the pointer to the next good allocation + auto save = _current; + _current = _current.alignUpTo(a); + 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() + { + 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) + { + assert(owns(b) == Ternary.yes || b.ptr is null); + assert(b.ptr + b.length <= _current || b.ptr is null); + if (!b.ptr) return delta == 0; + auto newLength = b.length + delta; + if (_current < b.ptr + b.length + alignment) + { + // This was the last allocation! Allocate some more and we're done. + if (this.goodAllocSize(b.length) == this.goodAllocSize(newLength) + || allocate(delta).length == delta) + { + b = b.ptr[0 .. newLength]; + assert(_current < b.ptr + b.length + alignment); + return true; + } + } + return false; + } + + /** + Deallocates $(D b). This works only if $(D b) was obtained as the last call + to $(D allocate); otherwise (i.e. another allocation has occurred since) it + does nothing. This semantics is tricky and therefore $(D deallocate) is + defined only if $(D Region) is instantiated with $(D Yes.defineDeallocate) + as the third template argument. + + Params: + b = Block previously obtained by a call to $(D allocate) against this + allocator ($(D null) is allowed). + */ + bool deallocate(void[] b) + { + assert(owns(b) == Ternary.yes || b.ptr is null); + static if (growDownwards) + { + if (b.ptr == _current) + { + _current += this.goodAllocSize(b.length); + return true; + } + } + else + { + if (b.ptr + this.goodAllocSize(b.length) == _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() + { + static if (growDownwards) + { + _current = _end; + } + else + { + _current = _begin; + } + return true; + } + + /** + Queries whether $(D b) has been allocated with this region. + + Params: + b = Arbitrary block of memory ($(D null) is allowed; $(D owns(null)) + returns $(D false)). + + Returns: + $(D true) if $(D b) has been allocated with this region, $(D false) + otherwise. + */ + Ternary owns(void[] b) const + { + return Ternary(b.ptr >= _begin && b.ptr + 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 + { + return Ternary(_current == _begin); + } + + /// Nonstandard property that returns bytes available for allocation. + size_t available() const + { + static if (growDownwards) + { + return _current - _begin; + } + else + { + return _end - _current; + } + } +} + +/// +@system unittest +{ + import std.algorithm.comparison : max; + import std.experimental.allocator.building_blocks.allocator_list + : AllocatorList; + import std.experimental.allocator.mallocator : Mallocator; + // Create a scalable list of regions. Each gets at least 1MB at a time by + // using malloc. + auto batchAllocator = AllocatorList!( + (size_t n) => Region!Mallocator(max(n, 1024 * 1024)) + )(); + auto b = batchAllocator.allocate(101); + assert(b.length == 101); + // This will cause a second allocation + b = batchAllocator.allocate(2 * 1024 * 1024); + assert(b.length == 2 * 1024 * 1024); + // Destructor will free the memory +} + +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + // Create a 64 KB region allocated with malloc + auto reg = Region!(Mallocator, Mallocator.alignment, + Yes.growDownwards)(1024 * 64); + const b = reg.allocate(101); + assert(b.length == 101); + // Destructor will free the memory +} + +/** + +$(D 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 $(D 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 (PPC) enum growDownwards = Yes.growDownwards; + else version (PPC64) 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 (SystemZ) enum growDownwards = Yes.growDownwards; + else static assert(0, "Dunno how the stack grows on this architecture."); + + @disable this(this); + + // state { + private Region!(NullAllocator, minAlign, growDownwards) _impl; + union + { + private ubyte[size] _store = void; + private double _forAlignmentOnly1 = void; + } + // } + + /** + An alias for $(D 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 $(D bytes) and returns them, or $(D 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 $(D 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); + goto entry; + } + + /** + Deallocates $(D b). This works only if $(D b) was obtained as the last call + to $(D allocate); otherwise (i.e. another allocation has occurred since) it + does nothing. This semantics is tricky and therefore $(D deallocate) is + defined only if $(D Region) is instantiated with $(D Yes.defineDeallocate) + as the third template argument. + + Params: + b = Block previously obtained by a call to $(D allocate) against this + allocator ($(D null) is allowed). + */ + bool deallocate(void[] b) + { + if (!_impl._current) return b is null; + return _impl.deallocate(b); + } + + /** + Returns `Ternary.yes` if `b` is the result of a previous allocation, + `Ternary.no` otherwise. + */ + Ternary owns(void[] b) + { + if (!_impl._current) return Ternary.no; + return _impl.owns(b); + } + + /** + Expands an allocated block in place. Expansion will succeed only if the + block is the last allocated. + */ + static if (hasMember!(typeof(_impl), "expand")) + bool expand(ref void[] b, size_t delta) + { + if (!_impl._current) lazyInit; + return _impl.expand(b, delta); + } + + /** + Deallocates all memory allocated with this allocator. + */ + bool deallocateAll() + { + // We don't care to lazily init the region + return _impl.deallocateAll; + } + + /** + Allocates all memory available with this allocator. + */ + void[] allocateAll() + { + if (!_impl._current) lazyInit; + return _impl.allocateAll; + } + + /** + Nonstandard function that returns the bytes available for allocation. + */ + size_t available() + { + if (!_impl._current) lazyInit; + return _impl.available; + } +} + +/// +@system unittest +{ + // 128KB region, allocated to x86's cache line + InSituRegion!(128 * 1024, 16) r1; + auto a1 = r1.allocate(101); + assert(a1.length == 101); + + // 128KB region, with fallback to the garbage collector. + import std.experimental.allocator.building_blocks.fallback_allocator + : FallbackAllocator; + import std.experimental.allocator.building_blocks.free_list + : FreeList; + import std.experimental.allocator.building_blocks.bitmapped_block + : BitmappedBlock; + import std.experimental.allocator.gc_allocator : GCAllocator; + FallbackAllocator!(InSituRegion!(128 * 1024), GCAllocator) r2; + const a2 = r2.allocate(102); + assert(a2.length == 102); + + // Reap with GC fallback. + InSituRegion!(128 * 1024, 8) tmp3; + FallbackAllocator!(BitmappedBlock!(64, 8), GCAllocator) r3; + r3.primary = BitmappedBlock!(64, 8)(cast(ubyte[])(tmp3.allocateAll())); + const a3 = r3.allocate(103); + assert(a3.length == 103); + + // Reap/GC with a freelist for small objects up to 16 bytes. + InSituRegion!(128 * 1024, 64) tmp4; + FreeList!(FallbackAllocator!(BitmappedBlock!(64, 64), GCAllocator), 0, 16) r4; + r4.parent.primary = BitmappedBlock!(64, 64)(cast(ubyte[])(tmp4.allocateAll())); + const a4 = r4.allocate(104); + assert(a4.length == 104); +} + +@system unittest +{ + InSituRegion!(4096, 1) r1; + auto a = r1.allocate(2001); + assert(a.length == 2001); + import std.conv : text; + assert(r1.available == 2095, text(r1.available)); + + InSituRegion!(65_536, 1024*4) r2; + assert(r2.available <= 65_536); + a = r2.allocate(2001); + assert(a.length == 2001); +} + +private extern(C) void* sbrk(long); +private extern(C) int brk(shared void*); + +/** + +Allocator backed by $(D $(LINK2 https://en.wikipedia.org/wiki/Sbrk, sbrk)) +for Posix systems. Due to the fact that $(D sbrk) is not thread-safe +$(HTTP lifecs.likai.org/2010/02/sbrk-is-not-thread-safe.html, by design), +$(D SbrkRegion) uses a mutex internally. This implies +that uncontrolled calls to $(D brk) and $(D sbrk) may affect the workings of $(D +SbrkRegion) adversely. + +*/ +version (Posix) struct SbrkRegion(uint minAlign = platformAlignment) +{ + import core.sys.posix.pthread : pthread_mutex_init, pthread_mutex_destroy, + pthread_mutex_t, pthread_mutex_lock, pthread_mutex_unlock, + PTHREAD_MUTEX_INITIALIZER; + private static shared pthread_mutex_t sbrkMutex = PTHREAD_MUTEX_INITIALIZER; + import std.typecons : Ternary; + + static assert(minAlign.isGoodStaticAlignment); + static assert(size_t.sizeof == (void*).sizeof); + private shared void* _brkInitial, _brkCurrent; + + /** + Instance shared by all callers. + */ + static shared SbrkRegion instance; + + /** + Standard allocator primitives. + */ + enum uint alignment = minAlign; + + /// Ditto + void[] allocate(size_t bytes) shared + { + static if (minAlign > 1) + const rounded = bytes.roundUpToMultipleOf(alignment); + else + alias rounded = bytes; + pthread_mutex_lock(cast(pthread_mutex_t*) &sbrkMutex) == 0 || assert(0); + scope(exit) pthread_mutex_unlock(cast(pthread_mutex_t*) &sbrkMutex) == 0 + || assert(0); + // Assume sbrk returns the old break. Most online documentation confirms + // that, except for http://www.inf.udec.cl/~leo/Malloc_tutorial.pdf, + // which claims the returned value is not portable. + auto p = sbrk(rounded); + if (p == cast(void*) -1) + { + return null; + } + if (!_brkInitial) + { + _brkInitial = cast(shared) p; + assert(cast(size_t) _brkInitial % minAlign == 0, + "Too large alignment chosen for " ~ typeof(this).stringof); + } + _brkCurrent = cast(shared) (p + rounded); + return p[0 .. bytes]; + } + + /// Ditto + void[] alignedAllocate(size_t bytes, uint a) shared + { + pthread_mutex_lock(cast(pthread_mutex_t*) &sbrkMutex) == 0 || assert(0); + scope(exit) pthread_mutex_unlock(cast(pthread_mutex_t*) &sbrkMutex) == 0 + || assert(0); + if (!_brkInitial) + { + // This is one extra call, but it'll happen only once. + _brkInitial = cast(shared) sbrk(0); + assert(cast(size_t) _brkInitial % minAlign == 0, + "Too large alignment chosen for " ~ typeof(this).stringof); + (_brkInitial != cast(void*) -1) || assert(0); + _brkCurrent = _brkInitial; + } + immutable size_t delta = cast(shared void*) roundUpToMultipleOf( + cast(size_t) _brkCurrent, a) - _brkCurrent; + // Still must make sure the total size is aligned to the allocator's + // alignment. + immutable rounded = (bytes + delta).roundUpToMultipleOf(alignment); + + auto p = sbrk(rounded); + if (p == cast(void*) -1) + { + return null; + } + _brkCurrent = cast(shared) (p + rounded); + return p[delta .. delta + bytes]; + } + + /** + + The $(D expand) method may only succeed if the argument is the last block + allocated. In that case, $(D expand) attempts to push the break pointer to + the right. + + */ + bool expand(ref void[] b, size_t delta) shared + { + if (b is null) return delta == 0; + assert(_brkInitial && _brkCurrent); // otherwise where did b come from? + pthread_mutex_lock(cast(pthread_mutex_t*) &sbrkMutex) == 0 || assert(0); + scope(exit) pthread_mutex_unlock(cast(pthread_mutex_t*) &sbrkMutex) == 0 + || assert(0); + if (_brkCurrent != b.ptr + b.length) return false; + // Great, can expand the last block + static if (minAlign > 1) + const rounded = delta.roundUpToMultipleOf(alignment); + else + alias rounded = bytes; + auto p = sbrk(rounded); + if (p == cast(void*) -1) + { + return false; + } + _brkCurrent = cast(shared) (p + rounded); + b = b.ptr[0 .. b.length + delta]; + return true; + } + + /// Ditto + Ternary owns(void[] b) shared + { + // No need to lock here. + assert(!_brkCurrent || b.ptr + b.length <= _brkCurrent); + return Ternary(_brkInitial && b.ptr >= _brkInitial); + } + + /** + + The $(D deallocate) method only works (and returns $(D true)) on systems + that support reducing the break address (i.e. accept calls to $(D sbrk) + with negative offsets). OSX does not accept such. In addition the argument + must be the last block allocated. + + */ + bool deallocate(void[] b) shared + { + static if (minAlign > 1) + const rounded = b.length.roundUpToMultipleOf(alignment); + else + const rounded = b.length; + pthread_mutex_lock(cast(pthread_mutex_t*) &sbrkMutex) == 0 || assert(0); + scope(exit) pthread_mutex_unlock(cast(pthread_mutex_t*) &sbrkMutex) == 0 + || assert(0); + if (_brkCurrent != b.ptr + rounded) return false; + assert(b.ptr >= _brkInitial); + if (sbrk(-rounded) == cast(void*) -1) + return false; + _brkCurrent = cast(shared) b.ptr; + return true; + } + + /** + The $(D deallocateAll) method only works (and returns $(D true)) on systems + that support reducing the break address (i.e. accept calls to $(D sbrk) + with negative offsets). OSX does not accept such. + */ + bool deallocateAll() shared + { + pthread_mutex_lock(cast(pthread_mutex_t*) &sbrkMutex) == 0 || assert(0); + scope(exit) pthread_mutex_unlock(cast(pthread_mutex_t*) &sbrkMutex) == 0 + || assert(0); + return !_brkInitial || brk(_brkInitial) == 0; + } + + /// Standard allocator API. + Ternary empty() + { + // Also works when they're both null. + return Ternary(_brkCurrent == _brkInitial); + } +} + +version (Posix) @system unittest +{ + // Let's test the assumption that sbrk(n) returns the old address + const p1 = sbrk(0); + const p2 = sbrk(4096); + assert(p1 == p2); + const p3 = sbrk(0); + assert(p3 == p2 + 4096); + // Try to reset brk, but don't make a fuss if it doesn't work + sbrk(-4096); +} + +version (Posix) @system unittest +{ + import std.typecons : Ternary; + alias alloc = SbrkRegion!(8).instance; + auto a = alloc.alignedAllocate(2001, 4096); + assert(a.length == 2001); + auto b = alloc.allocate(2001); + assert(b.length == 2001); + assert(alloc.owns(a) == Ternary.yes); + assert(alloc.owns(b) == Ternary.yes); + // reducing the brk does not work on OSX + version (OSX) {} else + { + assert(alloc.deallocate(b)); + assert(alloc.deallocateAll); + } +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/scoped_allocator.d b/libphobos/src/std/experimental/allocator/building_blocks/scoped_allocator.d new file mode 100644 index 0000000..ff3261f --- /dev/null +++ b/libphobos/src/std/experimental/allocator/building_blocks/scoped_allocator.d @@ -0,0 +1,221 @@ +/// +module std.experimental.allocator.building_blocks.scoped_allocator; + +import std.experimental.allocator.common; + +/** + +$(D ScopedAllocator) delegates all allocation requests to $(D ParentAllocator). +When destroyed, the $(D ScopedAllocator) object automatically calls $(D +deallocate) for all memory allocated through its lifetime. (The $(D +deallocateAll) function is also implemented with the same semantics.) + +$(D deallocate) is also supported, which is where most implementation effort +and overhead of $(D ScopedAllocator) go. If $(D deallocate) is not needed, a +simpler design combining $(D AllocatorList) with $(D Region) is recommended. + +*/ +struct ScopedAllocator(ParentAllocator) +{ + @system unittest + { + testAllocator!(() => ScopedAllocator()); + } + + import std.experimental.allocator.building_blocks.affix_allocator + : AffixAllocator; + import std.traits : hasMember; + import std.typecons : Ternary; + + private struct Node + { + Node* prev; + Node* next; + size_t length; + } + + alias Allocator = AffixAllocator!(ParentAllocator, Node); + + // state + /** + If $(D ParentAllocator) is stateful, $(D parent) is a property giving access + to an $(D AffixAllocator!ParentAllocator). Otherwise, $(D parent) is an alias for `AffixAllocator!ParentAllocator.instance`. + */ + static if (stateSize!ParentAllocator) + { + Allocator parent; + } + else + { + alias parent = Allocator.instance; + } + private Node* root; + + /** + $(D ScopedAllocator) is not copyable. + */ + @disable this(this); + + /** + $(D ScopedAllocator)'s destructor releases all memory allocated during its + lifetime. + */ + ~this() + { + deallocateAll; + } + + /// Alignment offered + enum alignment = Allocator.alignment; + + /** + Forwards to $(D parent.goodAllocSize) (which accounts for the management + overhead). + */ + size_t goodAllocSize(size_t n) + { + return parent.goodAllocSize(n); + } + + /** + Allocates memory. For management it actually allocates extra memory from + the parent. + */ + void[] allocate(size_t n) + { + auto b = parent.allocate(n); + if (!b.ptr) return b; + Node* toInsert = & parent.prefix(b); + toInsert.prev = null; + toInsert.next = root; + toInsert.length = n; + assert(!root || !root.prev); + if (root) root.prev = toInsert; + root = toInsert; + return b; + } + + /** + Forwards to $(D parent.expand(b, delta)). + */ + static if (hasMember!(Allocator, "expand")) + bool expand(ref void[] b, size_t delta) + { + auto result = parent.expand(b, delta); + if (result && b.ptr) + { + parent.prefix(b).length = b.length; + } + return result; + } + + /** + Reallocates $(D b) to new size $(D s). + */ + bool reallocate(ref void[] b, size_t s) + { + // Remove from list + if (b.ptr) + { + Node* n = & parent.prefix(b); + if (n.prev) n.prev.next = n.next; + else root = n.next; + if (n.next) n.next.prev = n.prev; + } + auto result = parent.reallocate(b, s); + // Add back to list + if (b.ptr) + { + Node* n = & parent.prefix(b); + n.prev = null; + n.next = root; + n.length = s; + if (root) root.prev = n; + root = n; + } + return result; + } + + /** + Forwards to $(D parent.owns(b)). + */ + static if (hasMember!(Allocator, "owns")) + Ternary owns(void[] b) + { + return parent.owns(b); + } + + /** + Deallocates $(D b). + */ + static if (hasMember!(Allocator, "deallocate")) + bool deallocate(void[] b) + { + // Remove from list + if (b.ptr) + { + Node* n = & parent.prefix(b); + if (n.prev) n.prev.next = n.next; + else root = n.next; + if (n.next) n.next.prev = n.prev; + } + return parent.deallocate(b); + } + + /** + Deallocates all memory allocated. + */ + bool deallocateAll() + { + bool result = true; + for (auto n = root; n; ) + { + void* p = n + 1; + auto length = n.length; + n = n.next; + if (!parent.deallocate(p[0 .. length])) + result = false; + } + root = null; + return result; + } + + /** + Returns `Ternary.yes` if this allocator is not responsible for any memory, + `Ternary.no` otherwise. (Never returns `Ternary.unknown`.) + */ + Ternary empty() const + { + return Ternary(root is null); + } +} + +/// +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + import std.typecons : Ternary; + ScopedAllocator!Mallocator alloc; + assert(alloc.empty == Ternary.yes); + const b = alloc.allocate(10); + assert(b.length == 10); + assert(alloc.empty == Ternary.no); +} + +@system unittest +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + testAllocator!(() => ScopedAllocator!GCAllocator()); +} + +@system unittest // https://issues.dlang.org/show_bug.cgi?id=16046 +{ + import std.exception; + import std.experimental.allocator; + import std.experimental.allocator.mallocator; + ScopedAllocator!Mallocator alloc; + auto foo = alloc.make!int(1).enforce; + auto bar = alloc.make!int(2).enforce; + alloc.dispose(foo); + alloc.dispose(bar); // segfault here +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/segregator.d b/libphobos/src/std/experimental/allocator/building_blocks/segregator.d new file mode 100644 index 0000000..76ca6f4 --- /dev/null +++ b/libphobos/src/std/experimental/allocator/building_blocks/segregator.d @@ -0,0 +1,361 @@ +/// +module std.experimental.allocator.building_blocks.segregator; + +import std.experimental.allocator.common; + +/** +Dispatches allocations (and deallocations) between two allocators ($(D +SmallAllocator) and $(D LargeAllocator)) depending on the size allocated, as +follows. All allocations smaller than or equal to $(D threshold) will be +dispatched to $(D SmallAllocator). The others will go to $(D LargeAllocator). + +If both allocators are $(D shared), the $(D Segregator) will also offer $(D +shared) methods. +*/ +struct Segregator(size_t threshold, SmallAllocator, LargeAllocator) +{ + import std.algorithm.comparison : min; + import std.traits : hasMember; + import std.typecons : Ternary; + + static if (stateSize!SmallAllocator) private SmallAllocator _small; + else private alias _small = SmallAllocator.instance; + static if (stateSize!LargeAllocator) private LargeAllocator _large; + else private alias _large = LargeAllocator.instance; + + version (StdDdoc) + { + /** + The alignment offered is the minimum of the two allocators' alignment. + */ + enum uint alignment; + /** + This method is defined only if at least one of the allocators defines + it. The good allocation size is obtained from $(D SmallAllocator) if $(D + s <= threshold), or $(D LargeAllocator) otherwise. (If one of the + allocators does not define $(D goodAllocSize), the default + implementation in this module applies.) + */ + static size_t goodAllocSize(size_t s); + /** + The memory is obtained from $(D SmallAllocator) if $(D s <= threshold), + or $(D LargeAllocator) otherwise. + */ + void[] allocate(size_t); + /** + This method is defined if both allocators define it, and forwards to + $(D SmallAllocator) or $(D LargeAllocator) appropriately. + */ + void[] alignedAllocate(size_t, uint); + /** + This method is defined only if at least one of the allocators defines + it. If $(D SmallAllocator) defines $(D expand) and $(D b.length + + delta <= threshold), the call is forwarded to $(D SmallAllocator). If $(D + LargeAllocator) defines $(D expand) and $(D b.length > threshold), the + call is forwarded to $(D LargeAllocator). Otherwise, the call returns + $(D false). + */ + bool expand(ref void[] b, size_t delta); + /** + This method is defined only if at least one of the allocators defines + it. If $(D SmallAllocator) defines $(D reallocate) and $(D b.length <= + threshold && s <= threshold), the call is forwarded to $(D + SmallAllocator). If $(D LargeAllocator) defines $(D expand) and $(D + b.length > threshold && s > threshold), the call is forwarded to $(D + LargeAllocator). Otherwise, the call returns $(D false). + */ + bool reallocate(ref void[] b, size_t s); + /** + This method is defined only if at least one of the allocators defines + it, and work similarly to $(D reallocate). + */ + bool alignedReallocate(ref void[] b, size_t s); + /** + This method is defined only if both allocators define it. The call is + forwarded to $(D SmallAllocator) if $(D b.length <= threshold), or $(D + LargeAllocator) otherwise. + */ + Ternary owns(void[] b); + /** + This function is defined only if both allocators define it, and forwards + appropriately depending on $(D b.length). + */ + bool deallocate(void[] b); + /** + This function is defined only if both allocators define it, and calls + $(D deallocateAll) for them in turn. + */ + bool deallocateAll(); + /** + This function is defined only if both allocators define it, and returns + the conjunction of $(D empty) calls for the two. + */ + Ternary empty(); + } + + /** + Composite allocators involving nested instantiations of $(D Segregator) make + it difficult to access individual sub-allocators stored within. $(D + allocatorForSize) simplifies the task by supplying the allocator nested + inside a $(D Segregator) that is responsible for a specific size $(D s). + + Example: + ---- + alias A = Segregator!(300, + Segregator!(200, A1, A2), + A3); + A a; + static assert(typeof(a.allocatorForSize!10) == A1); + static assert(typeof(a.allocatorForSize!250) == A2); + static assert(typeof(a.allocatorForSize!301) == A3); + ---- + */ + ref auto allocatorForSize(size_t s)() + { + static if (s <= threshold) + static if (is(SmallAllocator == Segregator!(Args), Args...)) + return _small.allocatorForSize!s; + else return _small; + else + static if (is(LargeAllocator == Segregator!(Args), Args...)) + return _large.allocatorForSize!s; + else return _large; + } + + enum uint alignment = min(SmallAllocator.alignment, + LargeAllocator.alignment); + + private template Impl() + { + size_t goodAllocSize(size_t s) + { + return s <= threshold + ? _small.goodAllocSize(s) + : _large.goodAllocSize(s); + } + + void[] allocate(size_t s) + { + return s <= threshold ? _small.allocate(s) : _large.allocate(s); + } + + static if (hasMember!(SmallAllocator, "alignedAllocate") + && hasMember!(LargeAllocator, "alignedAllocate")) + void[] alignedAllocate(size_t s, uint a) + { + return s <= threshold + ? _small.alignedAllocate(s, a) + : _large.alignedAllocate(s, a); + } + + static if (hasMember!(SmallAllocator, "expand") + || hasMember!(LargeAllocator, "expand")) + bool expand(ref void[] b, size_t delta) + { + if (!delta) return true; + if (b.length + delta <= threshold) + { + // Old and new allocations handled by _small + static if (hasMember!(SmallAllocator, "expand")) + return _small.expand(b, delta); + else + return false; + } + if (b.length > threshold) + { + // Old and new allocations handled by _large + static if (hasMember!(LargeAllocator, "expand")) + return _large.expand(b, delta); + else + return false; + } + // Oops, cross-allocator transgression + return false; + } + + static if (hasMember!(SmallAllocator, "reallocate") + || hasMember!(LargeAllocator, "reallocate")) + bool reallocate(ref void[] b, size_t s) + { + static if (hasMember!(SmallAllocator, "reallocate")) + if (b.length <= threshold && s <= threshold) + { + // Old and new allocations handled by _small + return _small.reallocate(b, s); + } + static if (hasMember!(LargeAllocator, "reallocate")) + if (b.length > threshold && s > threshold) + { + // Old and new allocations handled by _large + return _large.reallocate(b, s); + } + // Cross-allocator transgression + return .reallocate(this, b, s); + } + + static if (hasMember!(SmallAllocator, "alignedReallocate") + || hasMember!(LargeAllocator, "alignedReallocate")) + bool reallocate(ref void[] b, size_t s) + { + static if (hasMember!(SmallAllocator, "alignedReallocate")) + if (b.length <= threshold && s <= threshold) + { + // Old and new allocations handled by _small + return _small.alignedReallocate(b, s); + } + static if (hasMember!(LargeAllocator, "alignedReallocate")) + if (b.length > threshold && s > threshold) + { + // Old and new allocations handled by _large + return _large.alignedReallocate(b, s); + } + // Cross-allocator transgression + return .alignedReallocate(this, b, s); + } + + static if (hasMember!(SmallAllocator, "owns") + && hasMember!(LargeAllocator, "owns")) + Ternary owns(void[] b) + { + return Ternary(b.length <= threshold + ? _small.owns(b) : _large.owns(b)); + } + + static if (hasMember!(SmallAllocator, "deallocate") + && hasMember!(LargeAllocator, "deallocate")) + bool deallocate(void[] data) + { + return data.length <= threshold + ? _small.deallocate(data) + : _large.deallocate(data); + } + + static if (hasMember!(SmallAllocator, "deallocateAll") + && hasMember!(LargeAllocator, "deallocateAll")) + bool deallocateAll() + { + // Use & insted of && to evaluate both + return _small.deallocateAll() & _large.deallocateAll(); + } + + static if (hasMember!(SmallAllocator, "empty") + && hasMember!(LargeAllocator, "empty")) + Ternary empty() + { + return _small.empty && _large.empty; + } + + static if (hasMember!(SmallAllocator, "resolveInternalPointer") + && hasMember!(LargeAllocator, "resolveInternalPointer")) + Ternary resolveInternalPointer(const void* p, ref void[] result) + { + Ternary r = _small.resolveInternalPointer(p, result); + return r == Ternary.no ? _large.resolveInternalPointer(p, result) : r; + } + } + + private enum sharedMethods = + !stateSize!SmallAllocator + && !stateSize!LargeAllocator + && is(typeof(SmallAllocator.instance) == shared) + && is(typeof(LargeAllocator.instance) == shared); + + static if (sharedMethods) + { + static shared Segregator instance; + shared { mixin Impl!(); } + } + else + { + static if (!stateSize!SmallAllocator && !stateSize!LargeAllocator) + static __gshared Segregator instance; + mixin Impl!(); + } +} + +/// +@system unittest +{ + import std.experimental.allocator.building_blocks.free_list : FreeList; + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.experimental.allocator.mallocator : Mallocator; + alias A = + Segregator!( + 1024 * 4, + Segregator!( + 128, FreeList!(Mallocator, 0, 128), + GCAllocator), + Segregator!( + 1024 * 1024, Mallocator, + GCAllocator) + ); + A a; + auto b = a.allocate(200); + assert(b.length == 200); + a.deallocate(b); +} + +/** +A $(D Segregator) with more than three arguments expands to a composition of +elemental $(D Segregator)s, as illustrated by the following example: + +---- +alias A = + Segregator!( + n1, A1, + n2, A2, + n3, A3, + A4 + ); +---- + +With this definition, allocation requests for $(D n1) bytes or less are directed +to $(D A1); requests between $(D n1 + 1) and $(D n2) bytes (inclusive) are +directed to $(D A2); requests between $(D n2 + 1) and $(D n3) bytes (inclusive) +are directed to $(D A3); and requests for more than $(D n3) bytes are directed +to $(D A4). If some particular range should not be handled, $(D NullAllocator) +may be used appropriately. + +*/ +template Segregator(Args...) +if (Args.length > 3) +{ + // Binary search + private enum cutPoint = ((Args.length - 2) / 4) * 2; + static if (cutPoint >= 2) + { + alias Segregator = .Segregator!( + Args[cutPoint], + .Segregator!(Args[0 .. cutPoint], Args[cutPoint + 1]), + .Segregator!(Args[cutPoint + 2 .. $]) + ); + } + else + { + // Favor small sizes + alias Segregator = .Segregator!( + Args[0], + Args[1], + .Segregator!(Args[2 .. $]) + ); + } +} + +/// +@system unittest +{ + import std.experimental.allocator.building_blocks.free_list : FreeList; + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.experimental.allocator.mallocator : Mallocator; + alias A = + Segregator!( + 128, FreeList!(Mallocator, 0, 128), + 1024 * 4, GCAllocator, + 1024 * 1024, Mallocator, + GCAllocator + ); + A a; + auto b = a.allocate(201); + assert(b.length == 201); + a.deallocate(b); +} diff --git a/libphobos/src/std/experimental/allocator/building_blocks/stats_collector.d b/libphobos/src/std/experimental/allocator/building_blocks/stats_collector.d new file mode 100644 index 0000000..aba084e --- /dev/null +++ b/libphobos/src/std/experimental/allocator/building_blocks/stats_collector.d @@ -0,0 +1,735 @@ +// Written in the D programming language. +/** +Allocator that collects useful statistics about allocations, both global and per +calling point. The statistics collected can be configured statically by choosing +combinations of `Options` appropriately. + +Example: +---- +import std.experimental.allocator.gc_allocator : GCAllocator; +import std.experimental.allocator.building_blocks.free_list : FreeList; +alias Allocator = StatsCollector!(GCAllocator, Options.bytesUsed); +---- +*/ +module std.experimental.allocator.building_blocks.stats_collector; + +import std.experimental.allocator.common; + +/** +_Options for $(D StatsCollector) defined below. Each enables during +compilation one specific counter, statistic, or other piece of information. +*/ +enum Options : ulong +{ + /** + Counts the number of calls to $(D owns). + */ + numOwns = 1u << 0, + /** + Counts the number of calls to $(D allocate). All calls are counted, + including requests for zero bytes or failed requests. + */ + numAllocate = 1u << 1, + /** + Counts the number of calls to $(D allocate) that succeeded, i.e. they + returned a block as large as requested. (N.B. requests for zero bytes count + as successful.) + */ + numAllocateOK = 1u << 2, + /** + Counts the number of calls to $(D expand), regardless of arguments or + result. + */ + numExpand = 1u << 3, + /** + Counts the number of calls to $(D expand) that resulted in a successful + expansion. + */ + numExpandOK = 1u << 4, + /** + Counts the number of calls to $(D reallocate), regardless of arguments or + result. + */ + numReallocate = 1u << 5, + /** + Counts the number of calls to $(D reallocate) that succeeded. + (Reallocations to zero bytes count as successful.) + */ + numReallocateOK = 1u << 6, + /** + Counts the number of calls to $(D reallocate) that resulted in an in-place + reallocation (no memory moved). If this number is close to the total number + of reallocations, that indicates the allocator finds room at the current + block's end in a large fraction of the cases, but also that internal + fragmentation may be high (the size of the unit of allocation is large + compared to the typical allocation size of the application). + */ + numReallocateInPlace = 1u << 7, + /** + Counts the number of calls to $(D deallocate). + */ + numDeallocate = 1u << 8, + /** + Counts the number of calls to $(D deallocateAll). + */ + numDeallocateAll = 1u << 9, + /** + Chooses all $(D numXxx) flags. + */ + numAll = (1u << 10) - 1, + /** + Tracks bytes currently allocated by this allocator. This number goes up + and down as memory is allocated and deallocated, and is zero if the + allocator currently has no active allocation. + */ + bytesUsed = 1u << 10, + /** + Tracks total cumulative bytes allocated by means of $(D allocate), + $(D expand), and $(D reallocate) (when resulting in an expansion). This + number always grows and indicates allocation traffic. To compute bytes + deallocated cumulatively, subtract $(D bytesUsed) from $(D bytesAllocated). + */ + bytesAllocated = 1u << 11, + /** + Tracks the sum of all $(D delta) values in calls of the form + $(D expand(b, delta)) that succeed (return $(D true)). + */ + bytesExpanded = 1u << 12, + /** + Tracks the sum of all $(D b.length - s) with $(D b.length > s) in calls of + the form $(D realloc(b, s)) that succeed (return $(D true)). In per-call + statistics, also unambiguously counts the bytes deallocated with + $(D deallocate). + */ + bytesContracted = 1u << 13, + /** + Tracks the sum of all bytes moved as a result of calls to $(D realloc) that + were unable to reallocate in place. A large number (relative to $(D + bytesAllocated)) indicates that the application should use larger + preallocations. + */ + bytesMoved = 1u << 14, + /** + Tracks the sum of all bytes NOT moved as result of calls to $(D realloc) + that managed to reallocate in place. A large number (relative to $(D + bytesAllocated)) indicates that the application is expansion-intensive and + is saving a good amount of moves. However, if this number is relatively + small and $(D bytesSlack) is high, it means the application is + overallocating for little benefit. + */ + bytesNotMoved = 1u << 15, + /** + Measures the sum of extra bytes allocated beyond the bytes requested, i.e. + the $(HTTP goo.gl/YoKffF, internal fragmentation). This is the current + effective number of slack bytes, and it goes up and down with time. + */ + bytesSlack = 1u << 16, + /** + Measures the maximum bytes allocated over the time. This is useful for + dimensioning allocators. + */ + bytesHighTide = 1u << 17, + /** + Chooses all $(D byteXxx) flags. + */ + bytesAll = ((1u << 18) - 1) & ~numAll, + /** + Combines all flags above. + */ + all = (1u << 18) - 1 +} + +/** + +Allocator that collects extra data about allocations. Since each piece of +information adds size and time overhead, statistics can be individually enabled +or disabled through compile-time $(D flags). + +All stats of the form $(D numXxx) record counts of events occurring, such as +calls to functions and specific results. The stats of the form $(D bytesXxx) +collect cumulative sizes. + +In addition, the data $(D callerSize), $(D callerModule), $(D callerFile), $(D +callerLine), and $(D callerTime) is associated with each specific allocation. +This data prefixes each allocation. + +*/ +struct StatsCollector(Allocator, ulong flags = Options.all, + ulong perCallFlags = 0) +{ +private: + import std.traits : hasMember, Signed; + import std.typecons : Ternary; + + static string define(string type, string[] names...) + { + string result; + foreach (v; names) + result ~= "static if (flags & Options."~v~") {" + ~ "private "~type~" _"~v~";" + ~ "public const("~type~") "~v~"() const { return _"~v~"; }" + ~ "}"; + return result; + } + + void add(string counter)(Signed!size_t n) + { + mixin("static if (flags & Options." ~ counter + ~ ") _" ~ counter ~ " += n;"); + static if (counter == "bytesUsed" && (flags & Options.bytesHighTide)) + { + if (bytesHighTide < bytesUsed ) _bytesHighTide = bytesUsed; + } + } + + void up(string counter)() { add!counter(1); } + void down(string counter)() { add!counter(-1); } + + version (StdDdoc) + { + /** + Read-only properties enabled by the homonym $(D flags) chosen by the + user. + + Example: + ---- + StatsCollector!(Mallocator, + Options.bytesUsed | Options.bytesAllocated) a; + auto d1 = a.allocate(10); + auto d2 = a.allocate(11); + a.deallocate(d1); + assert(a.bytesAllocated == 21); + assert(a.bytesUsed == 11); + a.deallocate(d2); + assert(a.bytesAllocated == 21); + assert(a.bytesUsed == 0); + ---- + */ + @property ulong numOwns() const; + /// Ditto + @property ulong numAllocate() const; + /// Ditto + @property ulong numAllocateOK() const; + /// Ditto + @property ulong numExpand() const; + /// Ditto + @property ulong numExpandOK() const; + /// Ditto + @property ulong numReallocate() const; + /// Ditto + @property ulong numReallocateOK() const; + /// Ditto + @property ulong numReallocateInPlace() const; + /// Ditto + @property ulong numDeallocate() const; + /// Ditto + @property ulong numDeallocateAll() const; + /// Ditto + @property ulong bytesUsed() const; + /// Ditto + @property ulong bytesAllocated() const; + /// Ditto + @property ulong bytesExpanded() const; + /// Ditto + @property ulong bytesContracted() const; + /// Ditto + @property ulong bytesMoved() const; + /// Ditto + @property ulong bytesNotMoved() const; + /// Ditto + @property ulong bytesSlack() const; + /// Ditto + @property ulong bytesHighTide() const; + } + +public: + /** + The parent allocator is publicly accessible either as a direct member if it + holds state, or as an alias to `Allocator.instance` otherwise. One may use + it for making calls that won't count toward statistics collection. + */ + static if (stateSize!Allocator) Allocator parent; + else alias parent = Allocator.instance; + +private: + // Per-allocator state + mixin(define("ulong", + "numOwns", + "numAllocate", + "numAllocateOK", + "numExpand", + "numExpandOK", + "numReallocate", + "numReallocateOK", + "numReallocateInPlace", + "numDeallocate", + "numDeallocateAll", + "bytesUsed", + "bytesAllocated", + "bytesExpanded", + "bytesContracted", + "bytesMoved", + "bytesNotMoved", + "bytesSlack", + "bytesHighTide", + )); + +public: + + /// Alignment offered is equal to $(D Allocator.alignment). + alias alignment = Allocator.alignment; + + /** + Increments $(D numOwns) (per instance and and per call) and forwards to $(D + parent.owns(b)). + */ + static if (hasMember!(Allocator, "owns")) + { + static if ((perCallFlags & Options.numOwns) == 0) + Ternary owns(void[] b) + { return ownsImpl(b); } + else + Ternary owns(string f = __FILE, uint n = line)(void[] b) + { return ownsImpl!(f, n)(b); } + } + + private Ternary ownsImpl(string f = null, uint n = 0)(void[] b) + { + up!"numOwns"; + addPerCall!(f, n, "numOwns")(1); + return parent.owns(b); + } + + /** + Forwards to $(D parent.allocate). Affects per instance: $(D numAllocate), + $(D bytesUsed), $(D bytesAllocated), $(D bytesSlack), $(D numAllocateOK), + and $(D bytesHighTide). Affects per call: $(D numAllocate), $(D + numAllocateOK), and $(D bytesAllocated). + */ + static if (!(perCallFlags + & (Options.numAllocate | Options.numAllocateOK + | Options.bytesAllocated))) + { + void[] allocate(size_t n) + { return allocateImpl(n); } + } + else + { + void[] allocate(string f = __FILE__, ulong n = __LINE__) + (size_t bytes) + { return allocateImpl!(f, n)(bytes); } + } + + private void[] allocateImpl(string f = null, ulong n = 0)(size_t bytes) + { + auto result = parent.allocate(bytes); + add!"bytesUsed"(result.length); + add!"bytesAllocated"(result.length); + immutable slack = this.goodAllocSize(result.length) - result.length; + add!"bytesSlack"(slack); + up!"numAllocate"; + add!"numAllocateOK"(result.length == bytes); // allocating 0 bytes is OK + addPerCall!(f, n, "numAllocate", "numAllocateOK", "bytesAllocated") + (1, result.length == bytes, result.length); + return result; + } + + /** + Defined whether or not $(D Allocator.expand) is defined. Affects + per instance: $(D numExpand), $(D numExpandOK), $(D bytesExpanded), + $(D bytesSlack), $(D bytesAllocated), and $(D bytesUsed). Affects per call: + $(D numExpand), $(D numExpandOK), $(D bytesExpanded), and + $(D bytesAllocated). + */ + static if (!(perCallFlags + & (Options.numExpand | Options.numExpandOK | Options.bytesExpanded))) + { + bool expand(ref void[] b, size_t delta) + { return expandImpl(b, delta); } + } + else + { + bool expand(string f = __FILE__, uint n = __LINE__) + (ref void[] b, size_t delta) + { return expandImpl!(f, n)(b, delta); } + } + + private bool expandImpl(string f = null, uint n = 0)(ref void[] b, size_t s) + { + up!"numExpand"; + Signed!size_t slack = 0; + static if (!hasMember!(Allocator, "expand")) + { + auto result = s == 0; + } + else + { + immutable bytesSlackB4 = this.goodAllocSize(b.length) - b.length; + auto result = parent.expand(b, s); + if (result) + { + up!"numExpandOK"; + add!"bytesUsed"(s); + add!"bytesAllocated"(s); + add!"bytesExpanded"(s); + slack = Signed!size_t(this.goodAllocSize(b.length) - b.length + - bytesSlackB4); + add!"bytesSlack"(slack); + } + } + immutable xtra = result ? s : 0; + addPerCall!(f, n, "numExpand", "numExpandOK", "bytesExpanded", + "bytesAllocated") + (1, result, xtra, xtra); + return result; + } + + /** + Defined whether or not $(D Allocator.reallocate) is defined. Affects + per instance: $(D numReallocate), $(D numReallocateOK), $(D + numReallocateInPlace), $(D bytesNotMoved), $(D bytesAllocated), $(D + bytesSlack), $(D bytesExpanded), and $(D bytesContracted). Affects per call: + $(D numReallocate), $(D numReallocateOK), $(D numReallocateInPlace), + $(D bytesNotMoved), $(D bytesExpanded), $(D bytesContracted), and + $(D bytesMoved). + */ + static if (!(perCallFlags + & (Options.numReallocate | Options.numReallocateOK + | Options.numReallocateInPlace | Options.bytesNotMoved + | Options.bytesExpanded | Options.bytesContracted + | Options.bytesMoved))) + { + bool reallocate(ref void[] b, size_t s) + { return reallocateImpl(b, s); } + } + else + { + bool reallocate(string f = __FILE__, ulong n = __LINE__) + (ref void[] b, size_t s) + { return reallocateImpl!(f, n)(b, s); } + } + + private bool reallocateImpl(string f = null, uint n = 0) + (ref void[] b, size_t s) + { + up!"numReallocate"; + const bytesSlackB4 = this.goodAllocSize(b.length) - b.length; + const oldB = b.ptr; + const oldLength = b.length; + + const result = parent.reallocate(b, s); + + Signed!size_t slack = 0; + bool wasInPlace = false; + Signed!size_t delta = 0; + + if (result) + { + up!"numReallocateOK"; + slack = (this.goodAllocSize(b.length) - b.length) - bytesSlackB4; + add!"bytesSlack"(slack); + add!"bytesUsed"(Signed!size_t(b.length - oldLength)); + if (oldB == b.ptr) + { + // This was an in-place reallocation, yay + wasInPlace = true; + up!"numReallocateInPlace"; + add!"bytesNotMoved"(oldLength); + delta = b.length - oldLength; + if (delta >= 0) + { + // Expansion + add!"bytesAllocated"(delta); + add!"bytesExpanded"(delta); + } + else + { + // Contraction + add!"bytesContracted"(-delta); + } + } + else + { + // This was a allocate-move-deallocate cycle + add!"bytesAllocated"(b.length); + add!"bytesMoved"(oldLength); + } + } + addPerCall!(f, n, "numReallocate", "numReallocateOK", + "numReallocateInPlace", "bytesNotMoved", + "bytesExpanded", "bytesContracted", "bytesMoved") + (1, result, wasInPlace, wasInPlace ? oldLength : 0, + delta >= 0 ? delta : 0, delta < 0 ? -delta : 0, + wasInPlace ? 0 : oldLength); + return result; + } + + /** + Defined whether or not $(D Allocator.deallocate) is defined. Affects + per instance: $(D numDeallocate), $(D bytesUsed), and $(D bytesSlack). + Affects per call: $(D numDeallocate) and $(D bytesContracted). + */ + static if (!(perCallFlags & + (Options.numDeallocate | Options.bytesContracted))) + bool deallocate(void[] b) + { return deallocateImpl(b); } + else + bool deallocate(string f = __FILE__, uint n = __LINE__)(void[] b) + { return deallocateImpl!(f, n)(b); } + + private bool deallocateImpl(string f = null, uint n = 0)(void[] b) + { + up!"numDeallocate"; + add!"bytesUsed"(-Signed!size_t(b.length)); + add!"bytesSlack"(-(this.goodAllocSize(b.length) - b.length)); + addPerCall!(f, n, "numDeallocate", "bytesContracted")(1, b.length); + static if (hasMember!(Allocator, "deallocate")) + return parent.deallocate(b); + else + return false; + } + + static if (hasMember!(Allocator, "deallocateAll")) + { + /** + Defined only if $(D Allocator.deallocateAll) is defined. Affects + per instance and per call $(D numDeallocateAll). + */ + static if (!(perCallFlags & Options.numDeallocateAll)) + bool deallocateAll() + { return deallocateAllImpl(); } + else + bool deallocateAll(string f = __FILE__, uint n = __LINE__)() + { return deallocateAllImpl!(f, n)(); } + + private bool deallocateAllImpl(string f = null, uint n = 0)() + { + up!"numDeallocateAll"; + addPerCall!(f, n, "numDeallocateAll")(1); + static if ((flags & Options.bytesUsed)) + _bytesUsed = 0; + return parent.deallocateAll(); + } + } + + /** + Defined only if $(D Options.bytesUsed) is defined. Returns $(D bytesUsed == + 0). + */ + static if (flags & Options.bytesUsed) + Ternary empty() + { + return Ternary(_bytesUsed == 0); + } + + /** + Reports per instance statistics to $(D output) (e.g. $(D stdout)). The + format is simple: one kind and value per line, separated by a colon, e.g. + $(D bytesAllocated:7395404) + */ + void reportStatistics(R)(auto ref R output) + { + import std.conv : to; + import std.traits : EnumMembers; + foreach (e; EnumMembers!Options) + { + static if ((flags & e) && e != Options.numAll + && e != Options.bytesAll && e != Options.all) + output.write(e.to!string, ":", mixin(e.to!string), '\n'); + } + } + + static if (perCallFlags) + { + /** + Defined if $(D perCallFlags) is nonzero. + */ + struct PerCallStatistics + { + /// The file and line of the call. + string file; + /// Ditto + uint line; + /// The options corresponding to the statistics collected. + Options[] opts; + /// The values of the statistics. Has the same length as $(D opts). + ulong[] values; + // Next in the chain. + private PerCallStatistics* next; + + /** + Format to a string such as: + $(D mymodule.d(655): [numAllocate:21, numAllocateOK:21, bytesAllocated:324202]). + */ + string toString() const + { + import std.conv : text, to; + auto result = text(file, "(", line, "): ["); + foreach (i, opt; opts) + { + if (i) result ~= ", "; + result ~= opt.to!string; + result ~= ':'; + result ~= values[i].to!string; + } + return result ~= "]"; + } + } + private static PerCallStatistics* root; + + /** + Defined if $(D perCallFlags) is nonzero. Iterates all monitored + file/line instances. The order of iteration is not meaningful (items + are inserted at the front of a list upon the first call), so + preprocessing the statistics after collection might be appropriate. + */ + static auto byFileLine() + { + static struct Voldemort + { + PerCallStatistics* current; + bool empty() { return !current; } + ref PerCallStatistics front() { return *current; } + void popFront() { current = current.next; } + auto save() { return this; } + } + return Voldemort(root); + } + + /** + Defined if $(D perCallFlags) is nonzero. Outputs (e.g. to a $(D File)) + a simple report of the collected per-call statistics. + */ + static void reportPerCallStatistics(R)(auto ref R output) + { + output.write("Stats for: ", StatsCollector.stringof, '\n'); + foreach (ref stat; byFileLine) + { + output.write(stat, '\n'); + } + } + + private PerCallStatistics* statsAt(string f, uint n, opts...)() + { + import std.array : array; + import std.range : repeat; + + static PerCallStatistics s = { f, n, [ opts ], + repeat(0UL, opts.length).array }; + static bool inserted; + + if (!inserted) + { + // Insert as root + s.next = root; + root = &s; + inserted = true; + } + return &s; + } + + private void addPerCall(string f, uint n, names...)(ulong[] values...) + { + import std.array : join; + enum uint mask = mixin("Options."~[names].join("|Options.")); + static if (perCallFlags & mask) + { + // Per allocation info + auto ps = mixin("statsAt!(f, n," + ~ "Options."~[names].join(", Options.") + ~")"); + foreach (i; 0 .. names.length) + { + ps.values[i] += values[i]; + } + } + } + } + else + { + private void addPerCall(string f, uint n, names...)(ulong[]...) + { + } + } +} + +/// +@system unittest +{ + import std.experimental.allocator.building_blocks.free_list : FreeList; + import std.experimental.allocator.gc_allocator : GCAllocator; + alias Allocator = StatsCollector!(GCAllocator, Options.all, Options.all); + + Allocator alloc; + auto b = alloc.allocate(10); + alloc.reallocate(b, 20); + alloc.deallocate(b); + + import std.file : deleteme, remove; + import std.range : walkLength; + import std.stdio : File; + + auto f = deleteme ~ "-dlang.std.experimental.allocator.stats_collector.txt"; + scope(exit) remove(f); + Allocator.reportPerCallStatistics(File(f, "w")); + alloc.reportStatistics(File(f, "a")); + assert(File(f).byLine.walkLength == 22); +} + +@system unittest +{ + void test(Allocator)() + { + import std.range : walkLength; + import std.stdio : writeln; + Allocator a; + auto b1 = a.allocate(100); + assert(a.numAllocate == 1); + assert(a.expand(b1, 0)); + assert(a.reallocate(b1, b1.length + 1)); + auto b2 = a.allocate(101); + assert(a.numAllocate == 2); + assert(a.bytesAllocated == 202); + assert(a.bytesUsed == 202); + auto b3 = a.allocate(202); + assert(a.numAllocate == 3); + assert(a.bytesAllocated == 404); + + a.deallocate(b2); + assert(a.numDeallocate == 1); + a.deallocate(b1); + assert(a.numDeallocate == 2); + a.deallocate(b3); + assert(a.numDeallocate == 3); + assert(a.numAllocate == a.numDeallocate); + assert(a.bytesUsed == 0); + } + + import std.experimental.allocator.building_blocks.free_list : FreeList; + import std.experimental.allocator.gc_allocator : GCAllocator; + test!(StatsCollector!(GCAllocator, Options.all, Options.all)); + test!(StatsCollector!(FreeList!(GCAllocator, 128), Options.all, + Options.all)); +} + +@system unittest +{ + void test(Allocator)() + { + import std.range : walkLength; + import std.stdio : writeln; + Allocator a; + auto b1 = a.allocate(100); + assert(a.expand(b1, 0)); + assert(a.reallocate(b1, b1.length + 1)); + auto b2 = a.allocate(101); + auto b3 = a.allocate(202); + + a.deallocate(b2); + a.deallocate(b1); + a.deallocate(b3); + } + import std.experimental.allocator.building_blocks.free_list : FreeList; + import std.experimental.allocator.gc_allocator : GCAllocator; + test!(StatsCollector!(GCAllocator, 0, 0)); +} diff --git a/libphobos/src/std/experimental/allocator/common.d b/libphobos/src/std/experimental/allocator/common.d new file mode 100644 index 0000000..0eec0d3 --- /dev/null +++ b/libphobos/src/std/experimental/allocator/common.d @@ -0,0 +1,683 @@ +/** +Utility and ancillary artifacts of `std.experimental.allocator`. This module +shouldn't be used directly; its functionality will be migrated into more +appropriate parts of `std`. + +Authors: $(HTTP erdani.com, Andrei Alexandrescu), Timon Gehr (`Ternary`) +*/ +module std.experimental.allocator.common; +import std.algorithm.comparison, std.traits; + +/** +Returns the size in bytes of the state that needs to be allocated to hold an +object of type $(D T). $(D stateSize!T) is zero for $(D struct)s that are not +nested and have no nonstatic member variables. + */ +template stateSize(T) +{ + static if (is(T == class) || is(T == interface)) + enum stateSize = __traits(classInstanceSize, T); + else static if (is(T == struct) || is(T == union)) + enum stateSize = Fields!T.length || isNested!T ? T.sizeof : 0; + else static if (is(T == void)) + enum size_t stateSize = 0; + else + enum stateSize = T.sizeof; +} + +@safe @nogc nothrow pure +unittest +{ + static assert(stateSize!void == 0); + struct A {} + static assert(stateSize!A == 0); + struct B { int x; } + static assert(stateSize!B == 4); + interface I1 {} + //static assert(stateSize!I1 == 2 * size_t.sizeof); + class C1 {} + static assert(stateSize!C1 == 3 * size_t.sizeof); + class C2 { char c; } + static assert(stateSize!C2 == 4 * size_t.sizeof); + static class C3 { char c; } + static assert(stateSize!C3 == 2 * size_t.sizeof + char.sizeof); +} + +/** +Returns `true` if the `Allocator` has the alignment known at compile time; +otherwise it returns `false`. + */ +template hasStaticallyKnownAlignment(Allocator) +{ + enum hasStaticallyKnownAlignment = __traits(compiles, + {enum x = Allocator.alignment;}); +} + +/** +$(D chooseAtRuntime) is a compile-time constant of type $(D size_t) that several +parameterized structures in this module recognize to mean deferral to runtime of +the exact value. For example, $(D BitmappedBlock!(Allocator, 4096)) (described in +detail below) defines a block allocator with block size of 4096 bytes, whereas +$(D BitmappedBlock!(Allocator, chooseAtRuntime)) defines a block allocator that has a +field storing the block size, initialized by the user. +*/ +enum chooseAtRuntime = size_t.max - 1; + +/** +$(D unbounded) is a compile-time constant of type $(D size_t) that several +parameterized structures in this module recognize to mean "infinite" bounds for +the parameter. For example, $(D Freelist) (described in detail below) accepts a +$(D maxNodes) parameter limiting the number of freelist items. If $(D unbounded) +is passed for $(D maxNodes), then there is no limit and no checking for the +number of nodes. +*/ +enum unbounded = size_t.max; + +/** +The alignment that is guaranteed to accommodate any D object allocation on the +current platform. +*/ +enum uint platformAlignment = std.algorithm.comparison.max(double.alignof, real.alignof); + +/** +The default good size allocation is deduced as $(D n) rounded up to the +allocator's alignment. +*/ +size_t goodAllocSize(A)(auto ref A a, size_t n) +{ + return n.roundUpToMultipleOf(a.alignment); +} + +/** +Returns s rounded up to a multiple of base. +*/ +@safe @nogc nothrow pure +package size_t roundUpToMultipleOf(size_t s, uint base) +{ + assert(base); + auto rem = s % base; + return rem ? s + base - rem : s; +} + +@safe @nogc nothrow pure +unittest +{ + assert(10.roundUpToMultipleOf(11) == 11); + assert(11.roundUpToMultipleOf(11) == 11); + assert(12.roundUpToMultipleOf(11) == 22); + assert(118.roundUpToMultipleOf(11) == 121); +} + +/** +Returns `n` rounded up to a multiple of alignment, which must be a power of 2. +*/ +@safe @nogc nothrow pure +package size_t roundUpToAlignment(size_t n, uint alignment) +{ + import std.math : isPowerOf2; + assert(alignment.isPowerOf2); + immutable uint slack = cast(uint) n & (alignment - 1); + const result = slack + ? n + alignment - slack + : n; + assert(result >= n); + return result; +} + +@safe @nogc nothrow pure +unittest +{ + assert(10.roundUpToAlignment(4) == 12); + assert(11.roundUpToAlignment(2) == 12); + assert(12.roundUpToAlignment(8) == 16); + assert(118.roundUpToAlignment(64) == 128); +} + +/** +Returns `n` rounded down to a multiple of alignment, which must be a power of 2. +*/ +@safe @nogc nothrow pure +package size_t roundDownToAlignment(size_t n, uint alignment) +{ + import std.math : isPowerOf2; + assert(alignment.isPowerOf2); + return n & ~size_t(alignment - 1); +} + +@safe @nogc nothrow pure +unittest +{ + assert(10.roundDownToAlignment(4) == 8); + assert(11.roundDownToAlignment(2) == 10); + assert(12.roundDownToAlignment(8) == 8); + assert(63.roundDownToAlignment(64) == 0); +} + +/** +Advances the beginning of `b` to start at alignment `a`. The resulting buffer +may therefore be shorter. Returns the adjusted buffer, or null if obtaining a +non-empty buffer is impossible. +*/ +@nogc nothrow pure +package void[] roundUpToAlignment(void[] b, uint a) +{ + auto e = b.ptr + b.length; + auto p = cast(void*) roundUpToAlignment(cast(size_t) b.ptr, a); + if (e <= p) return null; + return p[0 .. e - p]; +} + +@nogc nothrow pure +@system unittest +{ + void[] empty; + assert(roundUpToAlignment(empty, 4) == null); + char[128] buf; + // At least one pointer inside buf is 128-aligned + assert(roundUpToAlignment(buf, 128) !is null); +} + +/** +Like `a / b` but rounds the result up, not down. +*/ +@safe @nogc nothrow pure +package size_t divideRoundUp(size_t a, size_t b) +{ + assert(b); + return (a + b - 1) / b; +} + +/** +Returns `s` rounded up to a multiple of `base`. +*/ +@nogc nothrow pure +package void[] roundStartToMultipleOf(void[] s, uint base) +{ + assert(base); + auto p = cast(void*) roundUpToMultipleOf( + cast(size_t) s.ptr, base); + auto end = s.ptr + s.length; + return p[0 .. end - p]; +} + +nothrow pure +@system unittest +{ + void[] p; + assert(roundStartToMultipleOf(p, 16) is null); + p = new ulong[10]; + assert(roundStartToMultipleOf(p, 16) is p); +} + +/** +Returns $(D s) rounded up to the nearest power of 2. +*/ +@safe @nogc nothrow pure +package size_t roundUpToPowerOf2(size_t s) +{ + import std.meta : AliasSeq; + assert(s <= (size_t.max >> 1) + 1); + --s; + static if (size_t.sizeof == 4) + alias Shifts = AliasSeq!(1, 2, 4, 8, 16); + else + alias Shifts = AliasSeq!(1, 2, 4, 8, 16, 32); + foreach (i; Shifts) + { + s |= s >> i; + } + return s + 1; +} + +@safe @nogc nothrow pure +unittest +{ + assert(0.roundUpToPowerOf2 == 0); + assert(1.roundUpToPowerOf2 == 1); + assert(2.roundUpToPowerOf2 == 2); + assert(3.roundUpToPowerOf2 == 4); + assert(7.roundUpToPowerOf2 == 8); + assert(8.roundUpToPowerOf2 == 8); + assert(10.roundUpToPowerOf2 == 16); + assert(11.roundUpToPowerOf2 == 16); + assert(12.roundUpToPowerOf2 == 16); + assert(118.roundUpToPowerOf2 == 128); + assert((size_t.max >> 1).roundUpToPowerOf2 == (size_t.max >> 1) + 1); + assert(((size_t.max >> 1) + 1).roundUpToPowerOf2 == (size_t.max >> 1) + 1); +} + +/** +Returns the number of trailing zeros of $(D x). +*/ +@safe @nogc nothrow pure +package uint trailingZeros(ulong x) +{ + uint result; + while (result < 64 && !(x & (1UL << result))) + { + ++result; + } + return result; +} + +@safe @nogc nothrow pure +unittest +{ + assert(trailingZeros(0) == 64); + assert(trailingZeros(1) == 0); + assert(trailingZeros(2) == 1); + assert(trailingZeros(3) == 0); + assert(trailingZeros(4) == 2); +} + +/** +Returns `true` if `ptr` is aligned at `alignment`. +*/ +@nogc nothrow pure +package bool alignedAt(T)(T* ptr, uint alignment) +{ + return cast(size_t) ptr % alignment == 0; +} + +/** +Returns the effective alignment of `ptr`, i.e. the largest power of two that is +a divisor of `ptr`. +*/ +@nogc nothrow pure +package uint effectiveAlignment(void* ptr) +{ + return 1U << trailingZeros(cast(size_t) ptr); +} + +@nogc nothrow pure +@system unittest +{ + int x; + assert(effectiveAlignment(&x) >= int.alignof); +} + +/** +Aligns a pointer down to a specified alignment. The resulting pointer is less +than or equal to the given pointer. +*/ +@nogc nothrow pure +package void* alignDownTo(void* ptr, uint alignment) +{ + import std.math : isPowerOf2; + assert(alignment.isPowerOf2); + return cast(void*) (cast(size_t) ptr & ~(alignment - 1UL)); +} + +/** +Aligns a pointer up to a specified alignment. The resulting pointer is greater +than or equal to the given pointer. +*/ +@nogc nothrow pure +package void* alignUpTo(void* ptr, uint alignment) +{ + import std.math : isPowerOf2; + assert(alignment.isPowerOf2); + immutable uint slack = cast(size_t) ptr & (alignment - 1U); + return slack ? ptr + alignment - slack : ptr; +} + +@safe @nogc nothrow pure +package bool isGoodStaticAlignment(uint x) +{ + import std.math : isPowerOf2; + return x.isPowerOf2; +} + +@safe @nogc nothrow pure +package bool isGoodDynamicAlignment(uint x) +{ + import std.math : isPowerOf2; + return x.isPowerOf2 && x >= (void*).sizeof; +} + +/** +The default $(D reallocate) function first attempts to use $(D expand). If $(D +Allocator.expand) is not defined or returns $(D false), $(D reallocate) +allocates a new block of memory of appropriate size and copies data from the old +block to the new block. Finally, if $(D Allocator) defines $(D deallocate), $(D +reallocate) uses it to free the old memory block. + +$(D reallocate) does not attempt to use $(D Allocator.reallocate) even if +defined. This is deliberate so allocators may use it internally within their own +implementation of $(D reallocate). + +*/ +bool reallocate(Allocator)(ref Allocator a, ref void[] b, size_t s) +{ + if (b.length == s) return true; + static if (hasMember!(Allocator, "expand")) + { + if (b.length <= s && a.expand(b, s - b.length)) return true; + } + auto newB = a.allocate(s); + if (newB.length != s) return false; + if (newB.length <= b.length) newB[] = b[0 .. newB.length]; + else newB[0 .. b.length] = b[]; + static if (hasMember!(Allocator, "deallocate")) + a.deallocate(b); + b = newB; + return true; +} + +/** + +The default $(D alignedReallocate) function first attempts to use $(D expand). +If $(D Allocator.expand) is not defined or returns $(D false), $(D +alignedReallocate) allocates a new block of memory of appropriate size and +copies data from the old block to the new block. Finally, if $(D Allocator) +defines $(D deallocate), $(D alignedReallocate) uses it to free the old memory +block. + +$(D alignedReallocate) does not attempt to use $(D Allocator.reallocate) even if +defined. This is deliberate so allocators may use it internally within their own +implementation of $(D reallocate). + +*/ +bool alignedReallocate(Allocator)(ref Allocator alloc, + ref void[] b, size_t s, uint a) +{ + static if (hasMember!(Allocator, "expand")) + { + if (b.length <= s && b.ptr.alignedAt(a) + && alloc.expand(b, s - b.length)) return true; + } + else + { + if (b.length == s) return true; + } + auto newB = alloc.alignedAllocate(s, a); + if (newB.length <= b.length) newB[] = b[0 .. newB.length]; + else newB[0 .. b.length] = b[]; + static if (hasMember!(Allocator, "deallocate")) + alloc.deallocate(b); + b = newB; + return true; +} + +/** +Forwards each of the methods in `funs` (if defined) to `member`. +*/ +/*package*/ string forwardToMember(string member, string[] funs...) +{ + string result = " import std.traits : hasMember, Parameters;\n"; + foreach (fun; funs) + { + result ~= " + static if (hasMember!(typeof("~member~"), `"~fun~"`)) + auto ref "~fun~"(Parameters!(typeof("~member~"."~fun~")) args) + { + return "~member~"."~fun~"(args); + }\n"; + } + return result; +} + +version (unittest) +{ + import std.experimental.allocator : IAllocator, ISharedAllocator; + + package void testAllocator(alias make)() + { + import std.conv : text; + import std.math : isPowerOf2; + import std.stdio : writeln, stderr; + import std.typecons : Ternary; + alias A = typeof(make()); + scope(failure) stderr.writeln("testAllocator failed for ", A.stringof); + + auto a = make(); + + // Test alignment + static assert(A.alignment.isPowerOf2); + + // Test goodAllocSize + assert(a.goodAllocSize(1) >= A.alignment, + text(a.goodAllocSize(1), " < ", A.alignment)); + assert(a.goodAllocSize(11) >= 11.roundUpToMultipleOf(A.alignment)); + assert(a.goodAllocSize(111) >= 111.roundUpToMultipleOf(A.alignment)); + + // Test allocate + assert(a.allocate(0) is null); + + auto b1 = a.allocate(1); + assert(b1.length == 1); + auto b2 = a.allocate(2); + assert(b2.length == 2); + assert(b2.ptr + b2.length <= b1.ptr || b1.ptr + b1.length <= b2.ptr); + + // Test alignedAllocate + static if (hasMember!(A, "alignedAllocate")) + {{ + auto b3 = a.alignedAllocate(1, 256); + assert(b3.length <= 1); + assert(b3.ptr.alignedAt(256)); + assert(a.alignedReallocate(b3, 2, 512)); + assert(b3.ptr.alignedAt(512)); + static if (hasMember!(A, "alignedDeallocate")) + { + a.alignedDeallocate(b3); + } + }} + else + { + static assert(!hasMember!(A, "alignedDeallocate")); + // This seems to be a bug in the compiler: + //static assert(!hasMember!(A, "alignedReallocate"), A.stringof); + } + + static if (hasMember!(A, "allocateAll")) + {{ + auto aa = make(); + if (aa.allocateAll().ptr) + { + // Can't get any more memory + assert(!aa.allocate(1).ptr); + } + auto ab = make(); + const b4 = ab.allocateAll(); + assert(b4.length); + // Can't get any more memory + assert(!ab.allocate(1).ptr); + }} + + static if (hasMember!(A, "expand")) + {{ + assert(a.expand(b1, 0)); + auto len = b1.length; + if (a.expand(b1, 102)) + { + assert(b1.length == len + 102, text(b1.length, " != ", len + 102)); + } + auto aa = make(); + void[] b5 = null; + assert(aa.expand(b5, 0)); + assert(b5 is null); + assert(!aa.expand(b5, 1)); + assert(b5.length == 0); + }} + + void[] b6 = null; + assert(a.reallocate(b6, 0)); + assert(b6.length == 0); + assert(a.reallocate(b6, 1)); + assert(b6.length == 1, text(b6.length)); + assert(a.reallocate(b6, 2)); + assert(b6.length == 2); + + // Test owns + static if (hasMember!(A, "owns")) + {{ + assert(a.owns(null) == Ternary.no); + assert(a.owns(b1) == Ternary.yes); + assert(a.owns(b2) == Ternary.yes); + assert(a.owns(b6) == Ternary.yes); + }} + + static if (hasMember!(A, "resolveInternalPointer")) + {{ + void[] p; + assert(a.resolveInternalPointer(null, p) == Ternary.no); + Ternary r = a.resolveInternalPointer(b1.ptr, p); + assert(p.ptr is b1.ptr && p.length >= b1.length); + r = a.resolveInternalPointer(b1.ptr + b1.length / 2, p); + assert(p.ptr is b1.ptr && p.length >= b1.length); + r = a.resolveInternalPointer(b2.ptr, p); + assert(p.ptr is b2.ptr && p.length >= b2.length); + r = a.resolveInternalPointer(b2.ptr + b2.length / 2, p); + assert(p.ptr is b2.ptr && p.length >= b2.length); + r = a.resolveInternalPointer(b6.ptr, p); + assert(p.ptr is b6.ptr && p.length >= b6.length); + r = a.resolveInternalPointer(b6.ptr + b6.length / 2, p); + assert(p.ptr is b6.ptr && p.length >= b6.length); + static int[10] b7 = [ 1, 2, 3 ]; + assert(a.resolveInternalPointer(b7.ptr, p) == Ternary.no); + assert(a.resolveInternalPointer(b7.ptr + b7.length / 2, p) == Ternary.no); + assert(a.resolveInternalPointer(b7.ptr + b7.length, p) == Ternary.no); + int[3] b8 = [ 1, 2, 3 ]; + assert(a.resolveInternalPointer(b8.ptr, p) == Ternary.no); + assert(a.resolveInternalPointer(b8.ptr + b8.length / 2, p) == Ternary.no); + assert(a.resolveInternalPointer(b8.ptr + b8.length, p) == Ternary.no); + }} + } + + package void testAllocatorObject(AllocInterface)(AllocInterface a) + if (is(AllocInterface : IAllocator) + || is (AllocInterface : shared ISharedAllocator)) + { + import std.conv : text; + import std.math : isPowerOf2; + import std.stdio : writeln, stderr; + import std.typecons : Ternary; + scope(failure) stderr.writeln("testAllocatorObject failed for ", + AllocInterface.stringof); + + assert(a); + + // Test alignment + assert(a.alignment.isPowerOf2); + + // Test goodAllocSize + assert(a.goodAllocSize(1) >= a.alignment, + text(a.goodAllocSize(1), " < ", a.alignment)); + assert(a.goodAllocSize(11) >= 11.roundUpToMultipleOf(a.alignment)); + assert(a.goodAllocSize(111) >= 111.roundUpToMultipleOf(a.alignment)); + + // Test empty + assert(a.empty != Ternary.no); + + // Test allocate + assert(a.allocate(0) is null); + + auto b1 = a.allocate(1); + assert(b1.length == 1); + auto b2 = a.allocate(2); + assert(b2.length == 2); + assert(b2.ptr + b2.length <= b1.ptr || b1.ptr + b1.length <= b2.ptr); + + // Test alignedAllocate + { + // If not implemented it will return null, so those should pass + auto b3 = a.alignedAllocate(1, 256); + assert(b3.length <= 1); + assert(b3.ptr.alignedAt(256)); + if (a.alignedReallocate(b3, 1, 256)) + { + // If it is false, then the wrapped allocator did not implement + // this + assert(a.alignedReallocate(b3, 2, 512)); + assert(b3.ptr.alignedAt(512)); + } + } + + // Test allocateAll + { + auto aa = a.allocateAll(); + if (aa.ptr) + { + // Can't get any more memory + assert(!a.allocate(1).ptr); + a.deallocate(aa); + } + const b4 = a.allocateAll(); + if (b4.ptr) + { + // Can't get any more memory + assert(!a.allocate(1).ptr); + } + } + + // Test expand + { + assert(a.expand(b1, 0)); + auto len = b1.length; + if (a.expand(b1, 102)) + { + assert(b1.length == len + 102, text(b1.length, " != ", len + 102)); + } + } + + void[] b6 = null; + assert(a.reallocate(b6, 0)); + assert(b6.length == 0); + assert(a.reallocate(b6, 1)); + assert(b6.length == 1, text(b6.length)); + assert(a.reallocate(b6, 2)); + assert(b6.length == 2); + + // Test owns + { + if (a.owns(null) != Ternary.unknown) + { + assert(a.owns(null) == Ternary.no); + assert(a.owns(b1) == Ternary.yes); + assert(a.owns(b2) == Ternary.yes); + assert(a.owns(b6) == Ternary.yes); + } + } + + // Test resolveInternalPointer + { + void[] p; + if (a.resolveInternalPointer(null, p) != Ternary.unknown) + { + assert(a.resolveInternalPointer(null, p) == Ternary.no); + Ternary r = a.resolveInternalPointer(b1.ptr, p); + assert(p.ptr is b1.ptr && p.length >= b1.length); + r = a.resolveInternalPointer(b1.ptr + b1.length / 2, p); + assert(p.ptr is b1.ptr && p.length >= b1.length); + r = a.resolveInternalPointer(b2.ptr, p); + assert(p.ptr is b2.ptr && p.length >= b2.length); + r = a.resolveInternalPointer(b2.ptr + b2.length / 2, p); + assert(p.ptr is b2.ptr && p.length >= b2.length); + r = a.resolveInternalPointer(b6.ptr, p); + assert(p.ptr is b6.ptr && p.length >= b6.length); + r = a.resolveInternalPointer(b6.ptr + b6.length / 2, p); + assert(p.ptr is b6.ptr && p.length >= b6.length); + static int[10] b7 = [ 1, 2, 3 ]; + assert(a.resolveInternalPointer(b7.ptr, p) == Ternary.no); + assert(a.resolveInternalPointer(b7.ptr + b7.length / 2, p) == Ternary.no); + assert(a.resolveInternalPointer(b7.ptr + b7.length, p) == Ternary.no); + int[3] b8 = [ 1, 2, 3 ]; + assert(a.resolveInternalPointer(b8.ptr, p) == Ternary.no); + assert(a.resolveInternalPointer(b8.ptr + b8.length / 2, p) == Ternary.no); + assert(a.resolveInternalPointer(b8.ptr + b8.length, p) == Ternary.no); + } + } + + // Test deallocateAll + { + if (a.deallocateAll()) + { + if (a.empty != Ternary.unknown) + { + assert(a.empty == Ternary.yes); + } + } + } + } +} diff --git a/libphobos/src/std/experimental/allocator/gc_allocator.d b/libphobos/src/std/experimental/allocator/gc_allocator.d new file mode 100644 index 0000000..4189456 --- /dev/null +++ b/libphobos/src/std/experimental/allocator/gc_allocator.d @@ -0,0 +1,167 @@ +/// +module std.experimental.allocator.gc_allocator; +import std.experimental.allocator.common; + +/** +D's built-in garbage-collected allocator. + */ +struct GCAllocator +{ + import core.memory : GC; + import std.typecons : Ternary; + @system unittest { testAllocator!(() => GCAllocator.instance); } + + /** + The alignment is a static constant equal to $(D platformAlignment), which + ensures proper alignment for any D data type. + */ + enum uint alignment = platformAlignment; + + /** + Standard allocator methods per the semantics defined above. The $(D + deallocate) and $(D reallocate) methods are $(D @system) because they may + move memory around, leaving dangling pointers in user code. + */ + pure nothrow @trusted void[] allocate(size_t bytes) shared + { + if (!bytes) return null; + auto p = GC.malloc(bytes); + return p ? p[0 .. bytes] : null; + } + + /// Ditto + @system bool expand(ref void[] b, size_t delta) shared + { + if (delta == 0) return true; + if (b is null) return false; + immutable curLength = GC.sizeOf(b.ptr); + assert(curLength != 0); // we have a valid GC pointer here + immutable desired = b.length + delta; + if (desired > curLength) // check to see if the current block can't hold the data + { + immutable sizeRequest = desired - curLength; + immutable newSize = GC.extend(b.ptr, sizeRequest, sizeRequest); + if (newSize == 0) + { + // expansion unsuccessful + return false; + } + assert(newSize >= desired); + } + b = b.ptr[0 .. desired]; + return true; + } + + /// Ditto + pure nothrow @system bool reallocate(ref void[] b, size_t newSize) shared + { + import core.exception : OutOfMemoryError; + try + { + auto p = cast(ubyte*) GC.realloc(b.ptr, newSize); + b = p[0 .. newSize]; + } + catch (OutOfMemoryError) + { + // leave the block in place, tell caller + return false; + } + return true; + } + + /// Ditto + pure nothrow + Ternary resolveInternalPointer(const void* p, ref void[] result) shared + { + auto r = GC.addrOf(cast(void*) p); + if (!r) return Ternary.no; + result = r[0 .. GC.sizeOf(r)]; + return Ternary.yes; + } + + /// Ditto + pure nothrow @system bool deallocate(void[] b) shared + { + GC.free(b.ptr); + return true; + } + + /// Ditto + size_t goodAllocSize(size_t n) shared + { + if (n == 0) + return 0; + if (n <= 16) + return 16; + + import core.bitop : bsr; + + auto largestBit = bsr(n-1) + 1; + if (largestBit <= 12) // 4096 or less + return size_t(1) << largestBit; + + // larger, we use a multiple of 4096. + return ((n + 4095) / 4096) * 4096; + } + + /** + Returns the global instance of this allocator type. The garbage collected + allocator is thread-safe, therefore all of its methods and `instance` itself + are $(D shared). + */ + + static shared GCAllocator instance; + + // Leave it undocummented for now. + nothrow @trusted void collect() shared + { + GC.collect(); + } +} + +/// +@system unittest +{ + auto buffer = GCAllocator.instance.allocate(1024 * 1024 * 4); + // deallocate upon scope's end (alternatively: leave it to collection) + scope(exit) GCAllocator.instance.deallocate(buffer); + //... +} + +@system unittest +{ + auto b = GCAllocator.instance.allocate(10_000); + assert(GCAllocator.instance.expand(b, 1)); +} + +@system unittest +{ + import core.memory : GC; + import std.typecons : Ternary; + + // test allocation sizes + assert(GCAllocator.instance.goodAllocSize(1) == 16); + for (size_t s = 16; s <= 8192; s *= 2) + { + assert(GCAllocator.instance.goodAllocSize(s) == s); + assert(GCAllocator.instance.goodAllocSize(s - (s / 2) + 1) == s); + + auto buffer = GCAllocator.instance.allocate(s); + scope(exit) GCAllocator.instance.deallocate(buffer); + + void[] p; + assert(GCAllocator.instance.resolveInternalPointer(null, p) == Ternary.no); + Ternary r = GCAllocator.instance.resolveInternalPointer(buffer.ptr, p); + assert(p.ptr is buffer.ptr && p.length >= buffer.length); + + assert(GC.sizeOf(buffer.ptr) == s); + + auto buffer2 = GCAllocator.instance.allocate(s - (s / 2) + 1); + scope(exit) GCAllocator.instance.deallocate(buffer2); + + assert(GC.sizeOf(buffer2.ptr) == s); + } + + // anything above a page is simply rounded up to next page + assert(GCAllocator.instance.goodAllocSize(4096 * 4 + 1) == 4096 * 5); +} diff --git a/libphobos/src/std/experimental/allocator/mallocator.d b/libphobos/src/std/experimental/allocator/mallocator.d new file mode 100644 index 0000000..146c974 --- /dev/null +++ b/libphobos/src/std/experimental/allocator/mallocator.d @@ -0,0 +1,387 @@ +/// +module std.experimental.allocator.mallocator; +import std.experimental.allocator.common; + +/** + The C heap allocator. + */ +struct Mallocator +{ + @system unittest { testAllocator!(() => Mallocator.instance); } + + /** + The alignment is a static constant equal to $(D platformAlignment), which + ensures proper alignment for any D data type. + */ + enum uint alignment = platformAlignment; + + /** + Standard allocator methods per the semantics defined above. The + $(D deallocate) and $(D reallocate) methods are $(D @system) because they + may move memory around, leaving dangling pointers in user code. Somewhat + paradoxically, $(D malloc) is $(D @safe) but that's only useful to safe + programs that can afford to leak memory allocated. + */ + @trusted @nogc nothrow + void[] allocate(size_t bytes) shared + { + import core.stdc.stdlib : malloc; + if (!bytes) return null; + auto p = malloc(bytes); + return p ? p[0 .. bytes] : null; + } + + /// Ditto + @system @nogc nothrow + bool deallocate(void[] b) shared + { + import core.stdc.stdlib : free; + free(b.ptr); + return true; + } + + /// Ditto + @system @nogc nothrow + bool reallocate(ref void[] b, size_t s) shared + { + import core.stdc.stdlib : realloc; + if (!s) + { + // fuzzy area in the C standard, see http://goo.gl/ZpWeSE + // so just deallocate and nullify the pointer + deallocate(b); + b = null; + return true; + } + auto p = cast(ubyte*) realloc(b.ptr, s); + if (!p) return false; + b = p[0 .. s]; + return true; + } + + /** + Returns the global instance of this allocator type. The C heap allocator is + thread-safe, therefore all of its methods and `it` itself are + $(D shared). + */ + static shared Mallocator instance; +} + +/// +@nogc nothrow +@system unittest +{ + auto buffer = Mallocator.instance.allocate(1024 * 1024 * 4); + scope(exit) Mallocator.instance.deallocate(buffer); + //... +} + +@nogc nothrow +@system unittest +{ + @nogc nothrow + static void test(A)() + { + int* p = null; + p = cast(int*) A.instance.allocate(int.sizeof); + scope(exit) A.instance.deallocate(p[0 .. int.sizeof]); + *p = 42; + assert(*p == 42); + } + test!Mallocator(); +} + +@nogc nothrow +@system unittest +{ + static void test(A)() + { + import std.experimental.allocator : make; + Object p = null; + p = A.instance.make!Object(); + assert(p !is null); + } + + test!Mallocator(); +} + +version (Posix) +@nogc nothrow +private extern(C) int posix_memalign(void**, size_t, size_t); + +version (Windows) +{ + // DMD Win 32 bit, DigitalMars C standard library misses the _aligned_xxx + // functions family (snn.lib) + version (CRuntime_DigitalMars) + { + // Helper to cast the infos written before the aligned pointer + // this header keeps track of the size (required to realloc) and of + // the base ptr (required to free). + private struct AlignInfo + { + void* basePtr; + size_t size; + + @nogc nothrow + static AlignInfo* opCall(void* ptr) + { + return cast(AlignInfo*) (ptr - AlignInfo.sizeof); + } + } + + @nogc nothrow + private void* _aligned_malloc(size_t size, size_t alignment) + { + import core.stdc.stdlib : malloc; + size_t offset = alignment + size_t.sizeof * 2 - 1; + + // unaligned chunk + void* basePtr = malloc(size + offset); + if (!basePtr) return null; + + // get aligned location within the chunk + void* alignedPtr = cast(void**)((cast(size_t)(basePtr) + offset) + & ~(alignment - 1)); + + // write the header before the aligned pointer + AlignInfo* head = AlignInfo(alignedPtr); + head.basePtr = basePtr; + head.size = size; + + return alignedPtr; + } + + @nogc nothrow + private void* _aligned_realloc(void* ptr, size_t size, size_t alignment) + { + import core.stdc.stdlib : free; + import core.stdc.string : memcpy; + + if (!ptr) return _aligned_malloc(size, alignment); + + // gets the header from the exising pointer + AlignInfo* head = AlignInfo(ptr); + + // gets a new aligned pointer + void* alignedPtr = _aligned_malloc(size, alignment); + if (!alignedPtr) + { + //to https://msdn.microsoft.com/en-us/library/ms235462.aspx + //see Return value: in this case the original block is unchanged + return null; + } + + // copy exising data + memcpy(alignedPtr, ptr, head.size); + free(head.basePtr); + + return alignedPtr; + } + + @nogc nothrow + private void _aligned_free(void *ptr) + { + import core.stdc.stdlib : free; + if (!ptr) return; + AlignInfo* head = AlignInfo(ptr); + free(head.basePtr); + } + + } + // DMD Win 64 bit, uses microsoft standard C library which implements them + else + { + @nogc nothrow private extern(C) void* _aligned_malloc(size_t, size_t); + @nogc nothrow private extern(C) void _aligned_free(void *memblock); + @nogc nothrow private extern(C) void* _aligned_realloc(void *, size_t, size_t); + } +} + +/** + Aligned allocator using OS-specific primitives, under a uniform API. + */ +struct AlignedMallocator +{ + @system unittest { testAllocator!(() => typeof(this).instance); } + + /** + The default alignment is $(D platformAlignment). + */ + enum uint alignment = platformAlignment; + + /** + Forwards to $(D alignedAllocate(bytes, platformAlignment)). + */ + @trusted @nogc nothrow + void[] allocate(size_t bytes) shared + { + if (!bytes) return null; + return alignedAllocate(bytes, alignment); + } + + /** + Uses $(HTTP man7.org/linux/man-pages/man3/posix_memalign.3.html, + $(D posix_memalign)) on Posix and + $(HTTP msdn.microsoft.com/en-us/library/8z34s9c6(v=vs.80).aspx, + $(D __aligned_malloc)) on Windows. + */ + version (Posix) + @trusted @nogc nothrow + void[] alignedAllocate(size_t bytes, uint a) shared + { + import core.stdc.errno : ENOMEM, EINVAL; + assert(a.isGoodDynamicAlignment); + void* result; + auto code = posix_memalign(&result, a, bytes); + if (code == ENOMEM) + return null; + + else if (code == EINVAL) + { + assert(0, "AlignedMallocator.alignment is not a power of two " + ~"multiple of (void*).sizeof, according to posix_memalign!"); + } + else if (code != 0) + assert(0, "posix_memalign returned an unknown code!"); + + else + return result[0 .. bytes]; + } + else version (Windows) + @trusted @nogc nothrow + void[] alignedAllocate(size_t bytes, uint a) shared + { + auto result = _aligned_malloc(bytes, a); + return result ? result[0 .. bytes] : null; + } + else static assert(0); + + /** + Calls $(D free(b.ptr)) on Posix and + $(HTTP msdn.microsoft.com/en-US/library/17b5h8td(v=vs.80).aspx, + $(D __aligned_free(b.ptr))) on Windows. + */ + version (Posix) + @system @nogc nothrow + bool deallocate(void[] b) shared + { + import core.stdc.stdlib : free; + free(b.ptr); + return true; + } + else version (Windows) + @system @nogc nothrow + bool deallocate(void[] b) shared + { + _aligned_free(b.ptr); + return true; + } + else static assert(0); + + /** + On Posix, forwards to $(D realloc). On Windows, forwards to + $(D alignedReallocate(b, newSize, platformAlignment)). + */ + version (Posix) + @system @nogc nothrow + bool reallocate(ref void[] b, size_t newSize) shared + { + return Mallocator.instance.reallocate(b, newSize); + } + version (Windows) + @system @nogc nothrow + bool reallocate(ref void[] b, size_t newSize) shared + { + return alignedReallocate(b, newSize, alignment); + } + + /** + On Posix, uses $(D alignedAllocate) and copies data around because there is + no realloc for aligned memory. On Windows, calls + $(HTTP msdn.microsoft.com/en-US/library/y69db7sx(v=vs.80).aspx, + $(D __aligned_realloc(b.ptr, newSize, a))). + */ + version (Windows) + @system @nogc nothrow + bool alignedReallocate(ref void[] b, size_t s, uint a) shared + { + if (!s) + { + deallocate(b); + b = null; + return true; + } + auto p = cast(ubyte*) _aligned_realloc(b.ptr, s, a); + if (!p) return false; + b = p[0 .. s]; + return true; + } + + /** + Returns the global instance of this allocator type. The C heap allocator is + thread-safe, therefore all of its methods and `instance` itself are + $(D shared). + */ + static shared AlignedMallocator instance; +} + +/// +@nogc nothrow +@system unittest +{ + auto buffer = AlignedMallocator.instance.alignedAllocate(1024 * 1024 * 4, + 128); + scope(exit) AlignedMallocator.instance.deallocate(buffer); + //... +} + +version (unittest) version (CRuntime_DigitalMars) +@nogc nothrow +size_t addr(ref void* ptr) { return cast(size_t) ptr; } + +version (CRuntime_DigitalMars) +@nogc nothrow +@system unittest +{ + void* m; + + m = _aligned_malloc(16, 0x10); + if (m) + { + assert((m.addr & 0xF) == 0); + _aligned_free(m); + } + + m = _aligned_malloc(16, 0x100); + if (m) + { + assert((m.addr & 0xFF) == 0); + _aligned_free(m); + } + + m = _aligned_malloc(16, 0x1000); + if (m) + { + assert((m.addr & 0xFFF) == 0); + _aligned_free(m); + } + + m = _aligned_malloc(16, 0x10); + if (m) + { + assert((cast(size_t) m & 0xF) == 0); + m = _aligned_realloc(m, 32, 0x10000); + if (m) assert((m.addr & 0xFFFF) == 0); + _aligned_free(m); + } + + m = _aligned_malloc(8, 0x10); + if (m) + { + *cast(ulong*) m = 0X01234567_89ABCDEF; + m = _aligned_realloc(m, 0x800, 0x1000); + if (m) assert(*cast(ulong*) m == 0X01234567_89ABCDEF); + _aligned_free(m); + } +} diff --git a/libphobos/src/std/experimental/allocator/mmap_allocator.d b/libphobos/src/std/experimental/allocator/mmap_allocator.d new file mode 100644 index 0000000..945859b --- /dev/null +++ b/libphobos/src/std/experimental/allocator/mmap_allocator.d @@ -0,0 +1,79 @@ +/// +module std.experimental.allocator.mmap_allocator; + +// MmapAllocator +/** + +Allocator (currently defined only for Posix and Windows) using +$(D $(LINK2 https://en.wikipedia.org/wiki/Mmap, mmap)) +and $(D $(LUCKY munmap)) directly (or their Windows equivalents). There is no +additional structure: each call to $(D allocate(s)) issues a call to +$(D mmap(null, s, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)), +and each call to $(D deallocate(b)) issues $(D munmap(b.ptr, b.length)). +So $(D MmapAllocator) is usually intended for allocating large chunks to be +managed by fine-granular allocators. + +*/ +struct MmapAllocator +{ + /// The one shared instance. + static shared MmapAllocator instance; + + /** + Alignment is page-size and hardcoded to 4096 (even though on certain systems + it could be larger). + */ + enum size_t alignment = 4096; + + version (Posix) + { + /// Allocator API. + void[] allocate(size_t bytes) shared + { + import core.sys.posix.sys.mman : mmap, MAP_ANON, PROT_READ, + PROT_WRITE, MAP_PRIVATE, MAP_FAILED; + if (!bytes) return null; + auto p = mmap(null, bytes, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, -1, 0); + if (p is MAP_FAILED) return null; + return p[0 .. bytes]; + } + + /// Ditto + bool deallocate(void[] b) shared + { + import core.sys.posix.sys.mman : munmap; + if (b.ptr) munmap(b.ptr, b.length) == 0 || assert(0); + return true; + } + } + else version (Windows) + { + import core.sys.windows.windows : VirtualAlloc, VirtualFree, MEM_COMMIT, + PAGE_READWRITE, MEM_RELEASE; + + /// Allocator API. + void[] allocate(size_t bytes) shared + { + if (!bytes) return null; + auto p = VirtualAlloc(null, bytes, MEM_COMMIT, PAGE_READWRITE); + if (p == null) + return null; + return p[0 .. bytes]; + } + + /// Ditto + bool deallocate(void[] b) shared + { + return b.ptr is null || VirtualFree(b.ptr, 0, MEM_RELEASE) != 0; + } + } +} + +@system unittest +{ + alias alloc = MmapAllocator.instance; + auto p = alloc.allocate(100); + assert(p.length == 100); + alloc.deallocate(p); +} diff --git a/libphobos/src/std/experimental/allocator/package.d b/libphobos/src/std/experimental/allocator/package.d new file mode 100644 index 0000000..11c8547 --- /dev/null +++ b/libphobos/src/std/experimental/allocator/package.d @@ -0,0 +1,3028 @@ +// Written in the D programming language. +/** + +High-level interface for allocators. Implements bundled allocation/creation +and destruction/deallocation of data including `struct`s and `class`es, +and also array primitives related to allocation. This module is the entry point +for both making use of allocators and for their documentation. + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE, +$(TR $(TH Category) $(TH Functions)) +$(TR $(TD Make) $(TD + $(LREF make) + $(LREF makeArray) + $(LREF makeMultidimensionalArray) +)) +$(TR $(TD Dispose) $(TD + $(LREF dispose) + $(LREF disposeMultidimensionalArray) +)) +$(TR $(TD Modify) $(TD + $(LREF expandArray) + $(LREF shrinkArray) +)) +$(TR $(TD Global) $(TD + $(LREF processAllocator) + $(LREF theAllocator) +)) +$(TR $(TD Class interface) $(TD + $(LREF allocatorObject) + $(LREF CAllocatorImpl) + $(LREF IAllocator) +)) +) + +Synopsis: +--- +// Allocate an int, initialize it with 42 +int* p = theAllocator.make!int(42); +assert(*p == 42); +// Destroy and deallocate it +theAllocator.dispose(p); + +// Allocate using the global process allocator +p = processAllocator.make!int(100); +assert(*p == 100); +// Destroy and deallocate +processAllocator.dispose(p); + +// Create an array of 50 doubles initialized to -1.0 +double[] arr = theAllocator.makeArray!double(50, -1.0); +// Append two zeros to it +theAllocator.expandArray(arr, 2, 0.0); +// On second thought, take that back +theAllocator.shrinkArray(arr, 2); +// Destroy and deallocate +theAllocator.dispose(arr); +--- + +$(H2 Layered Structure) + +D's allocators have a layered structure in both implementation and documentation: + +$(OL +$(LI A high-level, dynamically-typed layer (described further down in this +module). It consists of an interface called $(LREF IAllocator), which concret; +allocators need to implement. The interface primitives themselves are oblivious +to the type of the objects being allocated; they only deal in `void[]`, by +necessity of the interface being dynamic (as opposed to type-parameterized). +Each thread has a current allocator it uses by default, which is a thread-local +variable $(LREF theAllocator) of type $(LREF IAllocator). The process has a +global _allocator called $(LREF processAllocator), also of type $(LREF +IAllocator). When a new thread is created, $(LREF processAllocator) is copied +into $(LREF theAllocator). An application can change the objects to which these +references point. By default, at application startup, $(LREF processAllocator) +refers to an object that uses D's garbage collected heap. This layer also +include high-level functions such as $(LREF make) and $(LREF dispose) that +comfortably allocate/create and respectively destroy/deallocate objects. This +layer is all needed for most casual uses of allocation primitives.) + +$(LI A mid-level, statically-typed layer for assembling several allocators into +one. It uses properties of the type of the objects being created to route +allocation requests to possibly specialized allocators. This layer is relatively +thin and implemented and documented in the $(MREF +std,experimental,_allocator,typed) module. It allows an interested user to e.g. +use different allocators for arrays versus fixed-sized objects, to the end of +better overall performance.) + +$(LI A low-level collection of highly generic $(I heap building blocks)$(MDASH) +Lego-like pieces that can be used to assemble application-specific allocators. +The real allocation smarts are occurring at this level. This layer is of +interest to advanced applications that want to configure their own allocators. +A good illustration of typical uses of these building blocks is module $(MREF +std,experimental,_allocator,showcase) which defines a collection of frequently- +used preassembled allocator objects. The implementation and documentation entry +point is $(MREF std,experimental,_allocator,building_blocks). By design, the +primitives of the static interface have the same signatures as the $(LREF +IAllocator) primitives but are for the most part optional and driven by static +introspection. The parameterized class $(LREF CAllocatorImpl) offers an +immediate and useful means to package a static low-level _allocator into an +implementation of $(LREF IAllocator).) + +$(LI Core _allocator objects that interface with D's garbage collected heap +($(MREF std,experimental,_allocator,gc_allocator)), the C `malloc` family +($(MREF std,experimental,_allocator,mallocator)), and the OS ($(MREF +std,experimental,_allocator,mmap_allocator)). Most custom allocators would +ultimately obtain memory from one of these core allocators.) +) + +$(H2 Idiomatic Use of $(D std.experimental._allocator)) + +As of this time, $(D std.experimental._allocator) is not integrated with D's +built-in operators that allocate memory, such as `new`, array literals, or +array concatenation operators. That means $(D std.experimental._allocator) is +opt-in$(MDASH)applications need to make explicit use of it. + +For casual creation and disposal of dynamically-allocated objects, use $(LREF +make), $(LREF dispose), and the array-specific functions $(LREF makeArray), +$(LREF expandArray), and $(LREF shrinkArray). These use by default D's garbage +collected heap, but open the application to better configuration options. These +primitives work either with `theAllocator` but also with any allocator obtained +by combining heap building blocks. For example: + +---- +void fun(size_t n) +{ + // Use the current allocator + int[] a1 = theAllocator.makeArray!int(n); + scope(exit) theAllocator.dispose(a1); + ... +} +---- + +To experiment with alternative allocators, set $(LREF theAllocator) for the +current thread. For example, consider an application that allocates many 8-byte +objects. These are not well supported by the default _allocator, so a +$(MREF_ALTTEXT free list _allocator, +std,experimental,_allocator,building_blocks,free_list) would be recommended. +To install one in `main`, the application would use: + +---- +void main() +{ + import std.experimental.allocator.building_blocks.free_list + : FreeList; + theAllocator = allocatorObject(FreeList!8()); + ... +} +---- + +$(H3 Saving the `IAllocator` Reference For Later Use) + +As with any global resource, setting `theAllocator` and `processAllocator` +should not be done often and casually. In particular, allocating memory with +one allocator and deallocating with another causes undefined behavior. +Typically, these variables are set during application initialization phase and +last through the application. + +To avoid this, long-lived objects that need to perform allocations, +reallocations, and deallocations relatively often may want to store a reference +to the _allocator object they use throughout their lifetime. Then, instead of +using `theAllocator` for internal allocation-related tasks, they'd use the +internally held reference. For example, consider a user-defined hash table: + +---- +struct HashTable +{ + private IAllocator _allocator; + this(size_t buckets, IAllocator allocator = theAllocator) { + this._allocator = allocator; + ... + } + // Getter and setter + IAllocator allocator() { return _allocator; } + void allocator(IAllocator a) { assert(empty); _allocator = a; } +} +---- + +Following initialization, the `HashTable` object would consistently use its +$(D _allocator) object for acquiring memory. Furthermore, setting +$(D HashTable._allocator) to point to a different _allocator should be legal but +only if the object is empty; otherwise, the object wouldn't be able to +deallocate its existing state. + +$(H3 Using Allocators without `IAllocator`) + +Allocators assembled from the heap building blocks don't need to go through +`IAllocator` to be usable. They have the same primitives as `IAllocator` and +they work with $(LREF make), $(LREF makeArray), $(LREF dispose) etc. So it +suffice to create allocator objects wherever fit and use them appropriately: + +---- +void fun(size_t n) +{ + // Use a stack-installed allocator for up to 64KB + StackFront!65536 myAllocator; + int[] a2 = myAllocator.makeArray!int(n); + scope(exit) myAllocator.dispose(a2); + ... +} +---- + +In this case, `myAllocator` does not obey the `IAllocator` interface, but +implements its primitives so it can work with `makeArray` by means of duck +typing. + +One important thing to note about this setup is that statically-typed assembled +allocators are almost always faster than allocators that go through +`IAllocator`. An important rule of thumb is: "assemble allocator first, adapt +to `IAllocator` after". A good allocator implements intricate logic by means of +template assembly, and gets wrapped with `IAllocator` (usually by means of +$(LREF allocatorObject)) only once, at client level. + +Copyright: Andrei Alexandrescu 2013-. + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(HTTP erdani.com, Andrei Alexandrescu) + +Source: $(PHOBOSSRC std/experimental/_allocator) + +*/ + +module std.experimental.allocator; + +public import std.experimental.allocator.common, + std.experimental.allocator.typed; + +// Example in the synopsis above +@system unittest +{ + import std.algorithm.comparison : min, max; + import std.experimental.allocator.building_blocks.allocator_list + : AllocatorList; + import std.experimental.allocator.building_blocks.bitmapped_block + : BitmappedBlock; + import std.experimental.allocator.building_blocks.bucketizer : Bucketizer; + import std.experimental.allocator.building_blocks.free_list : FreeList; + import std.experimental.allocator.building_blocks.segregator : Segregator; + import std.experimental.allocator.gc_allocator : GCAllocator; + + alias FList = FreeList!(GCAllocator, 0, unbounded); + alias A = Segregator!( + 8, FreeList!(GCAllocator, 0, 8), + 128, Bucketizer!(FList, 1, 128, 16), + 256, Bucketizer!(FList, 129, 256, 32), + 512, Bucketizer!(FList, 257, 512, 64), + 1024, Bucketizer!(FList, 513, 1024, 128), + 2048, Bucketizer!(FList, 1025, 2048, 256), + 3584, Bucketizer!(FList, 2049, 3584, 512), + 4072 * 1024, AllocatorList!( + (n) => BitmappedBlock!(4096)( + cast(ubyte[])(GCAllocator.instance.allocate( + max(n, 4072 * 1024))))), + GCAllocator + ); + A tuMalloc; + auto b = tuMalloc.allocate(500); + assert(b.length == 500); + auto c = tuMalloc.allocate(113); + assert(c.length == 113); + assert(tuMalloc.expand(c, 14)); + tuMalloc.deallocate(b); + tuMalloc.deallocate(c); +} + +import std.range.primitives; +import std.traits; +import std.typecons; + +/** +Dynamic allocator interface. Code that defines allocators ultimately implements +this interface. This should be used wherever a uniform type is required for +encapsulating various allocator implementations. + +Composition of allocators is not recommended at this level due to +inflexibility of dynamic interfaces and inefficiencies caused by cascaded +multiple calls. Instead, compose allocators using the static interface defined +in $(A std_experimental_allocator_building_blocks.html, +`std.experimental.allocator.building_blocks`), then adapt the composed +allocator to `IAllocator` (possibly by using $(LREF CAllocatorImpl) below). + +Methods returning $(D Ternary) return $(D Ternary.yes) upon success, +$(D Ternary.no) upon failure, and $(D Ternary.unknown) if the primitive is not +implemented by the allocator instance. +*/ +interface IAllocator +{ + /** + Returns the alignment offered. + */ + @property uint alignment(); + + /** + Returns the good allocation size that guarantees zero internal + fragmentation. + */ + size_t goodAllocSize(size_t s); + + /** + Allocates `n` bytes of memory. + */ + void[] allocate(size_t, TypeInfo ti = null); + + /** + Allocates `n` bytes of memory with specified alignment `a`. Implementations + that do not support this primitive should always return `null`. + */ + void[] alignedAllocate(size_t n, uint a); + + /** + Allocates and returns all memory available to this allocator. + Implementations that do not support this primitive should always return + `null`. + */ + void[] allocateAll(); + + /** + Expands a memory block in place and returns `true` if successful. + Implementations that don't support this primitive should always return + `false`. + */ + bool expand(ref void[], size_t); + + /// Reallocates a memory block. + bool reallocate(ref void[], size_t); + + /// Reallocates a memory block with specified alignment. + bool alignedReallocate(ref void[] b, size_t size, uint alignment); + + /** + Returns $(D Ternary.yes) if the allocator owns $(D b), $(D Ternary.no) if + the allocator doesn't own $(D b), and $(D Ternary.unknown) if ownership + cannot be determined. Implementations that don't support this primitive + should always return `Ternary.unknown`. + */ + Ternary owns(void[] b); + + /** + Resolves an internal pointer to the full block allocated. Implementations + that don't support this primitive should always return `Ternary.unknown`. + */ + Ternary resolveInternalPointer(const void* p, ref void[] result); + + /** + Deallocates a memory block. Implementations that don't support this + primitive should always return `false`. A simple way to check that an + allocator supports deallocation is to call $(D deallocate(null)). + */ + bool deallocate(void[] b); + + /** + Deallocates all memory. Implementations that don't support this primitive + should always return `false`. + */ + bool deallocateAll(); + + /** + Returns $(D Ternary.yes) if no memory is currently allocated from this + allocator, $(D Ternary.no) if some allocations are currently active, or + $(D Ternary.unknown) if not supported. + */ + Ternary empty(); +} + +/** +Dynamic shared allocator interface. Code that defines allocators shareable +across threads ultimately implements this interface. This should be used +wherever a uniform type is required for encapsulating various allocator +implementations. + +Composition of allocators is not recommended at this level due to +inflexibility of dynamic interfaces and inefficiencies caused by cascaded +multiple calls. Instead, compose allocators using the static interface defined +in $(A std_experimental_allocator_building_blocks.html, +`std.experimental.allocator.building_blocks`), then adapt the composed +allocator to `ISharedAllocator` (possibly by using $(LREF CSharedAllocatorImpl) below). + +Methods returning $(D Ternary) return $(D Ternary.yes) upon success, +$(D Ternary.no) upon failure, and $(D Ternary.unknown) if the primitive is not +implemented by the allocator instance. +*/ +interface ISharedAllocator +{ + /** + Returns the alignment offered. + */ + @property uint alignment() shared; + + /** + Returns the good allocation size that guarantees zero internal + fragmentation. + */ + size_t goodAllocSize(size_t s) shared; + + /** + Allocates `n` bytes of memory. + */ + void[] allocate(size_t, TypeInfo ti = null) shared; + + /** + Allocates `n` bytes of memory with specified alignment `a`. Implementations + that do not support this primitive should always return `null`. + */ + void[] alignedAllocate(size_t n, uint a) shared; + + /** + Allocates and returns all memory available to this allocator. + Implementations that do not support this primitive should always return + `null`. + */ + void[] allocateAll() shared; + + /** + Expands a memory block in place and returns `true` if successful. + Implementations that don't support this primitive should always return + `false`. + */ + bool expand(ref void[], size_t) shared; + + /// Reallocates a memory block. + bool reallocate(ref void[], size_t) shared; + + /// Reallocates a memory block with specified alignment. + bool alignedReallocate(ref void[] b, size_t size, uint alignment) shared; + + /** + Returns $(D Ternary.yes) if the allocator owns $(D b), $(D Ternary.no) if + the allocator doesn't own $(D b), and $(D Ternary.unknown) if ownership + cannot be determined. Implementations that don't support this primitive + should always return `Ternary.unknown`. + */ + Ternary owns(void[] b) shared; + + /** + Resolves an internal pointer to the full block allocated. Implementations + that don't support this primitive should always return `Ternary.unknown`. + */ + Ternary resolveInternalPointer(const void* p, ref void[] result) shared; + + /** + Deallocates a memory block. Implementations that don't support this + primitive should always return `false`. A simple way to check that an + allocator supports deallocation is to call $(D deallocate(null)). + */ + bool deallocate(void[] b) shared; + + /** + Deallocates all memory. Implementations that don't support this primitive + should always return `false`. + */ + bool deallocateAll() shared; + + /** + Returns $(D Ternary.yes) if no memory is currently allocated from this + allocator, $(D Ternary.no) if some allocations are currently active, or + $(D Ternary.unknown) if not supported. + */ + Ternary empty() shared; +} + +private shared ISharedAllocator _processAllocator; +private IAllocator _threadAllocator; + +private IAllocator setupThreadAllocator() nothrow @nogc @safe +{ + /* + Forwards the `_threadAllocator` calls to the `processAllocator` + */ + static class ThreadAllocator : IAllocator + { + override @property uint alignment() + { + return processAllocator.alignment(); + } + + override size_t goodAllocSize(size_t s) + { + return processAllocator.goodAllocSize(s); + } + + override void[] allocate(size_t n, TypeInfo ti = null) + { + return processAllocator.allocate(n, ti); + } + + override void[] alignedAllocate(size_t n, uint a) + { + return processAllocator.alignedAllocate(n, a); + } + + override void[] allocateAll() + { + return processAllocator.allocateAll(); + } + + override bool expand(ref void[] b, size_t size) + { + return processAllocator.expand(b, size); + } + + override bool reallocate(ref void[] b, size_t size) + { + return processAllocator.reallocate(b, size); + } + + override bool alignedReallocate(ref void[] b, size_t size, uint alignment) + { + return processAllocator.alignedReallocate(b, size, alignment); + } + + override Ternary owns(void[] b) + { + return processAllocator.owns(b); + } + + override Ternary resolveInternalPointer(const void* p, ref void[] result) + { + return processAllocator.resolveInternalPointer(p, result); + } + + override bool deallocate(void[] b) + { + return processAllocator.deallocate(b); + } + + override bool deallocateAll() + { + return processAllocator.deallocateAll(); + } + + override Ternary empty() + { + return processAllocator.empty(); + } + } + + assert(!_threadAllocator); + import std.conv : emplace; + static ulong[stateSize!(ThreadAllocator).divideRoundUp(ulong.sizeof)] _threadAllocatorState; + _threadAllocator = () @trusted { return emplace!(ThreadAllocator)(_threadAllocatorState[]); } (); + return _threadAllocator; +} + +/** +Gets/sets the allocator for the current thread. This is the default allocator +that should be used for allocating thread-local memory. For allocating memory +to be shared across threads, use $(D processAllocator) (below). By default, +$(D theAllocator) ultimately fetches memory from $(D processAllocator), which +in turn uses the garbage collected heap. +*/ +nothrow @safe @nogc @property IAllocator theAllocator() +{ + auto p = _threadAllocator; + return p !is null ? p : setupThreadAllocator(); +} + +/// Ditto +nothrow @safe @nogc @property void theAllocator(IAllocator a) +{ + assert(a); + _threadAllocator = a; +} + +/// +@system unittest +{ + // Install a new allocator that is faster for 128-byte allocations. + import std.experimental.allocator.building_blocks.free_list : FreeList; + import std.experimental.allocator.gc_allocator : GCAllocator; + auto oldAllocator = theAllocator; + scope(exit) theAllocator = oldAllocator; + theAllocator = allocatorObject(FreeList!(GCAllocator, 128)()); + // Use the now changed allocator to allocate an array + const ubyte[] arr = theAllocator.makeArray!ubyte(128); + assert(arr.ptr); + //... +} + +/** +Gets/sets the allocator for the current process. This allocator must be used +for allocating memory shared across threads. Objects created using this +allocator can be cast to $(D shared). +*/ +@property shared(ISharedAllocator) processAllocator() +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.concurrency : initOnce; + return initOnce!_processAllocator( + sharedAllocatorObject(GCAllocator.instance)); +} + +/// Ditto +@property void processAllocator(shared ISharedAllocator a) +{ + assert(a); + _processAllocator = a; +} + +@system unittest +{ + import core.exception : AssertError; + import std.exception : assertThrown; + import std.experimental.allocator.building_blocks.free_list : SharedFreeList; + import std.experimental.allocator.mallocator : Mallocator; + + assert(processAllocator); + assert(theAllocator); + + testAllocatorObject(processAllocator); + testAllocatorObject(theAllocator); + + shared SharedFreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime) sharedFL; + shared ISharedAllocator sharedFLObj = sharedAllocatorObject(sharedFL); + assert(sharedFLObj); + testAllocatorObject(sharedFLObj); + + // Test processAllocator setter + shared ISharedAllocator oldProcessAllocator = processAllocator; + processAllocator = sharedFLObj; + assert(processAllocator is sharedFLObj); + + testAllocatorObject(processAllocator); + testAllocatorObject(theAllocator); + assertThrown!AssertError(processAllocator = null); + + // Restore initial processAllocator state + processAllocator = oldProcessAllocator; + assert(processAllocator is oldProcessAllocator); + + shared ISharedAllocator indirectShFLObj = sharedAllocatorObject(&sharedFL); + testAllocatorObject(indirectShFLObj); + + IAllocator indirectMallocator = allocatorObject(&Mallocator.instance); + testAllocatorObject(indirectMallocator); +} + +/** +Dynamically allocates (using $(D alloc)) and then creates in the memory +allocated an object of type $(D T), using $(D args) (if any) for its +initialization. Initialization occurs in the memory allocated and is otherwise +semantically the same as $(D T(args)). +(Note that using $(D alloc.make!(T[])) creates a pointer to an (empty) array +of $(D T)s, not an array. To use an allocator to allocate and initialize an +array, use $(D alloc.makeArray!T) described below.) + +Params: +T = Type of the object being created. +alloc = The allocator used for getting the needed memory. It may be an object +implementing the static interface for allocators, or an $(D IAllocator) +reference. +args = Optional arguments used for initializing the created object. If not +present, the object is default constructed. + +Returns: If $(D T) is a class type, returns a reference to the created $(D T) +object. Otherwise, returns a $(D T*) pointing to the created object. In all +cases, returns $(D null) if allocation failed. + +Throws: If $(D T)'s constructor throws, deallocates the allocated memory and +propagates the exception. +*/ +auto make(T, Allocator, A...)(auto ref Allocator alloc, auto ref A args) +{ + import std.algorithm.comparison : max; + import std.conv : emplace, emplaceRef; + auto m = alloc.allocate(max(stateSize!T, 1)); + if (!m.ptr) return null; + + // make can only be @safe if emplace or emplaceRef is `pure` + auto construct() + { + static if (is(T == class)) return emplace!T(m, args); + else + { + // Assume cast is safe as allocation succeeded for `stateSize!T` + auto p = () @trusted { return cast(T*) m.ptr; }(); + emplaceRef(*p, args); + return p; + } + } + + scope(failure) + { + static if (is(typeof(() pure { return construct(); }))) + { + // Assume deallocation is safe because: + // 1) in case of failure, `m` is the only reference to this memory + // 2) `m` is known to originate from `alloc` + () @trusted { alloc.deallocate(m); }(); + } + else + { + alloc.deallocate(m); + } + } + + return construct(); +} + +/// +@system unittest +{ + // Dynamically allocate one integer + const int* p1 = theAllocator.make!int; + // It's implicitly initialized with its .init value + assert(*p1 == 0); + // Dynamically allocate one double, initialize to 42.5 + const double* p2 = theAllocator.make!double(42.5); + assert(*p2 == 42.5); + + // Dynamically allocate a struct + static struct Point + { + int x, y, z; + } + // Use the generated constructor taking field values in order + const Point* p = theAllocator.make!Point(1, 2); + assert(p.x == 1 && p.y == 2 && p.z == 0); + + // Dynamically allocate a class object + static class Customer + { + uint id = uint.max; + this() {} + this(uint id) { this.id = id; } + // ... + } + Customer cust = theAllocator.make!Customer; + assert(cust.id == uint.max); // default initialized + cust = theAllocator.make!Customer(42); + assert(cust.id == 42); + + // explicit passing of outer pointer + static class Outer + { + int x = 3; + class Inner + { + auto getX() { return x; } + } + } + auto outer = theAllocator.make!Outer(); + auto inner = theAllocator.make!(Outer.Inner)(outer); + assert(outer.x == inner.getX); +} + +@system unittest // bugzilla 15639 & 15772 +{ + abstract class Foo {} + class Bar: Foo {} + static assert(!is(typeof(theAllocator.make!Foo))); + static assert( is(typeof(theAllocator.make!Bar))); +} + +@system unittest +{ + void test(Allocator)(auto ref Allocator alloc) + { + const int* a = alloc.make!int(10); + assert(*a == 10); + + struct A + { + int x; + string y; + double z; + } + + A* b = alloc.make!A(42); + assert(b.x == 42); + assert(b.y is null); + import std.math : isNaN; + assert(b.z.isNaN); + + b = alloc.make!A(43, "44", 45); + assert(b.x == 43); + assert(b.y == "44"); + assert(b.z == 45); + + static class B + { + int x; + string y; + double z; + this(int _x, string _y = null, double _z = double.init) + { + x = _x; + y = _y; + z = _z; + } + } + + B c = alloc.make!B(42); + assert(c.x == 42); + assert(c.y is null); + assert(c.z.isNaN); + + c = alloc.make!B(43, "44", 45); + assert(c.x == 43); + assert(c.y == "44"); + assert(c.z == 45); + + const parray = alloc.make!(int[]); + assert((*parray).empty); + } + + import std.experimental.allocator.gc_allocator : GCAllocator; + test(GCAllocator.instance); + test(theAllocator); +} + +// Attribute propagation +nothrow @safe @nogc unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + alias alloc = Mallocator.instance; + + void test(T, Args...)(auto ref Args args) + { + auto k = alloc.make!T(args); + () @trusted { alloc.dispose(k); }(); + } + + test!int; + test!(int*); + test!int(0); + test!(int*)(null); +} + +// should be pure with the GCAllocator +/*pure nothrow*/ @safe unittest +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + + alias alloc = GCAllocator.instance; + + void test(T, Args...)(auto ref Args args) + { + auto k = alloc.make!T(args); + (a) @trusted { a.dispose(k); }(alloc); + } + + test!int(); + test!(int*); + test!int(0); + test!(int*)(null); +} + +// Verify that making an object by calling an impure constructor is not @safe +nothrow @safe @nogc unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + static struct Pure { this(int) pure nothrow @nogc @safe {} } + + cast(void) Mallocator.instance.make!Pure(0); + + static int g = 0; + static struct Impure { this(int) nothrow @nogc @safe { + g++; + } } + static assert(!__traits(compiles, cast(void) Mallocator.instance.make!Impure(0))); +} + +// test failure with a pure, failing struct +@safe unittest +{ + import std.exception : assertThrown, enforce; + + // this struct can't be initialized + struct InvalidStruct + { + this(int b) + { + enforce(1 == 2); + } + } + import std.experimental.allocator.mallocator : Mallocator; + assertThrown(make!InvalidStruct(Mallocator.instance, 42)); +} + +// test failure with an impure, failing struct +@system unittest +{ + import std.exception : assertThrown, enforce; + static int g; + struct InvalidImpureStruct + { + this(int b) + { + g++; + enforce(1 == 2); + } + } + import std.experimental.allocator.mallocator : Mallocator; + assertThrown(make!InvalidImpureStruct(Mallocator.instance, 42)); +} + +private void fillWithMemcpy(T)(void[] array, auto ref T filler) nothrow +{ + import core.stdc.string : memcpy; + import std.algorithm.comparison : min; + if (!array.length) return; + memcpy(array.ptr, &filler, T.sizeof); + // Fill the array from the initialized portion of itself exponentially. + for (size_t offset = T.sizeof; offset < array.length; ) + { + size_t extent = min(offset, array.length - offset); + memcpy(array.ptr + offset, array.ptr, extent); + offset += extent; + } +} + +@system unittest +{ + int[] a; + fillWithMemcpy(a, 42); + assert(a.length == 0); + a = [ 1, 2, 3, 4, 5 ]; + fillWithMemcpy(a, 42); + assert(a == [ 42, 42, 42, 42, 42]); +} + +private T[] uninitializedFillDefault(T)(T[] array) nothrow +{ + T t = T.init; + fillWithMemcpy(array, t); + return array; +} + +pure nothrow @nogc +@system unittest +{ + static struct S { int x = 42; @disable this(this); } + + int[5] expected = [42, 42, 42, 42, 42]; + S[5] arr = void; + uninitializedFillDefault(arr); + assert((cast(int*) arr.ptr)[0 .. arr.length] == expected); +} + +@system unittest +{ + int[] a = [1, 2, 4]; + uninitializedFillDefault(a); + assert(a == [0, 0, 0]); +} + +/** +Create an array of $(D T) with $(D length) elements using $(D alloc). The array is either default-initialized, filled with copies of $(D init), or initialized with values fetched from `range`. + +Params: +T = element type of the array being created +alloc = the allocator used for getting memory +length = length of the newly created array +init = element used for filling the array +range = range used for initializing the array elements + +Returns: +The newly-created array, or $(D null) if either $(D length) was $(D 0) or +allocation failed. + +Throws: +The first two overloads throw only if `alloc`'s primitives do. The +overloads that involve copy initialization deallocate memory and propagate the +exception if the copy operation throws. +*/ +T[] makeArray(T, Allocator)(auto ref Allocator alloc, size_t length) +{ + if (!length) return null; + auto m = alloc.allocate(T.sizeof * length); + if (!m.ptr) return null; + alias U = Unqual!T; + return () @trusted { return cast(T[]) uninitializedFillDefault(cast(U[]) m); }(); +} + +@system unittest +{ + void test1(A)(auto ref A alloc) + { + int[] a = alloc.makeArray!int(0); + assert(a.length == 0 && a.ptr is null); + a = alloc.makeArray!int(5); + assert(a.length == 5); + static immutable cheatsheet = [0, 0, 0, 0, 0]; + assert(a == cheatsheet); + } + + void test2(A)(auto ref A alloc) + { + static struct S { int x = 42; @disable this(this); } + S[] arr = alloc.makeArray!S(5); + assert(arr.length == 5); + int[] arrInt = () @trusted { return (cast(int*) arr.ptr)[0 .. 5]; }(); + static immutable res = [42, 42, 42, 42, 42]; + assert(arrInt == res); + } + + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.experimental.allocator.mallocator : Mallocator; + (alloc) /*pure nothrow*/ @safe { test1(alloc); test2(alloc);} (GCAllocator.instance); + (alloc) nothrow @safe @nogc { test1(alloc); test2(alloc);} (Mallocator.instance); + test2(theAllocator); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + auto a = theAllocator.makeArray!(shared int)(5); + static assert(is(typeof(a) == shared(int)[])); + assert(a.length == 5); + assert(a.equal([0, 0, 0, 0, 0])); + + auto b = theAllocator.makeArray!(const int)(5); + static assert(is(typeof(b) == const(int)[])); + assert(b.length == 5); + assert(b.equal([0, 0, 0, 0, 0])); + + auto c = theAllocator.makeArray!(immutable int)(5); + static assert(is(typeof(c) == immutable(int)[])); + assert(c.length == 5); + assert(c.equal([0, 0, 0, 0, 0])); +} + +private enum hasPurePostblit(T) = !hasElaborateCopyConstructor!T || + is(typeof(() pure { T.init.__xpostblit(); })); + +private enum hasPureDtor(T) = !hasElaborateDestructor!T || + is(typeof(() pure { T.init.__xdtor(); })); + +// `true` when postblit and destructor of T cannot escape references to itself +private enum canSafelyDeallocPostRewind(T) = hasPurePostblit!T && hasPureDtor!T; + +/// Ditto +T[] makeArray(T, Allocator)(auto ref Allocator alloc, size_t length, + auto ref T init) +{ + if (!length) return null; + auto m = alloc.allocate(T.sizeof * length); + if (!m.ptr) return null; + auto result = () @trusted { return cast(T[]) m; } (); + import std.traits : hasElaborateCopyConstructor; + static if (hasElaborateCopyConstructor!T) + { + scope(failure) + { + static if (canSafelyDeallocPostRewind!T) + () @trusted { alloc.deallocate(m); } (); + else + alloc.deallocate(m); + } + + size_t i = 0; + static if (hasElaborateDestructor!T) + { + scope (failure) + { + foreach (j; 0 .. i) + { + destroy(result[j]); + } + } + } + import std.conv : emplace; + for (; i < length; ++i) + { + emplace!T(&result[i], init); + } + } + else + { + alias U = Unqual!T; + () @trusted { fillWithMemcpy(cast(U[]) result, *(cast(U*) &init)); }(); + } + return result; +} + +/// +@system unittest +{ + import std.algorithm.comparison : equal; + static void test(T)() + { + T[] a = theAllocator.makeArray!T(2); + assert(a.equal([0, 0])); + a = theAllocator.makeArray!T(3, 42); + assert(a.equal([42, 42, 42])); + import std.range : only; + a = theAllocator.makeArray!T(only(42, 43, 44)); + assert(a.equal([42, 43, 44])); + } + test!int(); + test!(shared int)(); + test!(const int)(); + test!(immutable int)(); +} + +@system unittest +{ + void test(A)(auto ref A alloc) + { + long[] a = alloc.makeArray!long(0, 42); + assert(a.length == 0 && a.ptr is null); + a = alloc.makeArray!long(5, 42); + assert(a.length == 5); + assert(a == [ 42, 42, 42, 42, 42 ]); + } + import std.experimental.allocator.gc_allocator : GCAllocator; + (alloc) /*pure nothrow*/ @safe { test(alloc); } (GCAllocator.instance); + test(theAllocator); +} + +// test failure with a pure, failing struct +@safe unittest +{ + import std.exception : assertThrown, enforce; + + struct NoCopy + { + @disable this(); + + this(int b){} + + // can't be copied + this(this) + { + enforce(1 == 2); + } + } + import std.experimental.allocator.mallocator : Mallocator; + assertThrown(makeArray!NoCopy(Mallocator.instance, 10, NoCopy(42))); +} + +// test failure with an impure, failing struct +@system unittest +{ + import std.exception : assertThrown, enforce; + + static int i = 0; + struct Singleton + { + @disable this(); + + this(int b){} + + // can't be copied + this(this) + { + enforce(i++ == 0); + } + + ~this() + { + i--; + } + } + import std.experimental.allocator.mallocator : Mallocator; + assertThrown(makeArray!Singleton(Mallocator.instance, 10, Singleton(42))); +} + +/// Ditto +Unqual!(ElementEncodingType!R)[] makeArray(Allocator, R)(auto ref Allocator alloc, R range) +if (isInputRange!R && !isInfinite!R) +{ + alias T = Unqual!(ElementEncodingType!R); + return makeArray!(T, Allocator, R)(alloc, range); +} + +/// Ditto +T[] makeArray(T, Allocator, R)(auto ref Allocator alloc, R range) +if (isInputRange!R && !isInfinite!R) +{ + static if (isForwardRange!R || hasLength!R) + { + static if (hasLength!R || isNarrowString!R) + immutable length = range.length; + else + immutable length = range.save.walkLength; + + if (!length) return null; + auto m = alloc.allocate(T.sizeof * length); + if (!m.ptr) return null; + auto result = () @trusted { return cast(T[]) m; } (); + + size_t i = 0; + scope (failure) + { + foreach (j; 0 .. i) + { + auto p = () @trusted { return cast(Unqual!T*) &result[j]; }(); + destroy(p); + } + + static if (canSafelyDeallocPostRewind!T) + () @trusted { alloc.deallocate(m); } (); + else + alloc.deallocate(m); + } + + import std.conv : emplaceRef; + static if (isNarrowString!R || isRandomAccessRange!R) + { + foreach (j; 0 .. range.length) + { + emplaceRef!T(result[i++], range[j]); + } + } + else + { + for (; !range.empty; range.popFront, ++i) + { + emplaceRef!T(result[i], range.front); + } + } + + return result; + } + else + { + // Estimated size + size_t estimated = 8; + auto m = alloc.allocate(T.sizeof * estimated); + if (!m.ptr) return null; + auto result = () @trusted { return cast(T[]) m; } (); + + size_t initialized = 0; + void bailout() + { + foreach (i; 0 .. initialized + 1) + { + destroy(result[i]); + } + + static if (canSafelyDeallocPostRewind!T) + () @trusted { alloc.deallocate(m); } (); + else + alloc.deallocate(m); + } + scope (failure) bailout; + + for (; !range.empty; range.popFront, ++initialized) + { + if (initialized == estimated) + { + // Need to reallocate + static if (hasPurePostblit!T) + auto success = () @trusted { return alloc.reallocate(m, T.sizeof * (estimated *= 2)); } (); + else + auto success = alloc.reallocate(m, T.sizeof * (estimated *= 2)); + if (!success) + { + bailout; + return null; + } + result = () @trusted { return cast(T[]) m; } (); + } + import std.conv : emplaceRef; + emplaceRef(result[initialized], range.front); + } + + if (initialized < estimated) + { + // Try to shrink memory, no harm if not possible + static if (hasPurePostblit!T) + auto success = () @trusted { return alloc.reallocate(m, T.sizeof * initialized); } (); + else + auto success = alloc.reallocate(m, T.sizeof * initialized); + if (success) + result = () @trusted { return cast(T[]) m; } (); + } + + return result[0 .. initialized]; + } +} + +@system unittest +{ + void test(A)(auto ref A alloc) + { + long[] a = alloc.makeArray!long((int[]).init); + assert(a.length == 0 && a.ptr is null); + a = alloc.makeArray!long([5, 42]); + assert(a.length == 2); + assert(a == [ 5, 42]); + + // we can also infer the type + auto b = alloc.makeArray([4.0, 2.0]); + static assert(is(typeof(b) == double[])); + assert(b == [4.0, 2.0]); + } + import std.experimental.allocator.gc_allocator : GCAllocator; + (alloc) pure nothrow @safe { test(alloc); } (GCAllocator.instance); + test(theAllocator); +} + +// infer types for strings +@system unittest +{ + void test(A)(auto ref A alloc) + { + auto c = alloc.makeArray("fooπ😜"); + static assert(is(typeof(c) == char[])); + assert(c == "fooπ😜"); + + auto d = alloc.makeArray("fooπ😜"d); + static assert(is(typeof(d) == dchar[])); + assert(d == "fooπ😜"); + + auto w = alloc.makeArray("fooπ😜"w); + static assert(is(typeof(w) == wchar[])); + assert(w == "fooπ😜"); + } + + import std.experimental.allocator.gc_allocator : GCAllocator; + (alloc) pure nothrow @safe { test(alloc); } (GCAllocator.instance); + test(theAllocator); +} + +/*pure*/ nothrow @safe unittest +{ + import std.algorithm.comparison : equal; + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.internal.test.dummyrange; + import std.range : iota; + foreach (DummyType; AllDummyRanges) + { + (alloc) pure nothrow @safe + { + DummyType d; + auto arr = alloc.makeArray(d); + assert(arr.length == 10); + assert(arr.equal(iota(1, 11))); + } (GCAllocator.instance); + } +} + +// test failure with a pure, failing struct +@safe unittest +{ + import std.exception : assertThrown, enforce; + + struct NoCopy + { + int b; + + @disable this(); + + this(int b) + { + this.b = b; + } + + // can't be copied + this(this) + { + enforce(b < 3, "there can only be three elements"); + } + } + import std.experimental.allocator.mallocator : Mallocator; + auto arr = [NoCopy(1), NoCopy(2), NoCopy(3)]; + assertThrown(makeArray!NoCopy(Mallocator.instance, arr)); + + struct NoCopyRange + { + static j = 0; + bool empty() + { + return j > 5; + } + + auto front() + { + return NoCopy(j); + } + + void popFront() + { + j++; + } + } + assertThrown(makeArray!NoCopy(Mallocator.instance, NoCopyRange())); +} + +// test failure with an impure, failing struct +@system unittest +{ + import std.exception : assertThrown, enforce; + + static i = 0; + static maxElements = 2; + struct NoCopy + { + int val; + @disable this(); + + this(int b){ + this.val = i++; + } + + // can't be copied + this(this) + { + enforce(i++ < maxElements, "there can only be four elements"); + } + } + + import std.experimental.allocator.mallocator : Mallocator; + auto arr = [NoCopy(1), NoCopy(2)]; + assertThrown(makeArray!NoCopy(Mallocator.instance, arr)); + + // allow more copies and thus force reallocation + i = 0; + maxElements = 30; + static j = 0; + + struct NoCopyRange + { + bool empty() + { + return j > 100; + } + + auto front() + { + return NoCopy(1); + } + + void popFront() + { + j++; + } + } + assertThrown(makeArray!NoCopy(Mallocator.instance, NoCopyRange())); + + maxElements = 300; + auto arr2 = makeArray!NoCopy(Mallocator.instance, NoCopyRange()); + + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map; + import std.range : iota; + assert(arr2.map!`a.val`.equal(iota(32, 204, 2))); +} + +version (unittest) +{ + private struct ForcedInputRange + { + int[]* array; + pure nothrow @safe @nogc: + bool empty() { return !array || (*array).empty; } + ref int front() { return (*array)[0]; } + void popFront() { *array = (*array)[1 .. $]; } + } +} + +@system unittest +{ + import std.array : array; + import std.range : iota; + int[] arr = iota(10).array; + + void test(A)(auto ref A alloc) + { + ForcedInputRange r; + long[] a = alloc.makeArray!long(r); + assert(a.length == 0 && a.ptr is null); + auto arr2 = arr; + r.array = () @trusted { return &arr2; } (); + a = alloc.makeArray!long(r); + assert(a.length == 10); + assert(a == iota(10).array); + } + import std.experimental.allocator.gc_allocator : GCAllocator; + (alloc) pure nothrow @safe { test(alloc); } (GCAllocator.instance); + test(theAllocator); +} + +/** +Grows $(D array) by appending $(D delta) more elements. The needed memory is +allocated using $(D alloc). The extra elements added are either default- +initialized, filled with copies of $(D init), or initialized with values +fetched from `range`. + +Params: +T = element type of the array being created +alloc = the allocator used for getting memory +array = a reference to the array being grown +delta = number of elements to add (upon success the new length of $(D array) is +$(D array.length + delta)) +init = element used for filling the array +range = range used for initializing the array elements + +Returns: +$(D true) upon success, $(D false) if memory could not be allocated. In the +latter case $(D array) is left unaffected. + +Throws: +The first two overloads throw only if `alloc`'s primitives do. The +overloads that involve copy initialization deallocate memory and propagate the +exception if the copy operation throws. +*/ +bool expandArray(T, Allocator)(auto ref Allocator alloc, ref T[] array, + size_t delta) +{ + if (!delta) return true; + if (array is null) return false; + immutable oldLength = array.length; + void[] buf = array; + if (!alloc.reallocate(buf, buf.length + T.sizeof * delta)) return false; + array = cast(T[]) buf; + array[oldLength .. $].uninitializedFillDefault; + return true; +} + +@system unittest +{ + void test(A)(auto ref A alloc) + { + auto arr = alloc.makeArray!int([1, 2, 3]); + assert(alloc.expandArray(arr, 3)); + assert(arr == [1, 2, 3, 0, 0, 0]); + } + import std.experimental.allocator.gc_allocator : GCAllocator; + test(GCAllocator.instance); + test(theAllocator); +} + +/// Ditto +bool expandArray(T, Allocator)(auto ref Allocator alloc, ref T[] array, + size_t delta, auto ref T init) +{ + if (!delta) return true; + if (array is null) return false; + void[] buf = array; + if (!alloc.reallocate(buf, buf.length + T.sizeof * delta)) return false; + immutable oldLength = array.length; + array = cast(T[]) buf; + scope(failure) array[oldLength .. $].uninitializedFillDefault; + import std.algorithm.mutation : uninitializedFill; + array[oldLength .. $].uninitializedFill(init); + return true; +} + +@system unittest +{ + void test(A)(auto ref A alloc) + { + auto arr = alloc.makeArray!int([1, 2, 3]); + assert(alloc.expandArray(arr, 3, 1)); + assert(arr == [1, 2, 3, 1, 1, 1]); + } + import std.experimental.allocator.gc_allocator : GCAllocator; + test(GCAllocator.instance); + test(theAllocator); +} + +/// Ditto +bool expandArray(T, Allocator, R)(auto ref Allocator alloc, ref T[] array, + R range) +if (isInputRange!R) +{ + if (array is null) return false; + static if (isForwardRange!R) + { + immutable delta = walkLength(range.save); + if (!delta) return true; + immutable oldLength = array.length; + + // Reallocate support memory + void[] buf = array; + if (!alloc.reallocate(buf, buf.length + T.sizeof * delta)) + { + return false; + } + array = cast(T[]) buf; + // At this point we're committed to the new length. + + auto toFill = array[oldLength .. $]; + scope (failure) + { + // Fill the remainder with default-constructed data + toFill.uninitializedFillDefault; + } + + for (; !range.empty; range.popFront, toFill.popFront) + { + assert(!toFill.empty); + import std.conv : emplace; + emplace!T(&toFill.front, range.front); + } + assert(toFill.empty); + } + else + { + scope(failure) + { + // The last element didn't make it, fill with default + array[$ - 1 .. $].uninitializedFillDefault; + } + void[] buf = array; + for (; !range.empty; range.popFront) + { + if (!alloc.reallocate(buf, buf.length + T.sizeof)) + { + array = cast(T[]) buf; + return false; + } + import std.conv : emplace; + emplace!T(buf[$ - T.sizeof .. $], range.front); + } + + array = cast(T[]) buf; + } + return true; +} + +/// +@system unittest +{ + auto arr = theAllocator.makeArray!int([1, 2, 3]); + assert(theAllocator.expandArray(arr, 2)); + assert(arr == [1, 2, 3, 0, 0]); + import std.range : only; + assert(theAllocator.expandArray(arr, only(4, 5))); + assert(arr == [1, 2, 3, 0, 0, 4, 5]); +} + +@system unittest +{ + auto arr = theAllocator.makeArray!int([1, 2, 3]); + ForcedInputRange r; + int[] b = [ 1, 2, 3, 4 ]; + auto temp = b; + r.array = &temp; + assert(theAllocator.expandArray(arr, r)); + assert(arr == [1, 2, 3, 1, 2, 3, 4]); +} + +/** +Shrinks an array by $(D delta) elements. + +If $(D array.length < delta), does nothing and returns `false`. Otherwise, +destroys the last $(D array.length - delta) elements in the array and then +reallocates the array's buffer. If reallocation fails, fills the array with +default-initialized data. + +Params: +T = element type of the array being created +alloc = the allocator used for getting memory +array = a reference to the array being shrunk +delta = number of elements to remove (upon success the new length of $(D array) is $(D array.length - delta)) + +Returns: +`true` upon success, `false` if memory could not be reallocated. In the latter +case, the slice $(D array[$ - delta .. $]) is left with default-initialized +elements. + +Throws: +The first two overloads throw only if `alloc`'s primitives do. The +overloads that involve copy initialization deallocate memory and propagate the +exception if the copy operation throws. +*/ +bool shrinkArray(T, Allocator)(auto ref Allocator alloc, + ref T[] array, size_t delta) +{ + if (delta > array.length) return false; + + // Destroy elements. If a destructor throws, fill the already destroyed + // stuff with the default initializer. + { + size_t destroyed; + scope(failure) + { + array[$ - delta .. $][0 .. destroyed].uninitializedFillDefault; + } + foreach (ref e; array[$ - delta .. $]) + { + e.destroy; + ++destroyed; + } + } + + if (delta == array.length) + { + alloc.deallocate(array); + array = null; + return true; + } + + void[] buf = array; + if (!alloc.reallocate(buf, buf.length - T.sizeof * delta)) + { + // urgh, at least fill back with default + array[$ - delta .. $].uninitializedFillDefault; + return false; + } + array = cast(T[]) buf; + return true; +} + +/// +@system unittest +{ + int[] a = theAllocator.makeArray!int(100, 42); + assert(a.length == 100); + assert(theAllocator.shrinkArray(a, 98)); + assert(a.length == 2); + assert(a == [42, 42]); +} + +@system unittest +{ + void test(A)(auto ref A alloc) + { + long[] a = alloc.makeArray!long((int[]).init); + assert(a.length == 0 && a.ptr is null); + a = alloc.makeArray!long(100, 42); + assert(alloc.shrinkArray(a, 98)); + assert(a.length == 2); + assert(a == [ 42, 42]); + } + import std.experimental.allocator.gc_allocator : GCAllocator; + test(GCAllocator.instance); + test(theAllocator); +} + +/** + +Destroys and then deallocates (using $(D alloc)) the object pointed to by a +pointer, the class object referred to by a $(D class) or $(D interface) +reference, or an entire array. It is assumed the respective entities had been +allocated with the same allocator. + +*/ +void dispose(A, T)(auto ref A alloc, T* p) +{ + static if (hasElaborateDestructor!T) + { + destroy(*p); + } + alloc.deallocate((cast(void*) p)[0 .. T.sizeof]); +} + +/// Ditto +void dispose(A, T)(auto ref A alloc, T p) +if (is(T == class) || is(T == interface)) +{ + if (!p) return; + static if (is(T == interface)) + { + version (Windows) + { + import core.sys.windows.unknwn : IUnknown; + static assert(!is(T: IUnknown), "COM interfaces can't be destroyed in " + ~ __PRETTY_FUNCTION__); + } + auto ob = cast(Object) p; + } + else + alias ob = p; + auto support = (cast(void*) ob)[0 .. typeid(ob).initializer.length]; + destroy(p); + alloc.deallocate(support); +} + +/// Ditto +void dispose(A, T)(auto ref A alloc, T[] array) +{ + static if (hasElaborateDestructor!(typeof(array[0]))) + { + foreach (ref e; array) + { + destroy(e); + } + } + alloc.deallocate(array); +} + +@system unittest +{ + static int x; + static interface I + { + void method(); + } + static class A : I + { + int y; + override void method() { x = 21; } + ~this() { x = 42; } + } + static class B : A + { + } + auto a = theAllocator.make!A; + a.method(); + assert(x == 21); + theAllocator.dispose(a); + assert(x == 42); + + B b = theAllocator.make!B; + b.method(); + assert(x == 21); + theAllocator.dispose(b); + assert(x == 42); + + I i = theAllocator.make!B; + i.method(); + assert(x == 21); + theAllocator.dispose(i); + assert(x == 42); + + int[] arr = theAllocator.makeArray!int(43); + theAllocator.dispose(arr); +} + +@system unittest //bugzilla 15721 +{ + import std.experimental.allocator.mallocator : Mallocator; + + interface Foo {} + class Bar: Foo {} + + Bar bar; + Foo foo; + bar = Mallocator.instance.make!Bar; + foo = cast(Foo) bar; + Mallocator.instance.dispose(foo); +} + +/** +Allocates a multidimensional array of elements of type T. + +Params: +N = number of dimensions +T = element type of an element of the multidimensional arrat +alloc = the allocator used for getting memory +lengths = static array containing the size of each dimension + +Returns: +An N-dimensional array with individual elements of type T. +*/ +auto makeMultidimensionalArray(T, Allocator, size_t N)(auto ref Allocator alloc, size_t[N] lengths...) +{ + static if (N == 1) + { + return makeArray!T(alloc, lengths[0]); + } + else + { + alias E = typeof(makeMultidimensionalArray!(T, Allocator, N - 1)(alloc, lengths[1 .. $])); + auto ret = makeArray!E(alloc, lengths[0]); + foreach (ref e; ret) + e = makeMultidimensionalArray!(T, Allocator, N - 1)(alloc, lengths[1 .. $]); + return ret; + } +} + +/// +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + + auto mArray = Mallocator.instance.makeMultidimensionalArray!int(2, 3, 6); + + // deallocate when exiting scope + scope(exit) + { + Mallocator.instance.disposeMultidimensionalArray(mArray); + } + + assert(mArray.length == 2); + foreach (lvl2Array; mArray) + { + assert(lvl2Array.length == 3); + foreach (lvl3Array; lvl2Array) + assert(lvl3Array.length == 6); + } +} + +/** +Destroys and then deallocates a multidimensional array, assuming it was +created with makeMultidimensionalArray and the same allocator was used. + +Params: +T = element type of an element of the multidimensional array +alloc = the allocator used for getting memory +array = the multidimensional array that is to be deallocated +*/ +void disposeMultidimensionalArray(T, Allocator)(auto ref Allocator alloc, T[] array) +{ + static if (isArray!T) + { + foreach (ref e; array) + disposeMultidimensionalArray(alloc, e); + } + + dispose(alloc, array); +} + +/// +@system unittest +{ + struct TestAllocator + { + import std.experimental.allocator.common : platformAlignment; + import std.experimental.allocator.mallocator : Mallocator; + + alias allocator = Mallocator.instance; + + private static struct ByteRange + { + void* ptr; + size_t length; + } + + private ByteRange[] _allocations; + + enum uint alignment = platformAlignment; + + void[] allocate(size_t numBytes) + { + auto ret = allocator.allocate(numBytes); + _allocations ~= ByteRange(ret.ptr, ret.length); + return ret; + } + + bool deallocate(void[] bytes) + { + import std.algorithm.mutation : remove; + import std.algorithm.searching : canFind; + + bool pred(ByteRange other) + { return other.ptr == bytes.ptr && other.length == bytes.length; } + + assert(_allocations.canFind!pred); + + _allocations = _allocations.remove!pred; + return allocator.deallocate(bytes); + } + + ~this() + { + assert(!_allocations.length); + } + } + + TestAllocator allocator; + + auto mArray = allocator.makeMultidimensionalArray!int(2, 3, 5, 6, 7, 2); + + allocator.disposeMultidimensionalArray(mArray); +} + +/** + +Returns a dynamically-typed $(D CAllocator) built around a given statically- +typed allocator $(D a) of type $(D A). Passing a pointer to the allocator +creates a dynamic allocator around the allocator pointed to by the pointer, +without attempting to copy or move it. Passing the allocator by value or +reference behaves as follows. + +$(UL +$(LI If $(D A) has no state, the resulting object is allocated in static +shared storage.) +$(LI If $(D A) has state and is copyable, the result will store a copy of it +within. The result itself is allocated in its own statically-typed allocator.) +$(LI If $(D A) has state and is not copyable, the result will move the +passed-in argument into the result. The result itself is allocated in its own +statically-typed allocator.) +) + +*/ +CAllocatorImpl!A allocatorObject(A)(auto ref A a) +if (!isPointer!A) +{ + import std.conv : emplace; + static if (stateSize!A == 0) + { + enum s = stateSize!(CAllocatorImpl!A).divideRoundUp(ulong.sizeof); + static __gshared ulong[s] state; + static __gshared CAllocatorImpl!A result; + if (!result) + { + // Don't care about a few races + result = emplace!(CAllocatorImpl!A)(state[]); + } + assert(result); + return result; + } + else static if (is(typeof({ A b = a; A c = b; }))) // copyable + { + auto state = a.allocate(stateSize!(CAllocatorImpl!A)); + import std.traits : hasMember; + static if (hasMember!(A, "deallocate")) + { + scope(failure) a.deallocate(state); + } + return cast(CAllocatorImpl!A) emplace!(CAllocatorImpl!A)(state); + } + else // the allocator object is not copyable + { + // This is sensitive... create on the stack and then move + enum s = stateSize!(CAllocatorImpl!A).divideRoundUp(ulong.sizeof); + ulong[s] state; + import std.algorithm.mutation : move; + emplace!(CAllocatorImpl!A)(state[], move(a)); + auto dynState = a.allocate(stateSize!(CAllocatorImpl!A)); + // Bitblast the object in its final destination + dynState[] = state[]; + return cast(CAllocatorImpl!A) dynState.ptr; + } +} + +/// Ditto +CAllocatorImpl!(A, Yes.indirect) allocatorObject(A)(A* pa) +{ + assert(pa); + import std.conv : emplace; + auto state = pa.allocate(stateSize!(CAllocatorImpl!(A, Yes.indirect))); + import std.traits : hasMember; + static if (hasMember!(A, "deallocate")) + { + scope(failure) pa.deallocate(state); + } + return emplace!(CAllocatorImpl!(A, Yes.indirect)) + (state, pa); +} + +/// +@system unittest +{ + import std.experimental.allocator.mallocator : Mallocator; + IAllocator a = allocatorObject(Mallocator.instance); + auto b = a.allocate(100); + assert(b.length == 100); + assert(a.deallocate(b)); + + // The in-situ region must be used by pointer + import std.experimental.allocator.building_blocks.region : InSituRegion; + auto r = InSituRegion!1024(); + a = allocatorObject(&r); + b = a.allocate(200); + assert(b.length == 200); + // In-situ regions can deallocate the last allocation + assert(a.deallocate(b)); +} + +/** + +Returns a dynamically-typed $(D CSharedAllocator) built around a given statically- +typed allocator $(D a) of type $(D A). Passing a pointer to the allocator +creates a dynamic allocator around the allocator pointed to by the pointer, +without attempting to copy or move it. Passing the allocator by value or +reference behaves as follows. + +$(UL +$(LI If $(D A) has no state, the resulting object is allocated in static +shared storage.) +$(LI If $(D A) has state and is copyable, the result will store a copy of it +within. The result itself is allocated in its own statically-typed allocator.) +$(LI If $(D A) has state and is not copyable, the result will move the +passed-in argument into the result. The result itself is allocated in its own +statically-typed allocator.) +) + +*/ +shared(CSharedAllocatorImpl!A) sharedAllocatorObject(A)(auto ref A a) +if (!isPointer!A) +{ + import std.conv : emplace; + static if (stateSize!A == 0) + { + enum s = stateSize!(CSharedAllocatorImpl!A).divideRoundUp(ulong.sizeof); + static __gshared ulong[s] state; + static shared CSharedAllocatorImpl!A result; + if (!result) + { + // Don't care about a few races + result = cast(shared + CSharedAllocatorImpl!A)(emplace!(CSharedAllocatorImpl!A)(state[])); + } + assert(result); + return result; + } + else static if (is(typeof({ shared A b = a; shared A c = b; }))) // copyable + { + auto state = a.allocate(stateSize!(CSharedAllocatorImpl!A)); + import std.traits : hasMember; + static if (hasMember!(A, "deallocate")) + { + scope(failure) a.deallocate(state); + } + return emplace!(shared CSharedAllocatorImpl!A)(state); + } + else // the allocator object is not copyable + { + assert(0, "Not yet implemented"); + } +} + +/// Ditto +shared(CSharedAllocatorImpl!(A, Yes.indirect)) sharedAllocatorObject(A)(A* pa) +{ + assert(pa); + import std.conv : emplace; + auto state = pa.allocate(stateSize!(CSharedAllocatorImpl!(A, Yes.indirect))); + import std.traits : hasMember; + static if (hasMember!(A, "deallocate")) + { + scope(failure) pa.deallocate(state); + } + return emplace!(shared CSharedAllocatorImpl!(A, Yes.indirect))(state, pa); +} + + +/** + +Implementation of `IAllocator` using `Allocator`. This adapts a +statically-built allocator type to `IAllocator` that is directly usable by +non-templated code. + +Usually `CAllocatorImpl` is used indirectly by calling $(LREF theAllocator). +*/ +class CAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect) + : IAllocator +{ + import std.traits : hasMember; + + /** + The implementation is available as a public member. + */ + static if (indirect) + { + private Allocator* pimpl; + ref Allocator impl() + { + return *pimpl; + } + this(Allocator* pa) + { + pimpl = pa; + } + } + else + { + static if (stateSize!Allocator) Allocator impl; + else alias impl = Allocator.instance; + } + + /// Returns `impl.alignment`. + override @property uint alignment() + { + return impl.alignment; + } + + /** + Returns `impl.goodAllocSize(s)`. + */ + override size_t goodAllocSize(size_t s) + { + return impl.goodAllocSize(s); + } + + /** + Returns `impl.allocate(s)`. + */ + override void[] allocate(size_t s, TypeInfo ti = null) + { + return impl.allocate(s); + } + + /** + If `impl.alignedAllocate` exists, calls it and returns the result. + Otherwise, always returns `null`. + */ + override void[] alignedAllocate(size_t s, uint a) + { + static if (hasMember!(Allocator, "alignedAllocate")) + return impl.alignedAllocate(s, a); + else + return null; + } + + /** + If `Allocator` implements `owns`, forwards to it. Otherwise, returns + `Ternary.unknown`. + */ + override Ternary owns(void[] b) + { + static if (hasMember!(Allocator, "owns")) return impl.owns(b); + else return Ternary.unknown; + } + + /// Returns $(D impl.expand(b, s)) if defined, `false` otherwise. + override bool expand(ref void[] b, size_t s) + { + static if (hasMember!(Allocator, "expand")) + return impl.expand(b, s); + else + return s == 0; + } + + /// Returns $(D impl.reallocate(b, s)). + override bool reallocate(ref void[] b, size_t s) + { + return impl.reallocate(b, s); + } + + /// Forwards to `impl.alignedReallocate` if defined, `false` otherwise. + bool alignedReallocate(ref void[] b, size_t s, uint a) + { + static if (!hasMember!(Allocator, "alignedAllocate")) + { + return false; + } + else + { + return impl.alignedReallocate(b, s, a); + } + } + + // Undocumented for now + Ternary resolveInternalPointer(const void* p, ref void[] result) + { + static if (hasMember!(Allocator, "resolveInternalPointer")) + { + return impl.resolveInternalPointer(p, result); + } + else + { + return Ternary.unknown; + } + } + + /** + If `impl.deallocate` is not defined, returns `false`. Otherwise it forwards + the call. + */ + override bool deallocate(void[] b) + { + static if (hasMember!(Allocator, "deallocate")) + { + return impl.deallocate(b); + } + else + { + return false; + } + } + + /** + Calls `impl.deallocateAll()` and returns the result if defined, + otherwise returns `false`. + */ + override bool deallocateAll() + { + static if (hasMember!(Allocator, "deallocateAll")) + { + return impl.deallocateAll(); + } + else + { + return false; + } + } + + /** + Forwards to `impl.empty()` if defined, otherwise returns `Ternary.unknown`. + */ + override Ternary empty() + { + static if (hasMember!(Allocator, "empty")) + { + return Ternary(impl.empty); + } + else + { + return Ternary.unknown; + } + } + + /** + Returns `impl.allocateAll()` if present, `null` otherwise. + */ + override void[] allocateAll() + { + static if (hasMember!(Allocator, "allocateAll")) + { + return impl.allocateAll(); + } + else + { + return null; + } + } +} + +/** + +Implementation of `ISharedAllocator` using `Allocator`. This adapts a +statically-built, shareable across threads, allocator type to `ISharedAllocator` +that is directly usable by non-templated code. + +Usually `CSharedAllocatorImpl` is used indirectly by calling +$(LREF processAllocator). +*/ +class CSharedAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect) + : ISharedAllocator +{ + import std.traits : hasMember; + + /** + The implementation is available as a public member. + */ + static if (indirect) + { + private shared Allocator* pimpl; + ref Allocator impl() shared + { + return *pimpl; + } + this(Allocator* pa) shared + { + pimpl = pa; + } + } + else + { + static if (stateSize!Allocator) shared Allocator impl; + else alias impl = Allocator.instance; + } + + /// Returns `impl.alignment`. + override @property uint alignment() shared + { + return impl.alignment; + } + + /** + Returns `impl.goodAllocSize(s)`. + */ + override size_t goodAllocSize(size_t s) shared + { + return impl.goodAllocSize(s); + } + + /** + Returns `impl.allocate(s)`. + */ + override void[] allocate(size_t s, TypeInfo ti = null) shared + { + return impl.allocate(s); + } + + /** + If `impl.alignedAllocate` exists, calls it and returns the result. + Otherwise, always returns `null`. + */ + override void[] alignedAllocate(size_t s, uint a) shared + { + static if (hasMember!(Allocator, "alignedAllocate")) + return impl.alignedAllocate(s, a); + else + return null; + } + + /** + If `Allocator` implements `owns`, forwards to it. Otherwise, returns + `Ternary.unknown`. + */ + override Ternary owns(void[] b) shared + { + static if (hasMember!(Allocator, "owns")) return impl.owns(b); + else return Ternary.unknown; + } + + /// Returns $(D impl.expand(b, s)) if defined, `false` otherwise. + override bool expand(ref void[] b, size_t s) shared + { + static if (hasMember!(Allocator, "expand")) + return impl.expand(b, s); + else + return s == 0; + } + + /// Returns $(D impl.reallocate(b, s)). + override bool reallocate(ref void[] b, size_t s) shared + { + return impl.reallocate(b, s); + } + + /// Forwards to `impl.alignedReallocate` if defined, `false` otherwise. + bool alignedReallocate(ref void[] b, size_t s, uint a) shared + { + static if (!hasMember!(Allocator, "alignedAllocate")) + { + return false; + } + else + { + return impl.alignedReallocate(b, s, a); + } + } + + // Undocumented for now + Ternary resolveInternalPointer(const void* p, ref void[] result) shared + { + static if (hasMember!(Allocator, "resolveInternalPointer")) + { + return impl.resolveInternalPointer(p, result); + } + else + { + return Ternary.unknown; + } + } + + /** + If `impl.deallocate` is not defined, returns `false`. Otherwise it forwards + the call. + */ + override bool deallocate(void[] b) shared + { + static if (hasMember!(Allocator, "deallocate")) + { + return impl.deallocate(b); + } + else + { + return false; + } + } + + /** + Calls `impl.deallocateAll()` and returns the result if defined, + otherwise returns `false`. + */ + override bool deallocateAll() shared + { + static if (hasMember!(Allocator, "deallocateAll")) + { + return impl.deallocateAll(); + } + else + { + return false; + } + } + + /** + Forwards to `impl.empty()` if defined, otherwise returns `Ternary.unknown`. + */ + override Ternary empty() shared + { + static if (hasMember!(Allocator, "empty")) + { + return Ternary(impl.empty); + } + else + { + return Ternary.unknown; + } + } + + /** + Returns `impl.allocateAll()` if present, `null` otherwise. + */ + override void[] allocateAll() shared + { + static if (hasMember!(Allocator, "allocateAll")) + { + return impl.allocateAll(); + } + else + { + return null; + } + } +} + + +// Example in intro above +@system unittest +{ + // Allocate an int, initialize it with 42 + int* p = theAllocator.make!int(42); + assert(*p == 42); + + // Destroy and deallocate it + theAllocator.dispose(p); + + // Allocate using the global process allocator + p = processAllocator.make!int(100); + assert(*p == 100); + + // Destroy and deallocate + processAllocator.dispose(p); + + // Create an array of 50 doubles initialized to -1.0 + double[] arr = theAllocator.makeArray!double(50, -1.0); + + // Check internal pointer + void[] result; + assert(theAllocator.resolveInternalPointer(null, result) == Ternary.no); + Ternary r = theAllocator.resolveInternalPointer(arr.ptr, result); + assert(result.ptr is arr.ptr && result.length >= arr.length); + + // Append two zeros to it + theAllocator.expandArray(arr, 2, 0.0); + // On second thought, take that back + theAllocator.shrinkArray(arr, 2); + // Destroy and deallocate + theAllocator.dispose(arr); +} + +__EOF__ + +/** + +Stores an allocator object in thread-local storage (i.e. non-$(D shared) D +global). $(D ThreadLocal!A) is a subtype of $(D A) so it appears to implement +$(D A)'s allocator primitives. + +$(D A) must hold state, otherwise $(D ThreadLocal!A) refuses instantiation. This +means e.g. $(D ThreadLocal!Mallocator) does not work because $(D Mallocator)'s +state is not stored as members of $(D Mallocator), but instead is hidden in the +C library implementation. + +*/ +struct ThreadLocal(A) +{ + static assert(stateSize!A, + typeof(A).stringof + ~ " does not have state so it cannot be used with ThreadLocal"); + + /** + The allocator instance. + */ + static A instance; + + /** + `ThreadLocal!A` is a subtype of `A` so it appears to implement `A`'s + allocator primitives. + */ + alias instance this; + + /** + `ThreadLocal` disables all constructors. The intended usage is + `ThreadLocal!A.instance`. + */ + @disable this(); + /// Ditto + @disable this(this); +} + +/// +unittest +{ + static assert(!is(ThreadLocal!Mallocator)); + static assert(!is(ThreadLocal!GCAllocator)); + alias ThreadLocal!(FreeList!(GCAllocator, 0, 8)) Allocator; + auto b = Allocator.instance.allocate(5); + static assert(hasMember!(Allocator, "allocate")); +} + +/* +(Not public.) + +A binary search tree that uses no allocation of its own. Instead, it relies on +user code to allocate nodes externally. Then $(D EmbeddedTree)'s primitives wire +the nodes appropriately. + +Warning: currently $(D EmbeddedTree) is not using rebalancing, so it may +degenerate. A red-black tree implementation storing the color with one of the +pointers is planned for the future. +*/ +private struct EmbeddedTree(T, alias less) +{ + static struct Node + { + T payload; + Node* left, right; + } + + private Node* root; + + private Node* insert(Node* n, ref Node* backref) + { + backref = n; + n.left = n.right = null; + return n; + } + + Node* find(Node* data) + { + for (auto n = root; n; ) + { + if (less(data, n)) + { + n = n.left; + } + else if (less(n, data)) + { + n = n.right; + } + else + { + return n; + } + } + return null; + } + + Node* insert(Node* data) + { + if (!root) + { + root = data; + data.left = data.right = null; + return root; + } + auto n = root; + for (;;) + { + if (less(data, n)) + { + if (!n.left) + { + // Found insertion point + return insert(data, n.left); + } + n = n.left; + } + else if (less(n, data)) + { + if (!n.right) + { + // Found insertion point + return insert(data, n.right); + } + n = n.right; + } + else + { + // Found + return n; + } + if (!n) return null; + } + } + + Node* remove(Node* data) + { + auto n = root; + Node* parent = null; + for (;;) + { + if (!n) return null; + if (less(data, n)) + { + parent = n; + n = n.left; + } + else if (less(n, data)) + { + parent = n; + n = n.right; + } + else + { + // Found + remove(n, parent); + return n; + } + } + } + + private void remove(Node* n, Node* parent) + { + assert(n); + assert(!parent || parent.left == n || parent.right == n); + Node** referrer = parent + ? (parent.left == n ? &parent.left : &parent.right) + : &root; + if (!n.left) + { + *referrer = n.right; + } + else if (!n.right) + { + *referrer = n.left; + } + else + { + // Find the leftmost child in the right subtree + auto leftmost = n.right; + Node** leftmostReferrer = &n.right; + while (leftmost.left) + { + leftmostReferrer = &leftmost.left; + leftmost = leftmost.left; + } + // Unlink leftmost from there + *leftmostReferrer = leftmost.right; + // Link leftmost in lieu of n + leftmost.left = n.left; + leftmost.right = n.right; + *referrer = leftmost; + } + } + + Ternary empty() const + { + return Ternary(!root); + } + + void dump() + { + writeln(typeid(this), " @ ", cast(void*) &this); + dump(root, 3); + } + + void dump(Node* r, uint indent) + { + write(repeat(' ', indent).array); + if (!r) + { + writeln("(null)"); + return; + } + writeln(r.payload, " @ ", cast(void*) r); + dump(r.left, indent + 3); + dump(r.right, indent + 3); + } + + void assertSane() + { + static bool isBST(Node* r, Node* lb, Node* ub) + { + if (!r) return true; + if (lb && !less(lb, r)) return false; + if (ub && !less(r, ub)) return false; + return isBST(r.left, lb, r) && + isBST(r.right, r, ub); + } + if (isBST(root, null, null)) return; + dump; + assert(0); + } +} + +unittest +{ + alias a = GCAllocator.instance; + alias Tree = EmbeddedTree!(int, (a, b) => a.payload < b.payload); + Tree t; + assert(t.empty); + int[] vals = [ 6, 3, 9, 1, 0, 2, 8, 11 ]; + foreach (v; vals) + { + auto n = new Tree.Node(v, null, null); + assert(t.insert(n)); + assert(n); + t.assertSane; + } + assert(!t.empty); + foreach (v; vals) + { + Tree.Node n = { v }; + assert(t.remove(&n)); + t.assertSane; + } + assert(t.empty); +} + +/* + +$(D InternalPointersTree) adds a primitive on top of another allocator: calling +$(D resolveInternalPointer(p)) returns the block within which the internal +pointer $(D p) lies. Pointers right after the end of allocated blocks are also +considered internal. + +The implementation stores three additional words with each allocation (one for +the block size and two for search management). + +*/ +private struct InternalPointersTree(Allocator) +{ + alias Tree = EmbeddedTree!(size_t, + (a, b) => cast(void*) a + a.payload < cast(void*) b); + alias Parent = AffixAllocator!(Allocator, Tree.Node); + + // Own state + private Tree blockMap; + + alias alignment = Parent.alignment; + + /** + The implementation is available as a public member. + */ + static if (stateSize!Parent) Parent parent; + else alias parent = Parent.instance; + + /// Allocator API. + void[] allocate(size_t bytes) + { + auto r = parent.allocate(bytes); + if (!r.ptr) return r; + Tree.Node* n = &parent.prefix(r); + n.payload = bytes; + blockMap.insert(n) || assert(0); + return r; + } + + /// Ditto + bool deallocate(void[] b) + { + if (!b.ptr) return; + Tree.Node* n = &parent.prefix(b); + blockMap.remove(n) || assert(false); + parent.deallocate(b); + return true; + } + + /// Ditto + static if (hasMember!(Allocator, "reallocate")) + bool reallocate(ref void[] b, size_t s) + { + auto n = &parent.prefix(b); + assert(n.payload == b.length); + blockMap.remove(n) || assert(0); + if (!parent.reallocate(b, s)) + { + // Failed, must reinsert the same node in the tree + assert(n.payload == b.length); + blockMap.insert(n) || assert(0); + return false; + } + // Insert the new node + n = &parent.prefix(b); + n.payload = s; + blockMap.insert(n) || assert(0); + return true; + } + + /// Ditto + Ternary owns(void[] b) + { + void[] result; + return resolveInternalPointer(b.ptr, result); + } + + /// Ditto + Ternary empty() + { + return Ternary(blockMap.empty); + } + + /** Returns the block inside which $(D p) resides, or $(D null) if the + pointer does not belong. + */ + Ternary resolveInternalPointer(const void* p, ref void[] result) + { + // Must define a custom find + Tree.Node* find() + { + for (auto n = blockMap.root; n; ) + { + if (p < n) + { + n = n.left; + } + else if (p > (cast(void*) (n + 1)) + n.payload) + { + n = n.right; + } + else + { + return n; + } + } + return null; + } + + auto n = find(); + if (!n) return Ternary.no; + result = (cast(void*) (n + 1))[0 .. n.payload]; + return Ternary.yes; + } +} + +unittest +{ + InternalPointersTree!(Mallocator) a; + int[] vals = [ 6, 3, 9, 1, 2, 8, 11 ]; + void[][] allox; + foreach (v; vals) + { + allox ~= a.allocate(v); + } + a.blockMap.assertSane; + + foreach (b; allox) + { + void[] p; + Ternary r = a.resolveInternalPointer(b.ptr, p); + assert(p.ptr is b.ptr && p.length >= b.length); + r = a.resolveInternalPointer(b.ptr + b.length, p); + assert(p.ptr is b.ptr && p.length >= b.length); + r = a.resolveInternalPointer(b.ptr + b.length / 2, p); + assert(p.ptr is b.ptr && p.length >= b.length); + auto bogus = new void[b.length]; + assert(a.resolveInternalPointer(bogus.ptr, p) == Ternary.no); + } + + foreach (b; allox.randomCover) + { + a.deallocate(b); + } + + assert(a.empty); +} + +//version (std_allocator_benchmark) +unittest +{ + static void testSpeed(A)() + { + static if (stateSize!A) A a; + else alias a = A.instance; + + void[][128] bufs; + + import std.random; + foreach (i; 0 .. 100_000) + { + auto j = uniform(0, bufs.length); + switch (uniform(0, 2)) + { + case 0: + a.deallocate(bufs[j]); + bufs[j] = a.allocate(uniform(0, 4096)); + break; + case 1: + a.deallocate(bufs[j]); + bufs[j] = null; + break; + default: + assert(0); + } + } + } + + alias FList = FreeList!(GCAllocator, 0, unbounded); + alias A = Segregator!( + 8, FreeList!(GCAllocator, 0, 8), + 128, Bucketizer!(FList, 1, 128, 16), + 256, Bucketizer!(FList, 129, 256, 32), + 512, Bucketizer!(FList, 257, 512, 64), + 1024, Bucketizer!(FList, 513, 1024, 128), + 2048, Bucketizer!(FList, 1025, 2048, 256), + 3584, Bucketizer!(FList, 2049, 3584, 512), + 4072 * 1024, AllocatorList!( + (size_t n) => BitmappedBlock!(4096)(GCAllocator.instance.allocate( + max(n, 4072 * 1024)))), + GCAllocator + ); + + import std.datetime, std.experimental.allocator.null_allocator; + if (false) writeln(benchmark!( + testSpeed!NullAllocator, + testSpeed!Mallocator, + testSpeed!GCAllocator, + testSpeed!(ThreadLocal!A), + testSpeed!(A), + )(20)[].map!(t => t.to!("seconds", double))); +} + +unittest +{ + auto a = allocatorObject(Mallocator.instance); + auto b = a.allocate(100); + assert(b.length == 100); + + FreeList!(GCAllocator, 0, 8) fl; + auto sa = allocatorObject(fl); + b = a.allocate(101); + assert(b.length == 101); + + FallbackAllocator!(InSituRegion!(10240, 64), GCAllocator) fb; + // Doesn't work yet... + //a = allocatorObject(fb); + //b = a.allocate(102); + //assert(b.length == 102); +} + +/// +unittest +{ + /// Define an allocator bound to the built-in GC. + IAllocator alloc = allocatorObject(GCAllocator.instance); + auto b = alloc.allocate(42); + assert(b.length == 42); + assert(alloc.deallocate(b) == Ternary.yes); + + // Define an elaborate allocator and bind it to the class API. + // Note that the same variable "alloc" is used. + alias FList = FreeList!(GCAllocator, 0, unbounded); + alias A = ThreadLocal!( + Segregator!( + 8, FreeList!(GCAllocator, 0, 8), + 128, Bucketizer!(FList, 1, 128, 16), + 256, Bucketizer!(FList, 129, 256, 32), + 512, Bucketizer!(FList, 257, 512, 64), + 1024, Bucketizer!(FList, 513, 1024, 128), + 2048, Bucketizer!(FList, 1025, 2048, 256), + 3584, Bucketizer!(FList, 2049, 3584, 512), + 4072 * 1024, AllocatorList!( + (n) => BitmappedBlock!(4096)(GCAllocator.instance.allocate( + max(n, 4072 * 1024)))), + GCAllocator + ) + ); + + auto alloc2 = allocatorObject(A.instance); + b = alloc.allocate(101); + assert(alloc.deallocate(b) == Ternary.yes); +} diff --git a/libphobos/src/std/experimental/allocator/showcase.d b/libphobos/src/std/experimental/allocator/showcase.d new file mode 100644 index 0000000..6985e5d --- /dev/null +++ b/libphobos/src/std/experimental/allocator/showcase.d @@ -0,0 +1,92 @@ +/** + +Collection of typical and useful prebuilt allocators using the given +components. User code would typically import this module and use its +facilities, or import individual heap building blocks and assemble them. + +*/ +module std.experimental.allocator.showcase; + +import std.experimental.allocator.building_blocks.fallback_allocator, + std.experimental.allocator.gc_allocator, + std.experimental.allocator.building_blocks.region; +import std.traits : hasMember; + +/** + +Allocator that uses stack allocation for up to $(D stackSize) bytes and +then falls back to $(D Allocator). Defined as: + +---- +alias StackFront(size_t stackSize, Allocator) = + FallbackAllocator!( + InSituRegion!(stackSize, Allocator.alignment, + hasMember!(Allocator, "deallocate") + ? Yes.defineDeallocate + : No.defineDeallocate), + Allocator); +---- + +Choosing `stackSize` is as always a compromise. Too small a size exhausts the +stack storage after a few allocations, after which there are no gains over the +backup allocator. Too large a size increases the stack consumed by the thread +and may end up worse off because it explores cold portions of the stack. + +*/ +alias StackFront(size_t stackSize, Allocator = GCAllocator) = + FallbackAllocator!( + InSituRegion!(stackSize, Allocator.alignment), + Allocator); + +/// +@system unittest +{ + StackFront!4096 a; + auto b = a.allocate(4000); + assert(b.length == 4000); + auto c = a.allocate(4000); + assert(c.length == 4000); + a.deallocate(b); + a.deallocate(c); +} + +/** +Creates a scalable `AllocatorList` of `Regions`, each having at least +`bytesPerRegion` bytes. Allocation is very fast. This allocator does not offer +`deallocate` but does free all regions in its destructor. It is recommended for +short-lived batch applications that count on never running out of memory. +*/ +auto mmapRegionList(size_t bytesPerRegion) +{ + static struct Factory + { + size_t bytesPerRegion; + import std.algorithm.comparison : max; + import std.experimental.allocator.building_blocks.region + : Region; + import std.experimental.allocator.mmap_allocator + : MmapAllocator; + this(size_t n) + { + bytesPerRegion = n; + } + auto opCall(size_t n) + { + return Region!MmapAllocator(max(n, bytesPerRegion)); + } + } + import std.experimental.allocator.building_blocks.allocator_list + : AllocatorList; + import std.experimental.allocator.building_blocks.null_allocator + : NullAllocator; + auto shop = Factory(bytesPerRegion); + return AllocatorList!(Factory, NullAllocator)(shop); +} + +/// +@system unittest +{ + auto alloc = mmapRegionList(1024 * 1024); + const b = alloc.allocate(100); + assert(b.length == 100); +} diff --git a/libphobos/src/std/experimental/allocator/typed.d b/libphobos/src/std/experimental/allocator/typed.d new file mode 100644 index 0000000..92828f3 --- /dev/null +++ b/libphobos/src/std/experimental/allocator/typed.d @@ -0,0 +1,423 @@ +/** +This module defines `TypedAllocator`, a statically-typed allocator that +aggregates multiple untyped allocators and uses them depending on the static +properties of the types allocated. For example, distinct allocators may be used +for thread-local vs. thread-shared data, or for fixed-size data (`struct`, +`class` objects) vs. resizable data (arrays). + +Macros: +T2=$(TR $(D $1) $(TD $(ARGS $+))) +*/ + +module std.experimental.allocator.typed; + +import std.experimental.allocator; +import std.experimental.allocator.common; +import std.range : isInputRange, isForwardRange, walkLength, save, empty, + front, popFront; +import std.traits : isPointer, hasElaborateDestructor; +import std.typecons : Flag, Yes, No; + +/** +Allocation-related flags dictated by type characteristics. `TypedAllocator` +deduces these flags from the type being allocated and uses the appropriate +allocator accordingly. +*/ +enum AllocFlag : uint +{ + init = 0, + /** + Fixed-size allocation (unlikely to get reallocated later). Examples: `int`, + `double`, any `struct` or `class` type. By default it is assumed that the + allocation is variable-size, i.e. susceptible to later reallocation + (for example all array types). This flag is advisory, i.e. in-place resizing + may be attempted for `fixedSize` allocations and may succeed. The flag is + just a hint to the compiler it may use allocation strategies that work well + with objects of fixed size. + */ + fixedSize = 1, + /** + The type being allocated embeds no pointers. Examples: `int`, `int[]`, $(D + Tuple!(int, float)). The implicit conservative assumption is that the type + has members with indirections so it needs to be scanned if garbage + collected. Example of types with pointers: `int*[]`, $(D Tuple!(int, + string)). + */ + hasNoIndirections = 4, + /** + By default it is conservatively assumed that allocated memory may be `cast` + to `shared`, passed across threads, and deallocated in a different thread + than the one that allocated it. If that's not the case, there are two + options. First, `immutableShared` means the memory is allocated for + `immutable` data and will be deallocated in the same thread it was + allocated in. Second, `threadLocal` means the memory is not to be shared + across threads at all. The two flags cannot be simultaneously present. + */ + immutableShared = 8, + /// ditto + threadLocal = 16, +} + +/** +`TypedAllocator` acts like a chassis on which several specialized allocators +can be assembled. To let the system make a choice about a particular kind of +allocation, use `Default` for the respective parameters. + +There is a hierarchy of allocation kinds. When an allocator is implemented for +a given combination of flags, it is used. Otherwise, the next down the list is +chosen. + +$(BOOKTABLE , + +$(TR $(TH `AllocFlag` combination) $(TH Description)) + +$(T2 AllocFlag.threadLocal |$(NBSP)AllocFlag.hasNoIndirections +|$(NBSP)AllocFlag.fixedSize, +This is the most specific allocation policy: the memory being allocated is +thread local, has no indirections at all, and will not be reallocated. Examples +of types fitting this description: `int`, `double`, $(D Tuple!(int, long)), but +not $(D Tuple!(int, string)), which contains an indirection.) + +$(T2 AllocFlag.threadLocal |$(NBSP)AllocFlag.hasNoIndirections, +As above, but may be reallocated later. Examples of types fitting this +description are $(D int[]), $(D double[]), $(D Tuple!(int, long)[]), but not +$(D Tuple!(int, string)[]), which contains an indirection.) + +$(T2 AllocFlag.threadLocal, +As above, but may embed indirections. Examples of types fitting this +description are $(D int*[]), $(D Object[]), $(D Tuple!(int, string)[]).) + +$(T2 AllocFlag.immutableShared |$(NBSP)AllocFlag.hasNoIndirections +|$(NBSP)AllocFlag.fixedSize, +The type being allocated is `immutable` and has no pointers. The thread that +allocated it must also deallocate it. Example: `immutable(int)`.) + +$(T2 AllocFlag.immutableShared |$(NBSP)AllocFlag.hasNoIndirections, +As above, but the type may be appended to in the future. Example: `string`.) + +$(T2 AllocFlag.immutableShared, +As above, but the type may embed references. Example: `immutable(Object)[]`.) + +$(T2 AllocFlag.hasNoIndirections |$(NBSP)AllocFlag.fixedSize, +The type being allocated may be shared across threads, embeds no indirections, +and has fixed size.) + +$(T2 AllocFlag.hasNoIndirections, +The type being allocated may be shared across threads, may embed indirections, +and has variable size.) + +$(T2 AllocFlag.fixedSize, +The type being allocated may be shared across threads, may embed indirections, +and has fixed size.) + +$(T2 0, The most conservative/general allocation: memory may be shared, +deallocated in a different thread, may or may not be resized, and may embed +references.) +) + +Params: +PrimaryAllocator = The default allocator. +Policies = Zero or more pairs consisting of an `AllocFlag` and an allocator +type. +*/ +struct TypedAllocator(PrimaryAllocator, Policies...) +{ + import std.algorithm.sorting : isSorted; + import std.meta : AliasSeq; + import std.typecons : Tuple; + + static assert(Policies.length == 0 || isSorted([Stride2!Policies])); + + private template Stride2(T...) + { + static if (T.length >= 2) + { + alias Stride2 = AliasSeq!(T[0], Stride2!(T[2 .. $])); + } + else + { + alias Stride2 = AliasSeq!(T[0 .. $]); + } + } + + // state + static if (stateSize!PrimaryAllocator) private PrimaryAllocator primary; + else alias primary = PrimaryAllocator.instance; + static if (Policies.length > 0) + private Tuple!(Stride2!(Policies[1 .. $])) extras; + + private static bool match(uint have, uint want) + { + enum uint maskAway = + ~(AllocFlag.immutableShared | AllocFlag.threadLocal); + // Do we offer thread local? + if (have & AllocFlag.threadLocal) + { + if (want & AllocFlag.threadLocal) + return match(have & maskAway, want & maskAway); + return false; + } + if (have & AllocFlag.immutableShared) + { + // Okay to ask for either thread local or immutable shared + if (want & (AllocFlag.threadLocal + | AllocFlag.immutableShared)) + return match(have & maskAway, want & maskAway); + return false; + } + // From here on we have full-blown thread sharing. + if (have & AllocFlag.hasNoIndirections) + { + if (want & AllocFlag.hasNoIndirections) + return match(have & ~AllocFlag.hasNoIndirections, + want & ~AllocFlag.hasNoIndirections); + return false; + } + // Fixed size or variable size both match. + return true; + } + + /** + Given `flags` as a combination of `AllocFlag` values, or a type `T`, returns + the allocator that's a closest fit in capabilities. + */ + auto ref allocatorFor(uint flags)() + { + static if (Policies.length == 0 || !match(Policies[0], flags)) + { + return primary; + } + else static if (Policies.length && match(Policies[$ - 2], flags)) + { + return extras[$ - 1]; + } + else + { + foreach (i, choice; Stride2!Policies) + { + static if (!match(choice, flags)) + { + return extras[i - 1]; + } + } + assert(0); + } + } + + /// ditto + auto ref allocatorFor(T)() + { + static if (is(T == void[])) + { + return primary; + } + else + { + return allocatorFor!(type2flags!T)(); + } + } + + /** + Given a type `T`, returns its allocation-related flags as a combination of + `AllocFlag` values. + */ + static uint type2flags(T)() + { + uint result; + static if (is(T == immutable)) + result |= AllocFlag.immutableShared; + else static if (is(T == shared)) + result |= AllocFlag.forSharing; + static if (!is(T == U[], U)) + result |= AllocFlag.fixedSize; + import std.traits : hasIndirections; + static if (!hasIndirections!T) + result |= AllocFlag.hasNoIndirections; + return result; + } + + /** + Dynamically allocates (using the appropriate allocator chosen with + `allocatorFor!T`) and then creates in the memory allocated an object of + type `T`, using `args` (if any) for its initialization. Initialization + occurs in the memory allocated and is otherwise semantically the same as + `T(args)`. (Note that using `make!(T[])` creates a pointer to an + (empty) array of `T`s, not an array. To allocate and initialize an + array, use `makeArray!T` described below.) + + Params: + T = Type of the object being created. + args = Optional arguments used for initializing the created object. If not + present, the object is default constructed. + + Returns: If `T` is a class type, returns a reference to the created `T` + object. Otherwise, returns a `T*` pointing to the created object. In all + cases, returns `null` if allocation failed. + + Throws: If `T`'s constructor throws, deallocates the allocated memory and + propagates the exception. + */ + auto make(T, A...)(auto ref A args) + { + return .make!T(allocatorFor!T, args); + } + + /** + Create an array of `T` with `length` elements. The array is either + default-initialized, filled with copies of `init`, or initialized with + values fetched from `range`. + + Params: + T = element type of the array being created + length = length of the newly created array + init = element used for filling the array + range = range used for initializing the array elements + + Returns: + The newly-created array, or `null` if either `length` was `0` or + allocation failed. + + Throws: + The first two overloads throw only if the used allocator's primitives do. + The overloads that involve copy initialization deallocate memory and propagate the exception if the copy operation throws. + */ + T[] makeArray(T)(size_t length) + { + return .makeArray!T(allocatorFor!(T[]), length); + } + + /// Ditto + T[] makeArray(T)(size_t length, auto ref T init) + { + return .makeArray!T(allocatorFor!(T[]), init, length); + } + + /// Ditto + T[] makeArray(T, R)(R range) + if (isInputRange!R) + { + return .makeArray!T(allocatorFor!(T[]), range); + } + + /** + Grows `array` by appending `delta` more elements. The needed memory is + allocated using the same allocator that was used for the array type. The + extra elements added are either default-initialized, filled with copies of + `init`, or initialized with values fetched from `range`. + + Params: + T = element type of the array being created + array = a reference to the array being grown + delta = number of elements to add (upon success the new length of `array` + is $(D array.length + delta)) + init = element used for filling the array + range = range used for initializing the array elements + + Returns: + `true` upon success, `false` if memory could not be allocated. In the + latter case `array` is left unaffected. + + Throws: + The first two overloads throw only if the used allocator's primitives do. + The overloads that involve copy initialization deallocate memory and + propagate the exception if the copy operation throws. + */ + bool expandArray(T)(ref T[] array, size_t delta) + { + return .expandArray(allocatorFor!(T[]), array, delta); + } + /// Ditto + bool expandArray(T)(T[] array, size_t delta, auto ref T init) + { + return .expandArray(allocatorFor!(T[]), array, delta, init); + } + /// Ditto + bool expandArray(T, R)(ref T[] array, R range) + if (isInputRange!R) + { + return .expandArray(allocatorFor!(T[]), array, range); + } + + /** + Shrinks an array by `delta` elements using `allocatorFor!(T[])`. + + If $(D arr.length < delta), does nothing and returns `false`. Otherwise, + destroys the last $(D arr.length - delta) elements in the array and then + reallocates the array's buffer. If reallocation fails, fills the array with + default-initialized data. + + Params: + T = element type of the array being created + arr = a reference to the array being shrunk + delta = number of elements to remove (upon success the new length of + `arr` is $(D arr.length - delta)) + + Returns: + `true` upon success, `false` if memory could not be reallocated. In the + latter case $(D arr[$ - delta .. $]) is left with default-initialized + elements. + + Throws: + The first two overloads throw only if the used allocator's primitives do. + The overloads that involve copy initialization deallocate memory and + propagate the exception if the copy operation throws. + */ + bool shrinkArray(T)(ref T[] arr, size_t delta) + { + return .shrinkArray(allocatorFor!(T[]), arr, delta); + } + + /** + Destroys and then deallocates (using `allocatorFor!T`) the object pointed + to by a pointer, the class object referred to by a `class` or `interface` + reference, or an entire array. It is assumed the respective entities had + been allocated with the same allocator. + */ + void dispose(T)(T* p) + { + return .dispose(allocatorFor!T, p); + } + /// Ditto + void dispose(T)(T p) + if (is(T == class) || is(T == interface)) + { + return .dispose(allocatorFor!T, p); + } + /// Ditto + void dispose(T)(T[] array) + { + return .dispose(allocatorFor!(T[]), array); + } +} + +/// +@system unittest +{ + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.experimental.allocator.mallocator : Mallocator; + import std.experimental.allocator.mmap_allocator : MmapAllocator; + alias MyAllocator = TypedAllocator!(GCAllocator, + AllocFlag.fixedSize | AllocFlag.threadLocal, Mallocator, + AllocFlag.fixedSize | AllocFlag.threadLocal + | AllocFlag.hasNoIndirections, + MmapAllocator, + ); + MyAllocator a; + auto b = &a.allocatorFor!0(); + static assert(is(typeof(*b) == shared GCAllocator)); + enum f1 = AllocFlag.fixedSize | AllocFlag.threadLocal; + auto c = &a.allocatorFor!f1(); + static assert(is(typeof(*c) == Mallocator)); + enum f2 = AllocFlag.fixedSize | AllocFlag.threadLocal; + static assert(is(typeof(a.allocatorFor!f2()) == Mallocator)); + // Partial match + enum f3 = AllocFlag.threadLocal; + static assert(is(typeof(a.allocatorFor!f3()) == Mallocator)); + + int* p = a.make!int; + scope(exit) a.dispose(p); + int[] arr = a.makeArray!int(42); + scope(exit) a.dispose(arr); + assert(a.expandArray(arr, 3)); + assert(a.shrinkArray(arr, 4)); +} diff --git a/libphobos/src/std/experimental/checkedint.d b/libphobos/src/std/experimental/checkedint.d new file mode 100644 index 0000000..48ed2f7 --- /dev/null +++ b/libphobos/src/std/experimental/checkedint.d @@ -0,0 +1,3063 @@ +/** +$(SCRIPT inhibitQuickIndex = 1;) + +This module defines facilities for efficient checking of integral operations +against overflow, casting with loss of precision, unexpected change of sign, +etc. The checking (and possibly correction) can be done at operation level, for +example $(LREF opChecked)$(D !"+"(x, y, overflow)) adds two integrals `x` and +`y` and sets `overflow` to `true` if an overflow occurred. The flag `overflow` +(a `bool` passed by reference) is not touched if the operation succeeded, so the +same flag can be reused for a sequence of operations and tested at the end. + +Issuing individual checked operations is flexible and efficient but often +tedious. The $(LREF Checked) facility offers encapsulated integral wrappers that +do all checking internally and have configurable behavior upon erroneous +results. For example, `Checked!int` is a type that behaves like `int` but aborts +execution immediately whenever involved in an operation that produces the +arithmetically wrong result. The accompanying convenience function $(LREF +checked) uses type deduction to convert a value `x` of integral type `T` to +`Checked!T` by means of `checked(x)`. For example: + +--- +void main() +{ + import std.experimental.checkedint, std.stdio; + writeln((checked(5) + 7).get); // 12 + writeln((checked(10) * 1000 * 1000 * 1000).get); // Overflow +} +--- + +Similarly, $(D checked(-1) > uint(0)) aborts execution (even though the built-in +comparison $(D int(-1) > uint(0)) is surprisingly true due to language's +conversion rules modeled after C). Thus, `Checked!int` is a virtually drop-in +replacement for `int` useable in debug builds, to be replaced by `int` in +release mode if efficiency demands it. + +`Checked` has customizable behavior with the help of a second type parameter, +`Hook`. Depending on what methods `Hook` defines, core operations on the +underlying integral may be verified for overflow or completely redefined. If +`Hook` defines no method at all and carries no state, there is no change in +behavior, i.e. $(D Checked!(int, void)) is a wrapper around `int` that adds no +customization at all. + +This module provides a few predefined hooks (below) that add useful behavior to +`Checked`: + +$(BOOKTABLE , + $(TR $(TD $(LREF Abort)) $(TD + fails every incorrect operation with a message to $(REF + stderr, std, stdio) followed by a call to `assert(0)`. It is the default + second parameter, i.e. `Checked!short` is the same as + $(D Checked!(short, Abort)). + )) + $(TR $(TD $(LREF Throw)) $(TD + fails every incorrect operation by throwing an exception. + )) + $(TR $(TD $(LREF Warn)) $(TD + prints incorrect operations to $(REF stderr, std, stdio) + but otherwise preserves the built-in behavior. + )) + $(TR $(TD $(LREF ProperCompare)) $(TD + fixes the comparison operators `==`, `!=`, `<`, `<=`, `>`, and `>=` + to return correct results in all circumstances, + at a slight cost in efficiency. For example, + $(D Checked!(uint, ProperCompare)(1) > -1) is `true`, + which is not the case for the built-in comparison. Also, comparing + numbers for equality with floating-point numbers only passes if the + integral can be converted to the floating-point number precisely, + so as to preserve transitivity of equality. + )) + $(TR $(TD $(LREF WithNaN)) $(TD + reserves a special "Not a Number" (NaN) value akin to the homonym value + reserved for floating-point values. Once a $(D Checked!(X, WithNaN)) + gets this special value, it preserves and propagates it until + reassigned. $(LREF isNaN) can be used to query whether the object + is not a number. + )) + $(TR $(TD $(LREF Saturate)) $(TD + implements saturating arithmetic, i.e. $(D Checked!(int, Saturate)) + "stops" at `int.max` for all operations that would cause an `int` to + overflow toward infinity, and at `int.min` for all operations that would + correspondingly overflow toward negative infinity. + )) +) + + +These policies may be used alone, e.g. $(D Checked!(uint, WithNaN)) defines a +`uint`-like type that reaches a stable NaN state for all erroneous operations. +They may also be "stacked" on top of each other, owing to the property that a +checked integral emulates an actual integral, which means another checked +integral can be built on top of it. Some combinations of interest include: + +$(BOOKTABLE , + $(TR $(TD $(D Checked!(Checked!int, ProperCompare)))) + $(TR $(TD +defines an `int` with fixed +comparison operators that will fail with `assert(0)` upon overflow. (Recall that +`Abort` is the default policy.) The order in which policies are combined is +important because the outermost policy (`ProperCompare` in this case) has the +first crack at intercepting an operator. The converse combination $(D +Checked!(Checked!(int, ProperCompare))) is meaningless because `Abort` will +intercept comparison and will fail without giving `ProperCompare` a chance to +intervene. + )) + $(TR $(TD)) + $(TR $(TDNW $(D Checked!(Checked!(int, ProperCompare), WithNaN)))) + $(TR $(TD +defines an `int`-like +type that supports a NaN value. For values that are not NaN, comparison works +properly. Again the composition order is important; $(D Checked!(Checked!(int, +WithNaN), ProperCompare)) does not have good semantics because `ProperCompare` +intercepts comparisons before the numbers involved are tested for NaN. + )) +) + +The hook's members are looked up statically in a Design by Introspection manner +and are all optional. The table below illustrates the members that a hook type +may define and their influence over the behavior of the `Checked` type using it. +In the table, `hook` is an alias for `Hook` if the type `Hook` does not +introduce any state, or an object of type `Hook` otherwise. + +$(TABLE , +$(TR $(TH `Hook` member) $(TH Semantics in $(D Checked!(T, Hook))) +) +$(TR $(TD `defaultValue`) $(TD If defined, `Hook.defaultValue!T` is used as the +default initializer of the payload.) +) +$(TR $(TD `min`) $(TD If defined, `Hook.min!T` is used as the minimum value of +the payload.) +) +$(TR $(TD `max`) $(TD If defined, `Hook.max!T` is used as the maximum value of +the payload.) +) +$(TR $(TD `hookOpCast`) $(TD If defined, `hook.hookOpCast!U(get)` is forwarded +to unconditionally when the payload is to be cast to type `U`.) +) +$(TR $(TD `onBadCast`) $(TD If defined and `hookOpCast` is $(I not) defined, +`onBadCast!U(get)` is forwarded to when the payload is to be cast to type `U` +and the cast would lose information or force a change of sign.) +) +$(TR $(TD `hookOpEquals`) $(TD If defined, $(D hook.hookOpEquals(get, rhs)) is +forwarded to unconditionally when the payload is compared for equality against +value `rhs` of integral, floating point, or Boolean type.) +) +$(TR $(TD `hookOpCmp`) $(TD If defined, $(D hook.hookOpCmp(get, rhs)) is +forwarded to unconditionally when the payload is compared for ordering against +value `rhs` of integral, floating point, or Boolean type.) +) +$(TR $(TD `hookOpUnary`) $(TD If defined, `hook.hookOpUnary!op(get)` (where `op` +is the operator symbol) is forwarded to for unary operators `-` and `~`. In +addition, for unary operators `++` and `--`, `hook.hookOpUnary!op(payload)` is +called, where `payload` is a reference to the value wrapped by `Checked` so the +hook can change it.) +) +$(TR $(TD `hookOpBinary`) $(TD If defined, $(D hook.hookOpBinary!op(get, rhs)) +(where `op` is the operator symbol and `rhs` is the right-hand side operand) is +forwarded to unconditionally for binary operators `+`, `-`, `*`, `/`, `%`, +`^^`, `&`, `|`, `^`, `<<`, `>>`, and `>>>`.) +) +$(TR $(TD `hookOpBinaryRight`) $(TD If defined, $(D +hook.hookOpBinaryRight!op(lhs, get)) (where `op` is the operator symbol and +`lhs` is the left-hand side operand) is forwarded to unconditionally for binary +operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, `^`, `<<`, `>>`, and `>>>`.) +) +$(TR $(TD `onOverflow`) $(TD If defined, `hook.onOverflow!op(get)` is forwarded +to for unary operators that overflow but only if `hookOpUnary` is not defined. +Unary `~` does not overflow; unary `-` overflows only when the most negative +value of a signed type is negated, and the result of the hook call is returned. +When the increment or decrement operators overflow, the payload is assigned the +result of `hook.onOverflow!op(get)`. When a binary operator overflows, the +result of $(D hook.onOverflow!op(get, rhs)) is returned, but only if `Hook` does +not define `hookOpBinary`.) +) +$(TR $(TD `hookOpOpAssign`) $(TD If defined, $(D hook.hookOpOpAssign!op(payload, +rhs)) (where `op` is the operator symbol and `rhs` is the right-hand side +operand) is forwarded to unconditionally for binary operators `+=`, `-=`, `*=`, `/=`, `%=`, +`^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, and `>>>=`.) +) +$(TR $(TD `onLowerBound`) $(TD If defined, $(D hook.onLowerBound(value, bound)) +(where `value` is the value being assigned) is forwarded to when the result of +binary operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, +and `>>>=` is smaller than the smallest value representable by `T`.) +) +$(TR $(TD `onUpperBound`) $(TD If defined, $(D hook.onUpperBound(value, bound)) +(where `value` is the value being assigned) is forwarded to when the result of +binary operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, +and `>>>=` is larger than the largest value representable by `T`.) +) +) + +*/ +module std.experimental.checkedint; +import std.traits : isFloatingPoint, isIntegral, isNumeric, isUnsigned, Unqual; + +/// +@system unittest +{ + int[] concatAndAdd(int[] a, int[] b, int offset) + { + // Aborts on overflow on size computation + auto r = new int[(checked(a.length) + b.length).get]; + // Aborts on overflow on element computation + foreach (i; 0 .. a.length) + r[i] = (a[i] + checked(offset)).get; + foreach (i; 0 .. b.length) + r[i + a.length] = (b[i] + checked(offset)).get; + return r; + } + assert(concatAndAdd([1, 2, 3], [4, 5], -1) == [0, 1, 2, 3, 4]); +} + +/** +Checked integral type wraps an integral `T` and customizes its behavior with the +help of a `Hook` type. The type wrapped must be one of the predefined integrals +(unqualified), or another instance of `Checked`. +*/ +struct Checked(T, Hook = Abort) +if (isIntegral!T || is(T == Checked!(U, H), U, H)) +{ + import std.algorithm.comparison : among; + import std.experimental.allocator.common : stateSize; + import std.traits : hasMember; + + /** + The type of the integral subject to checking. + */ + alias Representation = T; + + // state { + static if (hasMember!(Hook, "defaultValue")) + private T payload = Hook.defaultValue!T; + else + private T payload; + /** + `hook` is a member variable if it has state, or an alias for `Hook` + otherwise. + */ + static if (stateSize!Hook > 0) Hook hook; + else alias hook = Hook; + // } state + + // get + /** + Returns a copy of the underlying value. + */ + auto get() inout { return payload; } + /// + @safe unittest + { + auto x = checked(ubyte(42)); + static assert(is(typeof(x.get()) == ubyte)); + assert(x.get == 42); + const y = checked(ubyte(42)); + static assert(is(typeof(y.get()) == const ubyte)); + assert(y.get == 42); + } + + /** + Defines the minimum and maximum. These values are hookable by defining + `Hook.min` and/or `Hook.max`. + */ + static if (hasMember!(Hook, "min")) + { + enum Checked!(T, Hook) min = Checked!(T, Hook)(Hook.min!T); + /// + @system unittest + { + assert(Checked!short.min == -32768); + assert(Checked!(short, WithNaN).min == -32767); + assert(Checked!(uint, WithNaN).max == uint.max - 1); + } + } + else + enum Checked!(T, Hook) min = Checked(T.min); + /// ditto + static if (hasMember!(Hook, "max")) + enum Checked!(T, Hook) max = Checked(Hook.max!T); + else + enum Checked!(T, Hook) max = Checked(T.max); + + /** + Constructor taking a value properly convertible to the underlying type. `U` + may be either an integral that can be converted to `T` without a loss, or + another `Checked` instance whose representation may be in turn converted to + `T` without a loss. + */ + this(U)(U rhs) + if (valueConvertible!(U, T) || + !isIntegral!T && is(typeof(T(rhs))) || + is(U == Checked!(V, W), V, W) && + is(typeof(Checked!(T, Hook)(rhs.get)))) + { + static if (isIntegral!U) + payload = rhs; + else + payload = rhs.payload; + } + /// + @system unittest + { + auto a = checked(42L); + assert(a == 42); + auto b = Checked!long(4242); // convert 4242 to long + assert(b == 4242); + } + + /** + Assignment operator. Has the same constraints as the constructor. + */ + void opAssign(U)(U rhs) if (is(typeof(Checked!(T, Hook)(rhs)))) + { + static if (isIntegral!U) + payload = rhs; + else + payload = rhs.payload; + } + /// + @system unittest + { + Checked!long a; + a = 42L; + assert(a == 42); + a = 4242; + assert(a == 4242); + } + + // opCast + /** + Casting operator to integral, `bool`, or floating point type. If `Hook` + defines `hookOpCast`, the call immediately returns + `hook.hookOpCast!U(get)`. Otherwise, casting to `bool` yields $(D + get != 0) and casting to another integral that can represent all + values of `T` returns `get` promoted to `U`. + + If a cast to a floating-point type is requested and `Hook` defines + `onBadCast`, the cast is verified by ensuring $(D get == cast(T) + U(get)). If that is not `true`, `hook.onBadCast!U(get)` is returned. + + If a cast to an integral type is requested and `Hook` defines `onBadCast`, + the cast is verified by ensuring `get` and $(D cast(U) + get) are the same arithmetic number. (Note that `int(-1)` and + `uint(1)` are different values arithmetically although they have the same + bitwise representation and compare equal by language rules.) If the numbers + are not arithmetically equal, `hook.onBadCast!U(get)` is + returned. + + */ + U opCast(U, this _)() + if (isIntegral!U || isFloatingPoint!U || is(U == bool)) + { + static if (hasMember!(Hook, "hookOpCast")) + { + return hook.hookOpCast!U(payload); + } + else static if (is(U == bool)) + { + return payload != 0; + } + else static if (valueConvertible!(T, U)) + { + return payload; + } + // may lose bits or precision + else static if (!hasMember!(Hook, "onBadCast")) + { + return cast(U) payload; + } + else + { + if (isUnsigned!T || !isUnsigned!U || + T.sizeof > U.sizeof || payload >= 0) + { + auto result = cast(U) payload; + // If signedness is different, we need additional checks + if (result == payload && + (!isUnsigned!T || isUnsigned!U || result >= 0)) + return result; + } + return hook.onBadCast!U(payload); + } + } + /// + @system unittest + { + assert(cast(uint) checked(42) == 42); + assert(cast(uint) checked!WithNaN(-42) == uint.max); + } + + // opEquals + /** + Compares `this` against `rhs` for equality. If `Hook` defines + `hookOpEquals`, the function forwards to $(D + hook.hookOpEquals(get, rhs)). Otherwise, the result of the + built-in operation $(D get == rhs) is returned. + + If `U` is also an instance of `Checked`, both hooks (left- and right-hand + side) are introspected for the method `hookOpEquals`. If both define it, + priority is given to the left-hand side. + + */ + bool opEquals(U, this _)(U rhs) + if (isIntegral!U || isFloatingPoint!U || is(U == bool) || + is(U == Checked!(V, W), V, W) && is(typeof(this == rhs.payload))) + { + static if (is(U == Checked!(V, W), V, W)) + { + alias R = typeof(payload + rhs.payload); + static if (is(Hook == W)) + { + // Use the lhs hook if there + return this == rhs.payload; + } + else static if (valueConvertible!(T, R) && valueConvertible!(V, R)) + { + return payload == rhs.payload; + } + else static if (hasMember!(Hook, "hookOpEquals")) + { + return hook.hookOpEquals(payload, rhs.payload); + } + else static if (hasMember!(W, "hookOpEquals")) + { + return rhs.hook.hookOpEquals(rhs.payload, payload); + } + else + { + return payload == rhs.payload; + } + } + else static if (hasMember!(Hook, "hookOpEquals")) + return hook.hookOpEquals(payload, rhs); + else static if (isIntegral!U || isFloatingPoint!U || is(U == bool)) + return payload == rhs; + } + + /// + static if (is(T == int) && is(Hook == void)) @safe unittest + { + static struct MyHook + { + static bool thereWereErrors; + static bool hookOpEquals(L, R)(L lhs, R rhs) + { + if (lhs != rhs) return false; + static if (isUnsigned!L && !isUnsigned!R) + { + if (lhs > 0 && rhs < 0) thereWereErrors = true; + } + else static if (isUnsigned!R && !isUnsigned!L) + if (lhs < 0 && rhs > 0) thereWereErrors = true; + // Preserve built-in behavior. + return true; + } + } + auto a = checked!MyHook(-42); + assert(a == uint(-42)); + assert(MyHook.thereWereErrors); + MyHook.thereWereErrors = false; + assert(checked!MyHook(uint(-42)) == -42); + assert(MyHook.thereWereErrors); + static struct MyHook2 + { + static bool hookOpEquals(L, R)(L lhs, R rhs) + { + return lhs == rhs; + } + } + MyHook.thereWereErrors = false; + assert(checked!MyHook2(uint(-42)) == a); + // Hook on left hand side takes precedence, so no errors + assert(!MyHook.thereWereErrors); + } + + // opCmp + /** + + Compares `this` against `rhs` for ordering. If `Hook` defines `hookOpCmp`, + the function forwards to $(D hook.hookOpCmp(get, rhs)). Otherwise, the + result of the built-in comparison operation is returned. + + If `U` is also an instance of `Checked`, both hooks (left- and right-hand + side) are introspected for the method `hookOpCmp`. If both define it, + priority is given to the left-hand side. + + */ + auto opCmp(U, this _)(const U rhs) //const pure @safe nothrow @nogc + if (isIntegral!U || isFloatingPoint!U || is(U == bool)) + { + static if (hasMember!(Hook, "hookOpCmp")) + { + return hook.hookOpCmp(payload, rhs); + } + else static if (valueConvertible!(T, U) || valueConvertible!(U, T)) + { + return payload < rhs ? -1 : payload > rhs; + } + else static if (isFloatingPoint!U) + { + U lhs = payload; + return lhs < rhs ? U(-1.0) + : lhs > rhs ? U(1.0) + : lhs == rhs ? U(0.0) : U.init; + } + else + { + return payload < rhs ? -1 : payload > rhs; + } + } + + /// ditto + auto opCmp(U, Hook1, this _)(Checked!(U, Hook1) rhs) + { + alias R = typeof(payload + rhs.payload); + static if (valueConvertible!(T, R) && valueConvertible!(U, R)) + { + return payload < rhs.payload ? -1 : payload > rhs.payload; + } + else static if (is(Hook == Hook1)) + { + // Use the lhs hook + return this.opCmp(rhs.payload); + } + else static if (hasMember!(Hook, "hookOpCmp")) + { + return hook.hookOpCmp(get, rhs.get); + } + else static if (hasMember!(Hook1, "hookOpCmp")) + { + return -rhs.hook.hookOpCmp(rhs.payload, get); + } + else + { + return payload < rhs.payload ? -1 : payload > rhs.payload; + } + } + + /// + static if (is(T == int) && is(Hook == void)) @safe unittest + { + static struct MyHook + { + static bool thereWereErrors; + static int hookOpCmp(L, R)(L lhs, R rhs) + { + static if (isUnsigned!L && !isUnsigned!R) + { + if (rhs < 0 && rhs >= lhs) + thereWereErrors = true; + } + else static if (isUnsigned!R && !isUnsigned!L) + { + if (lhs < 0 && lhs >= rhs) + thereWereErrors = true; + } + // Preserve built-in behavior. + return lhs < rhs ? -1 : lhs > rhs; + } + } + auto a = checked!MyHook(-42); + assert(a > uint(42)); + assert(MyHook.thereWereErrors); + static struct MyHook2 + { + static int hookOpCmp(L, R)(L lhs, R rhs) + { + // Default behavior + return lhs < rhs ? -1 : lhs > rhs; + } + } + MyHook.thereWereErrors = false; + assert(Checked!(uint, MyHook2)(uint(-42)) <= a); + //assert(Checked!(uint, MyHook2)(uint(-42)) >= a); + // Hook on left hand side takes precedence, so no errors + assert(!MyHook.thereWereErrors); + assert(a <= Checked!(uint, MyHook2)(uint(-42))); + assert(MyHook.thereWereErrors); + } + + // For coverage + static if (is(T == int) && is(Hook == void)) @system unittest + { + assert(checked(42) <= checked!void(42)); + assert(checked!void(42) <= checked(42u)); + assert(checked!void(42) <= checked!(void*)(42u)); + } + + // opUnary + /** + + Defines unary operators `+`, `-`, `~`, `++`, and `--`. Unary `+` is not + overridable and always has built-in behavior (returns `this`). For the + others, if `Hook` defines `hookOpUnary`, `opUnary` forwards to $(D + Checked!(typeof(hook.hookOpUnary!op(get)), + Hook)(hook.hookOpUnary!op(get))). + + If `Hook` does not define `hookOpUnary` but defines `onOverflow`, `opUnary` + forwards to `hook.onOverflow!op(get)` in case an overflow occurs. + For `++` and `--`, the payload is assigned from the result of the call to + `onOverflow`. + + Note that unary `-` is considered to overflow if `T` is a signed integral of + 32 or 64 bits and is equal to the most negative value. This is because that + value has no positive negation. + + */ + auto opUnary(string op, this _)() + if (op == "+" || op == "-" || op == "~") + { + static if (op == "+") + return Checked(this); // "+" is not hookable + else static if (hasMember!(Hook, "hookOpUnary")) + { + auto r = hook.hookOpUnary!op(payload); + return Checked!(typeof(r), Hook)(r); + } + else static if (op == "-" && isIntegral!T && T.sizeof >= 4 && + !isUnsigned!T && hasMember!(Hook, "onOverflow")) + { + static assert(is(typeof(-payload) == typeof(payload))); + bool overflow; + import core.checkedint : negs; + auto r = negs(payload, overflow); + if (overflow) r = hook.onOverflow!op(payload); + return Checked(r); + } + else + return Checked(mixin(op ~ "payload")); + } + + /// ditto + ref Checked opUnary(string op)() return + if (op == "++" || op == "--") + { + static if (hasMember!(Hook, "hookOpUnary")) + hook.hookOpUnary!op(payload); + else static if (hasMember!(Hook, "onOverflow")) + { + static if (op == "++") + { + if (payload == max.payload) + payload = hook.onOverflow!"++"(payload); + else + ++payload; + } + else + { + if (payload == min.payload) + payload = hook.onOverflow!"--"(payload); + else + --payload; + } + } + else + mixin(op ~ "payload;"); + return this; + } + + /// + static if (is(T == int) && is(Hook == void)) @safe unittest + { + static struct MyHook + { + static bool thereWereErrors; + static L hookOpUnary(string x, L)(L lhs) + { + if (x == "-" && lhs == -lhs) thereWereErrors = true; + return -lhs; + } + } + auto a = checked!MyHook(long.min); + assert(a == -a); + assert(MyHook.thereWereErrors); + auto b = checked!void(42); + assert(++b == 43); + } + + // opBinary + /** + + Defines binary operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, `^`, `<<`, `>>`, + and `>>>`. If `Hook` defines `hookOpBinary`, `opBinary` forwards to $(D + Checked!(typeof(hook.hookOpBinary!op(get, rhs)), + Hook)(hook.hookOpBinary!op(get, rhs))). + + If `Hook` does not define `hookOpBinary` but defines `onOverflow`, + `opBinary` forwards to `hook.onOverflow!op(get, rhs)` in case an + overflow occurs. + + If two `Checked` instances are involved in a binary operation and both + define `hookOpBinary`, the left-hand side hook has priority. If both define + `onOverflow`, a compile-time error occurs. + + */ + auto opBinary(string op, Rhs)(const Rhs rhs) + if (isIntegral!Rhs || isFloatingPoint!Rhs || is(Rhs == bool)) + { + return opBinaryImpl!(op, Rhs, typeof(this))(rhs); + } + + /// ditto + auto opBinary(string op, Rhs)(const Rhs rhs) const + if (isIntegral!Rhs || isFloatingPoint!Rhs || is(Rhs == bool)) + { + return opBinaryImpl!(op, Rhs, typeof(this))(rhs); + } + + private auto opBinaryImpl(string op, Rhs, this _)(const Rhs rhs) + { + alias R = typeof(mixin("payload" ~ op ~ "rhs")); + static assert(is(typeof(mixin("payload" ~ op ~ "rhs")) == R)); + static if (isIntegral!R) alias Result = Checked!(R, Hook); + else alias Result = R; + + static if (hasMember!(Hook, "hookOpBinary")) + { + auto r = hook.hookOpBinary!op(payload, rhs); + return Checked!(typeof(r), Hook)(r); + } + else static if (is(Rhs == bool)) + { + return mixin("this" ~ op ~ "ubyte(rhs)"); + } + else static if (isFloatingPoint!Rhs) + { + return mixin("payload" ~ op ~ "rhs"); + } + else static if (hasMember!(Hook, "onOverflow")) + { + bool overflow; + auto r = opChecked!op(payload, rhs, overflow); + if (overflow) r = hook.onOverflow!op(payload, rhs); + return Result(r); + } + else + { + // Default is built-in behavior + return Result(mixin("payload" ~ op ~ "rhs")); + } + } + + /// ditto + auto opBinary(string op, U, Hook1)(Checked!(U, Hook1) rhs) + { + return opBinaryImpl2!(op, U, Hook1, typeof(this))(rhs); + } + + /// ditto + auto opBinary(string op, U, Hook1)(Checked!(U, Hook1) rhs) const + { + return opBinaryImpl2!(op, U, Hook1, typeof(this))(rhs); + } + + private + auto opBinaryImpl2(string op, U, Hook1, this _)(Checked!(U, Hook1) rhs) + { + alias R = typeof(get + rhs.payload); + static if (valueConvertible!(T, R) && valueConvertible!(U, R) || + is(Hook == Hook1)) + { + // Delegate to lhs + return mixin("this" ~ op ~ "rhs.payload"); + } + else static if (hasMember!(Hook, "hookOpBinary")) + { + return hook.hookOpBinary!op(payload, rhs); + } + else static if (hasMember!(Hook1, "hookOpBinary")) + { + // Delegate to rhs + return mixin("this.payload" ~ op ~ "rhs"); + } + else static if (hasMember!(Hook, "onOverflow") && + !hasMember!(Hook1, "onOverflow")) + { + // Delegate to lhs + return mixin("this" ~ op ~ "rhs.payload"); + } + else static if (hasMember!(Hook1, "onOverflow") && + !hasMember!(Hook, "onOverflow")) + { + // Delegate to rhs + return mixin("this.payload" ~ op ~ "rhs"); + } + else + { + static assert(0, "Conflict between lhs and rhs hooks," ~ + " use .get on one side to disambiguate."); + } + } + + static if (is(T == int) && is(Hook == void)) @system unittest + { + const a = checked(42); + assert(a + 1 == 43); + assert(a + checked(uint(42)) == 84); + assert(checked(42) + checked!void(42u) == 84); + assert(checked!void(42) + checked(42u) == 84); + + static struct MyHook + { + static uint tally; + static auto hookOpBinary(string x, L, R)(L lhs, R rhs) + { + ++tally; + return mixin("lhs" ~ x ~ "rhs"); + } + } + assert(checked!MyHook(42) + checked(42u) == 84); + assert(checked!void(42) + checked!MyHook(42u) == 84); + assert(MyHook.tally == 2); + } + + // opBinaryRight + /** + + Defines binary operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, `^`, `<<`, + `>>`, and `>>>` for the case when a built-in numeric or Boolean type is on + the left-hand side, and a `Checked` instance is on the right-hand side. + + */ + auto opBinaryRight(string op, Lhs)(const Lhs lhs) + if (isIntegral!Lhs || isFloatingPoint!Lhs || is(Lhs == bool)) + { + return opBinaryRightImpl!(op, Lhs, typeof(this))(lhs); + } + + /// ditto + auto opBinaryRight(string op, Lhs)(const Lhs lhs) const + if (isIntegral!Lhs || isFloatingPoint!Lhs || is(Lhs == bool)) + { + return opBinaryRightImpl!(op, Lhs, typeof(this))(lhs); + } + + private auto opBinaryRightImpl(string op, Lhs, this _)(const Lhs lhs) + { + static if (hasMember!(Hook, "hookOpBinaryRight")) + { + auto r = hook.hookOpBinaryRight!op(lhs, payload); + return Checked!(typeof(r), Hook)(r); + } + else static if (hasMember!(Hook, "hookOpBinary")) + { + auto r = hook.hookOpBinary!op(lhs, payload); + return Checked!(typeof(r), Hook)(r); + } + else static if (is(Lhs == bool)) + { + return mixin("ubyte(lhs)" ~ op ~ "this"); + } + else static if (isFloatingPoint!Lhs) + { + return mixin("lhs" ~ op ~ "payload"); + } + else static if (hasMember!(Hook, "onOverflow")) + { + bool overflow; + auto r = opChecked!op(lhs, T(payload), overflow); + if (overflow) r = hook.onOverflow!op(42); + return Checked!(typeof(r), Hook)(r); + } + else + { + // Default is built-in behavior + auto r = mixin("lhs" ~ op ~ "T(payload)"); + return Checked!(typeof(r), Hook)(r); + } + } + + static if (is(T == int) && is(Hook == void)) @system unittest + { + assert(1 + checked(1) == 2); + static uint tally; + static struct MyHook + { + static auto hookOpBinaryRight(string x, L, R)(L lhs, R rhs) + { + ++tally; + return mixin("lhs" ~ x ~ "rhs"); + } + } + assert(1 + checked!MyHook(1) == 2); + assert(tally == 1); + + immutable x1 = checked(1); + assert(1 + x1 == 2); + immutable x2 = checked!MyHook(1); + assert(1 + x2 == 2); + assert(tally == 2); + } + + // opOpAssign + /** + + Defines operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, + `<<=`, `>>=`, and `>>>=`. + + If `Hook` defines `hookOpOpAssign`, `opOpAssign` forwards to + `hook.hookOpOpAssign!op(payload, rhs)`, where `payload` is a reference to + the internally held data so the hook can change it. + + Otherwise, the operator first evaluates $(D auto result = + opBinary!op(payload, rhs).payload), which is subject to the hooks in + `opBinary`. Then, if `result` is less than $(D Checked!(T, Hook).min) and if + `Hook` defines `onLowerBound`, the payload is assigned from $(D + hook.onLowerBound(result, min)). If `result` is greater than $(D Checked!(T, + Hook).max) and if `Hook` defines `onUpperBound`, the payload is assigned + from $(D hook.onUpperBound(result, min)). + + In all other cases, the built-in behavior is carried out. + + Params: + op = The operator involved (without the `"="`, e.g. `"+"` for `"+="` etc) + rhs = The right-hand side of the operator (left-hand side is `this`) + + Returns: A reference to `this`. + */ + ref Checked opOpAssign(string op, Rhs)(const Rhs rhs) return + if (isIntegral!Rhs || isFloatingPoint!Rhs || is(Rhs == bool)) + { + static assert(is(typeof(mixin("payload" ~ op ~ "=rhs")) == T)); + + static if (hasMember!(Hook, "hookOpOpAssign")) + { + hook.hookOpOpAssign!op(payload, rhs); + } + else + { + alias R = typeof(get + rhs); + auto r = opBinary!op(rhs).get; + import std.conv : unsigned; + + static if (ProperCompare.hookOpCmp(R.min, min.get) < 0 && + hasMember!(Hook, "onLowerBound")) + { + if (ProperCompare.hookOpCmp(r, min.get) < 0) + { + // Example: Checked!uint(1) += int(-3) + payload = hook.onLowerBound(r, min.get); + return this; + } + } + static if (ProperCompare.hookOpCmp(max.get, R.max) < 0 && + hasMember!(Hook, "onUpperBound")) + { + if (ProperCompare.hookOpCmp(r, max.get) > 0) + { + // Example: Checked!uint(1) += long(uint.max) + payload = hook.onUpperBound(r, max.get); + return this; + } + } + payload = cast(T) r; + } + return this; + } + + /// + static if (is(T == int) && is(Hook == void)) @safe unittest + { + static struct MyHook + { + static bool thereWereErrors; + static T onLowerBound(Rhs, T)(Rhs rhs, T bound) + { + thereWereErrors = true; + return bound; + } + static T onUpperBound(Rhs, T)(Rhs rhs, T bound) + { + thereWereErrors = true; + return bound; + } + } + auto x = checked!MyHook(byte.min); + x -= 1; + assert(MyHook.thereWereErrors); + MyHook.thereWereErrors = false; + x = byte.max; + x += 1; + assert(MyHook.thereWereErrors); + } +} + +/** + +Convenience function that turns an integral into the corresponding `Checked` +instance by using template argument deduction. The hook type may be specified +(by default `Abort`). + +*/ +Checked!(T, Hook) checked(Hook = Abort, T)(const T value) +if (is(typeof(Checked!(T, Hook)(value)))) +{ + return Checked!(T, Hook)(value); +} + +/// +@system unittest +{ + static assert(is(typeof(checked(42)) == Checked!int)); + assert(checked(42) == Checked!int(42)); + static assert(is(typeof(checked!WithNaN(42)) == Checked!(int, WithNaN))); + assert(checked!WithNaN(42) == Checked!(int, WithNaN)(42)); +} + +// get +@safe unittest +{ + void test(T)() + { + assert(Checked!(T, void)(ubyte(22)).get == 22); + } + test!ubyte; + test!(const ubyte); + test!(immutable ubyte); +} + +// Abort +/** + +Force all integral errors to fail by printing an error message to `stderr` and +then abort the program. `Abort` is the default second argument for `Checked`. + +*/ +struct Abort +{ +static: + /** + + Called automatically upon a bad cast (one that loses precision or attempts + to convert a negative value to an unsigned type). The source type is `Src` + and the destination type is `Dst`. + + Params: + src = The source of the cast + + Returns: Nominally the result is the desired value of the cast operation, + which will be forwarded as the result of the cast. For `Abort`, the + function never returns because it aborts the program. + + */ + Dst onBadCast(Dst, Src)(Src src) + { + Warn.onBadCast!Dst(src); + assert(0); + } + + /** + + Called automatically upon a bounds error. + + Params: + rhs = The right-hand side value in the assignment, after the operator has + been evaluated + bound = The value of the bound being violated + + Returns: Nominally the result is the desired value of the operator, which + will be forwarded as result. For `Abort`, the function never returns because + it aborts the program. + + */ + T onLowerBound(Rhs, T)(Rhs rhs, T bound) + { + Warn.onLowerBound(rhs, bound); + assert(0); + } + /// ditto + T onUpperBound(Rhs, T)(Rhs rhs, T bound) + { + Warn.onUpperBound(rhs, bound); + assert(0); + } + + /** + + Called automatically upon a comparison for equality. In case of a erroneous + comparison (one that would make a signed negative value appear equal to an + unsigned positive value), this hook issues `assert(0)` which terminates the + application. + + Params: + lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of + the operator is `Checked!int` + rhs = The right-hand side type involved in the operator + + Returns: Upon a correct comparison, returns the result of the comparison. + Otherwise, the function terminates the application so it never returns. + + */ + static bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + bool error; + auto result = opChecked!"=="(lhs, rhs, error); + if (error) + { + Warn.hookOpEquals(lhs, rhs); + assert(0); + } + return result; + } + + /** + + Called automatically upon a comparison for ordering using one of the + operators `<`, `<=`, `>`, or `>=`. In case the comparison is erroneous (i.e. + it would make a signed negative value appear greater than or equal to an + unsigned positive value), then application is terminated with `assert(0)`. + Otherwise, the three-state result is returned (positive if $(D lhs > rhs), + negative if $(D lhs < rhs), `0` otherwise). + + Params: + lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of + the operator is `Checked!int` + rhs = The right-hand side type involved in the operator + + Returns: For correct comparisons, returns a positive integer if $(D lhs > + rhs), a negative integer if $(D lhs < rhs), `0` if the two are equal. Upon + a mistaken comparison such as $(D int(-1) < uint(0)), the function never + returns because it aborts the program. + + */ + int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + bool error; + auto result = opChecked!"cmp"(lhs, rhs, error); + if (error) + { + Warn.hookOpCmp(lhs, rhs); + assert(0); + } + return result; + } + + /** + + Called automatically upon an overflow during a unary or binary operation. + + Params: + x = The operator, e.g. `-` + lhs = The left-hand side (or sole) argument + rhs = The right-hand side type involved in the operator + + Returns: Nominally the result is the desired value of the operator, which + will be forwarded as result. For `Abort`, the function never returns because + it aborts the program. + + */ + typeof(~Lhs()) onOverflow(string x, Lhs)(Lhs lhs) + { + Warn.onOverflow!x(lhs); + assert(0); + } + /// ditto + typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + Warn.onOverflow!x(lhs, rhs); + assert(0); + } +} + +@system unittest +{ + void test(T)() + { + Checked!(int, Abort) x; + x = 42; + auto x1 = cast(T) x; + assert(x1 == 42); + //x1 += long(int.max); + } + test!short; + test!(const short); + test!(immutable short); +} + + +// Throw +/** + +Force all integral errors to fail by throwing an exception of type +`Throw.CheckFailure`. The message coming with the error is similar to the one +printed by `Warn`. + +*/ +struct Throw +{ + /** + Exception type thrown upon any failure. + */ + static class CheckFailure : Exception + { + this(T...)(string f, T vals) + { + import std.format : format; + super(format(f, vals)); + } + } + + /** + + Called automatically upon a bad cast (one that loses precision or attempts + to convert a negative value to an unsigned type). The source type is `Src` + and the destination type is `Dst`. + + Params: + src = The source of the cast + + Returns: Nominally the result is the desired value of the cast operation, + which will be forwarded as the result of the cast. For `Throw`, the + function never returns because it throws an exception. + + */ + static Dst onBadCast(Dst, Src)(Src src) + { + throw new CheckFailure("Erroneous cast: cast(%s) %s(%s)", + Dst.stringof, Src.stringof, src); + } + + /** + + Called automatically upon a bounds error. + + Params: + rhs = The right-hand side value in the assignment, after the operator has + been evaluated + bound = The value of the bound being violated + + Returns: Nominally the result is the desired value of the operator, which + will be forwarded as result. For `Throw`, the function never returns because + it throws. + + */ + static T onLowerBound(Rhs, T)(Rhs rhs, T bound) + { + throw new CheckFailure("Lower bound error: %s(%s) < %s(%s)", + Rhs.stringof, rhs, T.stringof, bound); + } + /// ditto + static T onUpperBound(Rhs, T)(Rhs rhs, T bound) + { + throw new CheckFailure("Upper bound error: %s(%s) > %s(%s)", + Rhs.stringof, rhs, T.stringof, bound); + } + + /** + + Called automatically upon a comparison for equality. Throws upon an + erroneous comparison (one that would make a signed negative value appear + equal to an unsigned positive value). + + Params: + lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of + the operator is `Checked!int` + rhs = The right-hand side type involved in the operator + + Returns: The result of the comparison. + + Throws: `CheckFailure` if the comparison is mathematically erroneous. + + */ + static bool hookOpEquals(L, R)(L lhs, R rhs) + { + bool error; + auto result = opChecked!"=="(lhs, rhs, error); + if (error) + { + throw new CheckFailure("Erroneous comparison: %s(%s) == %s(%s)", + L.stringof, lhs, R.stringof, rhs); + } + return result; + } + + /** + + Called automatically upon a comparison for ordering using one of the + operators `<`, `<=`, `>`, or `>=`. In case the comparison is erroneous (i.e. + it would make a signed negative value appear greater than or equal to an + unsigned positive value), throws a `Throw.CheckFailure` exception. + Otherwise, the three-state result is returned (positive if $(D lhs > rhs), + negative if $(D lhs < rhs), `0` otherwise). + + Params: + lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of + the operator is `Checked!int` + rhs = The right-hand side type involved in the operator + + Returns: For correct comparisons, returns a positive integer if $(D lhs > + rhs), a negative integer if $(D lhs < rhs), `0` if the two are equal. + + Throws: Upon a mistaken comparison such as $(D int(-1) < uint(0)), the + function never returns because it throws a `Throw.CheckedFailure` exception. + + */ + static int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + bool error; + auto result = opChecked!"cmp"(lhs, rhs, error); + if (error) + { + throw new CheckFailure("Erroneous ordering comparison: %s(%s) and %s(%s)", + Lhs.stringof, lhs, Rhs.stringof, rhs); + } + return result; + } + + /** + + Called automatically upon an overflow during a unary or binary operation. + + Params: + x = The operator, e.g. `-` + lhs = The left-hand side (or sole) argument + rhs = The right-hand side type involved in the operator + + Returns: Nominally the result is the desired value of the operator, which + will be forwarded as result. For `Throw`, the function never returns because + it throws an exception. + + */ + static typeof(~Lhs()) onOverflow(string x, Lhs)(Lhs lhs) + { + throw new CheckFailure("Overflow on unary operator: %s%s(%s)", + x, Lhs.stringof, lhs); + } + /// ditto + static typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + throw new CheckFailure("Overflow on binary operator: %s(%s) %s %s(%s)", + Lhs.stringof, lhs, x, Rhs.stringof, rhs); + } +} + +/// +@safe unittest +{ + void test(T)() + { + Checked!(int, Throw) x; + x = 42; + auto x1 = cast(T) x; + assert(x1 == 42); + x = T.max + 1; + import std.exception : assertThrown, assertNotThrown; + assertThrown(cast(T) x); + x = x.max; + assertThrown(x += 42); + assertThrown(x += 42L); + x = x.min; + assertThrown(-x); + assertThrown(x -= 42); + assertThrown(x -= 42L); + x = -1; + assertNotThrown(x == -1); + assertThrown(x == uint(-1)); + assertNotThrown(x <= -1); + assertThrown(x <= uint(-1)); + } + test!short; + test!(const short); + test!(immutable short); +} + +// Warn +/** +Hook that prints to `stderr` a trace of all integral errors, without affecting +default behavior. +*/ +struct Warn +{ + import std.stdio : stderr; +static: + /** + + Called automatically upon a bad cast from `src` to type `Dst` (one that + loses precision or attempts to convert a negative value to an unsigned + type). + + Params: + src = The source of the cast + Dst = The target type of the cast + + Returns: `cast(Dst) src` + + */ + Dst onBadCast(Dst, Src)(Src src) + { + stderr.writefln("Erroneous cast: cast(%s) %s(%s)", + Dst.stringof, Src.stringof, src); + return cast(Dst) src; + } + + /** + + Called automatically upon a bad `opOpAssign` call (one that loses precision + or attempts to convert a negative value to an unsigned type). + + Params: + rhs = The right-hand side value in the assignment, after the operator has + been evaluated + bound = The bound being violated + + Returns: `cast(Lhs) rhs` + */ + Lhs onLowerBound(Rhs, T)(Rhs rhs, T bound) + { + stderr.writefln("Lower bound error: %s(%s) < %s(%s)", + Rhs.stringof, rhs, T.stringof, bound); + return cast(T) rhs; + } + /// ditto + T onUpperBound(Rhs, T)(Rhs rhs, T bound) + { + stderr.writefln("Upper bound error: %s(%s) > %s(%s)", + Rhs.stringof, rhs, T.stringof, bound); + return cast(T) rhs; + } + + /** + + Called automatically upon a comparison for equality. In case of an Erroneous + comparison (one that would make a signed negative value appear equal to an + unsigned positive value), writes a warning message to `stderr` as a side + effect. + + Params: + lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of + the operator is `Checked!int` + rhs = The right-hand side type involved in the operator + + Returns: In all cases the function returns the built-in result of $(D lhs == + rhs). + + */ + bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + bool error; + auto result = opChecked!"=="(lhs, rhs, error); + if (error) + { + stderr.writefln("Erroneous comparison: %s(%s) == %s(%s)", + Lhs.stringof, lhs, Rhs.stringof, rhs); + return lhs == rhs; + } + return result; + } + + /// + @system unittest + { + auto x = checked!Warn(-42); + // Passes + assert(x == -42); + // Passes but prints a warning + // assert(x == uint(-42)); + } + + /** + + Called automatically upon a comparison for ordering using one of the + operators `<`, `<=`, `>`, or `>=`. In case the comparison is erroneous (i.e. + it would make a signed negative value appear greater than or equal to an + unsigned positive value), then a warning message is printed to `stderr`. + + Params: + lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of + the operator is `Checked!int` + rhs = The right-hand side type involved in the operator + + Returns: In all cases, returns $(D lhs < rhs ? -1 : lhs > rhs). The result + is not autocorrected in case of an erroneous comparison. + + */ + int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + bool error; + auto result = opChecked!"cmp"(lhs, rhs, error); + if (error) + { + stderr.writefln("Erroneous ordering comparison: %s(%s) and %s(%s)", + Lhs.stringof, lhs, Rhs.stringof, rhs); + return lhs < rhs ? -1 : lhs > rhs; + } + return result; + } + + /// + @system unittest + { + auto x = checked!Warn(-42); + // Passes + assert(x <= -42); + // Passes but prints a warning + // assert(x <= uint(-42)); + } + + /** + + Called automatically upon an overflow during a unary or binary operation. + + Params: + x = The operator involved + Lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of + the operator is `Checked!int` + Rhs = The right-hand side type involved in the operator + + Returns: $(D mixin(x ~ "lhs")) for unary, $(D mixin("lhs" ~ x ~ "rhs")) for + binary + + */ + typeof(~Lhs()) onOverflow(string x, Lhs)(ref Lhs lhs) + { + stderr.writefln("Overflow on unary operator: %s%s(%s)", + x, Lhs.stringof, lhs); + return mixin(x ~ "lhs"); + } + /// ditto + typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + stderr.writefln("Overflow on binary operator: %s(%s) %s %s(%s)", + Lhs.stringof, lhs, x, Rhs.stringof, rhs); + return mixin("lhs" ~ x ~ "rhs"); + } +} + +/// +@system unittest +{ + auto x = checked!Warn(42); + short x1 = cast(short) x; + //x += long(int.max); + auto y = checked!Warn(cast(const int) 42); + short y1 = cast(const byte) y; +} + +// ProperCompare +/** + +Hook that provides arithmetically correct comparisons for equality and ordering. +Comparing an object of type $(D Checked!(X, ProperCompare)) against another +integral (for equality or ordering) ensures that no surprising conversions from +signed to unsigned integral occur before the comparison. Using $(D Checked!(X, +ProperCompare)) on either side of a comparison for equality against a +floating-point number makes sure the integral can be properly converted to the +floating point type, thus making sure equality is transitive. + +*/ +struct ProperCompare +{ + /** + Hook for `==` and `!=` that ensures comparison against integral values has + the behavior expected by the usual arithmetic rules. The built-in semantics + yield surprising behavior when comparing signed values against unsigned + values for equality, for example $(D uint.max == -1) or $(D -1_294_967_296 == + 3_000_000_000u). The call $(D hookOpEquals(x, y)) returns `true` if and only + if `x` and `y` represent the same arithmetic number. + + If one of the numbers is an integral and the other is a floating-point + number, $(D hookOpEquals(x, y)) returns `true` if and only if the integral + can be converted exactly (without approximation) to the floating-point + number. This is in order to preserve transitivity of equality: if $(D + hookOpEquals(x, y)) and $(D hookOpEquals(y, z)) then $(D hookOpEquals(y, + z)), in case `x`, `y`, and `z` are a mix of integral and floating-point + numbers. + + Params: + lhs = The left-hand side of the comparison for equality + rhs = The right-hand side of the comparison for equality + + Returns: + The result of the comparison, `true` if the values are equal + */ + static bool hookOpEquals(L, R)(L lhs, R rhs) + { + alias C = typeof(lhs + rhs); + static if (isFloatingPoint!C) + { + static if (!isFloatingPoint!L) + { + return hookOpEquals(rhs, lhs); + } + else static if (!isFloatingPoint!R) + { + static assert(isFloatingPoint!L && !isFloatingPoint!R); + auto rhs1 = C(rhs); + return lhs == rhs1 && cast(R) rhs1 == rhs; + } + else + return lhs == rhs; + } + else + { + bool error; + auto result = opChecked!"=="(lhs, rhs, error); + if (error) + { + // Only possible error is a wrong "true" + return false; + } + return result; + } + } + + /** + Hook for `<`, `<=`, `>`, and `>=` that ensures comparison against integral + values has the behavior expected by the usual arithmetic rules. The built-in + semantics yield surprising behavior when comparing signed values against + unsigned values, for example $(D 0u < -1). The call $(D hookOpCmp(x, y)) + returns `-1` if and only if `x` is smaller than `y` in abstract arithmetic + sense. + + If one of the numbers is an integral and the other is a floating-point + number, $(D hookOpEquals(x, y)) returns a floating-point number that is `-1` + if `x < y`, `0` if `x == y`, `1` if `x > y`, and `NaN` if the floating-point + number is `NaN`. + + Params: + lhs = The left-hand side of the comparison for ordering + rhs = The right-hand side of the comparison for ordering + + Returns: + The result of the comparison (negative if $(D lhs < rhs), positive if $(D + lhs > rhs), `0` if the values are equal) + */ + static auto hookOpCmp(L, R)(L lhs, R rhs) + { + alias C = typeof(lhs + rhs); + static if (isFloatingPoint!C) + { + return lhs < rhs + ? C(-1) + : lhs > rhs ? C(1) : lhs == rhs ? C(0) : C.init; + } + else + { + static if (!valueConvertible!(L, C) || !valueConvertible!(R, C)) + { + static assert(isUnsigned!C); + static assert(isUnsigned!L != isUnsigned!R); + if (!isUnsigned!L && lhs < 0) + return -1; + if (!isUnsigned!R && rhs < 0) + return 1; + } + return lhs < rhs ? -1 : lhs > rhs; + } + } +} + +/// +@safe unittest +{ + alias opEqualsProper = ProperCompare.hookOpEquals; + assert(opEqualsProper(42, 42)); + assert(opEqualsProper(42.0, 42.0)); + assert(opEqualsProper(42u, 42)); + assert(opEqualsProper(42, 42u)); + assert(-1 == 4294967295u); + assert(!opEqualsProper(-1, 4294967295u)); + assert(!opEqualsProper(const uint(-1), -1)); + assert(!opEqualsProper(uint(-1), -1.0)); + assert(3_000_000_000U == -1_294_967_296); + assert(!opEqualsProper(3_000_000_000U, -1_294_967_296)); +} + +@safe unittest +{ + alias opCmpProper = ProperCompare.hookOpCmp; + assert(opCmpProper(42, 42) == 0); + assert(opCmpProper(42, 42.0) == 0); + assert(opCmpProper(41, 42.0) < 0); + assert(opCmpProper(42, 41.0) > 0); + import std.math : isNaN; + assert(isNaN(opCmpProper(41, double.init))); + assert(opCmpProper(42u, 42) == 0); + assert(opCmpProper(42, 42u) == 0); + assert(opCmpProper(-1, uint(-1)) < 0); + assert(opCmpProper(uint(-1), -1) > 0); + assert(opCmpProper(-1.0, -1) == 0); +} + +@safe unittest +{ + auto x1 = Checked!(uint, ProperCompare)(42u); + assert(x1.get < -1); + assert(x1 > -1); +} + +// WithNaN +/** + +Hook that reserves a special value as a "Not a Number" representative. For +signed integrals, the reserved value is `T.min`. For signed integrals, the +reserved value is `T.max`. + +The default value of a $(D Checked!(X, WithNaN)) is its NaN value, so care must +be taken that all variables are explicitly initialized. Any arithmetic and logic +operation involving at least on NaN becomes NaN itself. All of $(D a == b), $(D +a < b), $(D a > b), $(D a <= b), $(D a >= b) yield `false` if at least one of +`a` and `b` is NaN. + +*/ +struct WithNaN +{ +static: + /** + The default value used for values not explicitly initialized. It is the NaN + value, i.e. `T.min` for signed integrals and `T.max` for unsigned integrals. + */ + enum T defaultValue(T) = T.min == 0 ? T.max : T.min; + /** + The maximum value representable is $(D T.max) for signed integrals, $(D + T.max - 1) for unsigned integrals. The minimum value representable is $(D + T.min + 1) for signed integrals, $(D 0) for unsigned integrals. + */ + enum T max(T) = cast(T) (T.min == 0 ? T.max - 1 : T.max); + /// ditto + enum T min(T) = cast(T) (T.min == 0 ? T(0) : T.min + 1); + + /** + If `rhs` is `WithNaN.defaultValue!Rhs`, returns + `WithNaN.defaultValue!Lhs`. Otherwise, returns $(D cast(Lhs) rhs). + + Params: + rhs = the value being cast (`Rhs` is the first argument to `Checked`) + Lhs = the target type of the cast + + Returns: The result of the cast operation. + */ + Lhs hookOpCast(Lhs, Rhs)(Rhs rhs) + { + static if (is(Lhs == bool)) + { + return rhs != defaultValue!Rhs && rhs != 0; + } + else static if (valueConvertible!(Rhs, Lhs)) + { + return rhs != defaultValue!Rhs ? Lhs(rhs) : defaultValue!Lhs; + } + else + { + // Not value convertible, only viable option is rhs fits within the + // bounds of Lhs + static if (ProperCompare.hookOpCmp(Rhs.min, Lhs.min) < 0) + { + // Example: hookOpCast!short(int(42)), hookOpCast!uint(int(42)) + if (ProperCompare.hookOpCmp(rhs, Lhs.min) < 0) + return defaultValue!Lhs; + } + static if (ProperCompare.hookOpCmp(Rhs.max, Lhs.max) > 0) + { + // Example: hookOpCast!int(uint(42)) + if (ProperCompare.hookOpCmp(rhs, Lhs.max) > 0) + return defaultValue!Lhs; + } + return cast(Lhs) rhs; + } + } + + /// + @safe unittest + { + auto x = checked!WithNaN(422); + assert((cast(ubyte) x) == 255); + x = checked!WithNaN(-422); + assert((cast(byte) x) == -128); + assert(cast(short) x == -422); + assert(cast(bool) x); + x = x.init; // set back to NaN + assert(x != true); + assert(x != false); + } + + /** + + Returns `false` if $(D lhs == WithNaN.defaultValue!Lhs), $(D lhs == rhs) + otherwise. + + Params: + lhs = The left-hand side of the comparison (`Lhs` is the first argument to + `Checked`) + rhs = The right-hand side of the comparison + + Returns: `lhs != WithNaN.defaultValue!Lhs && lhs == rhs` + */ + bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + return lhs != defaultValue!Lhs && lhs == rhs; + } + + /** + + If $(D lhs == WithNaN.defaultValue!Lhs), returns `double.init`. Otherwise, + has the same semantics as the default comparison. + + Params: + lhs = The left-hand side of the comparison (`Lhs` is the first argument to + `Checked`) + rhs = The right-hand side of the comparison + + Returns: `double.init` if `lhs == WitnNaN.defaultValue!Lhs`, `-1.0` if $(D + lhs < rhs), `0.0` if $(D lhs == rhs), `1.0` if $(D lhs > rhs). + + */ + double hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + if (lhs == defaultValue!Lhs) return double.init; + return lhs < rhs + ? -1.0 + : lhs > rhs ? 1.0 : lhs == rhs ? 0.0 : double.init; + } + + /// + @safe unittest + { + Checked!(int, WithNaN) x; + assert(!(x < 0) && !(x > 0) && !(x == 0)); + x = 1; + assert(x > 0 && !(x < 0) && !(x == 0)); + } + + /** + Defines hooks for unary operators `-`, `~`, `++`, and `--`. + + For `-` and `~`, if $(D v == WithNaN.defaultValue!T), returns + `WithNaN.defaultValue!T`. Otherwise, the semantics is the same as for the + built-in operator. + + For `++` and `--`, if $(D v == WithNaN.defaultValue!Lhs) or the operation + would result in an overflow, sets `v` to `WithNaN.defaultValue!T`. + Otherwise, the semantics is the same as for the built-in operator. + + Params: + x = The operator symbol + v = The left-hand side of the comparison (`T` is the first argument to + `Checked`) + + Returns: $(UL $(LI For $(D x == "-" || x == "~"): If $(D v == + WithNaN.defaultValue!T), the function returns `WithNaN.defaultValue!T`. + Otherwise it returns the normal result of the operator.) $(LI For $(D x == + "++" || x == "--"): The function returns `void`.)) + + */ + auto hookOpUnary(string x, T)(ref T v) + { + static if (x == "-" || x == "~") + { + return v != defaultValue!T ? mixin(x ~ "v") : v; + } + else static if (x == "++") + { + static if (defaultValue!T == T.min) + { + if (v != defaultValue!T) + { + if (v == T.max) v = defaultValue!T; + else ++v; + } + } + else + { + static assert(defaultValue!T == T.max); + if (v != defaultValue!T) ++v; + } + } + else static if (x == "--") + { + if (v != defaultValue!T) --v; + } + } + + /// + @safe unittest + { + Checked!(int, WithNaN) x; + ++x; + assert(x.isNaN); + x = 1; + assert(!x.isNaN); + x = -x; + ++x; + assert(!x.isNaN); + } + + @safe unittest // for coverage + { + Checked!(uint, WithNaN) y; + ++y; + assert(y.isNaN); + } + + /** + Defines hooks for binary operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, + `^`, `<<`, `>>`, and `>>>` for cases where a `Checked` object is the + left-hand side operand. If $(D lhs == WithNaN.defaultValue!Lhs), returns + $(D WithNaN.defaultValue!(typeof(lhs + rhs))) without evaluating the + operand. Otherwise, evaluates the operand. If evaluation does not overflow, + returns the result. Otherwise, returns $(D WithNaN.defaultValue!(typeof(lhs + + rhs))). + + Params: + x = The operator symbol + lhs = The left-hand side operand (`Lhs` is the first argument to `Checked`) + rhs = The right-hand side operand + + Returns: If $(D lhs != WithNaN.defaultValue!Lhs) and the operator does not + overflow, the function returns the same result as the built-in operator. In + all other cases, returns $(D WithNaN.defaultValue!(typeof(lhs + rhs))). + */ + auto hookOpBinary(string x, L, R)(L lhs, R rhs) + { + alias Result = typeof(lhs + rhs); + if (lhs != defaultValue!L) + { + bool error; + auto result = opChecked!x(lhs, rhs, error); + if (!error) return result; + } + return defaultValue!Result; + } + + /// + @safe unittest + { + Checked!(int, WithNaN) x; + assert((x + 1).isNaN); + x = 100; + assert(!(x + 1).isNaN); + } + + /** + Defines hooks for binary operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, + `^`, `<<`, `>>`, and `>>>` for cases where a `Checked` object is the + right-hand side operand. If $(D rhs == WithNaN.defaultValue!Rhs), returns + $(D WithNaN.defaultValue!(typeof(lhs + rhs))) without evaluating the + operand. Otherwise, evaluates the operand. If evaluation does not overflow, + returns the result. Otherwise, returns $(D WithNaN.defaultValue!(typeof(lhs + + rhs))). + + Params: + x = The operator symbol + lhs = The left-hand side operand + rhs = The right-hand side operand (`Rhs` is the first argument to `Checked`) + + Returns: If $(D rhs != WithNaN.defaultValue!Rhs) and the operator does not + overflow, the function returns the same result as the built-in operator. In + all other cases, returns $(D WithNaN.defaultValue!(typeof(lhs + rhs))). + */ + auto hookOpBinaryRight(string x, L, R)(L lhs, R rhs) + { + alias Result = typeof(lhs + rhs); + if (rhs != defaultValue!R) + { + bool error; + auto result = opChecked!x(lhs, rhs, error); + if (!error) return result; + } + return defaultValue!Result; + } + /// + @safe unittest + { + Checked!(int, WithNaN) x; + assert((1 + x).isNaN); + x = 100; + assert(!(1 + x).isNaN); + } + + /** + + Defines hooks for binary operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, + `&=`, `|=`, `^=`, `<<=`, `>>=`, and `>>>=` for cases where a `Checked` + object is the left-hand side operand. If $(D lhs == + WithNaN.defaultValue!Lhs), no action is carried. Otherwise, evaluates the + operand. If evaluation does not overflow and fits in `Lhs` without loss of + information or change of sign, sets `lhs` to the result. Otherwise, sets + `lhs` to `WithNaN.defaultValue!Lhs`. + + Params: + x = The operator symbol (without the `=`) + lhs = The left-hand side operand (`Lhs` is the first argument to `Checked`) + rhs = The right-hand side operand + + Returns: `void` + */ + void hookOpOpAssign(string x, L, R)(ref L lhs, R rhs) + { + if (lhs == defaultValue!L) + return; + bool error; + auto temp = opChecked!x(lhs, rhs, error); + lhs = error + ? defaultValue!L + : hookOpCast!L(temp); + } + + /// + @safe unittest + { + Checked!(int, WithNaN) x; + x += 4; + assert(x.isNaN); + x = 0; + x += 4; + assert(!x.isNaN); + x += int.max; + assert(x.isNaN); + } +} + +/// +@safe unittest +{ + auto x1 = Checked!(int, WithNaN)(); + assert(x1.isNaN); + assert(x1.get == int.min); + assert(x1 != x1); + assert(!(x1 < x1)); + assert(!(x1 > x1)); + assert(!(x1 == x1)); + ++x1; + assert(x1.isNaN); + assert(x1.get == int.min); + --x1; + assert(x1.isNaN); + assert(x1.get == int.min); + x1 = 42; + assert(!x1.isNaN); + assert(x1 == x1); + assert(x1 <= x1); + assert(x1 >= x1); + static assert(x1.min == int.min + 1); + x1 += long(int.max); +} + +/** +Queries whether a $(D Checked!(T, WithNaN)) object is not a number (NaN). + +Params: x = the `Checked` instance queried + +Returns: `true` if `x` is a NaN, `false` otherwise +*/ +bool isNaN(T)(const Checked!(T, WithNaN) x) +{ + return x.get == x.init.get; +} + +/// +@safe unittest +{ + auto x1 = Checked!(int, WithNaN)(); + assert(x1.isNaN); + x1 = 1; + assert(!x1.isNaN); + x1 = x1.init; + assert(x1.isNaN); +} + +@safe unittest +{ + void test1(T)() + { + auto x1 = Checked!(T, WithNaN)(); + assert(x1.isNaN); + assert(x1.get == int.min); + assert(x1 != x1); + assert(!(x1 < x1)); + assert(!(x1 > x1)); + assert(!(x1 == x1)); + assert(x1.get == int.min); + auto x2 = Checked!(T, WithNaN)(42); + assert(!x2.isNaN); + assert(x2 == x2); + assert(x2 <= x2); + assert(x2 >= x2); + static assert(x2.min == T.min + 1); + } + test1!int; + test1!(const int); + test1!(immutable int); + + void test2(T)() + { + auto x1 = Checked!(T, WithNaN)(); + assert(x1.get == T.min); + assert(x1 != x1); + assert(!(x1 < x1)); + assert(!(x1 > x1)); + assert(!(x1 == x1)); + ++x1; + assert(x1.get == T.min); + --x1; + assert(x1.get == T.min); + x1 = 42; + assert(x1 == x1); + assert(x1 <= x1); + assert(x1 >= x1); + static assert(x1.min == T.min + 1); + x1 += long(T.max); + } + test2!int; +} + +@safe unittest +{ + alias Smart(T) = Checked!(Checked!(T, ProperCompare), WithNaN); + Smart!int x1; + assert(x1 != x1); + x1 = -1; + assert(x1 < 1u); + auto x2 = Smart!(const int)(42); +} + +// Saturate +/** + +Hook that implements $(I saturation), i.e. any arithmetic operation that would +overflow leaves the result at its extreme value (`min` or `max` depending on the +direction of the overflow). + +Saturation is not sticky; if a value reaches its saturation value, another +operation may take it back to normal range. + +*/ +struct Saturate +{ +static: + /** + + Implements saturation for operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, + and `>>>=`. This hook is called if the result of the binary operation does + not fit in `Lhs` without loss of information or a change in sign. + + Params: + Rhs = The right-hand side type in the assignment, after the operation has + been computed + bound = The bound being violated + + Returns: `Lhs.max` if $(D rhs >= 0), `Lhs.min` otherwise. + + */ + T onLowerBound(Rhs, T)(Rhs rhs, T bound) + { + return bound; + } + /// ditto + T onUpperBound(Rhs, T)(Rhs rhs, T bound) + { + return bound; + } + /// + @safe unittest + { + auto x = checked!Saturate(short(100)); + x += 33000; + assert(x == short.max); + x -= 70000; + assert(x == short.min); + } + + /** + + Implements saturation for operators `+`, `-` (unary and binary), `*`, `/`, + `%`, `^^`, `&`, `|`, `^`, `<<`, `>>`, and `>>>`. + + For unary `-`, `onOverflow` is called if $(D lhs == Lhs.min) and `Lhs` is a + signed type. The function returns `Lhs.max`. + + For binary operators, the result is as follows: $(UL $(LI `Lhs.max` if the + result overflows in the positive direction, on division by `0`, or on + shifting right by a negative value) $(LI `Lhs.min` if the result overflows + in the negative direction) $(LI `0` if `lhs` is being shifted left by a + negative value, or shifted right by a large positive value)) + + Params: + x = The operator involved in the `opAssign` operation + Lhs = The left-hand side of the operator (`Lhs` is the first argument to + `Checked`) + Rhs = The right-hand side type in the operator + + Returns: The saturated result of the operator. + + */ + typeof(~Lhs()) onOverflow(string x, Lhs)(Lhs lhs) + { + static assert(x == "-" || x == "++" || x == "--"); + return x == "--" ? Lhs.min : Lhs.max; + } + /// ditto + typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + static if (x == "+") + return rhs >= 0 ? Lhs.max : Lhs.min; + else static if (x == "*") + return (lhs >= 0) == (rhs >= 0) ? Lhs.max : Lhs.min; + else static if (x == "^^") + return lhs > 0 || !(rhs & 1) ? Lhs.max : Lhs.min; + else static if (x == "-") + return rhs >= 0 ? Lhs.min : Lhs.max; + else static if (x == "/" || x == "%") + return Lhs.max; + else static if (x == "<<") + return rhs >= 0 ? Lhs.max : 0; + else static if (x == ">>" || x == ">>>") + return rhs >= 0 ? 0 : Lhs.max; + else + static assert(false); + } + /// + @safe unittest + { + assert(checked!Saturate(int.max) + 1 == int.max); + assert(checked!Saturate(100) ^^ 10 == int.max); + assert(checked!Saturate(-100) ^^ 10 == int.max); + assert(checked!Saturate(100) / 0 == int.max); + assert(checked!Saturate(100) << -1 == 0); + assert(checked!Saturate(100) << 33 == int.max); + assert(checked!Saturate(100) >> -1 == int.max); + assert(checked!Saturate(100) >> 33 == 0); + } +} + +/// +@safe unittest +{ + auto x = checked!Saturate(int.max); + ++x; + assert(x == int.max); + --x; + assert(x == int.max - 1); + x = int.min; + assert(-x == int.max); + x -= 42; + assert(x == int.min); + assert(x * -2 == int.max); +} + +/* +Yields `true` if `T1` is "value convertible" (by C's "value preserving" rule, +see $(HTTP c-faq.com/expr/preservingrules.html)) to `T2`, where the two are +integral types. That is, all of values in `T1` are also in `T2`. For example +`int` is value convertible to `long` but not to `uint` or `ulong`. +*/ +private enum valueConvertible(T1, T2) = isIntegral!T1 && isIntegral!T2 && + is(T1 : T2) && ( + isUnsigned!T1 == isUnsigned!T2 || // same signedness + !isUnsigned!T2 && T2.sizeof > T1.sizeof // safely convertible + ); + +/** + +Defines binary operations with overflow checking for any two integral types. +The result type obeys the language rules (even when they may be +counterintuitive), and `overflow` is set if an overflow occurs (including +inadvertent change of signedness, e.g. `-1` is converted to `uint`). +Conceptually the behavior is: + +$(OL $(LI Perform the operation in infinite precision) +$(LI If the infinite-precision result fits in the result type, return it and +do not touch `overflow`) +$(LI Otherwise, set `overflow` to `true` and return an unspecified value) +) + +The implementation exploits properties of types and operations to minimize +additional work. + +Params: +x = The binary operator involved, e.g. `/` +lhs = The left-hand side of the operator +rhs = The right-hand side of the operator +overflow = The overflow indicator (assigned `true` in case there's an error) + +Returns: +The result of the operation, which is the same as the built-in operator +*/ +typeof(mixin(x == "cmp" ? "0" : ("L() " ~ x ~ " R()"))) +opChecked(string x, L, R)(const L lhs, const R rhs, ref bool overflow) +if (isIntegral!L && isIntegral!R) +{ + static if (x == "cmp") + alias Result = int; + else + alias Result = typeof(mixin("L() " ~ x ~ " R()")); + + import core.checkedint : addu, adds, subs, muls, subu, mulu; + import std.algorithm.comparison : among; + static if (x == "==") + { + alias C = typeof(lhs + rhs); + static if (valueConvertible!(L, C) && valueConvertible!(R, C)) + { + // Values are converted to R before comparison, cool. + return lhs == rhs; + } + else + { + static assert(isUnsigned!C); + static assert(isUnsigned!L != isUnsigned!R); + if (lhs != rhs) return false; + // R(lhs) and R(rhs) have the same bit pattern, yet may be + // different due to signedness change. + static if (!isUnsigned!R) + { + if (rhs >= 0) + return true; + } + else + { + if (lhs >= 0) + return true; + } + overflow = true; + return true; + } + } + else static if (x == "cmp") + { + alias C = typeof(lhs + rhs); + static if (!valueConvertible!(L, C) || !valueConvertible!(R, C)) + { + static assert(isUnsigned!C); + static assert(isUnsigned!L != isUnsigned!R); + if (!isUnsigned!L && lhs < 0) + { + overflow = true; + return -1; + } + if (!isUnsigned!R && rhs < 0) + { + overflow = true; + return 1; + } + } + return lhs < rhs ? -1 : lhs > rhs; + } + else static if (x.among("<<", ">>", ">>>")) + { + // Handle shift separately from all others. The test below covers + // negative rhs as well. + import std.conv : unsigned; + if (unsigned(rhs) > 8 * Result.sizeof) goto fail; + return mixin("lhs" ~ x ~ "rhs"); + } + else static if (x.among("&", "|", "^")) + { + // Nothing to check + return mixin("lhs" ~ x ~ "rhs"); + } + else static if (x == "^^") + { + // Exponentiation is weird, handle separately + return pow(lhs, rhs, overflow); + } + else static if (valueConvertible!(L, Result) && + valueConvertible!(R, Result)) + { + static if (L.sizeof < Result.sizeof && R.sizeof < Result.sizeof && + x.among("+", "-", "*")) + { + // No checks - both are value converted and result is in range + return mixin("lhs" ~ x ~ "rhs"); + } + else static if (x == "+") + { + static if (isUnsigned!Result) alias impl = addu; + else alias impl = adds; + return impl(Result(lhs), Result(rhs), overflow); + } + else static if (x == "-") + { + static if (isUnsigned!Result) alias impl = subu; + else alias impl = subs; + return impl(Result(lhs), Result(rhs), overflow); + } + else static if (x == "*") + { + static if (!isUnsigned!L && !isUnsigned!R && + is(L == Result)) + { + if (lhs == Result.min && rhs == -1) goto fail; + } + static if (isUnsigned!Result) alias impl = mulu; + else alias impl = muls; + return impl(Result(lhs), Result(rhs), overflow); + } + else static if (x == "/" || x == "%") + { + static if (!isUnsigned!L && !isUnsigned!R && + is(L == Result) && x == "/") + { + if (lhs == Result.min && rhs == -1) goto fail; + } + if (rhs == 0) goto fail; + return mixin("lhs" ~ x ~ "rhs"); + } + else static assert(0, x); + } + else // Mixed signs + { + static assert(isUnsigned!Result); + static assert(isUnsigned!L != isUnsigned!R); + static if (x == "+") + { + static if (!isUnsigned!L) + { + if (lhs < 0) + return subu(Result(rhs), Result(-lhs), overflow); + } + else static if (!isUnsigned!R) + { + if (rhs < 0) + return subu(Result(lhs), Result(-rhs), overflow); + } + return addu(Result(lhs), Result(rhs), overflow); + } + else static if (x == "-") + { + static if (!isUnsigned!L) + { + if (lhs < 0) goto fail; + } + else static if (!isUnsigned!R) + { + if (rhs < 0) + return addu(Result(lhs), Result(-rhs), overflow); + } + return subu(Result(lhs), Result(rhs), overflow); + } + else static if (x == "*") + { + static if (!isUnsigned!L) + { + if (lhs < 0) goto fail; + } + else static if (!isUnsigned!R) + { + if (rhs < 0) goto fail; + } + return mulu(Result(lhs), Result(rhs), overflow); + } + else static if (x == "/" || x == "%") + { + static if (!isUnsigned!L) + { + if (lhs < 0 || rhs == 0) goto fail; + } + else static if (!isUnsigned!R) + { + if (rhs <= 0) goto fail; + } + return mixin("Result(lhs)" ~ x ~ "Result(rhs)"); + } + else static assert(0, x); + } + debug assert(false); +fail: + overflow = true; + return Result(0); +} + +/// +@safe unittest +{ + bool overflow; + assert(opChecked!"+"(const short(1), short(1), overflow) == 2 && !overflow); + assert(opChecked!"+"(1, 1, overflow) == 2 && !overflow); + assert(opChecked!"+"(1, 1u, overflow) == 2 && !overflow); + assert(opChecked!"+"(-1, 1u, overflow) == 0 && !overflow); + assert(opChecked!"+"(1u, -1, overflow) == 0 && !overflow); +} + +/// +@safe unittest +{ + bool overflow; + assert(opChecked!"-"(1, 1, overflow) == 0 && !overflow); + assert(opChecked!"-"(1, 1u, overflow) == 0 && !overflow); + assert(opChecked!"-"(1u, -1, overflow) == 2 && !overflow); + assert(opChecked!"-"(-1, 1u, overflow) == 0 && overflow); +} + +@safe unittest +{ + bool overflow; + assert(opChecked!"*"(2, 3, overflow) == 6 && !overflow); + assert(opChecked!"*"(2, 3u, overflow) == 6 && !overflow); + assert(opChecked!"*"(1u, -1, overflow) == 0 && overflow); + //assert(mul(-1, 1u, overflow) == uint.max - 1 && overflow); +} + +@safe unittest +{ + bool overflow; + assert(opChecked!"/"(6, 3, overflow) == 2 && !overflow); + assert(opChecked!"/"(6, 3, overflow) == 2 && !overflow); + assert(opChecked!"/"(6u, 3, overflow) == 2 && !overflow); + assert(opChecked!"/"(6, 3u, overflow) == 2 && !overflow); + assert(opChecked!"/"(11, 0, overflow) == 0 && overflow); + overflow = false; + assert(opChecked!"/"(6u, 0, overflow) == 0 && overflow); + overflow = false; + assert(opChecked!"/"(-6, 2u, overflow) == 0 && overflow); + overflow = false; + assert(opChecked!"/"(-6, 0u, overflow) == 0 && overflow); + overflow = false; + assert(opChecked!"cmp"(0u, -6, overflow) == 1 && overflow); + overflow = false; + assert(opChecked!"|"(1, 2, overflow) == 3 && !overflow); +} + +/* +Exponentiation function used by the implementation of operator `^^`. +*/ +private pure @safe nothrow @nogc +auto pow(L, R)(const L lhs, const R rhs, ref bool overflow) +if (isIntegral!L && isIntegral!R) +{ + if (rhs <= 1) + { + if (rhs == 0) return 1; + static if (!isUnsigned!R) + return rhs == 1 + ? lhs + : (rhs == -1 && (lhs == 1 || lhs == -1)) ? lhs : 0; + else + return lhs; + } + + typeof(lhs ^^ rhs) b = void; + static if (!isUnsigned!L && isUnsigned!(typeof(b))) + { + // Need to worry about mixed-sign stuff + if (lhs < 0) + { + if (rhs & 1) + { + if (lhs < 0) overflow = true; + return 0; + } + b = -lhs; + } + else + { + b = lhs; + } + } + else + { + b = lhs; + } + if (b == 1) return 1; + if (b == -1) return (rhs & 1) ? -1 : 1; + if (rhs > 63) + { + overflow = true; + return 0; + } + + assert((b > 1 || b < -1) && rhs > 1); + return powImpl(b, cast(uint) rhs, overflow); +} + +// Inspiration: http://www.stepanovpapers.com/PAM.pdf +pure @safe nothrow @nogc +private T powImpl(T)(T b, uint e, ref bool overflow) +if (isIntegral!T && T.sizeof >= 4) +{ + assert(e > 1); + + import core.checkedint : muls, mulu; + static if (isUnsigned!T) alias mul = mulu; + else alias mul = muls; + + T r = b; + --e; + // Loop invariant: r * (b ^^ e) is the actual result + for (;; e /= 2) + { + if (e % 2) + { + r = mul(r, b, overflow); + if (e == 1) break; + } + b = mul(b, b, overflow); + } + return r; +} + +@safe unittest +{ + static void testPow(T)(T x, uint e) + { + bool overflow; + assert(opChecked!"^^"(T(0), 0, overflow) == 1); + assert(opChecked!"^^"(-2, T(0), overflow) == 1); + assert(opChecked!"^^"(-2, T(1), overflow) == -2); + assert(opChecked!"^^"(-1, -1, overflow) == -1); + assert(opChecked!"^^"(-2, 1, overflow) == -2); + assert(opChecked!"^^"(-2, -1, overflow) == 0); + assert(opChecked!"^^"(-2, 4u, overflow) == 16); + assert(!overflow); + assert(opChecked!"^^"(-2, 3u, overflow) == 0); + assert(overflow); + overflow = false; + assert(opChecked!"^^"(3, 64u, overflow) == 0); + assert(overflow); + overflow = false; + foreach (uint i; 0 .. e) + { + assert(opChecked!"^^"(x, i, overflow) == x ^^ i); + assert(!overflow); + } + assert(opChecked!"^^"(x, e, overflow) == x ^^ e); + assert(overflow); + } + + testPow!int(3, 21); + testPow!uint(3, 21); + testPow!long(3, 40); + testPow!ulong(3, 41); +} + +version (unittest) private struct CountOverflows +{ + uint calls; + auto onOverflow(string op, Lhs)(Lhs lhs) + { + ++calls; + return mixin(op ~ "lhs"); + } + auto onOverflow(string op, Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + ++calls; + return mixin("lhs" ~ op ~ "rhs"); + } + T onLowerBound(Rhs, T)(Rhs rhs, T bound) + { + ++calls; + return cast(T) rhs; + } + T onUpperBound(Rhs, T)(Rhs rhs, T bound) + { + ++calls; + return cast(T) rhs; + } +} + +version (unittest) private struct CountOpBinary +{ + uint calls; + auto hookOpBinary(string op, Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + ++calls; + return mixin("lhs" ~ op ~ "rhs"); + } +} + +// opBinary +@nogc nothrow pure @safe unittest +{ + auto x = Checked!(const int, void)(42), y = Checked!(immutable int, void)(142); + assert(x + y == 184); + assert(x + 100 == 142); + assert(y - x == 100); + assert(200 - x == 158); + assert(y * x == 142 * 42); + assert(x / 1 == 42); + assert(x % 20 == 2); + + auto x1 = Checked!(int, CountOverflows)(42); + assert(x1 + 0 == 42); + assert(x1 + false == 42); + assert(is(typeof(x1 + 0.5) == double)); + assert(x1 + 0.5 == 42.5); + assert(x1.hook.calls == 0); + assert(x1 + int.max == int.max + 42); + assert(x1.hook.calls == 1); + assert(x1 * 2 == 84); + assert(x1.hook.calls == 1); + assert(x1 / 2 == 21); + assert(x1.hook.calls == 1); + assert(x1 % 20 == 2); + assert(x1.hook.calls == 1); + assert(x1 << 2 == 42 << 2); + assert(x1.hook.calls == 1); + assert(x1 << 42 == x1.get << x1.get); + assert(x1.hook.calls == 2); + x1 = int.min; + assert(x1 - 1 == int.max); + assert(x1.hook.calls == 3); + + auto x2 = Checked!(int, CountOpBinary)(42); + assert(x2 + 1 == 43); + assert(x2.hook.calls == 1); + + auto x3 = Checked!(uint, CountOverflows)(42u); + assert(x3 + 1 == 43); + assert(x3.hook.calls == 0); + assert(x3 - 1 == 41); + assert(x3.hook.calls == 0); + assert(x3 + (-42) == 0); + assert(x3.hook.calls == 0); + assert(x3 - (-42) == 84); + assert(x3.hook.calls == 0); + assert(x3 * 2 == 84); + assert(x3.hook.calls == 0); + assert(x3 * -2 == -84); + assert(x3.hook.calls == 1); + assert(x3 / 2 == 21); + assert(x3.hook.calls == 1); + assert(x3 / -2 == 0); + assert(x3.hook.calls == 2); + assert(x3 ^^ 2 == 42 * 42); + assert(x3.hook.calls == 2); + + auto x4 = Checked!(int, CountOverflows)(42); + assert(x4 + 1 == 43); + assert(x4.hook.calls == 0); + assert(x4 + 1u == 43); + assert(x4.hook.calls == 0); + assert(x4 - 1 == 41); + assert(x4.hook.calls == 0); + assert(x4 * 2 == 84); + assert(x4.hook.calls == 0); + x4 = -2; + assert(x4 + 2u == 0); + assert(x4.hook.calls == 0); + assert(x4 * 2u == -4); + assert(x4.hook.calls == 1); + + auto x5 = Checked!(int, CountOverflows)(3); + assert(x5 ^^ 0 == 1); + assert(x5 ^^ 1 == 3); + assert(x5 ^^ 2 == 9); + assert(x5 ^^ 3 == 27); + assert(x5 ^^ 4 == 81); + assert(x5 ^^ 5 == 81 * 3); + assert(x5 ^^ 6 == 81 * 9); +} + +// opBinaryRight +@nogc nothrow pure @safe unittest +{ + auto x1 = Checked!(int, CountOverflows)(42); + assert(1 + x1 == 43); + assert(true + x1 == 43); + assert(0.5 + x1 == 42.5); + auto x2 = Checked!(int, void)(42); + assert(x1 + x2 == 84); + assert(x2 + x1 == 84); +} + +// opOpAssign +@safe unittest +{ + auto x1 = Checked!(int, CountOverflows)(3); + assert((x1 += 2) == 5); + x1 *= 2_000_000_000L; + assert(x1.hook.calls == 1); + x1 *= -2_000_000_000L; + assert(x1.hook.calls == 2); + + auto x2 = Checked!(ushort, CountOverflows)(ushort(3)); + assert((x2 += 2) == 5); + assert(x2.hook.calls == 0); + assert((x2 += ushort.max) == cast(ushort) (ushort(5) + ushort.max)); + assert(x2.hook.calls == 1); + + auto x3 = Checked!(uint, CountOverflows)(3u); + x3 *= ulong(2_000_000_000); + assert(x3.hook.calls == 1); +} + +// opAssign +@safe unittest +{ + Checked!(int, void) x; + x = 42; + assert(x.get == 42); + x = x; + assert(x.get == 42); + x = short(43); + assert(x.get == 43); + x = ushort(44); + assert(x.get == 44); +} + +@safe unittest +{ + static assert(!is(typeof(Checked!(short, void)(ushort(42))))); + static assert(!is(typeof(Checked!(int, void)(long(42))))); + static assert(!is(typeof(Checked!(int, void)(ulong(42))))); + assert(Checked!(short, void)(short(42)).get == 42); + assert(Checked!(int, void)(ushort(42)).get == 42); +} + +// opCast +@nogc nothrow pure @safe unittest +{ + static assert(is(typeof(cast(float) Checked!(int, void)(42)) == float)); + assert(cast(float) Checked!(int, void)(42) == 42); + + assert(is(typeof(cast(long) Checked!(int, void)(42)) == long)); + assert(cast(long) Checked!(int, void)(42) == 42); + static assert(is(typeof(cast(long) Checked!(uint, void)(42u)) == long)); + assert(cast(long) Checked!(uint, void)(42u) == 42); + + auto x = Checked!(int, void)(42); + if (x) {} else assert(0); + x = 0; + if (x) assert(0); + + static struct Hook1 + { + uint calls; + Dst hookOpCast(Dst, Src)(Src value) + { + ++calls; + return 42; + } + } + auto y = Checked!(long, Hook1)(long.max); + assert(cast(int) y == 42); + assert(cast(uint) y == 42); + assert(y.hook.calls == 2); + + static struct Hook2 + { + uint calls; + Dst onBadCast(Dst, Src)(Src value) + { + ++calls; + return 42; + } + } + auto x1 = Checked!(uint, Hook2)(100u); + assert(cast(ushort) x1 == 100); + assert(cast(short) x1 == 100); + assert(cast(float) x1 == 100); + assert(cast(double) x1 == 100); + assert(cast(real) x1 == 100); + assert(x1.hook.calls == 0); + assert(cast(int) x1 == 100); + assert(x1.hook.calls == 0); + x1 = uint.max; + assert(cast(int) x1 == 42); + assert(x1.hook.calls == 1); + + auto x2 = Checked!(int, Hook2)(-100); + assert(cast(short) x2 == -100); + assert(cast(ushort) x2 == 42); + assert(cast(uint) x2 == 42); + assert(cast(ulong) x2 == 42); + assert(x2.hook.calls == 3); +} + +// opEquals +@nogc nothrow pure @safe unittest +{ + assert(Checked!(int, void)(42) == 42L); + assert(42UL == Checked!(int, void)(42)); + + static struct Hook1 + { + uint calls; + bool hookOpEquals(Lhs, Rhs)(const Lhs lhs, const Rhs rhs) + { + ++calls; + return lhs != rhs; + } + } + auto x1 = Checked!(int, Hook1)(100); + assert(x1 != Checked!(long, Hook1)(100)); + assert(x1.hook.calls == 1); + assert(x1 != 100u); + assert(x1.hook.calls == 2); + + static struct Hook2 + { + uint calls; + bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + ++calls; + return false; + } + } + auto x2 = Checked!(int, Hook2)(-100); + assert(x2 != x1); + // For coverage: lhs has no hookOpEquals, rhs does + assert(Checked!(uint, void)(100u) != x2); + // For coverage: different types, neither has a hookOpEquals + assert(Checked!(uint, void)(100u) == Checked!(int, void*)(100)); + assert(x2.hook.calls == 0); + assert(x2 != -100); + assert(x2.hook.calls == 1); + assert(x2 != cast(uint) -100); + assert(x2.hook.calls == 2); + x2 = 100; + assert(x2 != cast(uint) 100); + assert(x2.hook.calls == 3); + x2 = -100; + + auto x3 = Checked!(uint, Hook2)(100u); + assert(x3 != 100); + x3 = uint.max; + assert(x3 != -1); + + assert(x2 != x3); +} + +// opCmp +@nogc nothrow pure @safe unittest +{ + Checked!(int, void) x; + assert(x <= x); + assert(x < 45); + assert(x < 45u); + assert(x > -45); + assert(x < 44.2); + assert(x > -44.2); + assert(!(x < double.init)); + assert(!(x > double.init)); + assert(!(x <= double.init)); + assert(!(x >= double.init)); + + static struct Hook1 + { + uint calls; + int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + ++calls; + return 0; + } + } + auto x1 = Checked!(int, Hook1)(42); + assert(!(x1 < 43u)); + assert(!(43u < x1)); + assert(x1.hook.calls == 2); + + static struct Hook2 + { + uint calls; + int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) + { + ++calls; + return ProperCompare.hookOpCmp(lhs, rhs); + } + } + auto x2 = Checked!(int, Hook2)(-42); + assert(x2 < 43u); + assert(43u > x2); + assert(x2.hook.calls == 2); + x2 = 42; + assert(x2 > 41u); + + auto x3 = Checked!(uint, Hook2)(42u); + assert(x3 > 41); + assert(x3 > -41); +} + +// opUnary +@nogc nothrow pure @safe unittest +{ + auto x = Checked!(int, void)(42); + assert(x == +x); + static assert(is(typeof(-x) == typeof(x))); + assert(-x == Checked!(int, void)(-42)); + static assert(is(typeof(~x) == typeof(x))); + assert(~x == Checked!(int, void)(~42)); + assert(++x == 43); + assert(--x == 42); + + static struct Hook1 + { + uint calls; + auto hookOpUnary(string op, T)(T value) if (op == "-") + { + ++calls; + return T(42); + } + auto hookOpUnary(string op, T)(T value) if (op == "~") + { + ++calls; + return T(43); + } + } + auto x1 = Checked!(int, Hook1)(100); + assert(is(typeof(-x1) == typeof(x1))); + assert(-x1 == Checked!(int, Hook1)(42)); + assert(is(typeof(~x1) == typeof(x1))); + assert(~x1 == Checked!(int, Hook1)(43)); + assert(x1.hook.calls == 2); + + static struct Hook2 + { + uint calls; + void hookOpUnary(string op, T)(ref T value) if (op == "++") + { + ++calls; + --value; + } + void hookOpUnary(string op, T)(ref T value) if (op == "--") + { + ++calls; + ++value; + } + } + auto x2 = Checked!(int, Hook2)(100); + assert(++x2 == 99); + assert(x2 == 99); + assert(--x2 == 100); + assert(x2 == 100); + + auto x3 = Checked!(int, CountOverflows)(int.max - 1); + assert(++x3 == int.max); + assert(x3.hook.calls == 0); + assert(++x3 == int.min); + assert(x3.hook.calls == 1); + assert(-x3 == int.min); + assert(x3.hook.calls == 2); + + x3 = int.min + 1; + assert(--x3 == int.min); + assert(x3.hook.calls == 2); + assert(--x3 == int.max); + assert(x3.hook.calls == 3); +} + +// +@nogc nothrow pure @safe unittest +{ + Checked!(int, void) x; + assert(x == x); + assert(x == +x); + assert(x == -x); + ++x; + assert(x == 1); + x++; + assert(x == 2); + + x = 42; + assert(x == 42); + const short _short = 43; + x = _short; + assert(x == _short); + ushort _ushort = 44; + x = _ushort; + assert(x == _ushort); + assert(x == 44.0); + assert(x != 44.1); + assert(x < 45); + assert(x < 44.2); + assert(x > -45); + assert(x > -44.2); + + assert(cast(long) x == 44); + assert(cast(short) x == 44); + + const Checked!(uint, void) y; + assert(y <= y); + assert(y == 0); + assert(y < x); + x = -1; + assert(x > y); +} diff --git a/libphobos/src/std/experimental/logger/core.d b/libphobos/src/std/experimental/logger/core.d new file mode 100644 index 0000000..2f857c6 --- /dev/null +++ b/libphobos/src/std/experimental/logger/core.d @@ -0,0 +1,3187 @@ +/// +module std.experimental.logger.core; + +import core.sync.mutex : Mutex; +import std.datetime.date : DateTime; +import std.datetime.systime : Clock, SysTime; +import std.range.primitives; +import std.traits; + +import std.experimental.logger.filelogger; + +/** This template evaluates if the passed $(D LogLevel) is active. +The previously described version statements are used to decide if the +$(D LogLevel) is active. The version statements only influence the compile +unit they are used with, therefore this function can only disable logging this +specific compile unit. +*/ +template isLoggingActiveAt(LogLevel ll) +{ + version (StdLoggerDisableLogging) + { + enum isLoggingActiveAt = false; + } + else + { + static if (ll == LogLevel.trace) + { + version (StdLoggerDisableTrace) enum isLoggingActiveAt = false; + } + else static if (ll == LogLevel.info) + { + version (StdLoggerDisableInfo) enum isLoggingActiveAt = false; + } + else static if (ll == LogLevel.warning) + { + version (StdLoggerDisableWarning) enum isLoggingActiveAt = false; + } + else static if (ll == LogLevel.error) + { + version (StdLoggerDisableError) enum isLoggingActiveAt = false; + } + else static if (ll == LogLevel.critical) + { + version (StdLoggerDisableCritical) enum isLoggingActiveAt = false; + } + else static if (ll == LogLevel.fatal) + { + version (StdLoggerDisableFatal) enum isLoggingActiveAt = false; + } + // If `isLoggingActiveAt` didn't get defined above to false, + // we default it to true. + static if (!is(typeof(isLoggingActiveAt) == bool)) + { + enum isLoggingActiveAt = true; + } + } +} + +/// This compile-time flag is $(D true) if logging is not statically disabled. +enum isLoggingActive = isLoggingActiveAt!(LogLevel.all); + +/** This functions is used at runtime to determine if a $(D LogLevel) is +active. The same previously defined version statements are used to disable +certain levels. Again the version statements are associated with a compile +unit and can therefore not disable logging in other compile units. +pure bool isLoggingEnabled()(LogLevel ll) @safe nothrow @nogc +*/ +bool isLoggingEnabled()(LogLevel ll, LogLevel loggerLL, + LogLevel globalLL, lazy bool condition = true) @safe +{ + switch (ll) + { + case LogLevel.trace: + version (StdLoggerDisableTrace) return false; + else break; + case LogLevel.info: + version (StdLoggerDisableInfo) return false; + else break; + case LogLevel.warning: + version (StdLoggerDisableWarning) return false; + else break; + case LogLevel.critical: + version (StdLoggerDisableCritical) return false; + else break; + case LogLevel.fatal: + version (StdLoggerDisableFatal) return false; + else break; + default: break; + } + + return ll >= globalLL + && ll >= loggerLL + && ll != LogLevel.off + && globalLL != LogLevel.off + && loggerLL != LogLevel.off + && condition; +} + +/** This template returns the $(D LogLevel) named "logLevel" of type $(D +LogLevel) defined in a user defined module where the filename has the +suffix "_loggerconfig.d". This $(D LogLevel) sets the minimal $(D LogLevel) +of the module. + +A minimal $(D LogLevel) can be defined on a per module basis. +In order to define a module $(D LogLevel) a file with a modulename +"MODULENAME_loggerconfig" must be found. If no such module exists and the +module is a nested module, it is checked if there exists a +"PARENT_MODULE_loggerconfig" module with such a symbol. +If this module exists and it contains a $(D LogLevel) called logLevel this $(D +LogLevel) will be used. This parent lookup is continued until there is no +parent module. Then the moduleLogLevel is $(D LogLevel.all). +*/ +template moduleLogLevel(string moduleName) +if (!moduleName.length) +{ + // default + enum moduleLogLevel = LogLevel.all; +} + +/// +@system unittest +{ + static assert(moduleLogLevel!"" == LogLevel.all); +} + +/// ditto +template moduleLogLevel(string moduleName) +if (moduleName.length) +{ + import std.string : format; + mixin(q{ + static if (__traits(compiles, {import %1$s : logLevel;})) + { + import %1$s : logLevel; + static assert(is(typeof(logLevel) : LogLevel), + "Expect 'logLevel' to be of Type 'LogLevel'."); + // don't enforce enum here + alias moduleLogLevel = logLevel; + } + else + // use logLevel of package or default + alias moduleLogLevel = moduleLogLevel!(parentOf(moduleName)); + }.format(moduleName ~ "_loggerconfig")); +} + +/// +@system unittest +{ + static assert(moduleLogLevel!"not.amodule.path" == LogLevel.all); +} + +private string parentOf(string mod) +{ + foreach_reverse (i, c; mod) + if (c == '.') return mod[0 .. i]; + return null; +} + +/* This function formates a $(D SysTime) into an $(D OutputRange). + +The $(D SysTime) is formatted similar to +$(LREF std.datatime.DateTime.toISOExtString) except the fractional second part. +The fractional second part is in milliseconds and is always 3 digits. +*/ +void systimeToISOString(OutputRange)(OutputRange o, const ref SysTime time) +if (isOutputRange!(OutputRange,string)) +{ + import std.format : formattedWrite; + + const auto dt = cast(DateTime) time; + const auto fsec = time.fracSecs.total!"msecs"; + + formattedWrite(o, "%04d-%02d-%02dT%02d:%02d:%02d.%03d", + dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, + fsec); +} + +/** This function logs data. + +In order for the data to be processed, the $(D LogLevel) of the log call must +be greater or equal to the $(D LogLevel) of the $(D sharedLog) and the +$(D defaultLogLevel); additionally the condition passed must be $(D true). + +Params: + ll = The $(D LogLevel) used by this log call. + condition = The condition must be $(D true) for the data to be logged. + args = The data that should be logged. + +Example: +-------------------- +log(LogLevel.warning, true, "Hello World", 3.1415); +-------------------- +*/ +void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, + lazy bool condition, lazy A args) +if (args.length != 1) +{ + static if (isLoggingActive) + { + if (ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.log!(line, file, funcName, prettyFuncName, moduleName) + (ll, condition, args); + } + } +} + +/// Ditto +void log(T, string moduleName = __MODULE__)(const LogLevel ll, + lazy bool condition, lazy T arg, int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__) +{ + static if (isLoggingActive) + { + if (ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.log!(T,moduleName)(ll, condition, arg, line, file, funcName, + prettyFuncName); + } + } +} + +/** This function logs data. + +In order for the data to be processed the $(D LogLevel) of the log call must +be greater or equal to the $(D LogLevel) of the $(D sharedLog). + +Params: + ll = The $(D LogLevel) used by this log call. + args = The data that should be logged. + +Example: +-------------------- +log(LogLevel.warning, "Hello World", 3.1415); +-------------------- +*/ +void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, lazy A args) +if (args.length > 1 && !is(Unqual!(A[0]) : bool)) +{ + static if (isLoggingActive) + { + if (ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.log!(line, file, funcName, prettyFuncName, moduleName) + (ll, args); + } + } +} + +/// Ditto +void log(T, string moduleName = __MODULE__)(const LogLevel ll, lazy T arg, + int line = __LINE__, string file = __FILE__, string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__) +{ + static if (isLoggingActive) + { + if (ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.log!T(ll, arg, line, file, funcName, prettyFuncName, + moduleName); + } + } +} + +/** This function logs data. + +In order for the data to be processed the $(D LogLevel) of the +$(D sharedLog) must be greater or equal to the $(D defaultLogLevel) +add the condition passed must be $(D true). + +Params: + condition = The condition must be $(D true) for the data to be logged. + args = The data that should be logged. + +Example: +-------------------- +log(true, "Hello World", 3.1415); +-------------------- +*/ +void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args) +if (args.length != 1) +{ + static if (isLoggingActive) + { + stdThreadLocalLog.log!(line, file, funcName, prettyFuncName, moduleName) + (stdThreadLocalLog.logLevel, condition, args); + } +} + +/// Ditto +void log(T, string moduleName = __MODULE__)(lazy bool condition, lazy T arg, + int line = __LINE__, string file = __FILE__, string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__) +{ + static if (isLoggingActive) + { + stdThreadLocalLog.log!(T,moduleName)(stdThreadLocalLog.logLevel, + condition, arg, line, file, funcName, prettyFuncName); + } +} + +/** This function logs data. + +In order for the data to be processed the $(D LogLevel) of the +$(D sharedLog) must be greater or equal to the $(D defaultLogLevel). + +Params: + args = The data that should be logged. + +Example: +-------------------- +log("Hello World", 3.1415); +-------------------- +*/ +void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy A args) +if ((args.length > 1 && !is(Unqual!(A[0]) : bool) + && !is(Unqual!(A[0]) == LogLevel)) + || args.length == 0) +{ + static if (isLoggingActive) + { + stdThreadLocalLog.log!(line, file, funcName, + prettyFuncName, moduleName)(stdThreadLocalLog.logLevel, args); + } +} + +void log(T)(lazy T arg, int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__) +{ + static if (isLoggingActive) + { + stdThreadLocalLog.log!T(stdThreadLocalLog.logLevel, arg, line, file, + funcName, prettyFuncName, moduleName); + } +} + +/** This function logs data in a $(D printf)-style manner. + +In order for the data to be processed the $(D LogLevel) of the log call must +be greater or equal to the $(D LogLevel) of the $(D sharedLog) and the +$(D defaultLogLevel) additionally the condition passed must be $(D true). + +Params: + ll = The $(D LogLevel) used by this log call. + condition = The condition must be $(D true) for the data to be logged. + msg = The $(D printf)-style string. + args = The data that should be logged. + +Example: +-------------------- +logf(LogLevel.warning, true, "Hello World %f", 3.1415); +-------------------- +*/ +void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, + lazy bool condition, lazy string msg, lazy A args) +{ + static if (isLoggingActive) + { + if (ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName) + (ll, condition, msg, args); + } + } +} + +/** This function logs data in a $(D printf)-style manner. + +In order for the data to be processed the $(D LogLevel) of the log call must +be greater or equal to the $(D LogLevel) of the $(D sharedLog) and the +$(D defaultLogLevel). + +Params: + ll = The $(D LogLevel) used by this log call. + msg = The $(D printf)-style string. + args = The data that should be logged. + +Example: +-------------------- +logf(LogLevel.warning, "Hello World %f", 3.1415); +-------------------- +*/ +void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, lazy string msg, + lazy A args) +{ + static if (isLoggingActive) + { + if (ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName) + (ll, msg, args); + } + } +} + +/** This function logs data in a $(D printf)-style manner. + +In order for the data to be processed the $(D LogLevel) of the log call must +be greater or equal to the $(D defaultLogLevel) additionally the condition +passed must be $(D true). + +Params: + condition = The condition must be $(D true) for the data to be logged. + msg = The $(D printf)-style string. + args = The data that should be logged. + +Example: +-------------------- +logf(true, "Hello World %f", 3.1415); +-------------------- +*/ +void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, + lazy string msg, lazy A args) +{ + static if (isLoggingActive) + { + stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName) + (stdThreadLocalLog.logLevel, condition, msg, args); + } +} + +/** This function logs data in a $(D printf)-style manner. + +In order for the data to be processed the $(D LogLevel) of the log call must +be greater or equal to the $(D defaultLogLevel). + +Params: + msg = The $(D printf)-style string. + args = The data that should be logged. + +Example: +-------------------- +logf("Hello World %f", 3.1415); +-------------------- +*/ +void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy string msg, lazy A args) +{ + static if (isLoggingActive) + { + stdThreadLocalLog.logf!(line, file, funcName,prettyFuncName, moduleName) + (stdThreadLocalLog.logLevel, msg, args); + } +} + +/** This template provides the global log functions with the $(D LogLevel) +is encoded in the function name. + +The aliases following this template create the public names of these log +functions. +*/ +template defaultLogFunction(LogLevel ll) +{ + void defaultLogFunction(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy A args) + if ((args.length > 0 && !is(Unqual!(A[0]) : bool)) || args.length == 0) + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.memLogFunctions!(ll).logImpl!(line, file, funcName, + prettyFuncName, moduleName)(args); + } + } + + void defaultLogFunction(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args) + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.memLogFunctions!(ll).logImpl!(line, file, funcName, + prettyFuncName, moduleName)(condition, args); + } + } +} + +/** This function logs data to the $(D stdThreadLocalLog), optionally depending +on a condition. + +In order for the resulting log message to be logged the $(D LogLevel) must +be greater or equal than the $(D LogLevel) of the $(D stdThreadLocalLog) and +must be greater or equal than the global $(D LogLevel). +Additionally the $(D LogLevel) must be greater or equal than the $(D LogLevel) +of the $(D stdSharedLogger). +If a condition is given, it must evaluate to $(D true). + +Params: + condition = The condition must be $(D true) for the data to be logged. + args = The data that should be logged. + +Example: +-------------------- +trace(1337, "is number"); +info(1337, "is number"); +error(1337, "is number"); +critical(1337, "is number"); +fatal(1337, "is number"); +trace(true, 1337, "is number"); +info(false, 1337, "is number"); +error(true, 1337, "is number"); +critical(false, 1337, "is number"); +fatal(true, 1337, "is number"); +-------------------- +*/ +alias trace = defaultLogFunction!(LogLevel.trace); +/// Ditto +alias info = defaultLogFunction!(LogLevel.info); +/// Ditto +alias warning = defaultLogFunction!(LogLevel.warning); +/// Ditto +alias error = defaultLogFunction!(LogLevel.error); +/// Ditto +alias critical = defaultLogFunction!(LogLevel.critical); +/// Ditto +alias fatal = defaultLogFunction!(LogLevel.fatal); + +/** This template provides the global $(D printf)-style log functions with +the $(D LogLevel) is encoded in the function name. + +The aliases following this template create the public names of the log +functions. +*/ +template defaultLogFunctionf(LogLevel ll) +{ + void defaultLogFunctionf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy string msg, lazy A args) + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.memLogFunctions!(ll).logImplf!(line, file, funcName, + prettyFuncName, moduleName)(msg, args); + } + } + + void defaultLogFunctionf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, + lazy string msg, lazy A args) + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.memLogFunctions!(ll).logImplf!(line, file, funcName, + prettyFuncName, moduleName)(condition, msg, args); + } + } +} + +/** This function logs data to the $(D sharedLog) in a $(D printf)-style +manner. + +In order for the resulting log message to be logged the $(D LogLevel) must +be greater or equal than the $(D LogLevel) of the $(D sharedLog) and +must be greater or equal than the global $(D LogLevel). +Additionally the $(D LogLevel) must be greater or equal than the $(D LogLevel) +of the $(D stdSharedLogger). + +Params: + msg = The $(D printf)-style string. + args = The data that should be logged. + +Example: +-------------------- +tracef("is number %d", 1); +infof("is number %d", 2); +errorf("is number %d", 3); +criticalf("is number %d", 4); +fatalf("is number %d", 5); +-------------------- + +The second version of the function logs data to the $(D sharedLog) in a $(D +printf)-style manner. + +In order for the resulting log message to be logged the $(D LogLevel) must +be greater or equal than the $(D LogLevel) of the $(D sharedLog) and +must be greater or equal than the global $(D LogLevel). +Additionally the $(D LogLevel) must be greater or equal than the $(D LogLevel) +of the $(D stdSharedLogger). + +Params: + condition = The condition must be $(D true) for the data to be logged. + msg = The $(D printf)-style string. + args = The data that should be logged. + +Example: +-------------------- +tracef(false, "is number %d", 1); +infof(false, "is number %d", 2); +errorf(true, "is number %d", 3); +criticalf(true, "is number %d", 4); +fatalf(someFunct(), "is number %d", 5); +-------------------- +*/ +alias tracef = defaultLogFunctionf!(LogLevel.trace); +/// Ditto +alias infof = defaultLogFunctionf!(LogLevel.info); +/// Ditto +alias warningf = defaultLogFunctionf!(LogLevel.warning); +/// Ditto +alias errorf = defaultLogFunctionf!(LogLevel.error); +/// Ditto +alias criticalf = defaultLogFunctionf!(LogLevel.critical); +/// Ditto +alias fatalf = defaultLogFunctionf!(LogLevel.fatal); + +private struct MsgRange +{ + import std.traits : isSomeString, isSomeChar; + + private Logger log; + + this(Logger log) @safe + { + this.log = log; + } + + void put(T)(T msg) @safe + if (isSomeString!T) + { + log.logMsgPart(msg); + } + + void put(dchar elem) @safe + { + import std.utf : encode; + char[4] buffer; + size_t len = encode(buffer, elem); + log.logMsgPart(buffer[0 .. len]); + } +} + +private void formatString(A...)(MsgRange oRange, A args) +{ + import std.format : formattedWrite; + + foreach (arg; args) + { + formattedWrite(oRange, "%s", arg); + } +} + +@system unittest +{ + void dummy() @safe + { + auto tl = new TestLogger(); + auto dst = MsgRange(tl); + formatString(dst, "aaa", "bbb"); + } + + dummy(); +} + +/** +There are eight usable logging level. These level are $(I all), $(I trace), +$(I info), $(I warning), $(I error), $(I critical), $(I fatal), and $(I off). +If a log function with $(D LogLevel.fatal) is called the shutdown handler of +that logger is called. +*/ +enum LogLevel : ubyte +{ + all = 1, /** Lowest possible assignable $(D LogLevel). */ + trace = 32, /** $(D LogLevel) for tracing the execution of the program. */ + info = 64, /** This level is used to display information about the + program. */ + warning = 96, /** warnings about the program should be displayed with this + level. */ + error = 128, /** Information about errors should be logged with this + level.*/ + critical = 160, /** Messages that inform about critical errors should be + logged with this level. */ + fatal = 192, /** Log messages that describe fatal errors should use this + level. */ + off = ubyte.max /** Highest possible $(D LogLevel). */ +} + +/** This class is the base of every logger. In order to create a new kind of +logger a deriving class needs to implement the $(D writeLogMsg) method. By +default this is not thread-safe. + +It is also possible to $(D override) the three methods $(D beginLogMsg), +$(D logMsgPart) and $(D finishLogMsg) together, this option gives more +flexibility. +*/ +abstract class Logger +{ + import std.array : appender, Appender; + import std.concurrency : thisTid, Tid; + + /** LogEntry is a aggregation combining all information associated + with a log message. This aggregation will be passed to the method + writeLogMsg. + */ + protected struct LogEntry + { + /// the filename the log function was called from + string file; + /// the line number the log function was called from + int line; + /// the name of the function the log function was called from + string funcName; + /// the pretty formatted name of the function the log function was + /// called from + string prettyFuncName; + /// the name of the module the log message is coming from + string moduleName; + /// the $(D LogLevel) associated with the log message + LogLevel logLevel; + /// thread id of the log message + Tid threadId; + /// the time the message was logged + SysTime timestamp; + /// the message of the log message + string msg; + /// A refernce to the $(D Logger) used to create this $(D LogEntry) + Logger logger; + } + + /** + Every subclass of `Logger` has to call this constructor from their + constructor. It sets the `LogLevel`, and creates a fatal handler. The fatal + handler will throw an `Error` if a log call is made with level + `LogLevel.fatal`. + + Params: + lv = `LogLevel` to use for this `Logger` instance. + */ + this(LogLevel lv) @safe + { + this.logLevel_ = lv; + this.fatalHandler_ = delegate() { + throw new Error("A fatal log message was logged"); + }; + + this.mutex = new Mutex(); + } + + /** A custom logger must implement this method in order to work in a + $(D MultiLogger) and $(D ArrayLogger). + + Params: + payload = All information associated with call to log function. + + See_Also: beginLogMsg, logMsgPart, finishLogMsg + */ + abstract protected void writeLogMsg(ref LogEntry payload) @safe; + + /* The default implementation will use an $(D std.array.appender) + internally to construct the message string. This means dynamic, + GC memory allocation. A logger can avoid this allocation by + reimplementing $(D beginLogMsg), $(D logMsgPart) and $(D finishLogMsg). + $(D beginLogMsg) is always called first, followed by any number of calls + to $(D logMsgPart) and one call to $(D finishLogMsg). + + As an example for such a custom $(D Logger) compare this: + ---------------- + class CLogger : Logger + { + override void beginLogMsg(string file, int line, string funcName, + string prettyFuncName, string moduleName, LogLevel logLevel, + Tid threadId, SysTime timestamp) + { + ... logic here + } + + override void logMsgPart(const(char)[] msg) + { + ... logic here + } + + override void finishLogMsg() + { + ... logic here + } + + void writeLogMsg(ref LogEntry payload) + { + this.beginLogMsg(payload.file, payload.line, payload.funcName, + payload.prettyFuncName, payload.moduleName, payload.logLevel, + payload.threadId, payload.timestamp, payload.logger); + + this.logMsgPart(payload.msg); + this.finishLogMsg(); + } + } + ---------------- + */ + protected void beginLogMsg(string file, int line, string funcName, + string prettyFuncName, string moduleName, LogLevel logLevel, + Tid threadId, SysTime timestamp, Logger logger) + @safe + { + static if (isLoggingActive) + { + msgAppender = appender!string(); + header = LogEntry(file, line, funcName, prettyFuncName, + moduleName, logLevel, threadId, timestamp, null, logger); + } + } + + /** Logs a part of the log message. */ + protected void logMsgPart(const(char)[] msg) @safe + { + static if (isLoggingActive) + { + msgAppender.put(msg); + } + } + + /** Signals that the message has been written and no more calls to + $(D logMsgPart) follow. */ + protected void finishLogMsg() @safe + { + static if (isLoggingActive) + { + header.msg = msgAppender.data; + this.writeLogMsg(header); + } + } + + /** The $(D LogLevel) determines if the log call are processed or dropped + by the $(D Logger). In order for the log call to be processed the + $(D LogLevel) of the log call must be greater or equal to the $(D LogLevel) + of the $(D logger). + + These two methods set and get the $(D LogLevel) of the used $(D Logger). + + Example: + ----------- + auto f = new FileLogger(stdout); + f.logLevel = LogLevel.info; + assert(f.logLevel == LogLevel.info); + ----------- + */ + @property final LogLevel logLevel() const pure @safe @nogc + { + return trustedLoad(this.logLevel_); + } + + /// Ditto + @property final void logLevel(const LogLevel lv) @safe @nogc + { + synchronized (mutex) this.logLevel_ = lv; + } + + /** This $(D delegate) is called in case a log message with + $(D LogLevel.fatal) gets logged. + + By default an $(D Error) will be thrown. + */ + @property final void delegate() fatalHandler() @safe @nogc + { + synchronized (mutex) return this.fatalHandler_; + } + + /// Ditto + @property final void fatalHandler(void delegate() @safe fh) @safe @nogc + { + synchronized (mutex) this.fatalHandler_ = fh; + } + + /** This method allows forwarding log entries from one logger to another. + + $(D forwardMsg) will ensure proper synchronization and then call + $(D writeLogMsg). This is an API for implementing your own loggers and + should not be called by normal user code. A notable difference from other + logging functions is that the $(D globalLogLevel) wont be evaluated again + since it is assumed that the caller already checked that. + */ + void forwardMsg(ref LogEntry payload) @trusted + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(payload.logLevel, this.logLevel_, + globalLogLevel)) + { + this.writeLogMsg(payload); + + if (payload.logLevel == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This template provides the log functions for the $(D Logger) $(D class) + with the $(D LogLevel) encoded in the function name. + + For further information see the the two functions defined inside of this + template. + + The aliases following this template create the public names of these log + functions. + */ + template memLogFunctions(LogLevel ll) + { + /** This function logs data to the used $(D Logger). + + In order for the resulting log message to be logged the $(D LogLevel) + must be greater or equal than the $(D LogLevel) of the used $(D Logger) + and must be greater or equal than the global $(D LogLevel). + + Params: + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stdout); + s.trace(1337, "is number"); + s.info(1337, "is number"); + s.error(1337, "is number"); + s.critical(1337, "is number"); + s.fatal(1337, "is number"); + -------------------- + */ + void logImpl(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy A args) + if (args.length == 0 || (args.length > 0 && !is(A[0] : bool))) + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + static if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) depending on a + condition. + + In order for the resulting log message to be logged the $(D LogLevel) must + be greater or equal than the $(D LogLevel) of the used $(D Logger) and + must be greater or equal than the global $(D LogLevel) additionally the + condition passed must be $(D true). + + Params: + condition = The condition must be $(D true) for the data to be logged. + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stdout); + s.trace(true, 1337, "is number"); + s.info(false, 1337, "is number"); + s.error(true, 1337, "is number"); + s.critical(false, 1337, "is number"); + s.fatal(true, 1337, "is number"); + -------------------- + */ + void logImpl(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, + lazy A args) + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, + condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + static if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) in a + $(D printf)-style manner. + + In order for the resulting log message to be logged the $(D LogLevel) + must be greater or equal than the $(D LogLevel) of the used $(D Logger) + and must be greater or equal than the global $(D LogLevel) additionally + the passed condition must be $(D true). + + Params: + condition = The condition must be $(D true) for the data to be logged. + msg = The $(D printf)-style string. + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stderr); + s.tracef(true, "is number %d", 1); + s.infof(true, "is number %d", 2); + s.errorf(false, "is number %d", 3); + s.criticalf(someFunc(), "is number %d", 4); + s.fatalf(true, "is number %d", 5); + -------------------- + */ + void logImplf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, + lazy string msg, lazy A args) + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + synchronized (mutex) + { + import std.format : formattedWrite; + + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, + condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + static if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) in a + $(D printf)-style manner. + + In order for the resulting log message to be logged the $(D LogLevel) must + be greater or equal than the $(D LogLevel) of the used $(D Logger) and + must be greater or equal than the global $(D LogLevel). + + Params: + msg = The $(D printf)-style string. + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stderr); + s.tracef("is number %d", 1); + s.infof("is number %d", 2); + s.errorf("is number %d", 3); + s.criticalf("is number %d", 4); + s.fatalf("is number %d", 5); + -------------------- + */ + void logImplf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy string msg, lazy A args) + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + synchronized (mutex) + { + import std.format : formattedWrite; + + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + static if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + } + + /// Ditto + alias trace = memLogFunctions!(LogLevel.trace).logImpl; + /// Ditto + alias tracef = memLogFunctions!(LogLevel.trace).logImplf; + /// Ditto + alias info = memLogFunctions!(LogLevel.info).logImpl; + /// Ditto + alias infof = memLogFunctions!(LogLevel.info).logImplf; + /// Ditto + alias warning = memLogFunctions!(LogLevel.warning).logImpl; + /// Ditto + alias warningf = memLogFunctions!(LogLevel.warning).logImplf; + /// Ditto + alias error = memLogFunctions!(LogLevel.error).logImpl; + /// Ditto + alias errorf = memLogFunctions!(LogLevel.error).logImplf; + /// Ditto + alias critical = memLogFunctions!(LogLevel.critical).logImpl; + /// Ditto + alias criticalf = memLogFunctions!(LogLevel.critical).logImplf; + /// Ditto + alias fatal = memLogFunctions!(LogLevel.fatal).logImpl; + /// Ditto + alias fatalf = memLogFunctions!(LogLevel.fatal).logImplf; + + /** This method logs data with the $(D LogLevel) of the used $(D Logger). + + This method takes a $(D bool) as first argument. In order for the + data to be processed the $(D bool) must be $(D true) and the $(D LogLevel) + of the Logger must be greater or equal to the global $(D LogLevel). + + Params: + args = The data that should be logged. + condition = The condition must be $(D true) for the data to be logged. + args = The data that is to be logged. + + Returns: The logger used by the logging function as reference. + + Example: + -------------------- + auto l = new StdioLogger(); + l.log(1337); + -------------------- + */ + void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, + lazy bool condition, lazy A args) + if (args.length != 1) + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /// Ditto + void log(T, string moduleName = __MODULE__)(const LogLevel ll, + lazy bool condition, lazy T args, int line = __LINE__, + string file = __FILE__, string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__) + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, + condition) && ll >= moduleLogLevel!moduleName) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) with a specific + $(D LogLevel). + + In order for the resulting log message to be logged the $(D LogLevel) + must be greater or equal than the $(D LogLevel) of the used $(D Logger) + and must be greater or equal than the global $(D LogLevel). + + Params: + ll = The specific $(D LogLevel) used for logging the log message. + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stdout); + s.log(LogLevel.trace, 1337, "is number"); + s.log(LogLevel.info, 1337, "is number"); + s.log(LogLevel.warning, 1337, "is number"); + s.log(LogLevel.error, 1337, "is number"); + s.log(LogLevel.fatal, 1337, "is number"); + -------------------- + */ + void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, lazy A args) + if ((args.length > 1 && !is(Unqual!(A[0]) : bool)) || args.length == 0) + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /// Ditto + void log(T)(const LogLevel ll, lazy T args, int line = __LINE__, + string file = __FILE__, string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__) + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) depending on a + explicitly passed condition with the $(D LogLevel) of the used + $(D Logger). + + In order for the resulting log message to be logged the $(D LogLevel) + of the used $(D Logger) must be greater or equal than the global + $(D LogLevel) and the condition must be $(D true). + + Params: + condition = The condition must be $(D true) for the data to be logged. + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stdout); + s.log(true, 1337, "is number"); + s.log(true, 1337, "is number"); + s.log(true, 1337, "is number"); + s.log(false, 1337, "is number"); + s.log(false, 1337, "is number"); + -------------------- + */ + void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args) + if (args.length != 1) + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(this.logLevel_, this.logLevel_, + globalLogLevel, condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /// Ditto + void log(T)(lazy bool condition, lazy T args, int line = __LINE__, + string file = __FILE__, string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__) + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel, + condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) with the $(D LogLevel) + of the used $(D Logger). + + In order for the resulting log message to be logged the $(D LogLevel) + of the used $(D Logger) must be greater or equal than the global + $(D LogLevel). + + Params: + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stdout); + s.log(1337, "is number"); + s.log(info, 1337, "is number"); + s.log(1337, "is number"); + s.log(1337, "is number"); + s.log(1337, "is number"); + -------------------- + */ + void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy A args) + if ((args.length > 1 + && !is(Unqual!(A[0]) : bool) + && !is(Unqual!(A[0]) == LogLevel)) + || args.length == 0) + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(this.logLevel_, this.logLevel_, + globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /// Ditto + void log(T)(lazy T arg, int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__) + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + auto writer = MsgRange(this); + formatString(writer, arg); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) with a specific + $(D LogLevel) and depending on a condition in a $(D printf)-style manner. + + In order for the resulting log message to be logged the $(D LogLevel) + must be greater or equal than the $(D LogLevel) of the used $(D Logger) + and must be greater or equal than the global $(D LogLevel) and the + condition must be $(D true). + + Params: + ll = The specific $(D LogLevel) used for logging the log message. + condition = The condition must be $(D true) for the data to be logged. + msg = The format string used for this log call. + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stdout); + s.logf(LogLevel.trace, true ,"%d %s", 1337, "is number"); + s.logf(LogLevel.info, true ,"%d %s", 1337, "is number"); + s.logf(LogLevel.warning, true ,"%d %s", 1337, "is number"); + s.logf(LogLevel.error, false ,"%d %s", 1337, "is number"); + s.logf(LogLevel.fatal, true ,"%d %s", 1337, "is number"); + -------------------- + */ + void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, + lazy bool condition, lazy string msg, lazy A args) + { + static if (isLoggingActive) synchronized (mutex) + { + import std.format : formattedWrite; + + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) with a specific + $(D LogLevel) in a $(D printf)-style manner. + + In order for the resulting log message to be logged the $(D LogLevel) + must be greater or equal than the $(D LogLevel) of the used $(D Logger) + and must be greater or equal than the global $(D LogLevel). + + Params: + ll = The specific $(D LogLevel) used for logging the log message. + msg = The format string used for this log call. + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stdout); + s.logf(LogLevel.trace, "%d %s", 1337, "is number"); + s.logf(LogLevel.info, "%d %s", 1337, "is number"); + s.logf(LogLevel.warning, "%d %s", 1337, "is number"); + s.logf(LogLevel.error, "%d %s", 1337, "is number"); + s.logf(LogLevel.fatal, "%d %s", 1337, "is number"); + -------------------- + */ + void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, + lazy string msg, lazy A args) + { + static if (isLoggingActive) synchronized (mutex) + { + import std.format : formattedWrite; + + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) depending on a + condition with the $(D LogLevel) of the used $(D Logger) in a + $(D printf)-style manner. + + In order for the resulting log message to be logged the $(D LogLevel) + of the used $(D Logger) must be greater or equal than the global + $(D LogLevel) and the condition must be $(D true). + + Params: + condition = The condition must be $(D true) for the data to be logged. + msg = The format string used for this log call. + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stdout); + s.logf(true ,"%d %s", 1337, "is number"); + s.logf(true ,"%d %s", 1337, "is number"); + s.logf(true ,"%d %s", 1337, "is number"); + s.logf(false ,"%d %s", 1337, "is number"); + s.logf(true ,"%d %s", 1337, "is number"); + -------------------- + */ + void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, + lazy string msg, lazy A args) + { + static if (isLoggingActive) synchronized (mutex) + { + import std.format : formattedWrite; + + if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel, + condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This method logs data to the used $(D Logger) with the $(D LogLevel) + of the this $(D Logger) in a $(D printf)-style manner. + + In order for the data to be processed the $(D LogLevel) of the $(D Logger) + must be greater or equal to the global $(D LogLevel). + + Params: + msg = The format string used for this log call. + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stdout); + s.logf("%d %s", 1337, "is number"); + s.logf("%d %s", 1337, "is number"); + s.logf("%d %s", 1337, "is number"); + s.logf("%d %s", 1337, "is number"); + s.logf("%d %s", 1337, "is number"); + -------------------- + */ + void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy string msg, lazy A args) + { + static if (isLoggingActive) synchronized (mutex) + { + import std.format : formattedWrite; + + if (isLoggingEnabled(this.logLevel_, this.logLevel_, + globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + private void delegate() @safe fatalHandler_; + private shared LogLevel logLevel_ = LogLevel.info; + private Mutex mutex; + + protected Appender!string msgAppender; + protected LogEntry header; +} + +// Thread Global + +private __gshared Logger stdSharedDefaultLogger; +private shared Logger stdSharedLogger; +private shared LogLevel stdLoggerGlobalLogLevel = LogLevel.all; + +/* This method returns the global default Logger. + * Marked @trusted because of excessive reliance on __gshared data + */ +private @property Logger defaultSharedLoggerImpl() @trusted +{ + import std.conv : emplace; + import std.stdio : stderr; + + static __gshared align(FileLogger.alignof) void[__traits(classInstanceSize, FileLogger)] _buffer; + + import std.concurrency : initOnce; + initOnce!stdSharedDefaultLogger({ + auto buffer = cast(ubyte[]) _buffer; + return emplace!FileLogger(buffer, stderr, LogLevel.all); + }()); + + return stdSharedDefaultLogger; +} + +/** This property sets and gets the default $(D Logger). + +Example: +------------- +sharedLog = new FileLogger(yourFile); +------------- +The example sets a new $(D FileLogger) as new $(D sharedLog). + +If at some point you want to use the original default logger again, you can +use $(D sharedLog = null;). This will put back the original. + +Note: +While getting and setting $(D sharedLog) is thread-safe, it has to be considered +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 $(D sharedLog). + +$(D sharedLog) is only thread-safe if the the used $(D Logger) is thread-safe. +The default $(D Logger) is thread-safe. +------------- +if (sharedLog !is myLogger) + sharedLog = new myLogger; +------------- +*/ +@property Logger sharedLog() @safe +{ + static auto trustedLoad(ref shared Logger logger) @trusted + { + import core.atomic : atomicLoad, MemoryOrder; + return cast() atomicLoad!(MemoryOrder.acq)(logger); + //FIXME: Casting shared away here. Not good. See issue 16232. + } + + // If we have set up our own logger use that + if (auto logger = trustedLoad(stdSharedLogger)) + { + return logger; + } + else + { + // Otherwise resort to the default logger + return defaultSharedLoggerImpl; + } +} + +/// Ditto +@property void sharedLog(Logger logger) @trusted +{ + import core.atomic : atomicStore, MemoryOrder; + atomicStore!(MemoryOrder.rel)(stdSharedLogger, cast(shared) logger); +} + +/** This methods get and set the global $(D LogLevel). + +Every log message with a $(D LogLevel) lower as the global $(D LogLevel) +will be discarded before it reaches $(D writeLogMessage) method of any +$(D Logger). +*/ +/* Implementation note: +For any public logging call, the global log level shall only be queried once on +entry. Otherwise when another threads changes the level, we would work with +different levels at different spots in the code. +*/ +@property LogLevel globalLogLevel() @safe @nogc +{ + return trustedLoad(stdLoggerGlobalLogLevel); +} + +/// Ditto +@property void globalLogLevel(LogLevel ll) @safe +{ + trustedStore(stdLoggerGlobalLogLevel, ll); +} + +// Thread Local + +/** The $(D StdForwardLogger) will always forward anything to the sharedLog. + +The $(D StdForwardLogger) will not throw if data is logged with $(D +LogLevel.fatal). +*/ +class StdForwardLogger : Logger +{ + /** The default constructor for the $(D StdForwardLogger). + + Params: + lv = The $(D LogLevel) for the $(D MultiLogger). By default the $(D + LogLevel) is $(D all). + */ + this(const LogLevel lv = LogLevel.all) @safe + { + super(lv); + this.fatalHandler = delegate() {}; + } + + override protected void writeLogMsg(ref LogEntry payload) + { + sharedLog.forwardMsg(payload); + } +} + +/// +@safe unittest +{ + auto nl1 = new StdForwardLogger(LogLevel.all); +} + +/** This $(D LogLevel) is unqiue to every thread. + +The thread local $(D Logger) will use this $(D LogLevel) to filter log calls +every same way as presented earlier. +*/ +//public LogLevel threadLogLevel = LogLevel.all; +private Logger stdLoggerThreadLogger; +private Logger stdLoggerDefaultThreadLogger; + +/* This method returns the thread local default Logger. +*/ +private @property Logger stdThreadLocalLogImpl() @trusted +{ + import std.conv : emplace; + + static void*[(__traits(classInstanceSize, StdForwardLogger) - 1) / (void*).sizeof + 1] _buffer; + + auto buffer = cast(ubyte[]) _buffer; + + if (stdLoggerDefaultThreadLogger is null) + { + stdLoggerDefaultThreadLogger = emplace!StdForwardLogger(buffer, LogLevel.all); + } + return stdLoggerDefaultThreadLogger; +} + +/** This function returns a thread unique $(D Logger), that by default +propergates all data logged to it to the $(D sharedLog). + +These properties can be used to set and get this $(D Logger). Every +modification to this $(D Logger) will only be visible in the thread the +modification has been done from. + +This $(D Logger) is called by the free standing log functions. This allows to +create thread local redirections and still use the free standing log +functions. +*/ +@property Logger stdThreadLocalLog() @safe +{ + // If we have set up our own logger use that + if (auto logger = stdLoggerThreadLogger) + return logger; + else + // Otherwise resort to the default logger + return stdThreadLocalLogImpl; +} + +/// Ditto +@property void stdThreadLocalLog(Logger logger) @safe +{ + stdLoggerThreadLogger = logger; +} + +/// Ditto +@system unittest +{ + import std.experimental.logger.filelogger : FileLogger; + import std.file : deleteme, remove; + Logger l = stdThreadLocalLog; + stdThreadLocalLog = new FileLogger(deleteme ~ "-someFile.log"); + scope(exit) remove(deleteme ~ "-someFile.log"); + + auto tempLog = stdThreadLocalLog; + stdThreadLocalLog = l; + destroy(tempLog); +} + +@safe unittest +{ + LogLevel ll = globalLogLevel; + globalLogLevel = LogLevel.fatal; + assert(globalLogLevel == LogLevel.fatal); + globalLogLevel = ll; +} + +package class TestLogger : Logger +{ + int line = -1; + string file = null; + string func = null; + string prettyFunc = null; + string msg = null; + LogLevel lvl; + + this(const LogLevel lv = LogLevel.all) @safe + { + super(lv); + } + + override protected void writeLogMsg(ref LogEntry payload) @safe + { + this.line = payload.line; + this.file = payload.file; + this.func = payload.funcName; + this.prettyFunc = payload.prettyFuncName; + this.lvl = payload.logLevel; + this.msg = payload.msg; + } +} + +version (unittest) private void testFuncNames(Logger logger) @safe +{ + string s = "I'm here"; + logger.log(s); +} + +@safe unittest +{ + auto tl1 = new TestLogger(); + testFuncNames(tl1); + assert(tl1.func == "std.experimental.logger.core.testFuncNames", tl1.func); + assert(tl1.prettyFunc == + "void std.experimental.logger.core.testFuncNames(Logger logger) @safe", + tl1.prettyFunc); + assert(tl1.msg == "I'm here", tl1.msg); +} + +@safe unittest +{ + auto tl1 = new TestLogger(LogLevel.all); + tl1.log(); + assert(tl1.line == __LINE__ - 1); + tl1.log(true); + assert(tl1.line == __LINE__ - 1); + tl1.log(false); + assert(tl1.line == __LINE__ - 3); + tl1.log(LogLevel.info); + assert(tl1.line == __LINE__ - 1); + tl1.log(LogLevel.off); + assert(tl1.line == __LINE__ - 3); + tl1.log(LogLevel.info, true); + assert(tl1.line == __LINE__ - 1); + tl1.log(LogLevel.info, false); + assert(tl1.line == __LINE__ - 3); + + auto oldunspecificLogger = sharedLog; + scope(exit) { + sharedLog = oldunspecificLogger; + } + + sharedLog = tl1; + + log(); + assert(tl1.line == __LINE__ - 1); + + log(LogLevel.info); + assert(tl1.line == __LINE__ - 1); + + log(true); + assert(tl1.line == __LINE__ - 1); + + log(LogLevel.warning, true); + assert(tl1.line == __LINE__ - 1); + + trace(); + assert(tl1.line == __LINE__ - 1); +} + +@safe unittest +{ + import std.experimental.logger.multilogger : MultiLogger; + + auto tl1 = new TestLogger; + auto tl2 = new TestLogger; + + auto ml = new MultiLogger(); + ml.insertLogger("one", tl1); + ml.insertLogger("two", tl2); + + string msg = "Hello Logger World"; + ml.log(msg); + int lineNumber = __LINE__ - 1; + assert(tl1.msg == msg); + assert(tl1.line == lineNumber); + assert(tl2.msg == msg); + assert(tl2.line == lineNumber); + + ml.removeLogger("one"); + ml.removeLogger("two"); + auto n = ml.removeLogger("one"); + assert(n is null); +} + +@safe unittest +{ + bool errorThrown = false; + auto tl = new TestLogger; + auto dele = delegate() { + errorThrown = true; + }; + tl.fatalHandler = dele; + tl.fatal(); + assert(errorThrown); +} + +@safe unittest +{ + import std.conv : to; + import std.exception : assertThrown, assertNotThrown; + import std.format : format; + + auto l = new TestLogger(LogLevel.all); + string msg = "Hello Logger World"; + l.log(msg); + int lineNumber = __LINE__ - 1; + assert(l.msg == msg); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + l.log(true, msg); + lineNumber = __LINE__ - 1; + assert(l.msg == msg, l.msg); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + l.log(false, msg); + assert(l.msg == msg); + assert(l.line == lineNumber, to!string(l.line)); + assert(l.logLevel == LogLevel.all); + + msg = "%s Another message"; + l.logf(msg, "Yet"); + lineNumber = __LINE__ - 1; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + l.logf(true, msg, "Yet"); + lineNumber = __LINE__ - 1; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + l.logf(false, msg, "Yet"); + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + () @trusted { + assertThrown!Throwable(l.logf(LogLevel.fatal, msg, "Yet")); + } (); + lineNumber = __LINE__ - 2; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + () @trusted { + assertThrown!Throwable(l.logf(LogLevel.fatal, true, msg, "Yet")); + } (); + lineNumber = __LINE__ - 2; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + assertNotThrown(l.logf(LogLevel.fatal, false, msg, "Yet")); + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + auto oldunspecificLogger = sharedLog; + + assert(oldunspecificLogger.logLevel == LogLevel.all, + to!string(oldunspecificLogger.logLevel)); + + assert(l.logLevel == LogLevel.all); + sharedLog = l; + assert(globalLogLevel == LogLevel.all, + to!string(globalLogLevel)); + + scope(exit) + { + sharedLog = oldunspecificLogger; + } + + assert(sharedLog.logLevel == LogLevel.all); + assert(stdThreadLocalLog.logLevel == LogLevel.all); + assert(globalLogLevel == LogLevel.all); + + msg = "Another message"; + log(msg); + lineNumber = __LINE__ - 1; + assert(l.logLevel == LogLevel.all); + assert(l.line == lineNumber, to!string(l.line)); + assert(l.msg == msg, l.msg); + + log(true, msg); + lineNumber = __LINE__ - 1; + assert(l.msg == msg); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + log(false, msg); + assert(l.msg == msg); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + msg = "%s Another message"; + logf(msg, "Yet"); + lineNumber = __LINE__ - 1; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + logf(true, msg, "Yet"); + lineNumber = __LINE__ - 1; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + logf(false, msg, "Yet"); + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + msg = "%s Another message"; + () @trusted { + assertThrown!Throwable(logf(LogLevel.fatal, msg, "Yet")); + } (); + lineNumber = __LINE__ - 2; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + () @trusted { + assertThrown!Throwable(logf(LogLevel.fatal, true, msg, "Yet")); + } (); + lineNumber = __LINE__ - 2; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + assertNotThrown(logf(LogLevel.fatal, false, msg, "Yet")); + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); +} + +@system unittest // default logger +{ + import std.file : deleteme, exists, remove; + import std.stdio : File; + import std.string : indexOf; + + string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile"; + FileLogger l = new FileLogger(filename); + auto oldunspecificLogger = sharedLog; + sharedLog = l; + + scope(exit) + { + remove(filename); + assert(!exists(filename)); + sharedLog = oldunspecificLogger; + globalLogLevel = LogLevel.all; + } + + string notWritten = "this should not be written to file"; + string written = "this should be written to file"; + + globalLogLevel = LogLevel.critical; + assert(globalLogLevel == LogLevel.critical); + + log(LogLevel.warning, notWritten); + log(LogLevel.critical, written); + + l.file.flush(); + l.file.close(); + + auto file = File(filename, "r"); + assert(!file.eof); + + string readLine = file.readln(); + assert(readLine.indexOf(written) != -1, readLine); + assert(readLine.indexOf(notWritten) == -1, readLine); + file.close(); +} + +@system unittest +{ + import std.file : deleteme, remove; + import std.stdio : File; + import std.string : indexOf; + + string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile"; + auto oldunspecificLogger = sharedLog; + + scope(exit) + { + remove(filename); + sharedLog = oldunspecificLogger; + globalLogLevel = LogLevel.all; + } + + string notWritten = "this should not be written to file"; + string written = "this should be written to file"; + + auto l = new FileLogger(filename); + sharedLog = l; + sharedLog.logLevel = LogLevel.critical; + + log(LogLevel.error, false, notWritten); + log(LogLevel.critical, true, written); + destroy(l); + + auto file = File(filename, "r"); + auto readLine = file.readln(); + assert(!readLine.empty, readLine); + assert(readLine.indexOf(written) != -1); + assert(readLine.indexOf(notWritten) == -1); + file.close(); +} + +@safe unittest +{ + import std.conv : to; + + auto tl = new TestLogger(LogLevel.all); + int l = __LINE__; + tl.info("a"); + assert(tl.line == l+1); + assert(tl.msg == "a"); + assert(tl.logLevel == LogLevel.all); + assert(globalLogLevel == LogLevel.all); + l = __LINE__; + tl.trace("b"); + assert(tl.msg == "b", tl.msg); + assert(tl.line == l+1, to!string(tl.line)); +} + +// testing possible log conditions +@safe unittest +{ + import std.conv : to; + import std.format : format; + import std.string : indexOf; + + auto oldunspecificLogger = sharedLog; + + auto mem = new TestLogger; + mem.fatalHandler = delegate() {}; + sharedLog = mem; + + scope(exit) + { + sharedLog = oldunspecificLogger; + globalLogLevel = LogLevel.all; + } + + int value = 0; + foreach (gll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + + globalLogLevel = gll; + + foreach (ll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + + mem.logLevel = ll; + + foreach (cond; [true, false]) + { + foreach (condValue; [true, false]) + { + foreach (memOrG; [true, false]) + { + foreach (prntf; [true, false]) + { + foreach (ll2; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, + LogLevel.error, LogLevel.critical, + LogLevel.fatal, LogLevel.off]) + { + foreach (singleMulti; 0 .. 2) + { + int lineCall; + mem.msg = "-1"; + if (memOrG) + { + if (prntf) + { + if (cond) + { + if (singleMulti == 0) + { + mem.logf(ll2, condValue, "%s", + value); + lineCall = __LINE__; + } + else + { + mem.logf(ll2, condValue, + "%d %d", value, value); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + mem.logf(ll2, "%s", value); + lineCall = __LINE__; + } + else + { + mem.logf(ll2, "%d %d", + value, value); + lineCall = __LINE__; + } + } + } + else + { + if (cond) + { + if (singleMulti == 0) + { + mem.log(ll2, condValue, + to!string(value)); + lineCall = __LINE__; + } + else + { + mem.log(ll2, condValue, + to!string(value), value); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + mem.log(ll2, to!string(value)); + lineCall = __LINE__; + } + else + { + mem.log(ll2, + to!string(value), + value); + lineCall = __LINE__; + } + } + } + } + else + { + if (prntf) + { + if (cond) + { + if (singleMulti == 0) + { + logf(ll2, condValue, "%s", + value); + lineCall = __LINE__; + } + else + { + logf(ll2, condValue, + "%s %d", value, value); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + logf(ll2, "%s", value); + lineCall = __LINE__; + } + else + { + logf(ll2, "%s %s", value, + value); + lineCall = __LINE__; + } + } + } + else + { + if (cond) + { + if (singleMulti == 0) + { + log(ll2, condValue, + to!string(value)); + lineCall = __LINE__; + } + else + { + log(ll2, condValue, value, + to!string(value)); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + log(ll2, to!string(value)); + lineCall = __LINE__; + } + else + { + log(ll2, value, + to!string(value)); + lineCall = __LINE__; + } + } + } + } + + string valueStr = to!string(value); + ++value; + + bool ll2Off = (ll2 != LogLevel.off); + bool gllOff = (gll != LogLevel.off); + bool llOff = (ll != LogLevel.off); + bool condFalse = (cond ? condValue : true); + bool ll2VSgll = (ll2 >= gll); + bool ll2VSll = (ll2 >= ll); + + bool shouldLog = ll2Off && gllOff && llOff + && condFalse && ll2VSgll && ll2VSll; + + /* + writefln( + "go(%b) ll2o(%b) c(%b) lg(%b) ll(%b) s(%b)" + , gll != LogLevel.off, ll2 != LogLevel.off, + cond ? condValue : true, + ll2 >= gll, ll2 >= ll, shouldLog); + */ + + + if (shouldLog) + { + assert(mem.msg.indexOf(valueStr) != -1, + format( + "lineCall(%d) ll2Off(%u) gll(%u) ll(%u) ll2(%u) " ~ + "cond(%b) condValue(%b)" ~ + " memOrG(%b) shouldLog(%b) %s == %s" ~ + " %b %b %b %b %b", + lineCall, ll2Off, gll, ll, ll2, cond, + condValue, memOrG, shouldLog, mem.msg, + valueStr, gllOff, llOff, condFalse, + ll2VSgll, ll2VSll + )); + } + else + { + assert(mem.msg.indexOf(valueStr), + format( + "lineCall(%d) ll2Off(%u) gll(%u) ll(%u) ll2(%u) " ~ + "cond(%b) condValue(%b)" ~ + " memOrG(%b) shouldLog(%b) %s == %s" ~ + " %b %b %b %b %b", + lineCall, ll2Off, gll, ll, ll2, cond, + condValue, memOrG, shouldLog, mem.msg, + valueStr, gllOff, llOff, condFalse, + ll2VSgll, ll2VSll + )); + } + } + } + } + } + } + } + } + } +} + +// more testing +@safe unittest +{ + import std.conv : to; + import std.format : format; + import std.string : indexOf; + + auto oldunspecificLogger = sharedLog; + + auto mem = new TestLogger; + mem.fatalHandler = delegate() {}; + sharedLog = mem; + + scope(exit) + { + sharedLog = oldunspecificLogger; + globalLogLevel = LogLevel.all; + } + + int value = 0; + foreach (gll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + + globalLogLevel = gll; + + foreach (ll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + mem.logLevel = ll; + + foreach (tll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + stdThreadLocalLog.logLevel = tll; + + foreach (cond; [true, false]) + { + foreach (condValue; [true, false]) + { + foreach (memOrG; [true, false]) + { + foreach (prntf; [true, false]) + { + foreach (singleMulti; 0 .. 2) + { + int lineCall; + mem.msg = "-1"; + if (memOrG) + { + if (prntf) + { + if (cond) + { + if (singleMulti == 0) + { + mem.logf(condValue, "%s", + value); + lineCall = __LINE__; + } + else + { + mem.logf(condValue, + "%d %d", value, value); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + mem.logf("%s", value); + lineCall = __LINE__; + } + else + { + mem.logf("%d %d", + value, value); + lineCall = __LINE__; + } + } + } + else + { + if (cond) + { + if (singleMulti == 0) + { + mem.log(condValue, + to!string(value)); + lineCall = __LINE__; + } + else + { + mem.log(condValue, + to!string(value), value); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + mem.log(to!string(value)); + lineCall = __LINE__; + } + else + { + mem.log(to!string(value), + value); + lineCall = __LINE__; + } + } + } + } + else + { + if (prntf) + { + if (cond) + { + if (singleMulti == 0) + { + logf(condValue, "%s", value); + lineCall = __LINE__; + } + else + { + logf(condValue, "%s %d", value, + value); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + logf("%s", value); + lineCall = __LINE__; + } + else + { + logf("%s %s", value, value); + lineCall = __LINE__; + } + } + } + else + { + if (cond) + { + if (singleMulti == 0) + { + log(condValue, + to!string(value)); + lineCall = __LINE__; + } + else + { + log(condValue, value, + to!string(value)); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + log(to!string(value)); + lineCall = __LINE__; + } + else + { + log(value, to!string(value)); + lineCall = __LINE__; + } + } + } + } + + string valueStr = to!string(value); + ++value; + + bool gllOff = (gll != LogLevel.off); + bool llOff = (ll != LogLevel.off); + bool tllOff = (tll != LogLevel.off); + bool llVSgll = (ll >= gll); + bool tllVSll = + (stdThreadLocalLog.logLevel >= ll); + bool condFalse = (cond ? condValue : true); + + bool shouldLog = gllOff && llOff + && (memOrG ? true : tllOff) + && (memOrG ? + (ll >= gll) : + (tll >= gll && tll >= ll)) + && condFalse; + + if (shouldLog) + { + assert(mem.msg.indexOf(valueStr) != -1, + format("\ngll(%s) ll(%s) tll(%s) " ~ + "cond(%s) condValue(%s) " ~ + "memOrG(%s) prntf(%s) " ~ + "singleMulti(%s)", + gll, ll, tll, cond, condValue, + memOrG, prntf, singleMulti) + ~ format(" gllOff(%s) llOff(%s) " ~ + "llVSgll(%s) tllVSll(%s) " ~ + "tllOff(%s) condFalse(%s) " + ~ "shoudlLog(%s)", + gll != LogLevel.off, + ll != LogLevel.off, llVSgll, + tllVSll, tllOff, condFalse, + shouldLog) + ~ format("msg(%s) line(%s) " ~ + "lineCall(%s) valueStr(%s)", + mem.msg, mem.line, lineCall, + valueStr) + ); + } + else + { + assert(mem.msg.indexOf(valueStr) == -1, + format("\ngll(%s) ll(%s) tll(%s) " ~ + "cond(%s) condValue(%s) " ~ + "memOrG(%s) prntf(%s) " ~ + "singleMulti(%s)", + gll, ll, tll, cond, condValue, + memOrG, prntf, singleMulti) + ~ format(" gllOff(%s) llOff(%s) " ~ + "llVSgll(%s) tllVSll(%s) " ~ + "tllOff(%s) condFalse(%s) " + ~ "shoudlLog(%s)", + gll != LogLevel.off, + ll != LogLevel.off, llVSgll, + tllVSll, tllOff, condFalse, + shouldLog) + ~ format("msg(%s) line(%s) " ~ + "lineCall(%s) valueStr(%s)", + mem.msg, mem.line, lineCall, + valueStr) + ); + } + } + } + } + } + } + } + } + } +} + +// testing more possible log conditions +@safe unittest +{ + bool fatalLog; + auto mem = new TestLogger; + mem.fatalHandler = delegate() { fatalLog = true; }; + auto oldunspecificLogger = sharedLog; + + stdThreadLocalLog.logLevel = LogLevel.all; + + sharedLog = mem; + scope(exit) + { + sharedLog = oldunspecificLogger; + globalLogLevel = LogLevel.all; + } + + foreach (gll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + + globalLogLevel = gll; + + foreach (ll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + mem.logLevel = ll; + + foreach (tll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + stdThreadLocalLog.logLevel = tll; + + foreach (cond; [true, false]) + { + assert(globalLogLevel == gll); + assert(mem.logLevel == ll); + + bool gllVSll = LogLevel.trace >= globalLogLevel; + bool llVSgll = ll >= globalLogLevel; + bool lVSll = LogLevel.trace >= ll; + bool gllOff = globalLogLevel != LogLevel.off; + bool llOff = mem.logLevel != LogLevel.off; + bool tllOff = stdThreadLocalLog.logLevel != LogLevel.off; + bool tllVSll = tll >= ll; + bool tllVSgll = tll >= gll; + bool lVSgll = LogLevel.trace >= tll; + + bool test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond; + bool testG = gllOff && llOff && tllOff && lVSgll && tllVSll && tllVSgll && cond; + + mem.line = -1; + /* + writefln("gll(%3u) ll(%3u) cond(%b) test(%b)", + gll, ll, cond, test); + writefln("%b %b %b %b %b %b test2(%b)", llVSgll, gllVSll, lVSll, + gllOff, llOff, cond, test2); + */ + + mem.trace(__LINE__); int line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + trace(__LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.trace(cond, __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + trace(cond, __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.tracef("%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + tracef("%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.tracef(cond, "%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + tracef(cond, "%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + llVSgll = ll >= globalLogLevel; + lVSll = LogLevel.info >= ll; + lVSgll = LogLevel.info >= tll; + test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond; + testG = gllOff && llOff && tllOff && tllVSll && tllVSgll && + lVSgll && cond; + + mem.info(__LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + info(__LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.info(cond, __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + info(cond, __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.infof("%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + infof("%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.infof(cond, "%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + infof(cond, "%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + llVSgll = ll >= globalLogLevel; + lVSll = LogLevel.warning >= ll; + lVSgll = LogLevel.warning >= tll; + test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond; + testG = gllOff && llOff && tllOff && tllVSll && tllVSgll && + lVSgll && cond; + + mem.warning(__LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + warning(__LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.warning(cond, __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + warning(cond, __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.warningf("%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + warningf("%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.warningf(cond, "%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + warningf(cond, "%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + llVSgll = ll >= globalLogLevel; + lVSll = LogLevel.critical >= ll; + lVSgll = LogLevel.critical >= tll; + test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond; + testG = gllOff && llOff && tllOff && tllVSll && tllVSgll && + lVSgll && cond; + + mem.critical(__LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + critical(__LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.critical(cond, __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + critical(cond, __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.criticalf("%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + criticalf("%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.criticalf(cond, "%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + criticalf(cond, "%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + llVSgll = ll >= globalLogLevel; + lVSll = LogLevel.fatal >= ll; + lVSgll = LogLevel.fatal >= tll; + test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond; + testG = gllOff && llOff && tllOff && tllVSll && tllVSgll && + lVSgll && cond; + + mem.fatal(__LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + assert(test ? fatalLog : true); + fatalLog = false; + + fatal(__LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + assert(testG ? fatalLog : true); + fatalLog = false; + + mem.fatal(cond, __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + assert(test ? fatalLog : true); + fatalLog = false; + + fatal(cond, __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + assert(testG ? fatalLog : true); + fatalLog = false; + + mem.fatalf("%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + assert(test ? fatalLog : true); + fatalLog = false; + + fatalf("%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + assert(testG ? fatalLog : true); + fatalLog = false; + + mem.fatalf(cond, "%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + assert(test ? fatalLog : true); + fatalLog = false; + + fatalf(cond, "%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + assert(testG ? fatalLog : true); + fatalLog = false; + } + } + } + } +} + +// Issue #5 +@safe unittest +{ + import std.string : indexOf; + + auto oldunspecificLogger = sharedLog; + + scope(exit) + { + sharedLog = oldunspecificLogger; + globalLogLevel = LogLevel.all; + } + + auto tl = new TestLogger(LogLevel.info); + sharedLog = tl; + + trace("trace"); + assert(tl.msg.indexOf("trace") == -1); +} + +// Issue #5 +@safe unittest +{ + import std.experimental.logger.multilogger : MultiLogger; + import std.string : indexOf; + + stdThreadLocalLog.logLevel = LogLevel.all; + + auto oldunspecificLogger = sharedLog; + + scope(exit) + { + sharedLog = oldunspecificLogger; + globalLogLevel = LogLevel.all; + } + + auto logger = new MultiLogger(LogLevel.error); + + auto tl = new TestLogger(LogLevel.info); + logger.insertLogger("required", tl); + sharedLog = logger; + + trace("trace"); + assert(tl.msg.indexOf("trace") == -1); + info("info"); + assert(tl.msg.indexOf("info") == -1); + error("error"); + assert(tl.msg.indexOf("error") == 0); +} + +@system unittest +{ + import std.exception : assertThrown; + auto tl = new TestLogger(); + assertThrown!Throwable(tl.fatal("fatal")); +} + +// log objects with non-safe toString +@system unittest +{ + struct Test + { + string toString() const @system + { + return "test"; + } + } + + auto tl = new TestLogger(); + tl.info(Test.init); + assert(tl.msg == "test"); +} + +// Workaround for atomics not allowed in @safe code +private auto trustedLoad(T)(ref shared T value) @trusted +{ + import core.atomic : atomicLoad, MemoryOrder; + return atomicLoad!(MemoryOrder.acq)(value); +} + +// ditto +private void trustedStore(T)(ref shared T dst, ref T src) @trusted +{ + import core.atomic : atomicStore, MemoryOrder; + atomicStore!(MemoryOrder.rel)(dst, src); +} + +// check that thread-local logging does not propagate +// to shared logger +@system unittest +{ + import core.atomic, core.thread, std.concurrency; + + static shared logged_count = 0; + + class TestLog : Logger + { + Tid tid; + + this() + { + super (LogLevel.trace); + this.tid = thisTid; + } + + override void writeLogMsg(ref LogEntry payload) @trusted + { + assert(thisTid == this.tid); + atomicOp!"+="(logged_count, 1); + } + } + + class IgnoredLog : Logger + { + this() + { + super (LogLevel.trace); + } + + override void writeLogMsg(ref LogEntry payload) @trusted + { + assert(false); + } + } + + auto oldSharedLog = sharedLog; + scope(exit) + { + sharedLog = oldSharedLog; + } + + sharedLog = new IgnoredLog; + Thread[] spawned; + + foreach (i; 0 .. 4) + { + spawned ~= new Thread({ + stdThreadLocalLog = new TestLog; + trace("zzzzzzzzzz"); + }); + spawned[$-1].start(); + } + + foreach (t; spawned) + t.join(); + + assert(atomicOp!"=="(logged_count, 4)); +} + +@safe unittest +{ + auto dl = cast(FileLogger) sharedLog; + assert(dl !is null); + assert(dl.logLevel == LogLevel.all); + assert(globalLogLevel == LogLevel.all); + + auto tl = cast(StdForwardLogger) stdThreadLocalLog; + assert(tl !is null); + stdThreadLocalLog.logLevel = LogLevel.all; +} + +// Issue 14940 +@safe unittest +{ + import std.typecons : Nullable; + + Nullable!int a = 1; + auto l = new TestLogger(); + l.infof("log: %s", a); + assert(l.msg == "log: 1"); +} + +// Ensure @system toString methods work +@system unittest +{ + enum SystemToStringMsg = "SystemToString"; + static struct SystemToString + { + string toString() @system + { + return SystemToStringMsg; + } + } + + auto tl = new TestLogger(); + + SystemToString sts; + tl.logf("%s", sts); + assert(tl.msg == SystemToStringMsg); +} + +// Issue 17328 +@safe unittest +{ + import std.format : format; + + ubyte[] data = [0]; + string s = format("%(%02x%)", data); // format 00 + assert(s == "00"); + + auto tl = new TestLogger(); + + tl.infof("%(%02x%)", data); // infof 000 + + size_t i; + string fs = tl.msg; + for (; i < s.length; ++i) + { + assert(s[s.length - 1 - i] == fs[fs.length - 1 - i], fs); + } + assert(fs.length == 2); +} + +// Issue 15954 +@safe unittest +{ + import std.conv : to; + auto tl = new TestLogger(); + tl.log("123456789".to!wstring); + assert(tl.msg == "123456789"); +} + +// Issue 16256 +@safe unittest +{ + import std.conv : to; + auto tl = new TestLogger(); + tl.log("123456789"d); + assert(tl.msg == "123456789"); +} + +// Issue 15517 +@system unittest +{ + import std.file : exists, remove; + import std.stdio : File; + import std.string : indexOf; + + string fn = "logfile.log"; + if (exists(fn)) + { + remove(fn); + } + + auto oldShared = sharedLog; + scope(exit) + { + sharedLog = oldShared; + if (exists(fn)) + { + remove(fn); + } + } + + auto ts = [ "Test log 1", "Test log 2", "Test log 3"]; + + auto fl = new FileLogger(fn); + sharedLog = fl; + assert(exists(fn)); + + foreach (t; ts) + { + log(t); + } + + auto f = File(fn); + auto l = f.byLine(); + assert(!l.empty); + size_t idx; + foreach (it; l) + { + assert(it.indexOf(ts[idx]) != -1, it); + ++idx; + } + + assert(exists(fn)); + fl.file.close(); +} diff --git a/libphobos/src/std/experimental/logger/filelogger.d b/libphobos/src/std/experimental/logger/filelogger.d new file mode 100644 index 0000000..8f97b5b --- /dev/null +++ b/libphobos/src/std/experimental/logger/filelogger.d @@ -0,0 +1,265 @@ +/// +module std.experimental.logger.filelogger; + +import std.experimental.logger.core; +import std.stdio; + +import std.typecons : Flag; + +/** An option to create $(LREF FileLogger) directory if it is non-existent. +*/ +alias CreateFolder = Flag!"CreateFolder"; + +/** This $(D Logger) implementation writes log messages to the associated +file. The name of the file has to be passed on construction time. If the file +is already present new log messages will be append at its end. +*/ +class FileLogger : Logger +{ + import std.concurrency : Tid; + import std.datetime.systime : SysTime; + import std.format : formattedWrite; + + /** A constructor for the $(D FileLogger) Logger. + + Params: + fn = The filename of the output file of the $(D FileLogger). If that + file can not be opened for writting an exception will be thrown. + lv = The $(D LogLevel) for the $(D FileLogger). By default the + + Example: + ------------- + auto l1 = new FileLogger("logFile"); + auto l2 = new FileLogger("logFile", LogLevel.fatal); + auto l3 = new FileLogger("logFile", LogLevel.fatal, CreateFolder.yes); + ------------- + */ + this(in string fn, const LogLevel lv = LogLevel.all) @safe + { + this(fn, lv, CreateFolder.yes); + } + + /** A constructor for the $(D FileLogger) Logger that takes a reference to + a $(D File). + + The $(D File) passed must be open for all the log call to the + $(D FileLogger). If the $(D File) gets closed, using the $(D FileLogger) + for logging will result in undefined behaviour. + + Params: + fn = The file used for logging. + lv = The $(D LogLevel) for the $(D FileLogger). By default the + $(D LogLevel) for $(D FileLogger) is $(D LogLevel.all). + createFileNameFolder = if yes and fn contains a folder name, this + folder will be created. + + Example: + ------------- + auto file = File("logFile.log", "w"); + auto l1 = new FileLogger(file); + auto l2 = new FileLogger(file, LogLevel.fatal); + ------------- + */ + this(in string fn, const LogLevel lv, CreateFolder createFileNameFolder) @safe + { + import std.file : exists, mkdirRecurse; + import std.path : dirName; + import std.conv : text; + + super(lv); + this.filename = fn; + + if (createFileNameFolder) + { + auto d = dirName(this.filename); + mkdirRecurse(d); + assert(exists(d), text("The folder the FileLogger should have", + " created in '", d,"' could not be created.")); + } + + this.file_.open(this.filename, "a"); + } + + /** A constructor for the $(D FileLogger) Logger that takes a reference to + a $(D File). + + The $(D File) passed must be open for all the log call to the + $(D FileLogger). If the $(D File) gets closed, using the $(D FileLogger) + for logging will result in undefined behaviour. + + Params: + file = The file used for logging. + lv = The $(D LogLevel) for the $(D FileLogger). By default the + $(D LogLevel) for $(D FileLogger) is $(D LogLevel.all). + + Example: + ------------- + auto file = File("logFile.log", "w"); + auto l1 = new FileLogger(file); + auto l2 = new FileLogger(file, LogLevel.fatal); + ------------- + */ + this(File file, const LogLevel lv = LogLevel.all) @safe + { + super(lv); + this.file_ = file; + } + + /** If the $(D FileLogger) is managing the $(D File) it logs to, this + method will return a reference to this File. + */ + @property File file() @safe + { + return this.file_; + } + + /* This method overrides the base class method in order to log to a file + without requiring heap allocated memory. Additionally, the $(D FileLogger) + local mutex is logged to serialize the log calls. + */ + override protected void beginLogMsg(string file, int line, string funcName, + string prettyFuncName, string moduleName, LogLevel logLevel, + Tid threadId, SysTime timestamp, Logger logger) + @safe + { + import std.string : lastIndexOf; + ptrdiff_t fnIdx = file.lastIndexOf('/') + 1; + ptrdiff_t funIdx = funcName.lastIndexOf('.') + 1; + + auto lt = this.file_.lockingTextWriter(); + systimeToISOString(lt, timestamp); + formattedWrite(lt, ":%s:%s:%u ", file[fnIdx .. $], + funcName[funIdx .. $], line); + } + + /* This methods overrides the base class method and writes the parts of + the log call directly to the file. + */ + override protected void logMsgPart(const(char)[] msg) + { + formattedWrite(this.file_.lockingTextWriter(), "%s", msg); + } + + /* This methods overrides the base class method and finalizes the active + log call. This requires flushing the $(D File) and releasing the + $(D FileLogger) local mutex. + */ + override protected void finishLogMsg() + { + this.file_.lockingTextWriter().put("\n"); + this.file_.flush(); + } + + /* This methods overrides the base class method and delegates the + $(D LogEntry) data to the actual implementation. + */ + override protected void writeLogMsg(ref LogEntry payload) + { + this.beginLogMsg(payload.file, payload.line, payload.funcName, + payload.prettyFuncName, payload.moduleName, payload.logLevel, + payload.threadId, payload.timestamp, payload.logger); + this.logMsgPart(payload.msg); + this.finishLogMsg(); + } + + /** If the $(D FileLogger) was constructed with a filename, this method + returns this filename. Otherwise an empty $(D string) is returned. + */ + string getFilename() + { + return this.filename; + } + + private File file_; + private string filename; +} + +@system unittest +{ + import std.array : empty; + import std.file : deleteme, remove; + import std.string : indexOf; + + string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile"; + auto l = new FileLogger(filename); + + scope(exit) + { + remove(filename); + } + + string notWritten = "this should not be written to file"; + string written = "this should be written to file"; + + l.logLevel = LogLevel.critical; + l.log(LogLevel.warning, notWritten); + l.log(LogLevel.critical, written); + destroy(l); + + auto file = File(filename, "r"); + string readLine = file.readln(); + assert(readLine.indexOf(written) != -1, readLine); + readLine = file.readln(); + assert(readLine.indexOf(notWritten) == -1, readLine); +} + +@safe unittest +{ + import std.file : rmdirRecurse, exists, deleteme; + import std.path : dirName; + + const string tmpFolder = dirName(deleteme); + const string filepath = tmpFolder ~ "/bug15771/minas/oops/"; + const string filename = filepath ~ "output.txt"; + assert(!exists(filepath)); + + auto f = new FileLogger(filename, LogLevel.all, CreateFolder.yes); + scope(exit) () @trusted { rmdirRecurse(tmpFolder ~ "/bug15771"); }(); + + f.log("Hello World!"); + assert(exists(filepath)); + f.file.close(); +} + +@system unittest +{ + import std.array : empty; + import std.file : deleteme, remove; + import std.string : indexOf; + + string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile"; + auto file = File(filename, "w"); + auto l = new FileLogger(file); + + scope(exit) + { + remove(filename); + } + + string notWritten = "this should not be written to file"; + string written = "this should be written to file"; + + l.logLevel = LogLevel.critical; + l.log(LogLevel.warning, notWritten); + l.log(LogLevel.critical, written); + file.close(); + + file = File(filename, "r"); + string readLine = file.readln(); + assert(readLine.indexOf(written) != -1, readLine); + readLine = file.readln(); + assert(readLine.indexOf(notWritten) == -1, readLine); + file.close(); +} + +@safe unittest +{ + auto dl = cast(FileLogger) sharedLog; + assert(dl !is null); + assert(dl.logLevel == LogLevel.all); + assert(globalLogLevel == LogLevel.all); + + auto tl = cast(StdForwardLogger) stdThreadLocalLog; + assert(tl !is null); + stdThreadLocalLog.logLevel = LogLevel.all; +} diff --git a/libphobos/src/std/experimental/logger/multilogger.d b/libphobos/src/std/experimental/logger/multilogger.d new file mode 100644 index 0000000..ed9cfd9 --- /dev/null +++ b/libphobos/src/std/experimental/logger/multilogger.d @@ -0,0 +1,197 @@ +/// +module std.experimental.logger.multilogger; + +import std.experimental.logger.core; +import std.experimental.logger.filelogger; + +/** This Element is stored inside the $(D MultiLogger) and associates a +$(D Logger) to a $(D string). +*/ +struct MultiLoggerEntry +{ + string name; /// The name if the $(D Logger) + Logger logger; /// The stored $(D Logger) +} + +/** MultiLogger logs to multiple $(D Logger). The $(D Logger)s are stored in an +$(D Logger[]) in their order of insertion. + +Every data logged to this $(D MultiLogger) will be distributed to all the $(D +Logger)s inserted into it. This $(D MultiLogger) implementation can +hold multiple $(D Logger)s with the same name. If the method $(D removeLogger) +is used to remove a $(D Logger) only the first occurrence with that name will +be removed. +*/ +class MultiLogger : Logger +{ + /** A constructor for the $(D MultiLogger) Logger. + + Params: + lv = The $(D LogLevel) for the $(D MultiLogger). By default the + $(D LogLevel) for $(D MultiLogger) is $(D LogLevel.all). + + Example: + ------------- + auto l1 = new MultiLogger(LogLevel.trace); + ------------- + */ + this(const LogLevel lv = LogLevel.all) @safe + { + super(lv); + } + + /** This member holds all $(D Logger)s stored in the $(D MultiLogger). + + When inheriting from $(D MultiLogger) this member can be used to gain + access to the stored $(D Logger). + */ + protected MultiLoggerEntry[] logger; + + /** This method inserts a new Logger into the $(D MultiLogger). + + Params: + name = The name of the $(D Logger) to insert. + newLogger = The $(D Logger) to insert. + */ + void insertLogger(string name, Logger newLogger) @safe + { + this.logger ~= MultiLoggerEntry(name, newLogger); + } + + /** This method removes a Logger from the $(D MultiLogger). + + Params: + toRemove = The name of the $(D Logger) to remove. If the $(D Logger) + is not found $(D null) will be returned. Only the first occurrence of + a $(D Logger) with the given name will be removed. + + Returns: The removed $(D Logger). + */ + Logger removeLogger(in char[] toRemove) @safe + { + import std.algorithm.mutation : copy; + import std.range.primitives : back, popBack; + for (size_t i = 0; i < this.logger.length; ++i) + { + if (this.logger[i].name == toRemove) + { + Logger ret = this.logger[i].logger; + this.logger[i] = this.logger.back; + this.logger.popBack(); + + return ret; + } + } + + return null; + } + + /* The override to pass the payload to all children of the + $(D MultiLoggerBase). + */ + override protected void writeLogMsg(ref LogEntry payload) @safe + { + foreach (it; this.logger) + { + /* We don't perform any checks here to avoid race conditions. + Instead the child will check on its own if its log level matches + and assume LogLevel.all for the globalLogLevel (since we already + know the message passes this test). + */ + it.logger.forwardMsg(payload); + } + } +} + +@safe unittest +{ + import std.exception : assertThrown; + import std.experimental.logger.nulllogger; + auto a = new MultiLogger; + auto n0 = new NullLogger(); + auto n1 = new NullLogger(); + a.insertLogger("zero", n0); + a.insertLogger("one", n1); + + auto n0_1 = a.removeLogger("zero"); + assert(n0_1 is n0); + auto n = a.removeLogger("zero"); + assert(n is null); + + auto n1_1 = a.removeLogger("one"); + assert(n1_1 is n1); + n = a.removeLogger("one"); + assert(n is null); +} + +@safe unittest +{ + auto a = new MultiLogger; + auto n0 = new TestLogger; + auto n1 = new TestLogger; + a.insertLogger("zero", n0); + a.insertLogger("one", n1); + + a.log("Hello TestLogger"); int line = __LINE__; + assert(n0.msg == "Hello TestLogger"); + assert(n0.line == line); + assert(n1.msg == "Hello TestLogger"); + assert(n1.line == line); +} + +// Issue #16 +@system unittest +{ + import std.file : deleteme; + import std.stdio : File; + import std.string : indexOf; + string logName = deleteme ~ __FUNCTION__ ~ ".log"; + auto logFileOutput = File(logName, "w"); + scope(exit) + { + import std.file : remove; + logFileOutput.close(); + remove(logName); + } + auto traceLog = new FileLogger(logFileOutput, LogLevel.all); + auto infoLog = new TestLogger(LogLevel.info); + + auto root = new MultiLogger(LogLevel.all); + root.insertLogger("fileLogger", traceLog); + root.insertLogger("stdoutLogger", infoLog); + + string tMsg = "A trace message"; + root.trace(tMsg); int line1 = __LINE__; + + assert(infoLog.line != line1); + assert(infoLog.msg != tMsg); + + string iMsg = "A info message"; + root.info(iMsg); int line2 = __LINE__; + + assert(infoLog.line == line2); + assert(infoLog.msg == iMsg, infoLog.msg ~ ":" ~ iMsg); + + logFileOutput.close(); + logFileOutput = File(logName, "r"); + assert(logFileOutput.isOpen); + assert(!logFileOutput.eof); + + auto line = logFileOutput.readln(); + assert(line.indexOf(tMsg) != -1, line ~ ":" ~ tMsg); + assert(!logFileOutput.eof); + line = logFileOutput.readln(); + assert(line.indexOf(iMsg) != -1, line ~ ":" ~ tMsg); +} + +@safe unittest +{ + auto dl = cast(FileLogger) sharedLog; + assert(dl !is null); + assert(dl.logLevel == LogLevel.all); + assert(globalLogLevel == LogLevel.all); + + auto tl = cast(StdForwardLogger) stdThreadLocalLog; + assert(tl !is null); + stdThreadLocalLog.logLevel = LogLevel.all; +} diff --git a/libphobos/src/std/experimental/logger/nulllogger.d b/libphobos/src/std/experimental/logger/nulllogger.d new file mode 100644 index 0000000..fa511be --- /dev/null +++ b/libphobos/src/std/experimental/logger/nulllogger.d @@ -0,0 +1,39 @@ +/// +module std.experimental.logger.nulllogger; + +import std.experimental.logger.core; + +/** The $(D NullLogger) will not process any log messages. + +In case of a log message with $(D LogLevel.fatal) nothing will happen. +*/ +class NullLogger : Logger +{ + /** The default constructor for the $(D NullLogger). + + Independent of the parameter this Logger will never log a message. + + Params: + lv = The $(D LogLevel) for the $(D NullLogger). By default the $(D LogLevel) + for $(D NullLogger) is $(D LogLevel.all). + */ + this(const LogLevel lv = LogLevel.all) @safe + { + super(lv); + this.fatalHandler = delegate() {}; + } + + override protected void writeLogMsg(ref LogEntry payload) @safe @nogc + { + } +} + +/// +@safe unittest +{ + import std.experimental.logger.nulllogger : LogLevel; + + auto nl1 = new NullLogger(LogLevel.all); + nl1.info("You will never read this."); + nl1.fatal("You will never read this, either and it will not throw"); +} diff --git a/libphobos/src/std/experimental/logger/package.d b/libphobos/src/std/experimental/logger/package.d new file mode 100644 index 0000000..b9a075c --- /dev/null +++ b/libphobos/src/std/experimental/logger/package.d @@ -0,0 +1,185 @@ +/** +Implements logging facilities. + +Copyright: Copyright Robert "burner" Schadek 2013 -- +License: Boost License 1.0. +Authors: $(HTTP www.svs.informatik.uni-oldenburg.de/60865.html, Robert burner Schadek) + +$(H3 Basic Logging) + +Message logging is a common approach to expose runtime information of a +program. Logging should be easy, but also flexible and powerful, therefore +$(D D) provides a standard interface for logging. + +The easiest way to create a log message is to write: +------------- +import std.experimental.logger; + +void main() { + log("Hello World"); +} +------------- +This will print a message to the $(D stderr) device. The message will contain +the filename, the line number, the name of the surrounding function, the time +and the message. + +More complex log call can go along the lines like: +------------- +log("Logging to the sharedLog with its default LogLevel"); +logf(LogLevel.info, 5 < 6, "%s to the sharedLog with its LogLevel.info", "Logging"); +info("Logging to the sharedLog with its info LogLevel"); +warning(5 < 6, "Logging to the sharedLog with its LogLevel.warning if 5 is less than 6"); +error("Logging to the sharedLog with its error LogLevel"); +errorf("Logging %s the sharedLog %s its error LogLevel", "to", "with"); +critical("Logging to the"," sharedLog with its error LogLevel"); +fatal("Logging to the sharedLog with its fatal LogLevel"); + +auto fLogger = new FileLogger("NameOfTheLogFile"); +fLogger.log("Logging to the fileLogger with its default LogLevel"); +fLogger.info("Logging to the fileLogger with its default LogLevel"); +fLogger.warning(5 < 6, "Logging to the fileLogger with its LogLevel.warning if 5 is less than 6"); +fLogger.warningf(5 < 6, "Logging to the fileLogger with its LogLevel.warning if %s is %s than 6", 5, "less"); +fLogger.critical("Logging to the fileLogger with its info LogLevel"); +fLogger.log(LogLevel.trace, 5 < 6, "Logging to the fileLogger"," with its default LogLevel if 5 is less than 6"); +fLogger.fatal("Logging to the fileLogger with its warning LogLevel"); +------------- +Additionally, this example shows how a new $(D FileLogger) is created. +Individual $(D Logger) and the global log functions share commonly named +functions to log data. + +The names of the functions are as follows: +$(UL + $(LI $(D log)) + $(LI $(D trace)) + $(LI $(D info)) + $(LI $(D warning)) + $(LI $(D critical)) + $(LI $(D fatal)) +) +The default $(D Logger) will by default log to $(D stderr) and has a default +$(D LogLevel) of $(D LogLevel.all). The default Logger can be accessed by +using the property called $(D sharedLog). This property is a reference to the +current default $(D Logger). This reference can be used to assign a new +default $(D Logger). +------------- +sharedLog = new FileLogger("New_Default_Log_File.log"); +------------- + +Additional $(D Logger) can be created by creating a new instance of the +required $(D Logger). + +$(H3 Logging Fundamentals) +$(H4 LogLevel) +The $(D LogLevel) of a log call can be defined in two ways. The first is by +calling $(D log) and passing the $(D LogLevel) explicitly as the first argument. +The second way of setting the $(D LogLevel) of a +log call, is by calling either $(D trace), $(D info), $(D warning), +$(D critical), or $(D fatal). The log call will then have the respective +$(D LogLevel). If no $(D LogLevel) is defined the log call will use the +current $(D LogLevel) of the used $(D Logger). If data is logged with +$(D LogLevel) $(D fatal) by default an $(D Error) will be thrown. +This behaviour can be modified by using the member $(D fatalHandler) to +assign a custom delegate to handle log call with $(D LogLevel) $(D fatal). + +$(H4 Conditional Logging) +Conditional logging can be achieved be passing a $(D bool) as first +argument to a log function. If conditional logging is used the condition must +be $(D true) in order to have the log message logged. + +In order to combine an explicit $(D LogLevel) passing with conditional +logging, the $(D LogLevel) has to be passed as first argument followed by the +$(D bool). + +$(H4 Filtering Log Messages) +Messages are logged if the $(D LogLevel) of the log message is greater than or +equal to the $(D LogLevel) of the used $(D Logger) and additionally if the +$(D LogLevel) of the log message is greater than or equal to the global $(D LogLevel). +If a condition is passed into the log call, this condition must be true. + +The global $(D LogLevel) is accessible by using $(D globalLogLevel). +To assign a $(D LogLevel) of a $(D Logger) use the $(D logLevel) property of +the logger. + +$(H4 Printf Style Logging) +If $(D printf)-style logging is needed add a $(B f) to the logging call, such as +$(D myLogger.infof("Hello %s", "world");) or $(D fatalf("errno %d", 1337)). +The additional $(B f) appended to the function name enables $(D printf)-style +logging for all combinations of explicit $(D LogLevel) and conditional +logging functions and methods. + +$(H4 Thread Local Redirection) +Calls to the free standing log functions are not directly forwarded to the +global $(D Logger) $(D sharedLog). Actually, a thread local $(D Logger) of +type $(D StdForwardLogger) processes the log call and then, by default, forwards +the created $(D Logger.LogEntry) to the $(D sharedLog) $(D Logger). +The thread local $(D Logger) is accessible by the $(D stdThreadLocalLog) +property. This property allows to assign user defined $(D Logger). The default +$(D LogLevel) of the $(D stdThreadLocalLog) $(D Logger) is $(D LogLevel.all) +and it will therefore forward all messages to the $(D sharedLog) $(D Logger). +The $(D LogLevel) of the $(D stdThreadLocalLog) can be used to filter log +calls before they reach the $(D sharedLog) $(D Logger). + +$(H3 User Defined Logger) +To customize the $(D Logger) behavior, create a new $(D class) that inherits from +the abstract $(D Logger) $(D class), and implements the $(D writeLogMsg) +method. +------------- +class MyCustomLogger : Logger +{ + this(LogLevel lv) @safe + { + super(lv); + } + + override void writeLogMsg(ref LogEntry payload) + { + // log message in my custom way + } +} + +auto logger = new MyCustomLogger(LogLevel.info); +logger.log("Awesome log message with LogLevel.info"); +------------- + +To gain more precise control over the logging process, additionally to +overriding the $(D writeLogMsg) method the methods $(D beginLogMsg), +$(D logMsgPart) and $(D finishLogMsg) can be overridden. + +$(H3 Compile Time Disabling of $(D Logger)) +In order to disable logging at compile time, pass $(D StdLoggerDisableLogging) as a +version argument to the $(D D) compiler when compiling your program code. +This will disable all logging functionality. +Specific $(D LogLevel) can be disabled at compile time as well. +In order to disable logging with the $(D trace) $(D LogLevel) pass +$(D StdLoggerDisableTrace) as a version. +The following table shows which version statement disables which +$(D LogLevel). +$(TABLE + $(TR $(TD $(D LogLevel.trace) ) $(TD StdLoggerDisableTrace)) + $(TR $(TD $(D LogLevel.info) ) $(TD StdLoggerDisableInfo)) + $(TR $(TD $(D LogLevel.warning) ) $(TD StdLoggerDisableWarning)) + $(TR $(TD $(D LogLevel.error) ) $(TD StdLoggerDisableError)) + $(TR $(TD $(D LogLevel.critical) ) $(TD StdLoggerDisableCritical)) + $(TR $(TD $(D LogLevel.fatal) ) $(TD StdLoggerDisableFatal)) +) +Such a version statement will only disable logging in the associated compile +unit. + +$(H3 Provided Logger) +By default four $(D Logger) implementations are given. The $(D FileLogger) +logs data to files. It can also be used to log to $(D stdout) and $(D stderr) +as these devices are files as well. A $(D Logger) that logs to $(D stdout) can +therefore be created by $(D new FileLogger(stdout)). +The $(D MultiLogger) is basically an associative array of $(D string)s to +$(D Logger). It propagates log calls to its stored $(D Logger). The +$(D ArrayLogger) contains an array of $(D Logger) and also propagates log +calls to its stored $(D Logger). The $(D NullLogger) does not do anything. It +will never log a message and will never throw on a log call with $(D LogLevel) +$(D error). +*/ +module std.experimental.logger; + +public import std.experimental.logger.core; +public import std.experimental.logger.filelogger; +public import std.experimental.logger.multilogger; +public import std.experimental.logger.nulllogger; diff --git a/libphobos/src/std/experimental/note.md b/libphobos/src/std/experimental/note.md new file mode 100644 index 0000000..3773270 --- /dev/null +++ b/libphobos/src/std/experimental/note.md @@ -0,0 +1 @@ +This is intended for experimental modules. diff --git a/libphobos/src/std/experimental/typecons.d b/libphobos/src/std/experimental/typecons.d new file mode 100644 index 0000000..e1d90d7 --- /dev/null +++ b/libphobos/src/std/experimental/typecons.d @@ -0,0 +1,1078 @@ +// 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, + isImplicitlyConvertible, mixinAll, staticIota, + 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 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 opCast(T)() {} Object instance(); } + interface J { @disable 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); + 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 std.functional : 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; staticIota!(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: + mixin mixinAll!( + staticMap!(generateFun, staticIota!(0, TargetMembers.length))); + } + } + } +} + +// 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); + } +} +@system unittest +{ + // Bugzilla 10377 + 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])); +} +@system unittest +{ + // Bugzilla 10536 + 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); +} +@system unittest +{ + // Enhancement 10538 + 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)); + + static void foo(ref A a) pure nothrow @safe @nogc {} + static assert(!__traits(compiles, foo(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]); +} + +// issue 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/libphobos/src/std/file.d b/libphobos/src/std/file.d new file mode 100644 index 0000000..6b12c04 --- /dev/null +++ b/libphobos/src/std/file.d @@ -0,0 +1,4325 @@ +// Written in the D programming language. + +/** +Utilities for manipulating files and scanning directories. Functions +in this module handle files as a unit, e.g., read or write one _file +at a time. For opening files and manipulating them via handles refer +to module $(MREF std, stdio). + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE, +$(TR $(TH Category) $(TH Functions)) +$(TR $(TD General) $(TD + $(LREF exists) + $(LREF isDir) + $(LREF isFile) + $(LREF isSymlink) + $(LREF rename) + $(LREF thisExePath) +)) +$(TR $(TD Directories) $(TD + $(LREF chdir) + $(LREF dirEntries) + $(LREF getcwd) + $(LREF mkdir) + $(LREF mkdirRecurse) + $(LREF rmdir) + $(LREF rmdirRecurse) + $(LREF tempDir) +)) +$(TR $(TD Files) $(TD + $(LREF append) + $(LREF copy) + $(LREF read) + $(LREF readText) + $(LREF remove) + $(LREF slurp) + $(LREF write) +)) +$(TR $(TD Symlinks) $(TD + $(LREF symlink) + $(LREF readLink) +)) +$(TR $(TD Attributes) $(TD + $(LREF attrIsDir) + $(LREF attrIsFile) + $(LREF attrIsSymlink) + $(LREF getAttributes) + $(LREF getLinkAttributes) + $(LREF getSize) + $(LREF setAttributes) +)) +$(TR $(TD Timestamp) $(TD + $(LREF getTimes) + $(LREF getTimesWin) + $(LREF setTimes) + $(LREF timeLastModified) +)) +$(TR $(TD Other) $(TD + $(LREF DirEntry) + $(LREF FileException) + $(LREF PreserveAttributes) + $(LREF SpanMode) +)) +) + + +Copyright: Copyright Digital Mars 2007 - 2011. +See_Also: The $(HTTP ddili.org/ders/d.en/files.html, official tutorial) for an +introduction to working with files in D, module +$(MREF std, stdio) for opening files and manipulating them via handles, +and module $(MREF std, path) for manipulating path strings. + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). +Authors: $(HTTP digitalmars.com, Walter Bright), + $(HTTP erdani.org, Andrei Alexandrescu), + Jonathan M Davis +Source: $(PHOBOSSRC std/_file.d) + */ +module std.file; + +import core.stdc.errno, core.stdc.stdlib, core.stdc.string; +import core.time : abs, dur, hnsecs, seconds; + +import std.datetime.date : DateTime; +import std.datetime.systime : Clock, SysTime, unixTimeToStdTime; +import std.internal.cstring; +import std.meta; +import std.range.primitives; +import std.traits; +import std.typecons; + +version (Windows) +{ + import core.sys.windows.windows, std.windows.syserror; +} +else version (Posix) +{ + import core.sys.posix.dirent, core.sys.posix.fcntl, core.sys.posix.sys.stat, + core.sys.posix.sys.time, core.sys.posix.unistd, core.sys.posix.utime; +} +else + static assert(false, "Module " ~ .stringof ~ " not implemented for this OS."); + +// Character type used for operating system filesystem APIs +version (Windows) +{ + private alias FSChar = wchar; +} +else version (Posix) +{ + private alias FSChar = char; +} +else + static assert(0); + +// Purposefully not documented. Use at your own risk +@property string deleteme() @safe +{ + import std.conv : to; + import std.path : buildPath; + import std.process : thisProcessID; + + static _deleteme = "deleteme.dmd.unittest.pid"; + static _first = true; + + if (_first) + { + _deleteme = buildPath(tempDir(), _deleteme) ~ to!string(thisProcessID); + _first = false; + } + + return _deleteme; +} + +version (unittest) private struct TestAliasedString +{ + string get() @safe @nogc pure nothrow { return _s; } + alias get this; + @disable this(this); + string _s; +} + +version (Android) +{ + package enum system_directory = "/system/etc"; + package enum system_file = "/system/etc/hosts"; +} +else version (Posix) +{ + package enum system_directory = "/usr/include"; + package enum system_file = "/usr/include/assert.h"; +} + + +/++ + Exception thrown for file I/O errors. + +/ +class FileException : Exception +{ + import std.conv : text, to; + + /++ + OS error code. + +/ + immutable uint errno; + + /++ + Constructor which takes an error message. + + Params: + name = Name of file for which the error occurred. + msg = Message describing the error. + file = The _file where the error occurred. + line = The _line where the error occurred. + +/ + this(in char[] name, in char[] msg, string file = __FILE__, size_t line = __LINE__) @safe pure + { + if (msg.empty) + super(name.idup, file, line); + else + super(text(name, ": ", msg), file, line); + + errno = 0; + } + + /++ + Constructor which takes the error number ($(LUCKY GetLastError) + in Windows, $(D_PARAM errno) in Posix). + + Params: + name = Name of file for which the error occurred. + errno = The error number. + file = The _file where the error occurred. + Defaults to $(D __FILE__). + line = The _line where the error occurred. + Defaults to $(D __LINE__). + +/ + version (Windows) this(in char[] name, + uint errno = .GetLastError(), + string file = __FILE__, + size_t line = __LINE__) @safe + { + this(name, sysErrorString(errno), file, line); + this.errno = errno; + } + else version (Posix) this(in char[] name, + uint errno = .errno, + string file = __FILE__, + size_t line = __LINE__) @trusted + { + import std.exception : errnoString; + this(name, errnoString(errno), file, line); + this.errno = errno; + } +} + +private T cenforce(T)(T condition, lazy const(char)[] name, string file = __FILE__, size_t line = __LINE__) +{ + if (condition) + return condition; + version (Windows) + { + throw new FileException(name, .GetLastError(), file, line); + } + else version (Posix) + { + throw new FileException(name, .errno, file, line); + } +} + +version (Windows) +@trusted +private T cenforce(T)(T condition, const(char)[] name, const(FSChar)* namez, + string file = __FILE__, size_t line = __LINE__) +{ + if (condition) + return condition; + if (!name) + { + import core.stdc.wchar_ : wcslen; + import std.conv : to; + + auto len = namez ? wcslen(namez) : 0; + name = to!string(namez[0 .. len]); + } + throw new FileException(name, .GetLastError(), file, line); +} + +version (Posix) +@trusted +private T cenforce(T)(T condition, const(char)[] name, const(FSChar)* namez, + string file = __FILE__, size_t line = __LINE__) +{ + if (condition) + return condition; + if (!name) + { + import core.stdc.string : strlen; + + auto len = namez ? strlen(namez) : 0; + name = namez[0 .. len].idup; + } + throw new FileException(name, .errno, file, line); +} + +@safe unittest +{ + // issue 17102 + try + { + cenforce(false, null, null, + __FILE__, __LINE__); + } + catch (FileException) {} +} + +/* ********************************** + * Basic File operations. + */ + +/******************************************** +Read entire contents of file $(D name) and returns it as an untyped +array. If the file size is larger than $(D upTo), only $(D upTo) +bytes are _read. + +Params: + name = string or range of characters representing the file _name + upTo = if present, the maximum number of bytes to _read + +Returns: Untyped array of bytes _read. + +Throws: $(LREF FileException) on error. + */ + +void[] read(R)(R name, size_t upTo = size_t.max) +if (isInputRange!R && isSomeChar!(ElementEncodingType!R) && !isInfinite!R && + !isConvertibleToString!R) +{ + static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + return readImpl(name, name.tempCString!FSChar(), upTo); + else + return readImpl(null, name.tempCString!FSChar(), upTo); +} + +/// +@safe unittest +{ + import std.utf : byChar; + scope(exit) + { + assert(exists(deleteme)); + remove(deleteme); + } + + write(deleteme, "1234"); // deleteme is the name of a temporary file + assert(read(deleteme, 2) == "12"); + assert(read(deleteme.byChar) == "1234"); + assert((cast(const(ubyte)[])read(deleteme)).length == 4); +} + +/// ditto +void[] read(R)(auto ref R name, size_t upTo = size_t.max) +if (isConvertibleToString!R) +{ + return read!(StringTypeOf!R)(name, upTo); +} + +@safe unittest +{ + static assert(__traits(compiles, read(TestAliasedString(null)))); +} + +version (Posix) private void[] readImpl(const(char)[] name, const(FSChar)* namez, size_t upTo = size_t.max) @trusted +{ + import core.memory : GC; + import std.algorithm.comparison : min; + import std.array : uninitializedArray; + import std.conv : to; + + // A few internal configuration parameters { + enum size_t + minInitialAlloc = 1024 * 4, + maxInitialAlloc = size_t.max / 2, + sizeIncrement = 1024 * 16, + maxSlackMemoryAllowed = 1024; + // } + + immutable fd = core.sys.posix.fcntl.open(namez, + core.sys.posix.fcntl.O_RDONLY); + cenforce(fd != -1, name); + scope(exit) core.sys.posix.unistd.close(fd); + + stat_t statbuf = void; + cenforce(fstat(fd, &statbuf) == 0, name, namez); + + immutable initialAlloc = min(upTo, to!size_t(statbuf.st_size + ? min(statbuf.st_size + 1, maxInitialAlloc) + : minInitialAlloc)); + void[] result = uninitializedArray!(ubyte[])(initialAlloc); + scope(failure) GC.free(result.ptr); + size_t size = 0; + + for (;;) + { + immutable actual = core.sys.posix.unistd.read(fd, result.ptr + size, + min(result.length, upTo) - size); + cenforce(actual != -1, name, namez); + if (actual == 0) break; + size += actual; + if (size >= upTo) break; + if (size < result.length) continue; + immutable newAlloc = size + sizeIncrement; + result = GC.realloc(result.ptr, newAlloc, GC.BlkAttr.NO_SCAN)[0 .. newAlloc]; + } + + return result.length - size >= maxSlackMemoryAllowed + ? GC.realloc(result.ptr, size, GC.BlkAttr.NO_SCAN)[0 .. size] + : result[0 .. size]; +} + + +version (Windows) private void[] readImpl(const(char)[] name, const(FSChar)* namez, size_t upTo = size_t.max) @safe +{ + import core.memory : GC; + import std.algorithm.comparison : min; + import std.array : uninitializedArray; + static trustedCreateFileW(const(wchar)* namez, DWORD dwDesiredAccess, DWORD dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, DWORD dwCreationDisposition, + DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) @trusted + { + return CreateFileW(namez, dwDesiredAccess, dwShareMode, + lpSecurityAttributes, dwCreationDisposition, + dwFlagsAndAttributes, hTemplateFile); + + } + static trustedCloseHandle(HANDLE hObject) @trusted + { + return CloseHandle(hObject); + } + static trustedGetFileSize(HANDLE hFile, out ulong fileSize) @trusted + { + DWORD sizeHigh; + DWORD sizeLow = GetFileSize(hFile, &sizeHigh); + const bool result = sizeLow != INVALID_FILE_SIZE; + if (result) + fileSize = makeUlong(sizeLow, sizeHigh); + return result; + } + static trustedReadFile(HANDLE hFile, void *lpBuffer, ulong nNumberOfBytesToRead) @trusted + { + // Read by chunks of size < 4GB (Windows API limit) + ulong totalNumRead = 0; + while (totalNumRead != nNumberOfBytesToRead) + { + const uint chunkSize = min(nNumberOfBytesToRead - totalNumRead, 0xffff_0000); + DWORD numRead = void; + const result = ReadFile(hFile, lpBuffer + totalNumRead, chunkSize, &numRead, null); + if (result == 0 || numRead != chunkSize) + return false; + totalNumRead += chunkSize; + } + return true; + } + + alias defaults = + AliasSeq!(GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, (SECURITY_ATTRIBUTES*).init, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, + HANDLE.init); + auto h = trustedCreateFileW(namez, defaults); + + cenforce(h != INVALID_HANDLE_VALUE, name, namez); + scope(exit) cenforce(trustedCloseHandle(h), name, namez); + ulong fileSize = void; + cenforce(trustedGetFileSize(h, fileSize), name, namez); + size_t size = min(upTo, fileSize); + auto buf = uninitializedArray!(ubyte[])(size); + + scope(failure) + { + () @trusted { GC.free(buf.ptr); } (); + } + + if (size) + cenforce(trustedReadFile(h, &buf[0], size), name, namez); + return buf[0 .. size]; +} + +version (linux) @safe unittest +{ + // A file with "zero" length that doesn't have 0 length at all + auto s = std.file.readText("/proc/sys/kernel/osrelease"); + assert(s.length > 0); + //writefln("'%s'", s); +} + +@safe unittest +{ + scope(exit) if (exists(deleteme)) remove(deleteme); + import std.stdio; + auto f = File(deleteme, "w"); + f.write("abcd"); f.flush(); + assert(read(deleteme) == "abcd"); +} + +/******************************************** +Read and validates (using $(REF validate, std,utf)) a text file. $(D S) +can be a type of array of characters of any width and constancy. No +width conversion is performed; if the width of the characters in file +$(D name) is different from the width of elements of $(D S), +validation will fail. + +Params: + name = string or range of characters representing the file _name + +Returns: Array of characters read. + +Throws: $(D FileException) on file error, $(D UTFException) on UTF +decoding error. + */ + +S readText(S = string, R)(R name) +if (isSomeString!S && + (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || isSomeString!R) && + !isConvertibleToString!R) +{ + import std.utf : validate; + static auto trustedCast(void[] buf) @trusted { return cast(S) buf; } + auto result = trustedCast(read(name)); + validate(result); + return result; +} + +/// +@safe unittest +{ + import std.exception : enforce; + write(deleteme, "abc"); // deleteme is the name of a temporary file + scope(exit) remove(deleteme); + string content = readText(deleteme); + enforce(content == "abc"); +} + +/// ditto +S readText(S = string, R)(auto ref R name) +if (isConvertibleToString!R) +{ + return readText!(S, StringTypeOf!R)(name); +} + +@safe unittest +{ + static assert(__traits(compiles, readText(TestAliasedString(null)))); +} + +/********************************************* +Write $(D buffer) to file $(D name). + +Creates the file if it does not already exist. + +Params: + name = string or range of characters representing the file _name + buffer = data to be written to file + +Throws: $(D FileException) on error. + +See_also: $(REF toFile, std,stdio) + */ +void write(R)(R name, const void[] buffer) +if ((isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || isSomeString!R) && + !isConvertibleToString!R) +{ + static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + writeImpl(name, name.tempCString!FSChar(), buffer, false); + else + writeImpl(null, name.tempCString!FSChar(), buffer, false); +} + +/// +@system unittest +{ + scope(exit) + { + assert(exists(deleteme)); + remove(deleteme); + } + + int[] a = [ 0, 1, 1, 2, 3, 5, 8 ]; + write(deleteme, a); // deleteme is the name of a temporary file + assert(cast(int[]) read(deleteme) == a); +} + +/// ditto +void write(R)(auto ref R name, const void[] buffer) +if (isConvertibleToString!R) +{ + write!(StringTypeOf!R)(name, buffer); +} + +@safe unittest +{ + static assert(__traits(compiles, write(TestAliasedString(null), null))); +} + +/********************************************* +Appends $(D buffer) to file $(D name). + +Creates the file if it does not already exist. + +Params: + name = string or range of characters representing the file _name + buffer = data to be appended to file + +Throws: $(D FileException) on error. + */ +void append(R)(R name, const void[] buffer) +if ((isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || isSomeString!R) && + !isConvertibleToString!R) +{ + static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + writeImpl(name, name.tempCString!FSChar(), buffer, true); + else + writeImpl(null, name.tempCString!FSChar(), buffer, true); +} + +/// +@system unittest +{ + scope(exit) + { + assert(exists(deleteme)); + remove(deleteme); + } + + int[] a = [ 0, 1, 1, 2, 3, 5, 8 ]; + write(deleteme, a); // deleteme is the name of a temporary file + int[] b = [ 13, 21 ]; + append(deleteme, b); + assert(cast(int[]) read(deleteme) == a ~ b); +} + +/// ditto +void append(R)(auto ref R name, const void[] buffer) +if (isConvertibleToString!R) +{ + append!(StringTypeOf!R)(name, buffer); +} + +@safe unittest +{ + static assert(__traits(compiles, append(TestAliasedString("foo"), [0, 1, 2, 3]))); +} + +// Posix implementation helper for write and append + +version (Posix) private void writeImpl(const(char)[] name, const(FSChar)* namez, + in void[] buffer, bool append) @trusted +{ + import std.conv : octal; + + // append or write + auto mode = append ? O_CREAT | O_WRONLY | O_APPEND + : O_CREAT | O_WRONLY | O_TRUNC; + + immutable fd = core.sys.posix.fcntl.open(namez, mode, octal!666); + cenforce(fd != -1, name, namez); + { + scope(failure) core.sys.posix.unistd.close(fd); + + immutable size = buffer.length; + size_t sum, cnt = void; + while (sum != size) + { + cnt = (size - sum < 2^^30) ? (size - sum) : 2^^30; + const numwritten = core.sys.posix.unistd.write(fd, buffer.ptr + sum, cnt); + if (numwritten != cnt) + break; + sum += numwritten; + } + cenforce(sum == size, name, namez); + } + cenforce(core.sys.posix.unistd.close(fd) == 0, name, namez); +} + +// Windows implementation helper for write and append + +version (Windows) private void writeImpl(const(char)[] name, const(FSChar)* namez, + in void[] buffer, bool append) @trusted +{ + HANDLE h; + if (append) + { + alias defaults = + AliasSeq!(GENERIC_WRITE, 0, null, OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, + HANDLE.init); + + h = CreateFileW(namez, defaults); + cenforce(h != INVALID_HANDLE_VALUE, name, namez); + cenforce(SetFilePointer(h, 0, null, FILE_END) != INVALID_SET_FILE_POINTER, + name, namez); + } + else // write + { + alias defaults = + AliasSeq!(GENERIC_WRITE, 0, null, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, + HANDLE.init); + + h = CreateFileW(namez, defaults); + cenforce(h != INVALID_HANDLE_VALUE, name, namez); + } + immutable size = buffer.length; + size_t sum, cnt = void; + DWORD numwritten = void; + while (sum != size) + { + cnt = (size - sum < 2^^30) ? (size - sum) : 2^^30; + WriteFile(h, buffer.ptr + sum, cast(uint) cnt, &numwritten, null); + if (numwritten != cnt) + break; + sum += numwritten; + } + cenforce(sum == size && CloseHandle(h), name, namez); +} + +/*************************************************** + * Rename file $(D from) _to $(D to). + * If the target file exists, it is overwritten. + * Params: + * from = string or range of characters representing the existing file name + * to = string or range of characters representing the target file name + * Throws: $(D FileException) on error. + */ +void rename(RF, RT)(RF from, RT to) +if ((isInputRange!RF && !isInfinite!RF && isSomeChar!(ElementEncodingType!RF) || isSomeString!RF) + && !isConvertibleToString!RF && + (isInputRange!RT && !isInfinite!RT && isSomeChar!(ElementEncodingType!RT) || isSomeString!RT) + && !isConvertibleToString!RT) +{ + // Place outside of @trusted block + auto fromz = from.tempCString!FSChar(); + auto toz = to.tempCString!FSChar(); + + static if (isNarrowString!RF && is(Unqual!(ElementEncodingType!RF) == char)) + alias f = from; + else + enum string f = null; + + static if (isNarrowString!RT && is(Unqual!(ElementEncodingType!RT) == char)) + alias t = to; + else + enum string t = null; + + renameImpl(f, t, fromz, toz); +} + +/// ditto +void rename(RF, RT)(auto ref RF from, auto ref RT to) +if (isConvertibleToString!RF || isConvertibleToString!RT) +{ + import std.meta : staticMap; + alias Types = staticMap!(convertToString, RF, RT); + rename!Types(from, to); +} + +@safe unittest +{ + static assert(__traits(compiles, rename(TestAliasedString(null), TestAliasedString(null)))); + static assert(__traits(compiles, rename("", TestAliasedString(null)))); + static assert(__traits(compiles, rename(TestAliasedString(null), ""))); + import std.utf : byChar; + static assert(__traits(compiles, rename(TestAliasedString(null), "".byChar))); +} + +private void renameImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, const(FSChar)* toz) @trusted +{ + version (Windows) + { + import std.exception : enforce; + + const result = MoveFileExW(fromz, toz, MOVEFILE_REPLACE_EXISTING); + if (!result) + { + import core.stdc.wchar_ : wcslen; + import std.conv : to, text; + + if (!f) + f = to!(typeof(f))(fromz[0 .. wcslen(fromz)]); + + if (!t) + t = to!(typeof(t))(toz[0 .. wcslen(toz)]); + + enforce(false, + new FileException( + text("Attempting to rename file ", f, " to ", t))); + } + } + else version (Posix) + { + static import core.stdc.stdio; + + cenforce(core.stdc.stdio.rename(fromz, toz) == 0, t, toz); + } +} + +@safe unittest +{ + import std.utf : byWchar; + + auto t1 = deleteme, t2 = deleteme~"2"; + scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove(); + write(t1, "1"); + rename(t1, t2); + assert(readText(t2) == "1"); + write(t1, "2"); + rename(t1, t2.byWchar); + assert(readText(t2) == "2"); +} + + +/*************************************************** +Delete file $(D name). + +Params: + name = string or range of characters representing the file _name + +Throws: $(D FileException) on error. + */ +void remove(R)(R name) +if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && + !isConvertibleToString!R) +{ + static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + removeImpl(name, name.tempCString!FSChar()); + else + removeImpl(null, name.tempCString!FSChar()); +} + +/// ditto +void remove(R)(auto ref R name) +if (isConvertibleToString!R) +{ + remove!(StringTypeOf!R)(name); +} + +@safe unittest +{ + static assert(__traits(compiles, remove(TestAliasedString("foo")))); +} + +private void removeImpl(const(char)[] name, const(FSChar)* namez) @trusted +{ + version (Windows) + { + cenforce(DeleteFileW(namez), name, namez); + } + else version (Posix) + { + static import core.stdc.stdio; + + if (!name) + { + import core.stdc.string : strlen; + auto len = strlen(namez); + name = namez[0 .. len]; + } + cenforce(core.stdc.stdio.remove(namez) == 0, + "Failed to remove file " ~ name); + } +} + +version (Windows) private WIN32_FILE_ATTRIBUTE_DATA getFileAttributesWin(R)(R name) +if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R)) +{ + auto namez = name.tempCString!FSChar(); + + WIN32_FILE_ATTRIBUTE_DATA fad = void; + + static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + { + static void getFA(const(char)[] name, const(FSChar)* namez, out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted + { + import std.exception : enforce; + enforce(GetFileAttributesExW(namez, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &fad), + new FileException(name.idup)); + } + getFA(name, namez, fad); + } + else + { + static void getFA(const(FSChar)* namez, out WIN32_FILE_ATTRIBUTE_DATA fad) @trusted + { + import core.stdc.wchar_ : wcslen; + import std.conv : to; + import std.exception : enforce; + + enforce(GetFileAttributesExW(namez, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &fad), + new FileException(namez[0 .. wcslen(namez)].to!string)); + } + getFA(namez, fad); + } + return fad; +} + +version (Windows) private ulong makeUlong(DWORD dwLow, DWORD dwHigh) @safe pure nothrow @nogc +{ + ULARGE_INTEGER li; + li.LowPart = dwLow; + li.HighPart = dwHigh; + return li.QuadPart; +} + +/*************************************************** +Get size of file $(D name) in bytes. + +Params: + name = string or range of characters representing the file _name + +Throws: $(D FileException) on error (e.g., file not found). + */ +ulong getSize(R)(R name) +if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && + !isConvertibleToString!R) +{ + version (Windows) + { + with (getFileAttributesWin(name)) + return makeUlong(nFileSizeLow, nFileSizeHigh); + } + else version (Posix) + { + auto namez = name.tempCString(); + + static trustedStat(const(FSChar)* namez, out stat_t buf) @trusted + { + return stat(namez, &buf); + } + static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + alias names = name; + else + string names = null; + stat_t statbuf = void; + cenforce(trustedStat(namez, statbuf) == 0, names, namez); + return statbuf.st_size; + } +} + +/// ditto +ulong getSize(R)(auto ref R name) +if (isConvertibleToString!R) +{ + return getSize!(StringTypeOf!R)(name); +} + +@safe unittest +{ + static assert(__traits(compiles, getSize(TestAliasedString("foo")))); +} + +@safe unittest +{ + // create a file of size 1 + write(deleteme, "a"); + scope(exit) { assert(exists(deleteme)); remove(deleteme); } + assert(getSize(deleteme) == 1); + // create a file of size 3 + write(deleteme, "abc"); + import std.utf : byChar; + assert(getSize(deleteme.byChar) == 3); +} + + +// Reads a time field from a stat_t with full precision. +version (Posix) +private SysTime statTimeToStdTime(char which)(ref stat_t statbuf) +{ + auto unixTime = mixin(`statbuf.st_` ~ which ~ `time`); + long stdTime = unixTimeToStdTime(unixTime); + + static if (is(typeof(mixin(`statbuf.st_` ~ which ~ `tim`)))) + stdTime += mixin(`statbuf.st_` ~ which ~ `tim.tv_nsec`) / 100; + else + static if (is(typeof(mixin(`statbuf.st_` ~ which ~ `timensec`)))) + stdTime += mixin(`statbuf.st_` ~ which ~ `timensec`) / 100; + else + static if (is(typeof(mixin(`statbuf.st_` ~ which ~ `time_nsec`)))) + stdTime += mixin(`statbuf.st_` ~ which ~ `time_nsec`) / 100; + else + static if (is(typeof(mixin(`statbuf.__st_` ~ which ~ `timensec`)))) + stdTime += mixin(`statbuf.__st_` ~ which ~ `timensec`) / 100; + + return SysTime(stdTime); +} + +/++ + Get the access and modified times of file or folder $(D name). + + Params: + name = File/Folder _name to get times for. + accessTime = Time the file/folder was last accessed. + modificationTime = Time the file/folder was last modified. + + Throws: + $(D FileException) on error. + +/ +void getTimes(R)(R name, + out SysTime accessTime, + out SysTime modificationTime) +if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && + !isConvertibleToString!R) +{ + version (Windows) + { + import std.datetime.systime : FILETIMEToSysTime; + + with (getFileAttributesWin(name)) + { + accessTime = FILETIMEToSysTime(&ftLastAccessTime); + modificationTime = FILETIMEToSysTime(&ftLastWriteTime); + } + } + else version (Posix) + { + auto namez = name.tempCString(); + + static auto trustedStat(const(FSChar)* namez, ref stat_t buf) @trusted + { + return stat(namez, &buf); + } + stat_t statbuf = void; + + static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + alias names = name; + else + string names = null; + cenforce(trustedStat(namez, statbuf) == 0, names, namez); + + accessTime = statTimeToStdTime!'a'(statbuf); + modificationTime = statTimeToStdTime!'m'(statbuf); + } +} + +/// ditto +void getTimes(R)(auto ref R name, + out SysTime accessTime, + out SysTime modificationTime) +if (isConvertibleToString!R) +{ + return getTimes!(StringTypeOf!R)(name, accessTime, modificationTime); +} + +@safe unittest +{ + SysTime atime, mtime; + static assert(__traits(compiles, getTimes(TestAliasedString("foo"), atime, mtime))); +} + +@system unittest +{ + import std.stdio : writefln; + + auto currTime = Clock.currTime(); + + write(deleteme, "a"); + scope(exit) { assert(exists(deleteme)); remove(deleteme); } + + SysTime accessTime1 = void; + SysTime modificationTime1 = void; + + getTimes(deleteme, accessTime1, modificationTime1); + + enum leeway = dur!"seconds"(5); + + { + auto diffa = accessTime1 - currTime; + auto diffm = modificationTime1 - currTime; + scope(failure) writefln("[%s] [%s] [%s] [%s] [%s]", accessTime1, modificationTime1, currTime, diffa, diffm); + + assert(abs(diffa) <= leeway); + assert(abs(diffm) <= leeway); + } + + version (fullFileTests) + { + import core.thread; + enum sleepTime = dur!"seconds"(2); + Thread.sleep(sleepTime); + + currTime = Clock.currTime(); + write(deleteme, "b"); + + SysTime accessTime2 = void; + SysTime modificationTime2 = void; + + getTimes(deleteme, accessTime2, modificationTime2); + + { + auto diffa = accessTime2 - currTime; + auto diffm = modificationTime2 - currTime; + scope(failure) writefln("[%s] [%s] [%s] [%s] [%s]", accessTime2, modificationTime2, currTime, diffa, diffm); + + //There is no guarantee that the access time will be updated. + assert(abs(diffa) <= leeway + sleepTime); + assert(abs(diffm) <= leeway); + } + + assert(accessTime1 <= accessTime2); + assert(modificationTime1 <= modificationTime2); + } +} + + +version (StdDdoc) +{ + /++ + $(BLUE This function is Windows-Only.) + + Get creation/access/modified times of file $(D name). + + This is the same as $(D getTimes) except that it also gives you the file + creation time - which isn't possible on Posix systems. + + Params: + name = File _name to get times for. + fileCreationTime = Time the file was created. + fileAccessTime = Time the file was last accessed. + fileModificationTime = Time the file was last modified. + + Throws: + $(D FileException) on error. + +/ + void getTimesWin(R)(R name, + out SysTime fileCreationTime, + out SysTime fileAccessTime, + out SysTime fileModificationTime) + if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && + !isConvertibleToString!R); +} +else version (Windows) +{ + void getTimesWin(R)(R name, + out SysTime fileCreationTime, + out SysTime fileAccessTime, + out SysTime fileModificationTime) + if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && + !isConvertibleToString!R) + { + import std.datetime.systime : FILETIMEToSysTime; + + with (getFileAttributesWin(name)) + { + fileCreationTime = FILETIMEToSysTime(&ftCreationTime); + fileAccessTime = FILETIMEToSysTime(&ftLastAccessTime); + fileModificationTime = FILETIMEToSysTime(&ftLastWriteTime); + } + } + + void getTimesWin(R)(auto ref R name, + out SysTime fileCreationTime, + out SysTime fileAccessTime, + out SysTime fileModificationTime) + if (isConvertibleToString!R) + { + getTimesWin!(StringTypeOf!R)(name, fileCreationTime, fileAccessTime, fileModificationTime); + } +} + +version (Windows) @system unittest +{ + import std.stdio : writefln; + auto currTime = Clock.currTime(); + + write(deleteme, "a"); + scope(exit) { assert(exists(deleteme)); remove(deleteme); } + + SysTime creationTime1 = void; + SysTime accessTime1 = void; + SysTime modificationTime1 = void; + + getTimesWin(deleteme, creationTime1, accessTime1, modificationTime1); + + enum leeway = dur!"seconds"(5); + + { + auto diffc = creationTime1 - currTime; + auto diffa = accessTime1 - currTime; + auto diffm = modificationTime1 - currTime; + scope(failure) + { + writefln("[%s] [%s] [%s] [%s] [%s] [%s] [%s]", + creationTime1, accessTime1, modificationTime1, currTime, diffc, diffa, diffm); + } + + // Deleting and recreating a file doesn't seem to always reset the "file creation time" + //assert(abs(diffc) <= leeway); + assert(abs(diffa) <= leeway); + assert(abs(diffm) <= leeway); + } + + version (fullFileTests) + { + import core.thread; + Thread.sleep(dur!"seconds"(2)); + + currTime = Clock.currTime(); + write(deleteme, "b"); + + SysTime creationTime2 = void; + SysTime accessTime2 = void; + SysTime modificationTime2 = void; + + getTimesWin(deleteme, creationTime2, accessTime2, modificationTime2); + + { + auto diffa = accessTime2 - currTime; + auto diffm = modificationTime2 - currTime; + scope(failure) + { + writefln("[%s] [%s] [%s] [%s] [%s]", + accessTime2, modificationTime2, currTime, diffa, diffm); + } + + assert(abs(diffa) <= leeway); + assert(abs(diffm) <= leeway); + } + + assert(creationTime1 == creationTime2); + assert(accessTime1 <= accessTime2); + assert(modificationTime1 <= modificationTime2); + } + + { + SysTime ctime, atime, mtime; + static assert(__traits(compiles, getTimesWin(TestAliasedString("foo"), ctime, atime, mtime))); + } +} + + +/++ + Set access/modified times of file or folder $(D name). + + Params: + name = File/Folder _name to get times for. + accessTime = Time the file/folder was last accessed. + modificationTime = Time the file/folder was last modified. + + Throws: + $(D FileException) on error. + +/ +void setTimes(R)(R name, + SysTime accessTime, + SysTime modificationTime) +if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && + !isConvertibleToString!R) +{ + version (Windows) + { + import std.datetime.systime : SysTimeToFILETIME; + + auto namez = name.tempCString!FSChar(); + static auto trustedCreateFileW(const(FSChar)* namez, DWORD dwDesiredAccess, DWORD dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, DWORD dwCreationDisposition, + DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) @trusted + { + return CreateFileW(namez, dwDesiredAccess, dwShareMode, + lpSecurityAttributes, dwCreationDisposition, + dwFlagsAndAttributes, hTemplateFile); + + } + static auto trustedCloseHandle(HANDLE hObject) @trusted + { + return CloseHandle(hObject); + } + static auto trustedSetFileTime(HANDLE hFile, in FILETIME *lpCreationTime, + in ref FILETIME lpLastAccessTime, in ref FILETIME lpLastWriteTime) @trusted + { + return SetFileTime(hFile, lpCreationTime, &lpLastAccessTime, &lpLastWriteTime); + } + + const ta = SysTimeToFILETIME(accessTime); + const tm = SysTimeToFILETIME(modificationTime); + alias defaults = + AliasSeq!(GENERIC_WRITE, + 0, + null, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | + FILE_ATTRIBUTE_DIRECTORY | + FILE_FLAG_BACKUP_SEMANTICS, + HANDLE.init); + auto h = trustedCreateFileW(namez, defaults); + + static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + alias names = name; + else + string names = null; + cenforce(h != INVALID_HANDLE_VALUE, names, namez); + + scope(exit) + cenforce(trustedCloseHandle(h), names, namez); + + cenforce(trustedSetFileTime(h, null, ta, tm), names, namez); + } + else version (Posix) + { + auto namez = name.tempCString!FSChar(); + static if (is(typeof(&utimensat))) + { + static auto trustedUtimensat(int fd, const(FSChar)* namez, const ref timespec[2] times, int flags) @trusted + { + return utimensat(fd, namez, times, flags); + } + timespec[2] t = void; + + t[0] = accessTime.toTimeSpec(); + t[1] = modificationTime.toTimeSpec(); + + static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + alias names = name; + else + string names = null; + cenforce(trustedUtimensat(AT_FDCWD, namez, t, 0) == 0, names, namez); + } + else + { + static auto trustedUtimes(const(FSChar)* namez, const ref timeval[2] times) @trusted + { + return utimes(namez, times); + } + timeval[2] t = void; + + t[0] = accessTime.toTimeVal(); + t[1] = modificationTime.toTimeVal(); + + static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + alias names = name; + else + string names = null; + cenforce(trustedUtimes(namez, t) == 0, names, namez); + } + } +} + +/// ditto +void setTimes(R)(auto ref R name, + SysTime accessTime, + SysTime modificationTime) +if (isConvertibleToString!R) +{ + setTimes!(StringTypeOf!R)(name, accessTime, modificationTime); +} + +@safe unittest +{ + if (false) // Test instatiation + setTimes(TestAliasedString("foo"), SysTime.init, SysTime.init); +} + +@system unittest +{ + import std.stdio : File; + string newdir = deleteme ~ r".dir"; + string dir = newdir ~ r"/a/b/c"; + string file = dir ~ "/file"; + + if (!exists(dir)) mkdirRecurse(dir); + { auto f = File(file, "w"); } + + void testTimes(int hnsecValue) + { + foreach (path; [file, dir]) // test file and dir + { + SysTime atime = SysTime(DateTime(2010, 10, 4, 0, 0, 30), hnsecs(hnsecValue)); + SysTime mtime = SysTime(DateTime(2011, 10, 4, 0, 0, 30), hnsecs(hnsecValue)); + setTimes(path, atime, mtime); + + SysTime atime_res; + SysTime mtime_res; + getTimes(path, atime_res, mtime_res); + assert(atime == atime_res); + assert(mtime == mtime_res); + } + } + + testTimes(0); + version (linux) + testTimes(123_456_7); + + rmdirRecurse(newdir); +} + +/++ + Returns the time that the given file was last modified. + + Throws: + $(D FileException) if the given file does not exist. ++/ +SysTime timeLastModified(R)(R name) +if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && + !isConvertibleToString!R) +{ + version (Windows) + { + SysTime dummy; + SysTime ftm; + + getTimesWin(name, dummy, dummy, ftm); + + return ftm; + } + else version (Posix) + { + auto namez = name.tempCString!FSChar(); + static auto trustedStat(const(FSChar)* namez, ref stat_t buf) @trusted + { + return stat(namez, &buf); + } + stat_t statbuf = void; + + static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + alias names = name; + else + string names = null; + cenforce(trustedStat(namez, statbuf) == 0, names, namez); + + return statTimeToStdTime!'m'(statbuf); + } +} + +/// ditto +SysTime timeLastModified(R)(auto ref R name) +if (isConvertibleToString!R) +{ + return timeLastModified!(StringTypeOf!R)(name); +} + +@safe unittest +{ + static assert(__traits(compiles, timeLastModified(TestAliasedString("foo")))); +} + +/++ + Returns the time that the given file was last modified. If the + file does not exist, returns $(D returnIfMissing). + + A frequent usage pattern occurs in build automation tools such as + $(HTTP gnu.org/software/make, make) or $(HTTP + en.wikipedia.org/wiki/Apache_Ant, ant). To check whether file $(D + target) must be rebuilt from file $(D source) (i.e., $(D target) is + older than $(D source) or does not exist), use the comparison + below. The code throws a $(D FileException) if $(D source) does not + exist (as it should). On the other hand, the $(D SysTime.min) default + makes a non-existing $(D target) seem infinitely old so the test + correctly prompts building it. + + Params: + name = The _name of the file to get the modification time for. + returnIfMissing = The time to return if the given file does not exist. + +Example: +-------------------- +if (timeLastModified(source) >= timeLastModified(target, SysTime.min)) +{ + // must (re)build +} +else +{ + // target is up-to-date +} +-------------------- ++/ +SysTime timeLastModified(R)(R name, SysTime returnIfMissing) +if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R)) +{ + version (Windows) + { + if (!exists(name)) + return returnIfMissing; + + SysTime dummy; + SysTime ftm; + + getTimesWin(name, dummy, dummy, ftm); + + return ftm; + } + else version (Posix) + { + auto namez = name.tempCString!FSChar(); + static auto trustedStat(const(FSChar)* namez, ref stat_t buf) @trusted + { + return stat(namez, &buf); + } + stat_t statbuf = void; + + return trustedStat(namez, statbuf) != 0 ? + returnIfMissing : + statTimeToStdTime!'m'(statbuf); + } +} + +@safe unittest +{ + //std.process.system("echo a > deleteme") == 0 || assert(false); + if (exists(deleteme)) + remove(deleteme); + + write(deleteme, "a\n"); + + scope(exit) + { + assert(exists(deleteme)); + remove(deleteme); + } + + // assert(lastModified("deleteme") > + // lastModified("this file does not exist", SysTime.min)); + //assert(lastModified("deleteme") > lastModified(__FILE__)); +} + + +// Tests sub-second precision of querying file times. +// Should pass on most modern systems running on modern filesystems. +// Exceptions: +// - FreeBSD, where one would need to first set the +// vfs.timestamp_precision sysctl to a value greater than zero. +// - OS X, where the native filesystem (HFS+) stores filesystem +// timestamps with 1-second precision. +version (FreeBSD) {} else +version (OSX) {} else +@system unittest +{ + import core.thread; + + if (exists(deleteme)) + remove(deleteme); + + SysTime lastTime; + foreach (n; 0 .. 3) + { + write(deleteme, "a"); + auto time = timeLastModified(deleteme); + remove(deleteme); + assert(time != lastTime); + lastTime = time; + Thread.sleep(10.msecs); + } +} + + +/** + * Determine whether the given file (or directory) _exists. + * Params: + * name = string or range of characters representing the file _name + * Returns: + * true if the file _name specified as input _exists + */ +bool exists(R)(R name) +if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && + !isConvertibleToString!R) +{ + return existsImpl(name.tempCString!FSChar()); +} + +/// ditto +bool exists(R)(auto ref R name) +if (isConvertibleToString!R) +{ + return exists!(StringTypeOf!R)(name); +} + +private bool existsImpl(const(FSChar)* namez) @trusted nothrow @nogc +{ + version (Windows) + { + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ + // fileio/base/getfileattributes.asp + return GetFileAttributesW(namez) != 0xFFFFFFFF; + } + else version (Posix) + { + /* + The reason why we use stat (and not access) here is + the quirky behavior of access for SUID programs: if + we used access, a file may not appear to "exist", + despite that the program would be able to open it + just fine. The behavior in question is described as + follows in the access man page: + + > The check is done using the calling process's real + > UID and GID, rather than the effective IDs as is + > done when actually attempting an operation (e.g., + > open(2)) on the file. This allows set-user-ID + > programs to easily determine the invoking user's + > authority. + + While various operating systems provide eaccess or + euidaccess functions, these are not part of POSIX - + so it's safer to use stat instead. + */ + + stat_t statbuf = void; + return lstat(namez, &statbuf) == 0; + } + else + static assert(0); +} + +@safe unittest +{ + assert(exists(".")); + assert(!exists("this file does not exist")); + write(deleteme, "a\n"); + scope(exit) { assert(exists(deleteme)); remove(deleteme); } + assert(exists(deleteme)); +} + +@safe unittest // Bugzilla 16573 +{ + enum S : string { foo = "foo" } + assert(__traits(compiles, S.foo.exists)); +} + +/++ + Returns the attributes of the given file. + + Note that the file attributes on Windows and Posix systems are + completely different. On Windows, they're what is returned by + $(HTTP msdn.microsoft.com/en-us/library/aa364944(v=vs.85).aspx, + GetFileAttributes), whereas on Posix systems, they're the $(LUCKY + st_mode) value which is part of the $(D stat struct) gotten by + calling the $(HTTP en.wikipedia.org/wiki/Stat_%28Unix%29, $(D stat)) + function. + + On Posix systems, if the given file is a symbolic link, then + attributes are the attributes of the file pointed to by the symbolic + link. + + Params: + name = The file to get the attributes of. + + Throws: $(D FileException) on error. + +/ +uint getAttributes(R)(R name) +if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && + !isConvertibleToString!R) +{ + version (Windows) + { + auto namez = name.tempCString!FSChar(); + static auto trustedGetFileAttributesW(const(FSChar)* namez) @trusted + { + return GetFileAttributesW(namez); + } + immutable result = trustedGetFileAttributesW(namez); + + static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + alias names = name; + else + string names = null; + cenforce(result != INVALID_FILE_ATTRIBUTES, names, namez); + + return result; + } + else version (Posix) + { + auto namez = name.tempCString!FSChar(); + static auto trustedStat(const(FSChar)* namez, ref stat_t buf) @trusted + { + return stat(namez, &buf); + } + stat_t statbuf = void; + + static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + alias names = name; + else + string names = null; + cenforce(trustedStat(namez, statbuf) == 0, names, namez); + + return statbuf.st_mode; + } +} + +/// ditto +uint getAttributes(R)(auto ref R name) +if (isConvertibleToString!R) +{ + return getAttributes!(StringTypeOf!R)(name); +} + +@safe unittest +{ + static assert(__traits(compiles, getAttributes(TestAliasedString(null)))); +} + +/++ + If the given file is a symbolic link, then this returns the attributes of the + symbolic link itself rather than file that it points to. If the given file + is $(I not) a symbolic link, then this function returns the same result + as getAttributes. + + On Windows, getLinkAttributes is identical to getAttributes. It exists on + Windows so that you don't have to special-case code for Windows when dealing + with symbolic links. + + Params: + name = The file to get the symbolic link attributes of. + + Returns: + the attributes + + Throws: + $(D FileException) on error. + +/ +uint getLinkAttributes(R)(R name) +if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && + !isConvertibleToString!R) +{ + version (Windows) + { + return getAttributes(name); + } + else version (Posix) + { + auto namez = name.tempCString!FSChar(); + static auto trustedLstat(const(FSChar)* namez, ref stat_t buf) @trusted + { + return lstat(namez, &buf); + } + stat_t lstatbuf = void; + static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + alias names = name; + else + string names = null; + cenforce(trustedLstat(namez, lstatbuf) == 0, names, namez); + return lstatbuf.st_mode; + } +} + +/// ditto +uint getLinkAttributes(R)(auto ref R name) +if (isConvertibleToString!R) +{ + return getLinkAttributes!(StringTypeOf!R)(name); +} + +@safe unittest +{ + static assert(__traits(compiles, getLinkAttributes(TestAliasedString(null)))); +} + +/++ + Set the _attributes of the given file. + + Params: + name = the file _name + attributes = the _attributes to set the file to + + Throws: + $(D FileException) if the given file does not exist. + +/ +void setAttributes(R)(R name, uint attributes) +if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && + !isConvertibleToString!R) +{ + version (Windows) + { + auto namez = name.tempCString!FSChar(); + static auto trustedSetFileAttributesW(const(FSChar)* namez, uint dwFileAttributes) @trusted + { + return SetFileAttributesW(namez, dwFileAttributes); + } + static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + alias names = name; + else + string names = null; + cenforce(trustedSetFileAttributesW(namez, attributes), names, namez); + } + else version (Posix) + { + auto namez = name.tempCString!FSChar(); + static auto trustedChmod(const(FSChar)* namez, mode_t mode) @trusted + { + return chmod(namez, mode); + } + assert(attributes <= mode_t.max); + static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + alias names = name; + else + string names = null; + cenforce(!trustedChmod(namez, cast(mode_t) attributes), names, namez); + } +} + +/// ditto +void setAttributes(R)(auto ref R name, uint attributes) +if (isConvertibleToString!R) +{ + return setAttributes!(StringTypeOf!R)(name, attributes); +} + +@safe unittest +{ + static assert(__traits(compiles, setAttributes(TestAliasedString(null), 0))); +} + +/++ + Returns whether the given file is a directory. + + Params: + name = The path to the file. + + Returns: + true if name specifies a directory + + Throws: + $(D FileException) if the given file does not exist. + +Example: +-------------------- +assert(!"/etc/fonts/fonts.conf".isDir); +assert("/usr/share/include".isDir); +-------------------- + +/ +@property bool isDir(R)(R name) +if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && + !isConvertibleToString!R) +{ + version (Windows) + { + return (getAttributes(name) & FILE_ATTRIBUTE_DIRECTORY) != 0; + } + else version (Posix) + { + return (getAttributes(name) & S_IFMT) == S_IFDIR; + } +} + +/// ditto +@property bool isDir(R)(auto ref R name) +if (isConvertibleToString!R) +{ + return name.isDir!(StringTypeOf!R); +} + +@safe unittest +{ + static assert(__traits(compiles, TestAliasedString(null).isDir)); +} + +@safe unittest +{ + version (Windows) + { + if ("C:\\Program Files\\".exists) + assert("C:\\Program Files\\".isDir); + + if ("C:\\Windows\\system.ini".exists) + assert(!"C:\\Windows\\system.ini".isDir); + } + else version (Posix) + { + if (system_directory.exists) + assert(system_directory.isDir); + + if (system_file.exists) + assert(!system_file.isDir); + } +} + +@system unittest +{ + version (Windows) + enum dir = "C:\\Program Files\\"; + else version (Posix) + enum dir = system_directory; + + if (dir.exists) + { + DirEntry de = DirEntry(dir); + assert(de.isDir); + assert(DirEntry(dir).isDir); + } +} + +/++ + Returns whether the given file _attributes are for a directory. + + Params: + attributes = The file _attributes. + + Returns: + true if attributes specifies a directory + +Example: +-------------------- +assert(!attrIsDir(getAttributes("/etc/fonts/fonts.conf"))); +assert(!attrIsDir(getLinkAttributes("/etc/fonts/fonts.conf"))); +-------------------- + +/ +bool attrIsDir(uint attributes) @safe pure nothrow @nogc +{ + version (Windows) + { + return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + } + else version (Posix) + { + return (attributes & S_IFMT) == S_IFDIR; + } +} + +@safe unittest +{ + version (Windows) + { + if ("C:\\Program Files\\".exists) + { + assert(attrIsDir(getAttributes("C:\\Program Files\\"))); + assert(attrIsDir(getLinkAttributes("C:\\Program Files\\"))); + } + + if ("C:\\Windows\\system.ini".exists) + { + assert(!attrIsDir(getAttributes("C:\\Windows\\system.ini"))); + assert(!attrIsDir(getLinkAttributes("C:\\Windows\\system.ini"))); + } + } + else version (Posix) + { + if (system_directory.exists) + { + assert(attrIsDir(getAttributes(system_directory))); + assert(attrIsDir(getLinkAttributes(system_directory))); + } + + if (system_file.exists) + { + assert(!attrIsDir(getAttributes(system_file))); + assert(!attrIsDir(getLinkAttributes(system_file))); + } + } +} + + +/++ + Returns whether the given file (or directory) is a file. + + On Windows, if a file is not a directory, then it's a file. So, + either $(D isFile) or $(D isDir) will return true for any given file. + + On Posix systems, if $(D isFile) is $(D true), that indicates that the file + is a regular file (e.g. not a block not device). So, on Posix systems, it's + possible for both $(D isFile) and $(D isDir) to be $(D false) for a + particular file (in which case, it's a special file). You can use + $(D getAttributes) to get the attributes to figure out what type of special + it is, or you can use $(D DirEntry) to get at its $(D statBuf), which is the + result from $(D stat). In either case, see the man page for $(D stat) for + more information. + + Params: + name = The path to the file. + + Returns: + true if name specifies a file + + Throws: + $(D FileException) if the given file does not exist. + +Example: +-------------------- +assert("/etc/fonts/fonts.conf".isFile); +assert(!"/usr/share/include".isFile); +-------------------- + +/ +@property bool isFile(R)(R name) +if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && + !isConvertibleToString!R) +{ + version (Windows) + return !name.isDir; + else version (Posix) + return (getAttributes(name) & S_IFMT) == S_IFREG; +} + +/// ditto +@property bool isFile(R)(auto ref R name) +if (isConvertibleToString!R) +{ + return isFile!(StringTypeOf!R)(name); +} + +@system unittest // bugzilla 15658 +{ + DirEntry e = DirEntry("."); + static assert(is(typeof(isFile(e)))); +} + +@safe unittest +{ + static assert(__traits(compiles, TestAliasedString(null).isFile)); +} + +@safe unittest +{ + version (Windows) + { + if ("C:\\Program Files\\".exists) + assert(!"C:\\Program Files\\".isFile); + + if ("C:\\Windows\\system.ini".exists) + assert("C:\\Windows\\system.ini".isFile); + } + else version (Posix) + { + if (system_directory.exists) + assert(!system_directory.isFile); + + if (system_file.exists) + assert(system_file.isFile); + } +} + + +/++ + Returns whether the given file _attributes are for a file. + + On Windows, if a file is not a directory, it's a file. So, either + $(D attrIsFile) or $(D attrIsDir) will return $(D true) for the + _attributes of any given file. + + On Posix systems, if $(D attrIsFile) is $(D true), that indicates that the + file is a regular file (e.g. not a block not device). So, on Posix systems, + it's possible for both $(D attrIsFile) and $(D attrIsDir) to be $(D false) + for a particular file (in which case, it's a special file). If a file is a + special file, you can use the _attributes to check what type of special file + it is (see the man page for $(D stat) for more information). + + Params: + attributes = The file _attributes. + + Returns: + true if the given file _attributes are for a file + +Example: +-------------------- +assert(attrIsFile(getAttributes("/etc/fonts/fonts.conf"))); +assert(attrIsFile(getLinkAttributes("/etc/fonts/fonts.conf"))); +-------------------- + +/ +bool attrIsFile(uint attributes) @safe pure nothrow @nogc +{ + version (Windows) + { + return (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0; + } + else version (Posix) + { + return (attributes & S_IFMT) == S_IFREG; + } +} + +@safe unittest +{ + version (Windows) + { + if ("C:\\Program Files\\".exists) + { + assert(!attrIsFile(getAttributes("C:\\Program Files\\"))); + assert(!attrIsFile(getLinkAttributes("C:\\Program Files\\"))); + } + + if ("C:\\Windows\\system.ini".exists) + { + assert(attrIsFile(getAttributes("C:\\Windows\\system.ini"))); + assert(attrIsFile(getLinkAttributes("C:\\Windows\\system.ini"))); + } + } + else version (Posix) + { + if (system_directory.exists) + { + assert(!attrIsFile(getAttributes(system_directory))); + assert(!attrIsFile(getLinkAttributes(system_directory))); + } + + if (system_file.exists) + { + assert(attrIsFile(getAttributes(system_file))); + assert(attrIsFile(getLinkAttributes(system_file))); + } + } +} + + +/++ + Returns whether the given file is a symbolic link. + + On Windows, returns $(D true) when the file is either a symbolic link or a + junction point. + + Params: + name = The path to the file. + + Returns: + true if name is a symbolic link + + Throws: + $(D FileException) if the given file does not exist. + +/ +@property bool isSymlink(R)(R name) +if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && + !isConvertibleToString!R) +{ + version (Windows) + return (getAttributes(name) & FILE_ATTRIBUTE_REPARSE_POINT) != 0; + else version (Posix) + return (getLinkAttributes(name) & S_IFMT) == S_IFLNK; +} + +/// ditto +@property bool isSymlink(R)(auto ref R name) +if (isConvertibleToString!R) +{ + return name.isSymlink!(StringTypeOf!R); +} + +@safe unittest +{ + static assert(__traits(compiles, TestAliasedString(null).isSymlink)); +} + +@system unittest +{ + version (Windows) + { + if ("C:\\Program Files\\".exists) + assert(!"C:\\Program Files\\".isSymlink); + + if ("C:\\Users\\".exists && "C:\\Documents and Settings\\".exists) + assert("C:\\Documents and Settings\\".isSymlink); + + enum fakeSymFile = "C:\\Windows\\system.ini"; + if (fakeSymFile.exists) + { + assert(!fakeSymFile.isSymlink); + + assert(!fakeSymFile.isSymlink); + assert(!attrIsSymlink(getAttributes(fakeSymFile))); + assert(!attrIsSymlink(getLinkAttributes(fakeSymFile))); + + assert(attrIsFile(getAttributes(fakeSymFile))); + assert(attrIsFile(getLinkAttributes(fakeSymFile))); + assert(!attrIsDir(getAttributes(fakeSymFile))); + assert(!attrIsDir(getLinkAttributes(fakeSymFile))); + + assert(getAttributes(fakeSymFile) == getLinkAttributes(fakeSymFile)); + } + } + else version (Posix) + { + if (system_directory.exists) + { + assert(!system_directory.isSymlink); + + immutable symfile = deleteme ~ "_slink\0"; + scope(exit) if (symfile.exists) symfile.remove(); + + core.sys.posix.unistd.symlink(system_directory, symfile.ptr); + + assert(symfile.isSymlink); + assert(!attrIsSymlink(getAttributes(symfile))); + assert(attrIsSymlink(getLinkAttributes(symfile))); + + assert(attrIsDir(getAttributes(symfile))); + assert(!attrIsDir(getLinkAttributes(symfile))); + + assert(!attrIsFile(getAttributes(symfile))); + assert(!attrIsFile(getLinkAttributes(symfile))); + } + + if (system_file.exists) + { + assert(!system_file.isSymlink); + + immutable symfile = deleteme ~ "_slink\0"; + scope(exit) if (symfile.exists) symfile.remove(); + + core.sys.posix.unistd.symlink(system_file, symfile.ptr); + + assert(symfile.isSymlink); + assert(!attrIsSymlink(getAttributes(symfile))); + assert(attrIsSymlink(getLinkAttributes(symfile))); + + assert(!attrIsDir(getAttributes(symfile))); + assert(!attrIsDir(getLinkAttributes(symfile))); + + assert(attrIsFile(getAttributes(symfile))); + assert(!attrIsFile(getLinkAttributes(symfile))); + } + } + + static assert(__traits(compiles, () @safe { return "dummy".isSymlink; })); +} + + +/++ + Returns whether the given file attributes are for a symbolic link. + + On Windows, return $(D true) when the file is either a symbolic link or a + junction point. + + Params: + attributes = The file attributes. + + Returns: + true if attributes are for a symbolic link + +Example: +-------------------- +core.sys.posix.unistd.symlink("/etc/fonts/fonts.conf", "/tmp/alink"); + +assert(!getAttributes("/tmp/alink").isSymlink); +assert(getLinkAttributes("/tmp/alink").isSymlink); +-------------------- + +/ +bool attrIsSymlink(uint attributes) @safe pure nothrow @nogc +{ + version (Windows) + return (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; + else version (Posix) + return (attributes & S_IFMT) == S_IFLNK; +} + + +/**************************************************** + * Change directory to $(D pathname). + * Throws: $(D FileException) on error. + */ +void chdir(R)(R pathname) +if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && + !isConvertibleToString!R) +{ + // Place outside of @trusted block + auto pathz = pathname.tempCString!FSChar(); + + version (Windows) + { + static auto trustedChdir(const(FSChar)* pathz) @trusted + { + return SetCurrentDirectoryW(pathz); + } + } + else version (Posix) + { + static auto trustedChdir(const(FSChar)* pathz) @trusted + { + return core.sys.posix.unistd.chdir(pathz) == 0; + } + } + static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + alias pathStr = pathname; + else + string pathStr = null; + cenforce(trustedChdir(pathz), pathStr, pathz); +} + +/// ditto +void chdir(R)(auto ref R pathname) +if (isConvertibleToString!R) +{ + return chdir!(StringTypeOf!R)(pathname); +} + +@safe unittest +{ + static assert(__traits(compiles, chdir(TestAliasedString(null)))); +} + +/**************************************************** +Make directory $(D pathname). + +Throws: $(D FileException) on Posix or $(D WindowsException) on Windows + if an error occured. + */ +void mkdir(R)(R pathname) +if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && + !isConvertibleToString!R) +{ + // Place outside of @trusted block + const pathz = pathname.tempCString!FSChar(); + + version (Windows) + { + static auto trustedCreateDirectoryW(const(FSChar)* pathz) @trusted + { + return CreateDirectoryW(pathz, null); + } + static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + alias pathStr = pathname; + else + string pathStr = null; + wenforce(trustedCreateDirectoryW(pathz), pathStr, pathz); + } + else version (Posix) + { + import std.conv : octal; + + static auto trustedMkdir(const(FSChar)* pathz, mode_t mode) @trusted + { + return core.sys.posix.sys.stat.mkdir(pathz, mode); + } + static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + alias pathStr = pathname; + else + string pathStr = null; + cenforce(trustedMkdir(pathz, octal!777) == 0, pathStr, pathz); + } +} + +/// ditto +void mkdir(R)(auto ref R pathname) +if (isConvertibleToString!R) +{ + return mkdir!(StringTypeOf!R)(pathname); +} + +@safe unittest +{ + import std.path : mkdir; + static assert(__traits(compiles, mkdir(TestAliasedString(null)))); +} + +// Same as mkdir but ignores "already exists" errors. +// Returns: "true" if the directory was created, +// "false" if it already existed. +private bool ensureDirExists()(in char[] pathname) +{ + import std.exception : enforce; + const pathz = pathname.tempCString!FSChar(); + + version (Windows) + { + if (() @trusted { return CreateDirectoryW(pathz, null); }()) + return true; + cenforce(GetLastError() == ERROR_ALREADY_EXISTS, pathname.idup); + } + else version (Posix) + { + import std.conv : octal; + + if (() @trusted { return core.sys.posix.sys.stat.mkdir(pathz, octal!777); }() == 0) + return true; + cenforce(errno == EEXIST || errno == EISDIR, pathname); + } + enforce(pathname.isDir, new FileException(pathname.idup)); + return false; +} + +/**************************************************** + * Make directory and all parent directories as needed. + * + * Does nothing if the directory specified by + * $(D pathname) already exists. + * + * Throws: $(D FileException) on error. + */ + +void mkdirRecurse(in char[] pathname) @safe +{ + import std.path : dirName, baseName; + + const left = dirName(pathname); + if (left.length != pathname.length && !exists(left)) + { + mkdirRecurse(left); + } + if (!baseName(pathname).empty) + { + ensureDirExists(pathname); + } +} + +@safe unittest +{ + import std.exception : assertThrown; + { + import std.path : buildPath, buildNormalizedPath; + + immutable basepath = deleteme ~ "_dir"; + scope(exit) () @trusted { rmdirRecurse(basepath); }(); + + auto path = buildPath(basepath, "a", "..", "b"); + mkdirRecurse(path); + path = path.buildNormalizedPath; + assert(path.isDir); + + path = buildPath(basepath, "c"); + write(path, ""); + assertThrown!FileException(mkdirRecurse(path)); + + path = buildPath(basepath, "d"); + mkdirRecurse(path); + mkdirRecurse(path); // should not throw + } + + version (Windows) + { + assertThrown!FileException(mkdirRecurse(`1:\foobar`)); + } + + // bug3570 + { + immutable basepath = deleteme ~ "_dir"; + version (Windows) + { + immutable path = basepath ~ "\\fake\\here\\"; + } + else version (Posix) + { + immutable path = basepath ~ `/fake/here/`; + } + + mkdirRecurse(path); + assert(basepath.exists && basepath.isDir); + scope(exit) () @trusted { rmdirRecurse(basepath); }(); + assert(path.exists && path.isDir); + } +} + +/**************************************************** +Remove directory $(D pathname). + +Params: + pathname = Range or string specifying the directory name + +Throws: $(D FileException) on error. + */ +void rmdir(R)(R pathname) +if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) && + !isConvertibleToString!R) +{ + // Place outside of @trusted block + auto pathz = pathname.tempCString!FSChar(); + + version (Windows) + { + static auto trustedRmdir(const(FSChar)* pathz) @trusted + { + return RemoveDirectoryW(pathz); + } + } + else version (Posix) + { + static auto trustedRmdir(const(FSChar)* pathz) @trusted + { + return core.sys.posix.unistd.rmdir(pathz) == 0; + } + } + static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + alias pathStr = pathname; + else + string pathStr = null; + cenforce(trustedRmdir(pathz), pathStr, pathz); +} + +/// ditto +void rmdir(R)(auto ref R pathname) +if (isConvertibleToString!R) +{ + rmdir!(StringTypeOf!R)(pathname); +} + +@safe unittest +{ + static assert(__traits(compiles, rmdir(TestAliasedString(null)))); +} + +/++ + $(BLUE This function is Posix-Only.) + + Creates a symbolic _link (_symlink). + + Params: + original = The file that is being linked. This is the target path that's + stored in the _symlink. A relative path is relative to the created + _symlink. + link = The _symlink to create. A relative path is relative to the + current working directory. + + Throws: + $(D FileException) on error (which includes if the _symlink already + exists). + +/ +version (StdDdoc) void symlink(RO, RL)(RO original, RL link) +if ((isInputRange!RO && !isInfinite!RO && isSomeChar!(ElementEncodingType!RO) || + isConvertibleToString!RO) && + (isInputRange!RL && !isInfinite!RL && isSomeChar!(ElementEncodingType!RL) || + isConvertibleToString!RL)); +else version (Posix) void symlink(RO, RL)(RO original, RL link) +if ((isInputRange!RO && !isInfinite!RO && isSomeChar!(ElementEncodingType!RO) || + isConvertibleToString!RO) && + (isInputRange!RL && !isInfinite!RL && isSomeChar!(ElementEncodingType!RL) || + isConvertibleToString!RL)) +{ + static if (isConvertibleToString!RO || isConvertibleToString!RL) + { + import std.meta : staticMap; + alias Types = staticMap!(convertToString, RO, RL); + symlink!Types(original, link); + } + else + { + import std.conv : text; + auto oz = original.tempCString(); + auto lz = link.tempCString(); + alias posixSymlink = core.sys.posix.unistd.symlink; + immutable int result = () @trusted { return posixSymlink(oz, lz); } (); + cenforce(result == 0, text(link)); + } +} + +version (Posix) @safe unittest +{ + if (system_directory.exists) + { + immutable symfile = deleteme ~ "_slink\0"; + scope(exit) if (symfile.exists) symfile.remove(); + + symlink(system_directory, symfile); + + assert(symfile.exists); + assert(symfile.isSymlink); + assert(!attrIsSymlink(getAttributes(symfile))); + assert(attrIsSymlink(getLinkAttributes(symfile))); + + assert(attrIsDir(getAttributes(symfile))); + assert(!attrIsDir(getLinkAttributes(symfile))); + + assert(!attrIsFile(getAttributes(symfile))); + assert(!attrIsFile(getLinkAttributes(symfile))); + } + + if (system_file.exists) + { + assert(!system_file.isSymlink); + + immutable symfile = deleteme ~ "_slink\0"; + scope(exit) if (symfile.exists) symfile.remove(); + + symlink(system_file, symfile); + + assert(symfile.exists); + assert(symfile.isSymlink); + assert(!attrIsSymlink(getAttributes(symfile))); + assert(attrIsSymlink(getLinkAttributes(symfile))); + + assert(!attrIsDir(getAttributes(symfile))); + assert(!attrIsDir(getLinkAttributes(symfile))); + + assert(attrIsFile(getAttributes(symfile))); + assert(!attrIsFile(getLinkAttributes(symfile))); + } +} + +version (Posix) @safe unittest +{ + static assert(__traits(compiles, + symlink(TestAliasedString(null), TestAliasedString(null)))); +} + + +/++ + $(BLUE This function is Posix-Only.) + + Returns the path to the file pointed to by a symlink. Note that the + path could be either relative or absolute depending on the symlink. + If the path is relative, it's relative to the symlink, not the current + working directory. + + Throws: + $(D FileException) on error. + +/ +version (StdDdoc) string readLink(R)(R link) +if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || + isConvertibleToString!R); +else version (Posix) string readLink(R)(R link) +if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) || + isConvertibleToString!R) +{ + static if (isConvertibleToString!R) + { + return readLink!(convertToString!R)(link); + } + else + { + import std.conv : to; + import std.exception : assumeUnique; + alias posixReadlink = core.sys.posix.unistd.readlink; + enum bufferLen = 2048; + enum maxCodeUnits = 6; + char[bufferLen] buffer; + const linkz = link.tempCString(); + auto size = () @trusted { + return posixReadlink(linkz, buffer.ptr, buffer.length); + } (); + cenforce(size != -1, to!string(link)); + + if (size <= bufferLen - maxCodeUnits) + return to!string(buffer[0 .. size]); + + auto dynamicBuffer = new char[](bufferLen * 3 / 2); + + foreach (i; 0 .. 10) + { + size = () @trusted { + return posixReadlink(linkz, dynamicBuffer.ptr, + dynamicBuffer.length); + } (); + cenforce(size != -1, to!string(link)); + + if (size <= dynamicBuffer.length - maxCodeUnits) + { + dynamicBuffer.length = size; + return () @trusted { + return assumeUnique(dynamicBuffer); + } (); + } + + dynamicBuffer.length = dynamicBuffer.length * 3 / 2; + } + + throw new FileException(to!string(link), "Path is too long to read."); + } +} + +version (Posix) @safe unittest +{ + import std.exception : assertThrown; + import std.string; + + foreach (file; [system_directory, system_file]) + { + if (file.exists) + { + immutable symfile = deleteme ~ "_slink\0"; + scope(exit) if (symfile.exists) symfile.remove(); + + symlink(file, symfile); + assert(readLink(symfile) == file, format("Failed file: %s", file)); + } + } + + assertThrown!FileException(readLink("/doesnotexist")); +} + +version (Posix) @safe unittest +{ + static assert(__traits(compiles, readLink(TestAliasedString("foo")))); +} + +version (Posix) @system unittest // input range of dchars +{ + mkdirRecurse(deleteme); + scope(exit) if (deleteme.exists) rmdirRecurse(deleteme); + write(deleteme ~ "/f", ""); + import std.range.interfaces : InputRange, inputRangeObject; + import std.utf : byChar; + immutable string link = deleteme ~ "/l"; + symlink("f", link); + InputRange!dchar linkr = inputRangeObject(link); + alias R = typeof(linkr); + static assert(isInputRange!R); + static assert(!isForwardRange!R); + assert(readLink(linkr) == "f"); +} + + +/**************************************************** + * Get the current working directory. + * Throws: $(D FileException) on error. + */ +version (Windows) string getcwd() +{ + import std.conv : to; + /* GetCurrentDirectory's return value: + 1. function succeeds: the number of characters that are written to + the buffer, not including the terminating null character. + 2. function fails: zero + 3. the buffer (lpBuffer) is not large enough: the required size of + the buffer, in characters, including the null-terminating character. + */ + wchar[4096] buffW = void; //enough for most common case + immutable n = cenforce(GetCurrentDirectoryW(to!DWORD(buffW.length), buffW.ptr), + "getcwd"); + // we can do it because toUTFX always produces a fresh string + if (n < buffW.length) + { + return buffW[0 .. n].to!string; + } + else //staticBuff isn't enough + { + auto ptr = cast(wchar*) malloc(wchar.sizeof * n); + scope(exit) free(ptr); + immutable n2 = GetCurrentDirectoryW(n, ptr); + cenforce(n2 && n2 < n, "getcwd"); + return ptr[0 .. n2].to!string; + } +} +else version (Solaris) string getcwd() +{ + /* BUF_SIZE >= PATH_MAX */ + enum BUF_SIZE = 4096; + /* The user should be able to specify any size buffer > 0 */ + auto p = cenforce(core.sys.posix.unistd.getcwd(null, BUF_SIZE), + "cannot get cwd"); + scope(exit) core.stdc.stdlib.free(p); + return p[0 .. core.stdc.string.strlen(p)].idup; +} +else version (Posix) string getcwd() +{ + auto p = cenforce(core.sys.posix.unistd.getcwd(null, 0), + "cannot get cwd"); + scope(exit) core.stdc.stdlib.free(p); + return p[0 .. core.stdc.string.strlen(p)].idup; +} + +@system unittest +{ + auto s = getcwd(); + assert(s.length); +} + +version (OSX) + private extern (C) int _NSGetExecutablePath(char* buf, uint* bufsize); +else version (FreeBSD) + private extern (C) int sysctl (const int* name, uint namelen, void* oldp, + size_t* oldlenp, const void* newp, size_t newlen); +else version (NetBSD) + private extern (C) int sysctl (const int* name, uint namelen, void* oldp, + size_t* oldlenp, const void* newp, size_t newlen); + +/** + * Returns the full path of the current executable. + * + * Throws: + * $(REF1 Exception, object) + */ +@trusted string thisExePath () +{ + version (OSX) + { + import core.sys.posix.stdlib : realpath; + import std.conv : to; + import std.exception : errnoEnforce; + + uint size; + + _NSGetExecutablePath(null, &size); // get the length of the path + auto buffer = new char[size]; + _NSGetExecutablePath(buffer.ptr, &size); + + auto absolutePath = realpath(buffer.ptr, null); // let the function allocate + + scope (exit) + { + if (absolutePath) + free(absolutePath); + } + + errnoEnforce(absolutePath); + return to!(string)(absolutePath); + } + else version (linux) + { + return readLink("/proc/self/exe"); + } + else version (Windows) + { + import std.conv : to; + import std.exception : enforce; + + wchar[MAX_PATH] buf; + wchar[] buffer = buf[]; + + while (true) + { + auto len = GetModuleFileNameW(null, buffer.ptr, cast(DWORD) buffer.length); + enforce(len, sysErrorString(GetLastError())); + if (len != buffer.length) + return to!(string)(buffer[0 .. len]); + buffer.length *= 2; + } + } + else version (FreeBSD) + { + import std.exception : errnoEnforce, assumeUnique; + enum + { + CTL_KERN = 1, + KERN_PROC = 14, + KERN_PROC_PATHNAME = 12 + } + + int[4] mib = [CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1]; + size_t len; + + auto result = sysctl(mib.ptr, mib.length, null, &len, null, 0); // get the length of the path + errnoEnforce(result == 0); + + auto buffer = new char[len - 1]; + result = sysctl(mib.ptr, mib.length, buffer.ptr, &len, null, 0); + errnoEnforce(result == 0); + + return buffer.assumeUnique; + } + else version (NetBSD) + { + return readLink("/proc/self/exe"); + } + else version (Solaris) + { + import core.sys.posix.unistd : getpid; + import std.string : format; + + // Only Solaris 10 and later + return readLink(format("/proc/%d/path/a.out", getpid())); + } + else + static assert(0, "thisExePath is not supported on this platform"); +} + +@safe unittest +{ + import std.path : isAbsolute; + auto path = thisExePath(); + + assert(path.exists); + assert(path.isAbsolute); + assert(path.isFile); +} + +version (StdDdoc) +{ + /++ + Info on a file, similar to what you'd get from stat on a Posix system. + +/ + struct DirEntry + { + /++ + Constructs a $(D DirEntry) for the given file (or directory). + + Params: + path = The file (or directory) to get a DirEntry for. + + Throws: + $(D FileException) if the file does not exist. + +/ + this(string path); + + version (Windows) + { + private this(string path, in WIN32_FIND_DATAW *fd); + } + else version (Posix) + { + private this(string path, core.sys.posix.dirent.dirent* fd); + } + + /++ + Returns the path to the file represented by this $(D DirEntry). + +Example: +-------------------- +auto de1 = DirEntry("/etc/fonts/fonts.conf"); +assert(de1.name == "/etc/fonts/fonts.conf"); + +auto de2 = DirEntry("/usr/share/include"); +assert(de2.name == "/usr/share/include"); +-------------------- + +/ + @property string name() const; + + + /++ + Returns whether the file represented by this $(D DirEntry) is a + directory. + +Example: +-------------------- +auto de1 = DirEntry("/etc/fonts/fonts.conf"); +assert(!de1.isDir); + +auto de2 = DirEntry("/usr/share/include"); +assert(de2.isDir); +-------------------- + +/ + @property bool isDir(); + + + /++ + Returns whether the file represented by this $(D DirEntry) is a file. + + On Windows, if a file is not a directory, then it's a file. So, + either $(D isFile) or $(D isDir) will return $(D true). + + On Posix systems, if $(D isFile) is $(D true), that indicates that + the file is a regular file (e.g. not a block not device). So, on + Posix systems, it's possible for both $(D isFile) and $(D isDir) to + be $(D false) for a particular file (in which case, it's a special + file). You can use $(D attributes) or $(D statBuf) to get more + information about a special file (see the stat man page for more + details). + +Example: +-------------------- +auto de1 = DirEntry("/etc/fonts/fonts.conf"); +assert(de1.isFile); + +auto de2 = DirEntry("/usr/share/include"); +assert(!de2.isFile); +-------------------- + +/ + @property bool isFile(); + + /++ + Returns whether the file represented by this $(D DirEntry) is a + symbolic link. + + On Windows, return $(D true) when the file is either a symbolic + link or a junction point. + +/ + @property bool isSymlink(); + + /++ + Returns the size of the the file represented by this $(D DirEntry) + in bytes. + +/ + @property ulong size(); + + /++ + $(BLUE This function is Windows-Only.) + + Returns the creation time of the file represented by this + $(D DirEntry). + +/ + @property SysTime timeCreated() const; + + /++ + Returns the time that the file represented by this $(D DirEntry) was + last accessed. + + Note that many file systems do not update the access time for files + (generally for performance reasons), so there's a good chance that + $(D timeLastAccessed) will return the same value as + $(D timeLastModified). + +/ + @property SysTime timeLastAccessed(); + + /++ + Returns the time that the file represented by this $(D DirEntry) was + last modified. + +/ + @property SysTime timeLastModified(); + + /++ + Returns the _attributes of the file represented by this $(D DirEntry). + + Note that the file _attributes on Windows and Posix systems are + completely different. On, Windows, they're what is returned by + $(D GetFileAttributes) + $(HTTP msdn.microsoft.com/en-us/library/aa364944(v=vs.85).aspx, GetFileAttributes) + Whereas, an Posix systems, they're the $(D st_mode) value which is + part of the $(D stat) struct gotten by calling $(D stat). + + On Posix systems, if the file represented by this $(D DirEntry) is a + symbolic link, then _attributes are the _attributes of the file + pointed to by the symbolic link. + +/ + @property uint attributes(); + + /++ + On Posix systems, if the file represented by this $(D DirEntry) is a + symbolic link, then $(D linkAttributes) are the attributes of the + symbolic link itself. Otherwise, $(D linkAttributes) is identical to + $(D attributes). + + On Windows, $(D linkAttributes) is identical to $(D attributes). It + exists on Windows so that you don't have to special-case code for + Windows when dealing with symbolic links. + +/ + @property uint linkAttributes(); + + version (Windows) + alias stat_t = void*; + + /++ + $(BLUE This function is Posix-Only.) + + The $(D stat) struct gotten from calling $(D stat). + +/ + @property stat_t statBuf(); + } +} +else version (Windows) +{ + struct DirEntry + { + public: + alias name this; + + this(string path) + { + import std.datetime.systime : FILETIMEToSysTime; + + if (!path.exists()) + throw new FileException(path, "File does not exist"); + + _name = path; + + with (getFileAttributesWin(path)) + { + _size = makeUlong(nFileSizeLow, nFileSizeHigh); + _timeCreated = FILETIMEToSysTime(&ftCreationTime); + _timeLastAccessed = FILETIMEToSysTime(&ftLastAccessTime); + _timeLastModified = FILETIMEToSysTime(&ftLastWriteTime); + _attributes = dwFileAttributes; + } + } + + private this(string path, in WIN32_FIND_DATAW *fd) + { + import core.stdc.wchar_ : wcslen; + import std.conv : to; + import std.datetime.systime : FILETIMEToSysTime; + import std.path : buildPath; + + size_t clength = wcslen(fd.cFileName.ptr); + _name = buildPath(path, fd.cFileName[0 .. clength].to!string); + _size = (cast(ulong) fd.nFileSizeHigh << 32) | fd.nFileSizeLow; + _timeCreated = FILETIMEToSysTime(&fd.ftCreationTime); + _timeLastAccessed = FILETIMEToSysTime(&fd.ftLastAccessTime); + _timeLastModified = FILETIMEToSysTime(&fd.ftLastWriteTime); + _attributes = fd.dwFileAttributes; + } + + @property string name() const pure nothrow + { + return _name; + } + + @property bool isDir() const pure nothrow + { + return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + } + + @property bool isFile() const pure nothrow + { + //Are there no options in Windows other than directory and file? + //If there are, then this probably isn't the best way to determine + //whether this DirEntry is a file or not. + return !isDir; + } + + @property bool isSymlink() const pure nothrow + { + return (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; + } + + @property ulong size() const pure nothrow + { + return _size; + } + + @property SysTime timeCreated() const pure nothrow + { + return cast(SysTime)_timeCreated; + } + + @property SysTime timeLastAccessed() const pure nothrow + { + return cast(SysTime)_timeLastAccessed; + } + + @property SysTime timeLastModified() const pure nothrow + { + return cast(SysTime)_timeLastModified; + } + + @property uint attributes() const pure nothrow + { + return _attributes; + } + + @property uint linkAttributes() const pure nothrow + { + return _attributes; + } + + private: + string _name; /// The file or directory represented by this DirEntry. + + SysTime _timeCreated; /// The time when the file was created. + SysTime _timeLastAccessed; /// The time when the file was last accessed. + SysTime _timeLastModified; /// The time when the file was last modified. + + ulong _size; /// The size of the file in bytes. + uint _attributes; /// The file attributes from WIN32_FIND_DATAW. + } +} +else version (Posix) +{ + struct DirEntry + { + public: + alias name this; + + this(string path) + { + if (!path.exists) + throw new FileException(path, "File does not exist"); + + _name = path; + + _didLStat = false; + _didStat = false; + _dTypeSet = false; + } + + private this(string path, core.sys.posix.dirent.dirent* fd) + { + import std.path : buildPath; + + immutable len = core.stdc.string.strlen(fd.d_name.ptr); + _name = buildPath(path, fd.d_name[0 .. len]); + + _didLStat = false; + _didStat = false; + + //fd_d_type doesn't work for all file systems, + //in which case the result is DT_UNKOWN. But we + //can determine the correct type from lstat, so + //we'll only set the dtype here if we could + //correctly determine it (not lstat in the case + //of DT_UNKNOWN in case we don't ever actually + //need the dtype, thus potentially avoiding the + //cost of calling lstat). + static if (__traits(compiles, fd.d_type != DT_UNKNOWN)) + { + if (fd.d_type != DT_UNKNOWN) + { + _dType = fd.d_type; + _dTypeSet = true; + } + else + _dTypeSet = false; + } + else + { + // e.g. Solaris does not have the d_type member + _dTypeSet = false; + } + } + + @property string name() const pure nothrow + { + return _name; + } + + @property bool isDir() + { + _ensureStatOrLStatDone(); + + return (_statBuf.st_mode & S_IFMT) == S_IFDIR; + } + + @property bool isFile() + { + _ensureStatOrLStatDone(); + + return (_statBuf.st_mode & S_IFMT) == S_IFREG; + } + + @property bool isSymlink() + { + _ensureLStatDone(); + + return (_lstatMode & S_IFMT) == S_IFLNK; + } + + @property ulong size() + { + _ensureStatDone(); + return _statBuf.st_size; + } + + @property SysTime timeStatusChanged() + { + _ensureStatDone(); + + return statTimeToStdTime!'c'(_statBuf); + } + + @property SysTime timeLastAccessed() + { + _ensureStatDone(); + + return statTimeToStdTime!'a'(_statBuf); + } + + @property SysTime timeLastModified() + { + _ensureStatDone(); + + return statTimeToStdTime!'m'(_statBuf); + } + + @property uint attributes() + { + _ensureStatDone(); + + return _statBuf.st_mode; + } + + @property uint linkAttributes() + { + _ensureLStatDone(); + + return _lstatMode; + } + + @property stat_t statBuf() + { + _ensureStatDone(); + + return _statBuf; + } + + private: + /++ + This is to support lazy evaluation, because doing stat's is + expensive and not always needed. + +/ + void _ensureStatDone() @safe + { + import std.exception : enforce; + + static auto trustedStat(in char[] path, stat_t* buf) @trusted + { + return stat(path.tempCString(), buf); + } + if (_didStat) + return; + + enforce(trustedStat(_name, &_statBuf) == 0, + "Failed to stat file `" ~ _name ~ "'"); + + _didStat = true; + } + + /++ + This is to support lazy evaluation, because doing stat's is + expensive and not always needed. + + Try both stat and lstat for isFile and isDir + to detect broken symlinks. + +/ + void _ensureStatOrLStatDone() + { + if (_didStat) + return; + + if ( stat(_name.tempCString(), &_statBuf) != 0 ) + { + _ensureLStatDone(); + + _statBuf = stat_t.init; + _statBuf.st_mode = S_IFLNK; + } + else + { + _didStat = true; + } + } + + /++ + This is to support lazy evaluation, because doing stat's is + expensive and not always needed. + +/ + void _ensureLStatDone() + { + import std.exception : enforce; + + if (_didLStat) + return; + + stat_t statbuf = void; + + enforce(lstat(_name.tempCString(), &statbuf) == 0, + "Failed to stat file `" ~ _name ~ "'"); + + _lstatMode = statbuf.st_mode; + + _dTypeSet = true; + _didLStat = true; + } + + string _name; /// The file or directory represented by this DirEntry. + + stat_t _statBuf = void; /// The result of stat(). + uint _lstatMode; /// The stat mode from lstat(). + ubyte _dType; /// The type of the file. + + bool _didLStat = false; /// Whether lstat() has been called for this DirEntry. + bool _didStat = false; /// Whether stat() has been called for this DirEntry. + bool _dTypeSet = false; /// Whether the dType of the file has been set. + } +} + +@system unittest +{ + version (Windows) + { + if ("C:\\Program Files\\".exists) + { + auto de = DirEntry("C:\\Program Files\\"); + assert(!de.isFile); + assert(de.isDir); + assert(!de.isSymlink); + } + + if ("C:\\Users\\".exists && "C:\\Documents and Settings\\".exists) + { + auto de = DirEntry("C:\\Documents and Settings\\"); + assert(de.isSymlink); + } + + if ("C:\\Windows\\system.ini".exists) + { + auto de = DirEntry("C:\\Windows\\system.ini"); + assert(de.isFile); + assert(!de.isDir); + assert(!de.isSymlink); + } + } + else version (Posix) + { + import std.exception : assertThrown; + + if (system_directory.exists) + { + { + auto de = DirEntry(system_directory); + assert(!de.isFile); + assert(de.isDir); + assert(!de.isSymlink); + } + + immutable symfile = deleteme ~ "_slink\0"; + scope(exit) if (symfile.exists) symfile.remove(); + + core.sys.posix.unistd.symlink(system_directory, symfile.ptr); + + { + auto de = DirEntry(symfile); + assert(!de.isFile); + assert(de.isDir); + assert(de.isSymlink); + } + + symfile.remove(); + core.sys.posix.unistd.symlink((deleteme ~ "_broken_symlink\0").ptr, symfile.ptr); + + { + //Issue 8298 + DirEntry de = DirEntry(symfile); + + assert(!de.isFile); + assert(!de.isDir); + assert(de.isSymlink); + assertThrown(de.size); + assertThrown(de.timeStatusChanged); + assertThrown(de.timeLastAccessed); + assertThrown(de.timeLastModified); + assertThrown(de.attributes); + assertThrown(de.statBuf); + assert(symfile.exists); + symfile.remove(); + } + } + + if (system_file.exists) + { + auto de = DirEntry(system_file); + assert(de.isFile); + assert(!de.isDir); + assert(!de.isSymlink); + } + } +} + +alias PreserveAttributes = Flag!"preserveAttributes"; + +version (StdDdoc) +{ + /// Defaults to $(D Yes.preserveAttributes) on Windows, and the opposite on all other platforms. + PreserveAttributes preserveAttributesDefault; +} +else version (Windows) +{ + enum preserveAttributesDefault = Yes.preserveAttributes; +} +else +{ + enum preserveAttributesDefault = No.preserveAttributes; +} + +/*************************************************** +Copy file $(D from) _to file $(D to). File timestamps are preserved. +File attributes are preserved, if $(D preserve) equals $(D Yes.preserveAttributes). +On Windows only $(D Yes.preserveAttributes) (the default on Windows) is supported. +If the target file exists, it is overwritten. + +Params: + from = string or range of characters representing the existing file name + to = string or range of characters representing the target file name + preserve = whether to _preserve the file attributes + +Throws: $(D FileException) on error. + */ +void copy(RF, RT)(RF from, RT to, PreserveAttributes preserve = preserveAttributesDefault) +if (isInputRange!RF && !isInfinite!RF && isSomeChar!(ElementEncodingType!RF) && !isConvertibleToString!RF && + isInputRange!RT && !isInfinite!RT && isSomeChar!(ElementEncodingType!RT) && !isConvertibleToString!RT) +{ + // Place outside of @trusted block + auto fromz = from.tempCString!FSChar(); + auto toz = to.tempCString!FSChar(); + + static if (isNarrowString!RF && is(Unqual!(ElementEncodingType!RF) == char)) + alias f = from; + else + enum string f = null; + + static if (isNarrowString!RT && is(Unqual!(ElementEncodingType!RT) == char)) + alias t = to; + else + enum string t = null; + + copyImpl(f, t, fromz, toz, preserve); +} + +/// ditto +void copy(RF, RT)(auto ref RF from, auto ref RT to, PreserveAttributes preserve = preserveAttributesDefault) +if (isConvertibleToString!RF || isConvertibleToString!RT) +{ + import std.meta : staticMap; + alias Types = staticMap!(convertToString, RF, RT); + copy!Types(from, to, preserve); +} + +@safe unittest // issue 15319 +{ + assert(__traits(compiles, copy("from.txt", "to.txt"))); +} + +private void copyImpl(const(char)[] f, const(char)[] t, const(FSChar)* fromz, const(FSChar)* toz, + PreserveAttributes preserve) @trusted +{ + version (Windows) + { + assert(preserve == Yes.preserveAttributes); + immutable result = CopyFileW(fromz, toz, false); + if (!result) + { + import core.stdc.wchar_ : wcslen; + import std.conv : to; + + if (!t) + t = to!(typeof(t))(toz[0 .. wcslen(toz)]); + + throw new FileException(t); + } + } + else version (Posix) + { + static import core.stdc.stdio; + import std.conv : to, octal; + + immutable fdr = core.sys.posix.fcntl.open(fromz, O_RDONLY); + cenforce(fdr != -1, f, fromz); + scope(exit) core.sys.posix.unistd.close(fdr); + + stat_t statbufr = void; + cenforce(fstat(fdr, &statbufr) == 0, f, fromz); + //cenforce(core.sys.posix.sys.stat.fstat(fdr, &statbufr) == 0, f, fromz); + + immutable fdw = core.sys.posix.fcntl.open(toz, + O_CREAT | O_WRONLY, octal!666); + cenforce(fdw != -1, t, toz); + { + scope(failure) core.sys.posix.unistd.close(fdw); + + stat_t statbufw = void; + cenforce(fstat(fdw, &statbufw) == 0, t, toz); + if (statbufr.st_dev == statbufw.st_dev && statbufr.st_ino == statbufw.st_ino) + throw new FileException(t, "Source and destination are the same file"); + } + + scope(failure) core.stdc.stdio.remove(toz); + { + scope(failure) core.sys.posix.unistd.close(fdw); + cenforce(ftruncate(fdw, 0) == 0, t, toz); + + auto BUFSIZ = 4096u * 16; + auto buf = core.stdc.stdlib.malloc(BUFSIZ); + if (!buf) + { + BUFSIZ = 4096; + buf = core.stdc.stdlib.malloc(BUFSIZ); + if (!buf) + { + import core.exception : onOutOfMemoryError; + onOutOfMemoryError(); + } + } + scope(exit) core.stdc.stdlib.free(buf); + + for (auto size = statbufr.st_size; size; ) + { + immutable toxfer = (size > BUFSIZ) ? BUFSIZ : cast(size_t) size; + cenforce( + core.sys.posix.unistd.read(fdr, buf, toxfer) == toxfer + && core.sys.posix.unistd.write(fdw, buf, toxfer) == toxfer, + f, fromz); + assert(size >= toxfer); + size -= toxfer; + } + if (preserve) + cenforce(fchmod(fdw, to!mode_t(statbufr.st_mode)) == 0, f, fromz); + } + + cenforce(core.sys.posix.unistd.close(fdw) != -1, f, fromz); + + utimbuf utim = void; + utim.actime = cast(time_t) statbufr.st_atime; + utim.modtime = cast(time_t) statbufr.st_mtime; + + cenforce(utime(toz, &utim) != -1, f, fromz); + } +} + +@safe unittest +{ + import std.algorithm, std.file; // issue 14817 + auto t1 = deleteme, t2 = deleteme~"2"; + scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove(); + write(t1, "11"); + copy(t1, t2); + assert(readText(t2) == "11"); + write(t1, "2"); + copy(t1, t2); + assert(readText(t2) == "2"); + + import std.utf : byChar; + copy(t1.byChar, t2.byChar); + assert(readText(t2.byChar) == "2"); +} + +@safe version (Posix) @safe unittest //issue 11434 +{ + import std.conv : octal; + auto t1 = deleteme, t2 = deleteme~"2"; + scope(exit) foreach (t; [t1, t2]) if (t.exists) t.remove(); + write(t1, "1"); + setAttributes(t1, octal!767); + copy(t1, t2, Yes.preserveAttributes); + assert(readText(t2) == "1"); + assert(getAttributes(t2) == octal!100767); +} + +@safe unittest // issue 15865 +{ + import std.exception : assertThrown; + auto t = deleteme; + write(t, "a"); + scope(exit) t.remove(); + assertThrown!FileException(copy(t, t)); + assert(readText(t) == "a"); +} + +/++ + Remove directory and all of its content and subdirectories, + recursively. + + Throws: + $(D FileException) if there is an error (including if the given + file is not a directory). + +/ +void rmdirRecurse(in char[] pathname) +{ + //No references to pathname will be kept after rmdirRecurse, + //so the cast is safe + rmdirRecurse(DirEntry(cast(string) pathname)); +} + +/++ + Remove directory and all of its content and subdirectories, + recursively. + + Throws: + $(D FileException) if there is an error (including if the given + file is not a directory). + +/ +void rmdirRecurse(ref DirEntry de) +{ + if (!de.isDir) + throw new FileException(de.name, "Not a directory"); + + if (de.isSymlink) + { + version (Windows) + rmdir(de.name); + else + remove(de.name); + } + else + { + // all children, recursively depth-first + foreach (DirEntry e; dirEntries(de.name, SpanMode.depth, false)) + { + attrIsDir(e.linkAttributes) ? rmdir(e.name) : remove(e.name); + } + + // the dir itself + rmdir(de.name); + } +} +///ditto +//Note, without this overload, passing an RValue DirEntry still works, but +//actually fully reconstructs a DirEntry inside the +//"rmdirRecurse(in char[] pathname)" implementation. That is needlessly +//expensive. +//A DirEntry is a bit big (72B), so keeping the "by ref" signature is desirable. +void rmdirRecurse(DirEntry de) +{ + rmdirRecurse(de); +} + +version (Windows) @system unittest +{ + import std.exception : enforce; + auto d = deleteme ~ r".dir\a\b\c\d\e\f\g"; + mkdirRecurse(d); + rmdirRecurse(deleteme ~ ".dir"); + enforce(!exists(deleteme ~ ".dir")); +} + +version (Posix) @system unittest +{ + import std.exception : enforce, collectException; + import std.process : executeShell; + collectException(rmdirRecurse(deleteme)); + auto d = deleteme~"/a/b/c/d/e/f/g"; + enforce(collectException(mkdir(d))); + mkdirRecurse(d); + core.sys.posix.unistd.symlink((deleteme~"/a/b/c\0").ptr, + (deleteme~"/link\0").ptr); + rmdirRecurse(deleteme~"/link"); + enforce(exists(d)); + rmdirRecurse(deleteme); + enforce(!exists(deleteme)); + + d = deleteme~"/a/b/c/d/e/f/g"; + mkdirRecurse(d); + version (Android) string link_cmd = "ln -s "; + else string link_cmd = "ln -sf "; + executeShell(link_cmd~deleteme~"/a/b/c "~deleteme~"/link"); + rmdirRecurse(deleteme); + enforce(!exists(deleteme)); +} + +@system unittest +{ + void[] buf; + + buf = new void[10]; + (cast(byte[]) buf)[] = 3; + string unit_file = deleteme ~ "-unittest_write.tmp"; + if (exists(unit_file)) remove(unit_file); + write(unit_file, buf); + void[] buf2 = read(unit_file); + assert(buf == buf2); + + string unit2_file = deleteme ~ "-unittest_write2.tmp"; + copy(unit_file, unit2_file); + buf2 = read(unit2_file); + assert(buf == buf2); + + remove(unit_file); + assert(!exists(unit_file)); + remove(unit2_file); + assert(!exists(unit2_file)); +} + +/** + * Dictates directory spanning policy for $(D_PARAM dirEntries) (see below). + */ +enum SpanMode +{ + /** Only spans one directory. */ + shallow, + /** Spans the directory in + $(HTTPS en.wikipedia.org/wiki/Tree_traversal#Post-order, + _depth-first $(B post)-order), i.e. the content of any + subdirectory is spanned before that subdirectory itself. Useful + e.g. when recursively deleting files. */ + depth, + /** Spans the directory in + $(HTTPS en.wikipedia.org/wiki/Tree_traversal#Pre-order, depth-first + $(B pre)-order), i.e. the content of any subdirectory is spanned + right after that subdirectory itself. + + Note that $(D SpanMode.breadth) will not result in all directory + members occurring before any subdirectory members, i.e. it is not + _true + $(HTTPS en.wikipedia.org/wiki/Tree_traversal#Breadth-first_search, + _breadth-first traversal). + */ + breadth, +} + +private struct DirIteratorImpl +{ + import std.array : Appender, appender; + SpanMode _mode; + // Whether we should follow symlinked directories while iterating. + // It also indicates whether we should avoid functions which call + // stat (since we should only need lstat in this case and it would + // be more efficient to not call stat in addition to lstat). + bool _followSymlink; + DirEntry _cur; + Appender!(DirHandle[]) _stack; + Appender!(DirEntry[]) _stashed; //used in depth first mode + //stack helpers + void pushExtra(DirEntry de){ _stashed.put(de); } + //ditto + bool hasExtra(){ return !_stashed.data.empty; } + //ditto + DirEntry popExtra() + { + DirEntry de; + de = _stashed.data[$-1]; + _stashed.shrinkTo(_stashed.data.length - 1); + return de; + + } + version (Windows) + { + struct DirHandle + { + string dirpath; + HANDLE h; + } + + bool stepIn(string directory) + { + import std.path : chainPath; + + auto search_pattern = chainPath(directory, "*.*"); + WIN32_FIND_DATAW findinfo; + HANDLE h = FindFirstFileW(search_pattern.tempCString!FSChar(), &findinfo); + cenforce(h != INVALID_HANDLE_VALUE, directory); + _stack.put(DirHandle(directory, h)); + return toNext(false, &findinfo); + } + + bool next() + { + if (_stack.data.empty) + return false; + WIN32_FIND_DATAW findinfo; + return toNext(true, &findinfo); + } + + bool toNext(bool fetch, WIN32_FIND_DATAW* findinfo) + { + import core.stdc.wchar_ : wcscmp; + + if (fetch) + { + if (FindNextFileW(_stack.data[$-1].h, findinfo) == FALSE) + { + popDirStack(); + return false; + } + } + while ( wcscmp(findinfo.cFileName.ptr, ".") == 0 + || wcscmp(findinfo.cFileName.ptr, "..") == 0) + if (FindNextFileW(_stack.data[$-1].h, findinfo) == FALSE) + { + popDirStack(); + return false; + } + _cur = DirEntry(_stack.data[$-1].dirpath, findinfo); + return true; + } + + void popDirStack() + { + assert(!_stack.data.empty); + FindClose(_stack.data[$-1].h); + _stack.shrinkTo(_stack.data.length-1); + } + + void releaseDirStack() + { + foreach ( d; _stack.data) + FindClose(d.h); + } + + bool mayStepIn() + { + return _followSymlink ? _cur.isDir : _cur.isDir && !_cur.isSymlink; + } + } + else version (Posix) + { + struct DirHandle + { + string dirpath; + DIR* h; + } + + bool stepIn(string directory) + { + auto h = directory.length ? opendir(directory.tempCString()) : opendir("."); + cenforce(h, directory); + _stack.put(DirHandle(directory, h)); + return next(); + } + + bool next() + { + if (_stack.data.empty) + return false; + for (dirent* fdata; (fdata = readdir(_stack.data[$-1].h)) != null; ) + { + // Skip "." and ".." + if (core.stdc.string.strcmp(fdata.d_name.ptr, ".") && + core.stdc.string.strcmp(fdata.d_name.ptr, "..") ) + { + _cur = DirEntry(_stack.data[$-1].dirpath, fdata); + return true; + } + } + popDirStack(); + return false; + } + + void popDirStack() + { + assert(!_stack.data.empty); + closedir(_stack.data[$-1].h); + _stack.shrinkTo(_stack.data.length-1); + } + + void releaseDirStack() + { + foreach ( d; _stack.data) + closedir(d.h); + } + + bool mayStepIn() + { + return _followSymlink ? _cur.isDir : attrIsDir(_cur.linkAttributes); + } + } + + this(R)(R pathname, SpanMode mode, bool followSymlink) + if (isInputRange!R && isSomeChar!(ElementEncodingType!R)) + { + _mode = mode; + _followSymlink = followSymlink; + _stack = appender(cast(DirHandle[])[]); + if (_mode == SpanMode.depth) + _stashed = appender(cast(DirEntry[])[]); + + static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char)) + alias pathnameStr = pathname; + else + { + import std.array : array; + string pathnameStr = pathname.array; + } + if (stepIn(pathnameStr)) + { + if (_mode == SpanMode.depth) + while (mayStepIn()) + { + auto thisDir = _cur; + if (stepIn(_cur.name)) + { + pushExtra(thisDir); + } + else + break; + } + } + } + @property bool empty(){ return _stashed.data.empty && _stack.data.empty; } + @property DirEntry front(){ return _cur; } + void popFront() + { + switch (_mode) + { + case SpanMode.depth: + if (next()) + { + while (mayStepIn()) + { + auto thisDir = _cur; + if (stepIn(_cur.name)) + { + pushExtra(thisDir); + } + else + break; + } + } + else if (hasExtra()) + _cur = popExtra(); + break; + case SpanMode.breadth: + if (mayStepIn()) + { + if (!stepIn(_cur.name)) + while (!empty && !next()){} + } + else + while (!empty && !next()){} + break; + default: + next(); + } + } + + ~this() + { + releaseDirStack(); + } +} + +struct DirIterator +{ +private: + RefCounted!(DirIteratorImpl, RefCountedAutoInitialize.no) impl; + this(string pathname, SpanMode mode, bool followSymlink) + { + impl = typeof(impl)(pathname, mode, followSymlink); + } +public: + @property bool empty(){ return impl.empty; } + @property DirEntry front(){ return impl.front; } + void popFront(){ impl.popFront(); } + +} +/++ + Returns an input range of $(D DirEntry) that lazily iterates a given directory, + also provides two ways of foreach iteration. The iteration variable can be of + type $(D string) if only the name is needed, or $(D DirEntry) + if additional details are needed. The span _mode dictates how the + directory is traversed. The name of each iterated directory entry + contains the absolute _path. + + Params: + path = The directory to iterate over. + If empty, the current directory will be iterated. + + pattern = Optional string with wildcards, such as $(RED + "*.d"). When present, it is used to filter the + results by their file name. The supported wildcard + strings are described under $(REF globMatch, + std,_path). + + mode = Whether the directory's sub-directories should be + iterated in depth-first port-order ($(LREF depth)), + depth-first pre-order ($(LREF breadth)), or not at all + ($(LREF shallow)). + + followSymlink = Whether symbolic links which point to directories + should be treated as directories and their contents + iterated over. + + Throws: + $(D FileException) if the directory does not exist. + +Example: +-------------------- +// Iterate a directory in depth +foreach (string name; dirEntries("destroy/me", SpanMode.depth)) +{ + remove(name); +} + +// Iterate the current directory in breadth +foreach (string name; dirEntries("", SpanMode.breadth)) +{ + writeln(name); +} + +// Iterate a directory and get detailed info about it +foreach (DirEntry e; dirEntries("dmd-testing", SpanMode.breadth)) +{ + writeln(e.name, "\t", e.size); +} + +// Iterate over all *.d files in current directory and all its subdirectories +auto dFiles = dirEntries("", SpanMode.depth).filter!(f => f.name.endsWith(".d")); +foreach (d; dFiles) + writeln(d.name); + +// Hook it up with std.parallelism to compile them all in parallel: +foreach (d; parallel(dFiles, 1)) //passes by 1 file to each thread +{ + string cmd = "dmd -c " ~ d.name; + writeln(cmd); + std.process.system(cmd); +} + +// Iterate over all D source files in current directory and all its +// subdirectories +auto dFiles = dirEntries("","*.{d,di}",SpanMode.depth); +foreach (d; dFiles) + writeln(d.name); +-------------------- + +/ +auto dirEntries(string path, SpanMode mode, bool followSymlink = true) +{ + return DirIterator(path, mode, followSymlink); +} + +/// Duplicate functionality of D1's $(D std.file.listdir()): +@safe unittest +{ + string[] listdir(string pathname) + { + import std.algorithm; + import std.array; + import std.file; + import std.path; + + return std.file.dirEntries(pathname, SpanMode.shallow) + .filter!(a => a.isFile) + .map!(a => std.path.baseName(a.name)) + .array; + } + + void main(string[] args) + { + import std.stdio; + + string[] files = listdir(args[1]); + writefln("%s", files); + } +} + +@system unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map; + import std.algorithm.searching : startsWith; + import std.array : array; + import std.conv : to; + import std.path : dirEntries, buildPath, absolutePath; + import std.process : thisProcessID; + import std.range.primitives : walkLength; + + version (Android) + string testdir = deleteme; // This has to be an absolute path when + // called from a shared library on Android, + // ie an apk + else + string testdir = "deleteme.dmd.unittest.std.file" ~ to!string(thisProcessID); // needs to be relative + mkdirRecurse(buildPath(testdir, "somedir")); + scope(exit) rmdirRecurse(testdir); + write(buildPath(testdir, "somefile"), null); + write(buildPath(testdir, "somedir", "somedeepfile"), null); + + // testing range interface + size_t equalEntries(string relpath, SpanMode mode) + { + import std.exception : enforce; + auto len = enforce(walkLength(dirEntries(absolutePath(relpath), mode))); + assert(walkLength(dirEntries(relpath, mode)) == len); + assert(equal( + map!(a => absolutePath(a.name))(dirEntries(relpath, mode)), + map!(a => a.name)(dirEntries(absolutePath(relpath), mode)))); + return len; + } + + assert(equalEntries(testdir, SpanMode.shallow) == 2); + assert(equalEntries(testdir, SpanMode.depth) == 3); + assert(equalEntries(testdir, SpanMode.breadth) == 3); + + // testing opApply + foreach (string name; dirEntries(testdir, SpanMode.breadth)) + { + //writeln(name); + assert(name.startsWith(testdir)); + } + foreach (DirEntry e; dirEntries(absolutePath(testdir), SpanMode.breadth)) + { + //writeln(name); + assert(e.isFile || e.isDir, e.name); + } + + //issue 7264 + foreach (string name; dirEntries(testdir, "*.d", SpanMode.breadth)) + { + + } + foreach (entry; dirEntries(testdir, SpanMode.breadth)) + { + static assert(is(typeof(entry) == DirEntry)); + } + //issue 7138 + auto a = array(dirEntries(testdir, SpanMode.shallow)); + + // issue 11392 + auto dFiles = dirEntries(testdir, SpanMode.shallow); + foreach (d; dFiles){} + + // issue 15146 + dirEntries("", SpanMode.shallow).walkLength(); +} + +/// Ditto +auto dirEntries(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)); +} + +@system unittest +{ + import std.stdio : writefln; + immutable dpath = deleteme ~ "_dir"; + immutable fpath = deleteme ~ "_file"; + immutable sdpath = deleteme ~ "_sdir"; + immutable sfpath = deleteme ~ "_sfile"; + scope(exit) + { + if (dpath.exists) rmdirRecurse(dpath); + if (fpath.exists) remove(fpath); + if (sdpath.exists) remove(sdpath); + if (sfpath.exists) remove(sfpath); + } + + mkdir(dpath); + write(fpath, "hello world"); + version (Posix) + { + core.sys.posix.unistd.symlink((dpath ~ '\0').ptr, (sdpath ~ '\0').ptr); + core.sys.posix.unistd.symlink((fpath ~ '\0').ptr, (sfpath ~ '\0').ptr); + } + + static struct Flags { bool dir, file, link; } + auto tests = [dpath : Flags(true), fpath : Flags(false, true)]; + version (Posix) + { + tests[sdpath] = Flags(true, false, true); + tests[sfpath] = Flags(false, true, true); + } + + auto past = Clock.currTime() - 2.seconds; + auto future = past + 4.seconds; + + foreach (path, flags; tests) + { + auto de = DirEntry(path); + assert(de.name == path); + assert(de.isDir == flags.dir); + assert(de.isFile == flags.file); + assert(de.isSymlink == flags.link); + + assert(de.isDir == path.isDir); + assert(de.isFile == path.isFile); + assert(de.isSymlink == path.isSymlink); + assert(de.size == path.getSize()); + assert(de.attributes == getAttributes(path)); + assert(de.linkAttributes == getLinkAttributes(path)); + + scope(failure) writefln("[%s] [%s] [%s] [%s]", past, de.timeLastAccessed, de.timeLastModified, future); + assert(de.timeLastAccessed > past); + assert(de.timeLastAccessed < future); + assert(de.timeLastModified > past); + assert(de.timeLastModified < future); + + assert(attrIsDir(de.attributes) == flags.dir); + assert(attrIsDir(de.linkAttributes) == (flags.dir && !flags.link)); + assert(attrIsFile(de.attributes) == flags.file); + assert(attrIsFile(de.linkAttributes) == (flags.file && !flags.link)); + assert(!attrIsSymlink(de.attributes)); + assert(attrIsSymlink(de.linkAttributes) == flags.link); + + version (Windows) + { + assert(de.timeCreated > past); + assert(de.timeCreated < future); + } + else version (Posix) + { + assert(de.timeStatusChanged > past); + assert(de.timeStatusChanged < future); + assert(de.attributes == de.statBuf.st_mode); + } + } +} + + +/** + * Reads a file line by line and parses the line into a single value or a + * $(REF Tuple, std,typecons) of values depending on the length of `Types`. + * The lines are parsed using the specified format string. The format string is + * passed to $(REF formattedRead, std,_format), and therefore must conform to the + * _format string specification outlined in $(MREF std, _format). + * + * Params: + * Types = the types that each of the elements in the line should be returned as + * filename = the name of the file to read + * format = the _format string to use when reading + * + * Returns: + * If only one type is passed, then an array of that type. Otherwise, an + * array of $(REF Tuple, std,typecons)s. + * + * Throws: + * `Exception` if the format string is malformed. Also, throws `Exception` + * if any of the lines in the file are not fully consumed by the call + * to $(REF formattedRead, std,_format). Meaning that no empty lines or lines + * with extra characters are allowed. + */ +Select!(Types.length == 1, Types[0][], Tuple!(Types)[]) +slurp(Types...)(string filename, in char[] format) +{ + import std.array : appender; + import std.conv : text; + import std.exception : enforce; + import std.format : formattedRead; + import std.stdio : File; + + auto app = appender!(typeof(return))(); + ElementType!(typeof(return)) toAdd; + auto f = File(filename); + scope(exit) f.close(); + foreach (line; f.byLine()) + { + formattedRead(line, format, &toAdd); + enforce(line.empty, + text("Trailing characters at the end of line: `", line, + "'")); + app.put(toAdd); + } + return app.data; +} + +/// +@system unittest +{ + import std.typecons : tuple; + + scope(exit) + { + assert(exists(deleteme)); + remove(deleteme); + } + + write(deleteme, "12 12.25\n345 1.125"); // deleteme is the name of a temporary file + + // Load file; each line is an int followed by comma, whitespace and a + // double. + auto a = slurp!(int, double)(deleteme, "%s %s"); + assert(a.length == 2); + assert(a[0] == tuple(12, 12.25)); + assert(a[1] == tuple(345, 1.125)); +} + + +/** +Returns the path to a directory for temporary files. + +On Windows, this function returns the result of calling the Windows API function +$(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/aa364992.aspx, $(D GetTempPath)). + +On POSIX platforms, it searches through the following list of directories +and returns the first one which is found to exist: +$(OL + $(LI The directory given by the $(D TMPDIR) environment variable.) + $(LI The directory given by the $(D TEMP) environment variable.) + $(LI The directory given by the $(D TMP) environment variable.) + $(LI $(D /tmp)) + $(LI $(D /var/tmp)) + $(LI $(D /usr/tmp)) +) + +On all platforms, $(D tempDir) returns $(D ".") on failure, representing +the current working directory. + +The return value of the function is cached, so the procedures described +above will only be performed the first time the function is called. All +subsequent runs will return the same string, regardless of whether +environment variables and directory structures have changed in the +meantime. + +The POSIX $(D tempDir) algorithm is inspired by Python's +$(LINK2 http://docs.python.org/library/tempfile.html#tempfile.tempdir, $(D tempfile.tempdir)). +*/ +string tempDir() @trusted +{ + static string cache; + if (cache is null) + { + version (Windows) + { + import std.conv : to; + // http://msdn.microsoft.com/en-us/library/windows/desktop/aa364992(v=vs.85).aspx + wchar[MAX_PATH + 2] buf; + DWORD len = GetTempPathW(buf.length, buf.ptr); + if (len) cache = buf[0 .. len].to!string; + } + else version (Android) + { + // Don't check for a global temporary directory as + // Android doesn't have one. + } + else version (Posix) + { + import std.process : environment; + // This function looks through the list of alternative directories + // and returns the first one which exists and is a directory. + static string findExistingDir(T...)(lazy T alternatives) + { + foreach (dir; alternatives) + if (!dir.empty && exists(dir)) return dir; + return null; + } + + cache = findExistingDir(environment.get("TMPDIR"), + environment.get("TEMP"), + environment.get("TMP"), + "/tmp", + "/var/tmp", + "/usr/tmp"); + } + else static assert(false, "Unsupported platform"); + + if (cache is null) cache = getcwd(); + } + return cache; +} diff --git a/libphobos/src/std/format.d b/libphobos/src/std/format.d new file mode 100644 index 0000000..64b1bd3 --- /dev/null +++ b/libphobos/src/std/format.d @@ -0,0 +1,6028 @@ +// Written in the D programming language. + +/** + This module implements the formatting functionality for strings and + I/O. It's comparable to C99's $(D vsprintf()) and uses a similar + _format encoding scheme. + + For an introductory look at $(B std._format)'s capabilities and how to use + this module see the dedicated + $(LINK2 http://wiki.dlang.org/Defining_custom_print_format_specifiers, DWiki article). + + This module centers around two functions: + +$(BOOKTABLE , +$(TR $(TH Function Name) $(TH Description) +) + $(TR $(TD $(LREF formattedRead)) + $(TD Reads values according to the _format string from an InputRange. + )) + $(TR $(TD $(LREF formattedWrite)) + $(TD Formats its arguments according to the _format string and puts them + to an OutputRange. + )) +) + + Please see the documentation of function $(LREF formattedWrite) for a + description of the _format string. + + Two functions have been added for convenience: + +$(BOOKTABLE , +$(TR $(TH Function Name) $(TH Description) +) + $(TR $(TD $(LREF _format)) + $(TD Returns a GC-allocated string with the formatting result. + )) + $(TR $(TD $(LREF sformat)) + $(TD Puts the formatting result into a preallocated array. + )) +) + + These two functions are publicly imported by $(MREF std, string) + to be easily available. + + The functions $(LREF formatValue) and $(LREF unformatValue) are + used for the plumbing. + Copyright: Copyright Digital Mars 2000-2013. + + 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) + */ +module std.format; + +//debug=format; // uncomment to turn on debugging printf's + +import core.vararg; +import std.exception; +import std.meta; +import std.range.primitives; +import std.traits; + + +/********************************************************************** + * Signals a mismatch between a format and its corresponding argument. + */ +class FormatException : Exception +{ + @safe pure nothrow + this() + { + super("format error"); + } + + @safe pure nothrow + this(string msg, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null) + { + super(msg, fn, ln, next); + } +} + +private alias enforceFmt = enforceEx!FormatException; + + +/********************************************************************** + Interprets variadic argument list $(D args), formats them according + to $(D fmt), and sends the resulting characters to $(D w). The + encoding of the output is the same as $(D Char). The type $(D Writer) + must satisfy $(D $(REF isOutputRange, std,range,primitives)!(Writer, Char)). + + The variadic arguments are normally consumed in order. POSIX-style + $(HTTP opengroup.org/onlinepubs/009695399/functions/printf.html, + positional parameter syntax) is also supported. Each argument is + formatted into a sequence of chars according to the format + specification, and the characters are passed to $(D w). As many + arguments as specified in the format string are consumed and + formatted. If there are fewer arguments than format specifiers, a + $(D FormatException) is thrown. If there are more remaining arguments + than needed by the format specification, they are ignored but only + if at least one argument was formatted. + + The format string supports the formatting of array and nested array elements + via the grouping format specifiers $(B %() and $(B %)). Each + matching pair of $(B %() and $(B %)) corresponds with a single array + argument. The enclosed sub-format string is applied to individual array + elements. The trailing portion of the sub-format string following the + conversion specifier for the array element is interpreted as the array + delimiter, and is therefore omitted following the last array element. The + $(B %|) specifier may be used to explicitly indicate the start of the + delimiter, so that the preceding portion of the string will be included + following the last array element. (See below for explicit examples.) + + Params: + + w = Output is sent to this writer. Typical output writers include + $(REF Appender!string, std,array) and $(REF LockingTextWriter, std,stdio). + + fmt = Format string. + + args = Variadic argument list. + + Returns: Formatted number of arguments. + + Throws: Mismatched arguments and formats result in a $(D + FormatException) being thrown. + + Format_String: $(I Format strings) + consist of characters interspersed with $(I format + specifications). Characters are simply copied to the output (such + as putc) after any necessary conversion to the corresponding UTF-8 + sequence. + + The format string has the following grammar: + +$(PRE +$(I FormatString): + $(I FormatStringItem)* +$(I FormatStringItem): + $(B '%%') + $(B '%') $(I Position) $(I Flags) $(I Width) $(I Separator) $(I Precision) $(I FormatChar) + $(B '%$(LPAREN)') $(I FormatString) $(B '%$(RPAREN)') + $(I OtherCharacterExceptPercent) +$(I Position): + $(I empty) + $(I Integer) $(B '$') +$(I Flags): + $(I empty) + $(B '-') $(I Flags) + $(B '+') $(I Flags) + $(B '#') $(I Flags) + $(B '0') $(I Flags) + $(B ' ') $(I Flags) +$(I Width): + $(I empty) + $(I Integer) + $(B '*') +$(I Separator): + $(I empty) + $(B ',') + $(B ',') $(B '?') + $(B ',') $(B '*') $(B '?') + $(B ',') $(I Integer) $(B '?') + $(B ',') $(B '*') + $(B ',') $(I Integer) +$(I Precision): + $(I empty) + $(B '.') + $(B '.') $(I Integer) + $(B '.*') +$(I Integer): + $(I Digit) + $(I Digit) $(I Integer) +$(I Digit): + $(B '0')|$(B '1')|$(B '2')|$(B '3')|$(B '4')|$(B '5')|$(B '6')|$(B '7')|$(B '8')|$(B '9') +$(I FormatChar): + $(B 's')|$(B 'c')|$(B 'b')|$(B 'd')|$(B 'o')|$(B 'x')|$(B 'X')|$(B 'e')|$(B 'E')|$(B 'f')|$(B 'F')|$(B 'g')|$(B 'G')|$(B 'a')|$(B 'A')|$(B '|') +) + + $(BOOKTABLE Flags affect formatting depending on the specifier as + follows., $(TR $(TH Flag) $(TH Types affected) $(TH Semantics)) + + $(TR $(TD $(B '-')) $(TD numeric) $(TD Left justify the result in + the field. It overrides any $(B 0) flag.)) + + $(TR $(TD $(B '+')) $(TD numeric) $(TD Prefix positive numbers in + a signed conversion with a $(B +). It overrides any $(I space) + flag.)) + + $(TR $(TD $(B '#')) $(TD integral ($(B 'o'))) $(TD Add to + precision as necessary so that the first digit of the octal + formatting is a '0', even if both the argument and the $(I + Precision) are zero.)) + + $(TR $(TD $(B '#')) $(TD integral ($(B 'x'), $(B 'X'))) $(TD If + non-zero, prefix result with $(B 0x) ($(B 0X)).)) + + $(TR $(TD $(B '#')) $(TD floating) $(TD Always insert the decimal + point and print trailing zeros.)) + + $(TR $(TD $(B '0')) $(TD numeric) $(TD Use leading + zeros to pad rather than spaces (except for the floating point + values $(D nan) and $(D infinity)). Ignore if there's a $(I + Precision).)) + + $(TR $(TD $(B ' ')) $(TD numeric) $(TD Prefix positive + numbers in a signed conversion with a space.))) + + $(DL + $(DT $(I Width)) + $(DD + Specifies the minimum field width. + If the width is a $(B *), an additional argument of type $(B int), + preceding the actual argument, is taken as the width. + If the width is negative, it is as if the $(B -) was given + as a $(I Flags) character.) + + $(DT $(I Precision)) + $(DD Gives the precision for numeric conversions. + If the precision is a $(B *), an additional argument of type $(B int), + preceding the actual argument, is taken as the precision. + If it is negative, it is as if there was no $(I Precision) specifier.) + + $(DT $(I Separator)) + $(DD Inserts the separator symbols ',' every $(I X) digits, from right + to left, into numeric values to increase readability. + The fractional part of floating point values inserts the separator + from left to right. + Entering an integer after the ',' allows to specify $(I X). + If a '*' is placed after the ',' then $(I X) is specified by an + additional parameter to the format function. + Adding a '?' after the ',' or $(I X) specifier allows to specify + the separator character as an additional parameter. + ) + + $(DT $(I FormatChar)) + $(DD + $(DL + $(DT $(B 's')) + $(DD The corresponding argument is formatted in a manner consistent + with its type: + $(DL + $(DT $(B bool)) + $(DD The result is $(D "true") or $(D "false").) + $(DT integral types) + $(DD The $(B %d) format is used.) + $(DT floating point types) + $(DD The $(B %g) format is used.) + $(DT string types) + $(DD The result is the string converted to UTF-8. + A $(I Precision) specifies the maximum number of characters + to use in the result.) + $(DT structs) + $(DD If the struct defines a $(B toString()) method the result is + the string returned from this function. Otherwise the result is + StructName(field0, field1, ...) where + fieldn is the nth element formatted with the default + format.) + $(DT classes derived from $(B Object)) + $(DD The result is the string returned from the class instance's + $(B .toString()) method. + A $(I Precision) specifies the maximum number of characters + to use in the result.) + $(DT unions) + $(DD If the union defines a $(B toString()) method the result is + the string returned from this function. Otherwise the result is + the name of the union, without its contents.) + $(DT non-string static and dynamic arrays) + $(DD The result is [s0, s1, ...] + where sn is the nth element + formatted with the default format.) + $(DT associative arrays) + $(DD The result is the equivalent of what the initializer + would look like for the contents of the associative array, + e.g.: ["red" : 10, "blue" : 20].) + )) + + $(DT $(B 'c')) + $(DD The corresponding argument must be a character type.) + + $(DT $(B 'b','d','o','x','X')) + $(DD The corresponding argument must be an integral type + and is formatted as an integer. If the argument is a signed type + and the $(I FormatChar) is $(B d) it is converted to + a signed string of characters, otherwise it is treated as + unsigned. An argument of type $(B bool) is formatted as '1' + or '0'. The base used is binary for $(B b), octal for $(B o), + decimal + for $(B d), and hexadecimal for $(B x) or $(B X). + $(B x) formats using lower case letters, $(B X) uppercase. + If there are fewer resulting digits than the $(I Precision), + leading zeros are used as necessary. + If the $(I Precision) is 0 and the number is 0, no digits + result.) + + $(DT $(B 'e','E')) + $(DD A floating point number is formatted as one digit before + the decimal point, $(I Precision) digits after, the $(I FormatChar), + ±, followed by at least a two digit exponent: + $(I d.dddddd)e$(I ±dd). + If there is no $(I Precision), six + digits are generated after the decimal point. + If the $(I Precision) is 0, no decimal point is generated.) + + $(DT $(B 'f','F')) + $(DD A floating point number is formatted in decimal notation. + The $(I Precision) specifies the number of digits generated + after the decimal point. It defaults to six. At least one digit + is generated before the decimal point. If the $(I Precision) + is zero, no decimal point is generated.) + + $(DT $(B 'g','G')) + $(DD A floating point number is formatted in either $(B e) or + $(B f) format for $(B g); $(B E) or $(B F) format for + $(B G). + The $(B f) format is used if the exponent for an $(B e) format + is greater than -5 and less than the $(I Precision). + The $(I Precision) specifies the number of significant + digits, and defaults to six. + Trailing zeros are elided after the decimal point, if the fractional + part is zero then no decimal point is generated.) + + $(DT $(B 'a','A')) + $(DD A floating point number is formatted in hexadecimal + exponential notation 0x$(I h.hhhhhh)p$(I ±d). + There is one hexadecimal digit before the decimal point, and as + many after as specified by the $(I Precision). + If the $(I Precision) is zero, no decimal point is generated. + If there is no $(I Precision), as many hexadecimal digits as + necessary to exactly represent the mantissa are generated. + The exponent is written in as few digits as possible, + but at least one, is in decimal, and represents a power of 2 as in + $(I h.hhhhhh)*2$(I ±d). + The exponent for zero is zero. + The hexadecimal digits, x and p are in upper case if the + $(I FormatChar) is upper case.) + )) + ) + + Floating point NaN's are formatted as $(B nan) if the + $(I FormatChar) is lower case, or $(B NAN) if upper. + Floating point infinities are formatted as $(B inf) or + $(B infinity) if the + $(I FormatChar) is lower case, or $(B INF) or $(B INFINITY) if upper. + + The positional and non-positional styles can be mixed in the same + format string. (POSIX leaves this behavior undefined.) The internal + counter for non-positional parameters tracks the next parameter after + the largest positional parameter already used. + + Example using array and nested array formatting: + ------------------------- + import std.stdio; + + void main() + { + writefln("My items are %(%s %).", [1,2,3]); + writefln("My items are %(%s, %).", [1,2,3]); + } + ------------------------- + The output is: +$(CONSOLE +My items are 1 2 3. +My items are 1, 2, 3. +) + + The trailing end of the sub-format string following the specifier for each + item is interpreted as the array delimiter, and is therefore omitted + following the last array item. The $(B %|) delimiter specifier may be used + to indicate where the delimiter begins, so that the portion of the format + string prior to it will be retained in the last array element: + ------------------------- + import std.stdio; + + void main() + { + writefln("My items are %(-%s-%|, %).", [1,2,3]); + } + ------------------------- + which gives the output: +$(CONSOLE +My items are -1-, -2-, -3-. +) + + These compound format specifiers may be nested in the case of a nested + array argument: + ------------------------- + import std.stdio; + void main() { + auto mat = [[1, 2, 3], + [4, 5, 6], + [7, 8, 9]]; + + writefln("%(%(%d %)\n%)", mat); + writeln(); + + writefln("[%(%(%d %)\n %)]", mat); + writeln(); + + writefln("[%([%(%d %)]%|\n %)]", mat); + writeln(); + } + ------------------------- + The output is: +$(CONSOLE +1 2 3 +4 5 6 +7 8 9 + +[1 2 3 + 4 5 6 + 7 8 9] + +[[1 2 3] + [4 5 6] + [7 8 9]] +) + + Inside a compound format specifier, strings and characters are escaped + automatically. To avoid this behavior, add $(B '-') flag to + $(D "%$(LPAREN)"). + ------------------------- + import std.stdio; + + void main() + { + writefln("My friends are %s.", ["John", "Nancy"]); + writefln("My friends are %(%s, %).", ["John", "Nancy"]); + writefln("My friends are %-(%s, %).", ["John", "Nancy"]); + } + ------------------------- + which gives the output: +$(CONSOLE +My friends are ["John", "Nancy"]. +My friends are "John", "Nancy". +My friends are John, Nancy. +) + */ +uint formattedWrite(alias fmt, Writer, A...)(auto ref Writer w, A args) +if (isSomeString!(typeof(fmt))) +{ + alias e = checkFormatException!(fmt, A); + static assert(!e, e.msg); + return .formattedWrite(w, fmt, args); +} + +/// The format string can be checked at compile-time (see $(LREF format) for details): +@safe pure unittest +{ + import std.array : appender; + import std.format : formattedWrite; + + auto writer = appender!string(); + writer.formattedWrite!"%s is the ultimate %s."(42, "answer"); + assert(writer.data == "42 is the ultimate answer."); + + // Clear the writer + writer = appender!string(); + formattedWrite(writer, "Date: %2$s %1$s", "October", 5); + assert(writer.data == "Date: 5 October"); +} + +/// ditto +uint formattedWrite(Writer, Char, A...)(auto ref Writer w, in Char[] fmt, A args) +{ + import std.conv : text; + + auto spec = FormatSpec!Char(fmt); + + // Are we already done with formats? Then just dump each parameter in turn + uint currentArg = 0; + while (spec.writeUpToNextSpec(w)) + { + if (currentArg == A.length && !spec.indexStart) + { + // leftover spec? + enforceFmt(fmt.length == 0, + text("Orphan format specifier: %", spec.spec)); + break; + } + + if (spec.width == spec.DYNAMIC) + { + auto width = getNthInt!"integer width"(currentArg, args); + if (width < 0) + { + spec.flDash = true; + width = -width; + } + spec.width = width; + ++currentArg; + } + else if (spec.width < 0) + { + // means: get width as a positional parameter + auto index = cast(uint) -spec.width; + assert(index > 0); + auto width = getNthInt!"integer width"(index - 1, args); + if (currentArg < index) currentArg = index; + if (width < 0) + { + spec.flDash = true; + width = -width; + } + spec.width = width; + } + + if (spec.precision == spec.DYNAMIC) + { + auto precision = getNthInt!"integer precision"(currentArg, args); + if (precision >= 0) spec.precision = precision; + // else negative precision is same as no precision + else spec.precision = spec.UNSPECIFIED; + ++currentArg; + } + else if (spec.precision < 0) + { + // means: get precision as a positional parameter + auto index = cast(uint) -spec.precision; + assert(index > 0); + auto precision = getNthInt!"integer precision"(index- 1, args); + if (currentArg < index) currentArg = index; + if (precision >= 0) spec.precision = precision; + // else negative precision is same as no precision + else spec.precision = spec.UNSPECIFIED; + } + + if (spec.separators == spec.DYNAMIC) + { + auto separators = getNthInt!"separator digit width"(currentArg, args); + spec.separators = separators; + ++currentArg; + } + + if (spec.separatorCharPos == spec.DYNAMIC) + { + auto separatorChar = + getNth!("separator character", isSomeChar, dchar)(currentArg, args); + spec.separatorChar = separatorChar; + ++currentArg; + } + + if (currentArg == A.length && !spec.indexStart) + { + // leftover spec? + enforceFmt(fmt.length == 0, + text("Orphan format specifier: %", spec.spec)); + break; + } + + // Format an argument + // This switch uses a static foreach to generate a jump table. + // Currently `spec.indexStart` use the special value '0' to signal + // we should use the current argument. An enhancement would be to + // always store the index. + size_t index = currentArg; + if (spec.indexStart != 0) + index = spec.indexStart - 1; + else + ++currentArg; + SWITCH: switch (index) + { + foreach (i, Tunused; A) + { + case i: + formatValue(w, args[i], spec); + if (currentArg < spec.indexEnd) + currentArg = spec.indexEnd; + // A little know feature of format is to format a range + // of arguments, e.g. `%1:3$` will format the first 3 + // arguments. Since they have to be consecutive we can + // just use explicit fallthrough to cover that case. + if (i + 1 < spec.indexEnd) + { + // You cannot goto case if the next case is the default + static if (i + 1 < A.length) + goto case; + else + goto default; + } + else + break SWITCH; + } + default: + throw new FormatException( + text("Positional specifier %", spec.indexStart, '$', spec.spec, + " index exceeds ", A.length)); + } + } + return currentArg; +} + +/// +@safe unittest +{ + assert(format("%,d", 1000) == "1,000"); + assert(format("%,f", 1234567.891011) == "1,234,567.891,011"); + assert(format("%,?d", '?', 1000) == "1?000"); + assert(format("%,1d", 1000) == "1,0,0,0", format("%,1d", 1000)); + assert(format("%,*d", 4, -12345) == "-1,2345"); + assert(format("%,*?d", 4, '_', -12345) == "-1_2345"); + assert(format("%,6?d", '_', -12345678) == "-12_345678"); + assert(format("%12,3.3f", 1234.5678) == " 1,234.568", "'" ~ + format("%12,3.3f", 1234.5678) ~ "'"); +} + +@safe pure unittest +{ + import std.array; + auto w = appender!string(); + formattedWrite(w, "%s %d", "@safe/pure", 42); + assert(w.data == "@safe/pure 42"); +} + +/** +Reads characters from input range $(D r), converts them according +to $(D fmt), and writes them to $(D args). + +Params: + r = The range to read from. + fmt = The format of the data to read. + args = The drain of the data read. + +Returns: + +On success, the function returns the number of variables filled. This count +can match the expected number of readings or fewer, even zero, if a +matching failure happens. + +Throws: + An `Exception` if `S.length == 0` and `fmt` has format specifiers. + */ +uint formattedRead(alias fmt, R, S...)(ref R r, auto ref S args) +if (isSomeString!(typeof(fmt))) +{ + alias e = checkFormatException!(fmt, S); + static assert(!e, e.msg); + return .formattedRead(r, fmt, args); +} + +/// ditto +uint formattedRead(R, Char, S...)(ref R r, const(Char)[] fmt, auto ref S args) +{ + import std.typecons : isTuple; + + auto spec = FormatSpec!Char(fmt); + static if (!S.length) + { + spec.readUpToNextSpec(r); + enforce(spec.trailing.empty, "Trailing characters in formattedRead format string"); + return 0; + } + else + { + enum hasPointer = isPointer!(typeof(args[0])); + + // The function below accounts for '*' == fields meant to be + // read and skipped + void skipUnstoredFields() + { + for (;;) + { + spec.readUpToNextSpec(r); + if (spec.width != spec.DYNAMIC) break; + // must skip this field + skipData(r, spec); + } + } + + skipUnstoredFields(); + if (r.empty) + { + // Input is empty, nothing to read + return 0; + } + static if (hasPointer) + alias A = typeof(*args[0]); + else + alias A = typeof(args[0]); + + static if (isTuple!A) + { + foreach (i, T; A.Types) + { + static if (hasPointer) + (*args[0])[i] = unformatValue!(T)(r, spec); + else + args[0][i] = unformatValue!(T)(r, spec); + skipUnstoredFields(); + } + } + else + { + static if (hasPointer) + *args[0] = unformatValue!(A)(r, spec); + else + args[0] = unformatValue!(A)(r, spec); + } + return 1 + formattedRead(r, spec.trailing, args[1 .. $]); + } +} + +/// The format string can be checked at compile-time (see $(LREF format) for details): +@safe pure unittest +{ + string s = "hello!124:34.5"; + string a; + int b; + double c; + s.formattedRead!"%s!%s:%s"(a, b, c); + assert(a == "hello" && b == 124 && c == 34.5); +} + +@safe unittest +{ + import std.math; + string s = " 1.2 3.4 "; + double x, y, z; + assert(formattedRead(s, " %s %s %s ", x, y, z) == 2); + assert(s.empty); + assert(approxEqual(x, 1.2)); + assert(approxEqual(y, 3.4)); + assert(isNaN(z)); +} + +// for backwards compatibility +@system pure unittest +{ + string s = "hello!124:34.5"; + string a; + int b; + double c; + formattedRead(s, "%s!%s:%s", &a, &b, &c); + assert(a == "hello" && b == 124 && c == 34.5); + + // mix pointers and auto-ref + s = "world!200:42.25"; + formattedRead(s, "%s!%s:%s", a, &b, &c); + assert(a == "world" && b == 200 && c == 42.25); + + s = "world1!201:42.5"; + formattedRead(s, "%s!%s:%s", &a, &b, c); + assert(a == "world1" && b == 201 && c == 42.5); + + s = "world2!202:42.75"; + formattedRead(s, "%s!%s:%s", a, b, &c); + assert(a == "world2" && b == 202 && c == 42.75); +} + +// for backwards compatibility +@system pure unittest +{ + import std.math; + string s = " 1.2 3.4 "; + double x, y, z; + assert(formattedRead(s, " %s %s %s ", &x, &y, &z) == 2); + assert(s.empty); + assert(approxEqual(x, 1.2)); + assert(approxEqual(y, 3.4)); + assert(isNaN(z)); +} + +@system pure unittest +{ + string line; + + bool f1; + + line = "true"; + formattedRead(line, "%s", &f1); + assert(f1); + + line = "TrUE"; + formattedRead(line, "%s", &f1); + assert(f1); + + line = "false"; + formattedRead(line, "%s", &f1); + assert(!f1); + + line = "fALsE"; + formattedRead(line, "%s", &f1); + assert(!f1); + + line = "1"; + formattedRead(line, "%d", &f1); + assert(f1); + + line = "-1"; + formattedRead(line, "%d", &f1); + assert(f1); + + line = "0"; + formattedRead(line, "%d", &f1); + assert(!f1); + + line = "-0"; + formattedRead(line, "%d", &f1); + assert(!f1); +} + +@system pure unittest +{ + union B + { + char[int.sizeof] untyped; + int typed; + } + B b; + b.typed = 5; + char[] input = b.untyped[]; + int witness; + formattedRead(input, "%r", &witness); + assert(witness == b.typed); +} + +@system pure unittest +{ + union A + { + char[float.sizeof] untyped; + float typed; + } + A a; + a.typed = 5.5; + char[] input = a.untyped[]; + float witness; + formattedRead(input, "%r", &witness); + assert(witness == a.typed); +} + +@system pure unittest +{ + import std.typecons; + char[] line = "1 2".dup; + int a, b; + formattedRead(line, "%s %s", &a, &b); + assert(a == 1 && b == 2); + + line = "10 2 3".dup; + formattedRead(line, "%d ", &a); + assert(a == 10); + assert(line == "2 3"); + + Tuple!(int, float) t; + line = "1 2.125".dup; + formattedRead(line, "%d %g", &t); + assert(t[0] == 1 && t[1] == 2.125); + + line = "1 7643 2.125".dup; + formattedRead(line, "%s %*u %s", &t); + assert(t[0] == 1 && t[1] == 2.125); +} + +@system pure unittest +{ + string line; + + char c1, c2; + + line = "abc"; + formattedRead(line, "%s%c", &c1, &c2); + assert(c1 == 'a' && c2 == 'b'); + assert(line == "c"); +} + +@system pure unittest +{ + string line; + + line = "[1,2,3]"; + int[] s1; + formattedRead(line, "%s", &s1); + assert(s1 == [1,2,3]); +} + +@system pure unittest +{ + string line; + + line = "[1,2,3]"; + int[] s1; + formattedRead(line, "[%(%s,%)]", &s1); + assert(s1 == [1,2,3]); + + line = `["hello", "world"]`; + string[] s2; + formattedRead(line, "[%(%s, %)]", &s2); + assert(s2 == ["hello", "world"]); + + line = "123 456"; + int[] s3; + formattedRead(line, "%(%s %)", &s3); + assert(s3 == [123, 456]); + + line = "h,e,l,l,o; w,o,r,l,d"; + string[] s4; + formattedRead(line, "%(%(%c,%); %)", &s4); + assert(s4 == ["hello", "world"]); +} + +@system pure unittest +{ + string line; + + int[4] sa1; + line = `[1,2,3,4]`; + formattedRead(line, "%s", &sa1); + assert(sa1 == [1,2,3,4]); + + int[4] sa2; + line = `[1,2,3]`; + assertThrown(formattedRead(line, "%s", &sa2)); + + int[4] sa3; + line = `[1,2,3,4,5]`; + assertThrown(formattedRead(line, "%s", &sa3)); +} + +@system pure unittest +{ + string input; + + int[4] sa1; + input = `[1,2,3,4]`; + formattedRead(input, "[%(%s,%)]", &sa1); + assert(sa1 == [1,2,3,4]); + + int[4] sa2; + input = `[1,2,3]`; + assertThrown(formattedRead(input, "[%(%s,%)]", &sa2)); +} + +@system pure unittest +{ + string line; + + string s1, s2; + + line = "hello, world"; + formattedRead(line, "%s", &s1); + assert(s1 == "hello, world", s1); + + line = "hello, world;yah"; + formattedRead(line, "%s;%s", &s1, &s2); + assert(s1 == "hello, world", s1); + assert(s2 == "yah", s2); + + line = `['h','e','l','l','o']`; + string s3; + formattedRead(line, "[%(%s,%)]", &s3); + assert(s3 == "hello"); + + line = `"hello"`; + string s4; + formattedRead(line, "\"%(%c%)\"", &s4); + assert(s4 == "hello"); +} + +@system pure unittest +{ + string line; + + string[int] aa1; + line = `[1:"hello", 2:"world"]`; + formattedRead(line, "%s", &aa1); + assert(aa1 == [1:"hello", 2:"world"]); + + int[string] aa2; + line = `{"hello"=1; "world"=2}`; + formattedRead(line, "{%(%s=%s; %)}", &aa2); + assert(aa2 == ["hello":1, "world":2]); + + int[string] aa3; + line = `{[hello=1]; [world=2]}`; + formattedRead(line, "{%([%(%c%)=%s]%|; %)}", &aa3); + assert(aa3 == ["hello":1, "world":2]); +} + +template FormatSpec(Char) +if (!is(Unqual!Char == Char)) +{ + alias FormatSpec = FormatSpec!(Unqual!Char); +} + +/** + * A General handler for $(D printf) style format specifiers. Used for building more + * specific formatting functions. + */ +struct FormatSpec(Char) +if (is(Unqual!Char == Char)) +{ + import std.algorithm.searching : startsWith; + import std.ascii : isDigit, isPunctuation, isAlpha; + import std.conv : parse, text, to; + + /** + Minimum _width, default $(D 0). + */ + int width = 0; + + /** + Precision. Its semantics depends on the argument type. For + floating point numbers, _precision dictates the number of + decimals printed. + */ + int precision = UNSPECIFIED; + + /** + Number of digits printed between _separators. + */ + int separators = UNSPECIFIED; + + /** + Set to `DYNAMIC` when the separator character is supplied at runtime. + */ + int separatorCharPos = UNSPECIFIED; + + /** + Character to insert between digits. + */ + dchar separatorChar = ','; + + /** + Special value for width and precision. $(D DYNAMIC) width or + precision means that they were specified with $(D '*') in the + format string and are passed at runtime through the varargs. + */ + enum int DYNAMIC = int.max; + + /** + Special value for precision, meaning the format specifier + contained no explicit precision. + */ + enum int UNSPECIFIED = DYNAMIC - 1; + + /** + The actual format specifier, $(D 's') by default. + */ + char spec = 's'; + + /** + Index of the argument for positional parameters, from $(D 1) to + $(D ubyte.max). ($(D 0) means not used). + */ + ubyte indexStart; + + /** + Index of the last argument for positional parameter range, from + $(D 1) to $(D ubyte.max). ($(D 0) means not used). + */ + ubyte indexEnd; + + version (StdDdoc) + { + /** + The format specifier contained a $(D '-') ($(D printf) + compatibility). + */ + bool flDash; + + /** + The format specifier contained a $(D '0') ($(D printf) + compatibility). + */ + bool flZero; + + /** + The format specifier contained a $(D ' ') ($(D printf) + compatibility). + */ + bool flSpace; + + /** + The format specifier contained a $(D '+') ($(D printf) + compatibility). + */ + bool flPlus; + + /** + The format specifier contained a $(D '#') ($(D printf) + compatibility). + */ + bool flHash; + + /** + The format specifier contained a $(D ',') + */ + bool flSeparator; + + // Fake field to allow compilation + ubyte allFlags; + } + else + { + union + { + import std.bitmanip : bitfields; + mixin(bitfields!( + bool, "flDash", 1, + bool, "flZero", 1, + bool, "flSpace", 1, + bool, "flPlus", 1, + bool, "flHash", 1, + bool, "flSeparator", 1, + ubyte, "", 2)); + ubyte allFlags; + } + } + + /** + In case of a compound format specifier starting with $(D + "%$(LPAREN)") and ending with $(D "%$(RPAREN)"), $(D _nested) + contains the string contained within the two separators. + */ + const(Char)[] nested; + + /** + In case of a compound format specifier, $(D _sep) contains the + string positioning after $(D "%|"). + `sep is null` means no separator else `sep.empty` means 0 length + separator. + */ + const(Char)[] sep; + + /** + $(D _trailing) contains the rest of the format string. + */ + const(Char)[] trailing; + + /* + This string is inserted before each sequence (e.g. array) + formatted (by default $(D "[")). + */ + enum immutable(Char)[] seqBefore = "["; + + /* + This string is inserted after each sequence formatted (by + default $(D "]")). + */ + enum immutable(Char)[] seqAfter = "]"; + + /* + This string is inserted after each element keys of a sequence (by + default $(D ":")). + */ + enum immutable(Char)[] keySeparator = ":"; + + /* + This string is inserted in between elements of a sequence (by + default $(D ", ")). + */ + enum immutable(Char)[] seqSeparator = ", "; + + /** + Construct a new $(D FormatSpec) using the format string $(D fmt), no + processing is done until needed. + */ + this(in Char[] fmt) @safe pure + { + trailing = fmt; + } + + bool writeUpToNextSpec(OutputRange)(ref OutputRange writer) + { + if (trailing.empty) + return false; + for (size_t i = 0; i < trailing.length; ++i) + { + if (trailing[i] != '%') continue; + put(writer, trailing[0 .. i]); + trailing = trailing[i .. $]; + enforceFmt(trailing.length >= 2, `Unterminated format specifier: "%"`); + trailing = trailing[1 .. $]; + + if (trailing[0] != '%') + { + // Spec found. Fill up the spec, and bailout + fillUp(); + return true; + } + // Doubled! Reset and Keep going + i = 0; + } + // no format spec found + put(writer, trailing); + trailing = null; + return false; + } + + @safe unittest + { + import std.array; + auto w = appender!(char[])(); + auto f = FormatSpec("abc%sdef%sghi"); + f.writeUpToNextSpec(w); + assert(w.data == "abc", w.data); + assert(f.trailing == "def%sghi", text(f.trailing)); + f.writeUpToNextSpec(w); + assert(w.data == "abcdef", w.data); + assert(f.trailing == "ghi"); + // test with embedded %%s + f = FormatSpec("ab%%cd%%ef%sg%%h%sij"); + w.clear(); + f.writeUpToNextSpec(w); + assert(w.data == "ab%cd%ef" && f.trailing == "g%%h%sij", w.data); + f.writeUpToNextSpec(w); + assert(w.data == "ab%cd%efg%h" && f.trailing == "ij"); + // bug4775 + f = FormatSpec("%%%s"); + w.clear(); + f.writeUpToNextSpec(w); + assert(w.data == "%" && f.trailing == ""); + f = FormatSpec("%%%%%s%%"); + w.clear(); + while (f.writeUpToNextSpec(w)) continue; + assert(w.data == "%%%"); + + f = FormatSpec("a%%b%%c%"); + w.clear(); + assertThrown!FormatException(f.writeUpToNextSpec(w)); + assert(w.data == "a%b%c" && f.trailing == "%"); + } + + private void fillUp() + { + // Reset content + if (__ctfe) + { + flDash = false; + flZero = false; + flSpace = false; + flPlus = false; + flHash = false; + flSeparator = false; + } + else + { + allFlags = 0; + } + + width = 0; + precision = UNSPECIFIED; + nested = null; + // Parse the spec (we assume we're past '%' already) + for (size_t i = 0; i < trailing.length; ) + { + switch (trailing[i]) + { + case '(': + // Embedded format specifier. + auto j = i + 1; + // Get the matching balanced paren + for (uint innerParens;;) + { + enforceFmt(j + 1 < trailing.length, + text("Incorrect format specifier: %", trailing[i .. $])); + if (trailing[j++] != '%') + { + // skip, we're waiting for %( and %) + continue; + } + if (trailing[j] == '-') // for %-( + { + ++j; // skip + enforceFmt(j < trailing.length, + text("Incorrect format specifier: %", trailing[i .. $])); + } + if (trailing[j] == ')') + { + if (innerParens-- == 0) break; + } + else if (trailing[j] == '|') + { + if (innerParens == 0) break; + } + else if (trailing[j] == '(') + { + ++innerParens; + } + } + if (trailing[j] == '|') + { + auto k = j; + for (++j;;) + { + if (trailing[j++] != '%') + continue; + if (trailing[j] == '%') + ++j; + else if (trailing[j] == ')') + break; + else + throw new Exception( + text("Incorrect format specifier: %", + trailing[j .. $])); + } + nested = trailing[i + 1 .. k - 1]; + sep = trailing[k + 1 .. j - 1]; + } + else + { + nested = trailing[i + 1 .. j - 1]; + sep = null; // no separator + } + //this = FormatSpec(innerTrailingSpec); + spec = '('; + // We practically found the format specifier + trailing = trailing[j + 1 .. $]; + return; + case '-': flDash = true; ++i; break; + case '+': flPlus = true; ++i; break; + case '#': flHash = true; ++i; break; + case '0': flZero = true; ++i; break; + case ' ': flSpace = true; ++i; break; + case '*': + if (isDigit(trailing[++i])) + { + // a '*' followed by digits and '$' is a + // positional format + trailing = trailing[1 .. $]; + width = -parse!(typeof(width))(trailing); + i = 0; + enforceFmt(trailing[i++] == '$', + "$ expected"); + } + else + { + // read result + width = DYNAMIC; + } + break; + case '1': .. case '9': + auto tmp = trailing[i .. $]; + const widthOrArgIndex = parse!uint(tmp); + enforceFmt(tmp.length, + text("Incorrect format specifier %", trailing[i .. $])); + i = arrayPtrDiff(tmp, trailing); + if (tmp.startsWith('$')) + { + // index of the form %n$ + indexEnd = indexStart = to!ubyte(widthOrArgIndex); + ++i; + } + else if (tmp.startsWith(':')) + { + // two indexes of the form %m:n$, or one index of the form %m:$ + indexStart = to!ubyte(widthOrArgIndex); + tmp = tmp[1 .. $]; + if (tmp.startsWith('$')) + { + indexEnd = indexEnd.max; + } + else + { + indexEnd = parse!(typeof(indexEnd))(tmp); + } + i = arrayPtrDiff(tmp, trailing); + enforceFmt(trailing[i++] == '$', + "$ expected"); + } + else + { + // width + width = to!int(widthOrArgIndex); + } + break; + case ',': + // Precision + ++i; + flSeparator = true; + + if (trailing[i] == '*') + { + ++i; + // read result + separators = DYNAMIC; + } + else if (isDigit(trailing[i])) + { + auto tmp = trailing[i .. $]; + separators = parse!int(tmp); + i = arrayPtrDiff(tmp, trailing); + } + else + { + // "," was specified, but nothing after it + separators = 3; + } + + if (trailing[i] == '?') + { + separatorCharPos = DYNAMIC; + ++i; + } + + break; + case '.': + // Precision + if (trailing[++i] == '*') + { + if (isDigit(trailing[++i])) + { + // a '.*' followed by digits and '$' is a + // positional precision + trailing = trailing[i .. $]; + i = 0; + precision = -parse!int(trailing); + enforceFmt(trailing[i++] == '$', + "$ expected"); + } + else + { + // read result + precision = DYNAMIC; + } + } + else if (trailing[i] == '-') + { + // negative precision, as good as 0 + precision = 0; + auto tmp = trailing[i .. $]; + parse!int(tmp); // skip digits + i = arrayPtrDiff(tmp, trailing); + } + else if (isDigit(trailing[i])) + { + auto tmp = trailing[i .. $]; + precision = parse!int(tmp); + i = arrayPtrDiff(tmp, trailing); + } + else + { + // "." was specified, but nothing after it + precision = 0; + } + break; + default: + // this is the format char + spec = cast(char) trailing[i++]; + trailing = trailing[i .. $]; + return; + } // end switch + } // end for + throw new Exception(text("Incorrect format specifier: ", trailing)); + } + + //-------------------------------------------------------------------------- + private bool readUpToNextSpec(R)(ref R r) + { + import std.ascii : isLower, isWhite; + import std.utf : stride; + + // Reset content + if (__ctfe) + { + flDash = false; + flZero = false; + flSpace = false; + flPlus = false; + flHash = false; + flSeparator = false; + } + else + { + allFlags = 0; + } + width = 0; + precision = UNSPECIFIED; + nested = null; + // Parse the spec + while (trailing.length) + { + const c = trailing[0]; + if (c == '%' && trailing.length > 1) + { + const c2 = trailing[1]; + if (c2 == '%') + { + assert(!r.empty); + // Require a '%' + if (r.front != '%') break; + trailing = trailing[2 .. $]; + r.popFront(); + } + else + { + enforce(isLower(c2) || c2 == '*' || + c2 == '(', + text("'%", c2, + "' not supported with formatted read")); + trailing = trailing[1 .. $]; + fillUp(); + return true; + } + } + else + { + if (c == ' ') + { + while (!r.empty && isWhite(r.front)) r.popFront(); + //r = std.algorithm.find!(not!(isWhite))(r); + } + else + { + enforce(!r.empty, + text("parseToFormatSpec: Cannot find character '", + c, "' in the input string.")); + if (r.front != trailing.front) break; + r.popFront(); + } + trailing = trailing[stride(trailing, 0) .. $]; + } + } + return false; + } + + private string getCurFmtStr() const + { + import std.array : appender; + auto w = appender!string(); + auto f = FormatSpec!Char("%s"); // for stringnize + + put(w, '%'); + if (indexStart != 0) + { + formatValue(w, indexStart, f); + put(w, '$'); + } + if (flDash) put(w, '-'); + if (flZero) put(w, '0'); + if (flSpace) put(w, ' '); + if (flPlus) put(w, '+'); + if (flHash) put(w, '#'); + if (flSeparator) put(w, ','); + if (width != 0) + formatValue(w, width, f); + if (precision != FormatSpec!Char.UNSPECIFIED) + { + put(w, '.'); + formatValue(w, precision, f); + } + put(w, spec); + return w.data; + } + + @safe unittest + { + // issue 5237 + import std.array; + auto w = appender!string(); + auto f = FormatSpec!char("%.16f"); + f.writeUpToNextSpec(w); // dummy eating + assert(f.spec == 'f'); + auto fmt = f.getCurFmtStr(); + assert(fmt == "%.16f"); + } + + private const(Char)[] headUpToNextSpec() + { + import std.array : appender; + auto w = appender!(typeof(return))(); + auto tr = trailing; + + while (tr.length) + { + if (tr[0] == '%') + { + if (tr.length > 1 && tr[1] == '%') + { + tr = tr[2 .. $]; + w.put('%'); + } + else + break; + } + else + { + w.put(tr.front); + tr.popFront(); + } + } + return w.data; + } + + string toString() + { + return text("address = ", cast(void*) &this, + "\nwidth = ", width, + "\nprecision = ", precision, + "\nspec = ", spec, + "\nindexStart = ", indexStart, + "\nindexEnd = ", indexEnd, + "\nflDash = ", flDash, + "\nflZero = ", flZero, + "\nflSpace = ", flSpace, + "\nflPlus = ", flPlus, + "\nflHash = ", flHash, + "\nflSeparator = ", flSeparator, + "\nnested = ", nested, + "\ntrailing = ", trailing, "\n"); + } +} + +/// +@safe pure unittest +{ + import std.array; + auto a = appender!(string)(); + auto fmt = "Number: %2.4e\nString: %s"; + auto f = FormatSpec!char(fmt); + + f.writeUpToNextSpec(a); + + assert(a.data == "Number: "); + assert(f.trailing == "\nString: %s"); + assert(f.spec == 'e'); + assert(f.width == 2); + assert(f.precision == 4); + + f.writeUpToNextSpec(a); + + assert(a.data == "Number: \nString: "); + assert(f.trailing == ""); + assert(f.spec == 's'); +} + +// Issue 14059 +@safe unittest +{ + import std.array : appender; + auto a = appender!(string)(); + + auto f = FormatSpec!char("%-(%s%"); // %)") + assertThrown(f.writeUpToNextSpec(a)); + + f = FormatSpec!char("%(%-"); // %)") + assertThrown(f.writeUpToNextSpec(a)); +} + +@safe unittest +{ + import std.array : appender; + auto a = appender!(string)(); + + auto f = FormatSpec!char("%,d"); + f.writeUpToNextSpec(a); + + assert(f.spec == 'd', format("%s", f.spec)); + assert(f.precision == FormatSpec!char.UNSPECIFIED); + assert(f.separators == 3); + + f = FormatSpec!char("%5,10f"); + f.writeUpToNextSpec(a); + assert(f.spec == 'f', format("%s", f.spec)); + assert(f.separators == 10); + assert(f.width == 5); + + f = FormatSpec!char("%5,10.4f"); + f.writeUpToNextSpec(a); + assert(f.spec == 'f', format("%s", f.spec)); + assert(f.separators == 10); + assert(f.width == 5); + assert(f.precision == 4); +} + +/** +Helper function that returns a $(D FormatSpec) for a single specifier given +in $(D fmt). + +Params: + fmt = A format specifier. + +Returns: + A $(D FormatSpec) with the specifier parsed. +Throws: + An `Exception` when more than one specifier is given or the specifier + is malformed. + */ +FormatSpec!Char singleSpec(Char)(Char[] fmt) +{ + import std.conv : text; + enforce(fmt.length >= 2, "fmt must be at least 2 characters long"); + enforce(fmt.front == '%', "fmt must start with a '%' character"); + + static struct DummyOutputRange { + void put(C)(C[] buf) {} // eat elements + } + auto a = DummyOutputRange(); + auto spec = FormatSpec!Char(fmt); + //dummy write + spec.writeUpToNextSpec(a); + + enforce(spec.trailing.empty, + text("Trailing characters in fmt string: '", spec.trailing)); + + return spec; +} + +/// +@safe pure unittest +{ + import std.exception : assertThrown; + auto spec = singleSpec("%2.3e"); + + assert(spec.trailing == ""); + assert(spec.spec == 'e'); + assert(spec.width == 2); + assert(spec.precision == 3); + + assertThrown(singleSpec("")); + assertThrown(singleSpec("2.3e")); + assertThrown(singleSpec("%2.3eTest")); +} + +/** +$(D bool)s are formatted as "true" or "false" with %s and as "1" or +"0" with integral-specific format specs. + +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. + */ +void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f) +if (is(BooleanTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) +{ + BooleanTypeOf!T val = obj; + + if (f.spec == 's') + { + string s = val ? "true" : "false"; + if (!f.flDash) + { + // right align + if (f.width > s.length) + foreach (i ; 0 .. f.width - s.length) put(w, ' '); + put(w, s); + } + else + { + // left align + put(w, s); + if (f.width > s.length) + foreach (i ; 0 .. f.width - s.length) put(w, ' '); + } + } + else + formatValue(w, cast(int) val, f); +} + +/// +@safe pure unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%s"); + formatValue(w, true, spec); + + assert(w.data == "true"); +} + +@safe pure unittest +{ + assertCTFEable!( + { + formatTest( false, "false" ); + formatTest( true, "true" ); + }); +} +@system unittest +{ + class C1 { bool val; alias val this; this(bool v){ val = v; } } + class C2 { bool val; alias val this; this(bool v){ val = v; } + override string toString() const { return "C"; } } + formatTest( new C1(false), "false" ); + formatTest( new C1(true), "true" ); + formatTest( new C2(false), "C" ); + formatTest( new C2(true), "C" ); + + struct S1 { bool val; alias val this; } + struct S2 { bool val; alias val this; + string toString() const { return "S"; } } + formatTest( S1(false), "false" ); + formatTest( S1(true), "true" ); + formatTest( S2(false), "S" ); + formatTest( S2(true), "S" ); +} + +@safe pure unittest +{ + string t1 = format("[%6s] [%6s] [%-6s]", true, false, true); + assert(t1 == "[ true] [ false] [true ]"); + + string t2 = format("[%3s] [%-2s]", true, false); + assert(t2 == "[true] [false]"); +} + +/** +$(D null) literal is formatted as $(D "null"). + +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. + */ +void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f) +if (is(Unqual!T == typeof(null)) && !is(T == enum) && !hasToString!(T, Char)) +{ + enforceFmt(f.spec == 's', + "null literal cannot match %" ~ f.spec); + + put(w, "null"); +} + +/// +@safe pure unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%s"); + formatValue(w, null, spec); + + assert(w.data == "null"); +} + +@safe pure unittest +{ + assert(collectExceptionMsg!FormatException(format("%p", null)).back == 'p'); + + assertCTFEable!( + { + formatTest( null, "null" ); + }); +} + +/** +Integrals are formatted like $(D printf) does. + +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. + */ +void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f) +if (is(IntegralTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) +{ + alias U = IntegralTypeOf!T; + U val = obj; // Extracting alias this may be impure/system/may-throw + + if (f.spec == 'r') + { + // raw write, skip all else and write the thing + auto raw = (ref val)@trusted{ + return (cast(const char*) &val)[0 .. val.sizeof]; + }(val); + if (needToSwapEndianess(f)) + { + foreach_reverse (c; raw) + put(w, c); + } + else + { + foreach (c; raw) + put(w, c); + } + return; + } + + immutable uint base = + f.spec == 'x' || f.spec == 'X' ? 16 : + f.spec == 'o' ? 8 : + f.spec == 'b' ? 2 : + f.spec == 's' || f.spec == 'd' || f.spec == 'u' ? 10 : + 0; + enforceFmt(base > 0, + "incompatible format character for integral argument: %" ~ f.spec); + + // Forward on to formatIntegral to handle both U and const(U) + // Saves duplication of code for both versions. + static if (is(ucent) && (is(U == cent) || is(U == ucent))) + alias C = U; + else static if (isSigned!U) + alias C = long; + else + alias C = ulong; + formatIntegral(w, cast(C) val, f, base, Unsigned!U.max); +} + +/// +@safe pure unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%d"); + formatValue(w, 1337, spec); + + assert(w.data == "1337"); +} + +private void formatIntegral(Writer, T, Char)(ref Writer w, const(T) val, const ref FormatSpec!Char fs, + uint base, ulong mask) +{ + T arg = val; + + immutable negative = (base == 10 && arg < 0); + if (negative) + { + arg = -arg; + } + + // All unsigned integral types should fit in ulong. + static if (is(ucent) && is(typeof(arg) == ucent)) + formatUnsigned(w, (cast(ucent) arg) & mask, fs, base, negative); + else + formatUnsigned(w, (cast(ulong) arg) & mask, fs, base, negative); +} + +private void formatUnsigned(Writer, T, Char) +(ref Writer w, T arg, const ref FormatSpec!Char fs, uint base, bool negative) +{ + /* Write string: + * leftpad prefix1 prefix2 zerofill digits rightpad + */ + + /* Convert arg to digits[]. + * Note that 0 becomes an empty digits[] + */ + char[64] buffer = void; // 64 bits in base 2 at most + char[] digits; + if (arg < base && base <= 10 && arg) + { + // Most numbers are a single digit - avoid expensive divide + buffer[0] = cast(char)(arg + '0'); + digits = buffer[0 .. 1]; + } + else + { + size_t i = buffer.length; + while (arg) + { + --i; + char c = cast(char) (arg % base); + arg /= base; + if (c < 10) + buffer[i] = cast(char)(c + '0'); + else + buffer[i] = cast(char)(c + (fs.spec == 'x' ? 'a' - 10 : 'A' - 10)); + } + digits = buffer[i .. $]; // got the digits without the sign + } + + + immutable precision = (fs.precision == fs.UNSPECIFIED) ? 1 : fs.precision; + + char padChar = 0; + if (!fs.flDash) + { + padChar = (fs.flZero && fs.precision == fs.UNSPECIFIED) ? '0' : ' '; + } + + // Compute prefix1 and prefix2 + char prefix1 = 0; + char prefix2 = 0; + if (base == 10) + { + if (negative) + prefix1 = '-'; + else if (fs.flPlus) + prefix1 = '+'; + else if (fs.flSpace) + prefix1 = ' '; + } + else if (base == 16 && fs.flHash && digits.length) + { + prefix1 = '0'; + prefix2 = fs.spec == 'x' ? 'x' : 'X'; + } + // adjust precision to print a '0' for octal if alternate format is on + else if (base == 8 && fs.flHash && + (precision <= 1 || precision <= digits.length) && // too low precision + digits.length > 0) + prefix1 = '0'; + + size_t zerofill = precision > digits.length ? precision - digits.length : 0; + size_t leftpad = 0; + size_t rightpad = 0; + + immutable ptrdiff_t spacesToPrint = + fs.width - ( + (prefix1 != 0) + + (prefix2 != 0) + + zerofill + + digits.length + + ((fs.flSeparator != 0) * (digits.length / fs.separators)) + ); + if (spacesToPrint > 0) // need to do some padding + { + if (padChar == '0') + zerofill += spacesToPrint; + else if (padChar) + leftpad = spacesToPrint; + else + rightpad = spacesToPrint; + } + + // Print + foreach (i ; 0 .. leftpad) + put(w, ' '); + + if (prefix1) put(w, prefix1); + if (prefix2) put(w, prefix2); + + foreach (i ; 0 .. zerofill) + put(w, '0'); + + if (fs.flSeparator) + { + for (size_t j = 0; j < digits.length; ++j) + { + if (j != 0 && (digits.length - j) % fs.separators == 0) + { + put(w, fs.separatorChar); + } + put(w, digits[j]); + } + } + else + { + put(w, digits); + } + + foreach (i ; 0 .. rightpad) + put(w, ' '); +} + +@safe pure unittest +{ + assert(collectExceptionMsg!FormatException(format("%c", 5)).back == 'c'); + + assertCTFEable!( + { + formatTest(9, "9"); + formatTest( 10, "10" ); + }); +} + +@system unittest +{ + class C1 { long val; alias val this; this(long v){ val = v; } } + class C2 { long val; alias val this; this(long v){ val = v; } + override string toString() const { return "C"; } } + formatTest( new C1(10), "10" ); + formatTest( new C2(10), "C" ); + + struct S1 { long val; alias val this; } + struct S2 { long val; alias val this; + string toString() const { return "S"; } } + formatTest( S1(10), "10" ); + formatTest( S2(10), "S" ); +} + +// bugzilla 9117 +@safe unittest +{ + static struct Frop {} + + static struct Foo + { + int n = 0; + alias n this; + T opCast(T) () if (is(T == Frop)) + { + return Frop(); + } + string toString() + { + return "Foo"; + } + } + + static struct Bar + { + Foo foo; + alias foo this; + string toString() + { + return "Bar"; + } + } + + const(char)[] result; + void put(const char[] s){ result ~= s; } + + Foo foo; + formattedWrite(&put, "%s", foo); // OK + assert(result == "Foo"); + + result = null; + + Bar bar; + formattedWrite(&put, "%s", bar); // NG + assert(result == "Bar"); + + result = null; + + int i = 9; + formattedWrite(&put, "%s", 9); + assert(result == "9"); +} + +private enum ctfpMessage = "Cannot format floating point types at compile-time"; + +/** +Floating-point values are formatted like $(D printf) does. + +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. + */ +void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f) +if (is(FloatingPointTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) +{ + import std.algorithm.comparison : min; + import std.algorithm.searching : find; + import std.string : indexOf, indexOfAny, indexOfNeither; + + FormatSpec!Char fs = f; // fs is copy for change its values. + FloatingPointTypeOf!T val = obj; + + if (fs.spec == 'r') + { + // raw write, skip all else and write the thing + auto raw = (ref val)@trusted{ + return (cast(const char*) &val)[0 .. val.sizeof]; + }(val); + if (needToSwapEndianess(f)) + { + foreach_reverse (c; raw) + put(w, c); + } + else + { + foreach (c; raw) + put(w, c); + } + return; + } + enforceFmt(find("fgFGaAeEs", fs.spec).length, + "incompatible format character for floating point argument: %" ~ fs.spec); + enforceFmt(!__ctfe, ctfpMessage); + + version (CRuntime_Microsoft) + { + import std.math : isNaN, isInfinity; + immutable double tval = val; // convert early to get "inf" in case of overflow + string s; + if (isNaN(tval)) + s = "nan"; // snprintf writes 1.#QNAN + else if (isInfinity(tval)) + s = val >= 0 ? "inf" : "-inf"; // snprintf writes 1.#INF + + if (s.length > 0) + { + version (none) + { + return formatValue(w, s, f); + } + else // FIXME:workaround + { + s = s[0 .. f.precision < $ ? f.precision : $]; + if (!f.flDash) + { + // right align + if (f.width > s.length) + foreach (j ; 0 .. f.width - s.length) put(w, ' '); + put(w, s); + } + else + { + // left align + put(w, s); + if (f.width > s.length) + foreach (j ; 0 .. f.width - s.length) put(w, ' '); + } + return; + } + } + } + else + alias tval = val; + if (fs.spec == 's') fs.spec = 'g'; + char[1 /*%*/ + 5 /*flags*/ + 3 /*width.prec*/ + 2 /*format*/ + + 1 /*\0*/] sprintfSpec = void; + sprintfSpec[0] = '%'; + uint i = 1; + if (fs.flDash) sprintfSpec[i++] = '-'; + if (fs.flPlus) sprintfSpec[i++] = '+'; + if (fs.flZero) sprintfSpec[i++] = '0'; + if (fs.flSpace) sprintfSpec[i++] = ' '; + if (fs.flHash) sprintfSpec[i++] = '#'; + sprintfSpec[i .. i + 3] = "*.*"; + i += 3; + if (is(Unqual!(typeof(val)) == real)) sprintfSpec[i++] = 'L'; + sprintfSpec[i++] = fs.spec; + sprintfSpec[i] = 0; + //printf("format: '%s'; geeba: %g\n", sprintfSpec.ptr, val); + char[512] buf = void; + + immutable n = ()@trusted{ + import core.stdc.stdio : snprintf; + return snprintf(buf.ptr, buf.length, + sprintfSpec.ptr, + fs.width, + // negative precision is same as no precision specified + fs.precision == fs.UNSPECIFIED ? -1 : fs.precision, + tval); + }(); + + enforceFmt(n >= 0, + "floating point formatting failure"); + + auto len = min(n, buf.length-1); + ptrdiff_t dot = buf[0 .. len].indexOf('.'); + if (fs.flSeparator && dot != -1) + { + ptrdiff_t firstDigit = buf[0 .. len].indexOfAny("0123456789"); + ptrdiff_t ePos = buf[0 .. len].indexOf('e'); + size_t j; + + ptrdiff_t firstLen = dot - firstDigit; + + size_t separatorScoreCnt = firstLen / fs.separators; + + size_t afterDotIdx; + if (ePos != -1) + { + afterDotIdx = ePos; + } + else + { + afterDotIdx = len; + } + + if (dot != -1) + { + ptrdiff_t mantissaLen = afterDotIdx - (dot + 1); + separatorScoreCnt += (mantissaLen > 0) ? (mantissaLen - 1) / fs.separators : 0; + } + + // plus, minus prefix + ptrdiff_t digitsBegin = buf[0 .. separatorScoreCnt].indexOfNeither(" "); + if (digitsBegin == -1) + { + digitsBegin = separatorScoreCnt; + } + put(w, buf[digitsBegin .. firstDigit]); + + // digits until dot with separator + for (j = 0; j < firstLen; ++j) + { + if (j > 0 && (firstLen - j) % fs.separators == 0) + { + put(w, fs.separatorChar); + } + put(w, buf[j + firstDigit]); + } + put(w, '.'); + + // digits after dot + for (j = dot + 1; j < afterDotIdx; ++j) + { + auto realJ = (j - (dot + 1)); + if (realJ != 0 && realJ % fs.separators == 0) + { + put(w, fs.separatorChar); + } + put(w, buf[j]); + } + + // rest + if (ePos != -1) + { + put(w, buf[afterDotIdx .. len]); + } + } + else + { + put(w, buf[0 .. len]); + } +} + +/// +@safe unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%.1f"); + formatValue(w, 1337.7, spec); + + assert(w.data == "1337.7"); +} + +@safe /*pure*/ unittest // formatting floating point values is now impure +{ + import std.conv : to; + + assert(collectExceptionMsg!FormatException(format("%d", 5.1)).back == 'd'); + + foreach (T; AliasSeq!(float, double, real)) + { + formatTest( to!( T)(5.5), "5.5" ); + formatTest( to!( const T)(5.5), "5.5" ); + formatTest( to!(immutable T)(5.5), "5.5" ); + + formatTest( T.nan, "nan" ); + } +} + +@system unittest +{ + formatTest( 2.25, "2.25" ); + + class C1 { double val; alias val this; this(double v){ val = v; } } + class C2 { double val; alias val this; this(double v){ val = v; } + override string toString() const { return "C"; } } + formatTest( new C1(2.25), "2.25" ); + formatTest( new C2(2.25), "C" ); + + struct S1 { double val; alias val this; } + struct S2 { double val; alias val this; + string toString() const { return "S"; } } + formatTest( S1(2.25), "2.25" ); + formatTest( S2(2.25), "S" ); +} + +/* +Formatting a $(D creal) is deprecated but still kept around for a while. + +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. + */ +void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f) +if (is(Unqual!T : creal) && !is(T == enum) && !hasToString!(T, Char)) +{ + immutable creal val = obj; + + formatValue(w, val.re, f); + if (val.im >= 0) + { + put(w, '+'); + } + formatValue(w, val.im, f); + put(w, 'i'); +} + +@safe /*pure*/ unittest // formatting floating point values is now impure +{ + import std.conv : to; + foreach (T; AliasSeq!(cfloat, cdouble, creal)) + { + formatTest( to!( T)(1 + 1i), "1+1i" ); + formatTest( to!( const T)(1 + 1i), "1+1i" ); + formatTest( to!(immutable T)(1 + 1i), "1+1i" ); + } + foreach (T; AliasSeq!(cfloat, cdouble, creal)) + { + formatTest( to!( T)(0 - 3i), "0-3i" ); + formatTest( to!( const T)(0 - 3i), "0-3i" ); + formatTest( to!(immutable T)(0 - 3i), "0-3i" ); + } +} + +@system unittest +{ + formatTest( 3+2.25i, "3+2.25i" ); + + class C1 { cdouble val; alias val this; this(cdouble v){ val = v; } } + class C2 { cdouble val; alias val this; this(cdouble v){ val = v; } + override string toString() const { return "C"; } } + formatTest( new C1(3+2.25i), "3+2.25i" ); + formatTest( new C2(3+2.25i), "C" ); + + struct S1 { cdouble val; alias val this; } + struct S2 { cdouble val; alias val this; + string toString() const { return "S"; } } + formatTest( S1(3+2.25i), "3+2.25i" ); + formatTest( S2(3+2.25i), "S" ); +} + +/* + Formatting an $(D ireal) is deprecated but still kept around for a while. + +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. + */ +void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f) +if (is(Unqual!T : ireal) && !is(T == enum) && !hasToString!(T, Char)) +{ + immutable ireal val = obj; + + formatValue(w, val.im, f); + put(w, 'i'); +} + +@safe /*pure*/ unittest // formatting floating point values is now impure +{ + import std.conv : to; + foreach (T; AliasSeq!(ifloat, idouble, ireal)) + { + formatTest( to!( T)(1i), "1i" ); + formatTest( to!( const T)(1i), "1i" ); + formatTest( to!(immutable T)(1i), "1i" ); + } +} + +@system unittest +{ + formatTest( 2.25i, "2.25i" ); + + class C1 { idouble val; alias val this; this(idouble v){ val = v; } } + class C2 { idouble val; alias val this; this(idouble v){ val = v; } + override string toString() const { return "C"; } } + formatTest( new C1(2.25i), "2.25i" ); + formatTest( new C2(2.25i), "C" ); + + struct S1 { idouble val; alias val this; } + struct S2 { idouble val; alias val this; + string toString() const { return "S"; } } + formatTest( S1(2.25i), "2.25i" ); + formatTest( S2(2.25i), "S" ); +} + +/** +Individual characters ($(D char), $(D wchar), or $(D dchar)) are formatted as +Unicode characters with %s and as integers with integral-specific format +specs. + +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. + */ +void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f) +if (is(CharTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) +{ + CharTypeOf!T val = obj; + + if (f.spec == 's' || f.spec == 'c') + { + put(w, val); + } + else + { + alias U = AliasSeq!(ubyte, ushort, uint)[CharTypeOf!T.sizeof/2]; + formatValue(w, cast(U) val, f); + } +} + +/// +@safe pure unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%c"); + formatValue(w, 'a', spec); + + assert(w.data == "a"); +} + +@safe pure unittest +{ + assertCTFEable!( + { + formatTest( 'c', "c" ); + }); +} + +@system unittest +{ + class C1 { char val; alias val this; this(char v){ val = v; } } + class C2 { char val; alias val this; this(char v){ val = v; } + override string toString() const { return "C"; } } + formatTest( new C1('c'), "c" ); + formatTest( new C2('c'), "C" ); + + struct S1 { char val; alias val this; } + struct S2 { char val; alias val this; + string toString() const { return "S"; } } + formatTest( S1('c'), "c" ); + formatTest( S2('c'), "S" ); +} + +@safe unittest +{ + //Little Endian + formatTest( "%-r", cast( char)'c', ['c' ] ); + formatTest( "%-r", cast(wchar)'c', ['c', 0 ] ); + formatTest( "%-r", cast(dchar)'c', ['c', 0, 0, 0] ); + formatTest( "%-r", '本', ['\x2c', '\x67'] ); + + //Big Endian + formatTest( "%+r", cast( char)'c', [ 'c'] ); + formatTest( "%+r", cast(wchar)'c', [0, 'c'] ); + formatTest( "%+r", cast(dchar)'c', [0, 0, 0, 'c'] ); + formatTest( "%+r", '本', ['\x67', '\x2c'] ); +} + +/** +Strings are formatted like $(D printf) does. + +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. + */ +void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f) +if (is(StringTypeOf!T) && !is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) +{ + Unqual!(StringTypeOf!T) val = obj; // for `alias this`, see bug5371 + formatRange(w, val, f); +} + +/// +@safe pure unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%s"); + formatValue(w, "hello", spec); + + assert(w.data == "hello"); +} + +@safe unittest +{ + formatTest( "abc", "abc" ); +} + +@system unittest +{ + // Test for bug 5371 for classes + class C1 { const string var; alias var this; this(string s){ var = s; } } + class C2 { string var; alias var this; this(string s){ var = s; } } + formatTest( new C1("c1"), "c1" ); + formatTest( new C2("c2"), "c2" ); + + // Test for bug 5371 for structs + struct S1 { const string var; alias var this; } + struct S2 { string var; alias var this; } + formatTest( S1("s1"), "s1" ); + formatTest( S2("s2"), "s2" ); +} + +@system unittest +{ + class C3 { string val; alias val this; this(string s){ val = s; } + override string toString() const { return "C"; } } + formatTest( new C3("c3"), "C" ); + + struct S3 { string val; alias val this; + string toString() const { return "S"; } } + formatTest( S3("s3"), "S" ); +} + +@safe pure unittest +{ + //Little Endian + formatTest( "%-r", "ab"c, ['a' , 'b' ] ); + formatTest( "%-r", "ab"w, ['a', 0 , 'b', 0 ] ); + formatTest( "%-r", "ab"d, ['a', 0, 0, 0, 'b', 0, 0, 0] ); + formatTest( "%-r", "日本語"c, ['\xe6', '\x97', '\xa5', '\xe6', '\x9c', '\xac', '\xe8', '\xaa', '\x9e'] ); + formatTest( "%-r", "日本語"w, ['\xe5', '\x65', '\x2c', '\x67', '\x9e', '\x8a']); + formatTest( "%-r", "日本語"d, ['\xe5', '\x65', '\x00', '\x00', '\x2c', '\x67', + '\x00', '\x00', '\x9e', '\x8a', '\x00', '\x00'] ); + + //Big Endian + formatTest( "%+r", "ab"c, [ 'a', 'b'] ); + formatTest( "%+r", "ab"w, [ 0, 'a', 0, 'b'] ); + formatTest( "%+r", "ab"d, [0, 0, 0, 'a', 0, 0, 0, 'b'] ); + formatTest( "%+r", "日本語"c, ['\xe6', '\x97', '\xa5', '\xe6', '\x9c', '\xac', '\xe8', '\xaa', '\x9e'] ); + formatTest( "%+r", "日本語"w, ['\x65', '\xe5', '\x67', '\x2c', '\x8a', '\x9e'] ); + formatTest( "%+r", "日本語"d, ['\x00', '\x00', '\x65', '\xe5', '\x00', '\x00', + '\x67', '\x2c', '\x00', '\x00', '\x8a', '\x9e'] ); +} + +/** +Static-size arrays are formatted as dynamic arrays. + +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. + */ +void formatValue(Writer, T, Char)(auto ref Writer w, auto ref T obj, const ref FormatSpec!Char f) +if (is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) +{ + formatValue(w, obj[], f); +} + +/// +@safe pure unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%s"); + char[2] two = ['a', 'b']; + formatValue(w, two, spec); + + assert(w.data == "ab"); +} + +@safe unittest // Test for issue 8310 +{ + import std.array : appender; + FormatSpec!char f; + auto w = appender!string(); + + char[2] two = ['a', 'b']; + formatValue(w, two, f); + + char[2] getTwo(){ return two; } + formatValue(w, getTwo(), f); +} + +/** +Dynamic arrays are formatted as input ranges. + +Specializations: + $(UL $(LI $(D void[]) is formatted like $(D ubyte[]).) + $(LI Const array is converted to input range by removing its qualifier.)) + +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. + */ +void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f) +if (is(DynamicArrayTypeOf!T) && !is(StringTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) +{ + static if (is(const(ArrayTypeOf!T) == const(void[]))) + { + formatValue(w, cast(const ubyte[]) obj, f); + } + else static if (!isInputRange!T) + { + alias U = Unqual!(ArrayTypeOf!T); + static assert(isInputRange!U); + U val = obj; + formatValue(w, val, f); + } + else + { + formatRange(w, obj, f); + } +} + +/// +@safe pure unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%s"); + auto two = [1, 2]; + formatValue(w, two, spec); + + assert(w.data == "[1, 2]"); +} + +// alias this, input range I/F, and toString() +@system unittest +{ + struct S(int flags) + { + int[] arr; + static if (flags & 1) + alias arr this; + + static if (flags & 2) + { + @property bool empty() const { return arr.length == 0; } + @property int front() const { return arr[0] * 2; } + void popFront() { arr = arr[1..$]; } + } + + static if (flags & 4) + string toString() const { return "S"; } + } + formatTest(S!0b000([0, 1, 2]), "S!0([0, 1, 2])"); + formatTest(S!0b001([0, 1, 2]), "[0, 1, 2]"); // Test for bug 7628 + formatTest(S!0b010([0, 1, 2]), "[0, 2, 4]"); + formatTest(S!0b011([0, 1, 2]), "[0, 2, 4]"); + formatTest(S!0b100([0, 1, 2]), "S"); + formatTest(S!0b101([0, 1, 2]), "S"); // Test for bug 7628 + formatTest(S!0b110([0, 1, 2]), "S"); + formatTest(S!0b111([0, 1, 2]), "S"); + + class C(uint flags) + { + int[] arr; + static if (flags & 1) + alias arr this; + + this(int[] a) { arr = a; } + + static if (flags & 2) + { + @property bool empty() const { return arr.length == 0; } + @property int front() const { return arr[0] * 2; } + void popFront() { arr = arr[1..$]; } + } + + static if (flags & 4) + override string toString() const { return "C"; } + } + formatTest(new C!0b000([0, 1, 2]), (new C!0b000([])).toString()); + formatTest(new C!0b001([0, 1, 2]), "[0, 1, 2]"); // Test for bug 7628 + formatTest(new C!0b010([0, 1, 2]), "[0, 2, 4]"); + formatTest(new C!0b011([0, 1, 2]), "[0, 2, 4]"); + formatTest(new C!0b100([0, 1, 2]), "C"); + formatTest(new C!0b101([0, 1, 2]), "C"); // Test for bug 7628 + formatTest(new C!0b110([0, 1, 2]), "C"); + formatTest(new C!0b111([0, 1, 2]), "C"); +} + +@system unittest +{ + // void[] + void[] val0; + formatTest( val0, "[]" ); + + void[] val = cast(void[]) cast(ubyte[])[1, 2, 3]; + formatTest( val, "[1, 2, 3]" ); + + void[0] sval0 = []; + formatTest( sval0, "[]"); + + void[3] sval = cast(void[3]) cast(ubyte[3])[1, 2, 3]; + formatTest( sval, "[1, 2, 3]" ); +} + +@safe unittest +{ + // const(T[]) -> const(T)[] + const short[] a = [1, 2, 3]; + formatTest( a, "[1, 2, 3]" ); + + struct S { const(int[]) arr; alias arr this; } + auto s = S([1,2,3]); + formatTest( s, "[1, 2, 3]" ); +} + +@safe unittest +{ + // 6640 + struct Range + { + @safe: + string value; + @property bool empty() const { return !value.length; } + @property dchar front() const { return value.front; } + void popFront() { value.popFront(); } + + @property size_t length() const { return value.length; } + } + immutable table = + [ + ["[%s]", "[string]"], + ["[%10s]", "[ string]"], + ["[%-10s]", "[string ]"], + ["[%(%02x %)]", "[73 74 72 69 6e 67]"], + ["[%(%c %)]", "[s t r i n g]"], + ]; + foreach (e; table) + { + formatTest(e[0], "string", e[1]); + formatTest(e[0], Range("string"), e[1]); + } +} + +@system unittest +{ + // string literal from valid UTF sequence is encoding free. + foreach (StrType; AliasSeq!(string, wstring, dstring)) + { + // Valid and printable (ASCII) + formatTest( [cast(StrType)"hello"], + `["hello"]` ); + + // 1 character escape sequences (' is not escaped in strings) + formatTest( [cast(StrType)"\"'\0\\\a\b\f\n\r\t\v"], + `["\"'\0\\\a\b\f\n\r\t\v"]` ); + + // 1 character optional escape sequences + formatTest( [cast(StrType)"\'\?"], + `["'?"]` ); + + // Valid and non-printable code point (<= U+FF) + formatTest( [cast(StrType)"\x10\x1F\x20test"], + `["\x10\x1F test"]` ); + + // Valid and non-printable code point (<= U+FFFF) + formatTest( [cast(StrType)"\u200B..\u200F"], + `["\u200B..\u200F"]` ); + + // Valid and non-printable code point (<= U+10FFFF) + formatTest( [cast(StrType)"\U000E0020..\U000E007F"], + `["\U000E0020..\U000E007F"]` ); + } + + // invalid UTF sequence needs hex-string literal postfix (c/w/d) + { + // U+FFFF with UTF-8 (Invalid code point for interchange) + formatTest( [cast(string)[0xEF, 0xBF, 0xBF]], + `[x"EF BF BF"c]` ); + + // U+FFFF with UTF-16 (Invalid code point for interchange) + formatTest( [cast(wstring)[0xFFFF]], + `[x"FFFF"w]` ); + + // U+FFFF with UTF-32 (Invalid code point for interchange) + formatTest( [cast(dstring)[0xFFFF]], + `[x"FFFF"d]` ); + } +} + +@safe unittest +{ + // nested range formatting with array of string + formatTest( "%({%(%02x %)}%| %)", ["test", "msg"], + `{74 65 73 74} {6d 73 67}` ); +} + +@safe unittest +{ + // stop auto escaping inside range formatting + auto arr = ["hello", "world"]; + formatTest( "%(%s, %)", arr, `"hello", "world"` ); + formatTest( "%-(%s, %)", arr, `hello, world` ); + + auto aa1 = [1:"hello", 2:"world"]; + formatTest( "%(%s:%s, %)", aa1, [`1:"hello", 2:"world"`, `2:"world", 1:"hello"`] ); + formatTest( "%-(%s:%s, %)", aa1, [`1:hello, 2:world`, `2:world, 1:hello`] ); + + auto aa2 = [1:["ab", "cd"], 2:["ef", "gh"]]; + formatTest( "%-(%s:%s, %)", aa2, [`1:["ab", "cd"], 2:["ef", "gh"]`, `2:["ef", "gh"], 1:["ab", "cd"]`] ); + formatTest( "%-(%s:%(%s%), %)", aa2, [`1:"ab""cd", 2:"ef""gh"`, `2:"ef""gh", 1:"ab""cd"`] ); + formatTest( "%-(%s:%-(%s%)%|, %)", aa2, [`1:abcd, 2:efgh`, `2:efgh, 1:abcd`] ); +} + +// input range formatting +private void formatRange(Writer, T, Char)(ref Writer w, ref T val, const ref FormatSpec!Char f) +if (isInputRange!T) +{ + import std.conv : text; + + // Formatting character ranges like string + if (f.spec == 's') + { + alias E = ElementType!T; + + static if (!is(E == enum) && is(CharTypeOf!E)) + { + static if (is(StringTypeOf!T)) + { + auto s = val[0 .. f.precision < $ ? f.precision : $]; + if (!f.flDash) + { + // right align + if (f.width > s.length) + foreach (i ; 0 .. f.width - s.length) put(w, ' '); + put(w, s); + } + else + { + // left align + put(w, s); + if (f.width > s.length) + foreach (i ; 0 .. f.width - s.length) put(w, ' '); + } + } + else + { + if (!f.flDash) + { + static if (hasLength!T) + { + // right align + auto len = val.length; + } + else static if (isForwardRange!T && !isInfinite!T) + { + auto len = walkLength(val.save); + } + else + { + enforce(f.width == 0, "Cannot right-align a range without length"); + size_t len = 0; + } + if (f.precision != f.UNSPECIFIED && len > f.precision) + len = f.precision; + + if (f.width > len) + foreach (i ; 0 .. f.width - len) + put(w, ' '); + if (f.precision == f.UNSPECIFIED) + put(w, val); + else + { + size_t printed = 0; + for (; !val.empty && printed < f.precision; val.popFront(), ++printed) + put(w, val.front); + } + } + else + { + size_t printed = void; + + // left align + if (f.precision == f.UNSPECIFIED) + { + static if (hasLength!T) + { + printed = val.length; + put(w, val); + } + else + { + printed = 0; + for (; !val.empty; val.popFront(), ++printed) + put(w, val.front); + } + } + else + { + printed = 0; + for (; !val.empty && printed < f.precision; val.popFront(), ++printed) + put(w, val.front); + } + + if (f.width > printed) + foreach (i ; 0 .. f.width - printed) + put(w, ' '); + } + } + } + else + { + put(w, f.seqBefore); + if (!val.empty) + { + formatElement(w, val.front, f); + val.popFront(); + for (size_t i; !val.empty; val.popFront(), ++i) + { + put(w, f.seqSeparator); + formatElement(w, val.front, f); + } + } + static if (!isInfinite!T) put(w, f.seqAfter); + } + } + else if (f.spec == 'r') + { + static if (is(DynamicArrayTypeOf!T)) + { + alias ARR = DynamicArrayTypeOf!T; + foreach (e ; cast(ARR) val) + { + formatValue(w, e, f); + } + } + else + { + for (size_t i; !val.empty; val.popFront(), ++i) + { + formatValue(w, val.front, f); + } + } + } + else if (f.spec == '(') + { + if (val.empty) + return; + // Nested specifier is to be used + for (;;) + { + auto fmt = FormatSpec!Char(f.nested); + fmt.writeUpToNextSpec(w); + if (f.flDash) + formatValue(w, val.front, fmt); + else + formatElement(w, val.front, fmt); + if (f.sep !is null) + { + put(w, fmt.trailing); + val.popFront(); + if (val.empty) + break; + put(w, f.sep); + } + else + { + val.popFront(); + if (val.empty) + break; + put(w, fmt.trailing); + } + } + } + else + throw new Exception(text("Incorrect format specifier for range: %", f.spec)); +} + +@safe pure unittest +{ + assert(collectExceptionMsg(format("%d", "hi")).back == 'd'); +} + +// character formatting with ecaping +private void formatChar(Writer)(ref Writer w, in dchar c, in char quote) +{ + import std.uni : isGraphical; + + string fmt; + if (isGraphical(c)) + { + if (c == quote || c == '\\') + put(w, '\\'); + put(w, c); + return; + } + else if (c <= 0xFF) + { + if (c < 0x20) + { + foreach (i, k; "\n\r\t\a\b\f\v\0") + { + if (c == k) + { + put(w, '\\'); + put(w, "nrtabfv0"[i]); + return; + } + } + } + fmt = "\\x%02X"; + } + else if (c <= 0xFFFF) + fmt = "\\u%04X"; + else + fmt = "\\U%08X"; + + formattedWrite(w, fmt, cast(uint) c); +} + +// undocumented because of deprecation +// string elements are formatted like UTF-8 string literals. +void formatElement(Writer, T, Char)(auto ref Writer w, T val, const ref FormatSpec!Char f) +if (is(StringTypeOf!T) && !is(T == enum)) +{ + import std.array : appender; + import std.utf : UTFException; + + StringTypeOf!T str = val; // bug 8015 + + if (f.spec == 's') + { + try + { + // ignore other specifications and quote + auto app = appender!(typeof(val[0])[])(); + put(app, '\"'); + for (size_t i = 0; i < str.length; ) + { + import std.utf : decode; + + auto c = decode(str, i); + // \uFFFE and \uFFFF are considered valid by isValidDchar, + // so need checking for interchange. + if (c == 0xFFFE || c == 0xFFFF) + goto LinvalidSeq; + formatChar(app, c, '"'); + } + put(app, '\"'); + put(w, app.data); + return; + } + catch (UTFException) + { + } + + // If val contains invalid UTF sequence, formatted like HexString literal + LinvalidSeq: + static if (is(typeof(str[0]) : const(char))) + { + enum postfix = 'c'; + alias IntArr = const(ubyte)[]; + } + else static if (is(typeof(str[0]) : const(wchar))) + { + enum postfix = 'w'; + alias IntArr = const(ushort)[]; + } + else static if (is(typeof(str[0]) : const(dchar))) + { + enum postfix = 'd'; + alias IntArr = const(uint)[]; + } + formattedWrite(w, "x\"%(%02X %)\"%s", cast(IntArr) str, postfix); + } + else + formatValue(w, str, f); +} + +@safe pure unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%s"); + formatElement(w, "Hello World", spec); + + assert(w.data == "\"Hello World\""); +} + +@safe unittest +{ + // Test for bug 8015 + import std.typecons; + + struct MyStruct { + string str; + @property string toStr() { + return str; + } + alias toStr this; + } + + Tuple!(MyStruct) t; +} + +// undocumented because of deprecation +// Character elements are formatted like UTF-8 character literals. +void formatElement(Writer, T, Char)(auto ref Writer w, T val, const ref FormatSpec!Char f) +if (is(CharTypeOf!T) && !is(T == enum)) +{ + if (f.spec == 's') + { + put(w, '\''); + formatChar(w, val, '\''); + put(w, '\''); + } + else + formatValue(w, val, f); +} + +/// +@safe unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%s"); + formatElement(w, "H", spec); + + assert(w.data == "\"H\"", w.data); +} + +// undocumented +// Maybe T is noncopyable struct, so receive it by 'auto ref'. +void formatElement(Writer, T, Char)(auto ref Writer w, auto ref T val, const ref FormatSpec!Char f) +if (!is(StringTypeOf!T) && !is(CharTypeOf!T) || is(T == enum)) +{ + formatValue(w, val, f); +} + +/** + Associative arrays are formatted by using $(D ':') and $(D ", ") as + separators, and enclosed by $(D '[') and $(D ']'). + +Params: + w = The $(D OutputRange) to write to. + obj = The value to write. + f = The $(D FormatSpec) defining how to write the value. + */ +void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f) +if (is(AssocArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) +{ + AssocArrayTypeOf!T val = obj; + + enforceFmt(f.spec == 's' || f.spec == '(', + "incompatible format character for associative array argument: %" ~ f.spec); + + enum const(Char)[] defSpec = "%s" ~ f.keySeparator ~ "%s" ~ f.seqSeparator; + auto fmtSpec = f.spec == '(' ? f.nested : defSpec; + + size_t i = 0; + immutable end = val.length; + + if (f.spec == 's') + put(w, f.seqBefore); + foreach (k, ref v; val) + { + auto fmt = FormatSpec!Char(fmtSpec); + fmt.writeUpToNextSpec(w); + if (f.flDash) + { + formatValue(w, k, fmt); + fmt.writeUpToNextSpec(w); + formatValue(w, v, fmt); + } + else + { + formatElement(w, k, fmt); + fmt.writeUpToNextSpec(w); + formatElement(w, v, fmt); + } + if (f.sep !is null) + { + fmt.writeUpToNextSpec(w); + if (++i != end) + put(w, f.sep); + } + else + { + if (++i != end) + fmt.writeUpToNextSpec(w); + } + } + if (f.spec == 's') + put(w, f.seqAfter); +} + +/// +@safe pure unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%s"); + auto aa = ["H":"W"]; + formatElement(w, aa, spec); + + assert(w.data == "[\"H\":\"W\"]", w.data); +} + +@safe unittest +{ + assert(collectExceptionMsg!FormatException(format("%d", [0:1])).back == 'd'); + + int[string] aa0; + formatTest( aa0, `[]` ); + + // elements escaping + formatTest( ["aaa":1, "bbb":2], + [`["aaa":1, "bbb":2]`, `["bbb":2, "aaa":1]`] ); + formatTest( ['c':"str"], + `['c':"str"]` ); + formatTest( ['"':"\"", '\'':"'"], + [`['"':"\"", '\'':"'"]`, `['\'':"'", '"':"\""]`] ); + + // range formatting for AA + auto aa3 = [1:"hello", 2:"world"]; + // escape + formatTest( "{%(%s:%s $ %)}", aa3, + [`{1:"hello" $ 2:"world"}`, `{2:"world" $ 1:"hello"}`]); + // use range formatting for key and value, and use %| + formatTest( "{%([%04d->%(%c.%)]%| $ %)}", aa3, + [`{[0001->h.e.l.l.o] $ [0002->w.o.r.l.d]}`, `{[0002->w.o.r.l.d] $ [0001->h.e.l.l.o]}`] ); + + // issue 12135 + formatTest("%(%s:<%s>%|,%)", [1:2], "1:<2>"); + formatTest("%(%s:<%s>%|%)" , [1:2], "1:<2>"); +} + +@system unittest +{ + class C1 { int[char] val; alias val this; this(int[char] v){ val = v; } } + class C2 { int[char] val; alias val this; this(int[char] v){ val = v; } + override string toString() const { return "C"; } } + formatTest( new C1(['c':1, 'd':2]), [`['c':1, 'd':2]`, `['d':2, 'c':1]`] ); + formatTest( new C2(['c':1, 'd':2]), "C" ); + + struct S1 { int[char] val; alias val this; } + struct S2 { int[char] val; alias val this; + string toString() const { return "S"; } } + formatTest( S1(['c':1, 'd':2]), [`['c':1, 'd':2]`, `['d':2, 'c':1]`] ); + formatTest( S2(['c':1, 'd':2]), "S" ); +} + +@safe unittest // Issue 8921 +{ + enum E : char { A = 'a', B = 'b', C = 'c' } + E[3] e = [E.A, E.B, E.C]; + formatTest(e, "[A, B, C]"); + + E[] e2 = [E.A, E.B, E.C]; + formatTest(e2, "[A, B, C]"); +} + +template hasToString(T, Char) +{ + static if (isPointer!T && !isAggregateType!T) + { + // X* does not have toString, even if X is aggregate type has toString. + enum hasToString = 0; + } + else static if (is(typeof({ T val = void; FormatSpec!Char f; val.toString((const(char)[] s){}, f); }))) + { + enum hasToString = 4; + } + else static if (is(typeof({ T val = void; val.toString((const(char)[] s){}, "%s"); }))) + { + enum hasToString = 3; + } + else static if (is(typeof({ T val = void; val.toString((const(char)[] s){}); }))) + { + enum hasToString = 2; + } + else static if (is(typeof({ T val = void; return val.toString(); }()) S) && isSomeString!S) + { + enum hasToString = 1; + } + else + { + enum hasToString = 0; + } +} + +// object formatting with toString +private void formatObject(Writer, T, Char)(ref Writer w, ref T val, const ref FormatSpec!Char f) +if (hasToString!(T, Char)) +{ + static if (is(typeof(val.toString((const(char)[] s){}, f)))) + { + val.toString((const(char)[] s) { put(w, s); }, f); + } + else static if (is(typeof(val.toString((const(char)[] s){}, "%s")))) + { + val.toString((const(char)[] s) { put(w, s); }, f.getCurFmtStr()); + } + else static if (is(typeof(val.toString((const(char)[] s){})))) + { + val.toString((const(char)[] s) { put(w, s); }); + } + else static if (is(typeof(val.toString()) S) && isSomeString!S) + { + put(w, val.toString()); + } + else + static assert(0); +} + +void enforceValidFormatSpec(T, Char)(const ref FormatSpec!Char f) +{ + static if (!isInputRange!T && hasToString!(T, Char) != 4) + { + enforceFmt(f.spec == 's', + "Expected '%s' format specifier for type '" ~ T.stringof ~ "'"); + } +} + +@system unittest +{ + static interface IF1 { } + class CIF1 : IF1 { } + static struct SF1 { } + static union UF1 { } + static class CF1 { } + + static interface IF2 { string toString(); } + static class CIF2 : IF2 { override string toString() { return ""; } } + static struct SF2 { string toString() { return ""; } } + static union UF2 { string toString() { return ""; } } + static class CF2 { override string toString() { return ""; } } + + static interface IK1 { void toString(scope void delegate(const(char)[]) sink, + FormatSpec!char) const; } + static class CIK1 : IK1 { override void toString(scope void delegate(const(char)[]) sink, + FormatSpec!char) const { sink("CIK1"); } } + static struct KS1 { void toString(scope void delegate(const(char)[]) sink, + FormatSpec!char) const { sink("KS1"); } } + + static union KU1 { void toString(scope void delegate(const(char)[]) sink, + FormatSpec!char) const { sink("KU1"); } } + + static class KC1 { void toString(scope void delegate(const(char)[]) sink, + FormatSpec!char) const { sink("KC1"); } } + + IF1 cif1 = new CIF1; + assertThrown!FormatException(format("%f", cif1)); + assertThrown!FormatException(format("%f", SF1())); + assertThrown!FormatException(format("%f", UF1())); + assertThrown!FormatException(format("%f", new CF1())); + + IF2 cif2 = new CIF2; + assertThrown!FormatException(format("%f", cif2)); + assertThrown!FormatException(format("%f", SF2())); + assertThrown!FormatException(format("%f", UF2())); + assertThrown!FormatException(format("%f", new CF2())); + + IK1 cik1 = new CIK1; + assert(format("%f", cik1) == "CIK1"); + assert(format("%f", KS1()) == "KS1"); + assert(format("%f", KU1()) == "KU1"); + assert(format("%f", new KC1()) == "KC1"); +} + +/** + Aggregates ($(D struct), $(D union), $(D class), and $(D interface)) are + basically formatted by calling $(D toString). + $(D toString) should have one of the following signatures: + +--- +const void toString(scope void delegate(const(char)[]) sink, FormatSpec fmt); +const void toString(scope void delegate(const(char)[]) sink, string fmt); +const void toString(scope void delegate(const(char)[]) sink); +const string toString(); +--- + + For the class objects which have input range interface, + $(UL $(LI If the instance $(D toString) has overridden + $(D Object.toString), it is used.) + $(LI Otherwise, the objects are formatted as input range.)) + + For the struct and union objects which does not have $(D toString), + $(UL $(LI If they have range interface, formatted as input range.) + $(LI Otherwise, they are formatted like $(D Type(field1, filed2, ...)).)) + + Otherwise, are formatted just as their type name. + */ +void formatValue(Writer, T, Char)(auto ref Writer w, T val, const ref FormatSpec!Char f) +if (is(T == class) && !is(T == enum)) +{ + enforceValidFormatSpec!(T, Char)(f); + // TODO: Change this once toString() works for shared objects. + static assert(!is(T == shared), "unable to format shared objects"); + + if (val is null) + put(w, "null"); + else + { + static if (hasToString!(T, Char) > 1 || (!isInputRange!T && !is(BuiltinTypeOf!T))) + { + formatObject!(Writer, T, Char)(w, val, f); + } + else + { + //string delegate() dg = &val.toString; + Object o = val; // workaround + string delegate() dg = &o.toString; + if (dg.funcptr != &Object.toString) // toString is overridden + { + formatObject(w, val, f); + } + else static if (isInputRange!T) + { + formatRange(w, val, f); + } + else static if (is(BuiltinTypeOf!T X)) + { + X x = val; + formatValue(w, x, f); + } + else + { + formatObject(w, val, f); + } + } + } +} + +/++ + $(D formatValue) allows to reuse existing format specifiers: + +/ +@system unittest +{ + import std.format; + + struct Point + { + int x, y; + + void toString(scope void delegate(const(char)[]) sink, + FormatSpec!char fmt) const + { + sink("("); + sink.formatValue(x, fmt); + sink(","); + sink.formatValue(y, fmt); + sink(")"); + } + } + + auto p = Point(16,11); + assert(format("%03d", p) == "(016,011)"); + assert(format("%02x", p) == "(10,0b)"); +} + +/++ + The following code compares the use of $(D formatValue) and $(D formattedWrite). + +/ +@safe pure unittest +{ + import std.array : appender; + import std.format; + + auto writer1 = appender!string(); + writer1.formattedWrite("%08b", 42); + + auto writer2 = appender!string(); + auto f = singleSpec("%08b"); + writer2.formatValue(42, f); + + assert(writer1.data == writer2.data && writer1.data == "00101010"); +} + +@system unittest +{ + import std.array : appender; + import std.range.interfaces; + // class range (issue 5154) + auto c = inputRangeObject([1,2,3,4]); + formatTest( c, "[1, 2, 3, 4]" ); + assert(c.empty); + c = null; + formatTest( c, "null" ); +} + +@system unittest +{ + // 5354 + // If the class has both range I/F and custom toString, the use of custom + // toString routine is prioritized. + + // Enable the use of custom toString that gets a sink delegate + // for class formatting. + + enum inputRangeCode = + q{ + int[] arr; + this(int[] a){ arr = a; } + @property int front() const { return arr[0]; } + @property bool empty() const { return arr.length == 0; } + void popFront(){ arr = arr[1..$]; } + }; + + class C1 + { + mixin(inputRangeCode); + void toString(scope void delegate(const(char)[]) dg, const ref FormatSpec!char f) const { dg("[012]"); } + } + class C2 + { + mixin(inputRangeCode); + void toString(scope void delegate(const(char)[]) dg, string f) const { dg("[012]"); } + } + class C3 + { + mixin(inputRangeCode); + void toString(scope void delegate(const(char)[]) dg) const { dg("[012]"); } + } + class C4 + { + mixin(inputRangeCode); + override string toString() const { return "[012]"; } + } + class C5 + { + mixin(inputRangeCode); + } + + formatTest( new C1([0, 1, 2]), "[012]" ); + formatTest( new C2([0, 1, 2]), "[012]" ); + formatTest( new C3([0, 1, 2]), "[012]" ); + formatTest( new C4([0, 1, 2]), "[012]" ); + formatTest( new C5([0, 1, 2]), "[0, 1, 2]" ); +} + +/// ditto +void formatValue(Writer, T, Char)(auto ref Writer w, T val, const ref FormatSpec!Char f) +if (is(T == interface) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) && !is(T == enum)) +{ + enforceValidFormatSpec!(T, Char)(f); + if (val is null) + put(w, "null"); + else + { + static if (hasToString!(T, Char)) + { + formatObject(w, val, f); + } + else static if (isInputRange!T) + { + formatRange(w, val, f); + } + else + { + version (Windows) + { + import core.sys.windows.com : IUnknown; + static if (is(T : IUnknown)) + { + formatValue(w, *cast(void**)&val, f); + } + else + { + formatValue(w, cast(Object) val, f); + } + } + else + { + formatValue(w, cast(Object) val, f); + } + } + } +} + +@system unittest +{ + // interface + import std.range.interfaces; + InputRange!int i = inputRangeObject([1,2,3,4]); + formatTest( i, "[1, 2, 3, 4]" ); + assert(i.empty); + i = null; + formatTest( i, "null" ); + + // interface (downcast to Object) + interface Whatever {} + class C : Whatever + { + override @property string toString() const { return "ab"; } + } + Whatever val = new C; + formatTest( val, "ab" ); + + // Issue 11175 + version (Windows) + { + import core.sys.windows.com : IUnknown, IID; + import core.sys.windows.windows : HRESULT; + + interface IUnknown2 : IUnknown { } + + class D : IUnknown2 + { + extern(Windows) HRESULT QueryInterface(const(IID)* riid, void** pvObject) { return typeof(return).init; } + extern(Windows) uint AddRef() { return 0; } + extern(Windows) uint Release() { return 0; } + } + + IUnknown2 d = new D; + string expected = format("%X", cast(void*) d); + formatTest(d, expected); + } +} + +/// ditto +// Maybe T is noncopyable struct, so receive it by 'auto ref'. +void formatValue(Writer, T, Char)(auto ref Writer w, auto ref T val, const ref FormatSpec!Char f) +if ((is(T == struct) || is(T == union)) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) && !is(T == enum)) +{ + enforceValidFormatSpec!(T, Char)(f); + static if (hasToString!(T, Char)) + { + formatObject(w, val, f); + } + else static if (isInputRange!T) + { + formatRange(w, val, f); + } + else static if (is(T == struct)) + { + enum left = T.stringof~"("; + enum separator = ", "; + enum right = ")"; + + put(w, left); + foreach (i, e; val.tupleof) + { + static if (0 < i && val.tupleof[i-1].offsetof == val.tupleof[i].offsetof) + { + static if (i == val.tupleof.length - 1 || val.tupleof[i].offsetof != val.tupleof[i+1].offsetof) + put(w, separator~val.tupleof[i].stringof[4..$]~"}"); + else + put(w, separator~val.tupleof[i].stringof[4..$]); + } + else + { + static if (i+1 < val.tupleof.length && val.tupleof[i].offsetof == val.tupleof[i+1].offsetof) + put(w, (i > 0 ? separator : "")~"#{overlap "~val.tupleof[i].stringof[4..$]); + else + { + static if (i > 0) + put(w, separator); + formatElement(w, e, f); + } + } + } + put(w, right); + } + else + { + put(w, T.stringof); + } +} + +@safe unittest +{ + // bug 4638 + struct U8 { string toString() const { return "blah"; } } + struct U16 { wstring toString() const { return "blah"; } } + struct U32 { dstring toString() const { return "blah"; } } + formatTest( U8(), "blah" ); + formatTest( U16(), "blah" ); + formatTest( U32(), "blah" ); +} + +@safe unittest +{ + // 3890 + struct Int{ int n; } + struct Pair{ string s; Int i; } + formatTest( Pair("hello", Int(5)), + `Pair("hello", Int(5))` ); +} + +@system unittest +{ + // union formatting without toString + union U1 + { + int n; + string s; + } + U1 u1; + formatTest( u1, "U1" ); + + // union formatting with toString + union U2 + { + int n; + string s; + string toString() const { return s; } + } + U2 u2; + u2.s = "hello"; + formatTest( u2, "hello" ); +} + +@system unittest +{ + import std.array; + // 7230 + static struct Bug7230 + { + string s = "hello"; + union { + string a; + int b; + double c; + } + long x = 10; + } + + Bug7230 bug; + bug.b = 123; + + FormatSpec!char f; + auto w = appender!(char[])(); + formatValue(w, bug, f); + assert(w.data == `Bug7230("hello", #{overlap a, b, c}, 10)`); +} + +@safe unittest +{ + import std.array; + static struct S{ @disable this(this); } + S s; + + FormatSpec!char f; + auto w = appender!string(); + formatValue(w, s, f); + assert(w.data == "S()"); +} + +/** +$(D enum) is formatted like its base value. + +Params: + w = The $(D OutputRange) to write to. + val = The value to write. + f = The $(D FormatSpec) defining how to write the value. + */ +void formatValue(Writer, T, Char)(auto ref Writer w, T val, const ref FormatSpec!Char f) +if (is(T == enum)) +{ + if (f.spec == 's') + { + foreach (i, e; EnumMembers!T) + { + if (val == e) + { + formatValue(w, __traits(allMembers, T)[i], f); + return; + } + } + + // val is not a member of T, output cast(T) rawValue instead. + put(w, "cast(" ~ T.stringof ~ ")"); + static assert(!is(OriginalType!T == T)); + } + formatValue(w, cast(OriginalType!T) val, f); +} + +/// +@safe pure unittest +{ + import std.array : appender; + auto w = appender!string(); + auto spec = singleSpec("%s"); + + enum A { first, second, third } + + formatElement(w, A.second, spec); + + assert(w.data == "second"); +} + +@safe unittest +{ + enum A { first, second, third } + formatTest( A.second, "second" ); + formatTest( cast(A) 72, "cast(A)72" ); +} +@safe unittest +{ + enum A : string { one = "uno", two = "dos", three = "tres" } + formatTest( A.three, "three" ); + formatTest( cast(A)"mill\ón", "cast(A)mill\ón" ); +} +@safe unittest +{ + enum A : bool { no, yes } + formatTest( A.yes, "yes" ); + formatTest( A.no, "no" ); +} +@safe unittest +{ + // Test for bug 6892 + enum Foo { A = 10 } + formatTest("%s", Foo.A, "A"); + formatTest(">%4s<", Foo.A, "> A<"); + formatTest("%04d", Foo.A, "0010"); + formatTest("%+2u", Foo.A, "+10"); + formatTest("%02x", Foo.A, "0a"); + formatTest("%3o", Foo.A, " 12"); + formatTest("%b", Foo.A, "1010"); +} + +/** + Pointers are formatted as hex integers. + */ +void formatValue(Writer, T, Char)(auto ref Writer w, T val, const ref FormatSpec!Char f) +if (isPointer!T && !is(T == enum) && !hasToString!(T, Char)) +{ + static if (isInputRange!T) + { + if (val !is null) + { + formatRange(w, *val, f); + return; + } + } + + static if (is(typeof({ shared const void* p = val; }))) + alias SharedOf(T) = shared(T); + else + alias SharedOf(T) = T; + + const SharedOf!(void*) p = val; + const pnum = ()@trusted{ return cast(ulong) p; }(); + + if (f.spec == 's') + { + if (p is null) + { + put(w, "null"); + return; + } + FormatSpec!Char fs = f; // fs is copy for change its values. + fs.spec = 'X'; + formatValue(w, pnum, fs); + } + else + { + enforceFmt(f.spec == 'X' || f.spec == 'x', + "Expected one of %s, %x or %X for pointer type."); + formatValue(w, pnum, f); + } +} + +@safe pure unittest +{ + // pointer + import std.range; + auto r = retro([1,2,3,4]); + auto p = ()@trusted{ auto p = &r; return p; }(); + formatTest( p, "[4, 3, 2, 1]" ); + assert(p.empty); + p = null; + formatTest( p, "null" ); + + auto q = ()@trusted{ return cast(void*) 0xFFEECCAA; }(); + formatTest( q, "FFEECCAA" ); +} + +@system pure unittest +{ + // Test for issue 7869 + struct S + { + string toString() const { return ""; } + } + S* p = null; + formatTest( p, "null" ); + + S* q = cast(S*) 0xFFEECCAA; + formatTest( q, "FFEECCAA" ); +} + +@system unittest +{ + // Test for issue 8186 + class B + { + int*a; + this(){ a = new int; } + alias a this; + } + formatTest( B.init, "null" ); +} + +@system pure unittest +{ + // Test for issue 9336 + shared int i; + format("%s", &i); +} + +@system pure unittest +{ + // Test for issue 11778 + int* p = null; + assertThrown(format("%d", p)); + assertThrown(format("%04d", p + 2)); +} + +@safe pure unittest +{ + // Test for issue 12505 + void* p = null; + formatTest( "%08X", p, "00000000" ); +} + +/** + Delegates are formatted by 'ReturnType delegate(Parameters) FunctionAttributes' + */ +void formatValue(Writer, T, Char)(auto ref Writer w, scope T, const ref FormatSpec!Char f) +if (isDelegate!T) +{ + formatValue(w, T.stringof, f); +} + +/// +@safe pure unittest +{ + import std.conv : to; + + int i; + + int foo(short k) @nogc + { + return i + k; + } + + @system int delegate(short) @nogc bar() nothrow pure + { + int* p = new int; + return &foo; + } + + assert(to!string(&bar) == "int delegate(short) @nogc delegate() pure nothrow @system"); +} + +@safe unittest +{ + void func() @system { __gshared int x; ++x; throw new Exception("msg"); } + version (linux) formatTest( &func, "void delegate() @system" ); +} + +@safe pure unittest +{ + int[] a = [ 1, 3, 2 ]; + formatTest( "testing %(%s & %) embedded", a, + "testing 1 & 3 & 2 embedded"); + formatTest( "testing %((%s) %)) wyda3", a, + "testing (1) (3) (2) wyda3" ); + + int[0] empt = []; + formatTest( "(%s)", empt, + "([])" ); +} + +//------------------------------------------------------------------------------ +// Fix for issue 1591 +private int getNthInt(string kind, A...)(uint index, A args) +{ + return getNth!(kind, isIntegral,int)(index, args); +} + +private T getNth(string kind, alias Condition, T, A...)(uint index, A args) +{ + import std.conv : text, to; + + switch (index) + { + foreach (n, _; A) + { + case n: + static if (Condition!(typeof(args[n]))) + { + return to!T(args[n]); + } + else + { + throw new FormatException( + text(kind, " expected, not ", typeof(args[n]).stringof, + " for argument #", index + 1)); + } + } + default: + throw new FormatException( + text("Missing ", kind, " argument")); + } +} + +@safe unittest +{ + // width/precision + assert(collectExceptionMsg!FormatException(format("%*.d", 5.1, 2)) + == "integer width expected, not double for argument #1"); + assert(collectExceptionMsg!FormatException(format("%-1*.d", 5.1, 2)) + == "integer width expected, not double for argument #1"); + + assert(collectExceptionMsg!FormatException(format("%.*d", '5', 2)) + == "integer precision expected, not char for argument #1"); + assert(collectExceptionMsg!FormatException(format("%-1.*d", 4.7, 3)) + == "integer precision expected, not double for argument #1"); + assert(collectExceptionMsg!FormatException(format("%.*d", 5)) + == "Orphan format specifier: %d"); + assert(collectExceptionMsg!FormatException(format("%*.*d", 5)) + == "Missing integer precision argument"); + + // separatorCharPos + assert(collectExceptionMsg!FormatException(format("%,?d", 5)) + == "separator character expected, not int for argument #1"); + assert(collectExceptionMsg!FormatException(format("%,?d", '?')) + == "Orphan format specifier: %d"); + assert(collectExceptionMsg!FormatException(format("%.*,*?d", 5)) + == "Missing separator digit width argument"); +} + +/* ======================== Unit Tests ====================================== */ + +version (unittest) +void formatTest(T)(T val, string expected, size_t ln = __LINE__, string fn = __FILE__) +{ + import core.exception : AssertError; + import std.array : appender; + import std.conv : text; + FormatSpec!char f; + auto w = appender!string(); + formatValue(w, val, f); + enforce!AssertError( + w.data == expected, + text("expected = `", expected, "`, result = `", w.data, "`"), fn, ln); +} + +version (unittest) +void formatTest(T)(string fmt, T val, string expected, size_t ln = __LINE__, string fn = __FILE__) @safe +{ + import core.exception : AssertError; + import std.array : appender; + import std.conv : text; + auto w = appender!string(); + formattedWrite(w, fmt, val); + enforce!AssertError( + w.data == expected, + text("expected = `", expected, "`, result = `", w.data, "`"), fn, ln); +} + +version (unittest) +void formatTest(T)(T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__) +{ + import core.exception : AssertError; + import std.array : appender; + import std.conv : text; + FormatSpec!char f; + auto w = appender!string(); + formatValue(w, val, f); + foreach (cur; expected) + { + if (w.data == cur) return; + } + enforce!AssertError( + false, + text("expected one of `", expected, "`, result = `", w.data, "`"), fn, ln); +} + +version (unittest) +void formatTest(T)(string fmt, T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__) @safe +{ + import core.exception : AssertError; + import std.array : appender; + import std.conv : text; + auto w = appender!string(); + formattedWrite(w, fmt, val); + foreach (cur; expected) + { + if (w.data == cur) return; + } + enforce!AssertError( + false, + text("expected one of `", expected, "`, result = `", w.data, "`"), fn, ln); +} + +@safe /*pure*/ unittest // formatting floating point values is now impure +{ + import std.array; + + auto stream = appender!string(); + formattedWrite(stream, "%s", 1.1); + assert(stream.data == "1.1", stream.data); +} + +@safe pure unittest +{ + import std.algorithm; + import std.array; + + auto stream = appender!string(); + formattedWrite(stream, "%s", map!"a*a"([2, 3, 5])); + assert(stream.data == "[4, 9, 25]", stream.data); + + // Test shared data. + stream = appender!string(); + shared int s = 6; + formattedWrite(stream, "%s", s); + assert(stream.data == "6"); +} + +@safe pure unittest +{ + import std.array; + auto stream = appender!string(); + formattedWrite(stream, "%u", 42); + assert(stream.data == "42", stream.data); +} + +@safe pure unittest +{ + // testing raw writes + import std.array; + auto w = appender!(char[])(); + uint a = 0x02030405; + formattedWrite(w, "%+r", a); + assert(w.data.length == 4 && w.data[0] == 2 && w.data[1] == 3 + && w.data[2] == 4 && w.data[3] == 5); + w.clear(); + formattedWrite(w, "%-r", a); + assert(w.data.length == 4 && w.data[0] == 5 && w.data[1] == 4 + && w.data[2] == 3 && w.data[3] == 2); +} + +@safe pure unittest +{ + // testing positional parameters + import std.array; + auto w = appender!(char[])(); + formattedWrite(w, + "Numbers %2$s and %1$s are reversed and %1$s%2$s repeated", + 42, 0); + assert(w.data == "Numbers 0 and 42 are reversed and 420 repeated", + w.data); + assert(collectExceptionMsg!FormatException(formattedWrite(w, "%1$s, %3$s", 1, 2)) + == "Positional specifier %3$s index exceeds 2"); + + w.clear(); + formattedWrite(w, "asd%s", 23); + assert(w.data == "asd23", w.data); + w.clear(); + formattedWrite(w, "%s%s", 23, 45); + assert(w.data == "2345", w.data); +} + +@safe unittest +{ + import core.stdc.string : strlen; + import std.array : appender; + import std.conv : text, octal; + import core.stdc.stdio : snprintf; + + debug(format) printf("std.format.format.unittest\n"); + + auto stream = appender!(char[])(); + //goto here; + + formattedWrite(stream, + "hello world! %s %s ", true, 57, 1_000_000_000, 'x', " foo"); + assert(stream.data == "hello world! true 57 ", + stream.data); + + stream.clear(); + formattedWrite(stream, "%g %A %s", 1.67, -1.28, float.nan); + // core.stdc.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr); + + /* The host C library is used to format floats. C99 doesn't + * specify what the hex digit before the decimal point is for + * %A. */ + + version (CRuntime_Glibc) + { + assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan", + stream.data); + } + else version (OSX) + { + assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan", + stream.data); + } + else version (MinGW) + { + assert(stream.data == "1.67 -0XA.3D70A3D70A3D8P-3 nan", + stream.data); + } + else version (CRuntime_Microsoft) + { + assert(stream.data == "1.67 -0X1.47AE14P+0 nan" + || stream.data == "1.67 -0X1.47AE147AE147BP+0 nan", // MSVCRT 14+ (VS 2015) + stream.data); + } + else + { + assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan", + stream.data); + } + stream.clear(); + + formattedWrite(stream, "%x %X", 0x1234AF, 0xAFAFAFAF); + assert(stream.data == "1234af AFAFAFAF"); + stream.clear(); + + formattedWrite(stream, "%b %o", 0x1234AF, 0xAFAFAFAF); + assert(stream.data == "100100011010010101111 25753727657"); + stream.clear(); + + formattedWrite(stream, "%d %s", 0x1234AF, 0xAFAFAFAF); + assert(stream.data == "1193135 2947526575"); + stream.clear(); + + // formattedWrite(stream, "%s", 1.2 + 3.4i); + // assert(stream.data == "1.2+3.4i"); + // stream.clear(); + + formattedWrite(stream, "%a %A", 1.32, 6.78f); + //formattedWrite(stream, "%x %X", 1.32); + version (CRuntime_Microsoft) + assert(stream.data == "0x1.51eb85p+0 0X1.B1EB86P+2" + || stream.data == "0x1.51eb851eb851fp+0 0X1.B1EB860000000P+2"); // MSVCRT 14+ (VS 2015) + else + assert(stream.data == "0x1.51eb851eb851fp+0 0X1.B1EB86P+2"); + stream.clear(); + + formattedWrite(stream, "%#06.*f",2,12.345); + assert(stream.data == "012.35"); + stream.clear(); + + formattedWrite(stream, "%#0*.*f",6,2,12.345); + assert(stream.data == "012.35"); + stream.clear(); + + const real constreal = 1; + formattedWrite(stream, "%g",constreal); + assert(stream.data == "1"); + stream.clear(); + + formattedWrite(stream, "%7.4g:", 12.678); + assert(stream.data == " 12.68:"); + stream.clear(); + + formattedWrite(stream, "%7.4g:", 12.678L); + assert(stream.data == " 12.68:"); + stream.clear(); + + formattedWrite(stream, "%04f|%05d|%#05x|%#5x",-4.0,-10,1,1); + assert(stream.data == "-4.000000|-0010|0x001| 0x1", + stream.data); + stream.clear(); + + int i; + string s; + + i = -10; + formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); + assert(stream.data == "-10|-10|-10|-10|-10.0000"); + stream.clear(); + + i = -5; + formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); + assert(stream.data == "-5| -5|-05|-5|-5.0000"); + stream.clear(); + + i = 0; + formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); + assert(stream.data == "0| 0|000|0|0.0000"); + stream.clear(); + + i = 5; + formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); + assert(stream.data == "5| 5|005|5|5.0000"); + stream.clear(); + + i = 10; + formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); + assert(stream.data == "10| 10|010|10|10.0000"); + stream.clear(); + + formattedWrite(stream, "%.0d", 0); + assert(stream.data == ""); + stream.clear(); + + formattedWrite(stream, "%.g", .34); + assert(stream.data == "0.3"); + stream.clear(); + + stream.clear(); formattedWrite(stream, "%.0g", .34); + assert(stream.data == "0.3"); + + stream.clear(); formattedWrite(stream, "%.2g", .34); + assert(stream.data == "0.34"); + + stream.clear(); formattedWrite(stream, "%0.0008f", 1e-08); + assert(stream.data == "0.00000001"); + + stream.clear(); formattedWrite(stream, "%0.0008f", 1e-05); + assert(stream.data == "0.00001000"); + + //return; + //core.stdc.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr); + + s = "helloworld"; + string r; + stream.clear(); formattedWrite(stream, "%.2s", s[0 .. 5]); + assert(stream.data == "he"); + stream.clear(); formattedWrite(stream, "%.20s", s[0 .. 5]); + assert(stream.data == "hello"); + stream.clear(); formattedWrite(stream, "%8s", s[0 .. 5]); + assert(stream.data == " hello"); + + byte[] arrbyte = new byte[4]; + arrbyte[0] = 100; + arrbyte[1] = -99; + arrbyte[3] = 0; + stream.clear(); formattedWrite(stream, "%s", arrbyte); + assert(stream.data == "[100, -99, 0, 0]", stream.data); + + ubyte[] arrubyte = new ubyte[4]; + arrubyte[0] = 100; + arrubyte[1] = 200; + arrubyte[3] = 0; + stream.clear(); formattedWrite(stream, "%s", arrubyte); + assert(stream.data == "[100, 200, 0, 0]", stream.data); + + short[] arrshort = new short[4]; + arrshort[0] = 100; + arrshort[1] = -999; + arrshort[3] = 0; + stream.clear(); formattedWrite(stream, "%s", arrshort); + assert(stream.data == "[100, -999, 0, 0]"); + stream.clear(); formattedWrite(stream, "%s",arrshort); + assert(stream.data == "[100, -999, 0, 0]"); + + ushort[] arrushort = new ushort[4]; + arrushort[0] = 100; + arrushort[1] = 20_000; + arrushort[3] = 0; + stream.clear(); formattedWrite(stream, "%s", arrushort); + assert(stream.data == "[100, 20000, 0, 0]"); + + int[] arrint = new int[4]; + arrint[0] = 100; + arrint[1] = -999; + arrint[3] = 0; + stream.clear(); formattedWrite(stream, "%s", arrint); + assert(stream.data == "[100, -999, 0, 0]"); + stream.clear(); formattedWrite(stream, "%s",arrint); + assert(stream.data == "[100, -999, 0, 0]"); + + long[] arrlong = new long[4]; + arrlong[0] = 100; + arrlong[1] = -999; + arrlong[3] = 0; + stream.clear(); formattedWrite(stream, "%s", arrlong); + assert(stream.data == "[100, -999, 0, 0]"); + stream.clear(); formattedWrite(stream, "%s",arrlong); + assert(stream.data == "[100, -999, 0, 0]"); + + ulong[] arrulong = new ulong[4]; + arrulong[0] = 100; + arrulong[1] = 999; + arrulong[3] = 0; + stream.clear(); formattedWrite(stream, "%s", arrulong); + assert(stream.data == "[100, 999, 0, 0]"); + + string[] arr2 = new string[4]; + arr2[0] = "hello"; + arr2[1] = "world"; + arr2[3] = "foo"; + stream.clear(); formattedWrite(stream, "%s", arr2); + assert(stream.data == `["hello", "world", "", "foo"]`, stream.data); + + stream.clear(); formattedWrite(stream, "%.8d", 7); + assert(stream.data == "00000007"); + + stream.clear(); formattedWrite(stream, "%.8x", 10); + assert(stream.data == "0000000a"); + + stream.clear(); formattedWrite(stream, "%-3d", 7); + assert(stream.data == "7 "); + + stream.clear(); formattedWrite(stream, "%*d", -3, 7); + assert(stream.data == "7 "); + + stream.clear(); formattedWrite(stream, "%.*d", -3, 7); + //writeln(stream.data); + assert(stream.data == "7"); + + stream.clear(); formattedWrite(stream, "%s", "abc"c); + assert(stream.data == "abc"); + stream.clear(); formattedWrite(stream, "%s", "def"w); + assert(stream.data == "def", text(stream.data.length)); + stream.clear(); formattedWrite(stream, "%s", "ghi"d); + assert(stream.data == "ghi"); + +here: + @trusted void* deadBeef() { return cast(void*) 0xDEADBEEF; } + stream.clear(); formattedWrite(stream, "%s", deadBeef()); + assert(stream.data == "DEADBEEF", stream.data); + + stream.clear(); formattedWrite(stream, "%#x", 0xabcd); + assert(stream.data == "0xabcd"); + stream.clear(); formattedWrite(stream, "%#X", 0xABCD); + assert(stream.data == "0XABCD"); + + stream.clear(); formattedWrite(stream, "%#o", octal!12345); + assert(stream.data == "012345"); + stream.clear(); formattedWrite(stream, "%o", 9); + assert(stream.data == "11"); + + stream.clear(); formattedWrite(stream, "%+d", 123); + assert(stream.data == "+123"); + stream.clear(); formattedWrite(stream, "%+d", -123); + assert(stream.data == "-123"); + stream.clear(); formattedWrite(stream, "% d", 123); + assert(stream.data == " 123"); + stream.clear(); formattedWrite(stream, "% d", -123); + assert(stream.data == "-123"); + + stream.clear(); formattedWrite(stream, "%%"); + assert(stream.data == "%"); + + stream.clear(); formattedWrite(stream, "%d", true); + assert(stream.data == "1"); + stream.clear(); formattedWrite(stream, "%d", false); + assert(stream.data == "0"); + + stream.clear(); formattedWrite(stream, "%d", 'a'); + assert(stream.data == "97", stream.data); + wchar wc = 'a'; + stream.clear(); formattedWrite(stream, "%d", wc); + assert(stream.data == "97"); + dchar dc = 'a'; + stream.clear(); formattedWrite(stream, "%d", dc); + assert(stream.data == "97"); + + byte b = byte.max; + stream.clear(); formattedWrite(stream, "%x", b); + assert(stream.data == "7f"); + stream.clear(); formattedWrite(stream, "%x", ++b); + assert(stream.data == "80"); + stream.clear(); formattedWrite(stream, "%x", ++b); + assert(stream.data == "81"); + + short sh = short.max; + stream.clear(); formattedWrite(stream, "%x", sh); + assert(stream.data == "7fff"); + stream.clear(); formattedWrite(stream, "%x", ++sh); + assert(stream.data == "8000"); + stream.clear(); formattedWrite(stream, "%x", ++sh); + assert(stream.data == "8001"); + + i = int.max; + stream.clear(); formattedWrite(stream, "%x", i); + assert(stream.data == "7fffffff"); + stream.clear(); formattedWrite(stream, "%x", ++i); + assert(stream.data == "80000000"); + stream.clear(); formattedWrite(stream, "%x", ++i); + assert(stream.data == "80000001"); + + stream.clear(); formattedWrite(stream, "%x", 10); + assert(stream.data == "a"); + stream.clear(); formattedWrite(stream, "%X", 10); + assert(stream.data == "A"); + stream.clear(); formattedWrite(stream, "%x", 15); + assert(stream.data == "f"); + stream.clear(); formattedWrite(stream, "%X", 15); + assert(stream.data == "F"); + + @trusted void ObjectTest() + { + Object c = null; + stream.clear(); formattedWrite(stream, "%s", c); + assert(stream.data == "null"); + } + ObjectTest(); + + enum TestEnum + { + Value1, Value2 + } + stream.clear(); formattedWrite(stream, "%s", TestEnum.Value2); + assert(stream.data == "Value2", stream.data); + stream.clear(); formattedWrite(stream, "%s", cast(TestEnum) 5); + assert(stream.data == "cast(TestEnum)5", stream.data); + + //immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); + //stream.clear(); formattedWrite(stream, "%s", aa.values); + //core.stdc.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr); + //assert(stream.data == "[[h,e,l,l,o],[b,e,t,t,y]]"); + //stream.clear(); formattedWrite(stream, "%s", aa); + //assert(stream.data == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]"); + + static const dchar[] ds = ['a','b']; + for (int j = 0; j < ds.length; ++j) + { + stream.clear(); formattedWrite(stream, " %d", ds[j]); + if (j == 0) + assert(stream.data == " 97"); + else + assert(stream.data == " 98"); + } + + stream.clear(); formattedWrite(stream, "%.-3d", 7); + assert(stream.data == "7", ">" ~ stream.data ~ "<"); +} + +@safe unittest +{ + import std.array; + import std.stdio; + + immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); + assert(aa[3] == "hello"); + assert(aa[4] == "betty"); + + auto stream = appender!(char[])(); + alias AllNumerics = + AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, + float, double, real); + foreach (T; AllNumerics) + { + T value = 1; + stream.clear(); + formattedWrite(stream, "%s", value); + assert(stream.data == "1"); + } + + stream.clear(); + formattedWrite(stream, "%s", aa); +} + +@system unittest +{ + string s = "hello!124:34.5"; + string a; + int b; + double c; + formattedRead(s, "%s!%s:%s", &a, &b, &c); + assert(a == "hello" && b == 124 && c == 34.5); +} + +version (unittest) +void formatReflectTest(T)(ref T val, string fmt, string formatted, string fn = __FILE__, size_t ln = __LINE__) +{ + import core.exception : AssertError; + import std.array : appender; + auto w = appender!string(); + formattedWrite(w, fmt, val); + + auto input = w.data; + enforce!AssertError( + input == formatted, + input, fn, ln); + + T val2; + formattedRead(input, fmt, &val2); + static if (isAssociativeArray!T) + if (__ctfe) + { + alias aa1 = val; + alias aa2 = val2; + assert(aa1 == aa2); + + assert(aa1.length == aa2.length); + + assert(aa1.keys == aa2.keys); + + assert(aa1.values == aa2.values); + assert(aa1.values.length == aa2.values.length); + foreach (i; 0 .. aa1.values.length) + assert(aa1.values[i] == aa2.values[i]); + + foreach (i, key; aa1.keys) + assert(aa1.values[i] == aa1[key]); + foreach (i, key; aa2.keys) + assert(aa2.values[i] == aa2[key]); + return; + } + enforce!AssertError( + val == val2, + input, fn, ln); +} + +version (unittest) +void formatReflectTest(T)(ref T val, string fmt, string[] formatted, string fn = __FILE__, size_t ln = __LINE__) +{ + import core.exception : AssertError; + import std.array : appender; + auto w = appender!string(); + formattedWrite(w, fmt, val); + + auto input = w.data; + + foreach (cur; formatted) + { + if (input == cur) return; + } + enforce!AssertError( + false, + input, + fn, + ln); + + T val2; + formattedRead(input, fmt, &val2); + static if (isAssociativeArray!T) + if (__ctfe) + { + alias aa1 = val; + alias aa2 = val2; + assert(aa1 == aa2); + + assert(aa1.length == aa2.length); + + assert(aa1.keys == aa2.keys); + + assert(aa1.values == aa2.values); + assert(aa1.values.length == aa2.values.length); + foreach (i; 0 .. aa1.values.length) + assert(aa1.values[i] == aa2.values[i]); + + foreach (i, key; aa1.keys) + assert(aa1.values[i] == aa1[key]); + foreach (i, key; aa2.keys) + assert(aa2.values[i] == aa2[key]); + return; + } + enforce!AssertError( + val == val2, + input, fn, ln); +} + +@system unittest +{ + void booleanTest() + { + auto b = true; + formatReflectTest(b, "%s", `true`); + formatReflectTest(b, "%b", `1`); + formatReflectTest(b, "%o", `1`); + formatReflectTest(b, "%d", `1`); + formatReflectTest(b, "%u", `1`); + formatReflectTest(b, "%x", `1`); + } + + void integerTest() + { + auto n = 127; + formatReflectTest(n, "%s", `127`); + formatReflectTest(n, "%b", `1111111`); + formatReflectTest(n, "%o", `177`); + formatReflectTest(n, "%d", `127`); + formatReflectTest(n, "%u", `127`); + formatReflectTest(n, "%x", `7f`); + } + + void floatingTest() + { + auto f = 3.14; + formatReflectTest(f, "%s", `3.14`); + version (MinGW) + formatReflectTest(f, "%e", `3.140000e+000`); + else + formatReflectTest(f, "%e", `3.140000e+00`); + formatReflectTest(f, "%f", `3.140000`); + formatReflectTest(f, "%g", `3.14`); + } + + void charTest() + { + auto c = 'a'; + formatReflectTest(c, "%s", `a`); + formatReflectTest(c, "%c", `a`); + formatReflectTest(c, "%b", `1100001`); + formatReflectTest(c, "%o", `141`); + formatReflectTest(c, "%d", `97`); + formatReflectTest(c, "%u", `97`); + formatReflectTest(c, "%x", `61`); + } + + void strTest() + { + auto s = "hello"; + formatReflectTest(s, "%s", `hello`); + formatReflectTest(s, "%(%c,%)", `h,e,l,l,o`); + formatReflectTest(s, "%(%s,%)", `'h','e','l','l','o'`); + formatReflectTest(s, "[%(<%c>%| $ %)]", `[ $ $ $ $ ]`); + } + + void daTest() + { + auto a = [1,2,3,4]; + formatReflectTest(a, "%s", `[1, 2, 3, 4]`); + formatReflectTest(a, "[%(%s; %)]", `[1; 2; 3; 4]`); + formatReflectTest(a, "[%(<%s>%| $ %)]", `[<1> $ <2> $ <3> $ <4>]`); + } + + void saTest() + { + int[4] sa = [1,2,3,4]; + formatReflectTest(sa, "%s", `[1, 2, 3, 4]`); + formatReflectTest(sa, "[%(%s; %)]", `[1; 2; 3; 4]`); + formatReflectTest(sa, "[%(<%s>%| $ %)]", `[<1> $ <2> $ <3> $ <4>]`); + } + + void aaTest() + { + auto aa = [1:"hello", 2:"world"]; + formatReflectTest(aa, "%s", [`[1:"hello", 2:"world"]`, `[2:"world", 1:"hello"]`]); + formatReflectTest(aa, "[%(%s->%s, %)]", [`[1->"hello", 2->"world"]`, `[2->"world", 1->"hello"]`]); + formatReflectTest(aa, "{%([%s=%(%c%)]%|; %)}", [`{[1=hello]; [2=world]}`, `{[2=world]; [1=hello]}`]); + } + + import std.exception; + assertCTFEable!( + { + booleanTest(); + integerTest(); + if (!__ctfe) floatingTest(); // snprintf + charTest(); + strTest(); + daTest(); + saTest(); + aaTest(); + return true; + }); +} + +//------------------------------------------------------------------------------ +private void skipData(Range, Char)(ref Range input, const ref FormatSpec!Char spec) +{ + import std.ascii : isDigit; + import std.conv : text; + + switch (spec.spec) + { + case 'c': input.popFront(); break; + case 'd': + if (input.front == '+' || input.front == '-') input.popFront(); + goto case 'u'; + case 'u': + while (!input.empty && isDigit(input.front)) input.popFront(); + break; + default: + assert(false, + text("Format specifier not understood: %", spec.spec)); + } +} + +private template acceptedSpecs(T) +{ + static if (isIntegral!T) enum acceptedSpecs = "bdosuxX"; + else static if (isFloatingPoint!T) enum acceptedSpecs = "seEfgG"; + else static if (isSomeChar!T) enum acceptedSpecs = "bcdosuxX"; // integral + 'c' + else enum acceptedSpecs = ""; +} + +/** + * Reads a value from the given _input range according to spec + * and returns it as type `T`. + * + * Params: + * T = the type to return + * input = the _input range to read from + * spec = the `FormatSpec` to use when reading from `input` + * Returns: + * A value from `input` of type `T` + * Throws: + * An `Exception` if `spec` cannot read a type `T` + * See_Also: + * $(REF parse, std, conv) and $(REF to, std, conv) + */ +T unformatValue(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) +{ + return unformatValueImpl!T(input, spec); +} + +/// Booleans +@safe pure unittest +{ + auto str = "false"; + auto spec = singleSpec("%s"); + assert(unformatValue!bool(str, spec) == false); + + str = "1"; + spec = singleSpec("%d"); + assert(unformatValue!bool(str, spec)); +} + +/// Null values +@safe pure unittest +{ + auto str = "null"; + auto spec = singleSpec("%s"); + assert(str.unformatValue!(typeof(null))(spec) == null); +} + +/// Integrals +@safe pure unittest +{ + auto str = "123"; + auto spec = singleSpec("%s"); + assert(str.unformatValue!int(spec) == 123); + + str = "ABC"; + spec = singleSpec("%X"); + assert(str.unformatValue!int(spec) == 2748); + + str = "11610"; + spec = singleSpec("%o"); + assert(str.unformatValue!int(spec) == 5000); +} + +/// Floating point numbers +@safe pure unittest +{ + import std.math : approxEqual; + + auto str = "123.456"; + auto spec = singleSpec("%s"); + assert(str.unformatValue!double(spec).approxEqual(123.456)); +} + +/// Character input ranges +@safe pure unittest +{ + auto str = "aaa"; + auto spec = singleSpec("%s"); + assert(str.unformatValue!char(spec) == 'a'); + + // Using a numerical format spec reads a Unicode value from a string + str = "65"; + spec = singleSpec("%d"); + assert(str.unformatValue!char(spec) == 'A'); + + str = "41"; + spec = singleSpec("%x"); + assert(str.unformatValue!char(spec) == 'A'); + + str = "10003"; + spec = singleSpec("%d"); + assert(str.unformatValue!dchar(spec) == '✓'); +} + +/// Arrays and static arrays +@safe pure unittest +{ + string str = "aaa"; + auto spec = singleSpec("%s"); + assert(str.unformatValue!(dchar[])(spec) == "aaa"d); + + str = "aaa"; + spec = singleSpec("%s"); + dchar[3] ret = ['a', 'a', 'a']; + assert(str.unformatValue!(dchar[3])(spec) == ret); + + str = "[1, 2, 3, 4]"; + spec = singleSpec("%s"); + assert(str.unformatValue!(int[])(spec) == [1, 2, 3, 4]); + + str = "[1, 2, 3, 4]"; + spec = singleSpec("%s"); + int[4] ret2 = [1, 2, 3, 4]; + assert(str.unformatValue!(int[4])(spec) == ret2); +} + +/// Associative arrays +@safe pure unittest +{ + auto str = `["one": 1, "two": 2]`; + auto spec = singleSpec("%s"); + assert(str.unformatValue!(int[string])(spec) == ["one": 1, "two": 2]); +} + +@safe pure unittest +{ + // 7241 + string input = "a"; + auto spec = FormatSpec!char("%s"); + spec.readUpToNextSpec(input); + auto result = unformatValue!(dchar[1])(input, spec); + assert(result[0] == 'a'); +} + +private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) +if (isInputRange!Range && is(Unqual!T == bool)) +{ + import std.algorithm.searching : find; + import std.conv : parse, text; + + if (spec.spec == 's') return parse!T(input); + + enforce(find(acceptedSpecs!long, spec.spec).length, + text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); + + return unformatValue!long(input, spec) != 0; +} + +private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) +if (isInputRange!Range && is(T == typeof(null))) +{ + import std.conv : parse, text; + enforce(spec.spec == 's', + text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); + + return parse!T(input); +} + +/// ditto +private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) +if (isInputRange!Range && isIntegral!T && !is(T == enum) && isSomeChar!(ElementType!Range)) +{ + + import std.algorithm.searching : find; + import std.conv : parse, text; + + if (spec.spec == 'r') + { + static if (is(Unqual!(ElementEncodingType!Range) == char) + || is(Unqual!(ElementEncodingType!Range) == byte) + || is(Unqual!(ElementEncodingType!Range) == ubyte)) + return rawRead!T(input); + else + throw new Exception("The raw read specifier %r may only be used with narrow strings and ranges of bytes."); + } + + enforce(find(acceptedSpecs!T, spec.spec).length, + text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); + + enforce(spec.width == 0, "Parsing integers with a width specification is not implemented"); // TODO + + immutable uint base = + spec.spec == 'x' || spec.spec == 'X' ? 16 : + spec.spec == 'o' ? 8 : + spec.spec == 'b' ? 2 : + spec.spec == 's' || spec.spec == 'd' || spec.spec == 'u' ? 10 : 0; + assert(base != 0); + + return parse!T(input, base); + +} + +/// ditto +private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) +if (isFloatingPoint!T && !is(T == enum) && isInputRange!Range + && isSomeChar!(ElementType!Range)&& !is(Range == enum)) +{ + import std.algorithm.searching : find; + import std.conv : parse, text; + + if (spec.spec == 'r') + { + static if (is(Unqual!(ElementEncodingType!Range) == char) + || is(Unqual!(ElementEncodingType!Range) == byte) + || is(Unqual!(ElementEncodingType!Range) == ubyte)) + return rawRead!T(input); + else + throw new Exception("The raw read specifier %r may only be used with narrow strings and ranges of bytes."); + } + + enforce(find(acceptedSpecs!T, spec.spec).length, + text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); + + return parse!T(input); +} + +/// ditto +private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) +if (isInputRange!Range && isSomeChar!T && !is(T == enum) && isSomeChar!(ElementType!Range)) +{ + import std.algorithm.searching : find; + import std.conv : to, text; + if (spec.spec == 's' || spec.spec == 'c') + { + auto result = to!T(input.front); + input.popFront(); + return result; + } + enforce(find(acceptedSpecs!T, spec.spec).length, + text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); + + static if (T.sizeof == 1) + return unformatValue!ubyte(input, spec); + else static if (T.sizeof == 2) + return unformatValue!ushort(input, spec); + else static if (T.sizeof == 4) + return unformatValue!uint(input, spec); + else + static assert(0); +} + +/// ditto +private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) +if (isInputRange!Range && is(StringTypeOf!T) && !isAggregateType!T && !is(T == enum)) +{ + import std.conv : text; + + if (spec.spec == '(') + { + return unformatRange!T(input, spec); + } + enforce(spec.spec == 's', + text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); + + static if (isStaticArray!T) + { + T result; + auto app = result[]; + } + else + { + import std.array : appender; + auto app = appender!T(); + } + if (spec.trailing.empty) + { + for (; !input.empty; input.popFront()) + { + static if (isStaticArray!T) + if (app.empty) + break; + app.put(input.front); + } + } + else + { + immutable end = spec.trailing.front; + for (; !input.empty && input.front != end; input.popFront()) + { + static if (isStaticArray!T) + if (app.empty) + break; + app.put(input.front); + } + } + static if (isStaticArray!T) + { + enforce(app.empty, "need more input"); + return result; + } + else + return app.data; +} + +/// ditto +private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) +if (isInputRange!Range && isArray!T && !is(StringTypeOf!T) && !isAggregateType!T && !is(T == enum)) +{ + import std.conv : parse, text; + if (spec.spec == '(') + { + return unformatRange!T(input, spec); + } + enforce(spec.spec == 's', + text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); + + return parse!T(input); +} + +/// ditto +private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) +if (isInputRange!Range && isAssociativeArray!T && !is(T == enum)) +{ + import std.conv : parse, text; + if (spec.spec == '(') + { + return unformatRange!T(input, spec); + } + enforce(spec.spec == 's', + text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); + + return parse!T(input); +} + +/** + * Function that performs raw reading. Used by unformatValue + * for integral and float types. + */ +private T rawRead(T, Range)(ref Range input) +if (is(Unqual!(ElementEncodingType!Range) == char) + || is(Unqual!(ElementEncodingType!Range) == byte) + || is(Unqual!(ElementEncodingType!Range) == ubyte)) +{ + union X + { + ubyte[T.sizeof] raw; + T typed; + } + X x; + foreach (i; 0 .. T.sizeof) + { + static if (isSomeString!Range) + { + x.raw[i] = input[0]; + input = input[1 .. $]; + } + else + { + // TODO: recheck this + x.raw[i] = input.front; + input.popFront(); + } + } + return x.typed; +} + +//debug = unformatRange; + +private T unformatRange(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) +in +{ + assert(spec.spec == '('); +} +body +{ + debug (unformatRange) printf("unformatRange:\n"); + + T result; + static if (isStaticArray!T) + { + size_t i; + } + + const(Char)[] cont = spec.trailing; + for (size_t j = 0; j < spec.trailing.length; ++j) + { + if (spec.trailing[j] == '%') + { + cont = spec.trailing[0 .. j]; + break; + } + } + debug (unformatRange) printf("\t"); + debug (unformatRange) if (!input.empty) printf("input.front = %c, ", input.front); + debug (unformatRange) printf("cont = %.*s\n", cont); + + bool checkEnd() + { + return input.empty || !cont.empty && input.front == cont.front; + } + + if (!checkEnd()) + { + for (;;) + { + auto fmt = FormatSpec!Char(spec.nested); + fmt.readUpToNextSpec(input); + enforce(!input.empty, "Unexpected end of input when parsing range"); + + debug (unformatRange) printf("\t) spec = %c, front = %c ", fmt.spec, input.front); + static if (isStaticArray!T) + { + result[i++] = unformatElement!(typeof(T.init[0]))(input, fmt); + } + else static if (isDynamicArray!T) + { + result ~= unformatElement!(ElementType!T)(input, fmt); + } + else static if (isAssociativeArray!T) + { + auto key = unformatElement!(typeof(T.init.keys[0]))(input, fmt); + fmt.readUpToNextSpec(input); // eat key separator + + result[key] = unformatElement!(typeof(T.init.values[0]))(input, fmt); + } + debug (unformatRange) { + if (input.empty) printf("-> front = [empty] "); + else printf("-> front = %c ", input.front); + } + + static if (isStaticArray!T) + { + debug (unformatRange) printf("i = %u < %u\n", i, T.length); + enforce(i <= T.length, "Too many format specifiers for static array of length %d".format(T.length)); + } + + if (spec.sep !is null) + fmt.readUpToNextSpec(input); + auto sep = spec.sep !is null ? spec.sep + : fmt.trailing; + debug (unformatRange) { + if (!sep.empty && !input.empty) printf("-> %c, sep = %.*s\n", input.front, sep); + else printf("\n"); + } + + if (checkEnd()) + break; + + if (!sep.empty && input.front == sep.front) + { + while (!sep.empty) + { + enforce(!input.empty, "Unexpected end of input when parsing range separator"); + enforce(input.front == sep.front, "Unexpected character when parsing range separator"); + input.popFront(); + sep.popFront(); + } + debug (unformatRange) printf("input.front = %c\n", input.front); + } + } + } + static if (isStaticArray!T) + { + enforce(i == T.length, "Too few (%d) format specifiers for static array of length %d".format(i, T.length)); + } + return result; +} + +// Undocumented +T unformatElement(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) +if (isInputRange!Range) +{ + import std.conv : parseElement; + static if (isSomeString!T) + { + if (spec.spec == 's') + { + return parseElement!T(input); + } + } + else static if (isSomeChar!T) + { + if (spec.spec == 's') + { + return parseElement!T(input); + } + } + + return unformatValue!T(input, spec); +} + + +// Legacy implementation + +enum Mangle : char +{ + Tvoid = 'v', + Tbool = 'b', + Tbyte = 'g', + Tubyte = 'h', + Tshort = 's', + Tushort = 't', + Tint = 'i', + Tuint = 'k', + Tlong = 'l', + Tulong = 'm', + Tfloat = 'f', + Tdouble = 'd', + Treal = 'e', + + Tifloat = 'o', + Tidouble = 'p', + Tireal = 'j', + Tcfloat = 'q', + Tcdouble = 'r', + Tcreal = 'c', + + Tchar = 'a', + Twchar = 'u', + Tdchar = 'w', + + Tarray = 'A', + Tsarray = 'G', + Taarray = 'H', + Tpointer = 'P', + Tfunction = 'F', + Tident = 'I', + Tclass = 'C', + Tstruct = 'S', + Tenum = 'E', + Ttypedef = 'T', + Tdelegate = 'D', + + Tconst = 'x', + Timmutable = 'y', +} + +// return the TypeInfo for a primitive type and null otherwise. This +// is required since for arrays of ints we only have the mangled char +// to work from. If arrays always subclassed TypeInfo_Array this +// routine could go away. +private TypeInfo primitiveTypeInfo(Mangle m) +{ + // BUG: should fix this in static this() to avoid double checked locking bug + __gshared TypeInfo[Mangle] dic; + if (!dic.length) + { + dic = [ + Mangle.Tvoid : typeid(void), + Mangle.Tbool : typeid(bool), + Mangle.Tbyte : typeid(byte), + Mangle.Tubyte : typeid(ubyte), + Mangle.Tshort : typeid(short), + Mangle.Tushort : typeid(ushort), + Mangle.Tint : typeid(int), + Mangle.Tuint : typeid(uint), + Mangle.Tlong : typeid(long), + Mangle.Tulong : typeid(ulong), + Mangle.Tfloat : typeid(float), + Mangle.Tdouble : typeid(double), + Mangle.Treal : typeid(real), + Mangle.Tifloat : typeid(ifloat), + Mangle.Tidouble : typeid(idouble), + Mangle.Tireal : typeid(ireal), + Mangle.Tcfloat : typeid(cfloat), + Mangle.Tcdouble : typeid(cdouble), + Mangle.Tcreal : typeid(creal), + Mangle.Tchar : typeid(char), + Mangle.Twchar : typeid(wchar), + Mangle.Tdchar : typeid(dchar) + ]; + } + auto p = m in dic; + return p ? *p : null; +} + +private bool needToSwapEndianess(Char)(const ref FormatSpec!Char f) +{ + import std.system : endian, Endian; + + return endian == Endian.littleEndian && f.flPlus + || endian == Endian.bigEndian && f.flDash; +} + +/* ======================== Unit Tests ====================================== */ + +@system unittest +{ + import std.conv : octal; + + int i; + string s; + + debug(format) printf("std.format.format.unittest\n"); + + s = format("hello world! %s %s %s%s%s", true, 57, 1_000_000_000, 'x', " foo"); + assert(s == "hello world! true 57 1000000000x foo"); + + s = format("%s %A %s", 1.67, -1.28, float.nan); + /* The host C library is used to format floats. + * C99 doesn't specify what the hex digit before the decimal point + * is for %A. + */ + //version (linux) + // assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan"); + //else version (OSX) + // assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan", s); + //else + version (MinGW) + assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan", s); + else version (CRuntime_Microsoft) + assert(s == "1.67 -0X1.47AE14P+0 nan" + || s == "1.67 -0X1.47AE147AE147BP+0 nan", s); // MSVCRT 14+ (VS 2015) + else + assert(s == "1.67 -0X1.47AE147AE147BP+0 nan", s); + + s = format("%x %X", 0x1234AF, 0xAFAFAFAF); + assert(s == "1234af AFAFAFAF"); + + s = format("%b %o", 0x1234AF, 0xAFAFAFAF); + assert(s == "100100011010010101111 25753727657"); + + s = format("%d %s", 0x1234AF, 0xAFAFAFAF); + assert(s == "1193135 2947526575"); + + //version (X86_64) + //{ + // pragma(msg, "several format tests disabled on x86_64 due to bug 5625"); + //} + //else + //{ + s = format("%s", 1.2 + 3.4i); + assert(s == "1.2+3.4i", s); + + //s = format("%x %X", 1.32, 6.78f); + //assert(s == "3ff51eb851eb851f 40D8F5C3"); + + //} + + s = format("%#06.*f",2,12.345); + assert(s == "012.35"); + + s = format("%#0*.*f",6,2,12.345); + assert(s == "012.35"); + + s = format("%7.4g:", 12.678); + assert(s == " 12.68:"); + + s = format("%7.4g:", 12.678L); + assert(s == " 12.68:"); + + s = format("%04f|%05d|%#05x|%#5x",-4.0,-10,1,1); + assert(s == "-4.000000|-0010|0x001| 0x1"); + + i = -10; + s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); + assert(s == "-10|-10|-10|-10|-10.0000"); + + i = -5; + s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); + assert(s == "-5| -5|-05|-5|-5.0000"); + + i = 0; + s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); + assert(s == "0| 0|000|0|0.0000"); + + i = 5; + s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); + assert(s == "5| 5|005|5|5.0000"); + + i = 10; + s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); + assert(s == "10| 10|010|10|10.0000"); + + s = format("%.0d", 0); + assert(s == ""); + + s = format("%.g", .34); + assert(s == "0.3"); + + s = format("%.0g", .34); + assert(s == "0.3"); + + s = format("%.2g", .34); + assert(s == "0.34"); + + s = format("%0.0008f", 1e-08); + assert(s == "0.00000001"); + + s = format("%0.0008f", 1e-05); + assert(s == "0.00001000"); + + s = "helloworld"; + string r; + r = format("%.2s", s[0 .. 5]); + assert(r == "he"); + r = format("%.20s", s[0 .. 5]); + assert(r == "hello"); + r = format("%8s", s[0 .. 5]); + assert(r == " hello"); + + byte[] arrbyte = new byte[4]; + arrbyte[0] = 100; + arrbyte[1] = -99; + arrbyte[3] = 0; + r = format("%s", arrbyte); + assert(r == "[100, -99, 0, 0]"); + + ubyte[] arrubyte = new ubyte[4]; + arrubyte[0] = 100; + arrubyte[1] = 200; + arrubyte[3] = 0; + r = format("%s", arrubyte); + assert(r == "[100, 200, 0, 0]"); + + short[] arrshort = new short[4]; + arrshort[0] = 100; + arrshort[1] = -999; + arrshort[3] = 0; + r = format("%s", arrshort); + assert(r == "[100, -999, 0, 0]"); + + ushort[] arrushort = new ushort[4]; + arrushort[0] = 100; + arrushort[1] = 20_000; + arrushort[3] = 0; + r = format("%s", arrushort); + assert(r == "[100, 20000, 0, 0]"); + + int[] arrint = new int[4]; + arrint[0] = 100; + arrint[1] = -999; + arrint[3] = 0; + r = format("%s", arrint); + assert(r == "[100, -999, 0, 0]"); + + long[] arrlong = new long[4]; + arrlong[0] = 100; + arrlong[1] = -999; + arrlong[3] = 0; + r = format("%s", arrlong); + assert(r == "[100, -999, 0, 0]"); + + ulong[] arrulong = new ulong[4]; + arrulong[0] = 100; + arrulong[1] = 999; + arrulong[3] = 0; + r = format("%s", arrulong); + assert(r == "[100, 999, 0, 0]"); + + string[] arr2 = new string[4]; + arr2[0] = "hello"; + arr2[1] = "world"; + arr2[3] = "foo"; + r = format("%s", arr2); + assert(r == `["hello", "world", "", "foo"]`); + + r = format("%.8d", 7); + assert(r == "00000007"); + r = format("%.8x", 10); + assert(r == "0000000a"); + + r = format("%-3d", 7); + assert(r == "7 "); + + r = format("%-1*d", 4, 3); + assert(r == "3 "); + + r = format("%*d", -3, 7); + assert(r == "7 "); + + r = format("%.*d", -3, 7); + assert(r == "7"); + + r = format("%-1.*f", 2, 3.1415); + assert(r == "3.14"); + + r = format("abc"c); + assert(r == "abc"); + + //format() returns the same type as inputted. + wstring wr; + wr = format("def"w); + assert(wr == "def"w); + + dstring dr; + dr = format("ghi"d); + assert(dr == "ghi"d); + + void* p = cast(void*) 0xDEADBEEF; + r = format("%s", p); + assert(r == "DEADBEEF"); + + r = format("%#x", 0xabcd); + assert(r == "0xabcd"); + r = format("%#X", 0xABCD); + assert(r == "0XABCD"); + + r = format("%#o", octal!12345); + assert(r == "012345"); + r = format("%o", 9); + assert(r == "11"); + r = format("%#o", 0); // issue 15663 + assert(r == "0"); + + r = format("%+d", 123); + assert(r == "+123"); + r = format("%+d", -123); + assert(r == "-123"); + r = format("% d", 123); + assert(r == " 123"); + r = format("% d", -123); + assert(r == "-123"); + + r = format("%%"); + assert(r == "%"); + + r = format("%d", true); + assert(r == "1"); + r = format("%d", false); + assert(r == "0"); + + r = format("%d", 'a'); + assert(r == "97"); + wchar wc = 'a'; + r = format("%d", wc); + assert(r == "97"); + dchar dc = 'a'; + r = format("%d", dc); + assert(r == "97"); + + byte b = byte.max; + r = format("%x", b); + assert(r == "7f"); + r = format("%x", ++b); + assert(r == "80"); + r = format("%x", ++b); + assert(r == "81"); + + short sh = short.max; + r = format("%x", sh); + assert(r == "7fff"); + r = format("%x", ++sh); + assert(r == "8000"); + r = format("%x", ++sh); + assert(r == "8001"); + + i = int.max; + r = format("%x", i); + assert(r == "7fffffff"); + r = format("%x", ++i); + assert(r == "80000000"); + r = format("%x", ++i); + assert(r == "80000001"); + + r = format("%x", 10); + assert(r == "a"); + r = format("%X", 10); + assert(r == "A"); + r = format("%x", 15); + assert(r == "f"); + r = format("%X", 15); + assert(r == "F"); + + Object c = null; + r = format("%s", c); + assert(r == "null"); + + enum TestEnum + { + Value1, Value2 + } + r = format("%s", TestEnum.Value2); + assert(r == "Value2"); + + immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); + r = format("%s", aa.values); + assert(r == `["hello", "betty"]` || r == `["betty", "hello"]`); + r = format("%s", aa); + assert(r == `[3:"hello", 4:"betty"]` || r == `[4:"betty", 3:"hello"]`); + + static const dchar[] ds = ['a','b']; + for (int j = 0; j < ds.length; ++j) + { + r = format(" %d", ds[j]); + if (j == 0) + assert(r == " 97"); + else + assert(r == " 98"); + } + + r = format(">%14d<, %s", 15, [1,2,3]); + assert(r == "> 15<, [1, 2, 3]"); + + assert(format("%8s", "bar") == " bar"); + assert(format("%8s", "b\u00e9ll\u00f4") == " b\u00e9ll\u00f4"); +} + +@safe unittest +{ + // bugzilla 3479 + import std.array; + auto stream = appender!(char[])(); + formattedWrite(stream, "%2$.*1$d", 12, 10); + assert(stream.data == "000000000010", stream.data); +} + +@safe unittest +{ + // bug 6893 + import std.array; + enum E : ulong { A, B, C } + auto stream = appender!(char[])(); + formattedWrite(stream, "%s", E.C); + assert(stream.data == "C"); +} + +// Used to check format strings are compatible with argument types +package static const checkFormatException(alias fmt, Args...) = +{ + try + .format(fmt, Args.init); + catch (Exception e) + return (e.msg == ctfpMessage) ? null : e; + return null; +}(); + +/***************************************************** + * Format arguments into a string. + * + * Params: fmt = Format string. For detailed specification, see $(LREF formattedWrite). + * args = Variadic list of arguments to _format into returned string. + */ +typeof(fmt) format(alias fmt, Args...)(Args args) +if (isSomeString!(typeof(fmt))) +{ + alias e = checkFormatException!(fmt, Args); + static assert(!e, e.msg); + return .format(fmt, args); +} + +/// Type checking can be done when fmt is known at compile-time: +@safe unittest +{ + auto s = format!"%s is %s"("Pi", 3.14); + assert(s == "Pi is 3.14"); + + static assert(!__traits(compiles, {s = format!"%l"();})); // missing arg + static assert(!__traits(compiles, {s = format!""(404);})); // surplus arg + static assert(!__traits(compiles, {s = format!"%d"(4.03);})); // incompatible arg +} + +/// ditto +immutable(Char)[] format(Char, Args...)(in Char[] fmt, Args args) +if (isSomeChar!Char) +{ + import std.array : appender; + import std.format : formattedWrite, FormatException; + auto w = appender!(immutable(Char)[]); + auto n = formattedWrite(w, fmt, args); + version (all) + { + // In the future, this check will be removed to increase consistency + // with formattedWrite + import std.conv : text; + import std.exception : enforce; + enforce(n == args.length, new FormatException( + text("Orphan format arguments: args[", n, "..", args.length, "]"))); + } + return w.data; +} + +@safe pure unittest +{ + import core.exception; + import std.exception; + import std.format; + assertCTFEable!( + { +// assert(format(null) == ""); + assert(format("foo") == "foo"); + assert(format("foo%%") == "foo%"); + assert(format("foo%s", 'C') == "fooC"); + assert(format("%s foo", "bar") == "bar foo"); + assert(format("%s foo %s", "bar", "abc") == "bar foo abc"); + assert(format("foo %d", -123) == "foo -123"); + assert(format("foo %d", 123) == "foo 123"); + + assertThrown!FormatException(format("foo %s")); + assertThrown!FormatException(format("foo %s", 123, 456)); + + assert(format("hel%slo%s%s%s", "world", -138, 'c', true) == + "helworldlo-138ctrue"); + }); + + assert(is(typeof(format("happy")) == string)); + assert(is(typeof(format("happy"w)) == wstring)); + assert(is(typeof(format("happy"d)) == dstring)); +} + +// https://issues.dlang.org/show_bug.cgi?id=16661 +@safe unittest +{ + assert(format("%.2f"d, 0.4) == "0.40"); + assert("%02d"d.format(1) == "01"d); +} + +/***************************************************** + * Format arguments into buffer $(I buf) which must be large + * enough to hold the result. + * + * Returns: + * The slice of `buf` containing the formatted string. + * + * Throws: + * A `RangeError` if `buf` isn't large enough to hold the + * formatted string. + * + * A $(LREF FormatException) if the length of `args` is different + * than the number of format specifiers in `fmt`. + */ +char[] sformat(alias fmt, Args...)(char[] buf, Args args) +if (isSomeString!(typeof(fmt))) +{ + alias e = checkFormatException!(fmt, Args); + static assert(!e, e.msg); + return .sformat(buf, fmt, args); +} + +/// ditto +char[] sformat(Char, Args...)(char[] buf, in Char[] fmt, Args args) +{ + import core.exception : RangeError; + import std.format : formattedWrite, FormatException; + import std.utf : encode; + + size_t i; + + struct Sink + { + void put(dchar c) + { + char[4] enc; + auto n = encode(enc, c); + + if (buf.length < i + n) + throw new RangeError(__FILE__, __LINE__); + + buf[i .. i + n] = enc[0 .. n]; + i += n; + } + void put(const(char)[] s) + { + if (buf.length < i + s.length) + throw new RangeError(__FILE__, __LINE__); + + buf[i .. i + s.length] = s[]; + i += s.length; + } + void put(const(wchar)[] s) + { + for (; !s.empty; s.popFront()) + put(s.front); + } + void put(const(dchar)[] s) + { + for (; !s.empty; s.popFront()) + put(s.front); + } + } + auto n = formattedWrite(Sink(), fmt, args); + version (all) + { + // In the future, this check will be removed to increase consistency + // with formattedWrite + import std.conv : text; + import std.exception : enforce; + enforce!FormatException( + n == args.length, + text("Orphan format arguments: args[", n, " .. ", args.length, "]") + ); + } + return buf[0 .. i]; +} + +/// The format string can be checked at compile-time (see $(LREF format) for details): +@system unittest +{ + char[10] buf; + + assert(buf[].sformat!"foo%s"('C') == "fooC"); + assert(sformat(buf[], "%s foo", "bar") == "bar foo"); +} + +@system unittest +{ + import core.exception; + import std.format; + + debug(string) trustedPrintf("std.string.sformat.unittest\n"); + + import std.exception; + assertCTFEable!( + { + char[10] buf; + + assert(sformat(buf[], "foo") == "foo"); + assert(sformat(buf[], "foo%%") == "foo%"); + assert(sformat(buf[], "foo%s", 'C') == "fooC"); + assert(sformat(buf[], "%s foo", "bar") == "bar foo"); + assertThrown!RangeError(sformat(buf[], "%s foo %s", "bar", "abc")); + assert(sformat(buf[], "foo %d", -123) == "foo -123"); + assert(sformat(buf[], "foo %d", 123) == "foo 123"); + + assertThrown!FormatException(sformat(buf[], "foo %s")); + assertThrown!FormatException(sformat(buf[], "foo %s", 123, 456)); + + assert(sformat(buf[], "%s %s %s", "c"c, "w"w, "d"d) == "c w d"); + }); +} + +/***************************** + * The .ptr is unsafe because it could be dereferenced and the length of the array may be 0. + * Returns: + * the difference between the starts of the arrays + */ +@trusted private pure nothrow @nogc + ptrdiff_t arrayPtrDiff(T)(const T[] array1, const T[] array2) +{ + return array1.ptr - array2.ptr; +} + +@safe unittest +{ + assertCTFEable!({ + auto tmp = format("%,d", 1000); + assert(tmp == "1,000", "'" ~ tmp ~ "'"); + + tmp = format("%,?d", 'z', 1234567); + assert(tmp == "1z234z567", "'" ~ tmp ~ "'"); + + tmp = format("%10,?d", 'z', 1234567); + assert(tmp == " 1z234z567", "'" ~ tmp ~ "'"); + + tmp = format("%11,2?d", 'z', 1234567); + assert(tmp == " 1z23z45z67", "'" ~ tmp ~ "'"); + + tmp = format("%11,*?d", 2, 'z', 1234567); + assert(tmp == " 1z23z45z67", "'" ~ tmp ~ "'"); + + tmp = format("%11,*d", 2, 1234567); + assert(tmp == " 1,23,45,67", "'" ~ tmp ~ "'"); + + tmp = format("%11,2d", 1234567); + assert(tmp == " 1,23,45,67", "'" ~ tmp ~ "'"); + }); +} + +@safe unittest +{ + auto tmp = format("%,f", 1000.0); + assert(tmp == "1,000.000,000", "'" ~ tmp ~ "'"); + + tmp = format("%,f", 1234567.891011); + assert(tmp == "1,234,567.891,011", "'" ~ tmp ~ "'"); + + tmp = format("%,f", -1234567.891011); + assert(tmp == "-1,234,567.891,011", "'" ~ tmp ~ "'"); + + tmp = format("%,2f", 1234567.891011); + assert(tmp == "1,23,45,67.89,10,11", "'" ~ tmp ~ "'"); + + tmp = format("%18,f", 1234567.891011); + assert(tmp == " 1,234,567.891,011", "'" ~ tmp ~ "'"); + + tmp = format("%18,?f", '.', 1234567.891011); + assert(tmp == " 1.234.567.891.011", "'" ~ tmp ~ "'"); + + tmp = format("%,?.3f", 'ä', 1234567.891011); + assert(tmp == "1ä234ä567.891", "'" ~ tmp ~ "'"); + + tmp = format("%,*?.3f", 1, 'ä', 1234567.891011); + assert(tmp == "1ä2ä3ä4ä5ä6ä7.8ä9ä1", "'" ~ tmp ~ "'"); + + tmp = format("%,4?.3f", '_', 1234567.891011); + assert(tmp == "123_4567.891", "'" ~ tmp ~ "'"); + + tmp = format("%12,3.3f", 1234.5678); + assert(tmp == " 1,234.568", "'" ~ tmp ~ "'"); + + tmp = format("%,e", 3.141592653589793238462); + assert(tmp == "3.141,593e+00", "'" ~ tmp ~ "'"); + + tmp = format("%15,e", 3.141592653589793238462); + assert(tmp == " 3.141,593e+00", "'" ~ tmp ~ "'"); + + tmp = format("%15,e", -3.141592653589793238462); + assert(tmp == " -3.141,593e+00", "'" ~ tmp ~ "'"); + + tmp = format("%.4,*e", 2, 3.141592653589793238462); + assert(tmp == "3.14,16e+00", "'" ~ tmp ~ "'"); + + tmp = format("%13.4,*e", 2, 3.141592653589793238462); + assert(tmp == " 3.14,16e+00", "'" ~ tmp ~ "'"); + + tmp = format("%,.0f", 3.14); + assert(tmp == "3", "'" ~ tmp ~ "'"); + + tmp = format("%3,g", 1_000_000.123456); + assert(tmp == "1e+06", "'" ~ tmp ~ "'"); + + tmp = format("%19,?f", '.', -1234567.891011); + assert(tmp == " -1.234.567.891.011", "'" ~ tmp ~ "'"); +} + +// Test for multiple indexes +@safe unittest +{ + auto tmp = format("%2:5$s", 1, 2, 3, 4, 5); + assert(tmp == "2345", tmp); +} diff --git a/libphobos/src/std/functional.d b/libphobos/src/std/functional.d new file mode 100644 index 0000000..f35d6ff --- /dev/null +++ b/libphobos/src/std/functional.d @@ -0,0 +1,1564 @@ +// Written in the D programming language. + +/** +Functions that manipulate other functions. + +This module provides functions for compile time function composition. These +functions are helpful when constructing predicates for the algorithms in +$(MREF std, algorithm) or $(MREF std, range). + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE , +$(TR $(TH Function Name) $(TH Description) +) + $(TR $(TD $(LREF adjoin)) + $(TD Joins a couple of functions into one that executes the original + functions independently and returns a tuple with all the results. + )) + $(TR $(TD $(LREF compose), $(LREF pipe)) + $(TD Join a couple of functions into one that executes the original + functions one after the other, using one function's result for the next + function's argument. + )) + $(TR $(TD $(LREF forward)) + $(TD Forwards function arguments while saving ref-ness. + )) + $(TR $(TD $(LREF lessThan), $(LREF greaterThan), $(LREF equalTo)) + $(TD Ready-made predicate functions to compare two values. + )) + $(TR $(TD $(LREF memoize)) + $(TD Creates a function that caches its result for fast re-evaluation. + )) + $(TR $(TD $(LREF not)) + $(TD Creates a function that negates another. + )) + $(TR $(TD $(LREF partial)) + $(TD Creates a function that binds the first argument of a given function + to a given value. + )) + $(TR $(TD $(LREF reverseArgs), $(LREF binaryReverseArgs)) + $(TD Predicate that reverses the order of its arguments. + )) + $(TR $(TD $(LREF toDelegate)) + $(TD Converts a callable to a delegate. + )) + $(TR $(TD $(LREF unaryFun), $(LREF binaryFun)) + $(TD Create a unary or binary function from a string. Most often + used when defining algorithms on ranges. + )) +) + +Copyright: Copyright Andrei Alexandrescu 2008 - 2009. +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). +Authors: $(HTTP erdani.org, Andrei Alexandrescu) +Source: $(PHOBOSSRC std/_functional.d) +*/ +/* + Copyright Andrei Alexandrescu 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) +*/ +module std.functional; + +import std.meta; // AliasSeq, Reverse +import std.traits; // isCallable, Parameters + + +private template needOpCallAlias(alias fun) +{ + /* Determine whether or not unaryFun and binaryFun need to alias to fun or + * fun.opCall. Basically, fun is a function object if fun(...) compiles. We + * want is(unaryFun!fun) (resp., is(binaryFun!fun)) to be true if fun is + * any function object. There are 4 possible cases: + * + * 1) fun is the type of a function object with static opCall; + * 2) fun is an instance of a function object with static opCall; + * 3) fun is the type of a function object with non-static opCall; + * 4) fun is an instance of a function object with non-static opCall. + * + * In case (1), is(unaryFun!fun) should compile, but does not if unaryFun + * aliases itself to fun, because typeof(fun) is an error when fun itself + * is a type. So it must be aliased to fun.opCall instead. All other cases + * should be aliased to fun directly. + */ + static if (is(typeof(fun.opCall) == function)) + { + enum needOpCallAlias = !is(typeof(fun)) && __traits(compiles, () { + return fun(Parameters!fun.init); + }); + } + else + enum needOpCallAlias = false; +} + +/** +Transforms a string representing an expression into a unary +function. The string must either use symbol name $(D a) as +the parameter or provide the symbol via the $(D parmName) argument. +If $(D fun) is not a string, $(D unaryFun) aliases itself away to $(D fun). +*/ + +template unaryFun(alias fun, string parmName = "a") +{ + static if (is(typeof(fun) : string)) + { + static if (!fun._ctfeMatchUnary(parmName)) + { + import std.algorithm, std.conv, std.exception, std.math, std.range, std.string; + import std.meta, std.traits, std.typecons; + } + auto unaryFun(ElementType)(auto ref ElementType __a) + { + mixin("alias " ~ parmName ~ " = __a ;"); + return mixin(fun); + } + } + else static if (needOpCallAlias!fun) + { + // Issue 9906 + alias unaryFun = fun.opCall; + } + else + { + alias unaryFun = fun; + } +} + +/// +@safe unittest +{ + // Strings are compiled into functions: + alias isEven = unaryFun!("(a & 1) == 0"); + assert(isEven(2) && !isEven(1)); +} + +@safe unittest +{ + static int f1(int a) { return a + 1; } + static assert(is(typeof(unaryFun!(f1)(1)) == int)); + assert(unaryFun!(f1)(41) == 42); + int f2(int a) { return a + 1; } + static assert(is(typeof(unaryFun!(f2)(1)) == int)); + assert(unaryFun!(f2)(41) == 42); + assert(unaryFun!("a + 1")(41) == 42); + //assert(unaryFun!("return a + 1;")(41) == 42); + + int num = 41; + assert(unaryFun!"a + 1"(num) == 42); + + // Issue 9906 + struct Seen + { + static bool opCall(int n) { return true; } + } + static assert(needOpCallAlias!Seen); + static assert(is(typeof(unaryFun!Seen(1)))); + assert(unaryFun!Seen(1)); + + Seen s; + static assert(!needOpCallAlias!s); + static assert(is(typeof(unaryFun!s(1)))); + assert(unaryFun!s(1)); + + struct FuncObj + { + bool opCall(int n) { return true; } + } + FuncObj fo; + static assert(!needOpCallAlias!fo); + static assert(is(typeof(unaryFun!fo))); + assert(unaryFun!fo(1)); + + // Function object with non-static opCall can only be called with an + // instance, not with merely the type. + static assert(!is(typeof(unaryFun!FuncObj))); +} + +/** +Transforms a string representing an expression into a binary function. The +string must either use symbol names $(D a) and $(D b) as the parameters or +provide the symbols via the $(D parm1Name) and $(D parm2Name) arguments. +If $(D fun) is not a string, $(D binaryFun) aliases itself away to +$(D fun). +*/ + +template binaryFun(alias fun, string parm1Name = "a", + string parm2Name = "b") +{ + static if (is(typeof(fun) : string)) + { + static if (!fun._ctfeMatchBinary(parm1Name, parm2Name)) + { + import std.algorithm, std.conv, std.exception, std.math, std.range, std.string; + import std.meta, std.traits, std.typecons; + } + auto binaryFun(ElementType1, ElementType2) + (auto ref ElementType1 __a, auto ref ElementType2 __b) + { + mixin("alias "~parm1Name~" = __a ;"); + mixin("alias "~parm2Name~" = __b ;"); + return mixin(fun); + } + } + else static if (needOpCallAlias!fun) + { + // Issue 9906 + alias binaryFun = fun.opCall; + } + else + { + alias binaryFun = fun; + } +} + +/// +@safe unittest +{ + alias less = binaryFun!("a < b"); + assert(less(1, 2) && !less(2, 1)); + alias greater = binaryFun!("a > b"); + assert(!greater("1", "2") && greater("2", "1")); +} + +@safe unittest +{ + static int f1(int a, string b) { return a + 1; } + static assert(is(typeof(binaryFun!(f1)(1, "2")) == int)); + assert(binaryFun!(f1)(41, "a") == 42); + string f2(int a, string b) { return b ~ "2"; } + static assert(is(typeof(binaryFun!(f2)(1, "1")) == string)); + assert(binaryFun!(f2)(1, "4") == "42"); + assert(binaryFun!("a + b")(41, 1) == 42); + //@@BUG + //assert(binaryFun!("return a + b;")(41, 1) == 42); + + // Issue 9906 + struct Seen + { + static bool opCall(int x, int y) { return true; } + } + static assert(is(typeof(binaryFun!Seen))); + assert(binaryFun!Seen(1,1)); + + struct FuncObj + { + bool opCall(int x, int y) { return true; } + } + FuncObj fo; + static assert(!needOpCallAlias!fo); + static assert(is(typeof(binaryFun!fo))); + assert(unaryFun!fo(1,1)); + + // Function object with non-static opCall can only be called with an + // instance, not with merely the type. + static assert(!is(typeof(binaryFun!FuncObj))); +} + +// skip all ASCII chars except a .. z, A .. Z, 0 .. 9, '_' and '.'. +private uint _ctfeSkipOp(ref string op) +{ + if (!__ctfe) assert(false); + import std.ascii : isASCII, isAlphaNum; + immutable oldLength = op.length; + while (op.length) + { + immutable front = op[0]; + if (front.isASCII() && !(front.isAlphaNum() || front == '_' || front == '.')) + op = op[1..$]; + else + break; + } + return oldLength != op.length; +} + +// skip all digits +private uint _ctfeSkipInteger(ref string op) +{ + if (!__ctfe) assert(false); + import std.ascii : isDigit; + immutable oldLength = op.length; + while (op.length) + { + immutable front = op[0]; + if (front.isDigit()) + op = op[1..$]; + else + break; + } + return oldLength != op.length; +} + +// skip name +private uint _ctfeSkipName(ref string op, string name) +{ + if (!__ctfe) assert(false); + if (op.length >= name.length && op[0 .. name.length] == name) + { + op = op[name.length..$]; + return 1; + } + return 0; +} + +// returns 1 if $(D fun) is trivial unary function +private uint _ctfeMatchUnary(string fun, string name) +{ + if (!__ctfe) assert(false); + fun._ctfeSkipOp(); + for (;;) + { + immutable h = fun._ctfeSkipName(name) + fun._ctfeSkipInteger(); + if (h == 0) + { + fun._ctfeSkipOp(); + break; + } + else if (h == 1) + { + if (!fun._ctfeSkipOp()) + break; + } + else + return 0; + } + return fun.length == 0; +} + +@safe unittest +{ + static assert(!_ctfeMatchUnary("sqrt(ё)", "ё")); + static assert(!_ctfeMatchUnary("ё.sqrt", "ё")); + static assert(!_ctfeMatchUnary(".ё+ё", "ё")); + static assert(!_ctfeMatchUnary("_ё+ё", "ё")); + static assert(!_ctfeMatchUnary("ёё", "ё")); + static assert(_ctfeMatchUnary("a+a", "a")); + static assert(_ctfeMatchUnary("a + 10", "a")); + static assert(_ctfeMatchUnary("4 == a", "a")); + static assert(_ctfeMatchUnary("2 == a", "a")); + static assert(_ctfeMatchUnary("1 != a", "a")); + static assert(_ctfeMatchUnary("a != 4", "a")); + static assert(_ctfeMatchUnary("a< 1", "a")); + static assert(_ctfeMatchUnary("434 < a", "a")); + static assert(_ctfeMatchUnary("132 > a", "a")); + static assert(_ctfeMatchUnary("123 >a", "a")); + static assert(_ctfeMatchUnary("a>82", "a")); + static assert(_ctfeMatchUnary("ё>82", "ё")); + static assert(_ctfeMatchUnary("ё[ё(ё)]", "ё")); + static assert(_ctfeMatchUnary("ё[21]", "ё")); +} + +// returns 1 if $(D fun) is trivial binary function +private uint _ctfeMatchBinary(string fun, string name1, string name2) +{ + if (!__ctfe) assert(false); + fun._ctfeSkipOp(); + for (;;) + { + immutable h = fun._ctfeSkipName(name1) + fun._ctfeSkipName(name2) + fun._ctfeSkipInteger(); + if (h == 0) + { + fun._ctfeSkipOp(); + break; + } + else if (h == 1) + { + if (!fun._ctfeSkipOp()) + break; + } + else + return 0; + } + return fun.length == 0; +} + +@safe unittest +{ + + static assert(!_ctfeMatchBinary("sqrt(ё)", "ё", "b")); + static assert(!_ctfeMatchBinary("ё.sqrt", "ё", "b")); + static assert(!_ctfeMatchBinary(".ё+ё", "ё", "b")); + static assert(!_ctfeMatchBinary("_ё+ё", "ё", "b")); + static assert(!_ctfeMatchBinary("ёё", "ё", "b")); + static assert(_ctfeMatchBinary("a+a", "a", "b")); + static assert(_ctfeMatchBinary("a + 10", "a", "b")); + static assert(_ctfeMatchBinary("4 == a", "a", "b")); + static assert(_ctfeMatchBinary("2 == a", "a", "b")); + static assert(_ctfeMatchBinary("1 != a", "a", "b")); + static assert(_ctfeMatchBinary("a != 4", "a", "b")); + static assert(_ctfeMatchBinary("a< 1", "a", "b")); + static assert(_ctfeMatchBinary("434 < a", "a", "b")); + static assert(_ctfeMatchBinary("132 > a", "a", "b")); + static assert(_ctfeMatchBinary("123 >a", "a", "b")); + static assert(_ctfeMatchBinary("a>82", "a", "b")); + static assert(_ctfeMatchBinary("ё>82", "ё", "q")); + static assert(_ctfeMatchBinary("ё[ё(10)]", "ё", "q")); + static assert(_ctfeMatchBinary("ё[21]", "ё", "q")); + + static assert(!_ctfeMatchBinary("sqrt(ё)+b", "b", "ё")); + static assert(!_ctfeMatchBinary("ё.sqrt-b", "b", "ё")); + static assert(!_ctfeMatchBinary(".ё+b", "b", "ё")); + static assert(!_ctfeMatchBinary("_b+ё", "b", "ё")); + static assert(!_ctfeMatchBinary("ba", "b", "a")); + static assert(_ctfeMatchBinary("a+b", "b", "a")); + static assert(_ctfeMatchBinary("a + b", "b", "a")); + static assert(_ctfeMatchBinary("b == a", "b", "a")); + static assert(_ctfeMatchBinary("b == a", "b", "a")); + static assert(_ctfeMatchBinary("b != a", "b", "a")); + static assert(_ctfeMatchBinary("a != b", "b", "a")); + static assert(_ctfeMatchBinary("a< b", "b", "a")); + static assert(_ctfeMatchBinary("b < a", "b", "a")); + static assert(_ctfeMatchBinary("b > a", "b", "a")); + static assert(_ctfeMatchBinary("b >a", "b", "a")); + static assert(_ctfeMatchBinary("a>b", "b", "a")); + static assert(_ctfeMatchBinary("ё>b", "b", "ё")); + static assert(_ctfeMatchBinary("b[ё(-1)]", "b", "ё")); + static assert(_ctfeMatchBinary("ё[-21]", "b", "ё")); +} + +//undocumented +template safeOp(string S) +if (S=="<"||S==">"||S=="<="||S==">="||S=="=="||S=="!=") +{ + import std.traits : isIntegral; + private bool unsafeOp(ElementType1, ElementType2)(ElementType1 a, ElementType2 b) pure + if (isIntegral!ElementType1 && isIntegral!ElementType2) + { + import std.traits : CommonType; + alias T = CommonType!(ElementType1, ElementType2); + return mixin("cast(T)a "~S~" cast(T) b"); + } + + bool safeOp(T0, T1)(auto ref T0 a, auto ref T1 b) + { + import std.traits : mostNegative; + static if (isIntegral!T0 && isIntegral!T1 && + (mostNegative!T0 < 0) != (mostNegative!T1 < 0)) + { + static if (S == "<=" || S == "<") + { + static if (mostNegative!T0 < 0) + immutable result = a < 0 || unsafeOp(a, b); + else + immutable result = b >= 0 && unsafeOp(a, b); + } + else + { + static if (mostNegative!T0 < 0) + immutable result = a >= 0 && unsafeOp(a, b); + else + immutable result = b < 0 || unsafeOp(a, b); + } + } + else + { + static assert(is(typeof(mixin("a "~S~" b"))), + "Invalid arguments: Cannot compare types " ~ T0.stringof ~ " and " ~ T1.stringof ~ "."); + + immutable result = mixin("a "~S~" b"); + } + return result; + } +} + +@safe unittest //check user defined types +{ + import std.algorithm.comparison : equal; + struct Foo + { + int a; + auto opEquals(Foo foo) + { + return a == foo.a; + } + } + assert(safeOp!"!="(Foo(1), Foo(2))); +} + +/** + Predicate that returns $(D_PARAM a < b). + Correctly compares signed and unsigned integers, ie. -1 < 2U. +*/ +alias lessThan = safeOp!"<"; + +/// +pure @safe @nogc nothrow unittest +{ + assert(lessThan(2, 3)); + assert(lessThan(2U, 3U)); + assert(lessThan(2, 3.0)); + assert(lessThan(-2, 3U)); + assert(lessThan(2, 3U)); + assert(!lessThan(3U, -2)); + assert(!lessThan(3U, 2)); + assert(!lessThan(0, 0)); + assert(!lessThan(0U, 0)); + assert(!lessThan(0, 0U)); +} + +/** + Predicate that returns $(D_PARAM a > b). + Correctly compares signed and unsigned integers, ie. 2U > -1. +*/ +alias greaterThan = safeOp!">"; + +/// +@safe unittest +{ + assert(!greaterThan(2, 3)); + assert(!greaterThan(2U, 3U)); + assert(!greaterThan(2, 3.0)); + assert(!greaterThan(-2, 3U)); + assert(!greaterThan(2, 3U)); + assert(greaterThan(3U, -2)); + assert(greaterThan(3U, 2)); + assert(!greaterThan(0, 0)); + assert(!greaterThan(0U, 0)); + assert(!greaterThan(0, 0U)); +} + +/** + Predicate that returns $(D_PARAM a == b). + Correctly compares signed and unsigned integers, ie. !(-1 == ~0U). +*/ +alias equalTo = safeOp!"=="; + +/// +@safe unittest +{ + assert(equalTo(0U, 0)); + assert(equalTo(0, 0U)); + assert(!equalTo(-1, ~0U)); +} +/** + N-ary predicate that reverses the order of arguments, e.g., given + $(D pred(a, b, c)), returns $(D pred(c, b, a)). +*/ +template reverseArgs(alias pred) +{ + auto reverseArgs(Args...)(auto ref Args args) + if (is(typeof(pred(Reverse!args)))) + { + return pred(Reverse!args); + } +} + +/// +@safe unittest +{ + alias gt = reverseArgs!(binaryFun!("a < b")); + assert(gt(2, 1) && !gt(1, 1)); + int x = 42; + bool xyz(int a, int b) { return a * x < b / x; } + auto foo = &xyz; + foo(4, 5); + alias zyx = reverseArgs!(foo); + assert(zyx(5, 4) == foo(4, 5)); +} + +/// +@safe unittest +{ + int abc(int a, int b, int c) { return a * b + c; } + alias cba = reverseArgs!abc; + assert(abc(91, 17, 32) == cba(32, 17, 91)); +} + +/// +@safe unittest +{ + int a(int a) { return a * 2; } + alias _a = reverseArgs!a; + assert(a(2) == _a(2)); +} + +/// +@safe unittest +{ + int b() { return 4; } + alias _b = reverseArgs!b; + assert(b() == _b()); +} + +/** + Binary predicate that reverses the order of arguments, e.g., given + $(D pred(a, b)), returns $(D pred(b, a)). +*/ +template binaryReverseArgs(alias pred) +{ + auto binaryReverseArgs(ElementType1, ElementType2) + (auto ref ElementType1 a, auto ref ElementType2 b) + { + return pred(b, a); + } +} + +/// +@safe unittest +{ + alias gt = binaryReverseArgs!(binaryFun!("a < b")); + assert(gt(2, 1) && !gt(1, 1)); +} + +/// +@safe unittest +{ + int x = 42; + bool xyz(int a, int b) { return a * x < b / x; } + auto foo = &xyz; + foo(4, 5); + alias zyx = binaryReverseArgs!(foo); + assert(zyx(5, 4) == foo(4, 5)); +} + +/** +Negates predicate $(D pred). + */ +template not(alias pred) +{ + auto not(T...)(auto ref T args) + { + static if (is(typeof(!pred(args)))) + return !pred(args); + else static if (T.length == 1) + return !unaryFun!pred(args); + else static if (T.length == 2) + return !binaryFun!pred(args); + else + static assert(0); + } +} + +/// +@safe unittest +{ + import std.algorithm.searching : find; + import std.functional; + import std.uni : isWhite; + string a = " Hello, world!"; + assert(find!(not!isWhite)(a) == "Hello, world!"); +} + +@safe unittest +{ + assert(not!"a != 5"(5)); + assert(not!"a != b"(5, 5)); + + assert(not!(() => false)()); + assert(not!(a => a != 5)(5)); + assert(not!((a, b) => a != b)(5, 5)); + assert(not!((a, b, c) => a * b * c != 125 )(5, 5, 5)); +} + +/** +$(LINK2 http://en.wikipedia.org/wiki/Partial_application, Partially +applies) $(D_PARAM fun) by tying its first argument to $(D_PARAM arg). + */ +template partial(alias fun, alias arg) +{ + static if (is(typeof(fun) == delegate) || is(typeof(fun) == function)) + { + import std.traits : ReturnType; + ReturnType!fun partial(Parameters!fun[1..$] args2) + { + return fun(arg, args2); + } + } + else + { + auto partial(Ts...)(Ts args2) + { + static if (is(typeof(fun(arg, args2)))) + { + return fun(arg, args2); + } + else + { + static string errormsg() + { + string msg = "Cannot call '" ~ fun.stringof ~ "' with arguments " ~ + "(" ~ arg.stringof; + foreach (T; Ts) + msg ~= ", " ~ T.stringof; + msg ~= ")."; + return msg; + } + static assert(0, errormsg()); + } + } + } +} + +/// +@safe unittest +{ + int fun(int a, int b) { return a + b; } + alias fun5 = partial!(fun, 5); + assert(fun5(6) == 11); + // Note that in most cases you'd use an alias instead of a value + // assignment. Using an alias allows you to partially evaluate template + // functions without committing to a particular type of the function. +} + +// tests for partially evaluating callables +@safe unittest +{ + static int f1(int a, int b) { return a + b; } + assert(partial!(f1, 5)(6) == 11); + + int f2(int a, int b) { return a + b; } + int x = 5; + assert(partial!(f2, x)(6) == 11); + x = 7; + assert(partial!(f2, x)(6) == 13); + static assert(partial!(f2, 5)(6) == 11); + + auto dg = &f2; + auto f3 = &partial!(dg, x); + assert(f3(6) == 13); + + static int funOneArg(int a) { return a; } + assert(partial!(funOneArg, 1)() == 1); + + static int funThreeArgs(int a, int b, int c) { return a + b + c; } + alias funThreeArgs1 = partial!(funThreeArgs, 1); + assert(funThreeArgs1(2, 3) == 6); + static assert(!is(typeof(funThreeArgs1(2)))); + + enum xe = 5; + alias fe = partial!(f2, xe); + static assert(fe(6) == 11); +} + +// tests for partially evaluating templated/overloaded callables +@safe unittest +{ + static auto add(A, B)(A x, B y) + { + return x + y; + } + + alias add5 = partial!(add, 5); + assert(add5(6) == 11); + static assert(!is(typeof(add5()))); + static assert(!is(typeof(add5(6, 7)))); + + // taking address of templated partial evaluation needs explicit type + auto dg = &add5!(int); + assert(dg(6) == 11); + + int x = 5; + alias addX = partial!(add, x); + assert(addX(6) == 11); + + static struct Callable + { + static string opCall(string a, string b) { return a ~ b; } + int opCall(int a, int b) { return a * b; } + double opCall(double a, double b) { return a + b; } + } + Callable callable; + assert(partial!(Callable, "5")("6") == "56"); + assert(partial!(callable, 5)(6) == 30); + assert(partial!(callable, 7.0)(3.0) == 7.0 + 3.0); + + static struct TCallable + { + auto opCall(A, B)(A a, B b) + { + return a + b; + } + } + TCallable tcallable; + assert(partial!(tcallable, 5)(6) == 11); + static assert(!is(typeof(partial!(tcallable, "5")(6)))); + + static A funOneArg(A)(A a) { return a; } + alias funOneArg1 = partial!(funOneArg, 1); + assert(funOneArg1() == 1); + + static auto funThreeArgs(A, B, C)(A a, B b, C c) { return a + b + c; } + alias funThreeArgs1 = partial!(funThreeArgs, 1); + assert(funThreeArgs1(2, 3) == 6); + static assert(!is(typeof(funThreeArgs1(1)))); + + auto dg2 = &funOneArg1!(); + assert(dg2() == 1); +} + +/** +Takes multiple functions and adjoins them together. The result is a +$(REF Tuple, std,typecons) with one element per passed-in function. Upon +invocation, the returned tuple is the adjoined results of all +functions. + +Note: In the special case where only a single function is provided +($(D F.length == 1)), adjoin simply aliases to the single passed function +($(D F[0])). +*/ +template adjoin(F...) +if (F.length == 1) +{ + alias adjoin = F[0]; +} +/// ditto +template adjoin(F...) +if (F.length > 1) +{ + auto adjoin(V...)(auto ref V a) + { + import std.typecons : tuple; + static if (F.length == 2) + { + return tuple(F[0](a), F[1](a)); + } + else static if (F.length == 3) + { + return tuple(F[0](a), F[1](a), F[2](a)); + } + else + { + import std.format : format; + import std.range : iota; + return mixin (q{tuple(%(F[%s](a)%|, %))}.format(iota(0, F.length))); + } + } +} + +/// +@safe unittest +{ + import std.functional, std.typecons : Tuple; + static bool f1(int a) { return a != 0; } + static int f2(int a) { return a / 2; } + auto x = adjoin!(f1, f2)(5); + assert(is(typeof(x) == Tuple!(bool, int))); + assert(x[0] == true && x[1] == 2); +} + +@safe unittest +{ + import std.typecons : Tuple; + static bool F1(int a) { return a != 0; } + auto x1 = adjoin!(F1)(5); + static int F2(int a) { return a / 2; } + auto x2 = adjoin!(F1, F2)(5); + assert(is(typeof(x2) == Tuple!(bool, int))); + assert(x2[0] && x2[1] == 2); + auto x3 = adjoin!(F1, F2, F2)(5); + assert(is(typeof(x3) == Tuple!(bool, int, int))); + assert(x3[0] && x3[1] == 2 && x3[2] == 2); + + bool F4(int a) { return a != x1; } + alias eff4 = adjoin!(F4); + static struct S + { + bool delegate(int) @safe store; + int fun() { return 42 + store(5); } + } + S s; + s.store = (int a) { return eff4(a); }; + auto x4 = s.fun(); + assert(x4 == 43); +} + +@safe unittest +{ + import std.meta : staticMap; + import std.typecons : Tuple, tuple; + alias funs = staticMap!(unaryFun, "a", "a * 2", "a * 3", "a * a", "-a"); + alias afun = adjoin!funs; + assert(afun(5) == tuple(5, 10, 15, 25, -5)); + + static class C{} + alias IC = immutable(C); + IC foo(){return typeof(return).init;} + Tuple!(IC, IC, IC, IC) ret1 = adjoin!(foo, foo, foo, foo)(); + + static struct S{int* p;} + alias IS = immutable(S); + IS bar(){return typeof(return).init;} + enum Tuple!(IS, IS, IS, IS) ret2 = adjoin!(bar, bar, bar, bar)(); +} + +/** + Composes passed-in functions $(D fun[0], fun[1], ...) returning a + function $(D f(x)) that in turn returns $(D + fun[0](fun[1](...(x)))...). Each function can be a regular + functions, a delegate, or a string. + + See_Also: $(LREF pipe) +*/ +template compose(fun...) +{ + static if (fun.length == 1) + { + alias compose = unaryFun!(fun[0]); + } + else static if (fun.length == 2) + { + // starch + alias fun0 = unaryFun!(fun[0]); + alias fun1 = unaryFun!(fun[1]); + + // protein: the core composition operation + typeof({ E a; return fun0(fun1(a)); }()) compose(E)(E a) + { + return fun0(fun1(a)); + } + } + else + { + // protein: assembling operations + alias compose = compose!(fun[0], compose!(fun[1 .. $])); + } +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map; + import std.array : split; + import std.conv : to; + + // First split a string in whitespace-separated tokens and then + // convert each token into an integer + assert(compose!(map!(to!(int)), split)("1 2 3").equal([1, 2, 3])); +} + +/** + Pipes functions in sequence. Offers the same functionality as $(D + compose), but with functions specified in reverse order. This may + lead to more readable code in some situation because the order of + execution is the same as lexical order. + + Example: + +---- +// Read an entire text file, split the resulting string in +// whitespace-separated tokens, and then convert each token into an +// integer +int[] a = pipe!(readText, split, map!(to!(int)))("file.txt"); +---- + + See_Also: $(LREF compose) + */ +alias pipe(fun...) = compose!(Reverse!(fun)); + +@safe unittest +{ + import std.conv : to; + string foo(int a) { return to!(string)(a); } + int bar(string a) { return to!(int)(a) + 1; } + double baz(int a) { return a + 0.5; } + assert(compose!(baz, bar, foo)(1) == 2.5); + assert(pipe!(foo, bar, baz)(1) == 2.5); + + assert(compose!(baz, `to!(int)(a) + 1`, foo)(1) == 2.5); + assert(compose!(baz, bar)("1"[]) == 2.5); + + assert(compose!(baz, bar)("1") == 2.5); + + assert(compose!(`a + 0.5`, `to!(int)(a) + 1`, foo)(1) == 2.5); +} + +/** + * $(LINK2 https://en.wikipedia.org/wiki/Memoization, Memoizes) a function so as + * to avoid repeated computation. The memoization structure is a hash table keyed by a + * tuple of the function's arguments. There is a speed gain if the + * function is repeatedly called with the same arguments and is more + * expensive than a hash table lookup. For more information on memoization, refer to $(HTTP docs.google.com/viewer?url=http%3A%2F%2Fhop.perl.plover.com%2Fbook%2Fpdf%2F03CachingAndMemoization.pdf, this book chapter). + +Example: +---- +double transmogrify(int a, string b) +{ + ... expensive computation ... +} +alias fastTransmogrify = memoize!transmogrify; +unittest +{ + auto slow = transmogrify(2, "hello"); + auto fast = fastTransmogrify(2, "hello"); + assert(slow == fast); +} +---- + +Technically the memoized function should be pure because $(D memoize) assumes it will +always return the same result for a given tuple of arguments. However, $(D memoize) does not +enforce that because sometimes it +is useful to memoize an impure function, too. +*/ +template memoize(alias fun) +{ + import std.traits : ReturnType; + // alias Args = Parameters!fun; // Bugzilla 13580 + + ReturnType!fun memoize(Parameters!fun args) + { + alias Args = Parameters!fun; + import std.typecons : Tuple; + + static ReturnType!fun[Tuple!Args] memo; + auto t = Tuple!Args(args); + if (auto p = t in memo) + return *p; + return memo[t] = fun(args); + } +} + +/// ditto +template memoize(alias fun, uint maxSize) +{ + import std.traits : ReturnType; + // alias Args = Parameters!fun; // Bugzilla 13580 + ReturnType!fun memoize(Parameters!fun args) + { + import std.traits : hasIndirections; + import std.typecons : tuple; + static struct Value { Parameters!fun args; ReturnType!fun res; } + static Value[] memo; + static size_t[] initialized; + + if (!memo.length) + { + import core.memory : GC; + + // Ensure no allocation overflows + static assert(maxSize < size_t.max / Value.sizeof); + static assert(maxSize < size_t.max - (8 * size_t.sizeof - 1)); + + enum attr = GC.BlkAttr.NO_INTERIOR | (hasIndirections!Value ? 0 : GC.BlkAttr.NO_SCAN); + memo = (cast(Value*) GC.malloc(Value.sizeof * maxSize, attr))[0 .. maxSize]; + enum nwords = (maxSize + 8 * size_t.sizeof - 1) / (8 * size_t.sizeof); + initialized = (cast(size_t*) GC.calloc(nwords * size_t.sizeof, attr | GC.BlkAttr.NO_SCAN))[0 .. nwords]; + } + + import core.bitop : bt, bts; + import std.conv : emplace; + + size_t hash; + foreach (ref arg; args) + hash = hashOf(arg, hash); + // cuckoo hashing + immutable idx1 = hash % maxSize; + if (!bt(initialized.ptr, idx1)) + { + emplace(&memo[idx1], args, fun(args)); + bts(initialized.ptr, idx1); // only set to initialized after setting args and value (bugzilla 14025) + return memo[idx1].res; + } + else if (memo[idx1].args == args) + return memo[idx1].res; + // FNV prime + immutable idx2 = (hash * 16_777_619) % maxSize; + if (!bt(initialized.ptr, idx2)) + { + emplace(&memo[idx2], memo[idx1]); + bts(initialized.ptr, idx2); // only set to initialized after setting args and value (bugzilla 14025) + } + else if (memo[idx2].args == args) + return memo[idx2].res; + else if (idx1 != idx2) + memo[idx2] = memo[idx1]; + + memo[idx1] = Value(args, fun(args)); + return memo[idx1].res; + } +} + +/** + * To _memoize a recursive function, simply insert the memoized call in lieu of the plain recursive call. + * For example, to transform the exponential-time Fibonacci implementation into a linear-time computation: + */ +@safe unittest +{ + ulong fib(ulong n) @safe + { + return n < 2 ? n : memoize!fib(n - 2) + memoize!fib(n - 1); + } + assert(fib(10) == 55); +} + +/** + * To improve the speed of the factorial function, + */ +@safe unittest +{ + ulong fact(ulong n) @safe + { + return n < 2 ? 1 : n * memoize!fact(n - 1); + } + assert(fact(10) == 3628800); +} + +/** + * This memoizes all values of $(D fact) up to the largest argument. To only cache the final + * result, move $(D memoize) outside the function as shown below. + */ +@safe unittest +{ + ulong factImpl(ulong n) @safe + { + return n < 2 ? 1 : n * factImpl(n - 1); + } + alias fact = memoize!factImpl; + assert(fact(10) == 3628800); +} + +/** + * When the $(D maxSize) parameter is specified, memoize will used + * a fixed size hash table to limit the number of cached entries. + */ +@system unittest // not @safe due to memoize +{ + ulong fact(ulong n) + { + // Memoize no more than 8 values + return n < 2 ? 1 : n * memoize!(fact, 8)(n - 1); + } + assert(fact(8) == 40320); + // using more entries than maxSize will overwrite existing entries + assert(fact(10) == 3628800); +} + +@system unittest // not @safe due to memoize +{ + import core.math : sqrt; + alias msqrt = memoize!(function double(double x) { return sqrt(x); }); + auto y = msqrt(2.0); + assert(y == msqrt(2.0)); + y = msqrt(4.0); + assert(y == sqrt(4.0)); + + // alias mrgb2cmyk = memoize!rgb2cmyk; + // auto z = mrgb2cmyk([43, 56, 76]); + // assert(z == mrgb2cmyk([43, 56, 76])); + + //alias mfib = memoize!fib; + + static ulong fib(ulong n) @safe + { + alias mfib = memoize!fib; + return n < 2 ? 1 : mfib(n - 2) + mfib(n - 1); + } + + auto z = fib(10); + assert(z == 89); + + static ulong fact(ulong n) @safe + { + alias mfact = memoize!fact; + return n < 2 ? 1 : n * mfact(n - 1); + } + assert(fact(10) == 3628800); + + // Issue 12568 + static uint len2(const string s) { // Error + alias mLen2 = memoize!len2; + if (s.length == 0) + return 0; + else + return 1 + mLen2(s[1 .. $]); + } + + int _func(int x) @safe { return 1; } + alias func = memoize!(_func, 10); + assert(func(int.init) == 1); + assert(func(int.init) == 1); +} + +// 16079: memoize should work with arrays +@safe unittest +{ + int executed = 0; + T median(T)(const T[] nums) { + import std.algorithm.sorting : sort; + executed++; + auto arr = nums.dup; + arr.sort(); + if (arr.length % 2) + return arr[$ / 2]; + else + return (arr[$ / 2 - 1] + + arr[$ / 2]) / 2; + } + + alias fastMedian = memoize!(median!int); + + assert(fastMedian([7, 5, 3]) == 5); + assert(fastMedian([7, 5, 3]) == 5); + + assert(executed == 1); +} + +// 16079: memoize should work with structs +@safe unittest +{ + int executed = 0; + T pickFirst(T)(T first) + { + executed++; + return first; + } + + struct Foo { int k; } + Foo A = Foo(3); + + alias first = memoize!(pickFirst!Foo); + assert(first(Foo(3)) == A); + assert(first(Foo(3)) == A); + assert(executed == 1); +} + +// 16079: memoize should work with classes +@safe unittest +{ + int executed = 0; + T pickFirst(T)(T first) + { + executed++; + return first; + } + + class Bar + { + size_t k; + this(size_t k) + { + this.k = k; + } + override size_t toHash() + { + return k; + } + override bool opEquals(Object o) + { + auto b = cast(Bar) o; + return b && k == b.k; + } + } + + alias firstClass = memoize!(pickFirst!Bar); + assert(firstClass(new Bar(3)).k == 3); + assert(firstClass(new Bar(3)).k == 3); + assert(executed == 1); +} + +private struct DelegateFaker(F) +{ + import std.typecons : FuncInfo, MemberFunctionGenerator; + + // for @safe + static F castToF(THIS)(THIS x) @trusted + { + return cast(F) x; + } + + /* + * What all the stuff below does is this: + *-------------------- + * struct DelegateFaker(F) { + * extern(linkage) + * [ref] ReturnType!F doIt(Parameters!F args) [@attributes] + * { + * auto fp = cast(F) &this; + * return fp(args); + * } + * } + *-------------------- + */ + + // We will use MemberFunctionGenerator in std.typecons. This is a policy + // configuration for generating the doIt(). + template GeneratingPolicy() + { + // Inform the genereator that we only have type information. + enum WITHOUT_SYMBOL = true; + + // Generate the function body of doIt(). + template generateFunctionBody(unused...) + { + enum generateFunctionBody = + // [ref] ReturnType doIt(Parameters args) @attributes + q{ + // When this function gets called, the this pointer isn't + // really a this pointer (no instance even really exists), but + // a function pointer that points to the function to be called. + // Cast it to the correct type and call it. + + auto fp = castToF(&this); + return fp(args); + }; + } + } + // Type information used by the generated code. + alias FuncInfo_doIt = FuncInfo!(F); + + // Generate the member function doIt(). + mixin( MemberFunctionGenerator!(GeneratingPolicy!()) + .generateFunction!("FuncInfo_doIt", "doIt", F) ); +} + +/** + * Convert a callable to a delegate with the same parameter list and + * return type, avoiding heap allocations and use of auxiliary storage. + * + * Example: + * ---- + * void doStuff() { + * writeln("Hello, world."); + * } + * + * void runDelegate(void delegate() myDelegate) { + * myDelegate(); + * } + * + * auto delegateToPass = toDelegate(&doStuff); + * runDelegate(delegateToPass); // Calls doStuff, prints "Hello, world." + * ---- + * + * BUGS: + * $(UL + * $(LI Does not work with $(D @safe) functions.) + * $(LI Ignores C-style / D-style variadic arguments.) + * ) + */ +auto toDelegate(F)(auto ref F fp) +if (isCallable!(F)) +{ + static if (is(F == delegate)) + { + return fp; + } + else static if (is(typeof(&F.opCall) == delegate) + || (is(typeof(&F.opCall) V : V*) && is(V == function))) + { + return toDelegate(&fp.opCall); + } + else + { + alias DelType = typeof(&(new DelegateFaker!(F)).doIt); + + static struct DelegateFields { + union { + DelType del; + //pragma(msg, typeof(del)); + + struct { + void* contextPtr; + void* funcPtr; + } + } + } + + // fp is stored in the returned delegate's context pointer. + // The returned delegate's function pointer points to + // DelegateFaker.doIt. + DelegateFields df; + + df.contextPtr = cast(void*) fp; + + DelegateFaker!(F) dummy; + auto dummyDel = &dummy.doIt; + df.funcPtr = dummyDel.funcptr; + + return df.del; + } +} + +/// +@system unittest +{ + static int inc(ref uint num) { + num++; + return 8675309; + } + + uint myNum = 0; + auto incMyNumDel = toDelegate(&inc); + auto returnVal = incMyNumDel(myNum); + assert(myNum == 1); +} + +@system unittest // not @safe due to toDelegate +{ + static int inc(ref uint num) { + num++; + return 8675309; + } + + uint myNum = 0; + auto incMyNumDel = toDelegate(&inc); + int delegate(ref uint) dg = incMyNumDel; + auto returnVal = incMyNumDel(myNum); + assert(myNum == 1); + + interface I { int opCall(); } + class C: I { int opCall() { inc(myNum); return myNum;} } + auto c = new C; + auto i = cast(I) c; + + auto getvalc = toDelegate(c); + assert(getvalc() == 2); + + auto getvali = toDelegate(i); + assert(getvali() == 3); + + struct S1 { int opCall() { inc(myNum); return myNum; } } + static assert(!is(typeof(&s1.opCall) == delegate)); + S1 s1; + auto getvals1 = toDelegate(s1); + assert(getvals1() == 4); + + struct S2 { static int opCall() { return 123456; } } + static assert(!is(typeof(&S2.opCall) == delegate)); + S2 s2; + auto getvals2 =&S2.opCall; + assert(getvals2() == 123456); + + /* test for attributes */ + { + static int refvar = 0xDeadFace; + + static ref int func_ref() { return refvar; } + static int func_pure() pure { return 1; } + static int func_nothrow() nothrow { return 2; } + static int func_property() @property { return 3; } + static int func_safe() @safe { return 4; } + static int func_trusted() @trusted { return 5; } + static int func_system() @system { return 6; } + static int func_pure_nothrow() pure nothrow { return 7; } + static int func_pure_nothrow_safe() pure nothrow @safe { return 8; } + + auto dg_ref = toDelegate(&func_ref); + int delegate() pure dg_pure = toDelegate(&func_pure); + int delegate() nothrow dg_nothrow = toDelegate(&func_nothrow); + int delegate() @property dg_property = toDelegate(&func_property); + int delegate() @safe dg_safe = toDelegate(&func_safe); + int delegate() @trusted dg_trusted = toDelegate(&func_trusted); + int delegate() @system dg_system = toDelegate(&func_system); + int delegate() pure nothrow dg_pure_nothrow = toDelegate(&func_pure_nothrow); + int delegate() @safe pure nothrow dg_pure_nothrow_safe = toDelegate(&func_pure_nothrow_safe); + + //static assert(is(typeof(dg_ref) == ref int delegate())); // [BUG@DMD] + + assert(dg_ref() == refvar); + assert(dg_pure() == 1); + assert(dg_nothrow() == 2); + assert(dg_property() == 3); + assert(dg_safe() == 4); + assert(dg_trusted() == 5); + assert(dg_system() == 6); + assert(dg_pure_nothrow() == 7); + assert(dg_pure_nothrow_safe() == 8); + } + /* test for linkage */ + { + struct S + { + extern(C) static void xtrnC() {} + extern(D) static void xtrnD() {} + } + auto dg_xtrnC = toDelegate(&S.xtrnC); + auto dg_xtrnD = toDelegate(&S.xtrnD); + static assert(! is(typeof(dg_xtrnC) == typeof(dg_xtrnD))); + } +} + +/** +Forwards function arguments with saving ref-ness. +*/ +template forward(args...) +{ + static if (args.length) + { + import std.algorithm.mutation : move; + + alias arg = args[0]; + static if (__traits(isRef, arg)) + alias fwd = arg; + else + @property fwd()(){ return move(arg); } + alias forward = AliasSeq!(fwd, forward!(args[1..$])); + } + else + alias forward = AliasSeq!(); +} + +/// +@safe unittest +{ + class C + { + static int foo(int n) { return 1; } + static int foo(ref int n) { return 2; } + } + int bar()(auto ref int x) { return C.foo(forward!x); } + + assert(bar(1) == 1); + int i; + assert(bar(i) == 2); +} + +/// +@safe unittest +{ + void foo(int n, ref string s) { s = null; foreach (i; 0 .. n) s ~= "Hello"; } + + // forwards all arguments which are bound to parameter tuple + void bar(Args...)(auto ref Args args) { return foo(forward!args); } + + // forwards all arguments with swapping order + void baz(Args...)(auto ref Args args) { return foo(forward!args[$/2..$], forward!args[0..$/2]); } + + string s; + bar(1, s); + assert(s == "Hello"); + baz(s, 2); + assert(s == "HelloHello"); +} + +@safe unittest +{ + auto foo(TL...)(auto ref TL args) + { + string result = ""; + foreach (i, _; args) + { + //pragma(msg, "[",i,"] ", __traits(isRef, args[i]) ? "L" : "R"); + result ~= __traits(isRef, args[i]) ? "L" : "R"; + } + return result; + } + + string bar(TL...)(auto ref TL args) + { + return foo(forward!args); + } + string baz(TL...)(auto ref TL args) + { + int x; + return foo(forward!args[3], forward!args[2], 1, forward!args[1], forward!args[0], x); + } + + struct S {} + S makeS(){ return S(); } + int n; + string s; + assert(bar(S(), makeS(), n, s) == "RRLL"); + assert(baz(S(), makeS(), n, s) == "LLRRRL"); +} + +@safe unittest +{ + ref int foo(return ref int a) { return a; } + ref int bar(Args)(auto ref Args args) + { + return foo(forward!args); + } + static assert(!__traits(compiles, { auto x1 = bar(3); })); // case of NG + int value = 3; + auto x2 = bar(value); // case of OK +} diff --git a/libphobos/src/std/getopt.d b/libphobos/src/std/getopt.d new file mode 100644 index 0000000..5beddcc --- /dev/null +++ b/libphobos/src/std/getopt.d @@ -0,0 +1,1857 @@ +// Written in the D programming language. + +/** +Processing of command line options. + +The getopt module implements a $(D getopt) function, which adheres to +the POSIX syntax for command line options. GNU extensions are +supported in the form of long options introduced by a double dash +("--"). Support for bundling of command line options, as was the case +with the more traditional single-letter approach, is provided but not +enabled by default. + +Copyright: Copyright Andrei Alexandrescu 2008 - 2015. +License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). +Authors: $(HTTP erdani.org, Andrei Alexandrescu) +Credits: This module and its documentation are inspired by Perl's $(HTTP + perldoc.perl.org/Getopt/Long.html, Getopt::Long) module. The syntax of + D's $(D getopt) is simpler than its Perl counterpart because $(D + getopt) infers the expected parameter types from the static types of + the passed-in pointers. +Source: $(PHOBOSSRC std/_getopt.d) +*/ +/* + Copyright Andrei Alexandrescu 2008 - 2015. +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) +*/ +module std.getopt; + +import std.exception; // basicExceptionCtors +import std.traits; + +/** +Thrown on one of the following conditions: +$(UL + $(LI An unrecognized command-line argument is passed, and + $(D std.getopt.config.passThrough) was not present.) + $(LI A command-line option was not found, and + $(D std.getopt.config.required) was present.) +) +*/ +class GetOptException : Exception +{ + mixin basicExceptionCtors; +} + +static assert(is(typeof(new GetOptException("message")))); +static assert(is(typeof(new GetOptException("message", Exception.init)))); + +/** + Parse and remove command line options from a string array. + + Synopsis: + +--------- +import std.getopt; + +string data = "file.dat"; +int length = 24; +bool verbose; +enum Color { no, yes }; +Color color; + +void main(string[] args) +{ + auto helpInformation = getopt( + args, + "length", &length, // numeric + "file", &data, // string + "verbose", &verbose, // flag + "color", "Information about this color", &color); // enum + ... + + if (helpInformation.helpWanted) + { + defaultGetoptPrinter("Some information about the program.", + helpInformation.options); + } +} +--------- + + The $(D getopt) function takes a reference to the command line + (as received by $(D main)) as its first argument, and an + unbounded number of pairs of strings and pointers. Each string is an + option meant to "fill" the value referenced by the pointer to its + right (the "bound" pointer). The option string in the call to + $(D getopt) should not start with a dash. + + In all cases, the command-line options that were parsed and used by + $(D getopt) are removed from $(D args). Whatever in the + arguments did not look like an option is left in $(D args) for + further processing by the program. Values that were unaffected by the + options are not touched, so a common idiom is to initialize options + to their defaults and then invoke $(D getopt). If a + command-line argument is recognized as an option with a parameter and + the parameter cannot be parsed properly (e.g., a number is expected + but not present), a $(D ConvException) exception is thrown. + If $(D std.getopt.config.passThrough) was not passed to $(D getopt) + and an unrecognized command-line argument is found, a $(D GetOptException) + is thrown. + + Depending on the type of the pointer being bound, $(D getopt) + recognizes the following kinds of options: + + $(OL + $(LI $(I Boolean options). A lone argument sets the option to $(D true). + Additionally $(B true) or $(B false) can be set within the option separated + with an "=" sign: + +--------- + bool verbose = false, debugging = true; + getopt(args, "verbose", &verbose, "debug", &debugging); +--------- + + To set $(D verbose) to $(D true), invoke the program with either + $(D --verbose) or $(D --verbose=true). + + To set $(D debugging) to $(D false), invoke the program with + $(D --debugging=false). + ) + + $(LI $(I Numeric options.) If an option is bound to a numeric type, a + number is expected as the next option, or right within the option separated + with an "=" sign: + +--------- + uint timeout; + getopt(args, "timeout", &timeout); +--------- + + To set $(D timeout) to $(D 5), invoke the program with either + $(D --timeout=5) or $(D --timeout 5). + ) + + $(LI $(I Incremental options.) If an option name has a "+" suffix and is + bound to a numeric type, then the option's value tracks the number of times + the option occurred on the command line: + +--------- + uint paranoid; + getopt(args, "paranoid+", ¶noid); +--------- + + Invoking the program with "--paranoid --paranoid --paranoid" will set $(D + paranoid) to 3. Note that an incremental option never expects a parameter, + e.g., in the command line "--paranoid 42 --paranoid", the "42" does not set + $(D paranoid) to 42; instead, $(D paranoid) is set to 2 and "42" is not + considered as part of the normal program arguments. + ) + + $(LI $(I Enum options.) If an option is bound to an enum, an enum symbol as + a string is expected as the next option, or right within the option + separated with an "=" sign: + +--------- + enum Color { no, yes }; + Color color; // default initialized to Color.no + getopt(args, "color", &color); +--------- + + To set $(D color) to $(D Color.yes), invoke the program with either + $(D --color=yes) or $(D --color yes). + ) + + $(LI $(I String options.) If an option is bound to a string, a string is + expected as the next option, or right within the option separated with an + "=" sign: + +--------- +string outputFile; +getopt(args, "output", &outputFile); +--------- + + Invoking the program with "--output=myfile.txt" or "--output myfile.txt" + will set $(D outputFile) to "myfile.txt". If you want to pass a string + containing spaces, you need to use the quoting that is appropriate to your + shell, e.g. --output='my file.txt'. + ) + + $(LI $(I Array options.) If an option is bound to an array, a new element + is appended to the array each time the option occurs: + +--------- +string[] outputFiles; +getopt(args, "output", &outputFiles); +--------- + + Invoking the program with "--output=myfile.txt --output=yourfile.txt" or + "--output myfile.txt --output yourfile.txt" will set $(D outputFiles) to + $(D [ "myfile.txt", "yourfile.txt" ]). + + Alternatively you can set $(LREF arraySep) as the element separator: + +--------- +string[] outputFiles; +arraySep = ","; // defaults to "", separation by whitespace +getopt(args, "output", &outputFiles); +--------- + + With the above code you can invoke the program with + "--output=myfile.txt,yourfile.txt", or "--output myfile.txt,yourfile.txt".) + + $(LI $(I Hash options.) If an option is bound to an associative array, a + string of the form "name=value" is expected as the next option, or right + within the option separated with an "=" sign: + +--------- +double[string] tuningParms; +getopt(args, "tune", &tuningParms); +--------- + + Invoking the program with e.g. "--tune=alpha=0.5 --tune beta=0.6" will set + $(D tuningParms) to [ "alpha" : 0.5, "beta" : 0.6 ]. + + Alternatively you can set $(LREF arraySep) as the element separator: + +--------- +double[string] tuningParms; +arraySep = ","; // defaults to "", separation by whitespace +getopt(args, "tune", &tuningParms); +--------- + + With the above code you can invoke the program with + "--tune=alpha=0.5,beta=0.6", or "--tune alpha=0.5,beta=0.6". + + In general, the keys and values can be of any parsable types. + ) + + $(LI $(I Callback options.) An option can be bound to a function or + delegate with the signature $(D void function()), $(D void function(string + option)), $(D void function(string option, string value)), or their + delegate equivalents. + + $(UL + $(LI If the callback doesn't take any arguments, the callback is + invoked whenever the option is seen. + ) + + $(LI If the callback takes one string argument, the option string + (without the leading dash(es)) is passed to the callback. After that, + the option string is considered handled and removed from the options + array. + +--------- +void main(string[] args) +{ + uint verbosityLevel = 1; + void myHandler(string option) + { + if (option == "quiet") + { + verbosityLevel = 0; + } + else + { + assert(option == "verbose"); + verbosityLevel = 2; + } + } + getopt(args, "verbose", &myHandler, "quiet", &myHandler); +} +--------- + + ) + + $(LI If the callback takes two string arguments, the option string is + handled as an option with one argument, and parsed accordingly. The + option and its value are passed to the callback. After that, whatever + was passed to the callback is considered handled and removed from the + list. + +--------- +int main(string[] args) +{ + uint verbosityLevel = 1; + bool handlerFailed = false; + void myHandler(string option, string value) + { + switch (value) + { + case "quiet": verbosityLevel = 0; break; + case "verbose": verbosityLevel = 2; break; + case "shouting": verbosityLevel = verbosityLevel.max; break; + default : + stderr.writeln("Unknown verbosity level ", value); + handlerFailed = true; + break; + } + } + getopt(args, "verbosity", &myHandler); + return handlerFailed ? 1 : 0; +} +--------- + ) + )) +) + +Options_with_multiple_names: +Sometimes option synonyms are desirable, e.g. "--verbose", +"--loquacious", and "--garrulous" should have the same effect. Such +alternate option names can be included in the option specification, +using "|" as a separator: + +--------- +bool verbose; +getopt(args, "verbose|loquacious|garrulous", &verbose); +--------- + +Case: +By default options are case-insensitive. You can change that behavior +by passing $(D getopt) the $(D caseSensitive) directive like this: + +--------- +bool foo, bar; +getopt(args, + std.getopt.config.caseSensitive, + "foo", &foo, + "bar", &bar); +--------- + +In the example above, "--foo" and "--bar" are recognized, but "--Foo", "--Bar", +"--FOo", "--bAr", etc. are rejected. +The directive is active until the end of $(D getopt), or until the +converse directive $(D caseInsensitive) is encountered: + +--------- +bool foo, bar; +getopt(args, + std.getopt.config.caseSensitive, + "foo", &foo, + std.getopt.config.caseInsensitive, + "bar", &bar); +--------- + +The option "--Foo" is rejected due to $(D +std.getopt.config.caseSensitive), but not "--Bar", "--bAr" +etc. because the directive $(D +std.getopt.config.caseInsensitive) turned sensitivity off before +option "bar" was parsed. + +Short_versus_long_options: +Traditionally, programs accepted single-letter options preceded by +only one dash (e.g. $(D -t)). $(D getopt) accepts such parameters +seamlessly. When used with a double-dash (e.g. $(D --t)), a +single-letter option behaves the same as a multi-letter option. When +used with a single dash, a single-letter option is accepted. If the +option has a parameter, that must be "stuck" to the option without +any intervening space or "=": + +--------- +uint timeout; +getopt(args, "timeout|t", &timeout); +--------- + +To set $(D timeout) to $(D 5), use either of the following: $(D --timeout=5), +$(D --timeout 5), $(D --t=5), $(D --t 5), or $(D -t5). Forms such as $(D -t 5) +and $(D -timeout=5) will be not accepted. + +For more details about short options, refer also to the next section. + +Bundling: +Single-letter options can be bundled together, i.e. "-abc" is the same as +$(D "-a -b -c"). By default, this option is turned off. You can turn it on +with the $(D std.getopt.config.bundling) directive: + +--------- +bool foo, bar; +getopt(args, + std.getopt.config.bundling, + "foo|f", &foo, + "bar|b", &bar); +--------- + +In case you want to only enable bundling for some of the parameters, +bundling can be turned off with $(D std.getopt.config.noBundling). + +Required: +An option can be marked as required. If that option is not present in the +arguments an exception will be thrown. + +--------- +bool foo, bar; +getopt(args, + std.getopt.config.required, + "foo|f", &foo, + "bar|b", &bar); +--------- + +Only the option directly following $(D std.getopt.config.required) is +required. + +Passing_unrecognized_options_through: +If an application needs to do its own processing of whichever arguments +$(D getopt) did not understand, it can pass the +$(D std.getopt.config.passThrough) directive to $(D getopt): + +--------- +bool foo, bar; +getopt(args, + std.getopt.config.passThrough, + "foo", &foo, + "bar", &bar); +--------- + +An unrecognized option such as "--baz" will be found untouched in +$(D args) after $(D getopt) returns. + +Help_Information_Generation: +If an option string is followed by another string, this string serves as a +description for this option. The $(D getopt) function returns a struct of type +$(D GetoptResult). This return value contains information about all passed options +as well a $(D bool GetoptResult.helpWanted) flag indicating whether information +about these options was requested. The $(D getopt) function always adds an option for +`--help|-h` to set the flag if the option is seen on the command line. + +Options_Terminator: +A lone double-dash terminates $(D getopt) gathering. It is used to +separate program options from other parameters (e.g., options to be passed +to another program). Invoking the example above with $(D "--foo -- --bar") +parses foo but leaves "--bar" in $(D args). The double-dash itself is +removed from the argument array unless the $(D std.getopt.config.keepEndOfOptions) +directive is given. +*/ +GetoptResult getopt(T...)(ref string[] args, T opts) +{ + import std.exception : enforce; + enforce(args.length, + "Invalid arguments string passed: program name missing"); + configuration cfg; + GetoptResult rslt; + + GetOptException excep; + void[][string] visitedLongOpts, visitedShortOpts; + getoptImpl(args, cfg, rslt, excep, visitedLongOpts, visitedShortOpts, opts); + + if (!rslt.helpWanted && excep !is null) + { + throw excep; + } + + return rslt; +} + +/// +@system unittest +{ + auto args = ["prog", "--foo", "-b"]; + + bool foo; + bool bar; + auto rslt = getopt(args, "foo|f", "Some information about foo.", &foo, "bar|b", + "Some help message about bar.", &bar); + + if (rslt.helpWanted) + { + defaultGetoptPrinter("Some information about the program.", + rslt.options); + } +} + +/** + Configuration options for $(D getopt). + + You can pass them to $(D getopt) in any position, except in between an option + string and its bound pointer. +*/ +enum config { + /// Turn case sensitivity on + caseSensitive, + /// Turn case sensitivity off (default) + caseInsensitive, + /// Turn bundling on + bundling, + /// Turn bundling off (default) + noBundling, + /// Pass unrecognized arguments through + passThrough, + /// Signal unrecognized arguments as errors (default) + noPassThrough, + /// Stop at first argument that does not look like an option + stopOnFirstNonOption, + /// Do not erase the endOfOptions separator from args + keepEndOfOptions, + /// Make the next option a required option + required +} + +/** The result of the $(D getopt) function. + +$(D helpWanted) is set if the option `--help` or `-h` was passed to the option parser. +*/ +struct GetoptResult { + bool helpWanted; /// Flag indicating if help was requested + Option[] options; /// All possible options +} + +/** Information about an option. +*/ +struct Option { + string optShort; /// The short symbol for this option + string optLong; /// The long symbol for this option + string help; /// The description of this option + bool required; /// If a option is required, not passing it will result in an error +} + +private pure Option splitAndGet(string opt) @trusted nothrow +{ + import std.array : split; + auto sp = split(opt, "|"); + Option ret; + if (sp.length > 1) + { + ret.optShort = "-" ~ (sp[0].length < sp[1].length ? + sp[0] : sp[1]); + ret.optLong = "--" ~ (sp[0].length > sp[1].length ? + sp[0] : sp[1]); + } + else if (sp[0].length > 1) + { + ret.optLong = "--" ~ sp[0]; + } + else + { + ret.optShort = "-" ~ sp[0]; + } + + return ret; +} + +@safe unittest +{ + auto oshort = splitAndGet("f"); + assert(oshort.optShort == "-f"); + assert(oshort.optLong == ""); + + auto olong = splitAndGet("foo"); + assert(olong.optShort == ""); + assert(olong.optLong == "--foo"); + + auto oshortlong = splitAndGet("f|foo"); + assert(oshortlong.optShort == "-f"); + assert(oshortlong.optLong == "--foo"); + + auto olongshort = splitAndGet("foo|f"); + assert(olongshort.optShort == "-f"); + assert(olongshort.optLong == "--foo"); +} + +/* +This function verifies that the variadic parameters passed in getOpt +follow this pattern: + + [config override], option, [description], receiver, + + - config override: a config value, optional + - option: a string or a char + - description: a string, optional + - receiver: a pointer or a callable +*/ +private template optionValidator(A...) +{ + import std.format : format; + import std.typecons : staticIota; + + enum fmt = "getopt validator: %s (at position %d)"; + enum isReceiver(T) = isPointer!T || (is(T == function)) || (is(T == delegate)); + enum isOptionStr(T) = isSomeString!T || isSomeChar!T; + + auto validator() + { + string msg; + static if (A.length > 0) + { + static if (isReceiver!(A[0])) + { + msg = format(fmt, "first argument must be a string or a config", 0); + } + else static if (!isOptionStr!(A[0]) && !is(A[0] == config)) + { + msg = format(fmt, "invalid argument type: " ~ A[0].stringof, 0); + } + else foreach (i; staticIota!(1, A.length)) + { + static if (!isReceiver!(A[i]) && !isOptionStr!(A[i]) && + !(is(A[i] == config))) + { + msg = format(fmt, "invalid argument type: " ~ A[i].stringof, i); + break; + } + else static if (isReceiver!(A[i]) && !isOptionStr!(A[i-1])) + { + msg = format(fmt, "a receiver can not be preceeded by a receiver", i); + break; + } + else static if (i > 1 && isOptionStr!(A[i]) && isOptionStr!(A[i-1]) + && isSomeString!(A[i-2])) + { + msg = format(fmt, "a string can not be preceeded by two strings", i); + break; + } + } + static if (!isReceiver!(A[$-1]) && !is(A[$-1] == config)) + { + msg = format(fmt, "last argument must be a receiver or a config", + A.length -1); + } + } + return msg; + } + enum message = validator; + alias optionValidator = message; +} + +@safe pure unittest +{ + alias P = void*; + alias S = string; + alias A = char; + alias C = config; + alias F = void function(); + + static assert(optionValidator!(S,P) == ""); + static assert(optionValidator!(S,F) == ""); + static assert(optionValidator!(A,P) == ""); + static assert(optionValidator!(A,F) == ""); + + static assert(optionValidator!(C,S,P) == ""); + static assert(optionValidator!(C,S,F) == ""); + static assert(optionValidator!(C,A,P) == ""); + static assert(optionValidator!(C,A,F) == ""); + + static assert(optionValidator!(C,S,S,P) == ""); + static assert(optionValidator!(C,S,S,F) == ""); + static assert(optionValidator!(C,A,S,P) == ""); + static assert(optionValidator!(C,A,S,F) == ""); + + static assert(optionValidator!(C,S,S,P) == ""); + static assert(optionValidator!(C,S,S,P,C,S,F) == ""); + static assert(optionValidator!(C,S,P,C,S,S,F) == ""); + + static assert(optionValidator!(C,A,P,A,S,F) == ""); + static assert(optionValidator!(C,A,P,C,A,S,F) == ""); + + static assert(optionValidator!(P,S,S) != ""); + static assert(optionValidator!(P,P,S) != ""); + static assert(optionValidator!(P,F,S,P) != ""); + static assert(optionValidator!(C,C,S) != ""); + static assert(optionValidator!(S,S,P,S,S,P,S) != ""); + static assert(optionValidator!(S,S,P,P) != ""); + static assert(optionValidator!(S,S,S,P) != ""); + + static assert(optionValidator!(C,A,S,P,C,A,F) == ""); + static assert(optionValidator!(C,A,P,C,A,S,F) == ""); +} + +@system unittest // bugzilla 15914 +{ + bool opt; + string[] args = ["program", "-a"]; + getopt(args, config.passThrough, 'a', &opt); + assert(opt); + opt = false; + args = ["program", "-a"]; + getopt(args, 'a', &opt); + assert(opt); + opt = false; + args = ["program", "-a"]; + getopt(args, 'a', "help string", &opt); + assert(opt); + opt = false; + args = ["program", "-a"]; + getopt(args, config.caseSensitive, 'a', "help string", &opt); + assert(opt); + + assertThrown(getopt(args, "", "forgot to put a string", &opt)); +} + +private void getoptImpl(T...)(ref string[] args, ref configuration cfg, + ref GetoptResult rslt, ref GetOptException excep, + void[][string] visitedLongOpts, void[][string] visitedShortOpts, T opts) +{ + enum validationMessage = optionValidator!T; + static assert(validationMessage == "", validationMessage); + + import std.algorithm.mutation : remove; + import std.conv : to; + static if (opts.length) + { + static if (is(typeof(opts[0]) : config)) + { + // it's a configuration flag, act on it + setConfig(cfg, opts[0]); + return getoptImpl(args, cfg, rslt, excep, visitedLongOpts, + visitedShortOpts, opts[1 .. $]); + } + else + { + // it's an option string + auto option = to!string(opts[0]); + if (option.length == 0) + { + excep = new GetOptException("An option name may not be an empty string", excep); + return; + } + Option optionHelp = splitAndGet(option); + optionHelp.required = cfg.required; + + if (optionHelp.optLong.length) + { + assert(optionHelp.optLong !in visitedLongOpts, + "Long option " ~ optionHelp.optLong ~ " is multiply defined"); + + visitedLongOpts[optionHelp.optLong] = []; + } + + if (optionHelp.optShort.length) + { + assert(optionHelp.optShort !in visitedShortOpts, + "Short option " ~ optionHelp.optShort + ~ " is multiply defined"); + + visitedShortOpts[optionHelp.optShort] = []; + } + + static if (is(typeof(opts[1]) : string)) + { + auto receiver = opts[2]; + optionHelp.help = opts[1]; + immutable lowSliceIdx = 3; + } + else + { + auto receiver = opts[1]; + immutable lowSliceIdx = 2; + } + + rslt.options ~= optionHelp; + + bool incremental; + // Handle options of the form --blah+ + if (option.length && option[$ - 1] == autoIncrementChar) + { + option = option[0 .. $ - 1]; + incremental = true; + } + + bool optWasHandled = handleOption(option, receiver, args, cfg, incremental); + + if (cfg.required && !optWasHandled) + { + excep = new GetOptException("Required option " + ~ option ~ " was not supplied", excep); + } + cfg.required = false; + + getoptImpl(args, cfg, rslt, excep, visitedLongOpts, + visitedShortOpts, opts[lowSliceIdx .. $]); + } + } + else + { + // no more options to look for, potentially some arguments left + for (size_t i = 1; i < args.length;) + { + auto a = args[i]; + if (endOfOptions.length && a == endOfOptions) + { + // Consume the "--" if keepEndOfOptions is not specified + if (!cfg.keepEndOfOptions) + args = args.remove(i); + break; + } + if (!a.length || a[0] != optionChar) + { + // not an option + if (cfg.stopOnFirstNonOption) break; + ++i; + continue; + } + if (a == "--help" || a == "-h") + { + rslt.helpWanted = true; + args = args.remove(i); + continue; + } + if (!cfg.passThrough) + { + throw new GetOptException("Unrecognized option "~a, excep); + } + ++i; + } + + Option helpOpt; + helpOpt.optShort = "-h"; + helpOpt.optLong = "--help"; + helpOpt.help = "This help information."; + rslt.options ~= helpOpt; + } +} + +private bool handleOption(R)(string option, R receiver, ref string[] args, + ref configuration cfg, bool incremental) +{ + import std.algorithm.iteration : map, splitter; + import std.ascii : isAlpha; + import std.conv : text, to; + // Scan arguments looking for a match for this option + bool ret = false; + for (size_t i = 1; i < args.length; ) + { + auto a = args[i]; + if (endOfOptions.length && a == endOfOptions) break; + if (cfg.stopOnFirstNonOption && (!a.length || a[0] != optionChar)) + { + // first non-option is end of options + break; + } + // Unbundle bundled arguments if necessary + if (cfg.bundling && a.length > 2 && a[0] == optionChar && + a[1] != optionChar) + { + string[] expanded; + foreach (j, dchar c; a[1 .. $]) + { + // If the character is not alpha, stop right there. This allows + // e.g. -j100 to work as "pass argument 100 to option -j". + if (!isAlpha(c)) + { + if (c == '=') + j++; + expanded ~= a[j + 1 .. $]; + break; + } + expanded ~= text(optionChar, c); + } + args = args[0 .. i] ~ expanded ~ args[i + 1 .. $]; + continue; + } + + string val; + if (!optMatch(a, option, val, cfg)) + { + ++i; + continue; + } + + ret = true; + + // found it + // from here on, commit to eat args[i] + // (and potentially args[i + 1] too, but that comes later) + args = args[0 .. i] ~ args[i + 1 .. $]; + + static if (is(typeof(*receiver) == bool)) + { + if (val.length) + { + // parse '--b=true/false' + *receiver = to!(typeof(*receiver))(val); + } + else + { + // no argument means set it to true + *receiver = true; + } + } + else + { + import std.exception : enforce; + // non-boolean option, which might include an argument + //enum isCallbackWithOneParameter = is(typeof(receiver("")) : void); + enum isCallbackWithLessThanTwoParameters = + (is(typeof(receiver) == delegate) || is(typeof(*receiver) == function)) && + !is(typeof(receiver("", ""))); + if (!isCallbackWithLessThanTwoParameters && !(val.length) && !incremental) + { + // Eat the next argument too. Check to make sure there's one + // to be eaten first, though. + enforce(i < args.length, + "Missing value for argument " ~ a ~ "."); + val = args[i]; + args = args[0 .. i] ~ args[i + 1 .. $]; + } + static if (is(typeof(*receiver) == enum)) + { + *receiver = to!(typeof(*receiver))(val); + } + else static if (is(typeof(*receiver) : real)) + { + // numeric receiver + if (incremental) ++*receiver; + else *receiver = to!(typeof(*receiver))(val); + } + else static if (is(typeof(*receiver) == string)) + { + // string receiver + *receiver = to!(typeof(*receiver))(val); + } + else static if (is(typeof(receiver) == delegate) || + is(typeof(*receiver) == function)) + { + static if (is(typeof(receiver("", "")) : void)) + { + // option with argument + receiver(option, val); + } + else static if (is(typeof(receiver("")) : void)) + { + static assert(is(typeof(receiver("")) : void)); + // boolean-style receiver + receiver(option); + } + else + { + static assert(is(typeof(receiver()) : void)); + // boolean-style receiver without argument + receiver(); + } + } + else static if (isArray!(typeof(*receiver))) + { + // array receiver + import std.range : ElementEncodingType; + alias E = ElementEncodingType!(typeof(*receiver)); + + if (arraySep == "") + { + *receiver ~= to!E(val); + } + else + { + foreach (elem; val.splitter(arraySep).map!(a => to!E(a))()) + *receiver ~= elem; + } + } + else static if (isAssociativeArray!(typeof(*receiver))) + { + // hash receiver + alias K = typeof(receiver.keys[0]); + alias V = typeof(receiver.values[0]); + + import std.range : only; + import std.string : indexOf; + import std.typecons : Tuple, tuple; + + static Tuple!(K, V) getter(string input) + { + auto j = indexOf(input, assignChar); + enforce!GetOptException(j != -1, "Could not find '" + ~ to!string(assignChar) ~ "' in argument '" ~ input ~ "'."); + auto key = input[0 .. j]; + auto value = input[j + 1 .. $]; + return tuple(to!K(key), to!V(value)); + } + + static void setHash(Range)(R receiver, Range range) + { + foreach (k, v; range.map!getter) + (*receiver)[k] = v; + } + + if (arraySep == "") + setHash(receiver, val.only); + else + setHash(receiver, val.splitter(arraySep)); + } + else + static assert(false, "getopt does not know how to handle the type " ~ typeof(receiver).stringof); + } + } + + return ret; +} + +// 17574 +@system unittest +{ + import std.algorithm.searching : startsWith; + + try + { + string[string] mapping; + immutable as = arraySep; + arraySep = ","; + scope (exit) + arraySep = as; + string[] args = ["testProgram", "-m", "a=b,c=\"d,e,f\""]; + args.getopt("m", &mapping); + assert(false, "Exception not thrown"); + } + catch (GetOptException goe) + assert(goe.msg.startsWith("Could not find")); +} + +// 5316 - arrays with arraySep +@system unittest +{ + import std.conv; + + arraySep = ","; + scope (exit) arraySep = ""; + + string[] names; + auto args = ["program.name", "-nfoo,bar,baz"]; + getopt(args, "name|n", &names); + assert(names == ["foo", "bar", "baz"], to!string(names)); + + names = names.init; + args = ["program.name", "-n", "foo,bar,baz"]; + getopt(args, "name|n", &names); + assert(names == ["foo", "bar", "baz"], to!string(names)); + + names = names.init; + args = ["program.name", "--name=foo,bar,baz"]; + getopt(args, "name|n", &names); + assert(names == ["foo", "bar", "baz"], to!string(names)); + + names = names.init; + args = ["program.name", "--name", "foo,bar,baz"]; + getopt(args, "name|n", &names); + assert(names == ["foo", "bar", "baz"], to!string(names)); +} + +// 5316 - associative arrays with arraySep +@system unittest +{ + import std.conv; + + arraySep = ","; + scope (exit) arraySep = ""; + + int[string] values; + values = values.init; + auto args = ["program.name", "-vfoo=0,bar=1,baz=2"]; + getopt(args, "values|v", &values); + assert(values == ["foo":0, "bar":1, "baz":2], to!string(values)); + + values = values.init; + args = ["program.name", "-v", "foo=0,bar=1,baz=2"]; + getopt(args, "values|v", &values); + assert(values == ["foo":0, "bar":1, "baz":2], to!string(values)); + + values = values.init; + args = ["program.name", "--values=foo=0,bar=1,baz=2"]; + getopt(args, "values|t", &values); + assert(values == ["foo":0, "bar":1, "baz":2], to!string(values)); + + values = values.init; + args = ["program.name", "--values", "foo=0,bar=1,baz=2"]; + getopt(args, "values|v", &values); + assert(values == ["foo":0, "bar":1, "baz":2], to!string(values)); +} + +/** + The option character (default '-'). + + Defaults to '-' but it can be assigned to prior to calling $(D getopt). + */ +dchar optionChar = '-'; + +/** + The string that conventionally marks the end of all options (default '--'). + + Defaults to "--" but can be assigned to prior to calling $(D getopt). Assigning an + empty string to $(D endOfOptions) effectively disables it. + */ +string endOfOptions = "--"; + +/** + The assignment character used in options with parameters (default '='). + + Defaults to '=' but can be assigned to prior to calling $(D getopt). + */ +dchar assignChar = '='; + +/** + The string used to separate the elements of an array or associative array + (default is "" which means the elements are separated by whitespace). + + Defaults to "" but can be assigned to prior to calling $(D getopt). + */ +string arraySep = ""; + +private enum autoIncrementChar = '+'; + +private struct configuration +{ + import std.bitmanip : bitfields; + mixin(bitfields!( + bool, "caseSensitive", 1, + bool, "bundling", 1, + bool, "passThrough", 1, + bool, "stopOnFirstNonOption", 1, + bool, "keepEndOfOptions", 1, + bool, "required", 1, + ubyte, "", 2)); +} + +private bool optMatch(string arg, string optPattern, ref string value, + configuration cfg) @safe +{ + import std.array : split; + import std.string : indexOf; + import std.uni : toUpper; + //writeln("optMatch:\n ", arg, "\n ", optPattern, "\n ", value); + //scope(success) writeln("optMatch result: ", value); + if (arg.length < 2 || arg[0] != optionChar) return false; + // yank the leading '-' + arg = arg[1 .. $]; + immutable isLong = arg.length > 1 && arg[0] == optionChar; + //writeln("isLong: ", isLong); + // yank the second '-' if present + if (isLong) arg = arg[1 .. $]; + immutable eqPos = indexOf(arg, assignChar); + if (isLong && eqPos >= 0) + { + // argument looks like --opt=value + value = arg[eqPos + 1 .. $]; + arg = arg[0 .. eqPos]; + } + else + { + if (!isLong && eqPos == 1) + { + // argument looks like -o=value + value = arg[2 .. $]; + arg = arg[0 .. 1]; + } + else + if (!isLong && !cfg.bundling) + { + // argument looks like -ovalue and there's no bundling + value = arg[1 .. $]; + arg = arg[0 .. 1]; + } + else + { + // argument looks like --opt, or -oxyz with bundling + value = null; + } + } + //writeln("Arg: ", arg, " pattern: ", optPattern, " value: ", value); + // Split the option + const variants = split(optPattern, "|"); + foreach (v ; variants) + { + //writeln("Trying variant: ", v, " against ", arg); + if (arg == v || !cfg.caseSensitive && toUpper(arg) == toUpper(v)) + return true; + if (cfg.bundling && !isLong && v.length == 1 + && indexOf(arg, v) >= 0) + { + //writeln("success"); + return true; + } + } + return false; +} + +private void setConfig(ref configuration cfg, config option) @safe pure nothrow @nogc +{ + final switch (option) + { + case config.caseSensitive: cfg.caseSensitive = true; break; + case config.caseInsensitive: cfg.caseSensitive = false; break; + case config.bundling: cfg.bundling = true; break; + case config.noBundling: cfg.bundling = false; break; + case config.passThrough: cfg.passThrough = true; break; + case config.noPassThrough: cfg.passThrough = false; break; + case config.required: cfg.required = true; break; + case config.stopOnFirstNonOption: + cfg.stopOnFirstNonOption = true; break; + case config.keepEndOfOptions: + cfg.keepEndOfOptions = true; break; + } +} + +@system unittest +{ + import std.conv; + import std.math; + + uint paranoid = 2; + string[] args = ["program.name", "--paranoid", "--paranoid", "--paranoid"]; + getopt(args, "paranoid+", ¶noid); + assert(paranoid == 5, to!(string)(paranoid)); + + enum Color { no, yes } + Color color; + args = ["program.name", "--color=yes",]; + getopt(args, "color", &color); + assert(color, to!(string)(color)); + + color = Color.no; + args = ["program.name", "--color", "yes",]; + getopt(args, "color", &color); + assert(color, to!(string)(color)); + + string data = "file.dat"; + int length = 24; + bool verbose = false; + args = ["program.name", "--length=5", "--file", "dat.file", "--verbose"]; + getopt( + args, + "length", &length, + "file", &data, + "verbose", &verbose); + assert(args.length == 1); + assert(data == "dat.file"); + assert(length == 5); + assert(verbose); + + // + string[] outputFiles; + args = ["program.name", "--output=myfile.txt", "--output", "yourfile.txt"]; + getopt(args, "output", &outputFiles); + assert(outputFiles.length == 2 + && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt"); + + outputFiles = []; + arraySep = ","; + args = ["program.name", "--output", "myfile.txt,yourfile.txt"]; + getopt(args, "output", &outputFiles); + assert(outputFiles.length == 2 + && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt"); + arraySep = ""; + + foreach (testArgs; + [["program.name", "--tune=alpha=0.5", "--tune", "beta=0.6"], + ["program.name", "--tune=alpha=0.5,beta=0.6"], + ["program.name", "--tune", "alpha=0.5,beta=0.6"]]) + { + arraySep = ","; + double[string] tuningParms; + getopt(testArgs, "tune", &tuningParms); + assert(testArgs.length == 1); + assert(tuningParms.length == 2); + assert(approxEqual(tuningParms["alpha"], 0.5)); + assert(approxEqual(tuningParms["beta"], 0.6)); + arraySep = ""; + } + + uint verbosityLevel = 1; + void myHandler(string option) + { + if (option == "quiet") + { + verbosityLevel = 0; + } + else + { + assert(option == "verbose"); + verbosityLevel = 2; + } + } + args = ["program.name", "--quiet"]; + getopt(args, "verbose", &myHandler, "quiet", &myHandler); + assert(verbosityLevel == 0); + args = ["program.name", "--verbose"]; + getopt(args, "verbose", &myHandler, "quiet", &myHandler); + assert(verbosityLevel == 2); + + verbosityLevel = 1; + void myHandler2(string option, string value) + { + assert(option == "verbose"); + verbosityLevel = 2; + } + args = ["program.name", "--verbose", "2"]; + getopt(args, "verbose", &myHandler2); + assert(verbosityLevel == 2); + + verbosityLevel = 1; + void myHandler3() + { + verbosityLevel = 2; + } + args = ["program.name", "--verbose"]; + getopt(args, "verbose", &myHandler3); + assert(verbosityLevel == 2); + + bool foo, bar; + args = ["program.name", "--foo", "--bAr"]; + getopt(args, + std.getopt.config.caseSensitive, + std.getopt.config.passThrough, + "foo", &foo, + "bar", &bar); + assert(args[1] == "--bAr"); + + // test stopOnFirstNonOption + + args = ["program.name", "--foo", "nonoption", "--bar"]; + foo = bar = false; + getopt(args, + std.getopt.config.stopOnFirstNonOption, + "foo", &foo, + "bar", &bar); + assert(foo && !bar && args[1] == "nonoption" && args[2] == "--bar"); + + args = ["program.name", "--foo", "nonoption", "--zab"]; + foo = bar = false; + getopt(args, + std.getopt.config.stopOnFirstNonOption, + "foo", &foo, + "bar", &bar); + assert(foo && !bar && args[1] == "nonoption" && args[2] == "--zab"); + + args = ["program.name", "--fb1", "--fb2=true", "--tb1=false"]; + bool fb1, fb2; + bool tb1 = true; + getopt(args, "fb1", &fb1, "fb2", &fb2, "tb1", &tb1); + assert(fb1 && fb2 && !tb1); + + // test keepEndOfOptions + + args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"]; + getopt(args, + std.getopt.config.keepEndOfOptions, + "foo", &foo, + "bar", &bar); + assert(args == ["program.name", "nonoption", "--", "--baz"]); + + // Ensure old behavior without the keepEndOfOptions + + args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"]; + getopt(args, + "foo", &foo, + "bar", &bar); + assert(args == ["program.name", "nonoption", "--baz"]); + + // test function callbacks + + static class MyEx : Exception + { + this() { super(""); } + this(string option) { this(); this.option = option; } + this(string option, string value) { this(option); this.value = value; } + + string option; + string value; + } + + static void myStaticHandler1() { throw new MyEx(); } + args = ["program.name", "--verbose"]; + try { getopt(args, "verbose", &myStaticHandler1); assert(0); } + catch (MyEx ex) { assert(ex.option is null && ex.value is null); } + + static void myStaticHandler2(string option) { throw new MyEx(option); } + args = ["program.name", "--verbose"]; + try { getopt(args, "verbose", &myStaticHandler2); assert(0); } + catch (MyEx ex) { assert(ex.option == "verbose" && ex.value is null); } + + static void myStaticHandler3(string option, string value) { throw new MyEx(option, value); } + args = ["program.name", "--verbose", "2"]; + try { getopt(args, "verbose", &myStaticHandler3); assert(0); } + catch (MyEx ex) { assert(ex.option == "verbose" && ex.value == "2"); } +} + +@safe unittest // @safe std.getopt.config option use +{ + long x = 0; + string[] args = ["program", "--inc-x", "--inc-x"]; + getopt(args, + std.getopt.config.caseSensitive, + "inc-x", "Add one to x", delegate void() { x++; }); + assert(x == 2); +} + +@system unittest +{ + // From bugzilla 2142 + bool f_linenum, f_filename; + string[] args = [ "", "-nl" ]; + getopt + ( + args, + std.getopt.config.bundling, + //std.getopt.config.caseSensitive, + "linenum|l", &f_linenum, + "filename|n", &f_filename + ); + assert(f_linenum); + assert(f_filename); +} + +@system unittest +{ + // From bugzilla 6887 + string[] p; + string[] args = ["", "-pa"]; + getopt(args, "p", &p); + assert(p.length == 1); + assert(p[0] == "a"); +} + +@system unittest +{ + // From bugzilla 6888 + int[string] foo; + auto args = ["", "-t", "a=1"]; + getopt(args, "t", &foo); + assert(foo == ["a":1]); +} + +@system unittest +{ + // From bugzilla 9583 + int opt; + auto args = ["prog", "--opt=123", "--", "--a", "--b", "--c"]; + getopt(args, "opt", &opt); + assert(args == ["prog", "--a", "--b", "--c"]); +} + +@system unittest +{ + string foo, bar; + auto args = ["prog", "-thello", "-dbar=baz"]; + getopt(args, "t", &foo, "d", &bar); + assert(foo == "hello"); + assert(bar == "bar=baz"); + + // From bugzilla 5762 + string a; + args = ["prog", "-a-0x12"]; + getopt(args, config.bundling, "a|addr", &a); + assert(a == "-0x12", a); + args = ["prog", "--addr=-0x12"]; + getopt(args, config.bundling, "a|addr", &a); + assert(a == "-0x12"); + + // From https://d.puremagic.com/issues/show_bug.cgi?id=11764 + args = ["main", "-test"]; + bool opt; + args.getopt(config.passThrough, "opt", &opt); + assert(args == ["main", "-test"]); + + // From https://issues.dlang.org/show_bug.cgi?id=15220 + args = ["main", "-o=str"]; + string o; + args.getopt("o", &o); + assert(o == "str"); + + args = ["main", "-o=str"]; + o = null; + args.getopt(config.bundling, "o", &o); + assert(o == "str"); +} + +@system unittest // 5228 +{ + import std.conv; + import std.exception; + + auto args = ["prog", "--foo=bar"]; + int abc; + assertThrown!GetOptException(getopt(args, "abc", &abc)); + + args = ["prog", "--abc=string"]; + assertThrown!ConvException(getopt(args, "abc", &abc)); +} + +@system unittest // From bugzilla 7693 +{ + import std.exception; + + enum Foo { + bar, + baz + } + + auto args = ["prog", "--foo=barZZZ"]; + Foo foo; + assertThrown(getopt(args, "foo", &foo)); + args = ["prog", "--foo=bar"]; + assertNotThrown(getopt(args, "foo", &foo)); + args = ["prog", "--foo", "barZZZ"]; + assertThrown(getopt(args, "foo", &foo)); + args = ["prog", "--foo", "baz"]; + assertNotThrown(getopt(args, "foo", &foo)); +} + +@system unittest // same bug as 7693 only for bool +{ + import std.exception; + + auto args = ["prog", "--foo=truefoobar"]; + bool foo; + assertThrown(getopt(args, "foo", &foo)); + args = ["prog", "--foo"]; + getopt(args, "foo", &foo); + assert(foo); +} + +@system unittest +{ + bool foo; + auto args = ["prog", "--foo"]; + getopt(args, "foo", &foo); + assert(foo); +} + +@system unittest +{ + bool foo; + bool bar; + auto args = ["prog", "--foo", "-b"]; + getopt(args, config.caseInsensitive,"foo|f", "Some foo", &foo, + config.caseSensitive, "bar|b", "Some bar", &bar); + assert(foo); + assert(bar); +} + +@system unittest +{ + bool foo; + bool bar; + auto args = ["prog", "-b", "--foo", "-z"]; + getopt(args, config.caseInsensitive, config.required, "foo|f", "Some foo", + &foo, config.caseSensitive, "bar|b", "Some bar", &bar, + config.passThrough); + assert(foo); + assert(bar); +} + +@system unittest +{ + import std.exception; + + bool foo; + bool bar; + auto args = ["prog", "-b", "-z"]; + assertThrown(getopt(args, config.caseInsensitive, config.required, "foo|f", + "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar", &bar, + config.passThrough)); +} + +@system unittest +{ + import std.exception; + + bool foo; + bool bar; + auto args = ["prog", "--foo", "-z"]; + assertNotThrown(getopt(args, config.caseInsensitive, config.required, + "foo|f", "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar", + &bar, config.passThrough)); + assert(foo); + assert(!bar); +} + +@system unittest +{ + bool foo; + auto args = ["prog", "-f"]; + auto r = getopt(args, config.caseInsensitive, "help|f", "Some foo", &foo); + assert(foo); + assert(!r.helpWanted); +} + +@safe unittest // implicit help option without config.passThrough +{ + string[] args = ["program", "--help"]; + auto r = getopt(args); + assert(r.helpWanted); +} + +// Issue 13316 - std.getopt: implicit help option breaks the next argument +@system unittest +{ + string[] args = ["program", "--help", "--", "something"]; + getopt(args); + assert(args == ["program", "something"]); + + args = ["program", "--help", "--"]; + getopt(args); + assert(args == ["program"]); + + bool b; + args = ["program", "--help", "nonoption", "--option"]; + getopt(args, config.stopOnFirstNonOption, "option", &b); + assert(args == ["program", "nonoption", "--option"]); +} + +// Issue 13317 - std.getopt: endOfOptions broken when it doesn't look like an option +@system unittest +{ + auto endOfOptionsBackup = endOfOptions; + scope(exit) endOfOptions = endOfOptionsBackup; + endOfOptions = "endofoptions"; + string[] args = ["program", "endofoptions", "--option"]; + bool b = false; + getopt(args, "option", &b); + assert(!b); + assert(args == ["program", "--option"]); +} + +/** This function prints the passed $(D Option)s and text in an aligned manner on $(D stdout). + +The passed text will be printed first, followed by a newline, then the short +and long version of every option will be printed. The short and long version +will be aligned to the longest option of every $(D Option) passed. If the option +is required, then "Required:" will be printed after the long version of the +$(D Option). If a help message is present it will be printed next. The format is +illustrated by this code: + +------------ +foreach (it; opt) +{ + writefln("%*s %*s%s%s", lengthOfLongestShortOption, it.optShort, + lengthOfLongestLongOption, it.optLong, + it.required ? " Required: " : " ", it.help); +} +------------ + +Params: + text = The text to printed at the beginning of the help output. + opt = The $(D Option) extracted from the $(D getopt) parameter. +*/ +void defaultGetoptPrinter(string text, Option[] opt) +{ + import std.stdio : stdout; + + defaultGetoptFormatter(stdout.lockingTextWriter(), text, opt); +} + +/** This function writes the passed text and $(D Option) into an output range +in the manner described in the documentation of function +$(D defaultGetoptPrinter). + +Params: + output = The output range used to write the help information. + text = The text to print at the beginning of the help output. + opt = The $(D Option) extracted from the $(D getopt) parameter. +*/ +void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt) +{ + import std.algorithm.comparison : min, max; + import std.format : formattedWrite; + + output.formattedWrite("%s\n", text); + + size_t ls, ll; + bool hasRequired = false; + foreach (it; opt) + { + ls = max(ls, it.optShort.length); + ll = max(ll, it.optLong.length); + + hasRequired = hasRequired || it.required; + } + + string re = " Required: "; + + foreach (it; opt) + { + output.formattedWrite("%*s %*s%*s%s\n", ls, it.optShort, ll, it.optLong, + hasRequired ? re.length : 1, it.required ? re : " ", it.help); + } +} + +@system unittest +{ + import std.conv; + + import std.array; + import std.string; + bool a; + auto args = ["prog", "--foo"]; + auto t = getopt(args, "foo|f", "Help", &a); + string s; + auto app = appender!string(); + defaultGetoptFormatter(app, "Some Text", t.options); + + string helpMsg = app.data; + //writeln(helpMsg); + assert(helpMsg.length); + assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " " + ~ helpMsg); + assert(helpMsg.indexOf("--foo") != -1); + assert(helpMsg.indexOf("-f") != -1); + assert(helpMsg.indexOf("-h") != -1); + assert(helpMsg.indexOf("--help") != -1); + assert(helpMsg.indexOf("Help") != -1); + + string wanted = "Some Text\n-f --foo Help\n-h --help This help " + ~ "information.\n"; + assert(wanted == helpMsg); +} + +@system unittest +{ + import std.array ; + import std.conv; + import std.string; + bool a; + auto args = ["prog", "--foo"]; + auto t = getopt(args, config.required, "foo|f", "Help", &a); + string s; + auto app = appender!string(); + defaultGetoptFormatter(app, "Some Text", t.options); + + string helpMsg = app.data; + //writeln(helpMsg); + assert(helpMsg.length); + assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " " + ~ helpMsg); + assert(helpMsg.indexOf("Required:") != -1); + assert(helpMsg.indexOf("--foo") != -1); + assert(helpMsg.indexOf("-f") != -1); + assert(helpMsg.indexOf("-h") != -1); + assert(helpMsg.indexOf("--help") != -1); + assert(helpMsg.indexOf("Help") != -1); + + string wanted = "Some Text\n-f --foo Required: Help\n-h --help " + ~ " This help information.\n"; + assert(wanted == helpMsg, helpMsg ~ wanted); +} + +@system unittest // Issue 14724 +{ + bool a; + auto args = ["prog", "--help"]; + GetoptResult rslt; + try + { + rslt = getopt(args, config.required, "foo|f", "bool a", &a); + } + catch (Exception e) + { + enum errorMsg = "If the request for help was passed required options" ~ + "must not be set."; + assert(false, errorMsg); + } + + assert(rslt.helpWanted); +} + +// throw on duplicate options +@system unittest +{ + import core.exception; + auto args = ["prog", "--abc", "1"]; + int abc, def; + assertThrown!AssertError(getopt(args, "abc", &abc, "abc", &abc)); + assertThrown!AssertError(getopt(args, "abc|a", &abc, "def|a", &def)); + assertNotThrown!AssertError(getopt(args, "abc", &abc, "def", &def)); +} + +@system unittest // Issue 17327 repeated option use +{ + long num = 0; + + string[] args = ["program", "--num", "3"]; + getopt(args, "n|num", &num); + assert(num == 3); + + args = ["program", "--num", "3", "--num", "5"]; + getopt(args, "n|num", &num); + assert(num == 5); + + args = ["program", "--n", "3", "--num", "5", "-n", "-7"]; + getopt(args, "n|num", &num); + assert(num == -7); + + void add1() { num++; } + void add2(string option) { num += 2; } + void addN(string option, string value) + { + import std.conv : to; + num += value.to!long; + } + + num = 0; + args = ["program", "--add1", "--add2", "--add1", "--add", "5", "--add2", "--add", "10"]; + getopt(args, + "add1", "Add 1 to num", &add1, + "add2", "Add 2 to num", &add2, + "add", "Add N to num", &addN,); + assert(num == 21); + + bool flag = false; + args = ["program", "--flag"]; + getopt(args, "f|flag", "Boolean", &flag); + assert(flag); + + flag = false; + args = ["program", "-f", "-f"]; + getopt(args, "f|flag", "Boolean", &flag); + assert(flag); + + flag = false; + args = ["program", "--flag=true", "--flag=false"]; + getopt(args, "f|flag", "Boolean", &flag); + assert(!flag); + + flag = false; + args = ["program", "--flag=true", "--flag=false", "-f"]; + getopt(args, "f|flag", "Boolean", &flag); + assert(flag); +} + +@safe unittest // Delegates as callbacks +{ + alias TwoArgOptionHandler = void delegate(string option, string value) @safe; + + TwoArgOptionHandler makeAddNHandler(ref long dest) + { + void addN(ref long dest, string n) + { + import std.conv : to; + dest += n.to!long; + } + + return (option, value) => addN(dest, value); + } + + long x = 0; + long y = 0; + + string[] args = + ["program", "--x-plus-1", "--x-plus-1", "--x-plus-5", "--x-plus-n", "10", + "--y-plus-n", "25", "--y-plus-7", "--y-plus-n", "15", "--y-plus-3"]; + + getopt(args, + "x-plus-1", "Add one to x", delegate void() { x += 1; }, + "x-plus-5", "Add five to x", delegate void(string option) { x += 5; }, + "x-plus-n", "Add NUM to x", makeAddNHandler(x), + "y-plus-7", "Add seven to y", delegate void() { y += 7; }, + "y-plus-3", "Add three to y", delegate void(string option) { y += 3; }, + "y-plus-n", "Add NUM to x", makeAddNHandler(y),); + + assert(x == 17); + assert(y == 50); +} + +@system unittest // Hyphens at the start of option values; Issue 17650 +{ + auto args = ["program", "-m", "-5", "-n", "-50", "-c", "-", "-f", "-"]; + + int m; + int n; + char c; + string f; + + getopt(args, + "m|mm", "integer", &m, + "n|nn", "integer", &n, + "c|cc", "character", &c, + "f|file", "filename or hyphen for stdin", &f); + + assert(m == -5); + assert(n == -50); + assert(c == '-'); + assert(f == "-"); +} diff --git a/libphobos/src/std/internal/cstring.d b/libphobos/src/std/internal/cstring.d new file mode 100644 index 0000000..257a100 --- /dev/null +++ b/libphobos/src/std/internal/cstring.d @@ -0,0 +1,267 @@ +/** +Helper functions for working with $(I C strings). + +This module is intended to provide fast, safe and garbage free +way to work with $(I C strings). + +Copyright: Denis Shelomovskij 2013-2014 + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: Denis Shelomovskij + +Macros: +COREREF = $(HTTP dlang.org/phobos/core_$1.html#$2, $(D core.$1.$2)) +*/ +module std.internal.cstring; + +/// +@safe unittest +{ + version (Posix) + { + import core.stdc.stdlib : free; + import core.sys.posix.stdlib : setenv; + import std.exception : enforce; + + void setEnvironment(in char[] name, in char[] value) + { enforce(setenv(name.tempCString(), value.tempCString(), 1) != -1); } + } + + version (Windows) + { + import core.sys.windows.windows : SetEnvironmentVariableW; + import std.exception : enforce; + + void setEnvironment(in char[] name, in char[] value) + { enforce(SetEnvironmentVariableW(name.tempCStringW(), value.tempCStringW())); } + } +} + +import std.range; +import std.traits; + +version (unittest) +@property inout(C)[] asArray(C)(inout C* cstr) pure nothrow @nogc @trusted +if (isSomeChar!C) +in { assert(cstr); } +body +{ + size_t length = 0; + while (cstr[length]) + ++length; + return cstr[0 .. length]; +} + +/** +Creates temporary 0-terminated $(I C string) with copy of passed text. + +Params: + To = character type of returned C string + str = string or input range to be converted + +Returns: + +The value returned is implicitly convertible to $(D const To*) and +has two properties: $(D ptr) to access $(I C string) as $(D const To*) +and $(D buffPtr) to access it as $(D To*). + +The value returned can be indexed by [] to access it as an array. + +The temporary $(I C string) is valid unless returned object is destroyed. +Thus if returned object is assigned to a variable the temporary is +valid unless the variable goes out of scope. If returned object isn't +assigned to a variable it will be destroyed at the end of creating +primary expression. + +Implementation_note: +For small strings tempCString will use stack allocated buffer, +for large strings (approximately 250 characters and more) it will +allocate temporary one using C's $(D malloc). + +Note: +This function is intended to be used in function call expression (like +$(D strlen(str.tempCString()))). Incorrect usage of this function may +lead to memory corruption. +See $(RED WARNING) in $(B Examples) section. +*/ + +auto tempCString(To = char, From)(From str) +if (isSomeChar!To && (isInputRange!From || isSomeString!From) && + isSomeChar!(ElementEncodingType!From)) +{ + + alias CF = Unqual!(ElementEncodingType!From); + + enum To* useStack = () @trusted { return cast(To*) size_t.max; }(); + + static struct Res + { + @trusted: + nothrow @nogc: + + @disable this(); + @disable this(this); + alias ptr this; + + @property inout(To)* buffPtr() inout pure + { + return _ptr == useStack ? _buff.ptr : _ptr; + } + + @property const(To)* ptr() const pure + { + return buffPtr; + } + + const(To)[] opIndex() const pure + { + return buffPtr[0 .. _length]; + } + + ~this() + { + if (_ptr != useStack) + { + import core.stdc.stdlib : free; + free(_ptr); + } + } + + private: + To* _ptr; + size_t _length; // length of the string + version (unittest) + { + enum buffLength = 16 / To.sizeof; // smaller size to trigger reallocations + } + else + { + enum buffLength = 256 / To.sizeof; // production size + } + + To[buffLength] _buff; // the 'small string optimization' + + static Res trustedVoidInit() { Res res = void; return res; } + } + + Res res = Res.trustedVoidInit(); // expensive to fill _buff[] + + // Note: res._ptr can't point to res._buff as structs are movable. + + To[] p; + bool p_is_onstack = true; + size_t i; + + static To[] trustedRealloc(To[] buf, size_t i, To[] res, size_t strLength, bool res_is_onstack) + @trusted @nogc nothrow + { + pragma(inline, false); // because it's rarely called + + import core.exception : onOutOfMemoryError; + import core.stdc.stdlib : malloc, realloc; + import core.stdc.string : memcpy; + + if (res_is_onstack) + { + size_t newlen = res.length * 3 / 2; + if (newlen <= strLength) + newlen = strLength + 1; // +1 for terminating 0 + auto ptr = cast(To*) malloc(newlen * To.sizeof); + if (!ptr) + onOutOfMemoryError(); + memcpy(ptr, res.ptr, i * To.sizeof); + return ptr[0 .. newlen]; + } + else + { + if (buf.length >= size_t.max / (2 * To.sizeof)) + onOutOfMemoryError(); + const newlen = buf.length * 3 / 2; + auto ptr = cast(To*) realloc(buf.ptr, newlen * To.sizeof); + if (!ptr) + onOutOfMemoryError(); + return ptr[0 .. newlen]; + } + } + + size_t strLength; + static if (hasLength!From) + { + strLength = str.length; + } + import std.utf : byUTF; + static if (isSomeString!From) + { + auto r = cast(const(CF)[])str; // because inout(CF) causes problems with byUTF + if (r is null) // Bugzilla 14980 + { + res._ptr = null; + return res; + } + } + else + alias r = str; + To[] q = res._buff; + foreach (const c; byUTF!(Unqual!To)(r)) + { + if (i + 1 == q.length) + { + p = trustedRealloc(p, i, res._buff, strLength, p_is_onstack); + p_is_onstack = false; + q = p; + } + q[i++] = c; + } + q[i] = 0; + res._length = i; + res._ptr = p_is_onstack ? useStack : &p[0]; + return res; +} + +/// +nothrow @nogc @system unittest +{ + import core.stdc.string; + + string str = "abc"; + + // Intended usage + assert(strlen(str.tempCString()) == 3); + + // Correct usage + auto tmp = str.tempCString(); + assert(strlen(tmp) == 3); // or `tmp.ptr`, or `tmp.buffPtr` + + // $(RED WARNING): $(RED Incorrect usage) + auto pInvalid1 = str.tempCString().ptr; + const char* pInvalid2 = str.tempCString(); + // Both pointers refer to invalid memory here as + // returned values aren't assigned to a variable and + // both primary expressions are ended. +} + +@safe nothrow @nogc unittest +{ + assert("abc".tempCString().asArray == "abc"); + assert("abc"d.tempCString().ptr.asArray == "abc"); + assert("abc".tempCString!wchar().buffPtr.asArray == "abc"w); + + import std.utf : byChar, byWchar; + char[300] abc = 'a'; + assert(tempCString(abc[].byChar).buffPtr.asArray == abc); + assert(tempCString(abc[].byWchar).buffPtr.asArray == abc); + assert(tempCString(abc[].byChar)[] == abc); +} + +// Bugzilla 14980 +nothrow @nogc @safe unittest +{ + const(char[]) str = null; + auto res = tempCString(str); + const char* ptr = res; + assert(ptr is null); +} + +version (Windows) + alias tempCStringW = tempCString!(wchar, const(char)[]); diff --git a/libphobos/src/std/internal/digest/sha_SSSE3.d b/libphobos/src/std/internal/digest/sha_SSSE3.d new file mode 100644 index 0000000..4060f34 --- /dev/null +++ b/libphobos/src/std/internal/digest/sha_SSSE3.d @@ -0,0 +1,729 @@ +// Written in the D programming language. + +/** + * Computes SHA1 digests of arbitrary data, using an optimized algorithm with SSSE3 instructions. + * + * Authors: + * The general idea is described by Dean Gaudet. + * Another important observation is published by Max Locktyukhin. + * (Both implementations are public domain.) + * Translation to X86 and D by Kai Nacke + * + * References: + * $(LINK2 http://arctic.org/~dean/crypto/sha1.html) + * $(LINK2 http://software.intel.com/en-us/articles/improving-the-performance-of-the-secure-hash-algorithm-1/, Fast implementation of SHA1) + */ +module std.internal.digest.sha_SSSE3; + +version (D_InlineAsm_X86) +{ + version (D_PIC) {} // Bugzilla 9378 + else + { + private version = USE_SSSE3; + private version = _32Bit; + } +} +else version (D_InlineAsm_X86_64) +{ + private version = USE_SSSE3; + private version = _64Bit; +} + +/* + * The idea is quite simple. The SHA-1 specification defines the following message schedule: + * W[i] = (W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16]) rol 1 + * + * To employ SSE, simply write down the formula four times: + * W[i ] = (W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16]) rol 1 + * W[i+1] = (W[i-2] ^ W[i-7] ^ W[i-13] ^ W[i-15]) rol 1 + * W[i+2] = (W[i-1] ^ W[i-6] ^ W[i-12] ^ W[i-14]) rol 1 + * W[i+3] = (W[i ] ^ W[i-5] ^ W[i-11] ^ W[i-13]) rol 1 + * The last formula requires value W[i] computed with the first formula. + * Because the xor operation and the rotate operation are commutative, we can replace the + * last formula with + * W[i+3] = ( 0 ^ W[i-5] ^ W[i-11] ^ W[i-13]) rol 1 + * and then calculate + * W[i+3] ^= W[i] rol 1 + * which unfortunately requires many additional operations. This approach was described by + * Dean Gaudet. + * + * Max Locktyukhin observed that + * W[i] = W[i-A] ^ W[i-B] + * is equivalent to + * W[i] = W[i-2*A] ^ W[i-2*B] + * (if the indices are still in valid ranges). Using this observation, the formula is + * translated to + * W[i] = (W[i-6] ^ W[i-16] ^ W[i-28] ^ W[i-32]) rol 2 + * Again, to employ SSE the formula is used four times. + * + * Later on, the expression W[i] + K(i) is used. (K(i) is the constant used in round i.) + * Once the 4 W[i] are calculated, we can also add the four K(i) values with one SSE instruction. + * + * The 32bit and 64bit implementations are almost identical. The main difference is that there + * are only 8 XMM registers in 32bit mode. Therefore, space on the stack is needed to save + * computed values. + */ + +version (USE_SSSE3) +{ + /* + * The general idea is to use the XMM registers as a sliding window over + * message schedule. XMM0 to XMM7 are used to store the last 64 byte of + * the message schedule. In 64 bit mode this is fine because of the number of + * registers. The main difference of the 32 bit code is that a part of the + * calculated message schedule is saved on the stack because 2 temporary + * registers are needed. + */ + + /* Number of message words we are precalculating. */ + private immutable int PRECALC_AHEAD = 16; + + /* T1 and T2 are used for intermediate results of computations. */ + private immutable string T1 = "EAX"; + private immutable string T2 = "EBX"; + + /* The registers used for the SHA-1 variables. */ + private immutable string A = "ECX"; + private immutable string B = "ESI"; + private immutable string C = "EDI"; + private immutable string D = "EBP"; + private immutable string E = "EDX"; + + /* */ + version (_32Bit) + { + private immutable string SP = "ESP"; + private immutable string BUFFER_PTR = "EAX"; + private immutable string STATE_PTR = "EBX"; + + // Control byte for shuffle instruction (only used in round 0-15) + private immutable string X_SHUFFLECTL = "XMM6"; + + // Round constant (only used in round 0-15) + private immutable string X_CONSTANT = "XMM7"; + } + version (_64Bit) + { + private immutable string SP = "RSP"; + private immutable string BUFFER_PTR = "R9"; + private immutable string STATE_PTR = "R8"; + private immutable string CONSTANTS_PTR = "R10"; + + // Registers for temporary results (XMM10 and XMM11 are also used temporary) + private immutable string W_TMP = "XMM8"; + private immutable string W_TMP2 = "XMM9"; + + // Control byte for shuffle instruction (only used in round 0-15) + private immutable string X_SHUFFLECTL = "XMM12"; + + // Round constant + private immutable string X_CONSTANT = "XMM13"; + } + + /* The control words for the byte shuffle instruction and the round constants. */ + align(16) public immutable uint[20] constants = + [ + // The control words for the byte shuffle instruction. + 0x0001_0203, 0x0405_0607, 0x0809_0a0b, 0x0c0d_0e0f, + // Constants for round 0-19 + 0x5a827999, 0x5a827999, 0x5a827999, 0x5a827999, + // Constants for round 20-39 + 0x6ed9eba1, 0x6ed9eba1, 0x6ed9eba1, 0x6ed9eba1, + // Constants for round 40-59 + 0x8f1bbcdc, 0x8f1bbcdc, 0x8f1bbcdc, 0x8f1bbcdc, + // Constants for round 60-79 + 0xca62c1d6, 0xca62c1d6, 0xca62c1d6, 0xca62c1d6 + ]; + + /** Simple version to produce numbers < 100 as string. */ + private nothrow pure string to_string(uint i) + { + if (i < 10) + return "0123456789"[i .. i + 1]; + + assert(i < 100); + char[2] s; + s[0] = cast(char)(i / 10 + '0'); + s[1] = cast(char)(i % 10 + '0'); + return s.idup; + } + + /** Returns the reference to the byte shuffle control word. */ + private nothrow pure string bswap_shufb_ctl() + { + version (_64Bit) + return "["~CONSTANTS_PTR~"]"; + else + return "[constants]"; + } + + /** Returns the reference to constant used in round i. */ + private nothrow pure string constant(uint i) + { + version (_64Bit) + return "16 + 16*"~to_string(i/20)~"["~CONSTANTS_PTR~"]"; + else + return "[constants + 16 + 16*"~to_string(i/20)~"]"; + } + + /** Returns the XMM register number used in round i */ + private nothrow pure uint regno(uint i) + { + return (i/4)&7; + } + + /** Returns reference to storage of vector W[i .. i+4]. */ + private nothrow pure string WiV(uint i) + { + return "["~SP~" + WI_PTR + "~to_string((i/4)&7)~"*16]"; + } + + /** Returns reference to storage of vector (W + K)[i .. i+4]. */ + private nothrow pure string WiKiV(uint i) + { + return "["~SP~" + WI_PLUS_KI_PTR + "~to_string((i/4)&3)~"*16]"; + } + + /** Returns reference to storage of value W[i] + K[i]. */ + private nothrow pure string WiKi(uint i) + { + return "["~SP~" + WI_PLUS_KI_PTR + 4*"~to_string(i&15)~"]"; + } + + /** + * Chooses the instruction sequence based on the 32bit or 64bit model. + */ + private nothrow pure string[] swt3264(string[] insn32, string[] insn64) + { + version (_32Bit) + { + return insn32; + } + version (_64Bit) + { + return insn64; + } + } + + /** + * Flattens the instruction sequence and wraps it in an asm block. + */ + private nothrow pure string wrap(string[] insn) + { + string s = "asm pure nothrow @nogc {"; + foreach (t; insn) s ~= (t ~ "; \n"); + s ~= "}"; + return s; + // Is not CTFE: + // return "asm pure nothrow @nogc { " ~ join(insn, "; \n") ~ "}"; + } + + /** + * Weaves the 2 instruction sequences together. + */ + private nothrow pure string[] weave(string[] seq1, string[] seq2, uint dist = 1) + { + string[] res = []; + auto i1 = 0, i2 = 0; + while (i1 < seq1.length || i2 < seq2.length) + { + if (i2 < seq2.length) + { + res ~= seq2[i2 .. i2+1]; + i2 += 1; + } + if (i1 < seq1.length) + { + import std.algorithm.comparison : min; + + res ~= seq1[i1 .. min(i1+dist, $)]; + i1 += dist; + } + } + return res; + } + + /** + * Generates instructions to load state from memory into registers. + */ + private nothrow pure string[] loadstate(string base, string a, string b, string c, string d, string e) + { + return ["mov "~a~",["~base~" + 0*4]", + "mov "~b~",["~base~" + 1*4]", + "mov "~c~",["~base~" + 2*4]", + "mov "~d~",["~base~" + 3*4]", + "mov "~e~",["~base~" + 4*4]" ]; + } + + /** + * Generates instructions to update state from registers, saving result in memory. + */ + private nothrow pure string[] savestate(string base, string a, string b, string c, string d, string e) + { + return ["add ["~base~" + 0*4],"~a, + "add ["~base~" + 1*4],"~b, + "add ["~base~" + 2*4],"~c, + "add ["~base~" + 3*4],"~d, + "add ["~base~" + 4*4],"~e ]; + } + + /** Calculates Ch(x, y, z) = z ^ (x & (y ^ z)) */ + private nothrow pure string[] Ch(string x, string y, string z) + { + return ["mov "~T1~","~y, + "xor "~T1~","~z, + "and "~T1~","~x, + "xor "~T1~","~z ]; + } + + /** Calculates Parity(x, y, z) = x ^ y ^ z */ + private nothrow pure string[] Parity(string x, string y, string z) + { + return ["mov "~T1~","~z, + "xor "~T1~","~y, + "xor "~T1~","~x ]; + } + + /** Calculates Maj(x, y, z) = (x & y) | (z & (x ^ y)) */ + private nothrow pure string[] Maj(string x, string y, string z) + { + return ["mov "~T1~","~y, + "mov "~T2~","~x, + "or "~T1~","~x, + "and "~T2~","~y, + "and "~T1~","~z, + "or "~T1~","~T2 ]; + } + + /** Returns function for round i. Function returns result in T1 and may destroy T2. */ + private nothrow pure string[] F(int i, string b, string c, string d) + { + string[] insn; + if (i >= 0 && i <= 19) insn = Ch(b, c, d); + else if (i >= 20 && i <= 39) insn = Parity(b, c, d); + else if (i >= 40 && i <= 59) insn = Maj(b, c, d); + else if (i >= 60 && i <= 79) insn = Parity(b, c, d); + else assert(false, "Coding error"); + return insn; + } + + /** Returns instruction used to setup a round. */ + private nothrow pure string[] xsetup(int i) + { + if (i == 0) + { + return swt3264(["movdqa "~X_SHUFFLECTL~","~bswap_shufb_ctl(), + "movdqa "~X_CONSTANT~","~constant(i)], + ["movdqa "~X_SHUFFLECTL~","~bswap_shufb_ctl(), + "movdqa "~X_CONSTANT~","~constant(i)]); + } + version (_64Bit) + { + if (i%20 == 0) + { + return ["movdqa "~X_CONSTANT~","~constant(i)]; + } + } + return []; + } + + /** + * Loads the message words and performs the little to big endian conversion. + * Requires that the shuffle control word and the round constant is loaded + * into required XMM register. The BUFFER_PTR register must point to the + * buffer. + */ + private nothrow pure string[] precalc_00_15(int i) + { + int regno = regno(i); + + string W = "XMM" ~ to_string(regno); + version (_32Bit) + { + string W_TMP = "XMM" ~ to_string(regno+2); + } + version (_64Bit) + { + string W_TMP = "XMM" ~ to_string(regno+8); + } + + if ((i & 3) == 0) + { + return ["movdqu "~W~",["~BUFFER_PTR~" + "~to_string(regno)~"*16]"]; + } + else if ((i & 3) == 1) + { + return ["pshufb "~W~","~X_SHUFFLECTL] ~ + swt3264(["movdqa "~WiV(i)~","~W], []); + } + else if ((i & 3) == 2) + { + return ["movdqa "~W_TMP~","~W, + "paddd "~W_TMP~","~X_CONSTANT, + ]; + } + else + { + return ["movdqa "~WiKiV(i)~","~W_TMP, + ]; + } + } + + /** + * Done on 4 consequtive W[i] values in a single XMM register + * W[i ] = (W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16]) rol 1 + * W[i+1] = (W[i-2] ^ W[i-7] ^ W[i-13] ^ W[i-15]) rol 1 + * W[i+2] = (W[i-1] ^ W[i-6] ^ W[i-12] ^ W[i-14]) rol 1 + * W[i+3] = ( 0 ^ W[i-5] ^ W[i-11] ^ W[i-13]) rol 1 + * + * This additional calculation unfortunately requires many additional operations + * W[i+3] ^= W[i] rol 1 + * + * Once we have 4 W[i] values in XMM we can also add four K values with one instruction + * W[i:i+3] += {K,K,K,K} + */ + private nothrow pure string[] precalc_16_31(int i) + { + int regno = regno(i); + + string W = "XMM" ~ to_string(regno); + string W_minus_4 = "XMM" ~ to_string((regno-1)&7); + string W_minus_8 = "XMM" ~ to_string((regno-2)&7); + string W_minus_12 = "XMM" ~ to_string((regno-3)&7); + string W_minus_16 = "XMM" ~ to_string((regno-4)&7); + version (_32Bit) + { + string W_TMP = "XMM" ~ to_string((regno+1)&7); + string W_TMP2 = "XMM" ~ to_string((regno+2)&7); + } + + if ((i & 3) == 0) + { + return ["movdqa "~W~","~W_minus_12, + "palignr "~W~","~W_minus_16~",8", // W[i] = W[i-14] + "pxor "~W~","~W_minus_16, // W[i] ^= W[i-16] + "pxor "~W~","~W_minus_8, // W[i] ^= W[i-8] + "movdqa "~W_TMP~","~W_minus_4, + ]; + } + else if ((i & 3) == 1) + { + return ["psrldq "~W_TMP~",4", // W[i-3] + "pxor "~W~","~W_TMP, // W[i] ^= W[i-3] + "movdqa "~W_TMP~","~W, + "psrld "~W~",31", + "pslld "~W_TMP~",1", + ]; + } + else if ((i & 3) == 2) + { + return ["por "~W~","~W_TMP, + "movdqa "~W_TMP~","~W, + "pslldq "~W_TMP~",12", + "movdqa "~W_TMP2~","~W_TMP, + "pslld "~W_TMP~",1", + ]; + } + else + { + return ["psrld "~W_TMP2~",31", + "por "~W_TMP~","~W_TMP2, + "pxor "~W~","~W_TMP, + "movdqa "~W_TMP~","~W ] ~ + swt3264(["movdqa "~WiV(i)~","~W, + "paddd "~W_TMP~","~constant(i) ], + ["paddd "~W_TMP~","~X_CONSTANT ]) ~ + ["movdqa "~WiKiV(i)~","~W_TMP]; + } + } + + /** Performs the main calculation as decribed above. */ + private nothrow pure string[] precalc_32_79(int i) + { + int regno = regno(i); + + string W = "XMM" ~ to_string(regno); + string W_minus_4 = "XMM" ~ to_string((regno-1)&7); + string W_minus_8 = "XMM" ~ to_string((regno-2)&7); + string W_minus_16 = "XMM" ~ to_string((regno-4)&7); + version (_32Bit) + { + string W_minus_28 = "[ESP + WI_PTR + "~ to_string((regno-7)&7)~"*16]"; + string W_minus_32 = "[ESP + WI_PTR + "~ to_string((regno-8)&7)~"*16]"; + string W_TMP = "XMM" ~ to_string((regno+1)&7); + string W_TMP2 = "XMM" ~ to_string((regno+2)&7); + } + version (_64Bit) + { + string W_minus_28 = "XMM" ~ to_string((regno-7)&7); + string W_minus_32 = "XMM" ~ to_string((regno-8)&7); + } + + if ((i & 3) == 0) + { + return swt3264(["movdqa "~W~","~W_minus_32], []) ~ + ["movdqa "~W_TMP~","~W_minus_4, + "pxor "~W~","~W_minus_28, // W is W_minus_32 before xor + "palignr "~W_TMP~","~W_minus_8~",8", + ]; + } + else if ((i & 3) == 1) + { + return ["pxor "~W~","~W_minus_16, + "pxor "~W~","~W_TMP, + "movdqa "~W_TMP~","~W, + ]; + } + else if ((i & 3) == 2) + { + return ["psrld "~W~",30", + "pslld "~W_TMP~",2", + "por "~W_TMP~","~W, + ]; + } + else + { + if (i < 76) + return ["movdqa "~W~","~W_TMP] ~ + swt3264(["movdqa "~WiV(i)~","~W, + "paddd "~W_TMP~","~constant(i)], + ["paddd "~W_TMP~","~X_CONSTANT]) ~ + ["movdqa "~WiKiV(i)~","~W_TMP]; + else + return swt3264(["paddd "~W_TMP~","~constant(i)], + ["paddd "~W_TMP~","~X_CONSTANT]) ~ + ["movdqa "~WiKiV(i)~","~W_TMP]; + } + } + + /** Choose right precalc method. */ + private nothrow pure string[] precalc(int i) + { + if (i >= 0 && i < 16) return precalc_00_15(i); + if (i >= 16 && i < 32) return precalc_16_31(i); + if (i >= 32 && i < 80) return precalc_32_79(i); + return []; + } + + /** + * Return code for round i and i+1. + * Performs the following rotation: + * in=>out: A=>D, B=>E, C=>A, D=>B, E=>C + */ + private nothrow pure string[] round(int i, string a, string b, string c, string d, string e) + { + return xsetup(PRECALC_AHEAD + i) ~ + weave(F(i, b, c, d) ~ // Returns result in T1; may destroy T2 + ["add "~e~","~WiKi(i), + "ror "~b~",2", + "mov "~T2~","~a, + "add "~d~","~WiKi(i+1), + "rol "~T2~",5", + "add "~e~","~T1 ], + precalc(PRECALC_AHEAD + i), 2) ~ + weave( + ["add "~T2~","~e, // T2 = (A <<< 5) + F(B, C, D) + Wi + Ki + E + "mov "~e~","~T2, + "rol "~T2~",5", + "add "~d~","~T2 ] ~ + F(i+1, a, b, c) ~ // Returns result in T1; may destroy T2 + ["add "~d~","~T1, + "ror "~a~",2"], + precalc(PRECALC_AHEAD + i+1), 2); + } + + // Offset into stack (see below) + version (_32Bit) + { + private enum { STATE_OFS = 4, WI_PLUS_KI_PTR = 8, WI_PTR = 72 }; + } + version (_64Bit) + { + private enum { WI_PLUS_KI_PTR = 0 }; + } + + /** The prologue sequence. */ + private nothrow pure string[] prologue() + { + version (_32Bit) + { + /* + * Parameters: + * EAX contains pointer to input buffer + * + * Stack layout as follows: + * +----------------+ + * | ptr to state | + * +----------------+ + * | return address | + * +----------------+ + * | EBP | + * +----------------+ + * | ESI | + * +----------------+ + * | EDI | + * +----------------+ + * | EBX | + * +----------------+ + * | Space for | + * | Wi | <- ESP+72 + * +----------------+ + * | Space for | + * | Wi+Ki | <- ESP+8 + * +----------------+ <- 16byte aligned + * | ptr to state | <- ESP+4 + * +----------------+ + * | old ESP | <- ESP + * +----------------+ + */ + static assert(BUFFER_PTR == "EAX"); + static assert(STATE_PTR == "EBX"); + return [// Save registers according to calling convention + "push EBP", + "push ESI", + "push EDI", + "push EBX", + // Load parameters + "mov EBX, [ESP + 5*4]", //pointer to state + // Align stack + "mov EBP, ESP", + "sub ESP, 4*16 + 8*16", + "and ESP, 0xffff_fff0", + "push EBX", + "push EBP", + ]; + } + version (_64Bit) + { + /* + * Parameters: + * RDX contains pointer to state + * RSI contains pointer to input buffer + * RDI contains pointer to constants + * + * Stack layout as follows: + * +----------------+ + * | return address | + * +----------------+ + * | RBP | + * +----------------+ + * | RBX | + * +----------------+ + * | Unused | + * +----------------+ + * | Space for | + * | Wi+Ki | <- RSP + * +----------------+ <- 16byte aligned + */ + return [// Save registers according to calling convention + "push RBP", + "push RBX", + // Save parameters + "mov "~STATE_PTR~", RDX", //pointer to state + "mov "~BUFFER_PTR~", RSI", //pointer to buffer + "mov "~CONSTANTS_PTR~", RDI", //pointer to constants to avoid absolute addressing + // Align stack + "sub RSP, 4*16+8", + ]; + } + } + + /** + * The epilogue sequence. Just pop the saved registers from stack and return to caller. + */ + private nothrow pure string[] epilogue() + { + version (_32Bit) + { + return ["pop ESP", + "pop EBX", + "pop EDI", + "pop ESI", + "pop EBP", + "ret 4", + ]; + } + version (_64Bit) + { + return ["add RSP,4*16+8", + "pop RBX", + "pop RBP", + "ret 0", + ]; + } + } + + // constants as extra argument for PIC, see Bugzilla 9378 + import std.meta : AliasSeq; + version (_64Bit) + alias ExtraArgs = AliasSeq!(typeof(&constants)); + else + alias ExtraArgs = AliasSeq!(); + + /** + * + */ + public void transformSSSE3(uint[5]* state, const(ubyte[64])* buffer, ExtraArgs) pure nothrow @nogc + { + mixin(wrap(["naked;"] ~ prologue())); + // Precalc first 4*16=64 bytes + mixin(wrap(xsetup(0))); + mixin(wrap(weave(precalc(0)~precalc(1)~precalc(2)~precalc(3), + precalc(4)~precalc(5)~precalc(6)~precalc(7)))); + mixin(wrap(weave(loadstate(STATE_PTR, A, B, C, D, E), + weave(precalc(8)~precalc(9)~precalc(10)~precalc(11), + precalc(12)~precalc(13)~precalc(14)~precalc(15))))); + // Round 1 + mixin(wrap(round( 0, A, B, C, D, E))); + mixin(wrap(round( 2, D, E, A, B, C))); + mixin(wrap(round( 4, B, C, D, E, A))); + mixin(wrap(round( 6, E, A, B, C, D))); + mixin(wrap(round( 8, C, D, E, A, B))); + mixin(wrap(round(10, A, B, C, D, E))); + mixin(wrap(round(12, D, E, A, B, C))); + mixin(wrap(round(14, B, C, D, E, A))); + mixin(wrap(round(16, E, A, B, C, D))); + mixin(wrap(round(18, C, D, E, A, B))); + // Round 2 + mixin(wrap(round(20, A, B, C, D, E))); + mixin(wrap(round(22, D, E, A, B, C))); + mixin(wrap(round(24, B, C, D, E, A))); + mixin(wrap(round(26, E, A, B, C, D))); + mixin(wrap(round(28, C, D, E, A, B))); + mixin(wrap(round(30, A, B, C, D, E))); + mixin(wrap(round(32, D, E, A, B, C))); + mixin(wrap(round(34, B, C, D, E, A))); + mixin(wrap(round(36, E, A, B, C, D))); + mixin(wrap(round(38, C, D, E, A, B))); + // Round 3 + mixin(wrap(round(40, A, B, C, D, E))); + mixin(wrap(round(42, D, E, A, B, C))); + mixin(wrap(round(44, B, C, D, E, A))); + mixin(wrap(round(46, E, A, B, C, D))); + mixin(wrap(round(48, C, D, E, A, B))); + mixin(wrap(round(50, A, B, C, D, E))); + mixin(wrap(round(52, D, E, A, B, C))); + mixin(wrap(round(54, B, C, D, E, A))); + mixin(wrap(round(56, E, A, B, C, D))); + mixin(wrap(round(58, C, D, E, A, B))); + // Round 4 + mixin(wrap(round(60, A, B, C, D, E))); + mixin(wrap(round(62, D, E, A, B, C))); + mixin(wrap(round(64, B, C, D, E, A))); + mixin(wrap(round(66, E, A, B, C, D))); + mixin(wrap(round(68, C, D, E, A, B))); + mixin(wrap(round(70, A, B, C, D, E))); + mixin(wrap(round(72, D, E, A, B, C))); + mixin(wrap(round(74, B, C, D, E, A))); + mixin(wrap(round(76, E, A, B, C, D))); + mixin(wrap(round(78, C, D, E, A, B))); + version (_32Bit) + { + // Load pointer to state + mixin(wrap(["mov "~STATE_PTR~",[ESP + STATE_OFS]"])); + } + mixin(wrap(savestate(STATE_PTR, A, B, C, D, E))); + mixin(wrap(epilogue())); + } +} diff --git a/libphobos/src/std/internal/math/biguintcore.d b/libphobos/src/std/internal/math/biguintcore.d new file mode 100644 index 0000000..f5cd769 --- /dev/null +++ b/libphobos/src/std/internal/math/biguintcore.d @@ -0,0 +1,2571 @@ +/** Fundamental operations for arbitrary-precision arithmetic + * + * These functions are for internal use only. + */ +/* Copyright Don Clugston 2008 - 2010. + * 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) + */ +/* References: + "Modern Computer Arithmetic" (MCA) is the primary reference for all + algorithms used in this library. + - R.P. Brent and P. Zimmermann, "Modern Computer Arithmetic", + Version 0.5.9, (Oct 2010). + - C. Burkinel and J. Ziegler, "Fast Recursive Division", MPI-I-98-1-022, + Max-Planck Institute fuer Informatik, (Oct 1998). + - G. Hanrot, M. Quercia, and P. Zimmermann, "The Middle Product Algorithm, I.", + INRIA 4664, (Dec 2002). + - M. Bodrato and A. Zanoni, "What about Toom-Cook Matrices Optimality?", + http://bodrato.it/papers (2006). + - A. Fog, "Optimizing subroutines in assembly language", + www.agner.org/optimize (2008). + - A. Fog, "The microarchitecture of Intel and AMD CPU's", + www.agner.org/optimize (2008). + - A. Fog, "Instruction tables: Lists of instruction latencies, throughputs + and micro-operation breakdowns for Intel and AMD CPU's.", www.agner.org/optimize (2008). + +Idioms: + Many functions in this module use + 'func(Tulong)(Tulong x) if (is(Tulong == ulong))' rather than 'func(ulong x)' + in order to disable implicit conversion. + +*/ +module std.internal.math.biguintcore; + +version (D_InlineAsm_X86) +{ + import std.internal.math.biguintx86; +} +else +{ + import std.internal.math.biguintnoasm; +} + +alias multibyteAdd = multibyteAddSub!('+'); +alias multibyteSub = multibyteAddSub!('-'); + + +import core.cpuid; +public import std.ascii : LetterCase; +import std.range.primitives; +import std.traits; + +shared static this() +{ + CACHELIMIT = core.cpuid.datacache[0].size*1024/2; +} + +private: +// Limits for when to switch between algorithms. +immutable size_t CACHELIMIT; // Half the size of the data cache. +enum size_t FASTDIVLIMIT = 100; // crossover to recursive division + + +// These constants are used by shift operations +static if (BigDigit.sizeof == int.sizeof) +{ + enum { LG2BIGDIGITBITS = 5, BIGDIGITSHIFTMASK = 31 }; + alias BIGHALFDIGIT = ushort; +} +else static if (BigDigit.sizeof == long.sizeof) +{ + alias BIGHALFDIGIT = uint; + enum { LG2BIGDIGITBITS = 6, BIGDIGITSHIFTMASK = 63 }; +} +else static assert(0, "Unsupported BigDigit size"); + +import std.exception : assumeUnique; +import std.traits : isIntegral; +enum BigDigitBits = BigDigit.sizeof*8; +template maxBigDigits(T) +if (isIntegral!T) +{ + enum maxBigDigits = (T.sizeof+BigDigit.sizeof-1)/BigDigit.sizeof; +} + +static immutable BigDigit[] ZERO = [0]; +static immutable BigDigit[] ONE = [1]; +static immutable BigDigit[] TWO = [2]; +static immutable BigDigit[] TEN = [10]; + + +public: + +/// BigUint performs memory management and wraps the low-level calls. +struct BigUint +{ +private: + pure invariant() + { + assert( data.length >= 1 && (data.length == 1 || data[$-1] != 0 )); + } + + immutable(BigDigit) [] data = ZERO; + + this(immutable(BigDigit) [] x) pure nothrow @nogc @safe + { + data = x; + } + package(std) // used from: std.bigint + this(T)(T x) pure nothrow @safe if (isIntegral!T) + { + opAssign(x); + } + + enum trustedAssumeUnique = function(BigDigit[] input) pure @trusted @nogc { + return assumeUnique(input); + }; +public: + // Length in uints + @property size_t uintLength() pure nothrow const @safe @nogc + { + static if (BigDigit.sizeof == uint.sizeof) + { + return data.length; + } + else static if (BigDigit.sizeof == ulong.sizeof) + { + return data.length * 2 - + ((data[$-1] & 0xFFFF_FFFF_0000_0000L) ? 1 : 0); + } + } + @property size_t ulongLength() pure nothrow const @safe @nogc + { + static if (BigDigit.sizeof == uint.sizeof) + { + return (data.length + 1) >> 1; + } + else static if (BigDigit.sizeof == ulong.sizeof) + { + return data.length; + } + } + + // The value at (cast(ulong[]) data)[n] + ulong peekUlong(int n) pure nothrow const @safe @nogc + { + static if (BigDigit.sizeof == int.sizeof) + { + if (data.length == n*2 + 1) return data[n*2]; + return data[n*2] + ((cast(ulong) data[n*2 + 1]) << 32 ); + } + else static if (BigDigit.sizeof == long.sizeof) + { + return data[n]; + } + } + uint peekUint(int n) pure nothrow const @safe @nogc + { + static if (BigDigit.sizeof == int.sizeof) + { + return data[n]; + } + else + { + immutable x = data[n >> 1]; + return (n & 1) ? cast(uint)(x >> 32) : cast(uint) x; + } + } +public: + /// + void opAssign(Tulong)(Tulong u) pure nothrow @safe if (is (Tulong == ulong)) + { + if (u == 0) data = ZERO; + else if (u == 1) data = ONE; + else if (u == 2) data = TWO; + else if (u == 10) data = TEN; + else + { + static if (BigDigit.sizeof == int.sizeof) + { + uint ulo = cast(uint)(u & 0xFFFF_FFFF); + uint uhi = cast(uint)(u >> 32); + if (uhi == 0) + { + data = [ulo]; + } + else + { + data = [ulo, uhi]; + } + } + else static if (BigDigit.sizeof == long.sizeof) + { + data = [u]; + } + } + } + void opAssign(Tdummy = void)(BigUint y) pure nothrow @nogc @safe + { + this.data = y.data; + } + + /// + int opCmp(Tdummy = void)(const BigUint y) pure nothrow @nogc const @safe + { + if (data.length != y.data.length) + return (data.length > y.data.length) ? 1 : -1; + size_t k = highestDifferentDigit(data, y.data); + if (data[k] == y.data[k]) + return 0; + return data[k] > y.data[k] ? 1 : -1; + } + + /// + int opCmp(Tulong)(Tulong y) pure nothrow @nogc const @safe if (is (Tulong == ulong)) + { + if (data.length > maxBigDigits!Tulong) + return 1; + + foreach_reverse (i; 0 .. maxBigDigits!Tulong) + { + BigDigit tmp = cast(BigDigit)(y>>(i*BigDigitBits)); + if (tmp == 0) + if (data.length >= i+1) + { + // Since ZERO is [0], so we cannot simply return 1 here, as + // data[i] would be 0 for i == 0 in that case. + return (data[i] > 0) ? 1 : 0; + } + else + continue; + else + if (i+1 > data.length) + return -1; + else if (tmp != data[i]) + return data[i] > tmp ? 1 : -1; + } + return 0; + } + + bool opEquals(Tdummy = void)(ref const BigUint y) pure nothrow @nogc const @safe + { + return y.data[] == data[]; + } + + bool opEquals(Tdummy = void)(ulong y) pure nothrow @nogc const @safe + { + if (data.length > 2) + return false; + uint ylo = cast(uint)(y & 0xFFFF_FFFF); + uint yhi = cast(uint)(y >> 32); + if (data.length == 2 && data[1]!=yhi) + return false; + if (data.length == 1 && yhi != 0) + return false; + return (data[0] == ylo); + } + + bool isZero() pure const nothrow @safe @nogc + { + return data.length == 1 && data[0] == 0; + } + + size_t numBytes() pure nothrow const @safe @nogc + { + return data.length * BigDigit.sizeof; + } + + // the extra bytes are added to the start of the string + char [] toDecimalString(int frontExtraBytes) const pure nothrow + { + immutable predictlength = 20+20*(data.length/2); // just over 19 + char [] buff = new char[frontExtraBytes + predictlength]; + ptrdiff_t sofar = biguintToDecimal(buff, data.dup); + return buff[sofar-frontExtraBytes..$]; + } + + /** Convert to a hex string, printing a minimum number of digits 'minPadding', + * allocating an additional 'frontExtraBytes' at the start of the string. + * Padding is done with padChar, which may be '0' or ' '. + * 'separator' is a digit separation character. If non-zero, it is inserted + * between every 8 digits. + * Separator characters do not contribute to the minPadding. + */ + char [] toHexString(int frontExtraBytes, char separator = 0, + int minPadding=0, char padChar = '0', + LetterCase letterCase = LetterCase.upper) const pure nothrow @safe + { + // Calculate number of extra padding bytes + size_t extraPad = (minPadding > data.length * 2 * BigDigit.sizeof) + ? minPadding - data.length * 2 * BigDigit.sizeof : 0; + + // Length not including separator bytes + size_t lenBytes = data.length * 2 * BigDigit.sizeof; + + // Calculate number of separator bytes + size_t mainSeparatorBytes = separator ? (lenBytes / 8) - 1 : 0; + immutable totalSeparatorBytes = separator ? ((extraPad + lenBytes + 7) / 8) - 1: 0; + + char [] buff = new char[lenBytes + extraPad + totalSeparatorBytes + frontExtraBytes]; + biguintToHex(buff[$ - lenBytes - mainSeparatorBytes .. $], data, separator, letterCase); + if (extraPad > 0) + { + if (separator) + { + size_t start = frontExtraBytes; // first index to pad + if (extraPad &7) + { + // Do 1 to 7 extra zeros. + buff[frontExtraBytes .. frontExtraBytes + (extraPad & 7)] = padChar; + buff[frontExtraBytes + (extraPad & 7)] = (padChar == ' ' ? ' ' : separator); + start += (extraPad & 7) + 1; + } + for (int i=0; i< (extraPad >> 3); ++i) + { + buff[start .. start + 8] = padChar; + buff[start + 8] = (padChar == ' ' ? ' ' : separator); + start += 9; + } + } + else + { + buff[frontExtraBytes .. frontExtraBytes + extraPad]=padChar; + } + } + int z = frontExtraBytes; + if (lenBytes > minPadding) + { + // Strip leading zeros. + ptrdiff_t maxStrip = lenBytes - minPadding; + while (z< buff.length-1 && (buff[z]=='0' || buff[z]==padChar) && maxStrip>0) + { + ++z; + --maxStrip; + } + } + if (padChar!='0') + { + // Convert leading zeros into padChars. + for (size_t k= z; k< buff.length-1 && (buff[k]=='0' || buff[k]==padChar); ++k) + { + if (buff[k]=='0') buff[k]=padChar; + } + } + return buff[z-frontExtraBytes..$]; + } + + /** + * Convert to an octal string. + */ + char[] toOctalString() const + { + auto predictLength = 1 + data.length*BigDigitBits / 3; + char[] buff = new char[predictLength]; + size_t firstNonZero = biguintToOctal(buff, data); + return buff[firstNonZero .. $]; + } + + // return false if invalid character found + bool fromHexString(Range)(Range s) if ( + isBidirectionalRange!Range && isSomeChar!(ElementType!Range)) + { + import std.range : walkLength; + + //Strip leading zeros + while (!s.empty && s.front == '0') + s.popFront; + + if (s.empty) + { + data = ZERO; + return true; + } + + immutable len = (s.save.walkLength + 15) / 4; + auto tmp = new BigDigit[len + 1]; + uint part, sofar, partcount; + + foreach_reverse (character; s) + { + if (character == '_') + continue; + + uint x; + if (character >= '0' && character <= '9') + { + x = character - '0'; + } + else if (character >= 'A' && character <= 'F') + { + x = character - 'A' + 10; + } + else if (character >= 'a' && character <= 'f') + { + x = character - 'a' + 10; + } + else + { + return false; + } + + part >>= 4; + part |= (x << (32 - 4)); + ++partcount; + + if (partcount == 8) + { + tmp[sofar] = part; + ++sofar; + partcount = 0; + part = 0; + } + } + if (part) + { + for ( ; partcount != 8; ++partcount) part >>= 4; + tmp[sofar] = part; + ++sofar; + } + if (sofar == 0) + data = ZERO; + else + data = trustedAssumeUnique(tmp[0 .. sofar]); + + return true; + } + + // return true if OK; false if erroneous characters found + bool fromDecimalString(Range)(Range s) if ( + isForwardRange!Range && isSomeChar!(ElementType!Range)) + { + import std.range : walkLength; + + while (!s.empty && s.front == '0') + { + s.popFront; + } + + if (s.empty) + { + data = ZERO; + return true; + } + + auto predict_length = (18 * 2 + 2 * s.save.walkLength) / 19; + auto tmp = new BigDigit[predict_length]; + + tmp.length = biguintFromDecimal(tmp, s); + + data = trustedAssumeUnique(tmp); + return true; + } + + //////////////////////// + // + // All of these member functions create a new BigUint. + + // return x >> y + BigUint opShr(Tulong)(Tulong y) pure nothrow const if (is (Tulong == ulong)) + { + assert(y>0); + uint bits = cast(uint) y & BIGDIGITSHIFTMASK; + if ((y >> LG2BIGDIGITBITS) >= data.length) return BigUint(ZERO); + uint words = cast(uint)(y >> LG2BIGDIGITBITS); + if (bits == 0) + { + return BigUint(data[words..$]); + } + else + { + uint [] result = new BigDigit[data.length - words]; + multibyteShr(result, data[words..$], bits); + + if (result.length > 1 && result[$-1] == 0) + return BigUint(trustedAssumeUnique(result[0 .. $-1])); + else + return BigUint(trustedAssumeUnique(result)); + } + } + + // return x << y + BigUint opShl(Tulong)(Tulong y) pure nothrow const if (is (Tulong == ulong)) + { + assert(y>0); + if (isZero()) return this; + uint bits = cast(uint) y & BIGDIGITSHIFTMASK; + assert((y >> LG2BIGDIGITBITS) < cast(ulong)(uint.max)); + uint words = cast(uint)(y >> LG2BIGDIGITBITS); + BigDigit [] result = new BigDigit[data.length + words+1]; + result[0 .. words] = 0; + if (bits == 0) + { + result[words .. words+data.length] = data[]; + return BigUint(trustedAssumeUnique(result[0 .. words+data.length])); + } + else + { + immutable c = multibyteShl(result[words .. words+data.length], data, bits); + if (c == 0) return BigUint(trustedAssumeUnique(result[0 .. words+data.length])); + result[$-1] = c; + return BigUint(trustedAssumeUnique(result)); + } + } + + // If wantSub is false, return x + y, leaving sign unchanged + // If wantSub is true, return abs(x - y), negating sign if x < y + static BigUint addOrSubInt(Tulong)(const BigUint x, Tulong y, + bool wantSub, ref bool sign) pure nothrow if (is(Tulong == ulong)) + { + BigUint r; + if (wantSub) + { // perform a subtraction + if (x.data.length > 2) + { + r.data = subInt(x.data, y); + } + else + { // could change sign! + ulong xx = x.data[0]; + if (x.data.length > 1) + xx += (cast(ulong) x.data[1]) << 32; + ulong d; + if (xx <= y) + { + d = y - xx; + sign = !sign; + } + else + { + d = xx - y; + } + if (d == 0) + { + r = 0UL; + sign = false; + return r; + } + if (d > uint.max) + { + r.data = [cast(uint)(d & 0xFFFF_FFFF), cast(uint)(d >> 32)]; + } + else + { + r.data = [cast(uint)(d & 0xFFFF_FFFF)]; + } + } + } + else + { + r.data = addInt(x.data, y); + } + return r; + } + + // If wantSub is false, return x + y, leaving sign unchanged. + // If wantSub is true, return abs(x - y), negating sign if x>> 32); + uint lo = cast(uint)(y & 0xFFFF_FFFF); + uint [] result = new BigDigit[x.data.length+1+(hi != 0)]; + result[x.data.length] = multibyteMul(result[0 .. x.data.length], x.data, lo, 0); + if (hi != 0) + { + result[x.data.length+1] = multibyteMulAdd!('+')(result[1 .. x.data.length+1], + x.data, hi, 0); + } + return BigUint(removeLeadingZeros(trustedAssumeUnique(result))); + } + + /* return x * y. + */ + static BigUint mul(BigUint x, BigUint y) pure nothrow + { + if (y == 0 || x == 0) + return BigUint(ZERO); + auto len = x.data.length + y.data.length; + BigDigit [] result = new BigDigit[len]; + if (y.data.length > x.data.length) + { + mulInternal(result, y.data, x.data); + } + else + { + if (x.data[]==y.data[]) squareInternal(result, x.data); + else mulInternal(result, x.data, y.data); + } + // the highest element could be zero, + // in which case we need to reduce the length + return BigUint(removeLeadingZeros(trustedAssumeUnique(result))); + } + + // return x / y + static BigUint divInt(T)(BigUint x, T y_) pure nothrow + if ( is(Unqual!T == uint) ) + { + uint y = y_; + if (y == 1) + return x; + uint [] result = new BigDigit[x.data.length]; + if ((y&(-y))==y) + { + assert(y != 0, "BigUint division by zero"); + // perfect power of 2 + uint b = 0; + for (;y != 1; y>>=1) + { + ++b; + } + multibyteShr(result, x.data, b); + } + else + { + result[] = x.data[]; + cast(void) multibyteDivAssign(result, y, 0); + } + return BigUint(removeLeadingZeros(trustedAssumeUnique(result))); + } + + static BigUint divInt(T)(BigUint x, T y) pure nothrow + if ( is(Unqual!T == ulong) ) + { + if (y <= uint.max) + return divInt!uint(x, cast(uint) y); + if (x.data.length < 2) + return BigUint(ZERO); + uint hi = cast(uint)(y >>> 32); + uint lo = cast(uint)(y & 0xFFFF_FFFF); + immutable uint[2] z = [lo, hi]; + BigDigit[] result = new BigDigit[x.data.length - z.length + 1]; + divModInternal(result, null, x.data, z[]); + return BigUint(removeLeadingZeros(trustedAssumeUnique(result))); + } + + // return x % y + static uint modInt(T)(BigUint x, T y_) pure if ( is(Unqual!T == uint) ) + { + import core.memory : GC; + uint y = y_; + assert(y != 0); + if ((y&(-y)) == y) + { // perfect power of 2 + return x.data[0] & (y-1); + } + else + { + // horribly inefficient - malloc, copy, & store are unnecessary. + uint [] wasteful = new BigDigit[x.data.length]; + wasteful[] = x.data[]; + immutable rem = multibyteDivAssign(wasteful, y, 0); + () @trusted { GC.free(wasteful.ptr); } (); + return rem; + } + } + + // return x / y + static BigUint div(BigUint x, BigUint y) pure nothrow + { + if (y.data.length > x.data.length) + return BigUint(ZERO); + if (y.data.length == 1) + return divInt(x, y.data[0]); + BigDigit [] result = new BigDigit[x.data.length - y.data.length + 1]; + divModInternal(result, null, x.data, y.data); + return BigUint(removeLeadingZeros(trustedAssumeUnique(result))); + } + + // return x % y + static BigUint mod(BigUint x, BigUint y) pure nothrow + { + if (y.data.length > x.data.length) return x; + if (y.data.length == 1) + { + return BigUint([modInt(x, y.data[0])]); + } + BigDigit [] result = new BigDigit[x.data.length - y.data.length + 1]; + BigDigit [] rem = new BigDigit[y.data.length]; + divModInternal(result, rem, x.data, y.data); + return BigUint(removeLeadingZeros(trustedAssumeUnique(rem))); + } + + // return x op y + static BigUint bitwiseOp(string op)(BigUint x, BigUint y, bool xSign, bool ySign, ref bool resultSign) + pure nothrow @safe if (op == "|" || op == "^" || op == "&") + { + auto d1 = includeSign(x.data, y.uintLength, xSign); + auto d2 = includeSign(y.data, x.uintLength, ySign); + + foreach (i; 0 .. d1.length) + { + mixin("d1[i] " ~ op ~ "= d2[i];"); + } + + mixin("resultSign = xSign " ~ op ~ " ySign;"); + + if (resultSign) + { + twosComplement(d1, d1); + } + + return BigUint(removeLeadingZeros(trustedAssumeUnique(d1))); + } + + /** + * Return a BigUint which is x raised to the power of y. + * Method: Powers of 2 are removed from x, then left-to-right binary + * exponentiation is used. + * Memory allocation is minimized: at most one temporary BigUint is used. + */ + static BigUint pow(BigUint x, ulong y) pure nothrow + { + // Deal with the degenerate cases first. + if (y == 0) return BigUint(ONE); + if (y == 1) return x; + if (x == 0 || x == 1) return x; + + BigUint result; + + // Simplify, step 1: Remove all powers of 2. + uint firstnonzero = firstNonZeroDigit(x.data); + // Now we know x = x[firstnonzero..$] * (2^^(firstnonzero*BigDigitBits)) + // where BigDigitBits = BigDigit.sizeof * 8 + + // See if x[firstnonzero..$] can now fit into a single digit. + bool singledigit = ((x.data.length - firstnonzero) == 1); + // If true, then x0 is that digit + // and the result will be (x0 ^^ y) * (2^^(firstnonzero*y*BigDigitBits)) + BigDigit x0 = x.data[firstnonzero]; + assert(x0 != 0); + // Length of the non-zero portion + size_t nonzerolength = x.data.length - firstnonzero; + ulong y0; + uint evenbits = 0; // number of even bits in the bottom of x + while (!(x0 & 1)) + { + x0 >>= 1; + ++evenbits; + } + + if (x.data.length- firstnonzero == 2) + { + // Check for a single digit straddling a digit boundary + const BigDigit x1 = x.data[firstnonzero+1]; + if ((x1 >> evenbits) == 0) + { + x0 |= (x1 << (BigDigit.sizeof * 8 - evenbits)); + singledigit = true; + } + } + // Now if (singledigit), x^^y = (x0 ^^ y) * 2^^(evenbits * y) * 2^^(firstnonzero*y*BigDigitBits)) + + uint evenshiftbits = 0; // Total powers of 2 to shift by, at the end + + // Simplify, step 2: For singledigits, see if we can trivially reduce y + + BigDigit finalMultiplier = 1UL; + + if (singledigit) + { + // x fits into a single digit. Raise it to the highest power we can + // that still fits into a single digit, then reduce the exponent accordingly. + // We're quite likely to have a residual multiply at the end. + // For example, 10^^100 = (((5^^13)^^7) * 5^^9) * 2^^100. + // and 5^^13 still fits into a uint. + evenshiftbits = cast(uint)( (evenbits * y) & BIGDIGITSHIFTMASK); + if (x0 == 1) + { // Perfect power of 2 + result = 1UL; + return result << (evenbits + firstnonzero * 8 * BigDigit.sizeof) * y; + } + immutable p = highestPowerBelowUintMax(x0); + if (y <= p) + { // Just do it with pow + result = cast(ulong) intpow(x0, y); + if (evenbits + firstnonzero == 0) + return result; + return result << (evenbits + firstnonzero * 8 * BigDigit.sizeof) * y; + } + y0 = y / p; + finalMultiplier = intpow(x0, y - y0*p); + x0 = intpow(x0, p); + // Result is x0 + nonzerolength = 1; + } + // Now if (singledigit), x^^y = finalMultiplier * (x0 ^^ y0) * 2^^(evenbits * y) * 2^^(firstnonzero*y*BigDigitBits)) + + // Perform a crude check for overflow and allocate result buffer. + // The length required is y * lg2(x) bits. + // which will always fit into y*x.length digits. But this is + // a gross overestimate if x is small (length 1 or 2) and the highest + // digit is nearly empty. + // A better estimate is: + // y * lg2(x[$-1]/BigDigit.max) + y * (x.length - 1) digits, + // and the first term is always between + // y * (bsr(x.data[$-1]) + 1) / BIGDIGITBITS and + // y * (bsr(x.data[$-1]) + 2) / BIGDIGITBITS + // For single digit payloads, we already have + // x^^y = finalMultiplier * (x0 ^^ y0) * 2^^(evenbits * y) * 2^^(firstnonzero*y*BigDigitBits)) + // and x0 is almost a full digit, so it's a tight estimate. + // Number of digits is therefore 1 + x0.length*y0 + (evenbits*y)/BIGDIGIT + firstnonzero*y + // Note that the divisions must be rounded up. + + // Estimated length in BigDigits + immutable estimatelength = singledigit + ? 1 + y0 + ((evenbits*y + BigDigit.sizeof * 8 - 1) / (BigDigit.sizeof *8)) + firstnonzero*y + : x.data.length * y; + // Imprecise check for overflow. Makes the extreme cases easier to debug + // (less extreme overflow will result in an out of memory error). + if (estimatelength > uint.max/(4*BigDigit.sizeof)) + assert(0, "Overflow in BigInt.pow"); + + // The result buffer includes space for all the trailing zeros + BigDigit [] resultBuffer = new BigDigit[cast(size_t) estimatelength]; + + // Do all the powers of 2! + size_t result_start = cast(size_t)( firstnonzero * y + + (singledigit ? ((evenbits * y) >> LG2BIGDIGITBITS) : 0)); + + resultBuffer[0 .. result_start] = 0; + BigDigit [] t1 = resultBuffer[result_start..$]; + BigDigit [] r1; + + if (singledigit) + { + r1 = t1[0 .. 1]; + r1[0] = x0; + y = y0; + } + else + { + // It's not worth right shifting by evenbits unless we also shrink the length after each + // multiply or squaring operation. That might still be worthwhile for large y. + r1 = t1[0 .. x.data.length - firstnonzero]; + r1[0..$] = x.data[firstnonzero..$]; + } + + if (y>1) + { // Set r1 = r1 ^^ y. + // The secondary buffer only needs space for the multiplication results + BigDigit [] t2 = new BigDigit[resultBuffer.length - result_start]; + BigDigit [] r2; + + int shifts = 63; // num bits in a long + while (!(y & 0x8000_0000_0000_0000L)) + { + y <<= 1; + --shifts; + } + y <<=1; + + while (y != 0) + { + // For each bit of y: Set r1 = r1 * r1 + // If the bit is 1, set r1 = r1 * x + // Eg, if y is 0b101, result = ((x^^2)^^2)*x == x^^5. + // Optimization opportunity: if more than 2 bits in y are set, + // it's usually possible to reduce the number of multiplies + // by caching odd powers of x. eg for y = 54, + // (0b110110), set u = x^^3, and result is ((u^^8)*u)^^2 + r2 = t2[0 .. r1.length*2]; + squareInternal(r2, r1); + if (y & 0x8000_0000_0000_0000L) + { + r1 = t1[0 .. r2.length + nonzerolength]; + if (singledigit) + { + r1[$-1] = multibyteMul(r1[0 .. $-1], r2, x0, 0); + } + else + { + mulInternal(r1, r2, x.data[firstnonzero..$]); + } + } + else + { + r1 = t1[0 .. r2.length]; + r1[] = r2[]; + } + y <<=1; + shifts--; + } + while (shifts>0) + { + r2 = t2[0 .. r1.length * 2]; + squareInternal(r2, r1); + r1 = t1[0 .. r2.length]; + r1[] = r2[]; + --shifts; + } + } + + if (finalMultiplier != 1) + { + const BigDigit carry = multibyteMul(r1, r1, finalMultiplier, 0); + if (carry) + { + r1 = t1[0 .. r1.length + 1]; + r1[$-1] = carry; + } + } + if (evenshiftbits) + { + const BigDigit carry = multibyteShl(r1, r1, evenshiftbits); + if (carry != 0) + { + r1 = t1[0 .. r1.length + 1]; + r1[$ - 1] = carry; + } + } + while (r1[$ - 1]==0) + { + r1=r1[0 .. $ - 1]; + } + return BigUint(trustedAssumeUnique(resultBuffer[0 .. result_start + r1.length])); + } + + // Implement toHash so that BigUint works properly as an AA key. + size_t toHash() const @trusted nothrow + { + return typeid(data).getHash(&data); + } + +} // end BigUint + +@safe pure nothrow unittest +{ + // ulong comparison test + BigUint a = [1]; + assert(a == 1); + assert(a < 0x8000_0000_0000_0000UL); // bug 9548 + + // bug 12234 + BigUint z = [0]; + assert(z == 0UL); + assert(!(z > 0UL)); + assert(!(z < 0UL)); +} + +// Remove leading zeros from x, to restore the BigUint invariant +inout(BigDigit) [] removeLeadingZeros(inout(BigDigit) [] x) pure nothrow @safe +{ + size_t k = x.length; + while (k>1 && x[k - 1]==0) --k; + return x[0 .. k]; +} + +pure @system unittest +{ + BigUint r = BigUint([5]); + BigUint t = BigUint([7]); + BigUint s = BigUint.mod(r, t); + assert(s == 5); +} + + +@safe pure unittest +{ + BigUint r; + r = 5UL; + assert(r.peekUlong(0) == 5UL); + assert(r.peekUint(0) == 5U); + r = 0x1234_5678_9ABC_DEF0UL; + assert(r.peekUlong(0) == 0x1234_5678_9ABC_DEF0UL); + assert(r.peekUint(0) == 0x9ABC_DEF0U); +} + + +// Pow tests +pure @system unittest +{ + BigUint r, s; + r.fromHexString("80000000_00000001"); + s = BigUint.pow(r, 5); + r.fromHexString("08000000_00000000_50000000_00000001_40000000_00000002_80000000" + ~ "_00000002_80000000_00000001"); + assert(s == r); + s = 10UL; + s = BigUint.pow(s, 39); + r.fromDecimalString("1000000000000000000000000000000000000000"); + assert(s == r); + r.fromHexString("1_E1178E81_00000000"); + s = BigUint.pow(r, 15); // Regression test: this used to overflow array bounds + + r.fromDecimalString("000_000_00"); + assert(r == 0); + r.fromDecimalString("0007"); + assert(r == 7); + r.fromDecimalString("0"); + assert(r == 0); +} + +// Radix conversion tests +@safe pure unittest +{ + BigUint r; + r.fromHexString("1_E1178E81_00000000"); + assert(r.toHexString(0, '_', 0) == "1_E1178E81_00000000"); + assert(r.toHexString(0, '_', 20) == "0001_E1178E81_00000000"); + assert(r.toHexString(0, '_', 16+8) == "00000001_E1178E81_00000000"); + assert(r.toHexString(0, '_', 16+9) == "0_00000001_E1178E81_00000000"); + assert(r.toHexString(0, '_', 16+8+8) == "00000000_00000001_E1178E81_00000000"); + assert(r.toHexString(0, '_', 16+8+8+1) == "0_00000000_00000001_E1178E81_00000000"); + assert(r.toHexString(0, '_', 16+8+8+1, ' ') == " 1_E1178E81_00000000"); + assert(r.toHexString(0, 0, 16+8+8+1) == "00000000000000001E1178E8100000000"); + r = 0UL; + assert(r.toHexString(0, '_', 0) == "0"); + assert(r.toHexString(0, '_', 7) == "0000000"); + assert(r.toHexString(0, '_', 7, ' ') == " 0"); + assert(r.toHexString(0, '#', 9) == "0#00000000"); + assert(r.toHexString(0, 0, 9) == "000000000"); +} + +// +@safe pure unittest +{ + BigUint r; + r.fromHexString("1_E1178E81_00000000"); + assert(r.toHexString(0, '_', 0, '0', LetterCase.lower) == "1_e1178e81_00000000"); + assert(r.toHexString(0, '_', 20, '0', LetterCase.lower) == "0001_e1178e81_00000000"); + assert(r.toHexString(0, '_', 16+8, '0', LetterCase.lower) == "00000001_e1178e81_00000000"); + assert(r.toHexString(0, '_', 16+9, '0', LetterCase.lower) == "0_00000001_e1178e81_00000000"); + assert(r.toHexString(0, '_', 16+8+8, '0', LetterCase.lower) == "00000000_00000001_e1178e81_00000000"); + assert(r.toHexString(0, '_', 16+8+8+1, '0', LetterCase.lower) == "0_00000000_00000001_e1178e81_00000000"); + assert(r.toHexString(0, '_', 16+8+8+1, ' ', LetterCase.lower) == " 1_e1178e81_00000000"); + assert(r.toHexString(0, 0, 16+8+8+1, '0', LetterCase.lower) == "00000000000000001e1178e8100000000"); + r = 0UL; + assert(r.toHexString(0, '_', 0, '0', LetterCase.lower) == "0"); + assert(r.toHexString(0, '_', 7, '0', LetterCase.lower) == "0000000"); + assert(r.toHexString(0, '_', 7, ' ', LetterCase.lower) == " 0"); + assert(r.toHexString(0, '#', 9, '0', LetterCase.lower) == "0#00000000"); + assert(r.toHexString(0, 'Z', 9, '0', LetterCase.lower) == "0Z00000000"); + assert(r.toHexString(0, 0, 9, '0', LetterCase.lower) == "000000000"); +} + + +private: +void twosComplement(const(BigDigit) [] x, BigDigit[] result) +pure nothrow @safe +{ + foreach (i; 0 .. x.length) + { + result[i] = ~x[i]; + } + result[x.length..$] = BigDigit.max; + + foreach (i; 0 .. result.length) + { + if (result[i] == BigDigit.max) + { + result[i] = 0; + } + else + { + result[i] += 1; + break; + } + } +} + +// Encode BigInt as BigDigit array (sign and 2's complement) +BigDigit[] includeSign(const(BigDigit) [] x, size_t minSize, bool sign) +pure nothrow @safe +{ + size_t length = (x.length > minSize) ? x.length : minSize; + BigDigit [] result = new BigDigit[length]; + if (sign) + { + twosComplement(x, result); + } + else + { + result[0 .. x.length] = x; + } + return result; +} + +// works for any type +T intpow(T)(T x, ulong n) pure nothrow @safe +{ + T p; + + switch (n) + { + case 0: + p = 1; + break; + + case 1: + p = x; + break; + + case 2: + p = x * x; + break; + + default: + p = 1; + while (1) + { + if (n & 1) + p *= x; + n >>= 1; + if (!n) + break; + x *= x; + } + break; + } + return p; +} + + +// returns the maximum power of x that will fit in a uint. +int highestPowerBelowUintMax(uint x) pure nothrow @safe +{ + assert(x>1); + static immutable ubyte [22] maxpwr = [ 31, 20, 15, 13, 12, 11, 10, 10, 9, 9, + 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7]; + if (x<24) return maxpwr[x-2]; + if (x<41) return 6; + if (x<85) return 5; + if (x<256) return 4; + if (x<1626) return 3; + if (x<65_536) return 2; + return 1; +} + +// returns the maximum power of x that will fit in a ulong. +int highestPowerBelowUlongMax(uint x) pure nothrow @safe +{ + assert(x>1); + static immutable ubyte [39] maxpwr = [ 63, 40, 31, 27, 24, 22, 21, 20, 19, 18, + 17, 17, 16, 16, 15, 15, 15, 15, 14, 14, + 14, 14, 13, 13, 13, 13, 13, 13, 13, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12]; + if (x<41) return maxpwr[x-2]; + if (x<57) return 11; + if (x<85) return 10; + if (x<139) return 9; + if (x<256) return 8; + if (x<566) return 7; + if (x<1626) return 6; + if (x<7132) return 5; + if (x<65_536) return 4; + if (x<2_642_246) return 3; + return 2; +} + +version (unittest) +{ + +int slowHighestPowerBelowUintMax(uint x) pure nothrow @safe +{ + int pwr = 1; + for (ulong q = x;x*q < cast(ulong) uint.max; ) + { + q*=x; ++pwr; + } + return pwr; +} + +@safe pure unittest +{ + assert(highestPowerBelowUintMax(10)==9); + for (int k=82; k<88; ++k) + { + assert(highestPowerBelowUintMax(k)== slowHighestPowerBelowUintMax(k)); + } +} + +} + + +/* General unsigned subtraction routine for bigints. + * Sets result = x - y. If the result is negative, negative will be true. + */ +BigDigit [] sub(const BigDigit [] x, const BigDigit [] y, bool *negative) +pure nothrow +{ + if (x.length == y.length) + { + // There's a possibility of cancellation, if x and y are almost equal. + ptrdiff_t last = highestDifferentDigit(x, y); + BigDigit [] result = new BigDigit[last+1]; + if (x[last] < y[last]) + { // we know result is negative + multibyteSub(result[0 .. last+1], y[0 .. last+1], x[0 .. last+1], 0); + *negative = true; + } + else + { // positive or zero result + multibyteSub(result[0 .. last+1], x[0 .. last+1], y[0 .. last+1], 0); + *negative = false; + } + while (result.length > 1 && result[$-1] == 0) + { + result = result[0..$-1]; + } +// if (result.length >1 && result[$-1]==0) return result[0..$-1]; + return result; + } + // Lengths are different + const(BigDigit) [] large, small; + if (x.length < y.length) + { + *negative = true; + large = y; small = x; + } + else + { + *negative = false; + large = x; small = y; + } + // result.length will be equal to larger length, or could decrease by 1. + + + BigDigit [] result = new BigDigit[large.length]; + BigDigit carry = multibyteSub(result[0 .. small.length], large[0 .. small.length], small, 0); + result[small.length..$] = large[small.length..$]; + if (carry) + { + multibyteIncrementAssign!('-')(result[small.length..$], carry); + } + while (result.length > 1 && result[$-1] == 0) + { + result = result[0..$-1]; + } + return result; +} + + +// return a + b +BigDigit [] add(const BigDigit [] a, const BigDigit [] b) pure nothrow +{ + const(BigDigit) [] x, y; + if (a.length < b.length) + { + x = b; y = a; + } + else + { + x = a; y = b; + } + // now we know x.length > y.length + // create result. add 1 in case it overflows + BigDigit [] result = new BigDigit[x.length + 1]; + + BigDigit carry = multibyteAdd(result[0 .. y.length], x[0 .. y.length], y, 0); + if (x.length != y.length) + { + result[y.length..$-1]= x[y.length..$]; + carry = multibyteIncrementAssign!('+')(result[y.length..$-1], carry); + } + if (carry) + { + result[$-1] = carry; + return result; + } + else + return result[0..$-1]; +} + +/** return x + y + */ +BigDigit [] addInt(const BigDigit[] x, ulong y) pure nothrow +{ + uint hi = cast(uint)(y >>> 32); + uint lo = cast(uint)(y& 0xFFFF_FFFF); + auto len = x.length; + if (x.length < 2 && hi != 0) ++len; + BigDigit [] result = new BigDigit[len+1]; + result[0 .. x.length] = x[]; + if (x.length < 2 && hi != 0) + { + result[1]=hi; + hi=0; + } + uint carry = multibyteIncrementAssign!('+')(result[0..$-1], lo); + if (hi != 0) carry += multibyteIncrementAssign!('+')(result[1..$-1], hi); + if (carry) + { + result[$-1] = carry; + return result; + } + else + return result[0..$-1]; +} + +/** Return x - y. + * x must be greater than y. + */ +BigDigit [] subInt(const BigDigit[] x, ulong y) pure nothrow +{ + uint hi = cast(uint)(y >>> 32); + uint lo = cast(uint)(y & 0xFFFF_FFFF); + BigDigit [] result = new BigDigit[x.length]; + result[] = x[]; + multibyteIncrementAssign!('-')(result[], lo); + if (hi) + multibyteIncrementAssign!('-')(result[1..$], hi); + if (result[$-1] == 0) + return result[0..$-1]; + else + return result; +} + +/** General unsigned multiply routine for bigints. + * Sets result = x * y. + * + * The length of y must not be larger than the length of x. + * Different algorithms are used, depending on the lengths of x and y. + * TODO: "Modern Computer Arithmetic" suggests the OddEvenKaratsuba algorithm for the + * unbalanced case. (But I doubt it would be faster in practice). + * + */ +void mulInternal(BigDigit[] result, const(BigDigit)[] x, const(BigDigit)[] y) + pure nothrow +{ + import core.memory : GC; + assert( result.length == x.length + y.length ); + assert( y.length > 0 ); + assert( x.length >= y.length); + if (y.length <= KARATSUBALIMIT) + { + // Small multiplier, we'll just use the asm classic multiply. + if (y.length == 1) + { // Trivial case, no cache effects to worry about + result[x.length] = multibyteMul(result[0 .. x.length], x, y[0], 0); + return; + } + + if (x.length + y.length < CACHELIMIT) + return mulSimple(result, x, y); + + // If x is so big that it won't fit into the cache, we divide it into chunks + // Every chunk must be greater than y.length. + // We make the first chunk shorter, if necessary, to ensure this. + + auto chunksize = CACHELIMIT / y.length; + immutable residual = x.length % chunksize; + if (residual < y.length) + { + chunksize -= y.length; + } + + // Use schoolbook multiply. + mulSimple(result[0 .. chunksize + y.length], x[0 .. chunksize], y); + auto done = chunksize; + + while (done < x.length) + { + // result[done .. done+ylength] already has a value. + chunksize = (done + (CACHELIMIT / y.length) < x.length) ? (CACHELIMIT / y.length) : x.length - done; + BigDigit [KARATSUBALIMIT] partial; + partial[0 .. y.length] = result[done .. done+y.length]; + mulSimple(result[done .. done+chunksize+y.length], x[done .. done+chunksize], y); + addAssignSimple(result[done .. done+chunksize + y.length], partial[0 .. y.length]); + done += chunksize; + } + return; + } + + immutable half = (x.length >> 1) + (x.length & 1); + if (2*y.length*y.length <= x.length*x.length) + { + // UNBALANCED MULTIPLY + // Use school multiply to cut into quasi-squares of Karatsuba-size + // or larger. The ratio of the two sides of the 'square' must be + // between 1.414:1 and 1:1. Use Karatsuba on each chunk. + // + // For maximum performance, we want the ratio to be as close to + // 1:1 as possible. To achieve this, we can either pad x or y. + // The best choice depends on the modulus x%y. + auto numchunks = x.length / y.length; + auto chunksize = y.length; + auto extra = x.length % y.length; + auto maxchunk = chunksize + extra; + bool paddingY; // true = we're padding Y, false = we're padding X. + if (extra * extra * 2 < y.length*y.length) + { + // The leftover bit is small enough that it should be incorporated + // in the existing chunks. + // Make all the chunks a tiny bit bigger + // (We're padding y with zeros) + chunksize += extra / numchunks; + extra = x.length - chunksize*numchunks; + // there will probably be a few left over. + // Every chunk will either have size chunksize, or chunksize+1. + maxchunk = chunksize + 1; + paddingY = true; + assert(chunksize + extra + chunksize *(numchunks-1) == x.length ); + } + else + { + // the extra bit is large enough that it's worth making a new chunk. + // (This means we're padding x with zeros, when doing the first one). + maxchunk = chunksize; + ++numchunks; + paddingY = false; + assert(extra + chunksize *(numchunks-1) == x.length ); + } + // We make the buffer a bit bigger so we have space for the partial sums. + BigDigit [] scratchbuff = new BigDigit[karatsubaRequiredBuffSize(maxchunk) + y.length]; + BigDigit [] partial = scratchbuff[$ - y.length .. $]; + size_t done; // how much of X have we done so far? + if (paddingY) + { + // If the first chunk is bigger, do it first. We're padding y. + mulKaratsuba(result[0 .. y.length + chunksize + (extra > 0 ? 1 : 0 )], + x[0 .. chunksize + (extra>0?1:0)], y, scratchbuff); + done = chunksize + (extra > 0 ? 1 : 0); + if (extra) --extra; + } + else + { // We're padding X. Begin with the extra bit. + mulKaratsuba(result[0 .. y.length + extra], y, x[0 .. extra], scratchbuff); + done = extra; + extra = 0; + } + immutable basechunksize = chunksize; + while (done < x.length) + { + chunksize = basechunksize + (extra > 0 ? 1 : 0); + if (extra) --extra; + partial[] = result[done .. done+y.length]; + mulKaratsuba(result[done .. done + y.length + chunksize], + x[done .. done+chunksize], y, scratchbuff); + addAssignSimple(result[done .. done + y.length + chunksize], partial); + done += chunksize; + } + () @trusted { GC.free(scratchbuff.ptr); } (); + } + else + { + // Balanced. Use Karatsuba directly. + BigDigit [] scratchbuff = new BigDigit[karatsubaRequiredBuffSize(x.length)]; + mulKaratsuba(result, x, y, scratchbuff); + () @trusted { GC.free(scratchbuff.ptr); } (); + } +} + +/** General unsigned squaring routine for BigInts. + * Sets result = x*x. + * NOTE: If the highest half-digit of x is zero, the highest digit of result will + * also be zero. + */ +void squareInternal(BigDigit[] result, const BigDigit[] x) pure nothrow +{ + import core.memory : GC; + // Squaring is potentially half a multiply, plus add the squares of + // the diagonal elements. + assert(result.length == 2*x.length); + if (x.length <= KARATSUBASQUARELIMIT) + { + if (x.length == 1) + { + result[1] = multibyteMul(result[0 .. 1], x, x[0], 0); + return; + } + return squareSimple(result, x); + } + // The nice thing about squaring is that it always stays balanced + BigDigit [] scratchbuff = new BigDigit[karatsubaRequiredBuffSize(x.length)]; + squareKaratsuba(result, x, scratchbuff); + () @trusted { GC.free(scratchbuff.ptr); } (); +} + + +import core.bitop : bsr; + +/// if remainder is null, only calculate quotient. +void divModInternal(BigDigit [] quotient, BigDigit[] remainder, const BigDigit [] u, + const BigDigit [] v) pure nothrow +{ + import core.memory : GC; + assert(quotient.length == u.length - v.length + 1); + assert(remainder == null || remainder.length == v.length); + assert(v.length > 1); + assert(u.length >= v.length); + + // Normalize by shifting v left just enough so that + // its high-order bit is on, and shift u left the + // same amount. The highest bit of u will never be set. + + BigDigit [] vn = new BigDigit[v.length]; + BigDigit [] un = new BigDigit[u.length + 1]; + // How much to left shift v, so that its MSB is set. + uint s = BIGDIGITSHIFTMASK - bsr(v[$-1]); + if (s != 0) + { + multibyteShl(vn, v, s); + un[$-1] = multibyteShl(un[0..$-1], u, s); + } + else + { + vn[] = v[]; + un[0..$-1] = u[]; + un[$-1] = 0; + } + if (quotient.length= 0; --i) + { + toHexZeroPadded(buff[x .. x+8], data[i], letterCase); + x+=8; + if (separator) + { + if (i>0) buff[x] = separator; + ++x; + } + } + return buff; +} + +/** + * Convert a big uint into an octal string. + * + * Params: + * buff = The destination buffer for the octal string. Must be large enough to + * store the result, including leading zeroes, which is + * ceil(data.length * BigDigitBits / 3) characters. + * The buffer is filled from back to front, starting from `buff[$-1]`. + * data = The biguint to be converted. + * + * Returns: The index of the leading non-zero digit in `buff`. Will be + * `buff.length - 1` if the entire big uint is zero. + */ +size_t biguintToOctal(char[] buff, const(BigDigit)[] data) + pure nothrow @safe @nogc +{ + ubyte carry = 0; + int shift = 0; + size_t penPos = buff.length - 1; + size_t lastNonZero = buff.length - 1; + + pragma(inline) void output(uint digit) @nogc nothrow + { + if (digit != 0) + lastNonZero = penPos; + buff[penPos--] = cast(char)('0' + digit); + } + + foreach (bigdigit; data) + { + if (shift < 0) + { + // Some bits were carried over from previous word. + assert(shift > -3); + output(((bigdigit << -shift) | carry) & 0b111); + shift += 3; + assert(shift > 0); + } + + while (shift <= BigDigitBits - 3) + { + output((bigdigit >>> shift) & 0b111); + shift += 3; + } + + if (shift < BigDigitBits) + { + // Some bits need to be carried forward. + carry = (bigdigit >>> shift) & 0b11; + } + shift -= BigDigitBits; + assert(shift >= -2 && shift <= 0); + } + + if (shift < 0) + { + // Last word had bits that haven't been output yet. + assert(shift > -3); + output(carry); + } + + return lastNonZero; +} + +/** Convert a big uint into a decimal string. + * + * Params: + * data The biguint to be converted. Will be destroyed. + * buff The destination buffer for the decimal string. Must be + * large enough to store the result, including leading zeros. + * Will be filled backwards, starting from buff[$-1]. + * + * buff.length must be >= (data.length*32)/log2(10) = 9.63296 * data.length. + * Returns: + * the lowest index of buff which was used. + */ +size_t biguintToDecimal(char [] buff, BigDigit [] data) pure nothrow +{ + ptrdiff_t sofar = buff.length; + // Might be better to divide by (10^38/2^32) since that gives 38 digits for + // the price of 3 divisions and a shr; this version only gives 27 digits + // for 3 divisions. + while (data.length>1) + { + uint rem = multibyteDivAssign(data, 10_0000_0000, 0); + itoaZeroPadded(buff[sofar-9 .. sofar], rem); + sofar -= 9; + if (data[$-1] == 0 && data.length > 1) + { + data.length = data.length - 1; + } + } + itoaZeroPadded(buff[sofar-10 .. sofar], data[0]); + sofar -= 10; + // and strip off the leading zeros + while (sofar != buff.length-1 && buff[sofar] == '0') + sofar++; + return sofar; +} + +/** Convert a decimal string into a big uint. + * + * Params: + * data The biguint to be receive the result. Must be large enough to + * store the result. + * s The decimal string. May contain _ or 0 .. 9 + * + * The required length for the destination buffer is slightly less than + * 1 + s.length/log2(10) = 1 + s.length/3.3219. + * + * Returns: + * the highest index of data which was used. + */ +int biguintFromDecimal(Range)(BigDigit[] data, Range s) +if ( + isInputRange!Range && + isSomeChar!(ElementType!Range) && + !isInfinite!Range) +in +{ + static if (hasLength!Range) + assert((data.length >= 2) || (data.length == 1 && s.length == 1)); +} +body +{ + import std.conv : ConvException; + + // Convert to base 1e19 = 10_000_000_000_000_000_000. + // (this is the largest power of 10 that will fit into a long). + // The length will be less than 1 + s.length/log2(10) = 1 + s.length/3.3219. + // 485 bits will only just fit into 146 decimal digits. + // As we convert the string, we record the number of digits we've seen in base 19: + // hi is the number of digits/19, lo is the extra digits (0 to 18). + // TODO: This is inefficient for very large strings (it is O(n^^2)). + // We should take advantage of fast multiplication once the numbers exceed + // Karatsuba size. + uint lo = 0; // number of powers of digits, 0 .. 18 + uint x = 0; + ulong y = 0; + uint hi = 0; // number of base 1e19 digits + data[0] = 0; // initially number is 0. + if (data.length > 1) + data[1] = 0; + + foreach (character; s) + { + if (character == '_') + continue; + + if (character < '0' || character > '9') + throw new ConvException("invalid digit"); + x *= 10; + x += character - '0'; + ++lo; + if (lo == 9) + { + y = x; + x = 0; + } + if (lo == 18) + { + y *= 10_0000_0000; + y += x; + x = 0; + } + if (lo == 19) + { + y *= 10; + y += x; + x = 0; + // Multiply existing number by 10^19, then add y1. + if (hi>0) + { + data[hi] = multibyteMul(data[0 .. hi], data[0 .. hi], 1_220_703_125*2u, 0); // 5^13*2 = 0x9184_E72A + ++hi; + data[hi] = multibyteMul(data[0 .. hi], data[0 .. hi], 15_625*262_144u, 0); // 5^6*2^18 = 0xF424_0000 + ++hi; + } + else + hi = 2; + uint c = multibyteIncrementAssign!('+')(data[0 .. hi], cast(uint)(y&0xFFFF_FFFF)); + c += multibyteIncrementAssign!('+')(data[1 .. hi], cast(uint)(y >> 32)); + if (c != 0) + { + data[hi]=c; + ++hi; + } + y = 0; + lo = 0; + } + } + // Now set y = all remaining digits. + if (lo >= 18) + { + } + else if (lo >= 9) + { + for (int k=9; k>> 32); + hi=2; + } + } + else + { + while (lo>0) + { + immutable c = multibyteMul(data[0 .. hi], data[0 .. hi], 10, 0); + if (c != 0) + { + data[hi]=c; + ++hi; + } + --lo; + } + uint c = multibyteIncrementAssign!('+')(data[0 .. hi], cast(uint)(y&0xFFFF_FFFF)); + if (y > 0xFFFF_FFFFL) + { + c += multibyteIncrementAssign!('+')(data[1 .. hi], cast(uint)(y >> 32)); + } + if (c != 0) + { + data[hi]=c; + ++hi; + } + } + } + while (hi>1 && data[hi-1]==0) + --hi; + return hi; +} + + +private: +// ------------------------ +// These in-place functions are only for internal use; they are incompatible +// with COW. + +// Classic 'schoolbook' multiplication. +void mulSimple(BigDigit[] result, const(BigDigit) [] left, + const(BigDigit)[] right) pure nothrow +in +{ + assert(result.length == left.length + right.length); + assert(right.length>1); +} +body +{ + result[left.length] = multibyteMul(result[0 .. left.length], left, right[0], 0); + multibyteMultiplyAccumulate(result[1..$], left, right[1..$]); +} + +// Classic 'schoolbook' squaring +void squareSimple(BigDigit[] result, const(BigDigit) [] x) pure nothrow +in +{ + assert(result.length == 2*x.length); + assert(x.length>1); +} +body +{ + multibyteSquare(result, x); +} + + +// add two uints of possibly different lengths. Result must be as long +// as the larger length. +// Returns carry (0 or 1). +uint addSimple(BigDigit[] result, const BigDigit [] left, const BigDigit [] right) +pure nothrow +in +{ + assert(result.length == left.length); + assert(left.length >= right.length); + assert(right.length>0); +} +body +{ + uint carry = multibyteAdd(result[0 .. right.length], + left[0 .. right.length], right, 0); + if (right.length < left.length) + { + result[right.length .. left.length] = left[right.length .. $]; + carry = multibyteIncrementAssign!('+')(result[right.length..$], carry); + } + return carry; +} + +// result = left - right +// returns carry (0 or 1) +BigDigit subSimple(BigDigit [] result,const(BigDigit) [] left, + const(BigDigit) [] right) pure nothrow +in +{ + assert(result.length == left.length); + assert(left.length >= right.length); + assert(right.length>0); +} +body +{ + BigDigit carry = multibyteSub(result[0 .. right.length], + left[0 .. right.length], right, 0); + if (right.length < left.length) + { + result[right.length .. left.length] = left[right.length .. $]; + carry = multibyteIncrementAssign!('-')(result[right.length..$], carry); + } //else if (result.length == left.length+1) { result[$-1] = carry; carry=0; } + return carry; +} + + +/* result = result - right + * Returns carry = 1 if result was less than right. +*/ +BigDigit subAssignSimple(BigDigit [] result, const(BigDigit) [] right) +pure nothrow +{ + assert(result.length >= right.length); + uint c = multibyteSub(result[0 .. right.length], result[0 .. right.length], right, 0); + if (c && result.length > right.length) + c = multibyteIncrementAssign!('-')(result[right.length .. $], c); + return c; +} + +/* result = result + right +*/ +BigDigit addAssignSimple(BigDigit [] result, const(BigDigit) [] right) +pure nothrow +{ + assert(result.length >= right.length); + uint c = multibyteAdd(result[0 .. right.length], result[0 .. right.length], right, 0); + if (c && result.length > right.length) + c = multibyteIncrementAssign!('+')(result[right.length .. $], c); + return c; +} + +/* performs result += wantSub? - right : right; +*/ +BigDigit addOrSubAssignSimple(BigDigit [] result, const(BigDigit) [] right, + bool wantSub) pure nothrow +{ + if (wantSub) + return subAssignSimple(result, right); + else + return addAssignSimple(result, right); +} + + +// return true if x= y.length); + auto k = x.length-1; + while (x[k]==0 && k >= y.length) + --k; + if (k >= y.length) + return false; + while (k>0 && x[k]==y[k]) + --k; + return x[k] < y[k]; +} + +// Set result = abs(x-y), return true if result is negative(x= y.length) ? x.length : y.length); + + size_t minlen; + bool negative; + if (x.length >= y.length) + { + minlen = y.length; + negative = less(x, y); + } + else + { + minlen = x.length; + negative = !less(y, x); + } + const (BigDigit)[] large, small; + if (negative) + { + large = y; small = x; + } + else + { + large = x; small = y; + } + + BigDigit carry = multibyteSub(result[0 .. minlen], large[0 .. minlen], small[0 .. minlen], 0); + if (x.length != y.length) + { + result[minlen .. large.length]= large[minlen..$]; + result[large.length..$] = 0; + if (carry) + multibyteIncrementAssign!('-')(result[minlen..$], carry); + } + return negative; +} + +/* Determine how much space is required for the temporaries + * when performing a Karatsuba multiplication. + */ +size_t karatsubaRequiredBuffSize(size_t xlen) pure nothrow @safe +{ + return xlen <= KARATSUBALIMIT ? 0 : 2*xlen; // - KARATSUBALIMIT+2; +} + +/* Sets result = x*y, using Karatsuba multiplication. +* x must be longer or equal to y. +* Valid only for balanced multiplies, where x is not shorter than y. +* It is superior to schoolbook multiplication if and only if +* sqrt(2)*y.length > x.length > y.length. +* Karatsuba multiplication is O(n^1.59), whereas schoolbook is O(n^2) +* The maximum allowable length of x and y is uint.max; but better algorithms +* should be used far before that length is reached. +* Params: +* scratchbuff An array long enough to store all the temporaries. Will be destroyed. +*/ +void mulKaratsuba(BigDigit [] result, const(BigDigit) [] x, + const(BigDigit)[] y, BigDigit [] scratchbuff) pure nothrow +{ + assert(x.length >= y.length); + assert(result.length < uint.max, "Operands too large"); + assert(result.length == x.length + y.length); + if (x.length <= KARATSUBALIMIT) + { + return mulSimple(result, x, y); + } + // Must be almost square (otherwise, a schoolbook iteration is better) + assert(2L * y.length * y.length > (x.length-1) * (x.length-1), + "Bigint Internal Error: Asymmetric Karatsuba"); + + // The subtractive version of Karatsuba multiply uses the following result: + // (Nx1 + x0)*(Ny1 + y0) = (N*N)*x1y1 + x0y0 + N * (x0y0 + x1y1 - mid) + // where mid = (x0-x1)*(y0-y1) + // requiring 3 multiplies of length N, instead of 4. + // The advantage of the subtractive over the additive version is that + // the mid multiply cannot exceed length N. But there are subtleties: + // (x0-x1),(y0-y1) may be negative or zero. To keep it simple, we + // retain all of the leading zeros in the subtractions + + // half length, round up. + auto half = (x.length >> 1) + (x.length & 1); + + const(BigDigit) [] x0 = x[0 .. half]; + const(BigDigit) [] x1 = x[half .. $]; + const(BigDigit) [] y0 = y[0 .. half]; + const(BigDigit) [] y1 = y[half .. $]; + BigDigit [] mid = scratchbuff[0 .. half*2]; + BigDigit [] newscratchbuff = scratchbuff[half*2 .. $]; + BigDigit [] resultLow = result[0 .. 2*half]; + BigDigit [] resultHigh = result[2*half .. $]; + // initially use result to store temporaries + BigDigit [] xdiff= result[0 .. half]; + BigDigit [] ydiff = result[half .. half*2]; + + // First, we calculate mid, and sign of mid + immutable bool midNegative = inplaceSub(xdiff, x0, x1) + ^ inplaceSub(ydiff, y0, y1); + mulKaratsuba(mid, xdiff, ydiff, newscratchbuff); + + // Low half of result gets x0 * y0. High half gets x1 * y1 + + mulKaratsuba(resultLow, x0, y0, newscratchbuff); + + if (2L * y1.length * y1.length < x1.length * x1.length) + { + // an asymmetric situation has been created. + // Worst case is if x:y = 1.414 : 1, then x1:y1 = 2.41 : 1. + // Applying one schoolbook multiply gives us two pieces each 1.2:1 + if (y1.length <= KARATSUBALIMIT) + mulSimple(resultHigh, x1, y1); + else + { + // divide x1 in two, then use schoolbook multiply on the two pieces. + auto quarter = (x1.length >> 1) + (x1.length & 1); + immutable ysmaller = (quarter >= y1.length); + mulKaratsuba(resultHigh[0 .. quarter+y1.length], ysmaller ? x1[0 .. quarter] : y1, + ysmaller ? y1 : x1[0 .. quarter], newscratchbuff); + // Save the part which will be overwritten. + immutable ysmaller2 = ((x1.length - quarter) >= y1.length); + newscratchbuff[0 .. y1.length] = resultHigh[quarter .. quarter + y1.length]; + mulKaratsuba(resultHigh[quarter..$], ysmaller2 ? x1[quarter..$] : y1, + ysmaller2 ? y1 : x1[quarter..$], newscratchbuff[y1.length..$]); + + resultHigh[quarter..$].addAssignSimple(newscratchbuff[0 .. y1.length]); + } + } + else + mulKaratsuba(resultHigh, x1, y1, newscratchbuff); + + /* We now have result = x0y0 + (N*N)*x1y1 + Before adding or subtracting mid, we must calculate + result += N * (x0y0 + x1y1) + We can do this with three half-length additions. With a = x0y0, b = x1y1: + aHI aLO + + aHI aLO + + bHI bLO + + bHI bLO + = R3 R2 R1 R0 + R1 = aHI + bLO + aLO + R2 = aHI + bLO + aHI + carry_from_R1 + R3 = bHi + carry_from_R2 + + It might actually be quicker to do it in two full-length additions: + newscratchbuff[2*half] = addSimple(newscratchbuff[0 .. 2*half], result[0 .. 2*half], result[2*half..$]); + addAssignSimple(result[half..$], newscratchbuff[0 .. 2*half+1]); + */ + BigDigit[] R1 = result[half .. half*2]; + BigDigit[] R2 = result[half*2 .. half*3]; + BigDigit[] R3 = result[half*3..$]; + BigDigit c1 = multibyteAdd(R2, R2, R1, 0); // c1:R2 = R2 + R1 + BigDigit c2 = multibyteAdd(R1, R2, result[0 .. half], 0); // c2:R1 = R2 + R1 + R0 + BigDigit c3 = addAssignSimple(R2, R3); // R2 = R2 + R1 + R3 + if (c1+c2) + multibyteIncrementAssign!('+')(result[half*2..$], c1+c2); + if (c1+c3) + multibyteIncrementAssign!('+')(R3, c1+c3); + + // And finally we subtract mid + addOrSubAssignSimple(result[half..$], mid, !midNegative); +} + +void squareKaratsuba(BigDigit [] result, const BigDigit [] x, + BigDigit [] scratchbuff) pure nothrow +{ + // See mulKaratsuba for implementation comments. + // Squaring is simpler, since it never gets asymmetric. + assert(result.length < uint.max, "Operands too large"); + assert(result.length == 2*x.length); + if (x.length <= KARATSUBASQUARELIMIT) + { + return squareSimple(result, x); + } + // half length, round up. + auto half = (x.length >> 1) + (x.length & 1); + + const(BigDigit)[] x0 = x[0 .. half]; + const(BigDigit)[] x1 = x[half .. $]; + BigDigit [] mid = scratchbuff[0 .. half*2]; + BigDigit [] newscratchbuff = scratchbuff[half*2 .. $]; + // initially use result to store temporaries + BigDigit [] xdiff= result[0 .. half]; + const BigDigit [] ydiff = result[half .. half*2]; + + // First, we calculate mid. We don't need its sign + inplaceSub(xdiff, x0, x1); + squareKaratsuba(mid, xdiff, newscratchbuff); + + // Set result = x0x0 + (N*N)*x1x1 + squareKaratsuba(result[0 .. 2*half], x0, newscratchbuff); + squareKaratsuba(result[2*half .. $], x1, newscratchbuff); + + /* result += N * (x0x0 + x1x1) + Do this with three half-length additions. With a = x0x0, b = x1x1: + R1 = aHI + bLO + aLO + R2 = aHI + bLO + aHI + carry_from_R1 + R3 = bHi + carry_from_R2 + */ + BigDigit[] R1 = result[half .. half*2]; + BigDigit[] R2 = result[half*2 .. half*3]; + BigDigit[] R3 = result[half*3..$]; + BigDigit c1 = multibyteAdd(R2, R2, R1, 0); // c1:R2 = R2 + R1 + BigDigit c2 = multibyteAdd(R1, R2, result[0 .. half], 0); // c2:R1 = R2 + R1 + R0 + BigDigit c3 = addAssignSimple(R2, R3); // R2 = R2 + R1 + R3 + if (c1+c2) multibyteIncrementAssign!('+')(result[half*2..$], c1+c2); + if (c1+c3) multibyteIncrementAssign!('+')(R3, c1+c3); + + // And finally we subtract mid, which is always positive + subAssignSimple(result[half..$], mid); +} + +/* Knuth's Algorithm D, as presented in + * H.S. Warren, "Hacker's Delight", Addison-Wesley Professional (2002). + * Also described in "Modern Computer Arithmetic" 0.2, Exercise 1.8.18. + * Given u and v, calculates quotient = u / v, u = u % v. + * v must be normalized (ie, the MSB of v must be 1). + * The most significant words of quotient and u may be zero. + * u[0 .. v.length] holds the remainder. + */ +void schoolbookDivMod(BigDigit [] quotient, BigDigit [] u, in BigDigit [] v) + pure nothrow +{ + assert(quotient.length == u.length - v.length); + assert(v.length > 1); + assert(u.length >= v.length); + assert((v[$-1]&0x8000_0000)!=0); + assert(u[$-1] < v[$-1]); + // BUG: This code only works if BigDigit is uint. + uint vhi = v[$-1]; + uint vlo = v[$-2]; + + for (ptrdiff_t j = u.length - v.length - 1; j >= 0; j--) + { + // Compute estimate of quotient[j], + // qhat = (three most significant words of u)/(two most sig words of v). + uint qhat; + if (u[j + v.length] == vhi) + { + // uu/vhi could exceed uint.max (it will be 0x8000_0000 or 0x8000_0001) + qhat = uint.max; + } + else + { + uint ulo = u[j + v.length - 2]; + version (D_InlineAsm_X86) + { + // Note: On DMD, this is only ~10% faster than the non-asm code. + uint *p = &u[j + v.length - 1]; + asm pure nothrow + { + mov EAX, p; + mov EDX, [EAX+4]; + mov EAX, [EAX]; + div dword ptr [vhi]; + mov qhat, EAX; + mov ECX, EDX; +div3by2correction: + mul dword ptr [vlo]; // EDX:EAX = qhat * vlo + sub EAX, ulo; + sbb EDX, ECX; + jbe div3by2done; + mov EAX, qhat; + dec EAX; + mov qhat, EAX; + add ECX, dword ptr [vhi]; + jnc div3by2correction; +div3by2done: ; + } + } + else + { // version (InlineAsm) + ulong uu = (cast(ulong)(u[j + v.length]) << 32) | u[j + v.length - 1]; + immutable bigqhat = uu / vhi; + ulong rhat = uu - bigqhat * vhi; + qhat = cast(uint) bigqhat; +again: + if (cast(ulong) qhat * vlo > ((rhat << 32) + ulo)) + { + --qhat; + rhat += vhi; + if (!(rhat & 0xFFFF_FFFF_0000_0000L)) + goto again; + } + } // version (InlineAsm) + } + // Multiply and subtract. + uint carry = multibyteMulAdd!('-')(u[j .. j + v.length], v, qhat, 0); + + if (u[j+v.length] < carry) + { + // If we subtracted too much, add back + --qhat; + carry -= multibyteAdd(u[j .. j + v.length],u[j .. j + v.length], v, 0); + } + quotient[j] = qhat; + u[j + v.length] = u[j + v.length] - carry; + } +} + +private: + +// TODO: Replace with a library call +void itoaZeroPadded(char[] output, uint value) + pure nothrow @safe @nogc +{ + for (auto i = output.length; i--;) + { + if (value < 10) + { + output[i] = cast(char)(value + '0'); + value = 0; + } + else + { + output[i] = cast(char)(value % 10 + '0'); + value /= 10; + } + } +} + +void toHexZeroPadded(char[] output, uint value, + LetterCase letterCase = LetterCase.upper) pure nothrow @safe +{ + ptrdiff_t x = output.length - 1; + static immutable string upperHexDigits = "0123456789ABCDEF"; + static immutable string lowerHexDigits = "0123456789abcdef"; + for ( ; x >= 0; --x) + { + if (letterCase == LetterCase.upper) + { + output[x] = upperHexDigits[value & 0xF]; + } + else + { + output[x] = lowerHexDigits[value & 0xF]; + } + value >>= 4; + } +} + +private: + +// Returns the highest value of i for which left[i]!=right[i], +// or 0 if left[] == right[] +size_t highestDifferentDigit(const BigDigit [] left, const BigDigit [] right) +pure nothrow @nogc @safe +{ + assert(left.length == right.length); + for (ptrdiff_t i = left.length - 1; i>0; --i) + { + if (left[i] != right[i]) + return i; + } + return 0; +} + +// Returns the lowest value of i for which x[i]!=0. +int firstNonZeroDigit(const BigDigit [] x) pure nothrow @nogc @safe +{ + int k = 0; + while (x[k]==0) + { + ++k; + assert(k= quotient + 1. + +Returns: + u[0 .. v.length] is the remainder. u[v.length..$] is corrupted. + + Implements algorithm 1.8 from MCA. + This algorithm has an annoying special case. After the first recursion, the + highest bit of the quotient may be set. This means that in the second + recursive call, the 'in' contract would be violated. (This happens only + when the top quarter of u is equal to the top half of v. A base 10 + equivalent example of this situation is 5517/56; the first step gives + 55/5 = 11). To maintain the in contract, we pad a zero to the top of both + u and the quotient. 'mayOverflow' indicates that that the special case + has occurred. + (In MCA, a different strategy is used: the in contract is weakened, and + schoolbookDivMod is more general: it allows the high bit of u to be set). + See also: + - C. Burkinel and J. Ziegler, "Fast Recursive Division", MPI-I-98-1-022, + Max-Planck Institute fuer Informatik, (Oct 1998). +*/ +void recursiveDivMod(BigDigit[] quotient, BigDigit[] u, const(BigDigit)[] v, + BigDigit[] scratch, bool mayOverflow = false) + pure nothrow +in +{ + // v must be normalized + assert(v.length > 1); + assert((v[$ - 1] & 0x8000_0000) != 0); + assert(!(u[$ - 1] & 0x8000_0000)); + assert(quotient.length == u.length - v.length); + if (mayOverflow) + { + assert(u[$-1] == 0); + assert(u[$-2] & 0x8000_0000); + } + + // Must be symmetric. Use block schoolbook division if not. + assert((mayOverflow ? u.length-1 : u.length) <= 2 * v.length); + assert((mayOverflow ? u.length-1 : u.length) >= v.length); + assert(scratch.length >= quotient.length + (mayOverflow ? 0 : 1)); +} +body +{ + if (quotient.length < FASTDIVLIMIT) + { + return schoolbookDivMod(quotient, u, v); + } + + // Split quotient into two halves, but keep padding in the top half + auto k = (mayOverflow ? quotient.length - 1 : quotient.length) >> 1; + + // RECURSION 1: Calculate the high half of the quotient + + // Note that if u and quotient were padded, they remain padded during + // this call, so in contract is satisfied. + recursiveDivMod(quotient[k .. $], u[2 * k .. $], v[k .. $], + scratch, mayOverflow); + + // quotient[k..$] is our guess at the high quotient. + // u[2*k .. 2.*k + v.length - k = k + v.length] is the high part of the + // first remainder. u[0 .. 2*k] is the low part. + + // Calculate the full first remainder to be + // remainder - highQuotient * lowDivisor + // reducing highQuotient until the remainder is positive. + // The low part of the remainder, u[0 .. k], cannot be altered by this. + + adjustRemainder(quotient[k .. $], u[k .. k + v.length], v, k, + scratch[0 .. quotient.length], mayOverflow); + + // RECURSION 2: Calculate the low half of the quotient + // The full first remainder is now in u[0 .. k + v.length]. + + if (u[k + v.length - 1] & 0x8000_0000) + { + // Special case. The high quotient is 0x1_00...000 or 0x1_00...001. + // This means we need an extra quotient word for the next recursion. + // We need to restore the invariant for the recursive calls. + // We do this by padding both u and quotient. Extending u is trivial, + // because the higher words will not be used again. But for the + // quotient, we're clobbering the low word of the high quotient, + // so we need save it, and add it back in after the recursive call. + + auto clobberedQuotient = quotient[k]; + u[k+v.length] = 0; + + recursiveDivMod(quotient[0 .. k+1], u[k .. k + v.length+1], + v[k .. $], scratch, true); + adjustRemainder(quotient[0 .. k+1], u[0 .. v.length], v, k, + scratch[0 .. 2 * k+1], true); + + // Now add the quotient word that got clobbered earlier. + multibyteIncrementAssign!('+')(quotient[k..$], clobberedQuotient); + } + else + { + // The special case has NOT happened. + recursiveDivMod(quotient[0 .. k], u[k .. k + v.length], v[k .. $], + scratch, false); + + // high remainder is in u[k .. k+(v.length-k)] == u[k .. v.length] + + adjustRemainder(quotient[0 .. k], u[0 .. v.length], v, k, + scratch[0 .. 2 * k]); + } +} + +// rem -= quot * v[0 .. k]. +// If would make rem negative, decrease quot until rem is >= 0. +// Needs (quot.length * k) scratch space to store the result of the multiply. +void adjustRemainder(BigDigit[] quot, BigDigit[] rem, const(BigDigit)[] v, + ptrdiff_t k, + BigDigit[] scratch, bool mayOverflow = false) pure nothrow +{ + assert(rem.length == v.length); + mulInternal(scratch, quot, v[0 .. k]); + uint carry = 0; + if (mayOverflow) + carry = scratch[$-1] + subAssignSimple(rem, scratch[0..$-1]); + else + carry = subAssignSimple(rem, scratch); + while (carry) + { + multibyteIncrementAssign!('-')(quot, 1); // quot-- + carry -= multibyteAdd(rem, rem, v, 0); + } +} + +// Cope with unbalanced division by performing block schoolbook division. +void blockDivMod(BigDigit [] quotient, BigDigit [] u, in BigDigit [] v) +pure nothrow +{ + import core.memory : GC; + assert(quotient.length == u.length - v.length); + assert(v.length > 1); + assert(u.length >= v.length); + assert((v[$-1] & 0x8000_0000)!=0); + assert((u[$-1] & 0x8000_0000)==0); + BigDigit [] scratch = new BigDigit[v.length + 1]; + + // Perform block schoolbook division, with 'v.length' blocks. + auto m = u.length - v.length; + while (m > v.length) + { + immutable mayOverflow = (u[m + v.length -1 ] & 0x8000_0000)!=0; + BigDigit saveq; + if (mayOverflow) + { + u[m + v.length] = 0; + saveq = quotient[m]; + } + recursiveDivMod(quotient[m-v.length .. m + (mayOverflow? 1: 0)], + u[m - v.length .. m + v.length + (mayOverflow? 1: 0)], v, scratch, mayOverflow); + if (mayOverflow) + { + assert(quotient[m] == 0); + quotient[m] = saveq; + } + m -= v.length; + } + recursiveDivMod(quotient[0 .. m], u[0 .. m + v.length], v, scratch); + () @trusted { GC.free(scratch.ptr); } (); +} + +@system unittest +{ + import core.stdc.stdio; + + void printBiguint(const uint [] data) + { + char [] buff = biguintToHex(new char[data.length*9], data, '_'); + printf("%.*s\n", buff.length, buff.ptr); + } + + void printDecimalBigUint(BigUint data) + { + auto str = data.toDecimalString(0); + printf("%.*s\n", str.length, str.ptr); + } + + uint [] a, b; + a = new uint[43]; + b = new uint[179]; + for (int i=0; i 0xFFFF_FFFF); + } + return cast(uint) c; +} + +@safe unittest +{ + uint [] a = new uint[40]; + uint [] b = new uint[40]; + uint [] c = new uint[40]; + for (size_t i = 0; i < a.length; ++i) + { + if (i&1) a[i]=cast(uint)(0x8000_0000 + i); + else a[i]=cast(uint) i; + b[i]= 0x8000_0003; + } + c[19]=0x3333_3333; + uint carry = multibyteAddSub!('+')(c[0 .. 18], b[0 .. 18], a[0 .. 18], 0); + assert(c[0]==0x8000_0003); + assert(c[1]==4); + assert(c[19]==0x3333_3333); // check for overrun + assert(carry == 1); + for (size_t i = 0; i < a.length; ++i) + { + a[i] = b[i] = c[i] = 0; + } + a[8]=0x048D159E; + b[8]=0x048D159E; + a[10]=0x1D950C84; + b[10]=0x1D950C84; + a[5] =0x44444444; + carry = multibyteAddSub!('-')(a[0 .. 12], a[0 .. 12], b[0 .. 12], 0); + assert(a[11] == 0); + for (size_t i = 0; i < 10; ++i) + if (i != 5) + assert(a[i] == 0); + + for (size_t q = 3; q < 36; ++q) + { + for (size_t i = 0; i< a.length; ++i) + { + a[i] = b[i] = c[i] = 0; + } + a[q-2]=0x040000; + b[q-2]=0x040000; + carry = multibyteAddSub!('-')(a[0 .. q], a[0 .. q], b[0 .. q], 0); + assert(a[q-2]==0); + } +} + + + +/** dest[] += carry, or dest[] -= carry. + * op must be '+' or '-' + * Returns final carry or borrow (0 or 1) + */ +uint multibyteIncrementAssign(char op)(uint[] dest, uint carry) + pure @nogc @safe +{ + static if (op=='+') + { + ulong c = carry; + c += dest[0]; + dest[0] = cast(uint) c; + if (c <= 0xFFFF_FFFF) + return 0; + + for (size_t i = 1; i < dest.length; ++i) + { + ++dest[i]; + if (dest[i] != 0) + return 0; + } + return 1; + } + else + { + ulong c = carry; + c = dest[0] - c; + dest[0] = cast(uint) c; + if (c <= 0xFFFF_FFFF) + return 0; + for (size_t i = 1; i < dest.length; ++i) + { + --dest[i]; + if (dest[i] != 0xFFFF_FFFF) + return 0; + } + return 1; + } +} + +/** dest[] = src[] << numbits + * numbits must be in the range 1 .. 31 + */ +uint multibyteShl(uint [] dest, const(uint) [] src, uint numbits) + pure @nogc @safe +{ + ulong c = 0; + for (size_t i = 0; i < dest.length; ++i) + { + c += (cast(ulong)(src[i]) << numbits); + dest[i] = cast(uint) c; + c >>>= 32; + } + return cast(uint) c; +} + + +/** dest[] = src[] >> numbits + * numbits must be in the range 1 .. 31 + */ +void multibyteShr(uint [] dest, const(uint) [] src, uint numbits) + pure @nogc @safe +{ + ulong c = 0; + for (ptrdiff_t i = dest.length; i != 0; --i) + { + c += (src[i-1] >>numbits) + (cast(ulong)(src[i-1]) << (64 - numbits)); + dest[i-1] = cast(uint) c; + c >>>= 32; + } +} + +@safe unittest +{ + + uint [] aa = [0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD, 0xEEEE_EEEE]; + multibyteShr(aa[0..$-2], aa, 4); + assert(aa[0] == 0x6122_2222 && aa[1] == 0xA455_5555 && aa[2] == 0x0899_9999); + assert(aa[3] == 0xBCCC_CCCD); + + aa = [0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD, 0xEEEE_EEEE]; + multibyteShr(aa[0..$-1], aa, 4); + assert(aa[0] == 0x6122_2222 && aa[1] == 0xA455_5555 + && aa[2] == 0xD899_9999 && aa[3] == 0x0BCC_CCCC); + + aa = [0xF0FF_FFFF, 0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD, + 0xEEEE_EEEE]; + multibyteShl(aa[1 .. 4], aa[1..$], 4); + assert(aa[0] == 0xF0FF_FFFF && aa[1] == 0x2222_2230 + && aa[2]==0x5555_5561 && aa[3]==0x9999_99A4 && aa[4]==0x0BCCC_CCCD); +} + +/** dest[] = src[] * multiplier + carry. + * Returns carry. + */ +uint multibyteMul(uint[] dest, const(uint)[] src, uint multiplier, uint carry) + pure @nogc @safe +{ + assert(dest.length == src.length); + ulong c = carry; + for (size_t i = 0; i < src.length; ++i) + { + c += cast(ulong)(src[i]) * multiplier; + dest[i] = cast(uint) c; + c>>=32; + } + return cast(uint) c; +} + +@safe unittest +{ + uint [] aa = [0xF0FF_FFFF, 0x1222_2223, 0x4555_5556, 0x8999_999A, + 0xBCCC_CCCD, 0xEEEE_EEEE]; + multibyteMul(aa[1 .. 4], aa[1 .. 4], 16, 0); + assert(aa[0] == 0xF0FF_FFFF && aa[1] == 0x2222_2230 && aa[2]==0x5555_5561 + && aa[3]==0x9999_99A4 && aa[4]==0x0BCCC_CCCD); +} + +/** + * dest[] += src[] * multiplier + carry(0 .. FFFF_FFFF). + * Returns carry out of MSB (0 .. FFFF_FFFF). + */ +uint multibyteMulAdd(char op)(uint [] dest, const(uint)[] src, + uint multiplier, uint carry) pure @nogc @safe +{ + assert(dest.length == src.length); + ulong c = carry; + for (size_t i = 0; i < src.length; ++i) + { + static if (op=='+') + { + c += cast(ulong)(multiplier) * src[i] + dest[i]; + dest[i] = cast(uint) c; + c >>= 32; + } + else + { + c += cast(ulong) multiplier * src[i]; + ulong t = cast(ulong) dest[i] - cast(uint) c; + dest[i] = cast(uint) t; + c = cast(uint)((c >> 32) - (t >> 32)); + } + } + return cast(uint) c; +} + +@safe unittest +{ + + uint [] aa = [0xF0FF_FFFF, 0x1222_2223, 0x4555_5556, 0x8999_999A, + 0xBCCC_CCCD, 0xEEEE_EEEE]; + uint [] bb = [0x1234_1234, 0xF0F0_F0F0, 0x00C0_C0C0, 0xF0F0_F0F0, + 0xC0C0_C0C0]; + multibyteMulAdd!('+')(bb[1..$-1], aa[1..$-2], 16, 5); + assert(bb[0] == 0x1234_1234 && bb[4] == 0xC0C0_C0C0); + assert(bb[1] == 0x2222_2230 + 0xF0F0_F0F0 + 5 + && bb[2] == 0x5555_5561 + 0x00C0_C0C0 + 1 + && bb[3] == 0x9999_99A4 + 0xF0F0_F0F0 ); +} + + +/** + Sets result = result[0 .. left.length] + left * right + + It is defined in this way to allow cache-efficient multiplication. + This function is equivalent to: + ---- + for (size_t i = 0; i< right.length; ++i) + { + dest[left.length + i] = multibyteMulAdd(dest[i .. left.length+i], + left, right[i], 0); + } + ---- + */ +void multibyteMultiplyAccumulate(uint [] dest, const(uint)[] left, const(uint) + [] right) pure @nogc @safe +{ + for (size_t i = 0; i < right.length; ++i) + { + dest[left.length + i] = multibyteMulAdd!('+')(dest[i .. left.length+i], + left, right[i], 0); + } +} + +/** dest[] /= divisor. + * overflow is the initial remainder, and must be in the range 0 .. divisor-1. + */ +uint multibyteDivAssign(uint [] dest, uint divisor, uint overflow) + pure @nogc @safe +{ + ulong c = cast(ulong) overflow; + for (ptrdiff_t i = dest.length-1; i >= 0; --i) + { + c = (c << 32) + cast(ulong)(dest[i]); + uint q = cast(uint)(c/divisor); + c -= divisor * q; + dest[i] = q; + } + return cast(uint) c; +} + +@safe unittest +{ + uint [] aa = new uint[101]; + for (uint i = 0; i < aa.length; ++i) + aa[i] = 0x8765_4321 * (i+3); + uint overflow = multibyteMul(aa, aa, 0x8EFD_FCFB, 0x33FF_7461); + uint r = multibyteDivAssign(aa, 0x8EFD_FCFB, overflow); + for (uint i=0; i>=32) + dest[2*i+1]; + dest[2*i+1] = cast(uint) c; + c >>= 32; + } +} + +// Does half a square multiply. (square = diagonal + 2*triangle) +void multibyteTriangleAccumulate(uint[] dest, const(uint)[] x) + pure @nogc @safe +{ + // x[0]*x[1...$] + x[1]*x[2..$] + ... + x[$-2]x[$-1..$] + dest[x.length] = multibyteMul(dest[1 .. x.length], x[1..$], x[0], 0); + if (x.length < 4) + { + if (x.length == 3) + { + ulong c = cast(ulong)(x[$-1]) * x[$-2] + dest[2*x.length-3]; + dest[2*x.length - 3] = cast(uint) c; + c >>= 32; + dest[2*x.length - 2] = cast(uint) c; + } + return; + } + for (size_t i = 2; i < x.length - 2; ++i) + { + dest[i-1+ x.length] = multibyteMulAdd!('+')( + dest[i+i-1 .. i+x.length-1], x[i..$], x[i-1], 0); + } + // Unroll the last two entries, to reduce loop overhead: + ulong c = cast(ulong)(x[$-3]) * x[$-2] + dest[2*x.length-5]; + dest[2*x.length-5] = cast(uint) c; + c >>= 32; + c += cast(ulong)(x[$-3]) * x[$-1] + dest[2*x.length-4]; + dest[2*x.length-4] = cast(uint) c; + c >>= 32; + c += cast(ulong)(x[$-1]) * x[$-2]; + dest[2*x.length-3] = cast(uint) c; + c >>= 32; + dest[2*x.length-2] = cast(uint) c; +} + +void multibyteSquare(BigDigit[] result, const(BigDigit) [] x) pure @nogc @safe +{ + multibyteTriangleAccumulate(result, x); + result[$-1] = multibyteShl(result[1..$-1], result[1..$-1], 1); // mul by 2 + result[0] = 0; + multibyteAddDiagonalSquares(result, x); +} diff --git a/libphobos/src/std/internal/math/biguintx86.d b/libphobos/src/std/internal/math/biguintx86.d new file mode 100644 index 0000000..bd03d2e --- /dev/null +++ b/libphobos/src/std/internal/math/biguintx86.d @@ -0,0 +1,1353 @@ +/** Optimised asm arbitrary precision arithmetic ('bignum') + * routines for X86 processors. + * + * All functions operate on arrays of uints, stored LSB first. + * If there is a destination array, it will be the first parameter. + * Currently, all of these functions are subject to change, and are + * intended for internal use only. + * The symbol [#] indicates an array of machine words which is to be + * interpreted as a multi-byte number. + */ + +/* Copyright Don Clugston 2008 - 2010. + * 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) + */ +/** + * In simple terms, there are 3 modern x86 microarchitectures: + * (a) the P6 family (Pentium Pro, PII, PIII, PM, Core), produced by Intel; + * (b) the K6, Athlon, and AMD64 families, produced by AMD; and + * (c) the Pentium 4, produced by Marketing. + * + * This code has been optimised for the Intel P6 family. + * Generally the code remains near-optimal for Intel Core2/Corei7, after + * translating EAX-> RAX, etc, since all these CPUs use essentially the same + * pipeline, and are typically limited by memory access. + * The code uses techniques described in Agner Fog's superb Pentium manuals + * available at www.agner.org. + * Not optimised for AMD, which can do two memory loads per cycle (Intel + * CPUs can only do one). Despite this, performance is superior on AMD. + * Performance is dreadful on P4. + * + * Timing results (cycles per int) + * --Intel Pentium-- --AMD-- + * PM P4 Core2 K7 + * +,- 2.25 15.6 2.25 1.5 + * <<,>> 2.0 6.6 2.0 5.0 + * (<< MMX) 1.7 5.3 1.5 1.2 + * * 5.0 15.0 4.0 4.3 + * mulAdd 5.7 19.0 4.9 4.0 + * div 30.0 32.0 32.0 22.4 + * mulAcc(32) 6.5 20.0 5.4 4.9 + * + * mulAcc(32) is multiplyAccumulate() for a 32*32 multiply. Thus it includes + * function call overhead. + * The timing for Div is quite unpredictable, but it's probably too slow + * to be useful. On 64-bit processors, these times should + * halve if run in 64-bit mode, except for the MMX functions. + */ + +module std.internal.math.biguintx86; + +@system: +pure: +nothrow: + +/* + Naked asm is used throughout, because: + (a) it frees up the EBP register + (b) compiler bugs prevent the use of .ptr when a frame pointer is used. +*/ + +version (D_InlineAsm_X86) +{ + +private: + +/* Duplicate string s, with n times, substituting index for '@'. + * + * Each instance of '@' in s is replaced by 0,1,...n-1. This is a helper + * function for some of the asm routines. + */ +string indexedLoopUnroll(int n, string s) pure @safe +{ + string u; + for (int i = 0; i9 ? ""~ cast(char)('0'+i/10) : "") ~ cast(char)('0' + i%10); + + int last = 0; + for (int j = 0; j> numbits + * numbits must be in the range 1 .. 31 + * This version uses MMX. + */ +uint multibyteShl(uint [] dest, const uint [] src, uint numbits) pure +{ + // Timing: + // K7 1.2/int. PM 1.7/int P4 5.3/int + enum { LASTPARAM = 4*4 } // 3* pushes + return address. + asm pure nothrow { + naked; + push ESI; + push EDI; + push EBX; + mov EDI, [ESP + LASTPARAM + 4*3]; //dest.ptr; + mov EBX, [ESP + LASTPARAM + 4*2]; //dest.length; + mov ESI, [ESP + LASTPARAM + 4*1]; //src.ptr; + + movd MM3, EAX; // numbits = bits to shift left + xor EAX, 63; + align 16; + inc EAX; + movd MM4, EAX ; // 64-numbits = bits to shift right + + // Get the return value into EAX + and EAX, 31; // EAX = 32-numbits + movd MM2, EAX; // 32-numbits + movd MM1, [ESI+4*EBX-4]; + psrlq MM1, MM2; + movd EAX, MM1; // EAX = return value + test EBX, 1; + jz L_even; +L_odd: + cmp EBX, 1; + jz L_length1; + + // deal with odd lengths + movq MM1, [ESI+4*EBX-8]; + psrlq MM1, MM2; + movd [EDI +4*EBX-4], MM1; + sub EBX, 1; +L_even: // It's either singly or doubly even + movq MM2, [ESI + 4*EBX - 8]; + psllq MM2, MM3; + sub EBX, 2; + jle L_last; + movq MM1, MM2; + add EBX, 2; + test EBX, 2; + jz L_onceeven; + sub EBX, 2; + + // MAIN LOOP -- 128 bytes per iteration + L_twiceeven: // here MM2 is the carry + movq MM0, [ESI + 4*EBX-8]; + psrlq MM0, MM4; + movq MM1, [ESI + 4*EBX-8]; + psllq MM1, MM3; + por MM2, MM0; + movq [EDI +4*EBX], MM2; +L_onceeven: // here MM1 is the carry + movq MM0, [ESI + 4*EBX-16]; + psrlq MM0, MM4; + movq MM2, [ESI + 4*EBX-16]; + por MM1, MM0; + movq [EDI +4*EBX-8], MM1; + psllq MM2, MM3; + sub EBX, 4; + jg L_twiceeven; +L_last: + movq [EDI +4*EBX], MM2; +L_alldone: + emms; // NOTE: costs 6 cycles on Intel CPUs + pop EBX; + pop EDI; + pop ESI; + ret 4*4; + +L_length1: + // length 1 is a special case + movd MM1, [ESI]; + psllq MM1, MM3; + movd [EDI], MM1; + jmp L_alldone; + } +} + +void multibyteShr(uint [] dest, const uint [] src, uint numbits) pure +{ + enum { LASTPARAM = 4*4 } // 3* pushes + return address. + asm pure nothrow { + naked; + push ESI; + push EDI; + push EBX; + mov EDI, [ESP + LASTPARAM + 4*3]; //dest.ptr; + mov EBX, [ESP + LASTPARAM + 4*2]; //dest.length; +align 16; + mov ESI, [ESP + LASTPARAM + 4*1]; //src.ptr; + lea EDI, [EDI + 4*EBX]; // EDI = end of dest + lea ESI, [ESI + 4*EBX]; // ESI = end of src + neg EBX; // count UP to zero. + + movd MM3, EAX; // numbits = bits to shift right + xor EAX, 63; + inc EAX; + movd MM4, EAX ; // 64-numbits = bits to shift left + + test EBX, 1; + jz L_even; +L_odd: + // deal with odd lengths + and EAX, 31; // EAX = 32-numbits + movd MM2, EAX; // 32-numbits + cmp EBX, -1; + jz L_length1; + + movq MM0, [ESI+4*EBX]; + psrlq MM0, MM3; + movd [EDI +4*EBX], MM0; + add EBX, 1; +L_even: + movq MM2, [ESI + 4*EBX]; + psrlq MM2, MM3; + + movq MM1, MM2; + add EBX, 4; + cmp EBX, -2+4; + jz L_last; + // It's either singly or doubly even + sub EBX, 2; + test EBX, 2; + jnz L_onceeven; + add EBX, 2; + + // MAIN LOOP -- 128 bytes per iteration + L_twiceeven: // here MM2 is the carry + movq MM0, [ESI + 4*EBX-8]; + psllq MM0, MM4; + movq MM1, [ESI + 4*EBX-8]; + psrlq MM1, MM3; + por MM2, MM0; + movq [EDI +4*EBX-16], MM2; +L_onceeven: // here MM1 is the carry + movq MM0, [ESI + 4*EBX]; + psllq MM0, MM4; + movq MM2, [ESI + 4*EBX]; + por MM1, MM0; + movq [EDI +4*EBX-8], MM1; + psrlq MM2, MM3; + add EBX, 4; + jl L_twiceeven; +L_last: + movq [EDI +4*EBX-16], MM2; +L_alldone: + emms; // NOTE: costs 6 cycles on Intel CPUs + pop EBX; + pop EDI; + pop ESI; + ret 4*4; + +L_length1: + // length 1 is a special case + movd MM1, [ESI+4*EBX]; + psrlq MM1, MM3; + movd [EDI +4*EBX], MM1; + jmp L_alldone; + + } +} + +/** dest[#] = src[#] >> numbits + * numbits must be in the range 1 .. 31 + */ +void multibyteShrNoMMX(uint [] dest, const uint [] src, uint numbits) pure +{ + // Timing: Optimal for P6 family. + // 2.0 cycles/int on PPro .. PM (limited by execution port p0) + // Terrible performance on AMD64, which has 7 cycles for SHRD!! + enum { LASTPARAM = 4*4 } // 3* pushes + return address. + asm pure nothrow { + naked; + push ESI; + push EDI; + push EBX; + mov EDI, [ESP + LASTPARAM + 4*3]; //dest.ptr; + mov EBX, [ESP + LASTPARAM + 4*2]; //dest.length; + mov ESI, [ESP + LASTPARAM + 4*1]; //src.ptr; + mov ECX, EAX; // numbits; + + lea EDI, [EDI + 4*EBX]; // EDI = end of dest + lea ESI, [ESI + 4*EBX]; // ESI = end of src + neg EBX; // count UP to zero. + mov EAX, [ESI + 4*EBX]; + cmp EBX, -1; + jz L_last; + mov EDX, [ESI + 4*EBX]; + test EBX, 1; + jz L_odd; + add EBX, 1; +L_even: + mov EDX, [ ESI + 4*EBX]; + shrd EAX, EDX, CL; + mov [-4 + EDI+4*EBX], EAX; +L_odd: + mov EAX, [4 + ESI + 4*EBX]; + shrd EDX, EAX, CL; + mov [EDI + 4*EBX], EDX; + add EBX, 2; + jl L_even; +L_last: + shr EAX, CL; + mov [-4 + EDI], EAX; + + pop EBX; + pop EDI; + pop ESI; + ret 4*4; + } +} + +@system unittest +{ + + uint [] aa = [0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD, 0xEEEE_EEEE]; + multibyteShr(aa[0..$-1], aa, 4); + assert(aa[0] == 0x6122_2222 && aa[1]==0xA455_5555 + && aa[2]==0xD899_9999 && aa[3]==0x0BCC_CCCC); + + aa = [0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD, 0xEEEE_EEEE]; + multibyteShr(aa[2..$-1], aa[2..$-1], 4); + assert(aa[0] == 0x1222_2223 && aa[1]==0x4555_5556 + && aa[2]==0xD899_9999 && aa[3]==0x0BCC_CCCC); + + aa = [0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD, 0xEEEE_EEEE]; + multibyteShr(aa[0..$-2], aa, 4); + assert(aa[1]==0xA455_5555 && aa[2]==0x0899_9999); + assert(aa[0]==0x6122_2222); + assert(aa[3]==0xBCCC_CCCD); + + + aa = [0xF0FF_FFFF, 0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD, 0xEEEE_EEEE]; + uint r = multibyteShl(aa[2 .. 4], aa[2 .. 4], 4); + assert(aa[0] == 0xF0FF_FFFF && aa[1]==0x1222_2223 + && aa[2]==0x5555_5560 && aa[3]==0x9999_99A4 && aa[4]==0xBCCC_CCCD); + assert(r == 8); + + aa = [0xF0FF_FFFF, 0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD, 0xEEEE_EEEE]; + r = multibyteShl(aa[1 .. 4], aa[1 .. 4], 4); + assert(aa[0] == 0xF0FF_FFFF + && aa[2]==0x5555_5561); + assert(aa[3]==0x9999_99A4 && aa[4]==0xBCCC_CCCD); + assert(r == 8); + assert(aa[1]==0x2222_2230); + + aa = [0xF0FF_FFFF, 0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD, 0xEEEE_EEEE]; + r = multibyteShl(aa[0 .. 4], aa[1 .. 5], 31); +} + +/** dest[#] = src[#] * multiplier + carry. + * Returns carry. + */ +uint multibyteMul(uint[] dest, const uint[] src, uint multiplier, uint carry) + pure +{ + // Timing: definitely not optimal. + // Pentium M: 5.0 cycles/operation, has 3 resource stalls/iteration + // Fastest implementation found was 4.6 cycles/op, but not worth the complexity. + + enum { LASTPARAM = 4*4 } // 4* pushes + return address. + // We'll use p2 (load unit) instead of the overworked p0 or p1 (ALU units) + // when initializing variables to zero. + version (D_PIC) + { + enum { zero = 0 } + } + else + { + __gshared int zero = 0; + } + asm pure nothrow { + naked; + push ESI; + push EDI; + push EBX; + + mov EDI, [ESP + LASTPARAM + 4*4]; // dest.ptr + mov EBX, [ESP + LASTPARAM + 4*3]; // dest.length + mov ESI, [ESP + LASTPARAM + 4*2]; // src.ptr + align 16; + lea EDI, [EDI + 4*EBX]; // EDI = end of dest + lea ESI, [ESI + 4*EBX]; // ESI = end of src + mov ECX, EAX; // [carry]; -- last param is in EAX. + neg EBX; // count UP to zero. + test EBX, 1; + jnz L_odd; + add EBX, 1; + L1: + mov EAX, [-4 + ESI + 4*EBX]; + mul int ptr [ESP+LASTPARAM]; //[multiplier]; + add EAX, ECX; + mov ECX, zero; + mov [-4+EDI + 4*EBX], EAX; + adc ECX, EDX; +L_odd: + mov EAX, [ESI + 4*EBX]; // p2 + mul int ptr [ESP+LASTPARAM]; //[multiplier]; // p0*3, + add EAX, ECX; + mov ECX, zero; + adc ECX, EDX; + mov [EDI + 4*EBX], EAX; + add EBX, 2; + jl L1; + + mov EAX, ECX; // get final carry + + pop EBX; + pop EDI; + pop ESI; + ret 5*4; + } +} + +@system unittest +{ + uint [] aa = [0xF0FF_FFFF, 0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD, 0xEEEE_EEEE]; + multibyteMul(aa[1 .. 4], aa[1 .. 4], 16, 0); + assert(aa[0] == 0xF0FF_FFFF && aa[1] == 0x2222_2230 && + aa[2]==0x5555_5561 && aa[3]==0x9999_99A4 && aa[4]==0x0BCCC_CCCD); +} + +// The inner multiply-and-add loop, together with the Even entry point. +// Multiples by M_ADDRESS which should be "ESP+LASTPARAM" or "ESP". OP must be "add" or "sub" +// This is the most time-critical code in the BigInt library. +// It is used by both MulAdd, multiplyAccumulate, and triangleAccumulate +string asmMulAdd_innerloop(string OP, string M_ADDRESS) pure { + // The bottlenecks in this code are extremely complicated. The MUL, ADD, and ADC + // need 4 cycles on each of the ALUs units p0 and p1. So we use memory load + // (unit p2) for initializing registers to zero. + // There are also dependencies between the instructions, and we run up against the + // ROB-read limit (can only read 2 registers per cycle). + // We also need the number of uops in the loop to be a multiple of 3. + // The only available execution unit for this is p3 (memory write). Unfortunately we can't do that + // if Position-Independent Code is required. + + // Register usage + // ESI = end of src + // EDI = end of dest + // EBX = index. Counts up to zero (in steps of 2). + // EDX:EAX = scratch, used in multiply. + // ECX = carry1. + // EBP = carry2. + // ESP = points to the multiplier. + + // The first member of 'dest' which will be modified is [EDI+4*EBX]. + // EAX must already contain the first member of 'src', [ESI+4*EBX]. + + version (D_PIC) { bool using_PIC = true; } else { bool using_PIC = false; } + return " + // Entry point for even length + add EBX, 1; + mov EBP, ECX; // carry + + mul int ptr [" ~ M_ADDRESS ~ "]; // M + mov ECX, 0; + + add EBP, EAX; + mov EAX, [ESI+4*EBX]; + adc ECX, EDX; + + mul int ptr [" ~ M_ADDRESS ~ "]; // M + " ~ OP ~ " [-4+EDI+4*EBX], EBP; + mov EBP, zero; + + adc ECX, EAX; + mov EAX, [4+ESI+4*EBX]; + + adc EBP, EDX; + add EBX, 2; + jnl L_done; +L1: + mul int ptr [" ~ M_ADDRESS ~ "]; + " ~ OP ~ " [-8+EDI+4*EBX], ECX; + adc EBP, EAX; + mov ECX, zero; + mov EAX, [ESI+4*EBX]; + adc ECX, EDX; +" ~ + (using_PIC ? "" : " mov storagenop, EDX; ") // make #uops in loop a multiple of 3, can't do this in PIC mode. +~ " + mul int ptr [" ~ M_ADDRESS ~ "]; + " ~ OP ~ " [-4+EDI+4*EBX], EBP; + mov EBP, zero; + + adc ECX, EAX; + mov EAX, [4+ESI+4*EBX]; + + adc EBP, EDX; + add EBX, 2; + jl L1; +L_done: " ~ OP ~ " [-8+EDI+4*EBX], ECX; + adc EBP, 0; +"; + // final carry is now in EBP +} + +string asmMulAdd_enter_odd(string OP, string M_ADDRESS) pure +{ + return " + mul int ptr [" ~M_ADDRESS ~"]; + mov EBP, zero; + add ECX, EAX; + mov EAX, [4+ESI+4*EBX]; + + adc EBP, EDX; + add EBX, 2; + jl L1; + jmp L_done; +"; +} + + + +/** + * dest[#] += src[#] * multiplier OP carry(0 .. FFFF_FFFF). + * where op == '+' or '-' + * Returns carry out of MSB (0 .. FFFF_FFFF). + */ +uint multibyteMulAdd(char op)(uint [] dest, const uint [] src, uint + multiplier, uint carry) pure { + // Timing: This is the most time-critical bignum function. + // Pentium M: 5.4 cycles/operation, still has 2 resource stalls + 1load block/iteration + + // The main loop is pipelined and unrolled by 2, + // so entry to the loop is also complicated. + + // Register usage + // EDX:EAX = multiply + // EBX = counter + // ECX = carry1 + // EBP = carry2 + // EDI = dest + // ESI = src + + enum string OP = (op=='+')? "add" : "sub"; + version (D_PIC) + { + enum { zero = 0 } + } + else + { + // use p2 (load unit) instead of the overworked p0 or p1 (ALU units) + // when initializing registers to zero. + __gshared int zero = 0; + // use p3/p4 units + __gshared int storagenop; // write-only + } + + enum { LASTPARAM = 5*4 } // 4* pushes + return address. + asm pure nothrow { + naked; + + push ESI; + push EDI; + push EBX; + push EBP; + mov EDI, [ESP + LASTPARAM + 4*4]; // dest.ptr + mov EBX, [ESP + LASTPARAM + 4*3]; // dest.length + align 16; + nop; + mov ESI, [ESP + LASTPARAM + 4*2]; // src.ptr + lea EDI, [EDI + 4*EBX]; // EDI = end of dest + lea ESI, [ESI + 4*EBX]; // ESI = end of src + mov EBP, 0; + mov ECX, EAX; // ECX = input carry. + neg EBX; // count UP to zero. + mov EAX, [ESI+4*EBX]; + test EBX, 1; + jnz L_enter_odd; + } + // Main loop, with entry point for even length + mixin("asm pure nothrow {" ~ asmMulAdd_innerloop(OP, "ESP+LASTPARAM") ~ "}"); + asm pure nothrow { + mov EAX, EBP; // get final carry + pop EBP; + pop EBX; + pop EDI; + pop ESI; + ret 5*4; + } +L_enter_odd: + mixin("asm pure nothrow {" ~ asmMulAdd_enter_odd(OP, "ESP+LASTPARAM") ~ "}"); +} + +@system unittest +{ + + uint [] aa = [0xF0FF_FFFF, 0x1222_2223, 0x4555_5556, 0x8999_999A, 0xBCCC_CCCD, 0xEEEE_EEEE]; + uint [] bb = [0x1234_1234, 0xF0F0_F0F0, 0x00C0_C0C0, 0xF0F0_F0F0, 0xC0C0_C0C0]; + multibyteMulAdd!('+')(bb[1..$-1], aa[1..$-2], 16, 5); + assert(bb[0] == 0x1234_1234 && bb[4] == 0xC0C0_C0C0); + assert(bb[1] == 0x2222_2230 + 0xF0F0_F0F0+5 && bb[2] == 0x5555_5561+0x00C0_C0C0+1 + && bb[3] == 0x9999_99A4+0xF0F0_F0F0 ); +} + +/** + Sets result[#] = result[0 .. left.length] + left[#] * right[#] + + It is defined in this way to allow cache-efficient multiplication. + This function is equivalent to: + ---- + for (int i = 0; i< right.length; ++i) + { + dest[left.length + i] = multibyteMulAdd(dest[i .. left.length+i], + left, right[i], 0); + } + ---- + */ +void multibyteMultiplyAccumulate(uint [] dest, const uint[] left, + const uint [] right) pure { + // Register usage + // EDX:EAX = used in multiply + // EBX = index + // ECX = carry1 + // EBP = carry2 + // EDI = end of dest for this pass through the loop. Index for outer loop. + // ESI = end of left. never changes + // [ESP] = M = right[i] = multiplier for this pass through the loop. + // right.length is changed into dest.ptr+dest.length + version (D_PIC) + { + enum { zero = 0 } + } + else + { + // use p2 (load unit) instead of the overworked p0 or p1 (ALU units) + // when initializing registers to zero. + __gshared int zero = 0; + // use p3/p4 units + __gshared int storagenop; // write-only + } + + enum { LASTPARAM = 6*4 } // 4* pushes + local + return address. + asm pure nothrow { + naked; + + push ESI; + push EDI; + align 16; + push EBX; + push EBP; + push EAX; // local variable M + mov EDI, [ESP + LASTPARAM + 4*5]; // dest.ptr + mov EBX, [ESP + LASTPARAM + 4*2]; // left.length + mov ESI, [ESP + LASTPARAM + 4*3]; // left.ptr + lea EDI, [EDI + 4*EBX]; // EDI = end of dest for first pass + + mov EAX, [ESP + LASTPARAM + 4*0]; // right.length + lea EAX, [EDI + 4*EAX]; + mov [ESP + LASTPARAM + 4*0], EAX; // last value for EDI + + lea ESI, [ESI + 4*EBX]; // ESI = end of left + mov EAX, [ESP + LASTPARAM + 4*1]; // right.ptr + mov EAX, [EAX]; + mov [ESP], EAX; // M +outer_loop: + mov EBP, 0; + mov ECX, 0; // ECX = input carry. + neg EBX; // count UP to zero. + mov EAX, [ESI+4*EBX]; + test EBX, 1; + jnz L_enter_odd; + } + // -- Inner loop, with even entry point + mixin("asm pure nothrow { " ~ asmMulAdd_innerloop("add", "ESP") ~ "}"); + asm pure nothrow { + mov [-4+EDI+4*EBX], EBP; + add EDI, 4; + cmp EDI, [ESP + LASTPARAM + 4*0]; // is EDI = &dest[$]? + jz outer_done; + mov EAX, [ESP + LASTPARAM + 4*1]; // right.ptr + mov EAX, [EAX+4]; // get new M + mov [ESP], EAX; // save new M + add int ptr [ESP + LASTPARAM + 4*1], 4; // right.ptr + mov EBX, [ESP + LASTPARAM + 4*2]; // left.length + jmp outer_loop; +outer_done: + pop EAX; + pop EBP; + pop EBX; + pop EDI; + pop ESI; + ret 6*4; + } +L_enter_odd: + mixin("asm pure nothrow {" ~ asmMulAdd_enter_odd("add", "ESP") ~ "}"); +} + +/** dest[#] /= divisor. + * overflow is the initial remainder, and must be in the range 0 .. divisor-1. + * divisor must not be a power of 2 (use right shift for that case; + * A division by zero will occur if divisor is a power of 2). + * Returns the final remainder + * + * Based on public domain code by Eric Bainville. + * (http://www.bealto.com/) Used with permission. + */ +uint multibyteDivAssign(uint [] dest, uint divisor, uint overflow) pure +{ + // Timing: limited by a horrible dependency chain. + // Pentium M: 18 cycles/op, 8 resource stalls/op. + // EAX, EDX = scratch, used by MUL + // EDI = dest + // CL = shift + // ESI = quotient + // EBX = remainderhi + // EBP = remainderlo + // [ESP-4] = mask + // [ESP] = kinv (2^64 /divisor) + enum { LASTPARAM = 5*4 } // 4* pushes + return address. + enum { LOCALS = 2*4} // MASK, KINV + asm pure nothrow { + naked; + + push ESI; + push EDI; + push EBX; + push EBP; + + mov EDI, [ESP + LASTPARAM + 4*2]; // dest.ptr + mov EBX, [ESP + LASTPARAM + 4*1]; // dest.length + + // Loop from msb to lsb + lea EDI, [EDI + 4*EBX]; + mov EBP, EAX; // rem is the input remainder, in 0 .. divisor-1 + // Build the pseudo-inverse of divisor k: 2^64/k + // First determine the shift in ecx to get the max number of bits in kinv + xor ECX, ECX; + mov EAX, [ESP + LASTPARAM]; //divisor; + mov EDX, 1; +kinv1: + inc ECX; + ror EDX, 1; + shl EAX, 1; + jnc kinv1; + dec ECX; + // Here, ecx is a left shift moving the msb of k to bit 32 + + mov EAX, 1; + shl EAX, CL; + dec EAX; + ror EAX, CL ; //ecx bits at msb + push EAX; // MASK + + // Then divide 2^(32+cx) by divisor (edx already ok) + xor EAX, EAX; + div int ptr [ESP + LASTPARAM + LOCALS-4*1]; //divisor; + push EAX; // kinv + align 16; +L2: + // Get 32 bits of quotient approx, multiplying + // most significant word of (rem*2^32+input) + mov EAX, [ESP+4]; //MASK; + and EAX, [EDI - 4]; + or EAX, EBP; + rol EAX, CL; + mov EBX, EBP; + mov EBP, [EDI - 4]; + mul int ptr [ESP]; //KINV; + + shl EAX, 1; + rcl EDX, 1; + + // Multiply by k and subtract to get remainder + // Subtraction must be done on two words + mov EAX, EDX; + mov ESI, EDX; // quot = high word + mul int ptr [ESP + LASTPARAM+LOCALS]; //divisor; + sub EBP, EAX; + sbb EBX, EDX; + jz Lb; // high word is 0, goto adjust on single word + + // Adjust quotient and remainder on two words +Ld: inc ESI; + sub EBP, [ESP + LASTPARAM+LOCALS]; //divisor; + sbb EBX, 0; + jnz Ld; + + // Adjust quotient and remainder on single word +Lb: cmp EBP, [ESP + LASTPARAM+LOCALS]; //divisor; + jc Lc; // rem in 0 .. divisor-1, OK + sub EBP, [ESP + LASTPARAM+LOCALS]; //divisor; + inc ESI; + jmp Lb; + + // Store result +Lc: + mov [EDI - 4], ESI; + lea EDI, [EDI - 4]; + dec int ptr [ESP + LASTPARAM + 4*1+LOCALS]; // len + jnz L2; + + pop EAX; // discard kinv + pop EAX; // discard mask + + mov EAX, EBP; // return final remainder + pop EBP; + pop EBX; + pop EDI; + pop ESI; + ret 3*4; + } +} + +@system unittest +{ + uint [] aa = new uint[101]; + for (int i=0; i>= 32; + c += cast(ulong)(x[$-3]) * x[$-1] + dest[$-4]; + dest[$-4] = cast(uint) c; + c >>= 32; +length2: + c += cast(ulong)(x[$-2]) * x[$-1]; + dest[$-3] = cast(uint) c; + c >>= 32; + dest[$-2] = cast(uint) c; +} + +//dest += src[0]*src[1...$] + src[1]*src[2..$] + ... + src[$-3]*src[$-2..$]+ src[$-2]*src[$-1] +// assert(dest.length = src.length*2); +// assert(src.length >= 3); +void multibyteTriangleAccumulateAsm(uint[] dest, const uint[] src) pure +{ + // Register usage + // EDX:EAX = used in multiply + // EBX = index + // ECX = carry1 + // EBP = carry2 + // EDI = end of dest for this pass through the loop. Index for outer loop. + // ESI = end of src. never changes + // [ESP] = M = src[i] = multiplier for this pass through the loop. + // dest.length is changed into dest.ptr+dest.length + version (D_PIC) + { + enum { zero = 0 } + } + else + { + // use p2 (load unit) instead of the overworked p0 or p1 (ALU units) + // when initializing registers to zero. + __gshared int zero = 0; + // use p3/p4 units + __gshared int storagenop; // write-only + } + + enum { LASTPARAM = 6*4 } // 4* pushes + local + return address. + asm pure nothrow { + naked; + + push ESI; + push EDI; + align 16; + push EBX; + push EBP; + push EAX; // local variable M= src[i] + mov EDI, [ESP + LASTPARAM + 4*3]; // dest.ptr + mov EBX, [ESP + LASTPARAM + 4*0]; // src.length + mov ESI, [ESP + LASTPARAM + 4*1]; // src.ptr + + lea ESI, [ESI + 4*EBX]; // ESI = end of left + add int ptr [ESP + LASTPARAM + 4*1], 4; // src.ptr, used for getting M + + // local variable [ESP + LASTPARAM + 4*2] = last value for EDI + lea EDI, [EDI + 4*EBX]; // EDI = end of dest for first pass + + lea EAX, [EDI + 4*EBX-3*4]; // up to src.length - 3 + mov [ESP + LASTPARAM + 4*2], EAX; // last value for EDI = &dest[src.length*2 -3] + + cmp EBX, 3; + jz length_is_3; + + // We start at src[1], not src[0]. + dec EBX; + mov [ESP + LASTPARAM + 4*0], EBX; + +outer_loop: + mov EBX, [ESP + LASTPARAM + 4*0]; // src.length + mov EBP, 0; + mov ECX, 0; // ECX = input carry. + dec [ESP + LASTPARAM + 4*0]; // Next time, the length will be shorter by 1. + neg EBX; // count UP to zero. + + mov EAX, [ESI + 4*EBX - 4*1]; // get new M + mov [ESP], EAX; // save new M + + mov EAX, [ESI+4*EBX]; + test EBX, 1; + jnz L_enter_odd; + } + // -- Inner loop, with even entry point + mixin("asm pure nothrow { " ~ asmMulAdd_innerloop("add", "ESP") ~ "}"); + asm pure nothrow { + mov [-4+EDI+4*EBX], EBP; + add EDI, 4; + cmp EDI, [ESP + LASTPARAM + 4*2]; // is EDI = &dest[$-3]? + jnz outer_loop; +length_is_3: + mov EAX, [ESI - 4*3]; + mul EAX, [ESI - 4*2]; + mov ECX, 0; + add [EDI-2*4], EAX; // ECX:dest[$-5] += x[$-3] * x[$-2] + adc ECX, EDX; + + mov EAX, [ESI - 4*3]; + mul EAX, [ESI - 4*1]; // x[$-3] * x[$-1] + add EAX, ECX; + mov ECX, 0; + adc EDX, 0; + // now EDX: EAX = c + x[$-3] * x[$-1] + add [EDI-1*4], EAX; // ECX:dest[$-4] += (EDX:EAX) + adc ECX, EDX; // ECX holds dest[$-3], it acts as carry for the last row +// do length == 2 + mov EAX, [ESI - 4*2]; + mul EAX, [ESI - 4*1]; + add ECX, EAX; + adc EDX, 0; + mov [EDI - 0*4], ECX; // dest[$-2:$-3] = c + x[$-2] * x[$-1]; + mov [EDI + 1*4], EDX; + + pop EAX; + pop EBP; + pop EBX; + pop EDI; + pop ESI; + ret 4*4; + } +L_enter_odd: + mixin("asm pure nothrow {" ~ asmMulAdd_enter_odd("add", "ESP") ~ "}"); +} + +@system unittest +{ + uint [] aa = new uint[200]; + uint [] a = aa[0 .. 100]; + uint [] b = new uint [100]; + aa[] = 761; + a[] = 0; + b[] = 0; + a[3] = 6; + b[0]=1; + b[1] = 17; + b[50 .. 100]=78; + multibyteTriangleAccumulateAsm(a, b[0 .. 50]); + uint [] c = new uint[100]; + c[] = 0; + c[1] = 17; + c[3] = 6; + assert(a[]==c[]); + assert(a[0]==0); + aa[] = 0xFFFF_FFFF; + a[] = 0; + b[] = 0; + b[0]= 0xbf6a1f01; + b[1]= 0x6e38ed64; + b[2]= 0xdaa797ed; + b[3] = 0; + + multibyteTriangleAccumulateAsm(a[0 .. 8], b[0 .. 4]); + assert(a[1]==0x3a600964); + assert(a[2]==0x339974f6); + assert(a[3]==0x46736fce); + assert(a[4]==0x5e24a2b4); + + b[3] = 0xe93ff9f4; + b[4] = 0x184f03; + a[]=0; + multibyteTriangleAccumulateAsm(a[0 .. 14], b[0 .. 7]); + assert(a[3]==0x79fff5c2); + assert(a[4]==0xcf384241); + assert(a[5]== 0x4a17fc8); + assert(a[6]==0x4d549025); +} + + +void multibyteSquare(BigDigit[] result, const BigDigit [] x) pure +{ + if (x.length < 4) + { + // Special cases, not worth doing triangular. + result[x.length] = multibyteMul(result[0 .. x.length], x, x[0], 0); + multibyteMultiplyAccumulate(result[1..$], x, x[1..$]); + return; + } + // Do half a square multiply. + // dest += src[0]*src[1...$] + src[1]*src[2..$] + ... + src[$-3]*src[$-2..$]+ src[$-2]*src[$-1] + result[x.length] = multibyteMul(result[1 .. x.length], x[1..$], x[0], 0); + multibyteTriangleAccumulateAsm(result[2..$], x[1..$]); + // Multiply by 2 + result[$-1] = multibyteShlNoMMX(result[1..$-1], result[1..$-1], 1); + // And add the diagonal elements + result[0] = 0; + multibyteAddDiagonalSquares(result, x); +} + +version (BignumPerformanceTest) +{ +import core.stdc.stdio; +int clock() { asm { push EBX; xor EAX, EAX; cpuid; pop EBX; rdtsc; } } + +__gshared uint [2200] X1; +__gshared uint [2200] Y1; +__gshared uint [4000] Z1; + +void testPerformance() pure +{ + // The performance results at the top of this file were obtained using + // a Windows device driver to access the CPU performance counters. + // The code below is less accurate but more widely usable. + // The value for division is quite inconsistent. + for (int i=0; i$0 + * GAMMA = Γ + * INTEGRAL = ∫ + * INTEGRATE = $(BIG ∫$(SMALL $1)$2) + * POWER = $1$2 + * BIGSUM = $(BIG Σ $2$(SMALL $1)) + * CHOOSE = $(BIG () $(SMALL $1)$(SMALL $2) $(BIG )) + * TABLE_SV = + * + * $0
Special Values
+ * SVH = $(TR $(TH $1) $(TH $2)) + * SV = $(TR $(TD $1) $(TD $2)) + */ +module std.internal.math.errorfunction; +import std.math; + +pure: +nothrow: +@safe: +@nogc: + +private { +immutable real EXP_2 = 0.135335283236612691893999494972484403L; /* exp(-2) */ +enum real SQRT2PI = 2.50662827463100050241576528481104525L; // sqrt(2pi) + + +enum real MAXLOG = 0x1.62e42fefa39ef358p+13L; // log(real.max) +enum real MINLOG = -0x1.6436716d5406e6d8p+13L; // log(real.min*real.epsilon) = log(smallest denormal) +} + +T rationalPoly(T)(T x, const(T) [] numerator, const(T) [] denominator) pure nothrow +{ + return poly(x, numerator)/poly(x, denominator); +} + + +private { + +/* erfc(x) = exp(-x^2) P(1/x)/Q(1/x) + 1/8 <= 1/x <= 1 + Peak relative error 5.8e-21 */ +immutable real[10] P = [ -0x1.30dfa809b3cc6676p-17, 0x1.38637cd0913c0288p+18, + 0x1.2f015e047b4476bp+22, 0x1.24726f46aa9ab08p+25, 0x1.64b13c6395dc9c26p+27, + 0x1.294c93046ad55b5p+29, 0x1.5962a82f92576dap+30, 0x1.11a709299faba04ap+31, + 0x1.11028065b087be46p+31, 0x1.0d8ef40735b097ep+30 +]; + +immutable real[11] Q = [ 0x1.14d8e2a72dec49f4p+19, 0x1.0c880ff467626e1p+23, + 0x1.04417ef060b58996p+26, 0x1.404e61ba86df4ebap+28, 0x1.0f81887bc82b873ap+30, + 0x1.4552a5e39fb49322p+31, 0x1.11779a0ceb2a01cep+32, 0x1.3544dd691b5b1d5cp+32, + 0x1.a91781f12251f02ep+31, 0x1.0d8ef3da605a1c86p+30, 1.0 +]; + +// For 128 bit quadruple-precision floats, we use a higher-precision implementation +// with more polynomial segments. +enum isIEEEQuadruple = floatTraits!real.realFormat == RealFormat.ieeeQuadruple; +static if (isIEEEQuadruple) +{ + // erfc(x + 0.25) = erfc(0.25) + x R(x) + // 0 <= x < 0.125 + // Peak relative error 1.4e-35 + immutable real[9] RNr13 = [ + -2.353707097641280550282633036456457014829E3L, + 3.871159656228743599994116143079870279866E2L, + -3.888105134258266192210485617504098426679E2L, + -2.129998539120061668038806696199343094971E1L, + -8.125462263594034672468446317145384108734E1L, + 8.151549093983505810118308635926270319660E0L, + -5.033362032729207310462422357772568553670E0L, + -4.253956621135136090295893547735851168471E-2L, + -8.098602878463854789780108161581050357814E-2L + ]; + immutable real[9] RDr13 = [ + 2.220448796306693503549505450626652881752E3L, + 1.899133258779578688791041599040951431383E2L, + 1.061906712284961110196427571557149268454E3L, + 7.497086072306967965180978101974566760042E1L, + 2.146796115662672795876463568170441327274E2L, + 1.120156008362573736664338015952284925592E1L, + 2.211014952075052616409845051695042741074E1L, + 6.469655675326150785692908453094054988938E-1L, + 1.0 + ]; + + // erfc(0.25) = C13a + C13b to extra precision. + immutable real C13a = 0.723663330078125L; + immutable real C13b = 1.0279753638067014931732235184287934646022E-5L; + + // erfc(x + 0.375) = erfc(0.375) + x R(x) + // 0 <= x < 0.125 + // Peak relative error 1.2e-35 + immutable real[9] RNr14 = [ + -2.446164016404426277577283038988918202456E3L, + 6.718753324496563913392217011618096698140E2L, + -4.581631138049836157425391886957389240794E2L, + -2.382844088987092233033215402335026078208E1L, + -7.119237852400600507927038680970936336458E1L, + 1.313609646108420136332418282286454287146E1L, + -6.188608702082264389155862490056401365834E0L, + -2.787116601106678287277373011101132659279E-2L, + -2.230395570574153963203348263549700967918E-2L + ]; + immutable real[9] RDr14 = [ + 2.495187439241869732696223349840963702875E3L, + 2.503549449872925580011284635695738412162E2L, + 1.159033560988895481698051531263861842461E3L, + 9.493751466542304491261487998684383688622E1L, + 2.276214929562354328261422263078480321204E2L, + 1.367697521219069280358984081407807931847E1L, + 2.276988395995528495055594829206582732682E1L, + 7.647745753648996559837591812375456641163E-1L, + 1.0 + ]; + + // erfc(0.375) = C14a + C14b to extra precision. + immutable real C14a = 0.5958709716796875L; + immutable real C14b = 1.2118885490201676174914080878232469565953E-5L; + + // erfc(x + 0.5) = erfc(0.5) + x R(x) + // 0 <= x < 0.125 + // Peak relative error 4.7e-36 + immutable real[9] RNr15 = [ + -2.624212418011181487924855581955853461925E3L, + 8.473828904647825181073831556439301342756E2L, + -5.286207458628380765099405359607331669027E2L, + -3.895781234155315729088407259045269652318E1L, + -6.200857908065163618041240848728398496256E1L, + 1.469324610346924001393137895116129204737E1L, + -6.961356525370658572800674953305625578903E0L, + 5.145724386641163809595512876629030548495E-3L, + 1.990253655948179713415957791776180406812E-2L + ]; + immutable real[9] RDr15 = [ + 2.986190760847974943034021764693341524962E3L, + 5.288262758961073066335410218650047725985E2L, + 1.363649178071006978355113026427856008978E3L, + 1.921707975649915894241864988942255320833E2L, + 2.588651100651029023069013885900085533226E2L, + 2.628752920321455606558942309396855629459E1L, + 2.455649035885114308978333741080991380610E1L, + 1.378826653595128464383127836412100939126E0L, + 1.0 + ]; + // erfc(0.5) = C15a + C15b to extra precision. + immutable real C15a = 0.4794921875L; + immutable real C15b = 7.9346869534623172533461080354712635484242E-6L; + + // erfc(x + 0.625) = erfc(0.625) + x R(x) + // 0 <= x < 0.125 + // Peak relative error 5.1e-36 + immutable real[9] RNr16 = [ + -2.347887943200680563784690094002722906820E3L, + 8.008590660692105004780722726421020136482E2L, + -5.257363310384119728760181252132311447963E2L, + -4.471737717857801230450290232600243795637E1L, + -4.849540386452573306708795324759300320304E1L, + 1.140885264677134679275986782978655952843E1L, + -6.731591085460269447926746876983786152300E0L, + 1.370831653033047440345050025876085121231E-1L, + 2.022958279982138755020825717073966576670E-2L, + ]; + immutable real[9] RDr16 = [ + 3.075166170024837215399323264868308087281E3L, + 8.730468942160798031608053127270430036627E2L, + 1.458472799166340479742581949088453244767E3L, + 3.230423687568019709453130785873540386217E2L, + 2.804009872719893612081109617983169474655E2L, + 4.465334221323222943418085830026979293091E1L, + 2.612723259683205928103787842214809134746E1L, + 2.341526751185244109722204018543276124997E0L, + 1.0 + ]; + // erfc(0.625) = C16a + C16b to extra precision. + immutable real C16a = 0.3767547607421875L; + immutable real C16b = 4.3570693945275513594941232097252997287766E-6L; + + // erfc(x + 0.75) = erfc(0.75) + x R(x) + // 0 <= x < 0.125 + // Peak relative error 1.7e-35 + immutable real[9] RNr17 = [ + -1.767068734220277728233364375724380366826E3L, + 6.693746645665242832426891888805363898707E2L, + -4.746224241837275958126060307406616817753E2L, + -2.274160637728782675145666064841883803196E1L, + -3.541232266140939050094370552538987982637E1L, + 6.988950514747052676394491563585179503865E0L, + -5.807687216836540830881352383529281215100E0L, + 3.631915988567346438830283503729569443642E-1L, + -1.488945487149634820537348176770282391202E-2L + ]; + immutable real[9] RDr17 = [ + 2.748457523498150741964464942246913394647E3L, + 1.020213390713477686776037331757871252652E3L, + 1.388857635935432621972601695296561952738E3L, + 3.903363681143817750895999579637315491087E2L, + 2.784568344378139499217928969529219886578E2L, + 5.555800830216764702779238020065345401144E1L, + 2.646215470959050279430447295801291168941E1L, + 2.984905282103517497081766758550112011265E0L, + 1.0 + ]; + // erfc(0.75) = C17a + C17b to extra precision. + immutable real C17a = 0.2888336181640625L; + immutable real C17b = 1.0748182422368401062165408589222625794046E-5L; + + + // erfc(x + 0.875) = erfc(0.875) + x R(x) + // 0 <= x < 0.125 + // Peak relative error 2.2e-35 + immutable real[9] RNr18 = [ + -1.342044899087593397419622771847219619588E3L, + 6.127221294229172997509252330961641850598E2L, + -4.519821356522291185621206350470820610727E2L, + 1.223275177825128732497510264197915160235E1L, + -2.730789571382971355625020710543532867692E1L, + 4.045181204921538886880171727755445395862E0L, + -4.925146477876592723401384464691452700539E0L, + 5.933878036611279244654299924101068088582E-1L, + -5.557645435858916025452563379795159124753E-2L + ]; + immutable real[9] RDr18 = [ + 2.557518000661700588758505116291983092951E3L, + 1.070171433382888994954602511991940418588E3L, + 1.344842834423493081054489613250688918709E3L, + 4.161144478449381901208660598266288188426E2L, + 2.763670252219855198052378138756906980422E2L, + 5.998153487868943708236273854747564557632E1L, + 2.657695108438628847733050476209037025318E1L, + 3.252140524394421868923289114410336976512E0L, + 1.0 + ]; + + // erfc(0.875) = C18a + C18b to extra precision. + immutable real C18a = 0.215911865234375L; + immutable real C18b = 1.3073705765341685464282101150637224028267E-5L; + + // erfc(x + 1.0) = erfc(1.0) + x R(x) + // 0 <= x < 0.125 + // Peak relative error 1.6e-35 + immutable real[9] RNr19 = [ + -1.139180936454157193495882956565663294826E3L, + 6.134903129086899737514712477207945973616E2L, + -4.628909024715329562325555164720732868263E2L, + 4.165702387210732352564932347500364010833E1L, + -2.286979913515229747204101330405771801610E1L, + 1.870695256449872743066783202326943667722E0L, + -4.177486601273105752879868187237000032364E0L, + 7.533980372789646140112424811291782526263E-1L, + -8.629945436917752003058064731308767664446E-2L + ]; + immutable real[9] RDr19 = [ + 2.744303447981132701432716278363418643778E3L, + 1.266396359526187065222528050591302171471E3L, + 1.466739461422073351497972255511919814273E3L, + 4.868710570759693955597496520298058147162E2L, + 2.993694301559756046478189634131722579643E2L, + 6.868976819510254139741559102693828237440E1L, + 2.801505816247677193480190483913753613630E1L, + 3.604439909194350263552750347742663954481E0L, + 1.0 + ]; + + // erfc(1.0) = C19a + C19b to extra precision. + immutable real C19a = 0.15728759765625L; + immutable real C19b = 1.1609394035130658779364917390740703933002E-5L; + + // erfc(x + 1.125) = erfc(1.125) + x R(x) + // 0 <= x < 0.125 + // Peak relative error 3.6e-36 + immutable real[9] RNr20 = [ + -9.652706916457973956366721379612508047640E2L, + 5.577066396050932776683469951773643880634E2L, + -4.406335508848496713572223098693575485978E2L, + 5.202893466490242733570232680736966655434E1L, + -1.931311847665757913322495948705563937159E1L, + -9.364318268748287664267341457164918090611E-2L, + -3.306390351286352764891355375882586201069E0L, + 7.573806045289044647727613003096916516475E-1L, + -9.611744011489092894027478899545635991213E-2L + ]; + immutable real[9] RDr20 = [ + 3.032829629520142564106649167182428189014E3L, + 1.659648470721967719961167083684972196891E3L, + 1.703545128657284619402511356932569292535E3L, + 6.393465677731598872500200253155257708763E2L, + 3.489131397281030947405287112726059221934E2L, + 8.848641738570783406484348434387611713070E1L, + 3.132269062552392974833215844236160958502E1L, + 4.430131663290563523933419966185230513168E0L, + 1.0 + ]; + + // erfc(1.125) = C20a + C20b to extra precision. + immutable real C20a = 0.111602783203125L; + immutable real C20b = 8.9850951672359304215530728365232161564636E-6L; + + // erfc(x) = exp(-x^2) 1/x R(1/x^2) / S(1/x^2) + // 7/8 <= 1/x < 1 + // Peak relative error 1.4e-35 + immutable real[10] RNr8 = [ + 3.587451489255356250759834295199296936784E1L, + 5.406249749087340431871378009874875889602E2L, + 2.931301290625250886238822286506381194157E3L, + 7.359254185241795584113047248898753470923E3L, + 9.201031849810636104112101947312492532314E3L, + 5.749697096193191467751650366613289284777E3L, + 1.710415234419860825710780802678697889231E3L, + 2.150753982543378580859546706243022719599E2L, + 8.740953582272147335100537849981160931197E0L, + 4.876422978828717219629814794707963640913E-2L + ]; + immutable real[10] RDr8 = [ + 6.358593134096908350929496535931630140282E1L, + 9.900253816552450073757174323424051765523E2L, + 5.642928777856801020545245437089490805186E3L, + 1.524195375199570868195152698617273739609E4L, + 2.113829644500006749947332935305800887345E4L, + 1.526438562626465706267943737310282977138E4L, + 5.561370922149241457131421914140039411782E3L, + 9.394035530179705051609070428036834496942E2L, + 6.147019596150394577984175188032707343615E1L, + 1.0L + ]; + + // erfc(x) = exp(-x^2) 1/x R(1/x^2) / S(1/x^2) + // 3/4 <= 1/x < 7/8 + // Peak relative error 1.7e-36 + immutable real[10] RNr7 = [ + 1.293515364043117601705535485785956592493E2L, + 2.474534371269189867053251150063459055230E3L, + 1.756537563959875738809491329250457486510E4L, + 5.977479535376639763773153344676726091607E4L, + 1.054313816139671870123172936972055385049E5L, + 9.754699773487726957401038094714603033904E4L, + 4.579131242577671038339922925213209214880E4L, + 1.000710322164510887997115157797717324370E4L, + 8.496863250712471449526805271633794700452E2L, + 1.797349831386892396933210199236530557333E1L + ]; + immutable real[11] RDr7 = [ + 2.292696320307033494820399866075534515002E2L, + 4.500632608295626968062258401895610053116E3L, + 3.321218723485498111535866988511716659339E4L, + 1.196084512221845156596781258490840961462E5L, + 2.287033883912529843927983406878910939930E5L, + 2.370223495794642027268482075021298394425E5L, + 1.305173734022437154610938308944995159199E5L, + 3.589386258485887630236490009835928559621E4L, + 4.339996864041074149726360516336773136101E3L, + 1.753135522665469574605384979152863899099E2L, + 1.0L + ]; + + // erfc(x) = exp(-x^2) 1/x R(1/x^2) / S(1/x^2) + // 5/8 <= 1/x < 3/4 + // Peak relative error 1.6e-35 + immutable real[10] RNr6 = [ + 1.423313561367394923305025174137639124533E1L, + 3.244462503273003837514629113846075327206E2L, + 2.784937282403293364911673341412846781934E3L, + 1.163060685810874867196849890286455473382E4L, + 2.554141394931962276102205517358731053756E4L, + 2.982733782500729530503336931258698708782E4L, + 1.789683564523810605328169719436374742840E4L, + 5.056032142227470121262177112822018882754E3L, + 5.605349942234782054561269306895707034586E2L, + 1.561652599080729507274832243665726064881E1L + ]; + immutable real[11] RDr6 = [ + 2.522757606611479946069351519410222913326E1L, + 5.876797910931896554014229647006604017806E2L, + 5.211092128250480712011248211246144751074E3L, + 2.282679910855404599271496827409168580797E4L, + 5.371245819205596609986320599133109262447E4L, + 6.926186102106400355114925675028888924445E4L, + 4.794366033363621432575096487724913414473E4L, + 1.673190682734065914573814938835674963896E4L, + 2.589544846151313120096957014256536236242E3L, + 1.349438432583208276883323156200117027433E2L, + 1.0L + ]; + + // erfc(x) = exp(-x^2) 1/x R(1/x^2) / S(1/x^2) + // 1/2 <= 1/x < 5/8 + // Peak relative error 4.3e-36 + immutable real[11] RNr5 = [ + 6.743447478279267339131211137241149796763E-2L, + 2.031339015932962998168472743355874796350E0L, + 2.369234815713196480221800940201367484379E1L, + 1.387819614016107433603101545594790875922E2L, + 4.435600256936515839937720907171966121786E2L, + 7.881577949936817507981170892417739733047E2L, + 7.615749099291545976179905281851765734680E2L, + 3.752484528663442467089606663006771157777E2L, + 8.279644286027145214308303292537009564726E1L, + 6.201462983413738162709722770960040042647E0L, + 6.649631608720062333043506249503378282697E-2L + ]; + immutable real[11] RDr5 = [ + 1.195244945161889822018178270706903972343E-1L, + 3.660216908153253021384862427197665991311E0L, + 4.373405883243078019655721779021995159854E1L, + 2.653305963056235008916733402786877121865E2L, + 8.921329790491152046318422124415895506335E2L, + 1.705552231555600759729260640562363304312E3L, + 1.832711109606893446763174603477244625325E3L, + 1.056823953275835649973998168744261083316E3L, + 2.975561981792909722126456997074344895584E2L, + 3.393149095158232521894537008472203487436E1L, + 1.0L + ]; + + // erfc(x) = exp(-x^2) 1/x R(1/x^2) / S(1/x^2) + // 3/8 <= 1/x < 1/2 + // Peak relative error 1.8e-36 + immutable real[11] RNr4 = [ + 3.558685919236420073872459554885612994007E-2L, + 1.460223642496950651561817195253277924528E0L, + 2.379856746555189546876720308841066577268E1L, + 2.005205521246554860334064698817220160117E2L, + 9.533374928664989955629120027419490517596E2L, + 2.623024576994438336130421711314560425373E3L, + 4.126446434603735586340585027628851620886E3L, + 3.540675861596687801829655387867654030013E3L, + 1.506037084891064572653273761987617394259E3L, + 2.630715699182706745867272452228891752353E2L, + 1.202476629652900619635409242749750364878E1L + ]; + immutable real[12] RDr4 = [ + 6.307606561714590590399683184410336583739E-2L, + 2.619717051134271249293056836082721776665E0L, + 4.344441402681725017630451522968410844608E1L, + 3.752891116408399440953195184301023399176E2L, + 1.849305988804654653921972804388006355502E3L, + 5.358505261991675891835885654499883449403E3L, + 9.091890995405251314631428721090705475825E3L, + 8.731418313949291797856351745278287516416E3L, + 4.420211285043270337492325400764271868740E3L, + 1.031487363021856106882306509107923200832E3L, + 8.387036084846046121805145056040429461783E1L, + 1.0L + ]; + + // erfc(x) = exp(-x^2) 1/x R(1/x^2) / S(1/x^2) + // 1/4 <= 1/x < 3/8 + // Peak relative error 8.1e-37 + immutable real[12] RNr3 = [ + 4.584481542956275354582319313040418316755E-5L, + 2.674923158288848442110883948437930349128E-3L, + 6.344232532055212248017211243740466847311E-2L, + 7.985145965992002744933550450451513513963E-1L, + 5.845078061888281665064746347663123946270E0L, + 2.566625318816866587482759497608029522596E1L, + 6.736225182343446605268837827950856640948E1L, + 1.021796460139598089409347761712730512053E2L, + 8.344336615515430530929955615400706619764E1L, + 3.207749011528249356283897356277376306967E1L, + 4.386185123863412086856423971695142026036E0L, + 8.971576448581208351826868348023528863856E-2L + ]; + immutable real[12] RDr3 = [ + 8.125781965218112303281657065320409661370E-5L, + 4.781806762611504685247817818428945295520E-3L, + 1.147785538413798317790357996845767614561E-1L, + 1.469285552007088106614218996464752307606E0L, + 1.101712261349880339221039938999124077650E1L, + 5.008507527095093413713171655268276861071E1L, + 1.383058691613468970486425146336829447433E2L, + 2.264114250278912520501010108736339599752E2L, + 2.081377197698598680576330179979996940039E2L, + 9.724438129690013609440151781601781137944E1L, + 1.907905050771832372089975877589291760121E1L, + 1.0L + ]; + + // erfc(x) = exp(-x^2) 1/x R(1/x^2) / S(1/x^2) + // 1/8 <= 1/x < 1/4 + // Peak relative error 1.5e-36 + immutable real[12] RNr2 = [ + 6.928615158005256885698045840589513728399E-7L, + 5.616245938942075826026382337922413007879E-5L, + 1.871624980715261794832358438894219696113E-3L, + 3.349922063795792371642023765253747563009E-2L, + 3.531865233974349943956345502463135695834E-1L, + 2.264714157625072773976468825160906342360E0L, + 8.810720294489253776747319730638214883026E0L, + 2.014056685571655833019183248931442888437E1L, + 2.524586947657190747039554310814128743320E1L, + 1.520656940937208886246188940244581671609E1L, + 3.334145500790963675035841482334493680498E0L, + 1.122108380007109245896534245151140632457E-1L + ]; + immutable real[12] RDr2 = [ + 1.228065061824874795984937092427781089256E-6L, + 1.001593999520159167559129042893802235969E-4L, + 3.366527555699367241421450749821030974446E-3L, + 6.098626947195865254152265585991861150369E-2L, + 6.541547922508613985813189387198804660235E-1L, + 4.301130233305371976727117480925676583204E0L, + 1.737155892350891711527711121692994762909E1L, + 4.206892112110558214680649401236873828801E1L, + 5.787487996025016843403524261574779631219E1L, + 4.094047148590822715163181507813774861621E1L, + 1.230603930056944875836549716515643997094E1L, + 1.0L + ]; + + // erfc(x) = exp(-x^2) 1/x R(1/x^2) / S(1/x^2) + // 1/128 <= 1/x < 1/8 + // Peak relative error 2.2e-36 + immutable real[10] RNr1 = [ + 1.293111801138199795250035229383033322539E-6L, + 9.785224720880980456171438759161161816706E-5L, + 2.932474396233212166056331430621176065943E-3L, + 4.496429595719847083917435337780697436921E-2L, + 3.805989855927720844877478869846718877846E-1L, + 1.789745532222426292126781724570152590071E0L, + 4.465737379634389318903237306594171764628E0L, + 5.268535822258082278401240171488850433767E0L, + 2.258357766807433839494276681092713991651E0L, + 1.504459334078750002966538036652860809497E-1L + ]; + immutable real[10] RDr1 = [ + 2.291980991578770070179177302906728371406E-6L, + 1.745845828808028552029674694534934620384E-4L, + 5.283248841982102317072923869576785278019E-3L, + 8.221212297078141470944454807434634848018E-2L, + 7.120500674861902950423510939060230945621E-1L, + 3.475435367560809622183983439133664598155E0L, + 9.243253391989233533874386043611304387113E0L, + 1.227894792475280941511758877318903197188E1L, + 6.789361410398841316638617624392719077724E0L, + 1.0L + ]; + + // erf(z+1) = erfConst + P(z)/Q(z) + // -.125 <= z <= 0 + // Peak relative error 7.3e-36 + immutable real erfConst = 0.845062911510467529296875L; + immutable real[9] TN2 = [ + -4.088889697077485301010486931817357000235E1L, + 7.157046430681808553842307502826960051036E3L, + -2.191561912574409865550015485451373731780E3L, + 2.180174916555316874988981177654057337219E3L, + 2.848578658049670668231333682379720943455E2L, + 1.630362490952512836762810462174798925274E2L, + 6.317712353961866974143739396865293596895E0L, + 2.450441034183492434655586496522857578066E1L, + 5.127662277706787664956025545897050896203E-1L + ]; + immutable real[10] TD2 = [ + 1.731026445926834008273768924015161048885E4L, + 1.209682239007990370796112604286048173750E4L, + 1.160950290217993641320602282462976163857E4L, + 5.394294645127126577825507169061355698157E3L, + 2.791239340533632669442158497532521776093E3L, + 8.989365571337319032943005387378993827684E2L, + 2.974016493766349409725385710897298069677E2L, + 6.148192754590376378740261072533527271947E1L, + 1.178502892490738445655468927408440847480E1L, + 1.0L + ]; + + // erf(x) = x + x P(x^2)/Q(x^2) + // 0 <= x <= 7/8 + // Peak relative error 1.8e-35 + immutable real[9] TN1 = [ + -3.858252324254637124543172907442106422373E10L, + 9.580319248590464682316366876952214879858E10L, + 1.302170519734879977595901236693040544854E10L, + 2.922956950426397417800321486727032845006E9L, + 1.764317520783319397868923218385468729799E8L, + 1.573436014601118630105796794840834145120E7L, + 4.028077380105721388745632295157816229289E5L, + 1.644056806467289066852135096352853491530E4L, + 3.390868480059991640235675479463287886081E1L + ]; + immutable real[10] TD1 = [ + -3.005357030696532927149885530689529032152E11L, + -1.342602283126282827411658673839982164042E11L, + -2.777153893355340961288511024443668743399E10L, + -3.483826391033531996955620074072768276974E9L, + -2.906321047071299585682722511260895227921E8L, + -1.653347985722154162439387878512427542691E7L, + -6.245520581562848778466500301865173123136E5L, + -1.402124304177498828590239373389110545142E4L, + -1.209368072473510674493129989468348633579E2L, + 1.0L + ]; +} +else +{ + /* erfc(x) = exp(-x^2) 1/x R(1/x^2) / S(1/x^2) + 1/128 <= 1/x < 1/8 + Peak relative error 1.9e-21 */ + immutable real[5] R = [ 0x1.b9f6d8b78e22459ep-6, 0x1.1b84686b0a4ea43ap-1, + 0x1.b8f6aebe96000c2ap+1, 0x1.cb1dbedac27c8ec2p+2, 0x1.cf885f8f572a4c14p+1 + ]; + + immutable real[6] S = [ + 0x1.87ae3cae5f65eb5ep-5, 0x1.01616f266f306d08p+0, 0x1.a4abe0411eed6c22p+2, + 0x1.eac9ce3da600abaap+3, 0x1.5752a9ac2faebbccp+3, 1.0 + ]; + + /* erf(x) = x P(x^2)/Q(x^2) + 0 <= x <= 1 + Peak relative error 7.6e-23 */ + immutable real[7] T = [ 0x1.0da01654d757888cp+20, 0x1.2eb7497bc8b4f4acp+17, + 0x1.79078c19530f72a8p+15, 0x1.4eaf2126c0b2c23p+11, 0x1.1f2ea81c9d272a2ep+8, + 0x1.59ca6e2d866e625p+2, 0x1.c188e0b67435faf4p-4 + ]; + + immutable real[7] U = [ 0x1.dde6025c395ae34ep+19, 0x1.c4bc8b6235df35aap+18, + 0x1.8465900e88b6903ap+16, 0x1.855877093959ffdp+13, 0x1.e5c44395625ee358p+9, + 0x1.6a0fed103f1c68a6p+5, 1.0 + ]; +} +} + +/** + * Complementary error function + * + * erfc(x) = 1 - erf(x), and has high relative accuracy for + * values of x far from zero. (For values near zero, use erf(x)). + * + * 1 - erf(x) = 2/ $(SQRT)(π) + * $(INTEGRAL x, $(INFINITY)) exp( - $(POWER t, 2)) dt + * + * + * For small x, erfc(x) = 1 - erf(x); otherwise rational + * approximations are computed. + * + * A special function expx2(x) is used to suppress error amplification + * in computing exp(-x^2). + */ +real erfc(real a) +{ + if (a == real.infinity) + return 0.0; + if (a == -real.infinity) + return 2.0; + + immutable x = (a < 0.0) ? -a : a; + + if (x < (isIEEEQuadruple ? 0.25 : 1.0)) + return 1.0 - erf(a); + + static if (isIEEEQuadruple) + { + if (x < 1.25) + { + real y; + final switch (cast(int)(8.0 * x)) + { + case 2: + const z = x - 0.25; + y = C13b + z * rationalPoly(z, RNr13, RDr13); + y += C13a; + break; + case 3: + const z = x - 0.375; + y = C14b + z * rationalPoly(z, RNr14, RDr14); + y += C14a; + break; + case 4: + const z = x - 0.5; + y = C15b + z * rationalPoly(z, RNr15, RDr15); + y += C15a; + break; + case 5: + const z = x - 0.625; + y = C16b + z * rationalPoly(z, RNr16, RDr16); + y += C16a; + break; + case 6: + const z = x - 0.75; + y = C17b + z * rationalPoly(z, RNr17, RDr17); + y += C17a; + break; + case 7: + const z = x - 0.875; + y = C18b + z * rationalPoly(z, RNr18, RDr18); + y += C18a; + break; + case 8: + const z = x - 1.0; + y = C19b + z * rationalPoly(z, RNr19, RDr19); + y += C19a; + break; + case 9: + const z = x - 1.125; + y = C20b + z * rationalPoly(z, RNr20, RDr20); + y += C20a; + break; + } + if (a < 0.0) + y = 2.0 - y; + return y; + } + } + + if (-a * a < -MAXLOG) + { + // underflow + if (a < 0.0) return 2.0; + else return 0.0; + } + + real y; + immutable z = expx2(a, -1); + + static if (isIEEEQuadruple) + { + y = z * erfce(x); + } + else + { + y = 1.0 / x; + if (x < 8.0) + y = z * rationalPoly(y, P, Q); + else + y = z * y * rationalPoly(y * y, R, S); + } + + if (a < 0.0) + y = 2.0 - y; + + if (y == 0.0) + { + // underflow + if (a < 0.0) return 2.0; + else return 0.0; + } + + return y; +} + + +private { +/* Exponentially scaled erfc function + exp(x^2) erfc(x) + valid for x > 1. + Use with normalDistribution and expx2. */ +static if (isIEEEQuadruple) +{ + real erfce(real x) + { + immutable z = 1.0L / (x * x); + + real p; + switch (cast(int)(8.0 / x)) + { + default: + case 0: + p = rationalPoly(z, RNr1, RDr1); + break; + case 1: + p = rationalPoly(z, RNr2, RDr2); + break; + case 2: + p = rationalPoly(z, RNr3, RDr3); + break; + case 3: + p = rationalPoly(z, RNr4, RDr4); + break; + case 4: + p = rationalPoly(z, RNr5, RDr5); + break; + case 5: + p = rationalPoly(z, RNr6, RDr6); + break; + case 6: + p = rationalPoly(z, RNr7, RDr7); + break; + case 7: + p = rationalPoly(z, RNr8, RDr8); + break; + } + return p / x; + } +} +else +{ + real erfce(real x) + { + real y = 1.0/x; + + if (x < 8.0) + { + return rationalPoly(y, P, Q); + } + else + { + return y * rationalPoly(y * y, R, S); + } + } +} +} + +/** + * Error function + * + * The integral is + * + * erf(x) = 2/ $(SQRT)(π) + * $(INTEGRAL 0, x) exp( - $(POWER t, 2)) dt + * + * The magnitude of x is limited to about 106.56 for IEEE 80-bit + * arithmetic; 1 or -1 is returned outside this range. + * + * For 0 <= |x| < 1, a rational polynomials are used; otherwise + * erf(x) = 1 - erfc(x). + * + * ACCURACY: + * Relative error: + * arithmetic domain # trials peak rms + * IEEE 0,1 50000 2.0e-19 5.7e-20 + */ +real erf(real x) +{ + if (x == 0.0) + return x; // deal with negative zero + if (x == -real.infinity) + return -1.0; + if (x == real.infinity) + return 1.0; + immutable ax = abs(x); + if (ax > 1.0L) + return 1.0L - erfc(x); + + static if (isIEEEQuadruple) + { + immutable z = x * x; + + real y; + if (ax < 0.875) + { + y = ax + ax * rationalPoly(x * x, TN1, TD1); + } + else + { + y = erfConst + rationalPoly(ax - 1.0L, TN2, TD2); + } + + if (x < 0) + y = -y; + return y; + } + else + { + real z = x * x; + return x * rationalPoly(x * x, T, U); + } +} + +@safe unittest +{ + // High resolution test points. + enum real erfc0_250 = 0.723663330078125 + 1.0279753638067014931732235184287934646022E-5; + enum real erfc0_375 = 0.5958709716796875 + 1.2118885490201676174914080878232469565953E-5; + enum real erfc0_500 = 0.4794921875 + 7.9346869534623172533461080354712635484242E-6; + enum real erfc0_625 = 0.3767547607421875 + 4.3570693945275513594941232097252997287766E-6; + enum real erfc0_750 = 0.2888336181640625 + 1.0748182422368401062165408589222625794046E-5; + enum real erfc0_875 = 0.215911865234375 + 1.3073705765341685464282101150637224028267E-5; + enum real erfc1_000 = 0.15728759765625 + 1.1609394035130658779364917390740703933002E-5; + enum real erfc1_125 = 0.111602783203125 + 8.9850951672359304215530728365232161564636E-6; + + enum real erf0_875 = (1-0.215911865234375) - 1.3073705765341685464282101150637224028267E-5; + + static bool isNaNWithPayload(real x, ulong payload) @safe pure nothrow @nogc + { + return isNaN(x) && getNaNPayload(x) == payload; + } + + assert(feqrel(erfc(0.250L), erfc0_250 )>=real.mant_dig-1); + assert(feqrel(erfc(0.375L), erfc0_375 )>=real.mant_dig-0); + assert(feqrel(erfc(0.500L), erfc0_500 )>=real.mant_dig-2); + assert(feqrel(erfc(0.625L), erfc0_625 )>=real.mant_dig-1); + assert(feqrel(erfc(0.750L), erfc0_750 )>=real.mant_dig-1); + assert(feqrel(erfc(0.875L), erfc0_875 )>=real.mant_dig-4); + assert(feqrel(erfc(1.000L), erfc1_000 )>=real.mant_dig-2); + assert(feqrel(erfc(1.125L), erfc1_125 )>=real.mant_dig-2); + assert(feqrel(erf(0.875L), erf0_875 )>=real.mant_dig-1); + // The DMC implementation of erfc() fails this next test (just). + // Test point from Mathematica 11.0. + assert(feqrel(erfc(4.1L), 6.70002765408489837272673380763418472e-9L) >= real.mant_dig-5); + + assert(isIdentical(erf(0.0),0.0)); + assert(isIdentical(erf(-0.0),-0.0)); + assert(erf(real.infinity) == 1.0); + assert(erf(-real.infinity) == -1.0); + assert(isNaNWithPayload(erf(NaN(0xDEF)), 0xDEF)); + assert(isNaNWithPayload(erfc(NaN(0xDEF)), 0xDEF)); + assert(isIdentical(erfc(real.infinity),0.0)); + assert(erfc(-real.infinity) == 2.0); + assert(erfc(0) == 1.0); +} + +/* + * Exponential of squared argument + * + * Computes y = exp(x*x) while suppressing error amplification + * that would ordinarily arise from the inexactness of the + * exponential argument x*x. + * + * If sign < 0, the result is inverted; i.e., y = exp(-x*x) . + * + * ACCURACY: + * Relative error: + * arithmetic domain # trials peak rms + * IEEE -106.566, 106.566 10^5 1.6e-19 4.4e-20 + */ + +real expx2(real x, int sign) +{ + /* + Cephes Math Library Release 2.9: June, 2000 + Copyright 2000 by Stephen L. Moshier + */ + const real M = 32_768.0; + const real MINV = 3.0517578125e-5L; + + x = abs(x); + if (sign < 0) + x = -x; + + /* Represent x as an exact multiple of M plus a residual. + M is a power of 2 chosen so that exp(m * m) does not overflow + or underflow and so that |x - m| is small. */ + real m = MINV * floor(M * x + 0.5L); + real f = x - m; + + /* x^2 = m^2 + 2mf + f^2 */ + real u = m * m; + real u1 = 2 * m * f + f * f; + + if (sign < 0) + { + u = -u; + u1 = -u1; + } + + if ((u+u1) > MAXLOG) + return real.infinity; + + /* u is exact, u1 is small. */ + return exp(u) * exp(u1); +} + + +/* +Computes the normal distribution function. + +The normal (or Gaussian, or bell-shaped) distribution is +defined as: + +normalDist(x) = 1/$(SQRT) π $(INTEGRAL -$(INFINITY), x) exp( - $(POWER t, 2)/2) dt + = 0.5 + 0.5 * erf(x/sqrt(2)) + = 0.5 * erfc(- x/sqrt(2)) + +To maintain accuracy at high values of x, use +normalDistribution(x) = 1 - normalDistribution(-x). + +Accuracy: +Within a few bits of machine resolution over the entire +range. + +References: +$(LINK http://www.netlib.org/cephes/ldoubdoc.html), +G. Marsaglia, "Evaluating the Normal Distribution", +Journal of Statistical Software 11, (July 2004). +*/ +real normalDistributionImpl(real a) +{ + real x = a * SQRT1_2; + real z = abs(x); + + if ( z < 1.0 ) + return 0.5L + 0.5L * erf(x); + else + { + real y = 0.5L * erfce(z); + /* Multiply by exp(-x^2 / 2) */ + z = expx2(a, -1); + y = y * sqrt(z); + if ( x > 0.0L ) + y = 1.0L - y; + return y; + } +} + +@safe unittest +{ +assert(fabs(normalDistributionImpl(1L) - (0.841344746068543))< 0.0000000000000005); +assert(isIdentical(normalDistributionImpl(NaN(0x325)), NaN(0x325))); +} + +/* + * Inverse of Normal distribution function + * + * Returns the argument, x, for which the area under the + * Normal probability density function (integrated from + * minus infinity to x) is equal to p. + * + * For small arguments 0 < p < exp(-2), the program computes + * z = sqrt( -2 log(p) ); then the approximation is + * x = z - log(z)/z - (1/z) P(1/z) / Q(1/z) . + * For larger arguments, x/sqrt(2 pi) = w + w^3 R(w^2)/S(w^2)) , + * where w = p - 0.5. + */ +// TODO: isIEEEQuadruple (128 bit) real implementation; not available from CEPHES. +real normalDistributionInvImpl(real p) +in { + assert(p >= 0.0L && p <= 1.0L, "Domain error"); +} +body +{ +static immutable real[8] P0 = +[ -0x1.758f4d969484bfdcp-7, 0x1.53cee17a59259dd2p-3, + -0x1.ea01e4400a9427a2p-1, 0x1.61f7504a0105341ap+1, -0x1.09475a594d0399f6p+2, + 0x1.7c59e7a0df99e3e2p+1, -0x1.87a81da52edcdf14p-1, 0x1.1fb149fd3f83600cp-7 +]; + +static immutable real[8] Q0 = +[ -0x1.64b92ae791e64bb2p-7, 0x1.7585c7d597298286p-3, + -0x1.40011be4f7591ce6p+0, 0x1.1fc067d8430a425ep+2, -0x1.21008ffb1e7ccdf2p+3, + 0x1.3d1581cf9bc12fccp+3, -0x1.53723a89fd8f083cp+2, 1.0 +]; + +static immutable real[10] P1 = +[ 0x1.20ceea49ea142f12p-13, 0x1.cbe8a7267aea80bp-7, + 0x1.79fea765aa787c48p-2, 0x1.d1f59faa1f4c4864p+1, 0x1.1c22e426a013bb96p+4, + 0x1.a8675a0c51ef3202p+5, 0x1.75782c4f83614164p+6, 0x1.7a2f3d90948f1666p+6, + 0x1.5cd116ee4c088c3ap+5, 0x1.1361e3eb6e3cc20ap+2 +]; + +static immutable real[10] Q1 = +[ 0x1.3a4ce1406cea98fap-13, 0x1.f45332623335cda2p-7, + 0x1.98f28bbd4b98db1p-2, 0x1.ec3b24f9c698091cp+1, 0x1.1cc56ecda7cf58e4p+4, + 0x1.92c6f7376bf8c058p+5, 0x1.4154c25aa47519b4p+6, 0x1.1b321d3b927849eap+6, + 0x1.403a5f5a4ce7b202p+4, 1.0 +]; + +static immutable real[8] P2 = +[ 0x1.8c124a850116a6d8p-21, 0x1.534abda3c2fb90bap-13, + 0x1.29a055ec93a4718cp-7, 0x1.6468e98aad6dd474p-3, 0x1.3dab2ef4c67a601cp+0, + 0x1.e1fb3a1e70c67464p+1, 0x1.b6cce8035ff57b02p+2, 0x1.9f4c9e749ff35f62p+1 +]; + +static immutable real[8] Q2 = +[ 0x1.af03f4fc0655e006p-21, 0x1.713192048d11fb2p-13, + 0x1.4357e5bbf5fef536p-7, 0x1.7fdac8749985d43cp-3, 0x1.4a080c813a2d8e84p+0, + 0x1.c3a4b423cdb41bdap+1, 0x1.8160694e24b5557ap+2, 1.0 +]; + +static immutable real[8] P3 = +[ -0x1.55da447ae3806168p-34, -0x1.145635641f8778a6p-24, + -0x1.abf46d6b48040128p-17, -0x1.7da550945da790fcp-11, -0x1.aa0b2a31157775fap-8, + 0x1.b11d97522eed26bcp-3, 0x1.1106d22f9ae89238p+1, 0x1.029a358e1e630f64p+1 +]; + +static immutable real[8] Q3 = +[ -0x1.74022dd5523e6f84p-34, -0x1.2cb60d61e29ee836p-24, + -0x1.d19e6ec03a85e556p-17, -0x1.9ea2a7b4422f6502p-11, -0x1.c54b1e852f107162p-8, + 0x1.e05268dd3c07989ep-3, 0x1.239c6aff14afbf82p+1, 1.0 +]; + + if (p <= 0.0L || p >= 1.0L) + { + if (p == 0.0L) + return -real.infinity; + if ( p == 1.0L ) + return real.infinity; + return real.nan; // domain error + } + int code = 1; + real y = p; + if ( y > (1.0L - EXP_2) ) + { + y = 1.0L - y; + code = 0; + } + + real x, z, y2, x0, x1; + + if ( y > EXP_2 ) + { + y = y - 0.5L; + y2 = y * y; + x = y + y * (y2 * rationalPoly( y2, P0, Q0)); + return x * SQRT2PI; + } + + x = sqrt( -2.0L * log(y) ); + x0 = x - log(x)/x; + z = 1.0L/x; + if ( x < 8.0L ) + { + x1 = z * rationalPoly( z, P1, Q1); + } + else if ( x < 32.0L ) + { + x1 = z * rationalPoly( z, P2, Q2); + } + else + { + x1 = z * rationalPoly( z, P3, Q3); + } + x = x0 - x1; + if ( code != 0 ) + { + x = -x; + } + return x; +} + + +@safe unittest +{ + // TODO: Use verified test points. + // The values below are from Excel 2003. + assert(fabs(normalDistributionInvImpl(0.001) - (-3.09023230616779))< 0.00000000000005); + assert(fabs(normalDistributionInvImpl(1e-50) - (-14.9333375347885))< 0.00000000000005); + assert(feqrel(normalDistributionInvImpl(0.999), -normalDistributionInvImpl(0.001)) > real.mant_dig-6); + + // Excel 2003 gets all the following values wrong! + assert(normalDistributionInvImpl(0.0) == -real.infinity); + assert(normalDistributionInvImpl(1.0) == real.infinity); + assert(normalDistributionInvImpl(0.5) == 0); + // (Excel 2003 returns norminv(p) = -30 for all p < 1e-200). + // The value tested here is the one the function returned in Jan 2006. + real unknown1 = normalDistributionInvImpl(1e-250L); + assert( fabs(unknown1 -(-33.79958617269L) ) < 0.00000005); +} diff --git a/libphobos/src/std/internal/math/gammafunction.d b/libphobos/src/std/internal/math/gammafunction.d new file mode 100644 index 0000000..dd20691 --- /dev/null +++ b/libphobos/src/std/internal/math/gammafunction.d @@ -0,0 +1,1834 @@ +/** + * Implementation of the gamma and beta functions, and their integrals. + * + * License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Copyright: Based on the CEPHES math library, which is + * Copyright (C) 1994 Stephen L. Moshier (moshier@world.std.com). + * Authors: Stephen L. Moshier (original C code). Conversion to D by Don Clugston + * + * +Macros: + * TABLE_SV = + * + * $0
Special Values
+ * SVH = $(TR $(TH $1) $(TH $2)) + * SV = $(TR $(TD $1) $(TD $2)) + * GAMMA = Γ + * INTEGRATE = $(BIG ∫$(SMALL $1)$2) + * POWER = $1$2 + * NAN = $(RED NAN) + */ +module std.internal.math.gammafunction; +import std.internal.math.errorfunction; +import std.math; + +pure: +nothrow: +@safe: +@nogc: + +private { + +enum real SQRT2PI = 2.50662827463100050242E0L; // sqrt(2pi) +immutable real EULERGAMMA = 0.57721_56649_01532_86060_65120_90082_40243_10421_59335_93992L; /** Euler-Mascheroni constant 0.57721566.. */ + +// Polynomial approximations for gamma and loggamma. + +immutable real[8] GammaNumeratorCoeffs = [ 1.0, + 0x1.acf42d903366539ep-1, 0x1.73a991c8475f1aeap-2, 0x1.c7e918751d6b2a92p-4, + 0x1.86d162cca32cfe86p-6, 0x1.0c378e2e6eaf7cd8p-8, 0x1.dc5c66b7d05feb54p-12, + 0x1.616457b47e448694p-15 +]; + +immutable real[9] GammaDenominatorCoeffs = [ 1.0, + 0x1.a8f9faae5d8fc8bp-2, -0x1.cb7895a6756eebdep-3, -0x1.7b9bab006d30652ap-5, + 0x1.c671af78f312082ep-6, -0x1.a11ebbfaf96252dcp-11, -0x1.447b4d2230a77ddap-10, + 0x1.ec1d45bb85e06696p-13,-0x1.d4ce24d05bd0a8e6p-17 +]; + +immutable real[9] GammaSmallCoeffs = [ 1.0, + 0x1.2788cfc6fb618f52p-1, -0x1.4fcf4026afa2f7ecp-1, -0x1.5815e8fa24d7e306p-5, + 0x1.5512320aea2ad71ap-3, -0x1.59af0fb9d82e216p-5, -0x1.3b4b61d3bfdf244ap-7, + 0x1.d9358e9d9d69fd34p-8, -0x1.38fc4bcbada775d6p-10 +]; + +immutable real[9] GammaSmallNegCoeffs = [ -1.0, + 0x1.2788cfc6fb618f54p-1, 0x1.4fcf4026afa2bc4cp-1, -0x1.5815e8fa2468fec8p-5, + -0x1.5512320baedaf4b6p-3, -0x1.59af0fa283baf07ep-5, 0x1.3b4a70de31e05942p-7, + 0x1.d9398be3bad13136p-8, 0x1.291b73ee05bcbba2p-10 +]; + +immutable real[7] logGammaStirlingCoeffs = [ + 0x1.5555555555553f98p-4, -0x1.6c16c16c07509b1p-9, 0x1.a01a012461cbf1e4p-11, + -0x1.3813089d3f9d164p-11, 0x1.b911a92555a277b8p-11, -0x1.ed0a7b4206087b22p-10, + 0x1.402523859811b308p-8 +]; + +immutable real[7] logGammaNumerator = [ + -0x1.0edd25913aaa40a2p+23, -0x1.31c6ce2e58842d1ep+24, -0x1.f015814039477c3p+23, + -0x1.74ffe40c4b184b34p+22, -0x1.0d9c6d08f9eab55p+20, -0x1.54c6b71935f1fc88p+16, + -0x1.0e761b42932b2aaep+11 +]; + +immutable real[8] logGammaDenominator = [ + -0x1.4055572d75d08c56p+24, -0x1.deeb6013998e4d76p+24, -0x1.106f7cded5dcc79ep+24, + -0x1.25e17184848c66d2p+22, -0x1.301303b99a614a0ap+19, -0x1.09e76ab41ae965p+15, + -0x1.00f95ced9e5f54eep+9, 1.0 +]; + +/* + * Helper function: Gamma function computed by Stirling's formula. + * + * Stirling's formula for the gamma function is: + * + * $(GAMMA)(x) = sqrt(2 π) xx-0.5 exp(-x) (1 + 1/x P(1/x)) + * + */ +real gammaStirling(real x) +{ + // CEPHES code Copyright 1994 by Stephen L. Moshier + + static immutable real[9] SmallStirlingCoeffs = [ + 0x1.55555555555543aap-4, 0x1.c71c71c720dd8792p-9, -0x1.5f7268f0b5907438p-9, + -0x1.e13cd410e0477de6p-13, 0x1.9b0f31643442616ep-11, 0x1.2527623a3472ae08p-14, + -0x1.37f6bc8ef8b374dep-11,-0x1.8c968886052b872ap-16, 0x1.76baa9c6d3eeddbcp-11 + ]; + + static immutable real[7] LargeStirlingCoeffs = [ 1.0L, + 8.33333333333333333333E-2L, 3.47222222222222222222E-3L, + -2.68132716049382716049E-3L, -2.29472093621399176955E-4L, + 7.84039221720066627474E-4L, 6.97281375836585777429E-5L + ]; + + real w = 1.0L/x; + real y = exp(x); + if ( x > 1024.0L ) + { + // For large x, use rational coefficients from the analytical expansion. + w = poly(w, LargeStirlingCoeffs); + // Avoid overflow in pow() + real v = pow( x, 0.5L * x - 0.25L ); + y = v * (v / y); + } + else + { + w = 1.0L + w * poly( w, SmallStirlingCoeffs); + static if (floatTraits!(real).realFormat == RealFormat.ieeeDouble) + { + // Avoid overflow in pow() for 64-bit reals + if (x > 143.0) + { + real v = pow( x, 0.5 * x - 0.25 ); + y = v * (v / y); + } + else + { + y = pow( x, x - 0.5 ) / y; + } + } + else + { + y = pow( x, x - 0.5L ) / y; + } + } + y = SQRT2PI * y * w; + return y; +} + +/* + * Helper function: Incomplete gamma function computed by Temme's expansion. + * + * This is a port of igamma_temme_large from Boost. + * + */ +real igammaTemmeLarge(real a, real x) +{ + static immutable real[][13] coef = [ + [ -0.333333333333333333333, 0.0833333333333333333333, + -0.0148148148148148148148, 0.00115740740740740740741, + 0.000352733686067019400353, -0.0001787551440329218107, + 0.39192631785224377817e-4, -0.218544851067999216147e-5, + -0.18540622107151599607e-5, 0.829671134095308600502e-6, + -0.176659527368260793044e-6, 0.670785354340149858037e-8, + 0.102618097842403080426e-7, -0.438203601845335318655e-8, + 0.914769958223679023418e-9, -0.255141939949462497669e-10, + -0.583077213255042506746e-10, 0.243619480206674162437e-10, + -0.502766928011417558909e-11 ], + [ -0.00185185185185185185185, -0.00347222222222222222222, + 0.00264550264550264550265, -0.000990226337448559670782, + 0.000205761316872427983539, -0.40187757201646090535e-6, + -0.18098550334489977837e-4, 0.764916091608111008464e-5, + -0.161209008945634460038e-5, 0.464712780280743434226e-8, + 0.137863344691572095931e-6, -0.575254560351770496402e-7, + 0.119516285997781473243e-7, -0.175432417197476476238e-10, + -0.100915437106004126275e-8, 0.416279299184258263623e-9, + -0.856390702649298063807e-10 ], + [ 0.00413359788359788359788, -0.00268132716049382716049, + 0.000771604938271604938272, 0.200938786008230452675e-5, + -0.000107366532263651605215, 0.529234488291201254164e-4, + -0.127606351886187277134e-4, 0.342357873409613807419e-7, + 0.137219573090629332056e-5, -0.629899213838005502291e-6, + 0.142806142060642417916e-6, -0.204770984219908660149e-9, + -0.140925299108675210533e-7, 0.622897408492202203356e-8, + -0.136704883966171134993e-8 ], + [ 0.000649434156378600823045, 0.000229472093621399176955, + -0.000469189494395255712128, 0.000267720632062838852962, + -0.756180167188397641073e-4, -0.239650511386729665193e-6, + 0.110826541153473023615e-4, -0.56749528269915965675e-5, + 0.142309007324358839146e-5, -0.278610802915281422406e-10, + -0.169584040919302772899e-6, 0.809946490538808236335e-7, + -0.191111684859736540607e-7 ], + [ -0.000861888290916711698605, 0.000784039221720066627474, + -0.000299072480303190179733, -0.146384525788434181781e-5, + 0.664149821546512218666e-4, -0.396836504717943466443e-4, + 0.113757269706784190981e-4, 0.250749722623753280165e-9, + -0.169541495365583060147e-5, 0.890750753220530968883e-6, + -0.229293483400080487057e-6], + [ -0.000336798553366358150309, -0.697281375836585777429e-4, + 0.000277275324495939207873, -0.000199325705161888477003, + 0.679778047793720783882e-4, 0.141906292064396701483e-6, + -0.135940481897686932785e-4, 0.801847025633420153972e-5, + -0.229148117650809517038e-5 ], + [ 0.000531307936463992223166, -0.000592166437353693882865, + 0.000270878209671804482771, 0.790235323266032787212e-6, + -0.815396936756196875093e-4, 0.561168275310624965004e-4, + -0.183291165828433755673e-4, -0.307961345060330478256e-8, + 0.346515536880360908674e-5, -0.20291327396058603727e-5, + 0.57887928631490037089e-6 ], + [ 0.000344367606892377671254, 0.517179090826059219337e-4, + -0.000334931610811422363117, 0.000281269515476323702274, + -0.000109765822446847310235, -0.127410090954844853795e-6, + 0.277444515115636441571e-4, -0.182634888057113326614e-4, + 0.578769494973505239894e-5 ], + [ -0.000652623918595309418922, 0.000839498720672087279993, + -0.000438297098541721005061, -0.696909145842055197137e-6, + 0.000166448466420675478374, -0.000127835176797692185853, + 0.462995326369130429061e-4 ], + [ -0.000596761290192746250124, -0.720489541602001055909e-4, + 0.000678230883766732836162, -0.0006401475260262758451, + 0.000277501076343287044992 ], + [ 0.00133244544948006563713, -0.0019144384985654775265, + 0.00110893691345966373396 ], + [ 0.00157972766073083495909, 0.000162516262783915816899, + -0.00206334210355432762645, 0.00213896861856890981541, + -0.00101085593912630031708 ], + [ -0.00407251211951401664727, 0.00640336283380806979482, + -0.00404101610816766177474 ] + ]; + + // avoid nans when one of the arguments is inf: + if (x == real.infinity && a != real.infinity) + return 0; + + if (x != real.infinity && a == real.infinity) + return 1; + + real sigma = (x - a) / a; + real phi = sigma - log(sigma + 1); + + real y = a * phi; + real z = sqrt(2 * phi); + if (x < a) + z = -z; + + real[13] workspace; + foreach (i; 0 .. coef.length) + workspace[i] = poly(z, coef[i]); + + real result = poly(1 / a, workspace); + result *= exp(-y) / sqrt(2 * PI * a); + if (x < a) + result = -result; + + result += erfc(sqrt(y)) / 2; + + return result; +} + +} // private + +public: +/// The maximum value of x for which gamma(x) < real.infinity. +static if (floatTraits!(real).realFormat == RealFormat.ieeeQuadruple) + enum real MAXGAMMA = 1755.5483429L; +else static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended) + enum real MAXGAMMA = 1755.5483429L; +else static if (floatTraits!(real).realFormat == RealFormat.ieeeDouble) + enum real MAXGAMMA = 171.6243769L; +else + static assert(0, "missing MAXGAMMA for other real types"); + + +/***************************************************** + * The Gamma function, $(GAMMA)(x) + * + * $(GAMMA)(x) is a generalisation of the factorial function + * to real and complex numbers. + * Like x!, $(GAMMA)(x+1) = x*$(GAMMA)(x). + * + * Mathematically, if z.re > 0 then + * $(GAMMA)(z) = $(INTEGRATE 0, ∞) $(POWER t, z-1)$(POWER e, -t) dt + * + * $(TABLE_SV + * $(SVH x, $(GAMMA)(x) ) + * $(SV $(NAN), $(NAN) ) + * $(SV ±0.0, ±∞) + * $(SV integer > 0, (x-1)! ) + * $(SV integer < 0, $(NAN) ) + * $(SV +∞, +∞ ) + * $(SV -∞, $(NAN) ) + * ) + */ +real gamma(real x) +{ +/* Based on code from the CEPHES library. + * CEPHES code Copyright 1994 by Stephen L. Moshier + * + * Arguments |x| <= 13 are reduced by recurrence and the function + * approximated by a rational function of degree 7/8 in the + * interval (2,3). Large arguments are handled by Stirling's + * formula. Large negative arguments are made positive using + * a reflection formula. + */ + + real q, z; + if (isNaN(x)) return x; + if (x == -x.infinity) return real.nan; + if ( fabs(x) > MAXGAMMA ) return real.infinity; + if (x == 0) return 1.0 / x; // +- infinity depending on sign of x, create an exception. + + q = fabs(x); + + if ( q > 13.0L ) + { + // Large arguments are handled by Stirling's + // formula. Large negative arguments are made positive using + // the reflection formula. + + if ( x < 0.0L ) + { + if (x < -1/real.epsilon) + { + // Large negatives lose all precision + return real.nan; + } + int sgngam = 1; // sign of gamma. + long intpart = cast(long)(q); + if (q == intpart) + return real.nan; // poles for all integers <0. + real p = intpart; + if ( (intpart & 1) == 0 ) + sgngam = -1; + z = q - p; + if ( z > 0.5L ) + { + p += 1.0L; + z = q - p; + } + z = q * sin( PI * z ); + z = fabs(z) * gammaStirling(q); + if ( z <= PI/real.max ) return sgngam * real.infinity; + return sgngam * PI/z; + } + else + { + return gammaStirling(x); + } + } + + // Arguments |x| <= 13 are reduced by recurrence and the function + // approximated by a rational function of degree 7/8 in the + // interval (2,3). + + z = 1.0L; + while ( x >= 3.0L ) + { + x -= 1.0L; + z *= x; + } + + while ( x < -0.03125L ) + { + z /= x; + x += 1.0L; + } + + if ( x <= 0.03125L ) + { + if ( x == 0.0L ) + return real.nan; + else + { + if ( x < 0.0L ) + { + x = -x; + return z / (x * poly( x, GammaSmallNegCoeffs )); + } + else + { + return z / (x * poly( x, GammaSmallCoeffs )); + } + } + } + + while ( x < 2.0L ) + { + z /= x; + x += 1.0L; + } + if ( x == 2.0L ) return z; + + x -= 2.0L; + return z * poly( x, GammaNumeratorCoeffs ) / poly( x, GammaDenominatorCoeffs ); +} + +@safe unittest +{ + // gamma(n) = factorial(n-1) if n is an integer. + real fact = 1.0L; + for (int i=1; fact= real.mant_dig-15); + fact *= (i*1.0L); + } + assert(gamma(0.0) == real.infinity); + assert(gamma(-0.0) == -real.infinity); + assert(isNaN(gamma(-1.0))); + assert(isNaN(gamma(-15.0))); + assert(isIdentical(gamma(NaN(0xABC)), NaN(0xABC))); + assert(gamma(real.infinity) == real.infinity); + assert(gamma(real.max) == real.infinity); + assert(isNaN(gamma(-real.infinity))); + assert(gamma(real.min_normal*real.epsilon) == real.infinity); + assert(gamma(MAXGAMMA)< real.infinity); + assert(gamma(MAXGAMMA*2) == real.infinity); + + // Test some high-precision values (50 decimal digits) + real SQRT_PI = 1.77245385090551602729816748334114518279754945612238L; + + + assert(feqrel(gamma(0.5L), SQRT_PI) >= real.mant_dig-1); + assert(feqrel(gamma(17.25L), 4.224986665692703551570937158682064589938e13L) >= real.mant_dig-4); + + assert(feqrel(gamma(1.0 / 3.0L), 2.67893853470774763365569294097467764412868937795730L) >= real.mant_dig-2); + assert(feqrel(gamma(0.25L), + 3.62560990822190831193068515586767200299516768288006L) >= real.mant_dig-1); + assert(feqrel(gamma(1.0 / 5.0L), + 4.59084371199880305320475827592915200343410999829340L) >= real.mant_dig-1); +} + +/***************************************************** + * Natural logarithm of gamma function. + * + * Returns the base e (2.718...) logarithm of the absolute + * value of the gamma function of the argument. + * + * For reals, logGamma is equivalent to log(fabs(gamma(x))). + * + * $(TABLE_SV + * $(SVH x, logGamma(x) ) + * $(SV $(NAN), $(NAN) ) + * $(SV integer <= 0, +∞ ) + * $(SV ±∞, +∞ ) + * ) + */ +real logGamma(real x) +{ + /* Based on code from the CEPHES library. + * CEPHES code Copyright 1994 by Stephen L. Moshier + * + * For arguments greater than 33, the logarithm of the gamma + * function is approximated by the logarithmic version of + * Stirling's formula using a polynomial approximation of + * degree 4. Arguments between -33 and +33 are reduced by + * recurrence to the interval [2,3] of a rational approximation. + * The cosecant reflection formula is employed for arguments + * less than -33. + */ + real q, w, z, f, nx; + + if (isNaN(x)) return x; + if (fabs(x) == x.infinity) return x.infinity; + + if ( x < -34.0L ) + { + q = -x; + w = logGamma(q); + real p = floor(q); + if ( p == q ) + return real.infinity; + int intpart = cast(int)(p); + real sgngam = 1; + if ( (intpart & 1) == 0 ) + sgngam = -1; + z = q - p; + if ( z > 0.5L ) + { + p += 1.0L; + z = p - q; + } + z = q * sin( PI * z ); + if ( z == 0.0L ) + return sgngam * real.infinity; + /* z = LOGPI - logl( z ) - w; */ + z = log( PI/z ) - w; + return z; + } + + if ( x < 13.0L ) + { + z = 1.0L; + nx = floor( x + 0.5L ); + f = x - nx; + while ( x >= 3.0L ) + { + nx -= 1.0L; + x = nx + f; + z *= x; + } + while ( x < 2.0L ) + { + if ( fabs(x) <= 0.03125 ) + { + if ( x == 0.0L ) + return real.infinity; + if ( x < 0.0L ) + { + x = -x; + q = z / (x * poly( x, GammaSmallNegCoeffs)); + } else + q = z / (x * poly( x, GammaSmallCoeffs)); + return log( fabs(q) ); + } + z /= nx + f; + nx += 1.0L; + x = nx + f; + } + z = fabs(z); + if ( x == 2.0L ) + return log(z); + x = (nx - 2.0L) + f; + real p = x * rationalPoly( x, logGammaNumerator, logGammaDenominator); + return log(z) + p; + } + + // const real MAXLGM = 1.04848146839019521116e+4928L; + // if ( x > MAXLGM ) return sgngaml * real.infinity; + + const real LOGSQRT2PI = 0.91893853320467274178L; // log( sqrt( 2*pi ) ) + + q = ( x - 0.5L ) * log(x) - x + LOGSQRT2PI; + if (x > 1.0e10L) return q; + real p = 1.0L / (x*x); + q += poly( p, logGammaStirlingCoeffs ) / x; + return q ; +} + +@safe unittest +{ + assert(isIdentical(logGamma(NaN(0xDEF)), NaN(0xDEF))); + assert(logGamma(real.infinity) == real.infinity); + assert(logGamma(-1.0) == real.infinity); + assert(logGamma(0.0) == real.infinity); + assert(logGamma(-50.0) == real.infinity); + assert(isIdentical(0.0L, logGamma(1.0L))); + assert(isIdentical(0.0L, logGamma(2.0L))); + assert(logGamma(real.min_normal*real.epsilon) == real.infinity); + assert(logGamma(-real.min_normal*real.epsilon) == real.infinity); + + // x, correct loggamma(x), correct d/dx loggamma(x). + immutable static real[] testpoints = [ + 8.0L, 8.525146484375L + 1.48766904143001655310E-5, 2.01564147795560999654E0L, + 8.99993896484375e-1L, 6.6375732421875e-2L + 5.11505711292524166220E-6L, -7.54938684259372234258E-1, + 7.31597900390625e-1L, 2.2369384765625e-1 + 5.21506341809849792422E-6L, -1.13355566660398608343E0L, + 2.31639862060546875e-1L, 1.3686676025390625L + 1.12609441752996145670E-5L, -4.56670961813812679012E0, + 1.73162841796875L, -8.88214111328125e-2L + 3.36207740803753034508E-6L, 2.33339034686200586920E-1L, + 1.23162841796875L, -9.3902587890625e-2L + 1.28765089229009648104E-5L, -2.49677345775751390414E-1L, + 7.3786976294838206464e19L, 3.301798506038663053312e21L - 1.656137564136932662487046269677E5L, + 4.57477139169563904215E1L, + 1.08420217248550443401E-19L, 4.36682586669921875e1L + 1.37082843669932230418E-5L, + -9.22337203685477580858E18L, + 1.0L, 0.0L, -5.77215664901532860607E-1L, + 2.0L, 0.0L, 4.22784335098467139393E-1L, + -0.5L, 1.2655029296875L + 9.19379714539648894580E-6L, 3.64899739785765205590E-2L, + -1.5L, 8.6004638671875e-1L + 6.28657731014510932682E-7L, 7.03156640645243187226E-1L, + -2.5L, -5.6243896484375E-2L + 1.79986700949327405470E-7, 1.10315664064524318723E0L, + -3.5L, -1.30902099609375L + 1.43111007079536392848E-5L, 1.38887092635952890151E0L + ]; + // TODO: test derivatives as well. + for (int i=0; i real.mant_dig-5); + if (testpoints[i] real.mant_dig-5); + } + } + assert(logGamma(-50.2) == log(fabs(gamma(-50.2)))); + assert(logGamma(-0.008) == log(fabs(gamma(-0.008)))); + assert(feqrel(logGamma(-38.8),log(fabs(gamma(-38.8)))) > real.mant_dig-4); + static if (real.mant_dig >= 64) // incl. 80-bit reals + assert(feqrel(logGamma(1500.0L),log(gamma(1500.0L))) > real.mant_dig-2); + else static if (real.mant_dig >= 53) // incl. 64-bit reals + assert(feqrel(logGamma(150.0L),log(gamma(150.0L))) > real.mant_dig-2); +} + + +private { +/* + * These value can be calculated like this: + * 1) Get exact real.max/min_normal/epsilon from compiler: + * writefln!"%a"(real.max/min_normal_epsilon) + * 2) Convert for Wolfram Alpha + * 0xf.fffffffffffffffp+16380 ==> (f.fffffffffffffff base 16) * 2^16380 + * 3) Calculate result on wofram alpha: + * http://www.wolframalpha.com/input/?i=ln((1.ffffffffffffffffffffffffffff+base+16)+*+2%5E16383)+in+base+2 + * 4) Convert to proper format: + * string mantissa = "1.011..."; + * write(mantissa[0 .. 2]); mantissa = mantissa[2 .. $]; + * for (size_t i = 0; i < mantissa.length/4; i++) + * { + * writef!"%x"(to!ubyte(mantissa[0 .. 4], 2)); mantissa = mantissa[4 .. $]; + * } + */ +static if (floatTraits!(real).realFormat == RealFormat.ieeeQuadruple) +{ + enum real MAXLOG = 0x1.62e42fefa39ef35793c7673007e6p+13; // log(real.max) + enum real MINLOG = -0x1.6546282207802c89d24d65e96274p+13; // log(real.min_normal*real.epsilon) = log(smallest denormal) +} +else static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended) +{ + enum real MAXLOG = 0x1.62e42fefa39ef358p+13L; // log(real.max) + enum real MINLOG = -0x1.6436716d5406e6d8p+13L; // log(real.min_normal*real.epsilon) = log(smallest denormal) +} +else static if (floatTraits!(real).realFormat == RealFormat.ieeeDouble) +{ + enum real MAXLOG = 0x1.62e42fefa39efp+9L; // log(real.max) + enum real MINLOG = -0x1.74385446d71c3p+9L; // log(real.min_normal*real.epsilon) = log(smallest denormal) +} +else + static assert(0, "missing MAXLOG and MINLOG for other real types"); + +enum real BETA_BIG = 9.223372036854775808e18L; +enum real BETA_BIGINV = 1.084202172485504434007e-19L; +} + +/** Incomplete beta integral + * + * Returns incomplete beta integral of the arguments, evaluated + * from zero to x. The regularized incomplete beta function is defined as + * + * 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. + * + * The domain of definition is 0 <= x <= 1. In this + * implementation a and b are restricted to positive values. + * The integral from x to 1 may be obtained by the symmetry + * relation + * + * betaIncompleteCompl(a, b, x ) = betaIncomplete( b, a, 1-x ) + * + * The integral is evaluated by a continued fraction expansion + * or, when b*x is small, by a power series. + */ +real betaIncomplete(real aa, real bb, real xx ) +{ + if ( !(aa>0 && bb>0) ) + { + if ( isNaN(aa) ) return aa; + if ( isNaN(bb) ) return bb; + return real.nan; // domain error + } + if (!(xx>0 && xx<1.0)) + { + if (isNaN(xx)) return xx; + if ( xx == 0.0L ) return 0.0; + if ( xx == 1.0L ) return 1.0; + return real.nan; // domain error + } + if ( (bb * xx) <= 1.0L && xx <= 0.95L) + { + return betaDistPowerSeries(aa, bb, xx); + } + real x; + real xc; // = 1 - x + + real a, b; + int flag = 0; + + /* Reverse a and b if x is greater than the mean. */ + if ( xx > (aa/(aa+bb)) ) + { + // here x > aa/(aa+bb) and (bb*x>1 or x>0.95) + flag = 1; + a = bb; + b = aa; + xc = xx; + x = 1.0L - xx; + } + else + { + a = aa; + b = bb; + xc = 1.0L - xx; + x = xx; + } + + if ( flag == 1 && (b * x) <= 1.0L && x <= 0.95L) + { + // here xx > aa/(aa+bb) and ((bb*xx>1) or xx>0.95) and (aa*(1-xx)<=1) and xx > 0.05 + return 1.0 - betaDistPowerSeries(a, b, x); // note loss of precision + } + + real w; + // Choose expansion for optimal convergence + // One is for x * (a+b+2) < (a+1), + // the other is for x * (a+b+2) > (a+1). + real y = x * (a+b-2.0L) - (a-1.0L); + if ( y < 0.0L ) + { + w = betaDistExpansion1( a, b, x ); + } + else + { + w = betaDistExpansion2( a, b, x ) / xc; + } + + /* Multiply w by the factor + a b + x (1-x) Gamma(a+b) / ( a Gamma(a) Gamma(b) ) . */ + + y = a * log(x); + real t = b * log(xc); + if ( (a+b) < MAXGAMMA && fabs(y) < MAXLOG && fabs(t) < MAXLOG ) + { + t = pow(xc,b); + t *= pow(x,a); + t /= a; + t *= w; + t *= gamma(a+b) / (gamma(a) * gamma(b)); + } + else + { + /* Resort to logarithms. */ + y += t + logGamma(a+b) - logGamma(a) - logGamma(b); + y += log(w/a); + + t = exp(y); +/+ + // There seems to be a bug in Cephes at this point. + // Problems occur for y > MAXLOG, not y < MINLOG. + if ( y < MINLOG ) + { + t = 0.0L; + } + else + { + t = exp(y); + } ++/ + } + if ( flag == 1 ) + { +/+ // CEPHES includes this code, but I think it is erroneous. + if ( t <= real.epsilon ) + { + t = 1.0L - real.epsilon; + } else ++/ + t = 1.0L - t; + } + return t; +} + +/** Inverse of incomplete beta integral + * + * Given y, the function finds x such that + * + * betaIncomplete(a, b, x) == y + * + * Newton iterations or interval halving is used. + */ +real betaIncompleteInv(real aa, real bb, real yy0 ) +{ + real a, b, y0, d, y, x, x0, x1, lgm, yp, di, dithresh, yl, yh, xt; + int i, rflg, dir, nflg; + + if (isNaN(yy0)) return yy0; + if (isNaN(aa)) return aa; + if (isNaN(bb)) return bb; + if ( yy0 <= 0.0L ) + return 0.0L; + if ( yy0 >= 1.0L ) + return 1.0L; + x0 = 0.0L; + yl = 0.0L; + x1 = 1.0L; + yh = 1.0L; + if ( aa <= 1.0L || bb <= 1.0L ) + { + dithresh = 1.0e-7L; + rflg = 0; + a = aa; + b = bb; + y0 = yy0; + x = a/(a+b); + y = betaIncomplete( a, b, x ); + nflg = 0; + goto ihalve; + } + else + { + nflg = 0; + dithresh = 1.0e-4L; + } + + // approximation to inverse function + + yp = -normalDistributionInvImpl( yy0 ); + + if ( yy0 > 0.5L ) + { + rflg = 1; + a = bb; + b = aa; + y0 = 1.0L - yy0; + yp = -yp; + } + else + { + rflg = 0; + a = aa; + b = bb; + y0 = yy0; + } + + lgm = (yp * yp - 3.0L)/6.0L; + x = 2.0L/( 1.0L/(2.0L * a-1.0L) + 1.0L/(2.0L * b - 1.0L) ); + d = yp * sqrt( x + lgm ) / x + - ( 1.0L/(2.0L * b - 1.0L) - 1.0L/(2.0L * a - 1.0L) ) + * (lgm + (5.0L/6.0L) - 2.0L/(3.0L * x)); + d = 2.0L * d; + if ( d < MINLOG ) + { + x = 1.0L; + goto under; + } + x = a/( a + b * exp(d) ); + y = betaIncomplete( a, b, x ); + yp = (y - y0)/y0; + if ( fabs(yp) < 0.2 ) + goto newt; + + /* Resort to interval halving if not close enough. */ +ihalve: + + dir = 0; + di = 0.5L; + for ( i=0; i<400; i++ ) + { + if ( i != 0 ) + { + x = x0 + di * (x1 - x0); + if ( x == 1.0L ) + { + x = 1.0L - real.epsilon; + } + if ( x == 0.0L ) + { + di = 0.5; + x = x0 + di * (x1 - x0); + if ( x == 0.0 ) + goto under; + } + y = betaIncomplete( a, b, x ); + yp = (x1 - x0)/(x1 + x0); + if ( fabs(yp) < dithresh ) + goto newt; + yp = (y-y0)/y0; + if ( fabs(yp) < dithresh ) + goto newt; + } + if ( y < y0 ) + { + x0 = x; + yl = y; + if ( dir < 0 ) + { + dir = 0; + di = 0.5L; + } else if ( dir > 3 ) + di = 1.0L - (1.0L - di) * (1.0L - di); + else if ( dir > 1 ) + di = 0.5L * di + 0.5L; + else + di = (y0 - y)/(yh - yl); + dir += 1; + if ( x0 > 0.95L ) + { + if ( rflg == 1 ) + { + rflg = 0; + a = aa; + b = bb; + y0 = yy0; + } + else + { + rflg = 1; + a = bb; + b = aa; + y0 = 1.0 - yy0; + } + x = 1.0L - x; + y = betaIncomplete( a, b, x ); + x0 = 0.0; + yl = 0.0; + x1 = 1.0; + yh = 1.0; + goto ihalve; + } + } + else + { + x1 = x; + if ( rflg == 1 && x1 < real.epsilon ) + { + x = 0.0L; + goto done; + } + yh = y; + if ( dir > 0 ) + { + dir = 0; + di = 0.5L; + } + else if ( dir < -3 ) + di = di * di; + else if ( dir < -1 ) + di = 0.5L * di; + else + di = (y - y0)/(yh - yl); + dir -= 1; + } + } + if ( x0 >= 1.0L ) + { + // partial loss of precision + x = 1.0L - real.epsilon; + goto done; + } + if ( x <= 0.0L ) + { +under: + // underflow has occurred + x = real.min_normal * real.min_normal; + goto done; + } + +newt: + + if ( nflg ) + { + goto done; + } + nflg = 1; + lgm = logGamma(a+b) - logGamma(a) - logGamma(b); + + for ( i=0; i<15; i++ ) + { + /* Compute the function at this point. */ + if ( i != 0 ) + y = betaIncomplete(a,b,x); + if ( y < yl ) + { + x = x0; + y = yl; + } + else if ( y > yh ) + { + x = x1; + y = yh; + } + else if ( y < y0 ) + { + x0 = x; + yl = y; + } + else + { + x1 = x; + yh = y; + } + if ( x == 1.0L || x == 0.0L ) + break; + /* Compute the derivative of the function at this point. */ + d = (a - 1.0L) * log(x) + (b - 1.0L) * log(1.0L - x) + lgm; + if ( d < MINLOG ) + { + goto done; + } + if ( d > MAXLOG ) + { + break; + } + d = exp(d); + /* Compute the step to the next approximation of x. */ + d = (y - y0)/d; + xt = x - d; + if ( xt <= x0 ) + { + y = (x - x0) / (x1 - x0); + xt = x0 + 0.5L * y * (x - x0); + if ( xt <= 0.0L ) + break; + } + if ( xt >= x1 ) + { + y = (x1 - x) / (x1 - x0); + xt = x1 - 0.5L * y * (x1 - x); + if ( xt >= 1.0L ) + break; + } + x = xt; + if ( fabs(d/x) < (128.0L * real.epsilon) ) + goto done; + } + /* Did not converge. */ + dithresh = 256.0L * real.epsilon; + goto ihalve; + +done: + if ( rflg ) + { + if ( x <= real.epsilon ) + x = 1.0L - real.epsilon; + else + x = 1.0L - x; + } + return x; +} + +@safe unittest { // also tested by the normal distribution + // check NaN propagation + assert(isIdentical(betaIncomplete(NaN(0xABC),2,3), NaN(0xABC))); + assert(isIdentical(betaIncomplete(7,NaN(0xABC),3), NaN(0xABC))); + assert(isIdentical(betaIncomplete(7,15,NaN(0xABC)), NaN(0xABC))); + assert(isIdentical(betaIncompleteInv(NaN(0xABC),1,17), NaN(0xABC))); + assert(isIdentical(betaIncompleteInv(2,NaN(0xABC),8), NaN(0xABC))); + assert(isIdentical(betaIncompleteInv(2,3, NaN(0xABC)), NaN(0xABC))); + + assert(isNaN(betaIncomplete(-1, 2, 3))); + + assert(betaIncomplete(1, 2, 0)==0); + assert(betaIncomplete(1, 2, 1)==1); + assert(isNaN(betaIncomplete(1, 2, 3))); + assert(betaIncompleteInv(1, 1, 0)==0); + assert(betaIncompleteInv(1, 1, 1)==1); + + // Test against Mathematica betaRegularized[z,a,b] + // These arbitrary points are chosen to give good code coverage. + assert(feqrel(betaIncomplete(8, 10, 0.2), 0.010_934_315_234_099_2L) >= real.mant_dig - 5); + assert(feqrel(betaIncomplete(2, 2.5, 0.9), 0.989_722_597_604_452_767_171_003_59L) >= real.mant_dig - 1); + static if (real.mant_dig >= 64) // incl. 80-bit reals + assert(feqrel(betaIncomplete(1000, 800, 0.5), 1.179140859734704555102808541457164E-06L) >= real.mant_dig - 13); + else + assert(feqrel(betaIncomplete(1000, 800, 0.5), 1.179140859734704555102808541457164E-06L) >= real.mant_dig - 14); + assert(feqrel(betaIncomplete(0.0001, 10000, 0.0001), 0.999978059362107134278786L) >= real.mant_dig - 18); + assert(betaIncomplete(0.01, 327726.7, 0.545113) == 1.0); + assert(feqrel(betaIncompleteInv(8, 10, 0.010_934_315_234_099_2L), 0.2L) >= real.mant_dig - 2); + assert(feqrel(betaIncomplete(0.01, 498.437, 0.0121433), 0.99999664562033077636065L) >= real.mant_dig - 1); + assert(feqrel(betaIncompleteInv(5, 10, 0.2000002972865658842), 0.229121208190918L) >= real.mant_dig - 3); + assert(feqrel(betaIncompleteInv(4, 7, 0.8000002209179505L), 0.483657360076904L) >= real.mant_dig - 3); + + // Coverage tests. I don't have correct values for these tests, but + // these values cover most of the code, so they are useful for + // regression testing. + // Extensive testing failed to increase the coverage. It seems likely that about + // half the code in this function is unnecessary; there is potential for + // significant improvement over the original CEPHES code. + static if (real.mant_dig == 64) // 80-bit reals + { + assert(betaIncompleteInv(0.01, 8e-48, 5.45464e-20) == 1-real.epsilon); + assert(betaIncompleteInv(0.01, 8e-48, 9e-26) == 1-real.epsilon); + + // Beware: a one-bit change in pow() changes almost all digits in the result! + assert(feqrel( + betaIncompleteInv(0x1.b3d151fbba0eb18p+1, 1.2265e-19, 2.44859e-18), + 0x1.c0110c8531d0952cp-1L + ) > 10); + // This next case uncovered a one-bit difference in the FYL2X instruction + // between Intel and AMD processors. This difference gets magnified by 2^^38. + // WolframAlpha crashes attempting to calculate this. + assert(feqrel(betaIncompleteInv(0x1.ff1275ae5b939bcap-41, 4.6713e18, 0.0813601), + 0x1.f97749d90c7adba8p-63L) >= real.mant_dig - 39); + real a1 = 3.40483; + assert(betaIncompleteInv(a1, 4.0640301659679627772e19L, 0.545113) == 0x1.ba8c08108aaf5d14p-109); + real b1 = 2.82847e-25; + assert(feqrel(betaIncompleteInv(0.01, b1, 9e-26), 0x1.549696104490aa9p-830L) >= real.mant_dig-10); + + // --- Problematic cases --- + // This is a situation where the series expansion fails to converge + assert( isNaN(betaIncompleteInv(0.12167, 4.0640301659679627772e19L, 0.0813601))); + // This next result is almost certainly erroneous. + // Mathematica states: "(cannot be determined by current methods)" + assert(betaIncomplete(1.16251e20, 2.18e39, 5.45e-20) == -real.infinity); + // WolframAlpha gives no result for this, though indicates that it approximately 1.0 - 1.3e-9 + assert(1 - betaIncomplete(0.01, 328222, 4.0375e-5) == 0x1.5f62926b4p-30); + } +} + + +private { +// Implementation functions + +// Continued fraction expansion #1 for incomplete beta integral +// Use when x < (a+1)/(a+b+2) +real betaDistExpansion1(real a, real b, real x ) +{ + real xk, pk, pkm1, pkm2, qk, qkm1, qkm2; + real k1, k2, k3, k4, k5, k6, k7, k8; + real r, t, ans; + int n; + + k1 = a; + k2 = a + b; + k3 = a; + k4 = a + 1.0L; + k5 = 1.0L; + k6 = b - 1.0L; + k7 = k4; + k8 = a + 2.0L; + + pkm2 = 0.0L; + qkm2 = 1.0L; + pkm1 = 1.0L; + qkm1 = 1.0L; + ans = 1.0L; + r = 1.0L; + n = 0; + const real thresh = 3.0L * real.epsilon; + do + { + xk = -( x * k1 * k2 )/( k3 * k4 ); + pk = pkm1 + pkm2 * xk; + qk = qkm1 + qkm2 * xk; + pkm2 = pkm1; + pkm1 = pk; + qkm2 = qkm1; + qkm1 = qk; + + xk = ( x * k5 * k6 )/( k7 * k8 ); + pk = pkm1 + pkm2 * xk; + qk = qkm1 + qkm2 * xk; + pkm2 = pkm1; + pkm1 = pk; + qkm2 = qkm1; + qkm1 = qk; + + if ( qk != 0.0L ) + r = pk/qk; + if ( r != 0.0L ) + { + t = fabs( (ans - r)/r ); + ans = r; + } + else + { + t = 1.0L; + } + + if ( t < thresh ) + return ans; + + k1 += 1.0L; + k2 += 1.0L; + k3 += 2.0L; + k4 += 2.0L; + k5 += 1.0L; + k6 -= 1.0L; + k7 += 2.0L; + k8 += 2.0L; + + if ( (fabs(qk) + fabs(pk)) > BETA_BIG ) + { + pkm2 *= BETA_BIGINV; + pkm1 *= BETA_BIGINV; + qkm2 *= BETA_BIGINV; + qkm1 *= BETA_BIGINV; + } + if ( (fabs(qk) < BETA_BIGINV) || (fabs(pk) < BETA_BIGINV) ) + { + pkm2 *= BETA_BIG; + pkm1 *= BETA_BIG; + qkm2 *= BETA_BIG; + qkm1 *= BETA_BIG; + } + } + while ( ++n < 400 ); +// loss of precision has occurred +// mtherr( "incbetl", PLOSS ); + return ans; +} + +// Continued fraction expansion #2 for incomplete beta integral +// Use when x > (a+1)/(a+b+2) +real betaDistExpansion2(real a, real b, real x ) +{ + real xk, pk, pkm1, pkm2, qk, qkm1, qkm2; + real k1, k2, k3, k4, k5, k6, k7, k8; + real r, t, ans, z; + + k1 = a; + k2 = b - 1.0L; + k3 = a; + k4 = a + 1.0L; + k5 = 1.0L; + k6 = a + b; + k7 = a + 1.0L; + k8 = a + 2.0L; + + pkm2 = 0.0L; + qkm2 = 1.0L; + pkm1 = 1.0L; + qkm1 = 1.0L; + z = x / (1.0L-x); + ans = 1.0L; + r = 1.0L; + int n = 0; + const real thresh = 3.0L * real.epsilon; + do + { + xk = -( z * k1 * k2 )/( k3 * k4 ); + pk = pkm1 + pkm2 * xk; + qk = qkm1 + qkm2 * xk; + pkm2 = pkm1; + pkm1 = pk; + qkm2 = qkm1; + qkm1 = qk; + + xk = ( z * k5 * k6 )/( k7 * k8 ); + pk = pkm1 + pkm2 * xk; + qk = qkm1 + qkm2 * xk; + pkm2 = pkm1; + pkm1 = pk; + qkm2 = qkm1; + qkm1 = qk; + + if ( qk != 0.0L ) + r = pk/qk; + if ( r != 0.0L ) + { + t = fabs( (ans - r)/r ); + ans = r; + } else + t = 1.0L; + + if ( t < thresh ) + return ans; + k1 += 1.0L; + k2 -= 1.0L; + k3 += 2.0L; + k4 += 2.0L; + k5 += 1.0L; + k6 += 1.0L; + k7 += 2.0L; + k8 += 2.0L; + + if ( (fabs(qk) + fabs(pk)) > BETA_BIG ) + { + pkm2 *= BETA_BIGINV; + pkm1 *= BETA_BIGINV; + qkm2 *= BETA_BIGINV; + qkm1 *= BETA_BIGINV; + } + if ( (fabs(qk) < BETA_BIGINV) || (fabs(pk) < BETA_BIGINV) ) + { + pkm2 *= BETA_BIG; + pkm1 *= BETA_BIG; + qkm2 *= BETA_BIG; + qkm1 *= BETA_BIG; + } + } while ( ++n < 400 ); +// loss of precision has occurred +//mtherr( "incbetl", PLOSS ); + return ans; +} + +/* Power series for incomplete gamma integral. + Use when b*x is small. */ +real betaDistPowerSeries(real a, real b, real x ) +{ + real ai = 1.0L / a; + real u = (1.0L - b) * x; + real v = u / (a + 1.0L); + real t1 = v; + real t = u; + real n = 2.0L; + real s = 0.0L; + real z = real.epsilon * ai; + while ( fabs(v) > z ) + { + u = (n - b) * x / n; + t *= u; + v = t / (a + n); + s += v; + n += 1.0L; + } + s += t1; + s += ai; + + u = a * log(x); + if ( (a+b) < MAXGAMMA && fabs(u) < MAXLOG ) + { + t = gamma(a+b)/(gamma(a)*gamma(b)); + s = s * t * pow(x,a); + } + else + { + t = logGamma(a+b) - logGamma(a) - logGamma(b) + u + log(s); + + if ( t < MINLOG ) + { + s = 0.0L; + } else + s = exp(t); + } + return s; +} + +} + +/*************************************** + * Incomplete gamma integral and its complement + * + * These functions are defined by + * + * gammaIncomplete = ( $(INTEGRATE 0, x) $(POWER e, -t) $(POWER t, a-1) dt )/ $(GAMMA)(a) + * + * gammaIncompleteCompl(a,x) = 1 - gammaIncomplete(a,x) + * = ($(INTEGRATE x, ∞) $(POWER e, -t) $(POWER t, a-1) dt )/ $(GAMMA)(a) + * + * In this implementation both arguments must be positive. + * The integral is evaluated by either a power series or + * continued fraction expansion, depending on the relative + * values of a and x. + */ +real gammaIncomplete(real a, real x ) +in { + assert(x >= 0); + assert(a > 0); +} +body { + /* left tail of incomplete gamma function: + * + * inf. k + * a -x - x + * x e > ---------- + * - - + * k=0 | (a+k+1) + * + */ + if (x == 0) + return 0.0L; + + if ( (x > 1.0L) && (x > a ) ) + return 1.0L - gammaIncompleteCompl(a,x); + + real ax = a * log(x) - x - logGamma(a); +/+ + if ( ax < MINLOGL ) return 0; // underflow + // { mtherr( "igaml", UNDERFLOW ); return( 0.0L ); } ++/ + ax = exp(ax); + + /* power series */ + real r = a; + real c = 1.0L; + real ans = 1.0L; + + do + { + r += 1.0L; + c *= x/r; + ans += c; + } while ( c/ans > real.epsilon ); + + return ans * ax/a; +} + +/** ditto */ +real gammaIncompleteCompl(real a, real x ) +in { + assert(x >= 0); + assert(a > 0); +} +body { + if (x == 0) + return 1.0L; + if ( (x < 1.0L) || (x < a) ) + return 1.0L - gammaIncomplete(a,x); + + // DAC (Cephes bug fix): This is necessary to avoid + // spurious nans, eg + // log(x)-x = NaN when x = real.infinity + const real MAXLOGL = 1.1356523406294143949492E4L; + if (x > MAXLOGL) + return igammaTemmeLarge(a, x); + + real ax = a * log(x) - x - logGamma(a); +//const real MINLOGL = -1.1355137111933024058873E4L; +// if ( ax < MINLOGL ) return 0; // underflow; + ax = exp(ax); + + + /* continued fraction */ + real y = 1.0L - a; + real z = x + y + 1.0L; + real c = 0.0L; + + real pk, qk, t; + + real pkm2 = 1.0L; + real qkm2 = x; + real pkm1 = x + 1.0L; + real qkm1 = z * x; + real ans = pkm1/qkm1; + + do + { + c += 1.0L; + y += 1.0L; + z += 2.0L; + real yc = y * c; + pk = pkm1 * z - pkm2 * yc; + qk = qkm1 * z - qkm2 * yc; + if ( qk != 0.0L ) + { + real r = pk/qk; + t = fabs( (ans - r)/r ); + ans = r; + } + else + { + t = 1.0L; + } + pkm2 = pkm1; + pkm1 = pk; + qkm2 = qkm1; + qkm1 = qk; + + const real BIG = 9.223372036854775808e18L; + + if ( fabs(pk) > BIG ) + { + pkm2 /= BIG; + pkm1 /= BIG; + qkm2 /= BIG; + qkm1 /= BIG; + } + } while ( t > real.epsilon ); + + return ans * ax; +} + +/** Inverse of complemented incomplete gamma integral + * + * Given a and p, the function finds x such that + * + * gammaIncompleteCompl( a, x ) = p. + * + * Starting with the approximate value x = a $(POWER t, 3), where + * t = 1 - d - normalDistributionInv(p) sqrt(d), + * and d = 1/9a, + * the routine performs up to 10 Newton iterations to find the + * root of incompleteGammaCompl(a,x) - p = 0. + */ +real gammaIncompleteComplInv(real a, real p) +in { + assert(p >= 0 && p <= 1); + assert(a>0); +} +body { + if (p == 0) return real.infinity; + + real y0 = p; + const real MAXLOGL = 1.1356523406294143949492E4L; + real x0, x1, x, yl, yh, y, d, lgm, dithresh; + int i, dir; + + /* bound the solution */ + x0 = real.max; + yl = 0.0L; + x1 = 0.0L; + yh = 1.0L; + dithresh = 4.0 * real.epsilon; + + /* approximation to inverse function */ + d = 1.0L/(9.0L*a); + y = 1.0L - d - normalDistributionInvImpl(y0) * sqrt(d); + x = a * y * y * y; + + lgm = logGamma(a); + + for ( i=0; i<10; i++ ) + { + if ( x > x0 || x < x1 ) + goto ihalve; + y = gammaIncompleteCompl(a,x); + if ( y < yl || y > yh ) + goto ihalve; + if ( y < y0 ) + { + x0 = x; + yl = y; + } + else + { + x1 = x; + yh = y; + } + /* compute the derivative of the function at this point */ + d = (a - 1.0L) * log(x0) - x0 - lgm; + if ( d < -MAXLOGL ) + goto ihalve; + d = -exp(d); + /* compute the step to the next approximation of x */ + d = (y - y0)/d; + x = x - d; + if ( i < 3 ) continue; + if ( fabs(d/x) < dithresh ) return x; + } + + /* Resort to interval halving if Newton iteration did not converge. */ +ihalve: + d = 0.0625L; + if ( x0 == real.max ) + { + if ( x <= 0.0L ) + x = 1.0L; + while ( x0 == real.max ) + { + x = (1.0L + d) * x; + y = gammaIncompleteCompl( a, x ); + if ( y < y0 ) + { + x0 = x; + yl = y; + break; + } + d = d + d; + } + } + d = 0.5L; + dir = 0; + + for ( i=0; i<400; i++ ) + { + x = x1 + d * (x0 - x1); + y = gammaIncompleteCompl( a, x ); + lgm = (x0 - x1)/(x1 + x0); + if ( fabs(lgm) < dithresh ) + break; + lgm = (y - y0)/y0; + if ( fabs(lgm) < dithresh ) + break; + if ( x <= 0.0L ) + break; + if ( y > y0 ) + { + x1 = x; + yh = y; + if ( dir < 0 ) + { + dir = 0; + d = 0.5L; + } else if ( dir > 1 ) + d = 0.5L * d + 0.5L; + else + d = (y0 - yl)/(yh - yl); + dir += 1; + } + else + { + x0 = x; + yl = y; + if ( dir > 0 ) + { + dir = 0; + d = 0.5L; + } else if ( dir < -1 ) + d = 0.5L * d; + else + d = (y0 - yl)/(yh - yl); + dir -= 1; + } + } + /+ + if ( x == 0.0L ) + mtherr( "igamil", UNDERFLOW ); + +/ + return x; +} + +@safe unittest +{ +//Values from Excel's GammaInv(1-p, x, 1) +assert(fabs(gammaIncompleteComplInv(1, 0.5) - 0.693147188044814) < 0.00000005); +assert(fabs(gammaIncompleteComplInv(12, 0.99) - 5.42818075054289) < 0.00000005); +assert(fabs(gammaIncompleteComplInv(100, 0.8) - 91.5013985848288L) < 0.000005); +assert(gammaIncomplete(1, 0)==0); +assert(gammaIncompleteCompl(1, 0)==1); +assert(gammaIncomplete(4545, real.infinity)==1); + +// Values from Excel's (1-GammaDist(x, alpha, 1, TRUE)) + +assert(fabs(1.0L-gammaIncompleteCompl(0.5, 2) - 0.954499729507309L) < 0.00000005); +assert(fabs(gammaIncomplete(0.5, 2) - 0.954499729507309L) < 0.00000005); +// Fixed Cephes bug: +assert(gammaIncompleteCompl(384, real.infinity)==0); +assert(gammaIncompleteComplInv(3, 0)==real.infinity); +// Fixed a bug that caused gammaIncompleteCompl to return a wrong value when +// x was larger than a, but not by much, and both were large: +// The value is from WolframAlpha (Gamma[100000, 100001, inf] / Gamma[100000]) +static if (real.mant_dig >= 64) // incl. 80-bit reals + assert(fabs(gammaIncompleteCompl(100000, 100001) - 0.49831792109) < 0.000000000005); +else + assert(fabs(gammaIncompleteCompl(100000, 100001) - 0.49831792109) < 0.00000005); +} + + +// DAC: These values are Bn / n for n=2,4,6,8,10,12,14. +immutable real [7] Bn_n = [ + 1.0L/(6*2), -1.0L/(30*4), 1.0L/(42*6), -1.0L/(30*8), + 5.0L/(66*10), -691.0L/(2730*12), 7.0L/(6*14) ]; + +/** Digamma function +* +* The digamma function is the logarithmic derivative of the gamma function. +* +* digamma(x) = d/dx logGamma(x) +* +* References: +* 1. Abramowitz, M., and Stegun, I. A. (1970). +* Handbook of mathematical functions. Dover, New York, +* pages 258-259, equations 6.3.6 and 6.3.18. +*/ +real digamma(real x) +{ + // Based on CEPHES, Stephen L. Moshier. + + real p, q, nz, s, w, y, z; + long i, n; + int negative; + + negative = 0; + nz = 0.0; + + if ( x <= 0.0 ) + { + negative = 1; + q = x; + p = floor(q); + if ( p == q ) + { + return real.nan; // singularity. + } + /* Remove the zeros of tan(PI x) + * by subtracting the nearest integer from x + */ + nz = q - p; + if ( nz != 0.5 ) + { + if ( nz > 0.5 ) + { + p += 1.0; + nz = q - p; + } + nz = PI/tan(PI*nz); + } + else + { + nz = 0.0; + } + x = 1.0 - x; + } + + // check for small positive integer + if ((x <= 13.0) && (x == floor(x)) ) + { + y = 0.0; + n = lrint(x); + // DAC: CEPHES bugfix. Cephes did this in reverse order, which + // created a larger roundoff error. + for (i=n-1; i>0; --i) + { + y+=1.0L/i; + } + y -= EULERGAMMA; + goto done; + } + + s = x; + w = 0.0; + while ( s < 10.0 ) + { + w += 1.0/s; + s += 1.0; + } + + if ( s < 1.0e17 ) + { + z = 1.0/(s * s); + y = z * poly(z, Bn_n); + } else + y = 0.0; + + y = log(s) - 0.5L/s - y - w; + +done: + if ( negative ) + { + y -= nz; + } + return y; +} + +@safe unittest +{ + // Exact values + assert(digamma(1.0)== -EULERGAMMA); + assert(feqrel(digamma(0.25), -PI/2 - 3* LN2 - EULERGAMMA) >= real.mant_dig-7); + assert(feqrel(digamma(1.0L/6), -PI/2 *sqrt(3.0L) - 2* LN2 -1.5*log(3.0L) - EULERGAMMA) >= real.mant_dig-7); + assert(digamma(-5.0).isNaN()); + assert(feqrel(digamma(2.5), -EULERGAMMA - 2*LN2 + 2.0 + 2.0L/3) >= real.mant_dig-9); + assert(isIdentical(digamma(NaN(0xABC)), NaN(0xABC))); + + for (int k=1; k<40; ++k) + { + real y=0; + for (int u=k; u >= 1; --u) + { + y += 1.0L/u; + } + assert(feqrel(digamma(k+1.0), -EULERGAMMA + y) >= real.mant_dig-2); + } +} + +/** Log Minus Digamma function +* +* logmdigamma(x) = log(x) - digamma(x) +* +* References: +* 1. Abramowitz, M., and Stegun, I. A. (1970). +* Handbook of mathematical functions. Dover, New York, +* pages 258-259, equations 6.3.6 and 6.3.18. +*/ +real logmdigamma(real x) +{ + if (x <= 0.0) + { + if (x == 0.0) + { + return real.infinity; + } + return real.nan; + } + + real s = x; + real w = 0.0; + while ( s < 10.0 ) + { + w += 1.0/s; + s += 1.0; + } + + real y; + if ( s < 1.0e17 ) + { + immutable real z = 1.0/(s * s); + y = z * poly(z, Bn_n); + } else + y = 0.0; + + return x == s ? y + 0.5L/s : (log(x/s) + 0.5L/s + y + w); +} + +@safe unittest +{ + assert(logmdigamma(-5.0).isNaN()); + assert(isIdentical(logmdigamma(NaN(0xABC)), NaN(0xABC))); + assert(logmdigamma(0.0) == real.infinity); + for (auto x = 0.01; x < 1.0; x += 0.1) + assert(approxEqual(digamma(x), log(x) - logmdigamma(x))); + for (auto x = 1.0; x < 15.0; x += 1.0) + assert(approxEqual(digamma(x), log(x) - logmdigamma(x))); +} + +/** Inverse of the Log Minus Digamma function + * + * Returns x such $(D log(x) - digamma(x) == y). + * + * References: + * 1. Abramowitz, M., and Stegun, I. A. (1970). + * Handbook of mathematical functions. Dover, New York, + * pages 258-259, equation 6.3.18. + * + * Authors: Ilya Yaroshenko + */ +real logmdigammaInverse(real y) +{ + import std.numeric : findRoot; + // FIXME: should be returned back to enum. + // Fix requires CTFEable `log` on non-x86 targets (check both LDC and GDC). + immutable maxY = logmdigamma(real.min_normal); + assert(maxY > 0 && maxY <= real.max); + + if (y >= maxY) + { + //lim x->0 (log(x)-digamma(x))*x == 1 + return 1 / y; + } + if (y < 0) + { + return real.nan; + } + if (y < real.min_normal) + { + //6.3.18 + return 0.5 / y; + } + if (y > 0) + { + // x/2 <= logmdigamma(1 / x) <= x, x > 0 + // calls logmdigamma ~6 times + return 1 / findRoot((real x) => logmdigamma(1 / x) - y, y, 2*y); + } + return y; //NaN +} + +@safe unittest +{ + import std.typecons; + //WolframAlpha, 22.02.2015 + immutable Tuple!(real, real)[5] testData = [ + tuple(1.0L, 0.615556766479594378978099158335549201923L), + tuple(1.0L/8, 4.15937801516894947161054974029150730555L), + tuple(1.0L/1024, 512.166612384991507850643277924243523243L), + tuple(0.000500083333325000003968249801594877323784632117L, 1000.0L), + tuple(1017.644138623741168814449776695062817947092468536L, 1.0L/1024), + ]; + foreach (test; testData) + assert(approxEqual(logmdigammaInverse(test[0]), test[1], 2e-15, 0)); + + assert(approxEqual(logmdigamma(logmdigammaInverse(1)), 1, 1e-15, 0)); + assert(approxEqual(logmdigamma(logmdigammaInverse(real.min_normal)), real.min_normal, 1e-15, 0)); + assert(approxEqual(logmdigamma(logmdigammaInverse(real.max/2)), real.max/2, 1e-15, 0)); + assert(approxEqual(logmdigammaInverse(logmdigamma(1)), 1, 1e-15, 0)); + assert(approxEqual(logmdigammaInverse(logmdigamma(real.min_normal)), real.min_normal, 1e-15, 0)); + assert(approxEqual(logmdigammaInverse(logmdigamma(real.max/2)), real.max/2, 1e-15, 0)); +} diff --git a/libphobos/src/std/internal/scopebuffer.d b/libphobos/src/std/internal/scopebuffer.d new file mode 100644 index 0000000..70a7c8d --- /dev/null +++ b/libphobos/src/std/internal/scopebuffer.d @@ -0,0 +1,398 @@ +/* + * Copyright: 2014 by Digital Mars + * License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Walter Bright + * Source: $(PHOBOSSRC std/internal/_scopebuffer.d) + */ + +module std.internal.scopebuffer; + + +//debug=ScopeBuffer; + +import core.stdc.stdlib : realloc; +import std.traits; + +/************************************** + * ScopeBuffer encapsulates using a local array as a temporary buffer. + * It is initialized with a local array that should be large enough for + * most uses. If the need exceeds that size, ScopeBuffer will reallocate + * the data using its `realloc` function. + * + * ScopeBuffer cannot contain more than `(uint.max-16)/2` elements. + * + * ScopeBuffer is an Output Range. + * + * Since ScopeBuffer may store elements of type `T` in `malloc`'d memory, + * those elements are not scanned when the GC collects. This can cause + * memory corruption. Do not use ScopeBuffer when elements of type `T` point + * to the GC heap, except when a `realloc` function is provided which supports this. + * + * Example: +--- +import core.stdc.stdio; +import std.internal.scopebuffer; +void main() +{ + char[2] buf = void; + auto textbuf = ScopeBuffer!char(buf); + scope(exit) textbuf.free(); // necessary for cleanup + + // Put characters and strings into textbuf, verify they got there + textbuf.put('a'); + textbuf.put('x'); + textbuf.put("abc"); + assert(textbuf.length == 5); + assert(textbuf[1 .. 3] == "xa"); + assert(textbuf[3] == 'b'); + + // Can shrink it + textbuf.length = 3; + assert(textbuf[0 .. textbuf.length] == "axa"); + assert(textbuf[textbuf.length - 1] == 'a'); + assert(textbuf[1 .. 3] == "xa"); + + textbuf.put('z'); + assert(textbuf[] == "axaz"); + + // Can shrink it to 0 size, and reuse same memory + textbuf.length = 0; +} +--- + * It is invalid to access ScopeBuffer's contents when ScopeBuffer goes out of scope. + * Hence, copying the contents are necessary to keep them around: +--- +import std.internal.scopebuffer; +string cat(string s1, string s2) +{ + char[10] tmpbuf = void; + auto textbuf = ScopeBuffer!char(tmpbuf); + scope(exit) textbuf.free(); + textbuf.put(s1); + textbuf.put(s2); + textbuf.put("even more"); + return textbuf[].idup; +} +--- + * ScopeBuffer is intended for high performance usages in $(D @system) and $(D @trusted) code. + * It is designed to fit into two 64 bit registers, again for high performance use. + * If used incorrectly, memory leaks and corruption can result. Be sure to use + * $(D scope(exit) textbuf.free();) for proper cleanup, and do not refer to a ScopeBuffer + * instance's contents after $(D ScopeBuffer.free()) has been called. + * + * The `realloc` parameter defaults to C's `realloc()`. Another can be supplied to override it. + * + * ScopeBuffer instances may be copied, as in: +--- +textbuf = doSomething(textbuf, args); +--- + * which can be very efficent, but these must be regarded as a move rather than a copy. + * Additionally, the code between passing and returning the instance must not throw + * exceptions, otherwise when `ScopeBuffer.free()` is called, memory may get corrupted. + */ + +@system +struct ScopeBuffer(T, alias realloc = /*core.stdc.stdlib*/.realloc) +if (isAssignable!T && + !hasElaborateDestructor!T && + !hasElaborateCopyConstructor!T && + !hasElaborateAssign!T) +{ + import core.exception : onOutOfMemoryError; + import core.stdc.string : memcpy; + + + /************************** + * Initialize with buf to use as scratch buffer space. + * Params: + * buf = Scratch buffer space, must have length that is even + * Example: + * --- + * ubyte[10] tmpbuf = void; + * auto sbuf = ScopeBuffer!ubyte(tmpbuf); + * --- + * Note: + * If buf was created by the same `realloc` passed as a parameter + * to `ScopeBuffer`, then the contents of `ScopeBuffer` can be extracted without needing + * to copy them, and `ScopeBuffer.free()` will not need to be called. + */ + this(T[] buf) + in + { + assert(!(buf.length & wasResized)); // assure even length of scratch buffer space + assert(buf.length <= uint.max); // because we cast to uint later + } + body + { + this.buf = buf.ptr; + this.bufLen = cast(uint) buf.length; + } + + @system unittest + { + ubyte[10] tmpbuf = void; + auto sbuf = ScopeBuffer!ubyte(tmpbuf); + } + + /************************** + * Releases any memory used. + * This will invalidate any references returned by the `[]` operator. + * A destructor is not used, because that would make it not POD + * (Plain Old Data) and it could not be placed in registers. + */ + void free() + { + debug(ScopeBuffer) buf[0 .. bufLen] = 0; + if (bufLen & wasResized) + realloc(buf, 0); + buf = null; + bufLen = 0; + used = 0; + } + + /************************ + * Append element c to the buffer. + * This member function makes `ScopeBuffer` an Output Range. + */ + void put(T c) + { + /* j will get enregistered, while used will not because resize() may change used + */ + const j = used; + if (j == bufLen) + { + assert(j <= (uint.max - 16) / 2); + resize(j * 2 + 16); + } + buf[j] = c; + used = j + 1; + } + + /************************ + * Append array s to the buffer. + * + * If $(D const(T)) can be converted to $(D T), then put will accept + * $(D const(T)[]) as input. It will accept a $(D T[]) otherwise. + */ + package alias CT = Select!(is(const(T) : T), const(T), T); + /// ditto + void put(CT[] s) + { + const newlen = used + s.length; + assert((cast(ulong) used + s.length) <= uint.max); + const len = bufLen; + if (newlen > len) + { + assert(len <= uint.max / 2); + resize(newlen <= len * 2 ? len * 2 : newlen); + } + buf[used .. newlen] = s[]; + used = cast(uint) newlen; + } + + /****** + * Returns: + * A slice into the temporary buffer. + * Warning: + * The result is only valid until the next `put()` or `ScopeBuffer` goes out of scope. + */ + @system inout(T)[] opSlice(size_t lower, size_t upper) inout + in + { + assert(lower <= bufLen); + assert(upper <= bufLen); + assert(lower <= upper); + } + body + { + return buf[lower .. upper]; + } + + /// ditto + @system inout(T)[] opSlice() inout + { + assert(used <= bufLen); + return buf[0 .. used]; + } + + /******* + * Returns: + * The element at index i. + */ + ref inout(T) opIndex(size_t i) inout + { + assert(i < bufLen); + return buf[i]; + } + + /*** + * Returns: + * The number of elements in the `ScopeBuffer`. + */ + @property size_t length() const + { + return used; + } + + /*** + * Used to shrink the length of the buffer, + * typically to `0` so the buffer can be reused. + * Cannot be used to extend the length of the buffer. + */ + @property void length(size_t i) + in + { + assert(i <= this.used); + } + body + { + this.used = cast(uint) i; + } + + alias opDollar = length; + + private: + T* buf; + // Using uint instead of size_t so the struct fits in 2 registers in 64 bit code + uint bufLen; + enum wasResized = 1; // this bit is set in bufLen if we control the memory + uint used; + + void resize(size_t newsize) + in + { + assert(newsize <= uint.max); + } + body + { + //writefln("%s: oldsize %s newsize %s", id, buf.length, newsize); + newsize |= wasResized; + void *newBuf = realloc((bufLen & wasResized) ? buf : null, newsize * T.sizeof); + if (!newBuf) + onOutOfMemoryError(); + if (!(bufLen & wasResized)) + { + memcpy(newBuf, buf, used * T.sizeof); + debug(ScopeBuffer) buf[0 .. bufLen] = 0; + } + buf = cast(T*) newBuf; + bufLen = cast(uint) newsize; + + /* This function is called only rarely, + * inlining results in poorer register allocation. + */ + version (DigitalMars) + /* With dmd, a fake loop will prevent inlining. + * Using a hack until a language enhancement is implemented. + */ + while (1) { break; } + } +} + +@system unittest +{ + import core.stdc.stdio; + import std.range; + + char[2] tmpbuf = void; + { + // Exercise all the lines of code except for assert(0)'s + auto textbuf = ScopeBuffer!char(tmpbuf); + scope(exit) textbuf.free(); + + static assert(isOutputRange!(ScopeBuffer!char, char)); + + textbuf.put('a'); + textbuf.put('x'); + textbuf.put("abc"); // tickle put([])'s resize + assert(textbuf.length == 5); + assert(textbuf[1 .. 3] == "xa"); + assert(textbuf[3] == 'b'); + + textbuf.length = textbuf.length - 1; + assert(textbuf[0 .. textbuf.length] == "axab"); + + textbuf.length = 3; + assert(textbuf[0 .. textbuf.length] == "axa"); + assert(textbuf[textbuf.length - 1] == 'a'); + assert(textbuf[1 .. 3] == "xa"); + + textbuf.put(cast(dchar)'z'); + assert(textbuf[] == "axaz"); + + textbuf.length = 0; // reset for reuse + assert(textbuf.length == 0); + + foreach (char c; "asdf;lasdlfaklsdjfalksdjfa;lksdjflkajsfdasdfkja;sdlfj") + { + textbuf.put(c); // tickle put(c)'s resize + } + assert(textbuf[] == "asdf;lasdlfaklsdjfalksdjfa;lksdjflkajsfdasdfkja;sdlfj"); + } // run destructor on textbuf here + +} + +@system unittest +{ + string cat(string s1, string s2) + { + char[10] tmpbuf = void; + auto textbuf = ScopeBuffer!char(tmpbuf); + scope(exit) textbuf.free(); + textbuf.put(s1); + textbuf.put(s2); + textbuf.put("even more"); + return textbuf[].idup; + } + + auto s = cat("hello", "betty"); + assert(s == "hellobettyeven more"); +} + +// const +@system unittest +{ + char[10] tmpbuf = void; + auto textbuf = ScopeBuffer!char(tmpbuf); + scope(exit) textbuf.free(); + foreach (i; 0 .. 10) textbuf.put('w'); + const csb = textbuf; + const elem = csb[3]; + const slice0 = csb[0 .. 5]; + const slice1 = csb[]; +} + +/********************************* + * Creates a `ScopeBuffer` instance using type deduction - see + * $(LREF .ScopeBuffer.this) for details. + * Params: + * tmpbuf = the initial buffer to use + * Returns: + * An instance of `ScopeBuffer`. + */ + +auto scopeBuffer(T)(T[] tmpbuf) +{ + return ScopeBuffer!T(tmpbuf); +} + +/// +@system unittest +{ + ubyte[10] tmpbuf = void; + auto sb = scopeBuffer(tmpbuf); + scope(exit) sb.free(); +} + +@system unittest +{ + ScopeBuffer!(int*) b; + int*[] s; + b.put(s); + + ScopeBuffer!char c; + string s1; + char[] s2; + c.put(s1); + c.put(s2); +} diff --git a/libphobos/src/std/internal/test/dummyrange.d b/libphobos/src/std/internal/test/dummyrange.d new file mode 100644 index 0000000..a6bce0a --- /dev/null +++ b/libphobos/src/std/internal/test/dummyrange.d @@ -0,0 +1,565 @@ +/** +For testing only. +Used with the dummy ranges for testing higher order ranges. +*/ +module std.internal.test.dummyrange; + +import std.meta; +import std.range.primitives; +import std.typecons; + +enum RangeType +{ + Input, + Forward, + Bidirectional, + Random +} + +enum Length +{ + Yes, + No +} + +enum ReturnBy +{ + Reference, + Value +} + +import std.traits : isArray; + +// Range that's useful for testing other higher order ranges, +// can be parametrized with attributes. It just dumbs down an array of +// numbers 1 .. 10. +struct DummyRange(ReturnBy _r, Length _l, RangeType _rt, T = uint[]) +if (isArray!T) +{ + private static immutable uinttestData = + [1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U]; + // These enums are so that the template params are visible outside + // this instantiation. + enum r = _r; + enum l = _l; + enum rt = _rt; + + static if (is(T == uint[])) + { + T arr = uinttestData; + } + else + { + T arr; + } + + alias RetType = ElementType!(T); + alias RetTypeNoAutoDecoding = ElementEncodingType!(T); + + void reinit() + { + // Workaround for DMD bug 4378 + static if (is(T == uint[])) + { + arr = uinttestData.dup; + } + } + + void popFront() + { + arr = arr[1..$]; + } + + @property bool empty() const + { + return arr.length == 0; + } + + static if (r == ReturnBy.Reference) + { + @property ref inout(RetType) front() inout + { + return arr[0]; + } + } + else + { + @property RetType front() const + { + return arr[0]; + } + + @property void front(RetTypeNoAutoDecoding val) + { + arr[0] = val; + } + } + + static if (rt >= RangeType.Forward) + { + @property typeof(this) save() + { + return this; + } + } + + static if (rt >= RangeType.Bidirectional) + { + void popBack() + { + arr = arr[0..$ - 1]; + } + + static if (r == ReturnBy.Reference) + { + @property ref inout(RetType) back() inout + { + return arr[$ - 1]; + } + } + else + { + @property RetType back() const + { + return arr[$ - 1]; + } + + @property void back(RetTypeNoAutoDecoding val) + { + arr[$ - 1] = val; + } + } + } + + static if (rt >= RangeType.Random) + { + static if (r == ReturnBy.Reference) + { + ref inout(RetType) opIndex(size_t index) inout + { + return arr[index]; + } + } + else + { + RetType opIndex(size_t index) const + { + return arr[index]; + } + + RetType opIndexAssign(RetTypeNoAutoDecoding val, size_t index) + { + return arr[index] = val; + } + + RetType opIndexOpAssign(string op)(RetTypeNoAutoDecoding value, size_t index) + { + mixin("return arr[index] " ~ op ~ "= value;"); + } + + RetType opIndexUnary(string op)(size_t index) + { + mixin("return " ~ op ~ "arr[index];"); + } + } + + typeof(this) opSlice(size_t lower, size_t upper) + { + auto ret = this; + ret.arr = arr[lower .. upper]; + return ret; + } + + typeof(this) opSlice() + { + return this; + } + } + + static if (l == Length.Yes) + { + @property size_t length() const + { + return arr.length; + } + + alias opDollar = length; + } +} + +enum dummyLength = 10; + +alias AllDummyRanges = AliasSeq!( + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Forward), + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Bidirectional), + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random), + DummyRange!(ReturnBy.Reference, Length.No, RangeType.Forward), + DummyRange!(ReturnBy.Reference, Length.No, RangeType.Bidirectional), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Input), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Forward), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Bidirectional), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random), + DummyRange!(ReturnBy.Value, Length.No, RangeType.Input), + DummyRange!(ReturnBy.Value, Length.No, RangeType.Forward), + DummyRange!(ReturnBy.Value, Length.No, RangeType.Bidirectional) +); + +template AllDummyRangesType(T) +{ + alias AllDummyRangesType = AliasSeq!( + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Forward, T), + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Bidirectional, T), + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random, T), + DummyRange!(ReturnBy.Reference, Length.No, RangeType.Forward, T), + DummyRange!(ReturnBy.Reference, Length.No, RangeType.Bidirectional, T), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Input, T), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Forward, T), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Bidirectional, T), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random, T), + DummyRange!(ReturnBy.Value, Length.No, RangeType.Input, T), + DummyRange!(ReturnBy.Value, Length.No, RangeType.Forward, T), + DummyRange!(ReturnBy.Value, Length.No, RangeType.Bidirectional, T) + ); +} + +/** +Tests whether forward, bidirectional and random access properties are +propagated properly from the base range(s) R to the higher order range +H. Useful in combination with DummyRange for testing several higher +order ranges. +*/ +template propagatesRangeType(H, R...) +{ + static if (allSatisfy!(isRandomAccessRange, R)) + enum bool propagatesRangeType = isRandomAccessRange!H; + else static if (allSatisfy!(isBidirectionalRange, R)) + enum bool propagatesRangeType = isBidirectionalRange!H; + else static if (allSatisfy!(isForwardRange, R)) + enum bool propagatesRangeType = isForwardRange!H; + else + enum bool propagatesRangeType = isInputRange!H; +} + +template propagatesLength(H, R...) +{ + static if (allSatisfy!(hasLength, R)) + enum bool propagatesLength = hasLength!H; + else + enum bool propagatesLength = !hasLength!H; +} + +/** +Reference type input range +*/ +class ReferenceInputRange(T) +{ + import std.array : array; + + this(Range)(Range r) if (isInputRange!Range) {_payload = array(r);} + final @property ref T front(){return _payload.front;} + final void popFront(){_payload.popFront();} + final @property bool empty(){return _payload.empty;} + protected T[] _payload; +} + +/** +Infinite input range +*/ +class ReferenceInfiniteInputRange(T) +{ + this(T first = T.init) {_val = first;} + final @property T front(){return _val;} + final void popFront(){++_val;} + enum bool empty = false; + protected T _val; +} + +/** +Reference forward range +*/ +class ReferenceForwardRange(T) : ReferenceInputRange!T +{ + this(Range)(Range r) if (isInputRange!Range) {super(r);} + final @property auto save(this This)() {return new This( _payload);} +} + +/** +Infinite forward range +*/ +class ReferenceInfiniteForwardRange(T) : ReferenceInfiniteInputRange!T +{ + this(T first = T.init) {super(first);} + final @property ReferenceInfiniteForwardRange save() + {return new ReferenceInfiniteForwardRange!T(_val);} +} + +/** +Reference bidirectional range +*/ +class ReferenceBidirectionalRange(T) : ReferenceForwardRange!T +{ + this(Range)(Range r) if (isInputRange!Range) {super(r);} + final @property ref T back(){return _payload.back;} + final void popBack(){_payload.popBack();} +} + +@safe unittest +{ + static assert(isInputRange!(ReferenceInputRange!int)); + static assert(isInputRange!(ReferenceInfiniteInputRange!int)); + + static assert(isForwardRange!(ReferenceForwardRange!int)); + static assert(isForwardRange!(ReferenceInfiniteForwardRange!int)); + + static assert(isBidirectionalRange!(ReferenceBidirectionalRange!int)); +} + +private: + +pure struct Cmp(T) +if (is(T == uint)) +{ + static auto iota(size_t low = 1, size_t high = 11) + { + import std.range : iota; + return iota(cast(uint) low, cast(uint) high); + } + + static void initialize(ref uint[] arr) + { + import std.array : array; + arr = iota().array; + } + + static bool function(uint,uint) cmp = function(uint a, uint b) { return a == b; }; + + enum dummyValue = 1337U; + enum dummyValueRslt = 1337U * 2; +} + +pure struct Cmp(T) +if (is(T == double)) +{ + import std.math : approxEqual; + + static auto iota(size_t low = 1, size_t high = 11) + { + import std.range : iota; + return iota(cast(double) low, cast(double) high, 1.0); + } + + static void initialize(ref double[] arr) + { + import std.array : array; + arr = iota().array; + } + + alias cmp = approxEqual!(double,double); + + enum dummyValue = 1337.0; + enum dummyValueRslt = 1337.0 * 2.0; +} + +struct TestFoo +{ + int a; + + bool opEquals(const ref TestFoo other) const + { + return this.a == other.a; + } + + TestFoo opBinary(string op)(TestFoo other) + { + TestFoo ret = this; + mixin("ret.a " ~ op ~ "= other.a;"); + return ret; + } + + TestFoo opOpAssign(string op)(TestFoo other) + { + mixin("this.a " ~ op ~ "= other.a;"); + return this; + } +} + +pure struct Cmp(T) +if (is(T == TestFoo)) +{ + import std.math : approxEqual; + + static auto iota(size_t low = 1, size_t high = 11) + { + import std.algorithm.iteration : map; + import std.range : iota; + return iota(cast(int) low, cast(int) high).map!(a => TestFoo(a)); + } + + static void initialize(ref TestFoo[] arr) + { + import std.array : array; + arr = iota().array; + } + + static bool function(TestFoo,TestFoo) cmp = function(TestFoo a, TestFoo b) + { + return a.a == b.a; + }; + + @property static TestFoo dummyValue() + { + return TestFoo(1337); + } + + @property static TestFoo dummyValueRslt() + { + return TestFoo(1337 * 2); + } +} + +@system unittest +{ + import std.algorithm.comparison : equal; + import std.range : iota, retro, repeat; + import std.traits : Unqual; + + static void testInputRange(T,Cmp)() + { + T it; + Cmp.initialize(it.arr); + for (size_t numRuns = 0; numRuns < 2; ++numRuns) + { + if (numRuns == 1) + { + static if (is(Unqual!(ElementType!(T)) == uint)) + { + it.reinit(); + } + + Cmp.initialize(it.arr); + } + + assert(equal!(Cmp.cmp)(it, Cmp.iota(1, 11))); + + static if (hasLength!T) + { + assert(it.length == 10); + } + + assert(!Cmp.cmp(it.front, Cmp.dummyValue)); + auto s = it.front; + it.front = Cmp.dummyValue; + assert(Cmp.cmp(it.front, Cmp.dummyValue)); + it.front = s; + + auto cmp = Cmp.iota(1,11); + + size_t jdx = 0; + while (!it.empty && !cmp.empty) + { + static if (hasLength!T) + { + assert(it.length == 10 - jdx); + } + + assert(Cmp.cmp(it.front, cmp.front)); + it.popFront(); + cmp.popFront(); + + ++jdx; + } + + assert(it.empty); + assert(cmp.empty); + } + + } + + static void testForwardRange(T,Cmp)() + { + T it; + Cmp.initialize(it.arr); + auto s = it.save(); + s.popFront(); + assert(!Cmp.cmp(s.front, it.front)); + } + + static void testBidirectionalRange(T,Cmp)() + { + T it; + Cmp.initialize(it.arr); + assert(equal!(Cmp.cmp)(it.retro, Cmp.iota().retro)); + + auto s = it.back; + assert(!Cmp.cmp(s, Cmp.dummyValue)); + it.back = Cmp.dummyValue; + assert( Cmp.cmp(it.back, Cmp.dummyValue)); + it.back = s; + } + + static void testRandomAccessRange(T,Cmp)() + { + T it; + Cmp.initialize(it.arr); + size_t idx = 0; + foreach (jt; it) + { + assert(it[idx] == jt); + + T copy = it[idx .. $]; + auto cmp = Cmp.iota(idx + 1, it.length + 1); + assert(equal!(Cmp.cmp)(copy, cmp)); + + ++idx; + } + + { + auto copy = it; + copy.arr = it.arr.dup; + for (size_t i = 0; i < copy.length; ++i) + { + copy[i] = Cmp.dummyValue; + copy[i] += Cmp.dummyValue; + } + assert(equal!(Cmp.cmp)(copy, Cmp.dummyValueRslt.repeat(copy.length))); + } + + static if (it.r == ReturnBy.Reference) + { + T copy; + copy.arr = it.arr.dup; + for (size_t i = 0; i < copy.length; ++i) + { + copy[i] = Cmp.dummyValue; + copy[i] += Cmp.dummyValue; + } + + assert(equal!(Cmp.cmp)(copy, Cmp.dummyValueRslt.repeat(copy.length))); + } + } + + import std.meta : AliasSeq; + + foreach (S; AliasSeq!(uint, double, TestFoo)) + { + foreach (T; AllDummyRangesType!(S[])) + { + testInputRange!(T,Cmp!S)(); + + static if (isForwardRange!T) + { + testForwardRange!(T,Cmp!S)(); + } + + static if (isBidirectionalRange!T) + { + testBidirectionalRange!(T,Cmp!S)(); + } + + static if (isRandomAccessRange!T) + { + testRandomAccessRange!(T,Cmp!S)(); + } + } + } +} diff --git a/libphobos/src/std/internal/test/range.d b/libphobos/src/std/internal/test/range.d new file mode 100644 index 0000000..6aa9676 --- /dev/null +++ b/libphobos/src/std/internal/test/range.d @@ -0,0 +1,25 @@ +/** +For testing only. +Contains tests related to member privacy that cannot be verified inside +std.range itself. +*/ +module std.internal.test.range; + +// Note: currently can't be @safe because RefCounted, which is used by chunks, +// isn't. +@system /*@safe*/ unittest +{ + import std.algorithm.comparison : equal; + import std.range : chunks; + + struct R + { + int state = 0; + @property bool empty() { return state >= 5; } + @property int front() { return state; } + void popFront() { state++; } + } + + auto r = R().chunks(3); + assert(r.equal!equal([[ 0, 1, 2 ], [ 3, 4 ]])); +} diff --git a/libphobos/src/std/internal/test/uda.d b/libphobos/src/std/internal/test/uda.d new file mode 100644 index 0000000..88e3a1b --- /dev/null +++ b/libphobos/src/std/internal/test/uda.d @@ -0,0 +1,16 @@ +/** +For testing only. +Provides a struct with UDA's defined in an external module. +Useful for validating behavior with member privacy. +*/ +module std.internal.test.uda; + +enum Attr; + +struct HasPrivateMembers +{ + @Attr int a; + int b; + @Attr private int c; + private int d; +} diff --git a/libphobos/src/std/internal/unicode_comp.d b/libphobos/src/std/internal/unicode_comp.d new file mode 100644 index 0000000..fa0fcb5 --- /dev/null +++ b/libphobos/src/std/internal/unicode_comp.d @@ -0,0 +1,2984 @@ +module std.internal.unicode_comp; +import std.internal.unicode_tables; + +@safe pure nothrow @nogc package(std): + +static if (size_t.sizeof == 8) +{ + //6976 bytes + enum combiningClassTrieEntries = TrieEntry!(ubyte, 8, 7, 6)([0x0, 0x20, + 0x120], [0x100, 0x400, 0x1240], [0x402030202020100, + 0x206020202020205, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x0, 0x0, 0x0, 0x20001, 0x300000000, + 0x5000400000000, 0x8000000070006, 0xb0000000a0009, 0xe0000000d000c, + 0x11000f0010000f, 0x11000f0011000f, 0x1100000011000f, + 0x11000f00120000, 0x13000000110000, 0x17001600150014, + 0x1b001a00190018, 0x1d0000001c, 0x0, 0x0, 0x1e0000, 0x0, 0x0, 0x0, + 0x2000000000001f, 0x2100000000, 0x22, 0x240023, 0x28002700260025, + 0x2a000000000029, 0x2b000000000000, 0x0, 0x0, 0x2c000000000000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2d000000000000, 0x2f0000002e0000, 0x0, 0x0, 0x3100000030, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x34003300320000, 0x0, 0x36000000000035, 0x3a003900380037, + 0x3c003b00000000, 0x3d000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x3e, 0x0, 0x0, 0x3f, 0x0, 0x0, 0x40000000000000, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x41, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x4200350000, 0x3a000000000043, 0x0, 0x0, 0x0, 0x0, 0x4400000000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x4600450000, 0x470000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6e6e6e6e6e6e6e6, + 0xe6e6e6e6e6e6e6e6, 0xdcdce8e6e6e6e6e6, 0xdcdcdcdcd8e8dcdc, + 0xcadcdcdcdccacadc, 0xdcdcdcdcdcdcdcca, 0x1010101dcdcdcdc, + 0xe6e6e6dcdcdcdc01, 0xdce6f0e6e6e6e6e6, 0xdcdce6e6e6dcdc, + 0xe6dcdcdcdce6e6e6, 0xe9eaeae9e6dcdce8, 0xe6e6e6e6e6e9eaea, + 0xe6e6e6e6e6e6e6e6, 0x0, 0x0, 0xe6e6e6e6e6000000, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6dce6e6e6e6dc00, + 0xe6e6e6e6dcdee6e6, 0xdcdcdcdcdcdce6e6, 0xe6e4dee6e6dce6e6, + 0x11100f0e0d0c0b0a, 0x1700161514131312, 0x1200dce600191800, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6e6e6e6e6e6e6e6, + 0x201f1e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f1e1d1c1b000000, + 0xe6dcdce6e6222120, 0xdce6e6dce6e6e6e6, 0x0, 0x0, 0x23, 0x0, 0x0, + 0x0, 0xe6e6000000000000, 0xe60000e6e6e6e6e6, 0xe60000e6dce6e6e6, + 0xdce6e6dc00e6, 0x0, 0x0, 0x0, 0x0, 0x2400, 0x0, 0x0, 0x0, + 0xdce6e6dce6e6dce6, 0xe6dce6dcdce6dcdc, 0xe6dce6dce6dce6e6, + 0xe6e6dc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe6e6e6e6e6000000, 0xe6dce6e6, 0x0, 0x0, 0x0, 0xe6e6000000000000, + 0xe6e6e6e6e600e6e6, 0xe6e6e600e6e6e6e6, 0xe6e6e6e6e600, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xdcdcdc00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe6dce6e600000000, 0xdcdcdce6e6e6dce6, 0xe6dce6e6e61d1c1b, + 0xe6e6e6e6dcdce6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x700000000, + 0x0, 0x90000000000, 0xe6e6dce600, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x90000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x90000000000, + 0x5b540000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x90000, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x96767, + 0x0, 0x6b6b6b6b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x7676, 0x0, 0x7a7a7a7a, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xdcdc, 0x0, 0x0, 0xdc00dc0000000000, 0xd800, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8400828100, 0x828282820000, + 0xe6e60009e6e60082, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xdc000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x700000000000000, 0x90900, 0x0, 0xdc0000000000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6e6e60000000000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x900000000, 0x0, 0x0, 0x0, + 0x900000000, 0x0, 0x0, 0x0, 0x90000, 0xe60000000000, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe400, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xdce6de00, 0x0, 0x0, 0xe600000000000000, 0xdc, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0x0, 0xe6e6e60000000000, + 0xdc0000e6e6e6e6e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x700000000, 0x0, + 0x900000000, 0x0, 0x0, 0x0, 0x0, 0xe6e6e6dce6000000, 0xe6e6e6e6, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9090000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x7000000000000, 0x0, 0x9090000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x700000000000000, 0x0, 0x0, 0x0, 0xdcdcdc0100e6e6e6, + 0xdcdcdcdce6e6dcdc, 0x1010101010100e6, 0xdc0000000001, + 0xe600000000, 0x0, 0xe6e6e6e6e6dce6e6, 0xdcd6eae6e6dce6e6, + 0xe6e6e6e6e6e6e6ca, 0xe6e6e6e6e6e6e6e6, 0xe6e6e6e6e6e6e6, 0x0, 0x0, + 0xdce6dce900000000, 0x0, 0x0, 0xe6e6e6e60101e6e6, 0xe6e6010101, + 0xe60101000000e600, 0xdcdcdcdc0101e6dc, 0xe6, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xe600000000000000, 0xe6e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x900000000000000, 0x0, 0x0, 0x0, 0x0, + 0xe6e6e6e6e6e6e6e6, 0xe6e6e6e6e6e6e6e6, 0xe6e6e6e6e6e6e6e6, + 0xe6e6e6e6e6e6e6e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe0e0dee8e4da0000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x80800, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xe600000000000000, 0xe6e6e6e600000000, + 0xe6e6e6e6e6e6, 0x0, 0x0, 0x0, 0xe600000000000000, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6e6, 0x0, 0x9000000000000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x900000000, 0x0, 0x0, 0x0, + 0xe6e6e6e6e6e6e6e6, 0xe6e6e6e6e6e6e6e6, 0xe6e6, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xdcdcdc000000, 0x0, 0x0, 0x0, 0x0, 0x9000000, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7000000, 0x0, 0x9, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe60000dce6e600e6, 0xe6e60000000000e6, 0xe600, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x9000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x90000000000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xe6e6e6e6e6e6e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xdc0000000000, 0x0, 0xe600dc0000000000, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x900000000dc01e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x70900, 0xe6e6e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x909000000, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x709000000000000, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d8d80000000000, 0xd8d8e20000000101, 0xd8d8d8, + 0xdcdcdcdcdc000000, 0xe6e6e60000dcdcdc, 0xdcdce6e6, 0x0, 0x0, 0x0, + 0xe6e6e6e60000, 0x0, 0x0, 0xe6e6e60000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); + enum composeIdxMask = (1 << 11) - 1, composeCntShift = 11; + enum compositionJumpTrieEntries = TrieEntry!(ushort, 12, 9)([0x0, 0x400], + [0x1000, 0x2000], [0x3000200010000, 0x7000600050004, + 0x7000700070008, 0xa000700090007, 0x70007000c000b, 0x7000700070007, + 0x700070007000d, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x700070007000e, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffff080208010800, + 0x281618138003ffff, 0x383308328821301b, 0x285108507841383a, + 0x8068485f185c3056, 0x3882407affff1078, 0x30a510a398903889, + 0xffff30b648ad10ab, 0xffffffffffffffff, 0x28cf18cc80bcffff, + 0x38ec08eb88da30d4, 0x290b110970fb40f3, 0x8122491919163110, + 0x393c4134ffff1132, 0x3960115e994b4143, 0xffff317351691167, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffff1979, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffff217cffffffff, + 0x984118209810980, 0xffff2185ffffffff, 0x989ffffffffffff, + 0xffffffffffffffff, 0xffff0991198e218a, 0xffffffffffff0992, + 0xffffffffffff2193, 0xffff2197ffffffff, 0x99f119d099c099b, + 0xffff21a0ffffffff, 0x9a4ffffffffffff, 0xffffffffffffffff, + 0xffff09ac19a921a5, 0xffffffffffff09ad, 0xffffffffffff21ae, + 0x21b621b2ffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x11bc11baffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffff11c011be, 0xffffffffffffffff, + 0xffffffffffffffff, 0x9c309c2ffffffff, 0xffffffffffffffff, + 0xffffffff09c509c4, 0xffffffffffffffff, 0x9c909c809c709c6, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x9caffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffff29d029cb, 0xffffffffffffffff, + 0xffffffffffffffff, 0x29d5ffffffffffff, 0xffffffffffff29da, + 0x9dfffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x9e109e0ffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x9e309e2ffffffff, 0xffffffff09e509e4, + 0x9e709e6ffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffff09e8ffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffff39e9ffff, + 0x29f4ffff21f0ffff, 0xffffffff39f9ffff, 0x2200ffffffffffff, + 0xffffffff0a04ffff, 0xffffffff3205ffff, 0xffffffff2a0bffff, + 0xffff0a11ffff0a10, 0xffffffff4212ffff, 0x321effff221affff, + 0xffffffff4224ffff, 0x222cffffffffffff, 0xffffffff1230ffff, + 0xffffffff4232ffff, 0x1a431a40323affff, 0xffff0a46ffffffff, + 0xffff1247ffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffff0a49ffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xa4cffffffff124a, 0xa5212501a4dffff, + 0xffff0a57ffff2253, 0xffff0a58ffffffff, 0x2259ffffffffffff, + 0xa5dffffffffffff, 0xa5effffffffffff, 0xffffffff0a5fffff, + 0xa62ffffffff1260, 0xa6812661a63ffff, 0xffff0a6dffff2269, + 0xffff0a6effffffff, 0x226fffffffffffff, 0xa73ffffffffffff, + 0xa74ffffffffffff, 0xffffffff0a75ffff, 0xffffffffffffffff, + 0xffff0a76ffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffff0a780a77, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffff0a7a0a79, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffff0a7c0a7b, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x1a7dffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffff0a81ffff0a80, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffff0a82ffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffff0a83ffffffff, 0xffffffff0a84ffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffff0a85, 0xffffffffffffffff, 0xa87ffffffff0a86, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x1288ffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x1a8affffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffff0a8dffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xa90128effffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffff0a91ffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xa92ffffffffffff, 0xffffffffffffffff, + 0xffff1a93ffffffff, 0xffff0a96ffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xa991297ffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffff1a9affff, 0xffffffffffff0a9d, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffff0a9effff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xaa0ffff0a9fffff, 0xaa2ffff0aa1ffff, + 0xffffffff0aa3ffff, 0xffffffff0aa4ffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffff0aa5ffffffff, + 0xaa80aa7ffff0aa6, 0xffff0aa9ffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xaab0aaaffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xaad0aacffffffff, + 0xffffffffffffffff, 0xaaf0aaeffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffff12b212b0, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffff0ab50ab4, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffff0ab70ab6, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xac10ac022bc22b8, + 0xac50ac40ac30ac2, 0xacf0ace22ca22c6, 0xad30ad20ad10ad0, + 0xffffffff12d612d4, 0xffffffffffffffff, 0xffffffff12da12d8, + 0xffffffffffffffff, 0xae50ae422e022dc, 0xae90ae80ae70ae6, + 0xaf30af222ee22ea, 0xaf70af60af50af4, 0xffffffff1afb1af8, + 0xffffffffffffffff, 0xffffffff1b011afe, 0xffffffffffffffff, + 0xffffffff13061304, 0xffffffffffffffff, 0xffffffff130a1308, + 0xffffffffffffffff, 0xffffffff1b0f1b0c, 0xffffffffffffffff, + 0xffffffff1b12ffff, 0xffffffffffffffff, 0xb1e0b1d23192315, + 0xb220b210b200b1f, 0xb2c0b2b23272323, 0xb300b2f0b2e0b2d, + 0xffffffffffff0b31, 0xffffffffffff0b32, 0xffffffffffffffff, + 0xffffffffffff0b33, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffff0b34ffffffff, + 0xffffffffffffffff, 0x1b35ffffffffffff, 0xffffffffffffffff, + 0xffff0b38ffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffff0b39ffffffff, 0xffffffffffffffff, 0xffff1b3affffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffff0b3effff0b3d, 0xffffffffffff0b3f, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffff0b41ffff0b40, + 0xffffffffffff0b42, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xb43ffffffffffff, + 0xffffffffffffffff, 0xb45ffffffff0b44, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xb46ffffffffffff, 0xffffffff0b47ffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffff0b48, + 0xb49ffffffffffff, 0xffffffff0b4affff, 0xffffffffffff0b4b, + 0xffffffff0b4cffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffff0b4dffff, + 0xffffffff0b4f0b4e, 0xffffffffffffffff, 0xffffffffffffffff, + 0xb510b50ffffffff, 0xb530b52ffffffff, 0xb550b54ffffffff, + 0xffffffff0b570b56, 0xb590b58ffffffff, 0xb5b0b5affffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffff0b5d0b5cffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffff0b5effffffff, 0xffffffffffffffff, 0xb61ffff0b600b5f, + 0xffffffffffffffff, 0xb630b62ffffffff, 0xffffffff0b650b64, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffff0b66ffffffff, 0xb67ffffffffffff, 0xb69ffff0b68ffff, + 0xb6bffff0b6affff, 0xb6dffff0b6cffff, 0xb6fffff0b6effff, + 0xb71ffff0b70ffff, 0xffffffff0b72ffff, 0xffff0b74ffff0b73, + 0xffffffffffff0b75, 0x1376ffffffffffff, 0xffff1378ffffffff, + 0xffffffff137affff, 0x137effffffff137c, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffff0b80ffff, 0xffffffffffffffff, + 0xffff0b81ffffffff, 0xb82ffffffffffff, 0xb84ffff0b83ffff, + 0xb86ffff0b85ffff, 0xb88ffff0b87ffff, 0xb8affff0b89ffff, + 0xb8cffff0b8bffff, 0xffffffff0b8dffff, 0xffff0b8fffff0b8e, + 0xffffffffffff0b90, 0x1391ffffffffffff, 0xffff1393ffffffff, + 0xffffffff1395ffff, 0x1399ffffffff1397, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xb9bffffffffffff, 0xffff0b9e0b9d0b9c, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffff0b9fffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xba1ffff0ba0ffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffff0ba2ffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffff0ba40ba3ffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff]); + @property immutable(CompEntry[]) compositionTable() + { + alias CE = CompEntry; + static immutable CE[] t = [ + CE(0x00338, 0x0226e), CE(0x00338, 0x02260), CE(0x00338, 0x0226f), + CE(0x00300, 0x000c0), CE(0x00301, 0x000c1), CE(0x00302, 0x000c2), + CE(0x00303, 0x000c3), CE(0x00304, 0x00100), CE(0x00306, 0x00102), + CE(0x00307, 0x00226), CE(0x00308, 0x000c4), CE(0x00309, 0x01ea2), + CE(0x0030a, 0x000c5), CE(0x0030c, 0x001cd), CE(0x0030f, 0x00200), + CE(0x00311, 0x00202), CE(0x00323, 0x01ea0), CE(0x00325, 0x01e00), + CE(0x00328, 0x00104), CE(0x00307, 0x01e02), CE(0x00323, 0x01e04), + CE(0x00331, 0x01e06), CE(0x00301, 0x00106), CE(0x00302, 0x00108), + CE(0x00307, 0x0010a), CE(0x0030c, 0x0010c), CE(0x00327, 0x000c7), + CE(0x00307, 0x01e0a), CE(0x0030c, 0x0010e), CE(0x00323, 0x01e0c), + CE(0x00327, 0x01e10), CE(0x0032d, 0x01e12), CE(0x00331, 0x01e0e), + CE(0x00300, 0x000c8), CE(0x00301, 0x000c9), CE(0x00302, 0x000ca), + CE(0x00303, 0x01ebc), CE(0x00304, 0x00112), CE(0x00306, 0x00114), + CE(0x00307, 0x00116), CE(0x00308, 0x000cb), CE(0x00309, 0x01eba), + CE(0x0030c, 0x0011a), CE(0x0030f, 0x00204), CE(0x00311, 0x00206), + CE(0x00323, 0x01eb8), CE(0x00327, 0x00228), CE(0x00328, 0x00118), + CE(0x0032d, 0x01e18), CE(0x00330, 0x01e1a), CE(0x00307, 0x01e1e), + CE(0x00301, 0x001f4), CE(0x00302, 0x0011c), CE(0x00304, 0x01e20), + CE(0x00306, 0x0011e), CE(0x00307, 0x00120), CE(0x0030c, 0x001e6), + CE(0x00327, 0x00122), CE(0x00302, 0x00124), CE(0x00307, 0x01e22), + CE(0x00308, 0x01e26), CE(0x0030c, 0x0021e), CE(0x00323, 0x01e24), + CE(0x00327, 0x01e28), CE(0x0032e, 0x01e2a), CE(0x00300, 0x000cc), + CE(0x00301, 0x000cd), CE(0x00302, 0x000ce), CE(0x00303, 0x00128), + CE(0x00304, 0x0012a), CE(0x00306, 0x0012c), CE(0x00307, 0x00130), + CE(0x00308, 0x000cf), CE(0x00309, 0x01ec8), CE(0x0030c, 0x001cf), + CE(0x0030f, 0x00208), CE(0x00311, 0x0020a), CE(0x00323, 0x01eca), + CE(0x00328, 0x0012e), CE(0x00330, 0x01e2c), CE(0x00302, 0x00134), + CE(0x00301, 0x01e30), CE(0x0030c, 0x001e8), CE(0x00323, 0x01e32), + CE(0x00327, 0x00136), CE(0x00331, 0x01e34), CE(0x00301, 0x00139), + CE(0x0030c, 0x0013d), CE(0x00323, 0x01e36), CE(0x00327, 0x0013b), + CE(0x0032d, 0x01e3c), CE(0x00331, 0x01e3a), CE(0x00301, 0x01e3e), + CE(0x00307, 0x01e40), CE(0x00323, 0x01e42), CE(0x00300, 0x001f8), + CE(0x00301, 0x00143), CE(0x00303, 0x000d1), CE(0x00307, 0x01e44), + CE(0x0030c, 0x00147), CE(0x00323, 0x01e46), CE(0x00327, 0x00145), + CE(0x0032d, 0x01e4a), CE(0x00331, 0x01e48), CE(0x00300, 0x000d2), + CE(0x00301, 0x000d3), CE(0x00302, 0x000d4), CE(0x00303, 0x000d5), + CE(0x00304, 0x0014c), CE(0x00306, 0x0014e), CE(0x00307, 0x0022e), + CE(0x00308, 0x000d6), CE(0x00309, 0x01ece), CE(0x0030b, 0x00150), + CE(0x0030c, 0x001d1), CE(0x0030f, 0x0020c), CE(0x00311, 0x0020e), + CE(0x0031b, 0x001a0), CE(0x00323, 0x01ecc), CE(0x00328, 0x001ea), + CE(0x00301, 0x01e54), CE(0x00307, 0x01e56), CE(0x00301, 0x00154), + CE(0x00307, 0x01e58), CE(0x0030c, 0x00158), CE(0x0030f, 0x00210), + CE(0x00311, 0x00212), CE(0x00323, 0x01e5a), CE(0x00327, 0x00156), + CE(0x00331, 0x01e5e), CE(0x00301, 0x0015a), CE(0x00302, 0x0015c), + CE(0x00307, 0x01e60), CE(0x0030c, 0x00160), CE(0x00323, 0x01e62), + CE(0x00326, 0x00218), CE(0x00327, 0x0015e), CE(0x00307, 0x01e6a), + CE(0x0030c, 0x00164), CE(0x00323, 0x01e6c), CE(0x00326, 0x0021a), + CE(0x00327, 0x00162), CE(0x0032d, 0x01e70), CE(0x00331, 0x01e6e), + CE(0x00300, 0x000d9), CE(0x00301, 0x000da), CE(0x00302, 0x000db), + CE(0x00303, 0x00168), CE(0x00304, 0x0016a), CE(0x00306, 0x0016c), + CE(0x00308, 0x000dc), CE(0x00309, 0x01ee6), CE(0x0030a, 0x0016e), + CE(0x0030b, 0x00170), CE(0x0030c, 0x001d3), CE(0x0030f, 0x00214), + CE(0x00311, 0x00216), CE(0x0031b, 0x001af), CE(0x00323, 0x01ee4), + CE(0x00324, 0x01e72), CE(0x00328, 0x00172), CE(0x0032d, 0x01e76), + CE(0x00330, 0x01e74), CE(0x00303, 0x01e7c), CE(0x00323, 0x01e7e), + CE(0x00300, 0x01e80), CE(0x00301, 0x01e82), CE(0x00302, 0x00174), + CE(0x00307, 0x01e86), CE(0x00308, 0x01e84), CE(0x00323, 0x01e88), + CE(0x00307, 0x01e8a), CE(0x00308, 0x01e8c), CE(0x00300, 0x01ef2), + CE(0x00301, 0x000dd), CE(0x00302, 0x00176), CE(0x00303, 0x01ef8), + CE(0x00304, 0x00232), CE(0x00307, 0x01e8e), CE(0x00308, 0x00178), + CE(0x00309, 0x01ef6), CE(0x00323, 0x01ef4), CE(0x00301, 0x00179), + CE(0x00302, 0x01e90), CE(0x00307, 0x0017b), CE(0x0030c, 0x0017d), + CE(0x00323, 0x01e92), CE(0x00331, 0x01e94), CE(0x00300, 0x000e0), + CE(0x00301, 0x000e1), CE(0x00302, 0x000e2), CE(0x00303, 0x000e3), + CE(0x00304, 0x00101), CE(0x00306, 0x00103), CE(0x00307, 0x00227), + CE(0x00308, 0x000e4), CE(0x00309, 0x01ea3), CE(0x0030a, 0x000e5), + CE(0x0030c, 0x001ce), CE(0x0030f, 0x00201), CE(0x00311, 0x00203), + CE(0x00323, 0x01ea1), CE(0x00325, 0x01e01), CE(0x00328, 0x00105), + CE(0x00307, 0x01e03), CE(0x00323, 0x01e05), CE(0x00331, 0x01e07), + CE(0x00301, 0x00107), CE(0x00302, 0x00109), CE(0x00307, 0x0010b), + CE(0x0030c, 0x0010d), CE(0x00327, 0x000e7), CE(0x00307, 0x01e0b), + CE(0x0030c, 0x0010f), CE(0x00323, 0x01e0d), CE(0x00327, 0x01e11), + CE(0x0032d, 0x01e13), CE(0x00331, 0x01e0f), CE(0x00300, 0x000e8), + CE(0x00301, 0x000e9), CE(0x00302, 0x000ea), CE(0x00303, 0x01ebd), + CE(0x00304, 0x00113), CE(0x00306, 0x00115), CE(0x00307, 0x00117), + CE(0x00308, 0x000eb), CE(0x00309, 0x01ebb), CE(0x0030c, 0x0011b), + CE(0x0030f, 0x00205), CE(0x00311, 0x00207), CE(0x00323, 0x01eb9), + CE(0x00327, 0x00229), CE(0x00328, 0x00119), CE(0x0032d, 0x01e19), + CE(0x00330, 0x01e1b), CE(0x00307, 0x01e1f), CE(0x00301, 0x001f5), + CE(0x00302, 0x0011d), CE(0x00304, 0x01e21), CE(0x00306, 0x0011f), + CE(0x00307, 0x00121), CE(0x0030c, 0x001e7), CE(0x00327, 0x00123), + CE(0x00302, 0x00125), CE(0x00307, 0x01e23), CE(0x00308, 0x01e27), + CE(0x0030c, 0x0021f), CE(0x00323, 0x01e25), CE(0x00327, 0x01e29), + CE(0x0032e, 0x01e2b), CE(0x00331, 0x01e96), CE(0x00300, 0x000ec), + CE(0x00301, 0x000ed), CE(0x00302, 0x000ee), CE(0x00303, 0x00129), + CE(0x00304, 0x0012b), CE(0x00306, 0x0012d), CE(0x00308, 0x000ef), + CE(0x00309, 0x01ec9), CE(0x0030c, 0x001d0), CE(0x0030f, 0x00209), + CE(0x00311, 0x0020b), CE(0x00323, 0x01ecb), CE(0x00328, 0x0012f), + CE(0x00330, 0x01e2d), CE(0x00302, 0x00135), CE(0x0030c, 0x001f0), + CE(0x00301, 0x01e31), CE(0x0030c, 0x001e9), CE(0x00323, 0x01e33), + CE(0x00327, 0x00137), CE(0x00331, 0x01e35), CE(0x00301, 0x0013a), + CE(0x0030c, 0x0013e), CE(0x00323, 0x01e37), CE(0x00327, 0x0013c), + CE(0x0032d, 0x01e3d), CE(0x00331, 0x01e3b), CE(0x00301, 0x01e3f), + CE(0x00307, 0x01e41), CE(0x00323, 0x01e43), CE(0x00300, 0x001f9), + CE(0x00301, 0x00144), CE(0x00303, 0x000f1), CE(0x00307, 0x01e45), + CE(0x0030c, 0x00148), CE(0x00323, 0x01e47), CE(0x00327, 0x00146), + CE(0x0032d, 0x01e4b), CE(0x00331, 0x01e49), CE(0x00300, 0x000f2), + CE(0x00301, 0x000f3), CE(0x00302, 0x000f4), CE(0x00303, 0x000f5), + CE(0x00304, 0x0014d), CE(0x00306, 0x0014f), CE(0x00307, 0x0022f), + CE(0x00308, 0x000f6), CE(0x00309, 0x01ecf), CE(0x0030b, 0x00151), + CE(0x0030c, 0x001d2), CE(0x0030f, 0x0020d), CE(0x00311, 0x0020f), + CE(0x0031b, 0x001a1), CE(0x00323, 0x01ecd), CE(0x00328, 0x001eb), + CE(0x00301, 0x01e55), CE(0x00307, 0x01e57), CE(0x00301, 0x00155), + CE(0x00307, 0x01e59), CE(0x0030c, 0x00159), CE(0x0030f, 0x00211), + CE(0x00311, 0x00213), CE(0x00323, 0x01e5b), CE(0x00327, 0x00157), + CE(0x00331, 0x01e5f), CE(0x00301, 0x0015b), CE(0x00302, 0x0015d), + CE(0x00307, 0x01e61), CE(0x0030c, 0x00161), CE(0x00323, 0x01e63), + CE(0x00326, 0x00219), CE(0x00327, 0x0015f), CE(0x00307, 0x01e6b), + CE(0x00308, 0x01e97), CE(0x0030c, 0x00165), CE(0x00323, 0x01e6d), + CE(0x00326, 0x0021b), CE(0x00327, 0x00163), CE(0x0032d, 0x01e71), + CE(0x00331, 0x01e6f), CE(0x00300, 0x000f9), CE(0x00301, 0x000fa), + CE(0x00302, 0x000fb), CE(0x00303, 0x00169), CE(0x00304, 0x0016b), + CE(0x00306, 0x0016d), CE(0x00308, 0x000fc), CE(0x00309, 0x01ee7), + CE(0x0030a, 0x0016f), CE(0x0030b, 0x00171), CE(0x0030c, 0x001d4), + CE(0x0030f, 0x00215), CE(0x00311, 0x00217), CE(0x0031b, 0x001b0), + CE(0x00323, 0x01ee5), CE(0x00324, 0x01e73), CE(0x00328, 0x00173), + CE(0x0032d, 0x01e77), CE(0x00330, 0x01e75), CE(0x00303, 0x01e7d), + CE(0x00323, 0x01e7f), CE(0x00300, 0x01e81), CE(0x00301, 0x01e83), + CE(0x00302, 0x00175), CE(0x00307, 0x01e87), CE(0x00308, 0x01e85), + CE(0x0030a, 0x01e98), CE(0x00323, 0x01e89), CE(0x00307, 0x01e8b), + CE(0x00308, 0x01e8d), CE(0x00300, 0x01ef3), CE(0x00301, 0x000fd), + CE(0x00302, 0x00177), CE(0x00303, 0x01ef9), CE(0x00304, 0x00233), + CE(0x00307, 0x01e8f), CE(0x00308, 0x000ff), CE(0x00309, 0x01ef7), + CE(0x0030a, 0x01e99), CE(0x00323, 0x01ef5), CE(0x00301, 0x0017a), + CE(0x00302, 0x01e91), CE(0x00307, 0x0017c), CE(0x0030c, 0x0017e), + CE(0x00323, 0x01e93), CE(0x00331, 0x01e95), CE(0x00300, 0x01fed), + CE(0x00301, 0x00385), CE(0x00342, 0x01fc1), CE(0x00300, 0x01ea6), + CE(0x00301, 0x01ea4), CE(0x00303, 0x01eaa), CE(0x00309, 0x01ea8), + CE(0x00304, 0x001de), CE(0x00301, 0x001fa), CE(0x00301, 0x001fc), + CE(0x00304, 0x001e2), CE(0x00301, 0x01e08), CE(0x00300, 0x01ec0), + CE(0x00301, 0x01ebe), CE(0x00303, 0x01ec4), CE(0x00309, 0x01ec2), + CE(0x00301, 0x01e2e), CE(0x00300, 0x01ed2), CE(0x00301, 0x01ed0), + CE(0x00303, 0x01ed6), CE(0x00309, 0x01ed4), CE(0x00301, 0x01e4c), + CE(0x00304, 0x0022c), CE(0x00308, 0x01e4e), CE(0x00304, 0x0022a), + CE(0x00301, 0x001fe), CE(0x00300, 0x001db), CE(0x00301, 0x001d7), + CE(0x00304, 0x001d5), CE(0x0030c, 0x001d9), CE(0x00300, 0x01ea7), + CE(0x00301, 0x01ea5), CE(0x00303, 0x01eab), CE(0x00309, 0x01ea9), + CE(0x00304, 0x001df), CE(0x00301, 0x001fb), CE(0x00301, 0x001fd), + CE(0x00304, 0x001e3), CE(0x00301, 0x01e09), CE(0x00300, 0x01ec1), + CE(0x00301, 0x01ebf), CE(0x00303, 0x01ec5), CE(0x00309, 0x01ec3), + CE(0x00301, 0x01e2f), CE(0x00300, 0x01ed3), CE(0x00301, 0x01ed1), + CE(0x00303, 0x01ed7), CE(0x00309, 0x01ed5), CE(0x00301, 0x01e4d), + CE(0x00304, 0x0022d), CE(0x00308, 0x01e4f), CE(0x00304, 0x0022b), + CE(0x00301, 0x001ff), CE(0x00300, 0x001dc), CE(0x00301, 0x001d8), + CE(0x00304, 0x001d6), CE(0x0030c, 0x001da), CE(0x00300, 0x01eb0), + CE(0x00301, 0x01eae), CE(0x00303, 0x01eb4), CE(0x00309, 0x01eb2), + CE(0x00300, 0x01eb1), CE(0x00301, 0x01eaf), CE(0x00303, 0x01eb5), + CE(0x00309, 0x01eb3), CE(0x00300, 0x01e14), CE(0x00301, 0x01e16), + CE(0x00300, 0x01e15), CE(0x00301, 0x01e17), CE(0x00300, 0x01e50), + CE(0x00301, 0x01e52), CE(0x00300, 0x01e51), CE(0x00301, 0x01e53), + CE(0x00307, 0x01e64), CE(0x00307, 0x01e65), CE(0x00307, 0x01e66), + CE(0x00307, 0x01e67), CE(0x00301, 0x01e78), CE(0x00301, 0x01e79), + CE(0x00308, 0x01e7a), CE(0x00308, 0x01e7b), CE(0x00307, 0x01e9b), + CE(0x00300, 0x01edc), CE(0x00301, 0x01eda), CE(0x00303, 0x01ee0), + CE(0x00309, 0x01ede), CE(0x00323, 0x01ee2), CE(0x00300, 0x01edd), + CE(0x00301, 0x01edb), CE(0x00303, 0x01ee1), CE(0x00309, 0x01edf), + CE(0x00323, 0x01ee3), CE(0x00300, 0x01eea), CE(0x00301, 0x01ee8), + CE(0x00303, 0x01eee), CE(0x00309, 0x01eec), CE(0x00323, 0x01ef0), + CE(0x00300, 0x01eeb), CE(0x00301, 0x01ee9), CE(0x00303, 0x01eef), + CE(0x00309, 0x01eed), CE(0x00323, 0x01ef1), CE(0x0030c, 0x001ee), + CE(0x00304, 0x001ec), CE(0x00304, 0x001ed), CE(0x00304, 0x001e0), + CE(0x00304, 0x001e1), CE(0x00306, 0x01e1c), CE(0x00306, 0x01e1d), + CE(0x00304, 0x00230), CE(0x00304, 0x00231), CE(0x0030c, 0x001ef), + CE(0x00300, 0x01fba), CE(0x00301, 0x00386), CE(0x00304, 0x01fb9), + CE(0x00306, 0x01fb8), CE(0x00313, 0x01f08), CE(0x00314, 0x01f09), + CE(0x00345, 0x01fbc), CE(0x00300, 0x01fc8), CE(0x00301, 0x00388), + CE(0x00313, 0x01f18), CE(0x00314, 0x01f19), CE(0x00300, 0x01fca), + CE(0x00301, 0x00389), CE(0x00313, 0x01f28), CE(0x00314, 0x01f29), + CE(0x00345, 0x01fcc), CE(0x00300, 0x01fda), CE(0x00301, 0x0038a), + CE(0x00304, 0x01fd9), CE(0x00306, 0x01fd8), CE(0x00308, 0x003aa), + CE(0x00313, 0x01f38), CE(0x00314, 0x01f39), CE(0x00300, 0x01ff8), + CE(0x00301, 0x0038c), CE(0x00313, 0x01f48), CE(0x00314, 0x01f49), + CE(0x00314, 0x01fec), CE(0x00300, 0x01fea), CE(0x00301, 0x0038e), + CE(0x00304, 0x01fe9), CE(0x00306, 0x01fe8), CE(0x00308, 0x003ab), + CE(0x00314, 0x01f59), CE(0x00300, 0x01ffa), CE(0x00301, 0x0038f), + CE(0x00313, 0x01f68), CE(0x00314, 0x01f69), CE(0x00345, 0x01ffc), + CE(0x00345, 0x01fb4), CE(0x00345, 0x01fc4), CE(0x00300, 0x01f70), + CE(0x00301, 0x003ac), CE(0x00304, 0x01fb1), CE(0x00306, 0x01fb0), + CE(0x00313, 0x01f00), CE(0x00314, 0x01f01), CE(0x00342, 0x01fb6), + CE(0x00345, 0x01fb3), CE(0x00300, 0x01f72), CE(0x00301, 0x003ad), + CE(0x00313, 0x01f10), CE(0x00314, 0x01f11), CE(0x00300, 0x01f74), + CE(0x00301, 0x003ae), CE(0x00313, 0x01f20), CE(0x00314, 0x01f21), + CE(0x00342, 0x01fc6), CE(0x00345, 0x01fc3), CE(0x00300, 0x01f76), + CE(0x00301, 0x003af), CE(0x00304, 0x01fd1), CE(0x00306, 0x01fd0), + CE(0x00308, 0x003ca), CE(0x00313, 0x01f30), CE(0x00314, 0x01f31), + CE(0x00342, 0x01fd6), CE(0x00300, 0x01f78), CE(0x00301, 0x003cc), + CE(0x00313, 0x01f40), CE(0x00314, 0x01f41), CE(0x00313, 0x01fe4), + CE(0x00314, 0x01fe5), CE(0x00300, 0x01f7a), CE(0x00301, 0x003cd), + CE(0x00304, 0x01fe1), CE(0x00306, 0x01fe0), CE(0x00308, 0x003cb), + CE(0x00313, 0x01f50), CE(0x00314, 0x01f51), CE(0x00342, 0x01fe6), + CE(0x00300, 0x01f7c), CE(0x00301, 0x003ce), CE(0x00313, 0x01f60), + CE(0x00314, 0x01f61), CE(0x00342, 0x01ff6), CE(0x00345, 0x01ff3), + CE(0x00300, 0x01fd2), CE(0x00301, 0x00390), CE(0x00342, 0x01fd7), + CE(0x00300, 0x01fe2), CE(0x00301, 0x003b0), CE(0x00342, 0x01fe7), + CE(0x00345, 0x01ff4), CE(0x00301, 0x003d3), CE(0x00308, 0x003d4), + CE(0x00308, 0x00407), CE(0x00306, 0x004d0), CE(0x00308, 0x004d2), + CE(0x00301, 0x00403), CE(0x00300, 0x00400), CE(0x00306, 0x004d6), + CE(0x00308, 0x00401), CE(0x00306, 0x004c1), CE(0x00308, 0x004dc), + CE(0x00308, 0x004de), CE(0x00300, 0x0040d), CE(0x00304, 0x004e2), + CE(0x00306, 0x00419), CE(0x00308, 0x004e4), CE(0x00301, 0x0040c), + CE(0x00308, 0x004e6), CE(0x00304, 0x004ee), CE(0x00306, 0x0040e), + CE(0x00308, 0x004f0), CE(0x0030b, 0x004f2), CE(0x00308, 0x004f4), + CE(0x00308, 0x004f8), CE(0x00308, 0x004ec), CE(0x00306, 0x004d1), + CE(0x00308, 0x004d3), CE(0x00301, 0x00453), CE(0x00300, 0x00450), + CE(0x00306, 0x004d7), CE(0x00308, 0x00451), CE(0x00306, 0x004c2), + CE(0x00308, 0x004dd), CE(0x00308, 0x004df), CE(0x00300, 0x0045d), + CE(0x00304, 0x004e3), CE(0x00306, 0x00439), CE(0x00308, 0x004e5), + CE(0x00301, 0x0045c), CE(0x00308, 0x004e7), CE(0x00304, 0x004ef), + CE(0x00306, 0x0045e), CE(0x00308, 0x004f1), CE(0x0030b, 0x004f3), + CE(0x00308, 0x004f5), CE(0x00308, 0x004f9), CE(0x00308, 0x004ed), + CE(0x00308, 0x00457), CE(0x0030f, 0x00476), CE(0x0030f, 0x00477), + CE(0x00308, 0x004da), CE(0x00308, 0x004db), CE(0x00308, 0x004ea), + CE(0x00308, 0x004eb), CE(0x00653, 0x00622), CE(0x00654, 0x00623), + CE(0x00655, 0x00625), CE(0x00654, 0x00624), CE(0x00654, 0x00626), + CE(0x00654, 0x006c2), CE(0x00654, 0x006d3), CE(0x00654, 0x006c0), + CE(0x0093c, 0x00929), CE(0x0093c, 0x00931), CE(0x0093c, 0x00934), + CE(0x009be, 0x009cb), CE(0x009d7, 0x009cc), CE(0x00b3e, 0x00b4b), + CE(0x00b56, 0x00b48), CE(0x00b57, 0x00b4c), CE(0x00bd7, 0x00b94), + CE(0x00bbe, 0x00bca), CE(0x00bd7, 0x00bcc), CE(0x00bbe, 0x00bcb), + CE(0x00c56, 0x00c48), CE(0x00cd5, 0x00cc0), CE(0x00cc2, 0x00cca), + CE(0x00cd5, 0x00cc7), CE(0x00cd6, 0x00cc8), CE(0x00cd5, 0x00ccb), + CE(0x00d3e, 0x00d4a), CE(0x00d57, 0x00d4c), CE(0x00d3e, 0x00d4b), + CE(0x00dca, 0x00dda), CE(0x00dcf, 0x00ddc), CE(0x00ddf, 0x00dde), + CE(0x00dca, 0x00ddd), CE(0x0102e, 0x01026), CE(0x01b35, 0x01b06), + CE(0x01b35, 0x01b08), CE(0x01b35, 0x01b0a), CE(0x01b35, 0x01b0c), + CE(0x01b35, 0x01b0e), CE(0x01b35, 0x01b12), CE(0x01b35, 0x01b3b), + CE(0x01b35, 0x01b3d), CE(0x01b35, 0x01b40), CE(0x01b35, 0x01b41), + CE(0x01b35, 0x01b43), CE(0x00304, 0x01e38), CE(0x00304, 0x01e39), + CE(0x00304, 0x01e5c), CE(0x00304, 0x01e5d), CE(0x00307, 0x01e68), + CE(0x00307, 0x01e69), CE(0x00302, 0x01eac), CE(0x00306, 0x01eb6), + CE(0x00302, 0x01ead), CE(0x00306, 0x01eb7), CE(0x00302, 0x01ec6), + CE(0x00302, 0x01ec7), CE(0x00302, 0x01ed8), CE(0x00302, 0x01ed9), + CE(0x00300, 0x01f02), CE(0x00301, 0x01f04), CE(0x00342, 0x01f06), + CE(0x00345, 0x01f80), CE(0x00300, 0x01f03), CE(0x00301, 0x01f05), + CE(0x00342, 0x01f07), CE(0x00345, 0x01f81), CE(0x00345, 0x01f82), + CE(0x00345, 0x01f83), CE(0x00345, 0x01f84), CE(0x00345, 0x01f85), + CE(0x00345, 0x01f86), CE(0x00345, 0x01f87), CE(0x00300, 0x01f0a), + CE(0x00301, 0x01f0c), CE(0x00342, 0x01f0e), CE(0x00345, 0x01f88), + CE(0x00300, 0x01f0b), CE(0x00301, 0x01f0d), CE(0x00342, 0x01f0f), + CE(0x00345, 0x01f89), CE(0x00345, 0x01f8a), CE(0x00345, 0x01f8b), + CE(0x00345, 0x01f8c), CE(0x00345, 0x01f8d), CE(0x00345, 0x01f8e), + CE(0x00345, 0x01f8f), CE(0x00300, 0x01f12), CE(0x00301, 0x01f14), + CE(0x00300, 0x01f13), CE(0x00301, 0x01f15), CE(0x00300, 0x01f1a), + CE(0x00301, 0x01f1c), CE(0x00300, 0x01f1b), CE(0x00301, 0x01f1d), + CE(0x00300, 0x01f22), CE(0x00301, 0x01f24), CE(0x00342, 0x01f26), + CE(0x00345, 0x01f90), CE(0x00300, 0x01f23), CE(0x00301, 0x01f25), + CE(0x00342, 0x01f27), CE(0x00345, 0x01f91), CE(0x00345, 0x01f92), + CE(0x00345, 0x01f93), CE(0x00345, 0x01f94), CE(0x00345, 0x01f95), + CE(0x00345, 0x01f96), CE(0x00345, 0x01f97), CE(0x00300, 0x01f2a), + CE(0x00301, 0x01f2c), CE(0x00342, 0x01f2e), CE(0x00345, 0x01f98), + CE(0x00300, 0x01f2b), CE(0x00301, 0x01f2d), CE(0x00342, 0x01f2f), + CE(0x00345, 0x01f99), CE(0x00345, 0x01f9a), CE(0x00345, 0x01f9b), + CE(0x00345, 0x01f9c), CE(0x00345, 0x01f9d), CE(0x00345, 0x01f9e), + CE(0x00345, 0x01f9f), CE(0x00300, 0x01f32), CE(0x00301, 0x01f34), + CE(0x00342, 0x01f36), CE(0x00300, 0x01f33), CE(0x00301, 0x01f35), + CE(0x00342, 0x01f37), CE(0x00300, 0x01f3a), CE(0x00301, 0x01f3c), + CE(0x00342, 0x01f3e), CE(0x00300, 0x01f3b), CE(0x00301, 0x01f3d), + CE(0x00342, 0x01f3f), CE(0x00300, 0x01f42), CE(0x00301, 0x01f44), + CE(0x00300, 0x01f43), CE(0x00301, 0x01f45), CE(0x00300, 0x01f4a), + CE(0x00301, 0x01f4c), CE(0x00300, 0x01f4b), CE(0x00301, 0x01f4d), + CE(0x00300, 0x01f52), CE(0x00301, 0x01f54), CE(0x00342, 0x01f56), + CE(0x00300, 0x01f53), CE(0x00301, 0x01f55), CE(0x00342, 0x01f57), + CE(0x00300, 0x01f5b), CE(0x00301, 0x01f5d), CE(0x00342, 0x01f5f), + CE(0x00300, 0x01f62), CE(0x00301, 0x01f64), CE(0x00342, 0x01f66), + CE(0x00345, 0x01fa0), CE(0x00300, 0x01f63), CE(0x00301, 0x01f65), + CE(0x00342, 0x01f67), CE(0x00345, 0x01fa1), CE(0x00345, 0x01fa2), + CE(0x00345, 0x01fa3), CE(0x00345, 0x01fa4), CE(0x00345, 0x01fa5), + CE(0x00345, 0x01fa6), CE(0x00345, 0x01fa7), CE(0x00300, 0x01f6a), + CE(0x00301, 0x01f6c), CE(0x00342, 0x01f6e), CE(0x00345, 0x01fa8), + CE(0x00300, 0x01f6b), CE(0x00301, 0x01f6d), CE(0x00342, 0x01f6f), + CE(0x00345, 0x01fa9), CE(0x00345, 0x01faa), CE(0x00345, 0x01fab), + CE(0x00345, 0x01fac), CE(0x00345, 0x01fad), CE(0x00345, 0x01fae), + CE(0x00345, 0x01faf), CE(0x00345, 0x01fb2), CE(0x00345, 0x01fc2), + CE(0x00345, 0x01ff2), CE(0x00345, 0x01fb7), CE(0x00300, 0x01fcd), + CE(0x00301, 0x01fce), CE(0x00342, 0x01fcf), CE(0x00345, 0x01fc7), + CE(0x00345, 0x01ff7), CE(0x00300, 0x01fdd), CE(0x00301, 0x01fde), + CE(0x00342, 0x01fdf), CE(0x00338, 0x0219a), CE(0x00338, 0x0219b), + CE(0x00338, 0x021ae), CE(0x00338, 0x021cd), CE(0x00338, 0x021cf), + CE(0x00338, 0x021ce), CE(0x00338, 0x02204), CE(0x00338, 0x02209), + CE(0x00338, 0x0220c), CE(0x00338, 0x02224), CE(0x00338, 0x02226), + CE(0x00338, 0x02241), CE(0x00338, 0x02244), CE(0x00338, 0x02247), + CE(0x00338, 0x02249), CE(0x00338, 0x0226d), CE(0x00338, 0x02262), + CE(0x00338, 0x02270), CE(0x00338, 0x02271), CE(0x00338, 0x02274), + CE(0x00338, 0x02275), CE(0x00338, 0x02278), CE(0x00338, 0x02279), + CE(0x00338, 0x02280), CE(0x00338, 0x02281), CE(0x00338, 0x022e0), + CE(0x00338, 0x022e1), CE(0x00338, 0x02284), CE(0x00338, 0x02285), + CE(0x00338, 0x02288), CE(0x00338, 0x02289), CE(0x00338, 0x022e2), + CE(0x00338, 0x022e3), CE(0x00338, 0x022ac), CE(0x00338, 0x022ad), + CE(0x00338, 0x022ae), CE(0x00338, 0x022af), CE(0x00338, 0x022ea), + CE(0x00338, 0x022eb), CE(0x00338, 0x022ec), CE(0x00338, 0x022ed), + CE(0x03099, 0x03094), CE(0x03099, 0x0304c), CE(0x03099, 0x0304e), + CE(0x03099, 0x03050), CE(0x03099, 0x03052), CE(0x03099, 0x03054), + CE(0x03099, 0x03056), CE(0x03099, 0x03058), CE(0x03099, 0x0305a), + CE(0x03099, 0x0305c), CE(0x03099, 0x0305e), CE(0x03099, 0x03060), + CE(0x03099, 0x03062), CE(0x03099, 0x03065), CE(0x03099, 0x03067), + CE(0x03099, 0x03069), CE(0x03099, 0x03070), CE(0x0309a, 0x03071), + CE(0x03099, 0x03073), CE(0x0309a, 0x03074), CE(0x03099, 0x03076), + CE(0x0309a, 0x03077), CE(0x03099, 0x03079), CE(0x0309a, 0x0307a), + CE(0x03099, 0x0307c), CE(0x0309a, 0x0307d), CE(0x03099, 0x0309e), + CE(0x03099, 0x030f4), CE(0x03099, 0x030ac), CE(0x03099, 0x030ae), + CE(0x03099, 0x030b0), CE(0x03099, 0x030b2), CE(0x03099, 0x030b4), + CE(0x03099, 0x030b6), CE(0x03099, 0x030b8), CE(0x03099, 0x030ba), + CE(0x03099, 0x030bc), CE(0x03099, 0x030be), CE(0x03099, 0x030c0), + CE(0x03099, 0x030c2), CE(0x03099, 0x030c5), CE(0x03099, 0x030c7), + CE(0x03099, 0x030c9), CE(0x03099, 0x030d0), CE(0x0309a, 0x030d1), + CE(0x03099, 0x030d3), CE(0x0309a, 0x030d4), CE(0x03099, 0x030d6), + CE(0x0309a, 0x030d7), CE(0x03099, 0x030d9), CE(0x0309a, 0x030da), + CE(0x03099, 0x030dc), CE(0x0309a, 0x030dd), CE(0x03099, 0x030f7), + CE(0x03099, 0x030f8), CE(0x03099, 0x030f9), CE(0x03099, 0x030fa), + CE(0x03099, 0x030fe), CE(0x110ba, 0x1109a), CE(0x110ba, 0x1109c), + CE(0x110ba, 0x110ab), CE(0x11127, 0x1112e), CE(0x11127, 0x1112f), + ]; + return t; + } + +} + +static if (size_t.sizeof == 4) +{ + //6976 bytes + enum combiningClassTrieEntries = TrieEntry!(ubyte, 8, 7, 6)([0x0, 0x40, + 0x240], [0x100, 0x400, 0x1240], [0x2020100, 0x4020302, 0x2020205, + 0x2060202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20001, 0x0, 0x0, 0x3, + 0x0, 0x50004, 0x70006, 0x80000, 0xa0009, 0xb0000, 0xd000c, 0xe0000, + 0x10000f, 0x11000f, 0x11000f, 0x11000f, 0x11000f, 0x110000, + 0x120000, 0x11000f, 0x110000, 0x130000, 0x150014, 0x170016, + 0x190018, 0x1b001a, 0x1c, 0x1d, 0x0, 0x0, 0x0, 0x0, 0x1e0000, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x200000, 0x0, 0x21, 0x22, 0x0, + 0x240023, 0x0, 0x260025, 0x280027, 0x29, 0x2a0000, 0x0, 0x2b0000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2c0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x2d0000, 0x2e0000, 0x2f0000, 0x0, 0x0, 0x0, + 0x0, 0x30, 0x31, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x320000, 0x340033, 0x0, 0x0, 0x35, + 0x360000, 0x380037, 0x3a0039, 0x0, 0x3c003b, 0x0, 0x3d0000, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x3e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x400000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x41, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x350000, 0x42, 0x43, 0x3a0000, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x44, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x450000, 0x46, + 0x470000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6e6e6e6, 0xe6e6e6e6, 0xe6e6e6e6, + 0xe6e6e6e6, 0xe6e6e6e6, 0xdcdce8e6, 0xd8e8dcdc, 0xdcdcdcdc, + 0xdccacadc, 0xcadcdcdc, 0xdcdcdcca, 0xdcdcdcdc, 0xdcdcdcdc, + 0x1010101, 0xdcdcdc01, 0xe6e6e6dc, 0xe6e6e6e6, 0xdce6f0e6, + 0xe6e6dcdc, 0xdcdce6, 0xdce6e6e6, 0xe6dcdcdc, 0xe6dcdce8, + 0xe9eaeae9, 0xe6e9eaea, 0xe6e6e6e6, 0xe6e6e6e6, 0xe6e6e6e6, 0x0, + 0x0, 0x0, 0x0, 0xe6000000, 0xe6e6e6e6, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe6e6dc00, 0xe6dce6e6, 0xdcdee6e6, 0xe6e6e6e6, 0xdcdce6e6, + 0xdcdcdcdc, 0xe6dce6e6, 0xe6e4dee6, 0xd0c0b0a, 0x11100f0e, + 0x14131312, 0x17001615, 0x191800, 0x1200dce6, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xe6e6e6e6, 0xe6e6e6e6, 0x201f1e, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b000000, 0x1f1e1d1c, 0xe6222120, + 0xe6dcdce6, 0xe6e6e6e6, 0xdce6e6dc, 0x0, 0x0, 0x0, 0x0, 0x23, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6e60000, 0xe6e6e6e6, + 0xe60000e6, 0xdce6e6e6, 0xe60000e6, 0xe6dc00e6, 0xdce6, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2400, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xe6e6dce6, 0xdce6e6dc, 0xdce6dcdc, 0xe6dce6dc, 0xe6dce6e6, + 0xe6dce6dc, 0xe6e6dc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xe6000000, 0xe6e6e6e6, 0xe6dce6e6, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xe6e60000, 0xe600e6e6, 0xe6e6e6e6, 0xe6e6e6e6, + 0xe6e6e600, 0xe6e6e600, 0xe6e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xdcdcdc00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6dce6e6, + 0xe6e6dce6, 0xdcdcdce6, 0xe61d1c1b, 0xe6dce6e6, 0xe6dcdce6, + 0xe6e6e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x900, 0xe6dce600, 0xe6, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x900, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x900, 0x0, 0x5b5400, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x90000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x96767, 0x0, + 0x0, 0x0, 0x6b6b6b6b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x7676, 0x0, 0x0, 0x0, 0x7a7a7a7a, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xdcdc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xdc00dc00, 0xd800, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x828100, 0x84, 0x82820000, 0x8282, 0xe6e60082, + 0xe6e60009, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xdc0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7000000, 0x90900, 0x0, 0x0, + 0x0, 0x0, 0xdc00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6e6e600, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x90000, 0x0, 0x0, 0xe600, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe400, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xdce6de00, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xe6000000, 0xdc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x0, + 0xe6e6e600, 0xe6e6e6e6, 0xdc0000e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x9, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6000000, 0xe6e6e6dc, + 0xe6e6e6e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x9090000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x70000, 0x0, 0x0, 0x9090000, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x7000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6e6e6, 0xdcdcdc01, + 0xe6e6dcdc, 0xdcdcdcdc, 0x10100e6, 0x1010101, 0x1, 0xdc00, 0x0, + 0xe6, 0x0, 0x0, 0xe6dce6e6, 0xe6e6e6e6, 0xe6dce6e6, 0xdcd6eae6, + 0xe6e6e6ca, 0xe6e6e6e6, 0xe6e6e6e6, 0xe6e6e6e6, 0xe6e6e6e6, + 0xe6e6e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0xdce6dce9, 0x0, 0x0, 0x0, 0x0, + 0x101e6e6, 0xe6e6e6e6, 0xe6010101, 0xe6, 0xe600, 0xe6010100, + 0x101e6dc, 0xdcdcdcdc, 0xe6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6000000, 0xe6e6, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe6e6e6e6, 0xe6e6e6e6, 0xe6e6e6e6, 0xe6e6e6e6, 0xe6e6e6e6, + 0xe6e6e6e6, 0xe6e6e6e6, 0xe6e6e6e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe4da0000, 0xe0e0dee8, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80800, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xe6000000, 0x0, 0xe6e6e6e6, 0xe6e6e6e6, 0xe6e6, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6000000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xe6e6, 0x0, 0x0, 0x0, 0x0, 0x90000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6e6e6e6, 0xe6e6e6e6, 0xe6e6e6e6, + 0xe6e6e6e6, 0xe6e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xdc000000, 0xdcdc, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7000000, 0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6e600e6, + 0xe60000dc, 0xe6, 0xe6e60000, 0xe600, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x90000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x900, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a0000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe6e6e6e6, 0xe6e6e6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xdc00, + 0x0, 0x0, 0x0, 0xe600dc00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xdc01e6, 0x9000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x70900, 0x0, 0xe6e6e6, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9000000, 0x9, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7090000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d8d800, 0x101, 0xd8d8e200, 0xd8d8d8, 0x0, 0xdc000000, + 0xdcdcdcdc, 0xdcdcdc, 0xe6e6e600, 0xdcdce6e6, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xe6e60000, 0xe6e6, 0x0, 0x0, 0x0, 0x0, 0xe6e60000, + 0xe6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0]); + enum composeIdxMask = (1 << 11) - 1, composeCntShift = 11; + enum compositionJumpTrieEntries = TrieEntry!(ushort, 12, 9)([0x0, 0x800], + [0x1000, 0x2000], [0x10000, 0x30002, 0x50004, 0x70006, 0x70008, + 0x70007, 0x90007, 0xa0007, 0xc000b, 0x70007, 0x70007, 0x70007, + 0x7000d, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x7000e, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x8010800, + 0xffff0802, 0x8003ffff, 0x28161813, 0x8821301b, 0x38330832, + 0x7841383a, 0x28510850, 0x185c3056, 0x8068485f, 0xffff1078, + 0x3882407a, 0x98903889, 0x30a510a3, 0x48ad10ab, 0xffff30b6, + 0xffffffff, 0xffffffff, 0x80bcffff, 0x28cf18cc, 0x88da30d4, + 0x38ec08eb, 0x70fb40f3, 0x290b1109, 0x19163110, 0x81224919, + 0xffff1132, 0x393c4134, 0x994b4143, 0x3960115e, 0x51691167, + 0xffff3173, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff1979, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffff217c, 0x9810980, 0x9841182, 0xffffffff, + 0xffff2185, 0xffffffff, 0x989ffff, 0xffffffff, 0xffffffff, + 0x198e218a, 0xffff0991, 0xffff0992, 0xffffffff, 0xffff2193, + 0xffffffff, 0xffffffff, 0xffff2197, 0x99c099b, 0x99f119d, + 0xffffffff, 0xffff21a0, 0xffffffff, 0x9a4ffff, 0xffffffff, + 0xffffffff, 0x19a921a5, 0xffff09ac, 0xffff09ad, 0xffffffff, + 0xffff21ae, 0xffffffff, 0xffffffff, 0x21b621b2, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x11bc11ba, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x11c011be, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x9c309c2, 0xffffffff, 0xffffffff, + 0x9c509c4, 0xffffffff, 0xffffffff, 0xffffffff, 0x9c709c6, + 0x9c909c8, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x9caffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x29d029cb, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x29d5ffff, + 0xffff29da, 0xffffffff, 0xffffffff, 0x9dfffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x9e109e0, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x9e309e2, + 0x9e509e4, 0xffffffff, 0xffffffff, 0x9e709e6, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff09e8, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x39e9ffff, 0xffffffff, 0x21f0ffff, 0x29f4ffff, 0x39f9ffff, + 0xffffffff, 0xffffffff, 0x2200ffff, 0xa04ffff, 0xffffffff, + 0x3205ffff, 0xffffffff, 0x2a0bffff, 0xffffffff, 0xffff0a10, + 0xffff0a11, 0x4212ffff, 0xffffffff, 0x221affff, 0x321effff, + 0x4224ffff, 0xffffffff, 0xffffffff, 0x222cffff, 0x1230ffff, + 0xffffffff, 0x4232ffff, 0xffffffff, 0x323affff, 0x1a431a40, + 0xffffffff, 0xffff0a46, 0xffffffff, 0xffff1247, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0a49, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff124a, + 0xa4cffff, 0x1a4dffff, 0xa521250, 0xffff2253, 0xffff0a57, + 0xffffffff, 0xffff0a58, 0xffffffff, 0x2259ffff, 0xffffffff, + 0xa5dffff, 0xffffffff, 0xa5effff, 0xa5fffff, 0xffffffff, + 0xffff1260, 0xa62ffff, 0x1a63ffff, 0xa681266, 0xffff2269, + 0xffff0a6d, 0xffffffff, 0xffff0a6e, 0xffffffff, 0x226fffff, + 0xffffffff, 0xa73ffff, 0xffffffff, 0xa74ffff, 0xa75ffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0a76, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xa780a77, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xa7a0a79, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xa7c0a7b, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x1a7dffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0a80, 0xffff0a81, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xa82ffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffff0a83, 0xa84ffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffff0a85, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffff0a86, 0xa87ffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x1288ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x1a8affff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffff0a8d, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xa90128e, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffff0a91, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xa92ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff1a93, + 0xffffffff, 0xffff0a96, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xa991297, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x1a9affff, 0xffffffff, 0xffff0a9d, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xa9effff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xa9fffff, 0xaa0ffff, + 0xaa1ffff, 0xaa2ffff, 0xaa3ffff, 0xffffffff, 0xaa4ffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0aa5, + 0xffff0aa6, 0xaa80aa7, 0xffffffff, 0xffff0aa9, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xaab0aaa, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xaad0aac, 0xffffffff, + 0xffffffff, 0xffffffff, 0xaaf0aae, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x12b212b0, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xab50ab4, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xab70ab6, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x22bc22b8, 0xac10ac0, 0xac30ac2, 0xac50ac4, + 0x22ca22c6, 0xacf0ace, 0xad10ad0, 0xad30ad2, 0x12d612d4, + 0xffffffff, 0xffffffff, 0xffffffff, 0x12da12d8, 0xffffffff, + 0xffffffff, 0xffffffff, 0x22e022dc, 0xae50ae4, 0xae70ae6, + 0xae90ae8, 0x22ee22ea, 0xaf30af2, 0xaf50af4, 0xaf70af6, 0x1afb1af8, + 0xffffffff, 0xffffffff, 0xffffffff, 0x1b011afe, 0xffffffff, + 0xffffffff, 0xffffffff, 0x13061304, 0xffffffff, 0xffffffff, + 0xffffffff, 0x130a1308, 0xffffffff, 0xffffffff, 0xffffffff, + 0x1b0f1b0c, 0xffffffff, 0xffffffff, 0xffffffff, 0x1b12ffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x23192315, 0xb1e0b1d, + 0xb200b1f, 0xb220b21, 0x23272323, 0xb2c0b2b, 0xb2e0b2d, 0xb300b2f, + 0xffff0b31, 0xffffffff, 0xffff0b32, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffff0b33, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffff0b34, 0xffffffff, 0xffffffff, 0xffffffff, 0x1b35ffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0b38, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffff0b39, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffff1b3a, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffff0b3d, 0xffff0b3e, 0xffff0b3f, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0b40, + 0xffff0b41, 0xffff0b42, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xb43ffff, + 0xffffffff, 0xffffffff, 0xffff0b44, 0xb45ffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xb46ffff, 0xb47ffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0b48, 0xffffffff, + 0xffffffff, 0xb49ffff, 0xb4affff, 0xffffffff, 0xffff0b4b, + 0xffffffff, 0xb4cffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xb4dffff, 0xffffffff, 0xb4f0b4e, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xb510b50, 0xffffffff, 0xb530b52, 0xffffffff, 0xb550b54, 0xb570b56, + 0xffffffff, 0xffffffff, 0xb590b58, 0xffffffff, 0xb5b0b5a, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xb5cffff, + 0xffff0b5d, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0b5e, 0xffffffff, + 0xffffffff, 0xb600b5f, 0xb61ffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xb630b62, 0xb650b64, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0b66, 0xffffffff, + 0xb67ffff, 0xb68ffff, 0xb69ffff, 0xb6affff, 0xb6bffff, 0xb6cffff, + 0xb6dffff, 0xb6effff, 0xb6fffff, 0xb70ffff, 0xb71ffff, 0xb72ffff, + 0xffffffff, 0xffff0b73, 0xffff0b74, 0xffff0b75, 0xffffffff, + 0xffffffff, 0x1376ffff, 0xffffffff, 0xffff1378, 0x137affff, + 0xffffffff, 0xffff137c, 0x137effff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xb80ffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0b81, + 0xffffffff, 0xb82ffff, 0xb83ffff, 0xb84ffff, 0xb85ffff, 0xb86ffff, + 0xb87ffff, 0xb88ffff, 0xb89ffff, 0xb8affff, 0xb8bffff, 0xb8cffff, + 0xb8dffff, 0xffffffff, 0xffff0b8e, 0xffff0b8f, 0xffff0b90, + 0xffffffff, 0xffffffff, 0x1391ffff, 0xffffffff, 0xffff1393, + 0x1395ffff, 0xffffffff, 0xffff1397, 0x1399ffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xb9bffff, 0xb9d0b9c, + 0xffff0b9e, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xb9fffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xba0ffff, 0xba1ffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xba2ffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xba3ffff, 0xffff0ba4, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff]); + @property immutable(CompEntry[]) compositionTable() + { + alias CE = CompEntry; + static immutable CE[] t = [ + CE(0x00338, 0x0226e), CE(0x00338, 0x02260), CE(0x00338, 0x0226f), + CE(0x00300, 0x000c0), CE(0x00301, 0x000c1), CE(0x00302, 0x000c2), + CE(0x00303, 0x000c3), CE(0x00304, 0x00100), CE(0x00306, 0x00102), + CE(0x00307, 0x00226), CE(0x00308, 0x000c4), CE(0x00309, 0x01ea2), + CE(0x0030a, 0x000c5), CE(0x0030c, 0x001cd), CE(0x0030f, 0x00200), + CE(0x00311, 0x00202), CE(0x00323, 0x01ea0), CE(0x00325, 0x01e00), + CE(0x00328, 0x00104), CE(0x00307, 0x01e02), CE(0x00323, 0x01e04), + CE(0x00331, 0x01e06), CE(0x00301, 0x00106), CE(0x00302, 0x00108), + CE(0x00307, 0x0010a), CE(0x0030c, 0x0010c), CE(0x00327, 0x000c7), + CE(0x00307, 0x01e0a), CE(0x0030c, 0x0010e), CE(0x00323, 0x01e0c), + CE(0x00327, 0x01e10), CE(0x0032d, 0x01e12), CE(0x00331, 0x01e0e), + CE(0x00300, 0x000c8), CE(0x00301, 0x000c9), CE(0x00302, 0x000ca), + CE(0x00303, 0x01ebc), CE(0x00304, 0x00112), CE(0x00306, 0x00114), + CE(0x00307, 0x00116), CE(0x00308, 0x000cb), CE(0x00309, 0x01eba), + CE(0x0030c, 0x0011a), CE(0x0030f, 0x00204), CE(0x00311, 0x00206), + CE(0x00323, 0x01eb8), CE(0x00327, 0x00228), CE(0x00328, 0x00118), + CE(0x0032d, 0x01e18), CE(0x00330, 0x01e1a), CE(0x00307, 0x01e1e), + CE(0x00301, 0x001f4), CE(0x00302, 0x0011c), CE(0x00304, 0x01e20), + CE(0x00306, 0x0011e), CE(0x00307, 0x00120), CE(0x0030c, 0x001e6), + CE(0x00327, 0x00122), CE(0x00302, 0x00124), CE(0x00307, 0x01e22), + CE(0x00308, 0x01e26), CE(0x0030c, 0x0021e), CE(0x00323, 0x01e24), + CE(0x00327, 0x01e28), CE(0x0032e, 0x01e2a), CE(0x00300, 0x000cc), + CE(0x00301, 0x000cd), CE(0x00302, 0x000ce), CE(0x00303, 0x00128), + CE(0x00304, 0x0012a), CE(0x00306, 0x0012c), CE(0x00307, 0x00130), + CE(0x00308, 0x000cf), CE(0x00309, 0x01ec8), CE(0x0030c, 0x001cf), + CE(0x0030f, 0x00208), CE(0x00311, 0x0020a), CE(0x00323, 0x01eca), + CE(0x00328, 0x0012e), CE(0x00330, 0x01e2c), CE(0x00302, 0x00134), + CE(0x00301, 0x01e30), CE(0x0030c, 0x001e8), CE(0x00323, 0x01e32), + CE(0x00327, 0x00136), CE(0x00331, 0x01e34), CE(0x00301, 0x00139), + CE(0x0030c, 0x0013d), CE(0x00323, 0x01e36), CE(0x00327, 0x0013b), + CE(0x0032d, 0x01e3c), CE(0x00331, 0x01e3a), CE(0x00301, 0x01e3e), + CE(0x00307, 0x01e40), CE(0x00323, 0x01e42), CE(0x00300, 0x001f8), + CE(0x00301, 0x00143), CE(0x00303, 0x000d1), CE(0x00307, 0x01e44), + CE(0x0030c, 0x00147), CE(0x00323, 0x01e46), CE(0x00327, 0x00145), + CE(0x0032d, 0x01e4a), CE(0x00331, 0x01e48), CE(0x00300, 0x000d2), + CE(0x00301, 0x000d3), CE(0x00302, 0x000d4), CE(0x00303, 0x000d5), + CE(0x00304, 0x0014c), CE(0x00306, 0x0014e), CE(0x00307, 0x0022e), + CE(0x00308, 0x000d6), CE(0x00309, 0x01ece), CE(0x0030b, 0x00150), + CE(0x0030c, 0x001d1), CE(0x0030f, 0x0020c), CE(0x00311, 0x0020e), + CE(0x0031b, 0x001a0), CE(0x00323, 0x01ecc), CE(0x00328, 0x001ea), + CE(0x00301, 0x01e54), CE(0x00307, 0x01e56), CE(0x00301, 0x00154), + CE(0x00307, 0x01e58), CE(0x0030c, 0x00158), CE(0x0030f, 0x00210), + CE(0x00311, 0x00212), CE(0x00323, 0x01e5a), CE(0x00327, 0x00156), + CE(0x00331, 0x01e5e), CE(0x00301, 0x0015a), CE(0x00302, 0x0015c), + CE(0x00307, 0x01e60), CE(0x0030c, 0x00160), CE(0x00323, 0x01e62), + CE(0x00326, 0x00218), CE(0x00327, 0x0015e), CE(0x00307, 0x01e6a), + CE(0x0030c, 0x00164), CE(0x00323, 0x01e6c), CE(0x00326, 0x0021a), + CE(0x00327, 0x00162), CE(0x0032d, 0x01e70), CE(0x00331, 0x01e6e), + CE(0x00300, 0x000d9), CE(0x00301, 0x000da), CE(0x00302, 0x000db), + CE(0x00303, 0x00168), CE(0x00304, 0x0016a), CE(0x00306, 0x0016c), + CE(0x00308, 0x000dc), CE(0x00309, 0x01ee6), CE(0x0030a, 0x0016e), + CE(0x0030b, 0x00170), CE(0x0030c, 0x001d3), CE(0x0030f, 0x00214), + CE(0x00311, 0x00216), CE(0x0031b, 0x001af), CE(0x00323, 0x01ee4), + CE(0x00324, 0x01e72), CE(0x00328, 0x00172), CE(0x0032d, 0x01e76), + CE(0x00330, 0x01e74), CE(0x00303, 0x01e7c), CE(0x00323, 0x01e7e), + CE(0x00300, 0x01e80), CE(0x00301, 0x01e82), CE(0x00302, 0x00174), + CE(0x00307, 0x01e86), CE(0x00308, 0x01e84), CE(0x00323, 0x01e88), + CE(0x00307, 0x01e8a), CE(0x00308, 0x01e8c), CE(0x00300, 0x01ef2), + CE(0x00301, 0x000dd), CE(0x00302, 0x00176), CE(0x00303, 0x01ef8), + CE(0x00304, 0x00232), CE(0x00307, 0x01e8e), CE(0x00308, 0x00178), + CE(0x00309, 0x01ef6), CE(0x00323, 0x01ef4), CE(0x00301, 0x00179), + CE(0x00302, 0x01e90), CE(0x00307, 0x0017b), CE(0x0030c, 0x0017d), + CE(0x00323, 0x01e92), CE(0x00331, 0x01e94), CE(0x00300, 0x000e0), + CE(0x00301, 0x000e1), CE(0x00302, 0x000e2), CE(0x00303, 0x000e3), + CE(0x00304, 0x00101), CE(0x00306, 0x00103), CE(0x00307, 0x00227), + CE(0x00308, 0x000e4), CE(0x00309, 0x01ea3), CE(0x0030a, 0x000e5), + CE(0x0030c, 0x001ce), CE(0x0030f, 0x00201), CE(0x00311, 0x00203), + CE(0x00323, 0x01ea1), CE(0x00325, 0x01e01), CE(0x00328, 0x00105), + CE(0x00307, 0x01e03), CE(0x00323, 0x01e05), CE(0x00331, 0x01e07), + CE(0x00301, 0x00107), CE(0x00302, 0x00109), CE(0x00307, 0x0010b), + CE(0x0030c, 0x0010d), CE(0x00327, 0x000e7), CE(0x00307, 0x01e0b), + CE(0x0030c, 0x0010f), CE(0x00323, 0x01e0d), CE(0x00327, 0x01e11), + CE(0x0032d, 0x01e13), CE(0x00331, 0x01e0f), CE(0x00300, 0x000e8), + CE(0x00301, 0x000e9), CE(0x00302, 0x000ea), CE(0x00303, 0x01ebd), + CE(0x00304, 0x00113), CE(0x00306, 0x00115), CE(0x00307, 0x00117), + CE(0x00308, 0x000eb), CE(0x00309, 0x01ebb), CE(0x0030c, 0x0011b), + CE(0x0030f, 0x00205), CE(0x00311, 0x00207), CE(0x00323, 0x01eb9), + CE(0x00327, 0x00229), CE(0x00328, 0x00119), CE(0x0032d, 0x01e19), + CE(0x00330, 0x01e1b), CE(0x00307, 0x01e1f), CE(0x00301, 0x001f5), + CE(0x00302, 0x0011d), CE(0x00304, 0x01e21), CE(0x00306, 0x0011f), + CE(0x00307, 0x00121), CE(0x0030c, 0x001e7), CE(0x00327, 0x00123), + CE(0x00302, 0x00125), CE(0x00307, 0x01e23), CE(0x00308, 0x01e27), + CE(0x0030c, 0x0021f), CE(0x00323, 0x01e25), CE(0x00327, 0x01e29), + CE(0x0032e, 0x01e2b), CE(0x00331, 0x01e96), CE(0x00300, 0x000ec), + CE(0x00301, 0x000ed), CE(0x00302, 0x000ee), CE(0x00303, 0x00129), + CE(0x00304, 0x0012b), CE(0x00306, 0x0012d), CE(0x00308, 0x000ef), + CE(0x00309, 0x01ec9), CE(0x0030c, 0x001d0), CE(0x0030f, 0x00209), + CE(0x00311, 0x0020b), CE(0x00323, 0x01ecb), CE(0x00328, 0x0012f), + CE(0x00330, 0x01e2d), CE(0x00302, 0x00135), CE(0x0030c, 0x001f0), + CE(0x00301, 0x01e31), CE(0x0030c, 0x001e9), CE(0x00323, 0x01e33), + CE(0x00327, 0x00137), CE(0x00331, 0x01e35), CE(0x00301, 0x0013a), + CE(0x0030c, 0x0013e), CE(0x00323, 0x01e37), CE(0x00327, 0x0013c), + CE(0x0032d, 0x01e3d), CE(0x00331, 0x01e3b), CE(0x00301, 0x01e3f), + CE(0x00307, 0x01e41), CE(0x00323, 0x01e43), CE(0x00300, 0x001f9), + CE(0x00301, 0x00144), CE(0x00303, 0x000f1), CE(0x00307, 0x01e45), + CE(0x0030c, 0x00148), CE(0x00323, 0x01e47), CE(0x00327, 0x00146), + CE(0x0032d, 0x01e4b), CE(0x00331, 0x01e49), CE(0x00300, 0x000f2), + CE(0x00301, 0x000f3), CE(0x00302, 0x000f4), CE(0x00303, 0x000f5), + CE(0x00304, 0x0014d), CE(0x00306, 0x0014f), CE(0x00307, 0x0022f), + CE(0x00308, 0x000f6), CE(0x00309, 0x01ecf), CE(0x0030b, 0x00151), + CE(0x0030c, 0x001d2), CE(0x0030f, 0x0020d), CE(0x00311, 0x0020f), + CE(0x0031b, 0x001a1), CE(0x00323, 0x01ecd), CE(0x00328, 0x001eb), + CE(0x00301, 0x01e55), CE(0x00307, 0x01e57), CE(0x00301, 0x00155), + CE(0x00307, 0x01e59), CE(0x0030c, 0x00159), CE(0x0030f, 0x00211), + CE(0x00311, 0x00213), CE(0x00323, 0x01e5b), CE(0x00327, 0x00157), + CE(0x00331, 0x01e5f), CE(0x00301, 0x0015b), CE(0x00302, 0x0015d), + CE(0x00307, 0x01e61), CE(0x0030c, 0x00161), CE(0x00323, 0x01e63), + CE(0x00326, 0x00219), CE(0x00327, 0x0015f), CE(0x00307, 0x01e6b), + CE(0x00308, 0x01e97), CE(0x0030c, 0x00165), CE(0x00323, 0x01e6d), + CE(0x00326, 0x0021b), CE(0x00327, 0x00163), CE(0x0032d, 0x01e71), + CE(0x00331, 0x01e6f), CE(0x00300, 0x000f9), CE(0x00301, 0x000fa), + CE(0x00302, 0x000fb), CE(0x00303, 0x00169), CE(0x00304, 0x0016b), + CE(0x00306, 0x0016d), CE(0x00308, 0x000fc), CE(0x00309, 0x01ee7), + CE(0x0030a, 0x0016f), CE(0x0030b, 0x00171), CE(0x0030c, 0x001d4), + CE(0x0030f, 0x00215), CE(0x00311, 0x00217), CE(0x0031b, 0x001b0), + CE(0x00323, 0x01ee5), CE(0x00324, 0x01e73), CE(0x00328, 0x00173), + CE(0x0032d, 0x01e77), CE(0x00330, 0x01e75), CE(0x00303, 0x01e7d), + CE(0x00323, 0x01e7f), CE(0x00300, 0x01e81), CE(0x00301, 0x01e83), + CE(0x00302, 0x00175), CE(0x00307, 0x01e87), CE(0x00308, 0x01e85), + CE(0x0030a, 0x01e98), CE(0x00323, 0x01e89), CE(0x00307, 0x01e8b), + CE(0x00308, 0x01e8d), CE(0x00300, 0x01ef3), CE(0x00301, 0x000fd), + CE(0x00302, 0x00177), CE(0x00303, 0x01ef9), CE(0x00304, 0x00233), + CE(0x00307, 0x01e8f), CE(0x00308, 0x000ff), CE(0x00309, 0x01ef7), + CE(0x0030a, 0x01e99), CE(0x00323, 0x01ef5), CE(0x00301, 0x0017a), + CE(0x00302, 0x01e91), CE(0x00307, 0x0017c), CE(0x0030c, 0x0017e), + CE(0x00323, 0x01e93), CE(0x00331, 0x01e95), CE(0x00300, 0x01fed), + CE(0x00301, 0x00385), CE(0x00342, 0x01fc1), CE(0x00300, 0x01ea6), + CE(0x00301, 0x01ea4), CE(0x00303, 0x01eaa), CE(0x00309, 0x01ea8), + CE(0x00304, 0x001de), CE(0x00301, 0x001fa), CE(0x00301, 0x001fc), + CE(0x00304, 0x001e2), CE(0x00301, 0x01e08), CE(0x00300, 0x01ec0), + CE(0x00301, 0x01ebe), CE(0x00303, 0x01ec4), CE(0x00309, 0x01ec2), + CE(0x00301, 0x01e2e), CE(0x00300, 0x01ed2), CE(0x00301, 0x01ed0), + CE(0x00303, 0x01ed6), CE(0x00309, 0x01ed4), CE(0x00301, 0x01e4c), + CE(0x00304, 0x0022c), CE(0x00308, 0x01e4e), CE(0x00304, 0x0022a), + CE(0x00301, 0x001fe), CE(0x00300, 0x001db), CE(0x00301, 0x001d7), + CE(0x00304, 0x001d5), CE(0x0030c, 0x001d9), CE(0x00300, 0x01ea7), + CE(0x00301, 0x01ea5), CE(0x00303, 0x01eab), CE(0x00309, 0x01ea9), + CE(0x00304, 0x001df), CE(0x00301, 0x001fb), CE(0x00301, 0x001fd), + CE(0x00304, 0x001e3), CE(0x00301, 0x01e09), CE(0x00300, 0x01ec1), + CE(0x00301, 0x01ebf), CE(0x00303, 0x01ec5), CE(0x00309, 0x01ec3), + CE(0x00301, 0x01e2f), CE(0x00300, 0x01ed3), CE(0x00301, 0x01ed1), + CE(0x00303, 0x01ed7), CE(0x00309, 0x01ed5), CE(0x00301, 0x01e4d), + CE(0x00304, 0x0022d), CE(0x00308, 0x01e4f), CE(0x00304, 0x0022b), + CE(0x00301, 0x001ff), CE(0x00300, 0x001dc), CE(0x00301, 0x001d8), + CE(0x00304, 0x001d6), CE(0x0030c, 0x001da), CE(0x00300, 0x01eb0), + CE(0x00301, 0x01eae), CE(0x00303, 0x01eb4), CE(0x00309, 0x01eb2), + CE(0x00300, 0x01eb1), CE(0x00301, 0x01eaf), CE(0x00303, 0x01eb5), + CE(0x00309, 0x01eb3), CE(0x00300, 0x01e14), CE(0x00301, 0x01e16), + CE(0x00300, 0x01e15), CE(0x00301, 0x01e17), CE(0x00300, 0x01e50), + CE(0x00301, 0x01e52), CE(0x00300, 0x01e51), CE(0x00301, 0x01e53), + CE(0x00307, 0x01e64), CE(0x00307, 0x01e65), CE(0x00307, 0x01e66), + CE(0x00307, 0x01e67), CE(0x00301, 0x01e78), CE(0x00301, 0x01e79), + CE(0x00308, 0x01e7a), CE(0x00308, 0x01e7b), CE(0x00307, 0x01e9b), + CE(0x00300, 0x01edc), CE(0x00301, 0x01eda), CE(0x00303, 0x01ee0), + CE(0x00309, 0x01ede), CE(0x00323, 0x01ee2), CE(0x00300, 0x01edd), + CE(0x00301, 0x01edb), CE(0x00303, 0x01ee1), CE(0x00309, 0x01edf), + CE(0x00323, 0x01ee3), CE(0x00300, 0x01eea), CE(0x00301, 0x01ee8), + CE(0x00303, 0x01eee), CE(0x00309, 0x01eec), CE(0x00323, 0x01ef0), + CE(0x00300, 0x01eeb), CE(0x00301, 0x01ee9), CE(0x00303, 0x01eef), + CE(0x00309, 0x01eed), CE(0x00323, 0x01ef1), CE(0x0030c, 0x001ee), + CE(0x00304, 0x001ec), CE(0x00304, 0x001ed), CE(0x00304, 0x001e0), + CE(0x00304, 0x001e1), CE(0x00306, 0x01e1c), CE(0x00306, 0x01e1d), + CE(0x00304, 0x00230), CE(0x00304, 0x00231), CE(0x0030c, 0x001ef), + CE(0x00300, 0x01fba), CE(0x00301, 0x00386), CE(0x00304, 0x01fb9), + CE(0x00306, 0x01fb8), CE(0x00313, 0x01f08), CE(0x00314, 0x01f09), + CE(0x00345, 0x01fbc), CE(0x00300, 0x01fc8), CE(0x00301, 0x00388), + CE(0x00313, 0x01f18), CE(0x00314, 0x01f19), CE(0x00300, 0x01fca), + CE(0x00301, 0x00389), CE(0x00313, 0x01f28), CE(0x00314, 0x01f29), + CE(0x00345, 0x01fcc), CE(0x00300, 0x01fda), CE(0x00301, 0x0038a), + CE(0x00304, 0x01fd9), CE(0x00306, 0x01fd8), CE(0x00308, 0x003aa), + CE(0x00313, 0x01f38), CE(0x00314, 0x01f39), CE(0x00300, 0x01ff8), + CE(0x00301, 0x0038c), CE(0x00313, 0x01f48), CE(0x00314, 0x01f49), + CE(0x00314, 0x01fec), CE(0x00300, 0x01fea), CE(0x00301, 0x0038e), + CE(0x00304, 0x01fe9), CE(0x00306, 0x01fe8), CE(0x00308, 0x003ab), + CE(0x00314, 0x01f59), CE(0x00300, 0x01ffa), CE(0x00301, 0x0038f), + CE(0x00313, 0x01f68), CE(0x00314, 0x01f69), CE(0x00345, 0x01ffc), + CE(0x00345, 0x01fb4), CE(0x00345, 0x01fc4), CE(0x00300, 0x01f70), + CE(0x00301, 0x003ac), CE(0x00304, 0x01fb1), CE(0x00306, 0x01fb0), + CE(0x00313, 0x01f00), CE(0x00314, 0x01f01), CE(0x00342, 0x01fb6), + CE(0x00345, 0x01fb3), CE(0x00300, 0x01f72), CE(0x00301, 0x003ad), + CE(0x00313, 0x01f10), CE(0x00314, 0x01f11), CE(0x00300, 0x01f74), + CE(0x00301, 0x003ae), CE(0x00313, 0x01f20), CE(0x00314, 0x01f21), + CE(0x00342, 0x01fc6), CE(0x00345, 0x01fc3), CE(0x00300, 0x01f76), + CE(0x00301, 0x003af), CE(0x00304, 0x01fd1), CE(0x00306, 0x01fd0), + CE(0x00308, 0x003ca), CE(0x00313, 0x01f30), CE(0x00314, 0x01f31), + CE(0x00342, 0x01fd6), CE(0x00300, 0x01f78), CE(0x00301, 0x003cc), + CE(0x00313, 0x01f40), CE(0x00314, 0x01f41), CE(0x00313, 0x01fe4), + CE(0x00314, 0x01fe5), CE(0x00300, 0x01f7a), CE(0x00301, 0x003cd), + CE(0x00304, 0x01fe1), CE(0x00306, 0x01fe0), CE(0x00308, 0x003cb), + CE(0x00313, 0x01f50), CE(0x00314, 0x01f51), CE(0x00342, 0x01fe6), + CE(0x00300, 0x01f7c), CE(0x00301, 0x003ce), CE(0x00313, 0x01f60), + CE(0x00314, 0x01f61), CE(0x00342, 0x01ff6), CE(0x00345, 0x01ff3), + CE(0x00300, 0x01fd2), CE(0x00301, 0x00390), CE(0x00342, 0x01fd7), + CE(0x00300, 0x01fe2), CE(0x00301, 0x003b0), CE(0x00342, 0x01fe7), + CE(0x00345, 0x01ff4), CE(0x00301, 0x003d3), CE(0x00308, 0x003d4), + CE(0x00308, 0x00407), CE(0x00306, 0x004d0), CE(0x00308, 0x004d2), + CE(0x00301, 0x00403), CE(0x00300, 0x00400), CE(0x00306, 0x004d6), + CE(0x00308, 0x00401), CE(0x00306, 0x004c1), CE(0x00308, 0x004dc), + CE(0x00308, 0x004de), CE(0x00300, 0x0040d), CE(0x00304, 0x004e2), + CE(0x00306, 0x00419), CE(0x00308, 0x004e4), CE(0x00301, 0x0040c), + CE(0x00308, 0x004e6), CE(0x00304, 0x004ee), CE(0x00306, 0x0040e), + CE(0x00308, 0x004f0), CE(0x0030b, 0x004f2), CE(0x00308, 0x004f4), + CE(0x00308, 0x004f8), CE(0x00308, 0x004ec), CE(0x00306, 0x004d1), + CE(0x00308, 0x004d3), CE(0x00301, 0x00453), CE(0x00300, 0x00450), + CE(0x00306, 0x004d7), CE(0x00308, 0x00451), CE(0x00306, 0x004c2), + CE(0x00308, 0x004dd), CE(0x00308, 0x004df), CE(0x00300, 0x0045d), + CE(0x00304, 0x004e3), CE(0x00306, 0x00439), CE(0x00308, 0x004e5), + CE(0x00301, 0x0045c), CE(0x00308, 0x004e7), CE(0x00304, 0x004ef), + CE(0x00306, 0x0045e), CE(0x00308, 0x004f1), CE(0x0030b, 0x004f3), + CE(0x00308, 0x004f5), CE(0x00308, 0x004f9), CE(0x00308, 0x004ed), + CE(0x00308, 0x00457), CE(0x0030f, 0x00476), CE(0x0030f, 0x00477), + CE(0x00308, 0x004da), CE(0x00308, 0x004db), CE(0x00308, 0x004ea), + CE(0x00308, 0x004eb), CE(0x00653, 0x00622), CE(0x00654, 0x00623), + CE(0x00655, 0x00625), CE(0x00654, 0x00624), CE(0x00654, 0x00626), + CE(0x00654, 0x006c2), CE(0x00654, 0x006d3), CE(0x00654, 0x006c0), + CE(0x0093c, 0x00929), CE(0x0093c, 0x00931), CE(0x0093c, 0x00934), + CE(0x009be, 0x009cb), CE(0x009d7, 0x009cc), CE(0x00b3e, 0x00b4b), + CE(0x00b56, 0x00b48), CE(0x00b57, 0x00b4c), CE(0x00bd7, 0x00b94), + CE(0x00bbe, 0x00bca), CE(0x00bd7, 0x00bcc), CE(0x00bbe, 0x00bcb), + CE(0x00c56, 0x00c48), CE(0x00cd5, 0x00cc0), CE(0x00cc2, 0x00cca), + CE(0x00cd5, 0x00cc7), CE(0x00cd6, 0x00cc8), CE(0x00cd5, 0x00ccb), + CE(0x00d3e, 0x00d4a), CE(0x00d57, 0x00d4c), CE(0x00d3e, 0x00d4b), + CE(0x00dca, 0x00dda), CE(0x00dcf, 0x00ddc), CE(0x00ddf, 0x00dde), + CE(0x00dca, 0x00ddd), CE(0x0102e, 0x01026), CE(0x01b35, 0x01b06), + CE(0x01b35, 0x01b08), CE(0x01b35, 0x01b0a), CE(0x01b35, 0x01b0c), + CE(0x01b35, 0x01b0e), CE(0x01b35, 0x01b12), CE(0x01b35, 0x01b3b), + CE(0x01b35, 0x01b3d), CE(0x01b35, 0x01b40), CE(0x01b35, 0x01b41), + CE(0x01b35, 0x01b43), CE(0x00304, 0x01e38), CE(0x00304, 0x01e39), + CE(0x00304, 0x01e5c), CE(0x00304, 0x01e5d), CE(0x00307, 0x01e68), + CE(0x00307, 0x01e69), CE(0x00302, 0x01eac), CE(0x00306, 0x01eb6), + CE(0x00302, 0x01ead), CE(0x00306, 0x01eb7), CE(0x00302, 0x01ec6), + CE(0x00302, 0x01ec7), CE(0x00302, 0x01ed8), CE(0x00302, 0x01ed9), + CE(0x00300, 0x01f02), CE(0x00301, 0x01f04), CE(0x00342, 0x01f06), + CE(0x00345, 0x01f80), CE(0x00300, 0x01f03), CE(0x00301, 0x01f05), + CE(0x00342, 0x01f07), CE(0x00345, 0x01f81), CE(0x00345, 0x01f82), + CE(0x00345, 0x01f83), CE(0x00345, 0x01f84), CE(0x00345, 0x01f85), + CE(0x00345, 0x01f86), CE(0x00345, 0x01f87), CE(0x00300, 0x01f0a), + CE(0x00301, 0x01f0c), CE(0x00342, 0x01f0e), CE(0x00345, 0x01f88), + CE(0x00300, 0x01f0b), CE(0x00301, 0x01f0d), CE(0x00342, 0x01f0f), + CE(0x00345, 0x01f89), CE(0x00345, 0x01f8a), CE(0x00345, 0x01f8b), + CE(0x00345, 0x01f8c), CE(0x00345, 0x01f8d), CE(0x00345, 0x01f8e), + CE(0x00345, 0x01f8f), CE(0x00300, 0x01f12), CE(0x00301, 0x01f14), + CE(0x00300, 0x01f13), CE(0x00301, 0x01f15), CE(0x00300, 0x01f1a), + CE(0x00301, 0x01f1c), CE(0x00300, 0x01f1b), CE(0x00301, 0x01f1d), + CE(0x00300, 0x01f22), CE(0x00301, 0x01f24), CE(0x00342, 0x01f26), + CE(0x00345, 0x01f90), CE(0x00300, 0x01f23), CE(0x00301, 0x01f25), + CE(0x00342, 0x01f27), CE(0x00345, 0x01f91), CE(0x00345, 0x01f92), + CE(0x00345, 0x01f93), CE(0x00345, 0x01f94), CE(0x00345, 0x01f95), + CE(0x00345, 0x01f96), CE(0x00345, 0x01f97), CE(0x00300, 0x01f2a), + CE(0x00301, 0x01f2c), CE(0x00342, 0x01f2e), CE(0x00345, 0x01f98), + CE(0x00300, 0x01f2b), CE(0x00301, 0x01f2d), CE(0x00342, 0x01f2f), + CE(0x00345, 0x01f99), CE(0x00345, 0x01f9a), CE(0x00345, 0x01f9b), + CE(0x00345, 0x01f9c), CE(0x00345, 0x01f9d), CE(0x00345, 0x01f9e), + CE(0x00345, 0x01f9f), CE(0x00300, 0x01f32), CE(0x00301, 0x01f34), + CE(0x00342, 0x01f36), CE(0x00300, 0x01f33), CE(0x00301, 0x01f35), + CE(0x00342, 0x01f37), CE(0x00300, 0x01f3a), CE(0x00301, 0x01f3c), + CE(0x00342, 0x01f3e), CE(0x00300, 0x01f3b), CE(0x00301, 0x01f3d), + CE(0x00342, 0x01f3f), CE(0x00300, 0x01f42), CE(0x00301, 0x01f44), + CE(0x00300, 0x01f43), CE(0x00301, 0x01f45), CE(0x00300, 0x01f4a), + CE(0x00301, 0x01f4c), CE(0x00300, 0x01f4b), CE(0x00301, 0x01f4d), + CE(0x00300, 0x01f52), CE(0x00301, 0x01f54), CE(0x00342, 0x01f56), + CE(0x00300, 0x01f53), CE(0x00301, 0x01f55), CE(0x00342, 0x01f57), + CE(0x00300, 0x01f5b), CE(0x00301, 0x01f5d), CE(0x00342, 0x01f5f), + CE(0x00300, 0x01f62), CE(0x00301, 0x01f64), CE(0x00342, 0x01f66), + CE(0x00345, 0x01fa0), CE(0x00300, 0x01f63), CE(0x00301, 0x01f65), + CE(0x00342, 0x01f67), CE(0x00345, 0x01fa1), CE(0x00345, 0x01fa2), + CE(0x00345, 0x01fa3), CE(0x00345, 0x01fa4), CE(0x00345, 0x01fa5), + CE(0x00345, 0x01fa6), CE(0x00345, 0x01fa7), CE(0x00300, 0x01f6a), + CE(0x00301, 0x01f6c), CE(0x00342, 0x01f6e), CE(0x00345, 0x01fa8), + CE(0x00300, 0x01f6b), CE(0x00301, 0x01f6d), CE(0x00342, 0x01f6f), + CE(0x00345, 0x01fa9), CE(0x00345, 0x01faa), CE(0x00345, 0x01fab), + CE(0x00345, 0x01fac), CE(0x00345, 0x01fad), CE(0x00345, 0x01fae), + CE(0x00345, 0x01faf), CE(0x00345, 0x01fb2), CE(0x00345, 0x01fc2), + CE(0x00345, 0x01ff2), CE(0x00345, 0x01fb7), CE(0x00300, 0x01fcd), + CE(0x00301, 0x01fce), CE(0x00342, 0x01fcf), CE(0x00345, 0x01fc7), + CE(0x00345, 0x01ff7), CE(0x00300, 0x01fdd), CE(0x00301, 0x01fde), + CE(0x00342, 0x01fdf), CE(0x00338, 0x0219a), CE(0x00338, 0x0219b), + CE(0x00338, 0x021ae), CE(0x00338, 0x021cd), CE(0x00338, 0x021cf), + CE(0x00338, 0x021ce), CE(0x00338, 0x02204), CE(0x00338, 0x02209), + CE(0x00338, 0x0220c), CE(0x00338, 0x02224), CE(0x00338, 0x02226), + CE(0x00338, 0x02241), CE(0x00338, 0x02244), CE(0x00338, 0x02247), + CE(0x00338, 0x02249), CE(0x00338, 0x0226d), CE(0x00338, 0x02262), + CE(0x00338, 0x02270), CE(0x00338, 0x02271), CE(0x00338, 0x02274), + CE(0x00338, 0x02275), CE(0x00338, 0x02278), CE(0x00338, 0x02279), + CE(0x00338, 0x02280), CE(0x00338, 0x02281), CE(0x00338, 0x022e0), + CE(0x00338, 0x022e1), CE(0x00338, 0x02284), CE(0x00338, 0x02285), + CE(0x00338, 0x02288), CE(0x00338, 0x02289), CE(0x00338, 0x022e2), + CE(0x00338, 0x022e3), CE(0x00338, 0x022ac), CE(0x00338, 0x022ad), + CE(0x00338, 0x022ae), CE(0x00338, 0x022af), CE(0x00338, 0x022ea), + CE(0x00338, 0x022eb), CE(0x00338, 0x022ec), CE(0x00338, 0x022ed), + CE(0x03099, 0x03094), CE(0x03099, 0x0304c), CE(0x03099, 0x0304e), + CE(0x03099, 0x03050), CE(0x03099, 0x03052), CE(0x03099, 0x03054), + CE(0x03099, 0x03056), CE(0x03099, 0x03058), CE(0x03099, 0x0305a), + CE(0x03099, 0x0305c), CE(0x03099, 0x0305e), CE(0x03099, 0x03060), + CE(0x03099, 0x03062), CE(0x03099, 0x03065), CE(0x03099, 0x03067), + CE(0x03099, 0x03069), CE(0x03099, 0x03070), CE(0x0309a, 0x03071), + CE(0x03099, 0x03073), CE(0x0309a, 0x03074), CE(0x03099, 0x03076), + CE(0x0309a, 0x03077), CE(0x03099, 0x03079), CE(0x0309a, 0x0307a), + CE(0x03099, 0x0307c), CE(0x0309a, 0x0307d), CE(0x03099, 0x0309e), + CE(0x03099, 0x030f4), CE(0x03099, 0x030ac), CE(0x03099, 0x030ae), + CE(0x03099, 0x030b0), CE(0x03099, 0x030b2), CE(0x03099, 0x030b4), + CE(0x03099, 0x030b6), CE(0x03099, 0x030b8), CE(0x03099, 0x030ba), + CE(0x03099, 0x030bc), CE(0x03099, 0x030be), CE(0x03099, 0x030c0), + CE(0x03099, 0x030c2), CE(0x03099, 0x030c5), CE(0x03099, 0x030c7), + CE(0x03099, 0x030c9), CE(0x03099, 0x030d0), CE(0x0309a, 0x030d1), + CE(0x03099, 0x030d3), CE(0x0309a, 0x030d4), CE(0x03099, 0x030d6), + CE(0x0309a, 0x030d7), CE(0x03099, 0x030d9), CE(0x0309a, 0x030da), + CE(0x03099, 0x030dc), CE(0x0309a, 0x030dd), CE(0x03099, 0x030f7), + CE(0x03099, 0x030f8), CE(0x03099, 0x030f9), CE(0x03099, 0x030fa), + CE(0x03099, 0x030fe), CE(0x110ba, 0x1109a), CE(0x110ba, 0x1109c), + CE(0x110ba, 0x110ab), CE(0x11127, 0x1112e), CE(0x11127, 0x1112f), + ]; + return t; + } + +} diff --git a/libphobos/src/std/internal/unicode_decomp.d b/libphobos/src/std/internal/unicode_decomp.d new file mode 100644 index 0000000..736965d --- /dev/null +++ b/libphobos/src/std/internal/unicode_decomp.d @@ -0,0 +1,5301 @@ +module std.internal.unicode_decomp; +import std.internal.unicode_tables; + +@safe pure nothrow @nogc package(std): + +static if (size_t.sizeof == 8) +{ + //22656 bytes + enum compatMappingTrieEntries = TrieEntry!(ushort, 8, 8, 5)([0x0, 0x20, + 0x2a0], [0x100, 0xa00, 0x21c0], [0x402030202020100, + 0x706020202020205, 0x802020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x0, 0x3000200010000, 0x7000600050004, + 0xa000900080000, 0xc000b, 0xf000e000d0000, 0x11001000000000, + 0x15001400130012, 0x19001800170016, 0x1b001a00000000, 0x0, 0x1c, + 0x1e0000001d0000, 0x1f00000000, 0x0, 0x0, 0x0, 0x0, 0x2100200000, + 0x2200000000, 0x2400230000, 0x0, 0x2500000000, 0x2700000026, + 0x2800000000, 0x2900000000, 0x2a00000000, 0x2b00000000, 0x2c0000, + 0x2e002d0000, 0x3100300000002f, 0x330032, 0x340000, + 0x35000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3800370036, + 0x0, 0x0, 0x0, 0x3b003a00390000, 0x3d003c, 0x410040003f003e, + 0x45004400430042, 0x49004800470046, 0x4d004c004b004a, + 0x510050004f004e, 0x530052, 0x57005600550054, 0x5a00590058, + 0x5e005d005c005b, 0x6100000060005f, 0x620000, 0x0, + 0x63000000000000, 0x67006600650064, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x69000000000068, 0x6a00000000, 0x0, 0x0, + 0x6b000000000000, 0x0, 0x6c000000000000, 0x0, 0x0, + 0x6e00000000006d, 0x7200710070006f, 0x7500740073, 0x79007800770076, + 0x7d007c007b007a, 0x80007f007e0000, 0x81, 0x85008400830082, + 0x89008800870086, 0x8d008c008b008a, 0x910090008f008e, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x92000000000000, + 0x93000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x97009600950094, + 0x9b009a00990098, 0x9f009e009d009c, 0xa200a100a0, 0xa600a500a400a3, + 0xaa00a900a800a7, 0xae00ad00ac00ab, 0xb200b100b000af, + 0xb600b500b400b3, 0xba00b900b800b7, 0xbe00bd00bc00bb, + 0xc200c100c000bf, 0xc600c500c400c3, 0xca00c900c800c7, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xcc00cb, 0xcd0000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcf00ce00000000, 0xd100d00000, + 0x0, 0x0, 0x0, 0x0, 0xd500d400d300d2, 0xd900d800d700d6, + 0xdd00dc00db00da, 0xdf00d300d200de, 0xe200e100e000d5, + 0xe500e400e300d9, 0xe900e800e700e6, 0xed00ec00eb00ea, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xf100f000ef00ee, 0xf300f2, 0x0, 0x0, 0x0, 0x0, + 0xf700f600f500f4, 0xf8, 0xfb00fa00f9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xff00fe00fd00fc, 0x103010201010100, + 0x107010601050104, 0x10b010a01090108, 0x10c, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x0, 0x69200000015, 0x9000000000000, 0x30f034300000000, + 0x11b20003, 0x78703140048, 0x49403c603ce, 0x58605730570056d, + 0x5f8000005b005a6, 0x6580631062e062b, 0x6f906ea06e706e4, + 0x7a907a6078f0000, 0x7e307bf07ac, 0x8b708b408b10000, 0x95f08cb, + 0x9c209af09ac09a9, 0xa47000009ec09e2, 0xab30a8c0a890a86, + 0xb550b490b460b43, 0xc5e0c5b0c410000, 0xc980c740c61, + 0xd6e0d6b0d680000, 0xe1b00000e0c0d82, 0x9c8058c09c50589, + 0xa3b05ec0a0a05ce, 0xa4105f20a3e05ef, 0xa6e061a0a4405f5, + 0xaa2064700000000, 0xab006550aad0652, 0xab9065e0ad00675, + 0xb0106a00afb069a, 0xb0a06a90b0406a3, 0xb1606ba, 0xb4f06f00b4c06ed, + 0xb6b070f0b5206f3, 0xb3706d8000006f6, 0xbae072e0b730717, + 0x7500bcc07430000, 0x7400bcf07460bd9, 0x78c000000000bc9, + 0x7950c4d079b0c3e, 0xed70c47, 0xc8e07d90c8307ce, 0xca207ed, + 0xd1d08580d070842, 0xd2b086c0d0d0848, 0xd49088a0d320873, + 0xd5d08a60d380879, 0xd54089d, 0xd7808c10d7108ba, 0xd9808e10d7f08c8, + 0xdc4090d0d9b08e4, 0xe0f09620de9093f, 0x97f0e290979096e, + 0x8400614060d0e2f, 0xcae07f9, 0x0, 0x0, 0x8f0000000000000, 0xda7, + 0x0, 0x0, 0x0, 0x0, 0x7360a670613060c, 0x78307800bb9073d, + 0x70309f305b70c32, 0x8e70ca507f00b5f, 0x8d20d8d08d60d9e, + 0x8ce0d9108da0d89, 0x9e505a900000d85, 0xe630e5a09de05a2, + 0xb0706a600000000, 0xccc08170ba80728, 0xecc0e7b0ccf081a, + 0xa64061006090b76, 0xaf80697, 0x9ef05b30c3b0789, 0xe680e5d0e600e57, + 0x9f905bd09f605ba, 0xabf06640abc0661, 0xb6507090b620706, + 0xcab07f60ca807f3, 0xd13084e0d10084b, 0xda408ed0da108ea, + 0xd5a08a30d460887, 0xb1f06c300000000, 0x0, 0x9db059f00000000, + 0xc9b07e60ac9066e, 0xc9107dc0c7b07c6, 0xe1509680c9407df, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa11073e0e9a0b0d, 0xde10eb80eb60eb4, + 0x695, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4b00240012000f, + 0x270006, 0xb4108400a280e96, 0xecf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2b00000004001a, 0x1d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xed5, 0x5400000000, 0x54600000000, 0x0, + 0x7410ee8001c0003, 0xfb40f630f43, 0x103c101600000fed, 0x1185, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x101f0fbd00000000, 0x1175111910f5108f, 0x1213, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x120c117e00000000, 0x124b120311d5, + 0x10161011116e10ea, 0x11ee123c101f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x11f811f011ae, 0x10f00fad, 0x100d0000, 0x0, 0x12ad000012b612b0, + 0x12a4000000000000, 0x0, 0x12d712c212ce, 0x0, 0x0, 0x12c80000, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x130a0000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x12ef000012f812f2, 0x132d000000000000, 0x0, 0x131b13041310, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1333133000000000, 0x0, 0x0, 0x12fb12b90000, + 0x0, 0x0, 0x0, 0x12ec12aa12e912a7, 0x12f512b300000000, + 0x1339133600000000, 0x130112bf12fe12bc, 0x130712c500000000, + 0x131512d1130d12cb, 0x133f133c00000000, 0x131812d4132a12e6, + 0x132112dd131e12da, 0x132412e0, 0x132712e3, 0x0, 0x0, + 0x1342000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x13e913e600000000, 0x17ca13ec178f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x185b179213ef0000, 0x1811, 0x0, + 0x18520000186d, 0x0, 0x0, 0x0, 0x186a000000000000, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x18820000, 0x0, 0x188b0000, 0x188e, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1879187618731870, 0x18881885187f187c, 0x0, + 0x0, 0x189a000000000000, 0x189d, 0x0, 0x0, 0x0, 0x1897000018941891, + 0x0, 0x0, 0x0, 0x0, 0x18ac000000000000, 0x18af00000000, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18a618a318a00000, 0x18a900000000, + 0x0, 0x0, 0x18b80000000018bb, 0x18be, 0x0, 0x0, 0x0, 0x18b518b2, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x18c1, 0x0, 0x0, 0x0, 0x0, + 0x18ca18c400000000, 0x18c7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18cd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x18d0, 0x18da000000000000, + 0x18d618d3000018dd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x18e618e000000000, 0x18e3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x18e900000000, 0x18f318ef18ec, 0x0, 0x0, 0x0, 0x0, + 0x18f6000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x18ff000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x18fc18f9, 0x0, 0x0, 0x0, 0x1902, 0x0, 0x0, 0x0, 0x0, + 0x1907000000000000, 0x0, 0x0, 0x190a0000, 0x190d00000000, + 0x1910000000000000, 0x0, 0x1913, 0x0, 0x0, 0x19040000, 0x0, + 0x1916000000000000, 0x1931193519190000, 0x1938193c, 0x0, + 0x191c0000, 0x0, 0x0, 0x0, 0x1922000000000000, 0x0, 0x0, + 0x19250000, 0x192800000000, 0x192b000000000000, 0x0, 0x192e, 0x0, + 0x0, 0x191f0000, 0x0, 0x0, 0x193f00000000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1942, 0x0, + 0x1a3800000000, 0x1a3e00001a3b, 0x1a4400001a41, 0x1a4700000000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a4a000000000000, + 0x1a4d0000, 0x1a5600001a531a50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x5d50e550568, 0x6870e75062905e6, 0x71a060706cf06ac, + 0x77e07230734, 0x82c06af0e7e07a4, 0x6920770056b088d, + 0x9371a590e840e82, 0xe8e0e8c0a7d0a2e, 0xb79000006020e90, + 0xe8807870e7105d3, 0xba30cd31a5d1a5b, 0x86a0ea41a610a24, + 0x10ee10ec10ea1a63, 0xa110ae0123e123c, 0x10ec10ea086a0a24, + 0x123e123c11f0, 0x0, 0x0, 0x0, 0x1313, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xe86000000000000, 0xe900e660e8a09a0, 0xe980e940e920ad9, + 0x1a650ea00e9e0e9c, 0xed31a670ea20ed1, 0xeac0eaa0ea60ea8, + 0xeba0eb20eb00eae, 0xec00ebe0e790ebc, 0x6110ec40ec21a5f, + 0x116e0eca0ec80ec6, 0xa1305da0a0705cb, 0xa1905e00a1605dd, + 0xa6b06170a4a05fb, 0xa7a06260a71061d, 0xa7706230a740620, + 0xaa9064e0aa5064a, 0xad6067b0ad30678, 0xaef06840acc0671, + 0xb1906bd0afe069d, 0xb1c06c00b2206c6, 0xb2806cc0b2506c9, + 0xb5806fc0b6e0712, 0xbab072b0ba50725, 0xbd207490bb10731, + 0xbdf07560bd5074c, 0xc1207720bdc0753, 0xc1807780c150775, + 0xc4a07980c440792, 0xc50079e0c5307a1, 0xc7f07ca0c7707c2, + 0xc8a07d50c8607d1, 0xcef08380cec0835, 0xd1608510d0a0845, + 0xd20085b0d190854, 0xd3f08800d350876, 0xd3b087c0d2e086f, + 0xd4e089a0d420883, 0xd6308ac0d5708a0, 0xdc1090a0d6008a9, + 0xdc709100dca0913, 0xd7b08c40d7408bd, 0xdde09270ddb0924, + 0xde6093c0de30939, 0xdec09420def0945, 0xe0109540df50948, + 0xe18096b0e040957, 0xe3509850e2c097c, 0xd510b2b0e380988, + 0xd3509a60e210df2, 0x0, 0x9e905ad09fc05c0, 0x9b2057609b6057a, + 0x9ba057e09be0582, 0x9cf059309ff05c3, 0x9d7059b09cb058f, + 0xa0305c709d30597, 0xab6065b0ac20667, 0xa9306380a9f0644, + 0xa9b06400a8f0634, 0xac5066a0a97063c, 0xb68070c0b5c0700, + 0xc9f07ea0cc50810, 0xc6407af0c6807b3, 0xc6c07b70c7007bb, + 0xcb508000cc80813, 0xcbd08080cb107fc, 0xcc1080c0cb90804, + 0xd9508de0dbe0907, 0xdaa08f30dae08f7, 0xdb208fb0db608ff, + 0xe09095c0dba0903, 0xe1e09710e240974, 0xe120965, 0x0, + 0x10c1109f10be109c, 0x10d310b110ca10a8, 0xf160ef40f130ef1, + 0xf280f060f1f0efd, 0x110610fb110310f8, 0x110a10ff, + 0xf540f490f510f46, 0xf580f4d, 0x1145112311421120, + 0x11571135114e112c, 0xf8b0f690f880f66, 0xf9d0f7b0f940f72, + 0x119f1190119c118d, 0x11a7119811a31194, 0xfd20fc30fcf0fc0, + 0xfda0fcb0fd60fc7, 0x11e611db11e311d8, 0x11ea11df, + 0xffe0ff30ffb0ff0, 0x10020ff7, 0x122d121e122a121b, + 0x1235122612311222, 0x1025000010220000, 0x102d000010290000, + 0x1277125512741252, 0x128912671280125e, 0x106410421061103f, + 0x10761054106d104b, 0x10f510f2108f1088, 0x1175117211191112, + 0x1203120011d511d2, 0x124b1244, 0x10c510a310dc10ba, + 0x10d710b510ce10ac, 0xf1a0ef80f310f0f, 0xf2c0f0a0f230f01, + 0x114911271160113e, 0x115b113911521130, 0xf8f0f6d0fa60f84, + 0xfa10f7f0f980f76, 0x127b125912921270, 0x128d126b12841262, + 0x10681046107f105d, 0x107a10581071104f, 0x10e7108b10961099, + 0x10e310e000001092, 0xee80ee50eeb0eee, 0x2a1170002a0f35, + 0x116b111500200051, 0x116711640000111c, 0xf630f600f430f40, + 0x350031002d0faa, 0x118511811178117b, 0x118911ab00000000, + 0xfb40fb10fb70fba, 0x440040003c0000, 0x1213120f12061209, + 0x1217123911f511f2, 0x101610131019101c, 0x995001c0018100a, + 0x129d124700000000, 0x129912960000124e, 0x103c10390fed0fea, + 0x3900031083, 0x1000100010001, 0x1000100010001, 0x100010001, 0x0, + 0x1a690000, 0x4e000000000000, 0x0, 0x0, 0x0, 0x2ff02fc02fa, 0x0, + 0x1000000000000, 0x1a6f000000000000, 0x1a7e1a7b00001a72, 0x0, + 0xc0000008f, 0x0, 0x563000000000000, 0x920560, 0x0, 0x0, + 0x1a76000000000000, 0x0, 0x1000000000000, 0x0, 0x0, 0x0, 0x0, + 0xae00305, 0x392038303740365, 0x1aad02f403b003a1, + 0xb3b00a500a10544, 0x30f034303140305, 0x392038303740365, + 0x1aad02f403b003a1, 0xa500a10544, 0xb4107870a7d0692, + 0xa280b790b0d0e8c, 0x8400cd30b3b05d3, 0xba3, 0x0, 0x0, 0x83f, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe4d05e309a2099e, 0xe770a220a1e0000, + 0x6ac06020e500000, 0xe6d0b0d06ac06ac, 0xa28073406cf06cf, + 0x786077e0000, 0x82c083b06af0000, 0x82c082c, 0x897088f0863, + 0x77c0000060a, 0x5b0071a0000060a, 0xa7d000005e305d5, + 0x7230000067e0629, 0x136a136213540787, 0x68000000ae0136f, + 0x10060f3a10ec11ee, 0x1aab, 0xa7d0a2e05e60000, 0x73e0ae0, 0x0, + 0x3ca03c103e203da, 0x498045903d20455, 0x3de04e703d604cf, + 0x3be051104eb049c, 0x6de06d406d106cf, 0x91f091b091806b2, + 0x950094d068206e1, 0x72305e605e30734, 0xb3d0b330b300ae0, + 0xdd60dd20dcf086a, 0xdfd0dfa0b410b40, 0x5d30a2e09a00a28, 0x0, 0x0, + 0x30d0000, 0x0, 0x0, 0x0, 0x1a8d1a8600000000, 0x0, 0x0, 0x0, 0x0, + 0x1a9200000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1a981a9b1a950000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1aa0, 0x1aa50000, + 0x1aa8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1ab200001aaf, 0x0, + 0x1ac100001ab81ab5, 0x1ac4, 0x0, 0x0, 0x0, 0x1ac80000, + 0x1ace000000001acb, 0x1ad10000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1ad700000556, 0x0, 0x0, 0x55b054a1ad40000, 0x1add1ada, + 0x1ae31ae0, 0x1ae91ae6, 0x0, 0x1aef1aec, 0x1afb1af8, 0x1b011afe, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b131b101b0d1b0a, 0x0, + 0x0, 0x0, 0x0, 0x1b071b041af51af2, 0x0, 0x1b191b1600000000, + 0x1b1f1b1c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b371b350000, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x365030f03430314, 0x3a1039203830374, + 0x342032f031c03b0, 0x382037303640355, 0x3f703af03a00391, + 0xe600e200d900a3, 0xf600f200ee00ea, 0xb100ac00a700fa, + 0xc500c000bb00b6, 0xdd00d400cf00ca, 0x368035903460319, + 0x3a4039503860377, 0x3450332031f03b3, 0x385037603670358, + 0x3fa03b203a30394, 0x172016e016a0166, 0x182017e017a0176, + 0x192018e018a0186, 0x1a2019e019a0196, 0x1b201ae01aa01a6, + 0x1c201be01ba01b6, 0x5d5056801ca01c6, 0x67e062905e605e3, + 0x60706cf06ac0687, 0x77e07230734071a, 0x82c083b06af07a4, + 0x6b2056b088d085e, 0x60a095a06820770, 0xa2e09a009370692, + 0xb0d06020ad90a7d, 0xa280b79073e0ae0, 0xcd307870b3b05d3, + 0xba308400a1105d8, 0xb410de1086a0a24, 0x30506110695, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1abc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x552054f0542, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1b2c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6b2073e, 0x0, + 0x0, 0x0, 0x1b2f000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x227c000000000000, 0x0, 0x0, 0x0, 0x0, + 0x26b0000000000000, 0x0, 0x0, 0x0, 0x1f091f011efb1ee9, + 0x1f1b1f171f131f0d, 0x1f5f1f571f4b1f21, 0x1f871f771f6f1f67, + 0x1fb91fa51f8b1f89, 0x1fcd1fc71fc51fc1, 0x1feb1fe91fdd1fdb, + 0x204f20451ff71fef, 0x207f207d2079206f, 0x20b420ae20982087, + 0x20ce20cc20ca20c4, 0x20f820f220dc20da, 0x210f2108210020fc, + 0x212f212b21292113, 0x2141213921352131, 0x21972195218b214f, + 0x21e521e321d921d7, 0x32521f121ed21e9, 0x2260222303292211, + 0x227a2274226e2266, 0x228422822280227e, 0x230c230622e22286, + 0x231623122310230e, 0x2334233023222318, 0x235c235a23562354, + 0x2376237423622360, 0x238a238823862384, 0x23aa23a823a62394, + 0x23ee23de23dc23bc, 0x241c240a23fa23f6, 0x2452244c2442243e, + 0x245e245c245a2456, 0x247e247a246c246a, 0x248e248a24842482, + 0x2498249624922490, 0x250e250c24f224e8, 0x2530252c25282512, + 0x2558255425522534, 0x25742572255c255a, 0x2592258425822578, + 0x25ba25aa25982596, 0x25de25c825c425c2, 0x260025fa25e825e0, + 0x261a261826142608, 0x26262624261e261c, 0x263c263a2638262a, + 0x2658264e264a2648, 0x26622660265c265a, 0x2670266826662664, + 0x26862684267e267c, 0x2690268e268a2688, 0x26a0269c26982692, + 0x26aa26a826a626a2, 0x26b226ae, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b4900000000, + 0x1fd11fcf1fcd, 0x0, 0x0, 0x0, 0x0, 0x1b8100001b7e, 0x1b8700001b84, + 0x1b8d00001b8a, 0x1b9300001b90, 0x1b9900001b96, 0x1b9f00001b9c, + 0x1ba500001ba20000, 0x1ba80000, 0x0, 0x1bb100001bae1bab, + 0x1bba1bb700001bb4, 0x1bc01bbd0000, 0x1bc91bc6, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1b7b, 0x87000000000000, 0x1bcc1bd30000008a, 0x0, 0x0, 0x0, + 0x1c4300001c26, 0x1c9200001bf6, 0x1caf00001c9b, 0x1cca00001cbf, + 0x1cdc00001ccf, 0x1ceb00001ce1, 0x1cf700001cf20000, 0x1c100000, + 0x0, 0x1d3b00001d261d1d, 0x1d611d5700001d42, 0x1d7e1d760000, + 0x1caa1da1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1e44000000001c01, + 0x1e571e521e4d, 0x1ca11e6000000000, 0x0, 0x0, 0x0, 0x0, + 0x1a10194919440000, 0x19501a141a12194b, 0x1a181a1619571955, + 0x1a201a1e1a1c1a1a, 0x19661961195c19a6, 0x196f196d196819b0, + 0x198e198319811977, 0x1947199d19981993, 0x19de19dc19da19d8, + 0x198c19e419e219e0, 0x19ee19ec19ea19e8, 0x19f619f419f21975, + 0x19fe197f19fa19f8, 0x1a2219a419a219d4, 0x1a2a1a281a261a24, + 0x1a3019a81a2e1a2c, 0x19ae19ac19aa1a32, 0x19b819b619b419b2, + 0x19c019be19bc19ba, 0x19c819c619c419c2, 0x1a361a3419cc19ca, + 0x1a0019d219d019ce, 0x1a081a061a041a02, 0x1a0e1a0c1a0a, + 0x1f171ee900000000, 0x1efd1ef120471eef, 0x1ef71f0d23641ef3, + 0x1f212051208c1eeb, 0x1e901e001d701ce, 0x20d020401fb01f2, + 0x245023c02330225, 0x1db01d20257024e, 0x1ff01f601ed01e4, + 0x237022902110208, 0x25b025202490240, 0x21e0216022e, + 0x2a0026802700260, 0x284026402880274, 0x2c402b00290026c, + 0x2a402ec02b802c0, 0x2d002b402bc02ac, 0x2d402e402c80298, + 0x2a8029c0278028c, 0x29402e8027c02cc, 0x2e002dc028002d8, + 0x23fe21e321112021, 0x0, 0x0, 0x41c04110406082e, 0x440043904320427, + 0x475046e044e0447, 0x4850482047f047c, 0x19571950194b1944, + 0x196f19681961195c, 0x1993198e19831977, 0x194d1946199d1998, + 0x1963195e19591952, 0x198519791971196a, 0x199f199a19951990, + 0x1974197c1988, 0x20471eef1f171ee9, 0x1f5f1eed1f611f19, + 0x22e203291fcd1f0f, 0x204f25c822232286, 0x2240221b223b0325, + 0x23ca255e231c2007, 0x2098236823e21fab, 0x22961fdf1f4925a2, + 0x208a1f731f2b262c, 0x20fa1ef31efd1ef1, 0x20b220b81fc92001, + 0x1fd725681f292390, 0x48e048b04882083, 0x4b704b404b10491, + 0x4c304c004bd04ba, 0x4e404cc04c904c6, 0x4d604a3034e033b, + 0x5290518050304f2, 0x34d033a0327053a, 0x7390a7f0a8206b4, + 0x1c0a1bff1bf11bd8, 0x1c731c411c241c1a, 0x1cbd1cad1c991c90, + 0x1cdf1cda1ccd1c1e, 0x1bde1cf51cf01bfb, 0x1c8e1d111d0f1ca6, + 0x1d551d391d1b1d0d, 0x1ddc1c311d9f1d74, 0x1e041e001def1c22, + 0x1c351e1b1e191e11, 0x1e421c5d1e341bed, 0x1e551e501e4b, + 0x1beb1be51be01bda, 0x1c0c1c041bf91bf3, 0x1c331c201c1c1c13, + 0x1c2e1c291c3c1c37, 0x1c501c571c4b1c46, 0x1c6d1c661c5f1c5c, + 0x1c8b1c841c7d1c61, 0x1cb21ca81ca41c95, 0x1cd61cd21cc21cb7, + 0x1c811d031cfa1ce4, 0x1d291d351d171d0c, 0x1d4c1d451d201d30, + 0x1d6b1d641d3e1d51, 0x1d811d951d701d5a, 0x1d8f1d8a1d9b1d85, + 0x1db81da41dac1d79, 0x1dc51dbf1dbb1db2, 0x1dd61dd21dce1dca, + 0x1df11de61de31dde, 0x1e0b1e061c681df5, 0x1e291e241e1f1e13, + 0x1c6f1e391e361e2e, 0x3610352033f0311, 0x39d038e037f0370, + 0x33e032b03bb03ac, 0x37e036f03600351, 0x3ba03ab039c038d, + 0x4230418040d0402, 0x56a0a530b0f042e, 0xa590ce60c580a0f, + 0x210a06db0a600a5c, 0x223d21f920892200, 0xbea11b40c260cda, + 0x689075b071c0b7b, 0xc290cdd0b8c0a26, 0x6010bf611c011b7, + 0x68c07640b7e068d, 0xa560bfd11c30893, 0x11c60c350aec0b94, + 0xc030b970a300c00, 0xc070b9a0a340a33, 0xc1b0b9e0a380a37, + 0x7680b8206910c1f, 0xd000cfa0cf60690, 0xc0f11c90c380ce9, + 0xbed11ba0c2c0ce0, 0xc2f0ce3076c0b86, 0x76f0b890bf011bd, + 0x5d70999077b0bb4, 0x5e805ff0a2d0a2a, 0x6ae0b1306940a50, + 0xba20722071f0b3a, 0xbc60bc20bbf0bbc, 0x8200c0b0bf90bf3, + 0xd25082b08230cd5, 0x5d1092a09360869, 0x36c035d034a0337, + 0x3a80399038a037b, 0x3490336032303b7, 0x389037a036b035c, + 0x3fe03b603a70398, 0x42a041f04140409, 0x44a0443043c0435, + 0xaf4047804710451, 0x0, 0x0, 0x0, 0x0, 0x26b4, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xe730e6b, 0x0, 0x256a258422132556, + 0x26ae1ff91eff22c6, 0x202b25c8209226ae, 0x244a238221872090, + 0x25a8251e250424e6, 0x233c22f0229a2254, 0x1f11265225bc24ca, + 0x24e42302225e1fe3, 0x24dc22d620e6267a, 0x250a247821a12526, + 0x232822a6221d211f, 0x1fb31f7d1f3125ae, 0x23922300225a21d5, + 0x257e24ec24e02456, 0x23b02678266a2610, 0x25d624bc242c23d0, + 0x212d206d2540267e, 0x23b4231a24682408, 0x20d4206b260c2566, + 0x242422ca22b02256, 0x246e1fb125ec2438, 0x242e23e61f811f83, + 0x21a3254e25f024c8, 0x20be1f0525462254, 0x1fc3237223322159, + 0x1ef5214b1f3923b8, 0x1fed242221e12290, 0x253824cc23982063, + 0x21ab228c25962276, 0x1f1f237021bb24a8, 0x241822441f7f1f5d, + 0x1fb725c6253e2494, 0x21ef212720982011, 0x265625e423ba22d8, + 0x220f1fa5268c2680, 0x217b210d2590226c, 0x22f622d021d12189, + 0x2464243223e0234e, 0x25d8259c24d02588, 0x22ee201b1fa71f91, + 0x2155211d25382514, 0x232c2406227221b7, 0x20f020be20491f27, + 0x24502348233a215b, 0x2612260a25ca2460, 0x25c023da1f332630, + 0x1f451f15216725fe, 0x225421e720d020c0, 0x25a624d6238022fc, + 0x1fa325ea220726aa, 0x22be22a22233222d, 0x242023ae236e2340, + 0x25f221911f612636, 0x258a22b220e21f3d, 0x23322237216b2143, + 0x20d820091f9525f6, 0x2294224a222521fc, 0x251624462378233e, + 0x1fcb260425c4251c, 0x235022fe200b22c0, 0x2682266e25f824de, + 0x23f6247c22ae2231, 0x22ea2326240e23fc, 0x1f9724b01f23254c, + 0x241421a521151f8f, 0x258e220d229c20b6, 0x2123252c25ee250e, + 0x20371f4d, 0x220500002061, 0x238c232a1f850000, 0x23d823ce23cc23be, + 0x245224102616, 0x2544000024e2, 0x25b4259e0000, 0x2642264000000000, + 0x25fc25b026762644, 0x1faf1f511f471f35, 0x203d202f1fd51fb5, + 0x20d62065205f2041, 0x21792175216120da, 0x220921f321db2185, + 0x22ce22b622a82246, 0x23b22342230822f8, 0x23c623c223c42240, + 0x23d623d423ca23c8, 0x2432240023f223e8, 0x24582444243a2436, + 0x24ce249a249a2480, 0x254a2548252e2522, 0x259e259a256e256c, + 0x215d263426282606, 0x248c274b, 0x1f2f1f5b1f7b1ef9, + 0x1fbb1fad1f651f4f, 0x203b202d2025202f, 0x2094208e20692061, + 0x2125212120aa20a2, 0x21712165214d213d, 0x2185217321792169, + 0x21c921c521bf2193, 0x221f221d220521dd, 0x22a22276226e2229, + 0x22dc22ce22c422c8, 0x2324230a23a422f8, 0x236a2358234a232a, + 0x238e238c237e237c, 0x23b6239e23a02396, 0x2428240c240023f4, + 0x24b2245824402432, 0x252a2524250024c6, 0x253c2544253a252e, + 0x254a254225462548, 0x25a4258c256e2550, 0x260625f425ce25be, + 0x262e262826202616, 0x272126ae265e2634, 0x1ea11e8d2733271f, + 0x27a9277927671ea3, 0x26ac26a4, 0x0, 0xade0ae30adf0adb, + 0xd280d280ae2, 0x0, 0x0, 0x134e000000000000, 0x134b135113481345, + 0x0, 0x13d2000013850000, 0x1374136f135413a6, 0x13b7139b1360138e, + 0x13ca13c702f413cd, 0x1359135613c313bf, 0x1371136c1364135c, + 0x137f137c1376, 0x1390138b13881382, 0x139d00001398, + 0x13a8000013a313a0, 0x13b413b1000013ab, 0x137913cf13bc13b9, + 0x135f13ae13931367, 0x181e181e18181818, 0x18201820181e181e, + 0x1824182418201820, 0x181c181c18241824, 0x18221822181c181c, + 0x181a181a18221822, 0x183c183c181a181a, 0x183e183e183c183c, + 0x18281828183e183e, 0x1826182618281828, 0x182a182a18261826, + 0x182c182c182a182a, 0x18321832182c182c, 0x1834183418301830, + 0x18381838182e182e, 0x1840184018361836, 0x1844184418401840, + 0x1848184818441844, 0x1846184618481848, 0x184a184a18461846, + 0x184c184c184c184c, 0x18501850186d186d, 0x184e184e18501850, + 0x15911591184e184e, 0x186a186a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1842000000000000, 0x1803184218421842, 0x180717ff17ff1803, + 0x18621862185b1807, 0x1860186018551855, 0x180b180b180b180b, + 0x17cd17cd14151415, 0x17f117f1180d180d, 0x17fd17fd18011801, + 0x1809180918051805, 0x17f517f517f51809, 0x1864186418641864, + 0x17f517e517d517d1, 0x13fe13f713f417f9, 0x141e14171414140b, + 0x146a144d1438142d, 0x1484147b1472146d, 0x14311422148c1487, + 0x143c14d414d11435, 0x151a150c150514fa, 0x15a515a215931562, + 0x15c815c515ba15b0, 0x1607157515e415df, 0x16451642163f160a, + 0x165b16561653164c, 0x1679167416711662, 0x16851682167f167c, + 0x16aa169616931688, 0x1579158c16c816b9, 0x14591455145116e0, + 0x172d1461145d1526, 0x17691758174f1740, 0x177f17741771176c, + 0x17aa17a3179c1782, 0x14e417c717c417b3, 0x64005d179714ee, + 0x8000790072006b, 0x17e917e517e117dd, 0x140813db17f917f5, + 0x14171414140e140b, 0x1464144d144a1447, 0x14781475146d146a, + 0x14871484147e147b, 0x1674167116561653, 0x1693168816851679, + 0x16e01579158c1696, 0x17551752152616e5, 0x176c176917631758, + 0x17b317b017ad1797, 0x17d117c717c417be, 0x17ed17e517d917d5, + 0x140b13fe13f713f4, 0x1438142d141e1411, 0x148c147b1467144d, + 0x14d1143514311422, 0x150c150514fa143c, 0x1593156d1562151a, + 0x15ba15b015a515a2, 0x157515e415df15c5, 0x1642163f160a1607, + 0x1662165b164c1645, 0x16851682167f167c, 0x16c816b916aa1688, + 0x1455145113e0158c, 0x1740172d15261459, 0x177117661758174f, + 0x17a3179c17851774, 0x17e515ed17b317aa, 0x144d1411140b17ed, + 0x151a1481147b1467, 0x16851557154c1529, 0x17661758158c1688, + 0x162c162515ed17b3, 0x15ff15da15d71633, 0x152c161c16191602, + 0x1490155d155a152f, 0x1440142a142613fb, 0x15bd159d159a1402, + 0x1546153b153415c0, 0x157015171549154c, 0x15ff15da15d715b7, + 0x152c161c16191602, 0x1490155d155a152f, 0x1440142a142613fb, + 0x15bd159d159a1402, 0x1546153b153415c0, 0x157015171549154c, + 0x1546153b153415b7, 0x15c815571529154c, 0x1534150c150514fa, + 0x15df15c81546153b, 0x13e313e3, 0x0, 0x0, 0x0, 0x0, + 0x1434143014301421, 0x145814541450143b, 0x14c114c514a314a3, + 0x1521150114fd1508, 0x15251525151d1521, 0x153e159615651565, + 0x154f154f1537153e, 0x15b315a815531553, 0x15cf15cb15cb15b3, + 0x15f315f315e715d3, 0x16111615160d15f7, 0x1669166516481648, + 0x16ad16c016c416bc, 0x16d216cb16cb16ad, 0x170b170216fe16d2, + 0x1716171216f316eb, 0x177716ef00000000, 0x173417471743177b, + 0x175b175f17381734, 0x1429140117b617b6, 0x1460143f14431425, + 0x14a7148f14ab145c, 0x15ac15421569150f, 0x179f17a616d616b5, + 0x174b166d172117ba, 0x168f15fb16bc1665, 0x168b16b1171a1730, + 0x14ba1493173016b1, 0x168b13fa164f16f7, 0x173c1513159615e7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x13d913de165e158f, + 0x15eb14e915731706, 0x1497157c1578158a, 0x14f1, 0x0, 0x0, 0x0, 0x0, + 0x5401b331b3102f6, 0x1b770093008d0546, 0x2ff1b79, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x9931a6b1a6d02fc, 0xe3b00a500a10993, 0x1b451b4f1b4b0e3f, + 0x1b351b3b1b391b47, 0x1b411b3f1b3d1b37, 0x98b000000001b43, + 0xc000c000c098f, 0x99309930993000c, 0x2fa1b3102f6, + 0x8d009305400546, 0xe3b00a500a11a6d, 0x971b4f1b4b0e3f, + 0x2f802f402f2009d, 0x54405590548, 0x566009b0099098d, 0x0, + 0x5a161f0057, 0x1622006800000061, 0x163000761629006f, + 0x163a00841637007d, 0x13e913e613e613d5, 0x13ec178f178f13e9, + 0x17ca17ca17ca13ec, 0x13f213d713d717ca, 0x141a13f213f213f2, + 0x141c141c141c141a, 0x147014701470141c, 0x13f513f513f51470, + 0x13f813f813f813f5, 0x13ff13ff13ff13f8, 0x14e214e014e013ff, + 0x140913dc13dc14e2, 0x14f814f814f81409, 0x15321532153214f8, + 0x1560156015601532, 0x15a015a015a01560, 0x15c315c315c315a0, + 0x15dd15dd15dd15c3, 0x15e215e215e215dd, 0x16051605160515e2, + 0x163d163d163d1605, 0x165916591659163d, 0x1677167716771659, + 0x14ec14ec14ec1677, 0x140c140c140c14ec, 0x140f140f140f140c, + 0x13e113e113e1140f, 0x14151788178813e1, 0x13fc13fc13fc1415, + 0x16a2169e169e13fc, 0x169b16a616a616a2, 0x169b, 0x970095008d0000, + 0x9f009d009b0099, 0x2f402f200a500a1, 0x30302fa02f802f6, + 0x30f034303140305, 0x392038303740365, 0x546054003b003a1, + 0x93055905440548, 0x5e305d505680566, 0x687067e062905e6, + 0x71a060706cf06ac, 0x7a4077e07230734, 0x85e082c083b06af, + 0x77006b2056b088d, 0x98b060a095a0682, 0x9930991098f098d, + 0x9a0093706920995, 0x6020ad90a7d0a2e, 0xb79073e0ae00b0d, + 0x7870b3b05d30a28, 0x8400a1105d80cd3, 0xde1086a0a240ba3, + 0xe3b061106950b41, 0x1b280e410e3f0e3d, 0x1b3f1b3d1b331b2a, + 0x1bd61e551e5c1b31, 0x1c181c081bfd1bef, 0x1cee1e171e0f1e02, + 0x1bff1bf11bd81c16, 0x1c411c241c1a1c0a, 0x1cad1c991c901c73, + 0x1cda1ccd1c1e1cbd, 0x1cf51cf01bfb1cdf, 0x1d111d0f1ca61bde, + 0x1d391d1b1d0d1c8e, 0x1c311d9f1d741d55, 0x1e001def1c221ddc, + 0x1e1b1e191e111e04, 0x1c5d1e341bed1c35, 0x8b00881c061e42, + 0x1a101949194419d4, 0x19501a141a12194b, 0x1a181a1619571955, + 0x1a201a1e1a1c1a1a, 0x19661961195c19a6, 0x196f196d196819b0, + 0x198e198319811977, 0x199d19981993, 0x19d8194700000000, + 0x19e019de19dc19da, 0x19e419e200000000, 0x19ec19ea19e8198c, + 0x197519ee00000000, 0x19f819f619f419f2, 0x197f19fa00000000, 0x19fe, + 0x90e4b0e450e43, 0x1a820e470e49, 0x1a8b1a891a841b22, + 0x1b261b241a90, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x26b600000000, 0x26b9, 0x0, 0x0, 0x26bc000000000000, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x26c226bf00000000, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x26c826c500000000, + 0x26d726d326cf26cb, 0x26db, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x26df000000000000, 0x26e626ed26e226ea, 0x26f1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5e605e305d50568, + 0x6ac0687067e0629, 0x734071a060706cf, 0x6af07a4077e0723, + 0x88d085e082c083b, 0x682077006b2056b, 0x9370692060a095a, + 0xad90a7d0a2e09a0, 0x73e0ae00b0d0602, 0xb3b05d30a280b79, + 0xa1105d80cd30787, 0x86a0a240ba30840, 0x61106950b410de1, + 0x5e605e305d50568, 0x6ac0687067e0629, 0x734071a060706cf, + 0x6af07a4077e0723, 0x88d085e082c083b, 0x682077006b2056b, + 0x9370692060a095a, 0xad90a7d0a2e09a0, 0x73e0ae000000602, + 0xb3b05d30a280b79, 0xa1105d80cd30787, 0x86a0a240ba30840, + 0x61106950b410de1, 0x5e605e305d50568, 0x6ac0687067e0629, + 0x734071a060706cf, 0x6af07a4077e0723, 0x88d085e082c083b, + 0x682077006b2056b, 0x9370692060a095a, 0xad90a7d0a2e09a0, + 0x73e0ae00b0d0602, 0xb3b05d30a280b79, 0xa1105d80cd30787, + 0x86a0a240ba30840, 0x61106950b410de1, 0x5e605e300000568, + 0x68700000000, 0x71a06070000, 0x6af07a4077e0000, 0x88d085e0000083b, + 0x682077006b2056b, 0x9370692060a095a, 0xad900000a2e09a0, + 0x73e0ae00b0d0000, 0xb3b05d30a280b79, 0xa1105d80cd30000, + 0x86a0a240ba30840, 0x61106950b410de1, 0x5e605e305d50568, + 0x6ac0687067e0629, 0x734071a060706cf, 0x6af07a4077e0723, + 0x88d085e082c083b, 0x682077006b2056b, 0x9370692060a095a, + 0xad90a7d0a2e09a0, 0x73e0ae00b0d0602, 0xb3b05d30a280b79, + 0xa1105d80cd30787, 0x86a0a240ba30840, 0x61106950b410de1, + 0x5e6000005d50568, 0x687067e0629, 0x734071a06070000, + 0x6af07a4077e0723, 0x88d085e0000083b, 0x682077006b2056b, + 0x93706920000095a, 0xad90a7d0a2e09a0, 0x73e0ae00b0d0602, + 0xb3b05d30a280b79, 0xa1105d80cd30787, 0x86a0a240ba30840, + 0x61106950b410de1, 0x5e6000005d50568, 0x687067e0629, + 0x734071a060706cf, 0x7a400000723, 0x88d085e00000000, + 0x682077006b2056b, 0x93706920000095a, 0xad90a7d0a2e09a0, + 0x73e0ae00b0d0602, 0xb3b05d30a280b79, 0xa1105d80cd30787, + 0x86a0a240ba30840, 0x61106950b410de1, 0x5e605e305d50568, + 0x6ac0687067e0629, 0x734071a060706cf, 0x6af07a4077e0723, + 0x88d085e082c083b, 0x682077006b2056b, 0x9370692060a095a, + 0xad90a7d0a2e09a0, 0x73e0ae00b0d0602, 0xb3b05d30a280b79, + 0xa1105d80cd30787, 0x86a0a240ba30840, 0x61106950b410de1, + 0x6af07a4077e0723, 0x88d085e082c083b, 0x682077006b2056b, + 0x9370692060a095a, 0xad90a7d0a2e09a0, 0x73e0ae00b0d0602, + 0xb3b05d30a280b79, 0xa1105d80cd30787, 0x9370692060a095a, + 0xad90a7d0a2e09a0, 0x73e0ae00b0d0602, 0xb3b05d30a280b79, + 0xa1105d80cd30787, 0x86a0a240ba30840, 0x61106950b410de1, + 0x5e605e305d50568, 0x6ac0687067e0629, 0x734071a060706cf, + 0x6af07a4077e0723, 0x88d085e082c083b, 0x682077006b2056b, + 0x9370692060a095a, 0xad90a7d0a2e09a0, 0x73e0ae00b0d0602, + 0xb3b05d30a280b79, 0xa1105d80cd30787, 0x86a0a240ba30840, + 0x61106950b410de1, 0x5e605e305d50568, 0x6ac0687067e0629, + 0x734071a060706cf, 0x6af07a4077e0723, 0x61106950b410de1, 0xe800e6f, + 0xf3c0f3a0f380ee3, 0xfad0f5e0f5c0f3e, 0xfe20fe00fde0faf, + 0x10060fe80fe60fe4, 0x100f100d0fad1008, 0x1035103310311011, + 0x10ea10861aa3077c, 0x110e10f010ee10ec, 0x11ae1170116e1110, + 0x11ce11cc11b211b0, 0x11f811f011ee11d0, 0x123c11fe11fc11fa, + 0x1a9e12421240123e, 0x123c11ae116e10f0, 0xf380ee311ee11f0, + 0xf5c0f3e0f3c0f3a, 0xfde0faf0fad0f5e, 0xfe60fe40fe20fe0, + 0xfad100810060fe8, 0x10311011100f100d, 0x1aa3077c10351033, + 0x10ee10ec10ea1086, 0x116e1110110e10f0, 0x11b211b011ae1170, + 0x11ee11d011ce11cc, 0x11fc11fa11f811f0, 0x1240123e123c11fe, + 0x116e10f01a9e1242, 0x11ee11f0123c11ae, 0xf3c0f3a0f380ee3, + 0xfad0f5e0f5c0f3e, 0xfe20fe00fde0faf, 0x10060fe80fe60fe4, + 0x100f100d0fad1008, 0x1035103310311011, 0x10ea10861aa3077c, + 0x110e10f010ee10ec, 0x11ae1170116e1110, 0x11ce11cc11b211b0, + 0x11f811f011ee11d0, 0x123c11fe11fc11fa, 0x1a9e12421240123e, + 0x123c11ae116e10f0, 0xf380ee311ee11f0, 0xf5c0f3e0f3c0f3a, + 0xfde0faf0fad0f5e, 0xfe60fe40fe20fe0, 0xfad100810060fe8, + 0x10311011100f100d, 0x1aa3077c10351033, 0x10ee10ec10ea1086, + 0x116e1110110e10f0, 0x11b211b011ae1170, 0x11ee11d011ce11cc, + 0x11fc11fa11f811f0, 0x1240123e123c11fe, 0x116e10f01a9e1242, + 0x11ee11f0123c11ae, 0xf3c0f3a0f380ee3, 0xfad0f5e0f5c0f3e, + 0xfe20fe00fde0faf, 0x10060fe80fe60fe4, 0x100f100d0fad1008, + 0x1035103310311011, 0x10ea10861aa3077c, 0x110e10f010ee10ec, + 0x11ae1170116e1110, 0x11ce11cc11b211b0, 0x11f811f011ee11d0, + 0x123c11fe11fc11fa, 0x1a9e12421240123e, 0x123c11ae116e10f0, + 0x12a212a011ee11f0, 0x314030500000000, 0x3740365030f0343, + 0x3b003a103920383, 0x30f034303140305, 0x392038303740365, + 0x314030503b003a1, 0x3740365030f0343, 0x3b003a103920383, + 0x30f034303140305, 0x392038303740365, 0x314030503b003a1, + 0x3740365030f0343, 0x3b003a103920383, 0x14e013f513f213d7, + 0x13f8140917880000, 0x14ec167713fc15c3, 0x15e214f8140f140c, + 0x13dc16591560163d, 0x13ff1470141c1532, 0x160515dd15a014e2, + 0x1816183a184a1814, 0x13f513f20000, 0x13f80000000013e1, + 0x14ec167713fc0000, 0x15e214f8140f140c, 0x16591560163d, + 0x13ff1470141c1532, 0x1605000015a00000, 0x0, 0x13f500000000, + 0x13f8000000000000, 0x14ec000013fc0000, 0x15e214f8140f0000, + 0x165915600000, 0x13ff000000001532, 0x1605000015a00000, + 0x18160000184a0000, 0x13f513f20000, 0x13f80000000013e1, + 0x167713fc15c3, 0x15e214f8140f140c, 0x16591560163d, + 0x13ff1470141c1532, 0x160515dd15a00000, 0x183a00001814, + 0x14e013f513f213d7, 0x13f81409178813e1, 0x14ec000013fc15c3, + 0x15e214f8140f140c, 0x13dc16591560163d, 0x13ff1470141c1532, + 0x160515dd15a014e2, 0x0, 0x14e013f513f20000, 0x13f8140917880000, + 0x14ec000013fc15c3, 0x15e214f8140f140c, 0x13dc16591560163d, + 0x13ff1470141c1532, 0x160515dd15a014e2, 0x0, 0x3f103160307030a, + 0x4fa04de04ab0468, 0x5310520050b, 0x0, 0x10a0106010200fe, + 0x11a01160112010e, 0x12a01260122011e, 0x13a01360132012e, + 0x14a01460142013e, 0x15a01560152014e, 0x5e31b4d0162015e, + 0x93305e5082c, 0x5e605e305d50568, 0x6ac0687067e0629, + 0x734071a060706cf, 0x6af07a4077e0723, 0x88d085e082c083b, + 0x682077006b2056b, 0x76c06b1060a095a, 0x930082708660860, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x761075e00000000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x606, 0x0, 0x0, 0x0, 0x1cad1c9e1bc3, 0x0, 0x0, + 0x0, 0x1cf71ff320b02197, 0x208c253220811f17, 0x21e722f221fe1f1d, + 0x21eb1f6921451f9d, 0x2560235c24261f93, 0x219d22cc200f2073, + 0x25a01eef1ee921b3, 0x21ad20011efd20fa, 0x23f023d221992574, + 0x329221b22bc2005, 0x20351f9f2366, 0x0, 0x1b5d1b551b511b69, + 0x1b591b711b611b6d, 0x1b65, 0x0, 0x1ffd2147, 0x0, 0x0, 0x0, + 0x26f51f0b1f031f07, 0x1f3b1f371f351f2d, 0x1f431f471f411f3f, + 0x1f531f5126fd1e63, 0x1e6526f71f631f55, 0x1f7126fb1f691f59, + 0x1f7b1f791f251f75, 0x1e691f8d1f8927b9, 0x1fa11f9f1f9b1f99, + 0x1fb51faf1fad1e6b, 0x1fc31fbf1fbd1fbb, 0x1fe11fd91fd51fd3, + 0x1fe71fe71fe71fe5, 0x1ff51ff122e42703, 0x20031fff1ffb2705, + 0x20152013200d2017, 0x2023201f201d2019, 0x202d202920292027, + 0x204b203920332031, 0x2043203f204d203d, 0x2057205520711f8f, + 0x205b205d20532059, 0x2077207527072067, 0x209620852081207b, + 0x209e209c270b2709, 0x1e6d20a4209a20a0, 0x20ac20ac20a81e6f, + 0x20be20bc20ba270d, 0x20c820c6270f20c2, 0x20d21e7120cc2137, + 0x271320de20e020da, 0x20e820ea271520e4, 0x1e7320f620f420ec, + 0x21062104210220fe, 0x21171e7727171e75, 0x27cd211f211b2119, + 0x2486271b271b212b, 0x27291e7921332133, 0x1e7b213f213b277d, + 0x2157215321512149, 0x21611e7d1e7f215f, 0x216f216d2163271d, + 0x21792177216f2171, 0x2183217f217d2181, 0x218f210b21872185, + 0x21b121a7219f219b, 0x21b521a921af2723, 0x21c7272521c321b9, + 0x21cb1e8121bd21c1, 0x1e8321cd21d321cf, 0x21f5272721df21db, + 0x22091e8922032215, 0x1f6d1f6b1e851e87, 0x1ebb2470220b2217, + 0x222b2221221f221d, 0x22351e8b27312227, 0x273522462242222f, + 0x1e8d224c22392248, 0x225822522250224e, 0x22621e8f225c2737, + 0x226a1e9122642739, 0x273b227822762270, 0x273f2288273d2711, + 0x2298228a2292228e, 0x22a422a222a822a0, 0x229e274122ac22aa, + 0x22c41e9322ba22b8, 0x22d222b4274322c2, 0x22de22d427472745, + 0x22e01e9522da22dc, 0x26f922ec22e622e8, 0x274d22fa274922f4, + 0x274f2314230a2304, 0x275327512320231e, 0x23381e972336232e, + 0x234623441e991e99, 0x1e9b2352234c234a, 0x2757236c2755235e, + 0x2759237a27192372, 0x1e9f1e9d275d275b, 0x2763275f27612396, + 0x239c239c239a2765, 0x1ea523a21ea323a0, 0x23b023ac27691ea7, + 0x23c8276b1ea923b6, 0x23e423d8276f276d, 0x23ec23ea23e81eab, + 0x23f8277327732771, 0x2404240227751ead, 0x1eb1241227771eaf, + 0x277b241e2416241a, 0x243424301eb3242a, 0x2781277f1eb5243c, + 0x2785244827831eb7, 0x278724582454244e, 0x2466278b24622789, + 0x247424721eb9272b, 0x278d20a624761ebd, 0x2486272f272d278f, + 0x249e1ebf25942488, 0x24a21fa924a0249c, 0x279124aa24a624a4, + 0x24b824b624ac24a8, 0x24ce24c424ba24ae, 0x24c224c024be24b4, + 0x1ec1279527972793, 0x279f24d824d424d2, 0x1ec51ec3279924da, + 0x24ea1ec7279d279b, 0x24f624f024ee24ec, 0x250024f824fa24f4, + 0x1ec9250224fe24fc, 0x25101ecb25082506, 0x251a251827a12512, + 0x27a31e6725201ecd, 0x25361ed11ecf27a5, 0x27a7255825502542, + 0x2576257025642562, 0x257a257c26ff27ab, 0x258c258627012580, + 0x25b225ac27af27ad, 0x25cc25b827b125b6, 0x25da25d025d425d2, + 0x1ed325e227b325dc, 0x26021ed527b525e6, 0x27bb27b7260e20ee, + 0x27bd26221ed91ed7, 0x262e262e27bf1edb, 0x1edd263e27c12632, + 0x26542650264c2646, 0x266c265e27c31edf, 0x26741ee31ee12672, + 0x27c927c71ee527c5, 0x26901ee7268627cb, 0x269e269a26962694, + 0x27cf26a2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); + //12288 bytes + enum canonMappingTrieEntries = TrieEntry!(ushort, 8, 7, 6)([0x0, 0x20, + 0x120], [0x100, 0x400, 0x1380], [0x302020202020100, + 0x205020202020204, 0x602020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x1000000000000, 0x5000400030002, 0x6, + 0x9000800070000, 0xc0000000b000a, 0x0, 0xe00000000000d, 0x0, 0x0, + 0x1100000010000f, 0x130012, 0x16001500140000, 0x18000000170000, + 0x1a000000190000, 0x0, 0x1c001b0000, 0x1d, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f001e, 0x0, 0x0, 0x23002200210020, + 0x27002600250024, 0x28, 0x2b002a00000029, 0x2f002e002d002c, 0x30, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x31000000000000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x34003300320000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x38003700360035, 0x3c003b003a0039, 0x3e003d, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x3f00000000, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x43004200410000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x47004600450044, 0x4b004a00490048, 0x4c, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x250012000f000c, 0x850000004f0045, 0xcb00a400a1009e, + 0x13301240121011e, 0x1a0019d01880000, 0x1da01b601a3, + 0x2730270026d0000, 0x2f30287, 0x33803250322031f, 0x398000003620358, + 0x3de03b703b403b1, 0x446043a04370434, 0x4b404b1049c0000, + 0x4ee04ca04b7, 0x58a058705840000, 0x61c0000060d059e, + 0x33e002b033b0028, 0x38c00790380006d, 0x392007f038f007c, + 0x3a2008f03950082, 0x3cd00ba00000000, 0x3db00c803d800c5, + 0x3e400d103fb00e8, 0x41000fd040a00f7, 0x419010604130100, 0x41c0109, + 0x440012a043d0127, 0x45c01490443012d, 0x130, 0x471015d0462014f, + 0x170047701630000, 0x47a01660484, 0x185000000000000, + 0x18e04a801940499, 0x4a2, 0x4e401d004d901c5, 0x4f801e4, + 0x5450231052f021b, 0x54b023705350221, 0x56902550552023e, + 0x57b026405580244, 0x572025b, 0x594027d058d0276, 0x5b4029d059b0284, + 0x5e002c905b702a0, 0x61002f605f502de, 0x3110628030b0302, + 0x6310314062e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x50401f0, + 0x0, 0x0, 0x2ac000000000000, 0x5c3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x13d036900560000, 0x2a304fb01e70450, 0x28e05a9029205ba, + 0x28a05ad029605a5, 0x35b0048000005a1, 0x653064a03540041, + 0x416010300000000, 0x522020e046b0157, 0x65f065c05250211, 0x465, + 0x40700f4, 0x365005204960182, 0x656064d06500647, 0x36f005c036c0059, + 0x3ea00d703e700d4, 0x456014304530140, 0x50101ed04fe01ea, + 0x53b022705380224, 0x5c002a905bd02a6, 0x578026105660252, + 0x425011200000000, 0x0, 0x351003e00000000, 0x4f101dd03f400e1, + 0x4e701d304d101bd, 0x61602fc04ea01d6, 0x0, 0x0, 0x0, + 0x66b00000010000d, 0x137, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x662, 0x0, 0x100000000, 0x0, 0x6450670063d0000, + 0x72c06df06c3, 0x798077800000759, 0x8d1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x781073500000000, 0x8c10867084707e9, 0x92f, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x92808ca00000000, 0x95f091f08fd, 0x9b4000000000000, 0x9b7, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x9c3000009cc09c6, 0x9ba000000000000, 0x0, 0x9ed09d809e4, 0x0, 0x0, + 0x9de0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa200000, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa0500000a0e0a08, 0xa41000000000000, 0x0, + 0xa2f0a1a0a26, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa470a4400000000, 0x0, + 0x0, 0xa1109cf0000, 0x0, 0x0, 0x0, 0xa0209c009ff09bd, + 0xa0b09c900000000, 0xa4d0a4a00000000, 0xa1709d50a1409d2, + 0xa1d09db00000000, 0xa2909e70a2309e1, 0xa530a5000000000, + 0xa2c09ea0a3e09fc, 0xa3509f30a3209f0, 0xa3809f6, 0xa3b09f9, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xac10abe00000000, + 0xaca0ac40ac7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xacd00000ad3, 0x0, + 0x0, 0x0, 0xad0000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xae80000, 0x0, 0xaf10000, 0xaf4, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xadf0adc0ad90ad6, 0xaee0aeb0ae50ae2, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb00000000000000, 0xb03, 0x0, + 0x0, 0x0, 0xafd00000afa0af7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xb12000000000000, 0xb1500000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xb0c0b090b060000, 0xb0f00000000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb1e000000000b21, 0xb24, 0x0, 0x0, + 0x0, 0xb1b0b18, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xb27, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xb300b2a00000000, 0xb2d, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb33, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb36, + 0xb40000000000000, 0xb3c0b3900000b43, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb4c0b4600000000, + 0xb49, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb4f00000000, 0xb590b550b52, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb5f000000000000, 0x0, 0x0, + 0xb620000, 0xb6500000000, 0xb68000000000000, 0x0, 0xb6b, 0x0, 0x0, + 0xb5c0000, 0x0, 0xb6e000000000000, 0xb890b710000, 0xb8c, 0x0, + 0xb740000, 0x0, 0x0, 0x0, 0xb7a000000000000, 0x0, 0x0, 0xb7d0000, + 0xb8000000000, 0xb83000000000000, 0x0, 0xb86, 0x0, 0x0, 0xb770000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb8f00000000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb9200000000, 0xb9800000b95, + 0xb9e00000b9b, 0xba100000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xba4000000000000, 0xba70000, 0xbb000000bad0baa, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x3830070037d006a, 0x389007603860073, 0x39f008c039b0088, + 0x3ae009b03a50092, 0x3ab009803a80095, 0x3d400c103d000bd, + 0x40100ee03fe00eb, 0x40400f103f700e4, 0x41f010c040d00fa, + 0x422010f04280115, 0x42e011b042b0118, 0x4490136045f014c, + 0x46e015a04680154, 0x47d016904740160, 0x48a01760480016c, + 0x48d017904870173, 0x493017f0490017c, 0x4a50191049f018b, + 0x4ab019704ae019a, 0x4d501c104cd01b9, 0x4e001cc04dc01c8, + 0x52c021805290215, 0x53e022a0532021e, 0x54802340541022d, + 0x55f024b05550241, 0x55b0247054e023a, 0x56c02580562024e, + 0x581026a0575025e, 0x5dd02c6057e0267, 0x5e302cc05e602cf, + 0x597028005900279, 0x5ec02d505e902d2, 0x5f202db05ef02d8, + 0x5f802e105fb02e4, 0x60402ea060102e7, 0x61902ff060702ed, + 0x6340317062b030e, 0x56f04310637031a, 0x6590000062205fe, 0x0, + 0x35f004c0372005f, 0x3280015032c0019, 0x330001d03340021, + 0x345003203750062, 0x34d003a0341002e, 0x379006603490036, + 0x3e100ce03ed00da, 0x3be00ab03ca00b7, 0x3c600b303ba00a7, + 0x3f000dd03c200af, 0x4590146044d013a, 0x4f501e1051b0207, + 0x4ba01a604be01aa, 0x4c201ae04c601b2, 0x50b01f7051e020a, + 0x51301ff050701f3, 0x5170203050f01fb, 0x5b1029a05da02c3, + 0x5c602af05ca02b3, 0x5ce02b705d202bb, 0x60a02f005d602bf, + 0x61f030506250308, 0x61302f9, 0x0, 0x81b07f9081807f6, + 0x82d080b08240802, 0x69e067c069b0679, 0x6b0068e06a70685, + 0x858084d0855084a, 0x85c0851, 0x6d406c906d106c6, 0x6d806cd, + 0x89308710890086e, 0x8a50883089c087a, 0x70706e5070406e2, + 0x71906f7071006ee, 0x8eb08dc08e808d9, 0x8f308e408ef08e0, + 0x74a073b07470738, 0x7520743074e073f, 0x90e0903090b0900, 0x9120907, + 0x76a075f0767075c, 0x76e0763, 0x949093a09460937, 0x9510942094d093e, + 0x787000007840000, 0x78f0000078b0000, 0x98b096909880966, + 0x99d097b09940972, 0x7c0079e07bd079b, 0x7d207b007c907a7, + 0x847084407e907e2, 0x8c108be08670860, 0x91f091c08fd08fa, 0x95f0958, + 0x81f07fd08360814, 0x831080f08280806, 0x6a2068006b90697, + 0x6b4069206ab0689, 0x897087508ae088c, 0x8a9088708a0087e, + 0x70b06e907220700, 0x71d06fb071406f2, 0x98f096d09a60984, + 0x9a1097f09980976, 0x7c407a207db07b9, 0x7d607b407cd07ab, + 0x84107e507f007f3, 0x83d083a000007ec, 0x670066d06730676, + 0x8bc000006bd, 0x8b9086306400000, 0x8b508b20000086a, + 0x6df06dc06c306c0, 0xbb90bb60bb30726, 0x8d108cd08c408c7, + 0x8d508f700000000, 0x72c0729072f0732, 0xbc20bbf0bbc0000, + 0x92f092b09220925, 0x933095509190916, 0x7780775077b077e, + 0x31d063d063a0772, 0x9b1095b00000000, 0x9ad09aa00000962, + 0x798079507590756, 0x64307df, 0xbc70bc5, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x79300000000, 0x4f015200000000, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xbcc0bc900000000, 0x0, 0x0, 0x0, 0x0, 0xbcf00000000, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xbd50bd80bd20000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbdb, 0xbde0000, + 0xbe1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbe700000be4, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xbea0000, 0xbf0000000000bed, 0xbf30000, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xbf900000006, 0x0, 0x0, 0x900030bf60000, 0xbff0bfc, + 0xc050c02, 0xc0b0c08, 0x0, 0xc110c0e, 0xc1d0c1a, 0xc230c20, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc350c320c2f0c2c, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xc290c260c170c14, 0x0, 0xc3b0c3800000000, 0xc410c3e, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xc490c470000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xc44, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xc5100000c4e, 0xc5700000c54, 0xc5d00000c5a, 0xc6300000c60, + 0xc6900000c66, 0xc6f00000c6c, 0xc7500000c720000, 0xc780000, 0x0, + 0xc8100000c7e0c7b, 0xc8a0c8700000c84, 0xc900c8d0000, 0xc960c93, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xc4b, 0x0, 0xc9900000000, 0x0, 0x0, 0x0, + 0xca200000c9f, 0xca800000ca5, 0xcae00000cab, 0xcb400000cb1, + 0xcba00000cb7, 0xcc000000cbd, 0xcc600000cc30000, 0xcc90000, 0x0, + 0xcd200000ccf0ccc, 0xcdb0cd800000cd5, 0xce10cde0000, 0xce70ce4, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xcea000000000c9c, 0xcf30cf00ced, + 0xcf600000000, 0x124b125d0fb71241, 0x13270e290d831043, + 0xe4f12930e991327, 0x116710cd0f550e97, 0x1279121511fd11e3, + 0x109d106910190feb, 0xd8d12f3128911c7, 0x11e110790ff50e1d, + 0x11d910510edb1309, 0x120311890f65121d, 0x108d10250fbd0eff, + 0xe050dd90d9d127d, 0x10d310770ff10f95, 0x125911e711dd1171, + 0x10e9130712fb12cf, 0x12a111b9114d1107, 0xf0b0e87122f130b, + 0x10ed1083117d112f, 0xecb0e8512cb1249, 0x11471047102f0fed, + 0x117f0e0312b11159, 0x114f11150ddd0ddf, 0xf67123d12b511c5, + 0xebb0d8712350feb, 0xe1110c110950f27, 0xd7f0f1b0da510f1, + 0xe2311450f9d1011, 0x122711c910d70e7d, 0xf6f100d126d1005, + 0xd9110bf0f7b11a5, 0x113d0fdb0ddb0dc3, 0xe091291122d1195, + 0xfa10f070e9f0e37, 0x12f712ab10f31053, 0xfb50df91313130d, + 0xf490ef312690ffd, 0x106d104b0f910f57, 0x11791153111110af, + 0x12a3127111cd1261, 0x10670e410dfb0de9, 0xf230efd1227120b, + 0x1091112d10030f77, 0xee50ebb0e670d97, 0x116b10a9109b0f29, + 0x12d112c912951175, 0x128d110f0d9f12dd, 0xdb10d8f0f3512c1, + 0xfeb0f9f0ec70ebd, 0x127711d310cb1073, 0xdf712af0fad1323, + 0x103b10210fd10fcb, 0x114310e710bd10a1, 0x12b70f5d0dc512e3, + 0x126310310ed70da9, 0x10950fd50f390f17, 0xecf0e310deb12bb, + 0x10150fe10fc30fa7, 0x120d116310c3109f, 0xe1312c5128f1213, + 0x10b110750e33103d, 0x130f12ff12bd11db, 0x1121118b102d0fcf, + 0x1063108b11331125, 0xded11ad0d93123b, 0x11390f690ef50de7, + 0x12670fb3101b0eb5, 0xf03122112b31205, 0xe590db5, 0xfab00000e7b, + 0x10cf108f0de10000, 0x110d1105110310f5, 0x116d113512d3, + 0x1233000011df, 0x128312730000, 0x12e912e700000000, + 0x12bf127f130512eb, 0xe010db90db30da1, 0xe5f0e530e170e07, + 0xecd0e7f0e790e63, 0xf470f430f2f0ed1, 0xfaf0fa30f970f53, + 0x1049103510270fdd, 0x10eb10a3107d106f, 0x10fd10f910fb10f7, + 0x110b1109110110ff, 0x11531127111d1117, 0x11731161115b1157, + 0x11cb11971197118d, 0x1239123712231219, 0x1273126f124f124d, + 0xf2b12e112d912c7, 0x119313be, 0xd9b0dc10dd70d81, + 0xe0b0dff0dc90db7, 0xe5d0e510e490e53, 0xe9b0e950e830e7b, + 0xf050f010eb10ea9, 0xf3f0f330f1d0f13, 0xf530f410f470f37, + 0xf890f850f7f0f5f, 0xfbf0fbd0fab0f99, 0x102110050fff0fc7, + 0x1057104910411045, 0x1089107f10e3106f, 0x10b910b510ab108f, + 0x10d110cf10c910c7, 0x10ef10dd10df10d5, 0x114911311127111f, + 0x11af1173115f1153, 0x121f121b11f911c3, 0x122b123312291223, + 0x1239123112351237, 0x12751265124f123f, 0x12c712b91299128b, + 0x12db12d912d512d3, 0x1394132712f912e1, 0xd370d2313a61392, + 0x141c13ec13da0d39, 0x13251321, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xabb00000a7a0000, + 0x0, 0x0, 0xab50ab200000000, 0xa590a560aae0aaa, 0xa680a650a5f0a5c, + 0xa740a710a6b, 0xa830a800a7d0a77, 0xa8c00000a89, 0xa9500000a920a8f, + 0xaa10a9e00000a98, 0xa6e0ab80aa70aa4, 0xa9b0a860a62, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x132900000000, 0x132c, 0x0, 0x0, 0x132f000000000000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1335133200000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x133b133800000000, 0x134a13461342133e, + 0x134e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1352000000000000, + 0x135913601355135d, 0x1364, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x13680d8b0d850d89, + 0xda70da30da10d99, 0xdaf0db30dad0dab, 0xdbb0db913700cf9, + 0xcfb136a0dc70dbd, 0xdd1136e0dcb0dbf, 0xdd70dd50d950dd3, + 0xcff0de50de3142c, 0xdf50df30df10def, 0xe070e010dff0d01, + 0xe110e0f0e0d0e0b, 0xe1b0e190e170e15, 0xe210e210e210e1f, + 0xe270e25105d1376, 0xe2f0e2d0e2b1378, 0xe3b0e390e350e3d, + 0xe470e450e430e3f, 0xe510e4d0e4d0e4b, 0xe690e5b0e570e55, + 0xe650e610e6b0e5f, 0xe710e6f0e890de7, 0xe750e770e6d0e73, + 0xe8d0e8b137a0e81, 0xe9d0e930e910e8f, 0xea50ea3137e137c, + 0xd030eab0ea10ea7, 0xeb30eb30eaf0d05, 0xebb0eb90eb71380, + 0xec30ec113820ebf, 0xec90d070ec50f0f, 0x13860ed30ed50ed1, + 0xedd0edf13880ed9, 0xd090ee90ee70ee1, 0xef10eef0eed0eeb, + 0xef70d0d138a0d0b, 0x14400eff0efb0ef9, 0x118f138e138e0f09, + 0x139c0d0f0f0d0f0d, 0xd110f150f1113f0, 0xf250f210f1f0f19, + 0xf2f0d130d150f2d, 0xf3d0f3b0f311390, 0xf470f450f3d0f3f, + 0xf510f4d0f4b0f4f, 0xf5b0f590f550f53, 0xf730f6b0f630f61, + 0xf750f6d0f711396, 0xf8713980f830f79, 0xf8b0d170f7d0f81, + 0xd190f8d0f930f8f, 0xfa5139a0f9b0f97, 0xfaf0d1f0fa90fb9, + 0xdcf0dcd0d1b0d1d, 0xd5111810fb10fbb, 0xfc90fc10fbf0fbd, + 0xfd30d2113a40fc5, 0x13a80fdd0fd90fcd, 0xd230fe30fd70fdf, + 0xfef0fe90fe70fe5, 0xff70d250ff313aa, 0xffb0d270ff913ac, + 0x13ae100710051001, 0x13b2100913b01384, 0x1017100b1013100f, + 0x102310211027101f, 0x101d13b4102b1029, 0x10410d2910391037, + 0x104d103313b6103f, 0x1059104f13ba13b8, 0x105b0d2b10551057, + 0x136c1065105f1061, 0x13c0107113bc106b, 0x13c21081107f107b, + 0x13c613c410871085, 0x10990d2d10971093, 0x10a710a50d2f0d2f, + 0xd3110b310ad10ab, 0x13ca10bb13c810b7, 0x13cc10c5138c10c1, + 0xd350d3313d013ce, 0x13d613d213d410d5, 0x10db10db10d913d8, + 0xd3b10e10d3910df, 0x10e910e513dc0d3d, 0x10ff13de0d3f10ef, + 0x1113110d13e213e0, 0x111b111911170d41, 0x112313e613e613e4, + 0x112b112913e80d43, 0xd47113713ea0d45, 0x13ee1141113b113f, + 0x115511510d49114b, 0x13f413f20d4b115d, 0x13f8116513f60d4d, + 0x13fa1173116f1169, 0x117b13fe117713fc, 0x118511830d4f139e, + 0x14000ead11870d53, 0x118f13a213a01402, 0x119b0d55126b1191, + 0x119f0dfd119d1199, 0x140411a711a311a1, 0x11b511b311a911a5, + 0x11cb11c111b711ab, 0x11bf11bd11bb11b1, 0xd571408140a1406, + 0x141211d511d111cf, 0xd5b0d59140c11d7, 0x11e50d5d1410140e, + 0x11ef11eb11e911e7, 0x11f911f111f311ed, 0xd5f11fb11f711f5, + 0x12070d61120111ff, 0x1211120f14141209, 0x14160cfd12170d63, + 0x12250d670d651418, 0x141a1243123f1231, 0x1253125112471245, + 0x125512571372141e, 0x1265125f1374125b, 0x1281127b14221420, + 0x1297128714241285, 0x12a5129b129f129d, 0xd6912a9142612a7, + 0x12c30d6b142812ad, 0x142e142a12cd0ee3, 0x143012d70d6f0d6d, + 0x12db12db14320d71, 0xd7312e5143412df, 0x12f512f112ef12ed, + 0x12fd12f914360d75, 0x13030d790d771301, 0x143c143a0d7b1438, + 0x13150d7d1311143e, 0x131d131b13191317, 0x1442131f, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); + @property + { + private alias _IDCA = immutable(dchar[]); + _IDCA decompCanonTable() + { + static _IDCA t = [ + 0x0, 0x3b, 0x0, 0x3c, 0x338, 0x0, 0x3d, 0x338, 0x0, 0x3e, + 0x338, 0x0, 0x41, 0x300, 0x0, 0x41, 0x301, 0x0, 0x41, 0x302, + 0x0, 0x41, 0x302, 0x300, 0x0, 0x41, 0x302, 0x301, 0x0, 0x41, + 0x302, 0x303, 0x0, 0x41, 0x302, 0x309, 0x0, 0x41, 0x303, 0x0, + 0x41, 0x304, 0x0, 0x41, 0x306, 0x0, 0x41, 0x306, 0x300, 0x0, + 0x41, 0x306, 0x301, 0x0, 0x41, 0x306, 0x303, 0x0, 0x41, 0x306, + 0x309, 0x0, 0x41, 0x307, 0x0, 0x41, 0x307, 0x304, 0x0, 0x41, + 0x308, 0x0, 0x41, 0x308, 0x304, 0x0, 0x41, 0x309, 0x0, 0x41, + 0x30a, 0x0, 0x41, 0x30a, 0x301, 0x0, 0x41, 0x30c, 0x0, 0x41, + 0x30f, 0x0, 0x41, 0x311, 0x0, 0x41, 0x323, 0x0, 0x41, 0x323, + 0x302, 0x0, 0x41, 0x323, 0x306, 0x0, 0x41, 0x325, 0x0, 0x41, + 0x328, 0x0, 0x42, 0x307, 0x0, 0x42, 0x323, 0x0, 0x42, 0x331, + 0x0, 0x43, 0x301, 0x0, 0x43, 0x302, 0x0, 0x43, 0x307, 0x0, + 0x43, 0x30c, 0x0, 0x43, 0x327, 0x0, 0x43, 0x327, 0x301, 0x0, + 0x44, 0x307, 0x0, 0x44, 0x30c, 0x0, 0x44, 0x323, 0x0, 0x44, + 0x327, 0x0, 0x44, 0x32d, 0x0, 0x44, 0x331, 0x0, 0x45, 0x300, + 0x0, 0x45, 0x301, 0x0, 0x45, 0x302, 0x0, 0x45, 0x302, 0x300, + 0x0, 0x45, 0x302, 0x301, 0x0, 0x45, 0x302, 0x303, 0x0, 0x45, + 0x302, 0x309, 0x0, 0x45, 0x303, 0x0, 0x45, 0x304, 0x0, 0x45, + 0x304, 0x300, 0x0, 0x45, 0x304, 0x301, 0x0, 0x45, 0x306, 0x0, + 0x45, 0x307, 0x0, 0x45, 0x308, 0x0, 0x45, 0x309, 0x0, 0x45, + 0x30c, 0x0, 0x45, 0x30f, 0x0, 0x45, 0x311, 0x0, 0x45, 0x323, + 0x0, 0x45, 0x323, 0x302, 0x0, 0x45, 0x327, 0x0, 0x45, 0x327, + 0x306, 0x0, 0x45, 0x328, 0x0, 0x45, 0x32d, 0x0, 0x45, 0x330, + 0x0, 0x46, 0x307, 0x0, 0x47, 0x301, 0x0, 0x47, 0x302, 0x0, + 0x47, 0x304, 0x0, 0x47, 0x306, 0x0, 0x47, 0x307, 0x0, 0x47, + 0x30c, 0x0, 0x47, 0x327, 0x0, 0x48, 0x302, 0x0, 0x48, 0x307, + 0x0, 0x48, 0x308, 0x0, 0x48, 0x30c, 0x0, 0x48, 0x323, 0x0, + 0x48, 0x327, 0x0, 0x48, 0x32e, 0x0, 0x49, 0x300, 0x0, 0x49, + 0x301, 0x0, 0x49, 0x302, 0x0, 0x49, 0x303, 0x0, 0x49, 0x304, + 0x0, 0x49, 0x306, 0x0, 0x49, 0x307, 0x0, 0x49, 0x308, 0x0, + 0x49, 0x308, 0x301, 0x0, 0x49, 0x309, 0x0, 0x49, 0x30c, 0x0, + 0x49, 0x30f, 0x0, 0x49, 0x311, 0x0, 0x49, 0x323, 0x0, 0x49, + 0x328, 0x0, 0x49, 0x330, 0x0, 0x4a, 0x302, 0x0, 0x4b, 0x0, + 0x4b, 0x301, 0x0, 0x4b, 0x30c, 0x0, 0x4b, 0x323, 0x0, 0x4b, + 0x327, 0x0, 0x4b, 0x331, 0x0, 0x4c, 0x301, 0x0, 0x4c, 0x30c, + 0x0, 0x4c, 0x323, 0x0, 0x4c, 0x323, 0x304, 0x0, 0x4c, 0x327, + 0x0, 0x4c, 0x32d, 0x0, 0x4c, 0x331, 0x0, 0x4d, 0x301, 0x0, + 0x4d, 0x307, 0x0, 0x4d, 0x323, 0x0, 0x4e, 0x300, 0x0, 0x4e, + 0x301, 0x0, 0x4e, 0x303, 0x0, 0x4e, 0x307, 0x0, 0x4e, 0x30c, + 0x0, 0x4e, 0x323, 0x0, 0x4e, 0x327, 0x0, 0x4e, 0x32d, 0x0, + 0x4e, 0x331, 0x0, 0x4f, 0x300, 0x0, 0x4f, 0x301, 0x0, 0x4f, + 0x302, 0x0, 0x4f, 0x302, 0x300, 0x0, 0x4f, 0x302, 0x301, 0x0, + 0x4f, 0x302, 0x303, 0x0, 0x4f, 0x302, 0x309, 0x0, 0x4f, 0x303, + 0x0, 0x4f, 0x303, 0x301, 0x0, 0x4f, 0x303, 0x304, 0x0, 0x4f, + 0x303, 0x308, 0x0, 0x4f, 0x304, 0x0, 0x4f, 0x304, 0x300, 0x0, + 0x4f, 0x304, 0x301, 0x0, 0x4f, 0x306, 0x0, 0x4f, 0x307, 0x0, + 0x4f, 0x307, 0x304, 0x0, 0x4f, 0x308, 0x0, 0x4f, 0x308, 0x304, + 0x0, 0x4f, 0x309, 0x0, 0x4f, 0x30b, 0x0, 0x4f, 0x30c, 0x0, + 0x4f, 0x30f, 0x0, 0x4f, 0x311, 0x0, 0x4f, 0x31b, 0x0, 0x4f, + 0x31b, 0x300, 0x0, 0x4f, 0x31b, 0x301, 0x0, 0x4f, 0x31b, 0x303, + 0x0, 0x4f, 0x31b, 0x309, 0x0, 0x4f, 0x31b, 0x323, 0x0, 0x4f, + 0x323, 0x0, 0x4f, 0x323, 0x302, 0x0, 0x4f, 0x328, 0x0, 0x4f, + 0x328, 0x304, 0x0, 0x50, 0x301, 0x0, 0x50, 0x307, 0x0, 0x52, + 0x301, 0x0, 0x52, 0x307, 0x0, 0x52, 0x30c, 0x0, 0x52, 0x30f, + 0x0, 0x52, 0x311, 0x0, 0x52, 0x323, 0x0, 0x52, 0x323, 0x304, + 0x0, 0x52, 0x327, 0x0, 0x52, 0x331, 0x0, 0x53, 0x301, 0x0, + 0x53, 0x301, 0x307, 0x0, 0x53, 0x302, 0x0, 0x53, 0x307, 0x0, + 0x53, 0x30c, 0x0, 0x53, 0x30c, 0x307, 0x0, 0x53, 0x323, 0x0, + 0x53, 0x323, 0x307, 0x0, 0x53, 0x326, 0x0, 0x53, 0x327, 0x0, + 0x54, 0x307, 0x0, 0x54, 0x30c, 0x0, 0x54, 0x323, 0x0, 0x54, + 0x326, 0x0, 0x54, 0x327, 0x0, 0x54, 0x32d, 0x0, 0x54, 0x331, + 0x0, 0x55, 0x300, 0x0, 0x55, 0x301, 0x0, 0x55, 0x302, 0x0, + 0x55, 0x303, 0x0, 0x55, 0x303, 0x301, 0x0, 0x55, 0x304, 0x0, + 0x55, 0x304, 0x308, 0x0, 0x55, 0x306, 0x0, 0x55, 0x308, 0x0, + 0x55, 0x308, 0x300, 0x0, 0x55, 0x308, 0x301, 0x0, 0x55, 0x308, + 0x304, 0x0, 0x55, 0x308, 0x30c, 0x0, 0x55, 0x309, 0x0, 0x55, + 0x30a, 0x0, 0x55, 0x30b, 0x0, 0x55, 0x30c, 0x0, 0x55, 0x30f, + 0x0, 0x55, 0x311, 0x0, 0x55, 0x31b, 0x0, 0x55, 0x31b, 0x300, + 0x0, 0x55, 0x31b, 0x301, 0x0, 0x55, 0x31b, 0x303, 0x0, 0x55, + 0x31b, 0x309, 0x0, 0x55, 0x31b, 0x323, 0x0, 0x55, 0x323, 0x0, + 0x55, 0x324, 0x0, 0x55, 0x328, 0x0, 0x55, 0x32d, 0x0, 0x55, + 0x330, 0x0, 0x56, 0x303, 0x0, 0x56, 0x323, 0x0, 0x57, 0x300, + 0x0, 0x57, 0x301, 0x0, 0x57, 0x302, 0x0, 0x57, 0x307, 0x0, + 0x57, 0x308, 0x0, 0x57, 0x323, 0x0, 0x58, 0x307, 0x0, 0x58, + 0x308, 0x0, 0x59, 0x300, 0x0, 0x59, 0x301, 0x0, 0x59, 0x302, + 0x0, 0x59, 0x303, 0x0, 0x59, 0x304, 0x0, 0x59, 0x307, 0x0, + 0x59, 0x308, 0x0, 0x59, 0x309, 0x0, 0x59, 0x323, 0x0, 0x5a, + 0x301, 0x0, 0x5a, 0x302, 0x0, 0x5a, 0x307, 0x0, 0x5a, 0x30c, + 0x0, 0x5a, 0x323, 0x0, 0x5a, 0x331, 0x0, 0x60, 0x0, 0x61, + 0x300, 0x0, 0x61, 0x301, 0x0, 0x61, 0x302, 0x0, 0x61, 0x302, + 0x300, 0x0, 0x61, 0x302, 0x301, 0x0, 0x61, 0x302, 0x303, 0x0, + 0x61, 0x302, 0x309, 0x0, 0x61, 0x303, 0x0, 0x61, 0x304, 0x0, + 0x61, 0x306, 0x0, 0x61, 0x306, 0x300, 0x0, 0x61, 0x306, 0x301, + 0x0, 0x61, 0x306, 0x303, 0x0, 0x61, 0x306, 0x309, 0x0, 0x61, + 0x307, 0x0, 0x61, 0x307, 0x304, 0x0, 0x61, 0x308, 0x0, 0x61, + 0x308, 0x304, 0x0, 0x61, 0x309, 0x0, 0x61, 0x30a, 0x0, 0x61, + 0x30a, 0x301, 0x0, 0x61, 0x30c, 0x0, 0x61, 0x30f, 0x0, 0x61, + 0x311, 0x0, 0x61, 0x323, 0x0, 0x61, 0x323, 0x302, 0x0, 0x61, + 0x323, 0x306, 0x0, 0x61, 0x325, 0x0, 0x61, 0x328, 0x0, 0x62, + 0x307, 0x0, 0x62, 0x323, 0x0, 0x62, 0x331, 0x0, 0x63, 0x301, + 0x0, 0x63, 0x302, 0x0, 0x63, 0x307, 0x0, 0x63, 0x30c, 0x0, + 0x63, 0x327, 0x0, 0x63, 0x327, 0x301, 0x0, 0x64, 0x307, 0x0, + 0x64, 0x30c, 0x0, 0x64, 0x323, 0x0, 0x64, 0x327, 0x0, 0x64, + 0x32d, 0x0, 0x64, 0x331, 0x0, 0x65, 0x300, 0x0, 0x65, 0x301, + 0x0, 0x65, 0x302, 0x0, 0x65, 0x302, 0x300, 0x0, 0x65, 0x302, + 0x301, 0x0, 0x65, 0x302, 0x303, 0x0, 0x65, 0x302, 0x309, 0x0, + 0x65, 0x303, 0x0, 0x65, 0x304, 0x0, 0x65, 0x304, 0x300, 0x0, + 0x65, 0x304, 0x301, 0x0, 0x65, 0x306, 0x0, 0x65, 0x307, 0x0, + 0x65, 0x308, 0x0, 0x65, 0x309, 0x0, 0x65, 0x30c, 0x0, 0x65, + 0x30f, 0x0, 0x65, 0x311, 0x0, 0x65, 0x323, 0x0, 0x65, 0x323, + 0x302, 0x0, 0x65, 0x327, 0x0, 0x65, 0x327, 0x306, 0x0, 0x65, + 0x328, 0x0, 0x65, 0x32d, 0x0, 0x65, 0x330, 0x0, 0x66, 0x307, + 0x0, 0x67, 0x301, 0x0, 0x67, 0x302, 0x0, 0x67, 0x304, 0x0, + 0x67, 0x306, 0x0, 0x67, 0x307, 0x0, 0x67, 0x30c, 0x0, 0x67, + 0x327, 0x0, 0x68, 0x302, 0x0, 0x68, 0x307, 0x0, 0x68, 0x308, + 0x0, 0x68, 0x30c, 0x0, 0x68, 0x323, 0x0, 0x68, 0x327, 0x0, + 0x68, 0x32e, 0x0, 0x68, 0x331, 0x0, 0x69, 0x300, 0x0, 0x69, + 0x301, 0x0, 0x69, 0x302, 0x0, 0x69, 0x303, 0x0, 0x69, 0x304, + 0x0, 0x69, 0x306, 0x0, 0x69, 0x308, 0x0, 0x69, 0x308, 0x301, + 0x0, 0x69, 0x309, 0x0, 0x69, 0x30c, 0x0, 0x69, 0x30f, 0x0, + 0x69, 0x311, 0x0, 0x69, 0x323, 0x0, 0x69, 0x328, 0x0, 0x69, + 0x330, 0x0, 0x6a, 0x302, 0x0, 0x6a, 0x30c, 0x0, 0x6b, 0x301, + 0x0, 0x6b, 0x30c, 0x0, 0x6b, 0x323, 0x0, 0x6b, 0x327, 0x0, + 0x6b, 0x331, 0x0, 0x6c, 0x301, 0x0, 0x6c, 0x30c, 0x0, 0x6c, + 0x323, 0x0, 0x6c, 0x323, 0x304, 0x0, 0x6c, 0x327, 0x0, 0x6c, + 0x32d, 0x0, 0x6c, 0x331, 0x0, 0x6d, 0x301, 0x0, 0x6d, 0x307, + 0x0, 0x6d, 0x323, 0x0, 0x6e, 0x300, 0x0, 0x6e, 0x301, 0x0, + 0x6e, 0x303, 0x0, 0x6e, 0x307, 0x0, 0x6e, 0x30c, 0x0, 0x6e, + 0x323, 0x0, 0x6e, 0x327, 0x0, 0x6e, 0x32d, 0x0, 0x6e, 0x331, + 0x0, 0x6f, 0x300, 0x0, 0x6f, 0x301, 0x0, 0x6f, 0x302, 0x0, + 0x6f, 0x302, 0x300, 0x0, 0x6f, 0x302, 0x301, 0x0, 0x6f, 0x302, + 0x303, 0x0, 0x6f, 0x302, 0x309, 0x0, 0x6f, 0x303, 0x0, 0x6f, + 0x303, 0x301, 0x0, 0x6f, 0x303, 0x304, 0x0, 0x6f, 0x303, 0x308, + 0x0, 0x6f, 0x304, 0x0, 0x6f, 0x304, 0x300, 0x0, 0x6f, 0x304, + 0x301, 0x0, 0x6f, 0x306, 0x0, 0x6f, 0x307, 0x0, 0x6f, 0x307, + 0x304, 0x0, 0x6f, 0x308, 0x0, 0x6f, 0x308, 0x304, 0x0, 0x6f, + 0x309, 0x0, 0x6f, 0x30b, 0x0, 0x6f, 0x30c, 0x0, 0x6f, 0x30f, + 0x0, 0x6f, 0x311, 0x0, 0x6f, 0x31b, 0x0, 0x6f, 0x31b, 0x300, + 0x0, 0x6f, 0x31b, 0x301, 0x0, 0x6f, 0x31b, 0x303, 0x0, 0x6f, + 0x31b, 0x309, 0x0, 0x6f, 0x31b, 0x323, 0x0, 0x6f, 0x323, 0x0, + 0x6f, 0x323, 0x302, 0x0, 0x6f, 0x328, 0x0, 0x6f, 0x328, 0x304, + 0x0, 0x70, 0x301, 0x0, 0x70, 0x307, 0x0, 0x72, 0x301, 0x0, + 0x72, 0x307, 0x0, 0x72, 0x30c, 0x0, 0x72, 0x30f, 0x0, 0x72, + 0x311, 0x0, 0x72, 0x323, 0x0, 0x72, 0x323, 0x304, 0x0, 0x72, + 0x327, 0x0, 0x72, 0x331, 0x0, 0x73, 0x301, 0x0, 0x73, 0x301, + 0x307, 0x0, 0x73, 0x302, 0x0, 0x73, 0x307, 0x0, 0x73, 0x30c, + 0x0, 0x73, 0x30c, 0x307, 0x0, 0x73, 0x323, 0x0, 0x73, 0x323, + 0x307, 0x0, 0x73, 0x326, 0x0, 0x73, 0x327, 0x0, 0x74, 0x307, + 0x0, 0x74, 0x308, 0x0, 0x74, 0x30c, 0x0, 0x74, 0x323, 0x0, + 0x74, 0x326, 0x0, 0x74, 0x327, 0x0, 0x74, 0x32d, 0x0, 0x74, + 0x331, 0x0, 0x75, 0x300, 0x0, 0x75, 0x301, 0x0, 0x75, 0x302, + 0x0, 0x75, 0x303, 0x0, 0x75, 0x303, 0x301, 0x0, 0x75, 0x304, + 0x0, 0x75, 0x304, 0x308, 0x0, 0x75, 0x306, 0x0, 0x75, 0x308, + 0x0, 0x75, 0x308, 0x300, 0x0, 0x75, 0x308, 0x301, 0x0, 0x75, + 0x308, 0x304, 0x0, 0x75, 0x308, 0x30c, 0x0, 0x75, 0x309, 0x0, + 0x75, 0x30a, 0x0, 0x75, 0x30b, 0x0, 0x75, 0x30c, 0x0, 0x75, + 0x30f, 0x0, 0x75, 0x311, 0x0, 0x75, 0x31b, 0x0, 0x75, 0x31b, + 0x300, 0x0, 0x75, 0x31b, 0x301, 0x0, 0x75, 0x31b, 0x303, 0x0, + 0x75, 0x31b, 0x309, 0x0, 0x75, 0x31b, 0x323, 0x0, 0x75, 0x323, + 0x0, 0x75, 0x324, 0x0, 0x75, 0x328, 0x0, 0x75, 0x32d, 0x0, + 0x75, 0x330, 0x0, 0x76, 0x303, 0x0, 0x76, 0x323, 0x0, 0x77, + 0x300, 0x0, 0x77, 0x301, 0x0, 0x77, 0x302, 0x0, 0x77, 0x307, + 0x0, 0x77, 0x308, 0x0, 0x77, 0x30a, 0x0, 0x77, 0x323, 0x0, + 0x78, 0x307, 0x0, 0x78, 0x308, 0x0, 0x79, 0x300, 0x0, 0x79, + 0x301, 0x0, 0x79, 0x302, 0x0, 0x79, 0x303, 0x0, 0x79, 0x304, + 0x0, 0x79, 0x307, 0x0, 0x79, 0x308, 0x0, 0x79, 0x309, 0x0, + 0x79, 0x30a, 0x0, 0x79, 0x323, 0x0, 0x7a, 0x301, 0x0, 0x7a, + 0x302, 0x0, 0x7a, 0x307, 0x0, 0x7a, 0x30c, 0x0, 0x7a, 0x323, + 0x0, 0x7a, 0x331, 0x0, 0xa8, 0x300, 0x0, 0xa8, 0x301, 0x0, + 0xa8, 0x342, 0x0, 0xb4, 0x0, 0xb7, 0x0, 0xc6, 0x301, 0x0, 0xc6, + 0x304, 0x0, 0xd8, 0x301, 0x0, 0xe6, 0x301, 0x0, 0xe6, 0x304, + 0x0, 0xf8, 0x301, 0x0, 0x17f, 0x307, 0x0, 0x1b7, 0x30c, 0x0, + 0x292, 0x30c, 0x0, 0x2b9, 0x0, 0x300, 0x0, 0x301, 0x0, 0x308, + 0x301, 0x0, 0x313, 0x0, 0x391, 0x300, 0x0, 0x391, 0x301, 0x0, + 0x391, 0x304, 0x0, 0x391, 0x306, 0x0, 0x391, 0x313, 0x0, 0x391, + 0x313, 0x300, 0x0, 0x391, 0x313, 0x300, 0x345, 0x0, 0x391, + 0x313, 0x301, 0x0, 0x391, 0x313, 0x301, 0x345, 0x0, 0x391, + 0x313, 0x342, 0x0, 0x391, 0x313, 0x342, 0x345, 0x0, 0x391, + 0x313, 0x345, 0x0, 0x391, 0x314, 0x0, 0x391, 0x314, 0x300, 0x0, + 0x391, 0x314, 0x300, 0x345, 0x0, 0x391, 0x314, 0x301, 0x0, + 0x391, 0x314, 0x301, 0x345, 0x0, 0x391, 0x314, 0x342, 0x0, + 0x391, 0x314, 0x342, 0x345, 0x0, 0x391, 0x314, 0x345, 0x0, + 0x391, 0x345, 0x0, 0x395, 0x300, 0x0, 0x395, 0x301, 0x0, 0x395, + 0x313, 0x0, 0x395, 0x313, 0x300, 0x0, 0x395, 0x313, 0x301, 0x0, + 0x395, 0x314, 0x0, 0x395, 0x314, 0x300, 0x0, 0x395, 0x314, + 0x301, 0x0, 0x397, 0x300, 0x0, 0x397, 0x301, 0x0, 0x397, 0x313, + 0x0, 0x397, 0x313, 0x300, 0x0, 0x397, 0x313, 0x300, 0x345, 0x0, + 0x397, 0x313, 0x301, 0x0, 0x397, 0x313, 0x301, 0x345, 0x0, + 0x397, 0x313, 0x342, 0x0, 0x397, 0x313, 0x342, 0x345, 0x0, + 0x397, 0x313, 0x345, 0x0, 0x397, 0x314, 0x0, 0x397, 0x314, + 0x300, 0x0, 0x397, 0x314, 0x300, 0x345, 0x0, 0x397, 0x314, + 0x301, 0x0, 0x397, 0x314, 0x301, 0x345, 0x0, 0x397, 0x314, + 0x342, 0x0, 0x397, 0x314, 0x342, 0x345, 0x0, 0x397, 0x314, + 0x345, 0x0, 0x397, 0x345, 0x0, 0x399, 0x300, 0x0, 0x399, 0x301, + 0x0, 0x399, 0x304, 0x0, 0x399, 0x306, 0x0, 0x399, 0x308, 0x0, + 0x399, 0x313, 0x0, 0x399, 0x313, 0x300, 0x0, 0x399, 0x313, + 0x301, 0x0, 0x399, 0x313, 0x342, 0x0, 0x399, 0x314, 0x0, 0x399, + 0x314, 0x300, 0x0, 0x399, 0x314, 0x301, 0x0, 0x399, 0x314, + 0x342, 0x0, 0x39f, 0x300, 0x0, 0x39f, 0x301, 0x0, 0x39f, 0x313, + 0x0, 0x39f, 0x313, 0x300, 0x0, 0x39f, 0x313, 0x301, 0x0, 0x39f, + 0x314, 0x0, 0x39f, 0x314, 0x300, 0x0, 0x39f, 0x314, 0x301, 0x0, + 0x3a1, 0x314, 0x0, 0x3a5, 0x300, 0x0, 0x3a5, 0x301, 0x0, 0x3a5, + 0x304, 0x0, 0x3a5, 0x306, 0x0, 0x3a5, 0x308, 0x0, 0x3a5, 0x314, + 0x0, 0x3a5, 0x314, 0x300, 0x0, 0x3a5, 0x314, 0x301, 0x0, 0x3a5, + 0x314, 0x342, 0x0, 0x3a9, 0x0, 0x3a9, 0x300, 0x0, 0x3a9, 0x301, + 0x0, 0x3a9, 0x313, 0x0, 0x3a9, 0x313, 0x300, 0x0, 0x3a9, 0x313, + 0x300, 0x345, 0x0, 0x3a9, 0x313, 0x301, 0x0, 0x3a9, 0x313, + 0x301, 0x345, 0x0, 0x3a9, 0x313, 0x342, 0x0, 0x3a9, 0x313, + 0x342, 0x345, 0x0, 0x3a9, 0x313, 0x345, 0x0, 0x3a9, 0x314, 0x0, + 0x3a9, 0x314, 0x300, 0x0, 0x3a9, 0x314, 0x300, 0x345, 0x0, + 0x3a9, 0x314, 0x301, 0x0, 0x3a9, 0x314, 0x301, 0x345, 0x0, + 0x3a9, 0x314, 0x342, 0x0, 0x3a9, 0x314, 0x342, 0x345, 0x0, + 0x3a9, 0x314, 0x345, 0x0, 0x3a9, 0x345, 0x0, 0x3b1, 0x300, 0x0, + 0x3b1, 0x300, 0x345, 0x0, 0x3b1, 0x301, 0x0, 0x3b1, 0x301, + 0x345, 0x0, 0x3b1, 0x304, 0x0, 0x3b1, 0x306, 0x0, 0x3b1, 0x313, + 0x0, 0x3b1, 0x313, 0x300, 0x0, 0x3b1, 0x313, 0x300, 0x345, 0x0, + 0x3b1, 0x313, 0x301, 0x0, 0x3b1, 0x313, 0x301, 0x345, 0x0, + 0x3b1, 0x313, 0x342, 0x0, 0x3b1, 0x313, 0x342, 0x345, 0x0, + 0x3b1, 0x313, 0x345, 0x0, 0x3b1, 0x314, 0x0, 0x3b1, 0x314, + 0x300, 0x0, 0x3b1, 0x314, 0x300, 0x345, 0x0, 0x3b1, 0x314, + 0x301, 0x0, 0x3b1, 0x314, 0x301, 0x345, 0x0, 0x3b1, 0x314, + 0x342, 0x0, 0x3b1, 0x314, 0x342, 0x345, 0x0, 0x3b1, 0x314, + 0x345, 0x0, 0x3b1, 0x342, 0x0, 0x3b1, 0x342, 0x345, 0x0, 0x3b1, + 0x345, 0x0, 0x3b5, 0x300, 0x0, 0x3b5, 0x301, 0x0, 0x3b5, 0x313, + 0x0, 0x3b5, 0x313, 0x300, 0x0, 0x3b5, 0x313, 0x301, 0x0, 0x3b5, + 0x314, 0x0, 0x3b5, 0x314, 0x300, 0x0, 0x3b5, 0x314, 0x301, 0x0, + 0x3b7, 0x300, 0x0, 0x3b7, 0x300, 0x345, 0x0, 0x3b7, 0x301, 0x0, + 0x3b7, 0x301, 0x345, 0x0, 0x3b7, 0x313, 0x0, 0x3b7, 0x313, + 0x300, 0x0, 0x3b7, 0x313, 0x300, 0x345, 0x0, 0x3b7, 0x313, + 0x301, 0x0, 0x3b7, 0x313, 0x301, 0x345, 0x0, 0x3b7, 0x313, + 0x342, 0x0, 0x3b7, 0x313, 0x342, 0x345, 0x0, 0x3b7, 0x313, + 0x345, 0x0, 0x3b7, 0x314, 0x0, 0x3b7, 0x314, 0x300, 0x0, 0x3b7, + 0x314, 0x300, 0x345, 0x0, 0x3b7, 0x314, 0x301, 0x0, 0x3b7, + 0x314, 0x301, 0x345, 0x0, 0x3b7, 0x314, 0x342, 0x0, 0x3b7, + 0x314, 0x342, 0x345, 0x0, 0x3b7, 0x314, 0x345, 0x0, 0x3b7, + 0x342, 0x0, 0x3b7, 0x342, 0x345, 0x0, 0x3b7, 0x345, 0x0, 0x3b9, + 0x0, 0x3b9, 0x300, 0x0, 0x3b9, 0x301, 0x0, 0x3b9, 0x304, 0x0, + 0x3b9, 0x306, 0x0, 0x3b9, 0x308, 0x0, 0x3b9, 0x308, 0x300, 0x0, + 0x3b9, 0x308, 0x301, 0x0, 0x3b9, 0x308, 0x342, 0x0, 0x3b9, + 0x313, 0x0, 0x3b9, 0x313, 0x300, 0x0, 0x3b9, 0x313, 0x301, 0x0, + 0x3b9, 0x313, 0x342, 0x0, 0x3b9, 0x314, 0x0, 0x3b9, 0x314, + 0x300, 0x0, 0x3b9, 0x314, 0x301, 0x0, 0x3b9, 0x314, 0x342, 0x0, + 0x3b9, 0x342, 0x0, 0x3bf, 0x300, 0x0, 0x3bf, 0x301, 0x0, 0x3bf, + 0x313, 0x0, 0x3bf, 0x313, 0x300, 0x0, 0x3bf, 0x313, 0x301, 0x0, + 0x3bf, 0x314, 0x0, 0x3bf, 0x314, 0x300, 0x0, 0x3bf, 0x314, + 0x301, 0x0, 0x3c1, 0x313, 0x0, 0x3c1, 0x314, 0x0, 0x3c5, 0x300, + 0x0, 0x3c5, 0x301, 0x0, 0x3c5, 0x304, 0x0, 0x3c5, 0x306, 0x0, + 0x3c5, 0x308, 0x0, 0x3c5, 0x308, 0x300, 0x0, 0x3c5, 0x308, + 0x301, 0x0, 0x3c5, 0x308, 0x342, 0x0, 0x3c5, 0x313, 0x0, 0x3c5, + 0x313, 0x300, 0x0, 0x3c5, 0x313, 0x301, 0x0, 0x3c5, 0x313, + 0x342, 0x0, 0x3c5, 0x314, 0x0, 0x3c5, 0x314, 0x300, 0x0, 0x3c5, + 0x314, 0x301, 0x0, 0x3c5, 0x314, 0x342, 0x0, 0x3c5, 0x342, 0x0, + 0x3c9, 0x300, 0x0, 0x3c9, 0x300, 0x345, 0x0, 0x3c9, 0x301, 0x0, + 0x3c9, 0x301, 0x345, 0x0, 0x3c9, 0x313, 0x0, 0x3c9, 0x313, + 0x300, 0x0, 0x3c9, 0x313, 0x300, 0x345, 0x0, 0x3c9, 0x313, + 0x301, 0x0, 0x3c9, 0x313, 0x301, 0x345, 0x0, 0x3c9, 0x313, + 0x342, 0x0, 0x3c9, 0x313, 0x342, 0x345, 0x0, 0x3c9, 0x313, + 0x345, 0x0, 0x3c9, 0x314, 0x0, 0x3c9, 0x314, 0x300, 0x0, 0x3c9, + 0x314, 0x300, 0x345, 0x0, 0x3c9, 0x314, 0x301, 0x0, 0x3c9, + 0x314, 0x301, 0x345, 0x0, 0x3c9, 0x314, 0x342, 0x0, 0x3c9, + 0x314, 0x342, 0x345, 0x0, 0x3c9, 0x314, 0x345, 0x0, 0x3c9, + 0x342, 0x0, 0x3c9, 0x342, 0x345, 0x0, 0x3c9, 0x345, 0x0, 0x3d2, + 0x301, 0x0, 0x3d2, 0x308, 0x0, 0x406, 0x308, 0x0, 0x410, 0x306, + 0x0, 0x410, 0x308, 0x0, 0x413, 0x301, 0x0, 0x415, 0x300, 0x0, + 0x415, 0x306, 0x0, 0x415, 0x308, 0x0, 0x416, 0x306, 0x0, 0x416, + 0x308, 0x0, 0x417, 0x308, 0x0, 0x418, 0x300, 0x0, 0x418, 0x304, + 0x0, 0x418, 0x306, 0x0, 0x418, 0x308, 0x0, 0x41a, 0x301, 0x0, + 0x41e, 0x308, 0x0, 0x423, 0x304, 0x0, 0x423, 0x306, 0x0, 0x423, + 0x308, 0x0, 0x423, 0x30b, 0x0, 0x427, 0x308, 0x0, 0x42b, 0x308, + 0x0, 0x42d, 0x308, 0x0, 0x430, 0x306, 0x0, 0x430, 0x308, 0x0, + 0x433, 0x301, 0x0, 0x435, 0x300, 0x0, 0x435, 0x306, 0x0, 0x435, + 0x308, 0x0, 0x436, 0x306, 0x0, 0x436, 0x308, 0x0, 0x437, 0x308, + 0x0, 0x438, 0x300, 0x0, 0x438, 0x304, 0x0, 0x438, 0x306, 0x0, + 0x438, 0x308, 0x0, 0x43a, 0x301, 0x0, 0x43e, 0x308, 0x0, 0x443, + 0x304, 0x0, 0x443, 0x306, 0x0, 0x443, 0x308, 0x0, 0x443, 0x30b, + 0x0, 0x447, 0x308, 0x0, 0x44b, 0x308, 0x0, 0x44d, 0x308, 0x0, + 0x456, 0x308, 0x0, 0x474, 0x30f, 0x0, 0x475, 0x30f, 0x0, 0x4d8, + 0x308, 0x0, 0x4d9, 0x308, 0x0, 0x4e8, 0x308, 0x0, 0x4e9, 0x308, + 0x0, 0x5d0, 0x5b7, 0x0, 0x5d0, 0x5b8, 0x0, 0x5d0, 0x5bc, 0x0, + 0x5d1, 0x5bc, 0x0, 0x5d1, 0x5bf, 0x0, 0x5d2, 0x5bc, 0x0, 0x5d3, + 0x5bc, 0x0, 0x5d4, 0x5bc, 0x0, 0x5d5, 0x5b9, 0x0, 0x5d5, 0x5bc, + 0x0, 0x5d6, 0x5bc, 0x0, 0x5d8, 0x5bc, 0x0, 0x5d9, 0x5b4, 0x0, + 0x5d9, 0x5bc, 0x0, 0x5da, 0x5bc, 0x0, 0x5db, 0x5bc, 0x0, 0x5db, + 0x5bf, 0x0, 0x5dc, 0x5bc, 0x0, 0x5de, 0x5bc, 0x0, 0x5e0, 0x5bc, + 0x0, 0x5e1, 0x5bc, 0x0, 0x5e3, 0x5bc, 0x0, 0x5e4, 0x5bc, 0x0, + 0x5e4, 0x5bf, 0x0, 0x5e6, 0x5bc, 0x0, 0x5e7, 0x5bc, 0x0, 0x5e8, + 0x5bc, 0x0, 0x5e9, 0x5bc, 0x0, 0x5e9, 0x5bc, 0x5c1, 0x0, 0x5e9, + 0x5bc, 0x5c2, 0x0, 0x5e9, 0x5c1, 0x0, 0x5e9, 0x5c2, 0x0, 0x5ea, + 0x5bc, 0x0, 0x5f2, 0x5b7, 0x0, 0x627, 0x653, 0x0, 0x627, 0x654, + 0x0, 0x627, 0x655, 0x0, 0x648, 0x654, 0x0, 0x64a, 0x654, 0x0, + 0x6c1, 0x654, 0x0, 0x6d2, 0x654, 0x0, 0x6d5, 0x654, 0x0, 0x915, + 0x93c, 0x0, 0x916, 0x93c, 0x0, 0x917, 0x93c, 0x0, 0x91c, 0x93c, + 0x0, 0x921, 0x93c, 0x0, 0x922, 0x93c, 0x0, 0x928, 0x93c, 0x0, + 0x92b, 0x93c, 0x0, 0x92f, 0x93c, 0x0, 0x930, 0x93c, 0x0, 0x933, + 0x93c, 0x0, 0x9a1, 0x9bc, 0x0, 0x9a2, 0x9bc, 0x0, 0x9af, 0x9bc, + 0x0, 0x9c7, 0x9be, 0x0, 0x9c7, 0x9d7, 0x0, 0xa16, 0xa3c, 0x0, + 0xa17, 0xa3c, 0x0, 0xa1c, 0xa3c, 0x0, 0xa2b, 0xa3c, 0x0, 0xa32, + 0xa3c, 0x0, 0xa38, 0xa3c, 0x0, 0xb21, 0xb3c, 0x0, 0xb22, 0xb3c, + 0x0, 0xb47, 0xb3e, 0x0, 0xb47, 0xb56, 0x0, 0xb47, 0xb57, 0x0, + 0xb92, 0xbd7, 0x0, 0xbc6, 0xbbe, 0x0, 0xbc6, 0xbd7, 0x0, 0xbc7, + 0xbbe, 0x0, 0xc46, 0xc56, 0x0, 0xcbf, 0xcd5, 0x0, 0xcc6, 0xcc2, + 0x0, 0xcc6, 0xcc2, 0xcd5, 0x0, 0xcc6, 0xcd5, 0x0, 0xcc6, 0xcd6, + 0x0, 0xd46, 0xd3e, 0x0, 0xd46, 0xd57, 0x0, 0xd47, 0xd3e, 0x0, + 0xdd9, 0xdca, 0x0, 0xdd9, 0xdcf, 0x0, 0xdd9, 0xdcf, 0xdca, 0x0, + 0xdd9, 0xddf, 0x0, 0xf40, 0xfb5, 0x0, 0xf42, 0xfb7, 0x0, 0xf4c, + 0xfb7, 0x0, 0xf51, 0xfb7, 0x0, 0xf56, 0xfb7, 0x0, 0xf5b, 0xfb7, + 0x0, 0xf71, 0xf72, 0x0, 0xf71, 0xf74, 0x0, 0xf71, 0xf80, 0x0, + 0xf90, 0xfb5, 0x0, 0xf92, 0xfb7, 0x0, 0xf9c, 0xfb7, 0x0, 0xfa1, + 0xfb7, 0x0, 0xfa6, 0xfb7, 0x0, 0xfab, 0xfb7, 0x0, 0xfb2, 0xf80, + 0x0, 0xfb3, 0xf80, 0x0, 0x1025, 0x102e, 0x0, 0x1b05, 0x1b35, + 0x0, 0x1b07, 0x1b35, 0x0, 0x1b09, 0x1b35, 0x0, 0x1b0b, 0x1b35, + 0x0, 0x1b0d, 0x1b35, 0x0, 0x1b11, 0x1b35, 0x0, 0x1b3a, 0x1b35, + 0x0, 0x1b3c, 0x1b35, 0x0, 0x1b3e, 0x1b35, 0x0, 0x1b3f, 0x1b35, + 0x0, 0x1b42, 0x1b35, 0x0, 0x1fbf, 0x300, 0x0, 0x1fbf, 0x301, + 0x0, 0x1fbf, 0x342, 0x0, 0x1ffe, 0x300, 0x0, 0x1ffe, 0x301, + 0x0, 0x1ffe, 0x342, 0x0, 0x2002, 0x0, 0x2003, 0x0, 0x2190, + 0x338, 0x0, 0x2192, 0x338, 0x0, 0x2194, 0x338, 0x0, 0x21d0, + 0x338, 0x0, 0x21d2, 0x338, 0x0, 0x21d4, 0x338, 0x0, 0x2203, + 0x338, 0x0, 0x2208, 0x338, 0x0, 0x220b, 0x338, 0x0, 0x2223, + 0x338, 0x0, 0x2225, 0x338, 0x0, 0x223c, 0x338, 0x0, 0x2243, + 0x338, 0x0, 0x2245, 0x338, 0x0, 0x2248, 0x338, 0x0, 0x224d, + 0x338, 0x0, 0x2261, 0x338, 0x0, 0x2264, 0x338, 0x0, 0x2265, + 0x338, 0x0, 0x2272, 0x338, 0x0, 0x2273, 0x338, 0x0, 0x2276, + 0x338, 0x0, 0x2277, 0x338, 0x0, 0x227a, 0x338, 0x0, 0x227b, + 0x338, 0x0, 0x227c, 0x338, 0x0, 0x227d, 0x338, 0x0, 0x2282, + 0x338, 0x0, 0x2283, 0x338, 0x0, 0x2286, 0x338, 0x0, 0x2287, + 0x338, 0x0, 0x2291, 0x338, 0x0, 0x2292, 0x338, 0x0, 0x22a2, + 0x338, 0x0, 0x22a8, 0x338, 0x0, 0x22a9, 0x338, 0x0, 0x22ab, + 0x338, 0x0, 0x22b2, 0x338, 0x0, 0x22b3, 0x338, 0x0, 0x22b4, + 0x338, 0x0, 0x22b5, 0x338, 0x0, 0x2add, 0x338, 0x0, 0x3008, + 0x0, 0x3009, 0x0, 0x3046, 0x3099, 0x0, 0x304b, 0x3099, 0x0, + 0x304d, 0x3099, 0x0, 0x304f, 0x3099, 0x0, 0x3051, 0x3099, 0x0, + 0x3053, 0x3099, 0x0, 0x3055, 0x3099, 0x0, 0x3057, 0x3099, 0x0, + 0x3059, 0x3099, 0x0, 0x305b, 0x3099, 0x0, 0x305d, 0x3099, 0x0, + 0x305f, 0x3099, 0x0, 0x3061, 0x3099, 0x0, 0x3064, 0x3099, 0x0, + 0x3066, 0x3099, 0x0, 0x3068, 0x3099, 0x0, 0x306f, 0x3099, 0x0, + 0x306f, 0x309a, 0x0, 0x3072, 0x3099, 0x0, 0x3072, 0x309a, 0x0, + 0x3075, 0x3099, 0x0, 0x3075, 0x309a, 0x0, 0x3078, 0x3099, 0x0, + 0x3078, 0x309a, 0x0, 0x307b, 0x3099, 0x0, 0x307b, 0x309a, 0x0, + 0x309d, 0x3099, 0x0, 0x30a6, 0x3099, 0x0, 0x30ab, 0x3099, 0x0, + 0x30ad, 0x3099, 0x0, 0x30af, 0x3099, 0x0, 0x30b1, 0x3099, 0x0, + 0x30b3, 0x3099, 0x0, 0x30b5, 0x3099, 0x0, 0x30b7, 0x3099, 0x0, + 0x30b9, 0x3099, 0x0, 0x30bb, 0x3099, 0x0, 0x30bd, 0x3099, 0x0, + 0x30bf, 0x3099, 0x0, 0x30c1, 0x3099, 0x0, 0x30c4, 0x3099, 0x0, + 0x30c6, 0x3099, 0x0, 0x30c8, 0x3099, 0x0, 0x30cf, 0x3099, 0x0, + 0x30cf, 0x309a, 0x0, 0x30d2, 0x3099, 0x0, 0x30d2, 0x309a, 0x0, + 0x30d5, 0x3099, 0x0, 0x30d5, 0x309a, 0x0, 0x30d8, 0x3099, 0x0, + 0x30d8, 0x309a, 0x0, 0x30db, 0x3099, 0x0, 0x30db, 0x309a, 0x0, + 0x30ef, 0x3099, 0x0, 0x30f0, 0x3099, 0x0, 0x30f1, 0x3099, 0x0, + 0x30f2, 0x3099, 0x0, 0x30fd, 0x3099, 0x0, 0x349e, 0x0, 0x34b9, + 0x0, 0x34bb, 0x0, 0x34df, 0x0, 0x3515, 0x0, 0x36ee, 0x0, + 0x36fc, 0x0, 0x3781, 0x0, 0x382f, 0x0, 0x3862, 0x0, 0x387c, + 0x0, 0x38c7, 0x0, 0x38e3, 0x0, 0x391c, 0x0, 0x393a, 0x0, + 0x3a2e, 0x0, 0x3a6c, 0x0, 0x3ae4, 0x0, 0x3b08, 0x0, 0x3b19, + 0x0, 0x3b49, 0x0, 0x3b9d, 0x0, 0x3c18, 0x0, 0x3c4e, 0x0, + 0x3d33, 0x0, 0x3d96, 0x0, 0x3eac, 0x0, 0x3eb8, 0x0, 0x3f1b, + 0x0, 0x3ffc, 0x0, 0x4008, 0x0, 0x4018, 0x0, 0x4039, 0x0, + 0x4046, 0x0, 0x4096, 0x0, 0x40e3, 0x0, 0x412f, 0x0, 0x4202, + 0x0, 0x4227, 0x0, 0x42a0, 0x0, 0x4301, 0x0, 0x4334, 0x0, + 0x4359, 0x0, 0x43d5, 0x0, 0x43d9, 0x0, 0x440b, 0x0, 0x446b, + 0x0, 0x452b, 0x0, 0x455d, 0x0, 0x4561, 0x0, 0x456b, 0x0, + 0x45d7, 0x0, 0x45f9, 0x0, 0x4635, 0x0, 0x46be, 0x0, 0x46c7, + 0x0, 0x4995, 0x0, 0x49e6, 0x0, 0x4a6e, 0x0, 0x4a76, 0x0, + 0x4ab2, 0x0, 0x4b33, 0x0, 0x4bce, 0x0, 0x4cce, 0x0, 0x4ced, + 0x0, 0x4cf8, 0x0, 0x4d56, 0x0, 0x4e0d, 0x0, 0x4e26, 0x0, + 0x4e32, 0x0, 0x4e38, 0x0, 0x4e39, 0x0, 0x4e3d, 0x0, 0x4e41, + 0x0, 0x4e82, 0x0, 0x4e86, 0x0, 0x4eae, 0x0, 0x4ec0, 0x0, + 0x4ecc, 0x0, 0x4ee4, 0x0, 0x4f60, 0x0, 0x4f80, 0x0, 0x4f86, + 0x0, 0x4f8b, 0x0, 0x4fae, 0x0, 0x4fbb, 0x0, 0x4fbf, 0x0, + 0x5002, 0x0, 0x502b, 0x0, 0x507a, 0x0, 0x5099, 0x0, 0x50cf, + 0x0, 0x50da, 0x0, 0x50e7, 0x0, 0x5140, 0x0, 0x5145, 0x0, + 0x514d, 0x0, 0x5154, 0x0, 0x5164, 0x0, 0x5167, 0x0, 0x5168, + 0x0, 0x5169, 0x0, 0x516d, 0x0, 0x5177, 0x0, 0x5180, 0x0, + 0x518d, 0x0, 0x5192, 0x0, 0x5195, 0x0, 0x5197, 0x0, 0x51a4, + 0x0, 0x51ac, 0x0, 0x51b5, 0x0, 0x51b7, 0x0, 0x51c9, 0x0, + 0x51cc, 0x0, 0x51dc, 0x0, 0x51de, 0x0, 0x51f5, 0x0, 0x5203, + 0x0, 0x5207, 0x0, 0x5217, 0x0, 0x5229, 0x0, 0x523a, 0x0, + 0x523b, 0x0, 0x5246, 0x0, 0x5272, 0x0, 0x5277, 0x0, 0x5289, + 0x0, 0x529b, 0x0, 0x52a3, 0x0, 0x52b3, 0x0, 0x52c7, 0x0, + 0x52c9, 0x0, 0x52d2, 0x0, 0x52de, 0x0, 0x52e4, 0x0, 0x52f5, + 0x0, 0x52fa, 0x0, 0x5305, 0x0, 0x5306, 0x0, 0x5317, 0x0, + 0x533f, 0x0, 0x5349, 0x0, 0x5351, 0x0, 0x535a, 0x0, 0x5373, + 0x0, 0x5375, 0x0, 0x537d, 0x0, 0x537f, 0x0, 0x53c3, 0x0, + 0x53ca, 0x0, 0x53df, 0x0, 0x53e5, 0x0, 0x53eb, 0x0, 0x53f1, + 0x0, 0x5406, 0x0, 0x540f, 0x0, 0x541d, 0x0, 0x5438, 0x0, + 0x5442, 0x0, 0x5448, 0x0, 0x5468, 0x0, 0x549e, 0x0, 0x54a2, + 0x0, 0x54bd, 0x0, 0x54f6, 0x0, 0x5510, 0x0, 0x5553, 0x0, + 0x5555, 0x0, 0x5563, 0x0, 0x5584, 0x0, 0x5587, 0x0, 0x5599, + 0x0, 0x559d, 0x0, 0x55ab, 0x0, 0x55b3, 0x0, 0x55c0, 0x0, + 0x55c2, 0x0, 0x55e2, 0x0, 0x5606, 0x0, 0x5651, 0x0, 0x5668, + 0x0, 0x5674, 0x0, 0x56f9, 0x0, 0x5716, 0x0, 0x5717, 0x0, + 0x578b, 0x0, 0x57ce, 0x0, 0x57f4, 0x0, 0x580d, 0x0, 0x5831, + 0x0, 0x5832, 0x0, 0x5840, 0x0, 0x585a, 0x0, 0x585e, 0x0, + 0x58a8, 0x0, 0x58ac, 0x0, 0x58b3, 0x0, 0x58d8, 0x0, 0x58df, + 0x0, 0x58ee, 0x0, 0x58f2, 0x0, 0x58f7, 0x0, 0x5906, 0x0, + 0x591a, 0x0, 0x5922, 0x0, 0x5944, 0x0, 0x5948, 0x0, 0x5951, + 0x0, 0x5954, 0x0, 0x5962, 0x0, 0x5973, 0x0, 0x59d8, 0x0, + 0x59ec, 0x0, 0x5a1b, 0x0, 0x5a27, 0x0, 0x5a62, 0x0, 0x5a66, + 0x0, 0x5ab5, 0x0, 0x5b08, 0x0, 0x5b28, 0x0, 0x5b3e, 0x0, + 0x5b85, 0x0, 0x5bc3, 0x0, 0x5bd8, 0x0, 0x5be7, 0x0, 0x5bee, + 0x0, 0x5bf3, 0x0, 0x5bff, 0x0, 0x5c06, 0x0, 0x5c22, 0x0, + 0x5c3f, 0x0, 0x5c60, 0x0, 0x5c62, 0x0, 0x5c64, 0x0, 0x5c65, + 0x0, 0x5c6e, 0x0, 0x5c8d, 0x0, 0x5cc0, 0x0, 0x5d19, 0x0, + 0x5d43, 0x0, 0x5d50, 0x0, 0x5d6b, 0x0, 0x5d6e, 0x0, 0x5d7c, + 0x0, 0x5db2, 0x0, 0x5dba, 0x0, 0x5de1, 0x0, 0x5de2, 0x0, + 0x5dfd, 0x0, 0x5e28, 0x0, 0x5e3d, 0x0, 0x5e69, 0x0, 0x5e74, + 0x0, 0x5ea6, 0x0, 0x5eb0, 0x0, 0x5eb3, 0x0, 0x5eb6, 0x0, + 0x5ec9, 0x0, 0x5eca, 0x0, 0x5ed2, 0x0, 0x5ed3, 0x0, 0x5ed9, + 0x0, 0x5eec, 0x0, 0x5efe, 0x0, 0x5f04, 0x0, 0x5f22, 0x0, + 0x5f53, 0x0, 0x5f62, 0x0, 0x5f69, 0x0, 0x5f6b, 0x0, 0x5f8b, + 0x0, 0x5f9a, 0x0, 0x5fa9, 0x0, 0x5fad, 0x0, 0x5fcd, 0x0, + 0x5fd7, 0x0, 0x5ff5, 0x0, 0x5ff9, 0x0, 0x6012, 0x0, 0x601c, + 0x0, 0x6075, 0x0, 0x6081, 0x0, 0x6094, 0x0, 0x60c7, 0x0, + 0x60d8, 0x0, 0x60e1, 0x0, 0x6108, 0x0, 0x6144, 0x0, 0x6148, + 0x0, 0x614c, 0x0, 0x614e, 0x0, 0x6160, 0x0, 0x6168, 0x0, + 0x617a, 0x0, 0x618e, 0x0, 0x6190, 0x0, 0x61a4, 0x0, 0x61af, + 0x0, 0x61b2, 0x0, 0x61de, 0x0, 0x61f2, 0x0, 0x61f6, 0x0, + 0x6200, 0x0, 0x6210, 0x0, 0x621b, 0x0, 0x622e, 0x0, 0x6234, + 0x0, 0x625d, 0x0, 0x62b1, 0x0, 0x62c9, 0x0, 0x62cf, 0x0, + 0x62d3, 0x0, 0x62d4, 0x0, 0x62fc, 0x0, 0x62fe, 0x0, 0x633d, + 0x0, 0x6350, 0x0, 0x6368, 0x0, 0x637b, 0x0, 0x6383, 0x0, + 0x63a0, 0x0, 0x63a9, 0x0, 0x63c4, 0x0, 0x63c5, 0x0, 0x63e4, + 0x0, 0x641c, 0x0, 0x6422, 0x0, 0x6452, 0x0, 0x6469, 0x0, + 0x6477, 0x0, 0x647e, 0x0, 0x649a, 0x0, 0x649d, 0x0, 0x64c4, + 0x0, 0x654f, 0x0, 0x6556, 0x0, 0x656c, 0x0, 0x6578, 0x0, + 0x6599, 0x0, 0x65c5, 0x0, 0x65e2, 0x0, 0x65e3, 0x0, 0x6613, + 0x0, 0x6649, 0x0, 0x6674, 0x0, 0x6688, 0x0, 0x6691, 0x0, + 0x669c, 0x0, 0x66b4, 0x0, 0x66c6, 0x0, 0x66f4, 0x0, 0x66f8, + 0x0, 0x6700, 0x0, 0x6717, 0x0, 0x671b, 0x0, 0x6721, 0x0, + 0x674e, 0x0, 0x6753, 0x0, 0x6756, 0x0, 0x675e, 0x0, 0x677b, + 0x0, 0x6785, 0x0, 0x6797, 0x0, 0x67f3, 0x0, 0x67fa, 0x0, + 0x6817, 0x0, 0x681f, 0x0, 0x6852, 0x0, 0x6881, 0x0, 0x6885, + 0x0, 0x688e, 0x0, 0x68a8, 0x0, 0x6914, 0x0, 0x6942, 0x0, + 0x69a3, 0x0, 0x69ea, 0x0, 0x6a02, 0x0, 0x6a13, 0x0, 0x6aa8, + 0x0, 0x6ad3, 0x0, 0x6adb, 0x0, 0x6b04, 0x0, 0x6b21, 0x0, + 0x6b54, 0x0, 0x6b72, 0x0, 0x6b77, 0x0, 0x6b79, 0x0, 0x6b9f, + 0x0, 0x6bae, 0x0, 0x6bba, 0x0, 0x6bbb, 0x0, 0x6c4e, 0x0, + 0x6c67, 0x0, 0x6c88, 0x0, 0x6cbf, 0x0, 0x6ccc, 0x0, 0x6ccd, + 0x0, 0x6ce5, 0x0, 0x6d16, 0x0, 0x6d1b, 0x0, 0x6d1e, 0x0, + 0x6d34, 0x0, 0x6d3e, 0x0, 0x6d41, 0x0, 0x6d69, 0x0, 0x6d6a, + 0x0, 0x6d77, 0x0, 0x6d78, 0x0, 0x6d85, 0x0, 0x6dcb, 0x0, + 0x6dda, 0x0, 0x6dea, 0x0, 0x6df9, 0x0, 0x6e1a, 0x0, 0x6e2f, + 0x0, 0x6e6e, 0x0, 0x6e9c, 0x0, 0x6eba, 0x0, 0x6ec7, 0x0, + 0x6ecb, 0x0, 0x6ed1, 0x0, 0x6edb, 0x0, 0x6f0f, 0x0, 0x6f22, + 0x0, 0x6f23, 0x0, 0x6f6e, 0x0, 0x6fc6, 0x0, 0x6feb, 0x0, + 0x6ffe, 0x0, 0x701b, 0x0, 0x701e, 0x0, 0x7039, 0x0, 0x704a, + 0x0, 0x7070, 0x0, 0x7077, 0x0, 0x707d, 0x0, 0x7099, 0x0, + 0x70ad, 0x0, 0x70c8, 0x0, 0x70d9, 0x0, 0x7145, 0x0, 0x7149, + 0x0, 0x716e, 0x0, 0x719c, 0x0, 0x71ce, 0x0, 0x71d0, 0x0, + 0x7210, 0x0, 0x721b, 0x0, 0x7228, 0x0, 0x722b, 0x0, 0x7235, + 0x0, 0x7250, 0x0, 0x7262, 0x0, 0x7280, 0x0, 0x7295, 0x0, + 0x72af, 0x0, 0x72c0, 0x0, 0x72fc, 0x0, 0x732a, 0x0, 0x7375, + 0x0, 0x737a, 0x0, 0x7387, 0x0, 0x738b, 0x0, 0x73a5, 0x0, + 0x73b2, 0x0, 0x73de, 0x0, 0x7406, 0x0, 0x7409, 0x0, 0x7422, + 0x0, 0x7447, 0x0, 0x745c, 0x0, 0x7469, 0x0, 0x7471, 0x0, + 0x7485, 0x0, 0x7489, 0x0, 0x7498, 0x0, 0x74ca, 0x0, 0x7506, + 0x0, 0x7524, 0x0, 0x753b, 0x0, 0x753e, 0x0, 0x7559, 0x0, + 0x7565, 0x0, 0x7570, 0x0, 0x75e2, 0x0, 0x7610, 0x0, 0x761d, + 0x0, 0x761f, 0x0, 0x7642, 0x0, 0x7669, 0x0, 0x76ca, 0x0, + 0x76db, 0x0, 0x76e7, 0x0, 0x76f4, 0x0, 0x7701, 0x0, 0x771e, + 0x0, 0x771f, 0x0, 0x7740, 0x0, 0x774a, 0x0, 0x778b, 0x0, + 0x77a7, 0x0, 0x784e, 0x0, 0x786b, 0x0, 0x788c, 0x0, 0x7891, + 0x0, 0x78ca, 0x0, 0x78cc, 0x0, 0x78fb, 0x0, 0x792a, 0x0, + 0x793c, 0x0, 0x793e, 0x0, 0x7948, 0x0, 0x7949, 0x0, 0x7950, + 0x0, 0x7956, 0x0, 0x795d, 0x0, 0x795e, 0x0, 0x7965, 0x0, + 0x797f, 0x0, 0x798d, 0x0, 0x798e, 0x0, 0x798f, 0x0, 0x79ae, + 0x0, 0x79ca, 0x0, 0x79eb, 0x0, 0x7a1c, 0x0, 0x7a40, 0x0, + 0x7a4a, 0x0, 0x7a4f, 0x0, 0x7a81, 0x0, 0x7ab1, 0x0, 0x7acb, + 0x0, 0x7aee, 0x0, 0x7b20, 0x0, 0x7bc0, 0x0, 0x7bc6, 0x0, + 0x7bc9, 0x0, 0x7c3e, 0x0, 0x7c60, 0x0, 0x7c7b, 0x0, 0x7c92, + 0x0, 0x7cbe, 0x0, 0x7cd2, 0x0, 0x7cd6, 0x0, 0x7ce3, 0x0, + 0x7ce7, 0x0, 0x7ce8, 0x0, 0x7d00, 0x0, 0x7d10, 0x0, 0x7d22, + 0x0, 0x7d2f, 0x0, 0x7d5b, 0x0, 0x7d63, 0x0, 0x7da0, 0x0, + 0x7dbe, 0x0, 0x7dc7, 0x0, 0x7df4, 0x0, 0x7e02, 0x0, 0x7e09, + 0x0, 0x7e37, 0x0, 0x7e41, 0x0, 0x7e45, 0x0, 0x7f3e, 0x0, + 0x7f72, 0x0, 0x7f79, 0x0, 0x7f7a, 0x0, 0x7f85, 0x0, 0x7f95, + 0x0, 0x7f9a, 0x0, 0x7fbd, 0x0, 0x7ffa, 0x0, 0x8001, 0x0, + 0x8005, 0x0, 0x8046, 0x0, 0x8060, 0x0, 0x806f, 0x0, 0x8070, + 0x0, 0x807e, 0x0, 0x808b, 0x0, 0x80ad, 0x0, 0x80b2, 0x0, + 0x8103, 0x0, 0x813e, 0x0, 0x81d8, 0x0, 0x81e8, 0x0, 0x81ed, + 0x0, 0x8201, 0x0, 0x8204, 0x0, 0x8218, 0x0, 0x826f, 0x0, + 0x8279, 0x0, 0x828b, 0x0, 0x8291, 0x0, 0x829d, 0x0, 0x82b1, + 0x0, 0x82b3, 0x0, 0x82bd, 0x0, 0x82e5, 0x0, 0x82e6, 0x0, + 0x831d, 0x0, 0x8323, 0x0, 0x8336, 0x0, 0x8352, 0x0, 0x8353, + 0x0, 0x8363, 0x0, 0x83ad, 0x0, 0x83bd, 0x0, 0x83c9, 0x0, + 0x83ca, 0x0, 0x83cc, 0x0, 0x83dc, 0x0, 0x83e7, 0x0, 0x83ef, + 0x0, 0x83f1, 0x0, 0x843d, 0x0, 0x8449, 0x0, 0x8457, 0x0, + 0x84ee, 0x0, 0x84f1, 0x0, 0x84f3, 0x0, 0x84fc, 0x0, 0x8516, + 0x0, 0x8564, 0x0, 0x85cd, 0x0, 0x85fa, 0x0, 0x8606, 0x0, + 0x8612, 0x0, 0x862d, 0x0, 0x863f, 0x0, 0x8650, 0x0, 0x865c, + 0x0, 0x8667, 0x0, 0x8669, 0x0, 0x8688, 0x0, 0x86a9, 0x0, + 0x86e2, 0x0, 0x870e, 0x0, 0x8728, 0x0, 0x876b, 0x0, 0x8779, + 0x0, 0x8786, 0x0, 0x87ba, 0x0, 0x87e1, 0x0, 0x8801, 0x0, + 0x881f, 0x0, 0x884c, 0x0, 0x8860, 0x0, 0x8863, 0x0, 0x88c2, + 0x0, 0x88cf, 0x0, 0x88d7, 0x0, 0x88de, 0x0, 0x88e1, 0x0, + 0x88f8, 0x0, 0x88fa, 0x0, 0x8910, 0x0, 0x8941, 0x0, 0x8964, + 0x0, 0x8986, 0x0, 0x898b, 0x0, 0x8996, 0x0, 0x8aa0, 0x0, + 0x8aaa, 0x0, 0x8abf, 0x0, 0x8acb, 0x0, 0x8ad2, 0x0, 0x8ad6, + 0x0, 0x8aed, 0x0, 0x8af8, 0x0, 0x8afe, 0x0, 0x8b01, 0x0, + 0x8b39, 0x0, 0x8b58, 0x0, 0x8b80, 0x0, 0x8b8a, 0x0, 0x8c48, + 0x0, 0x8c55, 0x0, 0x8cab, 0x0, 0x8cc1, 0x0, 0x8cc2, 0x0, + 0x8cc8, 0x0, 0x8cd3, 0x0, 0x8d08, 0x0, 0x8d1b, 0x0, 0x8d77, + 0x0, 0x8dbc, 0x0, 0x8dcb, 0x0, 0x8def, 0x0, 0x8df0, 0x0, + 0x8eca, 0x0, 0x8ed4, 0x0, 0x8f26, 0x0, 0x8f2a, 0x0, 0x8f38, + 0x0, 0x8f3b, 0x0, 0x8f62, 0x0, 0x8f9e, 0x0, 0x8fb0, 0x0, + 0x8fb6, 0x0, 0x9023, 0x0, 0x9038, 0x0, 0x9072, 0x0, 0x907c, + 0x0, 0x908f, 0x0, 0x9094, 0x0, 0x90ce, 0x0, 0x90de, 0x0, + 0x90f1, 0x0, 0x90fd, 0x0, 0x9111, 0x0, 0x911b, 0x0, 0x916a, + 0x0, 0x9199, 0x0, 0x91b4, 0x0, 0x91cc, 0x0, 0x91cf, 0x0, + 0x91d1, 0x0, 0x9234, 0x0, 0x9238, 0x0, 0x9276, 0x0, 0x927c, + 0x0, 0x92d7, 0x0, 0x92d8, 0x0, 0x9304, 0x0, 0x934a, 0x0, + 0x93f9, 0x0, 0x9415, 0x0, 0x958b, 0x0, 0x95ad, 0x0, 0x95b7, + 0x0, 0x962e, 0x0, 0x964b, 0x0, 0x964d, 0x0, 0x9675, 0x0, + 0x9678, 0x0, 0x967c, 0x0, 0x9686, 0x0, 0x96a3, 0x0, 0x96b7, + 0x0, 0x96b8, 0x0, 0x96c3, 0x0, 0x96e2, 0x0, 0x96e3, 0x0, + 0x96f6, 0x0, 0x96f7, 0x0, 0x9723, 0x0, 0x9732, 0x0, 0x9748, + 0x0, 0x9756, 0x0, 0x97db, 0x0, 0x97e0, 0x0, 0x97ff, 0x0, + 0x980b, 0x0, 0x9818, 0x0, 0x9829, 0x0, 0x983b, 0x0, 0x985e, + 0x0, 0x98e2, 0x0, 0x98ef, 0x0, 0x98fc, 0x0, 0x9928, 0x0, + 0x9929, 0x0, 0x99a7, 0x0, 0x99c2, 0x0, 0x99f1, 0x0, 0x99fe, + 0x0, 0x9a6a, 0x0, 0x9b12, 0x0, 0x9b6f, 0x0, 0x9c40, 0x0, + 0x9c57, 0x0, 0x9cfd, 0x0, 0x9d67, 0x0, 0x9db4, 0x0, 0x9dfa, + 0x0, 0x9e1e, 0x0, 0x9e7f, 0x0, 0x9e97, 0x0, 0x9e9f, 0x0, + 0x9ebb, 0x0, 0x9ece, 0x0, 0x9ef9, 0x0, 0x9efe, 0x0, 0x9f05, + 0x0, 0x9f0f, 0x0, 0x9f16, 0x0, 0x9f3b, 0x0, 0x9f43, 0x0, + 0x9f8d, 0x0, 0x9f8e, 0x0, 0x9f9c, 0x0, 0x11099, 0x110ba, 0x0, + 0x1109b, 0x110ba, 0x0, 0x110a5, 0x110ba, 0x0, 0x11131, 0x11127, + 0x0, 0x11132, 0x11127, 0x0, 0x1d157, 0x1d165, 0x0, 0x1d158, + 0x1d165, 0x0, 0x1d158, 0x1d165, 0x1d16e, 0x0, 0x1d158, 0x1d165, + 0x1d16f, 0x0, 0x1d158, 0x1d165, 0x1d170, 0x0, 0x1d158, 0x1d165, + 0x1d171, 0x0, 0x1d158, 0x1d165, 0x1d172, 0x0, 0x1d1b9, 0x1d165, + 0x0, 0x1d1b9, 0x1d165, 0x1d16e, 0x0, 0x1d1b9, 0x1d165, 0x1d16f, + 0x0, 0x1d1ba, 0x1d165, 0x0, 0x1d1ba, 0x1d165, 0x1d16e, 0x0, + 0x1d1ba, 0x1d165, 0x1d16f, 0x0, 0x20122, 0x0, 0x2051c, 0x0, + 0x20525, 0x0, 0x2054b, 0x0, 0x2063a, 0x0, 0x20804, 0x0, + 0x208de, 0x0, 0x20a2c, 0x0, 0x20b63, 0x0, 0x214e4, 0x0, + 0x216a8, 0x0, 0x216ea, 0x0, 0x219c8, 0x0, 0x21b18, 0x0, + 0x21d0b, 0x0, 0x21de4, 0x0, 0x21de6, 0x0, 0x22183, 0x0, + 0x2219f, 0x0, 0x22331, 0x0, 0x226d4, 0x0, 0x22844, 0x0, + 0x2284a, 0x0, 0x22b0c, 0x0, 0x22bf1, 0x0, 0x2300a, 0x0, + 0x232b8, 0x0, 0x2335f, 0x0, 0x23393, 0x0, 0x2339c, 0x0, + 0x233c3, 0x0, 0x233d5, 0x0, 0x2346d, 0x0, 0x236a3, 0x0, + 0x238a7, 0x0, 0x23a8d, 0x0, 0x23afa, 0x0, 0x23cbc, 0x0, + 0x23d1e, 0x0, 0x23ed1, 0x0, 0x23f5e, 0x0, 0x23f8e, 0x0, + 0x24263, 0x0, 0x242ee, 0x0, 0x243ab, 0x0, 0x24608, 0x0, + 0x24735, 0x0, 0x24814, 0x0, 0x24c36, 0x0, 0x24c92, 0x0, + 0x24fa1, 0x0, 0x24fb8, 0x0, 0x25044, 0x0, 0x250f2, 0x0, + 0x250f3, 0x0, 0x25119, 0x0, 0x25133, 0x0, 0x25249, 0x0, + 0x2541d, 0x0, 0x25626, 0x0, 0x2569a, 0x0, 0x256c5, 0x0, + 0x2597c, 0x0, 0x25aa7, 0x0, 0x25bab, 0x0, 0x25c80, 0x0, + 0x25cd0, 0x0, 0x25f86, 0x0, 0x261da, 0x0, 0x26228, 0x0, + 0x26247, 0x0, 0x262d9, 0x0, 0x2633e, 0x0, 0x264da, 0x0, + 0x26523, 0x0, 0x265a8, 0x0, 0x267a7, 0x0, 0x267b5, 0x0, + 0x26b3c, 0x0, 0x26c36, 0x0, 0x26cd5, 0x0, 0x26d6b, 0x0, + 0x26f2c, 0x0, 0x26fb1, 0x0, 0x270d2, 0x0, 0x273ca, 0x0, + 0x27667, 0x0, 0x278ae, 0x0, 0x27966, 0x0, 0x27ca8, 0x0, + 0x27ed3, 0x0, 0x27f2f, 0x0, 0x285d2, 0x0, 0x285ed, 0x0, + 0x2872e, 0x0, 0x28bfa, 0x0, 0x28d77, 0x0, 0x29145, 0x0, + 0x291df, 0x0, 0x2921a, 0x0, 0x2940a, 0x0, 0x29496, 0x0, + 0x295b6, 0x0, 0x29b30, 0x0, 0x2a0ce, 0x0, 0x2a105, 0x0, + 0x2a20e, 0x0, 0x2a291, 0x0, 0x2a392, 0x0, 0x2a600, 0x0 + ]; + return t; + } + + _IDCA decompCompatTable() + { + static _IDCA t = [ + 0x0, 0x20, 0x0, 0x20, 0x301, 0x0, 0x20, 0x303, 0x0, 0x20, + 0x304, 0x0, 0x20, 0x305, 0x0, 0x20, 0x306, 0x0, 0x20, 0x307, + 0x0, 0x20, 0x308, 0x0, 0x20, 0x308, 0x300, 0x0, 0x20, 0x308, + 0x301, 0x0, 0x20, 0x308, 0x342, 0x0, 0x20, 0x30a, 0x0, 0x20, + 0x30b, 0x0, 0x20, 0x313, 0x0, 0x20, 0x313, 0x300, 0x0, 0x20, + 0x313, 0x301, 0x0, 0x20, 0x313, 0x342, 0x0, 0x20, 0x314, 0x0, + 0x20, 0x314, 0x300, 0x0, 0x20, 0x314, 0x301, 0x0, 0x20, 0x314, + 0x342, 0x0, 0x20, 0x327, 0x0, 0x20, 0x328, 0x0, 0x20, 0x333, + 0x0, 0x20, 0x342, 0x0, 0x20, 0x345, 0x0, 0x20, 0x64b, 0x0, + 0x20, 0x64c, 0x0, 0x20, 0x64c, 0x651, 0x0, 0x20, 0x64d, 0x0, + 0x20, 0x64d, 0x651, 0x0, 0x20, 0x64e, 0x0, 0x20, 0x64e, 0x651, + 0x0, 0x20, 0x64f, 0x0, 0x20, 0x64f, 0x651, 0x0, 0x20, 0x650, + 0x0, 0x20, 0x650, 0x651, 0x0, 0x20, 0x651, 0x0, 0x20, 0x651, + 0x670, 0x0, 0x20, 0x652, 0x0, 0x20, 0x3099, 0x0, 0x20, 0x309a, + 0x0, 0x21, 0x0, 0x21, 0x21, 0x0, 0x21, 0x3f, 0x0, 0x22, 0x0, + 0x23, 0x0, 0x24, 0x0, 0x25, 0x0, 0x26, 0x0, 0x27, 0x0, 0x28, + 0x0, 0x28, 0x31, 0x29, 0x0, 0x28, 0x31, 0x30, 0x29, 0x0, 0x28, + 0x31, 0x31, 0x29, 0x0, 0x28, 0x31, 0x32, 0x29, 0x0, 0x28, 0x31, + 0x33, 0x29, 0x0, 0x28, 0x31, 0x34, 0x29, 0x0, 0x28, 0x31, 0x35, + 0x29, 0x0, 0x28, 0x31, 0x36, 0x29, 0x0, 0x28, 0x31, 0x37, 0x29, + 0x0, 0x28, 0x31, 0x38, 0x29, 0x0, 0x28, 0x31, 0x39, 0x29, 0x0, + 0x28, 0x32, 0x29, 0x0, 0x28, 0x32, 0x30, 0x29, 0x0, 0x28, 0x33, + 0x29, 0x0, 0x28, 0x34, 0x29, 0x0, 0x28, 0x35, 0x29, 0x0, 0x28, + 0x36, 0x29, 0x0, 0x28, 0x37, 0x29, 0x0, 0x28, 0x38, 0x29, 0x0, + 0x28, 0x39, 0x29, 0x0, 0x28, 0x41, 0x29, 0x0, 0x28, 0x42, 0x29, + 0x0, 0x28, 0x43, 0x29, 0x0, 0x28, 0x44, 0x29, 0x0, 0x28, 0x45, + 0x29, 0x0, 0x28, 0x46, 0x29, 0x0, 0x28, 0x47, 0x29, 0x0, 0x28, + 0x48, 0x29, 0x0, 0x28, 0x49, 0x29, 0x0, 0x28, 0x4a, 0x29, 0x0, + 0x28, 0x4b, 0x29, 0x0, 0x28, 0x4c, 0x29, 0x0, 0x28, 0x4d, 0x29, + 0x0, 0x28, 0x4e, 0x29, 0x0, 0x28, 0x4f, 0x29, 0x0, 0x28, 0x50, + 0x29, 0x0, 0x28, 0x51, 0x29, 0x0, 0x28, 0x52, 0x29, 0x0, 0x28, + 0x53, 0x29, 0x0, 0x28, 0x54, 0x29, 0x0, 0x28, 0x55, 0x29, 0x0, + 0x28, 0x56, 0x29, 0x0, 0x28, 0x57, 0x29, 0x0, 0x28, 0x58, 0x29, + 0x0, 0x28, 0x59, 0x29, 0x0, 0x28, 0x5a, 0x29, 0x0, 0x28, 0x61, + 0x29, 0x0, 0x28, 0x62, 0x29, 0x0, 0x28, 0x63, 0x29, 0x0, 0x28, + 0x64, 0x29, 0x0, 0x28, 0x65, 0x29, 0x0, 0x28, 0x66, 0x29, 0x0, + 0x28, 0x67, 0x29, 0x0, 0x28, 0x68, 0x29, 0x0, 0x28, 0x69, 0x29, + 0x0, 0x28, 0x6a, 0x29, 0x0, 0x28, 0x6b, 0x29, 0x0, 0x28, 0x6c, + 0x29, 0x0, 0x28, 0x6d, 0x29, 0x0, 0x28, 0x6e, 0x29, 0x0, 0x28, + 0x6f, 0x29, 0x0, 0x28, 0x70, 0x29, 0x0, 0x28, 0x71, 0x29, 0x0, + 0x28, 0x72, 0x29, 0x0, 0x28, 0x73, 0x29, 0x0, 0x28, 0x74, 0x29, + 0x0, 0x28, 0x75, 0x29, 0x0, 0x28, 0x76, 0x29, 0x0, 0x28, 0x77, + 0x29, 0x0, 0x28, 0x78, 0x29, 0x0, 0x28, 0x79, 0x29, 0x0, 0x28, + 0x7a, 0x29, 0x0, 0x28, 0x1100, 0x29, 0x0, 0x28, 0x1100, 0x1161, + 0x29, 0x0, 0x28, 0x1102, 0x29, 0x0, 0x28, 0x1102, 0x1161, 0x29, + 0x0, 0x28, 0x1103, 0x29, 0x0, 0x28, 0x1103, 0x1161, 0x29, 0x0, + 0x28, 0x1105, 0x29, 0x0, 0x28, 0x1105, 0x1161, 0x29, 0x0, 0x28, + 0x1106, 0x29, 0x0, 0x28, 0x1106, 0x1161, 0x29, 0x0, 0x28, + 0x1107, 0x29, 0x0, 0x28, 0x1107, 0x1161, 0x29, 0x0, 0x28, + 0x1109, 0x29, 0x0, 0x28, 0x1109, 0x1161, 0x29, 0x0, 0x28, + 0x110b, 0x29, 0x0, 0x28, 0x110b, 0x1161, 0x29, 0x0, 0x28, + 0x110b, 0x1169, 0x110c, 0x1165, 0x11ab, 0x29, 0x0, 0x28, + 0x110b, 0x1169, 0x1112, 0x116e, 0x29, 0x0, 0x28, 0x110c, 0x29, + 0x0, 0x28, 0x110c, 0x1161, 0x29, 0x0, 0x28, 0x110c, 0x116e, + 0x29, 0x0, 0x28, 0x110e, 0x29, 0x0, 0x28, 0x110e, 0x1161, 0x29, + 0x0, 0x28, 0x110f, 0x29, 0x0, 0x28, 0x110f, 0x1161, 0x29, 0x0, + 0x28, 0x1110, 0x29, 0x0, 0x28, 0x1110, 0x1161, 0x29, 0x0, 0x28, + 0x1111, 0x29, 0x0, 0x28, 0x1111, 0x1161, 0x29, 0x0, 0x28, + 0x1112, 0x29, 0x0, 0x28, 0x1112, 0x1161, 0x29, 0x0, 0x28, + 0x4e00, 0x29, 0x0, 0x28, 0x4e03, 0x29, 0x0, 0x28, 0x4e09, 0x29, + 0x0, 0x28, 0x4e5d, 0x29, 0x0, 0x28, 0x4e8c, 0x29, 0x0, 0x28, + 0x4e94, 0x29, 0x0, 0x28, 0x4ee3, 0x29, 0x0, 0x28, 0x4f01, 0x29, + 0x0, 0x28, 0x4f11, 0x29, 0x0, 0x28, 0x516b, 0x29, 0x0, 0x28, + 0x516d, 0x29, 0x0, 0x28, 0x52b4, 0x29, 0x0, 0x28, 0x5341, 0x29, + 0x0, 0x28, 0x5354, 0x29, 0x0, 0x28, 0x540d, 0x29, 0x0, 0x28, + 0x547c, 0x29, 0x0, 0x28, 0x56db, 0x29, 0x0, 0x28, 0x571f, 0x29, + 0x0, 0x28, 0x5b66, 0x29, 0x0, 0x28, 0x65e5, 0x29, 0x0, 0x28, + 0x6708, 0x29, 0x0, 0x28, 0x6709, 0x29, 0x0, 0x28, 0x6728, 0x29, + 0x0, 0x28, 0x682a, 0x29, 0x0, 0x28, 0x6c34, 0x29, 0x0, 0x28, + 0x706b, 0x29, 0x0, 0x28, 0x7279, 0x29, 0x0, 0x28, 0x76e3, 0x29, + 0x0, 0x28, 0x793e, 0x29, 0x0, 0x28, 0x795d, 0x29, 0x0, 0x28, + 0x796d, 0x29, 0x0, 0x28, 0x81ea, 0x29, 0x0, 0x28, 0x81f3, 0x29, + 0x0, 0x28, 0x8ca1, 0x29, 0x0, 0x28, 0x8cc7, 0x29, 0x0, 0x28, + 0x91d1, 0x29, 0x0, 0x29, 0x0, 0x2a, 0x0, 0x2b, 0x0, 0x2c, 0x0, + 0x2d, 0x0, 0x2e, 0x0, 0x2e, 0x2e, 0x0, 0x2e, 0x2e, 0x2e, 0x0, + 0x2f, 0x0, 0x30, 0x0, 0x30, 0x2c, 0x0, 0x30, 0x2e, 0x0, 0x30, + 0x2044, 0x33, 0x0, 0x30, 0x70b9, 0x0, 0x31, 0x0, 0x31, 0x2c, + 0x0, 0x31, 0x2e, 0x0, 0x31, 0x30, 0x0, 0x31, 0x30, 0x2e, 0x0, + 0x31, 0x30, 0x65e5, 0x0, 0x31, 0x30, 0x6708, 0x0, 0x31, 0x30, + 0x70b9, 0x0, 0x31, 0x31, 0x0, 0x31, 0x31, 0x2e, 0x0, 0x31, + 0x31, 0x65e5, 0x0, 0x31, 0x31, 0x6708, 0x0, 0x31, 0x31, 0x70b9, + 0x0, 0x31, 0x32, 0x0, 0x31, 0x32, 0x2e, 0x0, 0x31, 0x32, + 0x65e5, 0x0, 0x31, 0x32, 0x6708, 0x0, 0x31, 0x32, 0x70b9, 0x0, + 0x31, 0x33, 0x0, 0x31, 0x33, 0x2e, 0x0, 0x31, 0x33, 0x65e5, + 0x0, 0x31, 0x33, 0x70b9, 0x0, 0x31, 0x34, 0x0, 0x31, 0x34, + 0x2e, 0x0, 0x31, 0x34, 0x65e5, 0x0, 0x31, 0x34, 0x70b9, 0x0, + 0x31, 0x35, 0x0, 0x31, 0x35, 0x2e, 0x0, 0x31, 0x35, 0x65e5, + 0x0, 0x31, 0x35, 0x70b9, 0x0, 0x31, 0x36, 0x0, 0x31, 0x36, + 0x2e, 0x0, 0x31, 0x36, 0x65e5, 0x0, 0x31, 0x36, 0x70b9, 0x0, + 0x31, 0x37, 0x0, 0x31, 0x37, 0x2e, 0x0, 0x31, 0x37, 0x65e5, + 0x0, 0x31, 0x37, 0x70b9, 0x0, 0x31, 0x38, 0x0, 0x31, 0x38, + 0x2e, 0x0, 0x31, 0x38, 0x65e5, 0x0, 0x31, 0x38, 0x70b9, 0x0, + 0x31, 0x39, 0x0, 0x31, 0x39, 0x2e, 0x0, 0x31, 0x39, 0x65e5, + 0x0, 0x31, 0x39, 0x70b9, 0x0, 0x31, 0x2044, 0x0, 0x31, 0x2044, + 0x31, 0x30, 0x0, 0x31, 0x2044, 0x32, 0x0, 0x31, 0x2044, 0x33, + 0x0, 0x31, 0x2044, 0x34, 0x0, 0x31, 0x2044, 0x35, 0x0, 0x31, + 0x2044, 0x36, 0x0, 0x31, 0x2044, 0x37, 0x0, 0x31, 0x2044, 0x38, + 0x0, 0x31, 0x2044, 0x39, 0x0, 0x31, 0x65e5, 0x0, 0x31, 0x6708, + 0x0, 0x31, 0x70b9, 0x0, 0x32, 0x0, 0x32, 0x2c, 0x0, 0x32, 0x2e, + 0x0, 0x32, 0x30, 0x0, 0x32, 0x30, 0x2e, 0x0, 0x32, 0x30, + 0x65e5, 0x0, 0x32, 0x30, 0x70b9, 0x0, 0x32, 0x31, 0x0, 0x32, + 0x31, 0x65e5, 0x0, 0x32, 0x31, 0x70b9, 0x0, 0x32, 0x32, 0x0, + 0x32, 0x32, 0x65e5, 0x0, 0x32, 0x32, 0x70b9, 0x0, 0x32, 0x33, + 0x0, 0x32, 0x33, 0x65e5, 0x0, 0x32, 0x33, 0x70b9, 0x0, 0x32, + 0x34, 0x0, 0x32, 0x34, 0x65e5, 0x0, 0x32, 0x34, 0x70b9, 0x0, + 0x32, 0x35, 0x0, 0x32, 0x35, 0x65e5, 0x0, 0x32, 0x36, 0x0, + 0x32, 0x36, 0x65e5, 0x0, 0x32, 0x37, 0x0, 0x32, 0x37, 0x65e5, + 0x0, 0x32, 0x38, 0x0, 0x32, 0x38, 0x65e5, 0x0, 0x32, 0x39, 0x0, + 0x32, 0x39, 0x65e5, 0x0, 0x32, 0x2044, 0x33, 0x0, 0x32, 0x2044, + 0x35, 0x0, 0x32, 0x65e5, 0x0, 0x32, 0x6708, 0x0, 0x32, 0x70b9, + 0x0, 0x33, 0x0, 0x33, 0x2c, 0x0, 0x33, 0x2e, 0x0, 0x33, 0x30, + 0x0, 0x33, 0x30, 0x65e5, 0x0, 0x33, 0x31, 0x0, 0x33, 0x31, + 0x65e5, 0x0, 0x33, 0x32, 0x0, 0x33, 0x33, 0x0, 0x33, 0x34, 0x0, + 0x33, 0x35, 0x0, 0x33, 0x36, 0x0, 0x33, 0x37, 0x0, 0x33, 0x38, + 0x0, 0x33, 0x39, 0x0, 0x33, 0x2044, 0x34, 0x0, 0x33, 0x2044, + 0x35, 0x0, 0x33, 0x2044, 0x38, 0x0, 0x33, 0x65e5, 0x0, 0x33, + 0x6708, 0x0, 0x33, 0x70b9, 0x0, 0x34, 0x0, 0x34, 0x2c, 0x0, + 0x34, 0x2e, 0x0, 0x34, 0x30, 0x0, 0x34, 0x31, 0x0, 0x34, 0x32, + 0x0, 0x34, 0x33, 0x0, 0x34, 0x34, 0x0, 0x34, 0x35, 0x0, 0x34, + 0x36, 0x0, 0x34, 0x37, 0x0, 0x34, 0x38, 0x0, 0x34, 0x39, 0x0, + 0x34, 0x2044, 0x35, 0x0, 0x34, 0x65e5, 0x0, 0x34, 0x6708, 0x0, + 0x34, 0x70b9, 0x0, 0x35, 0x0, 0x35, 0x2c, 0x0, 0x35, 0x2e, 0x0, + 0x35, 0x30, 0x0, 0x35, 0x2044, 0x36, 0x0, 0x35, 0x2044, 0x38, + 0x0, 0x35, 0x65e5, 0x0, 0x35, 0x6708, 0x0, 0x35, 0x70b9, 0x0, + 0x36, 0x0, 0x36, 0x2c, 0x0, 0x36, 0x2e, 0x0, 0x36, 0x65e5, 0x0, + 0x36, 0x6708, 0x0, 0x36, 0x70b9, 0x0, 0x37, 0x0, 0x37, 0x2c, + 0x0, 0x37, 0x2e, 0x0, 0x37, 0x2044, 0x38, 0x0, 0x37, 0x65e5, + 0x0, 0x37, 0x6708, 0x0, 0x37, 0x70b9, 0x0, 0x38, 0x0, 0x38, + 0x2c, 0x0, 0x38, 0x2e, 0x0, 0x38, 0x65e5, 0x0, 0x38, 0x6708, + 0x0, 0x38, 0x70b9, 0x0, 0x39, 0x0, 0x39, 0x2c, 0x0, 0x39, 0x2e, + 0x0, 0x39, 0x65e5, 0x0, 0x39, 0x6708, 0x0, 0x39, 0x70b9, 0x0, + 0x3a, 0x0, 0x3a, 0x3a, 0x3d, 0x0, 0x3b, 0x0, 0x3c, 0x0, 0x3c, + 0x338, 0x0, 0x3d, 0x0, 0x3d, 0x3d, 0x0, 0x3d, 0x3d, 0x3d, 0x0, + 0x3d, 0x338, 0x0, 0x3e, 0x0, 0x3e, 0x338, 0x0, 0x3f, 0x0, 0x3f, + 0x21, 0x0, 0x3f, 0x3f, 0x0, 0x40, 0x0, 0x41, 0x0, 0x41, 0x55, + 0x0, 0x41, 0x300, 0x0, 0x41, 0x301, 0x0, 0x41, 0x302, 0x0, + 0x41, 0x302, 0x300, 0x0, 0x41, 0x302, 0x301, 0x0, 0x41, 0x302, + 0x303, 0x0, 0x41, 0x302, 0x309, 0x0, 0x41, 0x303, 0x0, 0x41, + 0x304, 0x0, 0x41, 0x306, 0x0, 0x41, 0x306, 0x300, 0x0, 0x41, + 0x306, 0x301, 0x0, 0x41, 0x306, 0x303, 0x0, 0x41, 0x306, 0x309, + 0x0, 0x41, 0x307, 0x0, 0x41, 0x307, 0x304, 0x0, 0x41, 0x308, + 0x0, 0x41, 0x308, 0x304, 0x0, 0x41, 0x309, 0x0, 0x41, 0x30a, + 0x0, 0x41, 0x30a, 0x301, 0x0, 0x41, 0x30c, 0x0, 0x41, 0x30f, + 0x0, 0x41, 0x311, 0x0, 0x41, 0x323, 0x0, 0x41, 0x323, 0x302, + 0x0, 0x41, 0x323, 0x306, 0x0, 0x41, 0x325, 0x0, 0x41, 0x328, + 0x0, 0x41, 0x2215, 0x6d, 0x0, 0x42, 0x0, 0x42, 0x71, 0x0, 0x42, + 0x307, 0x0, 0x42, 0x323, 0x0, 0x42, 0x331, 0x0, 0x43, 0x0, + 0x43, 0x44, 0x0, 0x43, 0x6f, 0x2e, 0x0, 0x43, 0x301, 0x0, 0x43, + 0x302, 0x0, 0x43, 0x307, 0x0, 0x43, 0x30c, 0x0, 0x43, 0x327, + 0x0, 0x43, 0x327, 0x301, 0x0, 0x43, 0x2215, 0x6b, 0x67, 0x0, + 0x44, 0x0, 0x44, 0x4a, 0x0, 0x44, 0x5a, 0x0, 0x44, 0x5a, 0x30c, + 0x0, 0x44, 0x7a, 0x0, 0x44, 0x7a, 0x30c, 0x0, 0x44, 0x307, 0x0, + 0x44, 0x30c, 0x0, 0x44, 0x323, 0x0, 0x44, 0x327, 0x0, 0x44, + 0x32d, 0x0, 0x44, 0x331, 0x0, 0x45, 0x0, 0x45, 0x300, 0x0, + 0x45, 0x301, 0x0, 0x45, 0x302, 0x0, 0x45, 0x302, 0x300, 0x0, + 0x45, 0x302, 0x301, 0x0, 0x45, 0x302, 0x303, 0x0, 0x45, 0x302, + 0x309, 0x0, 0x45, 0x303, 0x0, 0x45, 0x304, 0x0, 0x45, 0x304, + 0x300, 0x0, 0x45, 0x304, 0x301, 0x0, 0x45, 0x306, 0x0, 0x45, + 0x307, 0x0, 0x45, 0x308, 0x0, 0x45, 0x309, 0x0, 0x45, 0x30c, + 0x0, 0x45, 0x30f, 0x0, 0x45, 0x311, 0x0, 0x45, 0x323, 0x0, + 0x45, 0x323, 0x302, 0x0, 0x45, 0x327, 0x0, 0x45, 0x327, 0x306, + 0x0, 0x45, 0x328, 0x0, 0x45, 0x32d, 0x0, 0x45, 0x330, 0x0, + 0x46, 0x0, 0x46, 0x41, 0x58, 0x0, 0x46, 0x307, 0x0, 0x47, 0x0, + 0x47, 0x42, 0x0, 0x47, 0x48, 0x7a, 0x0, 0x47, 0x50, 0x61, 0x0, + 0x47, 0x79, 0x0, 0x47, 0x301, 0x0, 0x47, 0x302, 0x0, 0x47, + 0x304, 0x0, 0x47, 0x306, 0x0, 0x47, 0x307, 0x0, 0x47, 0x30c, + 0x0, 0x47, 0x327, 0x0, 0x48, 0x0, 0x48, 0x50, 0x0, 0x48, 0x56, + 0x0, 0x48, 0x67, 0x0, 0x48, 0x7a, 0x0, 0x48, 0x302, 0x0, 0x48, + 0x307, 0x0, 0x48, 0x308, 0x0, 0x48, 0x30c, 0x0, 0x48, 0x323, + 0x0, 0x48, 0x327, 0x0, 0x48, 0x32e, 0x0, 0x49, 0x0, 0x49, 0x49, + 0x0, 0x49, 0x49, 0x49, 0x0, 0x49, 0x4a, 0x0, 0x49, 0x55, 0x0, + 0x49, 0x56, 0x0, 0x49, 0x58, 0x0, 0x49, 0x300, 0x0, 0x49, + 0x301, 0x0, 0x49, 0x302, 0x0, 0x49, 0x303, 0x0, 0x49, 0x304, + 0x0, 0x49, 0x306, 0x0, 0x49, 0x307, 0x0, 0x49, 0x308, 0x0, + 0x49, 0x308, 0x301, 0x0, 0x49, 0x309, 0x0, 0x49, 0x30c, 0x0, + 0x49, 0x30f, 0x0, 0x49, 0x311, 0x0, 0x49, 0x323, 0x0, 0x49, + 0x328, 0x0, 0x49, 0x330, 0x0, 0x4a, 0x0, 0x4a, 0x302, 0x0, + 0x4b, 0x0, 0x4b, 0x42, 0x0, 0x4b, 0x4b, 0x0, 0x4b, 0x4d, 0x0, + 0x4b, 0x301, 0x0, 0x4b, 0x30c, 0x0, 0x4b, 0x323, 0x0, 0x4b, + 0x327, 0x0, 0x4b, 0x331, 0x0, 0x4c, 0x0, 0x4c, 0x4a, 0x0, 0x4c, + 0x54, 0x44, 0x0, 0x4c, 0x6a, 0x0, 0x4c, 0xb7, 0x0, 0x4c, 0x301, + 0x0, 0x4c, 0x30c, 0x0, 0x4c, 0x323, 0x0, 0x4c, 0x323, 0x304, + 0x0, 0x4c, 0x327, 0x0, 0x4c, 0x32d, 0x0, 0x4c, 0x331, 0x0, + 0x4d, 0x0, 0x4d, 0x42, 0x0, 0x4d, 0x43, 0x0, 0x4d, 0x44, 0x0, + 0x4d, 0x48, 0x7a, 0x0, 0x4d, 0x50, 0x61, 0x0, 0x4d, 0x56, 0x0, + 0x4d, 0x57, 0x0, 0x4d, 0x301, 0x0, 0x4d, 0x307, 0x0, 0x4d, + 0x323, 0x0, 0x4d, 0x3a9, 0x0, 0x4e, 0x0, 0x4e, 0x4a, 0x0, 0x4e, + 0x6a, 0x0, 0x4e, 0x6f, 0x0, 0x4e, 0x300, 0x0, 0x4e, 0x301, 0x0, + 0x4e, 0x303, 0x0, 0x4e, 0x307, 0x0, 0x4e, 0x30c, 0x0, 0x4e, + 0x323, 0x0, 0x4e, 0x327, 0x0, 0x4e, 0x32d, 0x0, 0x4e, 0x331, + 0x0, 0x4f, 0x0, 0x4f, 0x300, 0x0, 0x4f, 0x301, 0x0, 0x4f, + 0x302, 0x0, 0x4f, 0x302, 0x300, 0x0, 0x4f, 0x302, 0x301, 0x0, + 0x4f, 0x302, 0x303, 0x0, 0x4f, 0x302, 0x309, 0x0, 0x4f, 0x303, + 0x0, 0x4f, 0x303, 0x301, 0x0, 0x4f, 0x303, 0x304, 0x0, 0x4f, + 0x303, 0x308, 0x0, 0x4f, 0x304, 0x0, 0x4f, 0x304, 0x300, 0x0, + 0x4f, 0x304, 0x301, 0x0, 0x4f, 0x306, 0x0, 0x4f, 0x307, 0x0, + 0x4f, 0x307, 0x304, 0x0, 0x4f, 0x308, 0x0, 0x4f, 0x308, 0x304, + 0x0, 0x4f, 0x309, 0x0, 0x4f, 0x30b, 0x0, 0x4f, 0x30c, 0x0, + 0x4f, 0x30f, 0x0, 0x4f, 0x311, 0x0, 0x4f, 0x31b, 0x0, 0x4f, + 0x31b, 0x300, 0x0, 0x4f, 0x31b, 0x301, 0x0, 0x4f, 0x31b, 0x303, + 0x0, 0x4f, 0x31b, 0x309, 0x0, 0x4f, 0x31b, 0x323, 0x0, 0x4f, + 0x323, 0x0, 0x4f, 0x323, 0x302, 0x0, 0x4f, 0x328, 0x0, 0x4f, + 0x328, 0x304, 0x0, 0x50, 0x0, 0x50, 0x48, 0x0, 0x50, 0x50, + 0x4d, 0x0, 0x50, 0x50, 0x56, 0x0, 0x50, 0x52, 0x0, 0x50, 0x54, + 0x45, 0x0, 0x50, 0x61, 0x0, 0x50, 0x301, 0x0, 0x50, 0x307, 0x0, + 0x51, 0x0, 0x52, 0x0, 0x52, 0x73, 0x0, 0x52, 0x301, 0x0, 0x52, + 0x307, 0x0, 0x52, 0x30c, 0x0, 0x52, 0x30f, 0x0, 0x52, 0x311, + 0x0, 0x52, 0x323, 0x0, 0x52, 0x323, 0x304, 0x0, 0x52, 0x327, + 0x0, 0x52, 0x331, 0x0, 0x53, 0x0, 0x53, 0x44, 0x0, 0x53, 0x4d, + 0x0, 0x53, 0x53, 0x0, 0x53, 0x76, 0x0, 0x53, 0x301, 0x0, 0x53, + 0x301, 0x307, 0x0, 0x53, 0x302, 0x0, 0x53, 0x307, 0x0, 0x53, + 0x30c, 0x0, 0x53, 0x30c, 0x307, 0x0, 0x53, 0x323, 0x0, 0x53, + 0x323, 0x307, 0x0, 0x53, 0x326, 0x0, 0x53, 0x327, 0x0, 0x54, + 0x0, 0x54, 0x45, 0x4c, 0x0, 0x54, 0x48, 0x7a, 0x0, 0x54, 0x4d, + 0x0, 0x54, 0x307, 0x0, 0x54, 0x30c, 0x0, 0x54, 0x323, 0x0, + 0x54, 0x326, 0x0, 0x54, 0x327, 0x0, 0x54, 0x32d, 0x0, 0x54, + 0x331, 0x0, 0x55, 0x0, 0x55, 0x300, 0x0, 0x55, 0x301, 0x0, + 0x55, 0x302, 0x0, 0x55, 0x303, 0x0, 0x55, 0x303, 0x301, 0x0, + 0x55, 0x304, 0x0, 0x55, 0x304, 0x308, 0x0, 0x55, 0x306, 0x0, + 0x55, 0x308, 0x0, 0x55, 0x308, 0x300, 0x0, 0x55, 0x308, 0x301, + 0x0, 0x55, 0x308, 0x304, 0x0, 0x55, 0x308, 0x30c, 0x0, 0x55, + 0x309, 0x0, 0x55, 0x30a, 0x0, 0x55, 0x30b, 0x0, 0x55, 0x30c, + 0x0, 0x55, 0x30f, 0x0, 0x55, 0x311, 0x0, 0x55, 0x31b, 0x0, + 0x55, 0x31b, 0x300, 0x0, 0x55, 0x31b, 0x301, 0x0, 0x55, 0x31b, + 0x303, 0x0, 0x55, 0x31b, 0x309, 0x0, 0x55, 0x31b, 0x323, 0x0, + 0x55, 0x323, 0x0, 0x55, 0x324, 0x0, 0x55, 0x328, 0x0, 0x55, + 0x32d, 0x0, 0x55, 0x330, 0x0, 0x56, 0x0, 0x56, 0x49, 0x0, 0x56, + 0x49, 0x49, 0x0, 0x56, 0x49, 0x49, 0x49, 0x0, 0x56, 0x303, 0x0, + 0x56, 0x323, 0x0, 0x56, 0x2215, 0x6d, 0x0, 0x57, 0x0, 0x57, + 0x43, 0x0, 0x57, 0x5a, 0x0, 0x57, 0x62, 0x0, 0x57, 0x300, 0x0, + 0x57, 0x301, 0x0, 0x57, 0x302, 0x0, 0x57, 0x307, 0x0, 0x57, + 0x308, 0x0, 0x57, 0x323, 0x0, 0x58, 0x0, 0x58, 0x49, 0x0, 0x58, + 0x49, 0x49, 0x0, 0x58, 0x307, 0x0, 0x58, 0x308, 0x0, 0x59, 0x0, + 0x59, 0x300, 0x0, 0x59, 0x301, 0x0, 0x59, 0x302, 0x0, 0x59, + 0x303, 0x0, 0x59, 0x304, 0x0, 0x59, 0x307, 0x0, 0x59, 0x308, + 0x0, 0x59, 0x309, 0x0, 0x59, 0x323, 0x0, 0x5a, 0x0, 0x5a, + 0x301, 0x0, 0x5a, 0x302, 0x0, 0x5a, 0x307, 0x0, 0x5a, 0x30c, + 0x0, 0x5a, 0x323, 0x0, 0x5a, 0x331, 0x0, 0x5b, 0x0, 0x5c, 0x0, + 0x5d, 0x0, 0x5e, 0x0, 0x5f, 0x0, 0x60, 0x0, 0x61, 0x0, 0x61, + 0x2e, 0x6d, 0x2e, 0x0, 0x61, 0x2f, 0x63, 0x0, 0x61, 0x2f, 0x73, + 0x0, 0x61, 0x2be, 0x0, 0x61, 0x300, 0x0, 0x61, 0x301, 0x0, + 0x61, 0x302, 0x0, 0x61, 0x302, 0x300, 0x0, 0x61, 0x302, 0x301, + 0x0, 0x61, 0x302, 0x303, 0x0, 0x61, 0x302, 0x309, 0x0, 0x61, + 0x303, 0x0, 0x61, 0x304, 0x0, 0x61, 0x306, 0x0, 0x61, 0x306, + 0x300, 0x0, 0x61, 0x306, 0x301, 0x0, 0x61, 0x306, 0x303, 0x0, + 0x61, 0x306, 0x309, 0x0, 0x61, 0x307, 0x0, 0x61, 0x307, 0x304, + 0x0, 0x61, 0x308, 0x0, 0x61, 0x308, 0x304, 0x0, 0x61, 0x309, + 0x0, 0x61, 0x30a, 0x0, 0x61, 0x30a, 0x301, 0x0, 0x61, 0x30c, + 0x0, 0x61, 0x30f, 0x0, 0x61, 0x311, 0x0, 0x61, 0x323, 0x0, + 0x61, 0x323, 0x302, 0x0, 0x61, 0x323, 0x306, 0x0, 0x61, 0x325, + 0x0, 0x61, 0x328, 0x0, 0x62, 0x0, 0x62, 0x61, 0x72, 0x0, 0x62, + 0x307, 0x0, 0x62, 0x323, 0x0, 0x62, 0x331, 0x0, 0x63, 0x0, + 0x63, 0x2f, 0x6f, 0x0, 0x63, 0x2f, 0x75, 0x0, 0x63, 0x61, 0x6c, + 0x0, 0x63, 0x63, 0x0, 0x63, 0x64, 0x0, 0x63, 0x6d, 0x0, 0x63, + 0x6d, 0x32, 0x0, 0x63, 0x6d, 0x33, 0x0, 0x63, 0x301, 0x0, 0x63, + 0x302, 0x0, 0x63, 0x307, 0x0, 0x63, 0x30c, 0x0, 0x63, 0x327, + 0x0, 0x63, 0x327, 0x301, 0x0, 0x64, 0x0, 0x64, 0x42, 0x0, 0x64, + 0x61, 0x0, 0x64, 0x6c, 0x0, 0x64, 0x6d, 0x0, 0x64, 0x6d, 0x32, + 0x0, 0x64, 0x6d, 0x33, 0x0, 0x64, 0x7a, 0x0, 0x64, 0x7a, 0x30c, + 0x0, 0x64, 0x307, 0x0, 0x64, 0x30c, 0x0, 0x64, 0x323, 0x0, + 0x64, 0x327, 0x0, 0x64, 0x32d, 0x0, 0x64, 0x331, 0x0, 0x65, + 0x0, 0x65, 0x56, 0x0, 0x65, 0x72, 0x67, 0x0, 0x65, 0x300, 0x0, + 0x65, 0x301, 0x0, 0x65, 0x302, 0x0, 0x65, 0x302, 0x300, 0x0, + 0x65, 0x302, 0x301, 0x0, 0x65, 0x302, 0x303, 0x0, 0x65, 0x302, + 0x309, 0x0, 0x65, 0x303, 0x0, 0x65, 0x304, 0x0, 0x65, 0x304, + 0x300, 0x0, 0x65, 0x304, 0x301, 0x0, 0x65, 0x306, 0x0, 0x65, + 0x307, 0x0, 0x65, 0x308, 0x0, 0x65, 0x309, 0x0, 0x65, 0x30c, + 0x0, 0x65, 0x30f, 0x0, 0x65, 0x311, 0x0, 0x65, 0x323, 0x0, + 0x65, 0x323, 0x302, 0x0, 0x65, 0x327, 0x0, 0x65, 0x327, 0x306, + 0x0, 0x65, 0x328, 0x0, 0x65, 0x32d, 0x0, 0x65, 0x330, 0x0, + 0x66, 0x0, 0x66, 0x66, 0x0, 0x66, 0x66, 0x69, 0x0, 0x66, 0x66, + 0x6c, 0x0, 0x66, 0x69, 0x0, 0x66, 0x6c, 0x0, 0x66, 0x6d, 0x0, + 0x66, 0x307, 0x0, 0x67, 0x0, 0x67, 0x61, 0x6c, 0x0, 0x67, + 0x301, 0x0, 0x67, 0x302, 0x0, 0x67, 0x304, 0x0, 0x67, 0x306, + 0x0, 0x67, 0x307, 0x0, 0x67, 0x30c, 0x0, 0x67, 0x327, 0x0, + 0x68, 0x0, 0x68, 0x50, 0x61, 0x0, 0x68, 0x61, 0x0, 0x68, 0x302, + 0x0, 0x68, 0x307, 0x0, 0x68, 0x308, 0x0, 0x68, 0x30c, 0x0, + 0x68, 0x323, 0x0, 0x68, 0x327, 0x0, 0x68, 0x32e, 0x0, 0x68, + 0x331, 0x0, 0x69, 0x0, 0x69, 0x69, 0x0, 0x69, 0x69, 0x69, 0x0, + 0x69, 0x6a, 0x0, 0x69, 0x6e, 0x0, 0x69, 0x76, 0x0, 0x69, 0x78, + 0x0, 0x69, 0x300, 0x0, 0x69, 0x301, 0x0, 0x69, 0x302, 0x0, + 0x69, 0x303, 0x0, 0x69, 0x304, 0x0, 0x69, 0x306, 0x0, 0x69, + 0x308, 0x0, 0x69, 0x308, 0x301, 0x0, 0x69, 0x309, 0x0, 0x69, + 0x30c, 0x0, 0x69, 0x30f, 0x0, 0x69, 0x311, 0x0, 0x69, 0x323, + 0x0, 0x69, 0x328, 0x0, 0x69, 0x330, 0x0, 0x6a, 0x0, 0x6a, + 0x302, 0x0, 0x6a, 0x30c, 0x0, 0x6b, 0x0, 0x6b, 0x41, 0x0, 0x6b, + 0x48, 0x7a, 0x0, 0x6b, 0x50, 0x61, 0x0, 0x6b, 0x56, 0x0, 0x6b, + 0x57, 0x0, 0x6b, 0x63, 0x61, 0x6c, 0x0, 0x6b, 0x67, 0x0, 0x6b, + 0x6c, 0x0, 0x6b, 0x6d, 0x0, 0x6b, 0x6d, 0x32, 0x0, 0x6b, 0x6d, + 0x33, 0x0, 0x6b, 0x74, 0x0, 0x6b, 0x301, 0x0, 0x6b, 0x30c, 0x0, + 0x6b, 0x323, 0x0, 0x6b, 0x327, 0x0, 0x6b, 0x331, 0x0, 0x6b, + 0x3a9, 0x0, 0x6c, 0x0, 0x6c, 0x6a, 0x0, 0x6c, 0x6d, 0x0, 0x6c, + 0x6e, 0x0, 0x6c, 0x6f, 0x67, 0x0, 0x6c, 0x78, 0x0, 0x6c, 0xb7, + 0x0, 0x6c, 0x301, 0x0, 0x6c, 0x30c, 0x0, 0x6c, 0x323, 0x0, + 0x6c, 0x323, 0x304, 0x0, 0x6c, 0x327, 0x0, 0x6c, 0x32d, 0x0, + 0x6c, 0x331, 0x0, 0x6d, 0x0, 0x6d, 0x32, 0x0, 0x6d, 0x33, 0x0, + 0x6d, 0x41, 0x0, 0x6d, 0x56, 0x0, 0x6d, 0x57, 0x0, 0x6d, 0x62, + 0x0, 0x6d, 0x67, 0x0, 0x6d, 0x69, 0x6c, 0x0, 0x6d, 0x6c, 0x0, + 0x6d, 0x6d, 0x0, 0x6d, 0x6d, 0x32, 0x0, 0x6d, 0x6d, 0x33, 0x0, + 0x6d, 0x6f, 0x6c, 0x0, 0x6d, 0x73, 0x0, 0x6d, 0x301, 0x0, 0x6d, + 0x307, 0x0, 0x6d, 0x323, 0x0, 0x6d, 0x2215, 0x73, 0x0, 0x6d, + 0x2215, 0x73, 0x32, 0x0, 0x6e, 0x0, 0x6e, 0x41, 0x0, 0x6e, + 0x46, 0x0, 0x6e, 0x56, 0x0, 0x6e, 0x57, 0x0, 0x6e, 0x6a, 0x0, + 0x6e, 0x6d, 0x0, 0x6e, 0x73, 0x0, 0x6e, 0x300, 0x0, 0x6e, + 0x301, 0x0, 0x6e, 0x303, 0x0, 0x6e, 0x307, 0x0, 0x6e, 0x30c, + 0x0, 0x6e, 0x323, 0x0, 0x6e, 0x327, 0x0, 0x6e, 0x32d, 0x0, + 0x6e, 0x331, 0x0, 0x6f, 0x0, 0x6f, 0x56, 0x0, 0x6f, 0x300, 0x0, + 0x6f, 0x301, 0x0, 0x6f, 0x302, 0x0, 0x6f, 0x302, 0x300, 0x0, + 0x6f, 0x302, 0x301, 0x0, 0x6f, 0x302, 0x303, 0x0, 0x6f, 0x302, + 0x309, 0x0, 0x6f, 0x303, 0x0, 0x6f, 0x303, 0x301, 0x0, 0x6f, + 0x303, 0x304, 0x0, 0x6f, 0x303, 0x308, 0x0, 0x6f, 0x304, 0x0, + 0x6f, 0x304, 0x300, 0x0, 0x6f, 0x304, 0x301, 0x0, 0x6f, 0x306, + 0x0, 0x6f, 0x307, 0x0, 0x6f, 0x307, 0x304, 0x0, 0x6f, 0x308, + 0x0, 0x6f, 0x308, 0x304, 0x0, 0x6f, 0x309, 0x0, 0x6f, 0x30b, + 0x0, 0x6f, 0x30c, 0x0, 0x6f, 0x30f, 0x0, 0x6f, 0x311, 0x0, + 0x6f, 0x31b, 0x0, 0x6f, 0x31b, 0x300, 0x0, 0x6f, 0x31b, 0x301, + 0x0, 0x6f, 0x31b, 0x303, 0x0, 0x6f, 0x31b, 0x309, 0x0, 0x6f, + 0x31b, 0x323, 0x0, 0x6f, 0x323, 0x0, 0x6f, 0x323, 0x302, 0x0, + 0x6f, 0x328, 0x0, 0x6f, 0x328, 0x304, 0x0, 0x70, 0x0, 0x70, + 0x2e, 0x6d, 0x2e, 0x0, 0x70, 0x41, 0x0, 0x70, 0x46, 0x0, 0x70, + 0x56, 0x0, 0x70, 0x57, 0x0, 0x70, 0x63, 0x0, 0x70, 0x73, 0x0, + 0x70, 0x301, 0x0, 0x70, 0x307, 0x0, 0x71, 0x0, 0x72, 0x0, 0x72, + 0x61, 0x64, 0x0, 0x72, 0x61, 0x64, 0x2215, 0x73, 0x0, 0x72, + 0x61, 0x64, 0x2215, 0x73, 0x32, 0x0, 0x72, 0x301, 0x0, 0x72, + 0x307, 0x0, 0x72, 0x30c, 0x0, 0x72, 0x30f, 0x0, 0x72, 0x311, + 0x0, 0x72, 0x323, 0x0, 0x72, 0x323, 0x304, 0x0, 0x72, 0x327, + 0x0, 0x72, 0x331, 0x0, 0x73, 0x0, 0x73, 0x72, 0x0, 0x73, 0x74, + 0x0, 0x73, 0x301, 0x0, 0x73, 0x301, 0x307, 0x0, 0x73, 0x302, + 0x0, 0x73, 0x307, 0x0, 0x73, 0x30c, 0x0, 0x73, 0x30c, 0x307, + 0x0, 0x73, 0x323, 0x0, 0x73, 0x323, 0x307, 0x0, 0x73, 0x326, + 0x0, 0x73, 0x327, 0x0, 0x74, 0x0, 0x74, 0x307, 0x0, 0x74, + 0x308, 0x0, 0x74, 0x30c, 0x0, 0x74, 0x323, 0x0, 0x74, 0x326, + 0x0, 0x74, 0x327, 0x0, 0x74, 0x32d, 0x0, 0x74, 0x331, 0x0, + 0x75, 0x0, 0x75, 0x300, 0x0, 0x75, 0x301, 0x0, 0x75, 0x302, + 0x0, 0x75, 0x303, 0x0, 0x75, 0x303, 0x301, 0x0, 0x75, 0x304, + 0x0, 0x75, 0x304, 0x308, 0x0, 0x75, 0x306, 0x0, 0x75, 0x308, + 0x0, 0x75, 0x308, 0x300, 0x0, 0x75, 0x308, 0x301, 0x0, 0x75, + 0x308, 0x304, 0x0, 0x75, 0x308, 0x30c, 0x0, 0x75, 0x309, 0x0, + 0x75, 0x30a, 0x0, 0x75, 0x30b, 0x0, 0x75, 0x30c, 0x0, 0x75, + 0x30f, 0x0, 0x75, 0x311, 0x0, 0x75, 0x31b, 0x0, 0x75, 0x31b, + 0x300, 0x0, 0x75, 0x31b, 0x301, 0x0, 0x75, 0x31b, 0x303, 0x0, + 0x75, 0x31b, 0x309, 0x0, 0x75, 0x31b, 0x323, 0x0, 0x75, 0x323, + 0x0, 0x75, 0x324, 0x0, 0x75, 0x328, 0x0, 0x75, 0x32d, 0x0, + 0x75, 0x330, 0x0, 0x76, 0x0, 0x76, 0x69, 0x0, 0x76, 0x69, 0x69, + 0x0, 0x76, 0x69, 0x69, 0x69, 0x0, 0x76, 0x303, 0x0, 0x76, + 0x323, 0x0, 0x77, 0x0, 0x77, 0x300, 0x0, 0x77, 0x301, 0x0, + 0x77, 0x302, 0x0, 0x77, 0x307, 0x0, 0x77, 0x308, 0x0, 0x77, + 0x30a, 0x0, 0x77, 0x323, 0x0, 0x78, 0x0, 0x78, 0x69, 0x0, 0x78, + 0x69, 0x69, 0x0, 0x78, 0x307, 0x0, 0x78, 0x308, 0x0, 0x79, 0x0, + 0x79, 0x300, 0x0, 0x79, 0x301, 0x0, 0x79, 0x302, 0x0, 0x79, + 0x303, 0x0, 0x79, 0x304, 0x0, 0x79, 0x307, 0x0, 0x79, 0x308, + 0x0, 0x79, 0x309, 0x0, 0x79, 0x30a, 0x0, 0x79, 0x323, 0x0, + 0x7a, 0x0, 0x7a, 0x301, 0x0, 0x7a, 0x302, 0x0, 0x7a, 0x307, + 0x0, 0x7a, 0x30c, 0x0, 0x7a, 0x323, 0x0, 0x7a, 0x331, 0x0, + 0x7b, 0x0, 0x7c, 0x0, 0x7d, 0x0, 0x7e, 0x0, 0xa2, 0x0, 0xa3, + 0x0, 0xa5, 0x0, 0xa6, 0x0, 0xac, 0x0, 0xb0, 0x43, 0x0, 0xb0, + 0x46, 0x0, 0xb7, 0x0, 0xc6, 0x0, 0xc6, 0x301, 0x0, 0xc6, 0x304, + 0x0, 0xd8, 0x301, 0x0, 0xe6, 0x301, 0x0, 0xe6, 0x304, 0x0, + 0xf0, 0x0, 0xf8, 0x301, 0x0, 0x126, 0x0, 0x127, 0x0, 0x131, + 0x0, 0x14b, 0x0, 0x153, 0x0, 0x18e, 0x0, 0x190, 0x0, 0x1ab, + 0x0, 0x1b7, 0x30c, 0x0, 0x222, 0x0, 0x237, 0x0, 0x250, 0x0, + 0x251, 0x0, 0x252, 0x0, 0x254, 0x0, 0x255, 0x0, 0x259, 0x0, + 0x25b, 0x0, 0x25c, 0x0, 0x25f, 0x0, 0x261, 0x0, 0x263, 0x0, + 0x265, 0x0, 0x266, 0x0, 0x268, 0x0, 0x269, 0x0, 0x26a, 0x0, + 0x26d, 0x0, 0x26f, 0x0, 0x270, 0x0, 0x271, 0x0, 0x272, 0x0, + 0x273, 0x0, 0x274, 0x0, 0x275, 0x0, 0x278, 0x0, 0x279, 0x0, + 0x27b, 0x0, 0x281, 0x0, 0x282, 0x0, 0x283, 0x0, 0x289, 0x0, + 0x28a, 0x0, 0x28b, 0x0, 0x28c, 0x0, 0x290, 0x0, 0x291, 0x0, + 0x292, 0x0, 0x292, 0x30c, 0x0, 0x295, 0x0, 0x29d, 0x0, 0x29f, + 0x0, 0x2b9, 0x0, 0x2bc, 0x6e, 0x0, 0x300, 0x0, 0x301, 0x0, + 0x308, 0x301, 0x0, 0x313, 0x0, 0x391, 0x0, 0x391, 0x300, 0x0, + 0x391, 0x301, 0x0, 0x391, 0x304, 0x0, 0x391, 0x306, 0x0, 0x391, + 0x313, 0x0, 0x391, 0x313, 0x300, 0x0, 0x391, 0x313, 0x300, + 0x345, 0x0, 0x391, 0x313, 0x301, 0x0, 0x391, 0x313, 0x301, + 0x345, 0x0, 0x391, 0x313, 0x342, 0x0, 0x391, 0x313, 0x342, + 0x345, 0x0, 0x391, 0x313, 0x345, 0x0, 0x391, 0x314, 0x0, 0x391, + 0x314, 0x300, 0x0, 0x391, 0x314, 0x300, 0x345, 0x0, 0x391, + 0x314, 0x301, 0x0, 0x391, 0x314, 0x301, 0x345, 0x0, 0x391, + 0x314, 0x342, 0x0, 0x391, 0x314, 0x342, 0x345, 0x0, 0x391, + 0x314, 0x345, 0x0, 0x391, 0x345, 0x0, 0x392, 0x0, 0x393, 0x0, + 0x394, 0x0, 0x395, 0x0, 0x395, 0x300, 0x0, 0x395, 0x301, 0x0, + 0x395, 0x313, 0x0, 0x395, 0x313, 0x300, 0x0, 0x395, 0x313, + 0x301, 0x0, 0x395, 0x314, 0x0, 0x395, 0x314, 0x300, 0x0, 0x395, + 0x314, 0x301, 0x0, 0x396, 0x0, 0x397, 0x0, 0x397, 0x300, 0x0, + 0x397, 0x301, 0x0, 0x397, 0x313, 0x0, 0x397, 0x313, 0x300, 0x0, + 0x397, 0x313, 0x300, 0x345, 0x0, 0x397, 0x313, 0x301, 0x0, + 0x397, 0x313, 0x301, 0x345, 0x0, 0x397, 0x313, 0x342, 0x0, + 0x397, 0x313, 0x342, 0x345, 0x0, 0x397, 0x313, 0x345, 0x0, + 0x397, 0x314, 0x0, 0x397, 0x314, 0x300, 0x0, 0x397, 0x314, + 0x300, 0x345, 0x0, 0x397, 0x314, 0x301, 0x0, 0x397, 0x314, + 0x301, 0x345, 0x0, 0x397, 0x314, 0x342, 0x0, 0x397, 0x314, + 0x342, 0x345, 0x0, 0x397, 0x314, 0x345, 0x0, 0x397, 0x345, 0x0, + 0x398, 0x0, 0x399, 0x0, 0x399, 0x300, 0x0, 0x399, 0x301, 0x0, + 0x399, 0x304, 0x0, 0x399, 0x306, 0x0, 0x399, 0x308, 0x0, 0x399, + 0x313, 0x0, 0x399, 0x313, 0x300, 0x0, 0x399, 0x313, 0x301, 0x0, + 0x399, 0x313, 0x342, 0x0, 0x399, 0x314, 0x0, 0x399, 0x314, + 0x300, 0x0, 0x399, 0x314, 0x301, 0x0, 0x399, 0x314, 0x342, 0x0, + 0x39a, 0x0, 0x39b, 0x0, 0x39c, 0x0, 0x39d, 0x0, 0x39e, 0x0, + 0x39f, 0x0, 0x39f, 0x300, 0x0, 0x39f, 0x301, 0x0, 0x39f, 0x313, + 0x0, 0x39f, 0x313, 0x300, 0x0, 0x39f, 0x313, 0x301, 0x0, 0x39f, + 0x314, 0x0, 0x39f, 0x314, 0x300, 0x0, 0x39f, 0x314, 0x301, 0x0, + 0x3a0, 0x0, 0x3a1, 0x0, 0x3a1, 0x314, 0x0, 0x3a3, 0x0, 0x3a4, + 0x0, 0x3a5, 0x0, 0x3a5, 0x300, 0x0, 0x3a5, 0x301, 0x0, 0x3a5, + 0x304, 0x0, 0x3a5, 0x306, 0x0, 0x3a5, 0x308, 0x0, 0x3a5, 0x314, + 0x0, 0x3a5, 0x314, 0x300, 0x0, 0x3a5, 0x314, 0x301, 0x0, 0x3a5, + 0x314, 0x342, 0x0, 0x3a6, 0x0, 0x3a7, 0x0, 0x3a8, 0x0, 0x3a9, + 0x0, 0x3a9, 0x300, 0x0, 0x3a9, 0x301, 0x0, 0x3a9, 0x313, 0x0, + 0x3a9, 0x313, 0x300, 0x0, 0x3a9, 0x313, 0x300, 0x345, 0x0, + 0x3a9, 0x313, 0x301, 0x0, 0x3a9, 0x313, 0x301, 0x345, 0x0, + 0x3a9, 0x313, 0x342, 0x0, 0x3a9, 0x313, 0x342, 0x345, 0x0, + 0x3a9, 0x313, 0x345, 0x0, 0x3a9, 0x314, 0x0, 0x3a9, 0x314, + 0x300, 0x0, 0x3a9, 0x314, 0x300, 0x345, 0x0, 0x3a9, 0x314, + 0x301, 0x0, 0x3a9, 0x314, 0x301, 0x345, 0x0, 0x3a9, 0x314, + 0x342, 0x0, 0x3a9, 0x314, 0x342, 0x345, 0x0, 0x3a9, 0x314, + 0x345, 0x0, 0x3a9, 0x345, 0x0, 0x3b1, 0x0, 0x3b1, 0x300, 0x0, + 0x3b1, 0x300, 0x345, 0x0, 0x3b1, 0x301, 0x0, 0x3b1, 0x301, + 0x345, 0x0, 0x3b1, 0x304, 0x0, 0x3b1, 0x306, 0x0, 0x3b1, 0x313, + 0x0, 0x3b1, 0x313, 0x300, 0x0, 0x3b1, 0x313, 0x300, 0x345, 0x0, + 0x3b1, 0x313, 0x301, 0x0, 0x3b1, 0x313, 0x301, 0x345, 0x0, + 0x3b1, 0x313, 0x342, 0x0, 0x3b1, 0x313, 0x342, 0x345, 0x0, + 0x3b1, 0x313, 0x345, 0x0, 0x3b1, 0x314, 0x0, 0x3b1, 0x314, + 0x300, 0x0, 0x3b1, 0x314, 0x300, 0x345, 0x0, 0x3b1, 0x314, + 0x301, 0x0, 0x3b1, 0x314, 0x301, 0x345, 0x0, 0x3b1, 0x314, + 0x342, 0x0, 0x3b1, 0x314, 0x342, 0x345, 0x0, 0x3b1, 0x314, + 0x345, 0x0, 0x3b1, 0x342, 0x0, 0x3b1, 0x342, 0x345, 0x0, 0x3b1, + 0x345, 0x0, 0x3b2, 0x0, 0x3b3, 0x0, 0x3b4, 0x0, 0x3b5, 0x0, + 0x3b5, 0x300, 0x0, 0x3b5, 0x301, 0x0, 0x3b5, 0x313, 0x0, 0x3b5, + 0x313, 0x300, 0x0, 0x3b5, 0x313, 0x301, 0x0, 0x3b5, 0x314, 0x0, + 0x3b5, 0x314, 0x300, 0x0, 0x3b5, 0x314, 0x301, 0x0, 0x3b6, 0x0, + 0x3b7, 0x0, 0x3b7, 0x300, 0x0, 0x3b7, 0x300, 0x345, 0x0, 0x3b7, + 0x301, 0x0, 0x3b7, 0x301, 0x345, 0x0, 0x3b7, 0x313, 0x0, 0x3b7, + 0x313, 0x300, 0x0, 0x3b7, 0x313, 0x300, 0x345, 0x0, 0x3b7, + 0x313, 0x301, 0x0, 0x3b7, 0x313, 0x301, 0x345, 0x0, 0x3b7, + 0x313, 0x342, 0x0, 0x3b7, 0x313, 0x342, 0x345, 0x0, 0x3b7, + 0x313, 0x345, 0x0, 0x3b7, 0x314, 0x0, 0x3b7, 0x314, 0x300, 0x0, + 0x3b7, 0x314, 0x300, 0x345, 0x0, 0x3b7, 0x314, 0x301, 0x0, + 0x3b7, 0x314, 0x301, 0x345, 0x0, 0x3b7, 0x314, 0x342, 0x0, + 0x3b7, 0x314, 0x342, 0x345, 0x0, 0x3b7, 0x314, 0x345, 0x0, + 0x3b7, 0x342, 0x0, 0x3b7, 0x342, 0x345, 0x0, 0x3b7, 0x345, 0x0, + 0x3b8, 0x0, 0x3b9, 0x0, 0x3b9, 0x300, 0x0, 0x3b9, 0x301, 0x0, + 0x3b9, 0x304, 0x0, 0x3b9, 0x306, 0x0, 0x3b9, 0x308, 0x0, 0x3b9, + 0x308, 0x300, 0x0, 0x3b9, 0x308, 0x301, 0x0, 0x3b9, 0x308, + 0x342, 0x0, 0x3b9, 0x313, 0x0, 0x3b9, 0x313, 0x300, 0x0, 0x3b9, + 0x313, 0x301, 0x0, 0x3b9, 0x313, 0x342, 0x0, 0x3b9, 0x314, 0x0, + 0x3b9, 0x314, 0x300, 0x0, 0x3b9, 0x314, 0x301, 0x0, 0x3b9, + 0x314, 0x342, 0x0, 0x3b9, 0x342, 0x0, 0x3ba, 0x0, 0x3bb, 0x0, + 0x3bc, 0x0, 0x3bc, 0x41, 0x0, 0x3bc, 0x46, 0x0, 0x3bc, 0x56, + 0x0, 0x3bc, 0x57, 0x0, 0x3bc, 0x67, 0x0, 0x3bc, 0x6c, 0x0, + 0x3bc, 0x6d, 0x0, 0x3bc, 0x73, 0x0, 0x3bd, 0x0, 0x3be, 0x0, + 0x3bf, 0x0, 0x3bf, 0x300, 0x0, 0x3bf, 0x301, 0x0, 0x3bf, 0x313, + 0x0, 0x3bf, 0x313, 0x300, 0x0, 0x3bf, 0x313, 0x301, 0x0, 0x3bf, + 0x314, 0x0, 0x3bf, 0x314, 0x300, 0x0, 0x3bf, 0x314, 0x301, 0x0, + 0x3c0, 0x0, 0x3c1, 0x0, 0x3c1, 0x313, 0x0, 0x3c1, 0x314, 0x0, + 0x3c2, 0x0, 0x3c3, 0x0, 0x3c4, 0x0, 0x3c5, 0x0, 0x3c5, 0x300, + 0x0, 0x3c5, 0x301, 0x0, 0x3c5, 0x304, 0x0, 0x3c5, 0x306, 0x0, + 0x3c5, 0x308, 0x0, 0x3c5, 0x308, 0x300, 0x0, 0x3c5, 0x308, + 0x301, 0x0, 0x3c5, 0x308, 0x342, 0x0, 0x3c5, 0x313, 0x0, 0x3c5, + 0x313, 0x300, 0x0, 0x3c5, 0x313, 0x301, 0x0, 0x3c5, 0x313, + 0x342, 0x0, 0x3c5, 0x314, 0x0, 0x3c5, 0x314, 0x300, 0x0, 0x3c5, + 0x314, 0x301, 0x0, 0x3c5, 0x314, 0x342, 0x0, 0x3c5, 0x342, 0x0, + 0x3c6, 0x0, 0x3c7, 0x0, 0x3c8, 0x0, 0x3c9, 0x0, 0x3c9, 0x300, + 0x0, 0x3c9, 0x300, 0x345, 0x0, 0x3c9, 0x301, 0x0, 0x3c9, 0x301, + 0x345, 0x0, 0x3c9, 0x313, 0x0, 0x3c9, 0x313, 0x300, 0x0, 0x3c9, + 0x313, 0x300, 0x345, 0x0, 0x3c9, 0x313, 0x301, 0x0, 0x3c9, + 0x313, 0x301, 0x345, 0x0, 0x3c9, 0x313, 0x342, 0x0, 0x3c9, + 0x313, 0x342, 0x345, 0x0, 0x3c9, 0x313, 0x345, 0x0, 0x3c9, + 0x314, 0x0, 0x3c9, 0x314, 0x300, 0x0, 0x3c9, 0x314, 0x300, + 0x345, 0x0, 0x3c9, 0x314, 0x301, 0x0, 0x3c9, 0x314, 0x301, + 0x345, 0x0, 0x3c9, 0x314, 0x342, 0x0, 0x3c9, 0x314, 0x342, + 0x345, 0x0, 0x3c9, 0x314, 0x345, 0x0, 0x3c9, 0x342, 0x0, 0x3c9, + 0x342, 0x345, 0x0, 0x3c9, 0x345, 0x0, 0x3dc, 0x0, 0x3dd, 0x0, + 0x406, 0x308, 0x0, 0x410, 0x306, 0x0, 0x410, 0x308, 0x0, 0x413, + 0x301, 0x0, 0x415, 0x300, 0x0, 0x415, 0x306, 0x0, 0x415, 0x308, + 0x0, 0x416, 0x306, 0x0, 0x416, 0x308, 0x0, 0x417, 0x308, 0x0, + 0x418, 0x300, 0x0, 0x418, 0x304, 0x0, 0x418, 0x306, 0x0, 0x418, + 0x308, 0x0, 0x41a, 0x301, 0x0, 0x41e, 0x308, 0x0, 0x423, 0x304, + 0x0, 0x423, 0x306, 0x0, 0x423, 0x308, 0x0, 0x423, 0x30b, 0x0, + 0x427, 0x308, 0x0, 0x42b, 0x308, 0x0, 0x42d, 0x308, 0x0, 0x430, + 0x306, 0x0, 0x430, 0x308, 0x0, 0x433, 0x301, 0x0, 0x435, 0x300, + 0x0, 0x435, 0x306, 0x0, 0x435, 0x308, 0x0, 0x436, 0x306, 0x0, + 0x436, 0x308, 0x0, 0x437, 0x308, 0x0, 0x438, 0x300, 0x0, 0x438, + 0x304, 0x0, 0x438, 0x306, 0x0, 0x438, 0x308, 0x0, 0x43a, 0x301, + 0x0, 0x43d, 0x0, 0x43e, 0x308, 0x0, 0x443, 0x304, 0x0, 0x443, + 0x306, 0x0, 0x443, 0x308, 0x0, 0x443, 0x30b, 0x0, 0x447, 0x308, + 0x0, 0x44b, 0x308, 0x0, 0x44d, 0x308, 0x0, 0x456, 0x308, 0x0, + 0x474, 0x30f, 0x0, 0x475, 0x30f, 0x0, 0x4d8, 0x308, 0x0, 0x4d9, + 0x308, 0x0, 0x4e8, 0x308, 0x0, 0x4e9, 0x308, 0x0, 0x565, 0x582, + 0x0, 0x574, 0x565, 0x0, 0x574, 0x56b, 0x0, 0x574, 0x56d, 0x0, + 0x574, 0x576, 0x0, 0x57e, 0x576, 0x0, 0x5d0, 0x0, 0x5d0, 0x5b7, + 0x0, 0x5d0, 0x5b8, 0x0, 0x5d0, 0x5bc, 0x0, 0x5d0, 0x5dc, 0x0, + 0x5d1, 0x0, 0x5d1, 0x5bc, 0x0, 0x5d1, 0x5bf, 0x0, 0x5d2, 0x0, + 0x5d2, 0x5bc, 0x0, 0x5d3, 0x0, 0x5d3, 0x5bc, 0x0, 0x5d4, 0x0, + 0x5d4, 0x5bc, 0x0, 0x5d5, 0x5b9, 0x0, 0x5d5, 0x5bc, 0x0, 0x5d6, + 0x5bc, 0x0, 0x5d8, 0x5bc, 0x0, 0x5d9, 0x5b4, 0x0, 0x5d9, 0x5bc, + 0x0, 0x5da, 0x5bc, 0x0, 0x5db, 0x0, 0x5db, 0x5bc, 0x0, 0x5db, + 0x5bf, 0x0, 0x5dc, 0x0, 0x5dc, 0x5bc, 0x0, 0x5dd, 0x0, 0x5de, + 0x5bc, 0x0, 0x5e0, 0x5bc, 0x0, 0x5e1, 0x5bc, 0x0, 0x5e2, 0x0, + 0x5e3, 0x5bc, 0x0, 0x5e4, 0x5bc, 0x0, 0x5e4, 0x5bf, 0x0, 0x5e6, + 0x5bc, 0x0, 0x5e7, 0x5bc, 0x0, 0x5e8, 0x0, 0x5e8, 0x5bc, 0x0, + 0x5e9, 0x5bc, 0x0, 0x5e9, 0x5bc, 0x5c1, 0x0, 0x5e9, 0x5bc, + 0x5c2, 0x0, 0x5e9, 0x5c1, 0x0, 0x5e9, 0x5c2, 0x0, 0x5ea, 0x0, + 0x5ea, 0x5bc, 0x0, 0x5f2, 0x5b7, 0x0, 0x621, 0x0, 0x627, 0x0, + 0x627, 0x643, 0x628, 0x631, 0x0, 0x627, 0x644, 0x644, 0x647, + 0x0, 0x627, 0x64b, 0x0, 0x627, 0x653, 0x0, 0x627, 0x654, 0x0, + 0x627, 0x655, 0x0, 0x627, 0x674, 0x0, 0x628, 0x0, 0x628, 0x62c, + 0x0, 0x628, 0x62d, 0x0, 0x628, 0x62d, 0x64a, 0x0, 0x628, 0x62e, + 0x0, 0x628, 0x62e, 0x64a, 0x0, 0x628, 0x631, 0x0, 0x628, 0x632, + 0x0, 0x628, 0x645, 0x0, 0x628, 0x646, 0x0, 0x628, 0x647, 0x0, + 0x628, 0x649, 0x0, 0x628, 0x64a, 0x0, 0x629, 0x0, 0x62a, 0x0, + 0x62a, 0x62c, 0x0, 0x62a, 0x62c, 0x645, 0x0, 0x62a, 0x62c, + 0x649, 0x0, 0x62a, 0x62c, 0x64a, 0x0, 0x62a, 0x62d, 0x0, 0x62a, + 0x62d, 0x62c, 0x0, 0x62a, 0x62d, 0x645, 0x0, 0x62a, 0x62e, 0x0, + 0x62a, 0x62e, 0x645, 0x0, 0x62a, 0x62e, 0x649, 0x0, 0x62a, + 0x62e, 0x64a, 0x0, 0x62a, 0x631, 0x0, 0x62a, 0x632, 0x0, 0x62a, + 0x645, 0x0, 0x62a, 0x645, 0x62c, 0x0, 0x62a, 0x645, 0x62d, 0x0, + 0x62a, 0x645, 0x62e, 0x0, 0x62a, 0x645, 0x649, 0x0, 0x62a, + 0x645, 0x64a, 0x0, 0x62a, 0x646, 0x0, 0x62a, 0x647, 0x0, 0x62a, + 0x649, 0x0, 0x62a, 0x64a, 0x0, 0x62b, 0x0, 0x62b, 0x62c, 0x0, + 0x62b, 0x631, 0x0, 0x62b, 0x632, 0x0, 0x62b, 0x645, 0x0, 0x62b, + 0x646, 0x0, 0x62b, 0x647, 0x0, 0x62b, 0x649, 0x0, 0x62b, 0x64a, + 0x0, 0x62c, 0x0, 0x62c, 0x62d, 0x0, 0x62c, 0x62d, 0x649, 0x0, + 0x62c, 0x62d, 0x64a, 0x0, 0x62c, 0x644, 0x20, 0x62c, 0x644, + 0x627, 0x644, 0x647, 0x0, 0x62c, 0x645, 0x0, 0x62c, 0x645, + 0x62d, 0x0, 0x62c, 0x645, 0x649, 0x0, 0x62c, 0x645, 0x64a, 0x0, + 0x62c, 0x649, 0x0, 0x62c, 0x64a, 0x0, 0x62d, 0x0, 0x62d, 0x62c, + 0x0, 0x62d, 0x62c, 0x64a, 0x0, 0x62d, 0x645, 0x0, 0x62d, 0x645, + 0x649, 0x0, 0x62d, 0x645, 0x64a, 0x0, 0x62d, 0x649, 0x0, 0x62d, + 0x64a, 0x0, 0x62e, 0x0, 0x62e, 0x62c, 0x0, 0x62e, 0x62d, 0x0, + 0x62e, 0x645, 0x0, 0x62e, 0x649, 0x0, 0x62e, 0x64a, 0x0, 0x62f, + 0x0, 0x630, 0x0, 0x630, 0x670, 0x0, 0x631, 0x0, 0x631, 0x633, + 0x648, 0x644, 0x0, 0x631, 0x670, 0x0, 0x631, 0x6cc, 0x627, + 0x644, 0x0, 0x632, 0x0, 0x633, 0x0, 0x633, 0x62c, 0x0, 0x633, + 0x62c, 0x62d, 0x0, 0x633, 0x62c, 0x649, 0x0, 0x633, 0x62d, 0x0, + 0x633, 0x62d, 0x62c, 0x0, 0x633, 0x62e, 0x0, 0x633, 0x62e, + 0x649, 0x0, 0x633, 0x62e, 0x64a, 0x0, 0x633, 0x631, 0x0, 0x633, + 0x645, 0x0, 0x633, 0x645, 0x62c, 0x0, 0x633, 0x645, 0x62d, 0x0, + 0x633, 0x645, 0x645, 0x0, 0x633, 0x647, 0x0, 0x633, 0x649, 0x0, + 0x633, 0x64a, 0x0, 0x634, 0x0, 0x634, 0x62c, 0x0, 0x634, 0x62c, + 0x64a, 0x0, 0x634, 0x62d, 0x0, 0x634, 0x62d, 0x645, 0x0, 0x634, + 0x62d, 0x64a, 0x0, 0x634, 0x62e, 0x0, 0x634, 0x631, 0x0, 0x634, + 0x645, 0x0, 0x634, 0x645, 0x62e, 0x0, 0x634, 0x645, 0x645, 0x0, + 0x634, 0x647, 0x0, 0x634, 0x649, 0x0, 0x634, 0x64a, 0x0, 0x635, + 0x0, 0x635, 0x62d, 0x0, 0x635, 0x62d, 0x62d, 0x0, 0x635, 0x62d, + 0x64a, 0x0, 0x635, 0x62e, 0x0, 0x635, 0x631, 0x0, 0x635, 0x644, + 0x639, 0x645, 0x0, 0x635, 0x644, 0x649, 0x0, 0x635, 0x644, + 0x649, 0x20, 0x627, 0x644, 0x644, 0x647, 0x20, 0x639, 0x644, + 0x64a, 0x647, 0x20, 0x648, 0x633, 0x644, 0x645, 0x0, 0x635, + 0x644, 0x6d2, 0x0, 0x635, 0x645, 0x0, 0x635, 0x645, 0x645, 0x0, + 0x635, 0x649, 0x0, 0x635, 0x64a, 0x0, 0x636, 0x0, 0x636, 0x62c, + 0x0, 0x636, 0x62d, 0x0, 0x636, 0x62d, 0x649, 0x0, 0x636, 0x62d, + 0x64a, 0x0, 0x636, 0x62e, 0x0, 0x636, 0x62e, 0x645, 0x0, 0x636, + 0x631, 0x0, 0x636, 0x645, 0x0, 0x636, 0x649, 0x0, 0x636, 0x64a, + 0x0, 0x637, 0x0, 0x637, 0x62d, 0x0, 0x637, 0x645, 0x0, 0x637, + 0x645, 0x62d, 0x0, 0x637, 0x645, 0x645, 0x0, 0x637, 0x645, + 0x64a, 0x0, 0x637, 0x649, 0x0, 0x637, 0x64a, 0x0, 0x638, 0x0, + 0x638, 0x645, 0x0, 0x639, 0x0, 0x639, 0x62c, 0x0, 0x639, 0x62c, + 0x645, 0x0, 0x639, 0x644, 0x64a, 0x647, 0x0, 0x639, 0x645, 0x0, + 0x639, 0x645, 0x645, 0x0, 0x639, 0x645, 0x649, 0x0, 0x639, + 0x645, 0x64a, 0x0, 0x639, 0x649, 0x0, 0x639, 0x64a, 0x0, 0x63a, + 0x0, 0x63a, 0x62c, 0x0, 0x63a, 0x645, 0x0, 0x63a, 0x645, 0x645, + 0x0, 0x63a, 0x645, 0x649, 0x0, 0x63a, 0x645, 0x64a, 0x0, 0x63a, + 0x649, 0x0, 0x63a, 0x64a, 0x0, 0x640, 0x64b, 0x0, 0x640, 0x64e, + 0x0, 0x640, 0x64e, 0x651, 0x0, 0x640, 0x64f, 0x0, 0x640, 0x64f, + 0x651, 0x0, 0x640, 0x650, 0x0, 0x640, 0x650, 0x651, 0x0, 0x640, + 0x651, 0x0, 0x640, 0x652, 0x0, 0x641, 0x0, 0x641, 0x62c, 0x0, + 0x641, 0x62d, 0x0, 0x641, 0x62e, 0x0, 0x641, 0x62e, 0x645, 0x0, + 0x641, 0x645, 0x0, 0x641, 0x645, 0x64a, 0x0, 0x641, 0x649, 0x0, + 0x641, 0x64a, 0x0, 0x642, 0x0, 0x642, 0x62d, 0x0, 0x642, 0x644, + 0x6d2, 0x0, 0x642, 0x645, 0x0, 0x642, 0x645, 0x62d, 0x0, 0x642, + 0x645, 0x645, 0x0, 0x642, 0x645, 0x64a, 0x0, 0x642, 0x649, 0x0, + 0x642, 0x64a, 0x0, 0x643, 0x0, 0x643, 0x627, 0x0, 0x643, 0x62c, + 0x0, 0x643, 0x62d, 0x0, 0x643, 0x62e, 0x0, 0x643, 0x644, 0x0, + 0x643, 0x645, 0x0, 0x643, 0x645, 0x645, 0x0, 0x643, 0x645, + 0x64a, 0x0, 0x643, 0x649, 0x0, 0x643, 0x64a, 0x0, 0x644, 0x0, + 0x644, 0x627, 0x0, 0x644, 0x627, 0x653, 0x0, 0x644, 0x627, + 0x654, 0x0, 0x644, 0x627, 0x655, 0x0, 0x644, 0x62c, 0x0, 0x644, + 0x62c, 0x62c, 0x0, 0x644, 0x62c, 0x645, 0x0, 0x644, 0x62c, + 0x64a, 0x0, 0x644, 0x62d, 0x0, 0x644, 0x62d, 0x645, 0x0, 0x644, + 0x62d, 0x649, 0x0, 0x644, 0x62d, 0x64a, 0x0, 0x644, 0x62e, 0x0, + 0x644, 0x62e, 0x645, 0x0, 0x644, 0x645, 0x0, 0x644, 0x645, + 0x62d, 0x0, 0x644, 0x645, 0x64a, 0x0, 0x644, 0x647, 0x0, 0x644, + 0x649, 0x0, 0x644, 0x64a, 0x0, 0x645, 0x0, 0x645, 0x627, 0x0, + 0x645, 0x62c, 0x0, 0x645, 0x62c, 0x62d, 0x0, 0x645, 0x62c, + 0x62e, 0x0, 0x645, 0x62c, 0x645, 0x0, 0x645, 0x62c, 0x64a, 0x0, + 0x645, 0x62d, 0x0, 0x645, 0x62d, 0x62c, 0x0, 0x645, 0x62d, + 0x645, 0x0, 0x645, 0x62d, 0x645, 0x62f, 0x0, 0x645, 0x62d, + 0x64a, 0x0, 0x645, 0x62e, 0x0, 0x645, 0x62e, 0x62c, 0x0, 0x645, + 0x62e, 0x645, 0x0, 0x645, 0x62e, 0x64a, 0x0, 0x645, 0x645, 0x0, + 0x645, 0x645, 0x64a, 0x0, 0x645, 0x649, 0x0, 0x645, 0x64a, 0x0, + 0x646, 0x0, 0x646, 0x62c, 0x0, 0x646, 0x62c, 0x62d, 0x0, 0x646, + 0x62c, 0x645, 0x0, 0x646, 0x62c, 0x649, 0x0, 0x646, 0x62c, + 0x64a, 0x0, 0x646, 0x62d, 0x0, 0x646, 0x62d, 0x645, 0x0, 0x646, + 0x62d, 0x649, 0x0, 0x646, 0x62d, 0x64a, 0x0, 0x646, 0x62e, 0x0, + 0x646, 0x631, 0x0, 0x646, 0x632, 0x0, 0x646, 0x645, 0x0, 0x646, + 0x645, 0x649, 0x0, 0x646, 0x645, 0x64a, 0x0, 0x646, 0x646, 0x0, + 0x646, 0x647, 0x0, 0x646, 0x649, 0x0, 0x646, 0x64a, 0x0, 0x647, + 0x0, 0x647, 0x62c, 0x0, 0x647, 0x645, 0x0, 0x647, 0x645, 0x62c, + 0x0, 0x647, 0x645, 0x645, 0x0, 0x647, 0x649, 0x0, 0x647, 0x64a, + 0x0, 0x647, 0x670, 0x0, 0x648, 0x0, 0x648, 0x633, 0x644, 0x645, + 0x0, 0x648, 0x654, 0x0, 0x648, 0x674, 0x0, 0x649, 0x0, 0x649, + 0x670, 0x0, 0x64a, 0x0, 0x64a, 0x62c, 0x0, 0x64a, 0x62c, 0x64a, + 0x0, 0x64a, 0x62d, 0x0, 0x64a, 0x62d, 0x64a, 0x0, 0x64a, 0x62e, + 0x0, 0x64a, 0x631, 0x0, 0x64a, 0x632, 0x0, 0x64a, 0x645, 0x0, + 0x64a, 0x645, 0x645, 0x0, 0x64a, 0x645, 0x64a, 0x0, 0x64a, + 0x646, 0x0, 0x64a, 0x647, 0x0, 0x64a, 0x649, 0x0, 0x64a, 0x64a, + 0x0, 0x64a, 0x654, 0x0, 0x64a, 0x654, 0x627, 0x0, 0x64a, 0x654, + 0x62c, 0x0, 0x64a, 0x654, 0x62d, 0x0, 0x64a, 0x654, 0x62e, 0x0, + 0x64a, 0x654, 0x631, 0x0, 0x64a, 0x654, 0x632, 0x0, 0x64a, + 0x654, 0x645, 0x0, 0x64a, 0x654, 0x646, 0x0, 0x64a, 0x654, + 0x647, 0x0, 0x64a, 0x654, 0x648, 0x0, 0x64a, 0x654, 0x649, 0x0, + 0x64a, 0x654, 0x64a, 0x0, 0x64a, 0x654, 0x6c6, 0x0, 0x64a, + 0x654, 0x6c7, 0x0, 0x64a, 0x654, 0x6c8, 0x0, 0x64a, 0x654, + 0x6d0, 0x0, 0x64a, 0x654, 0x6d5, 0x0, 0x64a, 0x674, 0x0, 0x66e, + 0x0, 0x66f, 0x0, 0x671, 0x0, 0x679, 0x0, 0x67a, 0x0, 0x67b, + 0x0, 0x67e, 0x0, 0x67f, 0x0, 0x680, 0x0, 0x683, 0x0, 0x684, + 0x0, 0x686, 0x0, 0x687, 0x0, 0x688, 0x0, 0x68c, 0x0, 0x68d, + 0x0, 0x68e, 0x0, 0x691, 0x0, 0x698, 0x0, 0x6a1, 0x0, 0x6a4, + 0x0, 0x6a6, 0x0, 0x6a9, 0x0, 0x6ad, 0x0, 0x6af, 0x0, 0x6b1, + 0x0, 0x6b3, 0x0, 0x6ba, 0x0, 0x6bb, 0x0, 0x6be, 0x0, 0x6c1, + 0x0, 0x6c1, 0x654, 0x0, 0x6c5, 0x0, 0x6c6, 0x0, 0x6c7, 0x0, + 0x6c7, 0x674, 0x0, 0x6c8, 0x0, 0x6c9, 0x0, 0x6cb, 0x0, 0x6cc, + 0x0, 0x6d0, 0x0, 0x6d2, 0x0, 0x6d2, 0x654, 0x0, 0x6d5, 0x654, + 0x0, 0x915, 0x93c, 0x0, 0x916, 0x93c, 0x0, 0x917, 0x93c, 0x0, + 0x91c, 0x93c, 0x0, 0x921, 0x93c, 0x0, 0x922, 0x93c, 0x0, 0x928, + 0x93c, 0x0, 0x92b, 0x93c, 0x0, 0x92f, 0x93c, 0x0, 0x930, 0x93c, + 0x0, 0x933, 0x93c, 0x0, 0x9a1, 0x9bc, 0x0, 0x9a2, 0x9bc, 0x0, + 0x9af, 0x9bc, 0x0, 0x9c7, 0x9be, 0x0, 0x9c7, 0x9d7, 0x0, 0xa16, + 0xa3c, 0x0, 0xa17, 0xa3c, 0x0, 0xa1c, 0xa3c, 0x0, 0xa2b, 0xa3c, + 0x0, 0xa32, 0xa3c, 0x0, 0xa38, 0xa3c, 0x0, 0xb21, 0xb3c, 0x0, + 0xb22, 0xb3c, 0x0, 0xb47, 0xb3e, 0x0, 0xb47, 0xb56, 0x0, 0xb47, + 0xb57, 0x0, 0xb92, 0xbd7, 0x0, 0xbc6, 0xbbe, 0x0, 0xbc6, 0xbd7, + 0x0, 0xbc7, 0xbbe, 0x0, 0xc46, 0xc56, 0x0, 0xcbf, 0xcd5, 0x0, + 0xcc6, 0xcc2, 0x0, 0xcc6, 0xcc2, 0xcd5, 0x0, 0xcc6, 0xcd5, 0x0, + 0xcc6, 0xcd6, 0x0, 0xd46, 0xd3e, 0x0, 0xd46, 0xd57, 0x0, 0xd47, + 0xd3e, 0x0, 0xdd9, 0xdca, 0x0, 0xdd9, 0xdcf, 0x0, 0xdd9, 0xdcf, + 0xdca, 0x0, 0xdd9, 0xddf, 0x0, 0xe4d, 0xe32, 0x0, 0xeab, 0xe99, + 0x0, 0xeab, 0xea1, 0x0, 0xecd, 0xeb2, 0x0, 0xf0b, 0x0, 0xf40, + 0xfb5, 0x0, 0xf42, 0xfb7, 0x0, 0xf4c, 0xfb7, 0x0, 0xf51, 0xfb7, + 0x0, 0xf56, 0xfb7, 0x0, 0xf5b, 0xfb7, 0x0, 0xf71, 0xf72, 0x0, + 0xf71, 0xf74, 0x0, 0xf71, 0xf80, 0x0, 0xf90, 0xfb5, 0x0, 0xf92, + 0xfb7, 0x0, 0xf9c, 0xfb7, 0x0, 0xfa1, 0xfb7, 0x0, 0xfa6, 0xfb7, + 0x0, 0xfab, 0xfb7, 0x0, 0xfb2, 0xf71, 0xf80, 0x0, 0xfb2, 0xf80, + 0x0, 0xfb3, 0xf71, 0xf80, 0x0, 0xfb3, 0xf80, 0x0, 0x1025, + 0x102e, 0x0, 0x10dc, 0x0, 0x1100, 0x0, 0x1100, 0x1161, 0x0, + 0x1101, 0x0, 0x1102, 0x0, 0x1102, 0x1161, 0x0, 0x1103, 0x0, + 0x1103, 0x1161, 0x0, 0x1104, 0x0, 0x1105, 0x0, 0x1105, 0x1161, + 0x0, 0x1106, 0x0, 0x1106, 0x1161, 0x0, 0x1107, 0x0, 0x1107, + 0x1161, 0x0, 0x1108, 0x0, 0x1109, 0x0, 0x1109, 0x1161, 0x0, + 0x110a, 0x0, 0x110b, 0x0, 0x110b, 0x1161, 0x0, 0x110b, 0x116e, + 0x0, 0x110c, 0x0, 0x110c, 0x1161, 0x0, 0x110c, 0x116e, 0x110b, + 0x1174, 0x0, 0x110d, 0x0, 0x110e, 0x0, 0x110e, 0x1161, 0x0, + 0x110e, 0x1161, 0x11b7, 0x1100, 0x1169, 0x0, 0x110f, 0x0, + 0x110f, 0x1161, 0x0, 0x1110, 0x0, 0x1110, 0x1161, 0x0, 0x1111, + 0x0, 0x1111, 0x1161, 0x0, 0x1112, 0x0, 0x1112, 0x1161, 0x0, + 0x1114, 0x0, 0x1115, 0x0, 0x111a, 0x0, 0x111c, 0x0, 0x111d, + 0x0, 0x111e, 0x0, 0x1120, 0x0, 0x1121, 0x0, 0x1122, 0x0, + 0x1123, 0x0, 0x1127, 0x0, 0x1129, 0x0, 0x112b, 0x0, 0x112c, + 0x0, 0x112d, 0x0, 0x112e, 0x0, 0x112f, 0x0, 0x1132, 0x0, + 0x1136, 0x0, 0x1140, 0x0, 0x1147, 0x0, 0x114c, 0x0, 0x1157, + 0x0, 0x1158, 0x0, 0x1159, 0x0, 0x1160, 0x0, 0x1161, 0x0, + 0x1162, 0x0, 0x1163, 0x0, 0x1164, 0x0, 0x1165, 0x0, 0x1166, + 0x0, 0x1167, 0x0, 0x1168, 0x0, 0x1169, 0x0, 0x116a, 0x0, + 0x116b, 0x0, 0x116c, 0x0, 0x116d, 0x0, 0x116e, 0x0, 0x116f, + 0x0, 0x1170, 0x0, 0x1171, 0x0, 0x1172, 0x0, 0x1173, 0x0, + 0x1174, 0x0, 0x1175, 0x0, 0x1184, 0x0, 0x1185, 0x0, 0x1188, + 0x0, 0x1191, 0x0, 0x1192, 0x0, 0x1194, 0x0, 0x119e, 0x0, + 0x11a1, 0x0, 0x11aa, 0x0, 0x11ac, 0x0, 0x11ad, 0x0, 0x11b0, + 0x0, 0x11b1, 0x0, 0x11b2, 0x0, 0x11b3, 0x0, 0x11b4, 0x0, + 0x11b5, 0x0, 0x11c7, 0x0, 0x11c8, 0x0, 0x11cc, 0x0, 0x11ce, + 0x0, 0x11d3, 0x0, 0x11d7, 0x0, 0x11d9, 0x0, 0x11dd, 0x0, + 0x11df, 0x0, 0x11f1, 0x0, 0x11f2, 0x0, 0x1b05, 0x1b35, 0x0, + 0x1b07, 0x1b35, 0x0, 0x1b09, 0x1b35, 0x0, 0x1b0b, 0x1b35, 0x0, + 0x1b0d, 0x1b35, 0x0, 0x1b11, 0x1b35, 0x0, 0x1b3a, 0x1b35, 0x0, + 0x1b3c, 0x1b35, 0x0, 0x1b3e, 0x1b35, 0x0, 0x1b3f, 0x1b35, 0x0, + 0x1b42, 0x1b35, 0x0, 0x1d02, 0x0, 0x1d16, 0x0, 0x1d17, 0x0, + 0x1d1c, 0x0, 0x1d1d, 0x0, 0x1d25, 0x0, 0x1d7b, 0x0, 0x1d85, + 0x0, 0x2010, 0x0, 0x2013, 0x0, 0x2014, 0x0, 0x2032, 0x2032, + 0x0, 0x2032, 0x2032, 0x2032, 0x0, 0x2032, 0x2032, 0x2032, + 0x2032, 0x0, 0x2035, 0x2035, 0x0, 0x2035, 0x2035, 0x2035, 0x0, + 0x20a9, 0x0, 0x2190, 0x0, 0x2190, 0x338, 0x0, 0x2191, 0x0, + 0x2192, 0x0, 0x2192, 0x338, 0x0, 0x2193, 0x0, 0x2194, 0x338, + 0x0, 0x21d0, 0x338, 0x0, 0x21d2, 0x338, 0x0, 0x21d4, 0x338, + 0x0, 0x2202, 0x0, 0x2203, 0x338, 0x0, 0x2207, 0x0, 0x2208, + 0x338, 0x0, 0x220b, 0x338, 0x0, 0x2211, 0x0, 0x2212, 0x0, + 0x2223, 0x338, 0x0, 0x2225, 0x338, 0x0, 0x222b, 0x222b, 0x0, + 0x222b, 0x222b, 0x222b, 0x0, 0x222b, 0x222b, 0x222b, 0x222b, + 0x0, 0x222e, 0x222e, 0x0, 0x222e, 0x222e, 0x222e, 0x0, 0x223c, + 0x338, 0x0, 0x2243, 0x338, 0x0, 0x2245, 0x338, 0x0, 0x2248, + 0x338, 0x0, 0x224d, 0x338, 0x0, 0x2261, 0x338, 0x0, 0x2264, + 0x338, 0x0, 0x2265, 0x338, 0x0, 0x2272, 0x338, 0x0, 0x2273, + 0x338, 0x0, 0x2276, 0x338, 0x0, 0x2277, 0x338, 0x0, 0x227a, + 0x338, 0x0, 0x227b, 0x338, 0x0, 0x227c, 0x338, 0x0, 0x227d, + 0x338, 0x0, 0x2282, 0x338, 0x0, 0x2283, 0x338, 0x0, 0x2286, + 0x338, 0x0, 0x2287, 0x338, 0x0, 0x2291, 0x338, 0x0, 0x2292, + 0x338, 0x0, 0x22a2, 0x338, 0x0, 0x22a8, 0x338, 0x0, 0x22a9, + 0x338, 0x0, 0x22ab, 0x338, 0x0, 0x22b2, 0x338, 0x0, 0x22b3, + 0x338, 0x0, 0x22b4, 0x338, 0x0, 0x22b5, 0x338, 0x0, 0x2502, + 0x0, 0x25a0, 0x0, 0x25cb, 0x0, 0x2985, 0x0, 0x2986, 0x0, + 0x2add, 0x338, 0x0, 0x2d61, 0x0, 0x3001, 0x0, 0x3002, 0x0, + 0x3008, 0x0, 0x3009, 0x0, 0x300a, 0x0, 0x300b, 0x0, 0x300c, + 0x0, 0x300d, 0x0, 0x300e, 0x0, 0x300f, 0x0, 0x3010, 0x0, + 0x3011, 0x0, 0x3012, 0x0, 0x3014, 0x0, 0x3014, 0x53, 0x3015, + 0x0, 0x3014, 0x4e09, 0x3015, 0x0, 0x3014, 0x4e8c, 0x3015, 0x0, + 0x3014, 0x52dd, 0x3015, 0x0, 0x3014, 0x5b89, 0x3015, 0x0, + 0x3014, 0x6253, 0x3015, 0x0, 0x3014, 0x6557, 0x3015, 0x0, + 0x3014, 0x672c, 0x3015, 0x0, 0x3014, 0x70b9, 0x3015, 0x0, + 0x3014, 0x76d7, 0x3015, 0x0, 0x3015, 0x0, 0x3016, 0x0, 0x3017, + 0x0, 0x3046, 0x3099, 0x0, 0x304b, 0x3099, 0x0, 0x304d, 0x3099, + 0x0, 0x304f, 0x3099, 0x0, 0x3051, 0x3099, 0x0, 0x3053, 0x3099, + 0x0, 0x3055, 0x3099, 0x0, 0x3057, 0x3099, 0x0, 0x3059, 0x3099, + 0x0, 0x305b, 0x3099, 0x0, 0x305d, 0x3099, 0x0, 0x305f, 0x3099, + 0x0, 0x3061, 0x3099, 0x0, 0x3064, 0x3099, 0x0, 0x3066, 0x3099, + 0x0, 0x3068, 0x3099, 0x0, 0x306f, 0x3099, 0x0, 0x306f, 0x309a, + 0x0, 0x3072, 0x3099, 0x0, 0x3072, 0x309a, 0x0, 0x3075, 0x3099, + 0x0, 0x3075, 0x309a, 0x0, 0x3078, 0x3099, 0x0, 0x3078, 0x309a, + 0x0, 0x307b, 0x304b, 0x0, 0x307b, 0x3099, 0x0, 0x307b, 0x309a, + 0x0, 0x3088, 0x308a, 0x0, 0x3099, 0x0, 0x309a, 0x0, 0x309d, + 0x3099, 0x0, 0x30a1, 0x0, 0x30a2, 0x0, 0x30a2, 0x30cf, 0x309a, + 0x30fc, 0x30c8, 0x0, 0x30a2, 0x30eb, 0x30d5, 0x30a1, 0x0, + 0x30a2, 0x30f3, 0x30d8, 0x309a, 0x30a2, 0x0, 0x30a2, 0x30fc, + 0x30eb, 0x0, 0x30a3, 0x0, 0x30a4, 0x0, 0x30a4, 0x30cb, 0x30f3, + 0x30af, 0x3099, 0x0, 0x30a4, 0x30f3, 0x30c1, 0x0, 0x30a5, 0x0, + 0x30a6, 0x0, 0x30a6, 0x3099, 0x0, 0x30a6, 0x30a9, 0x30f3, 0x0, + 0x30a7, 0x0, 0x30a8, 0x0, 0x30a8, 0x30b9, 0x30af, 0x30fc, + 0x30c8, 0x3099, 0x0, 0x30a8, 0x30fc, 0x30ab, 0x30fc, 0x0, + 0x30a9, 0x0, 0x30aa, 0x0, 0x30aa, 0x30f3, 0x30b9, 0x0, 0x30aa, + 0x30fc, 0x30e0, 0x0, 0x30ab, 0x0, 0x30ab, 0x3099, 0x0, 0x30ab, + 0x3099, 0x30ed, 0x30f3, 0x0, 0x30ab, 0x3099, 0x30f3, 0x30de, + 0x0, 0x30ab, 0x30a4, 0x30ea, 0x0, 0x30ab, 0x30e9, 0x30c3, + 0x30c8, 0x0, 0x30ab, 0x30ed, 0x30ea, 0x30fc, 0x0, 0x30ad, 0x0, + 0x30ad, 0x3099, 0x0, 0x30ad, 0x3099, 0x30ab, 0x3099, 0x0, + 0x30ad, 0x3099, 0x30cb, 0x30fc, 0x0, 0x30ad, 0x3099, 0x30eb, + 0x30bf, 0x3099, 0x30fc, 0x0, 0x30ad, 0x30e5, 0x30ea, 0x30fc, + 0x0, 0x30ad, 0x30ed, 0x0, 0x30ad, 0x30ed, 0x30af, 0x3099, + 0x30e9, 0x30e0, 0x0, 0x30ad, 0x30ed, 0x30e1, 0x30fc, 0x30c8, + 0x30eb, 0x0, 0x30ad, 0x30ed, 0x30ef, 0x30c3, 0x30c8, 0x0, + 0x30af, 0x0, 0x30af, 0x3099, 0x0, 0x30af, 0x3099, 0x30e9, + 0x30e0, 0x0, 0x30af, 0x3099, 0x30e9, 0x30e0, 0x30c8, 0x30f3, + 0x0, 0x30af, 0x30eb, 0x30bb, 0x3099, 0x30a4, 0x30ed, 0x0, + 0x30af, 0x30ed, 0x30fc, 0x30cd, 0x0, 0x30b1, 0x0, 0x30b1, + 0x3099, 0x0, 0x30b1, 0x30fc, 0x30b9, 0x0, 0x30b3, 0x0, 0x30b3, + 0x3099, 0x0, 0x30b3, 0x30b3, 0x0, 0x30b3, 0x30c8, 0x0, 0x30b3, + 0x30eb, 0x30ca, 0x0, 0x30b3, 0x30fc, 0x30db, 0x309a, 0x0, + 0x30b5, 0x0, 0x30b5, 0x3099, 0x0, 0x30b5, 0x30a4, 0x30af, + 0x30eb, 0x0, 0x30b5, 0x30f3, 0x30c1, 0x30fc, 0x30e0, 0x0, + 0x30b7, 0x0, 0x30b7, 0x3099, 0x0, 0x30b7, 0x30ea, 0x30f3, + 0x30af, 0x3099, 0x0, 0x30b9, 0x0, 0x30b9, 0x3099, 0x0, 0x30bb, + 0x0, 0x30bb, 0x3099, 0x0, 0x30bb, 0x30f3, 0x30c1, 0x0, 0x30bb, + 0x30f3, 0x30c8, 0x0, 0x30bd, 0x0, 0x30bd, 0x3099, 0x0, 0x30bf, + 0x0, 0x30bf, 0x3099, 0x0, 0x30bf, 0x3099, 0x30fc, 0x30b9, 0x0, + 0x30c1, 0x0, 0x30c1, 0x3099, 0x0, 0x30c3, 0x0, 0x30c4, 0x0, + 0x30c4, 0x3099, 0x0, 0x30c6, 0x0, 0x30c6, 0x3099, 0x0, 0x30c6, + 0x3099, 0x30b7, 0x0, 0x30c8, 0x0, 0x30c8, 0x3099, 0x0, 0x30c8, + 0x3099, 0x30eb, 0x0, 0x30c8, 0x30f3, 0x0, 0x30ca, 0x0, 0x30ca, + 0x30ce, 0x0, 0x30cb, 0x0, 0x30cc, 0x0, 0x30cd, 0x0, 0x30ce, + 0x0, 0x30ce, 0x30c3, 0x30c8, 0x0, 0x30cf, 0x0, 0x30cf, 0x3099, + 0x0, 0x30cf, 0x3099, 0x30fc, 0x30ec, 0x30eb, 0x0, 0x30cf, + 0x309a, 0x0, 0x30cf, 0x309a, 0x30fc, 0x30bb, 0x30f3, 0x30c8, + 0x0, 0x30cf, 0x309a, 0x30fc, 0x30c4, 0x0, 0x30cf, 0x30a4, + 0x30c4, 0x0, 0x30d2, 0x0, 0x30d2, 0x3099, 0x0, 0x30d2, 0x3099, + 0x30eb, 0x0, 0x30d2, 0x309a, 0x0, 0x30d2, 0x309a, 0x30a2, + 0x30b9, 0x30c8, 0x30eb, 0x0, 0x30d2, 0x309a, 0x30af, 0x30eb, + 0x0, 0x30d2, 0x309a, 0x30b3, 0x0, 0x30d5, 0x0, 0x30d5, 0x3099, + 0x0, 0x30d5, 0x3099, 0x30c3, 0x30b7, 0x30a7, 0x30eb, 0x0, + 0x30d5, 0x309a, 0x0, 0x30d5, 0x30a1, 0x30e9, 0x30c3, 0x30c8, + 0x3099, 0x0, 0x30d5, 0x30a3, 0x30fc, 0x30c8, 0x0, 0x30d5, + 0x30e9, 0x30f3, 0x0, 0x30d8, 0x0, 0x30d8, 0x3099, 0x0, 0x30d8, + 0x3099, 0x30fc, 0x30bf, 0x0, 0x30d8, 0x309a, 0x0, 0x30d8, + 0x309a, 0x30bd, 0x0, 0x30d8, 0x309a, 0x30cb, 0x30d2, 0x0, + 0x30d8, 0x309a, 0x30f3, 0x30b9, 0x0, 0x30d8, 0x309a, 0x30fc, + 0x30b7, 0x3099, 0x0, 0x30d8, 0x30af, 0x30bf, 0x30fc, 0x30eb, + 0x0, 0x30d8, 0x30eb, 0x30c4, 0x0, 0x30db, 0x0, 0x30db, 0x3099, + 0x0, 0x30db, 0x3099, 0x30eb, 0x30c8, 0x0, 0x30db, 0x309a, 0x0, + 0x30db, 0x309a, 0x30a4, 0x30f3, 0x30c8, 0x0, 0x30db, 0x309a, + 0x30f3, 0x30c8, 0x3099, 0x0, 0x30db, 0x30f3, 0x0, 0x30db, + 0x30fc, 0x30eb, 0x0, 0x30db, 0x30fc, 0x30f3, 0x0, 0x30de, 0x0, + 0x30de, 0x30a4, 0x30af, 0x30ed, 0x0, 0x30de, 0x30a4, 0x30eb, + 0x0, 0x30de, 0x30c3, 0x30cf, 0x0, 0x30de, 0x30eb, 0x30af, 0x0, + 0x30de, 0x30f3, 0x30b7, 0x30e7, 0x30f3, 0x0, 0x30df, 0x0, + 0x30df, 0x30af, 0x30ed, 0x30f3, 0x0, 0x30df, 0x30ea, 0x0, + 0x30df, 0x30ea, 0x30cf, 0x3099, 0x30fc, 0x30eb, 0x0, 0x30e0, + 0x0, 0x30e1, 0x0, 0x30e1, 0x30ab, 0x3099, 0x0, 0x30e1, 0x30ab, + 0x3099, 0x30c8, 0x30f3, 0x0, 0x30e1, 0x30fc, 0x30c8, 0x30eb, + 0x0, 0x30e2, 0x0, 0x30e3, 0x0, 0x30e4, 0x0, 0x30e4, 0x30fc, + 0x30c8, 0x3099, 0x0, 0x30e4, 0x30fc, 0x30eb, 0x0, 0x30e5, 0x0, + 0x30e6, 0x0, 0x30e6, 0x30a2, 0x30f3, 0x0, 0x30e7, 0x0, 0x30e8, + 0x0, 0x30e9, 0x0, 0x30ea, 0x0, 0x30ea, 0x30c3, 0x30c8, 0x30eb, + 0x0, 0x30ea, 0x30e9, 0x0, 0x30eb, 0x0, 0x30eb, 0x30d2, 0x309a, + 0x30fc, 0x0, 0x30eb, 0x30fc, 0x30d5, 0x3099, 0x30eb, 0x0, + 0x30ec, 0x0, 0x30ec, 0x30e0, 0x0, 0x30ec, 0x30f3, 0x30c8, + 0x30b1, 0x3099, 0x30f3, 0x0, 0x30ed, 0x0, 0x30ef, 0x0, 0x30ef, + 0x3099, 0x0, 0x30ef, 0x30c3, 0x30c8, 0x0, 0x30f0, 0x0, 0x30f0, + 0x3099, 0x0, 0x30f1, 0x0, 0x30f1, 0x3099, 0x0, 0x30f2, 0x0, + 0x30f2, 0x3099, 0x0, 0x30f3, 0x0, 0x30fb, 0x0, 0x30fc, 0x0, + 0x30fd, 0x3099, 0x0, 0x349e, 0x0, 0x34b9, 0x0, 0x34bb, 0x0, + 0x34df, 0x0, 0x3515, 0x0, 0x36ee, 0x0, 0x36fc, 0x0, 0x3781, + 0x0, 0x382f, 0x0, 0x3862, 0x0, 0x387c, 0x0, 0x38c7, 0x0, + 0x38e3, 0x0, 0x391c, 0x0, 0x393a, 0x0, 0x3a2e, 0x0, 0x3a6c, + 0x0, 0x3ae4, 0x0, 0x3b08, 0x0, 0x3b19, 0x0, 0x3b49, 0x0, + 0x3b9d, 0x0, 0x3c18, 0x0, 0x3c4e, 0x0, 0x3d33, 0x0, 0x3d96, + 0x0, 0x3eac, 0x0, 0x3eb8, 0x0, 0x3f1b, 0x0, 0x3ffc, 0x0, + 0x4008, 0x0, 0x4018, 0x0, 0x4039, 0x0, 0x4046, 0x0, 0x4096, + 0x0, 0x40e3, 0x0, 0x412f, 0x0, 0x4202, 0x0, 0x4227, 0x0, + 0x42a0, 0x0, 0x4301, 0x0, 0x4334, 0x0, 0x4359, 0x0, 0x43d5, + 0x0, 0x43d9, 0x0, 0x440b, 0x0, 0x446b, 0x0, 0x452b, 0x0, + 0x455d, 0x0, 0x4561, 0x0, 0x456b, 0x0, 0x45d7, 0x0, 0x45f9, + 0x0, 0x4635, 0x0, 0x46be, 0x0, 0x46c7, 0x0, 0x4995, 0x0, + 0x49e6, 0x0, 0x4a6e, 0x0, 0x4a76, 0x0, 0x4ab2, 0x0, 0x4b33, + 0x0, 0x4bce, 0x0, 0x4cce, 0x0, 0x4ced, 0x0, 0x4cf8, 0x0, + 0x4d56, 0x0, 0x4e00, 0x0, 0x4e01, 0x0, 0x4e03, 0x0, 0x4e09, + 0x0, 0x4e0a, 0x0, 0x4e0b, 0x0, 0x4e0d, 0x0, 0x4e19, 0x0, + 0x4e26, 0x0, 0x4e28, 0x0, 0x4e2d, 0x0, 0x4e32, 0x0, 0x4e36, + 0x0, 0x4e38, 0x0, 0x4e39, 0x0, 0x4e3d, 0x0, 0x4e3f, 0x0, + 0x4e41, 0x0, 0x4e59, 0x0, 0x4e5d, 0x0, 0x4e82, 0x0, 0x4e85, + 0x0, 0x4e86, 0x0, 0x4e8c, 0x0, 0x4e94, 0x0, 0x4ea0, 0x0, + 0x4ea4, 0x0, 0x4eae, 0x0, 0x4eba, 0x0, 0x4ec0, 0x0, 0x4ecc, + 0x0, 0x4ee4, 0x0, 0x4f01, 0x0, 0x4f11, 0x0, 0x4f60, 0x0, + 0x4f80, 0x0, 0x4f86, 0x0, 0x4f8b, 0x0, 0x4fae, 0x0, 0x4fbb, + 0x0, 0x4fbf, 0x0, 0x5002, 0x0, 0x502b, 0x0, 0x507a, 0x0, + 0x5099, 0x0, 0x50cf, 0x0, 0x50da, 0x0, 0x50e7, 0x0, 0x512a, + 0x0, 0x513f, 0x0, 0x5140, 0x0, 0x5145, 0x0, 0x514d, 0x0, + 0x5154, 0x0, 0x5164, 0x0, 0x5165, 0x0, 0x5167, 0x0, 0x5168, + 0x0, 0x5169, 0x0, 0x516b, 0x0, 0x516d, 0x0, 0x5177, 0x0, + 0x5180, 0x0, 0x5182, 0x0, 0x518d, 0x0, 0x5192, 0x0, 0x5195, + 0x0, 0x5196, 0x0, 0x5197, 0x0, 0x5199, 0x0, 0x51a4, 0x0, + 0x51ab, 0x0, 0x51ac, 0x0, 0x51b5, 0x0, 0x51b7, 0x0, 0x51c9, + 0x0, 0x51cc, 0x0, 0x51dc, 0x0, 0x51de, 0x0, 0x51e0, 0x0, + 0x51f5, 0x0, 0x5200, 0x0, 0x5203, 0x0, 0x5207, 0x0, 0x5217, + 0x0, 0x521d, 0x0, 0x5229, 0x0, 0x523a, 0x0, 0x523b, 0x0, + 0x5246, 0x0, 0x524d, 0x0, 0x5272, 0x0, 0x5277, 0x0, 0x5289, + 0x0, 0x529b, 0x0, 0x52a3, 0x0, 0x52b3, 0x0, 0x52b4, 0x0, + 0x52c7, 0x0, 0x52c9, 0x0, 0x52d2, 0x0, 0x52de, 0x0, 0x52e4, + 0x0, 0x52f5, 0x0, 0x52f9, 0x0, 0x52fa, 0x0, 0x5305, 0x0, + 0x5306, 0x0, 0x5315, 0x0, 0x5317, 0x0, 0x531a, 0x0, 0x5338, + 0x0, 0x533b, 0x0, 0x533f, 0x0, 0x5341, 0x0, 0x5344, 0x0, + 0x5345, 0x0, 0x5349, 0x0, 0x5351, 0x0, 0x5354, 0x0, 0x535a, + 0x0, 0x535c, 0x0, 0x5369, 0x0, 0x5370, 0x0, 0x5373, 0x0, + 0x5375, 0x0, 0x537d, 0x0, 0x537f, 0x0, 0x5382, 0x0, 0x53b6, + 0x0, 0x53c3, 0x0, 0x53c8, 0x0, 0x53ca, 0x0, 0x53cc, 0x0, + 0x53df, 0x0, 0x53e3, 0x0, 0x53e5, 0x0, 0x53eb, 0x0, 0x53ef, + 0x0, 0x53f1, 0x0, 0x53f3, 0x0, 0x5406, 0x0, 0x5408, 0x0, + 0x540d, 0x0, 0x540f, 0x0, 0x541d, 0x0, 0x5438, 0x0, 0x5439, + 0x0, 0x5442, 0x0, 0x5448, 0x0, 0x5468, 0x0, 0x549e, 0x0, + 0x54a2, 0x0, 0x54bd, 0x0, 0x54f6, 0x0, 0x5510, 0x0, 0x554f, + 0x0, 0x5553, 0x0, 0x5555, 0x0, 0x5563, 0x0, 0x5584, 0x0, + 0x5587, 0x0, 0x5599, 0x0, 0x559d, 0x0, 0x55ab, 0x0, 0x55b3, + 0x0, 0x55b6, 0x0, 0x55c0, 0x0, 0x55c2, 0x0, 0x55e2, 0x0, + 0x5606, 0x0, 0x5651, 0x0, 0x5668, 0x0, 0x5674, 0x0, 0x56d7, + 0x0, 0x56db, 0x0, 0x56f9, 0x0, 0x5716, 0x0, 0x5717, 0x0, + 0x571f, 0x0, 0x5730, 0x0, 0x578b, 0x0, 0x57ce, 0x0, 0x57f4, + 0x0, 0x580d, 0x0, 0x5831, 0x0, 0x5832, 0x0, 0x5840, 0x0, + 0x585a, 0x0, 0x585e, 0x0, 0x58a8, 0x0, 0x58ac, 0x0, 0x58b3, + 0x0, 0x58d8, 0x0, 0x58df, 0x0, 0x58eb, 0x0, 0x58ee, 0x0, + 0x58f0, 0x0, 0x58f2, 0x0, 0x58f7, 0x0, 0x5902, 0x0, 0x5906, + 0x0, 0x590a, 0x0, 0x5915, 0x0, 0x591a, 0x0, 0x591c, 0x0, + 0x5922, 0x0, 0x5927, 0x0, 0x5927, 0x6b63, 0x0, 0x5929, 0x0, + 0x5944, 0x0, 0x5948, 0x0, 0x5951, 0x0, 0x5954, 0x0, 0x5962, + 0x0, 0x5973, 0x0, 0x59d8, 0x0, 0x59ec, 0x0, 0x5a1b, 0x0, + 0x5a27, 0x0, 0x5a62, 0x0, 0x5a66, 0x0, 0x5ab5, 0x0, 0x5b08, + 0x0, 0x5b28, 0x0, 0x5b3e, 0x0, 0x5b50, 0x0, 0x5b57, 0x0, + 0x5b66, 0x0, 0x5b80, 0x0, 0x5b85, 0x0, 0x5b97, 0x0, 0x5bc3, + 0x0, 0x5bd8, 0x0, 0x5be7, 0x0, 0x5bee, 0x0, 0x5bf3, 0x0, + 0x5bf8, 0x0, 0x5bff, 0x0, 0x5c06, 0x0, 0x5c0f, 0x0, 0x5c22, + 0x0, 0x5c38, 0x0, 0x5c3f, 0x0, 0x5c60, 0x0, 0x5c62, 0x0, + 0x5c64, 0x0, 0x5c65, 0x0, 0x5c6e, 0x0, 0x5c71, 0x0, 0x5c8d, + 0x0, 0x5cc0, 0x0, 0x5d19, 0x0, 0x5d43, 0x0, 0x5d50, 0x0, + 0x5d6b, 0x0, 0x5d6e, 0x0, 0x5d7c, 0x0, 0x5db2, 0x0, 0x5dba, + 0x0, 0x5ddb, 0x0, 0x5de1, 0x0, 0x5de2, 0x0, 0x5de5, 0x0, + 0x5de6, 0x0, 0x5df1, 0x0, 0x5dfd, 0x0, 0x5dfe, 0x0, 0x5e28, + 0x0, 0x5e3d, 0x0, 0x5e69, 0x0, 0x5e72, 0x0, 0x5e73, 0x6210, + 0x0, 0x5e74, 0x0, 0x5e7a, 0x0, 0x5e7c, 0x0, 0x5e7f, 0x0, + 0x5ea6, 0x0, 0x5eb0, 0x0, 0x5eb3, 0x0, 0x5eb6, 0x0, 0x5ec9, + 0x0, 0x5eca, 0x0, 0x5ed2, 0x0, 0x5ed3, 0x0, 0x5ed9, 0x0, + 0x5eec, 0x0, 0x5ef4, 0x0, 0x5efe, 0x0, 0x5f04, 0x0, 0x5f0b, + 0x0, 0x5f13, 0x0, 0x5f22, 0x0, 0x5f50, 0x0, 0x5f53, 0x0, + 0x5f61, 0x0, 0x5f62, 0x0, 0x5f69, 0x0, 0x5f6b, 0x0, 0x5f73, + 0x0, 0x5f8b, 0x0, 0x5f8c, 0x0, 0x5f97, 0x0, 0x5f9a, 0x0, + 0x5fa9, 0x0, 0x5fad, 0x0, 0x5fc3, 0x0, 0x5fcd, 0x0, 0x5fd7, + 0x0, 0x5ff5, 0x0, 0x5ff9, 0x0, 0x6012, 0x0, 0x601c, 0x0, + 0x6075, 0x0, 0x6081, 0x0, 0x6094, 0x0, 0x60c7, 0x0, 0x60d8, + 0x0, 0x60e1, 0x0, 0x6108, 0x0, 0x6144, 0x0, 0x6148, 0x0, + 0x614c, 0x0, 0x614e, 0x0, 0x6160, 0x0, 0x6168, 0x0, 0x617a, + 0x0, 0x618e, 0x0, 0x6190, 0x0, 0x61a4, 0x0, 0x61af, 0x0, + 0x61b2, 0x0, 0x61de, 0x0, 0x61f2, 0x0, 0x61f6, 0x0, 0x6200, + 0x0, 0x6208, 0x0, 0x6210, 0x0, 0x621b, 0x0, 0x622e, 0x0, + 0x6234, 0x0, 0x6236, 0x0, 0x624b, 0x0, 0x6253, 0x0, 0x625d, + 0x0, 0x6295, 0x0, 0x62b1, 0x0, 0x62c9, 0x0, 0x62cf, 0x0, + 0x62d3, 0x0, 0x62d4, 0x0, 0x62fc, 0x0, 0x62fe, 0x0, 0x6307, + 0x0, 0x633d, 0x0, 0x6350, 0x0, 0x6355, 0x0, 0x6368, 0x0, + 0x637b, 0x0, 0x6383, 0x0, 0x63a0, 0x0, 0x63a9, 0x0, 0x63c4, + 0x0, 0x63c5, 0x0, 0x63e4, 0x0, 0x641c, 0x0, 0x6422, 0x0, + 0x6452, 0x0, 0x6469, 0x0, 0x6477, 0x0, 0x647e, 0x0, 0x649a, + 0x0, 0x649d, 0x0, 0x64c4, 0x0, 0x652f, 0x0, 0x6534, 0x0, + 0x654f, 0x0, 0x6556, 0x0, 0x656c, 0x0, 0x6578, 0x0, 0x6587, + 0x0, 0x6597, 0x0, 0x6599, 0x0, 0x65a4, 0x0, 0x65b0, 0x0, + 0x65b9, 0x0, 0x65c5, 0x0, 0x65e0, 0x0, 0x65e2, 0x0, 0x65e3, + 0x0, 0x65e5, 0x0, 0x660e, 0x6cbb, 0x0, 0x6613, 0x0, 0x6620, + 0x0, 0x662d, 0x548c, 0x0, 0x6649, 0x0, 0x6674, 0x0, 0x6688, + 0x0, 0x6691, 0x0, 0x669c, 0x0, 0x66b4, 0x0, 0x66c6, 0x0, + 0x66f0, 0x0, 0x66f4, 0x0, 0x66f8, 0x0, 0x6700, 0x0, 0x6708, + 0x0, 0x6709, 0x0, 0x6717, 0x0, 0x671b, 0x0, 0x6721, 0x0, + 0x6728, 0x0, 0x674e, 0x0, 0x6753, 0x0, 0x6756, 0x0, 0x675e, + 0x0, 0x677b, 0x0, 0x6785, 0x0, 0x6797, 0x0, 0x67f3, 0x0, + 0x67fa, 0x0, 0x6817, 0x0, 0x681f, 0x0, 0x682a, 0x0, 0x682a, + 0x5f0f, 0x4f1a, 0x793e, 0x0, 0x6852, 0x0, 0x6881, 0x0, 0x6885, + 0x0, 0x688e, 0x0, 0x68a8, 0x0, 0x6914, 0x0, 0x6942, 0x0, + 0x69a3, 0x0, 0x69ea, 0x0, 0x6a02, 0x0, 0x6a13, 0x0, 0x6aa8, + 0x0, 0x6ad3, 0x0, 0x6adb, 0x0, 0x6b04, 0x0, 0x6b20, 0x0, + 0x6b21, 0x0, 0x6b54, 0x0, 0x6b62, 0x0, 0x6b63, 0x0, 0x6b72, + 0x0, 0x6b77, 0x0, 0x6b79, 0x0, 0x6b9f, 0x0, 0x6bae, 0x0, + 0x6bb3, 0x0, 0x6bba, 0x0, 0x6bbb, 0x0, 0x6bcb, 0x0, 0x6bcd, + 0x0, 0x6bd4, 0x0, 0x6bdb, 0x0, 0x6c0f, 0x0, 0x6c14, 0x0, + 0x6c34, 0x0, 0x6c4e, 0x0, 0x6c67, 0x0, 0x6c88, 0x0, 0x6cbf, + 0x0, 0x6ccc, 0x0, 0x6ccd, 0x0, 0x6ce5, 0x0, 0x6ce8, 0x0, + 0x6d16, 0x0, 0x6d1b, 0x0, 0x6d1e, 0x0, 0x6d34, 0x0, 0x6d3e, + 0x0, 0x6d41, 0x0, 0x6d69, 0x0, 0x6d6a, 0x0, 0x6d77, 0x0, + 0x6d78, 0x0, 0x6d85, 0x0, 0x6dcb, 0x0, 0x6dda, 0x0, 0x6dea, + 0x0, 0x6df9, 0x0, 0x6e1a, 0x0, 0x6e2f, 0x0, 0x6e6e, 0x0, + 0x6e80, 0x0, 0x6e9c, 0x0, 0x6eba, 0x0, 0x6ec7, 0x0, 0x6ecb, + 0x0, 0x6ed1, 0x0, 0x6edb, 0x0, 0x6f0f, 0x0, 0x6f14, 0x0, + 0x6f22, 0x0, 0x6f23, 0x0, 0x6f6e, 0x0, 0x6fc6, 0x0, 0x6feb, + 0x0, 0x6ffe, 0x0, 0x701b, 0x0, 0x701e, 0x0, 0x7039, 0x0, + 0x704a, 0x0, 0x706b, 0x0, 0x7070, 0x0, 0x7077, 0x0, 0x707d, + 0x0, 0x7099, 0x0, 0x70ad, 0x0, 0x70c8, 0x0, 0x70d9, 0x0, + 0x7121, 0x0, 0x7145, 0x0, 0x7149, 0x0, 0x716e, 0x0, 0x719c, + 0x0, 0x71ce, 0x0, 0x71d0, 0x0, 0x7210, 0x0, 0x721b, 0x0, + 0x7228, 0x0, 0x722a, 0x0, 0x722b, 0x0, 0x7235, 0x0, 0x7236, + 0x0, 0x723b, 0x0, 0x723f, 0x0, 0x7247, 0x0, 0x7250, 0x0, + 0x7259, 0x0, 0x725b, 0x0, 0x7262, 0x0, 0x7279, 0x0, 0x7280, + 0x0, 0x7295, 0x0, 0x72ac, 0x0, 0x72af, 0x0, 0x72c0, 0x0, + 0x72fc, 0x0, 0x732a, 0x0, 0x7375, 0x0, 0x737a, 0x0, 0x7384, + 0x0, 0x7387, 0x0, 0x7389, 0x0, 0x738b, 0x0, 0x73a5, 0x0, + 0x73b2, 0x0, 0x73de, 0x0, 0x7406, 0x0, 0x7409, 0x0, 0x7422, + 0x0, 0x7447, 0x0, 0x745c, 0x0, 0x7469, 0x0, 0x7471, 0x0, + 0x7485, 0x0, 0x7489, 0x0, 0x7498, 0x0, 0x74ca, 0x0, 0x74dc, + 0x0, 0x74e6, 0x0, 0x7506, 0x0, 0x7518, 0x0, 0x751f, 0x0, + 0x7524, 0x0, 0x7528, 0x0, 0x7530, 0x0, 0x7532, 0x0, 0x7533, + 0x0, 0x7537, 0x0, 0x753b, 0x0, 0x753e, 0x0, 0x7559, 0x0, + 0x7565, 0x0, 0x7570, 0x0, 0x758b, 0x0, 0x7592, 0x0, 0x75e2, + 0x0, 0x7610, 0x0, 0x761d, 0x0, 0x761f, 0x0, 0x7642, 0x0, + 0x7669, 0x0, 0x7676, 0x0, 0x767d, 0x0, 0x76ae, 0x0, 0x76bf, + 0x0, 0x76ca, 0x0, 0x76db, 0x0, 0x76e3, 0x0, 0x76e7, 0x0, + 0x76ee, 0x0, 0x76f4, 0x0, 0x7701, 0x0, 0x771e, 0x0, 0x771f, + 0x0, 0x7740, 0x0, 0x774a, 0x0, 0x778b, 0x0, 0x77a7, 0x0, + 0x77db, 0x0, 0x77e2, 0x0, 0x77f3, 0x0, 0x784e, 0x0, 0x786b, + 0x0, 0x788c, 0x0, 0x7891, 0x0, 0x78ca, 0x0, 0x78cc, 0x0, + 0x78fb, 0x0, 0x792a, 0x0, 0x793a, 0x0, 0x793c, 0x0, 0x793e, + 0x0, 0x7948, 0x0, 0x7949, 0x0, 0x7950, 0x0, 0x7956, 0x0, + 0x795d, 0x0, 0x795e, 0x0, 0x7965, 0x0, 0x797f, 0x0, 0x7981, + 0x0, 0x798d, 0x0, 0x798e, 0x0, 0x798f, 0x0, 0x79ae, 0x0, + 0x79b8, 0x0, 0x79be, 0x0, 0x79ca, 0x0, 0x79d8, 0x0, 0x79eb, + 0x0, 0x7a1c, 0x0, 0x7a40, 0x0, 0x7a4a, 0x0, 0x7a4f, 0x0, + 0x7a74, 0x0, 0x7a7a, 0x0, 0x7a81, 0x0, 0x7ab1, 0x0, 0x7acb, + 0x0, 0x7aee, 0x0, 0x7af9, 0x0, 0x7b20, 0x0, 0x7b8f, 0x0, + 0x7bc0, 0x0, 0x7bc6, 0x0, 0x7bc9, 0x0, 0x7c3e, 0x0, 0x7c60, + 0x0, 0x7c73, 0x0, 0x7c7b, 0x0, 0x7c92, 0x0, 0x7cbe, 0x0, + 0x7cd2, 0x0, 0x7cd6, 0x0, 0x7ce3, 0x0, 0x7ce7, 0x0, 0x7ce8, + 0x0, 0x7cf8, 0x0, 0x7d00, 0x0, 0x7d10, 0x0, 0x7d22, 0x0, + 0x7d2f, 0x0, 0x7d42, 0x0, 0x7d5b, 0x0, 0x7d63, 0x0, 0x7da0, + 0x0, 0x7dbe, 0x0, 0x7dc7, 0x0, 0x7df4, 0x0, 0x7e02, 0x0, + 0x7e09, 0x0, 0x7e37, 0x0, 0x7e41, 0x0, 0x7e45, 0x0, 0x7f36, + 0x0, 0x7f3e, 0x0, 0x7f51, 0x0, 0x7f72, 0x0, 0x7f79, 0x0, + 0x7f7a, 0x0, 0x7f85, 0x0, 0x7f8a, 0x0, 0x7f95, 0x0, 0x7f9a, + 0x0, 0x7fbd, 0x0, 0x7ffa, 0x0, 0x8001, 0x0, 0x8005, 0x0, + 0x800c, 0x0, 0x8012, 0x0, 0x8033, 0x0, 0x8046, 0x0, 0x8060, + 0x0, 0x806f, 0x0, 0x8070, 0x0, 0x807e, 0x0, 0x807f, 0x0, + 0x8089, 0x0, 0x808b, 0x0, 0x80ad, 0x0, 0x80b2, 0x0, 0x8103, + 0x0, 0x813e, 0x0, 0x81d8, 0x0, 0x81e3, 0x0, 0x81e8, 0x0, + 0x81ea, 0x0, 0x81ed, 0x0, 0x81f3, 0x0, 0x81fc, 0x0, 0x8201, + 0x0, 0x8204, 0x0, 0x820c, 0x0, 0x8218, 0x0, 0x821b, 0x0, + 0x821f, 0x0, 0x826e, 0x0, 0x826f, 0x0, 0x8272, 0x0, 0x8278, + 0x0, 0x8279, 0x0, 0x828b, 0x0, 0x8291, 0x0, 0x829d, 0x0, + 0x82b1, 0x0, 0x82b3, 0x0, 0x82bd, 0x0, 0x82e5, 0x0, 0x82e6, + 0x0, 0x831d, 0x0, 0x8323, 0x0, 0x8336, 0x0, 0x8352, 0x0, + 0x8353, 0x0, 0x8363, 0x0, 0x83ad, 0x0, 0x83bd, 0x0, 0x83c9, + 0x0, 0x83ca, 0x0, 0x83cc, 0x0, 0x83dc, 0x0, 0x83e7, 0x0, + 0x83ef, 0x0, 0x83f1, 0x0, 0x843d, 0x0, 0x8449, 0x0, 0x8457, + 0x0, 0x84ee, 0x0, 0x84f1, 0x0, 0x84f3, 0x0, 0x84fc, 0x0, + 0x8516, 0x0, 0x8564, 0x0, 0x85cd, 0x0, 0x85fa, 0x0, 0x8606, + 0x0, 0x8612, 0x0, 0x862d, 0x0, 0x863f, 0x0, 0x864d, 0x0, + 0x8650, 0x0, 0x865c, 0x0, 0x8667, 0x0, 0x8669, 0x0, 0x866b, + 0x0, 0x8688, 0x0, 0x86a9, 0x0, 0x86e2, 0x0, 0x870e, 0x0, + 0x8728, 0x0, 0x876b, 0x0, 0x8779, 0x0, 0x8786, 0x0, 0x87ba, + 0x0, 0x87e1, 0x0, 0x8801, 0x0, 0x881f, 0x0, 0x8840, 0x0, + 0x884c, 0x0, 0x8860, 0x0, 0x8863, 0x0, 0x88c2, 0x0, 0x88cf, + 0x0, 0x88d7, 0x0, 0x88de, 0x0, 0x88e1, 0x0, 0x88f8, 0x0, + 0x88fa, 0x0, 0x8910, 0x0, 0x8941, 0x0, 0x8964, 0x0, 0x897e, + 0x0, 0x8986, 0x0, 0x898b, 0x0, 0x8996, 0x0, 0x89d2, 0x0, + 0x89e3, 0x0, 0x8a00, 0x0, 0x8aa0, 0x0, 0x8aaa, 0x0, 0x8abf, + 0x0, 0x8acb, 0x0, 0x8ad2, 0x0, 0x8ad6, 0x0, 0x8aed, 0x0, + 0x8af8, 0x0, 0x8afe, 0x0, 0x8b01, 0x0, 0x8b39, 0x0, 0x8b58, + 0x0, 0x8b80, 0x0, 0x8b8a, 0x0, 0x8c37, 0x0, 0x8c46, 0x0, + 0x8c48, 0x0, 0x8c55, 0x0, 0x8c78, 0x0, 0x8c9d, 0x0, 0x8ca1, + 0x0, 0x8ca9, 0x0, 0x8cab, 0x0, 0x8cc1, 0x0, 0x8cc2, 0x0, + 0x8cc7, 0x0, 0x8cc8, 0x0, 0x8cd3, 0x0, 0x8d08, 0x0, 0x8d1b, + 0x0, 0x8d64, 0x0, 0x8d70, 0x0, 0x8d77, 0x0, 0x8db3, 0x0, + 0x8dbc, 0x0, 0x8dcb, 0x0, 0x8def, 0x0, 0x8df0, 0x0, 0x8eab, + 0x0, 0x8eca, 0x0, 0x8ed4, 0x0, 0x8f26, 0x0, 0x8f2a, 0x0, + 0x8f38, 0x0, 0x8f3b, 0x0, 0x8f62, 0x0, 0x8f9b, 0x0, 0x8f9e, + 0x0, 0x8fb0, 0x0, 0x8fb5, 0x0, 0x8fb6, 0x0, 0x9023, 0x0, + 0x9038, 0x0, 0x904a, 0x0, 0x9069, 0x0, 0x9072, 0x0, 0x907c, + 0x0, 0x908f, 0x0, 0x9091, 0x0, 0x9094, 0x0, 0x90ce, 0x0, + 0x90de, 0x0, 0x90f1, 0x0, 0x90fd, 0x0, 0x9111, 0x0, 0x911b, + 0x0, 0x9149, 0x0, 0x916a, 0x0, 0x9199, 0x0, 0x91b4, 0x0, + 0x91c6, 0x0, 0x91cc, 0x0, 0x91cf, 0x0, 0x91d1, 0x0, 0x9234, + 0x0, 0x9238, 0x0, 0x9276, 0x0, 0x927c, 0x0, 0x92d7, 0x0, + 0x92d8, 0x0, 0x9304, 0x0, 0x934a, 0x0, 0x93f9, 0x0, 0x9415, + 0x0, 0x9577, 0x0, 0x9580, 0x0, 0x958b, 0x0, 0x95ad, 0x0, + 0x95b7, 0x0, 0x961c, 0x0, 0x962e, 0x0, 0x964b, 0x0, 0x964d, + 0x0, 0x9675, 0x0, 0x9678, 0x0, 0x967c, 0x0, 0x9686, 0x0, + 0x96a3, 0x0, 0x96b6, 0x0, 0x96b7, 0x0, 0x96b8, 0x0, 0x96b9, + 0x0, 0x96c3, 0x0, 0x96e2, 0x0, 0x96e3, 0x0, 0x96e8, 0x0, + 0x96f6, 0x0, 0x96f7, 0x0, 0x9723, 0x0, 0x9732, 0x0, 0x9748, + 0x0, 0x9751, 0x0, 0x9756, 0x0, 0x975e, 0x0, 0x9762, 0x0, + 0x9769, 0x0, 0x97cb, 0x0, 0x97db, 0x0, 0x97e0, 0x0, 0x97ed, + 0x0, 0x97f3, 0x0, 0x97ff, 0x0, 0x9801, 0x0, 0x9805, 0x0, + 0x980b, 0x0, 0x9818, 0x0, 0x9829, 0x0, 0x983b, 0x0, 0x985e, + 0x0, 0x98a8, 0x0, 0x98db, 0x0, 0x98df, 0x0, 0x98e2, 0x0, + 0x98ef, 0x0, 0x98fc, 0x0, 0x9928, 0x0, 0x9929, 0x0, 0x9996, + 0x0, 0x9999, 0x0, 0x99a7, 0x0, 0x99ac, 0x0, 0x99c2, 0x0, + 0x99f1, 0x0, 0x99fe, 0x0, 0x9a6a, 0x0, 0x9aa8, 0x0, 0x9ad8, + 0x0, 0x9adf, 0x0, 0x9b12, 0x0, 0x9b25, 0x0, 0x9b2f, 0x0, + 0x9b32, 0x0, 0x9b3c, 0x0, 0x9b5a, 0x0, 0x9b6f, 0x0, 0x9c40, + 0x0, 0x9c57, 0x0, 0x9ce5, 0x0, 0x9cfd, 0x0, 0x9d67, 0x0, + 0x9db4, 0x0, 0x9dfa, 0x0, 0x9e1e, 0x0, 0x9e75, 0x0, 0x9e7f, + 0x0, 0x9e97, 0x0, 0x9e9f, 0x0, 0x9ea5, 0x0, 0x9ebb, 0x0, + 0x9ec3, 0x0, 0x9ecd, 0x0, 0x9ece, 0x0, 0x9ed1, 0x0, 0x9ef9, + 0x0, 0x9efd, 0x0, 0x9efe, 0x0, 0x9f05, 0x0, 0x9f0e, 0x0, + 0x9f0f, 0x0, 0x9f13, 0x0, 0x9f16, 0x0, 0x9f20, 0x0, 0x9f3b, + 0x0, 0x9f43, 0x0, 0x9f4a, 0x0, 0x9f52, 0x0, 0x9f8d, 0x0, + 0x9f8e, 0x0, 0x9f9c, 0x0, 0x9f9f, 0x0, 0x9fa0, 0x0, 0xa76f, + 0x0, 0x11099, 0x110ba, 0x0, 0x1109b, 0x110ba, 0x0, 0x110a5, + 0x110ba, 0x0, 0x11131, 0x11127, 0x0, 0x11132, 0x11127, 0x0, + 0x1d157, 0x1d165, 0x0, 0x1d158, 0x1d165, 0x0, 0x1d158, 0x1d165, + 0x1d16e, 0x0, 0x1d158, 0x1d165, 0x1d16f, 0x0, 0x1d158, 0x1d165, + 0x1d170, 0x0, 0x1d158, 0x1d165, 0x1d171, 0x0, 0x1d158, 0x1d165, + 0x1d172, 0x0, 0x1d1b9, 0x1d165, 0x0, 0x1d1b9, 0x1d165, 0x1d16e, + 0x0, 0x1d1b9, 0x1d165, 0x1d16f, 0x0, 0x1d1ba, 0x1d165, 0x0, + 0x1d1ba, 0x1d165, 0x1d16e, 0x0, 0x1d1ba, 0x1d165, 0x1d16f, 0x0, + 0x20122, 0x0, 0x2051c, 0x0, 0x20525, 0x0, 0x2054b, 0x0, + 0x2063a, 0x0, 0x20804, 0x0, 0x208de, 0x0, 0x20a2c, 0x0, + 0x20b63, 0x0, 0x214e4, 0x0, 0x216a8, 0x0, 0x216ea, 0x0, + 0x219c8, 0x0, 0x21b18, 0x0, 0x21d0b, 0x0, 0x21de4, 0x0, + 0x21de6, 0x0, 0x22183, 0x0, 0x2219f, 0x0, 0x22331, 0x0, + 0x226d4, 0x0, 0x22844, 0x0, 0x2284a, 0x0, 0x22b0c, 0x0, + 0x22bf1, 0x0, 0x2300a, 0x0, 0x232b8, 0x0, 0x2335f, 0x0, + 0x23393, 0x0, 0x2339c, 0x0, 0x233c3, 0x0, 0x233d5, 0x0, + 0x2346d, 0x0, 0x236a3, 0x0, 0x238a7, 0x0, 0x23a8d, 0x0, + 0x23afa, 0x0, 0x23cbc, 0x0, 0x23d1e, 0x0, 0x23ed1, 0x0, + 0x23f5e, 0x0, 0x23f8e, 0x0, 0x24263, 0x0, 0x242ee, 0x0, + 0x243ab, 0x0, 0x24608, 0x0, 0x24735, 0x0, 0x24814, 0x0, + 0x24c36, 0x0, 0x24c92, 0x0, 0x24fa1, 0x0, 0x24fb8, 0x0, + 0x25044, 0x0, 0x250f2, 0x0, 0x250f3, 0x0, 0x25119, 0x0, + 0x25133, 0x0, 0x25249, 0x0, 0x2541d, 0x0, 0x25626, 0x0, + 0x2569a, 0x0, 0x256c5, 0x0, 0x2597c, 0x0, 0x25aa7, 0x0, + 0x25bab, 0x0, 0x25c80, 0x0, 0x25cd0, 0x0, 0x25f86, 0x0, + 0x261da, 0x0, 0x26228, 0x0, 0x26247, 0x0, 0x262d9, 0x0, + 0x2633e, 0x0, 0x264da, 0x0, 0x26523, 0x0, 0x265a8, 0x0, + 0x267a7, 0x0, 0x267b5, 0x0, 0x26b3c, 0x0, 0x26c36, 0x0, + 0x26cd5, 0x0, 0x26d6b, 0x0, 0x26f2c, 0x0, 0x26fb1, 0x0, + 0x270d2, 0x0, 0x273ca, 0x0, 0x27667, 0x0, 0x278ae, 0x0, + 0x27966, 0x0, 0x27ca8, 0x0, 0x27ed3, 0x0, 0x27f2f, 0x0, + 0x285d2, 0x0, 0x285ed, 0x0, 0x2872e, 0x0, 0x28bfa, 0x0, + 0x28d77, 0x0, 0x29145, 0x0, 0x291df, 0x0, 0x2921a, 0x0, + 0x2940a, 0x0, 0x29496, 0x0, 0x295b6, 0x0, 0x29b30, 0x0, + 0x2a0ce, 0x0, 0x2a105, 0x0, 0x2a20e, 0x0, 0x2a291, 0x0, + 0x2a392, 0x0, 0x2a600, 0x0 + ]; + return t; + } + } + +} + +static if (size_t.sizeof == 4) +{ + //22656 bytes + enum compatMappingTrieEntries = TrieEntry!(ushort, 8, 8, 5)([0x0, 0x40, + 0x540], [0x100, 0xa00, 0x21c0], [0x2020100, 0x4020302, 0x2020205, + 0x7060202, 0x2020202, 0x8020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x0, 0x0, 0x10000, 0x30002, 0x50004, 0x70006, 0x80000, + 0xa0009, 0xc000b, 0x0, 0xd0000, 0xf000e, 0x0, 0x110010, 0x130012, + 0x150014, 0x170016, 0x190018, 0x0, 0x1b001a, 0x0, 0x0, 0x1c, 0x0, + 0x1d0000, 0x1e0000, 0x0, 0x1f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x200000, 0x21, 0x0, 0x22, 0x230000, 0x24, 0x0, 0x0, 0x0, + 0x25, 0x26, 0x27, 0x0, 0x28, 0x0, 0x29, 0x0, 0x2a, 0x0, 0x2b, + 0x2c0000, 0x0, 0x2d0000, 0x2e, 0x2f, 0x310030, 0x330032, 0x0, + 0x340000, 0x0, 0x0, 0x350000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x370036, 0x38, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x390000, 0x3b003a, 0x3d003c, 0x0, 0x3f003e, + 0x410040, 0x430042, 0x450044, 0x470046, 0x490048, 0x4b004a, + 0x4d004c, 0x4f004e, 0x510050, 0x530052, 0x0, 0x550054, 0x570056, + 0x590058, 0x5a, 0x5c005b, 0x5e005d, 0x60005f, 0x610000, 0x620000, + 0x0, 0x0, 0x0, 0x0, 0x630000, 0x650064, 0x670066, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x68, 0x690000, 0x0, 0x6a, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x6b0000, 0x0, 0x0, 0x0, 0x6c0000, 0x0, 0x0, 0x0, 0x0, 0x6d, + 0x6e0000, 0x70006f, 0x720071, 0x740073, 0x75, 0x770076, 0x790078, + 0x7b007a, 0x7d007c, 0x7e0000, 0x80007f, 0x81, 0x0, 0x830082, + 0x850084, 0x870086, 0x890088, 0x8b008a, 0x8d008c, 0x8f008e, + 0x910090, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x920000, 0x0, 0x930000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x950094, 0x970096, 0x990098, + 0x9b009a, 0x9d009c, 0x9f009e, 0xa100a0, 0xa2, 0xa400a3, 0xa600a5, + 0xa800a7, 0xaa00a9, 0xac00ab, 0xae00ad, 0xb000af, 0xb200b1, + 0xb400b3, 0xb600b5, 0xb800b7, 0xba00b9, 0xbc00bb, 0xbe00bd, + 0xc000bf, 0xc200c1, 0xc400c3, 0xc600c5, 0xc800c7, 0xca00c9, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xcc00cb, 0x0, 0xcd0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xcf00ce, 0xd00000, 0xd1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xd300d2, 0xd500d4, 0xd700d6, 0xd900d8, 0xdb00da, + 0xdd00dc, 0xd200de, 0xdf00d3, 0xe000d5, 0xe200e1, 0xe300d9, + 0xe500e4, 0xe700e6, 0xe900e8, 0xeb00ea, 0xed00ec, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xef00ee, 0xf100f0, 0xf300f2, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf500f4, 0xf700f6, + 0xf8, 0x0, 0xfa00f9, 0xfb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xfd00fc, 0xff00fe, 0x1010100, 0x1030102, 0x1050104, 0x1070106, + 0x1090108, 0x10b010a, 0x10c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x15, 0x692, 0x0, 0x90000, + 0x0, 0x30f0343, 0x11b20003, 0x0, 0x3140048, 0x787, 0x3c603ce, + 0x494, 0x570056d, 0x5860573, 0x5b005a6, 0x5f80000, 0x62e062b, + 0x6580631, 0x6e706e4, 0x6f906ea, 0x78f0000, 0x7a907a6, 0x7bf07ac, + 0x7e3, 0x8b10000, 0x8b708b4, 0x95f08cb, 0x0, 0x9ac09a9, 0x9c209af, + 0x9ec09e2, 0xa470000, 0xa890a86, 0xab30a8c, 0xb460b43, 0xb550b49, + 0xc410000, 0xc5e0c5b, 0xc740c61, 0xc98, 0xd680000, 0xd6e0d6b, + 0xe0c0d82, 0xe1b0000, 0x9c50589, 0x9c8058c, 0xa0a05ce, 0xa3b05ec, + 0xa3e05ef, 0xa4105f2, 0xa4405f5, 0xa6e061a, 0x0, 0xaa20647, + 0xaad0652, 0xab00655, 0xad00675, 0xab9065e, 0xafb069a, 0xb0106a0, + 0xb0406a3, 0xb0a06a9, 0xb1606ba, 0x0, 0xb4c06ed, 0xb4f06f0, + 0xb5206f3, 0xb6b070f, 0x6f6, 0xb3706d8, 0xb730717, 0xbae072e, + 0x7430000, 0x7500bcc, 0x7460bd9, 0x7400bcf, 0xbc9, 0x78c0000, + 0x79b0c3e, 0x7950c4d, 0xed70c47, 0x0, 0xc8307ce, 0xc8e07d9, + 0xca207ed, 0x0, 0xd070842, 0xd1d0858, 0xd0d0848, 0xd2b086c, + 0xd320873, 0xd49088a, 0xd380879, 0xd5d08a6, 0xd54089d, 0x0, + 0xd7108ba, 0xd7808c1, 0xd7f08c8, 0xd9808e1, 0xd9b08e4, 0xdc4090d, + 0xde9093f, 0xe0f0962, 0x979096e, 0x97f0e29, 0x60d0e2f, 0x8400614, + 0xcae07f9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f00000, 0xda7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x613060c, 0x7360a67, + 0xbb9073d, 0x7830780, 0x5b70c32, 0x70309f3, 0x7f00b5f, 0x8e70ca5, + 0x8d60d9e, 0x8d20d8d, 0x8da0d89, 0x8ce0d91, 0xd85, 0x9e505a9, + 0x9de05a2, 0xe630e5a, 0x0, 0xb0706a6, 0xba80728, 0xccc0817, + 0xccf081a, 0xecc0e7b, 0x6090b76, 0xa640610, 0xaf80697, 0x0, + 0xc3b0789, 0x9ef05b3, 0xe600e57, 0xe680e5d, 0x9f605ba, 0x9f905bd, + 0xabc0661, 0xabf0664, 0xb620706, 0xb650709, 0xca807f3, 0xcab07f6, + 0xd10084b, 0xd13084e, 0xda108ea, 0xda408ed, 0xd460887, 0xd5a08a3, + 0x0, 0xb1f06c3, 0x0, 0x0, 0x0, 0x9db059f, 0xac9066e, 0xc9b07e6, + 0xc7b07c6, 0xc9107dc, 0xc9407df, 0xe150968, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe9a0b0d, + 0xa11073e, 0xeb60eb4, 0xde10eb8, 0x695, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12000f, + 0x4b0024, 0x270006, 0x0, 0xa280e96, 0xb410840, 0xecf, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4001a, + 0x2b0000, 0x1d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xed5, 0x0, 0x0, 0x54, 0x0, 0x546, 0x0, 0x0, 0x1c0003, 0x7410ee8, + 0xf630f43, 0xfb4, 0xfed, 0x103c1016, 0x1185, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x101f0fbd, 0x10f5108f, + 0x11751119, 0x1213, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x120c117e, 0x120311d5, 0x124b, 0x116e10ea, + 0x10161011, 0x123c101f, 0x11ee, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x11f011ae, 0x11f8, 0x10f00fad, 0x0, + 0x100d0000, 0x0, 0x0, 0x0, 0x12b612b0, 0x12ad0000, 0x0, 0x12a40000, + 0x0, 0x0, 0x12c212ce, 0x12d7, 0x0, 0x0, 0x0, 0x0, 0x12c80000, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x130a0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x12f812f2, 0x12ef0000, 0x0, 0x132d0000, 0x0, 0x0, 0x13041310, + 0x131b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x13331330, 0x0, 0x0, 0x0, 0x0, 0x12b90000, 0x12fb, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x12e912a7, 0x12ec12aa, 0x0, 0x12f512b3, 0x0, + 0x13391336, 0x12fe12bc, 0x130112bf, 0x0, 0x130712c5, 0x130d12cb, + 0x131512d1, 0x0, 0x133f133c, 0x132a12e6, 0x131812d4, 0x131e12da, + 0x132112dd, 0x132412e0, 0x0, 0x132712e3, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x13420000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x13e913e6, 0x13ec178f, 0x17ca, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x13ef0000, 0x185b1792, 0x1811, 0x0, 0x0, + 0x0, 0x186d, 0x1852, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x186a0000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18820000, 0x0, + 0x0, 0x0, 0x188b0000, 0x0, 0x188e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18731870, + 0x18791876, 0x187f187c, 0x18881885, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x189a0000, 0x189d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18941891, + 0x18970000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x18ac0000, 0x0, 0x18af, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18a00000, 0x18a618a3, + 0x0, 0x18a9, 0x0, 0x0, 0x0, 0x0, 0x18bb, 0x18b80000, 0x18be, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18b518b2, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18c1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x18ca18c4, 0x18c7, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18cd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18d0, 0x0, 0x0, + 0x18da0000, 0x18dd, 0x18d618d3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18e618e0, 0x18e3, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18e9, 0x18ef18ec, 0x18f3, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18f60000, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x18ff0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18fc18f9, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1902, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x19070000, 0x0, 0x0, 0x0, 0x0, 0x190a0000, 0x0, + 0x0, 0x190d, 0x0, 0x19100000, 0x0, 0x0, 0x1913, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x19040000, 0x0, 0x0, 0x0, 0x0, 0x19160000, 0x19190000, + 0x19311935, 0x1938193c, 0x0, 0x0, 0x0, 0x191c0000, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x19220000, 0x0, 0x0, 0x0, 0x0, + 0x19250000, 0x0, 0x0, 0x1928, 0x0, 0x192b0000, 0x0, 0x0, 0x192e, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x191f0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x193f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1942, 0x0, 0x0, 0x0, 0x0, 0x1a38, 0x1a3b, 0x1a3e, + 0x1a41, 0x1a44, 0x0, 0x1a47, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1a4a0000, 0x1a4d0000, 0x0, 0x1a531a50, 0x1a560000, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe550568, 0x5d5, 0x62905e6, 0x6870e75, + 0x6cf06ac, 0x71a0607, 0x7230734, 0x77e, 0xe7e07a4, 0x82c06af, + 0x56b088d, 0x6920770, 0xe840e82, 0x9371a59, 0xa7d0a2e, 0xe8e0e8c, + 0x6020e90, 0xb790000, 0xe7105d3, 0xe880787, 0x1a5d1a5b, 0xba30cd3, + 0x1a610a24, 0x86a0ea4, 0x10ea1a63, 0x10ee10ec, 0x123e123c, + 0xa110ae0, 0x86a0a24, 0x10ec10ea, 0x123c11f0, 0x123e, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1313, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe860000, 0xe8a09a0, + 0xe900e66, 0xe920ad9, 0xe980e94, 0xe9e0e9c, 0x1a650ea0, 0xea20ed1, + 0xed31a67, 0xea60ea8, 0xeac0eaa, 0xeb00eae, 0xeba0eb2, 0xe790ebc, + 0xec00ebe, 0xec21a5f, 0x6110ec4, 0xec80ec6, 0x116e0eca, 0xa0705cb, + 0xa1305da, 0xa1605dd, 0xa1905e0, 0xa4a05fb, 0xa6b0617, 0xa71061d, + 0xa7a0626, 0xa740620, 0xa770623, 0xaa5064a, 0xaa9064e, 0xad30678, + 0xad6067b, 0xacc0671, 0xaef0684, 0xafe069d, 0xb1906bd, 0xb2206c6, + 0xb1c06c0, 0xb2506c9, 0xb2806cc, 0xb6e0712, 0xb5806fc, 0xba50725, + 0xbab072b, 0xbb10731, 0xbd20749, 0xbd5074c, 0xbdf0756, 0xbdc0753, + 0xc120772, 0xc150775, 0xc180778, 0xc440792, 0xc4a0798, 0xc5307a1, + 0xc50079e, 0xc7707c2, 0xc7f07ca, 0xc8607d1, 0xc8a07d5, 0xcec0835, + 0xcef0838, 0xd0a0845, 0xd160851, 0xd190854, 0xd20085b, 0xd350876, + 0xd3f0880, 0xd2e086f, 0xd3b087c, 0xd420883, 0xd4e089a, 0xd5708a0, + 0xd6308ac, 0xd6008a9, 0xdc1090a, 0xdca0913, 0xdc70910, 0xd7408bd, + 0xd7b08c4, 0xddb0924, 0xdde0927, 0xde30939, 0xde6093c, 0xdef0945, + 0xdec0942, 0xdf50948, 0xe010954, 0xe040957, 0xe18096b, 0xe2c097c, + 0xe350985, 0xe380988, 0xd510b2b, 0xe210df2, 0xd3509a6, 0x0, 0x0, + 0x9fc05c0, 0x9e905ad, 0x9b6057a, 0x9b20576, 0x9be0582, 0x9ba057e, + 0x9ff05c3, 0x9cf0593, 0x9cb058f, 0x9d7059b, 0x9d30597, 0xa0305c7, + 0xac20667, 0xab6065b, 0xa9f0644, 0xa930638, 0xa8f0634, 0xa9b0640, + 0xa97063c, 0xac5066a, 0xb5c0700, 0xb68070c, 0xcc50810, 0xc9f07ea, + 0xc6807b3, 0xc6407af, 0xc7007bb, 0xc6c07b7, 0xcc80813, 0xcb50800, + 0xcb107fc, 0xcbd0808, 0xcb90804, 0xcc1080c, 0xdbe0907, 0xd9508de, + 0xdae08f7, 0xdaa08f3, 0xdb608ff, 0xdb208fb, 0xdba0903, 0xe09095c, + 0xe240974, 0xe1e0971, 0xe120965, 0x0, 0x0, 0x0, 0x10be109c, + 0x10c1109f, 0x10ca10a8, 0x10d310b1, 0xf130ef1, 0xf160ef4, + 0xf1f0efd, 0xf280f06, 0x110310f8, 0x110610fb, 0x110a10ff, 0x0, + 0xf510f46, 0xf540f49, 0xf580f4d, 0x0, 0x11421120, 0x11451123, + 0x114e112c, 0x11571135, 0xf880f66, 0xf8b0f69, 0xf940f72, 0xf9d0f7b, + 0x119c118d, 0x119f1190, 0x11a31194, 0x11a71198, 0xfcf0fc0, + 0xfd20fc3, 0xfd60fc7, 0xfda0fcb, 0x11e311d8, 0x11e611db, + 0x11ea11df, 0x0, 0xffb0ff0, 0xffe0ff3, 0x10020ff7, 0x0, 0x122a121b, + 0x122d121e, 0x12311222, 0x12351226, 0x10220000, 0x10250000, + 0x10290000, 0x102d0000, 0x12741252, 0x12771255, 0x1280125e, + 0x12891267, 0x1061103f, 0x10641042, 0x106d104b, 0x10761054, + 0x108f1088, 0x10f510f2, 0x11191112, 0x11751172, 0x11d511d2, + 0x12031200, 0x124b1244, 0x0, 0x10dc10ba, 0x10c510a3, 0x10ce10ac, + 0x10d710b5, 0xf310f0f, 0xf1a0ef8, 0xf230f01, 0xf2c0f0a, 0x1160113e, + 0x11491127, 0x11521130, 0x115b1139, 0xfa60f84, 0xf8f0f6d, + 0xf980f76, 0xfa10f7f, 0x12921270, 0x127b1259, 0x12841262, + 0x128d126b, 0x107f105d, 0x10681046, 0x1071104f, 0x107a1058, + 0x10961099, 0x10e7108b, 0x1092, 0x10e310e0, 0xeeb0eee, 0xee80ee5, + 0x2a0f35, 0x2a1170, 0x200051, 0x116b1115, 0x111c, 0x11671164, + 0xf430f40, 0xf630f60, 0x2d0faa, 0x350031, 0x1178117b, 0x11851181, + 0x0, 0x118911ab, 0xfb70fba, 0xfb40fb1, 0x3c0000, 0x440040, + 0x12061209, 0x1213120f, 0x11f511f2, 0x12171239, 0x1019101c, + 0x10161013, 0x18100a, 0x995001c, 0x0, 0x129d1247, 0x124e, + 0x12991296, 0xfed0fea, 0x103c1039, 0x31083, 0x39, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x1, 0x0, 0x0, 0x1a690000, 0x0, 0x0, + 0x4e0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2fc02fa, 0x2ff, 0x0, 0x0, + 0x0, 0x10000, 0x0, 0x1a6f0000, 0x1a72, 0x1a7e1a7b, 0x0, 0x0, 0x8f, + 0xc, 0x0, 0x0, 0x0, 0x5630000, 0x920560, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1a760000, 0x0, 0x0, 0x0, 0x10000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xae00305, 0x0, 0x3740365, 0x3920383, 0x3b003a1, + 0x1aad02f4, 0xa10544, 0xb3b00a5, 0x3140305, 0x30f0343, 0x3740365, + 0x3920383, 0x3b003a1, 0x1aad02f4, 0xa10544, 0xa5, 0xa7d0692, + 0xb410787, 0xb0d0e8c, 0xa280b79, 0xb3b05d3, 0x8400cd3, 0xba3, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x83f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9a2099e, 0xe4d05e3, 0xa1e0000, 0xe770a22, + 0xe500000, 0x6ac0602, 0x6ac06ac, 0xe6d0b0d, 0x6cf06cf, 0xa280734, + 0x77e0000, 0x786, 0x6af0000, 0x82c083b, 0x82c082c, 0x0, 0x88f0863, + 0x897, 0x60a, 0x77c, 0x60a, 0x5b0071a, 0x5e305d5, 0xa7d0000, + 0x67e0629, 0x7230000, 0x13540787, 0x136a1362, 0xae0136f, 0x6800000, + 0x10ec11ee, 0x10060f3a, 0x1aab, 0x0, 0x5e60000, 0xa7d0a2e, + 0x73e0ae0, 0x0, 0x0, 0x0, 0x3e203da, 0x3ca03c1, 0x3d20455, + 0x4980459, 0x3d604cf, 0x3de04e7, 0x4eb049c, 0x3be0511, 0x6d106cf, + 0x6de06d4, 0x91806b2, 0x91f091b, 0x68206e1, 0x950094d, 0x5e30734, + 0x72305e6, 0xb300ae0, 0xb3d0b33, 0xdcf086a, 0xdd60dd2, 0xb410b40, + 0xdfd0dfa, 0x9a00a28, 0x5d30a2e, 0x0, 0x0, 0x0, 0x0, 0x30d0000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a8d1a86, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a92, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a950000, + 0x1a981a9b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1aa0, 0x0, 0x1aa50000, 0x0, 0x1aa8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1aaf, 0x1ab2, 0x0, 0x0, 0x1ab81ab5, + 0x1ac10000, 0x1ac4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1ac80000, + 0x0, 0x1acb, 0x1ace0000, 0x1ad10000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x556, 0x1ad7, 0x0, 0x0, 0x0, 0x0, + 0x1ad40000, 0x55b054a, 0x1add1ada, 0x0, 0x1ae31ae0, 0x0, + 0x1ae91ae6, 0x0, 0x0, 0x0, 0x1aef1aec, 0x0, 0x1afb1af8, 0x0, + 0x1b011afe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b0d1b0a, 0x1b131b10, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1af51af2, 0x1b071b04, 0x0, 0x0, + 0x0, 0x1b191b16, 0x1b1f1b1c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b350000, 0x1b37, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3430314, 0x365030f, 0x3830374, + 0x3a10392, 0x31c03b0, 0x342032f, 0x3640355, 0x3820373, 0x3a00391, + 0x3f703af, 0xd900a3, 0xe600e2, 0xee00ea, 0xf600f2, 0xa700fa, + 0xb100ac, 0xbb00b6, 0xc500c0, 0xcf00ca, 0xdd00d4, 0x3460319, + 0x3680359, 0x3860377, 0x3a40395, 0x31f03b3, 0x3450332, 0x3670358, + 0x3850376, 0x3a30394, 0x3fa03b2, 0x16a0166, 0x172016e, 0x17a0176, + 0x182017e, 0x18a0186, 0x192018e, 0x19a0196, 0x1a2019e, 0x1aa01a6, + 0x1b201ae, 0x1ba01b6, 0x1c201be, 0x1ca01c6, 0x5d50568, 0x5e605e3, + 0x67e0629, 0x6ac0687, 0x60706cf, 0x734071a, 0x77e0723, 0x6af07a4, + 0x82c083b, 0x88d085e, 0x6b2056b, 0x6820770, 0x60a095a, 0x9370692, + 0xa2e09a0, 0xad90a7d, 0xb0d0602, 0x73e0ae0, 0xa280b79, 0xb3b05d3, + 0xcd30787, 0xa1105d8, 0xba30840, 0x86a0a24, 0xb410de1, 0x6110695, + 0x305, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1abc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x54f0542, 0x552, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b2c, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6b2073e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b2f0000, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x227c0000, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x26b00000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1efb1ee9, 0x1f091f01, 0x1f131f0d, 0x1f1b1f17, + 0x1f4b1f21, 0x1f5f1f57, 0x1f6f1f67, 0x1f871f77, 0x1f8b1f89, + 0x1fb91fa5, 0x1fc51fc1, 0x1fcd1fc7, 0x1fdd1fdb, 0x1feb1fe9, + 0x1ff71fef, 0x204f2045, 0x2079206f, 0x207f207d, 0x20982087, + 0x20b420ae, 0x20ca20c4, 0x20ce20cc, 0x20dc20da, 0x20f820f2, + 0x210020fc, 0x210f2108, 0x21292113, 0x212f212b, 0x21352131, + 0x21412139, 0x218b214f, 0x21972195, 0x21d921d7, 0x21e521e3, + 0x21ed21e9, 0x32521f1, 0x3292211, 0x22602223, 0x226e2266, + 0x227a2274, 0x2280227e, 0x22842282, 0x22e22286, 0x230c2306, + 0x2310230e, 0x23162312, 0x23222318, 0x23342330, 0x23562354, + 0x235c235a, 0x23622360, 0x23762374, 0x23862384, 0x238a2388, + 0x23a62394, 0x23aa23a8, 0x23dc23bc, 0x23ee23de, 0x23fa23f6, + 0x241c240a, 0x2442243e, 0x2452244c, 0x245a2456, 0x245e245c, + 0x246c246a, 0x247e247a, 0x24842482, 0x248e248a, 0x24922490, + 0x24982496, 0x24f224e8, 0x250e250c, 0x25282512, 0x2530252c, + 0x25522534, 0x25582554, 0x255c255a, 0x25742572, 0x25822578, + 0x25922584, 0x25982596, 0x25ba25aa, 0x25c425c2, 0x25de25c8, + 0x25e825e0, 0x260025fa, 0x26142608, 0x261a2618, 0x261e261c, + 0x26262624, 0x2638262a, 0x263c263a, 0x264a2648, 0x2658264e, + 0x265c265a, 0x26622660, 0x26662664, 0x26702668, 0x267e267c, + 0x26862684, 0x268a2688, 0x2690268e, 0x26982692, 0x26a0269c, + 0x26a626a2, 0x26aa26a8, 0x26b226ae, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1b49, 0x1fcf1fcd, 0x1fd1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1b7e, 0x1b81, 0x1b84, 0x1b87, 0x1b8a, 0x1b8d, 0x1b90, 0x1b93, + 0x1b96, 0x1b99, 0x1b9c, 0x1b9f, 0x1ba20000, 0x1ba50000, 0x1ba80000, + 0x0, 0x0, 0x0, 0x1bae1bab, 0x1bb10000, 0x1bb4, 0x1bba1bb7, + 0x1bbd0000, 0x1bc0, 0x1bc91bc6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1b7b, 0x0, 0x0, 0x870000, 0x8a, 0x1bcc1bd3, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1c26, 0x1c43, 0x1bf6, 0x1c92, + 0x1c9b, 0x1caf, 0x1cbf, 0x1cca, 0x1ccf, 0x1cdc, 0x1ce1, 0x1ceb, + 0x1cf20000, 0x1cf70000, 0x1c100000, 0x0, 0x0, 0x0, 0x1d261d1d, + 0x1d3b0000, 0x1d42, 0x1d611d57, 0x1d760000, 0x1d7e, 0x1caa1da1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1c01, + 0x1e440000, 0x1e521e4d, 0x1e57, 0x0, 0x1ca11e60, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x19440000, 0x1a101949, 0x1a12194b, + 0x19501a14, 0x19571955, 0x1a181a16, 0x1a1c1a1a, 0x1a201a1e, + 0x195c19a6, 0x19661961, 0x196819b0, 0x196f196d, 0x19811977, + 0x198e1983, 0x19981993, 0x1947199d, 0x19da19d8, 0x19de19dc, + 0x19e219e0, 0x198c19e4, 0x19ea19e8, 0x19ee19ec, 0x19f21975, + 0x19f619f4, 0x19fa19f8, 0x19fe197f, 0x19a219d4, 0x1a2219a4, + 0x1a261a24, 0x1a2a1a28, 0x1a2e1a2c, 0x1a3019a8, 0x19aa1a32, + 0x19ae19ac, 0x19b419b2, 0x19b819b6, 0x19bc19ba, 0x19c019be, + 0x19c419c2, 0x19c819c6, 0x19cc19ca, 0x1a361a34, 0x19d019ce, + 0x1a0019d2, 0x1a041a02, 0x1a081a06, 0x1a0c1a0a, 0x1a0e, 0x0, + 0x1f171ee9, 0x20471eef, 0x1efd1ef1, 0x23641ef3, 0x1ef71f0d, + 0x208c1eeb, 0x1f212051, 0x1d701ce, 0x1e901e0, 0x1fb01f2, 0x20d0204, + 0x2330225, 0x245023c, 0x257024e, 0x1db01d2, 0x1ed01e4, 0x1ff01f6, + 0x2110208, 0x2370229, 0x2490240, 0x25b0252, 0x216022e, 0x21e, + 0x2700260, 0x2a00268, 0x2880274, 0x2840264, 0x290026c, 0x2c402b0, + 0x2b802c0, 0x2a402ec, 0x2bc02ac, 0x2d002b4, 0x2c80298, 0x2d402e4, + 0x278028c, 0x2a8029c, 0x27c02cc, 0x29402e8, 0x28002d8, 0x2e002dc, + 0x21112021, 0x23fe21e3, 0x0, 0x0, 0x0, 0x0, 0x406082e, 0x41c0411, + 0x4320427, 0x4400439, 0x44e0447, 0x475046e, 0x47f047c, 0x4850482, + 0x194b1944, 0x19571950, 0x1961195c, 0x196f1968, 0x19831977, + 0x1993198e, 0x199d1998, 0x194d1946, 0x19591952, 0x1963195e, + 0x1971196a, 0x19851979, 0x19951990, 0x199f199a, 0x197c1988, 0x1974, + 0x1f171ee9, 0x20471eef, 0x1f611f19, 0x1f5f1eed, 0x1fcd1f0f, + 0x22e20329, 0x22232286, 0x204f25c8, 0x223b0325, 0x2240221b, + 0x231c2007, 0x23ca255e, 0x23e21fab, 0x20982368, 0x1f4925a2, + 0x22961fdf, 0x1f2b262c, 0x208a1f73, 0x1efd1ef1, 0x20fa1ef3, + 0x1fc92001, 0x20b220b8, 0x1f292390, 0x1fd72568, 0x4882083, + 0x48e048b, 0x4b10491, 0x4b704b4, 0x4bd04ba, 0x4c304c0, 0x4c904c6, + 0x4e404cc, 0x34e033b, 0x4d604a3, 0x50304f2, 0x5290518, 0x327053a, + 0x34d033a, 0xa8206b4, 0x7390a7f, 0x1bf11bd8, 0x1c0a1bff, + 0x1c241c1a, 0x1c731c41, 0x1c991c90, 0x1cbd1cad, 0x1ccd1c1e, + 0x1cdf1cda, 0x1cf01bfb, 0x1bde1cf5, 0x1d0f1ca6, 0x1c8e1d11, + 0x1d1b1d0d, 0x1d551d39, 0x1d9f1d74, 0x1ddc1c31, 0x1def1c22, + 0x1e041e00, 0x1e191e11, 0x1c351e1b, 0x1e341bed, 0x1e421c5d, + 0x1e501e4b, 0x1e55, 0x1be01bda, 0x1beb1be5, 0x1bf91bf3, 0x1c0c1c04, + 0x1c1c1c13, 0x1c331c20, 0x1c3c1c37, 0x1c2e1c29, 0x1c4b1c46, + 0x1c501c57, 0x1c5f1c5c, 0x1c6d1c66, 0x1c7d1c61, 0x1c8b1c84, + 0x1ca41c95, 0x1cb21ca8, 0x1cc21cb7, 0x1cd61cd2, 0x1cfa1ce4, + 0x1c811d03, 0x1d171d0c, 0x1d291d35, 0x1d201d30, 0x1d4c1d45, + 0x1d3e1d51, 0x1d6b1d64, 0x1d701d5a, 0x1d811d95, 0x1d9b1d85, + 0x1d8f1d8a, 0x1dac1d79, 0x1db81da4, 0x1dbb1db2, 0x1dc51dbf, + 0x1dce1dca, 0x1dd61dd2, 0x1de31dde, 0x1df11de6, 0x1c681df5, + 0x1e0b1e06, 0x1e1f1e13, 0x1e291e24, 0x1e361e2e, 0x1c6f1e39, + 0x33f0311, 0x3610352, 0x37f0370, 0x39d038e, 0x3bb03ac, 0x33e032b, + 0x3600351, 0x37e036f, 0x39c038d, 0x3ba03ab, 0x40d0402, 0x4230418, + 0xb0f042e, 0x56a0a53, 0xc580a0f, 0xa590ce6, 0xa600a5c, 0x210a06db, + 0x20892200, 0x223d21f9, 0xc260cda, 0xbea11b4, 0x71c0b7b, 0x689075b, + 0xb8c0a26, 0xc290cdd, 0x11c011b7, 0x6010bf6, 0xb7e068d, 0x68c0764, + 0x11c30893, 0xa560bfd, 0xaec0b94, 0x11c60c35, 0xa300c00, 0xc030b97, + 0xa340a33, 0xc070b9a, 0xa380a37, 0xc1b0b9e, 0x6910c1f, 0x7680b82, + 0xcf60690, 0xd000cfa, 0xc380ce9, 0xc0f11c9, 0xc2c0ce0, 0xbed11ba, + 0x76c0b86, 0xc2f0ce3, 0xbf011bd, 0x76f0b89, 0x77b0bb4, 0x5d70999, + 0xa2d0a2a, 0x5e805ff, 0x6940a50, 0x6ae0b13, 0x71f0b3a, 0xba20722, + 0xbbf0bbc, 0xbc60bc2, 0xbf90bf3, 0x8200c0b, 0x8230cd5, 0xd25082b, + 0x9360869, 0x5d1092a, 0x34a0337, 0x36c035d, 0x38a037b, 0x3a80399, + 0x32303b7, 0x3490336, 0x36b035c, 0x389037a, 0x3a70398, 0x3fe03b6, + 0x4140409, 0x42a041f, 0x43c0435, 0x44a0443, 0x4710451, 0xaf40478, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x26b4, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xe730e6b, 0x0, 0x0, 0x0, 0x22132556, 0x256a2584, + 0x1eff22c6, 0x26ae1ff9, 0x209226ae, 0x202b25c8, 0x21872090, + 0x244a2382, 0x250424e6, 0x25a8251e, 0x229a2254, 0x233c22f0, + 0x25bc24ca, 0x1f112652, 0x225e1fe3, 0x24e42302, 0x20e6267a, + 0x24dc22d6, 0x21a12526, 0x250a2478, 0x221d211f, 0x232822a6, + 0x1f3125ae, 0x1fb31f7d, 0x225a21d5, 0x23922300, 0x24e02456, + 0x257e24ec, 0x266a2610, 0x23b02678, 0x242c23d0, 0x25d624bc, + 0x2540267e, 0x212d206d, 0x24682408, 0x23b4231a, 0x260c2566, + 0x20d4206b, 0x22b02256, 0x242422ca, 0x25ec2438, 0x246e1fb1, + 0x1f811f83, 0x242e23e6, 0x25f024c8, 0x21a3254e, 0x25462254, + 0x20be1f05, 0x23322159, 0x1fc32372, 0x1f3923b8, 0x1ef5214b, + 0x21e12290, 0x1fed2422, 0x23982063, 0x253824cc, 0x25962276, + 0x21ab228c, 0x21bb24a8, 0x1f1f2370, 0x1f7f1f5d, 0x24182244, + 0x253e2494, 0x1fb725c6, 0x20982011, 0x21ef2127, 0x23ba22d8, + 0x265625e4, 0x268c2680, 0x220f1fa5, 0x2590226c, 0x217b210d, + 0x21d12189, 0x22f622d0, 0x23e0234e, 0x24642432, 0x24d02588, + 0x25d8259c, 0x1fa71f91, 0x22ee201b, 0x25382514, 0x2155211d, + 0x227221b7, 0x232c2406, 0x20491f27, 0x20f020be, 0x233a215b, + 0x24502348, 0x25ca2460, 0x2612260a, 0x1f332630, 0x25c023da, + 0x216725fe, 0x1f451f15, 0x20d020c0, 0x225421e7, 0x238022fc, + 0x25a624d6, 0x220726aa, 0x1fa325ea, 0x2233222d, 0x22be22a2, + 0x236e2340, 0x242023ae, 0x1f612636, 0x25f22191, 0x20e21f3d, + 0x258a22b2, 0x216b2143, 0x23322237, 0x1f9525f6, 0x20d82009, + 0x222521fc, 0x2294224a, 0x2378233e, 0x25162446, 0x25c4251c, + 0x1fcb2604, 0x200b22c0, 0x235022fe, 0x25f824de, 0x2682266e, + 0x22ae2231, 0x23f6247c, 0x240e23fc, 0x22ea2326, 0x1f23254c, + 0x1f9724b0, 0x21151f8f, 0x241421a5, 0x229c20b6, 0x258e220d, + 0x25ee250e, 0x2123252c, 0x20371f4d, 0x0, 0x2061, 0x2205, + 0x1f850000, 0x238c232a, 0x23cc23be, 0x23d823ce, 0x24102616, 0x2452, + 0x24e2, 0x2544, 0x259e0000, 0x25b4, 0x0, 0x26422640, 0x26762644, + 0x25fc25b0, 0x1f471f35, 0x1faf1f51, 0x1fd51fb5, 0x203d202f, + 0x205f2041, 0x20d62065, 0x216120da, 0x21792175, 0x21db2185, + 0x220921f3, 0x22a82246, 0x22ce22b6, 0x230822f8, 0x23b22342, + 0x23c42240, 0x23c623c2, 0x23ca23c8, 0x23d623d4, 0x23f223e8, + 0x24322400, 0x243a2436, 0x24582444, 0x249a2480, 0x24ce249a, + 0x252e2522, 0x254a2548, 0x256e256c, 0x259e259a, 0x26282606, + 0x215d2634, 0x248c274b, 0x0, 0x1f7b1ef9, 0x1f2f1f5b, 0x1f651f4f, + 0x1fbb1fad, 0x2025202f, 0x203b202d, 0x20692061, 0x2094208e, + 0x20aa20a2, 0x21252121, 0x214d213d, 0x21712165, 0x21792169, + 0x21852173, 0x21bf2193, 0x21c921c5, 0x220521dd, 0x221f221d, + 0x226e2229, 0x22a22276, 0x22c422c8, 0x22dc22ce, 0x23a422f8, + 0x2324230a, 0x234a232a, 0x236a2358, 0x237e237c, 0x238e238c, + 0x23a02396, 0x23b6239e, 0x240023f4, 0x2428240c, 0x24402432, + 0x24b22458, 0x250024c6, 0x252a2524, 0x253a252e, 0x253c2544, + 0x25462548, 0x254a2542, 0x256e2550, 0x25a4258c, 0x25ce25be, + 0x260625f4, 0x26202616, 0x262e2628, 0x265e2634, 0x272126ae, + 0x2733271f, 0x1ea11e8d, 0x27671ea3, 0x27a92779, 0x26ac26a4, 0x0, + 0x0, 0x0, 0xadf0adb, 0xade0ae3, 0xd280ae2, 0xd28, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x134e0000, 0x13481345, 0x134b1351, 0x0, 0x0, 0x13850000, + 0x13d20000, 0x135413a6, 0x1374136f, 0x1360138e, 0x13b7139b, + 0x2f413cd, 0x13ca13c7, 0x13c313bf, 0x13591356, 0x1364135c, + 0x1371136c, 0x137c1376, 0x137f, 0x13881382, 0x1390138b, 0x1398, + 0x139d, 0x13a313a0, 0x13a80000, 0x13ab, 0x13b413b1, 0x13bc13b9, + 0x137913cf, 0x13931367, 0x135f13ae, 0x18181818, 0x181e181e, + 0x181e181e, 0x18201820, 0x18201820, 0x18241824, 0x18241824, + 0x181c181c, 0x181c181c, 0x18221822, 0x18221822, 0x181a181a, + 0x181a181a, 0x183c183c, 0x183c183c, 0x183e183e, 0x183e183e, + 0x18281828, 0x18281828, 0x18261826, 0x18261826, 0x182a182a, + 0x182a182a, 0x182c182c, 0x182c182c, 0x18321832, 0x18301830, + 0x18341834, 0x182e182e, 0x18381838, 0x18361836, 0x18401840, + 0x18401840, 0x18441844, 0x18441844, 0x18481848, 0x18481848, + 0x18461846, 0x18461846, 0x184a184a, 0x184c184c, 0x184c184c, + 0x186d186d, 0x18501850, 0x18501850, 0x184e184e, 0x184e184e, + 0x15911591, 0x186a186a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18420000, 0x18421842, + 0x18031842, 0x17ff1803, 0x180717ff, 0x185b1807, 0x18621862, + 0x18551855, 0x18601860, 0x180b180b, 0x180b180b, 0x14151415, + 0x17cd17cd, 0x180d180d, 0x17f117f1, 0x18011801, 0x17fd17fd, + 0x18051805, 0x18091809, 0x17f51809, 0x17f517f5, 0x18641864, + 0x18641864, 0x17d517d1, 0x17f517e5, 0x13f417f9, 0x13fe13f7, + 0x1414140b, 0x141e1417, 0x1438142d, 0x146a144d, 0x1472146d, + 0x1484147b, 0x148c1487, 0x14311422, 0x14d11435, 0x143c14d4, + 0x150514fa, 0x151a150c, 0x15931562, 0x15a515a2, 0x15ba15b0, + 0x15c815c5, 0x15e415df, 0x16071575, 0x163f160a, 0x16451642, + 0x1653164c, 0x165b1656, 0x16711662, 0x16791674, 0x167f167c, + 0x16851682, 0x16931688, 0x16aa1696, 0x16c816b9, 0x1579158c, + 0x145116e0, 0x14591455, 0x145d1526, 0x172d1461, 0x174f1740, + 0x17691758, 0x1771176c, 0x177f1774, 0x179c1782, 0x17aa17a3, + 0x17c417b3, 0x14e417c7, 0x179714ee, 0x64005d, 0x72006b, 0x800079, + 0x17e117dd, 0x17e917e5, 0x17f917f5, 0x140813db, 0x140e140b, + 0x14171414, 0x144a1447, 0x1464144d, 0x146d146a, 0x14781475, + 0x147e147b, 0x14871484, 0x16561653, 0x16741671, 0x16851679, + 0x16931688, 0x158c1696, 0x16e01579, 0x152616e5, 0x17551752, + 0x17631758, 0x176c1769, 0x17ad1797, 0x17b317b0, 0x17c417be, + 0x17d117c7, 0x17d917d5, 0x17ed17e5, 0x13f713f4, 0x140b13fe, + 0x141e1411, 0x1438142d, 0x1467144d, 0x148c147b, 0x14311422, + 0x14d11435, 0x14fa143c, 0x150c1505, 0x1562151a, 0x1593156d, + 0x15a515a2, 0x15ba15b0, 0x15df15c5, 0x157515e4, 0x160a1607, + 0x1642163f, 0x164c1645, 0x1662165b, 0x167f167c, 0x16851682, + 0x16aa1688, 0x16c816b9, 0x13e0158c, 0x14551451, 0x15261459, + 0x1740172d, 0x1758174f, 0x17711766, 0x17851774, 0x17a3179c, + 0x17b317aa, 0x17e515ed, 0x140b17ed, 0x144d1411, 0x147b1467, + 0x151a1481, 0x154c1529, 0x16851557, 0x158c1688, 0x17661758, + 0x15ed17b3, 0x162c1625, 0x15d71633, 0x15ff15da, 0x16191602, + 0x152c161c, 0x155a152f, 0x1490155d, 0x142613fb, 0x1440142a, + 0x159a1402, 0x15bd159d, 0x153415c0, 0x1546153b, 0x1549154c, + 0x15701517, 0x15d715b7, 0x15ff15da, 0x16191602, 0x152c161c, + 0x155a152f, 0x1490155d, 0x142613fb, 0x1440142a, 0x159a1402, + 0x15bd159d, 0x153415c0, 0x1546153b, 0x1549154c, 0x15701517, + 0x153415b7, 0x1546153b, 0x1529154c, 0x15c81557, 0x150514fa, + 0x1534150c, 0x1546153b, 0x15df15c8, 0x13e313e3, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x14301421, 0x14341430, 0x1450143b, + 0x14581454, 0x14a314a3, 0x14c114c5, 0x14fd1508, 0x15211501, + 0x151d1521, 0x15251525, 0x15651565, 0x153e1596, 0x1537153e, + 0x154f154f, 0x15531553, 0x15b315a8, 0x15cb15b3, 0x15cf15cb, + 0x15e715d3, 0x15f315f3, 0x160d15f7, 0x16111615, 0x16481648, + 0x16691665, 0x16c416bc, 0x16ad16c0, 0x16cb16ad, 0x16d216cb, + 0x16fe16d2, 0x170b1702, 0x16f316eb, 0x17161712, 0x0, 0x177716ef, + 0x1743177b, 0x17341747, 0x17381734, 0x175b175f, 0x17b617b6, + 0x14291401, 0x14431425, 0x1460143f, 0x14ab145c, 0x14a7148f, + 0x1569150f, 0x15ac1542, 0x16d616b5, 0x179f17a6, 0x172117ba, + 0x174b166d, 0x16bc1665, 0x168f15fb, 0x171a1730, 0x168b16b1, + 0x173016b1, 0x14ba1493, 0x164f16f7, 0x168b13fa, 0x159615e7, + 0x173c1513, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x165e158f, + 0x13d913de, 0x15731706, 0x15eb14e9, 0x1578158a, 0x1497157c, 0x14f1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b3102f6, 0x5401b33, + 0x8d0546, 0x1b770093, 0x2ff1b79, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1a6d02fc, 0x9931a6b, 0xa10993, 0xe3b00a5, + 0x1b4b0e3f, 0x1b451b4f, 0x1b391b47, 0x1b351b3b, 0x1b3d1b37, + 0x1b411b3f, 0x1b43, 0x98b0000, 0xc098f, 0xc000c, 0x993000c, + 0x9930993, 0x1b3102f6, 0x2fa, 0x5400546, 0x8d0093, 0xa11a6d, + 0xe3b00a5, 0x1b4b0e3f, 0x971b4f, 0x2f2009d, 0x2f802f4, 0x5590548, + 0x544, 0x99098d, 0x566009b, 0x0, 0x0, 0x161f0057, 0x5a, 0x61, + 0x16220068, 0x1629006f, 0x16300076, 0x1637007d, 0x163a0084, + 0x13e613d5, 0x13e913e6, 0x178f13e9, 0x13ec178f, 0x17ca13ec, + 0x17ca17ca, 0x13d717ca, 0x13f213d7, 0x13f213f2, 0x141a13f2, + 0x141c141a, 0x141c141c, 0x1470141c, 0x14701470, 0x13f51470, + 0x13f513f5, 0x13f813f5, 0x13f813f8, 0x13ff13f8, 0x13ff13ff, + 0x14e013ff, 0x14e214e0, 0x13dc14e2, 0x140913dc, 0x14f81409, + 0x14f814f8, 0x153214f8, 0x15321532, 0x15601532, 0x15601560, + 0x15a01560, 0x15a015a0, 0x15c315a0, 0x15c315c3, 0x15dd15c3, + 0x15dd15dd, 0x15e215dd, 0x15e215e2, 0x160515e2, 0x16051605, + 0x163d1605, 0x163d163d, 0x1659163d, 0x16591659, 0x16771659, + 0x16771677, 0x14ec1677, 0x14ec14ec, 0x140c14ec, 0x140c140c, + 0x140f140c, 0x140f140f, 0x13e1140f, 0x13e113e1, 0x178813e1, + 0x14151788, 0x13fc1415, 0x13fc13fc, 0x169e13fc, 0x16a2169e, + 0x16a616a2, 0x169b16a6, 0x169b, 0x0, 0x8d0000, 0x970095, 0x9b0099, + 0x9f009d, 0xa500a1, 0x2f402f2, 0x2f802f6, 0x30302fa, 0x3140305, + 0x30f0343, 0x3740365, 0x3920383, 0x3b003a1, 0x5460540, 0x5440548, + 0x930559, 0x5680566, 0x5e305d5, 0x62905e6, 0x687067e, 0x6cf06ac, + 0x71a0607, 0x7230734, 0x7a4077e, 0x83b06af, 0x85e082c, 0x56b088d, + 0x77006b2, 0x95a0682, 0x98b060a, 0x98f098d, 0x9930991, 0x6920995, + 0x9a00937, 0xa7d0a2e, 0x6020ad9, 0xae00b0d, 0xb79073e, 0x5d30a28, + 0x7870b3b, 0x5d80cd3, 0x8400a11, 0xa240ba3, 0xde1086a, 0x6950b41, + 0xe3b0611, 0xe3f0e3d, 0x1b280e41, 0x1b331b2a, 0x1b3f1b3d, + 0x1e5c1b31, 0x1bd61e55, 0x1bfd1bef, 0x1c181c08, 0x1e0f1e02, + 0x1cee1e17, 0x1bd81c16, 0x1bff1bf1, 0x1c1a1c0a, 0x1c411c24, + 0x1c901c73, 0x1cad1c99, 0x1c1e1cbd, 0x1cda1ccd, 0x1bfb1cdf, + 0x1cf51cf0, 0x1ca61bde, 0x1d111d0f, 0x1d0d1c8e, 0x1d391d1b, + 0x1d741d55, 0x1c311d9f, 0x1c221ddc, 0x1e001def, 0x1e111e04, + 0x1e1b1e19, 0x1bed1c35, 0x1c5d1e34, 0x1c061e42, 0x8b0088, + 0x194419d4, 0x1a101949, 0x1a12194b, 0x19501a14, 0x19571955, + 0x1a181a16, 0x1a1c1a1a, 0x1a201a1e, 0x195c19a6, 0x19661961, + 0x196819b0, 0x196f196d, 0x19811977, 0x198e1983, 0x19981993, 0x199d, + 0x0, 0x19d81947, 0x19dc19da, 0x19e019de, 0x0, 0x19e419e2, + 0x19e8198c, 0x19ec19ea, 0x0, 0x197519ee, 0x19f419f2, 0x19f819f6, + 0x0, 0x197f19fa, 0x19fe, 0x0, 0xe450e43, 0x90e4b, 0xe470e49, + 0x1a82, 0x1a841b22, 0x1a8b1a89, 0x1b241a90, 0x1b26, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x26b6, 0x26b9, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x26bc0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x26c226bf, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x26c826c5, 0x26cf26cb, 0x26d726d3, + 0x26db, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x26df0000, 0x26e226ea, 0x26e626ed, 0x26f1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x5d50568, 0x5e605e3, 0x67e0629, 0x6ac0687, 0x60706cf, 0x734071a, + 0x77e0723, 0x6af07a4, 0x82c083b, 0x88d085e, 0x6b2056b, 0x6820770, + 0x60a095a, 0x9370692, 0xa2e09a0, 0xad90a7d, 0xb0d0602, 0x73e0ae0, + 0xa280b79, 0xb3b05d3, 0xcd30787, 0xa1105d8, 0xba30840, 0x86a0a24, + 0xb410de1, 0x6110695, 0x5d50568, 0x5e605e3, 0x67e0629, 0x6ac0687, + 0x60706cf, 0x734071a, 0x77e0723, 0x6af07a4, 0x82c083b, 0x88d085e, + 0x6b2056b, 0x6820770, 0x60a095a, 0x9370692, 0xa2e09a0, 0xad90a7d, + 0x602, 0x73e0ae0, 0xa280b79, 0xb3b05d3, 0xcd30787, 0xa1105d8, + 0xba30840, 0x86a0a24, 0xb410de1, 0x6110695, 0x5d50568, 0x5e605e3, + 0x67e0629, 0x6ac0687, 0x60706cf, 0x734071a, 0x77e0723, 0x6af07a4, + 0x82c083b, 0x88d085e, 0x6b2056b, 0x6820770, 0x60a095a, 0x9370692, + 0xa2e09a0, 0xad90a7d, 0xb0d0602, 0x73e0ae0, 0xa280b79, 0xb3b05d3, + 0xcd30787, 0xa1105d8, 0xba30840, 0x86a0a24, 0xb410de1, 0x6110695, + 0x568, 0x5e605e3, 0x0, 0x687, 0x6070000, 0x71a, 0x77e0000, + 0x6af07a4, 0x83b, 0x88d085e, 0x6b2056b, 0x6820770, 0x60a095a, + 0x9370692, 0xa2e09a0, 0xad90000, 0xb0d0000, 0x73e0ae0, 0xa280b79, + 0xb3b05d3, 0xcd30000, 0xa1105d8, 0xba30840, 0x86a0a24, 0xb410de1, + 0x6110695, 0x5d50568, 0x5e605e3, 0x67e0629, 0x6ac0687, 0x60706cf, + 0x734071a, 0x77e0723, 0x6af07a4, 0x82c083b, 0x88d085e, 0x6b2056b, + 0x6820770, 0x60a095a, 0x9370692, 0xa2e09a0, 0xad90a7d, 0xb0d0602, + 0x73e0ae0, 0xa280b79, 0xb3b05d3, 0xcd30787, 0xa1105d8, 0xba30840, + 0x86a0a24, 0xb410de1, 0x6110695, 0x5d50568, 0x5e60000, 0x67e0629, + 0x687, 0x6070000, 0x734071a, 0x77e0723, 0x6af07a4, 0x83b, + 0x88d085e, 0x6b2056b, 0x6820770, 0x95a, 0x9370692, 0xa2e09a0, + 0xad90a7d, 0xb0d0602, 0x73e0ae0, 0xa280b79, 0xb3b05d3, 0xcd30787, + 0xa1105d8, 0xba30840, 0x86a0a24, 0xb410de1, 0x6110695, 0x5d50568, + 0x5e60000, 0x67e0629, 0x687, 0x60706cf, 0x734071a, 0x723, 0x7a4, + 0x0, 0x88d085e, 0x6b2056b, 0x6820770, 0x95a, 0x9370692, 0xa2e09a0, + 0xad90a7d, 0xb0d0602, 0x73e0ae0, 0xa280b79, 0xb3b05d3, 0xcd30787, + 0xa1105d8, 0xba30840, 0x86a0a24, 0xb410de1, 0x6110695, 0x5d50568, + 0x5e605e3, 0x67e0629, 0x6ac0687, 0x60706cf, 0x734071a, 0x77e0723, + 0x6af07a4, 0x82c083b, 0x88d085e, 0x6b2056b, 0x6820770, 0x60a095a, + 0x9370692, 0xa2e09a0, 0xad90a7d, 0xb0d0602, 0x73e0ae0, 0xa280b79, + 0xb3b05d3, 0xcd30787, 0xa1105d8, 0xba30840, 0x86a0a24, 0xb410de1, + 0x6110695, 0x77e0723, 0x6af07a4, 0x82c083b, 0x88d085e, 0x6b2056b, + 0x6820770, 0x60a095a, 0x9370692, 0xa2e09a0, 0xad90a7d, 0xb0d0602, + 0x73e0ae0, 0xa280b79, 0xb3b05d3, 0xcd30787, 0xa1105d8, 0x60a095a, + 0x9370692, 0xa2e09a0, 0xad90a7d, 0xb0d0602, 0x73e0ae0, 0xa280b79, + 0xb3b05d3, 0xcd30787, 0xa1105d8, 0xba30840, 0x86a0a24, 0xb410de1, + 0x6110695, 0x5d50568, 0x5e605e3, 0x67e0629, 0x6ac0687, 0x60706cf, + 0x734071a, 0x77e0723, 0x6af07a4, 0x82c083b, 0x88d085e, 0x6b2056b, + 0x6820770, 0x60a095a, 0x9370692, 0xa2e09a0, 0xad90a7d, 0xb0d0602, + 0x73e0ae0, 0xa280b79, 0xb3b05d3, 0xcd30787, 0xa1105d8, 0xba30840, + 0x86a0a24, 0xb410de1, 0x6110695, 0x5d50568, 0x5e605e3, 0x67e0629, + 0x6ac0687, 0x60706cf, 0x734071a, 0x77e0723, 0x6af07a4, 0xb410de1, + 0x6110695, 0xe800e6f, 0x0, 0xf380ee3, 0xf3c0f3a, 0xf5c0f3e, + 0xfad0f5e, 0xfde0faf, 0xfe20fe0, 0xfe60fe4, 0x10060fe8, 0xfad1008, + 0x100f100d, 0x10311011, 0x10351033, 0x1aa3077c, 0x10ea1086, + 0x10ee10ec, 0x110e10f0, 0x116e1110, 0x11ae1170, 0x11b211b0, + 0x11ce11cc, 0x11ee11d0, 0x11f811f0, 0x11fc11fa, 0x123c11fe, + 0x1240123e, 0x1a9e1242, 0x116e10f0, 0x123c11ae, 0x11ee11f0, + 0xf380ee3, 0xf3c0f3a, 0xf5c0f3e, 0xfad0f5e, 0xfde0faf, 0xfe20fe0, + 0xfe60fe4, 0x10060fe8, 0xfad1008, 0x100f100d, 0x10311011, + 0x10351033, 0x1aa3077c, 0x10ea1086, 0x10ee10ec, 0x110e10f0, + 0x116e1110, 0x11ae1170, 0x11b211b0, 0x11ce11cc, 0x11ee11d0, + 0x11f811f0, 0x11fc11fa, 0x123c11fe, 0x1240123e, 0x1a9e1242, + 0x116e10f0, 0x123c11ae, 0x11ee11f0, 0xf380ee3, 0xf3c0f3a, + 0xf5c0f3e, 0xfad0f5e, 0xfde0faf, 0xfe20fe0, 0xfe60fe4, 0x10060fe8, + 0xfad1008, 0x100f100d, 0x10311011, 0x10351033, 0x1aa3077c, + 0x10ea1086, 0x10ee10ec, 0x110e10f0, 0x116e1110, 0x11ae1170, + 0x11b211b0, 0x11ce11cc, 0x11ee11d0, 0x11f811f0, 0x11fc11fa, + 0x123c11fe, 0x1240123e, 0x1a9e1242, 0x116e10f0, 0x123c11ae, + 0x11ee11f0, 0xf380ee3, 0xf3c0f3a, 0xf5c0f3e, 0xfad0f5e, 0xfde0faf, + 0xfe20fe0, 0xfe60fe4, 0x10060fe8, 0xfad1008, 0x100f100d, + 0x10311011, 0x10351033, 0x1aa3077c, 0x10ea1086, 0x10ee10ec, + 0x110e10f0, 0x116e1110, 0x11ae1170, 0x11b211b0, 0x11ce11cc, + 0x11ee11d0, 0x11f811f0, 0x11fc11fa, 0x123c11fe, 0x1240123e, + 0x1a9e1242, 0x116e10f0, 0x123c11ae, 0x11ee11f0, 0xf380ee3, + 0xf3c0f3a, 0xf5c0f3e, 0xfad0f5e, 0xfde0faf, 0xfe20fe0, 0xfe60fe4, + 0x10060fe8, 0xfad1008, 0x100f100d, 0x10311011, 0x10351033, + 0x1aa3077c, 0x10ea1086, 0x10ee10ec, 0x110e10f0, 0x116e1110, + 0x11ae1170, 0x11b211b0, 0x11ce11cc, 0x11ee11d0, 0x11f811f0, + 0x11fc11fa, 0x123c11fe, 0x1240123e, 0x1a9e1242, 0x116e10f0, + 0x123c11ae, 0x11ee11f0, 0x12a212a0, 0x0, 0x3140305, 0x30f0343, + 0x3740365, 0x3920383, 0x3b003a1, 0x3140305, 0x30f0343, 0x3740365, + 0x3920383, 0x3b003a1, 0x3140305, 0x30f0343, 0x3740365, 0x3920383, + 0x3b003a1, 0x3140305, 0x30f0343, 0x3740365, 0x3920383, 0x3b003a1, + 0x3140305, 0x30f0343, 0x3740365, 0x3920383, 0x3b003a1, 0x13f213d7, + 0x14e013f5, 0x17880000, 0x13f81409, 0x13fc15c3, 0x14ec1677, + 0x140f140c, 0x15e214f8, 0x1560163d, 0x13dc1659, 0x141c1532, + 0x13ff1470, 0x15a014e2, 0x160515dd, 0x184a1814, 0x1816183a, + 0x13f20000, 0x13f5, 0x13e1, 0x13f80000, 0x13fc0000, 0x14ec1677, + 0x140f140c, 0x15e214f8, 0x1560163d, 0x1659, 0x141c1532, 0x13ff1470, + 0x15a00000, 0x16050000, 0x0, 0x0, 0x0, 0x13f5, 0x0, 0x13f80000, + 0x13fc0000, 0x14ec0000, 0x140f0000, 0x15e214f8, 0x15600000, 0x1659, + 0x1532, 0x13ff0000, 0x15a00000, 0x16050000, 0x184a0000, 0x18160000, + 0x13f20000, 0x13f5, 0x13e1, 0x13f80000, 0x13fc15c3, 0x1677, + 0x140f140c, 0x15e214f8, 0x1560163d, 0x1659, 0x141c1532, 0x13ff1470, + 0x15a00000, 0x160515dd, 0x1814, 0x183a, 0x13f213d7, 0x14e013f5, + 0x178813e1, 0x13f81409, 0x13fc15c3, 0x14ec0000, 0x140f140c, + 0x15e214f8, 0x1560163d, 0x13dc1659, 0x141c1532, 0x13ff1470, + 0x15a014e2, 0x160515dd, 0x0, 0x0, 0x13f20000, 0x14e013f5, + 0x17880000, 0x13f81409, 0x13fc15c3, 0x14ec0000, 0x140f140c, + 0x15e214f8, 0x1560163d, 0x13dc1659, 0x141c1532, 0x13ff1470, + 0x15a014e2, 0x160515dd, 0x0, 0x0, 0x307030a, 0x3f10316, 0x4ab0468, + 0x4fa04de, 0x520050b, 0x531, 0x0, 0x0, 0x10200fe, 0x10a0106, + 0x112010e, 0x11a0116, 0x122011e, 0x12a0126, 0x132012e, 0x13a0136, + 0x142013e, 0x14a0146, 0x152014e, 0x15a0156, 0x162015e, 0x5e31b4d, + 0x5e5082c, 0x933, 0x5d50568, 0x5e605e3, 0x67e0629, 0x6ac0687, + 0x60706cf, 0x734071a, 0x77e0723, 0x6af07a4, 0x82c083b, 0x88d085e, + 0x6b2056b, 0x6820770, 0x60a095a, 0x76c06b1, 0x8660860, 0x9300827, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x761075e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x606, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1c9e1bc3, 0x1cad, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x20b02197, 0x1cf71ff3, 0x20811f17, 0x208c2532, 0x21fe1f1d, + 0x21e722f2, 0x21451f9d, 0x21eb1f69, 0x24261f93, 0x2560235c, + 0x200f2073, 0x219d22cc, 0x1ee921b3, 0x25a01eef, 0x1efd20fa, + 0x21ad2001, 0x21992574, 0x23f023d2, 0x22bc2005, 0x329221b, + 0x1f9f2366, 0x2035, 0x0, 0x0, 0x1b511b69, 0x1b5d1b55, 0x1b611b6d, + 0x1b591b71, 0x1b65, 0x0, 0x0, 0x0, 0x1ffd2147, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1f031f07, 0x26f51f0b, 0x1f351f2d, 0x1f3b1f37, + 0x1f411f3f, 0x1f431f47, 0x26fd1e63, 0x1f531f51, 0x1f631f55, + 0x1e6526f7, 0x1f691f59, 0x1f7126fb, 0x1f251f75, 0x1f7b1f79, + 0x1f8927b9, 0x1e691f8d, 0x1f9b1f99, 0x1fa11f9f, 0x1fad1e6b, + 0x1fb51faf, 0x1fbd1fbb, 0x1fc31fbf, 0x1fd51fd3, 0x1fe11fd9, + 0x1fe71fe5, 0x1fe71fe7, 0x22e42703, 0x1ff51ff1, 0x1ffb2705, + 0x20031fff, 0x200d2017, 0x20152013, 0x201d2019, 0x2023201f, + 0x20292027, 0x202d2029, 0x20332031, 0x204b2039, 0x204d203d, + 0x2043203f, 0x20711f8f, 0x20572055, 0x20532059, 0x205b205d, + 0x27072067, 0x20772075, 0x2081207b, 0x20962085, 0x270b2709, + 0x209e209c, 0x209a20a0, 0x1e6d20a4, 0x20a81e6f, 0x20ac20ac, + 0x20ba270d, 0x20be20bc, 0x270f20c2, 0x20c820c6, 0x20cc2137, + 0x20d21e71, 0x20e020da, 0x271320de, 0x271520e4, 0x20e820ea, + 0x20f420ec, 0x1e7320f6, 0x210220fe, 0x21062104, 0x27171e75, + 0x21171e77, 0x211b2119, 0x27cd211f, 0x271b212b, 0x2486271b, + 0x21332133, 0x27291e79, 0x213b277d, 0x1e7b213f, 0x21512149, + 0x21572153, 0x1e7f215f, 0x21611e7d, 0x2163271d, 0x216f216d, + 0x216f2171, 0x21792177, 0x217d2181, 0x2183217f, 0x21872185, + 0x218f210b, 0x219f219b, 0x21b121a7, 0x21af2723, 0x21b521a9, + 0x21c321b9, 0x21c72725, 0x21bd21c1, 0x21cb1e81, 0x21d321cf, + 0x1e8321cd, 0x21df21db, 0x21f52727, 0x22032215, 0x22091e89, + 0x1e851e87, 0x1f6d1f6b, 0x220b2217, 0x1ebb2470, 0x221f221d, + 0x222b2221, 0x27312227, 0x22351e8b, 0x2242222f, 0x27352246, + 0x22392248, 0x1e8d224c, 0x2250224e, 0x22582252, 0x225c2737, + 0x22621e8f, 0x22642739, 0x226a1e91, 0x22762270, 0x273b2278, + 0x273d2711, 0x273f2288, 0x2292228e, 0x2298228a, 0x22a822a0, + 0x22a422a2, 0x22ac22aa, 0x229e2741, 0x22ba22b8, 0x22c41e93, + 0x274322c2, 0x22d222b4, 0x27472745, 0x22de22d4, 0x22da22dc, + 0x22e01e95, 0x22e622e8, 0x26f922ec, 0x274922f4, 0x274d22fa, + 0x230a2304, 0x274f2314, 0x2320231e, 0x27532751, 0x2336232e, + 0x23381e97, 0x1e991e99, 0x23462344, 0x234c234a, 0x1e9b2352, + 0x2755235e, 0x2757236c, 0x27192372, 0x2759237a, 0x275d275b, + 0x1e9f1e9d, 0x27612396, 0x2763275f, 0x239a2765, 0x239c239c, + 0x1ea323a0, 0x1ea523a2, 0x27691ea7, 0x23b023ac, 0x1ea923b6, + 0x23c8276b, 0x276f276d, 0x23e423d8, 0x23e81eab, 0x23ec23ea, + 0x27732771, 0x23f82773, 0x27751ead, 0x24042402, 0x27771eaf, + 0x1eb12412, 0x2416241a, 0x277b241e, 0x1eb3242a, 0x24342430, + 0x1eb5243c, 0x2781277f, 0x27831eb7, 0x27852448, 0x2454244e, + 0x27872458, 0x24622789, 0x2466278b, 0x1eb9272b, 0x24742472, + 0x24761ebd, 0x278d20a6, 0x272d278f, 0x2486272f, 0x25942488, + 0x249e1ebf, 0x24a0249c, 0x24a21fa9, 0x24a624a4, 0x279124aa, + 0x24ac24a8, 0x24b824b6, 0x24ba24ae, 0x24ce24c4, 0x24be24b4, + 0x24c224c0, 0x27972793, 0x1ec12795, 0x24d424d2, 0x279f24d8, + 0x279924da, 0x1ec51ec3, 0x279d279b, 0x24ea1ec7, 0x24ee24ec, + 0x24f624f0, 0x24fa24f4, 0x250024f8, 0x24fe24fc, 0x1ec92502, + 0x25082506, 0x25101ecb, 0x27a12512, 0x251a2518, 0x25201ecd, + 0x27a31e67, 0x1ecf27a5, 0x25361ed1, 0x25502542, 0x27a72558, + 0x25642562, 0x25762570, 0x26ff27ab, 0x257a257c, 0x27012580, + 0x258c2586, 0x27af27ad, 0x25b225ac, 0x27b125b6, 0x25cc25b8, + 0x25d425d2, 0x25da25d0, 0x27b325dc, 0x1ed325e2, 0x27b525e6, + 0x26021ed5, 0x260e20ee, 0x27bb27b7, 0x1ed91ed7, 0x27bd2622, + 0x27bf1edb, 0x262e262e, 0x27c12632, 0x1edd263e, 0x264c2646, + 0x26542650, 0x27c31edf, 0x266c265e, 0x1ee12672, 0x26741ee3, + 0x1ee527c5, 0x27c927c7, 0x268627cb, 0x26901ee7, 0x26962694, + 0x269e269a, 0x27cf26a2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); + //12288 bytes + enum canonMappingTrieEntries = TrieEntry!(ushort, 8, 7, 6)([0x0, 0x40, + 0x240], [0x100, 0x400, 0x1380], [0x2020100, 0x3020202, 0x2020204, + 0x2050202, 0x2020202, 0x6020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x0, 0x10000, 0x30002, 0x50004, 0x6, 0x0, 0x70000, + 0x90008, 0xb000a, 0xc0000, 0x0, 0x0, 0xd, 0xe0000, 0x0, 0x0, 0x0, + 0x0, 0x10000f, 0x110000, 0x130012, 0x0, 0x140000, 0x160015, + 0x170000, 0x180000, 0x190000, 0x1a0000, 0x0, 0x0, 0x1b0000, 0x1c, + 0x1d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f001e, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x210020, 0x230022, 0x250024, 0x270026, 0x28, 0x0, + 0x29, 0x2b002a, 0x2d002c, 0x2f002e, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x310000, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x320000, 0x340033, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x360035, 0x380037, 0x3a0039, 0x3c003b, 0x3e003d, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3f, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x410000, 0x430042, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x450044, 0x470046, 0x490048, 0x4b004a, 0x4c, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xf000c, 0x250012, 0x4f0045, 0x850000, 0xa1009e, 0xcb00a4, + 0x121011e, 0x1330124, 0x1880000, 0x1a0019d, 0x1b601a3, 0x1da, + 0x26d0000, 0x2730270, 0x2f30287, 0x0, 0x322031f, 0x3380325, + 0x3620358, 0x3980000, 0x3b403b1, 0x3de03b7, 0x4370434, 0x446043a, + 0x49c0000, 0x4b404b1, 0x4ca04b7, 0x4ee, 0x5840000, 0x58a0587, + 0x60d059e, 0x61c0000, 0x33b0028, 0x33e002b, 0x380006d, 0x38c0079, + 0x38f007c, 0x392007f, 0x3950082, 0x3a2008f, 0x0, 0x3cd00ba, + 0x3d800c5, 0x3db00c8, 0x3fb00e8, 0x3e400d1, 0x40a00f7, 0x41000fd, + 0x4130100, 0x4190106, 0x41c0109, 0x0, 0x43d0127, 0x440012a, + 0x443012d, 0x45c0149, 0x130, 0x0, 0x462014f, 0x471015d, 0x1630000, + 0x1700477, 0x1660484, 0x47a, 0x0, 0x1850000, 0x1940499, 0x18e04a8, + 0x4a2, 0x0, 0x4d901c5, 0x4e401d0, 0x4f801e4, 0x0, 0x52f021b, + 0x5450231, 0x5350221, 0x54b0237, 0x552023e, 0x5690255, 0x5580244, + 0x57b0264, 0x572025b, 0x0, 0x58d0276, 0x594027d, 0x59b0284, + 0x5b4029d, 0x5b702a0, 0x5e002c9, 0x5f502de, 0x61002f6, 0x30b0302, + 0x3110628, 0x314062e, 0x631, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x50401f0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x2ac0000, 0x5c3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x560000, 0x13d0369, 0x1e70450, + 0x2a304fb, 0x29205ba, 0x28e05a9, 0x29605a5, 0x28a05ad, 0x5a1, + 0x35b0048, 0x3540041, 0x653064a, 0x0, 0x4160103, 0x46b0157, + 0x522020e, 0x5250211, 0x65f065c, 0x465, 0x0, 0x40700f4, 0x0, + 0x4960182, 0x3650052, 0x6500647, 0x656064d, 0x36c0059, 0x36f005c, + 0x3e700d4, 0x3ea00d7, 0x4530140, 0x4560143, 0x4fe01ea, 0x50101ed, + 0x5380224, 0x53b0227, 0x5bd02a6, 0x5c002a9, 0x5660252, 0x5780261, + 0x0, 0x4250112, 0x0, 0x0, 0x0, 0x351003e, 0x3f400e1, 0x4f101dd, + 0x4d101bd, 0x4e701d3, 0x4ea01d6, 0x61602fc, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x10000d, 0x66b0000, 0x137, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x662, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, + 0x63d0000, 0x6450670, 0x6df06c3, 0x72c, 0x759, 0x7980778, 0x8d1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7810735, 0x84707e9, 0x8c10867, 0x92f, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x92808ca, 0x91f08fd, 0x95f, + 0x0, 0x9b40000, 0x9b7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x9cc09c6, 0x9c30000, 0x0, 0x9ba0000, 0x0, 0x0, 0x9d809e4, 0x9ed, + 0x0, 0x0, 0x0, 0x0, 0x9de0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa200000, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa0e0a08, 0xa050000, 0x0, + 0xa410000, 0x0, 0x0, 0xa1a0a26, 0xa2f, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa470a44, 0x0, 0x0, 0x0, 0x0, + 0x9cf0000, 0xa11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9ff09bd, + 0xa0209c0, 0x0, 0xa0b09c9, 0x0, 0xa4d0a4a, 0xa1409d2, 0xa1709d5, + 0x0, 0xa1d09db, 0xa2309e1, 0xa2909e7, 0x0, 0xa530a50, 0xa3e09fc, + 0xa2c09ea, 0xa3209f0, 0xa3509f3, 0xa3809f6, 0x0, 0xa3b09f9, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xac10abe, 0xac40ac7, 0xaca, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xad3, + 0xacd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xad00000, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xae80000, 0x0, 0x0, 0x0, 0xaf10000, 0x0, 0xaf4, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xad90ad6, 0xadf0adc, 0xae50ae2, 0xaee0aeb, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xb000000, 0xb03, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xafa0af7, 0xafd0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb120000, 0x0, 0xb15, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xb060000, 0xb0c0b09, 0x0, 0xb0f, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xb21, 0xb1e0000, 0xb24, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xb1b0b18, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xb27, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb300b2a, 0xb2d, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb33, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xb36, 0x0, 0x0, 0xb400000, 0xb43, 0xb3c0b39, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xb4c0b46, 0xb49, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xb4f, 0xb550b52, 0xb59, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xb5f0000, 0x0, 0x0, 0x0, 0x0, 0xb620000, 0x0, 0x0, 0xb65, 0x0, + 0xb680000, 0x0, 0x0, 0xb6b, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb5c0000, + 0x0, 0x0, 0x0, 0x0, 0xb6e0000, 0xb710000, 0xb89, 0xb8c, 0x0, 0x0, + 0x0, 0xb740000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb7a0000, + 0x0, 0x0, 0x0, 0x0, 0xb7d0000, 0x0, 0x0, 0xb80, 0x0, 0xb830000, + 0x0, 0x0, 0xb86, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb770000, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb8f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb92, 0xb95, 0xb98, + 0xb9b, 0xb9e, 0x0, 0xba1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xba40000, + 0xba70000, 0x0, 0xbad0baa, 0xbb00000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x37d006a, + 0x3830070, 0x3860073, 0x3890076, 0x39b0088, 0x39f008c, 0x3a50092, + 0x3ae009b, 0x3a80095, 0x3ab0098, 0x3d000bd, 0x3d400c1, 0x3fe00eb, + 0x40100ee, 0x3f700e4, 0x40400f1, 0x40d00fa, 0x41f010c, 0x4280115, + 0x422010f, 0x42b0118, 0x42e011b, 0x45f014c, 0x4490136, 0x4680154, + 0x46e015a, 0x4740160, 0x47d0169, 0x480016c, 0x48a0176, 0x4870173, + 0x48d0179, 0x490017c, 0x493017f, 0x49f018b, 0x4a50191, 0x4ae019a, + 0x4ab0197, 0x4cd01b9, 0x4d501c1, 0x4dc01c8, 0x4e001cc, 0x5290215, + 0x52c0218, 0x532021e, 0x53e022a, 0x541022d, 0x5480234, 0x5550241, + 0x55f024b, 0x54e023a, 0x55b0247, 0x562024e, 0x56c0258, 0x575025e, + 0x581026a, 0x57e0267, 0x5dd02c6, 0x5e602cf, 0x5e302cc, 0x5900279, + 0x5970280, 0x5e902d2, 0x5ec02d5, 0x5ef02d8, 0x5f202db, 0x5fb02e4, + 0x5f802e1, 0x60102e7, 0x60402ea, 0x60702ed, 0x61902ff, 0x62b030e, + 0x6340317, 0x637031a, 0x56f0431, 0x62205fe, 0x6590000, 0x0, 0x0, + 0x372005f, 0x35f004c, 0x32c0019, 0x3280015, 0x3340021, 0x330001d, + 0x3750062, 0x3450032, 0x341002e, 0x34d003a, 0x3490036, 0x3790066, + 0x3ed00da, 0x3e100ce, 0x3ca00b7, 0x3be00ab, 0x3ba00a7, 0x3c600b3, + 0x3c200af, 0x3f000dd, 0x44d013a, 0x4590146, 0x51b0207, 0x4f501e1, + 0x4be01aa, 0x4ba01a6, 0x4c601b2, 0x4c201ae, 0x51e020a, 0x50b01f7, + 0x50701f3, 0x51301ff, 0x50f01fb, 0x5170203, 0x5da02c3, 0x5b1029a, + 0x5ca02b3, 0x5c602af, 0x5d202bb, 0x5ce02b7, 0x5d602bf, 0x60a02f0, + 0x6250308, 0x61f0305, 0x61302f9, 0x0, 0x0, 0x0, 0x81807f6, + 0x81b07f9, 0x8240802, 0x82d080b, 0x69b0679, 0x69e067c, 0x6a70685, + 0x6b0068e, 0x855084a, 0x858084d, 0x85c0851, 0x0, 0x6d106c6, + 0x6d406c9, 0x6d806cd, 0x0, 0x890086e, 0x8930871, 0x89c087a, + 0x8a50883, 0x70406e2, 0x70706e5, 0x71006ee, 0x71906f7, 0x8e808d9, + 0x8eb08dc, 0x8ef08e0, 0x8f308e4, 0x7470738, 0x74a073b, 0x74e073f, + 0x7520743, 0x90b0900, 0x90e0903, 0x9120907, 0x0, 0x767075c, + 0x76a075f, 0x76e0763, 0x0, 0x9460937, 0x949093a, 0x94d093e, + 0x9510942, 0x7840000, 0x7870000, 0x78b0000, 0x78f0000, 0x9880966, + 0x98b0969, 0x9940972, 0x99d097b, 0x7bd079b, 0x7c0079e, 0x7c907a7, + 0x7d207b0, 0x7e907e2, 0x8470844, 0x8670860, 0x8c108be, 0x8fd08fa, + 0x91f091c, 0x95f0958, 0x0, 0x8360814, 0x81f07fd, 0x8280806, + 0x831080f, 0x6b90697, 0x6a20680, 0x6ab0689, 0x6b40692, 0x8ae088c, + 0x8970875, 0x8a0087e, 0x8a90887, 0x7220700, 0x70b06e9, 0x71406f2, + 0x71d06fb, 0x9a60984, 0x98f096d, 0x9980976, 0x9a1097f, 0x7db07b9, + 0x7c407a2, 0x7cd07ab, 0x7d607b4, 0x7f007f3, 0x84107e5, 0x7ec, + 0x83d083a, 0x6730676, 0x670066d, 0x6bd, 0x8bc, 0x6400000, + 0x8b90863, 0x86a, 0x8b508b2, 0x6c306c0, 0x6df06dc, 0xbb30726, + 0xbb90bb6, 0x8c408c7, 0x8d108cd, 0x0, 0x8d508f7, 0x72f0732, + 0x72c0729, 0xbbc0000, 0xbc20bbf, 0x9220925, 0x92f092b, 0x9190916, + 0x9330955, 0x77b077e, 0x7780775, 0x63a0772, 0x31d063d, 0x0, + 0x9b1095b, 0x962, 0x9ad09aa, 0x7590756, 0x7980795, 0x64307df, 0x0, + 0xbc70bc5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x793, 0x0, 0x4f0152, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xbcc0bc9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbcf, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xbd20000, 0xbd50bd8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xbdb, 0x0, 0xbde0000, 0x0, 0xbe1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbe4, 0xbe7, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xbea0000, 0x0, 0xbed, 0xbf00000, 0xbf30000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0xbf9, 0x0, 0x0, 0x0, 0x0, + 0xbf60000, 0x90003, 0xbff0bfc, 0x0, 0xc050c02, 0x0, 0xc0b0c08, 0x0, + 0x0, 0x0, 0xc110c0e, 0x0, 0xc1d0c1a, 0x0, 0xc230c20, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xc2f0c2c, 0xc350c32, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xc170c14, 0xc290c26, 0x0, 0x0, 0x0, 0xc3b0c38, + 0xc410c3e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xc470000, 0xc49, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc44, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xc4e, 0xc51, 0xc54, 0xc57, 0xc5a, 0xc5d, + 0xc60, 0xc63, 0xc66, 0xc69, 0xc6c, 0xc6f, 0xc720000, 0xc750000, + 0xc780000, 0x0, 0x0, 0x0, 0xc7e0c7b, 0xc810000, 0xc84, 0xc8a0c87, + 0xc8d0000, 0xc90, 0xc960c93, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xc4b, 0x0, 0x0, 0x0, 0x0, 0xc99, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xc9f, 0xca2, 0xca5, 0xca8, 0xcab, 0xcae, + 0xcb1, 0xcb4, 0xcb7, 0xcba, 0xcbd, 0xcc0, 0xcc30000, 0xcc60000, + 0xcc90000, 0x0, 0x0, 0x0, 0xccf0ccc, 0xcd20000, 0xcd5, 0xcdb0cd8, + 0xcde0000, 0xce1, 0xce70ce4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xc9c, 0xcea0000, 0xcf00ced, 0xcf3, 0x0, 0xcf6, + 0xfb71241, 0x124b125d, 0xd831043, 0x13270e29, 0xe991327, 0xe4f1293, + 0xf550e97, 0x116710cd, 0x11fd11e3, 0x12791215, 0x10190feb, + 0x109d1069, 0x128911c7, 0xd8d12f3, 0xff50e1d, 0x11e11079, + 0xedb1309, 0x11d91051, 0xf65121d, 0x12031189, 0xfbd0eff, + 0x108d1025, 0xd9d127d, 0xe050dd9, 0xff10f95, 0x10d31077, + 0x11dd1171, 0x125911e7, 0x12fb12cf, 0x10e91307, 0x114d1107, + 0x12a111b9, 0x122f130b, 0xf0b0e87, 0x117d112f, 0x10ed1083, + 0x12cb1249, 0xecb0e85, 0x102f0fed, 0x11471047, 0x12b11159, + 0x117f0e03, 0xddd0ddf, 0x114f1115, 0x12b511c5, 0xf67123d, + 0x12350feb, 0xebb0d87, 0x10950f27, 0xe1110c1, 0xda510f1, 0xd7f0f1b, + 0xf9d1011, 0xe231145, 0x10d70e7d, 0x122711c9, 0x126d1005, + 0xf6f100d, 0xf7b11a5, 0xd9110bf, 0xddb0dc3, 0x113d0fdb, 0x122d1195, + 0xe091291, 0xe9f0e37, 0xfa10f07, 0x10f31053, 0x12f712ab, + 0x1313130d, 0xfb50df9, 0x12690ffd, 0xf490ef3, 0xf910f57, + 0x106d104b, 0x111110af, 0x11791153, 0x11cd1261, 0x12a31271, + 0xdfb0de9, 0x10670e41, 0x1227120b, 0xf230efd, 0x10030f77, + 0x1091112d, 0xe670d97, 0xee50ebb, 0x109b0f29, 0x116b10a9, + 0x12951175, 0x12d112c9, 0xd9f12dd, 0x128d110f, 0xf3512c1, + 0xdb10d8f, 0xec70ebd, 0xfeb0f9f, 0x10cb1073, 0x127711d3, 0xfad1323, + 0xdf712af, 0xfd10fcb, 0x103b1021, 0x10bd10a1, 0x114310e7, + 0xdc512e3, 0x12b70f5d, 0xed70da9, 0x12631031, 0xf390f17, + 0x10950fd5, 0xdeb12bb, 0xecf0e31, 0xfc30fa7, 0x10150fe1, + 0x10c3109f, 0x120d1163, 0x128f1213, 0xe1312c5, 0xe33103d, + 0x10b11075, 0x12bd11db, 0x130f12ff, 0x102d0fcf, 0x1121118b, + 0x11331125, 0x1063108b, 0xd93123b, 0xded11ad, 0xef50de7, + 0x11390f69, 0x101b0eb5, 0x12670fb3, 0x12b31205, 0xf031221, + 0xe590db5, 0x0, 0xe7b, 0xfab, 0xde10000, 0x10cf108f, 0x110310f5, + 0x110d1105, 0x113512d3, 0x116d, 0x11df, 0x1233, 0x12730000, 0x1283, + 0x0, 0x12e912e7, 0x130512eb, 0x12bf127f, 0xdb30da1, 0xe010db9, + 0xe170e07, 0xe5f0e53, 0xe790e63, 0xecd0e7f, 0xf2f0ed1, 0xf470f43, + 0xf970f53, 0xfaf0fa3, 0x10270fdd, 0x10491035, 0x107d106f, + 0x10eb10a3, 0x10fb10f7, 0x10fd10f9, 0x110110ff, 0x110b1109, + 0x111d1117, 0x11531127, 0x115b1157, 0x11731161, 0x1197118d, + 0x11cb1197, 0x12231219, 0x12391237, 0x124f124d, 0x1273126f, + 0x12d912c7, 0xf2b12e1, 0x119313be, 0x0, 0xdd70d81, 0xd9b0dc1, + 0xdc90db7, 0xe0b0dff, 0xe490e53, 0xe5d0e51, 0xe830e7b, 0xe9b0e95, + 0xeb10ea9, 0xf050f01, 0xf1d0f13, 0xf3f0f33, 0xf470f37, 0xf530f41, + 0xf7f0f5f, 0xf890f85, 0xfab0f99, 0xfbf0fbd, 0xfff0fc7, 0x10211005, + 0x10411045, 0x10571049, 0x10e3106f, 0x1089107f, 0x10ab108f, + 0x10b910b5, 0x10c910c7, 0x10d110cf, 0x10df10d5, 0x10ef10dd, + 0x1127111f, 0x11491131, 0x115f1153, 0x11af1173, 0x11f911c3, + 0x121f121b, 0x12291223, 0x122b1233, 0x12351237, 0x12391231, + 0x124f123f, 0x12751265, 0x1299128b, 0x12c712b9, 0x12d512d3, + 0x12db12d9, 0x12f912e1, 0x13941327, 0x13a61392, 0xd370d23, + 0x13da0d39, 0x141c13ec, 0x13251321, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa7a0000, 0xabb0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0xab50ab2, + 0xaae0aaa, 0xa590a56, 0xa5f0a5c, 0xa680a65, 0xa710a6b, 0xa74, + 0xa7d0a77, 0xa830a80, 0xa89, 0xa8c, 0xa920a8f, 0xa950000, 0xa98, + 0xaa10a9e, 0xaa70aa4, 0xa6e0ab8, 0xa860a62, 0xa9b, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1329, 0x132c, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x132f0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x13351332, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x133b1338, 0x1342133e, 0x134a1346, 0x134e, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x13520000, 0x1355135d, 0x13591360, 0x1364, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xd850d89, 0x13680d8b, 0xda10d99, 0xda70da3, 0xdad0dab, + 0xdaf0db3, 0x13700cf9, 0xdbb0db9, 0xdc70dbd, 0xcfb136a, 0xdcb0dbf, + 0xdd1136e, 0xd950dd3, 0xdd70dd5, 0xde3142c, 0xcff0de5, 0xdf10def, + 0xdf50df3, 0xdff0d01, 0xe070e01, 0xe0d0e0b, 0xe110e0f, 0xe170e15, + 0xe1b0e19, 0xe210e1f, 0xe210e21, 0x105d1376, 0xe270e25, 0xe2b1378, + 0xe2f0e2d, 0xe350e3d, 0xe3b0e39, 0xe430e3f, 0xe470e45, 0xe4d0e4b, + 0xe510e4d, 0xe570e55, 0xe690e5b, 0xe6b0e5f, 0xe650e61, 0xe890de7, + 0xe710e6f, 0xe6d0e73, 0xe750e77, 0x137a0e81, 0xe8d0e8b, 0xe910e8f, + 0xe9d0e93, 0x137e137c, 0xea50ea3, 0xea10ea7, 0xd030eab, 0xeaf0d05, + 0xeb30eb3, 0xeb71380, 0xebb0eb9, 0x13820ebf, 0xec30ec1, 0xec50f0f, + 0xec90d07, 0xed50ed1, 0x13860ed3, 0x13880ed9, 0xedd0edf, 0xee70ee1, + 0xd090ee9, 0xeed0eeb, 0xef10eef, 0x138a0d0b, 0xef70d0d, 0xefb0ef9, + 0x14400eff, 0x138e0f09, 0x118f138e, 0xf0d0f0d, 0x139c0d0f, + 0xf1113f0, 0xd110f15, 0xf1f0f19, 0xf250f21, 0xd150f2d, 0xf2f0d13, + 0xf311390, 0xf3d0f3b, 0xf3d0f3f, 0xf470f45, 0xf4b0f4f, 0xf510f4d, + 0xf550f53, 0xf5b0f59, 0xf630f61, 0xf730f6b, 0xf711396, 0xf750f6d, + 0xf830f79, 0xf871398, 0xf7d0f81, 0xf8b0d17, 0xf930f8f, 0xd190f8d, + 0xf9b0f97, 0xfa5139a, 0xfa90fb9, 0xfaf0d1f, 0xd1b0d1d, 0xdcf0dcd, + 0xfb10fbb, 0xd511181, 0xfbf0fbd, 0xfc90fc1, 0x13a40fc5, 0xfd30d21, + 0xfd90fcd, 0x13a80fdd, 0xfd70fdf, 0xd230fe3, 0xfe70fe5, 0xfef0fe9, + 0xff313aa, 0xff70d25, 0xff913ac, 0xffb0d27, 0x10051001, 0x13ae1007, + 0x13b01384, 0x13b21009, 0x1013100f, 0x1017100b, 0x1027101f, + 0x10231021, 0x102b1029, 0x101d13b4, 0x10391037, 0x10410d29, + 0x13b6103f, 0x104d1033, 0x13ba13b8, 0x1059104f, 0x10551057, + 0x105b0d2b, 0x105f1061, 0x136c1065, 0x13bc106b, 0x13c01071, + 0x107f107b, 0x13c21081, 0x10871085, 0x13c613c4, 0x10971093, + 0x10990d2d, 0xd2f0d2f, 0x10a710a5, 0x10ad10ab, 0xd3110b3, + 0x13c810b7, 0x13ca10bb, 0x138c10c1, 0x13cc10c5, 0x13d013ce, + 0xd350d33, 0x13d410d5, 0x13d613d2, 0x10d913d8, 0x10db10db, + 0xd3910df, 0xd3b10e1, 0x13dc0d3d, 0x10e910e5, 0xd3f10ef, + 0x10ff13de, 0x13e213e0, 0x1113110d, 0x11170d41, 0x111b1119, + 0x13e613e4, 0x112313e6, 0x13e80d43, 0x112b1129, 0x13ea0d45, + 0xd471137, 0x113b113f, 0x13ee1141, 0xd49114b, 0x11551151, + 0xd4b115d, 0x13f413f2, 0x13f60d4d, 0x13f81165, 0x116f1169, + 0x13fa1173, 0x117713fc, 0x117b13fe, 0xd4f139e, 0x11851183, + 0x11870d53, 0x14000ead, 0x13a01402, 0x118f13a2, 0x126b1191, + 0x119b0d55, 0x119d1199, 0x119f0dfd, 0x11a311a1, 0x140411a7, + 0x11a911a5, 0x11b511b3, 0x11b711ab, 0x11cb11c1, 0x11bb11b1, + 0x11bf11bd, 0x140a1406, 0xd571408, 0x11d111cf, 0x141211d5, + 0x140c11d7, 0xd5b0d59, 0x1410140e, 0x11e50d5d, 0x11e911e7, + 0x11ef11eb, 0x11f311ed, 0x11f911f1, 0x11f711f5, 0xd5f11fb, + 0x120111ff, 0x12070d61, 0x14141209, 0x1211120f, 0x12170d63, + 0x14160cfd, 0xd651418, 0x12250d67, 0x123f1231, 0x141a1243, + 0x12471245, 0x12531251, 0x1372141e, 0x12551257, 0x1374125b, + 0x1265125f, 0x14221420, 0x1281127b, 0x14241285, 0x12971287, + 0x129f129d, 0x12a5129b, 0x142612a7, 0xd6912a9, 0x142812ad, + 0x12c30d6b, 0x12cd0ee3, 0x142e142a, 0xd6f0d6d, 0x143012d7, + 0x14320d71, 0x12db12db, 0x143412df, 0xd7312e5, 0x12ef12ed, + 0x12f512f1, 0x14360d75, 0x12fd12f9, 0xd771301, 0x13030d79, + 0xd7b1438, 0x143c143a, 0x1311143e, 0x13150d7d, 0x13191317, + 0x131d131b, 0x1442131f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); + @property + { + private alias _IDCA = immutable(dchar[]); + _IDCA decompCanonTable() + { + static _IDCA t = [ + 0x0, 0x3b, 0x0, 0x3c, 0x338, 0x0, 0x3d, 0x338, 0x0, 0x3e, + 0x338, 0x0, 0x41, 0x300, 0x0, 0x41, 0x301, 0x0, 0x41, 0x302, + 0x0, 0x41, 0x302, 0x300, 0x0, 0x41, 0x302, 0x301, 0x0, 0x41, + 0x302, 0x303, 0x0, 0x41, 0x302, 0x309, 0x0, 0x41, 0x303, 0x0, + 0x41, 0x304, 0x0, 0x41, 0x306, 0x0, 0x41, 0x306, 0x300, 0x0, + 0x41, 0x306, 0x301, 0x0, 0x41, 0x306, 0x303, 0x0, 0x41, 0x306, + 0x309, 0x0, 0x41, 0x307, 0x0, 0x41, 0x307, 0x304, 0x0, 0x41, + 0x308, 0x0, 0x41, 0x308, 0x304, 0x0, 0x41, 0x309, 0x0, 0x41, + 0x30a, 0x0, 0x41, 0x30a, 0x301, 0x0, 0x41, 0x30c, 0x0, 0x41, + 0x30f, 0x0, 0x41, 0x311, 0x0, 0x41, 0x323, 0x0, 0x41, 0x323, + 0x302, 0x0, 0x41, 0x323, 0x306, 0x0, 0x41, 0x325, 0x0, 0x41, + 0x328, 0x0, 0x42, 0x307, 0x0, 0x42, 0x323, 0x0, 0x42, 0x331, + 0x0, 0x43, 0x301, 0x0, 0x43, 0x302, 0x0, 0x43, 0x307, 0x0, + 0x43, 0x30c, 0x0, 0x43, 0x327, 0x0, 0x43, 0x327, 0x301, 0x0, + 0x44, 0x307, 0x0, 0x44, 0x30c, 0x0, 0x44, 0x323, 0x0, 0x44, + 0x327, 0x0, 0x44, 0x32d, 0x0, 0x44, 0x331, 0x0, 0x45, 0x300, + 0x0, 0x45, 0x301, 0x0, 0x45, 0x302, 0x0, 0x45, 0x302, 0x300, + 0x0, 0x45, 0x302, 0x301, 0x0, 0x45, 0x302, 0x303, 0x0, 0x45, + 0x302, 0x309, 0x0, 0x45, 0x303, 0x0, 0x45, 0x304, 0x0, 0x45, + 0x304, 0x300, 0x0, 0x45, 0x304, 0x301, 0x0, 0x45, 0x306, 0x0, + 0x45, 0x307, 0x0, 0x45, 0x308, 0x0, 0x45, 0x309, 0x0, 0x45, + 0x30c, 0x0, 0x45, 0x30f, 0x0, 0x45, 0x311, 0x0, 0x45, 0x323, + 0x0, 0x45, 0x323, 0x302, 0x0, 0x45, 0x327, 0x0, 0x45, 0x327, + 0x306, 0x0, 0x45, 0x328, 0x0, 0x45, 0x32d, 0x0, 0x45, 0x330, + 0x0, 0x46, 0x307, 0x0, 0x47, 0x301, 0x0, 0x47, 0x302, 0x0, + 0x47, 0x304, 0x0, 0x47, 0x306, 0x0, 0x47, 0x307, 0x0, 0x47, + 0x30c, 0x0, 0x47, 0x327, 0x0, 0x48, 0x302, 0x0, 0x48, 0x307, + 0x0, 0x48, 0x308, 0x0, 0x48, 0x30c, 0x0, 0x48, 0x323, 0x0, + 0x48, 0x327, 0x0, 0x48, 0x32e, 0x0, 0x49, 0x300, 0x0, 0x49, + 0x301, 0x0, 0x49, 0x302, 0x0, 0x49, 0x303, 0x0, 0x49, 0x304, + 0x0, 0x49, 0x306, 0x0, 0x49, 0x307, 0x0, 0x49, 0x308, 0x0, + 0x49, 0x308, 0x301, 0x0, 0x49, 0x309, 0x0, 0x49, 0x30c, 0x0, + 0x49, 0x30f, 0x0, 0x49, 0x311, 0x0, 0x49, 0x323, 0x0, 0x49, + 0x328, 0x0, 0x49, 0x330, 0x0, 0x4a, 0x302, 0x0, 0x4b, 0x0, + 0x4b, 0x301, 0x0, 0x4b, 0x30c, 0x0, 0x4b, 0x323, 0x0, 0x4b, + 0x327, 0x0, 0x4b, 0x331, 0x0, 0x4c, 0x301, 0x0, 0x4c, 0x30c, + 0x0, 0x4c, 0x323, 0x0, 0x4c, 0x323, 0x304, 0x0, 0x4c, 0x327, + 0x0, 0x4c, 0x32d, 0x0, 0x4c, 0x331, 0x0, 0x4d, 0x301, 0x0, + 0x4d, 0x307, 0x0, 0x4d, 0x323, 0x0, 0x4e, 0x300, 0x0, 0x4e, + 0x301, 0x0, 0x4e, 0x303, 0x0, 0x4e, 0x307, 0x0, 0x4e, 0x30c, + 0x0, 0x4e, 0x323, 0x0, 0x4e, 0x327, 0x0, 0x4e, 0x32d, 0x0, + 0x4e, 0x331, 0x0, 0x4f, 0x300, 0x0, 0x4f, 0x301, 0x0, 0x4f, + 0x302, 0x0, 0x4f, 0x302, 0x300, 0x0, 0x4f, 0x302, 0x301, 0x0, + 0x4f, 0x302, 0x303, 0x0, 0x4f, 0x302, 0x309, 0x0, 0x4f, 0x303, + 0x0, 0x4f, 0x303, 0x301, 0x0, 0x4f, 0x303, 0x304, 0x0, 0x4f, + 0x303, 0x308, 0x0, 0x4f, 0x304, 0x0, 0x4f, 0x304, 0x300, 0x0, + 0x4f, 0x304, 0x301, 0x0, 0x4f, 0x306, 0x0, 0x4f, 0x307, 0x0, + 0x4f, 0x307, 0x304, 0x0, 0x4f, 0x308, 0x0, 0x4f, 0x308, 0x304, + 0x0, 0x4f, 0x309, 0x0, 0x4f, 0x30b, 0x0, 0x4f, 0x30c, 0x0, + 0x4f, 0x30f, 0x0, 0x4f, 0x311, 0x0, 0x4f, 0x31b, 0x0, 0x4f, + 0x31b, 0x300, 0x0, 0x4f, 0x31b, 0x301, 0x0, 0x4f, 0x31b, 0x303, + 0x0, 0x4f, 0x31b, 0x309, 0x0, 0x4f, 0x31b, 0x323, 0x0, 0x4f, + 0x323, 0x0, 0x4f, 0x323, 0x302, 0x0, 0x4f, 0x328, 0x0, 0x4f, + 0x328, 0x304, 0x0, 0x50, 0x301, 0x0, 0x50, 0x307, 0x0, 0x52, + 0x301, 0x0, 0x52, 0x307, 0x0, 0x52, 0x30c, 0x0, 0x52, 0x30f, + 0x0, 0x52, 0x311, 0x0, 0x52, 0x323, 0x0, 0x52, 0x323, 0x304, + 0x0, 0x52, 0x327, 0x0, 0x52, 0x331, 0x0, 0x53, 0x301, 0x0, + 0x53, 0x301, 0x307, 0x0, 0x53, 0x302, 0x0, 0x53, 0x307, 0x0, + 0x53, 0x30c, 0x0, 0x53, 0x30c, 0x307, 0x0, 0x53, 0x323, 0x0, + 0x53, 0x323, 0x307, 0x0, 0x53, 0x326, 0x0, 0x53, 0x327, 0x0, + 0x54, 0x307, 0x0, 0x54, 0x30c, 0x0, 0x54, 0x323, 0x0, 0x54, + 0x326, 0x0, 0x54, 0x327, 0x0, 0x54, 0x32d, 0x0, 0x54, 0x331, + 0x0, 0x55, 0x300, 0x0, 0x55, 0x301, 0x0, 0x55, 0x302, 0x0, + 0x55, 0x303, 0x0, 0x55, 0x303, 0x301, 0x0, 0x55, 0x304, 0x0, + 0x55, 0x304, 0x308, 0x0, 0x55, 0x306, 0x0, 0x55, 0x308, 0x0, + 0x55, 0x308, 0x300, 0x0, 0x55, 0x308, 0x301, 0x0, 0x55, 0x308, + 0x304, 0x0, 0x55, 0x308, 0x30c, 0x0, 0x55, 0x309, 0x0, 0x55, + 0x30a, 0x0, 0x55, 0x30b, 0x0, 0x55, 0x30c, 0x0, 0x55, 0x30f, + 0x0, 0x55, 0x311, 0x0, 0x55, 0x31b, 0x0, 0x55, 0x31b, 0x300, + 0x0, 0x55, 0x31b, 0x301, 0x0, 0x55, 0x31b, 0x303, 0x0, 0x55, + 0x31b, 0x309, 0x0, 0x55, 0x31b, 0x323, 0x0, 0x55, 0x323, 0x0, + 0x55, 0x324, 0x0, 0x55, 0x328, 0x0, 0x55, 0x32d, 0x0, 0x55, + 0x330, 0x0, 0x56, 0x303, 0x0, 0x56, 0x323, 0x0, 0x57, 0x300, + 0x0, 0x57, 0x301, 0x0, 0x57, 0x302, 0x0, 0x57, 0x307, 0x0, + 0x57, 0x308, 0x0, 0x57, 0x323, 0x0, 0x58, 0x307, 0x0, 0x58, + 0x308, 0x0, 0x59, 0x300, 0x0, 0x59, 0x301, 0x0, 0x59, 0x302, + 0x0, 0x59, 0x303, 0x0, 0x59, 0x304, 0x0, 0x59, 0x307, 0x0, + 0x59, 0x308, 0x0, 0x59, 0x309, 0x0, 0x59, 0x323, 0x0, 0x5a, + 0x301, 0x0, 0x5a, 0x302, 0x0, 0x5a, 0x307, 0x0, 0x5a, 0x30c, + 0x0, 0x5a, 0x323, 0x0, 0x5a, 0x331, 0x0, 0x60, 0x0, 0x61, + 0x300, 0x0, 0x61, 0x301, 0x0, 0x61, 0x302, 0x0, 0x61, 0x302, + 0x300, 0x0, 0x61, 0x302, 0x301, 0x0, 0x61, 0x302, 0x303, 0x0, + 0x61, 0x302, 0x309, 0x0, 0x61, 0x303, 0x0, 0x61, 0x304, 0x0, + 0x61, 0x306, 0x0, 0x61, 0x306, 0x300, 0x0, 0x61, 0x306, 0x301, + 0x0, 0x61, 0x306, 0x303, 0x0, 0x61, 0x306, 0x309, 0x0, 0x61, + 0x307, 0x0, 0x61, 0x307, 0x304, 0x0, 0x61, 0x308, 0x0, 0x61, + 0x308, 0x304, 0x0, 0x61, 0x309, 0x0, 0x61, 0x30a, 0x0, 0x61, + 0x30a, 0x301, 0x0, 0x61, 0x30c, 0x0, 0x61, 0x30f, 0x0, 0x61, + 0x311, 0x0, 0x61, 0x323, 0x0, 0x61, 0x323, 0x302, 0x0, 0x61, + 0x323, 0x306, 0x0, 0x61, 0x325, 0x0, 0x61, 0x328, 0x0, 0x62, + 0x307, 0x0, 0x62, 0x323, 0x0, 0x62, 0x331, 0x0, 0x63, 0x301, + 0x0, 0x63, 0x302, 0x0, 0x63, 0x307, 0x0, 0x63, 0x30c, 0x0, + 0x63, 0x327, 0x0, 0x63, 0x327, 0x301, 0x0, 0x64, 0x307, 0x0, + 0x64, 0x30c, 0x0, 0x64, 0x323, 0x0, 0x64, 0x327, 0x0, 0x64, + 0x32d, 0x0, 0x64, 0x331, 0x0, 0x65, 0x300, 0x0, 0x65, 0x301, + 0x0, 0x65, 0x302, 0x0, 0x65, 0x302, 0x300, 0x0, 0x65, 0x302, + 0x301, 0x0, 0x65, 0x302, 0x303, 0x0, 0x65, 0x302, 0x309, 0x0, + 0x65, 0x303, 0x0, 0x65, 0x304, 0x0, 0x65, 0x304, 0x300, 0x0, + 0x65, 0x304, 0x301, 0x0, 0x65, 0x306, 0x0, 0x65, 0x307, 0x0, + 0x65, 0x308, 0x0, 0x65, 0x309, 0x0, 0x65, 0x30c, 0x0, 0x65, + 0x30f, 0x0, 0x65, 0x311, 0x0, 0x65, 0x323, 0x0, 0x65, 0x323, + 0x302, 0x0, 0x65, 0x327, 0x0, 0x65, 0x327, 0x306, 0x0, 0x65, + 0x328, 0x0, 0x65, 0x32d, 0x0, 0x65, 0x330, 0x0, 0x66, 0x307, + 0x0, 0x67, 0x301, 0x0, 0x67, 0x302, 0x0, 0x67, 0x304, 0x0, + 0x67, 0x306, 0x0, 0x67, 0x307, 0x0, 0x67, 0x30c, 0x0, 0x67, + 0x327, 0x0, 0x68, 0x302, 0x0, 0x68, 0x307, 0x0, 0x68, 0x308, + 0x0, 0x68, 0x30c, 0x0, 0x68, 0x323, 0x0, 0x68, 0x327, 0x0, + 0x68, 0x32e, 0x0, 0x68, 0x331, 0x0, 0x69, 0x300, 0x0, 0x69, + 0x301, 0x0, 0x69, 0x302, 0x0, 0x69, 0x303, 0x0, 0x69, 0x304, + 0x0, 0x69, 0x306, 0x0, 0x69, 0x308, 0x0, 0x69, 0x308, 0x301, + 0x0, 0x69, 0x309, 0x0, 0x69, 0x30c, 0x0, 0x69, 0x30f, 0x0, + 0x69, 0x311, 0x0, 0x69, 0x323, 0x0, 0x69, 0x328, 0x0, 0x69, + 0x330, 0x0, 0x6a, 0x302, 0x0, 0x6a, 0x30c, 0x0, 0x6b, 0x301, + 0x0, 0x6b, 0x30c, 0x0, 0x6b, 0x323, 0x0, 0x6b, 0x327, 0x0, + 0x6b, 0x331, 0x0, 0x6c, 0x301, 0x0, 0x6c, 0x30c, 0x0, 0x6c, + 0x323, 0x0, 0x6c, 0x323, 0x304, 0x0, 0x6c, 0x327, 0x0, 0x6c, + 0x32d, 0x0, 0x6c, 0x331, 0x0, 0x6d, 0x301, 0x0, 0x6d, 0x307, + 0x0, 0x6d, 0x323, 0x0, 0x6e, 0x300, 0x0, 0x6e, 0x301, 0x0, + 0x6e, 0x303, 0x0, 0x6e, 0x307, 0x0, 0x6e, 0x30c, 0x0, 0x6e, + 0x323, 0x0, 0x6e, 0x327, 0x0, 0x6e, 0x32d, 0x0, 0x6e, 0x331, + 0x0, 0x6f, 0x300, 0x0, 0x6f, 0x301, 0x0, 0x6f, 0x302, 0x0, + 0x6f, 0x302, 0x300, 0x0, 0x6f, 0x302, 0x301, 0x0, 0x6f, 0x302, + 0x303, 0x0, 0x6f, 0x302, 0x309, 0x0, 0x6f, 0x303, 0x0, 0x6f, + 0x303, 0x301, 0x0, 0x6f, 0x303, 0x304, 0x0, 0x6f, 0x303, 0x308, + 0x0, 0x6f, 0x304, 0x0, 0x6f, 0x304, 0x300, 0x0, 0x6f, 0x304, + 0x301, 0x0, 0x6f, 0x306, 0x0, 0x6f, 0x307, 0x0, 0x6f, 0x307, + 0x304, 0x0, 0x6f, 0x308, 0x0, 0x6f, 0x308, 0x304, 0x0, 0x6f, + 0x309, 0x0, 0x6f, 0x30b, 0x0, 0x6f, 0x30c, 0x0, 0x6f, 0x30f, + 0x0, 0x6f, 0x311, 0x0, 0x6f, 0x31b, 0x0, 0x6f, 0x31b, 0x300, + 0x0, 0x6f, 0x31b, 0x301, 0x0, 0x6f, 0x31b, 0x303, 0x0, 0x6f, + 0x31b, 0x309, 0x0, 0x6f, 0x31b, 0x323, 0x0, 0x6f, 0x323, 0x0, + 0x6f, 0x323, 0x302, 0x0, 0x6f, 0x328, 0x0, 0x6f, 0x328, 0x304, + 0x0, 0x70, 0x301, 0x0, 0x70, 0x307, 0x0, 0x72, 0x301, 0x0, + 0x72, 0x307, 0x0, 0x72, 0x30c, 0x0, 0x72, 0x30f, 0x0, 0x72, + 0x311, 0x0, 0x72, 0x323, 0x0, 0x72, 0x323, 0x304, 0x0, 0x72, + 0x327, 0x0, 0x72, 0x331, 0x0, 0x73, 0x301, 0x0, 0x73, 0x301, + 0x307, 0x0, 0x73, 0x302, 0x0, 0x73, 0x307, 0x0, 0x73, 0x30c, + 0x0, 0x73, 0x30c, 0x307, 0x0, 0x73, 0x323, 0x0, 0x73, 0x323, + 0x307, 0x0, 0x73, 0x326, 0x0, 0x73, 0x327, 0x0, 0x74, 0x307, + 0x0, 0x74, 0x308, 0x0, 0x74, 0x30c, 0x0, 0x74, 0x323, 0x0, + 0x74, 0x326, 0x0, 0x74, 0x327, 0x0, 0x74, 0x32d, 0x0, 0x74, + 0x331, 0x0, 0x75, 0x300, 0x0, 0x75, 0x301, 0x0, 0x75, 0x302, + 0x0, 0x75, 0x303, 0x0, 0x75, 0x303, 0x301, 0x0, 0x75, 0x304, + 0x0, 0x75, 0x304, 0x308, 0x0, 0x75, 0x306, 0x0, 0x75, 0x308, + 0x0, 0x75, 0x308, 0x300, 0x0, 0x75, 0x308, 0x301, 0x0, 0x75, + 0x308, 0x304, 0x0, 0x75, 0x308, 0x30c, 0x0, 0x75, 0x309, 0x0, + 0x75, 0x30a, 0x0, 0x75, 0x30b, 0x0, 0x75, 0x30c, 0x0, 0x75, + 0x30f, 0x0, 0x75, 0x311, 0x0, 0x75, 0x31b, 0x0, 0x75, 0x31b, + 0x300, 0x0, 0x75, 0x31b, 0x301, 0x0, 0x75, 0x31b, 0x303, 0x0, + 0x75, 0x31b, 0x309, 0x0, 0x75, 0x31b, 0x323, 0x0, 0x75, 0x323, + 0x0, 0x75, 0x324, 0x0, 0x75, 0x328, 0x0, 0x75, 0x32d, 0x0, + 0x75, 0x330, 0x0, 0x76, 0x303, 0x0, 0x76, 0x323, 0x0, 0x77, + 0x300, 0x0, 0x77, 0x301, 0x0, 0x77, 0x302, 0x0, 0x77, 0x307, + 0x0, 0x77, 0x308, 0x0, 0x77, 0x30a, 0x0, 0x77, 0x323, 0x0, + 0x78, 0x307, 0x0, 0x78, 0x308, 0x0, 0x79, 0x300, 0x0, 0x79, + 0x301, 0x0, 0x79, 0x302, 0x0, 0x79, 0x303, 0x0, 0x79, 0x304, + 0x0, 0x79, 0x307, 0x0, 0x79, 0x308, 0x0, 0x79, 0x309, 0x0, + 0x79, 0x30a, 0x0, 0x79, 0x323, 0x0, 0x7a, 0x301, 0x0, 0x7a, + 0x302, 0x0, 0x7a, 0x307, 0x0, 0x7a, 0x30c, 0x0, 0x7a, 0x323, + 0x0, 0x7a, 0x331, 0x0, 0xa8, 0x300, 0x0, 0xa8, 0x301, 0x0, + 0xa8, 0x342, 0x0, 0xb4, 0x0, 0xb7, 0x0, 0xc6, 0x301, 0x0, 0xc6, + 0x304, 0x0, 0xd8, 0x301, 0x0, 0xe6, 0x301, 0x0, 0xe6, 0x304, + 0x0, 0xf8, 0x301, 0x0, 0x17f, 0x307, 0x0, 0x1b7, 0x30c, 0x0, + 0x292, 0x30c, 0x0, 0x2b9, 0x0, 0x300, 0x0, 0x301, 0x0, 0x308, + 0x301, 0x0, 0x313, 0x0, 0x391, 0x300, 0x0, 0x391, 0x301, 0x0, + 0x391, 0x304, 0x0, 0x391, 0x306, 0x0, 0x391, 0x313, 0x0, 0x391, + 0x313, 0x300, 0x0, 0x391, 0x313, 0x300, 0x345, 0x0, 0x391, + 0x313, 0x301, 0x0, 0x391, 0x313, 0x301, 0x345, 0x0, 0x391, + 0x313, 0x342, 0x0, 0x391, 0x313, 0x342, 0x345, 0x0, 0x391, + 0x313, 0x345, 0x0, 0x391, 0x314, 0x0, 0x391, 0x314, 0x300, 0x0, + 0x391, 0x314, 0x300, 0x345, 0x0, 0x391, 0x314, 0x301, 0x0, + 0x391, 0x314, 0x301, 0x345, 0x0, 0x391, 0x314, 0x342, 0x0, + 0x391, 0x314, 0x342, 0x345, 0x0, 0x391, 0x314, 0x345, 0x0, + 0x391, 0x345, 0x0, 0x395, 0x300, 0x0, 0x395, 0x301, 0x0, 0x395, + 0x313, 0x0, 0x395, 0x313, 0x300, 0x0, 0x395, 0x313, 0x301, 0x0, + 0x395, 0x314, 0x0, 0x395, 0x314, 0x300, 0x0, 0x395, 0x314, + 0x301, 0x0, 0x397, 0x300, 0x0, 0x397, 0x301, 0x0, 0x397, 0x313, + 0x0, 0x397, 0x313, 0x300, 0x0, 0x397, 0x313, 0x300, 0x345, 0x0, + 0x397, 0x313, 0x301, 0x0, 0x397, 0x313, 0x301, 0x345, 0x0, + 0x397, 0x313, 0x342, 0x0, 0x397, 0x313, 0x342, 0x345, 0x0, + 0x397, 0x313, 0x345, 0x0, 0x397, 0x314, 0x0, 0x397, 0x314, + 0x300, 0x0, 0x397, 0x314, 0x300, 0x345, 0x0, 0x397, 0x314, + 0x301, 0x0, 0x397, 0x314, 0x301, 0x345, 0x0, 0x397, 0x314, + 0x342, 0x0, 0x397, 0x314, 0x342, 0x345, 0x0, 0x397, 0x314, + 0x345, 0x0, 0x397, 0x345, 0x0, 0x399, 0x300, 0x0, 0x399, 0x301, + 0x0, 0x399, 0x304, 0x0, 0x399, 0x306, 0x0, 0x399, 0x308, 0x0, + 0x399, 0x313, 0x0, 0x399, 0x313, 0x300, 0x0, 0x399, 0x313, + 0x301, 0x0, 0x399, 0x313, 0x342, 0x0, 0x399, 0x314, 0x0, 0x399, + 0x314, 0x300, 0x0, 0x399, 0x314, 0x301, 0x0, 0x399, 0x314, + 0x342, 0x0, 0x39f, 0x300, 0x0, 0x39f, 0x301, 0x0, 0x39f, 0x313, + 0x0, 0x39f, 0x313, 0x300, 0x0, 0x39f, 0x313, 0x301, 0x0, 0x39f, + 0x314, 0x0, 0x39f, 0x314, 0x300, 0x0, 0x39f, 0x314, 0x301, 0x0, + 0x3a1, 0x314, 0x0, 0x3a5, 0x300, 0x0, 0x3a5, 0x301, 0x0, 0x3a5, + 0x304, 0x0, 0x3a5, 0x306, 0x0, 0x3a5, 0x308, 0x0, 0x3a5, 0x314, + 0x0, 0x3a5, 0x314, 0x300, 0x0, 0x3a5, 0x314, 0x301, 0x0, 0x3a5, + 0x314, 0x342, 0x0, 0x3a9, 0x0, 0x3a9, 0x300, 0x0, 0x3a9, 0x301, + 0x0, 0x3a9, 0x313, 0x0, 0x3a9, 0x313, 0x300, 0x0, 0x3a9, 0x313, + 0x300, 0x345, 0x0, 0x3a9, 0x313, 0x301, 0x0, 0x3a9, 0x313, + 0x301, 0x345, 0x0, 0x3a9, 0x313, 0x342, 0x0, 0x3a9, 0x313, + 0x342, 0x345, 0x0, 0x3a9, 0x313, 0x345, 0x0, 0x3a9, 0x314, 0x0, + 0x3a9, 0x314, 0x300, 0x0, 0x3a9, 0x314, 0x300, 0x345, 0x0, + 0x3a9, 0x314, 0x301, 0x0, 0x3a9, 0x314, 0x301, 0x345, 0x0, + 0x3a9, 0x314, 0x342, 0x0, 0x3a9, 0x314, 0x342, 0x345, 0x0, + 0x3a9, 0x314, 0x345, 0x0, 0x3a9, 0x345, 0x0, 0x3b1, 0x300, 0x0, + 0x3b1, 0x300, 0x345, 0x0, 0x3b1, 0x301, 0x0, 0x3b1, 0x301, + 0x345, 0x0, 0x3b1, 0x304, 0x0, 0x3b1, 0x306, 0x0, 0x3b1, 0x313, + 0x0, 0x3b1, 0x313, 0x300, 0x0, 0x3b1, 0x313, 0x300, 0x345, 0x0, + 0x3b1, 0x313, 0x301, 0x0, 0x3b1, 0x313, 0x301, 0x345, 0x0, + 0x3b1, 0x313, 0x342, 0x0, 0x3b1, 0x313, 0x342, 0x345, 0x0, + 0x3b1, 0x313, 0x345, 0x0, 0x3b1, 0x314, 0x0, 0x3b1, 0x314, + 0x300, 0x0, 0x3b1, 0x314, 0x300, 0x345, 0x0, 0x3b1, 0x314, + 0x301, 0x0, 0x3b1, 0x314, 0x301, 0x345, 0x0, 0x3b1, 0x314, + 0x342, 0x0, 0x3b1, 0x314, 0x342, 0x345, 0x0, 0x3b1, 0x314, + 0x345, 0x0, 0x3b1, 0x342, 0x0, 0x3b1, 0x342, 0x345, 0x0, 0x3b1, + 0x345, 0x0, 0x3b5, 0x300, 0x0, 0x3b5, 0x301, 0x0, 0x3b5, 0x313, + 0x0, 0x3b5, 0x313, 0x300, 0x0, 0x3b5, 0x313, 0x301, 0x0, 0x3b5, + 0x314, 0x0, 0x3b5, 0x314, 0x300, 0x0, 0x3b5, 0x314, 0x301, 0x0, + 0x3b7, 0x300, 0x0, 0x3b7, 0x300, 0x345, 0x0, 0x3b7, 0x301, 0x0, + 0x3b7, 0x301, 0x345, 0x0, 0x3b7, 0x313, 0x0, 0x3b7, 0x313, + 0x300, 0x0, 0x3b7, 0x313, 0x300, 0x345, 0x0, 0x3b7, 0x313, + 0x301, 0x0, 0x3b7, 0x313, 0x301, 0x345, 0x0, 0x3b7, 0x313, + 0x342, 0x0, 0x3b7, 0x313, 0x342, 0x345, 0x0, 0x3b7, 0x313, + 0x345, 0x0, 0x3b7, 0x314, 0x0, 0x3b7, 0x314, 0x300, 0x0, 0x3b7, + 0x314, 0x300, 0x345, 0x0, 0x3b7, 0x314, 0x301, 0x0, 0x3b7, + 0x314, 0x301, 0x345, 0x0, 0x3b7, 0x314, 0x342, 0x0, 0x3b7, + 0x314, 0x342, 0x345, 0x0, 0x3b7, 0x314, 0x345, 0x0, 0x3b7, + 0x342, 0x0, 0x3b7, 0x342, 0x345, 0x0, 0x3b7, 0x345, 0x0, 0x3b9, + 0x0, 0x3b9, 0x300, 0x0, 0x3b9, 0x301, 0x0, 0x3b9, 0x304, 0x0, + 0x3b9, 0x306, 0x0, 0x3b9, 0x308, 0x0, 0x3b9, 0x308, 0x300, 0x0, + 0x3b9, 0x308, 0x301, 0x0, 0x3b9, 0x308, 0x342, 0x0, 0x3b9, + 0x313, 0x0, 0x3b9, 0x313, 0x300, 0x0, 0x3b9, 0x313, 0x301, 0x0, + 0x3b9, 0x313, 0x342, 0x0, 0x3b9, 0x314, 0x0, 0x3b9, 0x314, + 0x300, 0x0, 0x3b9, 0x314, 0x301, 0x0, 0x3b9, 0x314, 0x342, 0x0, + 0x3b9, 0x342, 0x0, 0x3bf, 0x300, 0x0, 0x3bf, 0x301, 0x0, 0x3bf, + 0x313, 0x0, 0x3bf, 0x313, 0x300, 0x0, 0x3bf, 0x313, 0x301, 0x0, + 0x3bf, 0x314, 0x0, 0x3bf, 0x314, 0x300, 0x0, 0x3bf, 0x314, + 0x301, 0x0, 0x3c1, 0x313, 0x0, 0x3c1, 0x314, 0x0, 0x3c5, 0x300, + 0x0, 0x3c5, 0x301, 0x0, 0x3c5, 0x304, 0x0, 0x3c5, 0x306, 0x0, + 0x3c5, 0x308, 0x0, 0x3c5, 0x308, 0x300, 0x0, 0x3c5, 0x308, + 0x301, 0x0, 0x3c5, 0x308, 0x342, 0x0, 0x3c5, 0x313, 0x0, 0x3c5, + 0x313, 0x300, 0x0, 0x3c5, 0x313, 0x301, 0x0, 0x3c5, 0x313, + 0x342, 0x0, 0x3c5, 0x314, 0x0, 0x3c5, 0x314, 0x300, 0x0, 0x3c5, + 0x314, 0x301, 0x0, 0x3c5, 0x314, 0x342, 0x0, 0x3c5, 0x342, 0x0, + 0x3c9, 0x300, 0x0, 0x3c9, 0x300, 0x345, 0x0, 0x3c9, 0x301, 0x0, + 0x3c9, 0x301, 0x345, 0x0, 0x3c9, 0x313, 0x0, 0x3c9, 0x313, + 0x300, 0x0, 0x3c9, 0x313, 0x300, 0x345, 0x0, 0x3c9, 0x313, + 0x301, 0x0, 0x3c9, 0x313, 0x301, 0x345, 0x0, 0x3c9, 0x313, + 0x342, 0x0, 0x3c9, 0x313, 0x342, 0x345, 0x0, 0x3c9, 0x313, + 0x345, 0x0, 0x3c9, 0x314, 0x0, 0x3c9, 0x314, 0x300, 0x0, 0x3c9, + 0x314, 0x300, 0x345, 0x0, 0x3c9, 0x314, 0x301, 0x0, 0x3c9, + 0x314, 0x301, 0x345, 0x0, 0x3c9, 0x314, 0x342, 0x0, 0x3c9, + 0x314, 0x342, 0x345, 0x0, 0x3c9, 0x314, 0x345, 0x0, 0x3c9, + 0x342, 0x0, 0x3c9, 0x342, 0x345, 0x0, 0x3c9, 0x345, 0x0, 0x3d2, + 0x301, 0x0, 0x3d2, 0x308, 0x0, 0x406, 0x308, 0x0, 0x410, 0x306, + 0x0, 0x410, 0x308, 0x0, 0x413, 0x301, 0x0, 0x415, 0x300, 0x0, + 0x415, 0x306, 0x0, 0x415, 0x308, 0x0, 0x416, 0x306, 0x0, 0x416, + 0x308, 0x0, 0x417, 0x308, 0x0, 0x418, 0x300, 0x0, 0x418, 0x304, + 0x0, 0x418, 0x306, 0x0, 0x418, 0x308, 0x0, 0x41a, 0x301, 0x0, + 0x41e, 0x308, 0x0, 0x423, 0x304, 0x0, 0x423, 0x306, 0x0, 0x423, + 0x308, 0x0, 0x423, 0x30b, 0x0, 0x427, 0x308, 0x0, 0x42b, 0x308, + 0x0, 0x42d, 0x308, 0x0, 0x430, 0x306, 0x0, 0x430, 0x308, 0x0, + 0x433, 0x301, 0x0, 0x435, 0x300, 0x0, 0x435, 0x306, 0x0, 0x435, + 0x308, 0x0, 0x436, 0x306, 0x0, 0x436, 0x308, 0x0, 0x437, 0x308, + 0x0, 0x438, 0x300, 0x0, 0x438, 0x304, 0x0, 0x438, 0x306, 0x0, + 0x438, 0x308, 0x0, 0x43a, 0x301, 0x0, 0x43e, 0x308, 0x0, 0x443, + 0x304, 0x0, 0x443, 0x306, 0x0, 0x443, 0x308, 0x0, 0x443, 0x30b, + 0x0, 0x447, 0x308, 0x0, 0x44b, 0x308, 0x0, 0x44d, 0x308, 0x0, + 0x456, 0x308, 0x0, 0x474, 0x30f, 0x0, 0x475, 0x30f, 0x0, 0x4d8, + 0x308, 0x0, 0x4d9, 0x308, 0x0, 0x4e8, 0x308, 0x0, 0x4e9, 0x308, + 0x0, 0x5d0, 0x5b7, 0x0, 0x5d0, 0x5b8, 0x0, 0x5d0, 0x5bc, 0x0, + 0x5d1, 0x5bc, 0x0, 0x5d1, 0x5bf, 0x0, 0x5d2, 0x5bc, 0x0, 0x5d3, + 0x5bc, 0x0, 0x5d4, 0x5bc, 0x0, 0x5d5, 0x5b9, 0x0, 0x5d5, 0x5bc, + 0x0, 0x5d6, 0x5bc, 0x0, 0x5d8, 0x5bc, 0x0, 0x5d9, 0x5b4, 0x0, + 0x5d9, 0x5bc, 0x0, 0x5da, 0x5bc, 0x0, 0x5db, 0x5bc, 0x0, 0x5db, + 0x5bf, 0x0, 0x5dc, 0x5bc, 0x0, 0x5de, 0x5bc, 0x0, 0x5e0, 0x5bc, + 0x0, 0x5e1, 0x5bc, 0x0, 0x5e3, 0x5bc, 0x0, 0x5e4, 0x5bc, 0x0, + 0x5e4, 0x5bf, 0x0, 0x5e6, 0x5bc, 0x0, 0x5e7, 0x5bc, 0x0, 0x5e8, + 0x5bc, 0x0, 0x5e9, 0x5bc, 0x0, 0x5e9, 0x5bc, 0x5c1, 0x0, 0x5e9, + 0x5bc, 0x5c2, 0x0, 0x5e9, 0x5c1, 0x0, 0x5e9, 0x5c2, 0x0, 0x5ea, + 0x5bc, 0x0, 0x5f2, 0x5b7, 0x0, 0x627, 0x653, 0x0, 0x627, 0x654, + 0x0, 0x627, 0x655, 0x0, 0x648, 0x654, 0x0, 0x64a, 0x654, 0x0, + 0x6c1, 0x654, 0x0, 0x6d2, 0x654, 0x0, 0x6d5, 0x654, 0x0, 0x915, + 0x93c, 0x0, 0x916, 0x93c, 0x0, 0x917, 0x93c, 0x0, 0x91c, 0x93c, + 0x0, 0x921, 0x93c, 0x0, 0x922, 0x93c, 0x0, 0x928, 0x93c, 0x0, + 0x92b, 0x93c, 0x0, 0x92f, 0x93c, 0x0, 0x930, 0x93c, 0x0, 0x933, + 0x93c, 0x0, 0x9a1, 0x9bc, 0x0, 0x9a2, 0x9bc, 0x0, 0x9af, 0x9bc, + 0x0, 0x9c7, 0x9be, 0x0, 0x9c7, 0x9d7, 0x0, 0xa16, 0xa3c, 0x0, + 0xa17, 0xa3c, 0x0, 0xa1c, 0xa3c, 0x0, 0xa2b, 0xa3c, 0x0, 0xa32, + 0xa3c, 0x0, 0xa38, 0xa3c, 0x0, 0xb21, 0xb3c, 0x0, 0xb22, 0xb3c, + 0x0, 0xb47, 0xb3e, 0x0, 0xb47, 0xb56, 0x0, 0xb47, 0xb57, 0x0, + 0xb92, 0xbd7, 0x0, 0xbc6, 0xbbe, 0x0, 0xbc6, 0xbd7, 0x0, 0xbc7, + 0xbbe, 0x0, 0xc46, 0xc56, 0x0, 0xcbf, 0xcd5, 0x0, 0xcc6, 0xcc2, + 0x0, 0xcc6, 0xcc2, 0xcd5, 0x0, 0xcc6, 0xcd5, 0x0, 0xcc6, 0xcd6, + 0x0, 0xd46, 0xd3e, 0x0, 0xd46, 0xd57, 0x0, 0xd47, 0xd3e, 0x0, + 0xdd9, 0xdca, 0x0, 0xdd9, 0xdcf, 0x0, 0xdd9, 0xdcf, 0xdca, 0x0, + 0xdd9, 0xddf, 0x0, 0xf40, 0xfb5, 0x0, 0xf42, 0xfb7, 0x0, 0xf4c, + 0xfb7, 0x0, 0xf51, 0xfb7, 0x0, 0xf56, 0xfb7, 0x0, 0xf5b, 0xfb7, + 0x0, 0xf71, 0xf72, 0x0, 0xf71, 0xf74, 0x0, 0xf71, 0xf80, 0x0, + 0xf90, 0xfb5, 0x0, 0xf92, 0xfb7, 0x0, 0xf9c, 0xfb7, 0x0, 0xfa1, + 0xfb7, 0x0, 0xfa6, 0xfb7, 0x0, 0xfab, 0xfb7, 0x0, 0xfb2, 0xf80, + 0x0, 0xfb3, 0xf80, 0x0, 0x1025, 0x102e, 0x0, 0x1b05, 0x1b35, + 0x0, 0x1b07, 0x1b35, 0x0, 0x1b09, 0x1b35, 0x0, 0x1b0b, 0x1b35, + 0x0, 0x1b0d, 0x1b35, 0x0, 0x1b11, 0x1b35, 0x0, 0x1b3a, 0x1b35, + 0x0, 0x1b3c, 0x1b35, 0x0, 0x1b3e, 0x1b35, 0x0, 0x1b3f, 0x1b35, + 0x0, 0x1b42, 0x1b35, 0x0, 0x1fbf, 0x300, 0x0, 0x1fbf, 0x301, + 0x0, 0x1fbf, 0x342, 0x0, 0x1ffe, 0x300, 0x0, 0x1ffe, 0x301, + 0x0, 0x1ffe, 0x342, 0x0, 0x2002, 0x0, 0x2003, 0x0, 0x2190, + 0x338, 0x0, 0x2192, 0x338, 0x0, 0x2194, 0x338, 0x0, 0x21d0, + 0x338, 0x0, 0x21d2, 0x338, 0x0, 0x21d4, 0x338, 0x0, 0x2203, + 0x338, 0x0, 0x2208, 0x338, 0x0, 0x220b, 0x338, 0x0, 0x2223, + 0x338, 0x0, 0x2225, 0x338, 0x0, 0x223c, 0x338, 0x0, 0x2243, + 0x338, 0x0, 0x2245, 0x338, 0x0, 0x2248, 0x338, 0x0, 0x224d, + 0x338, 0x0, 0x2261, 0x338, 0x0, 0x2264, 0x338, 0x0, 0x2265, + 0x338, 0x0, 0x2272, 0x338, 0x0, 0x2273, 0x338, 0x0, 0x2276, + 0x338, 0x0, 0x2277, 0x338, 0x0, 0x227a, 0x338, 0x0, 0x227b, + 0x338, 0x0, 0x227c, 0x338, 0x0, 0x227d, 0x338, 0x0, 0x2282, + 0x338, 0x0, 0x2283, 0x338, 0x0, 0x2286, 0x338, 0x0, 0x2287, + 0x338, 0x0, 0x2291, 0x338, 0x0, 0x2292, 0x338, 0x0, 0x22a2, + 0x338, 0x0, 0x22a8, 0x338, 0x0, 0x22a9, 0x338, 0x0, 0x22ab, + 0x338, 0x0, 0x22b2, 0x338, 0x0, 0x22b3, 0x338, 0x0, 0x22b4, + 0x338, 0x0, 0x22b5, 0x338, 0x0, 0x2add, 0x338, 0x0, 0x3008, + 0x0, 0x3009, 0x0, 0x3046, 0x3099, 0x0, 0x304b, 0x3099, 0x0, + 0x304d, 0x3099, 0x0, 0x304f, 0x3099, 0x0, 0x3051, 0x3099, 0x0, + 0x3053, 0x3099, 0x0, 0x3055, 0x3099, 0x0, 0x3057, 0x3099, 0x0, + 0x3059, 0x3099, 0x0, 0x305b, 0x3099, 0x0, 0x305d, 0x3099, 0x0, + 0x305f, 0x3099, 0x0, 0x3061, 0x3099, 0x0, 0x3064, 0x3099, 0x0, + 0x3066, 0x3099, 0x0, 0x3068, 0x3099, 0x0, 0x306f, 0x3099, 0x0, + 0x306f, 0x309a, 0x0, 0x3072, 0x3099, 0x0, 0x3072, 0x309a, 0x0, + 0x3075, 0x3099, 0x0, 0x3075, 0x309a, 0x0, 0x3078, 0x3099, 0x0, + 0x3078, 0x309a, 0x0, 0x307b, 0x3099, 0x0, 0x307b, 0x309a, 0x0, + 0x309d, 0x3099, 0x0, 0x30a6, 0x3099, 0x0, 0x30ab, 0x3099, 0x0, + 0x30ad, 0x3099, 0x0, 0x30af, 0x3099, 0x0, 0x30b1, 0x3099, 0x0, + 0x30b3, 0x3099, 0x0, 0x30b5, 0x3099, 0x0, 0x30b7, 0x3099, 0x0, + 0x30b9, 0x3099, 0x0, 0x30bb, 0x3099, 0x0, 0x30bd, 0x3099, 0x0, + 0x30bf, 0x3099, 0x0, 0x30c1, 0x3099, 0x0, 0x30c4, 0x3099, 0x0, + 0x30c6, 0x3099, 0x0, 0x30c8, 0x3099, 0x0, 0x30cf, 0x3099, 0x0, + 0x30cf, 0x309a, 0x0, 0x30d2, 0x3099, 0x0, 0x30d2, 0x309a, 0x0, + 0x30d5, 0x3099, 0x0, 0x30d5, 0x309a, 0x0, 0x30d8, 0x3099, 0x0, + 0x30d8, 0x309a, 0x0, 0x30db, 0x3099, 0x0, 0x30db, 0x309a, 0x0, + 0x30ef, 0x3099, 0x0, 0x30f0, 0x3099, 0x0, 0x30f1, 0x3099, 0x0, + 0x30f2, 0x3099, 0x0, 0x30fd, 0x3099, 0x0, 0x349e, 0x0, 0x34b9, + 0x0, 0x34bb, 0x0, 0x34df, 0x0, 0x3515, 0x0, 0x36ee, 0x0, + 0x36fc, 0x0, 0x3781, 0x0, 0x382f, 0x0, 0x3862, 0x0, 0x387c, + 0x0, 0x38c7, 0x0, 0x38e3, 0x0, 0x391c, 0x0, 0x393a, 0x0, + 0x3a2e, 0x0, 0x3a6c, 0x0, 0x3ae4, 0x0, 0x3b08, 0x0, 0x3b19, + 0x0, 0x3b49, 0x0, 0x3b9d, 0x0, 0x3c18, 0x0, 0x3c4e, 0x0, + 0x3d33, 0x0, 0x3d96, 0x0, 0x3eac, 0x0, 0x3eb8, 0x0, 0x3f1b, + 0x0, 0x3ffc, 0x0, 0x4008, 0x0, 0x4018, 0x0, 0x4039, 0x0, + 0x4046, 0x0, 0x4096, 0x0, 0x40e3, 0x0, 0x412f, 0x0, 0x4202, + 0x0, 0x4227, 0x0, 0x42a0, 0x0, 0x4301, 0x0, 0x4334, 0x0, + 0x4359, 0x0, 0x43d5, 0x0, 0x43d9, 0x0, 0x440b, 0x0, 0x446b, + 0x0, 0x452b, 0x0, 0x455d, 0x0, 0x4561, 0x0, 0x456b, 0x0, + 0x45d7, 0x0, 0x45f9, 0x0, 0x4635, 0x0, 0x46be, 0x0, 0x46c7, + 0x0, 0x4995, 0x0, 0x49e6, 0x0, 0x4a6e, 0x0, 0x4a76, 0x0, + 0x4ab2, 0x0, 0x4b33, 0x0, 0x4bce, 0x0, 0x4cce, 0x0, 0x4ced, + 0x0, 0x4cf8, 0x0, 0x4d56, 0x0, 0x4e0d, 0x0, 0x4e26, 0x0, + 0x4e32, 0x0, 0x4e38, 0x0, 0x4e39, 0x0, 0x4e3d, 0x0, 0x4e41, + 0x0, 0x4e82, 0x0, 0x4e86, 0x0, 0x4eae, 0x0, 0x4ec0, 0x0, + 0x4ecc, 0x0, 0x4ee4, 0x0, 0x4f60, 0x0, 0x4f80, 0x0, 0x4f86, + 0x0, 0x4f8b, 0x0, 0x4fae, 0x0, 0x4fbb, 0x0, 0x4fbf, 0x0, + 0x5002, 0x0, 0x502b, 0x0, 0x507a, 0x0, 0x5099, 0x0, 0x50cf, + 0x0, 0x50da, 0x0, 0x50e7, 0x0, 0x5140, 0x0, 0x5145, 0x0, + 0x514d, 0x0, 0x5154, 0x0, 0x5164, 0x0, 0x5167, 0x0, 0x5168, + 0x0, 0x5169, 0x0, 0x516d, 0x0, 0x5177, 0x0, 0x5180, 0x0, + 0x518d, 0x0, 0x5192, 0x0, 0x5195, 0x0, 0x5197, 0x0, 0x51a4, + 0x0, 0x51ac, 0x0, 0x51b5, 0x0, 0x51b7, 0x0, 0x51c9, 0x0, + 0x51cc, 0x0, 0x51dc, 0x0, 0x51de, 0x0, 0x51f5, 0x0, 0x5203, + 0x0, 0x5207, 0x0, 0x5217, 0x0, 0x5229, 0x0, 0x523a, 0x0, + 0x523b, 0x0, 0x5246, 0x0, 0x5272, 0x0, 0x5277, 0x0, 0x5289, + 0x0, 0x529b, 0x0, 0x52a3, 0x0, 0x52b3, 0x0, 0x52c7, 0x0, + 0x52c9, 0x0, 0x52d2, 0x0, 0x52de, 0x0, 0x52e4, 0x0, 0x52f5, + 0x0, 0x52fa, 0x0, 0x5305, 0x0, 0x5306, 0x0, 0x5317, 0x0, + 0x533f, 0x0, 0x5349, 0x0, 0x5351, 0x0, 0x535a, 0x0, 0x5373, + 0x0, 0x5375, 0x0, 0x537d, 0x0, 0x537f, 0x0, 0x53c3, 0x0, + 0x53ca, 0x0, 0x53df, 0x0, 0x53e5, 0x0, 0x53eb, 0x0, 0x53f1, + 0x0, 0x5406, 0x0, 0x540f, 0x0, 0x541d, 0x0, 0x5438, 0x0, + 0x5442, 0x0, 0x5448, 0x0, 0x5468, 0x0, 0x549e, 0x0, 0x54a2, + 0x0, 0x54bd, 0x0, 0x54f6, 0x0, 0x5510, 0x0, 0x5553, 0x0, + 0x5555, 0x0, 0x5563, 0x0, 0x5584, 0x0, 0x5587, 0x0, 0x5599, + 0x0, 0x559d, 0x0, 0x55ab, 0x0, 0x55b3, 0x0, 0x55c0, 0x0, + 0x55c2, 0x0, 0x55e2, 0x0, 0x5606, 0x0, 0x5651, 0x0, 0x5668, + 0x0, 0x5674, 0x0, 0x56f9, 0x0, 0x5716, 0x0, 0x5717, 0x0, + 0x578b, 0x0, 0x57ce, 0x0, 0x57f4, 0x0, 0x580d, 0x0, 0x5831, + 0x0, 0x5832, 0x0, 0x5840, 0x0, 0x585a, 0x0, 0x585e, 0x0, + 0x58a8, 0x0, 0x58ac, 0x0, 0x58b3, 0x0, 0x58d8, 0x0, 0x58df, + 0x0, 0x58ee, 0x0, 0x58f2, 0x0, 0x58f7, 0x0, 0x5906, 0x0, + 0x591a, 0x0, 0x5922, 0x0, 0x5944, 0x0, 0x5948, 0x0, 0x5951, + 0x0, 0x5954, 0x0, 0x5962, 0x0, 0x5973, 0x0, 0x59d8, 0x0, + 0x59ec, 0x0, 0x5a1b, 0x0, 0x5a27, 0x0, 0x5a62, 0x0, 0x5a66, + 0x0, 0x5ab5, 0x0, 0x5b08, 0x0, 0x5b28, 0x0, 0x5b3e, 0x0, + 0x5b85, 0x0, 0x5bc3, 0x0, 0x5bd8, 0x0, 0x5be7, 0x0, 0x5bee, + 0x0, 0x5bf3, 0x0, 0x5bff, 0x0, 0x5c06, 0x0, 0x5c22, 0x0, + 0x5c3f, 0x0, 0x5c60, 0x0, 0x5c62, 0x0, 0x5c64, 0x0, 0x5c65, + 0x0, 0x5c6e, 0x0, 0x5c8d, 0x0, 0x5cc0, 0x0, 0x5d19, 0x0, + 0x5d43, 0x0, 0x5d50, 0x0, 0x5d6b, 0x0, 0x5d6e, 0x0, 0x5d7c, + 0x0, 0x5db2, 0x0, 0x5dba, 0x0, 0x5de1, 0x0, 0x5de2, 0x0, + 0x5dfd, 0x0, 0x5e28, 0x0, 0x5e3d, 0x0, 0x5e69, 0x0, 0x5e74, + 0x0, 0x5ea6, 0x0, 0x5eb0, 0x0, 0x5eb3, 0x0, 0x5eb6, 0x0, + 0x5ec9, 0x0, 0x5eca, 0x0, 0x5ed2, 0x0, 0x5ed3, 0x0, 0x5ed9, + 0x0, 0x5eec, 0x0, 0x5efe, 0x0, 0x5f04, 0x0, 0x5f22, 0x0, + 0x5f53, 0x0, 0x5f62, 0x0, 0x5f69, 0x0, 0x5f6b, 0x0, 0x5f8b, + 0x0, 0x5f9a, 0x0, 0x5fa9, 0x0, 0x5fad, 0x0, 0x5fcd, 0x0, + 0x5fd7, 0x0, 0x5ff5, 0x0, 0x5ff9, 0x0, 0x6012, 0x0, 0x601c, + 0x0, 0x6075, 0x0, 0x6081, 0x0, 0x6094, 0x0, 0x60c7, 0x0, + 0x60d8, 0x0, 0x60e1, 0x0, 0x6108, 0x0, 0x6144, 0x0, 0x6148, + 0x0, 0x614c, 0x0, 0x614e, 0x0, 0x6160, 0x0, 0x6168, 0x0, + 0x617a, 0x0, 0x618e, 0x0, 0x6190, 0x0, 0x61a4, 0x0, 0x61af, + 0x0, 0x61b2, 0x0, 0x61de, 0x0, 0x61f2, 0x0, 0x61f6, 0x0, + 0x6200, 0x0, 0x6210, 0x0, 0x621b, 0x0, 0x622e, 0x0, 0x6234, + 0x0, 0x625d, 0x0, 0x62b1, 0x0, 0x62c9, 0x0, 0x62cf, 0x0, + 0x62d3, 0x0, 0x62d4, 0x0, 0x62fc, 0x0, 0x62fe, 0x0, 0x633d, + 0x0, 0x6350, 0x0, 0x6368, 0x0, 0x637b, 0x0, 0x6383, 0x0, + 0x63a0, 0x0, 0x63a9, 0x0, 0x63c4, 0x0, 0x63c5, 0x0, 0x63e4, + 0x0, 0x641c, 0x0, 0x6422, 0x0, 0x6452, 0x0, 0x6469, 0x0, + 0x6477, 0x0, 0x647e, 0x0, 0x649a, 0x0, 0x649d, 0x0, 0x64c4, + 0x0, 0x654f, 0x0, 0x6556, 0x0, 0x656c, 0x0, 0x6578, 0x0, + 0x6599, 0x0, 0x65c5, 0x0, 0x65e2, 0x0, 0x65e3, 0x0, 0x6613, + 0x0, 0x6649, 0x0, 0x6674, 0x0, 0x6688, 0x0, 0x6691, 0x0, + 0x669c, 0x0, 0x66b4, 0x0, 0x66c6, 0x0, 0x66f4, 0x0, 0x66f8, + 0x0, 0x6700, 0x0, 0x6717, 0x0, 0x671b, 0x0, 0x6721, 0x0, + 0x674e, 0x0, 0x6753, 0x0, 0x6756, 0x0, 0x675e, 0x0, 0x677b, + 0x0, 0x6785, 0x0, 0x6797, 0x0, 0x67f3, 0x0, 0x67fa, 0x0, + 0x6817, 0x0, 0x681f, 0x0, 0x6852, 0x0, 0x6881, 0x0, 0x6885, + 0x0, 0x688e, 0x0, 0x68a8, 0x0, 0x6914, 0x0, 0x6942, 0x0, + 0x69a3, 0x0, 0x69ea, 0x0, 0x6a02, 0x0, 0x6a13, 0x0, 0x6aa8, + 0x0, 0x6ad3, 0x0, 0x6adb, 0x0, 0x6b04, 0x0, 0x6b21, 0x0, + 0x6b54, 0x0, 0x6b72, 0x0, 0x6b77, 0x0, 0x6b79, 0x0, 0x6b9f, + 0x0, 0x6bae, 0x0, 0x6bba, 0x0, 0x6bbb, 0x0, 0x6c4e, 0x0, + 0x6c67, 0x0, 0x6c88, 0x0, 0x6cbf, 0x0, 0x6ccc, 0x0, 0x6ccd, + 0x0, 0x6ce5, 0x0, 0x6d16, 0x0, 0x6d1b, 0x0, 0x6d1e, 0x0, + 0x6d34, 0x0, 0x6d3e, 0x0, 0x6d41, 0x0, 0x6d69, 0x0, 0x6d6a, + 0x0, 0x6d77, 0x0, 0x6d78, 0x0, 0x6d85, 0x0, 0x6dcb, 0x0, + 0x6dda, 0x0, 0x6dea, 0x0, 0x6df9, 0x0, 0x6e1a, 0x0, 0x6e2f, + 0x0, 0x6e6e, 0x0, 0x6e9c, 0x0, 0x6eba, 0x0, 0x6ec7, 0x0, + 0x6ecb, 0x0, 0x6ed1, 0x0, 0x6edb, 0x0, 0x6f0f, 0x0, 0x6f22, + 0x0, 0x6f23, 0x0, 0x6f6e, 0x0, 0x6fc6, 0x0, 0x6feb, 0x0, + 0x6ffe, 0x0, 0x701b, 0x0, 0x701e, 0x0, 0x7039, 0x0, 0x704a, + 0x0, 0x7070, 0x0, 0x7077, 0x0, 0x707d, 0x0, 0x7099, 0x0, + 0x70ad, 0x0, 0x70c8, 0x0, 0x70d9, 0x0, 0x7145, 0x0, 0x7149, + 0x0, 0x716e, 0x0, 0x719c, 0x0, 0x71ce, 0x0, 0x71d0, 0x0, + 0x7210, 0x0, 0x721b, 0x0, 0x7228, 0x0, 0x722b, 0x0, 0x7235, + 0x0, 0x7250, 0x0, 0x7262, 0x0, 0x7280, 0x0, 0x7295, 0x0, + 0x72af, 0x0, 0x72c0, 0x0, 0x72fc, 0x0, 0x732a, 0x0, 0x7375, + 0x0, 0x737a, 0x0, 0x7387, 0x0, 0x738b, 0x0, 0x73a5, 0x0, + 0x73b2, 0x0, 0x73de, 0x0, 0x7406, 0x0, 0x7409, 0x0, 0x7422, + 0x0, 0x7447, 0x0, 0x745c, 0x0, 0x7469, 0x0, 0x7471, 0x0, + 0x7485, 0x0, 0x7489, 0x0, 0x7498, 0x0, 0x74ca, 0x0, 0x7506, + 0x0, 0x7524, 0x0, 0x753b, 0x0, 0x753e, 0x0, 0x7559, 0x0, + 0x7565, 0x0, 0x7570, 0x0, 0x75e2, 0x0, 0x7610, 0x0, 0x761d, + 0x0, 0x761f, 0x0, 0x7642, 0x0, 0x7669, 0x0, 0x76ca, 0x0, + 0x76db, 0x0, 0x76e7, 0x0, 0x76f4, 0x0, 0x7701, 0x0, 0x771e, + 0x0, 0x771f, 0x0, 0x7740, 0x0, 0x774a, 0x0, 0x778b, 0x0, + 0x77a7, 0x0, 0x784e, 0x0, 0x786b, 0x0, 0x788c, 0x0, 0x7891, + 0x0, 0x78ca, 0x0, 0x78cc, 0x0, 0x78fb, 0x0, 0x792a, 0x0, + 0x793c, 0x0, 0x793e, 0x0, 0x7948, 0x0, 0x7949, 0x0, 0x7950, + 0x0, 0x7956, 0x0, 0x795d, 0x0, 0x795e, 0x0, 0x7965, 0x0, + 0x797f, 0x0, 0x798d, 0x0, 0x798e, 0x0, 0x798f, 0x0, 0x79ae, + 0x0, 0x79ca, 0x0, 0x79eb, 0x0, 0x7a1c, 0x0, 0x7a40, 0x0, + 0x7a4a, 0x0, 0x7a4f, 0x0, 0x7a81, 0x0, 0x7ab1, 0x0, 0x7acb, + 0x0, 0x7aee, 0x0, 0x7b20, 0x0, 0x7bc0, 0x0, 0x7bc6, 0x0, + 0x7bc9, 0x0, 0x7c3e, 0x0, 0x7c60, 0x0, 0x7c7b, 0x0, 0x7c92, + 0x0, 0x7cbe, 0x0, 0x7cd2, 0x0, 0x7cd6, 0x0, 0x7ce3, 0x0, + 0x7ce7, 0x0, 0x7ce8, 0x0, 0x7d00, 0x0, 0x7d10, 0x0, 0x7d22, + 0x0, 0x7d2f, 0x0, 0x7d5b, 0x0, 0x7d63, 0x0, 0x7da0, 0x0, + 0x7dbe, 0x0, 0x7dc7, 0x0, 0x7df4, 0x0, 0x7e02, 0x0, 0x7e09, + 0x0, 0x7e37, 0x0, 0x7e41, 0x0, 0x7e45, 0x0, 0x7f3e, 0x0, + 0x7f72, 0x0, 0x7f79, 0x0, 0x7f7a, 0x0, 0x7f85, 0x0, 0x7f95, + 0x0, 0x7f9a, 0x0, 0x7fbd, 0x0, 0x7ffa, 0x0, 0x8001, 0x0, + 0x8005, 0x0, 0x8046, 0x0, 0x8060, 0x0, 0x806f, 0x0, 0x8070, + 0x0, 0x807e, 0x0, 0x808b, 0x0, 0x80ad, 0x0, 0x80b2, 0x0, + 0x8103, 0x0, 0x813e, 0x0, 0x81d8, 0x0, 0x81e8, 0x0, 0x81ed, + 0x0, 0x8201, 0x0, 0x8204, 0x0, 0x8218, 0x0, 0x826f, 0x0, + 0x8279, 0x0, 0x828b, 0x0, 0x8291, 0x0, 0x829d, 0x0, 0x82b1, + 0x0, 0x82b3, 0x0, 0x82bd, 0x0, 0x82e5, 0x0, 0x82e6, 0x0, + 0x831d, 0x0, 0x8323, 0x0, 0x8336, 0x0, 0x8352, 0x0, 0x8353, + 0x0, 0x8363, 0x0, 0x83ad, 0x0, 0x83bd, 0x0, 0x83c9, 0x0, + 0x83ca, 0x0, 0x83cc, 0x0, 0x83dc, 0x0, 0x83e7, 0x0, 0x83ef, + 0x0, 0x83f1, 0x0, 0x843d, 0x0, 0x8449, 0x0, 0x8457, 0x0, + 0x84ee, 0x0, 0x84f1, 0x0, 0x84f3, 0x0, 0x84fc, 0x0, 0x8516, + 0x0, 0x8564, 0x0, 0x85cd, 0x0, 0x85fa, 0x0, 0x8606, 0x0, + 0x8612, 0x0, 0x862d, 0x0, 0x863f, 0x0, 0x8650, 0x0, 0x865c, + 0x0, 0x8667, 0x0, 0x8669, 0x0, 0x8688, 0x0, 0x86a9, 0x0, + 0x86e2, 0x0, 0x870e, 0x0, 0x8728, 0x0, 0x876b, 0x0, 0x8779, + 0x0, 0x8786, 0x0, 0x87ba, 0x0, 0x87e1, 0x0, 0x8801, 0x0, + 0x881f, 0x0, 0x884c, 0x0, 0x8860, 0x0, 0x8863, 0x0, 0x88c2, + 0x0, 0x88cf, 0x0, 0x88d7, 0x0, 0x88de, 0x0, 0x88e1, 0x0, + 0x88f8, 0x0, 0x88fa, 0x0, 0x8910, 0x0, 0x8941, 0x0, 0x8964, + 0x0, 0x8986, 0x0, 0x898b, 0x0, 0x8996, 0x0, 0x8aa0, 0x0, + 0x8aaa, 0x0, 0x8abf, 0x0, 0x8acb, 0x0, 0x8ad2, 0x0, 0x8ad6, + 0x0, 0x8aed, 0x0, 0x8af8, 0x0, 0x8afe, 0x0, 0x8b01, 0x0, + 0x8b39, 0x0, 0x8b58, 0x0, 0x8b80, 0x0, 0x8b8a, 0x0, 0x8c48, + 0x0, 0x8c55, 0x0, 0x8cab, 0x0, 0x8cc1, 0x0, 0x8cc2, 0x0, + 0x8cc8, 0x0, 0x8cd3, 0x0, 0x8d08, 0x0, 0x8d1b, 0x0, 0x8d77, + 0x0, 0x8dbc, 0x0, 0x8dcb, 0x0, 0x8def, 0x0, 0x8df0, 0x0, + 0x8eca, 0x0, 0x8ed4, 0x0, 0x8f26, 0x0, 0x8f2a, 0x0, 0x8f38, + 0x0, 0x8f3b, 0x0, 0x8f62, 0x0, 0x8f9e, 0x0, 0x8fb0, 0x0, + 0x8fb6, 0x0, 0x9023, 0x0, 0x9038, 0x0, 0x9072, 0x0, 0x907c, + 0x0, 0x908f, 0x0, 0x9094, 0x0, 0x90ce, 0x0, 0x90de, 0x0, + 0x90f1, 0x0, 0x90fd, 0x0, 0x9111, 0x0, 0x911b, 0x0, 0x916a, + 0x0, 0x9199, 0x0, 0x91b4, 0x0, 0x91cc, 0x0, 0x91cf, 0x0, + 0x91d1, 0x0, 0x9234, 0x0, 0x9238, 0x0, 0x9276, 0x0, 0x927c, + 0x0, 0x92d7, 0x0, 0x92d8, 0x0, 0x9304, 0x0, 0x934a, 0x0, + 0x93f9, 0x0, 0x9415, 0x0, 0x958b, 0x0, 0x95ad, 0x0, 0x95b7, + 0x0, 0x962e, 0x0, 0x964b, 0x0, 0x964d, 0x0, 0x9675, 0x0, + 0x9678, 0x0, 0x967c, 0x0, 0x9686, 0x0, 0x96a3, 0x0, 0x96b7, + 0x0, 0x96b8, 0x0, 0x96c3, 0x0, 0x96e2, 0x0, 0x96e3, 0x0, + 0x96f6, 0x0, 0x96f7, 0x0, 0x9723, 0x0, 0x9732, 0x0, 0x9748, + 0x0, 0x9756, 0x0, 0x97db, 0x0, 0x97e0, 0x0, 0x97ff, 0x0, + 0x980b, 0x0, 0x9818, 0x0, 0x9829, 0x0, 0x983b, 0x0, 0x985e, + 0x0, 0x98e2, 0x0, 0x98ef, 0x0, 0x98fc, 0x0, 0x9928, 0x0, + 0x9929, 0x0, 0x99a7, 0x0, 0x99c2, 0x0, 0x99f1, 0x0, 0x99fe, + 0x0, 0x9a6a, 0x0, 0x9b12, 0x0, 0x9b6f, 0x0, 0x9c40, 0x0, + 0x9c57, 0x0, 0x9cfd, 0x0, 0x9d67, 0x0, 0x9db4, 0x0, 0x9dfa, + 0x0, 0x9e1e, 0x0, 0x9e7f, 0x0, 0x9e97, 0x0, 0x9e9f, 0x0, + 0x9ebb, 0x0, 0x9ece, 0x0, 0x9ef9, 0x0, 0x9efe, 0x0, 0x9f05, + 0x0, 0x9f0f, 0x0, 0x9f16, 0x0, 0x9f3b, 0x0, 0x9f43, 0x0, + 0x9f8d, 0x0, 0x9f8e, 0x0, 0x9f9c, 0x0, 0x11099, 0x110ba, 0x0, + 0x1109b, 0x110ba, 0x0, 0x110a5, 0x110ba, 0x0, 0x11131, 0x11127, + 0x0, 0x11132, 0x11127, 0x0, 0x1d157, 0x1d165, 0x0, 0x1d158, + 0x1d165, 0x0, 0x1d158, 0x1d165, 0x1d16e, 0x0, 0x1d158, 0x1d165, + 0x1d16f, 0x0, 0x1d158, 0x1d165, 0x1d170, 0x0, 0x1d158, 0x1d165, + 0x1d171, 0x0, 0x1d158, 0x1d165, 0x1d172, 0x0, 0x1d1b9, 0x1d165, + 0x0, 0x1d1b9, 0x1d165, 0x1d16e, 0x0, 0x1d1b9, 0x1d165, 0x1d16f, + 0x0, 0x1d1ba, 0x1d165, 0x0, 0x1d1ba, 0x1d165, 0x1d16e, 0x0, + 0x1d1ba, 0x1d165, 0x1d16f, 0x0, 0x20122, 0x0, 0x2051c, 0x0, + 0x20525, 0x0, 0x2054b, 0x0, 0x2063a, 0x0, 0x20804, 0x0, + 0x208de, 0x0, 0x20a2c, 0x0, 0x20b63, 0x0, 0x214e4, 0x0, + 0x216a8, 0x0, 0x216ea, 0x0, 0x219c8, 0x0, 0x21b18, 0x0, + 0x21d0b, 0x0, 0x21de4, 0x0, 0x21de6, 0x0, 0x22183, 0x0, + 0x2219f, 0x0, 0x22331, 0x0, 0x226d4, 0x0, 0x22844, 0x0, + 0x2284a, 0x0, 0x22b0c, 0x0, 0x22bf1, 0x0, 0x2300a, 0x0, + 0x232b8, 0x0, 0x2335f, 0x0, 0x23393, 0x0, 0x2339c, 0x0, + 0x233c3, 0x0, 0x233d5, 0x0, 0x2346d, 0x0, 0x236a3, 0x0, + 0x238a7, 0x0, 0x23a8d, 0x0, 0x23afa, 0x0, 0x23cbc, 0x0, + 0x23d1e, 0x0, 0x23ed1, 0x0, 0x23f5e, 0x0, 0x23f8e, 0x0, + 0x24263, 0x0, 0x242ee, 0x0, 0x243ab, 0x0, 0x24608, 0x0, + 0x24735, 0x0, 0x24814, 0x0, 0x24c36, 0x0, 0x24c92, 0x0, + 0x24fa1, 0x0, 0x24fb8, 0x0, 0x25044, 0x0, 0x250f2, 0x0, + 0x250f3, 0x0, 0x25119, 0x0, 0x25133, 0x0, 0x25249, 0x0, + 0x2541d, 0x0, 0x25626, 0x0, 0x2569a, 0x0, 0x256c5, 0x0, + 0x2597c, 0x0, 0x25aa7, 0x0, 0x25bab, 0x0, 0x25c80, 0x0, + 0x25cd0, 0x0, 0x25f86, 0x0, 0x261da, 0x0, 0x26228, 0x0, + 0x26247, 0x0, 0x262d9, 0x0, 0x2633e, 0x0, 0x264da, 0x0, + 0x26523, 0x0, 0x265a8, 0x0, 0x267a7, 0x0, 0x267b5, 0x0, + 0x26b3c, 0x0, 0x26c36, 0x0, 0x26cd5, 0x0, 0x26d6b, 0x0, + 0x26f2c, 0x0, 0x26fb1, 0x0, 0x270d2, 0x0, 0x273ca, 0x0, + 0x27667, 0x0, 0x278ae, 0x0, 0x27966, 0x0, 0x27ca8, 0x0, + 0x27ed3, 0x0, 0x27f2f, 0x0, 0x285d2, 0x0, 0x285ed, 0x0, + 0x2872e, 0x0, 0x28bfa, 0x0, 0x28d77, 0x0, 0x29145, 0x0, + 0x291df, 0x0, 0x2921a, 0x0, 0x2940a, 0x0, 0x29496, 0x0, + 0x295b6, 0x0, 0x29b30, 0x0, 0x2a0ce, 0x0, 0x2a105, 0x0, + 0x2a20e, 0x0, 0x2a291, 0x0, 0x2a392, 0x0, 0x2a600, 0x0 + ]; + return t; + } + + _IDCA decompCompatTable() + { + static _IDCA t = [ + 0x0, 0x20, 0x0, 0x20, 0x301, 0x0, 0x20, 0x303, 0x0, 0x20, + 0x304, 0x0, 0x20, 0x305, 0x0, 0x20, 0x306, 0x0, 0x20, 0x307, + 0x0, 0x20, 0x308, 0x0, 0x20, 0x308, 0x300, 0x0, 0x20, 0x308, + 0x301, 0x0, 0x20, 0x308, 0x342, 0x0, 0x20, 0x30a, 0x0, 0x20, + 0x30b, 0x0, 0x20, 0x313, 0x0, 0x20, 0x313, 0x300, 0x0, 0x20, + 0x313, 0x301, 0x0, 0x20, 0x313, 0x342, 0x0, 0x20, 0x314, 0x0, + 0x20, 0x314, 0x300, 0x0, 0x20, 0x314, 0x301, 0x0, 0x20, 0x314, + 0x342, 0x0, 0x20, 0x327, 0x0, 0x20, 0x328, 0x0, 0x20, 0x333, + 0x0, 0x20, 0x342, 0x0, 0x20, 0x345, 0x0, 0x20, 0x64b, 0x0, + 0x20, 0x64c, 0x0, 0x20, 0x64c, 0x651, 0x0, 0x20, 0x64d, 0x0, + 0x20, 0x64d, 0x651, 0x0, 0x20, 0x64e, 0x0, 0x20, 0x64e, 0x651, + 0x0, 0x20, 0x64f, 0x0, 0x20, 0x64f, 0x651, 0x0, 0x20, 0x650, + 0x0, 0x20, 0x650, 0x651, 0x0, 0x20, 0x651, 0x0, 0x20, 0x651, + 0x670, 0x0, 0x20, 0x652, 0x0, 0x20, 0x3099, 0x0, 0x20, 0x309a, + 0x0, 0x21, 0x0, 0x21, 0x21, 0x0, 0x21, 0x3f, 0x0, 0x22, 0x0, + 0x23, 0x0, 0x24, 0x0, 0x25, 0x0, 0x26, 0x0, 0x27, 0x0, 0x28, + 0x0, 0x28, 0x31, 0x29, 0x0, 0x28, 0x31, 0x30, 0x29, 0x0, 0x28, + 0x31, 0x31, 0x29, 0x0, 0x28, 0x31, 0x32, 0x29, 0x0, 0x28, 0x31, + 0x33, 0x29, 0x0, 0x28, 0x31, 0x34, 0x29, 0x0, 0x28, 0x31, 0x35, + 0x29, 0x0, 0x28, 0x31, 0x36, 0x29, 0x0, 0x28, 0x31, 0x37, 0x29, + 0x0, 0x28, 0x31, 0x38, 0x29, 0x0, 0x28, 0x31, 0x39, 0x29, 0x0, + 0x28, 0x32, 0x29, 0x0, 0x28, 0x32, 0x30, 0x29, 0x0, 0x28, 0x33, + 0x29, 0x0, 0x28, 0x34, 0x29, 0x0, 0x28, 0x35, 0x29, 0x0, 0x28, + 0x36, 0x29, 0x0, 0x28, 0x37, 0x29, 0x0, 0x28, 0x38, 0x29, 0x0, + 0x28, 0x39, 0x29, 0x0, 0x28, 0x41, 0x29, 0x0, 0x28, 0x42, 0x29, + 0x0, 0x28, 0x43, 0x29, 0x0, 0x28, 0x44, 0x29, 0x0, 0x28, 0x45, + 0x29, 0x0, 0x28, 0x46, 0x29, 0x0, 0x28, 0x47, 0x29, 0x0, 0x28, + 0x48, 0x29, 0x0, 0x28, 0x49, 0x29, 0x0, 0x28, 0x4a, 0x29, 0x0, + 0x28, 0x4b, 0x29, 0x0, 0x28, 0x4c, 0x29, 0x0, 0x28, 0x4d, 0x29, + 0x0, 0x28, 0x4e, 0x29, 0x0, 0x28, 0x4f, 0x29, 0x0, 0x28, 0x50, + 0x29, 0x0, 0x28, 0x51, 0x29, 0x0, 0x28, 0x52, 0x29, 0x0, 0x28, + 0x53, 0x29, 0x0, 0x28, 0x54, 0x29, 0x0, 0x28, 0x55, 0x29, 0x0, + 0x28, 0x56, 0x29, 0x0, 0x28, 0x57, 0x29, 0x0, 0x28, 0x58, 0x29, + 0x0, 0x28, 0x59, 0x29, 0x0, 0x28, 0x5a, 0x29, 0x0, 0x28, 0x61, + 0x29, 0x0, 0x28, 0x62, 0x29, 0x0, 0x28, 0x63, 0x29, 0x0, 0x28, + 0x64, 0x29, 0x0, 0x28, 0x65, 0x29, 0x0, 0x28, 0x66, 0x29, 0x0, + 0x28, 0x67, 0x29, 0x0, 0x28, 0x68, 0x29, 0x0, 0x28, 0x69, 0x29, + 0x0, 0x28, 0x6a, 0x29, 0x0, 0x28, 0x6b, 0x29, 0x0, 0x28, 0x6c, + 0x29, 0x0, 0x28, 0x6d, 0x29, 0x0, 0x28, 0x6e, 0x29, 0x0, 0x28, + 0x6f, 0x29, 0x0, 0x28, 0x70, 0x29, 0x0, 0x28, 0x71, 0x29, 0x0, + 0x28, 0x72, 0x29, 0x0, 0x28, 0x73, 0x29, 0x0, 0x28, 0x74, 0x29, + 0x0, 0x28, 0x75, 0x29, 0x0, 0x28, 0x76, 0x29, 0x0, 0x28, 0x77, + 0x29, 0x0, 0x28, 0x78, 0x29, 0x0, 0x28, 0x79, 0x29, 0x0, 0x28, + 0x7a, 0x29, 0x0, 0x28, 0x1100, 0x29, 0x0, 0x28, 0x1100, 0x1161, + 0x29, 0x0, 0x28, 0x1102, 0x29, 0x0, 0x28, 0x1102, 0x1161, 0x29, + 0x0, 0x28, 0x1103, 0x29, 0x0, 0x28, 0x1103, 0x1161, 0x29, 0x0, + 0x28, 0x1105, 0x29, 0x0, 0x28, 0x1105, 0x1161, 0x29, 0x0, 0x28, + 0x1106, 0x29, 0x0, 0x28, 0x1106, 0x1161, 0x29, 0x0, 0x28, + 0x1107, 0x29, 0x0, 0x28, 0x1107, 0x1161, 0x29, 0x0, 0x28, + 0x1109, 0x29, 0x0, 0x28, 0x1109, 0x1161, 0x29, 0x0, 0x28, + 0x110b, 0x29, 0x0, 0x28, 0x110b, 0x1161, 0x29, 0x0, 0x28, + 0x110b, 0x1169, 0x110c, 0x1165, 0x11ab, 0x29, 0x0, 0x28, + 0x110b, 0x1169, 0x1112, 0x116e, 0x29, 0x0, 0x28, 0x110c, 0x29, + 0x0, 0x28, 0x110c, 0x1161, 0x29, 0x0, 0x28, 0x110c, 0x116e, + 0x29, 0x0, 0x28, 0x110e, 0x29, 0x0, 0x28, 0x110e, 0x1161, 0x29, + 0x0, 0x28, 0x110f, 0x29, 0x0, 0x28, 0x110f, 0x1161, 0x29, 0x0, + 0x28, 0x1110, 0x29, 0x0, 0x28, 0x1110, 0x1161, 0x29, 0x0, 0x28, + 0x1111, 0x29, 0x0, 0x28, 0x1111, 0x1161, 0x29, 0x0, 0x28, + 0x1112, 0x29, 0x0, 0x28, 0x1112, 0x1161, 0x29, 0x0, 0x28, + 0x4e00, 0x29, 0x0, 0x28, 0x4e03, 0x29, 0x0, 0x28, 0x4e09, 0x29, + 0x0, 0x28, 0x4e5d, 0x29, 0x0, 0x28, 0x4e8c, 0x29, 0x0, 0x28, + 0x4e94, 0x29, 0x0, 0x28, 0x4ee3, 0x29, 0x0, 0x28, 0x4f01, 0x29, + 0x0, 0x28, 0x4f11, 0x29, 0x0, 0x28, 0x516b, 0x29, 0x0, 0x28, + 0x516d, 0x29, 0x0, 0x28, 0x52b4, 0x29, 0x0, 0x28, 0x5341, 0x29, + 0x0, 0x28, 0x5354, 0x29, 0x0, 0x28, 0x540d, 0x29, 0x0, 0x28, + 0x547c, 0x29, 0x0, 0x28, 0x56db, 0x29, 0x0, 0x28, 0x571f, 0x29, + 0x0, 0x28, 0x5b66, 0x29, 0x0, 0x28, 0x65e5, 0x29, 0x0, 0x28, + 0x6708, 0x29, 0x0, 0x28, 0x6709, 0x29, 0x0, 0x28, 0x6728, 0x29, + 0x0, 0x28, 0x682a, 0x29, 0x0, 0x28, 0x6c34, 0x29, 0x0, 0x28, + 0x706b, 0x29, 0x0, 0x28, 0x7279, 0x29, 0x0, 0x28, 0x76e3, 0x29, + 0x0, 0x28, 0x793e, 0x29, 0x0, 0x28, 0x795d, 0x29, 0x0, 0x28, + 0x796d, 0x29, 0x0, 0x28, 0x81ea, 0x29, 0x0, 0x28, 0x81f3, 0x29, + 0x0, 0x28, 0x8ca1, 0x29, 0x0, 0x28, 0x8cc7, 0x29, 0x0, 0x28, + 0x91d1, 0x29, 0x0, 0x29, 0x0, 0x2a, 0x0, 0x2b, 0x0, 0x2c, 0x0, + 0x2d, 0x0, 0x2e, 0x0, 0x2e, 0x2e, 0x0, 0x2e, 0x2e, 0x2e, 0x0, + 0x2f, 0x0, 0x30, 0x0, 0x30, 0x2c, 0x0, 0x30, 0x2e, 0x0, 0x30, + 0x2044, 0x33, 0x0, 0x30, 0x70b9, 0x0, 0x31, 0x0, 0x31, 0x2c, + 0x0, 0x31, 0x2e, 0x0, 0x31, 0x30, 0x0, 0x31, 0x30, 0x2e, 0x0, + 0x31, 0x30, 0x65e5, 0x0, 0x31, 0x30, 0x6708, 0x0, 0x31, 0x30, + 0x70b9, 0x0, 0x31, 0x31, 0x0, 0x31, 0x31, 0x2e, 0x0, 0x31, + 0x31, 0x65e5, 0x0, 0x31, 0x31, 0x6708, 0x0, 0x31, 0x31, 0x70b9, + 0x0, 0x31, 0x32, 0x0, 0x31, 0x32, 0x2e, 0x0, 0x31, 0x32, + 0x65e5, 0x0, 0x31, 0x32, 0x6708, 0x0, 0x31, 0x32, 0x70b9, 0x0, + 0x31, 0x33, 0x0, 0x31, 0x33, 0x2e, 0x0, 0x31, 0x33, 0x65e5, + 0x0, 0x31, 0x33, 0x70b9, 0x0, 0x31, 0x34, 0x0, 0x31, 0x34, + 0x2e, 0x0, 0x31, 0x34, 0x65e5, 0x0, 0x31, 0x34, 0x70b9, 0x0, + 0x31, 0x35, 0x0, 0x31, 0x35, 0x2e, 0x0, 0x31, 0x35, 0x65e5, + 0x0, 0x31, 0x35, 0x70b9, 0x0, 0x31, 0x36, 0x0, 0x31, 0x36, + 0x2e, 0x0, 0x31, 0x36, 0x65e5, 0x0, 0x31, 0x36, 0x70b9, 0x0, + 0x31, 0x37, 0x0, 0x31, 0x37, 0x2e, 0x0, 0x31, 0x37, 0x65e5, + 0x0, 0x31, 0x37, 0x70b9, 0x0, 0x31, 0x38, 0x0, 0x31, 0x38, + 0x2e, 0x0, 0x31, 0x38, 0x65e5, 0x0, 0x31, 0x38, 0x70b9, 0x0, + 0x31, 0x39, 0x0, 0x31, 0x39, 0x2e, 0x0, 0x31, 0x39, 0x65e5, + 0x0, 0x31, 0x39, 0x70b9, 0x0, 0x31, 0x2044, 0x0, 0x31, 0x2044, + 0x31, 0x30, 0x0, 0x31, 0x2044, 0x32, 0x0, 0x31, 0x2044, 0x33, + 0x0, 0x31, 0x2044, 0x34, 0x0, 0x31, 0x2044, 0x35, 0x0, 0x31, + 0x2044, 0x36, 0x0, 0x31, 0x2044, 0x37, 0x0, 0x31, 0x2044, 0x38, + 0x0, 0x31, 0x2044, 0x39, 0x0, 0x31, 0x65e5, 0x0, 0x31, 0x6708, + 0x0, 0x31, 0x70b9, 0x0, 0x32, 0x0, 0x32, 0x2c, 0x0, 0x32, 0x2e, + 0x0, 0x32, 0x30, 0x0, 0x32, 0x30, 0x2e, 0x0, 0x32, 0x30, + 0x65e5, 0x0, 0x32, 0x30, 0x70b9, 0x0, 0x32, 0x31, 0x0, 0x32, + 0x31, 0x65e5, 0x0, 0x32, 0x31, 0x70b9, 0x0, 0x32, 0x32, 0x0, + 0x32, 0x32, 0x65e5, 0x0, 0x32, 0x32, 0x70b9, 0x0, 0x32, 0x33, + 0x0, 0x32, 0x33, 0x65e5, 0x0, 0x32, 0x33, 0x70b9, 0x0, 0x32, + 0x34, 0x0, 0x32, 0x34, 0x65e5, 0x0, 0x32, 0x34, 0x70b9, 0x0, + 0x32, 0x35, 0x0, 0x32, 0x35, 0x65e5, 0x0, 0x32, 0x36, 0x0, + 0x32, 0x36, 0x65e5, 0x0, 0x32, 0x37, 0x0, 0x32, 0x37, 0x65e5, + 0x0, 0x32, 0x38, 0x0, 0x32, 0x38, 0x65e5, 0x0, 0x32, 0x39, 0x0, + 0x32, 0x39, 0x65e5, 0x0, 0x32, 0x2044, 0x33, 0x0, 0x32, 0x2044, + 0x35, 0x0, 0x32, 0x65e5, 0x0, 0x32, 0x6708, 0x0, 0x32, 0x70b9, + 0x0, 0x33, 0x0, 0x33, 0x2c, 0x0, 0x33, 0x2e, 0x0, 0x33, 0x30, + 0x0, 0x33, 0x30, 0x65e5, 0x0, 0x33, 0x31, 0x0, 0x33, 0x31, + 0x65e5, 0x0, 0x33, 0x32, 0x0, 0x33, 0x33, 0x0, 0x33, 0x34, 0x0, + 0x33, 0x35, 0x0, 0x33, 0x36, 0x0, 0x33, 0x37, 0x0, 0x33, 0x38, + 0x0, 0x33, 0x39, 0x0, 0x33, 0x2044, 0x34, 0x0, 0x33, 0x2044, + 0x35, 0x0, 0x33, 0x2044, 0x38, 0x0, 0x33, 0x65e5, 0x0, 0x33, + 0x6708, 0x0, 0x33, 0x70b9, 0x0, 0x34, 0x0, 0x34, 0x2c, 0x0, + 0x34, 0x2e, 0x0, 0x34, 0x30, 0x0, 0x34, 0x31, 0x0, 0x34, 0x32, + 0x0, 0x34, 0x33, 0x0, 0x34, 0x34, 0x0, 0x34, 0x35, 0x0, 0x34, + 0x36, 0x0, 0x34, 0x37, 0x0, 0x34, 0x38, 0x0, 0x34, 0x39, 0x0, + 0x34, 0x2044, 0x35, 0x0, 0x34, 0x65e5, 0x0, 0x34, 0x6708, 0x0, + 0x34, 0x70b9, 0x0, 0x35, 0x0, 0x35, 0x2c, 0x0, 0x35, 0x2e, 0x0, + 0x35, 0x30, 0x0, 0x35, 0x2044, 0x36, 0x0, 0x35, 0x2044, 0x38, + 0x0, 0x35, 0x65e5, 0x0, 0x35, 0x6708, 0x0, 0x35, 0x70b9, 0x0, + 0x36, 0x0, 0x36, 0x2c, 0x0, 0x36, 0x2e, 0x0, 0x36, 0x65e5, 0x0, + 0x36, 0x6708, 0x0, 0x36, 0x70b9, 0x0, 0x37, 0x0, 0x37, 0x2c, + 0x0, 0x37, 0x2e, 0x0, 0x37, 0x2044, 0x38, 0x0, 0x37, 0x65e5, + 0x0, 0x37, 0x6708, 0x0, 0x37, 0x70b9, 0x0, 0x38, 0x0, 0x38, + 0x2c, 0x0, 0x38, 0x2e, 0x0, 0x38, 0x65e5, 0x0, 0x38, 0x6708, + 0x0, 0x38, 0x70b9, 0x0, 0x39, 0x0, 0x39, 0x2c, 0x0, 0x39, 0x2e, + 0x0, 0x39, 0x65e5, 0x0, 0x39, 0x6708, 0x0, 0x39, 0x70b9, 0x0, + 0x3a, 0x0, 0x3a, 0x3a, 0x3d, 0x0, 0x3b, 0x0, 0x3c, 0x0, 0x3c, + 0x338, 0x0, 0x3d, 0x0, 0x3d, 0x3d, 0x0, 0x3d, 0x3d, 0x3d, 0x0, + 0x3d, 0x338, 0x0, 0x3e, 0x0, 0x3e, 0x338, 0x0, 0x3f, 0x0, 0x3f, + 0x21, 0x0, 0x3f, 0x3f, 0x0, 0x40, 0x0, 0x41, 0x0, 0x41, 0x55, + 0x0, 0x41, 0x300, 0x0, 0x41, 0x301, 0x0, 0x41, 0x302, 0x0, + 0x41, 0x302, 0x300, 0x0, 0x41, 0x302, 0x301, 0x0, 0x41, 0x302, + 0x303, 0x0, 0x41, 0x302, 0x309, 0x0, 0x41, 0x303, 0x0, 0x41, + 0x304, 0x0, 0x41, 0x306, 0x0, 0x41, 0x306, 0x300, 0x0, 0x41, + 0x306, 0x301, 0x0, 0x41, 0x306, 0x303, 0x0, 0x41, 0x306, 0x309, + 0x0, 0x41, 0x307, 0x0, 0x41, 0x307, 0x304, 0x0, 0x41, 0x308, + 0x0, 0x41, 0x308, 0x304, 0x0, 0x41, 0x309, 0x0, 0x41, 0x30a, + 0x0, 0x41, 0x30a, 0x301, 0x0, 0x41, 0x30c, 0x0, 0x41, 0x30f, + 0x0, 0x41, 0x311, 0x0, 0x41, 0x323, 0x0, 0x41, 0x323, 0x302, + 0x0, 0x41, 0x323, 0x306, 0x0, 0x41, 0x325, 0x0, 0x41, 0x328, + 0x0, 0x41, 0x2215, 0x6d, 0x0, 0x42, 0x0, 0x42, 0x71, 0x0, 0x42, + 0x307, 0x0, 0x42, 0x323, 0x0, 0x42, 0x331, 0x0, 0x43, 0x0, + 0x43, 0x44, 0x0, 0x43, 0x6f, 0x2e, 0x0, 0x43, 0x301, 0x0, 0x43, + 0x302, 0x0, 0x43, 0x307, 0x0, 0x43, 0x30c, 0x0, 0x43, 0x327, + 0x0, 0x43, 0x327, 0x301, 0x0, 0x43, 0x2215, 0x6b, 0x67, 0x0, + 0x44, 0x0, 0x44, 0x4a, 0x0, 0x44, 0x5a, 0x0, 0x44, 0x5a, 0x30c, + 0x0, 0x44, 0x7a, 0x0, 0x44, 0x7a, 0x30c, 0x0, 0x44, 0x307, 0x0, + 0x44, 0x30c, 0x0, 0x44, 0x323, 0x0, 0x44, 0x327, 0x0, 0x44, + 0x32d, 0x0, 0x44, 0x331, 0x0, 0x45, 0x0, 0x45, 0x300, 0x0, + 0x45, 0x301, 0x0, 0x45, 0x302, 0x0, 0x45, 0x302, 0x300, 0x0, + 0x45, 0x302, 0x301, 0x0, 0x45, 0x302, 0x303, 0x0, 0x45, 0x302, + 0x309, 0x0, 0x45, 0x303, 0x0, 0x45, 0x304, 0x0, 0x45, 0x304, + 0x300, 0x0, 0x45, 0x304, 0x301, 0x0, 0x45, 0x306, 0x0, 0x45, + 0x307, 0x0, 0x45, 0x308, 0x0, 0x45, 0x309, 0x0, 0x45, 0x30c, + 0x0, 0x45, 0x30f, 0x0, 0x45, 0x311, 0x0, 0x45, 0x323, 0x0, + 0x45, 0x323, 0x302, 0x0, 0x45, 0x327, 0x0, 0x45, 0x327, 0x306, + 0x0, 0x45, 0x328, 0x0, 0x45, 0x32d, 0x0, 0x45, 0x330, 0x0, + 0x46, 0x0, 0x46, 0x41, 0x58, 0x0, 0x46, 0x307, 0x0, 0x47, 0x0, + 0x47, 0x42, 0x0, 0x47, 0x48, 0x7a, 0x0, 0x47, 0x50, 0x61, 0x0, + 0x47, 0x79, 0x0, 0x47, 0x301, 0x0, 0x47, 0x302, 0x0, 0x47, + 0x304, 0x0, 0x47, 0x306, 0x0, 0x47, 0x307, 0x0, 0x47, 0x30c, + 0x0, 0x47, 0x327, 0x0, 0x48, 0x0, 0x48, 0x50, 0x0, 0x48, 0x56, + 0x0, 0x48, 0x67, 0x0, 0x48, 0x7a, 0x0, 0x48, 0x302, 0x0, 0x48, + 0x307, 0x0, 0x48, 0x308, 0x0, 0x48, 0x30c, 0x0, 0x48, 0x323, + 0x0, 0x48, 0x327, 0x0, 0x48, 0x32e, 0x0, 0x49, 0x0, 0x49, 0x49, + 0x0, 0x49, 0x49, 0x49, 0x0, 0x49, 0x4a, 0x0, 0x49, 0x55, 0x0, + 0x49, 0x56, 0x0, 0x49, 0x58, 0x0, 0x49, 0x300, 0x0, 0x49, + 0x301, 0x0, 0x49, 0x302, 0x0, 0x49, 0x303, 0x0, 0x49, 0x304, + 0x0, 0x49, 0x306, 0x0, 0x49, 0x307, 0x0, 0x49, 0x308, 0x0, + 0x49, 0x308, 0x301, 0x0, 0x49, 0x309, 0x0, 0x49, 0x30c, 0x0, + 0x49, 0x30f, 0x0, 0x49, 0x311, 0x0, 0x49, 0x323, 0x0, 0x49, + 0x328, 0x0, 0x49, 0x330, 0x0, 0x4a, 0x0, 0x4a, 0x302, 0x0, + 0x4b, 0x0, 0x4b, 0x42, 0x0, 0x4b, 0x4b, 0x0, 0x4b, 0x4d, 0x0, + 0x4b, 0x301, 0x0, 0x4b, 0x30c, 0x0, 0x4b, 0x323, 0x0, 0x4b, + 0x327, 0x0, 0x4b, 0x331, 0x0, 0x4c, 0x0, 0x4c, 0x4a, 0x0, 0x4c, + 0x54, 0x44, 0x0, 0x4c, 0x6a, 0x0, 0x4c, 0xb7, 0x0, 0x4c, 0x301, + 0x0, 0x4c, 0x30c, 0x0, 0x4c, 0x323, 0x0, 0x4c, 0x323, 0x304, + 0x0, 0x4c, 0x327, 0x0, 0x4c, 0x32d, 0x0, 0x4c, 0x331, 0x0, + 0x4d, 0x0, 0x4d, 0x42, 0x0, 0x4d, 0x43, 0x0, 0x4d, 0x44, 0x0, + 0x4d, 0x48, 0x7a, 0x0, 0x4d, 0x50, 0x61, 0x0, 0x4d, 0x56, 0x0, + 0x4d, 0x57, 0x0, 0x4d, 0x301, 0x0, 0x4d, 0x307, 0x0, 0x4d, + 0x323, 0x0, 0x4d, 0x3a9, 0x0, 0x4e, 0x0, 0x4e, 0x4a, 0x0, 0x4e, + 0x6a, 0x0, 0x4e, 0x6f, 0x0, 0x4e, 0x300, 0x0, 0x4e, 0x301, 0x0, + 0x4e, 0x303, 0x0, 0x4e, 0x307, 0x0, 0x4e, 0x30c, 0x0, 0x4e, + 0x323, 0x0, 0x4e, 0x327, 0x0, 0x4e, 0x32d, 0x0, 0x4e, 0x331, + 0x0, 0x4f, 0x0, 0x4f, 0x300, 0x0, 0x4f, 0x301, 0x0, 0x4f, + 0x302, 0x0, 0x4f, 0x302, 0x300, 0x0, 0x4f, 0x302, 0x301, 0x0, + 0x4f, 0x302, 0x303, 0x0, 0x4f, 0x302, 0x309, 0x0, 0x4f, 0x303, + 0x0, 0x4f, 0x303, 0x301, 0x0, 0x4f, 0x303, 0x304, 0x0, 0x4f, + 0x303, 0x308, 0x0, 0x4f, 0x304, 0x0, 0x4f, 0x304, 0x300, 0x0, + 0x4f, 0x304, 0x301, 0x0, 0x4f, 0x306, 0x0, 0x4f, 0x307, 0x0, + 0x4f, 0x307, 0x304, 0x0, 0x4f, 0x308, 0x0, 0x4f, 0x308, 0x304, + 0x0, 0x4f, 0x309, 0x0, 0x4f, 0x30b, 0x0, 0x4f, 0x30c, 0x0, + 0x4f, 0x30f, 0x0, 0x4f, 0x311, 0x0, 0x4f, 0x31b, 0x0, 0x4f, + 0x31b, 0x300, 0x0, 0x4f, 0x31b, 0x301, 0x0, 0x4f, 0x31b, 0x303, + 0x0, 0x4f, 0x31b, 0x309, 0x0, 0x4f, 0x31b, 0x323, 0x0, 0x4f, + 0x323, 0x0, 0x4f, 0x323, 0x302, 0x0, 0x4f, 0x328, 0x0, 0x4f, + 0x328, 0x304, 0x0, 0x50, 0x0, 0x50, 0x48, 0x0, 0x50, 0x50, + 0x4d, 0x0, 0x50, 0x50, 0x56, 0x0, 0x50, 0x52, 0x0, 0x50, 0x54, + 0x45, 0x0, 0x50, 0x61, 0x0, 0x50, 0x301, 0x0, 0x50, 0x307, 0x0, + 0x51, 0x0, 0x52, 0x0, 0x52, 0x73, 0x0, 0x52, 0x301, 0x0, 0x52, + 0x307, 0x0, 0x52, 0x30c, 0x0, 0x52, 0x30f, 0x0, 0x52, 0x311, + 0x0, 0x52, 0x323, 0x0, 0x52, 0x323, 0x304, 0x0, 0x52, 0x327, + 0x0, 0x52, 0x331, 0x0, 0x53, 0x0, 0x53, 0x44, 0x0, 0x53, 0x4d, + 0x0, 0x53, 0x53, 0x0, 0x53, 0x76, 0x0, 0x53, 0x301, 0x0, 0x53, + 0x301, 0x307, 0x0, 0x53, 0x302, 0x0, 0x53, 0x307, 0x0, 0x53, + 0x30c, 0x0, 0x53, 0x30c, 0x307, 0x0, 0x53, 0x323, 0x0, 0x53, + 0x323, 0x307, 0x0, 0x53, 0x326, 0x0, 0x53, 0x327, 0x0, 0x54, + 0x0, 0x54, 0x45, 0x4c, 0x0, 0x54, 0x48, 0x7a, 0x0, 0x54, 0x4d, + 0x0, 0x54, 0x307, 0x0, 0x54, 0x30c, 0x0, 0x54, 0x323, 0x0, + 0x54, 0x326, 0x0, 0x54, 0x327, 0x0, 0x54, 0x32d, 0x0, 0x54, + 0x331, 0x0, 0x55, 0x0, 0x55, 0x300, 0x0, 0x55, 0x301, 0x0, + 0x55, 0x302, 0x0, 0x55, 0x303, 0x0, 0x55, 0x303, 0x301, 0x0, + 0x55, 0x304, 0x0, 0x55, 0x304, 0x308, 0x0, 0x55, 0x306, 0x0, + 0x55, 0x308, 0x0, 0x55, 0x308, 0x300, 0x0, 0x55, 0x308, 0x301, + 0x0, 0x55, 0x308, 0x304, 0x0, 0x55, 0x308, 0x30c, 0x0, 0x55, + 0x309, 0x0, 0x55, 0x30a, 0x0, 0x55, 0x30b, 0x0, 0x55, 0x30c, + 0x0, 0x55, 0x30f, 0x0, 0x55, 0x311, 0x0, 0x55, 0x31b, 0x0, + 0x55, 0x31b, 0x300, 0x0, 0x55, 0x31b, 0x301, 0x0, 0x55, 0x31b, + 0x303, 0x0, 0x55, 0x31b, 0x309, 0x0, 0x55, 0x31b, 0x323, 0x0, + 0x55, 0x323, 0x0, 0x55, 0x324, 0x0, 0x55, 0x328, 0x0, 0x55, + 0x32d, 0x0, 0x55, 0x330, 0x0, 0x56, 0x0, 0x56, 0x49, 0x0, 0x56, + 0x49, 0x49, 0x0, 0x56, 0x49, 0x49, 0x49, 0x0, 0x56, 0x303, 0x0, + 0x56, 0x323, 0x0, 0x56, 0x2215, 0x6d, 0x0, 0x57, 0x0, 0x57, + 0x43, 0x0, 0x57, 0x5a, 0x0, 0x57, 0x62, 0x0, 0x57, 0x300, 0x0, + 0x57, 0x301, 0x0, 0x57, 0x302, 0x0, 0x57, 0x307, 0x0, 0x57, + 0x308, 0x0, 0x57, 0x323, 0x0, 0x58, 0x0, 0x58, 0x49, 0x0, 0x58, + 0x49, 0x49, 0x0, 0x58, 0x307, 0x0, 0x58, 0x308, 0x0, 0x59, 0x0, + 0x59, 0x300, 0x0, 0x59, 0x301, 0x0, 0x59, 0x302, 0x0, 0x59, + 0x303, 0x0, 0x59, 0x304, 0x0, 0x59, 0x307, 0x0, 0x59, 0x308, + 0x0, 0x59, 0x309, 0x0, 0x59, 0x323, 0x0, 0x5a, 0x0, 0x5a, + 0x301, 0x0, 0x5a, 0x302, 0x0, 0x5a, 0x307, 0x0, 0x5a, 0x30c, + 0x0, 0x5a, 0x323, 0x0, 0x5a, 0x331, 0x0, 0x5b, 0x0, 0x5c, 0x0, + 0x5d, 0x0, 0x5e, 0x0, 0x5f, 0x0, 0x60, 0x0, 0x61, 0x0, 0x61, + 0x2e, 0x6d, 0x2e, 0x0, 0x61, 0x2f, 0x63, 0x0, 0x61, 0x2f, 0x73, + 0x0, 0x61, 0x2be, 0x0, 0x61, 0x300, 0x0, 0x61, 0x301, 0x0, + 0x61, 0x302, 0x0, 0x61, 0x302, 0x300, 0x0, 0x61, 0x302, 0x301, + 0x0, 0x61, 0x302, 0x303, 0x0, 0x61, 0x302, 0x309, 0x0, 0x61, + 0x303, 0x0, 0x61, 0x304, 0x0, 0x61, 0x306, 0x0, 0x61, 0x306, + 0x300, 0x0, 0x61, 0x306, 0x301, 0x0, 0x61, 0x306, 0x303, 0x0, + 0x61, 0x306, 0x309, 0x0, 0x61, 0x307, 0x0, 0x61, 0x307, 0x304, + 0x0, 0x61, 0x308, 0x0, 0x61, 0x308, 0x304, 0x0, 0x61, 0x309, + 0x0, 0x61, 0x30a, 0x0, 0x61, 0x30a, 0x301, 0x0, 0x61, 0x30c, + 0x0, 0x61, 0x30f, 0x0, 0x61, 0x311, 0x0, 0x61, 0x323, 0x0, + 0x61, 0x323, 0x302, 0x0, 0x61, 0x323, 0x306, 0x0, 0x61, 0x325, + 0x0, 0x61, 0x328, 0x0, 0x62, 0x0, 0x62, 0x61, 0x72, 0x0, 0x62, + 0x307, 0x0, 0x62, 0x323, 0x0, 0x62, 0x331, 0x0, 0x63, 0x0, + 0x63, 0x2f, 0x6f, 0x0, 0x63, 0x2f, 0x75, 0x0, 0x63, 0x61, 0x6c, + 0x0, 0x63, 0x63, 0x0, 0x63, 0x64, 0x0, 0x63, 0x6d, 0x0, 0x63, + 0x6d, 0x32, 0x0, 0x63, 0x6d, 0x33, 0x0, 0x63, 0x301, 0x0, 0x63, + 0x302, 0x0, 0x63, 0x307, 0x0, 0x63, 0x30c, 0x0, 0x63, 0x327, + 0x0, 0x63, 0x327, 0x301, 0x0, 0x64, 0x0, 0x64, 0x42, 0x0, 0x64, + 0x61, 0x0, 0x64, 0x6c, 0x0, 0x64, 0x6d, 0x0, 0x64, 0x6d, 0x32, + 0x0, 0x64, 0x6d, 0x33, 0x0, 0x64, 0x7a, 0x0, 0x64, 0x7a, 0x30c, + 0x0, 0x64, 0x307, 0x0, 0x64, 0x30c, 0x0, 0x64, 0x323, 0x0, + 0x64, 0x327, 0x0, 0x64, 0x32d, 0x0, 0x64, 0x331, 0x0, 0x65, + 0x0, 0x65, 0x56, 0x0, 0x65, 0x72, 0x67, 0x0, 0x65, 0x300, 0x0, + 0x65, 0x301, 0x0, 0x65, 0x302, 0x0, 0x65, 0x302, 0x300, 0x0, + 0x65, 0x302, 0x301, 0x0, 0x65, 0x302, 0x303, 0x0, 0x65, 0x302, + 0x309, 0x0, 0x65, 0x303, 0x0, 0x65, 0x304, 0x0, 0x65, 0x304, + 0x300, 0x0, 0x65, 0x304, 0x301, 0x0, 0x65, 0x306, 0x0, 0x65, + 0x307, 0x0, 0x65, 0x308, 0x0, 0x65, 0x309, 0x0, 0x65, 0x30c, + 0x0, 0x65, 0x30f, 0x0, 0x65, 0x311, 0x0, 0x65, 0x323, 0x0, + 0x65, 0x323, 0x302, 0x0, 0x65, 0x327, 0x0, 0x65, 0x327, 0x306, + 0x0, 0x65, 0x328, 0x0, 0x65, 0x32d, 0x0, 0x65, 0x330, 0x0, + 0x66, 0x0, 0x66, 0x66, 0x0, 0x66, 0x66, 0x69, 0x0, 0x66, 0x66, + 0x6c, 0x0, 0x66, 0x69, 0x0, 0x66, 0x6c, 0x0, 0x66, 0x6d, 0x0, + 0x66, 0x307, 0x0, 0x67, 0x0, 0x67, 0x61, 0x6c, 0x0, 0x67, + 0x301, 0x0, 0x67, 0x302, 0x0, 0x67, 0x304, 0x0, 0x67, 0x306, + 0x0, 0x67, 0x307, 0x0, 0x67, 0x30c, 0x0, 0x67, 0x327, 0x0, + 0x68, 0x0, 0x68, 0x50, 0x61, 0x0, 0x68, 0x61, 0x0, 0x68, 0x302, + 0x0, 0x68, 0x307, 0x0, 0x68, 0x308, 0x0, 0x68, 0x30c, 0x0, + 0x68, 0x323, 0x0, 0x68, 0x327, 0x0, 0x68, 0x32e, 0x0, 0x68, + 0x331, 0x0, 0x69, 0x0, 0x69, 0x69, 0x0, 0x69, 0x69, 0x69, 0x0, + 0x69, 0x6a, 0x0, 0x69, 0x6e, 0x0, 0x69, 0x76, 0x0, 0x69, 0x78, + 0x0, 0x69, 0x300, 0x0, 0x69, 0x301, 0x0, 0x69, 0x302, 0x0, + 0x69, 0x303, 0x0, 0x69, 0x304, 0x0, 0x69, 0x306, 0x0, 0x69, + 0x308, 0x0, 0x69, 0x308, 0x301, 0x0, 0x69, 0x309, 0x0, 0x69, + 0x30c, 0x0, 0x69, 0x30f, 0x0, 0x69, 0x311, 0x0, 0x69, 0x323, + 0x0, 0x69, 0x328, 0x0, 0x69, 0x330, 0x0, 0x6a, 0x0, 0x6a, + 0x302, 0x0, 0x6a, 0x30c, 0x0, 0x6b, 0x0, 0x6b, 0x41, 0x0, 0x6b, + 0x48, 0x7a, 0x0, 0x6b, 0x50, 0x61, 0x0, 0x6b, 0x56, 0x0, 0x6b, + 0x57, 0x0, 0x6b, 0x63, 0x61, 0x6c, 0x0, 0x6b, 0x67, 0x0, 0x6b, + 0x6c, 0x0, 0x6b, 0x6d, 0x0, 0x6b, 0x6d, 0x32, 0x0, 0x6b, 0x6d, + 0x33, 0x0, 0x6b, 0x74, 0x0, 0x6b, 0x301, 0x0, 0x6b, 0x30c, 0x0, + 0x6b, 0x323, 0x0, 0x6b, 0x327, 0x0, 0x6b, 0x331, 0x0, 0x6b, + 0x3a9, 0x0, 0x6c, 0x0, 0x6c, 0x6a, 0x0, 0x6c, 0x6d, 0x0, 0x6c, + 0x6e, 0x0, 0x6c, 0x6f, 0x67, 0x0, 0x6c, 0x78, 0x0, 0x6c, 0xb7, + 0x0, 0x6c, 0x301, 0x0, 0x6c, 0x30c, 0x0, 0x6c, 0x323, 0x0, + 0x6c, 0x323, 0x304, 0x0, 0x6c, 0x327, 0x0, 0x6c, 0x32d, 0x0, + 0x6c, 0x331, 0x0, 0x6d, 0x0, 0x6d, 0x32, 0x0, 0x6d, 0x33, 0x0, + 0x6d, 0x41, 0x0, 0x6d, 0x56, 0x0, 0x6d, 0x57, 0x0, 0x6d, 0x62, + 0x0, 0x6d, 0x67, 0x0, 0x6d, 0x69, 0x6c, 0x0, 0x6d, 0x6c, 0x0, + 0x6d, 0x6d, 0x0, 0x6d, 0x6d, 0x32, 0x0, 0x6d, 0x6d, 0x33, 0x0, + 0x6d, 0x6f, 0x6c, 0x0, 0x6d, 0x73, 0x0, 0x6d, 0x301, 0x0, 0x6d, + 0x307, 0x0, 0x6d, 0x323, 0x0, 0x6d, 0x2215, 0x73, 0x0, 0x6d, + 0x2215, 0x73, 0x32, 0x0, 0x6e, 0x0, 0x6e, 0x41, 0x0, 0x6e, + 0x46, 0x0, 0x6e, 0x56, 0x0, 0x6e, 0x57, 0x0, 0x6e, 0x6a, 0x0, + 0x6e, 0x6d, 0x0, 0x6e, 0x73, 0x0, 0x6e, 0x300, 0x0, 0x6e, + 0x301, 0x0, 0x6e, 0x303, 0x0, 0x6e, 0x307, 0x0, 0x6e, 0x30c, + 0x0, 0x6e, 0x323, 0x0, 0x6e, 0x327, 0x0, 0x6e, 0x32d, 0x0, + 0x6e, 0x331, 0x0, 0x6f, 0x0, 0x6f, 0x56, 0x0, 0x6f, 0x300, 0x0, + 0x6f, 0x301, 0x0, 0x6f, 0x302, 0x0, 0x6f, 0x302, 0x300, 0x0, + 0x6f, 0x302, 0x301, 0x0, 0x6f, 0x302, 0x303, 0x0, 0x6f, 0x302, + 0x309, 0x0, 0x6f, 0x303, 0x0, 0x6f, 0x303, 0x301, 0x0, 0x6f, + 0x303, 0x304, 0x0, 0x6f, 0x303, 0x308, 0x0, 0x6f, 0x304, 0x0, + 0x6f, 0x304, 0x300, 0x0, 0x6f, 0x304, 0x301, 0x0, 0x6f, 0x306, + 0x0, 0x6f, 0x307, 0x0, 0x6f, 0x307, 0x304, 0x0, 0x6f, 0x308, + 0x0, 0x6f, 0x308, 0x304, 0x0, 0x6f, 0x309, 0x0, 0x6f, 0x30b, + 0x0, 0x6f, 0x30c, 0x0, 0x6f, 0x30f, 0x0, 0x6f, 0x311, 0x0, + 0x6f, 0x31b, 0x0, 0x6f, 0x31b, 0x300, 0x0, 0x6f, 0x31b, 0x301, + 0x0, 0x6f, 0x31b, 0x303, 0x0, 0x6f, 0x31b, 0x309, 0x0, 0x6f, + 0x31b, 0x323, 0x0, 0x6f, 0x323, 0x0, 0x6f, 0x323, 0x302, 0x0, + 0x6f, 0x328, 0x0, 0x6f, 0x328, 0x304, 0x0, 0x70, 0x0, 0x70, + 0x2e, 0x6d, 0x2e, 0x0, 0x70, 0x41, 0x0, 0x70, 0x46, 0x0, 0x70, + 0x56, 0x0, 0x70, 0x57, 0x0, 0x70, 0x63, 0x0, 0x70, 0x73, 0x0, + 0x70, 0x301, 0x0, 0x70, 0x307, 0x0, 0x71, 0x0, 0x72, 0x0, 0x72, + 0x61, 0x64, 0x0, 0x72, 0x61, 0x64, 0x2215, 0x73, 0x0, 0x72, + 0x61, 0x64, 0x2215, 0x73, 0x32, 0x0, 0x72, 0x301, 0x0, 0x72, + 0x307, 0x0, 0x72, 0x30c, 0x0, 0x72, 0x30f, 0x0, 0x72, 0x311, + 0x0, 0x72, 0x323, 0x0, 0x72, 0x323, 0x304, 0x0, 0x72, 0x327, + 0x0, 0x72, 0x331, 0x0, 0x73, 0x0, 0x73, 0x72, 0x0, 0x73, 0x74, + 0x0, 0x73, 0x301, 0x0, 0x73, 0x301, 0x307, 0x0, 0x73, 0x302, + 0x0, 0x73, 0x307, 0x0, 0x73, 0x30c, 0x0, 0x73, 0x30c, 0x307, + 0x0, 0x73, 0x323, 0x0, 0x73, 0x323, 0x307, 0x0, 0x73, 0x326, + 0x0, 0x73, 0x327, 0x0, 0x74, 0x0, 0x74, 0x307, 0x0, 0x74, + 0x308, 0x0, 0x74, 0x30c, 0x0, 0x74, 0x323, 0x0, 0x74, 0x326, + 0x0, 0x74, 0x327, 0x0, 0x74, 0x32d, 0x0, 0x74, 0x331, 0x0, + 0x75, 0x0, 0x75, 0x300, 0x0, 0x75, 0x301, 0x0, 0x75, 0x302, + 0x0, 0x75, 0x303, 0x0, 0x75, 0x303, 0x301, 0x0, 0x75, 0x304, + 0x0, 0x75, 0x304, 0x308, 0x0, 0x75, 0x306, 0x0, 0x75, 0x308, + 0x0, 0x75, 0x308, 0x300, 0x0, 0x75, 0x308, 0x301, 0x0, 0x75, + 0x308, 0x304, 0x0, 0x75, 0x308, 0x30c, 0x0, 0x75, 0x309, 0x0, + 0x75, 0x30a, 0x0, 0x75, 0x30b, 0x0, 0x75, 0x30c, 0x0, 0x75, + 0x30f, 0x0, 0x75, 0x311, 0x0, 0x75, 0x31b, 0x0, 0x75, 0x31b, + 0x300, 0x0, 0x75, 0x31b, 0x301, 0x0, 0x75, 0x31b, 0x303, 0x0, + 0x75, 0x31b, 0x309, 0x0, 0x75, 0x31b, 0x323, 0x0, 0x75, 0x323, + 0x0, 0x75, 0x324, 0x0, 0x75, 0x328, 0x0, 0x75, 0x32d, 0x0, + 0x75, 0x330, 0x0, 0x76, 0x0, 0x76, 0x69, 0x0, 0x76, 0x69, 0x69, + 0x0, 0x76, 0x69, 0x69, 0x69, 0x0, 0x76, 0x303, 0x0, 0x76, + 0x323, 0x0, 0x77, 0x0, 0x77, 0x300, 0x0, 0x77, 0x301, 0x0, + 0x77, 0x302, 0x0, 0x77, 0x307, 0x0, 0x77, 0x308, 0x0, 0x77, + 0x30a, 0x0, 0x77, 0x323, 0x0, 0x78, 0x0, 0x78, 0x69, 0x0, 0x78, + 0x69, 0x69, 0x0, 0x78, 0x307, 0x0, 0x78, 0x308, 0x0, 0x79, 0x0, + 0x79, 0x300, 0x0, 0x79, 0x301, 0x0, 0x79, 0x302, 0x0, 0x79, + 0x303, 0x0, 0x79, 0x304, 0x0, 0x79, 0x307, 0x0, 0x79, 0x308, + 0x0, 0x79, 0x309, 0x0, 0x79, 0x30a, 0x0, 0x79, 0x323, 0x0, + 0x7a, 0x0, 0x7a, 0x301, 0x0, 0x7a, 0x302, 0x0, 0x7a, 0x307, + 0x0, 0x7a, 0x30c, 0x0, 0x7a, 0x323, 0x0, 0x7a, 0x331, 0x0, + 0x7b, 0x0, 0x7c, 0x0, 0x7d, 0x0, 0x7e, 0x0, 0xa2, 0x0, 0xa3, + 0x0, 0xa5, 0x0, 0xa6, 0x0, 0xac, 0x0, 0xb0, 0x43, 0x0, 0xb0, + 0x46, 0x0, 0xb7, 0x0, 0xc6, 0x0, 0xc6, 0x301, 0x0, 0xc6, 0x304, + 0x0, 0xd8, 0x301, 0x0, 0xe6, 0x301, 0x0, 0xe6, 0x304, 0x0, + 0xf0, 0x0, 0xf8, 0x301, 0x0, 0x126, 0x0, 0x127, 0x0, 0x131, + 0x0, 0x14b, 0x0, 0x153, 0x0, 0x18e, 0x0, 0x190, 0x0, 0x1ab, + 0x0, 0x1b7, 0x30c, 0x0, 0x222, 0x0, 0x237, 0x0, 0x250, 0x0, + 0x251, 0x0, 0x252, 0x0, 0x254, 0x0, 0x255, 0x0, 0x259, 0x0, + 0x25b, 0x0, 0x25c, 0x0, 0x25f, 0x0, 0x261, 0x0, 0x263, 0x0, + 0x265, 0x0, 0x266, 0x0, 0x268, 0x0, 0x269, 0x0, 0x26a, 0x0, + 0x26d, 0x0, 0x26f, 0x0, 0x270, 0x0, 0x271, 0x0, 0x272, 0x0, + 0x273, 0x0, 0x274, 0x0, 0x275, 0x0, 0x278, 0x0, 0x279, 0x0, + 0x27b, 0x0, 0x281, 0x0, 0x282, 0x0, 0x283, 0x0, 0x289, 0x0, + 0x28a, 0x0, 0x28b, 0x0, 0x28c, 0x0, 0x290, 0x0, 0x291, 0x0, + 0x292, 0x0, 0x292, 0x30c, 0x0, 0x295, 0x0, 0x29d, 0x0, 0x29f, + 0x0, 0x2b9, 0x0, 0x2bc, 0x6e, 0x0, 0x300, 0x0, 0x301, 0x0, + 0x308, 0x301, 0x0, 0x313, 0x0, 0x391, 0x0, 0x391, 0x300, 0x0, + 0x391, 0x301, 0x0, 0x391, 0x304, 0x0, 0x391, 0x306, 0x0, 0x391, + 0x313, 0x0, 0x391, 0x313, 0x300, 0x0, 0x391, 0x313, 0x300, + 0x345, 0x0, 0x391, 0x313, 0x301, 0x0, 0x391, 0x313, 0x301, + 0x345, 0x0, 0x391, 0x313, 0x342, 0x0, 0x391, 0x313, 0x342, + 0x345, 0x0, 0x391, 0x313, 0x345, 0x0, 0x391, 0x314, 0x0, 0x391, + 0x314, 0x300, 0x0, 0x391, 0x314, 0x300, 0x345, 0x0, 0x391, + 0x314, 0x301, 0x0, 0x391, 0x314, 0x301, 0x345, 0x0, 0x391, + 0x314, 0x342, 0x0, 0x391, 0x314, 0x342, 0x345, 0x0, 0x391, + 0x314, 0x345, 0x0, 0x391, 0x345, 0x0, 0x392, 0x0, 0x393, 0x0, + 0x394, 0x0, 0x395, 0x0, 0x395, 0x300, 0x0, 0x395, 0x301, 0x0, + 0x395, 0x313, 0x0, 0x395, 0x313, 0x300, 0x0, 0x395, 0x313, + 0x301, 0x0, 0x395, 0x314, 0x0, 0x395, 0x314, 0x300, 0x0, 0x395, + 0x314, 0x301, 0x0, 0x396, 0x0, 0x397, 0x0, 0x397, 0x300, 0x0, + 0x397, 0x301, 0x0, 0x397, 0x313, 0x0, 0x397, 0x313, 0x300, 0x0, + 0x397, 0x313, 0x300, 0x345, 0x0, 0x397, 0x313, 0x301, 0x0, + 0x397, 0x313, 0x301, 0x345, 0x0, 0x397, 0x313, 0x342, 0x0, + 0x397, 0x313, 0x342, 0x345, 0x0, 0x397, 0x313, 0x345, 0x0, + 0x397, 0x314, 0x0, 0x397, 0x314, 0x300, 0x0, 0x397, 0x314, + 0x300, 0x345, 0x0, 0x397, 0x314, 0x301, 0x0, 0x397, 0x314, + 0x301, 0x345, 0x0, 0x397, 0x314, 0x342, 0x0, 0x397, 0x314, + 0x342, 0x345, 0x0, 0x397, 0x314, 0x345, 0x0, 0x397, 0x345, 0x0, + 0x398, 0x0, 0x399, 0x0, 0x399, 0x300, 0x0, 0x399, 0x301, 0x0, + 0x399, 0x304, 0x0, 0x399, 0x306, 0x0, 0x399, 0x308, 0x0, 0x399, + 0x313, 0x0, 0x399, 0x313, 0x300, 0x0, 0x399, 0x313, 0x301, 0x0, + 0x399, 0x313, 0x342, 0x0, 0x399, 0x314, 0x0, 0x399, 0x314, + 0x300, 0x0, 0x399, 0x314, 0x301, 0x0, 0x399, 0x314, 0x342, 0x0, + 0x39a, 0x0, 0x39b, 0x0, 0x39c, 0x0, 0x39d, 0x0, 0x39e, 0x0, + 0x39f, 0x0, 0x39f, 0x300, 0x0, 0x39f, 0x301, 0x0, 0x39f, 0x313, + 0x0, 0x39f, 0x313, 0x300, 0x0, 0x39f, 0x313, 0x301, 0x0, 0x39f, + 0x314, 0x0, 0x39f, 0x314, 0x300, 0x0, 0x39f, 0x314, 0x301, 0x0, + 0x3a0, 0x0, 0x3a1, 0x0, 0x3a1, 0x314, 0x0, 0x3a3, 0x0, 0x3a4, + 0x0, 0x3a5, 0x0, 0x3a5, 0x300, 0x0, 0x3a5, 0x301, 0x0, 0x3a5, + 0x304, 0x0, 0x3a5, 0x306, 0x0, 0x3a5, 0x308, 0x0, 0x3a5, 0x314, + 0x0, 0x3a5, 0x314, 0x300, 0x0, 0x3a5, 0x314, 0x301, 0x0, 0x3a5, + 0x314, 0x342, 0x0, 0x3a6, 0x0, 0x3a7, 0x0, 0x3a8, 0x0, 0x3a9, + 0x0, 0x3a9, 0x300, 0x0, 0x3a9, 0x301, 0x0, 0x3a9, 0x313, 0x0, + 0x3a9, 0x313, 0x300, 0x0, 0x3a9, 0x313, 0x300, 0x345, 0x0, + 0x3a9, 0x313, 0x301, 0x0, 0x3a9, 0x313, 0x301, 0x345, 0x0, + 0x3a9, 0x313, 0x342, 0x0, 0x3a9, 0x313, 0x342, 0x345, 0x0, + 0x3a9, 0x313, 0x345, 0x0, 0x3a9, 0x314, 0x0, 0x3a9, 0x314, + 0x300, 0x0, 0x3a9, 0x314, 0x300, 0x345, 0x0, 0x3a9, 0x314, + 0x301, 0x0, 0x3a9, 0x314, 0x301, 0x345, 0x0, 0x3a9, 0x314, + 0x342, 0x0, 0x3a9, 0x314, 0x342, 0x345, 0x0, 0x3a9, 0x314, + 0x345, 0x0, 0x3a9, 0x345, 0x0, 0x3b1, 0x0, 0x3b1, 0x300, 0x0, + 0x3b1, 0x300, 0x345, 0x0, 0x3b1, 0x301, 0x0, 0x3b1, 0x301, + 0x345, 0x0, 0x3b1, 0x304, 0x0, 0x3b1, 0x306, 0x0, 0x3b1, 0x313, + 0x0, 0x3b1, 0x313, 0x300, 0x0, 0x3b1, 0x313, 0x300, 0x345, 0x0, + 0x3b1, 0x313, 0x301, 0x0, 0x3b1, 0x313, 0x301, 0x345, 0x0, + 0x3b1, 0x313, 0x342, 0x0, 0x3b1, 0x313, 0x342, 0x345, 0x0, + 0x3b1, 0x313, 0x345, 0x0, 0x3b1, 0x314, 0x0, 0x3b1, 0x314, + 0x300, 0x0, 0x3b1, 0x314, 0x300, 0x345, 0x0, 0x3b1, 0x314, + 0x301, 0x0, 0x3b1, 0x314, 0x301, 0x345, 0x0, 0x3b1, 0x314, + 0x342, 0x0, 0x3b1, 0x314, 0x342, 0x345, 0x0, 0x3b1, 0x314, + 0x345, 0x0, 0x3b1, 0x342, 0x0, 0x3b1, 0x342, 0x345, 0x0, 0x3b1, + 0x345, 0x0, 0x3b2, 0x0, 0x3b3, 0x0, 0x3b4, 0x0, 0x3b5, 0x0, + 0x3b5, 0x300, 0x0, 0x3b5, 0x301, 0x0, 0x3b5, 0x313, 0x0, 0x3b5, + 0x313, 0x300, 0x0, 0x3b5, 0x313, 0x301, 0x0, 0x3b5, 0x314, 0x0, + 0x3b5, 0x314, 0x300, 0x0, 0x3b5, 0x314, 0x301, 0x0, 0x3b6, 0x0, + 0x3b7, 0x0, 0x3b7, 0x300, 0x0, 0x3b7, 0x300, 0x345, 0x0, 0x3b7, + 0x301, 0x0, 0x3b7, 0x301, 0x345, 0x0, 0x3b7, 0x313, 0x0, 0x3b7, + 0x313, 0x300, 0x0, 0x3b7, 0x313, 0x300, 0x345, 0x0, 0x3b7, + 0x313, 0x301, 0x0, 0x3b7, 0x313, 0x301, 0x345, 0x0, 0x3b7, + 0x313, 0x342, 0x0, 0x3b7, 0x313, 0x342, 0x345, 0x0, 0x3b7, + 0x313, 0x345, 0x0, 0x3b7, 0x314, 0x0, 0x3b7, 0x314, 0x300, 0x0, + 0x3b7, 0x314, 0x300, 0x345, 0x0, 0x3b7, 0x314, 0x301, 0x0, + 0x3b7, 0x314, 0x301, 0x345, 0x0, 0x3b7, 0x314, 0x342, 0x0, + 0x3b7, 0x314, 0x342, 0x345, 0x0, 0x3b7, 0x314, 0x345, 0x0, + 0x3b7, 0x342, 0x0, 0x3b7, 0x342, 0x345, 0x0, 0x3b7, 0x345, 0x0, + 0x3b8, 0x0, 0x3b9, 0x0, 0x3b9, 0x300, 0x0, 0x3b9, 0x301, 0x0, + 0x3b9, 0x304, 0x0, 0x3b9, 0x306, 0x0, 0x3b9, 0x308, 0x0, 0x3b9, + 0x308, 0x300, 0x0, 0x3b9, 0x308, 0x301, 0x0, 0x3b9, 0x308, + 0x342, 0x0, 0x3b9, 0x313, 0x0, 0x3b9, 0x313, 0x300, 0x0, 0x3b9, + 0x313, 0x301, 0x0, 0x3b9, 0x313, 0x342, 0x0, 0x3b9, 0x314, 0x0, + 0x3b9, 0x314, 0x300, 0x0, 0x3b9, 0x314, 0x301, 0x0, 0x3b9, + 0x314, 0x342, 0x0, 0x3b9, 0x342, 0x0, 0x3ba, 0x0, 0x3bb, 0x0, + 0x3bc, 0x0, 0x3bc, 0x41, 0x0, 0x3bc, 0x46, 0x0, 0x3bc, 0x56, + 0x0, 0x3bc, 0x57, 0x0, 0x3bc, 0x67, 0x0, 0x3bc, 0x6c, 0x0, + 0x3bc, 0x6d, 0x0, 0x3bc, 0x73, 0x0, 0x3bd, 0x0, 0x3be, 0x0, + 0x3bf, 0x0, 0x3bf, 0x300, 0x0, 0x3bf, 0x301, 0x0, 0x3bf, 0x313, + 0x0, 0x3bf, 0x313, 0x300, 0x0, 0x3bf, 0x313, 0x301, 0x0, 0x3bf, + 0x314, 0x0, 0x3bf, 0x314, 0x300, 0x0, 0x3bf, 0x314, 0x301, 0x0, + 0x3c0, 0x0, 0x3c1, 0x0, 0x3c1, 0x313, 0x0, 0x3c1, 0x314, 0x0, + 0x3c2, 0x0, 0x3c3, 0x0, 0x3c4, 0x0, 0x3c5, 0x0, 0x3c5, 0x300, + 0x0, 0x3c5, 0x301, 0x0, 0x3c5, 0x304, 0x0, 0x3c5, 0x306, 0x0, + 0x3c5, 0x308, 0x0, 0x3c5, 0x308, 0x300, 0x0, 0x3c5, 0x308, + 0x301, 0x0, 0x3c5, 0x308, 0x342, 0x0, 0x3c5, 0x313, 0x0, 0x3c5, + 0x313, 0x300, 0x0, 0x3c5, 0x313, 0x301, 0x0, 0x3c5, 0x313, + 0x342, 0x0, 0x3c5, 0x314, 0x0, 0x3c5, 0x314, 0x300, 0x0, 0x3c5, + 0x314, 0x301, 0x0, 0x3c5, 0x314, 0x342, 0x0, 0x3c5, 0x342, 0x0, + 0x3c6, 0x0, 0x3c7, 0x0, 0x3c8, 0x0, 0x3c9, 0x0, 0x3c9, 0x300, + 0x0, 0x3c9, 0x300, 0x345, 0x0, 0x3c9, 0x301, 0x0, 0x3c9, 0x301, + 0x345, 0x0, 0x3c9, 0x313, 0x0, 0x3c9, 0x313, 0x300, 0x0, 0x3c9, + 0x313, 0x300, 0x345, 0x0, 0x3c9, 0x313, 0x301, 0x0, 0x3c9, + 0x313, 0x301, 0x345, 0x0, 0x3c9, 0x313, 0x342, 0x0, 0x3c9, + 0x313, 0x342, 0x345, 0x0, 0x3c9, 0x313, 0x345, 0x0, 0x3c9, + 0x314, 0x0, 0x3c9, 0x314, 0x300, 0x0, 0x3c9, 0x314, 0x300, + 0x345, 0x0, 0x3c9, 0x314, 0x301, 0x0, 0x3c9, 0x314, 0x301, + 0x345, 0x0, 0x3c9, 0x314, 0x342, 0x0, 0x3c9, 0x314, 0x342, + 0x345, 0x0, 0x3c9, 0x314, 0x345, 0x0, 0x3c9, 0x342, 0x0, 0x3c9, + 0x342, 0x345, 0x0, 0x3c9, 0x345, 0x0, 0x3dc, 0x0, 0x3dd, 0x0, + 0x406, 0x308, 0x0, 0x410, 0x306, 0x0, 0x410, 0x308, 0x0, 0x413, + 0x301, 0x0, 0x415, 0x300, 0x0, 0x415, 0x306, 0x0, 0x415, 0x308, + 0x0, 0x416, 0x306, 0x0, 0x416, 0x308, 0x0, 0x417, 0x308, 0x0, + 0x418, 0x300, 0x0, 0x418, 0x304, 0x0, 0x418, 0x306, 0x0, 0x418, + 0x308, 0x0, 0x41a, 0x301, 0x0, 0x41e, 0x308, 0x0, 0x423, 0x304, + 0x0, 0x423, 0x306, 0x0, 0x423, 0x308, 0x0, 0x423, 0x30b, 0x0, + 0x427, 0x308, 0x0, 0x42b, 0x308, 0x0, 0x42d, 0x308, 0x0, 0x430, + 0x306, 0x0, 0x430, 0x308, 0x0, 0x433, 0x301, 0x0, 0x435, 0x300, + 0x0, 0x435, 0x306, 0x0, 0x435, 0x308, 0x0, 0x436, 0x306, 0x0, + 0x436, 0x308, 0x0, 0x437, 0x308, 0x0, 0x438, 0x300, 0x0, 0x438, + 0x304, 0x0, 0x438, 0x306, 0x0, 0x438, 0x308, 0x0, 0x43a, 0x301, + 0x0, 0x43d, 0x0, 0x43e, 0x308, 0x0, 0x443, 0x304, 0x0, 0x443, + 0x306, 0x0, 0x443, 0x308, 0x0, 0x443, 0x30b, 0x0, 0x447, 0x308, + 0x0, 0x44b, 0x308, 0x0, 0x44d, 0x308, 0x0, 0x456, 0x308, 0x0, + 0x474, 0x30f, 0x0, 0x475, 0x30f, 0x0, 0x4d8, 0x308, 0x0, 0x4d9, + 0x308, 0x0, 0x4e8, 0x308, 0x0, 0x4e9, 0x308, 0x0, 0x565, 0x582, + 0x0, 0x574, 0x565, 0x0, 0x574, 0x56b, 0x0, 0x574, 0x56d, 0x0, + 0x574, 0x576, 0x0, 0x57e, 0x576, 0x0, 0x5d0, 0x0, 0x5d0, 0x5b7, + 0x0, 0x5d0, 0x5b8, 0x0, 0x5d0, 0x5bc, 0x0, 0x5d0, 0x5dc, 0x0, + 0x5d1, 0x0, 0x5d1, 0x5bc, 0x0, 0x5d1, 0x5bf, 0x0, 0x5d2, 0x0, + 0x5d2, 0x5bc, 0x0, 0x5d3, 0x0, 0x5d3, 0x5bc, 0x0, 0x5d4, 0x0, + 0x5d4, 0x5bc, 0x0, 0x5d5, 0x5b9, 0x0, 0x5d5, 0x5bc, 0x0, 0x5d6, + 0x5bc, 0x0, 0x5d8, 0x5bc, 0x0, 0x5d9, 0x5b4, 0x0, 0x5d9, 0x5bc, + 0x0, 0x5da, 0x5bc, 0x0, 0x5db, 0x0, 0x5db, 0x5bc, 0x0, 0x5db, + 0x5bf, 0x0, 0x5dc, 0x0, 0x5dc, 0x5bc, 0x0, 0x5dd, 0x0, 0x5de, + 0x5bc, 0x0, 0x5e0, 0x5bc, 0x0, 0x5e1, 0x5bc, 0x0, 0x5e2, 0x0, + 0x5e3, 0x5bc, 0x0, 0x5e4, 0x5bc, 0x0, 0x5e4, 0x5bf, 0x0, 0x5e6, + 0x5bc, 0x0, 0x5e7, 0x5bc, 0x0, 0x5e8, 0x0, 0x5e8, 0x5bc, 0x0, + 0x5e9, 0x5bc, 0x0, 0x5e9, 0x5bc, 0x5c1, 0x0, 0x5e9, 0x5bc, + 0x5c2, 0x0, 0x5e9, 0x5c1, 0x0, 0x5e9, 0x5c2, 0x0, 0x5ea, 0x0, + 0x5ea, 0x5bc, 0x0, 0x5f2, 0x5b7, 0x0, 0x621, 0x0, 0x627, 0x0, + 0x627, 0x643, 0x628, 0x631, 0x0, 0x627, 0x644, 0x644, 0x647, + 0x0, 0x627, 0x64b, 0x0, 0x627, 0x653, 0x0, 0x627, 0x654, 0x0, + 0x627, 0x655, 0x0, 0x627, 0x674, 0x0, 0x628, 0x0, 0x628, 0x62c, + 0x0, 0x628, 0x62d, 0x0, 0x628, 0x62d, 0x64a, 0x0, 0x628, 0x62e, + 0x0, 0x628, 0x62e, 0x64a, 0x0, 0x628, 0x631, 0x0, 0x628, 0x632, + 0x0, 0x628, 0x645, 0x0, 0x628, 0x646, 0x0, 0x628, 0x647, 0x0, + 0x628, 0x649, 0x0, 0x628, 0x64a, 0x0, 0x629, 0x0, 0x62a, 0x0, + 0x62a, 0x62c, 0x0, 0x62a, 0x62c, 0x645, 0x0, 0x62a, 0x62c, + 0x649, 0x0, 0x62a, 0x62c, 0x64a, 0x0, 0x62a, 0x62d, 0x0, 0x62a, + 0x62d, 0x62c, 0x0, 0x62a, 0x62d, 0x645, 0x0, 0x62a, 0x62e, 0x0, + 0x62a, 0x62e, 0x645, 0x0, 0x62a, 0x62e, 0x649, 0x0, 0x62a, + 0x62e, 0x64a, 0x0, 0x62a, 0x631, 0x0, 0x62a, 0x632, 0x0, 0x62a, + 0x645, 0x0, 0x62a, 0x645, 0x62c, 0x0, 0x62a, 0x645, 0x62d, 0x0, + 0x62a, 0x645, 0x62e, 0x0, 0x62a, 0x645, 0x649, 0x0, 0x62a, + 0x645, 0x64a, 0x0, 0x62a, 0x646, 0x0, 0x62a, 0x647, 0x0, 0x62a, + 0x649, 0x0, 0x62a, 0x64a, 0x0, 0x62b, 0x0, 0x62b, 0x62c, 0x0, + 0x62b, 0x631, 0x0, 0x62b, 0x632, 0x0, 0x62b, 0x645, 0x0, 0x62b, + 0x646, 0x0, 0x62b, 0x647, 0x0, 0x62b, 0x649, 0x0, 0x62b, 0x64a, + 0x0, 0x62c, 0x0, 0x62c, 0x62d, 0x0, 0x62c, 0x62d, 0x649, 0x0, + 0x62c, 0x62d, 0x64a, 0x0, 0x62c, 0x644, 0x20, 0x62c, 0x644, + 0x627, 0x644, 0x647, 0x0, 0x62c, 0x645, 0x0, 0x62c, 0x645, + 0x62d, 0x0, 0x62c, 0x645, 0x649, 0x0, 0x62c, 0x645, 0x64a, 0x0, + 0x62c, 0x649, 0x0, 0x62c, 0x64a, 0x0, 0x62d, 0x0, 0x62d, 0x62c, + 0x0, 0x62d, 0x62c, 0x64a, 0x0, 0x62d, 0x645, 0x0, 0x62d, 0x645, + 0x649, 0x0, 0x62d, 0x645, 0x64a, 0x0, 0x62d, 0x649, 0x0, 0x62d, + 0x64a, 0x0, 0x62e, 0x0, 0x62e, 0x62c, 0x0, 0x62e, 0x62d, 0x0, + 0x62e, 0x645, 0x0, 0x62e, 0x649, 0x0, 0x62e, 0x64a, 0x0, 0x62f, + 0x0, 0x630, 0x0, 0x630, 0x670, 0x0, 0x631, 0x0, 0x631, 0x633, + 0x648, 0x644, 0x0, 0x631, 0x670, 0x0, 0x631, 0x6cc, 0x627, + 0x644, 0x0, 0x632, 0x0, 0x633, 0x0, 0x633, 0x62c, 0x0, 0x633, + 0x62c, 0x62d, 0x0, 0x633, 0x62c, 0x649, 0x0, 0x633, 0x62d, 0x0, + 0x633, 0x62d, 0x62c, 0x0, 0x633, 0x62e, 0x0, 0x633, 0x62e, + 0x649, 0x0, 0x633, 0x62e, 0x64a, 0x0, 0x633, 0x631, 0x0, 0x633, + 0x645, 0x0, 0x633, 0x645, 0x62c, 0x0, 0x633, 0x645, 0x62d, 0x0, + 0x633, 0x645, 0x645, 0x0, 0x633, 0x647, 0x0, 0x633, 0x649, 0x0, + 0x633, 0x64a, 0x0, 0x634, 0x0, 0x634, 0x62c, 0x0, 0x634, 0x62c, + 0x64a, 0x0, 0x634, 0x62d, 0x0, 0x634, 0x62d, 0x645, 0x0, 0x634, + 0x62d, 0x64a, 0x0, 0x634, 0x62e, 0x0, 0x634, 0x631, 0x0, 0x634, + 0x645, 0x0, 0x634, 0x645, 0x62e, 0x0, 0x634, 0x645, 0x645, 0x0, + 0x634, 0x647, 0x0, 0x634, 0x649, 0x0, 0x634, 0x64a, 0x0, 0x635, + 0x0, 0x635, 0x62d, 0x0, 0x635, 0x62d, 0x62d, 0x0, 0x635, 0x62d, + 0x64a, 0x0, 0x635, 0x62e, 0x0, 0x635, 0x631, 0x0, 0x635, 0x644, + 0x639, 0x645, 0x0, 0x635, 0x644, 0x649, 0x0, 0x635, 0x644, + 0x649, 0x20, 0x627, 0x644, 0x644, 0x647, 0x20, 0x639, 0x644, + 0x64a, 0x647, 0x20, 0x648, 0x633, 0x644, 0x645, 0x0, 0x635, + 0x644, 0x6d2, 0x0, 0x635, 0x645, 0x0, 0x635, 0x645, 0x645, 0x0, + 0x635, 0x649, 0x0, 0x635, 0x64a, 0x0, 0x636, 0x0, 0x636, 0x62c, + 0x0, 0x636, 0x62d, 0x0, 0x636, 0x62d, 0x649, 0x0, 0x636, 0x62d, + 0x64a, 0x0, 0x636, 0x62e, 0x0, 0x636, 0x62e, 0x645, 0x0, 0x636, + 0x631, 0x0, 0x636, 0x645, 0x0, 0x636, 0x649, 0x0, 0x636, 0x64a, + 0x0, 0x637, 0x0, 0x637, 0x62d, 0x0, 0x637, 0x645, 0x0, 0x637, + 0x645, 0x62d, 0x0, 0x637, 0x645, 0x645, 0x0, 0x637, 0x645, + 0x64a, 0x0, 0x637, 0x649, 0x0, 0x637, 0x64a, 0x0, 0x638, 0x0, + 0x638, 0x645, 0x0, 0x639, 0x0, 0x639, 0x62c, 0x0, 0x639, 0x62c, + 0x645, 0x0, 0x639, 0x644, 0x64a, 0x647, 0x0, 0x639, 0x645, 0x0, + 0x639, 0x645, 0x645, 0x0, 0x639, 0x645, 0x649, 0x0, 0x639, + 0x645, 0x64a, 0x0, 0x639, 0x649, 0x0, 0x639, 0x64a, 0x0, 0x63a, + 0x0, 0x63a, 0x62c, 0x0, 0x63a, 0x645, 0x0, 0x63a, 0x645, 0x645, + 0x0, 0x63a, 0x645, 0x649, 0x0, 0x63a, 0x645, 0x64a, 0x0, 0x63a, + 0x649, 0x0, 0x63a, 0x64a, 0x0, 0x640, 0x64b, 0x0, 0x640, 0x64e, + 0x0, 0x640, 0x64e, 0x651, 0x0, 0x640, 0x64f, 0x0, 0x640, 0x64f, + 0x651, 0x0, 0x640, 0x650, 0x0, 0x640, 0x650, 0x651, 0x0, 0x640, + 0x651, 0x0, 0x640, 0x652, 0x0, 0x641, 0x0, 0x641, 0x62c, 0x0, + 0x641, 0x62d, 0x0, 0x641, 0x62e, 0x0, 0x641, 0x62e, 0x645, 0x0, + 0x641, 0x645, 0x0, 0x641, 0x645, 0x64a, 0x0, 0x641, 0x649, 0x0, + 0x641, 0x64a, 0x0, 0x642, 0x0, 0x642, 0x62d, 0x0, 0x642, 0x644, + 0x6d2, 0x0, 0x642, 0x645, 0x0, 0x642, 0x645, 0x62d, 0x0, 0x642, + 0x645, 0x645, 0x0, 0x642, 0x645, 0x64a, 0x0, 0x642, 0x649, 0x0, + 0x642, 0x64a, 0x0, 0x643, 0x0, 0x643, 0x627, 0x0, 0x643, 0x62c, + 0x0, 0x643, 0x62d, 0x0, 0x643, 0x62e, 0x0, 0x643, 0x644, 0x0, + 0x643, 0x645, 0x0, 0x643, 0x645, 0x645, 0x0, 0x643, 0x645, + 0x64a, 0x0, 0x643, 0x649, 0x0, 0x643, 0x64a, 0x0, 0x644, 0x0, + 0x644, 0x627, 0x0, 0x644, 0x627, 0x653, 0x0, 0x644, 0x627, + 0x654, 0x0, 0x644, 0x627, 0x655, 0x0, 0x644, 0x62c, 0x0, 0x644, + 0x62c, 0x62c, 0x0, 0x644, 0x62c, 0x645, 0x0, 0x644, 0x62c, + 0x64a, 0x0, 0x644, 0x62d, 0x0, 0x644, 0x62d, 0x645, 0x0, 0x644, + 0x62d, 0x649, 0x0, 0x644, 0x62d, 0x64a, 0x0, 0x644, 0x62e, 0x0, + 0x644, 0x62e, 0x645, 0x0, 0x644, 0x645, 0x0, 0x644, 0x645, + 0x62d, 0x0, 0x644, 0x645, 0x64a, 0x0, 0x644, 0x647, 0x0, 0x644, + 0x649, 0x0, 0x644, 0x64a, 0x0, 0x645, 0x0, 0x645, 0x627, 0x0, + 0x645, 0x62c, 0x0, 0x645, 0x62c, 0x62d, 0x0, 0x645, 0x62c, + 0x62e, 0x0, 0x645, 0x62c, 0x645, 0x0, 0x645, 0x62c, 0x64a, 0x0, + 0x645, 0x62d, 0x0, 0x645, 0x62d, 0x62c, 0x0, 0x645, 0x62d, + 0x645, 0x0, 0x645, 0x62d, 0x645, 0x62f, 0x0, 0x645, 0x62d, + 0x64a, 0x0, 0x645, 0x62e, 0x0, 0x645, 0x62e, 0x62c, 0x0, 0x645, + 0x62e, 0x645, 0x0, 0x645, 0x62e, 0x64a, 0x0, 0x645, 0x645, 0x0, + 0x645, 0x645, 0x64a, 0x0, 0x645, 0x649, 0x0, 0x645, 0x64a, 0x0, + 0x646, 0x0, 0x646, 0x62c, 0x0, 0x646, 0x62c, 0x62d, 0x0, 0x646, + 0x62c, 0x645, 0x0, 0x646, 0x62c, 0x649, 0x0, 0x646, 0x62c, + 0x64a, 0x0, 0x646, 0x62d, 0x0, 0x646, 0x62d, 0x645, 0x0, 0x646, + 0x62d, 0x649, 0x0, 0x646, 0x62d, 0x64a, 0x0, 0x646, 0x62e, 0x0, + 0x646, 0x631, 0x0, 0x646, 0x632, 0x0, 0x646, 0x645, 0x0, 0x646, + 0x645, 0x649, 0x0, 0x646, 0x645, 0x64a, 0x0, 0x646, 0x646, 0x0, + 0x646, 0x647, 0x0, 0x646, 0x649, 0x0, 0x646, 0x64a, 0x0, 0x647, + 0x0, 0x647, 0x62c, 0x0, 0x647, 0x645, 0x0, 0x647, 0x645, 0x62c, + 0x0, 0x647, 0x645, 0x645, 0x0, 0x647, 0x649, 0x0, 0x647, 0x64a, + 0x0, 0x647, 0x670, 0x0, 0x648, 0x0, 0x648, 0x633, 0x644, 0x645, + 0x0, 0x648, 0x654, 0x0, 0x648, 0x674, 0x0, 0x649, 0x0, 0x649, + 0x670, 0x0, 0x64a, 0x0, 0x64a, 0x62c, 0x0, 0x64a, 0x62c, 0x64a, + 0x0, 0x64a, 0x62d, 0x0, 0x64a, 0x62d, 0x64a, 0x0, 0x64a, 0x62e, + 0x0, 0x64a, 0x631, 0x0, 0x64a, 0x632, 0x0, 0x64a, 0x645, 0x0, + 0x64a, 0x645, 0x645, 0x0, 0x64a, 0x645, 0x64a, 0x0, 0x64a, + 0x646, 0x0, 0x64a, 0x647, 0x0, 0x64a, 0x649, 0x0, 0x64a, 0x64a, + 0x0, 0x64a, 0x654, 0x0, 0x64a, 0x654, 0x627, 0x0, 0x64a, 0x654, + 0x62c, 0x0, 0x64a, 0x654, 0x62d, 0x0, 0x64a, 0x654, 0x62e, 0x0, + 0x64a, 0x654, 0x631, 0x0, 0x64a, 0x654, 0x632, 0x0, 0x64a, + 0x654, 0x645, 0x0, 0x64a, 0x654, 0x646, 0x0, 0x64a, 0x654, + 0x647, 0x0, 0x64a, 0x654, 0x648, 0x0, 0x64a, 0x654, 0x649, 0x0, + 0x64a, 0x654, 0x64a, 0x0, 0x64a, 0x654, 0x6c6, 0x0, 0x64a, + 0x654, 0x6c7, 0x0, 0x64a, 0x654, 0x6c8, 0x0, 0x64a, 0x654, + 0x6d0, 0x0, 0x64a, 0x654, 0x6d5, 0x0, 0x64a, 0x674, 0x0, 0x66e, + 0x0, 0x66f, 0x0, 0x671, 0x0, 0x679, 0x0, 0x67a, 0x0, 0x67b, + 0x0, 0x67e, 0x0, 0x67f, 0x0, 0x680, 0x0, 0x683, 0x0, 0x684, + 0x0, 0x686, 0x0, 0x687, 0x0, 0x688, 0x0, 0x68c, 0x0, 0x68d, + 0x0, 0x68e, 0x0, 0x691, 0x0, 0x698, 0x0, 0x6a1, 0x0, 0x6a4, + 0x0, 0x6a6, 0x0, 0x6a9, 0x0, 0x6ad, 0x0, 0x6af, 0x0, 0x6b1, + 0x0, 0x6b3, 0x0, 0x6ba, 0x0, 0x6bb, 0x0, 0x6be, 0x0, 0x6c1, + 0x0, 0x6c1, 0x654, 0x0, 0x6c5, 0x0, 0x6c6, 0x0, 0x6c7, 0x0, + 0x6c7, 0x674, 0x0, 0x6c8, 0x0, 0x6c9, 0x0, 0x6cb, 0x0, 0x6cc, + 0x0, 0x6d0, 0x0, 0x6d2, 0x0, 0x6d2, 0x654, 0x0, 0x6d5, 0x654, + 0x0, 0x915, 0x93c, 0x0, 0x916, 0x93c, 0x0, 0x917, 0x93c, 0x0, + 0x91c, 0x93c, 0x0, 0x921, 0x93c, 0x0, 0x922, 0x93c, 0x0, 0x928, + 0x93c, 0x0, 0x92b, 0x93c, 0x0, 0x92f, 0x93c, 0x0, 0x930, 0x93c, + 0x0, 0x933, 0x93c, 0x0, 0x9a1, 0x9bc, 0x0, 0x9a2, 0x9bc, 0x0, + 0x9af, 0x9bc, 0x0, 0x9c7, 0x9be, 0x0, 0x9c7, 0x9d7, 0x0, 0xa16, + 0xa3c, 0x0, 0xa17, 0xa3c, 0x0, 0xa1c, 0xa3c, 0x0, 0xa2b, 0xa3c, + 0x0, 0xa32, 0xa3c, 0x0, 0xa38, 0xa3c, 0x0, 0xb21, 0xb3c, 0x0, + 0xb22, 0xb3c, 0x0, 0xb47, 0xb3e, 0x0, 0xb47, 0xb56, 0x0, 0xb47, + 0xb57, 0x0, 0xb92, 0xbd7, 0x0, 0xbc6, 0xbbe, 0x0, 0xbc6, 0xbd7, + 0x0, 0xbc7, 0xbbe, 0x0, 0xc46, 0xc56, 0x0, 0xcbf, 0xcd5, 0x0, + 0xcc6, 0xcc2, 0x0, 0xcc6, 0xcc2, 0xcd5, 0x0, 0xcc6, 0xcd5, 0x0, + 0xcc6, 0xcd6, 0x0, 0xd46, 0xd3e, 0x0, 0xd46, 0xd57, 0x0, 0xd47, + 0xd3e, 0x0, 0xdd9, 0xdca, 0x0, 0xdd9, 0xdcf, 0x0, 0xdd9, 0xdcf, + 0xdca, 0x0, 0xdd9, 0xddf, 0x0, 0xe4d, 0xe32, 0x0, 0xeab, 0xe99, + 0x0, 0xeab, 0xea1, 0x0, 0xecd, 0xeb2, 0x0, 0xf0b, 0x0, 0xf40, + 0xfb5, 0x0, 0xf42, 0xfb7, 0x0, 0xf4c, 0xfb7, 0x0, 0xf51, 0xfb7, + 0x0, 0xf56, 0xfb7, 0x0, 0xf5b, 0xfb7, 0x0, 0xf71, 0xf72, 0x0, + 0xf71, 0xf74, 0x0, 0xf71, 0xf80, 0x0, 0xf90, 0xfb5, 0x0, 0xf92, + 0xfb7, 0x0, 0xf9c, 0xfb7, 0x0, 0xfa1, 0xfb7, 0x0, 0xfa6, 0xfb7, + 0x0, 0xfab, 0xfb7, 0x0, 0xfb2, 0xf71, 0xf80, 0x0, 0xfb2, 0xf80, + 0x0, 0xfb3, 0xf71, 0xf80, 0x0, 0xfb3, 0xf80, 0x0, 0x1025, + 0x102e, 0x0, 0x10dc, 0x0, 0x1100, 0x0, 0x1100, 0x1161, 0x0, + 0x1101, 0x0, 0x1102, 0x0, 0x1102, 0x1161, 0x0, 0x1103, 0x0, + 0x1103, 0x1161, 0x0, 0x1104, 0x0, 0x1105, 0x0, 0x1105, 0x1161, + 0x0, 0x1106, 0x0, 0x1106, 0x1161, 0x0, 0x1107, 0x0, 0x1107, + 0x1161, 0x0, 0x1108, 0x0, 0x1109, 0x0, 0x1109, 0x1161, 0x0, + 0x110a, 0x0, 0x110b, 0x0, 0x110b, 0x1161, 0x0, 0x110b, 0x116e, + 0x0, 0x110c, 0x0, 0x110c, 0x1161, 0x0, 0x110c, 0x116e, 0x110b, + 0x1174, 0x0, 0x110d, 0x0, 0x110e, 0x0, 0x110e, 0x1161, 0x0, + 0x110e, 0x1161, 0x11b7, 0x1100, 0x1169, 0x0, 0x110f, 0x0, + 0x110f, 0x1161, 0x0, 0x1110, 0x0, 0x1110, 0x1161, 0x0, 0x1111, + 0x0, 0x1111, 0x1161, 0x0, 0x1112, 0x0, 0x1112, 0x1161, 0x0, + 0x1114, 0x0, 0x1115, 0x0, 0x111a, 0x0, 0x111c, 0x0, 0x111d, + 0x0, 0x111e, 0x0, 0x1120, 0x0, 0x1121, 0x0, 0x1122, 0x0, + 0x1123, 0x0, 0x1127, 0x0, 0x1129, 0x0, 0x112b, 0x0, 0x112c, + 0x0, 0x112d, 0x0, 0x112e, 0x0, 0x112f, 0x0, 0x1132, 0x0, + 0x1136, 0x0, 0x1140, 0x0, 0x1147, 0x0, 0x114c, 0x0, 0x1157, + 0x0, 0x1158, 0x0, 0x1159, 0x0, 0x1160, 0x0, 0x1161, 0x0, + 0x1162, 0x0, 0x1163, 0x0, 0x1164, 0x0, 0x1165, 0x0, 0x1166, + 0x0, 0x1167, 0x0, 0x1168, 0x0, 0x1169, 0x0, 0x116a, 0x0, + 0x116b, 0x0, 0x116c, 0x0, 0x116d, 0x0, 0x116e, 0x0, 0x116f, + 0x0, 0x1170, 0x0, 0x1171, 0x0, 0x1172, 0x0, 0x1173, 0x0, + 0x1174, 0x0, 0x1175, 0x0, 0x1184, 0x0, 0x1185, 0x0, 0x1188, + 0x0, 0x1191, 0x0, 0x1192, 0x0, 0x1194, 0x0, 0x119e, 0x0, + 0x11a1, 0x0, 0x11aa, 0x0, 0x11ac, 0x0, 0x11ad, 0x0, 0x11b0, + 0x0, 0x11b1, 0x0, 0x11b2, 0x0, 0x11b3, 0x0, 0x11b4, 0x0, + 0x11b5, 0x0, 0x11c7, 0x0, 0x11c8, 0x0, 0x11cc, 0x0, 0x11ce, + 0x0, 0x11d3, 0x0, 0x11d7, 0x0, 0x11d9, 0x0, 0x11dd, 0x0, + 0x11df, 0x0, 0x11f1, 0x0, 0x11f2, 0x0, 0x1b05, 0x1b35, 0x0, + 0x1b07, 0x1b35, 0x0, 0x1b09, 0x1b35, 0x0, 0x1b0b, 0x1b35, 0x0, + 0x1b0d, 0x1b35, 0x0, 0x1b11, 0x1b35, 0x0, 0x1b3a, 0x1b35, 0x0, + 0x1b3c, 0x1b35, 0x0, 0x1b3e, 0x1b35, 0x0, 0x1b3f, 0x1b35, 0x0, + 0x1b42, 0x1b35, 0x0, 0x1d02, 0x0, 0x1d16, 0x0, 0x1d17, 0x0, + 0x1d1c, 0x0, 0x1d1d, 0x0, 0x1d25, 0x0, 0x1d7b, 0x0, 0x1d85, + 0x0, 0x2010, 0x0, 0x2013, 0x0, 0x2014, 0x0, 0x2032, 0x2032, + 0x0, 0x2032, 0x2032, 0x2032, 0x0, 0x2032, 0x2032, 0x2032, + 0x2032, 0x0, 0x2035, 0x2035, 0x0, 0x2035, 0x2035, 0x2035, 0x0, + 0x20a9, 0x0, 0x2190, 0x0, 0x2190, 0x338, 0x0, 0x2191, 0x0, + 0x2192, 0x0, 0x2192, 0x338, 0x0, 0x2193, 0x0, 0x2194, 0x338, + 0x0, 0x21d0, 0x338, 0x0, 0x21d2, 0x338, 0x0, 0x21d4, 0x338, + 0x0, 0x2202, 0x0, 0x2203, 0x338, 0x0, 0x2207, 0x0, 0x2208, + 0x338, 0x0, 0x220b, 0x338, 0x0, 0x2211, 0x0, 0x2212, 0x0, + 0x2223, 0x338, 0x0, 0x2225, 0x338, 0x0, 0x222b, 0x222b, 0x0, + 0x222b, 0x222b, 0x222b, 0x0, 0x222b, 0x222b, 0x222b, 0x222b, + 0x0, 0x222e, 0x222e, 0x0, 0x222e, 0x222e, 0x222e, 0x0, 0x223c, + 0x338, 0x0, 0x2243, 0x338, 0x0, 0x2245, 0x338, 0x0, 0x2248, + 0x338, 0x0, 0x224d, 0x338, 0x0, 0x2261, 0x338, 0x0, 0x2264, + 0x338, 0x0, 0x2265, 0x338, 0x0, 0x2272, 0x338, 0x0, 0x2273, + 0x338, 0x0, 0x2276, 0x338, 0x0, 0x2277, 0x338, 0x0, 0x227a, + 0x338, 0x0, 0x227b, 0x338, 0x0, 0x227c, 0x338, 0x0, 0x227d, + 0x338, 0x0, 0x2282, 0x338, 0x0, 0x2283, 0x338, 0x0, 0x2286, + 0x338, 0x0, 0x2287, 0x338, 0x0, 0x2291, 0x338, 0x0, 0x2292, + 0x338, 0x0, 0x22a2, 0x338, 0x0, 0x22a8, 0x338, 0x0, 0x22a9, + 0x338, 0x0, 0x22ab, 0x338, 0x0, 0x22b2, 0x338, 0x0, 0x22b3, + 0x338, 0x0, 0x22b4, 0x338, 0x0, 0x22b5, 0x338, 0x0, 0x2502, + 0x0, 0x25a0, 0x0, 0x25cb, 0x0, 0x2985, 0x0, 0x2986, 0x0, + 0x2add, 0x338, 0x0, 0x2d61, 0x0, 0x3001, 0x0, 0x3002, 0x0, + 0x3008, 0x0, 0x3009, 0x0, 0x300a, 0x0, 0x300b, 0x0, 0x300c, + 0x0, 0x300d, 0x0, 0x300e, 0x0, 0x300f, 0x0, 0x3010, 0x0, + 0x3011, 0x0, 0x3012, 0x0, 0x3014, 0x0, 0x3014, 0x53, 0x3015, + 0x0, 0x3014, 0x4e09, 0x3015, 0x0, 0x3014, 0x4e8c, 0x3015, 0x0, + 0x3014, 0x52dd, 0x3015, 0x0, 0x3014, 0x5b89, 0x3015, 0x0, + 0x3014, 0x6253, 0x3015, 0x0, 0x3014, 0x6557, 0x3015, 0x0, + 0x3014, 0x672c, 0x3015, 0x0, 0x3014, 0x70b9, 0x3015, 0x0, + 0x3014, 0x76d7, 0x3015, 0x0, 0x3015, 0x0, 0x3016, 0x0, 0x3017, + 0x0, 0x3046, 0x3099, 0x0, 0x304b, 0x3099, 0x0, 0x304d, 0x3099, + 0x0, 0x304f, 0x3099, 0x0, 0x3051, 0x3099, 0x0, 0x3053, 0x3099, + 0x0, 0x3055, 0x3099, 0x0, 0x3057, 0x3099, 0x0, 0x3059, 0x3099, + 0x0, 0x305b, 0x3099, 0x0, 0x305d, 0x3099, 0x0, 0x305f, 0x3099, + 0x0, 0x3061, 0x3099, 0x0, 0x3064, 0x3099, 0x0, 0x3066, 0x3099, + 0x0, 0x3068, 0x3099, 0x0, 0x306f, 0x3099, 0x0, 0x306f, 0x309a, + 0x0, 0x3072, 0x3099, 0x0, 0x3072, 0x309a, 0x0, 0x3075, 0x3099, + 0x0, 0x3075, 0x309a, 0x0, 0x3078, 0x3099, 0x0, 0x3078, 0x309a, + 0x0, 0x307b, 0x304b, 0x0, 0x307b, 0x3099, 0x0, 0x307b, 0x309a, + 0x0, 0x3088, 0x308a, 0x0, 0x3099, 0x0, 0x309a, 0x0, 0x309d, + 0x3099, 0x0, 0x30a1, 0x0, 0x30a2, 0x0, 0x30a2, 0x30cf, 0x309a, + 0x30fc, 0x30c8, 0x0, 0x30a2, 0x30eb, 0x30d5, 0x30a1, 0x0, + 0x30a2, 0x30f3, 0x30d8, 0x309a, 0x30a2, 0x0, 0x30a2, 0x30fc, + 0x30eb, 0x0, 0x30a3, 0x0, 0x30a4, 0x0, 0x30a4, 0x30cb, 0x30f3, + 0x30af, 0x3099, 0x0, 0x30a4, 0x30f3, 0x30c1, 0x0, 0x30a5, 0x0, + 0x30a6, 0x0, 0x30a6, 0x3099, 0x0, 0x30a6, 0x30a9, 0x30f3, 0x0, + 0x30a7, 0x0, 0x30a8, 0x0, 0x30a8, 0x30b9, 0x30af, 0x30fc, + 0x30c8, 0x3099, 0x0, 0x30a8, 0x30fc, 0x30ab, 0x30fc, 0x0, + 0x30a9, 0x0, 0x30aa, 0x0, 0x30aa, 0x30f3, 0x30b9, 0x0, 0x30aa, + 0x30fc, 0x30e0, 0x0, 0x30ab, 0x0, 0x30ab, 0x3099, 0x0, 0x30ab, + 0x3099, 0x30ed, 0x30f3, 0x0, 0x30ab, 0x3099, 0x30f3, 0x30de, + 0x0, 0x30ab, 0x30a4, 0x30ea, 0x0, 0x30ab, 0x30e9, 0x30c3, + 0x30c8, 0x0, 0x30ab, 0x30ed, 0x30ea, 0x30fc, 0x0, 0x30ad, 0x0, + 0x30ad, 0x3099, 0x0, 0x30ad, 0x3099, 0x30ab, 0x3099, 0x0, + 0x30ad, 0x3099, 0x30cb, 0x30fc, 0x0, 0x30ad, 0x3099, 0x30eb, + 0x30bf, 0x3099, 0x30fc, 0x0, 0x30ad, 0x30e5, 0x30ea, 0x30fc, + 0x0, 0x30ad, 0x30ed, 0x0, 0x30ad, 0x30ed, 0x30af, 0x3099, + 0x30e9, 0x30e0, 0x0, 0x30ad, 0x30ed, 0x30e1, 0x30fc, 0x30c8, + 0x30eb, 0x0, 0x30ad, 0x30ed, 0x30ef, 0x30c3, 0x30c8, 0x0, + 0x30af, 0x0, 0x30af, 0x3099, 0x0, 0x30af, 0x3099, 0x30e9, + 0x30e0, 0x0, 0x30af, 0x3099, 0x30e9, 0x30e0, 0x30c8, 0x30f3, + 0x0, 0x30af, 0x30eb, 0x30bb, 0x3099, 0x30a4, 0x30ed, 0x0, + 0x30af, 0x30ed, 0x30fc, 0x30cd, 0x0, 0x30b1, 0x0, 0x30b1, + 0x3099, 0x0, 0x30b1, 0x30fc, 0x30b9, 0x0, 0x30b3, 0x0, 0x30b3, + 0x3099, 0x0, 0x30b3, 0x30b3, 0x0, 0x30b3, 0x30c8, 0x0, 0x30b3, + 0x30eb, 0x30ca, 0x0, 0x30b3, 0x30fc, 0x30db, 0x309a, 0x0, + 0x30b5, 0x0, 0x30b5, 0x3099, 0x0, 0x30b5, 0x30a4, 0x30af, + 0x30eb, 0x0, 0x30b5, 0x30f3, 0x30c1, 0x30fc, 0x30e0, 0x0, + 0x30b7, 0x0, 0x30b7, 0x3099, 0x0, 0x30b7, 0x30ea, 0x30f3, + 0x30af, 0x3099, 0x0, 0x30b9, 0x0, 0x30b9, 0x3099, 0x0, 0x30bb, + 0x0, 0x30bb, 0x3099, 0x0, 0x30bb, 0x30f3, 0x30c1, 0x0, 0x30bb, + 0x30f3, 0x30c8, 0x0, 0x30bd, 0x0, 0x30bd, 0x3099, 0x0, 0x30bf, + 0x0, 0x30bf, 0x3099, 0x0, 0x30bf, 0x3099, 0x30fc, 0x30b9, 0x0, + 0x30c1, 0x0, 0x30c1, 0x3099, 0x0, 0x30c3, 0x0, 0x30c4, 0x0, + 0x30c4, 0x3099, 0x0, 0x30c6, 0x0, 0x30c6, 0x3099, 0x0, 0x30c6, + 0x3099, 0x30b7, 0x0, 0x30c8, 0x0, 0x30c8, 0x3099, 0x0, 0x30c8, + 0x3099, 0x30eb, 0x0, 0x30c8, 0x30f3, 0x0, 0x30ca, 0x0, 0x30ca, + 0x30ce, 0x0, 0x30cb, 0x0, 0x30cc, 0x0, 0x30cd, 0x0, 0x30ce, + 0x0, 0x30ce, 0x30c3, 0x30c8, 0x0, 0x30cf, 0x0, 0x30cf, 0x3099, + 0x0, 0x30cf, 0x3099, 0x30fc, 0x30ec, 0x30eb, 0x0, 0x30cf, + 0x309a, 0x0, 0x30cf, 0x309a, 0x30fc, 0x30bb, 0x30f3, 0x30c8, + 0x0, 0x30cf, 0x309a, 0x30fc, 0x30c4, 0x0, 0x30cf, 0x30a4, + 0x30c4, 0x0, 0x30d2, 0x0, 0x30d2, 0x3099, 0x0, 0x30d2, 0x3099, + 0x30eb, 0x0, 0x30d2, 0x309a, 0x0, 0x30d2, 0x309a, 0x30a2, + 0x30b9, 0x30c8, 0x30eb, 0x0, 0x30d2, 0x309a, 0x30af, 0x30eb, + 0x0, 0x30d2, 0x309a, 0x30b3, 0x0, 0x30d5, 0x0, 0x30d5, 0x3099, + 0x0, 0x30d5, 0x3099, 0x30c3, 0x30b7, 0x30a7, 0x30eb, 0x0, + 0x30d5, 0x309a, 0x0, 0x30d5, 0x30a1, 0x30e9, 0x30c3, 0x30c8, + 0x3099, 0x0, 0x30d5, 0x30a3, 0x30fc, 0x30c8, 0x0, 0x30d5, + 0x30e9, 0x30f3, 0x0, 0x30d8, 0x0, 0x30d8, 0x3099, 0x0, 0x30d8, + 0x3099, 0x30fc, 0x30bf, 0x0, 0x30d8, 0x309a, 0x0, 0x30d8, + 0x309a, 0x30bd, 0x0, 0x30d8, 0x309a, 0x30cb, 0x30d2, 0x0, + 0x30d8, 0x309a, 0x30f3, 0x30b9, 0x0, 0x30d8, 0x309a, 0x30fc, + 0x30b7, 0x3099, 0x0, 0x30d8, 0x30af, 0x30bf, 0x30fc, 0x30eb, + 0x0, 0x30d8, 0x30eb, 0x30c4, 0x0, 0x30db, 0x0, 0x30db, 0x3099, + 0x0, 0x30db, 0x3099, 0x30eb, 0x30c8, 0x0, 0x30db, 0x309a, 0x0, + 0x30db, 0x309a, 0x30a4, 0x30f3, 0x30c8, 0x0, 0x30db, 0x309a, + 0x30f3, 0x30c8, 0x3099, 0x0, 0x30db, 0x30f3, 0x0, 0x30db, + 0x30fc, 0x30eb, 0x0, 0x30db, 0x30fc, 0x30f3, 0x0, 0x30de, 0x0, + 0x30de, 0x30a4, 0x30af, 0x30ed, 0x0, 0x30de, 0x30a4, 0x30eb, + 0x0, 0x30de, 0x30c3, 0x30cf, 0x0, 0x30de, 0x30eb, 0x30af, 0x0, + 0x30de, 0x30f3, 0x30b7, 0x30e7, 0x30f3, 0x0, 0x30df, 0x0, + 0x30df, 0x30af, 0x30ed, 0x30f3, 0x0, 0x30df, 0x30ea, 0x0, + 0x30df, 0x30ea, 0x30cf, 0x3099, 0x30fc, 0x30eb, 0x0, 0x30e0, + 0x0, 0x30e1, 0x0, 0x30e1, 0x30ab, 0x3099, 0x0, 0x30e1, 0x30ab, + 0x3099, 0x30c8, 0x30f3, 0x0, 0x30e1, 0x30fc, 0x30c8, 0x30eb, + 0x0, 0x30e2, 0x0, 0x30e3, 0x0, 0x30e4, 0x0, 0x30e4, 0x30fc, + 0x30c8, 0x3099, 0x0, 0x30e4, 0x30fc, 0x30eb, 0x0, 0x30e5, 0x0, + 0x30e6, 0x0, 0x30e6, 0x30a2, 0x30f3, 0x0, 0x30e7, 0x0, 0x30e8, + 0x0, 0x30e9, 0x0, 0x30ea, 0x0, 0x30ea, 0x30c3, 0x30c8, 0x30eb, + 0x0, 0x30ea, 0x30e9, 0x0, 0x30eb, 0x0, 0x30eb, 0x30d2, 0x309a, + 0x30fc, 0x0, 0x30eb, 0x30fc, 0x30d5, 0x3099, 0x30eb, 0x0, + 0x30ec, 0x0, 0x30ec, 0x30e0, 0x0, 0x30ec, 0x30f3, 0x30c8, + 0x30b1, 0x3099, 0x30f3, 0x0, 0x30ed, 0x0, 0x30ef, 0x0, 0x30ef, + 0x3099, 0x0, 0x30ef, 0x30c3, 0x30c8, 0x0, 0x30f0, 0x0, 0x30f0, + 0x3099, 0x0, 0x30f1, 0x0, 0x30f1, 0x3099, 0x0, 0x30f2, 0x0, + 0x30f2, 0x3099, 0x0, 0x30f3, 0x0, 0x30fb, 0x0, 0x30fc, 0x0, + 0x30fd, 0x3099, 0x0, 0x349e, 0x0, 0x34b9, 0x0, 0x34bb, 0x0, + 0x34df, 0x0, 0x3515, 0x0, 0x36ee, 0x0, 0x36fc, 0x0, 0x3781, + 0x0, 0x382f, 0x0, 0x3862, 0x0, 0x387c, 0x0, 0x38c7, 0x0, + 0x38e3, 0x0, 0x391c, 0x0, 0x393a, 0x0, 0x3a2e, 0x0, 0x3a6c, + 0x0, 0x3ae4, 0x0, 0x3b08, 0x0, 0x3b19, 0x0, 0x3b49, 0x0, + 0x3b9d, 0x0, 0x3c18, 0x0, 0x3c4e, 0x0, 0x3d33, 0x0, 0x3d96, + 0x0, 0x3eac, 0x0, 0x3eb8, 0x0, 0x3f1b, 0x0, 0x3ffc, 0x0, + 0x4008, 0x0, 0x4018, 0x0, 0x4039, 0x0, 0x4046, 0x0, 0x4096, + 0x0, 0x40e3, 0x0, 0x412f, 0x0, 0x4202, 0x0, 0x4227, 0x0, + 0x42a0, 0x0, 0x4301, 0x0, 0x4334, 0x0, 0x4359, 0x0, 0x43d5, + 0x0, 0x43d9, 0x0, 0x440b, 0x0, 0x446b, 0x0, 0x452b, 0x0, + 0x455d, 0x0, 0x4561, 0x0, 0x456b, 0x0, 0x45d7, 0x0, 0x45f9, + 0x0, 0x4635, 0x0, 0x46be, 0x0, 0x46c7, 0x0, 0x4995, 0x0, + 0x49e6, 0x0, 0x4a6e, 0x0, 0x4a76, 0x0, 0x4ab2, 0x0, 0x4b33, + 0x0, 0x4bce, 0x0, 0x4cce, 0x0, 0x4ced, 0x0, 0x4cf8, 0x0, + 0x4d56, 0x0, 0x4e00, 0x0, 0x4e01, 0x0, 0x4e03, 0x0, 0x4e09, + 0x0, 0x4e0a, 0x0, 0x4e0b, 0x0, 0x4e0d, 0x0, 0x4e19, 0x0, + 0x4e26, 0x0, 0x4e28, 0x0, 0x4e2d, 0x0, 0x4e32, 0x0, 0x4e36, + 0x0, 0x4e38, 0x0, 0x4e39, 0x0, 0x4e3d, 0x0, 0x4e3f, 0x0, + 0x4e41, 0x0, 0x4e59, 0x0, 0x4e5d, 0x0, 0x4e82, 0x0, 0x4e85, + 0x0, 0x4e86, 0x0, 0x4e8c, 0x0, 0x4e94, 0x0, 0x4ea0, 0x0, + 0x4ea4, 0x0, 0x4eae, 0x0, 0x4eba, 0x0, 0x4ec0, 0x0, 0x4ecc, + 0x0, 0x4ee4, 0x0, 0x4f01, 0x0, 0x4f11, 0x0, 0x4f60, 0x0, + 0x4f80, 0x0, 0x4f86, 0x0, 0x4f8b, 0x0, 0x4fae, 0x0, 0x4fbb, + 0x0, 0x4fbf, 0x0, 0x5002, 0x0, 0x502b, 0x0, 0x507a, 0x0, + 0x5099, 0x0, 0x50cf, 0x0, 0x50da, 0x0, 0x50e7, 0x0, 0x512a, + 0x0, 0x513f, 0x0, 0x5140, 0x0, 0x5145, 0x0, 0x514d, 0x0, + 0x5154, 0x0, 0x5164, 0x0, 0x5165, 0x0, 0x5167, 0x0, 0x5168, + 0x0, 0x5169, 0x0, 0x516b, 0x0, 0x516d, 0x0, 0x5177, 0x0, + 0x5180, 0x0, 0x5182, 0x0, 0x518d, 0x0, 0x5192, 0x0, 0x5195, + 0x0, 0x5196, 0x0, 0x5197, 0x0, 0x5199, 0x0, 0x51a4, 0x0, + 0x51ab, 0x0, 0x51ac, 0x0, 0x51b5, 0x0, 0x51b7, 0x0, 0x51c9, + 0x0, 0x51cc, 0x0, 0x51dc, 0x0, 0x51de, 0x0, 0x51e0, 0x0, + 0x51f5, 0x0, 0x5200, 0x0, 0x5203, 0x0, 0x5207, 0x0, 0x5217, + 0x0, 0x521d, 0x0, 0x5229, 0x0, 0x523a, 0x0, 0x523b, 0x0, + 0x5246, 0x0, 0x524d, 0x0, 0x5272, 0x0, 0x5277, 0x0, 0x5289, + 0x0, 0x529b, 0x0, 0x52a3, 0x0, 0x52b3, 0x0, 0x52b4, 0x0, + 0x52c7, 0x0, 0x52c9, 0x0, 0x52d2, 0x0, 0x52de, 0x0, 0x52e4, + 0x0, 0x52f5, 0x0, 0x52f9, 0x0, 0x52fa, 0x0, 0x5305, 0x0, + 0x5306, 0x0, 0x5315, 0x0, 0x5317, 0x0, 0x531a, 0x0, 0x5338, + 0x0, 0x533b, 0x0, 0x533f, 0x0, 0x5341, 0x0, 0x5344, 0x0, + 0x5345, 0x0, 0x5349, 0x0, 0x5351, 0x0, 0x5354, 0x0, 0x535a, + 0x0, 0x535c, 0x0, 0x5369, 0x0, 0x5370, 0x0, 0x5373, 0x0, + 0x5375, 0x0, 0x537d, 0x0, 0x537f, 0x0, 0x5382, 0x0, 0x53b6, + 0x0, 0x53c3, 0x0, 0x53c8, 0x0, 0x53ca, 0x0, 0x53cc, 0x0, + 0x53df, 0x0, 0x53e3, 0x0, 0x53e5, 0x0, 0x53eb, 0x0, 0x53ef, + 0x0, 0x53f1, 0x0, 0x53f3, 0x0, 0x5406, 0x0, 0x5408, 0x0, + 0x540d, 0x0, 0x540f, 0x0, 0x541d, 0x0, 0x5438, 0x0, 0x5439, + 0x0, 0x5442, 0x0, 0x5448, 0x0, 0x5468, 0x0, 0x549e, 0x0, + 0x54a2, 0x0, 0x54bd, 0x0, 0x54f6, 0x0, 0x5510, 0x0, 0x554f, + 0x0, 0x5553, 0x0, 0x5555, 0x0, 0x5563, 0x0, 0x5584, 0x0, + 0x5587, 0x0, 0x5599, 0x0, 0x559d, 0x0, 0x55ab, 0x0, 0x55b3, + 0x0, 0x55b6, 0x0, 0x55c0, 0x0, 0x55c2, 0x0, 0x55e2, 0x0, + 0x5606, 0x0, 0x5651, 0x0, 0x5668, 0x0, 0x5674, 0x0, 0x56d7, + 0x0, 0x56db, 0x0, 0x56f9, 0x0, 0x5716, 0x0, 0x5717, 0x0, + 0x571f, 0x0, 0x5730, 0x0, 0x578b, 0x0, 0x57ce, 0x0, 0x57f4, + 0x0, 0x580d, 0x0, 0x5831, 0x0, 0x5832, 0x0, 0x5840, 0x0, + 0x585a, 0x0, 0x585e, 0x0, 0x58a8, 0x0, 0x58ac, 0x0, 0x58b3, + 0x0, 0x58d8, 0x0, 0x58df, 0x0, 0x58eb, 0x0, 0x58ee, 0x0, + 0x58f0, 0x0, 0x58f2, 0x0, 0x58f7, 0x0, 0x5902, 0x0, 0x5906, + 0x0, 0x590a, 0x0, 0x5915, 0x0, 0x591a, 0x0, 0x591c, 0x0, + 0x5922, 0x0, 0x5927, 0x0, 0x5927, 0x6b63, 0x0, 0x5929, 0x0, + 0x5944, 0x0, 0x5948, 0x0, 0x5951, 0x0, 0x5954, 0x0, 0x5962, + 0x0, 0x5973, 0x0, 0x59d8, 0x0, 0x59ec, 0x0, 0x5a1b, 0x0, + 0x5a27, 0x0, 0x5a62, 0x0, 0x5a66, 0x0, 0x5ab5, 0x0, 0x5b08, + 0x0, 0x5b28, 0x0, 0x5b3e, 0x0, 0x5b50, 0x0, 0x5b57, 0x0, + 0x5b66, 0x0, 0x5b80, 0x0, 0x5b85, 0x0, 0x5b97, 0x0, 0x5bc3, + 0x0, 0x5bd8, 0x0, 0x5be7, 0x0, 0x5bee, 0x0, 0x5bf3, 0x0, + 0x5bf8, 0x0, 0x5bff, 0x0, 0x5c06, 0x0, 0x5c0f, 0x0, 0x5c22, + 0x0, 0x5c38, 0x0, 0x5c3f, 0x0, 0x5c60, 0x0, 0x5c62, 0x0, + 0x5c64, 0x0, 0x5c65, 0x0, 0x5c6e, 0x0, 0x5c71, 0x0, 0x5c8d, + 0x0, 0x5cc0, 0x0, 0x5d19, 0x0, 0x5d43, 0x0, 0x5d50, 0x0, + 0x5d6b, 0x0, 0x5d6e, 0x0, 0x5d7c, 0x0, 0x5db2, 0x0, 0x5dba, + 0x0, 0x5ddb, 0x0, 0x5de1, 0x0, 0x5de2, 0x0, 0x5de5, 0x0, + 0x5de6, 0x0, 0x5df1, 0x0, 0x5dfd, 0x0, 0x5dfe, 0x0, 0x5e28, + 0x0, 0x5e3d, 0x0, 0x5e69, 0x0, 0x5e72, 0x0, 0x5e73, 0x6210, + 0x0, 0x5e74, 0x0, 0x5e7a, 0x0, 0x5e7c, 0x0, 0x5e7f, 0x0, + 0x5ea6, 0x0, 0x5eb0, 0x0, 0x5eb3, 0x0, 0x5eb6, 0x0, 0x5ec9, + 0x0, 0x5eca, 0x0, 0x5ed2, 0x0, 0x5ed3, 0x0, 0x5ed9, 0x0, + 0x5eec, 0x0, 0x5ef4, 0x0, 0x5efe, 0x0, 0x5f04, 0x0, 0x5f0b, + 0x0, 0x5f13, 0x0, 0x5f22, 0x0, 0x5f50, 0x0, 0x5f53, 0x0, + 0x5f61, 0x0, 0x5f62, 0x0, 0x5f69, 0x0, 0x5f6b, 0x0, 0x5f73, + 0x0, 0x5f8b, 0x0, 0x5f8c, 0x0, 0x5f97, 0x0, 0x5f9a, 0x0, + 0x5fa9, 0x0, 0x5fad, 0x0, 0x5fc3, 0x0, 0x5fcd, 0x0, 0x5fd7, + 0x0, 0x5ff5, 0x0, 0x5ff9, 0x0, 0x6012, 0x0, 0x601c, 0x0, + 0x6075, 0x0, 0x6081, 0x0, 0x6094, 0x0, 0x60c7, 0x0, 0x60d8, + 0x0, 0x60e1, 0x0, 0x6108, 0x0, 0x6144, 0x0, 0x6148, 0x0, + 0x614c, 0x0, 0x614e, 0x0, 0x6160, 0x0, 0x6168, 0x0, 0x617a, + 0x0, 0x618e, 0x0, 0x6190, 0x0, 0x61a4, 0x0, 0x61af, 0x0, + 0x61b2, 0x0, 0x61de, 0x0, 0x61f2, 0x0, 0x61f6, 0x0, 0x6200, + 0x0, 0x6208, 0x0, 0x6210, 0x0, 0x621b, 0x0, 0x622e, 0x0, + 0x6234, 0x0, 0x6236, 0x0, 0x624b, 0x0, 0x6253, 0x0, 0x625d, + 0x0, 0x6295, 0x0, 0x62b1, 0x0, 0x62c9, 0x0, 0x62cf, 0x0, + 0x62d3, 0x0, 0x62d4, 0x0, 0x62fc, 0x0, 0x62fe, 0x0, 0x6307, + 0x0, 0x633d, 0x0, 0x6350, 0x0, 0x6355, 0x0, 0x6368, 0x0, + 0x637b, 0x0, 0x6383, 0x0, 0x63a0, 0x0, 0x63a9, 0x0, 0x63c4, + 0x0, 0x63c5, 0x0, 0x63e4, 0x0, 0x641c, 0x0, 0x6422, 0x0, + 0x6452, 0x0, 0x6469, 0x0, 0x6477, 0x0, 0x647e, 0x0, 0x649a, + 0x0, 0x649d, 0x0, 0x64c4, 0x0, 0x652f, 0x0, 0x6534, 0x0, + 0x654f, 0x0, 0x6556, 0x0, 0x656c, 0x0, 0x6578, 0x0, 0x6587, + 0x0, 0x6597, 0x0, 0x6599, 0x0, 0x65a4, 0x0, 0x65b0, 0x0, + 0x65b9, 0x0, 0x65c5, 0x0, 0x65e0, 0x0, 0x65e2, 0x0, 0x65e3, + 0x0, 0x65e5, 0x0, 0x660e, 0x6cbb, 0x0, 0x6613, 0x0, 0x6620, + 0x0, 0x662d, 0x548c, 0x0, 0x6649, 0x0, 0x6674, 0x0, 0x6688, + 0x0, 0x6691, 0x0, 0x669c, 0x0, 0x66b4, 0x0, 0x66c6, 0x0, + 0x66f0, 0x0, 0x66f4, 0x0, 0x66f8, 0x0, 0x6700, 0x0, 0x6708, + 0x0, 0x6709, 0x0, 0x6717, 0x0, 0x671b, 0x0, 0x6721, 0x0, + 0x6728, 0x0, 0x674e, 0x0, 0x6753, 0x0, 0x6756, 0x0, 0x675e, + 0x0, 0x677b, 0x0, 0x6785, 0x0, 0x6797, 0x0, 0x67f3, 0x0, + 0x67fa, 0x0, 0x6817, 0x0, 0x681f, 0x0, 0x682a, 0x0, 0x682a, + 0x5f0f, 0x4f1a, 0x793e, 0x0, 0x6852, 0x0, 0x6881, 0x0, 0x6885, + 0x0, 0x688e, 0x0, 0x68a8, 0x0, 0x6914, 0x0, 0x6942, 0x0, + 0x69a3, 0x0, 0x69ea, 0x0, 0x6a02, 0x0, 0x6a13, 0x0, 0x6aa8, + 0x0, 0x6ad3, 0x0, 0x6adb, 0x0, 0x6b04, 0x0, 0x6b20, 0x0, + 0x6b21, 0x0, 0x6b54, 0x0, 0x6b62, 0x0, 0x6b63, 0x0, 0x6b72, + 0x0, 0x6b77, 0x0, 0x6b79, 0x0, 0x6b9f, 0x0, 0x6bae, 0x0, + 0x6bb3, 0x0, 0x6bba, 0x0, 0x6bbb, 0x0, 0x6bcb, 0x0, 0x6bcd, + 0x0, 0x6bd4, 0x0, 0x6bdb, 0x0, 0x6c0f, 0x0, 0x6c14, 0x0, + 0x6c34, 0x0, 0x6c4e, 0x0, 0x6c67, 0x0, 0x6c88, 0x0, 0x6cbf, + 0x0, 0x6ccc, 0x0, 0x6ccd, 0x0, 0x6ce5, 0x0, 0x6ce8, 0x0, + 0x6d16, 0x0, 0x6d1b, 0x0, 0x6d1e, 0x0, 0x6d34, 0x0, 0x6d3e, + 0x0, 0x6d41, 0x0, 0x6d69, 0x0, 0x6d6a, 0x0, 0x6d77, 0x0, + 0x6d78, 0x0, 0x6d85, 0x0, 0x6dcb, 0x0, 0x6dda, 0x0, 0x6dea, + 0x0, 0x6df9, 0x0, 0x6e1a, 0x0, 0x6e2f, 0x0, 0x6e6e, 0x0, + 0x6e80, 0x0, 0x6e9c, 0x0, 0x6eba, 0x0, 0x6ec7, 0x0, 0x6ecb, + 0x0, 0x6ed1, 0x0, 0x6edb, 0x0, 0x6f0f, 0x0, 0x6f14, 0x0, + 0x6f22, 0x0, 0x6f23, 0x0, 0x6f6e, 0x0, 0x6fc6, 0x0, 0x6feb, + 0x0, 0x6ffe, 0x0, 0x701b, 0x0, 0x701e, 0x0, 0x7039, 0x0, + 0x704a, 0x0, 0x706b, 0x0, 0x7070, 0x0, 0x7077, 0x0, 0x707d, + 0x0, 0x7099, 0x0, 0x70ad, 0x0, 0x70c8, 0x0, 0x70d9, 0x0, + 0x7121, 0x0, 0x7145, 0x0, 0x7149, 0x0, 0x716e, 0x0, 0x719c, + 0x0, 0x71ce, 0x0, 0x71d0, 0x0, 0x7210, 0x0, 0x721b, 0x0, + 0x7228, 0x0, 0x722a, 0x0, 0x722b, 0x0, 0x7235, 0x0, 0x7236, + 0x0, 0x723b, 0x0, 0x723f, 0x0, 0x7247, 0x0, 0x7250, 0x0, + 0x7259, 0x0, 0x725b, 0x0, 0x7262, 0x0, 0x7279, 0x0, 0x7280, + 0x0, 0x7295, 0x0, 0x72ac, 0x0, 0x72af, 0x0, 0x72c0, 0x0, + 0x72fc, 0x0, 0x732a, 0x0, 0x7375, 0x0, 0x737a, 0x0, 0x7384, + 0x0, 0x7387, 0x0, 0x7389, 0x0, 0x738b, 0x0, 0x73a5, 0x0, + 0x73b2, 0x0, 0x73de, 0x0, 0x7406, 0x0, 0x7409, 0x0, 0x7422, + 0x0, 0x7447, 0x0, 0x745c, 0x0, 0x7469, 0x0, 0x7471, 0x0, + 0x7485, 0x0, 0x7489, 0x0, 0x7498, 0x0, 0x74ca, 0x0, 0x74dc, + 0x0, 0x74e6, 0x0, 0x7506, 0x0, 0x7518, 0x0, 0x751f, 0x0, + 0x7524, 0x0, 0x7528, 0x0, 0x7530, 0x0, 0x7532, 0x0, 0x7533, + 0x0, 0x7537, 0x0, 0x753b, 0x0, 0x753e, 0x0, 0x7559, 0x0, + 0x7565, 0x0, 0x7570, 0x0, 0x758b, 0x0, 0x7592, 0x0, 0x75e2, + 0x0, 0x7610, 0x0, 0x761d, 0x0, 0x761f, 0x0, 0x7642, 0x0, + 0x7669, 0x0, 0x7676, 0x0, 0x767d, 0x0, 0x76ae, 0x0, 0x76bf, + 0x0, 0x76ca, 0x0, 0x76db, 0x0, 0x76e3, 0x0, 0x76e7, 0x0, + 0x76ee, 0x0, 0x76f4, 0x0, 0x7701, 0x0, 0x771e, 0x0, 0x771f, + 0x0, 0x7740, 0x0, 0x774a, 0x0, 0x778b, 0x0, 0x77a7, 0x0, + 0x77db, 0x0, 0x77e2, 0x0, 0x77f3, 0x0, 0x784e, 0x0, 0x786b, + 0x0, 0x788c, 0x0, 0x7891, 0x0, 0x78ca, 0x0, 0x78cc, 0x0, + 0x78fb, 0x0, 0x792a, 0x0, 0x793a, 0x0, 0x793c, 0x0, 0x793e, + 0x0, 0x7948, 0x0, 0x7949, 0x0, 0x7950, 0x0, 0x7956, 0x0, + 0x795d, 0x0, 0x795e, 0x0, 0x7965, 0x0, 0x797f, 0x0, 0x7981, + 0x0, 0x798d, 0x0, 0x798e, 0x0, 0x798f, 0x0, 0x79ae, 0x0, + 0x79b8, 0x0, 0x79be, 0x0, 0x79ca, 0x0, 0x79d8, 0x0, 0x79eb, + 0x0, 0x7a1c, 0x0, 0x7a40, 0x0, 0x7a4a, 0x0, 0x7a4f, 0x0, + 0x7a74, 0x0, 0x7a7a, 0x0, 0x7a81, 0x0, 0x7ab1, 0x0, 0x7acb, + 0x0, 0x7aee, 0x0, 0x7af9, 0x0, 0x7b20, 0x0, 0x7b8f, 0x0, + 0x7bc0, 0x0, 0x7bc6, 0x0, 0x7bc9, 0x0, 0x7c3e, 0x0, 0x7c60, + 0x0, 0x7c73, 0x0, 0x7c7b, 0x0, 0x7c92, 0x0, 0x7cbe, 0x0, + 0x7cd2, 0x0, 0x7cd6, 0x0, 0x7ce3, 0x0, 0x7ce7, 0x0, 0x7ce8, + 0x0, 0x7cf8, 0x0, 0x7d00, 0x0, 0x7d10, 0x0, 0x7d22, 0x0, + 0x7d2f, 0x0, 0x7d42, 0x0, 0x7d5b, 0x0, 0x7d63, 0x0, 0x7da0, + 0x0, 0x7dbe, 0x0, 0x7dc7, 0x0, 0x7df4, 0x0, 0x7e02, 0x0, + 0x7e09, 0x0, 0x7e37, 0x0, 0x7e41, 0x0, 0x7e45, 0x0, 0x7f36, + 0x0, 0x7f3e, 0x0, 0x7f51, 0x0, 0x7f72, 0x0, 0x7f79, 0x0, + 0x7f7a, 0x0, 0x7f85, 0x0, 0x7f8a, 0x0, 0x7f95, 0x0, 0x7f9a, + 0x0, 0x7fbd, 0x0, 0x7ffa, 0x0, 0x8001, 0x0, 0x8005, 0x0, + 0x800c, 0x0, 0x8012, 0x0, 0x8033, 0x0, 0x8046, 0x0, 0x8060, + 0x0, 0x806f, 0x0, 0x8070, 0x0, 0x807e, 0x0, 0x807f, 0x0, + 0x8089, 0x0, 0x808b, 0x0, 0x80ad, 0x0, 0x80b2, 0x0, 0x8103, + 0x0, 0x813e, 0x0, 0x81d8, 0x0, 0x81e3, 0x0, 0x81e8, 0x0, + 0x81ea, 0x0, 0x81ed, 0x0, 0x81f3, 0x0, 0x81fc, 0x0, 0x8201, + 0x0, 0x8204, 0x0, 0x820c, 0x0, 0x8218, 0x0, 0x821b, 0x0, + 0x821f, 0x0, 0x826e, 0x0, 0x826f, 0x0, 0x8272, 0x0, 0x8278, + 0x0, 0x8279, 0x0, 0x828b, 0x0, 0x8291, 0x0, 0x829d, 0x0, + 0x82b1, 0x0, 0x82b3, 0x0, 0x82bd, 0x0, 0x82e5, 0x0, 0x82e6, + 0x0, 0x831d, 0x0, 0x8323, 0x0, 0x8336, 0x0, 0x8352, 0x0, + 0x8353, 0x0, 0x8363, 0x0, 0x83ad, 0x0, 0x83bd, 0x0, 0x83c9, + 0x0, 0x83ca, 0x0, 0x83cc, 0x0, 0x83dc, 0x0, 0x83e7, 0x0, + 0x83ef, 0x0, 0x83f1, 0x0, 0x843d, 0x0, 0x8449, 0x0, 0x8457, + 0x0, 0x84ee, 0x0, 0x84f1, 0x0, 0x84f3, 0x0, 0x84fc, 0x0, + 0x8516, 0x0, 0x8564, 0x0, 0x85cd, 0x0, 0x85fa, 0x0, 0x8606, + 0x0, 0x8612, 0x0, 0x862d, 0x0, 0x863f, 0x0, 0x864d, 0x0, + 0x8650, 0x0, 0x865c, 0x0, 0x8667, 0x0, 0x8669, 0x0, 0x866b, + 0x0, 0x8688, 0x0, 0x86a9, 0x0, 0x86e2, 0x0, 0x870e, 0x0, + 0x8728, 0x0, 0x876b, 0x0, 0x8779, 0x0, 0x8786, 0x0, 0x87ba, + 0x0, 0x87e1, 0x0, 0x8801, 0x0, 0x881f, 0x0, 0x8840, 0x0, + 0x884c, 0x0, 0x8860, 0x0, 0x8863, 0x0, 0x88c2, 0x0, 0x88cf, + 0x0, 0x88d7, 0x0, 0x88de, 0x0, 0x88e1, 0x0, 0x88f8, 0x0, + 0x88fa, 0x0, 0x8910, 0x0, 0x8941, 0x0, 0x8964, 0x0, 0x897e, + 0x0, 0x8986, 0x0, 0x898b, 0x0, 0x8996, 0x0, 0x89d2, 0x0, + 0x89e3, 0x0, 0x8a00, 0x0, 0x8aa0, 0x0, 0x8aaa, 0x0, 0x8abf, + 0x0, 0x8acb, 0x0, 0x8ad2, 0x0, 0x8ad6, 0x0, 0x8aed, 0x0, + 0x8af8, 0x0, 0x8afe, 0x0, 0x8b01, 0x0, 0x8b39, 0x0, 0x8b58, + 0x0, 0x8b80, 0x0, 0x8b8a, 0x0, 0x8c37, 0x0, 0x8c46, 0x0, + 0x8c48, 0x0, 0x8c55, 0x0, 0x8c78, 0x0, 0x8c9d, 0x0, 0x8ca1, + 0x0, 0x8ca9, 0x0, 0x8cab, 0x0, 0x8cc1, 0x0, 0x8cc2, 0x0, + 0x8cc7, 0x0, 0x8cc8, 0x0, 0x8cd3, 0x0, 0x8d08, 0x0, 0x8d1b, + 0x0, 0x8d64, 0x0, 0x8d70, 0x0, 0x8d77, 0x0, 0x8db3, 0x0, + 0x8dbc, 0x0, 0x8dcb, 0x0, 0x8def, 0x0, 0x8df0, 0x0, 0x8eab, + 0x0, 0x8eca, 0x0, 0x8ed4, 0x0, 0x8f26, 0x0, 0x8f2a, 0x0, + 0x8f38, 0x0, 0x8f3b, 0x0, 0x8f62, 0x0, 0x8f9b, 0x0, 0x8f9e, + 0x0, 0x8fb0, 0x0, 0x8fb5, 0x0, 0x8fb6, 0x0, 0x9023, 0x0, + 0x9038, 0x0, 0x904a, 0x0, 0x9069, 0x0, 0x9072, 0x0, 0x907c, + 0x0, 0x908f, 0x0, 0x9091, 0x0, 0x9094, 0x0, 0x90ce, 0x0, + 0x90de, 0x0, 0x90f1, 0x0, 0x90fd, 0x0, 0x9111, 0x0, 0x911b, + 0x0, 0x9149, 0x0, 0x916a, 0x0, 0x9199, 0x0, 0x91b4, 0x0, + 0x91c6, 0x0, 0x91cc, 0x0, 0x91cf, 0x0, 0x91d1, 0x0, 0x9234, + 0x0, 0x9238, 0x0, 0x9276, 0x0, 0x927c, 0x0, 0x92d7, 0x0, + 0x92d8, 0x0, 0x9304, 0x0, 0x934a, 0x0, 0x93f9, 0x0, 0x9415, + 0x0, 0x9577, 0x0, 0x9580, 0x0, 0x958b, 0x0, 0x95ad, 0x0, + 0x95b7, 0x0, 0x961c, 0x0, 0x962e, 0x0, 0x964b, 0x0, 0x964d, + 0x0, 0x9675, 0x0, 0x9678, 0x0, 0x967c, 0x0, 0x9686, 0x0, + 0x96a3, 0x0, 0x96b6, 0x0, 0x96b7, 0x0, 0x96b8, 0x0, 0x96b9, + 0x0, 0x96c3, 0x0, 0x96e2, 0x0, 0x96e3, 0x0, 0x96e8, 0x0, + 0x96f6, 0x0, 0x96f7, 0x0, 0x9723, 0x0, 0x9732, 0x0, 0x9748, + 0x0, 0x9751, 0x0, 0x9756, 0x0, 0x975e, 0x0, 0x9762, 0x0, + 0x9769, 0x0, 0x97cb, 0x0, 0x97db, 0x0, 0x97e0, 0x0, 0x97ed, + 0x0, 0x97f3, 0x0, 0x97ff, 0x0, 0x9801, 0x0, 0x9805, 0x0, + 0x980b, 0x0, 0x9818, 0x0, 0x9829, 0x0, 0x983b, 0x0, 0x985e, + 0x0, 0x98a8, 0x0, 0x98db, 0x0, 0x98df, 0x0, 0x98e2, 0x0, + 0x98ef, 0x0, 0x98fc, 0x0, 0x9928, 0x0, 0x9929, 0x0, 0x9996, + 0x0, 0x9999, 0x0, 0x99a7, 0x0, 0x99ac, 0x0, 0x99c2, 0x0, + 0x99f1, 0x0, 0x99fe, 0x0, 0x9a6a, 0x0, 0x9aa8, 0x0, 0x9ad8, + 0x0, 0x9adf, 0x0, 0x9b12, 0x0, 0x9b25, 0x0, 0x9b2f, 0x0, + 0x9b32, 0x0, 0x9b3c, 0x0, 0x9b5a, 0x0, 0x9b6f, 0x0, 0x9c40, + 0x0, 0x9c57, 0x0, 0x9ce5, 0x0, 0x9cfd, 0x0, 0x9d67, 0x0, + 0x9db4, 0x0, 0x9dfa, 0x0, 0x9e1e, 0x0, 0x9e75, 0x0, 0x9e7f, + 0x0, 0x9e97, 0x0, 0x9e9f, 0x0, 0x9ea5, 0x0, 0x9ebb, 0x0, + 0x9ec3, 0x0, 0x9ecd, 0x0, 0x9ece, 0x0, 0x9ed1, 0x0, 0x9ef9, + 0x0, 0x9efd, 0x0, 0x9efe, 0x0, 0x9f05, 0x0, 0x9f0e, 0x0, + 0x9f0f, 0x0, 0x9f13, 0x0, 0x9f16, 0x0, 0x9f20, 0x0, 0x9f3b, + 0x0, 0x9f43, 0x0, 0x9f4a, 0x0, 0x9f52, 0x0, 0x9f8d, 0x0, + 0x9f8e, 0x0, 0x9f9c, 0x0, 0x9f9f, 0x0, 0x9fa0, 0x0, 0xa76f, + 0x0, 0x11099, 0x110ba, 0x0, 0x1109b, 0x110ba, 0x0, 0x110a5, + 0x110ba, 0x0, 0x11131, 0x11127, 0x0, 0x11132, 0x11127, 0x0, + 0x1d157, 0x1d165, 0x0, 0x1d158, 0x1d165, 0x0, 0x1d158, 0x1d165, + 0x1d16e, 0x0, 0x1d158, 0x1d165, 0x1d16f, 0x0, 0x1d158, 0x1d165, + 0x1d170, 0x0, 0x1d158, 0x1d165, 0x1d171, 0x0, 0x1d158, 0x1d165, + 0x1d172, 0x0, 0x1d1b9, 0x1d165, 0x0, 0x1d1b9, 0x1d165, 0x1d16e, + 0x0, 0x1d1b9, 0x1d165, 0x1d16f, 0x0, 0x1d1ba, 0x1d165, 0x0, + 0x1d1ba, 0x1d165, 0x1d16e, 0x0, 0x1d1ba, 0x1d165, 0x1d16f, 0x0, + 0x20122, 0x0, 0x2051c, 0x0, 0x20525, 0x0, 0x2054b, 0x0, + 0x2063a, 0x0, 0x20804, 0x0, 0x208de, 0x0, 0x20a2c, 0x0, + 0x20b63, 0x0, 0x214e4, 0x0, 0x216a8, 0x0, 0x216ea, 0x0, + 0x219c8, 0x0, 0x21b18, 0x0, 0x21d0b, 0x0, 0x21de4, 0x0, + 0x21de6, 0x0, 0x22183, 0x0, 0x2219f, 0x0, 0x22331, 0x0, + 0x226d4, 0x0, 0x22844, 0x0, 0x2284a, 0x0, 0x22b0c, 0x0, + 0x22bf1, 0x0, 0x2300a, 0x0, 0x232b8, 0x0, 0x2335f, 0x0, + 0x23393, 0x0, 0x2339c, 0x0, 0x233c3, 0x0, 0x233d5, 0x0, + 0x2346d, 0x0, 0x236a3, 0x0, 0x238a7, 0x0, 0x23a8d, 0x0, + 0x23afa, 0x0, 0x23cbc, 0x0, 0x23d1e, 0x0, 0x23ed1, 0x0, + 0x23f5e, 0x0, 0x23f8e, 0x0, 0x24263, 0x0, 0x242ee, 0x0, + 0x243ab, 0x0, 0x24608, 0x0, 0x24735, 0x0, 0x24814, 0x0, + 0x24c36, 0x0, 0x24c92, 0x0, 0x24fa1, 0x0, 0x24fb8, 0x0, + 0x25044, 0x0, 0x250f2, 0x0, 0x250f3, 0x0, 0x25119, 0x0, + 0x25133, 0x0, 0x25249, 0x0, 0x2541d, 0x0, 0x25626, 0x0, + 0x2569a, 0x0, 0x256c5, 0x0, 0x2597c, 0x0, 0x25aa7, 0x0, + 0x25bab, 0x0, 0x25c80, 0x0, 0x25cd0, 0x0, 0x25f86, 0x0, + 0x261da, 0x0, 0x26228, 0x0, 0x26247, 0x0, 0x262d9, 0x0, + 0x2633e, 0x0, 0x264da, 0x0, 0x26523, 0x0, 0x265a8, 0x0, + 0x267a7, 0x0, 0x267b5, 0x0, 0x26b3c, 0x0, 0x26c36, 0x0, + 0x26cd5, 0x0, 0x26d6b, 0x0, 0x26f2c, 0x0, 0x26fb1, 0x0, + 0x270d2, 0x0, 0x273ca, 0x0, 0x27667, 0x0, 0x278ae, 0x0, + 0x27966, 0x0, 0x27ca8, 0x0, 0x27ed3, 0x0, 0x27f2f, 0x0, + 0x285d2, 0x0, 0x285ed, 0x0, 0x2872e, 0x0, 0x28bfa, 0x0, + 0x28d77, 0x0, 0x29145, 0x0, 0x291df, 0x0, 0x2921a, 0x0, + 0x2940a, 0x0, 0x29496, 0x0, 0x295b6, 0x0, 0x29b30, 0x0, + 0x2a0ce, 0x0, 0x2a105, 0x0, 0x2a20e, 0x0, 0x2a291, 0x0, + 0x2a392, 0x0, 0x2a600, 0x0 + ]; + return t; + } + } + +} diff --git a/libphobos/src/std/internal/unicode_grapheme.d b/libphobos/src/std/internal/unicode_grapheme.d new file mode 100644 index 0000000..b4befc9 --- /dev/null +++ b/libphobos/src/std/internal/unicode_grapheme.d @@ -0,0 +1,293 @@ +module std.internal.unicode_grapheme; +import std.internal.unicode_tables; + +package(std): + +static if (size_t.sizeof == 8) +{ + //832 bytes + enum hangulLVTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x20, 0x40], + [0x100, 0x80, 0xa00], [0x2010000000000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x4000300020001, 0x1000700060005, 0x5000400030002, 0x2000100070006, + 0x6000500040003, 0x3000200010007, 0x7000600050004, 0x4000300020001, + 0x1000700060005, 0x5000400030002, 0x8000100070006, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x100000010000001, 0x1000000100000, 0x10000001000, + 0x1000000100000010, 0x10000001000000, 0x100000010000, 0x1000000100, + 0x100000010000001, 0x1000000100000, 0x10000001000, + 0x1000000100000010, 0x10000001000000, 0x100000010000, 0x1000000100, + 0x100000010000001, 0x1000000100000, 0x10000001000, + 0x1000000100000010, 0x10000001000000, 0x100000010000, 0x1000000100, + 0x100000010000001, 0x1000000100000, 0x10000001000, + 0x1000000100000010, 0x10000001000000, 0x100000010000, 0x1000000100, + 0x10000001000000, 0x100000010000, 0x100, 0x0, 0x0, 0x0, 0x0, 0x0]); + //832 bytes + enum hangulLVTTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x20, 0x40], + [0x100, 0x80, 0xa00], [0x2010000000000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x4000300020001, 0x1000700060005, 0x5000400030002, 0x2000100070006, + 0x6000500040003, 0x3000200010007, 0x7000600050004, 0x4000300020001, + 0x1000700060005, 0x5000400030002, 0x8000100070006, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xfeffffffeffffffe, 0xfffeffffffefffff, 0xfffffeffffffefff, + 0xeffffffeffffffef, 0xffeffffffeffffff, 0xffffeffffffeffff, + 0xffffffeffffffeff, 0xfeffffffeffffffe, 0xfffeffffffefffff, + 0xfffffeffffffefff, 0xeffffffeffffffef, 0xffeffffffeffffff, + 0xffffeffffffeffff, 0xffffffeffffffeff, 0xfeffffffeffffffe, + 0xfffeffffffefffff, 0xfffffeffffffefff, 0xeffffffeffffffef, + 0xffeffffffeffffff, 0xffffeffffffeffff, 0xffffffeffffffeff, + 0xfeffffffeffffffe, 0xfffeffffffefffff, 0xfffffeffffffefff, + 0xeffffffeffffffef, 0xffeffffffeffffff, 0xffffeffffffeffff, + 0xffffffeffffffeff, 0xffeffffffeffffff, 0xffffeffffffeffff, + 0xffffffeff, 0x0, 0x0, 0x0, 0x0, 0x0]); + //1536 bytes + enum mcTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x20, 0x60], [0x100, + 0x100, 0x1800], [0x202030202020100, 0x206020205020204, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x3000200010000, 0x6000000050004, 0x7, 0x8000000000000, + 0xb000a00090000, 0xc, 0x0, 0x0, 0x0, 0x0, 0xd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x110010000f000e, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x130012, 0x1400000000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x15000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x160000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc800000000000008, 0xde01, + 0xc00000000000000c, 0x801981, 0xc000000000000008, 0x1, + 0xc000000000000008, 0x1a01, 0x400000000000000c, 0x801981, + 0xc000000000000000, 0x801dc6, 0xe, 0x1e, 0x400000000000000c, + 0x600d9f, 0xc00000000000000c, 0x801dc1, 0xc, 0xc0000ff038000, + 0xc000000000000000, 0x8000000000000000, 0x0, 0x0, + 0x1902180000000000, 0x3f9c00c00000, 0x1c009f98, 0x0, 0x0, 0x0, + 0xc040000000000000, 0x1bf, 0x1fb0e7800000000, 0x0, + 0xffff000000000000, 0x301, 0x6000000, 0x7e01a00a00000, 0x0, 0x0, + 0xe820000000000010, 0x1b, 0x34c200000004, 0xc5c8000000000, + 0x300ff000000000, 0x0, 0x0, 0xc000200000000, 0xc00000000000, 0x0, + 0x0, 0x0, 0x9800000000, 0x0, 0xfff0000000000003, 0xf, 0x0, 0xc0000, + 0xec30000000000008, 0x1, 0x19800000000000, 0x800000000002000, 0x0, + 0x20c80000000000, 0x0, 0x0, 0x0, 0x16d800000000, 0x5, 0x0, + 0x187000000000004, 0x0, 0x100000000000, 0x0, 0x8038000000000004, + 0x1, 0x0, 0x0, 0x40d00000000000, 0x0, 0x0, 0x7ffffffffffe0000, 0x0, + 0x0, 0x0, 0x7e06000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); + //2336 bytes + enum graphemeExtendTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x20, + 0x70], [0x100, 0x140, 0x2d00], [0x402030202020100, + 0x207020206020205, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020208, 0x202020202020202, + 0x202020202020202, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1000000000000, 0x5000400030002, + 0x9000800070006, 0xd000c000b000a, 0xf00000000000e, + 0x10000000000000, 0x14001300120011, 0x160015, 0x17, 0x0, 0x0, + 0x190018, 0x1a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1b00000000, 0x1f001e001d001c, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20000000000000, 0x22002100000000, + 0x230000, 0x0, 0x2400000000, 0x0, 0x260025, 0x2700000000, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x28000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x2a00290000, 0x0, 0x0, 0x0, 0x2b0000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xffffffffffffffff, 0xffffffffffff, 0x0, 0x0, 0x0, 0x0, + 0x3f8, 0x0, 0x0, 0x0, 0xbffffffffffe0000, 0xb6, 0x7ff0000, + 0x10000fffff800, 0x0, 0x3d9f9fc00000, 0xffff000000020000, 0x7ff, + 0x1ffc000000000, 0xff80000000000, 0x3eeffbc00000, 0xe000000, 0x0, + 0x7ffffff000000000, 0x1400000000000007, 0xc00fe21fe, + 0x5000000000000002, 0xc0080201e, 0x1000000000000006, + 0x23000000023986, 0x1000000000000006, 0xc000021be, + 0xd000000000000002, 0xc00c0201e, 0x4000000000000004, 0x802001, + 0xc000000000000000, 0xc00603dc1, 0x9000000000000000, 0xc00603044, + 0x4000000000000000, 0xc0080201e, 0x0, 0x805c8400, + 0x7f2000000000000, 0x7f80, 0x1bf2000000000000, 0x3f00, + 0x2a0000003000000, 0x7ffe000000000000, 0x1ffffffffeffe0df, 0x40, + 0x66fde00000000000, 0x1e0001c3000000, 0x20002064, 0x0, 0x0, + 0xe0000000, 0x0, 0x0, 0x1c0000001c0000, 0xc0000000c0000, + 0x3fb0000000000000, 0x200ffe40, 0x3800, 0x0, 0x20000000000, 0x0, + 0xe04018700000000, 0x0, 0x0, 0x0, 0x9800000, 0x9ff81fe57f400000, + 0x0, 0x0, 0x17d000000000000f, 0xff80000000004, 0xb3c00000003, + 0x3a34000000000, 0xcff00000000000, 0x0, 0x0, 0x1021fdfff70000, 0x0, + 0x0, 0x0, 0xf000007fffffffff, 0x3000, 0x0, 0x0, 0x1ffffffff0000, + 0x0, 0x0, 0x0, 0x3800000000000, 0x0, 0x8000000000000000, 0x0, + 0xffffffff00000000, 0xfc0000000000, 0x0, 0x6000000, 0x0, 0x0, + 0x3ff7800000000000, 0x80000000, 0x3000000000000, 0x6000000844, 0x0, + 0x0, 0x3ffff00000010, 0x3fc000000000, 0x3ff80, 0x13c8000000000007, + 0x0, 0x667e0000000000, 0x1008, 0xc19d000000000000, + 0x40300000000002, 0x0, 0x0, 0x0, 0x212000000000, 0x40000000, 0x0, + 0x0, 0x0, 0x7f0000ffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0000000, 0x0, + 0x0, 0x0, 0x0, 0x2000000000000000, 0x870000000000f06e, 0x0, 0x0, + 0x0, 0xff00000000000002, 0x7f, 0x678000000000003, 0x0, + 0x1fef8000000007, 0x0, 0x7fc0000000000003, 0x0, 0x0, 0x0, + 0xbf280000000000, 0x0, 0x0, 0x0, 0x78000, 0x0, 0x0, + 0xf807c3a000000000, 0x3c0000000fe7, 0x0, 0x0, 0x1c, 0x0, 0x0, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffff, 0x0, 0x0, 0x0, 0x0]); + +} + +static if (size_t.sizeof == 4) +{ + //832 bytes + enum hangulLVTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x40, 0x80], + [0x100, 0x80, 0xa00], [0x0, 0x20100, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x20001, 0x40003, 0x60005, 0x10007, 0x30002, 0x50004, 0x70006, + 0x20001, 0x40003, 0x60005, 0x10007, 0x30002, 0x50004, 0x70006, + 0x20001, 0x40003, 0x60005, 0x10007, 0x30002, 0x50004, 0x70006, + 0x80001, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x10000001, 0x1000000, 0x100000, 0x10000, + 0x1000, 0x100, 0x10, 0x10000001, 0x1000000, 0x100000, 0x10000, + 0x1000, 0x100, 0x10, 0x10000001, 0x1000000, 0x100000, 0x10000, + 0x1000, 0x100, 0x10, 0x10000001, 0x1000000, 0x100000, 0x10000, + 0x1000, 0x100, 0x10, 0x10000001, 0x1000000, 0x100000, 0x10000, + 0x1000, 0x100, 0x10, 0x10000001, 0x1000000, 0x100000, 0x10000, + 0x1000, 0x100, 0x10, 0x10000001, 0x1000000, 0x100000, 0x10000, + 0x1000, 0x100, 0x10, 0x10000001, 0x1000000, 0x100000, 0x10000, + 0x1000, 0x100, 0x10, 0x1000000, 0x100000, 0x10000, 0x1000, 0x100, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); + //832 bytes + enum hangulLVTTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x40, 0x80], + [0x100, 0x80, 0xa00], [0x0, 0x20100, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x20001, 0x40003, 0x60005, 0x10007, 0x30002, 0x50004, 0x70006, + 0x20001, 0x40003, 0x60005, 0x10007, 0x30002, 0x50004, 0x70006, + 0x20001, 0x40003, 0x60005, 0x10007, 0x30002, 0x50004, 0x70006, + 0x80001, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xeffffffe, 0xfeffffff, 0xffefffff, 0xfffeffff, + 0xffffefff, 0xfffffeff, 0xffffffef, 0xeffffffe, 0xfeffffff, + 0xffefffff, 0xfffeffff, 0xffffefff, 0xfffffeff, 0xffffffef, + 0xeffffffe, 0xfeffffff, 0xffefffff, 0xfffeffff, 0xffffefff, + 0xfffffeff, 0xffffffef, 0xeffffffe, 0xfeffffff, 0xffefffff, + 0xfffeffff, 0xffffefff, 0xfffffeff, 0xffffffef, 0xeffffffe, + 0xfeffffff, 0xffefffff, 0xfffeffff, 0xffffefff, 0xfffffeff, + 0xffffffef, 0xeffffffe, 0xfeffffff, 0xffefffff, 0xfffeffff, + 0xffffefff, 0xfffffeff, 0xffffffef, 0xeffffffe, 0xfeffffff, + 0xffefffff, 0xfffeffff, 0xffffefff, 0xfffffeff, 0xffffffef, + 0xeffffffe, 0xfeffffff, 0xffefffff, 0xfffeffff, 0xffffefff, + 0xfffffeff, 0xffffffef, 0xfeffffff, 0xffefffff, 0xfffeffff, + 0xffffefff, 0xfffffeff, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0]); + //1536 bytes + enum mcTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x40, 0xc0], [0x100, + 0x100, 0x1800], [0x2020100, 0x2020302, 0x5020204, 0x2060202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10000, 0x30002, 0x50004, + 0x60000, 0x7, 0x0, 0x0, 0x80000, 0x90000, 0xb000a, 0xc, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf000e, 0x110010, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x130012, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x150000, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x160000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0xc8000000, 0xde01, 0x0, + 0xc, 0xc0000000, 0x801981, 0x0, 0x8, 0xc0000000, 0x1, 0x0, 0x8, + 0xc0000000, 0x1a01, 0x0, 0xc, 0x40000000, 0x801981, 0x0, 0x0, + 0xc0000000, 0x801dc6, 0x0, 0xe, 0x0, 0x1e, 0x0, 0xc, 0x40000000, + 0x600d9f, 0x0, 0xc, 0xc0000000, 0x801dc1, 0x0, 0xc, 0x0, + 0xff038000, 0xc0000, 0x0, 0xc0000000, 0x0, 0x80000000, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x19021800, 0xc00000, 0x3f9c, 0x1c009f98, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0400000, 0x1bf, 0x0, 0x0, + 0x1fb0e78, 0x0, 0x0, 0x0, 0xffff0000, 0x301, 0x0, 0x6000000, 0x0, + 0xa00000, 0x7e01a, 0x0, 0x0, 0x0, 0x0, 0x10, 0xe8200000, 0x1b, 0x0, + 0x4, 0x34c2, 0x0, 0xc5c80, 0x0, 0x300ff0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xc0002, 0x0, 0xc000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x98, 0x0, + 0x0, 0x3, 0xfff00000, 0xf, 0x0, 0x0, 0x0, 0xc0000, 0x0, 0x8, + 0xec300000, 0x1, 0x0, 0x0, 0x198000, 0x2000, 0x8000000, 0x0, 0x0, + 0x0, 0x20c800, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x16d8, 0x5, 0x0, + 0x0, 0x0, 0x4, 0x1870000, 0x0, 0x0, 0x0, 0x1000, 0x0, 0x0, 0x4, + 0x80380000, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40d000, 0x0, 0x0, + 0x0, 0x0, 0xfffe0000, 0x7fffffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x7e060, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); + //2336 bytes + enum graphemeExtendTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x40, + 0xe0], [0x100, 0x140, 0x2d00], [0x2020100, 0x4020302, 0x6020205, + 0x2070202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020208, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10000, 0x30002, 0x50004, + 0x70006, 0x90008, 0xb000a, 0xd000c, 0xe, 0xf0000, 0x0, 0x100000, + 0x120011, 0x140013, 0x160015, 0x0, 0x17, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x190018, 0x0, 0x1a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1b, 0x1d001c, 0x1f001e, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x200000, 0x0, 0x220021, 0x230000, + 0x0, 0x0, 0x0, 0x0, 0x24, 0x0, 0x0, 0x260025, 0x0, 0x0, 0x27, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x280000, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x290000, 0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2b0000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x3f8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xfffe0000, 0xbfffffff, 0xb6, 0x0, 0x7ff0000, 0x0, 0xfffff800, + 0x10000, 0x0, 0x0, 0x9fc00000, 0x3d9f, 0x20000, 0xffff0000, 0x7ff, + 0x0, 0x0, 0x1ffc0, 0x0, 0xff800, 0xfbc00000, 0x3eef, 0xe000000, + 0x0, 0x0, 0x0, 0x0, 0x7ffffff0, 0x7, 0x14000000, 0xfe21fe, 0xc, + 0x2, 0x50000000, 0x80201e, 0xc, 0x6, 0x10000000, 0x23986, 0x230000, + 0x6, 0x10000000, 0x21be, 0xc, 0x2, 0xd0000000, 0xc0201e, 0xc, 0x4, + 0x40000000, 0x802001, 0x0, 0x0, 0xc0000000, 0x603dc1, 0xc, 0x0, + 0x90000000, 0x603044, 0xc, 0x0, 0x40000000, 0x80201e, 0xc, 0x0, + 0x0, 0x805c8400, 0x0, 0x0, 0x7f20000, 0x7f80, 0x0, 0x0, 0x1bf20000, + 0x3f00, 0x0, 0x3000000, 0x2a00000, 0x0, 0x7ffe0000, 0xfeffe0df, + 0x1fffffff, 0x40, 0x0, 0x0, 0x66fde000, 0xc3000000, 0x1e0001, + 0x20002064, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe0000000, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1c0000, 0x1c0000, 0xc0000, 0xc0000, 0x0, 0x3fb00000, + 0x200ffe40, 0x0, 0x3800, 0x0, 0x0, 0x0, 0x0, 0x200, 0x0, 0x0, 0x0, + 0xe040187, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9800000, 0x0, + 0x7f400000, 0x9ff81fe5, 0x0, 0x0, 0x0, 0x0, 0xf, 0x17d00000, 0x4, + 0xff800, 0x3, 0xb3c, 0x0, 0x3a340, 0x0, 0xcff000, 0x0, 0x0, 0x0, + 0x0, 0xfff70000, 0x1021fd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xffffffff, 0xf000007f, 0x3000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xffff0000, 0x1ffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x38000, + 0x0, 0x0, 0x0, 0x80000000, 0x0, 0x0, 0x0, 0xffffffff, 0x0, 0xfc00, + 0x0, 0x0, 0x6000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3ff78000, + 0x80000000, 0x0, 0x0, 0x30000, 0x844, 0x60, 0x0, 0x0, 0x0, 0x0, + 0x10, 0x3ffff, 0x0, 0x3fc0, 0x3ff80, 0x0, 0x7, 0x13c80000, 0x0, + 0x0, 0x0, 0x667e00, 0x1008, 0x0, 0x0, 0xc19d0000, 0x2, 0x403000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2120, 0x40000000, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xffff, 0x7f, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x20000000, 0xf06e, 0x87000000, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0xff000000, 0x7f, 0x0, 0x3, 0x6780000, 0x0, + 0x0, 0x7, 0x1fef80, 0x0, 0x0, 0x3, 0x7fc00000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xbf2800, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x78000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf807c3a0, 0xfe7, 0x3c00, 0x0, 0x0, + 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); + +} diff --git a/libphobos/src/std/internal/unicode_norm.d b/libphobos/src/std/internal/unicode_norm.d new file mode 100644 index 0000000..16059cd --- /dev/null +++ b/libphobos/src/std/internal/unicode_norm.d @@ -0,0 +1,548 @@ +module std.internal.unicode_norm; +import std.internal.unicode_tables; + +package(std): + +static if (size_t.sizeof == 8) +{ + //1600 bytes + enum nfcQCTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x20, 0x60], + [0x100, 0x100, 0x1a00], [0x302020202020100, 0x205020202020204, + 0x602020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1000000000000, 0x200000000, 0x5000400030000, 0x8000000070006, + 0xa0009, 0x0, 0xb000000000000, 0xc000000000000, 0xf0000000e000d, + 0x0, 0x1000000000, 0x0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x14001300120000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x160015, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x170000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1800120012, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x10361f8081a9fdf, 0x401000000000003f, 0x80, 0x0, + 0x0, 0x380000, 0x0, 0x0, 0x1000000000000000, 0xff000000, + 0x4000000000000000, 0xb0800000, 0x48000000000000, 0x4e000000, 0x0, + 0x0, 0x4000000000000000, 0x30c00000, 0x4000000000000000, 0x800000, + 0x0, 0x400000, 0x0, 0x600004, 0x4000000000000000, 0x800000, 0x0, + 0x80008400, 0x0, 0x168020010842008, 0x200108420080002, 0x0, + 0x400000000000, 0x0, 0x0, 0x0, 0x0, 0x3ffffe00000000, + 0xffffff0000000000, 0x7, 0x20000000000000, 0x0, 0x0, 0x0, 0x0, + 0x2aaa000000000000, 0x4800000000000000, 0x2a00c80808080a00, 0x3, + 0x0, 0x0, 0x0, 0xc4000000000, 0x0, 0x0, 0x0, 0x60000000000, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x10000000, 0x0, 0x0, 0x6000000, 0x0, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xfffffc657fe53fff, 0xffff3fffffffffff, + 0xffffffffffffffff, 0x3ffffff, 0x5f7ffc00a0000000, 0x7fdb, 0x0, + 0x0, 0x0, 0x0, 0x400000000000000, 0x0, 0x8000000000, 0x0, 0x0, 0x0, + 0x0, 0x1fc0000000, 0xf800000000000000, 0x1, 0x3fffffff, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0]); + //1920 bytes + enum nfdQCTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x20, 0x70], + [0x100, 0x140, 0x2000], [0x504030202020100, 0x207020202020206, + 0x802020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3000200010000, 0x5000600050004, 0x9000800070005, 0xc0005000b000a, + 0x500050005000d, 0x5000500050005, 0xe000500050005, + 0x10000f00050005, 0x14001300120011, 0x5000500050005, + 0x5001500050005, 0x5000500050005, 0x5000500050016, 0x5000500050005, + 0x5000500050005, 0x5000500050005, 0x5000500050005, 0x5000500050005, + 0x5000500050005, 0x5000500050005, 0x5000500050005, 0x5000500050005, + 0x5000500050005, 0x5000500050005, 0x5000500050005, 0x5000500050005, + 0x5000500050005, 0x17001700170017, 0x17001700170017, + 0x17001700170017, 0x17001700170017, 0x17001700170017, + 0x17001700170017, 0x17001700170017, 0x17001700170017, + 0x17001700170017, 0x17001700170017, 0x18001700170017, + 0x5000500050005, 0x5000500050005, 0x5000500050005, 0x5000500050005, + 0x5000500050005, 0x5000500050005, 0x5000500050005, 0x5000500050005, + 0x1a001900170005, 0x5000500050005, 0x5000500050005, + 0x5000500050005, 0x5000500050005, 0x5000500050005, 0x50005001c001b, + 0x5000500050005, 0x5000500050005, 0x5000500050005, 0x5000500050005, + 0x5000500050005, 0x5000500050005, 0x5000500050005, 0x50005001d0005, + 0x5000500050005, 0x5000500050005, 0x5000500050005, 0x5000500050005, + 0x5000500050005, 0x5000500050005, 0x5000500050005, 0x5000500050005, + 0x5000500050005, 0x5001e00170017, 0x5000500050005, 0x5000500050005, + 0x5000500050005, 0x5000500050005, 0x5000500050005, 0x5000500050005, + 0x5000500050005, 0x5000500050005, 0x5000500050005, 0x0, 0x0, 0x0, + 0xbe7effbf3e7effbf, 0x7ef1ff3ffffcffff, 0x7fffff3ffff3f1f8, + 0x1800300000000, 0xff31ffcfdfffe000, 0xfffc0cfffffff, 0x0, 0x0, + 0x0, 0x0, 0x401000000000001b, 0x1fc000001d7e0, 0x187c00, + 0x20000000200708b, 0xc00000708b0000, 0x0, 0x33ffcfcfccf0006, 0x0, + 0x0, 0x0, 0x0, 0x7c00000000, 0x0, 0x0, 0x80005, 0x12020000000000, + 0xff000000, 0x0, 0xb0001800, 0x48000000000000, 0x4e000000, 0x0, + 0x0, 0x0, 0x30001900, 0x100000, 0x1c00, 0x0, 0x100, 0x0, 0xd81, + 0x0, 0x1c00, 0x0, 0x74000000, 0x0, 0x168020010842008, + 0x200108420080002, 0x0, 0x4000000000, 0x0, 0x0, 0x0, + 0x2800000000045540, 0xb, 0x0, 0x0, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffff0bffffff, 0x3ffffffffffffff, + 0xffffffff3f3fffff, 0x3fffffffaaff3f3f, 0x5fdfffffffffffff, + 0x3fdcffffefcfffde, 0x3, 0x0, 0x0, 0x0, 0xc4000000000, 0x0, + 0x40000c000000, 0xe000, 0x5000001210, 0x333e00500000292, + 0xf00000000333, 0x3c0f00000000, 0x60000000000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x10000000, 0x0, 0x36db02a555555000, 0x5555500040100000, + 0x4790000036db02a5, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xfffffffff, 0x0, 0xfffffc657fe53fff, + 0xffff3fffffffffff, 0xffffffffffffffff, 0x3ffffff, + 0x5f7ffc00a0000000, 0x7fdb, 0x0, 0x0, 0x0, 0x0, 0x80014000000, 0x0, + 0xc00000000000, 0x0, 0x0, 0x0, 0x0, 0x1fc0000000, + 0xf800000000000000, 0x1, 0x3fffffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); + //2560 bytes + enum nfkcQCTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x20, 0x70], + [0x100, 0x140, 0x3400], [0x402030202020100, 0x706020202020205, + 0x802020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3000200010000, 0x4000600050004, 0x9000800070004, 0xd000c000b000a, + 0x40004000f000e, 0x4000400040004, 0x10000400040004, + 0x13001200110004, 0x17001600150014, 0x4000400040018, + 0x4001900040004, 0x1d001c001b001a, 0x210020001f001e, + 0x4000400040004, 0x4000400040004, 0x4000400040004, 0x4000400040004, + 0x4000400040004, 0x4000400040004, 0x4000400040004, 0x4000400040004, + 0x4000400040004, 0x4000400040004, 0x4000400040004, 0x4000400040004, + 0x22000400040004, 0x4000400040004, 0x4000400040004, + 0x4000400040004, 0x4000400040004, 0x4000400040004, 0x4000400040004, + 0x4000400040004, 0x4000400040004, 0x4000400040004, 0x4000400040004, + 0x4000400040004, 0x4000400040004, 0x24002300210004, + 0x27002600250021, 0x4000400040004, 0x4000400040004, + 0x4000400040004, 0x4000400040004, 0x4000400290028, 0x4000400040004, + 0x4000400040004, 0x4000400040004, 0x4000400040004, 0x4000400040004, + 0x4000400040004, 0x4000400040004, 0x40004002a0004, + 0x2e002d002c002b, 0x4000400040004, 0x4000400040004, + 0x4000400040004, 0x4000400040004, 0x4000400040004, 0x4002f00040004, + 0x4003100300004, 0x4000400040004, 0x4000400040004, 0x4000400040004, + 0x4000400040004, 0x4000400040004, 0x4000400040004, 0x4000400040004, + 0x4000400040004, 0x4000400040004, 0x4003200210021, 0x4000400040004, + 0x4000400040004, 0x4000400040004, 0x4000400040004, 0x4000400040004, + 0x4000400040004, 0x4000400040004, 0x4000400040004, 0x4000400040004, + 0x0, 0x0, 0x773c850100000000, 0x0, 0x800c000000000000, + 0x8000000000000201, 0x0, 0xe000000001ff0, 0x0, 0x0, + 0x1ff000000000000, 0x1f3f000000, 0x10361f8081a9fdf, + 0x441000000000003f, 0xb0, 0x2370000007f0000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x80, 0x0, 0x0, 0x1e0000000380000, 0x0, 0x0, + 0x1000000000000000, 0xff000000, 0x4000000000000000, 0xb0800000, + 0x48000000000000, 0x4e000000, 0x0, 0x0, 0x4000000000000000, + 0x30c00000, 0x4000000000000000, 0x800000, 0x0, 0x400000, 0x0, + 0x600004, 0x4000000000000000, 0x800000, 0x0, 0x80008400, + 0x8000000000000, 0x0, 0x8000000000000, 0x30000000, 0x1000, + 0x3e8020010842008, 0x200108420080002, 0x0, 0x400000000000, 0x0, + 0x0, 0x1000000000000000, 0x0, 0x3ffffe00000000, 0xffffff0000000000, + 0x7, 0x20000000000000, 0x0, 0x0, 0x0, 0xf7ff700000000000, + 0x10007ffffffbfff, 0xfffffffff8000000, 0x0, 0x0, 0x0, 0xc000000, + 0x0, 0x0, 0x2aaa000000000000, 0xe800000000000000, + 0x6a00e808e808ea03, 0x50d88070008207ff, 0xfff3000080800380, + 0x1001fff7fff, 0x0, 0xfbfbbd573e6ffeef, 0xffffffffffff03e1, 0x200, + 0x0, 0x1b00000000000, 0x0, 0x0, 0x0, 0x60000000000, 0x0, 0x0, 0x0, + 0x0, 0xffffffff00000000, 0xffffffffffffffff, 0x7ffffffffff, 0x1000, + 0x70000000000000, 0x0, 0x10000000, 0x0, 0x3000000000000000, 0x0, + 0x0, 0x0, 0x800000000000, 0x0, 0x0, 0x0, 0x0, 0x80000000, + 0x8000000000000, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x3fffff, 0x740000000000001, 0x0, 0x9e000000, + 0x8000000000000000, 0xfffe000000000000, 0xffffffffffffffff, + 0xfffc7fff, 0x0, 0xffffffff7fffffff, 0x7fffffffffff00ff, + 0xffffffffffffffff, 0x7fffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x0, + 0x1000000000000, 0x0, 0x300000000000000, 0xfffffc657fe53fff, + 0xffff3fffffffffff, 0xffffffffffffffff, 0x3ffffff, + 0x5f7fffffa0f8007f, 0xffffffffffffffdb, 0x3ffffffffffff, + 0xfffffffffff80000, 0x3fffffffffffffff, 0xffffffffffff0000, + 0xfffffffffffcffff, 0x1fff0000000000ff, 0xffff000003ff0000, + 0xffd70f7ffff7ff9f, 0xffffffffffffffff, 0x1fffffffffffffff, + 0xfffffffffffffffe, 0xffffffffffffffff, 0x7fffffffffffffff, + 0x7f7f1cfcfcfc, 0x0, 0x0, 0x400000000000000, 0x0, 0x8000000000, + 0x0, 0x0, 0x0, 0x0, 0x1fc0000000, 0xf800000000000000, 0x1, + 0xffffffffffffffff, 0xffffffffffdfffff, 0xebffde64dfffffff, + 0xffffffffffffffef, 0x7bffffffdfdfe7bf, 0xfffffffffffdfc5f, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffff3fffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffcfff, 0xaf7fe96ffffffef, 0x5ef7f796aa96ea84, + 0xffffbee0ffffbff, 0x0, 0xffff7fffffff07ff, 0xc000000ffff, 0x10000, + 0x0, 0x7ffffffffff0007, 0x301ff, 0x0, 0x0, 0x3fffffff, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0]); + //2656 bytes + enum nfkdQCTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x20, 0x78], + [0x100, 0x160, 0x3500], [0x504030202020100, 0x807020202020206, + 0x902020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3000200010000, 0x7000600050004, 0xa000900080007, 0xe000d000c000b, + 0x700070007000f, 0x7000700070007, 0x10000700070007, + 0x13001200110007, 0x17001600150014, 0x7000700070018, + 0x7001900070007, 0x1d001c001b001a, 0x210020001f001e, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x22000700070007, 0x7000700070007, 0x21002100210021, + 0x21002100210021, 0x21002100210021, 0x21002100210021, + 0x21002100210021, 0x21002100210021, 0x21002100210021, + 0x21002100210021, 0x21002100210021, 0x21002100210021, + 0x23002100210021, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x25002400210007, + 0x28002700260021, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x70007002a0029, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x70007002b0007, + 0x2f002e002d002c, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7003000070007, + 0x7003200310007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7003300210021, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x7000700070007, 0x7000700070007, 0x7000700070007, 0x7000700070007, + 0x0, 0x0, 0x773c850100000000, 0xbe7effbf3e7effbf, + 0xfefdff3ffffcffff, 0xffffff3ffff3f3f9, 0x1800300000000, + 0xff3fffcfdffffff0, 0xfffc0cfffffff, 0x0, 0x1ff000000000000, + 0x1f3f000000, 0x0, 0x441000000000001b, 0x1fc000001d7f0, + 0x2370000007f7c00, 0x20000000200708b, 0xc00000708b0000, 0x0, + 0x33ffcfcfccf0006, 0x0, 0x0, 0x80, 0x0, 0x7c00000000, + 0x1e0000000000000, 0x0, 0x80005, 0x0, 0x0, 0x0, 0x0, + 0x12020000000000, 0xff000000, 0x0, 0xb0001800, 0x48000000000000, + 0x4e000000, 0x0, 0x0, 0x0, 0x30001900, 0x100000, 0x1c00, 0x0, + 0x100, 0x0, 0xd81, 0x0, 0x1c00, 0x0, 0x74000000, 0x8000000000000, + 0x0, 0x8000000000000, 0x30000000, 0x1000, 0x3e8020010842008, + 0x200108420080002, 0x0, 0x4000000000, 0x0, 0x0, 0x1000000000000000, + 0x2800000000045540, 0xb, 0x0, 0x0, 0xf7ff700000000000, + 0x10007ffffffbfff, 0xfffffffff8000000, 0x0, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffff0fffffff, 0x3ffffffffffffff, + 0xffffffff3f3fffff, 0x3fffffffaaff3f3f, 0xffdfffffffffffff, + 0x7fdcffffefcfffdf, 0x50d88070008207ff, 0xfff3000080800380, + 0x1001fff7fff, 0x0, 0xfbfbbd573e6ffeef, 0xffffffffffff03e1, + 0x40000c000200, 0xe000, 0x1b05000001210, 0x333e00500000292, + 0xf00000000333, 0x3c0f00000000, 0x60000000000, 0x0, 0x0, 0x0, 0x0, + 0xffffffff00000000, 0xffffffffffffffff, 0x7ffffffffff, 0x1000, + 0x70000000000000, 0x0, 0x10000000, 0x0, 0x3000000000000000, 0x0, + 0x0, 0x0, 0x800000000000, 0x0, 0x0, 0x0, 0x0, 0x80000000, + 0x8000000000000, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x3fffff, 0x740000000000001, + 0x36db02a555555000, 0x55555000d8100000, 0xc790000036db02a5, + 0xfffe000000000000, 0xffffffffffffffff, 0xfffc7fff, 0x0, + 0xffffffff7fffffff, 0x7fffffffffff00ff, 0xffffffffffffffff, + 0x7fffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x0, 0x1000000000000, 0x0, + 0x300000000000000, 0xffffffffffffffff, 0xffffffffffffffff, + 0xfffffffff, 0x0, 0xfffffc657fe53fff, 0xffff3fffffffffff, + 0xffffffffffffffff, 0x3ffffff, 0x5f7fffffa0f8007f, + 0xffffffffffffffdb, 0x3ffffffffffff, 0xfffffffffff80000, + 0x3fffffffffffffff, 0xffffffffffff0000, 0xfffffffffffcffff, + 0x1fff0000000000ff, 0xffff000003ff0000, 0xffd70f7ffff7ff9f, + 0xffffffffffffffff, 0x1fffffffffffffff, 0xfffffffffffffffe, + 0xffffffffffffffff, 0x7fffffffffffffff, 0x7f7f1cfcfcfc, 0x0, 0x0, + 0x80014000000, 0x0, 0xc00000000000, 0x0, 0x0, 0x0, 0x0, + 0x1fc0000000, 0xf800000000000000, 0x1, 0xffffffffffffffff, + 0xffffffffffdfffff, 0xebffde64dfffffff, 0xffffffffffffffef, + 0x7bffffffdfdfe7bf, 0xfffffffffffdfc5f, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffff3fffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffcfff, + 0xaf7fe96ffffffef, 0x5ef7f796aa96ea84, 0xffffbee0ffffbff, 0x0, + 0xffff7fffffff07ff, 0xc000000ffff, 0x10000, 0x0, 0x7ffffffffff0007, + 0x301ff, 0x0, 0x0, 0x3fffffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); + +} + +static if (size_t.sizeof == 4) +{ + //1600 bytes + enum nfcQCTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x40, 0xc0], + [0x100, 0x100, 0x1a00], [0x2020100, 0x3020202, 0x2020204, + 0x2050202, 0x2020202, 0x6020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10000, 0x0, 0x2, 0x30000, + 0x50004, 0x70006, 0x80000, 0xa0009, 0x0, 0x0, 0x0, 0x0, 0xb0000, + 0x0, 0xc0000, 0xe000d, 0xf0000, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, + 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x120000, + 0x140013, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x160015, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x170000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x120012, 0x18, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x81a9fdf, 0x10361f8, 0x3f, 0x40100000, 0x80, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x380000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x10000000, 0xff000000, 0x0, 0x0, 0x40000000, 0xb0800000, 0x0, 0x0, + 0x480000, 0x4e000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40000000, + 0x30c00000, 0x0, 0x0, 0x40000000, 0x800000, 0x0, 0x0, 0x0, + 0x400000, 0x0, 0x0, 0x0, 0x600004, 0x0, 0x0, 0x40000000, 0x800000, + 0x0, 0x0, 0x0, 0x80008400, 0x0, 0x0, 0x0, 0x10842008, 0x1680200, + 0x20080002, 0x2001084, 0x0, 0x0, 0x0, 0x4000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x3ffffe, 0x0, 0xffffff00, 0x7, 0x0, 0x0, + 0x200000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2aaa0000, + 0x0, 0x48000000, 0x8080a00, 0x2a00c808, 0x3, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xc40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x600, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x10000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6000000, 0x0, 0x0, 0x0, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x7fe53fff, 0xfffffc65, + 0xffffffff, 0xffff3fff, 0xffffffff, 0xffffffff, 0x3ffffff, 0x0, + 0xa0000000, 0x5f7ffc00, 0x7fdb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x4000000, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xc0000000, 0x1f, 0x0, 0xf8000000, 0x1, 0x0, + 0x3fffffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0]); + //1920 bytes + enum nfdQCTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x40, 0xe0], + [0x100, 0x140, 0x2000], [0x2020100, 0x5040302, 0x2020206, + 0x2070202, 0x2020202, 0x8020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10000, 0x30002, 0x50004, 0x50006, + 0x70005, 0x90008, 0xb000a, 0xc0005, 0x5000d, 0x50005, 0x50005, + 0x50005, 0x50005, 0xe0005, 0x50005, 0x10000f, 0x120011, 0x140013, + 0x50005, 0x50005, 0x50005, 0x50015, 0x50005, 0x50005, 0x50016, + 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, + 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, + 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, + 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, + 0x50005, 0x170017, 0x170017, 0x170017, 0x170017, 0x170017, + 0x170017, 0x170017, 0x170017, 0x170017, 0x170017, 0x170017, + 0x170017, 0x170017, 0x170017, 0x170017, 0x170017, 0x170017, + 0x170017, 0x170017, 0x170017, 0x170017, 0x180017, 0x50005, 0x50005, + 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, + 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, + 0x170005, 0x1a0019, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, + 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x1c001b, 0x50005, + 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, + 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, + 0x1d0005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, + 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, + 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x170017, + 0x5001e, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, + 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, + 0x50005, 0x50005, 0x50005, 0x50005, 0x50005, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x3e7effbf, 0xbe7effbf, 0xfffcffff, 0x7ef1ff3f, + 0xfff3f1f8, 0x7fffff3f, 0x0, 0x18003, 0xdfffe000, 0xff31ffcf, + 0xcfffffff, 0xfffc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, + 0x40100000, 0x1d7e0, 0x1fc00, 0x187c00, 0x0, 0x200708b, 0x2000000, + 0x708b0000, 0xc00000, 0x0, 0x0, 0xfccf0006, 0x33ffcfc, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7c, 0x0, 0x0, 0x0, 0x0, + 0x80005, 0x0, 0x0, 0x120200, 0xff000000, 0x0, 0x0, 0x0, 0xb0001800, + 0x0, 0x0, 0x480000, 0x4e000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x30001900, 0x0, 0x100000, 0x0, 0x1c00, 0x0, 0x0, 0x0, 0x100, 0x0, + 0x0, 0x0, 0xd81, 0x0, 0x0, 0x0, 0x1c00, 0x0, 0x0, 0x0, 0x74000000, + 0x0, 0x0, 0x0, 0x10842008, 0x1680200, 0x20080002, 0x2001084, 0x0, + 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x45540, 0x28000000, + 0xb, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xbffffff, 0xffffffff, 0xffffffff, 0x3ffffff, + 0x3f3fffff, 0xffffffff, 0xaaff3f3f, 0x3fffffff, 0xffffffff, + 0x5fdfffff, 0xefcfffde, 0x3fdcffff, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xc40, 0x0, 0x0, 0xc000000, 0x4000, 0xe000, 0x0, + 0x1210, 0x50, 0x292, 0x333e005, 0x333, 0xf000, 0x0, 0x3c0f, 0x0, + 0x600, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x10000000, 0x0, 0x0, 0x0, 0x55555000, 0x36db02a5, 0x40100000, + 0x55555000, 0x36db02a5, 0x47900000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xf, 0x0, 0x0, 0x7fe53fff, 0xfffffc65, 0xffffffff, + 0xffff3fff, 0xffffffff, 0xffffffff, 0x3ffffff, 0x0, 0xa0000000, + 0x5f7ffc00, 0x7fdb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x14000000, 0x800, 0x0, 0x0, 0x0, 0xc000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xc0000000, 0x1f, 0x0, 0xf8000000, 0x1, 0x0, + 0x3fffffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0]); + //2560 bytes + enum nfkcQCTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x40, 0xe0], + [0x100, 0x140, 0x3400], [0x2020100, 0x4020302, 0x2020205, + 0x7060202, 0x2020202, 0x8020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10000, 0x30002, 0x50004, 0x40006, + 0x70004, 0x90008, 0xb000a, 0xd000c, 0xf000e, 0x40004, 0x40004, + 0x40004, 0x40004, 0x100004, 0x110004, 0x130012, 0x150014, 0x170016, + 0x40018, 0x40004, 0x40004, 0x40019, 0x1b001a, 0x1d001c, 0x1f001e, + 0x210020, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, + 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, + 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, + 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x220004, 0x40004, + 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, + 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, + 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, + 0x40004, 0x40004, 0x210004, 0x240023, 0x250021, 0x270026, 0x40004, + 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, + 0x290028, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, + 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, + 0x40004, 0x40004, 0x2a0004, 0x40004, 0x2c002b, 0x2e002d, 0x40004, + 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, + 0x40004, 0x40004, 0x40004, 0x4002f, 0x300004, 0x40031, 0x40004, + 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, + 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, + 0x40004, 0x40004, 0x40004, 0x210021, 0x40032, 0x40004, 0x40004, + 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, + 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, 0x40004, + 0x40004, 0x40004, 0x0, 0x0, 0x0, 0x0, 0x0, 0x773c8501, 0x0, 0x0, + 0x0, 0x800c0000, 0x201, 0x80000000, 0x0, 0x0, 0x1ff0, 0xe0000, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1ff0000, 0x3f000000, 0x1f, 0x81a9fdf, + 0x10361f8, 0x3f, 0x44100000, 0xb0, 0x0, 0x7f0000, 0x2370000, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x380000, 0x1e00000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x10000000, 0xff000000, 0x0, 0x0, 0x40000000, 0xb0800000, 0x0, 0x0, + 0x480000, 0x4e000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40000000, + 0x30c00000, 0x0, 0x0, 0x40000000, 0x800000, 0x0, 0x0, 0x0, + 0x400000, 0x0, 0x0, 0x0, 0x600004, 0x0, 0x0, 0x40000000, 0x800000, + 0x0, 0x0, 0x0, 0x80008400, 0x0, 0x0, 0x80000, 0x0, 0x0, 0x0, + 0x80000, 0x30000000, 0x0, 0x1000, 0x0, 0x10842008, 0x3e80200, + 0x20080002, 0x2001084, 0x0, 0x0, 0x0, 0x4000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x10000000, 0x0, 0x0, 0x0, 0x3ffffe, 0x0, 0xffffff00, 0x7, + 0x0, 0x0, 0x200000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf7ff7000, + 0xffffbfff, 0x10007ff, 0xf8000000, 0xffffffff, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xc000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2aaa0000, 0x0, + 0xe8000000, 0xe808ea03, 0x6a00e808, 0x8207ff, 0x50d88070, + 0x80800380, 0xfff30000, 0x1fff7fff, 0x100, 0x0, 0x0, 0x3e6ffeef, + 0xfbfbbd57, 0xffff03e1, 0xffffffff, 0x200, 0x0, 0x0, 0x0, 0x0, + 0x1b000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x600, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x7ff, 0x1000, 0x0, 0x0, 0x700000, 0x0, 0x0, + 0x10000000, 0x0, 0x0, 0x0, 0x0, 0x30000000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x8000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x80000000, 0x0, 0x0, 0x80000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x3fffff, 0x0, 0x1, 0x7400000, + 0x0, 0x0, 0x9e000000, 0x0, 0x0, 0x80000000, 0x0, 0xfffe0000, + 0xffffffff, 0xffffffff, 0xfffc7fff, 0x0, 0x0, 0x0, 0x7fffffff, + 0xffffffff, 0xffff00ff, 0x7fffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x7fffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x0, + 0x0, 0x0, 0x10000, 0x0, 0x0, 0x0, 0x3000000, 0x7fe53fff, + 0xfffffc65, 0xffffffff, 0xffff3fff, 0xffffffff, 0xffffffff, + 0x3ffffff, 0x0, 0xa0f8007f, 0x5f7fffff, 0xffffffdb, 0xffffffff, + 0xffffffff, 0x3ffff, 0xfff80000, 0xffffffff, 0xffffffff, + 0x3fffffff, 0xffff0000, 0xffffffff, 0xfffcffff, 0xffffffff, 0xff, + 0x1fff0000, 0x3ff0000, 0xffff0000, 0xfff7ff9f, 0xffd70f7f, + 0xffffffff, 0xffffffff, 0xffffffff, 0x1fffffff, 0xfffffffe, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x7fffffff, + 0x1cfcfcfc, 0x7f7f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4000000, 0x0, 0x0, + 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0000000, + 0x1f, 0x0, 0xf8000000, 0x1, 0x0, 0xffffffff, 0xffffffff, + 0xffdfffff, 0xffffffff, 0xdfffffff, 0xebffde64, 0xffffffef, + 0xffffffff, 0xdfdfe7bf, 0x7bffffff, 0xfffdfc5f, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffff3f, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffcfff, 0xffffffff, + 0xffffffef, 0xaf7fe96, 0xaa96ea84, 0x5ef7f796, 0xffffbff, + 0xffffbee, 0x0, 0x0, 0xffff07ff, 0xffff7fff, 0xffff, 0xc00, + 0x10000, 0x0, 0x0, 0x0, 0xffff0007, 0x7ffffff, 0x301ff, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x3fffffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); + //2656 bytes + enum nfkdQCTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x40, 0xf0], + [0x100, 0x160, 0x3500], [0x2020100, 0x5040302, 0x2020206, + 0x8070202, 0x2020202, 0x9020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10000, 0x30002, 0x50004, 0x70006, + 0x80007, 0xa0009, 0xc000b, 0xe000d, 0x7000f, 0x70007, 0x70007, + 0x70007, 0x70007, 0x100007, 0x110007, 0x130012, 0x150014, 0x170016, + 0x70018, 0x70007, 0x70007, 0x70019, 0x1b001a, 0x1d001c, 0x1f001e, + 0x210020, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x220007, 0x70007, + 0x70007, 0x210021, 0x210021, 0x210021, 0x210021, 0x210021, + 0x210021, 0x210021, 0x210021, 0x210021, 0x210021, 0x210021, + 0x210021, 0x210021, 0x210021, 0x210021, 0x210021, 0x210021, + 0x210021, 0x210021, 0x210021, 0x210021, 0x230021, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x210007, 0x250024, 0x260021, 0x280027, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x2a0029, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x2b0007, 0x70007, 0x2d002c, 0x2f002e, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70030, 0x310007, 0x70032, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x210021, 0x70033, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, + 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x70007, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x773c8501, 0x3e7effbf, 0xbe7effbf, 0xfffcffff, + 0xfefdff3f, 0xfff3f3f9, 0xffffff3f, 0x0, 0x18003, 0xdffffff0, + 0xff3fffcf, 0xcfffffff, 0xfffc0, 0x0, 0x0, 0x0, 0x1ff0000, + 0x3f000000, 0x1f, 0x0, 0x0, 0x1b, 0x44100000, 0x1d7f0, 0x1fc00, + 0x7f7c00, 0x2370000, 0x200708b, 0x2000000, 0x708b0000, 0xc00000, + 0x0, 0x0, 0xfccf0006, 0x33ffcfc, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, + 0x0, 0x0, 0x0, 0x7c, 0x0, 0x1e00000, 0x0, 0x0, 0x80005, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x120200, 0xff000000, 0x0, + 0x0, 0x0, 0xb0001800, 0x0, 0x0, 0x480000, 0x4e000000, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x30001900, 0x0, 0x100000, 0x0, 0x1c00, + 0x0, 0x0, 0x0, 0x100, 0x0, 0x0, 0x0, 0xd81, 0x0, 0x0, 0x0, 0x1c00, + 0x0, 0x0, 0x0, 0x74000000, 0x0, 0x0, 0x80000, 0x0, 0x0, 0x0, + 0x80000, 0x30000000, 0x0, 0x1000, 0x0, 0x10842008, 0x3e80200, + 0x20080002, 0x2001084, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x10000000, 0x45540, 0x28000000, 0xb, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xf7ff7000, 0xffffbfff, 0x10007ff, 0xf8000000, 0xffffffff, + 0x0, 0x0, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xfffffff, 0xffffffff, 0xffffffff, 0x3ffffff, 0x3f3fffff, + 0xffffffff, 0xaaff3f3f, 0x3fffffff, 0xffffffff, 0xffdfffff, + 0xefcfffdf, 0x7fdcffff, 0x8207ff, 0x50d88070, 0x80800380, + 0xfff30000, 0x1fff7fff, 0x100, 0x0, 0x0, 0x3e6ffeef, 0xfbfbbd57, + 0xffff03e1, 0xffffffff, 0xc000200, 0x4000, 0xe000, 0x0, 0x1210, + 0x1b050, 0x292, 0x333e005, 0x333, 0xf000, 0x0, 0x3c0f, 0x0, 0x600, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x7ff, 0x1000, 0x0, 0x0, + 0x700000, 0x0, 0x0, 0x10000000, 0x0, 0x0, 0x0, 0x0, 0x30000000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x80000000, 0x0, 0x0, 0x80000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x3fffff, 0x0, 0x1, 0x7400000, 0x55555000, 0x36db02a5, 0xd8100000, + 0x55555000, 0x36db02a5, 0xc7900000, 0x0, 0xfffe0000, 0xffffffff, + 0xffffffff, 0xfffc7fff, 0x0, 0x0, 0x0, 0x7fffffff, 0xffffffff, + 0xffff00ff, 0x7fffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x7fffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x0, 0x0, 0x0, + 0x10000, 0x0, 0x0, 0x0, 0x3000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xf, 0x0, 0x0, 0x7fe53fff, + 0xfffffc65, 0xffffffff, 0xffff3fff, 0xffffffff, 0xffffffff, + 0x3ffffff, 0x0, 0xa0f8007f, 0x5f7fffff, 0xffffffdb, 0xffffffff, + 0xffffffff, 0x3ffff, 0xfff80000, 0xffffffff, 0xffffffff, + 0x3fffffff, 0xffff0000, 0xffffffff, 0xfffcffff, 0xffffffff, 0xff, + 0x1fff0000, 0x3ff0000, 0xffff0000, 0xfff7ff9f, 0xffd70f7f, + 0xffffffff, 0xffffffff, 0xffffffff, 0x1fffffff, 0xfffffffe, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x7fffffff, + 0x1cfcfcfc, 0x7f7f, 0x0, 0x0, 0x0, 0x0, 0x14000000, 0x800, 0x0, + 0x0, 0x0, 0xc000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xc0000000, 0x1f, 0x0, 0xf8000000, 0x1, 0x0, 0xffffffff, + 0xffffffff, 0xffdfffff, 0xffffffff, 0xdfffffff, 0xebffde64, + 0xffffffef, 0xffffffff, 0xdfdfe7bf, 0x7bffffff, 0xfffdfc5f, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffff3f, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffcfff, + 0xffffffff, 0xffffffef, 0xaf7fe96, 0xaa96ea84, 0x5ef7f796, + 0xffffbff, 0xffffbee, 0x0, 0x0, 0xffff07ff, 0xffff7fff, 0xffff, + 0xc00, 0x10000, 0x0, 0x0, 0x0, 0xffff0007, 0x7ffffff, 0x301ff, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x3fffffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); + +} diff --git a/libphobos/src/std/internal/unicode_tables.d b/libphobos/src/std/internal/unicode_tables.d new file mode 100644 index 0000000..00b94bd --- /dev/null +++ b/libphobos/src/std/internal/unicode_tables.d @@ -0,0 +1,11081 @@ +//Written in the D programming language +/** + * License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + * + * Authors: Dmitry Olshansky + * + */ +//Automatically generated from Unicode Character Database files + +//dfmt off +module std.internal.unicode_tables; +@safe pure nothrow @nogc package(std): + + +struct SimpleCaseEntry +{ + uint ch; + ubyte n, bucket;// n - number in bucket + +pure nothrow @nogc: + + @property ubyte size() const + { + return bucket & 0x3F; + } + @property auto isLower() const + { + return bucket & 0x40; + } + @property auto isUpper() const + { + return bucket & 0x80; + } +} + +struct FullCaseEntry +{ + dchar[3] seq; + ubyte n, size;// n number in batch, size - size of batch + ubyte entry_len; + + @property auto value() const @trusted pure nothrow @nogc return + { + return seq[0 .. entry_len]; + } +} + +struct CompEntry +{ + dchar rhs, composed; +} + +struct UnicodeProperty +{ + string name; + ubyte[] compressed; +} + +struct TrieEntry(T...) +{ + size_t[] offsets; + size_t[] sizes; + size_t[] data; +} + +@property immutable(SimpleCaseEntry[]) simpleCaseTable() +{ +alias SCE = SimpleCaseEntry; +static immutable SCE[] t = [ +SCE(0x2c00, 0, 0x82), +SCE(0x2c30, 1, 0x42),SCE(0x24c3, 0, 0x82),SCE(0x24dd, 1, 0x42),SCE(0x2c01, 0, 0x82), +SCE(0x2c31, 1, 0x42),SCE(0x2c1d, 0, 0x82),SCE(0x2c4d, 1, 0x42),SCE(0x2c02, 0, 0x82), +SCE(0x2c32, 1, 0x42),SCE(0x2c03, 0, 0x82),SCE(0x2c33, 1, 0x42),SCE(0x2c04, 0, 0x82), +SCE(0x2c34, 1, 0x42),SCE(0x2c05, 0, 0x82),SCE(0x2c35, 1, 0x42),SCE(0x2c06, 0, 0x82), +SCE(0x2c36, 1, 0x42),SCE(0x10400, 0, 0x82),SCE(0x10428, 1, 0x42),SCE(0x2cc2, 0, 0x82), +SCE(0x2cc3, 1, 0x42),SCE(0x2c07, 0, 0x82),SCE(0x2c37, 1, 0x42),SCE(0x2c08, 0, 0x82), +SCE(0x2c38, 1, 0x42),SCE(0x2c09, 0, 0x82),SCE(0x2c39, 1, 0x42),SCE(0x2c0a, 0, 0x82), +SCE(0x2c3a, 1, 0x42),SCE(0xa68c, 0, 0x82),SCE(0xa68d, 1, 0x42),SCE(0x0041, 0, 0x82), +SCE(0x0061, 1, 0x42),SCE(0x0042, 0, 0x82),SCE(0x0062, 1, 0x42),SCE(0x0043, 0, 0x82), +SCE(0x0063, 1, 0x42),SCE(0x0044, 0, 0x82),SCE(0x0064, 1, 0x42),SCE(0x0045, 0, 0x82), +SCE(0x0065, 1, 0x42),SCE(0x0046, 0, 0x82),SCE(0x0066, 1, 0x42),SCE(0x0047, 0, 0x82), +SCE(0x0067, 1, 0x42),SCE(0x0048, 0, 0x82),SCE(0x0068, 1, 0x42),SCE(0x0049, 0, 0x82), +SCE(0x0069, 1, 0x42),SCE(0x004a, 0, 0x82),SCE(0x006a, 1, 0x42),SCE(0x004b, 0, 0x83), +SCE(0x006b, 1, 0x43),SCE(0x212a, 2, 0x83),SCE(0x004c, 0, 0x82),SCE(0x006c, 1, 0x42), +SCE(0x004d, 0, 0x82),SCE(0x006d, 1, 0x42),SCE(0x004e, 0, 0x82),SCE(0x006e, 1, 0x42), +SCE(0x004f, 0, 0x82),SCE(0x006f, 1, 0x42),SCE(0x0050, 0, 0x82),SCE(0x0070, 1, 0x42), +SCE(0x0051, 0, 0x82),SCE(0x0071, 1, 0x42),SCE(0x0052, 0, 0x82),SCE(0x0072, 1, 0x42), +SCE(0x0053, 0, 0x83),SCE(0x0073, 1, 0x43),SCE(0x017f, 2, 0x43),SCE(0x0054, 0, 0x82), +SCE(0x0074, 1, 0x42),SCE(0x0055, 0, 0x82),SCE(0x0075, 1, 0x42),SCE(0x0056, 0, 0x82), +SCE(0x0076, 1, 0x42),SCE(0x0057, 0, 0x82),SCE(0x0077, 1, 0x42),SCE(0x0058, 0, 0x82), +SCE(0x0078, 1, 0x42),SCE(0x0059, 0, 0x82),SCE(0x0079, 1, 0x42),SCE(0x005a, 0, 0x82), +SCE(0x007a, 1, 0x42),SCE(0x2c0f, 0, 0x82),SCE(0x2c3f, 1, 0x42),SCE(0x2c10, 0, 0x82), +SCE(0x2c40, 1, 0x42),SCE(0x10402, 0, 0x82),SCE(0x1042a, 1, 0x42),SCE(0x2cc4, 0, 0x82), +SCE(0x2cc5, 1, 0x42),SCE(0x2166, 0, 0x82),SCE(0x2176, 1, 0x42),SCE(0x2c11, 0, 0x82), +SCE(0x2c41, 1, 0x42),SCE(0x2c12, 0, 0x82),SCE(0x2c42, 1, 0x42),SCE(0x2168, 0, 0x82), +SCE(0x2178, 1, 0x42),SCE(0x2c13, 0, 0x82),SCE(0x2c43, 1, 0x42),SCE(0xa682, 0, 0x82), +SCE(0xa683, 1, 0x42),SCE(0x2c14, 0, 0x82),SCE(0x2c44, 1, 0x42),SCE(0x216a, 0, 0x82), +SCE(0x217a, 1, 0x42),SCE(0x24c7, 0, 0x82),SCE(0x24e1, 1, 0x42),SCE(0x2c15, 0, 0x82), +SCE(0x2c45, 1, 0x42),SCE(0x10403, 0, 0x82),SCE(0x1042b, 1, 0x42),SCE(0x2c16, 0, 0x82), +SCE(0x2c46, 1, 0x42),SCE(0x216c, 0, 0x82),SCE(0x217c, 1, 0x42),SCE(0x2c17, 0, 0x82), +SCE(0x2c47, 1, 0x42),SCE(0xff38, 0, 0x82),SCE(0xff58, 1, 0x42),SCE(0x2c18, 0, 0x82), +SCE(0x2c48, 1, 0x42),SCE(0x216e, 0, 0x82),SCE(0x217e, 1, 0x42),SCE(0x2c19, 0, 0x82), +SCE(0x2c49, 1, 0x42),SCE(0x2c1a, 0, 0x82),SCE(0x2c4a, 1, 0x42),SCE(0x2c1e, 0, 0x82), +SCE(0x2c4e, 1, 0x42),SCE(0x10a0, 0, 0x82),SCE(0x2d00, 1, 0x42),SCE(0x10a1, 0, 0x82), +SCE(0x2d01, 1, 0x42),SCE(0x10a2, 0, 0x82),SCE(0x2d02, 1, 0x42),SCE(0x10a3, 0, 0x82), +SCE(0x2d03, 1, 0x42),SCE(0x10a4, 0, 0x82),SCE(0x2d04, 1, 0x42),SCE(0x10a5, 0, 0x82), +SCE(0x2d05, 1, 0x42),SCE(0x10a6, 0, 0x82),SCE(0x2d06, 1, 0x42),SCE(0x10a7, 0, 0x82), +SCE(0x2d07, 1, 0x42),SCE(0x10a8, 0, 0x82),SCE(0x2d08, 1, 0x42),SCE(0x10a9, 0, 0x82), +SCE(0x2d09, 1, 0x42),SCE(0x10aa, 0, 0x82),SCE(0x2d0a, 1, 0x42),SCE(0x10ab, 0, 0x82), +SCE(0x2d0b, 1, 0x42),SCE(0x10ac, 0, 0x82),SCE(0x2d0c, 1, 0x42),SCE(0x10ad, 0, 0x82), +SCE(0x2d0d, 1, 0x42),SCE(0x10ae, 0, 0x82),SCE(0x2d0e, 1, 0x42),SCE(0x10af, 0, 0x82), +SCE(0x2d0f, 1, 0x42),SCE(0x10b0, 0, 0x82),SCE(0x2d10, 1, 0x42),SCE(0x10b1, 0, 0x82), +SCE(0x2d11, 1, 0x42),SCE(0x10b2, 0, 0x82),SCE(0x2d12, 1, 0x42),SCE(0x10b3, 0, 0x82), +SCE(0x2d13, 1, 0x42),SCE(0x10b4, 0, 0x82),SCE(0x2d14, 1, 0x42),SCE(0x10b5, 0, 0x82), +SCE(0x2d15, 1, 0x42),SCE(0x10b6, 0, 0x82),SCE(0x2d16, 1, 0x42),SCE(0x10b7, 0, 0x82), +SCE(0x2d17, 1, 0x42),SCE(0x10b8, 0, 0x82),SCE(0x2d18, 1, 0x42),SCE(0x10b9, 0, 0x82), +SCE(0x2d19, 1, 0x42),SCE(0x10ba, 0, 0x82),SCE(0x2d1a, 1, 0x42),SCE(0x10bb, 0, 0x82), +SCE(0x2d1b, 1, 0x42),SCE(0x10bc, 0, 0x82),SCE(0x2d1c, 1, 0x42),SCE(0x10bd, 0, 0x82), +SCE(0x2d1d, 1, 0x42),SCE(0x10be, 0, 0x82),SCE(0x2d1e, 1, 0x42),SCE(0x10bf, 0, 0x82), +SCE(0x2d1f, 1, 0x42),SCE(0x00c0, 0, 0x82),SCE(0x00e0, 1, 0x42),SCE(0x00c1, 0, 0x82), +SCE(0x00e1, 1, 0x42),SCE(0x10c2, 0, 0x82),SCE(0x2d22, 1, 0x42),SCE(0x00c3, 0, 0x82), +SCE(0x00e3, 1, 0x42),SCE(0x10c4, 0, 0x82),SCE(0x2d24, 1, 0x42),SCE(0x00c5, 0, 0x83), +SCE(0x00e5, 1, 0x43),SCE(0x212b, 2, 0x83),SCE(0x00c6, 0, 0x82),SCE(0x00e6, 1, 0x42), +SCE(0x00c7, 0, 0x82),SCE(0x00e7, 1, 0x42),SCE(0x00c8, 0, 0x82),SCE(0x00e8, 1, 0x42), +SCE(0x00c9, 0, 0x82),SCE(0x00e9, 1, 0x42),SCE(0x00ca, 0, 0x82),SCE(0x00ea, 1, 0x42), +SCE(0x00cb, 0, 0x82),SCE(0x00eb, 1, 0x42),SCE(0x00cc, 0, 0x82),SCE(0x00ec, 1, 0x42), +SCE(0x00cd, 0, 0x82),SCE(0x00ed, 1, 0x42),SCE(0x00ce, 0, 0x82),SCE(0x00ee, 1, 0x42), +SCE(0x00cf, 0, 0x82),SCE(0x00ef, 1, 0x42),SCE(0x00d0, 0, 0x82),SCE(0x00f0, 1, 0x42), +SCE(0x00d1, 0, 0x82),SCE(0x00f1, 1, 0x42),SCE(0x00d2, 0, 0x82),SCE(0x00f2, 1, 0x42), +SCE(0x00d3, 0, 0x82),SCE(0x00f3, 1, 0x42),SCE(0x00d4, 0, 0x82),SCE(0x00f4, 1, 0x42), +SCE(0x00d5, 0, 0x82),SCE(0x00f5, 1, 0x42),SCE(0x00d6, 0, 0x82),SCE(0x00f6, 1, 0x42), +SCE(0x00d8, 0, 0x82),SCE(0x00f8, 1, 0x42),SCE(0x00d9, 0, 0x82),SCE(0x00f9, 1, 0x42), +SCE(0x00da, 0, 0x82),SCE(0x00fa, 1, 0x42),SCE(0x00db, 0, 0x82),SCE(0x00fb, 1, 0x42), +SCE(0x00dc, 0, 0x82),SCE(0x00fc, 1, 0x42),SCE(0x00dd, 0, 0x82),SCE(0x00fd, 1, 0x42), +SCE(0x00de, 0, 0x82),SCE(0x00fe, 1, 0x42),SCE(0x2c25, 0, 0x82),SCE(0x2c55, 1, 0x42), +SCE(0x2c26, 0, 0x82),SCE(0x2c56, 1, 0x42),SCE(0x2c27, 0, 0x82),SCE(0x2c57, 1, 0x42), +SCE(0x2c28, 0, 0x82),SCE(0x2c58, 1, 0x42),SCE(0x1040f, 0, 0x82),SCE(0x10437, 1, 0x42), +SCE(0x24cb, 0, 0x82),SCE(0x24e5, 1, 0x42),SCE(0x2c29, 0, 0x82),SCE(0x2c59, 1, 0x42), +SCE(0x10407, 0, 0x82),SCE(0x1042f, 1, 0x42),SCE(0x2c2a, 0, 0x82),SCE(0x2c5a, 1, 0x42), +SCE(0x0100, 0, 0x82),SCE(0x0101, 1, 0x42),SCE(0x0102, 0, 0x82),SCE(0x0103, 1, 0x42), +SCE(0x2c2b, 0, 0x82),SCE(0x2c5b, 1, 0x42),SCE(0x0104, 0, 0x82),SCE(0x0105, 1, 0x42), +SCE(0x0106, 0, 0x82),SCE(0x0107, 1, 0x42),SCE(0x0108, 0, 0x82),SCE(0x0109, 1, 0x42), +SCE(0x2c2c, 0, 0x82),SCE(0x2c5c, 1, 0x42),SCE(0x010a, 0, 0x82),SCE(0x010b, 1, 0x42), +SCE(0x010c, 0, 0x82),SCE(0x010d, 1, 0x42),SCE(0x010e, 0, 0x82),SCE(0x010f, 1, 0x42), +SCE(0x2c2d, 0, 0x82),SCE(0x2c5d, 1, 0x42),SCE(0x0110, 0, 0x82),SCE(0x0111, 1, 0x42), +SCE(0x0112, 0, 0x82),SCE(0x0113, 1, 0x42),SCE(0x0114, 0, 0x82),SCE(0x0115, 1, 0x42), +SCE(0x2c2e, 0, 0x82),SCE(0x2c5e, 1, 0x42),SCE(0x0116, 0, 0x82),SCE(0x0117, 1, 0x42), +SCE(0x0118, 0, 0x82),SCE(0x0119, 1, 0x42),SCE(0x011a, 0, 0x82),SCE(0x011b, 1, 0x42), +SCE(0x011c, 0, 0x82),SCE(0x011d, 1, 0x42),SCE(0x011e, 0, 0x82),SCE(0x011f, 1, 0x42), +SCE(0x0120, 0, 0x82),SCE(0x0121, 1, 0x42),SCE(0x0122, 0, 0x82),SCE(0x0123, 1, 0x42), +SCE(0x0124, 0, 0x82),SCE(0x0125, 1, 0x42),SCE(0x0126, 0, 0x82),SCE(0x0127, 1, 0x42), +SCE(0x0128, 0, 0x82),SCE(0x0129, 1, 0x42),SCE(0x012a, 0, 0x82),SCE(0x012b, 1, 0x42), +SCE(0x00c5, 0, 0x83),SCE(0x00e5, 1, 0x43),SCE(0x212b, 2, 0x83),SCE(0x012c, 0, 0x82), +SCE(0x012d, 1, 0x42),SCE(0x012e, 0, 0x82),SCE(0x012f, 1, 0x42),SCE(0x0132, 0, 0x82), +SCE(0x0133, 1, 0x42),SCE(0x0134, 0, 0x82),SCE(0x0135, 1, 0x42),SCE(0x0136, 0, 0x82), +SCE(0x0137, 1, 0x42),SCE(0x0139, 0, 0x82),SCE(0x013a, 1, 0x42),SCE(0x013b, 0, 0x82), +SCE(0x013c, 1, 0x42),SCE(0x2cde, 0, 0x82),SCE(0x2cdf, 1, 0x42),SCE(0x013d, 0, 0x82), +SCE(0x013e, 1, 0x42),SCE(0x013f, 0, 0x82),SCE(0x0140, 1, 0x42),SCE(0x0141, 0, 0x82), +SCE(0x0142, 1, 0x42),SCE(0x0143, 0, 0x82),SCE(0x0144, 1, 0x42),SCE(0x0145, 0, 0x82), +SCE(0x0146, 1, 0x42),SCE(0x0147, 0, 0x82),SCE(0x0148, 1, 0x42),SCE(0x014a, 0, 0x82), +SCE(0x014b, 1, 0x42),SCE(0x014c, 0, 0x82),SCE(0x014d, 1, 0x42),SCE(0x014e, 0, 0x82), +SCE(0x014f, 1, 0x42),SCE(0x0150, 0, 0x82),SCE(0x0151, 1, 0x42),SCE(0x0152, 0, 0x82), +SCE(0x0153, 1, 0x42),SCE(0x0154, 0, 0x82),SCE(0x0155, 1, 0x42),SCE(0x0156, 0, 0x82), +SCE(0x0157, 1, 0x42),SCE(0x0158, 0, 0x82),SCE(0x0159, 1, 0x42),SCE(0x015a, 0, 0x82), +SCE(0x015b, 1, 0x42),SCE(0x015c, 0, 0x82),SCE(0x015d, 1, 0x42),SCE(0x015e, 0, 0x82), +SCE(0x015f, 1, 0x42),SCE(0x0160, 0, 0x82),SCE(0x0161, 1, 0x42),SCE(0x2161, 0, 0x82), +SCE(0x2171, 1, 0x42),SCE(0x0162, 0, 0x82),SCE(0x0163, 1, 0x42),SCE(0x2163, 0, 0x82), +SCE(0x2173, 1, 0x42),SCE(0x0164, 0, 0x82),SCE(0x0165, 1, 0x42),SCE(0x2165, 0, 0x82), +SCE(0x2175, 1, 0x42),SCE(0x0166, 0, 0x82),SCE(0x0167, 1, 0x42),SCE(0x2167, 0, 0x82), +SCE(0x2177, 1, 0x42),SCE(0x0168, 0, 0x82),SCE(0x0169, 1, 0x42),SCE(0x2169, 0, 0x82), +SCE(0x2179, 1, 0x42),SCE(0x016a, 0, 0x82),SCE(0x016b, 1, 0x42),SCE(0x216b, 0, 0x82), +SCE(0x217b, 1, 0x42),SCE(0x016c, 0, 0x82),SCE(0x016d, 1, 0x42),SCE(0x216d, 0, 0x82), +SCE(0x217d, 1, 0x42),SCE(0x016e, 0, 0x82),SCE(0x016f, 1, 0x42),SCE(0x216f, 0, 0x82), +SCE(0x217f, 1, 0x42),SCE(0x0170, 0, 0x82),SCE(0x0171, 1, 0x42),SCE(0x2ccc, 0, 0x82), +SCE(0x2ccd, 1, 0x42),SCE(0x0172, 0, 0x82),SCE(0x0173, 1, 0x42),SCE(0x0174, 0, 0x82), +SCE(0x0175, 1, 0x42),SCE(0x0176, 0, 0x82),SCE(0x0177, 1, 0x42),SCE(0x00ff, 0, 0x42), +SCE(0x0178, 1, 0x82),SCE(0x0179, 0, 0x82),SCE(0x017a, 1, 0x42),SCE(0x017b, 0, 0x82), +SCE(0x017c, 1, 0x42),SCE(0x017d, 0, 0x82),SCE(0x017e, 1, 0x42),SCE(0x0053, 0, 0x83), +SCE(0x0073, 1, 0x43),SCE(0x017f, 2, 0x43),SCE(0x0181, 0, 0x82),SCE(0x0253, 1, 0x42), +SCE(0x0182, 0, 0x82),SCE(0x0183, 1, 0x42),SCE(0x2183, 0, 0x82),SCE(0x2184, 1, 0x42), +SCE(0x0184, 0, 0x82),SCE(0x0185, 1, 0x42),SCE(0x0186, 0, 0x82),SCE(0x0254, 1, 0x42), +SCE(0x0187, 0, 0x82),SCE(0x0188, 1, 0x42),SCE(0x0189, 0, 0x82),SCE(0x0256, 1, 0x42), +SCE(0x018a, 0, 0x82),SCE(0x0257, 1, 0x42),SCE(0x018b, 0, 0x82),SCE(0x018c, 1, 0x42), +SCE(0x018e, 0, 0x82),SCE(0x01dd, 1, 0x42),SCE(0x018f, 0, 0x82),SCE(0x0259, 1, 0x42), +SCE(0x0190, 0, 0x82),SCE(0x025b, 1, 0x42),SCE(0x0191, 0, 0x82),SCE(0x0192, 1, 0x42), +SCE(0x0193, 0, 0x82),SCE(0x0260, 1, 0x42),SCE(0x0194, 0, 0x82),SCE(0x0263, 1, 0x42), +SCE(0x0196, 0, 0x82),SCE(0x0269, 1, 0x42),SCE(0x0197, 0, 0x82),SCE(0x0268, 1, 0x42), +SCE(0x0198, 0, 0x82),SCE(0x0199, 1, 0x42),SCE(0x019c, 0, 0x82),SCE(0x026f, 1, 0x42), +SCE(0x019d, 0, 0x82),SCE(0x0272, 1, 0x42),SCE(0x019f, 0, 0x82),SCE(0x0275, 1, 0x42), +SCE(0x01a0, 0, 0x82),SCE(0x01a1, 1, 0x42),SCE(0x01a2, 0, 0x82),SCE(0x01a3, 1, 0x42), +SCE(0x01a4, 0, 0x82),SCE(0x01a5, 1, 0x42),SCE(0x01a6, 0, 0x82),SCE(0x0280, 1, 0x42), +SCE(0x01a7, 0, 0x82),SCE(0x01a8, 1, 0x42),SCE(0x01a9, 0, 0x82),SCE(0x0283, 1, 0x42), +SCE(0x01ac, 0, 0x82),SCE(0x01ad, 1, 0x42),SCE(0x01ae, 0, 0x82),SCE(0x0288, 1, 0x42), +SCE(0x01af, 0, 0x82),SCE(0x01b0, 1, 0x42),SCE(0x01b1, 0, 0x82),SCE(0x028a, 1, 0x42), +SCE(0x01b2, 0, 0x82),SCE(0x028b, 1, 0x42),SCE(0x01b3, 0, 0x82),SCE(0x01b4, 1, 0x42), +SCE(0x01b5, 0, 0x82),SCE(0x01b6, 1, 0x42),SCE(0x01b7, 0, 0x82),SCE(0x0292, 1, 0x42), +SCE(0x01b8, 0, 0x82),SCE(0x01b9, 1, 0x42),SCE(0x01bc, 0, 0x82),SCE(0x01bd, 1, 0x42), +SCE(0x01c4, 0, 0x83),SCE(0x01c5, 1, 0x3),SCE(0x01c6, 2, 0x43),SCE(0x01c4, 0, 0x83), +SCE(0x01c5, 1, 0x3),SCE(0x01c6, 2, 0x43),SCE(0x01c7, 0, 0x83),SCE(0x01c8, 1, 0x3), +SCE(0x01c9, 2, 0x43),SCE(0x01c7, 0, 0x83),SCE(0x01c8, 1, 0x3),SCE(0x01c9, 2, 0x43), +SCE(0x01ca, 0, 0x83),SCE(0x01cb, 1, 0x3),SCE(0x01cc, 2, 0x43),SCE(0x01ca, 0, 0x83), +SCE(0x01cb, 1, 0x3),SCE(0x01cc, 2, 0x43),SCE(0x01cd, 0, 0x82),SCE(0x01ce, 1, 0x42), +SCE(0x01cf, 0, 0x82),SCE(0x01d0, 1, 0x42),SCE(0x01d1, 0, 0x82),SCE(0x01d2, 1, 0x42), +SCE(0x01d3, 0, 0x82),SCE(0x01d4, 1, 0x42),SCE(0x01d5, 0, 0x82),SCE(0x01d6, 1, 0x42), +SCE(0x01d7, 0, 0x82),SCE(0x01d8, 1, 0x42),SCE(0x01d9, 0, 0x82),SCE(0x01da, 1, 0x42), +SCE(0x01db, 0, 0x82),SCE(0x01dc, 1, 0x42),SCE(0x01de, 0, 0x82),SCE(0x01df, 1, 0x42), +SCE(0xff36, 0, 0x82),SCE(0xff56, 1, 0x42),SCE(0x01e0, 0, 0x82),SCE(0x01e1, 1, 0x42), +SCE(0x01e2, 0, 0x82),SCE(0x01e3, 1, 0x42),SCE(0x01e4, 0, 0x82),SCE(0x01e5, 1, 0x42), +SCE(0x01e6, 0, 0x82),SCE(0x01e7, 1, 0x42),SCE(0x01e8, 0, 0x82),SCE(0x01e9, 1, 0x42), +SCE(0x01ea, 0, 0x82),SCE(0x01eb, 1, 0x42),SCE(0x01ec, 0, 0x82),SCE(0x01ed, 1, 0x42), +SCE(0x01ee, 0, 0x82),SCE(0x01ef, 1, 0x42),SCE(0x01f1, 0, 0x83),SCE(0x01f2, 1, 0x3), +SCE(0x01f3, 2, 0x43),SCE(0x01f1, 0, 0x83),SCE(0x01f2, 1, 0x3),SCE(0x01f3, 2, 0x43), +SCE(0x01f4, 0, 0x82),SCE(0x01f5, 1, 0x42),SCE(0x0195, 0, 0x42),SCE(0x01f6, 1, 0x82), +SCE(0x01bf, 0, 0x42),SCE(0x01f7, 1, 0x82),SCE(0x01f8, 0, 0x82),SCE(0x01f9, 1, 0x42), +SCE(0x1041d, 0, 0x82),SCE(0x10445, 1, 0x42),SCE(0x01fa, 0, 0x82),SCE(0x01fb, 1, 0x42), +SCE(0x01fc, 0, 0x82),SCE(0x01fd, 1, 0x42),SCE(0x01fe, 0, 0x82),SCE(0x01ff, 1, 0x42), +SCE(0x0200, 0, 0x82),SCE(0x0201, 1, 0x42),SCE(0x0202, 0, 0x82),SCE(0x0203, 1, 0x42), +SCE(0x0204, 0, 0x82),SCE(0x0205, 1, 0x42),SCE(0x0206, 0, 0x82),SCE(0x0207, 1, 0x42), +SCE(0x0208, 0, 0x82),SCE(0x0209, 1, 0x42),SCE(0x020a, 0, 0x82),SCE(0x020b, 1, 0x42), +SCE(0x020c, 0, 0x82),SCE(0x020d, 1, 0x42),SCE(0x020e, 0, 0x82),SCE(0x020f, 1, 0x42), +SCE(0x0210, 0, 0x82),SCE(0x0211, 1, 0x42),SCE(0x0212, 0, 0x82),SCE(0x0213, 1, 0x42), +SCE(0x0214, 0, 0x82),SCE(0x0215, 1, 0x42),SCE(0x0216, 0, 0x82),SCE(0x0217, 1, 0x42), +SCE(0x0218, 0, 0x82),SCE(0x0219, 1, 0x42),SCE(0x021a, 0, 0x82),SCE(0x021b, 1, 0x42), +SCE(0x021c, 0, 0x82),SCE(0x021d, 1, 0x42),SCE(0x021e, 0, 0x82),SCE(0x021f, 1, 0x42), +SCE(0x019e, 0, 0x42),SCE(0x0220, 1, 0x82),SCE(0x0222, 0, 0x82),SCE(0x0223, 1, 0x42), +SCE(0x0224, 0, 0x82),SCE(0x0225, 1, 0x42),SCE(0x0226, 0, 0x82),SCE(0x0227, 1, 0x42), +SCE(0x0228, 0, 0x82),SCE(0x0229, 1, 0x42),SCE(0x022a, 0, 0x82),SCE(0x022b, 1, 0x42), +SCE(0x022c, 0, 0x82),SCE(0x022d, 1, 0x42),SCE(0x022e, 0, 0x82),SCE(0x022f, 1, 0x42), +SCE(0x0230, 0, 0x82),SCE(0x0231, 1, 0x42),SCE(0x0232, 0, 0x82),SCE(0x0233, 1, 0x42), +SCE(0xa684, 0, 0x82),SCE(0xa685, 1, 0x42),SCE(0x023a, 0, 0x82),SCE(0x2c65, 1, 0x42), +SCE(0x023b, 0, 0x82),SCE(0x023c, 1, 0x42),SCE(0x019a, 0, 0x42),SCE(0x023d, 1, 0x82), +SCE(0x023e, 0, 0x82),SCE(0x2c66, 1, 0x42),SCE(0x0241, 0, 0x82),SCE(0x0242, 1, 0x42), +SCE(0x10412, 0, 0x82),SCE(0x1043a, 1, 0x42),SCE(0x0180, 0, 0x42),SCE(0x0243, 1, 0x82), +SCE(0x0244, 0, 0x82),SCE(0x0289, 1, 0x42),SCE(0x0245, 0, 0x82),SCE(0x028c, 1, 0x42), +SCE(0x0246, 0, 0x82),SCE(0x0247, 1, 0x42),SCE(0x0248, 0, 0x82),SCE(0x0249, 1, 0x42), +SCE(0x024a, 0, 0x82),SCE(0x024b, 1, 0x42),SCE(0x024c, 0, 0x82),SCE(0x024d, 1, 0x42), +SCE(0x2c1b, 0, 0x82),SCE(0x2c4b, 1, 0x42),SCE(0x024e, 0, 0x82),SCE(0x024f, 1, 0x42), +SCE(0x1040a, 0, 0x82),SCE(0x10432, 1, 0x42),SCE(0x2160, 0, 0x82),SCE(0x2170, 1, 0x42), +SCE(0xa692, 0, 0x82),SCE(0xa693, 1, 0x42),SCE(0x027d, 0, 0x42),SCE(0x2c64, 1, 0x82), +SCE(0x10410, 0, 0x82),SCE(0x10438, 1, 0x42),SCE(0x2c21, 0, 0x82),SCE(0x2c51, 1, 0x42), +SCE(0x2c69, 0, 0x82),SCE(0x2c6a, 1, 0x42),SCE(0x10409, 0, 0x82),SCE(0x10431, 1, 0x42), +SCE(0x10414, 0, 0x82),SCE(0x1043c, 1, 0x42),SCE(0x2162, 0, 0x82),SCE(0x2172, 1, 0x42), +SCE(0x1041e, 0, 0x82),SCE(0x10446, 1, 0x42),SCE(0x0271, 0, 0x42),SCE(0x2c6e, 1, 0x82), +SCE(0x10415, 0, 0x82),SCE(0x1043d, 1, 0x42),SCE(0x0252, 0, 0x42),SCE(0x2c70, 1, 0x82), +SCE(0x2c72, 0, 0x82),SCE(0x2c73, 1, 0x42),SCE(0x2c0b, 0, 0x82),SCE(0x2c3b, 1, 0x42), +SCE(0x10416, 0, 0x82),SCE(0x1043e, 1, 0x42),SCE(0x2c75, 0, 0x82),SCE(0x2c76, 1, 0x42), +SCE(0x2164, 0, 0x82),SCE(0x2174, 1, 0x42),SCE(0xa640, 0, 0x82),SCE(0xa641, 1, 0x42), +SCE(0xff22, 0, 0x82),SCE(0xff42, 1, 0x42),SCE(0x2c0c, 0, 0x82),SCE(0x2c3c, 1, 0x42), +SCE(0x10417, 0, 0x82),SCE(0x1043f, 1, 0x42),SCE(0xff24, 0, 0x82),SCE(0xff44, 1, 0x42), +SCE(0xff25, 0, 0x82),SCE(0xff45, 1, 0x42),SCE(0xff26, 0, 0x82),SCE(0xff46, 1, 0x42), +SCE(0x2c0d, 0, 0x82),SCE(0x2c3d, 1, 0x42),SCE(0x24c1, 0, 0x82),SCE(0x24db, 1, 0x42), +SCE(0xa728, 0, 0x82),SCE(0xa729, 1, 0x42),SCE(0x023f, 0, 0x42),SCE(0x2c7e, 1, 0x82), +SCE(0x10411, 0, 0x82),SCE(0x10439, 1, 0x42),SCE(0xff29, 0, 0x82),SCE(0xff49, 1, 0x42), +SCE(0x1040b, 0, 0x82),SCE(0x10433, 1, 0x42),SCE(0xa72a, 0, 0x82),SCE(0xa72b, 1, 0x42), +SCE(0x2c80, 0, 0x82),SCE(0x2c81, 1, 0x42),SCE(0xff2b, 0, 0x82),SCE(0xff4b, 1, 0x42), +SCE(0xa72c, 0, 0x82),SCE(0xa72d, 1, 0x42),SCE(0x2c0e, 0, 0x82),SCE(0x2c3e, 1, 0x42), +SCE(0xff2d, 0, 0x82),SCE(0xff4d, 1, 0x42),SCE(0x10419, 0, 0x82),SCE(0x10441, 1, 0x42), +SCE(0xa72e, 0, 0x82),SCE(0xa72f, 1, 0x42),SCE(0x1040d, 0, 0x82),SCE(0x10435, 1, 0x42), +SCE(0xff2f, 0, 0x82),SCE(0xff4f, 1, 0x42),SCE(0xff31, 0, 0x82),SCE(0xff51, 1, 0x42), +SCE(0xff32, 0, 0x82),SCE(0xff52, 1, 0x42),SCE(0x1041a, 0, 0x82),SCE(0x10442, 1, 0x42), +SCE(0xff34, 0, 0x82),SCE(0xff54, 1, 0x42),SCE(0x2c98, 0, 0x82),SCE(0x2c99, 1, 0x42), +SCE(0x2c8a, 0, 0x82),SCE(0x2c8b, 1, 0x42),SCE(0x0345, 0, 0x44),SCE(0x0399, 1, 0x84), +SCE(0x03b9, 2, 0x44),SCE(0x1fbe, 3, 0x44),SCE(0x2c8c, 0, 0x82),SCE(0x2c8d, 1, 0x42), +SCE(0xff37, 0, 0x82),SCE(0xff57, 1, 0x42),SCE(0xa656, 0, 0x82),SCE(0xa657, 1, 0x42), +SCE(0x1041b, 0, 0x82),SCE(0x10443, 1, 0x42),SCE(0xa738, 0, 0x82),SCE(0xa739, 1, 0x42), +SCE(0x2c8e, 0, 0x82),SCE(0x2c8f, 1, 0x42),SCE(0xff39, 0, 0x82),SCE(0xff59, 1, 0x42), +SCE(0x10404, 0, 0x82),SCE(0x1042c, 1, 0x42),SCE(0xa73a, 0, 0x82),SCE(0xa73b, 1, 0x42), +SCE(0x2c90, 0, 0x82),SCE(0x2c91, 1, 0x42),SCE(0xa73c, 0, 0x82),SCE(0xa73d, 1, 0x42), +SCE(0x2c92, 0, 0x82),SCE(0x2c93, 1, 0x42),SCE(0x1041c, 0, 0x82),SCE(0x10444, 1, 0x42), +SCE(0x0370, 0, 0x82),SCE(0x0371, 1, 0x42),SCE(0x0372, 0, 0x82),SCE(0x0373, 1, 0x42), +SCE(0xa73e, 0, 0x82),SCE(0xa73f, 1, 0x42),SCE(0x0376, 0, 0x82),SCE(0x0377, 1, 0x42), +SCE(0x2c94, 0, 0x82),SCE(0x2c95, 1, 0x42),SCE(0x2c96, 0, 0x82),SCE(0x2c97, 1, 0x42), +SCE(0x0386, 0, 0x82),SCE(0x03ac, 1, 0x42),SCE(0x10405, 0, 0x82),SCE(0x1042d, 1, 0x42), +SCE(0x0388, 0, 0x82),SCE(0x03ad, 1, 0x42),SCE(0x0389, 0, 0x82),SCE(0x03ae, 1, 0x42), +SCE(0x038a, 0, 0x82),SCE(0x03af, 1, 0x42),SCE(0x038c, 0, 0x82),SCE(0x03cc, 1, 0x42), +SCE(0x038e, 0, 0x82),SCE(0x03cd, 1, 0x42),SCE(0x038f, 0, 0x82),SCE(0x03ce, 1, 0x42), +SCE(0x0391, 0, 0x82),SCE(0x03b1, 1, 0x42),SCE(0x0392, 0, 0x83),SCE(0x03b2, 1, 0x43), +SCE(0x03d0, 2, 0x43),SCE(0x0393, 0, 0x82),SCE(0x03b3, 1, 0x42),SCE(0x0394, 0, 0x82), +SCE(0x03b4, 1, 0x42),SCE(0x0395, 0, 0x83),SCE(0x03b5, 1, 0x43),SCE(0x03f5, 2, 0x43), +SCE(0x0396, 0, 0x82),SCE(0x03b6, 1, 0x42),SCE(0x0397, 0, 0x82),SCE(0x03b7, 1, 0x42), +SCE(0x0398, 0, 0x84),SCE(0x03b8, 1, 0x44),SCE(0x03d1, 2, 0x44),SCE(0x03f4, 3, 0x84), +SCE(0x0345, 0, 0x44),SCE(0x0399, 1, 0x84),SCE(0x03b9, 2, 0x44),SCE(0x1fbe, 3, 0x44), +SCE(0x039a, 0, 0x83),SCE(0x03ba, 1, 0x43),SCE(0x03f0, 2, 0x43),SCE(0x039b, 0, 0x82), +SCE(0x03bb, 1, 0x42),SCE(0x00b5, 0, 0x43),SCE(0x039c, 1, 0x83),SCE(0x03bc, 2, 0x43), +SCE(0x039d, 0, 0x82),SCE(0x03bd, 1, 0x42),SCE(0x039e, 0, 0x82),SCE(0x03be, 1, 0x42), +SCE(0x039f, 0, 0x82),SCE(0x03bf, 1, 0x42),SCE(0x03a0, 0, 0x83),SCE(0x03c0, 1, 0x43), +SCE(0x03d6, 2, 0x43),SCE(0x03a1, 0, 0x83),SCE(0x03c1, 1, 0x43),SCE(0x03f1, 2, 0x43), +SCE(0x03a3, 0, 0x83),SCE(0x03c2, 1, 0x43),SCE(0x03c3, 2, 0x43),SCE(0x03a4, 0, 0x82), +SCE(0x03c4, 1, 0x42),SCE(0x03a5, 0, 0x82),SCE(0x03c5, 1, 0x42),SCE(0x03a6, 0, 0x83), +SCE(0x03c6, 1, 0x43),SCE(0x03d5, 2, 0x43),SCE(0x03a7, 0, 0x82),SCE(0x03c7, 1, 0x42), +SCE(0x03a8, 0, 0x82),SCE(0x03c8, 1, 0x42),SCE(0x03a9, 0, 0x83),SCE(0x03c9, 1, 0x43), +SCE(0x2126, 2, 0x83),SCE(0x03aa, 0, 0x82),SCE(0x03ca, 1, 0x42),SCE(0x03ab, 0, 0x82), +SCE(0x03cb, 1, 0x42),SCE(0x24c9, 0, 0x82),SCE(0x24e3, 1, 0x42),SCE(0x2ce0, 0, 0x82), +SCE(0x2ce1, 1, 0x42),SCE(0xa748, 0, 0x82),SCE(0xa749, 1, 0x42),SCE(0x2c9c, 0, 0x82), +SCE(0x2c9d, 1, 0x42),SCE(0x2c9e, 0, 0x82),SCE(0x2c9f, 1, 0x42),SCE(0xa74a, 0, 0x82), +SCE(0xa74b, 1, 0x42),SCE(0x2ca0, 0, 0x82),SCE(0x2ca1, 1, 0x42),SCE(0x03a3, 0, 0x83), +SCE(0x03c2, 1, 0x43),SCE(0x03c3, 2, 0x43),SCE(0x1041f, 0, 0x82),SCE(0x10447, 1, 0x42), +SCE(0xa74c, 0, 0x82),SCE(0xa74d, 1, 0x42),SCE(0xa68a, 0, 0x82),SCE(0xa68b, 1, 0x42), +SCE(0x2ca2, 0, 0x82),SCE(0x2ca3, 1, 0x42),SCE(0x03cf, 0, 0x82),SCE(0x03d7, 1, 0x42), +SCE(0x0392, 0, 0x83),SCE(0x03b2, 1, 0x43),SCE(0x03d0, 2, 0x43),SCE(0x0398, 0, 0x84), +SCE(0x03b8, 1, 0x44),SCE(0x03d1, 2, 0x44),SCE(0x03f4, 3, 0x84),SCE(0x03a6, 0, 0x83), +SCE(0x03c6, 1, 0x43),SCE(0x03d5, 2, 0x43),SCE(0x03a0, 0, 0x83),SCE(0x03c0, 1, 0x43), +SCE(0x03d6, 2, 0x43),SCE(0x03d8, 0, 0x82),SCE(0x03d9, 1, 0x42),SCE(0x2ca4, 0, 0x82), +SCE(0x2ca5, 1, 0x42),SCE(0x03da, 0, 0x82),SCE(0x03db, 1, 0x42),SCE(0x03dc, 0, 0x82), +SCE(0x03dd, 1, 0x42),SCE(0x03de, 0, 0x82),SCE(0x03df, 1, 0x42),SCE(0x03e0, 0, 0x82), +SCE(0x03e1, 1, 0x42),SCE(0x03e2, 0, 0x82),SCE(0x03e3, 1, 0x42),SCE(0x03e4, 0, 0x82), +SCE(0x03e5, 1, 0x42),SCE(0x2ca6, 0, 0x82),SCE(0x2ca7, 1, 0x42),SCE(0x03e6, 0, 0x82), +SCE(0x03e7, 1, 0x42),SCE(0x10420, 0, 0x82),SCE(0x10448, 1, 0x42),SCE(0x03e8, 0, 0x82), +SCE(0x03e9, 1, 0x42),SCE(0x2ce2, 0, 0x82),SCE(0x2ce3, 1, 0x42),SCE(0x03ea, 0, 0x82), +SCE(0x03eb, 1, 0x42),SCE(0x03ec, 0, 0x82),SCE(0x03ed, 1, 0x42),SCE(0x03ee, 0, 0x82), +SCE(0x03ef, 1, 0x42),SCE(0x039a, 0, 0x83),SCE(0x03ba, 1, 0x43),SCE(0x03f0, 2, 0x43), +SCE(0x03a1, 0, 0x83),SCE(0x03c1, 1, 0x43),SCE(0x03f1, 2, 0x43),SCE(0x0398, 0, 0x84), +SCE(0x03b8, 1, 0x44),SCE(0x03d1, 2, 0x44),SCE(0x03f4, 3, 0x84),SCE(0x0395, 0, 0x83), +SCE(0x03b5, 1, 0x43),SCE(0x03f5, 2, 0x43),SCE(0x03f7, 0, 0x82),SCE(0x03f8, 1, 0x42), +SCE(0x03f2, 0, 0x42),SCE(0x03f9, 1, 0x82),SCE(0x03fa, 0, 0x82),SCE(0x03fb, 1, 0x42), +SCE(0x037b, 0, 0x42),SCE(0x03fd, 1, 0x82),SCE(0x037c, 0, 0x42),SCE(0x03fe, 1, 0x82), +SCE(0x037d, 0, 0x42),SCE(0x03ff, 1, 0x82),SCE(0x0400, 0, 0x82),SCE(0x0450, 1, 0x42), +SCE(0x0401, 0, 0x82),SCE(0x0451, 1, 0x42),SCE(0x0402, 0, 0x82),SCE(0x0452, 1, 0x42), +SCE(0x0403, 0, 0x82),SCE(0x0453, 1, 0x42),SCE(0x0404, 0, 0x82),SCE(0x0454, 1, 0x42), +SCE(0x0405, 0, 0x82),SCE(0x0455, 1, 0x42),SCE(0x0406, 0, 0x82),SCE(0x0456, 1, 0x42), +SCE(0x0407, 0, 0x82),SCE(0x0457, 1, 0x42),SCE(0x0408, 0, 0x82),SCE(0x0458, 1, 0x42), +SCE(0x0409, 0, 0x82),SCE(0x0459, 1, 0x42),SCE(0x040a, 0, 0x82),SCE(0x045a, 1, 0x42), +SCE(0x040b, 0, 0x82),SCE(0x045b, 1, 0x42),SCE(0x040c, 0, 0x82),SCE(0x045c, 1, 0x42), +SCE(0x040d, 0, 0x82),SCE(0x045d, 1, 0x42),SCE(0x040e, 0, 0x82),SCE(0x045e, 1, 0x42), +SCE(0x040f, 0, 0x82),SCE(0x045f, 1, 0x42),SCE(0x0410, 0, 0x82),SCE(0x0430, 1, 0x42), +SCE(0x0411, 0, 0x82),SCE(0x0431, 1, 0x42),SCE(0x0412, 0, 0x82),SCE(0x0432, 1, 0x42), +SCE(0x0413, 0, 0x82),SCE(0x0433, 1, 0x42),SCE(0x0414, 0, 0x82),SCE(0x0434, 1, 0x42), +SCE(0x0415, 0, 0x82),SCE(0x0435, 1, 0x42),SCE(0x0416, 0, 0x82),SCE(0x0436, 1, 0x42), +SCE(0x0417, 0, 0x82),SCE(0x0437, 1, 0x42),SCE(0x0418, 0, 0x82),SCE(0x0438, 1, 0x42), +SCE(0x0419, 0, 0x82),SCE(0x0439, 1, 0x42),SCE(0x041a, 0, 0x82),SCE(0x043a, 1, 0x42), +SCE(0x041b, 0, 0x82),SCE(0x043b, 1, 0x42),SCE(0x041c, 0, 0x82),SCE(0x043c, 1, 0x42), +SCE(0x041d, 0, 0x82),SCE(0x043d, 1, 0x42),SCE(0x041e, 0, 0x82),SCE(0x043e, 1, 0x42), +SCE(0x041f, 0, 0x82),SCE(0x043f, 1, 0x42),SCE(0x0420, 0, 0x82),SCE(0x0440, 1, 0x42), +SCE(0x0421, 0, 0x82),SCE(0x0441, 1, 0x42),SCE(0x0422, 0, 0x82),SCE(0x0442, 1, 0x42), +SCE(0x0423, 0, 0x82),SCE(0x0443, 1, 0x42),SCE(0x0424, 0, 0x82),SCE(0x0444, 1, 0x42), +SCE(0x0425, 0, 0x82),SCE(0x0445, 1, 0x42),SCE(0x0426, 0, 0x82),SCE(0x0446, 1, 0x42), +SCE(0x0427, 0, 0x82),SCE(0x0447, 1, 0x42),SCE(0x0428, 0, 0x82),SCE(0x0448, 1, 0x42), +SCE(0x0429, 0, 0x82),SCE(0x0449, 1, 0x42),SCE(0x042a, 0, 0x82),SCE(0x044a, 1, 0x42), +SCE(0x042b, 0, 0x82),SCE(0x044b, 1, 0x42),SCE(0x042c, 0, 0x82),SCE(0x044c, 1, 0x42), +SCE(0x042d, 0, 0x82),SCE(0x044d, 1, 0x42),SCE(0x042e, 0, 0x82),SCE(0x044e, 1, 0x42), +SCE(0x042f, 0, 0x82),SCE(0x044f, 1, 0x42),SCE(0xff3a, 0, 0x82),SCE(0xff5a, 1, 0x42), +SCE(0x2cb4, 0, 0x82),SCE(0x2cb5, 1, 0x42),SCE(0x00b5, 0, 0x43),SCE(0x039c, 1, 0x83), +SCE(0x03bc, 2, 0x43),SCE(0x10423, 0, 0x82),SCE(0x1044b, 1, 0x42),SCE(0x24b6, 0, 0x82), +SCE(0x24d0, 1, 0x42),SCE(0x24b8, 0, 0x82),SCE(0x24d2, 1, 0x42),SCE(0xff2c, 0, 0x82), +SCE(0xff4c, 1, 0x42),SCE(0x10421, 0, 0x82),SCE(0x10449, 1, 0x42),SCE(0x24ba, 0, 0x82), +SCE(0x24d4, 1, 0x42),SCE(0x10424, 0, 0x82),SCE(0x1044c, 1, 0x42),SCE(0x0460, 0, 0x82), +SCE(0x0461, 1, 0x42),SCE(0x0462, 0, 0x82),SCE(0x0463, 1, 0x42),SCE(0x1d7d, 0, 0x42), +SCE(0x2c63, 1, 0x82),SCE(0x0464, 0, 0x82),SCE(0x0465, 1, 0x42),SCE(0x0466, 0, 0x82), +SCE(0x0467, 1, 0x42),SCE(0x2c67, 0, 0x82),SCE(0x2c68, 1, 0x42),SCE(0x0468, 0, 0x82), +SCE(0x0469, 1, 0x42),SCE(0x24bc, 0, 0x82),SCE(0x24d6, 1, 0x42),SCE(0x046a, 0, 0x82), +SCE(0x046b, 1, 0x42),SCE(0x2c6b, 0, 0x82),SCE(0x2c6c, 1, 0x42),SCE(0x046c, 0, 0x82), +SCE(0x046d, 1, 0x42),SCE(0x0251, 0, 0x42),SCE(0x2c6d, 1, 0x82),SCE(0x046e, 0, 0x82), +SCE(0x046f, 1, 0x42),SCE(0x0250, 0, 0x42),SCE(0x2c6f, 1, 0x82),SCE(0x0470, 0, 0x82), +SCE(0x0471, 1, 0x42),SCE(0xa768, 0, 0x82),SCE(0xa769, 1, 0x42),SCE(0x0472, 0, 0x82), +SCE(0x0473, 1, 0x42),SCE(0x0474, 0, 0x82),SCE(0x0475, 1, 0x42),SCE(0x24be, 0, 0x82), +SCE(0x24d8, 1, 0x42),SCE(0x0476, 0, 0x82),SCE(0x0477, 1, 0x42),SCE(0x0478, 0, 0x82), +SCE(0x0479, 1, 0x42),SCE(0x047a, 0, 0x82),SCE(0x047b, 1, 0x42),SCE(0x047c, 0, 0x82), +SCE(0x047d, 1, 0x42),SCE(0xa76a, 0, 0x82),SCE(0xa76b, 1, 0x42),SCE(0x047e, 0, 0x82), +SCE(0x047f, 1, 0x42),SCE(0x0240, 0, 0x42),SCE(0x2c7f, 1, 0x82),SCE(0x0480, 0, 0x82), +SCE(0x0481, 1, 0x42),SCE(0x10c0, 0, 0x82),SCE(0x2d20, 1, 0x42),SCE(0x2c82, 0, 0x82), +SCE(0x2c83, 1, 0x42),SCE(0x2c84, 0, 0x82),SCE(0x2c85, 1, 0x42),SCE(0x2c86, 0, 0x82), +SCE(0x2c87, 1, 0x42),SCE(0x10c1, 0, 0x82),SCE(0x2d21, 1, 0x42),SCE(0x2c88, 0, 0x82), +SCE(0x2c89, 1, 0x42),SCE(0xa76c, 0, 0x82),SCE(0xa76d, 1, 0x42),SCE(0x048a, 0, 0x82), +SCE(0x048b, 1, 0x42),SCE(0x048c, 0, 0x82),SCE(0x048d, 1, 0x42),SCE(0x00c2, 0, 0x82), +SCE(0x00e2, 1, 0x42),SCE(0x048e, 0, 0x82),SCE(0x048f, 1, 0x42),SCE(0x0490, 0, 0x82), +SCE(0x0491, 1, 0x42),SCE(0x0492, 0, 0x82),SCE(0x0493, 1, 0x42),SCE(0x10c3, 0, 0x82), +SCE(0x2d23, 1, 0x42),SCE(0x0494, 0, 0x82),SCE(0x0495, 1, 0x42),SCE(0xa76e, 0, 0x82), +SCE(0xa76f, 1, 0x42),SCE(0x0496, 0, 0x82),SCE(0x0497, 1, 0x42),SCE(0x0498, 0, 0x82), +SCE(0x0499, 1, 0x42),SCE(0x00c4, 0, 0x82),SCE(0x00e4, 1, 0x42),SCE(0x049a, 0, 0x82), +SCE(0x049b, 1, 0x42),SCE(0x10426, 0, 0x82),SCE(0x1044e, 1, 0x42),SCE(0x049c, 0, 0x82), +SCE(0x049d, 1, 0x42),SCE(0x049e, 0, 0x82),SCE(0x049f, 1, 0x42),SCE(0x10c5, 0, 0x82), +SCE(0x2d25, 1, 0x42),SCE(0x04a0, 0, 0x82),SCE(0x04a1, 1, 0x42),SCE(0x04a2, 0, 0x82), +SCE(0x04a3, 1, 0x42),SCE(0x04a4, 0, 0x82),SCE(0x04a5, 1, 0x42),SCE(0x2cc6, 0, 0x82), +SCE(0x2cc7, 1, 0x42),SCE(0x04a6, 0, 0x82),SCE(0x04a7, 1, 0x42),SCE(0x04a8, 0, 0x82), +SCE(0x04a9, 1, 0x42),SCE(0x2c60, 0, 0x82),SCE(0x2c61, 1, 0x42),SCE(0x04aa, 0, 0x82), +SCE(0x04ab, 1, 0x42),SCE(0x10c7, 0, 0x82),SCE(0x2d27, 1, 0x42),SCE(0x04ac, 0, 0x82), +SCE(0x04ad, 1, 0x42),SCE(0x10413, 0, 0x82),SCE(0x1043b, 1, 0x42),SCE(0x04ae, 0, 0x82), +SCE(0x04af, 1, 0x42),SCE(0x04b0, 0, 0x82),SCE(0x04b1, 1, 0x42),SCE(0x2cc8, 0, 0x82), +SCE(0x2cc9, 1, 0x42),SCE(0x04b2, 0, 0x82),SCE(0x04b3, 1, 0x42),SCE(0x04b4, 0, 0x82), +SCE(0x04b5, 1, 0x42),SCE(0x04b6, 0, 0x82),SCE(0x04b7, 1, 0x42),SCE(0x24b7, 0, 0x82), +SCE(0x24d1, 1, 0x42),SCE(0x04b8, 0, 0x82),SCE(0x04b9, 1, 0x42),SCE(0x24b9, 0, 0x82), +SCE(0x24d3, 1, 0x42),SCE(0x04ba, 0, 0x82),SCE(0x04bb, 1, 0x42),SCE(0x24bb, 0, 0x82), +SCE(0x24d5, 1, 0x42),SCE(0x04bc, 0, 0x82),SCE(0x04bd, 1, 0x42),SCE(0x24bd, 0, 0x82), +SCE(0x24d7, 1, 0x42),SCE(0x04be, 0, 0x82),SCE(0x04bf, 1, 0x42),SCE(0x24bf, 0, 0x82), +SCE(0x24d9, 1, 0x42),SCE(0x04c0, 0, 0x82),SCE(0x04cf, 1, 0x42),SCE(0x04c1, 0, 0x82), +SCE(0x04c2, 1, 0x42),SCE(0x24c2, 0, 0x82),SCE(0x24dc, 1, 0x42),SCE(0x04c3, 0, 0x82), +SCE(0x04c4, 1, 0x42),SCE(0x24c4, 0, 0x82),SCE(0x24de, 1, 0x42),SCE(0x04c5, 0, 0x82), +SCE(0x04c6, 1, 0x42),SCE(0x24c6, 0, 0x82),SCE(0x24e0, 1, 0x42),SCE(0x04c7, 0, 0x82), +SCE(0x04c8, 1, 0x42),SCE(0x24c8, 0, 0x82),SCE(0x24e2, 1, 0x42),SCE(0x04c9, 0, 0x82), +SCE(0x04ca, 1, 0x42),SCE(0x24ca, 0, 0x82),SCE(0x24e4, 1, 0x42),SCE(0x04cb, 0, 0x82), +SCE(0x04cc, 1, 0x42),SCE(0x24cc, 0, 0x82),SCE(0x24e6, 1, 0x42),SCE(0x04cd, 0, 0x82), +SCE(0x04ce, 1, 0x42),SCE(0x24ce, 0, 0x82),SCE(0x24e8, 1, 0x42),SCE(0x10cd, 0, 0x82), +SCE(0x2d2d, 1, 0x42),SCE(0x04d0, 0, 0x82),SCE(0x04d1, 1, 0x42),SCE(0x04d2, 0, 0x82), +SCE(0x04d3, 1, 0x42),SCE(0x04d4, 0, 0x82),SCE(0x04d5, 1, 0x42),SCE(0x2cce, 0, 0x82), +SCE(0x2ccf, 1, 0x42),SCE(0x04d6, 0, 0x82),SCE(0x04d7, 1, 0x42),SCE(0xa779, 0, 0x82), +SCE(0xa77a, 1, 0x42),SCE(0x04d8, 0, 0x82),SCE(0x04d9, 1, 0x42),SCE(0x04da, 0, 0x82), +SCE(0x04db, 1, 0x42),SCE(0x24cf, 0, 0x82),SCE(0x24e9, 1, 0x42),SCE(0x04dc, 0, 0x82), +SCE(0x04dd, 1, 0x42),SCE(0x04de, 0, 0x82),SCE(0x04df, 1, 0x42),SCE(0x04e0, 0, 0x82), +SCE(0x04e1, 1, 0x42),SCE(0x2cd0, 0, 0x82),SCE(0x2cd1, 1, 0x42),SCE(0x04e2, 0, 0x82), +SCE(0x04e3, 1, 0x42),SCE(0x04e4, 0, 0x82),SCE(0x04e5, 1, 0x42),SCE(0x026b, 0, 0x42), +SCE(0x2c62, 1, 0x82),SCE(0x04e6, 0, 0x82),SCE(0x04e7, 1, 0x42),SCE(0x04e8, 0, 0x82), +SCE(0x04e9, 1, 0x42),SCE(0x04ea, 0, 0x82),SCE(0x04eb, 1, 0x42),SCE(0x2132, 0, 0x82), +SCE(0x214e, 1, 0x42),SCE(0x04ec, 0, 0x82),SCE(0x04ed, 1, 0x42),SCE(0x2cd2, 0, 0x82), +SCE(0x2cd3, 1, 0x42),SCE(0x04ee, 0, 0x82),SCE(0x04ef, 1, 0x42),SCE(0x04f0, 0, 0x82), +SCE(0x04f1, 1, 0x42),SCE(0x10422, 0, 0x82),SCE(0x1044a, 1, 0x42),SCE(0x04f2, 0, 0x82), +SCE(0x04f3, 1, 0x42),SCE(0x04f4, 0, 0x82),SCE(0x04f5, 1, 0x42),SCE(0x04f6, 0, 0x82), +SCE(0x04f7, 1, 0x42),SCE(0x04f8, 0, 0x82),SCE(0x04f9, 1, 0x42),SCE(0x2cd4, 0, 0x82), +SCE(0x2cd5, 1, 0x42),SCE(0x04fa, 0, 0x82),SCE(0x04fb, 1, 0x42),SCE(0x04fc, 0, 0x82), +SCE(0x04fd, 1, 0x42),SCE(0x04fe, 0, 0x82),SCE(0x04ff, 1, 0x42),SCE(0x0500, 0, 0x82), +SCE(0x0501, 1, 0x42),SCE(0x0502, 0, 0x82),SCE(0x0503, 1, 0x42),SCE(0x0504, 0, 0x82), +SCE(0x0505, 1, 0x42),SCE(0x2cd6, 0, 0x82),SCE(0x2cd7, 1, 0x42),SCE(0x0506, 0, 0x82), +SCE(0x0507, 1, 0x42),SCE(0x0508, 0, 0x82),SCE(0x0509, 1, 0x42),SCE(0x050a, 0, 0x82), +SCE(0x050b, 1, 0x42),SCE(0x050c, 0, 0x82),SCE(0x050d, 1, 0x42),SCE(0x050e, 0, 0x82), +SCE(0x050f, 1, 0x42),SCE(0x0510, 0, 0x82),SCE(0x0511, 1, 0x42),SCE(0x2cd8, 0, 0x82), +SCE(0x2cd9, 1, 0x42),SCE(0x0512, 0, 0x82),SCE(0x0513, 1, 0x42),SCE(0x0514, 0, 0x82), +SCE(0x0515, 1, 0x42),SCE(0x0516, 0, 0x82),SCE(0x0517, 1, 0x42),SCE(0x0518, 0, 0x82), +SCE(0x0519, 1, 0x42),SCE(0x051a, 0, 0x82),SCE(0x051b, 1, 0x42),SCE(0x2ca8, 0, 0x82), +SCE(0x2ca9, 1, 0x42),SCE(0x051c, 0, 0x82),SCE(0x051d, 1, 0x42),SCE(0x2cda, 0, 0x82), +SCE(0x2cdb, 1, 0x42),SCE(0x051e, 0, 0x82),SCE(0x051f, 1, 0x42),SCE(0x0520, 0, 0x82), +SCE(0x0521, 1, 0x42),SCE(0x0522, 0, 0x82),SCE(0x0523, 1, 0x42),SCE(0x0524, 0, 0x82), +SCE(0x0525, 1, 0x42),SCE(0x0526, 0, 0x82),SCE(0x0527, 1, 0x42),SCE(0x2c20, 0, 0x82), +SCE(0x2c50, 1, 0x42),SCE(0x2cdc, 0, 0x82),SCE(0x2cdd, 1, 0x42),SCE(0x0531, 0, 0x82), +SCE(0x0561, 1, 0x42),SCE(0x0532, 0, 0x82),SCE(0x0562, 1, 0x42),SCE(0x0533, 0, 0x82), +SCE(0x0563, 1, 0x42),SCE(0x0534, 0, 0x82),SCE(0x0564, 1, 0x42),SCE(0x0535, 0, 0x82), +SCE(0x0565, 1, 0x42),SCE(0x0536, 0, 0x82),SCE(0x0566, 1, 0x42),SCE(0x0537, 0, 0x82), +SCE(0x0567, 1, 0x42),SCE(0x0538, 0, 0x82),SCE(0x0568, 1, 0x42),SCE(0x0539, 0, 0x82), +SCE(0x0569, 1, 0x42),SCE(0x053a, 0, 0x82),SCE(0x056a, 1, 0x42),SCE(0x053b, 0, 0x82), +SCE(0x056b, 1, 0x42),SCE(0x053c, 0, 0x82),SCE(0x056c, 1, 0x42),SCE(0x053d, 0, 0x82), +SCE(0x056d, 1, 0x42),SCE(0x053e, 0, 0x82),SCE(0x056e, 1, 0x42),SCE(0x053f, 0, 0x82), +SCE(0x056f, 1, 0x42),SCE(0x0540, 0, 0x82),SCE(0x0570, 1, 0x42),SCE(0x0541, 0, 0x82), +SCE(0x0571, 1, 0x42),SCE(0x0542, 0, 0x82),SCE(0x0572, 1, 0x42),SCE(0x0543, 0, 0x82), +SCE(0x0573, 1, 0x42),SCE(0x0544, 0, 0x82),SCE(0x0574, 1, 0x42),SCE(0x0545, 0, 0x82), +SCE(0x0575, 1, 0x42),SCE(0x0546, 0, 0x82),SCE(0x0576, 1, 0x42),SCE(0x0547, 0, 0x82), +SCE(0x0577, 1, 0x42),SCE(0x0548, 0, 0x82),SCE(0x0578, 1, 0x42),SCE(0x0549, 0, 0x82), +SCE(0x0579, 1, 0x42),SCE(0x054a, 0, 0x82),SCE(0x057a, 1, 0x42),SCE(0x054b, 0, 0x82), +SCE(0x057b, 1, 0x42),SCE(0x054c, 0, 0x82),SCE(0x057c, 1, 0x42),SCE(0x054d, 0, 0x82), +SCE(0x057d, 1, 0x42),SCE(0x054e, 0, 0x82),SCE(0x057e, 1, 0x42),SCE(0x054f, 0, 0x82), +SCE(0x057f, 1, 0x42),SCE(0x0550, 0, 0x82),SCE(0x0580, 1, 0x42),SCE(0x0551, 0, 0x82), +SCE(0x0581, 1, 0x42),SCE(0x0552, 0, 0x82),SCE(0x0582, 1, 0x42),SCE(0x0553, 0, 0x82), +SCE(0x0583, 1, 0x42),SCE(0x0554, 0, 0x82),SCE(0x0584, 1, 0x42),SCE(0x0555, 0, 0x82), +SCE(0x0585, 1, 0x42),SCE(0x0556, 0, 0x82),SCE(0x0586, 1, 0x42),SCE(0x2caa, 0, 0x82), +SCE(0x2cab, 1, 0x42),SCE(0x2c22, 0, 0x82),SCE(0x2c52, 1, 0x42),SCE(0x2c23, 0, 0x82), +SCE(0x2c53, 1, 0x42),SCE(0x2ceb, 0, 0x82),SCE(0x2cec, 1, 0x42),SCE(0x2cca, 0, 0x82), +SCE(0x2ccb, 1, 0x42),SCE(0xa642, 0, 0x82),SCE(0xa643, 1, 0x42),SCE(0x2ced, 0, 0x82), +SCE(0x2cee, 1, 0x42),SCE(0x2cac, 0, 0x82),SCE(0x2cad, 1, 0x42),SCE(0xa644, 0, 0x82), +SCE(0xa645, 1, 0x42),SCE(0x2c24, 0, 0x82),SCE(0x2c54, 1, 0x42),SCE(0xa646, 0, 0x82), +SCE(0xa647, 1, 0x42),SCE(0x2cf2, 0, 0x82),SCE(0x2cf3, 1, 0x42),SCE(0x10408, 0, 0x82), +SCE(0x10430, 1, 0x42),SCE(0xa648, 0, 0x82),SCE(0xa649, 1, 0x42),SCE(0xa64a, 0, 0x82), +SCE(0xa64b, 1, 0x42),SCE(0xa64c, 0, 0x82),SCE(0xa64d, 1, 0x42),SCE(0x2cae, 0, 0x82), +SCE(0x2caf, 1, 0x42),SCE(0xa64e, 0, 0x82),SCE(0xa64f, 1, 0x42),SCE(0xa650, 0, 0x82), +SCE(0xa651, 1, 0x42),SCE(0xa78b, 0, 0x82),SCE(0xa78c, 1, 0x42),SCE(0xa652, 0, 0x82), +SCE(0xa653, 1, 0x42),SCE(0xa7a8, 0, 0x82),SCE(0xa7a9, 1, 0x42),SCE(0xa654, 0, 0x82), +SCE(0xa655, 1, 0x42),SCE(0x0266, 0, 0x42),SCE(0xa7aa, 1, 0x82),SCE(0x1e00, 0, 0x82), +SCE(0x1e01, 1, 0x42),SCE(0x1e02, 0, 0x82),SCE(0x1e03, 1, 0x42),SCE(0x1e04, 0, 0x82), +SCE(0x1e05, 1, 0x42),SCE(0x2c1f, 0, 0x82),SCE(0x2c4f, 1, 0x42),SCE(0x1e06, 0, 0x82), +SCE(0x1e07, 1, 0x42),SCE(0x1040e, 0, 0x82),SCE(0x10436, 1, 0x42),SCE(0x1e08, 0, 0x82), +SCE(0x1e09, 1, 0x42),SCE(0x1e0a, 0, 0x82),SCE(0x1e0b, 1, 0x42),SCE(0x2cb0, 0, 0x82), +SCE(0x2cb1, 1, 0x42),SCE(0x1e0c, 0, 0x82),SCE(0x1e0d, 1, 0x42),SCE(0x1e0e, 0, 0x82), +SCE(0x1e0f, 1, 0x42),SCE(0x1e10, 0, 0x82),SCE(0x1e11, 1, 0x42),SCE(0xa658, 0, 0x82), +SCE(0xa659, 1, 0x42),SCE(0x1e12, 0, 0x82),SCE(0x1e13, 1, 0x42),SCE(0x1e14, 0, 0x82), +SCE(0x1e15, 1, 0x42),SCE(0x24cd, 0, 0x82),SCE(0x24e7, 1, 0x42),SCE(0x1e16, 0, 0x82), +SCE(0x1e17, 1, 0x42),SCE(0x1e18, 0, 0x82),SCE(0x1e19, 1, 0x42),SCE(0x1e1a, 0, 0x82), +SCE(0x1e1b, 1, 0x42),SCE(0x1e1c, 0, 0x82),SCE(0x1e1d, 1, 0x42),SCE(0xa65a, 0, 0x82), +SCE(0xa65b, 1, 0x42),SCE(0x1e1e, 0, 0x82),SCE(0x1e1f, 1, 0x42),SCE(0x1e20, 0, 0x82), +SCE(0x1e21, 1, 0x42),SCE(0x1e22, 0, 0x82),SCE(0x1e23, 1, 0x42),SCE(0x1e24, 0, 0x82), +SCE(0x1e25, 1, 0x42),SCE(0x1e26, 0, 0x82),SCE(0x1e27, 1, 0x42),SCE(0x1e28, 0, 0x82), +SCE(0x1e29, 1, 0x42),SCE(0xa65c, 0, 0x82),SCE(0xa65d, 1, 0x42),SCE(0x1e2a, 0, 0x82), +SCE(0x1e2b, 1, 0x42),SCE(0x1e2c, 0, 0x82),SCE(0x1e2d, 1, 0x42),SCE(0x1e2e, 0, 0x82), +SCE(0x1e2f, 1, 0x42),SCE(0x1e30, 0, 0x82),SCE(0x1e31, 1, 0x42),SCE(0x1e32, 0, 0x82), +SCE(0x1e33, 1, 0x42),SCE(0x1e34, 0, 0x82),SCE(0x1e35, 1, 0x42),SCE(0xa65e, 0, 0x82), +SCE(0xa65f, 1, 0x42),SCE(0x1e36, 0, 0x82),SCE(0x1e37, 1, 0x42),SCE(0x1e38, 0, 0x82), +SCE(0x1e39, 1, 0x42),SCE(0x1e3a, 0, 0x82),SCE(0x1e3b, 1, 0x42),SCE(0x1e3c, 0, 0x82), +SCE(0x1e3d, 1, 0x42),SCE(0x1e3e, 0, 0x82),SCE(0x1e3f, 1, 0x42),SCE(0x1e40, 0, 0x82), +SCE(0x1e41, 1, 0x42),SCE(0xa660, 0, 0x82),SCE(0xa661, 1, 0x42),SCE(0x1e42, 0, 0x82), +SCE(0x1e43, 1, 0x42),SCE(0x1e44, 0, 0x82),SCE(0x1e45, 1, 0x42),SCE(0x1e46, 0, 0x82), +SCE(0x1e47, 1, 0x42),SCE(0x2cb2, 0, 0x82),SCE(0x2cb3, 1, 0x42),SCE(0x1e48, 0, 0x82), +SCE(0x1e49, 1, 0x42),SCE(0x2cc0, 0, 0x82),SCE(0x2cc1, 1, 0x42),SCE(0x1e4a, 0, 0x82), +SCE(0x1e4b, 1, 0x42),SCE(0x1e4c, 0, 0x82),SCE(0x1e4d, 1, 0x42),SCE(0xa662, 0, 0x82), +SCE(0xa663, 1, 0x42),SCE(0x1e4e, 0, 0x82),SCE(0x1e4f, 1, 0x42),SCE(0x1e50, 0, 0x82), +SCE(0x1e51, 1, 0x42),SCE(0x1e52, 0, 0x82),SCE(0x1e53, 1, 0x42),SCE(0x1e54, 0, 0x82), +SCE(0x1e55, 1, 0x42),SCE(0x1e56, 0, 0x82),SCE(0x1e57, 1, 0x42),SCE(0x1e58, 0, 0x82), +SCE(0x1e59, 1, 0x42),SCE(0xa664, 0, 0x82),SCE(0xa665, 1, 0x42),SCE(0x1e5a, 0, 0x82), +SCE(0x1e5b, 1, 0x42),SCE(0x1e5c, 0, 0x82),SCE(0x1e5d, 1, 0x42),SCE(0x1e5e, 0, 0x82), +SCE(0x1e5f, 1, 0x42),SCE(0x1e60, 0, 0x83),SCE(0x1e61, 1, 0x43),SCE(0x1e9b, 2, 0x43), +SCE(0x1e62, 0, 0x82),SCE(0x1e63, 1, 0x42),SCE(0x1e64, 0, 0x82),SCE(0x1e65, 1, 0x42), +SCE(0xa666, 0, 0x82),SCE(0xa667, 1, 0x42),SCE(0x1e66, 0, 0x82),SCE(0x1e67, 1, 0x42), +SCE(0x1e68, 0, 0x82),SCE(0x1e69, 1, 0x42),SCE(0x1e6a, 0, 0x82),SCE(0x1e6b, 1, 0x42), +SCE(0x1e6c, 0, 0x82),SCE(0x1e6d, 1, 0x42),SCE(0x1e6e, 0, 0x82),SCE(0x1e6f, 1, 0x42), +SCE(0x1e70, 0, 0x82),SCE(0x1e71, 1, 0x42),SCE(0xa668, 0, 0x82),SCE(0xa669, 1, 0x42), +SCE(0x1e72, 0, 0x82),SCE(0x1e73, 1, 0x42),SCE(0x1e74, 0, 0x82),SCE(0x1e75, 1, 0x42), +SCE(0x1e76, 0, 0x82),SCE(0x1e77, 1, 0x42),SCE(0x2cbe, 0, 0x82),SCE(0x2cbf, 1, 0x42), +SCE(0x1e78, 0, 0x82),SCE(0x1e79, 1, 0x42),SCE(0x1e7a, 0, 0x82),SCE(0x1e7b, 1, 0x42), +SCE(0x1e7c, 0, 0x82),SCE(0x1e7d, 1, 0x42),SCE(0xa66a, 0, 0x82),SCE(0xa66b, 1, 0x42), +SCE(0x1e7e, 0, 0x82),SCE(0x1e7f, 1, 0x42),SCE(0x1e80, 0, 0x82),SCE(0x1e81, 1, 0x42), +SCE(0x1e82, 0, 0x82),SCE(0x1e83, 1, 0x42),SCE(0x1e84, 0, 0x82),SCE(0x1e85, 1, 0x42), +SCE(0x1e86, 0, 0x82),SCE(0x1e87, 1, 0x42),SCE(0x1e88, 0, 0x82),SCE(0x1e89, 1, 0x42), +SCE(0xa66c, 0, 0x82),SCE(0xa66d, 1, 0x42),SCE(0x1e8a, 0, 0x82),SCE(0x1e8b, 1, 0x42), +SCE(0x1e8c, 0, 0x82),SCE(0x1e8d, 1, 0x42),SCE(0x1e8e, 0, 0x82),SCE(0x1e8f, 1, 0x42), +SCE(0x1e90, 0, 0x82),SCE(0x1e91, 1, 0x42),SCE(0x1e92, 0, 0x82),SCE(0x1e93, 1, 0x42), +SCE(0x1e94, 0, 0x82),SCE(0x1e95, 1, 0x42),SCE(0xa696, 0, 0x82),SCE(0xa697, 1, 0x42), +SCE(0x10406, 0, 0x82),SCE(0x1042e, 1, 0x42),SCE(0x1e60, 0, 0x83),SCE(0x1e61, 1, 0x43), +SCE(0x1e9b, 2, 0x43),SCE(0x00df, 0, 0x42),SCE(0x1e9e, 1, 0x82),SCE(0x1ea0, 0, 0x82), +SCE(0x1ea1, 1, 0x42),SCE(0x1ea2, 0, 0x82),SCE(0x1ea3, 1, 0x42),SCE(0x1ea4, 0, 0x82), +SCE(0x1ea5, 1, 0x42),SCE(0x24c5, 0, 0x82),SCE(0x24df, 1, 0x42),SCE(0x1ea6, 0, 0x82), +SCE(0x1ea7, 1, 0x42),SCE(0x1ea8, 0, 0x82),SCE(0x1ea9, 1, 0x42),SCE(0x1eaa, 0, 0x82), +SCE(0x1eab, 1, 0x42),SCE(0x1eac, 0, 0x82),SCE(0x1ead, 1, 0x42),SCE(0x1eae, 0, 0x82), +SCE(0x1eaf, 1, 0x42),SCE(0xff28, 0, 0x82),SCE(0xff48, 1, 0x42),SCE(0x1eb0, 0, 0x82), +SCE(0x1eb1, 1, 0x42),SCE(0x1eb2, 0, 0x82),SCE(0x1eb3, 1, 0x42),SCE(0x10425, 0, 0x82), +SCE(0x1044d, 1, 0x42),SCE(0x1eb4, 0, 0x82),SCE(0x1eb5, 1, 0x42),SCE(0x1eb6, 0, 0x82), +SCE(0x1eb7, 1, 0x42),SCE(0x1eb8, 0, 0x82),SCE(0x1eb9, 1, 0x42),SCE(0x1eba, 0, 0x82), +SCE(0x1ebb, 1, 0x42),SCE(0x1ebc, 0, 0x82),SCE(0x1ebd, 1, 0x42),SCE(0x1ebe, 0, 0x82), +SCE(0x1ebf, 1, 0x42),SCE(0x2cb6, 0, 0x82),SCE(0x2cb7, 1, 0x42),SCE(0x1ec0, 0, 0x82), +SCE(0x1ec1, 1, 0x42),SCE(0x1ec2, 0, 0x82),SCE(0x1ec3, 1, 0x42),SCE(0x2c9a, 0, 0x82), +SCE(0x2c9b, 1, 0x42),SCE(0x1ec4, 0, 0x82),SCE(0x1ec5, 1, 0x42),SCE(0x1ec6, 0, 0x82), +SCE(0x1ec7, 1, 0x42),SCE(0x1ec8, 0, 0x82),SCE(0x1ec9, 1, 0x42),SCE(0x1eca, 0, 0x82), +SCE(0x1ecb, 1, 0x42),SCE(0x1ecc, 0, 0x82),SCE(0x1ecd, 1, 0x42),SCE(0x1ece, 0, 0x82), +SCE(0x1ecf, 1, 0x42),SCE(0x1ed0, 0, 0x82),SCE(0x1ed1, 1, 0x42),SCE(0x1ed2, 0, 0x82), +SCE(0x1ed3, 1, 0x42),SCE(0x1ed4, 0, 0x82),SCE(0x1ed5, 1, 0x42),SCE(0x1ed6, 0, 0x82), +SCE(0x1ed7, 1, 0x42),SCE(0x1ed8, 0, 0x82),SCE(0x1ed9, 1, 0x42),SCE(0x1eda, 0, 0x82), +SCE(0x1edb, 1, 0x42),SCE(0x1edc, 0, 0x82),SCE(0x1edd, 1, 0x42),SCE(0x1ede, 0, 0x82), +SCE(0x1edf, 1, 0x42),SCE(0x1ee0, 0, 0x82),SCE(0x1ee1, 1, 0x42),SCE(0x1ee2, 0, 0x82), +SCE(0x1ee3, 1, 0x42),SCE(0x1ee4, 0, 0x82),SCE(0x1ee5, 1, 0x42),SCE(0x03a9, 0, 0x83), +SCE(0x03c9, 1, 0x43),SCE(0x2126, 2, 0x83),SCE(0x1ee6, 0, 0x82),SCE(0x1ee7, 1, 0x42), +SCE(0x1ee8, 0, 0x82),SCE(0x1ee9, 1, 0x42),SCE(0x1eea, 0, 0x82),SCE(0x1eeb, 1, 0x42), +SCE(0xff2a, 0, 0x82),SCE(0xff4a, 1, 0x42),SCE(0x1eec, 0, 0x82),SCE(0x1eed, 1, 0x42), +SCE(0x1eee, 0, 0x82),SCE(0x1eef, 1, 0x42),SCE(0x1ef0, 0, 0x82),SCE(0x1ef1, 1, 0x42), +SCE(0x1ef2, 0, 0x82),SCE(0x1ef3, 1, 0x42),SCE(0x1ef4, 0, 0x82),SCE(0x1ef5, 1, 0x42), +SCE(0x1ef6, 0, 0x82),SCE(0x1ef7, 1, 0x42),SCE(0x1ef8, 0, 0x82),SCE(0x1ef9, 1, 0x42), +SCE(0x1efa, 0, 0x82),SCE(0x1efb, 1, 0x42),SCE(0x2cb8, 0, 0x82),SCE(0x2cb9, 1, 0x42), +SCE(0x1efc, 0, 0x82),SCE(0x1efd, 1, 0x42),SCE(0x004b, 0, 0x83),SCE(0x006b, 1, 0x43), +SCE(0x212a, 2, 0x83),SCE(0x1efe, 0, 0x82),SCE(0x1eff, 1, 0x42),SCE(0xa680, 0, 0x82), +SCE(0xa681, 1, 0x42),SCE(0x1f00, 0, 0x42),SCE(0x1f08, 1, 0x82),SCE(0x1f01, 0, 0x42), +SCE(0x1f09, 1, 0x82),SCE(0x1f02, 0, 0x42),SCE(0x1f0a, 1, 0x82),SCE(0x1f03, 0, 0x42), +SCE(0x1f0b, 1, 0x82),SCE(0x1f04, 0, 0x42),SCE(0x1f0c, 1, 0x82),SCE(0x1f05, 0, 0x42), +SCE(0x1f0d, 1, 0x82),SCE(0x1f06, 0, 0x42),SCE(0x1f0e, 1, 0x82),SCE(0x1f07, 0, 0x42), +SCE(0x1f0f, 1, 0x82),SCE(0x10418, 0, 0x82),SCE(0x10440, 1, 0x42),SCE(0x0265, 0, 0x42), +SCE(0xa78d, 1, 0x82),SCE(0x1f10, 0, 0x42),SCE(0x1f18, 1, 0x82),SCE(0x1f11, 0, 0x42), +SCE(0x1f19, 1, 0x82),SCE(0x1f12, 0, 0x42),SCE(0x1f1a, 1, 0x82),SCE(0x1f13, 0, 0x42), +SCE(0x1f1b, 1, 0x82),SCE(0x1f14, 0, 0x42),SCE(0x1f1c, 1, 0x82),SCE(0x1f15, 0, 0x42), +SCE(0x1f1d, 1, 0x82),SCE(0xff21, 0, 0x82),SCE(0xff41, 1, 0x42),SCE(0xa722, 0, 0x82), +SCE(0xa723, 1, 0x42),SCE(0xff23, 0, 0x82),SCE(0xff43, 1, 0x42),SCE(0xa724, 0, 0x82), +SCE(0xa725, 1, 0x42),SCE(0xa686, 0, 0x82),SCE(0xa687, 1, 0x42),SCE(0xa726, 0, 0x82), +SCE(0xa727, 1, 0x42),SCE(0xff27, 0, 0x82),SCE(0xff47, 1, 0x42),SCE(0x1f20, 0, 0x42), +SCE(0x1f28, 1, 0x82),SCE(0x1f21, 0, 0x42),SCE(0x1f29, 1, 0x82),SCE(0x1f22, 0, 0x42), +SCE(0x1f2a, 1, 0x82),SCE(0x1f23, 0, 0x42),SCE(0x1f2b, 1, 0x82),SCE(0x1f24, 0, 0x42), +SCE(0x1f2c, 1, 0x82),SCE(0x1f25, 0, 0x42),SCE(0x1f2d, 1, 0x82),SCE(0x1f26, 0, 0x42), +SCE(0x1f2e, 1, 0x82),SCE(0x1f27, 0, 0x42),SCE(0x1f2f, 1, 0x82),SCE(0xff30, 0, 0x82), +SCE(0xff50, 1, 0x42),SCE(0xa688, 0, 0x82),SCE(0xa689, 1, 0x42),SCE(0xa732, 0, 0x82), +SCE(0xa733, 1, 0x42),SCE(0xff33, 0, 0x82),SCE(0xff53, 1, 0x42),SCE(0xa734, 0, 0x82), +SCE(0xa735, 1, 0x42),SCE(0xff35, 0, 0x82),SCE(0xff55, 1, 0x42),SCE(0xa736, 0, 0x82), +SCE(0xa737, 1, 0x42),SCE(0x2cba, 0, 0x82),SCE(0x2cbb, 1, 0x42),SCE(0x1f30, 0, 0x42), +SCE(0x1f38, 1, 0x82),SCE(0x1f31, 0, 0x42),SCE(0x1f39, 1, 0x82),SCE(0x1f32, 0, 0x42), +SCE(0x1f3a, 1, 0x82),SCE(0x1f33, 0, 0x42),SCE(0x1f3b, 1, 0x82),SCE(0x1f34, 0, 0x42), +SCE(0x1f3c, 1, 0x82),SCE(0x1f35, 0, 0x42),SCE(0x1f3d, 1, 0x82),SCE(0x1f36, 0, 0x42), +SCE(0x1f3e, 1, 0x82),SCE(0x1f37, 0, 0x42),SCE(0x1f3f, 1, 0x82),SCE(0xa740, 0, 0x82), +SCE(0xa741, 1, 0x42),SCE(0xa742, 0, 0x82),SCE(0xa743, 1, 0x42),SCE(0xa744, 0, 0x82), +SCE(0xa745, 1, 0x42),SCE(0xa746, 0, 0x82),SCE(0xa747, 1, 0x42),SCE(0x1f40, 0, 0x42), +SCE(0x1f48, 1, 0x82),SCE(0x1f41, 0, 0x42),SCE(0x1f49, 1, 0x82),SCE(0x1f42, 0, 0x42), +SCE(0x1f4a, 1, 0x82),SCE(0x1f43, 0, 0x42),SCE(0x1f4b, 1, 0x82),SCE(0x1f44, 0, 0x42), +SCE(0x1f4c, 1, 0x82),SCE(0x1f45, 0, 0x42),SCE(0x1f4d, 1, 0x82),SCE(0xa74e, 0, 0x82), +SCE(0xa74f, 1, 0x42),SCE(0xa750, 0, 0x82),SCE(0xa751, 1, 0x42),SCE(0xa752, 0, 0x82), +SCE(0xa753, 1, 0x42),SCE(0xa754, 0, 0x82),SCE(0xa755, 1, 0x42),SCE(0xa68e, 0, 0x82), +SCE(0xa68f, 1, 0x42),SCE(0xa756, 0, 0x82),SCE(0xa757, 1, 0x42),SCE(0xa758, 0, 0x82), +SCE(0xa759, 1, 0x42),SCE(0x1f51, 0, 0x42),SCE(0x1f59, 1, 0x82),SCE(0xa75a, 0, 0x82), +SCE(0xa75b, 1, 0x42),SCE(0x1f53, 0, 0x42),SCE(0x1f5b, 1, 0x82),SCE(0xa75c, 0, 0x82), +SCE(0xa75d, 1, 0x42),SCE(0x1f55, 0, 0x42),SCE(0x1f5d, 1, 0x82),SCE(0xa75e, 0, 0x82), +SCE(0xa75f, 1, 0x42),SCE(0x1f57, 0, 0x42),SCE(0x1f5f, 1, 0x82),SCE(0xa760, 0, 0x82), +SCE(0xa761, 1, 0x42),SCE(0xa690, 0, 0x82),SCE(0xa691, 1, 0x42),SCE(0xa762, 0, 0x82), +SCE(0xa763, 1, 0x42),SCE(0xff2e, 0, 0x82),SCE(0xff4e, 1, 0x42),SCE(0xa764, 0, 0x82), +SCE(0xa765, 1, 0x42),SCE(0xa766, 0, 0x82),SCE(0xa767, 1, 0x42),SCE(0x1f60, 0, 0x42), +SCE(0x1f68, 1, 0x82),SCE(0x1f61, 0, 0x42),SCE(0x1f69, 1, 0x82),SCE(0x1f62, 0, 0x42), +SCE(0x1f6a, 1, 0x82),SCE(0x1f63, 0, 0x42),SCE(0x1f6b, 1, 0x82),SCE(0x1f64, 0, 0x42), +SCE(0x1f6c, 1, 0x82),SCE(0x1f65, 0, 0x42),SCE(0x1f6d, 1, 0x82),SCE(0x1f66, 0, 0x42), +SCE(0x1f6e, 1, 0x82),SCE(0x1f67, 0, 0x42),SCE(0x1f6f, 1, 0x82),SCE(0x2c1c, 0, 0x82), +SCE(0x2c4c, 1, 0x42),SCE(0x2cbc, 0, 0x82),SCE(0x2cbd, 1, 0x42),SCE(0xa694, 0, 0x82), +SCE(0xa695, 1, 0x42),SCE(0xa77b, 0, 0x82),SCE(0xa77c, 1, 0x42),SCE(0x1d79, 0, 0x42), +SCE(0xa77d, 1, 0x82),SCE(0xa77e, 0, 0x82),SCE(0xa77f, 1, 0x42),SCE(0xa780, 0, 0x82), +SCE(0xa781, 1, 0x42),SCE(0xa782, 0, 0x82),SCE(0xa783, 1, 0x42),SCE(0xa784, 0, 0x82), +SCE(0xa785, 1, 0x42),SCE(0xa786, 0, 0x82),SCE(0xa787, 1, 0x42),SCE(0x1f80, 0, 0x42), +SCE(0x1f88, 1, 0x2),SCE(0x1f81, 0, 0x42),SCE(0x1f89, 1, 0x2),SCE(0x1f82, 0, 0x42), +SCE(0x1f8a, 1, 0x2),SCE(0x1f83, 0, 0x42),SCE(0x1f8b, 1, 0x2),SCE(0x1f84, 0, 0x42), +SCE(0x1f8c, 1, 0x2),SCE(0x1f85, 0, 0x42),SCE(0x1f8d, 1, 0x2),SCE(0x1f86, 0, 0x42), +SCE(0x1f8e, 1, 0x2),SCE(0x1f87, 0, 0x42),SCE(0x1f8f, 1, 0x2),SCE(0xa790, 0, 0x82), +SCE(0xa791, 1, 0x42),SCE(0xa792, 0, 0x82),SCE(0xa793, 1, 0x42),SCE(0x1f90, 0, 0x42), +SCE(0x1f98, 1, 0x2),SCE(0x1f91, 0, 0x42),SCE(0x1f99, 1, 0x2),SCE(0x1f92, 0, 0x42), +SCE(0x1f9a, 1, 0x2),SCE(0x1f93, 0, 0x42),SCE(0x1f9b, 1, 0x2),SCE(0x1f94, 0, 0x42), +SCE(0x1f9c, 1, 0x2),SCE(0x1f95, 0, 0x42),SCE(0x1f9d, 1, 0x2),SCE(0x1f96, 0, 0x42), +SCE(0x1f9e, 1, 0x2),SCE(0x1f97, 0, 0x42),SCE(0x1f9f, 1, 0x2),SCE(0xa7a0, 0, 0x82), +SCE(0xa7a1, 1, 0x42),SCE(0xa7a2, 0, 0x82),SCE(0xa7a3, 1, 0x42),SCE(0xa7a4, 0, 0x82), +SCE(0xa7a5, 1, 0x42),SCE(0xa7a6, 0, 0x82),SCE(0xa7a7, 1, 0x42),SCE(0x1fa0, 0, 0x42), +SCE(0x1fa8, 1, 0x2),SCE(0x1fa1, 0, 0x42),SCE(0x1fa9, 1, 0x2),SCE(0x1fa2, 0, 0x42), +SCE(0x1faa, 1, 0x2),SCE(0x1fa3, 0, 0x42),SCE(0x1fab, 1, 0x2),SCE(0x1fa4, 0, 0x42), +SCE(0x1fac, 1, 0x2),SCE(0x1fa5, 0, 0x42),SCE(0x1fad, 1, 0x2),SCE(0x1fa6, 0, 0x42), +SCE(0x1fae, 1, 0x2),SCE(0x1fa7, 0, 0x42),SCE(0x1faf, 1, 0x2),SCE(0x1fb0, 0, 0x42), +SCE(0x1fb8, 1, 0x82),SCE(0x1fb1, 0, 0x42),SCE(0x1fb9, 1, 0x82),SCE(0x1f70, 0, 0x42), +SCE(0x1fba, 1, 0x82),SCE(0x1f71, 0, 0x42),SCE(0x1fbb, 1, 0x82),SCE(0x1fb3, 0, 0x42), +SCE(0x1fbc, 1, 0x2),SCE(0x0345, 0, 0x44),SCE(0x0399, 1, 0x84),SCE(0x03b9, 2, 0x44), +SCE(0x1fbe, 3, 0x44),SCE(0x1f72, 0, 0x42),SCE(0x1fc8, 1, 0x82),SCE(0x1f73, 0, 0x42), +SCE(0x1fc9, 1, 0x82),SCE(0x1f74, 0, 0x42),SCE(0x1fca, 1, 0x82),SCE(0x1f75, 0, 0x42), +SCE(0x1fcb, 1, 0x82),SCE(0x1fc3, 0, 0x42),SCE(0x1fcc, 1, 0x2),SCE(0x1fd0, 0, 0x42), +SCE(0x1fd8, 1, 0x82),SCE(0x1fd1, 0, 0x42),SCE(0x1fd9, 1, 0x82),SCE(0x1f76, 0, 0x42), +SCE(0x1fda, 1, 0x82),SCE(0x1f77, 0, 0x42),SCE(0x1fdb, 1, 0x82),SCE(0x10427, 0, 0x82), +SCE(0x1044f, 1, 0x42),SCE(0x1fe0, 0, 0x42),SCE(0x1fe8, 1, 0x82),SCE(0x1fe1, 0, 0x42), +SCE(0x1fe9, 1, 0x82),SCE(0x1f7a, 0, 0x42),SCE(0x1fea, 1, 0x82),SCE(0x1f7b, 0, 0x42), +SCE(0x1feb, 1, 0x82),SCE(0x1fe5, 0, 0x42),SCE(0x1fec, 1, 0x82),SCE(0x10401, 0, 0x82), +SCE(0x10429, 1, 0x42),SCE(0x1040c, 0, 0x82),SCE(0x10434, 1, 0x42),SCE(0x1f78, 0, 0x42), +SCE(0x1ff8, 1, 0x82),SCE(0x1f79, 0, 0x42),SCE(0x1ff9, 1, 0x82),SCE(0x1f7c, 0, 0x42), +SCE(0x1ffa, 1, 0x82),SCE(0x1f7d, 0, 0x42),SCE(0x1ffb, 1, 0x82),SCE(0x1ff3, 0, 0x42), +SCE(0x1ffc, 1, 0x2),SCE(0x24c0, 0, 0x82),SCE(0x24da, 1, 0x42),]; +return t; +} +@property immutable(FullCaseEntry[]) fullCaseTable() +{ +alias FCE = FullCaseEntry; +static immutable FCE[] t = [ +FCE("Ⰰ", 0, 2, 1), +FCE("ⰰ", 1, 2, 1),FCE("Ⓝ", 0, 2, 1),FCE("ⓝ", 1, 2, 1),FCE("Ⰱ", 0, 2, 1), +FCE("ⰱ", 1, 2, 1),FCE("Ⱍ", 0, 2, 1),FCE("ⱍ", 1, 2, 1),FCE("Ⰲ", 0, 2, 1), +FCE("ⰲ", 1, 2, 1),FCE("Ⰳ", 0, 2, 1),FCE("ⰳ", 1, 2, 1),FCE("Ⰴ", 0, 2, 1), +FCE("ⰴ", 1, 2, 1),FCE("Ⰵ", 0, 2, 1),FCE("ⰵ", 1, 2, 1),FCE("Ⰶ", 0, 2, 1), +FCE("ⰶ", 1, 2, 1),FCE("𐐀", 0, 2, 1),FCE("𐐨", 1, 2, 1),FCE("Ⳃ", 0, 2, 1), +FCE("ⳃ", 1, 2, 1),FCE("Ⰷ", 0, 2, 1),FCE("ⰷ", 1, 2, 1),FCE("Ⰸ", 0, 2, 1), +FCE("ⰸ", 1, 2, 1),FCE("Ⰹ", 0, 2, 1),FCE("ⰹ", 1, 2, 1),FCE("Ⰺ", 0, 2, 1), +FCE("ⰺ", 1, 2, 1),FCE("Ꚍ", 0, 2, 1),FCE("ꚍ", 1, 2, 1),FCE("A", 0, 2, 1), +FCE("a", 1, 2, 1),FCE("B", 0, 2, 1),FCE("b", 1, 2, 1),FCE("C", 0, 2, 1), +FCE("c", 1, 2, 1),FCE("D", 0, 2, 1),FCE("d", 1, 2, 1),FCE("E", 0, 2, 1), +FCE("e", 1, 2, 1),FCE("F", 0, 2, 1),FCE("f", 1, 2, 1),FCE("G", 0, 2, 1), +FCE("g", 1, 2, 1),FCE("H", 0, 2, 1),FCE("h", 1, 2, 1),FCE("I", 0, 2, 1), +FCE("i", 1, 2, 1),FCE("J", 0, 2, 1),FCE("j", 1, 2, 1),FCE("K", 0, 3, 1), +FCE("k", 1, 3, 1),FCE("K", 2, 3, 1),FCE("L", 0, 2, 1),FCE("l", 1, 2, 1), +FCE("M", 0, 2, 1),FCE("m", 1, 2, 1),FCE("N", 0, 2, 1),FCE("n", 1, 2, 1), +FCE("O", 0, 2, 1),FCE("o", 1, 2, 1),FCE("P", 0, 2, 1),FCE("p", 1, 2, 1), +FCE("Q", 0, 2, 1),FCE("q", 1, 2, 1),FCE("R", 0, 2, 1),FCE("r", 1, 2, 1), +FCE("S", 0, 3, 1),FCE("s", 1, 3, 1),FCE("ſ", 2, 3, 1),FCE("T", 0, 2, 1), +FCE("t", 1, 2, 1),FCE("U", 0, 2, 1),FCE("u", 1, 2, 1),FCE("V", 0, 2, 1), +FCE("v", 1, 2, 1),FCE("W", 0, 2, 1),FCE("w", 1, 2, 1),FCE("X", 0, 2, 1), +FCE("x", 1, 2, 1),FCE("Y", 0, 2, 1),FCE("y", 1, 2, 1),FCE("Z", 0, 2, 1), +FCE("z", 1, 2, 1),FCE("Ⰿ", 0, 2, 1),FCE("ⰿ", 1, 2, 1),FCE("Ⱀ", 0, 2, 1), +FCE("ⱀ", 1, 2, 1),FCE("𐐂", 0, 2, 1),FCE("𐐪", 1, 2, 1),FCE("Ⳅ", 0, 2, 1), +FCE("ⳅ", 1, 2, 1),FCE("Ⅶ", 0, 2, 1),FCE("ⅶ", 1, 2, 1),FCE("Ⱁ", 0, 2, 1), +FCE("ⱁ", 1, 2, 1),FCE("Ⱂ", 0, 2, 1),FCE("ⱂ", 1, 2, 1),FCE("Ⅸ", 0, 2, 1), +FCE("ⅸ", 1, 2, 1),FCE("Ⱃ", 0, 2, 1),FCE("ⱃ", 1, 2, 1),FCE("Ꚃ", 0, 2, 1), +FCE("ꚃ", 1, 2, 1),FCE("Ⱄ", 0, 2, 1),FCE("ⱄ", 1, 2, 1),FCE("Ⅺ", 0, 2, 1), +FCE("ⅺ", 1, 2, 1),FCE("Ⓡ", 0, 2, 1),FCE("ⓡ", 1, 2, 1),FCE("Ⱅ", 0, 2, 1), +FCE("ⱅ", 1, 2, 1),FCE("𐐃", 0, 2, 1),FCE("𐐫", 1, 2, 1),FCE("Ⱆ", 0, 2, 1), +FCE("ⱆ", 1, 2, 1),FCE("Ⅼ", 0, 2, 1),FCE("ⅼ", 1, 2, 1),FCE("Ⱇ", 0, 2, 1), +FCE("ⱇ", 1, 2, 1),FCE("X", 0, 2, 1),FCE("x", 1, 2, 1),FCE("Ⱈ", 0, 2, 1), +FCE("ⱈ", 1, 2, 1),FCE("Ⅾ", 0, 2, 1),FCE("ⅾ", 1, 2, 1),FCE("Ⱉ", 0, 2, 1), +FCE("ⱉ", 1, 2, 1),FCE("Ⱊ", 0, 2, 1),FCE("ⱊ", 1, 2, 1),FCE("Ⱎ", 0, 2, 1), +FCE("ⱎ", 1, 2, 1),FCE("Ⴀ", 0, 2, 1),FCE("ⴀ", 1, 2, 1),FCE("Ⴁ", 0, 2, 1), +FCE("ⴁ", 1, 2, 1),FCE("Ⴂ", 0, 2, 1),FCE("ⴂ", 1, 2, 1),FCE("Ⴃ", 0, 2, 1), +FCE("ⴃ", 1, 2, 1),FCE("Ⴄ", 0, 2, 1),FCE("ⴄ", 1, 2, 1),FCE("Ⴅ", 0, 2, 1), +FCE("ⴅ", 1, 2, 1),FCE("Ⴆ", 0, 2, 1),FCE("ⴆ", 1, 2, 1),FCE("Ⴇ", 0, 2, 1), +FCE("ⴇ", 1, 2, 1),FCE("Ⴈ", 0, 2, 1),FCE("ⴈ", 1, 2, 1),FCE("Ⴉ", 0, 2, 1), +FCE("ⴉ", 1, 2, 1),FCE("Ⴊ", 0, 2, 1),FCE("ⴊ", 1, 2, 1),FCE("Ⴋ", 0, 2, 1), +FCE("ⴋ", 1, 2, 1),FCE("Ⴌ", 0, 2, 1),FCE("ⴌ", 1, 2, 1),FCE("Ⴍ", 0, 2, 1), +FCE("ⴍ", 1, 2, 1),FCE("Ⴎ", 0, 2, 1),FCE("ⴎ", 1, 2, 1),FCE("Ⴏ", 0, 2, 1), +FCE("ⴏ", 1, 2, 1),FCE("Ⴐ", 0, 2, 1),FCE("ⴐ", 1, 2, 1),FCE("Ⴑ", 0, 2, 1), +FCE("ⴑ", 1, 2, 1),FCE("Ⴒ", 0, 2, 1),FCE("ⴒ", 1, 2, 1),FCE("Ⴓ", 0, 2, 1), +FCE("ⴓ", 1, 2, 1),FCE("Ⴔ", 0, 2, 1),FCE("ⴔ", 1, 2, 1),FCE("Ⴕ", 0, 2, 1), +FCE("ⴕ", 1, 2, 1),FCE("Ⴖ", 0, 2, 1),FCE("ⴖ", 1, 2, 1),FCE("Ⴗ", 0, 2, 1), +FCE("ⴗ", 1, 2, 1),FCE("Ⴘ", 0, 2, 1),FCE("ⴘ", 1, 2, 1),FCE("Ⴙ", 0, 2, 1), +FCE("ⴙ", 1, 2, 1),FCE("Ⴚ", 0, 2, 1),FCE("ⴚ", 1, 2, 1),FCE("Ⴛ", 0, 2, 1), +FCE("ⴛ", 1, 2, 1),FCE("Ⴜ", 0, 2, 1),FCE("ⴜ", 1, 2, 1),FCE("Ⴝ", 0, 2, 1), +FCE("ⴝ", 1, 2, 1),FCE("Ⴞ", 0, 2, 1),FCE("ⴞ", 1, 2, 1),FCE("Ⴟ", 0, 2, 1), +FCE("ⴟ", 1, 2, 1),FCE("À", 0, 2, 1),FCE("à", 1, 2, 1),FCE("Á", 0, 2, 1), +FCE("á", 1, 2, 1),FCE("Ⴢ", 0, 2, 1),FCE("ⴢ", 1, 2, 1),FCE("Ã", 0, 2, 1), +FCE("ã", 1, 2, 1),FCE("Ⴤ", 0, 2, 1),FCE("ⴤ", 1, 2, 1),FCE("Å", 0, 3, 1), +FCE("å", 1, 3, 1),FCE("Å", 2, 3, 1),FCE("Æ", 0, 2, 1),FCE("æ", 1, 2, 1), +FCE("Ç", 0, 2, 1),FCE("ç", 1, 2, 1),FCE("È", 0, 2, 1),FCE("è", 1, 2, 1), +FCE("É", 0, 2, 1),FCE("é", 1, 2, 1),FCE("Ê", 0, 2, 1),FCE("ê", 1, 2, 1), +FCE("Ë", 0, 2, 1),FCE("ë", 1, 2, 1),FCE("Ì", 0, 2, 1),FCE("ì", 1, 2, 1), +FCE("Í", 0, 2, 1),FCE("í", 1, 2, 1),FCE("Î", 0, 2, 1),FCE("î", 1, 2, 1), +FCE("Ï", 0, 2, 1),FCE("ï", 1, 2, 1),FCE("Ð", 0, 2, 1),FCE("ð", 1, 2, 1), +FCE("Ñ", 0, 2, 1),FCE("ñ", 1, 2, 1),FCE("Ò", 0, 2, 1),FCE("ò", 1, 2, 1), +FCE("Ó", 0, 2, 1),FCE("ó", 1, 2, 1),FCE("Ô", 0, 2, 1),FCE("ô", 1, 2, 1), +FCE("Õ", 0, 2, 1),FCE("õ", 1, 2, 1),FCE("Ö", 0, 2, 1),FCE("ö", 1, 2, 1), +FCE("Ø", 0, 2, 1),FCE("ø", 1, 2, 1),FCE("Ù", 0, 2, 1),FCE("ù", 1, 2, 1), +FCE("Ú", 0, 2, 1),FCE("ú", 1, 2, 1),FCE("Û", 0, 2, 1),FCE("û", 1, 2, 1), +FCE("Ü", 0, 2, 1),FCE("ü", 1, 2, 1),FCE("Ý", 0, 2, 1),FCE("ý", 1, 2, 1), +FCE("Þ", 0, 2, 1),FCE("þ", 1, 2, 1),FCE("ß", 0, 3, 1),FCE("ẞ", 1, 3, 1), +FCE("ss", 2, 3, 2),FCE("Ⱖ", 0, 2, 1),FCE("ⱖ", 1, 2, 1),FCE("Ⱗ", 0, 2, 1), +FCE("ⱗ", 1, 2, 1),FCE("Ⱘ", 0, 2, 1),FCE("ⱘ", 1, 2, 1),FCE("𐐆", 0, 2, 1), +FCE("𐐮", 1, 2, 1),FCE("𐐏", 0, 2, 1),FCE("𐐷", 1, 2, 1),FCE("Ⓥ", 0, 2, 1), +FCE("ⓥ", 1, 2, 1),FCE("Ⱙ", 0, 2, 1),FCE("ⱙ", 1, 2, 1),FCE("𐐇", 0, 2, 1), +FCE("𐐯", 1, 2, 1),FCE("Ⱚ", 0, 2, 1),FCE("ⱚ", 1, 2, 1),FCE("Ā", 0, 2, 1), +FCE("ā", 1, 2, 1),FCE("Ă", 0, 2, 1),FCE("ă", 1, 2, 1),FCE("Ⱛ", 0, 2, 1), +FCE("ⱛ", 1, 2, 1),FCE("Ą", 0, 2, 1),FCE("ą", 1, 2, 1),FCE("Ć", 0, 2, 1), +FCE("ć", 1, 2, 1),FCE("Ĉ", 0, 2, 1),FCE("ĉ", 1, 2, 1),FCE("Ⱜ", 0, 2, 1), +FCE("ⱜ", 1, 2, 1),FCE("Ċ", 0, 2, 1),FCE("ċ", 1, 2, 1),FCE("Č", 0, 2, 1), +FCE("č", 1, 2, 1),FCE("Ď", 0, 2, 1),FCE("ď", 1, 2, 1),FCE("Ⱝ", 0, 2, 1), +FCE("ⱝ", 1, 2, 1),FCE("Đ", 0, 2, 1),FCE("đ", 1, 2, 1),FCE("Ē", 0, 2, 1), +FCE("ē", 1, 2, 1),FCE("Ĕ", 0, 2, 1),FCE("ĕ", 1, 2, 1),FCE("Ⱞ", 0, 2, 1), +FCE("ⱞ", 1, 2, 1),FCE("Ė", 0, 2, 1),FCE("ė", 1, 2, 1),FCE("Ę", 0, 2, 1), +FCE("ę", 1, 2, 1),FCE("Ě", 0, 2, 1),FCE("ě", 1, 2, 1),FCE("Ĝ", 0, 2, 1), +FCE("ĝ", 1, 2, 1),FCE("Ğ", 0, 2, 1),FCE("ğ", 1, 2, 1),FCE("Ġ", 0, 2, 1), +FCE("ġ", 1, 2, 1),FCE("Ģ", 0, 2, 1),FCE("ģ", 1, 2, 1),FCE("K", 0, 2, 1), +FCE("k", 1, 2, 1),FCE("Ĥ", 0, 2, 1),FCE("ĥ", 1, 2, 1),FCE("Ħ", 0, 2, 1), +FCE("ħ", 1, 2, 1),FCE("Ĩ", 0, 2, 1),FCE("ĩ", 1, 2, 1),FCE("Ī", 0, 2, 1), +FCE("ī", 1, 2, 1),FCE("Å", 0, 3, 1),FCE("å", 1, 3, 1),FCE("Å", 2, 3, 1), +FCE("Ĭ", 0, 2, 1),FCE("ĭ", 1, 2, 1),FCE("Į", 0, 2, 1),FCE("į", 1, 2, 1), +FCE("İ", 0, 2, 1),FCE("i̇", 1, 2, 2),FCE("IJ", 0, 2, 1),FCE("ij", 1, 2, 1), +FCE("Ĵ", 0, 2, 1),FCE("ĵ", 1, 2, 1),FCE("Ķ", 0, 2, 1),FCE("ķ", 1, 2, 1), +FCE("Ĺ", 0, 2, 1),FCE("ĺ", 1, 2, 1),FCE("Ļ", 0, 2, 1),FCE("ļ", 1, 2, 1), +FCE("Ⳟ", 0, 2, 1),FCE("ⳟ", 1, 2, 1),FCE("Ľ", 0, 2, 1),FCE("ľ", 1, 2, 1), +FCE("Ŀ", 0, 2, 1),FCE("ŀ", 1, 2, 1),FCE("Ł", 0, 2, 1),FCE("ł", 1, 2, 1), +FCE("Ń", 0, 2, 1),FCE("ń", 1, 2, 1),FCE("Ņ", 0, 2, 1),FCE("ņ", 1, 2, 1), +FCE("Ň", 0, 2, 1),FCE("ň", 1, 2, 1),FCE("ʼn", 0, 2, 1),FCE("ʼn", 1, 2, 2), +FCE("Ŋ", 0, 2, 1),FCE("ŋ", 1, 2, 1),FCE("Ō", 0, 2, 1),FCE("ō", 1, 2, 1), +FCE("Ŏ", 0, 2, 1),FCE("ŏ", 1, 2, 1),FCE("Ő", 0, 2, 1),FCE("ő", 1, 2, 1), +FCE("Œ", 0, 2, 1),FCE("œ", 1, 2, 1),FCE("Ŕ", 0, 2, 1),FCE("ŕ", 1, 2, 1), +FCE("Ŗ", 0, 2, 1),FCE("ŗ", 1, 2, 1),FCE("Ř", 0, 2, 1),FCE("ř", 1, 2, 1), +FCE("Ś", 0, 2, 1),FCE("ś", 1, 2, 1),FCE("Ŝ", 0, 2, 1),FCE("ŝ", 1, 2, 1), +FCE("Ş", 0, 2, 1),FCE("ş", 1, 2, 1),FCE("Š", 0, 2, 1),FCE("š", 1, 2, 1), +FCE("Ⅱ", 0, 2, 1),FCE("ⅱ", 1, 2, 1),FCE("Ţ", 0, 2, 1),FCE("ţ", 1, 2, 1), +FCE("Ⅳ", 0, 2, 1),FCE("ⅳ", 1, 2, 1),FCE("Ť", 0, 2, 1),FCE("ť", 1, 2, 1), +FCE("Ⅵ", 0, 2, 1),FCE("ⅵ", 1, 2, 1),FCE("Ŧ", 0, 2, 1),FCE("ŧ", 1, 2, 1), +FCE("Ⅷ", 0, 2, 1),FCE("ⅷ", 1, 2, 1),FCE("Ũ", 0, 2, 1),FCE("ũ", 1, 2, 1), +FCE("Ⅹ", 0, 2, 1),FCE("ⅹ", 1, 2, 1),FCE("Ū", 0, 2, 1),FCE("ū", 1, 2, 1), +FCE("Ⅻ", 0, 2, 1),FCE("ⅻ", 1, 2, 1),FCE("Ŭ", 0, 2, 1),FCE("ŭ", 1, 2, 1), +FCE("Ⅽ", 0, 2, 1),FCE("ⅽ", 1, 2, 1),FCE("Ů", 0, 2, 1),FCE("ů", 1, 2, 1), +FCE("Ⅿ", 0, 2, 1),FCE("ⅿ", 1, 2, 1),FCE("Ű", 0, 2, 1),FCE("ű", 1, 2, 1), +FCE("Ⳍ", 0, 2, 1),FCE("ⳍ", 1, 2, 1),FCE("Ų", 0, 2, 1),FCE("ų", 1, 2, 1), +FCE("Ŵ", 0, 2, 1),FCE("ŵ", 1, 2, 1),FCE("Ŷ", 0, 2, 1),FCE("ŷ", 1, 2, 1), +FCE("ÿ", 0, 2, 1),FCE("Ÿ", 1, 2, 1),FCE("Ź", 0, 2, 1),FCE("ź", 1, 2, 1), +FCE("Ż", 0, 2, 1),FCE("ż", 1, 2, 1),FCE("Ž", 0, 2, 1),FCE("ž", 1, 2, 1), +FCE("S", 0, 3, 1),FCE("s", 1, 3, 1),FCE("ſ", 2, 3, 1),FCE("Ɓ", 0, 2, 1), +FCE("ɓ", 1, 2, 1),FCE("Ƃ", 0, 2, 1),FCE("ƃ", 1, 2, 1),FCE("Ↄ", 0, 2, 1), +FCE("ↄ", 1, 2, 1),FCE("Ƅ", 0, 2, 1),FCE("ƅ", 1, 2, 1),FCE("Ɔ", 0, 2, 1), +FCE("ɔ", 1, 2, 1),FCE("Ƈ", 0, 2, 1),FCE("ƈ", 1, 2, 1),FCE("Ɖ", 0, 2, 1), +FCE("ɖ", 1, 2, 1),FCE("Ɗ", 0, 2, 1),FCE("ɗ", 1, 2, 1),FCE("Ƌ", 0, 2, 1), +FCE("ƌ", 1, 2, 1),FCE("Ǝ", 0, 2, 1),FCE("ǝ", 1, 2, 1),FCE("Ə", 0, 2, 1), +FCE("ə", 1, 2, 1),FCE("Ɛ", 0, 2, 1),FCE("ɛ", 1, 2, 1),FCE("Ƒ", 0, 2, 1), +FCE("ƒ", 1, 2, 1),FCE("Ɠ", 0, 2, 1),FCE("ɠ", 1, 2, 1),FCE("Ɣ", 0, 2, 1), +FCE("ɣ", 1, 2, 1),FCE("Ɩ", 0, 2, 1),FCE("ɩ", 1, 2, 1),FCE("Ɨ", 0, 2, 1), +FCE("ɨ", 1, 2, 1),FCE("Ƙ", 0, 2, 1),FCE("ƙ", 1, 2, 1),FCE("Ɯ", 0, 2, 1), +FCE("ɯ", 1, 2, 1),FCE("Ɲ", 0, 2, 1),FCE("ɲ", 1, 2, 1),FCE("Ꙋ", 0, 2, 1), +FCE("ꙋ", 1, 2, 1),FCE("Ɵ", 0, 2, 1),FCE("ɵ", 1, 2, 1),FCE("Ơ", 0, 2, 1), +FCE("ơ", 1, 2, 1),FCE("Ƣ", 0, 2, 1),FCE("ƣ", 1, 2, 1),FCE("Ƥ", 0, 2, 1), +FCE("ƥ", 1, 2, 1),FCE("Ʀ", 0, 2, 1),FCE("ʀ", 1, 2, 1),FCE("Ƨ", 0, 2, 1), +FCE("ƨ", 1, 2, 1),FCE("Ʃ", 0, 2, 1),FCE("ʃ", 1, 2, 1),FCE("Ƭ", 0, 2, 1), +FCE("ƭ", 1, 2, 1),FCE("Ʈ", 0, 2, 1),FCE("ʈ", 1, 2, 1),FCE("Ư", 0, 2, 1), +FCE("ư", 1, 2, 1),FCE("Ʊ", 0, 2, 1),FCE("ʊ", 1, 2, 1),FCE("Ʋ", 0, 2, 1), +FCE("ʋ", 1, 2, 1),FCE("Ƴ", 0, 2, 1),FCE("ƴ", 1, 2, 1),FCE("Ƶ", 0, 2, 1), +FCE("ƶ", 1, 2, 1),FCE("Ʒ", 0, 2, 1),FCE("ʒ", 1, 2, 1),FCE("Ƹ", 0, 2, 1), +FCE("ƹ", 1, 2, 1),FCE("Ƽ", 0, 2, 1),FCE("ƽ", 1, 2, 1),FCE("DŽ", 0, 3, 1), +FCE("Dž", 1, 3, 1),FCE("dž", 2, 3, 1),FCE("DŽ", 0, 3, 1),FCE("Dž", 1, 3, 1), +FCE("dž", 2, 3, 1),FCE("LJ", 0, 3, 1),FCE("Lj", 1, 3, 1),FCE("lj", 2, 3, 1), +FCE("LJ", 0, 3, 1),FCE("Lj", 1, 3, 1),FCE("lj", 2, 3, 1),FCE("NJ", 0, 3, 1), +FCE("Nj", 1, 3, 1),FCE("nj", 2, 3, 1),FCE("NJ", 0, 3, 1),FCE("Nj", 1, 3, 1), +FCE("nj", 2, 3, 1),FCE("Ǎ", 0, 2, 1),FCE("ǎ", 1, 2, 1),FCE("Ǐ", 0, 2, 1), +FCE("ǐ", 1, 2, 1),FCE("Ǒ", 0, 2, 1),FCE("ǒ", 1, 2, 1),FCE("Ǔ", 0, 2, 1), +FCE("ǔ", 1, 2, 1),FCE("Ǖ", 0, 2, 1),FCE("ǖ", 1, 2, 1),FCE("Ǘ", 0, 2, 1), +FCE("ǘ", 1, 2, 1),FCE("Ǚ", 0, 2, 1),FCE("ǚ", 1, 2, 1),FCE("Ǜ", 0, 2, 1), +FCE("ǜ", 1, 2, 1),FCE("Ǟ", 0, 2, 1),FCE("ǟ", 1, 2, 1),FCE("V", 0, 2, 1), +FCE("v", 1, 2, 1),FCE("Ǡ", 0, 2, 1),FCE("ǡ", 1, 2, 1),FCE("Ǣ", 0, 2, 1), +FCE("ǣ", 1, 2, 1),FCE("Ǥ", 0, 2, 1),FCE("ǥ", 1, 2, 1),FCE("Ǧ", 0, 2, 1), +FCE("ǧ", 1, 2, 1),FCE("Ǩ", 0, 2, 1),FCE("ǩ", 1, 2, 1),FCE("Ǫ", 0, 2, 1), +FCE("ǫ", 1, 2, 1),FCE("Ǭ", 0, 2, 1),FCE("ǭ", 1, 2, 1),FCE("Ǯ", 0, 2, 1), +FCE("ǯ", 1, 2, 1),FCE("ǰ", 0, 2, 1),FCE("ǰ", 1, 2, 2),FCE("DZ", 0, 3, 1), +FCE("Dz", 1, 3, 1),FCE("dz", 2, 3, 1),FCE("DZ", 0, 3, 1),FCE("Dz", 1, 3, 1), +FCE("dz", 2, 3, 1),FCE("Ǵ", 0, 2, 1),FCE("ǵ", 1, 2, 1),FCE("ƕ", 0, 2, 1), +FCE("Ƕ", 1, 2, 1),FCE("ƿ", 0, 2, 1),FCE("Ƿ", 1, 2, 1),FCE("Ǹ", 0, 2, 1), +FCE("ǹ", 1, 2, 1),FCE("𐐝", 0, 2, 1),FCE("𐑅", 1, 2, 1),FCE("Ǻ", 0, 2, 1), +FCE("ǻ", 1, 2, 1),FCE("Ǽ", 0, 2, 1),FCE("ǽ", 1, 2, 1),FCE("Ǿ", 0, 2, 1), +FCE("ǿ", 1, 2, 1),FCE("Ȁ", 0, 2, 1),FCE("ȁ", 1, 2, 1),FCE("Ȃ", 0, 2, 1), +FCE("ȃ", 1, 2, 1),FCE("Ȅ", 0, 2, 1),FCE("ȅ", 1, 2, 1),FCE("Ȇ", 0, 2, 1), +FCE("ȇ", 1, 2, 1),FCE("fi", 0, 2, 1),FCE("fi", 1, 2, 2),FCE("Ȉ", 0, 2, 1), +FCE("ȉ", 1, 2, 1),FCE("Ȋ", 0, 2, 1),FCE("ȋ", 1, 2, 1),FCE("Ȍ", 0, 2, 1), +FCE("ȍ", 1, 2, 1),FCE("Ȏ", 0, 2, 1),FCE("ȏ", 1, 2, 1),FCE("Ȑ", 0, 2, 1), +FCE("ȑ", 1, 2, 1),FCE("Ȓ", 0, 2, 1),FCE("ȓ", 1, 2, 1),FCE("Ȕ", 0, 2, 1), +FCE("ȕ", 1, 2, 1),FCE("Ȗ", 0, 2, 1),FCE("ȗ", 1, 2, 1),FCE("Ș", 0, 2, 1), +FCE("ș", 1, 2, 1),FCE("Ț", 0, 2, 1),FCE("ț", 1, 2, 1),FCE("Ȝ", 0, 2, 1), +FCE("ȝ", 1, 2, 1),FCE("Ȟ", 0, 2, 1),FCE("ȟ", 1, 2, 1),FCE("ƞ", 0, 2, 1), +FCE("Ƞ", 1, 2, 1),FCE("Ȣ", 0, 2, 1),FCE("ȣ", 1, 2, 1),FCE("Ȥ", 0, 2, 1), +FCE("ȥ", 1, 2, 1),FCE("Ȧ", 0, 2, 1),FCE("ȧ", 1, 2, 1),FCE("Ȩ", 0, 2, 1), +FCE("ȩ", 1, 2, 1),FCE("Ꝗ", 0, 2, 1),FCE("ꝗ", 1, 2, 1),FCE("Ȫ", 0, 2, 1), +FCE("ȫ", 1, 2, 1),FCE("Ȭ", 0, 2, 1),FCE("ȭ", 1, 2, 1),FCE("Ȯ", 0, 2, 1), +FCE("ȯ", 1, 2, 1),FCE("Ȱ", 0, 2, 1),FCE("ȱ", 1, 2, 1),FCE("Ȳ", 0, 2, 1), +FCE("ȳ", 1, 2, 1),FCE("Ꚅ", 0, 2, 1),FCE("ꚅ", 1, 2, 1),FCE("Ⱥ", 0, 2, 1), +FCE("ⱥ", 1, 2, 1),FCE("Ȼ", 0, 2, 1),FCE("ȼ", 1, 2, 1),FCE("ƚ", 0, 2, 1), +FCE("Ƚ", 1, 2, 1),FCE("Ⱦ", 0, 2, 1),FCE("ⱦ", 1, 2, 1),FCE("Ɂ", 0, 2, 1), +FCE("ɂ", 1, 2, 1),FCE("𐐒", 0, 2, 1),FCE("𐐺", 1, 2, 1),FCE("ƀ", 0, 2, 1), +FCE("Ƀ", 1, 2, 1),FCE("Ʉ", 0, 2, 1),FCE("ʉ", 1, 2, 1),FCE("Ʌ", 0, 2, 1), +FCE("ʌ", 1, 2, 1),FCE("Ɇ", 0, 2, 1),FCE("ɇ", 1, 2, 1),FCE("Ɉ", 0, 2, 1), +FCE("ɉ", 1, 2, 1),FCE("Ɋ", 0, 2, 1),FCE("ɋ", 1, 2, 1),FCE("Ɍ", 0, 2, 1), +FCE("ɍ", 1, 2, 1),FCE("Ⱋ", 0, 2, 1),FCE("ⱋ", 1, 2, 1),FCE("Ɏ", 0, 2, 1), +FCE("ɏ", 1, 2, 1),FCE("𐐊", 0, 2, 1),FCE("𐐲", 1, 2, 1),FCE("Ⅰ", 0, 2, 1), +FCE("ⅰ", 1, 2, 1),FCE("Ꚓ", 0, 2, 1),FCE("ꚓ", 1, 2, 1),FCE("ɽ", 0, 2, 1), +FCE("Ɽ", 1, 2, 1),FCE("𐐐", 0, 2, 1),FCE("𐐸", 1, 2, 1),FCE("Ⱑ", 0, 2, 1), +FCE("ⱑ", 1, 2, 1),FCE("Ⱪ", 0, 2, 1),FCE("ⱪ", 1, 2, 1),FCE("𐐉", 0, 2, 1), +FCE("𐐱", 1, 2, 1),FCE("𐐔", 0, 2, 1),FCE("𐐼", 1, 2, 1),FCE("ﬕ", 0, 2, 1), +FCE("մի", 1, 2, 2),FCE("Ⅲ", 0, 2, 1),FCE("ⅲ", 1, 2, 1),FCE("𐐞", 0, 2, 1), +FCE("𐑆", 1, 2, 1),FCE("ɱ", 0, 2, 1),FCE("Ɱ", 1, 2, 1),FCE("𐐕", 0, 2, 1), +FCE("𐐽", 1, 2, 1),FCE("ɒ", 0, 2, 1),FCE("Ɒ", 1, 2, 1),FCE("Ⱳ", 0, 2, 1), +FCE("ⱳ", 1, 2, 1),FCE("Ⰻ", 0, 2, 1),FCE("ⰻ", 1, 2, 1),FCE("𐐖", 0, 2, 1), +FCE("𐐾", 1, 2, 1),FCE("Ꚗ", 0, 2, 1),FCE("ꚗ", 1, 2, 1),FCE("Ⱶ", 0, 2, 1), +FCE("ⱶ", 1, 2, 1),FCE("Ⅴ", 0, 2, 1),FCE("ⅴ", 1, 2, 1),FCE("Ꙁ", 0, 2, 1), +FCE("ꙁ", 1, 2, 1),FCE("B", 0, 2, 1),FCE("b", 1, 2, 1),FCE("Ⰼ", 0, 2, 1), +FCE("ⰼ", 1, 2, 1),FCE("𐐗", 0, 2, 1),FCE("𐐿", 1, 2, 1),FCE("D", 0, 2, 1), +FCE("d", 1, 2, 1),FCE("E", 0, 2, 1),FCE("e", 1, 2, 1),FCE("F", 0, 2, 1), +FCE("f", 1, 2, 1),FCE("Ⰽ", 0, 2, 1),FCE("ⰽ", 1, 2, 1),FCE("Ⓛ", 0, 2, 1), +FCE("ⓛ", 1, 2, 1),FCE("Ꜩ", 0, 2, 1),FCE("ꜩ", 1, 2, 1),FCE("ȿ", 0, 2, 1), +FCE("Ȿ", 1, 2, 1),FCE("𐐑", 0, 2, 1),FCE("𐐹", 1, 2, 1),FCE("I", 0, 2, 1), +FCE("i", 1, 2, 1),FCE("𐐋", 0, 2, 1),FCE("𐐳", 1, 2, 1),FCE("Ꜫ", 0, 2, 1), +FCE("ꜫ", 1, 2, 1),FCE("ff", 0, 2, 1),FCE("ff", 1, 2, 2),FCE("Ⲁ", 0, 2, 1), +FCE("ⲁ", 1, 2, 1),FCE("fl", 0, 2, 1),FCE("fl", 1, 2, 2),FCE("ffi", 0, 2, 1), +FCE("ffi", 1, 2, 3),FCE("ffl", 0, 2, 1),FCE("ffl", 1, 2, 3),FCE("ſt", 0, 3, 1), +FCE("st", 1, 3, 1),FCE("st", 2, 3, 2),FCE("ſt", 0, 3, 1),FCE("st", 1, 3, 1), +FCE("st", 2, 3, 2),FCE("Ꜭ", 0, 2, 1),FCE("ꜭ", 1, 2, 1),FCE("Ⰾ", 0, 2, 1), +FCE("ⰾ", 1, 2, 1),FCE("M", 0, 2, 1),FCE("m", 1, 2, 1),FCE("ﬓ", 0, 2, 1), +FCE("մն", 1, 2, 2),FCE("ﬔ", 0, 2, 1),FCE("մե", 1, 2, 2),FCE("Ꜯ", 0, 2, 1), +FCE("ꜯ", 1, 2, 1),FCE("ﬖ", 0, 2, 1),FCE("վն", 1, 2, 2),FCE("ﬗ", 0, 2, 1), +FCE("մխ", 1, 2, 2),FCE("𐐍", 0, 2, 1),FCE("𐐵", 1, 2, 1),FCE("O", 0, 2, 1), +FCE("o", 1, 2, 1),FCE("Q", 0, 2, 1),FCE("q", 1, 2, 1),FCE("R", 0, 2, 1), +FCE("r", 1, 2, 1),FCE("𐐚", 0, 2, 1),FCE("𐑂", 1, 2, 1),FCE("T", 0, 2, 1), +FCE("t", 1, 2, 1),FCE("Ⲙ", 0, 2, 1),FCE("ⲙ", 1, 2, 1),FCE("Ⲋ", 0, 2, 1), +FCE("ⲋ", 1, 2, 1),FCE("ͅ", 0, 4, 1),FCE("Ι", 1, 4, 1),FCE("ι", 2, 4, 1), +FCE("ι", 3, 4, 1),FCE("Ⲍ", 0, 2, 1),FCE("ⲍ", 1, 2, 1),FCE("W", 0, 2, 1), +FCE("w", 1, 2, 1),FCE("Ꙗ", 0, 2, 1),FCE("ꙗ", 1, 2, 1),FCE("𐐛", 0, 2, 1), +FCE("𐑃", 1, 2, 1),FCE("Ꜹ", 0, 2, 1),FCE("ꜹ", 1, 2, 1),FCE("Ⲏ", 0, 2, 1), +FCE("ⲏ", 1, 2, 1),FCE("Y", 0, 2, 1),FCE("y", 1, 2, 1),FCE("𐐄", 0, 2, 1), +FCE("𐐬", 1, 2, 1),FCE("Ꜻ", 0, 2, 1),FCE("ꜻ", 1, 2, 1),FCE("Ⲑ", 0, 2, 1), +FCE("ⲑ", 1, 2, 1),FCE("Ꜽ", 0, 2, 1),FCE("ꜽ", 1, 2, 1),FCE("Ⲓ", 0, 2, 1), +FCE("ⲓ", 1, 2, 1),FCE("𐐜", 0, 2, 1),FCE("𐑄", 1, 2, 1),FCE("Ͱ", 0, 2, 1), +FCE("ͱ", 1, 2, 1),FCE("Ͳ", 0, 2, 1),FCE("ͳ", 1, 2, 1),FCE("Ꜿ", 0, 2, 1), +FCE("ꜿ", 1, 2, 1),FCE("Ͷ", 0, 2, 1),FCE("ͷ", 1, 2, 1),FCE("Ⲕ", 0, 2, 1), +FCE("ⲕ", 1, 2, 1),FCE("Ⲗ", 0, 2, 1),FCE("ⲗ", 1, 2, 1),FCE("Ά", 0, 2, 1), +FCE("ά", 1, 2, 1),FCE("𐐅", 0, 2, 1),FCE("𐐭", 1, 2, 1),FCE("Έ", 0, 2, 1), +FCE("έ", 1, 2, 1),FCE("Ή", 0, 2, 1),FCE("ή", 1, 2, 1),FCE("Ί", 0, 2, 1), +FCE("ί", 1, 2, 1),FCE("Ό", 0, 2, 1),FCE("ό", 1, 2, 1),FCE("Ύ", 0, 2, 1), +FCE("ύ", 1, 2, 1),FCE("Ώ", 0, 2, 1),FCE("ώ", 1, 2, 1),FCE("ΐ", 0, 3, 1), +FCE("ΐ", 1, 3, 1),FCE("ΐ", 2, 3, 3),FCE("Α", 0, 2, 1),FCE("α", 1, 2, 1), +FCE("Β", 0, 3, 1),FCE("β", 1, 3, 1),FCE("ϐ", 2, 3, 1),FCE("Γ", 0, 2, 1), +FCE("γ", 1, 2, 1),FCE("Δ", 0, 2, 1),FCE("δ", 1, 2, 1),FCE("Ε", 0, 3, 1), +FCE("ε", 1, 3, 1),FCE("ϵ", 2, 3, 1),FCE("Ζ", 0, 2, 1),FCE("ζ", 1, 2, 1), +FCE("Η", 0, 2, 1),FCE("η", 1, 2, 1),FCE("Θ", 0, 4, 1),FCE("θ", 1, 4, 1), +FCE("ϑ", 2, 4, 1),FCE("ϴ", 3, 4, 1),FCE("ͅ", 0, 4, 1),FCE("Ι", 1, 4, 1), +FCE("ι", 2, 4, 1),FCE("ι", 3, 4, 1),FCE("Κ", 0, 3, 1),FCE("κ", 1, 3, 1), +FCE("ϰ", 2, 3, 1),FCE("Λ", 0, 2, 1),FCE("λ", 1, 2, 1),FCE("µ", 0, 3, 1), +FCE("Μ", 1, 3, 1),FCE("μ", 2, 3, 1),FCE("Ν", 0, 2, 1),FCE("ν", 1, 2, 1), +FCE("Ξ", 0, 2, 1),FCE("ξ", 1, 2, 1),FCE("Ο", 0, 2, 1),FCE("ο", 1, 2, 1), +FCE("Π", 0, 3, 1),FCE("π", 1, 3, 1),FCE("ϖ", 2, 3, 1),FCE("Ρ", 0, 3, 1), +FCE("ρ", 1, 3, 1),FCE("ϱ", 2, 3, 1),FCE("Σ", 0, 3, 1),FCE("ς", 1, 3, 1), +FCE("σ", 2, 3, 1),FCE("Τ", 0, 2, 1),FCE("τ", 1, 2, 1),FCE("Υ", 0, 2, 1), +FCE("υ", 1, 2, 1),FCE("Φ", 0, 3, 1),FCE("φ", 1, 3, 1),FCE("ϕ", 2, 3, 1), +FCE("Χ", 0, 2, 1),FCE("χ", 1, 2, 1),FCE("Ψ", 0, 2, 1),FCE("ψ", 1, 2, 1), +FCE("Ω", 0, 3, 1),FCE("ω", 1, 3, 1),FCE("Ω", 2, 3, 1),FCE("Ϊ", 0, 2, 1), +FCE("ϊ", 1, 2, 1),FCE("Ϋ", 0, 2, 1),FCE("ϋ", 1, 2, 1),FCE("Ⓣ", 0, 2, 1), +FCE("ⓣ", 1, 2, 1),FCE("Ⳡ", 0, 2, 1),FCE("ⳡ", 1, 2, 1),FCE("ΰ", 0, 3, 1), +FCE("ΰ", 1, 3, 1),FCE("ΰ", 2, 3, 3),FCE("Ꝉ", 0, 2, 1),FCE("ꝉ", 1, 2, 1), +FCE("Ⲝ", 0, 2, 1),FCE("ⲝ", 1, 2, 1),FCE("Ⲟ", 0, 2, 1),FCE("ⲟ", 1, 2, 1), +FCE("Ꝋ", 0, 2, 1),FCE("ꝋ", 1, 2, 1),FCE("Ⲡ", 0, 2, 1),FCE("ⲡ", 1, 2, 1), +FCE("Σ", 0, 3, 1),FCE("ς", 1, 3, 1),FCE("σ", 2, 3, 1),FCE("𐐟", 0, 2, 1), +FCE("𐑇", 1, 2, 1),FCE("Ꝍ", 0, 2, 1),FCE("ꝍ", 1, 2, 1),FCE("Ꚋ", 0, 2, 1), +FCE("ꚋ", 1, 2, 1),FCE("Ⲣ", 0, 2, 1),FCE("ⲣ", 1, 2, 1),FCE("Ϗ", 0, 2, 1), +FCE("ϗ", 1, 2, 1),FCE("Β", 0, 3, 1),FCE("β", 1, 3, 1),FCE("ϐ", 2, 3, 1), +FCE("Θ", 0, 4, 1),FCE("θ", 1, 4, 1),FCE("ϑ", 2, 4, 1),FCE("ϴ", 3, 4, 1), +FCE("Φ", 0, 3, 1),FCE("φ", 1, 3, 1),FCE("ϕ", 2, 3, 1),FCE("Π", 0, 3, 1), +FCE("π", 1, 3, 1),FCE("ϖ", 2, 3, 1),FCE("Ϙ", 0, 2, 1),FCE("ϙ", 1, 2, 1), +FCE("Ⲥ", 0, 2, 1),FCE("ⲥ", 1, 2, 1),FCE("Ϛ", 0, 2, 1),FCE("ϛ", 1, 2, 1), +FCE("Ϝ", 0, 2, 1),FCE("ϝ", 1, 2, 1),FCE("Ϟ", 0, 2, 1),FCE("ϟ", 1, 2, 1), +FCE("Ϡ", 0, 2, 1),FCE("ϡ", 1, 2, 1),FCE("Ꝑ", 0, 2, 1),FCE("ꝑ", 1, 2, 1), +FCE("Ϣ", 0, 2, 1),FCE("ϣ", 1, 2, 1),FCE("Ϥ", 0, 2, 1),FCE("ϥ", 1, 2, 1), +FCE("Ⲧ", 0, 2, 1),FCE("ⲧ", 1, 2, 1),FCE("Ϧ", 0, 2, 1),FCE("ϧ", 1, 2, 1), +FCE("𐐠", 0, 2, 1),FCE("𐑈", 1, 2, 1),FCE("Ϩ", 0, 2, 1),FCE("ϩ", 1, 2, 1), +FCE("Ⳣ", 0, 2, 1),FCE("ⳣ", 1, 2, 1),FCE("Ϫ", 0, 2, 1),FCE("ϫ", 1, 2, 1), +FCE("Ϭ", 0, 2, 1),FCE("ϭ", 1, 2, 1),FCE("Ꝓ", 0, 2, 1),FCE("ꝓ", 1, 2, 1), +FCE("Ϯ", 0, 2, 1),FCE("ϯ", 1, 2, 1),FCE("Κ", 0, 3, 1),FCE("κ", 1, 3, 1), +FCE("ϰ", 2, 3, 1),FCE("Ρ", 0, 3, 1),FCE("ρ", 1, 3, 1),FCE("ϱ", 2, 3, 1), +FCE("Θ", 0, 4, 1),FCE("θ", 1, 4, 1),FCE("ϑ", 2, 4, 1),FCE("ϴ", 3, 4, 1), +FCE("Ε", 0, 3, 1),FCE("ε", 1, 3, 1),FCE("ϵ", 2, 3, 1),FCE("Ϸ", 0, 2, 1), +FCE("ϸ", 1, 2, 1),FCE("ϲ", 0, 2, 1),FCE("Ϲ", 1, 2, 1),FCE("Ϻ", 0, 2, 1), +FCE("ϻ", 1, 2, 1),FCE("ͻ", 0, 2, 1),FCE("Ͻ", 1, 2, 1),FCE("ͼ", 0, 2, 1), +FCE("Ͼ", 1, 2, 1),FCE("ͽ", 0, 2, 1),FCE("Ͽ", 1, 2, 1),FCE("Ѐ", 0, 2, 1), +FCE("ѐ", 1, 2, 1),FCE("Ё", 0, 2, 1),FCE("ё", 1, 2, 1),FCE("Ђ", 0, 2, 1), +FCE("ђ", 1, 2, 1),FCE("Ѓ", 0, 2, 1),FCE("ѓ", 1, 2, 1),FCE("Є", 0, 2, 1), +FCE("є", 1, 2, 1),FCE("Ѕ", 0, 2, 1),FCE("ѕ", 1, 2, 1),FCE("І", 0, 2, 1), +FCE("і", 1, 2, 1),FCE("Ї", 0, 2, 1),FCE("ї", 1, 2, 1),FCE("Ј", 0, 2, 1), +FCE("ј", 1, 2, 1),FCE("Љ", 0, 2, 1),FCE("љ", 1, 2, 1),FCE("Њ", 0, 2, 1), +FCE("њ", 1, 2, 1),FCE("Ћ", 0, 2, 1),FCE("ћ", 1, 2, 1),FCE("Ќ", 0, 2, 1), +FCE("ќ", 1, 2, 1),FCE("Ѝ", 0, 2, 1),FCE("ѝ", 1, 2, 1),FCE("Ў", 0, 2, 1), +FCE("ў", 1, 2, 1),FCE("Џ", 0, 2, 1),FCE("џ", 1, 2, 1),FCE("А", 0, 2, 1), +FCE("а", 1, 2, 1),FCE("Б", 0, 2, 1),FCE("б", 1, 2, 1),FCE("В", 0, 2, 1), +FCE("в", 1, 2, 1),FCE("Г", 0, 2, 1),FCE("г", 1, 2, 1),FCE("Д", 0, 2, 1), +FCE("д", 1, 2, 1),FCE("Е", 0, 2, 1),FCE("е", 1, 2, 1),FCE("Ж", 0, 2, 1), +FCE("ж", 1, 2, 1),FCE("З", 0, 2, 1),FCE("з", 1, 2, 1),FCE("И", 0, 2, 1), +FCE("и", 1, 2, 1),FCE("Й", 0, 2, 1),FCE("й", 1, 2, 1),FCE("К", 0, 2, 1), +FCE("к", 1, 2, 1),FCE("Л", 0, 2, 1),FCE("л", 1, 2, 1),FCE("М", 0, 2, 1), +FCE("м", 1, 2, 1),FCE("Н", 0, 2, 1),FCE("н", 1, 2, 1),FCE("О", 0, 2, 1), +FCE("о", 1, 2, 1),FCE("П", 0, 2, 1),FCE("п", 1, 2, 1),FCE("Р", 0, 2, 1), +FCE("р", 1, 2, 1),FCE("С", 0, 2, 1),FCE("с", 1, 2, 1),FCE("Т", 0, 2, 1), +FCE("т", 1, 2, 1),FCE("У", 0, 2, 1),FCE("у", 1, 2, 1),FCE("Ф", 0, 2, 1), +FCE("ф", 1, 2, 1),FCE("Х", 0, 2, 1),FCE("х", 1, 2, 1),FCE("Ц", 0, 2, 1), +FCE("ц", 1, 2, 1),FCE("Ч", 0, 2, 1),FCE("ч", 1, 2, 1),FCE("Ш", 0, 2, 1), +FCE("ш", 1, 2, 1),FCE("Щ", 0, 2, 1),FCE("щ", 1, 2, 1),FCE("Ъ", 0, 2, 1), +FCE("ъ", 1, 2, 1),FCE("Ы", 0, 2, 1),FCE("ы", 1, 2, 1),FCE("Ь", 0, 2, 1), +FCE("ь", 1, 2, 1),FCE("Э", 0, 2, 1),FCE("э", 1, 2, 1),FCE("Ю", 0, 2, 1), +FCE("ю", 1, 2, 1),FCE("Я", 0, 2, 1),FCE("я", 1, 2, 1),FCE("Z", 0, 2, 1), +FCE("z", 1, 2, 1),FCE("Ⲵ", 0, 2, 1),FCE("ⲵ", 1, 2, 1),FCE("µ", 0, 3, 1), +FCE("Μ", 1, 3, 1),FCE("μ", 2, 3, 1),FCE("𐐣", 0, 2, 1),FCE("𐑋", 1, 2, 1), +FCE("Ⓐ", 0, 2, 1),FCE("ⓐ", 1, 2, 1),FCE("Ⓒ", 0, 2, 1),FCE("ⓒ", 1, 2, 1), +FCE("L", 0, 2, 1),FCE("l", 1, 2, 1),FCE("𐐡", 0, 2, 1),FCE("𐑉", 1, 2, 1), +FCE("Ⓔ", 0, 2, 1),FCE("ⓔ", 1, 2, 1),FCE("𐐙", 0, 2, 1),FCE("𐑁", 1, 2, 1), +FCE("Ѡ", 0, 2, 1),FCE("ѡ", 1, 2, 1),FCE("Ѣ", 0, 2, 1),FCE("ѣ", 1, 2, 1), +FCE("ᵽ", 0, 2, 1),FCE("Ᵽ", 1, 2, 1),FCE("Ѥ", 0, 2, 1),FCE("ѥ", 1, 2, 1), +FCE("Ѧ", 0, 2, 1),FCE("ѧ", 1, 2, 1),FCE("Ⱨ", 0, 2, 1),FCE("ⱨ", 1, 2, 1), +FCE("Ѩ", 0, 2, 1),FCE("ѩ", 1, 2, 1),FCE("Ⓖ", 0, 2, 1),FCE("ⓖ", 1, 2, 1), +FCE("Ѫ", 0, 2, 1),FCE("ѫ", 1, 2, 1),FCE("Ⱬ", 0, 2, 1),FCE("ⱬ", 1, 2, 1), +FCE("Ѭ", 0, 2, 1),FCE("ѭ", 1, 2, 1),FCE("ɑ", 0, 2, 1),FCE("Ɑ", 1, 2, 1), +FCE("Ѯ", 0, 2, 1),FCE("ѯ", 1, 2, 1),FCE("ɐ", 0, 2, 1),FCE("Ɐ", 1, 2, 1), +FCE("Ѱ", 0, 2, 1),FCE("ѱ", 1, 2, 1),FCE("Ꝩ", 0, 2, 1),FCE("ꝩ", 1, 2, 1), +FCE("Ѳ", 0, 2, 1),FCE("ѳ", 1, 2, 1),FCE("Ѵ", 0, 2, 1),FCE("ѵ", 1, 2, 1), +FCE("Ⓘ", 0, 2, 1),FCE("ⓘ", 1, 2, 1),FCE("Ѷ", 0, 2, 1),FCE("ѷ", 1, 2, 1), +FCE("Ѹ", 0, 2, 1),FCE("ѹ", 1, 2, 1),FCE("Ѻ", 0, 2, 1),FCE("ѻ", 1, 2, 1), +FCE("Ѽ", 0, 2, 1),FCE("ѽ", 1, 2, 1),FCE("Ꝫ", 0, 2, 1),FCE("ꝫ", 1, 2, 1), +FCE("Ѿ", 0, 2, 1),FCE("ѿ", 1, 2, 1),FCE("ɀ", 0, 2, 1),FCE("Ɀ", 1, 2, 1), +FCE("Ҁ", 0, 2, 1),FCE("ҁ", 1, 2, 1),FCE("Ⴠ", 0, 2, 1),FCE("ⴠ", 1, 2, 1), +FCE("Ⲃ", 0, 2, 1),FCE("ⲃ", 1, 2, 1),FCE("Ⲅ", 0, 2, 1),FCE("ⲅ", 1, 2, 1), +FCE("Ⲇ", 0, 2, 1),FCE("ⲇ", 1, 2, 1),FCE("Ⴡ", 0, 2, 1),FCE("ⴡ", 1, 2, 1), +FCE("Ⲉ", 0, 2, 1),FCE("ⲉ", 1, 2, 1),FCE("Ꝭ", 0, 2, 1),FCE("ꝭ", 1, 2, 1), +FCE("Ҋ", 0, 2, 1),FCE("ҋ", 1, 2, 1),FCE("Ҍ", 0, 2, 1),FCE("ҍ", 1, 2, 1), +FCE("Â", 0, 2, 1),FCE("â", 1, 2, 1),FCE("Ҏ", 0, 2, 1),FCE("ҏ", 1, 2, 1), +FCE("Ґ", 0, 2, 1),FCE("ґ", 1, 2, 1),FCE("Ғ", 0, 2, 1),FCE("ғ", 1, 2, 1), +FCE("Ⴣ", 0, 2, 1),FCE("ⴣ", 1, 2, 1),FCE("Ҕ", 0, 2, 1),FCE("ҕ", 1, 2, 1), +FCE("Ꝯ", 0, 2, 1),FCE("ꝯ", 1, 2, 1),FCE("Җ", 0, 2, 1),FCE("җ", 1, 2, 1), +FCE("Ҙ", 0, 2, 1),FCE("ҙ", 1, 2, 1),FCE("Ä", 0, 2, 1),FCE("ä", 1, 2, 1), +FCE("Қ", 0, 2, 1),FCE("қ", 1, 2, 1),FCE("𐐦", 0, 2, 1),FCE("𐑎", 1, 2, 1), +FCE("Ҝ", 0, 2, 1),FCE("ҝ", 1, 2, 1),FCE("Ҟ", 0, 2, 1),FCE("ҟ", 1, 2, 1), +FCE("Ⴥ", 0, 2, 1),FCE("ⴥ", 1, 2, 1),FCE("Ҡ", 0, 2, 1),FCE("ҡ", 1, 2, 1), +FCE("Ң", 0, 2, 1),FCE("ң", 1, 2, 1),FCE("Ҥ", 0, 2, 1),FCE("ҥ", 1, 2, 1), +FCE("Ⳇ", 0, 2, 1),FCE("ⳇ", 1, 2, 1),FCE("Ҧ", 0, 2, 1),FCE("ҧ", 1, 2, 1), +FCE("Ҩ", 0, 2, 1),FCE("ҩ", 1, 2, 1),FCE("Ⱡ", 0, 2, 1),FCE("ⱡ", 1, 2, 1), +FCE("Ҫ", 0, 2, 1),FCE("ҫ", 1, 2, 1),FCE("Ⴧ", 0, 2, 1),FCE("ⴧ", 1, 2, 1), +FCE("Ҭ", 0, 2, 1),FCE("ҭ", 1, 2, 1),FCE("𐐓", 0, 2, 1),FCE("𐐻", 1, 2, 1), +FCE("Ү", 0, 2, 1),FCE("ү", 1, 2, 1),FCE("Ұ", 0, 2, 1),FCE("ұ", 1, 2, 1), +FCE("Ⳉ", 0, 2, 1),FCE("ⳉ", 1, 2, 1),FCE("Ҳ", 0, 2, 1),FCE("ҳ", 1, 2, 1), +FCE("Ҵ", 0, 2, 1),FCE("ҵ", 1, 2, 1),FCE("Ҷ", 0, 2, 1),FCE("ҷ", 1, 2, 1), +FCE("Ⓑ", 0, 2, 1),FCE("ⓑ", 1, 2, 1),FCE("Ҹ", 0, 2, 1),FCE("ҹ", 1, 2, 1), +FCE("Ⓓ", 0, 2, 1),FCE("ⓓ", 1, 2, 1),FCE("Һ", 0, 2, 1),FCE("һ", 1, 2, 1), +FCE("Ⓕ", 0, 2, 1),FCE("ⓕ", 1, 2, 1),FCE("Ҽ", 0, 2, 1),FCE("ҽ", 1, 2, 1), +FCE("Ⓗ", 0, 2, 1),FCE("ⓗ", 1, 2, 1),FCE("Ҿ", 0, 2, 1),FCE("ҿ", 1, 2, 1), +FCE("Ⓙ", 0, 2, 1),FCE("ⓙ", 1, 2, 1),FCE("Ӏ", 0, 2, 1),FCE("ӏ", 1, 2, 1), +FCE("Ӂ", 0, 2, 1),FCE("ӂ", 1, 2, 1),FCE("Ⓜ", 0, 2, 1),FCE("ⓜ", 1, 2, 1), +FCE("Ӄ", 0, 2, 1),FCE("ӄ", 1, 2, 1),FCE("Ⓞ", 0, 2, 1),FCE("ⓞ", 1, 2, 1), +FCE("Ӆ", 0, 2, 1),FCE("ӆ", 1, 2, 1),FCE("Ⓠ", 0, 2, 1),FCE("ⓠ", 1, 2, 1), +FCE("Ӈ", 0, 2, 1),FCE("ӈ", 1, 2, 1),FCE("Ⓢ", 0, 2, 1),FCE("ⓢ", 1, 2, 1), +FCE("Ӊ", 0, 2, 1),FCE("ӊ", 1, 2, 1),FCE("Ⓤ", 0, 2, 1),FCE("ⓤ", 1, 2, 1), +FCE("Ӌ", 0, 2, 1),FCE("ӌ", 1, 2, 1),FCE("Ⓦ", 0, 2, 1),FCE("ⓦ", 1, 2, 1), +FCE("Ӎ", 0, 2, 1),FCE("ӎ", 1, 2, 1),FCE("Ⓨ", 0, 2, 1),FCE("ⓨ", 1, 2, 1), +FCE("Ⴭ", 0, 2, 1),FCE("ⴭ", 1, 2, 1),FCE("Ӑ", 0, 2, 1),FCE("ӑ", 1, 2, 1), +FCE("Ӓ", 0, 2, 1),FCE("ӓ", 1, 2, 1),FCE("Ӕ", 0, 2, 1),FCE("ӕ", 1, 2, 1), +FCE("Ⳏ", 0, 2, 1),FCE("ⳏ", 1, 2, 1),FCE("Ӗ", 0, 2, 1),FCE("ӗ", 1, 2, 1), +FCE("Ꝺ", 0, 2, 1),FCE("ꝺ", 1, 2, 1),FCE("Ә", 0, 2, 1),FCE("ә", 1, 2, 1), +FCE("Ӛ", 0, 2, 1),FCE("ӛ", 1, 2, 1),FCE("Ⓩ", 0, 2, 1),FCE("ⓩ", 1, 2, 1), +FCE("Ӝ", 0, 2, 1),FCE("ӝ", 1, 2, 1),FCE("Ӟ", 0, 2, 1),FCE("ӟ", 1, 2, 1), +FCE("Ӡ", 0, 2, 1),FCE("ӡ", 1, 2, 1),FCE("Ⳑ", 0, 2, 1),FCE("ⳑ", 1, 2, 1), +FCE("Ӣ", 0, 2, 1),FCE("ӣ", 1, 2, 1),FCE("Ӥ", 0, 2, 1),FCE("ӥ", 1, 2, 1), +FCE("ɫ", 0, 2, 1),FCE("Ɫ", 1, 2, 1),FCE("Ӧ", 0, 2, 1),FCE("ӧ", 1, 2, 1), +FCE("Ө", 0, 2, 1),FCE("ө", 1, 2, 1),FCE("Ӫ", 0, 2, 1),FCE("ӫ", 1, 2, 1), +FCE("Ⅎ", 0, 2, 1),FCE("ⅎ", 1, 2, 1),FCE("Ӭ", 0, 2, 1),FCE("ӭ", 1, 2, 1), +FCE("Ⳓ", 0, 2, 1),FCE("ⳓ", 1, 2, 1),FCE("Ӯ", 0, 2, 1),FCE("ӯ", 1, 2, 1), +FCE("Ӱ", 0, 2, 1),FCE("ӱ", 1, 2, 1),FCE("𐐢", 0, 2, 1),FCE("𐑊", 1, 2, 1), +FCE("Ӳ", 0, 2, 1),FCE("ӳ", 1, 2, 1),FCE("Ӵ", 0, 2, 1),FCE("ӵ", 1, 2, 1), +FCE("Ӷ", 0, 2, 1),FCE("ӷ", 1, 2, 1),FCE("Ӹ", 0, 2, 1),FCE("ӹ", 1, 2, 1), +FCE("Ⳕ", 0, 2, 1),FCE("ⳕ", 1, 2, 1),FCE("Ӻ", 0, 2, 1),FCE("ӻ", 1, 2, 1), +FCE("Ӽ", 0, 2, 1),FCE("ӽ", 1, 2, 1),FCE("Ӿ", 0, 2, 1),FCE("ӿ", 1, 2, 1), +FCE("Ԁ", 0, 2, 1),FCE("ԁ", 1, 2, 1),FCE("Ꞁ", 0, 2, 1),FCE("ꞁ", 1, 2, 1), +FCE("Ԃ", 0, 2, 1),FCE("ԃ", 1, 2, 1),FCE("Ԅ", 0, 2, 1),FCE("ԅ", 1, 2, 1), +FCE("Ⳗ", 0, 2, 1),FCE("ⳗ", 1, 2, 1),FCE("Ԇ", 0, 2, 1),FCE("ԇ", 1, 2, 1), +FCE("Ԉ", 0, 2, 1),FCE("ԉ", 1, 2, 1),FCE("Ԋ", 0, 2, 1),FCE("ԋ", 1, 2, 1), +FCE("Ԍ", 0, 2, 1),FCE("ԍ", 1, 2, 1),FCE("Ꞃ", 0, 2, 1),FCE("ꞃ", 1, 2, 1), +FCE("Ԏ", 0, 2, 1),FCE("ԏ", 1, 2, 1),FCE("Ԑ", 0, 2, 1),FCE("ԑ", 1, 2, 1), +FCE("Ⳙ", 0, 2, 1),FCE("ⳙ", 1, 2, 1),FCE("Ԓ", 0, 2, 1),FCE("ԓ", 1, 2, 1), +FCE("Ԕ", 0, 2, 1),FCE("ԕ", 1, 2, 1),FCE("Ԗ", 0, 2, 1),FCE("ԗ", 1, 2, 1), +FCE("Ԙ", 0, 2, 1),FCE("ԙ", 1, 2, 1),FCE("Ꞅ", 0, 2, 1),FCE("ꞅ", 1, 2, 1), +FCE("Ԛ", 0, 2, 1),FCE("ԛ", 1, 2, 1),FCE("Ⲩ", 0, 2, 1),FCE("ⲩ", 1, 2, 1), +FCE("Ԝ", 0, 2, 1),FCE("ԝ", 1, 2, 1),FCE("Ⳛ", 0, 2, 1),FCE("ⳛ", 1, 2, 1), +FCE("Ԟ", 0, 2, 1),FCE("ԟ", 1, 2, 1),FCE("Ԡ", 0, 2, 1),FCE("ԡ", 1, 2, 1), +FCE("Ԣ", 0, 2, 1),FCE("ԣ", 1, 2, 1),FCE("Ԥ", 0, 2, 1),FCE("ԥ", 1, 2, 1), +FCE("Ꞇ", 0, 2, 1),FCE("ꞇ", 1, 2, 1),FCE("Ԧ", 0, 2, 1),FCE("ԧ", 1, 2, 1), +FCE("Ⱐ", 0, 2, 1),FCE("ⱐ", 1, 2, 1),FCE("Ⳝ", 0, 2, 1),FCE("ⳝ", 1, 2, 1), +FCE("Ա", 0, 2, 1),FCE("ա", 1, 2, 1),FCE("Բ", 0, 2, 1),FCE("բ", 1, 2, 1), +FCE("Գ", 0, 2, 1),FCE("գ", 1, 2, 1),FCE("Դ", 0, 2, 1),FCE("դ", 1, 2, 1), +FCE("Ե", 0, 2, 1),FCE("ե", 1, 2, 1),FCE("Զ", 0, 2, 1),FCE("զ", 1, 2, 1), +FCE("Է", 0, 2, 1),FCE("է", 1, 2, 1),FCE("Ը", 0, 2, 1),FCE("ը", 1, 2, 1), +FCE("Թ", 0, 2, 1),FCE("թ", 1, 2, 1),FCE("Ժ", 0, 2, 1),FCE("ժ", 1, 2, 1), +FCE("Ի", 0, 2, 1),FCE("ի", 1, 2, 1),FCE("Լ", 0, 2, 1),FCE("լ", 1, 2, 1), +FCE("Խ", 0, 2, 1),FCE("խ", 1, 2, 1),FCE("Ծ", 0, 2, 1),FCE("ծ", 1, 2, 1), +FCE("Կ", 0, 2, 1),FCE("կ", 1, 2, 1),FCE("Հ", 0, 2, 1),FCE("հ", 1, 2, 1), +FCE("Ձ", 0, 2, 1),FCE("ձ", 1, 2, 1),FCE("Ղ", 0, 2, 1),FCE("ղ", 1, 2, 1), +FCE("Ճ", 0, 2, 1),FCE("ճ", 1, 2, 1),FCE("Մ", 0, 2, 1),FCE("մ", 1, 2, 1), +FCE("Յ", 0, 2, 1),FCE("յ", 1, 2, 1),FCE("Ն", 0, 2, 1),FCE("ն", 1, 2, 1), +FCE("Շ", 0, 2, 1),FCE("շ", 1, 2, 1),FCE("Ո", 0, 2, 1),FCE("ո", 1, 2, 1), +FCE("Չ", 0, 2, 1),FCE("չ", 1, 2, 1),FCE("Պ", 0, 2, 1),FCE("պ", 1, 2, 1), +FCE("Ջ", 0, 2, 1),FCE("ջ", 1, 2, 1),FCE("Ռ", 0, 2, 1),FCE("ռ", 1, 2, 1), +FCE("Ս", 0, 2, 1),FCE("ս", 1, 2, 1),FCE("Վ", 0, 2, 1),FCE("վ", 1, 2, 1), +FCE("Տ", 0, 2, 1),FCE("տ", 1, 2, 1),FCE("Ր", 0, 2, 1),FCE("ր", 1, 2, 1), +FCE("Ց", 0, 2, 1),FCE("ց", 1, 2, 1),FCE("Ւ", 0, 2, 1),FCE("ւ", 1, 2, 1), +FCE("Փ", 0, 2, 1),FCE("փ", 1, 2, 1),FCE("Ք", 0, 2, 1),FCE("ք", 1, 2, 1), +FCE("Օ", 0, 2, 1),FCE("օ", 1, 2, 1),FCE("Ֆ", 0, 2, 1),FCE("ֆ", 1, 2, 1), +FCE("Ⲫ", 0, 2, 1),FCE("ⲫ", 1, 2, 1),FCE("Ꞑ", 0, 2, 1),FCE("ꞑ", 1, 2, 1), +FCE("Ⱒ", 0, 2, 1),FCE("ⱒ", 1, 2, 1),FCE("Ꞓ", 0, 2, 1),FCE("ꞓ", 1, 2, 1), +FCE("Ⱓ", 0, 2, 1),FCE("ⱓ", 1, 2, 1),FCE("Ⳬ", 0, 2, 1),FCE("ⳬ", 1, 2, 1), +FCE("Ⳋ", 0, 2, 1),FCE("ⳋ", 1, 2, 1),FCE("և", 0, 2, 1),FCE("եւ", 1, 2, 2), +FCE("Ꙃ", 0, 2, 1),FCE("ꙃ", 1, 2, 1),FCE("Ⳮ", 0, 2, 1),FCE("ⳮ", 1, 2, 1), +FCE("Ⲭ", 0, 2, 1),FCE("ⲭ", 1, 2, 1),FCE("Ꙅ", 0, 2, 1),FCE("ꙅ", 1, 2, 1), +FCE("Ⱔ", 0, 2, 1),FCE("ⱔ", 1, 2, 1),FCE("Ꝕ", 0, 2, 1),FCE("ꝕ", 1, 2, 1), +FCE("Ꙇ", 0, 2, 1),FCE("ꙇ", 1, 2, 1),FCE("Ⳳ", 0, 2, 1),FCE("ⳳ", 1, 2, 1), +FCE("𐐈", 0, 2, 1),FCE("𐐰", 1, 2, 1),FCE("Ꙉ", 0, 2, 1),FCE("ꙉ", 1, 2, 1), +FCE("Ⱕ", 0, 2, 1),FCE("ⱕ", 1, 2, 1),FCE("Ꞡ", 0, 2, 1),FCE("ꞡ", 1, 2, 1), +FCE("Ꙍ", 0, 2, 1),FCE("ꙍ", 1, 2, 1),FCE("Ꞣ", 0, 2, 1),FCE("ꞣ", 1, 2, 1), +FCE("Ⲯ", 0, 2, 1),FCE("ⲯ", 1, 2, 1),FCE("Ꙏ", 0, 2, 1),FCE("ꙏ", 1, 2, 1), +FCE("Ꞥ", 0, 2, 1),FCE("ꞥ", 1, 2, 1),FCE("Ꙑ", 0, 2, 1),FCE("ꙑ", 1, 2, 1), +FCE("Ꞧ", 0, 2, 1),FCE("ꞧ", 1, 2, 1),FCE("Ꞌ", 0, 2, 1),FCE("ꞌ", 1, 2, 1), +FCE("Ꙓ", 0, 2, 1),FCE("ꙓ", 1, 2, 1),FCE("Ꞩ", 0, 2, 1),FCE("ꞩ", 1, 2, 1), +FCE("Ꙕ", 0, 2, 1),FCE("ꙕ", 1, 2, 1),FCE("ɦ", 0, 2, 1),FCE("Ɦ", 1, 2, 1), +FCE("Ḁ", 0, 2, 1),FCE("ḁ", 1, 2, 1),FCE("Ḃ", 0, 2, 1),FCE("ḃ", 1, 2, 1), +FCE("Ḅ", 0, 2, 1),FCE("ḅ", 1, 2, 1),FCE("Ⱏ", 0, 2, 1),FCE("ⱏ", 1, 2, 1), +FCE("Ḇ", 0, 2, 1),FCE("ḇ", 1, 2, 1),FCE("𐐎", 0, 2, 1),FCE("𐐶", 1, 2, 1), +FCE("Ḉ", 0, 2, 1),FCE("ḉ", 1, 2, 1),FCE("Ḋ", 0, 2, 1),FCE("ḋ", 1, 2, 1), +FCE("Ⲱ", 0, 2, 1),FCE("ⲱ", 1, 2, 1),FCE("Ḍ", 0, 2, 1),FCE("ḍ", 1, 2, 1), +FCE("Ḏ", 0, 2, 1),FCE("ḏ", 1, 2, 1),FCE("Ḑ", 0, 2, 1),FCE("ḑ", 1, 2, 1), +FCE("Ꙙ", 0, 2, 1),FCE("ꙙ", 1, 2, 1),FCE("Ḓ", 0, 2, 1),FCE("ḓ", 1, 2, 1), +FCE("Ḕ", 0, 2, 1),FCE("ḕ", 1, 2, 1),FCE("Ⓧ", 0, 2, 1),FCE("ⓧ", 1, 2, 1), +FCE("Ḗ", 0, 2, 1),FCE("ḗ", 1, 2, 1),FCE("Ḙ", 0, 2, 1),FCE("ḙ", 1, 2, 1), +FCE("Ḛ", 0, 2, 1),FCE("ḛ", 1, 2, 1),FCE("Ḝ", 0, 2, 1),FCE("ḝ", 1, 2, 1), +FCE("Ꙛ", 0, 2, 1),FCE("ꙛ", 1, 2, 1),FCE("Ḟ", 0, 2, 1),FCE("ḟ", 1, 2, 1), +FCE("Ḡ", 0, 2, 1),FCE("ḡ", 1, 2, 1),FCE("Ḣ", 0, 2, 1),FCE("ḣ", 1, 2, 1), +FCE("Ḥ", 0, 2, 1),FCE("ḥ", 1, 2, 1),FCE("Ḧ", 0, 2, 1),FCE("ḧ", 1, 2, 1), +FCE("Ḩ", 0, 2, 1),FCE("ḩ", 1, 2, 1),FCE("Ꙝ", 0, 2, 1),FCE("ꙝ", 1, 2, 1), +FCE("Ḫ", 0, 2, 1),FCE("ḫ", 1, 2, 1),FCE("Ḭ", 0, 2, 1),FCE("ḭ", 1, 2, 1), +FCE("Ḯ", 0, 2, 1),FCE("ḯ", 1, 2, 1),FCE("Ḱ", 0, 2, 1),FCE("ḱ", 1, 2, 1), +FCE("Ḳ", 0, 2, 1),FCE("ḳ", 1, 2, 1),FCE("Ḵ", 0, 2, 1),FCE("ḵ", 1, 2, 1), +FCE("Ꙟ", 0, 2, 1),FCE("ꙟ", 1, 2, 1),FCE("Ḷ", 0, 2, 1),FCE("ḷ", 1, 2, 1), +FCE("Ḹ", 0, 2, 1),FCE("ḹ", 1, 2, 1),FCE("Ḻ", 0, 2, 1),FCE("ḻ", 1, 2, 1), +FCE("Ḽ", 0, 2, 1),FCE("ḽ", 1, 2, 1),FCE("Ḿ", 0, 2, 1),FCE("ḿ", 1, 2, 1), +FCE("Ṁ", 0, 2, 1),FCE("ṁ", 1, 2, 1),FCE("Ꙡ", 0, 2, 1),FCE("ꙡ", 1, 2, 1), +FCE("Ṃ", 0, 2, 1),FCE("ṃ", 1, 2, 1),FCE("Ṅ", 0, 2, 1),FCE("ṅ", 1, 2, 1), +FCE("Ṇ", 0, 2, 1),FCE("ṇ", 1, 2, 1),FCE("Ⲳ", 0, 2, 1),FCE("ⲳ", 1, 2, 1), +FCE("Ṉ", 0, 2, 1),FCE("ṉ", 1, 2, 1),FCE("Ⳁ", 0, 2, 1),FCE("ⳁ", 1, 2, 1), +FCE("Ṋ", 0, 2, 1),FCE("ṋ", 1, 2, 1),FCE("Ṍ", 0, 2, 1),FCE("ṍ", 1, 2, 1), +FCE("Ꙣ", 0, 2, 1),FCE("ꙣ", 1, 2, 1),FCE("Ṏ", 0, 2, 1),FCE("ṏ", 1, 2, 1), +FCE("Ṑ", 0, 2, 1),FCE("ṑ", 1, 2, 1),FCE("Ṓ", 0, 2, 1),FCE("ṓ", 1, 2, 1), +FCE("Ṕ", 0, 2, 1),FCE("ṕ", 1, 2, 1),FCE("Ṗ", 0, 2, 1),FCE("ṗ", 1, 2, 1), +FCE("Ṙ", 0, 2, 1),FCE("ṙ", 1, 2, 1),FCE("Ꙥ", 0, 2, 1),FCE("ꙥ", 1, 2, 1), +FCE("Ṛ", 0, 2, 1),FCE("ṛ", 1, 2, 1),FCE("Ṝ", 0, 2, 1),FCE("ṝ", 1, 2, 1), +FCE("Ṟ", 0, 2, 1),FCE("ṟ", 1, 2, 1),FCE("Ṡ", 0, 3, 1),FCE("ṡ", 1, 3, 1), +FCE("ẛ", 2, 3, 1),FCE("Ṣ", 0, 2, 1),FCE("ṣ", 1, 2, 1),FCE("𐐤", 0, 2, 1), +FCE("𐑌", 1, 2, 1),FCE("Ṥ", 0, 2, 1),FCE("ṥ", 1, 2, 1),FCE("Ꙧ", 0, 2, 1), +FCE("ꙧ", 1, 2, 1),FCE("Ṧ", 0, 2, 1),FCE("ṧ", 1, 2, 1),FCE("Ṩ", 0, 2, 1), +FCE("ṩ", 1, 2, 1),FCE("Ṫ", 0, 2, 1),FCE("ṫ", 1, 2, 1),FCE("Ṭ", 0, 2, 1), +FCE("ṭ", 1, 2, 1),FCE("Ṯ", 0, 2, 1),FCE("ṯ", 1, 2, 1),FCE("Ṱ", 0, 2, 1), +FCE("ṱ", 1, 2, 1),FCE("Ꙩ", 0, 2, 1),FCE("ꙩ", 1, 2, 1),FCE("Ṳ", 0, 2, 1), +FCE("ṳ", 1, 2, 1),FCE("Ṵ", 0, 2, 1),FCE("ṵ", 1, 2, 1),FCE("Ṷ", 0, 2, 1), +FCE("ṷ", 1, 2, 1),FCE("Ⲿ", 0, 2, 1),FCE("ⲿ", 1, 2, 1),FCE("Ṹ", 0, 2, 1), +FCE("ṹ", 1, 2, 1),FCE("Ṻ", 0, 2, 1),FCE("ṻ", 1, 2, 1),FCE("Ṽ", 0, 2, 1), +FCE("ṽ", 1, 2, 1),FCE("Ꙫ", 0, 2, 1),FCE("ꙫ", 1, 2, 1),FCE("Ṿ", 0, 2, 1), +FCE("ṿ", 1, 2, 1),FCE("Ẁ", 0, 2, 1),FCE("ẁ", 1, 2, 1),FCE("Ẃ", 0, 2, 1), +FCE("ẃ", 1, 2, 1),FCE("Ẅ", 0, 2, 1),FCE("ẅ", 1, 2, 1),FCE("Ẇ", 0, 2, 1), +FCE("ẇ", 1, 2, 1),FCE("Ẉ", 0, 2, 1),FCE("ẉ", 1, 2, 1),FCE("Ꙭ", 0, 2, 1), +FCE("ꙭ", 1, 2, 1),FCE("Ẋ", 0, 2, 1),FCE("ẋ", 1, 2, 1),FCE("Ẍ", 0, 2, 1), +FCE("ẍ", 1, 2, 1),FCE("Ẏ", 0, 2, 1),FCE("ẏ", 1, 2, 1),FCE("Ẑ", 0, 2, 1), +FCE("ẑ", 1, 2, 1),FCE("Ẓ", 0, 2, 1),FCE("ẓ", 1, 2, 1),FCE("Ẕ", 0, 2, 1), +FCE("ẕ", 1, 2, 1),FCE("ẖ", 0, 2, 1),FCE("ẖ", 1, 2, 2),FCE("ẗ", 0, 2, 1), +FCE("ẗ", 1, 2, 2),FCE("ẘ", 0, 2, 1),FCE("ẘ", 1, 2, 2),FCE("ẙ", 0, 2, 1), +FCE("ẙ", 1, 2, 2),FCE("ẚ", 0, 2, 1),FCE("aʾ", 1, 2, 2),FCE("Ṡ", 0, 3, 1), +FCE("ṡ", 1, 3, 1),FCE("ẛ", 2, 3, 1),FCE("ß", 0, 3, 1),FCE("ẞ", 1, 3, 1), +FCE("ss", 2, 3, 2),FCE("Ạ", 0, 2, 1),FCE("ạ", 1, 2, 1),FCE("Ả", 0, 2, 1), +FCE("ả", 1, 2, 1),FCE("Ấ", 0, 2, 1),FCE("ấ", 1, 2, 1),FCE("Ⓟ", 0, 2, 1), +FCE("ⓟ", 1, 2, 1),FCE("Ầ", 0, 2, 1),FCE("ầ", 1, 2, 1),FCE("Ẩ", 0, 2, 1), +FCE("ẩ", 1, 2, 1),FCE("Ẫ", 0, 2, 1),FCE("ẫ", 1, 2, 1),FCE("Ậ", 0, 2, 1), +FCE("ậ", 1, 2, 1),FCE("Ắ", 0, 2, 1),FCE("ắ", 1, 2, 1),FCE("H", 0, 2, 1), +FCE("h", 1, 2, 1),FCE("Ằ", 0, 2, 1),FCE("ằ", 1, 2, 1),FCE("Ẳ", 0, 2, 1), +FCE("ẳ", 1, 2, 1),FCE("𐐥", 0, 2, 1),FCE("𐑍", 1, 2, 1),FCE("Ẵ", 0, 2, 1), +FCE("ẵ", 1, 2, 1),FCE("Ặ", 0, 2, 1),FCE("ặ", 1, 2, 1),FCE("Ẹ", 0, 2, 1), +FCE("ẹ", 1, 2, 1),FCE("Ẻ", 0, 2, 1),FCE("ẻ", 1, 2, 1),FCE("Ẽ", 0, 2, 1), +FCE("ẽ", 1, 2, 1),FCE("Ế", 0, 2, 1),FCE("ế", 1, 2, 1),FCE("Ⲷ", 0, 2, 1), +FCE("ⲷ", 1, 2, 1),FCE("Ề", 0, 2, 1),FCE("ề", 1, 2, 1),FCE("Ể", 0, 2, 1), +FCE("ể", 1, 2, 1),FCE("Ⲛ", 0, 2, 1),FCE("ⲛ", 1, 2, 1),FCE("Ễ", 0, 2, 1), +FCE("ễ", 1, 2, 1),FCE("Ệ", 0, 2, 1),FCE("ệ", 1, 2, 1),FCE("Ỉ", 0, 2, 1), +FCE("ỉ", 1, 2, 1),FCE("Ị", 0, 2, 1),FCE("ị", 1, 2, 1),FCE("Ọ", 0, 2, 1), +FCE("ọ", 1, 2, 1),FCE("Ỏ", 0, 2, 1),FCE("ỏ", 1, 2, 1),FCE("Ố", 0, 2, 1), +FCE("ố", 1, 2, 1),FCE("Ồ", 0, 2, 1),FCE("ồ", 1, 2, 1),FCE("Ổ", 0, 2, 1), +FCE("ổ", 1, 2, 1),FCE("Ỗ", 0, 2, 1),FCE("ỗ", 1, 2, 1),FCE("Ộ", 0, 2, 1), +FCE("ộ", 1, 2, 1),FCE("Ớ", 0, 2, 1),FCE("ớ", 1, 2, 1),FCE("Ờ", 0, 2, 1), +FCE("ờ", 1, 2, 1),FCE("Ở", 0, 2, 1),FCE("ở", 1, 2, 1),FCE("Ỡ", 0, 2, 1), +FCE("ỡ", 1, 2, 1),FCE("Ợ", 0, 2, 1),FCE("ợ", 1, 2, 1),FCE("Ụ", 0, 2, 1), +FCE("ụ", 1, 2, 1),FCE("Ω", 0, 3, 1),FCE("ω", 1, 3, 1),FCE("Ω", 2, 3, 1), +FCE("Ủ", 0, 2, 1),FCE("ủ", 1, 2, 1),FCE("Ứ", 0, 2, 1),FCE("ứ", 1, 2, 1), +FCE("Ừ", 0, 2, 1),FCE("ừ", 1, 2, 1),FCE("J", 0, 2, 1),FCE("j", 1, 2, 1), +FCE("Ử", 0, 2, 1),FCE("ử", 1, 2, 1),FCE("Ữ", 0, 2, 1),FCE("ữ", 1, 2, 1), +FCE("Ự", 0, 2, 1),FCE("ự", 1, 2, 1),FCE("Ỳ", 0, 2, 1),FCE("ỳ", 1, 2, 1), +FCE("Ỵ", 0, 2, 1),FCE("ỵ", 1, 2, 1),FCE("Ỷ", 0, 2, 1),FCE("ỷ", 1, 2, 1), +FCE("Ỹ", 0, 2, 1),FCE("ỹ", 1, 2, 1),FCE("Ỻ", 0, 2, 1),FCE("ỻ", 1, 2, 1), +FCE("Ⲹ", 0, 2, 1),FCE("ⲹ", 1, 2, 1),FCE("Ỽ", 0, 2, 1),FCE("ỽ", 1, 2, 1), +FCE("K", 0, 3, 1),FCE("k", 1, 3, 1),FCE("K", 2, 3, 1),FCE("Ỿ", 0, 2, 1), +FCE("ỿ", 1, 2, 1),FCE("Ꚁ", 0, 2, 1),FCE("ꚁ", 1, 2, 1),FCE("ἀ", 0, 2, 1), +FCE("Ἀ", 1, 2, 1),FCE("ἁ", 0, 2, 1),FCE("Ἁ", 1, 2, 1),FCE("ἂ", 0, 2, 1), +FCE("Ἂ", 1, 2, 1),FCE("ἃ", 0, 2, 1),FCE("Ἃ", 1, 2, 1),FCE("ἄ", 0, 2, 1), +FCE("Ἄ", 1, 2, 1),FCE("ἅ", 0, 2, 1),FCE("Ἅ", 1, 2, 1),FCE("ἆ", 0, 2, 1), +FCE("Ἆ", 1, 2, 1),FCE("ἇ", 0, 2, 1),FCE("Ἇ", 1, 2, 1),FCE("𐐘", 0, 2, 1), +FCE("𐑀", 1, 2, 1),FCE("ɥ", 0, 2, 1),FCE("Ɥ", 1, 2, 1),FCE("ἐ", 0, 2, 1), +FCE("Ἐ", 1, 2, 1),FCE("ἑ", 0, 2, 1),FCE("Ἑ", 1, 2, 1),FCE("ἒ", 0, 2, 1), +FCE("Ἒ", 1, 2, 1),FCE("ἓ", 0, 2, 1),FCE("Ἓ", 1, 2, 1),FCE("ἔ", 0, 2, 1), +FCE("Ἔ", 1, 2, 1),FCE("ἕ", 0, 2, 1),FCE("Ἕ", 1, 2, 1),FCE("A", 0, 2, 1), +FCE("a", 1, 2, 1),FCE("Ꜣ", 0, 2, 1),FCE("ꜣ", 1, 2, 1),FCE("C", 0, 2, 1), +FCE("c", 1, 2, 1),FCE("Ꜥ", 0, 2, 1),FCE("ꜥ", 1, 2, 1),FCE("Ꚇ", 0, 2, 1), +FCE("ꚇ", 1, 2, 1),FCE("Ꜧ", 0, 2, 1),FCE("ꜧ", 1, 2, 1),FCE("G", 0, 2, 1), +FCE("g", 1, 2, 1),FCE("ἠ", 0, 2, 1),FCE("Ἠ", 1, 2, 1),FCE("ἡ", 0, 2, 1), +FCE("Ἡ", 1, 2, 1),FCE("ἢ", 0, 2, 1),FCE("Ἢ", 1, 2, 1),FCE("ἣ", 0, 2, 1), +FCE("Ἣ", 1, 2, 1),FCE("ἤ", 0, 2, 1),FCE("Ἤ", 1, 2, 1),FCE("ἥ", 0, 2, 1), +FCE("Ἥ", 1, 2, 1),FCE("ἦ", 0, 2, 1),FCE("Ἦ", 1, 2, 1),FCE("ἧ", 0, 2, 1), +FCE("Ἧ", 1, 2, 1),FCE("P", 0, 2, 1),FCE("p", 1, 2, 1),FCE("Ꚉ", 0, 2, 1), +FCE("ꚉ", 1, 2, 1),FCE("Ꜳ", 0, 2, 1),FCE("ꜳ", 1, 2, 1),FCE("S", 0, 2, 1), +FCE("s", 1, 2, 1),FCE("Ꜵ", 0, 2, 1),FCE("ꜵ", 1, 2, 1),FCE("U", 0, 2, 1), +FCE("u", 1, 2, 1),FCE("Ꜷ", 0, 2, 1),FCE("ꜷ", 1, 2, 1),FCE("Ⲻ", 0, 2, 1), +FCE("ⲻ", 1, 2, 1),FCE("ἰ", 0, 2, 1),FCE("Ἰ", 1, 2, 1),FCE("ἱ", 0, 2, 1), +FCE("Ἱ", 1, 2, 1),FCE("ἲ", 0, 2, 1),FCE("Ἲ", 1, 2, 1),FCE("ἳ", 0, 2, 1), +FCE("Ἳ", 1, 2, 1),FCE("ἴ", 0, 2, 1),FCE("Ἴ", 1, 2, 1),FCE("ἵ", 0, 2, 1), +FCE("Ἵ", 1, 2, 1),FCE("ἶ", 0, 2, 1),FCE("Ἶ", 1, 2, 1),FCE("ἷ", 0, 2, 1), +FCE("Ἷ", 1, 2, 1),FCE("Ꝁ", 0, 2, 1),FCE("ꝁ", 1, 2, 1),FCE("Ꝃ", 0, 2, 1), +FCE("ꝃ", 1, 2, 1),FCE("Ꝅ", 0, 2, 1),FCE("ꝅ", 1, 2, 1),FCE("Ꝇ", 0, 2, 1), +FCE("ꝇ", 1, 2, 1),FCE("ὀ", 0, 2, 1),FCE("Ὀ", 1, 2, 1),FCE("ὁ", 0, 2, 1), +FCE("Ὁ", 1, 2, 1),FCE("ὂ", 0, 2, 1),FCE("Ὂ", 1, 2, 1),FCE("ὃ", 0, 2, 1), +FCE("Ὃ", 1, 2, 1),FCE("ὄ", 0, 2, 1),FCE("Ὄ", 1, 2, 1),FCE("ὅ", 0, 2, 1), +FCE("Ὅ", 1, 2, 1),FCE("Ꝏ", 0, 2, 1),FCE("ꝏ", 1, 2, 1),FCE("ὐ", 0, 2, 1), +FCE("ὐ", 1, 2, 2),FCE("ὒ", 0, 2, 1),FCE("ὒ", 1, 2, 3),FCE("ὔ", 0, 2, 1), +FCE("ὔ", 1, 2, 3),FCE("Ꚏ", 0, 2, 1),FCE("ꚏ", 1, 2, 1),FCE("ὖ", 0, 2, 1), +FCE("ὖ", 1, 2, 3),FCE("Ꝙ", 0, 2, 1),FCE("ꝙ", 1, 2, 1),FCE("ὑ", 0, 2, 1), +FCE("Ὑ", 1, 2, 1),FCE("Ꝛ", 0, 2, 1),FCE("ꝛ", 1, 2, 1),FCE("ὓ", 0, 2, 1), +FCE("Ὓ", 1, 2, 1),FCE("Ꝝ", 0, 2, 1),FCE("ꝝ", 1, 2, 1),FCE("ὕ", 0, 2, 1), +FCE("Ὕ", 1, 2, 1),FCE("Ꝟ", 0, 2, 1),FCE("ꝟ", 1, 2, 1),FCE("ὗ", 0, 2, 1), +FCE("Ὗ", 1, 2, 1),FCE("Ꝡ", 0, 2, 1),FCE("ꝡ", 1, 2, 1),FCE("Ꚑ", 0, 2, 1), +FCE("ꚑ", 1, 2, 1),FCE("Ꝣ", 0, 2, 1),FCE("ꝣ", 1, 2, 1),FCE("N", 0, 2, 1), +FCE("n", 1, 2, 1),FCE("Ꝥ", 0, 2, 1),FCE("ꝥ", 1, 2, 1),FCE("Ꝧ", 0, 2, 1), +FCE("ꝧ", 1, 2, 1),FCE("ὠ", 0, 2, 1),FCE("Ὠ", 1, 2, 1),FCE("ὡ", 0, 2, 1), +FCE("Ὡ", 1, 2, 1),FCE("ὢ", 0, 2, 1),FCE("Ὢ", 1, 2, 1),FCE("ὣ", 0, 2, 1), +FCE("Ὣ", 1, 2, 1),FCE("ὤ", 0, 2, 1),FCE("Ὤ", 1, 2, 1),FCE("ὥ", 0, 2, 1), +FCE("Ὥ", 1, 2, 1),FCE("ὦ", 0, 2, 1),FCE("Ὦ", 1, 2, 1),FCE("ὧ", 0, 2, 1), +FCE("Ὧ", 1, 2, 1),FCE("Ⱌ", 0, 2, 1),FCE("ⱌ", 1, 2, 1),FCE("Ⲽ", 0, 2, 1), +FCE("ⲽ", 1, 2, 1),FCE("Ꚕ", 0, 2, 1),FCE("ꚕ", 1, 2, 1),FCE("Ꝼ", 0, 2, 1), +FCE("ꝼ", 1, 2, 1),FCE("ᵹ", 0, 2, 1),FCE("Ᵹ", 1, 2, 1),FCE("Ꝿ", 0, 2, 1), +FCE("ꝿ", 1, 2, 1),FCE("ᾀ", 0, 3, 1),FCE("ᾈ", 1, 3, 1),FCE("ἀι", 2, 3, 2), +FCE("ᾁ", 0, 3, 1),FCE("ᾉ", 1, 3, 1),FCE("ἁι", 2, 3, 2),FCE("ᾂ", 0, 3, 1), +FCE("ᾊ", 1, 3, 1),FCE("ἂι", 2, 3, 2),FCE("ᾃ", 0, 3, 1),FCE("ᾋ", 1, 3, 1), +FCE("ἃι", 2, 3, 2),FCE("ᾄ", 0, 3, 1),FCE("ᾌ", 1, 3, 1),FCE("ἄι", 2, 3, 2), +FCE("ᾅ", 0, 3, 1),FCE("ᾍ", 1, 3, 1),FCE("ἅι", 2, 3, 2),FCE("ᾆ", 0, 3, 1), +FCE("ᾎ", 1, 3, 1),FCE("ἆι", 2, 3, 2),FCE("ᾇ", 0, 3, 1),FCE("ᾏ", 1, 3, 1), +FCE("ἇι", 2, 3, 2),FCE("ᾀ", 0, 3, 1),FCE("ᾈ", 1, 3, 1),FCE("ἀι", 2, 3, 2), +FCE("ᾁ", 0, 3, 1),FCE("ᾉ", 1, 3, 1),FCE("ἁι", 2, 3, 2),FCE("ᾂ", 0, 3, 1), +FCE("ᾊ", 1, 3, 1),FCE("ἂι", 2, 3, 2),FCE("ᾃ", 0, 3, 1),FCE("ᾋ", 1, 3, 1), +FCE("ἃι", 2, 3, 2),FCE("ᾄ", 0, 3, 1),FCE("ᾌ", 1, 3, 1),FCE("ἄι", 2, 3, 2), +FCE("ᾅ", 0, 3, 1),FCE("ᾍ", 1, 3, 1),FCE("ἅι", 2, 3, 2),FCE("ᾆ", 0, 3, 1), +FCE("ᾎ", 1, 3, 1),FCE("ἆι", 2, 3, 2),FCE("ᾇ", 0, 3, 1),FCE("ᾏ", 1, 3, 1), +FCE("ἇι", 2, 3, 2),FCE("ᾐ", 0, 3, 1),FCE("ᾘ", 1, 3, 1),FCE("ἠι", 2, 3, 2), +FCE("ᾑ", 0, 3, 1),FCE("ᾙ", 1, 3, 1),FCE("ἡι", 2, 3, 2),FCE("ᾒ", 0, 3, 1), +FCE("ᾚ", 1, 3, 1),FCE("ἢι", 2, 3, 2),FCE("ᾓ", 0, 3, 1),FCE("ᾛ", 1, 3, 1), +FCE("ἣι", 2, 3, 2),FCE("ᾔ", 0, 3, 1),FCE("ᾜ", 1, 3, 1),FCE("ἤι", 2, 3, 2), +FCE("ᾕ", 0, 3, 1),FCE("ᾝ", 1, 3, 1),FCE("ἥι", 2, 3, 2),FCE("ᾖ", 0, 3, 1), +FCE("ᾞ", 1, 3, 1),FCE("ἦι", 2, 3, 2),FCE("ᾗ", 0, 3, 1),FCE("ᾟ", 1, 3, 1), +FCE("ἧι", 2, 3, 2),FCE("ᾐ", 0, 3, 1),FCE("ᾘ", 1, 3, 1),FCE("ἠι", 2, 3, 2), +FCE("ᾑ", 0, 3, 1),FCE("ᾙ", 1, 3, 1),FCE("ἡι", 2, 3, 2),FCE("ᾒ", 0, 3, 1), +FCE("ᾚ", 1, 3, 1),FCE("ἢι", 2, 3, 2),FCE("ᾓ", 0, 3, 1),FCE("ᾛ", 1, 3, 1), +FCE("ἣι", 2, 3, 2),FCE("ᾔ", 0, 3, 1),FCE("ᾜ", 1, 3, 1),FCE("ἤι", 2, 3, 2), +FCE("ᾕ", 0, 3, 1),FCE("ᾝ", 1, 3, 1),FCE("ἥι", 2, 3, 2),FCE("ᾖ", 0, 3, 1), +FCE("ᾞ", 1, 3, 1),FCE("ἦι", 2, 3, 2),FCE("ᾗ", 0, 3, 1),FCE("ᾟ", 1, 3, 1), +FCE("ἧι", 2, 3, 2),FCE("ᾠ", 0, 3, 1),FCE("ᾨ", 1, 3, 1),FCE("ὠι", 2, 3, 2), +FCE("ᾡ", 0, 3, 1),FCE("ᾩ", 1, 3, 1),FCE("ὡι", 2, 3, 2),FCE("ᾢ", 0, 3, 1), +FCE("ᾪ", 1, 3, 1),FCE("ὢι", 2, 3, 2),FCE("ᾣ", 0, 3, 1),FCE("ᾫ", 1, 3, 1), +FCE("ὣι", 2, 3, 2),FCE("ᾤ", 0, 3, 1),FCE("ᾬ", 1, 3, 1),FCE("ὤι", 2, 3, 2), +FCE("ᾥ", 0, 3, 1),FCE("ᾭ", 1, 3, 1),FCE("ὥι", 2, 3, 2),FCE("ᾦ", 0, 3, 1), +FCE("ᾮ", 1, 3, 1),FCE("ὦι", 2, 3, 2),FCE("ᾧ", 0, 3, 1),FCE("ᾯ", 1, 3, 1), +FCE("ὧι", 2, 3, 2),FCE("ᾠ", 0, 3, 1),FCE("ᾨ", 1, 3, 1),FCE("ὠι", 2, 3, 2), +FCE("ᾡ", 0, 3, 1),FCE("ᾩ", 1, 3, 1),FCE("ὡι", 2, 3, 2),FCE("ᾢ", 0, 3, 1), +FCE("ᾪ", 1, 3, 1),FCE("ὢι", 2, 3, 2),FCE("ᾣ", 0, 3, 1),FCE("ᾫ", 1, 3, 1), +FCE("ὣι", 2, 3, 2),FCE("ᾤ", 0, 3, 1),FCE("ᾬ", 1, 3, 1),FCE("ὤι", 2, 3, 2), +FCE("ᾥ", 0, 3, 1),FCE("ᾭ", 1, 3, 1),FCE("ὥι", 2, 3, 2),FCE("ᾦ", 0, 3, 1), +FCE("ᾮ", 1, 3, 1),FCE("ὦι", 2, 3, 2),FCE("ᾧ", 0, 3, 1),FCE("ᾯ", 1, 3, 1), +FCE("ὧι", 2, 3, 2),FCE("ᾲ", 0, 2, 1),FCE("ὰι", 1, 2, 2),FCE("ᾳ", 0, 3, 1), +FCE("ᾼ", 1, 3, 1),FCE("αι", 2, 3, 2),FCE("ᾴ", 0, 2, 1),FCE("άι", 1, 2, 2), +FCE("ᾶ", 0, 2, 1),FCE("ᾶ", 1, 2, 2),FCE("ᾷ", 0, 2, 1),FCE("ᾶι", 1, 2, 3), +FCE("ᾰ", 0, 2, 1),FCE("Ᾰ", 1, 2, 1),FCE("ᾱ", 0, 2, 1),FCE("Ᾱ", 1, 2, 1), +FCE("ὰ", 0, 2, 1),FCE("Ὰ", 1, 2, 1),FCE("ά", 0, 2, 1),FCE("Ά", 1, 2, 1), +FCE("ᾳ", 0, 3, 1),FCE("ᾼ", 1, 3, 1),FCE("αι", 2, 3, 2),FCE("ͅ", 0, 4, 1), +FCE("Ι", 1, 4, 1),FCE("ι", 2, 4, 1),FCE("ι", 3, 4, 1),FCE("ῂ", 0, 2, 1), +FCE("ὴι", 1, 2, 2),FCE("ῃ", 0, 3, 1),FCE("ῌ", 1, 3, 1),FCE("ηι", 2, 3, 2), +FCE("ῄ", 0, 2, 1),FCE("ήι", 1, 2, 2),FCE("𐐌", 0, 2, 1),FCE("𐐴", 1, 2, 1), +FCE("ῆ", 0, 2, 1),FCE("ῆ", 1, 2, 2),FCE("ῇ", 0, 2, 1),FCE("ῆι", 1, 2, 3), +FCE("ὲ", 0, 2, 1),FCE("Ὲ", 1, 2, 1),FCE("έ", 0, 2, 1),FCE("Έ", 1, 2, 1), +FCE("ὴ", 0, 2, 1),FCE("Ὴ", 1, 2, 1),FCE("ή", 0, 2, 1),FCE("Ή", 1, 2, 1), +FCE("ῃ", 0, 3, 1),FCE("ῌ", 1, 3, 1),FCE("ηι", 2, 3, 2),FCE("ῒ", 0, 2, 1), +FCE("ῒ", 1, 2, 3),FCE("ΐ", 0, 3, 1),FCE("ΐ", 1, 3, 1),FCE("ΐ", 2, 3, 3), +FCE("ῖ", 0, 2, 1),FCE("ῖ", 1, 2, 2),FCE("ῗ", 0, 2, 1),FCE("ῗ", 1, 2, 3), +FCE("ῐ", 0, 2, 1),FCE("Ῐ", 1, 2, 1),FCE("ῑ", 0, 2, 1),FCE("Ῑ", 1, 2, 1), +FCE("ὶ", 0, 2, 1),FCE("Ὶ", 1, 2, 1),FCE("ί", 0, 2, 1),FCE("Ί", 1, 2, 1), +FCE("𐐧", 0, 2, 1),FCE("𐑏", 1, 2, 1),FCE("ῢ", 0, 2, 1),FCE("ῢ", 1, 2, 3), +FCE("ΰ", 0, 3, 1),FCE("ΰ", 1, 3, 1),FCE("ΰ", 2, 3, 3),FCE("ῤ", 0, 2, 1), +FCE("ῤ", 1, 2, 2),FCE("ῦ", 0, 2, 1),FCE("ῦ", 1, 2, 2),FCE("ῧ", 0, 2, 1), +FCE("ῧ", 1, 2, 3),FCE("ῠ", 0, 2, 1),FCE("Ῠ", 1, 2, 1),FCE("ῡ", 0, 2, 1), +FCE("Ῡ", 1, 2, 1),FCE("ὺ", 0, 2, 1),FCE("Ὺ", 1, 2, 1),FCE("ύ", 0, 2, 1), +FCE("Ύ", 1, 2, 1),FCE("ῥ", 0, 2, 1),FCE("Ῥ", 1, 2, 1),FCE("𐐁", 0, 2, 1), +FCE("𐐩", 1, 2, 1),FCE("ῲ", 0, 2, 1),FCE("ὼι", 1, 2, 2),FCE("ῳ", 0, 3, 1), +FCE("ῼ", 1, 3, 1),FCE("ωι", 2, 3, 2),FCE("ῴ", 0, 2, 1),FCE("ώι", 1, 2, 2), +FCE("ῶ", 0, 2, 1),FCE("ῶ", 1, 2, 2),FCE("ῷ", 0, 2, 1),FCE("ῶι", 1, 2, 3), +FCE("ὸ", 0, 2, 1),FCE("Ὸ", 1, 2, 1),FCE("ό", 0, 2, 1),FCE("Ό", 1, 2, 1), +FCE("ὼ", 0, 2, 1),FCE("Ὼ", 1, 2, 1),FCE("ώ", 0, 2, 1),FCE("Ώ", 1, 2, 1), +FCE("ῳ", 0, 3, 1),FCE("ῼ", 1, 3, 1),FCE("ωι", 2, 3, 2),FCE("Ⓚ", 0, 2, 1), +FCE("ⓚ", 1, 2, 1),]; +return t; +} + +struct uniProps +{ +private alias _U = immutable(UnicodeProperty); +@property static _U[] tab() pure { return _tab; } +static immutable: +private alias _T = ubyte[]; +_T So = [0x80, 0xa6, 0x1, 0x2, 0x1, 0x4, 0x1, 0x1, 0x1, 0x83, 0xd1, 0x1, 0x81, + 0x8b, 0x2, 0x80, 0xce, 0x1, 0xa, 0x1, 0x13, 0x2, 0x80, 0xf7, 0x1, 0x82, + 0x3, 0x1, 0x81, 0x75, 0x1, 0x80, 0x82, 0x6, 0x1, 0x1, 0x80, 0x84, 0x1, + 0x80, 0xf9, 0x1, 0x81, 0x87, 0x3, 0xf, 0x1, 0x1, 0x3, 0x2, 0x6, 0x14, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x80, 0x85, 0x8, 0x1, 0x6, 0x1, 0x2, 0x5, 0x4, 0x80, + 0xc5, 0x2, 0x82, 0xf0, 0xa, 0x85, 0xa6, 0x1, 0x80, 0x9d, 0x22, 0x81, + 0x61, 0xa, 0x9, 0x9, 0x85, 0x83, 0x2, 0x1, 0x4, 0x1, 0x2, 0xa, 0x1, 0x1, + 0x2, 0x6, 0x6, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x1, 0xb, 0x2, 0xe, 0x1, + 0x1, 0x2, 0x1, 0x1, 0x45, 0x5, 0x2, 0x4, 0x1, 0x2, 0x1, 0x2, 0x1, 0x7, + 0x1, 0x1f, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1f, 0x81, 0xc, 0x8, 0x4, 0x14, 0x2, + 0x7, 0x2, 0x51, 0x1, 0x1e, 0x19, 0x28, 0x6, 0x12, 0xc, 0x27, 0x19, 0xb, + 0x51, 0x4e, 0x16, 0x80, 0xb7, 0x1, 0x9, 0x1, 0x36, 0x8, 0x6f, 0x1, 0x80, + 0x90, 0x1, 0x67, 0x2c, 0x2c, 0x40, 0x81, 0x0, 0x82, 0x0, 0x30, 0x15, 0x2, + 0x9, 0xa, 0x81, 0x8b, 0x6, 0x81, 0x95, 0x1a, 0x1, 0x59, 0xc, 0x80, 0xd6, + 0x1a, 0xc, 0x8, 0x1, 0xd, 0x2, 0xc, 0x1, 0x15, 0x2, 0x6, 0x2, 0x81, 0x50, + 0x2, 0x4, 0xa, 0x20, 0x24, 0x1c, 0x1f, 0xb, 0x1e, 0x8, 0x1, 0xf, 0x20, 0xa, + 0x27, 0xf, 0x3f, 0x1, 0x81, 0x0, 0x99, 0xc0, 0x40, 0xa0, 0x56, 0x90, + 0x37, 0x83, 0x61, 0x4, 0xa, 0x2, 0x1, 0x1, 0x82, 0x3d, 0x3, 0xa0, 0x53, + 0x83, 0x1, 0x81, 0xe6, 0x1, 0x3, 0x1, 0x4, 0x2, 0xd, 0x2, 0x81, 0x39, + 0x9, 0x39, 0x11, 0x6, 0xc, 0x34, 0x2d, 0xa0, 0xce, 0x3, 0x80, 0xf6, 0xa, + 0x27, 0x2, 0x3c, 0x5, 0x3, 0x16, 0x2, 0x7, 0x1e, 0x4, 0x30, 0x22, 0x42, + 0x3, 0x1, 0x80, 0xba, 0x57, 0x9c, 0xa9, 0x2c, 0x4, 0x64, 0xc, 0xf, 0x2, + 0xe, 0x2, 0xf, 0x1, 0xf, 0x30, 0x1f, 0x1, 0x3c, 0x4, 0x2b, 0x4b, 0x1d, + 0xd, 0x2b, 0x5, 0x9, 0x7, 0x2, 0x80, 0xae, 0x21, 0xf, 0x6, 0x1, 0x46, + 0x3, 0x14, 0xc, 0x25, 0x1, 0x5, 0x15, 0x11, 0xf, 0x3f, 0x1, 0x1, 0x1, + 0x80, 0xb6, 0x1, 0x4, 0x3, 0x3e, 0x2, 0x4, 0xc, 0x18, 0x80, 0x93, 0x46, + 0x4, 0xb, 0x30, 0x46, 0x3a, 0x74]; +_T Pf = [0x80, 0xbb, 0x1, 0x9f, 0x5d, 0x1, 0x3, 0x1, 0x1c, 0x1, 0x8d, 0xc8, + 0x1, 0x1, 0x1, 0x4, 0x1, 0x2, 0x1, 0xf, 0x1, 0x3, 0x1]; +_T Bidi_Control = [0x86, 0x1c, 0x1, 0x99, 0xf1, 0x2, 0x1a, 0x5, 0x37, 0x4]; +_T Hex_Digit = [0x30, 0xa, 0x7, 0x6, 0x1a, 0x6, 0xa0, 0xfe, 0xa9, 0xa, 0x7, 0x6, 0x1a, + 0x6]; +_T Other_Lowercase = [ + 0x80, 0xaa, 0x1, 0xf, 0x1, 0x81, 0xf5, 0x9, 0x7, 0x2, 0x1e, 0x5, 0x60, 0x1, + 0x34, 0x1, 0x99, 0xb1, 0x3f, 0xd, 0x1, 0x22, 0x25, 0x82, 0xb1, 0x1, 0xd, + 0x1, 0x10, 0xd, 0x80, 0xd3, 0x10, 0x83, 0x50, 0x1a, 0x87, 0x92, 0x2, 0xa0, + 0x7a, 0xf2, 0x1, 0x80, 0x87, 0x2 +]; +_T Quotation_Mark = [ + 0x22, 0x1, 0x4, 0x1, 0x80, 0x83, 0x1, 0xf, 0x1, 0x9f, 0x5c, 0x8, 0x19, 0x2, + 0x8f, 0xd1, 0x4, 0xd, 0x3, 0xa0, 0xce, 0x21, 0x4, 0x80, 0xbd, 0x1, 0x4, 0x1, 0x5a, + 0x2 +]; +_T XID_Start = [ + 0x41, 0x1a, 0x6, 0x1a, 0x2f, 0x1, 0xa, 0x1, 0x4, 0x1, 0x5, 0x17, 0x1, 0x1f, + 0x1, 0x81, 0xca, 0x4, 0xc, 0xe, 0x5, 0x7, 0x1, 0x1, 0x1, 0x80, 0x81, 0x5, + 0x1, 0x2, 0x3, 0x3, 0x8, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x14, 0x1, 0x53, + 0x1, 0x80, 0x8b, 0x8, 0x80, 0x9e, 0x9, 0x26, 0x2, 0x1, 0x7, 0x27, 0x48, + 0x1b, 0x5, 0x3, 0x2d, 0x2b, 0x23, 0x2, 0x1, 0x63, 0x1, 0x1, 0xf, 0x2, 0x7, + 0x2, 0xa, 0x3, 0x2, 0x1, 0x10, 0x1, 0x1, 0x1e, 0x1d, 0x59, 0xb, 0x1, + 0x18, 0x21, 0x9, 0x2, 0x4, 0x1, 0x5, 0x16, 0x4, 0x1, 0x9, 0x1, 0x3, 0x1, + 0x17, 0x19, 0x47, 0x1, 0x1, 0xb, 0x57, 0x36, 0x3, 0x1, 0x12, 0x1, 0x7, + 0xa, 0xf, 0x7, 0x1, 0x7, 0x5, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, + 0x1, 0x3, 0x4, 0x3, 0x1, 0x10, 0x1, 0xd, 0x2, 0x1, 0x3, 0xe, 0x2, 0x13, + 0x6, 0x4, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x2, 0x1, 0x2, 0x1f, + 0x4, 0x1, 0x1, 0x13, 0x3, 0x10, 0x9, 0x1, 0x3, 0x1, 0x16, 0x1, 0x7, 0x1, + 0x2, 0x1, 0x5, 0x3, 0x1, 0x12, 0x1, 0xf, 0x2, 0x23, 0x8, 0x2, 0x2, 0x2, + 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x3, 0x1, 0x1e, 0x2, 0x1, 0x3, 0xf, + 0x1, 0x11, 0x1, 0x1, 0x6, 0x3, 0x3, 0x1, 0x4, 0x3, 0x2, 0x1, 0x1, 0x1, + 0x2, 0x3, 0x2, 0x3, 0x3, 0x3, 0xc, 0x16, 0x1, 0x34, 0x8, 0x1, 0x3, 0x1, + 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x1, 0x1a, 0x2, 0x6, 0x2, 0x23, 0x8, 0x1, + 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x1, 0x20, 0x1, 0x1, 0x2, 0xf, + 0x2, 0x12, 0x8, 0x1, 0x3, 0x1, 0x29, 0x2, 0x1, 0x10, 0x1, 0x11, 0x2, + 0x18, 0x6, 0x5, 0x12, 0x3, 0x18, 0x1, 0x9, 0x1, 0x1, 0x2, 0x7, 0x3a, 0x30, + 0x1, 0x1, 0xd, 0x7, 0x3a, 0x2, 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x2, 0x1, + 0x6, 0x4, 0x1, 0x7, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0x4, 0x1, + 0x1, 0xa, 0x1, 0x2, 0x5, 0x1, 0x1, 0x15, 0x4, 0x20, 0x1, 0x3f, 0x8, 0x1, + 0x24, 0x1b, 0x5, 0x73, 0x2b, 0x14, 0x1, 0x10, 0x6, 0x4, 0x4, 0x3, 0x1, + 0x3, 0x2, 0x7, 0x3, 0x4, 0xd, 0xc, 0x1, 0x11, 0x26, 0x1, 0x1, 0x5, 0x1, + 0x2, 0x2b, 0x1, 0x81, 0x4d, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, + 0x29, 0x1, 0x4, 0x2, 0x21, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, + 0xf, 0x1, 0x39, 0x1, 0x4, 0x2, 0x43, 0x25, 0x10, 0x10, 0x55, 0xc, 0x82, + 0x6c, 0x2, 0x11, 0x1, 0x1a, 0x5, 0x4b, 0x3, 0x3, 0xf, 0xd, 0x1, 0x4, 0xe, + 0x12, 0xe, 0x12, 0xe, 0xd, 0x1, 0x3, 0xf, 0x34, 0x23, 0x1, 0x4, 0x1, + 0x43, 0x58, 0x8, 0x29, 0x1, 0x1, 0x5, 0x46, 0xa, 0x1d, 0x33, 0x1e, 0x2, + 0x5, 0xb, 0x2c, 0x15, 0x7, 0x38, 0x17, 0x9, 0x35, 0x52, 0x1, 0x5d, 0x2f, + 0x11, 0x7, 0x37, 0x1e, 0xd, 0x2, 0xa, 0x2c, 0x1a, 0x24, 0x29, 0x3, 0xa, + 0x24, 0x6b, 0x4, 0x1, 0x4, 0x3, 0x2, 0x9, 0x80, 0xc0, 0x40, 0x81, 0x16, + 0x2, 0x6, 0x2, 0x26, 0x2, 0x6, 0x2, 0x8, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1f, 0x2, 0x35, 0x1, 0x7, 0x1, 0x1, 0x3, 0x3, 0x1, 0x7, 0x3, 0x4, 0x2, + 0x6, 0x4, 0xd, 0x5, 0x3, 0x1, 0x7, 0x74, 0x1, 0xd, 0x1, 0x10, 0xd, 0x65, + 0x1, 0x4, 0x1, 0x2, 0xa, 0x1, 0x1, 0x2, 0x6, 0x6, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x10, 0x2, 0x4, 0x5, 0x5, 0x4, 0x1, 0x11, 0x29, 0x8a, 0x77, 0x2f, + 0x1, 0x2f, 0x1, 0x80, 0x85, 0x6, 0x4, 0x3, 0x2, 0xc, 0x26, 0x1, 0x1, 0x5, + 0x1, 0x2, 0x38, 0x7, 0x1, 0x10, 0x17, 0x9, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, + 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x82, 0x26, 0x3, 0x19, 0x9, + 0x7, 0x5, 0x2, 0x5, 0x4, 0x56, 0x6, 0x3, 0x1, 0x5a, 0x1, 0x4, 0x5, 0x29, + 0x3, 0x5e, 0x11, 0x1b, 0x35, 0x10, 0x82, 0x0, 0x99, 0xb6, 0x4a, 0xa0, 0x51, + 0xcd, 0x33, 0x84, 0x8d, 0x43, 0x2e, 0x2, 0x81, 0xd, 0x3, 0x10, 0xa, 0x2, + 0x14, 0x2f, 0x10, 0x19, 0x8, 0x50, 0x27, 0x9, 0x2, 0x67, 0x2, 0x4, 0x1, + 0x4, 0xc, 0xb, 0x4d, 0xa, 0x1, 0x3, 0x1, 0x4, 0x1, 0x17, 0x1d, 0x34, 0xe, + 0x32, 0x3e, 0x6, 0x3, 0x1, 0xe, 0x1c, 0xa, 0x17, 0x19, 0x1d, 0x7, 0x2f, + 0x1c, 0x1, 0x30, 0x29, 0x17, 0x3, 0x1, 0x8, 0x14, 0x17, 0x3, 0x1, 0x5, + 0x30, 0x1, 0x1, 0x3, 0x2, 0x2, 0x5, 0x2, 0x1, 0x1, 0x1, 0x18, 0x3, 0x2, + 0xb, 0x7, 0x3, 0xc, 0x6, 0x2, 0x6, 0x2, 0x6, 0x9, 0x7, 0x1, 0x7, 0x80, + 0x91, 0x23, 0x1d, 0xa0, 0x2b, 0xa4, 0xc, 0x17, 0x4, 0x31, 0xa0, 0x21, 0x4, + 0x81, 0x6e, 0x2, 0x6a, 0x26, 0x7, 0xc, 0x5, 0x5, 0x1, 0x1, 0xa, 0x1, 0xd, + 0x1, 0x5, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x6c, 0x21, 0x80, 0x8b, 0x6, + 0x80, 0xda, 0x12, 0x40, 0x2, 0x36, 0x28, 0xa, 0x77, 0x1, 0x1, 0x1, 0x3, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x7e, 0x24, 0x1a, 0x6, 0x1a, 0xb, + 0x38, 0x2, 0x1f, 0x3, 0x6, 0x2, 0x6, 0x2, 0x6, 0x2, 0x3, 0x23, 0xc, 0x1, + 0x1a, 0x1, 0x13, 0x1, 0x2, 0x1, 0xf, 0x2, 0xe, 0x22, 0x7b, 0x45, 0x35, + 0x81, 0xb, 0x1d, 0x3, 0x31, 0x2f, 0x1f, 0x11, 0x1b, 0x35, 0x1e, 0x2, 0x24, + 0x4, 0x8, 0x1, 0x5, 0x2a, 0x80, 0x9e, 0x83, 0x62, 0x6, 0x2, 0x1, 0x1, + 0x2c, 0x1, 0x2, 0x3, 0x1, 0x2, 0x17, 0x80, 0xaa, 0x16, 0xa, 0x1a, 0x46, + 0x38, 0x6, 0x2, 0x40, 0x1, 0xf, 0x4, 0x1, 0x3, 0x1, 0x1b, 0x2c, 0x1d, 0x80, + 0x83, 0x36, 0xa, 0x16, 0xa, 0x13, 0x80, 0x8d, 0x49, 0x83, 0xba, 0x35, 0x4b, + 0x2d, 0x20, 0x19, 0x1a, 0x24, 0x5c, 0x30, 0xe, 0x4, 0x84, 0xbb, 0x2b, 0x89, + 0x55, 0x83, 0x6f, 0x80, 0x91, 0x63, 0x8b, 0x9d, 0x84, 0x2f, 0xa0, 0x33, + 0xd1, 0x82, 0x39, 0x84, 0xc7, 0x45, 0xb, 0x1, 0x42, 0xd, 0xa0, 0x40, 0x60, + 0x2, 0xa0, 0x23, 0xfe, 0x55, 0x1, 0x47, 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, + 0x4, 0x1, 0xc, 0x1, 0x1, 0x1, 0x7, 0x1, 0x41, 0x1, 0x4, 0x2, 0x8, 0x1, + 0x7, 0x1, 0x1c, 0x1, 0x4, 0x1, 0x5, 0x1, 0x1, 0x3, 0x7, 0x1, 0x81, 0x54, + 0x2, 0x19, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, + 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x8, 0x96, 0x34, 0x4, 0x1, + 0x1b, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0xa, 0x1, 0x4, 0x1, 0x1, 0x1, + 0x1, 0x6, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x2, 0x1, 0x1, + 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, + 0x4, 0x1, 0x7, 0x1, 0x4, 0x1, 0x4, 0x1, 0x1, 0x1, 0xa, 0x1, 0x11, 0x5, + 0x3, 0x1, 0x5, 0x1, 0x11, 0x91, 0x44, 0xa0, 0xa6, 0xd7, 0x29, 0x90, 0x35, + 0xb, 0x80, 0xde, 0xa0, 0x3f, 0xe2, 0x82, 0x1e +]; +_T Terminal_Punctuation = [ + 0x21, 0x1, 0xa, 0x1, 0x1, 0x1, 0xb, 0x2, 0x3, 0x1, 0x83, 0x3e, 0x1, 0x8, + 0x1, 0x82, 0x1, 0x1, 0x39, 0x1, 0x48, 0x1, 0xe, 0x1, 0x3, 0x1, 0x80, + 0xb4, 0x1, 0x2b, 0xb, 0x1, 0x1, 0x80, 0xeb, 0x2, 0x36, 0xf, 0x1f, 0x1, + 0x81, 0x5, 0x2, 0x84, 0xf4, 0x2, 0x80, 0xac, 0x1, 0x4, 0x6, 0x81, 0x37, + 0x2, 0x83, 0x15, 0x8, 0x83, 0x4, 0x2, 0x7c, 0x3, 0x80, 0xe6, 0x3, 0x3, + 0x1, 0x27, 0x4, 0x2, 0x2, 0x81, 0x3a, 0x2, 0x81, 0x62, 0x4, 0x80, 0xae, + 0x2, 0x1, 0x3, 0x80, 0xdb, 0x5, 0x3e, 0x2, 0x83, 0xbc, 0x2, 0x9, 0x3, 0x8d, + 0xe4, 0x1, 0x81, 0xd2, 0x2, 0xa0, 0x74, 0xfb, 0x2, 0x81, 0xd, 0x3, 0x80, + 0xe3, 0x5, 0x81, 0x7e, 0x2, 0x56, 0x2, 0x5f, 0x1, 0x80, 0x97, 0x3, 0x80, + 0x93, 0x3, 0x7f, 0x1, 0x10, 0x2, 0x80, 0xf9, 0x1, 0xa0, 0x52, 0x64, 0x3, + 0x1, 0x4, 0x80, 0xa9, 0x1, 0xa, 0x1, 0x1, 0x1, 0xb, 0x2, 0x3, 0x1, 0x41, + 0x1, 0x2, 0x1, 0x84, 0x3a, 0x1, 0x30, 0x1, 0x84, 0x86, 0x1, 0x80, 0xc7, + 0x1, 0x82, 0x1a, 0x6, 0x85, 0x7, 0x7, 0x70, 0x4, 0x7f, 0x3, 0x80, 0x81, 0x2, 0x92, + 0xa9, 0x4 +]; +_T Math = [0x2b, 0x1, 0x10, 0x3, 0x1f, 0x1, 0x1d, 0x1, 0x1, 0x1, 0x2d, 0x1, + 0x4, 0x1, 0x25, 0x1, 0x1f, 0x1, 0x82, 0xd8, 0x3, 0x2, 0x1, 0x1a, 0x2, + 0x2, 0x3, 0x82, 0xf, 0x3, 0x9a, 0xd, 0x1, 0x1b, 0x3, 0xb, 0x1, 0x3, 0x1, + 0xd, 0x1, 0xe, 0x4, 0x15, 0x5, 0xb, 0x5, 0x41, 0xd, 0x4, 0x1, 0x3, 0x2, + 0x4, 0x5, 0x12, 0x1, 0x4, 0x1, 0x2, 0xa, 0x1, 0x1, 0x2, 0x6, 0x6, 0x1, + 0x3, 0x2, 0x2, 0x2, 0x1, 0x3, 0x1, 0x6, 0x3, 0xe, 0x1, 0x1, 0x44, 0x18, + 0x1, 0x6, 0x1, 0x2, 0x4, 0x2, 0x4, 0x20, 0x1, 0x1, 0x6, 0x2, 0xe, 0x81, + 0xc, 0x8, 0x4, 0x14, 0x2, 0x5a, 0x1, 0x1e, 0x1b, 0x1, 0x1, 0x18, 0x1, + 0xb, 0x7, 0x81, 0xbd, 0x2, 0xc, 0xa, 0x4, 0x6, 0x4, 0x2, 0x2, 0x2, 0x3, + 0x5, 0xe, 0x1, 0x1, 0x1, 0x2, 0x6, 0xb, 0x8, 0x5, 0x2, 0x39, 0x1, 0x1, + 0x1, 0x1d, 0x4, 0x9, 0x3, 0x81, 0x50, 0x40, 0x81, 0x0, 0x82, 0x0, 0x30, + 0x15, 0x2, 0x6, 0xa0, 0xcf, 0xdc, 0x1, 0x83, 0x37, 0x6, 0x1, 0x1, 0x80, + 0xa2, 0x1, 0x10, 0x3, 0x1d, 0x1, 0x1, 0x1, 0x1d, 0x1, 0x1, 0x1, 0x80, + 0x83, 0x1, 0x6, 0x4, 0xa0, 0xd4, 0x13, 0x55, 0x1, 0x47, 0x1, 0x2, 0x2, + 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0xc, 0x1, 0x1, 0x1, 0x7, 0x1, 0x41, 0x1, + 0x4, 0x2, 0x8, 0x1, 0x7, 0x1, 0x1c, 0x1, 0x4, 0x1, 0x5, 0x1, 0x1, 0x3, 0x7, + 0x1, 0x81, 0x54, 0x2, 0x81, 0x24, 0x2, 0x32, 0x96, 0x0, 0x4, 0x1, 0x1b, + 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0xa, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x6, + 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x4, 0x1, + 0x7, 0x1, 0x4, 0x1, 0x4, 0x1, 0x1, 0x1, 0xa, 0x1, 0x11, 0x5, 0x3, 0x1, 0x5, + 0x1, 0x11, 0x34, 0x2]; +_T Lu = [0x41, 0x1a, 0x65, 0x17, 0x1, 0x7, 0x21, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x3, 0x2, 0x1, + 0x1, 0x1, 0x2, 0x1, 0x3, 0x2, 0x4, 0x1, 0x2, 0x1, 0x3, 0x3, 0x2, 0x1, 0x2, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x3, 0x1, + 0x1, 0x1, 0x2, 0x3, 0x1, 0x7, 0x1, 0x2, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x2, 0x1, 0x2, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x7, 0x2, 0x1, 0x2, 0x2, 0x1, 0x1, 0x4, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x81, 0x21, 0x1, 0x1, 0x1, 0x3, 0x1, 0xf, 0x1, + 0x1, 0x3, 0x1, 0x1, 0x1, 0x2, 0x1, 0x11, 0x1, 0x9, 0x23, 0x1, 0x2, 0x3, + 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x5, 0x1, 0x2, 0x1, 0x1, + 0x2, 0x2, 0x33, 0x30, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x9, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0xa, + 0x26, 0x8b, 0x49, 0x26, 0x1, 0x1, 0x5, 0x1, 0x8d, 0x32, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x9, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x9, 0x8, 0x8, 0x6, 0xa, 0x8, 0x8, 0x8, 0x8, 0x6, 0xb, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x8, 0x8, 0x48, 0x4, 0xc, 0x4, 0xc, + 0x4, 0xc, 0x5, 0xb, 0x4, 0x81, 0x6, 0x1, 0x4, 0x1, 0x3, 0x3, 0x2, 0x3, + 0x2, 0x1, 0x3, 0x5, 0x6, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x2, 0x4, + 0xa, 0x2, 0x5, 0x1, 0x3d, 0x1, 0x8a, 0x7c, 0x2f, 0x31, 0x1, 0x1, 0x3, 0x2, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x1, 0x1, 0x2, 0x1, 0x8, 0x3, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x8, 0x1, 0x1, 0x1, 0x4, 0x1, 0xa0, 0x79, + 0x4d, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x13, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x80, + 0x8b, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0xa, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x4, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0xd, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0xa0, 0x57, 0x76, 0x1a, 0x84, 0xc5, 0x28, + 0xa0, 0xcf, 0xd8, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1, 0x1, 0x2, 0x2, + 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0x8, 0x1a, 0x1a, 0x1a, 0x2, 0x1, 0x4, 0x2, + 0x8, 0x1, 0x7, 0x1b, 0x2, 0x1, 0x4, 0x1, 0x5, 0x1, 0x1, 0x3, 0x7, 0x1b, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1e, + 0x19, 0x21, 0x19, 0x21, 0x19, 0x21, 0x19, 0x21, 0x19, 0x21, 0x1]; +_T Other_Uppercase = [0xa0, 0x21, 0x60, 0x10, 0x83, 0x46, 0x1a]; +_T Sk = [0x5e, 0x1, 0x1, 0x1, 0x47, 0x1, 0x6, 0x1, 0x4, 0x1, 0x3, 0x1, 0x82, + 0x9, 0x4, 0xc, 0xe, 0x5, 0x7, 0x1, 0x1, 0x1, 0x11, 0x75, 0x1, 0xe, 0x2, + 0x9c, 0x37, 0x1, 0x1, 0x3, 0xb, 0x3, 0xd, 0x3, 0xd, 0x3, 0xd, 0x2, 0x90, + 0x9c, 0x2, 0xa0, 0x76, 0x63, 0x17, 0x9, 0x2, 0x67, 0x2, 0xa0, 0x54, 0x27, + 0x10, 0x83, 0x7c, 0x1, 0x1, 0x1, 0x80, 0xa2, 0x1]; +_T Other_ID_Start = [0xa0, 0x21, 0x18, 0x1, 0x15, 0x1, 0x8f, 0x6c, 0x2]; +_T Nl = [0x96, 0xee, 0x3, 0x8a, 0x6f, 0x23, 0x2, 0x4, 0x8e, 0x7e, 0x1, 0x19, + 0x9, 0xe, 0x3, 0xa0, 0x76, 0xab, 0xa, 0xa0, 0x5a, 0x50, 0x35, 0x81, 0xcc, + 0x1, 0x8, 0x1, 0x80, 0x86, 0x5, 0xa0, 0x20, 0x2a, 0x63]; +_T Other_Alphabetic = [ + 0x83, 0x45, 0x1, 0x82, 0x6a, 0xe, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, + 0x48, 0xb, 0x30, 0xd, 0x1, 0x7, 0x10, 0x1, 0x65, 0x7, 0x4, 0x4, 0x2, 0x2, + 0x4, 0x1, 0x23, 0x1, 0x1e, 0x10, 0x66, 0xb, 0x65, 0x2, 0x3, 0x9, 0x1, + 0x3, 0x1, 0x4, 0x80, 0xb7, 0x6, 0x6, 0xf, 0x1, 0x4, 0x36, 0x2, 0x2, 0xf, + 0x1, 0x2, 0x5, 0x3, 0xa, 0x2, 0x1d, 0x3, 0x3a, 0x7, 0x2, 0x2, 0x2, 0x2, + 0xa, 0x1, 0xa, 0x2, 0x1d, 0x3, 0x3a, 0x5, 0x4, 0x2, 0x2, 0x2, 0x4, 0x1, + 0x1e, 0x2, 0x3, 0x1, 0xb, 0x3, 0x3a, 0x8, 0x1, 0x3, 0x1, 0x2, 0x15, 0x2, + 0x1d, 0x3, 0x3a, 0x7, 0x2, 0x2, 0x2, 0x2, 0x9, 0x2, 0xa, 0x2, 0x1e, 0x1, + 0x3b, 0x5, 0x3, 0x3, 0x1, 0x3, 0xa, 0x1, 0x29, 0x3, 0x3a, 0x7, 0x1, 0x3, + 0x1, 0x3, 0x8, 0x2, 0xb, 0x2, 0x1e, 0x2, 0x3a, 0x7, 0x1, 0x3, 0x1, 0x3, + 0x8, 0x2, 0xb, 0x2, 0x1e, 0x2, 0x3a, 0x7, 0x1, 0x3, 0x1, 0x3, 0xa, 0x1, + 0xa, 0x2, 0x1e, 0x2, 0x4b, 0x6, 0x1, 0x1, 0x1, 0x8, 0x12, 0x2, 0x3d, 0x1, + 0x2, 0x7, 0x12, 0x1, 0x63, 0x1, 0x2, 0x6, 0x1, 0x2, 0x10, 0x1, 0x80, + 0xa3, 0x11, 0xb, 0xb, 0x1, 0x24, 0x6e, 0xc, 0x1, 0x1, 0x2, 0x4, 0x17, 0x4, + 0x4, 0x3, 0x1, 0x1, 0x4, 0x2, 0x8, 0x4, 0xd, 0x5, 0x15, 0x2, 0x82, 0xc1, + 0x1, 0x83, 0xb2, 0x2, 0x1e, 0x2, 0x1e, 0x2, 0x1e, 0x2, 0x42, 0x13, 0x80, + 0xe0, 0x1, 0x76, 0xc, 0x4, 0x9, 0x77, 0x11, 0x7, 0x2, 0x4d, 0x5, 0x39, 0xa, + 0x2, 0x14, 0x80, 0x8b, 0x5, 0x30, 0xf, 0x3c, 0x3, 0x1e, 0x9, 0x2, 0x2, + 0x39, 0xb, 0x32, 0x12, 0x80, 0xbc, 0x2, 0x87, 0xc2, 0x34, 0x88, 0xf6, 0x20, + 0xa0, 0x78, 0x74, 0x8, 0x23, 0x1, 0x81, 0x83, 0x5, 0x58, 0x2, 0x32, 0x10, + 0x62, 0x5, 0x1c, 0xc, 0x2d, 0x4, 0x30, 0xc, 0x69, 0xe, 0xc, 0x1, 0x8, + 0x2, 0x62, 0x1, 0x1, 0x3, 0x2, 0x2, 0x5, 0x1, 0x2c, 0x5, 0x5, 0x1, 0x80, + 0xed, 0x8, 0xa0, 0x4f, 0x33, 0x1, 0x8e, 0xe2, 0x3, 0x1, 0x2, 0x5, 0x4, + 0x85, 0xf0, 0x3, 0x35, 0xe, 0x3c, 0x1, 0x2d, 0x9, 0x47, 0x3, 0x24, 0xc, + 0x4d, 0x3, 0x30, 0xd, 0x84, 0xeb, 0xb, 0xa0, 0x58, 0x9b, 0x2e +]; +_T Alphabetic = [ + 0x41, 0x1a, 0x6, 0x1a, 0x2f, 0x1, 0xa, 0x1, 0x4, 0x1, 0x5, 0x17, 0x1, 0x1f, + 0x1, 0x81, 0xca, 0x4, 0xc, 0xe, 0x5, 0x7, 0x1, 0x1, 0x1, 0x56, 0x1, 0x2a, + 0x5, 0x1, 0x2, 0x2, 0x4, 0x8, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x14, 0x1, + 0x53, 0x1, 0x80, 0x8b, 0x8, 0x80, 0x9e, 0x9, 0x26, 0x2, 0x1, 0x7, 0x27, + 0x28, 0xe, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, 0x8, 0x1b, 0x5, 0x3, + 0x1d, 0xb, 0x5, 0x38, 0x1, 0x7, 0xe, 0x66, 0x1, 0x8, 0x4, 0x8, 0x4, 0x3, + 0xa, 0x3, 0x2, 0x1, 0x10, 0x30, 0xd, 0x65, 0x18, 0x21, 0x9, 0x2, 0x4, + 0x1, 0x5, 0x18, 0x2, 0x13, 0x13, 0x19, 0x47, 0x1, 0x1, 0xb, 0x37, 0x6, + 0x6, 0xf, 0x1, 0x3c, 0x1, 0x10, 0x1, 0x3, 0x4, 0xf, 0xd, 0x7, 0x1, 0x7, + 0x1, 0x3, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x1, 0x3, 0x4, + 0x3, 0x8, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x8, 0x1, 0x4, 0x2, 0x1, 0x5, 0xc, + 0x2, 0xf, 0x3, 0x1, 0x6, 0x4, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, + 0x2, 0x1, 0x2, 0x4, 0x5, 0x4, 0x2, 0x2, 0x2, 0x4, 0x1, 0x7, 0x4, 0x1, + 0x1, 0x11, 0x6, 0xb, 0x3, 0x1, 0x9, 0x1, 0x3, 0x1, 0x16, 0x1, 0x7, 0x1, + 0x2, 0x1, 0x5, 0x3, 0x9, 0x1, 0x3, 0x1, 0x2, 0x3, 0x1, 0xf, 0x4, 0x1d, + 0x3, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x3, + 0x8, 0x2, 0x2, 0x2, 0x2, 0x9, 0x2, 0x4, 0x2, 0x1, 0x5, 0xd, 0x1, 0x10, + 0x2, 0x1, 0x6, 0x3, 0x3, 0x1, 0x4, 0x3, 0x2, 0x1, 0x1, 0x1, 0x2, 0x3, 0x2, + 0x3, 0x3, 0x3, 0xc, 0x4, 0x5, 0x3, 0x3, 0x1, 0x3, 0x3, 0x1, 0x6, 0x1, + 0x29, 0x3, 0x1, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x8, + 0x1, 0x3, 0x1, 0x3, 0x8, 0x2, 0x1, 0x2, 0x6, 0x4, 0x1e, 0x2, 0x1, 0x8, + 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x8, 0x1, 0x3, 0x1, 0x3, + 0x8, 0x2, 0x7, 0x1, 0x1, 0x4, 0xd, 0x2, 0xf, 0x2, 0x1, 0x8, 0x1, 0x3, + 0x1, 0x29, 0x2, 0x8, 0x1, 0x3, 0x1, 0x3, 0x1, 0x1, 0x8, 0x1, 0x8, 0x4, + 0x16, 0x6, 0x2, 0x2, 0x1, 0x12, 0x3, 0x18, 0x1, 0x9, 0x1, 0x1, 0x2, 0x7, + 0x8, 0x6, 0x1, 0x1, 0x1, 0x8, 0x12, 0x2, 0xd, 0x3a, 0x5, 0x7, 0x6, 0x1, + 0x33, 0x2, 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x2, 0x1, 0x6, 0x4, 0x1, 0x7, + 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0xd, 0x1, 0x3, 0x2, 0x5, 0x1, + 0x1, 0x6, 0x1, 0xe, 0x4, 0x20, 0x1, 0x3f, 0x8, 0x1, 0x24, 0x4, 0x11, 0x6, + 0x10, 0x1, 0x24, 0x43, 0x37, 0x1, 0x1, 0x2, 0x5, 0x10, 0x13, 0x2, 0x4, + 0x5, 0x19, 0x7, 0x1, 0xd, 0x2, 0x2, 0x26, 0x1, 0x1, 0x5, 0x1, 0x2, 0x2b, + 0x1, 0x81, 0x4d, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0x29, 0x1, + 0x4, 0x2, 0x21, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0xf, 0x1, + 0x39, 0x1, 0x4, 0x2, 0x43, 0x4, 0x1, 0x20, 0x10, 0x10, 0x55, 0xc, 0x82, + 0x6c, 0x2, 0x11, 0x1, 0x1a, 0x5, 0x4b, 0x3, 0x3, 0xf, 0xd, 0x1, 0x6, 0xc, + 0x14, 0xc, 0x14, 0xc, 0xd, 0x1, 0x3, 0x1, 0x2, 0xc, 0x34, 0x2, 0x13, 0xe, + 0x1, 0x4, 0x1, 0x43, 0x58, 0x8, 0x2b, 0x5, 0x46, 0xa, 0x1d, 0x3, 0xc, + 0x4, 0x9, 0x17, 0x1e, 0x2, 0x5, 0xb, 0x2c, 0x4, 0x1a, 0x36, 0x1c, 0x4, + 0x3f, 0x2, 0x14, 0x32, 0x1, 0x58, 0x34, 0x1, 0xf, 0x1, 0x7, 0x34, 0x2a, + 0x2, 0x4, 0xa, 0x2c, 0x1, 0xb, 0xe, 0x36, 0x17, 0x3, 0xa, 0x24, 0x6b, 0x4, + 0x1, 0x6, 0x1, 0x2, 0x9, 0x80, 0xc0, 0x40, 0x81, 0x16, 0x2, 0x6, 0x2, + 0x26, 0x2, 0x6, 0x2, 0x8, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1f, 0x2, + 0x35, 0x1, 0x7, 0x1, 0x1, 0x3, 0x3, 0x1, 0x7, 0x3, 0x4, 0x2, 0x6, 0x4, + 0xd, 0x5, 0x3, 0x1, 0x7, 0x74, 0x1, 0xd, 0x1, 0x10, 0xd, 0x65, 0x1, 0x4, + 0x1, 0x2, 0xa, 0x1, 0x1, 0x3, 0x5, 0x6, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, + 0x1, 0xb, 0x2, 0x4, 0x5, 0x5, 0x4, 0x1, 0x11, 0x29, 0x83, 0x2d, 0x34, 0x87, + 0x16, 0x2f, 0x1, 0x2f, 0x1, 0x80, 0x85, 0x6, 0x4, 0x3, 0x2, 0xc, 0x26, + 0x1, 0x1, 0x5, 0x1, 0x2, 0x38, 0x7, 0x1, 0x10, 0x17, 0x9, 0x7, 0x1, 0x7, + 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x20, + 0x2f, 0x1, 0x81, 0xd5, 0x3, 0x19, 0x9, 0x7, 0x5, 0x2, 0x5, 0x4, 0x56, + 0x6, 0x3, 0x1, 0x5a, 0x1, 0x4, 0x5, 0x29, 0x3, 0x5e, 0x11, 0x1b, 0x35, + 0x10, 0x82, 0x0, 0x99, 0xb6, 0x4a, 0xa0, 0x51, 0xcd, 0x33, 0x84, 0x8d, + 0x43, 0x2e, 0x2, 0x81, 0xd, 0x3, 0x10, 0xa, 0x2, 0x14, 0x2f, 0x5, 0x8, 0x3, + 0x19, 0x7, 0x51, 0x27, 0x9, 0x2, 0x67, 0x2, 0x4, 0x1, 0x4, 0xc, 0xb, 0x4d, + 0xa, 0x1, 0x3, 0x1, 0x4, 0x1, 0x1c, 0x18, 0x34, 0xc, 0x44, 0x2e, 0x6, + 0x3, 0x1, 0xe, 0x21, 0x5, 0x23, 0xd, 0x1d, 0x3, 0x33, 0x1, 0xc, 0xf, 0x1, + 0x30, 0x37, 0x9, 0xe, 0x12, 0x17, 0x3, 0x1, 0x5, 0x3f, 0x1, 0x1, 0x1, + 0x1, 0x18, 0x3, 0x2, 0x10, 0x2, 0x4, 0xb, 0x6, 0x2, 0x6, 0x2, 0x6, 0x9, + 0x7, 0x1, 0x7, 0x80, 0x91, 0x2b, 0x15, 0xa0, 0x2b, 0xa4, 0xc, 0x17, 0x4, + 0x31, 0xa0, 0x21, 0x4, 0x81, 0x6e, 0x2, 0x6a, 0x26, 0x7, 0xc, 0x5, 0x5, + 0xc, 0x1, 0xd, 0x1, 0x5, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x6c, 0x21, + 0x81, 0x6b, 0x12, 0x40, 0x2, 0x36, 0x28, 0xc, 0x74, 0x5, 0x1, 0x80, 0x87, + 0x24, 0x1a, 0x6, 0x1a, 0xb, 0x59, 0x3, 0x6, 0x2, 0x6, 0x2, 0x6, 0x2, 0x3, + 0x23, 0xc, 0x1, 0x1a, 0x1, 0x13, 0x1, 0x2, 0x1, 0xf, 0x2, 0xe, 0x22, 0x7b, + 0x45, 0x35, 0x81, 0xb, 0x1d, 0x3, 0x31, 0x2f, 0x1f, 0x11, 0x1b, 0x35, + 0x1e, 0x2, 0x24, 0x4, 0x8, 0x1, 0x5, 0x2a, 0x80, 0x9e, 0x83, 0x62, 0x6, + 0x2, 0x1, 0x1, 0x2c, 0x1, 0x2, 0x3, 0x1, 0x2, 0x17, 0x80, 0xaa, 0x16, 0xa, + 0x1a, 0x46, 0x38, 0x6, 0x2, 0x40, 0x4, 0x1, 0x2, 0x5, 0x8, 0x1, 0x3, 0x1, + 0x1b, 0x2c, 0x1d, 0x80, 0x83, 0x36, 0xa, 0x16, 0xa, 0x13, 0x80, 0x8d, 0x49, + 0x83, 0xb7, 0x46, 0x3c, 0x37, 0x17, 0x19, 0x17, 0x33, 0x4d, 0x40, 0x1, 0x4, + 0x84, 0xbb, 0x36, 0x89, 0x4a, 0x83, 0x6f, 0x80, 0x91, 0x63, 0x8b, 0x9d, + 0x84, 0x2f, 0xa0, 0x33, 0xd1, 0x82, 0x39, 0x84, 0xc7, 0x45, 0xb, 0x2f, + 0x14, 0xd, 0xa0, 0x40, 0x60, 0x2, 0xa0, 0x23, 0xfe, 0x55, 0x1, 0x47, 0x1, + 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0xc, 0x1, 0x1, 0x1, 0x7, 0x1, + 0x41, 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, 0x1, 0x1c, 0x1, 0x4, 0x1, 0x5, 0x1, + 0x1, 0x3, 0x7, 0x1, 0x81, 0x54, 0x2, 0x19, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, + 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x8, + 0x96, 0x34, 0x4, 0x1, 0x1b, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0xa, 0x1, + 0x4, 0x1, 0x1, 0x1, 0x1, 0x6, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, + 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x2, 0x1, 0x1, 0x2, 0x4, 0x1, 0x7, 0x1, 0x4, 0x1, 0x4, 0x1, 0x1, 0x1, + 0xa, 0x1, 0x11, 0x5, 0x3, 0x1, 0x5, 0x1, 0x11, 0x91, 0x44, 0xa0, 0xa6, + 0xd7, 0x29, 0x90, 0x35, 0xb, 0x80, 0xde, 0xa0, 0x3f, 0xe2, 0x82, 0x1e +]; +_T Zs = [0x20, 0x1, 0x7f, 0x1, 0x95, 0xdf, 0x1, 0x89, 0x7f, 0xb, 0x24, 0x1, + 0x2f, 0x1, 0x8f, 0xa0, 0x1]; +_T Variation_Selector = [0x98, 0xb, 0x3, 0xa0, 0xe5, 0xf2, 0x10, 0xad, 0x2, 0xf0, 0x80, + 0xf0]; +_T Other_Default_Ignorable_Code_Point = [ + 0x83, 0x4f, 0x1, 0x8e, 0xf, 0x2, 0x86, 0x53, 0x2, 0x88, 0xaf, 0x1, 0x90, + 0xfe, 0x1, 0xa0, 0xce, 0x3b, 0x1, 0x4f, 0x9, 0xad, 0x0, 0x7, 0x1, 0x1, + 0x1e, 0x60, 0x80, 0x80, 0x80, 0xf0, 0x8e, 0x10 +]; +_T IDS_Binary_Operator = [0xa0, 0x2f, 0xf0, 0x2, 0x2, 0x8]; +_T Grapheme_Base = [ + 0x20, 0x5f, 0x21, 0xd, 0x1, 0x82, 0x52, 0x70, 0x8, 0x2, 0x5, 0x5, 0x7, + 0x1, 0x1, 0x1, 0x14, 0x1, 0x80, 0xe0, 0x7, 0x80, 0x9e, 0x9, 0x26, 0x2, + 0x7, 0x1, 0x27, 0x1, 0x2, 0x4, 0x1, 0x2e, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, + 0x1, 0x9, 0x1b, 0x5, 0x5, 0x11, 0xa, 0xb, 0x1, 0x2, 0x2d, 0x15, 0x10, + 0x1, 0x65, 0x8, 0x1, 0x6, 0x2, 0x2, 0x1, 0x4, 0x20, 0x2, 0x1, 0x1, 0x1e, + 0x1d, 0x59, 0xb, 0x1, 0xe, 0x2b, 0x9, 0x7, 0x5, 0x16, 0x4, 0x1, 0x9, 0x1, + 0x3, 0x1, 0x7, 0xf, 0x1, 0x19, 0x5, 0x1, 0x41, 0x1, 0x1, 0xb, 0x56, 0x37, + 0x1, 0x1, 0x1, 0x4, 0x8, 0x4, 0x1, 0x3, 0x7, 0xa, 0x2, 0x14, 0x1, 0x7, + 0x2, 0x2, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x1, 0x3, 0x4, + 0x3, 0x1, 0x1, 0x2, 0x6, 0x2, 0x2, 0x2, 0x1, 0x1, 0xd, 0x2, 0x1, 0x3, + 0x4, 0x16, 0x7, 0x1, 0x1, 0x6, 0x4, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, + 0x1, 0x2, 0x1, 0x2, 0x4, 0x3, 0x18, 0x4, 0x1, 0x1, 0x7, 0xa, 0x2, 0x3, + 0xe, 0x1, 0x1, 0x9, 0x1, 0x3, 0x1, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, + 0x3, 0x4, 0x8, 0x1, 0x1, 0x2, 0x3, 0x1, 0xf, 0x2, 0x4, 0xc, 0x10, 0x2, + 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x3, 0x1, + 0x2, 0x1, 0x6, 0x2, 0x2, 0x2, 0xf, 0x2, 0x1, 0x3, 0x4, 0x12, 0xb, 0x1, + 0x1, 0x6, 0x3, 0x3, 0x1, 0x4, 0x3, 0x2, 0x1, 0x1, 0x1, 0x2, 0x3, 0x2, 0x3, + 0x3, 0x3, 0xc, 0x5, 0x1, 0x1, 0x2, 0x3, 0x3, 0x1, 0x3, 0x3, 0x1, 0x15, + 0x15, 0x6, 0x3, 0x1, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, + 0x1, 0x3, 0x4, 0x13, 0x2, 0x6, 0x2, 0x4, 0xa, 0x8, 0x8, 0x2, 0x2, 0x1, + 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x2, 0x1, 0x2, 0x1, + 0x2, 0x2, 0x2, 0x1, 0x2, 0x12, 0x1, 0x1, 0x2, 0x4, 0xa, 0x1, 0x2, 0xf, + 0x2, 0x1, 0x8, 0x1, 0x3, 0x1, 0x29, 0x2, 0x1, 0x1, 0x2, 0x5, 0x3, 0x1, + 0x3, 0x1, 0x1, 0x11, 0x2, 0x4, 0x10, 0x3, 0x7, 0x2, 0x2, 0x1, 0x12, 0x3, + 0x18, 0x1, 0x9, 0x1, 0x1, 0x2, 0x7, 0x9, 0x2, 0x6, 0x7, 0x13, 0x3, 0xc, + 0x30, 0x1, 0x2, 0xb, 0x8, 0x8, 0xd, 0x25, 0x2, 0x1, 0x1, 0x2, 0x2, 0x1, + 0x1, 0x2, 0x1, 0x6, 0x4, 0x1, 0x7, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, + 0x1, 0x4, 0x1, 0x2, 0x9, 0x1, 0x2, 0x5, 0x1, 0x1, 0x9, 0xa, 0x2, 0x4, 0x20, + 0x18, 0x2, 0x1b, 0x1, 0x1, 0x1, 0x1, 0x1, 0xe, 0x1, 0x24, 0x12, 0x1, 0x5, + 0x1, 0x2, 0x5, 0x31, 0x8, 0x1, 0x6, 0x1, 0xd, 0x25, 0x2d, 0x4, 0x1, 0x6, + 0x1, 0x2, 0x2, 0x2, 0x19, 0x2, 0x4, 0x3, 0x10, 0x4, 0xd, 0x1, 0x2, 0x2, + 0x6, 0x1, 0xf, 0x1, 0x28, 0x1, 0x1, 0x5, 0x1, 0x2, 0x81, 0x79, 0x1, 0x4, + 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0x29, 0x1, 0x4, 0x2, 0x21, 0x1, 0x4, + 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0xf, 0x1, 0x39, 0x1, 0x4, 0x2, 0x43, + 0x5, 0x1d, 0x3, 0x1a, 0x6, 0x55, 0xb, 0x82, 0x9d, 0x3, 0x51, 0xf, 0xd, + 0x1, 0x4, 0xe, 0x12, 0x3, 0x2, 0x9, 0x12, 0xe, 0xd, 0x1, 0x3, 0xf, 0x34, + 0x2, 0x1, 0x7, 0x8, 0x1, 0x2, 0xb, 0x9, 0x3, 0xa, 0x6, 0xa, 0x6, 0xb, + 0x5, 0xa, 0x6, 0x58, 0x8, 0x29, 0x1, 0x1, 0x5, 0x46, 0xa, 0x1d, 0x6, 0x4, + 0x2, 0x3, 0x4, 0x2, 0x1, 0x6, 0x7, 0x1, 0x3, 0x2a, 0x2, 0x5, 0xb, 0x2c, + 0x4, 0x1a, 0x6, 0xb, 0x3, 0x39, 0x2, 0x2, 0x3, 0x38, 0x1, 0x1, 0x9, 0x1, + 0x1, 0x2, 0x8, 0x6, 0xd, 0xa, 0x6, 0xa, 0x6, 0xe, 0x56, 0x30, 0x1, 0x1, + 0x5, 0x1, 0x1, 0x5, 0x1, 0x9, 0x4, 0x1b, 0x9, 0x9, 0x5, 0x20, 0x4, 0x2, + 0x2, 0x1, 0x1, 0x3a, 0x1, 0x1, 0x2, 0x3, 0x1, 0x1, 0x3, 0x2, 0x8, 0x30, + 0x8, 0x2, 0x5, 0xf, 0x3, 0x33, 0x40, 0x8, 0xb, 0x1, 0xd, 0x1, 0x7, 0x4, + 0x1, 0x6, 0x1, 0x2, 0x9, 0x80, 0xc0, 0x40, 0x81, 0x16, 0x2, 0x6, 0x2, + 0x26, 0x2, 0x6, 0x2, 0x8, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1f, 0x2, + 0x35, 0x1, 0xf, 0x1, 0xe, 0x2, 0x6, 0x1, 0x13, 0x2, 0x3, 0x1, 0x9, 0x1, + 0xb, 0x5, 0x18, 0x7, 0x31, 0x10, 0x2, 0x2, 0x1b, 0x1, 0xd, 0x3, 0x1b, 0x45, + 0x80, 0x8a, 0x6, 0x82, 0x64, 0xc, 0x27, 0x19, 0xb, 0x15, 0x82, 0xa0, 0x1, + 0x84, 0x4c, 0x3, 0xa, 0x80, 0xa6, 0x2f, 0x1, 0x2f, 0x1, 0x80, 0x8f, 0x3, + 0x2, 0x5, 0x2d, 0x1, 0x1, 0x5, 0x1, 0x2, 0x38, 0x7, 0x2, 0xf, 0x17, 0x9, + 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, + 0x21, 0x3c, 0x44, 0x1a, 0x1, 0x59, 0xc, 0x80, 0xd6, 0x1a, 0xc, 0x4, 0x2a, + 0x6, 0x10, 0x1, 0x56, 0x4, 0x65, 0x5, 0x29, 0x3, 0x5e, 0x1, 0x2b, 0x5, + 0x24, 0xc, 0x2f, 0x1, 0x80, 0xdf, 0x1, 0x9a, 0xb6, 0xa, 0xa0, 0x52, 0xd, + 0x33, 0x84, 0x8d, 0x3, 0x37, 0x9, 0x81, 0x5c, 0x14, 0x2f, 0x4, 0x1, 0xa, + 0x1a, 0x8, 0x50, 0x2, 0x6, 0x8, 0x80, 0x8f, 0x1, 0x4, 0xc, 0xb, 0x4d, 0xa, + 0x1, 0x3, 0x1, 0x4, 0x1, 0x19, 0x2, 0x5, 0x4, 0xa, 0x6, 0x38, 0x8, 0x44, + 0xa, 0xc, 0x18, 0xa, 0x4, 0x26, 0x8, 0x19, 0xb, 0x2, 0xb, 0x1e, 0x6, 0x30, + 0x1, 0x2, 0x4, 0x2, 0x1, 0x11, 0x1, 0xb, 0x4, 0x2, 0x20, 0x29, 0x6, 0x2, + 0x2, 0x2, 0xb, 0x3, 0x1, 0x8, 0x1, 0x1, 0x2, 0xa, 0x2, 0x20, 0x4, 0x30, + 0x1, 0x1, 0x3, 0x2, 0x2, 0x5, 0x2, 0x1, 0x1, 0x1, 0x18, 0x11, 0x2, 0x8, + 0xb, 0x6, 0x2, 0x6, 0x2, 0x6, 0x9, 0x7, 0x1, 0x7, 0x80, 0x91, 0x25, 0x1, + 0x2, 0x1, 0x4, 0x3, 0xa, 0x6, 0xa0, 0x2b, 0xa4, 0xc, 0x17, 0x4, 0x31, 0xa0, + 0x21, 0x4, 0x81, 0x6e, 0x2, 0x6a, 0x26, 0x7, 0xc, 0x5, 0x5, 0x1, 0x1, + 0x18, 0x1, 0x5, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x7c, 0x11, 0x81, 0x6d, + 0x10, 0x40, 0x2, 0x36, 0x28, 0xe, 0x12, 0xa, 0x16, 0x23, 0x1, 0x13, 0x1, + 0x4, 0x4, 0x5, 0x1, 0x80, 0x87, 0x4, 0x80, 0x9d, 0x2, 0x1f, 0x3, 0x6, 0x2, + 0x6, 0x2, 0x6, 0x2, 0x3, 0x3, 0x7, 0x1, 0x7, 0xd, 0x2, 0x2, 0xc, 0x1, + 0x1a, 0x1, 0x13, 0x1, 0x2, 0x1, 0xf, 0x2, 0xe, 0x22, 0x7b, 0x5, 0x3, 0x4, + 0x2d, 0x3, 0x54, 0x5, 0xc, 0x34, 0x2d, 0x80, 0x83, 0x1d, 0x3, 0x31, 0x2f, + 0x1f, 0x1, 0x4, 0xc, 0x1b, 0x35, 0x1e, 0x1, 0x25, 0x4, 0xe, 0x2a, 0x80, + 0x9e, 0x2, 0xa, 0x83, 0x56, 0x6, 0x2, 0x1, 0x1, 0x2c, 0x1, 0x2, 0x3, 0x1, + 0x2, 0x17, 0x1, 0x9, 0x80, 0xa0, 0x1c, 0x3, 0x1b, 0x5, 0x1, 0x40, 0x38, + 0x6, 0x2, 0x40, 0x1, 0xf, 0x4, 0x1, 0x3, 0x1, 0x1b, 0xc, 0x8, 0x8, 0x9, + 0x7, 0x20, 0x80, 0x80, 0x36, 0x3, 0x1d, 0x2, 0x1b, 0x5, 0x8, 0x80, 0x80, + 0x49, 0x82, 0x17, 0x1f, 0x81, 0x81, 0x1, 0x1, 0x36, 0xf, 0x7, 0x4, 0x1e, + 0x12, 0x31, 0x4, 0x2, 0x2, 0x2, 0x1, 0x4, 0xe, 0x19, 0x7, 0xa, 0x9, 0x24, + 0x5, 0x1, 0x9, 0xe, 0x3e, 0x34, 0x9, 0xa, 0x7, 0xa, 0x84, 0xa6, 0x2b, 0x1, + 0x1, 0x1, 0x2, 0x6, 0x1, 0x9, 0xa, 0x89, 0x36, 0x83, 0x6f, 0x80, 0x91, + 0x63, 0xd, 0x4, 0x8b, 0x8c, 0x84, 0x2f, 0xa0, 0x33, 0xd1, 0x82, 0x39, 0x84, + 0xc7, 0x45, 0xb, 0x2f, 0x14, 0xd, 0xa0, 0x40, 0x60, 0x2, 0x9f, 0xfe, + 0x80, 0xf6, 0xa, 0x27, 0x2, 0x3c, 0x1, 0x1, 0x3, 0x4, 0x15, 0x2, 0x7, 0x1e, + 0x4, 0x30, 0x22, 0x42, 0x3, 0x1, 0x80, 0xba, 0x57, 0x9, 0x12, 0x80, 0x8e, + 0x55, 0x1, 0x47, 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0xc, 0x1, + 0x1, 0x1, 0x7, 0x1, 0x41, 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, 0x1, 0x1c, 0x1, + 0x4, 0x1, 0x5, 0x1, 0x1, 0x3, 0x7, 0x1, 0x81, 0x54, 0x2, 0x81, 0x24, 0x2, + 0x32, 0x96, 0x0, 0x4, 0x1, 0x1b, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0xa, + 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x6, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x3, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x2, 0x1, 0x1, 0x2, 0x4, 0x1, 0x7, 0x1, 0x4, 0x1, 0x4, 0x1, 0x1, + 0x1, 0xa, 0x1, 0x11, 0x5, 0x3, 0x1, 0x5, 0x1, 0x11, 0x34, 0x2, 0x81, 0xe, + 0x2c, 0x4, 0x64, 0xc, 0xf, 0x2, 0xe, 0x2, 0xf, 0x1, 0xf, 0x20, 0xb, 0x5, + 0x1f, 0x1, 0x3c, 0x4, 0x2b, 0x4b, 0x1d, 0xd, 0x2b, 0x5, 0x9, 0x7, 0x2, + 0x80, 0xae, 0x21, 0xf, 0x6, 0x1, 0x46, 0x3, 0x14, 0xc, 0x25, 0x1, 0x5, + 0x15, 0x11, 0xf, 0x3f, 0x1, 0x1, 0x1, 0x80, 0xb6, 0x1, 0x4, 0x3, 0x3e, 0x2, + 0x4, 0xc, 0x18, 0x80, 0x93, 0x46, 0x4, 0xb, 0x30, 0x46, 0x3a, 0x74, 0x88, + 0x8c, 0xa0, 0xa6, 0xd7, 0x29, 0x90, 0x35, 0xb, 0x80, 0xde, 0xa0, 0x3f, 0xe2, 0x82, + 0x1e +]; +_T Case_Ignorable = [ + 0x27, 0x1, 0x6, 0x1, 0xb, 0x1, 0x23, 0x1, 0x1, 0x1, 0x47, 0x1, 0x4, 0x1, + 0x1, 0x1, 0x4, 0x1, 0x2, 0x2, 0x81, 0xf7, 0x80, 0xc0, 0x4, 0x2, 0x4, 0x1, + 0x9, 0x2, 0x1, 0x1, 0x80, 0xfb, 0x7, 0x80, 0xcf, 0x1, 0x37, 0x2d, 0x1, + 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, 0x2c, 0x1, 0xb, 0x5, 0xb, 0xb, 0x1, 0x1, + 0x23, 0x1, 0xa, 0x15, 0x10, 0x1, 0x65, 0x8, 0x1, 0xa, 0x1, 0x4, 0x21, + 0x1, 0x1, 0x1, 0x1e, 0x1b, 0x5b, 0xb, 0x3a, 0xb, 0x4, 0x1, 0x1b, 0x18, + 0x2b, 0x3, 0x80, 0x88, 0x1b, 0x1, 0x3, 0x37, 0x1, 0x1, 0x1, 0x4, 0x8, 0x4, + 0x1, 0x3, 0x7, 0xa, 0x2, 0xd, 0x1, 0xf, 0x1, 0x3a, 0x1, 0x4, 0x4, 0x8, 0x1, + 0x14, 0x2, 0x1d, 0x2, 0x39, 0x1, 0x4, 0x2, 0x4, 0x2, 0x2, 0x3, 0x3, 0x1, + 0x1e, 0x2, 0x3, 0x1, 0xb, 0x2, 0x39, 0x1, 0x4, 0x5, 0x1, 0x2, 0x4, 0x1, + 0x14, 0x2, 0x1d, 0x1, 0x3a, 0x1, 0x2, 0x1, 0x1, 0x4, 0x8, 0x1, 0x8, 0x1, + 0xb, 0x2, 0x1e, 0x1, 0x3d, 0x1, 0xc, 0x1, 0x70, 0x3, 0x5, 0x3, 0x1, 0x4, + 0x7, 0x2, 0xb, 0x2, 0x58, 0x1, 0x2, 0x1, 0x6, 0x1, 0x5, 0x2, 0x14, 0x2, + 0x5d, 0x4, 0x8, 0x1, 0x14, 0x2, 0x66, 0x1, 0x7, 0x3, 0x1, 0x1, 0x5a, 0x1, + 0x2, 0x7, 0xb, 0x9, 0x62, 0x1, 0x2, 0x6, 0x1, 0x2, 0x9, 0x1, 0x1, 0x6, + 0x4a, 0x2, 0x1b, 0x1, 0x1, 0x1, 0x1, 0x1, 0x37, 0xe, 0x1, 0x5, 0x1, 0x2, + 0x5, 0xb, 0x1, 0x24, 0x9, 0x1, 0x66, 0x4, 0x1, 0x6, 0x1, 0x2, 0x2, 0x2, + 0x19, 0x2, 0x4, 0x3, 0x10, 0x4, 0xd, 0x1, 0x2, 0x2, 0x6, 0x1, 0xf, 0x1, + 0x5e, 0x1, 0x82, 0x60, 0x3, 0x83, 0xb2, 0x3, 0x1d, 0x3, 0x1d, 0x2, 0x1e, + 0x2, 0x40, 0x2, 0x1, 0x7, 0x8, 0x1, 0x2, 0xb, 0x3, 0x1, 0x5, 0x1, 0x2d, + 0x4, 0x34, 0x1, 0x65, 0x1, 0x76, 0x3, 0x4, 0x2, 0x9, 0x1, 0x6, 0x3, 0x80, + 0xdb, 0x2, 0x2, 0x1, 0x3a, 0x1, 0x1, 0x7, 0x1, 0x1, 0x1, 0x1, 0x2, 0x8, + 0x6, 0xa, 0x2, 0x1, 0x27, 0x1, 0x58, 0x4, 0x30, 0x1, 0x1, 0x5, 0x1, 0x1, + 0x5, 0x1, 0x28, 0x9, 0xc, 0x2, 0x20, 0x4, 0x2, 0x2, 0x1, 0x1, 0x3a, 0x1, + 0x1, 0x2, 0x3, 0x1, 0x1, 0x3, 0x3a, 0x8, 0x2, 0x2, 0x40, 0x6, 0x52, 0x3, + 0x1, 0xd, 0x1, 0x7, 0x4, 0x1, 0x6, 0x1, 0x37, 0x3f, 0xd, 0x1, 0x22, 0x4c, + 0x15, 0x4, 0x81, 0xbd, 0x1, 0x1, 0x3, 0xb, 0x3, 0xd, 0x3, 0xd, 0x3, 0xd, + 0x2, 0xc, 0x5, 0x8, 0x2, 0xa, 0x1, 0x2, 0x1, 0x2, 0x5, 0x31, 0x5, 0x1, + 0xa, 0x1, 0x1, 0xd, 0x1, 0x10, 0xd, 0x33, 0x21, 0x8b, 0x8b, 0x2, 0x71, + 0x3, 0x7d, 0x1, 0xf, 0x1, 0x60, 0x20, 0x2f, 0x1, 0x81, 0xd5, 0x1, 0x24, + 0x4, 0x3, 0x5, 0x5, 0x1, 0x5d, 0x6, 0x5d, 0x3, 0xa0, 0x6f, 0x16, 0x1, 0x84, + 0xe2, 0x6, 0x81, 0xe, 0x1, 0x62, 0x4, 0x1, 0xa, 0x1, 0x1, 0x1f, 0x1, + 0x50, 0x2, 0xe, 0x22, 0x4e, 0x1, 0x17, 0x3, 0x6d, 0x2, 0x8, 0x1, 0x3, + 0x1, 0x4, 0x1, 0x19, 0x2, 0x80, 0x9d, 0x1, 0x1b, 0x12, 0x34, 0x8, 0x19, + 0xb, 0x2e, 0x3, 0x30, 0x1, 0x2, 0x4, 0x2, 0x1, 0x12, 0x1, 0x59, 0x6, 0x2, + 0x2, 0x2, 0x2, 0xc, 0x1, 0x8, 0x1, 0x23, 0x1, 0x3f, 0x1, 0x1, 0x3, 0x2, + 0x2, 0x5, 0x2, 0x1, 0x1, 0x1b, 0x1, 0xe, 0x2, 0x5, 0x2, 0x1, 0x1, 0x80, + 0xee, 0x1, 0x2, 0x1, 0x4, 0x1, 0xa0, 0x4f, 0x30, 0x1, 0x80, 0x93, 0x10, + 0x82, 0x3e, 0x10, 0x3, 0x1, 0xc, 0x7, 0x2b, 0x1, 0x2, 0x1, 0x80, 0xa9, + 0x1, 0x7, 0x1, 0x6, 0x1, 0xb, 0x1, 0x23, 0x1, 0x1, 0x1, 0x2f, 0x1, 0x2d, + 0x2, 0x43, 0x1, 0x15, 0x3, 0x82, 0x1, 0x1, 0x88, 0x3, 0x3, 0x1, 0x2, 0x5, + 0x4, 0x28, 0x3, 0x4, 0x1, 0x85, 0xc1, 0x1, 0x36, 0xf, 0x39, 0x2, 0x31, + 0x4, 0x2, 0x2, 0x2, 0x1, 0x42, 0x3, 0x24, 0x5, 0x1, 0x8, 0x4b, 0x2, 0x34, + 0x9, 0x84, 0xec, 0x1, 0x1, 0x1, 0x2, 0x6, 0x1, 0x1, 0xa0, 0x58, 0xd7, 0x11, + 0xa0, 0x61, 0xc7, 0x3, 0x9, 0x10, 0x2, 0x7, 0x1e, 0x4, 0x80, 0x94, 0x3, + 0xac, 0x2d, 0xbc, 0x1, 0x1e, 0x60, 0x80, 0x80, 0x80, 0xf0 +]; +_T STerm = [0x21, 0x1, 0xc, 0x1, 0x10, 0x1, 0x85, 0x1c, 0x1, 0x1, 0x1, 0x2a, + 0x1, 0x80, 0x95, 0x1, 0x80, 0xb4, 0x1, 0x2b, 0x3, 0x80, 0xf6, 0x1, 0x81, + 0x6a, 0x2, 0x86, 0xe4, 0x2, 0x83, 0x16, 0x1, 0x4, 0x2, 0x83, 0x5, 0x1, + 0x80, 0xc6, 0x2, 0x80, 0xcc, 0x1, 0x5, 0x1, 0x81, 0x3a, 0x2, 0x81, 0x62, + 0x4, 0x80, 0xae, 0x2, 0x2, 0x2, 0x80, 0xdb, 0x2, 0x41, 0x2, 0x83, 0xbc, + 0x2, 0x9, 0x3, 0x8d, 0xe4, 0x1, 0x81, 0xd3, 0x1, 0xa0, 0x74, 0xfc, 0x1, + 0x81, 0xe, 0x2, 0x80, 0xe3, 0x1, 0x3, 0x1, 0x81, 0x7e, 0x2, 0x56, 0x2, + 0x5f, 0x1, 0x80, 0x98, 0x2, 0x80, 0x93, 0x3, 0x80, 0x90, 0x2, 0x80, 0xf9, + 0x1, 0xa0, 0x52, 0x66, 0x1, 0x3, 0x2, 0x80, 0xa9, 0x1, 0xc, 0x1, 0x10, 0x1, + 0x41, 0x1, 0x8a, 0xf4, 0x2, 0x85, 0xef, 0x2, 0x75, 0x4, 0x7f, 0x3, 0x80, 0x81, + 0x2]; +_T Diacritic = [ + 0x5e, 0x1, 0x1, 0x1, 0x47, 0x1, 0x6, 0x1, 0x4, 0x1, 0x2, 0x2, 0x81, 0xf7, + 0x80, 0x9f, 0x1, 0x8, 0x5, 0x6, 0x11, 0x2, 0x4, 0x1, 0x9, 0x2, 0x80, + 0xfd, 0x5, 0x80, 0xd1, 0x1, 0x37, 0x11, 0x1, 0x1b, 0x1, 0x1, 0x1, 0x2, 0x1, + 0x1, 0x80, 0x86, 0x8, 0x4, 0x2, 0x80, 0x86, 0x2, 0x4, 0x2, 0x3, 0x3, 0x43, + 0x1b, 0x5b, 0xb, 0x3a, 0xb, 0x22, 0x2, 0x80, 0xca, 0x1b, 0x3d, 0x1, 0x10, + 0x1, 0x3, 0x4, 0x1c, 0x1, 0x4a, 0x1, 0x10, 0x1, 0x6e, 0x1, 0x10, 0x1, 0x6e, + 0x1, 0x10, 0x1, 0x6e, 0x1, 0x10, 0x1, 0x7f, 0x1, 0x7f, 0x1, 0x6e, 0x1, + 0x10, 0x1, 0x7f, 0x1, 0x7c, 0x1, 0x7c, 0x6, 0x1, 0x1, 0x79, 0x5, 0x4b, + 0x2, 0x1b, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x2, 0x42, 0x3, 0x1, 0x2, 0x3e, + 0x1, 0x70, 0x1, 0x1, 0x2, 0x4c, 0x7, 0x1, 0x1, 0xa, 0x2, 0x87, 0x2d, 0xb, + 0x9, 0x1, 0x81, 0x5b, 0x3, 0x81, 0x39, 0x8, 0x2, 0x1, 0x80, 0xb4, 0x1, 0xf, + 0x1, 0x26, 0x9, 0x36, 0x2, 0x80, 0x8a, 0x2, 0x40, 0x6, 0x52, 0x19, 0x4, + 0x1, 0x6, 0x1, 0x37, 0x3f, 0x59, 0xc, 0x2d, 0x3, 0x81, 0xbd, 0x1, 0x1, + 0x3, 0xb, 0x3, 0xd, 0x3, 0xd, 0x3, 0xd, 0x2, 0x8c, 0xf0, 0x3, 0x81, 0x3d, + 0x1, 0x81, 0xfa, 0x6, 0x69, 0x4, 0x5f, 0x1, 0xa0, 0x75, 0x72, 0x1, 0xc, + 0x2, 0x1, 0x1, 0x70, 0x2, 0x25, 0xb, 0x66, 0x1, 0x6f, 0x2, 0x80, 0xca, 0x1, + 0x1b, 0x12, 0x39, 0x4, 0x24, 0x1, 0x5f, 0x1, 0xc, 0x1, 0x80, 0xba, 0x1, + 0x43, 0x4, 0x33, 0x1, 0x80, 0xf5, 0x2, 0xa0, 0x4f, 0x30, 0x1, 0x83, 0x1, + 0x7, 0x81, 0x17, 0x1, 0x1, 0x1, 0x2f, 0x1, 0x2d, 0x2, 0x43, 0x1, 0x90, + 0xd5, 0x2, 0x78, 0x2, 0x80, 0x8b, 0x1, 0x84, 0xf5, 0x2, 0xa0, 0x58, 0xd7, + 0x11, 0xa0, 0x61, 0xc7, 0x3, 0x3, 0x6, 0x8, 0x8, 0x2, 0x7, 0x1e, 0x4 +]; +_T Lm = [0x82, 0xb0, 0x12, 0x4, 0xc, 0xe, 0x5, 0x7, 0x1, 0x1, 0x1, 0x80, + 0x85, 0x1, 0x5, 0x1, 0x81, 0xde, 0x1, 0x80, 0xe6, 0x1, 0x80, 0xa4, 0x2, + 0x81, 0xd, 0x2, 0x4, 0x1, 0x1f, 0x1, 0x9, 0x1, 0x3, 0x1, 0x81, 0x48, 0x1, + 0x84, 0xd4, 0x1, 0x7f, 0x1, 0x82, 0x35, 0x1, 0x86, 0xda, 0x1, 0x6b, 0x1, + 0x82, 0x63, 0x1, 0x81, 0xd0, 0x6, 0x80, 0xae, 0x3f, 0xd, 0x1, 0x22, 0x25, + 0x82, 0xb1, 0x1, 0xd, 0x1, 0x10, 0xd, 0x8b, 0xdf, 0x2, 0x80, 0xf1, 0x1, + 0x80, 0xbf, 0x1, 0x81, 0xd5, 0x1, 0x2b, 0x5, 0x5, 0x1, 0x61, 0x2, 0x5d, + 0x3, 0xa0, 0x6f, 0x16, 0x1, 0x84, 0xe2, 0x6, 0x81, 0xe, 0x1, 0x72, 0x1, + 0x80, 0x97, 0x9, 0x50, 0x1, 0x17, 0x1, 0x6f, 0x2, 0x81, 0xd5, 0x1, 0x80, + 0xa0, 0x1, 0x6c, 0x1, 0x15, 0x2, 0xa0, 0x54, 0x7b, 0x1, 0x2d, 0x2, 0xa0, 0x6f, + 0xf3, 0xd]; +_T Mc = [0x89, 0x3, 0x1, 0x37, 0x1, 0x2, 0x3, 0x8, 0x4, 0x1, 0x2, 0x32, 0x2, + 0x3a, 0x3, 0x6, 0x2, 0x2, 0x2, 0xa, 0x1, 0x2b, 0x1, 0x3a, 0x3, 0x42, 0x1, + 0x3a, 0x3, 0x8, 0x1, 0x1, 0x2, 0x35, 0x2, 0x3a, 0x1, 0x1, 0x1, 0x6, 0x2, + 0x2, 0x2, 0xa, 0x1, 0x66, 0x2, 0x1, 0x2, 0x3, 0x3, 0x1, 0x3, 0xa, 0x1, + 0x29, 0x3, 0x3d, 0x4, 0x3d, 0x2, 0x3a, 0x1, 0x1, 0x5, 0x2, 0x2, 0x1, 0x2, + 0x9, 0x2, 0x2b, 0x2, 0x3a, 0x3, 0x5, 0x3, 0x1, 0x3, 0xa, 0x1, 0x2a, 0x2, + 0x4b, 0x3, 0x6, 0x8, 0x12, 0x2, 0x81, 0x4a, 0x2, 0x3f, 0x1, 0x80, 0xab, + 0x2, 0x4, 0x1, 0x6, 0x1, 0x2, 0x2, 0x19, 0x2, 0xa, 0x3, 0x2, 0x7, 0x15, + 0x2, 0x2, 0x6, 0x2, 0x1, 0xa, 0x3, 0x87, 0x19, 0x1, 0x7, 0x8, 0x1, 0x2, + 0x81, 0x5a, 0x4, 0x2, 0x3, 0x4, 0x2, 0x1, 0x6, 0x77, 0x11, 0x7, 0x2, + 0x4f, 0x2, 0x3a, 0x1, 0x1, 0x1, 0x9, 0x1, 0x1, 0x2, 0x8, 0x6, 0x80, 0x91, + 0x1, 0x30, 0x1, 0x5, 0x1, 0x1, 0x5, 0x1, 0x2, 0x3d, 0x1, 0x1e, 0x1, 0x4, + 0x2, 0x2, 0x1, 0x1, 0x2, 0x39, 0x1, 0x2, 0x3, 0x1, 0x1, 0x3, 0x2, 0x30, + 0x8, 0x8, 0x2, 0x80, 0xab, 0x1, 0x10, 0x2, 0x93, 0x3a, 0x2, 0xa0, 0x77, + 0xf3, 0x2, 0x2, 0x1, 0x58, 0x2, 0x32, 0x10, 0x80, 0x8e, 0x2, 0x2f, 0x1, + 0x30, 0x2, 0x4, 0x2, 0x1, 0x4, 0x6e, 0x2, 0x2, 0x2, 0x18, 0x1, 0x2d, 0x1, + 0x6f, 0x1, 0x2, 0x2, 0x5, 0x1, 0x80, 0xed, 0x2, 0x1, 0x2, 0x1, 0x2, 0x1, + 0x1, 0xa0, 0x64, 0x13, 0x1, 0x1, 0x1, 0x7f, 0x1, 0x2d, 0x3, 0x4, 0x2, 0x73, + 0x1, 0x55, 0x1, 0x30, 0x3, 0x9, 0x2, 0x84, 0xeb, 0x1, 0x1, 0x2, 0x6, 0x1, + 0xa0, 0x58, 0x9a, 0x2e, 0xa0, 0x61, 0xe6, 0x2, 0x6, 0x6]; +_T Lo = [0x80, 0xaa, 0x1, 0xf, 0x1, 0x81, 0x0, 0x1, 0x4, 0x4, 0x80, 0xd0, 0x1, + 0x83, 0x3b, 0x1b, 0x5, 0x3, 0x2d, 0x20, 0x1, 0xa, 0x23, 0x2, 0x1, 0x63, + 0x1, 0x1, 0x18, 0x2, 0xa, 0x3, 0x2, 0x1, 0x10, 0x1, 0x1, 0x1e, 0x1d, + 0x59, 0xb, 0x1, 0x18, 0x21, 0x15, 0x16, 0x2a, 0x19, 0x47, 0x1, 0x1, 0xb, + 0x57, 0x36, 0x3, 0x1, 0x12, 0x1, 0x7, 0xa, 0x10, 0x6, 0x1, 0x7, 0x5, 0x8, + 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x1, 0x3, 0x4, 0x3, 0x1, 0x10, 0x1, + 0xd, 0x2, 0x1, 0x3, 0xe, 0x2, 0x13, 0x6, 0x4, 0x2, 0x2, 0x16, 0x1, 0x7, + 0x1, 0x2, 0x1, 0x2, 0x1, 0x2, 0x1f, 0x4, 0x1, 0x1, 0x13, 0x3, 0x10, 0x9, + 0x1, 0x3, 0x1, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x3, 0x1, 0x12, 0x1, + 0xf, 0x2, 0x23, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, + 0x3, 0x1, 0x1e, 0x2, 0x1, 0x3, 0xf, 0x1, 0x11, 0x1, 0x1, 0x6, 0x3, 0x3, + 0x1, 0x4, 0x3, 0x2, 0x1, 0x1, 0x1, 0x2, 0x3, 0x2, 0x3, 0x3, 0x3, 0xc, + 0x16, 0x1, 0x34, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x1, + 0x1a, 0x2, 0x6, 0x2, 0x23, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, + 0x3, 0x1, 0x20, 0x1, 0x1, 0x2, 0xf, 0x2, 0x12, 0x8, 0x1, 0x3, 0x1, 0x29, + 0x2, 0x1, 0x10, 0x1, 0x11, 0x2, 0x18, 0x6, 0x5, 0x12, 0x3, 0x18, 0x1, 0x9, + 0x1, 0x1, 0x2, 0x7, 0x3a, 0x30, 0x1, 0x2, 0xc, 0x6, 0x3b, 0x2, 0x1, 0x1, + 0x2, 0x2, 0x1, 0x1, 0x2, 0x1, 0x6, 0x4, 0x1, 0x7, 0x1, 0x3, 0x1, 0x1, 0x1, + 0x1, 0x2, 0x2, 0x1, 0x4, 0x1, 0x2, 0x9, 0x1, 0x2, 0x5, 0x17, 0x4, 0x20, + 0x1, 0x3f, 0x8, 0x1, 0x24, 0x1b, 0x5, 0x73, 0x2b, 0x14, 0x1, 0x10, 0x6, + 0x4, 0x4, 0x3, 0x1, 0x3, 0x2, 0x7, 0x3, 0x4, 0xd, 0xc, 0x1, 0x41, 0x2b, + 0x2, 0x81, 0x4c, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0x29, 0x1, + 0x4, 0x2, 0x21, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0xf, 0x1, + 0x39, 0x1, 0x4, 0x2, 0x43, 0x25, 0x10, 0x10, 0x55, 0xc, 0x82, 0x6c, 0x2, + 0x11, 0x1, 0x1a, 0x5, 0x4b, 0x15, 0xd, 0x1, 0x4, 0xe, 0x12, 0xe, 0x12, + 0xe, 0xd, 0x1, 0x3, 0xf, 0x34, 0x28, 0x1, 0x43, 0x23, 0x1, 0x34, 0x8, + 0x29, 0x1, 0x1, 0x5, 0x46, 0xa, 0x1d, 0x33, 0x1e, 0x2, 0x5, 0xb, 0x2c, + 0x15, 0x7, 0x38, 0x17, 0x9, 0x35, 0x80, 0xb0, 0x2f, 0x11, 0x7, 0x37, + 0x1e, 0xd, 0x2, 0xa, 0x2c, 0x1a, 0x24, 0x29, 0x3, 0xa, 0x1e, 0x71, 0x4, + 0x1, 0x4, 0x3, 0x2, 0x84, 0x3e, 0x4, 0x8b, 0xf7, 0x38, 0x18, 0x17, 0x9, + 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, + 0x82, 0x27, 0x1, 0x35, 0x1, 0x4, 0x56, 0x8, 0x1, 0x1, 0x5a, 0x4, 0x1, 0x5, + 0x29, 0x3, 0x5e, 0x11, 0x1b, 0x35, 0x10, 0x82, 0x0, 0x99, 0xb6, 0x4a, 0xa0, + 0x51, 0xcd, 0x33, 0x15, 0x1, 0x84, 0x77, 0x43, 0x28, 0x8, 0x81, 0xc, 0x4, + 0x10, 0xa, 0x2, 0x42, 0x1, 0x31, 0x46, 0x81, 0x15, 0x7, 0x1, 0x3, 0x1, 0x4, + 0x1, 0x17, 0x1d, 0x34, 0xe, 0x32, 0x3e, 0x6, 0x3, 0x1, 0xe, 0x1c, 0xa, + 0x17, 0x19, 0x1d, 0x7, 0x2f, 0x4d, 0x29, 0x17, 0x3, 0x1, 0x8, 0x14, 0x10, + 0x1, 0x6, 0x3, 0x1, 0x5, 0x30, 0x1, 0x1, 0x3, 0x2, 0x2, 0x5, 0x2, 0x1, + 0x1, 0x1, 0x18, 0x2, 0x3, 0xb, 0x7, 0x1, 0xe, 0x6, 0x2, 0x6, 0x2, 0x6, + 0x9, 0x7, 0x1, 0x7, 0x80, 0x91, 0x23, 0x1d, 0xa0, 0x2b, 0xa4, 0xc, 0x17, + 0x4, 0x31, 0xa0, 0x21, 0x4, 0x81, 0x6e, 0x2, 0x6a, 0x43, 0x1, 0x1, 0xa, + 0x1, 0xd, 0x1, 0x5, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x6c, 0x21, 0x81, + 0x6b, 0x12, 0x40, 0x2, 0x36, 0x28, 0xc, 0x74, 0x5, 0x1, 0x80, 0x87, 0x69, + 0xa, 0x1, 0x2d, 0x2, 0x1f, 0x3, 0x6, 0x2, 0x6, 0x2, 0x6, 0x2, 0x3, 0x23, + 0xc, 0x1, 0x1a, 0x1, 0x13, 0x1, 0x2, 0x1, 0xf, 0x2, 0xe, 0x22, 0x7b, 0x81, + 0x85, 0x1d, 0x3, 0x31, 0x2f, 0x1f, 0x11, 0x11, 0x1, 0x8, 0x36, 0x1e, 0x2, + 0x24, 0x4, 0x8, 0x80, 0x80, 0x4e, 0x83, 0x62, 0x6, 0x2, 0x1, 0x1, 0x2c, + 0x1, 0x2, 0x3, 0x1, 0x2, 0x17, 0x80, 0xaa, 0x16, 0xa, 0x1a, 0x46, 0x38, + 0x6, 0x2, 0x40, 0x1, 0xf, 0x4, 0x1, 0x3, 0x1, 0x1b, 0x2c, 0x1d, 0x80, 0x83, + 0x36, 0xa, 0x16, 0xa, 0x13, 0x80, 0x8d, 0x49, 0x83, 0xba, 0x35, 0x4b, 0x2d, + 0x20, 0x19, 0x1a, 0x24, 0x5c, 0x30, 0xe, 0x4, 0x84, 0xbb, 0x2b, 0x89, 0x55, + 0x83, 0x6f, 0x8c, 0x91, 0x84, 0x2f, 0xa0, 0x33, 0xd1, 0x82, 0x39, 0x84, + 0xc7, 0x45, 0xb, 0x1, 0xa0, 0x40, 0xaf, 0x2, 0xa0, 0x3d, 0xfe, 0x4, 0x1, + 0x1b, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0xa, 0x1, 0x4, 0x1, 0x1, 0x1, + 0x1, 0x6, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x2, 0x1, 0x1, + 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, + 0x4, 0x1, 0x7, 0x1, 0x4, 0x1, 0x4, 0x1, 0x1, 0x1, 0xa, 0x1, 0x11, 0x5, + 0x3, 0x1, 0x5, 0x1, 0x11, 0x91, 0x44, 0xa0, 0xa6, 0xd7, 0x29, 0x90, 0x35, + 0xb, 0x80, 0xde, 0xa0, 0x3f, 0xe2, 0x82, 0x1e]; +_T Me = [0x84, 0x88, 0x2, 0x9c, 0x53, 0x4, 0x1, 0x3, 0xa0, 0x85, 0x8b, 0x3]; +_T ID_Start = [ + 0x41, 0x1a, 0x6, 0x1a, 0x2f, 0x1, 0xa, 0x1, 0x4, 0x1, 0x5, 0x17, 0x1, 0x1f, + 0x1, 0x81, 0xca, 0x4, 0xc, 0xe, 0x5, 0x7, 0x1, 0x1, 0x1, 0x80, 0x81, 0x5, + 0x1, 0x2, 0x2, 0x4, 0x8, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x14, 0x1, 0x53, + 0x1, 0x80, 0x8b, 0x8, 0x80, 0x9e, 0x9, 0x26, 0x2, 0x1, 0x7, 0x27, 0x48, + 0x1b, 0x5, 0x3, 0x2d, 0x2b, 0x23, 0x2, 0x1, 0x63, 0x1, 0x1, 0xf, 0x2, 0x7, + 0x2, 0xa, 0x3, 0x2, 0x1, 0x10, 0x1, 0x1, 0x1e, 0x1d, 0x59, 0xb, 0x1, + 0x18, 0x21, 0x9, 0x2, 0x4, 0x1, 0x5, 0x16, 0x4, 0x1, 0x9, 0x1, 0x3, 0x1, + 0x17, 0x19, 0x47, 0x1, 0x1, 0xb, 0x57, 0x36, 0x3, 0x1, 0x12, 0x1, 0x7, + 0xa, 0xf, 0x7, 0x1, 0x7, 0x5, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, + 0x1, 0x3, 0x4, 0x3, 0x1, 0x10, 0x1, 0xd, 0x2, 0x1, 0x3, 0xe, 0x2, 0x13, + 0x6, 0x4, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x2, 0x1, 0x2, 0x1f, + 0x4, 0x1, 0x1, 0x13, 0x3, 0x10, 0x9, 0x1, 0x3, 0x1, 0x16, 0x1, 0x7, 0x1, + 0x2, 0x1, 0x5, 0x3, 0x1, 0x12, 0x1, 0xf, 0x2, 0x23, 0x8, 0x2, 0x2, 0x2, + 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x3, 0x1, 0x1e, 0x2, 0x1, 0x3, 0xf, + 0x1, 0x11, 0x1, 0x1, 0x6, 0x3, 0x3, 0x1, 0x4, 0x3, 0x2, 0x1, 0x1, 0x1, + 0x2, 0x3, 0x2, 0x3, 0x3, 0x3, 0xc, 0x16, 0x1, 0x34, 0x8, 0x1, 0x3, 0x1, + 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x1, 0x1a, 0x2, 0x6, 0x2, 0x23, 0x8, 0x1, + 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x1, 0x20, 0x1, 0x1, 0x2, 0xf, + 0x2, 0x12, 0x8, 0x1, 0x3, 0x1, 0x29, 0x2, 0x1, 0x10, 0x1, 0x11, 0x2, + 0x18, 0x6, 0x5, 0x12, 0x3, 0x18, 0x1, 0x9, 0x1, 0x1, 0x2, 0x7, 0x3a, 0x30, + 0x1, 0x2, 0xc, 0x7, 0x3a, 0x2, 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x2, 0x1, + 0x6, 0x4, 0x1, 0x7, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0x4, 0x1, + 0x2, 0x9, 0x1, 0x2, 0x5, 0x1, 0x1, 0x15, 0x4, 0x20, 0x1, 0x3f, 0x8, 0x1, + 0x24, 0x1b, 0x5, 0x73, 0x2b, 0x14, 0x1, 0x10, 0x6, 0x4, 0x4, 0x3, 0x1, + 0x3, 0x2, 0x7, 0x3, 0x4, 0xd, 0xc, 0x1, 0x11, 0x26, 0x1, 0x1, 0x5, 0x1, + 0x2, 0x2b, 0x1, 0x81, 0x4d, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, + 0x29, 0x1, 0x4, 0x2, 0x21, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, + 0xf, 0x1, 0x39, 0x1, 0x4, 0x2, 0x43, 0x25, 0x10, 0x10, 0x55, 0xc, 0x82, + 0x6c, 0x2, 0x11, 0x1, 0x1a, 0x5, 0x4b, 0x3, 0x3, 0xf, 0xd, 0x1, 0x4, 0xe, + 0x12, 0xe, 0x12, 0xe, 0xd, 0x1, 0x3, 0xf, 0x34, 0x23, 0x1, 0x4, 0x1, + 0x43, 0x58, 0x8, 0x29, 0x1, 0x1, 0x5, 0x46, 0xa, 0x1d, 0x33, 0x1e, 0x2, + 0x5, 0xb, 0x2c, 0x15, 0x7, 0x38, 0x17, 0x9, 0x35, 0x52, 0x1, 0x5d, 0x2f, + 0x11, 0x7, 0x37, 0x1e, 0xd, 0x2, 0xa, 0x2c, 0x1a, 0x24, 0x29, 0x3, 0xa, + 0x24, 0x6b, 0x4, 0x1, 0x4, 0x3, 0x2, 0x9, 0x80, 0xc0, 0x40, 0x81, 0x16, + 0x2, 0x6, 0x2, 0x26, 0x2, 0x6, 0x2, 0x8, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1f, 0x2, 0x35, 0x1, 0x7, 0x1, 0x1, 0x3, 0x3, 0x1, 0x7, 0x3, 0x4, 0x2, + 0x6, 0x4, 0xd, 0x5, 0x3, 0x1, 0x7, 0x74, 0x1, 0xd, 0x1, 0x10, 0xd, 0x65, + 0x1, 0x4, 0x1, 0x2, 0xa, 0x1, 0x1, 0x2, 0x6, 0x6, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x10, 0x2, 0x4, 0x5, 0x5, 0x4, 0x1, 0x11, 0x29, 0x8a, 0x77, 0x2f, + 0x1, 0x2f, 0x1, 0x80, 0x85, 0x6, 0x4, 0x3, 0x2, 0xc, 0x26, 0x1, 0x1, 0x5, + 0x1, 0x2, 0x38, 0x7, 0x1, 0x10, 0x17, 0x9, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, + 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x82, 0x26, 0x3, 0x19, 0x9, + 0x7, 0x5, 0x2, 0x5, 0x4, 0x56, 0x4, 0x5, 0x1, 0x5a, 0x1, 0x4, 0x5, 0x29, + 0x3, 0x5e, 0x11, 0x1b, 0x35, 0x10, 0x82, 0x0, 0x99, 0xb6, 0x4a, 0xa0, 0x51, + 0xcd, 0x33, 0x84, 0x8d, 0x43, 0x2e, 0x2, 0x81, 0xd, 0x3, 0x10, 0xa, 0x2, + 0x14, 0x2f, 0x10, 0x19, 0x8, 0x50, 0x27, 0x9, 0x2, 0x67, 0x2, 0x4, 0x1, + 0x4, 0xc, 0xb, 0x4d, 0xa, 0x1, 0x3, 0x1, 0x4, 0x1, 0x17, 0x1d, 0x34, 0xe, + 0x32, 0x3e, 0x6, 0x3, 0x1, 0xe, 0x1c, 0xa, 0x17, 0x19, 0x1d, 0x7, 0x2f, + 0x1c, 0x1, 0x30, 0x29, 0x17, 0x3, 0x1, 0x8, 0x14, 0x17, 0x3, 0x1, 0x5, + 0x30, 0x1, 0x1, 0x3, 0x2, 0x2, 0x5, 0x2, 0x1, 0x1, 0x1, 0x18, 0x3, 0x2, + 0xb, 0x7, 0x3, 0xc, 0x6, 0x2, 0x6, 0x2, 0x6, 0x9, 0x7, 0x1, 0x7, 0x80, + 0x91, 0x23, 0x1d, 0xa0, 0x2b, 0xa4, 0xc, 0x17, 0x4, 0x31, 0xa0, 0x21, 0x4, + 0x81, 0x6e, 0x2, 0x6a, 0x26, 0x7, 0xc, 0x5, 0x5, 0x1, 0x1, 0xa, 0x1, 0xd, + 0x1, 0x5, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x6c, 0x21, 0x81, 0x6b, 0x12, + 0x40, 0x2, 0x36, 0x28, 0xc, 0x74, 0x5, 0x1, 0x80, 0x87, 0x24, 0x1a, 0x6, + 0x1a, 0xb, 0x59, 0x3, 0x6, 0x2, 0x6, 0x2, 0x6, 0x2, 0x3, 0x23, 0xc, 0x1, + 0x1a, 0x1, 0x13, 0x1, 0x2, 0x1, 0xf, 0x2, 0xe, 0x22, 0x7b, 0x45, 0x35, + 0x81, 0xb, 0x1d, 0x3, 0x31, 0x2f, 0x1f, 0x11, 0x1b, 0x35, 0x1e, 0x2, 0x24, + 0x4, 0x8, 0x1, 0x5, 0x2a, 0x80, 0x9e, 0x83, 0x62, 0x6, 0x2, 0x1, 0x1, + 0x2c, 0x1, 0x2, 0x3, 0x1, 0x2, 0x17, 0x80, 0xaa, 0x16, 0xa, 0x1a, 0x46, + 0x38, 0x6, 0x2, 0x40, 0x1, 0xf, 0x4, 0x1, 0x3, 0x1, 0x1b, 0x2c, 0x1d, 0x80, + 0x83, 0x36, 0xa, 0x16, 0xa, 0x13, 0x80, 0x8d, 0x49, 0x83, 0xba, 0x35, 0x4b, + 0x2d, 0x20, 0x19, 0x1a, 0x24, 0x5c, 0x30, 0xe, 0x4, 0x84, 0xbb, 0x2b, 0x89, + 0x55, 0x83, 0x6f, 0x80, 0x91, 0x63, 0x8b, 0x9d, 0x84, 0x2f, 0xa0, 0x33, + 0xd1, 0x82, 0x39, 0x84, 0xc7, 0x45, 0xb, 0x1, 0x42, 0xd, 0xa0, 0x40, 0x60, + 0x2, 0xa0, 0x23, 0xfe, 0x55, 0x1, 0x47, 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, + 0x4, 0x1, 0xc, 0x1, 0x1, 0x1, 0x7, 0x1, 0x41, 0x1, 0x4, 0x2, 0x8, 0x1, + 0x7, 0x1, 0x1c, 0x1, 0x4, 0x1, 0x5, 0x1, 0x1, 0x3, 0x7, 0x1, 0x81, 0x54, + 0x2, 0x19, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, + 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x8, 0x96, 0x34, 0x4, 0x1, + 0x1b, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0xa, 0x1, 0x4, 0x1, 0x1, 0x1, + 0x1, 0x6, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x2, 0x1, 0x1, + 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, + 0x4, 0x1, 0x7, 0x1, 0x4, 0x1, 0x4, 0x1, 0x1, 0x1, 0xa, 0x1, 0x11, 0x5, + 0x3, 0x1, 0x5, 0x1, 0x11, 0x91, 0x44, 0xa0, 0xa6, 0xd7, 0x29, 0x90, 0x35, + 0xb, 0x80, 0xde, 0xa0, 0x3f, 0xe2, 0x82, 0x1e +]; +_T Other_Grapheme_Extend = [ + 0x89, 0xbe, 0x1, 0x18, 0x1, 0x81, 0x66, 0x1, 0x18, 0x1, 0x66, 0x1, 0x18, + 0x1, 0x80, 0xea, 0x1, 0x12, 0x2, 0x67, 0x1, 0x18, 0x1, 0x77, 0x1, 0xf, 0x1, + 0x92, 0x2c, 0x2, 0x90, 0x20, 0x2, 0xa0, 0xcf, 0x6e, 0x2, 0xa0, 0xd1, 0xc5, 0x1, + 0x8, 0x5 +]; +_T Lt = [0x81, 0xc5, 0x1, 0x2, 0x1, 0x2, 0x1, 0x26, 0x1, 0x9d, 0x95, 0x8, 0x8, + 0x8, 0x8, 0x8, 0xc, 0x1, 0xf, 0x1, 0x2f, 0x1]; +_T Pattern_White_Space = [0x9, 0x5, 0x12, 0x1, 0x64, 0x1, 0x9f, 0x88, 0x2, 0x18, 0x2]; +_T Cased = [0x41, 0x1a, 0x6, 0x1a, 0x2f, 0x1, 0xa, 0x1, 0x4, 0x1, 0x5, 0x17, + 0x1, 0x1f, 0x1, 0x80, 0xc3, 0x1, 0x4, 0x4, 0x80, 0xd0, 0x1, 0x24, 0x7, 0x2, + 0x1e, 0x5, 0x60, 0x1, 0x2a, 0x4, 0x2, 0x2, 0x2, 0x4, 0x8, 0x1, 0x1, 0x3, + 0x1, 0x1, 0x1, 0x14, 0x1, 0x53, 0x1, 0x80, 0x8b, 0x8, 0x80, 0x9e, 0x9, + 0x26, 0xa, 0x27, 0x8b, 0x18, 0x26, 0x1, 0x1, 0x5, 0x1, 0x8c, 0x32, 0x80, + 0xc0, 0x40, 0x81, 0x16, 0x2, 0x6, 0x2, 0x26, 0x2, 0x6, 0x2, 0x8, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1f, 0x2, 0x35, 0x1, 0x7, 0x1, 0x1, 0x3, 0x3, + 0x1, 0x7, 0x3, 0x4, 0x2, 0x6, 0x4, 0xd, 0x5, 0x3, 0x1, 0x7, 0x74, 0x1, 0xd, + 0x1, 0x10, 0xd, 0x65, 0x1, 0x4, 0x1, 0x2, 0xa, 0x1, 0x1, 0x3, 0x5, 0x6, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x1, 0x6, 0x4, 0x1, 0x2, 0x4, 0x5, + 0x5, 0x4, 0x1, 0x11, 0x20, 0x3, 0x2, 0x83, 0x31, 0x34, 0x87, 0x16, 0x2f, + 0x1, 0x2f, 0x1, 0x80, 0x85, 0x6, 0x4, 0x3, 0x2, 0xc, 0x26, 0x1, 0x1, 0x5, + 0x1, 0xa0, 0x79, 0x12, 0x2e, 0x12, 0x18, 0x80, 0x8a, 0x66, 0x3, 0x4, 0x1, + 0x4, 0xc, 0xb, 0x4d, 0x3, 0xa0, 0x53, 0x5, 0x7, 0xc, 0x5, 0x84, 0x9, 0x1a, + 0x6, 0x1a, 0x84, 0xa5, 0x50, 0xa0, 0xcf, 0xb0, 0x55, 0x1, 0x47, 0x1, 0x2, + 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0xc, 0x1, 0x1, 0x1, 0x7, 0x1, 0x41, + 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, 0x1, 0x1c, 0x1, 0x4, 0x1, 0x5, 0x1, 0x1, 0x3, + 0x7, 0x1, 0x81, 0x54, 0x2, 0x19, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, + 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x8]; +_T Mn = [0x83, 0x0, 0x70, 0x81, 0x13, 0x5, 0x81, 0x9, 0x2d, 0x1, 0x1, 0x1, + 0x2, 0x1, 0x2, 0x1, 0x1, 0x48, 0xb, 0x30, 0x15, 0x10, 0x1, 0x65, 0x7, 0x2, + 0x6, 0x2, 0x2, 0x1, 0x4, 0x23, 0x1, 0x1e, 0x1b, 0x5b, 0xb, 0x3a, 0x9, + 0x22, 0x4, 0x1, 0x9, 0x1, 0x3, 0x1, 0x5, 0x2b, 0x3, 0x80, 0x88, 0x1b, + 0x1, 0x3, 0x37, 0x1, 0x1, 0x1, 0x4, 0x8, 0x4, 0x1, 0x3, 0x7, 0xa, 0x2, + 0x1d, 0x1, 0x3a, 0x1, 0x4, 0x4, 0x8, 0x1, 0x14, 0x2, 0x1d, 0x2, 0x39, 0x1, + 0x4, 0x2, 0x4, 0x2, 0x2, 0x3, 0x3, 0x1, 0x1e, 0x2, 0x3, 0x1, 0xb, 0x2, + 0x39, 0x1, 0x4, 0x5, 0x1, 0x2, 0x4, 0x1, 0x14, 0x2, 0x1d, 0x1, 0x3a, 0x1, + 0x2, 0x1, 0x1, 0x4, 0x8, 0x1, 0x8, 0x1, 0xb, 0x2, 0x1e, 0x1, 0x3d, 0x1, + 0xc, 0x1, 0x70, 0x3, 0x5, 0x3, 0x1, 0x4, 0x7, 0x2, 0xb, 0x2, 0x58, 0x1, + 0x2, 0x1, 0x6, 0x1, 0x5, 0x2, 0x14, 0x2, 0x5d, 0x4, 0x8, 0x1, 0x14, 0x2, + 0x66, 0x1, 0x7, 0x3, 0x1, 0x1, 0x5a, 0x1, 0x2, 0x7, 0xc, 0x8, 0x62, 0x1, + 0x2, 0x6, 0x1, 0x2, 0xb, 0x6, 0x4a, 0x2, 0x1b, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x37, 0xe, 0x1, 0x5, 0x1, 0x2, 0x5, 0xb, 0x1, 0x24, 0x9, 0x1, 0x66, 0x4, + 0x1, 0x6, 0x1, 0x2, 0x2, 0x2, 0x19, 0x2, 0x4, 0x3, 0x10, 0x4, 0xd, 0x1, + 0x2, 0x2, 0x6, 0x1, 0xf, 0x1, 0x82, 0xbf, 0x3, 0x83, 0xb2, 0x3, 0x1d, + 0x3, 0x1d, 0x2, 0x1e, 0x2, 0x40, 0x2, 0x1, 0x7, 0x8, 0x1, 0x2, 0xb, 0x9, + 0x1, 0x2d, 0x3, 0x80, 0x9b, 0x1, 0x76, 0x3, 0x4, 0x2, 0x9, 0x1, 0x6, 0x3, + 0x80, 0xdb, 0x2, 0x2, 0x1, 0x3a, 0x1, 0x1, 0x7, 0x1, 0x1, 0x1, 0x1, 0x2, + 0x8, 0x6, 0xa, 0x2, 0x1, 0x80, 0x80, 0x4, 0x30, 0x1, 0x1, 0x5, 0x1, 0x1, + 0x5, 0x1, 0x28, 0x9, 0xc, 0x2, 0x20, 0x4, 0x2, 0x2, 0x1, 0x1, 0x3a, 0x1, + 0x1, 0x2, 0x3, 0x1, 0x1, 0x3, 0x3a, 0x8, 0x2, 0x2, 0x80, 0x98, 0x3, 0x1, + 0xd, 0x1, 0x7, 0x4, 0x1, 0x6, 0x1, 0x80, 0xcb, 0x27, 0x15, 0x4, 0x82, + 0xd0, 0xd, 0x4, 0x1, 0x3, 0xc, 0x8b, 0xfe, 0x3, 0x80, 0x8d, 0x1, 0x60, + 0x20, 0x82, 0x2a, 0x4, 0x6b, 0x2, 0xa0, 0x75, 0xd4, 0x1, 0x4, 0xa, 0x21, + 0x1, 0x50, 0x2, 0x81, 0x10, 0x1, 0x3, 0x1, 0x4, 0x1, 0x19, 0x2, 0x80, 0x9d, + 0x1, 0x1b, 0x12, 0x34, 0x8, 0x19, 0xb, 0x2e, 0x3, 0x30, 0x1, 0x2, 0x4, + 0x2, 0x1, 0x6c, 0x6, 0x2, 0x2, 0x2, 0x2, 0xc, 0x1, 0x8, 0x1, 0x63, 0x1, + 0x1, 0x3, 0x2, 0x2, 0x5, 0x2, 0x1, 0x1, 0x2a, 0x2, 0x8, 0x1, 0x80, 0xee, + 0x1, 0x2, 0x1, 0x4, 0x1, 0xa0, 0x4f, 0x30, 0x1, 0x82, 0xe1, 0x10, 0x10, + 0x7, 0x83, 0xd6, 0x1, 0x88, 0x3, 0x3, 0x1, 0x2, 0x5, 0x4, 0x28, 0x3, 0x4, + 0x1, 0x85, 0xc1, 0x1, 0x36, 0xf, 0x39, 0x2, 0x31, 0x4, 0x2, 0x2, 0x45, + 0x3, 0x24, 0x5, 0x1, 0x8, 0x4b, 0x2, 0x34, 0x9, 0x84, 0xec, 0x1, 0x1, 0x1, + 0x2, 0x6, 0x1, 0x1, 0xa0, 0x58, 0xd7, 0x4, 0xa0, 0x61, 0xd4, 0x3, 0x11, + 0x8, 0x2, 0x7, 0x1e, 0x4, 0x80, 0x94, 0x3, 0xac, 0x2e, 0xbb, 0x80, 0xf0]; +_T Dash = [0x2d, 0x1, 0x85, 0x5c, 0x1, 0x33, 0x1, 0x8e, 0x41, 0x1, 0x84, 0x5, + 0x1, 0x88, 0x9, 0x6, 0x3d, 0x1, 0x27, 0x1, 0xf, 0x1, 0x81, 0x86, 0x1, + 0x8c, 0x4, 0x1, 0x2, 0x1, 0x1f, 0x2, 0x81, 0xe0, 0x1, 0x13, 0x1, 0x6f, 0x1, + 0xa0, 0xcd, 0x90, 0x2, 0x25, 0x1, 0xa, 0x1, 0x80, 0xa9, 0x1]; +_T ID_Continue = [ + 0x30, 0xa, 0x7, 0x1a, 0x4, 0x1, 0x1, 0x1a, 0x2f, 0x1, 0xa, 0x1, 0x1, 0x1, + 0x2, 0x1, 0x5, 0x17, 0x1, 0x1f, 0x1, 0x81, 0xca, 0x4, 0xc, 0xe, 0x5, 0x7, + 0x1, 0x1, 0x1, 0x11, 0x75, 0x1, 0x2, 0x2, 0x4, 0x8, 0x5, 0x1, 0x1, 0x1, + 0x14, 0x1, 0x53, 0x1, 0x80, 0x8b, 0x1, 0x5, 0x2, 0x80, 0x9e, 0x9, 0x26, + 0x2, 0x1, 0x7, 0x27, 0x9, 0x2d, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, + 0x8, 0x1b, 0x5, 0x3, 0x1d, 0xb, 0x5, 0x4a, 0x4, 0x66, 0x1, 0x8, 0x2, 0xa, + 0x1, 0x13, 0x2, 0x1, 0x10, 0x3b, 0x2, 0x65, 0xe, 0x36, 0x4, 0x1, 0x5, 0x2e, + 0x12, 0x1c, 0x44, 0x1, 0x1, 0xb, 0x37, 0x1b, 0x1, 0x64, 0x2, 0xa, 0x1, + 0x7, 0x1, 0x7, 0x1, 0x3, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, + 0x1, 0x3, 0x4, 0x2, 0x9, 0x2, 0x2, 0x2, 0x4, 0x8, 0x1, 0x4, 0x2, 0x1, 0x5, + 0x2, 0xc, 0xf, 0x3, 0x1, 0x6, 0x4, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, + 0x1, 0x2, 0x1, 0x2, 0x2, 0x1, 0x1, 0x5, 0x4, 0x2, 0x2, 0x3, 0x3, 0x1, 0x7, + 0x4, 0x1, 0x1, 0x7, 0x10, 0xb, 0x3, 0x1, 0x9, 0x1, 0x3, 0x1, 0x16, 0x1, + 0x7, 0x1, 0x2, 0x1, 0x5, 0x2, 0xa, 0x1, 0x3, 0x1, 0x3, 0x2, 0x1, 0xf, + 0x4, 0x2, 0xa, 0x11, 0x3, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, + 0x2, 0x1, 0x5, 0x2, 0x9, 0x2, 0x2, 0x2, 0x3, 0x8, 0x2, 0x4, 0x2, 0x1, 0x5, + 0x2, 0xa, 0x1, 0x1, 0x10, 0x2, 0x1, 0x6, 0x3, 0x3, 0x1, 0x4, 0x3, 0x2, + 0x1, 0x1, 0x1, 0x2, 0x3, 0x2, 0x3, 0x3, 0x3, 0xc, 0x4, 0x5, 0x3, 0x3, 0x1, + 0x4, 0x2, 0x1, 0x6, 0x1, 0xe, 0xa, 0x11, 0x3, 0x1, 0x8, 0x1, 0x3, 0x1, + 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x8, 0x1, 0x3, 0x1, 0x4, 0x7, 0x2, 0x1, + 0x2, 0x6, 0x4, 0x2, 0xa, 0x12, 0x2, 0x1, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, + 0xa, 0x1, 0x5, 0x2, 0x9, 0x1, 0x3, 0x1, 0x4, 0x7, 0x2, 0x7, 0x1, 0x1, 0x4, + 0x2, 0xa, 0x1, 0x2, 0xf, 0x2, 0x1, 0x8, 0x1, 0x3, 0x1, 0x29, 0x2, 0x8, + 0x1, 0x3, 0x1, 0x5, 0x8, 0x1, 0x8, 0x4, 0x2, 0xa, 0xa, 0x6, 0x2, 0x2, + 0x1, 0x12, 0x3, 0x18, 0x1, 0x9, 0x1, 0x1, 0x2, 0x7, 0x3, 0x1, 0x4, 0x6, + 0x1, 0x1, 0x1, 0x8, 0x12, 0x2, 0xd, 0x3a, 0x5, 0xf, 0x1, 0xa, 0x27, 0x2, + 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x2, 0x1, 0x6, 0x4, 0x1, 0x7, 0x1, 0x3, 0x1, + 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0xd, 0x1, 0x3, 0x2, 0x5, 0x1, 0x1, 0x1, 0x6, + 0x2, 0xa, 0x2, 0x4, 0x20, 0x1, 0x17, 0x2, 0x6, 0xa, 0xb, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x4, 0xa, 0x1, 0x24, 0x4, 0x14, 0x1, 0x12, 0x1, 0x24, 0x9, 0x1, + 0x39, 0x4a, 0x6, 0x4e, 0x2, 0x26, 0x1, 0x1, 0x5, 0x1, 0x2, 0x2b, 0x1, + 0x81, 0x4d, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0x29, 0x1, 0x4, + 0x2, 0x21, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0xf, 0x1, 0x39, + 0x1, 0x4, 0x2, 0x43, 0x2, 0x3, 0x9, 0x9, 0xe, 0x10, 0x10, 0x55, 0xc, + 0x82, 0x6c, 0x2, 0x11, 0x1, 0x1a, 0x5, 0x4b, 0x3, 0x3, 0xf, 0xd, 0x1, + 0x7, 0xb, 0x15, 0xb, 0x14, 0xc, 0xd, 0x1, 0x3, 0x1, 0x2, 0xc, 0x54, 0x3, + 0x1, 0x4, 0x2, 0x2, 0xa, 0x21, 0x3, 0x2, 0xa, 0x6, 0x58, 0x8, 0x2b, 0x5, + 0x46, 0xa, 0x1d, 0x3, 0xc, 0x4, 0xc, 0xa, 0x28, 0x2, 0x5, 0xb, 0x2c, 0x4, + 0x1a, 0x6, 0xb, 0x25, 0x1c, 0x4, 0x3f, 0x1, 0x1d, 0x2, 0xb, 0x6, 0xa, + 0xd, 0x1, 0x58, 0x4c, 0x4, 0xa, 0x11, 0x9, 0xc, 0x74, 0xc, 0x38, 0x8, + 0xa, 0x3, 0x31, 0x52, 0x3, 0x1, 0x23, 0x9, 0x80, 0xe7, 0x15, 0x81, 0x1a, + 0x2, 0x6, 0x2, 0x26, 0x2, 0x6, 0x2, 0x8, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1f, 0x2, 0x35, 0x1, 0x7, 0x1, 0x1, 0x3, 0x3, 0x1, 0x7, 0x3, 0x4, 0x2, + 0x6, 0x4, 0xd, 0x5, 0x3, 0x1, 0x7, 0x42, 0x2, 0x13, 0x1, 0x1c, 0x1, 0xd, + 0x1, 0x10, 0xd, 0x33, 0xd, 0x4, 0x1, 0x3, 0xc, 0x11, 0x1, 0x4, 0x1, 0x2, + 0xa, 0x1, 0x1, 0x2, 0x6, 0x6, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x10, 0x2, + 0x4, 0x5, 0x5, 0x4, 0x1, 0x11, 0x29, 0x8a, 0x77, 0x2f, 0x1, 0x2f, 0x1, + 0x80, 0x85, 0x6, 0x9, 0xc, 0x26, 0x1, 0x1, 0x5, 0x1, 0x2, 0x38, 0x7, 0x1, + 0xf, 0x18, 0x9, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, + 0x1, 0x7, 0x1, 0x7, 0x1, 0x20, 0x82, 0x5, 0x3, 0x19, 0xf, 0x1, 0x5, 0x2, + 0x5, 0x4, 0x56, 0x2, 0x7, 0x1, 0x5a, 0x1, 0x4, 0x5, 0x29, 0x3, 0x5e, 0x11, + 0x1b, 0x35, 0x10, 0x82, 0x0, 0x99, 0xb6, 0x4a, 0xa0, 0x51, 0xcd, 0x33, + 0x84, 0x8d, 0x43, 0x2e, 0x2, 0x81, 0xd, 0x3, 0x1c, 0x14, 0x30, 0x4, 0xa, + 0x1, 0x19, 0x7, 0x53, 0x25, 0x9, 0x2, 0x67, 0x2, 0x4, 0x1, 0x4, 0xc, 0xb, + 0x4d, 0x30, 0x18, 0x34, 0xc, 0x45, 0xb, 0xa, 0x6, 0x18, 0x3, 0x1, 0x4, + 0x2e, 0x2, 0x24, 0xc, 0x1d, 0x3, 0x41, 0xe, 0xb, 0x26, 0x37, 0x9, 0xe, + 0x2, 0xa, 0x6, 0x17, 0x3, 0x2, 0x4, 0x43, 0x18, 0x3, 0x2, 0x10, 0x2, 0x5, + 0xa, 0x6, 0x2, 0x6, 0x2, 0x6, 0x9, 0x7, 0x1, 0x7, 0x80, 0x91, 0x2b, 0x1, + 0x2, 0x2, 0xa, 0x6, 0xa0, 0x2b, 0xa4, 0xc, 0x17, 0x4, 0x31, 0xa0, 0x21, + 0x4, 0x81, 0x6e, 0x2, 0x6a, 0x26, 0x7, 0xc, 0x5, 0x5, 0xc, 0x1, 0xd, 0x1, + 0x5, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x6c, 0x21, 0x81, 0x6b, 0x12, 0x40, + 0x2, 0x36, 0x28, 0xc, 0x4, 0x10, 0x10, 0x7, 0xc, 0x2, 0x18, 0x3, 0x20, 0x5, + 0x1, 0x80, 0x87, 0x13, 0xa, 0x7, 0x1a, 0x4, 0x1, 0x1, 0x1a, 0xb, 0x59, + 0x3, 0x6, 0x2, 0x6, 0x2, 0x6, 0x2, 0x3, 0x23, 0xc, 0x1, 0x1a, 0x1, 0x13, + 0x1, 0x2, 0x1, 0xf, 0x2, 0xe, 0x22, 0x7b, 0x45, 0x35, 0x80, 0x88, 0x1, + 0x80, 0x82, 0x1d, 0x3, 0x31, 0x2f, 0x1f, 0x11, 0x1b, 0x35, 0x1e, 0x2, + 0x24, 0x4, 0x8, 0x1, 0x5, 0x2a, 0x80, 0x9e, 0x2, 0xa, 0x83, 0x56, 0x6, + 0x2, 0x1, 0x1, 0x2c, 0x1, 0x2, 0x3, 0x1, 0x2, 0x17, 0x80, 0xaa, 0x16, 0xa, + 0x1a, 0x46, 0x38, 0x6, 0x2, 0x40, 0x4, 0x1, 0x2, 0x5, 0x8, 0x1, 0x3, 0x1, + 0x1b, 0x4, 0x3, 0x4, 0x1, 0x20, 0x1d, 0x80, 0x83, 0x36, 0xa, 0x16, 0xa, + 0x13, 0x80, 0x8d, 0x49, 0x83, 0xb7, 0x47, 0x1f, 0xa, 0x10, 0x3b, 0x15, + 0x19, 0x7, 0xa, 0x6, 0x35, 0x1, 0xa, 0x40, 0x45, 0xb, 0xa, 0x84, 0xa6, + 0x38, 0x8, 0xa, 0x89, 0x36, 0x83, 0x6f, 0x80, 0x91, 0x63, 0x8b, 0x9d, 0x84, + 0x2f, 0xa0, 0x33, 0xd1, 0x82, 0x39, 0x84, 0xc7, 0x45, 0xb, 0x2f, 0x10, + 0x11, 0xa0, 0x40, 0x60, 0x2, 0xa0, 0x21, 0x63, 0x5, 0x3, 0x6, 0x8, 0x8, + 0x2, 0x7, 0x1e, 0x4, 0x80, 0x94, 0x3, 0x81, 0xbb, 0x55, 0x1, 0x47, 0x1, + 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0xc, 0x1, 0x1, 0x1, 0x7, 0x1, + 0x41, 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, 0x1, 0x1c, 0x1, 0x4, 0x1, 0x5, 0x1, + 0x1, 0x3, 0x7, 0x1, 0x81, 0x54, 0x2, 0x19, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, + 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x8, + 0x2, 0x32, 0x96, 0x0, 0x4, 0x1, 0x1b, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, + 0xa, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x6, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x3, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x4, 0x1, 0x7, 0x1, 0x4, 0x1, 0x4, 0x1, + 0x1, 0x1, 0xa, 0x1, 0x11, 0x5, 0x3, 0x1, 0x5, 0x1, 0x11, 0x91, 0x44, 0xa0, + 0xa6, 0xd7, 0x29, 0x90, 0x35, 0xb, 0x80, 0xde, 0xa0, 0x3f, 0xe2, 0x82, + 0x1e, 0xab, 0x6, 0xe2, 0x80, 0xf0 +]; +_T White_Space = [ + 0x9, 0x5, 0x12, 0x1, 0x64, 0x1, 0x1a, 0x1, 0x95, 0xdf, 0x1, 0x89, 0x7f, + 0xb, 0x1d, 0x2, 0x5, 0x1, 0x2f, 0x1, 0x8f, 0xa0, 0x1 +]; +_T Grapheme_Link = [ + 0x89, 0x4d, 0x1, 0x7f, 0x1, 0x7f, 0x1, 0x7f, 0x1, 0x7f, 0x1, 0x7f, 0x1, + 0x7f, 0x1, 0x7f, 0x1, 0x7f, 0x1, 0x7c, 0x1, 0x6f, 0x1, 0x81, 0x49, 0x1, + 0x80, 0xb4, 0x2, 0x86, 0xd9, 0x1, 0x1f, 0x1, 0x80, 0x9d, 0x1, 0x82, 0x8d, + 0x1, 0x80, 0xe3, 0x1, 0x65, 0x2, 0x46, 0x2, 0x91, 0x8b, 0x1, 0xa0, 0x7a, + 0x86, 0x1, 0x80, 0xbd, 0x1, 0x80, 0x8e, 0x1, 0x6c, 0x1, 0x81, 0x35, 0x1, + 0x80, 0xf6, 0x1, 0xa0, 0x5e, 0x51, 0x1, 0x86, 0x6, 0x1, 0x72, 0x1, 0x79, + 0x2, 0x80, 0x8b, 0x1, 0x84, 0xf5, 0x1 +]; +_T Ll = [0x61, 0x1a, 0x3a, 0x1, 0x29, 0x18, 0x1, 0x8, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x3, 0x2, + 0x1, 0x1, 0x1, 0x2, 0x1, 0x3, 0x2, 0x4, 0x1, 0x2, 0x1, 0x3, 0x3, 0x2, 0x1, + 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x3, + 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x3, 0x6, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x2, 0x2, 0x1, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x7, 0x2, 0x1, 0x2, 0x2, 0x1, 0x1, 0x4, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x45, 0x1, 0x1b, 0x80, 0xc1, 0x1, 0x1, 0x1, + 0x3, 0x1, 0x3, 0x3, 0x12, 0x1, 0x1b, 0x23, 0x1, 0x2, 0x3, 0x3, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x5, 0x1, 0x1, 0x2, 0x1, 0x2, 0x2, 0x33, + 0x30, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x9, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x39, 0x27, + 0x97, 0x78, 0x2c, 0x3f, 0xd, 0x1, 0x22, 0x66, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x9, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x9, 0x8, 0x6, 0xa, 0x8, 0x8, 0x8, 0x8, 0x6, 0xa, 0x8, 0x8, 0x8, 0x8, + 0xe, 0x2, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x5, 0x1, 0x2, 0x6, 0x1, 0x3, 0x3, + 0x1, 0x2, 0x8, 0x4, 0x2, 0x2, 0x8, 0x8, 0xa, 0x3, 0x1, 0x2, 0x81, 0x12, + 0x1, 0x3, 0x2, 0x3, 0x1, 0x1b, 0x1, 0x4, 0x1, 0x4, 0x1, 0x2, 0x2, 0x8, + 0x4, 0x4, 0x1, 0x35, 0x1, 0x8a, 0xab, 0x2f, 0x2, 0x1, 0x3, 0x2, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x4, 0x1, 0x1, 0x2, 0x1, 0x6, 0x5, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x1, 0xc, 0x26, 0x1, 0x1, + 0x5, 0x1, 0xa0, 0x79, 0x13, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x13, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x80, 0x8b, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x8, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0xd, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x50, 0x1, 0xa0, 0x53, 0x5, + 0x7, 0xc, 0x5, 0x84, 0x29, 0x1a, 0x84, 0xcd, 0x28, 0xa0, 0xcf, 0xca, + 0x1a, 0x1a, 0x7, 0x1, 0x12, 0x1a, 0x1a, 0x1a, 0x4, 0x1, 0x1, 0x1, 0x7, 0x1, + 0xb, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1c, 0x1c, 0x19, 0x1, 0x6, 0x1a, 0x19, + 0x1, 0x6, 0x1a, 0x19, 0x1, 0x6, 0x1a, 0x19, 0x1, 0x6, 0x1a, 0x19, 0x1, 0x6, 0x1, + 0x1]; +_T Cc = [0x0, 0x20, 0x5f, 0x21]; +_T Pattern_Syntax = [ + 0x21, 0xf, 0xa, 0x7, 0x1a, 0x4, 0x1, 0x1, 0x1a, 0x4, 0x22, 0x7, 0x1, 0x1, + 0x1, 0x2, 0x1, 0x1, 0x1, 0x2, 0x4, 0x1, 0x4, 0x1, 0x3, 0x1, 0x17, 0x1, + 0x1f, 0x1, 0x9f, 0x18, 0x18, 0x8, 0xf, 0x2, 0x13, 0x1, 0xa, 0x81, 0x31, + 0x82, 0xd0, 0x80, 0xa0, 0x82, 0x76, 0x1e, 0x84, 0x6c, 0x82, 0x0, 0x80, + 0x80, 0x81, 0x81, 0x3, 0x4, 0x19, 0xf, 0x1, 0xa0, 0xcd, 0xd, 0x2, 0x81, 0x5, 0x2 +]; +_T XID_Continue = [ + 0x30, 0xa, 0x7, 0x1a, 0x4, 0x1, 0x1, 0x1a, 0x2f, 0x1, 0xa, 0x1, 0x1, 0x1, + 0x2, 0x1, 0x5, 0x17, 0x1, 0x1f, 0x1, 0x81, 0xca, 0x4, 0xc, 0xe, 0x5, 0x7, + 0x1, 0x1, 0x1, 0x11, 0x75, 0x1, 0x2, 0x3, 0x3, 0x8, 0x5, 0x1, 0x1, 0x1, + 0x14, 0x1, 0x53, 0x1, 0x80, 0x8b, 0x1, 0x5, 0x2, 0x80, 0x9e, 0x9, 0x26, + 0x2, 0x1, 0x7, 0x27, 0x9, 0x2d, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, + 0x8, 0x1b, 0x5, 0x3, 0x1d, 0xb, 0x5, 0x4a, 0x4, 0x66, 0x1, 0x8, 0x2, 0xa, + 0x1, 0x13, 0x2, 0x1, 0x10, 0x3b, 0x2, 0x65, 0xe, 0x36, 0x4, 0x1, 0x5, 0x2e, + 0x12, 0x1c, 0x44, 0x1, 0x1, 0xb, 0x37, 0x1b, 0x1, 0x64, 0x2, 0xa, 0x1, + 0x7, 0x1, 0x7, 0x1, 0x3, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, + 0x1, 0x3, 0x4, 0x2, 0x9, 0x2, 0x2, 0x2, 0x4, 0x8, 0x1, 0x4, 0x2, 0x1, 0x5, + 0x2, 0xc, 0xf, 0x3, 0x1, 0x6, 0x4, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, + 0x1, 0x2, 0x1, 0x2, 0x2, 0x1, 0x1, 0x5, 0x4, 0x2, 0x2, 0x3, 0x3, 0x1, 0x7, + 0x4, 0x1, 0x1, 0x7, 0x10, 0xb, 0x3, 0x1, 0x9, 0x1, 0x3, 0x1, 0x16, 0x1, + 0x7, 0x1, 0x2, 0x1, 0x5, 0x2, 0xa, 0x1, 0x3, 0x1, 0x3, 0x2, 0x1, 0xf, + 0x4, 0x2, 0xa, 0x11, 0x3, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, + 0x2, 0x1, 0x5, 0x2, 0x9, 0x2, 0x2, 0x2, 0x3, 0x8, 0x2, 0x4, 0x2, 0x1, 0x5, + 0x2, 0xa, 0x1, 0x1, 0x10, 0x2, 0x1, 0x6, 0x3, 0x3, 0x1, 0x4, 0x3, 0x2, + 0x1, 0x1, 0x1, 0x2, 0x3, 0x2, 0x3, 0x3, 0x3, 0xc, 0x4, 0x5, 0x3, 0x3, 0x1, + 0x4, 0x2, 0x1, 0x6, 0x1, 0xe, 0xa, 0x11, 0x3, 0x1, 0x8, 0x1, 0x3, 0x1, + 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x8, 0x1, 0x3, 0x1, 0x4, 0x7, 0x2, 0x1, + 0x2, 0x6, 0x4, 0x2, 0xa, 0x12, 0x2, 0x1, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, + 0xa, 0x1, 0x5, 0x2, 0x9, 0x1, 0x3, 0x1, 0x4, 0x7, 0x2, 0x7, 0x1, 0x1, 0x4, + 0x2, 0xa, 0x1, 0x2, 0xf, 0x2, 0x1, 0x8, 0x1, 0x3, 0x1, 0x29, 0x2, 0x8, + 0x1, 0x3, 0x1, 0x5, 0x8, 0x1, 0x8, 0x4, 0x2, 0xa, 0xa, 0x6, 0x2, 0x2, + 0x1, 0x12, 0x3, 0x18, 0x1, 0x9, 0x1, 0x1, 0x2, 0x7, 0x3, 0x1, 0x4, 0x6, + 0x1, 0x1, 0x1, 0x8, 0x12, 0x2, 0xd, 0x3a, 0x5, 0xf, 0x1, 0xa, 0x27, 0x2, + 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x2, 0x1, 0x6, 0x4, 0x1, 0x7, 0x1, 0x3, 0x1, + 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0xd, 0x1, 0x3, 0x2, 0x5, 0x1, 0x1, 0x1, 0x6, + 0x2, 0xa, 0x2, 0x4, 0x20, 0x1, 0x17, 0x2, 0x6, 0xa, 0xb, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x4, 0xa, 0x1, 0x24, 0x4, 0x14, 0x1, 0x12, 0x1, 0x24, 0x9, 0x1, + 0x39, 0x4a, 0x6, 0x4e, 0x2, 0x26, 0x1, 0x1, 0x5, 0x1, 0x2, 0x2b, 0x1, + 0x81, 0x4d, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0x29, 0x1, 0x4, + 0x2, 0x21, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0xf, 0x1, 0x39, + 0x1, 0x4, 0x2, 0x43, 0x2, 0x3, 0x9, 0x9, 0xe, 0x10, 0x10, 0x55, 0xc, + 0x82, 0x6c, 0x2, 0x11, 0x1, 0x1a, 0x5, 0x4b, 0x3, 0x3, 0xf, 0xd, 0x1, + 0x7, 0xb, 0x15, 0xb, 0x14, 0xc, 0xd, 0x1, 0x3, 0x1, 0x2, 0xc, 0x54, 0x3, + 0x1, 0x4, 0x2, 0x2, 0xa, 0x21, 0x3, 0x2, 0xa, 0x6, 0x58, 0x8, 0x2b, 0x5, + 0x46, 0xa, 0x1d, 0x3, 0xc, 0x4, 0xc, 0xa, 0x28, 0x2, 0x5, 0xb, 0x2c, 0x4, + 0x1a, 0x6, 0xb, 0x25, 0x1c, 0x4, 0x3f, 0x1, 0x1d, 0x2, 0xb, 0x6, 0xa, + 0xd, 0x1, 0x58, 0x4c, 0x4, 0xa, 0x11, 0x9, 0xc, 0x74, 0xc, 0x38, 0x8, + 0xa, 0x3, 0x31, 0x52, 0x3, 0x1, 0x23, 0x9, 0x80, 0xe7, 0x15, 0x81, 0x1a, + 0x2, 0x6, 0x2, 0x26, 0x2, 0x6, 0x2, 0x8, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1f, 0x2, 0x35, 0x1, 0x7, 0x1, 0x1, 0x3, 0x3, 0x1, 0x7, 0x3, 0x4, 0x2, + 0x6, 0x4, 0xd, 0x5, 0x3, 0x1, 0x7, 0x42, 0x2, 0x13, 0x1, 0x1c, 0x1, 0xd, + 0x1, 0x10, 0xd, 0x33, 0xd, 0x4, 0x1, 0x3, 0xc, 0x11, 0x1, 0x4, 0x1, 0x2, + 0xa, 0x1, 0x1, 0x2, 0x6, 0x6, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x10, 0x2, + 0x4, 0x5, 0x5, 0x4, 0x1, 0x11, 0x29, 0x8a, 0x77, 0x2f, 0x1, 0x2f, 0x1, + 0x80, 0x85, 0x6, 0x9, 0xc, 0x26, 0x1, 0x1, 0x5, 0x1, 0x2, 0x38, 0x7, 0x1, + 0xf, 0x18, 0x9, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, + 0x1, 0x7, 0x1, 0x7, 0x1, 0x20, 0x82, 0x5, 0x3, 0x19, 0xf, 0x1, 0x5, 0x2, + 0x5, 0x4, 0x56, 0x2, 0x2, 0x2, 0x3, 0x1, 0x5a, 0x1, 0x4, 0x5, 0x29, 0x3, + 0x5e, 0x11, 0x1b, 0x35, 0x10, 0x82, 0x0, 0x99, 0xb6, 0x4a, 0xa0, 0x51, + 0xcd, 0x33, 0x84, 0x8d, 0x43, 0x2e, 0x2, 0x81, 0xd, 0x3, 0x1c, 0x14, + 0x30, 0x4, 0xa, 0x1, 0x19, 0x7, 0x53, 0x25, 0x9, 0x2, 0x67, 0x2, 0x4, + 0x1, 0x4, 0xc, 0xb, 0x4d, 0x30, 0x18, 0x34, 0xc, 0x45, 0xb, 0xa, 0x6, + 0x18, 0x3, 0x1, 0x4, 0x2e, 0x2, 0x24, 0xc, 0x1d, 0x3, 0x41, 0xe, 0xb, + 0x26, 0x37, 0x9, 0xe, 0x2, 0xa, 0x6, 0x17, 0x3, 0x2, 0x4, 0x43, 0x18, + 0x3, 0x2, 0x10, 0x2, 0x5, 0xa, 0x6, 0x2, 0x6, 0x2, 0x6, 0x9, 0x7, 0x1, 0x7, + 0x80, 0x91, 0x2b, 0x1, 0x2, 0x2, 0xa, 0x6, 0xa0, 0x2b, 0xa4, 0xc, 0x17, + 0x4, 0x31, 0xa0, 0x21, 0x4, 0x81, 0x6e, 0x2, 0x6a, 0x26, 0x7, 0xc, 0x5, + 0x5, 0xc, 0x1, 0xd, 0x1, 0x5, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x6c, + 0x21, 0x80, 0x8b, 0x6, 0x80, 0xda, 0x12, 0x40, 0x2, 0x36, 0x28, 0xa, 0x6, + 0x10, 0x10, 0x7, 0xc, 0x2, 0x18, 0x3, 0x21, 0x1, 0x1, 0x1, 0x3, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x7e, 0x13, 0xa, 0x7, 0x1a, 0x4, 0x1, 0x1, + 0x1a, 0xb, 0x59, 0x3, 0x6, 0x2, 0x6, 0x2, 0x6, 0x2, 0x3, 0x23, 0xc, 0x1, + 0x1a, 0x1, 0x13, 0x1, 0x2, 0x1, 0xf, 0x2, 0xe, 0x22, 0x7b, 0x45, 0x35, + 0x80, 0x88, 0x1, 0x80, 0x82, 0x1d, 0x3, 0x31, 0x2f, 0x1f, 0x11, 0x1b, 0x35, + 0x1e, 0x2, 0x24, 0x4, 0x8, 0x1, 0x5, 0x2a, 0x80, 0x9e, 0x2, 0xa, 0x83, + 0x56, 0x6, 0x2, 0x1, 0x1, 0x2c, 0x1, 0x2, 0x3, 0x1, 0x2, 0x17, 0x80, 0xaa, + 0x16, 0xa, 0x1a, 0x46, 0x38, 0x6, 0x2, 0x40, 0x4, 0x1, 0x2, 0x5, 0x8, + 0x1, 0x3, 0x1, 0x1b, 0x4, 0x3, 0x4, 0x1, 0x20, 0x1d, 0x80, 0x83, 0x36, 0xa, + 0x16, 0xa, 0x13, 0x80, 0x8d, 0x49, 0x83, 0xb7, 0x47, 0x1f, 0xa, 0x10, 0x3b, + 0x15, 0x19, 0x7, 0xa, 0x6, 0x35, 0x1, 0xa, 0x40, 0x45, 0xb, 0xa, 0x84, + 0xa6, 0x38, 0x8, 0xa, 0x89, 0x36, 0x83, 0x6f, 0x80, 0x91, 0x63, 0x8b, 0x9d, + 0x84, 0x2f, 0xa0, 0x33, 0xd1, 0x82, 0x39, 0x84, 0xc7, 0x45, 0xb, 0x2f, + 0x10, 0x11, 0xa0, 0x40, 0x60, 0x2, 0xa0, 0x21, 0x63, 0x5, 0x3, 0x6, 0x8, + 0x8, 0x2, 0x7, 0x1e, 0x4, 0x80, 0x94, 0x3, 0x81, 0xbb, 0x55, 0x1, 0x47, + 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0xc, 0x1, 0x1, 0x1, 0x7, + 0x1, 0x41, 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, 0x1, 0x1c, 0x1, 0x4, 0x1, 0x5, + 0x1, 0x1, 0x3, 0x7, 0x1, 0x81, 0x54, 0x2, 0x19, 0x1, 0x19, 0x1, 0x1f, + 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, + 0x19, 0x1, 0x8, 0x2, 0x32, 0x96, 0x0, 0x4, 0x1, 0x1b, 0x1, 0x2, 0x1, 0x1, + 0x2, 0x1, 0x1, 0xa, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x6, 0x1, 0x4, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x4, 0x1, 0x7, 0x1, 0x4, 0x1, + 0x4, 0x1, 0x1, 0x1, 0xa, 0x1, 0x11, 0x5, 0x3, 0x1, 0x5, 0x1, 0x11, 0x91, + 0x44, 0xa0, 0xa6, 0xd7, 0x29, 0x90, 0x35, 0xb, 0x80, 0xde, 0xa0, 0x3f, + 0xe2, 0x82, 0x1e, 0xab, 0x6, 0xe2, 0x80, 0xf0 +]; +_T Lowercase = [0x61, 0x1a, 0x2f, 0x1, 0xa, 0x1, 0x4, 0x1, 0x24, 0x18, 0x1, + 0x8, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, + 0x1, 0x1, 0x1, 0x1, 0x3, 0x2, 0x1, 0x1, 0x1, 0x2, 0x1, 0x3, 0x2, 0x4, 0x1, + 0x2, 0x1, 0x3, 0x3, 0x2, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, + 0x2, 0x1, 0x1, 0x2, 0x1, 0x3, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x3, 0x6, 0x1, + 0x2, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x1, 0x3, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x7, 0x2, 0x1, 0x2, + 0x2, 0x1, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x45, 0x1, + 0x24, 0x7, 0x2, 0x1e, 0x5, 0x60, 0x1, 0x2b, 0x1, 0x1, 0x1, 0x3, 0x1, 0x2, + 0x4, 0x12, 0x1, 0x1b, 0x23, 0x1, 0x2, 0x3, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x5, 0x1, 0x1, 0x2, 0x1, 0x2, 0x2, 0x33, 0x30, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x9, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x39, 0x27, 0x97, 0x78, 0x80, + 0xc0, 0x41, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x9, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x9, 0x8, 0x6, 0xa, 0x8, 0x8, + 0x8, 0x8, 0x6, 0xa, 0x8, 0x8, 0x8, 0x8, 0xe, 0x2, 0x8, 0x8, 0x8, 0x8, 0x8, + 0x8, 0x5, 0x1, 0x2, 0x6, 0x1, 0x3, 0x3, 0x1, 0x2, 0x8, 0x4, 0x2, 0x2, 0x8, + 0x8, 0xa, 0x3, 0x1, 0x2, 0x79, 0x1, 0xd, 0x1, 0x10, 0xd, 0x6d, 0x1, 0x3, + 0x2, 0x3, 0x1, 0x1b, 0x1, 0x4, 0x1, 0x4, 0x1, 0x2, 0x2, 0x8, 0x4, 0x4, 0x1, + 0x21, 0x10, 0x4, 0x1, 0x83, 0x4b, 0x1a, 0x87, 0x46, 0x2f, 0x2, 0x1, 0x3, + 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x1, 0x1, 0x2, 0x1, 0x8, 0x3, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x1, + 0xc, 0x26, 0x1, 0x1, 0x5, 0x1, 0xa0, 0x79, 0x13, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x13, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x80, 0x8b, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0xa, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, + 0xd, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4e, 0x3, 0xa0, 0x53, + 0x5, 0x7, 0xc, 0x5, 0x84, 0x29, 0x1a, 0x84, 0xcd, 0x28, 0xa0, 0xcf, 0xca, + 0x1a, 0x1a, 0x7, 0x1, 0x12, 0x1a, 0x1a, 0x1a, 0x4, 0x1, 0x1, 0x1, 0x7, 0x1, + 0xb, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1c, 0x1c, 0x19, 0x1, 0x6, 0x1a, 0x19, + 0x1, 0x6, 0x1a, 0x19, 0x1, 0x6, 0x1a, 0x19, 0x1, 0x6, 0x1a, 0x19, 0x1, 0x6, 0x1, + 0x1]; +_T Zl = [0xa0, 0x20, 0x28, 0x1]; +_T Zp = [0xa0, 0x20, 0x29, 0x1]; +_T Radical = [0xa0, 0x2e, 0x80, 0x1a, 0x1, 0x59, 0xc, 0x80, 0xd6]; +_T Extender = [ + 0x80, 0xb7, 0x1, 0x82, 0x18, 0x2, 0x83, 0x6e, 0x1, 0x81, 0xb9, 0x1, 0x86, + 0x4b, 0x1, 0x7f, 0x1, 0x89, 0x43, 0x1, 0x38, 0x1, 0x82, 0x63, 0x1, 0x81, + 0x8e, 0x1, 0x44, 0x1, 0x93, 0x89, 0x1, 0x2b, 0x5, 0x67, 0x2, 0x5d, 0x3, + 0xa0, 0x6f, 0x16, 0x1, 0x85, 0xf6, 0x1, 0x83, 0xc2, 0x1, 0x80, 0xa0, 0x1, + 0x6c, 0x1, 0x15, 0x2, 0xa0, 0x54, 0x7b, 0x1 +]; +_T Co = [0xa0, 0xe0, 0x0, 0x99, 0x0, 0xae, 0x7, 0x0, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, + 0xfe]; +_T Unified_Ideograph = [ + 0xa0, 0x34, 0x0, 0x99, 0xb6, 0x4a, 0xa0, 0x51, 0xcd, 0xa0, 0x5a, 0x41, + 0x2, 0x1, 0x1, 0x1, 0x2, 0xa, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x3, 0xa1, 0x5, + 0xd6, 0xa0, 0xa6, 0xd7, 0x29, 0x90, 0x35, 0xb, 0x80, 0xde +]; +_T Pc = [0x5f, 0x1, 0x9f, 0xdf, 0x2, 0x13, 0x1, 0xa0, 0xdd, 0xde, 0x2, 0x18, 0x3, 0x80, + 0xef, 0x1]; +_T Cs = [0xa0, 0xd8, 0x0, 0x88, 0x0]; +_T Noncharacter_Code_Point = [ + 0xa0, 0xfd, 0xd0, 0x20, 0x82, 0xe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, + 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, + 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, + 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, + 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, + 0xfe, 0x2, 0xa0, 0xff, 0xfe +]; +_T Uppercase = [0x41, 0x1a, 0x65, 0x17, 0x1, 0x7, 0x21, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x3, + 0x2, 0x1, 0x1, 0x1, 0x2, 0x1, 0x3, 0x2, 0x4, 0x1, 0x2, 0x1, 0x3, 0x3, 0x2, + 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, + 0x3, 0x1, 0x1, 0x1, 0x2, 0x3, 0x1, 0x7, 0x1, 0x2, 0x1, 0x2, 0x1, 0x2, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x7, 0x2, 0x1, 0x2, 0x2, 0x1, 0x1, 0x4, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x81, 0x21, 0x1, 0x1, 0x1, 0x3, 0x1, + 0xf, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x2, 0x1, 0x11, 0x1, 0x9, 0x23, 0x1, + 0x2, 0x3, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x5, 0x1, 0x2, + 0x1, 0x1, 0x2, 0x2, 0x33, 0x30, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x9, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0xa, 0x26, 0x8b, 0x49, 0x26, 0x1, 0x1, 0x5, 0x1, 0x8d, 0x32, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x9, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x9, 0x8, 0x8, 0x6, 0xa, 0x8, 0x8, 0x8, + 0x8, 0x6, 0xb, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x8, 0x8, 0x48, 0x4, + 0xc, 0x4, 0xc, 0x4, 0xc, 0x5, 0xb, 0x4, 0x81, 0x6, 0x1, 0x4, 0x1, 0x3, + 0x3, 0x2, 0x3, 0x2, 0x1, 0x3, 0x5, 0x6, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, + 0x2, 0x4, 0xa, 0x2, 0x5, 0x1, 0x1a, 0x10, 0x13, 0x1, 0x83, 0x32, 0x1a, + 0x87, 0x30, 0x2f, 0x31, 0x1, 0x1, 0x3, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x4, 0x1, 0x1, 0x2, 0x1, 0x8, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x8, 0x1, 0x1, 0x1, 0x4, 0x1, 0xa0, 0x79, 0x4d, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x13, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x80, 0x8b, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0xa, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, + 0xd, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0xa0, 0x57, + 0x76, 0x1a, 0x84, 0xc5, 0x28, 0xa0, 0xcf, 0xd8, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1, 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0x8, 0x1a, + 0x1a, 0x1a, 0x2, 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, 0x1b, 0x2, 0x1, 0x4, 0x1, + 0x5, 0x1, 0x1, 0x3, 0x7, 0x1b, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1e, 0x19, 0x21, 0x19, 0x21, 0x19, 0x21, 0x19, 0x21, 0x19, + 0x21, 0x1]; +_T IDS_Trinary_Operator = [0xa0, 0x2f, 0xf2, 0x2]; +_T Logical_Order_Exception = [0x8e, 0x40, 0x5, 0x7b, 0x5, 0xa0, 0x9b, 0xf0, + 0x2, 0x2, 0x1, 0x1, 0x2]; +_T Pi = [0x80, 0xab, 0x1, 0x9f, 0x6c, 0x1, 0x2, 0x2, 0x2, 0x1, 0x19, 0x1, + 0x8d, 0xc8, 0x1, 0x1, 0x1, 0x4, 0x1, 0x2, 0x1, 0xf, 0x1, 0x3, 0x1]; +_T Soft_Dotted = [ + 0x69, 0x2, 0x80, 0xc4, 0x1, 0x81, 0x19, 0x1, 0x1e, 0x1, 0x34, 0x1, 0x14, + 0x1, 0x81, 0x40, 0x1, 0x62, 0x1, 0x1, 0x1, 0x99, 0x9, 0x1, 0x33, 0x1, + 0xd, 0x1, 0x3, 0x1, 0x80, 0x84, 0x1, 0x80, 0x9d, 0x1, 0x81, 0xa5, 0x1, + 0x80, 0xd6, 0x2, 0x8b, 0x32, 0x1, 0xa1, 0xa7, 0xa5, 0x2, 0x32, 0x2, 0x32, + 0x2, 0x32, 0x2, 0x32, 0x2, 0x32, 0x2, 0x32, 0x2, 0x32, 0x2, 0x32, 0x2, + 0x32, 0x2, 0x32, 0x2, 0x32, 0x2, 0x32, 0x2 +]; +_T Po = [0x21, 0x3, 0x1, 0x3, 0x2, 0x1, 0x1, 0x1, 0x1, 0x2, 0xa, 0x2, 0x3, 0x2, + 0x1b, 0x1, 0x44, 0x1, 0x5, 0x1, 0xe, 0x2, 0x7, 0x1, 0x82, 0xbe, 0x1, 0x8, + 0x1, 0x81, 0xd2, 0x6, 0x29, 0x1, 0x36, 0x1, 0x2, 0x1, 0x2, 0x1, 0x2c, + 0x2, 0x14, 0x2, 0x1, 0x2, 0xd, 0x1, 0x2, 0x2, 0x4a, 0x4, 0x66, 0x1, 0x2b, + 0xe, 0x80, 0xe9, 0x3, 0x36, 0xf, 0x1f, 0x1, 0x81, 0x5, 0x2, 0xa, 0x1, 0x81, + 0x7f, 0x1, 0x83, 0x3, 0x1, 0x5a, 0x1, 0xa, 0x2, 0x80, 0xa8, 0xf, 0x1, + 0x1, 0x70, 0x1, 0x4a, 0x5, 0x4, 0x2, 0x6f, 0x6, 0x80, 0xab, 0x1, 0x82, + 0x64, 0x9, 0x83, 0x4, 0x2, 0x7c, 0x3, 0x47, 0x2, 0x80, 0x9d, 0x3, 0x1, + 0x3, 0x25, 0x6, 0x1, 0x4, 0x81, 0x39, 0x2, 0x80, 0xd8, 0x2, 0x80, 0x80, + 0x7, 0x1, 0x6, 0x80, 0xac, 0x7, 0x80, 0x9b, 0x4, 0x3b, 0x5, 0x3e, 0x2, + 0x40, 0x8, 0xb, 0x1, 0x83, 0x42, 0x2, 0x8, 0x8, 0x8, 0x9, 0x2, 0x4, 0x2, + 0x3, 0x3, 0xb, 0x1, 0x1, 0x1, 0xa, 0x8c, 0x9a, 0x4, 0x1, 0x2, 0x70, 0x1, + 0x80, 0x8f, 0x2, 0x4, 0x3, 0x2, 0x1, 0x2, 0x9, 0x1, 0x2, 0x1, 0x1, 0x2, + 0x2, 0xa, 0x5, 0x1, 0xa, 0x81, 0xc7, 0x3, 0x39, 0x1, 0x80, 0xbd, 0x1, 0xa0, + 0x74, 0x2, 0x2, 0x81, 0xd, 0x3, 0x63, 0x1, 0xa, 0x1, 0x73, 0x6, 0x81, 0x7c, + 0x4, 0x56, 0x2, 0x28, 0x3, 0x33, 0x2, 0x2f, 0x1, 0x61, 0xd, 0x10, 0x2, + 0x7c, 0x4, 0x7e, 0x2, 0x10, 0x2, 0x80, 0xf9, 0x1, 0xa0, 0x52, 0x24, 0x7, + 0x2, 0x1, 0x16, 0x1, 0x14, 0x2, 0x2, 0x4, 0x3, 0x3, 0x1, 0x4, 0x7, 0x3, + 0x6, 0x1, 0x1, 0x2, 0x80, 0x95, 0x3, 0x1, 0x3, 0x2, 0x1, 0x1, 0x1, 0x1, + 0x2, 0xa, 0x2, 0x3, 0x2, 0x1b, 0x1, 0x24, 0x1, 0x2, 0x2, 0x81, 0x9a, 0x3, + 0x82, 0x9c, 0x1, 0x30, 0x1, 0x84, 0x86, 0x1, 0x80, 0xc7, 0x1, 0x1f, 0x1, + 0x81, 0x10, 0x9, 0x26, 0x1, 0x80, 0xb9, 0x7, 0x85, 0x7, 0x7, 0x6d, 0x2, + 0x1, 0x4, 0x7e, 0x4, 0x80, 0x81, 0x4, 0x92, 0xa7, 0x4]; +_T Cn = [0x83, 0x78, 0x2, 0x5, 0x5, 0x7, 0x1, 0x1, 0x1, 0x14, 0x1, 0x81, + 0x85, 0x9, 0x26, 0x2, 0x7, 0x1, 0x27, 0x1, 0x2, 0x4, 0x1, 0x1, 0x37, 0x8, + 0x1b, 0x5, 0x5, 0xb, 0x5, 0x1, 0x17, 0x1, 0x80, 0xf0, 0x1, 0x3c, 0x2, 0x65, + 0xe, 0x3b, 0x5, 0x2e, 0x2, 0xf, 0x1, 0x1c, 0x2, 0x1, 0x41, 0x1, 0x1, 0xb, + 0x37, 0x1b, 0x1, 0x78, 0x1, 0x7, 0x1, 0x3, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, + 0x1, 0x7, 0x1, 0x1, 0x3, 0x4, 0x2, 0x9, 0x2, 0x2, 0x2, 0x4, 0x8, 0x1, 0x4, + 0x2, 0x1, 0x5, 0x2, 0x16, 0x5, 0x3, 0x1, 0x6, 0x4, 0x2, 0x2, 0x16, 0x1, + 0x7, 0x1, 0x2, 0x1, 0x2, 0x1, 0x2, 0x2, 0x1, 0x1, 0x5, 0x4, 0x2, 0x2, 0x3, + 0x3, 0x1, 0x7, 0x4, 0x1, 0x1, 0x7, 0x10, 0xb, 0x3, 0x1, 0x9, 0x1, 0x3, + 0x1, 0x16, 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x2, 0xa, 0x1, 0x3, 0x1, 0x3, + 0x2, 0x1, 0xf, 0x4, 0x2, 0xc, 0xf, 0x3, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, + 0x1, 0x7, 0x1, 0x2, 0x1, 0x5, 0x2, 0x9, 0x2, 0x2, 0x2, 0x3, 0x8, 0x2, 0x4, + 0x2, 0x1, 0x5, 0x2, 0x12, 0xa, 0x2, 0x1, 0x6, 0x3, 0x3, 0x1, 0x4, 0x3, + 0x2, 0x1, 0x1, 0x1, 0x2, 0x3, 0x2, 0x3, 0x3, 0x3, 0xc, 0x4, 0x5, 0x3, 0x3, + 0x1, 0x4, 0x2, 0x1, 0x6, 0x1, 0xe, 0x15, 0x6, 0x3, 0x1, 0x8, 0x1, 0x3, + 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x3, 0x8, 0x1, 0x3, 0x1, 0x4, 0x7, 0x2, + 0x1, 0x2, 0x6, 0x4, 0x2, 0xa, 0x8, 0x8, 0x2, 0x2, 0x1, 0x8, 0x1, 0x3, + 0x1, 0x17, 0x1, 0xa, 0x1, 0x5, 0x2, 0x9, 0x1, 0x3, 0x1, 0x4, 0x7, 0x2, + 0x7, 0x1, 0x1, 0x4, 0x2, 0xa, 0x1, 0x2, 0xf, 0x2, 0x1, 0x8, 0x1, 0x3, + 0x1, 0x29, 0x2, 0x8, 0x1, 0x3, 0x1, 0x5, 0x8, 0x1, 0x8, 0x4, 0x2, 0x10, + 0x3, 0x7, 0x2, 0x2, 0x1, 0x12, 0x3, 0x18, 0x1, 0x9, 0x1, 0x1, 0x2, 0x7, + 0x3, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x8, 0x12, 0x3, 0xc, 0x3a, 0x4, 0x1d, + 0x25, 0x2, 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x2, 0x1, 0x6, 0x4, 0x1, 0x7, + 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0xd, 0x1, 0x3, 0x2, 0x5, 0x1, + 0x1, 0x1, 0x6, 0x2, 0xa, 0x2, 0x4, 0x20, 0x48, 0x1, 0x24, 0x4, 0x27, 0x1, + 0x24, 0x1, 0xf, 0x1, 0xd, 0x25, 0x80, 0xc6, 0x1, 0x1, 0x5, 0x1, 0x2, + 0x81, 0x79, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0x29, 0x1, 0x4, + 0x2, 0x21, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, 0xf, 0x1, 0x39, + 0x1, 0x4, 0x2, 0x43, 0x2, 0x20, 0x3, 0x1a, 0x6, 0x55, 0xb, 0x82, 0x9d, + 0x3, 0x51, 0xf, 0xd, 0x1, 0x7, 0xb, 0x17, 0x9, 0x14, 0xc, 0xd, 0x1, 0x3, + 0x1, 0x2, 0xc, 0x5e, 0x2, 0xa, 0x6, 0xa, 0x6, 0xf, 0x1, 0xa, 0x6, 0x58, + 0x8, 0x2b, 0x5, 0x46, 0xa, 0x1d, 0x3, 0xc, 0x4, 0xc, 0x4, 0x1, 0x3, 0x2a, + 0x2, 0x5, 0xb, 0x2c, 0x4, 0x1a, 0x6, 0xb, 0x3, 0x3e, 0x2, 0x41, 0x1, 0x1d, + 0x2, 0xb, 0x6, 0xa, 0x6, 0xe, 0x52, 0x4c, 0x4, 0x2d, 0x3, 0x74, 0x8, + 0x3c, 0x3, 0xf, 0x3, 0x33, 0x40, 0x8, 0x8, 0x27, 0x9, 0x80, 0xe7, 0x15, + 0x81, 0x1a, 0x2, 0x6, 0x2, 0x26, 0x2, 0x6, 0x2, 0x8, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1f, 0x2, 0x35, 0x1, 0xf, 0x1, 0xe, 0x2, 0x6, 0x1, 0x13, + 0x2, 0x3, 0x1, 0x9, 0x1, 0x65, 0x1, 0xc, 0x2, 0x1b, 0x1, 0xd, 0x3, 0x1b, + 0x15, 0x21, 0xf, 0x80, 0x8a, 0x6, 0x82, 0x64, 0xc, 0x27, 0x19, 0xb, 0x15, + 0x82, 0xa0, 0x1, 0x84, 0x4c, 0x3, 0xa, 0x80, 0xa6, 0x2f, 0x1, 0x2f, 0x1, + 0x80, 0x94, 0x5, 0x2d, 0x1, 0x1, 0x5, 0x1, 0x2, 0x38, 0x7, 0x2, 0xe, 0x18, + 0x9, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, + 0x1, 0x7, 0x1, 0x5c, 0x44, 0x1a, 0x1, 0x59, 0xc, 0x80, 0xd6, 0x1a, 0xc, + 0x4, 0x40, 0x1, 0x56, 0x2, 0x67, 0x5, 0x29, 0x3, 0x5e, 0x1, 0x2b, 0x5, + 0x24, 0xc, 0x2f, 0x1, 0x80, 0xdf, 0x1, 0x9a, 0xb6, 0xa, 0xa0, 0x52, 0xd, + 0x33, 0x84, 0x8d, 0x3, 0x37, 0x9, 0x81, 0x5c, 0x14, 0x58, 0x7, 0x59, 0x8, + 0x80, 0x8f, 0x1, 0x4, 0xc, 0xb, 0x4d, 0x34, 0x4, 0xa, 0x6, 0x38, 0x8, + 0x45, 0x9, 0xc, 0x6, 0x1c, 0x4, 0x54, 0xb, 0x1e, 0x3, 0x4e, 0x1, 0xb, + 0x4, 0x2, 0x20, 0x37, 0x9, 0xe, 0x2, 0xa, 0x2, 0x20, 0x4, 0x43, 0x18, + 0x1c, 0xa, 0x6, 0x2, 0x6, 0x2, 0x6, 0x9, 0x7, 0x1, 0x7, 0x80, 0x91, 0x2e, + 0x2, 0xa, 0x6, 0xa0, 0x2b, 0xa4, 0xc, 0x17, 0x4, 0x31, 0x4, 0xa0, 0x22, + 0x6e, 0x2, 0x6a, 0x26, 0x7, 0xc, 0x5, 0x5, 0x1a, 0x1, 0x5, 0x1, 0x1, 0x1, + 0x2, 0x1, 0x2, 0x1, 0x7c, 0x11, 0x81, 0x6d, 0x10, 0x40, 0x2, 0x36, 0x28, + 0xe, 0x2, 0x1a, 0x6, 0x7, 0x9, 0x23, 0x1, 0x13, 0x1, 0x4, 0x4, 0x5, 0x1, + 0x80, 0x87, 0x2, 0x1, 0x1, 0x80, 0xbe, 0x3, 0x6, 0x2, 0x6, 0x2, 0x6, 0x2, + 0x3, 0x3, 0x7, 0x1, 0x7, 0xa, 0x5, 0x2, 0xc, 0x1, 0x1a, 0x1, 0x13, 0x1, + 0x2, 0x1, 0xf, 0x2, 0xe, 0x22, 0x7b, 0x5, 0x3, 0x4, 0x2d, 0x3, 0x54, 0x5, + 0xc, 0x34, 0x2e, 0x80, 0x82, 0x1d, 0x3, 0x31, 0x2f, 0x1f, 0x1, 0x4, 0xc, + 0x1b, 0x35, 0x1e, 0x1, 0x25, 0x4, 0xe, 0x2a, 0x80, 0x9e, 0x2, 0xa, 0x83, + 0x56, 0x6, 0x2, 0x1, 0x1, 0x2c, 0x1, 0x2, 0x3, 0x1, 0x2, 0x17, 0x1, 0x9, + 0x80, 0xa0, 0x1c, 0x3, 0x1b, 0x5, 0x1, 0x40, 0x38, 0x6, 0x2, 0x40, 0x4, + 0x1, 0x2, 0x5, 0x8, 0x1, 0x3, 0x1, 0x1b, 0x4, 0x3, 0x4, 0x9, 0x8, 0x9, 0x7, + 0x20, 0x80, 0x80, 0x36, 0x3, 0x1d, 0x2, 0x1b, 0x5, 0x8, 0x80, 0x80, 0x49, + 0x82, 0x17, 0x1f, 0x81, 0x81, 0x4e, 0x4, 0x1e, 0x10, 0x42, 0xe, 0x19, + 0x7, 0xa, 0x6, 0x35, 0x1, 0xe, 0x3c, 0x49, 0x7, 0xa, 0x84, 0xa6, 0x38, 0x8, + 0xa, 0x89, 0x36, 0x83, 0x6f, 0x80, 0x91, 0x63, 0xd, 0x4, 0x8b, 0x8c, 0x84, + 0x2f, 0xa0, 0x33, 0xd1, 0x82, 0x39, 0x84, 0xc7, 0x45, 0xb, 0x2f, 0x10, + 0x11, 0xa0, 0x40, 0x60, 0x2, 0x9f, 0xfe, 0x80, 0xf6, 0xa, 0x27, 0x2, 0x80, + 0xb5, 0x22, 0x46, 0x80, 0xba, 0x57, 0x9, 0x12, 0x80, 0x8e, 0x55, 0x1, + 0x47, 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0xc, 0x1, 0x1, 0x1, + 0x7, 0x1, 0x41, 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, 0x1, 0x1c, 0x1, 0x4, 0x1, + 0x5, 0x1, 0x1, 0x3, 0x7, 0x1, 0x81, 0x54, 0x2, 0x81, 0x24, 0x2, 0x32, + 0x96, 0x0, 0x4, 0x1, 0x1b, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0xa, 0x1, + 0x4, 0x1, 0x1, 0x1, 0x1, 0x6, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, + 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x2, 0x1, 0x1, 0x2, 0x4, 0x1, 0x7, 0x1, 0x4, 0x1, 0x4, 0x1, 0x1, 0x1, + 0xa, 0x1, 0x11, 0x5, 0x3, 0x1, 0x5, 0x1, 0x11, 0x34, 0x2, 0x81, 0xe, + 0x2c, 0x4, 0x64, 0xc, 0xf, 0x2, 0xe, 0x2, 0xf, 0x1, 0xf, 0x20, 0xb, 0x5, + 0x1f, 0x1, 0x3c, 0x4, 0x2b, 0x4b, 0x1d, 0xd, 0x2b, 0x5, 0x9, 0x7, 0x2, + 0x80, 0xae, 0x21, 0xf, 0x6, 0x1, 0x46, 0x3, 0x14, 0xc, 0x25, 0x1, 0x5, + 0x15, 0x11, 0xf, 0x3f, 0x1, 0x1, 0x1, 0x80, 0xb6, 0x1, 0x4, 0x3, 0x3e, 0x2, + 0x4, 0xc, 0x18, 0x80, 0x93, 0x46, 0x4, 0xb, 0x30, 0x46, 0x3a, 0x74, 0x88, + 0x8c, 0xa0, 0xa6, 0xd7, 0x29, 0x90, 0x35, 0xb, 0x80, 0xde, 0xa0, 0x3f, + 0xe2, 0x82, 0x1e, 0xab, 0x5, 0xe3, 0x1, 0x1e, 0x60, 0x80, 0x80, 0x80, 0xf0, + 0xa0, 0xfe, 0x10, 0xa0, 0xff, 0xfe, 0x2, 0xa0, 0xff, 0xfe]; +_T Ps = [0x28, 0x1, 0x32, 0x1, 0x1f, 0x1, 0x8e, 0xbe, 0x1, 0x1, 0x1, 0x87, + 0x5e, 0x1, 0x89, 0x7e, 0x1, 0x3, 0x1, 0x26, 0x1, 0x37, 0x1, 0xf, 0x1, + 0x82, 0x7a, 0x1, 0x1, 0x1, 0x1e, 0x1, 0x84, 0x3e, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x50, 0x1, 0x20, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x81, 0x94, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x40, 0x1, 0x1, 0x1, 0x21, 0x1, 0x84, 0x25, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x81, 0xdf, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0xa0, 0xcd, 0x20, 0x1, 0x80, + 0xd8, 0x1, 0x1d, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x11, 0x1, 0x1, 0x1, 0x1, 0x1, 0x80, 0xaa, + 0x1, 0x32, 0x1, 0x1f, 0x1, 0x3, 0x1, 0x2, 0x1]; +_T ASCII_Hex_Digit = [0x30, 0xa, 0x7, 0x6, 0x1a, 0x6]; +_T No = [0x80, 0xb2, 0x2, 0x5, 0x1, 0x2, 0x3, 0x89, 0x35, 0x6, 0x81, 0x78, 0x6, + 0x78, 0x3, 0x80, 0x85, 0x7, 0x80, 0xf1, 0x6, 0x81, 0xb4, 0xa, 0x84, 0x35, + 0x14, 0x84, 0x73, 0xa, 0x81, 0xe0, 0x1, 0x86, 0x95, 0x1, 0x3, 0x6, 0x6, + 0xa, 0x80, 0xc6, 0x10, 0x29, 0x1, 0x82, 0xd6, 0x3c, 0x4e, 0x16, 0x82, 0x76, + 0x1e, 0x85, 0x69, 0x1, 0x84, 0x94, 0x4, 0x80, 0x8a, 0xa, 0x1e, 0x8, 0x1, + 0xf, 0x20, 0xa, 0x27, 0xf, 0xa0, 0x75, 0x70, 0x6, 0xa0, 0x58, 0xd1, 0x2d, + 0x41, 0x4, 0x11, 0x1, 0x81, 0x95, 0x4, 0x85, 0x34, 0x8, 0x80, 0xb6, 0x6, + 0x81, 0x24, 0x8, 0x35, 0x2, 0x80, 0xd9, 0x8, 0x18, 0x8, 0x82, 0xe0, 0x1f, + 0x81, 0xd3, 0x14, 0xa0, 0xc2, 0xfa, 0x12, 0x9d, 0x8e, 0xb]; +_T Sm = [0x2b, 0x1, 0x10, 0x3, 0x3d, 0x1, 0x1, 0x1, 0x2d, 0x1, 0x4, 0x1, + 0x25, 0x1, 0x1f, 0x1, 0x82, 0xfe, 0x1, 0x82, 0xf, 0x3, 0x9a, 0x3b, 0x1, + 0xd, 0x1, 0x27, 0x3, 0xd, 0x3, 0x80, 0x8b, 0x1, 0x27, 0x5, 0x6, 0x1, 0x44, + 0x5, 0x5, 0x2, 0x4, 0x1, 0x2, 0x1, 0x2, 0x1, 0x7, 0x1, 0x1f, 0x2, 0x2, 0x1, + 0x1, 0x1, 0x1f, 0x81, 0xc, 0x20, 0x2, 0x5a, 0x1, 0x1e, 0x19, 0x28, 0x6, + 0x81, 0xd5, 0x1, 0x9, 0x1, 0x36, 0x8, 0x6f, 0x1, 0x81, 0x50, 0x5, 0x2, + 0x1f, 0xa, 0x10, 0x81, 0x0, 0x80, 0x83, 0x16, 0x3f, 0x4, 0x20, 0x2, 0x81, + 0x2, 0x30, 0x15, 0x2, 0x6, 0xa0, 0xcf, 0xdc, 0x1, 0x83, 0x38, 0x1, 0x1, + 0x3, 0x80, 0xa4, 0x1, 0x10, 0x3, 0x3d, 0x1, 0x1, 0x1, 0x80, 0x83, 0x1, 0x6, + 0x4, 0xa0, 0xd6, 0xd4, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, + 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x97, 0x2c, 0x2]; +_T Other_Math = [ + 0x5e, 0x1, 0x83, 0x71, 0x3, 0x2, 0x1, 0x1a, 0x2, 0x2, 0x2, 0x9c, 0x20, + 0x1, 0x1b, 0x3, 0xb, 0x1, 0x20, 0x4, 0x18, 0x2, 0xe, 0x2, 0x41, 0xd, 0x4, + 0x1, 0x3, 0x2, 0x4, 0x5, 0x12, 0x1, 0x4, 0x1, 0x2, 0xa, 0x1, 0x1, 0x3, + 0x5, 0x6, 0x1, 0x3, 0x2, 0x2, 0x2, 0x1, 0x3, 0x1, 0x6, 0x3, 0x4, 0x5, + 0x5, 0x4b, 0x5, 0x2, 0x4, 0x1, 0x2, 0x1, 0x2, 0x1, 0x1, 0x1, 0x5, 0x2, + 0x2, 0x4, 0x2, 0x4, 0x12, 0x2, 0x2, 0x1, 0x1, 0x1, 0x7, 0x1, 0x1, 0x6, 0x2, + 0x81, 0x22, 0x4, 0x80, 0xa8, 0x2, 0x1, 0x1, 0x18, 0x1, 0x11, 0x1, 0x81, + 0xbd, 0x2, 0xc, 0x9, 0x5, 0x5, 0x5, 0x2, 0x2, 0x2, 0x3, 0x5, 0xe, 0x1, + 0x1, 0x1, 0x2, 0x6, 0x18, 0x2, 0x39, 0x1, 0x1, 0x1, 0x1d, 0x4, 0x9, 0x2, + 0x81, 0x56, 0x2, 0x1f, 0xa, 0x81, 0x93, 0x16, 0x3f, 0x4, 0x20, 0x2, 0xa0, + 0xd4, 0x63, 0x1, 0x1, 0x1, 0x4, 0x1, 0x80, 0xd3, 0x1, 0x1, 0x1, 0xa0, 0xd4, + 0xc1, 0x55, 0x1, 0x47, 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, 0x1, 0xc, + 0x1, 0x1, 0x1, 0x7, 0x1, 0x41, 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, 0x1, 0x1c, + 0x1, 0x4, 0x1, 0x5, 0x1, 0x1, 0x3, 0x7, 0x1, 0x81, 0x54, 0x2, 0x19, 0x1, + 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, 0x1, 0x1f, 0x1, 0x19, + 0x1, 0x1f, 0x1, 0x19, 0x1, 0x8, 0x2, 0x32, 0x96, 0x0, 0x4, 0x1, 0x1b, 0x1, + 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0xa, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x6, 0x1, + 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x4, 0x1, 0x7, + 0x1, 0x4, 0x1, 0x4, 0x1, 0x1, 0x1, 0xa, 0x1, 0x11, 0x5, 0x3, 0x1, 0x5, 0x1, 0x11 +]; +_T Join_Control = [0xa0, 0x20, 0xc, 0x2]; +_T Cf = [0x80, 0xad, 0x1, 0x85, 0x52, 0x5, 0x17, 0x1, 0x80, 0xc0, 0x1, 0x31, + 0x1, 0x90, 0xfe, 0x1, 0x87, 0xfc, 0x5, 0x1a, 0x5, 0x31, 0x5, 0x1, 0xa, + 0xa0, 0xde, 0x8f, 0x1, 0x80, 0xf9, 0x3, 0x90, 0xc1, 0x1, 0xa0, 0xc0, 0xb5, + 0x8, 0xac, 0x2e, 0x86, 0x1, 0x1e, 0x60]; +_T Ideographic = [ + 0xa0, 0x30, 0x6, 0x2, 0x19, 0x9, 0xe, 0x3, 0x83, 0xc5, 0x99, 0xb6, 0x4a, + 0xa0, 0x51, 0xcd, 0xa0, 0x59, 0x33, 0x81, 0x6e, 0x2, 0x6a, 0xa1, 0x5, 0x26, + 0xa0, 0xa6, 0xd7, 0x29, 0x90, 0x35, 0xb, 0x80, 0xde, 0xa0, 0x3f, 0xe2, 0x82, 0x1e +]; +_T Sc = [0x24, 0x1, 0x7d, 0x4, 0x84, 0xe9, 0x1, 0x7b, 0x1, 0x83, 0xe6, 0x2, + 0x7, 0x1, 0x80, 0xf5, 0x1, 0x81, 0x7, 0x1, 0x82, 0x45, 0x1, 0x89, 0x9b, + 0x1, 0x88, 0xc4, 0x1b, 0xa0, 0x87, 0x7d, 0x1, 0xa0, 0x55, 0xc3, 0x1, + 0x6c, 0x1, 0x80, 0x9a, 0x1, 0x80, 0xdb, 0x2, 0x3, 0x2]; +_T Nd = [0x30, 0xa, 0x86, 0x26, 0xa, 0x80, 0x86, 0xa, 0x80, 0xc6, 0xa, 0x81, + 0x9c, 0xa, 0x76, 0xa, 0x76, 0xa, 0x76, 0xa, 0x76, 0xa, 0x76, 0xa, 0x76, + 0xa, 0x76, 0xa, 0x76, 0xa, 0x80, 0xe0, 0xa, 0x76, 0xa, 0x46, 0xa, 0x81, + 0x16, 0xa, 0x46, 0xa, 0x87, 0x46, 0xa, 0x26, 0xa, 0x81, 0x2c, 0xa, 0x80, + 0x80, 0xa, 0x80, 0xa6, 0xa, 0x6, 0xa, 0x80, 0xb6, 0xa, 0x56, 0xa, 0x80, + 0x86, 0xa, 0x6, 0xa, 0xa0, 0x89, 0xc6, 0xa, 0x82, 0xa6, 0xa, 0x26, 0xa, + 0x80, 0xc6, 0xa, 0x76, 0xa, 0x81, 0x96, 0xa, 0xa0, 0x53, 0x16, 0xa, 0x85, + 0x86, 0xa, 0x8b, 0xbc, 0xa, 0x80, 0x80, 0xa, 0x3c, 0xa, 0x80, 0x90, 0xa, + 0x84, 0xe6, 0xa, 0xa0, 0xc1, 0x4, 0x32]; +_T Default_Ignorable_Code_Point = [ + 0x80, 0xad, 0x1, 0x82, 0xa1, 0x1, 0x82, 0xcc, 0x1, 0x8b, 0x42, 0x2, 0x86, + 0x53, 0x2, 0x55, 0x4, 0x87, 0xfc, 0x5, 0x1a, 0x5, 0x31, 0x10, 0x90, 0xf4, + 0x1, 0xa0, 0xcc, 0x9b, 0x10, 0x80, 0xef, 0x1, 0x80, 0xa0, 0x1, 0x4f, 0x9, + 0xa0, 0xd1, 0x7a, 0x8, 0xac, 0x2e, 0x85, 0x90, 0x0 +]; +_T Other_ID_Continue = [0x80, 0xb7, 0x1, 0x82, 0xcf, 0x1, 0x8f, 0xe1, 0x9, 0x86, 0x68, + 0x1]; +_T Pd = [0x2d, 0x1, 0x85, 0x5c, 0x1, 0x33, 0x1, 0x8e, 0x41, 0x1, 0x84, 0x5, + 0x1, 0x88, 0x9, 0x6, 0x8e, 0x1, 0x1, 0x2, 0x1, 0x1f, 0x2, 0x81, 0xe0, + 0x1, 0x13, 0x1, 0x6f, 0x1, 0xa0, 0xcd, 0x90, 0x2, 0x25, 0x1, 0xa, 0x1, 0x80, 0xa9, + 0x1]; +_T Deprecated = [ + 0x81, 0x49, 0x1, 0x85, 0x29, 0x1, 0x89, 0x3, 0x1, 0x1, 0x1, 0x88, 0x29, + 0x2, 0x88, 0xc5, 0x6, 0x82, 0xb9, 0x2, 0xad, 0xdc, 0xd6, 0x1, 0x1e, 0x60 +]; +_T Grapheme_Extend = [ + 0x83, 0x0, 0x70, 0x81, 0x13, 0x7, 0x81, 0x7, 0x2d, 0x1, 0x1, 0x1, 0x2, + 0x1, 0x2, 0x1, 0x1, 0x48, 0xb, 0x30, 0x15, 0x10, 0x1, 0x65, 0x7, 0x2, 0x6, + 0x2, 0x2, 0x1, 0x4, 0x23, 0x1, 0x1e, 0x1b, 0x5b, 0xb, 0x3a, 0x9, 0x22, + 0x4, 0x1, 0x9, 0x1, 0x3, 0x1, 0x5, 0x2b, 0x3, 0x80, 0x88, 0x1b, 0x1, 0x3, + 0x37, 0x1, 0x1, 0x1, 0x4, 0x8, 0x4, 0x1, 0x3, 0x7, 0xa, 0x2, 0x1d, 0x1, + 0x3a, 0x1, 0x1, 0x1, 0x2, 0x4, 0x8, 0x1, 0x9, 0x1, 0xa, 0x2, 0x1d, 0x2, + 0x39, 0x1, 0x4, 0x2, 0x4, 0x2, 0x2, 0x3, 0x3, 0x1, 0x1e, 0x2, 0x3, 0x1, + 0xb, 0x2, 0x39, 0x1, 0x4, 0x5, 0x1, 0x2, 0x4, 0x1, 0x14, 0x2, 0x1d, 0x1, + 0x3a, 0x1, 0x1, 0x2, 0x1, 0x4, 0x8, 0x1, 0x8, 0x2, 0xa, 0x2, 0x1e, 0x1, + 0x3b, 0x1, 0x1, 0x1, 0xc, 0x1, 0x9, 0x1, 0x66, 0x3, 0x5, 0x3, 0x1, 0x4, + 0x7, 0x2, 0xb, 0x2, 0x58, 0x1, 0x2, 0x1, 0x2, 0x1, 0x3, 0x1, 0x5, 0x2, + 0x7, 0x2, 0xb, 0x2, 0x5a, 0x1, 0x2, 0x4, 0x8, 0x1, 0x9, 0x1, 0xa, 0x2, + 0x66, 0x1, 0x4, 0x1, 0x2, 0x3, 0x1, 0x1, 0x8, 0x1, 0x51, 0x1, 0x2, 0x7, + 0xc, 0x8, 0x62, 0x1, 0x2, 0x6, 0x1, 0x2, 0xb, 0x6, 0x4a, 0x2, 0x1b, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x37, 0xe, 0x1, 0x5, 0x1, 0x2, 0x5, 0xb, 0x1, 0x24, + 0x9, 0x1, 0x66, 0x4, 0x1, 0x6, 0x1, 0x2, 0x2, 0x2, 0x19, 0x2, 0x4, 0x3, + 0x10, 0x4, 0xd, 0x1, 0x2, 0x2, 0x6, 0x1, 0xf, 0x1, 0x82, 0xbf, 0x3, 0x83, + 0xb2, 0x3, 0x1d, 0x3, 0x1d, 0x2, 0x1e, 0x2, 0x40, 0x2, 0x1, 0x7, 0x8, 0x1, + 0x2, 0xb, 0x9, 0x1, 0x2d, 0x3, 0x80, 0x9b, 0x1, 0x76, 0x3, 0x4, 0x2, 0x9, + 0x1, 0x6, 0x3, 0x80, 0xdb, 0x2, 0x2, 0x1, 0x3a, 0x1, 0x1, 0x7, 0x1, 0x1, + 0x1, 0x1, 0x2, 0x8, 0x6, 0xa, 0x2, 0x1, 0x80, 0x80, 0x4, 0x30, 0x1, 0x1, + 0x5, 0x1, 0x1, 0x5, 0x1, 0x28, 0x9, 0xc, 0x2, 0x20, 0x4, 0x2, 0x2, 0x1, + 0x1, 0x3a, 0x1, 0x1, 0x2, 0x3, 0x1, 0x1, 0x3, 0x3a, 0x8, 0x2, 0x2, 0x80, + 0x98, 0x3, 0x1, 0xd, 0x1, 0x7, 0x4, 0x1, 0x6, 0x1, 0x80, 0xcb, 0x27, + 0x15, 0x4, 0x82, 0xc, 0x2, 0x80, 0xc2, 0x21, 0x8b, 0xfe, 0x3, 0x80, 0x8d, + 0x1, 0x60, 0x20, 0x82, 0x2a, 0x6, 0x69, 0x2, 0xa0, 0x75, 0xd4, 0x4, 0x1, + 0xa, 0x21, 0x1, 0x50, 0x2, 0x81, 0x10, 0x1, 0x3, 0x1, 0x4, 0x1, 0x19, 0x2, + 0x80, 0x9d, 0x1, 0x1b, 0x12, 0x34, 0x8, 0x19, 0xb, 0x2e, 0x3, 0x30, 0x1, + 0x2, 0x4, 0x2, 0x1, 0x6c, 0x6, 0x2, 0x2, 0x2, 0x2, 0xc, 0x1, 0x8, 0x1, + 0x63, 0x1, 0x1, 0x3, 0x2, 0x2, 0x5, 0x2, 0x1, 0x1, 0x2a, 0x2, 0x8, 0x1, + 0x80, 0xee, 0x1, 0x2, 0x1, 0x4, 0x1, 0xa0, 0x4f, 0x30, 0x1, 0x82, 0xe1, + 0x10, 0x10, 0x7, 0x81, 0x77, 0x2, 0x82, 0x5d, 0x1, 0x88, 0x3, 0x3, 0x1, + 0x2, 0x5, 0x4, 0x28, 0x3, 0x4, 0x1, 0x85, 0xc1, 0x1, 0x36, 0xf, 0x39, + 0x2, 0x31, 0x4, 0x2, 0x2, 0x45, 0x3, 0x24, 0x5, 0x1, 0x8, 0x4b, 0x2, + 0x34, 0x9, 0x84, 0xec, 0x1, 0x1, 0x1, 0x2, 0x6, 0x1, 0x1, 0xa0, 0x58, 0xd7, + 0x4, 0xa0, 0x61, 0xd2, 0x1, 0x1, 0x3, 0x4, 0x5, 0x8, 0x8, 0x2, 0x7, 0x1e, + 0x4, 0x80, 0x94, 0x3, 0xac, 0x2e, 0xbb, 0x80, 0xf0 +]; +_T Hyphen = [0x2d, 0x1, 0x7f, 0x1, 0x84, 0xdc, 0x1, 0x92, 0x7b, 0x1, 0x88, 0x9, + 0x2, 0x8e, 0x5, 0x1, 0x82, 0xe3, 0x1, 0xa0, 0xcd, 0x67, 0x1, 0x80, 0xa9, 0x1, 0x57, + 0x1]; +_T Pe = [0x29, 0x1, 0x33, 0x1, 0x1f, 0x1, 0x8e, 0xbd, 0x1, 0x1, 0x1, 0x87, + 0x5e, 0x1, 0x89, 0xa9, 0x1, 0x37, 0x1, 0xf, 0x1, 0x82, 0x7a, 0x1, 0x1, 0x1, + 0x1e, 0x1, 0x84, 0x3e, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x50, 0x1, 0x20, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x81, 0x94, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x40, 0x1, 0x1, 0x1, + 0x21, 0x1, 0x84, 0x25, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x81, 0xdf, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x2, 0x2, 0xa0, 0xcd, 0x1f, 0x1, 0x80, 0xd8, 0x1, 0x1d, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x3, 0x1, 0x11, 0x1, 0x1, 0x1, 0x1, 0x1, 0x80, 0xaa, 0x1, 0x33, 0x1, 0x1f, + 0x1, 0x2, 0x1, 0x2, 0x1]; +_U[] _tab = [ +_U("Alphabetic", Alphabetic), +_U("ASCII_Hex_Digit", ASCII_Hex_Digit), +_U("Bidi_Control", Bidi_Control), +_U("Cased", Cased), +_U("Case_Ignorable", Case_Ignorable), +_U("Cc", Cc), +_U("Cf", Cf), +_U("Close_Punctuation", Pe), +_U("Cn", Cn), +_U("Co", Co), +_U("Connector_Punctuation", Pc), +_U("Control", Cc), +_U("Cs", Cs), +_U("Currency_Symbol", Sc), +_U("Dash", Dash), +_U("Dash_Punctuation", Pd), +_U("Decimal_Number", Nd), +_U("Default_Ignorable_Code_Point", Default_Ignorable_Code_Point), +_U("Deprecated", Deprecated), +_U("Diacritic", Diacritic), +_U("Enclosing_Mark", Me), +_U("Extender", Extender), +_U("Final_Punctuation", Pf), +_U("Format", Cf), +_U("Grapheme_Base", Grapheme_Base), +_U("Grapheme_Extend", Grapheme_Extend), +_U("Grapheme_Link", Grapheme_Link), +_U("Hex_Digit", Hex_Digit), +_U("Hyphen", Hyphen), +_U("ID_Continue", ID_Continue), +_U("Ideographic", Ideographic), +_U("IDS_Binary_Operator", IDS_Binary_Operator), +_U("ID_Start", ID_Start), +_U("IDS_Trinary_Operator", IDS_Trinary_Operator), +_U("Initial_Punctuation", Pi), +_U("Join_Control", Join_Control), +_U("Letter_Number", Nl), +_U("Line_Separator", Zl), +_U("Ll", Ll), +_U("Lm", Lm), +_U("Lo", Lo), +_U("Logical_Order_Exception", Logical_Order_Exception), +_U("Lowercase", Lowercase), +_U("Lowercase_Letter", Ll), +_U("Lt", Lt), +_U("Lu", Lu), +_U("Math", Math), +_U("Math_Symbol", Sm), +_U("Mc", Mc), +_U("Me", Me), +_U("Mn", Mn), +_U("Modifier_Letter", Lm), +_U("Modifier_Symbol", Sk), +_U("Nd", Nd), +_U("Nl", Nl), +_U("No", No), +_U("Noncharacter_Code_Point", Noncharacter_Code_Point), +_U("Nonspacing_Mark", Mn), +_U("Open_Punctuation", Ps), +_U("Other_Alphabetic", Other_Alphabetic), +_U("Other_Default_Ignorable_Code_Point", Other_Default_Ignorable_Code_Point), +_U("Other_Grapheme_Extend", Other_Grapheme_Extend), +_U("Other_ID_Continue", Other_ID_Continue), +_U("Other_ID_Start", Other_ID_Start), +_U("Other_Letter", Lo), +_U("Other_Lowercase", Other_Lowercase), +_U("Other_Math", Other_Math), +_U("Other_Number", No), +_U("Other_Punctuation", Po), +_U("Other_Symbol", So), +_U("Other_Uppercase", Other_Uppercase), +_U("Paragraph_Separator", Zp), +_U("Pattern_Syntax", Pattern_Syntax), +_U("Pattern_White_Space", Pattern_White_Space), +_U("Pc", Pc), +_U("Pd", Pd), +_U("Pe", Pe), +_U("Pf", Pf), +_U("Pi", Pi), +_U("Po", Po), +_U("Private_Use", Co), +_U("Ps", Ps), +_U("Quotation_Mark", Quotation_Mark), +_U("Radical", Radical), +_U("Sc", Sc), +_U("Sk", Sk), +_U("Sm", Sm), +_U("So", So), +_U("Soft_Dotted", Soft_Dotted), +_U("Space_Separator", Zs), +_U("Spacing_Mark", Mc), +_U("STerm", STerm), +_U("Surrogate", Cs), +_U("Terminal_Punctuation", Terminal_Punctuation), +_U("Titlecase_Letter", Lt), +_U("Unassigned", Cn), +_U("Unified_Ideograph", Unified_Ideograph), +_U("Uppercase", Uppercase), +_U("Uppercase_Letter", Lu), +_U("Variation_Selector", Variation_Selector), +_U("White_Space", White_Space), +_U("XID_Continue", XID_Continue), +_U("XID_Start", XID_Start), +_U("Zl", Zl), +_U("Zp", Zp), +_U("Zs", Zs), +]; +} + +struct blocks +{ +private alias _U = immutable(UnicodeProperty); +@property static _U[] tab() pure { return _tab; } +static immutable: +private alias _T = ubyte[]; +_T Number_Forms = [0xa0, 0x21, 0x50, 0x40]; +_T Sinhala = [0x8d, 0x80, 0x80, 0x80]; +_T Domino_Tiles = [0xa1, 0xf0, 0x30, 0x70]; +_T Oriya = [0x8b, 0x0, 0x80, 0x80]; +_T Thaana = [0x87, 0x80, 0x40]; +_T New_Tai_Lue = [0x99, 0x80, 0x60]; +_T Byzantine_Musical_Symbols = [0xa1, 0xd0, 0x0, 0x81, 0x0]; +_T Cham = [0xa0, 0xaa, 0x0, 0x60]; +_T IPA_Extensions = [0x82, 0x50, 0x60]; +_T Bopomofo = [0xa0, 0x31, 0x0, 0x30]; +_T Katakana_Phonetic_Extensions = [0xa0, 0x31, 0xf0, 0x10]; +_T Khmer_Symbols = [0x99, 0xe0, 0x20]; +_T Hebrew = [0x85, 0x90, 0x70]; +_T Saurashtra = [0xa0, 0xa8, 0x80, 0x60]; +_T Inscriptional_Parthian = [0xa1, 0xb, 0x40, 0x20]; +_T Lisu = [0xa0, 0xa4, 0xd0, 0x30]; +_T Latin_1_Supplement = [0x80, 0x80, 0x80, 0x80]; +_T Arabic_Extended_A = [0x88, 0xa0, 0x60]; +_T Tai_Tham = [0x9a, 0x20, 0x80, 0x90]; +_T Latin_Extended_A = [0x81, 0x0, 0x80, 0x80]; +_T Latin_Extended_B = [0x81, 0x80, 0x80, 0xd0]; +_T Latin_Extended_C = [0xa0, 0x2c, 0x60, 0x20]; +_T Latin_Extended_D = [0xa0, 0xa7, 0x20, 0x80, 0xe0]; +_T CJK_Radicals_Supplement = [0xa0, 0x2e, 0x80, 0x80, 0x80]; +_T Meroitic_Hieroglyphs = [0xa1, 0x9, 0x80, 0x20]; +_T Linear_B_Syllabary = [0xa1, 0x0, 0x0, 0x80, 0x80]; +_T Phonetic_Extensions_Supplement = [0x9d, 0x80, 0x40]; +_T Meroitic_Cursive = [0xa1, 0x9, 0xa0, 0x60]; +_T Enclosed_Ideographic_Supplement = [0xa1, 0xf2, 0x0, 0x81, 0x0]; +_T Halfwidth_and_Fullwidth_Forms = [0xa0, 0xff, 0x0, 0x80, 0xf0]; +_T Takri = [0xa1, 0x16, 0x80, 0x50]; +_T Supplemental_Punctuation = [0xa0, 0x2e, 0x0, 0x80, 0x80]; +_T Malayalam = [0x8d, 0x0, 0x80, 0x80]; +_T Lepcha = [0x9c, 0x0, 0x50]; +_T Miscellaneous_Symbols_And_Pictographs = [0xa1, 0xf3, 0x0, 0x83, 0x0]; +_T Arabic_Presentation_Forms_A = [0xa0, 0xfb, 0x50, 0x82, 0xb0]; +_T Sora_Sompeng = [0xa1, 0x10, 0xd0, 0x30]; +_T Lydian = [0xa1, 0x9, 0x20, 0x20]; +_T Hangul_Jamo_Extended_B = [0xa0, 0xd7, 0xb0, 0x50]; +_T Private_Use_Area = [0xa0, 0xe0, 0x0, 0x99, 0x0]; +_T Coptic = [0xa0, 0x2c, 0x80, 0x80, 0x80]; +_T Phaistos_Disc = [0xa1, 0x1, 0xd0, 0x30]; +_T Batak = [0x9b, 0xc0, 0x40]; +_T Khmer = [0x97, 0x80, 0x80, 0x80]; +_T Counting_Rod_Numerals = [0xa1, 0xd3, 0x60, 0x20]; +_T Old_South_Arabian = [0xa1, 0xa, 0x60, 0x20]; +_T Kannada = [0x8c, 0x80, 0x80, 0x80]; +_T Arrows = [0xa0, 0x21, 0x90, 0x70]; +_T CJK_Compatibility_Ideographs_Supplement = [0xa2, 0xf8, 0x0, 0x82, 0x20]; +_T Combining_Half_Marks = [0xa0, 0xfe, 0x20, 0x10]; +_T Miscellaneous_Technical = [0xa0, 0x23, 0x0, 0x81, 0x0]; +_T Thai = [0x8e, 0x0, 0x80, 0x80]; +_T Alphabetic_Presentation_Forms = [0xa0, 0xfb, 0x0, 0x50]; +_T CJK_Unified_Ideographs = [0xa0, 0x4e, 0x0, 0xa0, 0x52, 0x0]; +_T Phonetic_Extensions = [0x9d, 0x0, 0x80, 0x80]; +_T Kayah_Li = [0xa0, 0xa9, 0x0, 0x30]; +_T Supplementary_Private_Use_Area_B = [0xb0, 0x0, 0x0]; +_T Gujarati = [0x8a, 0x80, 0x80, 0x80]; +_T Unified_Canadian_Aboriginal_Syllabics_Extended = [0x98, 0xb0, 0x50]; +_T Hangul_Syllables = [0xa0, 0xac, 0x0, 0xa0, 0x2b, 0xb0]; +_T Vertical_Forms = [0xa0, 0xfe, 0x10, 0x10]; +_T Inscriptional_Pahlavi = [0xa1, 0xb, 0x60, 0x20]; +_T Control_Pictures = [0xa0, 0x24, 0x0, 0x40]; +_T Carian = [0xa1, 0x2, 0xa0, 0x40]; +_T Mahjong_Tiles = [0xa1, 0xf0, 0x0, 0x30]; +_T Geometric_Shapes = [0xa0, 0x25, 0xa0, 0x60]; +_T Cherokee = [0x93, 0xa0, 0x60]; +_T Imperial_Aramaic = [0xa1, 0x8, 0x40, 0x20]; +_T Rumi_Numeral_Symbols = [0xa1, 0xe, 0x60, 0x20]; +_T Combining_Diacritical_Marks = [0x83, 0x0, 0x70]; +_T Specials = [0xa0, 0xff, 0xf0, 0x10]; +_T Greek_Extended = [0x9f, 0x0, 0x81, 0x0]; +_T Ethiopic_Supplement = [0x93, 0x80, 0x20]; +_T Limbu = [0x99, 0x0, 0x50]; +_T Basic_Latin = [0x0, 0x80, 0x80]; +_T Enclosed_Alphanumeric_Supplement = [0xa1, 0xf1, 0x0, 0x81, 0x0]; +_T Cyrillic_Supplement = [0x85, 0x0, 0x30]; +_T Hangul_Compatibility_Jamo = [0xa0, 0x31, 0x30, 0x60]; +_T Supplemental_Arrows_A = [0xa0, 0x27, 0xf0, 0x10]; +_T Supplemental_Arrows_B = [0xa0, 0x29, 0x0, 0x80, 0x80]; +_T Katakana = [0xa0, 0x30, 0xa0, 0x60]; +_T Ancient_Greek_Musical_Notation = [0xa1, 0xd2, 0x0, 0x50]; +_T CJK_Compatibility = [0xa0, 0x33, 0x0, 0x81, 0x0]; +_T Old_Persian = [0xa1, 0x3, 0xa0, 0x40]; +_T Small_Form_Variants = [0xa0, 0xfe, 0x50, 0x20]; +_T General_Punctuation = [0xa0, 0x20, 0x0, 0x70]; +_T Miscellaneous_Mathematical_Symbols_A = [0xa0, 0x27, 0xc0, 0x30]; +_T Latin_Extended_Additional = [0x9e, 0x0, 0x81, 0x0]; +_T Playing_Cards = [0xa1, 0xf0, 0xa0, 0x60]; +_T Syriac = [0x87, 0x0, 0x50]; +_T Alchemical_Symbols = [0xa1, 0xf7, 0x0, 0x80, 0x80]; +_T Tibetan = [0x8f, 0x0, 0x81, 0x0]; +_T CJK_Strokes = [0xa0, 0x31, 0xc0, 0x30]; +_T Tamil = [0x8b, 0x80, 0x80, 0x80]; +_T Balinese = [0x9b, 0x0, 0x80, 0x80]; +_T Shavian = [0xa1, 0x4, 0x50, 0x30]; +_T Greek_and_Coptic = [0x83, 0x70, 0x80, 0x90]; +_T Telugu = [0x8c, 0x0, 0x80, 0x80]; +_T Runic = [0x96, 0xa0, 0x60]; +_T Javanese = [0xa0, 0xa9, 0x80, 0x60]; +_T Bopomofo_Extended = [0xa0, 0x31, 0xa0, 0x20]; +_T Ideographic_Description_Characters = [0xa0, 0x2f, 0xf0, 0x10]; +_T Old_Turkic = [0xa1, 0xc, 0x0, 0x50]; +_T Unified_Canadian_Aboriginal_Syllabics = [0x94, 0x0, 0x82, 0x80]; +_T Ugaritic = [0xa1, 0x3, 0x80, 0x20]; +_T Egyptian_Hieroglyphs = [0xa1, 0x30, 0x0, 0x84, 0x30]; +_T Buginese = [0x9a, 0x0, 0x20]; +_T Kangxi_Radicals = [0xa0, 0x2f, 0x0, 0x80, 0xe0]; +_T Cuneiform = [0xa1, 0x20, 0x0, 0x84, 0x0]; +_T NKo = [0x87, 0xc0, 0x40]; +_T Sundanese_Supplement = [0x9c, 0xc0, 0x10]; +_T Buhid = [0x97, 0x40, 0x20]; +_T Modifier_Tone_Letters = [0xa0, 0xa7, 0x0, 0x20]; +_T Kanbun = [0xa0, 0x31, 0x90, 0x10]; +_T Superscripts_and_Subscripts = [0xa0, 0x20, 0x70, 0x30]; +_T Lao = [0x8e, 0x80, 0x80, 0x80]; +_T Ol_Chiki = [0x9c, 0x50, 0x30]; +_T Common_Indic_Number_Forms = [0xa0, 0xa8, 0x30, 0x10]; +_T Hangul_Jamo_Extended_A = [0xa0, 0xa9, 0x60, 0x20]; +_T Arabic_Presentation_Forms_B = [0xa0, 0xfe, 0x70, 0x80, 0x90]; +_T Sharada = [0xa1, 0x11, 0x80, 0x60]; +_T Miscellaneous_Symbols = [0xa0, 0x26, 0x0, 0x81, 0x0]; +_T Variation_Selectors_Supplement = [0xae, 0x1, 0x0, 0x80, 0xf0]; +_T Rejang = [0xa0, 0xa9, 0x30, 0x30]; +_T Georgian_Supplement = [0xa0, 0x2d, 0x0, 0x30]; +_T Braille_Patterns = [0xa0, 0x28, 0x0, 0x81, 0x0]; +_T Lycian = [0xa1, 0x2, 0x80, 0x20]; +_T Tai_Le = [0x99, 0x50, 0x30]; +_T Miscellaneous_Mathematical_Symbols_B = [0xa0, 0x29, 0x80, 0x80, 0x80]; +_T Musical_Symbols = [0xa1, 0xd1, 0x0, 0x81, 0x0]; +_T Avestan = [0xa1, 0xb, 0x0, 0x40]; +_T Ethiopic = [0x92, 0x0, 0x81, 0x80]; +_T Arabic_Supplement = [0x87, 0x50, 0x30]; +_T Samaritan = [0x88, 0x0, 0x40]; +_T Cuneiform_Numbers_and_Punctuation = [0xa1, 0x24, 0x0, 0x80, 0x80]; +_T Mongolian = [0x98, 0x0, 0x80, 0xb0]; +_T Arabic = [0x86, 0x0, 0x81, 0x0]; +_T Vai = [0xa0, 0xa5, 0x0, 0x81, 0x40]; +_T Tifinagh = [0xa0, 0x2d, 0x30, 0x50]; +_T Bamum_Supplement = [0xa1, 0x68, 0x0, 0x82, 0x40]; +_T Tai_Viet = [0xa0, 0xaa, 0x80, 0x60]; +_T Mandaic = [0x88, 0x40, 0x20]; +_T Sundanese = [0x9b, 0x80, 0x40]; +_T Block_Elements = [0xa0, 0x25, 0x80, 0x20]; +_T Phoenician = [0xa1, 0x9, 0x0, 0x20]; +_T Hanunoo = [0x97, 0x20, 0x20]; +_T Supplemental_Mathematical_Operators = [0xa0, 0x2a, 0x0, 0x81, 0x0]; +_T Deseret = [0xa1, 0x4, 0x0, 0x50]; +_T Brahmi = [0xa1, 0x10, 0x0, 0x80, 0x80]; +_T Devanagari_Extended = [0xa0, 0xa8, 0xe0, 0x20]; +_T Supplementary_Private_Use_Area_A = [0xaf, 0x0, 0x0, 0xa1, 0x0, 0x0]; +_T Box_Drawing = [0xa0, 0x25, 0x0, 0x80, 0x80]; +_T Mathematical_Operators = [0xa0, 0x22, 0x0, 0x81, 0x0]; +_T Ogham = [0x96, 0x80, 0x20]; +_T Meetei_Mayek_Extensions = [0xa0, 0xaa, 0xe0, 0x20]; +_T Hangul_Jamo = [0x91, 0x0, 0x81, 0x0]; +_T Miao = [0xa1, 0x6f, 0x0, 0x80, 0xa0]; +_T Emoticons = [0xa1, 0xf6, 0x0, 0x50]; +_T Tags = [0xae, 0x0, 0x0, 0x80, 0x80]; +_T Yi_Syllables = [0xa0, 0xa0, 0x0, 0x84, 0x90]; +_T Gurmukhi = [0x8a, 0x0, 0x80, 0x80]; +_T Syloti_Nagri = [0xa0, 0xa8, 0x0, 0x30]; +_T Spacing_Modifier_Letters = [0x82, 0xb0, 0x50]; +_T Yi_Radicals = [0xa0, 0xa4, 0x90, 0x40]; +_T Ancient_Greek_Numbers = [0xa1, 0x1, 0x40, 0x50]; +_T Glagolitic = [0xa0, 0x2c, 0x0, 0x60]; +_T Georgian = [0x90, 0xa0, 0x60]; +_T Osmanya = [0xa1, 0x4, 0x80, 0x30]; +_T Variation_Selectors = [0xa0, 0xfe, 0x0, 0x10]; +_T Mathematical_Alphanumeric_Symbols = [0xa1, 0xd4, 0x0, 0x84, 0x0]; +_T Yijing_Hexagram_Symbols = [0xa0, 0x4d, 0xc0, 0x40]; +_T Ethiopic_Extended = [0xa0, 0x2d, 0x80, 0x60]; +_T Transport_And_Map_Symbols = [0xa1, 0xf6, 0x80, 0x80, 0x80]; +_T High_Private_Use_Surrogates = [0xa0, 0xdb, 0x80, 0x80, 0x80]; +_T Meetei_Mayek = [0xa0, 0xab, 0xc0, 0x40]; +_T CJK_Compatibility_Forms = [0xa0, 0xfe, 0x30, 0x20]; +_T Enclosed_Alphanumerics = [0xa0, 0x24, 0x60, 0x80, 0xa0]; +_T Ancient_Symbols = [0xa1, 0x1, 0x90, 0x40]; +_T Ethiopic_Extended_A = [0xa0, 0xab, 0x0, 0x30]; +_T Bengali = [0x89, 0x80, 0x80, 0x80]; +_T Currency_Symbols = [0xa0, 0x20, 0xa0, 0x30]; +_T Myanmar = [0x90, 0x0, 0x80, 0xa0]; +_T Cyrillic_Extended_A = [0xa0, 0x2d, 0xe0, 0x20]; +_T Cyrillic_Extended_B = [0xa0, 0xa6, 0x40, 0x60]; +_T Myanmar_Extended_A = [0xa0, 0xaa, 0x60, 0x20]; +_T Hiragana = [0xa0, 0x30, 0x40, 0x60]; +_T Dingbats = [0xa0, 0x27, 0x0, 0x80, 0xc0]; +_T Armenian = [0x85, 0x30, 0x60]; +_T Tai_Xuan_Jing_Symbols = [0xa1, 0xd3, 0x0, 0x60]; +_T Linear_B_Ideograms = [0xa1, 0x0, 0x80, 0x80, 0x80]; +_T Kharoshthi = [0xa1, 0xa, 0x0, 0x60]; +_T Optical_Character_Recognition = [0xa0, 0x24, 0x40, 0x20]; +_T Enclosed_CJK_Letters_and_Months = [0xa0, 0x32, 0x0, 0x81, 0x0]; +_T Cypriot_Syllabary = [0xa1, 0x8, 0x0, 0x40]; +_T Vedic_Extensions = [0x9c, 0xd0, 0x30]; +_T Kaithi = [0xa1, 0x10, 0x80, 0x50]; +_T Low_Surrogates = [0xa0, 0xdc, 0x0, 0x84, 0x0]; +_T Letterlike_Symbols = [0xa0, 0x21, 0x0, 0x50]; +_T Combining_Diacritical_Marks_for_Symbols = [0xa0, 0x20, 0xd0, 0x30]; +_T Aegean_Numbers = [0xa1, 0x1, 0x0, 0x40]; +_T High_Surrogates = [0xa0, 0xd8, 0x0, 0x83, 0x80]; +_T CJK_Compatibility_Ideographs = [0xa0, 0xf9, 0x0, 0x82, 0x0]; +_T CJK_Symbols_and_Punctuation = [0xa0, 0x30, 0x0, 0x40]; +_T Gothic = [0xa1, 0x3, 0x30, 0x20]; +_T Combining_Diacritical_Marks_Supplement = [0x9d, 0xc0, 0x40]; +_T Phags_pa = [0xa0, 0xa8, 0x40, 0x40]; +_T Miscellaneous_Symbols_and_Arrows = [0xa0, 0x2b, 0x0, 0x81, 0x0]; +_T Bamum = [0xa0, 0xa6, 0xa0, 0x60]; +_T Chakma = [0xa1, 0x11, 0x0, 0x50]; +_T Kana_Supplement = [0xa1, 0xb0, 0x0, 0x81, 0x0]; +_T Tagalog = [0x97, 0x0, 0x20]; +_T Tagbanwa = [0x97, 0x60, 0x20]; +_T Devanagari = [0x89, 0x0, 0x80, 0x80]; +_T Old_Italic = [0xa1, 0x3, 0x0, 0x30]; +_T Arabic_Mathematical_Alphabetic_Symbols = [0xa1, 0xee, 0x0, 0x81, 0x0]; +_T CJK_Unified_Ideographs_Extension_D = [0xa2, 0xb7, 0x40, 0x80, 0xe0]; +_T CJK_Unified_Ideographs_Extension_A = [0xa0, 0x34, 0x0, 0x99, 0xc0]; +_T CJK_Unified_Ideographs_Extension_B = [0xa2, 0x0, 0x0, 0xa0, 0xa6, 0xe0]; +_T CJK_Unified_Ideographs_Extension_C = [0xa2, 0xa7, 0x0, 0x90, 0x40]; +_T Cyrillic = [0x84, 0x0, 0x81, 0x0]; +_U[] _tab = [ +_U("Aegean Numbers", Aegean_Numbers), +_U("Alchemical Symbols", Alchemical_Symbols), +_U("Alphabetic Presentation Forms", Alphabetic_Presentation_Forms), +_U("Ancient Greek Musical Notation", Ancient_Greek_Musical_Notation), +_U("Ancient Greek Numbers", Ancient_Greek_Numbers), +_U("Ancient Symbols", Ancient_Symbols), +_U("Arabic", Arabic), +_U("Arabic Extended-A", Arabic_Extended_A), +_U("Arabic Mathematical Alphabetic Symbols", Arabic_Mathematical_Alphabetic_Symbols), +_U("Arabic Presentation Forms-A", Arabic_Presentation_Forms_A), +_U("Arabic Presentation Forms-B", Arabic_Presentation_Forms_B), +_U("Arabic Supplement", Arabic_Supplement), +_U("Armenian", Armenian), +_U("Arrows", Arrows), +_U("Avestan", Avestan), +_U("Balinese", Balinese), +_U("Bamum", Bamum), +_U("Bamum Supplement", Bamum_Supplement), +_U("Basic Latin", Basic_Latin), +_U("Batak", Batak), +_U("Bengali", Bengali), +_U("Block Elements", Block_Elements), +_U("Bopomofo", Bopomofo), +_U("Bopomofo Extended", Bopomofo_Extended), +_U("Box Drawing", Box_Drawing), +_U("Brahmi", Brahmi), +_U("Braille Patterns", Braille_Patterns), +_U("Buginese", Buginese), +_U("Buhid", Buhid), +_U("Byzantine Musical Symbols", Byzantine_Musical_Symbols), +_U("Carian", Carian), +_U("Chakma", Chakma), +_U("Cham", Cham), +_U("Cherokee", Cherokee), +_U("CJK Compatibility", CJK_Compatibility), +_U("CJK Compatibility Forms", CJK_Compatibility_Forms), +_U("CJK Compatibility Ideographs", CJK_Compatibility_Ideographs), +_U("CJK Compatibility Ideographs Supplement", CJK_Compatibility_Ideographs_Supplement), +_U("CJK Radicals Supplement", CJK_Radicals_Supplement), +_U("CJK Strokes", CJK_Strokes), +_U("CJK Symbols and Punctuation", CJK_Symbols_and_Punctuation), +_U("CJK Unified Ideographs", CJK_Unified_Ideographs), +_U("CJK Unified Ideographs Extension A", CJK_Unified_Ideographs_Extension_A), +_U("CJK Unified Ideographs Extension B", CJK_Unified_Ideographs_Extension_B), +_U("CJK Unified Ideographs Extension C", CJK_Unified_Ideographs_Extension_C), +_U("CJK Unified Ideographs Extension D", CJK_Unified_Ideographs_Extension_D), +_U("Combining Diacritical Marks", Combining_Diacritical_Marks), +_U("Combining Diacritical Marks for Symbols", Combining_Diacritical_Marks_for_Symbols), +_U("Combining Diacritical Marks Supplement", Combining_Diacritical_Marks_Supplement), +_U("Combining Half Marks", Combining_Half_Marks), +_U("Common Indic Number Forms", Common_Indic_Number_Forms), +_U("Control Pictures", Control_Pictures), +_U("Coptic", Coptic), +_U("Counting Rod Numerals", Counting_Rod_Numerals), +_U("Cuneiform", Cuneiform), +_U("Cuneiform Numbers and Punctuation", Cuneiform_Numbers_and_Punctuation), +_U("Currency Symbols", Currency_Symbols), +_U("Cypriot Syllabary", Cypriot_Syllabary), +_U("Cyrillic", Cyrillic), +_U("Cyrillic Extended-A", Cyrillic_Extended_A), +_U("Cyrillic Extended-B", Cyrillic_Extended_B), +_U("Cyrillic Supplement", Cyrillic_Supplement), +_U("Deseret", Deseret), +_U("Devanagari", Devanagari), +_U("Devanagari Extended", Devanagari_Extended), +_U("Dingbats", Dingbats), +_U("Domino Tiles", Domino_Tiles), +_U("Egyptian Hieroglyphs", Egyptian_Hieroglyphs), +_U("Emoticons", Emoticons), +_U("Enclosed Alphanumerics", Enclosed_Alphanumerics), +_U("Enclosed Alphanumeric Supplement", Enclosed_Alphanumeric_Supplement), +_U("Enclosed CJK Letters and Months", Enclosed_CJK_Letters_and_Months), +_U("Enclosed Ideographic Supplement", Enclosed_Ideographic_Supplement), +_U("Ethiopic", Ethiopic), +_U("Ethiopic Extended", Ethiopic_Extended), +_U("Ethiopic Extended-A", Ethiopic_Extended_A), +_U("Ethiopic Supplement", Ethiopic_Supplement), +_U("General Punctuation", General_Punctuation), +_U("Geometric Shapes", Geometric_Shapes), +_U("Georgian", Georgian), +_U("Georgian Supplement", Georgian_Supplement), +_U("Glagolitic", Glagolitic), +_U("Gothic", Gothic), +_U("Greek and Coptic", Greek_and_Coptic), +_U("Greek Extended", Greek_Extended), +_U("Gujarati", Gujarati), +_U("Gurmukhi", Gurmukhi), +_U("Halfwidth and Fullwidth Forms", Halfwidth_and_Fullwidth_Forms), +_U("Hangul Compatibility Jamo", Hangul_Compatibility_Jamo), +_U("Hangul Jamo", Hangul_Jamo), +_U("Hangul Jamo Extended-A", Hangul_Jamo_Extended_A), +_U("Hangul Jamo Extended-B", Hangul_Jamo_Extended_B), +_U("Hangul Syllables", Hangul_Syllables), +_U("Hanunoo", Hanunoo), +_U("Hebrew", Hebrew), +_U("High Private Use Surrogates", High_Private_Use_Surrogates), +_U("High Surrogates", High_Surrogates), +_U("Hiragana", Hiragana), +_U("Ideographic Description Characters", Ideographic_Description_Characters), +_U("Imperial Aramaic", Imperial_Aramaic), +_U("Inscriptional Pahlavi", Inscriptional_Pahlavi), +_U("Inscriptional Parthian", Inscriptional_Parthian), +_U("IPA Extensions", IPA_Extensions), +_U("Javanese", Javanese), +_U("Kaithi", Kaithi), +_U("Kana Supplement", Kana_Supplement), +_U("Kanbun", Kanbun), +_U("Kangxi Radicals", Kangxi_Radicals), +_U("Kannada", Kannada), +_U("Katakana", Katakana), +_U("Katakana Phonetic Extensions", Katakana_Phonetic_Extensions), +_U("Kayah Li", Kayah_Li), +_U("Kharoshthi", Kharoshthi), +_U("Khmer", Khmer), +_U("Khmer Symbols", Khmer_Symbols), +_U("Lao", Lao), +_U("Latin-1 Supplement", Latin_1_Supplement), +_U("Latin Extended-A", Latin_Extended_A), +_U("Latin Extended Additional", Latin_Extended_Additional), +_U("Latin Extended-B", Latin_Extended_B), +_U("Latin Extended-C", Latin_Extended_C), +_U("Latin Extended-D", Latin_Extended_D), +_U("Lepcha", Lepcha), +_U("Letterlike Symbols", Letterlike_Symbols), +_U("Limbu", Limbu), +_U("Linear B Ideograms", Linear_B_Ideograms), +_U("Linear B Syllabary", Linear_B_Syllabary), +_U("Lisu", Lisu), +_U("Low Surrogates", Low_Surrogates), +_U("Lycian", Lycian), +_U("Lydian", Lydian), +_U("Mahjong Tiles", Mahjong_Tiles), +_U("Malayalam", Malayalam), +_U("Mandaic", Mandaic), +_U("Mathematical Alphanumeric Symbols", Mathematical_Alphanumeric_Symbols), +_U("Mathematical Operators", Mathematical_Operators), +_U("Meetei Mayek", Meetei_Mayek), +_U("Meetei Mayek Extensions", Meetei_Mayek_Extensions), +_U("Meroitic Cursive", Meroitic_Cursive), +_U("Meroitic Hieroglyphs", Meroitic_Hieroglyphs), +_U("Miao", Miao), +_U("Miscellaneous Mathematical Symbols-A", Miscellaneous_Mathematical_Symbols_A), +_U("Miscellaneous Mathematical Symbols-B", Miscellaneous_Mathematical_Symbols_B), +_U("Miscellaneous Symbols", Miscellaneous_Symbols), +_U("Miscellaneous Symbols and Arrows", Miscellaneous_Symbols_and_Arrows), +_U("Miscellaneous Symbols And Pictographs", Miscellaneous_Symbols_And_Pictographs), +_U("Miscellaneous Technical", Miscellaneous_Technical), +_U("Modifier Tone Letters", Modifier_Tone_Letters), +_U("Mongolian", Mongolian), +_U("Musical Symbols", Musical_Symbols), +_U("Myanmar", Myanmar), +_U("Myanmar Extended-A", Myanmar_Extended_A), +_U("New Tai Lue", New_Tai_Lue), +_U("NKo", NKo), +_U("Number Forms", Number_Forms), +_U("Ogham", Ogham), +_U("Ol Chiki", Ol_Chiki), +_U("Old Italic", Old_Italic), +_U("Old Persian", Old_Persian), +_U("Old South Arabian", Old_South_Arabian), +_U("Old Turkic", Old_Turkic), +_U("Optical Character Recognition", Optical_Character_Recognition), +_U("Oriya", Oriya), +_U("Osmanya", Osmanya), +_U("Phags-pa", Phags_pa), +_U("Phaistos Disc", Phaistos_Disc), +_U("Phoenician", Phoenician), +_U("Phonetic Extensions", Phonetic_Extensions), +_U("Phonetic Extensions Supplement", Phonetic_Extensions_Supplement), +_U("Playing Cards", Playing_Cards), +_U("Private Use Area", Private_Use_Area), +_U("Rejang", Rejang), +_U("Rumi Numeral Symbols", Rumi_Numeral_Symbols), +_U("Runic", Runic), +_U("Samaritan", Samaritan), +_U("Saurashtra", Saurashtra), +_U("Sharada", Sharada), +_U("Shavian", Shavian), +_U("Sinhala", Sinhala), +_U("Small Form Variants", Small_Form_Variants), +_U("Sora Sompeng", Sora_Sompeng), +_U("Spacing Modifier Letters", Spacing_Modifier_Letters), +_U("Specials", Specials), +_U("Sundanese", Sundanese), +_U("Sundanese Supplement", Sundanese_Supplement), +_U("Superscripts and Subscripts", Superscripts_and_Subscripts), +_U("Supplemental Arrows-A", Supplemental_Arrows_A), +_U("Supplemental Arrows-B", Supplemental_Arrows_B), +_U("Supplemental Mathematical Operators", Supplemental_Mathematical_Operators), +_U("Supplemental Punctuation", Supplemental_Punctuation), +_U("Supplementary Private Use Area-A", Supplementary_Private_Use_Area_A), +_U("Supplementary Private Use Area-B", Supplementary_Private_Use_Area_B), +_U("Syloti Nagri", Syloti_Nagri), +_U("Syriac", Syriac), +_U("Tagalog", Tagalog), +_U("Tagbanwa", Tagbanwa), +_U("Tags", Tags), +_U("Tai Le", Tai_Le), +_U("Tai Tham", Tai_Tham), +_U("Tai Viet", Tai_Viet), +_U("Tai Xuan Jing Symbols", Tai_Xuan_Jing_Symbols), +_U("Takri", Takri), +_U("Tamil", Tamil), +_U("Telugu", Telugu), +_U("Thaana", Thaana), +_U("Thai", Thai), +_U("Tibetan", Tibetan), +_U("Tifinagh", Tifinagh), +_U("Transport And Map Symbols", Transport_And_Map_Symbols), +_U("Ugaritic", Ugaritic), +_U("Unified Canadian Aboriginal Syllabics", Unified_Canadian_Aboriginal_Syllabics), +_U("Unified Canadian Aboriginal Syllabics Extended", Unified_Canadian_Aboriginal_Syllabics_Extended), +_U("Vai", Vai), +_U("Variation Selectors", Variation_Selectors), +_U("Variation Selectors Supplement", Variation_Selectors_Supplement), +_U("Vedic Extensions", Vedic_Extensions), +_U("Vertical Forms", Vertical_Forms), +_U("Yijing Hexagram Symbols", Yijing_Hexagram_Symbols), +_U("Yi Radicals", Yi_Radicals), +_U("Yi Syllables", Yi_Syllables), +]; +} + +struct scripts +{ +private alias _U = immutable(UnicodeProperty); +@property static _U[] tab() pure nothrow @nogc { return _tab; } +static immutable: +private alias _T = ubyte[]; +_T Buhid = [0x97, 0x40, 0x14]; +_T Sinhala = [0x8d, 0x82, 0x2, 0x1, 0x12, 0x3, 0x18, 0x1, 0x9, 0x1, 0x1, 0x2, + 0x7, 0x3, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x8, 0x12, 0x3]; +_T Phags_Pa = [0xa0, 0xa8, 0x40, 0x38]; +_T Old_Turkic = [0xa1, 0xc, 0x0, 0x49]; +_T Oriya = [0x8b, 0x1, 0x3, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, 0x2, + 0x1, 0x5, 0x2, 0x9, 0x2, 0x2, 0x2, 0x3, 0x8, 0x2, 0x4, 0x2, 0x1, 0x5, 0x2, 0x12]; +_T Thaana = [0x87, 0x80, 0x32]; +_T Inherited = [ + 0x83, 0x0, 0x70, 0x81, 0x15, 0x2, 0x81, 0xc4, 0xb, 0x1a, 0x1, 0x82, 0xe0, + 0x2, 0x93, 0x7d, 0x3, 0x1, 0xd, 0x1, 0x7, 0x4, 0x1, 0x6, 0x1, 0x80, 0xcb, + 0x27, 0x15, 0x4, 0x82, 0xc, 0x2, 0x80, 0xc2, 0x21, 0x8f, 0x39, 0x4, 0x6b, + 0x2, 0xa0, 0xcd, 0x65, 0x10, 0x10, 0x7, 0x83, 0xd6, 0x1, 0xa0, 0xcf, + 0x69, 0x3, 0x11, 0x8, 0x2, 0x7, 0x1e, 0x4, 0xac, 0x2f, 0x52, 0x80, 0xf0 +]; +_T Sharada = [0xa1, 0x11, 0x80, 0x49, 0x7, 0xa]; +_T Rejang = [0xa0, 0xa9, 0x30, 0x24, 0xb, 0x1]; +_T Imperial_Aramaic = [0xa1, 0x8, 0x40, 0x16, 0x1, 0x9]; +_T Cham = [0xa0, 0xaa, 0x0, 0x37, 0x9, 0xe, 0x2, 0xa, 0x2, 0x4]; +_T Kaithi = [0xa1, 0x10, 0x80, 0x42]; +_T Bopomofo = [0x82, 0xea, 0x2, 0xa0, 0x2e, 0x19, 0x29, 0x72, 0x1b]; +_T Deseret = [0xa1, 0x4, 0x0, 0x50]; +_T Syloti_Nagri = [0xa0, 0xa8, 0x0, 0x2c]; +_T Lycian = [0xa1, 0x2, 0x80, 0x1d]; +_T Linear_B = [0xa1, 0x0, 0x0, 0xc, 0x1, 0x1a, 0x1, 0x13, 0x1, 0x2, 0x1, 0xf, + 0x2, 0xe, 0x22, 0x7b]; +_T Hebrew = [0x85, 0x91, 0x37, 0x8, 0x1b, 0x5, 0x5, 0xa0, 0xf5, 0x28, 0x1a, + 0x1, 0x5, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x1, 0xa]; +_T Saurashtra = [0xa0, 0xa8, 0x80, 0x45, 0x9, 0xc]; +_T Avestan = [0xa1, 0xb, 0x0, 0x36, 0x3, 0x7]; +_T Ethiopic = [0x92, 0x0, 0x49, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, + 0x29, 0x1, 0x4, 0x2, 0x21, 0x1, 0x4, 0x2, 0x7, 0x1, 0x1, 0x1, 0x4, 0x2, + 0xf, 0x1, 0x39, 0x1, 0x4, 0x2, 0x43, 0x2, 0x20, 0x3, 0x1a, 0x99, 0xe6, + 0x17, 0x9, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, 0x1, 0x7, + 0x1, 0x7, 0xa0, 0x7d, 0x22, 0x6, 0x2, 0x6, 0x2, 0x6, 0x9, 0x7, 0x1, 0x7]; +_T Braille = [0xa0, 0x28, 0x0, 0x81, 0x0]; +_T Lisu = [0xa0, 0xa4, 0xd0, 0x30]; +_T Samaritan = [0x88, 0x0, 0x2e, 0x2, 0xf]; +_T Mongolian = [0x98, 0x0, 0x2, 0x2, 0x1, 0x1, 0x9, 0x1, 0xa, 0x6, 0x58, 0x8, 0x2b]; +_T Hangul = [ + 0x91, 0x0, 0x81, 0x0, 0x9e, 0x2e, 0x2, 0x81, 0x1, 0x5e, 0x71, 0x1f, 0x41, + 0x1f, 0xa0, 0x76, 0xe1, 0x1d, 0x82, 0x83, 0xa0, 0x2b, 0xa4, 0xc, 0x17, 0x4, + 0x31, 0xa0, 0x27, 0xa4, 0x1f, 0x3, 0x6, 0x2, 0x6, 0x2, 0x6, 0x2, 0x3 +]; +_T Takri = [0xa1, 0x16, 0x80, 0x38, 0x8, 0xa]; +_T Phoenician = [0xa1, 0x9, 0x0, 0x1c, 0x3, 0x1]; +_T Vai = [0xa0, 0xa5, 0x0, 0x81, 0x2c]; +_T Batak = [0x9b, 0xc0, 0x34, 0x8, 0x4]; +_T Yi = [0xa0, 0xa0, 0x0, 0x84, 0x8d, 0x3, 0x37]; +_T Tifinagh = [0xa0, 0x2d, 0x30, 0x38, 0x7, 0x2, 0xe, 0x1]; +_T Glagolitic = [0xa0, 0x2c, 0x0, 0x2f, 0x1, 0x2f]; +_T Tai_Tham = [0x9a, 0x20, 0x3f, 0x1, 0x1d, 0x2, 0xb, 0x6, 0xa, 0x6, 0xe]; +_T Canadian_Aboriginal = [0x94, 0x0, 0x82, 0x80, 0x82, 0x30, 0x46]; +_T Meetei_Mayek = [0xa0, 0xaa, 0xe0, 0x17, 0x80, 0xc9, 0x2e, 0x2, 0xa]; +_T Balinese = [0x9b, 0x0, 0x4c, 0x4, 0x2d]; +_T Kayah_Li = [0xa0, 0xa9, 0x0, 0x30]; +_T Kharoshthi = [0xa1, 0xa, 0x0, 0x4, 0x1, 0x2, 0x5, 0x8, 0x1, 0x3, 0x1, 0x1b, + 0x4, 0x3, 0x4, 0x9, 0x8, 0x9]; +_T Lepcha = [0x9c, 0x0, 0x38, 0x3, 0xf, 0x3, 0x3]; +_T New_Tai_Lue = [0x99, 0x80, 0x2c, 0x4, 0x1a, 0x6, 0xb, 0x3, 0x2]; +_T Sora_Sompeng = [0xa1, 0x10, 0xd0, 0x19, 0x7, 0xa]; +_T Arabic = [0x86, 0x0, 0x5, 0x1, 0x6, 0x1, 0xe, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x20, 0x1, 0xa, 0xb, 0xa, 0xa, 0x6, 0x1, 0x6c, 0x1, 0x22, 0x50, 0x30, + 0x81, 0x20, 0x1, 0x1, 0xb, 0x37, 0x1b, 0xa0, 0xf2, 0x51, 0x72, 0x11, 0x81, + 0x6b, 0x12, 0x40, 0x2, 0x36, 0x28, 0xd, 0x73, 0x5, 0x1, 0x80, 0x87, 0x8f, + 0x63, 0x1f, 0xa0, 0xdf, 0x81, 0x4, 0x1, 0x1b, 0x1, 0x2, 0x1, 0x1, 0x2, + 0x1, 0x1, 0xa, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x6, 0x1, 0x4, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x3, 0x1, 0x2, 0x1, 0x1, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x2, 0x1, 0x1, 0x2, 0x4, 0x1, 0x7, 0x1, 0x4, 0x1, 0x4, + 0x1, 0x1, 0x1, 0xa, 0x1, 0x11, 0x5, 0x3, 0x1, 0x5, 0x1, 0x11, 0x34, 0x2]; +_T Hanunoo = [0x97, 0x20, 0x15]; +_T Lydian = [0xa1, 0x9, 0x20, 0x1a, 0x5, 0x1]; +_T Tai_Viet = [0xa0, 0xaa, 0x80, 0x43, 0x18, 0x5]; +_T Coptic = [0x83, 0xe2, 0xe, 0xa0, 0x28, 0x90, 0x74, 0x5, 0x7]; +_T Brahmi = [0xa1, 0x10, 0x0, 0x4e, 0x4, 0x1e]; +_T Runic = [0x96, 0xa0, 0x4b, 0x3, 0x3]; +_T Egyptian_Hieroglyphs = [0xa1, 0x30, 0x0, 0x84, 0x2f]; +_T Khmer = [0x97, 0x80, 0x5e, 0x2, 0xa, 0x6, 0xa, 0x81, 0xe6, 0x20]; +_T Ogham = [0x96, 0x80, 0x1d]; +_T Gothic = [0xa1, 0x3, 0x30, 0x1b]; +_T Katakana = [ + 0xa0, 0x30, 0xa1, 0x5a, 0x2, 0x3, 0x80, 0xf0, 0x10, 0x80, 0xd0, 0x2f, 0x1, + 0x58, 0xa0, 0xcc, 0xe, 0xa, 0x1, 0x2d, 0xa0, 0xb0, 0x62, 0x1 +]; +_T Miao = [0xa1, 0x6f, 0x0, 0x45, 0xb, 0x2f, 0x10, 0x11]; +_T Meroitic_Hieroglyphs = [0xa1, 0x9, 0x80, 0x20]; +_T Thai = [0x8e, 0x1, 0x3a, 0x5, 0x1c]; +_T Cypriot = [0xa1, 0x8, 0x0, 0x6, 0x2, 0x1, 0x1, 0x2c, 0x1, 0x2, 0x3, 0x1, 0x2, 0x1]; +_T Meroitic_Cursive = [0xa1, 0x9, 0xa0, 0x18, 0x6, 0x2]; +_T Gujarati = [0x8a, 0x81, 0x3, 0x1, 0x9, 0x1, 0x3, 0x1, 0x16, 0x1, 0x7, 0x1, + 0x2, 0x1, 0x5, 0x2, 0xa, 0x1, 0x3, 0x1, 0x3, 0x2, 0x1, 0xf, 0x4, 0x2, 0xc]; +_T Lao = [0x8e, 0x81, 0x2, 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x2, 0x1, 0x6, 0x4, + 0x1, 0x7, 0x1, 0x3, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0xd, 0x1, 0x3, 0x2, + 0x5, 0x1, 0x1, 0x1, 0x6, 0x2, 0xa, 0x2, 0x4]; +_T Georgian = [0x90, 0xa0, 0x26, 0x1, 0x1, 0x5, 0x1, 0x2, 0x2b, 0x1, 0x4, 0x9c, + 0x0, 0x26, 0x1, 0x1, 0x5, 0x1]; +_T Osmanya = [0xa1, 0x4, 0x80, 0x1e, 0x2, 0xa]; +_T Inscriptional_Pahlavi = [0xa1, 0xb, 0x60, 0x13, 0x5, 0x8]; +_T Shavian = [0xa1, 0x4, 0x50, 0x30]; +_T Carian = [0xa1, 0x2, 0xa0, 0x31]; +_T Cherokee = [0x93, 0xa0, 0x55]; +_T Mandaic = [0x88, 0x40, 0x1c, 0x2, 0x1]; +_T Han = [0xa0, 0x2e, 0x80, 0x1a, 0x1, 0x59, 0xc, 0x80, 0xd6, 0x2f, 0x1, 0x1, + 0x1, 0x19, 0x9, 0xe, 0x4, 0x83, 0xc4, 0x99, 0xb6, 0x4a, 0xa0, 0x51, 0xcd, + 0xa0, 0x59, 0x33, 0x81, 0x6e, 0x2, 0x6a, 0xa1, 0x5, 0x26, 0xa0, 0xa6, 0xd7, + 0x29, 0x90, 0x35, 0xb, 0x80, 0xde, 0xa0, 0x3f, 0xe2, 0x82, 0x1e]; +_T Latin = [0x41, 0x1a, 0x6, 0x1a, 0x2f, 0x1, 0xf, 0x1, 0x5, 0x17, 0x1, 0x1f, + 0x1, 0x81, 0xc1, 0x27, 0x5, 0x9a, 0x1b, 0x26, 0x6, 0x31, 0x5, 0x4, 0x5, + 0xd, 0x1, 0x46, 0x41, 0x81, 0x0, 0x81, 0x71, 0x1, 0xd, 0x1, 0x10, 0xd, + 0x80, 0x8d, 0x2, 0x6, 0x1, 0x1b, 0x1, 0x11, 0x29, 0x8a, 0xd7, 0x20, 0xa0, + 0x7a, 0xa2, 0x66, 0x3, 0x4, 0x1, 0x4, 0xc, 0xb, 0x4d, 0x8, 0xa0, 0x53, 0x0, + 0x7, 0x84, 0x1a, 0x1a, 0x6, 0x1a]; +_T Limbu = [0x99, 0x0, 0x1d, 0x3, 0xc, 0x4, 0xc, 0x4, 0x1, 0x3, 0xc]; +_T Ol_Chiki = [0x9c, 0x50, 0x30]; +_T Bengali = [0x89, 0x81, 0x3, 0x1, 0x8, 0x2, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, + 0x1, 0x3, 0x4, 0x2, 0x9, 0x2, 0x2, 0x2, 0x4, 0x8, 0x1, 0x4, 0x2, 0x1, 0x5, 0x2, + 0x16]; +_T Myanmar = [0x90, 0x0, 0x80, 0xa0, 0xa0, 0x99, 0xc0, 0x1c]; +_T Malayalam = [0x8d, 0x2, 0x2, 0x1, 0x8, 0x1, 0x3, 0x1, 0x29, 0x2, 0x8, 0x1, + 0x3, 0x1, 0x5, 0x8, 0x1, 0x8, 0x4, 0x2, 0x10, 0x3, 0x7]; +_T Hiragana = [0xa0, 0x30, 0x41, 0x56, 0x6, 0x3, 0xa1, 0x7f, 0x61, 0x1, 0xa0, 0x41, + 0xfe, 0x1]; +_T Kannada = [0x8c, 0x82, 0x2, 0x1, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, + 0x5, 0x2, 0x9, 0x1, 0x3, 0x1, 0x4, 0x7, 0x2, 0x7, 0x1, 0x1, 0x4, 0x2, 0xa, 0x1, + 0x2]; +_T Armenian = [0x85, 0x31, 0x26, 0x2, 0x7, 0x1, 0x27, 0x2, 0x1, 0x4, 0x1, 0xa0, 0xf5, + 0x83, 0x5]; +_T Common = [0x0, 0x41, 0x1a, 0x6, 0x1a, 0x2f, 0x1, 0xf, 0x1, 0x5, 0x17, 0x1, + 0x1f, 0x1, 0x81, 0xc1, 0x27, 0x5, 0x5, 0x2, 0x14, 0x74, 0x1, 0x9, 0x1, + 0x6, 0x1, 0x1, 0x1, 0x82, 0x1, 0x1, 0x80, 0x82, 0x1, 0xe, 0x1, 0x3, 0x1, + 0x20, 0x1, 0x1f, 0xa, 0x73, 0x1, 0x82, 0x86, 0x2, 0x84, 0xd9, 0x1, 0x81, + 0x95, 0x4, 0x81, 0x22, 0x1, 0x85, 0xef, 0x3, 0x47, 0x2, 0x80, 0xcb, 0x2, + 0x1, 0x1, 0x84, 0xcd, 0x1, 0xd, 0x1, 0x7, 0x4, 0x1, 0x6, 0x1, 0x2, 0x83, + 0x9, 0xc, 0x2, 0x57, 0x1, 0xb, 0x3, 0xb, 0x1, 0xf, 0x11, 0x1b, 0x45, 0x26, + 0x1, 0x3, 0x2, 0x6, 0x1, 0x1b, 0x1, 0x11, 0x29, 0x1, 0x6, 0x82, 0x64, 0xc, + 0x27, 0x19, 0xb, 0x15, 0x82, 0xa0, 0x1, 0x80, 0xff, 0x81, 0x0, 0x82, + 0x4d, 0x3, 0xa, 0x82, 0xa6, 0x3c, 0x81, 0xb4, 0xc, 0x4, 0x5, 0x1, 0x1, + 0x1, 0x19, 0xf, 0x8, 0x4, 0x4, 0x5b, 0x2, 0x3, 0x1, 0x5a, 0x2, 0x80, 0x93, + 0x10, 0x20, 0x24, 0x3c, 0x40, 0x1f, 0x51, 0x80, 0x88, 0x80, 0xa8, 0x99, + 0xc0, 0x40, 0xa0, 0x59, 0x0, 0x22, 0x66, 0x3, 0x80, 0xa5, 0xa, 0x81, 0x95, + 0x1, 0xa0, 0x53, 0x6e, 0x2, 0x80, 0xbd, 0x1, 0x12, 0xa, 0x16, 0x23, 0x1, + 0x13, 0x1, 0x4, 0x80, 0x93, 0x1, 0x1, 0x20, 0x1a, 0x6, 0x1a, 0xb, 0xa, 0x1, + 0x2d, 0x2, 0x40, 0x7, 0x1, 0x7, 0xa, 0x5, 0x81, 0x2, 0x3, 0x4, 0x2d, 0x3, + 0x9, 0x50, 0xc, 0x34, 0x2d, 0xa0, 0xce, 0x3, 0x80, 0xf6, 0xa, 0x27, 0x2, + 0x3e, 0x3, 0x11, 0x8, 0x2, 0x7, 0x1e, 0x4, 0x30, 0x81, 0x22, 0x57, 0x9, + 0x12, 0x80, 0x8e, 0x55, 0x1, 0x47, 0x1, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x4, + 0x1, 0xc, 0x1, 0x1, 0x1, 0x7, 0x1, 0x41, 0x1, 0x4, 0x2, 0x8, 0x1, 0x7, + 0x1, 0x1c, 0x1, 0x4, 0x1, 0x5, 0x1, 0x1, 0x3, 0x7, 0x1, 0x81, 0x54, 0x2, + 0x81, 0x24, 0x2, 0x32, 0x98, 0x0, 0x2c, 0x4, 0x64, 0xc, 0xf, 0x2, 0xe, + 0x2, 0xf, 0x1, 0xf, 0x20, 0xb, 0x5, 0x1f, 0x1, 0x3c, 0x4, 0x2b, 0x4b, + 0x1a, 0x1, 0x2, 0xd, 0x2b, 0x5, 0x9, 0x7, 0x2, 0x80, 0xae, 0x21, 0xf, + 0x6, 0x1, 0x46, 0x3, 0x14, 0xc, 0x25, 0x1, 0x5, 0x15, 0x11, 0xf, 0x3f, 0x1, + 0x1, 0x1, 0x80, 0xb6, 0x1, 0x4, 0x3, 0x3e, 0x2, 0x4, 0xc, 0x18, 0x80, 0x93, + 0x46, 0x4, 0xb, 0x30, 0x46, 0x3a, 0x74, 0xac, 0x8, 0x8d, 0x1, 0x1e, 0x60]; +_T Old_Italic = [0xa1, 0x3, 0x0, 0x1f, 0x1, 0x4]; +_T Old_Persian = [0xa1, 0x3, 0xa0, 0x24, 0x4, 0xe]; +_T Greek = [0x83, 0x70, 0x4, 0x1, 0x3, 0x2, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, + 0x3, 0x1, 0x1, 0x1, 0x14, 0x1, 0x3f, 0xe, 0x10, 0x99, 0x26, 0x5, 0x32, + 0x5, 0x4, 0x5, 0x54, 0x1, 0x81, 0x40, 0x16, 0x2, 0x6, 0x2, 0x26, 0x2, 0x6, + 0x2, 0x8, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1f, 0x2, 0x35, 0x1, 0xf, + 0x1, 0xe, 0x2, 0x6, 0x1, 0x13, 0x2, 0x3, 0x1, 0x9, 0x81, 0x27, 0x1, 0xa0, + 0xe0, 0x19, 0x4b, 0xa0, 0xd0, 0x75, 0x46]; +_T Sundanese = [0x9b, 0x80, 0x40, 0x81, 0x0, 0x8]; +_T Syriac = [0x87, 0x0, 0xe, 0x1, 0x3c, 0x2, 0x3]; +_T Gurmukhi = [0x8a, 0x1, 0x3, 0x1, 0x6, 0x4, 0x2, 0x2, 0x16, 0x1, 0x7, 0x1, + 0x2, 0x1, 0x2, 0x1, 0x2, 0x2, 0x1, 0x1, 0x5, 0x4, 0x2, 0x2, 0x3, 0x3, 0x1, + 0x7, 0x4, 0x1, 0x1, 0x7, 0x10]; +_T Tibetan = [0x8f, 0x0, 0x48, 0x1, 0x24, 0x4, 0x27, 0x1, 0x24, 0x1, 0xf, 0x1, 0x7, + 0x4, 0x2]; +_T Tamil = [0x8b, 0x82, 0x2, 0x1, 0x6, 0x3, 0x3, 0x1, 0x4, 0x3, 0x2, 0x1, 0x1, + 0x1, 0x2, 0x3, 0x2, 0x3, 0x3, 0x3, 0xc, 0x4, 0x5, 0x3, 0x3, 0x1, 0x4, 0x2, + 0x1, 0x6, 0x1, 0xe, 0x15]; +_T Telugu = [0x8c, 0x1, 0x3, 0x1, 0x8, 0x1, 0x3, 0x1, 0x17, 0x1, 0xa, 0x1, + 0x5, 0x3, 0x8, 0x1, 0x3, 0x1, 0x4, 0x7, 0x2, 0x1, 0x2, 0x6, 0x4, 0x2, 0xa, 0x8, + 0x8]; +_T Inscriptional_Parthian = [0xa1, 0xb, 0x40, 0x16, 0x2, 0x8]; +_T Nko = [0x87, 0xc0, 0x3b]; +_T Javanese = [0xa0, 0xa9, 0x80, 0x4e, 0x2, 0xa, 0x4, 0x2]; +_T Tai_Le = [0x99, 0x50, 0x1e, 0x2, 0x5]; +_T Old_South_Arabian = [0xa1, 0xa, 0x60, 0x20]; +_T Bamum = [0xa0, 0xa6, 0xa0, 0x58, 0xa0, 0xc1, 0x8, 0x82, 0x39]; +_T Chakma = [0xa1, 0x11, 0x0, 0x35, 0x1, 0xe]; +_T Ugaritic = [0xa1, 0x3, 0x80, 0x1e, 0x1, 0x1]; +_T Tagalog = [0x97, 0x0, 0xd, 0x1, 0x7]; +_T Tagbanwa = [0x97, 0x60, 0xd, 0x1, 0x3, 0x1, 0x2]; +_T Devanagari = [0x89, 0x0, 0x51, 0x2, 0x11, 0x2, 0x12, 0x1, 0x7, 0xa0, 0x9f, 0x60, + 0x1c]; +_T Buginese = [0x9a, 0x0, 0x1c, 0x2, 0x2]; +_T Cuneiform = [0xa1, 0x20, 0x0, 0x83, 0x6f, 0x80, 0x91, 0x63, 0xd, 0x4]; +_T Cyrillic = [ + 0x84, 0x0, 0x80, 0x85, 0x2, 0x80, 0xa1, 0x98, 0x3, 0x1, 0x4c, 0x1, 0x90, + 0x67, 0x20, 0xa0, 0x78, 0x40, 0x58, 0x7, 0x1 +]; +_U[] _tab = [ +_U("Arabic", Arabic), +_U("Armenian", Armenian), +_U("Avestan", Avestan), +_U("Balinese", Balinese), +_U("Bamum", Bamum), +_U("Batak", Batak), +_U("Bengali", Bengali), +_U("Bopomofo", Bopomofo), +_U("Brahmi", Brahmi), +_U("Braille", Braille), +_U("Buginese", Buginese), +_U("Buhid", Buhid), +_U("Canadian_Aboriginal", Canadian_Aboriginal), +_U("Carian", Carian), +_U("Chakma", Chakma), +_U("Cham", Cham), +_U("Cherokee", Cherokee), +_U("Common", Common), +_U("Coptic", Coptic), +_U("Cuneiform", Cuneiform), +_U("Cypriot", Cypriot), +_U("Cyrillic", Cyrillic), +_U("Deseret", Deseret), +_U("Devanagari", Devanagari), +_U("Egyptian_Hieroglyphs", Egyptian_Hieroglyphs), +_U("Ethiopic", Ethiopic), +_U("Georgian", Georgian), +_U("Glagolitic", Glagolitic), +_U("Gothic", Gothic), +_U("Greek", Greek), +_U("Gujarati", Gujarati), +_U("Gurmukhi", Gurmukhi), +_U("Han", Han), +_U("Hangul", Hangul), +_U("Hanunoo", Hanunoo), +_U("Hebrew", Hebrew), +_U("Hiragana", Hiragana), +_U("Imperial_Aramaic", Imperial_Aramaic), +_U("Inherited", Inherited), +_U("Inscriptional_Pahlavi", Inscriptional_Pahlavi), +_U("Inscriptional_Parthian", Inscriptional_Parthian), +_U("Javanese", Javanese), +_U("Kaithi", Kaithi), +_U("Kannada", Kannada), +_U("Katakana", Katakana), +_U("Kayah_Li", Kayah_Li), +_U("Kharoshthi", Kharoshthi), +_U("Khmer", Khmer), +_U("Lao", Lao), +_U("Latin", Latin), +_U("Lepcha", Lepcha), +_U("Limbu", Limbu), +_U("Linear_B", Linear_B), +_U("Lisu", Lisu), +_U("Lycian", Lycian), +_U("Lydian", Lydian), +_U("Malayalam", Malayalam), +_U("Mandaic", Mandaic), +_U("Meetei_Mayek", Meetei_Mayek), +_U("Meroitic_Cursive", Meroitic_Cursive), +_U("Meroitic_Hieroglyphs", Meroitic_Hieroglyphs), +_U("Miao", Miao), +_U("Mongolian", Mongolian), +_U("Myanmar", Myanmar), +_U("New_Tai_Lue", New_Tai_Lue), +_U("Nko", Nko), +_U("Ogham", Ogham), +_U("Ol_Chiki", Ol_Chiki), +_U("Old_Italic", Old_Italic), +_U("Old_Persian", Old_Persian), +_U("Old_South_Arabian", Old_South_Arabian), +_U("Old_Turkic", Old_Turkic), +_U("Oriya", Oriya), +_U("Osmanya", Osmanya), +_U("Phags_Pa", Phags_Pa), +_U("Phoenician", Phoenician), +_U("Rejang", Rejang), +_U("Runic", Runic), +_U("Samaritan", Samaritan), +_U("Saurashtra", Saurashtra), +_U("Sharada", Sharada), +_U("Shavian", Shavian), +_U("Sinhala", Sinhala), +_U("Sora_Sompeng", Sora_Sompeng), +_U("Sundanese", Sundanese), +_U("Syloti_Nagri", Syloti_Nagri), +_U("Syriac", Syriac), +_U("Tagalog", Tagalog), +_U("Tagbanwa", Tagbanwa), +_U("Tai_Le", Tai_Le), +_U("Tai_Tham", Tai_Tham), +_U("Tai_Viet", Tai_Viet), +_U("Takri", Takri), +_U("Tamil", Tamil), +_U("Telugu", Telugu), +_U("Thaana", Thaana), +_U("Thai", Thai), +_U("Tibetan", Tibetan), +_U("Tifinagh", Tifinagh), +_U("Ugaritic", Ugaritic), +_U("Vai", Vai), +_U("Yi", Yi), +]; +} + +struct hangul +{ +private alias _U = immutable(UnicodeProperty); +@property static _U[] tab() pure nothrow @nogc { return _tab; } +static immutable: +private alias _T = ubyte[]; +_T LVT = [0xa0, 0xac, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b]; +_T T = [0x91, 0xa8, 0x58, 0xa0, 0xc5, 0xcb, 0x31]; +_T V = [0x91, 0x60, 0x48, 0xa0, 0xc6, 0x8, 0x17]; +_T LV = [0xa0, 0xac, 0x0, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, + 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, + 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1, 0x1b, 0x1]; +_T L = [0x91, 0x0, 0x60, 0xa0, 0x98, 0x0, 0x1d]; +_U[] _tab = [ +_U("L", L), +_U("Leading_Jamo", L), +_U("LV", LV), +_U("LV_Syllable", LV), +_U("LVT", LVT), +_U("LVT_Syllable", LVT), +_U("T", T), +_U("Trailing_Jamo", T), +_U("V", V), +_U("Vowel_Jamo", V), +]; +} +bool isFormatGen(dchar ch) @safe pure nothrow +{ + if (ch < 8288) + { + if (ch < 1807) + { + if (ch < 1564) + { + if (ch == 173) return true; + if (ch < 1536) return false; + if (ch < 1541) return true; + return false; + } + else if (ch < 1565) return true; + else + { + if (ch == 1757) return true; + return false; + } + } + else if (ch < 1808) return true; + else + { + if (ch < 8203) + { + if (ch == 6158) return true; + return false; + } + else if (ch < 8208) return true; + else + { + if (ch < 8234) return false; + if (ch < 8239) return true; + return false; + } + } + } + else if (ch < 8293) return true; + else + { + if (ch < 69_821) + { + if (ch < 65_279) + { + if (ch < 8294) return false; + if (ch < 8304) return true; + return false; + } + else if (ch < 65_280) return true; + else + { + if (ch < 65_529) return false; + if (ch < 65_532) return true; + return false; + } + } + else if (ch < 69_822) return true; + else + { + if (ch < 917_505) + { + if (ch < 119_155) return false; + if (ch < 119_163) return true; + return false; + } + else if (ch < 917_506) return true; + else + { + if (ch < 917_536) return false; + if (ch < 917_632) return true; + return false; + } + } + } +} + +bool isControlGen(dchar ch) @safe pure nothrow +{ + if (ch < 32) return true; + if (ch < 127) return false; + if (ch < 160) return true; + return false; +} + +bool isSpaceGen(dchar ch) @safe pure nothrow +{ + if (ch < 160) + { + if (ch == 32) return true; + return false; + } + else if (ch < 161) return true; + else + { + if (ch < 8239) + { + if (ch == 5760) return true; + if (ch < 8192) return false; + if (ch < 8203) return true; + return false; + } + else if (ch < 8240) return true; + else + { + if (ch == 8287) return true; + if (ch == 12_288) return true; + return false; + } + } +} + +bool isWhiteGen(dchar ch) @safe pure nothrow @nogc +{ + if (ch < 133) + { + if (ch < 9) return false; + if (ch < 14) return true; + if (ch == 32) return true; + return false; + } + else if (ch < 134) return true; + else + { + if (ch < 8232) + { + if (ch < 5760) + { + if (ch == 160) return true; + return false; + } + else if (ch < 5761) return true; + else + { + if (ch < 8192) return false; + if (ch < 8203) return true; + return false; + } + } + else if (ch < 8234) return true; + else + { + if (ch < 8287) + { + if (ch == 8239) return true; + return false; + } + else if (ch < 8288) return true; + else + { + if (ch == 12_288) return true; + return false; + } + } + } +} + +bool isHangL(dchar ch) @safe pure nothrow +{ + if (ch < 4352) return false; + if (ch < 4448) return true; + if (ch < 43_360) return false; + if (ch < 43_389) return true; + return false; +} + +bool isHangV(dchar ch) @safe pure nothrow +{ + if (ch < 4448) return false; + if (ch < 4520) return true; + if (ch < 55_216) return false; + if (ch < 55_239) return true; + return false; +} + +bool isHangT(dchar ch) @safe pure nothrow +{ + if (ch < 4520) return false; + if (ch < 4608) return true; + if (ch < 55_243) return false; + if (ch < 55_292) return true; + return false; +} + +static if (size_t.sizeof == 8) +{ +//1536 bytes +enum lowerCaseTrieEntries = TrieEntry!(bool, 8, 4, 9)([0x0, 0x20, 0x40], + [0x100, 0x80, 0x2000], [0x402030202020100, 0x206020202020205, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3000200010000, 0x3000300030003, 0x3000300030003, 0x5000400030003, + 0x3000700030006, 0x3000800030003, 0x3000300030003, 0x3000300030003, + 0x3000300030003, 0x3000300030003, 0x3000300030003, 0x3000300030003, + 0x9000300030003, 0x3000300030003, 0x3000300030003, 0x3000300030003, + 0x3000300030003, 0x3000300030003, 0x3000300030003, 0xb0003000a0003, + 0x3000c00030003, 0x3000300030003, 0x3000300030003, 0x3000300030003, + 0x3000300030003, 0x3000300030003, 0xe000d00030003, 0x3000300030003, + 0x3000300030003, 0x3000300030003, 0x3000300030003, 0x3000300030003, + 0x0, 0x7fffffe00000000, 0x420040000000000, 0xff7fffff80000000, + 0x55aaaaaaaaaaaaaa, 0xd4aaaaaaaaaaab55, 0xe6512d2a4e243129, + 0xaa29aaaab5555240, 0x93faaaaaaaaaaaaa, 0xffffffffffffaa85, + 0x1ffffffffefffff, 0x1f00000003, 0x0, 0x3c8a000000000020, + 0xfffff00000010000, 0x192faaaaaae37fff, 0xffff000000000000, + 0xaaaaaaaaffffffff, 0xaaaaaaaaaaaaa802, 0xaaaaaaaaaaaad554, + 0xaaaaaaaaaa, 0xfffffffe00000000, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x0, 0xaaaaaaaaaaaaaaaa, + 0xaaaaaaaaaaaaaaaa, 0xaaaaaaaabfeaaaaa, 0xaaaaaaaaaaaaaaaa, + 0xff00ff003f00ff, 0x3fff00ff00ff003f, 0x40df00ff00ff00ff, + 0xdc00ff00cf00dc, 0x0, 0x8002000000000000, 0x1fff0000, 0x0, + 0x321080000008c400, 0xffff0000000043c0, 0x10, 0x0, 0x0, 0x0, 0x0, + 0x3ffffff0000, 0x0, 0x0, 0x0, 0x0, 0xffff000000000000, + 0x3fda15627fffffff, 0xaaaaaaaaaaaaaaaa, 0x8501aaaaaaaaa, + 0x20bfffffffff, 0x0, 0x0, 0x0, 0x0, 0x2aaaaaaaaaaa, 0xaaaaaa, 0x0, + 0xaaabaaa800000000, 0x95ffaaaaaaaaaaaa, 0x2aa000a50aa, + 0x700000000000000, 0x0, 0x0, 0x0, 0x0, 0xf8007f, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7fffffe, 0x0, 0x0, 0xffffff0000000000, 0xffff, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffc000000, 0xffffdfc000, + 0xebc000000ffffffc, 0xfffffc000000ffef, 0xffffffc000000f, + 0xffffffc0000, 0xfc000000ffffffc0, 0xffffc000000fffff, + 0xffffffc000000ff, 0xffffffc00000, 0x3ffffffc00, 0xf0000003f7fffffc, + 0xffc000000fdfffff, 0xffff0000003f7fff, 0xfffffc000000fdff, 0xbf7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); +//1472 bytes +enum upperCaseTrieEntries = TrieEntry!(bool, 8, 4, 9)([0x0, 0x20, 0x40], + [0x100, 0x80, 0x1e00], [0x402030202020100, 0x206020202020205, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3000200010000, 0x3000300030003, 0x3000300030004, 0x5000300030003, + 0x3000700030006, 0x3000800030003, 0x3000300030003, 0x3000300030003, + 0x3000300030003, 0x3000300030003, 0x3000300030003, 0x3000300030003, + 0x9000300030003, 0x3000300030003, 0x3000300030003, 0x3000300030003, + 0x3000300030003, 0x3000300030003, 0x3000300030003, 0xa000300030003, + 0x3000b00030003, 0x3000300030003, 0x3000300030003, 0x3000300030003, + 0x3000300030003, 0x3000300030003, 0xd000c00030003, 0x3000300030003, + 0x3000300030003, 0x3000300030003, 0x3000300030003, 0x3000300030003, + 0x0, 0x7fffffe, 0x0, 0x7f7fffff, 0xaa55555555555555, + 0x2b555555555554aa, 0x11aed2d5b1dbced6, 0x55d255554aaaa490, + 0x6c05555555555555, 0x557a, 0x0, 0x0, 0x0, 0x45000000000000, + 0xffbfffed740, 0xe6905555551c8000, 0xffffffffffff, 0x5555555500000000, + 0x5555555555555401, 0x5555555555552aab, 0xfffe005555555555, 0x7fffff, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xffffffff00000000, 0x20bf, 0x0, 0x0, 0x0, 0x0, 0x5555555555555555, + 0x5555555555555555, 0x5555555540155555, 0x5555555555555555, + 0xff00ff003f00ff00, 0xff00aa003f00, 0xf00000000000000, + 0xf001f000f000f00, 0x0, 0x0, 0x0, 0x0, 0xc00f3d503e273884, + 0xffff00000020, 0x8, 0x0, 0x0, 0x0, 0xffc0000000000000, 0xffff, 0x0, + 0x0, 0x0, 0x0, 0x7fffffffffff, 0xc025ea9d00000000, 0x5555555555555555, + 0x4280555555555, 0x0, 0x0, 0x0, 0x0, 0x0, 0x155555555555, 0x555555, + 0x0, 0x5554555400000000, 0x6a00555555555555, 0x55500052855, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x7fffffe00000000, 0x0, 0x0, 0x0, 0xffffffffff, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfff0000003ffffff, 0xffffff0000003fff, + 0x3fde64d0000003, 0x3ffffff0000, 0x7b0000001fdfe7b0, + 0xfffff0000001fc5f, 0x3ffffff0000003f, 0x3ffffff00000, + 0xf0000003ffffff00, 0xffff0000003fffff, 0xffffff00000003ff, + 0x7fffffc00000001, 0x1ffffff0000000, 0x7fffffc00000, 0x1ffffff0000, + 0x400, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); +//8704 bytes +enum simpleCaseTrieEntries = TrieEntry!(ushort, 8, 7, 6)([0x0, 0x20, 0x100], + [0x100, 0x380, 0xd00], [0x402030202020100, 0x202020202020205, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x3000200010000, 0x7000600050004, 0xa00090008, 0xd000c000b0000, + 0x110010000f000e, 0x1400130012, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x16001500000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x170000, 0x1b001a00190018, 0x1f001e001d001c, 0x0, + 0x2200210020, 0x0, 0x0, 0x24002300000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x28002700260025, 0x29, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2b002a0000, 0x2e002d002c, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x30002f, 0x0, + 0x0, 0x0, 0x0, 0x320031, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x2400220020ffff, 0x2c002a00280026, 0x72f00320030002e, + 0x3d003b00390037, 0x1b000430041003f, 0x4e004c004a0048, + 0xffff005400520050, 0xffffffffffffffff, 0x2500230021ffff, + 0x2d002b00290027, 0x73000330031002f, 0x3e003c003a0038, + 0x1b1004400420040, 0x4f004d004b0049, 0xffff005500530051, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffff043fffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xcc049800c800c6, + 0xd500d3014904aa, 0xdd00db00d900d7, 0xe500e300e100df, 0xed00eb00e900e7, + 0xffff00f300f100ef, 0xfb00f900f700f5, 0x6be010100ff00fd, + 0xcd049900c900c7, 0xd600d4014a04ab, 0xde00dc00da00d8, 0xe600e400e200e0, + 0xee00ec00ea00e8, 0xffff00f400f200f0, 0xfc00fa00f800f6, + 0x1a80102010000fe, 0x118011701160115, 0x11e011d011c011b, + 0x12401230120011f, 0x128012701260125, 0x12e012d012c012b, + 0x13401330130012f, 0x138013701360135, 0x13c013b013a0139, + 0x140013f013e013d, 0x144014301420141, 0x148014701460145, + 0x14f014e014d014c, 0x1510150ffffffff, 0x155015401530152, + 0x15801570156ffff, 0x15e015d015c0159, 0x16201610160015f, + 0x166016501640163, 0x1690168ffff0167, 0x16d016c016b016a, + 0x1710170016f016e, 0x175017401730172, 0x179017801770176, + 0x17d017c017b017a, 0x1830182017f017e, 0x18b018a01870186, + 0x1930192018f018e, 0x19b019a01970196, 0x1a301a2019f019e, + 0x1a701a601a501a4, 0x1ac01ab01aa01a9, 0x1b201af01ae01ad, + 0x1b601b501b3028b, 0x1bd01bb01ba01b9, 0x1c301c101bf01be, + 0x1c701c5ffff01c4, 0x1cd01cc01cb01c9, 0x1d301d1023b01cf, + 0xffff028301d601d5, 0x1db026901d901d7, 0x1e001df01de01dd, + 0x1e501e301e201e1, 0xffffffff01e701e6, 0x1ed01eb01ea01e9, + 0x1f301f101ef01ee, 0x1f701f601f501f4, 0xffffffff01fa01f9, + 0x23dffff01fc01fb, 0xffffffffffffffff, 0x206020202010200, + 0x20d020c02080207, 0x2110210020f020e, 0x215021402130212, + 0x219021802170216, 0x21d021c021b021a, 0x220021f01c6021e, + 0x226022502240223, 0x22a022902280227, 0x22e022d022c022b, + 0x23202310230022f, 0x23802370236ffff, 0x23e023c023a0239, + 0x24402430240023f, 0x248024702460245, 0x24c024b024a0249, + 0x250024f024e024d, 0x254025302520251, 0x258025702560255, + 0x25c025b025a0259, 0x260025f025e025d, 0x264026302620261, + 0x268026702660265, 0x26c026bffff026a, 0x270026f026e026d, + 0x274027302720271, 0x278027702760275, 0x27c027b027a0279, + 0xffffffffffffffff, 0x281027fffffffff, 0x2d7028502840282, + 0x28c028802870482, 0x2920291028f028d, 0x296029502940293, + 0x29c029b02980297, 0x1b402b70466046a, 0x1c201c0ffff01bc, + 0x1caffff01c8ffff, 0xffffffffffffffff, 0x1d0ffffffff01ce, + 0xffff05fa0748ffff, 0x528ffff01d201d4, 0x1d8ffffffffffff, + 0xffff01da02b3ffff, 0xffffffff01dcffff, 0xffffffffffffffff, + 0xffffffff02a3ffff, 0x1e8ffffffff01e4, 0xffffffffffffffff, + 0x1f201f0028e01ec, 0xffffffffffff0290, 0xffff01f8ffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffff083affff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x320031f031e031d, + 0x3240323ffffffff, 0x3d5ffffffffffff, 0xffffffff03d903d7, + 0xffffffffffffffff, 0xffff0329ffffffff, 0xffff0331032f032d, + 0x3370335ffff0333, 0x33e03950339ffff, 0x347034503cc0340, + 0x35403c2083b03c8, 0x35d035b03590440, 0x388ffff03c5039f, + 0x36f039c036a0368, 0x378037607100371, 0x3320330032e032a, + 0x33f0396033affff, 0x348034603cd0341, 0x35503c3083c03c9, + 0x35e035c035a0441, 0x38a038903c603a0, 0x370039d036b0369, + 0x379037707110372, 0x393033803360334, 0xffffffff03ca0397, + 0x39403a1039effff, 0x3a703a603a303a2, 0x3ab03aa03a903a8, + 0x3af03ae03ad03ac, 0x3b503b403b103b0, 0x3bd03bc03b903b8, + 0x3c103c003bf03be, 0xffff03d103c703c4, 0x3cfffff03ce03cb, + 0x3d403d303d203d0, 0x3da03d803d6ffff, 0x3e103df03dd03db, + 0x3e903e703e503e3, 0x3f103ef03ed03eb, 0x3f903f703f503f3, + 0x40103ff03fd03fb, 0x409040704050403, 0x411040f040d040b, + 0x419041704150413, 0x421041f041d041b, 0x429042704250423, + 0x431042f042d042b, 0x439043704350433, 0x402040003fe03fc, + 0x40a040804060404, 0x4120410040e040c, 0x41a041804160414, + 0x4220420041e041c, 0x42a042804260424, 0x4320430042e042c, + 0x43a043804360434, 0x3e203e003de03dc, 0x3ea03e803e603e4, + 0x3f203f003ee03ec, 0x3fa03f803f603f4, 0x453045204510450, + 0x459045804570456, 0x4610460045d045c, 0x469046804650464, + 0x4710470046d046c, 0x477047604730472, 0x47b047a04790478, + 0x4810480047d047c, 0xffffffff04850484, 0xffffffffffffffff, + 0x4950494ffffffff, 0x49b049a04970496, 0x49f049e049d049c, + 0x4a704a604a304a2, 0x4ad04ac04a904a8, 0x4b304b204b104b0, + 0x4b904b804b704b6, 0x4bf04be04bb04ba, 0x4c504c404c104c0, + 0x4cd04cc04c904c8, 0x4d304d204cf04ce, 0x4d704d604d504d4, + 0x4df04de04db04da, 0x4e704e604e304e2, 0x4f004ed04ec04ea, + 0x4f804f504f404f1, 0x50004fd04fc04f9, 0x4eb050505040501, + 0x50d050c050b050a, 0x5130512050f050e, 0x519051805170516, + 0x51f051e051d051c, 0x525052405210520, 0x52b052a05270526, + 0x52f052e052d052c, 0x537053605330532, 0x53d053c05390538, + 0x5410540053f053e, 0x547054605430542, 0x54b054a05490548, + 0x54f054e054d054c, 0x555055405510550, 0x559055805570556, + 0x55d055c055b055a, 0x5630562055f055e, 0x567056605650564, + 0x56b056a05690568, 0x5730572056f056e, 0x577057605750574, + 0x57b057a05790578, 0xffffffffffffffff, 0xffffffffffffffff, + 0x58405820580ffff, 0x58c058a05880586, 0x59405920590058e, + 0x59c059a05980596, 0x5a405a205a0059e, 0x5ac05aa05a805a6, + 0x5b405b205b005ae, 0x5bc05ba05b805b6, 0x5c405c205c005be, + 0xffff05ca05c805c6, 0xffffffffffffffff, 0xffffffffffffffff, + 0x58505830581ffff, 0x58d058b05890587, 0x59505930591058f, + 0x59d059b05990597, 0x5a505a305a1059f, 0x5ad05ab05a905a7, + 0x5b505b305b105af, 0x5bd05bb05b905b7, 0x5c505c305c105bf, + 0xffff05cb05c905c7, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x8c008a00880086, + 0x9400920090008e, 0x9c009a00980096, 0xa400a200a0009e, 0xac00aa00a800a6, + 0xb400b200b000ae, 0xbc00ba00b800b6, 0xc400c200c000be, + 0x4a000ca048e0486, 0x4c6ffff04b400ce, 0xffffffffffffffff, + 0xffffffff0508ffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffff07e8ffff, 0xffffffff0454ffff, 0x5ff05fe05fd05fc, + 0x605060406010600, 0x60b060a06090608, 0x6110610060f060e, + 0x617061606130612, 0x61d061c06190618, 0x6210620061f061e, + 0x627062606230622, 0x62b062a06290628, 0x62f062e062d062c, + 0x635063406310630, 0x639063806370636, 0x63d063c063b063a, + 0x6430642063f063e, 0x647064606450644, 0x64b064a06490648, + 0x6510650064d064c, 0x655065406530652, 0x65d065c06590658, + 0x6630662065f065e, 0x667066606650664, 0x66b066a06690668, + 0x6710670066d066c, 0x675067406730672, 0x67a067906bc06bb, + 0x680067f067c067b, 0x684068306820681, 0x688068706860685, + 0x68e068d068a0689, 0x69206910690068f, 0x698069706960695, + 0x69e069d069a0699, 0x6a206a106a0069f, 0x6a606a506a406a3, + 0x6ac06ab06a806a7, 0x6b006af06ae06ad, 0x6b406b306b206b1, + 0xffffffff06b606b5, 0x6bdffffffffffff, 0xffff06bfffffffff, + 0x6c306c206c106c0, 0x6c906c806c506c4, 0x6cd06cc06cb06ca, + 0x6d106d006cf06ce, 0x6d706d606d506d4, 0x6dd06dc06db06da, + 0x6e106e006df06de, 0x6e506e406e306e2, 0x6eb06ea06e906e8, + 0x6f106f006ef06ee, 0x6f506f406f306f2, 0x6f906f806f706f6, + 0x6fd06fc06fb06fa, 0x701070006ff06fe, 0x705070407030702, + 0x709070807070706, 0x70d070c070b070a, 0x7140713070f070e, + 0x718071707160715, 0x71e071d071c071b, 0x72207210720071f, + 0x726072507240723, 0x72a072907280727, 0x7330732072e072d, + 0x73c073a07380736, 0x74407420740073e, 0x73d073b07390737, + 0x74507430741073f, 0x750074e074c074a, 0xffffffff07540752, + 0x751074f074d074b, 0xffffffff07550753, 0x76a076807660764, + 0x7720770076e076c, 0x76b076907670765, 0x7730771076f076d, + 0x78a078807860784, 0x7920790078e078c, 0x78b078907870785, + 0x7930791078f078d, 0x7a207a0079e079c, 0xffffffff07a607a4, + 0x7a307a1079f079d, 0xffffffff07a707a5, 0x7baffff07b6ffff, + 0x7c2ffff07beffff, 0x7bbffff07b7ffff, 0x7c3ffff07bfffff, + 0x7d607d407d207d0, 0x7de07dc07da07d8, 0x7d707d507d307d1, + 0x7df07dd07db07d9, 0x840083e08360834, 0x84e084c08440842, + 0x858085608620860, 0xffffffff08660864, 0x7fa07f807f607f4, + 0x802080007fe07fc, 0x7fb07f907f707f5, 0x803080107ff07fd, + 0x80e080c080a0808, 0x816081408120810, 0x80f080d080b0809, + 0x817081508130811, 0x826082408220820, 0x82e082c082a0828, + 0x827082508230821, 0x82f082d082b0829, 0x838ffff08320830, + 0xffffffffffffffff, 0x837083508330831, 0xffff083dffff0839, + 0x846ffffffffffff, 0xffffffffffffffff, 0x84508430841083f, + 0xffffffffffff0847, 0xffffffff084a0848, 0xffffffffffffffff, + 0x84f084d084b0849, 0xffffffffffffffff, 0xffffffff08540852, + 0xffffffff085affff, 0x859085708550853, 0xffffffffffff085b, + 0x868ffffffffffff, 0xffffffffffffffff, 0x867086508630861, + 0xffffffffffff0869, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffff0712ffffffff, 0x14b0731ffffffff, + 0xffffffffffffffff, 0xffff0530ffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffff0531ffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x18402af0180029f, 0x18c005e018802c1, + 0x194006c01900064, 0x19c007e01980076, 0x18502b0018102a0, + 0x18d005f018902c2, 0x195006d01910065, 0x19d007f01990077, + 0x1b7ffffffffffff, 0xffffffffffff01b8, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x4d80444ffffffff, + 0x4e0044c04dc0446, 0x4e8047404e4045e, 0x204ee02d3086a, + 0x6e04f606c604f2, 0x10d04fe037a04fa, 0x51a0506061a0502, + 0x4dd044704d90445, 0x4e5045f04e1044d, 0x2d4086b04e90475, + 0x6c704f3000304ef, 0x37b04fb006f04f7, 0x61b0503010e04ff, + 0xffffffff051b0507, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xa000800040000, 0x160010000e000c, 0x2bb001c001a0018, 0x5602e702d102c7, + 0x66006200600058, 0x7800740070006a, 0x29900820080007c, + 0x6020084000607e0, 0x5d005ce02a7057c, 0x1070105010305de, + 0x1190113010f0109, 0xffff013101290121, 0xb000900050001, + 0x170011000f000d, 0x2bc001d001b0019, 0x5702e802d202c8, + 0x67006300610059, 0x7900750071006b, 0x29a00830081007d, + 0x6030085000707e1, 0x5d105cf02a8057d, 0x1080106010405df, + 0x11a01140110010a, 0xffff0132012a0122, 0x455052904c304c2, + 0x45a0286028002a4, 0x46202aa02a9045b, 0x46b02b404670463, + 0x2ba02b9ffff02b8, 0xffff02c002bfffff, 0xffffffffffffffff, + 0x48302d8ffffffff, 0x489048802e202e1, 0x48d048c048b048a, + 0x2fe02fd04910490, 0x30e030d03040303, 0x31a031903160315, + 0x328032703260325, 0x6ed06ec02fc02fb, 0x383038203810380, + 0x392039103870386, 0x3b303b203a503a4, 0x5cd05cc056d056c, + 0x5ed05ec05db05da, 0x6570656060d060c, 0x6e706e6043e043d, + 0x7830782072c072b, 0x694069307e307e2, 0x150014065b065a, + 0x4bd04bc005d005c, 0x5d505d404d104d0, 0x511051001a101a0, + 0x535053405230522, 0x553055205450544, 0x571057005610560, + 0x15b015a057f057e, 0x3bb03ba037d037c, 0xffffffffffffffff, + 0x5d2ffffffffffff, 0xffff05d905d805d3, 0x5e305e2ffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x8d008b00890087, 0x9500930091008f, 0x9d009b00990097, 0xa500a300a1009f, + 0xad00ab00a900a7, 0xb500b300b100af, 0xbd00bb00b900b7, 0xc500c300c100bf, + 0x4a100cb048f0487, 0x4c7ffff04b500cf, 0xffffffffffffffff, + 0xffffffff0509ffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x5d705d602c402c3, + 0x5e105e005dd05dc, 0x5e905e805e705e6, 0x5ef05ee05eb05ea, + 0x5f505f405f105f0, 0x308030705f905f8, 0x625062406150614, + 0x641064006330632, 0x6610660064f064e, 0x67e067d066f066e, + 0x69c069b068c068b, 0xffffffff06aa06a9, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x69006807350734, 0x75f075e027e027d, 0x390038f07770776, + 0x7b107b0001f001e, 0x2a202a107c707c6, 0x6b806b707e507e4, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x7590758ffffffff, 0x7610760075d075c, 0x2e002df02d602d5, + 0x2ee02ed02e602e5, 0x7790778ffffffff, 0x7810780077d077c, + 0x3140313030c030b, 0x322032103180317, 0x797079607950794, + 0x79b079a07990798, 0x3850384037f037e, 0x7a907a8038e038d, + 0x7ad07ac07ab07aa, 0x7b307b207af07ae, 0x7b907b807b507b4, + 0x7c107c007bd07bc, 0x7c907c807c507c4, 0x7cf07ce07cd07cc, + 0x47f047e046f046e, 0x4a504a404930492, 0xffffffffffffffff, + 0xffffffffffffffff, 0x7e605150514ffff, 0x7eb07ea07e907e7, + 0x7ef07ee07ed07ec, 0x7f307f207f107f0, 0x5f2ffffffffffff, + 0xffffffff074905f3, 0x807080608050804, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x81b081a08190818, + 0x81f081e081d081c, 0xffff05fb05f705f6, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x75a02c50756ffff, 0x76202cf02cd02cb, 0x2e3071902db06d2, + 0x2f107ca02e90448, 0x77a02f502f30774, 0x3050221077e02f9, + 0xffff043b030f007a, 0xffffffffffffffff, 0x75b02c60757ffff, + 0x76302d002ce02cc, 0x2e4071a02dc06d3, 0x2f207cb02ea0449, + 0x77b02f602f40775, 0x3060222077f02fa, 0xffff043c0310007b, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x72005a085c0012, 0x11106b9032b0311, 0x2dd029d02ab05e4, + 0x10b060602ef085e, 0x4ca028902d902a5, 0x2c902bd02b502ad, + 0x30902f702eb0746, 0x38b02b10241031b, 0x442053a044a03b6, + 0x85004ae06d8044e, 0x73005b085d0013, 0x11206ba032c0312, + 0x2de029e02ac05e5, 0x10c060702f0085f, 0x4cb028a02da02a6, + 0x2ca02be02b602ae, 0x30a02f802ec0747, 0x38c02b20242031c, + 0x443053b044b03b7, 0x85104af06d9044f, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff]); +//8832 bytes +enum fullCaseTrieEntries = TrieEntry!(ushort, 8, 7, 6)([0x0, 0x20, 0x100], + [0x100, 0x380, 0xd40], [0x402030202020100, 0x202020202020205, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x3000200010000, 0x7000600050004, 0xa00090008, 0xd000c000b0000, + 0x110010000f000e, 0x1400130012, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x16001500000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x170000, 0x1b001a00190018, 0x1f001e001d001c, 0x0, + 0x2200210020, 0x0, 0x0, 0x24002300000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x28002700260025, 0x29, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2b002a0000, 0x2e002d002c, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f, 0x0, 0x0, 0x0, 0x310030, 0x0, + 0x0, 0x0, 0x0, 0x330032, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x2400220020ffff, 0x2c002a00280026, 0x78100320030002e, + 0x3d003b00390037, 0x1b900430041003f, 0x4e004c004a0048, + 0xffff005400520050, 0xffffffffffffffff, 0x2500230021ffff, + 0x2d002b00290027, 0x78200330031002f, 0x3e003c003a0038, + 0x1ba004400420040, 0x4f004d004b0049, 0xffff005500530051, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffff0470ffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xcc04c900c800c6, + 0xd500d3014e04db, 0xdd00db00d900d7, 0xe500e300e100df, 0xed00eb00e900e7, + 0xffff00f300f100ef, 0xfb00f900f700f5, 0x70f010100ff00fd, + 0xcd04ca00c900c7, 0xd600d4014f04dc, 0xde00dc00da00d8, 0xe600e400e200e0, + 0xee00ec00ea00e8, 0xffff00f400f200f0, 0xfc00fa00f800f6, + 0x1b10102010000fe, 0x11b011a01190118, 0x1210120011f011e, + 0x127012601230122, 0x12b012a01290128, 0x1310130012f012e, + 0x137013601330132, 0x13b013a01390138, 0x13f013e013d013c, + 0x143014201410140, 0x149014801470146, 0x14d014c014b014a, + 0x154015301520151, 0x1580157ffff0155, 0x15c015b015a0159, + 0x15f015e015dffff, 0x165016401630160, 0x169016801670166, + 0x16d016c016b016a, 0x1720171016f016e, 0x176017501740173, + 0x17a017901780177, 0x17e017d017c017b, 0x18201810180017f, + 0x186018501840183, 0x18c018b01880187, 0x19401930190018f, + 0x19c019b01980197, 0x1a401a301a0019f, 0x1ac01ab01a801a7, + 0x1b001af01ae01ad, 0x1b501b401b301b2, 0x1bb01b801b701b6, + 0x1bf01be01bc029c, 0x1c601c401c301c2, 0x1cc01ca01c801c7, + 0x1d001ceffff01cd, 0x1d601d501d401d2, 0x1dc01da024801d8, + 0xffff029401df01de, 0x1e6027801e201e0, 0x1eb01ea01e901e8, + 0x1f001ee01ed01ec, 0xffffffff01f201f1, 0x1f801f601f501f4, + 0x1fe01fc01fa01f9, 0x2020201020001ff, 0xffffffff02050204, + 0x24affff02070206, 0xffffffffffffffff, 0x211020d020c020b, + 0x218021702130212, 0x21c021b021a0219, 0x220021f021e021d, + 0x224022302220221, 0x228022702260225, 0x22b022a01cf0229, + 0x2310230022f022e, 0x235023402330232, 0x239023802370236, + 0x23d023c023b023a, 0x24502440243023e, 0x24b024902470246, + 0x2510250024d024c, 0x255025402530252, 0x259025802570256, + 0x25d025c025b025a, 0x263026202610260, 0x267026602650264, + 0x26b026a02690268, 0x26f026e026d026c, 0x273027202710270, + 0x277027602750274, 0x27b027affff0279, 0x27f027e027d027c, + 0x285028402810280, 0x289028802870286, 0x28d028c028b028a, + 0xffffffffffffffff, 0x2920290ffffffff, 0x2ec029602950293, + 0x29d0299029804b3, 0x2a302a202a0029e, 0x2a702a602a502a4, + 0x2ad02ac02a902a8, 0x1bd02ca0497049b, 0x1cb01c9ffff01c5, + 0x1d3ffff01d1ffff, 0xffffffffffffffff, 0x1d9ffffffff01d7, + 0xffff0643079affff, 0x559ffff01db01dd, 0x1e1ffffffffffff, + 0xffff01e302c6ffff, 0xffffffff01e7ffff, 0xffffffffffffffff, + 0xffffffff02b4ffff, 0x1f3ffffffff01ef, 0xffffffffffffffff, + 0x1fd01fb029f01f7, 0xffffffffffff02a1, 0xffff0203ffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffff08e4ffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x347034603450344, + 0x34b034affffffff, 0x406ffffffffffff, 0xffffffff040a0408, + 0xffffffffffffffff, 0xffff0350ffffffff, 0xffff035803560354, + 0x35e035cffff035a, 0x36803c203630902, 0x371036f03fd036a, + 0x37e03f308e503f9, 0x387038503830471, 0x3b5ffff03f603cc, + 0x39903c903940392, 0x3a203a00762039b, 0x359035703550351, + 0x36903c303640915, 0x372037003fe036b, 0x37f03f408e603fa, + 0x388038603840472, 0x3b703b603f703cd, 0x39a03ca03950393, + 0x3a303a10763039c, 0x3c0035f035d035b, 0xffffffff03fb03c4, + 0x3c103ce03cbffff, 0x3d403d303d003cf, 0x3d803d703d603d5, + 0x3de03dd03da03d9, 0x3e403e303e003df, 0x3ec03eb03e803e7, + 0x3f203f103ee03ed, 0xffff040203f803f5, 0x400ffff03ff03fc, + 0x405040404030401, 0x40b04090407ffff, 0x4120410040e040c, + 0x41a041804160414, 0x4220420041e041c, 0x42a042804260424, + 0x4320430042e042c, 0x43a043804360434, 0x4420440043e043c, + 0x44a044804460444, 0x4520450044e044c, 0x45a045804560454, + 0x4620460045e045c, 0x46a046804660464, 0x4330431042f042d, + 0x43b043904370435, 0x4430441043f043d, 0x44b044904470445, + 0x4530451044f044d, 0x45b045904570455, 0x4630461045f045d, + 0x46b046904670465, 0x4130411040f040d, 0x41b041904170415, + 0x4230421041f041d, 0x42b042904270425, 0x484048304820481, + 0x48a048904880487, 0x4920491048e048d, 0x49a049904960495, + 0x4a204a1049e049d, 0x4a804a704a404a3, 0x4ac04ab04aa04a9, + 0x4b204b104ae04ad, 0xffffffff04b604b5, 0xffffffffffffffff, + 0x4c604c5ffffffff, 0x4cc04cb04c804c7, 0x4d004cf04ce04cd, + 0x4d804d704d404d3, 0x4de04dd04da04d9, 0x4e404e304e204e1, + 0x4ea04e904e804e7, 0x4f004ef04ec04eb, 0x4f604f504f204f1, + 0x4fe04fd04fa04f9, 0x5040503050004ff, 0x508050705060505, + 0x510050f050c050b, 0x518051705140513, 0x521051e051d051b, + 0x529052605250522, 0x531052e052d052a, 0x51c053605350532, + 0x53e053d053c053b, 0x54405430540053f, 0x54a054905480547, + 0x550054f054e054d, 0x556055505520551, 0x55c055b05580557, + 0x560055f055e055d, 0x568056705640563, 0x56e056d056a0569, + 0x57205710570056f, 0x578057705740573, 0x57c057b057a0579, + 0x5820581057e057d, 0x588058705840583, 0x58c058b058a0589, + 0x5920591058e058d, 0x598059705940593, 0x59c059b059a0599, + 0x5a205a1059e059d, 0x5aa05a905a605a5, 0x5ae05ad05ac05ab, + 0x5b405b305b005af, 0xffffffffffffffff, 0xffffffffffffffff, + 0x5bd05bb05b9ffff, 0x5c505c305c105bf, 0x5cd05cb05c905c7, + 0x5d505d305d105cf, 0x5dd05db05d905d7, 0x5e505e305e105df, + 0x5ed05eb05e905e7, 0x5f505f305f105ef, 0x5fd05fb05f905f7, + 0xffff0603060105ff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x5be05bc05baffff, 0x5c605c405c205c0, 0x5ce05cc05ca05c8, + 0x5d605d405d205d0, 0x5de05dc05da05d8, 0x5e605e405e205e0, + 0x5ee05ec05ea05e8, 0x5f605f405f205f0, 0x5fe05fc05fa05f8, + 0x613060406020600, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x8c008a00880086, + 0x9400920090008e, 0x9c009a00980096, 0xa400a200a0009e, 0xac00aa00a800a6, + 0xb400b200b000ae, 0xbc00ba00b800b6, 0xc400c200c000be, + 0x4d100ca04bf04b7, 0x4f7ffff04e500ce, 0xffffffffffffffff, + 0xffffffff0539ffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffff083affff, 0xffffffff0485ffff, 0x648064706460645, + 0x64e064d064a0649, 0x654065306520651, 0x65a065906580657, + 0x660065f065c065b, 0x666066506620661, 0x66a066906680667, + 0x670066f066c066b, 0x674067306720671, 0x678067706760675, + 0x67e067d067a0679, 0x68206810680067f, 0x686068506840683, + 0x68c068b06880687, 0x690068f068e068d, 0x694069306920691, + 0x69a069906960695, 0x69e069d069c069b, 0x6a606a506a206a1, + 0x6ac06ab06a806a7, 0x6b006af06ae06ad, 0x6b406b306b206b1, + 0x6ba06b906b606b5, 0x6be06bd06bc06bb, 0x6c306c2070d070c, + 0x6cb06ca06c706c6, 0x6cf06ce06cd06cc, 0x6d306d206d106d0, + 0x6d906d806d506d4, 0x6dd06dc06db06da, 0x6e306e206e106e0, + 0x6e906e806e506e4, 0x6ed06ec06eb06ea, 0x6f106f006ef06ee, + 0x6f706f606f306f2, 0x6fb06fa06f906f8, 0x6ff06fe06fd06fc, + 0x704070207010700, 0x70e070a07080706, 0xffff0710ffffffff, + 0x715071407130712, 0x71b071a07170716, 0x71f071e071d071c, + 0x723072207210720, 0x729072807270726, 0x72f072e072d072c, + 0x733073207310730, 0x737073607350734, 0x73d073c073b073a, + 0x743074207410740, 0x747074607450744, 0x74b074a07490748, + 0x74f074e074d074c, 0x753075207510750, 0x757075607550754, + 0x75b075a07590758, 0x75f075e075d075c, 0x766076507610760, + 0x76a076907680767, 0x770076f076e076d, 0x774077307720771, + 0x778077707760775, 0x77c077b077a0779, 0x78507840780077f, + 0x78e078c078a0788, 0x796079407920790, 0x78f078d078b0789, + 0x797079507930791, 0x7a207a0079e079c, 0xffffffff07a607a4, + 0x7a307a1079f079d, 0xffffffff07a707a5, 0x7bc07ba07b807b6, + 0x7c407c207c007be, 0x7bd07bb07b907b7, 0x7c507c307c107bf, + 0x7dc07da07d807d6, 0x7e407e207e007de, 0x7dd07db07d907d7, + 0x7e507e307e107df, 0x7f407f207f007ee, 0xffffffff07f807f6, + 0x7f507f307f107ef, 0xffffffff07f907f7, 0x80c07fe080807fc, + 0x814080408100800, 0x80dffff0809ffff, 0x815ffff0811ffff, + 0x828082608240822, 0x830082e082c082a, 0x829082708250823, + 0x831082f082d082b, 0x8f708f508df08dd, 0x90f090d08fb08f9, + 0x924092209370935, 0xffffffff093b0939, 0x85f085c08590856, + 0x86b086808650862, 0x860085d085a0857, 0x86c086908660863, + 0x88f088c08890886, 0x89b089808950892, 0x890088d088a0887, + 0x89c089908960893, 0x8bf08bc08b908b6, 0x8cb08c808c508c2, + 0x8c008bd08ba08b7, 0x8cc08c908c608c3, 0x8e108ce08db08d9, + 0x8d708d5ffff08d3, 0x8e008de08dc08da, 0xffff08e7ffff08e2, + 0x8fd08e8ffffffff, 0x8f308f1ffff08ed, 0x8fc08fa08f808f6, + 0xffffffffffff08fe, 0x9030900090b0909, 0x9070905ffffffff, + 0x910090e090c090a, 0xffffffffffffffff, 0x91609130920091e, + 0x91c091a09260918, 0x92509230921091f, 0xffffffffffff0927, + 0x93d092affffffff, 0x9330931ffff092f, 0x93c093a09380936, + 0xffffffffffff093e, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffff0764ffffffff, 0x1500783ffffffff, + 0xffffffffffffffff, 0xffff0561ffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffff0562ffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x18d02c2018902b0, 0x195005e019102d6, + 0x19d006c01990064, 0x1a5007e01a10076, 0x18e02c3018a02b1, + 0x196005f019202d7, 0x19e006d019a0065, 0x1a6007f01a20077, + 0x1c0ffffffffffff, 0xffffffffffff01c1, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x5090475ffffffff, + 0x511047d050d0477, 0x51904a50515048f, 0x2051f02e80940, + 0x6e052707180523, 0x110052f03a4052b, 0x54b053706630533, + 0x50e0478050a0476, 0x51604900512047e, 0x2e90941051a04a6, + 0x719052400030520, 0x3a5052c006f0528, 0x664053401110530, + 0xffffffff054c0538, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xa000800040000, 0x160010000e000c, 0x2ce001c001a0018, 0x56030802e602dc, + 0x66006200600058, 0x7800740070006a, 0x2aa00820080007c, + 0x64b008400060832, 0x60d060902b805b5, 0x10801060629061d, + 0x11c01160112010a, 0xffff0134012c0124, 0xb000900050001, + 0x170011000f000d, 0x2cf001d001b0019, 0x57030902e702dd, + 0x67006300610059, 0x7900750071006b, 0x2ab00830081007d, + 0x64c008500070833, 0x60e060a02b905b6, 0x1090107062a061e, + 0x11d01170113010b, 0xffff0135012d0125, 0x486055a04f404f3, + 0x48b0297029102b5, 0x49302bb02ba048c, 0x49c02c704980494, + 0x2cd02ccffff02cb, 0xffff02d502d4ffff, 0xffffffffffffffff, + 0x4b402edffffffff, 0x4ba04b902f902f8, 0x4be04bd04bc04bb, + 0x325032404c204c1, 0x3350334032b032a, 0x3410340033d033c, + 0x34f034e034d034c, 0x73f073e03230322, 0x3b003af03ae03ad, + 0x3bf03be03b403b3, 0x3e203e103d203d1, 0x606060505a405a3, + 0x6320631061a0619, 0x6a0069f06560655, 0x7390738046f046e, + 0x7d507d4077e077d, 0x6df06de08350834, 0x15001406a406a3, + 0x4ee04ed005d005c, 0x612061105020501, 0x542054101aa01a9, + 0x566056505540553, 0x586058505760575, 0x5a805a705960595, + 0x162016105b805b7, 0x3ea03e903a703a6, 0xffffffffffffffff, + 0x60fffffffffffff, 0xffff061806170610, 0x6240623ffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x8d008b00890087, 0x9500930091008f, 0x9d009b00990097, 0xa500a300a1009f, + 0xad00ab00a900a7, 0xb500b300b100af, 0xbd00bb00b900b7, 0xc500c300c100bf, + 0x4d200cb04c004b8, 0x4f8ffff04e600cf, 0xffffffffffffffff, + 0xffffffff053affff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x616061502d902d8, + 0x6220621061c061b, 0x1e501e406280627, 0x6340633062e062d, + 0x63e063d06380637, 0x32f032e06420641, 0x66e066d065e065d, + 0x68a0689067c067b, 0x6aa06a906980697, 0x6c906c806b806b7, + 0x6e706e606d706d6, 0xffffffff06f506f4, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x69006807870786, 0x7b107b0028f028e, 0x3bd03bc07c907c8, + 0x8030802001f001e, 0x2b302b208190818, 0x2d302d208370836, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x7ab07aaffffffff, 0x7b307b207af07ae, 0x2f502f402eb02ea, + 0x311031003070306, 0x7cb07caffffffff, 0x7d307d207cf07ce, + 0x33b033a03330332, 0x3490348033f033e, 0x7e907e807e707e6, + 0x7ed07ec07eb07ea, 0x3b203b103ac03ab, 0x7fb07fa03bb03ba, + 0x3f003ef03dc03db, 0x28302820620061f, 0x80b080a08070806, + 0x8130812080f080e, 0x81b081a08170816, 0x8210820081f081e, + 0x4b004af04a0049f, 0x4d604d504c404c3, 0xffffffffffffffff, + 0xffffffffffffffff, 0x83805460545ffff, 0x83d083c083b0839, + 0x590058f0580057f, 0x5b205b105a0059f, 0x63bffffffffffff, + 0xffffffff079b063c, 0x60c060b06080607, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x630062f062c062b, + 0x63a063906360635, 0xffff06440640063f, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x2fc02fa025e02f6, 0xffff0304030302fe, + 0xffffffffffffffff, 0xffffffffffffffff, 0x30cffffffffffff, + 0x314031202c0030e, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x7ac02da07a8ffff, 0x7b402e402e202e0, + 0x144076b02f00724, 0x318081c030a0479, 0x7cc031c031a07c6, + 0x32c022c07d00320, 0xffff046c0336007a, 0xffffffffffffffff, + 0x7ad02db07a9ffff, 0x7b502e502e302e1, 0x145076c02f10725, + 0x319081d030b047a, 0x7cd031d031b07c7, 0x32d022d07d10321, + 0xffff046d0337007b, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x72005a09280012, 0x114010c03520338, + 0x2f202ae02bc0625, 0x10e064f031608ef, 0x4fb029a02ee02b6, + 0x2de02d002c802be, 0x330031e047f0798, 0x3b802c4024e0342, + 0x473056b047b03e5, 0x91104df072a06c4, 0x73005b09290013, + 0x115010d03530339, 0x2f302af02bd0626, 0x10f0650031708f0, + 0x4fc029b02ef02b7, 0x2df02d102c902bf, 0x331031f04800799, + 0x3b902c5024f0343, 0x474056c047c03e6, 0x91204e0072b06c5, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff]); +//4000 bytes +enum alphaTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x20, 0xb0], [0x100, + 0x240, 0x5100], [0x706050403020100, 0xe0d0c0a0b0a0908, + 0x100a0f0303030303, 0xa0a0a0a0a0a0a0a, 0xa0a0a0a0a0a0a0a, + 0xa0a0a0a0a0a0a0a, 0xa0a0a0a0a0a0a0a, 0xa0a0a0a0a0a0a0a, + 0xa0a0a0a0a0a0a0a, 0xa0a0a0a0a0a0a0a, 0xa0a0a0a0a0a0a0a, + 0xa0a0a0a0a0a0a0a, 0xa0a0a0a0a0a0a0a, 0xa0a0a0a0a0a0a0a, + 0xa0a0a0a0a0a0a0a, 0xa0a0a0a0a0a0a0a, 0xa0a0a0a0a0a0a0a, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3000200010000, 0x7000600050004, 0xb000a00090008, 0xf000e000d000c, + 0x12001100010010, 0x15001400010013, 0x19001800170016, 0x1c0001001b001a, + 0x1f001f001e001d, 0x1f001f001f0020, 0x1f001f001f001f, 0x1f002300220021, + 0x1f001f00250024, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100260001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x27000100010001, + 0x1000100010001, 0x2a002900010028, 0x2e002d002c002b, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x2f000100010001, 0x1f001f001f001f, 0x1f001f001f001f, + 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, + 0x1f001f001f001f, 0x1f001f001f001f, 0x3100300001001f, 0x34003300320001, + 0x38003700360035, 0x1f001f001f0039, 0x3d003c003b003a, 0x1f001f001f003e, + 0x1f001f0040003f, 0x1f0041001f001f, 0x1f001f001f001f, 0x1f001f001f001f, + 0x42000100010001, 0x1f001f001f0043, 0x1f001f001f001f, 0x1f001f001f001f, + 0x1000100010001, 0x1f001f001f0044, 0x1f001f001f001f, 0x1f001f001f001f, + 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, + 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, + 0x1f001f001f001f, 0x1f001f001f001f, 0x1f004500010001, 0x46001f001f001f, + 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, + 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, + 0x1f001f001f0047, 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, + 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, + 0x1f001f001f001f, 0x4b004a00490048, 0x1f001f001f001f, 0x1f001f001f001f, + 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, 0x1f004c001f001f, + 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, + 0x1000100010001, 0x1004d00010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x4e000100010001, 0x1f001f001f004f, 0x1f001f001f001f, + 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, + 0x1f001f001f001f, 0x1f001f001f001f, 0x1f004f00010001, 0x1f001f001f001f, + 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, + 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, 0x1f001f001f001f, + 0x0, 0x7fffffe07fffffe, 0x420040000000000, 0xff7fffffff7fffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x501f0003ffc3, 0x0, 0x3cdf000000000020, + 0xfffffffbffffd740, 0xffbfffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xfffffffffffffc03, 0xffffffffffffffff, + 0xfffe00ffffffffff, 0xfffffffe027fffff, 0xbfff0000000000ff, + 0x707ffffff00b6, 0xffffffff07ff0000, 0xffffc000feffffff, + 0xffffffffffffffff, 0x9c00e1fe1fefffff, 0xffffffffffff0000, + 0xffffffffffffe000, 0x3ffffffffffff, 0x43007fffffffc00, 0x1ffffcffffff, + 0x1ffffff, 0x1ffd00000000, 0x7fff03f000000000, 0xefffffffffffffff, + 0xfefe000fffe1dfff, 0xe3c5fdfffff99fee, 0x3000fb080599f, + 0xc36dfdfffff987ee, 0x3f00005e021987, 0xe3edfdfffffbbfee, 0xf00011bbf, + 0xe3edfdfffff99fee, 0x2000fb0c0199f, 0xc3ffc718d63dc7ec, 0x811dc7, + 0xe3effdfffffddfee, 0xf03601ddf, 0xe3effdfffffddfec, 0x6000f40601ddf, + 0xe7fffffffffddfec, 0xfc00000f00805ddf, 0x2ffbfffffc7fffec, + 0xc0000ff5f807f, 0x7fffffffffffffe, 0x207f, 0x3bffecaefef02596, + 0xf000205f, 0x1, 0xfffe1ffffffffeff, 0x1ffffffffeffff03, 0x0, + 0xf97fffffffffffff, 0xffffc1e7ffff0000, 0xffffffff3000407f, + 0xf7ffffffffff20bf, 0xffffffffffffffff, 0xffffffff3d7f3dff, + 0x7f3dffffffff3dff, 0xffffffffff7fff3d, 0xffffffffff3dffff, 0x87ffffff, + 0xffffffff0000ffff, 0x1fffffffffffff, 0xfffffffffffffffe, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffff9fffffffffff, 0xffffffff07fffffe, + 0x1c7ffffffffff, 0xfffff000fdfff, 0xddfff000fffff, 0xffcfffffffffffff, + 0x108001ff, 0xffffffff00000000, 0xffffffffffffff, 0xffff07ffffffffff, + 0x3fffffffffffff, 0x1ff0fff1fffffff, 0x1f3fffffff0000, + 0xffff0fffffffffff, 0x3ff, 0xffffffff0fffffff, 0x1ffffe7fffffff, + 0x8000000000, 0x0, 0xffefffffffffffff, 0xfef, 0xfc00f3ffffffffff, + 0x3ffbfffffffff, 0x3fffffffffffff, 0x3ffffffffc00e000, 0x0, + 0x6fde0000000000, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x0, 0xffffffff3f3fffff, 0x3fffffffaaff3f3f, + 0x5fdfffffffffffff, 0x1fdc1fff0fcf1fdc, 0x0, 0x8002000000000000, + 0x1fff0000, 0x0, 0xf3ffbd503e2ffc84, 0xffffffff000043e0, 0x1ff, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffc0000000000000, 0x3ffffffffff, + 0xffff7fffffffffff, 0xffffffff7fffffff, 0xffffffffffffffff, + 0xc781fffffffff, 0xffff20bfffffffff, 0x80ffffffffff, + 0x7f7f7f7f007fffff, 0xffffffff7f7f7f7f, 0x800000000000, 0x0, 0x0, 0x0, + 0x1f3e03fe000000e0, 0xfffffffffffffffe, 0xfffffffee07fffff, + 0xf7ffffffffffffff, 0xfffe3fffffffffe0, 0xffffffffffffffff, + 0x7ffffff00007fff, 0xffff000000000000, 0xffffffffffffffff, + 0xffffffffffffffff, 0x3fffffffffffff, 0x0, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x1fff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x1fff, 0x3fffffffffff0000, 0xc00ffff1fff, + 0x8ff07fffffffffff, 0xffffffff80ffffff, 0xffffffffffff, + 0xfffffffcff800000, 0xffffffffffffffff, 0x7ff000f79ff, + 0xff00000000000000, 0xfffffff7bb, 0xfffffffffffff, 0xffffffffffffffff, + 0x8fc00000000000f, 0xffff07fffffffc00, 0x1fffffff0007ffff, + 0xfff7ffffffffffff, 0x8000, 0x7fffffffffffff, 0x47fffff00003fff, + 0x7fffffffffffffff, 0x3cffff38000005, 0x7f7f007e7e7e, 0x0, 0x0, + 0x7ffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffff000fffffffff, 0xffffffffffff87f, 0xffffffffffffffff, + 0xffff3fffffffffff, 0xffffffffffffffff, 0x3ffffff, 0x5f7ffdffe0f8007f, + 0xffffffffffffffdb, 0x3ffffffffffff, 0xfffffffffff80000, + 0x3fffffffffffffff, 0xffffffffffff0000, 0xfffffffffffcffff, + 0xfff0000000000ff, 0x0, 0xffdf000000000000, 0xffffffffffffffff, + 0x1fffffffffffffff, 0x7fffffe00000000, 0xffffffc007fffffe, + 0x7fffffffffffffff, 0x1cfcfcfc, 0xb7ffff7fffffefff, 0x3fff3fff, + 0xffffffffffffffff, 0x7ffffffffffffff, 0x0, 0x1fffffffffffff, 0x0, 0x0, + 0x0, 0x0, 0xffffffff1fffffff, 0x1ffff, 0xffff00007fffffff, 0x7ff, + 0xffffffff3fffffff, 0x3eff0f, 0xffffffffffffffff, 0xffffffffffffffff, + 0x3fffffff, 0x0, 0x91bffffffffffd3f, 0x3fffff, 0x0, 0x0, + 0x3ffffff003fffff, 0x0, 0xc0ffffffffffffff, 0x0, 0xffffffeeff06f, + 0x1fffffff00000000, 0x0, 0x0, 0x3fffffffffffff, 0x7ffff003fffff, 0x0, + 0x0, 0xffffffffffffffff, 0x1ff, 0x0, 0x0, 0xffffffffffffffff, 0x3f, + 0x1fffffffffffffc, 0x1ffffff0000, 0x7ffffffffffff, 0x0, + 0xffffffffffffffff, 0x1e, 0x0, 0x0, 0x3fffffffffffff, 0x0, + 0xffffffffffffffff, 0x7fffffffffff, 0x0, 0x0, 0xffffffffffffffff, + 0x7ffffffff, 0x0, 0x0, 0x7fffffffffff, 0x0, 0x0, 0x0, + 0x1ffffffffffffff, 0x0, 0x0, 0x0, 0xffffffffffffffff, + 0x7fffffffffff001f, 0xfff80000, 0x0, 0x3, 0x0, 0x0, 0x0, + 0xffffffffffffffff, 0xffffffffffdfffff, 0xebffde64dfffffff, + 0xffffffffffffffef, 0x7bffffffdfdfe7bf, 0xfffffffffffdfc5f, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffff3fffffffff, 0xf7fffffff7fffffd, + 0xffdfffffffdfffff, 0xffff7fffffff7fff, 0xfffffdfffffffdff, 0xff7, + 0xaf7fe96ffffffef, 0x5ef7f796aa96ea84, 0xffffbee0ffffbff, 0x0, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffff, + 0x1fffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x3fffffff, 0x0, 0x0, 0x0, 0x3fffffff, 0x0, 0x0, 0x0]); +//2304 bytes +enum markTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x20, 0x70], [0x100, + 0x140, 0x2c00], [0x402030202020100, 0x207020206020205, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020208, 0x202020202020202, 0x202020202020202, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1000000000000, 0x5000400030002, 0x9000800070006, 0xd000c000b000a, + 0xf00000000000e, 0x10000000000000, 0x14001300120011, 0x160015, 0x17, + 0x0, 0x0, 0x190018, 0x1a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1b00000000, 0x1f001e001d001c, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20000000000000, 0x2100000000, 0x220000, + 0x0, 0x2300000000, 0x0, 0x250024, 0x2600000000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x27000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2900280000, 0x0, 0x0, 0x0, 0x2a0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xffffffffffffffff, 0xffffffffffff, 0x0, 0x0, 0x0, 0x0, 0x3f8, 0x0, + 0x0, 0x0, 0xbffffffffffe0000, 0xb6, 0x7ff0000, 0x10000fffff800, 0x0, + 0x3d9f9fc00000, 0xffff000000020000, 0x7ff, 0x1ffc000000000, + 0xff80000000000, 0x3eeffbc00000, 0xe000000, 0x0, 0x7ffffff000000000, + 0xdc0000000000000f, 0xc00feffff, 0xd00000000000000e, 0xc0080399f, + 0xd00000000000000e, 0x23000000023987, 0xd00000000000000e, 0xc00003bbf, + 0xd00000000000000e, 0xc00c0399f, 0xc000000000000004, 0x803dc7, + 0xc00000000000000e, 0xc00603ddf, 0xd00000000000000c, 0xc00603ddf, + 0xc00000000000000c, 0xc00803ddf, 0xc, 0xc0000ff5f8400, + 0x7f2000000000000, 0x7f80, 0x1bf2000000000000, 0x3f00, + 0xc2a0000003000000, 0xfffe000000000000, 0x1ffffffffeffe0df, 0x40, + 0x7ffff80000000000, 0x1e3f9dc3c00000, 0x3c00bffc, 0x0, 0x0, 0xe0000000, + 0x0, 0x0, 0x1c0000001c0000, 0xc0000000c0000, 0xfff0000000000000, + 0x200fffff, 0x3800, 0x0, 0x20000000000, 0x0, 0xfff0fff00000000, 0x0, + 0xffff000000000000, 0x301, 0xf800000, 0x9fffffff7fe00000, 0x0, 0x0, + 0xfff000000000001f, 0xff8000000001f, 0x3ffe00000007, 0xfffc000000000, + 0xfffff000000000, 0x0, 0x0, 0x1c21fffff70000, 0x0, 0x0, 0x0, + 0xf000007fffffffff, 0x0, 0x0, 0x0, 0x1ffffffff0000, 0x0, 0x0, 0x0, + 0x3800000000000, 0x0, 0x8000000000000000, 0x0, 0xffffffff00000000, + 0xfc0000000000, 0x0, 0x6000000, 0x0, 0x0, 0x3ff7800000000000, + 0x80000000, 0x3000000000000, 0xf800000844, 0x0, 0xfff0000000000003, + 0x3ffff0000001f, 0x3fc000000000, 0xfff80, 0xfff800000000000f, 0x1, + 0x7ffe0000000000, 0x800000000003008, 0xc19d000000000000, + 0x60f80000000002, 0x0, 0x0, 0x0, 0x37f800000000, 0x40000000, 0x0, 0x0, + 0x0, 0x7f0000ffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2000000000000000, + 0x870000000000f06e, 0x0, 0x0, 0x0, 0xff00000000000007, 0x7f, + 0x7ff000000000007, 0x0, 0x1fff8000000007, 0x0, 0xfff8000000000007, 0x1, + 0x0, 0x0, 0xfff80000000000, 0x0, 0x0, 0x7ffffffffffe0000, 0x78000, 0x0, + 0x0, 0xf807e3e000000000, 0x3c0000000fe7, 0x0, 0x0, 0x1c, 0x0, 0x0, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffff, 0x0, 0x0, 0x0, 0x0]); +//2384 bytes +enum numberTrieEntries = TrieEntry!(bool, 8, 6, 7)([0x0, 0x20, 0xc0], [0x100, + 0x280, 0x1a80], [0x402030202020100, 0x807020202020605, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2000200010000, 0x2000200020002, 0x2000200020002, 0x5000200040003, + 0x7000600020002, 0x9000800060006, 0x2000b0006000a, 0x2000d000c000c, + 0x20002000e0005, 0x2000f00020002, 0x2000200020002, 0x11000200100002, + 0x1300120002000e, 0xc00140002, 0x2000200020015, 0x2000200020002, + 0x19001800170016, 0x2000200020002, 0x20002001b001a, 0x1d001c00020002, + 0x2000200020002, 0x2000200020002, 0x20002001e0002, 0x2000200020002, + 0x2000020002001f, 0x2000200220021, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200060023, + 0xc0017000c0024, 0x400020002000c, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000e00020002, + 0x26002500020002, 0x28002700020002, 0x2000200230002, 0x2000200020002, + 0x2002a00020029, 0x2002c0002002b, 0x2000200020002, 0x200020002002d, + 0xc002f0004002e, 0x2000200020002, 0x2000200020002, 0x2000200050002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020030, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2003100020002, 0x2000200020002, 0x32000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2003300020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x2000200020002, 0x2000200020002, 0x2000200020002, 0x2000200020002, + 0x3ff000000000000, 0x0, 0x720c000000000000, 0x0, 0x0, 0x0, 0x0, + 0x3ff00000000, 0x0, 0x3ff000000000000, 0x0, 0x3ff, 0x0, 0xffc000000000, + 0x0, 0x3f0ffc000000000, 0x0, 0xfcffc000000000, 0x0, 0x7ffc000000000, + 0x0, 0x7f00ffc000000000, 0x0, 0x3fffc000000000, 0x0, 0x3ff0000, + 0xfffff00000000, 0x0, 0x3ff0000, 0x0, 0x0, 0x1ffffe0000000000, 0x0, + 0x1c00000000000, 0x0, 0x3ff03ff00000000, 0x0, 0xffc0, 0x0, 0x7ff0000, + 0x3ff03ff, 0x0, 0x0, 0x3ff03ff, 0x0, 0x3f1000000000000, 0x3ff, 0x0, + 0x0, 0xffffffffffff0000, 0x3e7, 0x0, 0x0, 0xffffffff00000000, + 0xfffffff, 0xfffffc0000000000, 0x0, 0xffc0000000000000, 0xfffff, 0x0, + 0x0, 0x2000000000000000, 0x70003fe00000080, 0x0, 0x3c0000, 0x0, + 0x3ff00000000, 0xfffeff00, 0xfffe0000000003ff, 0x0, 0x3ff00000000, 0x0, + 0x3f000000000000, 0x0, 0xfffffffffff80, 0x1ffffffffffffff, 0x400, 0x0, + 0xf00000000, 0x402, 0x0, 0x3e0000, 0x0, 0xff000000, 0xfc00000, 0x0, + 0x0, 0x60000000000000ff, 0x0, 0xff000000ff000000, 0x0, + 0x7fffffff00000000, 0x0, 0xfffffffc0000, 0xffc0000000000000, 0x0, + 0xffffffffffffffff, 0x7ffffffff, 0x0, 0x3ffff00000000, 0x0, + 0xffffffffffffc000, 0x7ff, 0x0, 0x0, 0x0]); +//2336 bytes +enum punctuationTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x20, 0x60], + [0x100, 0x100, 0x3100], [0x402030202020100, 0x202020202020605, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2000100010000, 0x5000400030001, 0x1000800070006, 0xb000a00090001, + 0xd00010001000c, 0x10000f0001000e, 0x14001300120011, 0x1000100010015, + 0x17000100010016, 0x18000100010001, 0x1000100190001, 0x1001c001b001a, + 0x100010001001d, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1001f0001001e, 0x23002200210020, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x26002500240001, + 0x28000100270001, 0x1000100010001, 0x2c002b002a0029, 0x1000100010001, + 0x10001002e002d, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x100010001002f, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x8c00f7ee00000000, 0x28000000b8000001, 0x88c0088200000000, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x4000000000000000, 0x80, 0x0, 0x0, 0xfc000000, + 0x4000000000000600, 0x18000000000049, 0xc8003600, 0x3c0000000000, 0x0, + 0x100000, 0x3fff, 0x0, 0x0, 0x380000000000000, 0x7fff000000000000, + 0x40000000, 0x0, 0x0, 0x0, 0x1003000000000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1000000000000, 0x0, 0x0, 0x0, 0x10000000000000, 0x0, 0xc008000, 0x0, + 0x0, 0x3c0000000017fff0, 0x0, 0x20, 0x61f0000, 0x0, 0xfc00, 0x0, + 0x800000000000000, 0x0, 0x1ff00000000, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x600000000000, 0x18000000, 0x380000000000, 0x60000000000000, 0x0, + 0x0, 0x7700000, 0x7ff, 0x0, 0x0, 0x0, 0x0, 0x30, 0x0, 0x0, 0xc0000000, + 0x0, 0x3f7f00000000, 0x0, 0x0, 0x1fc000000, 0x0, 0xf000000000000000, + 0xf800000000000000, 0xc000000000000000, 0x0, 0x800ff, + 0xffff00ffffff0000, 0x600000007ffbffef, 0x6000, 0x0, 0x60000000f00, + 0x0, 0x0, 0x0, 0x0, 0x3fff0000000000, 0x0, 0xffc000000060, 0x0, 0x0, + 0x1fffff8, 0x300000000f000000, 0x0, 0x0, 0x0, 0xde00000000000000, 0x0, + 0x1000000000000, 0x0, 0x0, 0xfff7fffffffffff, 0x0, 0x0, 0x0, + 0x20010000fff3ff0e, 0x0, 0x100000000, 0x800000000000000, 0x0, 0x0, 0x0, + 0xc000000000000000, 0xe000, 0x4008000000000000, 0x0, 0xfc000000000000, + 0x0, 0xf0000000000000, 0x0, 0x70000000000c000, 0xc00000000000, + 0x80000000, 0x0, 0xc0003ffe, 0x0, 0xf0000000, 0x0, 0x30000c0000000, + 0x0, 0x0, 0x0, 0x80000000000, 0xc000000000000000, 0x0, 0x0, 0x0, + 0xffff000003ff0000, 0xd0bfff7ffff, 0x0, 0x0, 0xb80000018c00f7ee, + 0x3fa8000000, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80000000, + 0x10000, 0x0, 0x800000, 0x0, 0x0, 0x8000000080000000, 0x0, 0x0, 0x0, + 0x0, 0x8000000001ff0000, 0x0, 0x0, 0xfe00000000000000, 0x0, 0x0, 0x0, + 0x0, 0x3f80, 0xd800000000000000, 0x3, 0x0, 0xf, 0x0, 0x1e0, 0x0, + 0xf000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); +//2848 bytes +enum symbolTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x20, 0x70], [0x100, + 0x140, 0x3d00], [0x503040303020100, 0x807030303030306, + 0x303030303030303, 0x303030303030303, 0x303030303030303, + 0x303030303030303, 0x303030303030303, 0x303030303030303, + 0x303030303030303, 0x303030303030303, 0x303030303030303, + 0x303030303030303, 0x303030303030303, 0x303030303030303, + 0x303030303030303, 0x303030303030303, 0x303030303030303, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3000200010000, 0x7000600050004, 0xa000900080001, 0xe000d000c000b, + 0x1000010001000f, 0x11000100010001, 0x13000100120001, 0x14000100010001, + 0x18001700160015, 0x1a001700170019, 0x1c0017001b0017, 0x1f001e0001001d, + 0x17002200210020, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100230001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x25000100010024, 0x1002700010026, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x28000100010001, 0x2b002a00290001, + 0x10001002c0001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x30002f002e002d, 0x32003100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1003300010001, + 0x37003600350034, 0x3b003a00390038, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x7000081000000000, 0x5000000140000000, 0x113d37c00000000, + 0x80000000800000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xffffafe0fffc003c, 0x0, 0x20000000000000, 0x30, 0x40000000000000, 0x0, + 0x0, 0x4, 0x0, 0x0, 0x0, 0x8000, 0x0, 0xc9c0, 0x0, 0x0, + 0x6000020040000000, 0x0, 0x0, 0x0, 0x40000000000000, 0x0, 0x0, 0x0, + 0xc0c000000000000, 0x0, 0x0, 0x0, 0x2000000000000, 0x0, + 0x1000000000000, 0x0, 0x7f8000000000000, 0x0, 0x8000000000000000, 0x0, + 0x0, 0x0, 0x200000000000000, 0x0, 0x0, 0x8000000000000000, 0x0, 0x0, + 0x0, 0x1500000fce8000e, 0x0, 0xc000000000000000, 0x1e0dfbf, 0x0, 0x0, + 0xc0000000, 0x0, 0x0, 0x0, 0x3ff0000, 0x0, 0x0, 0x0, 0x0, 0x8000000, + 0x0, 0x1, 0x0, 0xffffffffc0000000, 0x0, 0x1ff007fe00000000, 0x0, 0x0, + 0x0, 0x0, 0xa000000000000000, 0x6000e000e000e003, 0x0, + 0x1c00000000040010, 0x7ffffff00001c00, 0x0, 0xc0042afc1d0037b, 0xbc1f, + 0xffffffffffff0000, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xfffff9fffffff0ff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xfffffffffffff, 0x7fffffffff, 0x7ff, 0xfffffffff0000000, + 0x3ffffffffff, 0xfffffffffffffffe, 0xffffffffff, 0xfffffffffff00000, + 0xffff003fffffff9f, 0xffffffffffffffff, 0xffffffffffffffff, + 0xfffffffffe000007, 0xcffffffff0ffffff, 0xffffffffffffffff, 0x3ff1fff, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x7e000000000, 0x0, 0x0, 0xfffffffffbffffff, + 0xfffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xfff0000003fffff, 0xc0c00001000c0010, 0x0, + 0x18000000, 0x0, 0x0, 0x0, 0xffc30000, 0xfffffffff, 0xfffffc007fffffff, + 0xffffffff000100ff, 0x1fffffffffc00, 0x7fffffffffffffff, 0x0, 0x0, 0x0, + 0xffffffffffffffff, 0x0, 0x0, 0xffffffffffff0000, 0x7f, 0x3007fffff, + 0x0, 0x600, 0x0, 0x3c00f0000000000, 0x0, 0x0, 0x0, 0x0, + 0x380000000000000, 0x0, 0x0, 0x20000000000, 0x0, 0xfffc000000000000, + 0x3, 0x0, 0x0, 0x0, 0x3000000000000000, 0x0, 0x27400000000, 0x0, 0x0, + 0x4000000070000810, 0x50000001, 0x0, 0x30007f7f00000000, + 0xff80000000000000, 0xfe00000000000000, 0xfff03ff, 0x1fffffffffff0000, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x3fffffffffffff, 0xfffffe7fffffffff, 0x1c1fffffffff, + 0xffffc3fffffff018, 0x3fffffff, 0xffffffffffffffff, 0x23, 0x0, 0x0, + 0xffffffffffffffff, 0x7fffff, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x800000008000002, 0x20000000200000, 0x800000008000, 0x20000000200, + 0x8, 0x0, 0x0, 0x0, 0x3000000000000, 0xffff0fffffffffff, + 0xffffffffffffffff, 0x7ffe7fff000fffff, 0xfffefffe, 0xffff7fffffff0000, + 0xffff0fffffffffff, 0x7ffffff, 0xffffffc000000000, 0x7ffffffffff0007, + 0x301ff, 0x0, 0x0, 0xffbf0001ffffffff, 0x1fffffffffffffff, + 0xffffffff000fffff, 0x1ffff000007df, 0x7fffffffffffffff, + 0xfffffffffffffffd, 0xffffffffffffffff, 0x1effffffffffffff, + 0x3fffffffffffffff, 0xffffff000f, 0x0, 0xf800000000000000, + 0xffffffffffffffff, 0xffe1, 0xffffffffffffffff, 0x3f, + 0xffffffffffffffff, 0xfffffffffffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); +//4576 bytes +enum graphicalTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x20, 0xb8], + [0x100, 0x260, 0x6100], [0x706050403020100, 0xe0d0c0a0b0a0908, + 0x100a0f0303030303, 0xa0a0a0a0a0a0a0a, 0xa0a0a0a0a0a0a0a, + 0xa0a0a0a0a0a0a0a, 0xa0a0a0a0a0a0a0a, 0xa0a0a0a0a0a0a0a, + 0xa0a0a0a0a0a0a0a, 0xa0a0a0a0a0a0a0a, 0xa0a0a0a0a0a0a0a, + 0xa0a0a0a0a0a0a0a, 0xa0a0a0a0a0a0a0a, 0xa0a0a0a0a0a0a0a, + 0xa0a0a0a0a0a0a11, 0xa0a0a0a0a0a0a0a, 0xa0a0a0a0a0a0a0a, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2000100010000, 0x5000400030001, 0x9000800070006, 0xd000c000b000a, + 0x10000f0001000e, 0x12001100010001, 0x16001500140013, 0x19000100180017, + 0x1c0001001b001a, 0x1e00010001001d, 0x1f000100010001, 0x23002200210020, + 0x1002600250024, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100270001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x28000100010001, + 0x1000100010001, 0x2b002a00010029, 0x2f002e002d002c, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x1000100010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x30000100010001, 0x31003100310031, 0x31003100310031, + 0x31003100310031, 0x31003100310031, 0x31003100310031, 0x31003100310031, + 0x31003100310031, 0x31003100310031, 0x33003200010031, 0x36003500340001, + 0x3a003900380037, 0x3100310031003b, 0x3f003e003d003c, 0x31004100310040, + 0x31003100430042, 0x31004400310031, 0x31003100310031, 0x31003100310031, + 0x45000100010001, 0x31003100310046, 0x31003100310031, 0x31003100310031, + 0x1000100010001, 0x31003100310047, 0x31003100310031, 0x31003100310031, + 0x31003100310031, 0x31003100310031, 0x31003100310031, 0x31003100310031, + 0x31003100310031, 0x31003100310031, 0x31003100310031, 0x31003100310031, + 0x31003100310031, 0x31003100310031, 0x31004800010001, 0x49003100310031, + 0x31003100310031, 0x31003100310031, 0x31003100310031, 0x31003100310031, + 0x31003100310031, 0x31003100310031, 0x31003100310031, 0x31003100310031, + 0x3100310031004a, 0x31003100310031, 0x31003100310031, 0x31003100310031, + 0x31003100310031, 0x31003100310031, 0x31003100310031, 0x31003100310031, + 0x4e004d004c004b, 0x5200510050004f, 0x31003100310031, 0x31003100310031, + 0x31003100310031, 0x31003100310031, 0x31003100310031, 0x31005300310031, + 0x57005600550054, 0x5b005a00590058, 0x31003100310031, 0x31003100310031, + 0x1000100010001, 0x1005c00010001, 0x1000100010001, 0x1000100010001, + 0x1000100010001, 0x5d000100010001, 0x3100310031005e, 0x31003100310031, + 0x31003100310031, 0x31003100310031, 0x31003100310031, 0x31003100310031, + 0x31003100310031, 0x31003100310031, 0x31005e00010001, 0x31003100310031, + 0x310031005f0031, 0x31003100310031, 0x31003100310031, 0x31003100310031, + 0x31003100310031, 0x31003100310031, 0x31003100310031, 0x31003100310031, + 0x31003100310031, 0x31003100310031, 0x31003100310031, 0x31003100310031, + 0x31003100310031, 0x31003100310031, 0x31003100310031, 0x31003100310031, + 0xffffffff00000000, 0x7fffffffffffffff, 0xffffdfff00000000, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x7cffffffffffffff, 0xfffffffbffffd7f0, 0xffffffffffffffff, + 0xfffe00ffffffffff, 0xfffffffefe7fffff, 0xfffffffffffe86ff, + 0x1f07ffffff00ff, 0xffffffffcfffffc0, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffdfffffff, 0xffffffffffff3fff, + 0xffffffffffffe7ff, 0x3ffffffffffff, 0x7ffffffffffffff, + 0x7fff3fffffffffff, 0x4fffffff, 0x1ffd00000000, 0x7ffffff000000000, + 0xffffffffffffffff, 0xfeffffffffffffff, 0xf3c5fdfffff99fee, + 0xfffffcfb080799f, 0xd36dfdfffff987ee, 0x3fffc05e023987, + 0xf3edfdfffffbbfee, 0x3ffcf00013bbf, 0xf3edfdfffff99fee, + 0xffffcfb0c0399f, 0xc3ffc718d63dc7ec, 0x7ffffc000813dc7, + 0xe3effdfffffddfee, 0xff00ffcf03603ddf, 0xf3effdfffffddfec, + 0x6ffcf40603ddf, 0xe7fffffffffddfec, 0xfe3fffcf00807ddf, + 0x2ffbfffffc7fffec, 0x1c0000ff5f847f, 0x87fffffffffffffe, 0xfffffff, + 0x3bffecaefef02596, 0xf3ff3f5f, 0xffffffffffffffff, 0xfffe1ffffffffeff, + 0xdffffffffeffffff, 0x7ffdfff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffff20bf, 0xffffffffffffffff, + 0xffffffff3d7f3dff, 0x7f3dffffffff3dff, 0xffffffffff7fff3d, + 0xffffffffff3dffff, 0x1fffffffe7ffffff, 0xffffffff03ffffff, + 0x1fffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffff1fffffff, 0x1ffffffffffff, 0x7fffff001fdfff, 0xddfff000fffff, + 0xffffffffffffffff, 0x3ff03ff3fffffff, 0xffffffff03ff3fff, + 0xffffffffffffff, 0xffff07ffffffffff, 0x3fffffffffffff, + 0xfff0fff1fffffff, 0x1f3ffffffffff1, 0xffff0fffffffffff, + 0xffffffffc7ff03ff, 0xffffffffcfffffff, 0x9fffffff7fffffff, + 0x3fff03ff03ff, 0x0, 0xffffffffffffffff, 0x1fffffffffff0fff, + 0xffffffffffffffff, 0xf00fffffffffffff, 0xf8ffffffffffffff, + 0xffffffffffffe3ff, 0x0, 0x7fffffffff00ff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xf000007fffffffff, + 0xffffffff3f3fffff, 0x3fffffffaaff3f3f, 0xffdfffffffffffff, + 0x7fdcffffefcfffdf, 0xffff80ffffff07ff, 0xfff30000ffffffff, + 0x7ffffff1fff7fff, 0x1ffffffff0000, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffff03ff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xfffffffffffff, 0x7fffffffff, 0xffffffff000007ff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xfffffffffffffffe, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3ff1fff, + 0x0, 0x0, 0xffff7fffffffffff, 0xffffffff7fffffff, 0xffffffffffffffff, + 0xfe0fffffffffffff, 0xffff20bfffffffff, 0x800180ffffffffff, + 0x7f7f7f7f007fffff, 0xffffffff7f7f7f7f, 0xfffffffffffffff, 0x0, + 0xfffffffffbffffff, 0xfffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xfff0000003fffff, + 0xffffffffffffffff, 0xfffffffffffffffe, 0xfffffffffe7fffff, + 0xffffffffffffffff, 0xfffe3fffffffffe0, 0xffffffffffffffff, + 0x7ffffffffff7fff, 0xffff000fffffffff, 0xffffffff7fffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x3fffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x1fff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffff1fff, 0xffffffffffff007f, 0xfffffffffff, + 0xffffffffffffffff, 0xffffffff80ffffff, 0xffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x7ff000f7fff, + 0xff00000000000000, 0x3ff0fffffffffff, 0xffffffffffffff, + 0xffffffffffffffff, 0xfffffff03ffc01f, 0xffffffffffffffff, + 0x1fffffff800fffff, 0xffffffffffffffff, 0xc3ffbfff, 0x7fffffffffffff, + 0xffffffff3ff3fff, 0xffffffffffffffff, 0x7ffffff8000007, + 0x7f7f007e7e7e, 0x0, 0x0, 0x3ff3fffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffff000fffffffff, 0xffffffffffff87f, 0x0, 0x0, + 0x0, 0x0, 0xffffffffffffffff, 0xffff3fffffffffff, 0xffffffffffffffff, + 0x3ffffff, 0x5f7fffffe0f8007f, 0xffffffffffffffdb, 0xffffffffffffffff, + 0xfffffffffff80003, 0xffffffffffffffff, 0xffffffffffff0000, + 0xfffffffffffcffff, 0x3fff0000000000ff, 0xffff007f03ffffff, + 0xffdf0f7ffff7ffff, 0xffffffffffffffff, 0x1fffffffffffffff, + 0xfffffffffffffffe, 0xffffffffffffffff, 0x7fffffffffffffff, + 0x30007f7f1cfcfcfc, 0xb7ffff7fffffefff, 0x3fff3fff, 0xffffffffffffffff, + 0x7ffffffffffffff, 0xff8fffffffffff87, 0xffffffffffffffff, 0xfff07ff, + 0x3fffffffffff0000, 0x0, 0x0, 0xffffffff1fffffff, 0x1ffff, + 0xffff000f7fffffff, 0x7ff, 0xffffffffbfffffff, 0x3fff0f, + 0xffffffffffffffff, 0xffffffffffffffff, 0x3ff3fffffff, 0x0, + 0x91bffffffffffd3f, 0xffbfffff, 0x0, 0x0, 0x83ffffff8fffffff, 0x0, + 0xc0ffffffffffffff, 0x0, 0x870ffffffeeff06f, 0xffffffff01ff00ff, 0x0, + 0x0, 0xfe3fffffffffffff, 0xff07ffffff3fffff, 0x0, 0x0, + 0xffffffffffffffff, 0x1ff, 0x0, 0x0, 0x0, 0x7fffffff00000000, 0x0, 0x0, + 0xffffffffffffffff, 0xfffffffc3fff, 0xdfffffffffffffff, + 0x3ff01ffffff0003, 0xffdfffffffffffff, 0xf, 0xffffffffffffffff, + 0x3ff01ff, 0x0, 0x0, 0xffffffffffffff, 0x3ff, 0xffffffffffffffff, + 0x7fffffffffff, 0x0, 0x0, 0xffffffffffffffff, 0xf0007ffffffff, 0x0, + 0x0, 0x7fffffffffff, 0x0, 0x0, 0x0, 0x1ffffffffffffff, 0x0, 0x0, 0x0, + 0xffffffffffffffff, 0x7fffffffffff001f, 0xffff8000, 0x0, 0x3, 0x0, 0x0, + 0x0, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x3fffffffffffff, 0xfffffe7fffffffff, 0xf807ffffffffffff, + 0xffffffffffffffff, 0x3fffffff, 0xffffffffffffffff, 0x3f, 0x0, 0x0, + 0xffffffffffffffff, 0x3ffff007fffff, 0x0, 0x0, 0xffffffffffffffff, + 0xffffffffffdfffff, 0xebffde64dfffffff, 0xffffffffffffffef, + 0x7bffffffdfdfe7bf, 0xfffffffffffdfc5f, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffff3fffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffcfff, + 0xaf7fe96ffffffef, 0x5ef7f796aa96ea84, 0xffffbee0ffffbff, + 0x3000000000000, 0xffff0fffffffffff, 0xffffffffffffffff, + 0x7ffe7fff000fffff, 0xfffefffe, 0xffff7fffffff07ff, 0xffff0fffffffffff, + 0x7ffffff, 0xffffffc000000000, 0x7ffffffffff0007, 0x301ff, 0x0, 0x0, + 0xffbf0001ffffffff, 0x1fffffffffffffff, 0xffffffff000fffff, + 0x1ffff000007df, 0x7fffffffffffffff, 0xfffffffffffffffd, + 0xffffffffffffffff, 0x1effffffffffffff, 0x3fffffffffffffff, + 0xffffff000f, 0x0, 0xf800000000000000, 0xffffffffffffffff, 0xffe1, + 0xffffffffffffffff, 0x3f, 0xffffffffffffffff, 0xfffffffffffff, 0x0, + 0x0, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x7fffff, 0x1fffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x3fffffff, 0x0, 0x0, 0x0, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffff, 0x0, 0x0, 0x0, 0x0]); +//3664 bytes +enum nonCharacterTrieEntries = TrieEntry!(bool, 7, 4, 4, 6)([0x0, 0x10, 0x4c, + 0x104], [0x80, 0xf0, 0x2e0, 0x3180], [0x706050403020100, + 0xb0b0b0b0a090808, 0xb0b0b0b0b0b0b0b, 0xb0b0b0b0b0b0b0b, + 0xb0b0b0b0b0b0b0b, 0xb0b0b0b0b0b0b0b, 0xb0b0b0b0b0b0b0b, + 0xd0808080b0b0b0c, 0xd080808, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3000200010000, 0x7000600050004, 0xb000a00090008, 0xd000d000d000c, + 0xe000d000d000d, 0xd000d000d000d, 0xd000d000d000d, 0xd000d000d000d, + 0xd000d000d000d, 0xf000d000d000d, 0xd00110010000d, 0xd000d000d000d, + 0xd000d000d000d, 0xd000d0012000d, 0xd000d000d000d, 0x140013000d000d, + 0x18001700160015, 0x1b001b001a0019, 0x1b001b001d001c, 0x1b001b001e000d, + 0x1b001b001b001b, 0x1b001b001b001b, 0x20001f001b001b, 0x1b001b001b001b, + 0x1b001b001b001b, 0x1b001b001b001b, 0x1b001b001b001b, 0x1b001b001b0021, + 0x1b001b001b001b, 0x1b001b00230022, 0x24001b001b001b, 0x1b001b00260025, + 0xd000d000d000d, 0xd000d000d000d, 0xd000d000d000d, 0xd000d000d000d, + 0xd000d000d000d, 0xd000d000d000d, 0xd000d0027000d, 0x1b00290028000d, + 0x1b001b001b001b, 0x1b001b001b001b, 0x1b001b001b001b, 0x1b002a001b001b, + 0x1b001b001b001b, 0x1b001b001b001b, 0x1b001b001b001b, 0x1b001b001b001b, + 0x1b001b001b002b, 0x1b001b001b001b, 0x1b001b001b001b, 0x1b001b001b001b, + 0xd000d000d000d, 0xd000d000d000d, 0xd000d000d000d, 0x2c000d000d000d, + 0xd000d000d000d, 0xd000d000d000d, 0xd000d000d000d, 0x2c000d000d000d, + 0x0, 0x0, 0x0, 0x200010000, 0x0, 0x6000500040003, 0x7, 0xb000a00090008, + 0xf000e000d000c, 0x12001100100000, 0x16001500140013, 0x1a001900180017, + 0x1e001d001c001b, 0x2200210020001f, 0x26002500240023, 0x29002800270000, + 0x2a000000000000, 0x0, 0x2d002c002b0000, 0x310030002f002e, 0x0, 0x0, + 0x33003200000000, 0x36000000350034, 0x3a003900380037, 0x3e003d003c003b, + 0x4200410040003f, 0x44000000430000, 0x47004200460045, 0x48000000000000, + 0x0, 0x4c004b004a0049, 0x4f004e004d0000, 0x5000000000, 0x0, + 0x51000000000000, 0x530052, 0x0, 0x0, 0x54, 0x0, 0x0, 0x0, + 0x42004200550000, 0x58000000570056, 0x5c005b005a0059, 0x51005e0042005d, + 0x5f000000000000, 0x6000540000, 0x63006200000061, 0x64000000000057, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3a00000000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x65000000000000, 0x67006600000000, 0x0, 0x38006900000068, + 0x6b006a00000000, 0x6d00000038006c, 0x6f0000006e0000, 0x72000000710070, + 0x74004200420073, 0x0, 0x0, 0x0, 0x75006300000000, 0x0, 0x0, + 0x77000000760000, 0x7a000000790078, 0x0, 0x7d007c007b0000, + 0x800000007f007e, 0x81006400000054, 0xb000000830082, 0x86008500000084, + 0x87003200420042, 0x8b008a00890088, 0x42008c00000000, 0x42004200420042, + 0x42004200420042, 0x42004200420042, 0x420042008e008d, 0x4200900042008f, + 0x42004200920091, 0x42004200940093, 0x42004200950000, 0x42004200420042, + 0x42004200960042, 0x42004200420042, 0x98000000970000, 0x9a00000099004b, + 0x42004200420042, 0x42004200420042, 0x42004200420042, 0x42004200420042, + 0x9b003800420042, 0x42004200420042, 0x42004200420042, 0x42004200420042, + 0x42004200420042, 0x42004200420042, 0x0, 0x0, 0x0, 0x420042009c0000, + 0x420042009d0000, 0x42004200420042, 0x42004200420042, 0x42004200420042, + 0x4200420042009c, 0x42004200420042, 0x42004200420042, 0x42004200420042, + 0x0, 0x0, 0x4200420042009e, 0x42004200420042, 0x42004200420042, + 0x42004200420042, 0x42004200420042, 0x4200a0009f0000, 0x420042004200a1, + 0x42004200420042, 0x42004200420042, 0x42004200420042, 0x3a000000000000, + 0xa30000000000a2, 0x42004200a40000, 0x42004200a50000, 0xa800a700a60000, + 0xaa00a9, 0xab00000000, 0xac000000000000, 0x42004200420042, + 0x42004200420042, 0xb000af00ae00ad, 0x42004200420042, 0xb200b10000003d, + 0xb500b4003d00b3, 0x42004200b700b6, 0xbb00ba00b900b8, 0xbd000000bc0064, + 0xc0004200bf00be, 0xa4000000c10000, 0x42004200510000, 0x0, 0x0, + 0xc2000000000000, 0x0, 0x0, 0x0, 0x0, 0x31, 0x420042004200a3, + 0x42004200420042, 0x42004200420042, 0x42004200420042, 0x0, 0x0, + 0x420042004200a3, 0x42004200420042, 0x420042000000c3, 0xc4000000000000, + 0x42004200420042, 0x42004200420042, 0x0, 0x0, 0x0, 0xbe000000000000, + 0x0, 0x0, 0x0, 0xbe000000000000, 0x0, 0x8300000000000000, 0x40000280f, + 0x1ff0000000000, 0x101800000, 0x17900, 0xffe0f8000000ff00, 0x20000020, + 0x4000, 0x1800, 0xfffc000000000000, 0xf800000000000000, + 0x8000c00000000000, 0xffffffffb0000000, 0xffffe002ffffffff, + 0x8000000fffffffff, 0x100000000000000, 0xc3a020000066011, + 0xf00000304f7f8660, 0x2c92020000067811, 0xffc0003fa1fdc678, + 0xc12020000044011, 0xfffc0030fffec440, 0xc12020000066011, + 0xff0000304f3fc660, 0x3c0038e729c23813, 0xf800003fff7ec238, + 0x1c10020000022011, 0xff0030fc9fc220, 0xc10020000022013, + 0xfff90030bf9fc220, 0x1800000000022013, 0x1c00030ff7f8220, + 0xd004000003800013, 0xffe3ffff00a07b80, 0x7800000000000001, + 0xfffffffff0000000, 0xc4001351010fda69, 0xffffffff0c00c0a0, + 0x1e00000000100, 0x2000000001000000, 0xfffffffff8002000, 0xdf40, + 0xc280c200, 0x80c200000000c200, 0x8000c2, 0xc20000, 0xe000000018000000, + 0xfc000000, 0xffe0000000000000, 0xe0000000, 0xfffe000000000000, + 0xff800000ffe02000, 0xfff22000fff00000, 0xfc00fc00c0000000, 0xfc008000, + 0xff00000000000000, 0xf80000000000, 0xffc0000000000000, + 0xf000f000e0000000, 0xffe0c0000000000e, 0xf00000000000, 0x3800fc00, + 0x30000000, 0x6000000080000000, 0xffffc000fc00fc00, 0xffffffffffffffff, + 0xe00000000000f000, 0xff0000000000000, 0x700000000000000, 0x1c00, + 0xff8000000000ff00, 0xfffff8000000000, 0xc0c00000, 0xc00000005500c0c0, + 0x20000000000000, 0x8023000010300020, 0xc002000000000, + 0xf8000000e0008000, 0xfffe00000000ffff, 0xfc00, 0xfff0000000000000, + 0xffffff8000000000, 0xfffff800, 0x1, 0xfffffffffc00e000, + 0x800000000000, 0x80000000, 0x1f0000000000000, 0xdf4000000000, + 0x7ffe7f0000000000, 0x80808080ff800000, 0x80808080, 0xf000000000000000, + 0x4000000, 0xf000ffffffc00000, 0x1800000, 0x1c0000000001f, + 0xf800000000008000, 0xfff000000000, 0x8000000000000000, + 0xffffffffffffe000, 0xe000, 0xff80, 0xfffff00000000000, 0x7f000000, + 0xfffff800fff08000, 0xffffffffffffff, 0xfc00f00000000000, + 0xf0000000fc003fe0, 0xe00000007ff00000, 0xffffffff3c004000, + 0xff80000000000000, 0xf00000000c00c000, 0xff80000007fffff8, + 0xffff8080ff818181, 0xfc00c00000000000, 0xf000000000000780, + 0xc00000000000, 0xfffffffffc000000, 0xa08000001f07ff80, 0x24, 0x7fffc, + 0xffff, 0x30000, 0xc000ffffffffff00, 0xff80fc000000, 0x20f08000080000, + 0x6000000000000000, 0xc1ff8080e3030303, 0x4800008000001000, + 0xffffffffc000c000, 0x70000000000078, 0xfffffffff000f800, + 0xc00000000000ffff, 0xfffffffffffe0000, 0xfff080000000, + 0xfffffffffffff800, 0x40000000, 0xffffffffffc000f0, 0xfffffc00c0000000, + 0x6e400000000002c0, 0xffffffff00400000, 0x7c00000070000000, + 0x3f00000000000000, 0x78f0000001100f90, 0xfe00ff00, 0x1c0000000000000, + 0xf8000000c00000, 0xfffffffffffffe00, 0x80000000ffffffff, + 0xffff00000003c000, 0xfc00fe000000fffc, 0xfffffffffffffff0, + 0xfffffffffc00fe00, 0xfffffffffffffc00, 0xffff800000000000, + 0xfff0fff800000000, 0xfe00000000000000, 0x800000000000ffe0, + 0xffffffff00007fff, 0xfffffffffffffffc, 0x18000000000, + 0xffffffffc0000000, 0xffffffffffffffc0, 0xfffc0000ff800000, 0x200000, + 0x1400219b20000000, 0x10, 0x8400000020201840, 0x203a0, 0xc000000000, + 0x3000, 0xf508016900000010, 0xa10808695569157b, 0xf0000411f0000400, + 0xfffcffffffffffff, 0x80018000fff00000, 0xffffffff00010001, + 0x80000000f800, 0xfffffffff8000000, 0x3fffffffff, 0xf80000000000fff8, + 0xfffffffffffcfe00, 0x40fffe00000000, 0xe000000000000000, 0xfff00000, + 0xfffe0000fffff820, 0x2, 0xe100000000000000, 0xc000000000000000, + 0xffffff000000fff0, 0x7ffffffffffffff, 0xffffffffffff001e, + 0xffffffffff800000, 0xfffffffd, 0xffff000000000000, 0xc000000000000000]); +enum MAX_SIMPLE_LOWER = 1043; +enum MAX_SIMPLE_UPPER = 1051; +enum MAX_SIMPLE_TITLE = 1055; +//8192 bytes +enum toUpperIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([0x0, 0x20, 0x100], + [0x100, 0x380, 0xc00], [0x402030202020100, 0x202020202020205, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x3000200010000, 0x7000600050004, 0xa00090008, 0xd000c000b0000, + 0x110010000f000e, 0x1400130012, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x150000, 0x19001800170016, 0x1d001c001b001a, 0x0, 0x1f001e0000, + 0x0, 0x0, 0x20000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x24002300220021, 0x25, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2700260000, 0x2a00290028, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2b, 0x0, 0x0, 0x0, 0x2c0000, 0x0, 0x0, + 0x0, 0x0, 0x2e002d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x200010000ffff, + 0x6000500040003, 0xa000900080007, 0xe000d000c000b, 0x1200110010000f, + 0x16001500140013, 0xffff001900180017, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffff001affff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x41bffffffffffff, + 0x1e001d001c001b, 0x2200210020001f, 0x26002500240023, 0x2a002900280027, + 0x2e002d002c002b, 0xffff00310030002f, 0x35003400330032, + 0x39003800370036, 0x3bffff003affff, 0x3dffff003cffff, 0x3fffff003effff, + 0x41ffff0040ffff, 0x43ffff0042ffff, 0x45ffff0044ffff, 0x47ffff0046ffff, + 0x49ffff0048ffff, 0x4bffff004affff, 0x4dffff004cffff, 0x4fffff004effff, + 0x51ffff0050ffff, 0x53ffff0052041d, 0x55ffff0054ffff, + 0xffff0056ffffffff, 0xffff0058ffff0057, 0xffff005affff0059, + 0xffff005cffff005b, 0x5effff043a005d, 0x60ffff005fffff, + 0x62ffff0061ffff, 0x64ffff0063ffff, 0x66ffff0065ffff, 0x68ffff0067ffff, + 0x6affff0069ffff, 0x6cffff006bffff, 0x6effff006dffff, 0x70ffff006fffff, + 0x72ffff0071ffff, 0x74ffff0073ffff, 0xffff0075ffffffff, + 0x780077ffff0076, 0x7affffffff0079, 0xffffffff007bffff, + 0xffffffffffff007c, 0xffffffffffff007d, 0xffff007effffffff, + 0xffffffff007fffff, 0xffff00810080ffff, 0xffff0082ffffffff, + 0x84ffff0083ffff, 0xffffffff0085ffff, 0xffffffffffff0086, + 0xffffffff0087ffff, 0xffffffffffff0088, 0xffff008affff0089, + 0xffffffff008bffff, 0x8dffff008cffff, 0xffffffffffffffff, + 0xffff008f008effff, 0x92ffff00910090, 0xffff0094ffff0093, + 0xffff0096ffff0095, 0xffff0098ffff0097, 0xffff009affff0099, + 0x9dffff009c009b, 0x9fffff009effff, 0xa1ffff00a0ffff, 0xa3ffff00a2ffff, + 0xa5ffff00a4ffff, 0xa700a6ffff0442, 0xffffffff00a8ffff, + 0xaaffff00a9ffff, 0xacffff00abffff, 0xaeffff00adffff, 0xb0ffff00afffff, + 0xb2ffff00b1ffff, 0xb4ffff00b3ffff, 0xb6ffff00b5ffff, 0xb8ffff00b7ffff, + 0xbaffff00b9ffff, 0xbcffff00bbffff, 0xbdffffffffffff, 0xbfffff00beffff, + 0xc1ffff00c0ffff, 0xc3ffff00c2ffff, 0xc5ffff00c4ffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xc7ffffffff00c6, + 0xffff00c9ffff00c8, 0xcaffffffffffff, 0xccffff00cbffff, + 0xceffff00cdffff, 0xd200d100d000cf, 0xd500d4ffff00d3, 0xd7ffff00d6ffff, + 0xffffffffffffffff, 0xd9ffffffff00d8, 0xffff00db00daffff, + 0xdeffff00dd00dc, 0xdfffffffffffff, 0xffff00e100e0ffff, + 0xffffffff00e2ffff, 0xffffffffffffffff, 0xffffffff00e3ffff, + 0xe5ffffffff00e4, 0xffffffffffffffff, 0xe900e800e700e6, + 0xffffffffffff00ea, 0xffff00ebffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffff00ecffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xeeffff00edffff, 0xefffffffffffff, + 0xf0ffffffffffff, 0xffffffff00f200f1, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffff043c, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xf600f500f400f3, 0xf900f800f7043f, + 0xfd00fc00fb00fa, 0x101010000ff00fe, 0x105010401030102, + 0x109010801070106, 0x10d010c010b010a, 0x1110110010f010e, + 0xffff011401130112, 0xffffffff01160115, 0x11901180117ffff, + 0x11bffff011affff, 0x11dffff011cffff, 0x11fffff011effff, + 0x121ffff0120ffff, 0x123ffff0122ffff, 0x125ffff0124ffff, + 0xffff012801270126, 0xffffffff0129ffff, 0x12bffffffff012a, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x12f012e012d012c, 0x133013201310130, + 0x137013601350134, 0x13b013a01390138, 0x13f013e013d013c, + 0x143014201410140, 0x147014601450144, 0x14b014a01490148, + 0x14f014e014d014c, 0x153015201510150, 0x157015601550154, + 0x15b015a01590158, 0x15dffff015cffff, 0x15fffff015effff, + 0x161ffff0160ffff, 0x163ffff0162ffff, 0x165ffff0164ffff, + 0x167ffff0166ffff, 0x169ffff0168ffff, 0x16bffff016affff, + 0xffffffff016cffff, 0xffffffffffffffff, 0x16dffffffffffff, + 0x16fffff016effff, 0x171ffff0170ffff, 0x173ffff0172ffff, + 0x175ffff0174ffff, 0x177ffff0176ffff, 0x179ffff0178ffff, + 0x17bffff017affff, 0x17dffff017cffff, 0x17fffff017effff, + 0x181ffff0180ffff, 0x183ffff0182ffff, 0x185ffff0184ffff, + 0x187ffff0186ffff, 0xffff0188ffffffff, 0xffff018affff0189, + 0xffff018cffff018b, 0x18f018effff018d, 0x191ffff0190ffff, + 0x193ffff0192ffff, 0x195ffff0194ffff, 0x197ffff0196ffff, + 0x199ffff0198ffff, 0x19bffff019affff, 0x19dffff019cffff, + 0x19fffff019effff, 0x1a1ffff01a0ffff, 0x1a3ffff01a2ffff, + 0x1a5ffff01a4ffff, 0x1a7ffff01a6ffff, 0x1a9ffff01a8ffff, + 0x1abffff01aaffff, 0x1adffff01acffff, 0x1afffff01aeffff, + 0x1b1ffff01b0ffff, 0x1b3ffff01b2ffff, 0x1b5ffff01b4ffff, + 0x1b7ffff01b6ffff, 0x1b9ffff01b8ffff, 0x1bbffff01baffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x1be01bd01bcffff, + 0x1c201c101c001bf, 0x1c601c501c401c3, 0x1ca01c901c801c7, + 0x1ce01cd01cc01cb, 0x1d201d101d001cf, 0x1d601d501d401d3, + 0x1da01d901d801d7, 0x1de01dd01dc01db, 0x42e01e101e001df, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffff01e2ffff, 0xffffffff01e3ffff, + 0x1e5ffff01e4ffff, 0x1e7ffff01e6ffff, 0x1e9ffff01e8ffff, + 0x1ebffff01eaffff, 0x1edffff01ecffff, 0x1efffff01eeffff, + 0x1f1ffff01f0ffff, 0x1f3ffff01f2ffff, 0x1f5ffff01f4ffff, + 0x1f7ffff01f6ffff, 0x1f9ffff01f8ffff, 0x1fbffff01faffff, + 0x1fdffff01fcffff, 0x1ffffff01feffff, 0x201ffff0200ffff, + 0x203ffff0202ffff, 0x205ffff0204ffff, 0x207ffff0206ffff, + 0x209ffff0208ffff, 0x20bffff020affff, 0x20dffff020cffff, + 0x20fffff020effff, 0x211ffff0210ffff, 0x213ffff0212ffff, + 0x215ffff0214ffff, 0x217ffff0216ffff, 0x219ffff0218ffff, + 0x21bffff021affff, 0x21dffff021cffff, 0x21fffff021effff, + 0x221ffff0220ffff, 0x223ffff0222ffff, 0x225ffff0224ffff, + 0x227ffff0226ffff, 0x229ffff0228ffff, 0x22bffff022affff, + 0x22dffff022cffff, 0x4460444022effff, 0x22f044c044a0448, + 0xffffffffffffffff, 0x231ffff0230ffff, 0x233ffff0232ffff, + 0x235ffff0234ffff, 0x237ffff0236ffff, 0x239ffff0238ffff, + 0x23bffff023affff, 0x23dffff023cffff, 0x23fffff023effff, + 0x241ffff0240ffff, 0x243ffff0242ffff, 0x245ffff0244ffff, + 0x247ffff0246ffff, 0x249ffff0248ffff, 0x24bffff024affff, + 0x24dffff024cffff, 0x24fffff024effff, 0x251ffff0250ffff, + 0x253ffff0252ffff, 0x255ffff0254ffff, 0x257ffff0256ffff, + 0x259ffff0258ffff, 0x25bffff025affff, 0x25dffff025cffff, + 0x25fffff025effff, 0x263026202610260, 0x267026602650264, + 0xffffffffffffffff, 0xffffffffffffffff, 0x26b026a02690268, + 0xffffffff026d026c, 0xffffffffffffffff, 0xffffffffffffffff, + 0x2710270026f026e, 0x275027402730272, 0xffffffffffffffff, + 0xffffffffffffffff, 0x279027802770276, 0x27d027c027b027a, + 0xffffffffffffffff, 0xffffffffffffffff, 0x2810280027f027e, + 0xffffffff02830282, 0xffffffffffffffff, 0xffffffffffffffff, + 0x28504500284044e, 0x287045602860453, 0xffffffffffffffff, + 0xffffffffffffffff, 0x28b028a02890288, 0x28f028e028d028c, + 0xffffffffffffffff, 0xffffffffffffffff, 0x293029202910290, + 0x297029602950294, 0x29b029a02990298, 0xffffffff029d029c, + 0x47d047b04790477, 0x48504830481047f, 0x48d048b04890487, + 0x49504930491048f, 0x49d049b04990497, 0x4a504a304a1049f, + 0x4ad04ab04a904a7, 0x4b504b304b104af, 0x4bd04bb04b904b7, + 0x4c504c304c104bf, 0x4cd04cb04c904c7, 0x4d504d304d104cf, + 0x4d704e302b702b6, 0x4ef0459ffff04e5, 0xffffffffffffffff, + 0xffff02b9ffff04d9, 0x4db04e7ffffffff, 0x4f2045bffff04e9, + 0xffffffffffffffff, 0xffffffffffff04dd, 0x460045d02bc02bb, + 0x4650463ffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x46b046802be02bd, 0x472047002bf046e, 0xffffffffffffffff, + 0xffffffffffffffff, 0x4df04ebffffffff, 0x4f50475ffff04ed, + 0xffffffffffffffff, 0xffffffffffff04e1, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffff02c1ffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x2c502c402c302c2, + 0x2c902c802c702c6, 0x2cd02cc02cb02ca, 0x2d102d002cf02ce, + 0xffffffffffffffff, 0xffffffffffff02d2, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x2d602d502d402d3, + 0x2da02d902d802d7, 0x2de02dd02dc02db, 0x2e202e102e002df, + 0x2e602e502e402e3, 0x2ea02e902e802e7, 0xffffffff02ec02eb, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x2f002ef02ee02ed, + 0x2f402f302f202f1, 0x2f802f702f602f5, 0x2fc02fb02fa02f9, + 0x30002ff02fe02fd, 0x304030303020301, 0x308030703060305, + 0x30c030b030a0309, 0x310030f030e030d, 0x314031303120311, + 0x318031703160315, 0xffff031b031a0319, 0xffffffff031cffff, + 0xffff031e031dffff, 0xffff0320ffff031f, 0xffffffffffff0321, + 0x322ffffffffffff, 0xffff0323ffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x325ffff0324ffff, 0x327ffff0326ffff, + 0x329ffff0328ffff, 0x32bffff032affff, 0x32dffff032cffff, + 0x32fffff032effff, 0x331ffff0330ffff, 0x333ffff0332ffff, + 0x335ffff0334ffff, 0x337ffff0336ffff, 0x339ffff0338ffff, + 0x33bffff033affff, 0x33dffff033cffff, 0x33fffff033effff, + 0x341ffff0340ffff, 0x343ffff0342ffff, 0x345ffff0344ffff, + 0x347ffff0346ffff, 0x349ffff0348ffff, 0x34bffff034affff, + 0x34dffff034cffff, 0x34fffff034effff, 0x351ffff0350ffff, + 0x353ffff0352ffff, 0x355ffff0354ffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffff0357ffff0356, 0x358ffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x35c035b035a0359, 0x360035f035e035d, 0x364036303620361, + 0x368036703660365, 0x36c036b036a0369, 0x370036f036e036d, + 0x374037303720371, 0x378037703760375, 0x37c037b037a0379, + 0x37fffff037e037d, 0xffffffffffffffff, 0xffffffff0380ffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x382ffff0381ffff, 0x384ffff0383ffff, + 0x386ffff0385ffff, 0x388ffff0387ffff, 0x38affff0389ffff, + 0x38cffff038bffff, 0x38effff038dffff, 0x390ffff038fffff, + 0x392ffff0391ffff, 0x394ffff0393ffff, 0x396ffff0395ffff, + 0xffffffff0397ffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x399ffff0398ffff, + 0x39bffff039affff, 0x39dffff039cffff, 0x39fffff039effff, + 0x3a1ffff03a0ffff, 0x3a3ffff03a2ffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x3a4ffffffffffff, + 0x3a6ffff03a5ffff, 0x3a8ffff03a7ffff, 0x3aaffff03a9ffff, + 0x3abffffffffffff, 0x3adffff03acffff, 0x3afffff03aeffff, + 0x3b1ffff03b0ffff, 0x3b3ffff03b2ffff, 0x3b5ffff03b4ffff, + 0x3b7ffff03b6ffff, 0x3b9ffff03b8ffff, 0x3bbffff03baffff, + 0x3bdffff03bcffff, 0x3bfffff03beffff, 0x3c1ffff03c0ffff, + 0x3c3ffff03c2ffff, 0x3c5ffff03c4ffff, 0x3c7ffff03c6ffff, + 0x3c9ffff03c8ffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffff03caffffffff, 0x3ccffffffff03cb, 0x3ceffff03cdffff, + 0x3d0ffff03cfffff, 0xffffffffffffffff, 0xffffffffffff03d1, + 0x3d3ffff03d2ffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x3d5ffff03d4ffff, 0x3d7ffff03d6ffff, + 0xffffffff03d8ffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x42404220420041e, 0xffff042c042a0427, 0xffffffffffffffff, + 0xffffffffffffffff, 0x430ffffffffffff, 0x438043604340432, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x3db03da03d9ffff, 0x3df03de03dd03dc, + 0x3e303e203e103e0, 0x3e703e603e503e4, 0x3eb03ea03e903e8, + 0x3ef03ee03ed03ec, 0xffff03f203f103f0, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x3f603f503f403f3, 0x3fa03f903f803f7, 0x3fe03fd03fc03fb, + 0x4020401040003ff, 0x406040504040403, 0x40a040904080407, + 0x40e040d040c040b, 0x41204110410040f, 0x416041504140413, + 0x41a041904180417, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff]); +//8064 bytes +enum toLowerIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([0x0, 0x20, 0x100], + [0x100, 0x380, 0xbc0], [0x402030202020100, 0x202020202020205, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x2000000010000, 0x6000500040003, 0x80007, 0xb000a00090000, + 0xf000e000d000c, 0x1200110010, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x14001300000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x18001700160015, 0x1c001b001a0019, 0x0, + 0x1f001e001d, 0x0, 0x0, 0x21002000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x25002400230022, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2700260000, 0x2a00290028, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2b, 0x0, 0x0, 0x0, 0x2c, 0x0, + 0x0, 0x0, 0x0, 0x2d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x200010000ffff, 0x6000500040003, 0xa000900080007, 0xe000d000c000b, + 0x1200110010000f, 0x16001500140013, 0xffff001900180017, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x1d001c001b001a, 0x210020001f001e, 0x25002400230022, 0x29002800270026, + 0x2d002c002b002a, 0xffff0030002f002e, 0x34003300320031, + 0x413003700360035, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffff0039ffff0038, 0xffff003bffff003a, 0xffff003dffff003c, + 0xffff003fffff003e, 0xffff0041ffff0040, 0xffff0043ffff0042, + 0xffff0045ffff0044, 0xffff0047ffff0046, 0xffff0049ffff0048, + 0xffff004bffff004a, 0xffff004dffff004c, 0xffff004fffff004e, + 0xffff0051ffff0414, 0xffff0053ffff0052, 0x55ffff0054ffff, + 0x57ffff0056ffff, 0x59ffff0058ffff, 0x5bffff005affff, + 0xffff005c0423ffff, 0xffff005effff005d, 0xffff0060ffff005f, + 0xffff0062ffff0061, 0xffff0064ffff0063, 0xffff0066ffff0065, + 0xffff0068ffff0067, 0xffff006affff0069, 0xffff006cffff006b, + 0xffff006effff006d, 0xffff0070ffff006f, 0xffff0072ffff0071, + 0x75ffff00740073, 0xffffffff0076ffff, 0xffff00780077ffff, + 0x7b007affff0079, 0x7e007d007cffff, 0x80007fffffffff, 0x83ffff00820081, + 0x860085ffff0084, 0xffffffffffff0087, 0x8affff00890088, + 0xffff008cffff008b, 0x8f008effff008d, 0xffffffff0090ffff, + 0x930092ffff0091, 0x9600950094ffff, 0x98ffff0097ffff, + 0xffffffffffff0099, 0xffffffffffff009a, 0xffffffffffffffff, + 0x9dffff009c009b, 0xa0009fffff009e, 0xa2ffff00a1ffff, 0xa4ffff00a3ffff, + 0xa6ffff00a5ffff, 0xa8ffff00a7ffff, 0xffff00a9ffffffff, + 0xffff00abffff00aa, 0xffff00adffff00ac, 0xffff00afffff00ae, + 0xffff00b1ffff00b0, 0xffff00b300b20426, 0xb600b5ffff00b4, + 0xffff00b8ffff00b7, 0xffff00baffff00b9, 0xffff00bcffff00bb, + 0xffff00beffff00bd, 0xffff00c0ffff00bf, 0xffff00c2ffff00c1, + 0xffff00c4ffff00c3, 0xffff00c6ffff00c5, 0xffff00c8ffff00c7, + 0xffff00caffff00c9, 0xffff00ccffff00cb, 0xffff00ceffff00cd, + 0xffff00d0ffff00cf, 0xffff00d2ffff00d1, 0xffff00d4ffff00d3, + 0xffffffffffffffff, 0xd600d5ffffffff, 0xffff00d800d7ffff, + 0xdaffff00d9ffff, 0xffff00dd00dc00db, 0xffff00dfffff00de, + 0xffff00e1ffff00e0, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffff00e3ffff00e2, 0xffff00e4ffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffff00e5ffffffff, 0xffff00e800e700e6, 0xeb00eaffff00e9, + 0xee00ed00ec0424, 0xf200f100f000ef, 0xf600f500f400f3, 0xfa00f900f800f7, + 0xfdffff00fc00fb, 0x101010000ff00fe, 0x105010401030102, + 0xffffffffffffffff, 0xffffffffffff0425, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x106ffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffff0108ffff0107, + 0xffff010affff0109, 0xffff010cffff010b, 0xffff010effff010d, + 0xffff0110ffff010f, 0xffff0112ffff0111, 0xffffffffffffffff, + 0x114ffffffff0113, 0xffff01160115ffff, 0x11901180117ffff, + 0x11d011c011b011a, 0x1210120011f011e, 0x125012401230122, + 0x129012801270126, 0x12d012c012b012a, 0x1310130012f012e, + 0x135013401330132, 0x139013801370136, 0x13d013c013b013a, + 0x1410140013f013e, 0x145014401430142, 0x149014801470146, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffff014bffff014a, 0xffff014dffff014c, 0xffff014fffff014e, + 0xffff0151ffff0150, 0xffff0153ffff0152, 0xffff0155ffff0154, + 0xffff0157ffff0156, 0xffff0159ffff0158, 0xffffffffffff015a, + 0xffffffffffffffff, 0xffff015bffffffff, 0xffff015dffff015c, + 0xffff015fffff015e, 0xffff0161ffff0160, 0xffff0163ffff0162, + 0xffff0165ffff0164, 0xffff0167ffff0166, 0xffff0169ffff0168, + 0xffff016bffff016a, 0xffff016dffff016c, 0xffff016fffff016e, + 0xffff0171ffff0170, 0xffff0173ffff0172, 0xffff0175ffff0174, + 0x178ffff01770176, 0x17affff0179ffff, 0x17cffff017bffff, + 0xffffffff017dffff, 0xffff017fffff017e, 0xffff0181ffff0180, + 0xffff0183ffff0182, 0xffff0185ffff0184, 0xffff0187ffff0186, + 0xffff0189ffff0188, 0xffff018bffff018a, 0xffff018dffff018c, + 0xffff018fffff018e, 0xffff0191ffff0190, 0xffff0193ffff0192, + 0xffff0195ffff0194, 0xffff0197ffff0196, 0xffff0199ffff0198, + 0xffff019bffff019a, 0xffff019dffff019c, 0xffff019fffff019e, + 0xffff01a1ffff01a0, 0xffff01a3ffff01a2, 0xffff01a5ffff01a4, + 0xffff01a7ffff01a6, 0xffff01a9ffff01a8, 0xffffffffffffffff, + 0xffffffffffffffff, 0x1ac01ab01aaffff, 0x1b001af01ae01ad, + 0x1b401b301b201b1, 0x1b801b701b601b5, 0x1bc01bb01ba01b9, + 0x1c001bf01be01bd, 0x1c401c301c201c1, 0x1c801c701c601c5, + 0x1cc01cb01ca01c9, 0xffff01cf01ce01cd, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x41dffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x1d301d201d101d0, 0x1d701d601d501d4, 0x1db01da01d901d8, + 0x1df01de01dd01dc, 0x1e301e201e101e0, 0x1e701e601e501e4, + 0x1eb01ea01e901e8, 0x1ef01ee01ed01ec, 0x1f301f201f101f0, + 0x1f6ffff01f501f4, 0xffffffffffffffff, 0xffffffff01f7ffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffff01f9ffff01f8, 0xffff01fbffff01fa, 0xffff01fdffff01fc, + 0xffff01ffffff01fe, 0xffff0201ffff0200, 0xffff0203ffff0202, + 0xffff0205ffff0204, 0xffff0207ffff0206, 0xffff0209ffff0208, + 0xffff020bffff020a, 0xffff020dffff020c, 0xffff020fffff020e, + 0xffff0211ffff0210, 0xffff0213ffff0212, 0xffff0215ffff0214, + 0xffff0217ffff0216, 0xffff0219ffff0218, 0xffff021bffff021a, + 0xffff021dffff021c, 0xffff021fffff021e, 0xffff0221ffff0220, + 0xffff0223ffff0222, 0xffff0225ffff0224, 0xffff0227ffff0226, + 0xffff0229ffff0228, 0xffff022bffff022a, 0xffff022dffff022c, + 0xffff022fffff022e, 0xffff0231ffff0230, 0xffff0233ffff0232, + 0xffff0235ffff0234, 0xffff0237ffff0236, 0xffff0239ffff0238, + 0xffff023bffff023a, 0xffff023dffff023c, 0xffff023fffff023e, + 0xffff0241ffff0240, 0x4280427ffff0242, 0xffff042b042a0429, + 0xffff0243ffffffff, 0xffff0245ffff0244, 0xffff0247ffff0246, + 0xffff0249ffff0248, 0xffff024bffff024a, 0xffff024dffff024c, + 0xffff024fffff024e, 0xffff0251ffff0250, 0xffff0253ffff0252, + 0xffff0255ffff0254, 0xffff0257ffff0256, 0xffff0259ffff0258, + 0xffff025bffff025a, 0xffff025dffff025c, 0xffff025fffff025e, + 0xffff0261ffff0260, 0xffff0263ffff0262, 0xffff0265ffff0264, + 0xffff0267ffff0266, 0xffff0269ffff0268, 0xffff026bffff026a, + 0xffff026dffff026c, 0xffff026fffff026e, 0xffff0271ffff0270, + 0xffff0273ffff0272, 0xffffffffffffffff, 0xffffffffffffffff, + 0x277027602750274, 0x27b027a02790278, 0xffffffffffffffff, + 0xffffffffffffffff, 0x27f027e027d027c, 0xffffffff02810280, + 0xffffffffffffffff, 0xffffffffffffffff, 0x285028402830282, + 0x289028802870286, 0xffffffffffffffff, 0xffffffffffffffff, + 0x28d028c028b028a, 0x2910290028f028e, 0xffffffffffffffff, + 0xffffffffffffffff, 0x295029402930292, 0xffffffff02970296, + 0xffff042dffff042c, 0xffff042fffff042e, 0x299ffff0298ffff, + 0x29bffff029affff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x29f029e029d029c, 0x2a302a202a102a0, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x43f043e043d043c, 0x443044204410440, 0x447044604450444, + 0x44b044a04490448, 0x44f044e044d044c, 0x453045204510450, + 0x457045604550454, 0x45b045a04590458, 0x45f045e045d045c, + 0x463046204610460, 0x467046604650464, 0x46b046a04690468, + 0x46c0472ffffffff, 0x4780430ffff0473, 0x2bf02be02bd02bc, + 0xffffffffffff046d, 0x46e0474ffffffff, 0x4790431ffff0475, + 0x2c402c302c202c1, 0xffffffffffff046f, 0x4330432ffffffff, + 0x4350434ffffffff, 0x2c902c802c702c6, 0xffffffffffffffff, + 0x4370436ffffffff, 0x43a0439ffff0438, 0x2cd02cc02cb02ca, + 0xffffffffffff02ce, 0x4700476ffffffff, 0x47a043bffff0477, + 0x2d202d102d002cf, 0xffffffffffff0471, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffff02d4ffffffff, + 0x2d602d5ffffffff, 0xffffffffffffffff, 0xffff02d7ffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x2db02da02d902d8, + 0x2df02de02dd02dc, 0x2e302e202e102e0, 0x2e702e602e502e4, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x2e8ffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x2ea02e9ffffffff, 0x2ee02ed02ec02eb, 0x2f202f102f002ef, + 0x2f602f502f402f3, 0x2fa02f902f802f7, 0x2fe02fd02fc02fb, + 0x3020301030002ff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x306030503040303, 0x30a030903080307, + 0x30e030d030c030b, 0x31203110310030f, 0x316031503140313, + 0x31a031903180317, 0x31e031d031c031b, 0x32203210320031f, + 0x326032503240323, 0x32a032903280327, 0x32e032d032c032b, + 0xffff03310330032f, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x3340333ffff0332, 0x336ffffffff0335, + 0x338ffff0337ffff, 0x33b033a0339ffff, 0xffff033dffff033c, + 0xffffffff033effff, 0xffffffffffffffff, 0x340033fffffffff, + 0xffff0342ffff0341, 0xffff0344ffff0343, 0xffff0346ffff0345, + 0xffff0348ffff0347, 0xffff034affff0349, 0xffff034cffff034b, + 0xffff034effff034d, 0xffff0350ffff034f, 0xffff0352ffff0351, + 0xffff0354ffff0353, 0xffff0356ffff0355, 0xffff0358ffff0357, + 0xffff035affff0359, 0xffff035cffff035b, 0xffff035effff035d, + 0xffff0360ffff035f, 0xffff0362ffff0361, 0xffff0364ffff0363, + 0xffff0366ffff0365, 0xffff0368ffff0367, 0xffff036affff0369, + 0xffff036cffff036b, 0xffff036effff036d, 0xffff0370ffff036f, + 0xffff0372ffff0371, 0xffffffffffffffff, 0x373ffffffffffff, + 0xffffffff0374ffff, 0xffff0375ffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffff0377ffff0376, + 0xffff0379ffff0378, 0xffff037bffff037a, 0xffff037dffff037c, + 0xffff037fffff037e, 0xffff0381ffff0380, 0xffff0383ffff0382, + 0xffff0385ffff0384, 0xffff0387ffff0386, 0xffff0389ffff0388, + 0xffff038bffff038a, 0xffffffffffff038c, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffff038effff038d, 0xffff0390ffff038f, 0xffff0392ffff0391, + 0xffff0394ffff0393, 0xffff0396ffff0395, 0xffff0398ffff0397, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffff0399ffffffff, 0xffff039bffff039a, 0xffff039dffff039c, + 0xffff039fffff039e, 0xffff03a0ffffffff, 0xffff03a2ffff03a1, + 0xffff03a4ffff03a3, 0xffff03a6ffff03a5, 0xffff03a8ffff03a7, + 0xffff03aaffff03a9, 0xffff03acffff03ab, 0xffff03aeffff03ad, + 0xffff03b0ffff03af, 0xffff03b2ffff03b1, 0xffff03b4ffff03b3, + 0xffff03b6ffff03b5, 0xffff03b8ffff03b7, 0xffff03baffff03b9, + 0xffff03bcffff03bb, 0xffff03beffff03bd, 0xffffffffffffffff, + 0xffffffffffffffff, 0x3c0ffff03bfffff, 0xffff03c203c1ffff, + 0xffff03c4ffff03c3, 0xffff03c6ffff03c5, 0x3c7ffffffffffff, + 0xffffffff03c8ffff, 0xffff03caffff03c9, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffff03ccffff03cb, + 0xffff03ceffff03cd, 0xffff03d0ffff03cf, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x419041804170416, 0xffff041c041b041a, + 0xffffffffffffffff, 0xffffffffffffffff, 0x41effffffffffff, + 0x42204210420041f, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x3d303d203d1ffff, 0x3d703d603d503d4, + 0x3db03da03d903d8, 0x3df03de03dd03dc, 0x3e303e203e103e0, + 0x3e703e603e503e4, 0xffff03ea03e903e8, 0xffffffffffffffff, + 0x3ee03ed03ec03eb, 0x3f203f103f003ef, 0x3f603f503f403f3, + 0x3fa03f903f803f7, 0x3fe03fd03fc03fb, 0x4020401040003ff, + 0x406040504040403, 0x40a040904080407, 0x40e040d040c040b, + 0x41204110410040f, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff]); +//8192 bytes +enum toTitleIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([0x0, 0x20, 0x100], + [0x100, 0x380, 0xc00], [0x402030202020100, 0x202020202020205, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x3000200010000, 0x7000600050004, 0xa00090008, 0xd000c000b0000, + 0x110010000f000e, 0x1400130012, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x150000, 0x19001800170016, 0x1d001c001b001a, 0x0, 0x1f001e0000, + 0x0, 0x0, 0x20000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x24002300220021, 0x25, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2700260000, 0x2a00290028, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2b, 0x0, 0x0, 0x0, 0x2c0000, 0x0, 0x0, + 0x0, 0x0, 0x2e002d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x200010000ffff, + 0x6000500040003, 0xa000900080007, 0xe000d000c000b, 0x1200110010000f, + 0x16001500140013, 0xffff001900180017, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffff001affff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x41fffffffffffff, + 0x1e001d001c001b, 0x2200210020001f, 0x26002500240023, 0x2a002900280027, + 0x2e002d002c002b, 0xffff00310030002f, 0x35003400330032, + 0x39003800370036, 0x3bffff003affff, 0x3dffff003cffff, 0x3fffff003effff, + 0x41ffff0040ffff, 0x43ffff0042ffff, 0x45ffff0044ffff, 0x47ffff0046ffff, + 0x49ffff0048ffff, 0x4bffff004affff, 0x4dffff004cffff, 0x4fffff004effff, + 0x51ffff0050ffff, 0x53ffff00520421, 0x55ffff0054ffff, + 0xffff0056ffffffff, 0xffff0058ffff0057, 0xffff005affff0059, + 0xffff005cffff005b, 0x5effff043e005d, 0x60ffff005fffff, + 0x62ffff0061ffff, 0x64ffff0063ffff, 0x66ffff0065ffff, 0x68ffff0067ffff, + 0x6affff0069ffff, 0x6cffff006bffff, 0x6effff006dffff, 0x70ffff006fffff, + 0x72ffff0071ffff, 0x74ffff0073ffff, 0xffff0075ffffffff, + 0x780077ffff0076, 0x7affffffff0079, 0xffffffff007bffff, + 0xffffffffffff007c, 0xffffffffffff007d, 0xffff007effffffff, + 0xffffffff007fffff, 0xffff00810080ffff, 0xffff0082ffffffff, + 0x84ffff0083ffff, 0xffffffff0085ffff, 0xffffffffffff0086, + 0xffffffff0087ffff, 0xffffffffffff0088, 0xffff008affff0089, + 0xffffffff008bffff, 0x8dffff008cffff, 0xffffffffffffffff, + 0x910090008f008e, 0x95009400930092, 0xffff0097ffff0096, + 0xffff0099ffff0098, 0xffff009bffff009a, 0xffff009dffff009c, + 0xa0ffff009f009e, 0xa2ffff00a1ffff, 0xa4ffff00a3ffff, 0xa6ffff00a5ffff, + 0xa8ffff00a7ffff, 0xab00aa00a90446, 0xffffffff00acffff, + 0xaeffff00adffff, 0xb0ffff00afffff, 0xb2ffff00b1ffff, 0xb4ffff00b3ffff, + 0xb6ffff00b5ffff, 0xb8ffff00b7ffff, 0xbaffff00b9ffff, 0xbcffff00bbffff, + 0xbeffff00bdffff, 0xc0ffff00bfffff, 0xc1ffffffffffff, 0xc3ffff00c2ffff, + 0xc5ffff00c4ffff, 0xc7ffff00c6ffff, 0xc9ffff00c8ffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xcbffffffff00ca, + 0xffff00cdffff00cc, 0xceffffffffffff, 0xd0ffff00cfffff, + 0xd2ffff00d1ffff, 0xd600d500d400d3, 0xd900d8ffff00d7, 0xdbffff00daffff, + 0xffffffffffffffff, 0xddffffffff00dc, 0xffff00df00deffff, + 0xe2ffff00e100e0, 0xe3ffffffffffff, 0xffff00e500e4ffff, + 0xffffffff00e6ffff, 0xffffffffffffffff, 0xffffffff00e7ffff, + 0xe9ffffffff00e8, 0xffffffffffffffff, 0xed00ec00eb00ea, + 0xffffffffffff00ee, 0xffff00efffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffff00f0ffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xf2ffff00f1ffff, 0xf3ffffffffffff, + 0xf4ffffffffffff, 0xffffffff00f600f5, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffff0440, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xfa00f900f800f7, 0xfd00fc00fb0443, + 0x101010000ff00fe, 0x105010401030102, 0x109010801070106, + 0x10d010c010b010a, 0x1110110010f010e, 0x115011401130112, + 0xffff011801170116, 0xffffffff011a0119, 0x11d011c011bffff, + 0x11fffff011effff, 0x121ffff0120ffff, 0x123ffff0122ffff, + 0x125ffff0124ffff, 0x127ffff0126ffff, 0x129ffff0128ffff, + 0xffff012c012b012a, 0xffffffff012dffff, 0x12fffffffff012e, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x133013201310130, 0x137013601350134, + 0x13b013a01390138, 0x13f013e013d013c, 0x143014201410140, + 0x147014601450144, 0x14b014a01490148, 0x14f014e014d014c, + 0x153015201510150, 0x157015601550154, 0x15b015a01590158, + 0x15f015e015d015c, 0x161ffff0160ffff, 0x163ffff0162ffff, + 0x165ffff0164ffff, 0x167ffff0166ffff, 0x169ffff0168ffff, + 0x16bffff016affff, 0x16dffff016cffff, 0x16fffff016effff, + 0xffffffff0170ffff, 0xffffffffffffffff, 0x171ffffffffffff, + 0x173ffff0172ffff, 0x175ffff0174ffff, 0x177ffff0176ffff, + 0x179ffff0178ffff, 0x17bffff017affff, 0x17dffff017cffff, + 0x17fffff017effff, 0x181ffff0180ffff, 0x183ffff0182ffff, + 0x185ffff0184ffff, 0x187ffff0186ffff, 0x189ffff0188ffff, + 0x18bffff018affff, 0xffff018cffffffff, 0xffff018effff018d, + 0xffff0190ffff018f, 0x1930192ffff0191, 0x195ffff0194ffff, + 0x197ffff0196ffff, 0x199ffff0198ffff, 0x19bffff019affff, + 0x19dffff019cffff, 0x19fffff019effff, 0x1a1ffff01a0ffff, + 0x1a3ffff01a2ffff, 0x1a5ffff01a4ffff, 0x1a7ffff01a6ffff, + 0x1a9ffff01a8ffff, 0x1abffff01aaffff, 0x1adffff01acffff, + 0x1afffff01aeffff, 0x1b1ffff01b0ffff, 0x1b3ffff01b2ffff, + 0x1b5ffff01b4ffff, 0x1b7ffff01b6ffff, 0x1b9ffff01b8ffff, + 0x1bbffff01baffff, 0x1bdffff01bcffff, 0x1bfffff01beffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x1c201c101c0ffff, + 0x1c601c501c401c3, 0x1ca01c901c801c7, 0x1ce01cd01cc01cb, + 0x1d201d101d001cf, 0x1d601d501d401d3, 0x1da01d901d801d7, + 0x1de01dd01dc01db, 0x1e201e101e001df, 0x43201e501e401e3, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffff01e6ffff, 0xffffffff01e7ffff, + 0x1e9ffff01e8ffff, 0x1ebffff01eaffff, 0x1edffff01ecffff, + 0x1efffff01eeffff, 0x1f1ffff01f0ffff, 0x1f3ffff01f2ffff, + 0x1f5ffff01f4ffff, 0x1f7ffff01f6ffff, 0x1f9ffff01f8ffff, + 0x1fbffff01faffff, 0x1fdffff01fcffff, 0x1ffffff01feffff, + 0x201ffff0200ffff, 0x203ffff0202ffff, 0x205ffff0204ffff, + 0x207ffff0206ffff, 0x209ffff0208ffff, 0x20bffff020affff, + 0x20dffff020cffff, 0x20fffff020effff, 0x211ffff0210ffff, + 0x213ffff0212ffff, 0x215ffff0214ffff, 0x217ffff0216ffff, + 0x219ffff0218ffff, 0x21bffff021affff, 0x21dffff021cffff, + 0x21fffff021effff, 0x221ffff0220ffff, 0x223ffff0222ffff, + 0x225ffff0224ffff, 0x227ffff0226ffff, 0x229ffff0228ffff, + 0x22bffff022affff, 0x22dffff022cffff, 0x22fffff022effff, + 0x231ffff0230ffff, 0x44a04480232ffff, 0x2330450044e044c, + 0xffffffffffffffff, 0x235ffff0234ffff, 0x237ffff0236ffff, + 0x239ffff0238ffff, 0x23bffff023affff, 0x23dffff023cffff, + 0x23fffff023effff, 0x241ffff0240ffff, 0x243ffff0242ffff, + 0x245ffff0244ffff, 0x247ffff0246ffff, 0x249ffff0248ffff, + 0x24bffff024affff, 0x24dffff024cffff, 0x24fffff024effff, + 0x251ffff0250ffff, 0x253ffff0252ffff, 0x255ffff0254ffff, + 0x257ffff0256ffff, 0x259ffff0258ffff, 0x25bffff025affff, + 0x25dffff025cffff, 0x25fffff025effff, 0x261ffff0260ffff, + 0x263ffff0262ffff, 0x267026602650264, 0x26b026a02690268, + 0xffffffffffffffff, 0xffffffffffffffff, 0x26f026e026d026c, + 0xffffffff02710270, 0xffffffffffffffff, 0xffffffffffffffff, + 0x275027402730272, 0x279027802770276, 0xffffffffffffffff, + 0xffffffffffffffff, 0x27d027c027b027a, 0x2810280027f027e, + 0xffffffffffffffff, 0xffffffffffffffff, 0x285028402830282, + 0xffffffff02870286, 0xffffffffffffffff, 0xffffffffffffffff, + 0x289045402880452, 0x28b045a028a0457, 0xffffffffffffffff, + 0xffffffffffffffff, 0x28f028e028d028c, 0x293029202910290, + 0xffffffffffffffff, 0xffffffffffffffff, 0x297029602950294, + 0x29b029a02990298, 0x29f029e029d029c, 0xffffffff02a102a0, + 0x47e047d047c047b, 0x48204810480047f, 0x486048504840483, + 0x48a048904880487, 0x48e048d048c048b, 0x49204910490048f, + 0x496049504940493, 0x49a049904980497, 0x49e049d049c049b, + 0x4a204a104a0049f, 0x4a604a504a404a3, 0x4aa04a904a804a7, + 0x4ab04b102bb02ba, 0x4bd045dffff04b3, 0xffffffffffffffff, + 0xffff02bdffff04ac, 0x4ad04b5ffffffff, 0x4c0045fffff04b7, + 0xffffffffffffffff, 0xffffffffffff04ae, 0x464046102c002bf, + 0x4690467ffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x46f046c02c202c1, 0x476047402c30472, 0xffffffffffffffff, + 0xffffffffffffffff, 0x4af04b9ffffffff, 0x4c30479ffff04bb, + 0xffffffffffffffff, 0xffffffffffff04b0, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffff02c5ffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x2c902c802c702c6, + 0x2cd02cc02cb02ca, 0x2d102d002cf02ce, 0x2d502d402d302d2, + 0xffffffffffffffff, 0xffffffffffff02d6, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x2da02d902d802d7, + 0x2de02dd02dc02db, 0x2e202e102e002df, 0x2e602e502e402e3, + 0x2ea02e902e802e7, 0x2ee02ed02ec02eb, 0xffffffff02f002ef, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x2f402f302f202f1, + 0x2f802f702f602f5, 0x2fc02fb02fa02f9, 0x30002ff02fe02fd, + 0x304030303020301, 0x308030703060305, 0x30c030b030a0309, + 0x310030f030e030d, 0x314031303120311, 0x318031703160315, + 0x31c031b031a0319, 0xffff031f031e031d, 0xffffffff0320ffff, + 0xffff03220321ffff, 0xffff0324ffff0323, 0xffffffffffff0325, + 0x326ffffffffffff, 0xffff0327ffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x329ffff0328ffff, 0x32bffff032affff, + 0x32dffff032cffff, 0x32fffff032effff, 0x331ffff0330ffff, + 0x333ffff0332ffff, 0x335ffff0334ffff, 0x337ffff0336ffff, + 0x339ffff0338ffff, 0x33bffff033affff, 0x33dffff033cffff, + 0x33fffff033effff, 0x341ffff0340ffff, 0x343ffff0342ffff, + 0x345ffff0344ffff, 0x347ffff0346ffff, 0x349ffff0348ffff, + 0x34bffff034affff, 0x34dffff034cffff, 0x34fffff034effff, + 0x351ffff0350ffff, 0x353ffff0352ffff, 0x355ffff0354ffff, + 0x357ffff0356ffff, 0x359ffff0358ffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffff035bffff035a, 0x35cffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x360035f035e035d, 0x364036303620361, 0x368036703660365, + 0x36c036b036a0369, 0x370036f036e036d, 0x374037303720371, + 0x378037703760375, 0x37c037b037a0379, 0x380037f037e037d, + 0x383ffff03820381, 0xffffffffffffffff, 0xffffffff0384ffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x386ffff0385ffff, 0x388ffff0387ffff, + 0x38affff0389ffff, 0x38cffff038bffff, 0x38effff038dffff, + 0x390ffff038fffff, 0x392ffff0391ffff, 0x394ffff0393ffff, + 0x396ffff0395ffff, 0x398ffff0397ffff, 0x39affff0399ffff, + 0xffffffff039bffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x39dffff039cffff, + 0x39fffff039effff, 0x3a1ffff03a0ffff, 0x3a3ffff03a2ffff, + 0x3a5ffff03a4ffff, 0x3a7ffff03a6ffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x3a8ffffffffffff, + 0x3aaffff03a9ffff, 0x3acffff03abffff, 0x3aeffff03adffff, + 0x3afffffffffffff, 0x3b1ffff03b0ffff, 0x3b3ffff03b2ffff, + 0x3b5ffff03b4ffff, 0x3b7ffff03b6ffff, 0x3b9ffff03b8ffff, + 0x3bbffff03baffff, 0x3bdffff03bcffff, 0x3bfffff03beffff, + 0x3c1ffff03c0ffff, 0x3c3ffff03c2ffff, 0x3c5ffff03c4ffff, + 0x3c7ffff03c6ffff, 0x3c9ffff03c8ffff, 0x3cbffff03caffff, + 0x3cdffff03ccffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffff03ceffffffff, 0x3d0ffffffff03cf, 0x3d2ffff03d1ffff, + 0x3d4ffff03d3ffff, 0xffffffffffffffff, 0xffffffffffff03d5, + 0x3d7ffff03d6ffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x3d9ffff03d8ffff, 0x3dbffff03daffff, + 0xffffffff03dcffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x428042604240422, 0xffff0430042e042b, 0xffffffffffffffff, + 0xffffffffffffffff, 0x434ffffffffffff, 0x43c043a04380436, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x3df03de03ddffff, 0x3e303e203e103e0, + 0x3e703e603e503e4, 0x3eb03ea03e903e8, 0x3ef03ee03ed03ec, + 0x3f303f203f103f0, 0xffff03f603f503f4, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x3fa03f903f803f7, 0x3fe03fd03fc03fb, 0x4020401040003ff, + 0x406040504040403, 0x40a040904080407, 0x40e040d040c040b, + 0x41204110410040f, 0x416041504140413, 0x41a041904180417, + 0x41e041d041c041b, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff]); +//8064 bytes +enum toUpperSimpleIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([0x0, 0x20, + 0x100], [0x100, 0x380, 0xbc0], [0x402030202020100, 0x202020202020205, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x3000200010000, 0x7000600050004, 0xa00090008, 0xd000c000b0000, + 0x110010000f000e, 0x1400130012, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x150000, 0x19001800170016, 0x1d001c001b001a, 0x0, 0x1f001e0000, + 0x0, 0x0, 0x20000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x24002300220021, 0x25, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2700260000, 0x2a00290028, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2b0000, 0x0, 0x0, + 0x0, 0x0, 0x2d002c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x200010000ffff, + 0x6000500040003, 0xa000900080007, 0xe000d000c000b, 0x1200110010000f, + 0x16001500140013, 0xffff001900180017, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffff001affff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x1e001d001c001b, 0x2200210020001f, 0x26002500240023, 0x2a002900280027, + 0x2e002d002c002b, 0xffff00310030002f, 0x35003400330032, + 0x39003800370036, 0x3bffff003affff, 0x3dffff003cffff, 0x3fffff003effff, + 0x41ffff0040ffff, 0x43ffff0042ffff, 0x45ffff0044ffff, 0x47ffff0046ffff, + 0x49ffff0048ffff, 0x4bffff004affff, 0x4dffff004cffff, 0x4fffff004effff, + 0x51ffff0050ffff, 0x53ffff0052ffff, 0x55ffff0054ffff, + 0xffff0056ffffffff, 0xffff0058ffff0057, 0xffff005affff0059, + 0xffff005cffff005b, 0x5effffffff005d, 0x60ffff005fffff, + 0x62ffff0061ffff, 0x64ffff0063ffff, 0x66ffff0065ffff, 0x68ffff0067ffff, + 0x6affff0069ffff, 0x6cffff006bffff, 0x6effff006dffff, 0x70ffff006fffff, + 0x72ffff0071ffff, 0x74ffff0073ffff, 0xffff0075ffffffff, + 0x780077ffff0076, 0x7affffffff0079, 0xffffffff007bffff, + 0xffffffffffff007c, 0xffffffffffff007d, 0xffff007effffffff, + 0xffffffff007fffff, 0xffff00810080ffff, 0xffff0082ffffffff, + 0x84ffff0083ffff, 0xffffffff0085ffff, 0xffffffffffff0086, + 0xffffffff0087ffff, 0xffffffffffff0088, 0xffff008affff0089, + 0xffffffff008bffff, 0x8dffff008cffff, 0xffffffffffffffff, + 0xffff008f008effff, 0x92ffff00910090, 0xffff0094ffff0093, + 0xffff0096ffff0095, 0xffff0098ffff0097, 0xffff009affff0099, + 0x9dffff009c009b, 0x9fffff009effff, 0xa1ffff00a0ffff, 0xa3ffff00a2ffff, + 0xa5ffff00a4ffff, 0xa700a6ffffffff, 0xffffffff00a8ffff, + 0xaaffff00a9ffff, 0xacffff00abffff, 0xaeffff00adffff, 0xb0ffff00afffff, + 0xb2ffff00b1ffff, 0xb4ffff00b3ffff, 0xb6ffff00b5ffff, 0xb8ffff00b7ffff, + 0xbaffff00b9ffff, 0xbcffff00bbffff, 0xbdffffffffffff, 0xbfffff00beffff, + 0xc1ffff00c0ffff, 0xc3ffff00c2ffff, 0xc5ffff00c4ffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xc7ffffffff00c6, + 0xffff00c9ffff00c8, 0xcaffffffffffff, 0xccffff00cbffff, + 0xceffff00cdffff, 0xd200d100d000cf, 0xd500d4ffff00d3, 0xd7ffff00d6ffff, + 0xffffffffffffffff, 0xd9ffffffff00d8, 0xffff00db00daffff, + 0xdeffff00dd00dc, 0xdfffffffffffff, 0xffff00e100e0ffff, + 0xffffffff00e2ffff, 0xffffffffffffffff, 0xffffffff00e3ffff, + 0xe5ffffffff00e4, 0xffffffffffffffff, 0xe900e800e700e6, + 0xffffffffffff00ea, 0xffff00ebffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffff00ecffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xeeffff00edffff, 0xefffffffffffff, + 0xf0ffffffffffff, 0xffffffff00f200f1, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xf600f500f400f3, 0xf900f800f7ffff, + 0xfd00fc00fb00fa, 0x101010000ff00fe, 0x105010401030102, + 0x109010801070106, 0x10d010c010b010a, 0x1110110010f010e, + 0xffff011401130112, 0xffffffff01160115, 0x11901180117ffff, + 0x11bffff011affff, 0x11dffff011cffff, 0x11fffff011effff, + 0x121ffff0120ffff, 0x123ffff0122ffff, 0x125ffff0124ffff, + 0xffff012801270126, 0xffffffff0129ffff, 0x12bffffffff012a, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x12f012e012d012c, 0x133013201310130, + 0x137013601350134, 0x13b013a01390138, 0x13f013e013d013c, + 0x143014201410140, 0x147014601450144, 0x14b014a01490148, + 0x14f014e014d014c, 0x153015201510150, 0x157015601550154, + 0x15b015a01590158, 0x15dffff015cffff, 0x15fffff015effff, + 0x161ffff0160ffff, 0x163ffff0162ffff, 0x165ffff0164ffff, + 0x167ffff0166ffff, 0x169ffff0168ffff, 0x16bffff016affff, + 0xffffffff016cffff, 0xffffffffffffffff, 0x16dffffffffffff, + 0x16fffff016effff, 0x171ffff0170ffff, 0x173ffff0172ffff, + 0x175ffff0174ffff, 0x177ffff0176ffff, 0x179ffff0178ffff, + 0x17bffff017affff, 0x17dffff017cffff, 0x17fffff017effff, + 0x181ffff0180ffff, 0x183ffff0182ffff, 0x185ffff0184ffff, + 0x187ffff0186ffff, 0xffff0188ffffffff, 0xffff018affff0189, + 0xffff018cffff018b, 0x18f018effff018d, 0x191ffff0190ffff, + 0x193ffff0192ffff, 0x195ffff0194ffff, 0x197ffff0196ffff, + 0x199ffff0198ffff, 0x19bffff019affff, 0x19dffff019cffff, + 0x19fffff019effff, 0x1a1ffff01a0ffff, 0x1a3ffff01a2ffff, + 0x1a5ffff01a4ffff, 0x1a7ffff01a6ffff, 0x1a9ffff01a8ffff, + 0x1abffff01aaffff, 0x1adffff01acffff, 0x1afffff01aeffff, + 0x1b1ffff01b0ffff, 0x1b3ffff01b2ffff, 0x1b5ffff01b4ffff, + 0x1b7ffff01b6ffff, 0x1b9ffff01b8ffff, 0x1bbffff01baffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x1be01bd01bcffff, + 0x1c201c101c001bf, 0x1c601c501c401c3, 0x1ca01c901c801c7, + 0x1ce01cd01cc01cb, 0x1d201d101d001cf, 0x1d601d501d401d3, + 0x1da01d901d801d7, 0x1de01dd01dc01db, 0xffff01e101e001df, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffff01e2ffff, 0xffffffff01e3ffff, + 0x1e5ffff01e4ffff, 0x1e7ffff01e6ffff, 0x1e9ffff01e8ffff, + 0x1ebffff01eaffff, 0x1edffff01ecffff, 0x1efffff01eeffff, + 0x1f1ffff01f0ffff, 0x1f3ffff01f2ffff, 0x1f5ffff01f4ffff, + 0x1f7ffff01f6ffff, 0x1f9ffff01f8ffff, 0x1fbffff01faffff, + 0x1fdffff01fcffff, 0x1ffffff01feffff, 0x201ffff0200ffff, + 0x203ffff0202ffff, 0x205ffff0204ffff, 0x207ffff0206ffff, + 0x209ffff0208ffff, 0x20bffff020affff, 0x20dffff020cffff, + 0x20fffff020effff, 0x211ffff0210ffff, 0x213ffff0212ffff, + 0x215ffff0214ffff, 0x217ffff0216ffff, 0x219ffff0218ffff, + 0x21bffff021affff, 0x21dffff021cffff, 0x21fffff021effff, + 0x221ffff0220ffff, 0x223ffff0222ffff, 0x225ffff0224ffff, + 0x227ffff0226ffff, 0x229ffff0228ffff, 0x22bffff022affff, + 0x22dffff022cffff, 0xffffffff022effff, 0x22fffffffffffff, + 0xffffffffffffffff, 0x231ffff0230ffff, 0x233ffff0232ffff, + 0x235ffff0234ffff, 0x237ffff0236ffff, 0x239ffff0238ffff, + 0x23bffff023affff, 0x23dffff023cffff, 0x23fffff023effff, + 0x241ffff0240ffff, 0x243ffff0242ffff, 0x245ffff0244ffff, + 0x247ffff0246ffff, 0x249ffff0248ffff, 0x24bffff024affff, + 0x24dffff024cffff, 0x24fffff024effff, 0x251ffff0250ffff, + 0x253ffff0252ffff, 0x255ffff0254ffff, 0x257ffff0256ffff, + 0x259ffff0258ffff, 0x25bffff025affff, 0x25dffff025cffff, + 0x25fffff025effff, 0x263026202610260, 0x267026602650264, + 0xffffffffffffffff, 0xffffffffffffffff, 0x26b026a02690268, + 0xffffffff026d026c, 0xffffffffffffffff, 0xffffffffffffffff, + 0x2710270026f026e, 0x275027402730272, 0xffffffffffffffff, + 0xffffffffffffffff, 0x279027802770276, 0x27d027c027b027a, + 0xffffffffffffffff, 0xffffffffffffffff, 0x2810280027f027e, + 0xffffffff02830282, 0xffffffffffffffff, 0xffffffffffffffff, + 0x285ffff0284ffff, 0x287ffff0286ffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x28b028a02890288, 0x28f028e028d028c, + 0xffffffffffffffff, 0xffffffffffffffff, 0x293029202910290, + 0x297029602950294, 0x29b029a02990298, 0xffffffff029d029c, + 0x2a102a0029f029e, 0x2a502a402a302a2, 0xffffffffffffffff, + 0xffffffffffffffff, 0x2a902a802a702a6, 0x2ad02ac02ab02aa, + 0xffffffffffffffff, 0xffffffffffffffff, 0x2b102b002af02ae, + 0x2b502b402b302b2, 0xffffffffffffffff, 0xffffffffffffffff, + 0x2b8ffff02b702b6, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffff02b9ffffffff, 0x2baffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffff02bc02bb, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffff02be02bd, 0xffffffff02bfffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x2c0ffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffff02c1ffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x2c502c402c302c2, + 0x2c902c802c702c6, 0x2cd02cc02cb02ca, 0x2d102d002cf02ce, + 0xffffffffffffffff, 0xffffffffffff02d2, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x2d602d502d402d3, + 0x2da02d902d802d7, 0x2de02dd02dc02db, 0x2e202e102e002df, + 0x2e602e502e402e3, 0x2ea02e902e802e7, 0xffffffff02ec02eb, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x2f002ef02ee02ed, + 0x2f402f302f202f1, 0x2f802f702f602f5, 0x2fc02fb02fa02f9, + 0x30002ff02fe02fd, 0x304030303020301, 0x308030703060305, + 0x30c030b030a0309, 0x310030f030e030d, 0x314031303120311, + 0x318031703160315, 0xffff031b031a0319, 0xffffffff031cffff, + 0xffff031e031dffff, 0xffff0320ffff031f, 0xffffffffffff0321, + 0x322ffffffffffff, 0xffff0323ffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x325ffff0324ffff, 0x327ffff0326ffff, + 0x329ffff0328ffff, 0x32bffff032affff, 0x32dffff032cffff, + 0x32fffff032effff, 0x331ffff0330ffff, 0x333ffff0332ffff, + 0x335ffff0334ffff, 0x337ffff0336ffff, 0x339ffff0338ffff, + 0x33bffff033affff, 0x33dffff033cffff, 0x33fffff033effff, + 0x341ffff0340ffff, 0x343ffff0342ffff, 0x345ffff0344ffff, + 0x347ffff0346ffff, 0x349ffff0348ffff, 0x34bffff034affff, + 0x34dffff034cffff, 0x34fffff034effff, 0x351ffff0350ffff, + 0x353ffff0352ffff, 0x355ffff0354ffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffff0357ffff0356, 0x358ffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x35c035b035a0359, 0x360035f035e035d, 0x364036303620361, + 0x368036703660365, 0x36c036b036a0369, 0x370036f036e036d, + 0x374037303720371, 0x378037703760375, 0x37c037b037a0379, + 0x37fffff037e037d, 0xffffffffffffffff, 0xffffffff0380ffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x382ffff0381ffff, 0x384ffff0383ffff, + 0x386ffff0385ffff, 0x388ffff0387ffff, 0x38affff0389ffff, + 0x38cffff038bffff, 0x38effff038dffff, 0x390ffff038fffff, + 0x392ffff0391ffff, 0x394ffff0393ffff, 0x396ffff0395ffff, + 0xffffffff0397ffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x399ffff0398ffff, + 0x39bffff039affff, 0x39dffff039cffff, 0x39fffff039effff, + 0x3a1ffff03a0ffff, 0x3a3ffff03a2ffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x3a4ffffffffffff, + 0x3a6ffff03a5ffff, 0x3a8ffff03a7ffff, 0x3aaffff03a9ffff, + 0x3abffffffffffff, 0x3adffff03acffff, 0x3afffff03aeffff, + 0x3b1ffff03b0ffff, 0x3b3ffff03b2ffff, 0x3b5ffff03b4ffff, + 0x3b7ffff03b6ffff, 0x3b9ffff03b8ffff, 0x3bbffff03baffff, + 0x3bdffff03bcffff, 0x3bfffff03beffff, 0x3c1ffff03c0ffff, + 0x3c3ffff03c2ffff, 0x3c5ffff03c4ffff, 0x3c7ffff03c6ffff, + 0x3c9ffff03c8ffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffff03caffffffff, 0x3ccffffffff03cb, 0x3ceffff03cdffff, + 0x3d0ffff03cfffff, 0xffffffffffffffff, 0xffffffffffff03d1, + 0x3d3ffff03d2ffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x3d5ffff03d4ffff, 0x3d7ffff03d6ffff, + 0xffffffff03d8ffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x3db03da03d9ffff, 0x3df03de03dd03dc, 0x3e303e203e103e0, + 0x3e703e603e503e4, 0x3eb03ea03e903e8, 0x3ef03ee03ed03ec, + 0xffff03f203f103f0, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x3f603f503f403f3, + 0x3fa03f903f803f7, 0x3fe03fd03fc03fb, 0x4020401040003ff, + 0x406040504040403, 0x40a040904080407, 0x40e040d040c040b, + 0x41204110410040f, 0x416041504140413, 0x41a041904180417, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff]); +//7808 bytes +enum toLowerSimpleIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([0x0, 0x20, + 0x100], [0x100, 0x380, 0xb40], [0x402030202020100, 0x202020202020205, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x2000000010000, 0x6000500040003, 0x80007, 0xb000a00090000, + 0xf000e000d000c, 0x110010, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x13001200000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x17001600150014, 0x1b001a00190018, 0x0, + 0x1e001d001c, 0x0, 0x0, 0x20001f00000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x24002300220021, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2600250000, 0x2900280027, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2a, 0x0, + 0x0, 0x0, 0x0, 0x2b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x200010000ffff, 0x6000500040003, 0xa000900080007, 0xe000d000c000b, + 0x1200110010000f, 0x16001500140013, 0xffff001900180017, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x1d001c001b001a, 0x210020001f001e, 0x25002400230022, 0x29002800270026, + 0x2d002c002b002a, 0xffff0030002f002e, 0x34003300320031, + 0xffff003700360035, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffff0039ffff0038, 0xffff003bffff003a, 0xffff003dffff003c, + 0xffff003fffff003e, 0xffff0041ffff0040, 0xffff0043ffff0042, + 0xffff0045ffff0044, 0xffff0047ffff0046, 0xffff0049ffff0048, + 0xffff004bffff004a, 0xffff004dffff004c, 0xffff004fffff004e, + 0xffff0051ffff0050, 0xffff0053ffff0052, 0x55ffff0054ffff, + 0x57ffff0056ffff, 0x59ffff0058ffff, 0x5bffff005affff, + 0xffff005cffffffff, 0xffff005effff005d, 0xffff0060ffff005f, + 0xffff0062ffff0061, 0xffff0064ffff0063, 0xffff0066ffff0065, + 0xffff0068ffff0067, 0xffff006affff0069, 0xffff006cffff006b, + 0xffff006effff006d, 0xffff0070ffff006f, 0xffff0072ffff0071, + 0x75ffff00740073, 0xffffffff0076ffff, 0xffff00780077ffff, + 0x7b007affff0079, 0x7e007d007cffff, 0x80007fffffffff, 0x83ffff00820081, + 0x860085ffff0084, 0xffffffffffff0087, 0x8affff00890088, + 0xffff008cffff008b, 0x8f008effff008d, 0xffffffff0090ffff, + 0x930092ffff0091, 0x9600950094ffff, 0x98ffff0097ffff, + 0xffffffffffff0099, 0xffffffffffff009a, 0xffffffffffffffff, + 0x9dffff009c009b, 0xa0009fffff009e, 0xa2ffff00a1ffff, 0xa4ffff00a3ffff, + 0xa6ffff00a5ffff, 0xa8ffff00a7ffff, 0xffff00a9ffffffff, + 0xffff00abffff00aa, 0xffff00adffff00ac, 0xffff00afffff00ae, + 0xffff00b1ffff00b0, 0xffff00b300b2ffff, 0xb600b5ffff00b4, + 0xffff00b8ffff00b7, 0xffff00baffff00b9, 0xffff00bcffff00bb, + 0xffff00beffff00bd, 0xffff00c0ffff00bf, 0xffff00c2ffff00c1, + 0xffff00c4ffff00c3, 0xffff00c6ffff00c5, 0xffff00c8ffff00c7, + 0xffff00caffff00c9, 0xffff00ccffff00cb, 0xffff00ceffff00cd, + 0xffff00d0ffff00cf, 0xffff00d2ffff00d1, 0xffff00d4ffff00d3, + 0xffffffffffffffff, 0xd600d5ffffffff, 0xffff00d800d7ffff, + 0xdaffff00d9ffff, 0xffff00dd00dc00db, 0xffff00dfffff00de, + 0xffff00e1ffff00e0, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffff00e3ffff00e2, 0xffff00e4ffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffff00e5ffffffff, 0xffff00e800e700e6, 0xeb00eaffff00e9, + 0xee00ed00ecffff, 0xf200f100f000ef, 0xf600f500f400f3, 0xfa00f900f800f7, + 0xfdffff00fc00fb, 0x101010000ff00fe, 0x105010401030102, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x106ffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffff0108ffff0107, + 0xffff010affff0109, 0xffff010cffff010b, 0xffff010effff010d, + 0xffff0110ffff010f, 0xffff0112ffff0111, 0xffffffffffffffff, + 0x114ffffffff0113, 0xffff01160115ffff, 0x11901180117ffff, + 0x11d011c011b011a, 0x1210120011f011e, 0x125012401230122, + 0x129012801270126, 0x12d012c012b012a, 0x1310130012f012e, + 0x135013401330132, 0x139013801370136, 0x13d013c013b013a, + 0x1410140013f013e, 0x145014401430142, 0x149014801470146, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffff014bffff014a, 0xffff014dffff014c, 0xffff014fffff014e, + 0xffff0151ffff0150, 0xffff0153ffff0152, 0xffff0155ffff0154, + 0xffff0157ffff0156, 0xffff0159ffff0158, 0xffffffffffff015a, + 0xffffffffffffffff, 0xffff015bffffffff, 0xffff015dffff015c, + 0xffff015fffff015e, 0xffff0161ffff0160, 0xffff0163ffff0162, + 0xffff0165ffff0164, 0xffff0167ffff0166, 0xffff0169ffff0168, + 0xffff016bffff016a, 0xffff016dffff016c, 0xffff016fffff016e, + 0xffff0171ffff0170, 0xffff0173ffff0172, 0xffff0175ffff0174, + 0x178ffff01770176, 0x17affff0179ffff, 0x17cffff017bffff, + 0xffffffff017dffff, 0xffff017fffff017e, 0xffff0181ffff0180, + 0xffff0183ffff0182, 0xffff0185ffff0184, 0xffff0187ffff0186, + 0xffff0189ffff0188, 0xffff018bffff018a, 0xffff018dffff018c, + 0xffff018fffff018e, 0xffff0191ffff0190, 0xffff0193ffff0192, + 0xffff0195ffff0194, 0xffff0197ffff0196, 0xffff0199ffff0198, + 0xffff019bffff019a, 0xffff019dffff019c, 0xffff019fffff019e, + 0xffff01a1ffff01a0, 0xffff01a3ffff01a2, 0xffff01a5ffff01a4, + 0xffff01a7ffff01a6, 0xffff01a9ffff01a8, 0xffffffffffffffff, + 0xffffffffffffffff, 0x1ac01ab01aaffff, 0x1b001af01ae01ad, + 0x1b401b301b201b1, 0x1b801b701b601b5, 0x1bc01bb01ba01b9, + 0x1c001bf01be01bd, 0x1c401c301c201c1, 0x1c801c701c601c5, + 0x1cc01cb01ca01c9, 0xffff01cf01ce01cd, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x1d301d201d101d0, + 0x1d701d601d501d4, 0x1db01da01d901d8, 0x1df01de01dd01dc, + 0x1e301e201e101e0, 0x1e701e601e501e4, 0x1eb01ea01e901e8, + 0x1ef01ee01ed01ec, 0x1f301f201f101f0, 0x1f6ffff01f501f4, + 0xffffffffffffffff, 0xffffffff01f7ffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffff01f9ffff01f8, + 0xffff01fbffff01fa, 0xffff01fdffff01fc, 0xffff01ffffff01fe, + 0xffff0201ffff0200, 0xffff0203ffff0202, 0xffff0205ffff0204, + 0xffff0207ffff0206, 0xffff0209ffff0208, 0xffff020bffff020a, + 0xffff020dffff020c, 0xffff020fffff020e, 0xffff0211ffff0210, + 0xffff0213ffff0212, 0xffff0215ffff0214, 0xffff0217ffff0216, + 0xffff0219ffff0218, 0xffff021bffff021a, 0xffff021dffff021c, + 0xffff021fffff021e, 0xffff0221ffff0220, 0xffff0223ffff0222, + 0xffff0225ffff0224, 0xffff0227ffff0226, 0xffff0229ffff0228, + 0xffff022bffff022a, 0xffff022dffff022c, 0xffff022fffff022e, + 0xffff0231ffff0230, 0xffff0233ffff0232, 0xffff0235ffff0234, + 0xffff0237ffff0236, 0xffff0239ffff0238, 0xffff023bffff023a, + 0xffff023dffff023c, 0xffff023fffff023e, 0xffff0241ffff0240, + 0xffffffffffff0242, 0xffffffffffffffff, 0xffff0243ffffffff, + 0xffff0245ffff0244, 0xffff0247ffff0246, 0xffff0249ffff0248, + 0xffff024bffff024a, 0xffff024dffff024c, 0xffff024fffff024e, + 0xffff0251ffff0250, 0xffff0253ffff0252, 0xffff0255ffff0254, + 0xffff0257ffff0256, 0xffff0259ffff0258, 0xffff025bffff025a, + 0xffff025dffff025c, 0xffff025fffff025e, 0xffff0261ffff0260, + 0xffff0263ffff0262, 0xffff0265ffff0264, 0xffff0267ffff0266, + 0xffff0269ffff0268, 0xffff026bffff026a, 0xffff026dffff026c, + 0xffff026fffff026e, 0xffff0271ffff0270, 0xffff0273ffff0272, + 0xffffffffffffffff, 0xffffffffffffffff, 0x277027602750274, + 0x27b027a02790278, 0xffffffffffffffff, 0xffffffffffffffff, + 0x27f027e027d027c, 0xffffffff02810280, 0xffffffffffffffff, + 0xffffffffffffffff, 0x285028402830282, 0x289028802870286, + 0xffffffffffffffff, 0xffffffffffffffff, 0x28d028c028b028a, + 0x2910290028f028e, 0xffffffffffffffff, 0xffffffffffffffff, + 0x295029402930292, 0xffffffff02970296, 0xffffffffffffffff, + 0xffffffffffffffff, 0x299ffff0298ffff, 0x29bffff029affff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x29f029e029d029c, + 0x2a302a202a102a0, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x2a702a602a502a4, 0x2ab02aa02a902a8, + 0xffffffffffffffff, 0xffffffffffffffff, 0x2af02ae02ad02ac, + 0x2b302b202b102b0, 0xffffffffffffffff, 0xffffffffffffffff, + 0x2b702b602b502b4, 0x2bb02ba02b902b8, 0xffffffffffffffff, + 0xffffffffffffffff, 0x2bf02be02bd02bc, 0xffffffffffff02c0, + 0xffffffffffffffff, 0xffffffffffffffff, 0x2c402c302c202c1, + 0xffffffffffff02c5, 0xffffffffffffffff, 0xffffffffffffffff, + 0x2c902c802c702c6, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x2cd02cc02cb02ca, 0xffffffffffff02ce, + 0xffffffffffffffff, 0xffffffffffffffff, 0x2d202d102d002cf, + 0xffffffffffff02d3, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffff02d4ffffffff, 0x2d602d5ffffffff, + 0xffffffffffffffff, 0xffff02d7ffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x2db02da02d902d8, 0x2df02de02dd02dc, + 0x2e302e202e102e0, 0x2e702e602e502e4, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x2e8ffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x2ea02e9ffffffff, + 0x2ee02ed02ec02eb, 0x2f202f102f002ef, 0x2f602f502f402f3, + 0x2fa02f902f802f7, 0x2fe02fd02fc02fb, 0x3020301030002ff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x306030503040303, 0x30a030903080307, 0x30e030d030c030b, + 0x31203110310030f, 0x316031503140313, 0x31a031903180317, + 0x31e031d031c031b, 0x32203210320031f, 0x326032503240323, + 0x32a032903280327, 0x32e032d032c032b, 0xffff03310330032f, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x3340333ffff0332, 0x336ffffffff0335, 0x338ffff0337ffff, + 0x33b033a0339ffff, 0xffff033dffff033c, 0xffffffff033effff, + 0xffffffffffffffff, 0x340033fffffffff, 0xffff0342ffff0341, + 0xffff0344ffff0343, 0xffff0346ffff0345, 0xffff0348ffff0347, + 0xffff034affff0349, 0xffff034cffff034b, 0xffff034effff034d, + 0xffff0350ffff034f, 0xffff0352ffff0351, 0xffff0354ffff0353, + 0xffff0356ffff0355, 0xffff0358ffff0357, 0xffff035affff0359, + 0xffff035cffff035b, 0xffff035effff035d, 0xffff0360ffff035f, + 0xffff0362ffff0361, 0xffff0364ffff0363, 0xffff0366ffff0365, + 0xffff0368ffff0367, 0xffff036affff0369, 0xffff036cffff036b, + 0xffff036effff036d, 0xffff0370ffff036f, 0xffff0372ffff0371, + 0xffffffffffffffff, 0x373ffffffffffff, 0xffffffff0374ffff, + 0xffff0375ffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffff0377ffff0376, 0xffff0379ffff0378, + 0xffff037bffff037a, 0xffff037dffff037c, 0xffff037fffff037e, + 0xffff0381ffff0380, 0xffff0383ffff0382, 0xffff0385ffff0384, + 0xffff0387ffff0386, 0xffff0389ffff0388, 0xffff038bffff038a, + 0xffffffffffff038c, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffff038effff038d, + 0xffff0390ffff038f, 0xffff0392ffff0391, 0xffff0394ffff0393, + 0xffff0396ffff0395, 0xffff0398ffff0397, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffff0399ffffffff, + 0xffff039bffff039a, 0xffff039dffff039c, 0xffff039fffff039e, + 0xffff03a0ffffffff, 0xffff03a2ffff03a1, 0xffff03a4ffff03a3, + 0xffff03a6ffff03a5, 0xffff03a8ffff03a7, 0xffff03aaffff03a9, + 0xffff03acffff03ab, 0xffff03aeffff03ad, 0xffff03b0ffff03af, + 0xffff03b2ffff03b1, 0xffff03b4ffff03b3, 0xffff03b6ffff03b5, + 0xffff03b8ffff03b7, 0xffff03baffff03b9, 0xffff03bcffff03bb, + 0xffff03beffff03bd, 0xffffffffffffffff, 0xffffffffffffffff, + 0x3c0ffff03bfffff, 0xffff03c203c1ffff, 0xffff03c4ffff03c3, + 0xffff03c6ffff03c5, 0x3c7ffffffffffff, 0xffffffff03c8ffff, + 0xffff03caffff03c9, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffff03ccffff03cb, 0xffff03ceffff03cd, + 0xffff03d0ffff03cf, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x3d303d203d1ffff, + 0x3d703d603d503d4, 0x3db03da03d903d8, 0x3df03de03dd03dc, + 0x3e303e203e103e0, 0x3e703e603e503e4, 0xffff03ea03e903e8, + 0xffffffffffffffff, 0x3ee03ed03ec03eb, 0x3f203f103f003ef, + 0x3f603f503f403f3, 0x3fa03f903f803f7, 0x3fe03fd03fc03fb, + 0x4020401040003ff, 0x406040504040403, 0x40a040904080407, + 0x40e040d040c040b, 0x41204110410040f, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff]); +//8064 bytes +enum toTitleSimpleIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([0x0, 0x20, + 0x100], [0x100, 0x380, 0xbc0], [0x402030202020100, 0x202020202020205, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x202020202020202, 0x202020202020202, 0x202020202020202, + 0x3000200010000, 0x7000600050004, 0xa00090008, 0xd000c000b0000, + 0x110010000f000e, 0x1400130012, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x150000, 0x19001800170016, 0x1d001c001b001a, 0x0, 0x1f001e0000, + 0x0, 0x0, 0x20000000000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x24002300220021, 0x25, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2700260000, 0x2a00290028, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2b0000, 0x0, 0x0, + 0x0, 0x0, 0x2d002c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x200010000ffff, + 0x6000500040003, 0xa000900080007, 0xe000d000c000b, 0x1200110010000f, + 0x16001500140013, 0xffff001900180017, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffff001affff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x1e001d001c001b, 0x2200210020001f, 0x26002500240023, 0x2a002900280027, + 0x2e002d002c002b, 0xffff00310030002f, 0x35003400330032, + 0x39003800370036, 0x3bffff003affff, 0x3dffff003cffff, 0x3fffff003effff, + 0x41ffff0040ffff, 0x43ffff0042ffff, 0x45ffff0044ffff, 0x47ffff0046ffff, + 0x49ffff0048ffff, 0x4bffff004affff, 0x4dffff004cffff, 0x4fffff004effff, + 0x51ffff0050ffff, 0x53ffff0052ffff, 0x55ffff0054ffff, + 0xffff0056ffffffff, 0xffff0058ffff0057, 0xffff005affff0059, + 0xffff005cffff005b, 0x5effffffff005d, 0x60ffff005fffff, + 0x62ffff0061ffff, 0x64ffff0063ffff, 0x66ffff0065ffff, 0x68ffff0067ffff, + 0x6affff0069ffff, 0x6cffff006bffff, 0x6effff006dffff, 0x70ffff006fffff, + 0x72ffff0071ffff, 0x74ffff0073ffff, 0xffff0075ffffffff, + 0x780077ffff0076, 0x7affffffff0079, 0xffffffff007bffff, + 0xffffffffffff007c, 0xffffffffffff007d, 0xffff007effffffff, + 0xffffffff007fffff, 0xffff00810080ffff, 0xffff0082ffffffff, + 0x84ffff0083ffff, 0xffffffff0085ffff, 0xffffffffffff0086, + 0xffffffff0087ffff, 0xffffffffffff0088, 0xffff008affff0089, + 0xffffffff008bffff, 0x8dffff008cffff, 0xffffffffffffffff, + 0x910090008f008e, 0x95009400930092, 0xffff0097ffff0096, + 0xffff0099ffff0098, 0xffff009bffff009a, 0xffff009dffff009c, + 0xa0ffff009f009e, 0xa2ffff00a1ffff, 0xa4ffff00a3ffff, 0xa6ffff00a5ffff, + 0xa8ffff00a7ffff, 0xab00aa00a9ffff, 0xffffffff00acffff, + 0xaeffff00adffff, 0xb0ffff00afffff, 0xb2ffff00b1ffff, 0xb4ffff00b3ffff, + 0xb6ffff00b5ffff, 0xb8ffff00b7ffff, 0xbaffff00b9ffff, 0xbcffff00bbffff, + 0xbeffff00bdffff, 0xc0ffff00bfffff, 0xc1ffffffffffff, 0xc3ffff00c2ffff, + 0xc5ffff00c4ffff, 0xc7ffff00c6ffff, 0xc9ffff00c8ffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xcbffffffff00ca, + 0xffff00cdffff00cc, 0xceffffffffffff, 0xd0ffff00cfffff, + 0xd2ffff00d1ffff, 0xd600d500d400d3, 0xd900d8ffff00d7, 0xdbffff00daffff, + 0xffffffffffffffff, 0xddffffffff00dc, 0xffff00df00deffff, + 0xe2ffff00e100e0, 0xe3ffffffffffff, 0xffff00e500e4ffff, + 0xffffffff00e6ffff, 0xffffffffffffffff, 0xffffffff00e7ffff, + 0xe9ffffffff00e8, 0xffffffffffffffff, 0xed00ec00eb00ea, + 0xffffffffffff00ee, 0xffff00efffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffff00f0ffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xf2ffff00f1ffff, 0xf3ffffffffffff, + 0xf4ffffffffffff, 0xffffffff00f600f5, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xfa00f900f800f7, 0xfd00fc00fbffff, + 0x101010000ff00fe, 0x105010401030102, 0x109010801070106, + 0x10d010c010b010a, 0x1110110010f010e, 0x115011401130112, + 0xffff011801170116, 0xffffffff011a0119, 0x11d011c011bffff, + 0x11fffff011effff, 0x121ffff0120ffff, 0x123ffff0122ffff, + 0x125ffff0124ffff, 0x127ffff0126ffff, 0x129ffff0128ffff, + 0xffff012c012b012a, 0xffffffff012dffff, 0x12fffffffff012e, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x133013201310130, 0x137013601350134, + 0x13b013a01390138, 0x13f013e013d013c, 0x143014201410140, + 0x147014601450144, 0x14b014a01490148, 0x14f014e014d014c, + 0x153015201510150, 0x157015601550154, 0x15b015a01590158, + 0x15f015e015d015c, 0x161ffff0160ffff, 0x163ffff0162ffff, + 0x165ffff0164ffff, 0x167ffff0166ffff, 0x169ffff0168ffff, + 0x16bffff016affff, 0x16dffff016cffff, 0x16fffff016effff, + 0xffffffff0170ffff, 0xffffffffffffffff, 0x171ffffffffffff, + 0x173ffff0172ffff, 0x175ffff0174ffff, 0x177ffff0176ffff, + 0x179ffff0178ffff, 0x17bffff017affff, 0x17dffff017cffff, + 0x17fffff017effff, 0x181ffff0180ffff, 0x183ffff0182ffff, + 0x185ffff0184ffff, 0x187ffff0186ffff, 0x189ffff0188ffff, + 0x18bffff018affff, 0xffff018cffffffff, 0xffff018effff018d, + 0xffff0190ffff018f, 0x1930192ffff0191, 0x195ffff0194ffff, + 0x197ffff0196ffff, 0x199ffff0198ffff, 0x19bffff019affff, + 0x19dffff019cffff, 0x19fffff019effff, 0x1a1ffff01a0ffff, + 0x1a3ffff01a2ffff, 0x1a5ffff01a4ffff, 0x1a7ffff01a6ffff, + 0x1a9ffff01a8ffff, 0x1abffff01aaffff, 0x1adffff01acffff, + 0x1afffff01aeffff, 0x1b1ffff01b0ffff, 0x1b3ffff01b2ffff, + 0x1b5ffff01b4ffff, 0x1b7ffff01b6ffff, 0x1b9ffff01b8ffff, + 0x1bbffff01baffff, 0x1bdffff01bcffff, 0x1bfffff01beffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x1c201c101c0ffff, + 0x1c601c501c401c3, 0x1ca01c901c801c7, 0x1ce01cd01cc01cb, + 0x1d201d101d001cf, 0x1d601d501d401d3, 0x1da01d901d801d7, + 0x1de01dd01dc01db, 0x1e201e101e001df, 0xffff01e501e401e3, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffff01e6ffff, 0xffffffff01e7ffff, + 0x1e9ffff01e8ffff, 0x1ebffff01eaffff, 0x1edffff01ecffff, + 0x1efffff01eeffff, 0x1f1ffff01f0ffff, 0x1f3ffff01f2ffff, + 0x1f5ffff01f4ffff, 0x1f7ffff01f6ffff, 0x1f9ffff01f8ffff, + 0x1fbffff01faffff, 0x1fdffff01fcffff, 0x1ffffff01feffff, + 0x201ffff0200ffff, 0x203ffff0202ffff, 0x205ffff0204ffff, + 0x207ffff0206ffff, 0x209ffff0208ffff, 0x20bffff020affff, + 0x20dffff020cffff, 0x20fffff020effff, 0x211ffff0210ffff, + 0x213ffff0212ffff, 0x215ffff0214ffff, 0x217ffff0216ffff, + 0x219ffff0218ffff, 0x21bffff021affff, 0x21dffff021cffff, + 0x21fffff021effff, 0x221ffff0220ffff, 0x223ffff0222ffff, + 0x225ffff0224ffff, 0x227ffff0226ffff, 0x229ffff0228ffff, + 0x22bffff022affff, 0x22dffff022cffff, 0x22fffff022effff, + 0x231ffff0230ffff, 0xffffffff0232ffff, 0x233ffffffffffff, + 0xffffffffffffffff, 0x235ffff0234ffff, 0x237ffff0236ffff, + 0x239ffff0238ffff, 0x23bffff023affff, 0x23dffff023cffff, + 0x23fffff023effff, 0x241ffff0240ffff, 0x243ffff0242ffff, + 0x245ffff0244ffff, 0x247ffff0246ffff, 0x249ffff0248ffff, + 0x24bffff024affff, 0x24dffff024cffff, 0x24fffff024effff, + 0x251ffff0250ffff, 0x253ffff0252ffff, 0x255ffff0254ffff, + 0x257ffff0256ffff, 0x259ffff0258ffff, 0x25bffff025affff, + 0x25dffff025cffff, 0x25fffff025effff, 0x261ffff0260ffff, + 0x263ffff0262ffff, 0x267026602650264, 0x26b026a02690268, + 0xffffffffffffffff, 0xffffffffffffffff, 0x26f026e026d026c, + 0xffffffff02710270, 0xffffffffffffffff, 0xffffffffffffffff, + 0x275027402730272, 0x279027802770276, 0xffffffffffffffff, + 0xffffffffffffffff, 0x27d027c027b027a, 0x2810280027f027e, + 0xffffffffffffffff, 0xffffffffffffffff, 0x285028402830282, + 0xffffffff02870286, 0xffffffffffffffff, 0xffffffffffffffff, + 0x289ffff0288ffff, 0x28bffff028affff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x28f028e028d028c, 0x293029202910290, + 0xffffffffffffffff, 0xffffffffffffffff, 0x297029602950294, + 0x29b029a02990298, 0x29f029e029d029c, 0xffffffff02a102a0, + 0x2a502a402a302a2, 0x2a902a802a702a6, 0xffffffffffffffff, + 0xffffffffffffffff, 0x2ad02ac02ab02aa, 0x2b102b002af02ae, + 0xffffffffffffffff, 0xffffffffffffffff, 0x2b502b402b302b2, + 0x2b902b802b702b6, 0xffffffffffffffff, 0xffffffffffffffff, + 0x2bcffff02bb02ba, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffff02bdffffffff, 0x2beffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffff02c002bf, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffff02c202c1, 0xffffffff02c3ffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x2c4ffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffff02c5ffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x2c902c802c702c6, + 0x2cd02cc02cb02ca, 0x2d102d002cf02ce, 0x2d502d402d302d2, + 0xffffffffffffffff, 0xffffffffffff02d6, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x2da02d902d802d7, + 0x2de02dd02dc02db, 0x2e202e102e002df, 0x2e602e502e402e3, + 0x2ea02e902e802e7, 0x2ee02ed02ec02eb, 0xffffffff02f002ef, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x2f402f302f202f1, + 0x2f802f702f602f5, 0x2fc02fb02fa02f9, 0x30002ff02fe02fd, + 0x304030303020301, 0x308030703060305, 0x30c030b030a0309, + 0x310030f030e030d, 0x314031303120311, 0x318031703160315, + 0x31c031b031a0319, 0xffff031f031e031d, 0xffffffff0320ffff, + 0xffff03220321ffff, 0xffff0324ffff0323, 0xffffffffffff0325, + 0x326ffffffffffff, 0xffff0327ffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x329ffff0328ffff, 0x32bffff032affff, + 0x32dffff032cffff, 0x32fffff032effff, 0x331ffff0330ffff, + 0x333ffff0332ffff, 0x335ffff0334ffff, 0x337ffff0336ffff, + 0x339ffff0338ffff, 0x33bffff033affff, 0x33dffff033cffff, + 0x33fffff033effff, 0x341ffff0340ffff, 0x343ffff0342ffff, + 0x345ffff0344ffff, 0x347ffff0346ffff, 0x349ffff0348ffff, + 0x34bffff034affff, 0x34dffff034cffff, 0x34fffff034effff, + 0x351ffff0350ffff, 0x353ffff0352ffff, 0x355ffff0354ffff, + 0x357ffff0356ffff, 0x359ffff0358ffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffff035bffff035a, 0x35cffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x360035f035e035d, 0x364036303620361, 0x368036703660365, + 0x36c036b036a0369, 0x370036f036e036d, 0x374037303720371, + 0x378037703760375, 0x37c037b037a0379, 0x380037f037e037d, + 0x383ffff03820381, 0xffffffffffffffff, 0xffffffff0384ffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x386ffff0385ffff, 0x388ffff0387ffff, + 0x38affff0389ffff, 0x38cffff038bffff, 0x38effff038dffff, + 0x390ffff038fffff, 0x392ffff0391ffff, 0x394ffff0393ffff, + 0x396ffff0395ffff, 0x398ffff0397ffff, 0x39affff0399ffff, + 0xffffffff039bffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x39dffff039cffff, + 0x39fffff039effff, 0x3a1ffff03a0ffff, 0x3a3ffff03a2ffff, + 0x3a5ffff03a4ffff, 0x3a7ffff03a6ffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x3a8ffffffffffff, + 0x3aaffff03a9ffff, 0x3acffff03abffff, 0x3aeffff03adffff, + 0x3afffffffffffff, 0x3b1ffff03b0ffff, 0x3b3ffff03b2ffff, + 0x3b5ffff03b4ffff, 0x3b7ffff03b6ffff, 0x3b9ffff03b8ffff, + 0x3bbffff03baffff, 0x3bdffff03bcffff, 0x3bfffff03beffff, + 0x3c1ffff03c0ffff, 0x3c3ffff03c2ffff, 0x3c5ffff03c4ffff, + 0x3c7ffff03c6ffff, 0x3c9ffff03c8ffff, 0x3cbffff03caffff, + 0x3cdffff03ccffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffff03ceffffffff, 0x3d0ffffffff03cf, 0x3d2ffff03d1ffff, + 0x3d4ffff03d3ffff, 0xffffffffffffffff, 0xffffffffffff03d5, + 0x3d7ffff03d6ffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0x3d9ffff03d8ffff, 0x3dbffff03daffff, + 0xffffffff03dcffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0x3df03de03ddffff, 0x3e303e203e103e0, 0x3e703e603e503e4, + 0x3eb03ea03e903e8, 0x3ef03ee03ed03ec, 0x3f303f203f103f0, + 0xffff03f603f503f4, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0x3fa03f903f803f7, + 0x3fe03fd03fc03fb, 0x4020401040003ff, 0x406040504040403, + 0x40a040904080407, 0x40e040d040c040b, 0x41204110410040f, + 0x416041504140413, 0x41a041904180417, 0x41e041d041c041b, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff]); +@property +{ + private alias _IUA = immutable(uint[]); + _IUA toUpperTable() + { + static _IUA t = [ + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, + 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, + 0x57, 0x58, 0x59, 0x5a, 0x39c, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, + 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, + 0xdd, 0xde, 0x178, 0x100, 0x102, 0x104, 0x106, 0x108, 0x10a, 0x10c, + 0x10e, 0x110, 0x112, 0x114, 0x116, 0x118, 0x11a, 0x11c, 0x11e, + 0x120, 0x122, 0x124, 0x126, 0x128, 0x12a, 0x12c, 0x12e, 0x49, + 0x132, 0x134, 0x136, 0x139, 0x13b, 0x13d, 0x13f, 0x141, 0x143, + 0x145, 0x147, 0x14a, 0x14c, 0x14e, 0x150, 0x152, 0x154, 0x156, + 0x158, 0x15a, 0x15c, 0x15e, 0x160, 0x162, 0x164, 0x166, 0x168, + 0x16a, 0x16c, 0x16e, 0x170, 0x172, 0x174, 0x176, 0x179, 0x17b, + 0x17d, 0x53, 0x243, 0x182, 0x184, 0x187, 0x18b, 0x191, 0x1f6, + 0x198, 0x23d, 0x220, 0x1a0, 0x1a2, 0x1a4, 0x1a7, 0x1ac, 0x1af, + 0x1b3, 0x1b5, 0x1b8, 0x1bc, 0x1f7, 0x1c4, 0x1c4, 0x1c7, 0x1c7, + 0x1ca, 0x1ca, 0x1cd, 0x1cf, 0x1d1, 0x1d3, 0x1d5, 0x1d7, 0x1d9, + 0x1db, 0x18e, 0x1de, 0x1e0, 0x1e2, 0x1e4, 0x1e6, 0x1e8, 0x1ea, + 0x1ec, 0x1ee, 0x1f1, 0x1f1, 0x1f4, 0x1f8, 0x1fa, 0x1fc, 0x1fe, + 0x200, 0x202, 0x204, 0x206, 0x208, 0x20a, 0x20c, 0x20e, 0x210, + 0x212, 0x214, 0x216, 0x218, 0x21a, 0x21c, 0x21e, 0x222, 0x224, + 0x226, 0x228, 0x22a, 0x22c, 0x22e, 0x230, 0x232, 0x23b, 0x2c7e, + 0x2c7f, 0x241, 0x246, 0x248, 0x24a, 0x24c, 0x24e, 0x2c6f, 0x2c6d, + 0x2c70, 0x181, 0x186, 0x189, 0x18a, 0x18f, 0x190, 0x193, 0x194, + 0xa78d, 0xa7aa, 0x197, 0x196, 0x2c62, 0x19c, 0x2c6e, 0x19d, 0x19f, + 0x2c64, 0x1a6, 0x1a9, 0x1ae, 0x244, 0x1b1, 0x1b2, 0x245, 0x1b7, + 0x399, 0x370, 0x372, 0x376, 0x3fd, 0x3fe, 0x3ff, 0x386, 0x388, + 0x389, 0x38a, 0x391, 0x392, 0x393, 0x394, 0x395, 0x396, 0x397, + 0x398, 0x399, 0x39a, 0x39b, 0x39c, 0x39d, 0x39e, 0x39f, 0x3a0, + 0x3a1, 0x3a3, 0x3a3, 0x3a4, 0x3a5, 0x3a6, 0x3a7, 0x3a8, 0x3a9, + 0x3aa, 0x3ab, 0x38c, 0x38e, 0x38f, 0x392, 0x398, 0x3a6, 0x3a0, + 0x3cf, 0x3d8, 0x3da, 0x3dc, 0x3de, 0x3e0, 0x3e2, 0x3e4, 0x3e6, + 0x3e8, 0x3ea, 0x3ec, 0x3ee, 0x39a, 0x3a1, 0x3f9, 0x395, 0x3f7, + 0x3fa, 0x410, 0x411, 0x412, 0x413, 0x414, 0x415, 0x416, 0x417, + 0x418, 0x419, 0x41a, 0x41b, 0x41c, 0x41d, 0x41e, 0x41f, 0x420, + 0x421, 0x422, 0x423, 0x424, 0x425, 0x426, 0x427, 0x428, 0x429, + 0x42a, 0x42b, 0x42c, 0x42d, 0x42e, 0x42f, 0x400, 0x401, 0x402, + 0x403, 0x404, 0x405, 0x406, 0x407, 0x408, 0x409, 0x40a, 0x40b, + 0x40c, 0x40d, 0x40e, 0x40f, 0x460, 0x462, 0x464, 0x466, 0x468, + 0x46a, 0x46c, 0x46e, 0x470, 0x472, 0x474, 0x476, 0x478, 0x47a, + 0x47c, 0x47e, 0x480, 0x48a, 0x48c, 0x48e, 0x490, 0x492, 0x494, + 0x496, 0x498, 0x49a, 0x49c, 0x49e, 0x4a0, 0x4a2, 0x4a4, 0x4a6, + 0x4a8, 0x4aa, 0x4ac, 0x4ae, 0x4b0, 0x4b2, 0x4b4, 0x4b6, 0x4b8, + 0x4ba, 0x4bc, 0x4be, 0x4c1, 0x4c3, 0x4c5, 0x4c7, 0x4c9, 0x4cb, + 0x4cd, 0x4c0, 0x4d0, 0x4d2, 0x4d4, 0x4d6, 0x4d8, 0x4da, 0x4dc, + 0x4de, 0x4e0, 0x4e2, 0x4e4, 0x4e6, 0x4e8, 0x4ea, 0x4ec, 0x4ee, + 0x4f0, 0x4f2, 0x4f4, 0x4f6, 0x4f8, 0x4fa, 0x4fc, 0x4fe, 0x500, + 0x502, 0x504, 0x506, 0x508, 0x50a, 0x50c, 0x50e, 0x510, 0x512, + 0x514, 0x516, 0x518, 0x51a, 0x51c, 0x51e, 0x520, 0x522, 0x524, + 0x526, 0x531, 0x532, 0x533, 0x534, 0x535, 0x536, 0x537, 0x538, + 0x539, 0x53a, 0x53b, 0x53c, 0x53d, 0x53e, 0x53f, 0x540, 0x541, + 0x542, 0x543, 0x544, 0x545, 0x546, 0x547, 0x548, 0x549, 0x54a, + 0x54b, 0x54c, 0x54d, 0x54e, 0x54f, 0x550, 0x551, 0x552, 0x553, + 0x554, 0x555, 0x556, 0xa77d, 0x2c63, 0x1e00, 0x1e02, 0x1e04, + 0x1e06, 0x1e08, 0x1e0a, 0x1e0c, 0x1e0e, 0x1e10, 0x1e12, 0x1e14, + 0x1e16, 0x1e18, 0x1e1a, 0x1e1c, 0x1e1e, 0x1e20, 0x1e22, 0x1e24, + 0x1e26, 0x1e28, 0x1e2a, 0x1e2c, 0x1e2e, 0x1e30, 0x1e32, 0x1e34, + 0x1e36, 0x1e38, 0x1e3a, 0x1e3c, 0x1e3e, 0x1e40, 0x1e42, 0x1e44, + 0x1e46, 0x1e48, 0x1e4a, 0x1e4c, 0x1e4e, 0x1e50, 0x1e52, 0x1e54, + 0x1e56, 0x1e58, 0x1e5a, 0x1e5c, 0x1e5e, 0x1e60, 0x1e62, 0x1e64, + 0x1e66, 0x1e68, 0x1e6a, 0x1e6c, 0x1e6e, 0x1e70, 0x1e72, 0x1e74, + 0x1e76, 0x1e78, 0x1e7a, 0x1e7c, 0x1e7e, 0x1e80, 0x1e82, 0x1e84, + 0x1e86, 0x1e88, 0x1e8a, 0x1e8c, 0x1e8e, 0x1e90, 0x1e92, 0x1e94, + 0x1e60, 0x1ea0, 0x1ea2, 0x1ea4, 0x1ea6, 0x1ea8, 0x1eaa, 0x1eac, + 0x1eae, 0x1eb0, 0x1eb2, 0x1eb4, 0x1eb6, 0x1eb8, 0x1eba, 0x1ebc, + 0x1ebe, 0x1ec0, 0x1ec2, 0x1ec4, 0x1ec6, 0x1ec8, 0x1eca, 0x1ecc, + 0x1ece, 0x1ed0, 0x1ed2, 0x1ed4, 0x1ed6, 0x1ed8, 0x1eda, 0x1edc, + 0x1ede, 0x1ee0, 0x1ee2, 0x1ee4, 0x1ee6, 0x1ee8, 0x1eea, 0x1eec, + 0x1eee, 0x1ef0, 0x1ef2, 0x1ef4, 0x1ef6, 0x1ef8, 0x1efa, 0x1efc, + 0x1efe, 0x1f08, 0x1f09, 0x1f0a, 0x1f0b, 0x1f0c, 0x1f0d, 0x1f0e, + 0x1f0f, 0x1f18, 0x1f19, 0x1f1a, 0x1f1b, 0x1f1c, 0x1f1d, 0x1f28, + 0x1f29, 0x1f2a, 0x1f2b, 0x1f2c, 0x1f2d, 0x1f2e, 0x1f2f, 0x1f38, + 0x1f39, 0x1f3a, 0x1f3b, 0x1f3c, 0x1f3d, 0x1f3e, 0x1f3f, 0x1f48, + 0x1f49, 0x1f4a, 0x1f4b, 0x1f4c, 0x1f4d, 0x1f59, 0x1f5b, 0x1f5d, + 0x1f5f, 0x1f68, 0x1f69, 0x1f6a, 0x1f6b, 0x1f6c, 0x1f6d, 0x1f6e, + 0x1f6f, 0x1fba, 0x1fbb, 0x1fc8, 0x1fc9, 0x1fca, 0x1fcb, 0x1fda, + 0x1fdb, 0x1ff8, 0x1ff9, 0x1fea, 0x1feb, 0x1ffa, 0x1ffb, 0x1f88, + 0x1f89, 0x1f8a, 0x1f8b, 0x1f8c, 0x1f8d, 0x1f8e, 0x1f8f, 0x1f98, + 0x1f99, 0x1f9a, 0x1f9b, 0x1f9c, 0x1f9d, 0x1f9e, 0x1f9f, 0x1fa8, + 0x1fa9, 0x1faa, 0x1fab, 0x1fac, 0x1fad, 0x1fae, 0x1faf, 0x1fb8, + 0x1fb9, 0x1fbc, 0x399, 0x1fcc, 0x1fd8, 0x1fd9, 0x1fe8, 0x1fe9, + 0x1fec, 0x1ffc, 0x2132, 0x2160, 0x2161, 0x2162, 0x2163, 0x2164, + 0x2165, 0x2166, 0x2167, 0x2168, 0x2169, 0x216a, 0x216b, 0x216c, + 0x216d, 0x216e, 0x216f, 0x2183, 0x24b6, 0x24b7, 0x24b8, 0x24b9, + 0x24ba, 0x24bb, 0x24bc, 0x24bd, 0x24be, 0x24bf, 0x24c0, 0x24c1, + 0x24c2, 0x24c3, 0x24c4, 0x24c5, 0x24c6, 0x24c7, 0x24c8, 0x24c9, + 0x24ca, 0x24cb, 0x24cc, 0x24cd, 0x24ce, 0x24cf, 0x2c00, 0x2c01, + 0x2c02, 0x2c03, 0x2c04, 0x2c05, 0x2c06, 0x2c07, 0x2c08, 0x2c09, + 0x2c0a, 0x2c0b, 0x2c0c, 0x2c0d, 0x2c0e, 0x2c0f, 0x2c10, 0x2c11, + 0x2c12, 0x2c13, 0x2c14, 0x2c15, 0x2c16, 0x2c17, 0x2c18, 0x2c19, + 0x2c1a, 0x2c1b, 0x2c1c, 0x2c1d, 0x2c1e, 0x2c1f, 0x2c20, 0x2c21, + 0x2c22, 0x2c23, 0x2c24, 0x2c25, 0x2c26, 0x2c27, 0x2c28, 0x2c29, + 0x2c2a, 0x2c2b, 0x2c2c, 0x2c2d, 0x2c2e, 0x2c60, 0x23a, 0x23e, + 0x2c67, 0x2c69, 0x2c6b, 0x2c72, 0x2c75, 0x2c80, 0x2c82, 0x2c84, + 0x2c86, 0x2c88, 0x2c8a, 0x2c8c, 0x2c8e, 0x2c90, 0x2c92, 0x2c94, + 0x2c96, 0x2c98, 0x2c9a, 0x2c9c, 0x2c9e, 0x2ca0, 0x2ca2, 0x2ca4, + 0x2ca6, 0x2ca8, 0x2caa, 0x2cac, 0x2cae, 0x2cb0, 0x2cb2, 0x2cb4, + 0x2cb6, 0x2cb8, 0x2cba, 0x2cbc, 0x2cbe, 0x2cc0, 0x2cc2, 0x2cc4, + 0x2cc6, 0x2cc8, 0x2cca, 0x2ccc, 0x2cce, 0x2cd0, 0x2cd2, 0x2cd4, + 0x2cd6, 0x2cd8, 0x2cda, 0x2cdc, 0x2cde, 0x2ce0, 0x2ce2, 0x2ceb, + 0x2ced, 0x2cf2, 0x10a0, 0x10a1, 0x10a2, 0x10a3, 0x10a4, 0x10a5, + 0x10a6, 0x10a7, 0x10a8, 0x10a9, 0x10aa, 0x10ab, 0x10ac, 0x10ad, + 0x10ae, 0x10af, 0x10b0, 0x10b1, 0x10b2, 0x10b3, 0x10b4, 0x10b5, + 0x10b6, 0x10b7, 0x10b8, 0x10b9, 0x10ba, 0x10bb, 0x10bc, 0x10bd, + 0x10be, 0x10bf, 0x10c0, 0x10c1, 0x10c2, 0x10c3, 0x10c4, 0x10c5, + 0x10c7, 0x10cd, 0xa640, 0xa642, 0xa644, 0xa646, 0xa648, 0xa64a, + 0xa64c, 0xa64e, 0xa650, 0xa652, 0xa654, 0xa656, 0xa658, 0xa65a, + 0xa65c, 0xa65e, 0xa660, 0xa662, 0xa664, 0xa666, 0xa668, 0xa66a, + 0xa66c, 0xa680, 0xa682, 0xa684, 0xa686, 0xa688, 0xa68a, 0xa68c, + 0xa68e, 0xa690, 0xa692, 0xa694, 0xa696, 0xa722, 0xa724, 0xa726, + 0xa728, 0xa72a, 0xa72c, 0xa72e, 0xa732, 0xa734, 0xa736, 0xa738, + 0xa73a, 0xa73c, 0xa73e, 0xa740, 0xa742, 0xa744, 0xa746, 0xa748, + 0xa74a, 0xa74c, 0xa74e, 0xa750, 0xa752, 0xa754, 0xa756, 0xa758, + 0xa75a, 0xa75c, 0xa75e, 0xa760, 0xa762, 0xa764, 0xa766, 0xa768, + 0xa76a, 0xa76c, 0xa76e, 0xa779, 0xa77b, 0xa77e, 0xa780, 0xa782, + 0xa784, 0xa786, 0xa78b, 0xa790, 0xa792, 0xa7a0, 0xa7a2, 0xa7a4, + 0xa7a6, 0xa7a8, 0xff21, 0xff22, 0xff23, 0xff24, 0xff25, 0xff26, + 0xff27, 0xff28, 0xff29, 0xff2a, 0xff2b, 0xff2c, 0xff2d, 0xff2e, + 0xff2f, 0xff30, 0xff31, 0xff32, 0xff33, 0xff34, 0xff35, 0xff36, + 0xff37, 0xff38, 0xff39, 0xff3a, 0x10400, 0x10401, 0x10402, 0x10403, + 0x10404, 0x10405, 0x10406, 0x10407, 0x10408, 0x10409, 0x1040a, + 0x1040b, 0x1040c, 0x1040d, 0x1040e, 0x1040f, 0x10410, 0x10411, + 0x10412, 0x10413, 0x10414, 0x10415, 0x10416, 0x10417, 0x10418, + 0x10419, 0x1041a, 0x1041b, 0x1041c, 0x1041d, 0x1041e, 0x1041f, + 0x10420, 0x10421, 0x10422, 0x10423, 0x10424, 0x10425, 0x10426, + 0x10427, 0x2000053, 0x53, 0x130, 0x2000046, 0x46, 0x2000046, 0x49, + 0x2000046, 0x4c, 0x3000046, 0x46, 0x49, 0x3000046, 0x46, 0x4c, + 0x2000053, 0x54, 0x2000053, 0x54, 0x2000535, 0x552, 0x2000544, + 0x546, 0x2000544, 0x535, 0x2000544, 0x53b, 0x200054e, 0x546, + 0x2000544, 0x53d, 0x20002bc, 0x4e, 0x3000399, 0x308, 0x301, + 0x30003a5, 0x308, 0x301, 0x200004a, 0x30c, 0x2000048, 0x331, + 0x2000054, 0x308, 0x2000057, 0x30a, 0x2000059, 0x30a, 0x2000041, + 0x2be, 0x20003a5, 0x313, 0x30003a5, 0x313, 0x300, 0x30003a5, 0x313, + 0x301, 0x30003a5, 0x313, 0x342, 0x2000391, 0x342, 0x2000397, 0x342, + 0x3000399, 0x308, 0x300, 0x3000399, 0x308, 0x301, 0x2000399, 0x342, + 0x3000399, 0x308, 0x342, 0x30003a5, 0x308, 0x300, 0x30003a5, 0x308, + 0x301, 0x20003a1, 0x313, 0x20003a5, 0x342, 0x30003a5, 0x308, 0x342, + 0x20003a9, 0x342, 0x2001f08, 0x399, 0x2001f09, 0x399, 0x2001f0a, + 0x399, 0x2001f0b, 0x399, 0x2001f0c, 0x399, 0x2001f0d, 0x399, + 0x2001f0e, 0x399, 0x2001f0f, 0x399, 0x2001f08, 0x399, 0x2001f09, + 0x399, 0x2001f0a, 0x399, 0x2001f0b, 0x399, 0x2001f0c, 0x399, + 0x2001f0d, 0x399, 0x2001f0e, 0x399, 0x2001f0f, 0x399, 0x2001f28, + 0x399, 0x2001f29, 0x399, 0x2001f2a, 0x399, 0x2001f2b, 0x399, + 0x2001f2c, 0x399, 0x2001f2d, 0x399, 0x2001f2e, 0x399, 0x2001f2f, + 0x399, 0x2001f28, 0x399, 0x2001f29, 0x399, 0x2001f2a, 0x399, + 0x2001f2b, 0x399, 0x2001f2c, 0x399, 0x2001f2d, 0x399, 0x2001f2e, + 0x399, 0x2001f2f, 0x399, 0x2001f68, 0x399, 0x2001f69, 0x399, + 0x2001f6a, 0x399, 0x2001f6b, 0x399, 0x2001f6c, 0x399, 0x2001f6d, + 0x399, 0x2001f6e, 0x399, 0x2001f6f, 0x399, 0x2001f68, 0x399, + 0x2001f69, 0x399, 0x2001f6a, 0x399, 0x2001f6b, 0x399, 0x2001f6c, + 0x399, 0x2001f6d, 0x399, 0x2001f6e, 0x399, 0x2001f6f, 0x399, + 0x2000391, 0x399, 0x2000391, 0x399, 0x2000397, 0x399, 0x2000397, + 0x399, 0x20003a9, 0x399, 0x20003a9, 0x399, 0x2001fba, 0x399, + 0x2000386, 0x399, 0x2001fca, 0x399, 0x2000389, 0x399, 0x2001ffa, + 0x399, 0x200038f, 0x399, 0x3000391, 0x342, 0x399, 0x3000397, 0x342, + 0x399, 0x30003a9, 0x342, 0x399 + ]; + return t; + } + + _IUA toLowerTable() + { + static _IUA t = [ + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, + 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, + 0x77, 0x78, 0x79, 0x7a, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, + 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, + 0xfe, 0x101, 0x103, 0x105, 0x107, 0x109, 0x10b, 0x10d, 0x10f, + 0x111, 0x113, 0x115, 0x117, 0x119, 0x11b, 0x11d, 0x11f, 0x121, + 0x123, 0x125, 0x127, 0x129, 0x12b, 0x12d, 0x12f, 0x69, 0x133, + 0x135, 0x137, 0x13a, 0x13c, 0x13e, 0x140, 0x142, 0x144, 0x146, + 0x148, 0x14b, 0x14d, 0x14f, 0x151, 0x153, 0x155, 0x157, 0x159, + 0x15b, 0x15d, 0x15f, 0x161, 0x163, 0x165, 0x167, 0x169, 0x16b, + 0x16d, 0x16f, 0x171, 0x173, 0x175, 0x177, 0xff, 0x17a, 0x17c, + 0x17e, 0x253, 0x183, 0x185, 0x254, 0x188, 0x256, 0x257, 0x18c, + 0x1dd, 0x259, 0x25b, 0x192, 0x260, 0x263, 0x269, 0x268, 0x199, + 0x26f, 0x272, 0x275, 0x1a1, 0x1a3, 0x1a5, 0x280, 0x1a8, 0x283, + 0x1ad, 0x288, 0x1b0, 0x28a, 0x28b, 0x1b4, 0x1b6, 0x292, 0x1b9, + 0x1bd, 0x1c6, 0x1c6, 0x1c9, 0x1c9, 0x1cc, 0x1cc, 0x1ce, 0x1d0, + 0x1d2, 0x1d4, 0x1d6, 0x1d8, 0x1da, 0x1dc, 0x1df, 0x1e1, 0x1e3, + 0x1e5, 0x1e7, 0x1e9, 0x1eb, 0x1ed, 0x1ef, 0x1f3, 0x1f3, 0x1f5, + 0x195, 0x1bf, 0x1f9, 0x1fb, 0x1fd, 0x1ff, 0x201, 0x203, 0x205, + 0x207, 0x209, 0x20b, 0x20d, 0x20f, 0x211, 0x213, 0x215, 0x217, + 0x219, 0x21b, 0x21d, 0x21f, 0x19e, 0x223, 0x225, 0x227, 0x229, + 0x22b, 0x22d, 0x22f, 0x231, 0x233, 0x2c65, 0x23c, 0x19a, 0x2c66, + 0x242, 0x180, 0x289, 0x28c, 0x247, 0x249, 0x24b, 0x24d, 0x24f, + 0x371, 0x373, 0x377, 0x3ac, 0x3ad, 0x3ae, 0x3af, 0x3cc, 0x3cd, + 0x3ce, 0x3b1, 0x3b2, 0x3b3, 0x3b4, 0x3b5, 0x3b6, 0x3b7, 0x3b8, + 0x3b9, 0x3ba, 0x3bb, 0x3bc, 0x3bd, 0x3be, 0x3bf, 0x3c0, 0x3c1, + 0x3c3, 0x3c4, 0x3c5, 0x3c6, 0x3c7, 0x3c8, 0x3c9, 0x3ca, 0x3cb, + 0x3d7, 0x3d9, 0x3db, 0x3dd, 0x3df, 0x3e1, 0x3e3, 0x3e5, 0x3e7, + 0x3e9, 0x3eb, 0x3ed, 0x3ef, 0x3b8, 0x3f8, 0x3f2, 0x3fb, 0x37b, + 0x37c, 0x37d, 0x450, 0x451, 0x452, 0x453, 0x454, 0x455, 0x456, + 0x457, 0x458, 0x459, 0x45a, 0x45b, 0x45c, 0x45d, 0x45e, 0x45f, + 0x430, 0x431, 0x432, 0x433, 0x434, 0x435, 0x436, 0x437, 0x438, + 0x439, 0x43a, 0x43b, 0x43c, 0x43d, 0x43e, 0x43f, 0x440, 0x441, + 0x442, 0x443, 0x444, 0x445, 0x446, 0x447, 0x448, 0x449, 0x44a, + 0x44b, 0x44c, 0x44d, 0x44e, 0x44f, 0x461, 0x463, 0x465, 0x467, + 0x469, 0x46b, 0x46d, 0x46f, 0x471, 0x473, 0x475, 0x477, 0x479, + 0x47b, 0x47d, 0x47f, 0x481, 0x48b, 0x48d, 0x48f, 0x491, 0x493, + 0x495, 0x497, 0x499, 0x49b, 0x49d, 0x49f, 0x4a1, 0x4a3, 0x4a5, + 0x4a7, 0x4a9, 0x4ab, 0x4ad, 0x4af, 0x4b1, 0x4b3, 0x4b5, 0x4b7, + 0x4b9, 0x4bb, 0x4bd, 0x4bf, 0x4cf, 0x4c2, 0x4c4, 0x4c6, 0x4c8, + 0x4ca, 0x4cc, 0x4ce, 0x4d1, 0x4d3, 0x4d5, 0x4d7, 0x4d9, 0x4db, + 0x4dd, 0x4df, 0x4e1, 0x4e3, 0x4e5, 0x4e7, 0x4e9, 0x4eb, 0x4ed, + 0x4ef, 0x4f1, 0x4f3, 0x4f5, 0x4f7, 0x4f9, 0x4fb, 0x4fd, 0x4ff, + 0x501, 0x503, 0x505, 0x507, 0x509, 0x50b, 0x50d, 0x50f, 0x511, + 0x513, 0x515, 0x517, 0x519, 0x51b, 0x51d, 0x51f, 0x521, 0x523, + 0x525, 0x527, 0x561, 0x562, 0x563, 0x564, 0x565, 0x566, 0x567, + 0x568, 0x569, 0x56a, 0x56b, 0x56c, 0x56d, 0x56e, 0x56f, 0x570, + 0x571, 0x572, 0x573, 0x574, 0x575, 0x576, 0x577, 0x578, 0x579, + 0x57a, 0x57b, 0x57c, 0x57d, 0x57e, 0x57f, 0x580, 0x581, 0x582, + 0x583, 0x584, 0x585, 0x586, 0x2d00, 0x2d01, 0x2d02, 0x2d03, 0x2d04, + 0x2d05, 0x2d06, 0x2d07, 0x2d08, 0x2d09, 0x2d0a, 0x2d0b, 0x2d0c, + 0x2d0d, 0x2d0e, 0x2d0f, 0x2d10, 0x2d11, 0x2d12, 0x2d13, 0x2d14, + 0x2d15, 0x2d16, 0x2d17, 0x2d18, 0x2d19, 0x2d1a, 0x2d1b, 0x2d1c, + 0x2d1d, 0x2d1e, 0x2d1f, 0x2d20, 0x2d21, 0x2d22, 0x2d23, 0x2d24, + 0x2d25, 0x2d27, 0x2d2d, 0x1e01, 0x1e03, 0x1e05, 0x1e07, 0x1e09, + 0x1e0b, 0x1e0d, 0x1e0f, 0x1e11, 0x1e13, 0x1e15, 0x1e17, 0x1e19, + 0x1e1b, 0x1e1d, 0x1e1f, 0x1e21, 0x1e23, 0x1e25, 0x1e27, 0x1e29, + 0x1e2b, 0x1e2d, 0x1e2f, 0x1e31, 0x1e33, 0x1e35, 0x1e37, 0x1e39, + 0x1e3b, 0x1e3d, 0x1e3f, 0x1e41, 0x1e43, 0x1e45, 0x1e47, 0x1e49, + 0x1e4b, 0x1e4d, 0x1e4f, 0x1e51, 0x1e53, 0x1e55, 0x1e57, 0x1e59, + 0x1e5b, 0x1e5d, 0x1e5f, 0x1e61, 0x1e63, 0x1e65, 0x1e67, 0x1e69, + 0x1e6b, 0x1e6d, 0x1e6f, 0x1e71, 0x1e73, 0x1e75, 0x1e77, 0x1e79, + 0x1e7b, 0x1e7d, 0x1e7f, 0x1e81, 0x1e83, 0x1e85, 0x1e87, 0x1e89, + 0x1e8b, 0x1e8d, 0x1e8f, 0x1e91, 0x1e93, 0x1e95, 0xdf, 0x1ea1, + 0x1ea3, 0x1ea5, 0x1ea7, 0x1ea9, 0x1eab, 0x1ead, 0x1eaf, 0x1eb1, + 0x1eb3, 0x1eb5, 0x1eb7, 0x1eb9, 0x1ebb, 0x1ebd, 0x1ebf, 0x1ec1, + 0x1ec3, 0x1ec5, 0x1ec7, 0x1ec9, 0x1ecb, 0x1ecd, 0x1ecf, 0x1ed1, + 0x1ed3, 0x1ed5, 0x1ed7, 0x1ed9, 0x1edb, 0x1edd, 0x1edf, 0x1ee1, + 0x1ee3, 0x1ee5, 0x1ee7, 0x1ee9, 0x1eeb, 0x1eed, 0x1eef, 0x1ef1, + 0x1ef3, 0x1ef5, 0x1ef7, 0x1ef9, 0x1efb, 0x1efd, 0x1eff, 0x1f00, + 0x1f01, 0x1f02, 0x1f03, 0x1f04, 0x1f05, 0x1f06, 0x1f07, 0x1f10, + 0x1f11, 0x1f12, 0x1f13, 0x1f14, 0x1f15, 0x1f20, 0x1f21, 0x1f22, + 0x1f23, 0x1f24, 0x1f25, 0x1f26, 0x1f27, 0x1f30, 0x1f31, 0x1f32, + 0x1f33, 0x1f34, 0x1f35, 0x1f36, 0x1f37, 0x1f40, 0x1f41, 0x1f42, + 0x1f43, 0x1f44, 0x1f45, 0x1f51, 0x1f53, 0x1f55, 0x1f57, 0x1f60, + 0x1f61, 0x1f62, 0x1f63, 0x1f64, 0x1f65, 0x1f66, 0x1f67, 0x1f80, + 0x1f81, 0x1f82, 0x1f83, 0x1f84, 0x1f85, 0x1f86, 0x1f87, 0x1f90, + 0x1f91, 0x1f92, 0x1f93, 0x1f94, 0x1f95, 0x1f96, 0x1f97, 0x1fa0, + 0x1fa1, 0x1fa2, 0x1fa3, 0x1fa4, 0x1fa5, 0x1fa6, 0x1fa7, 0x1fb0, + 0x1fb1, 0x1f70, 0x1f71, 0x1fb3, 0x1f72, 0x1f73, 0x1f74, 0x1f75, + 0x1fc3, 0x1fd0, 0x1fd1, 0x1f76, 0x1f77, 0x1fe0, 0x1fe1, 0x1f7a, + 0x1f7b, 0x1fe5, 0x1f78, 0x1f79, 0x1f7c, 0x1f7d, 0x1ff3, 0x3c9, + 0x6b, 0xe5, 0x214e, 0x2170, 0x2171, 0x2172, 0x2173, 0x2174, 0x2175, + 0x2176, 0x2177, 0x2178, 0x2179, 0x217a, 0x217b, 0x217c, 0x217d, + 0x217e, 0x217f, 0x2184, 0x24d0, 0x24d1, 0x24d2, 0x24d3, 0x24d4, + 0x24d5, 0x24d6, 0x24d7, 0x24d8, 0x24d9, 0x24da, 0x24db, 0x24dc, + 0x24dd, 0x24de, 0x24df, 0x24e0, 0x24e1, 0x24e2, 0x24e3, 0x24e4, + 0x24e5, 0x24e6, 0x24e7, 0x24e8, 0x24e9, 0x2c30, 0x2c31, 0x2c32, + 0x2c33, 0x2c34, 0x2c35, 0x2c36, 0x2c37, 0x2c38, 0x2c39, 0x2c3a, + 0x2c3b, 0x2c3c, 0x2c3d, 0x2c3e, 0x2c3f, 0x2c40, 0x2c41, 0x2c42, + 0x2c43, 0x2c44, 0x2c45, 0x2c46, 0x2c47, 0x2c48, 0x2c49, 0x2c4a, + 0x2c4b, 0x2c4c, 0x2c4d, 0x2c4e, 0x2c4f, 0x2c50, 0x2c51, 0x2c52, + 0x2c53, 0x2c54, 0x2c55, 0x2c56, 0x2c57, 0x2c58, 0x2c59, 0x2c5a, + 0x2c5b, 0x2c5c, 0x2c5d, 0x2c5e, 0x2c61, 0x26b, 0x1d7d, 0x27d, + 0x2c68, 0x2c6a, 0x2c6c, 0x251, 0x271, 0x250, 0x252, 0x2c73, 0x2c76, + 0x23f, 0x240, 0x2c81, 0x2c83, 0x2c85, 0x2c87, 0x2c89, 0x2c8b, + 0x2c8d, 0x2c8f, 0x2c91, 0x2c93, 0x2c95, 0x2c97, 0x2c99, 0x2c9b, + 0x2c9d, 0x2c9f, 0x2ca1, 0x2ca3, 0x2ca5, 0x2ca7, 0x2ca9, 0x2cab, + 0x2cad, 0x2caf, 0x2cb1, 0x2cb3, 0x2cb5, 0x2cb7, 0x2cb9, 0x2cbb, + 0x2cbd, 0x2cbf, 0x2cc1, 0x2cc3, 0x2cc5, 0x2cc7, 0x2cc9, 0x2ccb, + 0x2ccd, 0x2ccf, 0x2cd1, 0x2cd3, 0x2cd5, 0x2cd7, 0x2cd9, 0x2cdb, + 0x2cdd, 0x2cdf, 0x2ce1, 0x2ce3, 0x2cec, 0x2cee, 0x2cf3, 0xa641, + 0xa643, 0xa645, 0xa647, 0xa649, 0xa64b, 0xa64d, 0xa64f, 0xa651, + 0xa653, 0xa655, 0xa657, 0xa659, 0xa65b, 0xa65d, 0xa65f, 0xa661, + 0xa663, 0xa665, 0xa667, 0xa669, 0xa66b, 0xa66d, 0xa681, 0xa683, + 0xa685, 0xa687, 0xa689, 0xa68b, 0xa68d, 0xa68f, 0xa691, 0xa693, + 0xa695, 0xa697, 0xa723, 0xa725, 0xa727, 0xa729, 0xa72b, 0xa72d, + 0xa72f, 0xa733, 0xa735, 0xa737, 0xa739, 0xa73b, 0xa73d, 0xa73f, + 0xa741, 0xa743, 0xa745, 0xa747, 0xa749, 0xa74b, 0xa74d, 0xa74f, + 0xa751, 0xa753, 0xa755, 0xa757, 0xa759, 0xa75b, 0xa75d, 0xa75f, + 0xa761, 0xa763, 0xa765, 0xa767, 0xa769, 0xa76b, 0xa76d, 0xa76f, + 0xa77a, 0xa77c, 0x1d79, 0xa77f, 0xa781, 0xa783, 0xa785, 0xa787, + 0xa78c, 0x265, 0xa791, 0xa793, 0xa7a1, 0xa7a3, 0xa7a5, 0xa7a7, + 0xa7a9, 0x266, 0xff41, 0xff42, 0xff43, 0xff44, 0xff45, 0xff46, + 0xff47, 0xff48, 0xff49, 0xff4a, 0xff4b, 0xff4c, 0xff4d, 0xff4e, + 0xff4f, 0xff50, 0xff51, 0xff52, 0xff53, 0xff54, 0xff55, 0xff56, + 0xff57, 0xff58, 0xff59, 0xff5a, 0x10428, 0x10429, 0x1042a, 0x1042b, + 0x1042c, 0x1042d, 0x1042e, 0x1042f, 0x10430, 0x10431, 0x10432, + 0x10433, 0x10434, 0x10435, 0x10436, 0x10437, 0x10438, 0x10439, + 0x1043a, 0x1043b, 0x1043c, 0x1043d, 0x1043e, 0x1043f, 0x10440, + 0x10441, 0x10442, 0x10443, 0x10444, 0x10445, 0x10446, 0x10447, + 0x10448, 0x10449, 0x1044a, 0x1044b, 0x1044c, 0x1044d, 0x1044e, + 0x1044f, 0xdf, 0x2000069, 0x307, 0xfb00, 0xfb01, 0xfb02, 0xfb03, + 0xfb04, 0xfb05, 0xfb06, 0x587, 0xfb13, 0xfb14, 0xfb15, 0xfb16, + 0xfb17, 0x149, 0x390, 0x3b0, 0x1f0, 0x1e96, 0x1e97, 0x1e98, 0x1e99, + 0x1e9a, 0x1f50, 0x1f52, 0x1f54, 0x1f56, 0x1fb6, 0x1fc6, 0x1fd2, + 0x1fd3, 0x1fd6, 0x1fd7, 0x1fe2, 0x1fe3, 0x1fe4, 0x1fe6, 0x1fe7, + 0x1ff6, 0x1f80, 0x1f81, 0x1f82, 0x1f83, 0x1f84, 0x1f85, 0x1f86, + 0x1f87, 0x1f80, 0x1f81, 0x1f82, 0x1f83, 0x1f84, 0x1f85, 0x1f86, + 0x1f87, 0x1f90, 0x1f91, 0x1f92, 0x1f93, 0x1f94, 0x1f95, 0x1f96, + 0x1f97, 0x1f90, 0x1f91, 0x1f92, 0x1f93, 0x1f94, 0x1f95, 0x1f96, + 0x1f97, 0x1fa0, 0x1fa1, 0x1fa2, 0x1fa3, 0x1fa4, 0x1fa5, 0x1fa6, + 0x1fa7, 0x1fa0, 0x1fa1, 0x1fa2, 0x1fa3, 0x1fa4, 0x1fa5, 0x1fa6, + 0x1fa7, 0x1fb3, 0x1fb3, 0x1fc3, 0x1fc3, 0x1ff3, 0x1ff3, 0x1fb2, + 0x1fb4, 0x1fc2, 0x1fc4, 0x1ff2, 0x1ff4, 0x1fb7, 0x1fc7, 0x1ff7 + ]; + return t; + } + + _IUA toTitleTable() + { + static _IUA t = [ + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, + 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, + 0x57, 0x58, 0x59, 0x5a, 0x39c, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, + 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, + 0xdd, 0xde, 0x178, 0x100, 0x102, 0x104, 0x106, 0x108, 0x10a, 0x10c, + 0x10e, 0x110, 0x112, 0x114, 0x116, 0x118, 0x11a, 0x11c, 0x11e, + 0x120, 0x122, 0x124, 0x126, 0x128, 0x12a, 0x12c, 0x12e, 0x49, + 0x132, 0x134, 0x136, 0x139, 0x13b, 0x13d, 0x13f, 0x141, 0x143, + 0x145, 0x147, 0x14a, 0x14c, 0x14e, 0x150, 0x152, 0x154, 0x156, + 0x158, 0x15a, 0x15c, 0x15e, 0x160, 0x162, 0x164, 0x166, 0x168, + 0x16a, 0x16c, 0x16e, 0x170, 0x172, 0x174, 0x176, 0x179, 0x17b, + 0x17d, 0x53, 0x243, 0x182, 0x184, 0x187, 0x18b, 0x191, 0x1f6, + 0x198, 0x23d, 0x220, 0x1a0, 0x1a2, 0x1a4, 0x1a7, 0x1ac, 0x1af, + 0x1b3, 0x1b5, 0x1b8, 0x1bc, 0x1f7, 0x1c5, 0x1c5, 0x1c5, 0x1c8, + 0x1c8, 0x1c8, 0x1cb, 0x1cb, 0x1cb, 0x1cd, 0x1cf, 0x1d1, 0x1d3, + 0x1d5, 0x1d7, 0x1d9, 0x1db, 0x18e, 0x1de, 0x1e0, 0x1e2, 0x1e4, + 0x1e6, 0x1e8, 0x1ea, 0x1ec, 0x1ee, 0x1f2, 0x1f2, 0x1f2, 0x1f4, + 0x1f8, 0x1fa, 0x1fc, 0x1fe, 0x200, 0x202, 0x204, 0x206, 0x208, + 0x20a, 0x20c, 0x20e, 0x210, 0x212, 0x214, 0x216, 0x218, 0x21a, + 0x21c, 0x21e, 0x222, 0x224, 0x226, 0x228, 0x22a, 0x22c, 0x22e, + 0x230, 0x232, 0x23b, 0x2c7e, 0x2c7f, 0x241, 0x246, 0x248, 0x24a, + 0x24c, 0x24e, 0x2c6f, 0x2c6d, 0x2c70, 0x181, 0x186, 0x189, 0x18a, + 0x18f, 0x190, 0x193, 0x194, 0xa78d, 0xa7aa, 0x197, 0x196, 0x2c62, + 0x19c, 0x2c6e, 0x19d, 0x19f, 0x2c64, 0x1a6, 0x1a9, 0x1ae, 0x244, + 0x1b1, 0x1b2, 0x245, 0x1b7, 0x399, 0x370, 0x372, 0x376, 0x3fd, + 0x3fe, 0x3ff, 0x386, 0x388, 0x389, 0x38a, 0x391, 0x392, 0x393, + 0x394, 0x395, 0x396, 0x397, 0x398, 0x399, 0x39a, 0x39b, 0x39c, + 0x39d, 0x39e, 0x39f, 0x3a0, 0x3a1, 0x3a3, 0x3a3, 0x3a4, 0x3a5, + 0x3a6, 0x3a7, 0x3a8, 0x3a9, 0x3aa, 0x3ab, 0x38c, 0x38e, 0x38f, + 0x392, 0x398, 0x3a6, 0x3a0, 0x3cf, 0x3d8, 0x3da, 0x3dc, 0x3de, + 0x3e0, 0x3e2, 0x3e4, 0x3e6, 0x3e8, 0x3ea, 0x3ec, 0x3ee, 0x39a, + 0x3a1, 0x3f9, 0x395, 0x3f7, 0x3fa, 0x410, 0x411, 0x412, 0x413, + 0x414, 0x415, 0x416, 0x417, 0x418, 0x419, 0x41a, 0x41b, 0x41c, + 0x41d, 0x41e, 0x41f, 0x420, 0x421, 0x422, 0x423, 0x424, 0x425, + 0x426, 0x427, 0x428, 0x429, 0x42a, 0x42b, 0x42c, 0x42d, 0x42e, + 0x42f, 0x400, 0x401, 0x402, 0x403, 0x404, 0x405, 0x406, 0x407, + 0x408, 0x409, 0x40a, 0x40b, 0x40c, 0x40d, 0x40e, 0x40f, 0x460, + 0x462, 0x464, 0x466, 0x468, 0x46a, 0x46c, 0x46e, 0x470, 0x472, + 0x474, 0x476, 0x478, 0x47a, 0x47c, 0x47e, 0x480, 0x48a, 0x48c, + 0x48e, 0x490, 0x492, 0x494, 0x496, 0x498, 0x49a, 0x49c, 0x49e, + 0x4a0, 0x4a2, 0x4a4, 0x4a6, 0x4a8, 0x4aa, 0x4ac, 0x4ae, 0x4b0, + 0x4b2, 0x4b4, 0x4b6, 0x4b8, 0x4ba, 0x4bc, 0x4be, 0x4c1, 0x4c3, + 0x4c5, 0x4c7, 0x4c9, 0x4cb, 0x4cd, 0x4c0, 0x4d0, 0x4d2, 0x4d4, + 0x4d6, 0x4d8, 0x4da, 0x4dc, 0x4de, 0x4e0, 0x4e2, 0x4e4, 0x4e6, + 0x4e8, 0x4ea, 0x4ec, 0x4ee, 0x4f0, 0x4f2, 0x4f4, 0x4f6, 0x4f8, + 0x4fa, 0x4fc, 0x4fe, 0x500, 0x502, 0x504, 0x506, 0x508, 0x50a, + 0x50c, 0x50e, 0x510, 0x512, 0x514, 0x516, 0x518, 0x51a, 0x51c, + 0x51e, 0x520, 0x522, 0x524, 0x526, 0x531, 0x532, 0x533, 0x534, + 0x535, 0x536, 0x537, 0x538, 0x539, 0x53a, 0x53b, 0x53c, 0x53d, + 0x53e, 0x53f, 0x540, 0x541, 0x542, 0x543, 0x544, 0x545, 0x546, + 0x547, 0x548, 0x549, 0x54a, 0x54b, 0x54c, 0x54d, 0x54e, 0x54f, + 0x550, 0x551, 0x552, 0x553, 0x554, 0x555, 0x556, 0xa77d, 0x2c63, + 0x1e00, 0x1e02, 0x1e04, 0x1e06, 0x1e08, 0x1e0a, 0x1e0c, 0x1e0e, + 0x1e10, 0x1e12, 0x1e14, 0x1e16, 0x1e18, 0x1e1a, 0x1e1c, 0x1e1e, + 0x1e20, 0x1e22, 0x1e24, 0x1e26, 0x1e28, 0x1e2a, 0x1e2c, 0x1e2e, + 0x1e30, 0x1e32, 0x1e34, 0x1e36, 0x1e38, 0x1e3a, 0x1e3c, 0x1e3e, + 0x1e40, 0x1e42, 0x1e44, 0x1e46, 0x1e48, 0x1e4a, 0x1e4c, 0x1e4e, + 0x1e50, 0x1e52, 0x1e54, 0x1e56, 0x1e58, 0x1e5a, 0x1e5c, 0x1e5e, + 0x1e60, 0x1e62, 0x1e64, 0x1e66, 0x1e68, 0x1e6a, 0x1e6c, 0x1e6e, + 0x1e70, 0x1e72, 0x1e74, 0x1e76, 0x1e78, 0x1e7a, 0x1e7c, 0x1e7e, + 0x1e80, 0x1e82, 0x1e84, 0x1e86, 0x1e88, 0x1e8a, 0x1e8c, 0x1e8e, + 0x1e90, 0x1e92, 0x1e94, 0x1e60, 0x1ea0, 0x1ea2, 0x1ea4, 0x1ea6, + 0x1ea8, 0x1eaa, 0x1eac, 0x1eae, 0x1eb0, 0x1eb2, 0x1eb4, 0x1eb6, + 0x1eb8, 0x1eba, 0x1ebc, 0x1ebe, 0x1ec0, 0x1ec2, 0x1ec4, 0x1ec6, + 0x1ec8, 0x1eca, 0x1ecc, 0x1ece, 0x1ed0, 0x1ed2, 0x1ed4, 0x1ed6, + 0x1ed8, 0x1eda, 0x1edc, 0x1ede, 0x1ee0, 0x1ee2, 0x1ee4, 0x1ee6, + 0x1ee8, 0x1eea, 0x1eec, 0x1eee, 0x1ef0, 0x1ef2, 0x1ef4, 0x1ef6, + 0x1ef8, 0x1efa, 0x1efc, 0x1efe, 0x1f08, 0x1f09, 0x1f0a, 0x1f0b, + 0x1f0c, 0x1f0d, 0x1f0e, 0x1f0f, 0x1f18, 0x1f19, 0x1f1a, 0x1f1b, + 0x1f1c, 0x1f1d, 0x1f28, 0x1f29, 0x1f2a, 0x1f2b, 0x1f2c, 0x1f2d, + 0x1f2e, 0x1f2f, 0x1f38, 0x1f39, 0x1f3a, 0x1f3b, 0x1f3c, 0x1f3d, + 0x1f3e, 0x1f3f, 0x1f48, 0x1f49, 0x1f4a, 0x1f4b, 0x1f4c, 0x1f4d, + 0x1f59, 0x1f5b, 0x1f5d, 0x1f5f, 0x1f68, 0x1f69, 0x1f6a, 0x1f6b, + 0x1f6c, 0x1f6d, 0x1f6e, 0x1f6f, 0x1fba, 0x1fbb, 0x1fc8, 0x1fc9, + 0x1fca, 0x1fcb, 0x1fda, 0x1fdb, 0x1ff8, 0x1ff9, 0x1fea, 0x1feb, + 0x1ffa, 0x1ffb, 0x1f88, 0x1f89, 0x1f8a, 0x1f8b, 0x1f8c, 0x1f8d, + 0x1f8e, 0x1f8f, 0x1f98, 0x1f99, 0x1f9a, 0x1f9b, 0x1f9c, 0x1f9d, + 0x1f9e, 0x1f9f, 0x1fa8, 0x1fa9, 0x1faa, 0x1fab, 0x1fac, 0x1fad, + 0x1fae, 0x1faf, 0x1fb8, 0x1fb9, 0x1fbc, 0x399, 0x1fcc, 0x1fd8, + 0x1fd9, 0x1fe8, 0x1fe9, 0x1fec, 0x1ffc, 0x2132, 0x2160, 0x2161, + 0x2162, 0x2163, 0x2164, 0x2165, 0x2166, 0x2167, 0x2168, 0x2169, + 0x216a, 0x216b, 0x216c, 0x216d, 0x216e, 0x216f, 0x2183, 0x24b6, + 0x24b7, 0x24b8, 0x24b9, 0x24ba, 0x24bb, 0x24bc, 0x24bd, 0x24be, + 0x24bf, 0x24c0, 0x24c1, 0x24c2, 0x24c3, 0x24c4, 0x24c5, 0x24c6, + 0x24c7, 0x24c8, 0x24c9, 0x24ca, 0x24cb, 0x24cc, 0x24cd, 0x24ce, + 0x24cf, 0x2c00, 0x2c01, 0x2c02, 0x2c03, 0x2c04, 0x2c05, 0x2c06, + 0x2c07, 0x2c08, 0x2c09, 0x2c0a, 0x2c0b, 0x2c0c, 0x2c0d, 0x2c0e, + 0x2c0f, 0x2c10, 0x2c11, 0x2c12, 0x2c13, 0x2c14, 0x2c15, 0x2c16, + 0x2c17, 0x2c18, 0x2c19, 0x2c1a, 0x2c1b, 0x2c1c, 0x2c1d, 0x2c1e, + 0x2c1f, 0x2c20, 0x2c21, 0x2c22, 0x2c23, 0x2c24, 0x2c25, 0x2c26, + 0x2c27, 0x2c28, 0x2c29, 0x2c2a, 0x2c2b, 0x2c2c, 0x2c2d, 0x2c2e, + 0x2c60, 0x23a, 0x23e, 0x2c67, 0x2c69, 0x2c6b, 0x2c72, 0x2c75, + 0x2c80, 0x2c82, 0x2c84, 0x2c86, 0x2c88, 0x2c8a, 0x2c8c, 0x2c8e, + 0x2c90, 0x2c92, 0x2c94, 0x2c96, 0x2c98, 0x2c9a, 0x2c9c, 0x2c9e, + 0x2ca0, 0x2ca2, 0x2ca4, 0x2ca6, 0x2ca8, 0x2caa, 0x2cac, 0x2cae, + 0x2cb0, 0x2cb2, 0x2cb4, 0x2cb6, 0x2cb8, 0x2cba, 0x2cbc, 0x2cbe, + 0x2cc0, 0x2cc2, 0x2cc4, 0x2cc6, 0x2cc8, 0x2cca, 0x2ccc, 0x2cce, + 0x2cd0, 0x2cd2, 0x2cd4, 0x2cd6, 0x2cd8, 0x2cda, 0x2cdc, 0x2cde, + 0x2ce0, 0x2ce2, 0x2ceb, 0x2ced, 0x2cf2, 0x10a0, 0x10a1, 0x10a2, + 0x10a3, 0x10a4, 0x10a5, 0x10a6, 0x10a7, 0x10a8, 0x10a9, 0x10aa, + 0x10ab, 0x10ac, 0x10ad, 0x10ae, 0x10af, 0x10b0, 0x10b1, 0x10b2, + 0x10b3, 0x10b4, 0x10b5, 0x10b6, 0x10b7, 0x10b8, 0x10b9, 0x10ba, + 0x10bb, 0x10bc, 0x10bd, 0x10be, 0x10bf, 0x10c0, 0x10c1, 0x10c2, + 0x10c3, 0x10c4, 0x10c5, 0x10c7, 0x10cd, 0xa640, 0xa642, 0xa644, + 0xa646, 0xa648, 0xa64a, 0xa64c, 0xa64e, 0xa650, 0xa652, 0xa654, + 0xa656, 0xa658, 0xa65a, 0xa65c, 0xa65e, 0xa660, 0xa662, 0xa664, + 0xa666, 0xa668, 0xa66a, 0xa66c, 0xa680, 0xa682, 0xa684, 0xa686, + 0xa688, 0xa68a, 0xa68c, 0xa68e, 0xa690, 0xa692, 0xa694, 0xa696, + 0xa722, 0xa724, 0xa726, 0xa728, 0xa72a, 0xa72c, 0xa72e, 0xa732, + 0xa734, 0xa736, 0xa738, 0xa73a, 0xa73c, 0xa73e, 0xa740, 0xa742, + 0xa744, 0xa746, 0xa748, 0xa74a, 0xa74c, 0xa74e, 0xa750, 0xa752, + 0xa754, 0xa756, 0xa758, 0xa75a, 0xa75c, 0xa75e, 0xa760, 0xa762, + 0xa764, 0xa766, 0xa768, 0xa76a, 0xa76c, 0xa76e, 0xa779, 0xa77b, + 0xa77e, 0xa780, 0xa782, 0xa784, 0xa786, 0xa78b, 0xa790, 0xa792, + 0xa7a0, 0xa7a2, 0xa7a4, 0xa7a6, 0xa7a8, 0xff21, 0xff22, 0xff23, + 0xff24, 0xff25, 0xff26, 0xff27, 0xff28, 0xff29, 0xff2a, 0xff2b, + 0xff2c, 0xff2d, 0xff2e, 0xff2f, 0xff30, 0xff31, 0xff32, 0xff33, + 0xff34, 0xff35, 0xff36, 0xff37, 0xff38, 0xff39, 0xff3a, 0x10400, + 0x10401, 0x10402, 0x10403, 0x10404, 0x10405, 0x10406, 0x10407, + 0x10408, 0x10409, 0x1040a, 0x1040b, 0x1040c, 0x1040d, 0x1040e, + 0x1040f, 0x10410, 0x10411, 0x10412, 0x10413, 0x10414, 0x10415, + 0x10416, 0x10417, 0x10418, 0x10419, 0x1041a, 0x1041b, 0x1041c, + 0x1041d, 0x1041e, 0x1041f, 0x10420, 0x10421, 0x10422, 0x10423, + 0x10424, 0x10425, 0x10426, 0x10427, 0x2000053, 0x73, 0x130, + 0x2000046, 0x66, 0x2000046, 0x69, 0x2000046, 0x6c, 0x3000046, 0x66, + 0x69, 0x3000046, 0x66, 0x6c, 0x2000053, 0x74, 0x2000053, 0x74, + 0x2000535, 0x582, 0x2000544, 0x576, 0x2000544, 0x565, 0x2000544, + 0x56b, 0x200054e, 0x576, 0x2000544, 0x56d, 0x20002bc, 0x4e, + 0x3000399, 0x308, 0x301, 0x30003a5, 0x308, 0x301, 0x200004a, 0x30c, + 0x2000048, 0x331, 0x2000054, 0x308, 0x2000057, 0x30a, 0x2000059, + 0x30a, 0x2000041, 0x2be, 0x20003a5, 0x313, 0x30003a5, 0x313, 0x300, + 0x30003a5, 0x313, 0x301, 0x30003a5, 0x313, 0x342, 0x2000391, 0x342, + 0x2000397, 0x342, 0x3000399, 0x308, 0x300, 0x3000399, 0x308, 0x301, + 0x2000399, 0x342, 0x3000399, 0x308, 0x342, 0x30003a5, 0x308, 0x300, + 0x30003a5, 0x308, 0x301, 0x20003a1, 0x313, 0x20003a5, 0x342, + 0x30003a5, 0x308, 0x342, 0x20003a9, 0x342, 0x1f88, 0x1f89, 0x1f8a, + 0x1f8b, 0x1f8c, 0x1f8d, 0x1f8e, 0x1f8f, 0x1f88, 0x1f89, 0x1f8a, + 0x1f8b, 0x1f8c, 0x1f8d, 0x1f8e, 0x1f8f, 0x1f98, 0x1f99, 0x1f9a, + 0x1f9b, 0x1f9c, 0x1f9d, 0x1f9e, 0x1f9f, 0x1f98, 0x1f99, 0x1f9a, + 0x1f9b, 0x1f9c, 0x1f9d, 0x1f9e, 0x1f9f, 0x1fa8, 0x1fa9, 0x1faa, + 0x1fab, 0x1fac, 0x1fad, 0x1fae, 0x1faf, 0x1fa8, 0x1fa9, 0x1faa, + 0x1fab, 0x1fac, 0x1fad, 0x1fae, 0x1faf, 0x1fbc, 0x1fbc, 0x1fcc, + 0x1fcc, 0x1ffc, 0x1ffc, 0x2001fba, 0x345, 0x2000386, 0x345, + 0x2001fca, 0x345, 0x2000389, 0x345, 0x2001ffa, 0x345, 0x200038f, + 0x345, 0x3000391, 0x342, 0x345, 0x3000397, 0x342, 0x345, 0x30003a9, 0x342, + 0x345 + ]; + return t; + } +} + +} + +static if (size_t.sizeof == 4) +{ +//1536 bytes +enum lowerCaseTrieEntries = TrieEntry!(bool, 8, 4, 9)([0x0, 0x40, 0x80], + [0x100, 0x80, 0x2000], [0x2020100, 0x4020302, 0x2020205, 0x2060202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x10000, 0x30002, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, + 0x50004, 0x30006, 0x30007, 0x30003, 0x30008, 0x30003, 0x30003, 0x30003, + 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, + 0x30003, 0x30003, 0x90003, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, + 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0xa0003, + 0xb0003, 0x30003, 0x3000c, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, + 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0xe000d, 0x30003, + 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, + 0x30003, 0x0, 0x0, 0x0, 0x7fffffe, 0x0, 0x4200400, 0x80000000, + 0xff7fffff, 0xaaaaaaaa, 0x55aaaaaa, 0xaaaaab55, 0xd4aaaaaa, 0x4e243129, + 0xe6512d2a, 0xb5555240, 0xaa29aaaa, 0xaaaaaaaa, 0x93faaaaa, 0xffffaa85, + 0xffffffff, 0xffefffff, 0x1ffffff, 0x3, 0x1f, 0x0, 0x0, 0x20, + 0x3c8a0000, 0x10000, 0xfffff000, 0xaae37fff, 0x192faaaa, 0x0, + 0xffff0000, 0xffffffff, 0xaaaaaaaa, 0xaaaaa802, 0xaaaaaaaa, 0xaaaad554, + 0xaaaaaaaa, 0xaaaaaaaa, 0xaa, 0x0, 0xfffffffe, 0xff, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x0, 0x0, + 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xbfeaaaaa, 0xaaaaaaaa, + 0xaaaaaaaa, 0xaaaaaaaa, 0x3f00ff, 0xff00ff, 0xff003f, 0x3fff00ff, + 0xff00ff, 0x40df00ff, 0xcf00dc, 0xdc00ff, 0x0, 0x0, 0x0, 0x80020000, + 0x1fff0000, 0x0, 0x0, 0x0, 0x8c400, 0x32108000, 0x43c0, 0xffff0000, + 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffff0000, 0x3ff, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffff0000, 0x7fffffff, + 0x3fda1562, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0x8501a, 0xffffffff, + 0x20bf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xaaaaaaaa, 0x2aaa, + 0xaaaaaa, 0x0, 0x0, 0x0, 0x0, 0xaaabaaa8, 0xaaaaaaaa, 0x95ffaaaa, + 0xa50aa, 0x2aa, 0x0, 0x7000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xf8007f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x7fffffe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xffffff00, 0xffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xfc000000, 0xfffff, 0xffdfc000, 0xff, 0xffffffc, + 0xebc00000, 0xffef, 0xfffffc00, 0xc000000f, 0xffffff, 0xfffc0000, + 0xfff, 0xffffffc0, 0xfc000000, 0xfffff, 0xffffc000, 0xff, 0xffffffc, + 0xffc00000, 0xffff, 0xfffffc00, 0x3f, 0xf7fffffc, 0xf0000003, + 0xfdfffff, 0xffc00000, 0x3f7fff, 0xffff0000, 0xfdff, 0xfffffc00, 0xbf7, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0]); +//1472 bytes +enum upperCaseTrieEntries = TrieEntry!(bool, 8, 4, 9)([0x0, 0x40, 0x80], + [0x100, 0x80, 0x1e00], [0x2020100, 0x4020302, 0x2020205, 0x2060202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x10000, 0x30002, 0x30003, 0x30003, 0x30004, 0x30003, 0x30003, + 0x50003, 0x30006, 0x30007, 0x30003, 0x30008, 0x30003, 0x30003, 0x30003, + 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, + 0x30003, 0x30003, 0x90003, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, + 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, + 0xa0003, 0x30003, 0x3000b, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, + 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0xd000c, 0x30003, + 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, 0x30003, + 0x30003, 0x0, 0x0, 0x7fffffe, 0x0, 0x0, 0x0, 0x7f7fffff, 0x0, + 0x55555555, 0xaa555555, 0x555554aa, 0x2b555555, 0xb1dbced6, 0x11aed2d5, + 0x4aaaa490, 0x55d25555, 0x55555555, 0x6c055555, 0x557a, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x450000, 0xfffed740, 0xffb, 0x551c8000, + 0xe6905555, 0xffffffff, 0xffff, 0x0, 0x55555555, 0x55555401, + 0x55555555, 0x55552aab, 0x55555555, 0x55555555, 0xfffe0055, 0x7fffff, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, + 0x20bf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x55555555, + 0x55555555, 0x55555555, 0x55555555, 0x40155555, 0x55555555, 0x55555555, + 0x55555555, 0x3f00ff00, 0xff00ff00, 0xaa003f00, 0xff00, 0x0, 0xf000000, + 0xf000f00, 0xf001f00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3e273884, 0xc00f3d50, 0x20, 0xffff, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xffc00000, 0xffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xffffffff, 0x7fff, 0x0, 0xc025ea9d, 0x55555555, 0x55555555, + 0x55555555, 0x42805, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x55555555, 0x1555, 0x555555, 0x0, 0x0, 0x0, 0x0, 0x55545554, + 0x55555555, 0x6a005555, 0x52855, 0x555, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x7fffffe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xffffffff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x3ffffff, 0xfff00000, 0x3fff, 0xffffff00, + 0xd0000003, 0x3fde64, 0xffff0000, 0x3ff, 0x1fdfe7b0, 0x7b000000, + 0x1fc5f, 0xfffff000, 0x3f, 0x3ffffff, 0xfff00000, 0x3fff, 0xffffff00, + 0xf0000003, 0x3fffff, 0xffff0000, 0x3ff, 0xffffff00, 0x1, 0x7fffffc, + 0xf0000000, 0x1fffff, 0xffc00000, 0x7fff, 0xffff0000, 0x1ff, 0x400, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0]); +//8704 bytes +enum simpleCaseTrieEntries = TrieEntry!(ushort, 8, 7, 6)([0x0, 0x40, 0x200], + [0x100, 0x380, 0xd00], [0x2020100, 0x4020302, 0x2020205, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x10000, 0x30002, 0x50004, 0x70006, 0x90008, 0xa, 0xb0000, 0xd000c, + 0xf000e, 0x110010, 0x130012, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x160015, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x170000, + 0x0, 0x190018, 0x1b001a, 0x1d001c, 0x1f001e, 0x0, 0x0, 0x210020, 0x22, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x240023, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x260025, 0x280027, 0x29, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2a0000, 0x2b, 0x2d002c, 0x2e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x30002f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x320031, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x20ffff, 0x240022, 0x280026, 0x2c002a, 0x30002e, + 0x72f0032, 0x390037, 0x3d003b, 0x41003f, 0x1b00043, 0x4a0048, 0x4e004c, + 0x520050, 0xffff0054, 0xffffffff, 0xffffffff, 0x21ffff, 0x250023, + 0x290027, 0x2d002b, 0x31002f, 0x7300033, 0x3a0038, 0x3e003c, 0x420040, + 0x1b10044, 0x4b0049, 0x4f004d, 0x530051, 0xffff0055, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x43fffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xc800c6, 0xcc0498, 0x14904aa, + 0xd500d3, 0xd900d7, 0xdd00db, 0xe100df, 0xe500e3, 0xe900e7, 0xed00eb, + 0xf100ef, 0xffff00f3, 0xf700f5, 0xfb00f9, 0xff00fd, 0x6be0101, + 0xc900c7, 0xcd0499, 0x14a04ab, 0xd600d4, 0xda00d8, 0xde00dc, 0xe200e0, + 0xe600e4, 0xea00e8, 0xee00ec, 0xf200f0, 0xffff00f4, 0xf800f6, 0xfc00fa, + 0x10000fe, 0x1a80102, 0x1160115, 0x1180117, 0x11c011b, 0x11e011d, + 0x120011f, 0x1240123, 0x1260125, 0x1280127, 0x12c012b, 0x12e012d, + 0x130012f, 0x1340133, 0x1360135, 0x1380137, 0x13a0139, 0x13c013b, + 0x13e013d, 0x140013f, 0x1420141, 0x1440143, 0x1460145, 0x1480147, + 0x14d014c, 0x14f014e, 0xffffffff, 0x1510150, 0x1530152, 0x1550154, + 0x156ffff, 0x1580157, 0x15c0159, 0x15e015d, 0x160015f, 0x1620161, + 0x1640163, 0x1660165, 0xffff0167, 0x1690168, 0x16b016a, 0x16d016c, + 0x16f016e, 0x1710170, 0x1730172, 0x1750174, 0x1770176, 0x1790178, + 0x17b017a, 0x17d017c, 0x17f017e, 0x1830182, 0x1870186, 0x18b018a, + 0x18f018e, 0x1930192, 0x1970196, 0x19b019a, 0x19f019e, 0x1a301a2, + 0x1a501a4, 0x1a701a6, 0x1aa01a9, 0x1ac01ab, 0x1ae01ad, 0x1b201af, + 0x1b3028b, 0x1b601b5, 0x1ba01b9, 0x1bd01bb, 0x1bf01be, 0x1c301c1, + 0xffff01c4, 0x1c701c5, 0x1cb01c9, 0x1cd01cc, 0x23b01cf, 0x1d301d1, + 0x1d601d5, 0xffff0283, 0x1d901d7, 0x1db0269, 0x1de01dd, 0x1e001df, + 0x1e201e1, 0x1e501e3, 0x1e701e6, 0xffffffff, 0x1ea01e9, 0x1ed01eb, + 0x1ef01ee, 0x1f301f1, 0x1f501f4, 0x1f701f6, 0x1fa01f9, 0xffffffff, + 0x1fc01fb, 0x23dffff, 0xffffffff, 0xffffffff, 0x2010200, 0x2060202, + 0x2080207, 0x20d020c, 0x20f020e, 0x2110210, 0x2130212, 0x2150214, + 0x2170216, 0x2190218, 0x21b021a, 0x21d021c, 0x1c6021e, 0x220021f, + 0x2240223, 0x2260225, 0x2280227, 0x22a0229, 0x22c022b, 0x22e022d, + 0x230022f, 0x2320231, 0x236ffff, 0x2380237, 0x23a0239, 0x23e023c, + 0x240023f, 0x2440243, 0x2460245, 0x2480247, 0x24a0249, 0x24c024b, + 0x24e024d, 0x250024f, 0x2520251, 0x2540253, 0x2560255, 0x2580257, + 0x25a0259, 0x25c025b, 0x25e025d, 0x260025f, 0x2620261, 0x2640263, + 0x2660265, 0x2680267, 0xffff026a, 0x26c026b, 0x26e026d, 0x270026f, + 0x2720271, 0x2740273, 0x2760275, 0x2780277, 0x27a0279, 0x27c027b, + 0xffffffff, 0xffffffff, 0xffffffff, 0x281027f, 0x2840282, 0x2d70285, + 0x2870482, 0x28c0288, 0x28f028d, 0x2920291, 0x2940293, 0x2960295, + 0x2980297, 0x29c029b, 0x466046a, 0x1b402b7, 0xffff01bc, 0x1c201c0, + 0x1c8ffff, 0x1caffff, 0xffffffff, 0xffffffff, 0xffff01ce, 0x1d0ffff, + 0x748ffff, 0xffff05fa, 0x1d201d4, 0x528ffff, 0xffffffff, 0x1d8ffff, + 0x2b3ffff, 0xffff01da, 0x1dcffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x2a3ffff, 0xffffffff, 0xffff01e4, 0x1e8ffff, 0xffffffff, 0xffffffff, + 0x28e01ec, 0x1f201f0, 0xffff0290, 0xffffffff, 0xffffffff, 0xffff01f8, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x83affff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x31e031d, 0x320031f, + 0xffffffff, 0x3240323, 0xffffffff, 0x3d5ffff, 0x3d903d7, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0329, 0x32f032d, 0xffff0331, + 0xffff0333, 0x3370335, 0x339ffff, 0x33e0395, 0x3cc0340, 0x3470345, + 0x83b03c8, 0x35403c2, 0x3590440, 0x35d035b, 0x3c5039f, 0x388ffff, + 0x36a0368, 0x36f039c, 0x7100371, 0x3780376, 0x32e032a, 0x3320330, + 0x33affff, 0x33f0396, 0x3cd0341, 0x3480346, 0x83c03c9, 0x35503c3, + 0x35a0441, 0x35e035c, 0x3c603a0, 0x38a0389, 0x36b0369, 0x370039d, + 0x7110372, 0x3790377, 0x3360334, 0x3930338, 0x3ca0397, 0xffffffff, + 0x39effff, 0x39403a1, 0x3a303a2, 0x3a703a6, 0x3a903a8, 0x3ab03aa, + 0x3ad03ac, 0x3af03ae, 0x3b103b0, 0x3b503b4, 0x3b903b8, 0x3bd03bc, + 0x3bf03be, 0x3c103c0, 0x3c703c4, 0xffff03d1, 0x3ce03cb, 0x3cfffff, + 0x3d203d0, 0x3d403d3, 0x3d6ffff, 0x3da03d8, 0x3dd03db, 0x3e103df, + 0x3e503e3, 0x3e903e7, 0x3ed03eb, 0x3f103ef, 0x3f503f3, 0x3f903f7, + 0x3fd03fb, 0x40103ff, 0x4050403, 0x4090407, 0x40d040b, 0x411040f, + 0x4150413, 0x4190417, 0x41d041b, 0x421041f, 0x4250423, 0x4290427, + 0x42d042b, 0x431042f, 0x4350433, 0x4390437, 0x3fe03fc, 0x4020400, + 0x4060404, 0x40a0408, 0x40e040c, 0x4120410, 0x4160414, 0x41a0418, + 0x41e041c, 0x4220420, 0x4260424, 0x42a0428, 0x42e042c, 0x4320430, + 0x4360434, 0x43a0438, 0x3de03dc, 0x3e203e0, 0x3e603e4, 0x3ea03e8, + 0x3ee03ec, 0x3f203f0, 0x3f603f4, 0x3fa03f8, 0x4510450, 0x4530452, + 0x4570456, 0x4590458, 0x45d045c, 0x4610460, 0x4650464, 0x4690468, + 0x46d046c, 0x4710470, 0x4730472, 0x4770476, 0x4790478, 0x47b047a, + 0x47d047c, 0x4810480, 0x4850484, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x4950494, 0x4970496, 0x49b049a, 0x49d049c, 0x49f049e, + 0x4a304a2, 0x4a704a6, 0x4a904a8, 0x4ad04ac, 0x4b104b0, 0x4b304b2, + 0x4b704b6, 0x4b904b8, 0x4bb04ba, 0x4bf04be, 0x4c104c0, 0x4c504c4, + 0x4c904c8, 0x4cd04cc, 0x4cf04ce, 0x4d304d2, 0x4d504d4, 0x4d704d6, + 0x4db04da, 0x4df04de, 0x4e304e2, 0x4e704e6, 0x4ec04ea, 0x4f004ed, + 0x4f404f1, 0x4f804f5, 0x4fc04f9, 0x50004fd, 0x5040501, 0x4eb0505, + 0x50b050a, 0x50d050c, 0x50f050e, 0x5130512, 0x5170516, 0x5190518, + 0x51d051c, 0x51f051e, 0x5210520, 0x5250524, 0x5270526, 0x52b052a, + 0x52d052c, 0x52f052e, 0x5330532, 0x5370536, 0x5390538, 0x53d053c, + 0x53f053e, 0x5410540, 0x5430542, 0x5470546, 0x5490548, 0x54b054a, + 0x54d054c, 0x54f054e, 0x5510550, 0x5550554, 0x5570556, 0x5590558, + 0x55b055a, 0x55d055c, 0x55f055e, 0x5630562, 0x5650564, 0x5670566, + 0x5690568, 0x56b056a, 0x56f056e, 0x5730572, 0x5750574, 0x5770576, + 0x5790578, 0x57b057a, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x580ffff, 0x5840582, 0x5880586, 0x58c058a, 0x590058e, 0x5940592, + 0x5980596, 0x59c059a, 0x5a0059e, 0x5a405a2, 0x5a805a6, 0x5ac05aa, + 0x5b005ae, 0x5b405b2, 0x5b805b6, 0x5bc05ba, 0x5c005be, 0x5c405c2, + 0x5c805c6, 0xffff05ca, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x581ffff, 0x5850583, 0x5890587, 0x58d058b, 0x591058f, 0x5950593, + 0x5990597, 0x59d059b, 0x5a1059f, 0x5a505a3, 0x5a905a7, 0x5ad05ab, + 0x5b105af, 0x5b505b3, 0x5b905b7, 0x5bd05bb, 0x5c105bf, 0x5c505c3, + 0x5c905c7, 0xffff05cb, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x880086, 0x8c008a, + 0x90008e, 0x940092, 0x980096, 0x9c009a, 0xa0009e, 0xa400a2, 0xa800a6, + 0xac00aa, 0xb000ae, 0xb400b2, 0xb800b6, 0xbc00ba, 0xc000be, 0xc400c2, + 0x48e0486, 0x4a000ca, 0x4b400ce, 0x4c6ffff, 0xffffffff, 0xffffffff, + 0x508ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x7e8ffff, 0xffffffff, 0x454ffff, 0xffffffff, 0x5fd05fc, 0x5ff05fe, + 0x6010600, 0x6050604, 0x6090608, 0x60b060a, 0x60f060e, 0x6110610, + 0x6130612, 0x6170616, 0x6190618, 0x61d061c, 0x61f061e, 0x6210620, + 0x6230622, 0x6270626, 0x6290628, 0x62b062a, 0x62d062c, 0x62f062e, + 0x6310630, 0x6350634, 0x6370636, 0x6390638, 0x63b063a, 0x63d063c, + 0x63f063e, 0x6430642, 0x6450644, 0x6470646, 0x6490648, 0x64b064a, + 0x64d064c, 0x6510650, 0x6530652, 0x6550654, 0x6590658, 0x65d065c, + 0x65f065e, 0x6630662, 0x6650664, 0x6670666, 0x6690668, 0x66b066a, + 0x66d066c, 0x6710670, 0x6730672, 0x6750674, 0x6bc06bb, 0x67a0679, + 0x67c067b, 0x680067f, 0x6820681, 0x6840683, 0x6860685, 0x6880687, + 0x68a0689, 0x68e068d, 0x690068f, 0x6920691, 0x6960695, 0x6980697, + 0x69a0699, 0x69e069d, 0x6a0069f, 0x6a206a1, 0x6a406a3, 0x6a606a5, + 0x6a806a7, 0x6ac06ab, 0x6ae06ad, 0x6b006af, 0x6b206b1, 0x6b406b3, + 0x6b606b5, 0xffffffff, 0xffffffff, 0x6bdffff, 0xffffffff, 0xffff06bf, + 0x6c106c0, 0x6c306c2, 0x6c506c4, 0x6c906c8, 0x6cb06ca, 0x6cd06cc, + 0x6cf06ce, 0x6d106d0, 0x6d506d4, 0x6d706d6, 0x6db06da, 0x6dd06dc, + 0x6df06de, 0x6e106e0, 0x6e306e2, 0x6e506e4, 0x6e906e8, 0x6eb06ea, + 0x6ef06ee, 0x6f106f0, 0x6f306f2, 0x6f506f4, 0x6f706f6, 0x6f906f8, + 0x6fb06fa, 0x6fd06fc, 0x6ff06fe, 0x7010700, 0x7030702, 0x7050704, + 0x7070706, 0x7090708, 0x70b070a, 0x70d070c, 0x70f070e, 0x7140713, + 0x7160715, 0x7180717, 0x71c071b, 0x71e071d, 0x720071f, 0x7220721, + 0x7240723, 0x7260725, 0x7280727, 0x72a0729, 0x72e072d, 0x7330732, + 0x7380736, 0x73c073a, 0x740073e, 0x7440742, 0x7390737, 0x73d073b, + 0x741073f, 0x7450743, 0x74c074a, 0x750074e, 0x7540752, 0xffffffff, + 0x74d074b, 0x751074f, 0x7550753, 0xffffffff, 0x7660764, 0x76a0768, + 0x76e076c, 0x7720770, 0x7670765, 0x76b0769, 0x76f076d, 0x7730771, + 0x7860784, 0x78a0788, 0x78e078c, 0x7920790, 0x7870785, 0x78b0789, + 0x78f078d, 0x7930791, 0x79e079c, 0x7a207a0, 0x7a607a4, 0xffffffff, + 0x79f079d, 0x7a307a1, 0x7a707a5, 0xffffffff, 0x7b6ffff, 0x7baffff, + 0x7beffff, 0x7c2ffff, 0x7b7ffff, 0x7bbffff, 0x7bfffff, 0x7c3ffff, + 0x7d207d0, 0x7d607d4, 0x7da07d8, 0x7de07dc, 0x7d307d1, 0x7d707d5, + 0x7db07d9, 0x7df07dd, 0x8360834, 0x840083e, 0x8440842, 0x84e084c, + 0x8620860, 0x8580856, 0x8660864, 0xffffffff, 0x7f607f4, 0x7fa07f8, + 0x7fe07fc, 0x8020800, 0x7f707f5, 0x7fb07f9, 0x7ff07fd, 0x8030801, + 0x80a0808, 0x80e080c, 0x8120810, 0x8160814, 0x80b0809, 0x80f080d, + 0x8130811, 0x8170815, 0x8220820, 0x8260824, 0x82a0828, 0x82e082c, + 0x8230821, 0x8270825, 0x82b0829, 0x82f082d, 0x8320830, 0x838ffff, + 0xffffffff, 0xffffffff, 0x8330831, 0x8370835, 0xffff0839, 0xffff083d, + 0xffffffff, 0x846ffff, 0xffffffff, 0xffffffff, 0x841083f, 0x8450843, + 0xffff0847, 0xffffffff, 0x84a0848, 0xffffffff, 0xffffffff, 0xffffffff, + 0x84b0849, 0x84f084d, 0xffffffff, 0xffffffff, 0x8540852, 0xffffffff, + 0x85affff, 0xffffffff, 0x8550853, 0x8590857, 0xffff085b, 0xffffffff, + 0xffffffff, 0x868ffff, 0xffffffff, 0xffffffff, 0x8630861, 0x8670865, + 0xffff0869, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0712, 0xffffffff, 0x14b0731, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0530, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0531, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x180029f, 0x18402af, 0x18802c1, 0x18c005e, + 0x1900064, 0x194006c, 0x1980076, 0x19c007e, 0x18102a0, 0x18502b0, + 0x18902c2, 0x18d005f, 0x1910065, 0x195006d, 0x1990077, 0x19d007f, + 0xffffffff, 0x1b7ffff, 0xffff01b8, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x4d80444, + 0x4dc0446, 0x4e0044c, 0x4e4045e, 0x4e80474, 0x2d3086a, 0x204ee, + 0x6c604f2, 0x6e04f6, 0x37a04fa, 0x10d04fe, 0x61a0502, 0x51a0506, + 0x4d90445, 0x4dd0447, 0x4e1044d, 0x4e5045f, 0x4e90475, 0x2d4086b, + 0x304ef, 0x6c704f3, 0x6f04f7, 0x37b04fb, 0x10e04ff, 0x61b0503, + 0x51b0507, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x40000, 0xa0008, 0xe000c, 0x160010, 0x1a0018, 0x2bb001c, 0x2d102c7, + 0x5602e7, 0x600058, 0x660062, 0x70006a, 0x780074, 0x80007c, 0x2990082, + 0x607e0, 0x6020084, 0x2a7057c, 0x5d005ce, 0x10305de, 0x1070105, + 0x10f0109, 0x1190113, 0x1290121, 0xffff0131, 0x50001, 0xb0009, 0xf000d, + 0x170011, 0x1b0019, 0x2bc001d, 0x2d202c8, 0x5702e8, 0x610059, 0x670063, + 0x71006b, 0x790075, 0x81007d, 0x29a0083, 0x707e1, 0x6030085, 0x2a8057d, + 0x5d105cf, 0x10405df, 0x1080106, 0x110010a, 0x11a0114, 0x12a0122, + 0xffff0132, 0x4c304c2, 0x4550529, 0x28002a4, 0x45a0286, 0x2a9045b, + 0x46202aa, 0x4670463, 0x46b02b4, 0xffff02b8, 0x2ba02b9, 0x2bfffff, + 0xffff02c0, 0xffffffff, 0xffffffff, 0xffffffff, 0x48302d8, 0x2e202e1, + 0x4890488, 0x48b048a, 0x48d048c, 0x4910490, 0x2fe02fd, 0x3040303, + 0x30e030d, 0x3160315, 0x31a0319, 0x3260325, 0x3280327, 0x2fc02fb, + 0x6ed06ec, 0x3810380, 0x3830382, 0x3870386, 0x3920391, 0x3a503a4, + 0x3b303b2, 0x56d056c, 0x5cd05cc, 0x5db05da, 0x5ed05ec, 0x60d060c, + 0x6570656, 0x43e043d, 0x6e706e6, 0x72c072b, 0x7830782, 0x7e307e2, + 0x6940693, 0x65b065a, 0x150014, 0x5d005c, 0x4bd04bc, 0x4d104d0, + 0x5d505d4, 0x1a101a0, 0x5110510, 0x5230522, 0x5350534, 0x5450544, + 0x5530552, 0x5610560, 0x5710570, 0x57f057e, 0x15b015a, 0x37d037c, + 0x3bb03ba, 0xffffffff, 0xffffffff, 0xffffffff, 0x5d2ffff, 0x5d805d3, + 0xffff05d9, 0xffffffff, 0x5e305e2, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x890087, 0x8d008b, 0x91008f, + 0x950093, 0x990097, 0x9d009b, 0xa1009f, 0xa500a3, 0xa900a7, 0xad00ab, + 0xb100af, 0xb500b3, 0xb900b7, 0xbd00bb, 0xc100bf, 0xc500c3, 0x48f0487, + 0x4a100cb, 0x4b500cf, 0x4c7ffff, 0xffffffff, 0xffffffff, 0x509ffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x2c402c3, 0x5d705d6, 0x5dd05dc, + 0x5e105e0, 0x5e705e6, 0x5e905e8, 0x5eb05ea, 0x5ef05ee, 0x5f105f0, + 0x5f505f4, 0x5f905f8, 0x3080307, 0x6150614, 0x6250624, 0x6330632, + 0x6410640, 0x64f064e, 0x6610660, 0x66f066e, 0x67e067d, 0x68c068b, + 0x69c069b, 0x6aa06a9, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x7350734, + 0x690068, 0x27e027d, 0x75f075e, 0x7770776, 0x390038f, 0x1f001e, + 0x7b107b0, 0x7c707c6, 0x2a202a1, 0x7e507e4, 0x6b806b7, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x7590758, 0x75d075c, 0x7610760, 0x2d602d5, 0x2e002df, 0x2e602e5, + 0x2ee02ed, 0xffffffff, 0x7790778, 0x77d077c, 0x7810780, 0x30c030b, + 0x3140313, 0x3180317, 0x3220321, 0x7950794, 0x7970796, 0x7990798, + 0x79b079a, 0x37f037e, 0x3850384, 0x38e038d, 0x7a907a8, 0x7ab07aa, + 0x7ad07ac, 0x7af07ae, 0x7b307b2, 0x7b507b4, 0x7b907b8, 0x7bd07bc, + 0x7c107c0, 0x7c507c4, 0x7c907c8, 0x7cd07cc, 0x7cf07ce, 0x46f046e, + 0x47f047e, 0x4930492, 0x4a504a4, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x514ffff, 0x7e60515, 0x7e907e7, 0x7eb07ea, 0x7ed07ec, + 0x7ef07ee, 0x7f107f0, 0x7f307f2, 0xffffffff, 0x5f2ffff, 0x74905f3, + 0xffffffff, 0x8050804, 0x8070806, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x8190818, 0x81b081a, 0x81d081c, + 0x81f081e, 0x5f705f6, 0xffff05fb, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x756ffff, + 0x75a02c5, 0x2cd02cb, 0x76202cf, 0x2db06d2, 0x2e30719, 0x2e90448, + 0x2f107ca, 0x2f30774, 0x77a02f5, 0x77e02f9, 0x3050221, 0x30f007a, + 0xffff043b, 0xffffffff, 0xffffffff, 0x757ffff, 0x75b02c6, 0x2ce02cc, + 0x76302d0, 0x2dc06d3, 0x2e4071a, 0x2ea0449, 0x2f207cb, 0x2f40775, + 0x77b02f6, 0x77f02fa, 0x3060222, 0x310007b, 0xffff043c, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x85c0012, + 0x72005a, 0x32b0311, 0x11106b9, 0x2ab05e4, 0x2dd029d, 0x2ef085e, + 0x10b0606, 0x2d902a5, 0x4ca0289, 0x2b502ad, 0x2c902bd, 0x2eb0746, + 0x30902f7, 0x241031b, 0x38b02b1, 0x44a03b6, 0x442053a, 0x6d8044e, + 0x85004ae, 0x85d0013, 0x73005b, 0x32c0312, 0x11206ba, 0x2ac05e5, + 0x2de029e, 0x2f0085f, 0x10c0607, 0x2da02a6, 0x4cb028a, 0x2b602ae, + 0x2ca02be, 0x2ec0747, 0x30a02f8, 0x242031c, 0x38c02b2, 0x44b03b7, + 0x443053b, 0x6d9044f, 0x85104af, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff]); +//8832 bytes +enum fullCaseTrieEntries = TrieEntry!(ushort, 8, 7, 6)([0x0, 0x40, 0x200], + [0x100, 0x380, 0xd40], [0x2020100, 0x4020302, 0x2020205, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x10000, 0x30002, 0x50004, 0x70006, 0x90008, 0xa, 0xb0000, 0xd000c, + 0xf000e, 0x110010, 0x130012, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x160015, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x170000, + 0x0, 0x190018, 0x1b001a, 0x1d001c, 0x1f001e, 0x0, 0x0, 0x210020, 0x22, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x240023, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x260025, 0x280027, 0x29, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2a0000, 0x2b, 0x2d002c, 0x2e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x310030, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x330032, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x20ffff, 0x240022, 0x280026, 0x2c002a, 0x30002e, + 0x7810032, 0x390037, 0x3d003b, 0x41003f, 0x1b90043, 0x4a0048, 0x4e004c, + 0x520050, 0xffff0054, 0xffffffff, 0xffffffff, 0x21ffff, 0x250023, + 0x290027, 0x2d002b, 0x31002f, 0x7820033, 0x3a0038, 0x3e003c, 0x420040, + 0x1ba0044, 0x4b0049, 0x4f004d, 0x530051, 0xffff0055, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x470ffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xc800c6, 0xcc04c9, 0x14e04db, + 0xd500d3, 0xd900d7, 0xdd00db, 0xe100df, 0xe500e3, 0xe900e7, 0xed00eb, + 0xf100ef, 0xffff00f3, 0xf700f5, 0xfb00f9, 0xff00fd, 0x70f0101, + 0xc900c7, 0xcd04ca, 0x14f04dc, 0xd600d4, 0xda00d8, 0xde00dc, 0xe200e0, + 0xe600e4, 0xea00e8, 0xee00ec, 0xf200f0, 0xffff00f4, 0xf800f6, 0xfc00fa, + 0x10000fe, 0x1b10102, 0x1190118, 0x11b011a, 0x11f011e, 0x1210120, + 0x1230122, 0x1270126, 0x1290128, 0x12b012a, 0x12f012e, 0x1310130, + 0x1330132, 0x1370136, 0x1390138, 0x13b013a, 0x13d013c, 0x13f013e, + 0x1410140, 0x1430142, 0x1470146, 0x1490148, 0x14b014a, 0x14d014c, + 0x1520151, 0x1540153, 0xffff0155, 0x1580157, 0x15a0159, 0x15c015b, + 0x15dffff, 0x15f015e, 0x1630160, 0x1650164, 0x1670166, 0x1690168, + 0x16b016a, 0x16d016c, 0x16f016e, 0x1720171, 0x1740173, 0x1760175, + 0x1780177, 0x17a0179, 0x17c017b, 0x17e017d, 0x180017f, 0x1820181, + 0x1840183, 0x1860185, 0x1880187, 0x18c018b, 0x190018f, 0x1940193, + 0x1980197, 0x19c019b, 0x1a0019f, 0x1a401a3, 0x1a801a7, 0x1ac01ab, + 0x1ae01ad, 0x1b001af, 0x1b301b2, 0x1b501b4, 0x1b701b6, 0x1bb01b8, + 0x1bc029c, 0x1bf01be, 0x1c301c2, 0x1c601c4, 0x1c801c7, 0x1cc01ca, + 0xffff01cd, 0x1d001ce, 0x1d401d2, 0x1d601d5, 0x24801d8, 0x1dc01da, + 0x1df01de, 0xffff0294, 0x1e201e0, 0x1e60278, 0x1e901e8, 0x1eb01ea, + 0x1ed01ec, 0x1f001ee, 0x1f201f1, 0xffffffff, 0x1f501f4, 0x1f801f6, + 0x1fa01f9, 0x1fe01fc, 0x20001ff, 0x2020201, 0x2050204, 0xffffffff, + 0x2070206, 0x24affff, 0xffffffff, 0xffffffff, 0x20c020b, 0x211020d, + 0x2130212, 0x2180217, 0x21a0219, 0x21c021b, 0x21e021d, 0x220021f, + 0x2220221, 0x2240223, 0x2260225, 0x2280227, 0x1cf0229, 0x22b022a, + 0x22f022e, 0x2310230, 0x2330232, 0x2350234, 0x2370236, 0x2390238, + 0x23b023a, 0x23d023c, 0x243023e, 0x2450244, 0x2470246, 0x24b0249, + 0x24d024c, 0x2510250, 0x2530252, 0x2550254, 0x2570256, 0x2590258, + 0x25b025a, 0x25d025c, 0x2610260, 0x2630262, 0x2650264, 0x2670266, + 0x2690268, 0x26b026a, 0x26d026c, 0x26f026e, 0x2710270, 0x2730272, + 0x2750274, 0x2770276, 0xffff0279, 0x27b027a, 0x27d027c, 0x27f027e, + 0x2810280, 0x2850284, 0x2870286, 0x2890288, 0x28b028a, 0x28d028c, + 0xffffffff, 0xffffffff, 0xffffffff, 0x2920290, 0x2950293, 0x2ec0296, + 0x29804b3, 0x29d0299, 0x2a0029e, 0x2a302a2, 0x2a502a4, 0x2a702a6, + 0x2a902a8, 0x2ad02ac, 0x497049b, 0x1bd02ca, 0xffff01c5, 0x1cb01c9, + 0x1d1ffff, 0x1d3ffff, 0xffffffff, 0xffffffff, 0xffff01d7, 0x1d9ffff, + 0x79affff, 0xffff0643, 0x1db01dd, 0x559ffff, 0xffffffff, 0x1e1ffff, + 0x2c6ffff, 0xffff01e3, 0x1e7ffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x2b4ffff, 0xffffffff, 0xffff01ef, 0x1f3ffff, 0xffffffff, 0xffffffff, + 0x29f01f7, 0x1fd01fb, 0xffff02a1, 0xffffffff, 0xffffffff, 0xffff0203, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x8e4ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x3450344, 0x3470346, + 0xffffffff, 0x34b034a, 0xffffffff, 0x406ffff, 0x40a0408, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0350, 0x3560354, 0xffff0358, + 0xffff035a, 0x35e035c, 0x3630902, 0x36803c2, 0x3fd036a, 0x371036f, + 0x8e503f9, 0x37e03f3, 0x3830471, 0x3870385, 0x3f603cc, 0x3b5ffff, + 0x3940392, 0x39903c9, 0x762039b, 0x3a203a0, 0x3550351, 0x3590357, + 0x3640915, 0x36903c3, 0x3fe036b, 0x3720370, 0x8e603fa, 0x37f03f4, + 0x3840472, 0x3880386, 0x3f703cd, 0x3b703b6, 0x3950393, 0x39a03ca, + 0x763039c, 0x3a303a1, 0x35d035b, 0x3c0035f, 0x3fb03c4, 0xffffffff, + 0x3cbffff, 0x3c103ce, 0x3d003cf, 0x3d403d3, 0x3d603d5, 0x3d803d7, + 0x3da03d9, 0x3de03dd, 0x3e003df, 0x3e403e3, 0x3e803e7, 0x3ec03eb, + 0x3ee03ed, 0x3f203f1, 0x3f803f5, 0xffff0402, 0x3ff03fc, 0x400ffff, + 0x4030401, 0x4050404, 0x407ffff, 0x40b0409, 0x40e040c, 0x4120410, + 0x4160414, 0x41a0418, 0x41e041c, 0x4220420, 0x4260424, 0x42a0428, + 0x42e042c, 0x4320430, 0x4360434, 0x43a0438, 0x43e043c, 0x4420440, + 0x4460444, 0x44a0448, 0x44e044c, 0x4520450, 0x4560454, 0x45a0458, + 0x45e045c, 0x4620460, 0x4660464, 0x46a0468, 0x42f042d, 0x4330431, + 0x4370435, 0x43b0439, 0x43f043d, 0x4430441, 0x4470445, 0x44b0449, + 0x44f044d, 0x4530451, 0x4570455, 0x45b0459, 0x45f045d, 0x4630461, + 0x4670465, 0x46b0469, 0x40f040d, 0x4130411, 0x4170415, 0x41b0419, + 0x41f041d, 0x4230421, 0x4270425, 0x42b0429, 0x4820481, 0x4840483, + 0x4880487, 0x48a0489, 0x48e048d, 0x4920491, 0x4960495, 0x49a0499, + 0x49e049d, 0x4a204a1, 0x4a404a3, 0x4a804a7, 0x4aa04a9, 0x4ac04ab, + 0x4ae04ad, 0x4b204b1, 0x4b604b5, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x4c604c5, 0x4c804c7, 0x4cc04cb, 0x4ce04cd, 0x4d004cf, + 0x4d404d3, 0x4d804d7, 0x4da04d9, 0x4de04dd, 0x4e204e1, 0x4e404e3, + 0x4e804e7, 0x4ea04e9, 0x4ec04eb, 0x4f004ef, 0x4f204f1, 0x4f604f5, + 0x4fa04f9, 0x4fe04fd, 0x50004ff, 0x5040503, 0x5060505, 0x5080507, + 0x50c050b, 0x510050f, 0x5140513, 0x5180517, 0x51d051b, 0x521051e, + 0x5250522, 0x5290526, 0x52d052a, 0x531052e, 0x5350532, 0x51c0536, + 0x53c053b, 0x53e053d, 0x540053f, 0x5440543, 0x5480547, 0x54a0549, + 0x54e054d, 0x550054f, 0x5520551, 0x5560555, 0x5580557, 0x55c055b, + 0x55e055d, 0x560055f, 0x5640563, 0x5680567, 0x56a0569, 0x56e056d, + 0x570056f, 0x5720571, 0x5740573, 0x5780577, 0x57a0579, 0x57c057b, + 0x57e057d, 0x5820581, 0x5840583, 0x5880587, 0x58a0589, 0x58c058b, + 0x58e058d, 0x5920591, 0x5940593, 0x5980597, 0x59a0599, 0x59c059b, + 0x59e059d, 0x5a205a1, 0x5a605a5, 0x5aa05a9, 0x5ac05ab, 0x5ae05ad, + 0x5b005af, 0x5b405b3, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x5b9ffff, 0x5bd05bb, 0x5c105bf, 0x5c505c3, 0x5c905c7, 0x5cd05cb, + 0x5d105cf, 0x5d505d3, 0x5d905d7, 0x5dd05db, 0x5e105df, 0x5e505e3, + 0x5e905e7, 0x5ed05eb, 0x5f105ef, 0x5f505f3, 0x5f905f7, 0x5fd05fb, + 0x60105ff, 0xffff0603, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x5baffff, 0x5be05bc, 0x5c205c0, 0x5c605c4, 0x5ca05c8, 0x5ce05cc, + 0x5d205d0, 0x5d605d4, 0x5da05d8, 0x5de05dc, 0x5e205e0, 0x5e605e4, + 0x5ea05e8, 0x5ee05ec, 0x5f205f0, 0x5f605f4, 0x5fa05f8, 0x5fe05fc, + 0x6020600, 0x6130604, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x880086, 0x8c008a, + 0x90008e, 0x940092, 0x980096, 0x9c009a, 0xa0009e, 0xa400a2, 0xa800a6, + 0xac00aa, 0xb000ae, 0xb400b2, 0xb800b6, 0xbc00ba, 0xc000be, 0xc400c2, + 0x4bf04b7, 0x4d100ca, 0x4e500ce, 0x4f7ffff, 0xffffffff, 0xffffffff, + 0x539ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x83affff, 0xffffffff, 0x485ffff, 0xffffffff, 0x6460645, 0x6480647, + 0x64a0649, 0x64e064d, 0x6520651, 0x6540653, 0x6580657, 0x65a0659, + 0x65c065b, 0x660065f, 0x6620661, 0x6660665, 0x6680667, 0x66a0669, + 0x66c066b, 0x670066f, 0x6720671, 0x6740673, 0x6760675, 0x6780677, + 0x67a0679, 0x67e067d, 0x680067f, 0x6820681, 0x6840683, 0x6860685, + 0x6880687, 0x68c068b, 0x68e068d, 0x690068f, 0x6920691, 0x6940693, + 0x6960695, 0x69a0699, 0x69c069b, 0x69e069d, 0x6a206a1, 0x6a606a5, + 0x6a806a7, 0x6ac06ab, 0x6ae06ad, 0x6b006af, 0x6b206b1, 0x6b406b3, + 0x6b606b5, 0x6ba06b9, 0x6bc06bb, 0x6be06bd, 0x70d070c, 0x6c306c2, + 0x6c706c6, 0x6cb06ca, 0x6cd06cc, 0x6cf06ce, 0x6d106d0, 0x6d306d2, + 0x6d506d4, 0x6d906d8, 0x6db06da, 0x6dd06dc, 0x6e106e0, 0x6e306e2, + 0x6e506e4, 0x6e906e8, 0x6eb06ea, 0x6ed06ec, 0x6ef06ee, 0x6f106f0, + 0x6f306f2, 0x6f706f6, 0x6f906f8, 0x6fb06fa, 0x6fd06fc, 0x6ff06fe, + 0x7010700, 0x7040702, 0x7080706, 0x70e070a, 0xffffffff, 0xffff0710, + 0x7130712, 0x7150714, 0x7170716, 0x71b071a, 0x71d071c, 0x71f071e, + 0x7210720, 0x7230722, 0x7270726, 0x7290728, 0x72d072c, 0x72f072e, + 0x7310730, 0x7330732, 0x7350734, 0x7370736, 0x73b073a, 0x73d073c, + 0x7410740, 0x7430742, 0x7450744, 0x7470746, 0x7490748, 0x74b074a, + 0x74d074c, 0x74f074e, 0x7510750, 0x7530752, 0x7550754, 0x7570756, + 0x7590758, 0x75b075a, 0x75d075c, 0x75f075e, 0x7610760, 0x7660765, + 0x7680767, 0x76a0769, 0x76e076d, 0x770076f, 0x7720771, 0x7740773, + 0x7760775, 0x7780777, 0x77a0779, 0x77c077b, 0x780077f, 0x7850784, + 0x78a0788, 0x78e078c, 0x7920790, 0x7960794, 0x78b0789, 0x78f078d, + 0x7930791, 0x7970795, 0x79e079c, 0x7a207a0, 0x7a607a4, 0xffffffff, + 0x79f079d, 0x7a307a1, 0x7a707a5, 0xffffffff, 0x7b807b6, 0x7bc07ba, + 0x7c007be, 0x7c407c2, 0x7b907b7, 0x7bd07bb, 0x7c107bf, 0x7c507c3, + 0x7d807d6, 0x7dc07da, 0x7e007de, 0x7e407e2, 0x7d907d7, 0x7dd07db, + 0x7e107df, 0x7e507e3, 0x7f007ee, 0x7f407f2, 0x7f807f6, 0xffffffff, + 0x7f107ef, 0x7f507f3, 0x7f907f7, 0xffffffff, 0x80807fc, 0x80c07fe, + 0x8100800, 0x8140804, 0x809ffff, 0x80dffff, 0x811ffff, 0x815ffff, + 0x8240822, 0x8280826, 0x82c082a, 0x830082e, 0x8250823, 0x8290827, + 0x82d082b, 0x831082f, 0x8df08dd, 0x8f708f5, 0x8fb08f9, 0x90f090d, + 0x9370935, 0x9240922, 0x93b0939, 0xffffffff, 0x8590856, 0x85f085c, + 0x8650862, 0x86b0868, 0x85a0857, 0x860085d, 0x8660863, 0x86c0869, + 0x8890886, 0x88f088c, 0x8950892, 0x89b0898, 0x88a0887, 0x890088d, + 0x8960893, 0x89c0899, 0x8b908b6, 0x8bf08bc, 0x8c508c2, 0x8cb08c8, + 0x8ba08b7, 0x8c008bd, 0x8c608c3, 0x8cc08c9, 0x8db08d9, 0x8e108ce, + 0xffff08d3, 0x8d708d5, 0x8dc08da, 0x8e008de, 0xffff08e2, 0xffff08e7, + 0xffffffff, 0x8fd08e8, 0xffff08ed, 0x8f308f1, 0x8f808f6, 0x8fc08fa, + 0xffff08fe, 0xffffffff, 0x90b0909, 0x9030900, 0xffffffff, 0x9070905, + 0x90c090a, 0x910090e, 0xffffffff, 0xffffffff, 0x920091e, 0x9160913, + 0x9260918, 0x91c091a, 0x921091f, 0x9250923, 0xffff0927, 0xffffffff, + 0xffffffff, 0x93d092a, 0xffff092f, 0x9330931, 0x9380936, 0x93c093a, + 0xffff093e, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0764, 0xffffffff, 0x1500783, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0561, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0562, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x18902b0, 0x18d02c2, 0x19102d6, 0x195005e, + 0x1990064, 0x19d006c, 0x1a10076, 0x1a5007e, 0x18a02b1, 0x18e02c3, + 0x19202d7, 0x196005f, 0x19a0065, 0x19e006d, 0x1a20077, 0x1a6007f, + 0xffffffff, 0x1c0ffff, 0xffff01c1, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x5090475, + 0x50d0477, 0x511047d, 0x515048f, 0x51904a5, 0x2e80940, 0x2051f, + 0x7180523, 0x6e0527, 0x3a4052b, 0x110052f, 0x6630533, 0x54b0537, + 0x50a0476, 0x50e0478, 0x512047e, 0x5160490, 0x51a04a6, 0x2e90941, + 0x30520, 0x7190524, 0x6f0528, 0x3a5052c, 0x1110530, 0x6640534, + 0x54c0538, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x40000, 0xa0008, 0xe000c, 0x160010, 0x1a0018, 0x2ce001c, 0x2e602dc, + 0x560308, 0x600058, 0x660062, 0x70006a, 0x780074, 0x80007c, 0x2aa0082, + 0x60832, 0x64b0084, 0x2b805b5, 0x60d0609, 0x629061d, 0x1080106, + 0x112010a, 0x11c0116, 0x12c0124, 0xffff0134, 0x50001, 0xb0009, 0xf000d, + 0x170011, 0x1b0019, 0x2cf001d, 0x2e702dd, 0x570309, 0x610059, 0x670063, + 0x71006b, 0x790075, 0x81007d, 0x2ab0083, 0x70833, 0x64c0085, 0x2b905b6, + 0x60e060a, 0x62a061e, 0x1090107, 0x113010b, 0x11d0117, 0x12d0125, + 0xffff0135, 0x4f404f3, 0x486055a, 0x29102b5, 0x48b0297, 0x2ba048c, + 0x49302bb, 0x4980494, 0x49c02c7, 0xffff02cb, 0x2cd02cc, 0x2d4ffff, + 0xffff02d5, 0xffffffff, 0xffffffff, 0xffffffff, 0x4b402ed, 0x2f902f8, + 0x4ba04b9, 0x4bc04bb, 0x4be04bd, 0x4c204c1, 0x3250324, 0x32b032a, + 0x3350334, 0x33d033c, 0x3410340, 0x34d034c, 0x34f034e, 0x3230322, + 0x73f073e, 0x3ae03ad, 0x3b003af, 0x3b403b3, 0x3bf03be, 0x3d203d1, + 0x3e203e1, 0x5a405a3, 0x6060605, 0x61a0619, 0x6320631, 0x6560655, + 0x6a0069f, 0x46f046e, 0x7390738, 0x77e077d, 0x7d507d4, 0x8350834, + 0x6df06de, 0x6a406a3, 0x150014, 0x5d005c, 0x4ee04ed, 0x5020501, + 0x6120611, 0x1aa01a9, 0x5420541, 0x5540553, 0x5660565, 0x5760575, + 0x5860585, 0x5960595, 0x5a805a7, 0x5b805b7, 0x1620161, 0x3a703a6, + 0x3ea03e9, 0xffffffff, 0xffffffff, 0xffffffff, 0x60fffff, 0x6170610, + 0xffff0618, 0xffffffff, 0x6240623, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x890087, 0x8d008b, 0x91008f, + 0x950093, 0x990097, 0x9d009b, 0xa1009f, 0xa500a3, 0xa900a7, 0xad00ab, + 0xb100af, 0xb500b3, 0xb900b7, 0xbd00bb, 0xc100bf, 0xc500c3, 0x4c004b8, + 0x4d200cb, 0x4e600cf, 0x4f8ffff, 0xffffffff, 0xffffffff, 0x53affff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x2d902d8, 0x6160615, 0x61c061b, + 0x6220621, 0x6280627, 0x1e501e4, 0x62e062d, 0x6340633, 0x6380637, + 0x63e063d, 0x6420641, 0x32f032e, 0x65e065d, 0x66e066d, 0x67c067b, + 0x68a0689, 0x6980697, 0x6aa06a9, 0x6b806b7, 0x6c906c8, 0x6d706d6, + 0x6e706e6, 0x6f506f4, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x7870786, + 0x690068, 0x28f028e, 0x7b107b0, 0x7c907c8, 0x3bd03bc, 0x1f001e, + 0x8030802, 0x8190818, 0x2b302b2, 0x8370836, 0x2d302d2, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x7ab07aa, 0x7af07ae, 0x7b307b2, 0x2eb02ea, 0x2f502f4, 0x3070306, + 0x3110310, 0xffffffff, 0x7cb07ca, 0x7cf07ce, 0x7d307d2, 0x3330332, + 0x33b033a, 0x33f033e, 0x3490348, 0x7e707e6, 0x7e907e8, 0x7eb07ea, + 0x7ed07ec, 0x3ac03ab, 0x3b203b1, 0x3bb03ba, 0x7fb07fa, 0x3dc03db, + 0x3f003ef, 0x620061f, 0x2830282, 0x8070806, 0x80b080a, 0x80f080e, + 0x8130812, 0x8170816, 0x81b081a, 0x81f081e, 0x8210820, 0x4a0049f, + 0x4b004af, 0x4c404c3, 0x4d604d5, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x545ffff, 0x8380546, 0x83b0839, 0x83d083c, 0x580057f, + 0x590058f, 0x5a0059f, 0x5b205b1, 0xffffffff, 0x63bffff, 0x79b063c, + 0xffffffff, 0x6080607, 0x60c060b, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x62c062b, 0x630062f, 0x6360635, + 0x63a0639, 0x640063f, 0xffff0644, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x25e02f6, 0x2fc02fa, 0x30302fe, 0xffff0304, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x30cffff, 0x2c0030e, + 0x3140312, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x7a8ffff, 0x7ac02da, 0x2e202e0, 0x7b402e4, 0x2f00724, + 0x144076b, 0x30a0479, 0x318081c, 0x31a07c6, 0x7cc031c, 0x7d00320, + 0x32c022c, 0x336007a, 0xffff046c, 0xffffffff, 0xffffffff, 0x7a9ffff, + 0x7ad02db, 0x2e302e1, 0x7b502e5, 0x2f10725, 0x145076c, 0x30b047a, + 0x319081d, 0x31b07c7, 0x7cd031d, 0x7d10321, 0x32d022d, 0x337007b, + 0xffff046d, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x9280012, 0x72005a, 0x3520338, 0x114010c, 0x2bc0625, + 0x2f202ae, 0x31608ef, 0x10e064f, 0x2ee02b6, 0x4fb029a, 0x2c802be, + 0x2de02d0, 0x47f0798, 0x330031e, 0x24e0342, 0x3b802c4, 0x47b03e5, + 0x473056b, 0x72a06c4, 0x91104df, 0x9290013, 0x73005b, 0x3530339, + 0x115010d, 0x2bd0626, 0x2f302af, 0x31708f0, 0x10f0650, 0x2ef02b7, + 0x4fc029b, 0x2c902bf, 0x2df02d1, 0x4800799, 0x331031f, 0x24f0343, + 0x3b902c5, 0x47c03e6, 0x474056c, 0x72b06c5, 0x91204e0, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff]); +//4000 bytes +enum alphaTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x40, 0x160], [0x100, + 0x240, 0x5100], [0x3020100, 0x7060504, 0xb0a0908, 0xe0d0c0a, 0x3030303, + 0x100a0f03, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, + 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, + 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, + 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, + 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x10000, 0x30002, 0x50004, 0x70006, 0x90008, 0xb000a, 0xd000c, 0xf000e, + 0x10010, 0x120011, 0x10013, 0x150014, 0x170016, 0x190018, 0x1b001a, + 0x1c0001, 0x1e001d, 0x1f001f, 0x1f0020, 0x1f001f, 0x1f001f, 0x1f001f, + 0x220021, 0x1f0023, 0x250024, 0x1f001f, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x260001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x270001, 0x10001, 0x10001, 0x10028, + 0x2a0029, 0x2c002b, 0x2e002d, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x2f0001, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, + 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, + 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1001f, 0x310030, 0x320001, + 0x340033, 0x360035, 0x380037, 0x1f0039, 0x1f001f, 0x3b003a, 0x3d003c, + 0x1f003e, 0x1f001f, 0x40003f, 0x1f001f, 0x1f001f, 0x1f0041, 0x1f001f, + 0x1f001f, 0x1f001f, 0x1f001f, 0x10001, 0x420001, 0x1f0043, 0x1f001f, + 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x10001, 0x10001, 0x1f0044, + 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, + 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, + 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, + 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x10001, 0x1f0045, 0x1f001f, + 0x46001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, + 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, + 0x1f001f, 0x1f001f, 0x1f001f, 0x1f0047, 0x1f001f, 0x1f001f, 0x1f001f, + 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, + 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, + 0x490048, 0x4b004a, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, + 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f004c, + 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, + 0x1f001f, 0x10001, 0x10001, 0x10001, 0x1004d, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x4e0001, 0x1f004f, + 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, + 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, + 0x1f001f, 0x10001, 0x1f004f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, + 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, + 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, 0x1f001f, + 0x0, 0x0, 0x7fffffe, 0x7fffffe, 0x0, 0x4200400, 0xff7fffff, 0xff7fffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x3ffc3, 0x501f, 0x0, 0x0, 0x20, 0x3cdf0000, + 0xffffd740, 0xfffffffb, 0xffffffff, 0xffbfffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xfffffc03, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xfffe00ff, 0x27fffff, 0xfffffffe, 0xff, 0xbfff0000, + 0xffff00b6, 0x707ff, 0x7ff0000, 0xffffffff, 0xfeffffff, 0xffffc000, + 0xffffffff, 0xffffffff, 0x1fefffff, 0x9c00e1fe, 0xffff0000, 0xffffffff, + 0xffffe000, 0xffffffff, 0xffffffff, 0x3ffff, 0xfffffc00, 0x43007ff, + 0xfcffffff, 0x1fff, 0x1ffffff, 0x0, 0x0, 0x1ffd, 0x0, 0x7fff03f0, + 0xffffffff, 0xefffffff, 0xffe1dfff, 0xfefe000f, 0xfff99fee, 0xe3c5fdff, + 0xb080599f, 0x3000f, 0xfff987ee, 0xc36dfdff, 0x5e021987, 0x3f0000, + 0xfffbbfee, 0xe3edfdff, 0x11bbf, 0xf, 0xfff99fee, 0xe3edfdff, + 0xb0c0199f, 0x2000f, 0xd63dc7ec, 0xc3ffc718, 0x811dc7, 0x0, 0xfffddfee, + 0xe3effdff, 0x3601ddf, 0xf, 0xfffddfec, 0xe3effdff, 0x40601ddf, + 0x6000f, 0xfffddfec, 0xe7ffffff, 0x805ddf, 0xfc00000f, 0xfc7fffec, + 0x2ffbffff, 0xff5f807f, 0xc0000, 0xfffffffe, 0x7ffffff, 0x207f, 0x0, + 0xfef02596, 0x3bffecae, 0xf000205f, 0x0, 0x1, 0x0, 0xfffffeff, + 0xfffe1fff, 0xfeffff03, 0x1fffffff, 0x0, 0x0, 0xffffffff, 0xf97fffff, + 0xffff0000, 0xffffc1e7, 0x3000407f, 0xffffffff, 0xffff20bf, 0xf7ffffff, + 0xffffffff, 0xffffffff, 0x3d7f3dff, 0xffffffff, 0xffff3dff, 0x7f3dffff, + 0xff7fff3d, 0xffffffff, 0xff3dffff, 0xffffffff, 0x87ffffff, 0x0, + 0xffff, 0xffffffff, 0xffffffff, 0x1fffff, 0xfffffffe, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff9fff, 0x7fffffe, 0xffffffff, + 0xffffffff, 0x1c7ff, 0xfdfff, 0xfffff, 0xfffff, 0xddfff, 0xffffffff, + 0xffcfffff, 0x108001ff, 0x0, 0x0, 0xffffffff, 0xffffffff, 0xffffff, + 0xffffffff, 0xffff07ff, 0xffffffff, 0x3fffff, 0x1fffffff, 0x1ff0fff, + 0xffff0000, 0x1f3fff, 0xffffffff, 0xffff0fff, 0x3ff, 0x0, 0xfffffff, + 0xffffffff, 0x7fffffff, 0x1ffffe, 0x0, 0x80, 0x0, 0x0, 0xffffffff, + 0xffefffff, 0xfef, 0x0, 0xffffffff, 0xfc00f3ff, 0xffffffff, 0x3ffbf, + 0xffffffff, 0x3fffff, 0xfc00e000, 0x3fffffff, 0x0, 0x0, 0x0, 0x6fde00, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x0, 0x0, 0x3f3fffff, 0xffffffff, 0xaaff3f3f, 0x3fffffff, 0xffffffff, + 0x5fdfffff, 0xfcf1fdc, 0x1fdc1fff, 0x0, 0x0, 0x0, 0x80020000, + 0x1fff0000, 0x0, 0x0, 0x0, 0x3e2ffc84, 0xf3ffbd50, 0x43e0, 0xffffffff, + 0x1ff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xffc00000, 0xffffffff, 0x3ff, 0xffffffff, 0xffff7fff, + 0x7fffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xc781f, + 0xffffffff, 0xffff20bf, 0xffffffff, 0x80ff, 0x7fffff, 0x7f7f7f7f, + 0x7f7f7f7f, 0xffffffff, 0x0, 0x8000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe0, 0x1f3e03fe, 0xfffffffe, 0xffffffff, 0xe07fffff, 0xfffffffe, + 0xffffffff, 0xf7ffffff, 0xffffffe0, 0xfffe3fff, 0xffffffff, 0xffffffff, + 0x7fff, 0x7ffffff, 0x0, 0xffff0000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x3fffff, 0x0, 0x0, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x1fff, 0x0, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x1fff, 0x0, + 0xffff0000, 0x3fffffff, 0xffff1fff, 0xc00, 0xffffffff, 0x8ff07fff, + 0x80ffffff, 0xffffffff, 0xffffffff, 0xffff, 0xff800000, 0xfffffffc, + 0xffffffff, 0xffffffff, 0xf79ff, 0x7ff, 0x0, 0xff000000, 0xfffff7bb, + 0xff, 0xffffffff, 0xfffff, 0xffffffff, 0xffffffff, 0xf, 0x8fc0000, + 0xfffffc00, 0xffff07ff, 0x7ffff, 0x1fffffff, 0xffffffff, 0xfff7ffff, + 0x8000, 0x0, 0xffffffff, 0x7fffff, 0x3fff, 0x47fffff, 0xffffffff, + 0x7fffffff, 0x38000005, 0x3cffff, 0x7e7e7e, 0x7f7f, 0x0, 0x0, 0x0, 0x0, + 0xffffffff, 0x7ff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffff000f, 0xfffff87f, 0xfffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffff3fff, 0xffffffff, 0xffffffff, 0x3ffffff, 0x0, + 0xe0f8007f, 0x5f7ffdff, 0xffffffdb, 0xffffffff, 0xffffffff, 0x3ffff, + 0xfff80000, 0xffffffff, 0xffffffff, 0x3fffffff, 0xffff0000, 0xffffffff, + 0xfffcffff, 0xffffffff, 0xff, 0xfff0000, 0x0, 0x0, 0x0, 0xffdf0000, + 0xffffffff, 0xffffffff, 0xffffffff, 0x1fffffff, 0x0, 0x7fffffe, + 0x7fffffe, 0xffffffc0, 0xffffffff, 0x7fffffff, 0x1cfcfcfc, 0x0, + 0xffffefff, 0xb7ffff7f, 0x3fff3fff, 0x0, 0xffffffff, 0xffffffff, + 0xffffffff, 0x7ffffff, 0x0, 0x0, 0xffffffff, 0x1fffff, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1fffffff, 0xffffffff, 0x1ffff, 0x0, + 0x7fffffff, 0xffff0000, 0x7ff, 0x0, 0x3fffffff, 0xffffffff, 0x3eff0f, + 0x0, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x3fffffff, 0x0, + 0x0, 0x0, 0xfffffd3f, 0x91bfffff, 0x3fffff, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3fffff, 0x3ffffff, 0x0, 0x0, 0xffffffff, 0xc0ffffff, 0x0, 0x0, + 0xfeeff06f, 0xfffff, 0x0, 0x1fffffff, 0x0, 0x0, 0x0, 0x0, 0xffffffff, + 0x3fffff, 0x3fffff, 0x7ffff, 0x0, 0x0, 0x0, 0x0, 0xffffffff, + 0xffffffff, 0x1ff, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0xffffffff, + 0x3f, 0x0, 0xfffffffc, 0x1ffffff, 0xffff0000, 0x1ff, 0xffffffff, + 0x7ffff, 0x0, 0x0, 0xffffffff, 0xffffffff, 0x1e, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xffffffff, 0x3fffff, 0x0, 0x0, 0xffffffff, 0xffffffff, + 0xffffffff, 0x7fff, 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0xffffffff, + 0xffffffff, 0x7, 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0x7fff, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xffffffff, 0x1ffffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xffffffff, 0xffffffff, 0xffff001f, 0x7fffffff, 0xfff80000, 0x0, 0x0, + 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0xffffffff, + 0xffdfffff, 0xffffffff, 0xdfffffff, 0xebffde64, 0xffffffef, 0xffffffff, + 0xdfdfe7bf, 0x7bffffff, 0xfffdfc5f, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffff3f, 0xf7fffffd, 0xf7ffffff, 0xffdfffff, 0xffdfffff, + 0xffff7fff, 0xffff7fff, 0xfffffdff, 0xfffffdff, 0xff7, 0x0, 0xffffffef, + 0xaf7fe96, 0xaa96ea84, 0x5ef7f796, 0xffffbff, 0xffffbee, 0x0, 0x0, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x7fffff, 0x0, 0xffffffff, 0x1fffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x3fffffff, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x3fffffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); +//2304 bytes +enum markTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x40, 0xe0], [0x100, + 0x140, 0x2c00], [0x2020100, 0x4020302, 0x6020205, 0x2070202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020208, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x10000, 0x30002, 0x50004, 0x70006, 0x90008, 0xb000a, 0xd000c, 0xe, + 0xf0000, 0x0, 0x100000, 0x120011, 0x140013, 0x160015, 0x0, 0x17, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x190018, 0x0, 0x1a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x1d001c, 0x1f001e, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x200000, 0x0, 0x21, 0x220000, 0x0, 0x0, + 0x0, 0x0, 0x23, 0x0, 0x0, 0x250024, 0x0, 0x0, 0x26, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x270000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x280000, + 0x29, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2a0000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f8, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xfffe0000, 0xbfffffff, 0xb6, 0x0, 0x7ff0000, 0x0, + 0xfffff800, 0x10000, 0x0, 0x0, 0x9fc00000, 0x3d9f, 0x20000, 0xffff0000, + 0x7ff, 0x0, 0x0, 0x1ffc0, 0x0, 0xff800, 0xfbc00000, 0x3eef, 0xe000000, + 0x0, 0x0, 0x0, 0x0, 0x7ffffff0, 0xf, 0xdc000000, 0xfeffff, 0xc, 0xe, + 0xd0000000, 0x80399f, 0xc, 0xe, 0xd0000000, 0x23987, 0x230000, 0xe, + 0xd0000000, 0x3bbf, 0xc, 0xe, 0xd0000000, 0xc0399f, 0xc, 0x4, + 0xc0000000, 0x803dc7, 0x0, 0xe, 0xc0000000, 0x603ddf, 0xc, 0xc, + 0xd0000000, 0x603ddf, 0xc, 0xc, 0xc0000000, 0x803ddf, 0xc, 0xc, 0x0, + 0xff5f8400, 0xc0000, 0x0, 0x7f20000, 0x7f80, 0x0, 0x0, 0x1bf20000, + 0x3f00, 0x0, 0x3000000, 0xc2a00000, 0x0, 0xfffe0000, 0xfeffe0df, + 0x1fffffff, 0x40, 0x0, 0x0, 0x7ffff800, 0xc3c00000, 0x1e3f9d, + 0x3c00bffc, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe0000000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1c0000, 0x1c0000, 0xc0000, 0xc0000, 0x0, 0xfff00000, 0x200fffff, + 0x0, 0x3800, 0x0, 0x0, 0x0, 0x0, 0x200, 0x0, 0x0, 0x0, 0xfff0fff, 0x0, + 0x0, 0x0, 0xffff0000, 0x301, 0x0, 0xf800000, 0x0, 0x7fe00000, + 0x9fffffff, 0x0, 0x0, 0x0, 0x0, 0x1f, 0xfff00000, 0x1f, 0xff800, 0x7, + 0x3ffe, 0x0, 0xfffc0, 0x0, 0xfffff0, 0x0, 0x0, 0x0, 0x0, 0xfff70000, + 0x1c21ff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0xf000007f, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xffff0000, 0x1ffff, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x38000, 0x0, 0x0, 0x0, 0x80000000, 0x0, 0x0, 0x0, + 0xffffffff, 0x0, 0xfc00, 0x0, 0x0, 0x6000000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x3ff78000, 0x80000000, 0x0, 0x0, 0x30000, 0x844, 0xf8, 0x0, 0x0, + 0x3, 0xfff00000, 0x1f, 0x3ffff, 0x0, 0x3fc0, 0xfff80, 0x0, 0xf, + 0xfff80000, 0x1, 0x0, 0x0, 0x7ffe00, 0x3008, 0x8000000, 0x0, + 0xc19d0000, 0x2, 0x60f800, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x37f8, + 0x40000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffff, 0x7f, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20000000, + 0xf06e, 0x87000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff000000, + 0x7f, 0x0, 0x7, 0x7ff0000, 0x0, 0x0, 0x7, 0x1fff80, 0x0, 0x0, 0x7, + 0xfff80000, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfff800, 0x0, 0x0, 0x0, + 0x0, 0xfffe0000, 0x7fffffff, 0x78000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xf807e3e0, 0xfe7, 0x3c00, 0x0, 0x0, 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); +//2384 bytes +enum numberTrieEntries = TrieEntry!(bool, 8, 6, 7)([0x0, 0x40, 0x180], [0x100, + 0x280, 0x1a80], [0x2020100, 0x4020302, 0x2020605, 0x8070202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x10000, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x40003, 0x50002, + 0x20002, 0x70006, 0x60006, 0x90008, 0x6000a, 0x2000b, 0xc000c, 0x2000d, + 0xe0005, 0x20002, 0x20002, 0x2000f, 0x20002, 0x20002, 0x100002, + 0x110002, 0x2000e, 0x130012, 0x140002, 0xc, 0x20015, 0x20002, 0x20002, + 0x20002, 0x170016, 0x190018, 0x20002, 0x20002, 0x1b001a, 0x20002, + 0x20002, 0x1d001c, 0x20002, 0x20002, 0x20002, 0x20002, 0x1e0002, + 0x20002, 0x20002, 0x20002, 0x2001f, 0x200002, 0x220021, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x60023, 0x20002, 0xc0024, 0xc0017, 0x2000c, 0x40002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x2000e, 0x20002, 0x260025, 0x20002, + 0x280027, 0x230002, 0x20002, 0x20002, 0x20002, 0x20029, 0x2002a, + 0x2002b, 0x2002c, 0x20002, 0x20002, 0x2002d, 0x20002, 0x4002e, 0xc002f, + 0x20002, 0x20002, 0x20002, 0x20002, 0x50002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20030, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20031, 0x20002, 0x20002, 0x20002, 0x320002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20033, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, + 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x20002, 0x0, + 0x3ff0000, 0x0, 0x0, 0x0, 0x720c0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x3ff, 0x0, 0x0, 0x0, 0x3ff0000, 0x0, 0x0, 0x3ff, 0x0, + 0x0, 0x0, 0x0, 0xffc0, 0x0, 0x0, 0x0, 0x3f0ffc0, 0x0, 0x0, 0x0, + 0xfcffc0, 0x0, 0x0, 0x0, 0x7ffc0, 0x0, 0x0, 0x0, 0x7f00ffc0, 0x0, 0x0, + 0x0, 0x3fffc0, 0x0, 0x0, 0x3ff0000, 0x0, 0x0, 0xfffff, 0x0, 0x0, + 0x3ff0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1ffffe00, 0x0, 0x0, 0x0, + 0x1c000, 0x0, 0x0, 0x0, 0x3ff03ff, 0x0, 0x0, 0xffc0, 0x0, 0x0, 0x0, + 0x7ff0000, 0x0, 0x3ff03ff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3ff03ff, 0x0, + 0x0, 0x0, 0x0, 0x3f10000, 0x3ff, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffff0000, + 0xffffffff, 0x3e7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0xfffffff, + 0x0, 0x0, 0xfffffc00, 0x0, 0x0, 0x0, 0xffc00000, 0xfffff, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x20000000, 0x80, 0x70003fe, 0x0, 0x0, 0x3c0000, + 0x0, 0x0, 0x0, 0x0, 0x3ff, 0xfffeff00, 0x0, 0x3ff, 0xfffe0000, 0x0, + 0x0, 0x0, 0x3ff, 0x0, 0x0, 0x0, 0x3f0000, 0x0, 0x0, 0xffffff80, + 0xfffff, 0xffffffff, 0x1ffffff, 0x400, 0x0, 0x0, 0x0, 0x0, 0xf, 0x402, + 0x0, 0x0, 0x0, 0x3e0000, 0x0, 0x0, 0x0, 0xff000000, 0x0, 0xfc00000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x60000000, 0x0, 0x0, 0xff000000, + 0xff000000, 0x0, 0x0, 0x0, 0x7fffffff, 0x0, 0x0, 0xfffc0000, 0xffff, + 0x0, 0xffc00000, 0x0, 0x0, 0xffffffff, 0xffffffff, 0xffffffff, 0x7, + 0x0, 0x0, 0x0, 0x3ffff, 0x0, 0x0, 0xffffc000, 0xffffffff, 0x7ff, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); +//2336 bytes +enum punctuationTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x40, 0xc0], + [0x100, 0x100, 0x3100], [0x2020100, 0x4020302, 0x2020605, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x10000, 0x20001, 0x30001, 0x50004, 0x70006, 0x10008, 0x90001, + 0xb000a, 0x1000c, 0xd0001, 0x1000e, 0x10000f, 0x120011, 0x140013, + 0x10015, 0x10001, 0x10016, 0x170001, 0x10001, 0x180001, 0x190001, + 0x10001, 0x1b001a, 0x1001c, 0x1001d, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x1001e, 0x1001f, + 0x210020, 0x230022, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x240001, 0x260025, 0x270001, 0x280001, + 0x10001, 0x10001, 0x2a0029, 0x2c002b, 0x10001, 0x10001, 0x2e002d, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x1002f, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x0, + 0x8c00f7ee, 0xb8000001, 0x28000000, 0x0, 0x88c00882, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40000000, 0x80, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xfc000000, 0x0, 0x600, 0x40000000, 0x49, + 0x180000, 0xc8003600, 0x0, 0x0, 0x3c00, 0x0, 0x0, 0x100000, 0x0, + 0x3fff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3800000, 0x0, 0x7fff0000, + 0x40000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10030, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10000, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x100000, 0x0, 0x0, 0xc008000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x17fff0, 0x3c000000, 0x0, 0x0, 0x20, 0x0, 0x61f0000, 0x0, 0x0, + 0x0, 0xfc00, 0x0, 0x0, 0x0, 0x0, 0x8000000, 0x0, 0x0, 0x0, 0x1ff, 0x0, + 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6000, 0x18000000, 0x0, 0x0, 0x3800, 0x0, 0x600000, 0x0, 0x0, 0x0, + 0x0, 0x7700000, 0x0, 0x7ff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0000000, 0x0, 0x0, 0x0, 0x0, + 0x3f7f, 0x0, 0x0, 0x0, 0x0, 0xfc000000, 0x1, 0x0, 0x0, 0x0, 0xf0000000, + 0x0, 0xf8000000, 0x0, 0xc0000000, 0x0, 0x0, 0x800ff, 0x0, 0xffff0000, + 0xffff00ff, 0x7ffbffef, 0x60000000, 0x6000, 0x0, 0x0, 0x0, 0xf00, + 0x600, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3fff00, 0x0, 0x0, + 0x60, 0xffc0, 0x0, 0x0, 0x0, 0x0, 0x1fffff8, 0x0, 0xf000000, + 0x30000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xde000000, 0x0, 0x0, + 0x0, 0x10000, 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0xfff7fff, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xfff3ff0e, 0x20010000, 0x0, 0x0, 0x0, 0x1, 0x0, + 0x8000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0000000, 0xe000, 0x0, + 0x0, 0x40080000, 0x0, 0x0, 0x0, 0xfc0000, 0x0, 0x0, 0x0, 0xf00000, 0x0, + 0x0, 0xc000, 0x7000000, 0x0, 0xc000, 0x80000000, 0x0, 0x0, 0x0, + 0xc0003ffe, 0x0, 0x0, 0x0, 0xf0000000, 0x0, 0x0, 0x0, 0xc0000000, + 0x30000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x800, 0x0, 0xc0000000, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3ff0000, 0xffff0000, 0xfff7ffff, 0xd0b, + 0x0, 0x0, 0x0, 0x0, 0x8c00f7ee, 0xb8000001, 0xa8000000, 0x3f, 0x0, 0x0, + 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x80000000, 0x0, 0x10000, 0x0, 0x0, 0x0, 0x800000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x80000000, 0x80000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1ff0000, 0x80000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe000000, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f80, 0x0, 0x0, 0xd8000000, 0x3, 0x0, + 0x0, 0x0, 0xf, 0x0, 0x0, 0x0, 0x1e0, 0x0, 0x0, 0x0, 0x0, 0xf0000, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); +//2848 bytes +enum symbolTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x40, 0xe0], [0x100, + 0x140, 0x3d00], [0x3020100, 0x5030403, 0x3030306, 0x8070303, 0x3030303, + 0x3030303, 0x3030303, 0x3030303, 0x3030303, 0x3030303, 0x3030303, + 0x3030303, 0x3030303, 0x3030303, 0x3030303, 0x3030303, 0x3030303, + 0x3030303, 0x3030303, 0x3030303, 0x3030303, 0x3030303, 0x3030303, + 0x3030303, 0x3030303, 0x3030303, 0x3030303, 0x3030303, 0x3030303, + 0x3030303, 0x3030303, 0x3030303, 0x3030303, 0x3030303, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x10000, 0x30002, 0x50004, 0x70006, 0x80001, 0xa0009, 0xc000b, 0xe000d, + 0x1000f, 0x100001, 0x10001, 0x110001, 0x120001, 0x130001, 0x10001, + 0x140001, 0x160015, 0x180017, 0x170019, 0x1a0017, 0x1b0017, 0x1c0017, + 0x1001d, 0x1f001e, 0x210020, 0x170022, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x230001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10024, + 0x250001, 0x10026, 0x10027, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x280001, 0x290001, 0x2b002a, 0x2c0001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x2e002d, 0x30002f, 0x10001, 0x320031, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10033, 0x350034, 0x370036, 0x390038, 0x3b003a, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x0, 0x70000810, 0x40000000, 0x50000001, 0x0, + 0x113d37c, 0x800000, 0x800000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfffc003c, 0xffffafe0, 0x0, 0x0, 0x0, + 0x200000, 0x30, 0x0, 0x0, 0x400000, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x8000, 0x0, 0x0, 0x0, 0xc9c0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x40000000, 0x60000200, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x400000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0c0000, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x20000, 0x0, 0x0, 0x0, 0x10000, 0x0, 0x0, 0x0, + 0x7f80000, 0x0, 0x0, 0x0, 0x80000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x2000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80000000, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xfce8000e, 0x1500000, 0x0, 0x0, 0x0, 0xc0000000, + 0x1e0dfbf, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0000000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x3ff0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x8000000, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0xc0000000, 0xffffffff, + 0x0, 0x0, 0x0, 0x1ff007fe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xa0000000, 0xe000e003, 0x6000e000, 0x0, 0x0, 0x40010, 0x1c000000, + 0x1c00, 0x7ffffff, 0x0, 0x0, 0xc1d0037b, 0xc0042af, 0xbc1f, 0x0, + 0xffff0000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xfffff0ff, 0xfffff9ff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xfffff, 0xffffffff, 0x7f, 0x7ff, 0x0, 0xf0000000, + 0xffffffff, 0xffffffff, 0x3ff, 0xfffffffe, 0xffffffff, 0xffffffff, + 0xff, 0xfff00000, 0xffffffff, 0xffffff9f, 0xffff003f, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xfe000007, 0xffffffff, 0xf0ffffff, + 0xcfffffff, 0xffffffff, 0xffffffff, 0x3ff1fff, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7e0, 0x0, 0x0, 0x0, 0x0, + 0xfbffffff, 0xffffffff, 0xffffffff, 0xfffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x3fffff, 0xfff0000, + 0xc0010, 0xc0c00001, 0x0, 0x0, 0x18000000, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xffc30000, 0x0, 0xffffffff, 0xf, 0x7fffffff, 0xfffffc00, + 0x100ff, 0xffffffff, 0xfffffc00, 0x1ffff, 0xffffffff, 0x7fffffff, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0xffffffff, 0x0, 0x0, 0x0, 0x0, + 0xffff0000, 0xffffffff, 0x7f, 0x0, 0x7fffff, 0x3, 0x0, 0x0, 0x600, 0x0, + 0x0, 0x0, 0x0, 0x3c00f00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3800000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x200, 0x0, 0x0, 0x0, 0xfffc0000, + 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x30000000, 0x0, 0x0, 0x0, + 0x274, 0x0, 0x0, 0x0, 0x0, 0x70000810, 0x40000000, 0x50000001, 0x0, + 0x0, 0x0, 0x0, 0x30007f7f, 0x0, 0xff800000, 0x0, 0xfe000000, 0xfff03ff, + 0x0, 0xffff0000, 0x1fffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x3fffff, 0xffffffff, + 0xfffffe7f, 0xffffffff, 0x1c1f, 0xfffff018, 0xffffc3ff, 0x3fffffff, + 0x0, 0xffffffff, 0xffffffff, 0x23, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, + 0xffffffff, 0x7fffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x8000002, 0x8000000, 0x200000, 0x200000, 0x8000, 0x8000, 0x200, + 0x200, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x30000, + 0xffffffff, 0xffff0fff, 0xffffffff, 0xffffffff, 0xfffff, 0x7ffe7fff, + 0xfffefffe, 0x0, 0xffff0000, 0xffff7fff, 0xffffffff, 0xffff0fff, + 0x7ffffff, 0x0, 0x0, 0xffffffc0, 0xffff0007, 0x7ffffff, 0x301ff, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0xffbf0001, 0xffffffff, 0x1fffffff, + 0xfffff, 0xffffffff, 0x7df, 0x1ffff, 0xffffffff, 0x7fffffff, + 0xfffffffd, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x1effffff, + 0xffffffff, 0x3fffffff, 0xffff000f, 0xff, 0x0, 0x0, 0x0, 0xf8000000, + 0xffffffff, 0xffffffff, 0xffe1, 0x0, 0xffffffff, 0xffffffff, 0x3f, 0x0, + 0xffffffff, 0xffffffff, 0xffffffff, 0xfffff, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); +//4576 bytes +enum graphicalTrieEntries = TrieEntry!(bool, 8, 5, 8)([0x0, 0x40, 0x170], + [0x100, 0x260, 0x6100], [0x3020100, 0x7060504, 0xb0a0908, 0xe0d0c0a, + 0x3030303, 0x100a0f03, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, + 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, + 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, + 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, + 0xa0a0a11, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0xa0a0a0a, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x10000, 0x20001, 0x30001, 0x50004, 0x70006, 0x90008, 0xb000a, + 0xd000c, 0x1000e, 0x10000f, 0x10001, 0x120011, 0x140013, 0x160015, + 0x180017, 0x190001, 0x1b001a, 0x1c0001, 0x1001d, 0x1e0001, 0x10001, + 0x1f0001, 0x210020, 0x230022, 0x250024, 0x10026, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x270001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x280001, 0x10001, 0x10001, + 0x10029, 0x2b002a, 0x2d002c, 0x2f002e, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, + 0x10001, 0x10001, 0x300001, 0x310031, 0x310031, 0x310031, 0x310031, + 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, + 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x10031, 0x330032, + 0x340001, 0x360035, 0x380037, 0x3a0039, 0x31003b, 0x310031, 0x3d003c, + 0x3f003e, 0x310040, 0x310041, 0x430042, 0x310031, 0x310031, 0x310044, + 0x310031, 0x310031, 0x310031, 0x310031, 0x10001, 0x450001, 0x310046, + 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x10001, 0x10001, + 0x310047, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, + 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, + 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, + 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x10001, 0x310048, + 0x310031, 0x490031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, + 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, + 0x310031, 0x310031, 0x310031, 0x310031, 0x31004a, 0x310031, 0x310031, + 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, + 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x4c004b, + 0x4e004d, 0x50004f, 0x520051, 0x310031, 0x310031, 0x310031, 0x310031, + 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, + 0x310053, 0x550054, 0x570056, 0x590058, 0x5b005a, 0x310031, 0x310031, + 0x310031, 0x310031, 0x10001, 0x10001, 0x10001, 0x1005c, 0x10001, + 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x10001, 0x5d0001, + 0x31005e, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, + 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, + 0x310031, 0x310031, 0x10001, 0x31005e, 0x310031, 0x310031, 0x5f0031, + 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, + 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, + 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, + 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, 0x310031, + 0x310031, 0x310031, 0x310031, 0x0, 0xffffffff, 0xffffffff, 0x7fffffff, + 0x0, 0xffffdfff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x7cffffff, 0xffffd7f0, 0xfffffffb, + 0xffffffff, 0xffffffff, 0xffffffff, 0xfffe00ff, 0xfe7fffff, 0xfffffffe, + 0xfffe86ff, 0xffffffff, 0xffff00ff, 0x1f07ff, 0xcfffffc0, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xdfffffff, 0xffffffff, + 0xffff3fff, 0xffffffff, 0xffffe7ff, 0xffffffff, 0xffffffff, 0x3ffff, + 0xffffffff, 0x7ffffff, 0xffffffff, 0x7fff3fff, 0x4fffffff, 0x0, 0x0, + 0x1ffd, 0x0, 0x7ffffff0, 0xffffffff, 0xffffffff, 0xffffffff, + 0xfeffffff, 0xfff99fee, 0xf3c5fdff, 0xb080799f, 0xfffffcf, 0xfff987ee, + 0xd36dfdff, 0x5e023987, 0x3fffc0, 0xfffbbfee, 0xf3edfdff, 0x13bbf, + 0x3ffcf, 0xfff99fee, 0xf3edfdff, 0xb0c0399f, 0xffffcf, 0xd63dc7ec, + 0xc3ffc718, 0x813dc7, 0x7ffffc0, 0xfffddfee, 0xe3effdff, 0x3603ddf, + 0xff00ffcf, 0xfffddfec, 0xf3effdff, 0x40603ddf, 0x6ffcf, 0xfffddfec, + 0xe7ffffff, 0x807ddf, 0xfe3fffcf, 0xfc7fffec, 0x2ffbffff, 0xff5f847f, + 0x1c0000, 0xfffffffe, 0x87ffffff, 0xfffffff, 0x0, 0xfef02596, + 0x3bffecae, 0xf3ff3f5f, 0x0, 0xffffffff, 0xffffffff, 0xfffffeff, + 0xfffe1fff, 0xfeffffff, 0xdfffffff, 0x7ffdfff, 0x0, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff20bf, + 0xffffffff, 0xffffffff, 0xffffffff, 0x3d7f3dff, 0xffffffff, 0xffff3dff, + 0x7f3dffff, 0xff7fff3d, 0xffffffff, 0xff3dffff, 0xffffffff, 0xe7ffffff, + 0x1fffffff, 0x3ffffff, 0xffffffff, 0xffffffff, 0x1fffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x1fffffff, 0xffffffff, 0xffffffff, + 0x1ffff, 0x1fdfff, 0x7fffff, 0xfffff, 0xddfff, 0xffffffff, 0xffffffff, + 0x3fffffff, 0x3ff03ff, 0x3ff3fff, 0xffffffff, 0xffffffff, 0xffffff, + 0xffffffff, 0xffff07ff, 0xffffffff, 0x3fffff, 0x1fffffff, 0xfff0fff, + 0xfffffff1, 0x1f3fff, 0xffffffff, 0xffff0fff, 0xc7ff03ff, 0xffffffff, + 0xcfffffff, 0xffffffff, 0x7fffffff, 0x9fffffff, 0x3ff03ff, 0x3fff, 0x0, + 0x0, 0xffffffff, 0xffffffff, 0xffff0fff, 0x1fffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xf00fffff, 0xffffffff, 0xf8ffffff, 0xffffe3ff, + 0xffffffff, 0x0, 0x0, 0xffff00ff, 0x7fffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xf000007f, + 0x3f3fffff, 0xffffffff, 0xaaff3f3f, 0x3fffffff, 0xffffffff, 0xffdfffff, + 0xefcfffdf, 0x7fdcffff, 0xffff07ff, 0xffff80ff, 0xffffffff, 0xfff30000, + 0x1fff7fff, 0x7ffffff, 0xffff0000, 0x1ffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffff03ff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xfffff, 0xffffffff, 0x7f, 0x7ff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xfffffffe, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x3ff1fff, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0xffff7fff, + 0x7fffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xfe0fffff, + 0xffffffff, 0xffff20bf, 0xffffffff, 0x800180ff, 0x7fffff, 0x7f7f7f7f, + 0x7f7f7f7f, 0xffffffff, 0xffffffff, 0xfffffff, 0x0, 0x0, 0xfbffffff, + 0xffffffff, 0xffffffff, 0xfffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x3fffff, 0xfff0000, 0xffffffff, + 0xffffffff, 0xfffffffe, 0xffffffff, 0xfe7fffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffe0, 0xfffe3fff, 0xffffffff, 0xffffffff, 0xffff7fff, + 0x7ffffff, 0xffffffff, 0xffff000f, 0x7fffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x7fffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x3fffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x1fff, 0x0, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffff1fff, 0xffffffff, 0xffff007f, 0xffffffff, 0xffffffff, + 0xfff, 0xffffffff, 0xffffffff, 0x80ffffff, 0xffffffff, 0xffffffff, + 0xffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xf7fff, + 0x7ff, 0x0, 0xff000000, 0xffffffff, 0x3ff0fff, 0xffffffff, 0xffffff, + 0xffffffff, 0xffffffff, 0x3ffc01f, 0xfffffff, 0xffffffff, 0xffffffff, + 0x800fffff, 0x1fffffff, 0xffffffff, 0xffffffff, 0xc3ffbfff, 0x0, + 0xffffffff, 0x7fffff, 0xf3ff3fff, 0xfffffff, 0xffffffff, 0xffffffff, + 0xf8000007, 0x7fffff, 0x7e7e7e, 0x7f7f, 0x0, 0x0, 0x0, 0x0, 0xffffffff, + 0x3ff3fff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffff000f, 0xfffff87f, 0xfffffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff3fff, 0xffffffff, + 0xffffffff, 0x3ffffff, 0x0, 0xe0f8007f, 0x5f7fffff, 0xffffffdb, + 0xffffffff, 0xffffffff, 0xffffffff, 0xfff80003, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffff0000, 0xffffffff, 0xfffcffff, 0xffffffff, 0xff, + 0x3fff0000, 0x3ffffff, 0xffff007f, 0xfff7ffff, 0xffdf0f7f, 0xffffffff, + 0xffffffff, 0xffffffff, 0x1fffffff, 0xfffffffe, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x7fffffff, 0x1cfcfcfc, 0x30007f7f, 0xffffefff, + 0xb7ffff7f, 0x3fff3fff, 0x0, 0xffffffff, 0xffffffff, 0xffffffff, + 0x7ffffff, 0xffffff87, 0xff8fffff, 0xffffffff, 0xffffffff, 0xfff07ff, + 0x0, 0xffff0000, 0x3fffffff, 0x0, 0x0, 0x0, 0x0, 0x1fffffff, + 0xffffffff, 0x1ffff, 0x0, 0x7fffffff, 0xffff000f, 0x7ff, 0x0, + 0xbfffffff, 0xffffffff, 0x3fff0f, 0x0, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x3fffffff, 0x3ff, 0x0, 0x0, 0xfffffd3f, + 0x91bfffff, 0xffbfffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8fffffff, + 0x83ffffff, 0x0, 0x0, 0xffffffff, 0xc0ffffff, 0x0, 0x0, 0xfeeff06f, + 0x870fffff, 0x1ff00ff, 0xffffffff, 0x0, 0x0, 0x0, 0x0, 0xffffffff, + 0xfe3fffff, 0xff3fffff, 0xff07ffff, 0x0, 0x0, 0x0, 0x0, 0xffffffff, + 0xffffffff, 0x1ff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7fffffff, + 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0xffffffff, 0xfffc3fff, 0xffff, + 0xffffffff, 0xdfffffff, 0xffff0003, 0x3ff01ff, 0xffffffff, 0xffdfffff, + 0xf, 0x0, 0xffffffff, 0xffffffff, 0x3ff01ff, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xffffffff, 0xffffff, 0x3ff, 0x0, 0xffffffff, 0xffffffff, 0xffffffff, + 0x7fff, 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0xffffffff, 0xffffffff, + 0xf0007, 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0x7fff, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xffffffff, 0x1ffffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xffffffff, 0xffffffff, 0xffff001f, 0x7fffffff, 0xffff8000, 0x0, 0x0, + 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x3fffff, + 0xffffffff, 0xfffffe7f, 0xffffffff, 0xf807ffff, 0xffffffff, 0xffffffff, + 0x3fffffff, 0x0, 0xffffffff, 0xffffffff, 0x3f, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xffffffff, 0xffffffff, 0x7fffff, 0x3ffff, 0x0, 0x0, 0x0, 0x0, + 0xffffffff, 0xffffffff, 0xffdfffff, 0xffffffff, 0xdfffffff, 0xebffde64, + 0xffffffef, 0xffffffff, 0xdfdfe7bf, 0x7bffffff, 0xfffdfc5f, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffff3f, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffcfff, 0xffffffff, 0xffffffef, 0xaf7fe96, 0xaa96ea84, 0x5ef7f796, + 0xffffbff, 0xffffbee, 0x0, 0x30000, 0xffffffff, 0xffff0fff, 0xffffffff, + 0xffffffff, 0xfffff, 0x7ffe7fff, 0xfffefffe, 0x0, 0xffff07ff, + 0xffff7fff, 0xffffffff, 0xffff0fff, 0x7ffffff, 0x0, 0x0, 0xffffffc0, + 0xffff0007, 0x7ffffff, 0x301ff, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, + 0xffbf0001, 0xffffffff, 0x1fffffff, 0xfffff, 0xffffffff, 0x7df, + 0x1ffff, 0xffffffff, 0x7fffffff, 0xfffffffd, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x1effffff, 0xffffffff, 0x3fffffff, 0xffff000f, + 0xff, 0x0, 0x0, 0x0, 0xf8000000, 0xffffffff, 0xffffffff, 0xffe1, 0x0, + 0xffffffff, 0xffffffff, 0x3f, 0x0, 0xffffffff, 0xffffffff, 0xffffffff, + 0xfffff, 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x7fffff, 0x0, 0xffffffff, + 0x1fffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x3fffffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); +//3664 bytes +enum nonCharacterTrieEntries = TrieEntry!(bool, 7, 4, 4, 6)([0x0, 0x20, 0x98, + 0x208], [0x80, 0xf0, 0x2e0, 0x3180], [0x3020100, 0x7060504, 0xa090808, + 0xb0b0b0b, 0xb0b0b0b, 0xb0b0b0b, 0xb0b0b0b, 0xb0b0b0b, 0xb0b0b0b, + 0xb0b0b0b, 0xb0b0b0b, 0xb0b0b0b, 0xb0b0b0b, 0xb0b0b0b, 0xb0b0b0c, + 0xd080808, 0xd080808, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x10000, 0x30002, 0x50004, 0x70006, 0x90008, + 0xb000a, 0xd000c, 0xd000d, 0xd000d, 0xe000d, 0xd000d, 0xd000d, 0xd000d, + 0xd000d, 0xd000d, 0xd000d, 0xd000d, 0xd000d, 0xd000d, 0xf000d, + 0x10000d, 0xd0011, 0xd000d, 0xd000d, 0xd000d, 0xd000d, 0x12000d, + 0xd000d, 0xd000d, 0xd000d, 0xd000d, 0x140013, 0x160015, 0x180017, + 0x1a0019, 0x1b001b, 0x1d001c, 0x1b001b, 0x1e000d, 0x1b001b, 0x1b001b, + 0x1b001b, 0x1b001b, 0x1b001b, 0x1b001b, 0x20001f, 0x1b001b, 0x1b001b, + 0x1b001b, 0x1b001b, 0x1b001b, 0x1b001b, 0x1b001b, 0x1b001b, 0x1b0021, + 0x1b001b, 0x1b001b, 0x1b001b, 0x230022, 0x1b001b, 0x1b001b, 0x24001b, + 0x260025, 0x1b001b, 0xd000d, 0xd000d, 0xd000d, 0xd000d, 0xd000d, + 0xd000d, 0xd000d, 0xd000d, 0xd000d, 0xd000d, 0xd000d, 0xd000d, + 0x27000d, 0xd000d, 0x28000d, 0x1b0029, 0x1b001b, 0x1b001b, 0x1b001b, + 0x1b001b, 0x1b001b, 0x1b001b, 0x1b001b, 0x1b002a, 0x1b001b, 0x1b001b, + 0x1b001b, 0x1b001b, 0x1b001b, 0x1b001b, 0x1b001b, 0x1b001b, 0x1b002b, + 0x1b001b, 0x1b001b, 0x1b001b, 0x1b001b, 0x1b001b, 0x1b001b, 0x1b001b, + 0xd000d, 0xd000d, 0xd000d, 0xd000d, 0xd000d, 0xd000d, 0xd000d, + 0x2c000d, 0xd000d, 0xd000d, 0xd000d, 0xd000d, 0xd000d, 0xd000d, + 0xd000d, 0x2c000d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10000, 0x2, 0x0, + 0x0, 0x40003, 0x60005, 0x7, 0x0, 0x90008, 0xb000a, 0xd000c, 0xf000e, + 0x100000, 0x120011, 0x140013, 0x160015, 0x180017, 0x1a0019, 0x1c001b, + 0x1e001d, 0x20001f, 0x220021, 0x240023, 0x260025, 0x270000, 0x290028, + 0x0, 0x2a0000, 0x0, 0x0, 0x2b0000, 0x2d002c, 0x2f002e, 0x310030, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x330032, 0x350034, 0x360000, 0x380037, 0x3a0039, + 0x3c003b, 0x3e003d, 0x40003f, 0x420041, 0x430000, 0x440000, 0x460045, + 0x470042, 0x0, 0x480000, 0x0, 0x0, 0x4a0049, 0x4c004b, 0x4d0000, + 0x4f004e, 0x0, 0x50, 0x0, 0x0, 0x0, 0x510000, 0x530052, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x54, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x550000, 0x420042, + 0x570056, 0x580000, 0x5a0059, 0x5c005b, 0x42005d, 0x51005e, 0x0, + 0x5f0000, 0x540000, 0x60, 0x61, 0x630062, 0x57, 0x640000, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3a, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x650000, 0x0, 0x670066, + 0x0, 0x0, 0x68, 0x380069, 0x0, 0x6b006a, 0x38006c, 0x6d0000, 0x6e0000, + 0x6f0000, 0x710070, 0x720000, 0x420073, 0x740042, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x750063, 0x0, 0x0, 0x0, 0x0, 0x760000, 0x770000, + 0x790078, 0x7a0000, 0x0, 0x0, 0x7b0000, 0x7d007c, 0x7f007e, 0x800000, + 0x54, 0x810064, 0x830082, 0xb0000, 0x84, 0x860085, 0x420042, 0x870032, + 0x890088, 0x8b008a, 0x0, 0x42008c, 0x420042, 0x420042, 0x420042, + 0x420042, 0x420042, 0x420042, 0x8e008d, 0x420042, 0x42008f, 0x420090, + 0x920091, 0x420042, 0x940093, 0x420042, 0x950000, 0x420042, 0x420042, + 0x420042, 0x960042, 0x420042, 0x420042, 0x420042, 0x970000, 0x980000, + 0x99004b, 0x9a0000, 0x420042, 0x420042, 0x420042, 0x420042, 0x420042, + 0x420042, 0x420042, 0x420042, 0x420042, 0x9b0038, 0x420042, 0x420042, + 0x420042, 0x420042, 0x420042, 0x420042, 0x420042, 0x420042, 0x420042, + 0x420042, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9c0000, 0x420042, 0x9d0000, + 0x420042, 0x420042, 0x420042, 0x420042, 0x420042, 0x420042, 0x420042, + 0x42009c, 0x420042, 0x420042, 0x420042, 0x420042, 0x420042, 0x420042, + 0x420042, 0x0, 0x0, 0x0, 0x0, 0x42009e, 0x420042, 0x420042, 0x420042, + 0x420042, 0x420042, 0x420042, 0x420042, 0x420042, 0x420042, 0x9f0000, + 0x4200a0, 0x4200a1, 0x420042, 0x420042, 0x420042, 0x420042, 0x420042, + 0x420042, 0x420042, 0x0, 0x3a0000, 0xa2, 0xa30000, 0xa40000, 0x420042, + 0xa50000, 0x420042, 0xa60000, 0xa800a7, 0xaa00a9, 0x0, 0x0, 0xab, 0x0, + 0xac0000, 0x420042, 0x420042, 0x420042, 0x420042, 0xae00ad, 0xb000af, + 0x420042, 0x420042, 0x3d, 0xb200b1, 0x3d00b3, 0xb500b4, 0xb700b6, + 0x420042, 0xb900b8, 0xbb00ba, 0xbc0064, 0xbd0000, 0xbf00be, 0xc00042, + 0xc10000, 0xa40000, 0x510000, 0x420042, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xc20000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x31, 0x0, 0x4200a3, + 0x420042, 0x420042, 0x420042, 0x420042, 0x420042, 0x420042, 0x420042, + 0x0, 0x0, 0x0, 0x0, 0x4200a3, 0x420042, 0x420042, 0x420042, 0xc3, + 0x420042, 0x0, 0xc40000, 0x420042, 0x420042, 0x420042, 0x420042, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbe0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xbe0000, 0x0, 0x0, 0x0, 0x83000000, 0x280f, 0x4, 0x0, 0x1ff00, + 0x1800000, 0x1, 0x17900, 0x0, 0xff00, 0xffe0f800, 0x20000020, 0x0, + 0x4000, 0x0, 0x1800, 0x0, 0x0, 0xfffc0000, 0x0, 0xf8000000, 0x0, + 0x8000c000, 0xb0000000, 0xffffffff, 0xffffffff, 0xffffe002, 0xffffffff, + 0x8000000f, 0x0, 0x1000000, 0x66011, 0xc3a0200, 0x4f7f8660, 0xf0000030, + 0x67811, 0x2c920200, 0xa1fdc678, 0xffc0003f, 0x44011, 0xc120200, + 0xfffec440, 0xfffc0030, 0x66011, 0xc120200, 0x4f3fc660, 0xff000030, + 0x29c23813, 0x3c0038e7, 0xff7ec238, 0xf800003f, 0x22011, 0x1c100200, + 0xfc9fc220, 0xff0030, 0x22013, 0xc100200, 0xbf9fc220, 0xfff90030, + 0x22013, 0x18000000, 0xff7f8220, 0x1c00030, 0x3800013, 0xd0040000, + 0xa07b80, 0xffe3ffff, 0x1, 0x78000000, 0xf0000000, 0xffffffff, + 0x10fda69, 0xc4001351, 0xc00c0a0, 0xffffffff, 0x100, 0x1e000, + 0x1000000, 0x20000000, 0xf8002000, 0xffffffff, 0xdf40, 0x0, 0xc280c200, + 0x0, 0xc200, 0x80c20000, 0x8000c2, 0x0, 0xc20000, 0x0, 0x18000000, + 0xe0000000, 0xfc000000, 0x0, 0x0, 0xffe00000, 0xe0000000, 0x0, 0x0, + 0xfffe0000, 0xffe02000, 0xff800000, 0xfff00000, 0xfff22000, 0xc0000000, + 0xfc00fc00, 0xfc008000, 0x0, 0x0, 0xff000000, 0x0, 0xf800, 0x0, + 0xffc00000, 0xe0000000, 0xf000f000, 0xe, 0xffe0c000, 0x0, 0xf000, + 0x3800fc00, 0x0, 0x30000000, 0x0, 0x80000000, 0x60000000, 0xfc00fc00, + 0xffffc000, 0xffffffff, 0xffffffff, 0xf000, 0xe0000000, 0x0, 0xff00000, + 0x0, 0x7000000, 0x1c00, 0x0, 0xff00, 0xff800000, 0x0, 0xfffff80, + 0xc0c00000, 0x0, 0x5500c0c0, 0xc0000000, 0x0, 0x200000, 0x10300020, + 0x80230000, 0x0, 0xc0020, 0xe0008000, 0xf8000000, 0xffff, 0xfffe0000, + 0xfc00, 0x0, 0x0, 0xfff00000, 0x0, 0xffffff80, 0xfffff800, 0x0, 0x1, + 0x0, 0xfc00e000, 0xffffffff, 0x0, 0x8000, 0x80000000, 0x0, 0x0, + 0x1f00000, 0x0, 0xdf40, 0x0, 0x7ffe7f00, 0xff800000, 0x80808080, + 0x80808080, 0x0, 0x0, 0xf0000000, 0x4000000, 0x0, 0xffc00000, + 0xf000ffff, 0x1800000, 0x0, 0x1f, 0x1c000, 0x8000, 0xf8000000, 0x0, + 0xfff0, 0x0, 0x80000000, 0xffffe000, 0xffffffff, 0xe000, 0x0, 0xff80, + 0x0, 0x0, 0xfffff000, 0x7f000000, 0x0, 0xfff08000, 0xfffff800, + 0xffffffff, 0xffffff, 0x0, 0xfc00f000, 0xfc003fe0, 0xf0000000, + 0x7ff00000, 0xe0000000, 0x3c004000, 0xffffffff, 0x0, 0xff800000, + 0xc00c000, 0xf0000000, 0x7fffff8, 0xff800000, 0xff818181, 0xffff8080, + 0x0, 0xfc00c000, 0x780, 0xf0000000, 0x0, 0xc000, 0xfc000000, + 0xffffffff, 0x1f07ff80, 0xa0800000, 0x24, 0x0, 0x7fffc, 0x0, 0xffff, + 0x0, 0x30000, 0x0, 0xffffff00, 0xc000ffff, 0xfc000000, 0xff80, 0x80000, + 0x20f080, 0x0, 0x60000000, 0xe3030303, 0xc1ff8080, 0x1000, 0x48000080, + 0xc000c000, 0xffffffff, 0x78, 0x700000, 0xf000f800, 0xffffffff, 0xffff, + 0xc0000000, 0xfffe0000, 0xffffffff, 0x80000000, 0xfff0, 0xfffff800, + 0xffffffff, 0x40000000, 0x0, 0xffc000f0, 0xffffffff, 0xc0000000, + 0xfffffc00, 0x2c0, 0x6e400000, 0x400000, 0xffffffff, 0x70000000, + 0x7c000000, 0x0, 0x3f000000, 0x1100f90, 0x78f00000, 0xfe00ff00, 0x0, + 0x0, 0x1c00000, 0xc00000, 0xf80000, 0xfffffe00, 0xffffffff, 0xffffffff, + 0x80000000, 0x3c000, 0xffff0000, 0xfffc, 0xfc00fe00, 0xfffffff0, + 0xffffffff, 0xfc00fe00, 0xffffffff, 0xfffffc00, 0xffffffff, 0x0, + 0xffff8000, 0x0, 0xfff0fff8, 0x0, 0xfe000000, 0xffe0, 0x80000000, + 0x7fff, 0xffffffff, 0xfffffffc, 0xffffffff, 0x0, 0x180, 0xc0000000, + 0xffffffff, 0xffffffc0, 0xffffffff, 0xff800000, 0xfffc0000, 0x200000, + 0x0, 0x20000000, 0x1400219b, 0x10, 0x0, 0x20201840, 0x84000000, + 0x203a0, 0x0, 0x0, 0xc0, 0x3000, 0x0, 0x10, 0xf5080169, 0x5569157b, + 0xa1080869, 0xf0000400, 0xf0000411, 0xffffffff, 0xfffcffff, 0xfff00000, + 0x80018000, 0x10001, 0xffffffff, 0xf800, 0x8000, 0xf8000000, + 0xffffffff, 0xffffffff, 0x3f, 0xfff8, 0xf8000000, 0xfffcfe00, + 0xffffffff, 0x0, 0x40fffe, 0x0, 0xe0000000, 0xfff00000, 0x0, + 0xfffff820, 0xfffe0000, 0x2, 0x0, 0x0, 0xe1000000, 0x0, 0xc0000000, + 0xfff0, 0xffffff00, 0xffffffff, 0x7ffffff, 0xffff001e, 0xffffffff, + 0xff800000, 0xffffffff, 0xfffffffd, 0x0, 0x0, 0xffff0000, 0x0, 0xc0000000]); +enum MAX_SIMPLE_LOWER = 1043; +enum MAX_SIMPLE_UPPER = 1051; +enum MAX_SIMPLE_TITLE = 1055; +//8192 bytes +enum toUpperIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([0x0, 0x40, 0x200], + [0x100, 0x380, 0xc00], [0x2020100, 0x4020302, 0x2020205, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x10000, 0x30002, 0x50004, 0x70006, 0x90008, 0xa, 0xb0000, 0xd000c, + 0xf000e, 0x110010, 0x130012, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x150000, 0x0, + 0x170016, 0x190018, 0x1b001a, 0x1d001c, 0x0, 0x0, 0x1e0000, 0x1f, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x200000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x220021, 0x240023, 0x25, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x260000, + 0x27, 0x290028, 0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2c0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2e002d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff, 0x20001, + 0x40003, 0x60005, 0x80007, 0xa0009, 0xc000b, 0xe000d, 0x10000f, + 0x120011, 0x140013, 0x160015, 0x180017, 0xffff0019, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x1affff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x41bffff, 0x1c001b, 0x1e001d, 0x20001f, 0x220021, 0x240023, 0x260025, + 0x280027, 0x2a0029, 0x2c002b, 0x2e002d, 0x30002f, 0xffff0031, 0x330032, + 0x350034, 0x370036, 0x390038, 0x3affff, 0x3bffff, 0x3cffff, 0x3dffff, + 0x3effff, 0x3fffff, 0x40ffff, 0x41ffff, 0x42ffff, 0x43ffff, 0x44ffff, + 0x45ffff, 0x46ffff, 0x47ffff, 0x48ffff, 0x49ffff, 0x4affff, 0x4bffff, + 0x4cffff, 0x4dffff, 0x4effff, 0x4fffff, 0x50ffff, 0x51ffff, 0x52041d, + 0x53ffff, 0x54ffff, 0x55ffff, 0xffffffff, 0xffff0056, 0xffff0057, + 0xffff0058, 0xffff0059, 0xffff005a, 0xffff005b, 0xffff005c, 0x43a005d, + 0x5effff, 0x5fffff, 0x60ffff, 0x61ffff, 0x62ffff, 0x63ffff, 0x64ffff, + 0x65ffff, 0x66ffff, 0x67ffff, 0x68ffff, 0x69ffff, 0x6affff, 0x6bffff, + 0x6cffff, 0x6dffff, 0x6effff, 0x6fffff, 0x70ffff, 0x71ffff, 0x72ffff, + 0x73ffff, 0x74ffff, 0xffffffff, 0xffff0075, 0xffff0076, 0x780077, + 0xffff0079, 0x7affff, 0x7bffff, 0xffffffff, 0xffff007c, 0xffffffff, + 0xffff007d, 0xffffffff, 0xffffffff, 0xffff007e, 0x7fffff, 0xffffffff, + 0x80ffff, 0xffff0081, 0xffffffff, 0xffff0082, 0x83ffff, 0x84ffff, + 0x85ffff, 0xffffffff, 0xffff0086, 0xffffffff, 0x87ffff, 0xffffffff, + 0xffff0088, 0xffffffff, 0xffff0089, 0xffff008a, 0x8bffff, 0xffffffff, + 0x8cffff, 0x8dffff, 0xffffffff, 0xffffffff, 0x8effff, 0xffff008f, + 0x910090, 0x92ffff, 0xffff0093, 0xffff0094, 0xffff0095, 0xffff0096, + 0xffff0097, 0xffff0098, 0xffff0099, 0xffff009a, 0x9c009b, 0x9dffff, + 0x9effff, 0x9fffff, 0xa0ffff, 0xa1ffff, 0xa2ffff, 0xa3ffff, 0xa4ffff, + 0xa5ffff, 0xffff0442, 0xa700a6, 0xa8ffff, 0xffffffff, 0xa9ffff, + 0xaaffff, 0xabffff, 0xacffff, 0xadffff, 0xaeffff, 0xafffff, 0xb0ffff, + 0xb1ffff, 0xb2ffff, 0xb3ffff, 0xb4ffff, 0xb5ffff, 0xb6ffff, 0xb7ffff, + 0xb8ffff, 0xb9ffff, 0xbaffff, 0xbbffff, 0xbcffff, 0xffffffff, 0xbdffff, + 0xbeffff, 0xbfffff, 0xc0ffff, 0xc1ffff, 0xc2ffff, 0xc3ffff, 0xc4ffff, + 0xc5ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff00c6, + 0xc7ffff, 0xffff00c8, 0xffff00c9, 0xffffffff, 0xcaffff, 0xcbffff, + 0xccffff, 0xcdffff, 0xceffff, 0xd000cf, 0xd200d1, 0xffff00d3, 0xd500d4, + 0xd6ffff, 0xd7ffff, 0xffffffff, 0xffffffff, 0xffff00d8, 0xd9ffff, + 0xdaffff, 0xffff00db, 0xdd00dc, 0xdeffff, 0xffffffff, 0xdfffff, + 0xe0ffff, 0xffff00e1, 0xe2ffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xe3ffff, 0xffffffff, 0xffff00e4, 0xe5ffff, 0xffffffff, 0xffffffff, + 0xe700e6, 0xe900e8, 0xffff00ea, 0xffffffff, 0xffffffff, 0xffff00eb, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xecffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xedffff, 0xeeffff, + 0xffffffff, 0xefffff, 0xffffffff, 0xf0ffff, 0xf200f1, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffff043c, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xf400f3, 0xf600f5, + 0xf7043f, 0xf900f8, 0xfb00fa, 0xfd00fc, 0xff00fe, 0x1010100, 0x1030102, + 0x1050104, 0x1070106, 0x1090108, 0x10b010a, 0x10d010c, 0x10f010e, + 0x1110110, 0x1130112, 0xffff0114, 0x1160115, 0xffffffff, 0x117ffff, + 0x1190118, 0x11affff, 0x11bffff, 0x11cffff, 0x11dffff, 0x11effff, + 0x11fffff, 0x120ffff, 0x121ffff, 0x122ffff, 0x123ffff, 0x124ffff, + 0x125ffff, 0x1270126, 0xffff0128, 0x129ffff, 0xffffffff, 0xffff012a, + 0x12bffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x12d012c, 0x12f012e, 0x1310130, + 0x1330132, 0x1350134, 0x1370136, 0x1390138, 0x13b013a, 0x13d013c, + 0x13f013e, 0x1410140, 0x1430142, 0x1450144, 0x1470146, 0x1490148, + 0x14b014a, 0x14d014c, 0x14f014e, 0x1510150, 0x1530152, 0x1550154, + 0x1570156, 0x1590158, 0x15b015a, 0x15cffff, 0x15dffff, 0x15effff, + 0x15fffff, 0x160ffff, 0x161ffff, 0x162ffff, 0x163ffff, 0x164ffff, + 0x165ffff, 0x166ffff, 0x167ffff, 0x168ffff, 0x169ffff, 0x16affff, + 0x16bffff, 0x16cffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x16dffff, 0x16effff, 0x16fffff, 0x170ffff, 0x171ffff, 0x172ffff, + 0x173ffff, 0x174ffff, 0x175ffff, 0x176ffff, 0x177ffff, 0x178ffff, + 0x179ffff, 0x17affff, 0x17bffff, 0x17cffff, 0x17dffff, 0x17effff, + 0x17fffff, 0x180ffff, 0x181ffff, 0x182ffff, 0x183ffff, 0x184ffff, + 0x185ffff, 0x186ffff, 0x187ffff, 0xffffffff, 0xffff0188, 0xffff0189, + 0xffff018a, 0xffff018b, 0xffff018c, 0xffff018d, 0x18f018e, 0x190ffff, + 0x191ffff, 0x192ffff, 0x193ffff, 0x194ffff, 0x195ffff, 0x196ffff, + 0x197ffff, 0x198ffff, 0x199ffff, 0x19affff, 0x19bffff, 0x19cffff, + 0x19dffff, 0x19effff, 0x19fffff, 0x1a0ffff, 0x1a1ffff, 0x1a2ffff, + 0x1a3ffff, 0x1a4ffff, 0x1a5ffff, 0x1a6ffff, 0x1a7ffff, 0x1a8ffff, + 0x1a9ffff, 0x1aaffff, 0x1abffff, 0x1acffff, 0x1adffff, 0x1aeffff, + 0x1afffff, 0x1b0ffff, 0x1b1ffff, 0x1b2ffff, 0x1b3ffff, 0x1b4ffff, + 0x1b5ffff, 0x1b6ffff, 0x1b7ffff, 0x1b8ffff, 0x1b9ffff, 0x1baffff, + 0x1bbffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x1bcffff, + 0x1be01bd, 0x1c001bf, 0x1c201c1, 0x1c401c3, 0x1c601c5, 0x1c801c7, + 0x1ca01c9, 0x1cc01cb, 0x1ce01cd, 0x1d001cf, 0x1d201d1, 0x1d401d3, + 0x1d601d5, 0x1d801d7, 0x1da01d9, 0x1dc01db, 0x1de01dd, 0x1e001df, + 0x42e01e1, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x1e2ffff, 0xffffffff, 0x1e3ffff, + 0xffffffff, 0x1e4ffff, 0x1e5ffff, 0x1e6ffff, 0x1e7ffff, 0x1e8ffff, + 0x1e9ffff, 0x1eaffff, 0x1ebffff, 0x1ecffff, 0x1edffff, 0x1eeffff, + 0x1efffff, 0x1f0ffff, 0x1f1ffff, 0x1f2ffff, 0x1f3ffff, 0x1f4ffff, + 0x1f5ffff, 0x1f6ffff, 0x1f7ffff, 0x1f8ffff, 0x1f9ffff, 0x1faffff, + 0x1fbffff, 0x1fcffff, 0x1fdffff, 0x1feffff, 0x1ffffff, 0x200ffff, + 0x201ffff, 0x202ffff, 0x203ffff, 0x204ffff, 0x205ffff, 0x206ffff, + 0x207ffff, 0x208ffff, 0x209ffff, 0x20affff, 0x20bffff, 0x20cffff, + 0x20dffff, 0x20effff, 0x20fffff, 0x210ffff, 0x211ffff, 0x212ffff, + 0x213ffff, 0x214ffff, 0x215ffff, 0x216ffff, 0x217ffff, 0x218ffff, + 0x219ffff, 0x21affff, 0x21bffff, 0x21cffff, 0x21dffff, 0x21effff, + 0x21fffff, 0x220ffff, 0x221ffff, 0x222ffff, 0x223ffff, 0x224ffff, + 0x225ffff, 0x226ffff, 0x227ffff, 0x228ffff, 0x229ffff, 0x22affff, + 0x22bffff, 0x22cffff, 0x22dffff, 0x22effff, 0x4460444, 0x44a0448, + 0x22f044c, 0xffffffff, 0xffffffff, 0x230ffff, 0x231ffff, 0x232ffff, + 0x233ffff, 0x234ffff, 0x235ffff, 0x236ffff, 0x237ffff, 0x238ffff, + 0x239ffff, 0x23affff, 0x23bffff, 0x23cffff, 0x23dffff, 0x23effff, + 0x23fffff, 0x240ffff, 0x241ffff, 0x242ffff, 0x243ffff, 0x244ffff, + 0x245ffff, 0x246ffff, 0x247ffff, 0x248ffff, 0x249ffff, 0x24affff, + 0x24bffff, 0x24cffff, 0x24dffff, 0x24effff, 0x24fffff, 0x250ffff, + 0x251ffff, 0x252ffff, 0x253ffff, 0x254ffff, 0x255ffff, 0x256ffff, + 0x257ffff, 0x258ffff, 0x259ffff, 0x25affff, 0x25bffff, 0x25cffff, + 0x25dffff, 0x25effff, 0x25fffff, 0x2610260, 0x2630262, 0x2650264, + 0x2670266, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x2690268, + 0x26b026a, 0x26d026c, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x26f026e, 0x2710270, 0x2730272, 0x2750274, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x2770276, 0x2790278, 0x27b027a, + 0x27d027c, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x27f027e, + 0x2810280, 0x2830282, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x284044e, 0x2850450, 0x2860453, 0x2870456, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x2890288, 0x28b028a, 0x28d028c, + 0x28f028e, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x2910290, + 0x2930292, 0x2950294, 0x2970296, 0x2990298, 0x29b029a, 0x29d029c, + 0xffffffff, 0x4790477, 0x47d047b, 0x481047f, 0x4850483, 0x4890487, + 0x48d048b, 0x491048f, 0x4950493, 0x4990497, 0x49d049b, 0x4a1049f, + 0x4a504a3, 0x4a904a7, 0x4ad04ab, 0x4b104af, 0x4b504b3, 0x4b904b7, + 0x4bd04bb, 0x4c104bf, 0x4c504c3, 0x4c904c7, 0x4cd04cb, 0x4d104cf, + 0x4d504d3, 0x2b702b6, 0x4d704e3, 0xffff04e5, 0x4ef0459, 0xffffffff, + 0xffffffff, 0xffff04d9, 0xffff02b9, 0xffffffff, 0x4db04e7, 0xffff04e9, + 0x4f2045b, 0xffffffff, 0xffffffff, 0xffff04dd, 0xffffffff, 0x2bc02bb, + 0x460045d, 0xffffffff, 0x4650463, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2be02bd, 0x46b0468, 0x2bf046e, 0x4720470, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x4df04eb, 0xffff04ed, + 0x4f50475, 0xffffffff, 0xffffffff, 0xffff04e1, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffff02c1, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x2c302c2, + 0x2c502c4, 0x2c702c6, 0x2c902c8, 0x2cb02ca, 0x2cd02cc, 0x2cf02ce, + 0x2d102d0, 0xffffffff, 0xffffffff, 0xffff02d2, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x2d402d3, + 0x2d602d5, 0x2d802d7, 0x2da02d9, 0x2dc02db, 0x2de02dd, 0x2e002df, + 0x2e202e1, 0x2e402e3, 0x2e602e5, 0x2e802e7, 0x2ea02e9, 0x2ec02eb, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x2ee02ed, + 0x2f002ef, 0x2f202f1, 0x2f402f3, 0x2f602f5, 0x2f802f7, 0x2fa02f9, + 0x2fc02fb, 0x2fe02fd, 0x30002ff, 0x3020301, 0x3040303, 0x3060305, + 0x3080307, 0x30a0309, 0x30c030b, 0x30e030d, 0x310030f, 0x3120311, + 0x3140313, 0x3160315, 0x3180317, 0x31a0319, 0xffff031b, 0x31cffff, + 0xffffffff, 0x31dffff, 0xffff031e, 0xffff031f, 0xffff0320, 0xffff0321, + 0xffffffff, 0xffffffff, 0x322ffff, 0xffffffff, 0xffff0323, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x324ffff, 0x325ffff, 0x326ffff, + 0x327ffff, 0x328ffff, 0x329ffff, 0x32affff, 0x32bffff, 0x32cffff, + 0x32dffff, 0x32effff, 0x32fffff, 0x330ffff, 0x331ffff, 0x332ffff, + 0x333ffff, 0x334ffff, 0x335ffff, 0x336ffff, 0x337ffff, 0x338ffff, + 0x339ffff, 0x33affff, 0x33bffff, 0x33cffff, 0x33dffff, 0x33effff, + 0x33fffff, 0x340ffff, 0x341ffff, 0x342ffff, 0x343ffff, 0x344ffff, + 0x345ffff, 0x346ffff, 0x347ffff, 0x348ffff, 0x349ffff, 0x34affff, + 0x34bffff, 0x34cffff, 0x34dffff, 0x34effff, 0x34fffff, 0x350ffff, + 0x351ffff, 0x352ffff, 0x353ffff, 0x354ffff, 0x355ffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0356, 0xffff0357, 0xffffffff, + 0x358ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x35a0359, 0x35c035b, 0x35e035d, 0x360035f, 0x3620361, + 0x3640363, 0x3660365, 0x3680367, 0x36a0369, 0x36c036b, 0x36e036d, + 0x370036f, 0x3720371, 0x3740373, 0x3760375, 0x3780377, 0x37a0379, + 0x37c037b, 0x37e037d, 0x37fffff, 0xffffffff, 0xffffffff, 0x380ffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x381ffff, 0x382ffff, 0x383ffff, + 0x384ffff, 0x385ffff, 0x386ffff, 0x387ffff, 0x388ffff, 0x389ffff, + 0x38affff, 0x38bffff, 0x38cffff, 0x38dffff, 0x38effff, 0x38fffff, + 0x390ffff, 0x391ffff, 0x392ffff, 0x393ffff, 0x394ffff, 0x395ffff, + 0x396ffff, 0x397ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x398ffff, + 0x399ffff, 0x39affff, 0x39bffff, 0x39cffff, 0x39dffff, 0x39effff, + 0x39fffff, 0x3a0ffff, 0x3a1ffff, 0x3a2ffff, 0x3a3ffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x3a4ffff, 0x3a5ffff, 0x3a6ffff, 0x3a7ffff, 0x3a8ffff, 0x3a9ffff, + 0x3aaffff, 0xffffffff, 0x3abffff, 0x3acffff, 0x3adffff, 0x3aeffff, + 0x3afffff, 0x3b0ffff, 0x3b1ffff, 0x3b2ffff, 0x3b3ffff, 0x3b4ffff, + 0x3b5ffff, 0x3b6ffff, 0x3b7ffff, 0x3b8ffff, 0x3b9ffff, 0x3baffff, + 0x3bbffff, 0x3bcffff, 0x3bdffff, 0x3beffff, 0x3bfffff, 0x3c0ffff, + 0x3c1ffff, 0x3c2ffff, 0x3c3ffff, 0x3c4ffff, 0x3c5ffff, 0x3c6ffff, + 0x3c7ffff, 0x3c8ffff, 0x3c9ffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffff03ca, 0xffff03cb, 0x3ccffff, 0x3cdffff, + 0x3ceffff, 0x3cfffff, 0x3d0ffff, 0xffffffff, 0xffffffff, 0xffff03d1, + 0xffffffff, 0x3d2ffff, 0x3d3ffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x3d4ffff, 0x3d5ffff, 0x3d6ffff, + 0x3d7ffff, 0x3d8ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x420041e, 0x4240422, 0x42a0427, 0xffff042c, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x430ffff, 0x4340432, + 0x4380436, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x3d9ffff, 0x3db03da, 0x3dd03dc, + 0x3df03de, 0x3e103e0, 0x3e303e2, 0x3e503e4, 0x3e703e6, 0x3e903e8, + 0x3eb03ea, 0x3ed03ec, 0x3ef03ee, 0x3f103f0, 0xffff03f2, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x3f403f3, 0x3f603f5, 0x3f803f7, 0x3fa03f9, 0x3fc03fb, + 0x3fe03fd, 0x40003ff, 0x4020401, 0x4040403, 0x4060405, 0x4080407, + 0x40a0409, 0x40c040b, 0x40e040d, 0x410040f, 0x4120411, 0x4140413, + 0x4160415, 0x4180417, 0x41a0419, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff]); +//8064 bytes +enum toLowerIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([0x0, 0x40, 0x200], + [0x100, 0x380, 0xbc0], [0x2020100, 0x4020302, 0x2020205, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x10000, 0x20000, 0x40003, 0x60005, 0x80007, 0x0, 0x90000, 0xb000a, + 0xd000c, 0xf000e, 0x110010, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x140013, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x160015, 0x180017, 0x1a0019, 0x1c001b, 0x0, 0x0, 0x1e001d, 0x1f, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x210020, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x230022, 0x250024, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x260000, + 0x27, 0x290028, 0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2b, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x2c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x2d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffff, 0x20001, 0x40003, 0x60005, 0x80007, 0xa0009, 0xc000b, 0xe000d, + 0x10000f, 0x120011, 0x140013, 0x160015, 0x180017, 0xffff0019, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x1b001a, 0x1d001c, 0x1f001e, 0x210020, 0x230022, 0x250024, 0x270026, + 0x290028, 0x2b002a, 0x2d002c, 0x2f002e, 0xffff0030, 0x320031, 0x340033, + 0x360035, 0x4130037, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffff0038, 0xffff0039, 0xffff003a, 0xffff003b, 0xffff003c, 0xffff003d, + 0xffff003e, 0xffff003f, 0xffff0040, 0xffff0041, 0xffff0042, 0xffff0043, + 0xffff0044, 0xffff0045, 0xffff0046, 0xffff0047, 0xffff0048, 0xffff0049, + 0xffff004a, 0xffff004b, 0xffff004c, 0xffff004d, 0xffff004e, 0xffff004f, + 0xffff0414, 0xffff0051, 0xffff0052, 0xffff0053, 0x54ffff, 0x55ffff, + 0x56ffff, 0x57ffff, 0x58ffff, 0x59ffff, 0x5affff, 0x5bffff, 0x423ffff, + 0xffff005c, 0xffff005d, 0xffff005e, 0xffff005f, 0xffff0060, 0xffff0061, + 0xffff0062, 0xffff0063, 0xffff0064, 0xffff0065, 0xffff0066, 0xffff0067, + 0xffff0068, 0xffff0069, 0xffff006a, 0xffff006b, 0xffff006c, 0xffff006d, + 0xffff006e, 0xffff006f, 0xffff0070, 0xffff0071, 0xffff0072, 0x740073, + 0x75ffff, 0x76ffff, 0xffffffff, 0x77ffff, 0xffff0078, 0xffff0079, + 0x7b007a, 0x7cffff, 0x7e007d, 0xffffffff, 0x80007f, 0x820081, 0x83ffff, + 0xffff0084, 0x860085, 0xffff0087, 0xffffffff, 0x890088, 0x8affff, + 0xffff008b, 0xffff008c, 0xffff008d, 0x8f008e, 0x90ffff, 0xffffffff, + 0xffff0091, 0x930092, 0x94ffff, 0x960095, 0x97ffff, 0x98ffff, + 0xffff0099, 0xffffffff, 0xffff009a, 0xffffffff, 0xffffffff, 0xffffffff, + 0x9c009b, 0x9dffff, 0xffff009e, 0xa0009f, 0xa1ffff, 0xa2ffff, 0xa3ffff, + 0xa4ffff, 0xa5ffff, 0xa6ffff, 0xa7ffff, 0xa8ffff, 0xffffffff, + 0xffff00a9, 0xffff00aa, 0xffff00ab, 0xffff00ac, 0xffff00ad, 0xffff00ae, + 0xffff00af, 0xffff00b0, 0xffff00b1, 0xb20426, 0xffff00b3, 0xffff00b4, + 0xb600b5, 0xffff00b7, 0xffff00b8, 0xffff00b9, 0xffff00ba, 0xffff00bb, + 0xffff00bc, 0xffff00bd, 0xffff00be, 0xffff00bf, 0xffff00c0, 0xffff00c1, + 0xffff00c2, 0xffff00c3, 0xffff00c4, 0xffff00c5, 0xffff00c6, 0xffff00c7, + 0xffff00c8, 0xffff00c9, 0xffff00ca, 0xffff00cb, 0xffff00cc, 0xffff00cd, + 0xffff00ce, 0xffff00cf, 0xffff00d0, 0xffff00d1, 0xffff00d2, 0xffff00d3, + 0xffff00d4, 0xffffffff, 0xffffffff, 0xffffffff, 0xd600d5, 0xd7ffff, + 0xffff00d8, 0xd9ffff, 0xdaffff, 0xdc00db, 0xffff00dd, 0xffff00de, + 0xffff00df, 0xffff00e0, 0xffff00e1, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff00e2, 0xffff00e3, 0xffffffff, + 0xffff00e4, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffff00e5, 0xe700e6, 0xffff00e8, 0xffff00e9, + 0xeb00ea, 0xec0424, 0xee00ed, 0xf000ef, 0xf200f1, 0xf400f3, 0xf600f5, + 0xf800f7, 0xfa00f9, 0xfc00fb, 0xfdffff, 0xff00fe, 0x1010100, 0x1030102, + 0x1050104, 0xffffffff, 0xffffffff, 0xffff0425, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x106ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0107, + 0xffff0108, 0xffff0109, 0xffff010a, 0xffff010b, 0xffff010c, 0xffff010d, + 0xffff010e, 0xffff010f, 0xffff0110, 0xffff0111, 0xffff0112, 0xffffffff, + 0xffffffff, 0xffff0113, 0x114ffff, 0x115ffff, 0xffff0116, 0x117ffff, + 0x1190118, 0x11b011a, 0x11d011c, 0x11f011e, 0x1210120, 0x1230122, + 0x1250124, 0x1270126, 0x1290128, 0x12b012a, 0x12d012c, 0x12f012e, + 0x1310130, 0x1330132, 0x1350134, 0x1370136, 0x1390138, 0x13b013a, + 0x13d013c, 0x13f013e, 0x1410140, 0x1430142, 0x1450144, 0x1470146, + 0x1490148, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffff014a, 0xffff014b, 0xffff014c, 0xffff014d, 0xffff014e, + 0xffff014f, 0xffff0150, 0xffff0151, 0xffff0152, 0xffff0153, 0xffff0154, + 0xffff0155, 0xffff0156, 0xffff0157, 0xffff0158, 0xffff0159, 0xffff015a, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff015b, 0xffff015c, + 0xffff015d, 0xffff015e, 0xffff015f, 0xffff0160, 0xffff0161, 0xffff0162, + 0xffff0163, 0xffff0164, 0xffff0165, 0xffff0166, 0xffff0167, 0xffff0168, + 0xffff0169, 0xffff016a, 0xffff016b, 0xffff016c, 0xffff016d, 0xffff016e, + 0xffff016f, 0xffff0170, 0xffff0171, 0xffff0172, 0xffff0173, 0xffff0174, + 0xffff0175, 0x1770176, 0x178ffff, 0x179ffff, 0x17affff, 0x17bffff, + 0x17cffff, 0x17dffff, 0xffffffff, 0xffff017e, 0xffff017f, 0xffff0180, + 0xffff0181, 0xffff0182, 0xffff0183, 0xffff0184, 0xffff0185, 0xffff0186, + 0xffff0187, 0xffff0188, 0xffff0189, 0xffff018a, 0xffff018b, 0xffff018c, + 0xffff018d, 0xffff018e, 0xffff018f, 0xffff0190, 0xffff0191, 0xffff0192, + 0xffff0193, 0xffff0194, 0xffff0195, 0xffff0196, 0xffff0197, 0xffff0198, + 0xffff0199, 0xffff019a, 0xffff019b, 0xffff019c, 0xffff019d, 0xffff019e, + 0xffff019f, 0xffff01a0, 0xffff01a1, 0xffff01a2, 0xffff01a3, 0xffff01a4, + 0xffff01a5, 0xffff01a6, 0xffff01a7, 0xffff01a8, 0xffff01a9, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x1aaffff, 0x1ac01ab, 0x1ae01ad, + 0x1b001af, 0x1b201b1, 0x1b401b3, 0x1b601b5, 0x1b801b7, 0x1ba01b9, + 0x1bc01bb, 0x1be01bd, 0x1c001bf, 0x1c201c1, 0x1c401c3, 0x1c601c5, + 0x1c801c7, 0x1ca01c9, 0x1cc01cb, 0x1ce01cd, 0xffff01cf, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x41dffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x1d101d0, 0x1d301d2, 0x1d501d4, 0x1d701d6, 0x1d901d8, + 0x1db01da, 0x1dd01dc, 0x1df01de, 0x1e101e0, 0x1e301e2, 0x1e501e4, + 0x1e701e6, 0x1e901e8, 0x1eb01ea, 0x1ed01ec, 0x1ef01ee, 0x1f101f0, + 0x1f301f2, 0x1f501f4, 0x1f6ffff, 0xffffffff, 0xffffffff, 0x1f7ffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffff01f8, 0xffff01f9, 0xffff01fa, 0xffff01fb, 0xffff01fc, + 0xffff01fd, 0xffff01fe, 0xffff01ff, 0xffff0200, 0xffff0201, 0xffff0202, + 0xffff0203, 0xffff0204, 0xffff0205, 0xffff0206, 0xffff0207, 0xffff0208, + 0xffff0209, 0xffff020a, 0xffff020b, 0xffff020c, 0xffff020d, 0xffff020e, + 0xffff020f, 0xffff0210, 0xffff0211, 0xffff0212, 0xffff0213, 0xffff0214, + 0xffff0215, 0xffff0216, 0xffff0217, 0xffff0218, 0xffff0219, 0xffff021a, + 0xffff021b, 0xffff021c, 0xffff021d, 0xffff021e, 0xffff021f, 0xffff0220, + 0xffff0221, 0xffff0222, 0xffff0223, 0xffff0224, 0xffff0225, 0xffff0226, + 0xffff0227, 0xffff0228, 0xffff0229, 0xffff022a, 0xffff022b, 0xffff022c, + 0xffff022d, 0xffff022e, 0xffff022f, 0xffff0230, 0xffff0231, 0xffff0232, + 0xffff0233, 0xffff0234, 0xffff0235, 0xffff0236, 0xffff0237, 0xffff0238, + 0xffff0239, 0xffff023a, 0xffff023b, 0xffff023c, 0xffff023d, 0xffff023e, + 0xffff023f, 0xffff0240, 0xffff0241, 0xffff0242, 0x4280427, 0x42a0429, + 0xffff042b, 0xffffffff, 0xffff0243, 0xffff0244, 0xffff0245, 0xffff0246, + 0xffff0247, 0xffff0248, 0xffff0249, 0xffff024a, 0xffff024b, 0xffff024c, + 0xffff024d, 0xffff024e, 0xffff024f, 0xffff0250, 0xffff0251, 0xffff0252, + 0xffff0253, 0xffff0254, 0xffff0255, 0xffff0256, 0xffff0257, 0xffff0258, + 0xffff0259, 0xffff025a, 0xffff025b, 0xffff025c, 0xffff025d, 0xffff025e, + 0xffff025f, 0xffff0260, 0xffff0261, 0xffff0262, 0xffff0263, 0xffff0264, + 0xffff0265, 0xffff0266, 0xffff0267, 0xffff0268, 0xffff0269, 0xffff026a, + 0xffff026b, 0xffff026c, 0xffff026d, 0xffff026e, 0xffff026f, 0xffff0270, + 0xffff0271, 0xffff0272, 0xffff0273, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2750274, 0x2770276, 0x2790278, 0x27b027a, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x27d027c, 0x27f027e, 0x2810280, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x2830282, + 0x2850284, 0x2870286, 0x2890288, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x28b028a, 0x28d028c, 0x28f028e, 0x2910290, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x2930292, 0x2950294, 0x2970296, + 0xffffffff, 0xffff042c, 0xffff042d, 0xffff042e, 0xffff042f, 0x298ffff, + 0x299ffff, 0x29affff, 0x29bffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x29d029c, 0x29f029e, 0x2a102a0, 0x2a302a2, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x43d043c, 0x43f043e, 0x4410440, 0x4430442, 0x4450444, + 0x4470446, 0x4490448, 0x44b044a, 0x44d044c, 0x44f044e, 0x4510450, + 0x4530452, 0x4550454, 0x4570456, 0x4590458, 0x45b045a, 0x45d045c, + 0x45f045e, 0x4610460, 0x4630462, 0x4650464, 0x4670466, 0x4690468, + 0x46b046a, 0xffffffff, 0x46c0472, 0xffff0473, 0x4780430, 0x2bd02bc, + 0x2bf02be, 0xffff046d, 0xffffffff, 0xffffffff, 0x46e0474, 0xffff0475, + 0x4790431, 0x2c202c1, 0x2c402c3, 0xffff046f, 0xffffffff, 0xffffffff, + 0x4330432, 0xffffffff, 0x4350434, 0x2c702c6, 0x2c902c8, 0xffffffff, + 0xffffffff, 0xffffffff, 0x4370436, 0xffff0438, 0x43a0439, 0x2cb02ca, + 0x2cd02cc, 0xffff02ce, 0xffffffff, 0xffffffff, 0x4700476, 0xffff0477, + 0x47a043b, 0x2d002cf, 0x2d202d1, 0xffff0471, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffff02d4, 0xffffffff, 0x2d602d5, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffff02d7, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x2d902d8, + 0x2db02da, 0x2dd02dc, 0x2df02de, 0x2e102e0, 0x2e302e2, 0x2e502e4, + 0x2e702e6, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x2e8ffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x2ea02e9, 0x2ec02eb, 0x2ee02ed, 0x2f002ef, + 0x2f202f1, 0x2f402f3, 0x2f602f5, 0x2f802f7, 0x2fa02f9, 0x2fc02fb, + 0x2fe02fd, 0x30002ff, 0x3020301, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x3040303, 0x3060305, 0x3080307, + 0x30a0309, 0x30c030b, 0x30e030d, 0x310030f, 0x3120311, 0x3140313, + 0x3160315, 0x3180317, 0x31a0319, 0x31c031b, 0x31e031d, 0x320031f, + 0x3220321, 0x3240323, 0x3260325, 0x3280327, 0x32a0329, 0x32c032b, + 0x32e032d, 0x330032f, 0xffff0331, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0332, 0x3340333, 0xffff0335, + 0x336ffff, 0x337ffff, 0x338ffff, 0x339ffff, 0x33b033a, 0xffff033c, + 0xffff033d, 0x33effff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x340033f, 0xffff0341, 0xffff0342, 0xffff0343, 0xffff0344, 0xffff0345, + 0xffff0346, 0xffff0347, 0xffff0348, 0xffff0349, 0xffff034a, 0xffff034b, + 0xffff034c, 0xffff034d, 0xffff034e, 0xffff034f, 0xffff0350, 0xffff0351, + 0xffff0352, 0xffff0353, 0xffff0354, 0xffff0355, 0xffff0356, 0xffff0357, + 0xffff0358, 0xffff0359, 0xffff035a, 0xffff035b, 0xffff035c, 0xffff035d, + 0xffff035e, 0xffff035f, 0xffff0360, 0xffff0361, 0xffff0362, 0xffff0363, + 0xffff0364, 0xffff0365, 0xffff0366, 0xffff0367, 0xffff0368, 0xffff0369, + 0xffff036a, 0xffff036b, 0xffff036c, 0xffff036d, 0xffff036e, 0xffff036f, + 0xffff0370, 0xffff0371, 0xffff0372, 0xffffffff, 0xffffffff, 0xffffffff, + 0x373ffff, 0x374ffff, 0xffffffff, 0xffffffff, 0xffff0375, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0376, + 0xffff0377, 0xffff0378, 0xffff0379, 0xffff037a, 0xffff037b, 0xffff037c, + 0xffff037d, 0xffff037e, 0xffff037f, 0xffff0380, 0xffff0381, 0xffff0382, + 0xffff0383, 0xffff0384, 0xffff0385, 0xffff0386, 0xffff0387, 0xffff0388, + 0xffff0389, 0xffff038a, 0xffff038b, 0xffff038c, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffff038d, 0xffff038e, 0xffff038f, 0xffff0390, 0xffff0391, + 0xffff0392, 0xffff0393, 0xffff0394, 0xffff0395, 0xffff0396, 0xffff0397, + 0xffff0398, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffff0399, 0xffff039a, 0xffff039b, 0xffff039c, + 0xffff039d, 0xffff039e, 0xffff039f, 0xffffffff, 0xffff03a0, 0xffff03a1, + 0xffff03a2, 0xffff03a3, 0xffff03a4, 0xffff03a5, 0xffff03a6, 0xffff03a7, + 0xffff03a8, 0xffff03a9, 0xffff03aa, 0xffff03ab, 0xffff03ac, 0xffff03ad, + 0xffff03ae, 0xffff03af, 0xffff03b0, 0xffff03b1, 0xffff03b2, 0xffff03b3, + 0xffff03b4, 0xffff03b5, 0xffff03b6, 0xffff03b7, 0xffff03b8, 0xffff03b9, + 0xffff03ba, 0xffff03bb, 0xffff03bc, 0xffff03bd, 0xffff03be, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x3bfffff, 0x3c0ffff, 0x3c1ffff, + 0xffff03c2, 0xffff03c3, 0xffff03c4, 0xffff03c5, 0xffff03c6, 0xffffffff, + 0x3c7ffff, 0x3c8ffff, 0xffffffff, 0xffff03c9, 0xffff03ca, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff03cb, + 0xffff03cc, 0xffff03cd, 0xffff03ce, 0xffff03cf, 0xffff03d0, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x4170416, 0x4190418, 0x41b041a, + 0xffff041c, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x41effff, 0x420041f, 0x4220421, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x3d1ffff, 0x3d303d2, 0x3d503d4, + 0x3d703d6, 0x3d903d8, 0x3db03da, 0x3dd03dc, 0x3df03de, 0x3e103e0, + 0x3e303e2, 0x3e503e4, 0x3e703e6, 0x3e903e8, 0xffff03ea, 0xffffffff, + 0xffffffff, 0x3ec03eb, 0x3ee03ed, 0x3f003ef, 0x3f203f1, 0x3f403f3, + 0x3f603f5, 0x3f803f7, 0x3fa03f9, 0x3fc03fb, 0x3fe03fd, 0x40003ff, + 0x4020401, 0x4040403, 0x4060405, 0x4080407, 0x40a0409, 0x40c040b, + 0x40e040d, 0x410040f, 0x4120411, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff]); +//8192 bytes +enum toTitleIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([0x0, 0x40, 0x200], + [0x100, 0x380, 0xc00], [0x2020100, 0x4020302, 0x2020205, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x10000, 0x30002, 0x50004, 0x70006, 0x90008, 0xa, 0xb0000, 0xd000c, + 0xf000e, 0x110010, 0x130012, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x150000, 0x0, + 0x170016, 0x190018, 0x1b001a, 0x1d001c, 0x0, 0x0, 0x1e0000, 0x1f, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x200000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x220021, 0x240023, 0x25, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x260000, + 0x27, 0x290028, 0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2c0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2e002d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff, 0x20001, + 0x40003, 0x60005, 0x80007, 0xa0009, 0xc000b, 0xe000d, 0x10000f, + 0x120011, 0x140013, 0x160015, 0x180017, 0xffff0019, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x1affff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x41fffff, 0x1c001b, 0x1e001d, 0x20001f, 0x220021, 0x240023, 0x260025, + 0x280027, 0x2a0029, 0x2c002b, 0x2e002d, 0x30002f, 0xffff0031, 0x330032, + 0x350034, 0x370036, 0x390038, 0x3affff, 0x3bffff, 0x3cffff, 0x3dffff, + 0x3effff, 0x3fffff, 0x40ffff, 0x41ffff, 0x42ffff, 0x43ffff, 0x44ffff, + 0x45ffff, 0x46ffff, 0x47ffff, 0x48ffff, 0x49ffff, 0x4affff, 0x4bffff, + 0x4cffff, 0x4dffff, 0x4effff, 0x4fffff, 0x50ffff, 0x51ffff, 0x520421, + 0x53ffff, 0x54ffff, 0x55ffff, 0xffffffff, 0xffff0056, 0xffff0057, + 0xffff0058, 0xffff0059, 0xffff005a, 0xffff005b, 0xffff005c, 0x43e005d, + 0x5effff, 0x5fffff, 0x60ffff, 0x61ffff, 0x62ffff, 0x63ffff, 0x64ffff, + 0x65ffff, 0x66ffff, 0x67ffff, 0x68ffff, 0x69ffff, 0x6affff, 0x6bffff, + 0x6cffff, 0x6dffff, 0x6effff, 0x6fffff, 0x70ffff, 0x71ffff, 0x72ffff, + 0x73ffff, 0x74ffff, 0xffffffff, 0xffff0075, 0xffff0076, 0x780077, + 0xffff0079, 0x7affff, 0x7bffff, 0xffffffff, 0xffff007c, 0xffffffff, + 0xffff007d, 0xffffffff, 0xffffffff, 0xffff007e, 0x7fffff, 0xffffffff, + 0x80ffff, 0xffff0081, 0xffffffff, 0xffff0082, 0x83ffff, 0x84ffff, + 0x85ffff, 0xffffffff, 0xffff0086, 0xffffffff, 0x87ffff, 0xffffffff, + 0xffff0088, 0xffffffff, 0xffff0089, 0xffff008a, 0x8bffff, 0xffffffff, + 0x8cffff, 0x8dffff, 0xffffffff, 0xffffffff, 0x8f008e, 0x910090, + 0x930092, 0x950094, 0xffff0096, 0xffff0097, 0xffff0098, 0xffff0099, + 0xffff009a, 0xffff009b, 0xffff009c, 0xffff009d, 0x9f009e, 0xa0ffff, + 0xa1ffff, 0xa2ffff, 0xa3ffff, 0xa4ffff, 0xa5ffff, 0xa6ffff, 0xa7ffff, + 0xa8ffff, 0xa90446, 0xab00aa, 0xacffff, 0xffffffff, 0xadffff, 0xaeffff, + 0xafffff, 0xb0ffff, 0xb1ffff, 0xb2ffff, 0xb3ffff, 0xb4ffff, 0xb5ffff, + 0xb6ffff, 0xb7ffff, 0xb8ffff, 0xb9ffff, 0xbaffff, 0xbbffff, 0xbcffff, + 0xbdffff, 0xbeffff, 0xbfffff, 0xc0ffff, 0xffffffff, 0xc1ffff, 0xc2ffff, + 0xc3ffff, 0xc4ffff, 0xc5ffff, 0xc6ffff, 0xc7ffff, 0xc8ffff, 0xc9ffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff00ca, 0xcbffff, + 0xffff00cc, 0xffff00cd, 0xffffffff, 0xceffff, 0xcfffff, 0xd0ffff, + 0xd1ffff, 0xd2ffff, 0xd400d3, 0xd600d5, 0xffff00d7, 0xd900d8, 0xdaffff, + 0xdbffff, 0xffffffff, 0xffffffff, 0xffff00dc, 0xddffff, 0xdeffff, + 0xffff00df, 0xe100e0, 0xe2ffff, 0xffffffff, 0xe3ffff, 0xe4ffff, + 0xffff00e5, 0xe6ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xe7ffff, + 0xffffffff, 0xffff00e8, 0xe9ffff, 0xffffffff, 0xffffffff, 0xeb00ea, + 0xed00ec, 0xffff00ee, 0xffffffff, 0xffffffff, 0xffff00ef, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xf0ffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xf1ffff, 0xf2ffff, 0xffffffff, + 0xf3ffff, 0xffffffff, 0xf4ffff, 0xf600f5, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffff0440, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xf800f7, 0xfa00f9, 0xfb0443, + 0xfd00fc, 0xff00fe, 0x1010100, 0x1030102, 0x1050104, 0x1070106, + 0x1090108, 0x10b010a, 0x10d010c, 0x10f010e, 0x1110110, 0x1130112, + 0x1150114, 0x1170116, 0xffff0118, 0x11a0119, 0xffffffff, 0x11bffff, + 0x11d011c, 0x11effff, 0x11fffff, 0x120ffff, 0x121ffff, 0x122ffff, + 0x123ffff, 0x124ffff, 0x125ffff, 0x126ffff, 0x127ffff, 0x128ffff, + 0x129ffff, 0x12b012a, 0xffff012c, 0x12dffff, 0xffffffff, 0xffff012e, + 0x12fffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x1310130, 0x1330132, 0x1350134, + 0x1370136, 0x1390138, 0x13b013a, 0x13d013c, 0x13f013e, 0x1410140, + 0x1430142, 0x1450144, 0x1470146, 0x1490148, 0x14b014a, 0x14d014c, + 0x14f014e, 0x1510150, 0x1530152, 0x1550154, 0x1570156, 0x1590158, + 0x15b015a, 0x15d015c, 0x15f015e, 0x160ffff, 0x161ffff, 0x162ffff, + 0x163ffff, 0x164ffff, 0x165ffff, 0x166ffff, 0x167ffff, 0x168ffff, + 0x169ffff, 0x16affff, 0x16bffff, 0x16cffff, 0x16dffff, 0x16effff, + 0x16fffff, 0x170ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x171ffff, 0x172ffff, 0x173ffff, 0x174ffff, 0x175ffff, 0x176ffff, + 0x177ffff, 0x178ffff, 0x179ffff, 0x17affff, 0x17bffff, 0x17cffff, + 0x17dffff, 0x17effff, 0x17fffff, 0x180ffff, 0x181ffff, 0x182ffff, + 0x183ffff, 0x184ffff, 0x185ffff, 0x186ffff, 0x187ffff, 0x188ffff, + 0x189ffff, 0x18affff, 0x18bffff, 0xffffffff, 0xffff018c, 0xffff018d, + 0xffff018e, 0xffff018f, 0xffff0190, 0xffff0191, 0x1930192, 0x194ffff, + 0x195ffff, 0x196ffff, 0x197ffff, 0x198ffff, 0x199ffff, 0x19affff, + 0x19bffff, 0x19cffff, 0x19dffff, 0x19effff, 0x19fffff, 0x1a0ffff, + 0x1a1ffff, 0x1a2ffff, 0x1a3ffff, 0x1a4ffff, 0x1a5ffff, 0x1a6ffff, + 0x1a7ffff, 0x1a8ffff, 0x1a9ffff, 0x1aaffff, 0x1abffff, 0x1acffff, + 0x1adffff, 0x1aeffff, 0x1afffff, 0x1b0ffff, 0x1b1ffff, 0x1b2ffff, + 0x1b3ffff, 0x1b4ffff, 0x1b5ffff, 0x1b6ffff, 0x1b7ffff, 0x1b8ffff, + 0x1b9ffff, 0x1baffff, 0x1bbffff, 0x1bcffff, 0x1bdffff, 0x1beffff, + 0x1bfffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x1c0ffff, + 0x1c201c1, 0x1c401c3, 0x1c601c5, 0x1c801c7, 0x1ca01c9, 0x1cc01cb, + 0x1ce01cd, 0x1d001cf, 0x1d201d1, 0x1d401d3, 0x1d601d5, 0x1d801d7, + 0x1da01d9, 0x1dc01db, 0x1de01dd, 0x1e001df, 0x1e201e1, 0x1e401e3, + 0x43201e5, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x1e6ffff, 0xffffffff, 0x1e7ffff, + 0xffffffff, 0x1e8ffff, 0x1e9ffff, 0x1eaffff, 0x1ebffff, 0x1ecffff, + 0x1edffff, 0x1eeffff, 0x1efffff, 0x1f0ffff, 0x1f1ffff, 0x1f2ffff, + 0x1f3ffff, 0x1f4ffff, 0x1f5ffff, 0x1f6ffff, 0x1f7ffff, 0x1f8ffff, + 0x1f9ffff, 0x1faffff, 0x1fbffff, 0x1fcffff, 0x1fdffff, 0x1feffff, + 0x1ffffff, 0x200ffff, 0x201ffff, 0x202ffff, 0x203ffff, 0x204ffff, + 0x205ffff, 0x206ffff, 0x207ffff, 0x208ffff, 0x209ffff, 0x20affff, + 0x20bffff, 0x20cffff, 0x20dffff, 0x20effff, 0x20fffff, 0x210ffff, + 0x211ffff, 0x212ffff, 0x213ffff, 0x214ffff, 0x215ffff, 0x216ffff, + 0x217ffff, 0x218ffff, 0x219ffff, 0x21affff, 0x21bffff, 0x21cffff, + 0x21dffff, 0x21effff, 0x21fffff, 0x220ffff, 0x221ffff, 0x222ffff, + 0x223ffff, 0x224ffff, 0x225ffff, 0x226ffff, 0x227ffff, 0x228ffff, + 0x229ffff, 0x22affff, 0x22bffff, 0x22cffff, 0x22dffff, 0x22effff, + 0x22fffff, 0x230ffff, 0x231ffff, 0x232ffff, 0x44a0448, 0x44e044c, + 0x2330450, 0xffffffff, 0xffffffff, 0x234ffff, 0x235ffff, 0x236ffff, + 0x237ffff, 0x238ffff, 0x239ffff, 0x23affff, 0x23bffff, 0x23cffff, + 0x23dffff, 0x23effff, 0x23fffff, 0x240ffff, 0x241ffff, 0x242ffff, + 0x243ffff, 0x244ffff, 0x245ffff, 0x246ffff, 0x247ffff, 0x248ffff, + 0x249ffff, 0x24affff, 0x24bffff, 0x24cffff, 0x24dffff, 0x24effff, + 0x24fffff, 0x250ffff, 0x251ffff, 0x252ffff, 0x253ffff, 0x254ffff, + 0x255ffff, 0x256ffff, 0x257ffff, 0x258ffff, 0x259ffff, 0x25affff, + 0x25bffff, 0x25cffff, 0x25dffff, 0x25effff, 0x25fffff, 0x260ffff, + 0x261ffff, 0x262ffff, 0x263ffff, 0x2650264, 0x2670266, 0x2690268, + 0x26b026a, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x26d026c, + 0x26f026e, 0x2710270, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2730272, 0x2750274, 0x2770276, 0x2790278, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x27b027a, 0x27d027c, 0x27f027e, + 0x2810280, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x2830282, + 0x2850284, 0x2870286, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2880452, 0x2890454, 0x28a0457, 0x28b045a, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x28d028c, 0x28f028e, 0x2910290, + 0x2930292, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x2950294, + 0x2970296, 0x2990298, 0x29b029a, 0x29d029c, 0x29f029e, 0x2a102a0, + 0xffffffff, 0x47c047b, 0x47e047d, 0x480047f, 0x4820481, 0x4840483, + 0x4860485, 0x4880487, 0x48a0489, 0x48c048b, 0x48e048d, 0x490048f, + 0x4920491, 0x4940493, 0x4960495, 0x4980497, 0x49a0499, 0x49c049b, + 0x49e049d, 0x4a0049f, 0x4a204a1, 0x4a404a3, 0x4a604a5, 0x4a804a7, + 0x4aa04a9, 0x2bb02ba, 0x4ab04b1, 0xffff04b3, 0x4bd045d, 0xffffffff, + 0xffffffff, 0xffff04ac, 0xffff02bd, 0xffffffff, 0x4ad04b5, 0xffff04b7, + 0x4c0045f, 0xffffffff, 0xffffffff, 0xffff04ae, 0xffffffff, 0x2c002bf, + 0x4640461, 0xffffffff, 0x4690467, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2c202c1, 0x46f046c, 0x2c30472, 0x4760474, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x4af04b9, 0xffff04bb, + 0x4c30479, 0xffffffff, 0xffffffff, 0xffff04b0, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffff02c5, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x2c702c6, + 0x2c902c8, 0x2cb02ca, 0x2cd02cc, 0x2cf02ce, 0x2d102d0, 0x2d302d2, + 0x2d502d4, 0xffffffff, 0xffffffff, 0xffff02d6, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x2d802d7, + 0x2da02d9, 0x2dc02db, 0x2de02dd, 0x2e002df, 0x2e202e1, 0x2e402e3, + 0x2e602e5, 0x2e802e7, 0x2ea02e9, 0x2ec02eb, 0x2ee02ed, 0x2f002ef, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x2f202f1, + 0x2f402f3, 0x2f602f5, 0x2f802f7, 0x2fa02f9, 0x2fc02fb, 0x2fe02fd, + 0x30002ff, 0x3020301, 0x3040303, 0x3060305, 0x3080307, 0x30a0309, + 0x30c030b, 0x30e030d, 0x310030f, 0x3120311, 0x3140313, 0x3160315, + 0x3180317, 0x31a0319, 0x31c031b, 0x31e031d, 0xffff031f, 0x320ffff, + 0xffffffff, 0x321ffff, 0xffff0322, 0xffff0323, 0xffff0324, 0xffff0325, + 0xffffffff, 0xffffffff, 0x326ffff, 0xffffffff, 0xffff0327, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x328ffff, 0x329ffff, 0x32affff, + 0x32bffff, 0x32cffff, 0x32dffff, 0x32effff, 0x32fffff, 0x330ffff, + 0x331ffff, 0x332ffff, 0x333ffff, 0x334ffff, 0x335ffff, 0x336ffff, + 0x337ffff, 0x338ffff, 0x339ffff, 0x33affff, 0x33bffff, 0x33cffff, + 0x33dffff, 0x33effff, 0x33fffff, 0x340ffff, 0x341ffff, 0x342ffff, + 0x343ffff, 0x344ffff, 0x345ffff, 0x346ffff, 0x347ffff, 0x348ffff, + 0x349ffff, 0x34affff, 0x34bffff, 0x34cffff, 0x34dffff, 0x34effff, + 0x34fffff, 0x350ffff, 0x351ffff, 0x352ffff, 0x353ffff, 0x354ffff, + 0x355ffff, 0x356ffff, 0x357ffff, 0x358ffff, 0x359ffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff035a, 0xffff035b, 0xffffffff, + 0x35cffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x35e035d, 0x360035f, 0x3620361, 0x3640363, 0x3660365, + 0x3680367, 0x36a0369, 0x36c036b, 0x36e036d, 0x370036f, 0x3720371, + 0x3740373, 0x3760375, 0x3780377, 0x37a0379, 0x37c037b, 0x37e037d, + 0x380037f, 0x3820381, 0x383ffff, 0xffffffff, 0xffffffff, 0x384ffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x385ffff, 0x386ffff, 0x387ffff, + 0x388ffff, 0x389ffff, 0x38affff, 0x38bffff, 0x38cffff, 0x38dffff, + 0x38effff, 0x38fffff, 0x390ffff, 0x391ffff, 0x392ffff, 0x393ffff, + 0x394ffff, 0x395ffff, 0x396ffff, 0x397ffff, 0x398ffff, 0x399ffff, + 0x39affff, 0x39bffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x39cffff, + 0x39dffff, 0x39effff, 0x39fffff, 0x3a0ffff, 0x3a1ffff, 0x3a2ffff, + 0x3a3ffff, 0x3a4ffff, 0x3a5ffff, 0x3a6ffff, 0x3a7ffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x3a8ffff, 0x3a9ffff, 0x3aaffff, 0x3abffff, 0x3acffff, 0x3adffff, + 0x3aeffff, 0xffffffff, 0x3afffff, 0x3b0ffff, 0x3b1ffff, 0x3b2ffff, + 0x3b3ffff, 0x3b4ffff, 0x3b5ffff, 0x3b6ffff, 0x3b7ffff, 0x3b8ffff, + 0x3b9ffff, 0x3baffff, 0x3bbffff, 0x3bcffff, 0x3bdffff, 0x3beffff, + 0x3bfffff, 0x3c0ffff, 0x3c1ffff, 0x3c2ffff, 0x3c3ffff, 0x3c4ffff, + 0x3c5ffff, 0x3c6ffff, 0x3c7ffff, 0x3c8ffff, 0x3c9ffff, 0x3caffff, + 0x3cbffff, 0x3ccffff, 0x3cdffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffff03ce, 0xffff03cf, 0x3d0ffff, 0x3d1ffff, + 0x3d2ffff, 0x3d3ffff, 0x3d4ffff, 0xffffffff, 0xffffffff, 0xffff03d5, + 0xffffffff, 0x3d6ffff, 0x3d7ffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x3d8ffff, 0x3d9ffff, 0x3daffff, + 0x3dbffff, 0x3dcffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x4240422, 0x4280426, 0x42e042b, 0xffff0430, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x434ffff, 0x4380436, + 0x43c043a, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x3ddffff, 0x3df03de, 0x3e103e0, + 0x3e303e2, 0x3e503e4, 0x3e703e6, 0x3e903e8, 0x3eb03ea, 0x3ed03ec, + 0x3ef03ee, 0x3f103f0, 0x3f303f2, 0x3f503f4, 0xffff03f6, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x3f803f7, 0x3fa03f9, 0x3fc03fb, 0x3fe03fd, 0x40003ff, + 0x4020401, 0x4040403, 0x4060405, 0x4080407, 0x40a0409, 0x40c040b, + 0x40e040d, 0x410040f, 0x4120411, 0x4140413, 0x4160415, 0x4180417, + 0x41a0419, 0x41c041b, 0x41e041d, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff]); +//8064 bytes +enum toUpperSimpleIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([0x0, 0x40, + 0x200], [0x100, 0x380, 0xbc0], [0x2020100, 0x4020302, 0x2020205, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x10000, 0x30002, 0x50004, 0x70006, 0x90008, 0xa, 0xb0000, + 0xd000c, 0xf000e, 0x110010, 0x130012, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x150000, 0x0, 0x170016, 0x190018, 0x1b001a, 0x1d001c, 0x0, 0x0, + 0x1e0000, 0x1f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x200000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x220021, 0x240023, + 0x25, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x260000, 0x27, 0x290028, 0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2b0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2d002c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff, + 0x20001, 0x40003, 0x60005, 0x80007, 0xa0009, 0xc000b, 0xe000d, + 0x10000f, 0x120011, 0x140013, 0x160015, 0x180017, 0xffff0019, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x1affff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x1c001b, 0x1e001d, 0x20001f, 0x220021, + 0x240023, 0x260025, 0x280027, 0x2a0029, 0x2c002b, 0x2e002d, 0x30002f, + 0xffff0031, 0x330032, 0x350034, 0x370036, 0x390038, 0x3affff, 0x3bffff, + 0x3cffff, 0x3dffff, 0x3effff, 0x3fffff, 0x40ffff, 0x41ffff, 0x42ffff, + 0x43ffff, 0x44ffff, 0x45ffff, 0x46ffff, 0x47ffff, 0x48ffff, 0x49ffff, + 0x4affff, 0x4bffff, 0x4cffff, 0x4dffff, 0x4effff, 0x4fffff, 0x50ffff, + 0x51ffff, 0x52ffff, 0x53ffff, 0x54ffff, 0x55ffff, 0xffffffff, + 0xffff0056, 0xffff0057, 0xffff0058, 0xffff0059, 0xffff005a, 0xffff005b, + 0xffff005c, 0xffff005d, 0x5effff, 0x5fffff, 0x60ffff, 0x61ffff, + 0x62ffff, 0x63ffff, 0x64ffff, 0x65ffff, 0x66ffff, 0x67ffff, 0x68ffff, + 0x69ffff, 0x6affff, 0x6bffff, 0x6cffff, 0x6dffff, 0x6effff, 0x6fffff, + 0x70ffff, 0x71ffff, 0x72ffff, 0x73ffff, 0x74ffff, 0xffffffff, + 0xffff0075, 0xffff0076, 0x780077, 0xffff0079, 0x7affff, 0x7bffff, + 0xffffffff, 0xffff007c, 0xffffffff, 0xffff007d, 0xffffffff, 0xffffffff, + 0xffff007e, 0x7fffff, 0xffffffff, 0x80ffff, 0xffff0081, 0xffffffff, + 0xffff0082, 0x83ffff, 0x84ffff, 0x85ffff, 0xffffffff, 0xffff0086, + 0xffffffff, 0x87ffff, 0xffffffff, 0xffff0088, 0xffffffff, 0xffff0089, + 0xffff008a, 0x8bffff, 0xffffffff, 0x8cffff, 0x8dffff, 0xffffffff, + 0xffffffff, 0x8effff, 0xffff008f, 0x910090, 0x92ffff, 0xffff0093, + 0xffff0094, 0xffff0095, 0xffff0096, 0xffff0097, 0xffff0098, 0xffff0099, + 0xffff009a, 0x9c009b, 0x9dffff, 0x9effff, 0x9fffff, 0xa0ffff, 0xa1ffff, + 0xa2ffff, 0xa3ffff, 0xa4ffff, 0xa5ffff, 0xffffffff, 0xa700a6, 0xa8ffff, + 0xffffffff, 0xa9ffff, 0xaaffff, 0xabffff, 0xacffff, 0xadffff, 0xaeffff, + 0xafffff, 0xb0ffff, 0xb1ffff, 0xb2ffff, 0xb3ffff, 0xb4ffff, 0xb5ffff, + 0xb6ffff, 0xb7ffff, 0xb8ffff, 0xb9ffff, 0xbaffff, 0xbbffff, 0xbcffff, + 0xffffffff, 0xbdffff, 0xbeffff, 0xbfffff, 0xc0ffff, 0xc1ffff, 0xc2ffff, + 0xc3ffff, 0xc4ffff, 0xc5ffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffff00c6, 0xc7ffff, 0xffff00c8, 0xffff00c9, 0xffffffff, + 0xcaffff, 0xcbffff, 0xccffff, 0xcdffff, 0xceffff, 0xd000cf, 0xd200d1, + 0xffff00d3, 0xd500d4, 0xd6ffff, 0xd7ffff, 0xffffffff, 0xffffffff, + 0xffff00d8, 0xd9ffff, 0xdaffff, 0xffff00db, 0xdd00dc, 0xdeffff, + 0xffffffff, 0xdfffff, 0xe0ffff, 0xffff00e1, 0xe2ffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xe3ffff, 0xffffffff, 0xffff00e4, 0xe5ffff, + 0xffffffff, 0xffffffff, 0xe700e6, 0xe900e8, 0xffff00ea, 0xffffffff, + 0xffffffff, 0xffff00eb, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xecffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xedffff, 0xeeffff, 0xffffffff, 0xefffff, 0xffffffff, 0xf0ffff, + 0xf200f1, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xf400f3, 0xf600f5, 0xf7ffff, 0xf900f8, 0xfb00fa, 0xfd00fc, 0xff00fe, + 0x1010100, 0x1030102, 0x1050104, 0x1070106, 0x1090108, 0x10b010a, + 0x10d010c, 0x10f010e, 0x1110110, 0x1130112, 0xffff0114, 0x1160115, + 0xffffffff, 0x117ffff, 0x1190118, 0x11affff, 0x11bffff, 0x11cffff, + 0x11dffff, 0x11effff, 0x11fffff, 0x120ffff, 0x121ffff, 0x122ffff, + 0x123ffff, 0x124ffff, 0x125ffff, 0x1270126, 0xffff0128, 0x129ffff, + 0xffffffff, 0xffff012a, 0x12bffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x12d012c, + 0x12f012e, 0x1310130, 0x1330132, 0x1350134, 0x1370136, 0x1390138, + 0x13b013a, 0x13d013c, 0x13f013e, 0x1410140, 0x1430142, 0x1450144, + 0x1470146, 0x1490148, 0x14b014a, 0x14d014c, 0x14f014e, 0x1510150, + 0x1530152, 0x1550154, 0x1570156, 0x1590158, 0x15b015a, 0x15cffff, + 0x15dffff, 0x15effff, 0x15fffff, 0x160ffff, 0x161ffff, 0x162ffff, + 0x163ffff, 0x164ffff, 0x165ffff, 0x166ffff, 0x167ffff, 0x168ffff, + 0x169ffff, 0x16affff, 0x16bffff, 0x16cffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x16dffff, 0x16effff, 0x16fffff, 0x170ffff, + 0x171ffff, 0x172ffff, 0x173ffff, 0x174ffff, 0x175ffff, 0x176ffff, + 0x177ffff, 0x178ffff, 0x179ffff, 0x17affff, 0x17bffff, 0x17cffff, + 0x17dffff, 0x17effff, 0x17fffff, 0x180ffff, 0x181ffff, 0x182ffff, + 0x183ffff, 0x184ffff, 0x185ffff, 0x186ffff, 0x187ffff, 0xffffffff, + 0xffff0188, 0xffff0189, 0xffff018a, 0xffff018b, 0xffff018c, 0xffff018d, + 0x18f018e, 0x190ffff, 0x191ffff, 0x192ffff, 0x193ffff, 0x194ffff, + 0x195ffff, 0x196ffff, 0x197ffff, 0x198ffff, 0x199ffff, 0x19affff, + 0x19bffff, 0x19cffff, 0x19dffff, 0x19effff, 0x19fffff, 0x1a0ffff, + 0x1a1ffff, 0x1a2ffff, 0x1a3ffff, 0x1a4ffff, 0x1a5ffff, 0x1a6ffff, + 0x1a7ffff, 0x1a8ffff, 0x1a9ffff, 0x1aaffff, 0x1abffff, 0x1acffff, + 0x1adffff, 0x1aeffff, 0x1afffff, 0x1b0ffff, 0x1b1ffff, 0x1b2ffff, + 0x1b3ffff, 0x1b4ffff, 0x1b5ffff, 0x1b6ffff, 0x1b7ffff, 0x1b8ffff, + 0x1b9ffff, 0x1baffff, 0x1bbffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x1bcffff, 0x1be01bd, 0x1c001bf, 0x1c201c1, 0x1c401c3, + 0x1c601c5, 0x1c801c7, 0x1ca01c9, 0x1cc01cb, 0x1ce01cd, 0x1d001cf, + 0x1d201d1, 0x1d401d3, 0x1d601d5, 0x1d801d7, 0x1da01d9, 0x1dc01db, + 0x1de01dd, 0x1e001df, 0xffff01e1, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x1e2ffff, + 0xffffffff, 0x1e3ffff, 0xffffffff, 0x1e4ffff, 0x1e5ffff, 0x1e6ffff, + 0x1e7ffff, 0x1e8ffff, 0x1e9ffff, 0x1eaffff, 0x1ebffff, 0x1ecffff, + 0x1edffff, 0x1eeffff, 0x1efffff, 0x1f0ffff, 0x1f1ffff, 0x1f2ffff, + 0x1f3ffff, 0x1f4ffff, 0x1f5ffff, 0x1f6ffff, 0x1f7ffff, 0x1f8ffff, + 0x1f9ffff, 0x1faffff, 0x1fbffff, 0x1fcffff, 0x1fdffff, 0x1feffff, + 0x1ffffff, 0x200ffff, 0x201ffff, 0x202ffff, 0x203ffff, 0x204ffff, + 0x205ffff, 0x206ffff, 0x207ffff, 0x208ffff, 0x209ffff, 0x20affff, + 0x20bffff, 0x20cffff, 0x20dffff, 0x20effff, 0x20fffff, 0x210ffff, + 0x211ffff, 0x212ffff, 0x213ffff, 0x214ffff, 0x215ffff, 0x216ffff, + 0x217ffff, 0x218ffff, 0x219ffff, 0x21affff, 0x21bffff, 0x21cffff, + 0x21dffff, 0x21effff, 0x21fffff, 0x220ffff, 0x221ffff, 0x222ffff, + 0x223ffff, 0x224ffff, 0x225ffff, 0x226ffff, 0x227ffff, 0x228ffff, + 0x229ffff, 0x22affff, 0x22bffff, 0x22cffff, 0x22dffff, 0x22effff, + 0xffffffff, 0xffffffff, 0x22fffff, 0xffffffff, 0xffffffff, 0x230ffff, + 0x231ffff, 0x232ffff, 0x233ffff, 0x234ffff, 0x235ffff, 0x236ffff, + 0x237ffff, 0x238ffff, 0x239ffff, 0x23affff, 0x23bffff, 0x23cffff, + 0x23dffff, 0x23effff, 0x23fffff, 0x240ffff, 0x241ffff, 0x242ffff, + 0x243ffff, 0x244ffff, 0x245ffff, 0x246ffff, 0x247ffff, 0x248ffff, + 0x249ffff, 0x24affff, 0x24bffff, 0x24cffff, 0x24dffff, 0x24effff, + 0x24fffff, 0x250ffff, 0x251ffff, 0x252ffff, 0x253ffff, 0x254ffff, + 0x255ffff, 0x256ffff, 0x257ffff, 0x258ffff, 0x259ffff, 0x25affff, + 0x25bffff, 0x25cffff, 0x25dffff, 0x25effff, 0x25fffff, 0x2610260, + 0x2630262, 0x2650264, 0x2670266, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2690268, 0x26b026a, 0x26d026c, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x26f026e, 0x2710270, 0x2730272, + 0x2750274, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x2770276, + 0x2790278, 0x27b027a, 0x27d027c, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x27f027e, 0x2810280, 0x2830282, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x284ffff, 0x285ffff, 0x286ffff, + 0x287ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x2890288, + 0x28b028a, 0x28d028c, 0x28f028e, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2910290, 0x2930292, 0x2950294, 0x2970296, 0x2990298, + 0x29b029a, 0x29d029c, 0xffffffff, 0x29f029e, 0x2a102a0, 0x2a302a2, + 0x2a502a4, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x2a702a6, + 0x2a902a8, 0x2ab02aa, 0x2ad02ac, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2af02ae, 0x2b102b0, 0x2b302b2, 0x2b502b4, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x2b702b6, 0x2b8ffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff02b9, 0xffffffff, + 0x2baffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2bc02bb, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x2be02bd, 0xffffffff, 0x2bfffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x2c0ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffff02c1, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2c302c2, 0x2c502c4, 0x2c702c6, 0x2c902c8, 0x2cb02ca, + 0x2cd02cc, 0x2cf02ce, 0x2d102d0, 0xffffffff, 0xffffffff, 0xffff02d2, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2d402d3, 0x2d602d5, 0x2d802d7, 0x2da02d9, 0x2dc02db, + 0x2de02dd, 0x2e002df, 0x2e202e1, 0x2e402e3, 0x2e602e5, 0x2e802e7, + 0x2ea02e9, 0x2ec02eb, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2ee02ed, 0x2f002ef, 0x2f202f1, 0x2f402f3, 0x2f602f5, + 0x2f802f7, 0x2fa02f9, 0x2fc02fb, 0x2fe02fd, 0x30002ff, 0x3020301, + 0x3040303, 0x3060305, 0x3080307, 0x30a0309, 0x30c030b, 0x30e030d, + 0x310030f, 0x3120311, 0x3140313, 0x3160315, 0x3180317, 0x31a0319, + 0xffff031b, 0x31cffff, 0xffffffff, 0x31dffff, 0xffff031e, 0xffff031f, + 0xffff0320, 0xffff0321, 0xffffffff, 0xffffffff, 0x322ffff, 0xffffffff, + 0xffff0323, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x324ffff, + 0x325ffff, 0x326ffff, 0x327ffff, 0x328ffff, 0x329ffff, 0x32affff, + 0x32bffff, 0x32cffff, 0x32dffff, 0x32effff, 0x32fffff, 0x330ffff, + 0x331ffff, 0x332ffff, 0x333ffff, 0x334ffff, 0x335ffff, 0x336ffff, + 0x337ffff, 0x338ffff, 0x339ffff, 0x33affff, 0x33bffff, 0x33cffff, + 0x33dffff, 0x33effff, 0x33fffff, 0x340ffff, 0x341ffff, 0x342ffff, + 0x343ffff, 0x344ffff, 0x345ffff, 0x346ffff, 0x347ffff, 0x348ffff, + 0x349ffff, 0x34affff, 0x34bffff, 0x34cffff, 0x34dffff, 0x34effff, + 0x34fffff, 0x350ffff, 0x351ffff, 0x352ffff, 0x353ffff, 0x354ffff, + 0x355ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0356, + 0xffff0357, 0xffffffff, 0x358ffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x35a0359, 0x35c035b, 0x35e035d, + 0x360035f, 0x3620361, 0x3640363, 0x3660365, 0x3680367, 0x36a0369, + 0x36c036b, 0x36e036d, 0x370036f, 0x3720371, 0x3740373, 0x3760375, + 0x3780377, 0x37a0379, 0x37c037b, 0x37e037d, 0x37fffff, 0xffffffff, + 0xffffffff, 0x380ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x381ffff, + 0x382ffff, 0x383ffff, 0x384ffff, 0x385ffff, 0x386ffff, 0x387ffff, + 0x388ffff, 0x389ffff, 0x38affff, 0x38bffff, 0x38cffff, 0x38dffff, + 0x38effff, 0x38fffff, 0x390ffff, 0x391ffff, 0x392ffff, 0x393ffff, + 0x394ffff, 0x395ffff, 0x396ffff, 0x397ffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x398ffff, 0x399ffff, 0x39affff, 0x39bffff, 0x39cffff, + 0x39dffff, 0x39effff, 0x39fffff, 0x3a0ffff, 0x3a1ffff, 0x3a2ffff, + 0x3a3ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x3a4ffff, 0x3a5ffff, 0x3a6ffff, 0x3a7ffff, + 0x3a8ffff, 0x3a9ffff, 0x3aaffff, 0xffffffff, 0x3abffff, 0x3acffff, + 0x3adffff, 0x3aeffff, 0x3afffff, 0x3b0ffff, 0x3b1ffff, 0x3b2ffff, + 0x3b3ffff, 0x3b4ffff, 0x3b5ffff, 0x3b6ffff, 0x3b7ffff, 0x3b8ffff, + 0x3b9ffff, 0x3baffff, 0x3bbffff, 0x3bcffff, 0x3bdffff, 0x3beffff, + 0x3bfffff, 0x3c0ffff, 0x3c1ffff, 0x3c2ffff, 0x3c3ffff, 0x3c4ffff, + 0x3c5ffff, 0x3c6ffff, 0x3c7ffff, 0x3c8ffff, 0x3c9ffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff03ca, 0xffff03cb, + 0x3ccffff, 0x3cdffff, 0x3ceffff, 0x3cfffff, 0x3d0ffff, 0xffffffff, + 0xffffffff, 0xffff03d1, 0xffffffff, 0x3d2ffff, 0x3d3ffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x3d4ffff, + 0x3d5ffff, 0x3d6ffff, 0x3d7ffff, 0x3d8ffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x3d9ffff, 0x3db03da, 0x3dd03dc, + 0x3df03de, 0x3e103e0, 0x3e303e2, 0x3e503e4, 0x3e703e6, 0x3e903e8, + 0x3eb03ea, 0x3ed03ec, 0x3ef03ee, 0x3f103f0, 0xffff03f2, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x3f403f3, 0x3f603f5, 0x3f803f7, 0x3fa03f9, 0x3fc03fb, + 0x3fe03fd, 0x40003ff, 0x4020401, 0x4040403, 0x4060405, 0x4080407, + 0x40a0409, 0x40c040b, 0x40e040d, 0x410040f, 0x4120411, 0x4140413, + 0x4160415, 0x4180417, 0x41a0419, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff]); +//7808 bytes +enum toLowerSimpleIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([0x0, 0x40, + 0x200], [0x100, 0x380, 0xb40], [0x2020100, 0x4020302, 0x2020205, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x10000, 0x20000, 0x40003, 0x60005, 0x80007, 0x0, 0x90000, + 0xb000a, 0xd000c, 0xf000e, 0x110010, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x130012, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x150014, 0x170016, 0x190018, 0x1b001a, 0x0, 0x0, 0x1d001c, 0x1e, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x20001f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x220021, 0x240023, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x250000, 0x26, 0x280027, 0x29, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x2b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffff, 0x20001, 0x40003, 0x60005, 0x80007, 0xa0009, 0xc000b, 0xe000d, + 0x10000f, 0x120011, 0x140013, 0x160015, 0x180017, 0xffff0019, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x1b001a, 0x1d001c, 0x1f001e, 0x210020, 0x230022, 0x250024, 0x270026, + 0x290028, 0x2b002a, 0x2d002c, 0x2f002e, 0xffff0030, 0x320031, 0x340033, + 0x360035, 0xffff0037, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffff0038, 0xffff0039, 0xffff003a, 0xffff003b, 0xffff003c, 0xffff003d, + 0xffff003e, 0xffff003f, 0xffff0040, 0xffff0041, 0xffff0042, 0xffff0043, + 0xffff0044, 0xffff0045, 0xffff0046, 0xffff0047, 0xffff0048, 0xffff0049, + 0xffff004a, 0xffff004b, 0xffff004c, 0xffff004d, 0xffff004e, 0xffff004f, + 0xffff0050, 0xffff0051, 0xffff0052, 0xffff0053, 0x54ffff, 0x55ffff, + 0x56ffff, 0x57ffff, 0x58ffff, 0x59ffff, 0x5affff, 0x5bffff, 0xffffffff, + 0xffff005c, 0xffff005d, 0xffff005e, 0xffff005f, 0xffff0060, 0xffff0061, + 0xffff0062, 0xffff0063, 0xffff0064, 0xffff0065, 0xffff0066, 0xffff0067, + 0xffff0068, 0xffff0069, 0xffff006a, 0xffff006b, 0xffff006c, 0xffff006d, + 0xffff006e, 0xffff006f, 0xffff0070, 0xffff0071, 0xffff0072, 0x740073, + 0x75ffff, 0x76ffff, 0xffffffff, 0x77ffff, 0xffff0078, 0xffff0079, + 0x7b007a, 0x7cffff, 0x7e007d, 0xffffffff, 0x80007f, 0x820081, 0x83ffff, + 0xffff0084, 0x860085, 0xffff0087, 0xffffffff, 0x890088, 0x8affff, + 0xffff008b, 0xffff008c, 0xffff008d, 0x8f008e, 0x90ffff, 0xffffffff, + 0xffff0091, 0x930092, 0x94ffff, 0x960095, 0x97ffff, 0x98ffff, + 0xffff0099, 0xffffffff, 0xffff009a, 0xffffffff, 0xffffffff, 0xffffffff, + 0x9c009b, 0x9dffff, 0xffff009e, 0xa0009f, 0xa1ffff, 0xa2ffff, 0xa3ffff, + 0xa4ffff, 0xa5ffff, 0xa6ffff, 0xa7ffff, 0xa8ffff, 0xffffffff, + 0xffff00a9, 0xffff00aa, 0xffff00ab, 0xffff00ac, 0xffff00ad, 0xffff00ae, + 0xffff00af, 0xffff00b0, 0xffff00b1, 0xb2ffff, 0xffff00b3, 0xffff00b4, + 0xb600b5, 0xffff00b7, 0xffff00b8, 0xffff00b9, 0xffff00ba, 0xffff00bb, + 0xffff00bc, 0xffff00bd, 0xffff00be, 0xffff00bf, 0xffff00c0, 0xffff00c1, + 0xffff00c2, 0xffff00c3, 0xffff00c4, 0xffff00c5, 0xffff00c6, 0xffff00c7, + 0xffff00c8, 0xffff00c9, 0xffff00ca, 0xffff00cb, 0xffff00cc, 0xffff00cd, + 0xffff00ce, 0xffff00cf, 0xffff00d0, 0xffff00d1, 0xffff00d2, 0xffff00d3, + 0xffff00d4, 0xffffffff, 0xffffffff, 0xffffffff, 0xd600d5, 0xd7ffff, + 0xffff00d8, 0xd9ffff, 0xdaffff, 0xdc00db, 0xffff00dd, 0xffff00de, + 0xffff00df, 0xffff00e0, 0xffff00e1, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff00e2, 0xffff00e3, 0xffffffff, + 0xffff00e4, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffff00e5, 0xe700e6, 0xffff00e8, 0xffff00e9, + 0xeb00ea, 0xecffff, 0xee00ed, 0xf000ef, 0xf200f1, 0xf400f3, 0xf600f5, + 0xf800f7, 0xfa00f9, 0xfc00fb, 0xfdffff, 0xff00fe, 0x1010100, 0x1030102, + 0x1050104, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x106ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0107, + 0xffff0108, 0xffff0109, 0xffff010a, 0xffff010b, 0xffff010c, 0xffff010d, + 0xffff010e, 0xffff010f, 0xffff0110, 0xffff0111, 0xffff0112, 0xffffffff, + 0xffffffff, 0xffff0113, 0x114ffff, 0x115ffff, 0xffff0116, 0x117ffff, + 0x1190118, 0x11b011a, 0x11d011c, 0x11f011e, 0x1210120, 0x1230122, + 0x1250124, 0x1270126, 0x1290128, 0x12b012a, 0x12d012c, 0x12f012e, + 0x1310130, 0x1330132, 0x1350134, 0x1370136, 0x1390138, 0x13b013a, + 0x13d013c, 0x13f013e, 0x1410140, 0x1430142, 0x1450144, 0x1470146, + 0x1490148, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffff014a, 0xffff014b, 0xffff014c, 0xffff014d, 0xffff014e, + 0xffff014f, 0xffff0150, 0xffff0151, 0xffff0152, 0xffff0153, 0xffff0154, + 0xffff0155, 0xffff0156, 0xffff0157, 0xffff0158, 0xffff0159, 0xffff015a, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff015b, 0xffff015c, + 0xffff015d, 0xffff015e, 0xffff015f, 0xffff0160, 0xffff0161, 0xffff0162, + 0xffff0163, 0xffff0164, 0xffff0165, 0xffff0166, 0xffff0167, 0xffff0168, + 0xffff0169, 0xffff016a, 0xffff016b, 0xffff016c, 0xffff016d, 0xffff016e, + 0xffff016f, 0xffff0170, 0xffff0171, 0xffff0172, 0xffff0173, 0xffff0174, + 0xffff0175, 0x1770176, 0x178ffff, 0x179ffff, 0x17affff, 0x17bffff, + 0x17cffff, 0x17dffff, 0xffffffff, 0xffff017e, 0xffff017f, 0xffff0180, + 0xffff0181, 0xffff0182, 0xffff0183, 0xffff0184, 0xffff0185, 0xffff0186, + 0xffff0187, 0xffff0188, 0xffff0189, 0xffff018a, 0xffff018b, 0xffff018c, + 0xffff018d, 0xffff018e, 0xffff018f, 0xffff0190, 0xffff0191, 0xffff0192, + 0xffff0193, 0xffff0194, 0xffff0195, 0xffff0196, 0xffff0197, 0xffff0198, + 0xffff0199, 0xffff019a, 0xffff019b, 0xffff019c, 0xffff019d, 0xffff019e, + 0xffff019f, 0xffff01a0, 0xffff01a1, 0xffff01a2, 0xffff01a3, 0xffff01a4, + 0xffff01a5, 0xffff01a6, 0xffff01a7, 0xffff01a8, 0xffff01a9, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x1aaffff, 0x1ac01ab, 0x1ae01ad, + 0x1b001af, 0x1b201b1, 0x1b401b3, 0x1b601b5, 0x1b801b7, 0x1ba01b9, + 0x1bc01bb, 0x1be01bd, 0x1c001bf, 0x1c201c1, 0x1c401c3, 0x1c601c5, + 0x1c801c7, 0x1ca01c9, 0x1cc01cb, 0x1ce01cd, 0xffff01cf, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x1d101d0, + 0x1d301d2, 0x1d501d4, 0x1d701d6, 0x1d901d8, 0x1db01da, 0x1dd01dc, + 0x1df01de, 0x1e101e0, 0x1e301e2, 0x1e501e4, 0x1e701e6, 0x1e901e8, + 0x1eb01ea, 0x1ed01ec, 0x1ef01ee, 0x1f101f0, 0x1f301f2, 0x1f501f4, + 0x1f6ffff, 0xffffffff, 0xffffffff, 0x1f7ffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff01f8, + 0xffff01f9, 0xffff01fa, 0xffff01fb, 0xffff01fc, 0xffff01fd, 0xffff01fe, + 0xffff01ff, 0xffff0200, 0xffff0201, 0xffff0202, 0xffff0203, 0xffff0204, + 0xffff0205, 0xffff0206, 0xffff0207, 0xffff0208, 0xffff0209, 0xffff020a, + 0xffff020b, 0xffff020c, 0xffff020d, 0xffff020e, 0xffff020f, 0xffff0210, + 0xffff0211, 0xffff0212, 0xffff0213, 0xffff0214, 0xffff0215, 0xffff0216, + 0xffff0217, 0xffff0218, 0xffff0219, 0xffff021a, 0xffff021b, 0xffff021c, + 0xffff021d, 0xffff021e, 0xffff021f, 0xffff0220, 0xffff0221, 0xffff0222, + 0xffff0223, 0xffff0224, 0xffff0225, 0xffff0226, 0xffff0227, 0xffff0228, + 0xffff0229, 0xffff022a, 0xffff022b, 0xffff022c, 0xffff022d, 0xffff022e, + 0xffff022f, 0xffff0230, 0xffff0231, 0xffff0232, 0xffff0233, 0xffff0234, + 0xffff0235, 0xffff0236, 0xffff0237, 0xffff0238, 0xffff0239, 0xffff023a, + 0xffff023b, 0xffff023c, 0xffff023d, 0xffff023e, 0xffff023f, 0xffff0240, + 0xffff0241, 0xffff0242, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffff0243, 0xffff0244, 0xffff0245, 0xffff0246, 0xffff0247, 0xffff0248, + 0xffff0249, 0xffff024a, 0xffff024b, 0xffff024c, 0xffff024d, 0xffff024e, + 0xffff024f, 0xffff0250, 0xffff0251, 0xffff0252, 0xffff0253, 0xffff0254, + 0xffff0255, 0xffff0256, 0xffff0257, 0xffff0258, 0xffff0259, 0xffff025a, + 0xffff025b, 0xffff025c, 0xffff025d, 0xffff025e, 0xffff025f, 0xffff0260, + 0xffff0261, 0xffff0262, 0xffff0263, 0xffff0264, 0xffff0265, 0xffff0266, + 0xffff0267, 0xffff0268, 0xffff0269, 0xffff026a, 0xffff026b, 0xffff026c, + 0xffff026d, 0xffff026e, 0xffff026f, 0xffff0270, 0xffff0271, 0xffff0272, + 0xffff0273, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x2750274, + 0x2770276, 0x2790278, 0x27b027a, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x27d027c, 0x27f027e, 0x2810280, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x2830282, 0x2850284, 0x2870286, + 0x2890288, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x28b028a, + 0x28d028c, 0x28f028e, 0x2910290, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2930292, 0x2950294, 0x2970296, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x298ffff, 0x299ffff, 0x29affff, + 0x29bffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x29d029c, + 0x29f029e, 0x2a102a0, 0x2a302a2, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x2a502a4, 0x2a702a6, 0x2a902a8, + 0x2ab02aa, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x2ad02ac, + 0x2af02ae, 0x2b102b0, 0x2b302b2, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2b502b4, 0x2b702b6, 0x2b902b8, 0x2bb02ba, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x2bd02bc, 0x2bf02be, 0xffff02c0, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x2c202c1, + 0x2c402c3, 0xffff02c5, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2c702c6, 0x2c902c8, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x2cb02ca, 0x2cd02cc, 0xffff02ce, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x2d002cf, + 0x2d202d1, 0xffff02d3, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff02d4, 0xffffffff, + 0x2d602d5, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff02d7, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x2d902d8, 0x2db02da, 0x2dd02dc, + 0x2df02de, 0x2e102e0, 0x2e302e2, 0x2e502e4, 0x2e702e6, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x2e8ffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x2ea02e9, 0x2ec02eb, 0x2ee02ed, 0x2f002ef, 0x2f202f1, 0x2f402f3, + 0x2f602f5, 0x2f802f7, 0x2fa02f9, 0x2fc02fb, 0x2fe02fd, 0x30002ff, + 0x3020301, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x3040303, 0x3060305, 0x3080307, 0x30a0309, 0x30c030b, + 0x30e030d, 0x310030f, 0x3120311, 0x3140313, 0x3160315, 0x3180317, + 0x31a0319, 0x31c031b, 0x31e031d, 0x320031f, 0x3220321, 0x3240323, + 0x3260325, 0x3280327, 0x32a0329, 0x32c032b, 0x32e032d, 0x330032f, + 0xffff0331, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffff0332, 0x3340333, 0xffff0335, 0x336ffff, 0x337ffff, + 0x338ffff, 0x339ffff, 0x33b033a, 0xffff033c, 0xffff033d, 0x33effff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x340033f, 0xffff0341, + 0xffff0342, 0xffff0343, 0xffff0344, 0xffff0345, 0xffff0346, 0xffff0347, + 0xffff0348, 0xffff0349, 0xffff034a, 0xffff034b, 0xffff034c, 0xffff034d, + 0xffff034e, 0xffff034f, 0xffff0350, 0xffff0351, 0xffff0352, 0xffff0353, + 0xffff0354, 0xffff0355, 0xffff0356, 0xffff0357, 0xffff0358, 0xffff0359, + 0xffff035a, 0xffff035b, 0xffff035c, 0xffff035d, 0xffff035e, 0xffff035f, + 0xffff0360, 0xffff0361, 0xffff0362, 0xffff0363, 0xffff0364, 0xffff0365, + 0xffff0366, 0xffff0367, 0xffff0368, 0xffff0369, 0xffff036a, 0xffff036b, + 0xffff036c, 0xffff036d, 0xffff036e, 0xffff036f, 0xffff0370, 0xffff0371, + 0xffff0372, 0xffffffff, 0xffffffff, 0xffffffff, 0x373ffff, 0x374ffff, + 0xffffffff, 0xffffffff, 0xffff0375, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff0376, 0xffff0377, 0xffff0378, + 0xffff0379, 0xffff037a, 0xffff037b, 0xffff037c, 0xffff037d, 0xffff037e, + 0xffff037f, 0xffff0380, 0xffff0381, 0xffff0382, 0xffff0383, 0xffff0384, + 0xffff0385, 0xffff0386, 0xffff0387, 0xffff0388, 0xffff0389, 0xffff038a, + 0xffff038b, 0xffff038c, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff038d, + 0xffff038e, 0xffff038f, 0xffff0390, 0xffff0391, 0xffff0392, 0xffff0393, + 0xffff0394, 0xffff0395, 0xffff0396, 0xffff0397, 0xffff0398, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffff0399, 0xffff039a, 0xffff039b, 0xffff039c, 0xffff039d, 0xffff039e, + 0xffff039f, 0xffffffff, 0xffff03a0, 0xffff03a1, 0xffff03a2, 0xffff03a3, + 0xffff03a4, 0xffff03a5, 0xffff03a6, 0xffff03a7, 0xffff03a8, 0xffff03a9, + 0xffff03aa, 0xffff03ab, 0xffff03ac, 0xffff03ad, 0xffff03ae, 0xffff03af, + 0xffff03b0, 0xffff03b1, 0xffff03b2, 0xffff03b3, 0xffff03b4, 0xffff03b5, + 0xffff03b6, 0xffff03b7, 0xffff03b8, 0xffff03b9, 0xffff03ba, 0xffff03bb, + 0xffff03bc, 0xffff03bd, 0xffff03be, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x3bfffff, 0x3c0ffff, 0x3c1ffff, 0xffff03c2, 0xffff03c3, + 0xffff03c4, 0xffff03c5, 0xffff03c6, 0xffffffff, 0x3c7ffff, 0x3c8ffff, + 0xffffffff, 0xffff03c9, 0xffff03ca, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffff03cb, 0xffff03cc, 0xffff03cd, + 0xffff03ce, 0xffff03cf, 0xffff03d0, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x3d1ffff, + 0x3d303d2, 0x3d503d4, 0x3d703d6, 0x3d903d8, 0x3db03da, 0x3dd03dc, + 0x3df03de, 0x3e103e0, 0x3e303e2, 0x3e503e4, 0x3e703e6, 0x3e903e8, + 0xffff03ea, 0xffffffff, 0xffffffff, 0x3ec03eb, 0x3ee03ed, 0x3f003ef, + 0x3f203f1, 0x3f403f3, 0x3f603f5, 0x3f803f7, 0x3fa03f9, 0x3fc03fb, + 0x3fe03fd, 0x40003ff, 0x4020401, 0x4040403, 0x4060405, 0x4080407, + 0x40a0409, 0x40c040b, 0x40e040d, 0x410040f, 0x4120411, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff]); +//8064 bytes +enum toTitleSimpleIndexTrieEntries = TrieEntry!(ushort, 8, 7, 6)([0x0, 0x40, + 0x200], [0x100, 0x380, 0xbc0], [0x2020100, 0x4020302, 0x2020205, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, 0x2020202, + 0x2020202, 0x10000, 0x30002, 0x50004, 0x70006, 0x90008, 0xa, 0xb0000, + 0xd000c, 0xf000e, 0x110010, 0x130012, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x150000, 0x0, 0x170016, 0x190018, 0x1b001a, 0x1d001c, 0x0, 0x0, + 0x1e0000, 0x1f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x200000, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x220021, 0x240023, + 0x25, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x260000, 0x27, 0x290028, 0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2b0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2d002c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff, + 0x20001, 0x40003, 0x60005, 0x80007, 0xa0009, 0xc000b, 0xe000d, + 0x10000f, 0x120011, 0x140013, 0x160015, 0x180017, 0xffff0019, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x1affff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x1c001b, 0x1e001d, 0x20001f, 0x220021, + 0x240023, 0x260025, 0x280027, 0x2a0029, 0x2c002b, 0x2e002d, 0x30002f, + 0xffff0031, 0x330032, 0x350034, 0x370036, 0x390038, 0x3affff, 0x3bffff, + 0x3cffff, 0x3dffff, 0x3effff, 0x3fffff, 0x40ffff, 0x41ffff, 0x42ffff, + 0x43ffff, 0x44ffff, 0x45ffff, 0x46ffff, 0x47ffff, 0x48ffff, 0x49ffff, + 0x4affff, 0x4bffff, 0x4cffff, 0x4dffff, 0x4effff, 0x4fffff, 0x50ffff, + 0x51ffff, 0x52ffff, 0x53ffff, 0x54ffff, 0x55ffff, 0xffffffff, + 0xffff0056, 0xffff0057, 0xffff0058, 0xffff0059, 0xffff005a, 0xffff005b, + 0xffff005c, 0xffff005d, 0x5effff, 0x5fffff, 0x60ffff, 0x61ffff, + 0x62ffff, 0x63ffff, 0x64ffff, 0x65ffff, 0x66ffff, 0x67ffff, 0x68ffff, + 0x69ffff, 0x6affff, 0x6bffff, 0x6cffff, 0x6dffff, 0x6effff, 0x6fffff, + 0x70ffff, 0x71ffff, 0x72ffff, 0x73ffff, 0x74ffff, 0xffffffff, + 0xffff0075, 0xffff0076, 0x780077, 0xffff0079, 0x7affff, 0x7bffff, + 0xffffffff, 0xffff007c, 0xffffffff, 0xffff007d, 0xffffffff, 0xffffffff, + 0xffff007e, 0x7fffff, 0xffffffff, 0x80ffff, 0xffff0081, 0xffffffff, + 0xffff0082, 0x83ffff, 0x84ffff, 0x85ffff, 0xffffffff, 0xffff0086, + 0xffffffff, 0x87ffff, 0xffffffff, 0xffff0088, 0xffffffff, 0xffff0089, + 0xffff008a, 0x8bffff, 0xffffffff, 0x8cffff, 0x8dffff, 0xffffffff, + 0xffffffff, 0x8f008e, 0x910090, 0x930092, 0x950094, 0xffff0096, + 0xffff0097, 0xffff0098, 0xffff0099, 0xffff009a, 0xffff009b, 0xffff009c, + 0xffff009d, 0x9f009e, 0xa0ffff, 0xa1ffff, 0xa2ffff, 0xa3ffff, 0xa4ffff, + 0xa5ffff, 0xa6ffff, 0xa7ffff, 0xa8ffff, 0xa9ffff, 0xab00aa, 0xacffff, + 0xffffffff, 0xadffff, 0xaeffff, 0xafffff, 0xb0ffff, 0xb1ffff, 0xb2ffff, + 0xb3ffff, 0xb4ffff, 0xb5ffff, 0xb6ffff, 0xb7ffff, 0xb8ffff, 0xb9ffff, + 0xbaffff, 0xbbffff, 0xbcffff, 0xbdffff, 0xbeffff, 0xbfffff, 0xc0ffff, + 0xffffffff, 0xc1ffff, 0xc2ffff, 0xc3ffff, 0xc4ffff, 0xc5ffff, 0xc6ffff, + 0xc7ffff, 0xc8ffff, 0xc9ffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffff00ca, 0xcbffff, 0xffff00cc, 0xffff00cd, 0xffffffff, + 0xceffff, 0xcfffff, 0xd0ffff, 0xd1ffff, 0xd2ffff, 0xd400d3, 0xd600d5, + 0xffff00d7, 0xd900d8, 0xdaffff, 0xdbffff, 0xffffffff, 0xffffffff, + 0xffff00dc, 0xddffff, 0xdeffff, 0xffff00df, 0xe100e0, 0xe2ffff, + 0xffffffff, 0xe3ffff, 0xe4ffff, 0xffff00e5, 0xe6ffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xe7ffff, 0xffffffff, 0xffff00e8, 0xe9ffff, + 0xffffffff, 0xffffffff, 0xeb00ea, 0xed00ec, 0xffff00ee, 0xffffffff, + 0xffffffff, 0xffff00ef, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xf0ffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xf1ffff, 0xf2ffff, 0xffffffff, 0xf3ffff, 0xffffffff, 0xf4ffff, + 0xf600f5, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xf800f7, 0xfa00f9, 0xfbffff, 0xfd00fc, 0xff00fe, 0x1010100, 0x1030102, + 0x1050104, 0x1070106, 0x1090108, 0x10b010a, 0x10d010c, 0x10f010e, + 0x1110110, 0x1130112, 0x1150114, 0x1170116, 0xffff0118, 0x11a0119, + 0xffffffff, 0x11bffff, 0x11d011c, 0x11effff, 0x11fffff, 0x120ffff, + 0x121ffff, 0x122ffff, 0x123ffff, 0x124ffff, 0x125ffff, 0x126ffff, + 0x127ffff, 0x128ffff, 0x129ffff, 0x12b012a, 0xffff012c, 0x12dffff, + 0xffffffff, 0xffff012e, 0x12fffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x1310130, + 0x1330132, 0x1350134, 0x1370136, 0x1390138, 0x13b013a, 0x13d013c, + 0x13f013e, 0x1410140, 0x1430142, 0x1450144, 0x1470146, 0x1490148, + 0x14b014a, 0x14d014c, 0x14f014e, 0x1510150, 0x1530152, 0x1550154, + 0x1570156, 0x1590158, 0x15b015a, 0x15d015c, 0x15f015e, 0x160ffff, + 0x161ffff, 0x162ffff, 0x163ffff, 0x164ffff, 0x165ffff, 0x166ffff, + 0x167ffff, 0x168ffff, 0x169ffff, 0x16affff, 0x16bffff, 0x16cffff, + 0x16dffff, 0x16effff, 0x16fffff, 0x170ffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x171ffff, 0x172ffff, 0x173ffff, 0x174ffff, + 0x175ffff, 0x176ffff, 0x177ffff, 0x178ffff, 0x179ffff, 0x17affff, + 0x17bffff, 0x17cffff, 0x17dffff, 0x17effff, 0x17fffff, 0x180ffff, + 0x181ffff, 0x182ffff, 0x183ffff, 0x184ffff, 0x185ffff, 0x186ffff, + 0x187ffff, 0x188ffff, 0x189ffff, 0x18affff, 0x18bffff, 0xffffffff, + 0xffff018c, 0xffff018d, 0xffff018e, 0xffff018f, 0xffff0190, 0xffff0191, + 0x1930192, 0x194ffff, 0x195ffff, 0x196ffff, 0x197ffff, 0x198ffff, + 0x199ffff, 0x19affff, 0x19bffff, 0x19cffff, 0x19dffff, 0x19effff, + 0x19fffff, 0x1a0ffff, 0x1a1ffff, 0x1a2ffff, 0x1a3ffff, 0x1a4ffff, + 0x1a5ffff, 0x1a6ffff, 0x1a7ffff, 0x1a8ffff, 0x1a9ffff, 0x1aaffff, + 0x1abffff, 0x1acffff, 0x1adffff, 0x1aeffff, 0x1afffff, 0x1b0ffff, + 0x1b1ffff, 0x1b2ffff, 0x1b3ffff, 0x1b4ffff, 0x1b5ffff, 0x1b6ffff, + 0x1b7ffff, 0x1b8ffff, 0x1b9ffff, 0x1baffff, 0x1bbffff, 0x1bcffff, + 0x1bdffff, 0x1beffff, 0x1bfffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x1c0ffff, 0x1c201c1, 0x1c401c3, 0x1c601c5, 0x1c801c7, + 0x1ca01c9, 0x1cc01cb, 0x1ce01cd, 0x1d001cf, 0x1d201d1, 0x1d401d3, + 0x1d601d5, 0x1d801d7, 0x1da01d9, 0x1dc01db, 0x1de01dd, 0x1e001df, + 0x1e201e1, 0x1e401e3, 0xffff01e5, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x1e6ffff, + 0xffffffff, 0x1e7ffff, 0xffffffff, 0x1e8ffff, 0x1e9ffff, 0x1eaffff, + 0x1ebffff, 0x1ecffff, 0x1edffff, 0x1eeffff, 0x1efffff, 0x1f0ffff, + 0x1f1ffff, 0x1f2ffff, 0x1f3ffff, 0x1f4ffff, 0x1f5ffff, 0x1f6ffff, + 0x1f7ffff, 0x1f8ffff, 0x1f9ffff, 0x1faffff, 0x1fbffff, 0x1fcffff, + 0x1fdffff, 0x1feffff, 0x1ffffff, 0x200ffff, 0x201ffff, 0x202ffff, + 0x203ffff, 0x204ffff, 0x205ffff, 0x206ffff, 0x207ffff, 0x208ffff, + 0x209ffff, 0x20affff, 0x20bffff, 0x20cffff, 0x20dffff, 0x20effff, + 0x20fffff, 0x210ffff, 0x211ffff, 0x212ffff, 0x213ffff, 0x214ffff, + 0x215ffff, 0x216ffff, 0x217ffff, 0x218ffff, 0x219ffff, 0x21affff, + 0x21bffff, 0x21cffff, 0x21dffff, 0x21effff, 0x21fffff, 0x220ffff, + 0x221ffff, 0x222ffff, 0x223ffff, 0x224ffff, 0x225ffff, 0x226ffff, + 0x227ffff, 0x228ffff, 0x229ffff, 0x22affff, 0x22bffff, 0x22cffff, + 0x22dffff, 0x22effff, 0x22fffff, 0x230ffff, 0x231ffff, 0x232ffff, + 0xffffffff, 0xffffffff, 0x233ffff, 0xffffffff, 0xffffffff, 0x234ffff, + 0x235ffff, 0x236ffff, 0x237ffff, 0x238ffff, 0x239ffff, 0x23affff, + 0x23bffff, 0x23cffff, 0x23dffff, 0x23effff, 0x23fffff, 0x240ffff, + 0x241ffff, 0x242ffff, 0x243ffff, 0x244ffff, 0x245ffff, 0x246ffff, + 0x247ffff, 0x248ffff, 0x249ffff, 0x24affff, 0x24bffff, 0x24cffff, + 0x24dffff, 0x24effff, 0x24fffff, 0x250ffff, 0x251ffff, 0x252ffff, + 0x253ffff, 0x254ffff, 0x255ffff, 0x256ffff, 0x257ffff, 0x258ffff, + 0x259ffff, 0x25affff, 0x25bffff, 0x25cffff, 0x25dffff, 0x25effff, + 0x25fffff, 0x260ffff, 0x261ffff, 0x262ffff, 0x263ffff, 0x2650264, + 0x2670266, 0x2690268, 0x26b026a, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x26d026c, 0x26f026e, 0x2710270, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x2730272, 0x2750274, 0x2770276, + 0x2790278, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x27b027a, + 0x27d027c, 0x27f027e, 0x2810280, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2830282, 0x2850284, 0x2870286, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x288ffff, 0x289ffff, 0x28affff, + 0x28bffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x28d028c, + 0x28f028e, 0x2910290, 0x2930292, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2950294, 0x2970296, 0x2990298, 0x29b029a, 0x29d029c, + 0x29f029e, 0x2a102a0, 0xffffffff, 0x2a302a2, 0x2a502a4, 0x2a702a6, + 0x2a902a8, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x2ab02aa, + 0x2ad02ac, 0x2af02ae, 0x2b102b0, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2b302b2, 0x2b502b4, 0x2b702b6, 0x2b902b8, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x2bb02ba, 0x2bcffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff02bd, 0xffffffff, + 0x2beffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2c002bf, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x2c202c1, 0xffffffff, 0x2c3ffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x2c4ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffff02c5, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2c702c6, 0x2c902c8, 0x2cb02ca, 0x2cd02cc, 0x2cf02ce, + 0x2d102d0, 0x2d302d2, 0x2d502d4, 0xffffffff, 0xffffffff, 0xffff02d6, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2d802d7, 0x2da02d9, 0x2dc02db, 0x2de02dd, 0x2e002df, + 0x2e202e1, 0x2e402e3, 0x2e602e5, 0x2e802e7, 0x2ea02e9, 0x2ec02eb, + 0x2ee02ed, 0x2f002ef, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x2f202f1, 0x2f402f3, 0x2f602f5, 0x2f802f7, 0x2fa02f9, + 0x2fc02fb, 0x2fe02fd, 0x30002ff, 0x3020301, 0x3040303, 0x3060305, + 0x3080307, 0x30a0309, 0x30c030b, 0x30e030d, 0x310030f, 0x3120311, + 0x3140313, 0x3160315, 0x3180317, 0x31a0319, 0x31c031b, 0x31e031d, + 0xffff031f, 0x320ffff, 0xffffffff, 0x321ffff, 0xffff0322, 0xffff0323, + 0xffff0324, 0xffff0325, 0xffffffff, 0xffffffff, 0x326ffff, 0xffffffff, + 0xffff0327, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x328ffff, + 0x329ffff, 0x32affff, 0x32bffff, 0x32cffff, 0x32dffff, 0x32effff, + 0x32fffff, 0x330ffff, 0x331ffff, 0x332ffff, 0x333ffff, 0x334ffff, + 0x335ffff, 0x336ffff, 0x337ffff, 0x338ffff, 0x339ffff, 0x33affff, + 0x33bffff, 0x33cffff, 0x33dffff, 0x33effff, 0x33fffff, 0x340ffff, + 0x341ffff, 0x342ffff, 0x343ffff, 0x344ffff, 0x345ffff, 0x346ffff, + 0x347ffff, 0x348ffff, 0x349ffff, 0x34affff, 0x34bffff, 0x34cffff, + 0x34dffff, 0x34effff, 0x34fffff, 0x350ffff, 0x351ffff, 0x352ffff, + 0x353ffff, 0x354ffff, 0x355ffff, 0x356ffff, 0x357ffff, 0x358ffff, + 0x359ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff035a, + 0xffff035b, 0xffffffff, 0x35cffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x35e035d, 0x360035f, 0x3620361, + 0x3640363, 0x3660365, 0x3680367, 0x36a0369, 0x36c036b, 0x36e036d, + 0x370036f, 0x3720371, 0x3740373, 0x3760375, 0x3780377, 0x37a0379, + 0x37c037b, 0x37e037d, 0x380037f, 0x3820381, 0x383ffff, 0xffffffff, + 0xffffffff, 0x384ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x385ffff, + 0x386ffff, 0x387ffff, 0x388ffff, 0x389ffff, 0x38affff, 0x38bffff, + 0x38cffff, 0x38dffff, 0x38effff, 0x38fffff, 0x390ffff, 0x391ffff, + 0x392ffff, 0x393ffff, 0x394ffff, 0x395ffff, 0x396ffff, 0x397ffff, + 0x398ffff, 0x399ffff, 0x39affff, 0x39bffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x39cffff, 0x39dffff, 0x39effff, 0x39fffff, 0x3a0ffff, + 0x3a1ffff, 0x3a2ffff, 0x3a3ffff, 0x3a4ffff, 0x3a5ffff, 0x3a6ffff, + 0x3a7ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x3a8ffff, 0x3a9ffff, 0x3aaffff, 0x3abffff, + 0x3acffff, 0x3adffff, 0x3aeffff, 0xffffffff, 0x3afffff, 0x3b0ffff, + 0x3b1ffff, 0x3b2ffff, 0x3b3ffff, 0x3b4ffff, 0x3b5ffff, 0x3b6ffff, + 0x3b7ffff, 0x3b8ffff, 0x3b9ffff, 0x3baffff, 0x3bbffff, 0x3bcffff, + 0x3bdffff, 0x3beffff, 0x3bfffff, 0x3c0ffff, 0x3c1ffff, 0x3c2ffff, + 0x3c3ffff, 0x3c4ffff, 0x3c5ffff, 0x3c6ffff, 0x3c7ffff, 0x3c8ffff, + 0x3c9ffff, 0x3caffff, 0x3cbffff, 0x3ccffff, 0x3cdffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffff03ce, 0xffff03cf, + 0x3d0ffff, 0x3d1ffff, 0x3d2ffff, 0x3d3ffff, 0x3d4ffff, 0xffffffff, + 0xffffffff, 0xffff03d5, 0xffffffff, 0x3d6ffff, 0x3d7ffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x3d8ffff, + 0x3d9ffff, 0x3daffff, 0x3dbffff, 0x3dcffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x3ddffff, 0x3df03de, 0x3e103e0, + 0x3e303e2, 0x3e503e4, 0x3e703e6, 0x3e903e8, 0x3eb03ea, 0x3ed03ec, + 0x3ef03ee, 0x3f103f0, 0x3f303f2, 0x3f503f4, 0xffff03f6, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x3f803f7, 0x3fa03f9, 0x3fc03fb, 0x3fe03fd, 0x40003ff, + 0x4020401, 0x4040403, 0x4060405, 0x4080407, 0x40a0409, 0x40c040b, + 0x40e040d, 0x410040f, 0x4120411, 0x4140413, 0x4160415, 0x4180417, + 0x41a0419, 0x41c041b, 0x41e041d, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff]); +@property +{ +private alias _IUA = immutable(uint[]); +_IUA toUpperTable() +{ + static _IUA t = [ + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, + 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x39c, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, + 0xd4, 0xd5, 0xd6, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0x178, + 0x100, 0x102, 0x104, 0x106, 0x108, 0x10a, 0x10c, 0x10e, 0x110, 0x112, + 0x114, 0x116, 0x118, 0x11a, 0x11c, 0x11e, 0x120, 0x122, 0x124, 0x126, + 0x128, 0x12a, 0x12c, 0x12e, 0x49, 0x132, 0x134, 0x136, 0x139, 0x13b, + 0x13d, 0x13f, 0x141, 0x143, 0x145, 0x147, 0x14a, 0x14c, 0x14e, 0x150, + 0x152, 0x154, 0x156, 0x158, 0x15a, 0x15c, 0x15e, 0x160, 0x162, 0x164, + 0x166, 0x168, 0x16a, 0x16c, 0x16e, 0x170, 0x172, 0x174, 0x176, 0x179, + 0x17b, 0x17d, 0x53, 0x243, 0x182, 0x184, 0x187, 0x18b, 0x191, 0x1f6, + 0x198, 0x23d, 0x220, 0x1a0, 0x1a2, 0x1a4, 0x1a7, 0x1ac, 0x1af, 0x1b3, + 0x1b5, 0x1b8, 0x1bc, 0x1f7, 0x1c4, 0x1c4, 0x1c7, 0x1c7, 0x1ca, 0x1ca, + 0x1cd, 0x1cf, 0x1d1, 0x1d3, 0x1d5, 0x1d7, 0x1d9, 0x1db, 0x18e, 0x1de, + 0x1e0, 0x1e2, 0x1e4, 0x1e6, 0x1e8, 0x1ea, 0x1ec, 0x1ee, 0x1f1, 0x1f1, + 0x1f4, 0x1f8, 0x1fa, 0x1fc, 0x1fe, 0x200, 0x202, 0x204, 0x206, 0x208, + 0x20a, 0x20c, 0x20e, 0x210, 0x212, 0x214, 0x216, 0x218, 0x21a, 0x21c, + 0x21e, 0x222, 0x224, 0x226, 0x228, 0x22a, 0x22c, 0x22e, 0x230, 0x232, + 0x23b, 0x2c7e, 0x2c7f, 0x241, 0x246, 0x248, 0x24a, 0x24c, 0x24e, + 0x2c6f, 0x2c6d, 0x2c70, 0x181, 0x186, 0x189, 0x18a, 0x18f, 0x190, + 0x193, 0x194, 0xa78d, 0xa7aa, 0x197, 0x196, 0x2c62, 0x19c, 0x2c6e, + 0x19d, 0x19f, 0x2c64, 0x1a6, 0x1a9, 0x1ae, 0x244, 0x1b1, 0x1b2, 0x245, + 0x1b7, 0x399, 0x370, 0x372, 0x376, 0x3fd, 0x3fe, 0x3ff, 0x386, 0x388, + 0x389, 0x38a, 0x391, 0x392, 0x393, 0x394, 0x395, 0x396, 0x397, 0x398, + 0x399, 0x39a, 0x39b, 0x39c, 0x39d, 0x39e, 0x39f, 0x3a0, 0x3a1, 0x3a3, + 0x3a3, 0x3a4, 0x3a5, 0x3a6, 0x3a7, 0x3a8, 0x3a9, 0x3aa, 0x3ab, 0x38c, + 0x38e, 0x38f, 0x392, 0x398, 0x3a6, 0x3a0, 0x3cf, 0x3d8, 0x3da, 0x3dc, + 0x3de, 0x3e0, 0x3e2, 0x3e4, 0x3e6, 0x3e8, 0x3ea, 0x3ec, 0x3ee, 0x39a, + 0x3a1, 0x3f9, 0x395, 0x3f7, 0x3fa, 0x410, 0x411, 0x412, 0x413, 0x414, + 0x415, 0x416, 0x417, 0x418, 0x419, 0x41a, 0x41b, 0x41c, 0x41d, 0x41e, + 0x41f, 0x420, 0x421, 0x422, 0x423, 0x424, 0x425, 0x426, 0x427, 0x428, + 0x429, 0x42a, 0x42b, 0x42c, 0x42d, 0x42e, 0x42f, 0x400, 0x401, 0x402, + 0x403, 0x404, 0x405, 0x406, 0x407, 0x408, 0x409, 0x40a, 0x40b, 0x40c, + 0x40d, 0x40e, 0x40f, 0x460, 0x462, 0x464, 0x466, 0x468, 0x46a, 0x46c, + 0x46e, 0x470, 0x472, 0x474, 0x476, 0x478, 0x47a, 0x47c, 0x47e, 0x480, + 0x48a, 0x48c, 0x48e, 0x490, 0x492, 0x494, 0x496, 0x498, 0x49a, 0x49c, + 0x49e, 0x4a0, 0x4a2, 0x4a4, 0x4a6, 0x4a8, 0x4aa, 0x4ac, 0x4ae, 0x4b0, + 0x4b2, 0x4b4, 0x4b6, 0x4b8, 0x4ba, 0x4bc, 0x4be, 0x4c1, 0x4c3, 0x4c5, + 0x4c7, 0x4c9, 0x4cb, 0x4cd, 0x4c0, 0x4d0, 0x4d2, 0x4d4, 0x4d6, 0x4d8, + 0x4da, 0x4dc, 0x4de, 0x4e0, 0x4e2, 0x4e4, 0x4e6, 0x4e8, 0x4ea, 0x4ec, + 0x4ee, 0x4f0, 0x4f2, 0x4f4, 0x4f6, 0x4f8, 0x4fa, 0x4fc, 0x4fe, 0x500, + 0x502, 0x504, 0x506, 0x508, 0x50a, 0x50c, 0x50e, 0x510, 0x512, 0x514, + 0x516, 0x518, 0x51a, 0x51c, 0x51e, 0x520, 0x522, 0x524, 0x526, 0x531, + 0x532, 0x533, 0x534, 0x535, 0x536, 0x537, 0x538, 0x539, 0x53a, 0x53b, + 0x53c, 0x53d, 0x53e, 0x53f, 0x540, 0x541, 0x542, 0x543, 0x544, 0x545, + 0x546, 0x547, 0x548, 0x549, 0x54a, 0x54b, 0x54c, 0x54d, 0x54e, 0x54f, + 0x550, 0x551, 0x552, 0x553, 0x554, 0x555, 0x556, 0xa77d, 0x2c63, + 0x1e00, 0x1e02, 0x1e04, 0x1e06, 0x1e08, 0x1e0a, 0x1e0c, 0x1e0e, 0x1e10, + 0x1e12, 0x1e14, 0x1e16, 0x1e18, 0x1e1a, 0x1e1c, 0x1e1e, 0x1e20, 0x1e22, + 0x1e24, 0x1e26, 0x1e28, 0x1e2a, 0x1e2c, 0x1e2e, 0x1e30, 0x1e32, 0x1e34, + 0x1e36, 0x1e38, 0x1e3a, 0x1e3c, 0x1e3e, 0x1e40, 0x1e42, 0x1e44, 0x1e46, + 0x1e48, 0x1e4a, 0x1e4c, 0x1e4e, 0x1e50, 0x1e52, 0x1e54, 0x1e56, 0x1e58, + 0x1e5a, 0x1e5c, 0x1e5e, 0x1e60, 0x1e62, 0x1e64, 0x1e66, 0x1e68, 0x1e6a, + 0x1e6c, 0x1e6e, 0x1e70, 0x1e72, 0x1e74, 0x1e76, 0x1e78, 0x1e7a, 0x1e7c, + 0x1e7e, 0x1e80, 0x1e82, 0x1e84, 0x1e86, 0x1e88, 0x1e8a, 0x1e8c, 0x1e8e, + 0x1e90, 0x1e92, 0x1e94, 0x1e60, 0x1ea0, 0x1ea2, 0x1ea4, 0x1ea6, 0x1ea8, + 0x1eaa, 0x1eac, 0x1eae, 0x1eb0, 0x1eb2, 0x1eb4, 0x1eb6, 0x1eb8, 0x1eba, + 0x1ebc, 0x1ebe, 0x1ec0, 0x1ec2, 0x1ec4, 0x1ec6, 0x1ec8, 0x1eca, 0x1ecc, + 0x1ece, 0x1ed0, 0x1ed2, 0x1ed4, 0x1ed6, 0x1ed8, 0x1eda, 0x1edc, 0x1ede, + 0x1ee0, 0x1ee2, 0x1ee4, 0x1ee6, 0x1ee8, 0x1eea, 0x1eec, 0x1eee, 0x1ef0, + 0x1ef2, 0x1ef4, 0x1ef6, 0x1ef8, 0x1efa, 0x1efc, 0x1efe, 0x1f08, 0x1f09, + 0x1f0a, 0x1f0b, 0x1f0c, 0x1f0d, 0x1f0e, 0x1f0f, 0x1f18, 0x1f19, 0x1f1a, + 0x1f1b, 0x1f1c, 0x1f1d, 0x1f28, 0x1f29, 0x1f2a, 0x1f2b, 0x1f2c, 0x1f2d, + 0x1f2e, 0x1f2f, 0x1f38, 0x1f39, 0x1f3a, 0x1f3b, 0x1f3c, 0x1f3d, 0x1f3e, + 0x1f3f, 0x1f48, 0x1f49, 0x1f4a, 0x1f4b, 0x1f4c, 0x1f4d, 0x1f59, 0x1f5b, + 0x1f5d, 0x1f5f, 0x1f68, 0x1f69, 0x1f6a, 0x1f6b, 0x1f6c, 0x1f6d, 0x1f6e, + 0x1f6f, 0x1fba, 0x1fbb, 0x1fc8, 0x1fc9, 0x1fca, 0x1fcb, 0x1fda, 0x1fdb, + 0x1ff8, 0x1ff9, 0x1fea, 0x1feb, 0x1ffa, 0x1ffb, 0x1f88, 0x1f89, 0x1f8a, + 0x1f8b, 0x1f8c, 0x1f8d, 0x1f8e, 0x1f8f, 0x1f98, 0x1f99, 0x1f9a, 0x1f9b, + 0x1f9c, 0x1f9d, 0x1f9e, 0x1f9f, 0x1fa8, 0x1fa9, 0x1faa, 0x1fab, 0x1fac, + 0x1fad, 0x1fae, 0x1faf, 0x1fb8, 0x1fb9, 0x1fbc, 0x399, 0x1fcc, 0x1fd8, + 0x1fd9, 0x1fe8, 0x1fe9, 0x1fec, 0x1ffc, 0x2132, 0x2160, 0x2161, 0x2162, + 0x2163, 0x2164, 0x2165, 0x2166, 0x2167, 0x2168, 0x2169, 0x216a, 0x216b, + 0x216c, 0x216d, 0x216e, 0x216f, 0x2183, 0x24b6, 0x24b7, 0x24b8, 0x24b9, + 0x24ba, 0x24bb, 0x24bc, 0x24bd, 0x24be, 0x24bf, 0x24c0, 0x24c1, 0x24c2, + 0x24c3, 0x24c4, 0x24c5, 0x24c6, 0x24c7, 0x24c8, 0x24c9, 0x24ca, 0x24cb, + 0x24cc, 0x24cd, 0x24ce, 0x24cf, 0x2c00, 0x2c01, 0x2c02, 0x2c03, 0x2c04, + 0x2c05, 0x2c06, 0x2c07, 0x2c08, 0x2c09, 0x2c0a, 0x2c0b, 0x2c0c, 0x2c0d, + 0x2c0e, 0x2c0f, 0x2c10, 0x2c11, 0x2c12, 0x2c13, 0x2c14, 0x2c15, 0x2c16, + 0x2c17, 0x2c18, 0x2c19, 0x2c1a, 0x2c1b, 0x2c1c, 0x2c1d, 0x2c1e, 0x2c1f, + 0x2c20, 0x2c21, 0x2c22, 0x2c23, 0x2c24, 0x2c25, 0x2c26, 0x2c27, 0x2c28, + 0x2c29, 0x2c2a, 0x2c2b, 0x2c2c, 0x2c2d, 0x2c2e, 0x2c60, 0x23a, 0x23e, + 0x2c67, 0x2c69, 0x2c6b, 0x2c72, 0x2c75, 0x2c80, 0x2c82, 0x2c84, 0x2c86, + 0x2c88, 0x2c8a, 0x2c8c, 0x2c8e, 0x2c90, 0x2c92, 0x2c94, 0x2c96, 0x2c98, + 0x2c9a, 0x2c9c, 0x2c9e, 0x2ca0, 0x2ca2, 0x2ca4, 0x2ca6, 0x2ca8, 0x2caa, + 0x2cac, 0x2cae, 0x2cb0, 0x2cb2, 0x2cb4, 0x2cb6, 0x2cb8, 0x2cba, 0x2cbc, + 0x2cbe, 0x2cc0, 0x2cc2, 0x2cc4, 0x2cc6, 0x2cc8, 0x2cca, 0x2ccc, 0x2cce, + 0x2cd0, 0x2cd2, 0x2cd4, 0x2cd6, 0x2cd8, 0x2cda, 0x2cdc, 0x2cde, 0x2ce0, + 0x2ce2, 0x2ceb, 0x2ced, 0x2cf2, 0x10a0, 0x10a1, 0x10a2, 0x10a3, 0x10a4, + 0x10a5, 0x10a6, 0x10a7, 0x10a8, 0x10a9, 0x10aa, 0x10ab, 0x10ac, 0x10ad, + 0x10ae, 0x10af, 0x10b0, 0x10b1, 0x10b2, 0x10b3, 0x10b4, 0x10b5, 0x10b6, + 0x10b7, 0x10b8, 0x10b9, 0x10ba, 0x10bb, 0x10bc, 0x10bd, 0x10be, 0x10bf, + 0x10c0, 0x10c1, 0x10c2, 0x10c3, 0x10c4, 0x10c5, 0x10c7, 0x10cd, 0xa640, + 0xa642, 0xa644, 0xa646, 0xa648, 0xa64a, 0xa64c, 0xa64e, 0xa650, 0xa652, + 0xa654, 0xa656, 0xa658, 0xa65a, 0xa65c, 0xa65e, 0xa660, 0xa662, 0xa664, + 0xa666, 0xa668, 0xa66a, 0xa66c, 0xa680, 0xa682, 0xa684, 0xa686, 0xa688, + 0xa68a, 0xa68c, 0xa68e, 0xa690, 0xa692, 0xa694, 0xa696, 0xa722, 0xa724, + 0xa726, 0xa728, 0xa72a, 0xa72c, 0xa72e, 0xa732, 0xa734, 0xa736, 0xa738, + 0xa73a, 0xa73c, 0xa73e, 0xa740, 0xa742, 0xa744, 0xa746, 0xa748, 0xa74a, + 0xa74c, 0xa74e, 0xa750, 0xa752, 0xa754, 0xa756, 0xa758, 0xa75a, 0xa75c, + 0xa75e, 0xa760, 0xa762, 0xa764, 0xa766, 0xa768, 0xa76a, 0xa76c, 0xa76e, + 0xa779, 0xa77b, 0xa77e, 0xa780, 0xa782, 0xa784, 0xa786, 0xa78b, 0xa790, + 0xa792, 0xa7a0, 0xa7a2, 0xa7a4, 0xa7a6, 0xa7a8, 0xff21, 0xff22, 0xff23, + 0xff24, 0xff25, 0xff26, 0xff27, 0xff28, 0xff29, 0xff2a, 0xff2b, 0xff2c, + 0xff2d, 0xff2e, 0xff2f, 0xff30, 0xff31, 0xff32, 0xff33, 0xff34, 0xff35, + 0xff36, 0xff37, 0xff38, 0xff39, 0xff3a, 0x10400, 0x10401, 0x10402, + 0x10403, 0x10404, 0x10405, 0x10406, 0x10407, 0x10408, 0x10409, 0x1040a, + 0x1040b, 0x1040c, 0x1040d, 0x1040e, 0x1040f, 0x10410, 0x10411, 0x10412, + 0x10413, 0x10414, 0x10415, 0x10416, 0x10417, 0x10418, 0x10419, 0x1041a, + 0x1041b, 0x1041c, 0x1041d, 0x1041e, 0x1041f, 0x10420, 0x10421, 0x10422, + 0x10423, 0x10424, 0x10425, 0x10426, 0x10427, 0x2000053, 0x53, 0x130, + 0x2000046, 0x46, 0x2000046, 0x49, 0x2000046, 0x4c, 0x3000046, 0x46, + 0x49, 0x3000046, 0x46, 0x4c, 0x2000053, 0x54, 0x2000053, 0x54, + 0x2000535, 0x552, 0x2000544, 0x546, 0x2000544, 0x535, 0x2000544, 0x53b, + 0x200054e, 0x546, 0x2000544, 0x53d, 0x20002bc, 0x4e, 0x3000399, 0x308, + 0x301, 0x30003a5, 0x308, 0x301, 0x200004a, 0x30c, 0x2000048, 0x331, + 0x2000054, 0x308, 0x2000057, 0x30a, 0x2000059, 0x30a, 0x2000041, 0x2be, + 0x20003a5, 0x313, 0x30003a5, 0x313, 0x300, 0x30003a5, 0x313, 0x301, + 0x30003a5, 0x313, 0x342, 0x2000391, 0x342, 0x2000397, 0x342, 0x3000399, + 0x308, 0x300, 0x3000399, 0x308, 0x301, 0x2000399, 0x342, 0x3000399, + 0x308, 0x342, 0x30003a5, 0x308, 0x300, 0x30003a5, 0x308, 0x301, + 0x20003a1, 0x313, 0x20003a5, 0x342, 0x30003a5, 0x308, 0x342, 0x20003a9, + 0x342, 0x2001f08, 0x399, 0x2001f09, 0x399, 0x2001f0a, 0x399, 0x2001f0b, + 0x399, 0x2001f0c, 0x399, 0x2001f0d, 0x399, 0x2001f0e, 0x399, 0x2001f0f, + 0x399, 0x2001f08, 0x399, 0x2001f09, 0x399, 0x2001f0a, 0x399, 0x2001f0b, + 0x399, 0x2001f0c, 0x399, 0x2001f0d, 0x399, 0x2001f0e, 0x399, 0x2001f0f, + 0x399, 0x2001f28, 0x399, 0x2001f29, 0x399, 0x2001f2a, 0x399, 0x2001f2b, + 0x399, 0x2001f2c, 0x399, 0x2001f2d, 0x399, 0x2001f2e, 0x399, 0x2001f2f, + 0x399, 0x2001f28, 0x399, 0x2001f29, 0x399, 0x2001f2a, 0x399, 0x2001f2b, + 0x399, 0x2001f2c, 0x399, 0x2001f2d, 0x399, 0x2001f2e, 0x399, 0x2001f2f, + 0x399, 0x2001f68, 0x399, 0x2001f69, 0x399, 0x2001f6a, 0x399, 0x2001f6b, + 0x399, 0x2001f6c, 0x399, 0x2001f6d, 0x399, 0x2001f6e, 0x399, 0x2001f6f, + 0x399, 0x2001f68, 0x399, 0x2001f69, 0x399, 0x2001f6a, 0x399, 0x2001f6b, + 0x399, 0x2001f6c, 0x399, 0x2001f6d, 0x399, 0x2001f6e, 0x399, 0x2001f6f, + 0x399, 0x2000391, 0x399, 0x2000391, 0x399, 0x2000397, 0x399, 0x2000397, + 0x399, 0x20003a9, 0x399, 0x20003a9, 0x399, 0x2001fba, 0x399, 0x2000386, + 0x399, 0x2001fca, 0x399, 0x2000389, 0x399, 0x2001ffa, 0x399, 0x200038f, + 0x399, 0x3000391, 0x342, 0x399, 0x3000397, 0x342, 0x399, 0x30003a9, 0x342, + 0x399 + ]; + return t; +} +_IUA toLowerTable() +{ + static _IUA t = [ + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, + 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, + 0xf6, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0x101, 0x103, 0x105, + 0x107, 0x109, 0x10b, 0x10d, 0x10f, 0x111, 0x113, 0x115, 0x117, 0x119, + 0x11b, 0x11d, 0x11f, 0x121, 0x123, 0x125, 0x127, 0x129, 0x12b, 0x12d, + 0x12f, 0x69, 0x133, 0x135, 0x137, 0x13a, 0x13c, 0x13e, 0x140, 0x142, + 0x144, 0x146, 0x148, 0x14b, 0x14d, 0x14f, 0x151, 0x153, 0x155, 0x157, + 0x159, 0x15b, 0x15d, 0x15f, 0x161, 0x163, 0x165, 0x167, 0x169, 0x16b, + 0x16d, 0x16f, 0x171, 0x173, 0x175, 0x177, 0xff, 0x17a, 0x17c, 0x17e, + 0x253, 0x183, 0x185, 0x254, 0x188, 0x256, 0x257, 0x18c, 0x1dd, 0x259, + 0x25b, 0x192, 0x260, 0x263, 0x269, 0x268, 0x199, 0x26f, 0x272, 0x275, + 0x1a1, 0x1a3, 0x1a5, 0x280, 0x1a8, 0x283, 0x1ad, 0x288, 0x1b0, 0x28a, + 0x28b, 0x1b4, 0x1b6, 0x292, 0x1b9, 0x1bd, 0x1c6, 0x1c6, 0x1c9, 0x1c9, + 0x1cc, 0x1cc, 0x1ce, 0x1d0, 0x1d2, 0x1d4, 0x1d6, 0x1d8, 0x1da, 0x1dc, + 0x1df, 0x1e1, 0x1e3, 0x1e5, 0x1e7, 0x1e9, 0x1eb, 0x1ed, 0x1ef, 0x1f3, + 0x1f3, 0x1f5, 0x195, 0x1bf, 0x1f9, 0x1fb, 0x1fd, 0x1ff, 0x201, 0x203, + 0x205, 0x207, 0x209, 0x20b, 0x20d, 0x20f, 0x211, 0x213, 0x215, 0x217, + 0x219, 0x21b, 0x21d, 0x21f, 0x19e, 0x223, 0x225, 0x227, 0x229, 0x22b, + 0x22d, 0x22f, 0x231, 0x233, 0x2c65, 0x23c, 0x19a, 0x2c66, 0x242, 0x180, + 0x289, 0x28c, 0x247, 0x249, 0x24b, 0x24d, 0x24f, 0x371, 0x373, 0x377, + 0x3ac, 0x3ad, 0x3ae, 0x3af, 0x3cc, 0x3cd, 0x3ce, 0x3b1, 0x3b2, 0x3b3, + 0x3b4, 0x3b5, 0x3b6, 0x3b7, 0x3b8, 0x3b9, 0x3ba, 0x3bb, 0x3bc, 0x3bd, + 0x3be, 0x3bf, 0x3c0, 0x3c1, 0x3c3, 0x3c4, 0x3c5, 0x3c6, 0x3c7, 0x3c8, + 0x3c9, 0x3ca, 0x3cb, 0x3d7, 0x3d9, 0x3db, 0x3dd, 0x3df, 0x3e1, 0x3e3, + 0x3e5, 0x3e7, 0x3e9, 0x3eb, 0x3ed, 0x3ef, 0x3b8, 0x3f8, 0x3f2, 0x3fb, + 0x37b, 0x37c, 0x37d, 0x450, 0x451, 0x452, 0x453, 0x454, 0x455, 0x456, + 0x457, 0x458, 0x459, 0x45a, 0x45b, 0x45c, 0x45d, 0x45e, 0x45f, 0x430, + 0x431, 0x432, 0x433, 0x434, 0x435, 0x436, 0x437, 0x438, 0x439, 0x43a, + 0x43b, 0x43c, 0x43d, 0x43e, 0x43f, 0x440, 0x441, 0x442, 0x443, 0x444, + 0x445, 0x446, 0x447, 0x448, 0x449, 0x44a, 0x44b, 0x44c, 0x44d, 0x44e, + 0x44f, 0x461, 0x463, 0x465, 0x467, 0x469, 0x46b, 0x46d, 0x46f, 0x471, + 0x473, 0x475, 0x477, 0x479, 0x47b, 0x47d, 0x47f, 0x481, 0x48b, 0x48d, + 0x48f, 0x491, 0x493, 0x495, 0x497, 0x499, 0x49b, 0x49d, 0x49f, 0x4a1, + 0x4a3, 0x4a5, 0x4a7, 0x4a9, 0x4ab, 0x4ad, 0x4af, 0x4b1, 0x4b3, 0x4b5, + 0x4b7, 0x4b9, 0x4bb, 0x4bd, 0x4bf, 0x4cf, 0x4c2, 0x4c4, 0x4c6, 0x4c8, + 0x4ca, 0x4cc, 0x4ce, 0x4d1, 0x4d3, 0x4d5, 0x4d7, 0x4d9, 0x4db, 0x4dd, + 0x4df, 0x4e1, 0x4e3, 0x4e5, 0x4e7, 0x4e9, 0x4eb, 0x4ed, 0x4ef, 0x4f1, + 0x4f3, 0x4f5, 0x4f7, 0x4f9, 0x4fb, 0x4fd, 0x4ff, 0x501, 0x503, 0x505, + 0x507, 0x509, 0x50b, 0x50d, 0x50f, 0x511, 0x513, 0x515, 0x517, 0x519, + 0x51b, 0x51d, 0x51f, 0x521, 0x523, 0x525, 0x527, 0x561, 0x562, 0x563, + 0x564, 0x565, 0x566, 0x567, 0x568, 0x569, 0x56a, 0x56b, 0x56c, 0x56d, + 0x56e, 0x56f, 0x570, 0x571, 0x572, 0x573, 0x574, 0x575, 0x576, 0x577, + 0x578, 0x579, 0x57a, 0x57b, 0x57c, 0x57d, 0x57e, 0x57f, 0x580, 0x581, + 0x582, 0x583, 0x584, 0x585, 0x586, 0x2d00, 0x2d01, 0x2d02, 0x2d03, + 0x2d04, 0x2d05, 0x2d06, 0x2d07, 0x2d08, 0x2d09, 0x2d0a, 0x2d0b, 0x2d0c, + 0x2d0d, 0x2d0e, 0x2d0f, 0x2d10, 0x2d11, 0x2d12, 0x2d13, 0x2d14, 0x2d15, + 0x2d16, 0x2d17, 0x2d18, 0x2d19, 0x2d1a, 0x2d1b, 0x2d1c, 0x2d1d, 0x2d1e, + 0x2d1f, 0x2d20, 0x2d21, 0x2d22, 0x2d23, 0x2d24, 0x2d25, 0x2d27, 0x2d2d, + 0x1e01, 0x1e03, 0x1e05, 0x1e07, 0x1e09, 0x1e0b, 0x1e0d, 0x1e0f, 0x1e11, + 0x1e13, 0x1e15, 0x1e17, 0x1e19, 0x1e1b, 0x1e1d, 0x1e1f, 0x1e21, 0x1e23, + 0x1e25, 0x1e27, 0x1e29, 0x1e2b, 0x1e2d, 0x1e2f, 0x1e31, 0x1e33, 0x1e35, + 0x1e37, 0x1e39, 0x1e3b, 0x1e3d, 0x1e3f, 0x1e41, 0x1e43, 0x1e45, 0x1e47, + 0x1e49, 0x1e4b, 0x1e4d, 0x1e4f, 0x1e51, 0x1e53, 0x1e55, 0x1e57, 0x1e59, + 0x1e5b, 0x1e5d, 0x1e5f, 0x1e61, 0x1e63, 0x1e65, 0x1e67, 0x1e69, 0x1e6b, + 0x1e6d, 0x1e6f, 0x1e71, 0x1e73, 0x1e75, 0x1e77, 0x1e79, 0x1e7b, 0x1e7d, + 0x1e7f, 0x1e81, 0x1e83, 0x1e85, 0x1e87, 0x1e89, 0x1e8b, 0x1e8d, 0x1e8f, + 0x1e91, 0x1e93, 0x1e95, 0xdf, 0x1ea1, 0x1ea3, 0x1ea5, 0x1ea7, 0x1ea9, + 0x1eab, 0x1ead, 0x1eaf, 0x1eb1, 0x1eb3, 0x1eb5, 0x1eb7, 0x1eb9, 0x1ebb, + 0x1ebd, 0x1ebf, 0x1ec1, 0x1ec3, 0x1ec5, 0x1ec7, 0x1ec9, 0x1ecb, 0x1ecd, + 0x1ecf, 0x1ed1, 0x1ed3, 0x1ed5, 0x1ed7, 0x1ed9, 0x1edb, 0x1edd, 0x1edf, + 0x1ee1, 0x1ee3, 0x1ee5, 0x1ee7, 0x1ee9, 0x1eeb, 0x1eed, 0x1eef, 0x1ef1, + 0x1ef3, 0x1ef5, 0x1ef7, 0x1ef9, 0x1efb, 0x1efd, 0x1eff, 0x1f00, 0x1f01, + 0x1f02, 0x1f03, 0x1f04, 0x1f05, 0x1f06, 0x1f07, 0x1f10, 0x1f11, 0x1f12, + 0x1f13, 0x1f14, 0x1f15, 0x1f20, 0x1f21, 0x1f22, 0x1f23, 0x1f24, 0x1f25, + 0x1f26, 0x1f27, 0x1f30, 0x1f31, 0x1f32, 0x1f33, 0x1f34, 0x1f35, 0x1f36, + 0x1f37, 0x1f40, 0x1f41, 0x1f42, 0x1f43, 0x1f44, 0x1f45, 0x1f51, 0x1f53, + 0x1f55, 0x1f57, 0x1f60, 0x1f61, 0x1f62, 0x1f63, 0x1f64, 0x1f65, 0x1f66, + 0x1f67, 0x1f80, 0x1f81, 0x1f82, 0x1f83, 0x1f84, 0x1f85, 0x1f86, 0x1f87, + 0x1f90, 0x1f91, 0x1f92, 0x1f93, 0x1f94, 0x1f95, 0x1f96, 0x1f97, 0x1fa0, + 0x1fa1, 0x1fa2, 0x1fa3, 0x1fa4, 0x1fa5, 0x1fa6, 0x1fa7, 0x1fb0, 0x1fb1, + 0x1f70, 0x1f71, 0x1fb3, 0x1f72, 0x1f73, 0x1f74, 0x1f75, 0x1fc3, 0x1fd0, + 0x1fd1, 0x1f76, 0x1f77, 0x1fe0, 0x1fe1, 0x1f7a, 0x1f7b, 0x1fe5, 0x1f78, + 0x1f79, 0x1f7c, 0x1f7d, 0x1ff3, 0x3c9, 0x6b, 0xe5, 0x214e, 0x2170, + 0x2171, 0x2172, 0x2173, 0x2174, 0x2175, 0x2176, 0x2177, 0x2178, 0x2179, + 0x217a, 0x217b, 0x217c, 0x217d, 0x217e, 0x217f, 0x2184, 0x24d0, 0x24d1, + 0x24d2, 0x24d3, 0x24d4, 0x24d5, 0x24d6, 0x24d7, 0x24d8, 0x24d9, 0x24da, + 0x24db, 0x24dc, 0x24dd, 0x24de, 0x24df, 0x24e0, 0x24e1, 0x24e2, 0x24e3, + 0x24e4, 0x24e5, 0x24e6, 0x24e7, 0x24e8, 0x24e9, 0x2c30, 0x2c31, 0x2c32, + 0x2c33, 0x2c34, 0x2c35, 0x2c36, 0x2c37, 0x2c38, 0x2c39, 0x2c3a, 0x2c3b, + 0x2c3c, 0x2c3d, 0x2c3e, 0x2c3f, 0x2c40, 0x2c41, 0x2c42, 0x2c43, 0x2c44, + 0x2c45, 0x2c46, 0x2c47, 0x2c48, 0x2c49, 0x2c4a, 0x2c4b, 0x2c4c, 0x2c4d, + 0x2c4e, 0x2c4f, 0x2c50, 0x2c51, 0x2c52, 0x2c53, 0x2c54, 0x2c55, 0x2c56, + 0x2c57, 0x2c58, 0x2c59, 0x2c5a, 0x2c5b, 0x2c5c, 0x2c5d, 0x2c5e, 0x2c61, + 0x26b, 0x1d7d, 0x27d, 0x2c68, 0x2c6a, 0x2c6c, 0x251, 0x271, 0x250, + 0x252, 0x2c73, 0x2c76, 0x23f, 0x240, 0x2c81, 0x2c83, 0x2c85, 0x2c87, + 0x2c89, 0x2c8b, 0x2c8d, 0x2c8f, 0x2c91, 0x2c93, 0x2c95, 0x2c97, 0x2c99, + 0x2c9b, 0x2c9d, 0x2c9f, 0x2ca1, 0x2ca3, 0x2ca5, 0x2ca7, 0x2ca9, 0x2cab, + 0x2cad, 0x2caf, 0x2cb1, 0x2cb3, 0x2cb5, 0x2cb7, 0x2cb9, 0x2cbb, 0x2cbd, + 0x2cbf, 0x2cc1, 0x2cc3, 0x2cc5, 0x2cc7, 0x2cc9, 0x2ccb, 0x2ccd, 0x2ccf, + 0x2cd1, 0x2cd3, 0x2cd5, 0x2cd7, 0x2cd9, 0x2cdb, 0x2cdd, 0x2cdf, 0x2ce1, + 0x2ce3, 0x2cec, 0x2cee, 0x2cf3, 0xa641, 0xa643, 0xa645, 0xa647, 0xa649, + 0xa64b, 0xa64d, 0xa64f, 0xa651, 0xa653, 0xa655, 0xa657, 0xa659, 0xa65b, + 0xa65d, 0xa65f, 0xa661, 0xa663, 0xa665, 0xa667, 0xa669, 0xa66b, 0xa66d, + 0xa681, 0xa683, 0xa685, 0xa687, 0xa689, 0xa68b, 0xa68d, 0xa68f, 0xa691, + 0xa693, 0xa695, 0xa697, 0xa723, 0xa725, 0xa727, 0xa729, 0xa72b, 0xa72d, + 0xa72f, 0xa733, 0xa735, 0xa737, 0xa739, 0xa73b, 0xa73d, 0xa73f, 0xa741, + 0xa743, 0xa745, 0xa747, 0xa749, 0xa74b, 0xa74d, 0xa74f, 0xa751, 0xa753, + 0xa755, 0xa757, 0xa759, 0xa75b, 0xa75d, 0xa75f, 0xa761, 0xa763, 0xa765, + 0xa767, 0xa769, 0xa76b, 0xa76d, 0xa76f, 0xa77a, 0xa77c, 0x1d79, 0xa77f, + 0xa781, 0xa783, 0xa785, 0xa787, 0xa78c, 0x265, 0xa791, 0xa793, 0xa7a1, + 0xa7a3, 0xa7a5, 0xa7a7, 0xa7a9, 0x266, 0xff41, 0xff42, 0xff43, 0xff44, + 0xff45, 0xff46, 0xff47, 0xff48, 0xff49, 0xff4a, 0xff4b, 0xff4c, 0xff4d, + 0xff4e, 0xff4f, 0xff50, 0xff51, 0xff52, 0xff53, 0xff54, 0xff55, 0xff56, + 0xff57, 0xff58, 0xff59, 0xff5a, 0x10428, 0x10429, 0x1042a, 0x1042b, + 0x1042c, 0x1042d, 0x1042e, 0x1042f, 0x10430, 0x10431, 0x10432, 0x10433, + 0x10434, 0x10435, 0x10436, 0x10437, 0x10438, 0x10439, 0x1043a, 0x1043b, + 0x1043c, 0x1043d, 0x1043e, 0x1043f, 0x10440, 0x10441, 0x10442, 0x10443, + 0x10444, 0x10445, 0x10446, 0x10447, 0x10448, 0x10449, 0x1044a, 0x1044b, + 0x1044c, 0x1044d, 0x1044e, 0x1044f, 0xdf, 0x2000069, 0x307, 0xfb00, + 0xfb01, 0xfb02, 0xfb03, 0xfb04, 0xfb05, 0xfb06, 0x587, 0xfb13, 0xfb14, + 0xfb15, 0xfb16, 0xfb17, 0x149, 0x390, 0x3b0, 0x1f0, 0x1e96, 0x1e97, + 0x1e98, 0x1e99, 0x1e9a, 0x1f50, 0x1f52, 0x1f54, 0x1f56, 0x1fb6, 0x1fc6, + 0x1fd2, 0x1fd3, 0x1fd6, 0x1fd7, 0x1fe2, 0x1fe3, 0x1fe4, 0x1fe6, 0x1fe7, + 0x1ff6, 0x1f80, 0x1f81, 0x1f82, 0x1f83, 0x1f84, 0x1f85, 0x1f86, 0x1f87, + 0x1f80, 0x1f81, 0x1f82, 0x1f83, 0x1f84, 0x1f85, 0x1f86, 0x1f87, 0x1f90, + 0x1f91, 0x1f92, 0x1f93, 0x1f94, 0x1f95, 0x1f96, 0x1f97, 0x1f90, 0x1f91, + 0x1f92, 0x1f93, 0x1f94, 0x1f95, 0x1f96, 0x1f97, 0x1fa0, 0x1fa1, 0x1fa2, + 0x1fa3, 0x1fa4, 0x1fa5, 0x1fa6, 0x1fa7, 0x1fa0, 0x1fa1, 0x1fa2, 0x1fa3, + 0x1fa4, 0x1fa5, 0x1fa6, 0x1fa7, 0x1fb3, 0x1fb3, 0x1fc3, 0x1fc3, 0x1ff3, + 0x1ff3, 0x1fb2, 0x1fb4, 0x1fc2, 0x1fc4, 0x1ff2, 0x1ff4, 0x1fb7, 0x1fc7, 0x1ff7 + ]; + return t; +} + +_IUA toTitleTable() +{ + static _IUA t = [ + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, + 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x39c, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, + 0xd4, 0xd5, 0xd6, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0x178, + 0x100, 0x102, 0x104, 0x106, 0x108, 0x10a, 0x10c, 0x10e, 0x110, 0x112, + 0x114, 0x116, 0x118, 0x11a, 0x11c, 0x11e, 0x120, 0x122, 0x124, 0x126, + 0x128, 0x12a, 0x12c, 0x12e, 0x49, 0x132, 0x134, 0x136, 0x139, 0x13b, + 0x13d, 0x13f, 0x141, 0x143, 0x145, 0x147, 0x14a, 0x14c, 0x14e, 0x150, + 0x152, 0x154, 0x156, 0x158, 0x15a, 0x15c, 0x15e, 0x160, 0x162, 0x164, + 0x166, 0x168, 0x16a, 0x16c, 0x16e, 0x170, 0x172, 0x174, 0x176, 0x179, + 0x17b, 0x17d, 0x53, 0x243, 0x182, 0x184, 0x187, 0x18b, 0x191, 0x1f6, + 0x198, 0x23d, 0x220, 0x1a0, 0x1a2, 0x1a4, 0x1a7, 0x1ac, 0x1af, 0x1b3, + 0x1b5, 0x1b8, 0x1bc, 0x1f7, 0x1c5, 0x1c5, 0x1c5, 0x1c8, 0x1c8, 0x1c8, + 0x1cb, 0x1cb, 0x1cb, 0x1cd, 0x1cf, 0x1d1, 0x1d3, 0x1d5, 0x1d7, 0x1d9, + 0x1db, 0x18e, 0x1de, 0x1e0, 0x1e2, 0x1e4, 0x1e6, 0x1e8, 0x1ea, 0x1ec, + 0x1ee, 0x1f2, 0x1f2, 0x1f2, 0x1f4, 0x1f8, 0x1fa, 0x1fc, 0x1fe, 0x200, + 0x202, 0x204, 0x206, 0x208, 0x20a, 0x20c, 0x20e, 0x210, 0x212, 0x214, + 0x216, 0x218, 0x21a, 0x21c, 0x21e, 0x222, 0x224, 0x226, 0x228, 0x22a, + 0x22c, 0x22e, 0x230, 0x232, 0x23b, 0x2c7e, 0x2c7f, 0x241, 0x246, 0x248, + 0x24a, 0x24c, 0x24e, 0x2c6f, 0x2c6d, 0x2c70, 0x181, 0x186, 0x189, + 0x18a, 0x18f, 0x190, 0x193, 0x194, 0xa78d, 0xa7aa, 0x197, 0x196, + 0x2c62, 0x19c, 0x2c6e, 0x19d, 0x19f, 0x2c64, 0x1a6, 0x1a9, 0x1ae, + 0x244, 0x1b1, 0x1b2, 0x245, 0x1b7, 0x399, 0x370, 0x372, 0x376, 0x3fd, + 0x3fe, 0x3ff, 0x386, 0x388, 0x389, 0x38a, 0x391, 0x392, 0x393, 0x394, + 0x395, 0x396, 0x397, 0x398, 0x399, 0x39a, 0x39b, 0x39c, 0x39d, 0x39e, + 0x39f, 0x3a0, 0x3a1, 0x3a3, 0x3a3, 0x3a4, 0x3a5, 0x3a6, 0x3a7, 0x3a8, + 0x3a9, 0x3aa, 0x3ab, 0x38c, 0x38e, 0x38f, 0x392, 0x398, 0x3a6, 0x3a0, + 0x3cf, 0x3d8, 0x3da, 0x3dc, 0x3de, 0x3e0, 0x3e2, 0x3e4, 0x3e6, 0x3e8, + 0x3ea, 0x3ec, 0x3ee, 0x39a, 0x3a1, 0x3f9, 0x395, 0x3f7, 0x3fa, 0x410, + 0x411, 0x412, 0x413, 0x414, 0x415, 0x416, 0x417, 0x418, 0x419, 0x41a, + 0x41b, 0x41c, 0x41d, 0x41e, 0x41f, 0x420, 0x421, 0x422, 0x423, 0x424, + 0x425, 0x426, 0x427, 0x428, 0x429, 0x42a, 0x42b, 0x42c, 0x42d, 0x42e, + 0x42f, 0x400, 0x401, 0x402, 0x403, 0x404, 0x405, 0x406, 0x407, 0x408, + 0x409, 0x40a, 0x40b, 0x40c, 0x40d, 0x40e, 0x40f, 0x460, 0x462, 0x464, + 0x466, 0x468, 0x46a, 0x46c, 0x46e, 0x470, 0x472, 0x474, 0x476, 0x478, + 0x47a, 0x47c, 0x47e, 0x480, 0x48a, 0x48c, 0x48e, 0x490, 0x492, 0x494, + 0x496, 0x498, 0x49a, 0x49c, 0x49e, 0x4a0, 0x4a2, 0x4a4, 0x4a6, 0x4a8, + 0x4aa, 0x4ac, 0x4ae, 0x4b0, 0x4b2, 0x4b4, 0x4b6, 0x4b8, 0x4ba, 0x4bc, + 0x4be, 0x4c1, 0x4c3, 0x4c5, 0x4c7, 0x4c9, 0x4cb, 0x4cd, 0x4c0, 0x4d0, + 0x4d2, 0x4d4, 0x4d6, 0x4d8, 0x4da, 0x4dc, 0x4de, 0x4e0, 0x4e2, 0x4e4, + 0x4e6, 0x4e8, 0x4ea, 0x4ec, 0x4ee, 0x4f0, 0x4f2, 0x4f4, 0x4f6, 0x4f8, + 0x4fa, 0x4fc, 0x4fe, 0x500, 0x502, 0x504, 0x506, 0x508, 0x50a, 0x50c, + 0x50e, 0x510, 0x512, 0x514, 0x516, 0x518, 0x51a, 0x51c, 0x51e, 0x520, + 0x522, 0x524, 0x526, 0x531, 0x532, 0x533, 0x534, 0x535, 0x536, 0x537, + 0x538, 0x539, 0x53a, 0x53b, 0x53c, 0x53d, 0x53e, 0x53f, 0x540, 0x541, + 0x542, 0x543, 0x544, 0x545, 0x546, 0x547, 0x548, 0x549, 0x54a, 0x54b, + 0x54c, 0x54d, 0x54e, 0x54f, 0x550, 0x551, 0x552, 0x553, 0x554, 0x555, + 0x556, 0xa77d, 0x2c63, 0x1e00, 0x1e02, 0x1e04, 0x1e06, 0x1e08, 0x1e0a, + 0x1e0c, 0x1e0e, 0x1e10, 0x1e12, 0x1e14, 0x1e16, 0x1e18, 0x1e1a, 0x1e1c, + 0x1e1e, 0x1e20, 0x1e22, 0x1e24, 0x1e26, 0x1e28, 0x1e2a, 0x1e2c, 0x1e2e, + 0x1e30, 0x1e32, 0x1e34, 0x1e36, 0x1e38, 0x1e3a, 0x1e3c, 0x1e3e, 0x1e40, + 0x1e42, 0x1e44, 0x1e46, 0x1e48, 0x1e4a, 0x1e4c, 0x1e4e, 0x1e50, 0x1e52, + 0x1e54, 0x1e56, 0x1e58, 0x1e5a, 0x1e5c, 0x1e5e, 0x1e60, 0x1e62, 0x1e64, + 0x1e66, 0x1e68, 0x1e6a, 0x1e6c, 0x1e6e, 0x1e70, 0x1e72, 0x1e74, 0x1e76, + 0x1e78, 0x1e7a, 0x1e7c, 0x1e7e, 0x1e80, 0x1e82, 0x1e84, 0x1e86, 0x1e88, + 0x1e8a, 0x1e8c, 0x1e8e, 0x1e90, 0x1e92, 0x1e94, 0x1e60, 0x1ea0, 0x1ea2, + 0x1ea4, 0x1ea6, 0x1ea8, 0x1eaa, 0x1eac, 0x1eae, 0x1eb0, 0x1eb2, 0x1eb4, + 0x1eb6, 0x1eb8, 0x1eba, 0x1ebc, 0x1ebe, 0x1ec0, 0x1ec2, 0x1ec4, 0x1ec6, + 0x1ec8, 0x1eca, 0x1ecc, 0x1ece, 0x1ed0, 0x1ed2, 0x1ed4, 0x1ed6, 0x1ed8, + 0x1eda, 0x1edc, 0x1ede, 0x1ee0, 0x1ee2, 0x1ee4, 0x1ee6, 0x1ee8, 0x1eea, + 0x1eec, 0x1eee, 0x1ef0, 0x1ef2, 0x1ef4, 0x1ef6, 0x1ef8, 0x1efa, 0x1efc, + 0x1efe, 0x1f08, 0x1f09, 0x1f0a, 0x1f0b, 0x1f0c, 0x1f0d, 0x1f0e, 0x1f0f, + 0x1f18, 0x1f19, 0x1f1a, 0x1f1b, 0x1f1c, 0x1f1d, 0x1f28, 0x1f29, 0x1f2a, + 0x1f2b, 0x1f2c, 0x1f2d, 0x1f2e, 0x1f2f, 0x1f38, 0x1f39, 0x1f3a, 0x1f3b, + 0x1f3c, 0x1f3d, 0x1f3e, 0x1f3f, 0x1f48, 0x1f49, 0x1f4a, 0x1f4b, 0x1f4c, + 0x1f4d, 0x1f59, 0x1f5b, 0x1f5d, 0x1f5f, 0x1f68, 0x1f69, 0x1f6a, 0x1f6b, + 0x1f6c, 0x1f6d, 0x1f6e, 0x1f6f, 0x1fba, 0x1fbb, 0x1fc8, 0x1fc9, 0x1fca, + 0x1fcb, 0x1fda, 0x1fdb, 0x1ff8, 0x1ff9, 0x1fea, 0x1feb, 0x1ffa, 0x1ffb, + 0x1f88, 0x1f89, 0x1f8a, 0x1f8b, 0x1f8c, 0x1f8d, 0x1f8e, 0x1f8f, 0x1f98, + 0x1f99, 0x1f9a, 0x1f9b, 0x1f9c, 0x1f9d, 0x1f9e, 0x1f9f, 0x1fa8, 0x1fa9, + 0x1faa, 0x1fab, 0x1fac, 0x1fad, 0x1fae, 0x1faf, 0x1fb8, 0x1fb9, 0x1fbc, + 0x399, 0x1fcc, 0x1fd8, 0x1fd9, 0x1fe8, 0x1fe9, 0x1fec, 0x1ffc, 0x2132, + 0x2160, 0x2161, 0x2162, 0x2163, 0x2164, 0x2165, 0x2166, 0x2167, 0x2168, + 0x2169, 0x216a, 0x216b, 0x216c, 0x216d, 0x216e, 0x216f, 0x2183, 0x24b6, + 0x24b7, 0x24b8, 0x24b9, 0x24ba, 0x24bb, 0x24bc, 0x24bd, 0x24be, 0x24bf, + 0x24c0, 0x24c1, 0x24c2, 0x24c3, 0x24c4, 0x24c5, 0x24c6, 0x24c7, 0x24c8, + 0x24c9, 0x24ca, 0x24cb, 0x24cc, 0x24cd, 0x24ce, 0x24cf, 0x2c00, 0x2c01, + 0x2c02, 0x2c03, 0x2c04, 0x2c05, 0x2c06, 0x2c07, 0x2c08, 0x2c09, 0x2c0a, + 0x2c0b, 0x2c0c, 0x2c0d, 0x2c0e, 0x2c0f, 0x2c10, 0x2c11, 0x2c12, 0x2c13, + 0x2c14, 0x2c15, 0x2c16, 0x2c17, 0x2c18, 0x2c19, 0x2c1a, 0x2c1b, 0x2c1c, + 0x2c1d, 0x2c1e, 0x2c1f, 0x2c20, 0x2c21, 0x2c22, 0x2c23, 0x2c24, 0x2c25, + 0x2c26, 0x2c27, 0x2c28, 0x2c29, 0x2c2a, 0x2c2b, 0x2c2c, 0x2c2d, 0x2c2e, + 0x2c60, 0x23a, 0x23e, 0x2c67, 0x2c69, 0x2c6b, 0x2c72, 0x2c75, 0x2c80, + 0x2c82, 0x2c84, 0x2c86, 0x2c88, 0x2c8a, 0x2c8c, 0x2c8e, 0x2c90, 0x2c92, + 0x2c94, 0x2c96, 0x2c98, 0x2c9a, 0x2c9c, 0x2c9e, 0x2ca0, 0x2ca2, 0x2ca4, + 0x2ca6, 0x2ca8, 0x2caa, 0x2cac, 0x2cae, 0x2cb0, 0x2cb2, 0x2cb4, 0x2cb6, + 0x2cb8, 0x2cba, 0x2cbc, 0x2cbe, 0x2cc0, 0x2cc2, 0x2cc4, 0x2cc6, 0x2cc8, + 0x2cca, 0x2ccc, 0x2cce, 0x2cd0, 0x2cd2, 0x2cd4, 0x2cd6, 0x2cd8, 0x2cda, + 0x2cdc, 0x2cde, 0x2ce0, 0x2ce2, 0x2ceb, 0x2ced, 0x2cf2, 0x10a0, 0x10a1, + 0x10a2, 0x10a3, 0x10a4, 0x10a5, 0x10a6, 0x10a7, 0x10a8, 0x10a9, 0x10aa, + 0x10ab, 0x10ac, 0x10ad, 0x10ae, 0x10af, 0x10b0, 0x10b1, 0x10b2, 0x10b3, + 0x10b4, 0x10b5, 0x10b6, 0x10b7, 0x10b8, 0x10b9, 0x10ba, 0x10bb, 0x10bc, + 0x10bd, 0x10be, 0x10bf, 0x10c0, 0x10c1, 0x10c2, 0x10c3, 0x10c4, 0x10c5, + 0x10c7, 0x10cd, 0xa640, 0xa642, 0xa644, 0xa646, 0xa648, 0xa64a, 0xa64c, + 0xa64e, 0xa650, 0xa652, 0xa654, 0xa656, 0xa658, 0xa65a, 0xa65c, 0xa65e, + 0xa660, 0xa662, 0xa664, 0xa666, 0xa668, 0xa66a, 0xa66c, 0xa680, 0xa682, + 0xa684, 0xa686, 0xa688, 0xa68a, 0xa68c, 0xa68e, 0xa690, 0xa692, 0xa694, + 0xa696, 0xa722, 0xa724, 0xa726, 0xa728, 0xa72a, 0xa72c, 0xa72e, 0xa732, + 0xa734, 0xa736, 0xa738, 0xa73a, 0xa73c, 0xa73e, 0xa740, 0xa742, 0xa744, + 0xa746, 0xa748, 0xa74a, 0xa74c, 0xa74e, 0xa750, 0xa752, 0xa754, 0xa756, + 0xa758, 0xa75a, 0xa75c, 0xa75e, 0xa760, 0xa762, 0xa764, 0xa766, 0xa768, + 0xa76a, 0xa76c, 0xa76e, 0xa779, 0xa77b, 0xa77e, 0xa780, 0xa782, 0xa784, + 0xa786, 0xa78b, 0xa790, 0xa792, 0xa7a0, 0xa7a2, 0xa7a4, 0xa7a6, 0xa7a8, + 0xff21, 0xff22, 0xff23, 0xff24, 0xff25, 0xff26, 0xff27, 0xff28, 0xff29, + 0xff2a, 0xff2b, 0xff2c, 0xff2d, 0xff2e, 0xff2f, 0xff30, 0xff31, 0xff32, + 0xff33, 0xff34, 0xff35, 0xff36, 0xff37, 0xff38, 0xff39, 0xff3a, + 0x10400, 0x10401, 0x10402, 0x10403, 0x10404, 0x10405, 0x10406, 0x10407, + 0x10408, 0x10409, 0x1040a, 0x1040b, 0x1040c, 0x1040d, 0x1040e, 0x1040f, + 0x10410, 0x10411, 0x10412, 0x10413, 0x10414, 0x10415, 0x10416, 0x10417, + 0x10418, 0x10419, 0x1041a, 0x1041b, 0x1041c, 0x1041d, 0x1041e, 0x1041f, + 0x10420, 0x10421, 0x10422, 0x10423, 0x10424, 0x10425, 0x10426, 0x10427, + 0x2000053, 0x73, 0x130, 0x2000046, 0x66, 0x2000046, 0x69, 0x2000046, + 0x6c, 0x3000046, 0x66, 0x69, 0x3000046, 0x66, 0x6c, 0x2000053, 0x74, + 0x2000053, 0x74, 0x2000535, 0x582, 0x2000544, 0x576, 0x2000544, 0x565, + 0x2000544, 0x56b, 0x200054e, 0x576, 0x2000544, 0x56d, 0x20002bc, 0x4e, + 0x3000399, 0x308, 0x301, 0x30003a5, 0x308, 0x301, 0x200004a, 0x30c, + 0x2000048, 0x331, 0x2000054, 0x308, 0x2000057, 0x30a, 0x2000059, 0x30a, + 0x2000041, 0x2be, 0x20003a5, 0x313, 0x30003a5, 0x313, 0x300, 0x30003a5, + 0x313, 0x301, 0x30003a5, 0x313, 0x342, 0x2000391, 0x342, 0x2000397, + 0x342, 0x3000399, 0x308, 0x300, 0x3000399, 0x308, 0x301, 0x2000399, + 0x342, 0x3000399, 0x308, 0x342, 0x30003a5, 0x308, 0x300, 0x30003a5, + 0x308, 0x301, 0x20003a1, 0x313, 0x20003a5, 0x342, 0x30003a5, 0x308, + 0x342, 0x20003a9, 0x342, 0x1f88, 0x1f89, 0x1f8a, 0x1f8b, 0x1f8c, + 0x1f8d, 0x1f8e, 0x1f8f, 0x1f88, 0x1f89, 0x1f8a, 0x1f8b, 0x1f8c, 0x1f8d, + 0x1f8e, 0x1f8f, 0x1f98, 0x1f99, 0x1f9a, 0x1f9b, 0x1f9c, 0x1f9d, 0x1f9e, + 0x1f9f, 0x1f98, 0x1f99, 0x1f9a, 0x1f9b, 0x1f9c, 0x1f9d, 0x1f9e, 0x1f9f, + 0x1fa8, 0x1fa9, 0x1faa, 0x1fab, 0x1fac, 0x1fad, 0x1fae, 0x1faf, 0x1fa8, + 0x1fa9, 0x1faa, 0x1fab, 0x1fac, 0x1fad, 0x1fae, 0x1faf, 0x1fbc, 0x1fbc, + 0x1fcc, 0x1fcc, 0x1ffc, 0x1ffc, 0x2001fba, 0x345, 0x2000386, 0x345, + 0x2001fca, 0x345, 0x2000389, 0x345, 0x2001ffa, 0x345, 0x200038f, 0x345, + 0x3000391, 0x342, 0x345, 0x3000397, 0x342, 0x345, 0x30003a9, 0x342, 0x345 + ]; + return t; +} +} +} \ No newline at end of file diff --git a/libphobos/src/std/internal/windows/advapi32.d b/libphobos/src/std/internal/windows/advapi32.d new file mode 100644 index 0000000..b25956b --- /dev/null +++ b/libphobos/src/std/internal/windows/advapi32.d @@ -0,0 +1,69 @@ +// Written in the D programming language. + +/** + * The only purpose of this module is to do the static construction for + * std.windows.registry, to eliminate cyclic construction errors. + * + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Kenji Hara + * Source: $(PHOBOSSRC std/internal/windows/_advapi32.d) + */ +module std.internal.windows.advapi32; + +version (Windows): + +import core.sys.windows.windows; + +version (GNU) {} +else pragma(lib, "advapi32.lib"); + +immutable bool isWow64; + +shared static this() +{ + // WOW64 is the x86 emulator that allows 32-bit Windows-based applications to run seamlessly on 64-bit Windows + // IsWow64Process Function - Minimum supported client - Windows Vista, Windows XP with SP2 + alias fptr_t = extern(Windows) BOOL function(HANDLE, PBOOL); + auto hKernel = GetModuleHandleA("kernel32"); + auto IsWow64Process = cast(fptr_t) GetProcAddress(hKernel, "IsWow64Process"); + BOOL bIsWow64; + isWow64 = IsWow64Process && IsWow64Process(GetCurrentProcess(), &bIsWow64) && bIsWow64; +} + +HMODULE hAdvapi32 = null; +extern (Windows) +{ + LONG function(in HKEY hkey, in LPCWSTR lpSubKey, in REGSAM samDesired, in DWORD reserved) pRegDeleteKeyExW; +} + +void loadAdvapi32() +{ + if (!hAdvapi32) + { + hAdvapi32 = LoadLibraryA("Advapi32.dll"); + if (!hAdvapi32) + throw new Exception(`LoadLibraryA("Advapi32.dll")`); + + pRegDeleteKeyExW = cast(typeof(pRegDeleteKeyExW)) GetProcAddress(hAdvapi32 , "RegDeleteKeyExW"); + if (!pRegDeleteKeyExW) + throw new Exception(`GetProcAddress(hAdvapi32 , "RegDeleteKeyExW")`); + } +} + +// It will free Advapi32.dll, which may be loaded for RegDeleteKeyEx function +private void freeAdvapi32() +{ + if (hAdvapi32) + { + if (!FreeLibrary(hAdvapi32)) + throw new Exception(`FreeLibrary("Advapi32.dll")`); + hAdvapi32 = null; + + pRegDeleteKeyExW = null; + } +} + +static ~this() +{ + freeAdvapi32(); +} diff --git a/libphobos/src/std/json.d b/libphobos/src/std/json.d new file mode 100644 index 0000000..fd6cf41 --- /dev/null +++ b/libphobos/src/std/json.d @@ -0,0 +1,1859 @@ +// Written in the D programming language. + +/** +JavaScript Object Notation + +Copyright: Copyright Jeremie Pelletier 2008 - 2009. +License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). +Authors: Jeremie Pelletier, David Herberth +References: $(LINK http://json.org/) +Source: $(PHOBOSSRC std/_json.d) +*/ +/* + Copyright Jeremie Pelletier 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) +*/ +module std.json; + +import std.array; +import std.conv; +import std.range.primitives; +import std.traits; + +/// +@system unittest +{ + import std.conv : to; + + // parse a file or string of json into a usable structure + string s = `{ "language": "D", "rating": 3.5, "code": "42" }`; + JSONValue j = parseJSON(s); + // j and j["language"] return JSONValue, + // j["language"].str returns a string + assert(j["language"].str == "D"); + assert(j["rating"].floating == 3.5); + + // check a type + long x; + if (const(JSONValue)* code = "code" in j) + { + if (code.type() == JSON_TYPE.INTEGER) + x = code.integer; + else + x = to!int(code.str); + } + + // create a json struct + JSONValue jj = [ "language": "D" ]; + // rating doesnt exist yet, so use .object to assign + jj.object["rating"] = JSONValue(3.5); + // create an array to assign to list + jj.object["list"] = JSONValue( ["a", "b", "c"] ); + // list already exists, so .object optional + jj["list"].array ~= JSONValue("D"); + + string jjStr = `{"language":"D","list":["a","b","c","D"],"rating":3.5}`; + assert(jj.toString == jjStr); +} + +/** +String literals used to represent special float values within JSON strings. +*/ +enum JSONFloatLiteral : string +{ + nan = "NaN", /// string representation of floating-point NaN + inf = "Infinite", /// string representation of floating-point Infinity + negativeInf = "-Infinite", /// string representation of floating-point negative Infinity +} + +/** +Flags that control how json is encoded and parsed. +*/ +enum JSONOptions +{ + none, /// standard parsing + specialFloatLiterals = 0x1, /// encode NaN and Inf float values as strings + escapeNonAsciiChars = 0x2, /// encode non ascii characters with an unicode escape sequence + doNotEscapeSlashes = 0x4, /// do not escape slashes ('/') +} + +/** +JSON type enumeration +*/ +enum JSON_TYPE : byte +{ + /// Indicates the type of a $(D JSONValue). + NULL, + STRING, /// ditto + INTEGER, /// ditto + UINTEGER,/// ditto + FLOAT, /// ditto + OBJECT, /// ditto + ARRAY, /// ditto + TRUE, /// ditto + FALSE /// ditto +} + +/** +JSON value node +*/ +struct JSONValue +{ + import std.exception : enforceEx, enforce; + + union Store + { + string str; + long integer; + ulong uinteger; + double floating; + JSONValue[string] object; + JSONValue[] array; + } + private Store store; + private JSON_TYPE type_tag; + + /** + Returns the JSON_TYPE of the value stored in this structure. + */ + @property JSON_TYPE type() const pure nothrow @safe @nogc + { + return type_tag; + } + /// + @safe unittest + { + string s = "{ \"language\": \"D\" }"; + JSONValue j = parseJSON(s); + assert(j.type == JSON_TYPE.OBJECT); + assert(j["language"].type == JSON_TYPE.STRING); + } + + /*** + * Value getter/setter for $(D JSON_TYPE.STRING). + * Throws: $(D JSONException) for read access if $(D type) is not + * $(D JSON_TYPE.STRING). + */ + @property string str() const pure @trusted + { + enforce!JSONException(type == JSON_TYPE.STRING, + "JSONValue is not a string"); + return store.str; + } + /// ditto + @property string str(string v) pure nothrow @nogc @safe + { + assign(v); + return v; + } + /// + @safe unittest + { + JSONValue j = [ "language": "D" ]; + + // get value + assert(j["language"].str == "D"); + + // change existing key to new string + j["language"].str = "Perl"; + assert(j["language"].str == "Perl"); + } + + /*** + * Value getter/setter for $(D JSON_TYPE.INTEGER). + * Throws: $(D JSONException) for read access if $(D type) is not + * $(D JSON_TYPE.INTEGER). + */ + @property inout(long) integer() inout pure @safe + { + enforce!JSONException(type == JSON_TYPE.INTEGER, + "JSONValue is not an integer"); + return store.integer; + } + /// ditto + @property long integer(long v) pure nothrow @safe @nogc + { + assign(v); + return store.integer; + } + + /*** + * Value getter/setter for $(D JSON_TYPE.UINTEGER). + * Throws: $(D JSONException) for read access if $(D type) is not + * $(D JSON_TYPE.UINTEGER). + */ + @property inout(ulong) uinteger() inout pure @safe + { + enforce!JSONException(type == JSON_TYPE.UINTEGER, + "JSONValue is not an unsigned integer"); + return store.uinteger; + } + /// ditto + @property ulong uinteger(ulong v) pure nothrow @safe @nogc + { + assign(v); + return store.uinteger; + } + + /*** + * Value getter/setter for $(D JSON_TYPE.FLOAT). Note that despite + * the name, this is a $(B 64)-bit `double`, not a 32-bit `float`. + * Throws: $(D JSONException) for read access if $(D type) is not + * $(D JSON_TYPE.FLOAT). + */ + @property inout(double) floating() inout pure @safe + { + enforce!JSONException(type == JSON_TYPE.FLOAT, + "JSONValue is not a floating type"); + return store.floating; + } + /// ditto + @property double floating(double v) pure nothrow @safe @nogc + { + assign(v); + return store.floating; + } + + /*** + * Value getter/setter for $(D JSON_TYPE.OBJECT). + * Throws: $(D JSONException) for read access if $(D type) is not + * $(D JSON_TYPE.OBJECT). + * Note: this is @system because of the following pattern: + --- + auto a = &(json.object()); + json.uinteger = 0; // overwrite AA pointer + (*a)["hello"] = "world"; // segmentation fault + --- + */ + @property ref inout(JSONValue[string]) object() inout pure @system + { + enforce!JSONException(type == JSON_TYPE.OBJECT, + "JSONValue is not an object"); + return store.object; + } + /// ditto + @property JSONValue[string] object(JSONValue[string] v) pure nothrow @nogc @safe + { + assign(v); + return v; + } + + /*** + * Value getter for $(D JSON_TYPE.OBJECT). + * Unlike $(D object), this retrieves the object by value and can be used in @safe code. + * + * A caveat is that, if the returned value is null, modifications will not be visible: + * --- + * JSONValue json; + * json.object = null; + * json.objectNoRef["hello"] = JSONValue("world"); + * assert("hello" !in json.object); + * --- + * + * Throws: $(D JSONException) for read access if $(D type) is not + * $(D JSON_TYPE.OBJECT). + */ + @property inout(JSONValue[string]) objectNoRef() inout pure @trusted + { + enforce!JSONException(type == JSON_TYPE.OBJECT, + "JSONValue is not an object"); + return store.object; + } + + /*** + * Value getter/setter for $(D JSON_TYPE.ARRAY). + * Throws: $(D JSONException) for read access if $(D type) is not + * $(D JSON_TYPE.ARRAY). + * Note: this is @system because of the following pattern: + --- + auto a = &(json.array()); + json.uinteger = 0; // overwrite array pointer + (*a)[0] = "world"; // segmentation fault + --- + */ + @property ref inout(JSONValue[]) array() inout pure @system + { + enforce!JSONException(type == JSON_TYPE.ARRAY, + "JSONValue is not an array"); + return store.array; + } + /// ditto + @property JSONValue[] array(JSONValue[] v) pure nothrow @nogc @safe + { + assign(v); + return v; + } + + /*** + * Value getter for $(D JSON_TYPE.ARRAY). + * Unlike $(D array), this retrieves the array by value and can be used in @safe code. + * + * A caveat is that, if you append to the returned array, the new values aren't visible in the + * JSONValue: + * --- + * JSONValue json; + * json.array = [JSONValue("hello")]; + * json.arrayNoRef ~= JSONValue("world"); + * assert(json.array.length == 1); + * --- + * + * Throws: $(D JSONException) for read access if $(D type) is not + * $(D JSON_TYPE.ARRAY). + */ + @property inout(JSONValue[]) arrayNoRef() inout pure @trusted + { + enforce!JSONException(type == JSON_TYPE.ARRAY, + "JSONValue is not an array"); + return store.array; + } + + /// Test whether the type is $(D JSON_TYPE.NULL) + @property bool isNull() const pure nothrow @safe @nogc + { + return type == JSON_TYPE.NULL; + } + + private void assign(T)(T arg) @safe + { + static if (is(T : typeof(null))) + { + type_tag = JSON_TYPE.NULL; + } + else static if (is(T : string)) + { + type_tag = JSON_TYPE.STRING; + string t = arg; + () @trusted { store.str = t; }(); + } + else static if (isSomeString!T) // issue 15884 + { + type_tag = JSON_TYPE.STRING; + // FIXME: std.array.array(Range) is not deduced as 'pure' + () @trusted { + import std.utf : byUTF; + store.str = cast(immutable)(arg.byUTF!char.array); + }(); + } + else static if (is(T : bool)) + { + type_tag = arg ? JSON_TYPE.TRUE : JSON_TYPE.FALSE; + } + else static if (is(T : ulong) && isUnsigned!T) + { + type_tag = JSON_TYPE.UINTEGER; + store.uinteger = arg; + } + else static if (is(T : long)) + { + type_tag = JSON_TYPE.INTEGER; + store.integer = arg; + } + else static if (isFloatingPoint!T) + { + type_tag = JSON_TYPE.FLOAT; + store.floating = arg; + } + else static if (is(T : Value[Key], Key, Value)) + { + static assert(is(Key : string), "AA key must be string"); + type_tag = JSON_TYPE.OBJECT; + static if (is(Value : JSONValue)) + { + JSONValue[string] t = arg; + () @trusted { store.object = t; }(); + } + else + { + JSONValue[string] aa; + foreach (key, value; arg) + aa[key] = JSONValue(value); + () @trusted { store.object = aa; }(); + } + } + else static if (isArray!T) + { + type_tag = JSON_TYPE.ARRAY; + static if (is(ElementEncodingType!T : JSONValue)) + { + JSONValue[] t = arg; + () @trusted { store.array = t; }(); + } + else + { + JSONValue[] new_arg = new JSONValue[arg.length]; + foreach (i, e; arg) + new_arg[i] = JSONValue(e); + () @trusted { store.array = new_arg; }(); + } + } + else static if (is(T : JSONValue)) + { + type_tag = arg.type; + store = arg.store; + } + else + { + static assert(false, text(`unable to convert type "`, T.stringof, `" to json`)); + } + } + + private void assignRef(T)(ref T arg) if (isStaticArray!T) + { + type_tag = JSON_TYPE.ARRAY; + static if (is(ElementEncodingType!T : JSONValue)) + { + store.array = arg; + } + else + { + JSONValue[] new_arg = new JSONValue[arg.length]; + foreach (i, e; arg) + new_arg[i] = JSONValue(e); + store.array = new_arg; + } + } + + /** + * Constructor for $(D JSONValue). If $(D arg) is a $(D JSONValue) + * its value and type will be copied to the new $(D JSONValue). + * Note that this is a shallow copy: if type is $(D JSON_TYPE.OBJECT) + * or $(D JSON_TYPE.ARRAY) then only the reference to the data will + * be copied. + * Otherwise, $(D arg) must be implicitly convertible to one of the + * following types: $(D typeof(null)), $(D string), $(D ulong), + * $(D long), $(D double), an associative array $(D V[K]) for any $(D V) + * and $(D K) i.e. a JSON object, any array or $(D bool). The type will + * be set accordingly. + */ + this(T)(T arg) if (!isStaticArray!T) + { + assign(arg); + } + /// Ditto + this(T)(ref T arg) if (isStaticArray!T) + { + assignRef(arg); + } + /// Ditto + this(T : JSONValue)(inout T arg) inout + { + store = arg.store; + type_tag = arg.type; + } + /// + @safe unittest + { + JSONValue j = JSONValue( "a string" ); + j = JSONValue(42); + + j = JSONValue( [1, 2, 3] ); + assert(j.type == JSON_TYPE.ARRAY); + + j = JSONValue( ["language": "D"] ); + assert(j.type == JSON_TYPE.OBJECT); + } + + void opAssign(T)(T arg) if (!isStaticArray!T && !is(T : JSONValue)) + { + assign(arg); + } + + void opAssign(T)(ref T arg) if (isStaticArray!T) + { + assignRef(arg); + } + + /*** + * Array syntax for json arrays. + * Throws: $(D JSONException) if $(D type) is not $(D JSON_TYPE.ARRAY). + */ + ref inout(JSONValue) opIndex(size_t i) inout pure @safe + { + auto a = this.arrayNoRef; + enforceEx!JSONException(i < a.length, + "JSONValue array index is out of range"); + return a[i]; + } + /// + @safe unittest + { + JSONValue j = JSONValue( [42, 43, 44] ); + assert( j[0].integer == 42 ); + assert( j[1].integer == 43 ); + } + + /*** + * Hash syntax for json objects. + * Throws: $(D JSONException) if $(D type) is not $(D JSON_TYPE.OBJECT). + */ + ref inout(JSONValue) opIndex(string k) inout pure @safe + { + auto o = this.objectNoRef; + return *enforce!JSONException(k in o, + "Key not found: " ~ k); + } + /// + @safe unittest + { + JSONValue j = JSONValue( ["language": "D"] ); + assert( j["language"].str == "D" ); + } + + /*** + * Operator sets $(D value) for element of JSON object by $(D key). + * + * If JSON value is null, then operator initializes it with object and then + * sets $(D value) for it. + * + * Throws: $(D JSONException) if $(D type) is not $(D JSON_TYPE.OBJECT) + * or $(D JSON_TYPE.NULL). + */ + void opIndexAssign(T)(auto ref T value, string key) pure + { + enforceEx!JSONException(type == JSON_TYPE.OBJECT || type == JSON_TYPE.NULL, + "JSONValue must be object or null"); + JSONValue[string] aa = null; + if (type == JSON_TYPE.OBJECT) + { + aa = this.objectNoRef; + } + + aa[key] = value; + this.object = aa; + } + /// + @safe unittest + { + JSONValue j = JSONValue( ["language": "D"] ); + j["language"].str = "Perl"; + assert( j["language"].str == "Perl" ); + } + + void opIndexAssign(T)(T arg, size_t i) pure + { + auto a = this.arrayNoRef; + enforceEx!JSONException(i < a.length, + "JSONValue array index is out of range"); + a[i] = arg; + this.array = a; + } + /// + @safe unittest + { + JSONValue j = JSONValue( ["Perl", "C"] ); + j[1].str = "D"; + assert( j[1].str == "D" ); + } + + JSONValue opBinary(string op : "~", T)(T arg) @safe + { + auto a = this.arrayNoRef; + static if (isArray!T) + { + return JSONValue(a ~ JSONValue(arg).arrayNoRef); + } + else static if (is(T : JSONValue)) + { + return JSONValue(a ~ arg.arrayNoRef); + } + else + { + static assert(false, "argument is not an array or a JSONValue array"); + } + } + + void opOpAssign(string op : "~", T)(T arg) @safe + { + auto a = this.arrayNoRef; + static if (isArray!T) + { + a ~= JSONValue(arg).arrayNoRef; + } + else static if (is(T : JSONValue)) + { + a ~= arg.arrayNoRef; + } + else + { + static assert(false, "argument is not an array or a JSONValue array"); + } + this.array = a; + } + + /** + * Support for the $(D in) operator. + * + * Tests wether a key can be found in an object. + * + * Returns: + * when found, the $(D const(JSONValue)*) that matches to the key, + * otherwise $(D null). + * + * Throws: $(D JSONException) if the right hand side argument $(D JSON_TYPE) + * is not $(D OBJECT). + */ + auto opBinaryRight(string op : "in")(string k) const @safe + { + return k in this.objectNoRef; + } + /// + @safe unittest + { + JSONValue j = [ "language": "D", "author": "walter" ]; + string a = ("author" in j).str; + } + + bool opEquals(const JSONValue rhs) const @nogc nothrow pure @safe + { + return opEquals(rhs); + } + + bool opEquals(ref const JSONValue rhs) const @nogc nothrow pure @trusted + { + // Default doesn't work well since store is a union. Compare only + // what should be in store. + // This is @trusted to remain nogc, nothrow, fast, and usable from @safe code. + if (type_tag != rhs.type_tag) return false; + + final switch (type_tag) + { + case JSON_TYPE.STRING: + return store.str == rhs.store.str; + case JSON_TYPE.INTEGER: + return store.integer == rhs.store.integer; + case JSON_TYPE.UINTEGER: + return store.uinteger == rhs.store.uinteger; + case JSON_TYPE.FLOAT: + return store.floating == rhs.store.floating; + case JSON_TYPE.OBJECT: + return store.object == rhs.store.object; + case JSON_TYPE.ARRAY: + return store.array == rhs.store.array; + case JSON_TYPE.TRUE: + case JSON_TYPE.FALSE: + case JSON_TYPE.NULL: + return true; + } + } + + /// Implements the foreach $(D opApply) interface for json arrays. + int opApply(scope int delegate(size_t index, ref JSONValue) dg) @system + { + int result; + + foreach (size_t index, ref value; array) + { + result = dg(index, value); + if (result) + break; + } + + return result; + } + + /// Implements the foreach $(D opApply) interface for json objects. + int opApply(scope int delegate(string key, ref JSONValue) dg) @system + { + enforce!JSONException(type == JSON_TYPE.OBJECT, + "JSONValue is not an object"); + int result; + + foreach (string key, ref value; object) + { + result = dg(key, value); + if (result) + break; + } + + return result; + } + + /*** + * Implicitly calls $(D toJSON) on this JSONValue. + * + * $(I options) can be used to tweak the conversion behavior. + */ + string toString(in JSONOptions options = JSONOptions.none) const @safe + { + return toJSON(this, false, options); + } + + /*** + * Implicitly calls $(D toJSON) on this JSONValue, like $(D toString), but + * also passes $(I true) as $(I pretty) argument. + * + * $(I options) can be used to tweak the conversion behavior + */ + string toPrettyString(in JSONOptions options = JSONOptions.none) const @safe + { + return toJSON(this, true, options); + } +} + +/** +Parses a serialized string and returns a tree of JSON values. +Throws: $(LREF JSONException) if the depth exceeds the max depth. +Params: + json = json-formatted string to parse + maxDepth = maximum depth of nesting allowed, -1 disables depth checking + options = enable decoding string representations of NaN/Inf as float values +*/ +JSONValue parseJSON(T)(T json, int maxDepth = -1, JSONOptions options = JSONOptions.none) +if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T)) +{ + import std.ascii : isWhite, isDigit, isHexDigit, toUpper, toLower; + import std.typecons : Yes; + JSONValue root; + root.type_tag = JSON_TYPE.NULL; + + // Avoid UTF decoding when possible, as it is unnecessary when + // processing JSON. + static if (is(T : const(char)[])) + alias Char = char; + else + alias Char = Unqual!(ElementType!T); + + if (json.empty) return root; + + int depth = -1; + Char next = 0; + int line = 1, pos = 0; + + void error(string msg) + { + throw new JSONException(msg, line, pos); + } + + Char popChar() + { + if (json.empty) error("Unexpected end of data."); + static if (is(T : const(char)[])) + { + Char c = json[0]; + json = json[1..$]; + } + else + { + Char c = json.front; + json.popFront(); + } + + if (c == '\n') + { + line++; + pos = 0; + } + else + { + pos++; + } + + return c; + } + + Char peekChar() + { + if (!next) + { + if (json.empty) return '\0'; + next = popChar(); + } + return next; + } + + void skipWhitespace() + { + while (isWhite(peekChar())) next = 0; + } + + Char getChar(bool SkipWhitespace = false)() + { + static if (SkipWhitespace) skipWhitespace(); + + Char c; + if (next) + { + c = next; + next = 0; + } + else + c = popChar(); + + return c; + } + + void checkChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c) + { + static if (SkipWhitespace) skipWhitespace(); + auto c2 = getChar(); + static if (!CaseSensitive) c2 = toLower(c2); + + if (c2 != c) error(text("Found '", c2, "' when expecting '", c, "'.")); + } + + bool testChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c) + { + static if (SkipWhitespace) skipWhitespace(); + auto c2 = peekChar(); + static if (!CaseSensitive) c2 = toLower(c2); + + if (c2 != c) return false; + + getChar(); + return true; + } + + wchar parseWChar() + { + wchar val = 0; + foreach_reverse (i; 0 .. 4) + { + auto hex = toUpper(getChar()); + if (!isHexDigit(hex)) error("Expecting hex character"); + val += (isDigit(hex) ? hex - '0' : hex - ('A' - 10)) << (4 * i); + } + return val; + } + + string parseString() + { + import std.ascii : isControl; + import std.uni : isSurrogateHi, isSurrogateLo; + import std.utf : encode, decode; + + auto str = appender!string(); + + Next: + switch (peekChar()) + { + case '"': + getChar(); + break; + + case '\\': + getChar(); + auto c = getChar(); + switch (c) + { + case '"': str.put('"'); break; + case '\\': str.put('\\'); break; + case '/': str.put('/'); break; + case 'b': str.put('\b'); break; + case 'f': str.put('\f'); break; + case 'n': str.put('\n'); break; + case 'r': str.put('\r'); break; + case 't': str.put('\t'); break; + case 'u': + wchar wc = parseWChar(); + dchar val; + // Non-BMP characters are escaped as a pair of + // UTF-16 surrogate characters (see RFC 4627). + if (isSurrogateHi(wc)) + { + wchar[2] pair; + pair[0] = wc; + if (getChar() != '\\') error("Expected escaped low surrogate after escaped high surrogate"); + if (getChar() != 'u') error("Expected escaped low surrogate after escaped high surrogate"); + pair[1] = parseWChar(); + size_t index = 0; + val = decode(pair[], index); + if (index != 2) error("Invalid escaped surrogate pair"); + } + else + if (isSurrogateLo(wc)) + error(text("Unexpected low surrogate")); + else + val = wc; + + char[4] buf; + immutable len = encode!(Yes.useReplacementDchar)(buf, val); + str.put(buf[0 .. len]); + break; + + default: + error(text("Invalid escape sequence '\\", c, "'.")); + } + goto Next; + + default: + // RFC 7159 states that control characters U+0000 through + // U+001F must not appear unescaped in a JSON string. + auto c = getChar(); + if (isControl(c)) + error("Illegal control character."); + str.put(c); + goto Next; + } + + return str.data.length ? str.data : ""; + } + + bool tryGetSpecialFloat(string str, out double val) { + switch (str) + { + case JSONFloatLiteral.nan: + val = double.nan; + return true; + case JSONFloatLiteral.inf: + val = double.infinity; + return true; + case JSONFloatLiteral.negativeInf: + val = -double.infinity; + return true; + default: + return false; + } + } + + void parseValue(ref JSONValue value) + { + depth++; + + if (maxDepth != -1 && depth > maxDepth) error("Nesting too deep."); + + auto c = getChar!true(); + + switch (c) + { + case '{': + if (testChar('}')) + { + value.object = null; + break; + } + + JSONValue[string] obj; + do + { + checkChar('"'); + string name = parseString(); + checkChar(':'); + JSONValue member; + parseValue(member); + obj[name] = member; + } + while (testChar(',')); + value.object = obj; + + checkChar('}'); + break; + + case '[': + if (testChar(']')) + { + value.type_tag = JSON_TYPE.ARRAY; + break; + } + + JSONValue[] arr; + do + { + JSONValue element; + parseValue(element); + arr ~= element; + } + while (testChar(',')); + + checkChar(']'); + value.array = arr; + break; + + case '"': + auto str = parseString(); + + // if special float parsing is enabled, check if string represents NaN/Inf + if ((options & JSONOptions.specialFloatLiterals) && + tryGetSpecialFloat(str, value.store.floating)) + { + // found a special float, its value was placed in value.store.floating + value.type_tag = JSON_TYPE.FLOAT; + break; + } + + value.type_tag = JSON_TYPE.STRING; + value.store.str = str; + break; + + case '0': .. case '9': + case '-': + auto number = appender!string(); + bool isFloat, isNegative; + + void readInteger() + { + if (!isDigit(c)) error("Digit expected"); + + Next: number.put(c); + + if (isDigit(peekChar())) + { + c = getChar(); + goto Next; + } + } + + if (c == '-') + { + number.put('-'); + c = getChar(); + isNegative = true; + } + + readInteger(); + + if (testChar('.')) + { + isFloat = true; + number.put('.'); + c = getChar(); + readInteger(); + } + if (testChar!(false, false)('e')) + { + isFloat = true; + number.put('e'); + if (testChar('+')) number.put('+'); + else if (testChar('-')) number.put('-'); + c = getChar(); + readInteger(); + } + + string data = number.data; + if (isFloat) + { + value.type_tag = JSON_TYPE.FLOAT; + value.store.floating = parse!double(data); + } + else + { + if (isNegative) + value.store.integer = parse!long(data); + else + value.store.uinteger = parse!ulong(data); + + value.type_tag = !isNegative && value.store.uinteger & (1UL << 63) ? + JSON_TYPE.UINTEGER : JSON_TYPE.INTEGER; + } + break; + + case 't': + case 'T': + value.type_tag = JSON_TYPE.TRUE; + checkChar!(false, false)('r'); + checkChar!(false, false)('u'); + checkChar!(false, false)('e'); + break; + + case 'f': + case 'F': + value.type_tag = JSON_TYPE.FALSE; + checkChar!(false, false)('a'); + checkChar!(false, false)('l'); + checkChar!(false, false)('s'); + checkChar!(false, false)('e'); + break; + + case 'n': + case 'N': + value.type_tag = JSON_TYPE.NULL; + checkChar!(false, false)('u'); + checkChar!(false, false)('l'); + checkChar!(false, false)('l'); + break; + + default: + error(text("Unexpected character '", c, "'.")); + } + + depth--; + } + + parseValue(root); + return root; +} + +@safe unittest +{ + enum issue15742objectOfObject = `{ "key1": { "key2": 1 }}`; + static assert(parseJSON(issue15742objectOfObject).type == JSON_TYPE.OBJECT); + + enum issue15742arrayOfArray = `[[1]]`; + static assert(parseJSON(issue15742arrayOfArray).type == JSON_TYPE.ARRAY); +} + +@safe unittest +{ + // Ensure we can parse and use JSON from @safe code + auto a = `{ "key1": { "key2": 1 }}`.parseJSON; + assert(a["key1"]["key2"].integer == 1); + assert(a.toString == `{"key1":{"key2":1}}`); +} + +@system unittest +{ + // Ensure we can parse JSON from a @system range. + struct Range + { + string s; + size_t index; + @system + { + bool empty() { return index >= s.length; } + void popFront() { index++; } + char front() { return s[index]; } + } + } + auto s = Range(`{ "key1": { "key2": 1 }}`); + auto json = parseJSON(s); + assert(json["key1"]["key2"].integer == 1); +} + +/** +Parses a serialized string and returns a tree of JSON values. +Throws: $(REF JSONException, std,json) if the depth exceeds the max depth. +Params: + json = json-formatted string to parse + options = enable decoding string representations of NaN/Inf as float values +*/ +JSONValue parseJSON(T)(T json, JSONOptions options) +if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T)) +{ + return parseJSON!T(json, -1, options); +} + +deprecated( + "Please use the overload that takes a ref JSONValue rather than a pointer. This overload will " + ~ "be removed in November 2017.") +string toJSON(in JSONValue* root, in bool pretty = false, in JSONOptions options = JSONOptions.none) @safe +{ + return toJSON(*root, pretty, options); +} + +/** +Takes a tree of JSON values and returns the serialized string. + +Any Object types will be serialized in a key-sorted order. + +If $(D pretty) is false no whitespaces are generated. +If $(D pretty) is true serialized string is formatted to be human-readable. +Set the $(LREF JSONOptions.specialFloatLiterals) flag is set in $(D options) to encode NaN/Infinity as strings. +*/ +string toJSON(const ref JSONValue root, in bool pretty = false, in JSONOptions options = JSONOptions.none) @safe +{ + auto json = appender!string(); + + void toStringImpl(Char)(string str) @safe + { + json.put('"'); + + foreach (Char c; str) + { + switch (c) + { + case '"': json.put("\\\""); break; + case '\\': json.put("\\\\"); break; + + case '/': + if (!(options & JSONOptions.doNotEscapeSlashes)) + json.put('\\'); + json.put('/'); + break; + + case '\b': json.put("\\b"); break; + case '\f': json.put("\\f"); break; + case '\n': json.put("\\n"); break; + case '\r': json.put("\\r"); break; + case '\t': json.put("\\t"); break; + default: + { + import std.ascii : isControl; + import std.utf : encode; + + // Make sure we do UTF decoding iff we want to + // escape Unicode characters. + assert(((options & JSONOptions.escapeNonAsciiChars) != 0) + == is(Char == dchar)); + + with (JSONOptions) if (isControl(c) || + ((options & escapeNonAsciiChars) >= escapeNonAsciiChars && c >= 0x80)) + { + // Ensure non-BMP characters are encoded as a pair + // of UTF-16 surrogate characters, as per RFC 4627. + wchar[2] wchars; // 1 or 2 UTF-16 code units + size_t wNum = encode(wchars, c); // number of UTF-16 code units + foreach (wc; wchars[0 .. wNum]) + { + json.put("\\u"); + foreach_reverse (i; 0 .. 4) + { + char ch = (wc >>> (4 * i)) & 0x0f; + ch += ch < 10 ? '0' : 'A' - 10; + json.put(ch); + } + } + } + else + { + json.put(c); + } + } + } + } + + json.put('"'); + } + + void toString(string str) @safe + { + // Avoid UTF decoding when possible, as it is unnecessary when + // processing JSON. + if (options & JSONOptions.escapeNonAsciiChars) + toStringImpl!dchar(str); + else + toStringImpl!char(str); + } + + void toValue(ref in JSONValue value, ulong indentLevel) @safe + { + void putTabs(ulong additionalIndent = 0) + { + if (pretty) + foreach (i; 0 .. indentLevel + additionalIndent) + json.put(" "); + } + void putEOL() + { + if (pretty) + json.put('\n'); + } + void putCharAndEOL(char ch) + { + json.put(ch); + putEOL(); + } + + final switch (value.type) + { + case JSON_TYPE.OBJECT: + auto obj = value.objectNoRef; + if (!obj.length) + { + json.put("{}"); + } + else + { + putCharAndEOL('{'); + bool first = true; + + void emit(R)(R names) + { + foreach (name; names) + { + auto member = obj[name]; + if (!first) + putCharAndEOL(','); + first = false; + putTabs(1); + toString(name); + json.put(':'); + if (pretty) + json.put(' '); + toValue(member, indentLevel + 1); + } + } + + import std.algorithm.sorting : sort; + // @@@BUG@@@ 14439 + // auto names = obj.keys; // aa.keys can't be called in @safe code + auto names = new string[obj.length]; + size_t i = 0; + foreach (k, v; obj) + { + names[i] = k; + i++; + } + sort(names); + emit(names); + + putEOL(); + putTabs(); + json.put('}'); + } + break; + + case JSON_TYPE.ARRAY: + auto arr = value.arrayNoRef; + if (arr.empty) + { + json.put("[]"); + } + else + { + putCharAndEOL('['); + foreach (i, el; arr) + { + if (i) + putCharAndEOL(','); + putTabs(1); + toValue(el, indentLevel + 1); + } + putEOL(); + putTabs(); + json.put(']'); + } + break; + + case JSON_TYPE.STRING: + toString(value.str); + break; + + case JSON_TYPE.INTEGER: + json.put(to!string(value.store.integer)); + break; + + case JSON_TYPE.UINTEGER: + json.put(to!string(value.store.uinteger)); + break; + + case JSON_TYPE.FLOAT: + import std.math : isNaN, isInfinity; + + auto val = value.store.floating; + + if (val.isNaN) + { + if (options & JSONOptions.specialFloatLiterals) + { + toString(JSONFloatLiteral.nan); + } + else + { + throw new JSONException( + "Cannot encode NaN. Consider passing the specialFloatLiterals flag."); + } + } + else if (val.isInfinity) + { + if (options & JSONOptions.specialFloatLiterals) + { + toString((val > 0) ? JSONFloatLiteral.inf : JSONFloatLiteral.negativeInf); + } + else + { + throw new JSONException( + "Cannot encode Infinity. Consider passing the specialFloatLiterals flag."); + } + } + else + { + import std.format : format; + // The correct formula for the number of decimal digits needed for lossless round + // trips is actually: + // ceil(log(pow(2.0, double.mant_dig - 1)) / log(10.0) + 1) == (double.dig + 2) + // Anything less will round off (1 + double.epsilon) + json.put("%.18g".format(val)); + } + break; + + case JSON_TYPE.TRUE: + json.put("true"); + break; + + case JSON_TYPE.FALSE: + json.put("false"); + break; + + case JSON_TYPE.NULL: + json.put("null"); + break; + } + } + + toValue(root, 0); + return json.data; +} + +@safe unittest // bugzilla 12897 +{ + JSONValue jv0 = JSONValue("test测试"); + assert(toJSON(jv0, false, JSONOptions.escapeNonAsciiChars) == `"test\u6D4B\u8BD5"`); + JSONValue jv00 = JSONValue("test\u6D4B\u8BD5"); + assert(toJSON(jv00, false, JSONOptions.none) == `"test测试"`); + assert(toJSON(jv0, false, JSONOptions.none) == `"test测试"`); + JSONValue jv1 = JSONValue("été"); + assert(toJSON(jv1, false, JSONOptions.escapeNonAsciiChars) == `"\u00E9t\u00E9"`); + JSONValue jv11 = JSONValue("\u00E9t\u00E9"); + assert(toJSON(jv11, false, JSONOptions.none) == `"été"`); + assert(toJSON(jv1, false, JSONOptions.none) == `"été"`); +} + +/** +Exception thrown on JSON errors +*/ +class JSONException : Exception +{ + this(string msg, int line = 0, int pos = 0) pure nothrow @safe + { + if (line) + super(text(msg, " (Line ", line, ":", pos, ")")); + else + super(msg); + } + + this(string msg, string file, size_t line) pure nothrow @safe + { + super(msg, file, line); + } +} + + +@system unittest +{ + import std.exception; + JSONValue jv = "123"; + assert(jv.type == JSON_TYPE.STRING); + assertNotThrown(jv.str); + assertThrown!JSONException(jv.integer); + assertThrown!JSONException(jv.uinteger); + assertThrown!JSONException(jv.floating); + assertThrown!JSONException(jv.object); + assertThrown!JSONException(jv.array); + assertThrown!JSONException(jv["aa"]); + assertThrown!JSONException(jv[2]); + + jv = -3; + assert(jv.type == JSON_TYPE.INTEGER); + assertNotThrown(jv.integer); + + jv = cast(uint) 3; + assert(jv.type == JSON_TYPE.UINTEGER); + assertNotThrown(jv.uinteger); + + jv = 3.0; + assert(jv.type == JSON_TYPE.FLOAT); + assertNotThrown(jv.floating); + + jv = ["key" : "value"]; + assert(jv.type == JSON_TYPE.OBJECT); + assertNotThrown(jv.object); + assertNotThrown(jv["key"]); + assert("key" in jv); + assert("notAnElement" !in jv); + assertThrown!JSONException(jv["notAnElement"]); + const cjv = jv; + assert("key" in cjv); + assertThrown!JSONException(cjv["notAnElement"]); + + foreach (string key, value; jv) + { + static assert(is(typeof(value) == JSONValue)); + assert(key == "key"); + assert(value.type == JSON_TYPE.STRING); + assertNotThrown(value.str); + assert(value.str == "value"); + } + + jv = [3, 4, 5]; + assert(jv.type == JSON_TYPE.ARRAY); + assertNotThrown(jv.array); + assertNotThrown(jv[2]); + foreach (size_t index, value; jv) + { + static assert(is(typeof(value) == JSONValue)); + assert(value.type == JSON_TYPE.INTEGER); + assertNotThrown(value.integer); + assert(index == (value.integer-3)); + } + + jv = null; + assert(jv.type == JSON_TYPE.NULL); + assert(jv.isNull); + jv = "foo"; + assert(!jv.isNull); + + jv = JSONValue("value"); + assert(jv.type == JSON_TYPE.STRING); + assert(jv.str == "value"); + + JSONValue jv2 = JSONValue("value"); + assert(jv2.type == JSON_TYPE.STRING); + assert(jv2.str == "value"); + + JSONValue jv3 = JSONValue("\u001c"); + assert(jv3.type == JSON_TYPE.STRING); + assert(jv3.str == "\u001C"); +} + +@system unittest +{ + // Bugzilla 11504 + + JSONValue jv = 1; + assert(jv.type == JSON_TYPE.INTEGER); + + jv.str = "123"; + assert(jv.type == JSON_TYPE.STRING); + assert(jv.str == "123"); + + jv.integer = 1; + assert(jv.type == JSON_TYPE.INTEGER); + assert(jv.integer == 1); + + jv.uinteger = 2u; + assert(jv.type == JSON_TYPE.UINTEGER); + assert(jv.uinteger == 2u); + + jv.floating = 1.5; + assert(jv.type == JSON_TYPE.FLOAT); + assert(jv.floating == 1.5); + + jv.object = ["key" : JSONValue("value")]; + assert(jv.type == JSON_TYPE.OBJECT); + assert(jv.object == ["key" : JSONValue("value")]); + + jv.array = [JSONValue(1), JSONValue(2), JSONValue(3)]; + assert(jv.type == JSON_TYPE.ARRAY); + assert(jv.array == [JSONValue(1), JSONValue(2), JSONValue(3)]); + + jv = true; + assert(jv.type == JSON_TYPE.TRUE); + + jv = false; + assert(jv.type == JSON_TYPE.FALSE); + + enum E{True = true} + jv = E.True; + assert(jv.type == JSON_TYPE.TRUE); +} + +@system pure unittest +{ + // Adding new json element via array() / object() directly + + JSONValue jarr = JSONValue([10]); + foreach (i; 0 .. 9) + jarr.array ~= JSONValue(i); + assert(jarr.array.length == 10); + + JSONValue jobj = JSONValue(["key" : JSONValue("value")]); + foreach (i; 0 .. 9) + jobj.object[text("key", i)] = JSONValue(text("value", i)); + assert(jobj.object.length == 10); +} + +@system pure unittest +{ + // Adding new json element without array() / object() access + + JSONValue jarr = JSONValue([10]); + foreach (i; 0 .. 9) + jarr ~= [JSONValue(i)]; + assert(jarr.array.length == 10); + + JSONValue jobj = JSONValue(["key" : JSONValue("value")]); + foreach (i; 0 .. 9) + jobj[text("key", i)] = JSONValue(text("value", i)); + assert(jobj.object.length == 10); + + // No array alias + auto jarr2 = jarr ~ [1,2,3]; + jarr2[0] = 999; + assert(jarr[0] == JSONValue(10)); +} + +@system unittest +{ + // @system because JSONValue.array is @system + import std.exception; + + // An overly simple test suite, if it can parse a serializated string and + // then use the resulting values tree to generate an identical + // serialization, both the decoder and encoder works. + + auto jsons = [ + `null`, + `true`, + `false`, + `0`, + `123`, + `-4321`, + `0.25`, + `-0.25`, + `""`, + `"hello\nworld"`, + `"\"\\\/\b\f\n\r\t"`, + `[]`, + `[12,"foo",true,false]`, + `{}`, + `{"a":1,"b":null}`, + `{"goodbye":[true,"or",false,["test",42,{"nested":{"a":23.5,"b":0.140625}}]],` + ~`"hello":{"array":[12,null,{}],"json":"is great"}}`, + ]; + + enum dbl1_844 = `1.8446744073709568`; + version (MinGW) + jsons ~= dbl1_844 ~ `e+019`; + else + jsons ~= dbl1_844 ~ `e+19`; + + JSONValue val; + string result; + foreach (json; jsons) + { + try + { + val = parseJSON(json); + enum pretty = false; + result = toJSON(val, pretty); + assert(result == json, text(result, " should be ", json)); + } + catch (JSONException e) + { + import std.stdio : writefln; + writefln(text(json, "\n", e.toString())); + } + } + + // Should be able to correctly interpret unicode entities + val = parseJSON(`"\u003C\u003E"`); + assert(toJSON(val) == "\"\<\>\""); + assert(val.to!string() == "\"\<\>\""); + val = parseJSON(`"\u0391\u0392\u0393"`); + assert(toJSON(val) == "\"\Α\Β\Γ\""); + assert(val.to!string() == "\"\Α\Β\Γ\""); + val = parseJSON(`"\u2660\u2666"`); + assert(toJSON(val) == "\"\♠\♦\""); + assert(val.to!string() == "\"\♠\♦\""); + + //0x7F is a control character (see Unicode spec) + val = parseJSON(`"\u007F"`); + assert(toJSON(val) == "\"\\u007F\""); + assert(val.to!string() == "\"\\u007F\""); + + with(parseJSON(`""`)) + assert(str == "" && str !is null); + with(parseJSON(`[]`)) + assert(!array.length); + + // Formatting + val = parseJSON(`{"a":[null,{"x":1},{},[]]}`); + assert(toJSON(val, true) == `{ + "a": [ + null, + { + "x": 1 + }, + {}, + [] + ] +}`); +} + +@safe unittest +{ + auto json = `"hello\nworld"`; + const jv = parseJSON(json); + assert(jv.toString == json); + assert(jv.toPrettyString == json); +} + +@system pure unittest +{ + // Bugzilla 12969 + + JSONValue jv; + jv["int"] = 123; + + assert(jv.type == JSON_TYPE.OBJECT); + assert("int" in jv); + assert(jv["int"].integer == 123); + + jv["array"] = [1, 2, 3, 4, 5]; + + assert(jv["array"].type == JSON_TYPE.ARRAY); + assert(jv["array"][2].integer == 3); + + jv["str"] = "D language"; + assert(jv["str"].type == JSON_TYPE.STRING); + assert(jv["str"].str == "D language"); + + jv["bool"] = false; + assert(jv["bool"].type == JSON_TYPE.FALSE); + + assert(jv.object.length == 4); + + jv = [5, 4, 3, 2, 1]; + assert( jv.type == JSON_TYPE.ARRAY ); + assert( jv[3].integer == 2 ); +} + +@safe unittest +{ + auto s = q"EOF +[ + 1, + 2, + 3, + potato +] +EOF"; + + import std.exception; + + auto e = collectException!JSONException(parseJSON(s)); + assert(e.msg == "Unexpected character 'p'. (Line 5:3)", e.msg); +} + +// handling of special float values (NaN, Inf, -Inf) +@safe unittest +{ + import std.exception : assertThrown; + import std.math : isNaN, isInfinity; + + // expected representations of NaN and Inf + enum { + nanString = '"' ~ JSONFloatLiteral.nan ~ '"', + infString = '"' ~ JSONFloatLiteral.inf ~ '"', + negativeInfString = '"' ~ JSONFloatLiteral.negativeInf ~ '"', + } + + // with the specialFloatLiterals option, encode NaN/Inf as strings + assert(JSONValue(float.nan).toString(JSONOptions.specialFloatLiterals) == nanString); + assert(JSONValue(double.infinity).toString(JSONOptions.specialFloatLiterals) == infString); + assert(JSONValue(-real.infinity).toString(JSONOptions.specialFloatLiterals) == negativeInfString); + + // without the specialFloatLiterals option, throw on encoding NaN/Inf + assertThrown!JSONException(JSONValue(float.nan).toString); + assertThrown!JSONException(JSONValue(double.infinity).toString); + assertThrown!JSONException(JSONValue(-real.infinity).toString); + + // when parsing json with specialFloatLiterals option, decode special strings as floats + JSONValue jvNan = parseJSON(nanString, JSONOptions.specialFloatLiterals); + JSONValue jvInf = parseJSON(infString, JSONOptions.specialFloatLiterals); + JSONValue jvNegInf = parseJSON(negativeInfString, JSONOptions.specialFloatLiterals); + + assert(jvNan.floating.isNaN); + assert(jvInf.floating.isInfinity && jvInf.floating > 0); + assert(jvNegInf.floating.isInfinity && jvNegInf.floating < 0); + + // when parsing json without the specialFloatLiterals option, decode special strings as strings + jvNan = parseJSON(nanString); + jvInf = parseJSON(infString); + jvNegInf = parseJSON(negativeInfString); + + assert(jvNan.str == JSONFloatLiteral.nan); + assert(jvInf.str == JSONFloatLiteral.inf); + assert(jvNegInf.str == JSONFloatLiteral.negativeInf); +} + +pure nothrow @safe @nogc unittest +{ + JSONValue testVal; + testVal = "test"; + testVal = 10; + testVal = 10u; + testVal = 1.0; + testVal = (JSONValue[string]).init; + testVal = JSONValue[].init; + testVal = null; + assert(testVal.isNull); +} + +pure nothrow @safe unittest // issue 15884 +{ + import std.typecons; + void Test(C)() { + C[] a = ['x']; + JSONValue testVal = a; + assert(testVal.type == JSON_TYPE.STRING); + testVal = a.idup; + assert(testVal.type == JSON_TYPE.STRING); + } + Test!char(); + Test!wchar(); + Test!dchar(); +} + +@safe unittest // issue 15885 +{ + enum bool realInDoublePrecision = real.mant_dig == double.mant_dig; + + static bool test(const double num0) + { + import std.math : feqrel; + const json0 = JSONValue(num0); + const num1 = to!double(toJSON(json0)); + static if (realInDoublePrecision) + return feqrel(num1, num0) >= (double.mant_dig - 1); + else + return num1 == num0; + } + + assert(test( 0.23)); + assert(test(-0.23)); + assert(test(1.223e+24)); + assert(test(23.4)); + assert(test(0.0012)); + assert(test(30738.22)); + + assert(test(1 + double.epsilon)); + assert(test(double.min_normal)); + static if (realInDoublePrecision) + assert(test(-double.max / 2)); + else + assert(test(-double.max)); + + const minSub = double.min_normal * double.epsilon; + assert(test(minSub)); + assert(test(3*minSub)); +} + +@safe unittest // issue 17555 +{ + import std.exception : assertThrown; + + assertThrown!JSONException(parseJSON("\"a\nb\"")); +} + +@safe unittest // issue 17556 +{ + auto v = JSONValue("\U0001D11E"); + auto j = toJSON(v, false, JSONOptions.escapeNonAsciiChars); + assert(j == `"\uD834\uDD1E"`); +} + +@safe unittest // issue 5904 +{ + string s = `"\uD834\uDD1E"`; + auto j = parseJSON(s); + assert(j.str == "\U0001D11E"); +} + +@safe unittest // issue 17557 +{ + assert(parseJSON("\"\xFF\"").str == "\xFF"); + assert(parseJSON("\"\U0001D11E\"").str == "\U0001D11E"); +} + +@safe unittest // issue 17553 +{ + auto v = JSONValue("\xFF"); + assert(toJSON(v) == "\"\xFF\""); +} + +@safe unittest +{ + import std.utf; + assert(parseJSON("\"\xFF\"".byChar).str == "\xFF"); + assert(parseJSON("\"\U0001D11E\"".byChar).str == "\U0001D11E"); +} + +@safe unittest // JSONOptions.doNotEscapeSlashes (issue 17587) +{ + assert(parseJSON(`"/"`).toString == `"\/"`); + assert(parseJSON(`"\/"`).toString == `"\/"`); + assert(parseJSON(`"/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`); + assert(parseJSON(`"\/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`); +} diff --git a/libphobos/src/std/math.d b/libphobos/src/std/math.d new file mode 100644 index 0000000..84cf4a7 --- /dev/null +++ b/libphobos/src/std/math.d @@ -0,0 +1,8413 @@ +// Written in the D programming language. + +/** + * Contains the elementary mathematical functions (powers, roots, + * and trigonometric functions), and low-level floating-point operations. + * Mathematical special functions are available in $(D std.mathspecial). + * +$(SCRIPT inhibitQuickIndex = 1;) + +$(DIVC quickindex, +$(BOOKTABLE , +$(TR $(TH Category) $(TH Members) ) +$(TR $(TDNW Constants) $(TD + $(MYREF E) $(MYREF PI) $(MYREF PI_2) $(MYREF PI_4) $(MYREF M_1_PI) + $(MYREF M_2_PI) $(MYREF M_2_SQRTPI) $(MYREF LN10) $(MYREF LN2) + $(MYREF LOG2) $(MYREF LOG2E) $(MYREF LOG2T) $(MYREF LOG10E) + $(MYREF SQRT2) $(MYREF SQRT1_2) +)) +$(TR $(TDNW Classics) $(TD + $(MYREF abs) $(MYREF fabs) $(MYREF sqrt) $(MYREF cbrt) $(MYREF hypot) + $(MYREF poly) $(MYREF nextPow2) $(MYREF truncPow2) +)) +$(TR $(TDNW Trigonometry) $(TD + $(MYREF sin) $(MYREF cos) $(MYREF tan) $(MYREF asin) $(MYREF acos) + $(MYREF atan) $(MYREF atan2) $(MYREF sinh) $(MYREF cosh) $(MYREF tanh) + $(MYREF asinh) $(MYREF acosh) $(MYREF atanh) $(MYREF expi) +)) +$(TR $(TDNW Rounding) $(TD + $(MYREF ceil) $(MYREF floor) $(MYREF round) $(MYREF lround) + $(MYREF trunc) $(MYREF rint) $(MYREF lrint) $(MYREF nearbyint) + $(MYREF rndtol) $(MYREF quantize) +)) +$(TR $(TDNW Exponentiation & Logarithms) $(TD + $(MYREF pow) $(MYREF exp) $(MYREF exp2) $(MYREF expm1) $(MYREF ldexp) + $(MYREF frexp) $(MYREF log) $(MYREF log2) $(MYREF log10) $(MYREF logb) + $(MYREF ilogb) $(MYREF log1p) $(MYREF scalbn) +)) +$(TR $(TDNW Modulus) $(TD + $(MYREF fmod) $(MYREF modf) $(MYREF remainder) +)) +$(TR $(TDNW Floating-point operations) $(TD + $(MYREF approxEqual) $(MYREF feqrel) $(MYREF fdim) $(MYREF fmax) + $(MYREF fmin) $(MYREF fma) $(MYREF nextDown) $(MYREF nextUp) + $(MYREF nextafter) $(MYREF NaN) $(MYREF getNaNPayload) + $(MYREF cmp) +)) +$(TR $(TDNW Introspection) $(TD + $(MYREF isFinite) $(MYREF isIdentical) $(MYREF isInfinity) $(MYREF isNaN) + $(MYREF isNormal) $(MYREF isSubnormal) $(MYREF signbit) $(MYREF sgn) + $(MYREF copysign) $(MYREF isPowerOf2) +)) +$(TR $(TDNW Complex Numbers) $(TD + $(MYREF abs) $(MYREF conj) $(MYREF sin) $(MYREF cos) $(MYREF expi) +)) +$(TR $(TDNW Hardware Control) $(TD + $(MYREF IeeeFlags) $(MYREF FloatingPointControl) +)) +) +) + + * The functionality closely follows the IEEE754-2008 standard for + * floating-point arithmetic, including the use of camelCase names rather + * than C99-style lower case names. All of these functions behave correctly + * when presented with an infinity or NaN. + * + * The following IEEE 'real' formats are currently supported: + * $(UL + * $(LI 64 bit Big-endian 'double' (eg PowerPC)) + * $(LI 128 bit Big-endian 'quadruple' (eg SPARC)) + * $(LI 64 bit Little-endian 'double' (eg x86-SSE2)) + * $(LI 80 bit Little-endian, with implied bit 'real80' (eg x87, Itanium)) + * $(LI 128 bit Little-endian 'quadruple' (not implemented on any known processor!)) + * $(LI Non-IEEE 128 bit Big-endian 'doubledouble' (eg PowerPC) has partial support) + * ) + * Unlike C, there is no global 'errno' variable. Consequently, almost all of + * these functions are pure nothrow. + * + * Status: + * The semantics and names of feqrel and approxEqual will be revised. + * + * Macros: + * TABLE_SV = + * + * $0
Special Values
+ * SVH = $(TR $(TH $1) $(TH $2)) + * SV = $(TR $(TD $1) $(TD $2)) + * TH3 = $(TR $(TH $1) $(TH $2) $(TH $3)) + * TD3 = $(TR $(TD $1) $(TD $2) $(TD $3)) + * TABLE_DOMRG = + * $(SVH Domain X, Range Y) + $(SV $1, $2) + *
+ * DOMAIN=$1 + * RANGE=$1 + + * NAN = $(RED NAN) + * SUP = $0 + * GAMMA = Γ + * THETA = θ + * INTEGRAL = ∫ + * INTEGRATE = $(BIG ∫$(SMALL $1)$2) + * POWER = $1$2 + * SUB = $1$2 + * BIGSUM = $(BIG Σ $2$(SMALL $1)) + * CHOOSE = $(BIG () $(SMALL $1)$(SMALL $2) $(BIG )) + * PLUSMN = ± + * INFIN = ∞ + * PLUSMNINF = ±∞ + * PI = π + * LT = < + * GT = > + * SQRT = √ + * HALF = ½ + * + * Copyright: Copyright Digital Mars 2000 - 2011. + * D implementations of tan, atan, atan2, exp, expm1, exp2, log, log10, log1p, + * log2, floor, ceil and lrint functions are based on the CEPHES math library, + * which is Copyright (C) 2001 Stephen L. Moshier $(LT)steve@moshier.net$(GT) + * and are incorporated herein by permission of the author. The author + * reserves the right to distribute this material elsewhere under different + * copying permissions. These modifications are distributed here under + * the following terms: + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: $(HTTP digitalmars.com, Walter Bright), Don Clugston, + * Conversion of CEPHES math library to D by Iain Buclaw and David Nadlinger + * Source: $(PHOBOSSRC std/_math.d) + */ + +/* NOTE: This file has been patched from the original DMD distribution to + * work with the GDC compiler. + */ +module std.math; + +version (Win64) +{ + version (D_InlineAsm_X86_64) + version = Win64_DMD_InlineAsm; +} + +static import core.math; +static import core.stdc.math; +static import core.stdc.fenv; +import std.traits; // CommonType, isFloatingPoint, isIntegral, isSigned, isUnsigned, Largest, Unqual + +version (LDC) +{ + import ldc.intrinsics; +} + +version (DigitalMars) +{ + version = INLINE_YL2X; // x87 has opcodes for these +} + +version (X86) version = X86_Any; +version (X86_64) version = X86_Any; +version (PPC) version = PPC_Any; +version (PPC64) version = PPC_Any; +version (MIPS32) version = MIPS_Any; +version (MIPS64) version = MIPS_Any; +version (AArch64) version = ARM_Any; +version (ARM) version = ARM_Any; + +version (D_InlineAsm_X86) +{ + version = InlineAsm_X86_Any; +} +else version (D_InlineAsm_X86_64) +{ + version = InlineAsm_X86_Any; +} + +version (X86_64) version = StaticallyHaveSSE; +version (X86) version (OSX) version = StaticallyHaveSSE; + +version (StaticallyHaveSSE) +{ + private enum bool haveSSE = true; +} +else +{ + static import core.cpuid; + private alias haveSSE = core.cpuid.sse; +} + +version (unittest) +{ + import core.stdc.stdio; // : sprintf; + + static if (real.sizeof > double.sizeof) + enum uint useDigits = 16; + else + enum uint useDigits = 15; + + /****************************************** + * Compare floating point numbers to n decimal digits of precision. + * Returns: + * 1 match + * 0 nomatch + */ + + private bool equalsDigit(real x, real y, uint ndigits) + { + if (signbit(x) != signbit(y)) + return 0; + + if (isInfinity(x) && isInfinity(y)) + return 1; + if (isInfinity(x) || isInfinity(y)) + return 0; + + if (isNaN(x) && isNaN(y)) + return 1; + if (isNaN(x) || isNaN(y)) + return 0; + + char[30] bufx; + char[30] bufy; + assert(ndigits < bufx.length); + + int ix; + int iy; + version (CRuntime_Microsoft) + alias real_t = double; + else + alias real_t = real; + ix = sprintf(bufx.ptr, "%.*Lg", ndigits, cast(real_t) x); + iy = sprintf(bufy.ptr, "%.*Lg", ndigits, cast(real_t) y); + assert(ix < bufx.length && ix > 0); + assert(ix < bufy.length && ix > 0); + + return bufx[0 .. ix] == bufy[0 .. iy]; + } +} + + + +package: +// The following IEEE 'real' formats are currently supported. +version (LittleEndian) +{ + static assert(real.mant_dig == 53 || real.mant_dig == 64 + || real.mant_dig == 113, + "Only 64-bit, 80-bit, and 128-bit reals"~ + " are supported for LittleEndian CPUs"); +} +else +{ + static assert(real.mant_dig == 53 || real.mant_dig == 106 + || real.mant_dig == 113, + "Only 64-bit and 128-bit reals are supported for BigEndian CPUs."~ + " double-double reals have partial support"); +} + +// Underlying format exposed through floatTraits +enum RealFormat +{ + ieeeHalf, + ieeeSingle, + ieeeDouble, + ieeeExtended, // x87 80-bit real + ieeeExtended53, // x87 real rounded to precision of double. + ibmExtended, // IBM 128-bit extended + ieeeQuadruple, +} + +// Constants used for extracting the components of the representation. +// They supplement the built-in floating point properties. +template floatTraits(T) +{ + // EXPMASK is a ushort mask to select the exponent portion (without sign) + // EXPSHIFT is the number of bits the exponent is left-shifted by in its ushort + // EXPBIAS is the exponent bias - 1 (exp == EXPBIAS yields ×2^-1). + // EXPPOS_SHORT is the index of the exponent when represented as a ushort array. + // SIGNPOS_BYTE is the index of the sign when represented as a ubyte array. + // RECIP_EPSILON is the value such that (smallest_subnormal) * RECIP_EPSILON == T.min_normal + enum T RECIP_EPSILON = (1/T.epsilon); + static if (T.mant_dig == 24) + { + // Single precision float + enum ushort EXPMASK = 0x7F80; + enum ushort EXPSHIFT = 7; + enum ushort EXPBIAS = 0x3F00; + enum uint EXPMASK_INT = 0x7F80_0000; + enum uint MANTISSAMASK_INT = 0x007F_FFFF; + enum realFormat = RealFormat.ieeeSingle; + version (LittleEndian) + { + enum EXPPOS_SHORT = 1; + enum SIGNPOS_BYTE = 3; + } + else + { + enum EXPPOS_SHORT = 0; + enum SIGNPOS_BYTE = 0; + } + } + else static if (T.mant_dig == 53) + { + static if (T.sizeof == 8) + { + // Double precision float, or real == double + enum ushort EXPMASK = 0x7FF0; + enum ushort EXPSHIFT = 4; + enum ushort EXPBIAS = 0x3FE0; + enum uint EXPMASK_INT = 0x7FF0_0000; + enum uint MANTISSAMASK_INT = 0x000F_FFFF; // for the MSB only + enum realFormat = RealFormat.ieeeDouble; + version (LittleEndian) + { + enum EXPPOS_SHORT = 3; + enum SIGNPOS_BYTE = 7; + } + else + { + enum EXPPOS_SHORT = 0; + enum SIGNPOS_BYTE = 0; + } + } + else static if (T.sizeof == 12) + { + // Intel extended real80 rounded to double + enum ushort EXPMASK = 0x7FFF; + enum ushort EXPSHIFT = 0; + enum ushort EXPBIAS = 0x3FFE; + enum realFormat = RealFormat.ieeeExtended53; + version (LittleEndian) + { + enum EXPPOS_SHORT = 4; + enum SIGNPOS_BYTE = 9; + } + else + { + enum EXPPOS_SHORT = 0; + enum SIGNPOS_BYTE = 0; + } + } + else + static assert(false, "No traits support for " ~ T.stringof); + } + else static if (T.mant_dig == 64) + { + // Intel extended real80 + enum ushort EXPMASK = 0x7FFF; + enum ushort EXPSHIFT = 0; + enum ushort EXPBIAS = 0x3FFE; + enum realFormat = RealFormat.ieeeExtended; + version (LittleEndian) + { + enum EXPPOS_SHORT = 4; + enum SIGNPOS_BYTE = 9; + } + else + { + enum EXPPOS_SHORT = 0; + enum SIGNPOS_BYTE = 0; + } + } + else static if (T.mant_dig == 113) + { + // Quadruple precision float + enum ushort EXPMASK = 0x7FFF; + enum ushort EXPSHIFT = 0; + enum ushort EXPBIAS = 0x3FFE; + enum realFormat = RealFormat.ieeeQuadruple; + version (LittleEndian) + { + enum EXPPOS_SHORT = 7; + enum SIGNPOS_BYTE = 15; + } + else + { + enum EXPPOS_SHORT = 0; + enum SIGNPOS_BYTE = 0; + } + } + else static if (T.mant_dig == 106) + { + // IBM Extended doubledouble + enum ushort EXPMASK = 0x7FF0; + enum ushort EXPSHIFT = 4; + enum realFormat = RealFormat.ibmExtended; + // the exponent byte is not unique + version (LittleEndian) + { + enum EXPPOS_SHORT = 7; // [3] is also an exp short + enum SIGNPOS_BYTE = 15; + } + else + { + enum EXPPOS_SHORT = 0; // [4] is also an exp short + enum SIGNPOS_BYTE = 0; + } + } + else + static assert(false, "No traits support for " ~ T.stringof); +} + +// These apply to all floating-point types +version (LittleEndian) +{ + enum MANTISSA_LSB = 0; + enum MANTISSA_MSB = 1; +} +else +{ + enum MANTISSA_LSB = 1; + enum MANTISSA_MSB = 0; +} + +// Common code for math implementations. + +// Helper for floor/ceil +T floorImpl(T)(const T x) @trusted pure nothrow @nogc +{ + alias F = floatTraits!(T); + // Take care not to trigger library calls from the compiler, + // while ensuring that we don't get defeated by some optimizers. + union floatBits + { + T rv; + ushort[T.sizeof/2] vu; + } + floatBits y = void; + y.rv = x; + + // Find the exponent (power of 2) + static if (F.realFormat == RealFormat.ieeeSingle) + { + int exp = ((y.vu[F.EXPPOS_SHORT] >> 7) & 0xff) - 0x7f; + + version (LittleEndian) + int pos = 0; + else + int pos = 3; + } + else static if (F.realFormat == RealFormat.ieeeDouble) + { + int exp = ((y.vu[F.EXPPOS_SHORT] >> 4) & 0x7ff) - 0x3ff; + + version (LittleEndian) + int pos = 0; + else + int pos = 3; + } + else static if (F.realFormat == RealFormat.ieeeExtended) + { + int exp = (y.vu[F.EXPPOS_SHORT] & 0x7fff) - 0x3fff; + + version (LittleEndian) + int pos = 0; + else + int pos = 4; + } + else static if (F.realFormat == RealFormat.ieeeQuadruple) + { + int exp = (y.vu[F.EXPPOS_SHORT] & 0x7fff) - 0x3fff; + + version (LittleEndian) + int pos = 0; + else + int pos = 7; + } + else + static assert(false, "Not implemented for this architecture"); + + if (exp < 0) + { + if (x < 0.0) + return -1.0; + else + return 0.0; + } + + exp = (T.mant_dig - 1) - exp; + + // Zero 16 bits at a time. + while (exp >= 16) + { + version (LittleEndian) + y.vu[pos++] = 0; + else + y.vu[pos--] = 0; + exp -= 16; + } + + // Clear the remaining bits. + if (exp > 0) + y.vu[pos] &= 0xffff ^ ((1 << exp) - 1); + + if ((x < 0.0) && (x != y.rv)) + y.rv -= 1.0; + + return y.rv; +} + +public: + +// Values obtained from Wolfram Alpha. 116 bits ought to be enough for anybody. +// Wolfram Alpha LLC. 2011. Wolfram|Alpha. http://www.wolframalpha.com/input/?i=e+in+base+16 (access July 6, 2011). +enum real E = 0x1.5bf0a8b1457695355fb8ac404e7a8p+1L; /** e = 2.718281... */ +enum real LOG2T = 0x1.a934f0979a3715fc9257edfe9b5fbp+1L; /** $(SUB log, 2)10 = 3.321928... */ +enum real LOG2E = 0x1.71547652b82fe1777d0ffda0d23a8p+0L; /** $(SUB log, 2)e = 1.442695... */ +enum real LOG2 = 0x1.34413509f79fef311f12b35816f92p-2L; /** $(SUB log, 10)2 = 0.301029... */ +enum real LOG10E = 0x1.bcb7b1526e50e32a6ab7555f5a67cp-2L; /** $(SUB log, 10)e = 0.434294... */ +enum real LN2 = 0x1.62e42fefa39ef35793c7673007e5fp-1L; /** ln 2 = 0.693147... */ +enum real LN10 = 0x1.26bb1bbb5551582dd4adac5705a61p+1L; /** ln 10 = 2.302585... */ +enum real PI = 0x1.921fb54442d18469898cc51701b84p+1L; /** $(_PI) = 3.141592... */ +enum real PI_2 = PI/2; /** $(PI) / 2 = 1.570796... */ +enum real PI_4 = PI/4; /** $(PI) / 4 = 0.785398... */ +enum real M_1_PI = 0x1.45f306dc9c882a53f84eafa3ea69cp-2L; /** 1 / $(PI) = 0.318309... */ +enum real M_2_PI = 2*M_1_PI; /** 2 / $(PI) = 0.636619... */ +enum real M_2_SQRTPI = 0x1.20dd750429b6d11ae3a914fed7fd8p+0L; /** 2 / $(SQRT)$(PI) = 1.128379... */ +enum real SQRT2 = 0x1.6a09e667f3bcc908b2fb1366ea958p+0L; /** $(SQRT)2 = 1.414213... */ +enum real SQRT1_2 = SQRT2/2; /** $(SQRT)$(HALF) = 0.707106... */ +// Note: Make sure the magic numbers in compiler backend for x87 match these. + + +/*********************************** + * Calculates the absolute value of a number + * + * Params: + * Num = (template parameter) type of number + * x = real number value + * z = complex number value + * y = imaginary number value + * + * Returns: + * The absolute value of the number. If floating-point or integral, + * the return type will be the same as the input; if complex or + * imaginary, the returned value will be the corresponding floating + * point type. + * + * For complex numbers, abs(z) = sqrt( $(POWER z.re, 2) + $(POWER z.im, 2) ) + * = hypot(z.re, z.im). + */ +Num abs(Num)(Num x) @safe pure nothrow +if (is(typeof(Num.init >= 0)) && is(typeof(-Num.init)) && + !(is(Num* : const(ifloat*)) || is(Num* : const(idouble*)) + || is(Num* : const(ireal*)))) +{ + static if (isFloatingPoint!(Num)) + return fabs(x); + else + return x >= 0 ? x : -x; +} + +/// ditto +auto abs(Num)(Num z) @safe pure nothrow @nogc +if (is(Num* : const(cfloat*)) || is(Num* : const(cdouble*)) + || is(Num* : const(creal*))) +{ + return hypot(z.re, z.im); +} + +/// ditto +auto abs(Num)(Num y) @safe pure nothrow @nogc +if (is(Num* : const(ifloat*)) || is(Num* : const(idouble*)) + || is(Num* : const(ireal*))) +{ + return fabs(y.im); +} + +/// ditto +@safe pure nothrow @nogc unittest +{ + assert(isIdentical(abs(-0.0L), 0.0L)); + assert(isNaN(abs(real.nan))); + assert(abs(-real.infinity) == real.infinity); + assert(abs(-3.2Li) == 3.2L); + assert(abs(71.6Li) == 71.6L); + assert(abs(-56) == 56); + assert(abs(2321312L) == 2321312L); + assert(abs(-1L+1i) == sqrt(2.0L)); +} + +@safe pure nothrow @nogc unittest +{ + import std.meta : AliasSeq; + foreach (T; AliasSeq!(float, double, real)) + { + T f = 3; + assert(abs(f) == f); + assert(abs(-f) == f); + } + foreach (T; AliasSeq!(cfloat, cdouble, creal)) + { + T f = -12+3i; + assert(abs(f) == hypot(f.re, f.im)); + assert(abs(-f) == hypot(f.re, f.im)); + } +} + +/*********************************** + * Complex conjugate + * + * conj(x + iy) = x - iy + * + * Note that z * conj(z) = $(POWER z.re, 2) - $(POWER z.im, 2) + * is always a real number + */ +auto conj(Num)(Num z) @safe pure nothrow @nogc +if (is(Num* : const(cfloat*)) || is(Num* : const(cdouble*)) + || is(Num* : const(creal*))) +{ + //FIXME + //Issue 14206 + static if (is(Num* : const(cdouble*))) + return cast(cdouble) conj(cast(creal) z); + else + return z.re - z.im*1fi; +} + +/** ditto */ +auto conj(Num)(Num y) @safe pure nothrow @nogc +if (is(Num* : const(ifloat*)) || is(Num* : const(idouble*)) + || is(Num* : const(ireal*))) +{ + return -y; +} + +/// +@safe pure nothrow @nogc unittest +{ + creal c = 7 + 3Li; + assert(conj(c) == 7-3Li); + ireal z = -3.2Li; + assert(conj(z) == -z); +} +//Issue 14206 +@safe pure nothrow @nogc unittest +{ + cdouble c = 7 + 3i; + assert(conj(c) == 7-3i); + idouble z = -3.2i; + assert(conj(z) == -z); +} +//Issue 14206 +@safe pure nothrow @nogc unittest +{ + cfloat c = 7f + 3fi; + assert(conj(c) == 7f-3fi); + ifloat z = -3.2fi; + assert(conj(z) == -z); +} + +/*********************************** + * Returns cosine of x. x is in radians. + * + * $(TABLE_SV + * $(TR $(TH x) $(TH cos(x)) $(TH invalid?)) + * $(TR $(TD $(NAN)) $(TD $(NAN)) $(TD yes) ) + * $(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(NAN)) $(TD yes) ) + * ) + * Bugs: + * Results are undefined if |x| >= $(POWER 2,64). + */ + +real cos(real x) @safe pure nothrow @nogc { pragma(inline, true); return core.math.cos(x); } +//FIXME +///ditto +double cos(double x) @safe pure nothrow @nogc { return cos(cast(real) x); } +//FIXME +///ditto +float cos(float x) @safe pure nothrow @nogc { return cos(cast(real) x); } + +@safe unittest +{ + real function(real) pcos = &cos; + assert(pcos != null); +} + +/*********************************** + * Returns $(HTTP en.wikipedia.org/wiki/Sine, sine) of x. x is in $(HTTP en.wikipedia.org/wiki/Radian, radians). + * + * $(TABLE_SV + * $(TH3 x , sin(x) , invalid?) + * $(TD3 $(NAN) , $(NAN) , yes ) + * $(TD3 $(PLUSMN)0.0, $(PLUSMN)0.0, no ) + * $(TD3 $(PLUSMNINF), $(NAN) , yes ) + * ) + * + * Params: + * x = angle in radians (not degrees) + * Returns: + * sine of x + * See_Also: + * $(MYREF cos), $(MYREF tan), $(MYREF asin) + * Bugs: + * Results are undefined if |x| >= $(POWER 2,64). + */ + +real sin(real x) @safe pure nothrow @nogc { pragma(inline, true); return core.math.sin(x); } +//FIXME +///ditto +double sin(double x) @safe pure nothrow @nogc { return sin(cast(real) x); } +//FIXME +///ditto +float sin(float x) @safe pure nothrow @nogc { return sin(cast(real) x); } + +/// +@safe unittest +{ + import std.math : sin, PI; + import std.stdio : writefln; + + void someFunc() + { + real x = 30.0; + auto result = sin(x * (PI / 180)); // convert degrees to radians + writefln("The sine of %s degrees is %s", x, result); + } +} + +@safe unittest +{ + real function(real) psin = &sin; + assert(psin != null); +} + +/*********************************** + * Returns sine for complex and imaginary arguments. + * + * sin(z) = sin(z.re)*cosh(z.im) + cos(z.re)*sinh(z.im)i + * + * If both sin($(THETA)) and cos($(THETA)) are required, + * it is most efficient to use expi($(THETA)). + */ +creal sin(creal z) @safe pure nothrow @nogc +{ + const creal cs = expi(z.re); + const creal csh = coshisinh(z.im); + return cs.im * csh.re + cs.re * csh.im * 1i; +} + +/** ditto */ +ireal sin(ireal y) @safe pure nothrow @nogc +{ + return cosh(y.im)*1i; +} + +/// +@safe pure nothrow @nogc unittest +{ + assert(sin(0.0+0.0i) == 0.0); + assert(sin(2.0+0.0i) == sin(2.0L) ); +} + +/*********************************** + * cosine, complex and imaginary + * + * cos(z) = cos(z.re)*cosh(z.im) - sin(z.re)*sinh(z.im)i + */ +creal cos(creal z) @safe pure nothrow @nogc +{ + const creal cs = expi(z.re); + const creal csh = coshisinh(z.im); + return cs.re * csh.re - cs.im * csh.im * 1i; +} + +/** ditto */ +real cos(ireal y) @safe pure nothrow @nogc +{ + return cosh(y.im); +} + +/// +@safe pure nothrow @nogc unittest +{ + assert(cos(0.0+0.0i)==1.0); + assert(cos(1.3L+0.0i)==cos(1.3L)); + assert(cos(5.2Li)== cosh(5.2L)); +} + +/**************************************************************************** + * Returns tangent of x. x is in radians. + * + * $(TABLE_SV + * $(TR $(TH x) $(TH tan(x)) $(TH invalid?)) + * $(TR $(TD $(NAN)) $(TD $(NAN)) $(TD yes)) + * $(TR $(TD $(PLUSMN)0.0) $(TD $(PLUSMN)0.0) $(TD no)) + * $(TR $(TD $(PLUSMNINF)) $(TD $(NAN)) $(TD yes)) + * ) + */ + +real tan(real x) @trusted pure nothrow @nogc +{ + version (D_InlineAsm_X86) + { + asm pure nothrow @nogc + { + fld x[EBP] ; // load theta + fxam ; // test for oddball values + fstsw AX ; + sahf ; + jc trigerr ; // x is NAN, infinity, or empty + // 387's can handle subnormals +SC18: fptan ; + fstsw AX ; + sahf ; + jnp Clear1 ; // C2 = 1 (x is out of range) + + // Do argument reduction to bring x into range + fldpi ; + fxch ; +SC17: fprem1 ; + fstsw AX ; + sahf ; + jp SC17 ; + fstp ST(1) ; // remove pi from stack + jmp SC18 ; + +trigerr: + jnp Lret ; // if theta is NAN, return theta + fstp ST(0) ; // dump theta + } + return real.nan; + +Clear1: asm pure nothrow @nogc{ + fstp ST(0) ; // dump X, which is always 1 + } + +Lret: {} + } + else version (D_InlineAsm_X86_64) + { + version (Win64) + { + asm pure nothrow @nogc + { + fld real ptr [RCX] ; // load theta + } + } + else + { + asm pure nothrow @nogc + { + fld x[RBP] ; // load theta + } + } + asm pure nothrow @nogc + { + fxam ; // test for oddball values + fstsw AX ; + test AH,1 ; + jnz trigerr ; // x is NAN, infinity, or empty + // 387's can handle subnormals +SC18: fptan ; + fstsw AX ; + test AH,4 ; + jz Clear1 ; // C2 = 1 (x is out of range) + + // Do argument reduction to bring x into range + fldpi ; + fxch ; +SC17: fprem1 ; + fstsw AX ; + test AH,4 ; + jnz SC17 ; + fstp ST(1) ; // remove pi from stack + jmp SC18 ; + +trigerr: + test AH,4 ; + jz Lret ; // if theta is NAN, return theta + fstp ST(0) ; // dump theta + } + return real.nan; + +Clear1: asm pure nothrow @nogc{ + fstp ST(0) ; // dump X, which is always 1 + } + +Lret: {} + } + else + { + // Coefficients for tan(x) and PI/4 split into three parts. + static if (floatTraits!real.realFormat == RealFormat.ieeeQuadruple) + { + static immutable real[6] P = [ + 2.883414728874239697964612246732416606301E10L, + -2.307030822693734879744223131873392503321E9L, + 5.160188250214037865511600561074819366815E7L, + -4.249691853501233575668486667664718192660E5L, + 1.272297782199996882828849455156962260810E3L, + -9.889929415807650724957118893791829849557E-1L + ]; + static immutable real[7] Q = [ + 8.650244186622719093893836740197250197602E10L + -4.152206921457208101480801635640958361612E10L, + 2.758476078803232151774723646710890525496E9L, + -5.733709132766856723608447733926138506824E7L, + 4.529422062441341616231663543669583527923E5L, + -1.317243702830553658702531997959756728291E3L, + 1.0 + ]; + + enum real P1 = + 7.853981633974483067550664827649598009884357452392578125E-1L; + enum real P2 = + 2.8605943630549158983813312792950660807511260829685741796657E-18L; + enum real P3 = + 2.1679525325309452561992610065108379921905808E-35L; + } + else + { + static immutable real[3] P = [ + -1.7956525197648487798769E7L, + 1.1535166483858741613983E6L, + -1.3093693918138377764608E4L, + ]; + static immutable real[5] Q = [ + -5.3869575592945462988123E7L, + 2.5008380182335791583922E7L, + -1.3208923444021096744731E6L, + 1.3681296347069295467845E4L, + 1.0000000000000000000000E0L, + ]; + + enum real P1 = 7.853981554508209228515625E-1L; + enum real P2 = 7.946627356147928367136046290398E-9L; + enum real P3 = 3.061616997868382943065164830688E-17L; + } + + // Special cases. + if (x == 0.0 || isNaN(x)) + return x; + if (isInfinity(x)) + return real.nan; + + // Make argument positive but save the sign. + bool sign = false; + if (signbit(x)) + { + sign = true; + x = -x; + } + + // Compute x mod PI/4. + real y = floor(x / PI_4); + // Strip high bits of integer part. + real z = ldexp(y, -4); + // Compute y - 16 * (y / 16). + z = y - ldexp(floor(z), 4); + + // Integer and fraction part modulo one octant. + int j = cast(int)(z); + + // Map zeros and singularities to origin. + if (j & 1) + { + j += 1; + y += 1.0; + } + + z = ((x - y * P1) - y * P2) - y * P3; + const real zz = z * z; + + if (zz > 1.0e-20L) + y = z + z * (zz * poly(zz, P) / poly(zz, Q)); + else + y = z; + + if (j & 2) + y = -1.0 / y; + + return (sign) ? -y : y; + } +} + +@safe nothrow @nogc unittest +{ + static real[2][] vals = // angle,tan + [ + [ 0, 0], + [ .5, .5463024898], + [ 1, 1.557407725], + [ 1.5, 14.10141995], + [ 2, -2.185039863], + [ 2.5,-.7470222972], + [ 3, -.1425465431], + [ 3.5, .3745856402], + [ 4, 1.157821282], + [ 4.5, 4.637332055], + [ 5, -3.380515006], + [ 5.5,-.9955840522], + [ 6, -.2910061914], + [ 6.5, .2202772003], + [ 10, .6483608275], + + // special angles + [ PI_4, 1], + //[ PI_2, real.infinity], // PI_2 is not _exactly_ pi/2. + [ 3*PI_4, -1], + [ PI, 0], + [ 5*PI_4, 1], + //[ 3*PI_2, -real.infinity], + [ 7*PI_4, -1], + [ 2*PI, 0], + ]; + int i; + + for (i = 0; i < vals.length; i++) + { + real x = vals[i][0]; + real r = vals[i][1]; + real t = tan(x); + + //printf("tan(%Lg) = %Lg, should be %Lg\n", x, t, r); + if (!isIdentical(r, t)) assert(fabs(r-t) <= .0000001); + + x = -x; + r = -r; + t = tan(x); + //printf("tan(%Lg) = %Lg, should be %Lg\n", x, t, r); + if (!isIdentical(r, t) && !(r != r && t != t)) assert(fabs(r-t) <= .0000001); + } + // overflow + assert(isNaN(tan(real.infinity))); + assert(isNaN(tan(-real.infinity))); + // NaN propagation + assert(isIdentical( tan(NaN(0x0123L)), NaN(0x0123L) )); +} + +@system unittest +{ + assert(equalsDigit(tan(PI / 3), std.math.sqrt(3.0), useDigits)); +} + +/*************** + * Calculates the arc cosine of x, + * returning a value ranging from 0 to $(PI). + * + * $(TABLE_SV + * $(TR $(TH x) $(TH acos(x)) $(TH invalid?)) + * $(TR $(TD $(GT)1.0) $(TD $(NAN)) $(TD yes)) + * $(TR $(TD $(LT)-1.0) $(TD $(NAN)) $(TD yes)) + * $(TR $(TD $(NAN)) $(TD $(NAN)) $(TD yes)) + * ) + */ +real acos(real x) @safe pure nothrow @nogc +{ + return atan2(sqrt(1-x*x), x); +} + +/// ditto +double acos(double x) @safe pure nothrow @nogc { return acos(cast(real) x); } + +/// ditto +float acos(float x) @safe pure nothrow @nogc { return acos(cast(real) x); } + +@system unittest +{ + assert(equalsDigit(acos(0.5), std.math.PI / 3, useDigits)); +} + +/*************** + * Calculates the arc sine of x, + * returning a value ranging from -$(PI)/2 to $(PI)/2. + * + * $(TABLE_SV + * $(TR $(TH x) $(TH asin(x)) $(TH invalid?)) + * $(TR $(TD $(PLUSMN)0.0) $(TD $(PLUSMN)0.0) $(TD no)) + * $(TR $(TD $(GT)1.0) $(TD $(NAN)) $(TD yes)) + * $(TR $(TD $(LT)-1.0) $(TD $(NAN)) $(TD yes)) + * ) + */ +real asin(real x) @safe pure nothrow @nogc +{ + return atan2(x, sqrt(1-x*x)); +} + +/// ditto +double asin(double x) @safe pure nothrow @nogc { return asin(cast(real) x); } + +/// ditto +float asin(float x) @safe pure nothrow @nogc { return asin(cast(real) x); } + +@system unittest +{ + assert(equalsDigit(asin(0.5), PI / 6, useDigits)); +} + +/*************** + * Calculates the arc tangent of x, + * returning a value ranging from -$(PI)/2 to $(PI)/2. + * + * $(TABLE_SV + * $(TR $(TH x) $(TH atan(x)) $(TH invalid?)) + * $(TR $(TD $(PLUSMN)0.0) $(TD $(PLUSMN)0.0) $(TD no)) + * $(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(NAN)) $(TD yes)) + * ) + */ +real atan(real x) @safe pure nothrow @nogc +{ + version (InlineAsm_X86_Any) + { + return atan2(x, 1.0L); + } + else + { + // Coefficients for atan(x) + static if (floatTraits!real.realFormat == RealFormat.ieeeQuadruple) + { + static immutable real[9] P = [ + -6.880597774405940432145577545328795037141E2L, + -2.514829758941713674909996882101723647996E3L, + -3.696264445691821235400930243493001671932E3L, + -2.792272753241044941703278827346430350236E3L, + -1.148164399808514330375280133523543970854E3L, + -2.497759878476618348858065206895055957104E2L, + -2.548067867495502632615671450650071218995E1L, + -8.768423468036849091777415076702113400070E-1L, + -6.635810778635296712545011270011752799963E-4L + ]; + static immutable real[9] Q = [ + 2.064179332321782129643673263598686441900E3L, + 8.782996876218210302516194604424986107121E3L, + 1.547394317752562611786521896296215170819E4L, + 1.458510242529987155225086911411015961174E4L, + 7.928572347062145288093560392463784743935E3L, + 2.494680540950601626662048893678584497900E3L, + 4.308348370818927353321556740027020068897E2L, + 3.566239794444800849656497338030115886153E1L, + 1.0 + ]; + } + else + { + static immutable real[5] P = [ + -5.0894116899623603312185E1L, + -9.9988763777265819915721E1L, + -6.3976888655834347413154E1L, + -1.4683508633175792446076E1L, + -8.6863818178092187535440E-1L, + ]; + static immutable real[6] Q = [ + 1.5268235069887081006606E2L, + 3.9157570175111990631099E2L, + 3.6144079386152023162701E2L, + 1.4399096122250781605352E2L, + 2.2981886733594175366172E1L, + 1.0000000000000000000000E0L, + ]; + } + + // tan(PI/8) + enum real TAN_PI_8 = 0.414213562373095048801688724209698078569672L; + // tan(3 * PI/8) + enum real TAN3_PI_8 = 2.414213562373095048801688724209698078569672L; + + // Special cases. + if (x == 0.0) + return x; + if (isInfinity(x)) + return copysign(PI_2, x); + + // Make argument positive but save the sign. + bool sign = false; + if (signbit(x)) + { + sign = true; + x = -x; + } + + // Range reduction. + real y; + if (x > TAN3_PI_8) + { + y = PI_2; + x = -(1.0 / x); + } + else if (x > TAN_PI_8) + { + y = PI_4; + x = (x - 1.0)/(x + 1.0); + } + else + y = 0.0; + + // Rational form in x^^2. + const real z = x * x; + y = y + (poly(z, P) / poly(z, Q)) * z * x + x; + + return (sign) ? -y : y; + } +} + +/// ditto +double atan(double x) @safe pure nothrow @nogc { return atan(cast(real) x); } + +/// ditto +float atan(float x) @safe pure nothrow @nogc { return atan(cast(real) x); } + +@system unittest +{ + assert(equalsDigit(atan(std.math.sqrt(3.0)), PI / 3, useDigits)); +} + +/*************** + * Calculates the arc tangent of y / x, + * returning a value ranging from -$(PI) to $(PI). + * + * $(TABLE_SV + * $(TR $(TH y) $(TH x) $(TH atan(y, x))) + * $(TR $(TD $(NAN)) $(TD anything) $(TD $(NAN)) ) + * $(TR $(TD anything) $(TD $(NAN)) $(TD $(NAN)) ) + * $(TR $(TD $(PLUSMN)0.0) $(TD $(GT)0.0) $(TD $(PLUSMN)0.0) ) + * $(TR $(TD $(PLUSMN)0.0) $(TD +0.0) $(TD $(PLUSMN)0.0) ) + * $(TR $(TD $(PLUSMN)0.0) $(TD $(LT)0.0) $(TD $(PLUSMN)$(PI))) + * $(TR $(TD $(PLUSMN)0.0) $(TD -0.0) $(TD $(PLUSMN)$(PI))) + * $(TR $(TD $(GT)0.0) $(TD $(PLUSMN)0.0) $(TD $(PI)/2) ) + * $(TR $(TD $(LT)0.0) $(TD $(PLUSMN)0.0) $(TD -$(PI)/2) ) + * $(TR $(TD $(GT)0.0) $(TD $(INFIN)) $(TD $(PLUSMN)0.0) ) + * $(TR $(TD $(PLUSMN)$(INFIN)) $(TD anything) $(TD $(PLUSMN)$(PI)/2)) + * $(TR $(TD $(GT)0.0) $(TD -$(INFIN)) $(TD $(PLUSMN)$(PI)) ) + * $(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(INFIN)) $(TD $(PLUSMN)$(PI)/4)) + * $(TR $(TD $(PLUSMN)$(INFIN)) $(TD -$(INFIN)) $(TD $(PLUSMN)3$(PI)/4)) + * ) + */ +real atan2(real y, real x) @trusted pure nothrow @nogc +{ + version (InlineAsm_X86_Any) + { + version (Win64) + { + asm pure nothrow @nogc { + naked; + fld real ptr [RDX]; // y + fld real ptr [RCX]; // x + fpatan; + ret; + } + } + else + { + asm pure nothrow @nogc { + fld y; + fld x; + fpatan; + } + } + } + else + { + // Special cases. + if (isNaN(x) || isNaN(y)) + return real.nan; + if (y == 0.0) + { + if (x >= 0 && !signbit(x)) + return copysign(0, y); + else + return copysign(PI, y); + } + if (x == 0.0) + return copysign(PI_2, y); + if (isInfinity(x)) + { + if (signbit(x)) + { + if (isInfinity(y)) + return copysign(3*PI_4, y); + else + return copysign(PI, y); + } + else + { + if (isInfinity(y)) + return copysign(PI_4, y); + else + return copysign(0.0, y); + } + } + if (isInfinity(y)) + return copysign(PI_2, y); + + // Call atan and determine the quadrant. + real z = atan(y / x); + + if (signbit(x)) + { + if (signbit(y)) + z = z - PI; + else + z = z + PI; + } + + if (z == 0.0) + return copysign(z, y); + + return z; + } +} + +/// ditto +double atan2(double y, double x) @safe pure nothrow @nogc +{ + return atan2(cast(real) y, cast(real) x); +} + +/// ditto +float atan2(float y, float x) @safe pure nothrow @nogc +{ + return atan2(cast(real) y, cast(real) x); +} + +@system unittest +{ + assert(equalsDigit(atan2(1.0L, std.math.sqrt(3.0L)), PI / 6, useDigits)); +} + +/*********************************** + * Calculates the hyperbolic cosine of x. + * + * $(TABLE_SV + * $(TR $(TH x) $(TH cosh(x)) $(TH invalid?)) + * $(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(PLUSMN)0.0) $(TD no) ) + * ) + */ +real cosh(real x) @safe pure nothrow @nogc +{ + // cosh = (exp(x)+exp(-x))/2. + // The naive implementation works correctly. + const real y = exp(x); + return (y + 1.0/y) * 0.5; +} + +/// ditto +double cosh(double x) @safe pure nothrow @nogc { return cosh(cast(real) x); } + +/// ditto +float cosh(float x) @safe pure nothrow @nogc { return cosh(cast(real) x); } + +@system unittest +{ + assert(equalsDigit(cosh(1.0), (E + 1.0 / E) / 2, useDigits)); +} + +/*********************************** + * Calculates the hyperbolic sine of x. + * + * $(TABLE_SV + * $(TR $(TH x) $(TH sinh(x)) $(TH invalid?)) + * $(TR $(TD $(PLUSMN)0.0) $(TD $(PLUSMN)0.0) $(TD no)) + * $(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(PLUSMN)$(INFIN)) $(TD no)) + * ) + */ +real sinh(real x) @safe pure nothrow @nogc +{ + // sinh(x) = (exp(x)-exp(-x))/2; + // Very large arguments could cause an overflow, but + // the maximum value of x for which exp(x) + exp(-x)) != exp(x) + // is x = 0.5 * (real.mant_dig) * LN2. // = 22.1807 for real80. + if (fabs(x) > real.mant_dig * LN2) + { + return copysign(0.5 * exp(fabs(x)), x); + } + + const real y = expm1(x); + return 0.5 * y / (y+1) * (y+2); +} + +/// ditto +double sinh(double x) @safe pure nothrow @nogc { return sinh(cast(real) x); } + +/// ditto +float sinh(float x) @safe pure nothrow @nogc { return sinh(cast(real) x); } + +@system unittest +{ + assert(equalsDigit(sinh(1.0), (E - 1.0 / E) / 2, useDigits)); +} + +/*********************************** + * Calculates the hyperbolic tangent of x. + * + * $(TABLE_SV + * $(TR $(TH x) $(TH tanh(x)) $(TH invalid?)) + * $(TR $(TD $(PLUSMN)0.0) $(TD $(PLUSMN)0.0) $(TD no) ) + * $(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(PLUSMN)1.0) $(TD no)) + * ) + */ +real tanh(real x) @safe pure nothrow @nogc +{ + // tanh(x) = (exp(x) - exp(-x))/(exp(x)+exp(-x)) + if (fabs(x) > real.mant_dig * LN2) + { + return copysign(1, x); + } + + const real y = expm1(2*x); + return y / (y + 2); +} + +/// ditto +double tanh(double x) @safe pure nothrow @nogc { return tanh(cast(real) x); } + +/// ditto +float tanh(float x) @safe pure nothrow @nogc { return tanh(cast(real) x); } + +@system unittest +{ + assert(equalsDigit(tanh(1.0), sinh(1.0) / cosh(1.0), 15)); +} + +package: + +/* Returns cosh(x) + I * sinh(x) + * Only one call to exp() is performed. + */ +creal coshisinh(real x) @safe pure nothrow @nogc +{ + // See comments for cosh, sinh. + if (fabs(x) > real.mant_dig * LN2) + { + const real y = exp(fabs(x)); + return y * 0.5 + 0.5i * copysign(y, x); + } + else + { + const real y = expm1(x); + return (y + 1.0 + 1.0/(y + 1.0)) * 0.5 + 0.5i * y / (y+1) * (y+2); + } +} + +@safe pure nothrow @nogc unittest +{ + creal c = coshisinh(3.0L); + assert(c.re == cosh(3.0L)); + assert(c.im == sinh(3.0L)); +} + +public: + +/*********************************** + * Calculates the inverse hyperbolic cosine of x. + * + * Mathematically, acosh(x) = log(x + sqrt( x*x - 1)) + * + * $(TABLE_DOMRG + * $(DOMAIN 1..$(INFIN)), + * $(RANGE 0..$(INFIN)) + * ) + * + * $(TABLE_SV + * $(SVH x, acosh(x) ) + * $(SV $(NAN), $(NAN) ) + * $(SV $(LT)1, $(NAN) ) + * $(SV 1, 0 ) + * $(SV +$(INFIN),+$(INFIN)) + * ) + */ +real acosh(real x) @safe pure nothrow @nogc +{ + if (x > 1/real.epsilon) + return LN2 + log(x); + else + return log(x + sqrt(x*x - 1)); +} + +/// ditto +double acosh(double x) @safe pure nothrow @nogc { return acosh(cast(real) x); } + +/// ditto +float acosh(float x) @safe pure nothrow @nogc { return acosh(cast(real) x); } + + +@system unittest +{ + assert(isNaN(acosh(0.9))); + assert(isNaN(acosh(real.nan))); + assert(acosh(1.0)==0.0); + assert(acosh(real.infinity) == real.infinity); + assert(isNaN(acosh(0.5))); + assert(equalsDigit(acosh(cosh(3.0)), 3, useDigits)); +} + +/*********************************** + * Calculates the inverse hyperbolic sine of x. + * + * Mathematically, + * --------------- + * asinh(x) = log( x + sqrt( x*x + 1 )) // if x >= +0 + * asinh(x) = -log(-x + sqrt( x*x + 1 )) // if x <= -0 + * ------------- + * + * $(TABLE_SV + * $(SVH x, asinh(x) ) + * $(SV $(NAN), $(NAN) ) + * $(SV $(PLUSMN)0, $(PLUSMN)0 ) + * $(SV $(PLUSMN)$(INFIN),$(PLUSMN)$(INFIN)) + * ) + */ +real asinh(real x) @safe pure nothrow @nogc +{ + return (fabs(x) > 1 / real.epsilon) + // beyond this point, x*x + 1 == x*x + ? copysign(LN2 + log(fabs(x)), x) + // sqrt(x*x + 1) == 1 + x * x / ( 1 + sqrt(x*x + 1) ) + : copysign(log1p(fabs(x) + x*x / (1 + sqrt(x*x + 1)) ), x); +} + +/// ditto +double asinh(double x) @safe pure nothrow @nogc { return asinh(cast(real) x); } + +/// ditto +float asinh(float x) @safe pure nothrow @nogc { return asinh(cast(real) x); } + +@system unittest +{ + assert(isIdentical(asinh(0.0), 0.0)); + assert(isIdentical(asinh(-0.0), -0.0)); + assert(asinh(real.infinity) == real.infinity); + assert(asinh(-real.infinity) == -real.infinity); + assert(isNaN(asinh(real.nan))); + assert(equalsDigit(asinh(sinh(3.0)), 3, useDigits)); +} + +/*********************************** + * Calculates the inverse hyperbolic tangent of x, + * returning a value from ranging from -1 to 1. + * + * Mathematically, atanh(x) = log( (1+x)/(1-x) ) / 2 + * + * $(TABLE_DOMRG + * $(DOMAIN -$(INFIN)..$(INFIN)), + * $(RANGE -1 .. 1) + * ) + * $(BR) + * $(TABLE_SV + * $(SVH x, acosh(x) ) + * $(SV $(NAN), $(NAN) ) + * $(SV $(PLUSMN)0, $(PLUSMN)0) + * $(SV -$(INFIN), -0) + * ) + */ +real atanh(real x) @safe pure nothrow @nogc +{ + // log( (1+x)/(1-x) ) == log ( 1 + (2*x)/(1-x) ) + return 0.5 * log1p( 2 * x / (1 - x) ); +} + +/// ditto +double atanh(double x) @safe pure nothrow @nogc { return atanh(cast(real) x); } + +/// ditto +float atanh(float x) @safe pure nothrow @nogc { return atanh(cast(real) x); } + + +@system unittest +{ + assert(isIdentical(atanh(0.0), 0.0)); + assert(isIdentical(atanh(-0.0),-0.0)); + assert(isNaN(atanh(real.nan))); + assert(isNaN(atanh(-real.infinity))); + assert(atanh(0.0) == 0); + assert(equalsDigit(atanh(tanh(0.5L)), 0.5, useDigits)); +} + +/***************************************** + * Returns x rounded to a long value using the current rounding mode. + * If the integer value of x is + * greater than long.max, the result is + * indeterminate. + */ +long rndtol(real x) @nogc @safe pure nothrow { pragma(inline, true); return core.math.rndtol(x); } +//FIXME +///ditto +long rndtol(double x) @safe pure nothrow @nogc { return rndtol(cast(real) x); } +//FIXME +///ditto +long rndtol(float x) @safe pure nothrow @nogc { return rndtol(cast(real) x); } + +@safe unittest +{ + long function(real) prndtol = &rndtol; + assert(prndtol != null); +} + +/***************************************** + * Returns x rounded to a long value using the FE_TONEAREST rounding mode. + * If the integer value of x is + * greater than long.max, the result is + * indeterminate. + */ +extern (C) real rndtonl(real x); + +/*************************************** + * Compute square root of x. + * + * $(TABLE_SV + * $(TR $(TH x) $(TH sqrt(x)) $(TH invalid?)) + * $(TR $(TD -0.0) $(TD -0.0) $(TD no)) + * $(TR $(TD $(LT)0.0) $(TD $(NAN)) $(TD yes)) + * $(TR $(TD +$(INFIN)) $(TD +$(INFIN)) $(TD no)) + * ) + */ +float sqrt(float x) @nogc @safe pure nothrow { pragma(inline, true); return core.math.sqrt(x); } + +/// ditto +double sqrt(double x) @nogc @safe pure nothrow { pragma(inline, true); return core.math.sqrt(x); } + +/// ditto +real sqrt(real x) @nogc @safe pure nothrow { pragma(inline, true); return core.math.sqrt(x); } + +@safe pure nothrow @nogc unittest +{ + //ctfe + enum ZX80 = sqrt(7.0f); + enum ZX81 = sqrt(7.0); + enum ZX82 = sqrt(7.0L); + + assert(isNaN(sqrt(-1.0f))); + assert(isNaN(sqrt(-1.0))); + assert(isNaN(sqrt(-1.0L))); +} + +@safe unittest +{ + float function(float) psqrtf = &sqrt; + assert(psqrtf != null); + double function(double) psqrtd = &sqrt; + assert(psqrtd != null); + real function(real) psqrtr = &sqrt; + assert(psqrtr != null); +} + +creal sqrt(creal z) @nogc @safe pure nothrow +{ + creal c; + real x,y,w,r; + + if (z == 0) + { + c = 0 + 0i; + } + else + { + const real z_re = z.re; + const real z_im = z.im; + + x = fabs(z_re); + y = fabs(z_im); + if (x >= y) + { + r = y / x; + w = sqrt(x) * sqrt(0.5 * (1 + sqrt(1 + r * r))); + } + else + { + r = x / y; + w = sqrt(y) * sqrt(0.5 * (r + sqrt(1 + r * r))); + } + + if (z_re >= 0) + { + c = w + (z_im / (w + w)) * 1.0i; + } + else + { + if (z_im < 0) + w = -w; + c = z_im / (w + w) + w * 1.0i; + } + } + return c; +} + +/** + * Calculates e$(SUPERSCRIPT x). + * + * $(TABLE_SV + * $(TR $(TH x) $(TH e$(SUPERSCRIPT x)) ) + * $(TR $(TD +$(INFIN)) $(TD +$(INFIN)) ) + * $(TR $(TD -$(INFIN)) $(TD +0.0) ) + * $(TR $(TD $(NAN)) $(TD $(NAN)) ) + * ) + */ +real exp(real x) @trusted pure nothrow @nogc +{ + version (D_InlineAsm_X86) + { + // e^^x = 2^^(LOG2E*x) + // (This is valid because the overflow & underflow limits for exp + // and exp2 are so similar). + return exp2(LOG2E*x); + } + else version (D_InlineAsm_X86_64) + { + // e^^x = 2^^(LOG2E*x) + // (This is valid because the overflow & underflow limits for exp + // and exp2 are so similar). + return exp2(LOG2E*x); + } + else + { + alias F = floatTraits!real; + static if (F.realFormat == RealFormat.ieeeDouble) + { + // Coefficients for exp(x) + static immutable real[3] P = [ + 9.99999999999999999910E-1L, + 3.02994407707441961300E-2L, + 1.26177193074810590878E-4L, + ]; + static immutable real[4] Q = [ + 2.00000000000000000009E0L, + 2.27265548208155028766E-1L, + 2.52448340349684104192E-3L, + 3.00198505138664455042E-6L, + ]; + + // C1 + C2 = LN2. + enum real C1 = 6.93145751953125E-1; + enum real C2 = 1.42860682030941723212E-6; + + // Overflow and Underflow limits. + enum real OF = 7.09782712893383996732E2; // ln((1-2^-53) * 2^1024) + enum real UF = -7.451332191019412076235E2; // ln(2^-1075) + } + else static if (F.realFormat == RealFormat.ieeeExtended) + { + // Coefficients for exp(x) + static immutable real[3] P = [ + 9.9999999999999999991025E-1L, + 3.0299440770744196129956E-2L, + 1.2617719307481059087798E-4L, + ]; + static immutable real[4] Q = [ + 2.0000000000000000000897E0L, + 2.2726554820815502876593E-1L, + 2.5244834034968410419224E-3L, + 3.0019850513866445504159E-6L, + ]; + + // C1 + C2 = LN2. + enum real C1 = 6.9314575195312500000000E-1L; + enum real C2 = 1.4286068203094172321215E-6L; + + // Overflow and Underflow limits. + enum real OF = 1.1356523406294143949492E4L; // ln((1-2^-64) * 2^16384) + enum real UF = -1.13994985314888605586758E4L; // ln(2^-16446) + } + else static if (F.realFormat == RealFormat.ieeeQuadruple) + { + // Coefficients for exp(x) - 1 + static immutable real[5] P = [ + 9.999999999999999999999999999999999998502E-1L, + 3.508710990737834361215404761139478627390E-2L, + 2.708775201978218837374512615596512792224E-4L, + 6.141506007208645008909088812338454698548E-7L, + 3.279723985560247033712687707263393506266E-10L + ]; + static immutable real[6] Q = [ + 2.000000000000000000000000000000000000150E0, + 2.368408864814233538909747618894558968880E-1L, + 3.611828913847589925056132680618007270344E-3L, + 1.504792651814944826817779302637284053660E-5L, + 1.771372078166251484503904874657985291164E-8L, + 2.980756652081995192255342779918052538681E-12L + ]; + + // C1 + C2 = LN2. + enum real C1 = 6.93145751953125E-1L; + enum real C2 = 1.428606820309417232121458176568075500134E-6L; + + // Overflow and Underflow limits. + enum real OF = 1.135583025911358400418251384584930671458833e4L; + enum real UF = -1.143276959615573793352782661133116431383730e4L; + } + else + static assert(0, "Not implemented for this architecture"); + + // Special cases. Raises an overflow or underflow flag accordingly, + // except in the case for CTFE, where there are no hardware controls. + if (isNaN(x)) + return x; + if (x > OF) + { + if (__ctfe) + return real.infinity; + else + return real.max * copysign(real.max, real.infinity); + } + if (x < UF) + { + if (__ctfe) + return 0.0; + else + return real.min_normal * copysign(real.min_normal, 0.0); + } + + // Express: e^^x = e^^g * 2^^n + // = e^^g * e^^(n * LOG2E) + // = e^^(g + n * LOG2E) + int n = cast(int) floor(LOG2E * x + 0.5); + x -= n * C1; + x -= n * C2; + + // Rational approximation for exponential of the fractional part: + // e^^x = 1 + 2x P(x^^2) / (Q(x^^2) - P(x^^2)) + const real xx = x * x; + const real px = x * poly(xx, P); + x = px / (poly(xx, Q) - px); + x = 1.0 + ldexp(x, 1); + + // Scale by power of 2. + x = ldexp(x, n); + + return x; + } +} + +/// ditto +double exp(double x) @safe pure nothrow @nogc { return exp(cast(real) x); } + +/// ditto +float exp(float x) @safe pure nothrow @nogc { return exp(cast(real) x); } + +@system unittest +{ + assert(equalsDigit(exp(3.0L), E * E * E, useDigits)); +} + +/** + * Calculates the value of the natural logarithm base (e) + * raised to the power of x, minus 1. + * + * For very small x, expm1(x) is more accurate + * than exp(x)-1. + * + * $(TABLE_SV + * $(TR $(TH x) $(TH e$(SUPERSCRIPT x)-1) ) + * $(TR $(TD $(PLUSMN)0.0) $(TD $(PLUSMN)0.0) ) + * $(TR $(TD +$(INFIN)) $(TD +$(INFIN)) ) + * $(TR $(TD -$(INFIN)) $(TD -1.0) ) + * $(TR $(TD $(NAN)) $(TD $(NAN)) ) + * ) + */ +real expm1(real x) @trusted pure nothrow @nogc +{ + version (D_InlineAsm_X86) + { + enum PARAMSIZE = (real.sizeof+3)&(0xFFFF_FFFC); // always a multiple of 4 + asm pure nothrow @nogc + { + /* expm1() for x87 80-bit reals, IEEE754-2008 conformant. + * Author: Don Clugston. + * + * expm1(x) = 2^^(rndint(y))* 2^^(y-rndint(y)) - 1 where y = LN2*x. + * = 2rndy * 2ym1 + 2rndy - 1, where 2rndy = 2^^(rndint(y)) + * and 2ym1 = (2^^(y-rndint(y))-1). + * If 2rndy < 0.5*real.epsilon, result is -1. + * Implementation is otherwise the same as for exp2() + */ + naked; + fld real ptr [ESP+4] ; // x + mov AX, [ESP+4+8]; // AX = exponent and sign + sub ESP, 12+8; // Create scratch space on the stack + // [ESP,ESP+2] = scratchint + // [ESP+4..+6, +8..+10, +10] = scratchreal + // set scratchreal mantissa = 1.0 + mov dword ptr [ESP+8], 0; + mov dword ptr [ESP+8+4], 0x80000000; + and AX, 0x7FFF; // drop sign bit + cmp AX, 0x401D; // avoid InvalidException in fist + jae L_extreme; + fldl2e; + fmulp ST(1), ST; // y = x*log2(e) + fist dword ptr [ESP]; // scratchint = rndint(y) + fisub dword ptr [ESP]; // y - rndint(y) + // and now set scratchreal exponent + mov EAX, [ESP]; + add EAX, 0x3fff; + jle short L_largenegative; + cmp EAX,0x8000; + jge short L_largepositive; + mov [ESP+8+8],AX; + f2xm1; // 2ym1 = 2^^(y-rndint(y)) -1 + fld real ptr [ESP+8] ; // 2rndy = 2^^rndint(y) + fmul ST(1), ST; // ST=2rndy, ST(1)=2rndy*2ym1 + fld1; + fsubp ST(1), ST; // ST = 2rndy-1, ST(1) = 2rndy * 2ym1 - 1 + faddp ST(1), ST; // ST = 2rndy * 2ym1 + 2rndy - 1 + add ESP,12+8; + ret PARAMSIZE; + +L_extreme: // Extreme exponent. X is very large positive, very + // large negative, infinity, or NaN. + fxam; + fstsw AX; + test AX, 0x0400; // NaN_or_zero, but we already know x != 0 + jz L_was_nan; // if x is NaN, returns x + test AX, 0x0200; + jnz L_largenegative; +L_largepositive: + // Set scratchreal = real.max. + // squaring it will create infinity, and set overflow flag. + mov word ptr [ESP+8+8], 0x7FFE; + fstp ST(0); + fld real ptr [ESP+8]; // load scratchreal + fmul ST(0), ST; // square it, to create havoc! +L_was_nan: + add ESP,12+8; + ret PARAMSIZE; +L_largenegative: + fstp ST(0); + fld1; + fchs; // return -1. Underflow flag is not set. + add ESP,12+8; + ret PARAMSIZE; + } + } + else version (D_InlineAsm_X86_64) + { + asm pure nothrow @nogc + { + naked; + } + version (Win64) + { + asm pure nothrow @nogc + { + fld real ptr [RCX]; // x + mov AX,[RCX+8]; // AX = exponent and sign + } + } + else + { + asm pure nothrow @nogc + { + fld real ptr [RSP+8]; // x + mov AX,[RSP+8+8]; // AX = exponent and sign + } + } + asm pure nothrow @nogc + { + /* expm1() for x87 80-bit reals, IEEE754-2008 conformant. + * Author: Don Clugston. + * + * expm1(x) = 2^(rndint(y))* 2^(y-rndint(y)) - 1 where y = LN2*x. + * = 2rndy * 2ym1 + 2rndy - 1, where 2rndy = 2^(rndint(y)) + * and 2ym1 = (2^(y-rndint(y))-1). + * If 2rndy < 0.5*real.epsilon, result is -1. + * Implementation is otherwise the same as for exp2() + */ + sub RSP, 24; // Create scratch space on the stack + // [RSP,RSP+2] = scratchint + // [RSP+4..+6, +8..+10, +10] = scratchreal + // set scratchreal mantissa = 1.0 + mov dword ptr [RSP+8], 0; + mov dword ptr [RSP+8+4], 0x80000000; + and AX, 0x7FFF; // drop sign bit + cmp AX, 0x401D; // avoid InvalidException in fist + jae L_extreme; + fldl2e; + fmul ; // y = x*log2(e) + fist dword ptr [RSP]; // scratchint = rndint(y) + fisub dword ptr [RSP]; // y - rndint(y) + // and now set scratchreal exponent + mov EAX, [RSP]; + add EAX, 0x3fff; + jle short L_largenegative; + cmp EAX,0x8000; + jge short L_largepositive; + mov [RSP+8+8],AX; + f2xm1; // 2^(y-rndint(y)) -1 + fld real ptr [RSP+8] ; // 2^rndint(y) + fmul ST(1), ST; + fld1; + fsubp ST(1), ST; + fadd; + add RSP,24; + ret; + +L_extreme: // Extreme exponent. X is very large positive, very + // large negative, infinity, or NaN. + fxam; + fstsw AX; + test AX, 0x0400; // NaN_or_zero, but we already know x != 0 + jz L_was_nan; // if x is NaN, returns x + test AX, 0x0200; + jnz L_largenegative; +L_largepositive: + // Set scratchreal = real.max. + // squaring it will create infinity, and set overflow flag. + mov word ptr [RSP+8+8], 0x7FFE; + fstp ST(0); + fld real ptr [RSP+8]; // load scratchreal + fmul ST(0), ST; // square it, to create havoc! +L_was_nan: + add RSP,24; + ret; + +L_largenegative: + fstp ST(0); + fld1; + fchs; // return -1. Underflow flag is not set. + add RSP,24; + ret; + } + } + else + { + // Coefficients for exp(x) - 1 and overflow/underflow limits. + static if (floatTraits!real.realFormat == RealFormat.ieeeQuadruple) + { + static immutable real[8] P = [ + 2.943520915569954073888921213330863757240E8L, + -5.722847283900608941516165725053359168840E7L, + 8.944630806357575461578107295909719817253E6L, + -7.212432713558031519943281748462837065308E5L, + 4.578962475841642634225390068461943438441E4L, + -1.716772506388927649032068540558788106762E3L, + 4.401308817383362136048032038528753151144E1L, + -4.888737542888633647784737721812546636240E-1L + ]; + + static immutable real[9] Q = [ + 1.766112549341972444333352727998584753865E9L, + -7.848989743695296475743081255027098295771E8L, + 1.615869009634292424463780387327037251069E8L, + -2.019684072836541751428967854947019415698E7L, + 1.682912729190313538934190635536631941751E6L, + -9.615511549171441430850103489315371768998E4L, + 3.697714952261803935521187272204485251835E3L, + -8.802340681794263968892934703309274564037E1L, + 1.0 + ]; + + enum real OF = 1.1356523406294143949491931077970764891253E4L; + enum real UF = -1.143276959615573793352782661133116431383730e4L; + } + else + { + static immutable real[5] P = [ + -1.586135578666346600772998894928250240826E4L, + 2.642771505685952966904660652518429479531E3L, + -3.423199068835684263987132888286791620673E2L, + 1.800826371455042224581246202420972737840E1L, + -5.238523121205561042771939008061958820811E-1L, + ]; + static immutable real[6] Q = [ + -9.516813471998079611319047060563358064497E4L, + 3.964866271411091674556850458227710004570E4L, + -7.207678383830091850230366618190187434796E3L, + 7.206038318724600171970199625081491823079E2L, + -4.002027679107076077238836622982900945173E1L, + 1.0 + ]; + + enum real OF = 1.1356523406294143949492E4L; + enum real UF = -4.5054566736396445112120088E1L; + } + + + // C1 + C2 = LN2. + enum real C1 = 6.9314575195312500000000E-1L; + enum real C2 = 1.428606820309417232121458176568075500134E-6L; + + // Special cases. Raises an overflow flag, except in the case + // for CTFE, where there are no hardware controls. + if (x > OF) + { + if (__ctfe) + return real.infinity; + else + return real.max * copysign(real.max, real.infinity); + } + if (x == 0.0) + return x; + if (x < UF) + return -1.0; + + // Express x = LN2 (n + remainder), remainder not exceeding 1/2. + int n = cast(int) floor(0.5 + x / LN2); + x -= n * C1; + x -= n * C2; + + // Rational approximation: + // exp(x) - 1 = x + 0.5 x^^2 + x^^3 P(x) / Q(x) + real px = x * poly(x, P); + real qx = poly(x, Q); + const real xx = x * x; + qx = x + (0.5 * xx + xx * px / qx); + + // We have qx = exp(remainder LN2) - 1, so: + // exp(x) - 1 = 2^^n (qx + 1) - 1 = 2^^n qx + 2^^n - 1. + px = ldexp(1.0, n); + x = px * qx + (px - 1.0); + + return x; + } +} + + + +/** + * Calculates 2$(SUPERSCRIPT x). + * + * $(TABLE_SV + * $(TR $(TH x) $(TH exp2(x)) ) + * $(TR $(TD +$(INFIN)) $(TD +$(INFIN)) ) + * $(TR $(TD -$(INFIN)) $(TD +0.0) ) + * $(TR $(TD $(NAN)) $(TD $(NAN)) ) + * ) + */ +pragma(inline, true) +real exp2(real x) @nogc @trusted pure nothrow +{ + version (InlineAsm_X86_Any) + { + if (!__ctfe) + return exp2Asm(x); + else + return exp2Impl(x); + } + else + { + return exp2Impl(x); + } +} + +version (InlineAsm_X86_Any) +private real exp2Asm(real x) @nogc @trusted pure nothrow +{ + version (D_InlineAsm_X86) + { + enum PARAMSIZE = (real.sizeof+3)&(0xFFFF_FFFC); // always a multiple of 4 + + asm pure nothrow @nogc + { + /* exp2() for x87 80-bit reals, IEEE754-2008 conformant. + * Author: Don Clugston. + * + * exp2(x) = 2^^(rndint(x))* 2^^(y-rndint(x)) + * The trick for high performance is to avoid the fscale(28cycles on core2), + * frndint(19 cycles), leaving f2xm1(19 cycles) as the only slow instruction. + * + * We can do frndint by using fist. BUT we can't use it for huge numbers, + * because it will set the Invalid Operation flag if overflow or NaN occurs. + * Fortunately, whenever this happens the result would be zero or infinity. + * + * We can perform fscale by directly poking into the exponent. BUT this doesn't + * work for the (very rare) cases where the result is subnormal. So we fall back + * to the slow method in that case. + */ + naked; + fld real ptr [ESP+4] ; // x + mov AX, [ESP+4+8]; // AX = exponent and sign + sub ESP, 12+8; // Create scratch space on the stack + // [ESP,ESP+2] = scratchint + // [ESP+4..+6, +8..+10, +10] = scratchreal + // set scratchreal mantissa = 1.0 + mov dword ptr [ESP+8], 0; + mov dword ptr [ESP+8+4], 0x80000000; + and AX, 0x7FFF; // drop sign bit + cmp AX, 0x401D; // avoid InvalidException in fist + jae L_extreme; + fist dword ptr [ESP]; // scratchint = rndint(x) + fisub dword ptr [ESP]; // x - rndint(x) + // and now set scratchreal exponent + mov EAX, [ESP]; + add EAX, 0x3fff; + jle short L_subnormal; + cmp EAX,0x8000; + jge short L_overflow; + mov [ESP+8+8],AX; +L_normal: + f2xm1; + fld1; + faddp ST(1), ST; // 2^^(x-rndint(x)) + fld real ptr [ESP+8] ; // 2^^rndint(x) + add ESP,12+8; + fmulp ST(1), ST; + ret PARAMSIZE; + +L_subnormal: + // Result will be subnormal. + // In this rare case, the simple poking method doesn't work. + // The speed doesn't matter, so use the slow fscale method. + fild dword ptr [ESP]; // scratchint + fld1; + fscale; + fstp real ptr [ESP+8]; // scratchreal = 2^^scratchint + fstp ST(0); // drop scratchint + jmp L_normal; + +L_extreme: // Extreme exponent. X is very large positive, very + // large negative, infinity, or NaN. + fxam; + fstsw AX; + test AX, 0x0400; // NaN_or_zero, but we already know x != 0 + jz L_was_nan; // if x is NaN, returns x + // set scratchreal = real.min_normal + // squaring it will return 0, setting underflow flag + mov word ptr [ESP+8+8], 1; + test AX, 0x0200; + jnz L_waslargenegative; +L_overflow: + // Set scratchreal = real.max. + // squaring it will create infinity, and set overflow flag. + mov word ptr [ESP+8+8], 0x7FFE; +L_waslargenegative: + fstp ST(0); + fld real ptr [ESP+8]; // load scratchreal + fmul ST(0), ST; // square it, to create havoc! +L_was_nan: + add ESP,12+8; + ret PARAMSIZE; + } + } + else version (D_InlineAsm_X86_64) + { + asm pure nothrow @nogc + { + naked; + } + version (Win64) + { + asm pure nothrow @nogc + { + fld real ptr [RCX]; // x + mov AX,[RCX+8]; // AX = exponent and sign + } + } + else + { + asm pure nothrow @nogc + { + fld real ptr [RSP+8]; // x + mov AX,[RSP+8+8]; // AX = exponent and sign + } + } + asm pure nothrow @nogc + { + /* exp2() for x87 80-bit reals, IEEE754-2008 conformant. + * Author: Don Clugston. + * + * exp2(x) = 2^(rndint(x))* 2^(y-rndint(x)) + * The trick for high performance is to avoid the fscale(28cycles on core2), + * frndint(19 cycles), leaving f2xm1(19 cycles) as the only slow instruction. + * + * We can do frndint by using fist. BUT we can't use it for huge numbers, + * because it will set the Invalid Operation flag is overflow or NaN occurs. + * Fortunately, whenever this happens the result would be zero or infinity. + * + * We can perform fscale by directly poking into the exponent. BUT this doesn't + * work for the (very rare) cases where the result is subnormal. So we fall back + * to the slow method in that case. + */ + sub RSP, 24; // Create scratch space on the stack + // [RSP,RSP+2] = scratchint + // [RSP+4..+6, +8..+10, +10] = scratchreal + // set scratchreal mantissa = 1.0 + mov dword ptr [RSP+8], 0; + mov dword ptr [RSP+8+4], 0x80000000; + and AX, 0x7FFF; // drop sign bit + cmp AX, 0x401D; // avoid InvalidException in fist + jae L_extreme; + fist dword ptr [RSP]; // scratchint = rndint(x) + fisub dword ptr [RSP]; // x - rndint(x) + // and now set scratchreal exponent + mov EAX, [RSP]; + add EAX, 0x3fff; + jle short L_subnormal; + cmp EAX,0x8000; + jge short L_overflow; + mov [RSP+8+8],AX; +L_normal: + f2xm1; + fld1; + fadd; // 2^(x-rndint(x)) + fld real ptr [RSP+8] ; // 2^rndint(x) + add RSP,24; + fmulp ST(1), ST; + ret; + +L_subnormal: + // Result will be subnormal. + // In this rare case, the simple poking method doesn't work. + // The speed doesn't matter, so use the slow fscale method. + fild dword ptr [RSP]; // scratchint + fld1; + fscale; + fstp real ptr [RSP+8]; // scratchreal = 2^scratchint + fstp ST(0); // drop scratchint + jmp L_normal; + +L_extreme: // Extreme exponent. X is very large positive, very + // large negative, infinity, or NaN. + fxam; + fstsw AX; + test AX, 0x0400; // NaN_or_zero, but we already know x != 0 + jz L_was_nan; // if x is NaN, returns x + // set scratchreal = real.min + // squaring it will return 0, setting underflow flag + mov word ptr [RSP+8+8], 1; + test AX, 0x0200; + jnz L_waslargenegative; +L_overflow: + // Set scratchreal = real.max. + // squaring it will create infinity, and set overflow flag. + mov word ptr [RSP+8+8], 0x7FFE; +L_waslargenegative: + fstp ST(0); + fld real ptr [RSP+8]; // load scratchreal + fmul ST(0), ST; // square it, to create havoc! +L_was_nan: + add RSP,24; + ret; + } + } + else + static assert(0); +} + +private real exp2Impl(real x) @nogc @trusted pure nothrow +{ + // Coefficients for exp2(x) + static if (floatTraits!real.realFormat == RealFormat.ieeeQuadruple) + { + static immutable real[5] P = [ + 9.079594442980146270952372234833529694788E12L, + 1.530625323728429161131811299626419117557E11L, + 5.677513871931844661829755443994214173883E8L, + 6.185032670011643762127954396427045467506E5L, + 1.587171580015525194694938306936721666031E2L + ]; + + static immutable real[6] Q = [ + 2.619817175234089411411070339065679229869E13L, + 1.490560994263653042761789432690793026977E12L, + 1.092141473886177435056423606755843616331E10L, + 2.186249607051644894762167991800811827835E7L, + 1.236602014442099053716561665053645270207E4L, + 1.0 + ]; + } + else + { + static immutable real[3] P = [ + 2.0803843631901852422887E6L, + 3.0286971917562792508623E4L, + 6.0614853552242266094567E1L, + ]; + static immutable real[4] Q = [ + 6.0027204078348487957118E6L, + 3.2772515434906797273099E5L, + 1.7492876999891839021063E3L, + 1.0000000000000000000000E0L, + ]; + } + + // Overflow and Underflow limits. + enum real OF = 16_384.0L; + enum real UF = -16_382.0L; + + // Special cases. Raises an overflow or underflow flag accordingly, + // except in the case for CTFE, where there are no hardware controls. + if (isNaN(x)) + return x; + if (x > OF) + { + if (__ctfe) + return real.infinity; + else + return real.max * copysign(real.max, real.infinity); + } + if (x < UF) + { + if (__ctfe) + return 0.0; + else + return real.min_normal * copysign(real.min_normal, 0.0); + } + + // Separate into integer and fractional parts. + int n = cast(int) floor(x + 0.5); + x -= n; + + // Rational approximation: + // exp2(x) = 1.0 + 2x P(x^^2) / (Q(x^^2) - P(x^^2)) + const real xx = x * x; + const real px = x * poly(xx, P); + x = px / (poly(xx, Q) - px); + x = 1.0 + ldexp(x, 1); + + // Scale by power of 2. + x = ldexp(x, n); + + return x; +} + +/// +@safe unittest +{ + assert(feqrel(exp2(0.5L), SQRT2) >= real.mant_dig -1); + assert(exp2(8.0L) == 256.0); + assert(exp2(-9.0L)== 1.0L/512.0); +} + +@safe unittest +{ + version (CRuntime_Microsoft) {} else // aexp2/exp2f/exp2l not implemented + { + assert( core.stdc.math.exp2f(0.0f) == 1 ); + assert( core.stdc.math.exp2 (0.0) == 1 ); + assert( core.stdc.math.exp2l(0.0L) == 1 ); + } +} + +@system unittest +{ + FloatingPointControl ctrl; + if (FloatingPointControl.hasExceptionTraps) + ctrl.disableExceptions(FloatingPointControl.allExceptions); + ctrl.rounding = FloatingPointControl.roundToNearest; + + static if (real.mant_dig == 113) + { + static immutable real[2][] exptestpoints = + [ // x exp(x) + [ 1.0L, E ], + [ 0.5L, 0x1.a61298e1e069bc972dfefab6df34p+0L ], + [ 3.0L, E*E*E ], + [ 0x1.6p+13L, 0x1.6e509d45728655cdb4840542acb5p+16250L ], // near overflow + [ 0x1.7p+13L, real.infinity ], // close overflow + [ 0x1p+80L, real.infinity ], // far overflow + [ real.infinity, real.infinity ], + [-0x1.18p+13L, 0x1.5e4bf54b4807034ea97fef0059a6p-12927L ], // near underflow + [-0x1.625p+13L, 0x1.a6bd68a39d11fec3a250cd97f524p-16358L ], // ditto + [-0x1.62dafp+13L, 0x0.cb629e9813b80ed4d639e875be6cp-16382L ], // near underflow - subnormal + [-0x1.6549p+13L, 0x0.0000000000000000000000000001p-16382L ], // ditto + [-0x1.655p+13L, 0 ], // close underflow + [-0x1p+30L, 0 ], // far underflow + ]; + } + else static if (real.mant_dig == 64) // 80-bit reals + { + static immutable real[2][] exptestpoints = + [ // x exp(x) + [ 1.0L, E ], + [ 0.5L, 0x1.a61298e1e069bc97p+0L ], + [ 3.0L, E*E*E ], + [ 0x1.1p+13L, 0x1.29aeffefc8ec645p+12557L ], // near overflow + [ 0x1.7p+13L, real.infinity ], // close overflow + [ 0x1p+80L, real.infinity ], // far overflow + [ real.infinity, real.infinity ], + [-0x1.18p+13L, 0x1.5e4bf54b4806db9p-12927L ], // near underflow + [-0x1.625p+13L, 0x1.a6bd68a39d11f35cp-16358L ], // ditto + [-0x1.62dafp+13L, 0x1.96c53d30277021dp-16383L ], // near underflow - subnormal + [-0x1.643p+13L, 0x1p-16444L ], // ditto + [-0x1.645p+13L, 0 ], // close underflow + [-0x1p+30L, 0 ], // far underflow + ]; + } + else static if (real.mant_dig == 53) // 64-bit reals + { + static immutable real[2][] exptestpoints = + [ // x, exp(x) + [ 1.0L, E ], + [ 0.5L, 0x1.a61298e1e069cp+0L ], + [ 3.0L, E*E*E ], + [ 0x1.6p+9L, 0x1.93bf4ec282efbp+1015L ], // near overflow + [ 0x1.7p+9L, real.infinity ], // close overflow + [ 0x1p+80L, real.infinity ], // far overflow + [ real.infinity, real.infinity ], + [-0x1.6p+9L, 0x1.44a3824e5285fp-1016L ], // near underflow + [-0x1.64p+9L, 0x0.06f84920bb2d3p-1022L ], // near underflow - subnormal + [-0x1.743p+9L, 0x0.0000000000001p-1022L ], // ditto + [-0x1.8p+9L, 0 ], // close underflow + [-0x1p30L, 0 ], // far underflow + ]; + } + else + static assert(0, "No exp() tests for real type!"); + + const minEqualDecimalDigits = real.dig - 3; + real x; + IeeeFlags f; + foreach (ref pair; exptestpoints) + { + resetIeeeFlags(); + x = exp(pair[0]); + f = ieeeFlags; + assert(equalsDigit(x, pair[1], minEqualDecimalDigits)); + + version (IeeeFlagsSupport) + { + // Check the overflow bit + if (x == real.infinity) + { + // don't care about the overflow bit if input was inf + // (e.g., the LLVM intrinsic doesn't set it on Linux x86_64) + assert(pair[0] == real.infinity || f.overflow); + } + else + assert(!f.overflow); + // Check the underflow bit + assert(f.underflow == (fabs(x) < real.min_normal)); + // Invalid and div by zero shouldn't be affected. + assert(!f.invalid); + assert(!f.divByZero); + } + } + // Ideally, exp(0) would not set the inexact flag. + // Unfortunately, fldl2e sets it! + // So it's not realistic to avoid setting it. + assert(exp(0.0L) == 1.0); + + // NaN propagation. Doesn't set flags, bcos was already NaN. + resetIeeeFlags(); + x = exp(real.nan); + f = ieeeFlags; + assert(isIdentical(abs(x), real.nan)); + assert(f.flags == 0); + + resetIeeeFlags(); + x = exp(-real.nan); + f = ieeeFlags; + assert(isIdentical(abs(x), real.nan)); + assert(f.flags == 0); + + x = exp(NaN(0x123)); + assert(isIdentical(x, NaN(0x123))); + + // High resolution test (verified against GNU MPFR/Mathematica). + assert(exp(0.5L) == 0x1.A612_98E1_E069_BC97_2DFE_FAB6_DF34p+0L); +} + + +/** + * Calculate cos(y) + i sin(y). + * + * On many CPUs (such as x86), this is a very efficient operation; + * almost twice as fast as calculating sin(y) and cos(y) separately, + * and is the preferred method when both are required. + */ +creal expi(real y) @trusted pure nothrow @nogc +{ + version (InlineAsm_X86_Any) + { + version (Win64) + { + asm pure nothrow @nogc + { + naked; + fld real ptr [ECX]; + fsincos; + fxch ST(1), ST(0); + ret; + } + } + else + { + asm pure nothrow @nogc + { + fld y; + fsincos; + fxch ST(1), ST(0); + } + } + } + else + { + return cos(y) + sin(y)*1i; + } +} + +/// +@safe pure nothrow @nogc unittest +{ + assert(expi(1.3e5L) == cos(1.3e5L) + sin(1.3e5L) * 1i); + assert(expi(0.0L) == 1L + 0.0Li); +} + +/********************************************************************* + * Separate floating point value into significand and exponent. + * + * Returns: + * Calculate and return $(I x) and $(I exp) such that + * value =$(I x)*2$(SUPERSCRIPT exp) and + * .5 $(LT)= |$(I x)| $(LT) 1.0 + * + * $(I x) has same sign as value. + * + * $(TABLE_SV + * $(TR $(TH value) $(TH returns) $(TH exp)) + * $(TR $(TD $(PLUSMN)0.0) $(TD $(PLUSMN)0.0) $(TD 0)) + * $(TR $(TD +$(INFIN)) $(TD +$(INFIN)) $(TD int.max)) + * $(TR $(TD -$(INFIN)) $(TD -$(INFIN)) $(TD int.min)) + * $(TR $(TD $(PLUSMN)$(NAN)) $(TD $(PLUSMN)$(NAN)) $(TD int.min)) + * ) + */ +T frexp(T)(const T value, out int exp) @trusted pure nothrow @nogc +if (isFloatingPoint!T) +{ + Unqual!T vf = value; + ushort* vu = cast(ushort*)&vf; + static if (is(Unqual!T == float)) + int* vi = cast(int*)&vf; + else + long* vl = cast(long*)&vf; + int ex; + alias F = floatTraits!T; + + ex = vu[F.EXPPOS_SHORT] & F.EXPMASK; + static if (F.realFormat == RealFormat.ieeeExtended) + { + if (ex) + { // If exponent is non-zero + if (ex == F.EXPMASK) // infinity or NaN + { + if (*vl & 0x7FFF_FFFF_FFFF_FFFF) // NaN + { + *vl |= 0xC000_0000_0000_0000; // convert NaNS to NaNQ + exp = int.min; + } + else if (vu[F.EXPPOS_SHORT] & 0x8000) // negative infinity + exp = int.min; + else // positive infinity + exp = int.max; + + } + else + { + exp = ex - F.EXPBIAS; + vu[F.EXPPOS_SHORT] = (0x8000 & vu[F.EXPPOS_SHORT]) | 0x3FFE; + } + } + else if (!*vl) + { + // vf is +-0.0 + exp = 0; + } + else + { + // subnormal + + vf *= F.RECIP_EPSILON; + ex = vu[F.EXPPOS_SHORT] & F.EXPMASK; + exp = ex - F.EXPBIAS - T.mant_dig + 1; + vu[F.EXPPOS_SHORT] = ((-1 - F.EXPMASK) & vu[F.EXPPOS_SHORT]) | 0x3FFE; + } + return vf; + } + else static if (F.realFormat == RealFormat.ieeeQuadruple) + { + if (ex) // If exponent is non-zero + { + if (ex == F.EXPMASK) + { + // infinity or NaN + if (vl[MANTISSA_LSB] | + (vl[MANTISSA_MSB] & 0x0000_FFFF_FFFF_FFFF)) // NaN + { + // convert NaNS to NaNQ + vl[MANTISSA_MSB] |= 0x0000_8000_0000_0000; + exp = int.min; + } + else if (vu[F.EXPPOS_SHORT] & 0x8000) // negative infinity + exp = int.min; + else // positive infinity + exp = int.max; + } + else + { + exp = ex - F.EXPBIAS; + vu[F.EXPPOS_SHORT] = F.EXPBIAS | (0x8000 & vu[F.EXPPOS_SHORT]); + } + } + else if ((vl[MANTISSA_LSB] | + (vl[MANTISSA_MSB] & 0x0000_FFFF_FFFF_FFFF)) == 0) + { + // vf is +-0.0 + exp = 0; + } + else + { + // subnormal + vf *= F.RECIP_EPSILON; + ex = vu[F.EXPPOS_SHORT] & F.EXPMASK; + exp = ex - F.EXPBIAS - T.mant_dig + 1; + vu[F.EXPPOS_SHORT] = F.EXPBIAS | (0x8000 & vu[F.EXPPOS_SHORT]); + } + return vf; + } + else static if (F.realFormat == RealFormat.ieeeDouble) + { + if (ex) // If exponent is non-zero + { + if (ex == F.EXPMASK) // infinity or NaN + { + if (*vl == 0x7FF0_0000_0000_0000) // positive infinity + { + exp = int.max; + } + else if (*vl == 0xFFF0_0000_0000_0000) // negative infinity + exp = int.min; + else + { // NaN + *vl |= 0x0008_0000_0000_0000; // convert NaNS to NaNQ + exp = int.min; + } + } + else + { + exp = (ex - F.EXPBIAS) >> 4; + vu[F.EXPPOS_SHORT] = cast(ushort)((0x800F & vu[F.EXPPOS_SHORT]) | 0x3FE0); + } + } + else if (!(*vl & 0x7FFF_FFFF_FFFF_FFFF)) + { + // vf is +-0.0 + exp = 0; + } + else + { + // subnormal + vf *= F.RECIP_EPSILON; + ex = vu[F.EXPPOS_SHORT] & F.EXPMASK; + exp = ((ex - F.EXPBIAS) >> 4) - T.mant_dig + 1; + vu[F.EXPPOS_SHORT] = + cast(ushort)(((-1 - F.EXPMASK) & vu[F.EXPPOS_SHORT]) | 0x3FE0); + } + return vf; + } + else static if (F.realFormat == RealFormat.ieeeSingle) + { + if (ex) // If exponent is non-zero + { + if (ex == F.EXPMASK) // infinity or NaN + { + if (*vi == 0x7F80_0000) // positive infinity + { + exp = int.max; + } + else if (*vi == 0xFF80_0000) // negative infinity + exp = int.min; + else + { // NaN + *vi |= 0x0040_0000; // convert NaNS to NaNQ + exp = int.min; + } + } + else + { + exp = (ex - F.EXPBIAS) >> 7; + vu[F.EXPPOS_SHORT] = cast(ushort)((0x807F & vu[F.EXPPOS_SHORT]) | 0x3F00); + } + } + else if (!(*vi & 0x7FFF_FFFF)) + { + // vf is +-0.0 + exp = 0; + } + else + { + // subnormal + vf *= F.RECIP_EPSILON; + ex = vu[F.EXPPOS_SHORT] & F.EXPMASK; + exp = ((ex - F.EXPBIAS) >> 7) - T.mant_dig + 1; + vu[F.EXPPOS_SHORT] = + cast(ushort)(((-1 - F.EXPMASK) & vu[F.EXPPOS_SHORT]) | 0x3F00); + } + return vf; + } + else // static if (F.realFormat == RealFormat.ibmExtended) + { + assert(0, "frexp not implemented"); + } +} + +/// +@system unittest +{ + int exp; + real mantissa = frexp(123.456L, exp); + + // check if values are equal to 19 decimal digits of precision + assert(equalsDigit(mantissa * pow(2.0L, cast(real) exp), 123.456L, 19)); + + assert(frexp(-real.nan, exp) && exp == int.min); + assert(frexp(real.nan, exp) && exp == int.min); + assert(frexp(-real.infinity, exp) == -real.infinity && exp == int.min); + assert(frexp(real.infinity, exp) == real.infinity && exp == int.max); + assert(frexp(-0.0, exp) == -0.0 && exp == 0); + assert(frexp(0.0, exp) == 0.0 && exp == 0); +} + +@safe unittest +{ + import std.meta : AliasSeq; + import std.typecons : tuple, Tuple; + + foreach (T; AliasSeq!(real, double, float)) + { + Tuple!(T, T, int)[] vals = // x,frexp,exp + [ + tuple(T(0.0), T( 0.0 ), 0), + tuple(T(-0.0), T( -0.0), 0), + tuple(T(1.0), T( .5 ), 1), + tuple(T(-1.0), T( -.5 ), 1), + tuple(T(2.0), T( .5 ), 2), + tuple(T(float.min_normal/2.0f), T(.5), -126), + tuple(T.infinity, T.infinity, int.max), + tuple(-T.infinity, -T.infinity, int.min), + tuple(T.nan, T.nan, int.min), + tuple(-T.nan, -T.nan, int.min), + + // Phobos issue #16026: + tuple(3 * (T.min_normal * T.epsilon), T( .75), (T.min_exp - T.mant_dig) + 2) + ]; + + foreach (elem; vals) + { + T x = elem[0]; + T e = elem[1]; + int exp = elem[2]; + int eptr; + T v = frexp(x, eptr); + assert(isIdentical(e, v)); + assert(exp == eptr); + + } + + static if (floatTraits!(T).realFormat == RealFormat.ieeeExtended) + { + static T[3][] extendedvals = [ // x,frexp,exp + [0x1.a5f1c2eb3fe4efp+73L, 0x1.A5F1C2EB3FE4EFp-1L, 74], // normal + [0x1.fa01712e8f0471ap-1064L, 0x1.fa01712e8f0471ap-1L, -1063], + [T.min_normal, .5, -16381], + [T.min_normal/2.0L, .5, -16382] // subnormal + ]; + foreach (elem; extendedvals) + { + T x = elem[0]; + T e = elem[1]; + int exp = cast(int) elem[2]; + int eptr; + T v = frexp(x, eptr); + assert(isIdentical(e, v)); + assert(exp == eptr); + + } + } + } +} + +@safe unittest +{ + import std.meta : AliasSeq; + void foo() { + foreach (T; AliasSeq!(real, double, float)) + { + int exp; + const T a = 1; + immutable T b = 2; + auto c = frexp(a, exp); + auto d = frexp(b, exp); + } + } +} + +/****************************************** + * Extracts the exponent of x as a signed integral value. + * + * If x is not a special value, the result is the same as + * $(D cast(int) logb(x)). + * + * $(TABLE_SV + * $(TR $(TH x) $(TH ilogb(x)) $(TH Range error?)) + * $(TR $(TD 0) $(TD FP_ILOGB0) $(TD yes)) + * $(TR $(TD $(PLUSMN)$(INFIN)) $(TD int.max) $(TD no)) + * $(TR $(TD $(NAN)) $(TD FP_ILOGBNAN) $(TD no)) + * ) + */ +int ilogb(T)(const T x) @trusted pure nothrow @nogc +if (isFloatingPoint!T) +{ + import core.bitop : bsr; + alias F = floatTraits!T; + + union floatBits + { + T rv; + ushort[T.sizeof/2] vu; + uint[T.sizeof/4] vui; + static if (T.sizeof >= 8) + ulong[T.sizeof/8] vul; + } + floatBits y = void; + y.rv = x; + + int ex = y.vu[F.EXPPOS_SHORT] & F.EXPMASK; + static if (F.realFormat == RealFormat.ieeeExtended) + { + if (ex) + { + // If exponent is non-zero + if (ex == F.EXPMASK) // infinity or NaN + { + if (y.vul[0] & 0x7FFF_FFFF_FFFF_FFFF) // NaN + return FP_ILOGBNAN; + else // +-infinity + return int.max; + } + else + { + return ex - F.EXPBIAS - 1; + } + } + else if (!y.vul[0]) + { + // vf is +-0.0 + return FP_ILOGB0; + } + else + { + // subnormal + return ex - F.EXPBIAS - T.mant_dig + 1 + bsr(y.vul[0]); + } + } + else static if (F.realFormat == RealFormat.ieeeQuadruple) + { + if (ex) // If exponent is non-zero + { + if (ex == F.EXPMASK) + { + // infinity or NaN + if (y.vul[MANTISSA_LSB] | ( y.vul[MANTISSA_MSB] & 0x0000_FFFF_FFFF_FFFF)) // NaN + return FP_ILOGBNAN; + else // +- infinity + return int.max; + } + else + { + return ex - F.EXPBIAS - 1; + } + } + else if ((y.vul[MANTISSA_LSB] | (y.vul[MANTISSA_MSB] & 0x0000_FFFF_FFFF_FFFF)) == 0) + { + // vf is +-0.0 + return FP_ILOGB0; + } + else + { + // subnormal + const ulong msb = y.vul[MANTISSA_MSB] & 0x0000_FFFF_FFFF_FFFF; + const ulong lsb = y.vul[MANTISSA_LSB]; + if (msb) + return ex - F.EXPBIAS - T.mant_dig + 1 + bsr(msb) + 64; + else + return ex - F.EXPBIAS - T.mant_dig + 1 + bsr(lsb); + } + } + else static if (F.realFormat == RealFormat.ieeeDouble) + { + if (ex) // If exponent is non-zero + { + if (ex == F.EXPMASK) // infinity or NaN + { + if ((y.vul[0] & 0x7FFF_FFFF_FFFF_FFFF) == 0x7FF0_0000_0000_0000) // +- infinity + return int.max; + else // NaN + return FP_ILOGBNAN; + } + else + { + return ((ex - F.EXPBIAS) >> 4) - 1; + } + } + else if (!(y.vul[0] & 0x7FFF_FFFF_FFFF_FFFF)) + { + // vf is +-0.0 + return FP_ILOGB0; + } + else + { + // subnormal + enum MANTISSAMASK_64 = ((cast(ulong) F.MANTISSAMASK_INT) << 32) | 0xFFFF_FFFF; + return ((ex - F.EXPBIAS) >> 4) - T.mant_dig + 1 + bsr(y.vul[0] & MANTISSAMASK_64); + } + } + else static if (F.realFormat == RealFormat.ieeeSingle) + { + if (ex) // If exponent is non-zero + { + if (ex == F.EXPMASK) // infinity or NaN + { + if ((y.vui[0] & 0x7FFF_FFFF) == 0x7F80_0000) // +- infinity + return int.max; + else // NaN + return FP_ILOGBNAN; + } + else + { + return ((ex - F.EXPBIAS) >> 7) - 1; + } + } + else if (!(y.vui[0] & 0x7FFF_FFFF)) + { + // vf is +-0.0 + return FP_ILOGB0; + } + else + { + // subnormal + const uint mantissa = y.vui[0] & F.MANTISSAMASK_INT; + return ((ex - F.EXPBIAS) >> 7) - T.mant_dig + 1 + bsr(mantissa); + } + } + else // static if (F.realFormat == RealFormat.ibmExtended) + { + core.stdc.math.ilogbl(x); + } +} +/// ditto +int ilogb(T)(const T x) @safe pure nothrow @nogc +if (isIntegral!T && isUnsigned!T) +{ + import core.bitop : bsr; + if (x == 0) + return FP_ILOGB0; + else + { + static assert(T.sizeof <= ulong.sizeof, "integer size too large for the current ilogb implementation"); + return bsr(x); + } +} +/// ditto +int ilogb(T)(const T x) @safe pure nothrow @nogc +if (isIntegral!T && isSigned!T) +{ + import std.traits : Unsigned; + // Note: abs(x) can not be used because the return type is not Unsigned and + // the return value would be wrong for x == int.min + Unsigned!T absx = x >= 0 ? x : -x; + return ilogb(absx); +} + +alias FP_ILOGB0 = core.stdc.math.FP_ILOGB0; +alias FP_ILOGBNAN = core.stdc.math.FP_ILOGBNAN; + +@system nothrow @nogc unittest +{ + import std.meta : AliasSeq; + import std.typecons : Tuple; + foreach (F; AliasSeq!(float, double, real)) + { + alias T = Tuple!(F, int); + T[13] vals = // x, ilogb(x) + [ + T( F.nan , FP_ILOGBNAN ), + T( -F.nan , FP_ILOGBNAN ), + T( F.infinity, int.max ), + T( -F.infinity, int.max ), + T( 0.0 , FP_ILOGB0 ), + T( -0.0 , FP_ILOGB0 ), + T( 2.0 , 1 ), + T( 2.0001 , 1 ), + T( 1.9999 , 0 ), + T( 0.5 , -1 ), + T( 123.123 , 6 ), + T( -123.123 , 6 ), + T( 0.123 , -4 ), + ]; + + foreach (elem; vals) + { + assert(ilogb(elem[0]) == elem[1]); + } + } + + // min_normal and subnormals + assert(ilogb(-float.min_normal) == -126); + assert(ilogb(nextUp(-float.min_normal)) == -127); + assert(ilogb(nextUp(-float(0.0))) == -149); + assert(ilogb(-double.min_normal) == -1022); + assert(ilogb(nextUp(-double.min_normal)) == -1023); + assert(ilogb(nextUp(-double(0.0))) == -1074); + static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended) + { + assert(ilogb(-real.min_normal) == -16382); + assert(ilogb(nextUp(-real.min_normal)) == -16383); + assert(ilogb(nextUp(-real(0.0))) == -16445); + } + else static if (floatTraits!(real).realFormat == RealFormat.ieeeDouble) + { + assert(ilogb(-real.min_normal) == -1022); + assert(ilogb(nextUp(-real.min_normal)) == -1023); + assert(ilogb(nextUp(-real(0.0))) == -1074); + } + + // test integer types + assert(ilogb(0) == FP_ILOGB0); + assert(ilogb(int.max) == 30); + assert(ilogb(int.min) == 31); + assert(ilogb(uint.max) == 31); + assert(ilogb(long.max) == 62); + assert(ilogb(long.min) == 63); + assert(ilogb(ulong.max) == 63); +} + +/******************************************* + * Compute n * 2$(SUPERSCRIPT exp) + * References: frexp + */ + +real ldexp(real n, int exp) @nogc @safe pure nothrow { pragma(inline, true); return core.math.ldexp(n, exp); } +//FIXME +///ditto +double ldexp(double n, int exp) @safe pure nothrow @nogc { return ldexp(cast(real) n, exp); } +//FIXME +///ditto +float ldexp(float n, int exp) @safe pure nothrow @nogc { return ldexp(cast(real) n, exp); } + +/// +@nogc @safe pure nothrow unittest +{ + import std.meta : AliasSeq; + foreach (T; AliasSeq!(float, double, real)) + { + T r; + + r = ldexp(3.0L, 3); + assert(r == 24); + + r = ldexp(cast(T) 3.0, cast(int) 3); + assert(r == 24); + + T n = 3.0; + int exp = 3; + r = ldexp(n, exp); + assert(r == 24); + } +} + +@safe pure nothrow @nogc unittest +{ + static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended) + { + assert(ldexp(1.0L, -16384) == 0x1p-16384L); + assert(ldexp(1.0L, -16382) == 0x1p-16382L); + int x; + real n = frexp(0x1p-16384L, x); + assert(n == 0.5L); + assert(x==-16383); + assert(ldexp(n, x)==0x1p-16384L); + } + else static if (floatTraits!(real).realFormat == RealFormat.ieeeDouble) + { + assert(ldexp(1.0L, -1024) == 0x1p-1024L); + assert(ldexp(1.0L, -1022) == 0x1p-1022L); + int x; + real n = frexp(0x1p-1024L, x); + assert(n == 0.5L); + assert(x==-1023); + assert(ldexp(n, x)==0x1p-1024L); + } + else static assert(false, "Floating point type real not supported"); +} + +/* workaround Issue 14718, float parsing depends on platform strtold +@safe pure nothrow @nogc unittest +{ + assert(ldexp(1.0, -1024) == 0x1p-1024); + assert(ldexp(1.0, -1022) == 0x1p-1022); + int x; + double n = frexp(0x1p-1024, x); + assert(n == 0.5); + assert(x==-1023); + assert(ldexp(n, x)==0x1p-1024); +} + +@safe pure nothrow @nogc unittest +{ + assert(ldexp(1.0f, -128) == 0x1p-128f); + assert(ldexp(1.0f, -126) == 0x1p-126f); + int x; + float n = frexp(0x1p-128f, x); + assert(n == 0.5f); + assert(x==-127); + assert(ldexp(n, x)==0x1p-128f); +} +*/ + +@system unittest +{ + static real[3][] vals = // value,exp,ldexp + [ + [ 0, 0, 0], + [ 1, 0, 1], + [ -1, 0, -1], + [ 1, 1, 2], + [ 123, 10, 125952], + [ real.max, int.max, real.infinity], + [ real.max, -int.max, 0], + [ real.min_normal, -int.max, 0], + ]; + int i; + + for (i = 0; i < vals.length; i++) + { + real x = vals[i][0]; + int exp = cast(int) vals[i][1]; + real z = vals[i][2]; + real l = ldexp(x, exp); + + assert(equalsDigit(z, l, 7)); + } + + real function(real, int) pldexp = &ldexp; + assert(pldexp != null); +} + +private +{ + version (INLINE_YL2X) {} else + { + static if (floatTraits!real.realFormat == RealFormat.ieeeQuadruple) + { + // Coefficients for log(1 + x) = x - x**2/2 + x**3 P(x)/Q(x) + static immutable real[13] logCoeffsP = [ + 1.313572404063446165910279910527789794488E4L, + 7.771154681358524243729929227226708890930E4L, + 2.014652742082537582487669938141683759923E5L, + 3.007007295140399532324943111654767187848E5L, + 2.854829159639697837788887080758954924001E5L, + 1.797628303815655343403735250238293741397E5L, + 7.594356839258970405033155585486712125861E4L, + 2.128857716871515081352991964243375186031E4L, + 3.824952356185897735160588078446136783779E3L, + 4.114517881637811823002128927449878962058E2L, + 2.321125933898420063925789532045674660756E1L, + 4.998469661968096229986658302195402690910E-1L, + 1.538612243596254322971797716843006400388E-6L + ]; + static immutable real[13] logCoeffsQ = [ + 3.940717212190338497730839731583397586124E4L, + 2.626900195321832660448791748036714883242E5L, + 7.777690340007566932935753241556479363645E5L, + 1.347518538384329112529391120390701166528E6L, + 1.514882452993549494932585972882995548426E6L, + 1.158019977462989115839826904108208787040E6L, + 6.132189329546557743179177159925690841200E5L, + 2.248234257620569139969141618556349415120E5L, + 5.605842085972455027590989944010492125825E4L, + 9.147150349299596453976674231612674085381E3L, + 9.104928120962988414618126155557301584078E2L, + 4.839208193348159620282142911143429644326E1L, + 1.0 + ]; + + // Coefficients for log(x) = z + z^3 P(z^2)/Q(z^2) + // where z = 2(x-1)/(x+1) + static immutable real[6] logCoeffsR = [ + -8.828896441624934385266096344596648080902E-1L, + 8.057002716646055371965756206836056074715E1L, + -2.024301798136027039250415126250455056397E3L, + 2.048819892795278657810231591630928516206E4L, + -8.977257995689735303686582344659576526998E4L, + 1.418134209872192732479751274970992665513E5L + ]; + static immutable real[6] logCoeffsS = [ + 1.701761051846631278975701529965589676574E6L + -1.332535117259762928288745111081235577029E6L, + 4.001557694070773974936904547424676279307E5L, + -5.748542087379434595104154610899551484314E4L, + 3.998526750980007367835804959888064681098E3L, + -1.186359407982897997337150403816839480438E2L, + 1.0 + ]; + } + else + { + // Coefficients for log(1 + x) = x - x**2/2 + x**3 P(x)/Q(x) + static immutable real[7] logCoeffsP = [ + 2.0039553499201281259648E1L, + 5.7112963590585538103336E1L, + 6.0949667980987787057556E1L, + 2.9911919328553073277375E1L, + 6.5787325942061044846969E0L, + 4.9854102823193375972212E-1L, + 4.5270000862445199635215E-5L, + ]; + static immutable real[7] logCoeffsQ = [ + 6.0118660497603843919306E1L, + 2.1642788614495947685003E2L, + 3.0909872225312059774938E2L, + 2.2176239823732856465394E2L, + 8.3047565967967209469434E1L, + 1.5062909083469192043167E1L, + 1.0000000000000000000000E0L, + ]; + + // Coefficients for log(x) = z + z^3 P(z^2)/Q(z^2) + // where z = 2(x-1)/(x+1) + static immutable real[4] logCoeffsR = [ + -3.5717684488096787370998E1L, + 1.0777257190312272158094E1L, + -7.1990767473014147232598E-1L, + 1.9757429581415468984296E-3L, + ]; + static immutable real[4] logCoeffsS = [ + -4.2861221385716144629696E2L, + 1.9361891836232102174846E2L, + -2.6201045551331104417768E1L, + 1.0000000000000000000000E0L, + ]; + } + } +} + +/************************************** + * Calculate the natural logarithm of x. + * + * $(TABLE_SV + * $(TR $(TH x) $(TH log(x)) $(TH divide by 0?) $(TH invalid?)) + * $(TR $(TD $(PLUSMN)0.0) $(TD -$(INFIN)) $(TD yes) $(TD no)) + * $(TR $(TD $(LT)0.0) $(TD $(NAN)) $(TD no) $(TD yes)) + * $(TR $(TD +$(INFIN)) $(TD +$(INFIN)) $(TD no) $(TD no)) + * ) + */ +real log(real x) @safe pure nothrow @nogc +{ + version (INLINE_YL2X) + return core.math.yl2x(x, LN2); + else + { + // C1 + C2 = LN2. + enum real C1 = 6.93145751953125E-1L; + enum real C2 = 1.428606820309417232121458176568075500134E-6L; + + // Special cases. + if (isNaN(x)) + return x; + if (isInfinity(x) && !signbit(x)) + return x; + if (x == 0.0) + return -real.infinity; + if (x < 0.0) + return real.nan; + + // Separate mantissa from exponent. + // Note, frexp is used so that denormal numbers will be handled properly. + real y, z; + int exp; + + x = frexp(x, exp); + + // Logarithm using log(x) = z + z^^3 R(z) / S(z), + // where z = 2(x - 1)/(x + 1) + if ((exp > 2) || (exp < -2)) + { + if (x < SQRT1_2) + { // 2(2x - 1)/(2x + 1) + exp -= 1; + z = x - 0.5; + y = 0.5 * z + 0.5; + } + else + { // 2(x - 1)/(x + 1) + z = x - 0.5; + z -= 0.5; + y = 0.5 * x + 0.5; + } + x = z / y; + z = x * x; + z = x * (z * poly(z, logCoeffsR) / poly(z, logCoeffsS)); + z += exp * C2; + z += x; + z += exp * C1; + + return z; + } + + // Logarithm using log(1 + x) = x - .5x^^2 + x^^3 P(x) / Q(x) + if (x < SQRT1_2) + { // 2x - 1 + exp -= 1; + x = ldexp(x, 1) - 1.0; + } + else + { + x = x - 1.0; + } + z = x * x; + y = x * (z * poly(x, logCoeffsP) / poly(x, logCoeffsQ)); + y += exp * C2; + z = y - ldexp(z, -1); + + // Note, the sum of above terms does not exceed x/4, + // so it contributes at most about 1/4 lsb to the error. + z += x; + z += exp * C1; + + return z; + } +} + +/// +@safe pure nothrow @nogc unittest +{ + assert(log(E) == 1); +} + +/************************************** + * Calculate the base-10 logarithm of x. + * + * $(TABLE_SV + * $(TR $(TH x) $(TH log10(x)) $(TH divide by 0?) $(TH invalid?)) + * $(TR $(TD $(PLUSMN)0.0) $(TD -$(INFIN)) $(TD yes) $(TD no)) + * $(TR $(TD $(LT)0.0) $(TD $(NAN)) $(TD no) $(TD yes)) + * $(TR $(TD +$(INFIN)) $(TD +$(INFIN)) $(TD no) $(TD no)) + * ) + */ +real log10(real x) @safe pure nothrow @nogc +{ + version (INLINE_YL2X) + return core.math.yl2x(x, LOG2); + else + { + // log10(2) split into two parts. + enum real L102A = 0.3125L; + enum real L102B = -1.14700043360188047862611052755069732318101185E-2L; + + // log10(e) split into two parts. + enum real L10EA = 0.5L; + enum real L10EB = -6.570551809674817234887108108339491770560299E-2L; + + // Special cases are the same as for log. + if (isNaN(x)) + return x; + if (isInfinity(x) && !signbit(x)) + return x; + if (x == 0.0) + return -real.infinity; + if (x < 0.0) + return real.nan; + + // Separate mantissa from exponent. + // Note, frexp is used so that denormal numbers will be handled properly. + real y, z; + int exp; + + x = frexp(x, exp); + + // Logarithm using log(x) = z + z^^3 R(z) / S(z), + // where z = 2(x - 1)/(x + 1) + if ((exp > 2) || (exp < -2)) + { + if (x < SQRT1_2) + { // 2(2x - 1)/(2x + 1) + exp -= 1; + z = x - 0.5; + y = 0.5 * z + 0.5; + } + else + { // 2(x - 1)/(x + 1) + z = x - 0.5; + z -= 0.5; + y = 0.5 * x + 0.5; + } + x = z / y; + z = x * x; + y = x * (z * poly(z, logCoeffsR) / poly(z, logCoeffsS)); + goto Ldone; + } + + // Logarithm using log(1 + x) = x - .5x^^2 + x^^3 P(x) / Q(x) + if (x < SQRT1_2) + { // 2x - 1 + exp -= 1; + x = ldexp(x, 1) - 1.0; + } + else + x = x - 1.0; + + z = x * x; + y = x * (z * poly(x, logCoeffsP) / poly(x, logCoeffsQ)); + y = y - ldexp(z, -1); + + // Multiply log of fraction by log10(e) and base 2 exponent by log10(2). + // This sequence of operations is critical and it may be horribly + // defeated by some compiler optimizers. + Ldone: + z = y * L10EB; + z += x * L10EB; + z += exp * L102B; + z += y * L10EA; + z += x * L10EA; + z += exp * L102A; + + return z; + } +} + +/// +@safe pure nothrow @nogc unittest +{ + assert(fabs(log10(1000) - 3) < .000001); +} + +/****************************************** + * Calculates the natural logarithm of 1 + x. + * + * For very small x, log1p(x) will be more accurate than + * log(1 + x). + * + * $(TABLE_SV + * $(TR $(TH x) $(TH log1p(x)) $(TH divide by 0?) $(TH invalid?)) + * $(TR $(TD $(PLUSMN)0.0) $(TD $(PLUSMN)0.0) $(TD no) $(TD no)) + * $(TR $(TD -1.0) $(TD -$(INFIN)) $(TD yes) $(TD no)) + * $(TR $(TD $(LT)-1.0) $(TD $(NAN)) $(TD no) $(TD yes)) + * $(TR $(TD +$(INFIN)) $(TD -$(INFIN)) $(TD no) $(TD no)) + * ) + */ +real log1p(real x) @safe pure nothrow @nogc +{ + version (INLINE_YL2X) + { + // On x87, yl2xp1 is valid if and only if -0.5 <= lg(x) <= 0.5, + // ie if -0.29 <= x <= 0.414 + return (fabs(x) <= 0.25) ? core.math.yl2xp1(x, LN2) : core.math.yl2x(x+1, LN2); + } + else + { + // Special cases. + if (isNaN(x) || x == 0.0) + return x; + if (isInfinity(x) && !signbit(x)) + return x; + if (x == -1.0) + return -real.infinity; + if (x < -1.0) + return real.nan; + + return log(x + 1.0); + } +} + +/*************************************** + * Calculates the base-2 logarithm of x: + * $(SUB log, 2)x + * + * $(TABLE_SV + * $(TR $(TH x) $(TH log2(x)) $(TH divide by 0?) $(TH invalid?)) + * $(TR $(TD $(PLUSMN)0.0) $(TD -$(INFIN)) $(TD yes) $(TD no) ) + * $(TR $(TD $(LT)0.0) $(TD $(NAN)) $(TD no) $(TD yes) ) + * $(TR $(TD +$(INFIN)) $(TD +$(INFIN)) $(TD no) $(TD no) ) + * ) + */ +real log2(real x) @safe pure nothrow @nogc +{ + version (INLINE_YL2X) + return core.math.yl2x(x, 1); + else + { + // Special cases are the same as for log. + if (isNaN(x)) + return x; + if (isInfinity(x) && !signbit(x)) + return x; + if (x == 0.0) + return -real.infinity; + if (x < 0.0) + return real.nan; + + // Separate mantissa from exponent. + // Note, frexp is used so that denormal numbers will be handled properly. + real y, z; + int exp; + + x = frexp(x, exp); + + // Logarithm using log(x) = z + z^^3 R(z) / S(z), + // where z = 2(x - 1)/(x + 1) + if ((exp > 2) || (exp < -2)) + { + if (x < SQRT1_2) + { // 2(2x - 1)/(2x + 1) + exp -= 1; + z = x - 0.5; + y = 0.5 * z + 0.5; + } + else + { // 2(x - 1)/(x + 1) + z = x - 0.5; + z -= 0.5; + y = 0.5 * x + 0.5; + } + x = z / y; + z = x * x; + y = x * (z * poly(z, logCoeffsR) / poly(z, logCoeffsS)); + goto Ldone; + } + + // Logarithm using log(1 + x) = x - .5x^^2 + x^^3 P(x) / Q(x) + if (x < SQRT1_2) + { // 2x - 1 + exp -= 1; + x = ldexp(x, 1) - 1.0; + } + else + x = x - 1.0; + + z = x * x; + y = x * (z * poly(x, logCoeffsP) / poly(x, logCoeffsQ)); + y = y - ldexp(z, -1); + + // Multiply log of fraction by log10(e) and base 2 exponent by log10(2). + // This sequence of operations is critical and it may be horribly + // defeated by some compiler optimizers. + Ldone: + z = y * (LOG2E - 1.0); + z += x * (LOG2E - 1.0); + z += y; + z += x; + z += exp; + + return z; + } +} + +/// +@system unittest +{ + // check if values are equal to 19 decimal digits of precision + assert(equalsDigit(log2(1024.0L), 10, 19)); +} + +/***************************************** + * Extracts the exponent of x as a signed integral value. + * + * If x is subnormal, it is treated as if it were normalized. + * For a positive, finite x: + * + * 1 $(LT)= $(I x) * FLT_RADIX$(SUPERSCRIPT -logb(x)) $(LT) FLT_RADIX + * + * $(TABLE_SV + * $(TR $(TH x) $(TH logb(x)) $(TH divide by 0?) ) + * $(TR $(TD $(PLUSMN)$(INFIN)) $(TD +$(INFIN)) $(TD no)) + * $(TR $(TD $(PLUSMN)0.0) $(TD -$(INFIN)) $(TD yes) ) + * ) + */ +real logb(real x) @trusted nothrow @nogc +{ + version (Win64_DMD_InlineAsm) + { + asm pure nothrow @nogc + { + naked ; + fld real ptr [RCX] ; + fxtract ; + fstp ST(0) ; + ret ; + } + } + else version (CRuntime_Microsoft) + { + asm pure nothrow @nogc + { + fld x ; + fxtract ; + fstp ST(0) ; + } + } + else + return core.stdc.math.logbl(x); +} + +/************************************ + * Calculates the remainder from the calculation x/y. + * Returns: + * The value of x - i * y, where i is the number of times that y can + * be completely subtracted from x. The result has the same sign as x. + * + * $(TABLE_SV + * $(TR $(TH x) $(TH y) $(TH fmod(x, y)) $(TH invalid?)) + * $(TR $(TD $(PLUSMN)0.0) $(TD not 0.0) $(TD $(PLUSMN)0.0) $(TD no)) + * $(TR $(TD $(PLUSMNINF)) $(TD anything) $(TD $(NAN)) $(TD yes)) + * $(TR $(TD anything) $(TD $(PLUSMN)0.0) $(TD $(NAN)) $(TD yes)) + * $(TR $(TD !=$(PLUSMNINF)) $(TD $(PLUSMNINF)) $(TD x) $(TD no)) + * ) + */ +real fmod(real x, real y) @trusted nothrow @nogc +{ + version (CRuntime_Microsoft) + { + return x % y; + } + else + return core.stdc.math.fmodl(x, y); +} + +/************************************ + * Breaks x into an integral part and a fractional part, each of which has + * the same sign as x. The integral part is stored in i. + * Returns: + * The fractional part of x. + * + * $(TABLE_SV + * $(TR $(TH x) $(TH i (on input)) $(TH modf(x, i)) $(TH i (on return))) + * $(TR $(TD $(PLUSMNINF)) $(TD anything) $(TD $(PLUSMN)0.0) $(TD $(PLUSMNINF))) + * ) + */ +real modf(real x, ref real i) @trusted nothrow @nogc +{ + version (CRuntime_Microsoft) + { + i = trunc(x); + return copysign(isInfinity(x) ? 0.0 : x - i, x); + } + else + return core.stdc.math.modfl(x,&i); +} + +/************************************* + * Efficiently calculates x * 2$(SUPERSCRIPT n). + * + * scalbn handles underflow and overflow in + * the same fashion as the basic arithmetic operators. + * + * $(TABLE_SV + * $(TR $(TH x) $(TH scalb(x))) + * $(TR $(TD $(PLUSMNINF)) $(TD $(PLUSMNINF)) ) + * $(TR $(TD $(PLUSMN)0.0) $(TD $(PLUSMN)0.0) ) + * ) + */ +real scalbn(real x, int n) @trusted nothrow @nogc +{ + version (InlineAsm_X86_Any) + { + // scalbnl is not supported on DMD-Windows, so use asm pure nothrow @nogc. + version (Win64) + { + asm pure nothrow @nogc { + naked ; + mov 16[RSP],RCX ; + fild word ptr 16[RSP] ; + fld real ptr [RDX] ; + fscale ; + fstp ST(1) ; + ret ; + } + } + else + { + asm pure nothrow @nogc { + fild n; + fld x; + fscale; + fstp ST(1); + } + } + } + else + { + return core.stdc.math.scalbnl(x, n); + } +} + +/// +@safe nothrow @nogc unittest +{ + assert(scalbn(-real.infinity, 5) == -real.infinity); +} + +/*************** + * Calculates the cube root of x. + * + * $(TABLE_SV + * $(TR $(TH $(I x)) $(TH cbrt(x)) $(TH invalid?)) + * $(TR $(TD $(PLUSMN)0.0) $(TD $(PLUSMN)0.0) $(TD no) ) + * $(TR $(TD $(NAN)) $(TD $(NAN)) $(TD yes) ) + * $(TR $(TD $(PLUSMN)$(INFIN)) $(TD $(PLUSMN)$(INFIN)) $(TD no) ) + * ) + */ +real cbrt(real x) @trusted nothrow @nogc +{ + version (CRuntime_Microsoft) + { + version (INLINE_YL2X) + return copysign(exp2(core.math.yl2x(fabs(x), 1.0L/3.0L)), x); + else + return core.stdc.math.cbrtl(x); + } + else + return core.stdc.math.cbrtl(x); +} + + +/******************************* + * Returns |x| + * + * $(TABLE_SV + * $(TR $(TH x) $(TH fabs(x))) + * $(TR $(TD $(PLUSMN)0.0) $(TD +0.0) ) + * $(TR $(TD $(PLUSMN)$(INFIN)) $(TD +$(INFIN)) ) + * ) + */ +real fabs(real x) @safe pure nothrow @nogc { pragma(inline, true); return core.math.fabs(x); } +//FIXME +///ditto +double fabs(double x) @safe pure nothrow @nogc { return fabs(cast(real) x); } +//FIXME +///ditto +float fabs(float x) @safe pure nothrow @nogc { return fabs(cast(real) x); } + +@safe unittest +{ + real function(real) pfabs = &fabs; + assert(pfabs != null); +} + +/*********************************************************************** + * Calculates the length of the + * hypotenuse of a right-angled triangle with sides of length x and y. + * The hypotenuse is the value of the square root of + * the sums of the squares of x and y: + * + * sqrt($(POWER x, 2) + $(POWER y, 2)) + * + * Note that hypot(x, y), hypot(y, x) and + * hypot(x, -y) are equivalent. + * + * $(TABLE_SV + * $(TR $(TH x) $(TH y) $(TH hypot(x, y)) $(TH invalid?)) + * $(TR $(TD x) $(TD $(PLUSMN)0.0) $(TD |x|) $(TD no)) + * $(TR $(TD $(PLUSMNINF)) $(TD y) $(TD +$(INFIN)) $(TD no)) + * $(TR $(TD $(PLUSMNINF)) $(TD $(NAN)) $(TD +$(INFIN)) $(TD no)) + * ) + */ + +real hypot(real x, real y) @safe pure nothrow @nogc +{ + // Scale x and y to avoid underflow and overflow. + // If one is huge and the other tiny, return the larger. + // If both are huge, avoid overflow by scaling by 1/sqrt(real.max/2). + // If both are tiny, avoid underflow by scaling by sqrt(real.min_normal*real.epsilon). + + enum real SQRTMIN = 0.5 * sqrt(real.min_normal); // This is a power of 2. + enum real SQRTMAX = 1.0L / SQRTMIN; // 2^^((max_exp)/2) = nextUp(sqrt(real.max)) + + static assert(2*(SQRTMAX/2)*(SQRTMAX/2) <= real.max); + + // Proves that sqrt(real.max) ~~ 0.5/sqrt(real.min_normal) + static assert(real.min_normal*real.max > 2 && real.min_normal*real.max <= 4); + + real u = fabs(x); + real v = fabs(y); + if (!(u >= v)) // check for NaN as well. + { + v = u; + u = fabs(y); + if (u == real.infinity) return u; // hypot(inf, nan) == inf + if (v == real.infinity) return v; // hypot(nan, inf) == inf + } + + // Now u >= v, or else one is NaN. + if (v >= SQRTMAX*0.5) + { + // hypot(huge, huge) -- avoid overflow + u *= SQRTMIN*0.5; + v *= SQRTMIN*0.5; + return sqrt(u*u + v*v) * SQRTMAX * 2.0; + } + + if (u <= SQRTMIN) + { + // hypot (tiny, tiny) -- avoid underflow + // This is only necessary to avoid setting the underflow + // flag. + u *= SQRTMAX / real.epsilon; + v *= SQRTMAX / real.epsilon; + return sqrt(u*u + v*v) * SQRTMIN * real.epsilon; + } + + if (u * real.epsilon > v) + { + // hypot (huge, tiny) = huge + return u; + } + + // both are in the normal range + return sqrt(u*u + v*v); +} + +@safe unittest +{ + static real[3][] vals = // x,y,hypot + [ + [ 0.0, 0.0, 0.0], + [ 0.0, -0.0, 0.0], + [ -0.0, -0.0, 0.0], + [ 3.0, 4.0, 5.0], + [ -300, -400, 500], + [0.0, 7.0, 7.0], + [9.0, 9*real.epsilon, 9.0], + [88/(64*sqrt(real.min_normal)), 105/(64*sqrt(real.min_normal)), 137/(64*sqrt(real.min_normal))], + [88/(128*sqrt(real.min_normal)), 105/(128*sqrt(real.min_normal)), 137/(128*sqrt(real.min_normal))], + [3*real.min_normal*real.epsilon, 4*real.min_normal*real.epsilon, 5*real.min_normal*real.epsilon], + [ real.min_normal, real.min_normal, sqrt(2.0L)*real.min_normal], + [ real.max/sqrt(2.0L), real.max/sqrt(2.0L), real.max], + [ real.infinity, real.nan, real.infinity], + [ real.nan, real.infinity, real.infinity], + [ real.nan, real.nan, real.nan], + [ real.nan, real.max, real.nan], + [ real.max, real.nan, real.nan], + ]; + for (int i = 0; i < vals.length; i++) + { + real x = vals[i][0]; + real y = vals[i][1]; + real z = vals[i][2]; + real h = hypot(x, y); + assert(isIdentical(z,h) || feqrel(z, h) >= real.mant_dig - 1); + } +} + +/************************************** + * Returns the value of x rounded upward to the next integer + * (toward positive infinity). + */ +real ceil(real x) @trusted pure nothrow @nogc +{ + version (Win64_DMD_InlineAsm) + { + asm pure nothrow @nogc + { + naked ; + fld real ptr [RCX] ; + fstcw 8[RSP] ; + mov AL,9[RSP] ; + mov DL,AL ; + and AL,0xC3 ; + or AL,0x08 ; // round to +infinity + mov 9[RSP],AL ; + fldcw 8[RSP] ; + frndint ; + mov 9[RSP],DL ; + fldcw 8[RSP] ; + ret ; + } + } + else version (CRuntime_Microsoft) + { + short cw; + asm pure nothrow @nogc + { + fld x ; + fstcw cw ; + mov AL,byte ptr cw+1 ; + mov DL,AL ; + and AL,0xC3 ; + or AL,0x08 ; // round to +infinity + mov byte ptr cw+1,AL ; + fldcw cw ; + frndint ; + mov byte ptr cw+1,DL ; + fldcw cw ; + } + } + else + { + // Special cases. + if (isNaN(x) || isInfinity(x)) + return x; + + real y = floorImpl(x); + if (y < x) + y += 1.0; + + return y; + } +} + +/// +@safe pure nothrow @nogc unittest +{ + assert(ceil(+123.456L) == +124); + assert(ceil(-123.456L) == -123); + assert(ceil(-1.234L) == -1); + assert(ceil(-0.123L) == 0); + assert(ceil(0.0L) == 0); + assert(ceil(+0.123L) == 1); + assert(ceil(+1.234L) == 2); + assert(ceil(real.infinity) == real.infinity); + assert(isNaN(ceil(real.nan))); + assert(isNaN(ceil(real.init))); +} + +// ditto +double ceil(double x) @trusted pure nothrow @nogc +{ + // Special cases. + if (isNaN(x) || isInfinity(x)) + return x; + + double y = floorImpl(x); + if (y < x) + y += 1.0; + + return y; +} + +@safe pure nothrow @nogc unittest +{ + assert(ceil(+123.456) == +124); + assert(ceil(-123.456) == -123); + assert(ceil(-1.234) == -1); + assert(ceil(-0.123) == 0); + assert(ceil(0.0) == 0); + assert(ceil(+0.123) == 1); + assert(ceil(+1.234) == 2); + assert(ceil(double.infinity) == double.infinity); + assert(isNaN(ceil(double.nan))); + assert(isNaN(ceil(double.init))); +} + +// ditto +float ceil(float x) @trusted pure nothrow @nogc +{ + // Special cases. + if (isNaN(x) || isInfinity(x)) + return x; + + float y = floorImpl(x); + if (y < x) + y += 1.0; + + return y; +} + +@safe pure nothrow @nogc unittest +{ + assert(ceil(+123.456f) == +124); + assert(ceil(-123.456f) == -123); + assert(ceil(-1.234f) == -1); + assert(ceil(-0.123f) == 0); + assert(ceil(0.0f) == 0); + assert(ceil(+0.123f) == 1); + assert(ceil(+1.234f) == 2); + assert(ceil(float.infinity) == float.infinity); + assert(isNaN(ceil(float.nan))); + assert(isNaN(ceil(float.init))); +} + +/************************************** + * Returns the value of x rounded downward to the next integer + * (toward negative infinity). + */ +real floor(real x) @trusted pure nothrow @nogc +{ + version (Win64_DMD_InlineAsm) + { + asm pure nothrow @nogc + { + naked ; + fld real ptr [RCX] ; + fstcw 8[RSP] ; + mov AL,9[RSP] ; + mov DL,AL ; + and AL,0xC3 ; + or AL,0x04 ; // round to -infinity + mov 9[RSP],AL ; + fldcw 8[RSP] ; + frndint ; + mov 9[RSP],DL ; + fldcw 8[RSP] ; + ret ; + } + } + else version (CRuntime_Microsoft) + { + short cw; + asm pure nothrow @nogc + { + fld x ; + fstcw cw ; + mov AL,byte ptr cw+1 ; + mov DL,AL ; + and AL,0xC3 ; + or AL,0x04 ; // round to -infinity + mov byte ptr cw+1,AL ; + fldcw cw ; + frndint ; + mov byte ptr cw+1,DL ; + fldcw cw ; + } + } + else + { + // Special cases. + if (isNaN(x) || isInfinity(x) || x == 0.0) + return x; + + return floorImpl(x); + } +} + +/// +@safe pure nothrow @nogc unittest +{ + assert(floor(+123.456L) == +123); + assert(floor(-123.456L) == -124); + assert(floor(-1.234L) == -2); + assert(floor(-0.123L) == -1); + assert(floor(0.0L) == 0); + assert(floor(+0.123L) == 0); + assert(floor(+1.234L) == 1); + assert(floor(real.infinity) == real.infinity); + assert(isNaN(floor(real.nan))); + assert(isNaN(floor(real.init))); +} + +// ditto +double floor(double x) @trusted pure nothrow @nogc +{ + // Special cases. + if (isNaN(x) || isInfinity(x) || x == 0.0) + return x; + + return floorImpl(x); +} + +@safe pure nothrow @nogc unittest +{ + assert(floor(+123.456) == +123); + assert(floor(-123.456) == -124); + assert(floor(-1.234) == -2); + assert(floor(-0.123) == -1); + assert(floor(0.0) == 0); + assert(floor(+0.123) == 0); + assert(floor(+1.234) == 1); + assert(floor(double.infinity) == double.infinity); + assert(isNaN(floor(double.nan))); + assert(isNaN(floor(double.init))); +} + +// ditto +float floor(float x) @trusted pure nothrow @nogc +{ + // Special cases. + if (isNaN(x) || isInfinity(x) || x == 0.0) + return x; + + return floorImpl(x); +} + +@safe pure nothrow @nogc unittest +{ + assert(floor(+123.456f) == +123); + assert(floor(-123.456f) == -124); + assert(floor(-1.234f) == -2); + assert(floor(-0.123f) == -1); + assert(floor(0.0f) == 0); + assert(floor(+0.123f) == 0); + assert(floor(+1.234f) == 1); + assert(floor(float.infinity) == float.infinity); + assert(isNaN(floor(float.nan))); + assert(isNaN(floor(float.init))); +} + +/** + * Round `val` to a multiple of `unit`. `rfunc` specifies the rounding + * function to use; by default this is `rint`, which uses the current + * rounding mode. + */ +Unqual!F quantize(alias rfunc = rint, F)(const F val, const F unit) +if (is(typeof(rfunc(F.init)) : F) && isFloatingPoint!F) +{ + typeof(return) ret = val; + if (unit != 0) + { + const scaled = val / unit; + if (!scaled.isInfinity) + ret = rfunc(scaled) * unit; + } + return ret; +} + +/// +@safe pure nothrow @nogc unittest +{ + assert(12345.6789L.quantize(0.01L) == 12345.68L); + assert(12345.6789L.quantize!floor(0.01L) == 12345.67L); + assert(12345.6789L.quantize(22.0L) == 12342.0L); +} + +/// +@safe pure nothrow @nogc unittest +{ + assert(12345.6789L.quantize(0) == 12345.6789L); + assert(12345.6789L.quantize(real.infinity).isNaN); + assert(12345.6789L.quantize(real.nan).isNaN); + assert(real.infinity.quantize(0.01L) == real.infinity); + assert(real.infinity.quantize(real.nan).isNaN); + assert(real.nan.quantize(0.01L).isNaN); + assert(real.nan.quantize(real.infinity).isNaN); + assert(real.nan.quantize(real.nan).isNaN); +} + +/** + * Round `val` to a multiple of `pow(base, exp)`. `rfunc` specifies the + * rounding function to use; by default this is `rint`, which uses the + * current rounding mode. + */ +Unqual!F quantize(real base, alias rfunc = rint, F, E)(const F val, const E exp) +if (is(typeof(rfunc(F.init)) : F) && isFloatingPoint!F && isIntegral!E) +{ + // TODO: Compile-time optimization for power-of-two bases? + return quantize!rfunc(val, pow(cast(F) base, exp)); +} + +/// ditto +Unqual!F quantize(real base, long exp = 1, alias rfunc = rint, F)(const F val) +if (is(typeof(rfunc(F.init)) : F) && isFloatingPoint!F) +{ + enum unit = cast(F) pow(base, exp); + return quantize!rfunc(val, unit); +} + +/// +@safe pure nothrow @nogc unittest +{ + assert(12345.6789L.quantize!10(-2) == 12345.68L); + assert(12345.6789L.quantize!(10, -2) == 12345.68L); + assert(12345.6789L.quantize!(10, floor)(-2) == 12345.67L); + assert(12345.6789L.quantize!(10, -2, floor) == 12345.67L); + + assert(12345.6789L.quantize!22(1) == 12342.0L); + assert(12345.6789L.quantize!22 == 12342.0L); +} + +@safe pure nothrow @nogc unittest +{ + import std.meta : AliasSeq; + + foreach (F; AliasSeq!(real, double, float)) + { + const maxL10 = cast(int) F.max.log10.floor; + const maxR10 = pow(cast(F) 10, maxL10); + assert((cast(F) 0.9L * maxR10).quantize!10(maxL10) == maxR10); + assert((cast(F)-0.9L * maxR10).quantize!10(maxL10) == -maxR10); + + assert(F.max.quantize(F.min_normal) == F.max); + assert((-F.max).quantize(F.min_normal) == -F.max); + assert(F.min_normal.quantize(F.max) == 0); + assert((-F.min_normal).quantize(F.max) == 0); + assert(F.min_normal.quantize(F.min_normal) == F.min_normal); + assert((-F.min_normal).quantize(F.min_normal) == -F.min_normal); + } +} + +/****************************************** + * Rounds x to the nearest integer value, using the current rounding + * mode. + * + * Unlike the rint functions, nearbyint does not raise the + * FE_INEXACT exception. + */ +real nearbyint(real x) @trusted nothrow @nogc +{ + version (CRuntime_Microsoft) + { + assert(0); // not implemented in C library + } + else + return core.stdc.math.nearbyintl(x); +} + +/********************************** + * Rounds x to the nearest integer value, using the current rounding + * mode. + * If the return value is not equal to x, the FE_INEXACT + * exception is raised. + * $(B nearbyint) performs + * the same operation, but does not set the FE_INEXACT exception. + */ +real rint(real x) @safe pure nothrow @nogc { pragma(inline, true); return core.math.rint(x); } +//FIXME +///ditto +double rint(double x) @safe pure nothrow @nogc { return rint(cast(real) x); } +//FIXME +///ditto +float rint(float x) @safe pure nothrow @nogc { return rint(cast(real) x); } + +@safe unittest +{ + real function(real) print = &rint; + assert(print != null); +} + +/*************************************** + * Rounds x to the nearest integer value, using the current rounding + * mode. + * + * This is generally the fastest method to convert a floating-point number + * to an integer. Note that the results from this function + * depend on the rounding mode, if the fractional part of x is exactly 0.5. + * If using the default rounding mode (ties round to even integers) + * lrint(4.5) == 4, lrint(5.5)==6. + */ +long lrint(real x) @trusted pure nothrow @nogc +{ + version (InlineAsm_X86_Any) + { + version (Win64) + { + asm pure nothrow @nogc + { + naked; + fld real ptr [RCX]; + fistp qword ptr 8[RSP]; + mov RAX,8[RSP]; + ret; + } + } + else + { + long n; + asm pure nothrow @nogc + { + fld x; + fistp n; + } + return n; + } + } + else + { + alias F = floatTraits!(real); + static if (F.realFormat == RealFormat.ieeeDouble) + { + long result; + + // Rounding limit when casting from real(double) to ulong. + enum real OF = 4.50359962737049600000E15L; + + uint* vi = cast(uint*)(&x); + + // Find the exponent and sign + uint msb = vi[MANTISSA_MSB]; + uint lsb = vi[MANTISSA_LSB]; + int exp = ((msb >> 20) & 0x7ff) - 0x3ff; + const int sign = msb >> 31; + msb &= 0xfffff; + msb |= 0x100000; + + if (exp < 63) + { + if (exp >= 52) + result = (cast(long) msb << (exp - 20)) | (lsb << (exp - 52)); + else + { + // Adjust x and check result. + const real j = sign ? -OF : OF; + x = (j + x) - j; + msb = vi[MANTISSA_MSB]; + lsb = vi[MANTISSA_LSB]; + exp = ((msb >> 20) & 0x7ff) - 0x3ff; + msb &= 0xfffff; + msb |= 0x100000; + + if (exp < 0) + result = 0; + else if (exp < 20) + result = cast(long) msb >> (20 - exp); + else if (exp == 20) + result = cast(long) msb; + else + result = (cast(long) msb << (exp - 20)) | (lsb >> (52 - exp)); + } + } + else + { + // It is left implementation defined when the number is too large. + return cast(long) x; + } + + return sign ? -result : result; + } + else static if (F.realFormat == RealFormat.ieeeExtended) + { + long result; + + // Rounding limit when casting from real(80-bit) to ulong. + enum real OF = 9.22337203685477580800E18L; + + ushort* vu = cast(ushort*)(&x); + uint* vi = cast(uint*)(&x); + + // Find the exponent and sign + int exp = (vu[F.EXPPOS_SHORT] & 0x7fff) - 0x3fff; + const int sign = (vu[F.EXPPOS_SHORT] >> 15) & 1; + + if (exp < 63) + { + // Adjust x and check result. + const real j = sign ? -OF : OF; + x = (j + x) - j; + exp = (vu[F.EXPPOS_SHORT] & 0x7fff) - 0x3fff; + + version (LittleEndian) + { + if (exp < 0) + result = 0; + else if (exp <= 31) + result = vi[1] >> (31 - exp); + else + result = (cast(long) vi[1] << (exp - 31)) | (vi[0] >> (63 - exp)); + } + else + { + if (exp < 0) + result = 0; + else if (exp <= 31) + result = vi[1] >> (31 - exp); + else + result = (cast(long) vi[1] << (exp - 31)) | (vi[2] >> (63 - exp)); + } + } + else + { + // It is left implementation defined when the number is too large + // to fit in a 64bit long. + return cast(long) x; + } + + return sign ? -result : result; + } + else static if (F.realFormat == RealFormat.ieeeQuadruple) + { + const vu = cast(ushort*)(&x); + + // Find the exponent and sign + const sign = (vu[F.EXPPOS_SHORT] >> 15) & 1; + if ((vu[F.EXPPOS_SHORT] & F.EXPMASK) - (F.EXPBIAS + 1) > 63) + { + // The result is left implementation defined when the number is + // too large to fit in a 64 bit long. + return cast(long) x; + } + + // Force rounding of lower bits according to current rounding + // mode by adding ±2^-112 and subtracting it again. + enum OF = 5.19229685853482762853049632922009600E33L; + const j = sign ? -OF : OF; + x = (j + x) - j; + + const implicitOne = 1UL << 48; + auto vl = cast(ulong*)(&x); + vl[MANTISSA_MSB] &= implicitOne - 1; + vl[MANTISSA_MSB] |= implicitOne; + + long result; + + const exp = (vu[F.EXPPOS_SHORT] & F.EXPMASK) - (F.EXPBIAS + 1); + if (exp < 0) + result = 0; + else if (exp <= 48) + result = vl[MANTISSA_MSB] >> (48 - exp); + else + result = (vl[MANTISSA_MSB] << (exp - 48)) | (vl[MANTISSA_LSB] >> (112 - exp)); + + return sign ? -result : result; + } + else + { + static assert(false, "real type not supported by lrint()"); + } + } +} + +/// +@safe pure nothrow @nogc unittest +{ + assert(lrint(4.5) == 4); + assert(lrint(5.5) == 6); + assert(lrint(-4.5) == -4); + assert(lrint(-5.5) == -6); + + assert(lrint(int.max - 0.5) == 2147483646L); + assert(lrint(int.max + 0.5) == 2147483648L); + assert(lrint(int.min - 0.5) == -2147483648L); + assert(lrint(int.min + 0.5) == -2147483648L); +} + +static if (real.mant_dig >= long.sizeof * 8) +{ + @safe pure nothrow @nogc unittest + { + assert(lrint(long.max - 1.5L) == long.max - 1); + assert(lrint(long.max - 0.5L) == long.max - 1); + assert(lrint(long.min + 0.5L) == long.min); + assert(lrint(long.min + 1.5L) == long.min + 2); + } +} + +/******************************************* + * Return the value of x rounded to the nearest integer. + * If the fractional part of x is exactly 0.5, the return value is + * rounded away from zero. + */ +real round(real x) @trusted nothrow @nogc +{ + version (CRuntime_Microsoft) + { + auto old = FloatingPointControl.getControlState(); + FloatingPointControl.setControlState( + (old & ~FloatingPointControl.roundingMask) | FloatingPointControl.roundToZero + ); + x = rint((x >= 0) ? x + 0.5 : x - 0.5); + FloatingPointControl.setControlState(old); + return x; + } + else + return core.stdc.math.roundl(x); +} + +/********************************************** + * Return the value of x rounded to the nearest integer. + * + * If the fractional part of x is exactly 0.5, the return value is rounded + * away from zero. + * + * $(BLUE This function is Posix-Only.) + */ +long lround(real x) @trusted nothrow @nogc +{ + version (Posix) + return core.stdc.math.llroundl(x); + else + assert(0, "lround not implemented"); +} + +version (Posix) +{ + @safe nothrow @nogc unittest + { + assert(lround(0.49) == 0); + assert(lround(0.5) == 1); + assert(lround(1.5) == 2); + } +} + +/**************************************************** + * Returns the integer portion of x, dropping the fractional portion. + * + * This is also known as "chop" rounding. + */ +real trunc(real x) @trusted nothrow @nogc +{ + version (Win64_DMD_InlineAsm) + { + asm pure nothrow @nogc + { + naked ; + fld real ptr [RCX] ; + fstcw 8[RSP] ; + mov AL,9[RSP] ; + mov DL,AL ; + and AL,0xC3 ; + or AL,0x0C ; // round to 0 + mov 9[RSP],AL ; + fldcw 8[RSP] ; + frndint ; + mov 9[RSP],DL ; + fldcw 8[RSP] ; + ret ; + } + } + else version (CRuntime_Microsoft) + { + short cw; + asm pure nothrow @nogc + { + fld x ; + fstcw cw ; + mov AL,byte ptr cw+1 ; + mov DL,AL ; + and AL,0xC3 ; + or AL,0x0C ; // round to 0 + mov byte ptr cw+1,AL ; + fldcw cw ; + frndint ; + mov byte ptr cw+1,DL ; + fldcw cw ; + } + } + else + return core.stdc.math.truncl(x); +} + +/**************************************************** + * Calculate the remainder x REM y, following IEC 60559. + * + * REM is the value of x - y * n, where n is the integer nearest the exact + * value of x / y. + * If |n - x / y| == 0.5, n is even. + * If the result is zero, it has the same sign as x. + * Otherwise, the sign of the result is the sign of x / y. + * Precision mode has no effect on the remainder functions. + * + * remquo returns n in the parameter n. + * + * $(TABLE_SV + * $(TR $(TH x) $(TH y) $(TH remainder(x, y)) $(TH n) $(TH invalid?)) + * $(TR $(TD $(PLUSMN)0.0) $(TD not 0.0) $(TD $(PLUSMN)0.0) $(TD 0.0) $(TD no)) + * $(TR $(TD $(PLUSMNINF)) $(TD anything) $(TD $(NAN)) $(TD ?) $(TD yes)) + * $(TR $(TD anything) $(TD $(PLUSMN)0.0) $(TD $(NAN)) $(TD ?) $(TD yes)) + * $(TR $(TD != $(PLUSMNINF)) $(TD $(PLUSMNINF)) $(TD x) $(TD ?) $(TD no)) + * ) + * + * $(BLUE `remquo` and `remainder` not supported on Windows.) + */ +real remainder(real x, real y) @trusted nothrow @nogc +{ + version (CRuntime_Microsoft) + { + int n; + return remquo(x, y, n); + } + else + return core.stdc.math.remainderl(x, y); +} + +real remquo(real x, real y, out int n) @trusted nothrow @nogc /// ditto +{ + version (Posix) + return core.stdc.math.remquol(x, y, &n); + else + assert(0, "remquo not implemented"); +} + +/** IEEE exception status flags ('sticky bits') + + These flags indicate that an exceptional floating-point condition has occurred. + They indicate that a NaN or an infinity has been generated, that a result + is inexact, or that a signalling NaN has been encountered. If floating-point + exceptions are enabled (unmasked), a hardware exception will be generated + instead of setting these flags. + */ +struct IeeeFlags +{ +private: + // The x87 FPU status register is 16 bits. + // The Pentium SSE2 status register is 32 bits. + // The ARM and PowerPC FPSCR is a 32-bit register. + // The SPARC FSR is a 32bit register (64 bits for SPARC 7 & 8, but high bits are uninteresting). + uint flags; + + version (CRuntime_Microsoft) + { + // Microsoft uses hardware-incompatible custom constants in fenv.h (core.stdc.fenv). + // Applies to both x87 status word (16 bits) and SSE2 status word(32 bits). + enum : int + { + INEXACT_MASK = 0x20, + UNDERFLOW_MASK = 0x10, + OVERFLOW_MASK = 0x08, + DIVBYZERO_MASK = 0x04, + INVALID_MASK = 0x01, + + EXCEPTIONS_MASK = 0b11_1111 + } + // Don't bother about subnormals, they are not supported on most CPUs. + // SUBNORMAL_MASK = 0x02; + } + else + { + enum : int + { + INEXACT_MASK = core.stdc.fenv.FE_INEXACT, + UNDERFLOW_MASK = core.stdc.fenv.FE_UNDERFLOW, + OVERFLOW_MASK = core.stdc.fenv.FE_OVERFLOW, + DIVBYZERO_MASK = core.stdc.fenv.FE_DIVBYZERO, + INVALID_MASK = core.stdc.fenv.FE_INVALID, + EXCEPTIONS_MASK = core.stdc.fenv.FE_ALL_EXCEPT, + } + } + +private: + static uint getIeeeFlags() + { + version (GNU) + { + version (X86_Any) + { + ushort sw; + asm pure nothrow @nogc + { + "fstsw %0" : "=a" (sw); + } + // OR the result with the SSE2 status register (MXCSR). + if (haveSSE) + { + uint mxcsr; + asm pure nothrow @nogc + { + "stmxcsr %0" : "=m" (mxcsr); + } + return (sw | mxcsr) & EXCEPTIONS_MASK; + } + else + return sw & EXCEPTIONS_MASK; + } + else version (ARM) + { + version (ARM_SoftFloat) + return 0; + else + { + uint result = void; + asm pure nothrow @nogc + { + "vmrs %0, FPSCR; and %0, %0, #0x1F;" : "=r" result; + } + return result; + } + } + else + assert(0, "Not yet supported"); + } + else + version (InlineAsm_X86_Any) + { + ushort sw; + asm pure nothrow @nogc { fstsw sw; } + + // OR the result with the SSE2 status register (MXCSR). + if (haveSSE) + { + uint mxcsr; + asm pure nothrow @nogc { stmxcsr mxcsr; } + return (sw | mxcsr) & EXCEPTIONS_MASK; + } + else return sw & EXCEPTIONS_MASK; + } + else version (SPARC) + { + /* + int retval; + asm pure nothrow @nogc { st %fsr, retval; } + return retval; + */ + assert(0, "Not yet supported"); + } + else version (ARM) + { + assert(false, "Not yet supported."); + } + else + assert(0, "Not yet supported"); + } + static void resetIeeeFlags() @nogc + { + version (GNU) + { + version (X86_Any) + { + asm pure nothrow @nogc + { + "fnclex"; + } + + // Also clear exception flags in MXCSR, SSE's control register. + if (haveSSE) + { + uint mxcsr; + asm pure nothrow @nogc + { + "stmxcsr %0" : "=m" (mxcsr); + } + mxcsr &= ~EXCEPTIONS_MASK; + asm pure nothrow @nogc + { + "ldmxcsr %0" : : "m" (mxcsr); + } + } + } + else version (ARM) + { + version (ARM_SoftFloat) + return; + else + { + uint old = FloatingPointControl.getControlState(); + old &= ~0b11111; // http://infocenter.arm.com/help/topic/com.arm.doc.ddi0408i/Chdfifdc.html + asm pure nothrow @nogc + { + "vmsr FPSCR, %0" : : "r" (old); + } + } + } + else + assert(0, "Not yet supported"); + } + else + version (InlineAsm_X86_Any) + { + asm pure nothrow @nogc + { + fnclex; + } + + // Also clear exception flags in MXCSR, SSE's control register. + if (haveSSE) + { + uint mxcsr; + asm nothrow @nogc { stmxcsr mxcsr; } + mxcsr &= ~EXCEPTIONS_MASK; + asm nothrow @nogc { ldmxcsr mxcsr; } + } + } + else + { + /* SPARC: + int tmpval; + asm pure nothrow @nogc { st %fsr, tmpval; } + tmpval &=0xFFFF_FC00; + asm pure nothrow @nogc { ld tmpval, %fsr; } + */ + assert(0, "Not yet supported"); + } + } +public: + version (IeeeFlagsSupport) + { + + /** + * The result cannot be represented exactly, so rounding occurred. + * Example: `x = sin(0.1);` + */ + @property bool inexact() const { return (flags & INEXACT_MASK) != 0; } + + /** + * A zero was generated by underflow + * Example: `x = real.min*real.epsilon/2;` + */ + @property bool underflow() const { return (flags & UNDERFLOW_MASK) != 0; } + + /** + * An infinity was generated by overflow + * Example: `x = real.max*2;` + */ + @property bool overflow() const { return (flags & OVERFLOW_MASK) != 0; } + + /** + * An infinity was generated by division by zero + * Example: `x = 3/0.0;` + */ + @property bool divByZero() const { return (flags & DIVBYZERO_MASK) != 0; } + + /** + * A machine NaN was generated. + * Example: `x = real.infinity * 0.0;` + */ + @property bool invalid() const { return (flags & INVALID_MASK) != 0; } + + } +} + +/// +version (GNU) +{ + unittest + { + pragma(msg, "ieeeFlags test disabled, see LDC Issue #888"); + } +} +else +@system unittest +{ + static void func() { + int a = 10 * 10; + } + + real a=3.5; + // Set all the flags to zero + resetIeeeFlags(); + assert(!ieeeFlags.divByZero); + // Perform a division by zero. + a/=0.0L; + assert(a == real.infinity); + assert(ieeeFlags.divByZero); + // Create a NaN + a*=0.0L; + assert(ieeeFlags.invalid); + assert(isNaN(a)); + + // Check that calling func() has no effect on the + // status flags. + IeeeFlags f = ieeeFlags; + func(); + assert(ieeeFlags == f); +} + +version (GNU) +{ + unittest + { + pragma(msg, "ieeeFlags test disabled, see LDC Issue #888"); + } +} +else +@system unittest +{ + import std.meta : AliasSeq; + + static struct Test + { + void delegate() action; + bool function() ieeeCheck; + } + + foreach (T; AliasSeq!(float, double, real)) + { + T x; /* Needs to be here to trick -O. It would optimize away the + calculations if x were local to the function literals. */ + auto tests = [ + Test( + () { x = 1; x += 0.1; }, + () => ieeeFlags.inexact + ), + Test( + () { x = T.min_normal; x /= T.max; }, + () => ieeeFlags.underflow + ), + Test( + () { x = T.max; x += T.max; }, + () => ieeeFlags.overflow + ), + Test( + () { x = 1; x /= 0; }, + () => ieeeFlags.divByZero + ), + Test( + () { x = 0; x /= 0; }, + () => ieeeFlags.invalid + ) + ]; + foreach (test; tests) + { + resetIeeeFlags(); + assert(!test.ieeeCheck()); + test.action(); + assert(test.ieeeCheck()); + } + } +} + +version (X86_Any) +{ + version = IeeeFlagsSupport; +} +version (X86_Any) +{ + version = IeeeFlagsSupport; +} +else version (PPC_Any) +{ + version = IeeeFlagsSupport; +} +else version (MIPS_Any) +{ + version = IeeeFlagsSupport; +} +else version (ARM_Any) +{ + version = IeeeFlagsSupport; +} + +/// Set all of the floating-point status flags to false. +void resetIeeeFlags() @nogc { IeeeFlags.resetIeeeFlags(); } + +/// Returns: snapshot of the current state of the floating-point status flags +@property IeeeFlags ieeeFlags() +{ + return IeeeFlags(IeeeFlags.getIeeeFlags()); +} + +/** Control the Floating point hardware + + Change the IEEE754 floating-point rounding mode and the floating-point + hardware exceptions. + + By default, the rounding mode is roundToNearest and all hardware exceptions + are disabled. For most applications, debugging is easier if the $(I division + by zero), $(I overflow), and $(I invalid operation) exceptions are enabled. + These three are combined into a $(I severeExceptions) value for convenience. + Note in particular that if $(I invalidException) is enabled, a hardware trap + will be generated whenever an uninitialized floating-point variable is used. + + All changes are temporary. The previous state is restored at the + end of the scope. + + +Example: +---- +{ + FloatingPointControl fpctrl; + + // Enable hardware exceptions for division by zero, overflow to infinity, + // invalid operations, and uninitialized floating-point variables. + fpctrl.enableExceptions(FloatingPointControl.severeExceptions); + + // This will generate a hardware exception, if x is a + // default-initialized floating point variable: + real x; // Add `= 0` or even `= real.nan` to not throw the exception. + real y = x * 3.0; + + // The exception is only thrown for default-uninitialized NaN-s. + // NaN-s with other payload are valid: + real z = y * real.nan; // ok + + // Changing the rounding mode: + fpctrl.rounding = FloatingPointControl.roundUp; + assert(rint(1.1) == 2); + + // The set hardware exceptions will be disabled when leaving this scope. + // The original rounding mode will also be restored. +} + +// Ensure previous values are returned: +assert(!FloatingPointControl.enabledExceptions); +assert(FloatingPointControl.rounding == FloatingPointControl.roundToNearest); +assert(rint(1.1) == 1); +---- + + */ +struct FloatingPointControl +{ + alias RoundingMode = uint; /// + + version (StdDdoc) + { + enum : RoundingMode + { + /** IEEE rounding modes. + * The default mode is roundToNearest. + * + * roundingMask = A mask of all rounding modes. + */ + roundToNearest, + roundDown, /// ditto + roundUp, /// ditto + roundToZero, /// ditto + roundingMask, /// ditto + } + } + else version (CRuntime_Microsoft) + { + // Microsoft uses hardware-incompatible custom constants in fenv.h (core.stdc.fenv). + enum : RoundingMode + { + roundToNearest = 0x0000, + roundDown = 0x0400, + roundUp = 0x0800, + roundToZero = 0x0C00, + roundingMask = roundToNearest | roundDown + | roundUp | roundToZero, + } + } + else + { + enum : RoundingMode + { + roundToNearest = core.stdc.fenv.FE_TONEAREST, + roundDown = core.stdc.fenv.FE_DOWNWARD, + roundUp = core.stdc.fenv.FE_UPWARD, + roundToZero = core.stdc.fenv.FE_TOWARDZERO, + roundingMask = roundToNearest | roundDown + | roundUp | roundToZero, + } + } + + //// Change the floating-point hardware rounding mode + @property void rounding(RoundingMode newMode) @nogc + { + initialize(); + setControlState(cast(ushort)((getControlState() & (-1 - roundingMask)) | (newMode & roundingMask))); + } + + /// Returns: the currently active rounding mode + @property static RoundingMode rounding() @nogc + { + return cast(RoundingMode)(getControlState() & roundingMask); + } + + alias ExceptionMask = uint; /// + + version (StdDdoc) + { + enum : ExceptionMask + { + /** IEEE hardware exceptions. + * By default, all exceptions are masked (disabled). + * + * severeExceptions = The overflow, division by zero, and invalid + * exceptions. + */ + subnormalException, + inexactException, /// ditto + underflowException, /// ditto + overflowException, /// ditto + divByZeroException, /// ditto + invalidException, /// ditto + severeExceptions, /// ditto + allExceptions, /// ditto + } + } + else version (ARM_Any) + { + enum : ExceptionMask + { + subnormalException = 0x8000, + inexactException = 0x1000, + underflowException = 0x0800, + overflowException = 0x0400, + divByZeroException = 0x0200, + invalidException = 0x0100, + severeExceptions = overflowException | divByZeroException + | invalidException, + allExceptions = severeExceptions | underflowException + | inexactException | subnormalException, + } + } + else version (MIPS_Any) + { + enum : ExceptionMask + { + inexactException = 0x0080, + underflowException = 0x0100, + overflowException = 0x0200, + divByZeroException = 0x0400, + invalidException = 0x0800, + severeExceptions = overflowException | divByZeroException + | invalidException, + allExceptions = severeExceptions | underflowException + | inexactException, + } + } + else version (PPC_Any) + { + enum : ExceptionMask + { + inexactException = 0x08, + divByZeroException = 0x10, + underflowException = 0x20, + overflowException = 0x40, + invalidException = 0x80, + severeExceptions = overflowException | divByZeroException + | invalidException, + allExceptions = severeExceptions | underflowException + | inexactException, + } + } + else version (SPARC64) + { + enum : ExceptionMask + { + inexactException = 0x0800000, + divByZeroException = 0x1000000, + overflowException = 0x4000000, + underflowException = 0x2000000, + invalidException = 0x8000000, + severeExceptions = overflowException | divByZeroException + | invalidException, + allExceptions = severeExceptions | underflowException + | inexactException, + } + } + else version (SystemZ) + { + enum : ExceptionMask + { + inexactException = 0x08000000, + divByZeroException = 0x40000000, + overflowException = 0x20000000, + underflowException = 0x10000000, + invalidException = 0x80000000, + severeExceptions = overflowException | divByZeroException + | invalidException, + allExceptions = severeExceptions | underflowException + | inexactException, + } + } + else version (X86_Any) + { + enum : ExceptionMask + { + inexactException = 0x20, + underflowException = 0x10, + overflowException = 0x08, + divByZeroException = 0x04, + subnormalException = 0x02, + invalidException = 0x01, + severeExceptions = overflowException | divByZeroException + | invalidException, + allExceptions = severeExceptions | underflowException + | inexactException | subnormalException, + } + } + else + static assert(false, "Not implemented for this architecture"); + +public: + /// Returns: true if the current FPU supports exception trapping + @property static bool hasExceptionTraps() @safe nothrow @nogc + { + version (X86_Any) + return true; + else version (PPC_Any) + return true; + else version (MIPS_Any) + return true; + else version (ARM_Any) + { + auto oldState = getControlState(); + // If exceptions are not supported, we set the bit but read it back as zero + // https://sourceware.org/ml/libc-ports/2012-06/msg00091.html + setControlState(oldState | divByZeroException); + immutable result = (getControlState() & allExceptions) != 0; + setControlState(oldState); + return result; + } + else + assert(0, "Not yet supported"); + } + + /// Enable (unmask) specific hardware exceptions. Multiple exceptions may be ORed together. + void enableExceptions(ExceptionMask exceptions) @nogc + { + assert(hasExceptionTraps); + initialize(); + version (X86_Any) + setControlState(getControlState() & ~(exceptions & allExceptions)); + else + setControlState(getControlState() | (exceptions & allExceptions)); + } + + /// Disable (mask) specific hardware exceptions. Multiple exceptions may be ORed together. + void disableExceptions(ExceptionMask exceptions) @nogc + { + assert(hasExceptionTraps); + initialize(); + version (X86_Any) + setControlState(getControlState() | (exceptions & allExceptions)); + else + setControlState(getControlState() & ~(exceptions & allExceptions)); + } + + /// Returns: the exceptions which are currently enabled (unmasked) + @property static ExceptionMask enabledExceptions() @nogc + { + assert(hasExceptionTraps); + version (X86_Any) + return (getControlState() & allExceptions) ^ allExceptions; + else + return (getControlState() & allExceptions); + } + + /// Clear all pending exceptions, then restore the original exception state and rounding mode. + ~this() @nogc + { + clearExceptions(); + if (initialized) + setControlState(savedState); + } + +private: + ControlState savedState; + + bool initialized = false; + + version (ARM_Any) + { + alias ControlState = uint; + } + else version (PPC_Any) + { + alias ControlState = uint; + } + else version (MIPS_Any) + { + alias ControlState = uint; + } + else version (SPARC64) + { + alias ControlState = ulong; + } + else version (SystemZ) + { + alias ControlState = uint; + } + else version (X86_Any) + { + alias ControlState = ushort; + } + else + static assert(false, "Not implemented for this architecture"); + + void initialize() @nogc + { + // BUG: This works around the absence of this() constructors. + if (initialized) return; + clearExceptions(); + savedState = getControlState(); + initialized = true; + } + + // Clear all pending exceptions + static void clearExceptions() @nogc + { + resetIeeeFlags(); + } + + // Read from the control register + static ControlState getControlState() @trusted nothrow @nogc + { + version (GNU) + { + version (X86_Any) + { + ControlState cont; + asm pure nothrow @nogc + { + "fstcw %0" : "=m" cont; + } + return cont; + } + else version (AArch64) + { + asm pure nothrow @nogc + { + "mrs %0, FPCR;" : "=r" cont; + } + return cont; + } + else version (ARM) + { + ControlState cont; + version (ARM_SoftFloat) + cont = 0; + else + { + asm pure nothrow @nogc + { + "vmrs %0, FPSCR" : "=r" cont; + } + } + return cont; + } + else + assert(0, "Not yet supported"); + } + else + version (D_InlineAsm_X86) + { + short cont; + asm nothrow @nogc + { + xor EAX, EAX; + fstcw cont; + } + return cont; + } + else + version (D_InlineAsm_X86_64) + { + short cont; + asm nothrow @nogc + { + xor RAX, RAX; + fstcw cont; + } + return cont; + } + else + assert(0, "Not yet supported"); + } + + // Set the control register + static void setControlState(ControlState newState) @trusted nothrow @nogc + { + version (GNU) + { + version (X86_Any) + { + asm pure nothrow @nogc + { + "fclex; fldcw %0" : : "m" newState; + } + + // Also update MXCSR, SSE's control register. + if (haveSSE) + { + uint mxcsr; + asm pure nothrow @nogc + { + "stmxcsr %0" : "=m" mxcsr; + } + + /* In the FPU control register, rounding mode is in bits 10 and + 11. In MXCSR it's in bits 13 and 14. */ + mxcsr &= ~(roundingMask << 3); // delete old rounding mode + mxcsr |= (newState & roundingMask) << 3; // write new rounding mode + + /* In the FPU control register, masks are bits 0 through 5. + In MXCSR they're 7 through 12. */ + mxcsr &= ~(allExceptions << 7); // delete old masks + mxcsr |= (newState & allExceptions) << 7; // write new exception masks + + asm pure nothrow @nogc + { + "ldmxcsr %0" : : "m" mxcsr; + } + } + } + else version (AArch64) + { + asm pure nothrow @nogc + { + "msr FPCR, %0;" : : "r" (newState); + } + } + else version (ARM) + { + version (ARM_SoftFloat) + return; + else + { + asm pure nothrow @nogc + { + "vmsr FPSCR, %0" : : "r" (newState); + } + } + } + else + assert(0, "Not yet supported"); + } + else + version (InlineAsm_X86_Any) + { + asm nothrow @nogc + { + fclex; + fldcw newState; + } + + // Also update MXCSR, SSE's control register. + if (haveSSE) + { + uint mxcsr; + asm nothrow @nogc { stmxcsr mxcsr; } + + /* In the FPU control register, rounding mode is in bits 10 and + 11. In MXCSR it's in bits 13 and 14. */ + mxcsr &= ~(roundingMask << 3); // delete old rounding mode + mxcsr |= (newState & roundingMask) << 3; // write new rounding mode + + /* In the FPU control register, masks are bits 0 through 5. + In MXCSR they're 7 through 12. */ + mxcsr &= ~(allExceptions << 7); // delete old masks + mxcsr |= (newState & allExceptions) << 7; // write new exception masks + + asm nothrow @nogc { ldmxcsr mxcsr; } + } + } + else + assert(0, "Not yet supported"); + } +} + +@system unittest +{ + // GCC floating point emulation doesn't allow changing + // rounding modes, getting error bits etc + version (GNU) version (D_SoftFloat) + return; + + void ensureDefaults() + { + assert(FloatingPointControl.rounding + == FloatingPointControl.roundToNearest); + if (FloatingPointControl.hasExceptionTraps) + assert(FloatingPointControl.enabledExceptions == 0); + } + + { + FloatingPointControl ctrl; + } + ensureDefaults(); + + version (D_HardFloat) + { + { + FloatingPointControl ctrl; + ctrl.rounding = FloatingPointControl.roundDown; + assert(FloatingPointControl.rounding == FloatingPointControl.roundDown); + } + ensureDefaults(); + } + + if (FloatingPointControl.hasExceptionTraps) + { + FloatingPointControl ctrl; + ctrl.enableExceptions(FloatingPointControl.divByZeroException + | FloatingPointControl.overflowException); + assert(ctrl.enabledExceptions == + (FloatingPointControl.divByZeroException + | FloatingPointControl.overflowException)); + + ctrl.rounding = FloatingPointControl.roundUp; + assert(FloatingPointControl.rounding == FloatingPointControl.roundUp); + } + ensureDefaults(); +} + +@system unittest // rounding +{ + import std.meta : AliasSeq; + + foreach (T; AliasSeq!(float, double, real)) + { + FloatingPointControl fpctrl; + + fpctrl.rounding = FloatingPointControl.roundUp; + T u = 1; + u += 0.1; + + fpctrl.rounding = FloatingPointControl.roundDown; + T d = 1; + d += 0.1; + + fpctrl.rounding = FloatingPointControl.roundToZero; + T z = 1; + z += 0.1; + + assert(u > d); + assert(z == d); + + fpctrl.rounding = FloatingPointControl.roundUp; + u = -1; + u -= 0.1; + + fpctrl.rounding = FloatingPointControl.roundDown; + d = -1; + d -= 0.1; + + fpctrl.rounding = FloatingPointControl.roundToZero; + z = -1; + z -= 0.1; + + assert(u > d); + assert(z == u); + } +} + + +/********************************* + * Determines if $(D_PARAM x) is NaN. + * Params: + * x = a floating point number. + * Returns: + * $(D true) if $(D_PARAM x) is Nan. + */ +bool isNaN(X)(X x) @nogc @trusted pure nothrow +if (isFloatingPoint!(X)) +{ + alias F = floatTraits!(X); + static if (F.realFormat == RealFormat.ieeeSingle) + { + const uint p = *cast(uint *)&x; + return ((p & 0x7F80_0000) == 0x7F80_0000) + && p & 0x007F_FFFF; // not infinity + } + else static if (F.realFormat == RealFormat.ieeeDouble) + { + const ulong p = *cast(ulong *)&x; + return ((p & 0x7FF0_0000_0000_0000) == 0x7FF0_0000_0000_0000) + && p & 0x000F_FFFF_FFFF_FFFF; // not infinity + } + else static if (F.realFormat == RealFormat.ieeeExtended) + { + const ushort e = F.EXPMASK & (cast(ushort *)&x)[F.EXPPOS_SHORT]; + const ulong ps = *cast(ulong *)&x; + return e == F.EXPMASK && + ps & 0x7FFF_FFFF_FFFF_FFFF; // not infinity + } + else static if (F.realFormat == RealFormat.ieeeQuadruple) + { + const ushort e = F.EXPMASK & (cast(ushort *)&x)[F.EXPPOS_SHORT]; + const ulong psLsb = (cast(ulong *)&x)[MANTISSA_LSB]; + const ulong psMsb = (cast(ulong *)&x)[MANTISSA_MSB]; + return e == F.EXPMASK && + (psLsb | (psMsb& 0x0000_FFFF_FFFF_FFFF)) != 0; + } + else + { + return x != x; + } +} + +/// +@safe pure nothrow @nogc unittest +{ + assert( isNaN(float.init)); + assert( isNaN(-double.init)); + assert( isNaN(real.nan)); + assert( isNaN(-real.nan)); + assert(!isNaN(cast(float) 53.6)); + assert(!isNaN(cast(real)-53.6)); +} + +@safe pure nothrow @nogc unittest +{ + import std.meta : AliasSeq; + + foreach (T; AliasSeq!(float, double, real)) + { + // CTFE-able tests + assert(isNaN(T.init)); + assert(isNaN(-T.init)); + assert(isNaN(T.nan)); + assert(isNaN(-T.nan)); + assert(!isNaN(T.infinity)); + assert(!isNaN(-T.infinity)); + assert(!isNaN(cast(T) 53.6)); + assert(!isNaN(cast(T)-53.6)); + + // Runtime tests + shared T f; + f = T.init; + assert(isNaN(f)); + assert(isNaN(-f)); + f = T.nan; + assert(isNaN(f)); + assert(isNaN(-f)); + f = T.infinity; + assert(!isNaN(f)); + assert(!isNaN(-f)); + f = cast(T) 53.6; + assert(!isNaN(f)); + assert(!isNaN(-f)); + } +} + +/********************************* + * Determines if $(D_PARAM x) is finite. + * Params: + * x = a floating point number. + * Returns: + * $(D true) if $(D_PARAM x) is finite. + */ +bool isFinite(X)(X x) @trusted pure nothrow @nogc +{ + alias F = floatTraits!(X); + ushort* pe = cast(ushort *)&x; + return (pe[F.EXPPOS_SHORT] & F.EXPMASK) != F.EXPMASK; +} + +/// +@safe pure nothrow @nogc unittest +{ + assert( isFinite(1.23f)); + assert( isFinite(float.max)); + assert( isFinite(float.min_normal)); + assert(!isFinite(float.nan)); + assert(!isFinite(float.infinity)); +} + +@safe pure nothrow @nogc unittest +{ + assert(isFinite(1.23)); + assert(isFinite(double.max)); + assert(isFinite(double.min_normal)); + assert(!isFinite(double.nan)); + assert(!isFinite(double.infinity)); + + assert(isFinite(1.23L)); + assert(isFinite(real.max)); + assert(isFinite(real.min_normal)); + assert(!isFinite(real.nan)); + assert(!isFinite(real.infinity)); +} + + +/********************************* + * Determines if $(D_PARAM x) is normalized. + * + * A normalized number must not be zero, subnormal, infinite nor $(NAN). + * + * Params: + * x = a floating point number. + * Returns: + * $(D true) if $(D_PARAM x) is normalized. + */ + +/* Need one for each format because subnormal floats might + * be converted to normal reals. + */ +bool isNormal(X)(X x) @trusted pure nothrow @nogc +{ + alias F = floatTraits!(X); + static if (F.realFormat == RealFormat.ibmExtended) + { + // doubledouble is normal if the least significant part is normal. + return isNormal((cast(double*)&x)[MANTISSA_LSB]); + } + else + { + ushort e = F.EXPMASK & (cast(ushort *)&x)[F.EXPPOS_SHORT]; + return (e != F.EXPMASK && e != 0); + } +} + +/// +@safe pure nothrow @nogc unittest +{ + float f = 3; + double d = 500; + real e = 10e+48; + + assert(isNormal(f)); + assert(isNormal(d)); + assert(isNormal(e)); + f = d = e = 0; + assert(!isNormal(f)); + assert(!isNormal(d)); + assert(!isNormal(e)); + assert(!isNormal(real.infinity)); + assert(isNormal(-real.max)); + assert(!isNormal(real.min_normal/4)); + +} + +/********************************* + * Determines if $(D_PARAM x) is subnormal. + * + * Subnormals (also known as "denormal number"), have a 0 exponent + * and a 0 most significant mantissa bit. + * + * Params: + * x = a floating point number. + * Returns: + * $(D true) if $(D_PARAM x) is a denormal number. + */ +bool isSubnormal(X)(X x) @trusted pure nothrow @nogc +{ + /* + Need one for each format because subnormal floats might + be converted to normal reals. + */ + alias F = floatTraits!(X); + static if (F.realFormat == RealFormat.ieeeSingle) + { + uint *p = cast(uint *)&x; + return (*p & F.EXPMASK_INT) == 0 && *p & F.MANTISSAMASK_INT; + } + else static if (F.realFormat == RealFormat.ieeeDouble) + { + uint *p = cast(uint *)&x; + return (p[MANTISSA_MSB] & F.EXPMASK_INT) == 0 + && (p[MANTISSA_LSB] || p[MANTISSA_MSB] & F.MANTISSAMASK_INT); + } + else static if (F.realFormat == RealFormat.ieeeQuadruple) + { + ushort e = F.EXPMASK & (cast(ushort *)&x)[F.EXPPOS_SHORT]; + long* ps = cast(long *)&x; + return (e == 0 && + ((ps[MANTISSA_LSB]|(ps[MANTISSA_MSB]& 0x0000_FFFF_FFFF_FFFF)) != 0)); + } + else static if (F.realFormat == RealFormat.ieeeExtended) + { + ushort* pe = cast(ushort *)&x; + long* ps = cast(long *)&x; + + return (pe[F.EXPPOS_SHORT] & F.EXPMASK) == 0 && *ps > 0; + } + else static if (F.realFormat == RealFormat.ibmExtended) + { + return isSubnormal((cast(double*)&x)[MANTISSA_MSB]); + } + else + { + static assert(false, "Not implemented for this architecture"); + } +} + +/// +@safe pure nothrow @nogc unittest +{ + import std.meta : AliasSeq; + + foreach (T; AliasSeq!(float, double, real)) + { + T f; + for (f = 1.0; !isSubnormal(f); f /= 2) + assert(f != 0); + } +} + +/********************************* + * Determines if $(D_PARAM x) is $(PLUSMN)$(INFIN). + * Params: + * x = a floating point number. + * Returns: + * $(D true) if $(D_PARAM x) is $(PLUSMN)$(INFIN). + */ +bool isInfinity(X)(X x) @nogc @trusted pure nothrow +if (isFloatingPoint!(X)) +{ + alias F = floatTraits!(X); + static if (F.realFormat == RealFormat.ieeeSingle) + { + return ((*cast(uint *)&x) & 0x7FFF_FFFF) == 0x7F80_0000; + } + else static if (F.realFormat == RealFormat.ieeeDouble) + { + return ((*cast(ulong *)&x) & 0x7FFF_FFFF_FFFF_FFFF) + == 0x7FF0_0000_0000_0000; + } + else static if (F.realFormat == RealFormat.ieeeExtended) + { + const ushort e = cast(ushort)(F.EXPMASK & (cast(ushort *)&x)[F.EXPPOS_SHORT]); + const ulong ps = *cast(ulong *)&x; + + // On Motorola 68K, infinity can have hidden bit = 1 or 0. On x86, it is always 1. + return e == F.EXPMASK && (ps & 0x7FFF_FFFF_FFFF_FFFF) == 0; + } + else static if (F.realFormat == RealFormat.ibmExtended) + { + return (((cast(ulong *)&x)[MANTISSA_MSB]) & 0x7FFF_FFFF_FFFF_FFFF) + == 0x7FF8_0000_0000_0000; + } + else static if (F.realFormat == RealFormat.ieeeQuadruple) + { + const long psLsb = (cast(long *)&x)[MANTISSA_LSB]; + const long psMsb = (cast(long *)&x)[MANTISSA_MSB]; + return (psLsb == 0) + && (psMsb & 0x7FFF_FFFF_FFFF_FFFF) == 0x7FFF_0000_0000_0000; + } + else + { + return (x < -X.max) || (X.max < x); + } +} + +/// +@nogc @safe pure nothrow unittest +{ + assert(!isInfinity(float.init)); + assert(!isInfinity(-float.init)); + assert(!isInfinity(float.nan)); + assert(!isInfinity(-float.nan)); + assert(isInfinity(float.infinity)); + assert(isInfinity(-float.infinity)); + assert(isInfinity(-1.0f / 0.0f)); +} + +@safe pure nothrow @nogc unittest +{ + // CTFE-able tests + assert(!isInfinity(double.init)); + assert(!isInfinity(-double.init)); + assert(!isInfinity(double.nan)); + assert(!isInfinity(-double.nan)); + assert(isInfinity(double.infinity)); + assert(isInfinity(-double.infinity)); + assert(isInfinity(-1.0 / 0.0)); + + assert(!isInfinity(real.init)); + assert(!isInfinity(-real.init)); + assert(!isInfinity(real.nan)); + assert(!isInfinity(-real.nan)); + assert(isInfinity(real.infinity)); + assert(isInfinity(-real.infinity)); + assert(isInfinity(-1.0L / 0.0L)); + + // Runtime tests + shared float f; + f = float.init; + assert(!isInfinity(f)); + assert(!isInfinity(-f)); + f = float.nan; + assert(!isInfinity(f)); + assert(!isInfinity(-f)); + f = float.infinity; + assert(isInfinity(f)); + assert(isInfinity(-f)); + f = (-1.0f / 0.0f); + assert(isInfinity(f)); + + shared double d; + d = double.init; + assert(!isInfinity(d)); + assert(!isInfinity(-d)); + d = double.nan; + assert(!isInfinity(d)); + assert(!isInfinity(-d)); + d = double.infinity; + assert(isInfinity(d)); + assert(isInfinity(-d)); + d = (-1.0 / 0.0); + assert(isInfinity(d)); + + shared real e; + e = real.init; + assert(!isInfinity(e)); + assert(!isInfinity(-e)); + e = real.nan; + assert(!isInfinity(e)); + assert(!isInfinity(-e)); + e = real.infinity; + assert(isInfinity(e)); + assert(isInfinity(-e)); + e = (-1.0L / 0.0L); + assert(isInfinity(e)); +} + +/********************************* + * Is the binary representation of x identical to y? + * + * Same as ==, except that positive and negative zero are not identical, + * and two $(NAN)s are identical if they have the same 'payload'. + */ +bool isIdentical(real x, real y) @trusted pure nothrow @nogc +{ + // We're doing a bitwise comparison so the endianness is irrelevant. + long* pxs = cast(long *)&x; + long* pys = cast(long *)&y; + alias F = floatTraits!(real); + static if (F.realFormat == RealFormat.ieeeDouble) + { + return pxs[0] == pys[0]; + } + else static if (F.realFormat == RealFormat.ieeeQuadruple + || F.realFormat == RealFormat.ibmExtended) + { + return pxs[0] == pys[0] && pxs[1] == pys[1]; + } + else + { + ushort* pxe = cast(ushort *)&x; + ushort* pye = cast(ushort *)&y; + return pxe[4] == pye[4] && pxs[0] == pys[0]; + } +} + +/********************************* + * Return 1 if sign bit of e is set, 0 if not. + */ +int signbit(X)(X x) @nogc @trusted pure nothrow +{ + alias F = floatTraits!(X); + return ((cast(ubyte *)&x)[F.SIGNPOS_BYTE] & 0x80) != 0; +} + +/// +@nogc @safe pure nothrow unittest +{ + assert(!signbit(float.nan)); + assert(signbit(-float.nan)); + assert(!signbit(168.1234f)); + assert(signbit(-168.1234f)); + assert(!signbit(0.0f)); + assert(signbit(-0.0f)); + assert(signbit(-float.max)); + assert(!signbit(float.max)); + + assert(!signbit(double.nan)); + assert(signbit(-double.nan)); + assert(!signbit(168.1234)); + assert(signbit(-168.1234)); + assert(!signbit(0.0)); + assert(signbit(-0.0)); + assert(signbit(-double.max)); + assert(!signbit(double.max)); + + assert(!signbit(real.nan)); + assert(signbit(-real.nan)); + assert(!signbit(168.1234L)); + assert(signbit(-168.1234L)); + assert(!signbit(0.0L)); + assert(signbit(-0.0L)); + assert(signbit(-real.max)); + assert(!signbit(real.max)); +} + + +/********************************* + * Return a value composed of to with from's sign bit. + */ +R copysign(R, X)(R to, X from) @trusted pure nothrow @nogc +if (isFloatingPoint!(R) && isFloatingPoint!(X)) +{ + ubyte* pto = cast(ubyte *)&to; + const ubyte* pfrom = cast(ubyte *)&from; + + alias T = floatTraits!(R); + alias F = floatTraits!(X); + pto[T.SIGNPOS_BYTE] &= 0x7F; + pto[T.SIGNPOS_BYTE] |= pfrom[F.SIGNPOS_BYTE] & 0x80; + return to; +} + +// ditto +R copysign(R, X)(X to, R from) @trusted pure nothrow @nogc +if (isIntegral!(X) && isFloatingPoint!(R)) +{ + return copysign(cast(R) to, from); +} + +@safe pure nothrow @nogc unittest +{ + import std.meta : AliasSeq; + + foreach (X; AliasSeq!(float, double, real, int, long)) + { + foreach (Y; AliasSeq!(float, double, real)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + X x = 21; + Y y = 23.8; + Y e = void; + + e = copysign(x, y); + assert(e == 21.0); + + e = copysign(-x, y); + assert(e == 21.0); + + e = copysign(x, -y); + assert(e == -21.0); + + e = copysign(-x, -y); + assert(e == -21.0); + + static if (isFloatingPoint!X) + { + e = copysign(X.nan, y); + assert(isNaN(e) && !signbit(e)); + + e = copysign(X.nan, -y); + assert(isNaN(e) && signbit(e)); + } + }(); + } +} + +/********************************* +Returns $(D -1) if $(D x < 0), $(D x) if $(D x == 0), $(D 1) if +$(D x > 0), and $(NAN) if x==$(NAN). + */ +F sgn(F)(F x) @safe pure nothrow @nogc +{ + // @@@TODO@@@: make this faster + return x > 0 ? 1 : x < 0 ? -1 : x; +} + +/// +@safe pure nothrow @nogc unittest +{ + assert(sgn(168.1234) == 1); + assert(sgn(-168.1234) == -1); + assert(sgn(0.0) == 0); + assert(sgn(-0.0) == 0); +} + +// Functions for NaN payloads +/* + * A 'payload' can be stored in the significand of a $(NAN). One bit is required + * to distinguish between a quiet and a signalling $(NAN). This leaves 22 bits + * of payload for a float; 51 bits for a double; 62 bits for an 80-bit real; + * and 111 bits for a 128-bit quad. +*/ +/** + * Create a quiet $(NAN), storing an integer inside the payload. + * + * For floats, the largest possible payload is 0x3F_FFFF. + * For doubles, it is 0x3_FFFF_FFFF_FFFF. + * For 80-bit or 128-bit reals, it is 0x3FFF_FFFF_FFFF_FFFF. + */ +real NaN(ulong payload) @trusted pure nothrow @nogc +{ + alias F = floatTraits!(real); + static if (F.realFormat == RealFormat.ieeeExtended) + { + // real80 (in x86 real format, the implied bit is actually + // not implied but a real bit which is stored in the real) + ulong v = 3; // implied bit = 1, quiet bit = 1 + } + else + { + ulong v = 1; // no implied bit. quiet bit = 1 + } + + ulong a = payload; + + // 22 Float bits + ulong w = a & 0x3F_FFFF; + a -= w; + + v <<=22; + v |= w; + a >>=22; + + // 29 Double bits + v <<=29; + w = a & 0xFFF_FFFF; + v |= w; + a -= w; + a >>=29; + + static if (F.realFormat == RealFormat.ieeeDouble) + { + v |= 0x7FF0_0000_0000_0000; + real x; + * cast(ulong *)(&x) = v; + return x; + } + else + { + v <<=11; + a &= 0x7FF; + v |= a; + real x = real.nan; + + // Extended real bits + static if (F.realFormat == RealFormat.ieeeQuadruple) + { + v <<= 1; // there's no implicit bit + + version (LittleEndian) + { + *cast(ulong*)(6+cast(ubyte*)(&x)) = v; + } + else + { + *cast(ulong*)(2+cast(ubyte*)(&x)) = v; + } + } + else + { + *cast(ulong *)(&x) = v; + } + return x; + } +} + +@system pure nothrow @nogc unittest // not @safe because taking address of local. +{ + static if (floatTraits!(real).realFormat == RealFormat.ieeeDouble) + { + auto x = NaN(1); + auto xl = *cast(ulong*)&x; + assert(xl & 0x8_0000_0000_0000UL); //non-signaling bit, bit 52 + assert((xl & 0x7FF0_0000_0000_0000UL) == 0x7FF0_0000_0000_0000UL); //all exp bits set + } +} + +/** + * Extract an integral payload from a $(NAN). + * + * Returns: + * the integer payload as a ulong. + * + * For floats, the largest possible payload is 0x3F_FFFF. + * For doubles, it is 0x3_FFFF_FFFF_FFFF. + * For 80-bit or 128-bit reals, it is 0x3FFF_FFFF_FFFF_FFFF. + */ +ulong getNaNPayload(real x) @trusted pure nothrow @nogc +{ + // assert(isNaN(x)); + alias F = floatTraits!(real); + static if (F.realFormat == RealFormat.ieeeDouble) + { + ulong m = *cast(ulong *)(&x); + // Make it look like an 80-bit significand. + // Skip exponent, and quiet bit + m &= 0x0007_FFFF_FFFF_FFFF; + m <<= 11; + } + else static if (F.realFormat == RealFormat.ieeeQuadruple) + { + version (LittleEndian) + { + ulong m = *cast(ulong*)(6+cast(ubyte*)(&x)); + } + else + { + ulong m = *cast(ulong*)(2+cast(ubyte*)(&x)); + } + + m >>= 1; // there's no implicit bit + } + else + { + ulong m = *cast(ulong *)(&x); + } + + // ignore implicit bit and quiet bit + + const ulong f = m & 0x3FFF_FF00_0000_0000L; + + ulong w = f >>> 40; + w |= (m & 0x00FF_FFFF_F800L) << (22 - 11); + w |= (m & 0x7FF) << 51; + return w; +} + +debug(UnitTest) +{ + @safe pure nothrow @nogc unittest + { + real nan4 = NaN(0x789_ABCD_EF12_3456); + static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended + || floatTraits!(real).realFormat == RealFormat.ieeeQuadruple) + { + assert(getNaNPayload(nan4) == 0x789_ABCD_EF12_3456); + } + else + { + assert(getNaNPayload(nan4) == 0x1_ABCD_EF12_3456); + } + double nan5 = nan4; + assert(getNaNPayload(nan5) == 0x1_ABCD_EF12_3456); + float nan6 = nan4; + assert(getNaNPayload(nan6) == 0x12_3456); + nan4 = NaN(0xFABCD); + assert(getNaNPayload(nan4) == 0xFABCD); + nan6 = nan4; + assert(getNaNPayload(nan6) == 0xFABCD); + nan5 = NaN(0x100_0000_0000_3456); + assert(getNaNPayload(nan5) == 0x0000_0000_3456); + } +} + +/** + * Calculate the next largest floating point value after x. + * + * Return the least number greater than x that is representable as a real; + * thus, it gives the next point on the IEEE number line. + * + * $(TABLE_SV + * $(SVH x, nextUp(x) ) + * $(SV -$(INFIN), -real.max ) + * $(SV $(PLUSMN)0.0, real.min_normal*real.epsilon ) + * $(SV real.max, $(INFIN) ) + * $(SV $(INFIN), $(INFIN) ) + * $(SV $(NAN), $(NAN) ) + * ) + */ +real nextUp(real x) @trusted pure nothrow @nogc +{ + alias F = floatTraits!(real); + static if (F.realFormat == RealFormat.ieeeDouble) + { + return nextUp(cast(double) x); + } + else static if (F.realFormat == RealFormat.ieeeQuadruple) + { + ushort e = F.EXPMASK & (cast(ushort *)&x)[F.EXPPOS_SHORT]; + if (e == F.EXPMASK) + { + // NaN or Infinity + if (x == -real.infinity) return -real.max; + return x; // +Inf and NaN are unchanged. + } + + auto ps = cast(ulong *)&x; + if (ps[MANTISSA_MSB] & 0x8000_0000_0000_0000) + { + // Negative number + if (ps[MANTISSA_LSB] == 0 && ps[MANTISSA_MSB] == 0x8000_0000_0000_0000) + { + // it was negative zero, change to smallest subnormal + ps[MANTISSA_LSB] = 1; + ps[MANTISSA_MSB] = 0; + return x; + } + if (ps[MANTISSA_LSB] == 0) --ps[MANTISSA_MSB]; + --ps[MANTISSA_LSB]; + } + else + { + // Positive number + ++ps[MANTISSA_LSB]; + if (ps[MANTISSA_LSB] == 0) ++ps[MANTISSA_MSB]; + } + return x; + } + else static if (F.realFormat == RealFormat.ieeeExtended) + { + // For 80-bit reals, the "implied bit" is a nuisance... + ushort *pe = cast(ushort *)&x; + ulong *ps = cast(ulong *)&x; + + if ((pe[F.EXPPOS_SHORT] & F.EXPMASK) == F.EXPMASK) + { + // First, deal with NANs and infinity + if (x == -real.infinity) return -real.max; + return x; // +Inf and NaN are unchanged. + } + if (pe[F.EXPPOS_SHORT] & 0x8000) + { + // Negative number -- need to decrease the significand + --*ps; + // Need to mask with 0x7FFF... so subnormals are treated correctly. + if ((*ps & 0x7FFF_FFFF_FFFF_FFFF) == 0x7FFF_FFFF_FFFF_FFFF) + { + if (pe[F.EXPPOS_SHORT] == 0x8000) // it was negative zero + { + *ps = 1; + pe[F.EXPPOS_SHORT] = 0; // smallest subnormal. + return x; + } + + --pe[F.EXPPOS_SHORT]; + + if (pe[F.EXPPOS_SHORT] == 0x8000) + return x; // it's become a subnormal, implied bit stays low. + + *ps = 0xFFFF_FFFF_FFFF_FFFF; // set the implied bit + return x; + } + return x; + } + else + { + // Positive number -- need to increase the significand. + // Works automatically for positive zero. + ++*ps; + if ((*ps & 0x7FFF_FFFF_FFFF_FFFF) == 0) + { + // change in exponent + ++pe[F.EXPPOS_SHORT]; + *ps = 0x8000_0000_0000_0000; // set the high bit + } + } + return x; + } + else // static if (F.realFormat == RealFormat.ibmExtended) + { + assert(0, "nextUp not implemented"); + } +} + +/** ditto */ +double nextUp(double x) @trusted pure nothrow @nogc +{ + ulong *ps = cast(ulong *)&x; + + if ((*ps & 0x7FF0_0000_0000_0000) == 0x7FF0_0000_0000_0000) + { + // First, deal with NANs and infinity + if (x == -x.infinity) return -x.max; + return x; // +INF and NAN are unchanged. + } + if (*ps & 0x8000_0000_0000_0000) // Negative number + { + if (*ps == 0x8000_0000_0000_0000) // it was negative zero + { + *ps = 0x0000_0000_0000_0001; // change to smallest subnormal + return x; + } + --*ps; + } + else + { // Positive number + ++*ps; + } + return x; +} + +/** ditto */ +float nextUp(float x) @trusted pure nothrow @nogc +{ + uint *ps = cast(uint *)&x; + + if ((*ps & 0x7F80_0000) == 0x7F80_0000) + { + // First, deal with NANs and infinity + if (x == -x.infinity) return -x.max; + + return x; // +INF and NAN are unchanged. + } + if (*ps & 0x8000_0000) // Negative number + { + if (*ps == 0x8000_0000) // it was negative zero + { + *ps = 0x0000_0001; // change to smallest subnormal + return x; + } + + --*ps; + } + else + { + // Positive number + ++*ps; + } + return x; +} + +/** + * Calculate the next smallest floating point value before x. + * + * Return the greatest number less than x that is representable as a real; + * thus, it gives the previous point on the IEEE number line. + * + * $(TABLE_SV + * $(SVH x, nextDown(x) ) + * $(SV $(INFIN), real.max ) + * $(SV $(PLUSMN)0.0, -real.min_normal*real.epsilon ) + * $(SV -real.max, -$(INFIN) ) + * $(SV -$(INFIN), -$(INFIN) ) + * $(SV $(NAN), $(NAN) ) + * ) + */ +real nextDown(real x) @safe pure nothrow @nogc +{ + return -nextUp(-x); +} + +/** ditto */ +double nextDown(double x) @safe pure nothrow @nogc +{ + return -nextUp(-x); +} + +/** ditto */ +float nextDown(float x) @safe pure nothrow @nogc +{ + return -nextUp(-x); +} + +/// +@safe pure nothrow @nogc unittest +{ + assert( nextDown(1.0 + real.epsilon) == 1.0); +} + +@safe pure nothrow @nogc unittest +{ + static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended) + { + + // Tests for 80-bit reals + assert(isIdentical(nextUp(NaN(0xABC)), NaN(0xABC))); + // negative numbers + assert( nextUp(-real.infinity) == -real.max ); + assert( nextUp(-1.0L-real.epsilon) == -1.0 ); + assert( nextUp(-2.0L) == -2.0 + real.epsilon); + // subnormals and zero + assert( nextUp(-real.min_normal) == -real.min_normal*(1-real.epsilon) ); + assert( nextUp(-real.min_normal*(1-real.epsilon)) == -real.min_normal*(1-2*real.epsilon) ); + assert( isIdentical(-0.0L, nextUp(-real.min_normal*real.epsilon)) ); + assert( nextUp(-0.0L) == real.min_normal*real.epsilon ); + assert( nextUp(0.0L) == real.min_normal*real.epsilon ); + assert( nextUp(real.min_normal*(1-real.epsilon)) == real.min_normal ); + assert( nextUp(real.min_normal) == real.min_normal*(1+real.epsilon) ); + // positive numbers + assert( nextUp(1.0L) == 1.0 + real.epsilon ); + assert( nextUp(2.0L-real.epsilon) == 2.0 ); + assert( nextUp(real.max) == real.infinity ); + assert( nextUp(real.infinity)==real.infinity ); + } + + double n = NaN(0xABC); + assert(isIdentical(nextUp(n), n)); + // negative numbers + assert( nextUp(-double.infinity) == -double.max ); + assert( nextUp(-1-double.epsilon) == -1.0 ); + assert( nextUp(-2.0) == -2.0 + double.epsilon); + // subnormals and zero + + assert( nextUp(-double.min_normal) == -double.min_normal*(1-double.epsilon) ); + assert( nextUp(-double.min_normal*(1-double.epsilon)) == -double.min_normal*(1-2*double.epsilon) ); + assert( isIdentical(-0.0, nextUp(-double.min_normal*double.epsilon)) ); + assert( nextUp(0.0) == double.min_normal*double.epsilon ); + assert( nextUp(-0.0) == double.min_normal*double.epsilon ); + assert( nextUp(double.min_normal*(1-double.epsilon)) == double.min_normal ); + assert( nextUp(double.min_normal) == double.min_normal*(1+double.epsilon) ); + // positive numbers + assert( nextUp(1.0) == 1.0 + double.epsilon ); + assert( nextUp(2.0-double.epsilon) == 2.0 ); + assert( nextUp(double.max) == double.infinity ); + + float fn = NaN(0xABC); + assert(isIdentical(nextUp(fn), fn)); + float f = -float.min_normal*(1-float.epsilon); + float f1 = -float.min_normal; + assert( nextUp(f1) == f); + f = 1.0f+float.epsilon; + f1 = 1.0f; + assert( nextUp(f1) == f ); + f1 = -0.0f; + assert( nextUp(f1) == float.min_normal*float.epsilon); + assert( nextUp(float.infinity)==float.infinity ); + + assert(nextDown(1.0L+real.epsilon)==1.0); + assert(nextDown(1.0+double.epsilon)==1.0); + f = 1.0f+float.epsilon; + assert(nextDown(f)==1.0); + assert(nextafter(1.0+real.epsilon, -real.infinity)==1.0); +} + + + +/****************************************** + * Calculates the next representable value after x in the direction of y. + * + * If y > x, the result will be the next largest floating-point value; + * if y < x, the result will be the next smallest value. + * If x == y, the result is y. + * + * Remarks: + * This function is not generally very useful; it's almost always better to use + * the faster functions nextUp() or nextDown() instead. + * + * The FE_INEXACT and FE_OVERFLOW exceptions will be raised if x is finite and + * the function result is infinite. The FE_INEXACT and FE_UNDERFLOW + * exceptions will be raised if the function value is subnormal, and x is + * not equal to y. + */ +T nextafter(T)(const T x, const T y) @safe pure nothrow @nogc +{ + if (x == y) return y; + return ((y>x) ? nextUp(x) : nextDown(x)); +} + +/// +@safe pure nothrow @nogc unittest +{ + float a = 1; + assert(is(typeof(nextafter(a, a)) == float)); + assert(nextafter(a, a.infinity) > a); + + double b = 2; + assert(is(typeof(nextafter(b, b)) == double)); + assert(nextafter(b, b.infinity) > b); + + real c = 3; + assert(is(typeof(nextafter(c, c)) == real)); + assert(nextafter(c, c.infinity) > c); +} + +//real nexttoward(real x, real y) { return core.stdc.math.nexttowardl(x, y); } + +/******************************************* + * Returns the positive difference between x and y. + * Returns: + * $(TABLE_SV + * $(TR $(TH x, y) $(TH fdim(x, y))) + * $(TR $(TD x $(GT) y) $(TD x - y)) + * $(TR $(TD x $(LT)= y) $(TD +0.0)) + * ) + */ +real fdim(real x, real y) @safe pure nothrow @nogc { return (x > y) ? x - y : +0.0; } + +/**************************************** + * Returns the larger of x and y. + */ +real fmax(real x, real y) @safe pure nothrow @nogc { return x > y ? x : y; } + +/**************************************** + * Returns the smaller of x and y. + */ +real fmin(real x, real y) @safe pure nothrow @nogc { return x < y ? x : y; } + +/************************************** + * Returns (x * y) + z, rounding only once according to the + * current rounding mode. + * + * BUGS: Not currently implemented - rounds twice. + */ +real fma(real x, real y, real z) @safe pure nothrow @nogc { return (x * y) + z; } + +/******************************************************************* + * Compute the value of x $(SUPERSCRIPT n), where n is an integer + */ +Unqual!F pow(F, G)(F x, G n) @nogc @trusted pure nothrow +if (isFloatingPoint!(F) && isIntegral!(G)) +{ + import std.traits : Unsigned; + real p = 1.0, v = void; + Unsigned!(Unqual!G) m = n; + if (n < 0) + { + switch (n) + { + case -1: + return 1 / x; + case -2: + return 1 / (x * x); + default: + } + + m = cast(typeof(m))(0 - n); + v = p / x; + } + else + { + switch (n) + { + case 0: + return 1.0; + case 1: + return x; + case 2: + return x * x; + default: + } + + v = x; + } + + while (1) + { + if (m & 1) + p *= v; + m >>= 1; + if (!m) + break; + v *= v; + } + return p; +} + +@safe pure nothrow @nogc unittest +{ + // Make sure it instantiates and works properly on immutable values and + // with various integer and float types. + immutable real x = 46; + immutable float xf = x; + immutable double xd = x; + immutable uint one = 1; + immutable ushort two = 2; + immutable ubyte three = 3; + immutable ulong eight = 8; + + immutable int neg1 = -1; + immutable short neg2 = -2; + immutable byte neg3 = -3; + immutable long neg8 = -8; + + + assert(pow(x,0) == 1.0); + assert(pow(xd,one) == x); + assert(pow(xf,two) == x * x); + assert(pow(x,three) == x * x * x); + assert(pow(x,eight) == (x * x) * (x * x) * (x * x) * (x * x)); + + assert(pow(x, neg1) == 1 / x); + + version (X86_64) + { + pragma(msg, "test disabled on x86_64, see bug 5628"); + } + else version (ARM) + { + pragma(msg, "test disabled on ARM, see bug 5628"); + } + else + { + assert(pow(xd, neg2) == 1 / (x * x)); + assert(pow(xf, neg8) == 1 / ((x * x) * (x * x) * (x * x) * (x * x))); + } + + assert(feqrel(pow(x, neg3), 1 / (x * x * x)) >= real.mant_dig - 1); +} + +@system unittest +{ + assert(equalsDigit(pow(2.0L, 10.0L), 1024, 19)); +} + +/** Compute the value of an integer x, raised to the power of a positive + * integer n. + * + * If both x and n are 0, the result is 1. + * If n is negative, an integer divide error will occur at runtime, + * regardless of the value of x. + */ +typeof(Unqual!(F).init * Unqual!(G).init) pow(F, G)(F x, G n) @nogc @trusted pure nothrow +if (isIntegral!(F) && isIntegral!(G)) +{ + if (n<0) return x/0; // Only support positive powers + typeof(return) p, v = void; + Unqual!G m = n; + + switch (m) + { + case 0: + p = 1; + break; + + case 1: + p = x; + break; + + case 2: + p = x * x; + break; + + default: + v = x; + p = 1; + while (1) + { + if (m & 1) + p *= v; + m >>= 1; + if (!m) + break; + v *= v; + } + break; + } + return p; +} + +/// +@safe pure nothrow @nogc unittest +{ + immutable int one = 1; + immutable byte two = 2; + immutable ubyte three = 3; + immutable short four = 4; + immutable long ten = 10; + + assert(pow(two, three) == 8); + assert(pow(two, ten) == 1024); + assert(pow(one, ten) == 1); + assert(pow(ten, four) == 10_000); + assert(pow(four, 10) == 1_048_576); + assert(pow(three, four) == 81); + +} + +/**Computes integer to floating point powers.*/ +real pow(I, F)(I x, F y) @nogc @trusted pure nothrow +if (isIntegral!I && isFloatingPoint!F) +{ + return pow(cast(real) x, cast(Unqual!F) y); +} + +/********************************************* + * Calculates x$(SUPERSCRIPT y). + * + * $(TABLE_SV + * $(TR $(TH x) $(TH y) $(TH pow(x, y)) + * $(TH div 0) $(TH invalid?)) + * $(TR $(TD anything) $(TD $(PLUSMN)0.0) $(TD 1.0) + * $(TD no) $(TD no) ) + * $(TR $(TD |x| $(GT) 1) $(TD +$(INFIN)) $(TD +$(INFIN)) + * $(TD no) $(TD no) ) + * $(TR $(TD |x| $(LT) 1) $(TD +$(INFIN)) $(TD +0.0) + * $(TD no) $(TD no) ) + * $(TR $(TD |x| $(GT) 1) $(TD -$(INFIN)) $(TD +0.0) + * $(TD no) $(TD no) ) + * $(TR $(TD |x| $(LT) 1) $(TD -$(INFIN)) $(TD +$(INFIN)) + * $(TD no) $(TD no) ) + * $(TR $(TD +$(INFIN)) $(TD $(GT) 0.0) $(TD +$(INFIN)) + * $(TD no) $(TD no) ) + * $(TR $(TD +$(INFIN)) $(TD $(LT) 0.0) $(TD +0.0) + * $(TD no) $(TD no) ) + * $(TR $(TD -$(INFIN)) $(TD odd integer $(GT) 0.0) $(TD -$(INFIN)) + * $(TD no) $(TD no) ) + * $(TR $(TD -$(INFIN)) $(TD $(GT) 0.0, not odd integer) $(TD +$(INFIN)) + * $(TD no) $(TD no)) + * $(TR $(TD -$(INFIN)) $(TD odd integer $(LT) 0.0) $(TD -0.0) + * $(TD no) $(TD no) ) + * $(TR $(TD -$(INFIN)) $(TD $(LT) 0.0, not odd integer) $(TD +0.0) + * $(TD no) $(TD no) ) + * $(TR $(TD $(PLUSMN)1.0) $(TD $(PLUSMN)$(INFIN)) $(TD $(NAN)) + * $(TD no) $(TD yes) ) + * $(TR $(TD $(LT) 0.0) $(TD finite, nonintegral) $(TD $(NAN)) + * $(TD no) $(TD yes)) + * $(TR $(TD $(PLUSMN)0.0) $(TD odd integer $(LT) 0.0) $(TD $(PLUSMNINF)) + * $(TD yes) $(TD no) ) + * $(TR $(TD $(PLUSMN)0.0) $(TD $(LT) 0.0, not odd integer) $(TD +$(INFIN)) + * $(TD yes) $(TD no)) + * $(TR $(TD $(PLUSMN)0.0) $(TD odd integer $(GT) 0.0) $(TD $(PLUSMN)0.0) + * $(TD no) $(TD no) ) + * $(TR $(TD $(PLUSMN)0.0) $(TD $(GT) 0.0, not odd integer) $(TD +0.0) + * $(TD no) $(TD no) ) + * ) + */ +Unqual!(Largest!(F, G)) pow(F, G)(F x, G y) @nogc @trusted pure nothrow +if (isFloatingPoint!(F) && isFloatingPoint!(G)) +{ + alias Float = typeof(return); + + static real impl(real x, real y) @nogc pure nothrow + { + // Special cases. + if (isNaN(y)) + return y; + if (isNaN(x) && y != 0.0) + return x; + + // Even if x is NaN. + if (y == 0.0) + return 1.0; + if (y == 1.0) + return x; + + if (isInfinity(y)) + { + if (fabs(x) > 1) + { + if (signbit(y)) + return +0.0; + else + return F.infinity; + } + else if (fabs(x) == 1) + { + return y * 0; // generate NaN. + } + else // < 1 + { + if (signbit(y)) + return F.infinity; + else + return +0.0; + } + } + if (isInfinity(x)) + { + if (signbit(x)) + { + long i = cast(long) y; + if (y > 0.0) + { + if (i == y && i & 1) + return -F.infinity; + else + return F.infinity; + } + else if (y < 0.0) + { + if (i == y && i & 1) + return -0.0; + else + return +0.0; + } + } + else + { + if (y > 0.0) + return F.infinity; + else if (y < 0.0) + return +0.0; + } + } + + if (x == 0.0) + { + if (signbit(x)) + { + long i = cast(long) y; + if (y > 0.0) + { + if (i == y && i & 1) + return -0.0; + else + return +0.0; + } + else if (y < 0.0) + { + if (i == y && i & 1) + return -F.infinity; + else + return F.infinity; + } + } + else + { + if (y > 0.0) + return +0.0; + else if (y < 0.0) + return F.infinity; + } + } + if (x == 1.0) + return 1.0; + + if (y >= F.max) + { + if ((x > 0.0 && x < 1.0) || (x > -1.0 && x < 0.0)) + return 0.0; + if (x > 1.0 || x < -1.0) + return F.infinity; + } + if (y <= -F.max) + { + if ((x > 0.0 && x < 1.0) || (x > -1.0 && x < 0.0)) + return F.infinity; + if (x > 1.0 || x < -1.0) + return 0.0; + } + + if (x >= F.max) + { + if (y > 0.0) + return F.infinity; + else + return 0.0; + } + if (x <= -F.max) + { + long i = cast(long) y; + if (y > 0.0) + { + if (i == y && i & 1) + return -F.infinity; + else + return F.infinity; + } + else if (y < 0.0) + { + if (i == y && i & 1) + return -0.0; + else + return +0.0; + } + } + + // Integer power of x. + long iy = cast(long) y; + if (iy == y && fabs(y) < 32_768.0) + return pow(x, iy); + + real sign = 1.0; + if (x < 0) + { + // Result is real only if y is an integer + // Check for a non-zero fractional part + enum maxOdd = pow(2.0L, real.mant_dig) - 1.0L; + static if (maxOdd > ulong.max) + { + // Generic method, for any FP type + if (floor(y) != y) + return sqrt(x); // Complex result -- create a NaN + + const hy = ldexp(y, -1); + if (floor(hy) != hy) + sign = -1.0; + } + else + { + // Much faster, if ulong has enough precision + const absY = fabs(y); + if (absY <= maxOdd) + { + const uy = cast(ulong) absY; + if (uy != absY) + return sqrt(x); // Complex result -- create a NaN + + if (uy & 1) + sign = -1.0; + } + } + x = -x; + } + version (INLINE_YL2X) + { + // If x > 0, x ^^ y == 2 ^^ ( y * log2(x) ) + // TODO: This is not accurate in practice. A fast and accurate + // (though complicated) method is described in: + // "An efficient rounding boundary test for pow(x, y) + // in double precision", C.Q. Lauter and V. Lefèvre, INRIA (2007). + return sign * exp2( core.math.yl2x(x, y) ); + } + else + { + // If x > 0, x ^^ y == 2 ^^ ( y * log2(x) ) + // TODO: This is not accurate in practice. A fast and accurate + // (though complicated) method is described in: + // "An efficient rounding boundary test for pow(x, y) + // in double precision", C.Q. Lauter and V. Lefèvre, INRIA (2007). + Float w = exp2(y * log2(x)); + return sign * w; + } + } + return impl(x, y); +} + +@safe pure nothrow @nogc unittest +{ + // Test all the special values. These unittests can be run on Windows + // by temporarily changing the version (linux) to version (all). + immutable float zero = 0; + immutable real one = 1; + immutable double two = 2; + immutable float three = 3; + immutable float fnan = float.nan; + immutable double dnan = double.nan; + immutable real rnan = real.nan; + immutable dinf = double.infinity; + immutable rninf = -real.infinity; + + assert(pow(fnan, zero) == 1); + assert(pow(dnan, zero) == 1); + assert(pow(rnan, zero) == 1); + + assert(pow(two, dinf) == double.infinity); + assert(isIdentical(pow(0.2f, dinf), +0.0)); + assert(pow(0.99999999L, rninf) == real.infinity); + assert(isIdentical(pow(1.000000001, rninf), +0.0)); + assert(pow(dinf, 0.001) == dinf); + assert(isIdentical(pow(dinf, -0.001), +0.0)); + assert(pow(rninf, 3.0L) == rninf); + assert(pow(rninf, 2.0L) == real.infinity); + assert(isIdentical(pow(rninf, -3.0), -0.0)); + assert(isIdentical(pow(rninf, -2.0), +0.0)); + + // @@@BUG@@@ somewhere + version (OSX) {} else assert(isNaN(pow(one, dinf))); + version (OSX) {} else assert(isNaN(pow(-one, dinf))); + assert(isNaN(pow(-0.2, PI))); + // boundary cases. Note that epsilon == 2^^-n for some n, + // so 1/epsilon == 2^^n is always even. + assert(pow(-1.0L, 1/real.epsilon - 1.0L) == -1.0L); + assert(pow(-1.0L, 1/real.epsilon) == 1.0L); + assert(isNaN(pow(-1.0L, 1/real.epsilon-0.5L))); + assert(isNaN(pow(-1.0L, -1/real.epsilon+0.5L))); + + assert(pow(0.0, -3.0) == double.infinity); + assert(pow(-0.0, -3.0) == -double.infinity); + assert(pow(0.0, -PI) == double.infinity); + assert(pow(-0.0, -PI) == double.infinity); + assert(isIdentical(pow(0.0, 5.0), 0.0)); + assert(isIdentical(pow(-0.0, 5.0), -0.0)); + assert(isIdentical(pow(0.0, 6.0), 0.0)); + assert(isIdentical(pow(-0.0, 6.0), 0.0)); + + // Issue #14786 fixed + immutable real maxOdd = pow(2.0L, real.mant_dig) - 1.0L; + assert(pow(-1.0L, maxOdd) == -1.0L); + assert(pow(-1.0L, -maxOdd) == -1.0L); + assert(pow(-1.0L, maxOdd + 1.0L) == 1.0L); + assert(pow(-1.0L, -maxOdd + 1.0L) == 1.0L); + assert(pow(-1.0L, maxOdd - 1.0L) == 1.0L); + assert(pow(-1.0L, -maxOdd - 1.0L) == 1.0L); + + // Now, actual numbers. + assert(approxEqual(pow(two, three), 8.0)); + assert(approxEqual(pow(two, -2.5), 0.1767767)); + + // Test integer to float power. + immutable uint twoI = 2; + assert(approxEqual(pow(twoI, three), 8.0)); +} + +/************************************** + * To what precision is x equal to y? + * + * Returns: the number of mantissa bits which are equal in x and y. + * eg, 0x1.F8p+60 and 0x1.F1p+60 are equal to 5 bits of precision. + * + * $(TABLE_SV + * $(TR $(TH x) $(TH y) $(TH feqrel(x, y))) + * $(TR $(TD x) $(TD x) $(TD real.mant_dig)) + * $(TR $(TD x) $(TD $(GT)= 2*x) $(TD 0)) + * $(TR $(TD x) $(TD $(LT)= x/2) $(TD 0)) + * $(TR $(TD $(NAN)) $(TD any) $(TD 0)) + * $(TR $(TD any) $(TD $(NAN)) $(TD 0)) + * ) + */ +int feqrel(X)(const X x, const X y) @trusted pure nothrow @nogc +if (isFloatingPoint!(X)) +{ + /* Public Domain. Author: Don Clugston, 18 Aug 2005. + */ + alias F = floatTraits!(X); + static if (F.realFormat == RealFormat.ibmExtended) + { + if (cast(double*)(&x)[MANTISSA_MSB] == cast(double*)(&y)[MANTISSA_MSB]) + { + return double.mant_dig + + feqrel(cast(double*)(&x)[MANTISSA_LSB], + cast(double*)(&y)[MANTISSA_LSB]); + } + else + { + return feqrel(cast(double*)(&x)[MANTISSA_MSB], + cast(double*)(&y)[MANTISSA_MSB]); + } + } + else + { + static assert(F.realFormat == RealFormat.ieeeSingle + || F.realFormat == RealFormat.ieeeDouble + || F.realFormat == RealFormat.ieeeExtended + || F.realFormat == RealFormat.ieeeQuadruple); + + if (x == y) + return X.mant_dig; // ensure diff != 0, cope with INF. + + Unqual!X diff = fabs(x - y); + + ushort *pa = cast(ushort *)(&x); + ushort *pb = cast(ushort *)(&y); + ushort *pd = cast(ushort *)(&diff); + + + // The difference in abs(exponent) between x or y and abs(x-y) + // is equal to the number of significand bits of x which are + // equal to y. If negative, x and y have different exponents. + // If positive, x and y are equal to 'bitsdiff' bits. + // AND with 0x7FFF to form the absolute value. + // To avoid out-by-1 errors, we subtract 1 so it rounds down + // if the exponents were different. This means 'bitsdiff' is + // always 1 lower than we want, except that if bitsdiff == 0, + // they could have 0 or 1 bits in common. + + int bitsdiff = ((( (pa[F.EXPPOS_SHORT] & F.EXPMASK) + + (pb[F.EXPPOS_SHORT] & F.EXPMASK) + - (1 << F.EXPSHIFT)) >> 1) + - (pd[F.EXPPOS_SHORT] & F.EXPMASK)) >> F.EXPSHIFT; + if ( (pd[F.EXPPOS_SHORT] & F.EXPMASK) == 0) + { // Difference is subnormal + // For subnormals, we need to add the number of zeros that + // lie at the start of diff's significand. + // We do this by multiplying by 2^^real.mant_dig + diff *= F.RECIP_EPSILON; + return bitsdiff + X.mant_dig - ((pd[F.EXPPOS_SHORT] & F.EXPMASK) >> F.EXPSHIFT); + } + + if (bitsdiff > 0) + return bitsdiff + 1; // add the 1 we subtracted before + + // Avoid out-by-1 errors when factor is almost 2. + if (bitsdiff == 0 + && ((pa[F.EXPPOS_SHORT] ^ pb[F.EXPPOS_SHORT]) & F.EXPMASK) == 0) + { + return 1; + } else return 0; + } +} + +@safe pure nothrow @nogc unittest +{ + void testFeqrel(F)() + { + // Exact equality + assert(feqrel(F.max, F.max) == F.mant_dig); + assert(feqrel!(F)(0.0, 0.0) == F.mant_dig); + assert(feqrel(F.infinity, F.infinity) == F.mant_dig); + + // a few bits away from exact equality + F w=1; + for (int i = 1; i < F.mant_dig - 1; ++i) + { + assert(feqrel!(F)(1.0 + w * F.epsilon, 1.0) == F.mant_dig-i); + assert(feqrel!(F)(1.0 - w * F.epsilon, 1.0) == F.mant_dig-i); + assert(feqrel!(F)(1.0, 1 + (w-1) * F.epsilon) == F.mant_dig - i + 1); + w*=2; + } + + assert(feqrel!(F)(1.5+F.epsilon, 1.5) == F.mant_dig-1); + assert(feqrel!(F)(1.5-F.epsilon, 1.5) == F.mant_dig-1); + assert(feqrel!(F)(1.5-F.epsilon, 1.5+F.epsilon) == F.mant_dig-2); + + + // Numbers that are close + assert(feqrel!(F)(0x1.Bp+84, 0x1.B8p+84) == 5); + assert(feqrel!(F)(0x1.8p+10, 0x1.Cp+10) == 2); + assert(feqrel!(F)(1.5 * (1 - F.epsilon), 1.0L) == 2); + assert(feqrel!(F)(1.5, 1.0) == 1); + assert(feqrel!(F)(2 * (1 - F.epsilon), 1.0L) == 1); + + // Factors of 2 + assert(feqrel(F.max, F.infinity) == 0); + assert(feqrel!(F)(2 * (1 - F.epsilon), 1.0L) == 1); + assert(feqrel!(F)(1.0, 2.0) == 0); + assert(feqrel!(F)(4.0, 1.0) == 0); + + // Extreme inequality + assert(feqrel(F.nan, F.nan) == 0); + assert(feqrel!(F)(0.0L, -F.nan) == 0); + assert(feqrel(F.nan, F.infinity) == 0); + assert(feqrel(F.infinity, -F.infinity) == 0); + assert(feqrel(F.max, -F.max) == 0); + + assert(feqrel(F.min_normal / 8, F.min_normal / 17) == 3); + + const F Const = 2; + immutable F Immutable = 2; + auto Compiles = feqrel(Const, Immutable); + } + + assert(feqrel(7.1824L, 7.1824L) == real.mant_dig); + + testFeqrel!(real)(); + testFeqrel!(double)(); + testFeqrel!(float)(); +} + +package: // Not public yet +/* Return the value that lies halfway between x and y on the IEEE number line. + * + * Formally, the result is the arithmetic mean of the binary significands of x + * and y, multiplied by the geometric mean of the binary exponents of x and y. + * x and y must have the same sign, and must not be NaN. + * Note: this function is useful for ensuring O(log n) behaviour in algorithms + * involving a 'binary chop'. + * + * Special cases: + * If x and y are within a factor of 2, (ie, feqrel(x, y) > 0), the return value + * is the arithmetic mean (x + y) / 2. + * If x and y are even powers of 2, the return value is the geometric mean, + * ieeeMean(x, y) = sqrt(x * y). + * + */ +T ieeeMean(T)(const T x, const T y) @trusted pure nothrow @nogc +in +{ + // both x and y must have the same sign, and must not be NaN. + assert(signbit(x) == signbit(y)); + assert(x == x && y == y); +} +body +{ + // Runtime behaviour for contract violation: + // If signs are opposite, or one is a NaN, return 0. + if (!((x >= 0 && y >= 0) || (x <= 0 && y <= 0))) return 0.0; + + // The implementation is simple: cast x and y to integers, + // average them (avoiding overflow), and cast the result back to a floating-point number. + + alias F = floatTraits!(T); + T u; + static if (F.realFormat == RealFormat.ieeeExtended) + { + // There's slight additional complexity because they are actually + // 79-bit reals... + ushort *ue = cast(ushort *)&u; + ulong *ul = cast(ulong *)&u; + ushort *xe = cast(ushort *)&x; + ulong *xl = cast(ulong *)&x; + ushort *ye = cast(ushort *)&y; + ulong *yl = cast(ulong *)&y; + + // Ignore the useless implicit bit. (Bonus: this prevents overflows) + ulong m = ((*xl) & 0x7FFF_FFFF_FFFF_FFFFL) + ((*yl) & 0x7FFF_FFFF_FFFF_FFFFL); + + // @@@ BUG? @@@ + // Cast shouldn't be here + ushort e = cast(ushort) ((xe[F.EXPPOS_SHORT] & F.EXPMASK) + + (ye[F.EXPPOS_SHORT] & F.EXPMASK)); + if (m & 0x8000_0000_0000_0000L) + { + ++e; + m &= 0x7FFF_FFFF_FFFF_FFFFL; + } + // Now do a multi-byte right shift + const uint c = e & 1; // carry + e >>= 1; + m >>>= 1; + if (c) + m |= 0x4000_0000_0000_0000L; // shift carry into significand + if (e) + *ul = m | 0x8000_0000_0000_0000L; // set implicit bit... + else + *ul = m; // ... unless exponent is 0 (subnormal or zero). + + ue[4]= e | (xe[F.EXPPOS_SHORT]& 0x8000); // restore sign bit + } + else static if (F.realFormat == RealFormat.ieeeQuadruple) + { + // This would be trivial if 'ucent' were implemented... + ulong *ul = cast(ulong *)&u; + ulong *xl = cast(ulong *)&x; + ulong *yl = cast(ulong *)&y; + + // Multi-byte add, then multi-byte right shift. + import core.checkedint : addu; + bool carry; + ulong ml = addu(xl[MANTISSA_LSB], yl[MANTISSA_LSB], carry); + + ulong mh = carry + (xl[MANTISSA_MSB] & 0x7FFF_FFFF_FFFF_FFFFL) + + (yl[MANTISSA_MSB] & 0x7FFF_FFFF_FFFF_FFFFL); + + ul[MANTISSA_MSB] = (mh >>> 1) | (xl[MANTISSA_MSB] & 0x8000_0000_0000_0000); + ul[MANTISSA_LSB] = (ml >>> 1) | (mh & 1) << 63; + } + else static if (F.realFormat == RealFormat.ieeeDouble) + { + ulong *ul = cast(ulong *)&u; + ulong *xl = cast(ulong *)&x; + ulong *yl = cast(ulong *)&y; + ulong m = (((*xl) & 0x7FFF_FFFF_FFFF_FFFFL) + + ((*yl) & 0x7FFF_FFFF_FFFF_FFFFL)) >>> 1; + m |= ((*xl) & 0x8000_0000_0000_0000L); + *ul = m; + } + else static if (F.realFormat == RealFormat.ieeeSingle) + { + uint *ul = cast(uint *)&u; + uint *xl = cast(uint *)&x; + uint *yl = cast(uint *)&y; + uint m = (((*xl) & 0x7FFF_FFFF) + ((*yl) & 0x7FFF_FFFF)) >>> 1; + m |= ((*xl) & 0x8000_0000); + *ul = m; + } + else + { + assert(0, "Not implemented"); + } + return u; +} + +@safe pure nothrow @nogc unittest +{ + assert(ieeeMean(-0.0,-1e-20)<0); + assert(ieeeMean(0.0,1e-20)>0); + + assert(ieeeMean(1.0L,4.0L)==2L); + assert(ieeeMean(2.0*1.013,8.0*1.013)==4*1.013); + assert(ieeeMean(-1.0L,-4.0L)==-2L); + assert(ieeeMean(-1.0,-4.0)==-2); + assert(ieeeMean(-1.0f,-4.0f)==-2f); + assert(ieeeMean(-1.0,-2.0)==-1.5); + assert(ieeeMean(-1*(1+8*real.epsilon),-2*(1+8*real.epsilon)) + ==-1.5*(1+5*real.epsilon)); + assert(ieeeMean(0x1p60,0x1p-10)==0x1p25); + + static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended) + { + assert(ieeeMean(1.0L,real.infinity)==0x1p8192L); + assert(ieeeMean(0.0L,real.infinity)==1.5); + } + assert(ieeeMean(0.5*real.min_normal*(1-4*real.epsilon),0.5*real.min_normal) + == 0.5*real.min_normal*(1-2*real.epsilon)); +} + +public: + + +/*********************************** + * Evaluate polynomial A(x) = $(SUB a, 0) + $(SUB a, 1)x + $(SUB a, 2)$(POWER x,2) + * + $(SUB a,3)$(POWER x,3); ... + * + * Uses Horner's rule A(x) = $(SUB a, 0) + x($(SUB a, 1) + x($(SUB a, 2) + * + x($(SUB a, 3) + ...))) + * Params: + * x = the value to evaluate. + * A = array of coefficients $(SUB a, 0), $(SUB a, 1), etc. + */ +Unqual!(CommonType!(T1, T2)) poly(T1, T2)(T1 x, in T2[] A) @trusted pure nothrow @nogc +if (isFloatingPoint!T1 && isFloatingPoint!T2) +in +{ + assert(A.length > 0); +} +body +{ + static if (is(Unqual!T2 == real)) + { + return polyImpl(x, A); + } + else + { + return polyImplBase(x, A); + } +} + +/// +@safe nothrow @nogc unittest +{ + real x = 3.1; + static real[] pp = [56.1, 32.7, 6]; + + assert(poly(x, pp) == (56.1L + (32.7L + 6.0L * x) * x)); +} + +@safe nothrow @nogc unittest +{ + double x = 3.1; + static double[] pp = [56.1, 32.7, 6]; + double y = x; + y *= 6.0; + y += 32.7; + y *= x; + y += 56.1; + assert(poly(x, pp) == y); +} + +@safe unittest +{ + static assert(poly(3.0, [1.0, 2.0, 3.0]) == 34); +} + +private Unqual!(CommonType!(T1, T2)) polyImplBase(T1, T2)(T1 x, in T2[] A) @trusted pure nothrow @nogc +if (isFloatingPoint!T1 && isFloatingPoint!T2) +{ + ptrdiff_t i = A.length - 1; + typeof(return) r = A[i]; + while (--i >= 0) + { + r *= x; + r += A[i]; + } + return r; +} + +private real polyImpl(real x, in real[] A) @trusted pure nothrow @nogc +{ + version (D_InlineAsm_X86) + { + if (__ctfe) + { + return polyImplBase(x, A); + } + version (Windows) + { + // BUG: This code assumes a frame pointer in EBP. + asm pure nothrow @nogc // assembler by W. Bright + { + // EDX = (A.length - 1) * real.sizeof + mov ECX,A[EBP] ; // ECX = A.length + dec ECX ; + lea EDX,[ECX][ECX*8] ; + add EDX,ECX ; + add EDX,A+4[EBP] ; + fld real ptr [EDX] ; // ST0 = coeff[ECX] + jecxz return_ST ; + fld x[EBP] ; // ST0 = x + fxch ST(1) ; // ST1 = x, ST0 = r + align 4 ; + L2: fmul ST,ST(1) ; // r *= x + fld real ptr -10[EDX] ; + sub EDX,10 ; // deg-- + faddp ST(1),ST ; + dec ECX ; + jne L2 ; + fxch ST(1) ; // ST1 = r, ST0 = x + fstp ST(0) ; // dump x + align 4 ; + return_ST: ; + ; + } + } + else version (linux) + { + asm pure nothrow @nogc // assembler by W. Bright + { + // EDX = (A.length - 1) * real.sizeof + mov ECX,A[EBP] ; // ECX = A.length + dec ECX ; + lea EDX,[ECX*8] ; + lea EDX,[EDX][ECX*4] ; + add EDX,A+4[EBP] ; + fld real ptr [EDX] ; // ST0 = coeff[ECX] + jecxz return_ST ; + fld x[EBP] ; // ST0 = x + fxch ST(1) ; // ST1 = x, ST0 = r + align 4 ; + L2: fmul ST,ST(1) ; // r *= x + fld real ptr -12[EDX] ; + sub EDX,12 ; // deg-- + faddp ST(1),ST ; + dec ECX ; + jne L2 ; + fxch ST(1) ; // ST1 = r, ST0 = x + fstp ST(0) ; // dump x + align 4 ; + return_ST: ; + ; + } + } + else version (OSX) + { + asm pure nothrow @nogc // assembler by W. Bright + { + // EDX = (A.length - 1) * real.sizeof + mov ECX,A[EBP] ; // ECX = A.length + dec ECX ; + lea EDX,[ECX*8] ; + add EDX,EDX ; + add EDX,A+4[EBP] ; + fld real ptr [EDX] ; // ST0 = coeff[ECX] + jecxz return_ST ; + fld x[EBP] ; // ST0 = x + fxch ST(1) ; // ST1 = x, ST0 = r + align 4 ; + L2: fmul ST,ST(1) ; // r *= x + fld real ptr -16[EDX] ; + sub EDX,16 ; // deg-- + faddp ST(1),ST ; + dec ECX ; + jne L2 ; + fxch ST(1) ; // ST1 = r, ST0 = x + fstp ST(0) ; // dump x + align 4 ; + return_ST: ; + ; + } + } + else version (FreeBSD) + { + asm pure nothrow @nogc // assembler by W. Bright + { + // EDX = (A.length - 1) * real.sizeof + mov ECX,A[EBP] ; // ECX = A.length + dec ECX ; + lea EDX,[ECX*8] ; + lea EDX,[EDX][ECX*4] ; + add EDX,A+4[EBP] ; + fld real ptr [EDX] ; // ST0 = coeff[ECX] + jecxz return_ST ; + fld x[EBP] ; // ST0 = x + fxch ST(1) ; // ST1 = x, ST0 = r + align 4 ; + L2: fmul ST,ST(1) ; // r *= x + fld real ptr -12[EDX] ; + sub EDX,12 ; // deg-- + faddp ST(1),ST ; + dec ECX ; + jne L2 ; + fxch ST(1) ; // ST1 = r, ST0 = x + fstp ST(0) ; // dump x + align 4 ; + return_ST: ; + ; + } + } + else version (Solaris) + { + asm pure nothrow @nogc // assembler by W. Bright + { + // EDX = (A.length - 1) * real.sizeof + mov ECX,A[EBP] ; // ECX = A.length + dec ECX ; + lea EDX,[ECX*8] ; + lea EDX,[EDX][ECX*4] ; + add EDX,A+4[EBP] ; + fld real ptr [EDX] ; // ST0 = coeff[ECX] + jecxz return_ST ; + fld x[EBP] ; // ST0 = x + fxch ST(1) ; // ST1 = x, ST0 = r + align 4 ; + L2: fmul ST,ST(1) ; // r *= x + fld real ptr -12[EDX] ; + sub EDX,12 ; // deg-- + faddp ST(1),ST ; + dec ECX ; + jne L2 ; + fxch ST(1) ; // ST1 = r, ST0 = x + fstp ST(0) ; // dump x + align 4 ; + return_ST: ; + ; + } + } + else + { + static assert(0); + } + } + else + { + return polyImplBase(x, A); + } +} + + +/** + Computes whether two values are approximately equal, admitting a maximum + relative difference, and a maximum absolute difference. + + Params: + lhs = First item to compare. + rhs = Second item to compare. + maxRelDiff = Maximum allowable difference relative to `rhs`. + maxAbsDiff = Maximum absolute difference. + + Returns: + `true` if the two items are approximately equal under either criterium. + If one item is a range, and the other is a single value, then the result + is the logical and-ing of calling `approxEqual` on each element of the + ranged item against the single item. If both items are ranges, then + `approxEqual` returns `true` if and only if the ranges have the same + number of elements and if `approxEqual` evaluates to `true` for each + pair of elements. + */ +bool approxEqual(T, U, V)(T lhs, U rhs, V maxRelDiff, V maxAbsDiff = 1e-5) +{ + import std.range.primitives : empty, front, isInputRange, popFront; + static if (isInputRange!T) + { + static if (isInputRange!U) + { + // Two ranges + for (;; lhs.popFront(), rhs.popFront()) + { + if (lhs.empty) return rhs.empty; + if (rhs.empty) return lhs.empty; + if (!approxEqual(lhs.front, rhs.front, maxRelDiff, maxAbsDiff)) + return false; + } + } + else static if (isIntegral!U) + { + // convert rhs to real + return approxEqual(lhs, real(rhs), maxRelDiff, maxAbsDiff); + } + else + { + // lhs is range, rhs is number + for (; !lhs.empty; lhs.popFront()) + { + if (!approxEqual(lhs.front, rhs, maxRelDiff, maxAbsDiff)) + return false; + } + return true; + } + } + else + { + static if (isInputRange!U) + { + // lhs is number, rhs is range + for (; !rhs.empty; rhs.popFront()) + { + if (!approxEqual(lhs, rhs.front, maxRelDiff, maxAbsDiff)) + return false; + } + return true; + } + else static if (isIntegral!T || isIntegral!U) + { + // convert both lhs and rhs to real + return approxEqual(real(lhs), real(rhs), maxRelDiff, maxAbsDiff); + } + else + { + // two numbers + //static assert(is(T : real) && is(U : real)); + if (rhs == 0) + { + return fabs(lhs) <= maxAbsDiff; + } + static if (is(typeof(lhs.infinity)) && is(typeof(rhs.infinity))) + { + if (lhs == lhs.infinity && rhs == rhs.infinity || + lhs == -lhs.infinity && rhs == -rhs.infinity) return true; + } + return fabs((lhs - rhs) / rhs) <= maxRelDiff + || maxAbsDiff != 0 && fabs(lhs - rhs) <= maxAbsDiff; + } + } +} + +/** + Returns $(D approxEqual(lhs, rhs, 1e-2, 1e-5)). + */ +bool approxEqual(T, U)(T lhs, U rhs) +{ + return approxEqual(lhs, rhs, 1e-2, 1e-5); +} + +/// +@safe pure nothrow unittest +{ + assert(approxEqual(1.0, 1.0099)); + assert(!approxEqual(1.0, 1.011)); + float[] arr1 = [ 1.0, 2.0, 3.0 ]; + double[] arr2 = [ 1.001, 1.999, 3 ]; + assert(approxEqual(arr1, arr2)); + + real num = real.infinity; + assert(num == real.infinity); // Passes. + assert(approxEqual(num, real.infinity)); // Fails. + num = -real.infinity; + assert(num == -real.infinity); // Passes. + assert(approxEqual(num, -real.infinity)); // Fails. + + assert(!approxEqual(3, 0)); + assert(approxEqual(3, 3)); + assert(approxEqual(3.0, 3)); + assert(approxEqual([3, 3, 3], 3.0)); + assert(approxEqual([3.0, 3.0, 3.0], 3)); + int a = 10; + assert(approxEqual(10, a)); +} + +@safe pure nothrow @nogc unittest +{ + real num = real.infinity; + assert(num == real.infinity); // Passes. + assert(approxEqual(num, real.infinity)); // Fails. +} + + +@safe pure nothrow @nogc unittest +{ + float f = sqrt(2.0f); + assert(fabs(f * f - 2.0f) < .00001); + + double d = sqrt(2.0); + assert(fabs(d * d - 2.0) < .00001); + + real r = sqrt(2.0L); + assert(fabs(r * r - 2.0) < .00001); +} + +@safe pure nothrow @nogc unittest +{ + float f = fabs(-2.0f); + assert(f == 2); + + double d = fabs(-2.0); + assert(d == 2); + + real r = fabs(-2.0L); + assert(r == 2); +} + +@safe pure nothrow @nogc unittest +{ + float f = sin(-2.0f); + assert(fabs(f - -0.909297f) < .00001); + + double d = sin(-2.0); + assert(fabs(d - -0.909297f) < .00001); + + real r = sin(-2.0L); + assert(fabs(r - -0.909297f) < .00001); +} + +@safe pure nothrow @nogc unittest +{ + float f = cos(-2.0f); + assert(fabs(f - -0.416147f) < .00001); + + double d = cos(-2.0); + assert(fabs(d - -0.416147f) < .00001); + + real r = cos(-2.0L); + assert(fabs(r - -0.416147f) < .00001); +} + +@safe pure nothrow @nogc unittest +{ + float f = tan(-2.0f); + assert(fabs(f - 2.18504f) < .00001); + + double d = tan(-2.0); + assert(fabs(d - 2.18504f) < .00001); + + real r = tan(-2.0L); + assert(fabs(r - 2.18504f) < .00001); + + // Verify correct behavior for large inputs + assert(!isNaN(tan(0x1p63))); + assert(!isNaN(tan(0x1p300L))); + assert(!isNaN(tan(-0x1p63))); + assert(!isNaN(tan(-0x1p300L))); +} + +@safe pure nothrow unittest +{ + // issue 6381: floor/ceil should be usable in pure function. + auto x = floor(1.2); + auto y = ceil(1.2); +} + +@safe pure nothrow unittest +{ + // relative comparison depends on rhs, make sure proper side is used when + // comparing range to single value. Based on bugzilla issue 15763 + auto a = [2e-3 - 1e-5]; + auto b = 2e-3 + 1e-5; + assert(a[0].approxEqual(b)); + assert(!b.approxEqual(a[0])); + assert(a.approxEqual(b)); + assert(!b.approxEqual(a)); +} + +/*********************************** + * Defines a total order on all floating-point numbers. + * + * The order is defined as follows: + * $(UL + * $(LI All numbers in [-$(INFIN), +$(INFIN)] are ordered + * the same way as by built-in comparison, with the exception of + * -0.0, which is less than +0.0;) + * $(LI If the sign bit is set (that is, it's 'negative'), $(NAN) is less + * than any number; if the sign bit is not set (it is 'positive'), + * $(NAN) is greater than any number;) + * $(LI $(NAN)s of the same sign are ordered by the payload ('negative' + * ones - in reverse order).) + * ) + * + * Returns: + * negative value if $(D x) precedes $(D y) in the order specified above; + * 0 if $(D x) and $(D y) are identical, and positive value otherwise. + * + * See_Also: + * $(MYREF isIdentical) + * Standards: Conforms to IEEE 754-2008 + */ +int cmp(T)(const(T) x, const(T) y) @nogc @trusted pure nothrow +if (isFloatingPoint!T) +{ + alias F = floatTraits!T; + + static if (F.realFormat == RealFormat.ieeeSingle + || F.realFormat == RealFormat.ieeeDouble) + { + static if (T.sizeof == 4) + alias UInt = uint; + else + alias UInt = ulong; + + union Repainter + { + T number; + UInt bits; + } + + enum msb = ~(UInt.max >>> 1); + + import std.typecons : Tuple; + Tuple!(Repainter, Repainter) vars = void; + vars[0].number = x; + vars[1].number = y; + + foreach (ref var; vars) + if (var.bits & msb) + var.bits = ~var.bits; + else + var.bits |= msb; + + if (vars[0].bits < vars[1].bits) + return -1; + else if (vars[0].bits > vars[1].bits) + return 1; + else + return 0; + } + else static if (F.realFormat == RealFormat.ieeeExtended53 + || F.realFormat == RealFormat.ieeeExtended + || F.realFormat == RealFormat.ieeeQuadruple) + { + static if (F.realFormat == RealFormat.ieeeQuadruple) + alias RemT = ulong; + else + alias RemT = ushort; + + struct Bits + { + ulong bulk; + RemT rem; + } + + union Repainter + { + T number; + Bits bits; + ubyte[T.sizeof] bytes; + } + + import std.typecons : Tuple; + Tuple!(Repainter, Repainter) vars = void; + vars[0].number = x; + vars[1].number = y; + + foreach (ref var; vars) + if (var.bytes[F.SIGNPOS_BYTE] & 0x80) + { + var.bits.bulk = ~var.bits.bulk; + var.bits.rem = cast(typeof(var.bits.rem))(-1 - var.bits.rem); // ~var.bits.rem + } + else + { + var.bytes[F.SIGNPOS_BYTE] |= 0x80; + } + + version (LittleEndian) + { + if (vars[0].bits.rem < vars[1].bits.rem) + return -1; + else if (vars[0].bits.rem > vars[1].bits.rem) + return 1; + else if (vars[0].bits.bulk < vars[1].bits.bulk) + return -1; + else if (vars[0].bits.bulk > vars[1].bits.bulk) + return 1; + else + return 0; + } + else + { + if (vars[0].bits.bulk < vars[1].bits.bulk) + return -1; + else if (vars[0].bits.bulk > vars[1].bits.bulk) + return 1; + else if (vars[0].bits.rem < vars[1].bits.rem) + return -1; + else if (vars[0].bits.rem > vars[1].bits.rem) + return 1; + else + return 0; + } + } + else + { + // IBM Extended doubledouble does not follow the general + // sign-exponent-significand layout, so has to be handled generically + + const int xSign = signbit(x), + ySign = signbit(y); + + if (xSign == 1 && ySign == 1) + return cmp(-y, -x); + else if (xSign == 1) + return -1; + else if (ySign == 1) + return 1; + else if (x < y) + return -1; + else if (x == y) + return 0; + else if (x > y) + return 1; + else if (isNaN(x) && !isNaN(y)) + return 1; + else if (isNaN(y) && !isNaN(x)) + return -1; + else if (getNaNPayload(x) < getNaNPayload(y)) + return -1; + else if (getNaNPayload(x) > getNaNPayload(y)) + return 1; + else + return 0; + } +} + +/// Most numbers are ordered naturally. +@safe unittest +{ + assert(cmp(-double.infinity, -double.max) < 0); + assert(cmp(-double.max, -100.0) < 0); + assert(cmp(-100.0, -0.5) < 0); + assert(cmp(-0.5, 0.0) < 0); + assert(cmp(0.0, 0.5) < 0); + assert(cmp(0.5, 100.0) < 0); + assert(cmp(100.0, double.max) < 0); + assert(cmp(double.max, double.infinity) < 0); + + assert(cmp(1.0, 1.0) == 0); +} + +/// Positive and negative zeroes are distinct. +@safe unittest +{ + assert(cmp(-0.0, +0.0) < 0); + assert(cmp(+0.0, -0.0) > 0); +} + +/// Depending on the sign, $(NAN)s go to either end of the spectrum. +@safe unittest +{ + assert(cmp(-double.nan, -double.infinity) < 0); + assert(cmp(double.infinity, double.nan) < 0); + assert(cmp(-double.nan, double.nan) < 0); +} + +/// $(NAN)s of the same sign are ordered by the payload. +@safe unittest +{ + assert(cmp(NaN(10), NaN(20)) < 0); + assert(cmp(-NaN(20), -NaN(10)) < 0); +} + +@safe unittest +{ + import std.meta : AliasSeq; + foreach (T; AliasSeq!(float, double, real)) + { + T[] values = [-cast(T) NaN(20), -cast(T) NaN(10), -T.nan, -T.infinity, + -T.max, -T.max / 2, T(-16.0), T(-1.0).nextDown, + T(-1.0), T(-1.0).nextUp, + T(-0.5), -T.min_normal, (-T.min_normal).nextUp, + -2 * T.min_normal * T.epsilon, + -T.min_normal * T.epsilon, + T(-0.0), T(0.0), + T.min_normal * T.epsilon, + 2 * T.min_normal * T.epsilon, + T.min_normal.nextDown, T.min_normal, T(0.5), + T(1.0).nextDown, T(1.0), + T(1.0).nextUp, T(16.0), T.max / 2, T.max, + T.infinity, T.nan, cast(T) NaN(10), cast(T) NaN(20)]; + + foreach (i, x; values) + { + foreach (y; values[i + 1 .. $]) + { + assert(cmp(x, y) < 0); + assert(cmp(y, x) > 0); + } + assert(cmp(x, x) == 0); + } + } +} + +private enum PowType +{ + floor, + ceil +} + +pragma(inline, true) +private T powIntegralImpl(PowType type, T)(T val) +{ + import core.bitop : bsr; + + if (val == 0 || (type == PowType.ceil && (val > T.max / 2 || val == T.min))) + return 0; + else + { + static if (isSigned!T) + return cast(Unqual!T) (val < 0 ? -(T(1) << bsr(0 - val) + type) : T(1) << bsr(val) + type); + else + return cast(Unqual!T) (T(1) << bsr(val) + type); + } +} + +private T powFloatingPointImpl(PowType type, T)(T x) +{ + if (!x.isFinite) + return x; + + if (!x) + return x; + + int exp; + auto y = frexp(x, exp); + + static if (type == PowType.ceil) + y = ldexp(cast(T) 0.5, exp + 1); + else + y = ldexp(cast(T) 0.5, exp); + + if (!y.isFinite) + return cast(T) 0.0; + + y = copysign(y, x); + + return y; +} + +/** + * Gives the next power of two after $(D val). `T` can be any built-in + * numerical type. + * + * If the operation would lead to an over/underflow, this function will + * return `0`. + * + * Params: + * val = any number + * + * Returns: + * the next power of two after $(D val) + */ +T nextPow2(T)(const T val) +if (isIntegral!T) +{ + return powIntegralImpl!(PowType.ceil)(val); +} + +/// ditto +T nextPow2(T)(const T val) +if (isFloatingPoint!T) +{ + return powFloatingPointImpl!(PowType.ceil)(val); +} + +/// +@safe @nogc pure nothrow unittest +{ + assert(nextPow2(2) == 4); + assert(nextPow2(10) == 16); + assert(nextPow2(4000) == 4096); + + assert(nextPow2(-2) == -4); + assert(nextPow2(-10) == -16); + + assert(nextPow2(uint.max) == 0); + assert(nextPow2(uint.min) == 0); + assert(nextPow2(size_t.max) == 0); + assert(nextPow2(size_t.min) == 0); + + assert(nextPow2(int.max) == 0); + assert(nextPow2(int.min) == 0); + assert(nextPow2(long.max) == 0); + assert(nextPow2(long.min) == 0); +} + +/// +@safe @nogc pure nothrow unittest +{ + assert(nextPow2(2.1) == 4.0); + assert(nextPow2(-2.0) == -4.0); + assert(nextPow2(0.25) == 0.5); + assert(nextPow2(-4.0) == -8.0); + + assert(nextPow2(double.max) == 0.0); + assert(nextPow2(double.infinity) == double.infinity); +} + +@safe @nogc pure nothrow unittest +{ + assert(nextPow2(ubyte(2)) == 4); + assert(nextPow2(ubyte(10)) == 16); + + assert(nextPow2(byte(2)) == 4); + assert(nextPow2(byte(10)) == 16); + + assert(nextPow2(short(2)) == 4); + assert(nextPow2(short(10)) == 16); + assert(nextPow2(short(4000)) == 4096); + + assert(nextPow2(ushort(2)) == 4); + assert(nextPow2(ushort(10)) == 16); + assert(nextPow2(ushort(4000)) == 4096); +} + +@safe @nogc pure nothrow unittest +{ + foreach (ulong i; 1 .. 62) + { + assert(nextPow2(1UL << i) == 2UL << i); + assert(nextPow2((1UL << i) - 1) == 1UL << i); + assert(nextPow2((1UL << i) + 1) == 2UL << i); + assert(nextPow2((1UL << i) + (1UL<<(i-1))) == 2UL << i); + } +} + +@safe @nogc pure nothrow unittest +{ + import std.meta : AliasSeq; + + foreach (T; AliasSeq!(float, double, real)) + { + enum T subNormal = T.min_normal / 2; + + static if (subNormal) assert(nextPow2(subNormal) == T.min_normal); + + assert(nextPow2(T(0.0)) == 0.0); + + assert(nextPow2(T(2.0)) == 4.0); + assert(nextPow2(T(2.1)) == 4.0); + assert(nextPow2(T(3.1)) == 4.0); + assert(nextPow2(T(4.0)) == 8.0); + assert(nextPow2(T(0.25)) == 0.5); + + assert(nextPow2(T(-2.0)) == -4.0); + assert(nextPow2(T(-2.1)) == -4.0); + assert(nextPow2(T(-3.1)) == -4.0); + assert(nextPow2(T(-4.0)) == -8.0); + assert(nextPow2(T(-0.25)) == -0.5); + + assert(nextPow2(T.max) == 0); + assert(nextPow2(-T.max) == 0); + + assert(nextPow2(T.infinity) == T.infinity); + assert(nextPow2(T.init).isNaN); + } +} + +@safe @nogc pure nothrow unittest // Issue 15973 +{ + assert(nextPow2(uint.max / 2) == uint.max / 2 + 1); + assert(nextPow2(uint.max / 2 + 2) == 0); + assert(nextPow2(int.max / 2) == int.max / 2 + 1); + assert(nextPow2(int.max / 2 + 2) == 0); + assert(nextPow2(int.min + 1) == int.min); +} + +/** + * Gives the last power of two before $(D val). $(T) can be any built-in + * numerical type. + * + * Params: + * val = any number + * + * Returns: + * the last power of two before $(D val) + */ +T truncPow2(T)(const T val) +if (isIntegral!T) +{ + return powIntegralImpl!(PowType.floor)(val); +} + +/// ditto +T truncPow2(T)(const T val) +if (isFloatingPoint!T) +{ + return powFloatingPointImpl!(PowType.floor)(val); +} + +/// +@safe @nogc pure nothrow unittest +{ + assert(truncPow2(3) == 2); + assert(truncPow2(4) == 4); + assert(truncPow2(10) == 8); + assert(truncPow2(4000) == 2048); + + assert(truncPow2(-5) == -4); + assert(truncPow2(-20) == -16); + + assert(truncPow2(uint.max) == int.max + 1); + assert(truncPow2(uint.min) == 0); + assert(truncPow2(ulong.max) == long.max + 1); + assert(truncPow2(ulong.min) == 0); + + assert(truncPow2(int.max) == (int.max / 2) + 1); + assert(truncPow2(int.min) == int.min); + assert(truncPow2(long.max) == (long.max / 2) + 1); + assert(truncPow2(long.min) == long.min); +} + +/// +@safe @nogc pure nothrow unittest +{ + assert(truncPow2(2.1) == 2.0); + assert(truncPow2(7.0) == 4.0); + assert(truncPow2(-1.9) == -1.0); + assert(truncPow2(0.24) == 0.125); + assert(truncPow2(-7.0) == -4.0); + + assert(truncPow2(double.infinity) == double.infinity); +} + +@safe @nogc pure nothrow unittest +{ + assert(truncPow2(ubyte(3)) == 2); + assert(truncPow2(ubyte(4)) == 4); + assert(truncPow2(ubyte(10)) == 8); + + assert(truncPow2(byte(3)) == 2); + assert(truncPow2(byte(4)) == 4); + assert(truncPow2(byte(10)) == 8); + + assert(truncPow2(ushort(3)) == 2); + assert(truncPow2(ushort(4)) == 4); + assert(truncPow2(ushort(10)) == 8); + assert(truncPow2(ushort(4000)) == 2048); + + assert(truncPow2(short(3)) == 2); + assert(truncPow2(short(4)) == 4); + assert(truncPow2(short(10)) == 8); + assert(truncPow2(short(4000)) == 2048); +} + +@safe @nogc pure nothrow unittest +{ + foreach (ulong i; 1 .. 62) + { + assert(truncPow2(2UL << i) == 2UL << i); + assert(truncPow2((2UL << i) + 1) == 2UL << i); + assert(truncPow2((2UL << i) - 1) == 1UL << i); + assert(truncPow2((2UL << i) - (2UL<<(i-1))) == 1UL << i); + } +} + +@safe @nogc pure nothrow unittest +{ + import std.meta : AliasSeq; + + foreach (T; AliasSeq!(float, double, real)) + { + assert(truncPow2(T(0.0)) == 0.0); + + assert(truncPow2(T(4.0)) == 4.0); + assert(truncPow2(T(2.1)) == 2.0); + assert(truncPow2(T(3.5)) == 2.0); + assert(truncPow2(T(7.0)) == 4.0); + assert(truncPow2(T(0.24)) == 0.125); + + assert(truncPow2(T(-2.0)) == -2.0); + assert(truncPow2(T(-2.1)) == -2.0); + assert(truncPow2(T(-3.1)) == -2.0); + assert(truncPow2(T(-7.0)) == -4.0); + assert(truncPow2(T(-0.24)) == -0.125); + + assert(truncPow2(T.infinity) == T.infinity); + assert(truncPow2(T.init).isNaN); + } +} + +/** +Check whether a number is an integer power of two. + +Note that only positive numbers can be integer powers of two. This +function always return `false` if `x` is negative or zero. + +Params: + x = the number to test + +Returns: + `true` if `x` is an integer power of two. +*/ +bool isPowerOf2(X)(const X x) pure @safe nothrow @nogc +if (isNumeric!X) +{ + static if (isFloatingPoint!X) + { + int exp; + const X sig = frexp(x, exp); + + return (exp != int.min) && (sig is cast(X) 0.5L); + } + else + { + static if (isSigned!X) + { + auto y = cast(typeof(x + 0))x; + return y > 0 && !(y & (y - 1)); + } + else + { + auto y = cast(typeof(x + 0u))x; + return (y & -y) > (y - 1); + } + } +} +/// +@safe unittest +{ + assert( isPowerOf2(1.0L)); + assert( isPowerOf2(2.0L)); + assert( isPowerOf2(0.5L)); + assert( isPowerOf2(pow(2.0L, 96))); + assert( isPowerOf2(pow(2.0L, -77))); + + assert(!isPowerOf2(-2.0L)); + assert(!isPowerOf2(-0.5L)); + assert(!isPowerOf2(0.0L)); + assert(!isPowerOf2(4.315)); + assert(!isPowerOf2(1.0L / 3.0L)); + + assert(!isPowerOf2(real.nan)); + assert(!isPowerOf2(real.infinity)); +} +/// +@safe unittest +{ + assert( isPowerOf2(1)); + assert( isPowerOf2(2)); + assert( isPowerOf2(1uL << 63)); + + assert(!isPowerOf2(-4)); + assert(!isPowerOf2(0)); + assert(!isPowerOf2(1337u)); +} + +@safe unittest +{ + import std.meta : AliasSeq; + + immutable smallP2 = pow(2.0L, -62); + immutable bigP2 = pow(2.0L, 50); + immutable smallP7 = pow(7.0L, -35); + immutable bigP7 = pow(7.0L, 30); + + foreach (X; AliasSeq!(float, double, real)) + { + immutable min_sub = X.min_normal * X.epsilon; + + foreach (x; AliasSeq!(smallP2, min_sub, X.min_normal, .25L, 0.5L, 1.0L, + 2.0L, 8.0L, pow(2.0L, X.max_exp - 1), bigP2)) + { + assert( isPowerOf2(cast(X) x)); + assert(!isPowerOf2(cast(X)-x)); + } + + foreach (x; AliasSeq!(0.0L, 3 * min_sub, smallP7, 0.1L, 1337.0L, bigP7, X.max, real.nan, real.infinity)) + { + assert(!isPowerOf2(cast(X) x)); + assert(!isPowerOf2(cast(X)-x)); + } + } + + foreach (X; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong)) + { + foreach (x; [1, 2, 4, 8, (X.max >>> 1) + 1]) + { + assert( isPowerOf2(cast(X) x)); + static if (isSigned!X) + assert(!isPowerOf2(cast(X)-x)); + } + + foreach (x; [0, 3, 5, 13, 77, X.min, X.max]) + assert(!isPowerOf2(cast(X) x)); + } +} diff --git a/libphobos/src/std/mathspecial.d b/libphobos/src/std/mathspecial.d new file mode 100644 index 0000000..e35c74c --- /dev/null +++ b/libphobos/src/std/mathspecial.d @@ -0,0 +1,361 @@ +// Written in the D programming language. + +/** + * Mathematical Special Functions + * + * The technical term 'Special Functions' includes several families of + * transcendental functions, which have important applications in particular + * branches of mathematics and physics. + * + * The gamma and related functions, and the error function are crucial for + * mathematical statistics. + * The Bessel and related functions arise in problems involving wave propagation + * (especially in optics). + * Other major categories of special functions include the elliptic integrals + * (related to the arc length of an ellipse), and the hypergeometric functions. + * + * Status: + * Many more functions will be added to this module. + * The naming convention for the distribution functions (gammaIncomplete, etc) + * is not yet finalized and will probably change. + * + * Macros: + * TABLE_SV = + * + * $0
Special Values
+ * SVH = $(TR $(TH $1) $(TH $2)) + * SV = $(TR $(TD $1) $(TD $2)) + * + * NAN = $(RED NAN) + * SUP = $0 + * GAMMA = Γ + * THETA = θ + * INTEGRAL = ∫ + * INTEGRATE = $(BIG ∫$(SMALL $1)$2) + * POWER = $1$2 + * SUB = $1$2 + * BIGSUM = $(BIG Σ $2$(SMALL $1)) + * CHOOSE = $(BIG () $(SMALL $1)$(SMALL $2) $(BIG )) + * PLUSMN = ± + * INFIN = ∞ + * PLUSMNINF = ±∞ + * PI = π + * LT = < + * GT = > + * SQRT = √ + * HALF = ½ + * + * + * Copyright: Based on the CEPHES math library, which is + * Copyright (C) 1994 Stephen L. Moshier (moshier@world.std.com). + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Stephen L. Moshier (original C code). Conversion to D by Don Clugston + * Source: $(PHOBOSSRC std/_mathspecial.d) + */ +module std.mathspecial; +import std.internal.math.errorfunction; +import std.internal.math.gammafunction; +public import std.math; + +/* *********************************************** + * GAMMA AND RELATED FUNCTIONS * + * ***********************************************/ + +pure: +nothrow: +@safe: +@nogc: + +/** The Gamma function, $(GAMMA)(x) + * + * $(GAMMA)(x) is a generalisation of the factorial function + * to real and complex numbers. + * Like x!, $(GAMMA)(x+1) = x * $(GAMMA)(x). + * + * Mathematically, if z.re > 0 then + * $(GAMMA)(z) = $(INTEGRATE 0, $(INFIN)) $(POWER t, z-1)$(POWER e, -t) dt + * + * $(TABLE_SV + * $(SVH x, $(GAMMA)(x) ) + * $(SV $(NAN), $(NAN) ) + * $(SV $(PLUSMN)0.0, $(PLUSMNINF)) + * $(SV integer > 0, (x-1)! ) + * $(SV integer < 0, $(NAN) ) + * $(SV +$(INFIN), +$(INFIN) ) + * $(SV -$(INFIN), $(NAN) ) + * ) + */ +real gamma(real x) +{ + return std.internal.math.gammafunction.gamma(x); +} + +/** Natural logarithm of the gamma function, $(GAMMA)(x) + * + * Returns the base e (2.718...) logarithm of the absolute + * value of the gamma function of the argument. + * + * For reals, logGamma is equivalent to log(fabs(gamma(x))). + * + * $(TABLE_SV + * $(SVH x, logGamma(x) ) + * $(SV $(NAN), $(NAN) ) + * $(SV integer <= 0, +$(INFIN) ) + * $(SV $(PLUSMNINF), +$(INFIN) ) + * ) + */ +real logGamma(real x) +{ + return std.internal.math.gammafunction.logGamma(x); +} + +/** The sign of $(GAMMA)(x). + * + * Returns -1 if $(GAMMA)(x) < 0, +1 if $(GAMMA)(x) > 0, + * $(NAN) if sign is indeterminate. + * + * Note that this function can be used in conjunction with logGamma(x) to + * evaluate gamma for very large values of x. + */ +real sgnGamma(real x) +{ + /* Author: Don Clugston. */ + if (isNaN(x)) return x; + if (x > 0) return 1.0; + if (x < -1/real.epsilon) + { + // Large negatives lose all precision + return real.nan; + } + long n = rndtol(x); + if (x == n) + { + return x == 0 ? copysign(1, x) : real.nan; + } + return n & 1 ? 1.0 : -1.0; +} + +@safe unittest +{ + assert(sgnGamma(5.0) == 1.0); + assert(isNaN(sgnGamma(-3.0))); + assert(sgnGamma(-0.1) == -1.0); + assert(sgnGamma(-55.1) == 1.0); + assert(isNaN(sgnGamma(-real.infinity))); + assert(isIdentical(sgnGamma(NaN(0xABC)), NaN(0xABC))); +} + +/** Beta function + * + * The beta function is defined as + * + * beta(x, y) = ($(GAMMA)(x) * $(GAMMA)(y)) / $(GAMMA)(x + y) + */ +real beta(real x, real y) +{ + if ((x+y)> MAXGAMMA) + { + return exp(logGamma(x) + logGamma(y) - logGamma(x+y)); + } else return gamma(x) * gamma(y) / gamma(x+y); +} + +@safe unittest +{ + assert(isIdentical(beta(NaN(0xABC), 4), NaN(0xABC))); + assert(isIdentical(beta(2, NaN(0xABC)), NaN(0xABC))); +} + +/** Digamma function + * + * The digamma function is the logarithmic derivative of the gamma function. + * + * digamma(x) = d/dx logGamma(x) + * + * See_Also: $(LREF logmdigamma), $(LREF logmdigammaInverse). + */ +real digamma(real x) +{ + return std.internal.math.gammafunction.digamma(x); +} + +/** Log Minus Digamma function + * + * logmdigamma(x) = log(x) - digamma(x) + * + * See_Also: $(LREF digamma), $(LREF logmdigammaInverse). + */ +real logmdigamma(real x) +{ + return std.internal.math.gammafunction.logmdigamma(x); +} + +/** Inverse of the Log Minus Digamma function + * + * Given y, the function finds x such log(x) - digamma(x) = y. + * + * See_Also: $(LREF logmdigamma), $(LREF digamma). + */ +real logmdigammaInverse(real x) +{ + return std.internal.math.gammafunction.logmdigammaInverse(x); +} + +/** Incomplete beta integral + * + * Returns incomplete beta integral of the arguments, evaluated + * from zero to x. The regularized incomplete beta function is defined as + * + * betaIncomplete(a, b, x) = $(GAMMA)(a + b) / ( $(GAMMA)(a) $(GAMMA)(b) ) * + * $(INTEGRATE 0, x) $(POWER t, a-1)$(POWER (1-t), b-1) dt + * + * and is the same as the the cumulative distribution function. + * + * The domain of definition is 0 <= x <= 1. In this + * implementation a and b are restricted to positive values. + * The integral from x to 1 may be obtained by the symmetry + * relation + * + * betaIncompleteCompl(a, b, x ) = betaIncomplete( b, a, 1-x ) + * + * The integral is evaluated by a continued fraction expansion + * or, when b * x is small, by a power series. + */ +real betaIncomplete(real a, real b, real x ) +{ + return std.internal.math.gammafunction.betaIncomplete(a, b, x); +} + +/** Inverse of incomplete beta integral + * + * Given y, the function finds x such that + * + * betaIncomplete(a, b, x) == y + * + * Newton iterations or interval halving is used. + */ +real betaIncompleteInverse(real a, real b, real y ) +{ + return std.internal.math.gammafunction.betaIncompleteInv(a, b, y); +} + +/** Incomplete gamma integral and its complement + * + * These functions are defined by + * + * gammaIncomplete = ( $(INTEGRATE 0, x) $(POWER e, -t) $(POWER t, a-1) dt )/ $(GAMMA)(a) + * + * gammaIncompleteCompl(a,x) = 1 - gammaIncomplete(a,x) + * = ($(INTEGRATE x, $(INFIN)) $(POWER e, -t) $(POWER t, a-1) dt )/ $(GAMMA)(a) + * + * In this implementation both arguments must be positive. + * The integral is evaluated by either a power series or + * continued fraction expansion, depending on the relative + * values of a and x. + */ +real gammaIncomplete(real a, real x ) +in { + assert(x >= 0); + assert(a > 0); +} +body { + return std.internal.math.gammafunction.gammaIncomplete(a, x); +} + +/** ditto */ +real gammaIncompleteCompl(real a, real x ) +in { + assert(x >= 0); + assert(a > 0); +} +body { + return std.internal.math.gammafunction.gammaIncompleteCompl(a, x); +} + +/** Inverse of complemented incomplete gamma integral + * + * Given a and p, the function finds x such that + * + * gammaIncompleteCompl( a, x ) = p. + */ +real gammaIncompleteComplInverse(real a, real p) +in { + assert(p >= 0 && p <= 1); + assert(a > 0); +} +body { + return std.internal.math.gammafunction.gammaIncompleteComplInv(a, p); +} + + +/* *********************************************** + * ERROR FUNCTIONS & NORMAL DISTRIBUTION * + * ***********************************************/ + + /** Error function + * + * The integral is + * + * erf(x) = 2/ $(SQRT)($(PI)) + * $(INTEGRATE 0, x) exp( - $(POWER t, 2)) dt + * + * The magnitude of x is limited to about 106.56 for IEEE 80-bit + * arithmetic; 1 or -1 is returned outside this range. + */ +real erf(real x) +{ + return std.internal.math.errorfunction.erf(x); +} + +/** Complementary error function + * + * erfc(x) = 1 - erf(x) + * = 2/ $(SQRT)($(PI)) + * $(INTEGRATE x, $(INFIN)) exp( - $(POWER t, 2)) dt + * + * This function has high relative accuracy for + * values of x far from zero. (For values near zero, use erf(x)). + */ +real erfc(real x) +{ + return std.internal.math.errorfunction.erfc(x); +} + + +/** Normal distribution function. + * + * The normal (or Gaussian, or bell-shaped) distribution is + * defined as: + * + * normalDist(x) = 1/$(SQRT)(2$(PI)) $(INTEGRATE -$(INFIN), x) exp( - $(POWER t, 2)/2) dt + * = 0.5 + 0.5 * erf(x/sqrt(2)) + * = 0.5 * erfc(- x/sqrt(2)) + * + * To maintain accuracy at values of x near 1.0, use + * normalDistribution(x) = 1.0 - normalDistribution(-x). + * + * References: + * $(LINK http://www.netlib.org/cephes/ldoubdoc.html), + * G. Marsaglia, "Evaluating the Normal Distribution", + * Journal of Statistical Software 11, (July 2004). + */ +real normalDistribution(real x) +{ + return std.internal.math.errorfunction.normalDistributionImpl(x); +} + +/** Inverse of Normal distribution function + * + * Returns the argument, x, for which the area under the + * Normal probability density function (integrated from + * minus infinity to x) is equal to p. + * + * Note: This function is only implemented to 80 bit precision. + */ +real normalDistributionInverse(real p) +in { + assert(p >= 0.0L && p <= 1.0L, "Domain error"); +} +body +{ + return std.internal.math.errorfunction.normalDistributionInvImpl(p); +} diff --git a/libphobos/src/std/meta.d b/libphobos/src/std/meta.d new file mode 100644 index 0000000..308e50f --- /dev/null +++ b/libphobos/src/std/meta.d @@ -0,0 +1,1679 @@ +// Written in the D programming language. + +/** + * Templates to manipulate template argument lists (also known as type lists). + * + * Some operations on alias sequences are built in to the language, + * such as TL[$(I n)] which gets the $(I n)th type from the + * alias sequence. TL[$(I lwr) .. $(I upr)] returns a new type + * list that is a slice of the old one. + * + * Several templates in this module use or operate on eponymous templates that + * take a single argument and evaluate to a boolean constant. Such templates + * are referred to as $(I template predicates). + * + * $(SCRIPT inhibitQuickIndex = 1;) + * $(DIVC quickindex, + * $(BOOKTABLE , + * $(TR $(TH Category) $(TH Templates)) + * $(TR $(TD Building blocks) $(TD + * $(LREF Alias) + * $(LREF AliasSeq) + * $(LREF aliasSeqOf) + * )) + * $(TR $(TD Alias sequence filtering) $(TD + * $(LREF Erase) + * $(LREF EraseAll) + * $(LREF Filter) + * $(LREF NoDuplicates) + * $(LREF Stride) + * )) + * $(TR $(TD Alias sequence type hierarchy) $(TD + * $(LREF DerivedToFront) + * $(LREF MostDerived) + * )) + * $(TR $(TD Alias sequence transformation) $(TD + * $(LREF Repeat) + * $(LREF Replace) + * $(LREF ReplaceAll) + * $(LREF Reverse) + * $(LREF staticMap) + * $(LREF staticSort) + * )) + * $(TR $(TD Alias sequence searching) $(TD + * $(LREF allSatisfy) + * $(LREF anySatisfy) + * $(LREF staticIndexOf) + * )) + * $(TR $(TD Template predicates) $(TD + * $(LREF templateAnd) + * $(LREF templateNot) + * $(LREF templateOr) + * $(LREF staticIsSorted) + * )) + * $(TR $(TD Template instantiation) $(TD + * $(LREF ApplyLeft) + * $(LREF ApplyRight) + * )) + * )) + * + * References: + * Based on ideas in Table 3.1 from + * $(LINK2 http://amazon.com/exec/obidos/ASIN/0201704315/ref=ase_classicempire/102-2957199-2585768, + * Modern C++ Design), + * Andrei Alexandrescu (Addison-Wesley Professional, 2001) + * Copyright: Copyright Digital Mars 2005 - 2015. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: + * $(HTTP digitalmars.com, Walter Bright), + * $(HTTP klickverbot.at, David Nadlinger) + * Source: $(PHOBOSSRC std/_meta.d) + */ + +module std.meta; + +/** + * Creates a sequence of zero or more aliases. This is most commonly + * used as template parameters or arguments. + * + * In previous versions of Phobos, this was known as `TypeTuple`. + */ +template AliasSeq(TList...) +{ + alias AliasSeq = TList; +} + +/// +@safe unittest +{ + import std.meta; + alias TL = AliasSeq!(int, double); + + int foo(TL td) // same as int foo(int, double); + { + return td[0] + cast(int) td[1]; + } +} + +/// +@safe unittest +{ + alias TL = AliasSeq!(int, double); + + alias Types = AliasSeq!(TL, char); + static assert(is(Types == AliasSeq!(int, double, char))); +} + + +/** + Returns an `AliasSeq` expression of `Func` being + applied to every variadic template argument. + */ + +/// +@safe unittest +{ + auto ref ArgCall(alias Func, alias arg)() + { + return Func(arg); + } + + template Map(alias Func, args...) + { + static if (args.length > 1) + { + alias Map = AliasSeq!(ArgCall!(Func, args[0]), Map!(Func, args[1 .. $])); + } + else + { + alias Map = ArgCall!(Func, args[0]); + } + } + + static int square(int arg) + { + return arg * arg; + } + + static int refSquare(ref int arg) + { + arg *= arg; + return arg; + } + + static ref int refRetSquare(ref int arg) + { + arg *= arg; + return arg; + } + + static void test(int a, int b) + { + assert(a == 4); + assert(b == 16); + } + + static void testRef(ref int a, ref int b) + { + assert(a++ == 16); + assert(b++ == 256); + } + + static int a = 2; + static int b = 4; + + test(Map!(square, a, b)); + + test(Map!(refSquare, a, b)); + assert(a == 4); + assert(b == 16); + + testRef(Map!(refRetSquare, a, b)); + assert(a == 17); + assert(b == 257); +} + +/** + * Allows `alias`ing of any single symbol, type or compile-time expression. + * + * Not everything can be directly aliased. An alias cannot be declared + * of - for example - a literal: + * + * `alias a = 4; //Error` + * + * With this template any single entity can be aliased: + * + * `alias b = Alias!4; //OK` + * + * See_Also: + * To alias more than one thing at once, use $(LREF AliasSeq) + */ +alias Alias(alias a) = a; + +/// Ditto +alias Alias(T) = T; + +/// +@safe unittest +{ + // Without Alias this would fail if Args[0] was e.g. a value and + // some logic would be needed to detect when to use enum instead + alias Head(Args ...) = Alias!(Args[0]); + alias Tail(Args ...) = Args[1 .. $]; + + alias Blah = AliasSeq!(3, int, "hello"); + static assert(Head!Blah == 3); + static assert(is(Head!(Tail!Blah) == int)); + static assert((Tail!Blah)[1] == "hello"); +} + +/// +@safe unittest +{ + alias a = Alias!(123); + static assert(a == 123); + + enum abc = 1; + alias b = Alias!(abc); + static assert(b == 1); + + alias c = Alias!(3 + 4); + static assert(c == 7); + + alias concat = (s0, s1) => s0 ~ s1; + alias d = Alias!(concat("Hello", " World!")); + static assert(d == "Hello World!"); + + alias e = Alias!(int); + static assert(is(e == int)); + + alias f = Alias!(AliasSeq!(int)); + static assert(!is(typeof(f[0]))); //not an AliasSeq + static assert(is(f == int)); + + auto g = 6; + alias h = Alias!g; + ++h; + assert(g == 7); +} + +package template OldAlias(alias a) +{ + static if (__traits(compiles, { alias x = a; })) + alias OldAlias = a; + else static if (__traits(compiles, { enum x = a; })) + enum OldAlias = a; + else + static assert(0, "Cannot alias " ~ a.stringof); +} + +import std.traits : isAggregateType, Unqual; + +package template OldAlias(T) +if (!isAggregateType!T || is(Unqual!T == T)) +{ + alias OldAlias = T; +} + +@safe unittest +{ + static struct Foo {} + static assert(is(OldAlias!(const(Foo)) == Foo)); + static assert(is(OldAlias!(const(int)) == const(int))); + static assert(OldAlias!123 == 123); + enum abc = 123; + static assert(OldAlias!abc == 123); +} + +/** + * Returns the index of the first occurrence of type T in the + * sequence of zero or more types TList. + * If not found, -1 is returned. + */ +template staticIndexOf(T, TList...) +{ + enum staticIndexOf = genericIndexOf!(T, TList).index; +} + +/// Ditto +template staticIndexOf(alias T, TList...) +{ + enum staticIndexOf = genericIndexOf!(T, TList).index; +} + +/// +@safe unittest +{ + import std.stdio; + + void foo() + { + writefln("The index of long is %s", + staticIndexOf!(long, AliasSeq!(int, long, double))); + // prints: The index of long is 1 + } +} + +// [internal] +private template genericIndexOf(args...) +if (args.length >= 1) +{ + alias e = OldAlias!(args[0]); + alias tuple = args[1 .. $]; + + static if (tuple.length) + { + alias head = OldAlias!(tuple[0]); + alias tail = tuple[1 .. $]; + + static if (isSame!(e, head)) + { + enum index = 0; + } + else + { + enum next = genericIndexOf!(e, tail).index; + enum index = (next == -1) ? -1 : 1 + next; + } + } + else + { + enum index = -1; + } +} + +@safe unittest +{ + static assert(staticIndexOf!( byte, byte, short, int, long) == 0); + static assert(staticIndexOf!(short, byte, short, int, long) == 1); + static assert(staticIndexOf!( int, byte, short, int, long) == 2); + static assert(staticIndexOf!( long, byte, short, int, long) == 3); + static assert(staticIndexOf!( char, byte, short, int, long) == -1); + static assert(staticIndexOf!( -1, byte, short, int, long) == -1); + static assert(staticIndexOf!(void) == -1); + + static assert(staticIndexOf!("abc", "abc", "def", "ghi", "jkl") == 0); + static assert(staticIndexOf!("def", "abc", "def", "ghi", "jkl") == 1); + static assert(staticIndexOf!("ghi", "abc", "def", "ghi", "jkl") == 2); + static assert(staticIndexOf!("jkl", "abc", "def", "ghi", "jkl") == 3); + static assert(staticIndexOf!("mno", "abc", "def", "ghi", "jkl") == -1); + static assert(staticIndexOf!( void, "abc", "def", "ghi", "jkl") == -1); + static assert(staticIndexOf!(42) == -1); + + static assert(staticIndexOf!(void, 0, "void", void) == 2); + static assert(staticIndexOf!("void", 0, void, "void") == 2); +} + +/** + * Returns an `AliasSeq` created from TList with the first occurrence, + * if any, of T removed. + */ +template Erase(T, TList...) +{ + alias Erase = GenericErase!(T, TList).result; +} + +/// Ditto +template Erase(alias T, TList...) +{ + alias Erase = GenericErase!(T, TList).result; +} + +/// +@safe unittest +{ + alias Types = AliasSeq!(int, long, double, char); + alias TL = Erase!(long, Types); + static assert(is(TL == AliasSeq!(int, double, char))); +} + +// [internal] +private template GenericErase(args...) +if (args.length >= 1) +{ + alias e = OldAlias!(args[0]); + alias tuple = args[1 .. $] ; + + static if (tuple.length) + { + alias head = OldAlias!(tuple[0]); + alias tail = tuple[1 .. $]; + + static if (isSame!(e, head)) + alias result = tail; + else + alias result = AliasSeq!(head, GenericErase!(e, tail).result); + } + else + { + alias result = AliasSeq!(); + } +} + +@safe unittest +{ + static assert(Pack!(Erase!(int, + short, int, int, 4)). + equals!(short, int, 4)); + + static assert(Pack!(Erase!(1, + real, 3, 1, 4, 1, 5, 9)). + equals!(real, 3, 4, 1, 5, 9)); +} + + +/** + * Returns an `AliasSeq` created from TList with the all occurrences, + * if any, of T removed. + */ +template EraseAll(T, TList...) +{ + alias EraseAll = GenericEraseAll!(T, TList).result; +} + +/// Ditto +template EraseAll(alias T, TList...) +{ + alias EraseAll = GenericEraseAll!(T, TList).result; +} + +/// +@safe unittest +{ + alias Types = AliasSeq!(int, long, long, int); + + alias TL = EraseAll!(long, Types); + static assert(is(TL == AliasSeq!(int, int))); +} + +// [internal] +private template GenericEraseAll(args...) +if (args.length >= 1) +{ + alias e = OldAlias!(args[0]); + alias tuple = args[1 .. $]; + + static if (tuple.length) + { + alias head = OldAlias!(tuple[0]); + alias tail = tuple[1 .. $]; + alias next = AliasSeq!( + GenericEraseAll!(e, tail[0..$/2]).result, + GenericEraseAll!(e, tail[$/2..$]).result + ); + + static if (isSame!(e, head)) + alias result = next; + else + alias result = AliasSeq!(head, next); + } + else + { + alias result = AliasSeq!(); + } +} + +@safe unittest +{ + static assert(Pack!(EraseAll!(int, + short, int, int, 4)). + equals!(short, 4)); + + static assert(Pack!(EraseAll!(1, + real, 3, 1, 4, 1, 5, 9)). + equals!(real, 3, 4, 5, 9)); +} + + +/** + * Returns an `AliasSeq` created from TList with the all duplicate + * types removed. + */ +template NoDuplicates(TList...) +{ + template EraseAllN(uint N, T...) + { + static if (N <= 1) + { + alias EraseAllN = T; + } + else + { + alias EraseAllN = EraseAllN!(N-1, T[1 .. N], EraseAll!(T[0], T[N..$])); + } + } + static if (TList.length > 500) + { + enum steps = 16; + alias first = NoDuplicates!(TList[0 .. steps]); + alias NoDuplicates = NoDuplicates!(EraseAllN!(first.length, first, TList[steps..$])); + } + else static if (TList.length == 0) + { + alias NoDuplicates = TList; + } + else + { + alias NoDuplicates = + AliasSeq!(TList[0], NoDuplicates!(EraseAll!(TList[0], TList[1 .. $]))); + } +} + +/// +@safe unittest +{ + alias Types = AliasSeq!(int, long, long, int, float); + + alias TL = NoDuplicates!(Types); + static assert(is(TL == AliasSeq!(int, long, float))); +} + +@safe unittest +{ + // Bugzilla 14561: huge enums + alias LongList = Repeat!(1500, int); + static assert(NoDuplicates!LongList.length == 1); +} + +@safe unittest +{ + static assert( + Pack!( + NoDuplicates!(1, int, 1, NoDuplicates, int, NoDuplicates, real)) + .equals!(1, int, NoDuplicates, real)); +} + + +/** + * Returns an `AliasSeq` created from TList with the first occurrence + * of type T, if found, replaced with type U. + */ +template Replace(T, U, TList...) +{ + alias Replace = GenericReplace!(T, U, TList).result; +} + +/// Ditto +template Replace(alias T, U, TList...) +{ + alias Replace = GenericReplace!(T, U, TList).result; +} + +/// Ditto +template Replace(T, alias U, TList...) +{ + alias Replace = GenericReplace!(T, U, TList).result; +} + +/// Ditto +template Replace(alias T, alias U, TList...) +{ + alias Replace = GenericReplace!(T, U, TList).result; +} + +/// +@safe unittest +{ + alias Types = AliasSeq!(int, long, long, int, float); + + alias TL = Replace!(long, char, Types); + static assert(is(TL == AliasSeq!(int, char, long, int, float))); +} + +// [internal] +private template GenericReplace(args...) +if (args.length >= 2) +{ + alias from = OldAlias!(args[0]); + alias to = OldAlias!(args[1]); + alias tuple = args[2 .. $]; + + static if (tuple.length) + { + alias head = OldAlias!(tuple[0]); + alias tail = tuple[1 .. $]; + + static if (isSame!(from, head)) + alias result = AliasSeq!(to, tail); + else + alias result = AliasSeq!(head, + GenericReplace!(from, to, tail).result); + } + else + { + alias result = AliasSeq!(); + } + } + +@safe unittest +{ + static assert(Pack!(Replace!(byte, ubyte, + short, byte, byte, byte)). + equals!(short, ubyte, byte, byte)); + + static assert(Pack!(Replace!(1111, byte, + 2222, 1111, 1111, 1111)). + equals!(2222, byte, 1111, 1111)); + + static assert(Pack!(Replace!(byte, 1111, + short, byte, byte, byte)). + equals!(short, 1111, byte, byte)); + + static assert(Pack!(Replace!(1111, "11", + 2222, 1111, 1111, 1111)). + equals!(2222, "11", 1111, 1111)); +} + +/** + * Returns an `AliasSeq` created from TList with all occurrences + * of type T, if found, replaced with type U. + */ +template ReplaceAll(T, U, TList...) +{ + alias ReplaceAll = GenericReplaceAll!(T, U, TList).result; +} + +/// Ditto +template ReplaceAll(alias T, U, TList...) +{ + alias ReplaceAll = GenericReplaceAll!(T, U, TList).result; +} + +/// Ditto +template ReplaceAll(T, alias U, TList...) +{ + alias ReplaceAll = GenericReplaceAll!(T, U, TList).result; +} + +/// Ditto +template ReplaceAll(alias T, alias U, TList...) +{ + alias ReplaceAll = GenericReplaceAll!(T, U, TList).result; +} + +/// +@safe unittest +{ + alias Types = AliasSeq!(int, long, long, int, float); + + alias TL = ReplaceAll!(long, char, Types); + static assert(is(TL == AliasSeq!(int, char, char, int, float))); +} + +// [internal] +private template GenericReplaceAll(args...) +if (args.length >= 2) +{ + alias from = OldAlias!(args[0]); + alias to = OldAlias!(args[1]); + alias tuple = args[2 .. $]; + + static if (tuple.length) + { + alias head = OldAlias!(tuple[0]); + alias tail = tuple[1 .. $]; + alias next = GenericReplaceAll!(from, to, tail).result; + + static if (isSame!(from, head)) + alias result = AliasSeq!(to, next); + else + alias result = AliasSeq!(head, next); + } + else + { + alias result = AliasSeq!(); + } +} + +@safe unittest +{ + static assert(Pack!(ReplaceAll!(byte, ubyte, + byte, short, byte, byte)). + equals!(ubyte, short, ubyte, ubyte)); + + static assert(Pack!(ReplaceAll!(1111, byte, + 1111, 2222, 1111, 1111)). + equals!(byte, 2222, byte, byte)); + + static assert(Pack!(ReplaceAll!(byte, 1111, + byte, short, byte, byte)). + equals!(1111, short, 1111, 1111)); + + static assert(Pack!(ReplaceAll!(1111, "11", + 1111, 2222, 1111, 1111)). + equals!("11", 2222, "11", "11")); +} + +/** + * Returns an `AliasSeq` created from TList with the order reversed. + */ +template Reverse(TList...) +{ + static if (TList.length <= 1) + { + alias Reverse = TList; + } + else + { + alias Reverse = + AliasSeq!( + Reverse!(TList[$/2 .. $ ]), + Reverse!(TList[ 0 .. $/2])); + } +} + +/// +@safe unittest +{ + alias Types = AliasSeq!(int, long, long, int, float); + + alias TL = Reverse!(Types); + static assert(is(TL == AliasSeq!(float, int, long, long, int))); +} + +/** + * Returns the type from TList that is the most derived from type T. + * If none are found, T is returned. + */ +template MostDerived(T, TList...) +{ + static if (TList.length == 0) + alias MostDerived = T; + else static if (is(TList[0] : T)) + alias MostDerived = MostDerived!(TList[0], TList[1 .. $]); + else + alias MostDerived = MostDerived!(T, TList[1 .. $]); +} + +/// +@safe unittest +{ + class A { } + class B : A { } + class C : B { } + alias Types = AliasSeq!(A, C, B); + + MostDerived!(Object, Types) x; // x is declared as type C + static assert(is(typeof(x) == C)); +} + +/** + * Returns the `AliasSeq` TList with the types sorted so that the most + * derived types come first. + */ +template DerivedToFront(TList...) +{ + static if (TList.length == 0) + alias DerivedToFront = TList; + else + alias DerivedToFront = + AliasSeq!(MostDerived!(TList[0], TList[1 .. $]), + DerivedToFront!(ReplaceAll!(MostDerived!(TList[0], TList[1 .. $]), + TList[0], + TList[1 .. $]))); +} + +/// +@safe unittest +{ + class A { } + class B : A { } + class C : B { } + alias Types = AliasSeq!(A, C, B); + + alias TL = DerivedToFront!(Types); + static assert(is(TL == AliasSeq!(C, B, A))); +} + +/** +Evaluates to $(D AliasSeq!(F!(T[0]), F!(T[1]), ..., F!(T[$ - 1]))). + */ +template staticMap(alias F, T...) +{ + static if (T.length == 0) + { + alias staticMap = AliasSeq!(); + } + else static if (T.length == 1) + { + alias staticMap = AliasSeq!(F!(T[0])); + } + else + { + alias staticMap = + AliasSeq!( + staticMap!(F, T[ 0 .. $/2]), + staticMap!(F, T[$/2 .. $ ])); + } +} + +/// +@safe unittest +{ + import std.traits : Unqual; + alias TL = staticMap!(Unqual, int, const int, immutable int); + static assert(is(TL == AliasSeq!(int, int, int))); +} + +@safe unittest +{ + import std.traits : Unqual; + + // empty + alias Empty = staticMap!(Unqual); + static assert(Empty.length == 0); + + // single + alias Single = staticMap!(Unqual, const int); + static assert(is(Single == AliasSeq!int)); + + alias T = staticMap!(Unqual, int, const int, immutable int); + static assert(is(T == AliasSeq!(int, int, int))); +} + +/** +Tests whether all given items satisfy a template predicate, i.e. evaluates to +$(D F!(T[0]) && F!(T[1]) && ... && F!(T[$ - 1])). + +Evaluation is $(I not) short-circuited if a false result is encountered; the +template predicate must be instantiable with all the given items. + */ +template allSatisfy(alias F, T...) +{ + static if (T.length == 0) + { + enum allSatisfy = true; + } + else static if (T.length == 1) + { + enum allSatisfy = F!(T[0]); + } + else + { + enum allSatisfy = + allSatisfy!(F, T[ 0 .. $/2]) && + allSatisfy!(F, T[$/2 .. $ ]); + } +} + +/// +@safe unittest +{ + import std.traits : isIntegral; + + static assert(!allSatisfy!(isIntegral, int, double)); + static assert( allSatisfy!(isIntegral, int, long)); +} + +/** +Tests whether any given items satisfy a template predicate, i.e. evaluates to +$(D F!(T[0]) || F!(T[1]) || ... || F!(T[$ - 1])). + +Evaluation is short-circuited if a true result is encountered; the +template predicate must be instantiable with one of the given items. + */ +template anySatisfy(alias F, T...) +{ + static if (T.length == 0) + { + enum anySatisfy = false; + } + else static if (T.length == 1) + { + enum anySatisfy = F!(T[0]); + } + else + { + enum anySatisfy = + anySatisfy!(F, T[ 0 .. $/2]) || + anySatisfy!(F, T[$/2 .. $ ]); + } +} + +/// +@safe unittest +{ + import std.traits : isIntegral; + + static assert(!anySatisfy!(isIntegral, string, double)); + static assert( anySatisfy!(isIntegral, int, double)); +} + + +/** + * Filters an $(D AliasSeq) using a template predicate. Returns a + * $(D AliasSeq) of the elements which satisfy the predicate. + */ +template Filter(alias pred, TList...) +{ + static if (TList.length == 0) + { + alias Filter = AliasSeq!(); + } + else static if (TList.length == 1) + { + static if (pred!(TList[0])) + alias Filter = AliasSeq!(TList[0]); + else + alias Filter = AliasSeq!(); + } + else + { + alias Filter = + AliasSeq!( + Filter!(pred, TList[ 0 .. $/2]), + Filter!(pred, TList[$/2 .. $ ])); + } +} + +/// +@safe unittest +{ + import std.traits : isNarrowString, isUnsigned; + + alias Types1 = AliasSeq!(string, wstring, dchar[], char[], dstring, int); + alias TL1 = Filter!(isNarrowString, Types1); + static assert(is(TL1 == AliasSeq!(string, wstring, char[]))); + + alias Types2 = AliasSeq!(int, byte, ubyte, dstring, dchar, uint, ulong); + alias TL2 = Filter!(isUnsigned, Types2); + static assert(is(TL2 == AliasSeq!(ubyte, uint, ulong))); +} + +@safe unittest +{ + import std.traits : isPointer; + + static assert(is(Filter!(isPointer, int, void*, char[], int*) == AliasSeq!(void*, int*))); + static assert(is(Filter!isPointer == AliasSeq!())); +} + + +// Used in template predicate unit tests below. +private version (unittest) +{ + template testAlways(T...) + { + enum testAlways = true; + } + + template testNever(T...) + { + enum testNever = false; + } + + template testError(T...) + { + static assert(false, "Should never be instantiated."); + } +} + + +/** + * Negates the passed template predicate. + */ +template templateNot(alias pred) +{ + enum templateNot(T...) = !pred!T; +} + +/// +@safe unittest +{ + import std.traits : isPointer; + + alias isNoPointer = templateNot!isPointer; + static assert(!isNoPointer!(int*)); + static assert(allSatisfy!(isNoPointer, string, char, float)); +} + +@safe unittest +{ + foreach (T; AliasSeq!(int, staticMap, 42)) + { + static assert(!Instantiate!(templateNot!testAlways, T)); + static assert(Instantiate!(templateNot!testNever, T)); + } +} + + +/** + * Combines several template predicates using logical AND, i.e. constructs a new + * predicate which evaluates to true for a given input T if and only if all of + * the passed predicates are true for T. + * + * The predicates are evaluated from left to right, aborting evaluation in a + * short-cut manner if a false result is encountered, in which case the latter + * instantiations do not need to compile. + */ +template templateAnd(Preds...) +{ + template templateAnd(T...) + { + static if (Preds.length == 0) + { + enum templateAnd = true; + } + else + { + static if (Instantiate!(Preds[0], T)) + alias templateAnd = Instantiate!(.templateAnd!(Preds[1 .. $]), T); + else + enum templateAnd = false; + } + } +} + +/// +@safe unittest +{ + import std.traits : isNumeric, isUnsigned; + + alias storesNegativeNumbers = templateAnd!(isNumeric, templateNot!isUnsigned); + static assert(storesNegativeNumbers!int); + static assert(!storesNegativeNumbers!string && !storesNegativeNumbers!uint); + + // An empty list of predicates always yields true. + alias alwaysTrue = templateAnd!(); + static assert(alwaysTrue!int); +} + +@safe unittest +{ + foreach (T; AliasSeq!(int, staticMap, 42)) + { + static assert( Instantiate!(templateAnd!(), T)); + static assert( Instantiate!(templateAnd!(testAlways), T)); + static assert( Instantiate!(templateAnd!(testAlways, testAlways), T)); + static assert(!Instantiate!(templateAnd!(testNever), T)); + static assert(!Instantiate!(templateAnd!(testAlways, testNever), T)); + static assert(!Instantiate!(templateAnd!(testNever, testAlways), T)); + + static assert(!Instantiate!(templateAnd!(testNever, testError), T)); + static assert(!is(typeof(Instantiate!(templateAnd!(testAlways, testError), T)))); + } +} + + +/** + * Combines several template predicates using logical OR, i.e. constructs a new + * predicate which evaluates to true for a given input T if and only at least + * one of the passed predicates is true for T. + * + * The predicates are evaluated from left to right, aborting evaluation in a + * short-cut manner if a true result is encountered, in which case the latter + * instantiations do not need to compile. + */ +template templateOr(Preds...) +{ + template templateOr(T...) + { + static if (Preds.length == 0) + { + enum templateOr = false; + } + else + { + static if (Instantiate!(Preds[0], T)) + enum templateOr = true; + else + alias templateOr = Instantiate!(.templateOr!(Preds[1 .. $]), T); + } + } +} + +/// +@safe unittest +{ + import std.traits : isPointer, isUnsigned; + + alias isPtrOrUnsigned = templateOr!(isPointer, isUnsigned); + static assert( isPtrOrUnsigned!uint && isPtrOrUnsigned!(short*)); + static assert(!isPtrOrUnsigned!int && !isPtrOrUnsigned!(string)); + + // An empty list of predicates never yields true. + alias alwaysFalse = templateOr!(); + static assert(!alwaysFalse!int); +} + +@safe unittest +{ + foreach (T; AliasSeq!(int, staticMap, 42)) + { + static assert( Instantiate!(templateOr!(testAlways), T)); + static assert( Instantiate!(templateOr!(testAlways, testAlways), T)); + static assert( Instantiate!(templateOr!(testAlways, testNever), T)); + static assert( Instantiate!(templateOr!(testNever, testAlways), T)); + static assert(!Instantiate!(templateOr!(), T)); + static assert(!Instantiate!(templateOr!(testNever), T)); + + static assert( Instantiate!(templateOr!(testAlways, testError), T)); + static assert( Instantiate!(templateOr!(testNever, testAlways, testError), T)); + // DMD @@BUG@@: Assertion fails for int, seems like a error gagging + // problem. The bug goes away when removing some of the other template + // instantiations in the module. + // static assert(!is(typeof(Instantiate!(templateOr!(testNever, testError), T)))); + } +} + +/** + * Converts an input range $(D range) to an alias sequence. + */ +template aliasSeqOf(alias range) +{ + import std.traits : isArray, isNarrowString; + + alias ArrT = typeof(range); + static if (isArray!ArrT && !isNarrowString!ArrT) + { + static if (range.length == 0) + { + alias aliasSeqOf = AliasSeq!(); + } + else static if (range.length == 1) + { + alias aliasSeqOf = AliasSeq!(range[0]); + } + else + { + alias aliasSeqOf = AliasSeq!(aliasSeqOf!(range[0 .. $/2]), aliasSeqOf!(range[$/2 .. $])); + } + } + else + { + import std.range.primitives : isInputRange; + static if (isInputRange!ArrT) + { + import std.array : array; + alias aliasSeqOf = aliasSeqOf!(array(range)); + } + else + { + static assert(false, "Cannot transform range of type " ~ ArrT.stringof ~ " into a AliasSeq."); + } + } +} + +/// +@safe unittest +{ + import std.algorithm.iteration : map; + import std.algorithm.sorting : sort; + import std.string : capitalize; + + struct S + { + int a; + int c; + int b; + } + + alias capMembers = aliasSeqOf!([__traits(allMembers, S)].sort().map!capitalize()); + static assert(capMembers[0] == "A"); + static assert(capMembers[1] == "B"); + static assert(capMembers[2] == "C"); +} + +/// +@safe unittest +{ + static immutable REF = [0, 1, 2, 3]; + foreach (I, V; aliasSeqOf!([0, 1, 2, 3])) + { + static assert(V == I); + static assert(V == REF[I]); + } +} + +@safe unittest +{ + import std.conv : to, octal; + import std.range : iota; + //Testing compile time octal + foreach (I2; aliasSeqOf!(iota(0, 8))) + foreach (I1; aliasSeqOf!(iota(0, 8))) + { + enum oct = I2 * 8 + I1; + enum dec = I2 * 10 + I1; + enum str = to!string(dec); + static assert(octal!dec == oct); + static assert(octal!str == oct); + } +} + +@safe unittest +{ + enum REF = "日本語"d; + foreach (I, V; aliasSeqOf!"日本語"c) + { + static assert(V == REF[I]); + } +} + +/** + * $(LINK2 http://en.wikipedia.org/wiki/Partial_application, Partially applies) + * $(D_PARAM Template) by binding its first (left) or last (right) arguments + * to $(D_PARAM args). + * + * Behaves like the identity function when $(D_PARAM args) is empty. + * Params: + * Template = template to partially apply + * args = arguments to bind + * Returns: + * _Template with arity smaller than or equal to $(D_PARAM Template) + */ +template ApplyLeft(alias Template, args...) +{ + alias ApplyLeft(right...) = SmartAlias!(Template!(args, right)); +} + +/// Ditto +template ApplyRight(alias Template, args...) +{ + alias ApplyRight(left...) = SmartAlias!(Template!(left, args)); +} + +/// +@safe unittest +{ + // enum bool isImplicitlyConvertible(From, To) + import std.traits : isImplicitlyConvertible; + + static assert(allSatisfy!( + ApplyLeft!(isImplicitlyConvertible, ubyte), + short, ushort, int, uint, long, ulong)); + + static assert(is(Filter!(ApplyRight!(isImplicitlyConvertible, short), + ubyte, string, short, float, int) == AliasSeq!(ubyte, short))); +} + +/// +@safe unittest +{ + import std.traits : hasMember, ifTestable; + + struct T1 + { + bool foo; + } + + struct T2 + { + struct Test + { + bool opCast(T : bool)() { return true; } + } + + Test foo; + } + + static assert(allSatisfy!(ApplyRight!(hasMember, "foo"), T1, T2)); + static assert(allSatisfy!(ApplyRight!(ifTestable, a => a.foo), T1, T2)); +} + +/// +@safe unittest +{ + import std.traits : Largest; + + alias Types = AliasSeq!(byte, short, int, long); + + static assert(is(staticMap!(ApplyLeft!(Largest, short), Types) == + AliasSeq!(short, short, int, long))); + static assert(is(staticMap!(ApplyLeft!(Largest, int), Types) == + AliasSeq!(int, int, int, long))); +} + +/// +@safe unittest +{ + import std.traits : FunctionAttribute, SetFunctionAttributes; + + static void foo() @system; + static int bar(int) @system; + + alias SafeFunctions = AliasSeq!( + void function() @safe, + int function(int) @safe); + + static assert(is(staticMap!(ApplyRight!( + SetFunctionAttributes, "D", FunctionAttribute.safe), + typeof(&foo), typeof(&bar)) == SafeFunctions)); +} + +private template SmartAlias(T...) +{ + static if (T.length == 1) + { + alias SmartAlias = Alias!T; + } + else + { + alias SmartAlias = AliasSeq!T; + } +} + +@safe unittest +{ + static assert(is(typeof({ + alias T(T0, int a, double b, alias T1, string c) = AliasSeq!(T0, a, b, T1, c); + alias T0 = ApplyRight!(ApplyLeft, ApplyRight); + alias T1 = T0!ApplyLeft; + alias T2 = T1!T; + alias T3 = T2!(3, "foo"); + alias T4 = T3!(short, 3, 3.3); + static assert(Pack!T4.equals!(short, 3, 3.3, 3, "foo")); + + import std.traits : isImplicitlyConvertible; + alias U1 = ApplyLeft!(ApplyRight, isImplicitlyConvertible); + alias U2 = U1!int; + enum U3 = U2!short; + static assert(U3); + }))); +} + +/** + * Creates an `AliasSeq` which repeats a type or an `AliasSeq` exactly `n` times. + */ +template Repeat(size_t n, TList...) +if (n > 0) +{ + static if (n == 1) + { + alias Repeat = AliasSeq!TList; + } + else static if (n == 2) + { + alias Repeat = AliasSeq!(TList, TList); + } + else + { + alias R = Repeat!((n - 1) / 2, TList); + static if ((n - 1) % 2 == 0) + { + alias Repeat = AliasSeq!(TList, R, R); + } + else + { + alias Repeat = AliasSeq!(TList, TList, R, R); + } + } +} + +/// +@safe unittest +{ + alias ImInt1 = Repeat!(1, immutable(int)); + static assert(is(ImInt1 == AliasSeq!(immutable(int)))); + + alias Real3 = Repeat!(3, real); + static assert(is(Real3 == AliasSeq!(real, real, real))); + + alias Real12 = Repeat!(4, Real3); + static assert(is(Real12 == AliasSeq!(real, real, real, real, real, real, + real, real, real, real, real, real))); + + alias Composite = AliasSeq!(uint, int); + alias Composite2 = Repeat!(2, Composite); + static assert(is(Composite2 == AliasSeq!(uint, int, uint, int))); +} + + +/// +@safe unittest +{ + auto staticArray(T, size_t n)(Repeat!(n, T) elems) + { + T[n] a = [elems]; + return a; + } + + auto a = staticArray!(long, 3)(3, 1, 4); + assert(is(typeof(a) == long[3])); + assert(a == [3, 1, 4]); +} + +/** + * Sorts a $(LREF AliasSeq) using $(D cmp). + * + * Parameters: + * cmp = A template that returns a $(D bool) (if its first argument is less than the second one) + * or an $(D int) (-1 means less than, 0 means equal, 1 means greater than) + * + * Seq = The $(LREF AliasSeq) to sort + * + * Returns: The sorted alias sequence + */ +template staticSort(alias cmp, Seq...) +{ + static if (Seq.length < 2) + { + alias staticSort = Seq; + } + else + { + private alias btm = staticSort!(cmp, Seq[0 .. $ / 2]); + private alias top = staticSort!(cmp, Seq[$ / 2 .. $]); + + static if (isLessEq!(cmp, btm[$ - 1], top[0])) + alias staticSort = AliasSeq!(btm, top); // already ascending + else static if (isLessEq!(cmp, top[$ - 1], btm[0])) + alias staticSort = AliasSeq!(top, btm); // already descending + else + alias staticSort = staticMerge!(cmp, Seq.length / 2, btm, top); + } +} + +/// +@safe unittest +{ + alias Nums = AliasSeq!(7, 2, 3, 23); + enum Comp(int N1, int N2) = N1 < N2; + static assert(AliasSeq!(2, 3, 7, 23) == staticSort!(Comp, Nums)); +} + +/// +@safe unittest +{ + alias Types = AliasSeq!(uint, short, ubyte, long, ulong); + enum Comp(T1, T2) = __traits(isUnsigned, T2) - __traits(isUnsigned, T1); + static assert(is(AliasSeq!(uint, ubyte, ulong, short, long) == staticSort!(Comp, + Types))); +} + +private template staticMerge(alias cmp, int half, Seq...) +{ + static if (half == 0 || half == Seq.length) + { + alias staticMerge = Seq; + } + else + { + static if (isLessEq!(cmp, Seq[0], Seq[half])) + { + alias staticMerge = AliasSeq!(Seq[0], + staticMerge!(cmp, half - 1, Seq[1 .. $])); + } + else + { + alias staticMerge = AliasSeq!(Seq[half], + staticMerge!(cmp, half, Seq[0 .. half], Seq[half + 1 .. $])); + } + } +} + +private template isLessEq(alias cmp, Seq...) +if (Seq.length == 2) +{ + private enum Result = cmp!(Seq[1], Seq[0]); + static if (is(typeof(Result) == bool)) + enum isLessEq = !Result; + else static if (is(typeof(Result) : int)) + enum isLessEq = Result >= 0; + else + static assert(0, typeof(Result).stringof ~ " is not a value comparison type"); +} + +/** + * Checks if an $(LREF AliasSeq) is sorted according to $(D cmp). + * + * Parameters: + * cmp = A template that returns a $(D bool) (if its first argument is less than the second one) + * or an $(D int) (-1 means less than, 0 means equal, 1 means greater than) + * + * Seq = The $(LREF AliasSeq) to check + * + * Returns: `true` if `Seq` is sorted; otherwise `false` + */ +template staticIsSorted(alias cmp, Seq...) +{ + static if (Seq.length <= 1) + enum staticIsSorted = true; + else static if (Seq.length == 2) + enum staticIsSorted = isLessEq!(cmp, Seq[0], Seq[1]); + else + { + enum staticIsSorted = + isLessEq!(cmp, Seq[($ / 2) - 1], Seq[$ / 2]) && + staticIsSorted!(cmp, Seq[0 .. $ / 2]) && + staticIsSorted!(cmp, Seq[$ / 2 .. $]); + } +} + +/// +@safe unittest +{ + enum Comp(int N1, int N2) = N1 < N2; + static assert( staticIsSorted!(Comp, 2, 2)); + static assert( staticIsSorted!(Comp, 2, 3, 7, 23)); + static assert(!staticIsSorted!(Comp, 7, 2, 3, 23)); +} + +/// +@safe unittest +{ + enum Comp(T1, T2) = __traits(isUnsigned, T2) - __traits(isUnsigned, T1); + static assert( staticIsSorted!(Comp, uint, ubyte, ulong, short, long)); + static assert(!staticIsSorted!(Comp, uint, short, ubyte, long, ulong)); +} + +/** +Selects a subset of the argument list by stepping with fixed `stepSize` over the list. +A negative `stepSize` starts iteration with the last list element. + +Params: + stepSize = Number of elements to increment on each iteration. Can't be `0`. + Args = Template arguments + +Returns: A template argument list filtered by the selected stride. +*/ +template Stride(int stepSize, Args...) +if (stepSize != 0) +{ + static if (Args.length == 0) + { + alias Stride = AliasSeq!(); + } + else static if (stepSize > 0) + { + static if (stepSize >= Args.length) + alias Stride = AliasSeq!(Args[0]); + else + alias Stride = AliasSeq!(Args[0], Stride!(stepSize, Args[stepSize .. $])); + } + else + { + static if (-stepSize >= Args.length) + alias Stride = AliasSeq!(Args[$ - 1]); + else + alias Stride = AliasSeq!(Args[$ - 1], Stride!(stepSize, Args[0 .. $ + stepSize])); + } +} + +/// +@safe unittest +{ + static assert(is(Stride!(1, short, int, long) == AliasSeq!(short, int, long))); + static assert(is(Stride!(2, short, int, long) == AliasSeq!(short, long))); + static assert(is(Stride!(-1, short, int, long) == AliasSeq!(long, int, short))); + static assert(is(Stride!(-2, short, int, long) == AliasSeq!(long, short))); + + alias attribs = AliasSeq!(short, int, long, ushort, uint, ulong); + static assert(is(Stride!(3, attribs) == AliasSeq!(short, ushort))); + static assert(is(Stride!(3, attribs[1 .. $]) == AliasSeq!(int, uint))); + static assert(is(Stride!(-3, attribs) == AliasSeq!(ulong, long))); +} + +@safe unittest +{ + static assert(Pack!(Stride!(5, int)).equals!(int)); + static assert(Pack!(Stride!(-5, int)).equals!(int)); + static assert(!__traits(compiles, Stride!(0, int))); +} + +// : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : // +private: + +/* + * [internal] Returns true if a and b are the same thing, or false if + * not. Both a and b can be types, literals, or symbols. + * + * How: When: + * is(a == b) - both are types + * a == b - both are literals (true literals, enums) + * __traits(isSame, a, b) - other cases (variables, functions, + * templates, etc.) + */ +private template isSame(ab...) +if (ab.length == 2) +{ + static if (__traits(compiles, expectType!(ab[0]), + expectType!(ab[1]))) + { + enum isSame = is(ab[0] == ab[1]); + } + else static if (!__traits(compiles, expectType!(ab[0])) && + !__traits(compiles, expectType!(ab[1])) && + __traits(compiles, expectBool!(ab[0] == ab[1]))) + { + static if (!__traits(compiles, &ab[0]) || + !__traits(compiles, &ab[1])) + enum isSame = (ab[0] == ab[1]); + else + enum isSame = __traits(isSame, ab[0], ab[1]); + } + else + { + enum isSame = __traits(isSame, ab[0], ab[1]); + } +} +private template expectType(T) {} +private template expectBool(bool b) {} + +@safe unittest +{ + static assert( isSame!(int, int)); + static assert(!isSame!(int, short)); + + enum a = 1, b = 1, c = 2, s = "a", t = "a"; + static assert( isSame!(1, 1)); + static assert( isSame!(a, 1)); + static assert( isSame!(a, b)); + static assert(!isSame!(b, c)); + static assert( isSame!("a", "a")); + static assert( isSame!(s, "a")); + static assert( isSame!(s, t)); + static assert(!isSame!(1, "1")); + static assert(!isSame!(a, "a")); + static assert( isSame!(isSame, isSame)); + static assert(!isSame!(isSame, a)); + + static assert(!isSame!(byte, a)); + static assert(!isSame!(short, isSame)); + static assert(!isSame!(a, int)); + static assert(!isSame!(long, isSame)); + + static immutable X = 1, Y = 1, Z = 2; + static assert( isSame!(X, X)); + static assert(!isSame!(X, Y)); + static assert(!isSame!(Y, Z)); + + int foo(); + int bar(); + real baz(int); + static assert( isSame!(foo, foo)); + static assert(!isSame!(foo, bar)); + static assert(!isSame!(bar, baz)); + static assert( isSame!(baz, baz)); + static assert(!isSame!(foo, 0)); + + int x, y; + real z; + static assert( isSame!(x, x)); + static assert(!isSame!(x, y)); + static assert(!isSame!(y, z)); + static assert( isSame!(z, z)); + static assert(!isSame!(x, 0)); +} + +/* + * [internal] Confines a tuple within a template. + */ +private template Pack(T...) +{ + alias tuple = T; + + // For convenience + template equals(U...) + { + static if (T.length == U.length) + { + static if (T.length == 0) + enum equals = true; + else + enum equals = isSame!(T[0], U[0]) && + Pack!(T[1 .. $]).equals!(U[1 .. $]); + } + else + { + enum equals = false; + } + } +} + +@safe unittest +{ + static assert( Pack!(1, int, "abc").equals!(1, int, "abc")); + static assert(!Pack!(1, int, "abc").equals!(1, int, "cba")); +} + +/* + * Instantiates the given template with the given list of parameters. + * + * Used to work around syntactic limitations of D with regard to instantiating + * a template from an alias sequence (e.g. T[0]!(...) is not valid) or a template + * returning another template (e.g. Foo!(Bar)!(Baz) is not allowed). + */ +// TODO: Consider publicly exposing this, maybe even if only for better +// understandability of error messages. +alias Instantiate(alias Template, Params...) = Template!Params; diff --git a/libphobos/src/std/mmfile.d b/libphobos/src/std/mmfile.d new file mode 100644 index 0000000..453e2ec --- /dev/null +++ b/libphobos/src/std/mmfile.d @@ -0,0 +1,721 @@ +// Written in the D programming language. + +/** + * Read and write memory mapped files. + * Copyright: Copyright Digital Mars 2004 - 2009. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: $(HTTP digitalmars.com, Walter Bright), + * Matthew Wilson + * Source: $(PHOBOSSRC std/_mmfile.d) + * + * $(SCRIPT inhibitQuickIndex = 1;) + */ +/* Copyright Digital Mars 2004 - 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) + */ +module std.mmfile; + +import core.stdc.errno; +import core.stdc.stdio; +import core.stdc.stdlib; +import std.conv, std.exception, std.stdio; +import std.file; +import std.path; +import std.string; + +import std.internal.cstring; + +//debug = MMFILE; + +version (Windows) +{ + import core.sys.windows.windows; + import std.utf; + import std.windows.syserror; +} +else version (Posix) +{ + import core.sys.posix.fcntl; + import core.sys.posix.sys.mman; + import core.sys.posix.sys.stat; + import core.sys.posix.unistd; +} +else +{ + static assert(0); +} + +/** + * MmFile objects control the memory mapped file resource. + */ +class MmFile +{ + /** + * The mode the memory mapped file is opened with. + */ + enum Mode + { + read, /// Read existing file + readWriteNew, /// Delete existing file, write new file + readWrite, /// Read/Write existing file, create if not existing + readCopyOnWrite, /// Read/Write existing file, copy on write + } + + /** + * Open memory mapped file filename for reading. + * File is closed when the object instance is deleted. + * Throws: + * std.file.FileException + */ + this(string filename) + { + this(filename, Mode.read, 0, null); + } + + version (linux) this(File file, Mode mode = Mode.read, ulong size = 0, + void* address = null, size_t window = 0) + { + // Save a copy of the File to make sure the fd stays open. + this.file = file; + this(file.fileno, mode, size, address, window); + } + + version (linux) private this(int fildes, Mode mode, ulong size, + void* address, size_t window) + { + int oflag; + int fmode; + + switch (mode) + { + case Mode.read: + flags = MAP_SHARED; + prot = PROT_READ; + oflag = O_RDONLY; + fmode = 0; + break; + + case Mode.readWriteNew: + assert(size != 0); + flags = MAP_SHARED; + prot = PROT_READ | PROT_WRITE; + oflag = O_CREAT | O_RDWR | O_TRUNC; + fmode = octal!660; + break; + + case Mode.readWrite: + flags = MAP_SHARED; + prot = PROT_READ | PROT_WRITE; + oflag = O_CREAT | O_RDWR; + fmode = octal!660; + break; + + case Mode.readCopyOnWrite: + flags = MAP_PRIVATE; + prot = PROT_READ | PROT_WRITE; + oflag = O_RDWR; + fmode = 0; + break; + + default: + assert(0); + } + + fd = fildes; + + // Adjust size + stat_t statbuf = void; + errnoEnforce(fstat(fd, &statbuf) == 0); + if (prot & PROT_WRITE && size > statbuf.st_size) + { + // Need to make the file size bytes big + lseek(fd, cast(off_t)(size - 1), SEEK_SET); + char c = 0; + core.sys.posix.unistd.write(fd, &c, 1); + } + else if (prot & PROT_READ && size == 0) + size = statbuf.st_size; + this.size = size; + + // Map the file into memory! + size_t initial_map = (window && 2*window> 32); + hFileMap = CreateFileMappingW(hFile, null, flProtect, + hi, cast(uint) size, null); + wenforce(hFileMap, "CreateFileMapping"); + scope(failure) + { + CloseHandle(hFileMap); + hFileMap = null; + } + + if (size == 0 && filename != null) + { + uint sizehi; + uint sizelow = GetFileSize(hFile, &sizehi); + wenforce(sizelow != INVALID_FILE_SIZE || GetLastError() != ERROR_SUCCESS, + "GetFileSize"); + size = (cast(ulong) sizehi << 32) + sizelow; + } + this.size = size; + + size_t initial_map = (window && 2*window statbuf.st_size) + { + // Need to make the file size bytes big + .lseek(fd, cast(off_t)(size - 1), SEEK_SET); + char c = 0; + core.sys.posix.unistd.write(fd, &c, 1); + } + else if (prot & PROT_READ && size == 0) + size = statbuf.st_size; + } + else + { + fd = -1; + version (CRuntime_Glibc) import core.sys.linux.sys.mman : MAP_ANON; + flags |= MAP_ANON; + } + this.size = size; + size_t initial_map = (window && 2*window= start && i < start+data.length; + } + + // unmap the current range + private void unmap() + { + debug (MMFILE) printf("MmFile.unmap()\n"); + version (Windows) + { + wenforce(!data.ptr || UnmapViewOfFile(data.ptr) != FALSE, "UnmapViewOfFile"); + } + else + { + errnoEnforce(!data.ptr || munmap(cast(void*) data, data.length) == 0, + "munmap failed"); + } + data = null; + } + + // map range + private void map(ulong start, size_t len) + { + debug (MMFILE) printf("MmFile.map(%lld, %d)\n", start, len); + void* p; + if (start+len > size) + len = cast(size_t)(size-start); + version (Windows) + { + uint hi = cast(uint)(start >> 32); + p = MapViewOfFileEx(hFileMap, dwDesiredAccess, hi, cast(uint) start, len, address); + wenforce(p, "MapViewOfFileEx"); + } + else + { + p = mmap(address, len, prot, flags, fd, cast(off_t) start); + errnoEnforce(p != MAP_FAILED); + } + data = p[0 .. len]; + this.start = start; + } + + // ensure a given position is mapped + private void ensureMapped(ulong i) + { + debug (MMFILE) printf("MmFile.ensureMapped(%lld)\n", i); + if (!mapped(i)) + { + unmap(); + if (window == 0) + { + map(0,cast(size_t) size); + } + else + { + ulong block = i/window; + if (block == 0) + map(0,2*window); + else + map(window*(block-1),3*window); + } + } + } + + // ensure a given range is mapped + private void ensureMapped(ulong i, ulong j) + { + debug (MMFILE) printf("MmFile.ensureMapped(%lld, %lld)\n", i, j); + if (!mapped(i) || !mapped(j-1)) + { + unmap(); + if (window == 0) + { + map(0,cast(size_t) size); + } + else + { + ulong iblock = i/window; + ulong jblock = (j-1)/window; + if (iblock == 0) + { + map(0,cast(size_t)(window*(jblock+2))); + } + else + { + map(window*(iblock-1),cast(size_t)(window*(jblock-iblock+3))); + } + } + } + } + +private: + string filename; + void[] data; + ulong start; + size_t window; + ulong size; + Mode mMode; + void* address; + version (linux) File file; + + version (Windows) + { + HANDLE hFile = INVALID_HANDLE_VALUE; + HANDLE hFileMap = null; + uint dwDesiredAccess; + } + else version (Posix) + { + int fd; + int prot; + int flags; + int fmode; + } + else + { + static assert(0); + } + + // Report error, where errno gives the error number + // void errNo() + // { + // version (Windows) + // { + // throw new FileException(filename, GetLastError()); + // } + // else version (linux) + // { + // throw new FileException(filename, errno); + // } + // else + // { + // static assert(0); + // } + // } +} + +@system unittest +{ + import core.memory : GC; + import std.file : deleteme; + + const size_t K = 1024; + size_t win = 64*K; // assume the page size is 64K + version (Windows) + { + /+ these aren't defined in core.sys.windows.windows so let's use default + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + win = sysinfo.dwAllocationGranularity; + +/ + } + else version (linux) + { + // getpagesize() is not defined in the unix D headers so use the guess + } + string test_file = std.file.deleteme ~ "-testing.txt"; + MmFile mf = new MmFile(test_file,MmFile.Mode.readWriteNew, + 100*K,null,win); + ubyte[] str = cast(ubyte[])"1234567890"; + ubyte[] data = cast(ubyte[]) mf[0 .. 10]; + data[] = str[]; + assert( mf[0 .. 10] == str ); + data = cast(ubyte[]) mf[50 .. 60]; + data[] = str[]; + assert( mf[50 .. 60] == str ); + ubyte[] data2 = cast(ubyte[]) mf[20*K .. 60*K]; + assert( data2.length == 40*K ); + assert( data2[$-1] == 0 ); + mf[100*K-1] = cast(ubyte)'b'; + data2 = cast(ubyte[]) mf[21*K .. 100*K]; + assert( data2.length == 79*K ); + assert( data2[$-1] == 'b' ); + + destroy(mf); + GC.free(&mf); + + std.file.remove(test_file); + // Create anonymous mapping + auto test = new MmFile(null, MmFile.Mode.readWriteNew, 1024*1024, null); +} + +version (linux) +@system unittest // Issue 14868 +{ + import std.file : deleteme; + import std.typecons : scoped; + + // Test retaining ownership of File/fd + + auto fn = std.file.deleteme ~ "-testing.txt"; + scope(exit) std.file.remove(fn); + File(fn, "wb").writeln("Testing!"); + scoped!MmFile(File(fn)); + + // Test that unique ownership of File actually leads to the fd being closed + + auto f = File(fn); + auto fd = f.fileno; + { + auto mf = scoped!MmFile(f); + f = File.init; + } + assert(.close(fd) == -1); +} + +@system unittest // Issue 14994, 14995 +{ + import std.file : deleteme; + import std.typecons : scoped; + + // Zero-length map may or may not be valid on OSX and NetBSD + version (OSX) + import std.exception : verifyThrown = collectException; + version (NetBSD) + import std.exception : verifyThrown = collectException; + else + import std.exception : verifyThrown = assertThrown; + + auto fn = std.file.deleteme ~ "-testing.txt"; + scope(exit) std.file.remove(fn); + verifyThrown(scoped!MmFile(fn, MmFile.Mode.readWrite, 0, null)); +} diff --git a/libphobos/src/std/net/curl.d b/libphobos/src/std/net/curl.d new file mode 100644 index 0000000..3465bdf --- /dev/null +++ b/libphobos/src/std/net/curl.d @@ -0,0 +1,5109 @@ +// Written in the D programming language. + +/** +Networking client functionality as provided by $(HTTP _curl.haxx.se/libcurl, +libcurl). The libcurl library must be installed on the system in order to use +this module. + +$(SCRIPT inhibitQuickIndex = 1;) + +$(DIVC quickindex, +$(BOOKTABLE , +$(TR $(TH Category) $(TH Functions) +) +$(TR $(TDNW High level) $(TD $(MYREF download) $(MYREF upload) $(MYREF get) +$(MYREF post) $(MYREF put) $(MYREF del) $(MYREF options) $(MYREF trace) +$(MYREF connect) $(MYREF byLine) $(MYREF byChunk) +$(MYREF byLineAsync) $(MYREF byChunkAsync) ) +) +$(TR $(TDNW Low level) $(TD $(MYREF HTTP) $(MYREF FTP) $(MYREF +SMTP) ) +) +) +) + +Note: +You may need to link to the $(B curl) library, e.g. by adding $(D "libs": ["curl"]) +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://dlang.org/download.html, download page). + +Compared to using libcurl directly this module allows simpler client code for +common uses, requires no unsafe operations, and integrates better with the rest +of the language. Futhermore it provides $(D range) +access to protocols supported by libcurl both synchronously and asynchronously. + +A high level and a low level API are available. The high level API is built +entirely on top of the low level one. + +The high level API is for commonly used functionality such as HTTP/FTP get. The +$(LREF byLineAsync) and $(LREF byChunkAsync) provides asynchronous $(D ranges) that performs the request in another +thread while handling a line/chunk in the current thread. + +The low level API allows for streaming and other advanced features. + +$(BOOKTABLE Cheat Sheet, +$(TR $(TH Function Name) $(TH Description) +) +$(LEADINGROW High level) +$(TR $(TDNW $(LREF download)) $(TD $(D +download("ftp.digitalmars.com/sieve.ds", "/tmp/downloaded-ftp-file")) +downloads file from URL to file system.) +) +$(TR $(TDNW $(LREF upload)) $(TD $(D +upload("/tmp/downloaded-ftp-file", "ftp.digitalmars.com/sieve.ds");) +uploads file from file system to URL.) +) +$(TR $(TDNW $(LREF get)) $(TD $(D +get("dlang.org")) returns a char[] containing the dlang.org web page.) +) +$(TR $(TDNW $(LREF put)) $(TD $(D +put("dlang.org", "Hi")) returns a char[] containing +the dlang.org web page. after a HTTP PUT of "hi") +) +$(TR $(TDNW $(LREF post)) $(TD $(D +post("dlang.org", "Hi")) returns a char[] containing +the dlang.org web page. after a HTTP POST of "hi") +) +$(TR $(TDNW $(LREF byLine)) $(TD $(D +byLine("dlang.org")) returns a range of char[] containing the +dlang.org web page.) +) +$(TR $(TDNW $(LREF byChunk)) $(TD $(D +byChunk("dlang.org", 10)) returns a range of ubyte[10] containing the +dlang.org web page.) +) +$(TR $(TDNW $(LREF byLineAsync)) $(TD $(D +byLineAsync("dlang.org")) returns a range of char[] containing the dlang.org web + page asynchronously.) +) +$(TR $(TDNW $(LREF byChunkAsync)) $(TD $(D +byChunkAsync("dlang.org", 10)) returns a range of ubyte[10] containing the +dlang.org web page asynchronously.) +) +$(LEADINGROW Low level +) +$(TR $(TDNW $(LREF HTTP)) $(TD $(D HTTP) struct for advanced usage)) +$(TR $(TDNW $(LREF FTP)) $(TD $(D FTP) struct for advanced usage)) +$(TR $(TDNW $(LREF SMTP)) $(TD $(D SMTP) struct for advanced usage)) +) + + +Example: +--- +import std.net.curl, std.stdio; + +// Return a char[] containing the content specified by a URL +auto content = get("dlang.org"); + +// Post data and return a char[] containing the content specified by a URL +auto content = post("mydomain.com/here.cgi", ["name1" : "value1", "name2" : "value2"]); + +// Get content of file from ftp server +auto content = get("ftp.digitalmars.com/sieve.ds"); + +// Post and print out content line by line. The request is done in another thread. +foreach (line; byLineAsync("dlang.org", "Post data")) + writeln(line); + +// Get using a line range and proxy settings +auto client = HTTP(); +client.proxy = "1.2.3.4"; +foreach (line; byLine("dlang.org", client)) + writeln(line); +--- + +For more control than the high level functions provide, use the low level API: + +Example: +--- +import std.net.curl, std.stdio; + +// GET with custom data receivers +auto http = HTTP("dlang.org"); +http.onReceiveHeader = + (in char[] key, in char[] value) { writeln(key, ": ", value); }; +http.onReceive = (ubyte[] data) { /+ drop +/ return data.length; }; +http.perform(); +--- + +First, an instance of the reference-counted HTTP struct is created. Then the +custom delegates are set. These will be called whenever the HTTP instance +receives a header and a data buffer, respectively. In this simple example, the +headers are written to stdout and the data is ignored. If the request should be +stopped before it has finished then return something less than data.length from +the onReceive callback. See $(LREF onReceiveHeader)/$(LREF onReceive) for more +information. Finally the HTTP request is effected by calling perform(), which is +synchronous. + +Source: $(PHOBOSSRC std/net/_curl.d) + +Copyright: Copyright Jonas Drewsen 2011-2012 +License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). +Authors: Jonas Drewsen. Some of the SMTP code contributed by Jimmy Cao. + +Credits: The functionally is based on $(HTTP _curl.haxx.se/libcurl, libcurl). + LibCurl is licensed under an MIT/X derivative license. +*/ +/* + Copyright Jonas Drewsen 2011 - 2012. +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) +*/ +module std.net.curl; + +import core.thread; +import etc.c.curl; +import std.concurrency; +import std.encoding; +import std.exception; +import std.meta; +import std.range.primitives; +import std.socket : InternetAddress; +import std.traits; +import std.typecons; + +import std.internal.cstring; + +public import etc.c.curl : CurlOption; + +version (unittest) +{ + // Run unit test with the PHOBOS_TEST_ALLOW_NET=1 set in order to + // allow net traffic + import std.range; + import std.stdio; + + import std.socket : Address, INADDR_LOOPBACK, Socket, TcpSocket; + + private struct TestServer + { + string addr() { return _addr; } + + void handle(void function(Socket s) dg) + { + tid.send(dg); + } + + private: + string _addr; + Tid tid; + + static void loop(shared TcpSocket listener) + { + try while (true) + { + void function(Socket) handler = void; + try + handler = receiveOnly!(typeof(handler)); + catch (OwnerTerminated) + return; + handler((cast() listener).accept); + } + catch (Throwable e) + { + import core.stdc.stdlib : exit, EXIT_FAILURE; + stderr.writeln(e); + exit(EXIT_FAILURE); // Bugzilla 7018 + } + } + } + + private TestServer startServer() + { + auto sock = new TcpSocket; + sock.bind(new InternetAddress(INADDR_LOOPBACK, InternetAddress.PORT_ANY)); + sock.listen(1); + auto addr = sock.localAddress.toString(); + auto tid = spawn(&TestServer.loop, cast(shared) sock); + return TestServer(addr, tid); + } + + private ref TestServer testServer() + { + __gshared TestServer server; + return initOnce!server(startServer()); + } + + private struct Request(T) + { + string hdrs; + immutable(T)[] bdy; + } + + private Request!T recvReq(T=char)(Socket s) + { + import std.algorithm.comparison : min; + import std.algorithm.searching : find, canFind; + import std.conv : to; + import std.regex : ctRegex, matchFirst; + + ubyte[1024] tmp=void; + ubyte[] buf; + + while (true) + { + auto nbytes = s.receive(tmp[]); + assert(nbytes >= 0); + + immutable beg = buf.length > 3 ? buf.length - 3 : 0; + buf ~= tmp[0 .. nbytes]; + auto bdy = buf[beg .. $].find(cast(ubyte[])"\r\n\r\n"); + if (bdy.empty) + continue; + + auto hdrs = cast(string) buf[0 .. $ - bdy.length]; + bdy.popFrontN(4); + // no support for chunked transfer-encoding + if (auto m = hdrs.matchFirst(ctRegex!(`Content-Length: ([0-9]+)`, "i"))) + { + import std.uni : asUpperCase; + if (hdrs.asUpperCase.canFind("EXPECT: 100-CONTINUE")) + s.send(httpContinue); + + size_t remain = m.captures[1].to!size_t - bdy.length; + while (remain) + { + nbytes = s.receive(tmp[0 .. min(remain, $)]); + assert(nbytes >= 0); + buf ~= tmp[0 .. nbytes]; + remain -= nbytes; + } + } + else + { + assert(bdy.empty); + } + bdy = buf[hdrs.length + 4 .. $]; + return typeof(return)(hdrs, cast(immutable(T)[])bdy); + } + } + + private string httpOK(string msg) + { + import std.conv : to; + + return "HTTP/1.1 200 OK\r\n"~ + "Content-Type: text/plain\r\n"~ + "Content-Length: "~msg.length.to!string~"\r\n"~ + "\r\n"~ + msg; + } + + private string httpOK() + { + return "HTTP/1.1 200 OK\r\n"~ + "Content-Length: 0\r\n"~ + "\r\n"; + } + + private string httpNotFound() + { + return "HTTP/1.1 404 Not Found\r\n"~ + "Content-Length: 0\r\n"~ + "\r\n"; + } + + private enum httpContinue = "HTTP/1.1 100 Continue\r\n\r\n"; +} +version (StdDdoc) import std.stdio; + +// Default data timeout for Protocols +private enum _defaultDataTimeout = dur!"minutes"(2); + +/** +Macros: + +CALLBACK_PARAMS = $(TABLE , + $(DDOC_PARAM_ROW + $(DDOC_PARAM_ID $(DDOC_PARAM dlTotal)) + $(DDOC_PARAM_DESC total bytes to download) + ) + $(DDOC_PARAM_ROW + $(DDOC_PARAM_ID $(DDOC_PARAM dlNow)) + $(DDOC_PARAM_DESC currently downloaded bytes) + ) + $(DDOC_PARAM_ROW + $(DDOC_PARAM_ID $(DDOC_PARAM ulTotal)) + $(DDOC_PARAM_DESC total bytes to upload) + ) + $(DDOC_PARAM_ROW + $(DDOC_PARAM_ID $(DDOC_PARAM ulNow)) + $(DDOC_PARAM_DESC currently uploaded bytes) + ) +) +*/ + +/** Connection type used when the URL should be used to auto detect the protocol. + * + * This struct is used as placeholder for the connection parameter when calling + * the high level API and the connection type (HTTP/FTP) should be guessed by + * inspecting the URL parameter. + * + * The rules for guessing the protocol are: + * 1, if URL starts with ftp://, ftps:// or ftp. then FTP connection is assumed. + * 2, HTTP connection otherwise. + * + * Example: + * --- + * import std.net.curl; + * // Two requests below will do the same. + * string content; + * + * // Explicit connection provided + * content = get!HTTP("dlang.org"); + * + * // Guess connection type by looking at the URL + * content = get!AutoProtocol("ftp://foo.com/file"); + * // and since AutoProtocol is default this is the same as + * content = get("ftp://foo.com/file"); + * // and will end up detecting FTP from the url and be the same as + * content = get!FTP("ftp://foo.com/file"); + * --- + */ +struct AutoProtocol { } + +// Returns true if the url points to an FTP resource +private bool isFTPUrl(const(char)[] url) +{ + import std.algorithm.searching : startsWith; + import std.uni : toLower; + + return startsWith(url.toLower(), "ftp://", "ftps://", "ftp.") != 0; +} + +// Is true if the Conn type is a valid Curl Connection type. +private template isCurlConn(Conn) +{ + enum auto isCurlConn = is(Conn : HTTP) || + is(Conn : FTP) || is(Conn : AutoProtocol); +} + +/** HTTP/FTP download to local file system. + * + * Params: + * url = resource to download + * saveToPath = path to store the downloaded content on local disk + * conn = connection to use e.g. FTP or HTTP. The default AutoProtocol will + * guess connection type and create a new instance for this call only. + * + * Example: + * ---- + * import std.net.curl; + * download("d-lang.appspot.com/testUrl2", "/tmp/downloaded-http-file"); + * ---- + */ +void download(Conn = AutoProtocol)(const(char)[] url, string saveToPath, Conn conn = Conn()) +if (isCurlConn!Conn) +{ + static if (is(Conn : HTTP) || is(Conn : FTP)) + { + import std.stdio : File; + conn.url = url; + auto f = File(saveToPath, "wb"); + conn.onReceive = (ubyte[] data) { f.rawWrite(data); return data.length; }; + conn.perform(); + } + else + { + if (isFTPUrl(url)) + return download!FTP(url, saveToPath, FTP()); + else + return download!HTTP(url, saveToPath, HTTP()); + } +} + +@system unittest +{ + import std.algorithm.searching : canFind; + static import std.file; + + foreach (host; [testServer.addr, "http://"~testServer.addr]) + { + testServer.handle((s) { + assert(s.recvReq.hdrs.canFind("GET /")); + s.send(httpOK("Hello world")); + }); + auto fn = std.file.deleteme; + scope (exit) std.file.remove(fn); + download(host, fn); + assert(std.file.readText(fn) == "Hello world"); + } +} + +/** Upload file from local files system using the HTTP or FTP protocol. + * + * Params: + * loadFromPath = path load data from local disk. + * url = resource to upload to + * conn = connection to use e.g. FTP or HTTP. The default AutoProtocol will + * guess connection type and create a new instance for this call only. + * + * Example: + * ---- + * import std.net.curl; + * upload("/tmp/downloaded-ftp-file", "ftp.digitalmars.com/sieve.ds"); + * upload("/tmp/downloaded-http-file", "d-lang.appspot.com/testUrl2"); + * ---- + */ +void upload(Conn = AutoProtocol)(string loadFromPath, const(char)[] url, Conn conn = Conn()) +if (isCurlConn!Conn) +{ + static if (is(Conn : HTTP)) + { + conn.url = url; + conn.method = HTTP.Method.put; + } + else static if (is(Conn : FTP)) + { + conn.url = url; + conn.handle.set(CurlOption.upload, 1L); + } + else + { + if (isFTPUrl(url)) + return upload!FTP(loadFromPath, url, FTP()); + else + return upload!HTTP(loadFromPath, url, HTTP()); + } + + static if (is(Conn : HTTP) || is(Conn : FTP)) + { + import std.stdio : File; + auto f = File(loadFromPath, "rb"); + conn.onSend = buf => f.rawRead(buf).length; + immutable sz = f.size; + if (sz != ulong.max) + conn.contentLength = sz; + conn.perform(); + } +} + +@system unittest +{ + import std.algorithm.searching : canFind; + static import std.file; + + foreach (host; [testServer.addr, "http://"~testServer.addr]) + { + auto fn = std.file.deleteme; + scope (exit) std.file.remove(fn); + std.file.write(fn, "upload data\n"); + testServer.handle((s) { + auto req = s.recvReq; + assert(req.hdrs.canFind("PUT /path")); + assert(req.bdy.canFind("upload data")); + s.send(httpOK()); + }); + upload(fn, host ~ "/path"); + } +} + +/** HTTP/FTP get content. + * + * Params: + * url = resource to get + * conn = connection to use e.g. FTP or HTTP. The default AutoProtocol will + * guess connection type and create a new instance for this call only. + * + * The template parameter $(D T) specifies the type to return. Possible values + * are $(D char) and $(D ubyte) to return $(D char[]) or $(D ubyte[]). If asking + * for $(D char), content will be converted from the connection character set + * (specified in HTTP response headers or FTP connection properties, both ISO-8859-1 + * by default) to UTF-8. + * + * Example: + * ---- + * import std.net.curl; + * auto content = get("d-lang.appspot.com/testUrl2"); + * ---- + * + * Returns: + * A T[] range containing the content of the resource pointed to by the URL. + * + * Throws: + * + * $(D CurlException) on error. + * + * See_Also: $(LREF HTTP.Method) + */ +T[] get(Conn = AutoProtocol, T = char)(const(char)[] url, Conn conn = Conn()) +if ( isCurlConn!Conn && (is(T == char) || is(T == ubyte)) ) +{ + static if (is(Conn : HTTP)) + { + conn.method = HTTP.Method.get; + return _basicHTTP!(T)(url, "", conn); + + } + else static if (is(Conn : FTP)) + { + return _basicFTP!(T)(url, "", conn); + } + else + { + if (isFTPUrl(url)) + return get!(FTP,T)(url, FTP()); + else + return get!(HTTP,T)(url, HTTP()); + } +} + +@system unittest +{ + import std.algorithm.searching : canFind; + + foreach (host; [testServer.addr, "http://"~testServer.addr]) + { + testServer.handle((s) { + assert(s.recvReq.hdrs.canFind("GET /path")); + s.send(httpOK("GETRESPONSE")); + }); + auto res = get(host ~ "/path"); + assert(res == "GETRESPONSE"); + } +} + + +/** HTTP post content. + * + * Params: + * url = resource to post to + * postDict = data to send as the body of the request. An associative array + * of $(D string) is accepted and will be encoded using + * www-form-urlencoding + * postData = data to send as the body of the request. An array + * of an arbitrary type is accepted and will be cast to ubyte[] + * before sending it. + * conn = HTTP connection to use + * T = The template parameter $(D T) specifies the type to return. Possible values + * are $(D char) and $(D ubyte) to return $(D char[]) or $(D ubyte[]). If asking + * for $(D char), content will be converted from the connection character set + * (specified in HTTP response headers or FTP connection properties, both ISO-8859-1 + * by default) to UTF-8. + * + * Examples: + * ---- + * import std.net.curl; + * + * auto content1 = post("d-lang.appspot.com/testUrl2", ["name1" : "value1", "name2" : "value2"]); + * auto content2 = post("d-lang.appspot.com/testUrl2", [1,2,3,4]); + * ---- + * + * Returns: + * A T[] range containing the content of the resource pointed to by the URL. + * + * See_Also: $(LREF HTTP.Method) + */ +T[] post(T = char, PostUnit)(const(char)[] url, const(PostUnit)[] postData, HTTP conn = HTTP()) +if (is(T == char) || is(T == ubyte)) +{ + conn.method = HTTP.Method.post; + return _basicHTTP!(T)(url, postData, conn); +} + +@system unittest +{ + import std.algorithm.searching : canFind; + + foreach (host; [testServer.addr, "http://"~testServer.addr]) + { + testServer.handle((s) { + auto req = s.recvReq; + assert(req.hdrs.canFind("POST /path")); + assert(req.bdy.canFind("POSTBODY")); + s.send(httpOK("POSTRESPONSE")); + }); + auto res = post(host ~ "/path", "POSTBODY"); + assert(res == "POSTRESPONSE"); + } +} + +@system unittest +{ + import std.algorithm.searching : canFind; + + auto data = new ubyte[](256); + foreach (i, ref ub; data) + ub = cast(ubyte) i; + + testServer.handle((s) { + auto req = s.recvReq!ubyte; + assert(req.bdy.canFind(cast(ubyte[])[0, 1, 2, 3, 4])); + assert(req.bdy.canFind(cast(ubyte[])[253, 254, 255])); + s.send(httpOK(cast(ubyte[])[17, 27, 35, 41])); + }); + auto res = post!ubyte(testServer.addr, data); + assert(res == cast(ubyte[])[17, 27, 35, 41]); +} + +/// ditto +T[] post(T = char)(const(char)[] url, string[string] postDict, HTTP conn = HTTP()) +if (is(T == char) || is(T == ubyte)) +{ + import std.uri : urlEncode; + + return post(url, urlEncode(postDict), conn); +} + +@system unittest +{ + foreach (host; [testServer.addr, "http://" ~ testServer.addr]) + { + testServer.handle((s) { + auto req = s.recvReq!char; + s.send(httpOK(req.bdy)); + }); + auto res = post(host ~ "/path", ["name1" : "value1", "name2" : "value2"]); + assert(res == "name1=value1&name2=value2"); + } +} + +/** HTTP/FTP put content. + * + * Params: + * url = resource to put + * putData = data to send as the body of the request. An array + * of an arbitrary type is accepted and will be cast to ubyte[] + * before sending it. + * conn = connection to use e.g. FTP or HTTP. The default AutoProtocol will + * guess connection type and create a new instance for this call only. + * + * The template parameter $(D T) specifies the type to return. Possible values + * are $(D char) and $(D ubyte) to return $(D char[]) or $(D ubyte[]). If asking + * for $(D char), content will be converted from the connection character set + * (specified in HTTP response headers or FTP connection properties, both ISO-8859-1 + * by default) to UTF-8. + * + * Example: + * ---- + * import std.net.curl; + * auto content = put("d-lang.appspot.com/testUrl2", + * "Putting this data"); + * ---- + * + * Returns: + * A T[] range containing the content of the resource pointed to by the URL. + * + * See_Also: $(LREF HTTP.Method) + */ +T[] put(Conn = AutoProtocol, T = char, PutUnit)(const(char)[] url, const(PutUnit)[] putData, + Conn conn = Conn()) +if ( isCurlConn!Conn && (is(T == char) || is(T == ubyte)) ) +{ + static if (is(Conn : HTTP)) + { + conn.method = HTTP.Method.put; + return _basicHTTP!(T)(url, putData, conn); + } + else static if (is(Conn : FTP)) + { + return _basicFTP!(T)(url, putData, conn); + } + else + { + if (isFTPUrl(url)) + return put!(FTP,T)(url, putData, FTP()); + else + return put!(HTTP,T)(url, putData, HTTP()); + } +} + +@system unittest +{ + import std.algorithm.searching : canFind; + + foreach (host; [testServer.addr, "http://"~testServer.addr]) + { + testServer.handle((s) { + auto req = s.recvReq; + assert(req.hdrs.canFind("PUT /path")); + assert(req.bdy.canFind("PUTBODY")); + s.send(httpOK("PUTRESPONSE")); + }); + auto res = put(host ~ "/path", "PUTBODY"); + assert(res == "PUTRESPONSE"); + } +} + + +/** HTTP/FTP delete content. + * + * Params: + * url = resource to delete + * conn = connection to use e.g. FTP or HTTP. The default AutoProtocol will + * guess connection type and create a new instance for this call only. + * + * Example: + * ---- + * import std.net.curl; + * del("d-lang.appspot.com/testUrl2"); + * ---- + * + * See_Also: $(LREF HTTP.Method) + */ +void del(Conn = AutoProtocol)(const(char)[] url, Conn conn = Conn()) +if (isCurlConn!Conn) +{ + static if (is(Conn : HTTP)) + { + conn.method = HTTP.Method.del; + _basicHTTP!char(url, cast(void[]) null, conn); + } + else static if (is(Conn : FTP)) + { + import std.algorithm.searching : findSplitAfter; + import std.conv : text; + + auto trimmed = url.findSplitAfter("ftp://")[1]; + auto t = trimmed.findSplitAfter("/"); + enum minDomainNameLength = 3; + enforce!CurlException(t[0].length > minDomainNameLength, + text("Invalid FTP URL for delete ", url)); + conn.url = t[0]; + + enforce!CurlException(!t[1].empty, + text("No filename specified to delete for URL ", url)); + conn.addCommand("DELE " ~ t[1]); + conn.perform(); + } + else + { + if (isFTPUrl(url)) + return del!FTP(url, FTP()); + else + return del!HTTP(url, HTTP()); + } +} + +@system unittest +{ + import std.algorithm.searching : canFind; + + foreach (host; [testServer.addr, "http://"~testServer.addr]) + { + testServer.handle((s) { + auto req = s.recvReq; + assert(req.hdrs.canFind("DELETE /path")); + s.send(httpOK()); + }); + del(host ~ "/path"); + } +} + + +/** HTTP options request. + * + * Params: + * url = resource make a option call to + * conn = connection to use e.g. FTP or HTTP. The default AutoProtocol will + * guess connection type and create a new instance for this call only. + * + * The template parameter $(D T) specifies the type to return. Possible values + * are $(D char) and $(D ubyte) to return $(D char[]) or $(D ubyte[]). + * + * Example: + * ---- + * import std.net.curl; + * auto http = HTTP(); + * options("d-lang.appspot.com/testUrl2", http); + * writeln("Allow set to " ~ http.responseHeaders["Allow"]); + * ---- + * + * Returns: + * A T[] range containing the options of the resource pointed to by the URL. + * + * See_Also: $(LREF HTTP.Method) + */ +T[] options(T = char)(const(char)[] url, HTTP conn = HTTP()) +if (is(T == char) || is(T == ubyte)) +{ + conn.method = HTTP.Method.options; + return _basicHTTP!(T)(url, null, conn); +} + +@system unittest +{ + import std.algorithm.searching : canFind; + + testServer.handle((s) { + auto req = s.recvReq; + assert(req.hdrs.canFind("OPTIONS /path")); + s.send(httpOK("OPTIONSRESPONSE")); + }); + auto res = options(testServer.addr ~ "/path"); + assert(res == "OPTIONSRESPONSE"); +} + + +/** HTTP trace request. + * + * Params: + * url = resource make a trace call to + * conn = connection to use e.g. FTP or HTTP. The default AutoProtocol will + * guess connection type and create a new instance for this call only. + * + * The template parameter $(D T) specifies the type to return. Possible values + * are $(D char) and $(D ubyte) to return $(D char[]) or $(D ubyte[]). + * + * Example: + * ---- + * import std.net.curl; + * trace("d-lang.appspot.com/testUrl1"); + * ---- + * + * Returns: + * A T[] range containing the trace info of the resource pointed to by the URL. + * + * See_Also: $(LREF HTTP.Method) + */ +T[] trace(T = char)(const(char)[] url, HTTP conn = HTTP()) +if (is(T == char) || is(T == ubyte)) +{ + conn.method = HTTP.Method.trace; + return _basicHTTP!(T)(url, cast(void[]) null, conn); +} + +@system unittest +{ + import std.algorithm.searching : canFind; + + testServer.handle((s) { + auto req = s.recvReq; + assert(req.hdrs.canFind("TRACE /path")); + s.send(httpOK("TRACERESPONSE")); + }); + auto res = trace(testServer.addr ~ "/path"); + assert(res == "TRACERESPONSE"); +} + + +/** HTTP connect request. + * + * Params: + * url = resource make a connect to + * conn = HTTP connection to use + * + * The template parameter $(D T) specifies the type to return. Possible values + * are $(D char) and $(D ubyte) to return $(D char[]) or $(D ubyte[]). + * + * Example: + * ---- + * import std.net.curl; + * connect("d-lang.appspot.com/testUrl1"); + * ---- + * + * Returns: + * A T[] range containing the connect info of the resource pointed to by the URL. + * + * See_Also: $(LREF HTTP.Method) + */ +T[] connect(T = char)(const(char)[] url, HTTP conn = HTTP()) +if (is(T == char) || is(T == ubyte)) +{ + conn.method = HTTP.Method.connect; + return _basicHTTP!(T)(url, cast(void[]) null, conn); +} + +@system unittest +{ + import std.algorithm.searching : canFind; + + testServer.handle((s) { + auto req = s.recvReq; + assert(req.hdrs.canFind("CONNECT /path")); + s.send(httpOK("CONNECTRESPONSE")); + }); + auto res = connect(testServer.addr ~ "/path"); + assert(res == "CONNECTRESPONSE"); +} + + +/** HTTP patch content. + * + * Params: + * url = resource to patch + * patchData = data to send as the body of the request. An array + * of an arbitrary type is accepted and will be cast to ubyte[] + * before sending it. + * conn = HTTP connection to use + * + * The template parameter $(D T) specifies the type to return. Possible values + * are $(D char) and $(D ubyte) to return $(D char[]) or $(D ubyte[]). + * + * Example: + * ---- + * auto http = HTTP(); + * http.addRequestHeader("Content-Type", "application/json"); + * auto content = patch("d-lang.appspot.com/testUrl2", `{"title": "Patched Title"}`, http); + * ---- + * + * Returns: + * A T[] range containing the content of the resource pointed to by the URL. + * + * See_Also: $(LREF HTTP.Method) + */ +T[] patch(T = char, PatchUnit)(const(char)[] url, const(PatchUnit)[] patchData, + HTTP conn = HTTP()) +if (is(T == char) || is(T == ubyte)) +{ + conn.method = HTTP.Method.patch; + return _basicHTTP!(T)(url, patchData, conn); +} + +@system unittest +{ + import std.algorithm.searching : canFind; + + testServer.handle((s) { + auto req = s.recvReq; + assert(req.hdrs.canFind("PATCH /path")); + assert(req.bdy.canFind("PATCHBODY")); + s.send(httpOK("PATCHRESPONSE")); + }); + auto res = patch(testServer.addr ~ "/path", "PATCHBODY"); + assert(res == "PATCHRESPONSE"); +} + + +/* + * Helper function for the high level interface. + * + * It performs an HTTP request using the client which must have + * been setup correctly before calling this function. + */ +private auto _basicHTTP(T)(const(char)[] url, const(void)[] sendData, HTTP client) +{ + import std.algorithm.comparison : min; + import std.format : format; + + immutable doSend = sendData !is null && + (client.method == HTTP.Method.post || + client.method == HTTP.Method.put || + client.method == HTTP.Method.patch); + + scope (exit) + { + client.onReceiveHeader = null; + client.onReceiveStatusLine = null; + client.onReceive = null; + + if (doSend) + { + client.onSend = null; + client.handle.onSeek = null; + client.contentLength = 0; + } + } + client.url = url; + HTTP.StatusLine statusLine; + import std.array : appender; + auto content = appender!(ubyte[])(); + client.onReceive = (ubyte[] data) + { + content ~= data; + return data.length; + }; + + if (doSend) + { + client.contentLength = sendData.length; + auto remainingData = sendData; + client.onSend = delegate size_t(void[] buf) + { + size_t minLen = min(buf.length, remainingData.length); + if (minLen == 0) return 0; + buf[0 .. minLen] = remainingData[0 .. minLen]; + remainingData = remainingData[minLen..$]; + return minLen; + }; + client.handle.onSeek = delegate(long offset, CurlSeekPos mode) + { + switch (mode) + { + case CurlSeekPos.set: + remainingData = sendData[cast(size_t) offset..$]; + return CurlSeek.ok; + default: + // As of curl 7.18.0, libcurl will not pass + // anything other than CurlSeekPos.set. + return CurlSeek.cantseek; + } + }; + } + + client.onReceiveHeader = (in char[] key, + in char[] value) + { + if (key == "content-length") + { + import std.conv : to; + content.reserve(value.to!size_t); + } + }; + client.onReceiveStatusLine = (HTTP.StatusLine l) { statusLine = l; }; + client.perform(); + enforce(statusLine.code / 100 == 2, new HTTPStatusException(statusLine.code, + format("HTTP request returned status code %d (%s)", statusLine.code, statusLine.reason))); + + return _decodeContent!T(content.data, client.p.charset); +} + +@system unittest +{ + import std.algorithm.searching : canFind; + + testServer.handle((s) { + auto req = s.recvReq; + assert(req.hdrs.canFind("GET /path")); + s.send(httpNotFound()); + }); + auto e = collectException!HTTPStatusException(get(testServer.addr ~ "/path")); + assert(e.msg == "HTTP request returned status code 404 (Not Found)"); + assert(e.status == 404); +} + +// Bugzilla 14760 - content length must be reset after post +@system unittest +{ + import std.algorithm.searching : canFind; + + testServer.handle((s) { + auto req = s.recvReq; + assert(req.hdrs.canFind("POST /")); + assert(req.bdy.canFind("POSTBODY")); + s.send(httpOK("POSTRESPONSE")); + + req = s.recvReq; + assert(req.hdrs.canFind("TRACE /")); + assert(req.bdy.empty); + s.blocking = false; + ubyte[6] buf = void; + assert(s.receive(buf[]) < 0); + s.send(httpOK("TRACERESPONSE")); + }); + auto http = HTTP(); + auto res = post(testServer.addr, "POSTBODY", http); + assert(res == "POSTRESPONSE"); + res = trace(testServer.addr, http); + assert(res == "TRACERESPONSE"); +} + +@system unittest // charset detection and transcoding to T +{ + testServer.handle((s) { + s.send("HTTP/1.1 200 OK\r\n"~ + "Content-Length: 4\r\n"~ + "Content-Type: text/plain; charset=utf-8\r\n" ~ + "\r\n" ~ + "äbc"); + }); + auto client = HTTP(); + auto result = _basicHTTP!char(testServer.addr, "", client); + assert(result == "äbc"); + + testServer.handle((s) { + s.send("HTTP/1.1 200 OK\r\n"~ + "Content-Length: 3\r\n"~ + "Content-Type: text/plain; charset=iso-8859-1\r\n" ~ + "\r\n" ~ + 0xE4 ~ "bc"); + }); + client = HTTP(); + result = _basicHTTP!char(testServer.addr, "", client); + assert(result == "äbc"); +} + +/* + * Helper function for the high level interface. + * + * It performs an FTP request using the client which must have + * been setup correctly before calling this function. + */ +private auto _basicFTP(T)(const(char)[] url, const(void)[] sendData, FTP client) +{ + import std.algorithm.comparison : min; + + scope (exit) + { + client.onReceive = null; + if (!sendData.empty) + client.onSend = null; + } + + ubyte[] content; + + if (client.encoding.empty) + client.encoding = "ISO-8859-1"; + + client.url = url; + client.onReceive = (ubyte[] data) + { + content ~= data; + return data.length; + }; + + if (!sendData.empty) + { + client.handle.set(CurlOption.upload, 1L); + client.onSend = delegate size_t(void[] buf) + { + size_t minLen = min(buf.length, sendData.length); + if (minLen == 0) return 0; + buf[0 .. minLen] = sendData[0 .. minLen]; + sendData = sendData[minLen..$]; + return minLen; + }; + } + + client.perform(); + + return _decodeContent!T(content, client.encoding); +} + +/* Used by _basicHTTP() and _basicFTP() to decode ubyte[] to + * correct string format + */ +private auto _decodeContent(T)(ubyte[] content, string encoding) +{ + static if (is(T == ubyte)) + { + return content; + } + else + { + import std.format : format; + + // Optimally just return the utf8 encoded content + if (encoding == "UTF-8") + return cast(char[])(content); + + // The content has to be re-encoded to utf8 + auto scheme = EncodingScheme.create(encoding); + enforce!CurlException(scheme !is null, + format("Unknown encoding '%s'", encoding)); + + auto strInfo = decodeString(content, scheme); + enforce!CurlException(strInfo[0] != size_t.max, + format("Invalid encoding sequence for encoding '%s'", + encoding)); + + return strInfo[1]; + } +} + +alias KeepTerminator = Flag!"keepTerminator"; +/+ +struct ByLineBuffer(Char) +{ + bool linePresent; + bool EOF; + Char[] buffer; + ubyte[] decodeRemainder; + + bool append(const(ubyte)[] data) + { + byLineBuffer ~= data; + } + + @property bool linePresent() + { + return byLinePresent; + } + + Char[] get() + { + if (!linePresent) + { + // Decode ubyte[] into Char[] until a Terminator is found. + // If not Terminator is found and EOF is false then raise an + // exception. + } + return byLineBuffer; + } + +} +++/ +/** HTTP/FTP fetch content as a range of lines. + * + * A range of lines is returned when the request is complete. If the method or + * other request properties is to be customized then set the $(D conn) parameter + * with a HTTP/FTP instance that has these properties set. + * + * Example: + * ---- + * import std.net.curl, std.stdio; + * foreach (line; byLine("dlang.org")) + * writeln(line); + * ---- + * + * Params: + * url = The url to receive content from + * keepTerminator = $(D Yes.keepTerminator) signals that the line terminator should be + * returned as part of the lines in the range. + * terminator = The character that terminates a line + * conn = The connection to use e.g. HTTP or FTP. + * + * Returns: + * A range of Char[] with the content of the resource pointer to by the URL + */ +auto byLine(Conn = AutoProtocol, Terminator = char, Char = char) + (const(char)[] url, KeepTerminator keepTerminator = No.keepTerminator, + Terminator terminator = '\n', Conn conn = Conn()) +if (isCurlConn!Conn && isSomeChar!Char && isSomeChar!Terminator) +{ + static struct SyncLineInputRange + { + + private Char[] lines; + private Char[] current; + private bool currentValid; + private bool keepTerminator; + private Terminator terminator; + + this(Char[] lines, bool kt, Terminator terminator) + { + this.lines = lines; + this.keepTerminator = kt; + this.terminator = terminator; + currentValid = true; + popFront(); + } + + @property @safe bool empty() + { + return !currentValid; + } + + @property @safe Char[] front() + { + enforce!CurlException(currentValid, "Cannot call front() on empty range"); + return current; + } + + void popFront() + { + import std.algorithm.searching : findSplitAfter, findSplit; + + enforce!CurlException(currentValid, "Cannot call popFront() on empty range"); + if (lines.empty) + { + currentValid = false; + return; + } + + if (keepTerminator) + { + auto r = findSplitAfter(lines, [ terminator ]); + if (r[0].empty) + { + current = r[1]; + lines = r[0]; + } + else + { + current = r[0]; + lines = r[1]; + } + } + else + { + auto r = findSplit(lines, [ terminator ]); + current = r[0]; + lines = r[2]; + } + } + } + + auto result = _getForRange!Char(url, conn); + return SyncLineInputRange(result, keepTerminator == Yes.keepTerminator, terminator); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + + foreach (host; [testServer.addr, "http://"~testServer.addr]) + { + testServer.handle((s) { + auto req = s.recvReq; + s.send(httpOK("Line1\nLine2\nLine3")); + }); + assert(byLine(host).equal(["Line1", "Line2", "Line3"])); + } +} + +/** HTTP/FTP fetch content as a range of chunks. + * + * A range of chunks is returned when the request is complete. If the method or + * other request properties is to be customized then set the $(D conn) parameter + * with a HTTP/FTP instance that has these properties set. + * + * Example: + * ---- + * import std.net.curl, std.stdio; + * foreach (chunk; byChunk("dlang.org", 100)) + * writeln(chunk); // chunk is ubyte[100] + * ---- + * + * Params: + * url = The url to receive content from + * chunkSize = The size of each chunk + * conn = The connection to use e.g. HTTP or FTP. + * + * Returns: + * A range of ubyte[chunkSize] with the content of the resource pointer to by the URL + */ +auto byChunk(Conn = AutoProtocol) + (const(char)[] url, size_t chunkSize = 1024, Conn conn = Conn()) +if (isCurlConn!(Conn)) +{ + static struct SyncChunkInputRange + { + private size_t chunkSize; + private ubyte[] _bytes; + private size_t offset; + + this(ubyte[] bytes, size_t chunkSize) + { + this._bytes = bytes; + this.chunkSize = chunkSize; + } + + @property @safe auto empty() + { + return offset == _bytes.length; + } + + @property ubyte[] front() + { + size_t nextOffset = offset + chunkSize; + if (nextOffset > _bytes.length) nextOffset = _bytes.length; + return _bytes[offset .. nextOffset]; + } + + @safe void popFront() + { + offset += chunkSize; + if (offset > _bytes.length) offset = _bytes.length; + } + } + + auto result = _getForRange!ubyte(url, conn); + return SyncChunkInputRange(result, chunkSize); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + + foreach (host; [testServer.addr, "http://"~testServer.addr]) + { + testServer.handle((s) { + auto req = s.recvReq; + s.send(httpOK(cast(ubyte[])[0, 1, 2, 3, 4, 5])); + }); + assert(byChunk(host, 2).equal([[0, 1], [2, 3], [4, 5]])); + } +} + +private T[] _getForRange(T,Conn)(const(char)[] url, Conn conn) +{ + static if (is(Conn : HTTP)) + { + conn.method = conn.method == HTTP.Method.undefined ? HTTP.Method.get : conn.method; + return _basicHTTP!(T)(url, null, conn); + } + else static if (is(Conn : FTP)) + { + return _basicFTP!(T)(url, null, conn); + } + else + { + if (isFTPUrl(url)) + return get!(FTP,T)(url, FTP()); + else + return get!(HTTP,T)(url, HTTP()); + } +} + +/* + Main thread part of the message passing protocol used for all async + curl protocols. + */ +private mixin template WorkerThreadProtocol(Unit, alias units) +{ + @property bool empty() + { + tryEnsureUnits(); + return state == State.done; + } + + @property Unit[] front() + { + import std.format : format; + tryEnsureUnits(); + assert(state == State.gotUnits, + format("Expected %s but got $s", + State.gotUnits, state)); + return units; + } + + void popFront() + { + import std.format : format; + tryEnsureUnits(); + assert(state == State.gotUnits, + format("Expected %s but got $s", + State.gotUnits, state)); + state = State.needUnits; + // Send to worker thread for buffer reuse + workerTid.send(cast(immutable(Unit)[]) units); + units = null; + } + + /** Wait for duration or until data is available and return true if data is + available + */ + bool wait(Duration d) + { + import std.datetime.stopwatch : StopWatch; + + if (state == State.gotUnits) + return true; + + enum noDur = dur!"hnsecs"(0); + StopWatch sw; + sw.start(); + while (state != State.gotUnits && d > noDur) + { + final switch (state) + { + case State.needUnits: + receiveTimeout(d, + (Tid origin, CurlMessage!(immutable(Unit)[]) _data) + { + if (origin != workerTid) + return false; + units = cast(Unit[]) _data.data; + state = State.gotUnits; + return true; + }, + (Tid origin, CurlMessage!bool f) + { + if (origin != workerTid) + return false; + state = state.done; + return true; + } + ); + break; + case State.gotUnits: return true; + case State.done: + return false; + } + d -= sw.peek(); + sw.reset(); + } + return state == State.gotUnits; + } + + enum State + { + needUnits, + gotUnits, + done + } + State state; + + void tryEnsureUnits() + { + while (true) + { + final switch (state) + { + case State.needUnits: + receive( + (Tid origin, CurlMessage!(immutable(Unit)[]) _data) + { + if (origin != workerTid) + return false; + units = cast(Unit[]) _data.data; + state = State.gotUnits; + return true; + }, + (Tid origin, CurlMessage!bool f) + { + if (origin != workerTid) + return false; + state = state.done; + return true; + } + ); + break; + case State.gotUnits: return; + case State.done: + return; + } + } + } +} + +// @@@@BUG 15831@@@@ +// this should be inside byLineAsync +// Range that reads one line at a time asynchronously. +private static struct AsyncLineInputRange(Char) +{ + private Char[] line; + mixin WorkerThreadProtocol!(Char, line); + + private Tid workerTid; + private State running; + + private this(Tid tid, size_t transmitBuffers, size_t bufferSize) + { + workerTid = tid; + state = State.needUnits; + + // Send buffers to other thread for it to use. Since no mechanism is in + // place for moving ownership a cast to shared is done here and casted + // back to non-shared in the receiving end. + foreach (i ; 0 .. transmitBuffers) + { + auto arr = new Char[](bufferSize); + workerTid.send(cast(immutable(Char[]))arr); + } + } +} + +/** HTTP/FTP fetch content as a range of lines asynchronously. + * + * A range of lines is returned immediately and the request that fetches the + * lines is performed in another thread. If the method or other request + * properties is to be customized then set the $(D conn) parameter with a + * HTTP/FTP instance that has these properties set. + * + * If $(D postData) is non-_null the method will be set to $(D post) for HTTP + * requests. + * + * The background thread will buffer up to transmitBuffers number of lines + * before it stops receiving data from network. When the main thread reads the + * lines from the range it frees up buffers and allows for the background thread + * to receive more data from the network. + * + * If no data is available and the main thread accesses the range it will block + * until data becomes available. An exception to this is the $(D wait(Duration)) method on + * the $(LREF AsyncLineInputRange). This method will wait at maximum for the + * specified duration and return true if data is available. + * + * Example: + * ---- + * import std.net.curl, std.stdio; + * // Get some pages in the background + * auto range1 = byLineAsync("www.google.com"); + * auto range2 = byLineAsync("www.wikipedia.org"); + * foreach (line; byLineAsync("dlang.org")) + * writeln(line); + * + * // Lines already fetched in the background and ready + * foreach (line; range1) writeln(line); + * foreach (line; range2) writeln(line); + * ---- + * + * ---- + * import std.net.curl, std.stdio; + * // Get a line in a background thread and wait in + * // main thread for 2 seconds for it to arrive. + * auto range3 = byLineAsync("dlang.com"); + * if (range3.wait(dur!"seconds"(2))) + * writeln(range3.front); + * else + * writeln("No line received after 2 seconds!"); + * ---- + * + * Params: + * url = The url to receive content from + * postData = Data to HTTP Post + * keepTerminator = $(D Yes.keepTerminator) signals that the line terminator should be + * returned as part of the lines in the range. + * terminator = The character that terminates a line + * transmitBuffers = The number of lines buffered asynchronously + * conn = The connection to use e.g. HTTP or FTP. + * + * Returns: + * A range of Char[] with the content of the resource pointer to by the + * URL. + */ +auto byLineAsync(Conn = AutoProtocol, Terminator = char, Char = char, PostUnit) + (const(char)[] url, const(PostUnit)[] postData, + KeepTerminator keepTerminator = No.keepTerminator, + Terminator terminator = '\n', + size_t transmitBuffers = 10, Conn conn = Conn()) +if (isCurlConn!Conn && isSomeChar!Char && isSomeChar!Terminator) +{ + static if (is(Conn : AutoProtocol)) + { + if (isFTPUrl(url)) + return byLineAsync(url, postData, keepTerminator, + terminator, transmitBuffers, FTP()); + else + return byLineAsync(url, postData, keepTerminator, + terminator, transmitBuffers, HTTP()); + } + else + { + // 50 is just an arbitrary number for now + setMaxMailboxSize(thisTid, 50, OnCrowding.block); + auto tid = spawn(&_spawnAsync!(Conn, Char, Terminator)); + tid.send(thisTid); + tid.send(terminator); + tid.send(keepTerminator == Yes.keepTerminator); + + _asyncDuplicateConnection(url, conn, postData, tid); + + return AsyncLineInputRange!Char(tid, transmitBuffers, + Conn.defaultAsyncStringBufferSize); + } +} + +/// ditto +auto byLineAsync(Conn = AutoProtocol, Terminator = char, Char = char) + (const(char)[] url, KeepTerminator keepTerminator = No.keepTerminator, + Terminator terminator = '\n', + size_t transmitBuffers = 10, Conn conn = Conn()) +{ + static if (is(Conn : AutoProtocol)) + { + if (isFTPUrl(url)) + return byLineAsync(url, cast(void[]) null, keepTerminator, + terminator, transmitBuffers, FTP()); + else + return byLineAsync(url, cast(void[]) null, keepTerminator, + terminator, transmitBuffers, HTTP()); + } + else + { + return byLineAsync(url, cast(void[]) null, keepTerminator, + terminator, transmitBuffers, conn); + } +} + +@system unittest +{ + import std.algorithm.comparison : equal; + + foreach (host; [testServer.addr, "http://"~testServer.addr]) + { + testServer.handle((s) { + auto req = s.recvReq; + s.send(httpOK("Line1\nLine2\nLine3")); + }); + assert(byLineAsync(host).equal(["Line1", "Line2", "Line3"])); + } +} + +// @@@@BUG 15831@@@@ +// this should be inside byLineAsync +// Range that reads one chunk at a time asynchronously. +private static struct AsyncChunkInputRange +{ + private ubyte[] chunk; + mixin WorkerThreadProtocol!(ubyte, chunk); + + private Tid workerTid; + private State running; + + private this(Tid tid, size_t transmitBuffers, size_t chunkSize) + { + workerTid = tid; + state = State.needUnits; + + // Send buffers to other thread for it to use. Since no mechanism is in + // place for moving ownership a cast to shared is done here and a cast + // back to non-shared in the receiving end. + foreach (i ; 0 .. transmitBuffers) + { + ubyte[] arr = new ubyte[](chunkSize); + workerTid.send(cast(immutable(ubyte[]))arr); + } + } +} + +/** HTTP/FTP fetch content as a range of chunks asynchronously. + * + * A range of chunks is returned immediately and the request that fetches the + * chunks is performed in another thread. If the method or other request + * properties is to be customized then set the $(D conn) parameter with a + * HTTP/FTP instance that has these properties set. + * + * If $(D postData) is non-_null the method will be set to $(D post) for HTTP + * requests. + * + * The background thread will buffer up to transmitBuffers number of chunks + * before is stops receiving data from network. When the main thread reads the + * chunks from the range it frees up buffers and allows for the background + * thread to receive more data from the network. + * + * If no data is available and the main thread access the range it will block + * until data becomes available. An exception to this is the $(D wait(Duration)) + * method on the $(LREF AsyncChunkInputRange). This method will wait at maximum for the specified + * duration and return true if data is available. + * + * Example: + * ---- + * import std.net.curl, std.stdio; + * // Get some pages in the background + * auto range1 = byChunkAsync("www.google.com", 100); + * auto range2 = byChunkAsync("www.wikipedia.org"); + * foreach (chunk; byChunkAsync("dlang.org")) + * writeln(chunk); // chunk is ubyte[100] + * + * // Chunks already fetched in the background and ready + * foreach (chunk; range1) writeln(chunk); + * foreach (chunk; range2) writeln(chunk); + * ---- + * + * ---- + * import std.net.curl, std.stdio; + * // Get a line in a background thread and wait in + * // main thread for 2 seconds for it to arrive. + * auto range3 = byChunkAsync("dlang.com", 10); + * if (range3.wait(dur!"seconds"(2))) + * writeln(range3.front); + * else + * writeln("No chunk received after 2 seconds!"); + * ---- + * + * Params: + * url = The url to receive content from + * postData = Data to HTTP Post + * chunkSize = The size of the chunks + * transmitBuffers = The number of chunks buffered asynchronously + * conn = The connection to use e.g. HTTP or FTP. + * + * Returns: + * A range of ubyte[chunkSize] with the content of the resource pointer to by + * the URL. + */ +auto byChunkAsync(Conn = AutoProtocol, PostUnit) + (const(char)[] url, const(PostUnit)[] postData, + size_t chunkSize = 1024, size_t transmitBuffers = 10, + Conn conn = Conn()) +if (isCurlConn!(Conn)) +{ + static if (is(Conn : AutoProtocol)) + { + if (isFTPUrl(url)) + return byChunkAsync(url, postData, chunkSize, + transmitBuffers, FTP()); + else + return byChunkAsync(url, postData, chunkSize, + transmitBuffers, HTTP()); + } + else + { + // 50 is just an arbitrary number for now + setMaxMailboxSize(thisTid, 50, OnCrowding.block); + auto tid = spawn(&_spawnAsync!(Conn, ubyte)); + tid.send(thisTid); + + _asyncDuplicateConnection(url, conn, postData, tid); + + return AsyncChunkInputRange(tid, transmitBuffers, chunkSize); + } +} + +/// ditto +auto byChunkAsync(Conn = AutoProtocol) + (const(char)[] url, + size_t chunkSize = 1024, size_t transmitBuffers = 10, + Conn conn = Conn()) +if (isCurlConn!(Conn)) +{ + static if (is(Conn : AutoProtocol)) + { + if (isFTPUrl(url)) + return byChunkAsync(url, cast(void[]) null, chunkSize, + transmitBuffers, FTP()); + else + return byChunkAsync(url, cast(void[]) null, chunkSize, + transmitBuffers, HTTP()); + } + else + { + return byChunkAsync(url, cast(void[]) null, chunkSize, + transmitBuffers, conn); + } +} + +@system unittest +{ + import std.algorithm.comparison : equal; + + foreach (host; [testServer.addr, "http://"~testServer.addr]) + { + testServer.handle((s) { + auto req = s.recvReq; + s.send(httpOK(cast(ubyte[])[0, 1, 2, 3, 4, 5])); + }); + assert(byChunkAsync(host, 2).equal([[0, 1], [2, 3], [4, 5]])); + } +} + + +/* Used by byLineAsync/byChunkAsync to duplicate an existing connection + * that can be used exclusively in a spawned thread. + */ +private void _asyncDuplicateConnection(Conn, PostData) + (const(char)[] url, Conn conn, PostData postData, Tid tid) +{ + // no move semantic available in std.concurrency ie. must use casting. + auto connDup = conn.dup(); + connDup.url = url; + + static if ( is(Conn : HTTP) ) + { + connDup.p.headersOut = null; + connDup.method = conn.method == HTTP.Method.undefined ? + HTTP.Method.get : conn.method; + if (postData !is null) + { + if (connDup.method == HTTP.Method.put) + { + connDup.handle.set(CurlOption.infilesize_large, + postData.length); + } + else + { + // post + connDup.method = HTTP.Method.post; + connDup.handle.set(CurlOption.postfieldsize_large, + postData.length); + } + connDup.handle.set(CurlOption.copypostfields, + cast(void*) postData.ptr); + } + tid.send(cast(ulong) connDup.handle.handle); + tid.send(connDup.method); + } + else + { + enforce!CurlException(postData is null, + "Cannot put ftp data using byLineAsync()"); + tid.send(cast(ulong) connDup.handle.handle); + tid.send(HTTP.Method.undefined); + } + connDup.p.curl.handle = null; // make sure handle is not freed +} + +/* + Mixin template for all supported curl protocols. This is the commom + functionallity such as timeouts and network interface settings. This should + really be in the HTTP/FTP/SMTP structs but the documentation tool does not + support a mixin to put its doc strings where a mixin is done. Therefore docs + in this template is copied into each of HTTP/FTP/SMTP below. +*/ +private mixin template Protocol() +{ + + /// Value to return from $(D onSend)/$(D onReceive) delegates in order to + /// pause a request + alias requestPause = CurlReadFunc.pause; + + /// Value to return from onSend delegate in order to abort a request + alias requestAbort = CurlReadFunc.abort; + + static uint defaultAsyncStringBufferSize = 100; + + /** + The curl handle used by this connection. + */ + @property ref Curl handle() return + { + return p.curl; + } + + /** + True if the instance is stopped. A stopped instance is not usable. + */ + @property bool isStopped() + { + return p.curl.stopped; + } + + /// Stop and invalidate this instance. + void shutdown() + { + p.curl.shutdown(); + } + + /** Set verbose. + This will print request information to stderr. + */ + @property void verbose(bool on) + { + p.curl.set(CurlOption.verbose, on ? 1L : 0L); + } + + // Connection settings + + /// Set timeout for activity on connection. + @property void dataTimeout(Duration d) + { + p.curl.set(CurlOption.low_speed_limit, 1); + p.curl.set(CurlOption.low_speed_time, d.total!"seconds"); + } + + /** Set maximum time an operation is allowed to take. + This includes dns resolution, connecting, data transfer, etc. + */ + @property void operationTimeout(Duration d) + { + p.curl.set(CurlOption.timeout_ms, d.total!"msecs"); + } + + /// Set timeout for connecting. + @property void connectTimeout(Duration d) + { + p.curl.set(CurlOption.connecttimeout_ms, d.total!"msecs"); + } + + // Network settings + + /** Proxy + * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy) + */ + @property void proxy(const(char)[] host) + { + p.curl.set(CurlOption.proxy, host); + } + + /** Proxy port + * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXYPORT, _proxy_port) + */ + @property void proxyPort(ushort port) + { + p.curl.set(CurlOption.proxyport, cast(long) port); + } + + /// Type of proxy + alias CurlProxy = etc.c.curl.CurlProxy; + + /** Proxy type + * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy_type) + */ + @property void proxyType(CurlProxy type) + { + p.curl.set(CurlOption.proxytype, cast(long) type); + } + + /// DNS lookup timeout. + @property void dnsTimeout(Duration d) + { + p.curl.set(CurlOption.dns_cache_timeout, d.total!"msecs"); + } + + /** + * The network interface to use in form of the the IP of the interface. + * + * Example: + * ---- + * theprotocol.netInterface = "192.168.1.32"; + * theprotocol.netInterface = [ 192, 168, 1, 32 ]; + * ---- + * + * See: $(REF InternetAddress, std,socket) + */ + @property void netInterface(const(char)[] i) + { + p.curl.set(CurlOption.intrface, i); + } + + /// ditto + @property void netInterface(const(ubyte)[4] i) + { + import std.format : format; + const str = format("%d.%d.%d.%d", i[0], i[1], i[2], i[3]); + netInterface = str; + } + + /// ditto + @property void netInterface(InternetAddress i) + { + netInterface = i.toAddrString(); + } + + /** + Set the local outgoing port to use. + Params: + port = the first outgoing port number to try and use + */ + @property void localPort(ushort port) + { + p.curl.set(CurlOption.localport, cast(long) port); + } + + /** + Set the no proxy flag for the specified host names. + Params: + test = a list of comma host names that do not require + proxy to get reached + */ + void setNoProxy(string hosts) + { + p.curl.set(CurlOption.noproxy, hosts); + } + + /** + Set the local outgoing port range to use. + This can be used together with the localPort property. + Params: + range = if the first port is occupied then try this many + port number forwards + */ + @property void localPortRange(ushort range) + { + p.curl.set(CurlOption.localportrange, cast(long) range); + } + + /** Set the tcp no-delay socket option on or off. + See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTCPNODELAY, nodelay) + */ + @property void tcpNoDelay(bool on) + { + p.curl.set(CurlOption.tcp_nodelay, cast(long) (on ? 1 : 0) ); + } + + /** Sets whether SSL peer certificates should be verified. + See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTSSLVERIFYPEER, verifypeer) + */ + @property void verifyPeer(bool on) + { + p.curl.set(CurlOption.ssl_verifypeer, on ? 1 : 0); + } + + /** Sets whether the host within an SSL certificate should be verified. + See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTSSLVERIFYHOST, verifypeer) + */ + @property void verifyHost(bool on) + { + p.curl.set(CurlOption.ssl_verifyhost, on ? 2 : 0); + } + + // Authentication settings + + /** + Set the user name, password and optionally domain for authentication + purposes. + + Some protocols may need authentication in some cases. Use this + function to provide credentials. + + Params: + username = the username + password = the password + domain = used for NTLM authentication only and is set to the NTLM domain + name + */ + void setAuthentication(const(char)[] username, const(char)[] password, + const(char)[] domain = "") + { + import std.format : format; + if (!domain.empty) + username = format("%s/%s", domain, username); + p.curl.set(CurlOption.userpwd, format("%s:%s", username, password)); + } + + @system unittest + { + import std.algorithm.searching : canFind; + + testServer.handle((s) { + auto req = s.recvReq; + assert(req.hdrs.canFind("GET /")); + assert(req.hdrs.canFind("Basic dXNlcjpwYXNz")); + s.send(httpOK()); + }); + + auto http = HTTP(testServer.addr); + http.onReceive = (ubyte[] data) { return data.length; }; + http.setAuthentication("user", "pass"); + http.perform(); + + // Bugzilla 17540 + http.setNoProxy("www.example.com"); + } + + /** + Set the user name and password for proxy authentication. + + Params: + username = the username + password = the password + */ + void setProxyAuthentication(const(char)[] username, const(char)[] password) + { + import std.array : replace; + import std.format : format; + + p.curl.set(CurlOption.proxyuserpwd, + format("%s:%s", + username.replace(":", "%3A"), + password.replace(":", "%3A")) + ); + } + + /** + * The event handler that gets called when data is needed for sending. The + * length of the $(D void[]) specifies the maximum number of bytes that can + * be sent. + * + * Returns: + * The callback returns the number of elements in the buffer that have been + * filled and are ready to send. + * The special value $(D .abortRequest) can be returned in order to abort the + * current request. + * The special value $(D .pauseRequest) can be returned in order to pause the + * current request. + * + * Example: + * ---- + * import std.net.curl; + * string msg = "Hello world"; + * auto client = HTTP("dlang.org"); + * client.onSend = delegate size_t(void[] data) + * { + * auto m = cast(void[]) msg; + * size_t length = m.length > data.length ? data.length : m.length; + * if (length == 0) return 0; + * data[0 .. length] = m[0 .. length]; + * msg = msg[length..$]; + * return length; + * }; + * client.perform(); + * ---- + */ + @property void onSend(size_t delegate(void[]) callback) + { + p.curl.clear(CurlOption.postfields); // cannot specify data when using callback + p.curl.onSend = callback; + } + + /** + * The event handler that receives incoming data. Be sure to copy the + * incoming ubyte[] since it is not guaranteed to be valid after the + * callback returns. + * + * Returns: + * The callback returns the number of incoming bytes read. If the entire array is + * not read the request will abort. + * The special value .pauseRequest can be returned in order to pause the + * current request. + * + * Example: + * ---- + * import std.net.curl, std.stdio; + * auto client = HTTP("dlang.org"); + * client.onReceive = (ubyte[] data) + * { + * writeln("Got data", to!(const(char)[])(data)); + * return data.length; + * }; + * client.perform(); + * ---- + */ + @property void onReceive(size_t delegate(ubyte[]) callback) + { + p.curl.onReceive = callback; + } + + /** + * The event handler that gets called to inform of upload/download progress. + * + * Params: + * dlTotal = total bytes to download + * dlNow = currently downloaded bytes + * ulTotal = total bytes to upload + * ulNow = currently uploaded bytes + * + * Returns: + * Return 0 from the callback to signal success, return non-zero to abort + * transfer + * + * Example: + * ---- + * import std.net.curl, std.stdio; + * auto client = HTTP("dlang.org"); + * client.onProgress = delegate int(size_t dl, size_t dln, size_t ul, size_t ult) + * { + * writeln("Progress: downloaded ", dln, " of ", dl); + * writeln("Progress: uploaded ", uln, " of ", ul); + * }; + * client.perform(); + * ---- + */ + @property void onProgress(int delegate(size_t dlTotal, size_t dlNow, + size_t ulTotal, size_t ulNow) callback) + { + p.curl.onProgress = callback; + } +} + +/* + Decode $(D ubyte[]) array using the provided EncodingScheme up to maxChars + Returns: Tuple of ubytes read and the $(D Char[]) characters decoded. + Not all ubytes are guaranteed to be read in case of decoding error. +*/ +private Tuple!(size_t,Char[]) +decodeString(Char = char)(const(ubyte)[] data, + EncodingScheme scheme, + size_t maxChars = size_t.max) +{ + Char[] res; + immutable startLen = data.length; + size_t charsDecoded = 0; + while (data.length && charsDecoded < maxChars) + { + immutable dchar dc = scheme.safeDecode(data); + if (dc == INVALID_SEQUENCE) + { + return typeof(return)(size_t.max, cast(Char[]) null); + } + charsDecoded++; + res ~= dc; + } + return typeof(return)(startLen-data.length, res); +} + +/* + Decode $(D ubyte[]) array using the provided $(D EncodingScheme) until a the + line terminator specified is found. The basesrc parameter is effectively + prepended to src as the first thing. + + This function is used for decoding as much of the src buffer as + possible until either the terminator is found or decoding fails. If + it fails as the last data in the src it may mean that the src buffer + were missing some bytes in order to represent a correct code + point. Upon the next call to this function more bytes have been + received from net and the failing bytes should be given as the + basesrc parameter. It is done this way to minimize data copying. + + Returns: true if a terminator was found + Not all ubytes are guaranteed to be read in case of decoding error. + any decoded chars will be inserted into dst. +*/ +private bool decodeLineInto(Terminator, Char = char)(ref const(ubyte)[] basesrc, + ref const(ubyte)[] src, + ref Char[] dst, + EncodingScheme scheme, + Terminator terminator) +{ + import std.algorithm.searching : endsWith; + + // if there is anything in the basesrc then try to decode that + // first. + if (basesrc.length != 0) + { + // Try to ensure 4 entries in the basesrc by copying from src. + immutable blen = basesrc.length; + immutable len = (basesrc.length + src.length) >= 4 ? + 4 : basesrc.length + src.length; + basesrc.length = len; + + immutable dchar dc = scheme.safeDecode(basesrc); + if (dc == INVALID_SEQUENCE) + { + enforce!CurlException(len != 4, "Invalid code sequence"); + return false; + } + dst ~= dc; + src = src[len-basesrc.length-blen .. $]; // remove used ubytes from src + basesrc.length = 0; + } + + while (src.length) + { + const lsrc = src; + dchar dc = scheme.safeDecode(src); + if (dc == INVALID_SEQUENCE) + { + if (src.empty) + { + // The invalid sequence was in the end of the src. Maybe there + // just need to be more bytes available so these last bytes are + // put back to src for later use. + src = lsrc; + return false; + } + dc = '?'; + } + dst ~= dc; + + if (dst.endsWith(terminator)) + return true; + } + return false; // no terminator found +} + +/** + * HTTP client functionality. + * + * Example: + * --- + * import std.net.curl, std.stdio; + * + * // Get with custom data receivers + * auto http = HTTP("dlang.org"); + * http.onReceiveHeader = + * (in char[] key, in char[] value) { writeln(key ~ ": " ~ value); }; + * http.onReceive = (ubyte[] data) { /+ drop +/ return data.length; }; + * http.perform(); + * + * // Put with data senders + * auto msg = "Hello world"; + * http.contentLength = msg.length; + * http.onSend = (void[] data) + * { + * auto m = cast(void[]) msg; + * size_t len = m.length > data.length ? data.length : m.length; + * if (len == 0) return len; + * data[0 .. len] = m[0 .. len]; + * msg = msg[len..$]; + * return len; + * }; + * http.perform(); + * + * // Track progress + * http.method = HTTP.Method.get; + * http.url = "http://upload.wikimedia.org/wikipedia/commons/" + * "5/53/Wikipedia-logo-en-big.png"; + * http.onReceive = (ubyte[] data) { return data.length; }; + * http.onProgress = (size_t dltotal, size_t dlnow, + * size_t ultotal, size_t ulnow) + * { + * writeln("Progress ", dltotal, ", ", dlnow, ", ", ultotal, ", ", ulnow); + * return 0; + * }; + * http.perform(); + * --- + * + * See_Also: $(_HTTP www.ietf.org/rfc/rfc2616.txt, RFC2616) + * + */ +struct HTTP +{ + mixin Protocol; + + import std.datetime.systime : SysTime; + + /// Authentication method equal to $(REF CurlAuth, etc,c,curl) + alias AuthMethod = CurlAuth; + + static private uint defaultMaxRedirects = 10; + + private struct Impl + { + ~this() + { + if (headersOut !is null) + Curl.curl.slist_free_all(headersOut); + if (curl.handle !is null) // work around RefCounted/emplace bug + curl.shutdown(); + } + Curl curl; + curl_slist* headersOut; + string[string] headersIn; + string charset; + + /// The status line of the final sub-request in a request. + StatusLine status; + private void delegate(StatusLine) onReceiveStatusLine; + + /// The HTTP method to use. + Method method = Method.undefined; + + @system @property void onReceiveHeader(void delegate(in char[] key, + in char[] value) callback) + { + import std.algorithm.searching : startsWith; + import std.conv : to; + import std.regex : regex, match; + import std.uni : toLower; + + // Wrap incoming callback in order to separate http status line from + // http headers. On redirected requests there may be several such + // status lines. The last one is the one recorded. + auto dg = (in char[] header) + { + import std.utf : UTFException; + try + { + if (header.empty) + { + // header delimiter + return; + } + if (header.startsWith("HTTP/")) + { + headersIn.clear(); + + const m = match(header, regex(r"^HTTP/(\d+)\.(\d+) (\d+) (.*)$")); + if (m.empty) + { + // Invalid status line + } + else + { + status.majorVersion = to!ushort(m.captures[1]); + status.minorVersion = to!ushort(m.captures[2]); + status.code = to!ushort(m.captures[3]); + status.reason = m.captures[4].idup; + if (onReceiveStatusLine != null) + onReceiveStatusLine(status); + } + return; + } + + // Normal http header + auto m = match(cast(char[]) header, regex("(.*?): (.*)$")); + + auto fieldName = m.captures[1].toLower().idup; + if (fieldName == "content-type") + { + auto mct = match(cast(char[]) m.captures[2], + regex("charset=([^;]*)", "i")); + if (!mct.empty && mct.captures.length > 1) + charset = mct.captures[1].idup; + } + + if (!m.empty && callback !is null) + callback(fieldName, m.captures[2]); + headersIn[fieldName] = m.captures[2].idup; + } + catch (UTFException e) + { + //munch it - a header should be all ASCII, any "wrong UTF" is broken header + } + }; + + curl.onReceiveHeader = dg; + } + } + + private RefCounted!Impl p; + + /** Time condition enumeration as an alias of $(REF CurlTimeCond, etc,c,curl) + + $(HTTP www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25, _RFC2616 Section 14.25) + */ + alias TimeCond = CurlTimeCond; + + /** + Constructor taking the url as parameter. + */ + static HTTP opCall(const(char)[] url) + { + HTTP http; + http.initialize(); + http.url = url; + return http; + } + + /// + static HTTP opCall() + { + HTTP http; + http.initialize(); + return http; + } + + /// + HTTP dup() + { + HTTP copy; + copy.initialize(); + copy.p.method = p.method; + curl_slist* cur = p.headersOut; + curl_slist* newlist = null; + while (cur) + { + newlist = Curl.curl.slist_append(newlist, cur.data); + cur = cur.next; + } + copy.p.headersOut = newlist; + copy.p.curl.set(CurlOption.httpheader, copy.p.headersOut); + copy.p.curl = p.curl.dup(); + copy.dataTimeout = _defaultDataTimeout; + copy.onReceiveHeader = null; + return copy; + } + + private void initialize() + { + p.curl.initialize(); + maxRedirects = HTTP.defaultMaxRedirects; + p.charset = "ISO-8859-1"; // Default charset defined in HTTP RFC + p.method = Method.undefined; + setUserAgent(HTTP.defaultUserAgent); + dataTimeout = _defaultDataTimeout; + onReceiveHeader = null; + verifyPeer = true; + verifyHost = true; + } + + /** + Perform a http request. + + After the HTTP client has been setup and possibly assigned callbacks the + $(D perform()) method will start performing the request towards the + specified server. + + Params: + throwOnError = whether to throw an exception or return a CurlCode on error + */ + CurlCode perform(ThrowOnError throwOnError = Yes.throwOnError) + { + p.status.reset(); + + CurlOption opt; + final switch (p.method) + { + case Method.head: + p.curl.set(CurlOption.nobody, 1L); + opt = CurlOption.nobody; + break; + case Method.undefined: + case Method.get: + p.curl.set(CurlOption.httpget, 1L); + opt = CurlOption.httpget; + break; + case Method.post: + p.curl.set(CurlOption.post, 1L); + opt = CurlOption.post; + break; + case Method.put: + p.curl.set(CurlOption.upload, 1L); + opt = CurlOption.upload; + break; + case Method.del: + p.curl.set(CurlOption.customrequest, "DELETE"); + opt = CurlOption.customrequest; + break; + case Method.options: + p.curl.set(CurlOption.customrequest, "OPTIONS"); + opt = CurlOption.customrequest; + break; + case Method.trace: + p.curl.set(CurlOption.customrequest, "TRACE"); + opt = CurlOption.customrequest; + break; + case Method.connect: + p.curl.set(CurlOption.customrequest, "CONNECT"); + opt = CurlOption.customrequest; + break; + case Method.patch: + p.curl.set(CurlOption.customrequest, "PATCH"); + opt = CurlOption.customrequest; + break; + } + + scope (exit) p.curl.clear(opt); + return p.curl.perform(throwOnError); + } + + /// The URL to specify the location of the resource. + @property void url(const(char)[] url) + { + import std.algorithm.searching : startsWith; + import std.uni : toLower; + if (!startsWith(url.toLower(), "http://", "https://")) + url = "http://" ~ url; + p.curl.set(CurlOption.url, url); + } + + /// Set the CA certificate bundle file to use for SSL peer verification + @property void caInfo(const(char)[] caFile) + { + p.curl.set(CurlOption.cainfo, caFile); + } + + // This is a workaround for mixed in content not having its + // docs mixed in. + version (StdDdoc) + { + /// Value to return from $(D onSend)/$(D onReceive) delegates in order to + /// pause a request + alias requestPause = CurlReadFunc.pause; + + /// Value to return from onSend delegate in order to abort a request + alias requestAbort = CurlReadFunc.abort; + + /** + True if the instance is stopped. A stopped instance is not usable. + */ + @property bool isStopped(); + + /// Stop and invalidate this instance. + void shutdown(); + + /** Set verbose. + This will print request information to stderr. + */ + @property void verbose(bool on); + + // Connection settings + + /// Set timeout for activity on connection. + @property void dataTimeout(Duration d); + + /** Set maximum time an operation is allowed to take. + This includes dns resolution, connecting, data transfer, etc. + */ + @property void operationTimeout(Duration d); + + /// Set timeout for connecting. + @property void connectTimeout(Duration d); + + // Network settings + + /** Proxy + * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy) + */ + @property void proxy(const(char)[] host); + + /** Proxy port + * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXYPORT, _proxy_port) + */ + @property void proxyPort(ushort port); + + /// Type of proxy + alias CurlProxy = etc.c.curl.CurlProxy; + + /** Proxy type + * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy_type) + */ + @property void proxyType(CurlProxy type); + + /// DNS lookup timeout. + @property void dnsTimeout(Duration d); + + /** + * The network interface to use in form of the the IP of the interface. + * + * Example: + * ---- + * theprotocol.netInterface = "192.168.1.32"; + * theprotocol.netInterface = [ 192, 168, 1, 32 ]; + * ---- + * + * See: $(REF InternetAddress, std,socket) + */ + @property void netInterface(const(char)[] i); + + /// ditto + @property void netInterface(const(ubyte)[4] i); + + /// ditto + @property void netInterface(InternetAddress i); + + /** + Set the local outgoing port to use. + Params: + port = the first outgoing port number to try and use + */ + @property void localPort(ushort port); + + /** + Set the local outgoing port range to use. + This can be used together with the localPort property. + Params: + range = if the first port is occupied then try this many + port number forwards + */ + @property void localPortRange(ushort range); + + /** Set the tcp no-delay socket option on or off. + See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTCPNODELAY, nodelay) + */ + @property void tcpNoDelay(bool on); + + // Authentication settings + + /** + Set the user name, password and optionally domain for authentication + purposes. + + Some protocols may need authentication in some cases. Use this + function to provide credentials. + + Params: + username = the username + password = the password + domain = used for NTLM authentication only and is set to the NTLM domain + name + */ + void setAuthentication(const(char)[] username, const(char)[] password, + const(char)[] domain = ""); + + /** + Set the user name and password for proxy authentication. + + Params: + username = the username + password = the password + */ + void setProxyAuthentication(const(char)[] username, const(char)[] password); + + /** + * The event handler that gets called when data is needed for sending. The + * length of the $(D void[]) specifies the maximum number of bytes that can + * be sent. + * + * Returns: + * The callback returns the number of elements in the buffer that have been + * filled and are ready to send. + * The special value $(D .abortRequest) can be returned in order to abort the + * current request. + * The special value $(D .pauseRequest) can be returned in order to pause the + * current request. + * + * Example: + * ---- + * import std.net.curl; + * string msg = "Hello world"; + * auto client = HTTP("dlang.org"); + * client.onSend = delegate size_t(void[] data) + * { + * auto m = cast(void[]) msg; + * size_t length = m.length > data.length ? data.length : m.length; + * if (length == 0) return 0; + * data[0 .. length] = m[0 .. length]; + * msg = msg[length..$]; + * return length; + * }; + * client.perform(); + * ---- + */ + @property void onSend(size_t delegate(void[]) callback); + + /** + * The event handler that receives incoming data. Be sure to copy the + * incoming ubyte[] since it is not guaranteed to be valid after the + * callback returns. + * + * Returns: + * The callback returns the incoming bytes read. If not the entire array is + * the request will abort. + * The special value .pauseRequest can be returned in order to pause the + * current request. + * + * Example: + * ---- + * import std.net.curl, std.stdio; + * auto client = HTTP("dlang.org"); + * client.onReceive = (ubyte[] data) + * { + * writeln("Got data", to!(const(char)[])(data)); + * return data.length; + * }; + * client.perform(); + * ---- + */ + @property void onReceive(size_t delegate(ubyte[]) callback); + + /** + * Register an event handler that gets called to inform of + * upload/download progress. + * + * Callback_parameters: + * $(CALLBACK_PARAMS) + * + * Callback_returns: Return 0 to signal success, return non-zero to + * abort transfer. + * + * Example: + * ---- + * import std.net.curl, std.stdio; + * auto client = HTTP("dlang.org"); + * client.onProgress = delegate int(size_t dl, size_t dln, size_t ul, size_t ult) + * { + * writeln("Progress: downloaded ", dln, " of ", dl); + * writeln("Progress: uploaded ", uln, " of ", ul); + * }; + * client.perform(); + * ---- + */ + @property void onProgress(int delegate(size_t dlTotal, size_t dlNow, + size_t ulTotal, size_t ulNow) callback); + } + + /** Clear all outgoing headers. + */ + void clearRequestHeaders() + { + if (p.headersOut !is null) + Curl.curl.slist_free_all(p.headersOut); + p.headersOut = null; + p.curl.clear(CurlOption.httpheader); + } + + /** Add a header e.g. "X-CustomField: Something is fishy". + * + * There is no remove header functionality. Do a $(LREF clearRequestHeaders) + * and set the needed headers instead. + * + * Example: + * --- + * import std.net.curl; + * auto client = HTTP(); + * client.addRequestHeader("X-Custom-ABC", "This is the custom value"); + * auto content = get("dlang.org", client); + * --- + */ + void addRequestHeader(const(char)[] name, const(char)[] value) + { + import std.format : format; + import std.uni : icmp; + + if (icmp(name, "User-Agent") == 0) + return setUserAgent(value); + string nv = format("%s: %s", name, value); + p.headersOut = Curl.curl.slist_append(p.headersOut, + nv.tempCString().buffPtr); + p.curl.set(CurlOption.httpheader, p.headersOut); + } + + /** + * The default "User-Agent" value send with a request. + * It has the form "Phobos-std.net.curl/$(I PHOBOS_VERSION) (libcurl/$(I CURL_VERSION))" + */ + static string defaultUserAgent() @property + { + import std.compiler : version_major, version_minor; + import std.format : format, sformat; + + // http://curl.haxx.se/docs/versions.html + enum fmt = "Phobos-std.net.curl/%d.%03d (libcurl/%d.%d.%d)"; + enum maxLen = fmt.length - "%d%03d%d%d%d".length + 10 + 10 + 3 + 3 + 3; + + static char[maxLen] buf = void; + static string userAgent; + + if (!userAgent.length) + { + auto curlVer = Curl.curl.version_info(CURLVERSION_NOW).version_num; + userAgent = cast(immutable) sformat( + buf, fmt, version_major, version_minor, + curlVer >> 16 & 0xFF, curlVer >> 8 & 0xFF, curlVer & 0xFF); + } + return userAgent; + } + + /** Set the value of the user agent request header field. + * + * By default a request has it's "User-Agent" field set to $(LREF + * defaultUserAgent) even if $(D setUserAgent) was never called. Pass + * an empty string to suppress the "User-Agent" field altogether. + */ + void setUserAgent(const(char)[] userAgent) + { + p.curl.set(CurlOption.useragent, userAgent); + } + + /** + * Get various timings defined in $(REF CurlInfo, etc, c, curl). + * The value is usable only if the return value is equal to $(D etc.c.curl.CurlError.ok). + * + * Params: + * timing = one of the timings defined in $(REF CurlInfo, etc, c, curl). + * The values are: + * $(D etc.c.curl.CurlInfo.namelookup_time), + * $(D etc.c.curl.CurlInfo.connect_time), + * $(D etc.c.curl.CurlInfo.pretransfer_time), + * $(D etc.c.curl.CurlInfo.starttransfer_time), + * $(D etc.c.curl.CurlInfo.redirect_time), + * $(D etc.c.curl.CurlInfo.appconnect_time), + * $(D etc.c.curl.CurlInfo.total_time). + * val = the actual value of the inquired timing. + * + * Returns: + * The return code of the operation. The value stored in val + * should be used only if the return value is $(D etc.c.curl.CurlInfo.ok). + * + * Example: + * --- + * import std.net.curl; + * import etc.c.curl : CurlError, CurlInfo; + * + * auto client = HTTP("dlang.org"); + * client.perform(); + * + * double val; + * CurlCode code; + * + * code = http.getTiming(CurlInfo.namelookup_time, val); + * assert(code == CurlError.ok); + * --- + */ + CurlCode getTiming(CurlInfo timing, ref double val) + { + return p.curl.getTiming(timing, val); + } + + /** The headers read from a successful response. + * + */ + @property string[string] responseHeaders() + { + return p.headersIn; + } + + /// HTTP method used. + @property void method(Method m) + { + p.method = m; + } + + /// ditto + @property Method method() + { + return p.method; + } + + /** + HTTP status line of last response. One call to perform may + result in several requests because of redirection. + */ + @property StatusLine statusLine() + { + return p.status; + } + + /// Set the active cookie string e.g. "name1=value1;name2=value2" + void setCookie(const(char)[] cookie) + { + p.curl.set(CurlOption.cookie, cookie); + } + + /// Set a file path to where a cookie jar should be read/stored. + void setCookieJar(const(char)[] path) + { + p.curl.set(CurlOption.cookiefile, path); + if (path.length) + p.curl.set(CurlOption.cookiejar, path); + } + + /// Flush cookie jar to disk. + void flushCookieJar() + { + p.curl.set(CurlOption.cookielist, "FLUSH"); + } + + /// Clear session cookies. + void clearSessionCookies() + { + p.curl.set(CurlOption.cookielist, "SESS"); + } + + /// Clear all cookies. + void clearAllCookies() + { + p.curl.set(CurlOption.cookielist, "ALL"); + } + + /** + Set time condition on the request. + + Params: + cond = $(D CurlTimeCond.{none,ifmodsince,ifunmodsince,lastmod}) + timestamp = Timestamp for the condition + + $(HTTP www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25, _RFC2616 Section 14.25) + */ + void setTimeCondition(HTTP.TimeCond cond, SysTime timestamp) + { + p.curl.set(CurlOption.timecondition, cond); + p.curl.set(CurlOption.timevalue, timestamp.toUnixTime()); + } + + /** Specifying data to post when not using the onSend callback. + * + * The data is NOT copied by the library. Content-Type will default to + * application/octet-stream. Data is not converted or encoded by this + * method. + * + * Example: + * ---- + * import std.net.curl, std.stdio; + * auto http = HTTP("http://www.mydomain.com"); + * http.onReceive = (ubyte[] data) { writeln(to!(const(char)[])(data)); return data.length; }; + * http.postData = [1,2,3,4,5]; + * http.perform(); + * ---- + */ + @property void postData(const(void)[] data) + { + setPostData(data, "application/octet-stream"); + } + + /** Specifying data to post when not using the onSend callback. + * + * The data is NOT copied by the library. Content-Type will default to + * text/plain. Data is not converted or encoded by this method. + * + * Example: + * ---- + * import std.net.curl, std.stdio; + * auto http = HTTP("http://www.mydomain.com"); + * http.onReceive = (ubyte[] data) { writeln(to!(const(char)[])(data)); return data.length; }; + * http.postData = "The quick...."; + * http.perform(); + * ---- + */ + @property void postData(const(char)[] data) + { + setPostData(data, "text/plain"); + } + + /** + * Specify data to post when not using the onSend callback, with + * user-specified Content-Type. + * Params: + * data = Data to post. + * contentType = MIME type of the data, for example, "text/plain" or + * "application/octet-stream". See also: + * $(LINK2 http://en.wikipedia.org/wiki/Internet_media_type, + * Internet media type) on Wikipedia. + * ----- + * import std.net.curl; + * auto http = HTTP("http://onlineform.example.com"); + * auto data = "app=login&username=bob&password=s00perS3kret"; + * http.setPostData(data, "application/x-www-form-urlencoded"); + * http.onReceive = (ubyte[] data) { return data.length; }; + * http.perform(); + * ----- + */ + void setPostData(const(void)[] data, string contentType) + { + // cannot use callback when specifying data directly so it is disabled here. + p.curl.clear(CurlOption.readfunction); + addRequestHeader("Content-Type", contentType); + p.curl.set(CurlOption.postfields, cast(void*) data.ptr); + p.curl.set(CurlOption.postfieldsize, data.length); + if (method == Method.undefined) + method = Method.post; + } + + @system unittest + { + import std.algorithm.searching : canFind; + + testServer.handle((s) { + auto req = s.recvReq!ubyte; + assert(req.hdrs.canFind("POST /path")); + assert(req.bdy.canFind(cast(ubyte[])[0, 1, 2, 3, 4])); + assert(req.bdy.canFind(cast(ubyte[])[253, 254, 255])); + s.send(httpOK(cast(ubyte[])[17, 27, 35, 41])); + }); + auto data = new ubyte[](256); + foreach (i, ref ub; data) + ub = cast(ubyte) i; + + auto http = HTTP(testServer.addr~"/path"); + http.postData = data; + ubyte[] res; + http.onReceive = (data) { res ~= data; return data.length; }; + http.perform(); + assert(res == cast(ubyte[])[17, 27, 35, 41]); + } + + /** + * Set the event handler that receives incoming headers. + * + * The callback will receive a header field key, value as parameter. The + * $(D const(char)[]) arrays are not valid after the delegate has returned. + * + * Example: + * ---- + * import std.net.curl, std.stdio; + * auto http = HTTP("dlang.org"); + * http.onReceive = (ubyte[] data) { writeln(to!(const(char)[])(data)); return data.length; }; + * http.onReceiveHeader = (in char[] key, in char[] value) { writeln(key, " = ", value); }; + * http.perform(); + * ---- + */ + @property void onReceiveHeader(void delegate(in char[] key, + in char[] value) callback) + { + p.onReceiveHeader = callback; + } + + /** + Callback for each received StatusLine. + + Notice that several callbacks can be done for each call to + $(D perform()) due to redirections. + + See_Also: $(LREF StatusLine) + */ + @property void onReceiveStatusLine(void delegate(StatusLine) callback) + { + p.onReceiveStatusLine = callback; + } + + /** + The content length in bytes when using request that has content + e.g. POST/PUT and not using chunked transfer. Is set as the + "Content-Length" header. Set to ulong.max to reset to chunked transfer. + */ + @property void contentLength(ulong len) + { + import std.conv : to; + + CurlOption lenOpt; + + // Force post if necessary + if (p.method != Method.put && p.method != Method.post && + p.method != Method.patch) + p.method = Method.post; + + if (p.method == Method.post || p.method == Method.patch) + lenOpt = CurlOption.postfieldsize_large; + else + lenOpt = CurlOption.infilesize_large; + + if (size_t.max != ulong.max && len == size_t.max) + len = ulong.max; // check size_t.max for backwards compat, turn into error + + if (len == ulong.max) + { + // HTTP 1.1 supports requests with no length header set. + addRequestHeader("Transfer-Encoding", "chunked"); + addRequestHeader("Expect", "100-continue"); + } + else + { + p.curl.set(lenOpt, to!curl_off_t(len)); + } + } + + /** + Authentication method as specified in $(LREF AuthMethod). + */ + @property void authenticationMethod(AuthMethod authMethod) + { + p.curl.set(CurlOption.httpauth, cast(long) authMethod); + } + + /** + Set max allowed redirections using the location header. + uint.max for infinite. + */ + @property void maxRedirects(uint maxRedirs) + { + if (maxRedirs == uint.max) + { + // Disable + p.curl.set(CurlOption.followlocation, 0); + } + else + { + p.curl.set(CurlOption.followlocation, 1); + p.curl.set(CurlOption.maxredirs, maxRedirs); + } + } + + /** The standard HTTP methods : + * $(HTTP www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1, _RFC2616 Section 5.1.1) + */ + enum Method + { + undefined, + head, /// + get, /// + post, /// + put, /// + del, /// + options, /// + trace, /// + connect, /// + patch, /// + } + + /** + HTTP status line ie. the first line returned in an HTTP response. + + If authentication or redirections are done then the status will be for + the last response received. + */ + struct StatusLine + { + ushort majorVersion; /// Major HTTP version ie. 1 in HTTP/1.0. + ushort minorVersion; /// Minor HTTP version ie. 0 in HTTP/1.0. + ushort code; /// HTTP status line code e.g. 200. + string reason; /// HTTP status line reason string. + + /// Reset this status line + @safe void reset() + { + majorVersion = 0; + minorVersion = 0; + code = 0; + reason = ""; + } + + /// + string toString() const + { + import std.format : format; + return format("%s %s (%s.%s)", + code, reason, majorVersion, minorVersion); + } + } + +} // HTTP + +@system unittest // charset/Charset/CHARSET/... +{ + import std.meta : AliasSeq; + + foreach (c; AliasSeq!("charset", "Charset", "CHARSET", "CharSet", "charSet", + "ChArSeT", "cHaRsEt")) + { + testServer.handle((s) { + s.send("HTTP/1.1 200 OK\r\n"~ + "Content-Length: 0\r\n"~ + "Content-Type: text/plain; " ~ c ~ "=foo\r\n" ~ + "\r\n"); + }); + + auto http = HTTP(testServer.addr); + http.perform(); + assert(http.p.charset == "foo"); + + // Bugzilla 16736 + double val; + CurlCode code; + + code = http.getTiming(CurlInfo.total_time, val); + assert(code == CurlError.ok); + code = http.getTiming(CurlInfo.namelookup_time, val); + assert(code == CurlError.ok); + code = http.getTiming(CurlInfo.connect_time, val); + assert(code == CurlError.ok); + code = http.getTiming(CurlInfo.pretransfer_time, val); + assert(code == CurlError.ok); + code = http.getTiming(CurlInfo.starttransfer_time, val); + assert(code == CurlError.ok); + code = http.getTiming(CurlInfo.redirect_time, val); + assert(code == CurlError.ok); + code = http.getTiming(CurlInfo.appconnect_time, val); + assert(code == CurlError.ok); + } +} + +/** + FTP client functionality. + + See_Also: $(HTTP tools.ietf.org/html/rfc959, RFC959) +*/ +struct FTP +{ + + mixin Protocol; + + private struct Impl + { + ~this() + { + if (commands !is null) + Curl.curl.slist_free_all(commands); + if (curl.handle !is null) // work around RefCounted/emplace bug + curl.shutdown(); + } + curl_slist* commands; + Curl curl; + string encoding; + } + + private RefCounted!Impl p; + + /** + FTP access to the specified url. + */ + static FTP opCall(const(char)[] url) + { + FTP ftp; + ftp.initialize(); + ftp.url = url; + return ftp; + } + + /// + static FTP opCall() + { + FTP ftp; + ftp.initialize(); + return ftp; + } + + /// + FTP dup() + { + FTP copy = FTP(); + copy.initialize(); + copy.p.encoding = p.encoding; + copy.p.curl = p.curl.dup(); + curl_slist* cur = p.commands; + curl_slist* newlist = null; + while (cur) + { + newlist = Curl.curl.slist_append(newlist, cur.data); + cur = cur.next; + } + copy.p.commands = newlist; + copy.p.curl.set(CurlOption.postquote, copy.p.commands); + copy.dataTimeout = _defaultDataTimeout; + return copy; + } + + private void initialize() + { + p.curl.initialize(); + p.encoding = "ISO-8859-1"; + dataTimeout = _defaultDataTimeout; + } + + /** + Performs the ftp request as it has been configured. + + After a FTP client has been setup and possibly assigned callbacks the $(D + perform()) method will start performing the actual communication with the + server. + + Params: + throwOnError = whether to throw an exception or return a CurlCode on error + */ + CurlCode perform(ThrowOnError throwOnError = Yes.throwOnError) + { + return p.curl.perform(throwOnError); + } + + /// The URL to specify the location of the resource. + @property void url(const(char)[] url) + { + import std.algorithm.searching : startsWith; + import std.uni : toLower; + + if (!startsWith(url.toLower(), "ftp://", "ftps://")) + url = "ftp://" ~ url; + p.curl.set(CurlOption.url, url); + } + + // This is a workaround for mixed in content not having its + // docs mixed in. + version (StdDdoc) + { + /// Value to return from $(D onSend)/$(D onReceive) delegates in order to + /// pause a request + alias requestPause = CurlReadFunc.pause; + + /// Value to return from onSend delegate in order to abort a request + alias requestAbort = CurlReadFunc.abort; + + /** + True if the instance is stopped. A stopped instance is not usable. + */ + @property bool isStopped(); + + /// Stop and invalidate this instance. + void shutdown(); + + /** Set verbose. + This will print request information to stderr. + */ + @property void verbose(bool on); + + // Connection settings + + /// Set timeout for activity on connection. + @property void dataTimeout(Duration d); + + /** Set maximum time an operation is allowed to take. + This includes dns resolution, connecting, data transfer, etc. + */ + @property void operationTimeout(Duration d); + + /// Set timeout for connecting. + @property void connectTimeout(Duration d); + + // Network settings + + /** Proxy + * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy) + */ + @property void proxy(const(char)[] host); + + /** Proxy port + * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXYPORT, _proxy_port) + */ + @property void proxyPort(ushort port); + + /// Type of proxy + alias CurlProxy = etc.c.curl.CurlProxy; + + /** Proxy type + * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy_type) + */ + @property void proxyType(CurlProxy type); + + /// DNS lookup timeout. + @property void dnsTimeout(Duration d); + + /** + * The network interface to use in form of the the IP of the interface. + * + * Example: + * ---- + * theprotocol.netInterface = "192.168.1.32"; + * theprotocol.netInterface = [ 192, 168, 1, 32 ]; + * ---- + * + * See: $(REF InternetAddress, std,socket) + */ + @property void netInterface(const(char)[] i); + + /// ditto + @property void netInterface(const(ubyte)[4] i); + + /// ditto + @property void netInterface(InternetAddress i); + + /** + Set the local outgoing port to use. + Params: + port = the first outgoing port number to try and use + */ + @property void localPort(ushort port); + + /** + Set the local outgoing port range to use. + This can be used together with the localPort property. + Params: + range = if the first port is occupied then try this many + port number forwards + */ + @property void localPortRange(ushort range); + + /** Set the tcp no-delay socket option on or off. + See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTCPNODELAY, nodelay) + */ + @property void tcpNoDelay(bool on); + + // Authentication settings + + /** + Set the user name, password and optionally domain for authentication + purposes. + + Some protocols may need authentication in some cases. Use this + function to provide credentials. + + Params: + username = the username + password = the password + domain = used for NTLM authentication only and is set to the NTLM domain + name + */ + void setAuthentication(const(char)[] username, const(char)[] password, + const(char)[] domain = ""); + + /** + Set the user name and password for proxy authentication. + + Params: + username = the username + password = the password + */ + void setProxyAuthentication(const(char)[] username, const(char)[] password); + + /** + * The event handler that gets called when data is needed for sending. The + * length of the $(D void[]) specifies the maximum number of bytes that can + * be sent. + * + * Returns: + * The callback returns the number of elements in the buffer that have been + * filled and are ready to send. + * The special value $(D .abortRequest) can be returned in order to abort the + * current request. + * The special value $(D .pauseRequest) can be returned in order to pause the + * current request. + * + */ + @property void onSend(size_t delegate(void[]) callback); + + /** + * The event handler that receives incoming data. Be sure to copy the + * incoming ubyte[] since it is not guaranteed to be valid after the + * callback returns. + * + * Returns: + * The callback returns the incoming bytes read. If not the entire array is + * the request will abort. + * The special value .pauseRequest can be returned in order to pause the + * current request. + * + */ + @property void onReceive(size_t delegate(ubyte[]) callback); + + /** + * The event handler that gets called to inform of upload/download progress. + * + * Callback_parameters: + * $(CALLBACK_PARAMS) + * + * Callback_returns: + * Return 0 from the callback to signal success, return non-zero to + * abort transfer. + */ + @property void onProgress(int delegate(size_t dlTotal, size_t dlNow, + size_t ulTotal, size_t ulNow) callback); + } + + /** Clear all commands send to ftp server. + */ + void clearCommands() + { + if (p.commands !is null) + Curl.curl.slist_free_all(p.commands); + p.commands = null; + p.curl.clear(CurlOption.postquote); + } + + /** Add a command to send to ftp server. + * + * There is no remove command functionality. Do a $(LREF clearCommands) and + * set the needed commands instead. + * + * Example: + * --- + * import std.net.curl; + * auto client = FTP(); + * client.addCommand("RNFR my_file.txt"); + * client.addCommand("RNTO my_renamed_file.txt"); + * upload("my_file.txt", "ftp.digitalmars.com", client); + * --- + */ + void addCommand(const(char)[] command) + { + p.commands = Curl.curl.slist_append(p.commands, + command.tempCString().buffPtr); + p.curl.set(CurlOption.postquote, p.commands); + } + + /// Connection encoding. Defaults to ISO-8859-1. + @property void encoding(string name) + { + p.encoding = name; + } + + /// ditto + @property string encoding() + { + return p.encoding; + } + + /** + The content length in bytes of the ftp data. + */ + @property void contentLength(ulong len) + { + import std.conv : to; + p.curl.set(CurlOption.infilesize_large, to!curl_off_t(len)); + } + + /** + * Get various timings defined in $(REF CurlInfo, etc, c, curl). + * The value is usable only if the return value is equal to $(D etc.c.curl.CurlError.ok). + * + * Params: + * timing = one of the timings defined in $(REF CurlInfo, etc, c, curl). + * The values are: + * $(D etc.c.curl.CurlInfo.namelookup_time), + * $(D etc.c.curl.CurlInfo.connect_time), + * $(D etc.c.curl.CurlInfo.pretransfer_time), + * $(D etc.c.curl.CurlInfo.starttransfer_time), + * $(D etc.c.curl.CurlInfo.redirect_time), + * $(D etc.c.curl.CurlInfo.appconnect_time), + * $(D etc.c.curl.CurlInfo.total_time). + * val = the actual value of the inquired timing. + * + * Returns: + * The return code of the operation. The value stored in val + * should be used only if the return value is $(D etc.c.curl.CurlInfo.ok). + * + * Example: + * --- + * import std.net.curl; + * import etc.c.curl : CurlError, CurlInfo; + * + * auto client = FTP(); + * client.addCommand("RNFR my_file.txt"); + * client.addCommand("RNTO my_renamed_file.txt"); + * upload("my_file.txt", "ftp.digitalmars.com", client); + * + * double val; + * CurlCode code; + * + * code = http.getTiming(CurlInfo.namelookup_time, val); + * assert(code == CurlError.ok); + * --- + */ + CurlCode getTiming(CurlInfo timing, ref double val) + { + return p.curl.getTiming(timing, val); + } + + @system unittest + { + auto client = FTP(); + + double val; + CurlCode code; + + code = client.getTiming(CurlInfo.total_time, val); + assert(code == CurlError.ok); + code = client.getTiming(CurlInfo.namelookup_time, val); + assert(code == CurlError.ok); + code = client.getTiming(CurlInfo.connect_time, val); + assert(code == CurlError.ok); + code = client.getTiming(CurlInfo.pretransfer_time, val); + assert(code == CurlError.ok); + code = client.getTiming(CurlInfo.starttransfer_time, val); + assert(code == CurlError.ok); + code = client.getTiming(CurlInfo.redirect_time, val); + assert(code == CurlError.ok); + code = client.getTiming(CurlInfo.appconnect_time, val); + assert(code == CurlError.ok); + } +} + +/** + * Basic SMTP protocol support. + * + * Example: + * --- + * import std.net.curl; + * + * // Send an email with SMTPS + * auto smtp = SMTP("smtps://smtp.gmail.com"); + * smtp.setAuthentication("from.addr@gmail.com", "password"); + * smtp.mailTo = [""]; + * smtp.mailFrom = ""; + * smtp.message = "Example Message"; + * smtp.perform(); + * --- + * + * See_Also: $(HTTP www.ietf.org/rfc/rfc2821.txt, RFC2821) + */ +struct SMTP +{ + mixin Protocol; + + private struct Impl + { + ~this() + { + if (curl.handle !is null) // work around RefCounted/emplace bug + curl.shutdown(); + } + Curl curl; + + @property void message(string msg) + { + import std.algorithm.comparison : min; + + auto _message = msg; + /** + This delegate reads the message text and copies it. + */ + curl.onSend = delegate size_t(void[] data) + { + if (!msg.length) return 0; + size_t to_copy = min(data.length, _message.length); + data[0 .. to_copy] = (cast(void[])_message)[0 .. to_copy]; + _message = _message[to_copy..$]; + return to_copy; + }; + } + } + + private RefCounted!Impl p; + + /** + Sets to the URL of the SMTP server. + */ + static SMTP opCall(const(char)[] url) + { + SMTP smtp; + smtp.initialize(); + smtp.url = url; + return smtp; + } + + /// + static SMTP opCall() + { + SMTP smtp; + smtp.initialize(); + return smtp; + } + + /+ TODO: The other structs have this function. + SMTP dup() + { + SMTP copy = SMTP(); + copy.initialize(); + copy.p.encoding = p.encoding; + copy.p.curl = p.curl.dup(); + curl_slist* cur = p.commands; + curl_slist* newlist = null; + while (cur) + { + newlist = Curl.curl.slist_append(newlist, cur.data); + cur = cur.next; + } + copy.p.commands = newlist; + copy.p.curl.set(CurlOption.postquote, copy.p.commands); + copy.dataTimeout = _defaultDataTimeout; + return copy; + } + +/ + + /** + Performs the request as configured. + Params: + throwOnError = whether to throw an exception or return a CurlCode on error + */ + CurlCode perform(ThrowOnError throwOnError = Yes.throwOnError) + { + return p.curl.perform(throwOnError); + } + + /// The URL to specify the location of the resource. + @property void url(const(char)[] url) + { + import std.algorithm.searching : startsWith; + import std.uni : toLower; + + auto lowered = url.toLower(); + + if (lowered.startsWith("smtps://")) + { + p.curl.set(CurlOption.use_ssl, CurlUseSSL.all); + } + else + { + enforce!CurlException(lowered.startsWith("smtp://"), + "The url must be for the smtp protocol."); + } + p.curl.set(CurlOption.url, url); + } + + private void initialize() + { + p.curl.initialize(); + p.curl.set(CurlOption.upload, 1L); + dataTimeout = _defaultDataTimeout; + verifyPeer = true; + verifyHost = true; + } + + // This is a workaround for mixed in content not having its + // docs mixed in. + version (StdDdoc) + { + /// Value to return from $(D onSend)/$(D onReceive) delegates in order to + /// pause a request + alias requestPause = CurlReadFunc.pause; + + /// Value to return from onSend delegate in order to abort a request + alias requestAbort = CurlReadFunc.abort; + + /** + True if the instance is stopped. A stopped instance is not usable. + */ + @property bool isStopped(); + + /// Stop and invalidate this instance. + void shutdown(); + + /** Set verbose. + This will print request information to stderr. + */ + @property void verbose(bool on); + + // Connection settings + + /// Set timeout for activity on connection. + @property void dataTimeout(Duration d); + + /** Set maximum time an operation is allowed to take. + This includes dns resolution, connecting, data transfer, etc. + */ + @property void operationTimeout(Duration d); + + /// Set timeout for connecting. + @property void connectTimeout(Duration d); + + // Network settings + + /** Proxy + * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy) + */ + @property void proxy(const(char)[] host); + + /** Proxy port + * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXYPORT, _proxy_port) + */ + @property void proxyPort(ushort port); + + /// Type of proxy + alias CurlProxy = etc.c.curl.CurlProxy; + + /** Proxy type + * See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTPROXY, _proxy_type) + */ + @property void proxyType(CurlProxy type); + + /// DNS lookup timeout. + @property void dnsTimeout(Duration d); + + /** + * The network interface to use in form of the the IP of the interface. + * + * Example: + * ---- + * theprotocol.netInterface = "192.168.1.32"; + * theprotocol.netInterface = [ 192, 168, 1, 32 ]; + * ---- + * + * See: $(REF InternetAddress, std,socket) + */ + @property void netInterface(const(char)[] i); + + /// ditto + @property void netInterface(const(ubyte)[4] i); + + /// ditto + @property void netInterface(InternetAddress i); + + /** + Set the local outgoing port to use. + Params: + port = the first outgoing port number to try and use + */ + @property void localPort(ushort port); + + /** + Set the local outgoing port range to use. + This can be used together with the localPort property. + Params: + range = if the first port is occupied then try this many + port number forwards + */ + @property void localPortRange(ushort range); + + /** Set the tcp no-delay socket option on or off. + See: $(HTTP curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTCPNODELAY, nodelay) + */ + @property void tcpNoDelay(bool on); + + // Authentication settings + + /** + Set the user name, password and optionally domain for authentication + purposes. + + Some protocols may need authentication in some cases. Use this + function to provide credentials. + + Params: + username = the username + password = the password + domain = used for NTLM authentication only and is set to the NTLM domain + name + */ + void setAuthentication(const(char)[] username, const(char)[] password, + const(char)[] domain = ""); + + /** + Set the user name and password for proxy authentication. + + Params: + username = the username + password = the password + */ + void setProxyAuthentication(const(char)[] username, const(char)[] password); + + /** + * The event handler that gets called when data is needed for sending. The + * length of the $(D void[]) specifies the maximum number of bytes that can + * be sent. + * + * Returns: + * The callback returns the number of elements in the buffer that have been + * filled and are ready to send. + * The special value $(D .abortRequest) can be returned in order to abort the + * current request. + * The special value $(D .pauseRequest) can be returned in order to pause the + * current request. + */ + @property void onSend(size_t delegate(void[]) callback); + + /** + * The event handler that receives incoming data. Be sure to copy the + * incoming ubyte[] since it is not guaranteed to be valid after the + * callback returns. + * + * Returns: + * The callback returns the incoming bytes read. If not the entire array is + * the request will abort. + * The special value .pauseRequest can be returned in order to pause the + * current request. + */ + @property void onReceive(size_t delegate(ubyte[]) callback); + + /** + * The event handler that gets called to inform of upload/download progress. + * + * Callback_parameters: + * $(CALLBACK_PARAMS) + * + * Callback_returns: + * Return 0 from the callback to signal success, return non-zero to + * abort transfer. + */ + @property void onProgress(int delegate(size_t dlTotal, size_t dlNow, + size_t ulTotal, size_t ulNow) callback); + } + + /** + Setter for the sender's email address. + */ + @property void mailFrom()(const(char)[] sender) + { + assert(!sender.empty, "Sender must not be empty"); + p.curl.set(CurlOption.mail_from, sender); + } + + /** + Setter for the recipient email addresses. + */ + void mailTo()(const(char)[][] recipients...) + { + assert(!recipients.empty, "Recipient must not be empty"); + curl_slist* recipients_list = null; + foreach (recipient; recipients) + { + recipients_list = + Curl.curl.slist_append(recipients_list, + recipient.tempCString().buffPtr); + } + p.curl.set(CurlOption.mail_rcpt, recipients_list); + } + + /** + Sets the message body text. + */ + + @property void message(string msg) + { + p.message = msg; + } +} + +/++ + Exception thrown on errors in std.net.curl functions. ++/ +class CurlException : Exception +{ + /++ + Params: + msg = The message for the exception. + file = The file where the exception occurred. + line = The line number where the exception occurred. + next = The previous exception in the chain of exceptions, if any. + +/ + @safe pure nothrow + this(string msg, + string file = __FILE__, + size_t line = __LINE__, + Throwable next = null) + { + super(msg, file, line, next); + } +} + +/++ + Exception thrown on timeout errors in std.net.curl functions. ++/ +class CurlTimeoutException : CurlException +{ + /++ + Params: + msg = The message for the exception. + file = The file where the exception occurred. + line = The line number where the exception occurred. + next = The previous exception in the chain of exceptions, if any. + +/ + @safe pure nothrow + this(string msg, + string file = __FILE__, + size_t line = __LINE__, + Throwable next = null) + { + super(msg, file, line, next); + } +} + +/++ + Exception thrown on HTTP request failures, e.g. 404 Not Found. ++/ +class HTTPStatusException : CurlException +{ + /++ + Params: + status = The HTTP status code. + msg = The message for the exception. + file = The file where the exception occurred. + line = The line number where the exception occurred. + next = The previous exception in the chain of exceptions, if any. + +/ + @safe pure nothrow + this(int status, + string msg, + string file = __FILE__, + size_t line = __LINE__, + Throwable next = null) + { + super(msg, file, line, next); + this.status = status; + } + + immutable int status; /// The HTTP status code +} + +/// Equal to $(REF CURLcode, etc,c,curl) +alias CurlCode = CURLcode; + +import std.typecons : Flag, Yes, No; +/// Flag to specify whether or not an exception is thrown on error. +alias ThrowOnError = Flag!"throwOnError"; + +private struct CurlAPI +{ + static struct API + { + extern(C): + import core.stdc.config : c_long; + CURLcode function(c_long flags) global_init; + void function() global_cleanup; + curl_version_info_data * function(CURLversion) version_info; + CURL* function() easy_init; + CURLcode function(CURL *curl, CURLoption option,...) easy_setopt; + CURLcode function(CURL *curl) easy_perform; + CURLcode function(CURL *curl, CURLINFO info,...) easy_getinfo; + CURL* function(CURL *curl) easy_duphandle; + char* function(CURLcode) easy_strerror; + CURLcode function(CURL *handle, int bitmask) easy_pause; + void function(CURL *curl) easy_cleanup; + curl_slist* function(curl_slist *, char *) slist_append; + void function(curl_slist *) slist_free_all; + } + __gshared API _api; + __gshared void* _handle; + + static ref API instance() @property + { + import std.concurrency : initOnce; + initOnce!_handle(loadAPI()); + return _api; + } + + static void* loadAPI() + { + version (Posix) + { + import core.sys.posix.dlfcn : dlsym, dlopen, dlclose, RTLD_LAZY; + alias loadSym = dlsym; + } + else version (Windows) + { + import core.sys.windows.windows : GetProcAddress, GetModuleHandleA, + LoadLibraryA; + alias loadSym = GetProcAddress; + } + else + static assert(0, "unimplemented"); + + void* handle; + version (Posix) + handle = dlopen(null, RTLD_LAZY); + else version (Windows) + handle = GetModuleHandleA(null); + assert(handle !is null); + + // try to load curl from the executable to allow static linking + if (loadSym(handle, "curl_global_init") is null) + { + import std.format : format; + version (Posix) + dlclose(handle); + + version (OSX) + static immutable names = ["libcurl.4.dylib"]; + else version (Posix) + { + static immutable names = ["libcurl.so", "libcurl.so.4", + "libcurl-gnutls.so.4", "libcurl-nss.so.4", "libcurl.so.3"]; + } + else version (Windows) + static immutable names = ["libcurl.dll", "curl.dll"]; + + foreach (name; names) + { + version (Posix) + handle = dlopen(name.ptr, RTLD_LAZY); + else version (Windows) + handle = LoadLibraryA(name.ptr); + if (handle !is null) break; + } + + enforce!CurlException(handle !is null, "Failed to load curl, tried %(%s, %).".format(names)); + } + + foreach (i, FP; typeof(API.tupleof)) + { + enum name = __traits(identifier, _api.tupleof[i]); + auto p = enforce!CurlException(loadSym(handle, "curl_"~name), + "Couldn't load curl_"~name~" from libcurl."); + _api.tupleof[i] = cast(FP) p; + } + + enforce!CurlException(!_api.global_init(CurlGlobal.all), + "Failed to initialize libcurl"); + + static extern(C) void cleanup() + { + if (_handle is null) return; + _api.global_cleanup(); + version (Posix) + { + import core.sys.posix.dlfcn : dlclose; + dlclose(_handle); + } + else version (Windows) + { + import core.sys.windows.windows : FreeLibrary; + FreeLibrary(_handle); + } + else + static assert(0, "unimplemented"); + _api = API.init; + _handle = null; + } + + import core.stdc.stdlib : atexit; + atexit(&cleanup); + + return handle; + } +} + +/** + Wrapper to provide a better interface to libcurl than using the plain C API. + It is recommended to use the $(D HTTP)/$(D FTP) etc. structs instead unless + raw access to libcurl is needed. + + Warning: This struct uses interior pointers for callbacks. Only allocate it + on the stack if you never move or copy it. This also means passing by reference + when passing Curl to other functions. Otherwise always allocate on + the heap. +*/ +struct Curl +{ + alias OutData = void[]; + alias InData = ubyte[]; + private bool _stopped; + + private static auto ref curl() @property { return CurlAPI.instance; } + + // A handle should not be used by two threads simultaneously + private CURL* handle; + + // May also return $(D CURL_READFUNC_ABORT) or $(D CURL_READFUNC_PAUSE) + private size_t delegate(OutData) _onSend; + private size_t delegate(InData) _onReceive; + private void delegate(in char[]) _onReceiveHeader; + private CurlSeek delegate(long,CurlSeekPos) _onSeek; + private int delegate(curl_socket_t,CurlSockType) _onSocketOption; + private int delegate(size_t dltotal, size_t dlnow, + size_t ultotal, size_t ulnow) _onProgress; + + alias requestPause = CurlReadFunc.pause; + alias requestAbort = CurlReadFunc.abort; + + /** + Initialize the instance by creating a working curl handle. + */ + void initialize() + { + enforce!CurlException(!handle, "Curl instance already initialized"); + handle = curl.easy_init(); + enforce!CurlException(handle, "Curl instance couldn't be initialized"); + _stopped = false; + set(CurlOption.nosignal, 1); + } + + /// + @property bool stopped() const + { + return _stopped; + } + + /** + Duplicate this handle. + + The new handle will have all options set as the one it was duplicated + from. An exception to this is that all options that cannot be shared + across threads are reset thereby making it safe to use the duplicate + in a new thread. + */ + Curl dup() + { + Curl copy; + copy.handle = curl.easy_duphandle(handle); + copy._stopped = false; + + with (CurlOption) { + auto tt = AliasSeq!(file, writefunction, writeheader, + headerfunction, infile, readfunction, ioctldata, ioctlfunction, + seekdata, seekfunction, sockoptdata, sockoptfunction, + opensocketdata, opensocketfunction, progressdata, + progressfunction, debugdata, debugfunction, interleavedata, + interleavefunction, chunk_data, chunk_bgn_function, + chunk_end_function, fnmatch_data, fnmatch_function, cookiejar, postfields); + + foreach (option; tt) + copy.clear(option); + } + + // The options are only supported by libcurl when it has been built + // against certain versions of OpenSSL - if your libcurl uses an old + // OpenSSL, or uses an entirely different SSL engine, attempting to + // clear these normally will raise an exception + copy.clearIfSupported(CurlOption.ssl_ctx_function); + copy.clearIfSupported(CurlOption.ssh_keydata); + + // Enable for curl version > 7.21.7 + static if (LIBCURL_VERSION_MAJOR >= 7 && + LIBCURL_VERSION_MINOR >= 21 && + LIBCURL_VERSION_PATCH >= 7) + { + copy.clear(CurlOption.closesocketdata); + copy.clear(CurlOption.closesocketfunction); + } + + copy.set(CurlOption.nosignal, 1); + + // copy.clear(CurlOption.ssl_ctx_data); Let ssl function be shared + // copy.clear(CurlOption.ssh_keyfunction); Let key function be shared + + /* + Allow sharing of conv functions + copy.clear(CurlOption.conv_to_network_function); + copy.clear(CurlOption.conv_from_network_function); + copy.clear(CurlOption.conv_from_utf8_function); + */ + + return copy; + } + + private void _check(CurlCode code) + { + enforce!CurlTimeoutException(code != CurlError.operation_timedout, + errorString(code)); + + enforce!CurlException(code == CurlError.ok, + errorString(code)); + } + + private string errorString(CurlCode code) + { + import core.stdc.string : strlen; + import std.format : format; + + auto msgZ = curl.easy_strerror(code); + // doing the following (instead of just using std.conv.to!string) avoids 1 allocation + return format("%s on handle %s", msgZ[0 .. strlen(msgZ)], handle); + } + + private void throwOnStopped(string message = null) + { + auto def = "Curl instance called after being cleaned up"; + enforce!CurlException(!stopped, + message == null ? def : message); + } + + /** + Stop and invalidate this curl instance. + Warning: Do not call this from inside a callback handler e.g. $(D onReceive). + */ + void shutdown() + { + throwOnStopped(); + _stopped = true; + curl.easy_cleanup(this.handle); + this.handle = null; + } + + /** + Pausing and continuing transfers. + */ + void pause(bool sendingPaused, bool receivingPaused) + { + throwOnStopped(); + _check(curl.easy_pause(this.handle, + (sendingPaused ? CurlPause.send_cont : CurlPause.send) | + (receivingPaused ? CurlPause.recv_cont : CurlPause.recv))); + } + + /** + Set a string curl option. + Params: + option = A $(REF CurlOption, etc,c,curl) as found in the curl documentation + value = The string + */ + void set(CurlOption option, const(char)[] value) + { + throwOnStopped(); + _check(curl.easy_setopt(this.handle, option, value.tempCString().buffPtr)); + } + + /** + Set a long curl option. + Params: + option = A $(REF CurlOption, etc,c,curl) as found in the curl documentation + value = The long + */ + void set(CurlOption option, long value) + { + throwOnStopped(); + _check(curl.easy_setopt(this.handle, option, value)); + } + + /** + Set a void* curl option. + Params: + option = A $(REF CurlOption, etc,c,curl) as found in the curl documentation + value = The pointer + */ + void set(CurlOption option, void* value) + { + throwOnStopped(); + _check(curl.easy_setopt(this.handle, option, value)); + } + + /** + Clear a pointer option. + Params: + option = A $(REF CurlOption, etc,c,curl) as found in the curl documentation + */ + void clear(CurlOption option) + { + throwOnStopped(); + _check(curl.easy_setopt(this.handle, option, null)); + } + + /** + Clear a pointer option. Does not raise an exception if the underlying + libcurl does not support the option. Use sparingly. + Params: + option = A $(REF CurlOption, etc,c,curl) as found in the curl documentation + */ + void clearIfSupported(CurlOption option) + { + throwOnStopped(); + auto rval = curl.easy_setopt(this.handle, option, null); + if (rval != CurlError.unknown_option && rval != CurlError.not_built_in) + _check(rval); + } + + /** + perform the curl request by doing the HTTP,FTP etc. as it has + been setup beforehand. + + Params: + throwOnError = whether to throw an exception or return a CurlCode on error + */ + CurlCode perform(ThrowOnError throwOnError = Yes.throwOnError) + { + throwOnStopped(); + CurlCode code = curl.easy_perform(this.handle); + if (throwOnError) + _check(code); + return code; + } + + /** + Get the various timings like name lookup time, total time, connect time etc. + The timed category is passed through the timing parameter while the timing + value is stored at val. The value is usable only if res is equal to + $(D etc.c.curl.CurlError.ok). + */ + CurlCode getTiming(CurlInfo timing, ref double val) + { + CurlCode code; + code = curl.easy_getinfo(handle, timing, &val); + return code; + } + + /** + * The event handler that receives incoming data. + * + * Params: + * callback = the callback that receives the $(D ubyte[]) data. + * Be sure to copy the incoming data and not store + * a slice. + * + * Returns: + * The callback returns the incoming bytes read. If not the entire array is + * the request will abort. + * The special value HTTP.pauseRequest can be returned in order to pause the + * current request. + * + * Example: + * ---- + * import std.net.curl, std.stdio; + * Curl curl; + * curl.initialize(); + * curl.set(CurlOption.url, "http://dlang.org"); + * curl.onReceive = (ubyte[] data) { writeln("Got data", to!(const(char)[])(data)); return data.length;}; + * curl.perform(); + * ---- + */ + @property void onReceive(size_t delegate(InData) callback) + { + _onReceive = (InData id) + { + throwOnStopped("Receive callback called on cleaned up Curl instance"); + return callback(id); + }; + set(CurlOption.file, cast(void*) &this); + set(CurlOption.writefunction, cast(void*) &Curl._receiveCallback); + } + + /** + * The event handler that receives incoming headers for protocols + * that uses headers. + * + * Params: + * callback = the callback that receives the header string. + * Make sure the callback copies the incoming params if + * it needs to store it because they are references into + * the backend and may very likely change. + * + * Example: + * ---- + * import std.net.curl, std.stdio; + * Curl curl; + * curl.initialize(); + * curl.set(CurlOption.url, "http://dlang.org"); + * curl.onReceiveHeader = (in char[] header) { writeln(header); }; + * curl.perform(); + * ---- + */ + @property void onReceiveHeader(void delegate(in char[]) callback) + { + _onReceiveHeader = (in char[] od) + { + throwOnStopped("Receive header callback called on "~ + "cleaned up Curl instance"); + callback(od); + }; + set(CurlOption.writeheader, cast(void*) &this); + set(CurlOption.headerfunction, + cast(void*) &Curl._receiveHeaderCallback); + } + + /** + * The event handler that gets called when data is needed for sending. + * + * Params: + * callback = the callback that has a $(D void[]) buffer to be filled + * + * Returns: + * The callback returns the number of elements in the buffer that have been + * filled and are ready to send. + * The special value $(D Curl.abortRequest) can be returned in + * order to abort the current request. + * The special value $(D Curl.pauseRequest) can be returned in order to + * pause the current request. + * + * Example: + * ---- + * import std.net.curl; + * Curl curl; + * curl.initialize(); + * curl.set(CurlOption.url, "http://dlang.org"); + * + * string msg = "Hello world"; + * curl.onSend = (void[] data) + * { + * auto m = cast(void[]) msg; + * size_t length = m.length > data.length ? data.length : m.length; + * if (length == 0) return 0; + * data[0 .. length] = m[0 .. length]; + * msg = msg[length..$]; + * return length; + * }; + * curl.perform(); + * ---- + */ + @property void onSend(size_t delegate(OutData) callback) + { + _onSend = (OutData od) + { + throwOnStopped("Send callback called on cleaned up Curl instance"); + return callback(od); + }; + set(CurlOption.infile, cast(void*) &this); + set(CurlOption.readfunction, cast(void*) &Curl._sendCallback); + } + + /** + * The event handler that gets called when the curl backend needs to seek + * the data to be sent. + * + * Params: + * callback = the callback that receives a seek offset and a seek position + * $(REF CurlSeekPos, etc,c,curl) + * + * Returns: + * The callback returns the success state of the seeking + * $(REF CurlSeek, etc,c,curl) + * + * Example: + * ---- + * import std.net.curl; + * Curl curl; + * curl.initialize(); + * curl.set(CurlOption.url, "http://dlang.org"); + * curl.onSeek = (long p, CurlSeekPos sp) + * { + * return CurlSeek.cantseek; + * }; + * curl.perform(); + * ---- + */ + @property void onSeek(CurlSeek delegate(long, CurlSeekPos) callback) + { + _onSeek = (long ofs, CurlSeekPos sp) + { + throwOnStopped("Seek callback called on cleaned up Curl instance"); + return callback(ofs, sp); + }; + set(CurlOption.seekdata, cast(void*) &this); + set(CurlOption.seekfunction, cast(void*) &Curl._seekCallback); + } + + /** + * The event handler that gets called when the net socket has been created + * but a $(D connect()) call has not yet been done. This makes it possible to set + * misc. socket options. + * + * Params: + * callback = the callback that receives the socket and socket type + * $(REF CurlSockType, etc,c,curl) + * + * Returns: + * Return 0 from the callback to signal success, return 1 to signal error + * and make curl close the socket + * + * Example: + * ---- + * import std.net.curl; + * Curl curl; + * curl.initialize(); + * curl.set(CurlOption.url, "http://dlang.org"); + * curl.onSocketOption = delegate int(curl_socket_t s, CurlSockType t) { /+ do stuff +/ }; + * curl.perform(); + * ---- + */ + @property void onSocketOption(int delegate(curl_socket_t, + CurlSockType) callback) + { + _onSocketOption = (curl_socket_t sock, CurlSockType st) + { + throwOnStopped("Socket option callback called on "~ + "cleaned up Curl instance"); + return callback(sock, st); + }; + set(CurlOption.sockoptdata, cast(void*) &this); + set(CurlOption.sockoptfunction, + cast(void*) &Curl._socketOptionCallback); + } + + /** + * The event handler that gets called to inform of upload/download progress. + * + * Params: + * callback = the callback that receives the (total bytes to download, + * currently downloaded bytes, total bytes to upload, currently uploaded + * bytes). + * + * Returns: + * Return 0 from the callback to signal success, return non-zero to abort + * transfer + * + * Example: + * ---- + * import std.net.curl; + * Curl curl; + * curl.initialize(); + * curl.set(CurlOption.url, "http://dlang.org"); + * curl.onProgress = delegate int(size_t dltotal, size_t dlnow, size_t ultotal, size_t uln) + * { + * writeln("Progress: downloaded bytes ", dlnow, " of ", dltotal); + * writeln("Progress: uploaded bytes ", ulnow, " of ", ultotal); + * curl.perform(); + * }; + * ---- + */ + @property void onProgress(int delegate(size_t dlTotal, + size_t dlNow, + size_t ulTotal, + size_t ulNow) callback) + { + _onProgress = (size_t dlt, size_t dln, size_t ult, size_t uln) + { + throwOnStopped("Progress callback called on cleaned "~ + "up Curl instance"); + return callback(dlt, dln, ult, uln); + }; + set(CurlOption.noprogress, 0); + set(CurlOption.progressdata, cast(void*) &this); + set(CurlOption.progressfunction, cast(void*) &Curl._progressCallback); + } + + // Internal C callbacks to register with libcurl + extern (C) private static + size_t _receiveCallback(const char* str, + size_t size, size_t nmemb, void* ptr) + { + auto b = cast(Curl*) ptr; + if (b._onReceive != null) + return b._onReceive(cast(InData)(str[0 .. size*nmemb])); + return size*nmemb; + } + + extern (C) private static + size_t _receiveHeaderCallback(const char* str, + size_t size, size_t nmemb, void* ptr) + { + import std.string : chomp; + + auto b = cast(Curl*) ptr; + auto s = str[0 .. size*nmemb].chomp(); + if (b._onReceiveHeader != null) + b._onReceiveHeader(s); + + return size*nmemb; + } + + extern (C) private static + size_t _sendCallback(char *str, size_t size, size_t nmemb, void *ptr) + { + Curl* b = cast(Curl*) ptr; + auto a = cast(void[]) str[0 .. size*nmemb]; + if (b._onSend == null) + return 0; + return b._onSend(a); + } + + extern (C) private static + int _seekCallback(void *ptr, curl_off_t offset, int origin) + { + auto b = cast(Curl*) ptr; + if (b._onSeek == null) + return CurlSeek.cantseek; + + // origin: CurlSeekPos.set/current/end + // return: CurlSeek.ok/fail/cantseek + return b._onSeek(cast(long) offset, cast(CurlSeekPos) origin); + } + + extern (C) private static + int _socketOptionCallback(void *ptr, + curl_socket_t curlfd, curlsocktype purpose) + { + auto b = cast(Curl*) ptr; + if (b._onSocketOption == null) + return 0; + + // return: 0 ok, 1 fail + return b._onSocketOption(curlfd, cast(CurlSockType) purpose); + } + + extern (C) private static + int _progressCallback(void *ptr, + double dltotal, double dlnow, + double ultotal, double ulnow) + { + auto b = cast(Curl*) ptr; + if (b._onProgress == null) + return 0; + + // return: 0 ok, 1 fail + return b._onProgress(cast(size_t) dltotal, cast(size_t) dlnow, + cast(size_t) ultotal, cast(size_t) ulnow); + } + +} + +// Internal messages send between threads. +// The data is wrapped in this struct in order to ensure that +// other std.concurrency.receive calls does not pick up our messages +// by accident. +private struct CurlMessage(T) +{ + public T data; +} + +private static CurlMessage!T curlMessage(T)(T data) +{ + return CurlMessage!T(data); +} + +// Pool of to be used for reusing buffers +private struct Pool(Data) +{ + private struct Entry + { + Data data; + Entry* next; + } + private Entry* root; + private Entry* freeList; + + @safe @property bool empty() + { + return root == null; + } + + @safe nothrow void push(Data d) + { + if (freeList == null) + { + // Allocate new Entry since there is no one + // available in the freeList + freeList = new Entry; + } + freeList.data = d; + Entry* oldroot = root; + root = freeList; + freeList = freeList.next; + root.next = oldroot; + } + + @safe Data pop() + { + enforce!Exception(root != null, "pop() called on empty pool"); + auto d = root.data; + auto n = root.next; + root.next = freeList; + freeList = root; + root = n; + return d; + } +} + +// Shared function for reading incoming chunks of data and +// sending the to a parent thread +private static size_t _receiveAsyncChunks(ubyte[] data, ref ubyte[] outdata, + Pool!(ubyte[]) freeBuffers, + ref ubyte[] buffer, Tid fromTid, + ref bool aborted) +{ + immutable datalen = data.length; + + // Copy data to fill active buffer + while (!data.empty) + { + + // Make sure a buffer is present + while ( outdata.empty && freeBuffers.empty) + { + // Active buffer is invalid and there are no + // available buffers in the pool. Wait for buffers + // to return from main thread in order to reuse + // them. + receive((immutable(ubyte)[] buf) + { + buffer = cast(ubyte[]) buf; + outdata = buffer[]; + }, + (bool flag) { aborted = true; } + ); + if (aborted) return cast(size_t) 0; + } + if (outdata.empty) + { + buffer = freeBuffers.pop(); + outdata = buffer[]; + } + + // Copy data + auto copyBytes = outdata.length < data.length ? + outdata.length : data.length; + + outdata[0 .. copyBytes] = data[0 .. copyBytes]; + outdata = outdata[copyBytes..$]; + data = data[copyBytes..$]; + + if (outdata.empty) + fromTid.send(thisTid, curlMessage(cast(immutable(ubyte)[])buffer)); + } + + return datalen; +} + +// ditto +private static void _finalizeAsyncChunks(ubyte[] outdata, ref ubyte[] buffer, + Tid fromTid) +{ + if (!outdata.empty) + { + // Resize the last buffer + buffer.length = buffer.length - outdata.length; + fromTid.send(thisTid, curlMessage(cast(immutable(ubyte)[])buffer)); + } +} + + +// Shared function for reading incoming lines of data and sending the to a +// parent thread +private static size_t _receiveAsyncLines(Terminator, Unit) + (const(ubyte)[] data, ref EncodingScheme encodingScheme, + bool keepTerminator, Terminator terminator, + ref const(ubyte)[] leftOverBytes, ref bool bufferValid, + ref Pool!(Unit[]) freeBuffers, ref Unit[] buffer, + Tid fromTid, ref bool aborted) +{ + import std.format : format; + + immutable datalen = data.length; + + // Terminator is specified and buffers should be resized as determined by + // the terminator + + // Copy data to active buffer until terminator is found. + + // Decode as many lines as possible + while (true) + { + + // Make sure a buffer is present + while (!bufferValid && freeBuffers.empty) + { + // Active buffer is invalid and there are no available buffers in + // the pool. Wait for buffers to return from main thread in order to + // reuse them. + receive((immutable(Unit)[] buf) + { + buffer = cast(Unit[]) buf; + buffer.length = 0; + buffer.assumeSafeAppend(); + bufferValid = true; + }, + (bool flag) { aborted = true; } + ); + if (aborted) return cast(size_t) 0; + } + if (!bufferValid) + { + buffer = freeBuffers.pop(); + bufferValid = true; + } + + // Try to read a line from left over bytes from last onReceive plus the + // newly received bytes. + try + { + if (decodeLineInto(leftOverBytes, data, buffer, + encodingScheme, terminator)) + { + if (keepTerminator) + { + fromTid.send(thisTid, + curlMessage(cast(immutable(Unit)[])buffer)); + } + else + { + static if (isArray!Terminator) + fromTid.send(thisTid, + curlMessage(cast(immutable(Unit)[]) + buffer[0..$-terminator.length])); + else + fromTid.send(thisTid, + curlMessage(cast(immutable(Unit)[]) + buffer[0..$-1])); + } + bufferValid = false; + } + else + { + // Could not decode an entire line. Save + // bytes left in data for next call to + // onReceive. Can be up to a max of 4 bytes. + enforce!CurlException(data.length <= 4, + format( + "Too many bytes left not decoded %s"~ + " > 4. Maybe the charset specified in"~ + " headers does not match "~ + "the actual content downloaded?", + data.length)); + leftOverBytes ~= data; + break; + } + } + catch (CurlException ex) + { + prioritySend(fromTid, cast(immutable(CurlException))ex); + return cast(size_t) 0; + } + } + return datalen; +} + +// ditto +private static +void _finalizeAsyncLines(Unit)(bool bufferValid, Unit[] buffer, Tid fromTid) +{ + if (bufferValid && buffer.length != 0) + fromTid.send(thisTid, curlMessage(cast(immutable(Unit)[])buffer[0..$])); +} + + +// Spawn a thread for handling the reading of incoming data in the +// background while the delegate is executing. This will optimize +// throughput by allowing simultaneous input (this struct) and +// output (e.g. AsyncHTTPLineOutputRange). +private static void _spawnAsync(Conn, Unit, Terminator = void)() +{ + Tid fromTid = receiveOnly!Tid(); + + // Get buffer to read into + Pool!(Unit[]) freeBuffers; // Free list of buffer objects + + // Number of bytes filled into active buffer + Unit[] buffer; + bool aborted = false; + + EncodingScheme encodingScheme; + static if ( !is(Terminator == void)) + { + // Only lines reading will receive a terminator + const terminator = receiveOnly!Terminator(); + const keepTerminator = receiveOnly!bool(); + + // max number of bytes to carry over from an onReceive + // callback. This is 4 because it is the max code units to + // decode a code point in the supported encodings. + auto leftOverBytes = new const(ubyte)[4]; + leftOverBytes.length = 0; + auto bufferValid = false; + } + else + { + Unit[] outdata; + } + + // no move semantic available in std.concurrency ie. must use casting. + auto connDup = cast(CURL*) receiveOnly!ulong(); + auto client = Conn(); + client.p.curl.handle = connDup; + + // receive a method for both ftp and http but just use it for http + auto method = receiveOnly!(HTTP.Method)(); + + client.onReceive = (ubyte[] data) + { + // If no terminator is specified the chunk size is fixed. + static if ( is(Terminator == void) ) + return _receiveAsyncChunks(data, outdata, freeBuffers, buffer, + fromTid, aborted); + else + return _receiveAsyncLines(data, encodingScheme, + keepTerminator, terminator, leftOverBytes, + bufferValid, freeBuffers, buffer, + fromTid, aborted); + }; + + static if ( is(Conn == HTTP) ) + { + client.method = method; + // register dummy header handler + client.onReceiveHeader = (in char[] key, in char[] value) + { + if (key == "content-type") + encodingScheme = EncodingScheme.create(client.p.charset); + }; + } + else + { + encodingScheme = EncodingScheme.create(client.encoding); + } + + // Start the request + CurlCode code; + try + { + code = client.perform(No.throwOnError); + } + catch (Exception ex) + { + prioritySend(fromTid, cast(immutable(Exception)) ex); + fromTid.send(thisTid, curlMessage(true)); // signal done + return; + } + + if (code != CurlError.ok) + { + if (aborted && (code == CurlError.aborted_by_callback || + code == CurlError.write_error)) + { + fromTid.send(thisTid, curlMessage(true)); // signal done + return; + } + prioritySend(fromTid, cast(immutable(CurlException)) + new CurlException(client.p.curl.errorString(code))); + + fromTid.send(thisTid, curlMessage(true)); // signal done + return; + } + + // Send remaining data that is not a full chunk size + static if ( is(Terminator == void) ) + _finalizeAsyncChunks(outdata, buffer, fromTid); + else + _finalizeAsyncLines(bufferValid, buffer, fromTid); + + fromTid.send(thisTid, curlMessage(true)); // signal done +} diff --git a/libphobos/src/std/net/isemail.d b/libphobos/src/std/net/isemail.d new file mode 100644 index 0000000..cd6aa41 --- /dev/null +++ b/libphobos/src/std/net/isemail.d @@ -0,0 +1,1864 @@ +/** + * Validates an email address according to RFCs 5321, 5322 and others. + * + * Authors: Dominic Sayers $(LT)dominic@sayers.cc$(GT), Jacob Carlborg + * Copyright: Dominic Sayers, Jacob Carlborg 2008-. + * Test schema documentation: Copyright © 2011, Daniel Marschall + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) + * Version: 3.0.13 - Version 3.0 of the original PHP implementation: $(LINK http://www.dominicsayers.com/isemail) + * + * Standards: + * $(UL + * $(LI RFC 5321) + * $(LI RFC 5322) + * ) + * + * References: + * $(UL + * $(LI $(LINK http://www.dominicsayers.com/isemail)) + * $(LI $(LINK http://tools.ietf.org/html/rfc5321)) + * $(LI $(LINK http://tools.ietf.org/html/rfc5322)) + * ) + * + * Source: $(PHOBOSSRC std/net/_isemail.d) + */ +module std.net.isemail; + +// FIXME +import std.range.primitives; // : ElementType; +import std.regex; +import std.traits; +import std.typecons : Flag, Yes, No; + +/** + * Check that an email address conforms to RFCs 5321, 5322 and others. + * + * Distinguishes between a Mailbox as defined by RFC 5321 and an addr-spec as + * defined by RFC 5322. Depending on the context, either can be regarded as a + * valid email address. + * + * Note: The DNS check is currently not implemented. + * + * Params: + * email = The email address to check + * checkDNS = If $(D Yes.checkDns) then a DNS check for MX records will be made + * errorLevel = Determines the boundary between valid and invalid addresses. + * Status codes above this number will be returned as-is, + * status codes below will be returned as EmailStatusCode.valid. + * Thus the calling program can simply look for EmailStatusCode.valid + * if it is only interested in whether an address is valid or not. The + * $(D_PARAM errorLevel) will determine how "picky" isEmail() is about + * the address. + * + * If omitted or passed as EmailStatusCode.none then isEmail() will + * not perform any finer grained error checking and an address is + * either considered valid or not. Email status code will either be + * EmailStatusCode.valid or EmailStatusCode.error. + * + * Returns: + * An $(LREF EmailStatus), indicating the status of the email address. + */ +EmailStatus isEmail(Char)(const(Char)[] email, CheckDns checkDNS = No.checkDns, +EmailStatusCode errorLevel = EmailStatusCode.none) +if (isSomeChar!(Char)) +{ + import std.algorithm.iteration : uniq, filter, map; + import std.algorithm.searching : canFind, maxElement; + import std.array : array, split; + import std.conv : to; + import std.exception : enforce; + import std.string : indexOf, lastIndexOf; + import std.uni : isNumber; + + alias tstring = const(Char)[]; + alias Token = TokenImpl!(Char); + + static ipRegex = ctRegex!(`\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}`~ + `(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$`.to!(const(Char)[])); + static fourChars = ctRegex!(`^[0-9A-Fa-f]{0,4}$`.to!(const(Char)[])); + + enum defaultThreshold = 16; + int threshold; + bool diagnose; + + if (errorLevel == EmailStatusCode.any) + { + threshold = EmailStatusCode.valid; + diagnose = true; + } + + else if (errorLevel == EmailStatusCode.none) + threshold = defaultThreshold; + + else + { + diagnose = true; + + switch (errorLevel) + { + case EmailStatusCode.warning: threshold = defaultThreshold; break; + case EmailStatusCode.error: threshold = EmailStatusCode.valid; break; + default: threshold = errorLevel; + } + } + + auto returnStatus = [EmailStatusCode.valid]; + auto context = EmailPart.componentLocalPart; + auto contextStack = [context]; + auto contextPrior = context; + tstring token = ""; + tstring tokenPrior = ""; + tstring[EmailPart] parseData = [EmailPart.componentLocalPart : "", EmailPart.componentDomain : ""]; + tstring[][EmailPart] atomList = [EmailPart.componentLocalPart : [""], EmailPart.componentDomain : [""]]; + auto elementCount = 0; + auto elementLength = 0; + auto hyphenFlag = false; + auto endOrDie = false; + auto crlfCount = int.min; // int.min == not defined + + foreach (ref i, e ; email) + { + token = email.get(i, e); + + switch (context) + { + case EmailPart.componentLocalPart: + switch (token) + { + case Token.openParenthesis: + if (elementLength == 0) + returnStatus ~= elementCount == 0 ? EmailStatusCode.comment : + EmailStatusCode.deprecatedComment; + + else + { + returnStatus ~= EmailStatusCode.comment; + endOrDie = true; + } + + contextStack ~= context; + context = EmailPart.contextComment; + break; + + case Token.dot: + if (elementLength == 0) + returnStatus ~= elementCount == 0 ? EmailStatusCode.errorDotStart : + EmailStatusCode.errorConsecutiveDots; + + else + { + if (endOrDie) + returnStatus ~= EmailStatusCode.deprecatedLocalPart; + } + + endOrDie = false; + elementLength = 0; + elementCount++; + parseData[EmailPart.componentLocalPart] ~= token; + + if (elementCount >= atomList[EmailPart.componentLocalPart].length) + atomList[EmailPart.componentLocalPart] ~= ""; + + else + atomList[EmailPart.componentLocalPart][elementCount] = ""; + break; + + case Token.doubleQuote: + if (elementLength == 0) + { + returnStatus ~= elementCount == 0 ? EmailStatusCode.rfc5321QuotedString : + EmailStatusCode.deprecatedLocalPart; + + parseData[EmailPart.componentLocalPart] ~= token; + atomList[EmailPart.componentLocalPart][elementCount] ~= token; + elementLength++; + endOrDie = true; + contextStack ~= context; + context = EmailPart.contextQuotedString; + } + + else + returnStatus ~= EmailStatusCode.errorExpectingText; + break; + + case Token.cr: + case Token.space: + case Token.tab: + if ((token == Token.cr) && ((++i == email.length) || (email.get(i, e) != Token.lf))) + { + returnStatus ~= EmailStatusCode.errorCrNoLf; + break; + } + + if (elementLength == 0) + returnStatus ~= elementCount == 0 ? EmailStatusCode.foldingWhitespace : + EmailStatusCode.deprecatedFoldingWhitespace; + + else + endOrDie = true; + + contextStack ~= context; + context = EmailPart.contextFoldingWhitespace; + tokenPrior = token; + break; + + case Token.at: + enforce(contextStack.length == 1, "Unexpected item on context stack"); + + if (parseData[EmailPart.componentLocalPart] == "") + returnStatus ~= EmailStatusCode.errorNoLocalPart; + + else if (elementLength == 0) + returnStatus ~= EmailStatusCode.errorDotEnd; + + else if (parseData[EmailPart.componentLocalPart].length > 64) + returnStatus ~= EmailStatusCode.rfc5322LocalTooLong; + + else if (contextPrior == EmailPart.contextComment || + contextPrior == EmailPart.contextFoldingWhitespace) + returnStatus ~= EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt; + + context = EmailPart.componentDomain; + contextStack = [context]; + elementCount = 0; + elementLength = 0; + endOrDie = false; + break; + + default: + if (endOrDie) + { + switch (contextPrior) + { + case EmailPart.contextComment: + case EmailPart.contextFoldingWhitespace: + returnStatus ~= EmailStatusCode.errorTextAfterCommentFoldingWhitespace; + break; + + case EmailPart.contextQuotedString: + returnStatus ~= EmailStatusCode.errorTextAfterQuotedString; + break; + + default: + throw new Exception("More text found where none is allowed, but " + ~"unrecognised prior context: " ~ to!(string)(contextPrior)); + } + } + + else + { + contextPrior = context; + immutable c = token.front; + + if (c < '!' || c > '~' || c == '\n' || Token.specials.canFind(token)) + returnStatus ~= EmailStatusCode.errorExpectingText; + + parseData[EmailPart.componentLocalPart] ~= token; + atomList[EmailPart.componentLocalPart][elementCount] ~= token; + elementLength++; + } + } + break; + + case EmailPart.componentDomain: + switch (token) + { + case Token.openParenthesis: + if (elementLength == 0) + { + returnStatus ~= elementCount == 0 ? + EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt + : EmailStatusCode.deprecatedComment; + } + else + { + returnStatus ~= EmailStatusCode.comment; + endOrDie = true; + } + + contextStack ~= context; + context = EmailPart.contextComment; + break; + + case Token.dot: + if (elementLength == 0) + returnStatus ~= elementCount == 0 ? EmailStatusCode.errorDotStart : + EmailStatusCode.errorConsecutiveDots; + + else if (hyphenFlag) + returnStatus ~= EmailStatusCode.errorDomainHyphenEnd; + + else + { + if (elementLength > 63) + returnStatus ~= EmailStatusCode.rfc5322LabelTooLong; + } + + endOrDie = false; + elementLength = 0; + elementCount++; + + //atomList[EmailPart.componentDomain][elementCount] = ""; + atomList[EmailPart.componentDomain] ~= ""; + parseData[EmailPart.componentDomain] ~= token; + break; + + case Token.openBracket: + if (parseData[EmailPart.componentDomain] == "") + { + endOrDie = true; + elementLength++; + contextStack ~= context; + context = EmailPart.componentLiteral; + parseData[EmailPart.componentDomain] ~= token; + atomList[EmailPart.componentDomain][elementCount] ~= token; + parseData[EmailPart.componentLiteral] = ""; + } + + else + returnStatus ~= EmailStatusCode.errorExpectingText; + break; + + case Token.cr: + case Token.space: + case Token.tab: + if (token == Token.cr && (++i == email.length || email.get(i, e) != Token.lf)) + { + returnStatus ~= EmailStatusCode.errorCrNoLf; + break; + } + + if (elementLength == 0) + { + returnStatus ~= elementCount == 0 ? + EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt + : EmailStatusCode.deprecatedFoldingWhitespace; + } + else + { + returnStatus ~= EmailStatusCode.foldingWhitespace; + endOrDie = true; + } + + contextStack ~= context; + context = EmailPart.contextFoldingWhitespace; + tokenPrior = token; + break; + + default: + if (endOrDie) + { + switch (contextPrior) + { + case EmailPart.contextComment: + case EmailPart.contextFoldingWhitespace: + returnStatus ~= EmailStatusCode.errorTextAfterCommentFoldingWhitespace; + break; + + case EmailPart.componentLiteral: + returnStatus ~= EmailStatusCode.errorTextAfterDomainLiteral; + break; + + default: + throw new Exception("More text found where none is allowed, but " + ~"unrecognised prior context: " ~ to!(string)(contextPrior)); + } + + } + + immutable c = token.front; + hyphenFlag = false; + + if (c < '!' || c > '~' || Token.specials.canFind(token)) + returnStatus ~= EmailStatusCode.errorExpectingText; + + else if (token == Token.hyphen) + { + if (elementLength == 0) + returnStatus ~= EmailStatusCode.errorDomainHyphenStart; + + hyphenFlag = true; + } + + else if (!((c > '/' && c < ':') || (c > '@' && c < '[') || (c > '`' && c < '{'))) + returnStatus ~= EmailStatusCode.rfc5322Domain; + + parseData[EmailPart.componentDomain] ~= token; + atomList[EmailPart.componentDomain][elementCount] ~= token; + elementLength++; + } + break; + + case EmailPart.componentLiteral: + switch (token) + { + case Token.closeBracket: + if (returnStatus.maxElement() < EmailStatusCode.deprecated_) + { + auto maxGroups = 8; + size_t index = -1; + auto addressLiteral = parseData[EmailPart.componentLiteral]; + auto matchesIp = addressLiteral.matchAll(ipRegex).map!(a => a.hit).array; + + if (!matchesIp.empty) + { + index = addressLiteral.lastIndexOf(matchesIp.front); + + if (index != 0) + addressLiteral = addressLiteral[0 .. index] ~ "0:0"; + } + + if (index == 0) + returnStatus ~= EmailStatusCode.rfc5321AddressLiteral; + + else if (addressLiteral.compareFirstN(Token.ipV6Tag, 5)) + returnStatus ~= EmailStatusCode.rfc5322DomainLiteral; + + else + { + auto ipV6 = addressLiteral[5 .. $]; + matchesIp = ipV6.split(Token.colon); + immutable groupCount = matchesIp.length; + index = ipV6.indexOf(Token.doubleColon); + + if (index == -1) + { + if (groupCount != maxGroups) + returnStatus ~= EmailStatusCode.rfc5322IpV6GroupCount; + } + + else + { + if (index != ipV6.lastIndexOf(Token.doubleColon)) + returnStatus ~= EmailStatusCode.rfc5322IpV6TooManyDoubleColons; + + else + { + if (index == 0 || index == (ipV6.length - 2)) + maxGroups++; + + if (groupCount > maxGroups) + returnStatus ~= EmailStatusCode.rfc5322IpV6MaxGroups; + + else if (groupCount == maxGroups) + returnStatus ~= EmailStatusCode.rfc5321IpV6Deprecated; + } + } + + if (ipV6[0 .. 1] == Token.colon && ipV6[1 .. 2] != Token.colon) + returnStatus ~= EmailStatusCode.rfc5322IpV6ColonStart; + + else if (ipV6[$ - 1 .. $] == Token.colon && ipV6[$ - 2 .. $ - 1] != Token.colon) + returnStatus ~= EmailStatusCode.rfc5322IpV6ColonEnd; + + else if (!matchesIp + .filter!(a => a.matchFirst(fourChars).empty) + .empty) + returnStatus ~= EmailStatusCode.rfc5322IpV6BadChar; + + else + returnStatus ~= EmailStatusCode.rfc5321AddressLiteral; + } + } + + else + returnStatus ~= EmailStatusCode.rfc5322DomainLiteral; + + parseData[EmailPart.componentDomain] ~= token; + atomList[EmailPart.componentDomain][elementCount] ~= token; + elementLength++; + contextPrior = context; + context = contextStack.pop(); + break; + + case Token.backslash: + returnStatus ~= EmailStatusCode.rfc5322DomainLiteralObsoleteText; + contextStack ~= context; + context = EmailPart.contextQuotedPair; + break; + + case Token.cr: + case Token.space: + case Token.tab: + if (token == Token.cr && (++i == email.length || email.get(i, e) != Token.lf)) + { + returnStatus ~= EmailStatusCode.errorCrNoLf; + break; + } + + returnStatus ~= EmailStatusCode.foldingWhitespace; + contextStack ~= context; + context = EmailPart.contextFoldingWhitespace; + tokenPrior = token; + break; + + default: + immutable c = token.front; + + if (c > AsciiToken.delete_ || c == '\0' || token == Token.openBracket) + { + returnStatus ~= EmailStatusCode.errorExpectingDomainText; + break; + } + + else if (c < '!' || c == AsciiToken.delete_ ) + returnStatus ~= EmailStatusCode.rfc5322DomainLiteralObsoleteText; + + parseData[EmailPart.componentLiteral] ~= token; + parseData[EmailPart.componentDomain] ~= token; + atomList[EmailPart.componentDomain][elementCount] ~= token; + elementLength++; + } + break; + + case EmailPart.contextQuotedString: + switch (token) + { + case Token.backslash: + contextStack ~= context; + context = EmailPart.contextQuotedPair; + break; + + case Token.cr: + case Token.tab: + if (token == Token.cr && (++i == email.length || email.get(i, e) != Token.lf)) + { + returnStatus ~= EmailStatusCode.errorCrNoLf; + break; + } + + parseData[EmailPart.componentLocalPart] ~= Token.space; + atomList[EmailPart.componentLocalPart][elementCount] ~= Token.space; + elementLength++; + + returnStatus ~= EmailStatusCode.foldingWhitespace; + contextStack ~= context; + context = EmailPart.contextFoldingWhitespace; + tokenPrior = token; + break; + + case Token.doubleQuote: + parseData[EmailPart.componentLocalPart] ~= token; + atomList[EmailPart.componentLocalPart][elementCount] ~= token; + elementLength++; + contextPrior = context; + context = contextStack.pop(); + break; + + default: + immutable c = token.front; + + if (c > AsciiToken.delete_ || c == '\0' || c == '\n') + returnStatus ~= EmailStatusCode.errorExpectingQuotedText; + + else if (c < ' ' || c == AsciiToken.delete_) + returnStatus ~= EmailStatusCode.deprecatedQuotedText; + + parseData[EmailPart.componentLocalPart] ~= token; + atomList[EmailPart.componentLocalPart][elementCount] ~= token; + elementLength++; + } + break; + + case EmailPart.contextQuotedPair: + immutable c = token.front; + + if (c > AsciiToken.delete_) + returnStatus ~= EmailStatusCode.errorExpectingQuotedPair; + + else if (c < AsciiToken.unitSeparator && c != AsciiToken.horizontalTab || c == AsciiToken.delete_) + returnStatus ~= EmailStatusCode.deprecatedQuotedPair; + + contextPrior = context; + context = contextStack.pop(); + token = Token.backslash ~ token; + + switch (context) + { + case EmailPart.contextComment: break; + + case EmailPart.contextQuotedString: + parseData[EmailPart.componentLocalPart] ~= token; + atomList[EmailPart.componentLocalPart][elementCount] ~= token; + elementLength += 2; + break; + + case EmailPart.componentLiteral: + parseData[EmailPart.componentDomain] ~= token; + atomList[EmailPart.componentDomain][elementCount] ~= token; + elementLength += 2; + break; + + default: + throw new Exception("Quoted pair logic invoked in an invalid context: " ~ to!(string)(context)); + } + break; + + case EmailPart.contextComment: + switch (token) + { + case Token.openParenthesis: + contextStack ~= context; + context = EmailPart.contextComment; + break; + + case Token.closeParenthesis: + contextPrior = context; + context = contextStack.pop(); + break; + + case Token.backslash: + contextStack ~= context; + context = EmailPart.contextQuotedPair; + break; + + case Token.cr: + case Token.space: + case Token.tab: + if (token == Token.cr && (++i == email.length || email.get(i, e) != Token.lf)) + { + returnStatus ~= EmailStatusCode.errorCrNoLf; + break; + } + + returnStatus ~= EmailStatusCode.foldingWhitespace; + + contextStack ~= context; + context = EmailPart.contextFoldingWhitespace; + tokenPrior = token; + break; + + default: + immutable c = token.front; + + if (c > AsciiToken.delete_ || c == '\0' || c == '\n') + { + returnStatus ~= EmailStatusCode.errorExpectingCommentText; + break; + } + + else if (c < ' ' || c == AsciiToken.delete_) + returnStatus ~= EmailStatusCode.deprecatedCommentText; + } + break; + + case EmailPart.contextFoldingWhitespace: + if (tokenPrior == Token.cr) + { + if (token == Token.cr) + { + returnStatus ~= EmailStatusCode.errorFoldingWhitespaceCrflX2; + break; + } + + if (crlfCount != int.min) // int.min == not defined + { + if (++crlfCount > 1) + returnStatus ~= EmailStatusCode.deprecatedFoldingWhitespace; + } + + else + crlfCount = 1; + } + + switch (token) + { + case Token.cr: + if (++i == email.length || email.get(i, e) != Token.lf) + returnStatus ~= EmailStatusCode.errorCrNoLf; + break; + + case Token.space: + case Token.tab: + break; + + default: + if (tokenPrior == Token.cr) + { + returnStatus ~= EmailStatusCode.errorFoldingWhitespaceCrLfEnd; + break; + } + + crlfCount = int.min; // int.min == not defined + contextPrior = context; + context = contextStack.pop(); + i--; + break; + } + + tokenPrior = token; + break; + + default: + throw new Exception("Unkown context: " ~ to!(string)(context)); + } + + if (returnStatus.maxElement() > EmailStatusCode.rfc5322) + break; + } + + if (returnStatus.maxElement() < EmailStatusCode.rfc5322) + { + if (context == EmailPart.contextQuotedString) + returnStatus ~= EmailStatusCode.errorUnclosedQuotedString; + + else if (context == EmailPart.contextQuotedPair) + returnStatus ~= EmailStatusCode.errorBackslashEnd; + + else if (context == EmailPart.contextComment) + returnStatus ~= EmailStatusCode.errorUnclosedComment; + + else if (context == EmailPart.componentLiteral) + returnStatus ~= EmailStatusCode.errorUnclosedDomainLiteral; + + else if (token == Token.cr) + returnStatus ~= EmailStatusCode.errorFoldingWhitespaceCrLfEnd; + + else if (parseData[EmailPart.componentDomain] == "") + returnStatus ~= EmailStatusCode.errorNoDomain; + + else if (elementLength == 0) + returnStatus ~= EmailStatusCode.errorDotEnd; + + else if (hyphenFlag) + returnStatus ~= EmailStatusCode.errorDomainHyphenEnd; + + else if (parseData[EmailPart.componentDomain].length > 255) + returnStatus ~= EmailStatusCode.rfc5322DomainTooLong; + + else if ((parseData[EmailPart.componentLocalPart] ~ Token.at ~ parseData[EmailPart.componentDomain]).length > + 254) + returnStatus ~= EmailStatusCode.rfc5322TooLong; + + else if (elementLength > 63) + returnStatus ~= EmailStatusCode.rfc5322LabelTooLong; + } + + auto dnsChecked = false; + + if (checkDNS == Yes.checkDns && returnStatus.maxElement() < EmailStatusCode.dnsWarning) + { + assert(false, "DNS check is currently not implemented"); + } + + if (!dnsChecked && returnStatus.maxElement() < EmailStatusCode.dnsWarning) + { + if (elementCount == 0) + returnStatus ~= EmailStatusCode.rfc5321TopLevelDomain; + + if (isNumber(atomList[EmailPart.componentDomain][elementCount].front)) + returnStatus ~= EmailStatusCode.rfc5321TopLevelDomainNumeric; + } + + returnStatus = array(uniq(returnStatus)); + auto finalStatus = returnStatus.maxElement(); + + if (returnStatus.length != 1) + returnStatus.popFront(); + + parseData[EmailPart.status] = to!(tstring)(returnStatus); + + if (finalStatus < threshold) + finalStatus = EmailStatusCode.valid; + + if (!diagnose) + finalStatus = finalStatus < threshold ? EmailStatusCode.valid : EmailStatusCode.error; + + auto valid = finalStatus == EmailStatusCode.valid; + tstring localPart = ""; + tstring domainPart = ""; + + if (auto value = EmailPart.componentLocalPart in parseData) + localPart = *value; + + if (auto value = EmailPart.componentDomain in parseData) + domainPart = *value; + + return EmailStatus(valid, to!(string)(localPart), to!(string)(domainPart), finalStatus); +} + +@safe unittest +{ + assert(`test.test@iana.org`.isEmail(No.checkDns).statusCode == EmailStatusCode.valid); + assert(`test.test@iana.org`.isEmail(No.checkDns, EmailStatusCode.none).statusCode == EmailStatusCode.valid); + + assert(`test@[IPv6:1111:2222:3333:4444:5555:6666::8888]`.isEmail(No.checkDns, + EmailStatusCode.none).statusCode == EmailStatusCode.valid); + + assert(`test`.isEmail(No.checkDns, EmailStatusCode.none).statusCode == EmailStatusCode.error); + assert(`(comment)test@iana.org`.isEmail(No.checkDns, EmailStatusCode.none).statusCode == EmailStatusCode.error); + + assert(``.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoDomain); + assert(`test`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoDomain); + assert(`@`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoLocalPart); + assert(`test@`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoDomain); + + // assert(`test@io`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid, + // `io. currently has an MX-record (Feb 2011). Some DNS setups seem to find it, some don't.` + // ` If you don't see the MX for io. then try setting your DNS server to 8.8.8.8 (the Google DNS server)`); + + assert(`@io`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoLocalPart, + `io. currently has an MX-record (Feb 2011)`); + + assert(`@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoLocalPart); + assert(`test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid); + assert(`test@nominet.org.uk`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid); + assert(`test@about.museum`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid); + assert(`a@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid); + + //assert(`test@e.com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord); + // DNS check is currently not implemented + + //assert(`test@iana.a`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord); + // DNS check is currently not implemented + + assert(`test.test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid); + assert(`.test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorDotStart); + assert(`test.@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorDotEnd); + + assert(`test .. iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorConsecutiveDots); + + assert(`test_exa-mple.com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorNoDomain); + assert("!#$%&`*+/=?^`{|}~@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid); + + assert(`test\@test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorExpectingText); + + assert(`123@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid); + assert(`test@123.com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid); + + assert(`test@iana.123`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5321TopLevelDomainNumeric); + assert(`test@255.255.255.255`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5321TopLevelDomainNumeric); + + assert(`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@iana.org`.isEmail(No.checkDns, + EmailStatusCode.any).statusCode == EmailStatusCode.valid); + + assert(`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklmn@iana.org`.isEmail(No.checkDns, + EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322LocalTooLong); + + // assert(`test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.com`.isEmail(No.checkDns, + // EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord); + // DNS check is currently not implemented + + assert(`test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm.com`.isEmail(No.checkDns, + EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322LabelTooLong); + + assert(`test@mason-dixon.com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid); + + assert(`test@-iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorDomainHyphenStart); + + assert(`test@iana-.com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorDomainHyphenEnd); + + assert(`test@g--a.com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid); + + //assert(`test@iana.co-uk`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + //EmailStatusCode.dnsWarningNoRecord); // DNS check is currently not implemented + + assert(`test@.iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorDotStart); + assert(`test@iana.org.`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorDotEnd); + assert(`test@iana .. com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorConsecutiveDots); + + //assert(`a@a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z` + // `.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z` + // `.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + // EmailStatusCode.dnsWarningNoRecord); // DNS check is currently not implemented + + // assert(`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@abcdefghijklmnopqrstuvwxyz` + // `abcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.` + // `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghi`.isEmail(No.checkDns, + // EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord); + // DNS check is currently not implemented + + assert((`abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@abcdefghijklmnopqrstuvwxyz`~ + `abcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.`~ + `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij`).isEmail(No.checkDns, + EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322TooLong); + + assert((`a@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyz`~ + `abcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.`~ + `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.hij`).isEmail(No.checkDns, + EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322TooLong); + + assert((`a@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyz`~ + `abcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.`~ + `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.hijk`).isEmail(No.checkDns, + EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322DomainTooLong); + + assert(`"test"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5321QuotedString); + + assert(`""@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321QuotedString); + assert(`"""@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorExpectingText); + assert(`"\a"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321QuotedString); + assert(`"\""@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321QuotedString); + + assert(`"\"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorUnclosedQuotedString); + + assert(`"\\"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321QuotedString); + assert(`test"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorExpectingText); + + assert(`"test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorUnclosedQuotedString); + + assert(`"test"test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorTextAfterQuotedString); + + assert(`test"text"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorExpectingText); + + assert(`"test""test"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorExpectingText); + + assert(`"test"."test"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.deprecatedLocalPart); + + assert(`"test\ test"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5321QuotedString); + + assert(`"test".test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.deprecatedLocalPart); + + assert("\"test\u0000\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorExpectingQuotedText); + + assert("\"test\\\u0000\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.deprecatedQuotedPair); + + assert(`"abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefghj"@iana.org`.isEmail(No.checkDns, + EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322LocalTooLong, + `Quotes are still part of the length restriction`); + + assert(`"abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefg\h"@iana.org`.isEmail(No.checkDns, + EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322LocalTooLong, + `Quoted pair is still part of the length restriction`); + + assert(`test@[255.255.255.255]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5321AddressLiteral); + + assert(`test@a[255.255.255.255]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorExpectingText); + + assert(`test@[255.255.255]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5322DomainLiteral); + + assert(`test@[255.255.255.255.255]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5322DomainLiteral); + + assert(`test@[255.255.255.256]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5322DomainLiteral); + + assert(`test@[1111:2222:3333:4444:5555:6666:7777:8888]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5322DomainLiteral); + + assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5322IpV6GroupCount); + + assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode + == EmailStatusCode.rfc5321AddressLiteral); + + assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888:9999]`.isEmail(No.checkDns, + EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6GroupCount); + + assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:888G]`.isEmail(No.checkDns, + EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6BadChar); + + assert(`test@[IPv6:1111:2222:3333:4444:5555:6666::8888]`.isEmail(No.checkDns, + EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321IpV6Deprecated); + + assert(`test@[IPv6:1111:2222:3333:4444:5555::8888]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5321AddressLiteral); + + assert(`test@[IPv6:1111:2222:3333:4444:5555:6666::7777:8888]`.isEmail(No.checkDns, + EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6MaxGroups); + + assert(`test@[IPv6::3333:4444:5555:6666:7777:8888]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5322IpV6ColonStart); + + assert(`test@[IPv6:::3333:4444:5555:6666:7777:8888]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5321AddressLiteral); + + assert(`test@[IPv6:1111::4444:5555::8888]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5322IpV6TooManyDoubleColons); + + assert(`test@[IPv6:::]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5321AddressLiteral); + + assert(`test@[IPv6:1111:2222:3333:4444:5555:255.255.255.255]`.isEmail(No.checkDns, + EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6GroupCount); + + assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:255.255.255.255]`.isEmail(No.checkDns, + EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321AddressLiteral); + + assert(`test@[IPv6:1111:2222:3333:4444:5555:6666:7777:255.255.255.255]`.isEmail(No.checkDns, + EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6GroupCount); + + assert(`test@[IPv6:1111:2222:3333:4444::255.255.255.255]`.isEmail(No.checkDns, + EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321AddressLiteral); + + assert(`test@[IPv6:1111:2222:3333:4444:5555:6666::255.255.255.255]`.isEmail(No.checkDns, + EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322IpV6MaxGroups); + + assert(`test@[IPv6:1111:2222:3333:4444:::255.255.255.255]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode + == EmailStatusCode.rfc5322IpV6TooManyDoubleColons); + + assert(`test@[IPv6::255.255.255.255]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5322IpV6ColonStart); + + assert(` test @iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt); + + assert(`test@ iana .com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt); + + assert(`test . test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.deprecatedFoldingWhitespace); + + assert("\u000D\u000A test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.foldingWhitespace, `Folding whitespace`); + + assert("\u000D\u000A \u000D\u000A test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.deprecatedFoldingWhitespace, `FWS with one line composed entirely of WSP`~ + ` -- only allowed as obsolete FWS (someone might allow only non-obsolete FWS)`); + + assert(`(comment)test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.comment); + assert(`((comment)test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorUnclosedComment); + + assert(`(comment(comment))test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.comment); + + assert(`test@(comment)iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt); + + assert(`test(comment)test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorTextAfterCommentFoldingWhitespace); + + assert(`test@(comment)[255.255.255.255]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt); + + assert(`(comment)abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@iana.org`.isEmail(No.checkDns, + EmailStatusCode.any).statusCode == EmailStatusCode.comment); + + assert(`test@(comment)abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.com`.isEmail(No.checkDns, + EmailStatusCode.any).statusCode == EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt); + + assert((`(comment)test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghik.abcdefghijklmnopqrstuvwxyz`~ + `abcdefghijklmnopqrstuvwxyzabcdefghik.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.`~ + `abcdefghijklmnopqrstuvwxyzabcdefghijk.abcdefghijklmnopqrstu`).isEmail(No.checkDns, + EmailStatusCode.any).statusCode == EmailStatusCode.comment); + + assert("test@iana.org\u000A".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorExpectingText); + + assert(`test@xn--hxajbheg2az3al.xn--jxalpdlp`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.valid, `A valid IDN from ICANN's `~ + `IDN TLD evaluation gateway`); + + assert(`xn--test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.valid, + `RFC 3490: "unless the email standards are revised to invite the use of IDNA for local parts, a domain label`~ + ` that holds the local part of an email address SHOULD NOT begin with the ACE prefix, and even if it does,`~ + ` it is to be interpreted literally as a local part that happens to begin with the ACE prefix"`); + + assert(`test@iana.org-`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorDomainHyphenEnd); + + assert(`"test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorUnclosedQuotedString); + + assert(`(test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorUnclosedComment); + + assert(`test@(iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorUnclosedComment); + + assert(`test@[1.2.3.4`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorUnclosedDomainLiteral); + + assert(`"test\"@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorUnclosedQuotedString); + + assert(`(comment\)test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorUnclosedComment); + + assert(`test@iana.org(comment\)`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorUnclosedComment); + + assert(`test@iana.org(comment\`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorBackslashEnd); + + assert(`test@[RFC-5322-domain-literal]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5322DomainLiteral); + + assert(`test@[RFC-5322]-domain-literal]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorTextAfterDomainLiteral); + + assert(`test@[RFC-5322-[domain-literal]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorExpectingDomainText); + + assert("test@[RFC-5322-\\\u0007-domain-literal]".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5322DomainLiteralObsoleteText, `obs-dtext and obs-qp`); + + assert("test@[RFC-5322-\\\u0009-domain-literal]".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5322DomainLiteralObsoleteText); + + assert(`test@[RFC-5322-\]-domain-literal]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5322DomainLiteralObsoleteText); + + assert(`test@[RFC-5322-domain-literal\]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorUnclosedDomainLiteral); + + assert(`test@[RFC-5322-domain-literal\`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorBackslashEnd); + + assert(`test@[RFC 5322 domain literal]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5322DomainLiteral, `Spaces are FWS in a domain literal`); + + assert(`test@[RFC-5322-domain-literal] (comment)`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5322DomainLiteral); + + assert("\u007F@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorExpectingText); + assert("test@\u007F.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorExpectingText); + assert("\"\u007F\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.deprecatedQuotedText); + + assert("\"\\\u007F\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.deprecatedQuotedPair); + + assert("(\u007F)test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.deprecatedCommentText); + + assert("test@iana.org\u000D".isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorCrNoLf, + `No LF after the CR`); + + assert("\u000Dtest@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorCrNoLf, + `No LF after the CR`); + + assert("\"\u000Dtest\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorCrNoLf, `No LF after the CR`); + + assert("(\u000D)test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorCrNoLf, + `No LF after the CR`); + + assert("(\u000D".isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorCrNoLf, + `No LF after the CR`); + + assert("test@iana.org(\u000D)".isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.errorCrNoLf, + `No LF after the CR`); + + assert("\u000Atest@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorExpectingText); + + assert("\"\u000A\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorExpectingQuotedText); + + assert("\"\\\u000A\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.deprecatedQuotedPair); + + assert("(\u000A)test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorExpectingCommentText); + + assert("\u0007@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorExpectingText); + + assert("test@\u0007.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorExpectingText); + + assert("\"\u0007\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.deprecatedQuotedText); + + assert("\"\\\u0007\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.deprecatedQuotedPair); + + assert("(\u0007)test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.deprecatedCommentText); + + assert("\u000D\u000Atest@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no actual white space`); + + assert("\u000D\u000A \u000D\u000Atest@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not obs-FWS because there must be white space on each "fold"`); + + assert(" \u000D\u000Atest@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no white space after the fold`); + + assert(" \u000D\u000A test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.foldingWhitespace, `FWS`); + + assert(" \u000D\u000A \u000D\u000Atest@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no white space after the second fold`); + + assert(" \u000D\u000A\u000D\u000Atest@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorFoldingWhitespaceCrflX2, `Not FWS because no white space after either fold`); + + assert(" \u000D\u000A\u000D\u000A test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorFoldingWhitespaceCrflX2, `Not FWS because no white space after the first fold`); + + assert("test@iana.org\u000D\u000A ".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.foldingWhitespace, `FWS`); + + assert("test@iana.org\u000D\u000A \u000D\u000A ".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.deprecatedFoldingWhitespace, `FWS with one line composed entirely of WSP -- `~ + `only allowed as obsolete FWS (someone might allow only non-obsolete FWS)`); + + assert("test@iana.org\u000D\u000A".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no actual white space`); + + assert("test@iana.org\u000D\u000A \u000D\u000A".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not obs-FWS because there must be white space on each "fold"`); + + assert("test@iana.org \u000D\u000A".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no white space after the fold`); + + assert("test@iana.org \u000D\u000A ".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.foldingWhitespace, `FWS`); + + assert("test@iana.org \u000D\u000A \u000D\u000A".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorFoldingWhitespaceCrLfEnd, `Not FWS because no white space after the second fold`); + + assert("test@iana.org \u000D\u000A\u000D\u000A".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorFoldingWhitespaceCrflX2, `Not FWS because no white space after either fold`); + + assert("test@iana.org \u000D\u000A\u000D\u000A ".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorFoldingWhitespaceCrflX2, `Not FWS because no white space after the first fold`); + + assert(" test@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.foldingWhitespace); + assert(`test@iana.org `.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.foldingWhitespace); + + assert(`test@[IPv6:1::2:]`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.rfc5322IpV6ColonEnd); + + assert("\"test\\\u00A9\"@iana.org".isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.errorExpectingQuotedPair); + + assert(`test@iana/icann.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5322Domain); + + assert(`test.(comment)test@iana.org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + EmailStatusCode.deprecatedComment); + + assert(`test@org`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.rfc5321TopLevelDomain); + + // assert(`test@test.com`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == + //EmailStatusCode.dnsWarningNoMXRecord, `test.com has an A-record but not an MX-record`); + // DNS check is currently not implemented + // + // assert(`test@nic.no`.isEmail(No.checkDns, EmailStatusCode.any).statusCode == EmailStatusCode.dnsWarningNoRecord, + // `nic.no currently has no MX-records or A-records (Feb 2011). If you are seeing an A-record for nic.io then` + // ` try setting your DNS server to 8.8.8.8 (the Google DNS server) - your DNS server may be faking an A-record` + // ` (OpenDNS does this, for instance).`); // DNS check is currently not implemented +} + +// https://issues.dlang.org/show_bug.cgi?id=17217 +@safe unittest +{ + wstring a = `test.test@iana.org`w; + dstring b = `test.test@iana.org`d; + const(wchar)[] c = `test.test@iana.org`w; + const(dchar)[] d = `test.test@iana.org`d; + + assert(a.isEmail(No.checkDns).statusCode == EmailStatusCode.valid); + assert(b.isEmail(No.checkDns).statusCode == EmailStatusCode.valid); + assert(c.isEmail(No.checkDns).statusCode == EmailStatusCode.valid); + assert(d.isEmail(No.checkDns).statusCode == EmailStatusCode.valid); +} + +/** + * Flag for indicating if the isEmail function should perform a DNS check or not. + * + * If set to $(D CheckDns.no), isEmail does not perform DNS checking. + * + * Otherwise if set to $(D CheckDns.yes), isEmail performs DNS checking. + */ +alias CheckDns = Flag!"checkDns"; + +/// Represents the status of an email address +struct EmailStatus +{ + private + { + bool valid_; + string localPart_; + string domainPart_; + EmailStatusCode statusCode_; + } + + /// Self aliases to a `bool` representing if the email is valid or not + alias valid this; + + /* + * Params: + * valid = indicates if the email address is valid or not + * localPart = the local part of the email address + * domainPart = the domain part of the email address + * statusCode = the status code + */ + private this (bool valid, string localPart, string domainPart, EmailStatusCode statusCode) @safe @nogc pure nothrow + { + this.valid_ = valid; + this.localPart_ = localPart; + this.domainPart_ = domainPart; + this.statusCode_ = statusCode; + } + + /// Returns: If the email address is valid or not. + @property bool valid() const @safe @nogc pure nothrow + { + return valid_; + } + + /// Returns: The local part of the email address, that is, the part before the @ sign. + @property string localPart() const @safe @nogc pure nothrow + { + return localPart_; + } + + /// Returns: The domain part of the email address, that is, the part after the @ sign. + @property string domainPart() const @safe @nogc pure nothrow + { + return domainPart_; + } + + /// Returns: The email status code + @property EmailStatusCode statusCode() const @safe @nogc pure nothrow + { + return statusCode_; + } + + /// Returns: A describing string of the status code + @property string status() const @safe @nogc pure nothrow + { + return statusCodeDescription(statusCode_); + } + + /// Returns: A textual representation of the email status + string toString() const @safe pure + { + import std.format : format; + return format("EmailStatus\n{\n\tvalid: %s\n\tlocalPart: %s\n\tdomainPart: %s\n\tstatusCode: %s\n}", valid, + localPart, domainPart, statusCode); + } +} + +/** + * Params: + * statusCode = The $(LREF EmailStatusCode) to read + * Returns: + * A detailed string describing the given status code + */ +string statusCodeDescription(EmailStatusCode statusCode) @safe @nogc pure nothrow +{ + final switch (statusCode) + { + // Categories + case EmailStatusCode.validCategory: return "Address is valid"; + case EmailStatusCode.dnsWarning: return "Address is valid but a DNS check was not successful"; + case EmailStatusCode.rfc5321: return "Address is valid for SMTP but has unusual elements"; + + case EmailStatusCode.cFoldingWhitespace: return "Address is valid within the message but cannot be used"~ + " unmodified for the envelope"; + + case EmailStatusCode.deprecated_: return "Address contains deprecated elements but may still be valid in"~ + " restricted contexts"; + + case EmailStatusCode.rfc5322: return "The address is only valid according to the broad definition of RFC 5322."~ + " It is otherwise invalid"; + + case EmailStatusCode.any: return ""; + case EmailStatusCode.none: return ""; + case EmailStatusCode.warning: return ""; + case EmailStatusCode.error: return "Address is invalid for any purpose"; + + // Diagnoses + case EmailStatusCode.valid: return "Address is valid"; + + // Address is valid but a DNS check was not successful + case EmailStatusCode.dnsWarningNoMXRecord: return "Could not find an MX record for this domain but an A-record"~ + " does exist"; + + case EmailStatusCode.dnsWarningNoRecord: return "Could not find an MX record or an A-record for this domain"; + + // Address is valid for SMTP but has unusual elements + case EmailStatusCode.rfc5321TopLevelDomain: return "Address is valid but at a Top Level Domain"; + + case EmailStatusCode.rfc5321TopLevelDomainNumeric: return "Address is valid but the Top Level Domain begins"~ + " with a number"; + + case EmailStatusCode.rfc5321QuotedString: return "Address is valid but contains a quoted string"; + case EmailStatusCode.rfc5321AddressLiteral: return "Address is valid but at a literal address not a domain"; + + case EmailStatusCode.rfc5321IpV6Deprecated: return "Address is valid but contains a :: that only elides one"~ + " zero group"; + + + // Address is valid within the message but cannot be used unmodified for the envelope + case EmailStatusCode.comment: return "Address contains comments"; + case EmailStatusCode.foldingWhitespace: return "Address contains Folding White Space"; + + // Address contains deprecated elements but may still be valid in restricted contexts + case EmailStatusCode.deprecatedLocalPart: return "The local part is in a deprecated form"; + + case EmailStatusCode.deprecatedFoldingWhitespace: return "Address contains an obsolete form of"~ + " Folding White Space"; + + case EmailStatusCode.deprecatedQuotedText: return "A quoted string contains a deprecated character"; + case EmailStatusCode.deprecatedQuotedPair: return "A quoted pair contains a deprecated character"; + case EmailStatusCode.deprecatedComment: return "Address contains a comment in a position that is deprecated"; + case EmailStatusCode.deprecatedCommentText: return "A comment contains a deprecated character"; + + case EmailStatusCode.deprecatedCommentFoldingWhitespaceNearAt: return "Address contains a comment or"~ + " Folding White Space around the @ sign"; + + // The address is only valid according to the broad definition of RFC 5322 + case EmailStatusCode.rfc5322Domain: return "Address is RFC 5322 compliant but contains domain characters that"~ + " are not allowed by DNS"; + + case EmailStatusCode.rfc5322TooLong: return "Address is too long"; + case EmailStatusCode.rfc5322LocalTooLong: return "The local part of the address is too long"; + case EmailStatusCode.rfc5322DomainTooLong: return "The domain part is too long"; + case EmailStatusCode.rfc5322LabelTooLong: return "The domain part contains an element that is too long"; + case EmailStatusCode.rfc5322DomainLiteral: return "The domain literal is not a valid RFC 5321 address literal"; + + case EmailStatusCode.rfc5322DomainLiteralObsoleteText: return "The domain literal is not a valid RFC 5321"~ + " address literal and it contains obsolete characters"; + + case EmailStatusCode.rfc5322IpV6GroupCount: + return "The IPv6 literal address contains the wrong number of groups"; + + case EmailStatusCode.rfc5322IpV6TooManyDoubleColons: + return "The IPv6 literal address contains too many :: sequences"; + + case EmailStatusCode.rfc5322IpV6BadChar: return "The IPv6 address contains an illegal group of characters"; + case EmailStatusCode.rfc5322IpV6MaxGroups: return "The IPv6 address has too many groups"; + case EmailStatusCode.rfc5322IpV6ColonStart: return "IPv6 address starts with a single colon"; + case EmailStatusCode.rfc5322IpV6ColonEnd: return "IPv6 address ends with a single colon"; + + // Address is invalid for any purpose + case EmailStatusCode.errorExpectingDomainText: + return "A domain literal contains a character that is not allowed"; + + case EmailStatusCode.errorNoLocalPart: return "Address has no local part"; + case EmailStatusCode.errorNoDomain: return "Address has no domain part"; + case EmailStatusCode.errorConsecutiveDots: return "The address may not contain consecutive dots"; + + case EmailStatusCode.errorTextAfterCommentFoldingWhitespace: + return "Address contains text after a comment or Folding White Space"; + + case EmailStatusCode.errorTextAfterQuotedString: return "Address contains text after a quoted string"; + + case EmailStatusCode.errorTextAfterDomainLiteral: return "Extra characters were found after the end of"~ + " the domain literal"; + + case EmailStatusCode.errorExpectingQuotedPair: + return "The address contains a character that is not allowed in a quoted pair"; + + case EmailStatusCode.errorExpectingText: return "Address contains a character that is not allowed"; + + case EmailStatusCode.errorExpectingQuotedText: + return "A quoted string contains a character that is not allowed"; + + case EmailStatusCode.errorExpectingCommentText: return "A comment contains a character that is not allowed"; + case EmailStatusCode.errorBackslashEnd: return "The address cannot end with a backslash"; + case EmailStatusCode.errorDotStart: return "Neither part of the address may begin with a dot"; + case EmailStatusCode.errorDotEnd: return "Neither part of the address may end with a dot"; + case EmailStatusCode.errorDomainHyphenStart: return "A domain or subdomain cannot begin with a hyphen"; + case EmailStatusCode.errorDomainHyphenEnd: return "A domain or subdomain cannot end with a hyphen"; + case EmailStatusCode.errorUnclosedQuotedString: return "Unclosed quoted string"; + case EmailStatusCode.errorUnclosedComment: return "Unclosed comment"; + case EmailStatusCode.errorUnclosedDomainLiteral: return "Domain literal is missing its closing bracket"; + + case EmailStatusCode.errorFoldingWhitespaceCrflX2: + return "Folding White Space contains consecutive CRLF sequences"; + + case EmailStatusCode.errorFoldingWhitespaceCrLfEnd: return "Folding White Space ends with a CRLF sequence"; + + case EmailStatusCode.errorCrNoLf: + return "Address contains a carriage return that is not followed by a line feed"; + } +} + +/** + * An email status code, indicating if an email address is valid or not. + * If it is invalid it also indicates why. + */ +enum EmailStatusCode +{ + // Categories + + /// Address is valid + validCategory = 1, + + /// Address is valid but a DNS check was not successful + dnsWarning = 7, + + /// Address is valid for SMTP but has unusual elements + rfc5321 = 15, + + /// Address is valid within the message but cannot be used unmodified for the envelope + cFoldingWhitespace = 31, + + /// Address contains deprecated elements but may still be valid in restricted contexts + deprecated_ = 63, + + /// The address is only valid according to the broad definition of RFC 5322. It is otherwise invalid + rfc5322 = 127, + + /** + * All finer grained error checking is turned on. Address containing errors or + * warnings is considered invalid. A specific email status code will be + * returned indicating the error/warning of the address. + */ + any = 252, + + /** + * Address is either considered valid or not, no finer grained error checking + * is performed. Returned email status code will be either Error or Valid. + */ + none = 253, + + /** + * Address containing warnings is considered valid, that is, + * any status code below 16 is considered valid. + */ + warning = 254, + + /// Address is invalid for any purpose + error = 255, + + + + // Diagnoses + + /// Address is valid + valid = 0, + + // Address is valid but a DNS check was not successful + + /// Could not find an MX record for this domain but an A-record does exist + dnsWarningNoMXRecord = 5, + + /// Could not find an MX record or an A-record for this domain + dnsWarningNoRecord = 6, + + + + // Address is valid for SMTP but has unusual elements + + /// Address is valid but at a Top Level Domain + rfc5321TopLevelDomain = 9, + + /// Address is valid but the Top Level Domain begins with a number + rfc5321TopLevelDomainNumeric = 10, + + /// Address is valid but contains a quoted string + rfc5321QuotedString = 11, + + /// Address is valid but at a literal address not a domain + rfc5321AddressLiteral = 12, + + /// Address is valid but contains a :: that only elides one zero group + rfc5321IpV6Deprecated = 13, + + + + // Address is valid within the message but cannot be used unmodified for the envelope + + /// Address contains comments + comment = 17, + + /// Address contains Folding White Space + foldingWhitespace = 18, + + + + // Address contains deprecated elements but may still be valid in restricted contexts + + /// The local part is in a deprecated form + deprecatedLocalPart = 33, + + /// Address contains an obsolete form of Folding White Space + deprecatedFoldingWhitespace = 34, + + /// A quoted string contains a deprecated character + deprecatedQuotedText = 35, + + /// A quoted pair contains a deprecated character + deprecatedQuotedPair = 36, + + /// Address contains a comment in a position that is deprecated + deprecatedComment = 37, + + /// A comment contains a deprecated character + deprecatedCommentText = 38, + + /// Address contains a comment or Folding White Space around the @ sign + deprecatedCommentFoldingWhitespaceNearAt = 49, + + + + // The address is only valid according to the broad definition of RFC 5322 + + /// Address is RFC 5322 compliant but contains domain characters that are not allowed by DNS + rfc5322Domain = 65, + + /// Address is too long + rfc5322TooLong = 66, + + /// The local part of the address is too long + rfc5322LocalTooLong = 67, + + /// The domain part is too long + rfc5322DomainTooLong = 68, + + /// The domain part contains an element that is too long + rfc5322LabelTooLong = 69, + + /// The domain literal is not a valid RFC 5321 address literal + rfc5322DomainLiteral = 70, + + /// The domain literal is not a valid RFC 5321 address literal and it contains obsolete characters + rfc5322DomainLiteralObsoleteText = 71, + + /// The IPv6 literal address contains the wrong number of groups + rfc5322IpV6GroupCount = 72, + + /// The IPv6 literal address contains too many :: sequences + rfc5322IpV6TooManyDoubleColons = 73, + + /// The IPv6 address contains an illegal group of characters + rfc5322IpV6BadChar = 74, + + /// The IPv6 address has too many groups + rfc5322IpV6MaxGroups = 75, + + /// IPv6 address starts with a single colon + rfc5322IpV6ColonStart = 76, + + /// IPv6 address ends with a single colon + rfc5322IpV6ColonEnd = 77, + + + + // Address is invalid for any purpose + + /// A domain literal contains a character that is not allowed + errorExpectingDomainText = 129, + + /// Address has no local part + errorNoLocalPart = 130, + + /// Address has no domain part + errorNoDomain = 131, + + /// The address may not contain consecutive dots + errorConsecutiveDots = 132, + + /// Address contains text after a comment or Folding White Space + errorTextAfterCommentFoldingWhitespace = 133, + + /// Address contains text after a quoted string + errorTextAfterQuotedString = 134, + + /// Extra characters were found after the end of the domain literal + errorTextAfterDomainLiteral = 135, + + /// The address contains a character that is not allowed in a quoted pair + errorExpectingQuotedPair = 136, + + /// Address contains a character that is not allowed + errorExpectingText = 137, + + /// A quoted string contains a character that is not allowed + errorExpectingQuotedText = 138, + + /// A comment contains a character that is not allowed + errorExpectingCommentText = 139, + + /// The address cannot end with a backslash + errorBackslashEnd = 140, + + /// Neither part of the address may begin with a dot + errorDotStart = 141, + + /// Neither part of the address may end with a dot + errorDotEnd = 142, + + /// A domain or subdomain cannot begin with a hyphen + errorDomainHyphenStart = 143, + + /// A domain or subdomain cannot end with a hyphen + errorDomainHyphenEnd = 144, + + /// Unclosed quoted string + errorUnclosedQuotedString = 145, + + /// Unclosed comment + errorUnclosedComment = 146, + + /// Domain literal is missing its closing bracket + errorUnclosedDomainLiteral = 147, + + /// Folding White Space contains consecutive CRLF sequences + errorFoldingWhitespaceCrflX2 = 148, + + /// Folding White Space ends with a CRLF sequence + errorFoldingWhitespaceCrLfEnd = 149, + + /// Address contains a carriage return that is not followed by a line feed + errorCrNoLf = 150, +} + +private: + +// Email parts for the isEmail function +enum EmailPart +{ + // The local part of the email address, that is, the part before the @ sign + componentLocalPart, + + // The domain part of the email address, that is, the part after the @ sign. + componentDomain, + + componentLiteral, + contextComment, + contextFoldingWhitespace, + contextQuotedString, + contextQuotedPair, + status +} + +// Miscellaneous string constants +struct TokenImpl(Char) +{ + enum : const(Char)[] + { + at = "@", + backslash = `\`, + dot = ".", + doubleQuote = `"`, + openParenthesis = "(", + closeParenthesis = ")", + openBracket = "[", + closeBracket = "]", + hyphen = "-", + colon = ":", + doubleColon = "::", + space = " ", + tab = "\t", + cr = "\r", + lf = "\n", + ipV6Tag = "IPV6:", + + // US-ASCII visible characters not valid for atext (http://tools.ietf.org/html/rfc5322#section-3.2.3) + specials = `()<>[]:;@\\,."` + } +} + +enum AsciiToken +{ + horizontalTab = 9, + unitSeparator = 31, + delete_ = 127 +} + +/* + * Compare the two given strings lexicographically. An upper limit of the number of + * characters, that will be used in the comparison, can be specified. Supports both + * case-sensitive and case-insensitive comparison. + * + * Params: + * s1 = the first string to be compared + * s2 = the second string to be compared + * length = the length of strings to be used in the comparison. + * caseInsensitive = if true, a case-insensitive comparison will be made, + * otherwise a case-sensitive comparison will be made + * + * Returns: (for $(D pred = "a < b")): + * + * $(BOOKTABLE, + * $(TR $(TD $(D < 0)) $(TD $(D s1 < s2) )) + * $(TR $(TD $(D = 0)) $(TD $(D s1 == s2))) + * $(TR $(TD $(D > 0)) $(TD $(D s1 > s2))) + * ) + */ +int compareFirstN(alias pred = "a < b", S1, S2) (S1 s1, S2 s2, size_t length) +if (is(Unqual!(ElementType!(S1)) == dchar) && is(Unqual!(ElementType!(S2)) == dchar)) +{ + import std.uni : icmp; + auto s1End = length <= s1.length ? length : s1.length; + auto s2End = length <= s2.length ? length : s2.length; + + auto slice1 = s1[0 .. s1End]; + auto slice2 = s2[0 .. s2End]; + + return slice1.icmp(slice2); +} + +@safe unittest +{ + assert("abc".compareFirstN("abcdef", 3) == 0); + assert("abc".compareFirstN("Abc", 3) == 0); + assert("abc".compareFirstN("abcdef", 6) < 0); + assert("abcdef".compareFirstN("abc", 6) > 0); +} + +/* + * Pops the last element of the given range and returns the element. + * + * Params: + * range = the range to pop the element from + * + * Returns: the popped element + */ +ElementType!(A) pop (A) (ref A a) +if (isDynamicArray!(A) && !isNarrowString!(A) && isMutable!(A) && !is(A == void[])) +{ + auto e = a.back; + a.popBack(); + return e; +} + +@safe unittest +{ + auto array = [0, 1, 2, 3]; + auto result = array.pop(); + + assert(array == [0, 1, 2]); + assert(result == 3); +} + +/* + * Returns the character at the given index as a string. The returned string will be a + * slice of the original string. + * + * Params: + * str = the string to get the character from + * index = the index of the character to get + * c = the character to return, or any other of the same length + * + * Returns: the character at the given index as a string + */ +const(T)[] get (T) (const(T)[] str, size_t index, dchar c) +{ + import std.utf : codeLength; + return str[index .. index + codeLength!(T)(c)]; +} + +@safe unittest +{ + assert("abc".get(1, 'b') == "b"); + assert("löv".get(1, 'ö') == "ö"); +} + +@safe unittest +{ + assert("abc".get(1, 'b') == "b"); + assert("löv".get(1, 'ö') == "ö"); +} diff --git a/libphobos/src/std/numeric.d b/libphobos/src/std/numeric.d new file mode 100644 index 0000000..307406e --- /dev/null +++ b/libphobos/src/std/numeric.d @@ -0,0 +1,3467 @@ +// Written in the D programming language. + +/** +This module is a port of a growing fragment of the $(D_PARAM numeric) +header in Alexander Stepanov's $(LINK2 http://sgi.com/tech/stl, +Standard Template Library), with a few additions. + +Macros: +Copyright: Copyright Andrei Alexandrescu 2008 - 2009. +License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). +Authors: $(HTTP erdani.org, Andrei Alexandrescu), + Don Clugston, Robert Jacques, Ilya Yaroshenko +Source: $(PHOBOSSRC std/_numeric.d) +*/ +/* + Copyright Andrei Alexandrescu 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) +*/ +module std.numeric; + +import std.complex; +import std.math; +import std.range.primitives; +import std.traits; +import std.typecons; + +version (unittest) +{ + import std.stdio; +} +/// Format flags for CustomFloat. +public enum CustomFloatFlags +{ + /// Adds a sign bit to allow for signed numbers. + signed = 1, + + /** + * Store values in normalized form by default. The actual precision of the + * significand is extended by 1 bit by assuming an implicit leading bit of 1 + * instead of 0. i.e. $(D 1.nnnn) instead of $(D 0.nnnn). + * True for all $(LINK2 https://en.wikipedia.org/wiki/IEEE_floating_point, IEE754) types + */ + storeNormalized = 2, + + /** + * Stores the significand in $(LINK2 https://en.wikipedia.org/wiki/IEEE_754-1985#Denormalized_numbers, + * IEEE754 denormalized) form when the exponent is 0. Required to express the value 0. + */ + allowDenorm = 4, + + /** + * Allows the storage of $(LINK2 https://en.wikipedia.org/wiki/IEEE_754-1985#Positive_and_negative_infinity, + * IEEE754 _infinity) values. + */ + infinity = 8, + + /// Allows the storage of $(LINK2 https://en.wikipedia.org/wiki/NaN, IEEE754 Not a Number) values. + nan = 16, + + /** + * If set, select an exponent bias such that max_exp = 1. + * i.e. so that the maximum value is >= 1.0 and < 2.0. + * Ignored if the exponent bias is manually specified. + */ + probability = 32, + + /// If set, unsigned custom floats are assumed to be negative. + negativeUnsigned = 64, + + /**If set, 0 is the only allowed $(LINK2 https://en.wikipedia.org/wiki/IEEE_754-1985#Denormalized_numbers, + * IEEE754 denormalized) number. + * Requires allowDenorm and storeNormalized. + */ + allowDenormZeroOnly = 128 | allowDenorm | storeNormalized, + + /// Include _all of the $(LINK2 https://en.wikipedia.org/wiki/IEEE_floating_point, IEEE754) options. + ieee = signed | storeNormalized | allowDenorm | infinity | nan , + + /// Include none of the above options. + none = 0 +} + +private template CustomFloatParams(uint bits) +{ + enum CustomFloatFlags flags = CustomFloatFlags.ieee + ^ ((bits == 80) ? CustomFloatFlags.storeNormalized : CustomFloatFlags.none); + static if (bits == 8) alias CustomFloatParams = CustomFloatParams!( 4, 3, flags); + static if (bits == 16) alias CustomFloatParams = CustomFloatParams!(10, 5, flags); + static if (bits == 32) alias CustomFloatParams = CustomFloatParams!(23, 8, flags); + static if (bits == 64) alias CustomFloatParams = CustomFloatParams!(52, 11, flags); + static if (bits == 80) alias CustomFloatParams = CustomFloatParams!(64, 15, flags); +} + +private template CustomFloatParams(uint precision, uint exponentWidth, CustomFloatFlags flags) +{ + import std.meta : AliasSeq; + alias CustomFloatParams = + AliasSeq!( + precision, + exponentWidth, + flags, + (1 << (exponentWidth - ((flags & flags.probability) == 0))) + - ((flags & (flags.nan | flags.infinity)) != 0) - ((flags & flags.probability) != 0) + ); // ((flags & CustomFloatFlags.probability) == 0) +} + +/** + * Allows user code to define custom floating-point formats. These formats are + * for storage only; all operations on them are performed by first implicitly + * extracting them to $(D real) first. After the operation is completed the + * result can be stored in a custom floating-point value via assignment. + */ +template CustomFloat(uint bits) +if (bits == 8 || bits == 16 || bits == 32 || bits == 64 || bits == 80) +{ + alias CustomFloat = CustomFloat!(CustomFloatParams!(bits)); +} + +/// ditto +template CustomFloat(uint precision, uint exponentWidth, CustomFloatFlags flags = CustomFloatFlags.ieee) +if (((flags & flags.signed) + precision + exponentWidth) % 8 == 0 && precision + exponentWidth > 0) +{ + alias CustomFloat = CustomFloat!(CustomFloatParams!(precision, exponentWidth, flags)); +} + +/// +@safe unittest +{ + import std.math : sin, cos; + + // Define a 16-bit floating point values + CustomFloat!16 x; // Using the number of bits + CustomFloat!(10, 5) y; // Using the precision and exponent width + CustomFloat!(10, 5,CustomFloatFlags.ieee) z; // Using the precision, exponent width and format flags + CustomFloat!(10, 5,CustomFloatFlags.ieee, 15) w; // Using the precision, exponent width, format flags and exponent offset bias + + // Use the 16-bit floats mostly like normal numbers + w = x*y - 1; + + // Functions calls require conversion + z = sin(+x) + cos(+y); // Use unary plus to concisely convert to a real + z = sin(x.get!float) + cos(y.get!float); // Or use get!T + z = sin(cast(float) x) + cos(cast(float) y); // Or use cast(T) to explicitly convert + + // Define a 8-bit custom float for storing probabilities + alias Probability = CustomFloat!(4, 4, CustomFloatFlags.ieee^CustomFloatFlags.probability^CustomFloatFlags.signed ); + auto p = Probability(0.5); +} + +/// ditto +struct CustomFloat(uint precision, // fraction bits (23 for float) + uint exponentWidth, // exponent bits (8 for float) Exponent width + CustomFloatFlags flags, + uint bias) +if (((flags & flags.signed) + precision + exponentWidth) % 8 == 0 && + precision + exponentWidth > 0) +{ + import std.bitmanip : bitfields; + import std.meta : staticIndexOf; +private: + // get the correct unsigned bitfield type to support > 32 bits + template uType(uint bits) + { + static if (bits <= size_t.sizeof*8) alias uType = size_t; + else alias uType = ulong ; + } + + // get the correct signed bitfield type to support > 32 bits + template sType(uint bits) + { + static if (bits <= ptrdiff_t.sizeof*8-1) alias sType = ptrdiff_t; + else alias sType = long; + } + + alias T_sig = uType!precision; + alias T_exp = uType!exponentWidth; + alias T_signed_exp = sType!exponentWidth; + + alias Flags = CustomFloatFlags; + + // Facilitate converting numeric types to custom float + union ToBinary(F) + if (is(typeof(CustomFloatParams!(F.sizeof*8))) || is(F == real)) + { + F set; + + // If on Linux or Mac, where 80-bit reals are padded, ignore the + // padding. + import std.algorithm.comparison : min; + CustomFloat!(CustomFloatParams!(min(F.sizeof*8, 80))) get; + + // Convert F to the correct binary type. + static typeof(get) opCall(F value) + { + ToBinary r; + r.set = value; + return r.get; + } + alias get this; + } + + // Perform IEEE rounding with round to nearest detection + void roundedShift(T,U)(ref T sig, U shift) + { + if (sig << (T.sizeof*8 - shift) == cast(T) 1uL << (T.sizeof*8 - 1)) + { + // round to even + sig >>= shift; + sig += sig & 1; + } + else + { + sig >>= shift - 1; + sig += sig & 1; + // Perform standard rounding + sig >>= 1; + } + } + + // Convert the current value to signed exponent, normalized form + void toNormalized(T,U)(ref T sig, ref U exp) + { + sig = significand; + auto shift = (T.sizeof*8) - precision; + exp = exponent; + static if (flags&(Flags.infinity|Flags.nan)) + { + // Handle inf or nan + if (exp == exponent_max) + { + exp = exp.max; + sig <<= shift; + static if (flags&Flags.storeNormalized) + { + // Save inf/nan in denormalized format + sig >>= 1; + sig += cast(T) 1uL << (T.sizeof*8 - 1); + } + return; + } + } + if ((~flags&Flags.storeNormalized) || + // Convert denormalized form to normalized form + ((flags&Flags.allowDenorm) && exp == 0)) + { + if (sig > 0) + { + import core.bitop : bsr; + auto shift2 = precision - bsr(sig); + exp -= shift2-1; + shift += shift2; + } + else // value = 0.0 + { + exp = exp.min; + return; + } + } + sig <<= shift; + exp -= bias; + } + + // Set the current value from signed exponent, normalized form + void fromNormalized(T,U)(ref T sig, ref U exp) + { + auto shift = (T.sizeof*8) - precision; + if (exp == exp.max) + { + // infinity or nan + exp = exponent_max; + static if (flags & Flags.storeNormalized) + sig <<= 1; + + // convert back to normalized form + static if (~flags & Flags.infinity) + // No infinity support? + assert(sig != 0, "Infinity floating point value assigned to a " + ~ typeof(this).stringof ~ " (no infinity support)."); + + static if (~flags & Flags.nan) // No NaN support? + assert(sig == 0, "NaN floating point value assigned to a " ~ + typeof(this).stringof ~ " (no nan support)."); + sig >>= shift; + return; + } + if (exp == exp.min) // 0.0 + { + exp = 0; + sig = 0; + return; + } + + exp += bias; + if (exp <= 0) + { + static if ((flags&Flags.allowDenorm) || + // Convert from normalized form to denormalized + (~flags&Flags.storeNormalized)) + { + shift += -exp; + roundedShift(sig,1); + sig += cast(T) 1uL << (T.sizeof*8 - 1); + // Add the leading 1 + exp = 0; + } + else + assert((flags&Flags.storeNormalized) && exp == 0, + "Underflow occured assigning to a " ~ + typeof(this).stringof ~ " (no denormal support)."); + } + else + { + static if (~flags&Flags.storeNormalized) + { + // Convert from normalized form to denormalized + roundedShift(sig,1); + sig += cast(T) 1uL << (T.sizeof*8 - 1); + // Add the leading 1 + } + } + + if (shift > 0) + roundedShift(sig,shift); + if (sig > significand_max) + { + // handle significand overflow (should only be 1 bit) + static if (~flags&Flags.storeNormalized) + { + sig >>= 1; + } + else + sig &= significand_max; + exp++; + } + static if ((flags&Flags.allowDenormZeroOnly)==Flags.allowDenormZeroOnly) + { + // disallow non-zero denormals + if (exp == 0) + { + sig <<= 1; + if (sig > significand_max && (sig&significand_max) > 0) + // Check and round to even + exp++; + sig = 0; + } + } + + if (exp >= exponent_max) + { + static if (flags&(Flags.infinity|Flags.nan)) + { + sig = 0; + exp = exponent_max; + static if (~flags&(Flags.infinity)) + assert(0, "Overflow occured assigning to a " ~ + typeof(this).stringof ~ " (no infinity support)."); + } + else + assert(exp == exponent_max, "Overflow occured assigning to a " + ~ typeof(this).stringof ~ " (no infinity support)."); + } + } + +public: + static if (precision == 64) // CustomFloat!80 support hack + { + ulong significand; + enum ulong significand_max = ulong.max; + mixin(bitfields!( + T_exp , "exponent", exponentWidth, + bool , "sign" , flags & flags.signed )); + } + else + { + mixin(bitfields!( + T_sig, "significand", precision, + T_exp, "exponent" , exponentWidth, + bool , "sign" , flags & flags.signed )); + } + + /// Returns: infinity value + static if (flags & Flags.infinity) + static @property CustomFloat infinity() + { + CustomFloat value; + static if (flags & Flags.signed) + value.sign = 0; + value.significand = 0; + value.exponent = exponent_max; + return value; + } + + /// Returns: NaN value + static if (flags & Flags.nan) + static @property CustomFloat nan() + { + CustomFloat value; + static if (flags & Flags.signed) + value.sign = 0; + value.significand = cast(typeof(significand_max)) 1L << (precision-1); + value.exponent = exponent_max; + return value; + } + + /// Returns: number of decimal digits of precision + static @property size_t dig() + { + auto shiftcnt = precision - ((flags&Flags.storeNormalized) != 0); + immutable x = (shiftcnt == 64) ? 0 : 1uL << shiftcnt; + return cast(size_t) log10(x); + } + + /// Returns: smallest increment to the value 1 + static @property CustomFloat epsilon() + { + CustomFloat value; + static if (flags & Flags.signed) + value.sign = 0; + T_signed_exp exp = -precision; + T_sig sig = 0; + + value.fromNormalized(sig,exp); + if (exp == 0 && sig == 0) // underflowed to zero + { + static if ((flags&Flags.allowDenorm) || + (~flags&Flags.storeNormalized)) + sig = 1; + else + sig = cast(T) 1uL << (precision - 1); + } + value.exponent = cast(value.T_exp) exp; + value.significand = cast(value.T_sig) sig; + return value; + } + + /// the number of bits in mantissa + enum mant_dig = precision + ((flags&Flags.storeNormalized) != 0); + + /// Returns: maximum int value such that 10max_10_exp is representable + static @property int max_10_exp(){ return cast(int) log10( +max ); } + + /// maximum int value such that 2max_exp-1 is representable + enum max_exp = exponent_max-bias+((~flags&(Flags.infinity|flags.nan))!=0); + + /// Returns: minimum int value such that 10min_10_exp is representable + static @property int min_10_exp(){ return cast(int) log10( +min_normal ); } + + /// minimum int value such that 2min_exp-1 is representable as a normalized value + enum min_exp = cast(T_signed_exp)-bias +1+ ((flags&Flags.allowDenorm)!=0); + + /// Returns: largest representable value that's not infinity + static @property CustomFloat max() + { + CustomFloat value; + static if (flags & Flags.signed) + value.sign = 0; + value.exponent = exponent_max - ((flags&(flags.infinity|flags.nan)) != 0); + value.significand = significand_max; + return value; + } + + /// Returns: smallest representable normalized value that's not 0 + static @property CustomFloat min_normal() { + CustomFloat value; + static if (flags & Flags.signed) + value.sign = 0; + value.exponent = 1; + static if (flags&Flags.storeNormalized) + value.significand = 0; + else + value.significand = cast(T_sig) 1uL << (precision - 1); + return value; + } + + /// Returns: real part + @property CustomFloat re() { return this; } + + /// Returns: imaginary part + static @property CustomFloat im() { return CustomFloat(0.0f); } + + /// Initialize from any $(D real) compatible type. + this(F)(F input) if (__traits(compiles, cast(real) input )) + { + this = input; + } + + /// Self assignment + void opAssign(F:CustomFloat)(F input) + { + static if (flags & Flags.signed) + sign = input.sign; + exponent = input.exponent; + significand = input.significand; + } + + /// Assigns from any $(D real) compatible type. + void opAssign(F)(F input) + if (__traits(compiles, cast(real) input)) + { + import std.conv : text; + + static if (staticIndexOf!(Unqual!F, float, double, real) >= 0) + auto value = ToBinary!(Unqual!F)(input); + else + auto value = ToBinary!(real )(input); + + // Assign the sign bit + static if (~flags & Flags.signed) + assert((!value.sign) ^ ((flags&flags.negativeUnsigned) > 0), + "Incorrectly signed floating point value assigned to a " ~ + typeof(this).stringof ~ " (no sign support)."); + else + sign = value.sign; + + CommonType!(T_signed_exp ,value.T_signed_exp) exp = value.exponent; + CommonType!(T_sig, value.T_sig ) sig = value.significand; + + value.toNormalized(sig,exp); + fromNormalized(sig,exp); + + assert(exp <= exponent_max, text(typeof(this).stringof ~ + " exponent too large: " ,exp," > ",exponent_max, "\t",input,"\t",sig)); + assert(sig <= significand_max, text(typeof(this).stringof ~ + " significand too large: ",sig," > ",significand_max, + "\t",input,"\t",exp," ",exponent_max)); + exponent = cast(T_exp) exp; + significand = cast(T_sig) sig; + } + + /// Fetches the stored value either as a $(D float), $(D double) or $(D real). + @property F get(F)() + if (staticIndexOf!(Unqual!F, float, double, real) >= 0) + { + import std.conv : text; + + ToBinary!F result; + + static if (flags&Flags.signed) + result.sign = sign; + else + result.sign = (flags&flags.negativeUnsigned) > 0; + + CommonType!(T_signed_exp ,result.get.T_signed_exp ) exp = exponent; // Assign the exponent and fraction + CommonType!(T_sig, result.get.T_sig ) sig = significand; + + toNormalized(sig,exp); + result.fromNormalized(sig,exp); + assert(exp <= result.exponent_max, text("get exponent too large: " ,exp," > ",result.exponent_max) ); + assert(sig <= result.significand_max, text("get significand too large: ",sig," > ",result.significand_max) ); + result.exponent = cast(result.get.T_exp) exp; + result.significand = cast(result.get.T_sig) sig; + return result.set; + } + + ///ditto + T opCast(T)() if (__traits(compiles, get!T )) { return get!T; } + + /// Convert the CustomFloat to a real and perform the relavent operator on the result + real opUnary(string op)() + if (__traits(compiles, mixin(op~`(get!real)`)) || op=="++" || op=="--") + { + static if (op=="++" || op=="--") + { + auto result = get!real; + this = mixin(op~`result`); + return result; + } + else + return mixin(op~`get!real`); + } + + /// ditto + real opBinary(string op,T)(T b) + if (__traits(compiles, mixin(`get!real`~op~`b`))) + { + return mixin(`get!real`~op~`b`); + } + + /// ditto + real opBinaryRight(string op,T)(T a) + if ( __traits(compiles, mixin(`a`~op~`get!real`)) && + !__traits(compiles, mixin(`get!real`~op~`b`))) + { + return mixin(`a`~op~`get!real`); + } + + /// ditto + int opCmp(T)(auto ref T b) + if (__traits(compiles, cast(real) b)) + { + auto x = get!real; + auto y = cast(real) b; + return (x >= y)-(x <= y); + } + + /// ditto + void opOpAssign(string op, T)(auto ref T b) + if (__traits(compiles, mixin(`get!real`~op~`cast(real) b`))) + { + return mixin(`this = this `~op~` cast(real) b`); + } + + /// ditto + template toString() + { + import std.format : FormatSpec, formatValue; + // Needs to be a template because of DMD @@BUG@@ 13737. + void toString()(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) + { + sink.formatValue(get!real, fmt); + } + } +} + +@safe unittest +{ + import std.meta; + alias FPTypes = + AliasSeq!( + CustomFloat!(5, 10), + CustomFloat!(5, 11, CustomFloatFlags.ieee ^ CustomFloatFlags.signed), + CustomFloat!(1, 15, CustomFloatFlags.ieee ^ CustomFloatFlags.signed), + CustomFloat!(4, 3, CustomFloatFlags.ieee | CustomFloatFlags.probability ^ CustomFloatFlags.signed) + ); + + foreach (F; FPTypes) + { + auto x = F(0.125); + assert(x.get!float == 0.125F); + assert(x.get!double == 0.125); + + x -= 0.0625; + assert(x.get!float == 0.0625F); + assert(x.get!double == 0.0625); + + x *= 2; + assert(x.get!float == 0.125F); + assert(x.get!double == 0.125); + + x /= 4; + assert(x.get!float == 0.03125); + assert(x.get!double == 0.03125); + + x = 0.5; + x ^^= 4; + assert(x.get!float == 1 / 16.0F); + assert(x.get!double == 1 / 16.0); + } +} + +@system unittest +{ + // @system due to to!string(CustomFloat) + import std.conv; + CustomFloat!(5, 10) y = CustomFloat!(5, 10)(0.125); + assert(y.to!string == "0.125"); +} + +/** +Defines the fastest type to use when storing temporaries of a +calculation intended to ultimately yield a result of type $(D F) +(where $(D F) must be one of $(D float), $(D double), or $(D +real)). When doing a multi-step computation, you may want to store +intermediate results as $(D FPTemporary!F). + +The necessity of $(D FPTemporary) stems from the optimized +floating-point operations and registers present in virtually all +processors. When adding numbers in the example above, the addition may +in fact be done in $(D real) precision internally. In that case, +storing the intermediate $(D result) in $(D double format) is not only +less precise, it is also (surprisingly) slower, because a conversion +from $(D real) to $(D double) is performed every pass through the +loop. This being a lose-lose situation, $(D FPTemporary!F) has been +defined as the $(I fastest) type to use for calculations at precision +$(D F). There is no need to define a type for the $(I most accurate) +calculations, as that is always $(D real). + +Finally, there is no guarantee that using $(D FPTemporary!F) will +always be fastest, as the speed of floating-point calculations depends +on very many factors. + */ +template FPTemporary(F) +if (isFloatingPoint!F) +{ + version (X86) + alias FPTemporary = real; + else + alias FPTemporary = Unqual!F; +} + +/// +@safe unittest +{ + import std.math : approxEqual; + + // Average numbers in an array + double avg(in double[] a) + { + if (a.length == 0) return 0; + FPTemporary!double result = 0; + foreach (e; a) result += e; + return result / a.length; + } + + auto a = [1.0, 2.0, 3.0]; + assert(approxEqual(avg(a), 2)); +} + +/** +Implements the $(HTTP tinyurl.com/2zb9yr, secant method) for finding a +root of the function $(D fun) starting from points $(D [xn_1, x_n]) +(ideally close to the root). $(D Num) may be $(D float), $(D double), +or $(D real). +*/ +template secantMethod(alias fun) +{ + import std.functional : unaryFun; + Num secantMethod(Num)(Num xn_1, Num xn) + { + auto fxn = unaryFun!(fun)(xn_1), d = xn_1 - xn; + typeof(fxn) fxn_1; + + xn = xn_1; + while (!approxEqual(d, 0) && isFinite(d)) + { + xn_1 = xn; + xn -= d; + fxn_1 = fxn; + fxn = unaryFun!(fun)(xn); + d *= -fxn / (fxn - fxn_1); + } + return xn; + } +} + +/// +@safe unittest +{ + import std.math : approxEqual, cos; + + float f(float x) + { + return cos(x) - x*x*x; + } + auto x = secantMethod!(f)(0f, 1f); + assert(approxEqual(x, 0.865474)); +} + +@system unittest +{ + // @system because of __gshared stderr + scope(failure) stderr.writeln("Failure testing secantMethod"); + float f(float x) + { + return cos(x) - x*x*x; + } + immutable x = secantMethod!(f)(0f, 1f); + assert(approxEqual(x, 0.865474)); + auto d = &f; + immutable y = secantMethod!(d)(0f, 1f); + assert(approxEqual(y, 0.865474)); +} + + +/** + * Return true if a and b have opposite sign. + */ +private bool oppositeSigns(T1, T2)(T1 a, T2 b) +{ + return signbit(a) != signbit(b); +} + +public: + +/** Find a real root of a real function f(x) via bracketing. + * + * Given a function `f` and a range `[a .. b]` such that `f(a)` + * and `f(b)` have opposite signs or at least one of them equals ±0, + * returns the value of `x` in + * the range which is closest to a root of `f(x)`. If `f(x)` + * has more than one root in the range, one will be chosen + * arbitrarily. If `f(x)` returns NaN, NaN will be returned; + * otherwise, this algorithm is guaranteed to succeed. + * + * Uses an algorithm based on TOMS748, which uses inverse cubic + * interpolation whenever possible, otherwise reverting to parabolic + * or secant interpolation. Compared to TOMS748, this implementation + * improves worst-case performance by a factor of more than 100, and + * typical performance by a factor of 2. For 80-bit reals, most + * problems require 8 to 15 calls to `f(x)` to achieve full machine + * precision. The worst-case performance (pathological cases) is + * approximately twice the number of bits. + * + * 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. + * + */ +T findRoot(T, DF, DT)(scope DF f, in T a, in T b, + scope DT tolerance) //= (T a, T b) => false) +if ( + isFloatingPoint!T && + is(typeof(tolerance(T.init, T.init)) : bool) && + is(typeof(f(T.init)) == R, R) && isFloatingPoint!R + ) +{ + immutable fa = f(a); + if (fa == 0) + return a; + immutable fb = f(b); + if (fb == 0) + return b; + immutable r = findRoot(f, a, b, fa, fb, tolerance); + // Return the first value if it is smaller or NaN + return !(fabs(r[2]) > fabs(r[3])) ? r[0] : r[1]; +} + +///ditto +T findRoot(T, DF)(scope DF f, in T a, in T b) +{ + return findRoot(f, a, b, (T a, T b) => false); +} + +/** Find root of a real function f(x) by bracketing, allowing the + * termination condition to be specified. + * + * Params: + * + * f = Function to be analyzed + * + * ax = Left bound of initial range of `f` known to contain the + * root. + * + * bx = Right bound of initial range of `f` known to contain the + * root. + * + * fax = Value of $(D f(ax)). + * + * fbx = Value of $(D f(bx)). $(D fax) and $(D fbx) should have opposite signs. + * ($(D f(ax)) and $(D f(bx)) are commonly known in advance.) + * + * + * tolerance = Defines an early termination condition. Receives the + * current upper and lower bounds on the root. The + * delegate must return $(D true) when these bounds are + * acceptable. If this function always returns $(D false), + * full machine precision will be achieved. + * + * Returns: + * + * A tuple consisting of two ranges. The first two elements are the + * range (in `x`) of the root, while the second pair of elements + * are the corresponding function values at those points. If an exact + * root was found, both of the first two elements will contain the + * root, and the second pair of elements will be 0. + */ +Tuple!(T, T, R, R) findRoot(T, R, DF, DT)(scope DF f, in T ax, in T bx, in R fax, in R fbx, + scope DT tolerance) // = (T a, T b) => false) +if ( + isFloatingPoint!T && + is(typeof(tolerance(T.init, T.init)) : bool) && + is(typeof(f(T.init)) == R) && isFloatingPoint!R + ) +in +{ + assert(!ax.isNaN() && !bx.isNaN(), "Limits must not be NaN"); + assert(signbit(fax) != signbit(fbx), "Parameters must bracket the root."); +} +body +{ + // Author: Don Clugston. This code is (heavily) modified from TOMS748 + // (www.netlib.org). The changes to improve the worst-cast performance are + // entirely original. + + T a, b, d; // [a .. b] is our current bracket. d is the third best guess. + R fa, fb, fd; // Values of f at a, b, d. + bool done = false; // Has a root been found? + + // Allow ax and bx to be provided in reverse order + if (ax <= bx) + { + a = ax; fa = fax; + b = bx; fb = fbx; + } + else + { + a = bx; fa = fbx; + b = ax; fb = fax; + } + + // Test the function at point c; update brackets accordingly + void bracket(T c) + { + R fc = f(c); + if (fc == 0 || fc.isNaN()) // Exact solution, or NaN + { + a = c; + fa = fc; + d = c; + fd = fc; + done = true; + return; + } + + // Determine new enclosing interval + if (signbit(fa) != signbit(fc)) + { + d = b; + fd = fb; + b = c; + fb = fc; + } + else + { + d = a; + fd = fa; + a = c; + fa = fc; + } + } + + /* Perform a secant interpolation. If the result would lie on a or b, or if + a and b differ so wildly in magnitude that the result would be meaningless, + perform a bisection instead. + */ + static T secant_interpolate(T a, T b, R fa, R fb) + { + if (( ((a - b) == a) && b != 0) || (a != 0 && ((b - a) == b))) + { + // Catastrophic cancellation + if (a == 0) + a = copysign(T(0), b); + else if (b == 0) + b = copysign(T(0), a); + else if (signbit(a) != signbit(b)) + return 0; + T c = ieeeMean(a, b); + return c; + } + // avoid overflow + if (b - a > T.max) + return b / 2 + a / 2; + if (fb - fa > R.max) + return a - (b - a) / 2; + T c = a - (fa / (fb - fa)) * (b - a); + if (c == a || c == b) + return (a + b) / 2; + return c; + } + + /* Uses 'numsteps' newton steps to approximate the zero in [a .. b] of the + quadratic polynomial interpolating f(x) at a, b, and d. + Returns: + The approximate zero in [a .. b] of the quadratic polynomial. + */ + T newtonQuadratic(int numsteps) + { + // Find the coefficients of the quadratic polynomial. + immutable T a0 = fa; + immutable T a1 = (fb - fa)/(b - a); + immutable T a2 = ((fd - fb)/(d - b) - a1)/(d - a); + + // Determine the starting point of newton steps. + T c = oppositeSigns(a2, fa) ? a : b; + + // start the safeguarded newton steps. + foreach (int i; 0 .. numsteps) + { + immutable T pc = a0 + (a1 + a2 * (c - b))*(c - a); + immutable T pdc = a1 + a2*((2 * c) - (a + b)); + if (pdc == 0) + return a - a0 / a1; + else + c = c - pc / pdc; + } + return c; + } + + // On the first iteration we take a secant step: + if (fa == 0 || fa.isNaN()) + { + done = true; + b = a; + fb = fa; + } + else if (fb == 0 || fb.isNaN()) + { + done = true; + a = b; + fa = fb; + } + else + { + bracket(secant_interpolate(a, b, fa, fb)); + } + + // Starting with the second iteration, higher-order interpolation can + // be used. + int itnum = 1; // Iteration number + int baditer = 1; // Num bisections to take if an iteration is bad. + T c, e; // e is our fourth best guess + R fe; + +whileloop: + while (!done && (b != nextUp(a)) && !tolerance(a, b)) + { + T a0 = a, b0 = b; // record the brackets + + // Do two higher-order (cubic or parabolic) interpolation steps. + foreach (int QQ; 0 .. 2) + { + // Cubic inverse interpolation requires that + // all four function values fa, fb, fd, and fe are distinct; + // otherwise use quadratic interpolation. + bool distinct = (fa != fb) && (fa != fd) && (fa != fe) + && (fb != fd) && (fb != fe) && (fd != fe); + // The first time, cubic interpolation is impossible. + if (itnum<2) distinct = false; + bool ok = distinct; + if (distinct) + { + // Cubic inverse interpolation of f(x) at a, b, d, and e + immutable q11 = (d - e) * fd / (fe - fd); + immutable q21 = (b - d) * fb / (fd - fb); + immutable q31 = (a - b) * fa / (fb - fa); + immutable d21 = (b - d) * fd / (fd - fb); + immutable d31 = (a - b) * fb / (fb - fa); + + immutable q22 = (d21 - q11) * fb / (fe - fb); + immutable q32 = (d31 - q21) * fa / (fd - fa); + immutable d32 = (d31 - q21) * fd / (fd - fa); + immutable q33 = (d32 - q22) * fa / (fe - fa); + c = a + (q31 + q32 + q33); + if (c.isNaN() || (c <= a) || (c >= b)) + { + // DAC: If the interpolation predicts a or b, it's + // probable that it's the actual root. Only allow this if + // we're already close to the root. + if (c == a && a - b != a) + { + c = nextUp(a); + } + else if (c == b && a - b != -b) + { + c = nextDown(b); + } + else + { + ok = false; + } + } + } + if (!ok) + { + // DAC: Alefeld doesn't explain why the number of newton steps + // should vary. + c = newtonQuadratic(distinct ? 3 : 2); + if (c.isNaN() || (c <= a) || (c >= b)) + { + // Failure, try a secant step: + c = secant_interpolate(a, b, fa, fb); + } + } + ++itnum; + e = d; + fe = fd; + bracket(c); + if (done || ( b == nextUp(a)) || tolerance(a, b)) + break whileloop; + if (itnum == 2) + continue whileloop; + } + + // Now we take a double-length secant step: + T u; + R fu; + if (fabs(fa) < fabs(fb)) + { + u = a; + fu = fa; + } + else + { + u = b; + fu = fb; + } + c = u - 2 * (fu / (fb - fa)) * (b - a); + + // DAC: If the secant predicts a value equal to an endpoint, it's + // probably false. + if (c == a || c == b || c.isNaN() || fabs(c - u) > (b - a) / 2) + { + if ((a-b) == a || (b-a) == b) + { + if ((a>0 && b<0) || (a<0 && b>0)) + c = 0; + else + { + if (a == 0) + c = ieeeMean(copysign(T(0), b), b); + else if (b == 0) + c = ieeeMean(copysign(T(0), a), a); + else + c = ieeeMean(a, b); + } + } + else + { + c = a + (b - a) / 2; + } + } + e = d; + fe = fd; + bracket(c); + if (done || (b == nextUp(a)) || tolerance(a, b)) + break; + + // IMPROVE THE WORST-CASE PERFORMANCE + // We must ensure that the bounds reduce by a factor of 2 + // in binary space! every iteration. If we haven't achieved this + // yet, or if we don't yet know what the exponent is, + // perform a binary chop. + + if ((a == 0 || b == 0 || + (fabs(a) >= T(0.5) * fabs(b) && fabs(b) >= T(0.5) * fabs(a))) + && (b - a) < T(0.25) * (b0 - a0)) + { + baditer = 1; + continue; + } + + // DAC: If this happens on consecutive iterations, we probably have a + // pathological function. Perform a number of bisections equal to the + // total number of consecutive bad iterations. + + if ((b - a) < T(0.25) * (b0 - a0)) + baditer = 1; + foreach (int QQ; 0 .. baditer) + { + e = d; + fe = fd; + + T w; + if ((a>0 && b<0) || (a<0 && b>0)) + w = 0; + else + { + T usea = a; + T useb = b; + if (a == 0) + usea = copysign(T(0), b); + else if (b == 0) + useb = copysign(T(0), a); + w = ieeeMean(usea, useb); + } + bracket(w); + } + ++baditer; + } + return Tuple!(T, T, R, R)(a, b, fa, fb); +} + +///ditto +Tuple!(T, T, R, R) findRoot(T, R, DF)(scope DF f, in T ax, in T bx, in R fax, in R fbx) +{ + return findRoot(f, ax, bx, fax, fbx, (T a, T b) => false); +} + +///ditto +T findRoot(T, R)(scope R delegate(T) f, in T a, in T b, + scope bool delegate(T lo, T hi) tolerance = (T a, T b) => false) +{ + return findRoot!(T, R delegate(T), bool delegate(T lo, T hi))(f, a, b, tolerance); +} + +@safe nothrow unittest +{ + int numProblems = 0; + int numCalls; + + void testFindRoot(real delegate(real) @nogc @safe nothrow pure f , real x1, real x2) @nogc @safe nothrow pure + { + //numCalls=0; + //++numProblems; + assert(!x1.isNaN() && !x2.isNaN()); + assert(signbit(x1) != signbit(x2)); + auto result = findRoot(f, x1, x2, f(x1), f(x2), + (real lo, real hi) { return false; }); + + auto flo = f(result[0]); + auto fhi = f(result[1]); + if (flo != 0) + { + assert(oppositeSigns(flo, fhi)); + } + } + + // Test functions + real cubicfn(real x) @nogc @safe nothrow pure + { + //++numCalls; + if (x>float.max) + x = float.max; + if (x<-double.max) + x = -double.max; + // This has a single real root at -59.286543284815 + return 0.386*x*x*x + 23*x*x + 15.7*x + 525.2; + } + // Test a function with more than one root. + real multisine(real x) { ++numCalls; return sin(x); } + //testFindRoot( &multisine, 6, 90); + //testFindRoot(&cubicfn, -100, 100); + //testFindRoot( &cubicfn, -double.max, real.max); + + +/* Tests from the paper: + * "On Enclosing Simple Roots of Nonlinear Equations", G. Alefeld, F.A. Potra, + * Yixun Shi, Mathematics of Computation 61, pp733-744 (1993). + */ + // Parameters common to many alefeld tests. + int n; + real ale_a, ale_b; + + int powercalls = 0; + + real power(real x) + { + ++powercalls; + ++numCalls; + return pow(x, n) + double.min_normal; + } + int [] power_nvals = [3, 5, 7, 9, 19, 25]; + // Alefeld paper states that pow(x,n) is a very poor case, where bisection + // outperforms his method, and gives total numcalls = + // 921 for bisection (2.4 calls per bit), 1830 for Alefeld (4.76/bit), + // 2624 for brent (6.8/bit) + // ... but that is for double, not real80. + // This poor performance seems mainly due to catastrophic cancellation, + // which is avoided here by the use of ieeeMean(). + // I get: 231 (0.48/bit). + // IE this is 10X faster in Alefeld's worst case + numProblems=0; + foreach (k; power_nvals) + { + n = k; + //testFindRoot(&power, -1, 10); + } + + int powerProblems = numProblems; + + // Tests from Alefeld paper + + int [9] alefeldSums; + real alefeld0(real x) + { + ++alefeldSums[0]; + ++numCalls; + real q = sin(x) - x/2; + for (int i=1; i<20; ++i) + q+=(2*i-5.0)*(2*i-5.0)/((x-i*i)*(x-i*i)*(x-i*i)); + return q; + } + real alefeld1(real x) + { + ++numCalls; + ++alefeldSums[1]; + return ale_a*x + exp(ale_b * x); + } + real alefeld2(real x) + { + ++numCalls; + ++alefeldSums[2]; + return pow(x, n) - ale_a; + } + real alefeld3(real x) + { + ++numCalls; + ++alefeldSums[3]; + return (1.0 +pow(1.0L-n, 2))*x - pow(1.0L-n*x, 2); + } + real alefeld4(real x) + { + ++numCalls; + ++alefeldSums[4]; + return x*x - pow(1-x, n); + } + real alefeld5(real x) + { + ++numCalls; + ++alefeldSums[5]; + return (1+pow(1.0L-n, 4))*x - pow(1.0L-n*x, 4); + } + real alefeld6(real x) + { + ++numCalls; + ++alefeldSums[6]; + return exp(-n*x)*(x-1.01L) + pow(x, n); + } + real alefeld7(real x) + { + ++numCalls; + ++alefeldSums[7]; + return (n*x-1)/((n-1)*x); + } + + numProblems=0; + //testFindRoot(&alefeld0, PI_2, PI); + for (n=1; n <= 10; ++n) + { + //testFindRoot(&alefeld0, n*n+1e-9L, (n+1)*(n+1)-1e-9L); + } + ale_a = -40; ale_b = -1; + //testFindRoot(&alefeld1, -9, 31); + ale_a = -100; ale_b = -2; + //testFindRoot(&alefeld1, -9, 31); + ale_a = -200; ale_b = -3; + //testFindRoot(&alefeld1, -9, 31); + int [] nvals_3 = [1, 2, 5, 10, 15, 20]; + int [] nvals_5 = [1, 2, 4, 5, 8, 15, 20]; + int [] nvals_6 = [1, 5, 10, 15, 20]; + int [] nvals_7 = [2, 5, 15, 20]; + + for (int i=4; i<12; i+=2) + { + n = i; + ale_a = 0.2; + //testFindRoot(&alefeld2, 0, 5); + ale_a=1; + //testFindRoot(&alefeld2, 0.95, 4.05); + //testFindRoot(&alefeld2, 0, 1.5); + } + foreach (i; nvals_3) + { + n=i; + //testFindRoot(&alefeld3, 0, 1); + } + foreach (i; nvals_3) + { + n=i; + //testFindRoot(&alefeld4, 0, 1); + } + foreach (i; nvals_5) + { + n=i; + //testFindRoot(&alefeld5, 0, 1); + } + foreach (i; nvals_6) + { + n=i; + //testFindRoot(&alefeld6, 0, 1); + } + foreach (i; nvals_7) + { + n=i; + //testFindRoot(&alefeld7, 0.01L, 1); + } + real worstcase(real x) + { + ++numCalls; + return x<0.3*real.max? -0.999e-3 : 1.0; + } + //testFindRoot(&worstcase, -real.max, real.max); + + // just check that the double + float cases compile + //findRoot((double x){ return 0.0; }, -double.max, double.max); + //findRoot((float x){ return 0.0f; }, -float.max, float.max); + +/* + int grandtotal=0; + foreach (calls; alefeldSums) + { + grandtotal+=calls; + } + grandtotal-=2*numProblems; + printf("\nALEFELD TOTAL = %d avg = %f (alefeld avg=19.3 for double)\n", + grandtotal, (1.0*grandtotal)/numProblems); + powercalls -= 2*powerProblems; + printf("POWER TOTAL = %d avg = %f ", powercalls, + (1.0*powercalls)/powerProblems); +*/ + //Issue 14231 + auto xp = findRoot((float x) => x, 0f, 1f); + auto xn = findRoot((float x) => x, -1f, -0f); +} + +//regression control +@system unittest +{ + // @system due to the case in the 2nd line + static assert(__traits(compiles, findRoot((float x)=>cast(real) x, float.init, float.init))); + static assert(__traits(compiles, findRoot!real((x)=>cast(double) x, real.init, real.init))); + static assert(__traits(compiles, findRoot((real x)=>cast(double) x, real.init, real.init))); +} + +/++ +Find a real minimum of a real function `f(x)` via bracketing. +Given a function `f` and a range `(ax .. bx)`, +returns the value of `x` in the range which is closest to a minimum of `f(x)`. +`f` is never evaluted at the endpoints of `ax` and `bx`. +If `f(x)` has more than one minimum in the range, one will be chosen arbitrarily. +If `f(x)` returns NaN or -Infinity, `(x, f(x), NaN)` will be returned; +otherwise, this algorithm is guaranteed to succeed. + +Params: + f = Function to be analyzed + ax = Left bound of initial range of f known to contain the minimum. + bx = Right bound of initial range of f known to contain the minimum. + relTolerance = Relative tolerance. + absTolerance = Absolute tolerance. + +Preconditions: + `ax` and `bx` shall be finite reals. $(BR) + $(D relTolerance) shall be normal positive real. $(BR) + $(D absTolerance) shall be normal positive real no less then $(D T.epsilon*2). + +Returns: + A tuple consisting of `x`, `y = f(x)` and `error = 3 * (absTolerance * fabs(x) + relTolerance)`. + + The method used is a combination of golden section search and +successive parabolic interpolation. Convergence is never much slower +than that for a Fibonacci search. + +References: + "Algorithms for Minimization without Derivatives", Richard Brent, Prentice-Hall, Inc. (1973) + +See_Also: $(LREF findRoot), $(REF isNormal, std,math) ++/ +Tuple!(T, "x", Unqual!(ReturnType!DF), "y", T, "error") +findLocalMin(T, DF)( + scope DF f, + in T ax, + in T bx, + in T relTolerance = sqrt(T.epsilon), + in T absTolerance = sqrt(T.epsilon), + ) +if (isFloatingPoint!T + && __traits(compiles, {T _ = DF.init(T.init);})) +in +{ + assert(isFinite(ax), "ax is not finite"); + assert(isFinite(bx), "bx is not finite"); + assert(isNormal(relTolerance), "relTolerance is not normal floating point number"); + assert(isNormal(absTolerance), "absTolerance is not normal floating point number"); + assert(relTolerance >= 0, "absTolerance is not positive"); + assert(absTolerance >= T.epsilon*2, "absTolerance is not greater then `2*T.epsilon`"); +} +out (result) +{ + assert(isFinite(result.x)); +} +body +{ + alias R = Unqual!(CommonType!(ReturnType!DF, T)); + // c is the squared inverse of the golden ratio + // (3 - sqrt(5))/2 + // Value obtained from Wolfram Alpha. + enum T c = 0x0.61c8864680b583ea0c633f9fa31237p+0L; + enum T cm1 = 0x0.9e3779b97f4a7c15f39cc0605cedc8p+0L; + R tolerance; + T a = ax > bx ? bx : ax; + T b = ax > bx ? ax : bx; + // sequence of declarations suitable for SIMD instructions + T v = a * cm1 + b * c; + assert(isFinite(v)); + R fv = f(v); + if (isNaN(fv) || fv == -T.infinity) + { + return typeof(return)(v, fv, T.init); + } + T w = v; + R fw = fv; + T x = v; + R fx = fv; + size_t i; + for (R d = 0, e = 0;;) + { + i++; + T m = (a + b) / 2; + // This fix is not part of the original algorithm + if (!isFinite(m)) // fix infinity loop. Issue can be reproduced in R. + { + m = a / 2 + b / 2; + if (!isFinite(m)) // fast-math compiler switch is enabled + { + //SIMD instructions can be used by compiler, do not reduce declarations + int a_exp = void; + int b_exp = void; + immutable an = frexp(a, a_exp); + immutable bn = frexp(b, b_exp); + immutable am = ldexp(an, a_exp-1); + immutable bm = ldexp(bn, b_exp-1); + m = am + bm; + if (!isFinite(m)) // wrong input: constraints are disabled in release mode + { + return typeof(return).init; + } + } + } + tolerance = absTolerance * fabs(x) + relTolerance; + immutable t2 = tolerance * 2; + // check stopping criterion + if (!(fabs(x - m) > t2 - (b - a) / 2)) + { + break; + } + R p = 0; + R q = 0; + R r = 0; + // fit parabola + if (fabs(e) > tolerance) + { + immutable xw = x - w; + immutable fxw = fx - fw; + immutable xv = x - v; + immutable fxv = fx - fv; + immutable xwfxv = xw * fxv; + immutable xvfxw = xv * fxw; + p = xv * xvfxw - xw * xwfxv; + q = (xvfxw - xwfxv) * 2; + if (q > 0) + p = -p; + else + q = -q; + r = e; + e = d; + } + T u; + // a parabolic-interpolation step + if (fabs(p) < fabs(q * r / 2) && p > q * (a - x) && p < q * (b - x)) + { + d = p / q; + u = x + d; + // f must not be evaluated too close to a or b + if (u - a < t2 || b - u < t2) + d = x < m ? tolerance : -tolerance; + } + // a golden-section step + else + { + e = (x < m ? b : a) - x; + d = c * e; + } + // f must not be evaluated too close to x + u = x + (fabs(d) >= tolerance ? d : d > 0 ? tolerance : -tolerance); + immutable fu = f(u); + if (isNaN(fu) || fu == -T.infinity) + { + return typeof(return)(u, fu, T.init); + } + // update a, b, v, w, and x + if (fu <= fx) + { + u < x ? b : a = x; + v = w; fv = fw; + w = x; fw = fx; + x = u; fx = fu; + } + else + { + u < x ? a : b = u; + if (fu <= fw || w == x) + { + v = w; fv = fw; + w = u; fw = fu; + } + else if (fu <= fv || v == x || v == w) + { // do not remove this braces + v = u; fv = fu; + } + } + } + return typeof(return)(x, fx, tolerance * 3); +} + +/// +@safe unittest +{ + import std.math : approxEqual; + + auto ret = findLocalMin((double x) => (x-4)^^2, -1e7, 1e7); + assert(ret.x.approxEqual(4.0)); + assert(ret.y.approxEqual(0.0)); +} + +@safe unittest +{ + import std.meta : AliasSeq; + foreach (T; AliasSeq!(double, float, real)) + { + { + auto ret = findLocalMin!T((T x) => (x-4)^^2, T.min_normal, 1e7); + assert(ret.x.approxEqual(T(4))); + assert(ret.y.approxEqual(T(0))); + } + { + auto ret = findLocalMin!T((T x) => fabs(x-1), -T.max/4, T.max/4, T.min_normal, 2*T.epsilon); + assert(approxEqual(ret.x, T(1))); + assert(approxEqual(ret.y, T(0))); + assert(ret.error <= 10 * T.epsilon); + } + { + auto ret = findLocalMin!T((T x) => T.init, 0, 1, T.min_normal, 2*T.epsilon); + assert(!ret.x.isNaN); + assert(ret.y.isNaN); + assert(ret.error.isNaN); + } + { + auto ret = findLocalMin!T((T x) => log(x), 0, 1, T.min_normal, 2*T.epsilon); + assert(ret.error < 3.00001 * ((2*T.epsilon)*fabs(ret.x)+ T.min_normal)); + assert(ret.x >= 0 && ret.x <= ret.error); + } + { + auto ret = findLocalMin!T((T x) => log(x), 0, T.max, T.min_normal, 2*T.epsilon); + assert(ret.y < -18); + assert(ret.error < 5e-08); + assert(ret.x >= 0 && ret.x <= ret.error); + } + { + auto ret = findLocalMin!T((T x) => -fabs(x), -1, 1, T.min_normal, 2*T.epsilon); + assert(ret.x.fabs.approxEqual(T(1))); + assert(ret.y.fabs.approxEqual(T(1))); + assert(ret.error.approxEqual(T(0))); + } + } +} + +/** +Computes $(LINK2 https://en.wikipedia.org/wiki/Euclidean_distance, +Euclidean distance) between input ranges $(D a) and +$(D b). The two ranges must have the same length. The three-parameter +version stops computation as soon as the distance is greater than or +equal to $(D limit) (this is useful to save computation if a small +distance is sought). + */ +CommonType!(ElementType!(Range1), ElementType!(Range2)) +euclideanDistance(Range1, Range2)(Range1 a, Range2 b) +if (isInputRange!(Range1) && isInputRange!(Range2)) +{ + enum bool haveLen = hasLength!(Range1) && hasLength!(Range2); + static if (haveLen) assert(a.length == b.length); + Unqual!(typeof(return)) result = 0; + for (; !a.empty; a.popFront(), b.popFront()) + { + immutable t = a.front - b.front; + result += t * t; + } + static if (!haveLen) assert(b.empty); + return sqrt(result); +} + +/// Ditto +CommonType!(ElementType!(Range1), ElementType!(Range2)) +euclideanDistance(Range1, Range2, F)(Range1 a, Range2 b, F limit) +if (isInputRange!(Range1) && isInputRange!(Range2)) +{ + limit *= limit; + enum bool haveLen = hasLength!(Range1) && hasLength!(Range2); + static if (haveLen) assert(a.length == b.length); + Unqual!(typeof(return)) result = 0; + for (; ; a.popFront(), b.popFront()) + { + if (a.empty) + { + static if (!haveLen) assert(b.empty); + break; + } + immutable t = a.front - b.front; + result += t * t; + if (result >= limit) break; + } + return sqrt(result); +} + +@safe unittest +{ + import std.meta : AliasSeq; + foreach (T; AliasSeq!(double, const double, immutable double)) + { + T[] a = [ 1.0, 2.0, ]; + T[] b = [ 4.0, 6.0, ]; + assert(euclideanDistance(a, b) == 5); + assert(euclideanDistance(a, b, 5) == 5); + assert(euclideanDistance(a, b, 4) == 5); + assert(euclideanDistance(a, b, 2) == 3); + } +} + +/** +Computes the $(LINK2 https://en.wikipedia.org/wiki/Dot_product, +dot product) of input ranges $(D a) and $(D +b). The two ranges must have the same length. If both ranges define +length, the check is done once; otherwise, it is done at each +iteration. + */ +CommonType!(ElementType!(Range1), ElementType!(Range2)) +dotProduct(Range1, Range2)(Range1 a, Range2 b) +if (isInputRange!(Range1) && isInputRange!(Range2) && + !(isArray!(Range1) && isArray!(Range2))) +{ + enum bool haveLen = hasLength!(Range1) && hasLength!(Range2); + static if (haveLen) assert(a.length == b.length); + Unqual!(typeof(return)) result = 0; + for (; !a.empty; a.popFront(), b.popFront()) + { + result += a.front * b.front; + } + static if (!haveLen) assert(b.empty); + return result; +} + +/// Ditto +CommonType!(F1, F2) +dotProduct(F1, F2)(in F1[] avector, in F2[] bvector) +{ + immutable n = avector.length; + assert(n == bvector.length); + auto avec = avector.ptr, bvec = bvector.ptr; + Unqual!(typeof(return)) sum0 = 0, sum1 = 0; + + const all_endp = avec + n; + const smallblock_endp = avec + (n & ~3); + const bigblock_endp = avec + (n & ~15); + + for (; avec != bigblock_endp; avec += 16, bvec += 16) + { + sum0 += avec[0] * bvec[0]; + sum1 += avec[1] * bvec[1]; + sum0 += avec[2] * bvec[2]; + sum1 += avec[3] * bvec[3]; + sum0 += avec[4] * bvec[4]; + sum1 += avec[5] * bvec[5]; + sum0 += avec[6] * bvec[6]; + sum1 += avec[7] * bvec[7]; + sum0 += avec[8] * bvec[8]; + sum1 += avec[9] * bvec[9]; + sum0 += avec[10] * bvec[10]; + sum1 += avec[11] * bvec[11]; + sum0 += avec[12] * bvec[12]; + sum1 += avec[13] * bvec[13]; + sum0 += avec[14] * bvec[14]; + sum1 += avec[15] * bvec[15]; + } + + for (; avec != smallblock_endp; avec += 4, bvec += 4) + { + sum0 += avec[0] * bvec[0]; + sum1 += avec[1] * bvec[1]; + sum0 += avec[2] * bvec[2]; + sum1 += avec[3] * bvec[3]; + } + + sum0 += sum1; + + /* Do trailing portion in naive loop. */ + while (avec != all_endp) + { + sum0 += *avec * *bvec; + ++avec; + ++bvec; + } + + return sum0; +} + +@system unittest +{ + // @system due to dotProduct and assertCTFEable + import std.exception : assertCTFEable; + import std.meta : AliasSeq; + foreach (T; AliasSeq!(double, const double, immutable double)) + { + T[] a = [ 1.0, 2.0, ]; + T[] b = [ 4.0, 6.0, ]; + assert(dotProduct(a, b) == 16); + assert(dotProduct([1, 3, -5], [4, -2, -1]) == 3); + } + + // Make sure the unrolled loop codepath gets tested. + static const x = + [1.0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]; + static const y = + [2.0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]; + assertCTFEable!({ assert(dotProduct(x, y) == 2280); }); +} + +/** +Computes the $(LINK2 https://en.wikipedia.org/wiki/Cosine_similarity, +cosine similarity) of input ranges $(D a) and $(D +b). The two ranges must have the same length. If both ranges define +length, the check is done once; otherwise, it is done at each +iteration. If either range has all-zero elements, return 0. + */ +CommonType!(ElementType!(Range1), ElementType!(Range2)) +cosineSimilarity(Range1, Range2)(Range1 a, Range2 b) +if (isInputRange!(Range1) && isInputRange!(Range2)) +{ + enum bool haveLen = hasLength!(Range1) && hasLength!(Range2); + static if (haveLen) assert(a.length == b.length); + Unqual!(typeof(return)) norma = 0, normb = 0, dotprod = 0; + for (; !a.empty; a.popFront(), b.popFront()) + { + immutable t1 = a.front, t2 = b.front; + norma += t1 * t1; + normb += t2 * t2; + dotprod += t1 * t2; + } + static if (!haveLen) assert(b.empty); + if (norma == 0 || normb == 0) return 0; + return dotprod / sqrt(norma * normb); +} + +@safe unittest +{ + import std.meta : AliasSeq; + foreach (T; AliasSeq!(double, const double, immutable double)) + { + T[] a = [ 1.0, 2.0, ]; + T[] b = [ 4.0, 3.0, ]; + assert(approxEqual( + cosineSimilarity(a, b), 10.0 / sqrt(5.0 * 25), + 0.01)); + } +} + +/** +Normalizes values in $(D range) by multiplying each element with a +number chosen such that values sum up to $(D sum). If elements in $(D +range) sum to zero, assigns $(D sum / range.length) to +all. Normalization makes sense only if all elements in $(D range) are +positive. $(D normalize) assumes that is the case without checking it. + +Returns: $(D true) if normalization completed normally, $(D false) if +all elements in $(D range) were zero or if $(D range) is empty. + */ +bool normalize(R)(R range, ElementType!(R) sum = 1) +if (isForwardRange!(R)) +{ + ElementType!(R) s = 0; + // Step 1: Compute sum and length of the range + static if (hasLength!(R)) + { + const length = range.length; + foreach (e; range) + { + s += e; + } + } + else + { + uint length = 0; + foreach (e; range) + { + s += e; + ++length; + } + } + // Step 2: perform normalization + if (s == 0) + { + if (length) + { + immutable f = sum / range.length; + foreach (ref e; range) e = f; + } + return false; + } + // The path most traveled + assert(s >= 0); + immutable f = sum / s; + foreach (ref e; range) + e *= f; + return true; +} + +/// +@safe unittest +{ + double[] a = []; + assert(!normalize(a)); + a = [ 1.0, 3.0 ]; + assert(normalize(a)); + assert(a == [ 0.25, 0.75 ]); + a = [ 0.0, 0.0 ]; + assert(!normalize(a)); + assert(a == [ 0.5, 0.5 ]); +} + +/** +Compute the sum of binary logarithms of the input range $(D r). +The error of this method is much smaller than with a naive sum of log2. + */ +ElementType!Range sumOfLog2s(Range)(Range r) +if (isInputRange!Range && isFloatingPoint!(ElementType!Range)) +{ + long exp = 0; + Unqual!(typeof(return)) x = 1; + foreach (e; r) + { + if (e < 0) + return typeof(return).nan; + int lexp = void; + x *= frexp(e, lexp); + exp += lexp; + if (x < 0.5) + { + x *= 2; + exp--; + } + } + return exp + log2(x); +} + +/// +@safe unittest +{ + import std.math : isNaN; + + assert(sumOfLog2s(new double[0]) == 0); + assert(sumOfLog2s([0.0L]) == -real.infinity); + assert(sumOfLog2s([-0.0L]) == -real.infinity); + assert(sumOfLog2s([2.0L]) == 1); + assert(sumOfLog2s([-2.0L]).isNaN()); + assert(sumOfLog2s([real.nan]).isNaN()); + assert(sumOfLog2s([-real.nan]).isNaN()); + assert(sumOfLog2s([real.infinity]) == real.infinity); + assert(sumOfLog2s([-real.infinity]).isNaN()); + assert(sumOfLog2s([ 0.25, 0.25, 0.25, 0.125 ]) == -9); +} + +/** +Computes $(LINK2 https://en.wikipedia.org/wiki/Entropy_(information_theory), +_entropy) of input range $(D r) in bits. This +function assumes (without checking) that the values in $(D r) are all +in $(D [0, 1]). For the entropy to be meaningful, often $(D r) should +be normalized too (i.e., its values should sum to 1). The +two-parameter version stops evaluating as soon as the intermediate +result is greater than or equal to $(D max). + */ +ElementType!Range entropy(Range)(Range r) +if (isInputRange!Range) +{ + Unqual!(typeof(return)) result = 0.0; + for (;!r.empty; r.popFront) + { + if (!r.front) continue; + result -= r.front * log2(r.front); + } + return result; +} + +/// Ditto +ElementType!Range entropy(Range, F)(Range r, F max) +if (isInputRange!Range && + !is(CommonType!(ElementType!Range, F) == void)) +{ + Unqual!(typeof(return)) result = 0.0; + for (;!r.empty; r.popFront) + { + if (!r.front) continue; + result -= r.front * log2(r.front); + if (result >= max) break; + } + return result; +} + +@safe unittest +{ + import std.meta : AliasSeq; + foreach (T; AliasSeq!(double, const double, immutable double)) + { + T[] p = [ 0.0, 0, 0, 1 ]; + assert(entropy(p) == 0); + p = [ 0.25, 0.25, 0.25, 0.25 ]; + assert(entropy(p) == 2); + assert(entropy(p, 1) == 1); + } +} + +/** +Computes the $(LINK2 https://en.wikipedia.org/wiki/Kullback%E2%80%93Leibler_divergence, +Kullback-Leibler divergence) between input ranges +$(D a) and $(D b), which is the sum $(D ai * log(ai / bi)). The base +of logarithm is 2. The ranges are assumed to contain elements in $(D +[0, 1]). Usually the ranges are normalized probability distributions, +but this is not required or checked by $(D +kullbackLeiblerDivergence). If any element $(D bi) is zero and the +corresponding element $(D ai) nonzero, returns infinity. (Otherwise, +if $(D ai == 0 && bi == 0), the term $(D ai * log(ai / bi)) is +considered zero.) If the inputs are normalized, the result is +positive. + */ +CommonType!(ElementType!Range1, ElementType!Range2) +kullbackLeiblerDivergence(Range1, Range2)(Range1 a, Range2 b) +if (isInputRange!(Range1) && isInputRange!(Range2)) +{ + enum bool haveLen = hasLength!(Range1) && hasLength!(Range2); + static if (haveLen) assert(a.length == b.length); + Unqual!(typeof(return)) result = 0; + for (; !a.empty; a.popFront(), b.popFront()) + { + immutable t1 = a.front; + if (t1 == 0) continue; + immutable t2 = b.front; + if (t2 == 0) return result.infinity; + assert(t1 > 0 && t2 > 0); + result += t1 * log2(t1 / t2); + } + static if (!haveLen) assert(b.empty); + return result; +} + +/// +@safe unittest +{ + import std.math : approxEqual; + + double[] p = [ 0.0, 0, 0, 1 ]; + assert(kullbackLeiblerDivergence(p, p) == 0); + double[] p1 = [ 0.25, 0.25, 0.25, 0.25 ]; + assert(kullbackLeiblerDivergence(p1, p1) == 0); + assert(kullbackLeiblerDivergence(p, p1) == 2); + assert(kullbackLeiblerDivergence(p1, p) == double.infinity); + double[] p2 = [ 0.2, 0.2, 0.2, 0.4 ]; + assert(approxEqual(kullbackLeiblerDivergence(p1, p2), 0.0719281)); + assert(approxEqual(kullbackLeiblerDivergence(p2, p1), 0.0780719)); +} + +/** +Computes the $(LINK2 https://en.wikipedia.org/wiki/Jensen%E2%80%93Shannon_divergence, +Jensen-Shannon divergence) between $(D a) and $(D +b), which is the sum $(D (ai * log(2 * ai / (ai + bi)) + bi * log(2 * +bi / (ai + bi))) / 2). The base of logarithm is 2. The ranges are +assumed to contain elements in $(D [0, 1]). Usually the ranges are +normalized probability distributions, but this is not required or +checked by $(D jensenShannonDivergence). If the inputs are normalized, +the result is bounded within $(D [0, 1]). The three-parameter version +stops evaluations as soon as the intermediate result is greater than +or equal to $(D limit). + */ +CommonType!(ElementType!Range1, ElementType!Range2) +jensenShannonDivergence(Range1, Range2)(Range1 a, Range2 b) +if (isInputRange!Range1 && isInputRange!Range2 && + is(CommonType!(ElementType!Range1, ElementType!Range2))) +{ + enum bool haveLen = hasLength!(Range1) && hasLength!(Range2); + static if (haveLen) assert(a.length == b.length); + Unqual!(typeof(return)) result = 0; + for (; !a.empty; a.popFront(), b.popFront()) + { + immutable t1 = a.front; + immutable t2 = b.front; + immutable avg = (t1 + t2) / 2; + if (t1 != 0) + { + result += t1 * log2(t1 / avg); + } + if (t2 != 0) + { + result += t2 * log2(t2 / avg); + } + } + static if (!haveLen) assert(b.empty); + return result / 2; +} + +/// Ditto +CommonType!(ElementType!Range1, ElementType!Range2) +jensenShannonDivergence(Range1, Range2, F)(Range1 a, Range2 b, F limit) +if (isInputRange!Range1 && isInputRange!Range2 && + is(typeof(CommonType!(ElementType!Range1, ElementType!Range2).init + >= F.init) : bool)) +{ + enum bool haveLen = hasLength!(Range1) && hasLength!(Range2); + static if (haveLen) assert(a.length == b.length); + Unqual!(typeof(return)) result = 0; + limit *= 2; + for (; !a.empty; a.popFront(), b.popFront()) + { + immutable t1 = a.front; + immutable t2 = b.front; + immutable avg = (t1 + t2) / 2; + if (t1 != 0) + { + result += t1 * log2(t1 / avg); + } + if (t2 != 0) + { + result += t2 * log2(t2 / avg); + } + if (result >= limit) break; + } + static if (!haveLen) assert(b.empty); + return result / 2; +} + +/// +@safe unittest +{ + import std.math : approxEqual; + + double[] p = [ 0.0, 0, 0, 1 ]; + assert(jensenShannonDivergence(p, p) == 0); + double[] p1 = [ 0.25, 0.25, 0.25, 0.25 ]; + assert(jensenShannonDivergence(p1, p1) == 0); + assert(approxEqual(jensenShannonDivergence(p1, p), 0.548795)); + double[] p2 = [ 0.2, 0.2, 0.2, 0.4 ]; + assert(approxEqual(jensenShannonDivergence(p1, p2), 0.0186218)); + assert(approxEqual(jensenShannonDivergence(p2, p1), 0.0186218)); + assert(approxEqual(jensenShannonDivergence(p2, p1, 0.005), 0.00602366)); +} + +/** +The so-called "all-lengths gap-weighted string kernel" computes a +similarity measure between $(D s) and $(D t) based on all of their +common subsequences of all lengths. Gapped subsequences are also +included. + +To understand what $(D gapWeightedSimilarity(s, t, lambda)) computes, +consider first the case $(D lambda = 1) and the strings $(D s = +["Hello", "brave", "new", "world"]) and $(D t = ["Hello", "new", +"world"]). In that case, $(D gapWeightedSimilarity) counts the +following matches: + +$(OL $(LI three matches of length 1, namely $(D "Hello"), $(D "new"), +and $(D "world");) $(LI three matches of length 2, namely ($(D +"Hello", "new")), ($(D "Hello", "world")), and ($(D "new", "world"));) +$(LI one match of length 3, namely ($(D "Hello", "new", "world")).)) + +The call $(D gapWeightedSimilarity(s, t, 1)) simply counts all of +these matches and adds them up, returning 7. + +---- +string[] s = ["Hello", "brave", "new", "world"]; +string[] t = ["Hello", "new", "world"]; +assert(gapWeightedSimilarity(s, t, 1) == 7); +---- + +Note how the gaps in matching are simply ignored, for example ($(D +"Hello", "new")) is deemed as good a match as ($(D "new", +"world")). This may be too permissive for some applications. To +eliminate gapped matches entirely, use $(D lambda = 0): + +---- +string[] s = ["Hello", "brave", "new", "world"]; +string[] t = ["Hello", "new", "world"]; +assert(gapWeightedSimilarity(s, t, 0) == 4); +---- + +The call above eliminated the gapped matches ($(D "Hello", "new")), +($(D "Hello", "world")), and ($(D "Hello", "new", "world")) from the +tally. That leaves only 4 matches. + +The most interesting case is when gapped matches still participate in +the result, but not as strongly as ungapped matches. The result will +be a smooth, fine-grained similarity measure between the input +strings. This is where values of $(D lambda) between 0 and 1 enter +into play: gapped matches are $(I exponentially penalized with the +number of gaps) with base $(D lambda). This means that an ungapped +match adds 1 to the return value; a match with one gap in either +string adds $(D lambda) to the return value; ...; a match with a total +of $(D n) gaps in both strings adds $(D pow(lambda, n)) to the return +value. In the example above, we have 4 matches without gaps, 2 matches +with one gap, and 1 match with three gaps. The latter match is ($(D +"Hello", "world")), which has two gaps in the first string and one gap +in the second string, totaling to three gaps. Summing these up we get +$(D 4 + 2 * lambda + pow(lambda, 3)). + +---- +string[] s = ["Hello", "brave", "new", "world"]; +string[] t = ["Hello", "new", "world"]; +assert(gapWeightedSimilarity(s, t, 0.5) == 4 + 0.5 * 2 + 0.125); +---- + +$(D gapWeightedSimilarity) is useful wherever a smooth similarity +measure between sequences allowing for approximate matches is +needed. The examples above are given with words, but any sequences +with elements comparable for equality are allowed, e.g. characters or +numbers. $(D gapWeightedSimilarity) uses a highly optimized dynamic +programming implementation that needs $(D 16 * min(s.length, +t.length)) extra bytes of memory and $(BIGOH s.length * t.length) time +to complete. + */ +F gapWeightedSimilarity(alias comp = "a == b", R1, R2, F)(R1 s, R2 t, F lambda) +if (isRandomAccessRange!(R1) && hasLength!(R1) && + isRandomAccessRange!(R2) && hasLength!(R2)) +{ + import core.exception : onOutOfMemoryError; + import core.stdc.stdlib : malloc, free; + import std.algorithm.mutation : swap; + import std.functional : binaryFun; + + if (s.length < t.length) return gapWeightedSimilarity(t, s, lambda); + if (!t.length) return 0; + + auto dpvi = cast(F*) malloc(F.sizeof * 2 * t.length); + if (!dpvi) + onOutOfMemoryError(); + + auto dpvi1 = dpvi + t.length; + scope(exit) free(dpvi < dpvi1 ? dpvi : dpvi1); + dpvi[0 .. t.length] = 0; + dpvi1[0] = 0; + immutable lambda2 = lambda * lambda; + + F result = 0; + foreach (i; 0 .. s.length) + { + const si = s[i]; + for (size_t j = 0;;) + { + F dpsij = void; + if (binaryFun!(comp)(si, t[j])) + { + dpsij = 1 + dpvi[j]; + result += dpsij; + } + else + { + dpsij = 0; + } + immutable j1 = j + 1; + if (j1 == t.length) break; + dpvi1[j1] = dpsij + lambda * (dpvi1[j] + dpvi[j1]) - + lambda2 * dpvi[j]; + j = j1; + } + swap(dpvi, dpvi1); + } + return result; +} + +@system unittest +{ + string[] s = ["Hello", "brave", "new", "world"]; + string[] t = ["Hello", "new", "world"]; + assert(gapWeightedSimilarity(s, t, 1) == 7); + assert(gapWeightedSimilarity(s, t, 0) == 4); + assert(gapWeightedSimilarity(s, t, 0.5) == 4 + 2 * 0.5 + 0.125); +} + +/** +The similarity per $(D gapWeightedSimilarity) has an issue in that it +grows with the lengths of the two strings, even though the strings are +not actually very similar. For example, the range $(D ["Hello", +"world"]) is increasingly similar with the range $(D ["Hello", +"world", "world", "world",...]) as more instances of $(D "world") are +appended. To prevent that, $(D gapWeightedSimilarityNormalized) +computes a normalized version of the similarity that is computed as +$(D gapWeightedSimilarity(s, t, lambda) / +sqrt(gapWeightedSimilarity(s, t, lambda) * gapWeightedSimilarity(s, t, +lambda))). The function $(D gapWeightedSimilarityNormalized) (a +so-called normalized kernel) is bounded in $(D [0, 1]), reaches $(D 0) +only for ranges that don't match in any position, and $(D 1) only for +identical ranges. + +The optional parameters $(D sSelfSim) and $(D tSelfSim) are meant for +avoiding duplicate computation. Many applications may have already +computed $(D gapWeightedSimilarity(s, s, lambda)) and/or $(D +gapWeightedSimilarity(t, t, lambda)). In that case, they can be passed +as $(D sSelfSim) and $(D tSelfSim), respectively. + */ +Select!(isFloatingPoint!(F), F, double) +gapWeightedSimilarityNormalized(alias comp = "a == b", R1, R2, F) + (R1 s, R2 t, F lambda, F sSelfSim = F.init, F tSelfSim = F.init) +if (isRandomAccessRange!(R1) && hasLength!(R1) && + isRandomAccessRange!(R2) && hasLength!(R2)) +{ + static bool uncomputed(F n) + { + static if (isFloatingPoint!(F)) + return isNaN(n); + else + return n == n.init; + } + if (uncomputed(sSelfSim)) + sSelfSim = gapWeightedSimilarity!(comp)(s, s, lambda); + if (sSelfSim == 0) return 0; + if (uncomputed(tSelfSim)) + tSelfSim = gapWeightedSimilarity!(comp)(t, t, lambda); + if (tSelfSim == 0) return 0; + + return gapWeightedSimilarity!(comp)(s, t, lambda) / + sqrt(cast(typeof(return)) sSelfSim * tSelfSim); +} + +/// +@system unittest +{ + import std.math : approxEqual, sqrt; + + string[] s = ["Hello", "brave", "new", "world"]; + string[] t = ["Hello", "new", "world"]; + assert(gapWeightedSimilarity(s, s, 1) == 15); + assert(gapWeightedSimilarity(t, t, 1) == 7); + assert(gapWeightedSimilarity(s, t, 1) == 7); + assert(approxEqual(gapWeightedSimilarityNormalized(s, t, 1), + 7.0 / sqrt(15.0 * 7), 0.01)); +} + +/** +Similar to $(D gapWeightedSimilarity), just works in an incremental +manner by first revealing the matches of length 1, then gapped matches +of length 2, and so on. The memory requirement is $(BIGOH s.length * +t.length). The time complexity is $(BIGOH s.length * t.length) time +for computing each step. Continuing on the previous example: + +The implementation is based on the pseudocode in Fig. 4 of the paper +$(HTTP jmlr.csail.mit.edu/papers/volume6/rousu05a/rousu05a.pdf, +"Efficient Computation of Gapped Substring Kernels on Large Alphabets") +by Rousu et al., with additional algorithmic and systems-level +optimizations. + */ +struct GapWeightedSimilarityIncremental(Range, F = double) +if (isRandomAccessRange!(Range) && hasLength!(Range)) +{ + import core.stdc.stdlib : malloc, realloc, alloca, free; + +private: + Range s, t; + F currentValue = 0; + F* kl; + size_t gram = void; + F lambda = void, lambda2 = void; + +public: +/** +Constructs an object given two ranges $(D s) and $(D t) and a penalty +$(D lambda). Constructor completes in $(BIGOH s.length * t.length) +time and computes all matches of length 1. + */ + this(Range s, Range t, F lambda) + { + import core.exception : onOutOfMemoryError; + + assert(lambda > 0); + this.gram = 0; + this.lambda = lambda; + this.lambda2 = lambda * lambda; // for efficiency only + + size_t iMin = size_t.max, jMin = size_t.max, + iMax = 0, jMax = 0; + /* initialize */ + Tuple!(size_t, size_t) * k0; + size_t k0len; + scope(exit) free(k0); + currentValue = 0; + foreach (i, si; s) + { + foreach (j; 0 .. t.length) + { + if (si != t[j]) continue; + k0 = cast(typeof(k0)) realloc(k0, ++k0len * (*k0).sizeof); + with (k0[k0len - 1]) + { + field[0] = i; + field[1] = j; + } + // Maintain the minimum and maximum i and j + if (iMin > i) iMin = i; + if (iMax < i) iMax = i; + if (jMin > j) jMin = j; + if (jMax < j) jMax = j; + } + } + + if (iMin > iMax) return; + assert(k0len); + + currentValue = k0len; + // Chop strings down to the useful sizes + s = s[iMin .. iMax + 1]; + t = t[jMin .. jMax + 1]; + this.s = s; + this.t = t; + + kl = cast(F*) malloc(s.length * t.length * F.sizeof); + if (!kl) + onOutOfMemoryError(); + + kl[0 .. s.length * t.length] = 0; + foreach (pos; 0 .. k0len) + { + with (k0[pos]) + { + kl[(field[0] - iMin) * t.length + field[1] -jMin] = lambda2; + } + } + } + + /** + Returns: $(D this). + */ + ref GapWeightedSimilarityIncremental opSlice() + { + return this; + } + + /** + Computes the match of the popFront length. Completes in $(BIGOH s.length * + t.length) time. + */ + void popFront() + { + import std.algorithm.mutation : swap; + + // This is a large source of optimization: if similarity at + // the gram-1 level was 0, then we can safely assume + // similarity at the gram level is 0 as well. + if (empty) return; + + // Now attempt to match gapped substrings of length `gram' + ++gram; + currentValue = 0; + + auto Si = cast(F*) alloca(t.length * F.sizeof); + Si[0 .. t.length] = 0; + foreach (i; 0 .. s.length) + { + const si = s[i]; + F Sij_1 = 0; + F Si_1j_1 = 0; + auto kli = kl + i * t.length; + for (size_t j = 0;;) + { + const klij = kli[j]; + const Si_1j = Si[j]; + const tmp = klij + lambda * (Si_1j + Sij_1) - lambda2 * Si_1j_1; + // now update kl and currentValue + if (si == t[j]) + currentValue += kli[j] = lambda2 * Si_1j_1; + else + kli[j] = 0; + // commit to Si + Si[j] = tmp; + if (++j == t.length) break; + // get ready for the popFront step; virtually increment j, + // so essentially stuffj_1 <-- stuffj + Si_1j_1 = Si_1j; + Sij_1 = tmp; + } + } + currentValue /= pow(lambda, 2 * (gram + 1)); + + version (none) + { + Si_1[0 .. t.length] = 0; + kl[0 .. min(t.length, maxPerimeter + 1)] = 0; + foreach (i; 1 .. min(s.length, maxPerimeter + 1)) + { + auto kli = kl + i * t.length; + assert(s.length > i); + const si = s[i]; + auto kl_1i_1 = kl_1 + (i - 1) * t.length; + kli[0] = 0; + F lastS = 0; + foreach (j; 1 .. min(maxPerimeter - i + 1, t.length)) + { + immutable j_1 = j - 1; + immutable tmp = kl_1i_1[j_1] + + lambda * (Si_1[j] + lastS) + - lambda2 * Si_1[j_1]; + kl_1i_1[j_1] = float.nan; + Si_1[j_1] = lastS; + lastS = tmp; + if (si == t[j]) + { + currentValue += kli[j] = lambda2 * lastS; + } + else + { + kli[j] = 0; + } + } + Si_1[t.length - 1] = lastS; + } + currentValue /= pow(lambda, 2 * (gram + 1)); + // get ready for the popFront computation + swap(kl, kl_1); + } + } + + /** + Returns: The gapped similarity at the current match length (initially + 1, grows with each call to $(D popFront)). + */ + @property F front() { return currentValue; } + + /** + Returns: Whether there are more matches. + */ + @property bool empty() + { + if (currentValue) return false; + if (kl) + { + free(kl); + kl = null; + } + return true; + } +} + +/** +Ditto + */ +GapWeightedSimilarityIncremental!(R, F) gapWeightedSimilarityIncremental(R, F) +(R r1, R r2, F penalty) +{ + return typeof(return)(r1, r2, penalty); +} + +/// +@system unittest +{ + string[] s = ["Hello", "brave", "new", "world"]; + string[] t = ["Hello", "new", "world"]; + auto simIter = gapWeightedSimilarityIncremental(s, t, 1.0); + assert(simIter.front == 3); // three 1-length matches + simIter.popFront(); + assert(simIter.front == 3); // three 2-length matches + simIter.popFront(); + assert(simIter.front == 1); // one 3-length match + simIter.popFront(); + assert(simIter.empty); // no more match +} + +@system unittest +{ + import std.conv : text; + string[] s = ["Hello", "brave", "new", "world"]; + string[] t = ["Hello", "new", "world"]; + auto simIter = gapWeightedSimilarityIncremental(s, t, 1.0); + //foreach (e; simIter) writeln(e); + assert(simIter.front == 3); // three 1-length matches + simIter.popFront(); + assert(simIter.front == 3, text(simIter.front)); // three 2-length matches + simIter.popFront(); + assert(simIter.front == 1); // one 3-length matches + simIter.popFront(); + assert(simIter.empty); // no more match + + s = ["Hello"]; + t = ["bye"]; + simIter = gapWeightedSimilarityIncremental(s, t, 0.5); + assert(simIter.empty); + + s = ["Hello"]; + t = ["Hello"]; + simIter = gapWeightedSimilarityIncremental(s, t, 0.5); + assert(simIter.front == 1); // one match + simIter.popFront(); + assert(simIter.empty); + + s = ["Hello", "world"]; + t = ["Hello"]; + simIter = gapWeightedSimilarityIncremental(s, t, 0.5); + assert(simIter.front == 1); // one match + simIter.popFront(); + assert(simIter.empty); + + s = ["Hello", "world"]; + t = ["Hello", "yah", "world"]; + simIter = gapWeightedSimilarityIncremental(s, t, 0.5); + assert(simIter.front == 2); // two 1-gram matches + simIter.popFront(); + assert(simIter.front == 0.5, text(simIter.front)); // one 2-gram match, 1 gap +} + +@system unittest +{ + GapWeightedSimilarityIncremental!(string[]) sim = + GapWeightedSimilarityIncremental!(string[])( + ["nyuk", "I", "have", "no", "chocolate", "giba"], + ["wyda", "I", "have", "I", "have", "have", "I", "have", "hehe"], + 0.5); + double[] witness = [ 7.0, 4.03125, 0, 0 ]; + foreach (e; sim) + { + //writeln(e); + assert(e == witness.front); + witness.popFront(); + } + witness = [ 3.0, 1.3125, 0.25 ]; + sim = GapWeightedSimilarityIncremental!(string[])( + ["I", "have", "no", "chocolate"], + ["I", "have", "some", "chocolate"], + 0.5); + foreach (e; sim) + { + //writeln(e); + assert(e == witness.front); + witness.popFront(); + } + assert(witness.empty); +} + +/** +Computes the greatest common divisor of $(D a) and $(D b) by using +an efficient algorithm such as $(HTTPS en.wikipedia.org/wiki/Euclidean_algorithm, Euclid's) +or $(HTTPS en.wikipedia.org/wiki/Binary_GCD_algorithm, Stein's) algorithm. + +Params: + T = Any numerical type that supports the modulo operator `%`. If + bit-shifting `<<` and `>>` are also supported, Stein's algorithm will + be used; otherwise, Euclid's algorithm is used as _a fallback. +Returns: + The greatest common divisor of the given arguments. + */ +T gcd(T)(T a, T b) + if (isIntegral!T) +{ + static if (is(T == const) || is(T == immutable)) + { + return gcd!(Unqual!T)(a, b); + } + else version (DigitalMars) + { + static if (T.min < 0) + { + assert(a >= 0 && b >= 0); + } + while (b) + { + immutable t = b; + b = a % b; + a = t; + } + return a; + } + else + { + if (a == 0) + return b; + if (b == 0) + return a; + + import core.bitop : bsf; + import std.algorithm.mutation : swap; + + immutable uint shift = bsf(a | b); + a >>= a.bsf; + + do + { + b >>= b.bsf; + if (a > b) + swap(a, b); + b -= a; + } while (b); + + return a << shift; + } +} + +/// +@safe unittest +{ + assert(gcd(2 * 5 * 7 * 7, 5 * 7 * 11) == 5 * 7); + const int a = 5 * 13 * 23 * 23, b = 13 * 59; + assert(gcd(a, b) == 13); +} + +// This overload is for non-builtin numerical types like BigInt or +// user-defined types. +/// ditto +T gcd(T)(T a, T b) + if (!isIntegral!T && + is(typeof(T.init % T.init)) && + is(typeof(T.init == 0 || T.init > 0))) +{ + import std.algorithm.mutation : swap; + + enum canUseBinaryGcd = is(typeof(() { + T t, u; + t <<= 1; + t >>= 1; + t -= u; + bool b = (t & 1) == 0; + swap(t, u); + })); + + assert(a >= 0 && b >= 0); + + static if (canUseBinaryGcd) + { + uint shift = 0; + while ((a & 1) == 0 && (b & 1) == 0) + { + a >>= 1; + b >>= 1; + shift++; + } + + do + { + assert((a & 1) != 0); + while ((b & 1) == 0) + b >>= 1; + if (a > b) + swap(a, b); + b -= a; + } while (b); + + return a << shift; + } + else + { + // The only thing we have is %; fallback to Euclidean algorithm. + while (b != 0) + { + auto t = b; + b = a % b; + a = t; + } + return a; + } +} + +// Issue 7102 +@system pure unittest +{ + import std.bigint : BigInt; + assert(gcd(BigInt("71_000_000_000_000_000_000"), + BigInt("31_000_000_000_000_000_000")) == + BigInt("1_000_000_000_000_000_000")); +} + +@safe pure nothrow unittest +{ + // A numerical type that only supports % and - (to force gcd implementation + // to use Euclidean algorithm). + struct CrippledInt + { + int impl; + CrippledInt opBinary(string op : "%")(CrippledInt i) + { + return CrippledInt(impl % i.impl); + } + int opEquals(CrippledInt i) { return impl == i.impl; } + int opEquals(int i) { return impl == i; } + int opCmp(int i) { return (impl < i) ? -1 : (impl > i) ? 1 : 0; } + } + assert(gcd(CrippledInt(2310), CrippledInt(1309)) == CrippledInt(77)); +} + +// This is to make tweaking the speed/size vs. accuracy tradeoff easy, +// though floats seem accurate enough for all practical purposes, since +// they pass the "approxEqual(inverseFft(fft(arr)), arr)" test even for +// size 2 ^^ 22. +private alias lookup_t = float; + +/**A class for performing fast Fourier transforms of power of two sizes. + * This class encapsulates a large amount of state that is reusable when + * performing multiple FFTs of sizes smaller than or equal to that specified + * in the constructor. This results in substantial speedups when performing + * multiple FFTs with a known maximum size. However, + * a free function API is provided for convenience if you need to perform a + * one-off FFT. + * + * References: + * $(HTTP en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm) + */ +final class Fft +{ + import core.bitop : bsf; + import std.algorithm.iteration : map; + import std.array : uninitializedArray; + +private: + immutable lookup_t[][] negSinLookup; + + void enforceSize(R)(R range) const + { + import std.conv : text; + assert(range.length <= size, text( + "FFT size mismatch. Expected ", size, ", got ", range.length)); + } + + void fftImpl(Ret, R)(Stride!R range, Ret buf) const + in + { + assert(range.length >= 4); + assert(isPowerOf2(range.length)); + } + body + { + auto recurseRange = range; + recurseRange.doubleSteps(); + + if (buf.length > 4) + { + fftImpl(recurseRange, buf[0..$ / 2]); + recurseRange.popHalf(); + fftImpl(recurseRange, buf[$ / 2..$]); + } + else + { + // Do this here instead of in another recursion to save on + // recursion overhead. + slowFourier2(recurseRange, buf[0..$ / 2]); + recurseRange.popHalf(); + slowFourier2(recurseRange, buf[$ / 2..$]); + } + + butterfly(buf); + } + + // This algorithm works by performing the even and odd parts of our FFT + // using the "two for the price of one" method mentioned at + // http://www.engineeringproductivitytools.com/stuff/T0001/PT10.HTM#Head521 + // by making the odd terms into the imaginary components of our new FFT, + // and then using symmetry to recombine them. + void fftImplPureReal(Ret, R)(R range, Ret buf) const + in + { + assert(range.length >= 4); + assert(isPowerOf2(range.length)); + } + body + { + alias E = ElementType!R; + + // Converts odd indices of range to the imaginary components of + // a range half the size. The even indices become the real components. + static if (isArray!R && isFloatingPoint!E) + { + // Then the memory layout of complex numbers provides a dirt + // cheap way to convert. This is a common case, so take advantage. + auto oddsImag = cast(Complex!E[]) range; + } + else + { + // General case: Use a higher order range. We can assume + // source.length is even because it has to be a power of 2. + static struct OddToImaginary + { + R source; + alias C = Complex!(CommonType!(E, typeof(buf[0].re))); + + @property + { + C front() + { + return C(source[0], source[1]); + } + + C back() + { + immutable n = source.length; + return C(source[n - 2], source[n - 1]); + } + + typeof(this) save() + { + return typeof(this)(source.save); + } + + bool empty() + { + return source.empty; + } + + size_t length() + { + return source.length / 2; + } + } + + void popFront() + { + source.popFront(); + source.popFront(); + } + + void popBack() + { + source.popBack(); + source.popBack(); + } + + C opIndex(size_t index) + { + return C(source[index * 2], source[index * 2 + 1]); + } + + typeof(this) opSlice(size_t lower, size_t upper) + { + return typeof(this)(source[lower * 2 .. upper * 2]); + } + } + + auto oddsImag = OddToImaginary(range); + } + + fft(oddsImag, buf[0..$ / 2]); + auto evenFft = buf[0..$ / 2]; + auto oddFft = buf[$ / 2..$]; + immutable halfN = evenFft.length; + oddFft[0].re = buf[0].im; + oddFft[0].im = 0; + evenFft[0].im = 0; + // evenFft[0].re is already right b/c it's aliased with buf[0].re. + + foreach (k; 1 .. halfN / 2 + 1) + { + immutable bufk = buf[k]; + immutable bufnk = buf[buf.length / 2 - k]; + evenFft[k].re = 0.5 * (bufk.re + bufnk.re); + evenFft[halfN - k].re = evenFft[k].re; + evenFft[k].im = 0.5 * (bufk.im - bufnk.im); + evenFft[halfN - k].im = -evenFft[k].im; + + oddFft[k].re = 0.5 * (bufk.im + bufnk.im); + oddFft[halfN - k].re = oddFft[k].re; + oddFft[k].im = 0.5 * (bufnk.re - bufk.re); + oddFft[halfN - k].im = -oddFft[k].im; + } + + butterfly(buf); + } + + void butterfly(R)(R buf) const + in + { + assert(isPowerOf2(buf.length)); + } + body + { + immutable n = buf.length; + immutable localLookup = negSinLookup[bsf(n)]; + assert(localLookup.length == n); + + immutable cosMask = n - 1; + immutable cosAdd = n / 4 * 3; + + lookup_t negSinFromLookup(size_t index) pure nothrow + { + return localLookup[index]; + } + + lookup_t cosFromLookup(size_t index) pure nothrow + { + // cos is just -sin shifted by PI * 3 / 2. + return localLookup[(index + cosAdd) & cosMask]; + } + + immutable halfLen = n / 2; + + // This loop is unrolled and the two iterations are interleaved + // relative to the textbook FFT to increase ILP. This gives roughly 5% + // speedups on DMD. + for (size_t k = 0; k < halfLen; k += 2) + { + immutable cosTwiddle1 = cosFromLookup(k); + immutable sinTwiddle1 = negSinFromLookup(k); + immutable cosTwiddle2 = cosFromLookup(k + 1); + immutable sinTwiddle2 = negSinFromLookup(k + 1); + + immutable realLower1 = buf[k].re; + immutable imagLower1 = buf[k].im; + immutable realLower2 = buf[k + 1].re; + immutable imagLower2 = buf[k + 1].im; + + immutable upperIndex1 = k + halfLen; + immutable upperIndex2 = upperIndex1 + 1; + immutable realUpper1 = buf[upperIndex1].re; + immutable imagUpper1 = buf[upperIndex1].im; + immutable realUpper2 = buf[upperIndex2].re; + immutable imagUpper2 = buf[upperIndex2].im; + + immutable realAdd1 = cosTwiddle1 * realUpper1 + - sinTwiddle1 * imagUpper1; + immutable imagAdd1 = sinTwiddle1 * realUpper1 + + cosTwiddle1 * imagUpper1; + immutable realAdd2 = cosTwiddle2 * realUpper2 + - sinTwiddle2 * imagUpper2; + immutable imagAdd2 = sinTwiddle2 * realUpper2 + + cosTwiddle2 * imagUpper2; + + buf[k].re += realAdd1; + buf[k].im += imagAdd1; + buf[k + 1].re += realAdd2; + buf[k + 1].im += imagAdd2; + + buf[upperIndex1].re = realLower1 - realAdd1; + buf[upperIndex1].im = imagLower1 - imagAdd1; + buf[upperIndex2].re = realLower2 - realAdd2; + buf[upperIndex2].im = imagLower2 - imagAdd2; + } + } + + // This constructor is used within this module for allocating the + // buffer space elsewhere besides the GC heap. It's definitely **NOT** + // part of the public API and definitely **IS** subject to change. + // + // Also, this is unsafe because the memSpace buffer will be cast + // to immutable. + public this(lookup_t[] memSpace) // Public b/c of bug 4636. + { + immutable size = memSpace.length / 2; + + /* Create a lookup table of all negative sine values at a resolution of + * size and all smaller power of two resolutions. This may seem + * inefficient, but having all the lookups be next to each other in + * memory at every level of iteration is a huge win performance-wise. + */ + if (size == 0) + { + return; + } + + assert(isPowerOf2(size), + "Can only do FFTs on ranges with a size that is a power of two."); + + auto table = new lookup_t[][bsf(size) + 1]; + + table[$ - 1] = memSpace[$ - size..$]; + memSpace = memSpace[0 .. size]; + + auto lastRow = table[$ - 1]; + lastRow[0] = 0; // -sin(0) == 0. + foreach (ptrdiff_t i; 1 .. size) + { + // The hard coded cases are for improved accuracy and to prevent + // annoying non-zeroness when stuff should be zero. + + if (i == size / 4) + lastRow[i] = -1; // -sin(pi / 2) == -1. + else if (i == size / 2) + lastRow[i] = 0; // -sin(pi) == 0. + else if (i == size * 3 / 4) + lastRow[i] = 1; // -sin(pi * 3 / 2) == 1 + else + lastRow[i] = -sin(i * 2.0L * PI / size); + } + + // Fill in all the other rows with strided versions. + foreach (i; 1 .. table.length - 1) + { + immutable strideLength = size / (2 ^^ i); + auto strided = Stride!(lookup_t[])(lastRow, strideLength); + table[i] = memSpace[$ - strided.length..$]; + memSpace = memSpace[0..$ - strided.length]; + + size_t copyIndex; + foreach (elem; strided) + { + table[i][copyIndex++] = elem; + } + } + + negSinLookup = cast(immutable) table; + } + +public: + /**Create an $(D Fft) object for computing fast Fourier transforms of + * power of two sizes of $(D size) or smaller. $(D size) must be a + * power of two. + */ + this(size_t size) + { + // Allocate all twiddle factor buffers in one contiguous block so that, + // when one is done being used, the next one is next in cache. + auto memSpace = uninitializedArray!(lookup_t[])(2 * size); + this(memSpace); + } + + @property size_t size() const + { + return (negSinLookup is null) ? 0 : negSinLookup[$ - 1].length; + } + + /**Compute the Fourier transform of range using the $(BIGOH N log N) + * Cooley-Tukey Algorithm. $(D range) must be a random-access range with + * slicing and a length equal to $(D size) as provided at the construction of + * this object. The contents of range can be either numeric types, + * which will be interpreted as pure real values, or complex types with + * properties or members $(D .re) and $(D .im) that can be read. + * + * Note: Pure real FFTs are automatically detected and the relevant + * optimizations are performed. + * + * Returns: An array of complex numbers representing the transformed data in + * the frequency domain. + * + * Conventions: The exponent is negative and the factor is one, + * i.e., output[j] := sum[ exp(-2 PI i j k / N) input[k] ]. + */ + Complex!F[] fft(F = double, R)(R range) const + if (isFloatingPoint!F && isRandomAccessRange!R) + { + enforceSize(range); + Complex!F[] ret; + if (range.length == 0) + { + return ret; + } + + // Don't waste time initializing the memory for ret. + ret = uninitializedArray!(Complex!F[])(range.length); + + fft(range, ret); + return ret; + } + + /**Same as the overload, but allows for the results to be stored in a user- + * provided buffer. The buffer must be of the same length as range, must be + * a random-access range, must have slicing, and must contain elements that are + * complex-like. This means that they must have a .re and a .im member or + * property that can be both read and written and are floating point numbers. + */ + void fft(Ret, R)(R range, Ret buf) const + if (isRandomAccessRange!Ret && isComplexLike!(ElementType!Ret) && hasSlicing!Ret) + { + assert(buf.length == range.length); + enforceSize(range); + + if (range.length == 0) + { + return; + } + else if (range.length == 1) + { + buf[0] = range[0]; + return; + } + else if (range.length == 2) + { + slowFourier2(range, buf); + return; + } + else + { + alias E = ElementType!R; + static if (is(E : real)) + { + return fftImplPureReal(range, buf); + } + else + { + static if (is(R : Stride!R)) + return fftImpl(range, buf); + else + return fftImpl(Stride!R(range, 1), buf); + } + } + } + + /** + * Computes the inverse Fourier transform of a range. The range must be a + * random access range with slicing, have a length equal to the size + * provided at construction of this object, and contain elements that are + * either of type std.complex.Complex or have essentially + * the same compile-time interface. + * + * Returns: The time-domain signal. + * + * Conventions: The exponent is positive and the factor is 1/N, i.e., + * output[j] := (1 / N) sum[ exp(+2 PI i j k / N) input[k] ]. + */ + Complex!F[] inverseFft(F = double, R)(R range) const + if (isRandomAccessRange!R && isComplexLike!(ElementType!R) && isFloatingPoint!F) + { + enforceSize(range); + Complex!F[] ret; + if (range.length == 0) + { + return ret; + } + + // Don't waste time initializing the memory for ret. + ret = uninitializedArray!(Complex!F[])(range.length); + + inverseFft(range, ret); + return ret; + } + + /** + * Inverse FFT that allows a user-supplied buffer to be provided. The buffer + * must be a random access range with slicing, and its elements + * must be some complex-like type. + */ + void inverseFft(Ret, R)(R range, Ret buf) const + if (isRandomAccessRange!Ret && isComplexLike!(ElementType!Ret) && hasSlicing!Ret) + { + enforceSize(range); + + auto swapped = map!swapRealImag(range); + fft(swapped, buf); + + immutable lenNeg1 = 1.0 / buf.length; + foreach (ref elem; buf) + { + immutable temp = elem.re * lenNeg1; + elem.re = elem.im * lenNeg1; + elem.im = temp; + } + } +} + +// This mixin creates an Fft object in the scope it's mixed into such that all +// memory owned by the object is deterministically destroyed at the end of that +// scope. +private enum string MakeLocalFft = q{ + import core.stdc.stdlib; + import core.exception : onOutOfMemoryError; + + auto lookupBuf = (cast(lookup_t*) malloc(range.length * 2 * lookup_t.sizeof)) + [0 .. 2 * range.length]; + if (!lookupBuf.ptr) + onOutOfMemoryError(); + + scope(exit) free(cast(void*) lookupBuf.ptr); + auto fftObj = scoped!Fft(lookupBuf); +}; + +/**Convenience functions that create an $(D Fft) object, run the FFT or inverse + * FFT and return the result. Useful for one-off FFTs. + * + * Note: In addition to convenience, these functions are slightly more + * efficient than manually creating an Fft object for a single use, + * as the Fft object is deterministically destroyed before these + * functions return. + */ +Complex!F[] fft(F = double, R)(R range) +{ + mixin(MakeLocalFft); + return fftObj.fft!(F, R)(range); +} + +/// ditto +void fft(Ret, R)(R range, Ret buf) +{ + mixin(MakeLocalFft); + return fftObj.fft!(Ret, R)(range, buf); +} + +/// ditto +Complex!F[] inverseFft(F = double, R)(R range) +{ + mixin(MakeLocalFft); + return fftObj.inverseFft!(F, R)(range); +} + +/// ditto +void inverseFft(Ret, R)(R range, Ret buf) +{ + mixin(MakeLocalFft); + return fftObj.inverseFft!(Ret, R)(range, buf); +} + +@system unittest +{ + import std.algorithm; + import std.conv; + import std.range; + // Test values from R and Octave. + auto arr = [1,2,3,4,5,6,7,8]; + auto fft1 = fft(arr); + assert(approxEqual(map!"a.re"(fft1), + [36.0, -4, -4, -4, -4, -4, -4, -4])); + assert(approxEqual(map!"a.im"(fft1), + [0, 9.6568, 4, 1.6568, 0, -1.6568, -4, -9.6568])); + + auto fft1Retro = fft(retro(arr)); + assert(approxEqual(map!"a.re"(fft1Retro), + [36.0, 4, 4, 4, 4, 4, 4, 4])); + assert(approxEqual(map!"a.im"(fft1Retro), + [0, -9.6568, -4, -1.6568, 0, 1.6568, 4, 9.6568])); + + auto fft1Float = fft(to!(float[])(arr)); + assert(approxEqual(map!"a.re"(fft1), map!"a.re"(fft1Float))); + assert(approxEqual(map!"a.im"(fft1), map!"a.im"(fft1Float))); + + alias C = Complex!float; + auto arr2 = [C(1,2), C(3,4), C(5,6), C(7,8), C(9,10), + C(11,12), C(13,14), C(15,16)]; + auto fft2 = fft(arr2); + assert(approxEqual(map!"a.re"(fft2), + [64.0, -27.3137, -16, -11.3137, -8, -4.6862, 0, 11.3137])); + assert(approxEqual(map!"a.im"(fft2), + [72, 11.3137, 0, -4.686, -8, -11.3137, -16, -27.3137])); + + auto inv1 = inverseFft(fft1); + assert(approxEqual(map!"a.re"(inv1), arr)); + assert(reduce!max(map!"a.im"(inv1)) < 1e-10); + + auto inv2 = inverseFft(fft2); + assert(approxEqual(map!"a.re"(inv2), map!"a.re"(arr2))); + assert(approxEqual(map!"a.im"(inv2), map!"a.im"(arr2))); + + // FFTs of size 0, 1 and 2 are handled as special cases. Test them here. + ushort[] empty; + assert(fft(empty) == null); + assert(inverseFft(fft(empty)) == null); + + real[] oneElem = [4.5L]; + auto oneFft = fft(oneElem); + assert(oneFft.length == 1); + assert(oneFft[0].re == 4.5L); + assert(oneFft[0].im == 0); + + auto oneInv = inverseFft(oneFft); + assert(oneInv.length == 1); + assert(approxEqual(oneInv[0].re, 4.5)); + assert(approxEqual(oneInv[0].im, 0)); + + long[2] twoElems = [8, 4]; + auto twoFft = fft(twoElems[]); + assert(twoFft.length == 2); + assert(approxEqual(twoFft[0].re, 12)); + assert(approxEqual(twoFft[0].im, 0)); + assert(approxEqual(twoFft[1].re, 4)); + assert(approxEqual(twoFft[1].im, 0)); + auto twoInv = inverseFft(twoFft); + assert(approxEqual(twoInv[0].re, 8)); + assert(approxEqual(twoInv[0].im, 0)); + assert(approxEqual(twoInv[1].re, 4)); + assert(approxEqual(twoInv[1].im, 0)); +} + +// Swaps the real and imaginary parts of a complex number. This is useful +// for inverse FFTs. +C swapRealImag(C)(C input) +{ + return C(input.im, input.re); +} + +private: +// The reasons I couldn't use std.algorithm were b/c its stride length isn't +// modifiable on the fly and because range has grown some performance hacks +// for powers of 2. +struct Stride(R) +{ + import core.bitop : bsf; + Unqual!R range; + size_t _nSteps; + size_t _length; + alias E = ElementType!(R); + + this(R range, size_t nStepsIn) + { + this.range = range; + _nSteps = nStepsIn; + _length = (range.length + _nSteps - 1) / nSteps; + } + + size_t length() const @property + { + return _length; + } + + typeof(this) save() @property + { + auto ret = this; + ret.range = ret.range.save; + return ret; + } + + E opIndex(size_t index) + { + return range[index * _nSteps]; + } + + E front() @property + { + return range[0]; + } + + void popFront() + { + if (range.length >= _nSteps) + { + range = range[_nSteps .. range.length]; + _length--; + } + else + { + range = range[0 .. 0]; + _length = 0; + } + } + + // Pops half the range's stride. + void popHalf() + { + range = range[_nSteps / 2 .. range.length]; + } + + bool empty() const @property + { + return length == 0; + } + + size_t nSteps() const @property + { + return _nSteps; + } + + void doubleSteps() + { + _nSteps *= 2; + _length /= 2; + } + + size_t nSteps(size_t newVal) @property + { + _nSteps = newVal; + + // Using >> bsf(nSteps) is a few cycles faster than / nSteps. + _length = (range.length + _nSteps - 1) >> bsf(nSteps); + return newVal; + } +} + +// Hard-coded base case for FFT of size 2. This is actually a TON faster than +// using a generic slow DFT. This seems to be the best base case. (Size 1 +// can be coded inline as buf[0] = range[0]). +void slowFourier2(Ret, R)(R range, Ret buf) +{ + assert(range.length == 2); + assert(buf.length == 2); + buf[0] = range[0] + range[1]; + buf[1] = range[0] - range[1]; +} + +// Hard-coded base case for FFT of size 4. Doesn't work as well as the size +// 2 case. +void slowFourier4(Ret, R)(R range, Ret buf) +{ + alias C = ElementType!Ret; + + assert(range.length == 4); + assert(buf.length == 4); + buf[0] = range[0] + range[1] + range[2] + range[3]; + buf[1] = range[0] - range[1] * C(0, 1) - range[2] + range[3] * C(0, 1); + buf[2] = range[0] - range[1] + range[2] - range[3]; + buf[3] = range[0] + range[1] * C(0, 1) - range[2] - range[3] * C(0, 1); +} + +N roundDownToPowerOf2(N)(N num) +if (isScalarType!N && !isFloatingPoint!N) +{ + import core.bitop : bsr; + return num & (cast(N) 1 << bsr(num)); +} + +@safe unittest +{ + assert(roundDownToPowerOf2(7) == 4); + assert(roundDownToPowerOf2(4) == 4); +} + +template isComplexLike(T) +{ + enum bool isComplexLike = is(typeof(T.init.re)) && + is(typeof(T.init.im)); +} + +@safe unittest +{ + static assert(isComplexLike!(Complex!double)); + static assert(!isComplexLike!(uint)); +} diff --git a/libphobos/src/std/outbuffer.d b/libphobos/src/std/outbuffer.d new file mode 100644 index 0000000..1d59498 --- /dev/null +++ b/libphobos/src/std/outbuffer.d @@ -0,0 +1,418 @@ +// Written in the D programming language. + +/** +Serialize data to $(D ubyte) arrays. + + * Copyright: Copyright Digital Mars 2000 - 2015. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: $(HTTP digitalmars.com, Walter Bright) + * Source: $(PHOBOSSRC std/_outbuffer.d) + * + * $(SCRIPT inhibitQuickIndex = 1;) + */ +module std.outbuffer; + +import core.stdc.stdarg; // : va_list; + +/********************************************* + * OutBuffer provides a way to build up an array of bytes out + * of raw data. It is useful for things like preparing an + * array of bytes to write out to a file. + * OutBuffer's byte order is the format native to the computer. + * To control the byte order (endianness), use a class derived + * from OutBuffer. + * OutBuffer's internal buffer is allocated with the GC. Pointers + * stored into the buffer are scanned by the GC, but you have to + * ensure proper alignment, e.g. by using alignSize((void*).sizeof). + */ + +class OutBuffer +{ + ubyte[] data; + size_t offset; + + invariant() + { + assert(offset <= data.length); + } + + pure nothrow @safe + { + /********************************* + * Convert to array of bytes. + */ + ubyte[] toBytes() { return data[0 .. offset]; } + + /*********************************** + * Preallocate nbytes more to the size of the internal buffer. + * + * This is a + * speed optimization, a good guess at the maximum size of the resulting + * buffer will improve performance by eliminating reallocations and copying. + */ + void reserve(size_t nbytes) @trusted + in + { + assert(offset + nbytes >= offset); + } + out + { + assert(offset + nbytes <= data.length); + } + body + { + if (data.length < offset + nbytes) + { + void[] vdata = data; + vdata.length = (offset + nbytes + 7) * 2; // allocates as void[] to not set BlkAttr.NO_SCAN + data = cast(ubyte[]) vdata; + } + } + + /********************************** + * put enables OutBuffer to be used as an OutputRange. + */ + alias put = write; + + /************************************* + * Append data to the internal buffer. + */ + + void write(const(ubyte)[] bytes) + { + reserve(bytes.length); + data[offset .. offset + bytes.length] = bytes[]; + offset += bytes.length; + } + + void write(in wchar[] chars) @trusted + { + write(cast(ubyte[]) chars); + } + + void write(const(dchar)[] chars) @trusted + { + write(cast(ubyte[]) chars); + } + + void write(ubyte b) /// ditto + { + reserve(ubyte.sizeof); + this.data[offset] = b; + offset += ubyte.sizeof; + } + + void write(byte b) { write(cast(ubyte) b); } /// ditto + void write(char c) { write(cast(ubyte) c); } /// ditto + void write(dchar c) { write(cast(uint) c); } /// ditto + + void write(ushort w) @trusted /// ditto + { + reserve(ushort.sizeof); + *cast(ushort *)&data[offset] = w; + offset += ushort.sizeof; + } + + void write(short s) { write(cast(ushort) s); } /// ditto + + void write(wchar c) @trusted /// ditto + { + reserve(wchar.sizeof); + *cast(wchar *)&data[offset] = c; + offset += wchar.sizeof; + } + + void write(uint w) @trusted /// ditto + { + reserve(uint.sizeof); + *cast(uint *)&data[offset] = w; + offset += uint.sizeof; + } + + void write(int i) { write(cast(uint) i); } /// ditto + + void write(ulong l) @trusted /// ditto + { + reserve(ulong.sizeof); + *cast(ulong *)&data[offset] = l; + offset += ulong.sizeof; + } + + void write(long l) { write(cast(ulong) l); } /// ditto + + void write(float f) @trusted /// ditto + { + reserve(float.sizeof); + *cast(float *)&data[offset] = f; + offset += float.sizeof; + } + + void write(double f) @trusted /// ditto + { + reserve(double.sizeof); + *cast(double *)&data[offset] = f; + offset += double.sizeof; + } + + void write(real f) @trusted /// ditto + { + reserve(real.sizeof); + *cast(real *)&data[offset] = f; + offset += real.sizeof; + } + + void write(in char[] s) @trusted /// ditto + { + write(cast(ubyte[]) s); + } + + void write(OutBuffer buf) /// ditto + { + write(buf.toBytes()); + } + + /**************************************** + * Append nbytes of 0 to the internal buffer. + */ + + void fill0(size_t nbytes) + { + reserve(nbytes); + data[offset .. offset + nbytes] = 0; + offset += nbytes; + } + + /********************************** + * 0-fill to align on power of 2 boundary. + */ + + void alignSize(size_t alignsize) + in + { + assert(alignsize && (alignsize & (alignsize - 1)) == 0); + } + out + { + assert((offset & (alignsize - 1)) == 0); + } + body + { + auto nbytes = offset & (alignsize - 1); + if (nbytes) + fill0(alignsize - nbytes); + } + + /// Clear the data in the buffer + void clear() + { + offset = 0; + } + + /**************************************** + * Optimize common special case alignSize(2) + */ + + void align2() + { + if (offset & 1) + write(cast(byte) 0); + } + + /**************************************** + * Optimize common special case alignSize(4) + */ + + void align4() + { + if (offset & 3) + { auto nbytes = (4 - offset) & 3; + fill0(nbytes); + } + } + + /************************************** + * Convert internal buffer to array of chars. + */ + + override string toString() const + { + //printf("OutBuffer.toString()\n"); + return cast(string) data[0 .. offset].idup; + } + } + + /***************************************** + * Append output of C's vprintf() to internal buffer. + */ + + void vprintf(string format, va_list args) @trusted nothrow + { + import core.stdc.stdio : vsnprintf; + import core.stdc.stdlib : alloca; + import std.string : toStringz; + + version (unittest) + char[3] buffer = void; // trigger reallocation + else + char[128] buffer = void; + int count; + + // Can't use `tempCString()` here as it will result in compilation error: + // "cannot mix core.std.stdlib.alloca() and exception handling". + auto f = toStringz(format); + auto p = buffer.ptr; + auto psize = buffer.length; + for (;;) + { + va_list args2; + va_copy(args2, args); + count = vsnprintf(p, psize, f, args2); + va_end(args2); + if (count == -1) + { + if (psize > psize.max / 2) assert(0); // overflow check + psize *= 2; + } + else if (count >= psize) + { + if (count == count.max) assert(0); // overflow check + psize = count + 1; + } + else + break; + + p = cast(char *) alloca(psize); // buffer too small, try again with larger size + } + write(cast(ubyte[]) p[0 .. count]); + } + + /***************************************** + * Append output of C's printf() to internal buffer. + */ + + void printf(string format, ...) @trusted + { + va_list ap; + va_start(ap, format); + vprintf(format, ap); + va_end(ap); + } + + /** + * Formats and writes its arguments in text format to the OutBuffer. + * + * Params: + * fmt = format string as described in $(REF formattedWrite, std,format) + * args = arguments to be formatted + * + * See_Also: + * $(REF _writef, std,stdio); + * $(REF formattedWrite, std,format); + */ + void writef(Char, A...)(in Char[] fmt, A args) + { + import std.format : formattedWrite; + formattedWrite(this, fmt, args); + } + + /// + @safe unittest + { + OutBuffer b = new OutBuffer(); + b.writef("a%sb", 16); + assert(b.toString() == "a16b"); + } + + /** + * Formats and writes its arguments in text format to the OutBuffer, + * followed by a newline. + * + * Params: + * fmt = format string as described in $(REF formattedWrite, std,format) + * args = arguments to be formatted + * + * See_Also: + * $(REF _writefln, std,stdio); + * $(REF formattedWrite, std,format); + */ + void writefln(Char, A...)(in Char[] fmt, A args) + { + import std.format : formattedWrite; + formattedWrite(this, fmt, args); + put('\n'); + } + + /// + @safe unittest + { + OutBuffer b = new OutBuffer(); + b.writefln("a%sb", 16); + assert(b.toString() == "a16b\n"); + } + + /***************************************** + * At offset index into buffer, create nbytes of space by shifting upwards + * all data past index. + */ + + void spread(size_t index, size_t nbytes) pure nothrow @safe + in + { + assert(index <= offset); + } + body + { + reserve(nbytes); + + // This is an overlapping copy - should use memmove() + for (size_t i = offset; i > index; ) + { + --i; + data[i + nbytes] = data[i]; + } + offset += nbytes; + } +} + +/// +@safe unittest +{ + import std.string : cmp; + + OutBuffer buf = new OutBuffer(); + + assert(buf.offset == 0); + buf.write("hello"); + buf.write(cast(byte) 0x20); + buf.write("world"); + buf.printf(" %d", 62665); + assert(cmp(buf.toString(), "hello world 62665") == 0); + + buf.clear(); + assert(cmp(buf.toString(), "") == 0); + buf.write("New data"); + assert(cmp(buf.toString(),"New data") == 0); +} + +@safe unittest +{ + import std.range; + static assert(isOutputRange!(OutBuffer, char)); + + import std.algorithm; + { + OutBuffer buf = new OutBuffer(); + "hello".copy(buf); + assert(buf.toBytes() == "hello"); + } + { + OutBuffer buf = new OutBuffer(); + "hello"w.copy(buf); + assert(buf.toBytes() == "h\x00e\x00l\x00l\x00o\x00"); + } + { + OutBuffer buf = new OutBuffer(); + "hello"d.copy(buf); + assert(buf.toBytes() == "h\x00\x00\x00e\x00\x00\x00l\x00\x00\x00l\x00\x00\x00o\x00\x00\x00"); + } +} diff --git a/libphobos/src/std/parallelism.d b/libphobos/src/std/parallelism.d new file mode 100644 index 0000000..df07baf --- /dev/null +++ b/libphobos/src/std/parallelism.d @@ -0,0 +1,4636 @@ +/** +$(D std._parallelism) implements high-level primitives for SMP _parallelism. +These include parallel foreach, parallel reduce, parallel eager map, pipelining +and future/promise _parallelism. $(D std._parallelism) is recommended when the +same operation is to be executed in parallel on different data, or when a +function is to be executed in a background thread and its result returned to a +well-defined main thread. For communication between arbitrary threads, see +$(D std.concurrency). + +$(D std._parallelism) is based on the concept of a $(D Task). A $(D Task) is an +object that represents the fundamental unit of work in this library and may be +executed in parallel with any other $(D Task). Using $(D Task) +directly allows programming with a future/promise paradigm. All other +supported _parallelism paradigms (parallel foreach, map, reduce, pipelining) +represent an additional level of abstraction over $(D Task). They +automatically create one or more $(D Task) objects, or closely related types +that are conceptually identical but not part of the public API. + +After creation, a $(D Task) may be executed in a new thread, or submitted +to a $(D TaskPool) for execution. A $(D TaskPool) encapsulates a task queue +and its worker threads. Its purpose is to efficiently map a large +number of $(D Task)s onto a smaller number of threads. A task queue is a +FIFO queue of $(D Task) objects that have been submitted to the +$(D TaskPool) and are awaiting execution. A worker thread is a thread that +is associated with exactly one task queue. It executes the $(D Task) at the +front of its queue when the queue has work available, or sleeps when +no work is available. Each task queue is associated with zero or +more worker threads. If the result of a $(D Task) is needed before execution +by a worker thread has begun, the $(D Task) can be removed from the task queue +and executed immediately in the thread where the result is needed. + +Warning: Unless marked as $(D @trusted) or $(D @safe), artifacts in + this module allow implicit data sharing between threads and cannot + guarantee that client code is free from low level data races. + +Source: $(PHOBOSSRC std/_parallelism.d) +Author: David Simcha +Copyright: Copyright (c) 2009-2011, David Simcha. +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0) +*/ +module std.parallelism; + +/// +@system unittest +{ + import std.algorithm.iteration : map; + import std.math : approxEqual; + import std.parallelism : taskPool; + import std.range : iota; + + // Parallel reduce can be combined with + // std.algorithm.iteration.map to interesting effect. + // The following example (thanks to Russel Winder) + // calculates pi by quadrature using + // std.algorithm.map and TaskPool.reduce. + // getTerm is evaluated in parallel as needed by + // TaskPool.reduce. + // + // Timings on an Intel i5-3450 quad core machine + // for n = 1_000_000_000: + // + // TaskPool.reduce: 1.067 s + // std.algorithm.reduce: 4.011 s + + enum n = 1_000_000; + enum delta = 1.0 / n; + + alias getTerm = (int i) + { + immutable x = ( i - 0.5 ) * delta; + return delta / ( 1.0 + x * x ) ; + }; + + immutable pi = 4.0 * taskPool.reduce!"a + b"(n.iota.map!getTerm); + + assert(pi.approxEqual(3.1415926)); +} + +import core.atomic; +import core.memory; +import core.sync.condition; +import core.thread; + +import std.functional; +import std.meta; +import std.range.primitives; +import std.traits; + +version (OSX) +{ + version = useSysctlbyname; +} +else version (FreeBSD) +{ + version = useSysctlbyname; +} +else version (NetBSD) +{ + version = useSysctlbyname; +} + + +version (Windows) +{ + // BUGS: Only works on Windows 2000 and above. + shared static this() + { + import core.sys.windows.windows : SYSTEM_INFO, GetSystemInfo; + import std.algorithm.comparison : max; + + SYSTEM_INFO si; + GetSystemInfo(&si); + totalCPUs = max(1, cast(uint) si.dwNumberOfProcessors); + } + +} +else version (linux) +{ + shared static this() + { + import core.sys.posix.unistd : _SC_NPROCESSORS_ONLN, sysconf; + totalCPUs = cast(uint) sysconf(_SC_NPROCESSORS_ONLN); + } +} +else version (Solaris) +{ + shared static this() + { + import core.sys.posix.unistd : _SC_NPROCESSORS_ONLN, sysconf; + totalCPUs = cast(uint) sysconf(_SC_NPROCESSORS_ONLN); + } +} +else version (useSysctlbyname) +{ + extern(C) int sysctlbyname( + const char *, void *, size_t *, void *, size_t + ); + + shared static this() + { + version (OSX) + { + auto nameStr = "machdep.cpu.core_count\0".ptr; + } + else version (FreeBSD) + { + auto nameStr = "hw.ncpu\0".ptr; + } + else version (NetBSD) + { + auto nameStr = "hw.ncpu\0".ptr; + } + + uint ans; + size_t len = uint.sizeof; + sysctlbyname(nameStr, &ans, &len, null, 0); + totalCPUs = ans; + } + +} +else +{ + static assert(0, "Don't know how to get N CPUs on this OS."); +} + +immutable size_t cacheLineSize; +shared static this() +{ + import core.cpuid : datacache; + size_t lineSize = 0; + foreach (cachelevel; datacache) + { + if (cachelevel.lineSize > lineSize && cachelevel.lineSize < uint.max) + { + lineSize = cachelevel.lineSize; + } + } + + cacheLineSize = lineSize; +} + + +/* Atomics code. These forward to core.atomic, but are written like this + for two reasons: + + 1. They used to actually contain ASM code and I don' want to have to change + to directly calling core.atomic in a zillion different places. + + 2. core.atomic has some misc. issues that make my use cases difficult + without wrapping it. If I didn't wrap it, casts would be required + basically everywhere. +*/ +private void atomicSetUbyte(T)(ref T stuff, T newVal) +if (__traits(isIntegral, T) && is(T : ubyte)) +{ + //core.atomic.cas(cast(shared) &stuff, stuff, newVal); + atomicStore(*(cast(shared) &stuff), newVal); +} + +private ubyte atomicReadUbyte(T)(ref T val) +if (__traits(isIntegral, T) && is(T : ubyte)) +{ + return atomicLoad(*(cast(shared) &val)); +} + +// This gets rid of the need for a lot of annoying casts in other parts of the +// code, when enums are involved. +private bool atomicCasUbyte(T)(ref T stuff, T testVal, T newVal) +if (__traits(isIntegral, T) && is(T : ubyte)) +{ + return core.atomic.cas(cast(shared) &stuff, testVal, newVal); +} + +/*--------------------- Generic helper functions, etc.------------------------*/ +private template MapType(R, functions...) +{ + static assert(functions.length); + + ElementType!R e = void; + alias MapType = + typeof(adjoin!(staticMap!(unaryFun, functions))(e)); +} + +private template ReduceType(alias fun, R, E) +{ + alias ReduceType = typeof(binaryFun!fun(E.init, ElementType!R.init)); +} + +private template noUnsharedAliasing(T) +{ + enum bool noUnsharedAliasing = !hasUnsharedAliasing!T; +} + +// This template tests whether a function may be executed in parallel from +// @safe code via Task.executeInNewThread(). There is an additional +// requirement for executing it via a TaskPool. (See isSafeReturn). +private template isSafeTask(F) +{ + enum bool isSafeTask = + (functionAttributes!F & (FunctionAttribute.safe | FunctionAttribute.trusted)) != 0 && + (functionAttributes!F & FunctionAttribute.ref_) == 0 && + (isFunctionPointer!F || !hasUnsharedAliasing!F) && + allSatisfy!(noUnsharedAliasing, Parameters!F); +} + +@safe unittest +{ + alias F1 = void function() @safe; + alias F2 = void function(); + alias F3 = void function(uint, string) @trusted; + alias F4 = void function(uint, char[]); + + static assert( isSafeTask!F1); + static assert(!isSafeTask!F2); + static assert( isSafeTask!F3); + static assert(!isSafeTask!F4); + + alias F5 = uint[] function(uint, string) pure @trusted; + static assert( isSafeTask!F5); +} + +// This function decides whether Tasks that meet all of the other requirements +// for being executed from @safe code can be executed on a TaskPool. +// When executing via TaskPool, it's theoretically possible +// to return a value that is also pointed to by a worker thread's thread local +// storage. When executing from executeInNewThread(), the thread that executed +// the Task is terminated by the time the return value is visible in the calling +// thread, so this is a non-issue. It's also a non-issue for pure functions +// since they can't read global state. +private template isSafeReturn(T) +{ + static if (!hasUnsharedAliasing!(T.ReturnType)) + { + enum isSafeReturn = true; + } + else static if (T.isPure) + { + enum isSafeReturn = true; + } + else + { + enum isSafeReturn = false; + } +} + +private template randAssignable(R) +{ + enum randAssignable = isRandomAccessRange!R && hasAssignableElements!R; +} + +private enum TaskStatus : ubyte +{ + notStarted, + inProgress, + done +} + +private template AliasReturn(alias fun, T...) +{ + alias AliasReturn = typeof({ T args; return fun(args); }); +} + +// Should be private, but std.algorithm.reduce is used in the zero-thread case +// and won't work w/ private. +template reduceAdjoin(functions...) +{ + static if (functions.length == 1) + { + alias reduceAdjoin = binaryFun!(functions[0]); + } + else + { + T reduceAdjoin(T, U)(T lhs, U rhs) + { + alias funs = staticMap!(binaryFun, functions); + + foreach (i, Unused; typeof(lhs.expand)) + { + lhs.expand[i] = funs[i](lhs.expand[i], rhs); + } + + return lhs; + } + } +} + +private template reduceFinish(functions...) +{ + static if (functions.length == 1) + { + alias reduceFinish = binaryFun!(functions[0]); + } + else + { + T reduceFinish(T)(T lhs, T rhs) + { + alias funs = staticMap!(binaryFun, functions); + + foreach (i, Unused; typeof(lhs.expand)) + { + lhs.expand[i] = funs[i](lhs.expand[i], rhs.expand[i]); + } + + return lhs; + } + } +} + +private template isRoundRobin(R : RoundRobinBuffer!(C1, C2), C1, C2) +{ + enum isRoundRobin = true; +} + +private template isRoundRobin(T) +{ + enum isRoundRobin = false; +} + +@safe unittest +{ + static assert( isRoundRobin!(RoundRobinBuffer!(void delegate(char[]), bool delegate()))); + static assert(!isRoundRobin!(uint)); +} + +// This is the base "class" for all of the other tasks. Using C-style +// polymorphism to allow more direct control over memory allocation, etc. +private struct AbstractTask +{ + AbstractTask* prev; + AbstractTask* next; + + // Pointer to a function that executes this task. + void function(void*) runTask; + + Throwable exception; + ubyte taskStatus = TaskStatus.notStarted; + + bool done() @property + { + if (atomicReadUbyte(taskStatus) == TaskStatus.done) + { + if (exception) + { + throw exception; + } + + return true; + } + + return false; + } + + void job() + { + runTask(&this); + } +} + +/** +$(D Task) represents the fundamental unit of work. A $(D Task) may be +executed in parallel with any other $(D Task). Using this struct directly +allows future/promise _parallelism. In this paradigm, a function (or delegate +or other callable) is executed in a thread other than the one it was called +from. The calling thread does not block while the function is being executed. +A call to $(D workForce), $(D yieldForce), or $(D spinForce) is used to +ensure that the $(D Task) has finished executing and to obtain the return +value, if any. These functions and $(D done) also act as full memory barriers, +meaning that any memory writes made in the thread that executed the $(D Task) +are guaranteed to be visible in the calling thread after one of these functions +returns. + +The $(REF task, std,parallelism) and $(REF scopedTask, std,parallelism) functions can +be used to create an instance of this struct. See $(D task) for usage examples. + +Function results are returned from $(D yieldForce), $(D spinForce) and +$(D workForce) by ref. If $(D fun) returns by ref, the reference will point +to the returned reference of $(D fun). Otherwise it will point to a +field in this struct. + +Copying of this struct is disabled, since it would provide no useful semantics. +If you want to pass this struct around, you should do so by reference or +pointer. + +Bugs: Changes to $(D ref) and $(D out) arguments are not propagated to the + call site, only to $(D args) in this struct. +*/ +struct Task(alias fun, Args...) +{ + AbstractTask base = {runTask : &impl}; + alias base this; + + private @property AbstractTask* basePtr() + { + return &base; + } + + private static void impl(void* myTask) + { + import std.algorithm.internal : addressOf; + + Task* myCastedTask = cast(typeof(this)*) myTask; + static if (is(ReturnType == void)) + { + fun(myCastedTask._args); + } + else static if (is(typeof(addressOf(fun(myCastedTask._args))))) + { + myCastedTask.returnVal = addressOf(fun(myCastedTask._args)); + } + else + { + myCastedTask.returnVal = fun(myCastedTask._args); + } + } + + private TaskPool pool; + private bool isScoped; // True if created with scopedTask. + + Args _args; + + /** + The arguments the function was called with. Changes to $(D out) and + $(D ref) arguments will be visible here. + */ + static if (__traits(isSame, fun, run)) + { + alias args = _args[1..$]; + } + else + { + alias args = _args; + } + + + // The purpose of this code is to decide whether functions whose + // return values have unshared aliasing can be executed via + // TaskPool from @safe code. See isSafeReturn. + static if (__traits(isSame, fun, run)) + { + static if (isFunctionPointer!(_args[0])) + { + private enum bool isPure = + functionAttributes!(Args[0]) & FunctionAttribute.pure_; + } + else + { + // BUG: Should check this for delegates too, but std.traits + // apparently doesn't allow this. isPure is irrelevant + // for delegates, at least for now since shared delegates + // don't work. + private enum bool isPure = false; + } + + } + else + { + // We already know that we can't execute aliases in @safe code, so + // just put a dummy value here. + private enum bool isPure = false; + } + + + /** + The return type of the function called by this $(D Task). This can be + $(D void). + */ + alias ReturnType = typeof(fun(_args)); + + static if (!is(ReturnType == void)) + { + static if (is(typeof(&fun(_args)))) + { + // Ref return. + ReturnType* returnVal; + + ref ReturnType fixRef(ReturnType* val) + { + return *val; + } + + } + else + { + ReturnType returnVal; + + ref ReturnType fixRef(ref ReturnType val) + { + return val; + } + } + } + + private void enforcePool() + { + import std.exception : enforce; + enforce(this.pool !is null, "Job not submitted yet."); + } + + static if (Args.length > 0) + { + private this(Args args) + { + _args = args; + } + } + + // Work around DMD bug 6588, allow immutable elements. + static if (allSatisfy!(isAssignable, Args)) + { + typeof(this) opAssign(typeof(this) rhs) + { + foreach (i, Type; typeof(this.tupleof)) + { + this.tupleof[i] = rhs.tupleof[i]; + } + return this; + } + } + else + { + @disable typeof(this) opAssign(typeof(this) rhs) + { + assert(0); + } + } + + /** + If the $(D Task) isn't started yet, execute it in the current thread. + If it's done, return its return value, if any. If it's in progress, + busy spin until it's done, then return the return value. If it threw + an exception, rethrow that exception. + + This function should be used when you expect the result of the + $(D Task) to be available on a timescale shorter than that of an OS + context switch. + */ + @property ref ReturnType spinForce() @trusted + { + enforcePool(); + + this.pool.tryDeleteExecute(basePtr); + + while (atomicReadUbyte(this.taskStatus) != TaskStatus.done) {} + + if (exception) + { + throw exception; + } + + static if (!is(ReturnType == void)) + { + return fixRef(this.returnVal); + } + } + + /** + If the $(D Task) isn't started yet, execute it in the current thread. + If it's done, return its return value, if any. If it's in progress, + wait on a condition variable. If it threw an exception, rethrow that + exception. + + This function should be used for expensive functions, as waiting on a + condition variable introduces latency, but avoids wasted CPU cycles. + */ + @property ref ReturnType yieldForce() @trusted + { + enforcePool(); + this.pool.tryDeleteExecute(basePtr); + + if (done) + { + static if (is(ReturnType == void)) + { + return; + } + else + { + return fixRef(this.returnVal); + } + } + + pool.waiterLock(); + scope(exit) pool.waiterUnlock(); + + while (atomicReadUbyte(this.taskStatus) != TaskStatus.done) + { + pool.waitUntilCompletion(); + } + + if (exception) + { + throw exception; // nocoverage + } + + static if (!is(ReturnType == void)) + { + return fixRef(this.returnVal); + } + } + + /** + If this $(D Task) was not started yet, execute it in the current + thread. If it is finished, return its result. If it is in progress, + execute any other $(D Task) from the $(D TaskPool) instance that + this $(D Task) was submitted to until this one + is finished. If it threw an exception, rethrow that exception. + If no other tasks are available or this $(D Task) was executed using + $(D executeInNewThread), wait on a condition variable. + */ + @property ref ReturnType workForce() @trusted + { + enforcePool(); + this.pool.tryDeleteExecute(basePtr); + + while (true) + { + if (done) // done() implicitly checks for exceptions. + { + static if (is(ReturnType == void)) + { + return; + } + else + { + return fixRef(this.returnVal); + } + } + + AbstractTask* job; + { + // Locking explicitly and calling popNoSync() because + // pop() waits on a condition variable if there are no Tasks + // in the queue. + + pool.queueLock(); + scope(exit) pool.queueUnlock(); + job = pool.popNoSync(); + } + + + if (job !is null) + { + + version (verboseUnittest) + { + stderr.writeln("Doing workForce work."); + } + + pool.doJob(job); + + if (done) + { + static if (is(ReturnType == void)) + { + return; + } + else + { + return fixRef(this.returnVal); + } + } + } + else + { + version (verboseUnittest) + { + stderr.writeln("Yield from workForce."); + } + + return yieldForce; + } + } + } + + /** + Returns $(D true) if the $(D Task) is finished executing. + + Throws: Rethrows any exception thrown during the execution of the + $(D Task). + */ + @property bool done() @trusted + { + // Explicitly forwarded for documentation purposes. + return base.done; + } + + /** + Create a new thread for executing this $(D Task), execute it in the + newly created thread, then terminate the thread. This can be used for + future/promise parallelism. An explicit priority may be given + to the $(D Task). If one is provided, its value is forwarded to + $(D core.thread.Thread.priority). See $(REF task, std,parallelism) for + usage example. + */ + void executeInNewThread() @trusted + { + pool = new TaskPool(basePtr); + } + + /// Ditto + void executeInNewThread(int priority) @trusted + { + pool = new TaskPool(basePtr, priority); + } + + @safe ~this() + { + if (isScoped && pool !is null && taskStatus != TaskStatus.done) + { + yieldForce; + } + } + + // When this is uncommented, it somehow gets called on returning from + // scopedTask even though the struct shouldn't be getting copied. + //@disable this(this) {} +} + +// Calls $(D fpOrDelegate) with $(D args). This is an +// adapter that makes $(D Task) work with delegates, function pointers and +// functors instead of just aliases. +ReturnType!F run(F, Args...)(F fpOrDelegate, ref Args args) +{ + return fpOrDelegate(args); +} + +/** +Creates a $(D Task) on the GC heap that calls an alias. This may be executed +via $(D Task.executeInNewThread) or by submitting to a +$(REF TaskPool, std,parallelism). A globally accessible instance of +$(D TaskPool) is provided by $(REF taskPool, std,parallelism). + +Returns: A pointer to the $(D Task). + +Example: +--- +// Read two files into memory at the same time. +import std.file; + +void main() +{ + // Create and execute a Task for reading + // foo.txt. + auto file1Task = task!read("foo.txt"); + file1Task.executeInNewThread(); + + // Read bar.txt in parallel. + auto file2Data = read("bar.txt"); + + // Get the results of reading foo.txt. + auto file1Data = file1Task.yieldForce; +} +--- + +--- +// Sorts an array using a parallel quick sort algorithm. +// The first partition is done serially. Both recursion +// branches are then executed in parallel. +// +// Timings for sorting an array of 1,000,000 doubles on +// an Athlon 64 X2 dual core machine: +// +// This implementation: 176 milliseconds. +// Equivalent serial implementation: 280 milliseconds +void parallelSort(T)(T[] data) +{ + // Sort small subarrays serially. + if (data.length < 100) + { + std.algorithm.sort(data); + return; + } + + // Partition the array. + swap(data[$ / 2], data[$ - 1]); + auto pivot = data[$ - 1]; + bool lessThanPivot(T elem) { return elem < pivot; } + + auto greaterEqual = partition!lessThanPivot(data[0..$ - 1]); + swap(data[$ - greaterEqual.length - 1], data[$ - 1]); + + auto less = data[0..$ - greaterEqual.length - 1]; + greaterEqual = data[$ - greaterEqual.length..$]; + + // Execute both recursion branches in parallel. + auto recurseTask = task!parallelSort(greaterEqual); + taskPool.put(recurseTask); + parallelSort(less); + recurseTask.yieldForce; +} +--- +*/ +auto task(alias fun, Args...)(Args args) +{ + return new Task!(fun, Args)(args); +} + +/** +Creates a $(D Task) on the GC heap that calls a function pointer, delegate, or +class/struct with overloaded opCall. + +Example: +--- +// Read two files in at the same time again, +// but this time use a function pointer instead +// of an alias to represent std.file.read. +import std.file; + +void main() +{ + // Create and execute a Task for reading + // foo.txt. + auto file1Task = task(&read, "foo.txt"); + file1Task.executeInNewThread(); + + // Read bar.txt in parallel. + auto file2Data = read("bar.txt"); + + // Get the results of reading foo.txt. + auto file1Data = file1Task.yieldForce; +} +--- + +Notes: This function takes a non-scope delegate, meaning it can be + used with closures. If you can't allocate a closure due to objects + on the stack that have scoped destruction, see $(D scopedTask), which + takes a scope delegate. + */ +auto task(F, Args...)(F delegateOrFp, Args args) +if (is(typeof(delegateOrFp(args))) && !isSafeTask!F) +{ + return new Task!(run, F, Args)(delegateOrFp, args); +} + +/** +Version of $(D task) usable from $(D @safe) code. Usage mechanics are +identical to the non-@safe case, but safety introduces some restrictions: + +1. $(D fun) must be @safe or @trusted. + +2. $(D F) must not have any unshared aliasing as defined by + $(REF hasUnsharedAliasing, std,traits). This means it + may not be an unshared delegate or a non-shared class or struct + with overloaded $(D opCall). This also precludes accepting template + alias parameters. + +3. $(D Args) must not have unshared aliasing. + +4. $(D fun) must not return by reference. + +5. The return type must not have unshared aliasing unless $(D fun) is + $(D pure) or the $(D Task) is executed via $(D executeInNewThread) instead + of using a $(D TaskPool). + +*/ +@trusted auto task(F, Args...)(F fun, Args args) +if (is(typeof(fun(args))) && isSafeTask!F) +{ + return new Task!(run, F, Args)(fun, args); +} + +/** +These functions allow the creation of $(D Task) objects on the stack rather +than the GC heap. The lifetime of a $(D Task) created by $(D scopedTask) +cannot exceed the lifetime of the scope it was created in. + +$(D scopedTask) might be preferred over $(D task): + +1. When a $(D Task) that calls a delegate is being created and a closure + cannot be allocated due to objects on the stack that have scoped + destruction. The delegate overload of $(D scopedTask) takes a $(D scope) + delegate. + +2. As a micro-optimization, to avoid the heap allocation associated with + $(D task) or with the creation of a closure. + +Usage is otherwise identical to $(D task). + +Notes: $(D Task) objects created using $(D scopedTask) will automatically +call $(D Task.yieldForce) in their destructor if necessary to ensure +the $(D Task) is complete before the stack frame they reside on is destroyed. +*/ +auto scopedTask(alias fun, Args...)(Args args) +{ + auto ret = Task!(fun, Args)(args); + ret.isScoped = true; + return ret; +} + +/// Ditto +auto scopedTask(F, Args...)(scope F delegateOrFp, Args args) +if (is(typeof(delegateOrFp(args))) && !isSafeTask!F) +{ + auto ret = Task!(run, F, Args)(delegateOrFp, args); + ret.isScoped = true; + return ret; +} + +/// Ditto +@trusted auto scopedTask(F, Args...)(F fun, Args args) +if (is(typeof(fun(args))) && isSafeTask!F) +{ + auto ret = Task!(run, F, Args)(fun, args); + ret.isScoped = true; + return ret; +} + +/** +The total number of CPU cores available on the current machine, as reported by +the operating system. +*/ +immutable uint totalCPUs; + +/* +This class serves two purposes: + +1. It distinguishes std.parallelism threads from other threads so that + the std.parallelism daemon threads can be terminated. + +2. It adds a reference to the pool that the thread is a member of, + which is also necessary to allow the daemon threads to be properly + terminated. +*/ +private final class ParallelismThread : Thread +{ + this(void delegate() dg) + { + super(dg); + } + + TaskPool pool; +} + +// Kill daemon threads. +shared static ~this() +{ + foreach (ref thread; Thread) + { + auto pthread = cast(ParallelismThread) thread; + if (pthread is null) continue; + auto pool = pthread.pool; + if (!pool.isDaemon) continue; + pool.stop(); + pthread.join(); + } +} + +/** +This class encapsulates a task queue and a set of worker threads. Its purpose +is to efficiently map a large number of $(D Task)s onto a smaller number of +threads. A task queue is a FIFO queue of $(D Task) objects that have been +submitted to the $(D TaskPool) and are awaiting execution. A worker thread is a +thread that executes the $(D Task) at the front of the queue when one is +available and sleeps when the queue is empty. + +This class should usually be used via the global instantiation +available via the $(REF taskPool, std,parallelism) property. +Occasionally it is useful to explicitly instantiate a $(D TaskPool): + +1. When you want $(D TaskPool) instances with multiple priorities, for example + a low priority pool and a high priority pool. + +2. When the threads in the global task pool are waiting on a synchronization + primitive (for example a mutex), and you want to parallelize the code that + needs to run before these threads can be resumed. + */ +final class TaskPool +{ +private: + + // A pool can either be a regular pool or a single-task pool. A + // single-task pool is a dummy pool that's fired up for + // Task.executeInNewThread(). + bool isSingleTask; + + ParallelismThread[] pool; + Thread singleTaskThread; + + AbstractTask* head; + AbstractTask* tail; + PoolState status = PoolState.running; + Condition workerCondition; + Condition waiterCondition; + Mutex queueMutex; + Mutex waiterMutex; // For waiterCondition + + // The instanceStartIndex of the next instance that will be created. + __gshared static size_t nextInstanceIndex = 1; + + // The index of the current thread. + static size_t threadIndex; + + // The index of the first thread in this instance. + immutable size_t instanceStartIndex; + + // The index that the next thread to be initialized in this pool will have. + size_t nextThreadIndex; + + enum PoolState : ubyte + { + running, + finishing, + stopNow + } + + void doJob(AbstractTask* job) + { + assert(job.taskStatus == TaskStatus.inProgress); + assert(job.next is null); + assert(job.prev is null); + + scope(exit) + { + if (!isSingleTask) + { + waiterLock(); + scope(exit) waiterUnlock(); + notifyWaiters(); + } + } + + try + { + job.job(); + } + catch (Throwable e) + { + job.exception = e; + } + + atomicSetUbyte(job.taskStatus, TaskStatus.done); + } + + // This function is used for dummy pools created by Task.executeInNewThread(). + void doSingleTask() + { + // No synchronization. Pool is guaranteed to only have one thread, + // and the queue is submitted to before this thread is created. + assert(head); + auto t = head; + t.next = t.prev = head = null; + doJob(t); + } + + // This function performs initialization for each thread that affects + // thread local storage and therefore must be done from within the + // worker thread. It then calls executeWorkLoop(). + void startWorkLoop() + { + // Initialize thread index. + { + queueLock(); + scope(exit) queueUnlock(); + threadIndex = nextThreadIndex; + nextThreadIndex++; + } + + executeWorkLoop(); + } + + // This is the main work loop that worker threads spend their time in + // until they terminate. It's also entered by non-worker threads when + // finish() is called with the blocking variable set to true. + void executeWorkLoop() + { + while (atomicReadUbyte(status) != PoolState.stopNow) + { + AbstractTask* task = pop(); + if (task is null) + { + if (atomicReadUbyte(status) == PoolState.finishing) + { + atomicSetUbyte(status, PoolState.stopNow); + return; + } + } + else + { + doJob(task); + } + } + } + + // Pop a task off the queue. + AbstractTask* pop() + { + queueLock(); + scope(exit) queueUnlock(); + auto ret = popNoSync(); + while (ret is null && status == PoolState.running) + { + wait(); + ret = popNoSync(); + } + return ret; + } + + AbstractTask* popNoSync() + out(returned) + { + /* If task.prev and task.next aren't null, then another thread + * can try to delete this task from the pool after it's + * alreadly been deleted/popped. + */ + if (returned !is null) + { + assert(returned.next is null); + assert(returned.prev is null); + } + } + body + { + if (isSingleTask) return null; + + AbstractTask* returned = head; + if (head !is null) + { + head = head.next; + returned.prev = null; + returned.next = null; + returned.taskStatus = TaskStatus.inProgress; + } + if (head !is null) + { + head.prev = null; + } + + return returned; + } + + // Push a task onto the queue. + void abstractPut(AbstractTask* task) + { + queueLock(); + scope(exit) queueUnlock(); + abstractPutNoSync(task); + } + + void abstractPutNoSync(AbstractTask* task) + in + { + assert(task); + } + out + { + import std.conv : text; + + assert(tail.prev !is tail); + assert(tail.next is null, text(tail.prev, '\t', tail.next)); + if (tail.prev !is null) + { + assert(tail.prev.next is tail, text(tail.prev, '\t', tail.next)); + } + } + body + { + // Not using enforce() to save on function call overhead since this + // is a performance critical function. + if (status != PoolState.running) + { + throw new Error( + "Cannot submit a new task to a pool after calling " ~ + "finish() or stop()." + ); + } + + task.next = null; + if (head is null) //Queue is empty. + { + head = task; + tail = task; + tail.prev = null; + } + else + { + assert(tail); + task.prev = tail; + tail.next = task; + tail = task; + } + notify(); + } + + void abstractPutGroupNoSync(AbstractTask* h, AbstractTask* t) + { + if (status != PoolState.running) + { + throw new Error( + "Cannot submit a new task to a pool after calling " ~ + "finish() or stop()." + ); + } + + if (head is null) + { + head = h; + tail = t; + } + else + { + h.prev = tail; + tail.next = h; + tail = t; + } + + notifyAll(); + } + + void tryDeleteExecute(AbstractTask* toExecute) + { + if (isSingleTask) return; + + if ( !deleteItem(toExecute) ) + { + return; + } + + try + { + toExecute.job(); + } + catch (Exception e) + { + toExecute.exception = e; + } + + atomicSetUbyte(toExecute.taskStatus, TaskStatus.done); + } + + bool deleteItem(AbstractTask* item) + { + queueLock(); + scope(exit) queueUnlock(); + return deleteItemNoSync(item); + } + + bool deleteItemNoSync(AbstractTask* item) + { + if (item.taskStatus != TaskStatus.notStarted) + { + return false; + } + item.taskStatus = TaskStatus.inProgress; + + if (item is head) + { + // Make sure head gets set properly. + popNoSync(); + return true; + } + if (item is tail) + { + tail = tail.prev; + if (tail !is null) + { + tail.next = null; + } + item.next = null; + item.prev = null; + return true; + } + if (item.next !is null) + { + assert(item.next.prev is item); // Check queue consistency. + item.next.prev = item.prev; + } + if (item.prev !is null) + { + assert(item.prev.next is item); // Check queue consistency. + item.prev.next = item.next; + } + item.next = null; + item.prev = null; + return true; + } + + void queueLock() + { + assert(queueMutex); + if (!isSingleTask) queueMutex.lock(); + } + + void queueUnlock() + { + assert(queueMutex); + if (!isSingleTask) queueMutex.unlock(); + } + + void waiterLock() + { + if (!isSingleTask) waiterMutex.lock(); + } + + void waiterUnlock() + { + if (!isSingleTask) waiterMutex.unlock(); + } + + void wait() + { + if (!isSingleTask) workerCondition.wait(); + } + + void notify() + { + if (!isSingleTask) workerCondition.notify(); + } + + void notifyAll() + { + if (!isSingleTask) workerCondition.notifyAll(); + } + + void waitUntilCompletion() + { + if (isSingleTask) + { + singleTaskThread.join(); + } + else + { + waiterCondition.wait(); + } + } + + void notifyWaiters() + { + if (!isSingleTask) waiterCondition.notifyAll(); + } + + // Private constructor for creating dummy pools that only have one thread, + // only execute one Task, and then terminate. This is used for + // Task.executeInNewThread(). + this(AbstractTask* task, int priority = int.max) + { + assert(task); + + // Dummy value, not used. + instanceStartIndex = 0; + + this.isSingleTask = true; + task.taskStatus = TaskStatus.inProgress; + this.head = task; + singleTaskThread = new Thread(&doSingleTask); + singleTaskThread.start(); + + // Disabled until writing code to support + // running thread with specified priority + // See https://d.puremagic.com/issues/show_bug.cgi?id=8960 + + /*if (priority != int.max) + { + singleTaskThread.priority = priority; + }*/ + } + +public: + // This is used in parallel_algorithm but is too unstable to document + // as public API. + size_t defaultWorkUnitSize(size_t rangeLen) const @safe pure nothrow + { + import std.algorithm.comparison : max; + + if (this.size == 0) + { + return rangeLen; + } + + immutable size_t eightSize = 4 * (this.size + 1); + auto ret = (rangeLen / eightSize) + ((rangeLen % eightSize == 0) ? 0 : 1); + return max(ret, 1); + } + + /** + Default constructor that initializes a $(D TaskPool) with + $(D totalCPUs) - 1 worker threads. The minus 1 is included because the + main thread will also be available to do work. + + Note: On single-core machines, the primitives provided by $(D TaskPool) + operate transparently in single-threaded mode. + */ + this() @trusted + { + this(totalCPUs - 1); + } + + /** + Allows for custom number of worker threads. + */ + this(size_t nWorkers) @trusted + { + synchronized(typeid(TaskPool)) + { + instanceStartIndex = nextInstanceIndex; + + // The first worker thread to be initialized will have this index, + // and will increment it. The second worker to be initialized will + // have this index plus 1. + nextThreadIndex = instanceStartIndex; + nextInstanceIndex += nWorkers; + } + + queueMutex = new Mutex(this); + waiterMutex = new Mutex(); + workerCondition = new Condition(queueMutex); + waiterCondition = new Condition(waiterMutex); + + pool = new ParallelismThread[nWorkers]; + foreach (ref poolThread; pool) + { + poolThread = new ParallelismThread(&startWorkLoop); + poolThread.pool = this; + poolThread.start(); + } + } + + /** + Implements a parallel foreach loop over a range. This works by implicitly + creating and submitting one $(D Task) to the $(D TaskPool) for each worker + thread. A work unit is a set of consecutive elements of $(D range) to + be processed by a worker thread between communication with any other + thread. The number of elements processed per work unit is controlled by the + $(D workUnitSize) parameter. Smaller work units provide better load + balancing, but larger work units avoid the overhead of communicating + with other threads frequently to fetch the next work unit. Large work + units also avoid false sharing in cases where the range is being modified. + The less time a single iteration of the loop takes, the larger + $(D workUnitSize) should be. For very expensive loop bodies, + $(D workUnitSize) should be 1. An overload that chooses a default work + unit size is also available. + + Example: + --- + // Find the logarithm of every number from 1 to + // 10_000_000 in parallel. + auto logs = new double[10_000_000]; + + // Parallel foreach works with or without an index + // variable. It can be iterate by ref if range.front + // returns by ref. + + // Iterate over logs using work units of size 100. + foreach (i, ref elem; taskPool.parallel(logs, 100)) + { + elem = log(i + 1.0); + } + + // Same thing, but use the default work unit size. + // + // Timings on an Athlon 64 X2 dual core machine: + // + // Parallel foreach: 388 milliseconds + // Regular foreach: 619 milliseconds + foreach (i, ref elem; taskPool.parallel(logs)) + { + elem = log(i + 1.0); + } + --- + + Notes: + + The memory usage of this implementation is guaranteed to be constant + in $(D range.length). + + Breaking from a parallel foreach loop via a break, labeled break, + labeled continue, return or goto statement throws a + $(D ParallelForeachError). + + In the case of non-random access ranges, parallel foreach buffers lazily + to an array of size $(D workUnitSize) before executing the parallel portion + of the loop. The exception is that, if a parallel foreach is executed + over a range returned by $(D asyncBuf) or $(D map), the copying is elided + and the buffers are simply swapped. In this case $(D workUnitSize) is + ignored and the work unit size is set to the buffer size of $(D range). + + A memory barrier is guaranteed to be executed on exit from the loop, + so that results produced by all threads are visible in the calling thread. + + $(B Exception Handling): + + When at least one exception is thrown from inside a parallel foreach loop, + the submission of additional $(D Task) objects is terminated as soon as + possible, in a non-deterministic manner. All executing or + enqueued work units are allowed to complete. Then, all exceptions that + were thrown by any work unit are chained using $(D Throwable.next) and + rethrown. The order of the exception chaining is non-deterministic. + */ + ParallelForeach!R parallel(R)(R range, size_t workUnitSize) + { + import std.exception : enforce; + enforce(workUnitSize > 0, "workUnitSize must be > 0."); + alias RetType = ParallelForeach!R; + return RetType(this, range, workUnitSize); + } + + + /// Ditto + ParallelForeach!R parallel(R)(R range) + { + static if (hasLength!R) + { + // Default work unit size is such that we would use 4x as many + // slots as are in this thread pool. + size_t workUnitSize = defaultWorkUnitSize(range.length); + return parallel(range, workUnitSize); + } + else + { + // Just use a really, really dumb guess if the user is too lazy to + // specify. + return parallel(range, 512); + } + } + + /// + template amap(functions...) + { + /** + Eager parallel map. The eagerness of this function means it has less + overhead than the lazily evaluated $(D TaskPool.map) and should be + preferred where the memory requirements of eagerness are acceptable. + $(D functions) are the functions to be evaluated, passed as template + alias parameters in a style similar to + $(REF map, std,algorithm,iteration). + The first argument must be a random access range. For performance + reasons, amap will assume the range elements have not yet been + initialized. Elements will be overwritten without calling a destructor + nor doing an assignment. As such, the range must not contain meaningful + data$(DDOC_COMMENT not a section): either un-initialized objects, or + objects in their $(D .init) state. + + --- + auto numbers = iota(100_000_000.0); + + // Find the square roots of numbers. + // + // Timings on an Athlon 64 X2 dual core machine: + // + // Parallel eager map: 0.802 s + // Equivalent serial implementation: 1.768 s + auto squareRoots = taskPool.amap!sqrt(numbers); + --- + + Immediately after the range argument, an optional work unit size argument + may be provided. Work units as used by $(D amap) are identical to those + defined for parallel foreach. If no work unit size is provided, the + default work unit size is used. + + --- + // Same thing, but make work unit size 100. + auto squareRoots = taskPool.amap!sqrt(numbers, 100); + --- + + An output range for returning the results may be provided as the last + argument. If one is not provided, an array of the proper type will be + allocated on the garbage collected heap. If one is provided, it must be a + random access range with assignable elements, must have reference + semantics with respect to assignment to its elements, and must have the + same length as the input range. Writing to adjacent elements from + different threads must be safe. + + --- + // Same thing, but explicitly allocate an array + // to return the results in. The element type + // of the array may be either the exact type + // returned by functions or an implicit conversion + // target. + auto squareRoots = new float[numbers.length]; + taskPool.amap!sqrt(numbers, squareRoots); + + // Multiple functions, explicit output range, and + // explicit work unit size. + auto results = new Tuple!(float, real)[numbers.length]; + taskPool.amap!(sqrt, log)(numbers, 100, results); + --- + + Note: + + A memory barrier is guaranteed to be executed after all results are written + but before returning so that results produced by all threads are visible + in the calling thread. + + Tips: + + To perform the mapping operation in place, provide the same range for the + input and output range. + + To parallelize the copying of a range with expensive to evaluate elements + to an array, pass an identity function (a function that just returns + whatever argument is provided to it) to $(D amap). + + $(B Exception Handling): + + When at least one exception is thrown from inside the map functions, + the submission of additional $(D Task) objects is terminated as soon as + possible, in a non-deterministic manner. All currently executing or + enqueued work units are allowed to complete. Then, all exceptions that + were thrown from any work unit are chained using $(D Throwable.next) and + rethrown. The order of the exception chaining is non-deterministic. + */ + auto amap(Args...)(Args args) + if (isRandomAccessRange!(Args[0])) + { + import std.conv : emplaceRef; + + alias fun = adjoin!(staticMap!(unaryFun, functions)); + + alias range = args[0]; + immutable len = range.length; + + static if ( + Args.length > 1 && + randAssignable!(Args[$ - 1]) && + is(MapType!(Args[0], functions) : ElementType!(Args[$ - 1])) + ) + { + import std.conv : text; + import std.exception : enforce; + + alias buf = args[$ - 1]; + alias args2 = args[0..$ - 1]; + alias Args2 = Args[0..$ - 1]; + enforce(buf.length == len, + text("Can't use a user supplied buffer that's the wrong ", + "size. (Expected :", len, " Got: ", buf.length)); + } + else static if (randAssignable!(Args[$ - 1]) && Args.length > 1) + { + static assert(0, "Wrong buffer type."); + } + else + { + import std.array : uninitializedArray; + + auto buf = uninitializedArray!(MapType!(Args[0], functions)[])(len); + alias args2 = args; + alias Args2 = Args; + } + + if (!len) return buf; + + static if (isIntegral!(Args2[$ - 1])) + { + static assert(args2.length == 2); + auto workUnitSize = cast(size_t) args2[1]; + } + else + { + static assert(args2.length == 1, Args); + auto workUnitSize = defaultWorkUnitSize(range.length); + } + + alias R = typeof(range); + + if (workUnitSize > len) + { + workUnitSize = len; + } + + // Handle as a special case: + if (size == 0) + { + size_t index = 0; + foreach (elem; range) + { + emplaceRef(buf[index++], fun(elem)); + } + return buf; + } + + // Effectively -1: chunkIndex + 1 == 0: + shared size_t workUnitIndex = size_t.max; + shared bool shouldContinue = true; + + void doIt() + { + import std.algorithm.comparison : min; + + scope(failure) + { + // If an exception is thrown, all threads should bail. + atomicStore(shouldContinue, false); + } + + while (atomicLoad(shouldContinue)) + { + immutable myUnitIndex = atomicOp!"+="(workUnitIndex, 1); + immutable start = workUnitSize * myUnitIndex; + if (start >= len) + { + atomicStore(shouldContinue, false); + break; + } + + immutable end = min(len, start + workUnitSize); + + static if (hasSlicing!R) + { + auto subrange = range[start .. end]; + foreach (i; start .. end) + { + emplaceRef(buf[i], fun(subrange.front)); + subrange.popFront(); + } + } + else + { + foreach (i; start .. end) + { + emplaceRef(buf[i], fun(range[i])); + } + } + } + } + + submitAndExecute(this, &doIt); + return buf; + } + } + + /// + template map(functions...) + { + /** + A semi-lazy parallel map that can be used for pipelining. The map + functions are evaluated for the first $(D bufSize) elements and stored in a + buffer and made available to $(D popFront). Meanwhile, in the + background a second buffer of the same size is filled. When the first + buffer is exhausted, it is swapped with the second buffer and filled while + the values from what was originally the second buffer are read. This + implementation allows for elements to be written to the buffer without + the need for atomic operations or synchronization for each write, and + enables the mapping function to be evaluated efficiently in parallel. + + $(D map) has more overhead than the simpler procedure used by $(D amap) + but avoids the need to keep all results in memory simultaneously and works + with non-random access ranges. + + Params: + + source = The input range to be mapped. If $(D source) is not random + access it will be lazily buffered to an array of size $(D bufSize) before + the map function is evaluated. (For an exception to this rule, see Notes.) + + bufSize = The size of the buffer to store the evaluated elements. + + workUnitSize = The number of elements to evaluate in a single + $(D Task). Must be less than or equal to $(D bufSize), and + should be a fraction of $(D bufSize) such that all worker threads can be + used. If the default of size_t.max is used, workUnitSize will be set to + the pool-wide default. + + Returns: An input range representing the results of the map. This range + has a length iff $(D source) has a length. + + Notes: + + If a range returned by $(D map) or $(D asyncBuf) is used as an input to + $(D map), then as an optimization the copying from the output buffer + of the first range to the input buffer of the second range is elided, even + though the ranges returned by $(D map) and $(D asyncBuf) are non-random + access ranges. This means that the $(D bufSize) parameter passed to the + current call to $(D map) will be ignored and the size of the buffer + will be the buffer size of $(D source). + + Example: + --- + // Pipeline reading a file, converting each line + // to a number, taking the logarithms of the numbers, + // and performing the additions necessary to find + // the sum of the logarithms. + + auto lineRange = File("numberList.txt").byLine(); + auto dupedLines = std.algorithm.map!"a.idup"(lineRange); + auto nums = taskPool.map!(to!double)(dupedLines); + auto logs = taskPool.map!log10(nums); + + double sum = 0; + foreach (elem; logs) + { + sum += elem; + } + --- + + $(B Exception Handling): + + Any exceptions thrown while iterating over $(D source) + or computing the map function are re-thrown on a call to $(D popFront) or, + if thrown during construction, are simply allowed to propagate to the + caller. In the case of exceptions thrown while computing the map function, + the exceptions are chained as in $(D TaskPool.amap). + */ + auto + map(S)(S source, size_t bufSize = 100, size_t workUnitSize = size_t.max) + if (isInputRange!S) + { + import std.exception : enforce; + + enforce(workUnitSize == size_t.max || workUnitSize <= bufSize, + "Work unit size must be smaller than buffer size."); + alias fun = adjoin!(staticMap!(unaryFun, functions)); + + static final class Map + { + // This is a class because the task needs to be located on the + // heap and in the non-random access case source needs to be on + // the heap, too. + + private: + enum bufferTrick = is(typeof(source.buf1)) && + is(typeof(source.bufPos)) && + is(typeof(source.doBufSwap())); + + alias E = MapType!(S, functions); + E[] buf1, buf2; + S source; + TaskPool pool; + Task!(run, E[] delegate(E[]), E[]) nextBufTask; + size_t workUnitSize; + size_t bufPos; + bool lastTaskWaited; + + static if (isRandomAccessRange!S) + { + alias FromType = S; + + void popSource() + { + import std.algorithm.comparison : min; + + static if (__traits(compiles, source[0 .. source.length])) + { + source = source[min(buf1.length, source.length)..source.length]; + } + else static if (__traits(compiles, source[0..$])) + { + source = source[min(buf1.length, source.length)..$]; + } + else + { + static assert(0, "S must have slicing for Map." + ~ " " ~ S.stringof ~ " doesn't."); + } + } + } + else static if (bufferTrick) + { + // Make sure we don't have the buffer recycling overload of + // asyncBuf. + static if ( + is(typeof(source.source)) && + isRoundRobin!(typeof(source.source)) + ) + { + static assert(0, "Cannot execute a parallel map on " ~ + "the buffer recycling overload of asyncBuf." + ); + } + + alias FromType = typeof(source.buf1); + FromType from; + + // Just swap our input buffer with source's output buffer. + // No need to copy element by element. + FromType dumpToFrom() + { + import std.algorithm.mutation : swap; + + assert(source.buf1.length <= from.length); + from.length = source.buf1.length; + swap(source.buf1, from); + + // Just in case this source has been popped before + // being sent to map: + from = from[source.bufPos..$]; + + static if (is(typeof(source._length))) + { + source._length -= (from.length - source.bufPos); + } + + source.doBufSwap(); + + return from; + } + } + else + { + alias FromType = ElementType!S[]; + + // The temporary array that data is copied to before being + // mapped. + FromType from; + + FromType dumpToFrom() + { + assert(from !is null); + + size_t i; + for (; !source.empty && i < from.length; source.popFront()) + { + from[i++] = source.front; + } + + from = from[0 .. i]; + return from; + } + } + + static if (hasLength!S) + { + size_t _length; + + public @property size_t length() const @safe pure nothrow + { + return _length; + } + } + + this(S source, size_t bufSize, size_t workUnitSize, TaskPool pool) + { + static if (bufferTrick) + { + bufSize = source.buf1.length; + } + + buf1.length = bufSize; + buf2.length = bufSize; + + static if (!isRandomAccessRange!S) + { + from.length = bufSize; + } + + this.workUnitSize = (workUnitSize == size_t.max) ? + pool.defaultWorkUnitSize(bufSize) : workUnitSize; + this.source = source; + this.pool = pool; + + static if (hasLength!S) + { + _length = source.length; + } + + buf1 = fillBuf(buf1); + submitBuf2(); + } + + // The from parameter is a dummy and ignored in the random access + // case. + E[] fillBuf(E[] buf) + { + import std.algorithm.comparison : min; + + static if (isRandomAccessRange!S) + { + import std.range : take; + auto toMap = take(source, buf.length); + scope(success) popSource(); + } + else + { + auto toMap = dumpToFrom(); + } + + buf = buf[0 .. min(buf.length, toMap.length)]; + + // Handle as a special case: + if (pool.size == 0) + { + size_t index = 0; + foreach (elem; toMap) + { + buf[index++] = fun(elem); + } + return buf; + } + + pool.amap!functions(toMap, workUnitSize, buf); + + return buf; + } + + void submitBuf2() + in + { + assert(nextBufTask.prev is null); + assert(nextBufTask.next is null); + } body + { + // Hack to reuse the task object. + + nextBufTask = typeof(nextBufTask).init; + nextBufTask._args[0] = &fillBuf; + nextBufTask._args[1] = buf2; + pool.put(nextBufTask); + } + + void doBufSwap() + { + if (lastTaskWaited) + { + // Then the source is empty. Signal it here. + buf1 = null; + buf2 = null; + + static if (!isRandomAccessRange!S) + { + from = null; + } + + return; + } + + buf2 = buf1; + buf1 = nextBufTask.yieldForce; + bufPos = 0; + + if (source.empty) + { + lastTaskWaited = true; + } + else + { + submitBuf2(); + } + } + + public: + @property auto front() + { + return buf1[bufPos]; + } + + void popFront() + { + static if (hasLength!S) + { + _length--; + } + + bufPos++; + if (bufPos >= buf1.length) + { + doBufSwap(); + } + } + + static if (isInfinite!S) + { + enum bool empty = false; + } + else + { + + bool empty() const @property + { + // popFront() sets this when source is empty + return buf1.length == 0; + } + } + } + return new Map(source, bufSize, workUnitSize, this); + } + } + + /** + Given a $(D source) range that is expensive to iterate over, returns an + input range that asynchronously buffers the contents of + $(D source) into a buffer of $(D bufSize) elements in a worker thread, + while making previously buffered elements from a second buffer, also of size + $(D bufSize), available via the range interface of the returned + object. The returned range has a length iff $(D hasLength!S). + $(D asyncBuf) is useful, for example, when performing expensive operations + on the elements of ranges that represent data on a disk or network. + + Example: + --- + import std.conv, std.stdio; + + void main() + { + // Fetch lines of a file in a background thread + // while processing previously fetched lines, + // dealing with byLine's buffer recycling by + // eagerly duplicating every line. + auto lines = File("foo.txt").byLine(); + auto duped = std.algorithm.map!"a.idup"(lines); + + // Fetch more lines in the background while we + // process the lines already read into memory + // into a matrix of doubles. + double[][] matrix; + auto asyncReader = taskPool.asyncBuf(duped); + + foreach (line; asyncReader) + { + auto ls = line.split("\t"); + matrix ~= to!(double[])(ls); + } + } + --- + + $(B Exception Handling): + + Any exceptions thrown while iterating over $(D source) are re-thrown on a + call to $(D popFront) or, if thrown during construction, simply + allowed to propagate to the caller. + */ + auto asyncBuf(S)(S source, size_t bufSize = 100) if (isInputRange!S) + { + static final class AsyncBuf + { + // This is a class because the task and source both need to be on + // the heap. + + // The element type of S. + alias E = ElementType!S; // Needs to be here b/c of forward ref bugs. + + private: + E[] buf1, buf2; + S source; + TaskPool pool; + Task!(run, E[] delegate(E[]), E[]) nextBufTask; + size_t bufPos; + bool lastTaskWaited; + + static if (hasLength!S) + { + size_t _length; + + // Available if hasLength!S. + public @property size_t length() const @safe pure nothrow + { + return _length; + } + } + + this(S source, size_t bufSize, TaskPool pool) + { + buf1.length = bufSize; + buf2.length = bufSize; + + this.source = source; + this.pool = pool; + + static if (hasLength!S) + { + _length = source.length; + } + + buf1 = fillBuf(buf1); + submitBuf2(); + } + + E[] fillBuf(E[] buf) + { + assert(buf !is null); + + size_t i; + for (; !source.empty && i < buf.length; source.popFront()) + { + buf[i++] = source.front; + } + + buf = buf[0 .. i]; + return buf; + } + + void submitBuf2() + in + { + assert(nextBufTask.prev is null); + assert(nextBufTask.next is null); + } body + { + // Hack to reuse the task object. + + nextBufTask = typeof(nextBufTask).init; + nextBufTask._args[0] = &fillBuf; + nextBufTask._args[1] = buf2; + pool.put(nextBufTask); + } + + void doBufSwap() + { + if (lastTaskWaited) + { + // Then source is empty. Signal it here. + buf1 = null; + buf2 = null; + return; + } + + buf2 = buf1; + buf1 = nextBufTask.yieldForce; + bufPos = 0; + + if (source.empty) + { + lastTaskWaited = true; + } + else + { + submitBuf2(); + } + } + + public: + E front() @property + { + return buf1[bufPos]; + } + + void popFront() + { + static if (hasLength!S) + { + _length--; + } + + bufPos++; + if (bufPos >= buf1.length) + { + doBufSwap(); + } + } + + static if (isInfinite!S) + { + enum bool empty = false; + } + + else + { + /// + bool empty() @property + { + // popFront() sets this when source is empty: + return buf1.length == 0; + } + } + } + return new AsyncBuf(source, bufSize, this); + } + + /** + Given a callable object $(D next) that writes to a user-provided buffer and + a second callable object $(D empty) that determines whether more data is + available to write via $(D next), returns an input range that + asynchronously calls $(D next) with a set of size $(D nBuffers) of buffers + and makes the results available in the order they were obtained via the + input range interface of the returned object. Similarly to the + input range overload of $(D asyncBuf), the first half of the buffers + are made available via the range interface while the second half are + filled and vice-versa. + + Params: + + next = A callable object that takes a single argument that must be an array + with mutable elements. When called, $(D next) writes data to + the array provided by the caller. + + empty = A callable object that takes no arguments and returns a type + implicitly convertible to $(D bool). This is used to signify + that no more data is available to be obtained by calling $(D next). + + initialBufSize = The initial size of each buffer. If $(D next) takes its + array by reference, it may resize the buffers. + + nBuffers = The number of buffers to cycle through when calling $(D next). + + Example: + --- + // Fetch lines of a file in a background + // thread while processing previously fetched + // lines, without duplicating any lines. + auto file = File("foo.txt"); + + void next(ref char[] buf) + { + file.readln(buf); + } + + // Fetch more lines in the background while we + // process the lines already read into memory + // into a matrix of doubles. + double[][] matrix; + auto asyncReader = taskPool.asyncBuf(&next, &file.eof); + + foreach (line; asyncReader) + { + auto ls = line.split("\t"); + matrix ~= to!(double[])(ls); + } + --- + + $(B Exception Handling): + + Any exceptions thrown while iterating over $(D range) are re-thrown on a + call to $(D popFront). + + Warning: + + Using the range returned by this function in a parallel foreach loop + will not work because buffers may be overwritten while the task that + processes them is in queue. This is checked for at compile time + and will result in a static assertion failure. + */ + auto asyncBuf(C1, C2)(C1 next, C2 empty, size_t initialBufSize = 0, size_t nBuffers = 100) + if (is(typeof(C2.init()) : bool) && + Parameters!C1.length == 1 && + Parameters!C2.length == 0 && + isArray!(Parameters!C1[0]) + ) { + auto roundRobin = RoundRobinBuffer!(C1, C2)(next, empty, initialBufSize, nBuffers); + return asyncBuf(roundRobin, nBuffers / 2); + } + + /// + template reduce(functions...) + { + /** + Parallel reduce on a random access range. Except as otherwise noted, + usage is similar to $(REF _reduce, std,algorithm,iteration). This + function works by splitting the range to be reduced into work units, + which are slices to be reduced in parallel. Once the results from all + work units are computed, a final serial reduction is performed on these + results to compute the final answer. Therefore, care must be taken to + choose the seed value appropriately. + + Because the reduction is being performed in parallel, $(D functions) + must be associative. For notational simplicity, let # be an + infix operator representing $(D functions). Then, (a # b) # c must equal + a # (b # c). Floating point addition is not associative + even though addition in exact arithmetic is. Summing floating + point numbers using this function may give different results than summing + serially. However, for many practical purposes floating point addition + can be treated as associative. + + Note that, since $(D functions) are assumed to be associative, + additional optimizations are made to the serial portion of the reduction + algorithm. These take advantage of the instruction level parallelism of + modern CPUs, in addition to the thread-level parallelism that the rest + of this module exploits. This can lead to better than linear speedups + relative to $(REF _reduce, std,algorithm,iteration), especially for + fine-grained benchmarks like dot products. + + An explicit seed may be provided as the first argument. If + provided, it is used as the seed for all work units and for the final + reduction of results from all work units. Therefore, if it is not the + identity value for the operation being performed, results may differ + from those generated by $(REF _reduce, std,algorithm,iteration) or + depending on how many work units are used. The next argument must be + the range to be reduced. + --- + // Find the sum of squares of a range in parallel, using + // an explicit seed. + // + // Timings on an Athlon 64 X2 dual core machine: + // + // Parallel reduce: 72 milliseconds + // Using std.algorithm.reduce instead: 181 milliseconds + auto nums = iota(10_000_000.0f); + auto sumSquares = taskPool.reduce!"a + b"( + 0.0, std.algorithm.map!"a * a"(nums) + ); + --- + + If no explicit seed is provided, the first element of each work unit + is used as a seed. For the final reduction, the result from the first + work unit is used as the seed. + --- + // Find the sum of a range in parallel, using the first + // element of each work unit as the seed. + auto sum = taskPool.reduce!"a + b"(nums); + --- + + An explicit work unit size may be specified as the last argument. + Specifying too small a work unit size will effectively serialize the + reduction, as the final reduction of the result of each work unit will + dominate computation time. If $(D TaskPool.size) for this instance + is zero, this parameter is ignored and one work unit is used. + --- + // Use a work unit size of 100. + auto sum2 = taskPool.reduce!"a + b"(nums, 100); + + // Work unit size of 100 and explicit seed. + auto sum3 = taskPool.reduce!"a + b"(0.0, nums, 100); + --- + + Parallel reduce supports multiple functions, like + $(D std.algorithm.reduce). + --- + // Find both the min and max of nums. + auto minMax = taskPool.reduce!(min, max)(nums); + assert(minMax[0] == reduce!min(nums)); + assert(minMax[1] == reduce!max(nums)); + --- + + $(B Exception Handling): + + After this function is finished executing, any exceptions thrown + are chained together via $(D Throwable.next) and rethrown. The chaining + order is non-deterministic. + */ + auto reduce(Args...)(Args args) + { + import core.exception : OutOfMemoryError; + import std.conv : emplaceRef; + import std.exception : enforce; + + alias fun = reduceAdjoin!functions; + alias finishFun = reduceFinish!functions; + + static if (isIntegral!(Args[$ - 1])) + { + size_t workUnitSize = cast(size_t) args[$ - 1]; + alias args2 = args[0..$ - 1]; + alias Args2 = Args[0..$ - 1]; + } + else + { + alias args2 = args; + alias Args2 = Args; + } + + auto makeStartValue(Type)(Type e) + { + static if (functions.length == 1) + { + return e; + } + else + { + typeof(adjoin!(staticMap!(binaryFun, functions))(e, e)) seed = void; + foreach (i, T; seed.Types) + { + emplaceRef(seed.expand[i], e); + } + + return seed; + } + } + + static if (args2.length == 2) + { + static assert(isInputRange!(Args2[1])); + alias range = args2[1]; + alias seed = args2[0]; + enum explicitSeed = true; + + static if (!is(typeof(workUnitSize))) + { + size_t workUnitSize = defaultWorkUnitSize(range.length); + } + } + else + { + static assert(args2.length == 1); + alias range = args2[0]; + + static if (!is(typeof(workUnitSize))) + { + size_t workUnitSize = defaultWorkUnitSize(range.length); + } + + enforce(!range.empty, + "Cannot reduce an empty range with first element as start value."); + + auto seed = makeStartValue(range.front); + enum explicitSeed = false; + range.popFront(); + } + + alias E = typeof(seed); + alias R = typeof(range); + + E reduceOnRange(R range, size_t lowerBound, size_t upperBound) + { + // This is for exploiting instruction level parallelism by + // using multiple accumulator variables within each thread, + // since we're assuming functions are associative anyhow. + + // This is so that loops can be unrolled automatically. + enum ilpTuple = AliasSeq!(0, 1, 2, 3, 4, 5); + enum nILP = ilpTuple.length; + immutable subSize = (upperBound - lowerBound) / nILP; + + if (subSize <= 1) + { + // Handle as a special case. + static if (explicitSeed) + { + E result = seed; + } + else + { + E result = makeStartValue(range[lowerBound]); + lowerBound++; + } + + foreach (i; lowerBound .. upperBound) + { + result = fun(result, range[i]); + } + + return result; + } + + assert(subSize > 1); + E[nILP] results; + size_t[nILP] offsets; + + foreach (i; ilpTuple) + { + offsets[i] = lowerBound + subSize * i; + + static if (explicitSeed) + { + results[i] = seed; + } + else + { + results[i] = makeStartValue(range[offsets[i]]); + offsets[i]++; + } + } + + immutable nLoop = subSize - (!explicitSeed); + foreach (i; 0 .. nLoop) + { + foreach (j; ilpTuple) + { + results[j] = fun(results[j], range[offsets[j]]); + offsets[j]++; + } + } + + // Finish the remainder. + foreach (i; nILP * subSize + lowerBound .. upperBound) + { + results[$ - 1] = fun(results[$ - 1], range[i]); + } + + foreach (i; ilpTuple[1..$]) + { + results[0] = finishFun(results[0], results[i]); + } + + return results[0]; + } + + immutable len = range.length; + if (len == 0) + { + return seed; + } + + if (this.size == 0) + { + return finishFun(seed, reduceOnRange(range, 0, len)); + } + + // Unlike the rest of the functions here, I can't use the Task object + // recycling trick here because this has to work on non-commutative + // operations. After all the tasks are done executing, fun() has to + // be applied on the results of these to get a final result, but + // it can't be evaluated out of order. + + if (workUnitSize > len) + { + workUnitSize = len; + } + + immutable size_t nWorkUnits = (len / workUnitSize) + ((len % workUnitSize == 0) ? 0 : 1); + assert(nWorkUnits * workUnitSize >= len); + + alias RTask = Task!(run, typeof(&reduceOnRange), R, size_t, size_t); + RTask[] tasks; + + // Can't use alloca() due to Bug 3753. Use a fixed buffer + // backed by malloc(). + enum maxStack = 2_048; + byte[maxStack] buf = void; + immutable size_t nBytesNeeded = nWorkUnits * RTask.sizeof; + + import core.stdc.stdlib : malloc, free; + if (nBytesNeeded < maxStack) + { + tasks = (cast(RTask*) buf.ptr)[0 .. nWorkUnits]; + } + else + { + auto ptr = cast(RTask*) malloc(nBytesNeeded); + if (!ptr) + { + throw new OutOfMemoryError( + "Out of memory in std.parallelism." + ); + } + + tasks = ptr[0 .. nWorkUnits]; + } + + scope(exit) + { + if (nBytesNeeded > maxStack) + { + free(tasks.ptr); + } + } + + foreach (ref t; tasks[]) + emplaceRef(t, RTask()); + + // Hack to take the address of a nested function w/o + // making a closure. + static auto scopedAddress(D)(scope D del) @system + { + auto tmp = del; + return tmp; + } + + size_t curPos = 0; + void useTask(ref RTask task) + { + import std.algorithm.comparison : min; + + task.pool = this; + task._args[0] = scopedAddress(&reduceOnRange); + task._args[3] = min(len, curPos + workUnitSize); // upper bound. + task._args[1] = range; // range + task._args[2] = curPos; // lower bound. + + curPos += workUnitSize; + } + + foreach (ref task; tasks) + { + useTask(task); + } + + foreach (i; 1 .. tasks.length - 1) + { + tasks[i].next = tasks[i + 1].basePtr; + tasks[i + 1].prev = tasks[i].basePtr; + } + + if (tasks.length > 1) + { + queueLock(); + scope(exit) queueUnlock(); + + abstractPutGroupNoSync( + tasks[1].basePtr, + tasks[$ - 1].basePtr + ); + } + + if (tasks.length > 0) + { + try + { + tasks[0].job(); + } + catch (Throwable e) + { + tasks[0].exception = e; + } + tasks[0].taskStatus = TaskStatus.done; + + // Try to execute each of these in the current thread + foreach (ref task; tasks[1..$]) + { + tryDeleteExecute(task.basePtr); + } + } + + // Now that we've tried to execute every task, they're all either + // done or in progress. Force all of them. + E result = seed; + + Throwable firstException, lastException; + + foreach (ref task; tasks) + { + try + { + task.yieldForce; + } + catch (Throwable e) + { + addToChain(e, firstException, lastException); + continue; + } + + if (!firstException) result = finishFun(result, task.returnVal); + } + + if (firstException) throw firstException; + + return result; + } + } + + /** + Gets the index of the current thread relative to this $(D TaskPool). Any + thread not in this pool will receive an index of 0. The worker threads in + this pool receive unique indices of 1 through $(D this.size). + + This function is useful for maintaining worker-local resources. + + Example: + --- + // Execute a loop that computes the greatest common + // divisor of every number from 0 through 999 with + // 42 in parallel. Write the results out to + // a set of files, one for each thread. This allows + // results to be written out without any synchronization. + + import std.conv, std.range, std.numeric, std.stdio; + + void main() + { + auto filesHandles = new File[taskPool.size + 1]; + scope(exit) { + foreach (ref handle; fileHandles) + { + handle.close(); + } + } + + foreach (i, ref handle; fileHandles) + { + handle = File("workerResults" ~ to!string(i) ~ ".txt"); + } + + foreach (num; parallel(iota(1_000))) + { + auto outHandle = fileHandles[taskPool.workerIndex]; + outHandle.writeln(num, '\t', gcd(num, 42)); + } + } + --- + */ + size_t workerIndex() @property @safe const nothrow + { + immutable rawInd = threadIndex; + return (rawInd >= instanceStartIndex && rawInd < instanceStartIndex + size) ? + (rawInd - instanceStartIndex + 1) : 0; + } + + /** + Struct for creating worker-local storage. Worker-local storage is + thread-local storage that exists only for worker threads in a given + $(D TaskPool) plus a single thread outside the pool. It is allocated on the + garbage collected heap in a way that avoids _false sharing, and doesn't + necessarily have global scope within any thread. It can be accessed from + any worker thread in the $(D TaskPool) that created it, and one thread + outside this $(D TaskPool). All threads outside the pool that created a + given instance of worker-local storage share a single slot. + + Since the underlying data for this struct is heap-allocated, this struct + has reference semantics when passed between functions. + + The main uses cases for $(D WorkerLocalStorageStorage) are: + + 1. Performing parallel reductions with an imperative, as opposed to + functional, programming style. In this case, it's useful to treat + $(D WorkerLocalStorageStorage) as local to each thread for only the parallel + portion of an algorithm. + + 2. Recycling temporary buffers across iterations of a parallel foreach loop. + + Example: + --- + // Calculate pi as in our synopsis example, but + // use an imperative instead of a functional style. + immutable n = 1_000_000_000; + immutable delta = 1.0L / n; + + auto sums = taskPool.workerLocalStorage(0.0L); + foreach (i; parallel(iota(n))) + { + immutable x = ( i - 0.5L ) * delta; + immutable toAdd = delta / ( 1.0 + x * x ); + sums.get += toAdd; + } + + // Add up the results from each worker thread. + real pi = 0; + foreach (threadResult; sums.toRange) + { + pi += 4.0L * threadResult; + } + --- + */ + static struct WorkerLocalStorage(T) + { + private: + TaskPool pool; + size_t size; + + size_t elemSize; + bool* stillThreadLocal; + + static size_t roundToLine(size_t num) pure nothrow + { + if (num % cacheLineSize == 0) + { + return num; + } + else + { + return ((num / cacheLineSize) + 1) * cacheLineSize; + } + } + + void* data; + + void initialize(TaskPool pool) + { + this.pool = pool; + size = pool.size + 1; + stillThreadLocal = new bool; + *stillThreadLocal = true; + + // Determines whether the GC should scan the array. + auto blkInfo = (typeid(T).flags & 1) ? + cast(GC.BlkAttr) 0 : + GC.BlkAttr.NO_SCAN; + + immutable nElem = pool.size + 1; + elemSize = roundToLine(T.sizeof); + + // The + 3 is to pad one full cache line worth of space on either side + // of the data structure to make sure false sharing with completely + // unrelated heap data is prevented, and to provide enough padding to + // make sure that data is cache line-aligned. + data = GC.malloc(elemSize * (nElem + 3), blkInfo) + elemSize; + + // Cache line align data ptr. + data = cast(void*) roundToLine(cast(size_t) data); + + foreach (i; 0 .. nElem) + { + this.opIndex(i) = T.init; + } + } + + ref opIndex(this Qualified)(size_t index) + { + import std.conv : text; + assert(index < size, text(index, '\t', uint.max)); + return *(cast(CopyTypeQualifiers!(Qualified, T)*) (data + elemSize * index)); + } + + void opIndexAssign(T val, size_t index) + { + assert(index < size); + *(cast(T*) (data + elemSize * index)) = val; + } + + public: + /** + Get the current thread's instance. Returns by ref. + Note that calling $(D get) from any thread + outside the $(D TaskPool) that created this instance will return the + same reference, so an instance of worker-local storage should only be + accessed from one thread outside the pool that created it. If this + rule is violated, undefined behavior will result. + + If assertions are enabled and $(D toRange) has been called, then this + WorkerLocalStorage instance is no longer worker-local and an assertion + failure will result when calling this method. This is not checked + when assertions are disabled for performance reasons. + */ + ref get(this Qualified)() @property + { + assert(*stillThreadLocal, + "Cannot call get() on this instance of WorkerLocalStorage " ~ + "because it is no longer worker-local." + ); + return opIndex(pool.workerIndex); + } + + /** + Assign a value to the current thread's instance. This function has + the same caveats as its overload. + */ + void get(T val) @property + { + assert(*stillThreadLocal, + "Cannot call get() on this instance of WorkerLocalStorage " ~ + "because it is no longer worker-local." + ); + + opIndexAssign(val, pool.workerIndex); + } + + /** + Returns a range view of the values for all threads, which can be used + to further process the results of each thread after running the parallel + part of your algorithm. Do not use this method in the parallel portion + of your algorithm. + + Calling this function sets a flag indicating that this struct is no + longer worker-local, and attempting to use the $(D get) method again + will result in an assertion failure if assertions are enabled. + */ + WorkerLocalStorageRange!T toRange() @property + { + if (*stillThreadLocal) + { + *stillThreadLocal = false; + + // Make absolutely sure results are visible to all threads. + // This is probably not necessary since some other + // synchronization primitive will be used to signal that the + // parallel part of the algorithm is done, but the + // performance impact should be negligible, so it's better + // to be safe. + ubyte barrierDummy; + atomicSetUbyte(barrierDummy, 1); + } + + return WorkerLocalStorageRange!T(this); + } + } + + /** + Range primitives for worker-local storage. The purpose of this is to + access results produced by each worker thread from a single thread once you + are no longer using the worker-local storage from multiple threads. + Do not use this struct in the parallel portion of your algorithm. + + The proper way to instantiate this object is to call + $(D WorkerLocalStorage.toRange). Once instantiated, this object behaves + as a finite random-access range with assignable, lvalue elements and + a length equal to the number of worker threads in the $(D TaskPool) that + created it plus 1. + */ + static struct WorkerLocalStorageRange(T) + { + private: + WorkerLocalStorage!T workerLocalStorage; + + size_t _length; + size_t beginOffset; + + this(WorkerLocalStorage!T wl) + { + this.workerLocalStorage = wl; + _length = wl.size; + } + + public: + ref front(this Qualified)() @property + { + return this[0]; + } + + ref back(this Qualified)() @property + { + return this[_length - 1]; + } + + void popFront() + { + if (_length > 0) + { + beginOffset++; + _length--; + } + } + + void popBack() + { + if (_length > 0) + { + _length--; + } + } + + typeof(this) save() @property + { + return this; + } + + ref opIndex(this Qualified)(size_t index) + { + assert(index < _length); + return workerLocalStorage[index + beginOffset]; + } + + void opIndexAssign(T val, size_t index) + { + assert(index < _length); + workerLocalStorage[index] = val; + } + + typeof(this) opSlice(size_t lower, size_t upper) + { + assert(upper <= _length); + auto newWl = this.workerLocalStorage; + newWl.data += lower * newWl.elemSize; + newWl.size = upper - lower; + return typeof(this)(newWl); + } + + bool empty() const @property + { + return length == 0; + } + + size_t length() const @property + { + return _length; + } + } + + /** + Creates an instance of worker-local storage, initialized with a given + value. The value is $(D lazy) so that you can, for example, easily + create one instance of a class for each worker. For usage example, + see the $(D WorkerLocalStorage) struct. + */ + WorkerLocalStorage!T workerLocalStorage(T)(lazy T initialVal = T.init) + { + WorkerLocalStorage!T ret; + ret.initialize(this); + foreach (i; 0 .. size + 1) + { + ret[i] = initialVal; + } + + // Memory barrier to make absolutely sure that what we wrote is + // visible to worker threads. + ubyte barrierDummy; + atomicSetUbyte(barrierDummy, 0); + + return ret; + } + + /** + Signals to all worker threads to terminate as soon as they are finished + with their current $(D Task), or immediately if they are not executing a + $(D Task). $(D Task)s that were in queue will not be executed unless + a call to $(D Task.workForce), $(D Task.yieldForce) or $(D Task.spinForce) + causes them to be executed. + + Use only if you have waited on every $(D Task) and therefore know the + queue is empty, or if you speculatively executed some tasks and no longer + need the results. + */ + void stop() @trusted + { + queueLock(); + scope(exit) queueUnlock(); + atomicSetUbyte(status, PoolState.stopNow); + notifyAll(); + } + + /** + Signals worker threads to terminate when the queue becomes empty. + + If blocking argument is true, wait for all worker threads to terminate + before returning. This option might be used in applications where + task results are never consumed-- e.g. when $(D TaskPool) is employed as a + rudimentary scheduler for tasks which communicate by means other than + return values. + + Warning: Calling this function with $(D blocking = true) from a worker + thread that is a member of the same $(D TaskPool) that + $(D finish) is being called on will result in a deadlock. + */ + void finish(bool blocking = false) @trusted + { + { + queueLock(); + scope(exit) queueUnlock(); + atomicCasUbyte(status, PoolState.running, PoolState.finishing); + notifyAll(); + } + if (blocking) + { + // Use this thread as a worker until everything is finished. + executeWorkLoop(); + + foreach (t; pool) + { + // Maybe there should be something here to prevent a thread + // from calling join() on itself if this function is called + // from a worker thread in the same pool, but: + // + // 1. Using an if statement to skip join() would result in + // finish() returning without all tasks being finished. + // + // 2. If an exception were thrown, it would bubble up to the + // Task from which finish() was called and likely be + // swallowed. + t.join(); + } + } + } + + /// Returns the number of worker threads in the pool. + @property size_t size() @safe const pure nothrow + { + return pool.length; + } + + /** + Put a $(D Task) object on the back of the task queue. The $(D Task) + object may be passed by pointer or reference. + + Example: + --- + import std.file; + + // Create a task. + auto t = task!read("foo.txt"); + + // Add it to the queue to be executed. + taskPool.put(t); + --- + + Notes: + + @trusted overloads of this function are called for $(D Task)s if + $(REF hasUnsharedAliasing, std,traits) is false for the $(D Task)'s + return type or the function the $(D Task) executes is $(D pure). + $(D Task) objects that meet all other requirements specified in the + $(D @trusted) overloads of $(D task) and $(D scopedTask) may be created + and executed from $(D @safe) code via $(D Task.executeInNewThread) but + not via $(D TaskPool). + + While this function takes the address of variables that may + be on the stack, some overloads are marked as @trusted. + $(D Task) includes a destructor that waits for the task to complete + before destroying the stack frame it is allocated on. Therefore, + it is impossible for the stack frame to be destroyed before the task is + complete and no longer referenced by a $(D TaskPool). + */ + void put(alias fun, Args...)(ref Task!(fun, Args) task) + if (!isSafeReturn!(typeof(task))) + { + task.pool = this; + abstractPut(task.basePtr); + } + + /// Ditto + void put(alias fun, Args...)(Task!(fun, Args)* task) + if (!isSafeReturn!(typeof(*task))) + { + import std.exception : enforce; + enforce(task !is null, "Cannot put a null Task on a TaskPool queue."); + put(*task); + } + + @trusted void put(alias fun, Args...)(ref Task!(fun, Args) task) + if (isSafeReturn!(typeof(task))) + { + task.pool = this; + abstractPut(task.basePtr); + } + + @trusted void put(alias fun, Args...)(Task!(fun, Args)* task) + if (isSafeReturn!(typeof(*task))) + { + import std.exception : enforce; + enforce(task !is null, "Cannot put a null Task on a TaskPool queue."); + put(*task); + } + + /** + These properties control whether the worker threads are daemon threads. + A daemon thread is automatically terminated when all non-daemon threads + have terminated. A non-daemon thread will prevent a program from + terminating as long as it has not terminated. + + If any $(D TaskPool) with non-daemon threads is active, either $(D stop) + or $(D finish) must be called on it before the program can terminate. + + The worker treads in the $(D TaskPool) instance returned by the + $(D taskPool) property are daemon by default. The worker threads of + manually instantiated task pools are non-daemon by default. + + Note: For a size zero pool, the getter arbitrarily returns true and the + setter has no effect. + */ + bool isDaemon() @property @trusted + { + queueLock(); + scope(exit) queueUnlock(); + return (size == 0) ? true : pool[0].isDaemon; + } + + /// Ditto + void isDaemon(bool newVal) @property @trusted + { + queueLock(); + scope(exit) queueUnlock(); + foreach (thread; pool) + { + thread.isDaemon = newVal; + } + } + + /** + These functions allow getting and setting the OS scheduling priority of + the worker threads in this $(D TaskPool). They forward to + $(D core.thread.Thread.priority), so a given priority value here means the + same thing as an identical priority value in $(D core.thread). + + Note: For a size zero pool, the getter arbitrarily returns + $(D core.thread.Thread.PRIORITY_MIN) and the setter has no effect. + */ + int priority() @property @trusted + { + return (size == 0) ? core.thread.Thread.PRIORITY_MIN : + pool[0].priority; + } + + /// Ditto + void priority(int newPriority) @property @trusted + { + if (size > 0) + { + foreach (t; pool) + { + t.priority = newPriority; + } + } + } +} + +/** +Returns a lazily initialized global instantiation of $(D TaskPool). +This function can safely be called concurrently from multiple non-worker +threads. The worker threads in this pool are daemon threads, meaning that it +is not necessary to call $(D TaskPool.stop) or $(D TaskPool.finish) before +terminating the main thread. +*/ +@property TaskPool taskPool() @trusted +{ + import std.concurrency : initOnce; + __gshared TaskPool pool; + return initOnce!pool({ + auto p = new TaskPool(defaultPoolThreads); + p.isDaemon = true; + return p; + }()); +} + +private shared uint _defaultPoolThreads; +shared static this() +{ + atomicStore(_defaultPoolThreads, totalCPUs - 1); +} + +/** +These properties get and set the number of worker threads in the $(D TaskPool) +instance returned by $(D taskPool). The default value is $(D totalCPUs) - 1. +Calling the setter after the first call to $(D taskPool) does not changes +number of worker threads in the instance returned by $(D taskPool). +*/ +@property uint defaultPoolThreads() @trusted +{ + return atomicLoad(_defaultPoolThreads); +} + +/// Ditto +@property void defaultPoolThreads(uint newVal) @trusted +{ + atomicStore(_defaultPoolThreads, newVal); +} + +/** +Convenience functions that forwards to $(D taskPool.parallel). The +purpose of these is to make parallel foreach less verbose and more +readable. + +Example: +--- +// Find the logarithm of every number from +// 1 to 1_000_000 in parallel, using the +// default TaskPool instance. +auto logs = new double[1_000_000]; + +foreach (i, ref elem; parallel(logs)) +{ + elem = log(i + 1.0); +} +--- + +*/ +ParallelForeach!R parallel(R)(R range) +{ + return taskPool.parallel(range); +} + +/// Ditto +ParallelForeach!R parallel(R)(R range, size_t workUnitSize) +{ + return taskPool.parallel(range, workUnitSize); +} + +// Thrown when a parallel foreach loop is broken from. +class ParallelForeachError : Error +{ + this() + { + super("Cannot break from a parallel foreach loop using break, return, " + ~ "labeled break/continue or goto statements."); + } +} + +/*------Structs that implement opApply for parallel foreach.------------------*/ +private template randLen(R) +{ + enum randLen = isRandomAccessRange!R && hasLength!R; +} + +private void submitAndExecute( + TaskPool pool, + scope void delegate() doIt +) +{ + import core.exception : OutOfMemoryError; + immutable nThreads = pool.size + 1; + + alias PTask = typeof(scopedTask(doIt)); + import core.stdc.stdlib : malloc, free; + import core.stdc.string : memcpy; + + // The logical thing to do would be to just use alloca() here, but that + // causes problems on Windows for reasons that I don't understand + // (tentatively a compiler bug) and definitely doesn't work on Posix due + // to Bug 3753. Therefore, allocate a fixed buffer and fall back to + // malloc() if someone's using a ridiculous amount of threads. Also, + // the using a byte array instead of a PTask array as the fixed buffer + // is to prevent d'tors from being called on uninitialized excess PTask + // instances. + enum nBuf = 64; + byte[nBuf * PTask.sizeof] buf = void; + PTask[] tasks; + if (nThreads <= nBuf) + { + tasks = (cast(PTask*) buf.ptr)[0 .. nThreads]; + } + else + { + auto ptr = cast(PTask*) malloc(nThreads * PTask.sizeof); + if (!ptr) throw new OutOfMemoryError("Out of memory in std.parallelism."); + tasks = ptr[0 .. nThreads]; + } + + scope(exit) + { + if (nThreads > nBuf) + { + free(tasks.ptr); + } + } + + foreach (ref t; tasks) + { + import core.stdc.string : memcpy; + + // This silly looking code is necessary to prevent d'tors from being + // called on uninitialized objects. + auto temp = scopedTask(doIt); + memcpy(&t, &temp, PTask.sizeof); + + // This has to be done to t after copying, not temp before copying. + // Otherwise, temp's destructor will sit here and wait for the + // task to finish. + t.pool = pool; + } + + foreach (i; 1 .. tasks.length - 1) + { + tasks[i].next = tasks[i + 1].basePtr; + tasks[i + 1].prev = tasks[i].basePtr; + } + + if (tasks.length > 1) + { + pool.queueLock(); + scope(exit) pool.queueUnlock(); + + pool.abstractPutGroupNoSync( + tasks[1].basePtr, + tasks[$ - 1].basePtr + ); + } + + if (tasks.length > 0) + { + try + { + tasks[0].job(); + } + catch (Throwable e) + { + tasks[0].exception = e; // nocoverage + } + tasks[0].taskStatus = TaskStatus.done; + + // Try to execute each of these in the current thread + foreach (ref task; tasks[1..$]) + { + pool.tryDeleteExecute(task.basePtr); + } + } + + Throwable firstException, lastException; + + foreach (i, ref task; tasks) + { + try + { + task.yieldForce; + } + catch (Throwable e) + { + addToChain(e, firstException, lastException); + continue; + } + } + + if (firstException) throw firstException; +} + +void foreachErr() +{ + throw new ParallelForeachError(); +} + +int doSizeZeroCase(R, Delegate)(ref ParallelForeach!R p, Delegate dg) +{ + with(p) + { + int res = 0; + size_t index = 0; + + // The explicit ElementType!R in the foreach loops is necessary for + // correct behavior when iterating over strings. + static if (hasLvalueElements!R) + { + foreach (ref ElementType!R elem; range) + { + static if (Parameters!dg.length == 2) + { + res = dg(index, elem); + } + else + { + res = dg(elem); + } + if (res) break; + index++; + } + } + else + { + foreach (ElementType!R elem; range) + { + static if (Parameters!dg.length == 2) + { + res = dg(index, elem); + } + else + { + res = dg(elem); + } + if (res) break; + index++; + } + } + if (res) foreachErr; + return res; + } +} + +private enum string parallelApplyMixinRandomAccess = q{ + // Handle empty thread pool as special case. + if (pool.size == 0) + { + return doSizeZeroCase(this, dg); + } + + // Whether iteration is with or without an index variable. + enum withIndex = Parameters!(typeof(dg)).length == 2; + + shared size_t workUnitIndex = size_t.max; // Effectively -1: chunkIndex + 1 == 0 + immutable len = range.length; + if (!len) return 0; + + shared bool shouldContinue = true; + + void doIt() + { + import std.algorithm.comparison : min; + + scope(failure) + { + // If an exception is thrown, all threads should bail. + atomicStore(shouldContinue, false); + } + + while (atomicLoad(shouldContinue)) + { + immutable myUnitIndex = atomicOp!"+="(workUnitIndex, 1); + immutable start = workUnitSize * myUnitIndex; + if (start >= len) + { + atomicStore(shouldContinue, false); + break; + } + + immutable end = min(len, start + workUnitSize); + + foreach (i; start .. end) + { + static if (withIndex) + { + if (dg(i, range[i])) foreachErr(); + } + else + { + if (dg(range[i])) foreachErr(); + } + } + } + } + + submitAndExecute(pool, &doIt); + + return 0; +}; + +enum string parallelApplyMixinInputRange = q{ + // Handle empty thread pool as special case. + if (pool.size == 0) + { + return doSizeZeroCase(this, dg); + } + + // Whether iteration is with or without an index variable. + enum withIndex = Parameters!(typeof(dg)).length == 2; + + // This protects the range while copying it. + auto rangeMutex = new Mutex(); + + shared bool shouldContinue = true; + + // The total number of elements that have been popped off range. + // This is updated only while protected by rangeMutex; + size_t nPopped = 0; + + static if ( + is(typeof(range.buf1)) && + is(typeof(range.bufPos)) && + is(typeof(range.doBufSwap())) + ) + { + // Make sure we don't have the buffer recycling overload of + // asyncBuf. + static if ( + is(typeof(range.source)) && + isRoundRobin!(typeof(range.source)) + ) + { + static assert(0, "Cannot execute a parallel foreach loop on " ~ + "the buffer recycling overload of asyncBuf."); + } + + enum bool bufferTrick = true; + } + else + { + enum bool bufferTrick = false; + } + + void doIt() + { + scope(failure) + { + // If an exception is thrown, all threads should bail. + atomicStore(shouldContinue, false); + } + + static if (hasLvalueElements!R) + { + alias Temp = ElementType!R*[]; + Temp temp; + + // Returns: The previous value of nPopped. + size_t makeTemp() + { + import std.algorithm.internal : addressOf; + import std.array : uninitializedArray; + + if (temp is null) + { + temp = uninitializedArray!Temp(workUnitSize); + } + + rangeMutex.lock(); + scope(exit) rangeMutex.unlock(); + + size_t i = 0; + for (; i < workUnitSize && !range.empty; range.popFront(), i++) + { + temp[i] = addressOf(range.front); + } + + temp = temp[0 .. i]; + auto ret = nPopped; + nPopped += temp.length; + return ret; + } + + } + else + { + + alias Temp = ElementType!R[]; + Temp temp; + + // Returns: The previous value of nPopped. + static if (!bufferTrick) size_t makeTemp() + { + import std.array : uninitializedArray; + + if (temp is null) + { + temp = uninitializedArray!Temp(workUnitSize); + } + + rangeMutex.lock(); + scope(exit) rangeMutex.unlock(); + + size_t i = 0; + for (; i < workUnitSize && !range.empty; range.popFront(), i++) + { + temp[i] = range.front; + } + + temp = temp[0 .. i]; + auto ret = nPopped; + nPopped += temp.length; + return ret; + } + + static if (bufferTrick) size_t makeTemp() + { + import std.algorithm.mutation : swap; + rangeMutex.lock(); + scope(exit) rangeMutex.unlock(); + + // Elide copying by just swapping buffers. + temp.length = range.buf1.length; + swap(range.buf1, temp); + + // This is necessary in case popFront() has been called on + // range before entering the parallel foreach loop. + temp = temp[range.bufPos..$]; + + static if (is(typeof(range._length))) + { + range._length -= (temp.length - range.bufPos); + } + + range.doBufSwap(); + auto ret = nPopped; + nPopped += temp.length; + return ret; + } + } + + while (atomicLoad(shouldContinue)) + { + auto overallIndex = makeTemp(); + if (temp.empty) + { + atomicStore(shouldContinue, false); + break; + } + + foreach (i; 0 .. temp.length) + { + scope(success) overallIndex++; + + static if (hasLvalueElements!R) + { + static if (withIndex) + { + if (dg(overallIndex, *temp[i])) foreachErr(); + } + else + { + if (dg(*temp[i])) foreachErr(); + } + } + else + { + static if (withIndex) + { + if (dg(overallIndex, temp[i])) foreachErr(); + } + else + { + if (dg(temp[i])) foreachErr(); + } + } + } + } + } + + submitAndExecute(pool, &doIt); + + return 0; +}; + +// Calls e.next until the end of the chain is found. +private Throwable findLastException(Throwable e) pure nothrow +{ + if (e is null) return null; + + while (e.next) + { + e = e.next; + } + + return e; +} + +// Adds e to the exception chain. +private void addToChain( + Throwable e, + ref Throwable firstException, + ref Throwable lastException +) pure nothrow +{ + if (firstException) + { + assert(lastException); // nocoverage + lastException.next = e; // nocoverage + lastException = findLastException(e); // nocoverage + } + else + { + firstException = e; + lastException = findLastException(e); + } +} + +private struct ParallelForeach(R) +{ + TaskPool pool; + R range; + size_t workUnitSize; + alias E = ElementType!R; + + static if (hasLvalueElements!R) + { + alias NoIndexDg = int delegate(ref E); + alias IndexDg = int delegate(size_t, ref E); + } + else + { + alias NoIndexDg = int delegate(E); + alias IndexDg = int delegate(size_t, E); + } + + int opApply(scope NoIndexDg dg) + { + static if (randLen!R) + { + mixin(parallelApplyMixinRandomAccess); + } + else + { + mixin(parallelApplyMixinInputRange); + } + } + + int opApply(scope IndexDg dg) + { + static if (randLen!R) + { + mixin(parallelApplyMixinRandomAccess); + } + else + { + mixin(parallelApplyMixinInputRange); + } + } +} + +/* +This struct buffers the output of a callable that outputs data into a +user-supplied buffer into a set of buffers of some fixed size. It allows these +buffers to be accessed with an input range interface. This is used internally +in the buffer-recycling overload of TaskPool.asyncBuf, which creates an +instance and forwards it to the input range overload of asyncBuf. +*/ +private struct RoundRobinBuffer(C1, C2) +{ + // No need for constraints because they're already checked for in asyncBuf. + + alias Array = Parameters!(C1.init)[0]; + alias T = typeof(Array.init[0]); + + T[][] bufs; + size_t index; + C1 nextDel; + C2 emptyDel; + bool _empty; + bool primed; + + this( + C1 nextDel, + C2 emptyDel, + size_t initialBufSize, + size_t nBuffers + ) { + this.nextDel = nextDel; + this.emptyDel = emptyDel; + bufs.length = nBuffers; + + foreach (ref buf; bufs) + { + buf.length = initialBufSize; + } + } + + void prime() + in + { + assert(!empty); + } + body + { + scope(success) primed = true; + nextDel(bufs[index]); + } + + + T[] front() @property + in + { + assert(!empty); + } + body + { + if (!primed) prime(); + return bufs[index]; + } + + void popFront() + { + if (empty || emptyDel()) + { + _empty = true; + return; + } + + index = (index + 1) % bufs.length; + primed = false; + } + + bool empty() @property const @safe pure nothrow + { + return _empty; + } +} + +version (unittest) +{ + // This was the only way I could get nested maps to work. + __gshared TaskPool poolInstance; + + import std.stdio; +} + +// These test basic functionality but don't stress test for threading bugs. +// These are the tests that should be run every time Phobos is compiled. +@system unittest +{ + import std.algorithm.comparison : equal, min, max; + import std.algorithm.iteration : filter, map, reduce; + import std.array : split; + import std.conv : text; + import std.exception : assertThrown; + import std.math : approxEqual, sqrt, log; + import std.range : indexed, iota, join; + import std.typecons : Tuple, tuple; + + poolInstance = new TaskPool(2); + scope(exit) poolInstance.stop(); + + // The only way this can be verified is manually. + debug(std_parallelism) stderr.writeln("totalCPUs = ", totalCPUs); + + auto oldPriority = poolInstance.priority; + poolInstance.priority = Thread.PRIORITY_MAX; + assert(poolInstance.priority == Thread.PRIORITY_MAX); + + poolInstance.priority = Thread.PRIORITY_MIN; + assert(poolInstance.priority == Thread.PRIORITY_MIN); + + poolInstance.priority = oldPriority; + assert(poolInstance.priority == oldPriority); + + static void refFun(ref uint num) + { + num++; + } + + uint x; + + // Test task(). + auto t = task!refFun(x); + poolInstance.put(t); + t.yieldForce; + assert(t.args[0] == 1); + + auto t2 = task(&refFun, x); + poolInstance.put(t2); + t2.yieldForce; + assert(t2.args[0] == 1); + + // Test scopedTask(). + auto st = scopedTask!refFun(x); + poolInstance.put(st); + st.yieldForce; + assert(st.args[0] == 1); + + auto st2 = scopedTask(&refFun, x); + poolInstance.put(st2); + st2.yieldForce; + assert(st2.args[0] == 1); + + // Test executeInNewThread(). + auto ct = scopedTask!refFun(x); + ct.executeInNewThread(Thread.PRIORITY_MAX); + ct.yieldForce; + assert(ct.args[0] == 1); + + // Test ref return. + uint toInc = 0; + static ref T makeRef(T)(ref T num) + { + return num; + } + + auto t3 = task!makeRef(toInc); + taskPool.put(t3); + assert(t3.args[0] == 0); + t3.spinForce++; + assert(t3.args[0] == 1); + + static void testSafe() @safe { + static int bump(int num) + { + return num + 1; + } + + auto safePool = new TaskPool(0); + auto t = task(&bump, 1); + taskPool.put(t); + assert(t.yieldForce == 2); + + auto st = scopedTask(&bump, 1); + taskPool.put(st); + assert(st.yieldForce == 2); + safePool.stop(); + } + + auto arr = [1,2,3,4,5]; + auto nums = new uint[5]; + auto nums2 = new uint[5]; + + foreach (i, ref elem; poolInstance.parallel(arr)) + { + elem++; + nums[i] = cast(uint) i + 2; + nums2[i] = elem; + } + + assert(nums == [2,3,4,5,6], text(nums)); + assert(nums2 == nums, text(nums2)); + assert(arr == nums, text(arr)); + + // Test const/immutable arguments. + static int add(int lhs, int rhs) + { + return lhs + rhs; + } + immutable addLhs = 1; + immutable addRhs = 2; + auto addTask = task(&add, addLhs, addRhs); + auto addScopedTask = scopedTask(&add, addLhs, addRhs); + poolInstance.put(addTask); + poolInstance.put(addScopedTask); + assert(addTask.yieldForce == 3); + assert(addScopedTask.yieldForce == 3); + + // Test parallel foreach with non-random access range. + auto range = filter!"a != 666"([0, 1, 2, 3, 4]); + + foreach (i, elem; poolInstance.parallel(range)) + { + nums[i] = cast(uint) i; + } + + assert(nums == [0,1,2,3,4]); + + auto logs = new double[1_000_000]; + foreach (i, ref elem; poolInstance.parallel(logs)) + { + elem = log(i + 1.0); + } + + foreach (i, elem; logs) + { + assert(approxEqual(elem, cast(double) log(i + 1))); + } + + assert(poolInstance.amap!"a * a"([1,2,3,4,5]) == [1,4,9,16,25]); + assert(poolInstance.amap!"a * a"([1,2,3,4,5], new long[5]) == [1,4,9,16,25]); + assert(poolInstance.amap!("a * a", "-a")([1,2,3]) == + [tuple(1, -1), tuple(4, -2), tuple(9, -3)]); + + auto tupleBuf = new Tuple!(int, int)[3]; + poolInstance.amap!("a * a", "-a")([1,2,3], tupleBuf); + assert(tupleBuf == [tuple(1, -1), tuple(4, -2), tuple(9, -3)]); + poolInstance.amap!("a * a", "-a")([1,2,3], 5, tupleBuf); + assert(tupleBuf == [tuple(1, -1), tuple(4, -2), tuple(9, -3)]); + + // Test amap with a non-array buffer. + auto toIndex = new int[5]; + auto ind = indexed(toIndex, [3, 1, 4, 0, 2]); + poolInstance.amap!"a * 2"([1, 2, 3, 4, 5], ind); + assert(equal(ind, [2, 4, 6, 8, 10])); + assert(equal(toIndex, [8, 4, 10, 2, 6])); + poolInstance.amap!"a / 2"(ind, ind); + assert(equal(ind, [1, 2, 3, 4, 5])); + assert(equal(toIndex, [4, 2, 5, 1, 3])); + + auto buf = new int[5]; + poolInstance.amap!"a * a"([1,2,3,4,5], buf); + assert(buf == [1,4,9,16,25]); + poolInstance.amap!"a * a"([1,2,3,4,5], 4, buf); + assert(buf == [1,4,9,16,25]); + + assert(poolInstance.reduce!"a + b"([1]) == 1); + assert(poolInstance.reduce!"a + b"([1,2,3,4]) == 10); + assert(poolInstance.reduce!"a + b"(0.0, [1,2,3,4]) == 10); + assert(poolInstance.reduce!"a + b"(0.0, [1,2,3,4], 1) == 10); + assert(poolInstance.reduce!(min, max)([1,2,3,4]) == tuple(1, 4)); + assert(poolInstance.reduce!("a + b", "a * b")(tuple(0, 1), [1,2,3,4]) == + tuple(10, 24)); + + immutable serialAns = reduce!"a + b"(iota(1000)); + assert(poolInstance.reduce!"a + b"(0, iota(1000)) == serialAns); + assert(poolInstance.reduce!"a + b"(iota(1000)) == serialAns); + + // Test worker-local storage. + auto wl = poolInstance.workerLocalStorage(0); + foreach (i; poolInstance.parallel(iota(1000), 1)) + { + wl.get = wl.get + i; + } + + auto wlRange = wl.toRange; + auto parallelSum = poolInstance.reduce!"a + b"(wlRange); + assert(parallelSum == 499500); + assert(wlRange[0 .. 1][0] == wlRange[0]); + assert(wlRange[1 .. 2][0] == wlRange[1]); + + // Test finish() + { + static void slowFun() { Thread.sleep(dur!"msecs"(1)); } + + auto pool1 = new TaskPool(); + auto tSlow = task!slowFun(); + pool1.put(tSlow); + pool1.finish(); + tSlow.yieldForce; + // Can't assert that pool1.status == PoolState.stopNow because status + // doesn't change until after the "done" flag is set and the waiting + // thread is woken up. + + auto pool2 = new TaskPool(); + auto tSlow2 = task!slowFun(); + pool2.put(tSlow2); + pool2.finish(true); // blocking + assert(tSlow2.done); + + // Test fix for Bug 8582 by making pool size zero. + auto pool3 = new TaskPool(0); + auto tSlow3 = task!slowFun(); + pool3.put(tSlow3); + pool3.finish(true); // blocking + assert(tSlow3.done); + + // This is correct because no thread will terminate unless pool2.status + // and pool3.status have already been set to stopNow. + assert(pool2.status == TaskPool.PoolState.stopNow); + assert(pool3.status == TaskPool.PoolState.stopNow); + } + + // Test default pool stuff. + assert(taskPool.size == totalCPUs - 1); + + nums = new uint[1000]; + foreach (i; parallel(iota(1000))) + { + nums[i] = cast(uint) i; + } + assert(equal(nums, iota(1000))); + + assert(equal( + poolInstance.map!"a * a"(iota(30_000_001), 10_000), + map!"a * a"(iota(30_000_001)) + )); + + // The filter is to kill random access and test the non-random access + // branch. + assert(equal( + poolInstance.map!"a * a"( + filter!"a == a"(iota(30_000_001) + ), 10_000, 1000), + map!"a * a"(iota(30_000_001)) + )); + + assert( + reduce!"a + b"(0UL, + poolInstance.map!"a * a"(iota(3_000_001), 10_000) + ) == + reduce!"a + b"(0UL, + map!"a * a"(iota(3_000_001)) + ) + ); + + assert(equal( + iota(1_000_002), + poolInstance.asyncBuf(filter!"a == a"(iota(1_000_002))) + )); + + { + import std.conv : to; + import std.file : deleteme; + + string temp_file = deleteme ~ "-tempDelMe.txt"; + auto file = File(temp_file, "wb"); + scope(exit) + { + file.close(); + import std.file; + remove(temp_file); + } + + auto written = [[1.0, 2, 3], [4.0, 5, 6], [7.0, 8, 9]]; + foreach (row; written) + { + file.writeln(join(to!(string[])(row), "\t")); + } + + file = File(temp_file); + + void next(ref char[] buf) + { + file.readln(buf); + import std.string : chomp; + buf = chomp(buf); + } + + double[][] read; + auto asyncReader = taskPool.asyncBuf(&next, &file.eof); + + foreach (line; asyncReader) + { + if (line.length == 0) continue; + auto ls = line.split("\t"); + read ~= to!(double[])(ls); + } + + assert(read == written); + file.close(); + } + + // Test Map/AsyncBuf chaining. + + auto abuf = poolInstance.asyncBuf(iota(-1.0, 3_000_000), 100); + auto temp = poolInstance.map!sqrt( + abuf, 100, 5 + ); + auto lmchain = poolInstance.map!"a * a"(temp, 100, 5); + lmchain.popFront(); + + int ii; + foreach ( elem; (lmchain)) + { + if (!approxEqual(elem, ii)) + { + stderr.writeln(ii, '\t', elem); + } + ii++; + } + + // Test buffer trick in parallel foreach. + abuf = poolInstance.asyncBuf(iota(-1.0, 1_000_000), 100); + abuf.popFront(); + auto bufTrickTest = new size_t[abuf.length]; + foreach (i, elem; parallel(abuf)) + { + bufTrickTest[i] = i; + } + + assert(equal(iota(1_000_000), bufTrickTest)); + + auto myTask = task!(std.math.abs)(-1); + taskPool.put(myTask); + assert(myTask.spinForce == 1); + + // Test that worker local storage from one pool receives an index of 0 + // when the index is queried w.r.t. another pool. The only way to do this + // is non-deterministically. + foreach (i; parallel(iota(1000), 1)) + { + assert(poolInstance.workerIndex == 0); + } + + foreach (i; poolInstance.parallel(iota(1000), 1)) + { + assert(taskPool.workerIndex == 0); + } + + // Test exception handling. + static void parallelForeachThrow() + { + foreach (elem; parallel(iota(10))) + { + throw new Exception(""); + } + } + + assertThrown!Exception(parallelForeachThrow()); + + static int reduceException(int a, int b) + { + throw new Exception(""); + } + + assertThrown!Exception( + poolInstance.reduce!reduceException(iota(3)) + ); + + static int mapException(int a) + { + throw new Exception(""); + } + + assertThrown!Exception( + poolInstance.amap!mapException(iota(3)) + ); + + static void mapThrow() + { + auto m = poolInstance.map!mapException(iota(3)); + m.popFront(); + } + + assertThrown!Exception(mapThrow()); + + struct ThrowingRange + { + @property int front() + { + return 1; + } + void popFront() + { + throw new Exception(""); + } + enum bool empty = false; + } + + assertThrown!Exception(poolInstance.asyncBuf(ThrowingRange.init)); +} + +//version = parallelismStressTest; + +// These are more like stress tests than real unit tests. They print out +// tons of stuff and should not be run every time make unittest is run. +version (parallelismStressTest) +{ + @safe unittest + { + size_t attempt; + for (; attempt < 10; attempt++) + foreach (poolSize; [0, 4]) + { + + poolInstance = new TaskPool(poolSize); + + uint[] numbers = new uint[1_000]; + + foreach (i; poolInstance.parallel( iota(0, numbers.length)) ) + { + numbers[i] = cast(uint) i; + } + + // Make sure it works. + foreach (i; 0 .. numbers.length) + { + assert(numbers[i] == i); + } + + stderr.writeln("Done creating nums."); + + + auto myNumbers = filter!"a % 7 > 0"( iota(0, 1000)); + foreach (num; poolInstance.parallel(myNumbers)) + { + assert(num % 7 > 0 && num < 1000); + } + stderr.writeln("Done modulus test."); + + uint[] squares = poolInstance.amap!"a * a"(numbers, 100); + assert(squares.length == numbers.length); + foreach (i, number; numbers) + { + assert(squares[i] == number * number); + } + stderr.writeln("Done squares."); + + auto sumFuture = task!( reduce!"a + b" )(numbers); + poolInstance.put(sumFuture); + + ulong sumSquares = 0; + foreach (elem; numbers) + { + sumSquares += elem * elem; + } + + uint mySum = sumFuture.spinForce(); + assert(mySum == 999 * 1000 / 2); + + auto mySumParallel = poolInstance.reduce!"a + b"(numbers); + assert(mySum == mySumParallel); + stderr.writeln("Done sums."); + + auto myTask = task( + { + synchronized writeln("Our lives are parallel...Our lives are parallel."); + }); + poolInstance.put(myTask); + + auto nestedOuter = "abcd"; + auto nestedInner = iota(0, 10, 2); + + foreach (i, letter; poolInstance.parallel(nestedOuter, 1)) + { + foreach (j, number; poolInstance.parallel(nestedInner, 1)) + { + synchronized writeln(i, ": ", letter, " ", j, ": ", number); + } + } + + poolInstance.stop(); + } + + assert(attempt == 10); + writeln("Press enter to go to next round of unittests."); + readln(); + } + + // These unittests are intended more for actual testing and not so much + // as examples. + @safe unittest + { + foreach (attempt; 0 .. 10) + foreach (poolSize; [0, 4]) + { + poolInstance = new TaskPool(poolSize); + + // Test indexing. + stderr.writeln("Creator Raw Index: ", poolInstance.threadIndex); + assert(poolInstance.workerIndex() == 0); + + // Test worker-local storage. + auto workerLocalStorage = poolInstance.workerLocalStorage!uint(1); + foreach (i; poolInstance.parallel(iota(0U, 1_000_000))) + { + workerLocalStorage.get++; + } + assert(reduce!"a + b"(workerLocalStorage.toRange) == + 1_000_000 + poolInstance.size + 1); + + // Make sure work is reasonably balanced among threads. This test is + // non-deterministic and is more of a sanity check than something that + // has an absolute pass/fail. + shared(uint)[void*] nJobsByThread; + foreach (thread; poolInstance.pool) + { + nJobsByThread[cast(void*) thread] = 0; + } + nJobsByThread[ cast(void*) Thread.getThis()] = 0; + + foreach (i; poolInstance.parallel( iota(0, 1_000_000), 100 )) + { + atomicOp!"+="( nJobsByThread[ cast(void*) Thread.getThis() ], 1); + } + + stderr.writeln("\nCurrent thread is: ", + cast(void*) Thread.getThis()); + stderr.writeln("Workload distribution: "); + foreach (k, v; nJobsByThread) + { + stderr.writeln(k, '\t', v); + } + + // Test whether amap can be nested. + real[][] matrix = new real[][](1000, 1000); + foreach (i; poolInstance.parallel( iota(0, matrix.length) )) + { + foreach (j; poolInstance.parallel( iota(0, matrix[0].length) )) + { + matrix[i][j] = i * j; + } + } + + // Get around weird bugs having to do w/ sqrt being an intrinsic: + static real mySqrt(real num) + { + return sqrt(num); + } + + static real[] parallelSqrt(real[] nums) + { + return poolInstance.amap!mySqrt(nums); + } + + real[][] sqrtMatrix = poolInstance.amap!parallelSqrt(matrix); + + foreach (i, row; sqrtMatrix) + { + foreach (j, elem; row) + { + real shouldBe = sqrt( cast(real) i * j); + assert(approxEqual(shouldBe, elem)); + sqrtMatrix[i][j] = shouldBe; + } + } + + auto saySuccess = task( + { + stderr.writeln( + "Success doing matrix stuff that involves nested pool use."); + }); + poolInstance.put(saySuccess); + saySuccess.workForce(); + + // A more thorough test of amap, reduce: Find the sum of the square roots of + // matrix. + + static real parallelSum(real[] input) + { + return poolInstance.reduce!"a + b"(input); + } + + auto sumSqrt = poolInstance.reduce!"a + b"( + poolInstance.amap!parallelSum( + sqrtMatrix + ) + ); + + assert(approxEqual(sumSqrt, 4.437e8)); + stderr.writeln("Done sum of square roots."); + + // Test whether tasks work with function pointers. + auto nanTask = task(&isNaN, 1.0L); + poolInstance.put(nanTask); + assert(nanTask.spinForce == false); + + if (poolInstance.size > 0) + { + // Test work waiting. + static void uselessFun() + { + foreach (i; 0 .. 1_000_000) {} + } + + auto uselessTasks = new typeof(task(&uselessFun))[1000]; + foreach (ref uselessTask; uselessTasks) + { + uselessTask = task(&uselessFun); + } + foreach (ref uselessTask; uselessTasks) + { + poolInstance.put(uselessTask); + } + foreach (ref uselessTask; uselessTasks) + { + uselessTask.workForce(); + } + } + + // Test the case of non-random access + ref returns. + int[] nums = [1,2,3,4,5]; + static struct RemoveRandom + { + int[] arr; + + ref int front() + { + return arr.front; + } + void popFront() + { + arr.popFront(); + } + bool empty() + { + return arr.empty; + } + } + + auto refRange = RemoveRandom(nums); + foreach (ref elem; poolInstance.parallel(refRange)) + { + elem++; + } + assert(nums == [2,3,4,5,6], text(nums)); + stderr.writeln("Nums: ", nums); + + poolInstance.stop(); + } + } +} + +version (unittest) +{ + struct __S_12733 + { + invariant() { assert(checksum == 1_234_567_890); } + this(ulong u){n = u;} + void opAssign(__S_12733 s){this.n = s.n;} + ulong n; + ulong checksum = 1_234_567_890; + } + + static auto __genPair_12733(ulong n) { return __S_12733(n); } +} + +@system unittest +{ + immutable ulong[] data = [ 2UL^^59-1, 2UL^^59-1, 2UL^^59-1, 112_272_537_195_293UL ]; + + auto result = taskPool.amap!__genPair_12733(data); +} + +@safe unittest +{ + import std.range : iota; + + // this test was in std.range, but caused cycles. + assert(__traits(compiles, { foreach (i; iota(0, 100UL).parallel) {} })); +} + +@safe unittest +{ + import std.algorithm.iteration : each; + + long[] arr; + static assert(is(typeof({ + arr.parallel.each!"a++"; + }))); +} + +// https://issues.dlang.org/show_bug.cgi?id=17539 +@system unittest +{ + import std.random : rndGen; + // ensure compilation + try foreach (rnd; rndGen.parallel) break; + catch (ParallelForeachError e) {} +} diff --git a/libphobos/src/std/path.d b/libphobos/src/std/path.d new file mode 100644 index 0000000..32870ea --- /dev/null +++ b/libphobos/src/std/path.d @@ -0,0 +1,4115 @@ +// Written in the D programming language. + +/** This module is used to manipulate _path strings. + + All functions, with the exception of $(LREF expandTilde) (and in some + cases $(LREF absolutePath) and $(LREF relativePath)), are pure + string manipulation functions; they don't depend on any state outside + the program, nor do they perform any actual file system actions. + This has the consequence that the module does not make any distinction + between a _path that points to a directory and a _path that points to a + file, and it does not know whether or not the object pointed to by the + _path actually exists in the file system. + To differentiate between these cases, use $(REF isDir, std,file) and + $(REF exists, std,file). + + Note that on Windows, both the backslash ($(D `\`)) and the slash ($(D `/`)) + are in principle valid directory separators. This module treats them + both on equal footing, but in cases where a $(I new) separator is + added, a backslash will be used. Furthermore, the $(LREF buildNormalizedPath) + function will replace all slashes with backslashes on that platform. + + In general, the functions in this module assume that the input paths + are well-formed. (That is, they should not contain invalid characters, + they should follow the file system's _path format, etc.) The result + of calling a function on an ill-formed _path is undefined. When there + is a chance that a _path or a file name is invalid (for instance, when it + has been input by the user), it may sometimes be desirable to use the + $(LREF isValidFilename) and $(LREF isValidPath) functions to check + this. + + Most functions do not perform any memory allocations, and if a string is + returned, it is usually a slice of an input string. If a function + allocates, this is explicitly mentioned in the documentation. + +$(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, +$(BOOKTABLE, +$(TR $(TH Category) $(TH Functions)) +$(TR $(TD Normalization) $(TD + $(LREF absolutePath) + $(LREF asAbsolutePath) + $(LREF asNormalizedPath) + $(LREF asRelativePath) + $(LREF buildNormalizedPath) + $(LREF buildPath) + $(LREF chainPath) + $(LREF expandTilde) +)) +$(TR $(TD Partitioning) $(TD + $(LREF baseName) + $(LREF dirName) + $(LREF dirSeparator) + $(LREF driveName) + $(LREF pathSeparator) + $(LREF pathSplitter) + $(LREF relativePath) + $(LREF rootName) + $(LREF stripDrive) +)) +$(TR $(TD Validation) $(TD + $(LREF isAbsolute) + $(LREF isDirSeparator) + $(LREF isRooted) + $(LREF isValidFilename) + $(LREF isValidPath) +)) +$(TR $(TD Extension) $(TD + $(LREF defaultExtension) + $(LREF extension) + $(LREF setExtension) + $(LREF stripExtension) + $(LREF withDefaultExtension) + $(LREF withExtension) +)) +$(TR $(TD Other) $(TD + $(LREF filenameCharCmp) + $(LREF filenameCmp) + $(LREF globMatch) + $(LREF CaseSensitive) +)) +)) + + Authors: + Lars Tandle Kyllingstad, + $(HTTP digitalmars.com, Walter Bright), + Grzegorz Adam Hankiewicz, + Thomas K$(UUML)hne, + $(HTTP erdani.org, Andrei Alexandrescu) + Copyright: + Copyright (c) 2000-2014, the authors. All rights reserved. + License: + $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0) + Source: + $(PHOBOSSRC std/_path.d) +*/ +module std.path; + + +// FIXME +import std.file; //: getcwd; +static import std.meta; +import std.range.primitives; +import std.traits; + +version (unittest) +{ +private: + struct TestAliasedString + { + string get() @safe @nogc pure nothrow { return _s; } + alias get this; + @disable this(this); + string _s; + } + + bool testAliasedString(alias func, Args...)(string s, Args args) + { + return func(TestAliasedString(s), args) == func(s, args); + } +} + +/** String used to separate directory names in a path. Under + POSIX this is a slash, under Windows a backslash. +*/ +version (Posix) enum string dirSeparator = "/"; +else version (Windows) enum string dirSeparator = "\\"; +else static assert(0, "unsupported platform"); + + + + +/** Path separator string. A colon under POSIX, a semicolon + under Windows. +*/ +version (Posix) enum string pathSeparator = ":"; +else version (Windows) enum string pathSeparator = ";"; +else static assert(0, "unsupported platform"); + + + + +/** Determines whether the given character is a directory separator. + + On Windows, this includes both $(D `\`) and $(D `/`). + On POSIX, it's just $(D `/`). +*/ +bool isDirSeparator(dchar c) @safe pure nothrow @nogc +{ + if (c == '/') return true; + version (Windows) if (c == '\\') return true; + return false; +} + + +/* Determines whether the given character is a drive separator. + + On Windows, this is true if c is the ':' character that separates + the drive letter from the rest of the path. On POSIX, this always + returns false. +*/ +private bool isDriveSeparator(dchar c) @safe pure nothrow @nogc +{ + version (Windows) return c == ':'; + else return false; +} + + +/* Combines the isDirSeparator and isDriveSeparator tests. */ +version (Windows) private bool isSeparator(dchar c) @safe pure nothrow @nogc +{ + return isDirSeparator(c) || isDriveSeparator(c); +} +version (Posix) private alias isSeparator = isDirSeparator; + + +/* Helper function that determines the position of the last + drive/directory separator in a string. Returns -1 if none + is found. +*/ +private ptrdiff_t lastSeparator(R)(R path) +if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || + isNarrowString!R) +{ + auto i = (cast(ptrdiff_t) path.length) - 1; + while (i >= 0 && !isSeparator(path[i])) --i; + return i; +} + + +version (Windows) +{ + private bool isUNC(R)(R path) + if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || + isNarrowString!R) + { + return path.length >= 3 && isDirSeparator(path[0]) && isDirSeparator(path[1]) + && !isDirSeparator(path[2]); + } + + private ptrdiff_t uncRootLength(R)(R path) + if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || + isNarrowString!R) + in { assert(isUNC(path)); } + body + { + ptrdiff_t i = 3; + while (i < path.length && !isDirSeparator(path[i])) ++i; + if (i < path.length) + { + auto j = i; + do { ++j; } while (j < path.length && isDirSeparator(path[j])); + if (j < path.length) + { + do { ++j; } while (j < path.length && !isDirSeparator(path[j])); + i = j; + } + } + return i; + } + + private bool hasDrive(R)(R path) + if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || + isNarrowString!R) + { + return path.length >= 2 && isDriveSeparator(path[1]); + } + + private bool isDriveRoot(R)(R path) + if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || + isNarrowString!R) + { + return path.length >= 3 && isDriveSeparator(path[1]) + && isDirSeparator(path[2]); + } +} + + +/* Helper functions that strip leading/trailing slashes and backslashes + from a path. +*/ +private auto ltrimDirSeparators(R)(R path) +if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementType!R) || + isNarrowString!R) +{ + static if (isRandomAccessRange!R && hasSlicing!R || isNarrowString!R) + { + int i = 0; + while (i < path.length && isDirSeparator(path[i])) + ++i; + return path[i .. path.length]; + } + else + { + while (!path.empty && isDirSeparator(path.front)) + path.popFront(); + return path; + } +} + +@system unittest +{ + import std.array; + import std.utf : byDchar; + + assert(ltrimDirSeparators("//abc//").array == "abc//"); + assert(ltrimDirSeparators("//abc//"d).array == "abc//"d); + assert(ltrimDirSeparators("//abc//".byDchar).array == "abc//"d); +} + +private auto rtrimDirSeparators(R)(R path) +if (isBidirectionalRange!R && isSomeChar!(ElementType!R) || + isNarrowString!R) +{ + static if (isRandomAccessRange!R && hasSlicing!R && hasLength!R || isNarrowString!R) + { + auto i = (cast(ptrdiff_t) path.length) - 1; + while (i >= 0 && isDirSeparator(path[i])) + --i; + return path[0 .. i+1]; + } + else + { + while (!path.empty && isDirSeparator(path.back)) + path.popBack(); + return path; + } +} + +@system unittest +{ + import std.array; + import std.utf : byDchar; + + assert(rtrimDirSeparators("//abc//").array == "//abc"); + assert(rtrimDirSeparators("//abc//"d).array == "//abc"d); + + assert(rtrimDirSeparators(MockBiRange!char("//abc//")).array == "//abc"); +} + +private auto trimDirSeparators(R)(R path) +if (isBidirectionalRange!R && isSomeChar!(ElementType!R) || + isNarrowString!R) +{ + return ltrimDirSeparators(rtrimDirSeparators(path)); +} + +@system unittest +{ + import std.array; + import std.utf : byDchar; + + assert(trimDirSeparators("//abc//").array == "abc"); + assert(trimDirSeparators("//abc//"d).array == "abc"d); + + assert(trimDirSeparators(MockBiRange!char("//abc//")).array == "abc"); +} + + + + +/** This $(D enum) is used as a template argument to functions which + compare file names, and determines whether the comparison is + case sensitive or not. +*/ +enum CaseSensitive : bool +{ + /// File names are case insensitive + no = false, + + /// File names are case sensitive + yes = true, + + /** The default (or most common) setting for the current platform. + That is, $(D no) on Windows and Mac OS X, and $(D yes) on all + POSIX systems except OS X (Linux, *BSD, etc.). + */ + osDefault = osDefaultCaseSensitivity +} +version (Windows) private enum osDefaultCaseSensitivity = false; +else version (OSX) private enum osDefaultCaseSensitivity = false; +else version (Posix) private enum osDefaultCaseSensitivity = true; +else static assert(0); + + + + +/** + Params: + cs = Whether or not suffix matching is case-sensitive. + path = A path name. It can be a string, or any random-access range of + characters. + suffix = An optional suffix to be removed from the file name. + Returns: The name of the file in the path name, without any leading + directory and with an optional suffix chopped off. + + If $(D suffix) is specified, it will be compared to $(D path) + using $(D filenameCmp!cs), + where $(D cs) is an optional template parameter determining whether + the comparison is case sensitive or not. See the + $(LREF filenameCmp) documentation for details. + + Example: + --- + assert(baseName("dir/file.ext") == "file.ext"); + assert(baseName("dir/file.ext", ".ext") == "file"); + assert(baseName("dir/file.ext", ".xyz") == "file.ext"); + assert(baseName("dir/filename", "name") == "file"); + assert(baseName("dir/subdir/") == "subdir"); + + version (Windows) + { + assert(baseName(`d:file.ext`) == "file.ext"); + assert(baseName(`d:\dir\file.ext`) == "file.ext"); + } + --- + + Note: + This function $(I only) strips away the specified suffix, which + doesn't necessarily have to represent an extension. + To remove the extension from a path, regardless of what the extension + is, use $(LREF stripExtension). + To obtain the filename without leading directories and without + an extension, combine the functions like this: + --- + assert(baseName(stripExtension("dir/file.ext")) == "file"); + --- + + Standards: + This function complies with + $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/basename.html, + the POSIX requirements for the 'basename' shell utility) + (with suitable adaptations for Windows paths). +*/ +auto baseName(R)(R path) +if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) && !isSomeString!R) +{ + return _baseName(path); +} + +/// ditto +auto baseName(C)(C[] path) +if (isSomeChar!C) +{ + return _baseName(path); +} + +private R _baseName(R)(R path) +if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) || isNarrowString!R) +{ + auto p1 = stripDrive(path); + if (p1.empty) + { + version (Windows) if (isUNC(path)) + return path[0 .. 1]; + static if (isSomeString!R) + return null; + else + return p1; // which is empty + } + + auto p2 = rtrimDirSeparators(p1); + if (p2.empty) return p1[0 .. 1]; + + return p2[lastSeparator(p2)+1 .. p2.length]; +} + +/// ditto +inout(C)[] baseName(CaseSensitive cs = CaseSensitive.osDefault, C, C1) + (inout(C)[] path, in C1[] suffix) + @safe pure //TODO: nothrow (because of filenameCmp()) +if (isSomeChar!C && isSomeChar!C1) +{ + auto p = baseName(path); + if (p.length > suffix.length + && filenameCmp!cs(cast(const(C)[])p[$-suffix.length .. $], suffix) == 0) + { + return p[0 .. $-suffix.length]; + } + else return p; +} + +@safe unittest +{ + assert(baseName("").empty); + assert(baseName("file.ext"w) == "file.ext"); + assert(baseName("file.ext"d, ".ext") == "file"); + assert(baseName("file", "file"w.dup) == "file"); + assert(baseName("dir/file.ext"d.dup) == "file.ext"); + assert(baseName("dir/file.ext", ".ext"d) == "file"); + assert(baseName("dir/file"w, "file"d) == "file"); + assert(baseName("dir///subdir////") == "subdir"); + assert(baseName("dir/subdir.ext/", ".ext") == "subdir"); + assert(baseName("dir/subdir/".dup, "subdir") == "subdir"); + assert(baseName("/"w.dup) == "/"); + assert(baseName("//"d.dup) == "/"); + assert(baseName("///") == "/"); + + assert(baseName!(CaseSensitive.yes)("file.ext", ".EXT") == "file.ext"); + assert(baseName!(CaseSensitive.no)("file.ext", ".EXT") == "file"); + + { + auto r = MockRange!(immutable(char))(`dir/file.ext`); + auto s = r.baseName(); + foreach (i, c; `file`) + assert(s[i] == c); + } + + version (Windows) + { + assert(baseName(`dir\file.ext`) == `file.ext`); + assert(baseName(`dir\file.ext`, `.ext`) == `file`); + assert(baseName(`dir\file`, `file`) == `file`); + assert(baseName(`d:file.ext`) == `file.ext`); + assert(baseName(`d:file.ext`, `.ext`) == `file`); + assert(baseName(`d:file`, `file`) == `file`); + assert(baseName(`dir\\subdir\\\`) == `subdir`); + assert(baseName(`dir\subdir.ext\`, `.ext`) == `subdir`); + assert(baseName(`dir\subdir\`, `subdir`) == `subdir`); + assert(baseName(`\`) == `\`); + assert(baseName(`\\`) == `\`); + assert(baseName(`\\\`) == `\`); + assert(baseName(`d:\`) == `\`); + assert(baseName(`d:`).empty); + assert(baseName(`\\server\share\file`) == `file`); + assert(baseName(`\\server\share\`) == `\`); + assert(baseName(`\\server\share`) == `\`); + + auto r = MockRange!(immutable(char))(`\\server\share`); + auto s = r.baseName(); + foreach (i, c; `\`) + assert(s[i] == c); + } + + assert(baseName(stripExtension("dir/file.ext")) == "file"); + + static assert(baseName("dir/file.ext") == "file.ext"); + static assert(baseName("dir/file.ext", ".ext") == "file"); + + static struct DirEntry { string s; alias s this; } + assert(baseName(DirEntry("dir/file.ext")) == "file.ext"); +} + +@safe unittest +{ + assert(testAliasedString!baseName("file")); + + enum S : string { a = "file/path/to/test" } + assert(S.a.baseName == "test"); + + char[S.a.length] sa = S.a[]; + assert(sa.baseName == "test"); +} + +/** Returns the directory part of a path. On Windows, this + includes the drive letter if present. + + Params: + path = A path name. + + Returns: + A slice of $(D path) or ".". + + Standards: + This function complies with + $(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html, + the POSIX requirements for the 'dirname' shell utility) + (with suitable adaptations for Windows paths). +*/ +auto dirName(R)(R path) +if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) && !isSomeString!R) +{ + return _dirName(path); +} + +/// ditto +auto dirName(C)(C[] path) +if (isSomeChar!C) +{ + return _dirName(path); +} + +private auto _dirName(R)(R path) +if (isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) || + isNarrowString!R) +{ + static auto result(bool dot, typeof(path[0 .. 1]) p) + { + static if (isSomeString!R) + return dot ? "." : p; + else + { + import std.range : choose, only; + return choose(dot, only(cast(ElementEncodingType!R)'.'), p); + } + } + + if (path.empty) + return result(true, path[0 .. 0]); + + auto p = rtrimDirSeparators(path); + if (p.empty) + return result(false, path[0 .. 1]); + + version (Windows) + { + if (isUNC(p) && uncRootLength(p) == p.length) + return result(false, p); + + if (p.length == 2 && isDriveSeparator(p[1]) && path.length > 2) + return result(false, path[0 .. 3]); + } + + auto i = lastSeparator(p); + if (i == -1) + return result(true, p); + if (i == 0) + return result(false, p[0 .. 1]); + + version (Windows) + { + // If the directory part is either d: or d:\ + // do not chop off the last symbol. + if (isDriveSeparator(p[i]) || isDriveSeparator(p[i-1])) + return result(false, p[0 .. i+1]); + } + // Remove any remaining trailing (back)slashes. + return result(false, rtrimDirSeparators(p[0 .. i])); +} + +/// +@safe unittest +{ + assert(dirName("") == "."); + assert(dirName("file"w) == "."); + assert(dirName("dir/"d) == "."); + assert(dirName("dir///") == "."); + assert(dirName("dir/file"w.dup) == "dir"); + assert(dirName("dir///file"d.dup) == "dir"); + assert(dirName("dir/subdir/") == "dir"); + assert(dirName("/dir/file"w) == "/dir"); + assert(dirName("/file"d) == "/"); + assert(dirName("/") == "/"); + assert(dirName("///") == "/"); + + version (Windows) + { + assert(dirName(`dir\`) == `.`); + assert(dirName(`dir\\\`) == `.`); + assert(dirName(`dir\file`) == `dir`); + assert(dirName(`dir\\\file`) == `dir`); + assert(dirName(`dir\subdir\`) == `dir`); + assert(dirName(`\dir\file`) == `\dir`); + assert(dirName(`\file`) == `\`); + assert(dirName(`\`) == `\`); + assert(dirName(`\\\`) == `\`); + assert(dirName(`d:`) == `d:`); + assert(dirName(`d:file`) == `d:`); + assert(dirName(`d:\`) == `d:\`); + assert(dirName(`d:\file`) == `d:\`); + assert(dirName(`d:\dir\file`) == `d:\dir`); + assert(dirName(`\\server\share\dir\file`) == `\\server\share\dir`); + assert(dirName(`\\server\share\file`) == `\\server\share`); + assert(dirName(`\\server\share\`) == `\\server\share`); + assert(dirName(`\\server\share`) == `\\server\share`); + } +} + +@safe unittest +{ + assert(testAliasedString!dirName("file")); + + enum S : string { a = "file/path/to/test" } + assert(S.a.dirName == "file/path/to"); + + char[S.a.length] sa = S.a[]; + assert(sa.dirName == "file/path/to"); +} + +@system unittest +{ + static assert(dirName("dir/file") == "dir"); + + import std.array; + import std.utf : byChar, byWchar, byDchar; + + assert(dirName("".byChar).array == "."); + assert(dirName("file"w.byWchar).array == "."w); + assert(dirName("dir/"d.byDchar).array == "."d); + assert(dirName("dir///".byChar).array == "."); + assert(dirName("dir/subdir/".byChar).array == "dir"); + assert(dirName("/dir/file"w.byWchar).array == "/dir"w); + assert(dirName("/file"d.byDchar).array == "/"d); + assert(dirName("/".byChar).array == "/"); + assert(dirName("///".byChar).array == "/"); + + version (Windows) + { + assert(dirName(`dir\`.byChar).array == `.`); + assert(dirName(`dir\\\`.byChar).array == `.`); + assert(dirName(`dir\file`.byChar).array == `dir`); + assert(dirName(`dir\\\file`.byChar).array == `dir`); + assert(dirName(`dir\subdir\`.byChar).array == `dir`); + assert(dirName(`\dir\file`.byChar).array == `\dir`); + assert(dirName(`\file`.byChar).array == `\`); + assert(dirName(`\`.byChar).array == `\`); + assert(dirName(`\\\`.byChar).array == `\`); + assert(dirName(`d:`.byChar).array == `d:`); + assert(dirName(`d:file`.byChar).array == `d:`); + assert(dirName(`d:\`.byChar).array == `d:\`); + assert(dirName(`d:\file`.byChar).array == `d:\`); + assert(dirName(`d:\dir\file`.byChar).array == `d:\dir`); + assert(dirName(`\\server\share\dir\file`.byChar).array == `\\server\share\dir`); + assert(dirName(`\\server\share\file`) == `\\server\share`); + assert(dirName(`\\server\share\`.byChar).array == `\\server\share`); + assert(dirName(`\\server\share`.byChar).array == `\\server\share`); + } + + //static assert(dirName("dir/file".byChar).array == "dir"); +} + + + + +/** Returns the root directory of the specified path, or $(D null) if the + path is not rooted. + + Params: + path = A path name. + + Returns: + A slice of $(D path). +*/ +auto rootName(R)(R path) +if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) || + isNarrowString!R) && + !isConvertibleToString!R) +{ + if (path.empty) + goto Lnull; + + version (Posix) + { + if (isDirSeparator(path[0])) return path[0 .. 1]; + } + else version (Windows) + { + if (isDirSeparator(path[0])) + { + if (isUNC(path)) return path[0 .. uncRootLength(path)]; + else return path[0 .. 1]; + } + else if (path.length >= 3 && isDriveSeparator(path[1]) && + isDirSeparator(path[2])) + { + return path[0 .. 3]; + } + } + else static assert(0, "unsupported platform"); + + assert(!isRooted(path)); +Lnull: + static if (is(StringTypeOf!R)) + return null; // legacy code may rely on null return rather than slice + else + return path[0 .. 0]; +} + +/// +@safe unittest +{ + assert(rootName("") is null); + assert(rootName("foo") is null); + assert(rootName("/") == "/"); + assert(rootName("/foo/bar") == "/"); + + version (Windows) + { + assert(rootName("d:foo") is null); + assert(rootName(`d:\foo`) == `d:\`); + assert(rootName(`\\server\share\foo`) == `\\server\share`); + assert(rootName(`\\server\share`) == `\\server\share`); + } +} + +@safe unittest +{ + assert(testAliasedString!rootName("/foo/bar")); +} + +@safe unittest +{ + import std.array; + import std.utf : byChar; + + assert(rootName("".byChar).array == ""); + assert(rootName("foo".byChar).array == ""); + assert(rootName("/".byChar).array == "/"); + assert(rootName("/foo/bar".byChar).array == "/"); + + version (Windows) + { + assert(rootName("d:foo".byChar).array == ""); + assert(rootName(`d:\foo`.byChar).array == `d:\`); + assert(rootName(`\\server\share\foo`.byChar).array == `\\server\share`); + assert(rootName(`\\server\share`.byChar).array == `\\server\share`); + } +} + +auto rootName(R)(R path) +if (isConvertibleToString!R) +{ + return rootName!(StringTypeOf!R)(path); +} + + +/** + Get the drive portion of a path. + + Params: + path = string or range of characters + + Returns: + A slice of $(D _path) that is the drive, or an empty range if the drive + is not specified. In the case of UNC paths, the network share + is returned. + + Always returns an empty range on POSIX. +*/ +auto driveName(R)(R path) +if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) || + isNarrowString!R) && + !isConvertibleToString!R) +{ + version (Windows) + { + if (hasDrive(path)) + return path[0 .. 2]; + else if (isUNC(path)) + return path[0 .. uncRootLength(path)]; + } + static if (isSomeString!R) + return cast(ElementEncodingType!R[]) null; // legacy code may rely on null return rather than slice + else + return path[0 .. 0]; +} + +/// +@safe unittest +{ + import std.range : empty; + version (Posix) assert(driveName("c:/foo").empty); + version (Windows) + { + assert(driveName(`dir\file`).empty); + assert(driveName(`d:file`) == "d:"); + assert(driveName(`d:\file`) == "d:"); + assert(driveName("d:") == "d:"); + assert(driveName(`\\server\share\file`) == `\\server\share`); + assert(driveName(`\\server\share\`) == `\\server\share`); + assert(driveName(`\\server\share`) == `\\server\share`); + + static assert(driveName(`d:\file`) == "d:"); + } +} + +auto driveName(R)(auto ref R path) +if (isConvertibleToString!R) +{ + return driveName!(StringTypeOf!R)(path); +} + +@safe unittest +{ + assert(testAliasedString!driveName(`d:\file`)); +} + +@safe unittest +{ + import std.array; + import std.utf : byChar; + + version (Posix) assert(driveName("c:/foo".byChar).empty); + version (Windows) + { + assert(driveName(`dir\file`.byChar).empty); + assert(driveName(`d:file`.byChar).array == "d:"); + assert(driveName(`d:\file`.byChar).array == "d:"); + assert(driveName("d:".byChar).array == "d:"); + assert(driveName(`\\server\share\file`.byChar).array == `\\server\share`); + assert(driveName(`\\server\share\`.byChar).array == `\\server\share`); + assert(driveName(`\\server\share`.byChar).array == `\\server\share`); + + static assert(driveName(`d:\file`).array == "d:"); + } +} + + +/** Strips the drive from a Windows path. On POSIX, the path is returned + unaltered. + + Params: + path = A pathname + + Returns: A slice of path without the drive component. +*/ +auto stripDrive(R)(R path) +if ((isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) || + isNarrowString!R) && + !isConvertibleToString!R) +{ + version (Windows) + { + if (hasDrive!(BaseOf!R)(path)) return path[2 .. path.length]; + else if (isUNC!(BaseOf!R)(path)) return path[uncRootLength!(BaseOf!R)(path) .. path.length]; + } + return path; +} + +/// +@safe unittest +{ + version (Windows) + { + assert(stripDrive(`d:\dir\file`) == `\dir\file`); + assert(stripDrive(`\\server\share\dir\file`) == `\dir\file`); + } +} + +auto stripDrive(R)(auto ref R path) +if (isConvertibleToString!R) +{ + return stripDrive!(StringTypeOf!R)(path); +} + +@safe unittest +{ + assert(testAliasedString!stripDrive(`d:\dir\file`)); +} + +@safe unittest +{ + version (Windows) + { + assert(stripDrive(`d:\dir\file`) == `\dir\file`); + assert(stripDrive(`\\server\share\dir\file`) == `\dir\file`); + static assert(stripDrive(`d:\dir\file`) == `\dir\file`); + + auto r = MockRange!(immutable(char))(`d:\dir\file`); + auto s = r.stripDrive(); + foreach (i, c; `\dir\file`) + assert(s[i] == c); + } + version (Posix) + { + assert(stripDrive(`d:\dir\file`) == `d:\dir\file`); + + auto r = MockRange!(immutable(char))(`d:\dir\file`); + auto s = r.stripDrive(); + foreach (i, c; `d:\dir\file`) + assert(s[i] == c); + } +} + + +/* Helper function that returns the position of the filename/extension + separator dot in path. + + Params: + path = file spec as string or indexable range + Returns: + index of extension separator (the dot), or -1 if not found +*/ +private ptrdiff_t extSeparatorPos(R)(const R path) +if (isRandomAccessRange!R && hasLength!R && isSomeChar!(ElementType!R) || + isNarrowString!R) +{ + for (auto i = path.length; i-- > 0 && !isSeparator(path[i]); ) + { + if (path[i] == '.' && i > 0 && !isSeparator(path[i-1])) + return i; + } + return -1; +} + +@safe unittest +{ + assert(extSeparatorPos("file") == -1); + assert(extSeparatorPos("file.ext"w) == 4); + assert(extSeparatorPos("file.ext1.ext2"d) == 9); + assert(extSeparatorPos(".foo".dup) == -1); + assert(extSeparatorPos(".foo.ext"w.dup) == 4); +} + +@safe unittest +{ + assert(extSeparatorPos("dir/file"d.dup) == -1); + assert(extSeparatorPos("dir/file.ext") == 8); + assert(extSeparatorPos("dir/file.ext1.ext2"w) == 13); + assert(extSeparatorPos("dir/.foo"d) == -1); + assert(extSeparatorPos("dir/.foo.ext".dup) == 8); + + version (Windows) + { + assert(extSeparatorPos("dir\\file") == -1); + assert(extSeparatorPos("dir\\file.ext") == 8); + assert(extSeparatorPos("dir\\file.ext1.ext2") == 13); + assert(extSeparatorPos("dir\\.foo") == -1); + assert(extSeparatorPos("dir\\.foo.ext") == 8); + + assert(extSeparatorPos("d:file") == -1); + assert(extSeparatorPos("d:file.ext") == 6); + assert(extSeparatorPos("d:file.ext1.ext2") == 11); + assert(extSeparatorPos("d:.foo") == -1); + assert(extSeparatorPos("d:.foo.ext") == 6); + } + + static assert(extSeparatorPos("file") == -1); + static assert(extSeparatorPos("file.ext"w) == 4); +} + + +/** + Params: path = A path name. + Returns: The _extension part of a file name, including the dot. + + If there is no _extension, $(D null) is returned. +*/ +auto extension(R)(R path) +if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) || + is(StringTypeOf!R)) +{ + auto i = extSeparatorPos!(BaseOf!R)(path); + if (i == -1) + { + static if (is(StringTypeOf!R)) + return StringTypeOf!R.init[]; // which is null + else + return path[0 .. 0]; + } + else return path[i .. path.length]; +} + +/// +@safe unittest +{ + import std.range : empty; + assert(extension("file").empty); + assert(extension("file.") == "."); + assert(extension("file.ext"w) == ".ext"); + assert(extension("file.ext1.ext2"d) == ".ext2"); + assert(extension(".foo".dup).empty); + assert(extension(".foo.ext"w.dup) == ".ext"); + + static assert(extension("file").empty); + static assert(extension("file.ext") == ".ext"); +} + +@safe unittest +{ + { + auto r = MockRange!(immutable(char))(`file.ext1.ext2`); + auto s = r.extension(); + foreach (i, c; `.ext2`) + assert(s[i] == c); + } + + static struct DirEntry { string s; alias s this; } + assert(extension(DirEntry("file")).empty); +} + + +/** Remove extension from path. + + Params: + path = string or range to be sliced + + Returns: + slice of path with the extension (if any) stripped off +*/ +auto stripExtension(R)(R path) +if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) || + isNarrowString!R) && + !isConvertibleToString!R) +{ + auto i = extSeparatorPos(path); + return (i == -1) ? path : path[0 .. i]; +} + +/// +@safe unittest +{ + assert(stripExtension("file") == "file"); + assert(stripExtension("file.ext") == "file"); + assert(stripExtension("file.ext1.ext2") == "file.ext1"); + assert(stripExtension("file.") == "file"); + assert(stripExtension(".file") == ".file"); + assert(stripExtension(".file.ext") == ".file"); + assert(stripExtension("dir/file.ext") == "dir/file"); +} + +auto stripExtension(R)(auto ref R path) +if (isConvertibleToString!R) +{ + return stripExtension!(StringTypeOf!R)(path); +} + +@safe unittest +{ + assert(testAliasedString!stripExtension("file")); +} + +@safe unittest +{ + assert(stripExtension("file.ext"w) == "file"); + assert(stripExtension("file.ext1.ext2"d) == "file.ext1"); + + import std.array; + import std.utf : byChar, byWchar, byDchar; + + assert(stripExtension("file".byChar).array == "file"); + assert(stripExtension("file.ext"w.byWchar).array == "file"); + assert(stripExtension("file.ext1.ext2"d.byDchar).array == "file.ext1"); +} + + +/** Sets or replaces an extension. + + If the filename already has an extension, it is replaced. If not, the + extension is simply appended to the filename. Including a leading dot + in $(D ext) is optional. + + If the extension is empty, this function is equivalent to + $(LREF stripExtension). + + This function normally allocates a new string (the possible exception + being the case when path is immutable and doesn't already have an + extension). + + Params: + path = A path name + ext = The new extension + + Returns: A string containing the _path given by $(D path), but where + the extension has been set to $(D ext). + + See_Also: + $(LREF withExtension) which does not allocate and returns a lazy range. +*/ +immutable(Unqual!C1)[] setExtension(C1, C2)(in C1[] path, in C2[] ext) +if (isSomeChar!C1 && !is(C1 == immutable) && is(Unqual!C1 == Unqual!C2)) +{ + try + { + import std.conv : to; + return withExtension(path, ext).to!(typeof(return)); + } + catch (Exception e) + { + assert(0); + } +} + +///ditto +immutable(C1)[] setExtension(C1, C2)(immutable(C1)[] path, const(C2)[] ext) +if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2)) +{ + if (ext.length == 0) + return stripExtension(path); + + try + { + import std.conv : to; + return withExtension(path, ext).to!(typeof(return)); + } + catch (Exception e) + { + assert(0); + } +} + +/// +@safe unittest +{ + assert(setExtension("file", "ext") == "file.ext"); + assert(setExtension("file"w, ".ext"w) == "file.ext"); + assert(setExtension("file."d, "ext"d) == "file.ext"); + assert(setExtension("file.", ".ext") == "file.ext"); + assert(setExtension("file.old"w, "new"w) == "file.new"); + assert(setExtension("file.old"d, ".new"d) == "file.new"); +} + +@safe unittest +{ + assert(setExtension("file"w.dup, "ext"w) == "file.ext"); + assert(setExtension("file"w.dup, ".ext"w) == "file.ext"); + assert(setExtension("file."w, "ext"w.dup) == "file.ext"); + assert(setExtension("file."w, ".ext"w.dup) == "file.ext"); + assert(setExtension("file.old"d.dup, "new"d) == "file.new"); + assert(setExtension("file.old"d.dup, ".new"d) == "file.new"); + + static assert(setExtension("file", "ext") == "file.ext"); + static assert(setExtension("file.old", "new") == "file.new"); + + static assert(setExtension("file"w.dup, "ext"w) == "file.ext"); + static assert(setExtension("file.old"d.dup, "new"d) == "file.new"); + + // Issue 10601 + assert(setExtension("file", "") == "file"); + assert(setExtension("file.ext", "") == "file"); +} + +/************ + * Replace existing extension on filespec with new one. + * + * Params: + * path = string or random access range representing a filespec + * ext = the new extension + * Returns: + * Range with $(D path)'s extension (if any) replaced with $(D ext). + * The element encoding type of the returned range will be the same as $(D path)'s. + * See_Also: + * $(LREF setExtension) + */ +auto withExtension(R, C)(R path, C[] ext) +if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) || + isNarrowString!R) && + !isConvertibleToString!R && + isSomeChar!C) +{ + import std.range : only, chain; + import std.utf : byUTF; + + alias CR = Unqual!(ElementEncodingType!R); + auto dot = only(CR('.')); + if (ext.length == 0 || ext[0] == '.') + dot.popFront(); // so dot is an empty range, too + return chain(stripExtension(path).byUTF!CR, dot, ext.byUTF!CR); +} + +/// +@safe unittest +{ + import std.array; + assert(withExtension("file", "ext").array == "file.ext"); + assert(withExtension("file"w, ".ext"w).array == "file.ext"); + assert(withExtension("file.ext"w, ".").array == "file."); + + import std.utf : byChar, byWchar; + assert(withExtension("file".byChar, "ext").array == "file.ext"); + assert(withExtension("file"w.byWchar, ".ext"w).array == "file.ext"w); + assert(withExtension("file.ext"w.byWchar, ".").array == "file."w); +} + +auto withExtension(R, C)(auto ref R path, C[] ext) +if (isConvertibleToString!R) +{ + return withExtension!(StringTypeOf!R)(path, ext); +} + +@safe unittest +{ + assert(testAliasedString!withExtension("file", "ext")); +} + +/** Params: + path = A path name. + ext = The default extension to use. + + Returns: The _path given by $(D path), with the extension given by $(D ext) + appended if the path doesn't already have one. + + Including the dot in the extension is optional. + + This function always allocates a new string, except in the case when + path is immutable and already has an extension. +*/ +immutable(Unqual!C1)[] defaultExtension(C1, C2)(in C1[] path, in C2[] ext) +if (isSomeChar!C1 && is(Unqual!C1 == Unqual!C2)) +{ + import std.conv : to; + return withDefaultExtension(path, ext).to!(typeof(return)); +} + +/// +@safe unittest +{ + assert(defaultExtension("file", "ext") == "file.ext"); + assert(defaultExtension("file", ".ext") == "file.ext"); + assert(defaultExtension("file.", "ext") == "file."); + assert(defaultExtension("file.old", "new") == "file.old"); + assert(defaultExtension("file.old", ".new") == "file.old"); +} + +@safe unittest +{ + assert(defaultExtension("file"w.dup, "ext"w) == "file.ext"); + assert(defaultExtension("file.old"d.dup, "new"d) == "file.old"); + + static assert(defaultExtension("file", "ext") == "file.ext"); + static assert(defaultExtension("file.old", "new") == "file.old"); + + static assert(defaultExtension("file"w.dup, "ext"w) == "file.ext"); + static assert(defaultExtension("file.old"d.dup, "new"d) == "file.old"); +} + + +/******************************** + * Set the extension of $(D path) to $(D ext) if $(D path) doesn't have one. + * + * Params: + * path = filespec as string or range + * ext = extension, may have leading '.' + * Returns: + * range with the result + */ +auto withDefaultExtension(R, C)(R path, C[] ext) +if ((isRandomAccessRange!R && hasSlicing!R && hasLength!R && isSomeChar!(ElementType!R) || + isNarrowString!R) && + !isConvertibleToString!R && + isSomeChar!C) +{ + import std.range : only, chain; + import std.utf : byUTF; + + alias CR = Unqual!(ElementEncodingType!R); + auto dot = only(CR('.')); + auto i = extSeparatorPos(path); + if (i == -1) + { + if (ext.length > 0 && ext[0] == '.') + ext = ext[1 .. $]; // remove any leading . from ext[] + } + else + { + // path already has an extension, so make these empty + ext = ext[0 .. 0]; + dot.popFront(); + } + return chain(path.byUTF!CR, dot, ext.byUTF!CR); +} + +/// +@safe unittest +{ + import std.array; + assert(withDefaultExtension("file", "ext").array == "file.ext"); + assert(withDefaultExtension("file"w, ".ext").array == "file.ext"w); + assert(withDefaultExtension("file.", "ext").array == "file."); + assert(withDefaultExtension("file", "").array == "file."); + + import std.utf : byChar, byWchar; + assert(withDefaultExtension("file".byChar, "ext").array == "file.ext"); + assert(withDefaultExtension("file"w.byWchar, ".ext").array == "file.ext"w); + assert(withDefaultExtension("file.".byChar, "ext"d).array == "file."); + assert(withDefaultExtension("file".byChar, "").array == "file."); +} + +auto withDefaultExtension(R, C)(auto ref R path, C[] ext) +if (isConvertibleToString!R) +{ + return withDefaultExtension!(StringTypeOf!R, C)(path, ext); +} + +@safe unittest +{ + assert(testAliasedString!withDefaultExtension("file", "ext")); +} + +/** Combines one or more path segments. + + This function takes a set of path segments, given as an input + range of string elements or as a set of string arguments, + and concatenates them with each other. Directory separators + are inserted between segments if necessary. If any of the + path segments are absolute (as defined by $(LREF isAbsolute)), the + preceding segments will be dropped. + + On Windows, if one of the path segments are rooted, but not absolute + (e.g. $(D `\foo`)), all preceding path segments down to the previous + root will be dropped. (See below for an example.) + + This function always allocates memory to hold the resulting path. + The variadic overload is guaranteed to only perform a single + allocation, as is the range version if $(D paths) is a forward + range. + + Params: + segments = An input range of segments to assemble the path from. + Returns: The assembled path. +*/ +immutable(ElementEncodingType!(ElementType!Range))[] + buildPath(Range)(Range segments) + if (isInputRange!Range && !isInfinite!Range && isSomeString!(ElementType!Range)) +{ + if (segments.empty) return null; + + // If this is a forward range, we can pre-calculate a maximum length. + static if (isForwardRange!Range) + { + auto segments2 = segments.save; + size_t precalc = 0; + foreach (segment; segments2) precalc += segment.length + 1; + } + // Otherwise, just venture a guess and resize later if necessary. + else size_t precalc = 255; + + auto buf = new Unqual!(ElementEncodingType!(ElementType!Range))[](precalc); + size_t pos = 0; + foreach (segment; segments) + { + if (segment.empty) continue; + static if (!isForwardRange!Range) + { + immutable neededLength = pos + segment.length + 1; + if (buf.length < neededLength) + buf.length = reserve(buf, neededLength + buf.length/2); + } + auto r = chainPath(buf[0 .. pos], segment); + size_t i; + foreach (c; r) + { + buf[i] = c; + ++i; + } + pos = i; + } + static U trustedCast(U, V)(V v) @trusted pure nothrow { return cast(U) v; } + return trustedCast!(typeof(return))(buf[0 .. pos]); +} + +/// ditto +immutable(C)[] buildPath(C)(const(C)[][] paths...) + @safe pure nothrow +if (isSomeChar!C) +{ + return buildPath!(typeof(paths))(paths); +} + +/// +@safe unittest +{ + version (Posix) + { + assert(buildPath("foo", "bar", "baz") == "foo/bar/baz"); + assert(buildPath("/foo/", "bar/baz") == "/foo/bar/baz"); + assert(buildPath("/foo", "/bar") == "/bar"); + } + + version (Windows) + { + assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`); + assert(buildPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`); + assert(buildPath("foo", `d:\bar`) == `d:\bar`); + assert(buildPath("foo", `\bar`) == `\bar`); + assert(buildPath(`c:\foo`, `\bar`) == `c:\bar`); + } +} + +@system unittest // non-documented +{ + import std.range; + // ir() wraps an array in a plain (i.e. non-forward) input range, so that + // we can test both code paths + InputRange!(C[]) ir(C)(C[][] p...) { return inputRangeObject(p); } + version (Posix) + { + assert(buildPath("foo") == "foo"); + assert(buildPath("/foo/") == "/foo/"); + assert(buildPath("foo", "bar") == "foo/bar"); + assert(buildPath("foo", "bar", "baz") == "foo/bar/baz"); + assert(buildPath("foo/".dup, "bar") == "foo/bar"); + assert(buildPath("foo///", "bar".dup) == "foo///bar"); + assert(buildPath("/foo"w, "bar"w) == "/foo/bar"); + assert(buildPath("foo"w.dup, "/bar"w) == "/bar"); + assert(buildPath("foo"w, "bar/"w.dup) == "foo/bar/"); + assert(buildPath("/"d, "foo"d) == "/foo"); + assert(buildPath(""d.dup, "foo"d) == "foo"); + assert(buildPath("foo"d, ""d.dup) == "foo"); + assert(buildPath("foo", "bar".dup, "baz") == "foo/bar/baz"); + assert(buildPath("foo"w, "/bar"w, "baz"w.dup) == "/bar/baz"); + + static assert(buildPath("foo", "bar", "baz") == "foo/bar/baz"); + static assert(buildPath("foo", "/bar", "baz") == "/bar/baz"); + + // The following are mostly duplicates of the above, except that the + // range version does not accept mixed constness. + assert(buildPath(ir("foo")) == "foo"); + assert(buildPath(ir("/foo/")) == "/foo/"); + assert(buildPath(ir("foo", "bar")) == "foo/bar"); + assert(buildPath(ir("foo", "bar", "baz")) == "foo/bar/baz"); + assert(buildPath(ir("foo/".dup, "bar".dup)) == "foo/bar"); + assert(buildPath(ir("foo///".dup, "bar".dup)) == "foo///bar"); + assert(buildPath(ir("/foo"w, "bar"w)) == "/foo/bar"); + assert(buildPath(ir("foo"w.dup, "/bar"w.dup)) == "/bar"); + assert(buildPath(ir("foo"w.dup, "bar/"w.dup)) == "foo/bar/"); + assert(buildPath(ir("/"d, "foo"d)) == "/foo"); + assert(buildPath(ir(""d.dup, "foo"d.dup)) == "foo"); + assert(buildPath(ir("foo"d, ""d)) == "foo"); + assert(buildPath(ir("foo", "bar", "baz")) == "foo/bar/baz"); + assert(buildPath(ir("foo"w.dup, "/bar"w.dup, "baz"w.dup)) == "/bar/baz"); + } + version (Windows) + { + assert(buildPath("foo") == "foo"); + assert(buildPath(`\foo/`) == `\foo/`); + assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`); + assert(buildPath("foo", `\bar`) == `\bar`); + assert(buildPath(`c:\foo`, "bar") == `c:\foo\bar`); + assert(buildPath("foo"w, `d:\bar`w.dup) == `d:\bar`); + assert(buildPath(`c:\foo\bar`, `\baz`) == `c:\baz`); + assert(buildPath(`\\foo\bar\baz`d, `foo`d, `\bar`d) == `\\foo\bar\bar`d); + + static assert(buildPath("foo", "bar", "baz") == `foo\bar\baz`); + static assert(buildPath("foo", `c:\bar`, "baz") == `c:\bar\baz`); + + assert(buildPath(ir("foo")) == "foo"); + assert(buildPath(ir(`\foo/`)) == `\foo/`); + assert(buildPath(ir("foo", "bar", "baz")) == `foo\bar\baz`); + assert(buildPath(ir("foo", `\bar`)) == `\bar`); + assert(buildPath(ir(`c:\foo`, "bar")) == `c:\foo\bar`); + assert(buildPath(ir("foo"w.dup, `d:\bar`w.dup)) == `d:\bar`); + assert(buildPath(ir(`c:\foo\bar`, `\baz`)) == `c:\baz`); + assert(buildPath(ir(`\\foo\bar\baz`d, `foo`d, `\bar`d)) == `\\foo\bar\bar`d); + } + + // Test that allocation works as it should. + auto manyShort = "aaa".repeat(1000).array(); + auto manyShortCombined = join(manyShort, dirSeparator); + assert(buildPath(manyShort) == manyShortCombined); + assert(buildPath(ir(manyShort)) == manyShortCombined); + + auto fewLong = 'b'.repeat(500).array().repeat(10).array(); + auto fewLongCombined = join(fewLong, dirSeparator); + assert(buildPath(fewLong) == fewLongCombined); + assert(buildPath(ir(fewLong)) == fewLongCombined); +} + +@safe unittest +{ + // Test for issue 7397 + string[] ary = ["a", "b"]; + version (Posix) + { + assert(buildPath(ary) == "a/b"); + } + else version (Windows) + { + assert(buildPath(ary) == `a\b`); + } +} + + +/** + * Concatenate path segments together to form one path. + * + * Params: + * r1 = first segment + * r2 = second segment + * ranges = 0 or more segments + * Returns: + * Lazy range which is the concatenation of r1, r2 and ranges with path separators. + * The resulting element type is that of r1. + * See_Also: + * $(LREF buildPath) + */ +auto chainPath(R1, R2, Ranges...)(R1 r1, R2 r2, Ranges ranges) +if ((isRandomAccessRange!R1 && hasSlicing!R1 && hasLength!R1 && isSomeChar!(ElementType!R1) || + isNarrowString!R1 && + !isConvertibleToString!R1) && + (isRandomAccessRange!R2 && hasSlicing!R2 && hasLength!R2 && isSomeChar!(ElementType!R2) || + isNarrowString!R2 && + !isConvertibleToString!R2) && + (Ranges.length == 0 || is(typeof(chainPath(r2, ranges)))) + ) +{ + static if (Ranges.length) + { + return chainPath(chainPath(r1, r2), ranges); + } + else + { + import std.range : only, chain; + import std.utf : byUTF; + + alias CR = Unqual!(ElementEncodingType!R1); + auto sep = only(CR(dirSeparator[0])); + bool usesep = false; + + auto pos = r1.length; + + if (pos) + { + if (isRooted(r2)) + { + version (Posix) + { + pos = 0; + } + else version (Windows) + { + if (isAbsolute(r2)) + pos = 0; + else + { + pos = rootName(r1).length; + if (pos > 0 && isDirSeparator(r1[pos - 1])) + --pos; + } + } + else + static assert(0); + } + else if (!isDirSeparator(r1[pos - 1])) + usesep = true; + } + if (!usesep) + sep.popFront(); + // Return r1 ~ '/' ~ r2 + return chain(r1[0 .. pos].byUTF!CR, sep, r2.byUTF!CR); + } +} + +/// +@safe unittest +{ + import std.array; + version (Posix) + { + assert(chainPath("foo", "bar", "baz").array == "foo/bar/baz"); + assert(chainPath("/foo/", "bar/baz").array == "/foo/bar/baz"); + assert(chainPath("/foo", "/bar").array == "/bar"); + } + + version (Windows) + { + assert(chainPath("foo", "bar", "baz").array == `foo\bar\baz`); + assert(chainPath(`c:\foo`, `bar\baz`).array == `c:\foo\bar\baz`); + assert(chainPath("foo", `d:\bar`).array == `d:\bar`); + assert(chainPath("foo", `\bar`).array == `\bar`); + assert(chainPath(`c:\foo`, `\bar`).array == `c:\bar`); + } + + import std.utf : byChar; + version (Posix) + { + assert(chainPath("foo", "bar", "baz").array == "foo/bar/baz"); + assert(chainPath("/foo/".byChar, "bar/baz").array == "/foo/bar/baz"); + assert(chainPath("/foo", "/bar".byChar).array == "/bar"); + } + + version (Windows) + { + assert(chainPath("foo", "bar", "baz").array == `foo\bar\baz`); + assert(chainPath(`c:\foo`.byChar, `bar\baz`).array == `c:\foo\bar\baz`); + assert(chainPath("foo", `d:\bar`).array == `d:\bar`); + assert(chainPath("foo", `\bar`.byChar).array == `\bar`); + assert(chainPath(`c:\foo`, `\bar`w).array == `c:\bar`); + } +} + +auto chainPath(Ranges...)(auto ref Ranges ranges) +if (Ranges.length >= 2 && + std.meta.anySatisfy!(isConvertibleToString, Ranges)) +{ + import std.meta : staticMap; + alias Types = staticMap!(convertToString, Ranges); + return chainPath!Types(ranges); +} + +@safe unittest +{ + assert(chainPath(TestAliasedString(null), TestAliasedString(null), TestAliasedString(null)).empty); + assert(chainPath(TestAliasedString(null), TestAliasedString(null), "").empty); + assert(chainPath(TestAliasedString(null), "", TestAliasedString(null)).empty); + static struct S { string s; } + static assert(!__traits(compiles, chainPath(TestAliasedString(null), S(""), TestAliasedString(null)))); +} + +/** Performs the same task as $(LREF buildPath), + while at the same time resolving current/parent directory + symbols ($(D ".") and $(D "..")) and removing superfluous + directory separators. + It will return "." if the path leads to the starting directory. + On Windows, slashes are replaced with backslashes. + + Using buildNormalizedPath on null paths will always return null. + + Note that this function does not resolve symbolic links. + + This function always allocates memory to hold the resulting path. + Use $(LREF asNormalizedPath) to not allocate memory. + + Params: + paths = An array of paths to assemble. + + Returns: The assembled path. +*/ +immutable(C)[] buildNormalizedPath(C)(const(C[])[] paths...) + @trusted pure nothrow +if (isSomeChar!C) +{ + import std.array : array; + + const(C)[] result; + foreach (path; paths) + { + if (result) + result = chainPath(result, path).array; + else + result = path; + } + result = asNormalizedPath(result).array; + return cast(typeof(return)) result; +} + +/// +@safe unittest +{ + assert(buildNormalizedPath("foo", "..") == "."); + + version (Posix) + { + assert(buildNormalizedPath("/foo/./bar/..//baz/") == "/foo/baz"); + assert(buildNormalizedPath("../foo/.") == "../foo"); + assert(buildNormalizedPath("/foo", "bar/baz/") == "/foo/bar/baz"); + assert(buildNormalizedPath("/foo", "/bar/..", "baz") == "/baz"); + assert(buildNormalizedPath("foo/./bar", "../../", "../baz") == "../baz"); + assert(buildNormalizedPath("/foo/./bar", "../../baz") == "/baz"); + } + + version (Windows) + { + assert(buildNormalizedPath(`c:\foo\.\bar/..\\baz\`) == `c:\foo\baz`); + assert(buildNormalizedPath(`..\foo\.`) == `..\foo`); + assert(buildNormalizedPath(`c:\foo`, `bar\baz\`) == `c:\foo\bar\baz`); + assert(buildNormalizedPath(`c:\foo`, `bar/..`) == `c:\foo`); + assert(buildNormalizedPath(`\\server\share\foo`, `..\bar`) == + `\\server\share\bar`); + } +} + +@safe unittest +{ + assert(buildNormalizedPath(".", ".") == "."); + assert(buildNormalizedPath("foo", "..") == "."); + assert(buildNormalizedPath("", "") is null); + assert(buildNormalizedPath("", ".") == "."); + assert(buildNormalizedPath(".", "") == "."); + assert(buildNormalizedPath(null, "foo") == "foo"); + assert(buildNormalizedPath("", "foo") == "foo"); + assert(buildNormalizedPath("", "") == ""); + assert(buildNormalizedPath("", null) == ""); + assert(buildNormalizedPath(null, "") == ""); + assert(buildNormalizedPath!(char)(null, null) == ""); + + version (Posix) + { + assert(buildNormalizedPath("/", "foo", "bar") == "/foo/bar"); + assert(buildNormalizedPath("foo", "bar", "baz") == "foo/bar/baz"); + assert(buildNormalizedPath("foo", "bar/baz") == "foo/bar/baz"); + assert(buildNormalizedPath("foo", "bar//baz///") == "foo/bar/baz"); + assert(buildNormalizedPath("/foo", "bar/baz") == "/foo/bar/baz"); + assert(buildNormalizedPath("/foo", "/bar/baz") == "/bar/baz"); + assert(buildNormalizedPath("/foo/..", "/bar/./baz") == "/bar/baz"); + assert(buildNormalizedPath("/foo/..", "bar/baz") == "/bar/baz"); + assert(buildNormalizedPath("/foo/../../", "bar/baz") == "/bar/baz"); + assert(buildNormalizedPath("/foo/bar", "../baz") == "/foo/baz"); + assert(buildNormalizedPath("/foo/bar", "../../baz") == "/baz"); + assert(buildNormalizedPath("/foo/bar", ".././/baz/..", "wee/") == "/foo/wee"); + assert(buildNormalizedPath("//foo/bar", "baz///wee") == "/foo/bar/baz/wee"); + static assert(buildNormalizedPath("/foo/..", "/bar/./baz") == "/bar/baz"); + } + else version (Windows) + { + assert(buildNormalizedPath(`\`, `foo`, `bar`) == `\foo\bar`); + assert(buildNormalizedPath(`foo`, `bar`, `baz`) == `foo\bar\baz`); + assert(buildNormalizedPath(`foo`, `bar\baz`) == `foo\bar\baz`); + assert(buildNormalizedPath(`foo`, `bar\\baz\\\`) == `foo\bar\baz`); + assert(buildNormalizedPath(`\foo`, `bar\baz`) == `\foo\bar\baz`); + assert(buildNormalizedPath(`\foo`, `\bar\baz`) == `\bar\baz`); + assert(buildNormalizedPath(`\foo\..`, `\bar\.\baz`) == `\bar\baz`); + assert(buildNormalizedPath(`\foo\..`, `bar\baz`) == `\bar\baz`); + assert(buildNormalizedPath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`); + assert(buildNormalizedPath(`\foo\bar`, `..\baz`) == `\foo\baz`); + assert(buildNormalizedPath(`\foo\bar`, `../../baz`) == `\baz`); + assert(buildNormalizedPath(`\foo\bar`, `..\.\/baz\..`, `wee\`) == `\foo\wee`); + + assert(buildNormalizedPath(`c:\`, `foo`, `bar`) == `c:\foo\bar`); + assert(buildNormalizedPath(`c:foo`, `bar`, `baz`) == `c:foo\bar\baz`); + assert(buildNormalizedPath(`c:foo`, `bar\baz`) == `c:foo\bar\baz`); + assert(buildNormalizedPath(`c:foo`, `bar\\baz\\\`) == `c:foo\bar\baz`); + assert(buildNormalizedPath(`c:\foo`, `bar\baz`) == `c:\foo\bar\baz`); + assert(buildNormalizedPath(`c:\foo`, `\bar\baz`) == `c:\bar\baz`); + assert(buildNormalizedPath(`c:\foo\..`, `\bar\.\baz`) == `c:\bar\baz`); + assert(buildNormalizedPath(`c:\foo\..`, `bar\baz`) == `c:\bar\baz`); + assert(buildNormalizedPath(`c:\foo\..\..\`, `bar\baz`) == `c:\bar\baz`); + assert(buildNormalizedPath(`c:\foo\bar`, `..\baz`) == `c:\foo\baz`); + assert(buildNormalizedPath(`c:\foo\bar`, `..\..\baz`) == `c:\baz`); + assert(buildNormalizedPath(`c:\foo\bar`, `..\.\\baz\..`, `wee\`) == `c:\foo\wee`); + + assert(buildNormalizedPath(`\\server\share`, `foo`, `bar`) == `\\server\share\foo\bar`); + assert(buildNormalizedPath(`\\server\share\`, `foo`, `bar`) == `\\server\share\foo\bar`); + assert(buildNormalizedPath(`\\server\share\foo`, `bar\baz`) == `\\server\share\foo\bar\baz`); + assert(buildNormalizedPath(`\\server\share\foo`, `\bar\baz`) == `\\server\share\bar\baz`); + assert(buildNormalizedPath(`\\server\share\foo\..`, `\bar\.\baz`) == `\\server\share\bar\baz`); + assert(buildNormalizedPath(`\\server\share\foo\..`, `bar\baz`) == `\\server\share\bar\baz`); + assert(buildNormalizedPath(`\\server\share\foo\..\..\`, `bar\baz`) == `\\server\share\bar\baz`); + assert(buildNormalizedPath(`\\server\share\foo\bar`, `..\baz`) == `\\server\share\foo\baz`); + assert(buildNormalizedPath(`\\server\share\foo\bar`, `..\..\baz`) == `\\server\share\baz`); + assert(buildNormalizedPath(`\\server\share\foo\bar`, `..\.\\baz\..`, `wee\`) == `\\server\share\foo\wee`); + + static assert(buildNormalizedPath(`\foo\..\..\`, `bar\baz`) == `\bar\baz`); + } + else static assert(0); +} + +@safe unittest +{ + // Test for issue 7397 + string[] ary = ["a", "b"]; + version (Posix) + { + assert(buildNormalizedPath(ary) == "a/b"); + } + else version (Windows) + { + assert(buildNormalizedPath(ary) == `a\b`); + } +} + + +/** Normalize a path by resolving current/parent directory + symbols ($(D ".") and $(D "..")) and removing superfluous + directory separators. + It will return "." if the path leads to the starting directory. + On Windows, slashes are replaced with backslashes. + + Using asNormalizedPath on empty paths will always return an empty path. + + Does not resolve symbolic links. + + This function always allocates memory to hold the resulting path. + Use $(LREF buildNormalizedPath) to allocate memory and return a string. + + Params: + path = string or random access range representing the _path to normalize + + Returns: + normalized path as a forward range +*/ + +auto asNormalizedPath(R)(R path) +if (isSomeChar!(ElementEncodingType!R) && + (isRandomAccessRange!R && hasSlicing!R && hasLength!R || isNarrowString!R) && + !isConvertibleToString!R) +{ + alias C = Unqual!(ElementEncodingType!R); + alias S = typeof(path[0 .. 0]); + + static struct Result + { + @property bool empty() + { + return c == c.init; + } + + @property C front() + { + return c; + } + + void popFront() + { + C lastc = c; + c = c.init; + if (!element.empty) + { + c = getElement0(); + return; + } + L1: + while (1) + { + if (elements.empty) + { + element = element[0 .. 0]; + return; + } + element = elements.front; + elements.popFront(); + if (isDot(element) || (rooted && isDotDot(element))) + continue; + + if (rooted || !isDotDot(element)) + { + int n = 1; + auto elements2 = elements.save; + while (!elements2.empty) + { + auto e = elements2.front; + elements2.popFront(); + if (isDot(e)) + continue; + if (isDotDot(e)) + { + --n; + if (n == 0) + { + elements = elements2; + element = element[0 .. 0]; + continue L1; + } + } + else + ++n; + } + } + break; + } + + static assert(dirSeparator.length == 1); + if (lastc == dirSeparator[0] || lastc == lastc.init) + c = getElement0(); + else + c = dirSeparator[0]; + } + + static if (isForwardRange!R) + { + @property auto save() + { + auto result = this; + result.element = element.save; + result.elements = elements.save; + return result; + } + } + + private: + this(R path) + { + element = rootName(path); + auto i = element.length; + while (i < path.length && isDirSeparator(path[i])) + ++i; + rooted = i > 0; + elements = pathSplitter(path[i .. $]); + popFront(); + if (c == c.init && path.length) + c = C('.'); + } + + C getElement0() + { + static if (isNarrowString!S) // avoid autodecode + { + C c = element[0]; + element = element[1 .. $]; + } + else + { + C c = element.front; + element.popFront(); + } + version (Windows) + { + if (c == '/') // can appear in root element + c = '\\'; // use native Windows directory separator + } + return c; + } + + // See if elem is "." + static bool isDot(S elem) + { + return elem.length == 1 && elem[0] == '.'; + } + + // See if elem is ".." + static bool isDotDot(S elem) + { + return elem.length == 2 && elem[0] == '.' && elem[1] == '.'; + } + + bool rooted; // the path starts with a root directory + C c; + S element; + typeof(pathSplitter(path[0 .. 0])) elements; + } + + return Result(path); +} + +/// +@safe unittest +{ + import std.array; + assert(asNormalizedPath("foo/..").array == "."); + + version (Posix) + { + assert(asNormalizedPath("/foo/./bar/..//baz/").array == "/foo/baz"); + assert(asNormalizedPath("../foo/.").array == "../foo"); + assert(asNormalizedPath("/foo/bar/baz/").array == "/foo/bar/baz"); + assert(asNormalizedPath("/foo/./bar/../../baz").array == "/baz"); + } + + version (Windows) + { + assert(asNormalizedPath(`c:\foo\.\bar/..\\baz\`).array == `c:\foo\baz`); + assert(asNormalizedPath(`..\foo\.`).array == `..\foo`); + assert(asNormalizedPath(`c:\foo\bar\baz\`).array == `c:\foo\bar\baz`); + assert(asNormalizedPath(`c:\foo\bar/..`).array == `c:\foo`); + assert(asNormalizedPath(`\\server\share\foo\..\bar`).array == + `\\server\share\bar`); + } +} + +auto asNormalizedPath(R)(auto ref R path) +if (isConvertibleToString!R) +{ + return asNormalizedPath!(StringTypeOf!R)(path); +} + +@safe unittest +{ + assert(testAliasedString!asNormalizedPath(null)); +} + +@safe unittest +{ + import std.array; + import std.utf : byChar; + + assert(asNormalizedPath("").array is null); + assert(asNormalizedPath("foo").array == "foo"); + assert(asNormalizedPath(".").array == "."); + assert(asNormalizedPath("./.").array == "."); + assert(asNormalizedPath("foo/..").array == "."); + + auto save = asNormalizedPath("fob").save; + save.popFront(); + assert(save.front == 'o'); + + version (Posix) + { + assert(asNormalizedPath("/foo/bar").array == "/foo/bar"); + assert(asNormalizedPath("foo/bar/baz").array == "foo/bar/baz"); + assert(asNormalizedPath("foo/bar/baz").array == "foo/bar/baz"); + assert(asNormalizedPath("foo/bar//baz///").array == "foo/bar/baz"); + assert(asNormalizedPath("/foo/bar/baz").array == "/foo/bar/baz"); + assert(asNormalizedPath("/foo/../bar/baz").array == "/bar/baz"); + assert(asNormalizedPath("/foo/../..//bar/baz").array == "/bar/baz"); + assert(asNormalizedPath("/foo/bar/../baz").array == "/foo/baz"); + assert(asNormalizedPath("/foo/bar/../../baz").array == "/baz"); + assert(asNormalizedPath("/foo/bar/.././/baz/../wee/").array == "/foo/wee"); + assert(asNormalizedPath("//foo/bar/baz///wee").array == "/foo/bar/baz/wee"); + + assert(asNormalizedPath("foo//bar").array == "foo/bar"); + assert(asNormalizedPath("foo/bar").array == "foo/bar"); + + //Curent dir path + assert(asNormalizedPath("./").array == "."); + assert(asNormalizedPath("././").array == "."); + assert(asNormalizedPath("./foo/..").array == "."); + assert(asNormalizedPath("foo/..").array == "."); + } + else version (Windows) + { + assert(asNormalizedPath(`\foo\bar`).array == `\foo\bar`); + assert(asNormalizedPath(`foo\bar\baz`).array == `foo\bar\baz`); + assert(asNormalizedPath(`foo\bar\baz`).array == `foo\bar\baz`); + assert(asNormalizedPath(`foo\bar\\baz\\\`).array == `foo\bar\baz`); + assert(asNormalizedPath(`\foo\bar\baz`).array == `\foo\bar\baz`); + assert(asNormalizedPath(`\foo\..\\bar\.\baz`).array == `\bar\baz`); + assert(asNormalizedPath(`\foo\..\bar\baz`).array == `\bar\baz`); + assert(asNormalizedPath(`\foo\..\..\\bar\baz`).array == `\bar\baz`); + + assert(asNormalizedPath(`\foo\bar\..\baz`).array == `\foo\baz`); + assert(asNormalizedPath(`\foo\bar\../../baz`).array == `\baz`); + assert(asNormalizedPath(`\foo\bar\..\.\/baz\..\wee\`).array == `\foo\wee`); + + assert(asNormalizedPath(`c:\foo\bar`).array == `c:\foo\bar`); + assert(asNormalizedPath(`c:foo\bar\baz`).array == `c:foo\bar\baz`); + assert(asNormalizedPath(`c:foo\bar\baz`).array == `c:foo\bar\baz`); + assert(asNormalizedPath(`c:foo\bar\\baz\\\`).array == `c:foo\bar\baz`); + assert(asNormalizedPath(`c:\foo\bar\baz`).array == `c:\foo\bar\baz`); + + assert(asNormalizedPath(`c:\foo\..\\bar\.\baz`).array == `c:\bar\baz`); + assert(asNormalizedPath(`c:\foo\..\bar\baz`).array == `c:\bar\baz`); + assert(asNormalizedPath(`c:\foo\..\..\\bar\baz`).array == `c:\bar\baz`); + assert(asNormalizedPath(`c:\foo\bar\..\baz`).array == `c:\foo\baz`); + assert(asNormalizedPath(`c:\foo\bar\..\..\baz`).array == `c:\baz`); + assert(asNormalizedPath(`c:\foo\bar\..\.\\baz\..\wee\`).array == `c:\foo\wee`); + assert(asNormalizedPath(`\\server\share\foo\bar`).array == `\\server\share\foo\bar`); + assert(asNormalizedPath(`\\server\share\\foo\bar`).array == `\\server\share\foo\bar`); + assert(asNormalizedPath(`\\server\share\foo\bar\baz`).array == `\\server\share\foo\bar\baz`); + assert(asNormalizedPath(`\\server\share\foo\..\\bar\.\baz`).array == `\\server\share\bar\baz`); + assert(asNormalizedPath(`\\server\share\foo\..\bar\baz`).array == `\\server\share\bar\baz`); + assert(asNormalizedPath(`\\server\share\foo\..\..\\bar\baz`).array == `\\server\share\bar\baz`); + assert(asNormalizedPath(`\\server\share\foo\bar\..\baz`).array == `\\server\share\foo\baz`); + assert(asNormalizedPath(`\\server\share\foo\bar\..\..\baz`).array == `\\server\share\baz`); + assert(asNormalizedPath(`\\server\share\foo\bar\..\.\\baz\..\wee\`).array == `\\server\share\foo\wee`); + + static assert(asNormalizedPath(`\foo\..\..\\bar\baz`).array == `\bar\baz`); + + assert(asNormalizedPath("foo//bar").array == `foo\bar`); + + //Curent dir path + assert(asNormalizedPath(`.\`).array == "."); + assert(asNormalizedPath(`.\.\`).array == "."); + assert(asNormalizedPath(`.\foo\..`).array == "."); + assert(asNormalizedPath(`foo\..`).array == "."); + } + else static assert(0); +} + +@safe unittest +{ + import std.array; + + version (Posix) + { + // Trivial + assert(asNormalizedPath("").empty); + assert(asNormalizedPath("foo/bar").array == "foo/bar"); + + // Correct handling of leading slashes + assert(asNormalizedPath("/").array == "/"); + assert(asNormalizedPath("///").array == "/"); + assert(asNormalizedPath("////").array == "/"); + assert(asNormalizedPath("/foo/bar").array == "/foo/bar"); + assert(asNormalizedPath("//foo/bar").array == "/foo/bar"); + assert(asNormalizedPath("///foo/bar").array == "/foo/bar"); + assert(asNormalizedPath("////foo/bar").array == "/foo/bar"); + + // Correct handling of single-dot symbol (current directory) + assert(asNormalizedPath("/./foo").array == "/foo"); + assert(asNormalizedPath("/foo/./bar").array == "/foo/bar"); + + assert(asNormalizedPath("./foo").array == "foo"); + assert(asNormalizedPath("././foo").array == "foo"); + assert(asNormalizedPath("foo/././bar").array == "foo/bar"); + + // Correct handling of double-dot symbol (previous directory) + assert(asNormalizedPath("/foo/../bar").array == "/bar"); + assert(asNormalizedPath("/foo/../../bar").array == "/bar"); + assert(asNormalizedPath("/../foo").array == "/foo"); + assert(asNormalizedPath("/../../foo").array == "/foo"); + assert(asNormalizedPath("/foo/..").array == "/"); + assert(asNormalizedPath("/foo/../..").array == "/"); + + assert(asNormalizedPath("foo/../bar").array == "bar"); + assert(asNormalizedPath("foo/../../bar").array == "../bar"); + assert(asNormalizedPath("../foo").array == "../foo"); + assert(asNormalizedPath("../../foo").array == "../../foo"); + assert(asNormalizedPath("../foo/../bar").array == "../bar"); + assert(asNormalizedPath(".././../foo").array == "../../foo"); + assert(asNormalizedPath("foo/bar/..").array == "foo"); + assert(asNormalizedPath("/foo/../..").array == "/"); + + // The ultimate path + assert(asNormalizedPath("/foo/../bar//./../...///baz//").array == "/.../baz"); + static assert(asNormalizedPath("/foo/../bar//./../...///baz//").array == "/.../baz"); + } + else version (Windows) + { + // Trivial + assert(asNormalizedPath("").empty); + assert(asNormalizedPath(`foo\bar`).array == `foo\bar`); + assert(asNormalizedPath("foo/bar").array == `foo\bar`); + + // Correct handling of absolute paths + assert(asNormalizedPath("/").array == `\`); + assert(asNormalizedPath(`\`).array == `\`); + assert(asNormalizedPath(`\\\`).array == `\`); + assert(asNormalizedPath(`\\\\`).array == `\`); + assert(asNormalizedPath(`\foo\bar`).array == `\foo\bar`); + assert(asNormalizedPath(`\\foo`).array == `\\foo`); + assert(asNormalizedPath(`\\foo\\`).array == `\\foo`); + assert(asNormalizedPath(`\\foo/bar`).array == `\\foo\bar`); + assert(asNormalizedPath(`\\\foo\bar`).array == `\foo\bar`); + assert(asNormalizedPath(`\\\\foo\bar`).array == `\foo\bar`); + assert(asNormalizedPath(`c:\`).array == `c:\`); + assert(asNormalizedPath(`c:\foo\bar`).array == `c:\foo\bar`); + assert(asNormalizedPath(`c:\\foo\bar`).array == `c:\foo\bar`); + + // Correct handling of single-dot symbol (current directory) + assert(asNormalizedPath(`\./foo`).array == `\foo`); + assert(asNormalizedPath(`\foo/.\bar`).array == `\foo\bar`); + + assert(asNormalizedPath(`.\foo`).array == `foo`); + assert(asNormalizedPath(`./.\foo`).array == `foo`); + assert(asNormalizedPath(`foo\.\./bar`).array == `foo\bar`); + + // Correct handling of double-dot symbol (previous directory) + assert(asNormalizedPath(`\foo\..\bar`).array == `\bar`); + assert(asNormalizedPath(`\foo\../..\bar`).array == `\bar`); + assert(asNormalizedPath(`\..\foo`).array == `\foo`); + assert(asNormalizedPath(`\..\..\foo`).array == `\foo`); + assert(asNormalizedPath(`\foo\..`).array == `\`); + assert(asNormalizedPath(`\foo\../..`).array == `\`); + + assert(asNormalizedPath(`foo\..\bar`).array == `bar`); + assert(asNormalizedPath(`foo\..\../bar`).array == `..\bar`); + + assert(asNormalizedPath(`..\foo`).array == `..\foo`); + assert(asNormalizedPath(`..\..\foo`).array == `..\..\foo`); + assert(asNormalizedPath(`..\foo\..\bar`).array == `..\bar`); + assert(asNormalizedPath(`..\.\..\foo`).array == `..\..\foo`); + assert(asNormalizedPath(`foo\bar\..`).array == `foo`); + assert(asNormalizedPath(`\foo\..\..`).array == `\`); + assert(asNormalizedPath(`c:\foo\..\..`).array == `c:\`); + + // Correct handling of non-root path with drive specifier + assert(asNormalizedPath(`c:foo`).array == `c:foo`); + assert(asNormalizedPath(`c:..\foo\.\..\bar`).array == `c:..\bar`); + + // The ultimate path + assert(asNormalizedPath(`c:\foo\..\bar\\.\..\...\\\baz\\`).array == `c:\...\baz`); + static assert(asNormalizedPath(`c:\foo\..\bar\\.\..\...\\\baz\\`).array == `c:\...\baz`); + } + else static assert(false); +} + +/** Slice up a path into its elements. + + Params: + path = string or slicable random access range + + Returns: + bidirectional range of slices of `path` +*/ +auto pathSplitter(R)(R path) +if ((isRandomAccessRange!R && hasSlicing!R || + isNarrowString!R) && + !isConvertibleToString!R) +{ + static struct PathSplitter + { + @property bool empty() const { return pe == 0; } + + @property R front() + { + assert(!empty); + return _path[fs .. fe]; + } + + void popFront() + { + assert(!empty); + if (ps == pe) + { + if (fs == bs && fe == be) + { + pe = 0; + } + else + { + fs = bs; + fe = be; + } + } + else + { + fs = ps; + fe = fs; + while (fe < pe && !isDirSeparator(_path[fe])) + ++fe; + ps = ltrim(fe, pe); + } + } + + @property R back() + { + assert(!empty); + return _path[bs .. be]; + } + + void popBack() + { + assert(!empty); + if (ps == pe) + { + if (fs == bs && fe == be) + { + pe = 0; + } + else + { + bs = fs; + be = fe; + } + } + else + { + bs = pe; + be = bs; + while (bs > ps && !isDirSeparator(_path[bs - 1])) + --bs; + pe = rtrim(ps, bs); + } + } + @property auto save() { return this; } + + + private: + R _path; + size_t ps, pe; + size_t fs, fe; + size_t bs, be; + + this(R p) + { + if (p.empty) + { + pe = 0; + return; + } + _path = p; + + ps = 0; + pe = _path.length; + + // If path is rooted, first element is special + version (Windows) + { + if (isUNC(_path)) + { + auto i = uncRootLength(_path); + fs = 0; + fe = i; + ps = ltrim(fe, pe); + } + else if (isDriveRoot(_path)) + { + fs = 0; + fe = 3; + ps = ltrim(fe, pe); + } + else if (_path.length >= 1 && isDirSeparator(_path[0])) + { + fs = 0; + fe = 1; + ps = ltrim(fe, pe); + } + else + { + assert(!isRooted(_path)); + popFront(); + } + } + else version (Posix) + { + if (_path.length >= 1 && isDirSeparator(_path[0])) + { + fs = 0; + fe = 1; + ps = ltrim(fe, pe); + } + else + { + popFront(); + } + } + else static assert(0); + + if (ps == pe) + { + bs = fs; + be = fe; + } + else + { + pe = rtrim(ps, pe); + popBack(); + } + } + + size_t ltrim(size_t s, size_t e) + { + while (s < e && isDirSeparator(_path[s])) + ++s; + return s; + } + + size_t rtrim(size_t s, size_t e) + { + while (s < e && isDirSeparator(_path[e - 1])) + --e; + return e; + } + } + + return PathSplitter(path); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.conv : to; + + assert(equal(pathSplitter("/"), ["/"])); + assert(equal(pathSplitter("/foo/bar"), ["/", "foo", "bar"])); + assert(equal(pathSplitter("foo/../bar//./"), ["foo", "..", "bar", "."])); + + version (Posix) + { + assert(equal(pathSplitter("//foo/bar"), ["/", "foo", "bar"])); + } + + version (Windows) + { + assert(equal(pathSplitter(`foo\..\bar\/.\`), ["foo", "..", "bar", "."])); + assert(equal(pathSplitter("c:"), ["c:"])); + assert(equal(pathSplitter(`c:\foo\bar`), [`c:\`, "foo", "bar"])); + assert(equal(pathSplitter(`c:foo\bar`), ["c:foo", "bar"])); + } +} + +auto pathSplitter(R)(auto ref R path) +if (isConvertibleToString!R) +{ + return pathSplitter!(StringTypeOf!R)(path); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + assert(testAliasedString!pathSplitter("/")); +} + +@safe unittest +{ + // equal2 verifies that the range is the same both ways, i.e. + // through front/popFront and back/popBack. + import std.algorithm; + import std.range; + bool equal2(R1, R2)(R1 r1, R2 r2) + { + static assert(isBidirectionalRange!R1); + return equal(r1, r2) && equal(retro(r1), retro(r2)); + } + + assert(pathSplitter("").empty); + + // Root directories + assert(equal2(pathSplitter("/"), ["/"])); + assert(equal2(pathSplitter("//"), ["/"])); + assert(equal2(pathSplitter("///"w), ["/"w])); + + // Absolute paths + assert(equal2(pathSplitter("/foo/bar".dup), ["/", "foo", "bar"])); + + // General + assert(equal2(pathSplitter("foo/bar"d.dup), ["foo"d, "bar"d])); + assert(equal2(pathSplitter("foo//bar"), ["foo", "bar"])); + assert(equal2(pathSplitter("foo/bar//"w), ["foo"w, "bar"w])); + assert(equal2(pathSplitter("foo/../bar//./"d), ["foo"d, ".."d, "bar"d, "."d])); + + // save() + auto ps1 = pathSplitter("foo/bar/baz"); + auto ps2 = ps1.save; + ps1.popFront(); + assert(equal2(ps1, ["bar", "baz"])); + assert(equal2(ps2, ["foo", "bar", "baz"])); + + // Platform specific + version (Posix) + { + assert(equal2(pathSplitter("//foo/bar"w.dup), ["/"w, "foo"w, "bar"w])); + } + version (Windows) + { + assert(equal2(pathSplitter(`\`), [`\`])); + assert(equal2(pathSplitter(`foo\..\bar\/.\`), ["foo", "..", "bar", "."])); + assert(equal2(pathSplitter("c:"), ["c:"])); + assert(equal2(pathSplitter(`c:\foo\bar`), [`c:\`, "foo", "bar"])); + assert(equal2(pathSplitter(`c:foo\bar`), ["c:foo", "bar"])); + assert(equal2(pathSplitter(`\\foo\bar`), [`\\foo\bar`])); + assert(equal2(pathSplitter(`\\foo\bar\\`), [`\\foo\bar`])); + assert(equal2(pathSplitter(`\\foo\bar\baz`), [`\\foo\bar`, "baz"])); + } + + import std.exception; + assertCTFEable!( + { + assert(equal(pathSplitter("/foo/bar".dup), ["/", "foo", "bar"])); + }); + + static assert(is(typeof(pathSplitter!(const(char)[])(null).front) == const(char)[])); + + import std.utf : byDchar; + assert(equal2(pathSplitter("foo/bar"d.byDchar), ["foo"d, "bar"d])); +} + + + + +/** Determines whether a path starts at a root directory. + + Params: path = A path name. + Returns: Whether a path starts at a root directory. + + On POSIX, this function returns true if and only if the path starts + with a slash (/). + --- + version (Posix) + { + assert(isRooted("/")); + assert(isRooted("/foo")); + assert(!isRooted("foo")); + assert(!isRooted("../foo")); + } + --- + + On Windows, this function returns true if the path starts at + the root directory of the current drive, of some other drive, + or of a network drive. + --- + version (Windows) + { + assert(isRooted(`\`)); + assert(isRooted(`\foo`)); + assert(isRooted(`d:\foo`)); + assert(isRooted(`\\foo\bar`)); + assert(!isRooted("foo")); + assert(!isRooted("d:foo")); + } + --- +*/ +bool isRooted(R)(R path) +if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || + is(StringTypeOf!R)) +{ + if (path.length >= 1 && isDirSeparator(path[0])) return true; + version (Posix) return false; + else version (Windows) return isAbsolute!(BaseOf!R)(path); +} + + +@safe unittest +{ + assert(isRooted("/")); + assert(isRooted("/foo")); + assert(!isRooted("foo")); + assert(!isRooted("../foo")); + + version (Windows) + { + assert(isRooted(`\`)); + assert(isRooted(`\foo`)); + assert(isRooted(`d:\foo`)); + assert(isRooted(`\\foo\bar`)); + assert(!isRooted("foo")); + assert(!isRooted("d:foo")); + } + + static assert(isRooted("/foo")); + static assert(!isRooted("foo")); + + static struct DirEntry { string s; alias s this; } + assert(!isRooted(DirEntry("foo"))); +} + + + + +/** Determines whether a path is absolute or not. + + Params: path = A path name. + + Returns: Whether a path is absolute or not. + + Example: + On POSIX, an absolute path starts at the root directory. + (In fact, $(D _isAbsolute) is just an alias for $(LREF isRooted).) + --- + version (Posix) + { + assert(isAbsolute("/")); + assert(isAbsolute("/foo")); + assert(!isAbsolute("foo")); + assert(!isAbsolute("../foo")); + } + --- + + On Windows, an absolute path starts at the root directory of + a specific drive. Hence, it must start with $(D `d:\`) or $(D `d:/`), + where $(D d) is the drive letter. Alternatively, it may be a + network path, i.e. a path starting with a double (back)slash. + --- + version (Windows) + { + assert(isAbsolute(`d:\`)); + assert(isAbsolute(`d:\foo`)); + assert(isAbsolute(`\\foo\bar`)); + assert(!isAbsolute(`\`)); + assert(!isAbsolute(`\foo`)); + assert(!isAbsolute("d:foo")); + } + --- +*/ +version (StdDdoc) +{ + bool isAbsolute(R)(R path) pure nothrow @safe + if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || + is(StringTypeOf!R)); +} +else version (Windows) +{ + bool isAbsolute(R)(R path) + if (isRandomAccessRange!R && isSomeChar!(ElementType!R) || + is(StringTypeOf!R)) + { + return isDriveRoot!(BaseOf!R)(path) || isUNC!(BaseOf!R)(path); + } +} +else version (Posix) +{ + alias isAbsolute = isRooted; +} + + +@safe unittest +{ + assert(!isAbsolute("foo")); + assert(!isAbsolute("../foo"w)); + static assert(!isAbsolute("foo")); + + version (Posix) + { + assert(isAbsolute("/"d)); + assert(isAbsolute("/foo".dup)); + static assert(isAbsolute("/foo")); + } + + version (Windows) + { + assert(isAbsolute("d:\\"w)); + assert(isAbsolute("d:\\foo"d)); + assert(isAbsolute("\\\\foo\\bar")); + assert(!isAbsolute("\\"w.dup)); + assert(!isAbsolute("\\foo"d.dup)); + assert(!isAbsolute("d:")); + assert(!isAbsolute("d:foo")); + static assert(isAbsolute(`d:\foo`)); + } + + { + auto r = MockRange!(immutable(char))(`../foo`); + assert(!r.isAbsolute()); + } + + static struct DirEntry { string s; alias s this; } + assert(!isAbsolute(DirEntry("foo"))); +} + + + + +/** Transforms $(D path) into an absolute _path. + + The following algorithm is used: + $(OL + $(LI If $(D path) is empty, return $(D null).) + $(LI If $(D path) is already absolute, return it.) + $(LI Otherwise, append $(D path) to $(D base) and return + the result. If $(D base) is not specified, the current + working directory is used.) + ) + The function allocates memory if and only if it gets to the third stage + of this algorithm. + + Params: + path = the relative path to transform + base = the base directory of the relative path + + Returns: + string of transformed path + + Throws: + $(D Exception) if the specified _base directory is not absolute. + + See_Also: + $(LREF asAbsolutePath) which does not allocate +*/ +string absolutePath(string path, lazy string base = getcwd()) + @safe pure +{ + import std.array : array; + if (path.empty) return null; + if (isAbsolute(path)) return path; + auto baseVar = base; + if (!isAbsolute(baseVar)) throw new Exception("Base directory must be absolute"); + return chainPath(baseVar, path).array; +} + +/// +@safe unittest +{ + version (Posix) + { + assert(absolutePath("some/file", "/foo/bar") == "/foo/bar/some/file"); + assert(absolutePath("../file", "/foo/bar") == "/foo/bar/../file"); + assert(absolutePath("/some/file", "/foo/bar") == "/some/file"); + } + + version (Windows) + { + assert(absolutePath(`some\file`, `c:\foo\bar`) == `c:\foo\bar\some\file`); + assert(absolutePath(`..\file`, `c:\foo\bar`) == `c:\foo\bar\..\file`); + assert(absolutePath(`c:\some\file`, `c:\foo\bar`) == `c:\some\file`); + assert(absolutePath(`\`, `c:\`) == `c:\`); + assert(absolutePath(`\some\file`, `c:\foo\bar`) == `c:\some\file`); + } +} + +@safe unittest +{ + version (Posix) + { + static assert(absolutePath("some/file", "/foo/bar") == "/foo/bar/some/file"); + } + + version (Windows) + { + static assert(absolutePath(`some\file`, `c:\foo\bar`) == `c:\foo\bar\some\file`); + } + + import std.exception; + assertThrown(absolutePath("bar", "foo")); +} + +/** Transforms $(D path) into an absolute _path. + + The following algorithm is used: + $(OL + $(LI If $(D path) is empty, return $(D null).) + $(LI If $(D path) is already absolute, return it.) + $(LI Otherwise, append $(D path) to the current working directory, + which allocates memory.) + ) + + Params: + path = the relative path to transform + + Returns: + the transformed path as a lazy range + + See_Also: + $(LREF absolutePath) which returns an allocated string +*/ +auto asAbsolutePath(R)(R path) +if ((isRandomAccessRange!R && isSomeChar!(ElementType!R) || + isNarrowString!R) && + !isConvertibleToString!R) +{ + import std.file : getcwd; + string base = null; + if (!path.empty && !isAbsolute(path)) + base = getcwd(); + return chainPath(base, path); +} + +/// +@system unittest +{ + import std.array; + assert(asAbsolutePath(cast(string) null).array == ""); + version (Posix) + { + assert(asAbsolutePath("/foo").array == "/foo"); + } + version (Windows) + { + assert(asAbsolutePath("c:/foo").array == "c:/foo"); + } + asAbsolutePath("foo"); +} + +auto asAbsolutePath(R)(auto ref R path) +if (isConvertibleToString!R) +{ + return asAbsolutePath!(StringTypeOf!R)(path); +} + +@system unittest +{ + assert(testAliasedString!asAbsolutePath(null)); +} + +/** Translates $(D path) into a relative _path. + + The returned _path is relative to $(D base), which is by default + taken to be the current working directory. If specified, + $(D base) must be an absolute _path, and it is always assumed + to refer to a directory. If $(D path) and $(D base) refer to + the same directory, the function returns $(D `.`). + + The following algorithm is used: + $(OL + $(LI If $(D path) is a relative directory, return it unaltered.) + $(LI Find a common root between $(D path) and $(D base). + If there is no common root, return $(D path) unaltered.) + $(LI Prepare a string with as many $(D `../`) or $(D `..\`) as + necessary to reach the common root from base path.) + $(LI Append the remaining segments of $(D path) to the string + and return.) + ) + + In the second step, path components are compared using $(D filenameCmp!cs), + where $(D cs) is an optional template parameter determining whether + the comparison is case sensitive or not. See the + $(LREF filenameCmp) documentation for details. + + This function allocates memory. + + Params: + cs = Whether matching path name components against the base path should + be case-sensitive or not. + path = A path name. + base = The base path to construct the relative path from. + + Returns: The relative path. + + See_Also: + $(LREF asRelativePath) which does not allocate memory + + Throws: + $(D Exception) if the specified _base directory is not absolute. +*/ +string relativePath(CaseSensitive cs = CaseSensitive.osDefault) + (string path, lazy string base = getcwd()) +{ + if (!isAbsolute(path)) + return path; + auto baseVar = base; + if (!isAbsolute(baseVar)) + throw new Exception("Base directory must be absolute"); + + import std.conv : to; + return asRelativePath!cs(path, baseVar).to!string; +} + +/// +@system unittest +{ + assert(relativePath("foo") == "foo"); + + version (Posix) + { + assert(relativePath("foo", "/bar") == "foo"); + assert(relativePath("/foo/bar", "/foo/bar") == "."); + assert(relativePath("/foo/bar", "/foo/baz") == "../bar"); + assert(relativePath("/foo/bar/baz", "/foo/woo/wee") == "../../bar/baz"); + assert(relativePath("/foo/bar/baz", "/foo/bar") == "baz"); + } + version (Windows) + { + assert(relativePath("foo", `c:\bar`) == "foo"); + assert(relativePath(`c:\foo\bar`, `c:\foo\bar`) == "."); + assert(relativePath(`c:\foo\bar`, `c:\foo\baz`) == `..\bar`); + assert(relativePath(`c:\foo\bar\baz`, `c:\foo\woo\wee`) == `..\..\bar\baz`); + assert(relativePath(`c:\foo\bar\baz`, `c:\foo\bar`) == "baz"); + assert(relativePath(`c:\foo\bar`, `d:\foo`) == `c:\foo\bar`); + } +} + +@system unittest +{ + import std.exception; + assert(relativePath("foo") == "foo"); + version (Posix) + { + relativePath("/foo"); + assert(relativePath("/foo/bar", "/foo/baz") == "../bar"); + assertThrown(relativePath("/foo", "bar")); + } + else version (Windows) + { + relativePath(`\foo`); + assert(relativePath(`c:\foo\bar\baz`, `c:\foo\bar`) == "baz"); + assertThrown(relativePath(`c:\foo`, "bar")); + } + else static assert(0); +} + +/** Transforms `path` into a _path relative to `base`. + + The returned _path is relative to `base`, which is usually + the current working directory. + `base` must be an absolute _path, and it is always assumed + to refer to a directory. If `path` and `base` refer to + the same directory, the function returns `'.'`. + + The following algorithm is used: + $(OL + $(LI If `path` is a relative directory, return it unaltered.) + $(LI Find a common root between `path` and `base`. + If there is no common root, return `path` unaltered.) + $(LI Prepare a string with as many `../` or `..\` as + necessary to reach the common root from base path.) + $(LI Append the remaining segments of `path` to the string + and return.) + ) + + In the second step, path components are compared using `filenameCmp!cs`, + where `cs` is an optional template parameter determining whether + the comparison is case sensitive or not. See the + $(LREF filenameCmp) documentation for details. + + Params: + path = _path to transform + base = absolute path + cs = whether filespec comparisons are sensitive or not; defaults to + `CaseSensitive.osDefault` + + Returns: + a random access range of the transformed _path + + See_Also: + $(LREF relativePath) +*/ +auto asRelativePath(CaseSensitive cs = CaseSensitive.osDefault, R1, R2) + (R1 path, R2 base) +if ((isNarrowString!R1 || + (isRandomAccessRange!R1 && hasSlicing!R1 && isSomeChar!(ElementType!R1)) && + !isConvertibleToString!R1) && + (isNarrowString!R2 || + (isRandomAccessRange!R2 && hasSlicing!R2 && isSomeChar!(ElementType!R2)) && + !isConvertibleToString!R2)) +{ + bool choosePath = !isAbsolute(path); + + // Find common root with current working directory + + auto basePS = pathSplitter(base); + auto pathPS = pathSplitter(path); + choosePath |= filenameCmp!cs(basePS.front, pathPS.front) != 0; + + basePS.popFront(); + pathPS.popFront(); + + import std.algorithm.comparison : mismatch; + import std.algorithm.iteration : joiner; + import std.array : array; + import std.range.primitives : walkLength; + import std.range : repeat, chain, choose; + import std.utf : byCodeUnit, byChar; + + // Remove matching prefix from basePS and pathPS + auto tup = mismatch!((a, b) => filenameCmp!cs(a, b) == 0)(basePS, pathPS); + basePS = tup[0]; + pathPS = tup[1]; + + string sep; + if (basePS.empty && pathPS.empty) + sep = "."; // if base == path, this is the return + else if (!basePS.empty && !pathPS.empty) + sep = dirSeparator; + + // Append as many "../" as necessary to reach common base from path + auto r1 = ".." + .byChar + .repeat(basePS.walkLength()) + .joiner(dirSeparator.byChar); + + auto r2 = pathPS + .joiner(dirSeparator.byChar) + .byChar; + + // Return (r1 ~ sep ~ r2) + return choose(choosePath, path.byCodeUnit, chain(r1, sep.byChar, r2)); +} + +/// +@system unittest +{ + import std.array; + version (Posix) + { + assert(asRelativePath("foo", "/bar").array == "foo"); + assert(asRelativePath("/foo/bar", "/foo/bar").array == "."); + assert(asRelativePath("/foo/bar", "/foo/baz").array == "../bar"); + assert(asRelativePath("/foo/bar/baz", "/foo/woo/wee").array == "../../bar/baz"); + assert(asRelativePath("/foo/bar/baz", "/foo/bar").array == "baz"); + } + else version (Windows) + { + assert(asRelativePath("foo", `c:\bar`).array == "foo"); + assert(asRelativePath(`c:\foo\bar`, `c:\foo\bar`).array == "."); + assert(asRelativePath(`c:\foo\bar`, `c:\foo\baz`).array == `..\bar`); + assert(asRelativePath(`c:\foo\bar\baz`, `c:\foo\woo\wee`).array == `..\..\bar\baz`); + assert(asRelativePath(`c:/foo/bar/baz`, `c:\foo\woo\wee`).array == `..\..\bar\baz`); + assert(asRelativePath(`c:\foo\bar\baz`, `c:\foo\bar`).array == "baz"); + assert(asRelativePath(`c:\foo\bar`, `d:\foo`).array == `c:\foo\bar`); + assert(asRelativePath(`\\foo\bar`, `c:\foo`).array == `\\foo\bar`); + } + else + static assert(0); +} + +auto asRelativePath(CaseSensitive cs = CaseSensitive.osDefault, R1, R2) + (auto ref R1 path, auto ref R2 base) +if (isConvertibleToString!R1 || isConvertibleToString!R2) +{ + import std.meta : staticMap; + alias Types = staticMap!(convertToString, R1, R2); + return asRelativePath!(cs, Types)(path, base); +} + +@system unittest +{ + import std.array; + version (Posix) + assert(asRelativePath(TestAliasedString("foo"), TestAliasedString("/bar")).array == "foo"); + else version (Windows) + assert(asRelativePath(TestAliasedString("foo"), TestAliasedString(`c:\bar`)).array == "foo"); + assert(asRelativePath(TestAliasedString("foo"), "bar").array == "foo"); + assert(asRelativePath("foo", TestAliasedString("bar")).array == "foo"); + assert(asRelativePath(TestAliasedString("foo"), TestAliasedString("bar")).array == "foo"); + import std.utf : byDchar; + assert(asRelativePath("foo"d.byDchar, TestAliasedString("bar")).array == "foo"); +} + +@system unittest +{ + import std.array, std.utf : bCU=byCodeUnit; + version (Posix) + { + assert(asRelativePath("/foo/bar/baz".bCU, "/foo/bar".bCU).array == "baz"); + assert(asRelativePath("/foo/bar/baz"w.bCU, "/foo/bar"w.bCU).array == "baz"w); + assert(asRelativePath("/foo/bar/baz"d.bCU, "/foo/bar"d.bCU).array == "baz"d); + } + else version (Windows) + { + assert(asRelativePath(`\\foo\bar`.bCU, `c:\foo`.bCU).array == `\\foo\bar`); + assert(asRelativePath(`\\foo\bar`w.bCU, `c:\foo`w.bCU).array == `\\foo\bar`w); + assert(asRelativePath(`\\foo\bar`d.bCU, `c:\foo`d.bCU).array == `\\foo\bar`d); + } +} + +/** Compares filename characters. + + This function can perform a case-sensitive or a case-insensitive + comparison. This is controlled through the $(D cs) template parameter + which, if not specified, is given by $(LREF CaseSensitive)$(D .osDefault). + + On Windows, the backslash and slash characters ($(D `\`) and $(D `/`)) + are considered equal. + + Params: + cs = Case-sensitivity of the comparison. + a = A filename character. + b = A filename character. + + Returns: + $(D < 0) if $(D a < b), + $(D 0) if $(D a == b), and + $(D > 0) if $(D a > b). +*/ +int filenameCharCmp(CaseSensitive cs = CaseSensitive.osDefault)(dchar a, dchar b) + @safe pure nothrow +{ + if (isDirSeparator(a) && isDirSeparator(b)) return 0; + static if (!cs) + { + import std.uni : toLower; + a = toLower(a); + b = toLower(b); + } + return cast(int)(a - b); +} + +/// +@safe unittest +{ + assert(filenameCharCmp('a', 'a') == 0); + assert(filenameCharCmp('a', 'b') < 0); + assert(filenameCharCmp('b', 'a') > 0); + + version (linux) + { + // Same as calling filenameCharCmp!(CaseSensitive.yes)(a, b) + assert(filenameCharCmp('A', 'a') < 0); + assert(filenameCharCmp('a', 'A') > 0); + } + version (Windows) + { + // Same as calling filenameCharCmp!(CaseSensitive.no)(a, b) + assert(filenameCharCmp('a', 'A') == 0); + assert(filenameCharCmp('a', 'B') < 0); + assert(filenameCharCmp('A', 'b') < 0); + } +} + +@safe unittest +{ + assert(filenameCharCmp!(CaseSensitive.yes)('A', 'a') < 0); + assert(filenameCharCmp!(CaseSensitive.yes)('a', 'A') > 0); + + assert(filenameCharCmp!(CaseSensitive.no)('a', 'a') == 0); + assert(filenameCharCmp!(CaseSensitive.no)('a', 'b') < 0); + assert(filenameCharCmp!(CaseSensitive.no)('b', 'a') > 0); + assert(filenameCharCmp!(CaseSensitive.no)('A', 'a') == 0); + assert(filenameCharCmp!(CaseSensitive.no)('a', 'A') == 0); + assert(filenameCharCmp!(CaseSensitive.no)('a', 'B') < 0); + assert(filenameCharCmp!(CaseSensitive.no)('B', 'a') > 0); + assert(filenameCharCmp!(CaseSensitive.no)('A', 'b') < 0); + assert(filenameCharCmp!(CaseSensitive.no)('b', 'A') > 0); + + version (Posix) assert(filenameCharCmp('\\', '/') != 0); + version (Windows) assert(filenameCharCmp('\\', '/') == 0); +} + + +/** Compares file names and returns + + Individual characters are compared using $(D filenameCharCmp!cs), + where $(D cs) is an optional template parameter determining whether + the comparison is case sensitive or not. + + Treatment of invalid UTF encodings is implementation defined. + + Params: + cs = case sensitivity + filename1 = range for first file name + filename2 = range for second file name + + Returns: + $(D < 0) if $(D filename1 < filename2), + $(D 0) if $(D filename1 == filename2) and + $(D > 0) if $(D filename1 > filename2). + + See_Also: + $(LREF filenameCharCmp) +*/ +int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, Range1, Range2) + (Range1 filename1, Range2 filename2) +if (isInputRange!Range1 && !isInfinite!Range1 && + isSomeChar!(ElementEncodingType!Range1) && + !isConvertibleToString!Range1 && + isInputRange!Range2 && !isInfinite!Range2 && + isSomeChar!(ElementEncodingType!Range2) && + !isConvertibleToString!Range2) +{ + alias C1 = Unqual!(ElementEncodingType!Range1); + alias C2 = Unqual!(ElementEncodingType!Range2); + + static if (!cs && (C1.sizeof < 4 || C2.sizeof < 4) || + C1.sizeof != C2.sizeof) + { + // Case insensitive - decode so case is checkable + // Different char sizes - decode to bring to common type + import std.utf : byDchar; + return filenameCmp!cs(filename1.byDchar, filename2.byDchar); + } + else static if (isSomeString!Range1 && C1.sizeof < 4 || + isSomeString!Range2 && C2.sizeof < 4) + { + // Avoid autodecoding + import std.utf : byCodeUnit; + return filenameCmp!cs(filename1.byCodeUnit, filename2.byCodeUnit); + } + else + { + for (;;) + { + if (filename1.empty) return -(cast(int) !filename2.empty); + if (filename2.empty) return 1; + const c = filenameCharCmp!cs(filename1.front, filename2.front); + if (c != 0) return c; + filename1.popFront(); + filename2.popFront(); + } + } +} + +/// +@safe unittest +{ + assert(filenameCmp("abc", "abc") == 0); + assert(filenameCmp("abc", "abd") < 0); + assert(filenameCmp("abc", "abb") > 0); + assert(filenameCmp("abc", "abcd") < 0); + assert(filenameCmp("abcd", "abc") > 0); + + version (linux) + { + // Same as calling filenameCmp!(CaseSensitive.yes)(filename1, filename2) + assert(filenameCmp("Abc", "abc") < 0); + assert(filenameCmp("abc", "Abc") > 0); + } + version (Windows) + { + // Same as calling filenameCmp!(CaseSensitive.no)(filename1, filename2) + assert(filenameCmp("Abc", "abc") == 0); + assert(filenameCmp("abc", "Abc") == 0); + assert(filenameCmp("Abc", "abD") < 0); + assert(filenameCmp("abc", "AbB") > 0); + } +} + +int filenameCmp(CaseSensitive cs = CaseSensitive.osDefault, Range1, Range2) + (auto ref Range1 filename1, auto ref Range2 filename2) +if (isConvertibleToString!Range1 || isConvertibleToString!Range2) +{ + import std.meta : staticMap; + alias Types = staticMap!(convertToString, Range1, Range2); + return filenameCmp!(cs, Types)(filename1, filename2); +} + +@safe unittest +{ + assert(filenameCmp!(CaseSensitive.yes)(TestAliasedString("Abc"), "abc") < 0); + assert(filenameCmp!(CaseSensitive.yes)("Abc", TestAliasedString("abc")) < 0); + assert(filenameCmp!(CaseSensitive.yes)(TestAliasedString("Abc"), TestAliasedString("abc")) < 0); +} + +@safe unittest +{ + assert(filenameCmp!(CaseSensitive.yes)("Abc", "abc") < 0); + assert(filenameCmp!(CaseSensitive.yes)("abc", "Abc") > 0); + + assert(filenameCmp!(CaseSensitive.no)("abc", "abc") == 0); + assert(filenameCmp!(CaseSensitive.no)("abc", "abd") < 0); + assert(filenameCmp!(CaseSensitive.no)("abc", "abb") > 0); + assert(filenameCmp!(CaseSensitive.no)("abc", "abcd") < 0); + assert(filenameCmp!(CaseSensitive.no)("abcd", "abc") > 0); + assert(filenameCmp!(CaseSensitive.no)("Abc", "abc") == 0); + assert(filenameCmp!(CaseSensitive.no)("abc", "Abc") == 0); + assert(filenameCmp!(CaseSensitive.no)("Abc", "abD") < 0); + assert(filenameCmp!(CaseSensitive.no)("abc", "AbB") > 0); + + version (Posix) assert(filenameCmp(`abc\def`, `abc/def`) != 0); + version (Windows) assert(filenameCmp(`abc\def`, `abc/def`) == 0); +} + +/** Matches a pattern against a path. + + Some characters of pattern have a special meaning (they are + $(I meta-characters)) and can't be escaped. These are: + + $(BOOKTABLE, + $(TR $(TD $(D *)) + $(TD Matches 0 or more instances of any character.)) + $(TR $(TD $(D ?)) + $(TD Matches exactly one instance of any character.)) + $(TR $(TD $(D [)$(I chars)$(D ])) + $(TD Matches one instance of any character that appears + between the brackets.)) + $(TR $(TD $(D [!)$(I chars)$(D ])) + $(TD Matches one instance of any character that does not + appear between the brackets after the exclamation mark.)) + $(TR $(TD $(D {)$(I string1)$(D ,)$(I string2)$(D ,)…$(D })) + $(TD Matches either of the specified strings.)) + ) + + Individual characters are compared using $(D filenameCharCmp!cs), + where $(D cs) is an optional template parameter determining whether + the comparison is case sensitive or not. See the + $(LREF filenameCharCmp) documentation for details. + + Note that directory + separators and dots don't stop a meta-character from matching + further portions of the path. + + Params: + cs = Whether the matching should be case-sensitive + path = The path to be matched against + pattern = The glob pattern + + Returns: + $(D true) if pattern matches path, $(D false) otherwise. + + See_also: + $(LINK2 http://en.wikipedia.org/wiki/Glob_%28programming%29,Wikipedia: _glob (programming)) + */ +bool globMatch(CaseSensitive cs = CaseSensitive.osDefault, C, Range) + (Range path, const(C)[] pattern) + @safe pure nothrow +if (isForwardRange!Range && !isInfinite!Range && + isSomeChar!(ElementEncodingType!Range) && !isConvertibleToString!Range && + isSomeChar!C && is(Unqual!C == Unqual!(ElementEncodingType!Range))) +in +{ + // Verify that pattern[] is valid + import std.algorithm.searching : balancedParens; + assert(balancedParens(pattern, '[', ']', 0)); + assert(balancedParens(pattern, '{', '}', 0)); +} +body +{ + alias RC = Unqual!(ElementEncodingType!Range); + + static if (RC.sizeof == 1 && isSomeString!Range) + { + import std.utf : byChar; + return globMatch!cs(path.byChar, pattern); + } + else static if (RC.sizeof == 2 && isSomeString!Range) + { + import std.utf : byWchar; + return globMatch!cs(path.byWchar, pattern); + } + else + { + C[] pattmp; + foreach (ref pi; 0 .. pattern.length) + { + const pc = pattern[pi]; + switch (pc) + { + case '*': + if (pi + 1 == pattern.length) + return true; + for (; !path.empty; path.popFront()) + { + auto p = path.save; + if (globMatch!(cs, C)(p, + pattern[pi + 1 .. pattern.length])) + return true; + } + return false; + + case '?': + if (path.empty) + return false; + path.popFront(); + break; + + case '[': + if (path.empty) + return false; + auto nc = path.front; + path.popFront(); + auto not = false; + ++pi; + if (pattern[pi] == '!') + { + not = true; + ++pi; + } + auto anymatch = false; + while (1) + { + const pc2 = pattern[pi]; + if (pc2 == ']') + break; + if (!anymatch && (filenameCharCmp!cs(nc, pc2) == 0)) + anymatch = true; + ++pi; + } + if (anymatch == not) + return false; + break; + + case '{': + // find end of {} section + auto piRemain = pi; + for (; piRemain < pattern.length + && pattern[piRemain] != '}'; ++piRemain) + { } + + if (piRemain < pattern.length) + ++piRemain; + ++pi; + + while (pi < pattern.length) + { + const pi0 = pi; + C pc3 = pattern[pi]; + // find end of current alternative + for (; pi < pattern.length && pc3 != '}' && pc3 != ','; ++pi) + { + pc3 = pattern[pi]; + } + + auto p = path.save; + if (pi0 == pi) + { + if (globMatch!(cs, C)(p, pattern[piRemain..$])) + { + return true; + } + ++pi; + } + else + { + /* Match for: + * pattern[pi0 .. pi-1] ~ pattern[piRemain..$] + */ + if (pattmp is null) + // Allocate this only once per function invocation. + // Should do it with malloc/free, but that would make it impure. + pattmp = new C[pattern.length]; + + const len1 = pi - 1 - pi0; + pattmp[0 .. len1] = pattern[pi0 .. pi - 1]; + + const len2 = pattern.length - piRemain; + pattmp[len1 .. len1 + len2] = pattern[piRemain .. $]; + + if (globMatch!(cs, C)(p, pattmp[0 .. len1 + len2])) + { + return true; + } + } + if (pc3 == '}') + { + break; + } + } + return false; + + default: + if (path.empty) + return false; + if (filenameCharCmp!cs(pc, path.front) != 0) + return false; + path.popFront(); + break; + } + } + return path.empty; + } +} + +/// +@safe unittest +{ + assert(globMatch("foo.bar", "*")); + assert(globMatch("foo.bar", "*.*")); + assert(globMatch(`foo/foo\bar`, "f*b*r")); + assert(globMatch("foo.bar", "f???bar")); + assert(globMatch("foo.bar", "[fg]???bar")); + assert(globMatch("foo.bar", "[!gh]*bar")); + assert(globMatch("bar.fooz", "bar.{foo,bif}z")); + assert(globMatch("bar.bifz", "bar.{foo,bif}z")); + + version (Windows) + { + // Same as calling globMatch!(CaseSensitive.no)(path, pattern) + assert(globMatch("foo", "Foo")); + assert(globMatch("Goo.bar", "[fg]???bar")); + } + version (linux) + { + // Same as calling globMatch!(CaseSensitive.yes)(path, pattern) + assert(!globMatch("foo", "Foo")); + assert(!globMatch("Goo.bar", "[fg]???bar")); + } +} + +bool globMatch(CaseSensitive cs = CaseSensitive.osDefault, C, Range) + (auto ref Range path, const(C)[] pattern) + @safe pure nothrow +if (isConvertibleToString!Range) +{ + return globMatch!(cs, C, StringTypeOf!Range)(path, pattern); +} + +@safe unittest +{ + assert(testAliasedString!globMatch("foo.bar", "*")); +} + +@safe unittest +{ + assert(globMatch!(CaseSensitive.no)("foo", "Foo")); + assert(!globMatch!(CaseSensitive.yes)("foo", "Foo")); + + assert(globMatch("foo", "*")); + assert(globMatch("foo.bar"w, "*"w)); + assert(globMatch("foo.bar"d, "*.*"d)); + assert(globMatch("foo.bar", "foo*")); + assert(globMatch("foo.bar"w, "f*bar"w)); + assert(globMatch("foo.bar"d, "f*b*r"d)); + assert(globMatch("foo.bar", "f???bar")); + assert(globMatch("foo.bar"w, "[fg]???bar"w)); + assert(globMatch("foo.bar"d, "[!gh]*bar"d)); + + assert(!globMatch("foo", "bar")); + assert(!globMatch("foo"w, "*.*"w)); + assert(!globMatch("foo.bar"d, "f*baz"d)); + assert(!globMatch("foo.bar", "f*b*x")); + assert(!globMatch("foo.bar", "[gh]???bar")); + assert(!globMatch("foo.bar"w, "[!fg]*bar"w)); + assert(!globMatch("foo.bar"d, "[fg]???baz"d)); + assert(!globMatch("foo.di", "*.d")); // test issue 6634: triggered bad assertion + + assert(globMatch("foo.bar", "{foo,bif}.bar")); + assert(globMatch("bif.bar"w, "{foo,bif}.bar"w)); + + assert(globMatch("bar.foo"d, "bar.{foo,bif}"d)); + assert(globMatch("bar.bif", "bar.{foo,bif}")); + + assert(globMatch("bar.fooz"w, "bar.{foo,bif}z"w)); + assert(globMatch("bar.bifz"d, "bar.{foo,bif}z"d)); + + assert(globMatch("bar.foo", "bar.{biz,,baz}foo")); + assert(globMatch("bar.foo"w, "bar.{biz,}foo"w)); + assert(globMatch("bar.foo"d, "bar.{,biz}foo"d)); + assert(globMatch("bar.foo", "bar.{}foo")); + + assert(globMatch("bar.foo"w, "bar.{ar,,fo}o"w)); + assert(globMatch("bar.foo"d, "bar.{,ar,fo}o"d)); + assert(globMatch("bar.o", "bar.{,ar,fo}o")); + + assert(!globMatch("foo", "foo?")); + assert(!globMatch("foo", "foo[]")); + assert(!globMatch("foo", "foob")); + assert(!globMatch("foo", "foo{b}")); + + + static assert(globMatch("foo.bar", "[!gh]*bar")); +} + + + + +/** Checks that the given file or directory name is valid. + + The maximum length of $(D filename) is given by the constant + $(D core.stdc.stdio.FILENAME_MAX). (On Windows, this number is + defined as the maximum number of UTF-16 code points, and the + test will therefore only yield strictly correct results when + $(D filename) is a string of $(D wchar)s.) + + On Windows, the following criteria must be satisfied + ($(LINK2 http://msdn.microsoft.com/en-us/library/aa365247(v=vs.85).aspx,source)): + $(UL + $(LI $(D filename) must not contain any characters whose integer + representation is in the range 0-31.) + $(LI $(D filename) must not contain any of the following $(I reserved + characters): <>:"/\|?*) + $(LI $(D filename) may not end with a space ($(D ' ')) or a period + ($(D '.')).) + ) + + On POSIX, $(D filename) may not contain a forward slash ($(D '/')) or + the null character ($(D '\0')). + + Params: + filename = string to check + + Returns: + $(D true) if and only if $(D filename) is not + empty, not too long, and does not contain invalid characters. + +*/ +bool isValidFilename(Range)(Range filename) +if ((isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range && isSomeChar!(ElementEncodingType!Range) || + isNarrowString!Range) && + !isConvertibleToString!Range) +{ + import core.stdc.stdio : FILENAME_MAX; + if (filename.length == 0 || filename.length >= FILENAME_MAX) return false; + foreach (c; filename) + { + version (Windows) + { + switch (c) + { + case 0: + .. + case 31: + case '<': + case '>': + case ':': + case '"': + case '/': + case '\\': + case '|': + case '?': + case '*': + return false; + + default: + break; + } + } + else version (Posix) + { + if (c == 0 || c == '/') return false; + } + else static assert(0); + } + version (Windows) + { + auto last = filename[filename.length - 1]; + if (last == '.' || last == ' ') return false; + } + + // All criteria passed + return true; +} + +/// +@safe pure @nogc nothrow +unittest +{ + import std.utf : byCodeUnit; + + assert(isValidFilename("hello.exe".byCodeUnit)); +} + +bool isValidFilename(Range)(auto ref Range filename) +if (isConvertibleToString!Range) +{ + return isValidFilename!(StringTypeOf!Range)(filename); +} + +@safe unittest +{ + assert(testAliasedString!isValidFilename("hello.exe")); +} + +@safe pure +unittest +{ + import std.conv; + auto valid = ["foo"]; + auto invalid = ["", "foo\0bar", "foo/bar"]; + auto pfdep = [`foo\bar`, "*.txt"]; + version (Windows) invalid ~= pfdep; + else version (Posix) valid ~= pfdep; + else static assert(0); + + import std.meta : AliasSeq; + foreach (T; AliasSeq!(char[], const(char)[], string, wchar[], + const(wchar)[], wstring, dchar[], const(dchar)[], dstring)) + { + foreach (fn; valid) + assert(isValidFilename(to!T(fn))); + foreach (fn; invalid) + assert(!isValidFilename(to!T(fn))); + } + + { + auto r = MockRange!(immutable(char))(`dir/file.d`); + assert(!isValidFilename(r)); + } + + static struct DirEntry { string s; alias s this; } + assert(isValidFilename(DirEntry("file.ext"))); + + version (Windows) + { + immutable string cases = "<>:\"/\\|?*"; + foreach (i; 0 .. 31 + cases.length) + { + char[3] buf; + buf[0] = 'a'; + buf[1] = i <= 31 ? cast(char) i : cases[i - 32]; + buf[2] = 'b'; + assert(!isValidFilename(buf[])); + } + } +} + + + +/** Checks whether $(D path) is a valid _path. + + Generally, this function checks that $(D path) is not empty, and that + each component of the path either satisfies $(LREF isValidFilename) + or is equal to $(D ".") or $(D ".."). + + $(B It does $(I not) check whether the _path points to an existing file + or directory; use $(REF exists, std,file) for this purpose.) + + On Windows, some special rules apply: + $(UL + $(LI If the second character of $(D path) is a colon ($(D ':')), + the first character is interpreted as a drive letter, and + must be in the range A-Z (case insensitive).) + $(LI If $(D path) is on the form $(D `\\$(I server)\$(I share)\...`) + (UNC path), $(LREF isValidFilename) is applied to $(I server) + and $(I share) as well.) + $(LI If $(D path) starts with $(D `\\?\`) (long UNC path), the + only requirement for the rest of the string is that it does + not contain the null character.) + $(LI If $(D path) starts with $(D `\\.\`) (Win32 device namespace) + this function returns $(D false); such paths are beyond the scope + of this module.) + ) + + Params: + path = string or Range of characters to check + + Returns: + true if $(D path) is a valid _path. +*/ +bool isValidPath(Range)(Range path) +if ((isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range && isSomeChar!(ElementEncodingType!Range) || + isNarrowString!Range) && + !isConvertibleToString!Range) +{ + alias C = Unqual!(ElementEncodingType!Range); + + if (path.empty) return false; + + // Check whether component is "." or "..", or whether it satisfies + // isValidFilename. + bool isValidComponent(Range component) + { + assert(component.length > 0); + if (component[0] == '.') + { + if (component.length == 1) return true; + else if (component.length == 2 && component[1] == '.') return true; + } + return isValidFilename(component); + } + + if (path.length == 1) + return isDirSeparator(path[0]) || isValidComponent(path); + + Range remainder; + version (Windows) + { + if (isDirSeparator(path[0]) && isDirSeparator(path[1])) + { + // Some kind of UNC path + if (path.length < 5) + { + // All valid UNC paths must have at least 5 characters + return false; + } + else if (path[2] == '?') + { + // Long UNC path + if (!isDirSeparator(path[3])) return false; + foreach (c; path[4 .. $]) + { + if (c == '\0') return false; + } + return true; + } + else if (path[2] == '.') + { + // Win32 device namespace not supported + return false; + } + else + { + // Normal UNC path, i.e. \\server\share\... + size_t i = 2; + while (i < path.length && !isDirSeparator(path[i])) ++i; + if (i == path.length || !isValidFilename(path[2 .. i])) + return false; + ++i; // Skip a single dir separator + size_t j = i; + while (j < path.length && !isDirSeparator(path[j])) ++j; + if (!isValidFilename(path[i .. j])) return false; + remainder = path[j .. $]; + } + } + else if (isDriveSeparator(path[1])) + { + import std.ascii : isAlpha; + if (!isAlpha(path[0])) return false; + remainder = path[2 .. $]; + } + else + { + remainder = path; + } + } + else version (Posix) + { + remainder = path; + } + else static assert(0); + remainder = ltrimDirSeparators(remainder); + + // Check that each component satisfies isValidComponent. + while (!remainder.empty) + { + size_t i = 0; + while (i < remainder.length && !isDirSeparator(remainder[i])) ++i; + assert(i > 0); + if (!isValidComponent(remainder[0 .. i])) return false; + remainder = ltrimDirSeparators(remainder[i .. $]); + } + + // All criteria passed + return true; +} + +/// +@safe pure @nogc nothrow +unittest +{ + assert(isValidPath("/foo/bar")); + assert(!isValidPath("/foo\0/bar")); + assert(isValidPath("/")); + assert(isValidPath("a")); + + version (Windows) + { + assert(isValidPath(`c:\`)); + assert(isValidPath(`c:\foo`)); + assert(isValidPath(`c:\foo\.\bar\\\..\`)); + assert(!isValidPath(`!:\foo`)); + assert(!isValidPath(`c::\foo`)); + assert(!isValidPath(`c:\foo?`)); + assert(!isValidPath(`c:\foo.`)); + + assert(isValidPath(`\\server\share`)); + assert(isValidPath(`\\server\share\foo`)); + assert(isValidPath(`\\server\share\\foo`)); + assert(!isValidPath(`\\\server\share\foo`)); + assert(!isValidPath(`\\server\\share\foo`)); + assert(!isValidPath(`\\ser*er\share\foo`)); + assert(!isValidPath(`\\server\sha?e\foo`)); + assert(!isValidPath(`\\server\share\|oo`)); + + assert(isValidPath(`\\?\<>:"?*|/\..\.`)); + assert(!isValidPath("\\\\?\\foo\0bar")); + + assert(!isValidPath(`\\.\PhysicalDisk1`)); + assert(!isValidPath(`\\`)); + } + + import std.utf : byCodeUnit; + assert(isValidPath("/foo/bar".byCodeUnit)); +} + +bool isValidPath(Range)(auto ref Range path) +if (isConvertibleToString!Range) +{ + return isValidPath!(StringTypeOf!Range)(path); +} + +@safe unittest +{ + assert(testAliasedString!isValidPath("/foo/bar")); +} + +/** Performs tilde expansion in paths on POSIX systems. + On Windows, this function does nothing. + + There are two ways of using tilde expansion in a path. One + involves using the tilde alone or followed by a path separator. In + this case, the tilde will be expanded with the value of the + environment variable $(D HOME). The second way is putting + a username after the tilde (i.e. $(D ~john/Mail)). Here, + the username will be searched for in the user database + (i.e. $(D /etc/passwd) on Unix systems) and will expand to + whatever path is stored there. The username is considered the + string after the tilde ending at the first instance of a path + separator. + + Note that using the $(D ~user) syntax may give different + values from just $(D ~) if the environment variable doesn't + match the value stored in the user database. + + When the environment variable version is used, the path won't + be modified if the environment variable doesn't exist or it + is empty. When the database version is used, the path won't be + modified if the user doesn't exist in the database or there is + not enough memory to perform the query. + + This function performs several memory allocations. + + Params: + inputPath = The path name to expand. + + Returns: + $(D inputPath) with the tilde expanded, or just $(D inputPath) + if it could not be expanded. + For Windows, $(D expandTilde) merely returns its argument $(D inputPath). + + Example: + ----- + void processFile(string path) + { + // Allow calling this function with paths such as ~/foo + auto fullPath = expandTilde(path); + ... + } + ----- +*/ +string expandTilde(string inputPath) nothrow +{ + version (Posix) + { + import core.exception : onOutOfMemoryError; + import core.stdc.errno : errno, ERANGE; + import core.stdc.stdlib : malloc, free, realloc; + + /* Joins a path from a C string to the remainder of path. + + The last path separator from c_path is discarded. The result + is joined to path[char_pos .. length] if char_pos is smaller + than length, otherwise path is not appended to c_path. + */ + static string combineCPathWithDPath(char* c_path, string path, size_t char_pos) nothrow + { + import core.stdc.string : strlen; + + assert(c_path != null); + assert(path.length > 0); + assert(char_pos >= 0); + + // Search end of C string + size_t end = strlen(c_path); + + // Remove trailing path separator, if any + if (end && isDirSeparator(c_path[end - 1])) + end--; + + // (this is the only GC allocation done in expandTilde()) + string cp; + if (char_pos < path.length) + // Append something from path + cp = cast(string)(c_path[0 .. end] ~ path[char_pos .. $]); + else + // Create our own copy, as lifetime of c_path is undocumented + cp = c_path[0 .. end].idup; + + return cp; + } + + // Replaces the tilde from path with the environment variable HOME. + static string expandFromEnvironment(string path) nothrow + { + import core.stdc.stdlib : getenv; + + assert(path.length >= 1); + assert(path[0] == '~'); + + // Get HOME and use that to replace the tilde. + auto home = getenv("HOME"); + if (home == null) + return path; + + return combineCPathWithDPath(home, path, 1); + } + + // Replaces the tilde from path with the path from the user database. + static string expandFromDatabase(string path) nothrow + { + // bionic doesn't really support this, as getpwnam_r + // isn't provided and getpwnam is basically just a stub + version (CRuntime_Bionic) + { + return path; + } + else + { + import core.sys.posix.pwd : passwd, getpwnam_r; + import std.string : indexOf; + + assert(path.length > 2 || (path.length == 2 && !isDirSeparator(path[1]))); + assert(path[0] == '~'); + + // Extract username, searching for path separator. + auto last_char = indexOf(path, dirSeparator[0]); + + size_t username_len = (last_char == -1) ? path.length : last_char; + char* username = cast(char*) malloc(username_len * char.sizeof); + if (!username) + onOutOfMemoryError(); + scope(exit) free(username); + + if (last_char == -1) + { + username[0 .. username_len - 1] = path[1 .. $]; + last_char = path.length + 1; + } + else + { + username[0 .. username_len - 1] = path[1 .. last_char]; + } + username[username_len - 1] = 0; + + assert(last_char > 1); + + // Reserve C memory for the getpwnam_r() function. + version (unittest) + uint extra_memory_size = 2; + else + uint extra_memory_size = 5 * 1024; + char* extra_memory; + scope(exit) free(extra_memory); + + passwd result; + while (1) + { + extra_memory = cast(char*) realloc(extra_memory, extra_memory_size * char.sizeof); + if (extra_memory == null) + onOutOfMemoryError(); + + // Obtain info from database. + passwd *verify; + errno = 0; + if (getpwnam_r(username, &result, extra_memory, extra_memory_size, + &verify) == 0) + { + // Succeeded if verify points at result + if (verify == &result) + // username is found + path = combineCPathWithDPath(result.pw_dir, path, last_char); + break; + } + + if (errno != ERANGE && + // On FreeBSD and OSX, errno can be left at 0 instead of set to ERANGE + errno != 0) + onOutOfMemoryError(); + + // extra_memory isn't large enough + import core.checkedint : mulu; + bool overflow; + extra_memory_size = mulu(extra_memory_size, 2, overflow); + if (overflow) assert(0); + } + return path; + } + } + + // Return early if there is no tilde in path. + if (inputPath.length < 1 || inputPath[0] != '~') + return inputPath; + + if (inputPath.length == 1 || isDirSeparator(inputPath[1])) + return expandFromEnvironment(inputPath); + else + return expandFromDatabase(inputPath); + } + else version (Windows) + { + // Put here real windows implementation. + return inputPath; + } + else + { + static assert(0); // Guard. Implement on other platforms. + } +} + + +version (unittest) import std.process : environment; +@system unittest +{ + version (Posix) + { + // Retrieve the current home variable. + auto oldHome = environment.get("HOME"); + + // Testing when there is no environment variable. + environment.remove("HOME"); + assert(expandTilde("~/") == "~/"); + assert(expandTilde("~") == "~"); + + // Testing when an environment variable is set. + environment["HOME"] = "dmd/test"; + assert(expandTilde("~/") == "dmd/test/"); + assert(expandTilde("~") == "dmd/test"); + + // The same, but with a variable ending in a slash. + environment["HOME"] = "dmd/test/"; + assert(expandTilde("~/") == "dmd/test/"); + assert(expandTilde("~") == "dmd/test"); + + // Recover original HOME variable before continuing. + if (oldHome !is null) environment["HOME"] = oldHome; + else environment.remove("HOME"); + + // Test user expansion for root, no /root on Android + version (OSX) + { + assert(expandTilde("~root") == "/var/root", expandTilde("~root")); + assert(expandTilde("~root/") == "/var/root/", expandTilde("~root/")); + } + else version (Android) + { + } + else + { + assert(expandTilde("~root") == "/root", expandTilde("~root")); + assert(expandTilde("~root/") == "/root/", expandTilde("~root/")); + } + assert(expandTilde("~Idontexist/hey") == "~Idontexist/hey"); + } +} + +version (unittest) +{ + /* Define a mock RandomAccessRange to use for unittesting. + */ + + struct MockRange(C) + { + this(C[] array) { this.array = array; } + const + { + @property size_t length() { return array.length; } + @property bool empty() { return array.length == 0; } + @property C front() { return array[0]; } + @property C back() { return array[$ - 1]; } + @property size_t opDollar() { return length; } + C opIndex(size_t i) { return array[i]; } + } + void popFront() { array = array[1 .. $]; } + void popBack() { array = array[0 .. $-1]; } + MockRange!C opSlice( size_t lwr, size_t upr) const + { + return MockRange!C(array[lwr .. upr]); + } + @property MockRange save() { return this; } + private: + C[] array; + } + + static assert( isRandomAccessRange!(MockRange!(const(char))) ); +} + +version (unittest) +{ + /* Define a mock BidirectionalRange to use for unittesting. + */ + + struct MockBiRange(C) + { + this(const(C)[] array) { this.array = array; } + const + { + @property bool empty() { return array.length == 0; } + @property C front() { return array[0]; } + @property C back() { return array[$ - 1]; } + @property size_t opDollar() { return array.length; } + } + void popFront() { array = array[1 .. $]; } + void popBack() { array = array[0 .. $-1]; } + @property MockBiRange save() { return this; } + private: + const(C)[] array; + } + + static assert( isBidirectionalRange!(MockBiRange!(const(char))) ); +} + +private template BaseOf(R) +{ + static if (isRandomAccessRange!R && isSomeChar!(ElementType!R)) + alias BaseOf = R; + else + alias BaseOf = StringTypeOf!R; +} diff --git a/libphobos/src/std/process.d b/libphobos/src/std/process.d new file mode 100644 index 0000000..6571d47 --- /dev/null +++ b/libphobos/src/std/process.d @@ -0,0 +1,4047 @@ +// Written in the D programming language. + +/** +Functions for starting and interacting with other processes, and for +working with the current _process' execution environment. + +Process_handling: +$(UL $(LI + $(LREF spawnProcess) spawns a new _process, optionally assigning it an + arbitrary set of standard input, output, and error streams. + The function returns immediately, leaving the child _process to execute + in parallel with its parent. All other functions in this module that + spawn processes are built around $(D spawnProcess).) +$(LI + $(LREF wait) makes the parent _process wait for a child _process to + terminate. In general one should always do this, to avoid + child processes becoming "zombies" when the parent _process exits. + Scope guards are perfect for this – see the $(LREF spawnProcess) + documentation for examples. $(LREF tryWait) is similar to $(D wait), + but does not block if the _process has not yet terminated.) +$(LI + $(LREF pipeProcess) also spawns a child _process which runs + in parallel with its parent. However, instead of taking + arbitrary streams, it automatically creates a set of + pipes that allow the parent to communicate with the child + through the child's standard input, output, and/or error streams. + This function corresponds roughly to C's $(D popen) function.) +$(LI + $(LREF execute) starts a new _process and waits for it + to complete before returning. Additionally, it captures + the _process' standard output and error streams and returns + the output of these as a string.) +$(LI + $(LREF spawnShell), $(LREF pipeShell) and $(LREF executeShell) work like + $(D spawnProcess), $(D pipeProcess) and $(D execute), respectively, + except that they take a single command string and run it through + the current user's default command interpreter. + $(D executeShell) corresponds roughly to C's $(D system) function.) +$(LI + $(LREF kill) attempts to terminate a running _process.) +) + +The following table compactly summarises the different _process creation +functions and how they relate to each other: +$(BOOKTABLE, + $(TR $(TH ) + $(TH Runs program directly) + $(TH Runs shell command)) + $(TR $(TD Low-level _process creation) + $(TD $(LREF spawnProcess)) + $(TD $(LREF spawnShell))) + $(TR $(TD Automatic input/output redirection using pipes) + $(TD $(LREF pipeProcess)) + $(TD $(LREF pipeShell))) + $(TR $(TD Execute and wait for completion, collect output) + $(TD $(LREF execute)) + $(TD $(LREF executeShell))) +) + +Other_functionality: +$(UL +$(LI + $(LREF pipe) is used to create unidirectional pipes.) +$(LI + $(LREF environment) is an interface through which the current _process' + environment variables can be read and manipulated.) +$(LI + $(LREF escapeShellCommand) and $(LREF escapeShellFileName) are useful + for constructing shell command lines in a portable way.) +) + +Authors: + $(LINK2 https://github.com/kyllingstad, Lars Tandle Kyllingstad), + $(LINK2 https://github.com/schveiguy, Steven Schveighoffer), + $(HTTP thecybershadow.net, Vladimir Panteleev) +Copyright: + Copyright (c) 2013, the authors. All rights reserved. +License: + $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). +Source: + $(PHOBOSSRC std/_process.d) +Macros: + OBJECTREF=$(D $(LINK2 object.html#$0,$0)) + LREF=$(D $(LINK2 #.$0,$0)) +*/ +module std.process; + +version (Posix) +{ + import core.sys.posix.sys.wait; + import core.sys.posix.unistd; +} +version (Windows) +{ + import core.stdc.stdio; + import core.sys.windows.windows; + import std.utf; + import std.windows.syserror; +} + +import std.internal.cstring; +import std.range.primitives; +import std.stdio; + + +// When the DMC runtime is used, we have to use some custom functions +// to convert between Windows file handles and FILE*s. +version (Win32) version (CRuntime_DigitalMars) version = DMC_RUNTIME; + + +// Some of the following should be moved to druntime. +private +{ + // Microsoft Visual C Runtime (MSVCRT) declarations. + version (Windows) + { + version (DMC_RUNTIME) { } else + { + import core.stdc.stdint; + enum + { + STDIN_FILENO = 0, + STDOUT_FILENO = 1, + STDERR_FILENO = 2, + } + } + } + + // POSIX API declarations. + version (Posix) + { + version (OSX) + { + extern(C) char*** _NSGetEnviron() nothrow; + const(char**) getEnvironPtr() @trusted + { + return *_NSGetEnviron; + } + } + else + { + // Made available by the C runtime: + extern(C) extern __gshared const char** environ; + const(char**) getEnvironPtr() @trusted + { + return environ; + } + } + + @system unittest + { + new Thread({assert(getEnvironPtr !is null);}).start(); + } + } +} // private + + +// ============================================================================= +// Functions and classes for process management. +// ============================================================================= + + +/** +Spawns a new _process, optionally assigning it an arbitrary set of standard +input, output, and error streams. + +The function returns immediately, leaving the child _process to execute +in parallel with its parent. It is recommended to always call $(LREF wait) +on the returned $(LREF Pid) unless the process was spawned with +$(D Config.detached) flag, as detailed in the documentation for $(D wait). + +Command_line: +There are four overloads of this function. The first two take an array +of strings, $(D args), which should contain the program name as the +zeroth element and any command-line arguments in subsequent elements. +The third and fourth versions are included for convenience, and may be +used when there are no command-line arguments. They take a single string, +$(D program), which specifies the program name. + +Unless a directory is specified in $(D args[0]) or $(D program), +$(D spawnProcess) will search for the program in a platform-dependent +manner. On POSIX systems, it will look for the executable in the +directories listed in the PATH environment variable, in the order +they are listed. On Windows, it will search for the executable in +the following sequence: +$(OL + $(LI The directory from which the application loaded.) + $(LI The current directory for the parent process.) + $(LI The 32-bit Windows system directory.) + $(LI The 16-bit Windows system directory.) + $(LI The Windows directory.) + $(LI The directories listed in the PATH environment variable.) +) +--- +// Run an executable called "prog" located in the current working +// directory: +auto pid = spawnProcess("./prog"); +scope(exit) wait(pid); +// We can do something else while the program runs. The scope guard +// ensures that the process is waited for at the end of the scope. +... + +// Run DMD on the file "myprog.d", specifying a few compiler switches: +auto dmdPid = spawnProcess(["dmd", "-O", "-release", "-inline", "myprog.d" ]); +if (wait(dmdPid) != 0) + writeln("Compilation failed!"); +--- + +Environment_variables: +By default, the child process inherits the environment of the parent +process, along with any additional variables specified in the $(D env) +parameter. If the same variable exists in both the parent's environment +and in $(D env), the latter takes precedence. + +If the $(LREF Config.newEnv) flag is set in $(D config), the child +process will $(I not) inherit the parent's environment. Its entire +environment will then be determined by $(D env). +--- +wait(spawnProcess("myapp", ["foo" : "bar"], Config.newEnv)); +--- + +Standard_streams: +The optional arguments $(D stdin), $(D stdout) and $(D stderr) may +be used to assign arbitrary $(REF File, std,stdio) objects as the standard +input, output and error streams, respectively, of the child process. The +former must be opened for reading, while the latter two must be opened for +writing. The default is for the child process to inherit the standard +streams of its parent. +--- +// Run DMD on the file myprog.d, logging any error messages to a +// file named errors.log. +auto logFile = File("errors.log", "w"); +auto pid = spawnProcess(["dmd", "myprog.d"], + std.stdio.stdin, + std.stdio.stdout, + logFile); +if (wait(pid) != 0) + writeln("Compilation failed. See errors.log for details."); +--- + +Note that if you pass a $(D File) object that is $(I not) +one of the standard input/output/error streams of the parent process, +that stream will by default be $(I closed) in the parent process when +this function returns. See the $(LREF Config) documentation below for +information about how to disable this behaviour. + +Beware of buffering issues when passing $(D File) objects to +$(D spawnProcess). The child process will inherit the low-level raw +read/write offset associated with the underlying file descriptor, but +it will not be aware of any buffered data. In cases where this matters +(e.g. when a file should be aligned before being passed on to the +child process), it may be a good idea to use unbuffered streams, or at +least ensure all relevant buffers are flushed. + +Params: +args = An array which contains the program name as the zeroth element + and any command-line arguments in the following elements. +stdin = The standard input stream of the child process. + This can be any $(REF File, std,stdio) that is opened for reading. + By default the child process inherits the parent's input + stream. +stdout = The standard output stream of the child process. + This can be any $(REF File, std,stdio) that is opened for writing. + By default the child process inherits the parent's output stream. +stderr = The standard error stream of the child process. + This can be any $(REF File, std,stdio) that is opened for writing. + By default the child process inherits the parent's error stream. +env = Additional environment variables for the child process. +config = Flags that control process creation. See $(LREF Config) + for an overview of available flags. +workDir = The working directory for the new process. + By default the child process inherits the parent's working + directory. + +Returns: +A $(LREF Pid) object that corresponds to the spawned process. + +Throws: +$(LREF ProcessException) on failure to start the process.$(BR) +$(REF StdioException, std,stdio) on failure to pass one of the streams + to the child process (Windows only).$(BR) +$(REF RangeError, core,exception) if $(D args) is empty. +*/ +Pid spawnProcess(in char[][] args, + File stdin = std.stdio.stdin, + File stdout = std.stdio.stdout, + File stderr = std.stdio.stderr, + const string[string] env = null, + Config config = Config.none, + in char[] workDir = null) + @trusted // TODO: Should be @safe +{ + version (Windows) auto args2 = escapeShellArguments(args); + else version (Posix) alias args2 = args; + return spawnProcessImpl(args2, stdin, stdout, stderr, env, config, workDir); +} + +/// ditto +Pid spawnProcess(in char[][] args, + const string[string] env, + Config config = Config.none, + in char[] workDir = null) + @trusted // TODO: Should be @safe +{ + return spawnProcess(args, + std.stdio.stdin, + std.stdio.stdout, + std.stdio.stderr, + env, + config, + workDir); +} + +/// ditto +Pid spawnProcess(in char[] program, + File stdin = std.stdio.stdin, + File stdout = std.stdio.stdout, + File stderr = std.stdio.stderr, + const string[string] env = null, + Config config = Config.none, + in char[] workDir = null) + @trusted +{ + return spawnProcess((&program)[0 .. 1], + stdin, stdout, stderr, env, config, workDir); +} + +/// ditto +Pid spawnProcess(in char[] program, + const string[string] env, + Config config = Config.none, + in char[] workDir = null) + @trusted +{ + return spawnProcess((&program)[0 .. 1], env, config, workDir); +} + +version (Posix) private enum InternalError : ubyte +{ + noerror, + exec, + chdir, + getrlimit, + doubleFork, +} + +/* +Implementation of spawnProcess() for POSIX. + +envz should be a zero-terminated array of zero-terminated strings +on the form "var=value". +*/ +version (Posix) +private Pid spawnProcessImpl(in char[][] args, + File stdin, + File stdout, + File stderr, + const string[string] env, + Config config, + in char[] workDir) + @trusted // TODO: Should be @safe +{ + import core.exception : RangeError; + import std.algorithm.searching : any; + import std.conv : text; + import std.path : isDirSeparator; + import std.string : toStringz; + + if (args.empty) throw new RangeError(); + const(char)[] name = args[0]; + if (any!isDirSeparator(name)) + { + if (!isExecutable(name)) + throw new ProcessException(text("Not an executable file: ", name)); + } + else + { + name = searchPathFor(name); + if (name is null) + throw new ProcessException(text("Executable file not found: ", args[0])); + } + + // Convert program name and arguments to C-style strings. + auto argz = new const(char)*[args.length+1]; + argz[0] = toStringz(name); + foreach (i; 1 .. args.length) argz[i] = toStringz(args[i]); + argz[$-1] = null; + + // Prepare environment. + auto envz = createEnv(env, !(config & Config.newEnv)); + + // Open the working directory. + // We use open in the parent and fchdir in the child + // so that most errors (directory doesn't exist, not a directory) + // can be propagated as exceptions before forking. + int workDirFD = -1; + scope(exit) if (workDirFD >= 0) close(workDirFD); + if (workDir.length) + { + import core.sys.posix.fcntl : open, O_RDONLY, stat_t, fstat, S_ISDIR; + workDirFD = open(workDir.tempCString(), O_RDONLY); + if (workDirFD < 0) + throw ProcessException.newFromErrno("Failed to open working directory"); + stat_t s; + if (fstat(workDirFD, &s) < 0) + throw ProcessException.newFromErrno("Failed to stat working directory"); + if (!S_ISDIR(s.st_mode)) + throw new ProcessException("Not a directory: " ~ cast(string) workDir); + } + + static int getFD(ref File f) { return core.stdc.stdio.fileno(f.getFP()); } + + // Get the file descriptors of the streams. + // These could potentially be invalid, but that is OK. If so, later calls + // to dup2() and close() will just silently fail without causing any harm. + auto stdinFD = getFD(stdin); + auto stdoutFD = getFD(stdout); + auto stderrFD = getFD(stderr); + + // We don't have direct access to the errors that may happen in a child process. + // So we use this pipe to deliver them. + int[2] forkPipe; + if (core.sys.posix.unistd.pipe(forkPipe) == 0) + setCLOEXEC(forkPipe[1], true); + else + throw ProcessException.newFromErrno("Could not create pipe to check startup of child"); + scope(exit) close(forkPipe[0]); + + /* + To create detached process, we use double fork technique + but we don't have a direct access to the second fork pid from the caller side thus use a pipe. + We also can't reuse forkPipe for that purpose + because we can't predict the order in which pid and possible error will be written + since the first and the second forks will run in parallel. + */ + int[2] pidPipe; + if (config & Config.detached) + { + if (core.sys.posix.unistd.pipe(pidPipe) != 0) + throw ProcessException.newFromErrno("Could not create pipe to get process pid"); + setCLOEXEC(pidPipe[1], true); + } + scope(exit) if (config & Config.detached) close(pidPipe[0]); + + static void abortOnError(int forkPipeOut, InternalError errorType, int error) nothrow + { + core.sys.posix.unistd.write(forkPipeOut, &errorType, errorType.sizeof); + core.sys.posix.unistd.write(forkPipeOut, &error, error.sizeof); + close(forkPipeOut); + core.sys.posix.unistd._exit(1); + assert(0); + } + + void closePipeWriteEnds() + { + close(forkPipe[1]); + if (config & Config.detached) + close(pidPipe[1]); + } + + auto id = core.sys.posix.unistd.fork(); + if (id < 0) + { + closePipeWriteEnds(); + throw ProcessException.newFromErrno("Failed to spawn new process"); + } + + void forkChild() nothrow @nogc + { + static import core.sys.posix.stdio; + pragma(inline, true); + + // Child process + + // no need for the read end of pipe on child side + if (config & Config.detached) + close(pidPipe[0]); + close(forkPipe[0]); + immutable forkPipeOut = forkPipe[1]; + immutable pidPipeOut = pidPipe[1]; + + // Set the working directory. + if (workDirFD >= 0) + { + if (fchdir(workDirFD) < 0) + { + // Fail. It is dangerous to run a program + // in an unexpected working directory. + abortOnError(forkPipeOut, InternalError.chdir, .errno); + } + close(workDirFD); + } + + void execProcess() + { + // Redirect streams and close the old file descriptors. + // In the case that stderr is redirected to stdout, we need + // to backup the file descriptor since stdout may be redirected + // as well. + if (stderrFD == STDOUT_FILENO) stderrFD = dup(stderrFD); + dup2(stdinFD, STDIN_FILENO); + dup2(stdoutFD, STDOUT_FILENO); + dup2(stderrFD, STDERR_FILENO); + + // Ensure that the standard streams aren't closed on execute, and + // optionally close all other file descriptors. + setCLOEXEC(STDIN_FILENO, false); + setCLOEXEC(STDOUT_FILENO, false); + setCLOEXEC(STDERR_FILENO, false); + + if (!(config & Config.inheritFDs)) + { + import core.stdc.stdlib : malloc; + import core.sys.posix.poll : pollfd, poll, POLLNVAL; + import core.sys.posix.sys.resource : rlimit, getrlimit, RLIMIT_NOFILE; + + // Get the maximum number of file descriptors that could be open. + rlimit r; + if (getrlimit(RLIMIT_NOFILE, &r) != 0) + { + abortOnError(forkPipeOut, InternalError.getrlimit, .errno); + } + immutable maxDescriptors = cast(int) r.rlim_cur; + + // The above, less stdin, stdout, and stderr + immutable maxToClose = maxDescriptors - 3; + + // Call poll() to see which ones are actually open: + auto pfds = cast(pollfd*) malloc(pollfd.sizeof * maxToClose); + foreach (i; 0 .. maxToClose) + { + pfds[i].fd = i + 3; + pfds[i].events = 0; + pfds[i].revents = 0; + } + if (poll(pfds, maxToClose, 0) >= 0) + { + foreach (i; 0 .. maxToClose) + { + // don't close pipe write end + if (pfds[i].fd == forkPipeOut) continue; + // POLLNVAL will be set if the file descriptor is invalid. + if (!(pfds[i].revents & POLLNVAL)) close(pfds[i].fd); + } + } + else + { + // Fall back to closing everything. + foreach (i; 3 .. maxDescriptors) + { + if (i == forkPipeOut) continue; + close(i); + } + } + } + else // This is already done if we don't inherit descriptors. + { + // Close the old file descriptors, unless they are + // either of the standard streams. + if (stdinFD > STDERR_FILENO) close(stdinFD); + if (stdoutFD > STDERR_FILENO) close(stdoutFD); + if (stderrFD > STDERR_FILENO) close(stderrFD); + } + + // Execute program. + core.sys.posix.unistd.execve(argz[0], argz.ptr, envz); + + // If execution fails, exit as quickly as possible. + abortOnError(forkPipeOut, InternalError.exec, .errno); + } + + if (config & Config.detached) + { + auto secondFork = core.sys.posix.unistd.fork(); + if (secondFork == 0) + { + close(pidPipeOut); + execProcess(); + } + else if (secondFork == -1) + { + auto secondForkErrno = .errno; + close(pidPipeOut); + abortOnError(forkPipeOut, InternalError.doubleFork, secondForkErrno); + } + else + { + core.sys.posix.unistd.write(pidPipeOut, &secondFork, pid_t.sizeof); + close(pidPipeOut); + close(forkPipeOut); + _exit(0); + } + } + else + { + execProcess(); + } + } + + if (id == 0) + { + forkChild(); + assert(0); + } + else + { + closePipeWriteEnds(); + auto status = InternalError.noerror; + auto readExecResult = core.sys.posix.unistd.read(forkPipe[0], &status, status.sizeof); + // Save error number just in case if subsequent "waitpid" fails and overrides errno + immutable lastError = .errno; + + if (config & Config.detached) + { + // Forked child exits right after creating second fork. So it should be safe to wait here. + import core.sys.posix.sys.wait : waitpid; + int waitResult; + waitpid(id, &waitResult, 0); + } + + if (readExecResult == -1) + throw ProcessException.newFromErrno(lastError, "Could not read from pipe to get child status"); + + bool owned = true; + if (status != InternalError.noerror) + { + int error; + readExecResult = read(forkPipe[0], &error, error.sizeof); + string errorMsg; + final switch (status) + { + case InternalError.chdir: + errorMsg = "Failed to set working directory"; + break; + case InternalError.getrlimit: + errorMsg = "getrlimit failed"; + break; + case InternalError.exec: + errorMsg = "Failed to execute program"; + break; + case InternalError.doubleFork: + // Can happen only when starting detached process + assert(config & Config.detached); + errorMsg = "Failed to fork twice"; + break; + case InternalError.noerror: + assert(false); + } + if (readExecResult == error.sizeof) + throw ProcessException.newFromErrno(error, errorMsg); + throw new ProcessException(errorMsg); + } + else if (config & Config.detached) + { + owned = false; + if (read(pidPipe[0], &id, id.sizeof) != id.sizeof) + throw ProcessException.newFromErrno("Could not read from pipe to get detached process id"); + } + + // Parent process: Close streams and return. + if (!(config & Config.retainStdin ) && stdinFD > STDERR_FILENO + && stdinFD != getFD(std.stdio.stdin )) + stdin.close(); + if (!(config & Config.retainStdout) && stdoutFD > STDERR_FILENO + && stdoutFD != getFD(std.stdio.stdout)) + stdout.close(); + if (!(config & Config.retainStderr) && stderrFD > STDERR_FILENO + && stderrFD != getFD(std.stdio.stderr)) + stderr.close(); + return new Pid(id, owned); + } +} + +/* +Implementation of spawnProcess() for Windows. + +commandLine must contain the entire command line, properly +quoted/escaped as required by CreateProcessW(). + +envz must be a pointer to a block of UTF-16 characters on the form +"var1=value1\0var2=value2\0...varN=valueN\0\0". +*/ +version (Windows) +private Pid spawnProcessImpl(in char[] commandLine, + File stdin, + File stdout, + File stderr, + const string[string] env, + Config config, + in char[] workDir) + @trusted +{ + import core.exception : RangeError; + + if (commandLine.empty) throw new RangeError("Command line is empty"); + + // Prepare environment. + auto envz = createEnv(env, !(config & Config.newEnv)); + + // Startup info for CreateProcessW(). + STARTUPINFO_W startinfo; + startinfo.cb = startinfo.sizeof; + static int getFD(ref File f) { return f.isOpen ? f.fileno : -1; } + + // Extract file descriptors and HANDLEs from the streams and make the + // handles inheritable. + static void prepareStream(ref File file, DWORD stdHandle, string which, + out int fileDescriptor, out HANDLE handle) + { + fileDescriptor = getFD(file); + handle = null; + if (fileDescriptor >= 0) + handle = file.windowsHandle; + // Windows GUI applications have a fd but not a valid Windows HANDLE. + if (handle is null || handle == INVALID_HANDLE_VALUE) + handle = GetStdHandle(stdHandle); + + DWORD dwFlags; + if (GetHandleInformation(handle, &dwFlags)) + { + if (!(dwFlags & HANDLE_FLAG_INHERIT)) + { + if (!SetHandleInformation(handle, + HANDLE_FLAG_INHERIT, + HANDLE_FLAG_INHERIT)) + { + throw new StdioException( + "Failed to make "~which~" stream inheritable by child process (" + ~sysErrorString(GetLastError()) ~ ')', + 0); + } + } + } + } + int stdinFD = -1, stdoutFD = -1, stderrFD = -1; + prepareStream(stdin, STD_INPUT_HANDLE, "stdin" , stdinFD, startinfo.hStdInput ); + prepareStream(stdout, STD_OUTPUT_HANDLE, "stdout", stdoutFD, startinfo.hStdOutput); + prepareStream(stderr, STD_ERROR_HANDLE, "stderr", stderrFD, startinfo.hStdError ); + + if ((startinfo.hStdInput != null && startinfo.hStdInput != INVALID_HANDLE_VALUE) + || (startinfo.hStdOutput != null && startinfo.hStdOutput != INVALID_HANDLE_VALUE) + || (startinfo.hStdError != null && startinfo.hStdError != INVALID_HANDLE_VALUE)) + startinfo.dwFlags = STARTF_USESTDHANDLES; + + // Create process. + PROCESS_INFORMATION pi; + DWORD dwCreationFlags = + CREATE_UNICODE_ENVIRONMENT | + ((config & Config.suppressConsole) ? CREATE_NO_WINDOW : 0); + auto pworkDir = workDir.tempCStringW(); // workaround until Bugzilla 14696 is fixed + if (!CreateProcessW(null, commandLine.tempCStringW().buffPtr, null, null, true, dwCreationFlags, + envz, workDir.length ? pworkDir : null, &startinfo, &pi)) + throw ProcessException.newFromLastError("Failed to spawn new process"); + + // figure out if we should close any of the streams + if (!(config & Config.retainStdin ) && stdinFD > STDERR_FILENO + && stdinFD != getFD(std.stdio.stdin )) + stdin.close(); + if (!(config & Config.retainStdout) && stdoutFD > STDERR_FILENO + && stdoutFD != getFD(std.stdio.stdout)) + stdout.close(); + if (!(config & Config.retainStderr) && stderrFD > STDERR_FILENO + && stderrFD != getFD(std.stdio.stderr)) + stderr.close(); + + // close the thread handle in the process info structure + CloseHandle(pi.hThread); + if (config & Config.detached) + { + CloseHandle(pi.hProcess); + return new Pid(pi.dwProcessId); + } + return new Pid(pi.dwProcessId, pi.hProcess); +} + +// Converts childEnv to a zero-terminated array of zero-terminated strings +// on the form "name=value", optionally adding those of the current process' +// environment strings that are not present in childEnv. If the parent's +// environment should be inherited without modification, this function +// returns environ directly. +version (Posix) +private const(char*)* createEnv(const string[string] childEnv, + bool mergeWithParentEnv) +{ + // Determine the number of strings in the parent's environment. + int parentEnvLength = 0; + auto environ = getEnvironPtr; + if (mergeWithParentEnv) + { + if (childEnv.length == 0) return environ; + while (environ[parentEnvLength] != null) ++parentEnvLength; + } + + // Convert the "new" variables to C-style strings. + auto envz = new const(char)*[parentEnvLength + childEnv.length + 1]; + int pos = 0; + foreach (var, val; childEnv) + envz[pos++] = (var~'='~val~'\0').ptr; + + // Add the parent's environment. + foreach (environStr; environ[0 .. parentEnvLength]) + { + int eqPos = 0; + while (environStr[eqPos] != '=' && environStr[eqPos] != '\0') ++eqPos; + if (environStr[eqPos] != '=') continue; + auto var = environStr[0 .. eqPos]; + if (var in childEnv) continue; + envz[pos++] = environStr; + } + envz[pos] = null; + return envz.ptr; +} + +version (Posix) @system unittest +{ + auto e1 = createEnv(null, false); + assert(e1 != null && *e1 == null); + + auto e2 = createEnv(null, true); + assert(e2 != null); + int i = 0; + auto environ = getEnvironPtr; + for (; environ[i] != null; ++i) + { + assert(e2[i] != null); + import core.stdc.string; + assert(strcmp(e2[i], environ[i]) == 0); + } + assert(e2[i] == null); + + auto e3 = createEnv(["foo" : "bar", "hello" : "world"], false); + assert(e3 != null && e3[0] != null && e3[1] != null && e3[2] == null); + assert((e3[0][0 .. 8] == "foo=bar\0" && e3[1][0 .. 12] == "hello=world\0") + || (e3[0][0 .. 12] == "hello=world\0" && e3[1][0 .. 8] == "foo=bar\0")); +} + + +// Converts childEnv to a Windows environment block, which is on the form +// "name1=value1\0name2=value2\0...nameN=valueN\0\0", optionally adding +// those of the current process' environment strings that are not present +// in childEnv. Returns null if the parent's environment should be +// inherited without modification, as this is what is expected by +// CreateProcess(). +version (Windows) +private LPVOID createEnv(const string[string] childEnv, + bool mergeWithParentEnv) +{ + if (mergeWithParentEnv && childEnv.length == 0) return null; + import std.array : appender; + import std.uni : toUpper; + auto envz = appender!(wchar[])(); + void put(string var, string val) + { + envz.put(var); + envz.put('='); + envz.put(val); + envz.put(cast(wchar) '\0'); + } + + // Add the variables in childEnv, removing them from parentEnv + // if they exist there too. + auto parentEnv = mergeWithParentEnv ? environment.toAA() : null; + foreach (k, v; childEnv) + { + auto uk = toUpper(k); + put(uk, v); + if (uk in parentEnv) parentEnv.remove(uk); + } + + // Add remaining parent environment variables. + foreach (k, v; parentEnv) put(k, v); + + // Two final zeros are needed in case there aren't any environment vars, + // and the last one does no harm when there are. + envz.put("\0\0"w); + return envz.data.ptr; +} + +version (Windows) @system unittest +{ + assert(createEnv(null, true) == null); + assert((cast(wchar*) createEnv(null, false))[0 .. 2] == "\0\0"w); + auto e1 = (cast(wchar*) createEnv(["foo":"bar", "ab":"c"], false))[0 .. 14]; + assert(e1 == "FOO=bar\0AB=c\0\0"w || e1 == "AB=c\0FOO=bar\0\0"w); +} + +// Searches the PATH variable for the given executable file, +// (checking that it is in fact executable). +version (Posix) +private string searchPathFor(in char[] executable) + @trusted //TODO: @safe nothrow +{ + import std.algorithm.iteration : splitter; + import std.conv : to; + import std.path : buildPath; + + auto pathz = core.stdc.stdlib.getenv("PATH"); + if (pathz == null) return null; + + foreach (dir; splitter(to!string(pathz), ':')) + { + auto execPath = buildPath(dir, executable); + if (isExecutable(execPath)) return execPath; + } + + return null; +} + +// Checks whether the file exists and can be executed by the +// current user. +version (Posix) +private bool isExecutable(in char[] path) @trusted nothrow @nogc //TODO: @safe +{ + return (access(path.tempCString(), X_OK) == 0); +} + +version (Posix) @safe unittest +{ + import std.algorithm; + auto lsPath = searchPathFor("ls"); + assert(!lsPath.empty); + assert(lsPath[0] == '/'); + assert(lsPath.endsWith("ls")); + auto unlikely = searchPathFor("lkmqwpoialhggyaofijadsohufoiqezm"); + assert(unlikely is null, "Are you kidding me?"); +} + +// Sets or unsets the FD_CLOEXEC flag on the given file descriptor. +version (Posix) +private void setCLOEXEC(int fd, bool on) nothrow @nogc +{ + import core.sys.posix.fcntl : fcntl, F_GETFD, FD_CLOEXEC, F_SETFD; + auto flags = fcntl(fd, F_GETFD); + if (flags >= 0) + { + if (on) flags |= FD_CLOEXEC; + else flags &= ~(cast(typeof(flags)) FD_CLOEXEC); + flags = fcntl(fd, F_SETFD, flags); + } + assert(flags != -1 || .errno == EBADF); +} + +@system unittest // Command line arguments in spawnProcess(). +{ + version (Windows) TestScript prog = + "if not [%~1]==[foo] ( exit 1 ) + if not [%~2]==[bar] ( exit 2 ) + exit 0"; + else version (Posix) TestScript prog = + `if test "$1" != "foo"; then exit 1; fi + if test "$2" != "bar"; then exit 2; fi + exit 0`; + assert(wait(spawnProcess(prog.path)) == 1); + assert(wait(spawnProcess([prog.path])) == 1); + assert(wait(spawnProcess([prog.path, "foo"])) == 2); + assert(wait(spawnProcess([prog.path, "foo", "baz"])) == 2); + assert(wait(spawnProcess([prog.path, "foo", "bar"])) == 0); +} + +// test that file descriptors are correctly closed / left open. +// ideally this would be done by the child process making libc +// calls, but we make do... +version (Posix) @system unittest +{ + import core.sys.posix.fcntl : open, O_RDONLY; + import core.sys.posix.unistd : close; + import std.algorithm.searching : canFind, findSplitBefore; + import std.array : split; + import std.conv : to; + static import std.file; + import std.functional : reverseArgs; + import std.path : buildPath; + + auto directory = uniqueTempPath(); + std.file.mkdir(directory); + scope(exit) std.file.rmdirRecurse(directory); + auto path = buildPath(directory, "tmp"); + std.file.write(path, null); + auto fd = open(path.tempCString, O_RDONLY); + scope(exit) close(fd); + + // command >&2 (or any other number) checks whethether that number + // file descriptor is open. + // Can't use this for arbitrary descriptors as many shells only support + // single digit fds. + TestScript testDefaults = `command >&0 && command >&1 && command >&2`; + assert(execute(testDefaults.path).status == 0); + assert(execute(testDefaults.path, null, Config.inheritFDs).status == 0); + + // try /proc//fd/ on linux + version (linux) + { + TestScript proc = "ls /proc/$$/fd"; + auto procRes = execute(proc.path, null); + if (procRes.status == 0) + { + auto fdStr = fd.to!string; + assert(!procRes.output.split.canFind(fdStr)); + assert(execute(proc.path, null, Config.inheritFDs) + .output.split.canFind(fdStr)); + return; + } + } + + // try fuser (might sometimes need permissions) + TestScript fuser = "echo $$ && fuser -f " ~ path; + auto fuserRes = execute(fuser.path, null); + if (fuserRes.status == 0) + { + assert(!reverseArgs!canFind(fuserRes + .output.findSplitBefore("\n").expand)); + assert(reverseArgs!canFind(execute(fuser.path, null, Config.inheritFDs) + .output.findSplitBefore("\n").expand)); + return; + } + + // last resort, try lsof (not available on all Posix) + TestScript lsof = "lsof -p$$"; + auto lsofRes = execute(lsof.path, null); + if (lsofRes.status == 0) + { + assert(!lsofRes.output.canFind(path)); + assert(execute(lsof.path, null, Config.inheritFDs).output.canFind(path)); + return; + } + + std.stdio.stderr.writeln(__FILE__, ':', __LINE__, + ": Warning: Couldn't find any way to check open files"); + // DON'T DO ANY MORE TESTS BELOW HERE IN THIS UNITTEST BLOCK, THE ABOVE + // TESTS RETURN ON SUCCESS +} + +@system unittest // Environment variables in spawnProcess(). +{ + // We really should use set /a on Windows, but Wine doesn't support it. + version (Windows) TestScript envProg = + `if [%STD_PROCESS_UNITTEST1%] == [1] ( + if [%STD_PROCESS_UNITTEST2%] == [2] (exit 3) + exit 1 + ) + if [%STD_PROCESS_UNITTEST1%] == [4] ( + if [%STD_PROCESS_UNITTEST2%] == [2] (exit 6) + exit 4 + ) + if [%STD_PROCESS_UNITTEST2%] == [2] (exit 2) + exit 0`; + version (Posix) TestScript envProg = + `if test "$std_process_unittest1" = ""; then + std_process_unittest1=0 + fi + if test "$std_process_unittest2" = ""; then + std_process_unittest2=0 + fi + exit $(($std_process_unittest1+$std_process_unittest2))`; + + environment.remove("std_process_unittest1"); // Just in case. + environment.remove("std_process_unittest2"); + assert(wait(spawnProcess(envProg.path)) == 0); + assert(wait(spawnProcess(envProg.path, null, Config.newEnv)) == 0); + + environment["std_process_unittest1"] = "1"; + assert(wait(spawnProcess(envProg.path)) == 1); + assert(wait(spawnProcess(envProg.path, null, Config.newEnv)) == 0); + + auto env = ["std_process_unittest2" : "2"]; + assert(wait(spawnProcess(envProg.path, env)) == 3); + assert(wait(spawnProcess(envProg.path, env, Config.newEnv)) == 2); + + env["std_process_unittest1"] = "4"; + assert(wait(spawnProcess(envProg.path, env)) == 6); + assert(wait(spawnProcess(envProg.path, env, Config.newEnv)) == 6); + + environment.remove("std_process_unittest1"); + assert(wait(spawnProcess(envProg.path, env)) == 6); + assert(wait(spawnProcess(envProg.path, env, Config.newEnv)) == 6); +} + +@system unittest // Stream redirection in spawnProcess(). +{ + import std.path : buildPath; + import std.string; + version (Windows) TestScript prog = + "set /p INPUT= + echo %INPUT% output %~1 + echo %INPUT% error %~2 1>&2"; + else version (Posix) TestScript prog = + "read INPUT + echo $INPUT output $1 + echo $INPUT error $2 >&2"; + + // Pipes + void testPipes(Config config) + { + auto pipei = pipe(); + auto pipeo = pipe(); + auto pipee = pipe(); + auto pid = spawnProcess([prog.path, "foo", "bar"], + pipei.readEnd, pipeo.writeEnd, pipee.writeEnd, null, config); + pipei.writeEnd.writeln("input"); + pipei.writeEnd.flush(); + assert(pipeo.readEnd.readln().chomp() == "input output foo"); + assert(pipee.readEnd.readln().chomp().stripRight() == "input error bar"); + if (!(config & Config.detached)) + wait(pid); + } + + // Files + void testFiles(Config config) + { + import std.ascii, std.file, std.uuid, core.thread; + auto pathi = buildPath(tempDir(), randomUUID().toString()); + auto patho = buildPath(tempDir(), randomUUID().toString()); + auto pathe = buildPath(tempDir(), randomUUID().toString()); + std.file.write(pathi, "INPUT"~std.ascii.newline); + auto filei = File(pathi, "r"); + auto fileo = File(patho, "w"); + auto filee = File(pathe, "w"); + auto pid = spawnProcess([prog.path, "bar", "baz" ], filei, fileo, filee, null, config); + if (!(config & Config.detached)) + wait(pid); + else + // We need to wait a little to ensure that the process has finished and data was written to files + Thread.sleep(2.seconds); + assert(readText(patho).chomp() == "INPUT output bar"); + assert(readText(pathe).chomp().stripRight() == "INPUT error baz"); + remove(pathi); + remove(patho); + remove(pathe); + } + + testPipes(Config.none); + testFiles(Config.none); + testPipes(Config.detached); + testFiles(Config.detached); +} + +@system unittest // Error handling in spawnProcess() +{ + import std.exception : assertThrown; + assertThrown!ProcessException(spawnProcess("ewrgiuhrifuheiohnmnvqweoijwf")); + assertThrown!ProcessException(spawnProcess("./rgiuhrifuheiohnmnvqweoijwf")); + assertThrown!ProcessException(spawnProcess("ewrgiuhrifuheiohnmnvqweoijwf", null, Config.detached)); + assertThrown!ProcessException(spawnProcess("./rgiuhrifuheiohnmnvqweoijwf", null, Config.detached)); + + // can't execute malformed file with executable permissions + version (Posix) + { + import std.path : buildPath; + import std.file : remove, write, setAttributes; + import core.sys.posix.sys.stat : S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP, S_IROTH, S_IXOTH; + string deleteme = buildPath(tempDir(), "deleteme.std.process.unittest.pid") ~ to!string(thisProcessID); + write(deleteme, ""); + scope(exit) remove(deleteme); + setAttributes(deleteme, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); + assertThrown!ProcessException(spawnProcess(deleteme)); + assertThrown!ProcessException(spawnProcess(deleteme, null, Config.detached)); + } +} + +@system unittest // Specifying a working directory. +{ + import std.path; + TestScript prog = "echo foo>bar"; + + auto directory = uniqueTempPath(); + mkdir(directory); + scope(exit) rmdirRecurse(directory); + + auto pid = spawnProcess([prog.path], null, Config.none, directory); + wait(pid); + assert(exists(buildPath(directory, "bar"))); +} + +@system unittest // Specifying a bad working directory. +{ + import std.exception : assertThrown; + TestScript prog = "echo"; + + auto directory = uniqueTempPath(); + assertThrown!ProcessException(spawnProcess([prog.path], null, Config.none, directory)); + assertThrown!ProcessException(spawnProcess([prog.path], null, Config.detached, directory)); + + std.file.write(directory, "foo"); + scope(exit) remove(directory); + assertThrown!ProcessException(spawnProcess([prog.path], null, Config.none, directory)); + assertThrown!ProcessException(spawnProcess([prog.path], null, Config.detached, directory)); + + // can't run in directory if user does not have search permission on this directory + version (Posix) + { + import core.sys.posix.sys.stat : S_IRUSR; + auto directoryNoSearch = uniqueTempPath(); + mkdir(directoryNoSearch); + scope(exit) rmdirRecurse(directoryNoSearch); + setAttributes(directoryNoSearch, S_IRUSR); + assertThrown!ProcessException(spawnProcess(prog.path, null, Config.none, directoryNoSearch)); + assertThrown!ProcessException(spawnProcess(prog.path, null, Config.detached, directoryNoSearch)); + } +} + +@system unittest // Specifying empty working directory. +{ + TestScript prog = ""; + + string directory = ""; + assert(directory.ptr && !directory.length); + spawnProcess([prog.path], null, Config.none, directory).wait(); +} + +@system unittest // Reopening the standard streams (issue 13258) +{ + import std.string; + void fun() + { + spawnShell("echo foo").wait(); + spawnShell("echo bar").wait(); + } + + auto tmpFile = uniqueTempPath(); + scope(exit) if (exists(tmpFile)) remove(tmpFile); + + { + auto oldOut = std.stdio.stdout; + scope(exit) std.stdio.stdout = oldOut; + + std.stdio.stdout = File(tmpFile, "w"); + fun(); + std.stdio.stdout.close(); + } + + auto lines = readText(tmpFile).splitLines(); + assert(lines == ["foo", "bar"]); +} + +version (Windows) +@system unittest // MSVCRT workaround (issue 14422) +{ + auto fn = uniqueTempPath(); + std.file.write(fn, "AAAAAAAAAA"); + + auto f = File(fn, "a"); + spawnProcess(["cmd", "/c", "echo BBBBB"], std.stdio.stdin, f).wait(); + + auto data = readText(fn); + assert(data == "AAAAAAAAAABBBBB\r\n", data); +} + +/** +A variation on $(LREF spawnProcess) that runs the given _command through +the current user's preferred _command interpreter (aka. shell). + +The string $(D command) is passed verbatim to the shell, and is therefore +subject to its rules about _command structure, argument/filename quoting +and escaping of special characters. +The path to the shell executable defaults to $(LREF nativeShell). + +In all other respects this function works just like $(D spawnProcess). +Please refer to the $(LREF spawnProcess) documentation for descriptions +of the other function parameters, the return value and any exceptions +that may be thrown. +--- +// Run the command/program "foo" on the file named "my file.txt", and +// redirect its output into foo.log. +auto pid = spawnShell(`foo "my file.txt" > foo.log`); +wait(pid); +--- + +See_also: +$(LREF escapeShellCommand), which may be helpful in constructing a +properly quoted and escaped shell _command line for the current platform. +*/ +Pid spawnShell(in char[] command, + File stdin = std.stdio.stdin, + File stdout = std.stdio.stdout, + File stderr = std.stdio.stderr, + const string[string] env = null, + Config config = Config.none, + in char[] workDir = null, + string shellPath = nativeShell) + @trusted // TODO: Should be @safe +{ + version (Windows) + { + // CMD does not parse its arguments like other programs. + // It does not use CommandLineToArgvW. + // Instead, it treats the first and last quote specially. + // See CMD.EXE /? for details. + auto args = escapeShellFileName(shellPath) + ~ ` ` ~ shellSwitch ~ ` "` ~ command ~ `"`; + } + else version (Posix) + { + const(char)[][3] args; + args[0] = shellPath; + args[1] = shellSwitch; + args[2] = command; + } + return spawnProcessImpl(args, stdin, stdout, stderr, env, config, workDir); +} + +/// ditto +Pid spawnShell(in char[] command, + const string[string] env, + Config config = Config.none, + in char[] workDir = null, + string shellPath = nativeShell) + @trusted // TODO: Should be @safe +{ + return spawnShell(command, + std.stdio.stdin, + std.stdio.stdout, + std.stdio.stderr, + env, + config, + workDir, + shellPath); +} + +@system unittest +{ + version (Windows) + auto cmd = "echo %FOO%"; + else version (Posix) + auto cmd = "echo $foo"; + import std.file; + auto tmpFile = uniqueTempPath(); + scope(exit) if (exists(tmpFile)) remove(tmpFile); + auto redir = "> \""~tmpFile~'"'; + auto env = ["foo" : "bar"]; + assert(wait(spawnShell(cmd~redir, env)) == 0); + auto f = File(tmpFile, "a"); + version (CRuntime_Microsoft) f.seek(0, SEEK_END); // MSVCRT probably seeks to the end when writing, not before + assert(wait(spawnShell(cmd, std.stdio.stdin, f, std.stdio.stderr, env)) == 0); + f.close(); + auto output = std.file.readText(tmpFile); + assert(output == "bar\nbar\n" || output == "bar\r\nbar\r\n"); +} + +version (Windows) +@system unittest +{ + import std.string; + TestScript prog = "echo %0 %*"; + auto outputFn = uniqueTempPath(); + scope(exit) if (exists(outputFn)) remove(outputFn); + auto args = [`a b c`, `a\b\c\`, `a"b"c"`]; + auto result = executeShell( + escapeShellCommand([prog.path] ~ args) + ~ " > " ~ + escapeShellFileName(outputFn)); + assert(result.status == 0); + auto args2 = outputFn.readText().strip().parseCommandLine()[1..$]; + assert(args == args2, text(args2)); +} + + +/** +Flags that control the behaviour of $(LREF spawnProcess) and +$(LREF spawnShell). + +Use bitwise OR to combine flags. + +Example: +--- +auto logFile = File("myapp_error.log", "w"); + +// Start program, suppressing the console window (Windows only), +// redirect its error stream to logFile, and leave logFile open +// in the parent process as well. +auto pid = spawnProcess("myapp", stdin, stdout, logFile, + Config.retainStderr | Config.suppressConsole); +scope(exit) +{ + auto exitCode = wait(pid); + logFile.writeln("myapp exited with code ", exitCode); + logFile.close(); +} +--- +*/ +enum Config +{ + none = 0, + + /** + By default, the child process inherits the parent's environment, + and any environment variables passed to $(LREF spawnProcess) will + be added to it. If this flag is set, the only variables in the + child process' environment will be those given to spawnProcess. + */ + newEnv = 1, + + /** + Unless the child process inherits the standard input/output/error + streams of its parent, one almost always wants the streams closed + in the parent when $(LREF spawnProcess) returns. Therefore, by + default, this is done. If this is not desirable, pass any of these + options to spawnProcess. + */ + retainStdin = 2, + retainStdout = 4, /// ditto + retainStderr = 8, /// ditto + + /** + On Windows, if the child process is a console application, this + flag will prevent the creation of a console window. Otherwise, + it will be ignored. On POSIX, $(D suppressConsole) has no effect. + */ + suppressConsole = 16, + + /** + On POSIX, open $(LINK2 http://en.wikipedia.org/wiki/File_descriptor,file descriptors) + are by default inherited by the child process. As this may lead + to subtle bugs when pipes or multiple threads are involved, + $(LREF spawnProcess) ensures that all file descriptors except the + ones that correspond to standard input/output/error are closed + in the child process when it starts. Use $(D inheritFDs) to prevent + this. + + On Windows, this option has no effect, and any handles which have been + explicitly marked as inheritable will always be inherited by the child + process. + */ + inheritFDs = 32, + + /** + Spawn process in detached state. This removes the need in calling + $(LREF wait) to clean up the process resources. + + Note: + Calling $(LREF wait) or $(LREF kill) with the resulting $(D Pid) is invalid. + */ + detached = 64, +} + + +/// A handle that corresponds to a spawned process. +final class Pid +{ + /** + The process ID number. + + This is a number that uniquely identifies the process on the operating + system, for at least as long as the process is running. Once $(LREF wait) + has been called on the $(LREF Pid), this method will return an + invalid (negative) process ID. + */ + @property int processID() const @safe pure nothrow + { + return _processID; + } + + /** + An operating system handle to the process. + + This handle is used to specify the process in OS-specific APIs. + On POSIX, this function returns a $(D core.sys.posix.sys.types.pid_t) + with the same value as $(LREF Pid.processID), while on Windows it returns + a $(D core.sys.windows.windows.HANDLE). + + Once $(LREF wait) has been called on the $(LREF Pid), this method + will return an invalid handle. + */ + // Note: Since HANDLE is a reference, this function cannot be const. + version (Windows) + @property HANDLE osHandle() @safe pure nothrow + { + return _handle; + } + else version (Posix) + @property pid_t osHandle() @safe pure nothrow + { + return _processID; + } + +private: + /* + Pid.performWait() does the dirty work for wait() and nonBlockingWait(). + + If block == true, this function blocks until the process terminates, + sets _processID to terminated, and returns the exit code or terminating + signal as described in the wait() documentation. + + If block == false, this function returns immediately, regardless + of the status of the process. If the process has terminated, the + function has the exact same effect as the blocking version. If not, + it returns 0 and does not modify _processID. + */ + version (Posix) + int performWait(bool block) @trusted + { + import std.exception : enforceEx; + enforceEx!ProcessException(owned, "Can't wait on a detached process"); + if (_processID == terminated) return _exitCode; + int exitCode; + while (true) + { + int status; + auto check = waitpid(_processID, &status, block ? 0 : WNOHANG); + if (check == -1) + { + if (errno == ECHILD) + { + throw new ProcessException( + "Process does not exist or is not a child process."); + } + else + { + // waitpid() was interrupted by a signal. We simply + // restart it. + assert(errno == EINTR); + continue; + } + } + if (!block && check == 0) return 0; + if (WIFEXITED(status)) + { + exitCode = WEXITSTATUS(status); + break; + } + else if (WIFSIGNALED(status)) + { + exitCode = -WTERMSIG(status); + break; + } + // We check again whether the call should be blocking, + // since we don't care about other status changes besides + // "exited" and "terminated by signal". + if (!block) return 0; + + // Process has stopped, but not terminated, so we continue waiting. + } + // Mark Pid as terminated, and cache and return exit code. + _processID = terminated; + _exitCode = exitCode; + return exitCode; + } + else version (Windows) + { + int performWait(bool block) @trusted + { + import std.exception : enforceEx; + enforceEx!ProcessException(owned, "Can't wait on a detached process"); + if (_processID == terminated) return _exitCode; + assert(_handle != INVALID_HANDLE_VALUE); + if (block) + { + auto result = WaitForSingleObject(_handle, INFINITE); + if (result != WAIT_OBJECT_0) + throw ProcessException.newFromLastError("Wait failed."); + } + if (!GetExitCodeProcess(_handle, cast(LPDWORD)&_exitCode)) + throw ProcessException.newFromLastError(); + if (!block && _exitCode == STILL_ACTIVE) return 0; + CloseHandle(_handle); + _handle = INVALID_HANDLE_VALUE; + _processID = terminated; + return _exitCode; + } + + ~this() + { + if (_handle != INVALID_HANDLE_VALUE) + { + CloseHandle(_handle); + _handle = INVALID_HANDLE_VALUE; + } + } + } + + // Special values for _processID. + enum invalid = -1, terminated = -2; + + // OS process ID number. Only nonnegative IDs correspond to + // running processes. + int _processID = invalid; + + // Exit code cached by wait(). This is only expected to hold a + // sensible value if _processID == terminated. + int _exitCode; + + // Whether the process can be waited for by wait() for or killed by kill(). + // False if process was started as detached. True otherwise. + bool owned; + + // Pids are only meant to be constructed inside this module, so + // we make the constructor private. + version (Windows) + { + HANDLE _handle = INVALID_HANDLE_VALUE; + this(int pid, HANDLE handle) @safe pure nothrow + { + _processID = pid; + _handle = handle; + this.owned = true; + } + this(int pid) @safe pure nothrow + { + _processID = pid; + this.owned = false; + } + } + else + { + this(int id, bool owned) @safe pure nothrow + { + _processID = id; + this.owned = owned; + } + } +} + + +/** +Waits for the process associated with $(D pid) to terminate, and returns +its exit status. + +In general one should always _wait for child processes to terminate +before exiting the parent process unless the process was spawned as detached +(that was spawned with $(D Config.detached) flag). +Otherwise, they may become "$(HTTP en.wikipedia.org/wiki/Zombie_process,zombies)" +– processes that are defunct, yet still occupy a slot in the OS process table. +You should not and must not wait for detached processes, since you don't own them. + +If the process has already terminated, this function returns directly. +The exit code is cached, so that if wait() is called multiple times on +the same $(LREF Pid) it will always return the same value. + +POSIX_specific: +If the process is terminated by a signal, this function returns a +negative number whose absolute value is the signal number. +Since POSIX restricts normal exit codes to the range 0-255, a +negative return value will always indicate termination by signal. +Signal codes are defined in the $(D core.sys.posix.signal) module +(which corresponds to the $(D signal.h) POSIX header). + +Throws: +$(LREF ProcessException) on failure or on attempt to wait for detached process. + +Example: +See the $(LREF spawnProcess) documentation. + +See_also: +$(LREF tryWait), for a non-blocking function. +*/ +int wait(Pid pid) @safe +{ + assert(pid !is null, "Called wait on a null Pid."); + return pid.performWait(true); +} + + +@system unittest // Pid and wait() +{ + version (Windows) TestScript prog = "exit %~1"; + else version (Posix) TestScript prog = "exit $1"; + assert(wait(spawnProcess([prog.path, "0"])) == 0); + assert(wait(spawnProcess([prog.path, "123"])) == 123); + auto pid = spawnProcess([prog.path, "10"]); + assert(pid.processID > 0); + version (Windows) assert(pid.osHandle != INVALID_HANDLE_VALUE); + else version (Posix) assert(pid.osHandle == pid.processID); + assert(wait(pid) == 10); + assert(wait(pid) == 10); // cached exit code + assert(pid.processID < 0); + version (Windows) assert(pid.osHandle == INVALID_HANDLE_VALUE); + else version (Posix) assert(pid.osHandle < 0); +} + + +/** +A non-blocking version of $(LREF wait). + +If the process associated with $(D pid) has already terminated, +$(D tryWait) has the exact same effect as $(D wait). +In this case, it returns a tuple where the $(D terminated) field +is set to $(D true) and the $(D status) field has the same +interpretation as the return value of $(D wait). + +If the process has $(I not) yet terminated, this function differs +from $(D wait) in that does not wait for this to happen, but instead +returns immediately. The $(D terminated) field of the returned +tuple will then be set to $(D false), while the $(D status) field +will always be 0 (zero). $(D wait) or $(D tryWait) should then be +called again on the same $(D Pid) at some later time; not only to +get the exit code, but also to avoid the process becoming a "zombie" +when it finally terminates. (See $(LREF wait) for details). + +Returns: +An $(D std.typecons.Tuple!(bool, "terminated", int, "status")). + +Throws: +$(LREF ProcessException) on failure or on attempt to wait for detached process. + +Example: +--- +auto pid = spawnProcess("dmd myapp.d"); +scope(exit) wait(pid); +... +auto dmd = tryWait(pid); +if (dmd.terminated) +{ + if (dmd.status == 0) writeln("Compilation succeeded!"); + else writeln("Compilation failed"); +} +else writeln("Still compiling..."); +... +--- +Note that in this example, the first $(D wait) call will have no +effect if the process has already terminated by the time $(D tryWait) +is called. In the opposite case, however, the $(D scope) statement +ensures that we always wait for the process if it hasn't terminated +by the time we reach the end of the scope. +*/ +auto tryWait(Pid pid) @safe +{ + import std.typecons : Tuple; + assert(pid !is null, "Called tryWait on a null Pid."); + auto code = pid.performWait(false); + return Tuple!(bool, "terminated", int, "status")(pid._processID == Pid.terminated, code); +} +// unittest: This function is tested together with kill() below. + + +/** +Attempts to terminate the process associated with $(D pid). + +The effect of this function, as well as the meaning of $(D codeOrSignal), +is highly platform dependent. Details are given below. Common to all +platforms is that this function only $(I initiates) termination of the process, +and returns immediately. It does not wait for the process to end, +nor does it guarantee that the process does in fact get terminated. + +Always call $(LREF wait) to wait for a process to complete, even if $(D kill) +has been called on it. + +Windows_specific: +The process will be +$(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/ms686714%28v=vs.100%29.aspx, +forcefully and abruptly terminated). If $(D codeOrSignal) is specified, it +must be a nonnegative number which will be used as the exit code of the process. +If not, the process wil exit with code 1. Do not use $(D codeOrSignal = 259), +as this is a special value (aka. $(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/ms683189.aspx,STILL_ACTIVE)) +used by Windows to signal that a process has in fact $(I not) terminated yet. +--- +auto pid = spawnProcess("some_app"); +kill(pid, 10); +assert(wait(pid) == 10); +--- + +POSIX_specific: +A $(LINK2 http://en.wikipedia.org/wiki/Unix_signal,signal) will be sent to +the process, whose value is given by $(D codeOrSignal). Depending on the +signal sent, this may or may not terminate the process. Symbolic constants +for various $(LINK2 http://en.wikipedia.org/wiki/Unix_signal#POSIX_signals, +POSIX signals) are defined in $(D core.sys.posix.signal), which corresponds to the +$(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html, +$(D signal.h) POSIX header). If $(D codeOrSignal) is omitted, the +$(D SIGTERM) signal will be sent. (This matches the behaviour of the +$(LINK2 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/kill.html, +$(D _kill)) shell command.) +--- +import core.sys.posix.signal : SIGKILL; +auto pid = spawnProcess("some_app"); +kill(pid, SIGKILL); +assert(wait(pid) == -SIGKILL); // Negative return value on POSIX! +--- + +Throws: +$(LREF ProcessException) on error (e.g. if codeOrSignal is invalid). + or on attempt to kill detached process. + Note that failure to terminate the process is considered a "normal" + outcome, not an error.$(BR) +*/ +void kill(Pid pid) +{ + version (Windows) kill(pid, 1); + else version (Posix) + { + import core.sys.posix.signal : SIGTERM; + kill(pid, SIGTERM); + } +} + +/// ditto +void kill(Pid pid, int codeOrSignal) +{ + import std.exception : enforceEx; + enforceEx!ProcessException(pid.owned, "Can't kill detached process"); + version (Windows) + { + if (codeOrSignal < 0) throw new ProcessException("Invalid exit code"); + // On Windows, TerminateProcess() appears to terminate the + // *current* process if it is passed an invalid handle... + if (pid.osHandle == INVALID_HANDLE_VALUE) + throw new ProcessException("Invalid process handle"); + if (!TerminateProcess(pid.osHandle, codeOrSignal)) + throw ProcessException.newFromLastError(); + } + else version (Posix) + { + import core.sys.posix.signal : kill; + if (kill(pid.osHandle, codeOrSignal) == -1) + throw ProcessException.newFromErrno(); + } +} + +@system unittest // tryWait() and kill() +{ + import core.thread; + import std.exception : assertThrown; + // The test script goes into an infinite loop. + version (Windows) + { + TestScript prog = ":loop + goto loop"; + } + else version (Posix) + { + import core.sys.posix.signal : SIGTERM, SIGKILL; + TestScript prog = "while true; do sleep 1; done"; + } + auto pid = spawnProcess(prog.path); + // Android appears to automatically kill sleeping processes very quickly, + // so shorten the wait before killing here. + version (Android) + Thread.sleep(dur!"msecs"(5)); + else + Thread.sleep(dur!"seconds"(1)); + kill(pid); + version (Windows) assert(wait(pid) == 1); + else version (Posix) assert(wait(pid) == -SIGTERM); + + pid = spawnProcess(prog.path); + Thread.sleep(dur!"seconds"(1)); + auto s = tryWait(pid); + assert(!s.terminated && s.status == 0); + assertThrown!ProcessException(kill(pid, -123)); // Negative code not allowed. + version (Windows) kill(pid, 123); + else version (Posix) kill(pid, SIGKILL); + do { s = tryWait(pid); } while (!s.terminated); + version (Windows) assert(s.status == 123); + else version (Posix) assert(s.status == -SIGKILL); + assertThrown!ProcessException(kill(pid)); +} + +@system unittest // wait() and kill() detached process +{ + import core.thread; + import std.exception : assertThrown; + TestScript prog = "exit 0"; + auto pid = spawnProcess([prog.path], null, Config.detached); + /* + This sleep is needed because we can't wait() for detached process to end + and therefore TestScript destructor may run at the same time as /bin/sh tries to start the script. + This leads to the annoying message like "/bin/sh: 0: Can't open /tmp/std.process temporary file" to appear when running tests. + It does not happen in unittests with non-detached processes because we always wait() for them to finish. + */ + Thread.sleep(1.seconds); + assert(!pid.owned); + version (Windows) assert(pid.osHandle == INVALID_HANDLE_VALUE); + assertThrown!ProcessException(wait(pid)); + assertThrown!ProcessException(kill(pid)); +} + + +/** +Creates a unidirectional _pipe. + +Data is written to one end of the _pipe and read from the other. +--- +auto p = pipe(); +p.writeEnd.writeln("Hello World"); +p.writeEnd.flush(); +assert(p.readEnd.readln().chomp() == "Hello World"); +--- +Pipes can, for example, be used for interprocess communication +by spawning a new process and passing one end of the _pipe to +the child, while the parent uses the other end. +(See also $(LREF pipeProcess) and $(LREF pipeShell) for an easier +way of doing this.) +--- +// Use cURL to download the dlang.org front page, pipe its +// output to grep to extract a list of links to ZIP files, +// and write the list to the file "D downloads.txt": +auto p = pipe(); +auto outFile = File("D downloads.txt", "w"); +auto cpid = spawnProcess(["curl", "http://dlang.org/download.html"], + std.stdio.stdin, p.writeEnd); +scope(exit) wait(cpid); +auto gpid = spawnProcess(["grep", "-o", `http://\S*\.zip`], + p.readEnd, outFile); +scope(exit) wait(gpid); +--- + +Returns: +A $(LREF Pipe) object that corresponds to the created _pipe. + +Throws: +$(REF StdioException, std,stdio) on failure. +*/ +version (Posix) +Pipe pipe() @trusted //TODO: @safe +{ + import core.sys.posix.stdio : fdopen; + int[2] fds; + if (core.sys.posix.unistd.pipe(fds) != 0) + throw new StdioException("Unable to create pipe"); + Pipe p; + auto readFP = fdopen(fds[0], "r"); + if (readFP == null) + throw new StdioException("Cannot open read end of pipe"); + p._read = File(readFP, null); + auto writeFP = fdopen(fds[1], "w"); + if (writeFP == null) + throw new StdioException("Cannot open write end of pipe"); + p._write = File(writeFP, null); + return p; +} +else version (Windows) +Pipe pipe() @trusted //TODO: @safe +{ + // use CreatePipe to create an anonymous pipe + HANDLE readHandle; + HANDLE writeHandle; + if (!CreatePipe(&readHandle, &writeHandle, null, 0)) + { + throw new StdioException( + "Error creating pipe (" ~ sysErrorString(GetLastError()) ~ ')', + 0); + } + + scope(failure) + { + CloseHandle(readHandle); + CloseHandle(writeHandle); + } + + try + { + Pipe p; + p._read .windowsHandleOpen(readHandle , "r"); + p._write.windowsHandleOpen(writeHandle, "a"); + return p; + } + catch (Exception e) + { + throw new StdioException("Error attaching pipe (" ~ e.msg ~ ")", + 0); + } +} + + +/// An interface to a pipe created by the $(LREF pipe) function. +struct Pipe +{ + /// The read end of the pipe. + @property File readEnd() @safe nothrow { return _read; } + + + /// The write end of the pipe. + @property File writeEnd() @safe nothrow { return _write; } + + + /** + Closes both ends of the pipe. + + Normally it is not necessary to do this manually, as $(REF File, std,stdio) + objects are automatically closed when there are no more references + to them. + + Note that if either end of the pipe has been passed to a child process, + it will only be closed in the parent process. (What happens in the + child process is platform dependent.) + + Throws: + $(REF ErrnoException, std,exception) if an error occurs. + */ + void close() @safe + { + _read.close(); + _write.close(); + } + +private: + File _read, _write; +} + +@system unittest +{ + import std.string; + auto p = pipe(); + p.writeEnd.writeln("Hello World"); + p.writeEnd.flush(); + assert(p.readEnd.readln().chomp() == "Hello World"); + p.close(); + assert(!p.readEnd.isOpen); + assert(!p.writeEnd.isOpen); +} + + +/** +Starts a new process, creating pipes to redirect its standard +input, output and/or error streams. + +$(D pipeProcess) and $(D pipeShell) are convenient wrappers around +$(LREF spawnProcess) and $(LREF spawnShell), respectively, and +automate the task of redirecting one or more of the child process' +standard streams through pipes. Like the functions they wrap, +these functions return immediately, leaving the child process to +execute in parallel with the invoking process. It is recommended +to always call $(LREF wait) on the returned $(LREF ProcessPipes.pid), +as detailed in the documentation for $(D wait). + +The $(D args)/$(D program)/$(D command), $(D env) and $(D config) +parameters are forwarded straight to the underlying spawn functions, +and we refer to their documentation for details. + +Params: +args = An array which contains the program name as the zeroth element + and any command-line arguments in the following elements. + (See $(LREF spawnProcess) for details.) +program = The program name, $(I without) command-line arguments. + (See $(LREF spawnProcess) for details.) +command = A shell command which is passed verbatim to the command + interpreter. (See $(LREF spawnShell) for details.) +redirect = Flags that determine which streams are redirected, and + how. See $(LREF Redirect) for an overview of available + flags. +env = Additional environment variables for the child process. + (See $(LREF spawnProcess) for details.) +config = Flags that control process creation. See $(LREF Config) + for an overview of available flags, and note that the + $(D retainStd...) flags have no effect in this function. +workDir = The working directory for the new process. + By default the child process inherits the parent's working + directory. +shellPath = The path to the shell to use to run the specified program. + By default this is $(LREF nativeShell). + +Returns: +A $(LREF ProcessPipes) object which contains $(REF File, std,stdio) +handles that communicate with the redirected streams of the child +process, along with a $(LREF Pid) object that corresponds to the +spawned process. + +Throws: +$(LREF ProcessException) on failure to start the process.$(BR) +$(REF StdioException, std,stdio) on failure to redirect any of the streams.$(BR) + +Example: +--- +// my_application writes to stdout and might write to stderr +auto pipes = pipeProcess("my_application", Redirect.stdout | Redirect.stderr); +scope(exit) wait(pipes.pid); + +// Store lines of output. +string[] output; +foreach (line; pipes.stdout.byLine) output ~= line.idup; + +// Store lines of errors. +string[] errors; +foreach (line; pipes.stderr.byLine) errors ~= line.idup; + + +// sendmail expects to read from stdin +pipes = pipeProcess(["/usr/bin/sendmail", "-t"], Redirect.stdin); +pipes.stdin.writeln("To: you"); +pipes.stdin.writeln("From: me"); +pipes.stdin.writeln("Subject: dlang"); +pipes.stdin.writeln(""); +pipes.stdin.writeln(message); + +// a single period tells sendmail we are finished +pipes.stdin.writeln("."); + +// but at this point sendmail might not see it, we need to flush +pipes.stdin.flush(); + +// sendmail happens to exit on ".", but some you have to close the file: +pipes.stdin.close(); + +// otherwise this wait will wait forever +wait(pipes.pid); + +--- +*/ +ProcessPipes pipeProcess(in char[][] args, + Redirect redirect = Redirect.all, + const string[string] env = null, + Config config = Config.none, + in char[] workDir = null) + @safe +{ + return pipeProcessImpl!spawnProcess(args, redirect, env, config, workDir); +} + +/// ditto +ProcessPipes pipeProcess(in char[] program, + Redirect redirect = Redirect.all, + const string[string] env = null, + Config config = Config.none, + in char[] workDir = null) + @safe +{ + return pipeProcessImpl!spawnProcess(program, redirect, env, config, workDir); +} + +/// ditto +ProcessPipes pipeShell(in char[] command, + Redirect redirect = Redirect.all, + const string[string] env = null, + Config config = Config.none, + in char[] workDir = null, + string shellPath = nativeShell) + @safe +{ + return pipeProcessImpl!spawnShell(command, + redirect, + env, + config, + workDir, + shellPath); +} + +// Implementation of the pipeProcess() family of functions. +private ProcessPipes pipeProcessImpl(alias spawnFunc, Cmd, ExtraSpawnFuncArgs...) + (Cmd command, + Redirect redirectFlags, + const string[string] env = null, + Config config = Config.none, + in char[] workDir = null, + ExtraSpawnFuncArgs extraArgs = ExtraSpawnFuncArgs.init) + @trusted //TODO: @safe +{ + File childStdin, childStdout, childStderr; + ProcessPipes pipes; + pipes._redirectFlags = redirectFlags; + + if (redirectFlags & Redirect.stdin) + { + auto p = pipe(); + childStdin = p.readEnd; + pipes._stdin = p.writeEnd; + } + else + { + childStdin = std.stdio.stdin; + } + + if (redirectFlags & Redirect.stdout) + { + if ((redirectFlags & Redirect.stdoutToStderr) != 0) + throw new StdioException("Cannot create pipe for stdout AND " + ~"redirect it to stderr", 0); + auto p = pipe(); + childStdout = p.writeEnd; + pipes._stdout = p.readEnd; + } + else + { + childStdout = std.stdio.stdout; + } + + if (redirectFlags & Redirect.stderr) + { + if ((redirectFlags & Redirect.stderrToStdout) != 0) + throw new StdioException("Cannot create pipe for stderr AND " + ~"redirect it to stdout", 0); + auto p = pipe(); + childStderr = p.writeEnd; + pipes._stderr = p.readEnd; + } + else + { + childStderr = std.stdio.stderr; + } + + if (redirectFlags & Redirect.stdoutToStderr) + { + if (redirectFlags & Redirect.stderrToStdout) + { + // We know that neither of the other options have been + // set, so we assign the std.stdio.std* streams directly. + childStdout = std.stdio.stderr; + childStderr = std.stdio.stdout; + } + else + { + childStdout = childStderr; + } + } + else if (redirectFlags & Redirect.stderrToStdout) + { + childStderr = childStdout; + } + + config &= ~(Config.retainStdin | Config.retainStdout | Config.retainStderr); + pipes._pid = spawnFunc(command, childStdin, childStdout, childStderr, + env, config, workDir, extraArgs); + return pipes; +} + + +/** +Flags that can be passed to $(LREF pipeProcess) and $(LREF pipeShell) +to specify which of the child process' standard streams are redirected. +Use bitwise OR to combine flags. +*/ +enum Redirect +{ + /// Redirect the standard input, output or error streams, respectively. + stdin = 1, + stdout = 2, /// ditto + stderr = 4, /// ditto + + /** + Redirect _all three streams. This is equivalent to + $(D Redirect.stdin | Redirect.stdout | Redirect.stderr). + */ + all = stdin | stdout | stderr, + + /** + Redirect the standard error stream into the standard output stream. + This can not be combined with $(D Redirect.stderr). + */ + stderrToStdout = 8, + + /** + Redirect the standard output stream into the standard error stream. + This can not be combined with $(D Redirect.stdout). + */ + stdoutToStderr = 16, +} + +@system unittest +{ + import std.string; + version (Windows) TestScript prog = + "call :sub %~1 %~2 0 + call :sub %~1 %~2 1 + call :sub %~1 %~2 2 + call :sub %~1 %~2 3 + exit 3 + + :sub + set /p INPUT= + if -%INPUT%-==-stop- ( exit %~3 ) + echo %INPUT% %~1 + echo %INPUT% %~2 1>&2"; + else version (Posix) TestScript prog = + `for EXITCODE in 0 1 2 3; do + read INPUT + if test "$INPUT" = stop; then break; fi + echo "$INPUT $1" + echo "$INPUT $2" >&2 + done + exit $EXITCODE`; + auto pp = pipeProcess([prog.path, "bar", "baz"]); + pp.stdin.writeln("foo"); + pp.stdin.flush(); + assert(pp.stdout.readln().chomp() == "foo bar"); + assert(pp.stderr.readln().chomp().stripRight() == "foo baz"); + pp.stdin.writeln("1234567890"); + pp.stdin.flush(); + assert(pp.stdout.readln().chomp() == "1234567890 bar"); + assert(pp.stderr.readln().chomp().stripRight() == "1234567890 baz"); + pp.stdin.writeln("stop"); + pp.stdin.flush(); + assert(wait(pp.pid) == 2); + + pp = pipeProcess([prog.path, "12345", "67890"], + Redirect.stdin | Redirect.stdout | Redirect.stderrToStdout); + pp.stdin.writeln("xyz"); + pp.stdin.flush(); + assert(pp.stdout.readln().chomp() == "xyz 12345"); + assert(pp.stdout.readln().chomp().stripRight() == "xyz 67890"); + pp.stdin.writeln("stop"); + pp.stdin.flush(); + assert(wait(pp.pid) == 1); + + pp = pipeShell(escapeShellCommand(prog.path, "AAAAA", "BBB"), + Redirect.stdin | Redirect.stdoutToStderr | Redirect.stderr); + pp.stdin.writeln("ab"); + pp.stdin.flush(); + assert(pp.stderr.readln().chomp() == "ab AAAAA"); + assert(pp.stderr.readln().chomp().stripRight() == "ab BBB"); + pp.stdin.writeln("stop"); + pp.stdin.flush(); + assert(wait(pp.pid) == 1); +} + +@system unittest +{ + import std.exception : assertThrown; + TestScript prog = "exit 0"; + assertThrown!StdioException(pipeProcess( + prog.path, + Redirect.stdout | Redirect.stdoutToStderr)); + assertThrown!StdioException(pipeProcess( + prog.path, + Redirect.stderr | Redirect.stderrToStdout)); + auto p = pipeProcess(prog.path, Redirect.stdin); + assertThrown!Error(p.stdout); + assertThrown!Error(p.stderr); + wait(p.pid); + p = pipeProcess(prog.path, Redirect.stderr); + assertThrown!Error(p.stdin); + assertThrown!Error(p.stdout); + wait(p.pid); +} + +/** +Object which contains $(REF File, std,stdio) handles that allow communication +with a child process through its standard streams. +*/ +struct ProcessPipes +{ + /// The $(LREF Pid) of the child process. + @property Pid pid() @safe nothrow + { + return _pid; + } + + /** + An $(REF File, std,stdio) that allows writing to the child process' + standard input stream. + + Throws: + $(OBJECTREF Error) if the child process' standard input stream hasn't + been redirected. + */ + @property File stdin() @safe nothrow + { + if ((_redirectFlags & Redirect.stdin) == 0) + throw new Error("Child process' standard input stream hasn't " + ~"been redirected."); + return _stdin; + } + + /** + An $(REF File, std,stdio) that allows reading from the child process' + standard output stream. + + Throws: + $(OBJECTREF Error) if the child process' standard output stream hasn't + been redirected. + */ + @property File stdout() @safe nothrow + { + if ((_redirectFlags & Redirect.stdout) == 0) + throw new Error("Child process' standard output stream hasn't " + ~"been redirected."); + return _stdout; + } + + /** + An $(REF File, std,stdio) that allows reading from the child process' + standard error stream. + + Throws: + $(OBJECTREF Error) if the child process' standard error stream hasn't + been redirected. + */ + @property File stderr() @safe nothrow + { + if ((_redirectFlags & Redirect.stderr) == 0) + throw new Error("Child process' standard error stream hasn't " + ~"been redirected."); + return _stderr; + } + +private: + Redirect _redirectFlags; + Pid _pid; + File _stdin, _stdout, _stderr; +} + + + +/** +Executes the given program or shell command and returns its exit +code and output. + +$(D execute) and $(D executeShell) start a new process using +$(LREF spawnProcess) and $(LREF spawnShell), respectively, and wait +for the process to complete before returning. The functions capture +what the child process prints to both its standard output and +standard error streams, and return this together with its exit code. +--- +auto dmd = execute(["dmd", "myapp.d"]); +if (dmd.status != 0) writeln("Compilation failed:\n", dmd.output); + +auto ls = executeShell("ls -l"); +if (ls.status != 0) writeln("Failed to retrieve file listing"); +else writeln(ls.output); +--- + +The $(D args)/$(D program)/$(D command), $(D env) and $(D config) +parameters are forwarded straight to the underlying spawn functions, +and we refer to their documentation for details. + +Params: +args = An array which contains the program name as the zeroth element + and any command-line arguments in the following elements. + (See $(LREF spawnProcess) for details.) +program = The program name, $(I without) command-line arguments. + (See $(LREF spawnProcess) for details.) +command = A shell command which is passed verbatim to the command + interpreter. (See $(LREF spawnShell) for details.) +env = Additional environment variables for the child process. + (See $(LREF spawnProcess) for details.) +config = Flags that control process creation. See $(LREF Config) + for an overview of available flags, and note that the + $(D retainStd...) flags have no effect in this function. +maxOutput = The maximum number of bytes of output that should be + captured. +workDir = The working directory for the new process. + By default the child process inherits the parent's working + directory. +shellPath = The path to the shell to use to run the specified program. + By default this is $(LREF nativeShell). + + +Returns: +An $(D std.typecons.Tuple!(int, "status", string, "output")). + +POSIX_specific: +If the process is terminated by a signal, the $(D status) field of +the return value will contain a negative number whose absolute +value is the signal number. (See $(LREF wait) for details.) + +Throws: +$(LREF ProcessException) on failure to start the process.$(BR) +$(REF StdioException, std,stdio) on failure to capture output. +*/ +auto execute(in char[][] args, + const string[string] env = null, + Config config = Config.none, + size_t maxOutput = size_t.max, + in char[] workDir = null) + @trusted //TODO: @safe +{ + return executeImpl!pipeProcess(args, env, config, maxOutput, workDir); +} + +/// ditto +auto execute(in char[] program, + const string[string] env = null, + Config config = Config.none, + size_t maxOutput = size_t.max, + in char[] workDir = null) + @trusted //TODO: @safe +{ + return executeImpl!pipeProcess(program, env, config, maxOutput, workDir); +} + +/// ditto +auto executeShell(in char[] command, + const string[string] env = null, + Config config = Config.none, + size_t maxOutput = size_t.max, + in char[] workDir = null, + string shellPath = nativeShell) + @trusted //TODO: @safe +{ + return executeImpl!pipeShell(command, + env, + config, + maxOutput, + workDir, + shellPath); +} + +// Does the actual work for execute() and executeShell(). +private auto executeImpl(alias pipeFunc, Cmd, ExtraPipeFuncArgs...)( + Cmd commandLine, + const string[string] env = null, + Config config = Config.none, + size_t maxOutput = size_t.max, + in char[] workDir = null, + ExtraPipeFuncArgs extraArgs = ExtraPipeFuncArgs.init) +{ + import std.algorithm.comparison : min; + import std.array : appender; + import std.typecons : Tuple; + + auto p = pipeFunc(commandLine, Redirect.stdout | Redirect.stderrToStdout, + env, config, workDir, extraArgs); + + auto a = appender!(ubyte[])(); + enum size_t defaultChunkSize = 4096; + immutable chunkSize = min(maxOutput, defaultChunkSize); + + // Store up to maxOutput bytes in a. + foreach (ubyte[] chunk; p.stdout.byChunk(chunkSize)) + { + immutable size_t remain = maxOutput - a.data.length; + + if (chunk.length < remain) a.put(chunk); + else + { + a.put(chunk[0 .. remain]); + break; + } + } + // Exhaust the stream, if necessary. + foreach (ubyte[] chunk; p.stdout.byChunk(defaultChunkSize)) { } + + return Tuple!(int, "status", string, "output")(wait(p.pid), cast(string) a.data); +} + +@system unittest +{ + import std.string; + // To avoid printing the newline characters, we use the echo|set trick on + // Windows, and printf on POSIX (neither echo -n nor echo \c are portable). + version (Windows) TestScript prog = + "echo|set /p=%~1 + echo|set /p=%~2 1>&2 + exit 123"; + else version (Android) TestScript prog = + `echo -n $1 + echo -n $2 >&2 + exit 123`; + else version (Posix) TestScript prog = + `printf '%s' $1 + printf '%s' $2 >&2 + exit 123`; + auto r = execute([prog.path, "foo", "bar"]); + assert(r.status == 123); + assert(r.output.stripRight() == "foobar"); + auto s = execute([prog.path, "Hello", "World"]); + assert(s.status == 123); + assert(s.output.stripRight() == "HelloWorld"); +} + +@safe unittest +{ + import std.string; + auto r1 = executeShell("echo foo"); + assert(r1.status == 0); + assert(r1.output.chomp() == "foo"); + auto r2 = executeShell("echo bar 1>&2"); + assert(r2.status == 0); + assert(r2.output.chomp().stripRight() == "bar"); + auto r3 = executeShell("exit 123"); + assert(r3.status == 123); + assert(r3.output.empty); +} + +@safe unittest +{ + import std.typecons : Tuple; + void foo() //Just test the compilation + { + auto ret1 = execute(["dummy", "arg"]); + auto ret2 = executeShell("dummy arg"); + static assert(is(typeof(ret1) == typeof(ret2))); + + Tuple!(int, string) ret3 = execute(["dummy", "arg"]); + } +} + +/// An exception that signals a problem with starting or waiting for a process. +class ProcessException : Exception +{ + import std.exception : basicExceptionCtors; + mixin basicExceptionCtors; + + // Creates a new ProcessException based on errno. + static ProcessException newFromErrno(string customMsg = null, + string file = __FILE__, + size_t line = __LINE__) + { + import core.stdc.errno : errno; + return newFromErrno(errno, customMsg, file, line); + } + + // ditto, but error number is provided by caller + static ProcessException newFromErrno(int error, + string customMsg = null, + string file = __FILE__, + size_t line = __LINE__) + { + import std.exception : errnoString; + auto errnoMsg = errnoString(error); + auto msg = customMsg.empty ? errnoMsg + : customMsg ~ " (" ~ errnoMsg ~ ')'; + return new ProcessException(msg, file, line); + } + + // Creates a new ProcessException based on GetLastError() (Windows only). + version (Windows) + static ProcessException newFromLastError(string customMsg = null, + string file = __FILE__, + size_t line = __LINE__) + { + auto lastMsg = sysErrorString(GetLastError()); + auto msg = customMsg.empty ? lastMsg + : customMsg ~ " (" ~ lastMsg ~ ')'; + return new ProcessException(msg, file, line); + } +} + + +/** +Determines the path to the current user's preferred command interpreter. + +On Windows, this function returns the contents of the COMSPEC environment +variable, if it exists. Otherwise, it returns the result of $(LREF nativeShell). + +On POSIX, $(D userShell) returns the contents of the SHELL environment +variable, if it exists and is non-empty. Otherwise, it returns the result of +$(LREF nativeShell). +*/ +@property string userShell() @safe +{ + version (Windows) return environment.get("COMSPEC", nativeShell); + else version (Posix) return environment.get("SHELL", nativeShell); +} + +/** +The platform-specific native shell path. + +This function returns $(D "cmd.exe") on Windows, $(D "/bin/sh") on POSIX, and +$(D "/system/bin/sh") on Android. +*/ +@property string nativeShell() @safe @nogc pure nothrow +{ + version (Windows) return "cmd.exe"; + else version (Android) return "/system/bin/sh"; + else version (Posix) return "/bin/sh"; +} + +// A command-line switch that indicates to the shell that it should +// interpret the following argument as a command to be executed. +version (Posix) private immutable string shellSwitch = "-c"; +version (Windows) private immutable string shellSwitch = "/C"; + + +/** + * Returns the process ID of the current process, + * which is guaranteed to be unique on the system. + * + * Example: + * --- + * writefln("Current process ID: %d", thisProcessID); + * --- + */ +@property int thisProcessID() @trusted nothrow //TODO: @safe +{ + version (Windows) return GetCurrentProcessId(); + else version (Posix) return core.sys.posix.unistd.getpid(); +} + + +/** + * Returns the process ID of the current thread, + * which is guaranteed to be unique within the current process. + * + * Returns: + * A $(REF ThreadID, core,thread) value for the calling thread. + * + * Example: + * --- + * writefln("Current thread ID: %s", thisThreadID); + * --- + */ +@property ThreadID thisThreadID() @trusted nothrow //TODO: @safe +{ + version (Windows) + return GetCurrentThreadId(); + else + version (Posix) + { + import core.sys.posix.pthread : pthread_self; + return pthread_self(); + } +} + + +@system unittest +{ + int pidA, pidB; + ThreadID tidA, tidB; + pidA = thisProcessID; + tidA = thisThreadID; + + import core.thread; + auto t = new Thread({ + pidB = thisProcessID; + tidB = thisThreadID; + }); + t.start(); + t.join(); + + assert(pidA == pidB); + assert(tidA != tidB); +} + + +// Unittest support code: TestScript takes a string that contains a +// shell script for the current platform, and writes it to a temporary +// file. On Windows the file name gets a .cmd extension, while on +// POSIX its executable permission bit is set. The file is +// automatically deleted when the object goes out of scope. +version (unittest) +private struct TestScript +{ + this(string code) @system + { + // @system due to chmod + import std.ascii : newline; + import std.file : write; + version (Windows) + { + auto ext = ".cmd"; + auto firstLine = "@echo off"; + } + else version (Posix) + { + auto ext = ""; + auto firstLine = "#!" ~ nativeShell; + } + path = uniqueTempPath()~ext; + write(path, firstLine ~ newline ~ code ~ newline); + version (Posix) + { + import core.sys.posix.sys.stat : chmod; + chmod(path.tempCString(), octal!777); + } + } + + ~this() + { + import std.file : remove, exists; + if (!path.empty && exists(path)) + { + try { remove(path); } + catch (Exception e) + { + debug std.stdio.stderr.writeln(e.msg); + } + } + } + + string path; +} + +version (unittest) +private string uniqueTempPath() @safe +{ + import std.file : tempDir; + import std.path : buildPath; + import std.uuid : randomUUID; + // Path should contain spaces to test escaping whitespace + return buildPath(tempDir(), "std.process temporary file " ~ + randomUUID().toString()); +} + + +// ============================================================================= +// Functions for shell command quoting/escaping. +// ============================================================================= + + +/* + Command line arguments exist in three forms: + 1) string or char* array, as received by main. + Also used internally on POSIX systems. + 2) Command line string, as used in Windows' + CreateProcess and CommandLineToArgvW functions. + A specific quoting and escaping algorithm is used + to distinguish individual arguments. + 3) Shell command string, as written at a shell prompt + or passed to cmd /C - this one may contain shell + control characters, e.g. > or | for redirection / + piping - thus, yet another layer of escaping is + used to distinguish them from program arguments. + + Except for escapeWindowsArgument, the intermediary + format (2) is hidden away from the user in this module. +*/ + +/** +Escapes an argv-style argument array to be used with $(LREF spawnShell), +$(LREF pipeShell) or $(LREF executeShell). +--- +string url = "http://dlang.org/"; +executeShell(escapeShellCommand("wget", url, "-O", "dlang-index.html")); +--- + +Concatenate multiple $(D escapeShellCommand) and +$(LREF escapeShellFileName) results to use shell redirection or +piping operators. +--- +executeShell( + escapeShellCommand("curl", "http://dlang.org/download.html") ~ + "|" ~ + escapeShellCommand("grep", "-o", `http://\S*\.zip`) ~ + ">" ~ + escapeShellFileName("D download links.txt")); +--- + +Throws: +$(OBJECTREF Exception) if any part of the command line contains unescapable +characters (NUL on all platforms, as well as CR and LF on Windows). +*/ +string escapeShellCommand(in char[][] args...) @safe pure +{ + if (args.empty) + return null; + version (Windows) + { + // Do not ^-escape the first argument (the program path), + // as the shell parses it differently from parameters. + // ^-escaping a program path that contains spaces will fail. + string result = escapeShellFileName(args[0]); + if (args.length > 1) + { + result ~= " " ~ escapeShellCommandString( + escapeShellArguments(args[1..$])); + } + return result; + } + version (Posix) + { + return escapeShellCommandString(escapeShellArguments(args)); + } +} + +@safe unittest +{ + // This is a simple unit test without any special requirements, + // in addition to the unittest_burnin one below which requires + // special preparation. + + struct TestVector { string[] args; string windows, posix; } + TestVector[] tests = + [ + { + args : ["foo bar"], + windows : `"foo bar"`, + posix : `'foo bar'` + }, + { + args : ["foo bar", "hello"], + windows : `"foo bar" hello`, + posix : `'foo bar' 'hello'` + }, + { + args : ["foo bar", "hello world"], + windows : `"foo bar" ^"hello world^"`, + posix : `'foo bar' 'hello world'` + }, + { + args : ["foo bar", "hello", "world"], + windows : `"foo bar" hello world`, + posix : `'foo bar' 'hello' 'world'` + }, + { + args : ["foo bar", `'"^\`], + windows : `"foo bar" ^"'\^"^^\\^"`, + posix : `'foo bar' ''\''"^\'` + }, + ]; + + foreach (test; tests) + version (Windows) + assert(escapeShellCommand(test.args) == test.windows); + else + assert(escapeShellCommand(test.args) == test.posix ); +} + +private string escapeShellCommandString(string command) @safe pure +{ + version (Windows) + return escapeWindowsShellCommand(command); + else + return command; +} + +private string escapeWindowsShellCommand(in char[] command) @safe pure +{ + import std.array : appender; + auto result = appender!string(); + result.reserve(command.length); + + foreach (c; command) + switch (c) + { + case '\0': + throw new Exception("Cannot put NUL in command line"); + case '\r': + case '\n': + throw new Exception("CR/LF are not escapable"); + case '\x01': .. case '\x09': + case '\x0B': .. case '\x0C': + case '\x0E': .. case '\x1F': + case '"': + case '^': + case '&': + case '<': + case '>': + case '|': + result.put('^'); + goto default; + default: + result.put(c); + } + return result.data; +} + +private string escapeShellArguments(in char[][] args...) + @trusted pure nothrow +{ + import std.exception : assumeUnique; + char[] buf; + + @safe nothrow + char[] allocator(size_t size) + { + if (buf.length == 0) + return buf = new char[size]; + else + { + auto p = buf.length; + buf.length = buf.length + 1 + size; + buf[p++] = ' '; + return buf[p .. p+size]; + } + } + + foreach (arg; args) + escapeShellArgument!allocator(arg); + return assumeUnique(buf); +} + +private auto escapeShellArgument(alias allocator)(in char[] arg) @safe nothrow +{ + // The unittest for this function requires special + // preparation - see below. + + version (Windows) + return escapeWindowsArgumentImpl!allocator(arg); + else + return escapePosixArgumentImpl!allocator(arg); +} + +/** +Quotes a command-line argument in a manner conforming to the behavior of +$(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx, +CommandLineToArgvW). +*/ +string escapeWindowsArgument(in char[] arg) @trusted pure nothrow +{ + // Rationale for leaving this function as public: + // this algorithm of escaping paths is also used in other software, + // e.g. DMD's response files. + import std.exception : assumeUnique; + auto buf = escapeWindowsArgumentImpl!charAllocator(arg); + return assumeUnique(buf); +} + + +private char[] charAllocator(size_t size) @safe pure nothrow +{ + return new char[size]; +} + + +private char[] escapeWindowsArgumentImpl(alias allocator)(in char[] arg) + @safe nothrow +if (is(typeof(allocator(size_t.init)[0] = char.init))) +{ + // References: + // * http://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx + // * http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx + + // Check if the string needs to be escaped, + // and calculate the total string size. + + // Trailing backslashes must be escaped + bool escaping = true; + bool needEscape = false; + // Result size = input size + 2 for surrounding quotes + 1 for the + // backslash for each escaped character. + size_t size = 1 + arg.length + 1; + + foreach_reverse (char c; arg) + { + if (c == '"') + { + needEscape = true; + escaping = true; + size++; + } + else + if (c == '\\') + { + if (escaping) + size++; + } + else + { + if (c == ' ' || c == '\t') + needEscape = true; + escaping = false; + } + } + + import std.ascii : isDigit; + // Empty arguments need to be specified as "" + if (!arg.length) + needEscape = true; + else + // Arguments ending with digits need to be escaped, + // to disambiguate with 1>file redirection syntax + if (isDigit(arg[$-1])) + needEscape = true; + + if (!needEscape) + return allocator(arg.length)[] = arg; + + // Construct result string. + + auto buf = allocator(size); + size_t p = size; + buf[--p] = '"'; + escaping = true; + foreach_reverse (char c; arg) + { + if (c == '"') + escaping = true; + else + if (c != '\\') + escaping = false; + + buf[--p] = c; + if (escaping) + buf[--p] = '\\'; + } + buf[--p] = '"'; + assert(p == 0); + + return buf; +} + +version (Windows) version (unittest) +{ + import core.stdc.stddef; + import core.stdc.wchar_ : wcslen; + import core.sys.windows.shellapi : CommandLineToArgvW; + import core.sys.windows.windows; + import std.array; + + string[] parseCommandLine(string line) + { + import std.algorithm.iteration : map; + import std.array : array; + LPWSTR lpCommandLine = (to!(wchar[])(line) ~ "\0"w).ptr; + int numArgs; + LPWSTR* args = CommandLineToArgvW(lpCommandLine, &numArgs); + scope(exit) LocalFree(args); + return args[0 .. numArgs] + .map!(arg => to!string(arg[0 .. wcslen(arg)])) + .array(); + } + + @system unittest + { + string[] testStrings = [ + `Hello`, + `Hello, world`, + `Hello, "world"`, + `C:\`, + `C:\dmd`, + `C:\Program Files\`, + ]; + + enum CHARS = `_x\" *&^` ~ "\t"; // _ is placeholder for nothing + foreach (c1; CHARS) + foreach (c2; CHARS) + foreach (c3; CHARS) + foreach (c4; CHARS) + testStrings ~= [c1, c2, c3, c4].replace("_", ""); + + foreach (s; testStrings) + { + auto q = escapeWindowsArgument(s); + auto args = parseCommandLine("Dummy.exe " ~ q); + assert(args.length == 2, s ~ " => " ~ q ~ " #" ~ text(args.length-1)); + assert(args[1] == s, s ~ " => " ~ q ~ " => " ~ args[1]); + } + } +} + +private string escapePosixArgument(in char[] arg) @trusted pure nothrow +{ + import std.exception : assumeUnique; + auto buf = escapePosixArgumentImpl!charAllocator(arg); + return assumeUnique(buf); +} + +private char[] escapePosixArgumentImpl(alias allocator)(in char[] arg) + @safe nothrow +if (is(typeof(allocator(size_t.init)[0] = char.init))) +{ + // '\'' means: close quoted part of argument, append an escaped + // single quote, and reopen quotes + + // Below code is equivalent to: + // return `'` ~ std.array.replace(arg, `'`, `'\''`) ~ `'`; + + size_t size = 1 + arg.length + 1; + foreach (char c; arg) + if (c == '\'') + size += 3; + + auto buf = allocator(size); + size_t p = 0; + buf[p++] = '\''; + foreach (char c; arg) + if (c == '\'') + { + buf[p .. p+4] = `'\''`; + p += 4; + } + else + buf[p++] = c; + buf[p++] = '\''; + assert(p == size); + + return buf; +} + +/** +Escapes a filename to be used for shell redirection with $(LREF spawnShell), +$(LREF pipeShell) or $(LREF executeShell). +*/ +string escapeShellFileName(in char[] fileName) @trusted pure nothrow +{ + // The unittest for this function requires special + // preparation - see below. + + version (Windows) + { + // If a file starts with &, it can cause cmd.exe to misinterpret + // the file name as the stream redirection syntax: + // command > "&foo.txt" + // gets interpreted as + // command >&foo.txt + // Prepend .\ to disambiguate. + + if (fileName.length && fileName[0] == '&') + return cast(string)(`".\` ~ fileName ~ '"'); + + return cast(string)('"' ~ fileName ~ '"'); + } + else + return escapePosixArgument(fileName); +} + +// Loop generating strings with random characters +//version = unittest_burnin; + +version (unittest_burnin) +@system unittest +{ + // There are no readily-available commands on all platforms suitable + // for properly testing command escaping. The behavior of CMD's "echo" + // built-in differs from the POSIX program, and Windows ports of POSIX + // environments (Cygwin, msys, gnuwin32) may interfere with their own + // "echo" ports. + + // To run this unit test, create std_process_unittest_helper.d with the + // following content and compile it: + // import std.stdio, std.array; void main(string[] args) { write(args.join("\0")); } + // Then, test this module with: + // rdmd --main -unittest -version=unittest_burnin process.d + + auto helper = absolutePath("std_process_unittest_helper"); + assert(executeShell(helper ~ " hello").output.split("\0")[1..$] == ["hello"], "Helper malfunction"); + + void test(string[] s, string fn) + { + string e; + string[] g; + + e = escapeShellCommand(helper ~ s); + { + scope(failure) writefln("executeShell() failed.\nExpected:\t%s\nEncoded:\t%s", s, [e]); + auto result = executeShell(e); + assert(result.status == 0, "std_process_unittest_helper failed"); + g = result.output.split("\0")[1..$]; + } + assert(s == g, format("executeShell() test failed.\nExpected:\t%s\nGot:\t\t%s\nEncoded:\t%s", s, g, [e])); + + e = escapeShellCommand(helper ~ s) ~ ">" ~ escapeShellFileName(fn); + { + scope(failure) writefln( + "executeShell() with redirect failed.\nExpected:\t%s\nFilename:\t%s\nEncoded:\t%s", s, [fn], [e]); + auto result = executeShell(e); + assert(result.status == 0, "std_process_unittest_helper failed"); + assert(!result.output.length, "No output expected, got:\n" ~ result.output); + g = readText(fn).split("\0")[1..$]; + } + remove(fn); + assert(s == g, + format("executeShell() with redirect test failed.\nExpected:\t%s\nGot:\t\t%s\nEncoded:\t%s", s, g, [e])); + } + + while (true) + { + string[] args; + foreach (n; 0 .. uniform(1, 4)) + { + string arg; + foreach (l; 0 .. uniform(0, 10)) + { + dchar c; + while (true) + { + version (Windows) + { + // As long as DMD's system() uses CreateProcessA, + // we can't reliably pass Unicode + c = uniform(0, 128); + } + else + c = uniform!ubyte(); + + if (c == 0) + continue; // argv-strings are zero-terminated + version (Windows) + if (c == '\r' || c == '\n') + continue; // newlines are unescapable on Windows + break; + } + arg ~= c; + } + args ~= arg; + } + + // generate filename + string fn; + foreach (l; 0 .. uniform(1, 10)) + { + dchar c; + while (true) + { + version (Windows) + c = uniform(0, 128); // as above + else + c = uniform!ubyte(); + + if (c == 0 || c == '/') + continue; // NUL and / are the only characters + // forbidden in POSIX filenames + version (Windows) + if (c < '\x20' || c == '<' || c == '>' || c == ':' || + c == '"' || c == '\\' || c == '|' || c == '?' || c == '*') + continue; // http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx + break; + } + + fn ~= c; + } + fn = fn[0..$/2] ~ "_testfile_" ~ fn[$/2..$]; + + test(args, fn); + } +} + + +// ============================================================================= +// Environment variable manipulation. +// ============================================================================= + + +/** +Manipulates _environment variables using an associative-array-like +interface. + +This class contains only static methods, and cannot be instantiated. +See below for examples of use. +*/ +abstract final class environment +{ +static: + /** + Retrieves the value of the environment variable with the given $(D name). + --- + auto path = environment["PATH"]; + --- + + Throws: + $(OBJECTREF Exception) if the environment variable does not exist, + or $(REF UTFException, std,utf) if the variable contains invalid UTF-16 + characters (Windows only). + + See_also: + $(LREF environment.get), which doesn't throw on failure. + */ + string opIndex(in char[] name) @safe + { + import std.exception : enforce; + string value; + enforce(getImpl(name, value), "Environment variable not found: "~name); + return value; + } + + /** + Retrieves the value of the environment variable with the given $(D name), + or a default value if the variable doesn't exist. + + Unlike $(LREF environment.opIndex), this function never throws. + --- + auto sh = environment.get("SHELL", "/bin/sh"); + --- + This function is also useful in checking for the existence of an + environment variable. + --- + auto myVar = environment.get("MYVAR"); + if (myVar is null) + { + // Environment variable doesn't exist. + // Note that we have to use 'is' for the comparison, since + // myVar == null is also true if the variable exists but is + // empty. + } + --- + + Throws: + $(REF UTFException, std,utf) if the variable contains invalid UTF-16 + characters (Windows only). + */ + string get(in char[] name, string defaultValue = null) @safe + { + string value; + auto found = getImpl(name, value); + return found ? value : defaultValue; + } + + /** + Assigns the given $(D value) to the environment variable with the given + $(D name). + If $(D value) is null the variable is removed from environment. + + If the variable does not exist, it will be created. If it already exists, + it will be overwritten. + --- + environment["foo"] = "bar"; + --- + + Throws: + $(OBJECTREF Exception) if the environment variable could not be added + (e.g. if the name is invalid). + + Note: + On some platforms, modifying environment variables may not be allowed in + multi-threaded programs. See e.g. + $(LINK2 https://www.gnu.org/software/libc/manual/html_node/Environment-Access.html#Environment-Access, glibc). + */ + inout(char)[] opIndexAssign(inout char[] value, in char[] name) @trusted + { + version (Posix) + { + import std.exception : enforce, errnoEnforce; + if (value is null) + { + remove(name); + return value; + } + if (core.sys.posix.stdlib.setenv(name.tempCString(), value.tempCString(), 1) != -1) + { + return value; + } + // The default errno error message is very uninformative + // in the most common case, so we handle it manually. + enforce(errno != EINVAL, + "Invalid environment variable name: '"~name~"'"); + errnoEnforce(false, + "Failed to add environment variable"); + assert(0); + } + else version (Windows) + { + import std.exception : enforce; + enforce( + SetEnvironmentVariableW(name.tempCStringW(), value.tempCStringW()), + sysErrorString(GetLastError()) + ); + return value; + } + else static assert(0); + } + + /** + Removes the environment variable with the given $(D name). + + If the variable isn't in the environment, this function returns + successfully without doing anything. + + Note: + On some platforms, modifying environment variables may not be allowed in + multi-threaded programs. See e.g. + $(LINK2 https://www.gnu.org/software/libc/manual/html_node/Environment-Access.html#Environment-Access, glibc). + */ + void remove(in char[] name) @trusted nothrow @nogc // TODO: @safe + { + version (Windows) SetEnvironmentVariableW(name.tempCStringW(), null); + else version (Posix) core.sys.posix.stdlib.unsetenv(name.tempCString()); + else static assert(0); + } + + /** + Identify whether a variable is defined in the environment. + + Because it doesn't return the value, this function is cheaper than `get`. + However, if you do need the value as well, you should just check the + return of `get` for `null` instead of using this function first. + + Example: + ------------- + // good usage + if ("MY_ENV_FLAG" in environment) + doSomething(); + + // bad usage + if ("MY_ENV_VAR" in environment) + doSomething(environment["MY_ENV_VAR"]); + + // do this instead + if (auto var = environment.get("MY_ENV_VAR")) + doSomething(var); + ------------- + */ + bool opBinaryRight(string op : "in")(in char[] name) @trusted + { + version (Posix) + return core.sys.posix.stdlib.getenv(name.tempCString()) !is null; + else version (Windows) + { + SetLastError(NO_ERROR); + if (GetEnvironmentVariableW(name.tempCStringW, null, 0) > 0) + return true; + immutable err = GetLastError(); + if (err == ERROR_ENVVAR_NOT_FOUND) + return false; + // some other windows error. Might actually be NO_ERROR, because + // GetEnvironmentVariable doesn't specify whether it sets on all + // failures + throw new WindowsException(err); + } + else static assert(0); + } + + /** + Copies all environment variables into an associative array. + + Windows_specific: + While Windows environment variable names are case insensitive, D's + built-in associative arrays are not. This function will store all + variable names in uppercase (e.g. $(D PATH)). + + Throws: + $(OBJECTREF Exception) if the environment variables could not + be retrieved (Windows only). + */ + string[string] toAA() @trusted + { + import std.conv : to; + string[string] aa; + version (Posix) + { + auto environ = getEnvironPtr; + for (int i=0; environ[i] != null; ++i) + { + import std.string : indexOf; + + immutable varDef = to!string(environ[i]); + immutable eq = indexOf(varDef, '='); + assert(eq >= 0); + + immutable name = varDef[0 .. eq]; + immutable value = varDef[eq+1 .. $]; + + // In POSIX, environment variables may be defined more + // than once. This is a security issue, which we avoid + // by checking whether the key already exists in the array. + // For more info: + // http://www.dwheeler.com/secure-programs/Secure-Programs-HOWTO/environment-variables.html + if (name !in aa) aa[name] = value; + } + } + else version (Windows) + { + import std.exception : enforce; + import std.uni : toUpper; + auto envBlock = GetEnvironmentStringsW(); + enforce(envBlock, "Failed to retrieve environment variables."); + scope(exit) FreeEnvironmentStringsW(envBlock); + + for (int i=0; envBlock[i] != '\0'; ++i) + { + auto start = i; + while (envBlock[i] != '=') ++i; + immutable name = toUTF8(toUpper(envBlock[start .. i])); + + start = i+1; + while (envBlock[i] != '\0') ++i; + + // Ignore variables with empty names. These are used internally + // by Windows to keep track of each drive's individual current + // directory. + if (!name.length) + continue; + + // Just like in POSIX systems, environment variables may be + // defined more than once in an environment block on Windows, + // and it is just as much of a security issue there. Moreso, + // in fact, due to the case insensensitivity of variable names, + // which is not handled correctly by all programs. + auto val = toUTF8(envBlock[start .. i]); + if (name !in aa) aa[name] = val is null ? "" : val; + } + } + else static assert(0); + return aa; + } + +private: + // Retrieves the environment variable, returns false on failure. + bool getImpl(in char[] name, out string value) @trusted + { + version (Windows) + { + // first we ask windows how long the environment variable is, + // then we try to read it in to a buffer of that length. Lots + // of error conditions because the windows API is nasty. + + import std.conv : to; + const namezTmp = name.tempCStringW(); + WCHAR[] buf; + + // clear error because GetEnvironmentVariable only says it sets it + // if the environment variable is missing, not on other errors. + SetLastError(NO_ERROR); + // len includes terminating null + immutable len = GetEnvironmentVariableW(namezTmp, null, 0); + if (len == 0) + { + immutable err = GetLastError(); + if (err == ERROR_ENVVAR_NOT_FOUND) + return false; + // some other windows error. Might actually be NO_ERROR, because + // GetEnvironmentVariable doesn't specify whether it sets on all + // failures + throw new WindowsException(err); + } + if (len == 1) + { + value = ""; + return true; + } + buf.length = len; + + while (true) + { + // lenRead is either the number of bytes read w/o null - if buf was long enough - or + // the number of bytes necessary *including* null if buf wasn't long enough + immutable lenRead = GetEnvironmentVariableW(namezTmp, buf.ptr, to!DWORD(buf.length)); + if (lenRead == 0) + { + immutable err = GetLastError(); + if (err == NO_ERROR) // sucessfully read a 0-length variable + { + value = ""; + return true; + } + if (err == ERROR_ENVVAR_NOT_FOUND) // variable didn't exist + return false; + // some other windows error + throw new WindowsException(err); + } + assert(lenRead != buf.length, "impossible according to msft docs"); + if (lenRead < buf.length) // the buffer was long enough + { + value = toUTF8(buf[0 .. lenRead]); + return true; + } + // resize and go around again, because the environment variable grew + buf.length = lenRead; + } + } + else version (Posix) + { + const vz = core.sys.posix.stdlib.getenv(name.tempCString()); + if (vz == null) return false; + auto v = vz[0 .. strlen(vz)]; + + // Cache the last call's result. + static string lastResult; + if (v.empty) + { + // Return non-null array for blank result to distinguish from + // not-present result. + lastResult = ""; + } + else if (v != lastResult) + { + lastResult = v.idup; + } + value = lastResult; + return true; + } + else static assert(0); + } +} + +@safe unittest +{ + import std.exception : assertThrown; + // New variable + environment["std_process"] = "foo"; + assert(environment["std_process"] == "foo"); + assert("std_process" in environment); + + // Set variable again (also tests length 1 case) + environment["std_process"] = "b"; + assert(environment["std_process"] == "b"); + assert("std_process" in environment); + + // Remove variable + environment.remove("std_process"); + assert("std_process" !in environment); + + // Remove again, should succeed + environment.remove("std_process"); + assert("std_process" !in environment); + + // Throw on not found. + assertThrown(environment["std_process"]); + + // get() without default value + assert(environment.get("std_process") is null); + + // get() with default value + assert(environment.get("std_process", "baz") == "baz"); + + // get() on an empty (but present) value + environment["std_process"] = ""; + auto res = environment.get("std_process"); + assert(res !is null); + assert(res == ""); + assert("std_process" in environment); + + // Important to do the following round-trip after the previous test + // because it tests toAA with an empty var + + // Convert to associative array + auto aa = environment.toAA(); + assert(aa.length > 0); + foreach (n, v; aa) + { + // Wine has some bugs related to environment variables: + // - Wine allows the existence of an env. variable with the name + // "\0", but GetEnvironmentVariable refuses to retrieve it. + // As of 2.067 we filter these out anyway (see comment in toAA). + + assert(v == environment[n]); + } + + // ... and back again. + foreach (n, v; aa) + environment[n] = v; + + // Complete the roundtrip + auto aa2 = environment.toAA(); + import std.conv : text; + assert(aa == aa2, text(aa, " != ", aa2)); + assert("std_process" in environment); + + // Setting null must have the same effect as remove + environment["std_process"] = null; + assert("std_process" !in environment); +} + + + + +// ============================================================================= +// Everything below this line was part of the old std.process, and most of +// it will be deprecated and removed. +// ============================================================================= + + +/* +Copyright: Copyright Digital Mars 2007 - 2009. +License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). +Authors: $(HTTP digitalmars.com, Walter Bright), + $(HTTP erdani.org, Andrei Alexandrescu), + $(HTTP thecybershadow.net, Vladimir Panteleev) +Source: $(PHOBOSSRC std/_process.d) +*/ +/* + Copyright Digital Mars 2007 - 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) +*/ + + +import core.stdc.errno; +import core.stdc.stdlib; +import core.stdc.string; +import core.thread; + +version (Windows) +{ + import std.file, std.format, std.random; +} +version (Posix) +{ + import core.sys.posix.stdlib; +} +version (unittest) +{ + import std.conv, std.file, std.random; +} + + +private void toAStringz(in string[] a, const(char)**az) +{ + import std.string : toStringz; + foreach (string s; a) + { + *az++ = toStringz(s); + } + *az = null; +} + + +/* ========================================================== */ + +//version (Windows) +//{ +// int spawnvp(int mode, string pathname, string[] argv) +// { +// char** argv_ = cast(char**) core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length)); +// scope(exit) core.stdc.stdlib.free(argv_); +// +// toAStringz(argv, argv_); +// +// return spawnvp(mode, pathname.tempCString(), argv_); +// } +//} + +// Incorporating idea (for spawnvp() on Posix) from Dave Fladebo + +enum { _P_WAIT, _P_NOWAIT, _P_OVERLAY } +version (Windows) extern(C) int spawnvp(int, in char *, in char **); +alias P_WAIT = _P_WAIT; +alias P_NOWAIT = _P_NOWAIT; + +/* ========================================================== */ + +version (StdDdoc) +{ + /** + Replaces the current process by executing a command, $(D pathname), with + the arguments in $(D argv). + + $(BLUE This functions is Posix-Only.) + + Typically, the first element of $(D argv) is + the command being executed, i.e. $(D argv[0] == pathname). The 'p' + versions of $(D exec) search the PATH environment variable for $(D + pathname). The 'e' versions additionally take the new process' + environment variables as an array of strings of the form key=value. + + Does not return on success (the current process will have been + replaced). Returns -1 on failure with no indication of the + underlying error. + + Windows_specific: + These functions are only supported on POSIX platforms, as the Windows + operating systems do not provide the ability to overwrite the current + process image with another. In single-threaded programs it is possible + to approximate the effect of $(D execv*) by using $(LREF spawnProcess) + and terminating the current process once the child process has returned. + For example: + --- + auto commandLine = [ "program", "arg1", "arg2" ]; + version (Posix) + { + execv(commandLine[0], commandLine); + throw new Exception("Failed to execute program"); + } + else version (Windows) + { + import core.stdc.stdlib : _exit; + _exit(wait(spawnProcess(commandLine))); + } + --- + This is, however, NOT equivalent to POSIX' $(D execv*). For one thing, the + executed program is started as a separate process, with all this entails. + Secondly, in a multithreaded program, other threads will continue to do + work while the current thread is waiting for the child process to complete. + + A better option may sometimes be to terminate the current program immediately + after spawning the child process. This is the behaviour exhibited by the + $(LINK2 http://msdn.microsoft.com/en-us/library/431x4c1w.aspx,$(D __exec)) + functions in Microsoft's C runtime library, and it is how D's now-deprecated + Windows $(D execv*) functions work. Example: + --- + auto commandLine = [ "program", "arg1", "arg2" ]; + version (Posix) + { + execv(commandLine[0], commandLine); + throw new Exception("Failed to execute program"); + } + else version (Windows) + { + spawnProcess(commandLine); + import core.stdc.stdlib : _exit; + _exit(0); + } + --- + */ + int execv(in string pathname, in string[] argv); + ///ditto + int execve(in string pathname, in string[] argv, in string[] envp); + /// ditto + int execvp(in string pathname, in string[] argv); + /// ditto + int execvpe(in string pathname, in string[] argv, in string[] envp); +} +else version (Posix) +{ + int execv(in string pathname, in string[] argv) + { + return execv_(pathname, argv); + } + int execve(in string pathname, in string[] argv, in string[] envp) + { + return execve_(pathname, argv, envp); + } + int execvp(in string pathname, in string[] argv) + { + return execvp_(pathname, argv); + } + int execvpe(in string pathname, in string[] argv, in string[] envp) + { + return execvpe_(pathname, argv, envp); + } +} + +// Move these C declarations to druntime if we decide to keep the D wrappers +extern(C) +{ + int execv(in char *, in char **); + int execve(in char *, in char **, in char **); + int execvp(in char *, in char **); + version (Windows) int execvpe(in char *, in char **, in char **); +} + +private int execv_(in string pathname, in string[] argv) +{ + auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length)); + scope(exit) core.stdc.stdlib.free(argv_); + + toAStringz(argv, argv_); + + return execv(pathname.tempCString(), argv_); +} + +private int execve_(in string pathname, in string[] argv, in string[] envp) +{ + auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length)); + scope(exit) core.stdc.stdlib.free(argv_); + auto envp_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + envp.length)); + scope(exit) core.stdc.stdlib.free(envp_); + + toAStringz(argv, argv_); + toAStringz(envp, envp_); + + return execve(pathname.tempCString(), argv_, envp_); +} + +private int execvp_(in string pathname, in string[] argv) +{ + auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length)); + scope(exit) core.stdc.stdlib.free(argv_); + + toAStringz(argv, argv_); + + return execvp(pathname.tempCString(), argv_); +} + +private int execvpe_(in string pathname, in string[] argv, in string[] envp) +{ +version (Posix) +{ + import std.array : split; + import std.conv : to; + // Is pathname rooted? + if (pathname[0] == '/') + { + // Yes, so just call execve() + return execve(pathname, argv, envp); + } + else + { + // No, so must traverse PATHs, looking for first match + string[] envPaths = split( + to!string(core.stdc.stdlib.getenv("PATH")), ":"); + int iRet = 0; + + // Note: if any call to execve() succeeds, this process will cease + // execution, so there's no need to check the execve() result through + // the loop. + + foreach (string pathDir; envPaths) + { + string composite = cast(string) (pathDir ~ "/" ~ pathname); + + iRet = execve(composite, argv, envp); + } + if (0 != iRet) + { + iRet = execve(pathname, argv, envp); + } + + return iRet; + } +} +else version (Windows) +{ + auto argv_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + argv.length)); + scope(exit) core.stdc.stdlib.free(argv_); + auto envp_ = cast(const(char)**)core.stdc.stdlib.malloc((char*).sizeof * (1 + envp.length)); + scope(exit) core.stdc.stdlib.free(envp_); + + toAStringz(argv, argv_); + toAStringz(envp, envp_); + + return execvpe(pathname.tempCString(), argv_, envp_); +} +else +{ + static assert(0); +} // version +} + +version (StdDdoc) +{ + /**************************************** + * Start up the browser and set it to viewing the page at url. + */ + void browse(const(char)[] url); +} +else +version (Windows) +{ + import core.sys.windows.windows; + + version (GNU) {} + else pragma(lib,"shell32.lib"); + + void browse(const(char)[] url) + { + ShellExecuteW(null, "open", url.tempCStringW(), null, null, SW_SHOWNORMAL); + } +} +else version (OSX) +{ + import core.stdc.stdio; + import core.stdc.string; + import core.sys.posix.unistd; + + void browse(const(char)[] url) nothrow @nogc + { + const(char)*[5] args; + + auto curl = url.tempCString(); + const(char)* browser = core.stdc.stdlib.getenv("BROWSER"); + if (browser) + { browser = strdup(browser); + args[0] = browser; + args[1] = curl; + args[2] = null; + } + else + { + args[0] = "open".ptr; + args[1] = curl; + args[2] = null; + } + + auto childpid = core.sys.posix.unistd.fork(); + if (childpid == 0) + { + core.sys.posix.unistd.execvp(args[0], cast(char**) args.ptr); + perror(args[0]); // failed to execute + return; + } + if (browser) + free(cast(void*) browser); + } +} +else version (Posix) +{ + import core.stdc.stdio; + import core.stdc.string; + import core.sys.posix.unistd; + + void browse(const(char)[] url) nothrow @nogc + { + const(char)*[3] args; + + const(char)* browser = core.stdc.stdlib.getenv("BROWSER"); + if (browser) + { browser = strdup(browser); + args[0] = browser; + } + else + //args[0] = "x-www-browser".ptr; // doesn't work on some systems + args[0] = "xdg-open".ptr; + + args[1] = url.tempCString(); + args[2] = null; + + auto childpid = core.sys.posix.unistd.fork(); + if (childpid == 0) + { + core.sys.posix.unistd.execvp(args[0], cast(char**) args.ptr); + perror(args[0]); // failed to execute + return; + } + if (browser) + free(cast(void*) browser); + } +} +else + static assert(0, "os not supported"); diff --git a/libphobos/src/std/random.d b/libphobos/src/std/random.d new file mode 100644 index 0000000..b3116e18 --- /dev/null +++ b/libphobos/src/std/random.d @@ -0,0 +1,3344 @@ +// Written in the D programming language. + +/** +Facilities for random number generation. + +$(RED Disclaimer:) The _random number generators and API provided in this +module are not designed to be cryptographically secure, and are therefore +unsuitable for cryptographic or security-related purposes such as generating +authentication tokens or network sequence numbers. For such needs, please use a +reputable cryptographic library instead. + +The new-style generator objects hold their own state so they are +immune of threading issues. The generators feature a number of +well-known and well-documented methods of generating random +numbers. An overall fast and reliable means to generate random numbers +is the $(D_PARAM Mt19937) generator, which derives its name from +"$(LINK2 https://en.wikipedia.org/wiki/Mersenne_Twister, Mersenne Twister) +with a period of 2 to the power of +19937". In memory-constrained situations, +$(LINK2 https://en.wikipedia.org/wiki/Linear_congruential_generator, +linear congruential generators) such as $(D MinstdRand0) and $(D MinstdRand) might be +useful. The standard library provides an alias $(D_PARAM Random) for +whichever generator it considers the most fit for the target +environment. + +In addition to random number generators, this module features +distributions, which skew a generator's output statistical +distribution in various ways. So far the uniform distribution for +integers and real numbers have been implemented. + +Source: $(PHOBOSSRC std/_random.d) + +Macros: + +Copyright: Copyright Andrei Alexandrescu 2008 - 2009, Joseph Rushton Wakeling 2012. +License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). +Authors: $(HTTP erdani.org, Andrei Alexandrescu) + Masahiro Nakagawa (Xorshift random generator) + $(HTTP braingam.es, Joseph Rushton Wakeling) (Algorithm D for random sampling) + Ilya Yaroshenko (Mersenne Twister implementation, adapted from $(HTTPS github.com/libmir/mir-_random, mir-_random)) +Credits: The entire random number library architecture is derived from the + excellent $(HTTP open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2461.pdf, C++0X) + random number facility proposed by Jens Maurer and contributed to by + researchers at the Fermi laboratory (excluding Xorshift). +*/ +/* + Copyright Andrei Alexandrescu 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) +*/ +module std.random; + + +import std.range.primitives; +import std.traits; + +/// +@safe unittest +{ + // seed a random generator with a constant + auto rnd = Random(42); + + // Generate a uniformly-distributed integer in the range [0, 14] + // If no random generator is passed, the global `rndGen` would be used + auto i = uniform(0, 15, rnd); + assert(i == 12); + + // Generate a uniformly-distributed real in the range [0, 100) + auto r = uniform(0.0L, 100.0L, rnd); + assert(r == 79.65429843861011285); + + // Generate a 32-bit random number + auto u = uniform!uint(rnd); + assert(u == 4083286876); +} + +version (unittest) +{ + static import std.meta; + package alias PseudoRngTypes = std.meta.AliasSeq!(MinstdRand0, MinstdRand, Mt19937, Xorshift32, Xorshift64, + Xorshift96, Xorshift128, Xorshift160, Xorshift192); +} + +// Segments of the code in this file Copyright (c) 1997 by Rick Booth +// From "Inner Loops" by Rick Booth, Addison-Wesley + +// Work derived from: + +/* + A C-program for MT19937, with initialization improved 2002/1/26. + Coded by Takuji Nishimura and Makoto Matsumoto. + + Before using, initialize the state by using init_genrand(seed) + or init_by_array(init_key, key_length). + + Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The names of its contributors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Any feedback is very welcome. + http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html + email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space) +*/ + +/** + * Test if Rng is a random-number generator. The overload + * taking a ElementType also makes sure that the Rng generates + * values of that type. + * + * A random-number generator has at least the following features: + * $(UL + * $(LI it's an InputRange) + * $(LI it has a 'bool isUniformRandom' field readable in CTFE) + * ) + */ +template isUniformRNG(Rng, ElementType) +{ + enum bool isUniformRNG = isInputRange!Rng && + is(typeof(Rng.front) == ElementType) && + is(typeof( + { + static assert(Rng.isUniformRandom); //tag + })); +} + +/** + * ditto + */ +template isUniformRNG(Rng) +{ + enum bool isUniformRNG = isInputRange!Rng && + is(typeof( + { + static assert(Rng.isUniformRandom); //tag + })); +} + +/** + * Test if Rng is seedable. The overload + * taking a SeedType also makes sure that the Rng can be seeded with SeedType. + * + * A seedable random-number generator has the following additional features: + * $(UL + * $(LI it has a 'seed(ElementType)' function) + * ) + */ +template isSeedable(Rng, SeedType) +{ + enum bool isSeedable = isUniformRNG!(Rng) && + is(typeof( + { + Rng r = void; // can define a Rng object + r.seed(SeedType.init); // can seed a Rng + })); +} + +///ditto +template isSeedable(Rng) +{ + enum bool isSeedable = isUniformRNG!Rng && + is(typeof( + { + Rng r = void; // can define a Rng object + r.seed(typeof(r.front).init); // can seed a Rng + })); +} + +@safe pure nothrow unittest +{ + struct NoRng + { + @property uint front() {return 0;} + @property bool empty() {return false;} + void popFront() {} + } + static assert(!isUniformRNG!(NoRng, uint)); + static assert(!isUniformRNG!(NoRng)); + static assert(!isSeedable!(NoRng, uint)); + static assert(!isSeedable!(NoRng)); + + struct NoRng2 + { + @property uint front() {return 0;} + @property bool empty() {return false;} + void popFront() {} + + enum isUniformRandom = false; + } + static assert(!isUniformRNG!(NoRng2, uint)); + static assert(!isUniformRNG!(NoRng2)); + static assert(!isSeedable!(NoRng2, uint)); + static assert(!isSeedable!(NoRng2)); + + struct NoRng3 + { + @property bool empty() {return false;} + void popFront() {} + + enum isUniformRandom = true; + } + static assert(!isUniformRNG!(NoRng3, uint)); + static assert(!isUniformRNG!(NoRng3)); + static assert(!isSeedable!(NoRng3, uint)); + static assert(!isSeedable!(NoRng3)); + + struct validRng + { + @property uint front() {return 0;} + @property bool empty() {return false;} + void popFront() {} + + enum isUniformRandom = true; + } + static assert(isUniformRNG!(validRng, uint)); + static assert(isUniformRNG!(validRng)); + static assert(!isSeedable!(validRng, uint)); + static assert(!isSeedable!(validRng)); + + struct seedRng + { + @property uint front() {return 0;} + @property bool empty() {return false;} + void popFront() {} + void seed(uint val){} + enum isUniformRandom = true; + } + static assert(isUniformRNG!(seedRng, uint)); + static assert(isUniformRNG!(seedRng)); + static assert(isSeedable!(seedRng, uint)); + static assert(isSeedable!(seedRng)); +} + +/** +Linear Congruential generator. + */ +struct LinearCongruentialEngine(UIntType, UIntType a, UIntType c, UIntType m) +if (isUnsigned!UIntType) +{ + ///Mark this as a Rng + enum bool isUniformRandom = true; + /// Does this generator have a fixed range? ($(D_PARAM true)). + enum bool hasFixedRange = true; + /// Lowest generated value ($(D 1) if $(D c == 0), $(D 0) otherwise). + enum UIntType min = ( c == 0 ? 1 : 0 ); + /// Highest generated value ($(D modulus - 1)). + enum UIntType max = m - 1; +/** +The parameters of this distribution. The random number is $(D_PARAM x += (x * multipler + increment) % modulus). + */ + enum UIntType multiplier = a; + ///ditto + enum UIntType increment = c; + ///ditto + enum UIntType modulus = m; + + static assert(isIntegral!(UIntType)); + static assert(m == 0 || a < m); + static assert(m == 0 || c < m); + static assert(m == 0 || + (cast(ulong) a * (m-1) + c) % m == (c < a ? c - a + m : c - a)); + + // Check for maximum range + private static ulong gcd(ulong a, ulong b) @safe pure nothrow @nogc + { + while (b) + { + auto t = b; + b = a % b; + a = t; + } + return a; + } + + private static ulong primeFactorsOnly(ulong n) @safe pure nothrow @nogc + { + ulong result = 1; + ulong iter = 2; + for (; n >= iter * iter; iter += 2 - (iter == 2)) + { + if (n % iter) continue; + result *= iter; + do + { + n /= iter; + } while (n % iter == 0); + } + return result * n; + } + + @safe pure nothrow unittest + { + static assert(primeFactorsOnly(100) == 10); + //writeln(primeFactorsOnly(11)); + static assert(primeFactorsOnly(11) == 11); + static assert(primeFactorsOnly(7 * 7 * 7 * 11 * 15 * 11) == 7 * 11 * 15); + static assert(primeFactorsOnly(129 * 2) == 129 * 2); + // enum x = primeFactorsOnly(7 * 7 * 7 * 11 * 15); + // static assert(x == 7 * 11 * 15); + } + + private static bool properLinearCongruentialParameters(ulong m, + ulong a, ulong c) @safe pure nothrow @nogc + { + if (m == 0) + { + static if (is(UIntType == uint)) + { + // Assume m is uint.max + 1 + m = (1uL << 32); + } + else + { + return false; + } + } + // Bounds checking + if (a == 0 || a >= m || c >= m) return false; + // c and m are relatively prime + if (c > 0 && gcd(c, m) != 1) return false; + // a - 1 is divisible by all prime factors of m + if ((a - 1) % primeFactorsOnly(m)) return false; + // if a - 1 is multiple of 4, then m is a multiple of 4 too. + if ((a - 1) % 4 == 0 && m % 4) return false; + // Passed all tests + return true; + } + + // check here + static assert(c == 0 || properLinearCongruentialParameters(m, a, c), + "Incorrect instantiation of LinearCongruentialEngine"); + +/** +Constructs a $(D_PARAM LinearCongruentialEngine) generator seeded with +$(D x0). + */ + this(UIntType x0) @safe pure + { + seed(x0); + } + +/** + (Re)seeds the generator. +*/ + void seed(UIntType x0 = 1) @safe pure + { + static if (c == 0) + { + import std.exception : enforce; + enforce(x0, "Invalid (zero) seed for " + ~ LinearCongruentialEngine.stringof); + } + _x = modulus ? (x0 % modulus) : x0; + popFront(); + } + +/** + Advances the random sequence. +*/ + void popFront() @safe pure nothrow @nogc + { + static if (m) + { + static if (is(UIntType == uint) && m == uint.max) + { + immutable ulong + x = (cast(ulong) a * _x + c), + v = x >> 32, + w = x & uint.max; + immutable y = cast(uint)(v + w); + _x = (y < v || y == uint.max) ? (y + 1) : y; + } + else static if (is(UIntType == uint) && m == int.max) + { + immutable ulong + x = (cast(ulong) a * _x + c), + v = x >> 31, + w = x & int.max; + immutable uint y = cast(uint)(v + w); + _x = (y >= int.max) ? (y - int.max) : y; + } + else + { + _x = cast(UIntType) ((cast(ulong) a * _x + c) % m); + } + } + else + { + _x = a * _x + c; + } + } + +/** + Returns the current number in the random sequence. +*/ + @property UIntType front() const @safe pure nothrow @nogc + { + return _x; + } + +/// + @property typeof(this) save() @safe pure nothrow @nogc + { + return this; + } + +/** +Always $(D false) (random generators are infinite ranges). + */ + enum bool empty = false; + +/** + Compares against $(D_PARAM rhs) for equality. + */ + bool opEquals(ref const LinearCongruentialEngine rhs) const @safe pure nothrow @nogc + { + return _x == rhs._x; + } + + private UIntType _x = m ? (a + c) % m : (a + c); +} + +/** +Define $(D_PARAM LinearCongruentialEngine) generators with well-chosen +parameters. $(D MinstdRand0) implements Park and Miller's "minimal +standard" $(HTTP +wikipedia.org/wiki/Park%E2%80%93Miller_random_number_generator, +generator) that uses 16807 for the multiplier. $(D MinstdRand) +implements a variant that has slightly better spectral behavior by +using the multiplier 48271. Both generators are rather simplistic. + */ +alias MinstdRand0 = LinearCongruentialEngine!(uint, 16_807, 0, 2_147_483_647); +/// ditto +alias MinstdRand = LinearCongruentialEngine!(uint, 48_271, 0, 2_147_483_647); + +/// +@safe unittest +{ + // seed with a constant + auto rnd0 = MinstdRand0(1); + auto n = rnd0.front; // same for each run + // Seed with an unpredictable value + rnd0.seed(unpredictableSeed); + n = rnd0.front; // different across runs +} + +@safe unittest +{ + import std.range; + static assert(isForwardRange!MinstdRand); + static assert(isUniformRNG!MinstdRand); + static assert(isUniformRNG!MinstdRand0); + static assert(isUniformRNG!(MinstdRand, uint)); + static assert(isUniformRNG!(MinstdRand0, uint)); + static assert(isSeedable!MinstdRand); + static assert(isSeedable!MinstdRand0); + static assert(isSeedable!(MinstdRand, uint)); + static assert(isSeedable!(MinstdRand0, uint)); + + // The correct numbers are taken from The Database of Integer Sequences + // http://www.research.att.com/~njas/sequences/eisBTfry00128.txt + auto checking0 = [ + 16807UL,282475249,1622650073,984943658,1144108930,470211272, + 101027544,1457850878,1458777923,2007237709,823564440,1115438165, + 1784484492,74243042,114807987,1137522503,1441282327,16531729, + 823378840,143542612 ]; + //auto rnd0 = MinstdRand0(1); + MinstdRand0 rnd0; + + foreach (e; checking0) + { + assert(rnd0.front == e); + rnd0.popFront(); + } + // Test the 10000th invocation + // Correct value taken from: + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2461.pdf + rnd0.seed(); + popFrontN(rnd0, 9999); + assert(rnd0.front == 1043618065); + + // Test MinstdRand + auto checking = [48271UL,182605794,1291394886,1914720637,2078669041, + 407355683]; + //auto rnd = MinstdRand(1); + MinstdRand rnd; + foreach (e; checking) + { + assert(rnd.front == e); + rnd.popFront(); + } + + // Test the 10000th invocation + // Correct value taken from: + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2461.pdf + rnd.seed(); + popFrontN(rnd, 9999); + assert(rnd.front == 399268537); + + // Check .save works + foreach (Type; std.meta.AliasSeq!(MinstdRand0, MinstdRand)) + { + auto rnd1 = Type(unpredictableSeed); + auto rnd2 = rnd1.save; + assert(rnd1 == rnd2); + // Enable next test when RNGs are reference types + version (none) { assert(rnd1 !is rnd2); } + assert(rnd1.take(100).array() == rnd2.take(100).array()); + } +} + +/** +The $(LINK2 https://en.wikipedia.org/wiki/Mersenne_Twister, Mersenne Twister) generator. + */ +struct MersenneTwisterEngine(UIntType, size_t w, size_t n, size_t m, size_t r, + UIntType a, size_t u, UIntType d, size_t s, + UIntType b, size_t t, + UIntType c, size_t l, UIntType f) +if (isUnsigned!UIntType) +{ + static assert(0 < w && w <= UIntType.sizeof * 8); + static assert(1 <= m && m <= n); + static assert(0 <= r && 0 <= u && 0 <= s && 0 <= t && 0 <= l); + static assert(r <= w && u <= w && s <= w && t <= w && l <= w); + static assert(0 <= a && 0 <= b && 0 <= c); + static assert(n <= sizediff_t.max); + + ///Mark this as a Rng + enum bool isUniformRandom = true; + +/** +Parameters for the generator. +*/ + enum size_t wordSize = w; + enum size_t stateSize = n; /// ditto + enum size_t shiftSize = m; /// ditto + enum size_t maskBits = r; /// ditto + enum UIntType xorMask = a; /// ditto + enum size_t temperingU = u; /// ditto + enum UIntType temperingD = d; /// ditto + enum size_t temperingS = s; /// ditto + enum UIntType temperingB = b; /// ditto + enum size_t temperingT = t; /// ditto + enum UIntType temperingC = c; /// ditto + enum size_t temperingL = l; /// ditto + enum UIntType initializationMultiplier = f; /// ditto + + /// Smallest generated value (0). + enum UIntType min = 0; + /// Largest generated value. + enum UIntType max = UIntType.max >> (UIntType.sizeof * 8u - w); + // note, `max` also serves as a bitmask for the lowest `w` bits + static assert(a <= max && b <= max && c <= max && f <= max); + + /// The default seed value. + enum UIntType defaultSeed = 5489u; + + // Bitmasks used in the 'twist' part of the algorithm + private enum UIntType lowerMask = (cast(UIntType) 1u << r) - 1, + upperMask = (~lowerMask) & this.max; + + /* + Collection of all state variables + used by the generator + */ + private struct State + { + /* + State array of the generator. This + is iterated through backwards (from + last element to first), providing a + few extra compiler optimizations by + comparison to the forward iteration + used in most implementations. + */ + UIntType[n] data; + + /* + Cached copy of most recently updated + element of `data` state array, ready + to be tempered to generate next + `front` value + */ + UIntType z; + + /* + Most recently generated random variate + */ + UIntType front; + + /* + Index of the entry in the `data` + state array that will be twisted + in the next `popFront()` call + */ + size_t index; + } + + /* + State variables used by the generator; + initialized to values equivalent to + explicitly seeding the generator with + `defaultSeed` + */ + private State state = defaultState(); + /* NOTE: the above is a workaround to ensure + backwards compatibility with the original + implementation, which permitted implicit + construction. With `@disable this();` + it would not be necessary. */ + +/** + Constructs a MersenneTwisterEngine object. +*/ + this(UIntType value) @safe pure nothrow @nogc + { + seed(value); + } + + /** + Generates the default initial state for a Mersenne + Twister; equivalent to the internal state obtained + by calling `seed(defaultSeed)` + */ + private static State defaultState() @safe pure nothrow @nogc + { + if (!__ctfe) assert(false); + State mtState; + seedImpl(defaultSeed, mtState); + return mtState; + } + +/** + Seeds a MersenneTwisterEngine object. + Note: + This seed function gives 2^w starting points (the lowest w bits of + the value provided will be used). To allow the RNG to be started + in any one of its internal states use the seed overload taking an + InputRange. +*/ + void seed()(UIntType value = defaultSeed) @safe pure nothrow @nogc + { + this.seedImpl(value, this.state); + } + + /** + Implementation of the seeding mechanism, which + can be used with an arbitrary `State` instance + */ + private static void seedImpl(UIntType value, ref State mtState) + { + mtState.data[$ - 1] = value; + static if (this.max != UIntType.max) + { + mtState.data[$ - 1] &= this.max; + } + + foreach_reverse (size_t i, ref e; mtState.data[0 .. $ - 1]) + { + e = f * (mtState.data[i + 1] ^ (mtState.data[i + 1] >> (w - 2))) + cast(UIntType)(n - (i + 1)); + static if (this.max != UIntType.max) + { + e &= this.max; + } + } + + mtState.index = n - 1; + + /* double popFront() to guarantee both `mtState.z` + and `mtState.front` are derived from the newly + set values in `mtState.data` */ + MersenneTwisterEngine.popFrontImpl(mtState); + MersenneTwisterEngine.popFrontImpl(mtState); + } + +/** + Seeds a MersenneTwisterEngine object using an InputRange. + + Throws: + $(D Exception) if the InputRange didn't provide enough elements to seed the generator. + The number of elements required is the 'n' template parameter of the MersenneTwisterEngine struct. + */ + void seed(T)(T range) if (isInputRange!T && is(Unqual!(ElementType!T) == UIntType)) + { + this.seedImpl(range, this.state); + } + + /** + Implementation of the range-based seeding mechanism, + which can be used with an arbitrary `State` instance + */ + private static void seedImpl(T)(T range, ref State mtState) + if (isInputRange!T && is(Unqual!(ElementType!T) == UIntType)) + { + size_t j; + for (j = 0; j < n && !range.empty; ++j, range.popFront()) + { + sizediff_t idx = n - j - 1; + mtState.data[idx] = range.front; + } + + mtState.index = n - 1; + + if (range.empty && j < n) + { + import core.internal.string : UnsignedStringBuf, unsignedToTempString; + + UnsignedStringBuf buf = void; + string s = "MersenneTwisterEngine.seed: Input range didn't provide enough elements: Need "; + s ~= unsignedToTempString(n, buf, 10) ~ " elements."; + throw new Exception(s); + } + + /* double popFront() to guarantee both `mtState.z` + and `mtState.front` are derived from the newly + set values in `mtState.data` */ + MersenneTwisterEngine.popFrontImpl(mtState); + MersenneTwisterEngine.popFrontImpl(mtState); + } + +/** + Advances the generator. +*/ + void popFront() @safe pure nothrow @nogc + { + this.popFrontImpl(this.state); + } + + /* + Internal implementation of `popFront()`, which + can be used with an arbitrary `State` instance + */ + private static void popFrontImpl(ref State mtState) + { + /* This function blends two nominally independent + processes: (i) calculation of the next random + variate `mtState.front` from the cached previous + `data` entry `z`, and (ii) updating the value + of `data[index]` and `mtState.z` and advancing + the `index` value to the next in sequence. + + By interweaving the steps involved in these + procedures, rather than performing each of + them separately in sequence, the variables + are kept 'hot' in CPU registers, allowing + for significantly faster performance. */ + sizediff_t index = mtState.index; + sizediff_t next = index - 1; + if (next < 0) + next = n - 1; + auto z = mtState.z; + sizediff_t conj = index - m; + if (conj < 0) + conj = index - m + n; + + static if (d == UIntType.max) + { + z ^= (z >> u); + } + else + { + z ^= (z >> u) & d; + } + + auto q = mtState.data[index] & upperMask; + auto p = mtState.data[next] & lowerMask; + z ^= (z << s) & b; + auto y = q | p; + auto x = y >> 1; + z ^= (z << t) & c; + if (y & 1) + x ^= a; + auto e = mtState.data[conj] ^ x; + z ^= (z >> l); + mtState.z = mtState.data[index] = e; + mtState.index = next; + + /* technically we should take the lowest `w` + bits here, but if the tempering bitmasks + `b` and `c` are set correctly, this should + be unnecessary */ + mtState.front = z; + } + +/** + Returns the current random value. + */ + @property UIntType front() @safe const pure nothrow @nogc + { + return this.state.front; + } + +/// + @property typeof(this) save() @safe pure nothrow @nogc + { + return this; + } + +/** +Always $(D false). + */ + enum bool empty = false; +} + +/** +A $(D MersenneTwisterEngine) instantiated with the parameters of the +original engine $(HTTP math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html, +MT19937), generating uniformly-distributed 32-bit numbers with a +period of 2 to the power of 19937. Recommended for random number +generation unless memory is severely restricted, in which case a $(D +LinearCongruentialEngine) would be the generator of choice. + */ +alias Mt19937 = MersenneTwisterEngine!(uint, 32, 624, 397, 31, + 0x9908b0df, 11, 0xffffffff, 7, + 0x9d2c5680, 15, + 0xefc60000, 18, 1_812_433_253); + +/// +@safe unittest +{ + // seed with a constant + Mt19937 gen; + auto n = gen.front; // same for each run + // Seed with an unpredictable value + gen.seed(unpredictableSeed); + n = gen.front; // different across runs +} + +@safe nothrow unittest +{ + import std.algorithm; + import std.range; + static assert(isUniformRNG!Mt19937); + static assert(isUniformRNG!(Mt19937, uint)); + static assert(isSeedable!Mt19937); + static assert(isSeedable!(Mt19937, uint)); + static assert(isSeedable!(Mt19937, typeof(map!((a) => unpredictableSeed)(repeat(0))))); + Mt19937 gen; + assert(gen.front == 3499211612); + popFrontN(gen, 9999); + assert(gen.front == 4123659995); + try { gen.seed(iota(624u)); } catch (Exception) { assert(false); } + assert(gen.front == 3708921088u); + popFrontN(gen, 9999); + assert(gen.front == 165737292u); +} + +/** +A $(D MersenneTwisterEngine) instantiated with the parameters of the +original engine $(HTTP en.wikipedia.org/wiki/Mersenne_Twister, +MT19937-64), generating uniformly-distributed 64-bit numbers with a +period of 2 to the power of 19937. +*/ +alias Mt19937_64 = MersenneTwisterEngine!(ulong, 64, 312, 156, 31, + 0xb5026f5aa96619e9, 29, 0x5555555555555555, 17, + 0x71d67fffeda60000, 37, + 0xfff7eee000000000, 43, 6_364_136_223_846_793_005); + +/// +@safe unittest +{ + // Seed with a constant + auto gen = Mt19937_64(12345); + auto n = gen.front; // same for each run + // Seed with an unpredictable value + gen.seed(unpredictableSeed); + n = gen.front; // different across runs +} + +@safe nothrow unittest +{ + import std.algorithm; + import std.range; + static assert(isUniformRNG!Mt19937_64); + static assert(isUniformRNG!(Mt19937_64, ulong)); + static assert(isSeedable!Mt19937_64); + static assert(isSeedable!(Mt19937_64, ulong)); + // FIXME: this test demonstrates viably that Mt19937_64 + // is seedable with an infinite range of `ulong` values + // but it's a poor example of how to actually seed the + // generator, since it can't cover the full range of + // possible seed values. Ideally we need a 64-bit + // unpredictable seed to complement the 32-bit one! + static assert(isSeedable!(Mt19937_64, typeof(map!((a) => (cast(ulong) unpredictableSeed))(repeat(0))))); + Mt19937_64 gen; + assert(gen.front == 14514284786278117030uL); + popFrontN(gen, 9999); + assert(gen.front == 9981545732273789042uL); + try { gen.seed(iota(312uL)); } catch (Exception) { assert(false); } + assert(gen.front == 14660652410669508483uL); + popFrontN(gen, 9999); + assert(gen.front == 15956361063660440239uL); +} + +@safe unittest +{ + import std.algorithm; + import std.exception; + import std.range; + + Mt19937 gen; + + assertThrown(gen.seed(map!((a) => unpredictableSeed)(repeat(0, 623)))); + + gen.seed(map!((a) => unpredictableSeed)(repeat(0, 624))); + //infinite Range + gen.seed(map!((a) => unpredictableSeed)(repeat(0))); +} + +@safe pure nothrow unittest +{ + uint a, b; + { + Mt19937 gen; + a = gen.front; + } + { + Mt19937 gen; + gen.popFront(); + //popFrontN(gen, 1); // skip 1 element + b = gen.front; + } + assert(a != b); +} + +@safe unittest +{ + import std.range; + // Check .save works + foreach (Type; std.meta.AliasSeq!(Mt19937, Mt19937_64)) + { + auto gen1 = Type(unpredictableSeed); + auto gen2 = gen1.save; + assert(gen1 == gen2); // Danger, Will Robinson -- no opEquals for MT + // Enable next test when RNGs are reference types + version (none) { assert(gen1 !is gen2); } + assert(gen1.take(100).array() == gen2.take(100).array()); + } +} + +@safe pure nothrow unittest //11690 +{ + alias MT(UIntType, uint w) = MersenneTwisterEngine!(UIntType, w, 624, 397, 31, + 0x9908b0df, 11, 0xffffffff, 7, + 0x9d2c5680, 15, + 0xefc60000, 18, 1812433253); + + ulong[] expectedFirstValue = [3499211612uL, 3499211612uL, + 171143175841277uL, 1145028863177033374uL]; + + ulong[] expected10kValue = [4123659995uL, 4123659995uL, + 51991688252792uL, 3031481165133029945uL]; + + foreach (i, R; std.meta.AliasSeq!(MT!(uint, 32), MT!(ulong, 32), MT!(ulong, 48), MT!(ulong, 64))) + { + auto a = R(); + a.seed(a.defaultSeed); // checks that some alternative paths in `seed` are utilized + assert(a.front == expectedFirstValue[i]); + a.popFrontN(9999); + assert(a.front == expected10kValue[i]); + } +} + + +/** + * Xorshift generator using 32bit algorithm. + * + * Implemented according to $(HTTP www.jstatsoft.org/v08/i14/paper, Xorshift RNGs). + * Supporting bits are below, $(D bits) means second parameter of XorshiftEngine. + * + * $(BOOKTABLE , + * $(TR $(TH bits) $(TH period)) + * $(TR $(TD 32) $(TD 2^32 - 1)) + * $(TR $(TD 64) $(TD 2^64 - 1)) + * $(TR $(TD 96) $(TD 2^96 - 1)) + * $(TR $(TD 128) $(TD 2^128 - 1)) + * $(TR $(TD 160) $(TD 2^160 - 1)) + * $(TR $(TD 192) $(TD 2^192 - 2^32)) + * ) + */ +struct XorshiftEngine(UIntType, UIntType bits, UIntType a, UIntType b, UIntType c) +if (isUnsigned!UIntType) +{ + static assert(bits == 32 || bits == 64 || bits == 96 || bits == 128 || bits == 160 || bits == 192, + "Xorshift supports only 32, 64, 96, 128, 160 and 192 bit versions. " + ~ to!string(bits) ~ " is not supported."); + + public: + ///Mark this as a Rng + enum bool isUniformRandom = true; + /// Always $(D false) (random generators are infinite ranges). + enum empty = false; + /// Smallest generated value. + enum UIntType min = 0; + /// Largest generated value. + enum UIntType max = UIntType.max; + + + private: + enum size = bits / 32; + + static if (bits == 32) + UIntType[size] seeds_ = [2_463_534_242]; + else static if (bits == 64) + UIntType[size] seeds_ = [123_456_789, 362_436_069]; + else static if (bits == 96) + UIntType[size] seeds_ = [123_456_789, 362_436_069, 521_288_629]; + else static if (bits == 128) + UIntType[size] seeds_ = [123_456_789, 362_436_069, 521_288_629, 88_675_123]; + else static if (bits == 160) + UIntType[size] seeds_ = [123_456_789, 362_436_069, 521_288_629, 88_675_123, 5_783_321]; + else static if (bits == 192) + { + UIntType[size] seeds_ = [123_456_789, 362_436_069, 521_288_629, 88_675_123, 5_783_321, 6_615_241]; + UIntType value_; + } + else + { + static assert(false, "Phobos Error: Xorshift has no instantiation rule for " + ~ to!string(bits) ~ " bits."); + } + + + public: + /** + * Constructs a $(D XorshiftEngine) generator seeded with $(D_PARAM x0). + */ + this(UIntType x0) @safe pure nothrow @nogc + { + seed(x0); + } + + + /** + * (Re)seeds the generator. + */ + void seed(UIntType x0) @safe pure nothrow @nogc + { + // Initialization routine from MersenneTwisterEngine. + foreach (i, e; seeds_) + seeds_[i] = x0 = cast(UIntType)(1_812_433_253U * (x0 ^ (x0 >> 30)) + i + 1); + + // All seeds must not be 0. + sanitizeSeeds(seeds_); + + popFront(); + } + + + /** + * Returns the current number in the random sequence. + */ + @property + UIntType front() const @safe pure nothrow @nogc + { + static if (bits == 192) + return value_; + else + return seeds_[size - 1]; + } + + + /** + * Advances the random sequence. + */ + void popFront() @safe pure nothrow @nogc + { + UIntType temp; + + static if (bits == 32) + { + temp = seeds_[0] ^ (seeds_[0] << a); + temp = temp ^ (temp >> b); + seeds_[0] = temp ^ (temp << c); + } + else static if (bits == 64) + { + temp = seeds_[0] ^ (seeds_[0] << a); + seeds_[0] = seeds_[1]; + seeds_[1] = seeds_[1] ^ (seeds_[1] >> c) ^ temp ^ (temp >> b); + } + else static if (bits == 96) + { + temp = seeds_[0] ^ (seeds_[0] << a); + seeds_[0] = seeds_[1]; + seeds_[1] = seeds_[2]; + seeds_[2] = seeds_[2] ^ (seeds_[2] >> c) ^ temp ^ (temp >> b); + } + else static if (bits == 128) + { + temp = seeds_[0] ^ (seeds_[0] << a); + seeds_[0] = seeds_[1]; + seeds_[1] = seeds_[2]; + seeds_[2] = seeds_[3]; + seeds_[3] = seeds_[3] ^ (seeds_[3] >> c) ^ temp ^ (temp >> b); + } + else static if (bits == 160) + { + temp = seeds_[0] ^ (seeds_[0] << a); + seeds_[0] = seeds_[1]; + seeds_[1] = seeds_[2]; + seeds_[2] = seeds_[3]; + seeds_[3] = seeds_[4]; + seeds_[4] = seeds_[4] ^ (seeds_[4] >> c) ^ temp ^ (temp >> b); + } + else static if (bits == 192) + { + temp = seeds_[0] ^ (seeds_[0] >> a); + seeds_[0] = seeds_[1]; + seeds_[1] = seeds_[2]; + seeds_[2] = seeds_[3]; + seeds_[3] = seeds_[4]; + seeds_[4] = seeds_[4] ^ (seeds_[4] << c) ^ temp ^ (temp << b); + value_ = seeds_[4] + (seeds_[5] += 362_437); + } + else + { + static assert(false, "Phobos Error: Xorshift has no popFront() update for " + ~ to!string(bits) ~ " bits."); + } + } + + + /** + * Captures a range state. + */ + @property + typeof(this) save() @safe pure nothrow @nogc + { + return this; + } + + + /** + * Compares against $(D_PARAM rhs) for equality. + */ + bool opEquals(ref const XorshiftEngine rhs) const @safe pure nothrow @nogc + { + return seeds_ == rhs.seeds_; + } + + + private: + static void sanitizeSeeds(ref UIntType[size] seeds) @safe pure nothrow @nogc + { + for (uint i; i < seeds.length; i++) + { + if (seeds[i] == 0) + seeds[i] = i + 1; + } + } + + + @safe pure nothrow unittest + { + static if (size == 4) // Other bits too + { + UIntType[size] seeds = [1, 0, 0, 4]; + + sanitizeSeeds(seeds); + + assert(seeds == [1, 2, 3, 4]); + } + } +} + + +/** + * Define $(D XorshiftEngine) generators with well-chosen parameters. See each bits examples of "Xorshift RNGs". + * $(D Xorshift) is a Xorshift128's alias because 128bits implementation is mostly used. + */ +alias Xorshift32 = XorshiftEngine!(uint, 32, 13, 17, 15) ; +alias Xorshift64 = XorshiftEngine!(uint, 64, 10, 13, 10); /// ditto +alias Xorshift96 = XorshiftEngine!(uint, 96, 10, 5, 26); /// ditto +alias Xorshift128 = XorshiftEngine!(uint, 128, 11, 8, 19); /// ditto +alias Xorshift160 = XorshiftEngine!(uint, 160, 2, 1, 4); /// ditto +alias Xorshift192 = XorshiftEngine!(uint, 192, 2, 1, 4); /// ditto +alias Xorshift = Xorshift128; /// ditto + +/// +@safe unittest +{ + // Seed with a constant + auto rnd = Xorshift(1); + auto num = rnd.front; // same for each run + + // Seed with an unpredictable value + rnd.seed(unpredictableSeed); + num = rnd.front; // different across rnd +} + +@safe unittest +{ + import std.range; + static assert(isForwardRange!Xorshift); + static assert(isUniformRNG!Xorshift); + static assert(isUniformRNG!(Xorshift, uint)); + static assert(isSeedable!Xorshift); + static assert(isSeedable!(Xorshift, uint)); + + // Result from reference implementation. + auto checking = [ + [2463534242UL, 901999875, 3371835698, 2675058524, 1053936272, 3811264849, + 472493137, 3856898176, 2131710969, 2312157505], + [362436069UL, 2113136921, 19051112, 3010520417, 951284840, 1213972223, + 3173832558, 2611145638, 2515869689, 2245824891], + [521288629UL, 1950277231, 185954712, 1582725458, 3580567609, 2303633688, + 2394948066, 4108622809, 1116800180, 3357585673], + [88675123UL, 3701687786, 458299110, 2500872618, 3633119408, 516391518, + 2377269574, 2599949379, 717229868, 137866584], + [5783321UL, 393427209, 1947109840, 565829276, 1006220149, 971147905, + 1436324242, 2800460115, 1484058076, 3823330032], + [0UL, 246875399, 3690007200, 1264581005, 3906711041, 1866187943, 2481925219, + 2464530826, 1604040631, 3653403911] + ]; + + alias XorshiftTypes = std.meta.AliasSeq!(Xorshift32, Xorshift64, Xorshift96, Xorshift128, Xorshift160, Xorshift192); + + foreach (I, Type; XorshiftTypes) + { + Type rnd; + + foreach (e; checking[I]) + { + assert(rnd.front == e); + rnd.popFront(); + } + } + + // Check .save works + foreach (Type; XorshiftTypes) + { + auto rnd1 = Type(unpredictableSeed); + auto rnd2 = rnd1.save; + assert(rnd1 == rnd2); + // Enable next test when RNGs are reference types + version (none) { assert(rnd1 !is rnd2); } + assert(rnd1.take(100).array() == rnd2.take(100).array()); + } +} + + +/* A complete list of all pseudo-random number generators implemented in + * std.random. This can be used to confirm that a given function or + * object is compatible with all the pseudo-random number generators + * available. It is enabled only in unittest mode. + */ +@safe unittest +{ + foreach (Rng; PseudoRngTypes) + { + static assert(isUniformRNG!Rng); + auto rng = Rng(unpredictableSeed); + } +} + + +/** +A "good" seed for initializing random number engines. Initializing +with $(D_PARAM unpredictableSeed) makes engines generate different +random number sequences every run. + +Returns: +A single unsigned integer seed value, different on each successive call +*/ +@property uint unpredictableSeed() @trusted +{ + import core.thread : Thread, getpid, MonoTime; + static bool seeded; + static MinstdRand0 rand; + if (!seeded) + { + uint threadID = cast(uint) cast(void*) Thread.getThis(); + rand.seed((getpid() + threadID) ^ cast(uint) MonoTime.currTime.ticks); + seeded = true; + } + rand.popFront(); + return cast(uint) (MonoTime.currTime.ticks ^ rand.front); +} + +/// +@safe unittest +{ + auto rnd = Random(unpredictableSeed); + auto n = rnd.front; + static assert(is(typeof(n) == uint)); +} + +/** +The "default", "favorite", "suggested" random number generator type on +the current platform. It is an alias for one of the previously-defined +generators. You may want to use it if (1) you need to generate some +nice random numbers, and (2) you don't care for the minutiae of the +method being used. + */ + +alias Random = Mt19937; + +@safe unittest +{ + static assert(isUniformRNG!Random); + static assert(isUniformRNG!(Random, uint)); + static assert(isSeedable!Random); + static assert(isSeedable!(Random, uint)); +} + +/** +Global random number generator used by various functions in this +module whenever no generator is specified. It is allocated per-thread +and initialized to an unpredictable value for each thread. + +Returns: +A singleton instance of the default random number generator + */ +@property ref Random rndGen() @safe +{ + import std.algorithm.iteration : map; + import std.range : repeat; + + static Random result; + static bool initialized; + if (!initialized) + { + static if (isSeedable!(Random, typeof(map!((a) => unpredictableSeed)(repeat(0))))) + result.seed(map!((a) => unpredictableSeed)(repeat(0))); + else + result = Random(unpredictableSeed); + initialized = true; + } + return result; +} + +/** +Generates a number between $(D a) and $(D b). The $(D boundaries) +parameter controls the shape of the interval (open vs. closed on +either side). Valid values for $(D boundaries) are $(D "[]"), $(D +"$(LPAREN)]"), $(D "[$(RPAREN)"), and $(D "()"). The default interval +is closed to the left and open to the right. The version that does not +take $(D urng) uses the default generator $(D rndGen). + +Params: + a = lower bound of the _uniform distribution + b = upper bound of the _uniform distribution + urng = (optional) random number generator to use; + if not specified, defaults to $(D rndGen) + +Returns: + A single random variate drawn from the _uniform distribution + between $(D a) and $(D b), whose type is the common type of + these parameters + */ +auto uniform(string boundaries = "[)", T1, T2) +(T1 a, T2 b) +if (!is(CommonType!(T1, T2) == void)) +{ + return uniform!(boundaries, T1, T2, Random)(a, b, rndGen); +} + +/// +@safe unittest +{ + auto gen = Random(unpredictableSeed); + // Generate an integer in [0, 1023] + auto a = uniform(0, 1024, gen); + // Generate a float in [0, 1) + auto b = uniform(0.0f, 1.0f, gen); +} + +@safe unittest +{ + MinstdRand0 gen; + foreach (i; 0 .. 20) + { + auto x = uniform(0.0, 15.0, gen); + assert(0 <= x && x < 15); + } + foreach (i; 0 .. 20) + { + auto x = uniform!"[]"('a', 'z', gen); + assert('a' <= x && x <= 'z'); + } + + foreach (i; 0 .. 20) + { + auto x = uniform('a', 'z', gen); + assert('a' <= x && x < 'z'); + } + + foreach (i; 0 .. 20) + { + immutable ubyte a = 0; + immutable ubyte b = 15; + auto x = uniform(a, b, gen); + assert(a <= x && x < b); + } +} + +// Implementation of uniform for floating-point types +/// ditto +auto uniform(string boundaries = "[)", + T1, T2, UniformRandomNumberGenerator) +(T1 a, T2 b, ref UniformRandomNumberGenerator urng) +if (isFloatingPoint!(CommonType!(T1, T2)) && isUniformRNG!UniformRandomNumberGenerator) +{ + import std.conv : text; + import std.exception : enforce; + alias NumberType = Unqual!(CommonType!(T1, T2)); + static if (boundaries[0] == '(') + { + import std.math : nextafter; + NumberType _a = nextafter(cast(NumberType) a, NumberType.infinity); + } + else + { + NumberType _a = a; + } + static if (boundaries[1] == ')') + { + import std.math : nextafter; + NumberType _b = nextafter(cast(NumberType) b, -NumberType.infinity); + } + else + { + NumberType _b = b; + } + enforce(_a <= _b, + text("std.random.uniform(): invalid bounding interval ", + boundaries[0], a, ", ", b, boundaries[1])); + NumberType result = + _a + (_b - _a) * cast(NumberType) (urng.front - urng.min) + / (urng.max - urng.min); + urng.popFront(); + return result; +} + +// Implementation of uniform for integral types +/+ Description of algorithm and suggestion of correctness: + +The modulus operator maps an integer to a small, finite space. For instance, `x +% 3` will map whatever x is into the range [0 .. 3). 0 maps to 0, 1 maps to 1, 2 +maps to 2, 3 maps to 0, and so on infinitely. As long as the integer is +uniformly chosen from the infinite space of all non-negative integers then `x % +3` will uniformly fall into that range. + +(Non-negative is important in this case because some definitions of modulus, +namely the one used in computers generally, map negative numbers differently to +(-3 .. 0]. `uniform` does not use negative number modulus, thus we can safely +ignore that fact.) + +The issue with computers is that integers have a finite space they must fit in, +and our uniformly chosen random number is picked in that finite space. So, that +method is not sufficient. You can look at it as the integer space being divided +into "buckets" and every bucket after the first bucket maps directly into that +first bucket. `[0, 1, 2]`, `[3, 4, 5]`, ... When integers are finite, then the +last bucket has the chance to be "incomplete": `[uint.max - 3, uint.max - 2, +uint.max - 1]`, `[uint.max]` ... (the last bucket only has 1!). The issue here +is that _every_ bucket maps _completely_ to the first bucket except for that +last one. The last one doesn't have corresponding mappings to 1 or 2, in this +case, which makes it unfair. + +So, the answer is to simply "reroll" if you're in that last bucket, since it's +the only unfair one. Eventually you'll roll into a fair bucket. Simply, instead +of the meaning of the last bucket being "maps to `[0]`", it changes to "maps to +`[0, 1, 2]`", which is precisely what we want. + +To generalize, `upperDist` represents the size of our buckets (and, thus, the +exclusive upper bound for our desired uniform number). `rnum` is a uniformly +random number picked from the space of integers that a computer can hold (we'll +say `UpperType` represents that type). + +We'll first try to do the mapping into the first bucket by doing `offset = rnum +% upperDist`. We can figure out the position of the front of the bucket we're in +by `bucketFront = rnum - offset`. + +If we start at `UpperType.max` and walk backwards `upperDist - 1` spaces, then +the space we land on is the last acceptable position where a full bucket can +fit: + +``` + bucketFront UpperType.max + v v +[..., 0, 1, 2, ..., upperDist - 1] + ^~~ upperDist - 1 ~~^ +``` + +If the bucket starts any later, then it must have lost at least one number and +at least that number won't be represented fairly. + +``` + bucketFront UpperType.max + v v +[..., upperDist - 1, 0, 1, 2, ..., upperDist - 2] + ^~~~~~~~ upperDist - 1 ~~~~~~~^ +``` + +Hence, our condition to reroll is +`bucketFront > (UpperType.max - (upperDist - 1))` ++/ +auto uniform(string boundaries = "[)", T1, T2, RandomGen) +(T1 a, T2 b, ref RandomGen rng) +if ((isIntegral!(CommonType!(T1, T2)) || isSomeChar!(CommonType!(T1, T2))) && + isUniformRNG!RandomGen) +{ + import std.conv : text, unsigned; + import std.exception : enforce; + alias ResultType = Unqual!(CommonType!(T1, T2)); + static if (boundaries[0] == '(') + { + enforce(a < ResultType.max, + text("std.random.uniform(): invalid left bound ", a)); + ResultType lower = cast(ResultType) (a + 1); + } + else + { + ResultType lower = a; + } + + static if (boundaries[1] == ']') + { + enforce(lower <= b, + text("std.random.uniform(): invalid bounding interval ", + boundaries[0], a, ", ", b, boundaries[1])); + /* Cannot use this next optimization with dchar, as dchar + * only partially uses its full bit range + */ + static if (!is(ResultType == dchar)) + { + if (b == ResultType.max && lower == ResultType.min) + { + // Special case - all bits are occupied + return std.random.uniform!ResultType(rng); + } + } + auto upperDist = unsigned(b - lower) + 1u; + } + else + { + enforce(lower < b, + text("std.random.uniform(): invalid bounding interval ", + boundaries[0], a, ", ", b, boundaries[1])); + auto upperDist = unsigned(b - lower); + } + + assert(upperDist != 0); + + alias UpperType = typeof(upperDist); + static assert(UpperType.min == 0); + + UpperType offset, rnum, bucketFront; + do + { + rnum = uniform!UpperType(rng); + offset = rnum % upperDist; + bucketFront = rnum - offset; + } // while we're in an unfair bucket... + while (bucketFront > (UpperType.max - (upperDist - 1))); + + return cast(ResultType)(lower + offset); +} + +@safe unittest +{ + import std.conv : to; + auto gen = Mt19937(unpredictableSeed); + static assert(isForwardRange!(typeof(gen))); + + auto a = uniform(0, 1024, gen); + assert(0 <= a && a <= 1024); + auto b = uniform(0.0f, 1.0f, gen); + assert(0 <= b && b < 1, to!string(b)); + auto c = uniform(0.0, 1.0); + assert(0 <= c && c < 1); + + foreach (T; std.meta.AliasSeq!(char, wchar, dchar, byte, ubyte, short, ushort, + int, uint, long, ulong, float, double, real)) + { + T lo = 0, hi = 100; + + // Try tests with each of the possible bounds + { + T init = uniform(lo, hi); + size_t i = 50; + while (--i && uniform(lo, hi) == init) {} + assert(i > 0); + } + { + T init = uniform!"[)"(lo, hi); + size_t i = 50; + while (--i && uniform(lo, hi) == init) {} + assert(i > 0); + } + { + T init = uniform!"(]"(lo, hi); + size_t i = 50; + while (--i && uniform(lo, hi) == init) {} + assert(i > 0); + } + { + T init = uniform!"()"(lo, hi); + size_t i = 50; + while (--i && uniform(lo, hi) == init) {} + assert(i > 0); + } + { + T init = uniform!"[]"(lo, hi); + size_t i = 50; + while (--i && uniform(lo, hi) == init) {} + assert(i > 0); + } + + /* Test case with closed boundaries covering whole range + * of integral type + */ + static if (isIntegral!T || isSomeChar!T) + { + foreach (immutable _; 0 .. 100) + { + auto u = uniform!"[]"(T.min, T.max); + static assert(is(typeof(u) == T)); + assert(T.min <= u, "Lower bound violation for uniform!\"[]\" with " ~ T.stringof); + assert(u <= T.max, "Upper bound violation for uniform!\"[]\" with " ~ T.stringof); + } + } + } + + auto reproRng = Xorshift(239842); + + foreach (T; std.meta.AliasSeq!(char, wchar, dchar, byte, ubyte, short, + ushort, int, uint, long, ulong)) + { + T lo = T.min + 10, hi = T.max - 10; + T init = uniform(lo, hi, reproRng); + size_t i = 50; + while (--i && uniform(lo, hi, reproRng) == init) {} + assert(i > 0); + } + + { + bool sawLB = false, sawUB = false; + foreach (i; 0 .. 50) + { + auto x = uniform!"[]"('a', 'd', reproRng); + if (x == 'a') sawLB = true; + if (x == 'd') sawUB = true; + assert('a' <= x && x <= 'd'); + } + assert(sawLB && sawUB); + } + + { + bool sawLB = false, sawUB = false; + foreach (i; 0 .. 50) + { + auto x = uniform('a', 'd', reproRng); + if (x == 'a') sawLB = true; + if (x == 'c') sawUB = true; + assert('a' <= x && x < 'd'); + } + assert(sawLB && sawUB); + } + + { + bool sawLB = false, sawUB = false; + foreach (i; 0 .. 50) + { + immutable int lo = -2, hi = 2; + auto x = uniform!"()"(lo, hi, reproRng); + if (x == (lo+1)) sawLB = true; + if (x == (hi-1)) sawUB = true; + assert(lo < x && x < hi); + } + assert(sawLB && sawUB); + } + + { + bool sawLB = false, sawUB = false; + foreach (i; 0 .. 50) + { + immutable ubyte lo = 0, hi = 5; + auto x = uniform(lo, hi, reproRng); + if (x == lo) sawLB = true; + if (x == (hi-1)) sawUB = true; + assert(lo <= x && x < hi); + } + assert(sawLB && sawUB); + } + + { + foreach (i; 0 .. 30) + { + assert(i == uniform(i, i+1, reproRng)); + } + } +} + +/** +Generates a uniformly-distributed number in the range $(D [T.min, +T.max]) for any integral or character type $(D T). If no random +number generator is passed, uses the default $(D rndGen). + +Params: + urng = (optional) random number generator to use; + if not specified, defaults to $(D rndGen) + +Returns: + Random variate drawn from the _uniform distribution across all + possible values of the integral or character type $(D T). + */ +auto uniform(T, UniformRandomNumberGenerator) +(ref UniformRandomNumberGenerator urng) +if (!is(T == enum) && (isIntegral!T || isSomeChar!T) && isUniformRNG!UniformRandomNumberGenerator) +{ + /* dchar does not use its full bit range, so we must + * revert to the uniform with specified bounds + */ + static if (is(T == dchar)) + { + return uniform!"[]"(T.min, T.max); + } + else + { + auto r = urng.front; + urng.popFront(); + static if (T.sizeof <= r.sizeof) + { + return cast(T) r; + } + else + { + static assert(T.sizeof == 8 && r.sizeof == 4); + T r1 = urng.front | (cast(T) r << 32); + urng.popFront(); + return r1; + } + } +} + +/// Ditto +auto uniform(T)() +if (!is(T == enum) && (isIntegral!T || isSomeChar!T)) +{ + return uniform!T(rndGen); +} + +@safe unittest +{ + foreach (T; std.meta.AliasSeq!(char, wchar, dchar, byte, ubyte, short, ushort, + int, uint, long, ulong)) + { + T init = uniform!T(); + size_t i = 50; + while (--i && uniform!T() == init) {} + assert(i > 0); + + foreach (immutable _; 0 .. 100) + { + auto u = uniform!T(); + static assert(is(typeof(u) == T)); + assert(T.min <= u, "Lower bound violation for uniform!" ~ T.stringof); + assert(u <= T.max, "Upper bound violation for uniform!" ~ T.stringof); + } + } +} + +/** +Returns a uniformly selected member of enum $(D E). If no random number +generator is passed, uses the default $(D rndGen). + +Params: + urng = (optional) random number generator to use; + if not specified, defaults to $(D rndGen) + +Returns: + Random variate drawn with equal probability from any + of the possible values of the enum $(D E). + */ +auto uniform(E, UniformRandomNumberGenerator) +(ref UniformRandomNumberGenerator urng) +if (is(E == enum) && isUniformRNG!UniformRandomNumberGenerator) +{ + static immutable E[EnumMembers!E.length] members = [EnumMembers!E]; + return members[std.random.uniform(0, members.length, urng)]; +} + +/// Ditto +auto uniform(E)() +if (is(E == enum)) +{ + return uniform!E(rndGen); +} + +/// +@safe unittest +{ + enum Fruit { apple, mango, pear } + auto randFruit = uniform!Fruit(); +} + +@safe unittest +{ + enum Fruit { Apple = 12, Mango = 29, Pear = 72 } + foreach (_; 0 .. 100) + { + foreach (f; [uniform!Fruit(), rndGen.uniform!Fruit()]) + { + assert(f == Fruit.Apple || f == Fruit.Mango || f == Fruit.Pear); + } + } +} + +/** + * Generates a uniformly-distributed floating point number of type + * $(D T) in the range [0, 1$(RPAREN). If no random number generator is + * specified, the default RNG $(D rndGen) will be used as the source + * of randomness. + * + * $(D uniform01) offers a faster generation of random variates than + * the equivalent $(D uniform!"[$(RPAREN)"(0.0, 1.0)) and so may be preferred + * for some applications. + * + * Params: + * rng = (optional) random number generator to use; + * if not specified, defaults to $(D rndGen) + * + * Returns: + * Floating-point random variate of type $(D T) drawn from the _uniform + * distribution across the half-open interval [0, 1$(RPAREN). + * + */ +T uniform01(T = double)() +if (isFloatingPoint!T) +{ + return uniform01!T(rndGen); +} + +/// ditto +T uniform01(T = double, UniformRNG)(ref UniformRNG rng) +if (isFloatingPoint!T && isUniformRNG!UniformRNG) +out (result) +{ + assert(0 <= result); + assert(result < 1); +} +body +{ + alias R = typeof(rng.front); + static if (isIntegral!R) + { + enum T factor = 1 / (T(1) + rng.max - rng.min); + } + else static if (isFloatingPoint!R) + { + enum T factor = 1 / (rng.max - rng.min); + } + else + { + static assert(false); + } + + while (true) + { + immutable T u = (rng.front - rng.min) * factor; + rng.popFront(); + + import core.stdc.limits : CHAR_BIT; // CHAR_BIT is always 8 + static if (isIntegral!R && T.mant_dig >= (CHAR_BIT * R.sizeof)) + { + /* If RNG variates are integral and T has enough precision to hold + * R without loss, we're guaranteed by the definition of factor + * that precisely u < 1. + */ + return u; + } + else + { + /* Otherwise we have to check whether u is beyond the assumed range + * because of the loss of precision, or for another reason, a + * floating-point RNG can return a variate that is exactly equal to + * its maximum. + */ + if (u < 1) + { + return u; + } + } + } + + // Shouldn't ever get here. + assert(false); +} + +@safe unittest +{ + import std.meta; + foreach (UniformRNG; PseudoRngTypes) + { + + foreach (T; std.meta.AliasSeq!(float, double, real)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + UniformRNG rng = UniformRNG(unpredictableSeed); + + auto a = uniform01(); + assert(is(typeof(a) == double)); + assert(0 <= a && a < 1); + + auto b = uniform01(rng); + assert(is(typeof(a) == double)); + assert(0 <= b && b < 1); + + auto c = uniform01!T(); + assert(is(typeof(c) == T)); + assert(0 <= c && c < 1); + + auto d = uniform01!T(rng); + assert(is(typeof(d) == T)); + assert(0 <= d && d < 1); + + T init = uniform01!T(rng); + size_t i = 50; + while (--i && uniform01!T(rng) == init) {} + assert(i > 0); + assert(i < 50); + }(); + } +} + +/** +Generates a uniform probability distribution of size $(D n), i.e., an +array of size $(D n) of positive numbers of type $(D F) that sum to +$(D 1). If $(D useThis) is provided, it is used as storage. + */ +F[] uniformDistribution(F = double)(size_t n, F[] useThis = null) +if (isFloatingPoint!F) +{ + import std.numeric : normalize; + useThis.length = n; + foreach (ref e; useThis) + { + e = uniform(0.0, 1); + } + normalize(useThis); + return useThis; +} + +@safe unittest +{ + import std.algorithm; + import std.math; + static assert(is(CommonType!(double, int) == double)); + auto a = uniformDistribution(5); + assert(a.length == 5); + assert(approxEqual(reduce!"a + b"(a), 1)); + a = uniformDistribution(10, a); + assert(a.length == 10); + assert(approxEqual(reduce!"a + b"(a), 1)); +} + +/** +Returns a random, uniformly chosen, element `e` from the supplied +$(D Range range). If no random number generator is passed, the default +`rndGen` is used. + +Params: + range = a random access range that has the `length` property defined + urng = (optional) random number generator to use; + if not specified, defaults to `rndGen` + +Returns: + A single random element drawn from the `range`. If it can, it will + return a `ref` to the $(D range element), otherwise it will return + a copy. + */ +auto ref choice(Range, RandomGen = Random)(auto ref Range range, + ref RandomGen urng = rndGen) +if (isRandomAccessRange!Range && hasLength!Range && isUniformRNG!RandomGen) +{ + assert(range.length > 0, + __PRETTY_FUNCTION__ ~ ": invalid Range supplied. Range cannot be empty"); + + return range[uniform(size_t(0), $, urng)]; +} + +/// +@safe unittest +{ + import std.algorithm.searching : canFind; + + auto array = [1, 2, 3, 4, 5]; + auto elem = choice(array); + + assert(canFind(array, elem), + "Choice did not return a valid element from the given Range"); + + auto urng = Random(unpredictableSeed); + elem = choice(array, urng); + + assert(canFind(array, elem), + "Choice did not return a valid element from the given Range"); +} + +@safe unittest +{ + import std.algorithm.searching : canFind; + + class MyTestClass + { + int x; + + this(int x) + { + this.x = x; + } + } + + MyTestClass[] testClass; + foreach (i; 0 .. 5) + { + testClass ~= new MyTestClass(i); + } + + auto elem = choice(testClass); + + assert(canFind!((ref MyTestClass a, ref MyTestClass b) => a.x == b.x)(testClass, elem), + "Choice did not return a valid element from the given Range"); +} + +@system unittest +{ + import std.algorithm.iteration : map; + import std.algorithm.searching : canFind; + + auto array = [1, 2, 3, 4, 5]; + auto elemAddr = &choice(array); + + assert(array.map!((ref e) => &e).canFind(elemAddr), + "Choice did not return a ref to an element from the given Range"); + assert(array.canFind(*(cast(int *)(elemAddr))), + "Choice did not return a valid element from the given Range"); +} + +/** +Shuffles elements of $(D r) using $(D gen) as a shuffler. $(D r) must be +a random-access range with length. If no RNG is specified, $(D rndGen) +will be used. + +Params: + r = random-access range whose elements are to be shuffled + gen = (optional) random number generator to use; if not + specified, defaults to $(D rndGen) + */ + +void randomShuffle(Range, RandomGen)(Range r, ref RandomGen gen) +if (isRandomAccessRange!Range && isUniformRNG!RandomGen) +{ + return partialShuffle!(Range, RandomGen)(r, r.length, gen); +} + +/// ditto +void randomShuffle(Range)(Range r) +if (isRandomAccessRange!Range) +{ + return randomShuffle(r, rndGen); +} + +@safe unittest +{ + import std.algorithm.sorting : sort; + foreach (RandomGen; PseudoRngTypes) + { + // Also tests partialShuffle indirectly. + auto a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + auto b = a.dup; + auto gen = RandomGen(unpredictableSeed); + randomShuffle(a, gen); + sort(a); + assert(a == b); + randomShuffle(a); + sort(a); + assert(a == b); + } +} + +/** +Partially shuffles the elements of $(D r) such that upon returning $(D r[0 .. n]) +is a random subset of $(D r) and is randomly ordered. $(D r[n .. r.length]) +will contain the elements not in $(D r[0 .. n]). These will be in an undefined +order, but will not be random in the sense that their order after +$(D partialShuffle) returns will not be independent of their order before +$(D partialShuffle) was called. + +$(D r) must be a random-access range with length. $(D n) must be less than +or equal to $(D r.length). If no RNG is specified, $(D rndGen) will be used. + +Params: + r = random-access range whose elements are to be shuffled + n = number of elements of $(D r) to shuffle (counting from the beginning); + must be less than $(D r.length) + gen = (optional) random number generator to use; if not + specified, defaults to $(D rndGen) +*/ +void partialShuffle(Range, RandomGen)(Range r, in size_t n, ref RandomGen gen) +if (isRandomAccessRange!Range && isUniformRNG!RandomGen) +{ + import std.algorithm.mutation : swapAt; + import std.exception : enforce; + enforce(n <= r.length, "n must be <= r.length for partialShuffle."); + foreach (i; 0 .. n) + { + r.swapAt(i, uniform(i, r.length, gen)); + } +} + +/// ditto +void partialShuffle(Range)(Range r, in size_t n) +if (isRandomAccessRange!Range) +{ + return partialShuffle(r, n, rndGen); +} + +@safe unittest +{ + import std.algorithm; + foreach (RandomGen; PseudoRngTypes) + { + auto a = [0, 1, 1, 2, 3]; + auto b = a.dup; + + // Pick a fixed seed so that the outcome of the statistical + // test below is deterministic. + auto gen = RandomGen(12345); + + // NUM times, pick LEN elements from the array at random. + immutable int LEN = 2; + immutable int NUM = 750; + int[][] chk; + foreach (step; 0 .. NUM) + { + partialShuffle(a, LEN, gen); + chk ~= a[0 .. LEN].dup; + } + + // Check that each possible a[0 .. LEN] was produced at least once. + // For a perfectly random RandomGen, the probability that each + // particular combination failed to appear would be at most + // 0.95 ^^ NUM which is approximately 1,962e-17. + // As long as hardware failure (e.g. bit flip) probability + // is higher, we are fine with this unittest. + sort(chk); + assert(equal(uniq(chk), [ [0,1], [0,2], [0,3], + [1,0], [1,1], [1,2], [1,3], + [2,0], [2,1], [2,3], + [3,0], [3,1], [3,2], ])); + + // Check that all the elements are still there. + sort(a); + assert(equal(a, b)); + } +} + +/** +Rolls a dice with relative probabilities stored in $(D +proportions). Returns the index in $(D proportions) that was chosen. + +Params: + rnd = (optional) random number generator to use; if not + specified, defaults to $(D rndGen) + proportions = forward range or list of individual values + whose elements correspond to the probabilities + with which to choose the corresponding index + value + +Returns: + Random variate drawn from the index values + [0, ... $(D proportions.length) - 1], with the probability + of getting an individual index value $(D i) being proportional to + $(D proportions[i]). +*/ +size_t dice(Rng, Num)(ref Rng rnd, Num[] proportions...) +if (isNumeric!Num && isForwardRange!Rng) +{ + return diceImpl(rnd, proportions); +} + +/// Ditto +size_t dice(R, Range)(ref R rnd, Range proportions) +if (isForwardRange!Range && isNumeric!(ElementType!Range) && !isArray!Range) +{ + return diceImpl(rnd, proportions); +} + +/// Ditto +size_t dice(Range)(Range proportions) +if (isForwardRange!Range && isNumeric!(ElementType!Range) && !isArray!Range) +{ + return diceImpl(rndGen, proportions); +} + +/// Ditto +size_t dice(Num)(Num[] proportions...) +if (isNumeric!Num) +{ + return diceImpl(rndGen, proportions); +} + +/// +@safe unittest +{ + auto x = dice(0.5, 0.5); // x is 0 or 1 in equal proportions + auto y = dice(50, 50); // y is 0 or 1 in equal proportions + auto z = dice(70, 20, 10); // z is 0 70% of the time, 1 20% of the time, + // and 2 10% of the time +} + +private size_t diceImpl(Rng, Range)(ref Rng rng, scope Range proportions) +if (isForwardRange!Range && isNumeric!(ElementType!Range) && isForwardRange!Rng) +in +{ + import std.algorithm.searching : all; + assert(proportions.save.all!"a >= 0"); +} +body +{ + import std.algorithm.iteration : reduce; + import std.exception : enforce; + double sum = reduce!"a + b"(0.0, proportions.save); + enforce(sum > 0, "Proportions in a dice cannot sum to zero"); + immutable point = uniform(0.0, sum, rng); + assert(point < sum); + auto mass = 0.0; + + size_t i = 0; + foreach (e; proportions) + { + mass += e; + if (point < mass) return i; + i++; + } + // this point should not be reached + assert(false); +} + +@safe unittest +{ + auto rnd = Random(unpredictableSeed); + auto i = dice(rnd, 0.0, 100.0); + assert(i == 1); + i = dice(rnd, 100.0, 0.0); + assert(i == 0); + + i = dice(100U, 0U); + assert(i == 0); +} + +/** +Covers a given range $(D r) in a random manner, i.e. goes through each +element of $(D r) once and only once, just in a random order. $(D r) +must be a random-access range with length. + +If no random number generator is passed to $(D randomCover), the +thread-global RNG rndGen will be used internally. + +Params: + r = random-access range to cover + rng = (optional) random number generator to use; + if not specified, defaults to $(D rndGen) + +Returns: + Range whose elements consist of the elements of $(D r), + in random order. Will be a forward range if both $(D r) and + $(D rng) are forward ranges, an input range otherwise. + +Example: +---- +int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]; +foreach (e; randomCover(a)) +{ + writeln(e); +} +---- + +$(B WARNING:) If an alternative RNG is desired, it is essential for this +to be a $(I new) RNG seeded in an unpredictable manner. Passing it a RNG +used elsewhere in the program will result in unintended correlations, +due to the current implementation of RNGs as value types. + +Example: +---- +int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]; +foreach (e; randomCover(a, Random(unpredictableSeed))) // correct! +{ + writeln(e); +} + +foreach (e; randomCover(a, rndGen)) // DANGEROUS!! rndGen gets copied by value +{ + writeln(e); +} + +foreach (e; randomCover(a, rndGen)) // ... so this second random cover +{ // will output the same sequence as + writeln(e); // the previous one. +} +---- + */ +struct RandomCover(Range, UniformRNG = void) +if (isRandomAccessRange!Range && (isUniformRNG!UniformRNG || is(UniformRNG == void))) +{ + private Range _input; + private bool[] _chosen; + private size_t _current; + private size_t _alreadyChosen = 0; + private bool _isEmpty = false; + + static if (is(UniformRNG == void)) + { + this(Range input) + { + _input = input; + _chosen.length = _input.length; + if (_input.empty) + { + _isEmpty = true; + } + else + { + _current = uniform(0, _chosen.length); + } + } + } + else + { + private UniformRNG _rng; + + this(Range input, ref UniformRNG rng) + { + _input = input; + _rng = rng; + _chosen.length = _input.length; + if (_input.empty) + { + _isEmpty = true; + } + else + { + _current = uniform(0, _chosen.length, rng); + } + } + + this(Range input, UniformRNG rng) + { + this(input, rng); + } + } + + static if (hasLength!Range) + { + @property size_t length() + { + return _input.length - _alreadyChosen; + } + } + + @property auto ref front() + { + assert(!_isEmpty); + return _input[_current]; + } + + void popFront() + { + assert(!_isEmpty); + + size_t k = _input.length - _alreadyChosen - 1; + if (k == 0) + { + _isEmpty = true; + ++_alreadyChosen; + return; + } + + size_t i; + foreach (e; _input) + { + if (_chosen[i] || i == _current) { ++i; continue; } + // Roll a dice with k faces + static if (is(UniformRNG == void)) + { + auto chooseMe = uniform(0, k) == 0; + } + else + { + auto chooseMe = uniform(0, k, _rng) == 0; + } + assert(k > 1 || chooseMe); + if (chooseMe) + { + _chosen[_current] = true; + _current = i; + ++_alreadyChosen; + return; + } + --k; + ++i; + } + } + + static if (isForwardRange!UniformRNG) + { + @property typeof(this) save() + { + auto ret = this; + ret._input = _input.save; + ret._rng = _rng.save; + return ret; + } + } + + @property bool empty() { return _isEmpty; } +} + +/// Ditto +auto randomCover(Range, UniformRNG)(Range r, auto ref UniformRNG rng) +if (isRandomAccessRange!Range && isUniformRNG!UniformRNG) +{ + return RandomCover!(Range, UniformRNG)(r, rng); +} + +/// Ditto +auto randomCover(Range)(Range r) +if (isRandomAccessRange!Range) +{ + return RandomCover!(Range, void)(r); +} + +@safe unittest +{ + import std.algorithm; + import std.conv; + int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]; + int[] c; + foreach (UniformRNG; std.meta.AliasSeq!(void, PseudoRngTypes)) + { + static if (is(UniformRNG == void)) + { + auto rc = randomCover(a); + static assert(isInputRange!(typeof(rc))); + static assert(!isForwardRange!(typeof(rc))); + } + else + { + auto rng = UniformRNG(unpredictableSeed); + auto rc = randomCover(a, rng); + static assert(isForwardRange!(typeof(rc))); + // check for constructor passed a value-type RNG + auto rc2 = RandomCover!(int[], UniformRNG)(a, UniformRNG(unpredictableSeed)); + static assert(isForwardRange!(typeof(rc2))); + auto rcEmpty = randomCover(c, rng); + assert(rcEmpty.length == 0); + } + + int[] b = new int[9]; + uint i; + foreach (e; rc) + { + //writeln(e); + b[i++] = e; + } + sort(b); + assert(a == b, text(b)); + } +} + +@safe unittest +{ + // Bugzilla 12589 + int[] r = []; + auto rc = randomCover(r); + assert(rc.length == 0); + assert(rc.empty); + + // Bugzilla 16724 + import std.range : iota; + auto range = iota(10); + auto randy = range.randomCover; + + for (int i=1; i <= range.length; i++) + { + randy.popFront; + assert(randy.length == range.length - i); + } +} + +// RandomSample +/** +Selects a random subsample out of $(D r), containing exactly $(D n) +elements. The order of elements is the same as in the original +range. The total length of $(D r) must be known. If $(D total) is +passed in, the total number of sample is considered to be $(D +total). Otherwise, $(D RandomSample) uses $(D r.length). + +Params: + r = range to sample from + n = number of elements to include in the sample; + must be less than or equal to the total number + of elements in $(D r) and/or the parameter + $(D total) (if provided) + total = (semi-optional) number of elements of $(D r) + from which to select the sample (counting from + the beginning); must be less than or equal to + the total number of elements in $(D r) itself. + May be omitted if $(D r) has the $(D .length) + property and the sample is to be drawn from + all elements of $(D r). + rng = (optional) random number generator to use; + if not specified, defaults to $(D rndGen) + +Returns: + Range whose elements consist of a randomly selected subset of + the elements of $(D r), in the same order as these elements + appear in $(D r) itself. Will be a forward range if both $(D r) + and $(D rng) are forward ranges, an input range otherwise. + +$(D RandomSample) implements Jeffrey Scott Vitter's Algorithm D +(see Vitter $(HTTP dx.doi.org/10.1145/358105.893, 1984), $(HTTP +dx.doi.org/10.1145/23002.23003, 1987)), which selects a sample +of size $(D n) in O(n) steps and requiring O(n) random variates, +regardless of the size of the data being sampled. The exception +to this is if traversing k elements on the input range is itself +an O(k) operation (e.g. when sampling lines from an input file), +in which case the sampling calculation will inevitably be of +O(total). + +RandomSample will throw an exception if $(D total) is verifiably +less than the total number of elements available in the input, +or if $(D n > total). + +If no random number generator is passed to $(D randomSample), the +thread-global RNG rndGen will be used internally. + +Example: +---- +int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]; +// Print 5 random elements picked off from a +foreach (e; randomSample(a, 5)) +{ + writeln(e); +} +---- + +$(B WARNING:) If an alternative RNG is desired, it is essential for this +to be a $(I new) RNG seeded in an unpredictable manner. Passing it a RNG +used elsewhere in the program will result in unintended correlations, +due to the current implementation of RNGs as value types. + +Example: +---- +int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]; +foreach (e; randomSample(a, 5, Random(unpredictableSeed))) // correct! +{ + writeln(e); +} + +foreach (e; randomSample(a, 5, rndGen)) // DANGEROUS!! rndGen gets +{ // copied by value + writeln(e); +} + +foreach (e; randomSample(a, 5, rndGen)) // ... so this second random +{ // sample will select the same + writeln(e); // values as the previous one. +} +---- +*/ +struct RandomSample(Range, UniformRNG = void) +if (isInputRange!Range && (isUniformRNG!UniformRNG || is(UniformRNG == void))) +{ + private size_t _available, _toSelect; + private enum ushort _alphaInverse = 13; // Vitter's recommended value. + private double _Vprime; + private Range _input; + private size_t _index; + private enum Skip { None, A, D } + private Skip _skip = Skip.None; + + // If we're using the default thread-local random number generator then + // we shouldn't store a copy of it here. UniformRNG == void is a sentinel + // for this. If we're using a user-specified generator then we have no + // choice but to store a copy. + static if (is(UniformRNG == void)) + { + static if (hasLength!Range) + { + this(Range input, size_t howMany) + { + _input = input; + initialize(howMany, input.length); + } + } + + this(Range input, size_t howMany, size_t total) + { + _input = input; + initialize(howMany, total); + } + } + else + { + UniformRNG _rng; + + static if (hasLength!Range) + { + this(Range input, size_t howMany, ref UniformRNG rng) + { + _rng = rng; + _input = input; + initialize(howMany, input.length); + } + + this(Range input, size_t howMany, UniformRNG rng) + { + this(input, howMany, rng); + } + } + + this(Range input, size_t howMany, size_t total, ref UniformRNG rng) + { + _rng = rng; + _input = input; + initialize(howMany, total); + } + + this(Range input, size_t howMany, size_t total, UniformRNG rng) + { + this(input, howMany, total, rng); + } + } + + private void initialize(size_t howMany, size_t total) + { + import std.conv : text; + import std.exception : enforce; + _available = total; + _toSelect = howMany; + enforce(_toSelect <= _available, + text("RandomSample: cannot sample ", _toSelect, + " items when only ", _available, " are available")); + static if (hasLength!Range) + { + enforce(_available <= _input.length, + text("RandomSample: specified ", _available, + " items as available when input contains only ", + _input.length)); + } + } + + private void initializeFront() + { + assert(_skip == Skip.None); + // We can save ourselves a random variate by checking right + // at the beginning if we should use Algorithm A. + if ((_alphaInverse * _toSelect) > _available) + { + _skip = Skip.A; + } + else + { + _skip = Skip.D; + _Vprime = newVprime(_toSelect); + } + prime(); + } + +/** + Range primitives. +*/ + @property bool empty() const + { + return _toSelect == 0; + } + +/// Ditto + @property auto ref front() + { + assert(!empty); + // The first sample point must be determined here to avoid + // having it always correspond to the first element of the + // input. The rest of the sample points are determined each + // time we call popFront(). + if (_skip == Skip.None) + { + initializeFront(); + } + return _input.front; + } + +/// Ditto + void popFront() + { + // First we need to check if the sample has + // been initialized in the first place. + if (_skip == Skip.None) + { + initializeFront(); + } + + _input.popFront(); + --_available; + --_toSelect; + ++_index; + prime(); + } + +/// Ditto + static if (isForwardRange!Range && isForwardRange!UniformRNG) + { + @property typeof(this) save() + { + auto ret = this; + ret._input = _input.save; + ret._rng = _rng.save; + return ret; + } + } + +/// Ditto + @property size_t length() + { + return _toSelect; + } + +/** +Returns the index of the visited record. + */ + @property size_t index() + { + if (_skip == Skip.None) + { + initializeFront(); + } + return _index; + } + + private size_t skip() + { + assert(_skip != Skip.None); + + // Step D1: if the number of points still to select is greater + // than a certain proportion of the remaining data points, i.e. + // if n >= alpha * N where alpha = 1/13, we carry out the + // sampling with Algorithm A. + if (_skip == Skip.A) + { + return skipA(); + } + else if ((_alphaInverse * _toSelect) > _available) + { + // We shouldn't get here unless the current selected + // algorithm is D. + assert(_skip == Skip.D); + _skip = Skip.A; + return skipA(); + } + else + { + assert(_skip == Skip.D); + return skipD(); + } + } + +/* +Vitter's Algorithm A, used when the ratio of needed sample values +to remaining data values is sufficiently large. +*/ + private size_t skipA() + { + size_t s; + double v, quot, top; + + if (_toSelect == 1) + { + static if (is(UniformRNG == void)) + { + s = uniform(0, _available); + } + else + { + s = uniform(0, _available, _rng); + } + } + else + { + v = 0; + top = _available - _toSelect; + quot = top / _available; + + static if (is(UniformRNG == void)) + { + v = uniform!"()"(0.0, 1.0); + } + else + { + v = uniform!"()"(0.0, 1.0, _rng); + } + + while (quot > v) + { + ++s; + quot *= (top - s) / (_available - s); + } + } + + return s; + } + +/* +Randomly reset the value of _Vprime. +*/ + private double newVprime(size_t remaining) + { + static if (is(UniformRNG == void)) + { + double r = uniform!"()"(0.0, 1.0); + } + else + { + double r = uniform!"()"(0.0, 1.0, _rng); + } + + return r ^^ (1.0 / remaining); + } + +/* +Vitter's Algorithm D. For an extensive description of the algorithm +and its rationale, see: + + * Vitter, J.S. (1984), "Faster methods for random sampling", + Commun. ACM 27(7): 703--718 + + * Vitter, J.S. (1987) "An efficient algorithm for sequential random + sampling", ACM Trans. Math. Softw. 13(1): 58-67. + +Variable names are chosen to match those in Vitter's paper. +*/ + private size_t skipD() + { + import std.math : isNaN, trunc; + // Confirm that the check in Step D1 is valid and we + // haven't been sent here by mistake + assert((_alphaInverse * _toSelect) <= _available); + + // Now it's safe to use the standard Algorithm D mechanism. + if (_toSelect > 1) + { + size_t s; + size_t qu1 = 1 + _available - _toSelect; + double x, y1; + + assert(!_Vprime.isNaN()); + + while (true) + { + // Step D2: set values of x and u. + while (1) + { + x = _available * (1-_Vprime); + s = cast(size_t) trunc(x); + if (s < qu1) + break; + _Vprime = newVprime(_toSelect); + } + + static if (is(UniformRNG == void)) + { + double u = uniform!"()"(0.0, 1.0); + } + else + { + double u = uniform!"()"(0.0, 1.0, _rng); + } + + y1 = (u * (cast(double) _available) / qu1) ^^ (1.0/(_toSelect - 1)); + + _Vprime = y1 * ((-x/_available)+1.0) * ( qu1/( (cast(double) qu1) - s ) ); + + // Step D3: if _Vprime <= 1.0 our work is done and we return S. + // Otherwise ... + if (_Vprime > 1.0) + { + size_t top = _available - 1, limit; + double y2 = 1.0, bottom; + + if (_toSelect > (s+1)) + { + bottom = _available - _toSelect; + limit = _available - s; + } + else + { + bottom = _available - (s+1); + limit = qu1; + } + + foreach (size_t t; limit .. _available) + { + y2 *= top/bottom; + top--; + bottom--; + } + + // Step D4: decide whether or not to accept the current value of S. + if (_available/(_available-x) < y1 * (y2 ^^ (1.0/(_toSelect-1)))) + { + // If it's not acceptable, we generate a new value of _Vprime + // and go back to the start of the for (;;) loop. + _Vprime = newVprime(_toSelect); + } + else + { + // If it's acceptable we generate a new value of _Vprime + // based on the remaining number of sample points needed, + // and return S. + _Vprime = newVprime(_toSelect-1); + return s; + } + } + else + { + // Return if condition D3 satisfied. + return s; + } + } + } + else + { + // If only one sample point remains to be taken ... + return cast(size_t) trunc(_available * _Vprime); + } + } + + private void prime() + { + if (empty) + { + return; + } + assert(_available && _available >= _toSelect); + immutable size_t s = skip(); + assert(s + _toSelect <= _available); + static if (hasLength!Range) + { + assert(s + _toSelect <= _input.length); + } + assert(!_input.empty); + _input.popFrontExactly(s); + _index += s; + _available -= s; + assert(_available > 0); + } +} + +/// Ditto +auto randomSample(Range)(Range r, size_t n, size_t total) +if (isInputRange!Range) +{ + return RandomSample!(Range, void)(r, n, total); +} + +/// Ditto +auto randomSample(Range)(Range r, size_t n) +if (isInputRange!Range && hasLength!Range) +{ + return RandomSample!(Range, void)(r, n, r.length); +} + +/// Ditto +auto randomSample(Range, UniformRNG)(Range r, size_t n, size_t total, auto ref UniformRNG rng) +if (isInputRange!Range && isUniformRNG!UniformRNG) +{ + return RandomSample!(Range, UniformRNG)(r, n, total, rng); +} + +/// Ditto +auto randomSample(Range, UniformRNG)(Range r, size_t n, auto ref UniformRNG rng) +if (isInputRange!Range && hasLength!Range && isUniformRNG!UniformRNG) +{ + return RandomSample!(Range, UniformRNG)(r, n, r.length, rng); +} + +@system unittest +{ + // @system because it takes the address of a local + import std.conv : text; + import std.exception; + import std.range; + // For test purposes, an infinite input range + struct TestInputRange + { + private auto r = recurrence!"a[n-1] + 1"(0); + bool empty() @property const pure nothrow { return r.empty; } + auto front() @property pure nothrow { return r.front; } + void popFront() pure nothrow { r.popFront(); } + } + static assert(isInputRange!TestInputRange); + static assert(!isForwardRange!TestInputRange); + + int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]; + + foreach (UniformRNG; PseudoRngTypes) + { + auto rng = UniformRNG(1234); + /* First test the most general case: randomSample of input range, with and + * without a specified random number generator. + */ + static assert(isInputRange!(typeof(randomSample(TestInputRange(), 5, 10)))); + static assert(isInputRange!(typeof(randomSample(TestInputRange(), 5, 10, rng)))); + static assert(!isForwardRange!(typeof(randomSample(TestInputRange(), 5, 10)))); + static assert(!isForwardRange!(typeof(randomSample(TestInputRange(), 5, 10, rng)))); + // test case with range initialized by direct call to struct + { + auto sample = + RandomSample!(TestInputRange, UniformRNG) + (TestInputRange(), 5, 10, UniformRNG(unpredictableSeed)); + static assert(isInputRange!(typeof(sample))); + static assert(!isForwardRange!(typeof(sample))); + } + + /* Now test the case of an input range with length. We ignore the cases + * already covered by the previous tests. + */ + static assert(isInputRange!(typeof(randomSample(TestInputRange().takeExactly(10), 5)))); + static assert(isInputRange!(typeof(randomSample(TestInputRange().takeExactly(10), 5, rng)))); + static assert(!isForwardRange!(typeof(randomSample(TestInputRange().takeExactly(10), 5)))); + static assert(!isForwardRange!(typeof(randomSample(TestInputRange().takeExactly(10), 5, rng)))); + // test case with range initialized by direct call to struct + { + auto sample = + RandomSample!(typeof(TestInputRange().takeExactly(10)), UniformRNG) + (TestInputRange().takeExactly(10), 5, 10, UniformRNG(unpredictableSeed)); + static assert(isInputRange!(typeof(sample))); + static assert(!isForwardRange!(typeof(sample))); + } + + // Now test the case of providing a forward range as input. + static assert(!isForwardRange!(typeof(randomSample(a, 5)))); + static if (isForwardRange!UniformRNG) + { + static assert(isForwardRange!(typeof(randomSample(a, 5, rng)))); + // ... and test with range initialized directly + { + auto sample = + RandomSample!(int[], UniformRNG) + (a, 5, UniformRNG(unpredictableSeed)); + static assert(isForwardRange!(typeof(sample))); + } + } + else + { + static assert(isInputRange!(typeof(randomSample(a, 5, rng)))); + static assert(!isForwardRange!(typeof(randomSample(a, 5, rng)))); + // ... and test with range initialized directly + { + auto sample = + RandomSample!(int[], UniformRNG) + (a, 5, UniformRNG(unpredictableSeed)); + static assert(isInputRange!(typeof(sample))); + static assert(!isForwardRange!(typeof(sample))); + } + } + + /* Check that randomSample will throw an error if we claim more + * items are available than there actually are, or if we try to + * sample more items than are available. */ + assert(collectExceptionMsg( + randomSample(a, 5, 15) + ) == "RandomSample: specified 15 items as available when input contains only 10"); + assert(collectExceptionMsg( + randomSample(a, 15) + ) == "RandomSample: cannot sample 15 items when only 10 are available"); + assert(collectExceptionMsg( + randomSample(a, 9, 8) + ) == "RandomSample: cannot sample 9 items when only 8 are available"); + assert(collectExceptionMsg( + randomSample(TestInputRange(), 12, 11) + ) == "RandomSample: cannot sample 12 items when only 11 are available"); + + /* Check that sampling algorithm never accidentally overruns the end of + * the input range. If input is an InputRange without .length, this + * relies on the user specifying the total number of available items + * correctly. + */ + { + uint i = 0; + foreach (e; randomSample(a, a.length)) + { + assert(e == i); + ++i; + } + assert(i == a.length); + + i = 0; + foreach (e; randomSample(TestInputRange(), 17, 17)) + { + assert(e == i); + ++i; + } + assert(i == 17); + } + + + // Check length properties of random samples. + assert(randomSample(a, 5).length == 5); + assert(randomSample(a, 5, 10).length == 5); + assert(randomSample(a, 5, rng).length == 5); + assert(randomSample(a, 5, 10, rng).length == 5); + assert(randomSample(TestInputRange(), 5, 10).length == 5); + assert(randomSample(TestInputRange(), 5, 10, rng).length == 5); + + // ... and emptiness! + assert(randomSample(a, 0).empty); + assert(randomSample(a, 0, 5).empty); + assert(randomSample(a, 0, rng).empty); + assert(randomSample(a, 0, 5, rng).empty); + assert(randomSample(TestInputRange(), 0, 10).empty); + assert(randomSample(TestInputRange(), 0, 10, rng).empty); + + /* Test that the (lazy) evaluation of random samples works correctly. + * + * We cover 2 different cases: a sample where the ratio of sample points + * to total points is greater than the threshold for using Algorithm, and + * one where the ratio is small enough (< 1/13) for Algorithm D to be used. + * + * For each, we also cover the case with and without a specified RNG. + */ + { + // Small sample/source ratio, no specified RNG. + uint i = 0; + foreach (e; randomSample(randomCover(a), 5)) + { + ++i; + } + assert(i == 5); + + // Small sample/source ratio, specified RNG. + i = 0; + foreach (e; randomSample(randomCover(a), 5, rng)) + { + ++i; + } + assert(i == 5); + + // Large sample/source ratio, no specified RNG. + i = 0; + foreach (e; randomSample(TestInputRange(), 123, 123_456)) + { + ++i; + } + assert(i == 123); + + // Large sample/source ratio, specified RNG. + i = 0; + foreach (e; randomSample(TestInputRange(), 123, 123_456, rng)) + { + ++i; + } + assert(i == 123); + + /* Sample/source ratio large enough to start with Algorithm D, + * small enough to switch to Algorithm A. + */ + i = 0; + foreach (e; randomSample(TestInputRange(), 10, 131)) + { + ++i; + } + assert(i == 10); + } + + // Test that the .index property works correctly + { + auto sample1 = randomSample(TestInputRange(), 654, 654_321); + for (; !sample1.empty; sample1.popFront()) + { + assert(sample1.front == sample1.index); + } + + auto sample2 = randomSample(TestInputRange(), 654, 654_321, rng); + for (; !sample2.empty; sample2.popFront()) + { + assert(sample2.front == sample2.index); + } + + /* Check that it also works if .index is called before .front. + * See: http://d.puremagic.com/issues/show_bug.cgi?id=10322 + */ + auto sample3 = randomSample(TestInputRange(), 654, 654_321); + for (; !sample3.empty; sample3.popFront()) + { + assert(sample3.index == sample3.front); + } + + auto sample4 = randomSample(TestInputRange(), 654, 654_321, rng); + for (; !sample4.empty; sample4.popFront()) + { + assert(sample4.index == sample4.front); + } + } + + /* Test behaviour if .popFront() is called before sample is read. + * This is a rough-and-ready check that the statistical properties + * are in the ballpark -- not a proper validation of statistical + * quality! This incidentally also checks for reference-type + * initialization bugs, as the foreach () loop will operate on a + * copy of the popFronted (and hence initialized) sample. + */ + { + size_t count0, count1, count99; + foreach (_; 0 .. 100_000) + { + auto sample = randomSample(iota(100), 5, &rng); + sample.popFront(); + foreach (s; sample) + { + if (s == 0) + { + ++count0; + } + else if (s == 1) + { + ++count1; + } + else if (s == 99) + { + ++count99; + } + } + } + /* Statistical assumptions here: this is a sequential sampling process + * so (i) 0 can only be the first sample point, so _can't_ be in the + * remainder of the sample after .popFront() is called. (ii) By similar + * token, 1 can only be in the remainder if it's the 2nd point of the + * whole sample, and hence if 0 was the first; probability of 0 being + * first and 1 second is 5/100 * 4/99 (thank you, Algorithm S:-) and + * so the mean count of 1 should be about 202. Finally, 99 can only + * be the _last_ sample point to be picked, so its probability of + * inclusion should be independent of the .popFront() and it should + * occur with frequency 5/100, hence its count should be about 5000. + * Unfortunately we have to set quite a high tolerance because with + * sample size small enough for unittests to run in reasonable time, + * the variance can be quite high. + */ + assert(count0 == 0); + assert(count1 < 300, text("1: ", count1, " > 300.")); + assert(4_700 < count99, text("99: ", count99, " < 4700.")); + assert(count99 < 5_300, text("99: ", count99, " > 5300.")); + } + + /* Odd corner-cases: RandomSample has 2 constructors that are not called + * by the randomSample() helper functions, but that can be used if the + * constructor is called directly. These cover the case of the user + * specifying input but not input length. + */ + { + auto input1 = TestInputRange().takeExactly(456_789); + static assert(hasLength!(typeof(input1))); + auto sample1 = RandomSample!(typeof(input1), void)(input1, 789); + static assert(isInputRange!(typeof(sample1))); + static assert(!isForwardRange!(typeof(sample1))); + assert(sample1.length == 789); + assert(sample1._available == 456_789); + uint i = 0; + for (; !sample1.empty; sample1.popFront()) + { + assert(sample1.front == sample1.index); + ++i; + } + assert(i == 789); + + auto input2 = TestInputRange().takeExactly(456_789); + static assert(hasLength!(typeof(input2))); + auto sample2 = RandomSample!(typeof(input2), typeof(rng))(input2, 789, rng); + static assert(isInputRange!(typeof(sample2))); + static assert(!isForwardRange!(typeof(sample2))); + assert(sample2.length == 789); + assert(sample2._available == 456_789); + i = 0; + for (; !sample2.empty; sample2.popFront()) + { + assert(sample2.front == sample2.index); + ++i; + } + assert(i == 789); + } + + /* Test that the save property works where input is a forward range, + * and RandomSample is using a (forward range) random number generator + * that is not rndGen. + */ + static if (isForwardRange!UniformRNG) + { + auto sample1 = randomSample(a, 5, rng); + auto sample2 = sample1.save; + assert(sample1.array() == sample2.array()); + } + + // Bugzilla 8314 + { + auto sample(RandomGen)(uint seed) { return randomSample(a, 1, RandomGen(seed)).front; } + + // Start from 1 because not all RNGs accept 0 as seed. + immutable fst = sample!UniformRNG(1); + uint n = 1; + while (sample!UniformRNG(++n) == fst && n < n.max) {} + assert(n < n.max); + } + } +} diff --git a/libphobos/src/std/range/interfaces.d b/libphobos/src/std/range/interfaces.d new file mode 100644 index 0000000..7207776 --- /dev/null +++ b/libphobos/src/std/range/interfaces.d @@ -0,0 +1,567 @@ +/** +This module is a submodule of $(MREF std, range). + +The main $(MREF std, range) module provides template-based tools for working with +ranges, but sometimes an object-based interface for ranges is needed, such as +when runtime polymorphism is required. For this purpose, this submodule +provides a number of object and $(D interface) definitions that can be used to +wrap around _range objects created by the $(MREF std, range) templates. + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE , + $(TR $(TD $(LREF InputRange)) + $(TD Wrapper for input ranges. + )) + $(TR $(TD $(LREF InputAssignable)) + $(TD Wrapper for input ranges with assignable elements. + )) + $(TR $(TD $(LREF ForwardRange)) + $(TD Wrapper for forward ranges. + )) + $(TR $(TD $(LREF ForwardAssignable)) + $(TD Wrapper for forward ranges with assignable elements. + )) + $(TR $(TD $(LREF BidirectionalRange)) + $(TD Wrapper for bidirectional ranges. + )) + $(TR $(TD $(LREF BidirectionalAssignable)) + $(TD Wrapper for bidirectional ranges with assignable elements. + )) + $(TR $(TD $(LREF RandomAccessFinite)) + $(TD Wrapper for finite random-access ranges. + )) + $(TR $(TD $(LREF RandomAccessAssignable)) + $(TD Wrapper for finite random-access ranges with assignable elements. + )) + $(TR $(TD $(LREF RandomAccessInfinite)) + $(TD Wrapper for infinite random-access ranges. + )) + $(TR $(TD $(LREF OutputRange)) + $(TD Wrapper for output ranges. + )) + $(TR $(TD $(LREF OutputRangeObject)) + $(TD Class that implements the $(D OutputRange) interface and wraps the + $(D put) methods in virtual functions. + $(TR $(TD $(LREF outputRangeObject)) + Convenience function for creating an $(D OutputRangeObject) with a base + range of type R that accepts types E. + )) + $(TR $(TD $(LREF InputRangeObject)) + $(TD Class that implements the $(D InputRange) interface and wraps the + input _range methods in virtual functions. + )) + $(TR $(TD $(LREF inputRangeObject)) + $(TD Convenience function for creating an $(D InputRangeObject) + of the proper type. + )) + $(TR $(TD $(LREF MostDerivedInputRange)) + $(TD Returns the interface type that best matches the range.) + )) +) + + +Source: $(PHOBOSSRC std/range/_interfaces.d) + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(HTTP erdani.com, Andrei Alexandrescu), David Simcha, +and Jonathan M Davis. Credit for some of the ideas in building this module goes +to $(HTTP fantascienza.net/leonardo/so/, Leonardo Maffi). +*/ +module std.range.interfaces; + +import std.meta; +import std.range.primitives; +import std.traits; + +/**These interfaces are intended to provide virtual function-based wrappers + * around input ranges with element type E. This is useful where a well-defined + * binary interface is required, such as when a DLL function or virtual function + * needs to accept a generic range as a parameter. Note that + * $(REF_ALTTEXT isInputRange, isInputRange, std, range, primitives) + * and friends check for conformance to structural interfaces + * not for implementation of these $(D interface) types. + * + * Limitations: + * + * These interfaces are not capable of forwarding $(D ref) access to elements. + * + * Infiniteness of the wrapped range is not propagated. + * + * Length is not propagated in the case of non-random access ranges. + * + * See_Also: + * $(LREF inputRangeObject) + */ +interface InputRange(E) { + /// + @property E front(); + + /// + E moveFront(); + + /// + void popFront(); + + /// + @property bool empty(); + + /* Measurements of the benefits of using opApply instead of range primitives + * for foreach, using timings for iterating over an iota(100_000_000) range + * with an empty loop body, using the same hardware in each case: + * + * Bare Iota struct, range primitives: 278 milliseconds + * InputRangeObject, opApply: 436 milliseconds (1.57x penalty) + * InputRangeObject, range primitives: 877 milliseconds (3.15x penalty) + */ + + /**$(D foreach) iteration uses opApply, since one delegate call per loop + * iteration is faster than three virtual function calls. + */ + int opApply(scope int delegate(E)); + + /// Ditto + int opApply(scope int delegate(size_t, E)); + +} + +/// +@safe unittest +{ + import std.algorithm.iteration : map; + import std.range : iota; + + void useRange(InputRange!int range) { + // Function body. + } + + // Create a range type. + auto squares = map!"a * a"(iota(10)); + + // Wrap it in an interface. + auto squaresWrapped = inputRangeObject(squares); + + // Use it. + useRange(squaresWrapped); +} + +/**Interface for a forward range of type $(D E).*/ +interface ForwardRange(E) : InputRange!E { + /// + @property ForwardRange!E save(); +} + +/**Interface for a bidirectional range of type $(D E).*/ +interface BidirectionalRange(E) : ForwardRange!(E) { + /// + @property BidirectionalRange!E save(); + + /// + @property E back(); + + /// + E moveBack(); + + /// + void popBack(); +} + +/**Interface for a finite random access range of type $(D E).*/ +interface RandomAccessFinite(E) : BidirectionalRange!(E) { + /// + @property RandomAccessFinite!E save(); + + /// + E opIndex(size_t); + + /// + E moveAt(size_t); + + /// + @property size_t length(); + + /// + alias opDollar = length; + + // Can't support slicing until issues with requiring slicing for all + // finite random access ranges are fully resolved. + version (none) + { + /// + RandomAccessFinite!E opSlice(size_t, size_t); + } +} + +/**Interface for an infinite random access range of type $(D E).*/ +interface RandomAccessInfinite(E) : ForwardRange!E { + /// + E moveAt(size_t); + + /// + @property RandomAccessInfinite!E save(); + + /// + E opIndex(size_t); +} + +/**Adds assignable elements to InputRange.*/ +interface InputAssignable(E) : InputRange!E { + /// + @property void front(E newVal); + + alias front = InputRange!E.front; // overload base interface method +} + +@safe unittest +{ + static assert(isInputRange!(InputAssignable!int)); +} + +/**Adds assignable elements to ForwardRange.*/ +interface ForwardAssignable(E) : InputAssignable!E, ForwardRange!E { + /// + @property ForwardAssignable!E save(); +} + +/**Adds assignable elements to BidirectionalRange.*/ +interface BidirectionalAssignable(E) : ForwardAssignable!E, BidirectionalRange!E { + /// + @property BidirectionalAssignable!E save(); + + /// + @property void back(E newVal); +} + +/**Adds assignable elements to RandomAccessFinite.*/ +interface RandomFiniteAssignable(E) : RandomAccessFinite!E, BidirectionalAssignable!E { + /// + @property RandomFiniteAssignable!E save(); + + /// + void opIndexAssign(E val, size_t index); +} + +/**Interface for an output range of type $(D E). Usage is similar to the + * $(D InputRange) interface and descendants.*/ +interface OutputRange(E) { + /// + void put(E); +} + +@safe unittest +{ + // 6973 + static assert(isOutputRange!(OutputRange!int, int)); +} + + +// CTFE function that generates mixin code for one put() method for each +// type E. +private string putMethods(E...)() +{ + import std.conv : to; + + string ret; + + foreach (ti, Unused; E) + { + ret ~= "void put(E[" ~ to!string(ti) ~ "] e) { .put(_range, e); }"; + } + + return ret; +} + +/**Implements the $(D OutputRange) interface for all types E and wraps the + * $(D put) method for each type $(D E) in a virtual function. + */ +class OutputRangeObject(R, E...) : staticMap!(OutputRange, E) { + // @BUG 4689: There should be constraints on this template class, but + // DMD won't let me put them in. + private R _range; + + /// + this(R range) { + this._range = range; + } + + mixin(putMethods!E()); +} + + +/**Returns the interface type that best matches $(D R).*/ +template MostDerivedInputRange(R) +if (isInputRange!(Unqual!R)) +{ + private alias E = ElementType!R; + + static if (isRandomAccessRange!R) + { + static if (isInfinite!R) + { + alias MostDerivedInputRange = RandomAccessInfinite!E; + } + else static if (hasAssignableElements!R) + { + alias MostDerivedInputRange = RandomFiniteAssignable!E; + } + else + { + alias MostDerivedInputRange = RandomAccessFinite!E; + } + } + else static if (isBidirectionalRange!R) + { + static if (hasAssignableElements!R) + { + alias MostDerivedInputRange = BidirectionalAssignable!E; + } + else + { + alias MostDerivedInputRange = BidirectionalRange!E; + } + } + else static if (isForwardRange!R) + { + static if (hasAssignableElements!R) + { + alias MostDerivedInputRange = ForwardAssignable!E; + } + else + { + alias MostDerivedInputRange = ForwardRange!E; + } + } + else + { + static if (hasAssignableElements!R) + { + alias MostDerivedInputRange = InputAssignable!E; + } + else + { + alias MostDerivedInputRange = InputRange!E; + } + } +} + +/**Implements the most derived interface that $(D R) works with and wraps + * all relevant range primitives in virtual functions. If $(D R) is already + * derived from the $(D InputRange) interface, aliases itself away. + */ +template InputRangeObject(R) +if (isInputRange!(Unqual!R)) +{ + static if (is(R : InputRange!(ElementType!R))) + { + alias InputRangeObject = R; + } + else static if (!is(Unqual!R == R)) + { + alias InputRangeObject = InputRangeObject!(Unqual!R); + } + else + { + + /// + class InputRangeObject : MostDerivedInputRange!(R) { + private R _range; + private alias E = ElementType!R; + + this(R range) { + this._range = range; + } + + @property E front() { return _range.front; } + + E moveFront() { + return _range.moveFront(); + } + + void popFront() { _range.popFront(); } + @property bool empty() { return _range.empty; } + + static if (isForwardRange!R) + { + @property typeof(this) save() { + return new typeof(this)(_range.save); + } + } + + static if (hasAssignableElements!R) + { + @property void front(E newVal) { + _range.front = newVal; + } + } + + static if (isBidirectionalRange!R) + { + @property E back() { return _range.back; } + + E moveBack() { + return _range.moveBack(); + } + + void popBack() { return _range.popBack(); } + + static if (hasAssignableElements!R) + { + @property void back(E newVal) { + _range.back = newVal; + } + } + } + + static if (isRandomAccessRange!R) + { + E opIndex(size_t index) { + return _range[index]; + } + + E moveAt(size_t index) { + return _range.moveAt(index); + } + + static if (hasAssignableElements!R) + { + void opIndexAssign(E val, size_t index) { + _range[index] = val; + } + } + + static if (!isInfinite!R) + { + @property size_t length() { + return _range.length; + } + + alias opDollar = length; + + // Can't support slicing until all the issues with + // requiring slicing support for finite random access + // ranges are resolved. + version (none) + { + typeof(this) opSlice(size_t lower, size_t upper) { + return new typeof(this)(_range[lower .. upper]); + } + } + } + } + + // Optimization: One delegate call is faster than three virtual + // function calls. Use opApply for foreach syntax. + int opApply(scope int delegate(E) dg) { + int res; + + for (auto r = _range; !r.empty; r.popFront()) + { + res = dg(r.front); + if (res) break; + } + + return res; + } + + int opApply(scope int delegate(size_t, E) dg) { + int res; + + size_t i = 0; + for (auto r = _range; !r.empty; r.popFront()) + { + res = dg(i, r.front); + if (res) break; + i++; + } + + return res; + } + } + } +} + +/**Convenience function for creating an $(D InputRangeObject) of the proper type. + * See $(LREF InputRange) for an example. + */ +InputRangeObject!R inputRangeObject(R)(R range) +if (isInputRange!R) +{ + static if (is(R : InputRange!(ElementType!R))) + { + return range; + } + else + { + return new InputRangeObject!R(range); + } +} + +/**Convenience function for creating an $(D OutputRangeObject) with a base range + * of type $(D R) that accepts types $(D E). +*/ +template outputRangeObject(E...) { + + /// + OutputRangeObject!(R, E) outputRangeObject(R)(R range) { + return new OutputRangeObject!(R, E)(range); + } +} + +/// +@safe unittest +{ + import std.array; + auto app = appender!(uint[])(); + auto appWrapped = outputRangeObject!(uint, uint[])(app); + static assert(is(typeof(appWrapped) : OutputRange!(uint[]))); + static assert(is(typeof(appWrapped) : OutputRange!(uint))); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + import std.array; + import std.internal.test.dummyrange; + + static void testEquality(R)(iInputRange r1, R r2) { + assert(equal(r1, r2)); + } + + auto arr = [1,2,3,4]; + RandomFiniteAssignable!int arrWrapped = inputRangeObject(arr); + static assert(isRandomAccessRange!(typeof(arrWrapped))); + // static assert(hasSlicing!(typeof(arrWrapped))); + static assert(hasLength!(typeof(arrWrapped))); + arrWrapped[0] = 0; + assert(arr[0] == 0); + assert(arr.moveFront() == 0); + assert(arr.moveBack() == 4); + assert(arr.moveAt(1) == 2); + + foreach (elem; arrWrapped) {} + foreach (i, elem; arrWrapped) {} + + assert(inputRangeObject(arrWrapped) is arrWrapped); + + foreach (DummyType; AllDummyRanges) + { + auto d = DummyType.init; + static assert(propagatesRangeType!(DummyType, + typeof(inputRangeObject(d)))); + static assert(propagatesRangeType!(DummyType, + MostDerivedInputRange!DummyType)); + InputRange!uint wrapped = inputRangeObject(d); + assert(equal(wrapped, d)); + } + + // Test output range stuff. + auto app = appender!(uint[])(); + auto appWrapped = outputRangeObject!(uint, uint[])(app); + static assert(is(typeof(appWrapped) : OutputRange!(uint[]))); + static assert(is(typeof(appWrapped) : OutputRange!(uint))); + + appWrapped.put(1); + appWrapped.put([2, 3]); + assert(app.data.length == 3); + assert(equal(app.data, [1,2,3])); +} diff --git a/libphobos/src/std/range/package.d b/libphobos/src/std/range/package.d new file mode 100644 index 0000000..fe581f3 --- /dev/null +++ b/libphobos/src/std/range/package.d @@ -0,0 +1,12019 @@ +// Written in the D programming language. + +/** +This module defines the notion of a range. Ranges generalize the concept of +arrays, lists, or anything that involves sequential access. This abstraction +enables the same set of algorithms (see $(MREF std, algorithm)) to be used +with a vast variety of different concrete types. For example, +a linear search algorithm such as $(REF find, std, algorithm, searching) +works not just for arrays, but for linked-lists, input files, +incoming network data, etc. + +Guides: + +There are many articles available that can bolster understanding ranges: + +$(UL + $(LI Ali Çehreli's $(HTTP ddili.org/ders/d.en/ranges.html, tutorial on _ranges) + for the basics of working with and creating range-based code.) + $(LI Jonathan M. Davis $(LINK2 http://dconf.org/2015/talks/davis.html, $(I Introduction to Ranges)) + talk at DConf 2015 a vivid introduction from its core constructs to practical advice.) + $(LI The DLang Tour's $(LINK2 http://tour.dlang.org/tour/en/basics/ranges, chapter on ranges) + for an interactive introduction.) + $(LI H. S. Teoh's $(LINK2 http://wiki.dlang.org/Component_programming_with_ranges, tutorial on + component programming with ranges) for a real-world showcase of the influence + of _range-based programming on complex algorithms.) + $(LI Andrei Alexandrescu's article + $(LINK2 http://www.informit.com/articles/printerfriendly.aspx?p=1407357$(AMP)rll=1, + $(I On Iteration)) for conceptual aspect of ranges and the motivation + ) +) + +Submodules: + +This module has two submodules: + +The $(MREF std, _range, primitives) submodule +provides basic _range functionality. It defines several templates for testing +whether a given object is a _range, what kind of _range it is, and provides +some common _range operations. + +The $(MREF std, _range, interfaces) submodule +provides object-based interfaces for working with ranges via runtime +polymorphism. + +The remainder of this module provides a rich set of _range creation and +composition templates that let you construct new ranges out of existing ranges: + + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE , + $(TR $(TD $(LREF chain)) + $(TD Concatenates several ranges into a single _range. + )) + $(TR $(TD $(LREF choose)) + $(TD Chooses one of two ranges at runtime based on a boolean condition. + )) + $(TR $(TD $(LREF chooseAmong)) + $(TD Chooses one of several ranges at runtime based on an index. + )) + $(TR $(TD $(LREF chunks)) + $(TD Creates a _range that returns fixed-size chunks of the original + _range. + )) + $(TR $(TD $(LREF cycle)) + $(TD Creates an infinite _range that repeats the given forward _range + indefinitely. Good for implementing circular buffers. + )) + $(TR $(TD $(LREF drop)) + $(TD Creates the _range that results from discarding the first $(I n) + elements from the given _range. + )) + $(TR $(TD $(LREF dropBack)) + $(TD Creates the _range that results from discarding the last $(I n) + elements from the given _range. + )) + $(TR $(TD $(LREF dropExactly)) + $(TD Creates the _range that results from discarding exactly $(I n) + of the first elements from the given _range. + )) + $(TR $(TD $(LREF dropBackExactly)) + $(TD Creates the _range that results from discarding exactly $(I n) + of the last elements from the given _range. + )) + $(TR $(TD $(LREF dropOne)) + $(TD Creates the _range that results from discarding + the first element from the given _range. + )) + $(TR $(TD $(D $(LREF dropBackOne))) + $(TD Creates the _range that results from discarding + the last element from the given _range. + )) + $(TR $(TD $(LREF enumerate)) + $(TD Iterates a _range with an attached index variable. + )) + $(TR $(TD $(LREF evenChunks)) + $(TD Creates a _range that returns a number of chunks of + approximately equal length from the original _range. + )) + $(TR $(TD $(LREF frontTransversal)) + $(TD Creates a _range that iterates over the first elements of the + given ranges. + )) + $(TR $(TD $(LREF generate)) + $(TD Creates a _range by successive calls to a given function. This + allows to create ranges as a single delegate. + )) + $(TR $(TD $(LREF indexed)) + $(TD Creates a _range that offers a view of a given _range as though + its elements were reordered according to a given _range of indices. + )) + $(TR $(TD $(LREF iota)) + $(TD Creates a _range consisting of numbers between a starting point + and ending point, spaced apart by a given interval. + )) + $(TR $(TD $(LREF lockstep)) + $(TD Iterates $(I n) _ranges in lockstep, for use in a $(D foreach) + loop. Similar to $(D zip), except that $(D lockstep) is designed + especially for $(D foreach) loops. + )) + $(TR $(TD $(LREF NullSink)) + $(TD An output _range that discards the data it receives. + )) + $(TR $(TD $(LREF only)) + $(TD Creates a _range that iterates over the given arguments. + )) + $(TR $(TD $(LREF padLeft)) + $(TD Pads a _range to a specified length by adding a given element to + the front of the _range. Is lazy if the _range has a known length. + )) + $(TR $(TD $(LREF padRight)) + $(TD Lazily pads a _range to a specified length by adding a given element to + the back of the _range. + )) + $(TR $(TD $(LREF radial)) + $(TD Given a random-access _range and a starting point, creates a + _range that alternately returns the next left and next right element to + the starting point. + )) + $(TR $(TD $(LREF recurrence)) + $(TD Creates a forward _range whose values are defined by a + mathematical recurrence relation. + )) + $(TR $(TD $(LREF refRange)) + $(TD Pass a _range by reference. Both the original _range and the RefRange + will always have the exact same elements. + Any operation done on one will affect the other. + )) + $(TR $(TD $(LREF repeat)) + $(TD Creates a _range that consists of a single element repeated $(I n) + times, or an infinite _range repeating that element indefinitely. + )) + $(TR $(TD $(LREF retro)) + $(TD Iterates a bidirectional _range backwards. + )) + $(TR $(TD $(LREF roundRobin)) + $(TD Given $(I n) ranges, creates a new _range that return the $(I n) + first elements of each _range, in turn, then the second element of each + _range, and so on, in a round-robin fashion. + )) + $(TR $(TD $(LREF sequence)) + $(TD Similar to $(D recurrence), except that a random-access _range is + created. + )) + $(COMMENT Explicitly undocumented to delay the release until 2.076 + $(TR $(TD $(D $(LREF slide))) + $(TD Creates a _range that returns a fixed-size sliding window + over the original _range. Unlike chunks, + it advances a configurable number of items at a time, + not one chunk at a time. + )) + ) + $(TR $(TD $(LREF stride)) + $(TD Iterates a _range with stride $(I n). + )) + $(TR $(TD $(LREF tail)) + $(TD Return a _range advanced to within $(D n) elements of the end of + the given _range. + )) + $(TR $(TD $(LREF take)) + $(TD Creates a sub-_range consisting of only up to the first $(I n) + elements of the given _range. + )) + $(TR $(TD $(LREF takeExactly)) + $(TD Like $(D take), but assumes the given _range actually has $(I n) + elements, and therefore also defines the $(D length) property. + )) + $(TR $(TD $(LREF takeNone)) + $(TD Creates a random-access _range consisting of zero elements of the + given _range. + )) + $(TR $(TD $(LREF takeOne)) + $(TD Creates a random-access _range consisting of exactly the first + element of the given _range. + )) + $(TR $(TD $(LREF tee)) + $(TD Creates a _range that wraps a given _range, forwarding along + its elements while also calling a provided function with each element. + )) + $(TR $(TD $(LREF transposed)) + $(TD Transposes a _range of ranges. + )) + $(TR $(TD $(LREF transversal)) + $(TD Creates a _range that iterates over the $(I n)'th elements of the + given random-access ranges. + )) + $(TR $(TD $(LREF zip)) + $(TD Given $(I n) _ranges, creates a _range that successively returns a + tuple of all the first elements, a tuple of all the second elements, + etc. + )) +) + +Sortedness: + +Ranges whose elements are sorted afford better efficiency with certain +operations. For this, the $(LREF assumeSorted) function can be used to +construct a $(LREF SortedRange) from a pre-sorted _range. The $(REF +sort, std, algorithm, sorting) function also conveniently +returns a $(LREF SortedRange). $(LREF SortedRange) objects provide some additional +_range operations that take advantage of the fact that the _range is sorted. + +Source: $(PHOBOSSRC std/_range/_package.d) + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(HTTP erdani.com, Andrei Alexandrescu), David Simcha, Jonathan M Davis, +and Jack Stouffer. Credit for some of the ideas in building this module goes +to $(HTTP fantascienza.net/leonardo/so/, Leonardo Maffi). + */ +module std.range; + +public import std.array; +public import std.range.interfaces; +public import std.range.primitives; +public import std.typecons : Flag, Yes, No; + +import std.meta; // allSatisfy, staticMap +import std.traits; // CommonType, isCallable, isFloatingPoint, isIntegral, + // isPointer, isSomeFunction, isStaticArray, Unqual + + +/** +Iterates a bidirectional range backwards. The original range can be +accessed by using the $(D source) property. Applying retro twice to +the same range yields the original range. + +Params: + r = the bidirectional range to iterate backwards + +Returns: + A bidirectional range with length if `r` also provides a length. Or, + if `r` is a random access range, then the return value will be random + access as well. +See_Also: + $(REF reverse, std,algorithm,mutation) for mutating the source range directly. + */ +auto retro(Range)(Range r) +if (isBidirectionalRange!(Unqual!Range)) +{ + // Check for retro(retro(r)) and just return r in that case + static if (is(typeof(retro(r.source)) == Range)) + { + return r.source; + } + else + { + static struct Result() + { + private alias R = Unqual!Range; + + // User code can get and set source, too + R source; + + static if (hasLength!R) + { + size_t retroIndex(size_t n) + { + return source.length - n - 1; + } + } + + public: + alias Source = R; + + @property bool empty() { return source.empty; } + @property auto save() + { + return Result(source.save); + } + @property auto ref front() { return source.back; } + void popFront() { source.popBack(); } + @property auto ref back() { return source.front; } + void popBack() { source.popFront(); } + + static if (is(typeof(source.moveBack()))) + { + ElementType!R moveFront() + { + return source.moveBack(); + } + } + + static if (is(typeof(source.moveFront()))) + { + ElementType!R moveBack() + { + return source.moveFront(); + } + } + + static if (hasAssignableElements!R) + { + @property void front(ElementType!R val) + { + source.back = val; + } + + @property void back(ElementType!R val) + { + source.front = val; + } + } + + static if (isRandomAccessRange!(R) && hasLength!(R)) + { + auto ref opIndex(size_t n) { return source[retroIndex(n)]; } + + static if (hasAssignableElements!R) + { + void opIndexAssign(ElementType!R val, size_t n) + { + source[retroIndex(n)] = val; + } + } + + static if (is(typeof(source.moveAt(0)))) + { + ElementType!R moveAt(size_t index) + { + return source.moveAt(retroIndex(index)); + } + } + + static if (hasSlicing!R) + typeof(this) opSlice(size_t a, size_t b) + { + return typeof(this)(source[source.length - b .. source.length - a]); + } + } + + static if (hasLength!R) + { + @property auto length() + { + return source.length; + } + + alias opDollar = length; + } + } + + return Result!()(r); + } +} + + +/// +pure @safe nothrow @nogc unittest +{ + import std.algorithm.comparison : equal; + int[5] a = [ 1, 2, 3, 4, 5 ]; + int[5] b = [ 5, 4, 3, 2, 1 ]; + assert(equal(retro(a[]), b[])); + assert(retro(a[]).source is a[]); + assert(retro(retro(a[])) is a[]); +} + +pure @safe nothrow unittest +{ + import std.algorithm.comparison : equal; + static assert(isBidirectionalRange!(typeof(retro("hello")))); + int[] a; + static assert(is(typeof(a) == typeof(retro(retro(a))))); + assert(retro(retro(a)) is a); + static assert(isRandomAccessRange!(typeof(retro([1, 2, 3])))); + void test(int[] input, int[] witness) + { + auto r = retro(input); + assert(r.front == witness.front); + assert(r.back == witness.back); + assert(equal(r, witness)); + } + test([ 1 ], [ 1 ]); + test([ 1, 2 ], [ 2, 1 ]); + test([ 1, 2, 3 ], [ 3, 2, 1 ]); + test([ 1, 2, 3, 4 ], [ 4, 3, 2, 1 ]); + test([ 1, 2, 3, 4, 5 ], [ 5, 4, 3, 2, 1 ]); + test([ 1, 2, 3, 4, 5, 6 ], [ 6, 5, 4, 3, 2, 1 ]); + + immutable foo = [1,2,3].idup; + auto r = retro(foo); + assert(equal(r, [3, 2, 1])); +} + +pure @safe nothrow unittest +{ + import std.internal.test.dummyrange : AllDummyRanges, propagatesRangeType, + ReturnBy; + + foreach (DummyType; AllDummyRanges) + { + static if (!isBidirectionalRange!DummyType) + { + static assert(!__traits(compiles, Retro!DummyType)); + } + else + { + DummyType dummyRange; + dummyRange.reinit(); + + auto myRetro = retro(dummyRange); + static assert(propagatesRangeType!(typeof(myRetro), DummyType)); + assert(myRetro.front == 10); + assert(myRetro.back == 1); + assert(myRetro.moveFront() == 10); + assert(myRetro.moveBack() == 1); + + static if (isRandomAccessRange!DummyType && hasLength!DummyType) + { + assert(myRetro[0] == myRetro.front); + assert(myRetro.moveAt(2) == 8); + + static if (DummyType.r == ReturnBy.Reference) + { + { + myRetro[9]++; + scope(exit) myRetro[9]--; + assert(dummyRange[0] == 2); + myRetro.front++; + scope(exit) myRetro.front--; + assert(myRetro.front == 11); + myRetro.back++; + scope(exit) myRetro.back--; + assert(myRetro.back == 3); + } + + { + myRetro.front = 0xFF; + scope(exit) myRetro.front = 10; + assert(dummyRange.back == 0xFF); + + myRetro.back = 0xBB; + scope(exit) myRetro.back = 1; + assert(dummyRange.front == 0xBB); + + myRetro[1] = 11; + scope(exit) myRetro[1] = 8; + assert(dummyRange[8] == 11); + } + } + } + } + } +} + +pure @safe nothrow @nogc unittest +{ + import std.algorithm.comparison : equal; + auto LL = iota(1L, 4L); + auto r = retro(LL); + long[3] excepted = [3, 2, 1]; + assert(equal(r, excepted[])); +} + +// Issue 12662 +pure @safe nothrow @nogc unittest +{ + int[3] src = [1,2,3]; + int[] data = src[]; + foreach_reverse (x; data) {} + foreach (x; data.retro) {} +} + + +/** +Iterates range $(D r) with stride $(D n). If the range is a +random-access range, moves by indexing into the range; otherwise, +moves by successive calls to $(D popFront). Applying stride twice to +the same range results in a stride with a step that is the +product of the two applications. It is an error for $(D n) to be 0. + +Params: + r = the input range to stride over + n = the number of elements to skip over + +Returns: + At minimum, an input range. The resulting range will adopt the + range primitives of the underlying range as long as + $(REF hasLength, std,range,primitives) is `true`. + */ +auto stride(Range)(Range r, size_t n) +if (isInputRange!(Unqual!Range)) +in +{ + assert(n != 0, "stride cannot have step zero."); +} +body +{ + import std.algorithm.comparison : min; + + static if (is(typeof(stride(r.source, n)) == Range)) + { + // stride(stride(r, n1), n2) is stride(r, n1 * n2) + return stride(r.source, r._n * n); + } + else + { + static struct Result + { + private alias R = Unqual!Range; + public R source; + private size_t _n; + + // Chop off the slack elements at the end + static if (hasLength!R && + (isRandomAccessRange!R && hasSlicing!R + || isBidirectionalRange!R)) + private void eliminateSlackElements() + { + auto slack = source.length % _n; + + if (slack) + { + slack--; + } + else if (!source.empty) + { + slack = min(_n, source.length) - 1; + } + else + { + slack = 0; + } + if (!slack) return; + static if (isRandomAccessRange!R && hasLength!R && hasSlicing!R) + { + source = source[0 .. source.length - slack]; + } + else static if (isBidirectionalRange!R) + { + foreach (i; 0 .. slack) + { + source.popBack(); + } + } + } + + static if (isForwardRange!R) + { + @property auto save() + { + return Result(source.save, _n); + } + } + + static if (isInfinite!R) + { + enum bool empty = false; + } + else + { + @property bool empty() + { + return source.empty; + } + } + + @property auto ref front() + { + return source.front; + } + + static if (is(typeof(.moveFront(source)))) + { + ElementType!R moveFront() + { + return source.moveFront(); + } + } + + static if (hasAssignableElements!R) + { + @property void front(ElementType!R val) + { + source.front = val; + } + } + + void popFront() + { + source.popFrontN(_n); + } + + static if (isBidirectionalRange!R && hasLength!R) + { + void popBack() + { + popBackN(source, _n); + } + + @property auto ref back() + { + eliminateSlackElements(); + return source.back; + } + + static if (is(typeof(.moveBack(source)))) + { + ElementType!R moveBack() + { + eliminateSlackElements(); + return source.moveBack(); + } + } + + static if (hasAssignableElements!R) + { + @property void back(ElementType!R val) + { + eliminateSlackElements(); + source.back = val; + } + } + } + + static if (isRandomAccessRange!R && hasLength!R) + { + auto ref opIndex(size_t n) + { + return source[_n * n]; + } + + /** + Forwards to $(D moveAt(source, n)). + */ + static if (is(typeof(source.moveAt(0)))) + { + ElementType!R moveAt(size_t n) + { + return source.moveAt(_n * n); + } + } + + static if (hasAssignableElements!R) + { + void opIndexAssign(ElementType!R val, size_t n) + { + source[_n * n] = val; + } + } + } + + static if (hasSlicing!R && hasLength!R) + typeof(this) opSlice(size_t lower, size_t upper) + { + assert(upper >= lower && upper <= length); + immutable translatedUpper = (upper == 0) ? 0 : + (upper * _n - (_n - 1)); + immutable translatedLower = min(lower * _n, translatedUpper); + + assert(translatedLower <= translatedUpper); + + return typeof(this)(source[translatedLower .. translatedUpper], _n); + } + + static if (hasLength!R) + { + @property auto length() + { + return (source.length + _n - 1) / _n; + } + + alias opDollar = length; + } + } + return Result(r, n); + } +} + +/// +pure @safe nothrow unittest +{ + import std.algorithm.comparison : equal; + + int[] a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ]; + assert(equal(stride(a, 3), [ 1, 4, 7, 10 ][])); + assert(stride(stride(a, 2), 3) == stride(a, 6)); +} + +pure @safe nothrow @nogc unittest +{ + import std.algorithm.comparison : equal; + + int[4] testArr = [1,2,3,4]; + static immutable result = [1, 3]; + assert(equal(testArr[].stride(2), result)); +} + +debug pure nothrow @system unittest +{//check the contract + int[4] testArr = [1,2,3,4]; + bool passed = false; + scope (success) assert(passed); + import core.exception : AssertError; + //std.exception.assertThrown won't do because it can't infer nothrow + // @@@BUG@@@ 12647 + try + { + auto unused = testArr[].stride(0); + } + catch (AssertError unused) + { + passed = true; + } +} + +pure @safe nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : AllDummyRanges, propagatesRangeType, + ReturnBy; + + static assert(isRandomAccessRange!(typeof(stride([1, 2, 3], 2)))); + void test(size_t n, int[] input, int[] witness) + { + assert(equal(stride(input, n), witness)); + } + test(1, [], []); + int[] arr = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + assert(stride(stride(arr, 2), 3) is stride(arr, 6)); + test(1, arr, arr); + test(2, arr, [1, 3, 5, 7, 9]); + test(3, arr, [1, 4, 7, 10]); + test(4, arr, [1, 5, 9]); + + // Test slicing. + auto s1 = stride(arr, 1); + assert(equal(s1[1 .. 4], [2, 3, 4])); + assert(s1[1 .. 4].length == 3); + assert(equal(s1[1 .. 5], [2, 3, 4, 5])); + assert(s1[1 .. 5].length == 4); + assert(s1[0 .. 0].empty); + assert(s1[3 .. 3].empty); + // assert(s1[$ .. $].empty); + assert(s1[s1.opDollar .. s1.opDollar].empty); + + auto s2 = stride(arr, 2); + assert(equal(s2[0 .. 2], [1,3])); + assert(s2[0 .. 2].length == 2); + assert(equal(s2[1 .. 5], [3, 5, 7, 9])); + assert(s2[1 .. 5].length == 4); + assert(s2[0 .. 0].empty); + assert(s2[3 .. 3].empty); + // assert(s2[$ .. $].empty); + assert(s2[s2.opDollar .. s2.opDollar].empty); + + // Test fix for Bug 5035 + auto m = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]; // 3 rows, 4 columns + auto col = stride(m, 4); + assert(equal(col, [1, 1, 1])); + assert(equal(retro(col), [1, 1, 1])); + + immutable int[] immi = [ 1, 2, 3 ]; + static assert(isRandomAccessRange!(typeof(stride(immi, 1)))); + + // Check for infiniteness propagation. + static assert(isInfinite!(typeof(stride(repeat(1), 3)))); + + foreach (DummyType; AllDummyRanges) + { + DummyType dummyRange; + dummyRange.reinit(); + + auto myStride = stride(dummyRange, 4); + + // Should fail if no length and bidirectional b/c there's no way + // to know how much slack we have. + static if (hasLength!DummyType || !isBidirectionalRange!DummyType) + { + static assert(propagatesRangeType!(typeof(myStride), DummyType)); + } + assert(myStride.front == 1); + assert(myStride.moveFront() == 1); + assert(equal(myStride, [1, 5, 9])); + + static if (hasLength!DummyType) + { + assert(myStride.length == 3); + } + + static if (isBidirectionalRange!DummyType && hasLength!DummyType) + { + assert(myStride.back == 9); + assert(myStride.moveBack() == 9); + } + + static if (isRandomAccessRange!DummyType && hasLength!DummyType) + { + assert(myStride[0] == 1); + assert(myStride[1] == 5); + assert(myStride.moveAt(1) == 5); + assert(myStride[2] == 9); + + static assert(hasSlicing!(typeof(myStride))); + } + + static if (DummyType.r == ReturnBy.Reference) + { + // Make sure reference is propagated. + + { + myStride.front++; + scope(exit) myStride.front--; + assert(dummyRange.front == 2); + } + { + myStride.front = 4; + scope(exit) myStride.front = 1; + assert(dummyRange.front == 4); + } + + static if (isBidirectionalRange!DummyType && hasLength!DummyType) + { + { + myStride.back++; + scope(exit) myStride.back--; + assert(myStride.back == 10); + } + { + myStride.back = 111; + scope(exit) myStride.back = 9; + assert(myStride.back == 111); + } + + static if (isRandomAccessRange!DummyType) + { + { + myStride[1]++; + scope(exit) myStride[1]--; + assert(dummyRange[4] == 6); + } + { + myStride[1] = 55; + scope(exit) myStride[1] = 5; + assert(dummyRange[4] == 55); + } + } + } + } + } +} + +pure @safe nothrow unittest +{ + import std.algorithm.comparison : equal; + + auto LL = iota(1L, 10L); + auto s = stride(LL, 3); + assert(equal(s, [1L, 4L, 7L])); +} + +/** +Spans multiple ranges in sequence. The function $(D chain) takes any +number of ranges and returns a $(D Chain!(R1, R2,...)) object. The +ranges may be different, but they must have the same element type. The +result is a range that offers the $(D front), $(D popFront), and $(D +empty) primitives. If all input ranges offer random access and $(D +length), $(D Chain) offers them as well. + +If only one range is offered to $(D Chain) or $(D chain), the $(D +Chain) type exits the picture by aliasing itself directly to that +range's type. + +Params: + rs = the input ranges to chain together + +Returns: + An input range at minimum. If all of the ranges in `rs` provide + a range primitive, the returned range will also provide that range + primitive. + +See_Also: $(LREF only) to chain values to a range + */ +auto chain(Ranges...)(Ranges rs) +if (Ranges.length > 0 && + allSatisfy!(isInputRange, staticMap!(Unqual, Ranges)) && + !is(CommonType!(staticMap!(ElementType, staticMap!(Unqual, Ranges))) == void)) +{ + static if (Ranges.length == 1) + { + return rs[0]; + } + else + { + static struct Result + { + private: + alias R = staticMap!(Unqual, Ranges); + alias RvalueElementType = CommonType!(staticMap!(.ElementType, R)); + private template sameET(A) + { + enum sameET = is(.ElementType!A == RvalueElementType); + } + + enum bool allSameType = allSatisfy!(sameET, R); + + // This doesn't work yet + static if (allSameType) + { + alias ElementType = ref RvalueElementType; + } + else + { + alias ElementType = RvalueElementType; + } + static if (allSameType && allSatisfy!(hasLvalueElements, R)) + { + static ref RvalueElementType fixRef(ref RvalueElementType val) + { + return val; + } + } + else + { + static RvalueElementType fixRef(RvalueElementType val) + { + return val; + } + } + + // This is the entire state + R source; + // TODO: use a vtable (or more) instead of linear iteration + + public: + this(R input) + { + foreach (i, v; input) + { + source[i] = v; + } + } + + import std.meta : anySatisfy; + + static if (anySatisfy!(isInfinite, R)) + { + // Propagate infiniteness. + enum bool empty = false; + } + else + { + @property bool empty() + { + foreach (i, Unused; R) + { + if (!source[i].empty) return false; + } + return true; + } + } + + static if (allSatisfy!(isForwardRange, R)) + @property auto save() + { + typeof(this) result = this; + foreach (i, Unused; R) + { + result.source[i] = result.source[i].save; + } + return result; + } + + void popFront() + { + foreach (i, Unused; R) + { + if (source[i].empty) continue; + source[i].popFront(); + return; + } + } + + @property auto ref front() + { + foreach (i, Unused; R) + { + if (source[i].empty) continue; + return fixRef(source[i].front); + } + assert(false); + } + + static if (allSameType && allSatisfy!(hasAssignableElements, R)) + { + // @@@BUG@@@ + //@property void front(T)(T v) if (is(T : RvalueElementType)) + + @property void front(RvalueElementType v) + { + foreach (i, Unused; R) + { + if (source[i].empty) continue; + source[i].front = v; + return; + } + assert(false); + } + } + + static if (allSatisfy!(hasMobileElements, R)) + { + RvalueElementType moveFront() + { + foreach (i, Unused; R) + { + if (source[i].empty) continue; + return source[i].moveFront(); + } + assert(false); + } + } + + static if (allSatisfy!(isBidirectionalRange, R)) + { + @property auto ref back() + { + foreach_reverse (i, Unused; R) + { + if (source[i].empty) continue; + return fixRef(source[i].back); + } + assert(false); + } + + void popBack() + { + foreach_reverse (i, Unused; R) + { + if (source[i].empty) continue; + source[i].popBack(); + return; + } + } + + static if (allSatisfy!(hasMobileElements, R)) + { + RvalueElementType moveBack() + { + foreach_reverse (i, Unused; R) + { + if (source[i].empty) continue; + return source[i].moveBack(); + } + assert(false); + } + } + + static if (allSameType && allSatisfy!(hasAssignableElements, R)) + { + @property void back(RvalueElementType v) + { + foreach_reverse (i, Unused; R) + { + if (source[i].empty) continue; + source[i].back = v; + return; + } + assert(false); + } + } + } + + static if (allSatisfy!(hasLength, R)) + { + @property size_t length() + { + size_t result; + foreach (i, Unused; R) + { + result += source[i].length; + } + return result; + } + + alias opDollar = length; + } + + static if (allSatisfy!(isRandomAccessRange, R)) + { + auto ref opIndex(size_t index) + { + foreach (i, Range; R) + { + static if (isInfinite!(Range)) + { + return source[i][index]; + } + else + { + immutable length = source[i].length; + if (index < length) return fixRef(source[i][index]); + index -= length; + } + } + assert(false); + } + + static if (allSatisfy!(hasMobileElements, R)) + { + RvalueElementType moveAt(size_t index) + { + foreach (i, Range; R) + { + static if (isInfinite!(Range)) + { + return source[i].moveAt(index); + } + else + { + immutable length = source[i].length; + if (index < length) return source[i].moveAt(index); + index -= length; + } + } + assert(false); + } + } + + static if (allSameType && allSatisfy!(hasAssignableElements, R)) + void opIndexAssign(ElementType v, size_t index) + { + foreach (i, Range; R) + { + static if (isInfinite!(Range)) + { + source[i][index] = v; + } + else + { + immutable length = source[i].length; + if (index < length) + { + source[i][index] = v; + return; + } + index -= length; + } + } + assert(false); + } + } + + static if (allSatisfy!(hasLength, R) && allSatisfy!(hasSlicing, R)) + auto opSlice(size_t begin, size_t end) + { + auto result = this; + foreach (i, Unused; R) + { + immutable len = result.source[i].length; + if (len < begin) + { + result.source[i] = result.source[i] + [len .. len]; + begin -= len; + } + else + { + result.source[i] = result.source[i] + [begin .. len]; + break; + } + } + auto cut = length; + cut = cut <= end ? 0 : cut - end; + foreach_reverse (i, Unused; R) + { + immutable len = result.source[i].length; + if (cut > len) + { + result.source[i] = result.source[i] + [0 .. 0]; + cut -= len; + } + else + { + result.source[i] = result.source[i] + [0 .. len - cut]; + break; + } + } + return result; + } + } + return Result(rs); + } +} + +/// +pure @safe nothrow unittest +{ + import std.algorithm.comparison : equal; + + int[] arr1 = [ 1, 2, 3, 4 ]; + int[] arr2 = [ 5, 6 ]; + int[] arr3 = [ 7 ]; + auto s = chain(arr1, arr2, arr3); + assert(s.length == 7); + assert(s[5] == 6); + assert(equal(s, [1, 2, 3, 4, 5, 6, 7][])); +} + +/** + * Range primitives are carried over to the returned range if + * all of the ranges provide them + */ +pure @safe nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.sorting : sort; + + int[] arr1 = [5, 2, 8]; + int[] arr2 = [3, 7, 9]; + int[] arr3 = [1, 4, 6]; + + // in-place sorting across all of the arrays + auto s = arr1.chain(arr2, arr3).sort; + + assert(s.equal([1, 2, 3, 4, 5, 6, 7, 8, 9])); + assert(arr1.equal([1, 2, 3])); + assert(arr2.equal([4, 5, 6])); + assert(arr3.equal([7, 8, 9])); +} + +pure @safe nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : AllDummyRanges, dummyLength, + propagatesRangeType; + + { + int[] arr1 = [ 1, 2, 3, 4 ]; + int[] arr2 = [ 5, 6 ]; + int[] arr3 = [ 7 ]; + int[] witness = [ 1, 2, 3, 4, 5, 6, 7 ]; + auto s1 = chain(arr1); + static assert(isRandomAccessRange!(typeof(s1))); + auto s2 = chain(arr1, arr2); + static assert(isBidirectionalRange!(typeof(s2))); + static assert(isRandomAccessRange!(typeof(s2))); + s2.front = 1; + auto s = chain(arr1, arr2, arr3); + assert(s[5] == 6); + assert(equal(s, witness)); + assert(s[5] == 6); + } + { + int[] arr1 = [ 1, 2, 3, 4 ]; + int[] witness = [ 1, 2, 3, 4 ]; + assert(equal(chain(arr1), witness)); + } + { + uint[] foo = [1,2,3,4,5]; + uint[] bar = [1,2,3,4,5]; + auto c = chain(foo, bar); + c[3] = 42; + assert(c[3] == 42); + assert(c.moveFront() == 1); + assert(c.moveBack() == 5); + assert(c.moveAt(4) == 5); + assert(c.moveAt(5) == 1); + } + + // Make sure bug 3311 is fixed. ChainImpl should compile even if not all + // elements are mutable. + assert(equal(chain(iota(0, 3), iota(0, 3)), [0, 1, 2, 0, 1, 2])); + + // Test the case where infinite ranges are present. + auto inf = chain([0,1,2][], cycle([4,5,6][]), [7,8,9][]); // infinite range + assert(inf[0] == 0); + assert(inf[3] == 4); + assert(inf[6] == 4); + assert(inf[7] == 5); + static assert(isInfinite!(typeof(inf))); + + immutable int[] immi = [ 1, 2, 3 ]; + immutable float[] immf = [ 1, 2, 3 ]; + static assert(is(typeof(chain(immi, immf)))); + + // Check that chain at least instantiates and compiles with every possible + // pair of DummyRange types, in either order. + + foreach (DummyType1; AllDummyRanges) + { + DummyType1 dummy1; + foreach (DummyType2; AllDummyRanges) + { + DummyType2 dummy2; + auto myChain = chain(dummy1, dummy2); + + static assert( + propagatesRangeType!(typeof(myChain), DummyType1, DummyType2) + ); + + assert(myChain.front == 1); + foreach (i; 0 .. dummyLength) + { + myChain.popFront(); + } + assert(myChain.front == 1); + + static if (isBidirectionalRange!DummyType1 && + isBidirectionalRange!DummyType2) { + assert(myChain.back == 10); + } + + static if (isRandomAccessRange!DummyType1 && + isRandomAccessRange!DummyType2) { + assert(myChain[0] == 1); + } + + static if (hasLvalueElements!DummyType1 && hasLvalueElements!DummyType2) + { + static assert(hasLvalueElements!(typeof(myChain))); + } + else + { + static assert(!hasLvalueElements!(typeof(myChain))); + } + } + } +} + +pure @safe nothrow @nogc unittest +{ + class Foo{} + immutable(Foo)[] a; + immutable(Foo)[] b; + assert(chain(a, b).empty); +} + +/** +Choose one of two ranges at runtime depending on a Boolean condition. + +The ranges may be different, but they must have compatible element types (i.e. +$(D CommonType) must exist for the two element types). The result is a range +that offers the weakest capabilities of the two (e.g. $(D ForwardRange) if $(D +R1) is a random-access range and $(D R2) is a forward range). + +Params: + condition = which range to choose: $(D r1) if $(D true), $(D r2) otherwise + r1 = the "true" range + r2 = the "false" range + +Returns: + A range type dependent on $(D R1) and $(D R2). + +Bugs: + $(BUGZILLA 14660) + */ +auto choose(R1, R2)(bool condition, R1 r1, R2 r2) +if (isInputRange!(Unqual!R1) && isInputRange!(Unqual!R2) && + !is(CommonType!(ElementType!(Unqual!R1), ElementType!(Unqual!R2)) == void)) +{ + static struct Result + { + import std.algorithm.comparison : max; + import std.algorithm.internal : addressOf; + import std.traits : hasElaborateCopyConstructor, hasElaborateDestructor; + + private union + { + void[max(R1.sizeof, R2.sizeof)] buffer = void; + void* forAlignmentOnly = void; + } + private bool condition; + private @property ref R1 r1() + { + assert(condition); + return *cast(R1*) buffer.ptr; + } + private @property ref R2 r2() + { + assert(!condition); + return *cast(R2*) buffer.ptr; + } + + this(bool condition, R1 r1, R2 r2) + { + this.condition = condition; + import std.conv : emplace; + if (condition) emplace(addressOf(this.r1), r1); + else emplace(addressOf(this.r2), r2); + } + + // Carefully defined postblit to postblit the appropriate range + static if (hasElaborateCopyConstructor!R1 + || hasElaborateCopyConstructor!R2) + this(this) + { + if (condition) + { + static if (hasElaborateCopyConstructor!R1) r1.__postblit(); + } + else + { + static if (hasElaborateCopyConstructor!R2) r2.__postblit(); + } + } + + static if (hasElaborateDestructor!R1 || hasElaborateDestructor!R2) + ~this() + { + if (condition) destroy(r1); + else destroy(r2); + } + + static if (isInfinite!R1 && isInfinite!R2) + // Propagate infiniteness. + enum bool empty = false; + else + @property bool empty() + { + return condition ? r1.empty : r2.empty; + } + + @property auto ref front() + { + return condition ? r1.front : r2.front; + } + + void popFront() + { + return condition ? r1.popFront : r2.popFront; + } + + static if (isForwardRange!R1 && isForwardRange!R2) + @property auto save() + { + auto result = this; + if (condition) r1 = r1.save; + else r2 = r2.save; + return result; + } + + @property void front(T)(T v) + if (is(typeof({ r1.front = v; r2.front = v; }))) + { + if (condition) r1.front = v; else r2.front = v; + } + + static if (hasMobileElements!R1 && hasMobileElements!R2) + auto moveFront() + { + return condition ? r1.moveFront : r2.moveFront; + } + + static if (isBidirectionalRange!R1 && isBidirectionalRange!R2) + { + @property auto ref back() + { + return condition ? r1.back : r2.back; + } + + void popBack() + { + return condition ? r1.popBack : r2.popBack; + } + + static if (hasMobileElements!R1 && hasMobileElements!R2) + auto moveBack() + { + return condition ? r1.moveBack : r2.moveBack; + } + + @property void back(T)(T v) + if (is(typeof({ r1.back = v; r2.back = v; }))) + { + if (condition) r1.back = v; else r2.back = v; + } + } + + static if (hasLength!R1 && hasLength!R2) + { + @property size_t length() + { + return condition ? r1.length : r2.length; + } + alias opDollar = length; + } + + static if (isRandomAccessRange!R1 && isRandomAccessRange!R2) + { + auto ref opIndex(size_t index) + { + return condition ? r1[index] : r2[index]; + } + + static if (hasMobileElements!R1 && hasMobileElements!R2) + auto moveAt(size_t index) + { + return condition ? r1.moveAt(index) : r2.moveAt(index); + } + + void opIndexAssign(T)(T v, size_t index) + if (is(typeof({ r1[1] = v; r2[1] = v; }))) + { + if (condition) r1[index] = v; else r2[index] = v; + } + } + + // BUG: this should work for infinite ranges, too + static if (hasSlicing!R1 && hasSlicing!R2 && + !isInfinite!R2 && !isInfinite!R2) + auto opSlice(size_t begin, size_t end) + { + auto result = this; + if (condition) result.r1 = result.r1[begin .. end]; + else result.r2 = result.r2[begin .. end]; + return result; + } + } + return Result(condition, r1, r2); +} + +/// +@system unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filter, map; + + auto data1 = [ 1, 2, 3, 4 ].filter!(a => a != 3); + auto data2 = [ 5, 6, 7, 8 ].map!(a => a + 1); + + // choose() is primarily useful when you need to select one of two ranges + // with different types at runtime. + static assert(!is(typeof(data1) == typeof(data2))); + + auto chooseRange(bool pickFirst) + { + // The returned range is a common wrapper type that can be used for + // returning or storing either range without running into a type error. + return choose(pickFirst, data1, data2); + + // Simply returning the chosen range without using choose() does not + // work, because map() and filter() return different types. + //return pickFirst ? data1 : data2; // does not compile + } + + auto result = chooseRange(true); + assert(result.equal([ 1, 2, 4 ])); + + result = chooseRange(false); + assert(result.equal([ 6, 7, 8, 9 ])); +} + +/** +Choose one of multiple ranges at runtime. + +The ranges may be different, but they must have compatible element types. The +result is a range that offers the weakest capabilities of all $(D Ranges). + +Params: + index = which range to choose, must be less than the number of ranges + rs = two or more ranges + +Returns: + The indexed range. If rs consists of only one range, the return type is an + alias of that range's type. + */ +auto chooseAmong(Ranges...)(size_t index, Ranges rs) +if (Ranges.length >= 2 + && allSatisfy!(isInputRange, staticMap!(Unqual, Ranges)) + && !is(CommonType!(staticMap!(ElementType, Ranges)) == void)) +{ + static if (Ranges.length == 2) + return choose(index == 0, rs[0], rs[1]); + else + return choose(index == 0, rs[0], chooseAmong(index - 1, rs[1 .. $])); +} + +/// +@system unittest +{ + import std.algorithm.comparison : equal; + + int[] arr1 = [ 1, 2, 3, 4 ]; + int[] arr2 = [ 5, 6 ]; + int[] arr3 = [ 7 ]; + + { + auto s = chooseAmong(0, arr1, arr2, arr3); + auto t = s.save; + assert(s.length == 4); + assert(s[2] == 3); + s.popFront(); + assert(equal(t, [1, 2, 3, 4][])); + } + { + auto s = chooseAmong(1, arr1, arr2, arr3); + assert(s.length == 2); + s.front = 8; + assert(equal(s, [8, 6][])); + } + { + auto s = chooseAmong(1, arr1, arr2, arr3); + assert(s.length == 2); + s[1] = 9; + assert(equal(s, [8, 9][])); + } + { + auto s = chooseAmong(1, arr2, arr1, arr3)[1 .. 3]; + assert(s.length == 2); + assert(equal(s, [2, 3][])); + } + { + auto s = chooseAmong(0, arr1, arr2, arr3); + assert(s.length == 4); + assert(s.back == 4); + s.popBack(); + s.back = 5; + assert(equal(s, [1, 2, 5][])); + s.back = 3; + assert(equal(s, [1, 2, 3][])); + } + { + uint[] foo = [1,2,3,4,5]; + uint[] bar = [6,7,8,9,10]; + auto c = chooseAmong(1,foo, bar); + assert(c[3] == 9); + c[3] = 42; + assert(c[3] == 42); + assert(c.moveFront() == 6); + assert(c.moveBack() == 10); + assert(c.moveAt(4) == 10); + } + { + import std.range : cycle; + auto s = chooseAmong(1, cycle(arr2), cycle(arr3)); + assert(isInfinite!(typeof(s))); + assert(!s.empty); + assert(s[100] == 7); + } +} + +@system unittest +{ + int[] a = [1, 2, 3]; + long[] b = [4, 5, 6]; + auto c = chooseAmong(0, a, b); + c[0] = 42; + assert(c[0] == 42); +} + + +/** +$(D roundRobin(r1, r2, r3)) yields $(D r1.front), then $(D r2.front), +then $(D r3.front), after which it pops off one element from each and +continues again from $(D r1). For example, if two ranges are involved, +it alternately yields elements off the two ranges. $(D roundRobin) +stops after it has consumed all ranges (skipping over the ones that +finish early). + */ +auto roundRobin(Rs...)(Rs rs) +if (Rs.length > 1 && allSatisfy!(isInputRange, staticMap!(Unqual, Rs))) +{ + struct Result + { + import std.conv : to; + + public Rs source; + private size_t _current = size_t.max; + + @property bool empty() + { + foreach (i, Unused; Rs) + { + if (!source[i].empty) return false; + } + return true; + } + + @property auto ref front() + { + final switch (_current) + { + foreach (i, R; Rs) + { + case i: + assert( + !source[i].empty, + "Attempting to fetch the front of an empty roundRobin" + ); + return source[i].front; + } + } + assert(0); + } + + void popFront() + { + final switch (_current) + { + foreach (i, R; Rs) + { + case i: + source[i].popFront(); + break; + } + } + + auto next = _current == (Rs.length - 1) ? 0 : (_current + 1); + final switch (next) + { + foreach (i, R; Rs) + { + case i: + if (!source[i].empty) + { + _current = i; + return; + } + if (i == _current) + { + _current = _current.max; + return; + } + goto case (i + 1) % Rs.length; + } + } + } + + static if (allSatisfy!(isForwardRange, staticMap!(Unqual, Rs))) + @property auto save() + { + Result result = this; + foreach (i, Unused; Rs) + { + result.source[i] = result.source[i].save; + } + return result; + } + + static if (allSatisfy!(hasLength, Rs)) + { + @property size_t length() + { + size_t result; + foreach (i, R; Rs) + { + result += source[i].length; + } + return result; + } + + alias opDollar = length; + } + } + + return Result(rs, 0); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + + int[] a = [ 1, 2, 3 ]; + int[] b = [ 10, 20, 30, 40 ]; + auto r = roundRobin(a, b); + assert(equal(r, [ 1, 10, 2, 20, 3, 30, 40 ])); +} + +/** + * roundRobin can be used to create "interleave" functionality which inserts + * an element between each element in a range. + */ +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto interleave(R, E)(R range, E element) + if ((isInputRange!R && hasLength!R) || isForwardRange!R) + { + static if (hasLength!R) + immutable len = range.length; + else + immutable len = range.save.walkLength; + + return roundRobin( + range, + element.repeat(len - 1) + ); + } + + assert(interleave([1, 2, 3], 0).equal([1, 0, 2, 0, 3])); +} + +/** +Iterates a random-access range starting from a given point and +progressively extending left and right from that point. If no initial +point is given, iteration starts from the middle of the +range. Iteration spans the entire range. + +When `startingIndex` is 0 the range will be fully iterated in order +and in reverse order when `r.length` is given. + +Params: + r = a random access range with length and slicing + startingIndex = the index to begin iteration from + +Returns: + A forward range with length + */ +auto radial(Range, I)(Range r, I startingIndex) +if (isRandomAccessRange!(Unqual!Range) && hasLength!(Unqual!Range) && hasSlicing!(Unqual!Range) && isIntegral!I) +{ + if (startingIndex != r.length) ++startingIndex; + return roundRobin(retro(r[0 .. startingIndex]), r[startingIndex .. r.length]); +} + +/// Ditto +auto radial(R)(R r) +if (isRandomAccessRange!(Unqual!R) && hasLength!(Unqual!R) && hasSlicing!(Unqual!R)) +{ + return .radial(r, (r.length - !r.empty) / 2); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + int[] a = [ 1, 2, 3, 4, 5 ]; + assert(equal(radial(a), [ 3, 4, 2, 5, 1 ])); + a = [ 1, 2, 3, 4 ]; + assert(equal(radial(a), [ 2, 3, 1, 4 ])); + + // If the left end is reached first, the remaining elements on the right + // are concatenated in order: + a = [ 0, 1, 2, 3, 4, 5 ]; + assert(equal(radial(a, 1), [ 1, 2, 0, 3, 4, 5 ])); + + // If the right end is reached first, the remaining elements on the left + // are concatenated in reverse order: + assert(equal(radial(a, 4), [ 4, 5, 3, 2, 1, 0 ])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.conv : text; + import std.exception : enforce; + import std.internal.test.dummyrange : DummyRange, Length, RangeType, ReturnBy; + + void test(int[] input, int[] witness) + { + enforce(equal(radial(input), witness), + text(radial(input), " vs. ", witness)); + } + test([], []); + test([ 1 ], [ 1 ]); + test([ 1, 2 ], [ 1, 2 ]); + test([ 1, 2, 3 ], [ 2, 3, 1 ]); + test([ 1, 2, 3, 4 ], [ 2, 3, 1, 4 ]); + test([ 1, 2, 3, 4, 5 ], [ 3, 4, 2, 5, 1 ]); + test([ 1, 2, 3, 4, 5, 6 ], [ 3, 4, 2, 5, 1, 6 ]); + + int[] a = [ 1, 2, 3, 4, 5 ]; + assert(equal(radial(a, 1), [ 2, 3, 1, 4, 5 ])); + assert(equal(radial(a, 0), [ 1, 2, 3, 4, 5 ])); // only right subrange + assert(equal(radial(a, a.length), [ 5, 4, 3, 2, 1 ])); // only left subrange + static assert(isForwardRange!(typeof(radial(a, 1)))); + + auto r = radial([1,2,3,4,5]); + for (auto rr = r.save; !rr.empty; rr.popFront()) + { + assert(rr.front == moveFront(rr)); + } + r.front = 5; + assert(r.front == 5); + + // Test instantiation without lvalue elements. + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random) dummy; + assert(equal(radial(dummy, 4), [5, 6, 4, 7, 3, 8, 2, 9, 1, 10])); + + // immutable int[] immi = [ 1, 2 ]; + // static assert(is(typeof(radial(immi)))); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto LL = iota(1L, 6L); + auto r = radial(LL); + assert(equal(r, [3L, 4L, 2L, 5L, 1L])); +} + +/** +Lazily takes only up to `n` elements of a range. This is +particularly useful when using with infinite ranges. + +Unlike $(LREF takeExactly), `take` does not require that there +are `n` or more elements in `input`. As a consequence, length +information is not applied to the result unless `input` also has +length information. + +Params: + input = an input range to iterate over up to `n` times + n = the number of elements to take + +Returns: + At minimum, an input range. If the range offers random access + and `length`, `take` offers them as well. + */ +Take!R take(R)(R input, size_t n) +if (isInputRange!(Unqual!R)) +{ + alias U = Unqual!R; + static if (is(R T == Take!T)) + { + import std.algorithm.comparison : min; + return R(input.source, min(n, input._maxAvailable)); + } + else static if (!isInfinite!U && hasSlicing!U) + { + import std.algorithm.comparison : min; + return input[0 .. min(n, input.length)]; + } + else + { + return Take!R(input, n); + } +} + +/// ditto +struct Take(Range) +if (isInputRange!(Unqual!Range) && + //take _cannot_ test hasSlicing on infinite ranges, because hasSlicing uses + //take for slicing infinite ranges. + !((!isInfinite!(Unqual!Range) && hasSlicing!(Unqual!Range)) || is(Range T == Take!T))) +{ + private alias R = Unqual!Range; + + /// User accessible in read and write + public R source; + + private size_t _maxAvailable; + + alias Source = R; + + /// Range primitives + @property bool empty() + { + return _maxAvailable == 0 || source.empty; + } + + /// ditto + @property auto ref front() + { + assert(!empty, + "Attempting to fetch the front of an empty " + ~ Take.stringof); + return source.front; + } + + /// ditto + void popFront() + { + assert(!empty, + "Attempting to popFront() past the end of a " + ~ Take.stringof); + source.popFront(); + --_maxAvailable; + } + + static if (isForwardRange!R) + /// ditto + @property Take save() + { + return Take(source.save, _maxAvailable); + } + + static if (hasAssignableElements!R) + /// ditto + @property void front(ElementType!R v) + { + assert(!empty, + "Attempting to assign to the front of an empty " + ~ Take.stringof); + // This has to return auto instead of void because of Bug 4706. + source.front = v; + } + + static if (hasMobileElements!R) + { + /// ditto + auto moveFront() + { + assert(!empty, + "Attempting to move the front of an empty " + ~ Take.stringof); + return source.moveFront(); + } + } + + static if (isInfinite!R) + { + /// ditto + @property size_t length() const + { + return _maxAvailable; + } + + /// ditto + alias opDollar = length; + + //Note: Due to Take/hasSlicing circular dependency, + //This needs to be a restrained template. + /// ditto + auto opSlice()(size_t i, size_t j) + if (hasSlicing!R) + { + assert(i <= j, "Invalid slice bounds"); + assert(j <= length, "Attempting to slice past the end of a " + ~ Take.stringof); + return source[i .. j]; + } + } + else static if (hasLength!R) + { + /// ditto + @property size_t length() + { + import std.algorithm.comparison : min; + return min(_maxAvailable, source.length); + } + + alias opDollar = length; + } + + static if (isRandomAccessRange!R) + { + /// ditto + void popBack() + { + assert(!empty, + "Attempting to popBack() past the beginning of a " + ~ Take.stringof); + --_maxAvailable; + } + + /// ditto + @property auto ref back() + { + assert(!empty, + "Attempting to fetch the back of an empty " + ~ Take.stringof); + return source[this.length - 1]; + } + + /// ditto + auto ref opIndex(size_t index) + { + assert(index < length, + "Attempting to index out of the bounds of a " + ~ Take.stringof); + return source[index]; + } + + static if (hasAssignableElements!R) + { + /// ditto + @property void back(ElementType!R v) + { + // This has to return auto instead of void because of Bug 4706. + assert(!empty, + "Attempting to assign to the back of an empty " + ~ Take.stringof); + source[this.length - 1] = v; + } + + /// ditto + void opIndexAssign(ElementType!R v, size_t index) + { + assert(index < length, + "Attempting to index out of the bounds of a " + ~ Take.stringof); + source[index] = v; + } + } + + static if (hasMobileElements!R) + { + /// ditto + auto moveBack() + { + assert(!empty, + "Attempting to move the back of an empty " + ~ Take.stringof); + return source.moveAt(this.length - 1); + } + + /// ditto + auto moveAt(size_t index) + { + assert(index < length, + "Attempting to index out of the bounds of a " + ~ Take.stringof); + return source.moveAt(index); + } + } + } + + /** + Access to maximal length of the range. + Note: the actual length of the range depends on the underlying range. + If it has fewer elements, it will stop before maxLength is reached. + */ + @property size_t maxLength() const + { + return _maxAvailable; + } +} + +/** +This template simply aliases itself to R and is useful for consistency in +generic code. +*/ +template Take(R) +if (isInputRange!(Unqual!R) && + ((!isInfinite!(Unqual!R) && hasSlicing!(Unqual!R)) || is(R T == Take!T))) +{ + alias Take = R; +} + +/// +pure @safe nothrow unittest +{ + import std.algorithm.comparison : equal; + + int[] arr1 = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + auto s = take(arr1, 5); + assert(s.length == 5); + assert(s[4] == 5); + assert(equal(s, [ 1, 2, 3, 4, 5 ][])); +} + +/** + * If the range runs out before `n` elements, `take` simply returns the entire + * range (unlike $(LREF takeExactly), which will cause an assertion failure if + * the range ends prematurely): + */ +pure @safe nothrow unittest +{ + import std.algorithm.comparison : equal; + + int[] arr2 = [ 1, 2, 3 ]; + auto t = take(arr2, 5); + assert(t.length == 3); + assert(equal(t, [ 1, 2, 3 ])); +} + +pure @safe nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : AllDummyRanges; + + int[] arr1 = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + auto s = take(arr1, 5); + assert(s.length == 5); + assert(s[4] == 5); + assert(equal(s, [ 1, 2, 3, 4, 5 ][])); + assert(equal(retro(s), [ 5, 4, 3, 2, 1 ][])); + + // Test fix for bug 4464. + static assert(is(typeof(s) == Take!(int[]))); + static assert(is(typeof(s) == int[])); + + // Test using narrow strings. + import std.exception : assumeWontThrow; + + auto myStr = "This is a string."; + auto takeMyStr = take(myStr, 7); + assert(assumeWontThrow(equal(takeMyStr, "This is"))); + // Test fix for bug 5052. + auto takeMyStrAgain = take(takeMyStr, 4); + assert(assumeWontThrow(equal(takeMyStrAgain, "This"))); + static assert(is (typeof(takeMyStrAgain) == typeof(takeMyStr))); + takeMyStrAgain = take(takeMyStr, 10); + assert(assumeWontThrow(equal(takeMyStrAgain, "This is"))); + + foreach (DummyType; AllDummyRanges) + { + DummyType dummy; + auto t = take(dummy, 5); + alias T = typeof(t); + + static if (isRandomAccessRange!DummyType) + { + static assert(isRandomAccessRange!T); + assert(t[4] == 5); + + assert(moveAt(t, 1) == t[1]); + assert(t.back == moveBack(t)); + } + else static if (isForwardRange!DummyType) + { + static assert(isForwardRange!T); + } + + for (auto tt = t; !tt.empty; tt.popFront()) + { + assert(tt.front == moveFront(tt)); + } + + // Bidirectional ranges can't be propagated properly if they don't + // also have random access. + + assert(equal(t, [1,2,3,4,5])); + + //Test that take doesn't wrap the result of take. + assert(take(t, 4) == take(dummy, 4)); + } + + immutable myRepeat = repeat(1); + static assert(is(Take!(typeof(myRepeat)))); +} + +pure @safe nothrow @nogc unittest +{ + //check for correct slicing of Take on an infinite range + import std.algorithm.comparison : equal; + foreach (start; 0 .. 4) + foreach (stop; start .. 4) + assert(iota(4).cycle.take(4)[start .. stop] + .equal(iota(start, stop))); +} + +pure @safe nothrow @nogc unittest +{ + // Check that one can declare variables of all Take types, + // and that they match the return type of the corresponding + // take(). (See issue 4464.) + int[] r1; + Take!(int[]) t1; + t1 = take(r1, 1); + assert(t1.empty); + + string r2; + Take!string t2; + t2 = take(r2, 1); + assert(t2.empty); + + Take!(Take!string) t3; + t3 = take(t2, 1); + assert(t3.empty); +} + +pure @safe nothrow @nogc unittest +{ + alias R1 = typeof(repeat(1)); + alias R2 = typeof(cycle([1])); + alias TR1 = Take!R1; + alias TR2 = Take!R2; + static assert(isBidirectionalRange!TR1); + static assert(isBidirectionalRange!TR2); +} + +pure @safe nothrow @nogc unittest //12731 +{ + auto a = repeat(1); + auto s = a[1 .. 5]; + s = s[1 .. 3]; + assert(s.length == 2); + assert(s[0] == 1); + assert(s[1] == 1); +} + +pure @safe nothrow @nogc unittest //13151 +{ + import std.algorithm.comparison : equal; + + auto r = take(repeat(1, 4), 3); + assert(r.take(2).equal(repeat(1, 2))); +} + + +/** +Similar to $(LREF take), but assumes that $(D range) has at least $(D +n) elements. Consequently, the result of $(D takeExactly(range, n)) +always defines the $(D length) property (and initializes it to $(D n)) +even when $(D range) itself does not define $(D length). + +The result of $(D takeExactly) is identical to that of $(LREF take) in +cases where the original range defines $(D length) or is infinite. + +Unlike $(LREF take), however, it is illegal to pass a range with less than +$(D n) elements to $(D takeExactly); this will cause an assertion failure. + */ +auto takeExactly(R)(R range, size_t n) +if (isInputRange!R) +{ + static if (is(typeof(takeExactly(range._input, n)) == R)) + { + assert(n <= range._n, + "Attempted to take more than the length of the range with takeExactly."); + // takeExactly(takeExactly(r, n1), n2) has the same type as + // takeExactly(r, n1) and simply returns takeExactly(r, n2) + range._n = n; + return range; + } + //Also covers hasSlicing!R for finite ranges. + else static if (hasLength!R) + { + assert(n <= range.length, + "Attempted to take more than the length of the range with takeExactly."); + return take(range, n); + } + else static if (isInfinite!R) + return Take!R(range, n); + else + { + static struct Result + { + R _input; + private size_t _n; + + @property bool empty() const { return !_n; } + @property auto ref front() + { + assert(_n > 0, "front() on an empty " ~ Result.stringof); + return _input.front; + } + void popFront() { _input.popFront(); --_n; } + @property size_t length() const { return _n; } + alias opDollar = length; + + @property Take!R _takeExactly_Result_asTake() + { + return typeof(return)(_input, _n); + } + + alias _takeExactly_Result_asTake this; + + static if (isForwardRange!R) + @property auto save() + { + return Result(_input.save, _n); + } + + static if (hasMobileElements!R) + { + auto moveFront() + { + assert(!empty, + "Attempting to move the front of an empty " + ~ typeof(this).stringof); + return _input.moveFront(); + } + } + + static if (hasAssignableElements!R) + { + @property auto ref front(ElementType!R v) + { + assert(!empty, + "Attempting to assign to the front of an empty " + ~ typeof(this).stringof); + return _input.front = v; + } + } + } + + return Result(range, n); + } +} + +/// +pure @safe nothrow unittest +{ + import std.algorithm.comparison : equal; + + auto a = [ 1, 2, 3, 4, 5 ]; + + auto b = takeExactly(a, 3); + assert(equal(b, [1, 2, 3])); + static assert(is(typeof(b.length) == size_t)); + assert(b.length == 3); + assert(b.front == 1); + assert(b.back == 3); +} + +pure @safe nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filter; + + auto a = [ 1, 2, 3, 4, 5 ]; + auto b = takeExactly(a, 3); + assert(equal(b, [1, 2, 3])); + auto c = takeExactly(b, 2); + assert(equal(c, [1, 2])); + + + + auto d = filter!"a > 2"(a); + auto e = takeExactly(d, 3); + assert(equal(e, [3, 4, 5])); + static assert(is(typeof(e.length) == size_t)); + assert(e.length == 3); + assert(e.front == 3); + + assert(equal(takeExactly(e, 3), [3, 4, 5])); +} + +pure @safe nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : AllDummyRanges; + + auto a = [ 1, 2, 3, 4, 5 ]; + //Test that take and takeExactly are the same for ranges which define length + //but aren't sliceable. + struct L + { + @property auto front() { return _arr[0]; } + @property bool empty() { return _arr.empty; } + void popFront() { _arr.popFront(); } + @property size_t length() { return _arr.length; } + int[] _arr; + } + static assert(is(typeof(take(L(a), 3)) == typeof(takeExactly(L(a), 3)))); + assert(take(L(a), 3) == takeExactly(L(a), 3)); + + //Test that take and takeExactly are the same for ranges which are sliceable. + static assert(is(typeof(take(a, 3)) == typeof(takeExactly(a, 3)))); + assert(take(a, 3) == takeExactly(a, 3)); + + //Test that take and takeExactly are the same for infinite ranges. + auto inf = repeat(1); + static assert(is(typeof(take(inf, 5)) == Take!(typeof(inf)))); + assert(take(inf, 5) == takeExactly(inf, 5)); + + //Test that take and takeExactly are _not_ the same for ranges which don't + //define length. + static assert(!is(typeof(take(filter!"true"(a), 3)) == typeof(takeExactly(filter!"true"(a), 3)))); + + foreach (DummyType; AllDummyRanges) + { + { + DummyType dummy; + auto t = takeExactly(dummy, 5); + + //Test that takeExactly doesn't wrap the result of takeExactly. + assert(takeExactly(t, 4) == takeExactly(dummy, 4)); + } + + static if (hasMobileElements!DummyType) + { + { + auto t = takeExactly(DummyType.init, 4); + assert(t.moveFront() == 1); + assert(equal(t, [1, 2, 3, 4])); + } + } + + static if (hasAssignableElements!DummyType) + { + { + auto t = takeExactly(DummyType.init, 4); + t.front = 9; + assert(equal(t, [9, 2, 3, 4])); + } + } + } +} + +pure @safe nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : DummyRange, Length, RangeType, ReturnBy; + + alias DummyType = DummyRange!(ReturnBy.Value, Length.No, RangeType.Forward); + auto te = takeExactly(DummyType(), 5); + Take!DummyType t = te; + assert(equal(t, [1, 2, 3, 4, 5])); + assert(equal(t, te)); +} + +/** +Returns a range with at most one element; for example, $(D +takeOne([42, 43, 44])) returns a range consisting of the integer $(D +42). Calling $(D popFront()) off that range renders it empty. + +In effect $(D takeOne(r)) is somewhat equivalent to $(D take(r, 1)) but in +certain interfaces it is important to know statically that the range may only +have at most one element. + +The type returned by $(D takeOne) is a random-access range with length +regardless of $(D R)'s capabilities, as long as it is a forward range. +(another feature that distinguishes $(D takeOne) from $(D take)). If +(D R) is an input range but not a forward range, return type is an input +range with all random-access capabilities except save. + */ +auto takeOne(R)(R source) +if (isInputRange!R) +{ + static if (hasSlicing!R) + { + return source[0 .. !source.empty]; + } + else + { + static struct Result + { + private R _source; + private bool _empty = true; + @property bool empty() const { return _empty; } + @property auto ref front() + { + assert(!empty, "Attempting to fetch the front of an empty takeOne"); + return _source.front; + } + void popFront() + { + assert(!empty, "Attempting to popFront an empty takeOne"); + _source.popFront(); + _empty = true; + } + void popBack() + { + assert(!empty, "Attempting to popBack an empty takeOne"); + _source.popFront(); + _empty = true; + } + static if (isForwardRange!(Unqual!R)) + { + @property auto save() { return Result(_source.save, empty); } + } + @property auto ref back() + { + assert(!empty, "Attempting to fetch the back of an empty takeOne"); + return _source.front; + } + @property size_t length() const { return !empty; } + alias opDollar = length; + auto ref opIndex(size_t n) + { + assert(n < length, "Attempting to index a takeOne out of bounds"); + return _source.front; + } + auto opSlice(size_t m, size_t n) + { + assert(m <= n && n < length, "Attempting to index a takeOne out of bounds"); + return n > m ? this : Result(_source, false); + } + // Non-standard property + @property R source() { return _source; } + } + + return Result(source, source.empty); + } +} + +/// +pure @safe nothrow unittest +{ + auto s = takeOne([42, 43, 44]); + static assert(isRandomAccessRange!(typeof(s))); + assert(s.length == 1); + assert(!s.empty); + assert(s.front == 42); + s.front = 43; + assert(s.front == 43); + assert(s.back == 43); + assert(s[0] == 43); + s.popFront(); + assert(s.length == 0); + assert(s.empty); +} + +pure @safe nothrow @nogc unittest +{ + struct NonForwardRange + { + enum empty = false; + int front() { return 42; } + void popFront() {} + } + + static assert(!isForwardRange!NonForwardRange); + + auto s = takeOne(NonForwardRange()); + assert(s.front == 42); +} + +//guards against issue 16999 +pure @safe unittest +{ + auto myIota = new class + { + int front = 0; + @safe void popFront(){front++;} + enum empty = false; + }; + auto iotaPart = myIota.takeOne; + int sum; + foreach (var; chain(iotaPart, iotaPart, iotaPart)) + { + sum += var; + } + assert(sum == 3); + assert(iotaPart.front == 3); +} + +/++ + Returns an empty range which is statically known to be empty and is + guaranteed to have $(D length) and be random access regardless of $(D R)'s + capabilities. + +/ +auto takeNone(R)() +if (isInputRange!R) +{ + return typeof(takeOne(R.init)).init; +} + +/// +pure @safe nothrow @nogc unittest +{ + auto range = takeNone!(int[])(); + assert(range.length == 0); + assert(range.empty); +} + +pure @safe nothrow @nogc unittest +{ + enum ctfe = takeNone!(int[])(); + static assert(ctfe.length == 0); + static assert(ctfe.empty); +} + + +/++ + Creates an empty range from the given range in $(BIGOH 1). If it can, it + will return the same range type. If not, it will return + $(D takeExactly(range, 0)). + +/ +auto takeNone(R)(R range) +if (isInputRange!R) +{ + import std.traits : isDynamicArray; + //Makes it so that calls to takeNone which don't use UFCS still work with a + //member version if it's defined. + static if (is(typeof(R.takeNone))) + auto retval = range.takeNone(); + //@@@BUG@@@ 8339 + else static if (isDynamicArray!R)/+ || + (is(R == struct) && __traits(compiles, {auto r = R.init;}) && R.init.empty))+/ + { + auto retval = R.init; + } + //An infinite range sliced at [0 .. 0] would likely still not be empty... + else static if (hasSlicing!R && !isInfinite!R) + auto retval = range[0 .. 0]; + else + auto retval = takeExactly(range, 0); + + //@@@BUG@@@ 7892 prevents this from being done in an out block. + assert(retval.empty); + return retval; +} + +/// +pure @safe nothrow unittest +{ + import std.algorithm.iteration : filter; + assert(takeNone([42, 27, 19]).empty); + assert(takeNone("dlang.org").empty); + assert(takeNone(filter!"true"([42, 27, 19])).empty); +} + +@safe unittest +{ + import std.algorithm.iteration : filter; + import std.meta : AliasSeq; + + struct Dummy + { + mixin template genInput() + { + @safe: + @property bool empty() { return _arr.empty; } + @property auto front() { return _arr.front; } + void popFront() { _arr.popFront(); } + static assert(isInputRange!(typeof(this))); + } + } + alias genInput = Dummy.genInput; + + static struct NormalStruct + { + //Disabled to make sure that the takeExactly version is used. + @disable this(); + this(int[] arr) { _arr = arr; } + mixin genInput; + int[] _arr; + } + + static struct SliceStruct + { + @disable this(); + this(int[] arr) { _arr = arr; } + mixin genInput; + @property auto save() { return this; } + auto opSlice(size_t i, size_t j) { return typeof(this)(_arr[i .. j]); } + @property size_t length() { return _arr.length; } + int[] _arr; + } + + static struct InitStruct + { + mixin genInput; + int[] _arr; + } + + static struct TakeNoneStruct + { + this(int[] arr) { _arr = arr; } + @disable this(); + mixin genInput; + auto takeNone() { return typeof(this)(null); } + int[] _arr; + } + + static class NormalClass + { + this(int[] arr) {_arr = arr;} + mixin genInput; + int[] _arr; + } + + static class SliceClass + { + @safe: + this(int[] arr) { _arr = arr; } + mixin genInput; + @property auto save() { return new typeof(this)(_arr); } + auto opSlice(size_t i, size_t j) { return new typeof(this)(_arr[i .. j]); } + @property size_t length() { return _arr.length; } + int[] _arr; + } + + static class TakeNoneClass + { + @safe: + this(int[] arr) { _arr = arr; } + mixin genInput; + auto takeNone() { return new typeof(this)(null); } + int[] _arr; + } + + import std.format : format; + + foreach (range; AliasSeq!([1, 2, 3, 4, 5], + "hello world", + "hello world"w, + "hello world"d, + SliceStruct([1, 2, 3]), + //@@@BUG@@@ 8339 forces this to be takeExactly + //`InitStruct([1, 2, 3]), + TakeNoneStruct([1, 2, 3]))) + { + static assert(takeNone(range).empty, typeof(range).stringof); + assert(takeNone(range).empty); + static assert(is(typeof(range) == typeof(takeNone(range))), typeof(range).stringof); + } + + foreach (range; AliasSeq!(NormalStruct([1, 2, 3]), + InitStruct([1, 2, 3]))) + { + static assert(takeNone(range).empty, typeof(range).stringof); + assert(takeNone(range).empty); + static assert(is(typeof(takeExactly(range, 0)) == typeof(takeNone(range))), typeof(range).stringof); + } + + //Don't work in CTFE. + auto normal = new NormalClass([1, 2, 3]); + assert(takeNone(normal).empty); + static assert(is(typeof(takeExactly(normal, 0)) == typeof(takeNone(normal))), typeof(normal).stringof); + + auto slice = new SliceClass([1, 2, 3]); + assert(takeNone(slice).empty); + static assert(is(SliceClass == typeof(takeNone(slice))), typeof(slice).stringof); + + auto taken = new TakeNoneClass([1, 2, 3]); + assert(takeNone(taken).empty); + static assert(is(TakeNoneClass == typeof(takeNone(taken))), typeof(taken).stringof); + + auto filtered = filter!"true"([1, 2, 3, 4, 5]); + assert(takeNone(filtered).empty); + //@@@BUG@@@ 8339 and 5941 force this to be takeExactly + //static assert(is(typeof(filtered) == typeof(takeNone(filtered))), typeof(filtered).stringof); +} + +/++ + + Return a _range advanced to within $(D _n) elements of the end of + + $(D _range). + + + + Intended as the _range equivalent of the Unix + + $(HTTP en.wikipedia.org/wiki/Tail_%28Unix%29, _tail) utility. When the length + + of $(D _range) is less than or equal to $(D _n), $(D _range) is returned + + as-is. + + + + Completes in $(BIGOH 1) steps for ranges that support slicing and have + + length. Completes in $(BIGOH _range.length) time for all other ranges. + + + + Params: + + range = _range to get _tail of + + n = maximum number of elements to include in _tail + + + + Returns: + + Returns the _tail of $(D _range) augmented with length information + +/ +auto tail(Range)(Range range, size_t n) +if (isInputRange!Range && !isInfinite!Range && + (hasLength!Range || isForwardRange!Range)) +{ + static if (hasLength!Range) + { + immutable length = range.length; + if (n >= length) + return range.takeExactly(length); + else + return range.drop(length - n).takeExactly(n); + } + else + { + Range scout = range.save; + foreach (immutable i; 0 .. n) + { + if (scout.empty) + return range.takeExactly(i); + scout.popFront(); + } + + auto tail = range.save; + while (!scout.empty) + { + assert(!tail.empty); + scout.popFront(); + tail.popFront(); + } + + return tail.takeExactly(n); + } +} + +/// +pure @safe nothrow unittest +{ + // tail -c n + assert([1, 2, 3].tail(1) == [3]); + assert([1, 2, 3].tail(2) == [2, 3]); + assert([1, 2, 3].tail(3) == [1, 2, 3]); + assert([1, 2, 3].tail(4) == [1, 2, 3]); + assert([1, 2, 3].tail(0).length == 0); + + // tail --lines=n + import std.algorithm.comparison : equal; + import std.algorithm.iteration : joiner; + import std.exception : assumeWontThrow; + import std.string : lineSplitter; + assert("one\ntwo\nthree" + .lineSplitter + .tail(2) + .joiner("\n") + .equal("two\nthree") + .assumeWontThrow); +} + +// @nogc prevented by @@@BUG@@@ 15408 +pure nothrow @safe /+@nogc+/ unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : AllDummyRanges, DummyRange, Length, + RangeType, ReturnBy; + + static immutable cheatsheet = [6, 7, 8, 9, 10]; + + foreach (R; AllDummyRanges) + { + static if (isInputRange!R && !isInfinite!R && + (hasLength!R || isForwardRange!R)) + { + assert(R.init.tail(5).equal(cheatsheet)); + static assert(R.init.tail(5).equal(cheatsheet)); + + assert(R.init.tail(0).length == 0); + assert(R.init.tail(10).equal(R.init)); + assert(R.init.tail(11).equal(R.init)); + } + } + + // Infinite ranges are not supported + static assert(!__traits(compiles, repeat(0).tail(0))); + + // Neither are non-forward ranges without length + static assert(!__traits(compiles, DummyRange!(ReturnBy.Value, Length.No, + RangeType.Input).init.tail(5))); +} + +pure @safe nothrow @nogc unittest +{ + static immutable input = [1, 2, 3]; + static immutable expectedOutput = [2, 3]; + assert(input.tail(2) == expectedOutput); +} + +/++ + Convenience function which calls + $(REF popFrontN, std, _range, primitives)`(range, n)` and returns `range`. + `drop` makes it easier to pop elements from a range + and then pass it to another function within a single expression, + whereas `popFrontN` would require multiple statements. + + `dropBack` provides the same functionality but instead calls + $(REF popBackN, std, _range, primitives)`(range, n)` + + Note: `drop` and `dropBack` will only pop $(I up to) + `n` elements but will stop if the range is empty first. + In other languages this is sometimes called `skip`. + + Params: + range = the input range to drop from + n = the number of elements to drop + + Returns: + `range` with up to `n` elements dropped + + See_Also: + $(REF popFront, std, _range, primitives), $(REF popBackN, std, _range, primitives) + +/ +R drop(R)(R range, size_t n) +if (isInputRange!R) +{ + range.popFrontN(n); + return range; +} +/// ditto +R dropBack(R)(R range, size_t n) +if (isBidirectionalRange!R) +{ + range.popBackN(n); + return range; +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + + assert([0, 2, 1, 5, 0, 3].drop(3) == [5, 0, 3]); + assert("hello world".drop(6) == "world"); + assert("hello world".drop(50).empty); + assert("hello world".take(6).drop(3).equal("lo ")); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + assert([0, 2, 1, 5, 0, 3].dropBack(3) == [0, 2, 1]); + assert("hello world".dropBack(6) == "hello"); + assert("hello world".dropBack(50).empty); + assert("hello world".drop(4).dropBack(4).equal("o w")); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.container.dlist : DList; + + //Remove all but the first two elements + auto a = DList!int(0, 1, 9, 9, 9, 9); + a.remove(a[].drop(2)); + assert(a[].equal(a[].take(2))); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filter; + + assert(drop("", 5).empty); + assert(equal(drop(filter!"true"([0, 2, 1, 5, 0, 3]), 3), [5, 0, 3])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.container.dlist : DList; + + //insert before the last two elements + auto a = DList!int(0, 1, 2, 5, 6); + a.insertAfter(a[].dropBack(2), [3, 4]); + assert(a[].equal(iota(0, 7))); +} + +/++ + Similar to $(LREF drop) and $(D dropBack) but they call + $(D range.$(LREF popFrontExactly)(n)) and $(D range.popBackExactly(n)) + instead. + + Note: Unlike $(D drop), $(D dropExactly) will assume that the + range holds at least $(D n) elements. This makes $(D dropExactly) + faster than $(D drop), but it also means that if $(D range) does + not contain at least $(D n) elements, it will attempt to call $(D popFront) + on an empty range, which is undefined behavior. So, only use + $(D popFrontExactly) when it is guaranteed that $(D range) holds at least + $(D n) elements. + + Params: + range = the input range to drop from + n = the number of elements to drop + + Returns: + `range` with `n` elements dropped + + See_Also: + $(REF popFrontExcatly, std, _range, primitives), + $(REF popBackExcatly, std, _range, primitives) ++/ +R dropExactly(R)(R range, size_t n) +if (isInputRange!R) +{ + popFrontExactly(range, n); + return range; +} +/// ditto +R dropBackExactly(R)(R range, size_t n) +if (isBidirectionalRange!R) +{ + popBackExactly(range, n); + return range; +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filterBidirectional; + + auto a = [1, 2, 3]; + assert(a.dropExactly(2) == [3]); + assert(a.dropBackExactly(2) == [1]); + + string s = "日本語"; + assert(s.dropExactly(2) == "語"); + assert(s.dropBackExactly(2) == "日"); + + auto bd = filterBidirectional!"true"([1, 2, 3]); + assert(bd.dropExactly(2).equal([3])); + assert(bd.dropBackExactly(2).equal([1])); +} + +/++ + Convenience function which calls + $(D range.popFront()) and returns $(D range). $(D dropOne) + makes it easier to pop an element from a range + and then pass it to another function within a single expression, + whereas $(D popFront) would require multiple statements. + + $(D dropBackOne) provides the same functionality but instead calls + $(D range.popBack()). ++/ +R dropOne(R)(R range) +if (isInputRange!R) +{ + range.popFront(); + return range; +} +/// ditto +R dropBackOne(R)(R range) +if (isBidirectionalRange!R) +{ + range.popBack(); + return range; +} + +/// +pure @safe nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filterBidirectional; + import std.container.dlist : DList; + + auto dl = DList!int(9, 1, 2, 3, 9); + assert(dl[].dropOne().dropBackOne().equal([1, 2, 3])); + + auto a = [1, 2, 3]; + assert(a.dropOne() == [2, 3]); + assert(a.dropBackOne() == [1, 2]); + + string s = "日本語"; + import std.exception : assumeWontThrow; + assert(assumeWontThrow(s.dropOne() == "本語")); + assert(assumeWontThrow(s.dropBackOne() == "日本")); + + auto bd = filterBidirectional!"true"([1, 2, 3]); + assert(bd.dropOne().equal([2, 3])); + assert(bd.dropBackOne().equal([1, 2])); +} + +/** +Create a range which repeats one value forever. + +Params: + value = the value to repeat + +Returns: + An infinite random access range with slicing. +*/ +struct Repeat(T) +{ +private: + //Store a non-qualified T when possible: This is to make Repeat assignable + static if ((is(T == class) || is(T == interface)) && (is(T == const) || is(T == immutable))) + { + import std.typecons : Rebindable; + alias UT = Rebindable!T; + } + else static if (is(T : Unqual!T) && is(Unqual!T : T)) + alias UT = Unqual!T; + else + alias UT = T; + UT _value; + +public: + /// Range primitives + @property inout(T) front() inout { return _value; } + + /// ditto + @property inout(T) back() inout { return _value; } + + /// ditto + enum bool empty = false; + + /// ditto + void popFront() {} + + /// ditto + void popBack() {} + + /// ditto + @property auto save() inout { return this; } + + /// ditto + inout(T) opIndex(size_t) inout { return _value; } + + /// ditto + auto opSlice(size_t i, size_t j) + in + { + assert( + i <= j, + "Attempting to slice a Repeat with a larger first argument than the second." + ); + } + body + { + return this.takeExactly(j - i); + } + private static struct DollarToken {} + + /// ditto + enum opDollar = DollarToken.init; + + /// ditto + auto opSlice(size_t, DollarToken) inout { return this; } +} + +/// Ditto +Repeat!T repeat(T)(T value) { return Repeat!T(value); } + +/// +pure @safe nothrow unittest +{ + import std.algorithm.comparison : equal; + + assert(equal(5.repeat().take(4), [ 5, 5, 5, 5 ])); +} + +pure @safe nothrow unittest +{ + import std.algorithm.comparison : equal; + + auto r = repeat(5); + alias R = typeof(r); + static assert(isBidirectionalRange!R); + static assert(isForwardRange!R); + static assert(isInfinite!R); + static assert(hasSlicing!R); + + assert(r.back == 5); + assert(r.front == 5); + assert(r.take(4).equal([ 5, 5, 5, 5 ])); + assert(r[0 .. 4].equal([ 5, 5, 5, 5 ])); + + R r2 = r[5 .. $]; + assert(r2.back == 5); + assert(r2.front == 5); +} + +/** + Repeats $(D value) exactly $(D n) times. Equivalent to $(D + take(repeat(value), n)). +*/ +Take!(Repeat!T) repeat(T)(T value, size_t n) +{ + return take(repeat(value), n); +} + +/// +pure @safe nothrow @nogc unittest +{ + import std.algorithm.comparison : equal; + + assert(equal(5.repeat(4), 5.repeat().take(4))); +} + +pure @safe nothrow unittest //12007 +{ + static class C{} + Repeat!(immutable int) ri; + ri = ri.save; + Repeat!(immutable C) rc; + rc = rc.save; + + import std.algorithm.setops : cartesianProduct; + import std.algorithm.comparison : equal; + import std.typecons : tuple; + immutable int[] A = [1,2,3]; + immutable int[] B = [4,5,6]; + + assert(equal(cartesianProduct(A,B), + [ + tuple(1, 4), tuple(1, 5), tuple(1, 6), + tuple(2, 4), tuple(2, 5), tuple(2, 6), + tuple(3, 4), tuple(3, 5), tuple(3, 6), + ])); +} + +/** +Given callable ($(REF isCallable, std,traits)) `fun`, create as a range +whose front is defined by successive calls to `fun()`. +This is especially useful to call function with global side effects (random +functions), or to create ranges expressed as a single delegate, rather than +an entire `front`/`popFront`/`empty` structure. +`fun` maybe be passed either a template alias parameter (existing +function, delegate, struct type defining `static opCall`) or +a run-time value argument (delegate, function object). +The result range models an InputRange +($(REF isInputRange, std,range,primitives)). +The resulting range will call `fun()` on construction, and every call to +`popFront`, and the cached value will be returned when `front` is called. + +Returns: an `inputRange` where each element represents another call to fun. +*/ +auto generate(Fun)(Fun fun) +if (isCallable!fun) +{ + auto gen = Generator!(Fun)(fun); + gen.popFront(); // prime the first element + return gen; +} + +/// ditto +auto generate(alias fun)() +if (isCallable!fun) +{ + auto gen = Generator!(fun)(); + gen.popFront(); // prime the first element + return gen; +} + +/// +@safe pure unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map; + + int i = 1; + auto powersOfTwo = generate!(() => i *= 2)().take(10); + assert(equal(powersOfTwo, iota(1, 11).map!"2^^a"())); +} + +/// +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + //Returns a run-time delegate + auto infiniteIota(T)(T low, T high) + { + T i = high; + return (){if (i == high) i = low; return i++;}; + } + //adapted as a range. + assert(equal(generate(infiniteIota(1, 4)).take(10), [1, 2, 3, 1, 2, 3, 1, 2, 3, 1])); +} + +/// +@safe unittest +{ + import std.format : format; + import std.random : uniform; + + auto r = generate!(() => uniform(0, 6)).take(10); + format("%(%s %)", r); +} + +private struct Generator(Fun...) +{ + static assert(Fun.length == 1); + static assert(isInputRange!Generator); + +private: + static if (is(Fun[0])) + Fun[0] fun; + else + alias fun = Fun[0]; + + enum returnByRef_ = (functionAttributes!fun & FunctionAttribute.ref_) ? true : false; + static if (returnByRef_) + ReturnType!fun *elem_; + else + ReturnType!fun elem_; +public: + /// Range primitives + enum empty = false; + + static if (returnByRef_) + { + /// ditto + ref front() @property + { + return *elem_; + } + /// ditto + void popFront() + { + elem_ = &fun(); + } + } + else + { + /// ditto + auto front() @property + { + return elem_; + } + /// ditto + void popFront() + { + elem_ = fun(); + } + } +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + struct StaticOpCall + { + static ubyte opCall() { return 5 ; } + } + + assert(equal(generate!StaticOpCall().take(10), repeat(5).take(10))); +} + +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + struct OpCall + { + ubyte opCall() @safe pure { return 5 ; } + } + + OpCall op; + assert(equal(generate(op).take(10), repeat(5).take(10))); +} + +// verify ref mechanism works +@system unittest +{ + int[10] arr; + int idx; + + ref int fun() { + auto x = idx++; + idx %= arr.length; + return arr[x]; + } + int y = 1; + foreach (ref x; generate!(fun).take(20)) + { + x += y++; + } + import std.algorithm.comparison : equal; + assert(equal(arr[], iota(12, 32, 2))); +} + +// assure front isn't the mechanism to make generate go to the next element. +@safe unittest +{ + int i; + auto g = generate!(() => ++i); + auto f = g.front; + assert(f == g.front); + g = g.drop(5); // reassign because generate caches + assert(g.front == f + 5); +} + +/** +Repeats the given forward range ad infinitum. If the original range is +infinite (fact that would make $(D Cycle) the identity application), +$(D Cycle) detects that and aliases itself to the range type +itself. That works for non-forward ranges too. +If the original range has random access, $(D Cycle) offers +random access and also offers a constructor taking an initial position +$(D index). $(D Cycle) works with static arrays in addition to ranges, +mostly for performance reasons. + +Note: The input range must not be empty. + +Tip: This is a great way to implement simple circular buffers. +*/ +struct Cycle(R) +if (isForwardRange!R && !isInfinite!R) +{ + static if (isRandomAccessRange!R && hasLength!R) + { + private R _original; + private size_t _index; + + /// Range primitives + this(R input, size_t index = 0) + { + _original = input; + _index = index % _original.length; + } + + /// ditto + @property auto ref front() + { + return _original[_index]; + } + + static if (is(typeof((cast(const R)_original)[_index]))) + { + /// ditto + @property auto ref front() const + { + return _original[_index]; + } + } + + static if (hasAssignableElements!R) + { + /// ditto + @property void front(ElementType!R val) + { + _original[_index] = val; + } + } + + /// ditto + enum bool empty = false; + + /// ditto + void popFront() + { + ++_index; + if (_index >= _original.length) + _index = 0; + } + + /// ditto + auto ref opIndex(size_t n) + { + return _original[(n + _index) % _original.length]; + } + + static if (is(typeof((cast(const R)_original)[_index])) && + is(typeof((cast(const R)_original).length))) + { + /// ditto + auto ref opIndex(size_t n) const + { + return _original[(n + _index) % _original.length]; + } + } + + static if (hasAssignableElements!R) + { + /// ditto + void opIndexAssign(ElementType!R val, size_t n) + { + _original[(n + _index) % _original.length] = val; + } + } + + /// ditto + @property Cycle save() + { + //No need to call _original.save, because Cycle never actually modifies _original + return Cycle(_original, _index); + } + + private static struct DollarToken {} + + /// ditto + enum opDollar = DollarToken.init; + + static if (hasSlicing!R) + { + /// ditto + auto opSlice(size_t i, size_t j) + in + { + assert(i <= j); + } + body + { + return this[i .. $].takeExactly(j - i); + } + + /// ditto + auto opSlice(size_t i, DollarToken) + { + return typeof(this)(_original, _index + i); + } + } + } + else + { + private R _original; + private R _current; + + /// ditto + this(R input) + { + _original = input; + _current = input.save; + } + + /// ditto + @property auto ref front() + { + return _current.front; + } + + static if (is(typeof((cast(const R)_current).front))) + { + /// ditto + @property auto ref front() const + { + return _current.front; + } + } + + static if (hasAssignableElements!R) + { + /// ditto + @property auto front(ElementType!R val) + { + return _current.front = val; + } + } + + /// ditto + enum bool empty = false; + + /// ditto + void popFront() + { + _current.popFront(); + if (_current.empty) + _current = _original.save; + } + + /// ditto + @property Cycle save() + { + //No need to call _original.save, because Cycle never actually modifies _original + Cycle ret = this; + ret._original = _original; + ret._current = _current.save; + return ret; + } + } +} + +/// ditto +template Cycle(R) +if (isInfinite!R) +{ + alias Cycle = R; +} + +/// +struct Cycle(R) +if (isStaticArray!R) +{ + private alias ElementType = typeof(R.init[0]); + private ElementType* _ptr; + private size_t _index; + +nothrow: + + /// Range primitives + this(ref R input, size_t index = 0) @system + { + _ptr = input.ptr; + _index = index % R.length; + } + + /// ditto + @property ref inout(ElementType) front() inout @safe + { + static ref auto trustedPtrIdx(typeof(_ptr) p, size_t idx) @trusted + { + return p[idx]; + } + return trustedPtrIdx(_ptr, _index); + } + + /// ditto + enum bool empty = false; + + /// ditto + void popFront() @safe + { + ++_index; + if (_index >= R.length) + _index = 0; + } + + /// ditto + ref inout(ElementType) opIndex(size_t n) inout @safe + { + static ref auto trustedPtrIdx(typeof(_ptr) p, size_t idx) @trusted + { + return p[idx % R.length]; + } + return trustedPtrIdx(_ptr, n + _index); + } + + /// ditto + @property inout(Cycle) save() inout @safe + { + return this; + } + + private static struct DollarToken {} + /// ditto + enum opDollar = DollarToken.init; + + /// ditto + auto opSlice(size_t i, size_t j) @safe + in + { + assert( + i <= j, + "Attempting to slice a Repeat with a larger first argument than the second." + ); + } + body + { + return this[i .. $].takeExactly(j - i); + } + + /// ditto + inout(typeof(this)) opSlice(size_t i, DollarToken) inout @safe + { + static auto trustedCtor(typeof(_ptr) p, size_t idx) @trusted + { + return cast(inout) Cycle(*cast(R*)(p), idx); + } + return trustedCtor(_ptr, _index + i); + } +} + +/// Ditto +auto cycle(R)(R input) +if (isInputRange!R) +{ + static assert(isForwardRange!R || isInfinite!R, + "Cycle requires a forward range argument unless it's statically known" + ~ " to be infinite"); + assert(!input.empty, "Attempting to pass an empty input to cycle"); + static if (isInfinite!R) return input; + else return Cycle!R(input); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : cycle, take; + + // Here we create an infinitive cyclic sequence from [1, 2] + // (i.e. get here [1, 2, 1, 2, 1, 2 and so on]) then + // take 5 elements of this sequence (so we have [1, 2, 1, 2, 1]) + // and compare them with the expected values for equality. + assert(cycle([1, 2]).take(5).equal([ 1, 2, 1, 2, 1 ])); +} + +/// Ditto +Cycle!R cycle(R)(R input, size_t index = 0) +if (isRandomAccessRange!R && !isInfinite!R) +{ + assert(!input.empty, "Attempting to pass an empty input to cycle"); + return Cycle!R(input, index); +} + +/// Ditto +Cycle!R cycle(R)(ref R input, size_t index = 0) @system +if (isStaticArray!R) +{ + return Cycle!R(input, index); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : AllDummyRanges; + + static assert(isForwardRange!(Cycle!(uint[]))); + + // Make sure ref is getting propagated properly. + int[] nums = [1,2,3]; + auto c2 = cycle(nums); + c2[3]++; + assert(nums[0] == 2); + + immutable int[] immarr = [1, 2, 3]; + + foreach (DummyType; AllDummyRanges) + { + static if (isForwardRange!DummyType) + { + DummyType dummy; + auto cy = cycle(dummy); + static assert(isForwardRange!(typeof(cy))); + auto t = take(cy, 20); + assert(equal(t, [1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10])); + + const cRange = cy; + assert(cRange.front == 1); + + static if (hasAssignableElements!DummyType) + { + { + cy.front = 66; + scope(exit) cy.front = 1; + assert(dummy.front == 66); + } + + static if (isRandomAccessRange!DummyType) + { + { + cy[10] = 66; + scope(exit) cy[10] = 1; + assert(dummy.front == 66); + } + + assert(cRange[10] == 1); + } + } + + static if (hasSlicing!DummyType) + { + auto slice = cy[5 .. 15]; + assert(equal(slice, [6, 7, 8, 9, 10, 1, 2, 3, 4, 5])); + static assert(is(typeof(slice) == typeof(takeExactly(cy, 5)))); + + auto infSlice = cy[7 .. $]; + assert(equal(take(infSlice, 5), [8, 9, 10, 1, 2])); + static assert(isInfinite!(typeof(infSlice))); + } + } + } +} + +@system unittest // For static arrays. +{ + import std.algorithm.comparison : equal; + + int[3] a = [ 1, 2, 3 ]; + static assert(isStaticArray!(typeof(a))); + auto c = cycle(a); + assert(a.ptr == c._ptr); + assert(equal(take(cycle(a), 5), [ 1, 2, 3, 1, 2 ][])); + static assert(isForwardRange!(typeof(c))); + + // Test qualifiers on slicing. + alias C = typeof(c); + static assert(is(typeof(c[1 .. $]) == C)); + const cConst = c; + static assert(is(typeof(cConst[1 .. $]) == const(C))); +} + +@safe unittest // For infinite ranges +{ + struct InfRange + { + void popFront() { } + @property int front() { return 0; } + enum empty = false; + auto save() { return this; } + } + struct NonForwardInfRange + { + void popFront() { } + @property int front() { return 0; } + enum empty = false; + } + + InfRange i; + NonForwardInfRange j; + auto c = cycle(i); + assert(c == i); + //make sure it can alias out even non-forward infinite ranges + static assert(is(typeof(j.cycle) == typeof(j))); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + int[5] arr = [0, 1, 2, 3, 4]; + auto cleD = cycle(arr[]); //Dynamic + assert(equal(cleD[5 .. 10], arr[])); + + //n is a multiple of 5 worth about 3/4 of size_t.max + auto n = size_t.max/4 + size_t.max/2; + n -= n % 5; + + //Test index overflow + foreach (_ ; 0 .. 10) + { + cleD = cleD[n .. $]; + assert(equal(cleD[5 .. 10], arr[])); + } +} + +@system unittest +{ + import std.algorithm.comparison : equal; + + int[5] arr = [0, 1, 2, 3, 4]; + auto cleS = cycle(arr); //Static + assert(equal(cleS[5 .. 10], arr[])); + + //n is a multiple of 5 worth about 3/4 of size_t.max + auto n = size_t.max/4 + size_t.max/2; + n -= n % 5; + + //Test index overflow + foreach (_ ; 0 .. 10) + { + cleS = cleS[n .. $]; + assert(equal(cleS[5 .. 10], arr[])); + } +} + +@system unittest +{ + import std.algorithm.comparison : equal; + + int[1] arr = [0]; + auto cleS = cycle(arr); + cleS = cleS[10 .. $]; + assert(equal(cleS[5 .. 10], 0.repeat(5))); + assert(cleS.front == 0); +} + +@system unittest //10845 +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filter; + + auto a = inputRangeObject(iota(3).filter!"true"); + assert(equal(cycle(a).take(10), [0, 1, 2, 0, 1, 2, 0, 1, 2, 0])); +} + +@safe unittest // 12177 +{ + static assert(__traits(compiles, recurrence!q{a[n - 1] ~ a[n - 2]}("1", "0"))); +} + +// Issue 13390 +@system unittest +{ + import core.exception : AssertError; + import std.exception : assertThrown; + assertThrown!AssertError(cycle([0, 1, 2][0 .. 0])); +} + +private alias lengthType(R) = typeof(R.init.length.init); + +/** + Iterate several ranges in lockstep. The element type is a proxy tuple + that allows accessing the current element in the $(D n)th range by + using $(D e[n]). + + `zip` is similar to $(LREF lockstep), but `lockstep` doesn't + bundle its elements and uses the `opApply` protocol. + `lockstep` allows reference access to the elements in + `foreach` iterations. + + Params: + sp = controls what `zip` will do if the _ranges are different lengths + ranges = the ranges to zip together + Returns: + At minimum, an input range. `Zip` offers the lowest range facilities + of all components, e.g. it offers random access iff all ranges offer + random access, and also offers mutation and swapping if all ranges offer + it. Due to this, `Zip` is extremely powerful because it allows manipulating + several ranges in lockstep. + Throws: + An `Exception` if all of the _ranges are not the same length and + `sp` is set to `StoppingPolicy.requireSameLength`. +*/ +struct Zip(Ranges...) +if (Ranges.length && allSatisfy!(isInputRange, Ranges)) +{ + import std.format : format; //for generic mixins + import std.typecons : Tuple; + + alias R = Ranges; + private R ranges; + alias ElementType = Tuple!(staticMap!(.ElementType, R)); + private StoppingPolicy stoppingPolicy = StoppingPolicy.shortest; + +/** + Builds an object. Usually this is invoked indirectly by using the + $(LREF zip) function. + */ + this(R rs, StoppingPolicy s = StoppingPolicy.shortest) + { + ranges[] = rs[]; + stoppingPolicy = s; + } + +/** + Returns $(D true) if the range is at end. The test depends on the + stopping policy. +*/ + static if (allSatisfy!(isInfinite, R)) + { + // BUG: Doesn't propagate infiniteness if only some ranges are infinite + // and s == StoppingPolicy.longest. This isn't fixable in the + // current design since StoppingPolicy is known only at runtime. + enum bool empty = false; + } + else + { + /// + @property bool empty() + { + import std.exception : enforce; + import std.meta : anySatisfy; + + final switch (stoppingPolicy) + { + case StoppingPolicy.shortest: + foreach (i, Unused; R) + { + if (ranges[i].empty) return true; + } + return false; + case StoppingPolicy.longest: + static if (anySatisfy!(isInfinite, R)) + { + return false; + } + else + { + foreach (i, Unused; R) + { + if (!ranges[i].empty) return false; + } + return true; + } + case StoppingPolicy.requireSameLength: + foreach (i, Unused; R[1 .. $]) + { + enforce(ranges[0].empty == + ranges[i + 1].empty, + "Inequal-length ranges passed to Zip"); + } + return ranges[0].empty; + } + assert(false); + } + } + + static if (allSatisfy!(isForwardRange, R)) + { + /// + @property Zip save() + { + //Zip(ranges[0].save, ranges[1].save, ..., stoppingPolicy) + return mixin (q{Zip(%(ranges[%s].save%|, %), stoppingPolicy)}.format(iota(0, R.length))); + } + } + + private .ElementType!(R[i]) tryGetInit(size_t i)() + { + alias E = .ElementType!(R[i]); + static if (!is(typeof({static E i;}))) + throw new Exception("Range with non-default constructable elements exhausted."); + else + return E.init; + } + +/** + Returns the current iterated element. +*/ + @property ElementType front() + { + @property tryGetFront(size_t i)(){return ranges[i].empty ? tryGetInit!i() : ranges[i].front;} + //ElementType(tryGetFront!0, tryGetFront!1, ...) + return mixin(q{ElementType(%(tryGetFront!%s, %))}.format(iota(0, R.length))); + } + +/** + Sets the front of all iterated ranges. +*/ + static if (allSatisfy!(hasAssignableElements, R)) + { + @property void front(ElementType v) + { + foreach (i, Unused; R) + { + if (!ranges[i].empty) + { + ranges[i].front = v[i]; + } + } + } + } + +/** + Moves out the front. +*/ + static if (allSatisfy!(hasMobileElements, R)) + { + ElementType moveFront() + { + @property tryMoveFront(size_t i)(){return ranges[i].empty ? tryGetInit!i() : ranges[i].moveFront();} + //ElementType(tryMoveFront!0, tryMoveFront!1, ...) + return mixin(q{ElementType(%(tryMoveFront!%s, %))}.format(iota(0, R.length))); + } + } + +/** + Returns the rightmost element. +*/ + static if (allSatisfy!(isBidirectionalRange, R)) + { + @property ElementType back() + { + //TODO: Fixme! BackElement != back of all ranges in case of jagged-ness + + @property tryGetBack(size_t i)(){return ranges[i].empty ? tryGetInit!i() : ranges[i].back;} + //ElementType(tryGetBack!0, tryGetBack!1, ...) + return mixin(q{ElementType(%(tryGetBack!%s, %))}.format(iota(0, R.length))); + } + +/** + Moves out the back. +*/ + static if (allSatisfy!(hasMobileElements, R)) + { + ElementType moveBack() + { + //TODO: Fixme! BackElement != back of all ranges in case of jagged-ness + + @property tryMoveBack(size_t i)(){return ranges[i].empty ? tryGetInit!i() : ranges[i].moveFront();} + //ElementType(tryMoveBack!0, tryMoveBack!1, ...) + return mixin(q{ElementType(%(tryMoveBack!%s, %))}.format(iota(0, R.length))); + } + } + +/** + Returns the current iterated element. +*/ + static if (allSatisfy!(hasAssignableElements, R)) + { + @property void back(ElementType v) + { + //TODO: Fixme! BackElement != back of all ranges in case of jagged-ness. + //Not sure the call is even legal for StoppingPolicy.longest + + foreach (i, Unused; R) + { + if (!ranges[i].empty) + { + ranges[i].back = v[i]; + } + } + } + } + } + +/** + Advances to the next element in all controlled ranges. +*/ + void popFront() + { + import std.exception : enforce; + + final switch (stoppingPolicy) + { + case StoppingPolicy.shortest: + foreach (i, Unused; R) + { + assert(!ranges[i].empty); + ranges[i].popFront(); + } + break; + case StoppingPolicy.longest: + foreach (i, Unused; R) + { + if (!ranges[i].empty) ranges[i].popFront(); + } + break; + case StoppingPolicy.requireSameLength: + foreach (i, Unused; R) + { + enforce(!ranges[i].empty, "Invalid Zip object"); + ranges[i].popFront(); + } + break; + } + } + +/** + Calls $(D popBack) for all controlled ranges. +*/ + static if (allSatisfy!(isBidirectionalRange, R)) + { + void popBack() + { + //TODO: Fixme! In case of jaggedness, this is wrong. + import std.exception : enforce; + + final switch (stoppingPolicy) + { + case StoppingPolicy.shortest: + foreach (i, Unused; R) + { + assert(!ranges[i].empty); + ranges[i].popBack(); + } + break; + case StoppingPolicy.longest: + foreach (i, Unused; R) + { + if (!ranges[i].empty) ranges[i].popBack(); + } + break; + case StoppingPolicy.requireSameLength: + foreach (i, Unused; R) + { + enforce(!ranges[i].empty, "Invalid Zip object"); + ranges[i].popBack(); + } + break; + } + } + } + +/** + Returns the length of this range. Defined only if all ranges define + $(D length). +*/ + static if (allSatisfy!(hasLength, R)) + { + @property auto length() + { + static if (Ranges.length == 1) + return ranges[0].length; + else + { + if (stoppingPolicy == StoppingPolicy.requireSameLength) + return ranges[0].length; + + //[min|max](ranges[0].length, ranges[1].length, ...) + import std.algorithm.comparison : min, max; + if (stoppingPolicy == StoppingPolicy.shortest) + return mixin(q{min(%(ranges[%s].length%|, %))}.format(iota(0, R.length))); + else + return mixin(q{max(%(ranges[%s].length%|, %))}.format(iota(0, R.length))); + } + } + + alias opDollar = length; + } + +/** + Returns a slice of the range. Defined only if all range define + slicing. +*/ + static if (allSatisfy!(hasSlicing, R)) + { + auto opSlice(size_t from, size_t to) + { + //Slicing an infinite range yields the type Take!R + //For finite ranges, the type Take!R aliases to R + alias ZipResult = Zip!(staticMap!(Take, R)); + + //ZipResult(ranges[0][from .. to], ranges[1][from .. to], ..., stoppingPolicy) + return mixin (q{ZipResult(%(ranges[%s][from .. to]%|, %), stoppingPolicy)}.format(iota(0, R.length))); + } + } + +/** + Returns the $(D n)th element in the composite range. Defined if all + ranges offer random access. +*/ + static if (allSatisfy!(isRandomAccessRange, R)) + { + ElementType opIndex(size_t n) + { + //TODO: Fixme! This may create an out of bounds access + //for StoppingPolicy.longest + + //ElementType(ranges[0][n], ranges[1][n], ...) + return mixin (q{ElementType(%(ranges[%s][n]%|, %))}.format(iota(0, R.length))); + } + +/** + Assigns to the $(D n)th element in the composite range. Defined if + all ranges offer random access. +*/ + static if (allSatisfy!(hasAssignableElements, R)) + { + void opIndexAssign(ElementType v, size_t n) + { + //TODO: Fixme! Not sure the call is even legal for StoppingPolicy.longest + foreach (i, Range; R) + { + ranges[i][n] = v[i]; + } + } + } + +/** + Destructively reads the $(D n)th element in the composite + range. Defined if all ranges offer random access. +*/ + static if (allSatisfy!(hasMobileElements, R)) + { + ElementType moveAt(size_t n) + { + //TODO: Fixme! This may create an out of bounds access + //for StoppingPolicy.longest + + //ElementType(ranges[0].moveAt(n), ranges[1].moveAt(n), ..., ) + return mixin (q{ElementType(%(ranges[%s].moveAt(n)%|, %))}.format(iota(0, R.length))); + } + } + } +} + +/// Ditto +auto zip(Ranges...)(Ranges ranges) +if (Ranges.length && allSatisfy!(isInputRange, Ranges)) +{ + return Zip!Ranges(ranges); +} + +/// +pure @safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map; + + // pairwise sum + auto arr = [0, 1, 2]; + assert(zip(arr, arr.dropOne).map!"a[0] + a[1]".equal([1, 3])); +} + +/// +pure @safe unittest +{ + import std.conv : to; + + int[] a = [ 1, 2, 3 ]; + string[] b = [ "a", "b", "c" ]; + string[] result; + + foreach (tup; zip(a, b)) + { + result ~= tup[0].to!string ~ tup[1]; + } + + assert(result == [ "1a", "2b", "3c" ]); + + size_t idx = 0; + // unpacking tuple elements with foreach + foreach (e1, e2; zip(a, b)) + { + assert(e1 == a[idx]); + assert(e2 == b[idx]); + ++idx; + } +} + +/// $(D zip) is powerful - the following code sorts two arrays in parallel: +pure @safe unittest +{ + import std.algorithm.sorting : sort; + + int[] a = [ 1, 2, 3 ]; + string[] b = [ "a", "c", "b" ]; + zip(a, b).sort!((t1, t2) => t1[0] > t2[0]); + + assert(a == [ 3, 2, 1 ]); + // b is sorted according to a's sorting + assert(b == [ "b", "c", "a" ]); +} + +/// Ditto +auto zip(Ranges...)(StoppingPolicy sp, Ranges ranges) +if (Ranges.length && allSatisfy!(isInputRange, Ranges)) +{ + return Zip!Ranges(ranges, sp); +} + +/** + Dictates how iteration in a $(D Zip) should stop. By default stop at + the end of the shortest of all ranges. +*/ +enum StoppingPolicy +{ + /// Stop when the shortest range is exhausted + shortest, + /// Stop when the longest range is exhausted + longest, + /// Require that all ranges are equal + requireSameLength, +} + +@system unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filter, map; + import std.algorithm.mutation : swap; + import std.algorithm.sorting : sort; + + import std.exception : assertThrown, assertNotThrown; + import std.typecons : tuple; + + int[] a = [ 1, 2, 3 ]; + float[] b = [ 1.0, 2.0, 3.0 ]; + foreach (e; zip(a, b)) + { + assert(e[0] == e[1]); + } + + swap(a[0], a[1]); + auto z = zip(a, b); + //swap(z.front(), z.back()); + sort!("a[0] < b[0]")(zip(a, b)); + assert(a == [1, 2, 3]); + assert(b == [2.0, 1.0, 3.0]); + + z = zip(StoppingPolicy.requireSameLength, a, b); + assertNotThrown(z.popBack()); + assertNotThrown(z.popBack()); + assertNotThrown(z.popBack()); + assert(z.empty); + assertThrown(z.popBack()); + + a = [ 1, 2, 3 ]; + b = [ 1.0, 2.0, 3.0 ]; + sort!("a[0] > b[0]")(zip(StoppingPolicy.requireSameLength, a, b)); + assert(a == [3, 2, 1]); + assert(b == [3.0, 2.0, 1.0]); + + a = []; + b = []; + assert(zip(StoppingPolicy.requireSameLength, a, b).empty); + + // Test infiniteness propagation. + static assert(isInfinite!(typeof(zip(repeat(1), repeat(1))))); + + // Test stopping policies with both value and reference. + auto a1 = [1, 2]; + auto a2 = [1, 2, 3]; + auto stuff = tuple(tuple(a1, a2), + tuple(filter!"a"(a1), filter!"a"(a2))); + + alias FOO = Zip!(immutable(int)[], immutable(float)[]); + + foreach (t; stuff.expand) + { + auto arr1 = t[0]; + auto arr2 = t[1]; + auto zShortest = zip(arr1, arr2); + assert(equal(map!"a[0]"(zShortest), [1, 2])); + assert(equal(map!"a[1]"(zShortest), [1, 2])); + + try { + auto zSame = zip(StoppingPolicy.requireSameLength, arr1, arr2); + foreach (elem; zSame) {} + assert(0); + } catch (Throwable) { /* It's supposed to throw.*/ } + + auto zLongest = zip(StoppingPolicy.longest, arr1, arr2); + assert(!zLongest.ranges[0].empty); + assert(!zLongest.ranges[1].empty); + + zLongest.popFront(); + zLongest.popFront(); + assert(!zLongest.empty); + assert(zLongest.ranges[0].empty); + assert(!zLongest.ranges[1].empty); + + zLongest.popFront(); + assert(zLongest.empty); + } + + // BUG 8900 + assert(zip([1, 2], repeat('a')).array == [tuple(1, 'a'), tuple(2, 'a')]); + assert(zip(repeat('a'), [1, 2]).array == [tuple('a', 1), tuple('a', 2)]); + + // Doesn't work yet. Issues w/ emplace. + // static assert(is(Zip!(immutable int[], immutable float[]))); + + + // These unittests pass, but make the compiler consume an absurd amount + // of RAM and time. Therefore, they should only be run if explicitly + // uncommented when making changes to Zip. Also, running them using + // make -fwin32.mak unittest makes the compiler completely run out of RAM. + // You need to test just this module. + /+ + foreach (DummyType1; AllDummyRanges) + { + DummyType1 d1; + foreach (DummyType2; AllDummyRanges) + { + DummyType2 d2; + auto r = zip(d1, d2); + assert(equal(map!"a[0]"(r), [1,2,3,4,5,6,7,8,9,10])); + assert(equal(map!"a[1]"(r), [1,2,3,4,5,6,7,8,9,10])); + + static if (isForwardRange!DummyType1 && isForwardRange!DummyType2) + { + static assert(isForwardRange!(typeof(r))); + } + + static if (isBidirectionalRange!DummyType1 && + isBidirectionalRange!DummyType2) { + static assert(isBidirectionalRange!(typeof(r))); + } + static if (isRandomAccessRange!DummyType1 && + isRandomAccessRange!DummyType2) { + static assert(isRandomAccessRange!(typeof(r))); + } + } + } + +/ +} + +pure @safe unittest +{ + import std.algorithm.sorting : sort; + + auto a = [5,4,3,2,1]; + auto b = [3,1,2,5,6]; + auto z = zip(a, b); + + sort!"a[0] < b[0]"(z); + + assert(a == [1, 2, 3, 4, 5]); + assert(b == [6, 5, 2, 1, 3]); +} + +@safe pure unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : tuple; + + auto LL = iota(1L, 1000L); + auto z = zip(LL, [4]); + + assert(equal(z, [tuple(1L,4)])); + + auto LL2 = iota(0L, 500L); + auto z2 = zip([7], LL2); + assert(equal(z2, [tuple(7, 0L)])); +} + +// Text for Issue 11196 +@safe pure unittest +{ + import std.exception : assertThrown; + + static struct S { @disable this(); } + assert(zip((S[5]).init[]).length == 5); + assert(zip(StoppingPolicy.longest, cast(S[]) null, new int[1]).length == 1); + assertThrown(zip(StoppingPolicy.longest, cast(S[]) null, new int[1]).front); +} + +@safe pure unittest //12007 +{ + static struct R + { + enum empty = false; + void popFront(){} + int front(){return 1;} @property + R save(){return this;} @property + void opAssign(R) @disable; + } + R r; + auto z = zip(r, r); + assert(z.save == z); +} + +pure @system unittest +{ + import std.typecons : tuple; + + auto r1 = [0,1,2]; + auto r2 = [1,2,3]; + auto z1 = zip(refRange(&r1), refRange(&r2)); + auto z2 = z1.save; + z1.popFront(); + assert(z1.front == tuple(1,2)); + assert(z2.front == tuple(0,1)); +} + +/* + Generate lockstep's opApply function as a mixin string. + If withIndex is true prepend a size_t index to the delegate. +*/ +private string lockstepMixin(Ranges...)(bool withIndex, bool reverse) +{ + import std.format : format; + + string[] params; + string[] emptyChecks; + string[] dgArgs; + string[] popFronts; + string indexDef; + string indexInc; + + if (withIndex) + { + params ~= "size_t"; + dgArgs ~= "index"; + if (reverse) + { + indexDef = q{ + size_t index = ranges[0].length-1; + enforce(_stoppingPolicy == StoppingPolicy.requireSameLength, + "lockstep can only be used with foreach_reverse when stoppingPolicy == requireSameLength"); + + foreach (range; ranges[1..$]) + enforce(range.length == ranges[0].length); + }; + indexInc = "--index;"; + } + else + { + indexDef = "size_t index = 0;"; + indexInc = "++index;"; + } + } + + foreach (idx, Range; Ranges) + { + params ~= format("%sElementType!(Ranges[%s])", hasLvalueElements!Range ? "ref " : "", idx); + emptyChecks ~= format("!ranges[%s].empty", idx); + if (reverse) + { + dgArgs ~= format("ranges[%s].back", idx); + popFronts ~= format("ranges[%s].popBack();", idx); + } + else + { + dgArgs ~= format("ranges[%s].front", idx); + popFronts ~= format("ranges[%s].popFront();", idx); + } + } + + string name = reverse ? "opApplyReverse" : "opApply"; + + return format( + q{ + int %s(scope int delegate(%s) dg) + { + import std.exception : enforce; + + auto ranges = _ranges; + int res; + %s + + while (%s) + { + res = dg(%s); + if (res) break; + %s + %s + } + + if (_stoppingPolicy == StoppingPolicy.requireSameLength) + { + foreach (range; ranges) + enforce(range.empty); + } + return res; + } + }, name, params.join(", "), indexDef, + emptyChecks.join(" && "), dgArgs.join(", "), + popFronts.join("\n "), + indexInc); +} + +/** + Iterate multiple ranges in lockstep using a $(D foreach) loop. In contrast to + $(LREF zip) it allows reference access to its elements. If only a single + range is passed in, the $(D Lockstep) aliases itself away. If the + ranges are of different lengths and $(D s) == $(D StoppingPolicy.shortest) + stop after the shortest range is empty. If the ranges are of different + lengths and $(D s) == $(D StoppingPolicy.requireSameLength), throw an + exception. $(D s) may not be $(D StoppingPolicy.longest), and passing this + will throw an exception. + + Iterating over $(D Lockstep) in reverse and with an index is only possible + when $(D s) == $(D StoppingPolicy.requireSameLength), in order to preserve + indexes. If an attempt is made at iterating in reverse when $(D s) == + $(D StoppingPolicy.shortest), an exception will be thrown. + + By default $(D StoppingPolicy) is set to $(D StoppingPolicy.shortest). + + See_Also: $(LREF zip) + + `lockstep` is similar to $(LREF zip), but `zip` bundles its + elements and returns a range. + `lockstep` also supports reference access. + Use `zip` if you want to pass the result to a range function. +*/ +struct Lockstep(Ranges...) +if (Ranges.length > 1 && allSatisfy!(isInputRange, Ranges)) +{ + /// + this(R ranges, StoppingPolicy sp = StoppingPolicy.shortest) + { + import std.exception : enforce; + + _ranges = ranges; + enforce(sp != StoppingPolicy.longest, + "Can't use StoppingPolicy.Longest on Lockstep."); + _stoppingPolicy = sp; + } + + mixin(lockstepMixin!Ranges(false, false)); + mixin(lockstepMixin!Ranges(true, false)); + static if (allSatisfy!(isBidirectionalRange, Ranges)) + { + mixin(lockstepMixin!Ranges(false, true)); + static if (allSatisfy!(hasLength, Ranges)) + { + mixin(lockstepMixin!Ranges(true, true)); + } + else + { + mixin(lockstepReverseFailMixin!Ranges(true)); + } + } + else + { + mixin(lockstepReverseFailMixin!Ranges(false)); + mixin(lockstepReverseFailMixin!Ranges(true)); + } + +private: + alias R = Ranges; + R _ranges; + StoppingPolicy _stoppingPolicy; +} + +string lockstepReverseFailMixin(Ranges...)(bool withIndex) +{ + import std.format : format; + string[] params; + string message; + + if (withIndex) + { + message = "Indexed reverse iteration with lockstep is only supported" + ~"if all ranges are bidirectional and have a length.\n"; + } + else + { + message = "Reverse iteration with lockstep is only supported if all ranges are bidirectional.\n"; + } + + if (withIndex) + { + params ~= "size_t"; + } + + foreach (idx, Range; Ranges) + { + params ~= format("%sElementType!(Ranges[%s])", hasLvalueElements!Range ? "ref " : "", idx); + } + + return format( + q{ + int opApplyReverse()(scope int delegate(%s) dg) + { + static assert(false, "%s"); + } + }, params.join(", "), message); +} + +// For generic programming, make sure Lockstep!(Range) is well defined for a +// single range. +template Lockstep(Range) +{ + alias Lockstep = Range; +} + +/// Ditto +Lockstep!(Ranges) lockstep(Ranges...)(Ranges ranges) +if (allSatisfy!(isInputRange, Ranges)) +{ + return Lockstep!(Ranges)(ranges); +} +/// Ditto +Lockstep!(Ranges) lockstep(Ranges...)(Ranges ranges, StoppingPolicy s) +if (allSatisfy!(isInputRange, Ranges)) +{ + static if (Ranges.length > 1) + return Lockstep!Ranges(ranges, s); + else + return ranges[0]; +} + +/// +@system unittest +{ + auto arr1 = [1,2,3,4,5,100]; + auto arr2 = [6,7,8,9,10]; + + foreach (ref a, b; lockstep(arr1, arr2)) + { + a += b; + } + + assert(arr1 == [7,9,11,13,15,100]); + + /// Lockstep also supports iterating with an index variable: + foreach (index, a, b; lockstep(arr1, arr2)) + { + assert(arr1[index] == a); + assert(arr2[index] == b); + } +} + +@system unittest // Bugzilla 15860: foreach_reverse on lockstep +{ + auto arr1 = [0, 1, 2, 3]; + auto arr2 = [4, 5, 6, 7]; + + size_t n = arr1.length -1; + foreach_reverse (index, a, b; lockstep(arr1, arr2, StoppingPolicy.requireSameLength)) + { + assert(n == index); + assert(index == a); + assert(arr1[index] == a); + assert(arr2[index] == b); + n--; + } + + auto arr3 = [4, 5]; + n = 1; + foreach_reverse (a, b; lockstep(arr1, arr3)) + { + assert(a == arr1[$-n] && b == arr3[$-n]); + n++; + } +} + +@system unittest +{ + import std.algorithm.iteration : filter; + import std.conv : to; + + // The filters are to make these the lowest common forward denominator ranges, + // i.e. w/o ref return, random access, length, etc. + auto foo = filter!"a"([1,2,3,4,5]); + immutable bar = [6f,7f,8f,9f,10f].idup; + auto l = lockstep(foo, bar); + + // Should work twice. These are forward ranges with implicit save. + foreach (i; 0 .. 2) + { + uint[] res1; + float[] res2; + + foreach (a, ref b; l) + { + res1 ~= a; + res2 ~= b; + } + + assert(res1 == [1,2,3,4,5]); + assert(res2 == [6,7,8,9,10]); + assert(bar == [6f,7f,8f,9f,10f]); + } + + // Doc example. + auto arr1 = [1,2,3,4,5]; + auto arr2 = [6,7,8,9,10]; + + foreach (ref a, ref b; lockstep(arr1, arr2)) + { + a += b; + } + + assert(arr1 == [7,9,11,13,15]); + + // Make sure StoppingPolicy.requireSameLength doesn't throw. + auto ls = lockstep(arr1, arr2, StoppingPolicy.requireSameLength); + + int k = 1; + foreach (a, b; ls) + { + assert(a - b == k); + ++k; + } + + // Make sure StoppingPolicy.requireSameLength throws. + arr2.popBack(); + ls = lockstep(arr1, arr2, StoppingPolicy.requireSameLength); + + try { + foreach (a, b; ls) {} + assert(0); + } catch (Exception) {} + + // Just make sure 1-range case instantiates. This hangs the compiler + // when no explicit stopping policy is specified due to Bug 4652. + auto stuff = lockstep([1,2,3,4,5], StoppingPolicy.shortest); + foreach (int i, a; stuff) + { + assert(stuff[i] == a); + } + + // Test with indexing. + uint[] res1; + float[] res2; + size_t[] indices; + foreach (i, a, b; lockstep(foo, bar)) + { + indices ~= i; + res1 ~= a; + res2 ~= b; + } + + assert(indices == to!(size_t[])([0, 1, 2, 3, 4])); + assert(res1 == [1,2,3,4,5]); + assert(res2 == [6f,7f,8f,9f,10f]); + + // Make sure we've worked around the relevant compiler bugs and this at least + // compiles w/ >2 ranges. + lockstep(foo, foo, foo); + + // Make sure it works with const. + const(int[])[] foo2 = [[1, 2, 3]]; + const(int[])[] bar2 = [[4, 5, 6]]; + auto c = chain(foo2, bar2); + + foreach (f, b; lockstep(c, c)) {} + + // Regression 10468 + foreach (x, y; lockstep(iota(0, 10), iota(0, 10))) { } +} + +@system unittest +{ + struct RvalueRange + { + int[] impl; + @property bool empty() { return impl.empty; } + @property int front() { return impl[0]; } // N.B. non-ref + void popFront() { impl.popFront(); } + } + auto data1 = [ 1, 2, 3, 4 ]; + auto data2 = [ 5, 6, 7, 8 ]; + auto r1 = RvalueRange(data1); + auto r2 = data2; + foreach (a, ref b; lockstep(r1, r2)) + { + a++; + b++; + } + assert(data1 == [ 1, 2, 3, 4 ]); // changes to a do not propagate to data + assert(data2 == [ 6, 7, 8, 9 ]); // but changes to b do. + + // Since r1 is by-value only, the compiler should reject attempts to + // foreach over it with ref. + static assert(!__traits(compiles, { + foreach (ref a, ref b; lockstep(r1, r2)) { a++; } + })); +} + +/** +Creates a mathematical sequence given the initial values and a +recurrence function that computes the next value from the existing +values. The sequence comes in the form of an infinite forward +range. The type $(D Recurrence) itself is seldom used directly; most +often, recurrences are obtained by calling the function $(D +recurrence). + +When calling $(D recurrence), the function that computes the next +value is specified as a template argument, and the initial values in +the recurrence are passed as regular arguments. For example, in a +Fibonacci sequence, there are two initial values (and therefore a +state size of 2) because computing the next Fibonacci value needs the +past two values. + +The signature of this function should be: +---- +auto fun(R)(R state, size_t n) +---- +where $(D n) will be the index of the current value, and $(D state) will be an +opaque state vector that can be indexed with array-indexing notation +$(D state[i]), where valid values of $(D i) range from $(D (n - 1)) to +$(D (n - State.length)). + +If the function is passed in string form, the state has name $(D "a") +and the zero-based index in the recurrence has name $(D "n"). The +given string must return the desired value for $(D a[n]) given $(D a[n +- 1]), $(D a[n - 2]), $(D a[n - 3]),..., $(D a[n - stateSize]). The +state size is dictated by the number of arguments passed to the call +to $(D recurrence). The $(D Recurrence) struct itself takes care of +managing the recurrence's state and shifting it appropriately. + */ +struct Recurrence(alias fun, StateType, size_t stateSize) +{ + import std.functional : binaryFun; + + StateType[stateSize] _state; + size_t _n; + + this(StateType[stateSize] initial) { _state = initial; } + + void popFront() + { + static auto trustedCycle(ref typeof(_state) s) @trusted + { + return cycle(s); + } + // The cast here is reasonable because fun may cause integer + // promotion, but needs to return a StateType to make its operation + // closed. Therefore, we have no other choice. + _state[_n % stateSize] = cast(StateType) binaryFun!(fun, "a", "n")( + trustedCycle(_state), _n + stateSize); + ++_n; + } + + @property StateType front() + { + return _state[_n % stateSize]; + } + + @property typeof(this) save() + { + return this; + } + + enum bool empty = false; +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + + // The Fibonacci numbers, using function in string form: + // a[0] = 1, a[1] = 1, and compute a[n+1] = a[n-1] + a[n] + auto fib = recurrence!("a[n-1] + a[n-2]")(1, 1); + assert(fib.take(10).equal([1, 1, 2, 3, 5, 8, 13, 21, 34, 55])); + + // The factorials, using function in lambda form: + auto fac = recurrence!((a,n) => a[n-1] * n)(1); + assert(take(fac, 10).equal([ + 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880 + ])); + + // The triangular numbers, using function in explicit form: + static size_t genTriangular(R)(R state, size_t n) + { + return state[n-1] + n; + } + auto tri = recurrence!genTriangular(0); + assert(take(tri, 10).equal([0, 1, 3, 6, 10, 15, 21, 28, 36, 45])); +} + +/// Ditto +Recurrence!(fun, CommonType!(State), State.length) +recurrence(alias fun, State...)(State initial) +{ + CommonType!(State)[State.length] state; + foreach (i, Unused; State) + { + state[i] = initial[i]; + } + return typeof(return)(state); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto fib = recurrence!("a[n-1] + a[n-2]")(1, 1); + static assert(isForwardRange!(typeof(fib))); + + int[] witness = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55 ]; + assert(equal(take(fib, 10), witness)); + foreach (e; take(fib, 10)) {} + auto fact = recurrence!("n * a[n-1]")(1); + assert( equal(take(fact, 10), [1, 1, 2, 2*3, 2*3*4, 2*3*4*5, 2*3*4*5*6, + 2*3*4*5*6*7, 2*3*4*5*6*7*8, 2*3*4*5*6*7*8*9][]) ); + auto piapprox = recurrence!("a[n] + (n & 1 ? 4.0 : -4.0) / (2 * n + 3)")(4.0); + foreach (e; take(piapprox, 20)) {} + // Thanks to yebblies for this test and the associated fix + auto r = recurrence!"a[n-2]"(1, 2); + witness = [1, 2, 1, 2, 1]; + assert(equal(take(r, 5), witness)); +} + +/** + $(D Sequence) is similar to $(D Recurrence) except that iteration is + presented in the so-called $(HTTP en.wikipedia.org/wiki/Closed_form, + closed form). This means that the $(D n)th element in the series is + computable directly from the initial values and $(D n) itself. This + implies that the interface offered by $(D Sequence) is a random-access + range, as opposed to the regular $(D Recurrence), which only offers + forward iteration. + + The state of the sequence is stored as a $(D Tuple) so it can be + heterogeneous. +*/ +struct Sequence(alias fun, State) +{ +private: + import std.functional : binaryFun; + + alias compute = binaryFun!(fun, "a", "n"); + alias ElementType = typeof(compute(State.init, cast(size_t) 1)); + State _state; + size_t _n; + + static struct DollarToken{} + +public: + this(State initial, size_t n = 0) + { + _state = initial; + _n = n; + } + + @property ElementType front() + { + return compute(_state, _n); + } + + void popFront() + { + ++_n; + } + + enum opDollar = DollarToken(); + + auto opSlice(size_t lower, size_t upper) + in + { + assert( + upper >= lower, + "Attempting to slice a Sequence with a larger first argument than the second." + ); + } + body + { + return typeof(this)(_state, _n + lower).take(upper - lower); + } + + auto opSlice(size_t lower, DollarToken) + { + return typeof(this)(_state, _n + lower); + } + + ElementType opIndex(size_t n) + { + return compute(_state, n + _n); + } + + enum bool empty = false; + + @property Sequence save() { return this; } +} + +/// Ditto +auto sequence(alias fun, State...)(State args) +{ + import std.typecons : Tuple, tuple; + alias Return = Sequence!(fun, Tuple!State); + return Return(tuple(args)); +} + +/// Odd numbers, using function in string form: +@safe unittest +{ + auto odds = sequence!("a[0] + n * a[1]")(1, 2); + assert(odds.front == 1); + odds.popFront(); + assert(odds.front == 3); + odds.popFront(); + assert(odds.front == 5); +} + +/// Triangular numbers, using function in lambda form: +@safe unittest +{ + auto tri = sequence!((a,n) => n*(n+1)/2)(); + + // Note random access + assert(tri[0] == 0); + assert(tri[3] == 6); + assert(tri[1] == 1); + assert(tri[4] == 10); + assert(tri[2] == 3); +} + +/// Fibonacci numbers, using function in explicit form: +@safe unittest +{ + import std.math : pow, round, sqrt; + static ulong computeFib(S)(S state, size_t n) + { + // Binet's formula + return cast(ulong)(round((pow(state[0], n+1) - pow(state[1], n+1)) / + state[2])); + } + auto fib = sequence!computeFib( + (1.0 + sqrt(5.0)) / 2.0, // Golden Ratio + (1.0 - sqrt(5.0)) / 2.0, // Conjugate of Golden Ratio + sqrt(5.0)); + + // Note random access with [] operator + assert(fib[1] == 1); + assert(fib[4] == 5); + assert(fib[3] == 3); + assert(fib[2] == 2); + assert(fib[9] == 55); +} + +@safe unittest +{ + import std.typecons : Tuple, tuple; + auto y = Sequence!("a[0] + n * a[1]", Tuple!(int, int))(tuple(0, 4)); + static assert(isForwardRange!(typeof(y))); + + //@@BUG + //auto y = sequence!("a[0] + n * a[1]")(0, 4); + //foreach (e; take(y, 15)) + {} //writeln(e); + + auto odds = Sequence!("a[0] + n * a[1]", Tuple!(int, int))( + tuple(1, 2)); + for (int currentOdd = 1; currentOdd <= 21; currentOdd += 2) + { + assert(odds.front == odds[0]); + assert(odds[0] == currentOdd); + odds.popFront(); + } +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto odds = sequence!("a[0] + n * a[1]")(1, 2); + static assert(hasSlicing!(typeof(odds))); + + //Note: don't use drop or take as the target of an equal, + //since they'll both just forward to opSlice, making the tests irrelevant + + // static slicing tests + assert(equal(odds[0 .. 5], [1, 3, 5, 7, 9])); + assert(equal(odds[3 .. 7], [7, 9, 11, 13])); + + // relative slicing test, testing slicing is NOT agnostic of state + auto odds_less5 = odds.drop(5); //this should actually call odds[5 .. $] + assert(equal(odds_less5[0 .. 3], [11, 13, 15])); + assert(equal(odds_less5[0 .. 10], odds[5 .. 15])); + + //Infinite slicing tests + odds = odds[10 .. $]; + assert(equal(odds.take(3), [21, 23, 25])); +} + +// Issue 5036 +@safe unittest +{ + auto s = sequence!((a, n) => new int)(0); + assert(s.front != s.front); // no caching +} + +// iota +/** + Creates a range of values that span the given starting and stopping + values. + + Params: + begin = The starting value. + end = The value that serves as the stopping criterion. This value is not + included in the range. + step = The value to add to the current value at each iteration. + + Returns: + A range that goes through the numbers $(D begin), $(D begin + step), + $(D begin + 2 * step), $(D ...), up to and excluding $(D end). + + The two-argument overloads have $(D step = 1). If $(D begin < end && step < + 0) or $(D begin > end && step > 0) or $(D begin == end), then an empty range + is returned. If $(D step == 0) then $(D begin == end) is an error. + + For built-in types, the range returned is a random access range. For + user-defined types that support $(D ++), the range is an input + range. + + An integral iota also supports $(D in) operator from the right. It takes + the stepping into account, the integral won't be considered + contained if it falls between two consecutive values of the range. + $(D contains) does the same as in, but from lefthand side. + + Example: + --- + void main() + { + import std.stdio; + + // The following groups all produce the same output of: + // 0 1 2 3 4 + + foreach (i; 0 .. 5) + writef("%s ", i); + writeln(); + + import std.range : iota; + foreach (i; iota(0, 5)) + writef("%s ", i); + writeln(); + + writefln("%(%s %|%)", iota(0, 5)); + + import std.algorithm.iteration : map; + import std.algorithm.mutation : copy; + import std.format; + iota(0, 5).map!(i => format("%s ", i)).copy(stdout.lockingTextWriter()); + writeln(); + } + --- +*/ +auto iota(B, E, S)(B begin, E end, S step) +if ((isIntegral!(CommonType!(B, E)) || isPointer!(CommonType!(B, E))) + && isIntegral!S) +{ + import std.conv : unsigned; + + alias Value = CommonType!(Unqual!B, Unqual!E); + alias StepType = Unqual!S; + + assert(step != 0 || begin == end); + + static struct Result + { + private Value current, last; + private StepType step; // by convention, 0 if range is empty + + this(Value current, Value pastLast, StepType step) + { + if (current < pastLast && step > 0) + { + // Iterating upward + assert(unsigned((pastLast - current) / step) <= size_t.max); + // Cast below can't fail because current < pastLast + this.last = cast(Value) (pastLast - 1); + this.last -= unsigned(this.last - current) % step; + } + else if (current > pastLast && step < 0) + { + // Iterating downward + assert(unsigned((current - pastLast) / (0 - step)) <= size_t.max); + // Cast below can't fail because current > pastLast + this.last = cast(Value) (pastLast + 1); + this.last += unsigned(current - this.last) % (0 - step); + } + else + { + // Initialize an empty range + this.step = 0; + return; + } + this.step = step; + this.current = current; + } + + @property bool empty() const { return step == 0; } + @property inout(Value) front() inout { assert(!empty); return current; } + void popFront() + { + assert(!empty); + if (current == last) step = 0; + else current += step; + } + + @property inout(Value) back() inout + { + assert(!empty); + return last; + } + void popBack() + { + assert(!empty); + if (current == last) step = 0; + else last -= step; + } + + @property auto save() { return this; } + + inout(Value) opIndex(ulong n) inout + { + assert(n < this.length); + + // Just cast to Value here because doing so gives overflow behavior + // consistent with calling popFront() n times. + return cast(inout Value) (current + step * n); + } + auto opBinaryRight(string op)(Value val) const + if (op == "in") + { + if (empty) return false; + //cast to avoid becoming unsigned + auto supposedIndex = cast(StepType)(val - current) / step; + return supposedIndex < length && supposedIndex * step + current == val; + } + auto contains(Value x){return x in this;} + inout(Result) opSlice() inout { return this; } + inout(Result) opSlice(ulong lower, ulong upper) inout + { + assert(upper >= lower && upper <= this.length); + + return cast(inout Result) Result( + cast(Value)(current + lower * step), + cast(Value)(current + upper * step), + step); + } + @property size_t length() const + { + if (step > 0) + return 1 + cast(size_t) (unsigned(last - current) / step); + if (step < 0) + return 1 + cast(size_t) (unsigned(current - last) / (0 - step)); + return 0; + } + + alias opDollar = length; + } + + return Result(begin, end, step); +} + +/// Ditto +auto iota(B, E)(B begin, E end) +if (isFloatingPoint!(CommonType!(B, E))) +{ + return iota(begin, end, CommonType!(B, E)(1)); +} + +/// Ditto +auto iota(B, E)(B begin, E end) +if (isIntegral!(CommonType!(B, E)) || isPointer!(CommonType!(B, E))) +{ + import std.conv : unsigned; + + alias Value = CommonType!(Unqual!B, Unqual!E); + + static struct Result + { + private Value current, pastLast; + + this(Value current, Value pastLast) + { + if (current < pastLast) + { + assert(unsigned(pastLast - current) <= size_t.max); + + this.current = current; + this.pastLast = pastLast; + } + else + { + // Initialize an empty range + this.current = this.pastLast = current; + } + } + + @property bool empty() const { return current == pastLast; } + @property inout(Value) front() inout { assert(!empty); return current; } + void popFront() { assert(!empty); ++current; } + + @property inout(Value) back() inout { assert(!empty); return cast(inout(Value))(pastLast - 1); } + void popBack() { assert(!empty); --pastLast; } + + @property auto save() { return this; } + + inout(Value) opIndex(size_t n) inout + { + assert(n < this.length); + + // Just cast to Value here because doing so gives overflow behavior + // consistent with calling popFront() n times. + return cast(inout Value) (current + n); + } + auto opBinaryRight(string op)(Value val) const + if (op == "in") + { + return current <= val && val < pastLast; + } + auto contains(Value x){return x in this;} + inout(Result) opSlice() inout { return this; } + inout(Result) opSlice(ulong lower, ulong upper) inout + { + assert(upper >= lower && upper <= this.length); + + return cast(inout Result) Result(cast(Value)(current + lower), + cast(Value)(pastLast - (length - upper))); + } + @property size_t length() const + { + return cast(size_t)(pastLast - current); + } + + alias opDollar = length; + } + + return Result(begin, end); +} + +/// Ditto +auto iota(E)(E end) +if (is(typeof(iota(E(0), end)))) +{ + E begin = E(0); + return iota(begin, end); +} + +/// Ditto +// Specialization for floating-point types +auto iota(B, E, S)(B begin, E end, S step) +if (isFloatingPoint!(CommonType!(B, E, S))) +in +{ + assert(step != 0, "iota: step must not be 0"); + assert((end - begin) / step >= 0, "iota: incorrect startup parameters"); +} +body +{ + alias Value = Unqual!(CommonType!(B, E, S)); + static struct Result + { + private Value start, step; + private size_t index, count; + + this(Value start, Value end, Value step) + { + import std.conv : to; + + this.start = start; + this.step = step; + immutable fcount = (end - start) / step; + count = to!size_t(fcount); + auto pastEnd = start + count * step; + if (step > 0) + { + if (pastEnd < end) ++count; + assert(start + count * step >= end); + } + else + { + if (pastEnd > end) ++count; + assert(start + count * step <= end); + } + } + + @property bool empty() const { return index == count; } + @property Value front() const { assert(!empty); return start + step * index; } + void popFront() + { + assert(!empty); + ++index; + } + @property Value back() const + { + assert(!empty); + return start + step * (count - 1); + } + void popBack() + { + assert(!empty); + --count; + } + + @property auto save() { return this; } + + Value opIndex(size_t n) const + { + assert(n < count); + return start + step * (n + index); + } + inout(Result) opSlice() inout + { + return this; + } + inout(Result) opSlice(size_t lower, size_t upper) inout + { + assert(upper >= lower && upper <= count); + + Result ret = this; + ret.index += lower; + ret.count = upper - lower + ret.index; + return cast(inout Result) ret; + } + @property size_t length() const + { + return count - index; + } + + alias opDollar = length; + } + + return Result(begin, end, step); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.math : approxEqual; + + auto r = iota(0, 10, 1); + assert(equal(r, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])); + assert(equal(r, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])); + assert(3 in r); + assert(r.contains(3)); //Same as above + assert(!(10 in r)); + assert(!(-8 in r)); + r = iota(0, 11, 3); + assert(equal(r, [0, 3, 6, 9])); + assert(r[2] == 6); + assert(!(2 in r)); + auto rf = iota(0.0, 0.5, 0.1); + assert(approxEqual(rf, [0.0, 0.1, 0.2, 0.3, 0.4])); +} + +nothrow @nogc @safe unittest +{ + //float overloads use std.conv.to so can't be @nogc or nothrow + alias ssize_t = Signed!size_t; + assert(iota(ssize_t.max, 0, -1).length == ssize_t.max); + assert(iota(ssize_t.max, ssize_t.min, -1).length == size_t.max); + assert(iota(ssize_t.max, ssize_t.min, -2).length == 1 + size_t.max / 2); + assert(iota(ssize_t.min, ssize_t.max, 2).length == 1 + size_t.max / 2); + assert(iota(ssize_t.max, ssize_t.min, -3).length == size_t.max / 3); +} + +debug @system unittest +{//check the contracts + import core.exception : AssertError; + import std.exception : assertThrown; + assertThrown!AssertError(iota(1,2,0)); + assertThrown!AssertError(iota(0f,1f,0f)); + assertThrown!AssertError(iota(1f,0f,0.1f)); + assertThrown!AssertError(iota(0f,1f,-0.1f)); +} + +@system unittest +{ + int[] a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + auto r1 = iota(a.ptr, a.ptr + a.length, 1); + assert(r1.front == a.ptr); + assert(r1.back == a.ptr + a.length - 1); + assert(&a[4] in r1); +} + +@safe unittest +{ + assert(iota(1UL, 0UL).length == 0); + assert(iota(1UL, 0UL, 1).length == 0); + assert(iota(0, 1, 1).length == 1); + assert(iota(1, 0, -1).length == 1); + assert(iota(0, 1, -1).length == 0); + assert(iota(ulong.max, 0).length == 0); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.searching : count; + import std.math : approxEqual, nextUp, nextDown; + import std.meta : AliasSeq; + + static assert(is(ElementType!(typeof(iota(0f))) == float)); + + static assert(hasLength!(typeof(iota(0, 2)))); + auto r = iota(0, 10, 1); + assert(r[$ - 1] == 9); + assert(equal(r, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9][])); + + auto rSlice = r[2 .. 8]; + assert(equal(rSlice, [2, 3, 4, 5, 6, 7])); + + rSlice.popFront(); + assert(rSlice[0] == rSlice.front); + assert(rSlice.front == 3); + + rSlice.popBack(); + assert(rSlice[rSlice.length - 1] == rSlice.back); + assert(rSlice.back == 6); + + rSlice = r[0 .. 4]; + assert(equal(rSlice, [0, 1, 2, 3])); + assert(3 in rSlice); + assert(!(4 in rSlice)); + + auto rr = iota(10); + assert(equal(rr, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9][])); + + r = iota(0, -10, -1); + assert(equal(r, [0, -1, -2, -3, -4, -5, -6, -7, -8, -9][])); + rSlice = r[3 .. 9]; + assert(equal(rSlice, [-3, -4, -5, -6, -7, -8])); + + r = iota(0, -6, -3); + assert(equal(r, [0, -3][])); + rSlice = r[1 .. 2]; + assert(equal(rSlice, [-3])); + + r = iota(0, -7, -3); + assert(equal(r, [0, -3, -6][])); + assert(0 in r); + assert(-6 in r); + rSlice = r[1 .. 3]; + assert(equal(rSlice, [-3, -6])); + assert(!(0 in rSlice)); + assert(!(-2 in rSlice)); + assert(!(-5 in rSlice)); + assert(!(3 in rSlice)); + assert(!(-9 in rSlice)); + + r = iota(0, 11, 3); + assert(equal(r, [0, 3, 6, 9][])); + assert(r[2] == 6); + rSlice = r[1 .. 3]; + assert(equal(rSlice, [3, 6])); + + auto rf = iota(0.0, 0.5, 0.1); + assert(approxEqual(rf, [0.0, 0.1, 0.2, 0.3, 0.4][])); + assert(rf.length == 5); + + rf.popFront(); + assert(rf.length == 4); + + auto rfSlice = rf[1 .. 4]; + assert(rfSlice.length == 3); + assert(approxEqual(rfSlice, [0.2, 0.3, 0.4])); + + rfSlice.popFront(); + assert(approxEqual(rfSlice[0], 0.3)); + + rf.popFront(); + assert(rf.length == 3); + + rfSlice = rf[1 .. 3]; + assert(rfSlice.length == 2); + assert(approxEqual(rfSlice, [0.3, 0.4])); + assert(approxEqual(rfSlice[0], 0.3)); + + // With something just above 0.5 + rf = iota(0.0, nextUp(0.5), 0.1); + assert(approxEqual(rf, [0.0, 0.1, 0.2, 0.3, 0.4, 0.5][])); + rf.popBack(); + assert(rf[rf.length - 1] == rf.back); + assert(approxEqual(rf.back, 0.4)); + assert(rf.length == 5); + + // going down + rf = iota(0.0, -0.5, -0.1); + assert(approxEqual(rf, [0.0, -0.1, -0.2, -0.3, -0.4][])); + rfSlice = rf[2 .. 5]; + assert(approxEqual(rfSlice, [-0.2, -0.3, -0.4])); + + rf = iota(0.0, nextDown(-0.5), -0.1); + assert(approxEqual(rf, [0.0, -0.1, -0.2, -0.3, -0.4, -0.5][])); + + // iota of longs + auto rl = iota(5_000_000L); + assert(rl.length == 5_000_000L); + assert(0 in rl); + assert(4_000_000L in rl); + assert(!(-4_000_000L in rl)); + assert(!(5_000_000L in rl)); + + // iota of longs with steps + auto iota_of_longs_with_steps = iota(50L, 101L, 10); + assert(iota_of_longs_with_steps.length == 6); + assert(equal(iota_of_longs_with_steps, [50L, 60L, 70L, 80L, 90L, 100L])); + + // iota of unsigned zero length (issue 6222, actually trying to consume it + // is the only way to find something is wrong because the public + // properties are all correct) + auto iota_zero_unsigned = iota(0, 0u, 3); + assert(count(iota_zero_unsigned) == 0); + + // unsigned reverse iota can be buggy if .length doesn't take them into + // account (issue 7982). + assert(iota(10u, 0u, -1).length == 10); + assert(iota(10u, 0u, -2).length == 5); + assert(iota(uint.max, uint.max-10, -1).length == 10); + assert(iota(uint.max, uint.max-10, -2).length == 5); + assert(iota(uint.max, 0u, -1).length == uint.max); + + assert(20 in iota(20u, 10u, -2)); + assert(16 in iota(20u, 10u, -2)); + assert(!(15 in iota(20u, 10u, -2))); + assert(!(10 in iota(20u, 10u, -2))); + assert(!(uint.max in iota(20u, 10u, -1))); + assert(!(int.min in iota(20u, 10u, -1))); + assert(!(int.max in iota(20u, 10u, -1))); + + + // Issue 8920 + foreach (Type; AliasSeq!(byte, ubyte, short, ushort, + int, uint, long, ulong)) + { + Type val; + foreach (i; iota(cast(Type) 0, cast(Type) 10)) { val++; } + assert(val == 10); + } +} + +@safe unittest +{ + import std.algorithm.mutation : copy; + auto idx = new size_t[100]; + copy(iota(0, idx.length), idx); +} + +@safe unittest +{ + import std.meta : AliasSeq; + foreach (range; AliasSeq!(iota(2, 27, 4), + iota(3, 9), + iota(2.7, 12.3, .1), + iota(3.2, 9.7))) + { + const cRange = range; + const e = cRange.empty; + const f = cRange.front; + const b = cRange.back; + const i = cRange[2]; + const s1 = cRange[]; + const s2 = cRange[0 .. 3]; + const l = cRange.length; + } +} + +@system unittest +{ + //The ptr stuff can't be done at compile time, so we unfortunately end + //up with some code duplication here. + auto arr = [0, 5, 3, 5, 5, 7, 9, 2, 0, 42, 7, 6]; + + { + const cRange = iota(arr.ptr, arr.ptr + arr.length, 3); + const e = cRange.empty; + const f = cRange.front; + const b = cRange.back; + const i = cRange[2]; + const s1 = cRange[]; + const s2 = cRange[0 .. 3]; + const l = cRange.length; + } + + { + const cRange = iota(arr.ptr, arr.ptr + arr.length); + const e = cRange.empty; + const f = cRange.front; + const b = cRange.back; + const i = cRange[2]; + const s1 = cRange[]; + const s2 = cRange[0 .. 3]; + const l = cRange.length; + } +} + +@nogc nothrow pure @safe +unittest +{ + { + ushort start = 0, end = 10, step = 2; + foreach (i; iota(start, end, step)) + static assert(is(typeof(i) == ushort)); + } + { + ubyte start = 0, end = 255, step = 128; + uint x; + foreach (i; iota(start, end, step)) + { + static assert(is(typeof(i) == ubyte)); + ++x; + } + assert(x == 2); + } +} + +/* Generic overload that handles arbitrary types that support arithmetic + * operations. + * + * User-defined types such as $(REF BigInt, std,bigint) are also supported, as long + * as they can be incremented with $(D ++) and compared with $(D <) or $(D ==). + */ +/// ditto +auto iota(B, E)(B begin, E end) +if (!isIntegral!(CommonType!(B, E)) && + !isFloatingPoint!(CommonType!(B, E)) && + !isPointer!(CommonType!(B, E)) && + is(typeof((ref B b) { ++b; })) && + (is(typeof(B.init < E.init)) || is(typeof(B.init == E.init))) ) +{ + static struct Result + { + B current; + E end; + + @property bool empty() + { + static if (is(typeof(B.init < E.init))) + return !(current < end); + else static if (is(typeof(B.init != E.init))) + return current == end; + else + static assert(0); + } + @property auto front() { return current; } + void popFront() + { + assert(!empty); + ++current; + } + } + return Result(begin, end); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + // Test iota() for a type that only supports ++ and != but does not have + // '<'-ordering. + struct Cyclic(int wrapAround) + { + int current; + + this(int start) { current = start % wrapAround; } + + bool opEquals(Cyclic c) const { return current == c.current; } + bool opEquals(int i) const { return current == i; } + void opUnary(string op)() if (op == "++") + { + current = (current + 1) % wrapAround; + } + } + alias Cycle5 = Cyclic!5; + + // Easy case + auto i1 = iota(Cycle5(1), Cycle5(4)); + assert(i1.equal([1, 2, 3])); + + // Wraparound case + auto i2 = iota(Cycle5(3), Cycle5(2)); + assert(i2.equal([3, 4, 0, 1 ])); +} + +/** + Options for the $(LREF FrontTransversal) and $(LREF Transversal) ranges + (below). +*/ +enum TransverseOptions +{ +/** + When transversed, the elements of a range of ranges are assumed to + have different lengths (e.g. a jagged array). +*/ + assumeJagged, //default + /** + The transversal enforces that the elements of a range of ranges have + all the same length (e.g. an array of arrays, all having the same + length). Checking is done once upon construction of the transversal + range. + */ + enforceNotJagged, + /** + The transversal assumes, without verifying, that the elements of a + range of ranges have all the same length. This option is useful if + checking was already done from the outside of the range. + */ + assumeNotJagged, + } + +/** + Given a range of ranges, iterate transversally through the first + elements of each of the enclosed ranges. +*/ +struct FrontTransversal(Ror, + TransverseOptions opt = TransverseOptions.assumeJagged) +{ + alias RangeOfRanges = Unqual!(Ror); + alias RangeType = .ElementType!RangeOfRanges; + alias ElementType = .ElementType!RangeType; + + private void prime() + { + static if (opt == TransverseOptions.assumeJagged) + { + while (!_input.empty && _input.front.empty) + { + _input.popFront(); + } + static if (isBidirectionalRange!RangeOfRanges) + { + while (!_input.empty && _input.back.empty) + { + _input.popBack(); + } + } + } + } + +/** + Construction from an input. +*/ + this(RangeOfRanges input) + { + _input = input; + prime(); + static if (opt == TransverseOptions.enforceNotJagged) + // (isRandomAccessRange!RangeOfRanges + // && hasLength!RangeType) + { + import std.exception : enforce; + + if (empty) return; + immutable commonLength = _input.front.length; + foreach (e; _input) + { + enforce(e.length == commonLength); + } + } + } + +/** + Forward range primitives. +*/ + static if (isInfinite!RangeOfRanges) + { + enum bool empty = false; + } + else + { + @property bool empty() + { + static if (opt != TransverseOptions.assumeJagged) + { + if (!_input.empty) + return _input.front.empty; + } + + return _input.empty; + } + } + + /// Ditto + @property auto ref front() + { + assert(!empty, "Attempting to fetch the front of an empty FrontTransversal"); + return _input.front.front; + } + + /// Ditto + static if (hasMobileElements!RangeType) + { + ElementType moveFront() + { + return _input.front.moveFront(); + } + } + + static if (hasAssignableElements!RangeType) + { + @property void front(ElementType val) + { + _input.front.front = val; + } + } + + /// Ditto + void popFront() + { + assert(!empty, "Attempting to popFront an empty FrontTransversal"); + _input.popFront(); + prime(); + } + +/** + Duplicates this $(D frontTransversal). Note that only the encapsulating + range of range will be duplicated. Underlying ranges will not be + duplicated. +*/ + static if (isForwardRange!RangeOfRanges) + { + @property FrontTransversal save() + { + return FrontTransversal(_input.save); + } + } + + static if (isBidirectionalRange!RangeOfRanges) + { +/** + Bidirectional primitives. They are offered if $(D + isBidirectionalRange!RangeOfRanges). +*/ + @property auto ref back() + { + assert(!empty, "Attempting to fetch the back of an empty FrontTransversal"); + return _input.back.front; + } + /// Ditto + void popBack() + { + assert(!empty, "Attempting to popBack an empty FrontTransversal"); + _input.popBack(); + prime(); + } + + /// Ditto + static if (hasMobileElements!RangeType) + { + ElementType moveBack() + { + return _input.back.moveFront(); + } + } + + static if (hasAssignableElements!RangeType) + { + @property void back(ElementType val) + { + _input.back.front = val; + } + } + } + + static if (isRandomAccessRange!RangeOfRanges && + (opt == TransverseOptions.assumeNotJagged || + opt == TransverseOptions.enforceNotJagged)) + { +/** + Random-access primitive. It is offered if $(D + isRandomAccessRange!RangeOfRanges && (opt == + TransverseOptions.assumeNotJagged || opt == + TransverseOptions.enforceNotJagged)). +*/ + auto ref opIndex(size_t n) + { + return _input[n].front; + } + + /// Ditto + static if (hasMobileElements!RangeType) + { + ElementType moveAt(size_t n) + { + return _input[n].moveFront(); + } + } + /// Ditto + static if (hasAssignableElements!RangeType) + { + void opIndexAssign(ElementType val, size_t n) + { + _input[n].front = val; + } + } + /// Ditto + static if (hasLength!RangeOfRanges) + { + @property size_t length() + { + return _input.length; + } + + alias opDollar = length; + } + +/** + Slicing if offered if $(D RangeOfRanges) supports slicing and all the + conditions for supporting indexing are met. +*/ + static if (hasSlicing!RangeOfRanges) + { + typeof(this) opSlice(size_t lower, size_t upper) + { + return typeof(this)(_input[lower .. upper]); + } + } + } + + auto opSlice() { return this; } + +private: + RangeOfRanges _input; +} + +/// Ditto +FrontTransversal!(RangeOfRanges, opt) frontTransversal( + TransverseOptions opt = TransverseOptions.assumeJagged, + RangeOfRanges) +(RangeOfRanges rr) +{ + return typeof(return)(rr); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + int[][] x = new int[][2]; + x[0] = [1, 2]; + x[1] = [3, 4]; + auto ror = frontTransversal(x); + assert(equal(ror, [ 1, 3 ][])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : AllDummyRanges, DummyRange, ReturnBy; + + static assert(is(FrontTransversal!(immutable int[][]))); + + foreach (DummyType; AllDummyRanges) + { + auto dummies = + [DummyType.init, DummyType.init, DummyType.init, DummyType.init]; + + foreach (i, ref elem; dummies) + { + // Just violate the DummyRange abstraction to get what I want. + elem.arr = elem.arr[i..$ - (3 - i)]; + } + + auto ft = frontTransversal!(TransverseOptions.assumeNotJagged)(dummies); + static if (isForwardRange!DummyType) + { + static assert(isForwardRange!(typeof(ft))); + } + + assert(equal(ft, [1, 2, 3, 4])); + + // Test slicing. + assert(equal(ft[0 .. 2], [1, 2])); + assert(equal(ft[1 .. 3], [2, 3])); + + assert(ft.front == ft.moveFront()); + assert(ft.back == ft.moveBack()); + assert(ft.moveAt(1) == ft[1]); + + + // Test infiniteness propagation. + static assert(isInfinite!(typeof(frontTransversal(repeat("foo"))))); + + static if (DummyType.r == ReturnBy.Reference) + { + { + ft.front++; + scope(exit) ft.front--; + assert(dummies.front.front == 2); + } + + { + ft.front = 5; + scope(exit) ft.front = 1; + assert(dummies[0].front == 5); + } + + { + ft.back = 88; + scope(exit) ft.back = 4; + assert(dummies.back.front == 88); + } + + { + ft[1] = 99; + scope(exit) ft[1] = 2; + assert(dummies[1].front == 99); + } + } + } +} + +// Issue 16363 +@safe unittest +{ + import std.algorithm.comparison : equal; + + int[][] darr = [[0, 1], [4, 5]]; + auto ft = frontTransversal!(TransverseOptions.assumeNotJagged)(darr); + + assert(equal(ft, [0, 4])); + static assert(isRandomAccessRange!(typeof(ft))); +} + +// Bugzilla 16442 +@safe unittest +{ + int[][] arr = [[], []]; + + auto ft1 = frontTransversal!(TransverseOptions.assumeNotJagged)(arr); + assert(ft1.empty); + + auto ft2 = frontTransversal!(TransverseOptions.enforceNotJagged)(arr); + assert(ft2.empty); +} + +/** + Given a range of ranges, iterate transversally through the + `n`th element of each of the enclosed ranges. + + Params: + opt = Controls the assumptions the function makes about the lengths + of the ranges + rr = An input range of random access ranges + Returns: + At minimum, an input range. Range primitives such as bidirectionality + and random access are given if the element type of `rr` provides them. +*/ +struct Transversal(Ror, + TransverseOptions opt = TransverseOptions.assumeJagged) +{ + private alias RangeOfRanges = Unqual!Ror; + private alias InnerRange = ElementType!RangeOfRanges; + private alias E = ElementType!InnerRange; + + private void prime() + { + static if (opt == TransverseOptions.assumeJagged) + { + while (!_input.empty && _input.front.length <= _n) + { + _input.popFront(); + } + static if (isBidirectionalRange!RangeOfRanges) + { + while (!_input.empty && _input.back.length <= _n) + { + _input.popBack(); + } + } + } + } + +/** + Construction from an input and an index. +*/ + this(RangeOfRanges input, size_t n) + { + _input = input; + _n = n; + prime(); + static if (opt == TransverseOptions.enforceNotJagged) + { + import std.exception : enforce; + + if (empty) return; + immutable commonLength = _input.front.length; + foreach (e; _input) + { + enforce(e.length == commonLength); + } + } + } + +/** + Forward range primitives. +*/ + static if (isInfinite!(RangeOfRanges)) + { + enum bool empty = false; + } + else + { + @property bool empty() + { + return _input.empty; + } + } + + /// Ditto + @property auto ref front() + { + assert(!empty, "Attempting to fetch the front of an empty Transversal"); + return _input.front[_n]; + } + + /// Ditto + static if (hasMobileElements!InnerRange) + { + E moveFront() + { + return _input.front.moveAt(_n); + } + } + + /// Ditto + static if (hasAssignableElements!InnerRange) + { + @property void front(E val) + { + _input.front[_n] = val; + } + } + + + /// Ditto + void popFront() + { + assert(!empty, "Attempting to popFront an empty Transversal"); + _input.popFront(); + prime(); + } + + /// Ditto + static if (isForwardRange!RangeOfRanges) + { + @property typeof(this) save() + { + auto ret = this; + ret._input = _input.save; + return ret; + } + } + + static if (isBidirectionalRange!RangeOfRanges) + { +/** + Bidirectional primitives. They are offered if $(D + isBidirectionalRange!RangeOfRanges). +*/ + @property auto ref back() + { + assert(!empty, "Attempting to fetch the back of an empty Transversal"); + return _input.back[_n]; + } + + /// Ditto + void popBack() + { + assert(!empty, "Attempting to popBack an empty Transversal"); + _input.popBack(); + prime(); + } + + /// Ditto + static if (hasMobileElements!InnerRange) + { + E moveBack() + { + return _input.back.moveAt(_n); + } + } + + /// Ditto + static if (hasAssignableElements!InnerRange) + { + @property void back(E val) + { + _input.back[_n] = val; + } + } + + } + + static if (isRandomAccessRange!RangeOfRanges && + (opt == TransverseOptions.assumeNotJagged || + opt == TransverseOptions.enforceNotJagged)) + { +/** + Random-access primitive. It is offered if $(D + isRandomAccessRange!RangeOfRanges && (opt == + TransverseOptions.assumeNotJagged || opt == + TransverseOptions.enforceNotJagged)). +*/ + auto ref opIndex(size_t n) + { + return _input[n][_n]; + } + + /// Ditto + static if (hasMobileElements!InnerRange) + { + E moveAt(size_t n) + { + return _input[n].moveAt(_n); + } + } + + /// Ditto + static if (hasAssignableElements!InnerRange) + { + void opIndexAssign(E val, size_t n) + { + _input[n][_n] = val; + } + } + + /// Ditto + static if (hasLength!RangeOfRanges) + { + @property size_t length() + { + return _input.length; + } + + alias opDollar = length; + } + +/** + Slicing if offered if $(D RangeOfRanges) supports slicing and all the + conditions for supporting indexing are met. +*/ + static if (hasSlicing!RangeOfRanges) + { + typeof(this) opSlice(size_t lower, size_t upper) + { + return typeof(this)(_input[lower .. upper], _n); + } + } + } + + auto opSlice() { return this; } + +private: + RangeOfRanges _input; + size_t _n; +} + +/// Ditto +Transversal!(RangeOfRanges, opt) transversal +(TransverseOptions opt = TransverseOptions.assumeJagged, RangeOfRanges) +(RangeOfRanges rr, size_t n) +{ + return typeof(return)(rr, n); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + int[][] x = new int[][2]; + x[0] = [1, 2]; + x[1] = [3, 4]; + auto ror = transversal(x, 1); + assert(equal(ror, [ 2, 4 ][])); +} + +@safe unittest +{ + import std.internal.test.dummyrange : DummyRange, Length, RangeType, ReturnBy; + + int[][] x = new int[][2]; + x[0] = [ 1, 2 ]; + x[1] = [3, 4]; + auto ror = transversal!(TransverseOptions.assumeNotJagged)(x, 1); + auto witness = [ 2, 4 ]; + uint i; + foreach (e; ror) assert(e == witness[i++]); + assert(i == 2); + assert(ror.length == 2); + + static assert(is(Transversal!(immutable int[][]))); + + // Make sure ref, assign is being propagated. + { + ror.front++; + scope(exit) ror.front--; + assert(x[0][1] == 3); + } + { + ror.front = 5; + scope(exit) ror.front = 2; + assert(x[0][1] == 5); + assert(ror.moveFront() == 5); + } + { + ror.back = 999; + scope(exit) ror.back = 4; + assert(x[1][1] == 999); + assert(ror.moveBack() == 999); + } + { + ror[0] = 999; + scope(exit) ror[0] = 2; + assert(x[0][1] == 999); + assert(ror.moveAt(0) == 999); + } + + // Test w/o ref return. + alias D = DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random); + auto drs = [D.init, D.init]; + foreach (num; 0 .. 10) + { + auto t = transversal!(TransverseOptions.enforceNotJagged)(drs, num); + assert(t[0] == t[1]); + assert(t[1] == num + 1); + } + + static assert(isInfinite!(typeof(transversal(repeat([1,2,3]), 1)))); + + // Test slicing. + auto mat = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]; + auto mat1 = transversal!(TransverseOptions.assumeNotJagged)(mat, 1)[1 .. 3]; + assert(mat1[0] == 6); + assert(mat1[1] == 10); +} + +struct Transposed(RangeOfRanges) +if (isForwardRange!RangeOfRanges && + isInputRange!(ElementType!RangeOfRanges) && + hasAssignableElements!RangeOfRanges) +{ + //alias ElementType = typeof(map!"a.front"(RangeOfRanges.init)); + + this(RangeOfRanges input) + { + this._input = input; + } + + @property auto front() + { + import std.algorithm.iteration : filter, map; + return _input.save + .filter!(a => !a.empty) + .map!(a => a.front); + } + + void popFront() + { + // Advance the position of each subrange. + auto r = _input.save; + while (!r.empty) + { + auto e = r.front; + if (!e.empty) + { + e.popFront(); + r.front = e; + } + + r.popFront(); + } + } + + // ElementType opIndex(size_t n) + // { + // return _input[n].front; + // } + + @property bool empty() + { + if (_input.empty) return true; + foreach (e; _input.save) + { + if (!e.empty) return false; + } + return true; + } + + @property Transposed save() + { + return Transposed(_input.save); + } + + auto opSlice() { return this; } + +private: + RangeOfRanges _input; +} + +@safe unittest +{ + // Boundary case: transpose of empty range should be empty + int[][] ror = []; + assert(transposed(ror).empty); +} + +// Issue 9507 +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto r = [[1,2], [3], [4,5], [], [6]]; + assert(r.transposed.equal!equal([ + [1, 3, 4, 6], + [2, 5] + ])); +} + +/** +Given a range of ranges, returns a range of ranges where the $(I i)'th subrange +contains the $(I i)'th elements of the original subranges. + */ +Transposed!RangeOfRanges transposed(RangeOfRanges)(RangeOfRanges rr) +if (isForwardRange!RangeOfRanges && + isInputRange!(ElementType!RangeOfRanges) && + hasAssignableElements!RangeOfRanges) +{ + return Transposed!RangeOfRanges(rr); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + int[][] ror = [ + [1, 2, 3], + [4, 5, 6] + ]; + auto xp = transposed(ror); + assert(equal!"a.equal(b)"(xp, [ + [1, 4], + [2, 5], + [3, 6] + ])); +} + +/// +@safe unittest +{ + int[][] x = new int[][2]; + x[0] = [1, 2]; + x[1] = [3, 4]; + auto tr = transposed(x); + int[][] witness = [ [ 1, 3 ], [ 2, 4 ] ]; + uint i; + + foreach (e; tr) + { + assert(array(e) == witness[i++]); + } +} + +// Issue 8764 +@safe unittest +{ + import std.algorithm.comparison : equal; + ulong[1] t0 = [ 123 ]; + + assert(!hasAssignableElements!(typeof(t0[].chunks(1)))); + assert(!is(typeof(transposed(t0[].chunks(1))))); + assert(is(typeof(transposed(t0[].chunks(1).array())))); + + auto t1 = transposed(t0[].chunks(1).array()); + assert(equal!"a.equal(b)"(t1, [[123]])); +} + +/** +This struct takes two ranges, $(D source) and $(D indices), and creates a view +of $(D source) as if its elements were reordered according to $(D indices). +$(D indices) may include only a subset of the elements of $(D source) and +may also repeat elements. + +$(D Source) must be a random access range. The returned range will be +bidirectional or random-access if $(D Indices) is bidirectional or +random-access, respectively. +*/ +struct Indexed(Source, Indices) +if (isRandomAccessRange!Source && isInputRange!Indices && + is(typeof(Source.init[ElementType!(Indices).init]))) +{ + this(Source source, Indices indices) + { + this._source = source; + this._indices = indices; + } + + /// Range primitives + @property auto ref front() + { + assert(!empty, "Attempting to fetch the front of an empty Indexed"); + return _source[_indices.front]; + } + + /// Ditto + void popFront() + { + assert(!empty, "Attempting to popFront an empty Indexed"); + _indices.popFront(); + } + + static if (isInfinite!Indices) + { + enum bool empty = false; + } + else + { + /// Ditto + @property bool empty() + { + return _indices.empty; + } + } + + static if (isForwardRange!Indices) + { + /// Ditto + @property typeof(this) save() + { + // Don't need to save _source because it's never consumed. + return typeof(this)(_source, _indices.save); + } + } + + /// Ditto + static if (hasAssignableElements!Source) + { + @property auto ref front(ElementType!Source newVal) + { + assert(!empty); + return _source[_indices.front] = newVal; + } + } + + + static if (hasMobileElements!Source) + { + /// Ditto + auto moveFront() + { + assert(!empty); + return _source.moveAt(_indices.front); + } + } + + static if (isBidirectionalRange!Indices) + { + /// Ditto + @property auto ref back() + { + assert(!empty, "Attempting to fetch the back of an empty Indexed"); + return _source[_indices.back]; + } + + /// Ditto + void popBack() + { + assert(!empty, "Attempting to popBack an empty Indexed"); + _indices.popBack(); + } + + /// Ditto + static if (hasAssignableElements!Source) + { + @property auto ref back(ElementType!Source newVal) + { + assert(!empty); + return _source[_indices.back] = newVal; + } + } + + + static if (hasMobileElements!Source) + { + /// Ditto + auto moveBack() + { + assert(!empty); + return _source.moveAt(_indices.back); + } + } + } + + static if (hasLength!Indices) + { + /// Ditto + @property size_t length() + { + return _indices.length; + } + + alias opDollar = length; + } + + static if (isRandomAccessRange!Indices) + { + /// Ditto + auto ref opIndex(size_t index) + { + return _source[_indices[index]]; + } + + static if (hasSlicing!Indices) + { + /// Ditto + typeof(this) opSlice(size_t a, size_t b) + { + return typeof(this)(_source, _indices[a .. b]); + } + } + + + static if (hasAssignableElements!Source) + { + /// Ditto + auto opIndexAssign(ElementType!Source newVal, size_t index) + { + return _source[_indices[index]] = newVal; + } + } + + + static if (hasMobileElements!Source) + { + /// Ditto + auto moveAt(size_t index) + { + return _source.moveAt(_indices[index]); + } + } + } + + // All this stuff is useful if someone wants to index an Indexed + // without adding a layer of indirection. + + /** + Returns the source range. + */ + @property Source source() + { + return _source; + } + + /** + Returns the indices range. + */ + @property Indices indices() + { + return _indices; + } + + static if (isRandomAccessRange!Indices) + { + /** + Returns the physical index into the source range corresponding to a + given logical index. This is useful, for example, when indexing + an $(D Indexed) without adding another layer of indirection. + */ + size_t physicalIndex(size_t logicalIndex) + { + return _indices[logicalIndex]; + } + + /// + @safe unittest + { + auto ind = indexed([1, 2, 3, 4, 5], [1, 3, 4]); + assert(ind.physicalIndex(0) == 1); + } + } + +private: + Source _source; + Indices _indices; + +} + +/// Ditto +Indexed!(Source, Indices) indexed(Source, Indices)(Source source, Indices indices) +{ + return typeof(return)(source, indices); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + auto source = [1, 2, 3, 4, 5]; + auto indices = [4, 3, 1, 2, 0, 4]; + auto ind = indexed(source, indices); + assert(equal(ind, [5, 4, 2, 3, 1, 5])); + assert(equal(retro(ind), [5, 1, 3, 2, 4, 5])); +} + +@safe unittest +{ + { + auto ind = indexed([1, 2, 3, 4, 5], [1, 3, 4]); + assert(ind.physicalIndex(0) == 1); + } + + auto source = [1, 2, 3, 4, 5]; + auto indices = [4, 3, 1, 2, 0, 4]; + auto ind = indexed(source, indices); + + // When elements of indices are duplicated and Source has lvalue elements, + // these are aliased in ind. + ind[0]++; + assert(ind[0] == 6); + assert(ind[5] == 6); +} + +@safe unittest +{ + import std.internal.test.dummyrange : AllDummyRanges, propagatesLength, + propagatesRangeType, RangeType; + + foreach (DummyType; AllDummyRanges) + { + auto d = DummyType.init; + auto r = indexed([1, 2, 3, 4, 5], d); + static assert(propagatesRangeType!(DummyType, typeof(r))); + static assert(propagatesLength!(DummyType, typeof(r))); + } +} + +/** +This range iterates over fixed-sized chunks of size $(D chunkSize) of a +$(D source) range. $(D Source) must be an input range. $(D chunkSize) must be +greater than zero. + +If $(D !isInfinite!Source) and $(D source.walkLength) is not evenly +divisible by $(D chunkSize), the back element of this range will contain +fewer than $(D chunkSize) elements. + +If `Source` is a forward range, the resulting range will be forward ranges as +well. Otherwise, the resulting chunks will be input ranges consuming the same +input: iterating over `front` will shrink the chunk such that subsequent +invocations of `front` will no longer return the full chunk, and calling +`popFront` on the outer range will invalidate any lingering references to +previous values of `front`. + +Params: + source = Range from which the chunks will be selected + chunkSize = Chunk size + +See_Also: $(LREF slide) + +Returns: Range of chunks. +*/ +struct Chunks(Source) +if (isInputRange!Source) +{ + static if (isForwardRange!Source) + { + /// Standard constructor + this(Source source, size_t chunkSize) + { + assert(chunkSize != 0, "Cannot create a Chunk with an empty chunkSize"); + _source = source; + _chunkSize = chunkSize; + } + + /// Input range primitives. Always present. + @property auto front() + { + assert(!empty, "Attempting to fetch the front of an empty Chunks"); + return _source.save.take(_chunkSize); + } + + /// Ditto + void popFront() + { + assert(!empty, "Attempting to popFront and empty Chunks"); + _source.popFrontN(_chunkSize); + } + + static if (!isInfinite!Source) + /// Ditto + @property bool empty() + { + return _source.empty; + } + else + // undocumented + enum empty = false; + + /// Forward range primitives. Only present if `Source` is a forward range. + @property typeof(this) save() + { + return typeof(this)(_source.save, _chunkSize); + } + + static if (hasLength!Source) + { + /// Length. Only if $(D hasLength!Source) is $(D true) + @property size_t length() + { + // Note: _source.length + _chunkSize may actually overflow. + // We cast to ulong to mitigate the problem on x86 machines. + // For x64 machines, we just suppose we'll never overflow. + // The "safe" code would require either an extra branch, or a + // modulo operation, which is too expensive for such a rare case + return cast(size_t)((cast(ulong)(_source.length) + _chunkSize - 1) / _chunkSize); + } + //Note: No point in defining opDollar here without slicing. + //opDollar is defined below in the hasSlicing!Source section + } + + static if (hasSlicing!Source) + { + //Used for various purposes + private enum hasSliceToEnd = is(typeof(Source.init[_chunkSize .. $]) == Source); + + /** + Indexing and slicing operations. Provided only if + $(D hasSlicing!Source) is $(D true). + */ + auto opIndex(size_t index) + { + immutable start = index * _chunkSize; + immutable end = start + _chunkSize; + + static if (isInfinite!Source) + return _source[start .. end]; + else + { + import std.algorithm.comparison : min; + immutable len = _source.length; + assert(start < len, "chunks index out of bounds"); + return _source[start .. min(end, len)]; + } + } + + /// Ditto + static if (hasLength!Source) + typeof(this) opSlice(size_t lower, size_t upper) + { + import std.algorithm.comparison : min; + assert(lower <= upper && upper <= length, "chunks slicing index out of bounds"); + immutable len = _source.length; + return chunks(_source[min(lower * _chunkSize, len) .. min(upper * _chunkSize, len)], _chunkSize); + } + else static if (hasSliceToEnd) + //For slicing an infinite chunk, we need to slice the source to the end. + typeof(takeExactly(this, 0)) opSlice(size_t lower, size_t upper) + { + assert(lower <= upper, "chunks slicing index out of bounds"); + return chunks(_source[lower * _chunkSize .. $], _chunkSize).takeExactly(upper - lower); + } + + static if (isInfinite!Source) + { + static if (hasSliceToEnd) + { + private static struct DollarToken{} + DollarToken opDollar() + { + return DollarToken(); + } + //Slice to dollar + typeof(this) opSlice(size_t lower, DollarToken) + { + return typeof(this)(_source[lower * _chunkSize .. $], _chunkSize); + } + } + } + else + { + //Dollar token carries a static type, with no extra information. + //It can lazily transform into _source.length on algorithmic + //operations such as : chunks[$/2, $-1]; + private static struct DollarToken + { + Chunks!Source* mom; + @property size_t momLength() + { + return mom.length; + } + alias momLength this; + } + DollarToken opDollar() + { + return DollarToken(&this); + } + + //Slice overloads optimized for using dollar. Without this, to slice to end, we would... + //1. Evaluate chunks.length + //2. Multiply by _chunksSize + //3. To finally just compare it (with min) to the original length of source (!) + //These overloads avoid that. + typeof(this) opSlice(DollarToken, DollarToken) + { + static if (hasSliceToEnd) + return chunks(_source[$ .. $], _chunkSize); + else + { + immutable len = _source.length; + return chunks(_source[len .. len], _chunkSize); + } + } + typeof(this) opSlice(size_t lower, DollarToken) + { + import std.algorithm.comparison : min; + assert(lower <= length, "chunks slicing index out of bounds"); + static if (hasSliceToEnd) + return chunks(_source[min(lower * _chunkSize, _source.length) .. $], _chunkSize); + else + { + immutable len = _source.length; + return chunks(_source[min(lower * _chunkSize, len) .. len], _chunkSize); + } + } + typeof(this) opSlice(DollarToken, size_t upper) + { + assert(upper == length, "chunks slicing index out of bounds"); + return this[$ .. $]; + } + } + } + + //Bidirectional range primitives + static if (hasSlicing!Source && hasLength!Source) + { + /** + Bidirectional range primitives. Provided only if both + $(D hasSlicing!Source) and $(D hasLength!Source) are $(D true). + */ + @property auto back() + { + assert(!empty, "back called on empty chunks"); + immutable len = _source.length; + immutable start = (len - 1) / _chunkSize * _chunkSize; + return _source[start .. len]; + } + + /// Ditto + void popBack() + { + assert(!empty, "popBack() called on empty chunks"); + immutable end = (_source.length - 1) / _chunkSize * _chunkSize; + _source = _source[0 .. end]; + } + } + + private: + Source _source; + size_t _chunkSize; + } + else // is input range only + { + import std.typecons : RefCounted; + + static struct Chunk + { + private RefCounted!Impl impl; + + @property bool empty() { return impl.curSizeLeft == 0 || impl.r.empty; } + @property auto front() { return impl.r.front; } + void popFront() + { + assert(impl.curSizeLeft > 0 && !impl.r.empty); + impl.curSizeLeft--; + impl.r.popFront(); + } + } + + static struct Impl + { + private Source r; + private size_t chunkSize; + private size_t curSizeLeft; + } + + private RefCounted!Impl impl; + + private this(Source r, size_t chunkSize) + { + impl = RefCounted!Impl(r, r.empty ? 0 : chunkSize, chunkSize); + } + + @property bool empty() { return impl.chunkSize == 0; } + @property Chunk front() return { return Chunk(impl); } + + void popFront() + { + impl.curSizeLeft -= impl.r.popFrontN(impl.curSizeLeft); + if (!impl.r.empty) + impl.curSizeLeft = impl.chunkSize; + else + impl.chunkSize = 0; + } + + static assert(isInputRange!(typeof(this))); + } +} + +/// Ditto +Chunks!Source chunks(Source)(Source source, size_t chunkSize) +if (isInputRange!Source) +{ + return typeof(return)(source, chunkSize); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + auto source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + auto chunks = chunks(source, 4); + assert(chunks[0] == [1, 2, 3, 4]); + assert(chunks[1] == [5, 6, 7, 8]); + assert(chunks[2] == [9, 10]); + assert(chunks.back == chunks[2]); + assert(chunks.front == chunks[0]); + assert(chunks.length == 3); + assert(equal(retro(array(chunks)), array(retro(chunks)))); +} + +/// Non-forward input ranges are supported, but with limited semantics. +@system /*@safe*/ unittest // FIXME: can't be @safe because RefCounted isn't. +{ + import std.algorithm.comparison : equal; + + int i; + + // The generator doesn't save state, so it cannot be a forward range. + auto inputRange = generate!(() => ++i).take(10); + + // We can still process it in chunks, but it will be single-pass only. + auto chunked = inputRange.chunks(2); + + assert(chunked.front.equal([1, 2])); + assert(chunked.front.empty); // Iterating the chunk has consumed it + chunked.popFront; + assert(chunked.front.equal([3, 4])); +} + +@system /*@safe*/ unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : ReferenceInputRange; + + auto data = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + auto r = new ReferenceInputRange!int(data).chunks(3); + assert(r.equal!equal([ + [ 1, 2, 3 ], + [ 4, 5, 6 ], + [ 7, 8, 9 ], + [ 10 ] + ])); + + auto data2 = [ 1, 2, 3, 4, 5, 6 ]; + auto r2 = new ReferenceInputRange!int(data2).chunks(3); + assert(r2.equal!equal([ + [ 1, 2, 3 ], + [ 4, 5, 6 ] + ])); + + auto data3 = [ 1, 2, 3, 4, 5 ]; + auto r3 = new ReferenceInputRange!int(data3).chunks(2); + assert(r3.front.equal([1, 2])); + r3.popFront(); + assert(!r3.empty); + r3.popFront(); + assert(r3.front.equal([5])); +} + +@safe unittest +{ + auto source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + auto chunks = chunks(source, 4); + auto chunks2 = chunks.save; + chunks.popFront(); + assert(chunks[0] == [5, 6, 7, 8]); + assert(chunks[1] == [9, 10]); + chunks2.popBack(); + assert(chunks2[1] == [5, 6, 7, 8]); + assert(chunks2.length == 2); + + static assert(isRandomAccessRange!(typeof(chunks))); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + //Extra toying with slicing and indexing. + auto chunks1 = [0, 0, 1, 1, 2, 2, 3, 3, 4].chunks(2); + auto chunks2 = [0, 0, 1, 1, 2, 2, 3, 3, 4, 4].chunks(2); + + assert(chunks1.length == 5); + assert(chunks2.length == 5); + assert(chunks1[4] == [4]); + assert(chunks2[4] == [4, 4]); + assert(chunks1.back == [4]); + assert(chunks2.back == [4, 4]); + + assert(chunks1[0 .. 1].equal([[0, 0]])); + assert(chunks1[0 .. 2].equal([[0, 0], [1, 1]])); + assert(chunks1[4 .. 5].equal([[4]])); + assert(chunks2[4 .. 5].equal([[4, 4]])); + + assert(chunks1[0 .. 0].equal((int[][]).init)); + assert(chunks1[5 .. 5].equal((int[][]).init)); + assert(chunks2[5 .. 5].equal((int[][]).init)); + + //Fun with opDollar + assert(chunks1[$ .. $].equal((int[][]).init)); //Quick + assert(chunks2[$ .. $].equal((int[][]).init)); //Quick + assert(chunks1[$ - 1 .. $].equal([[4]])); //Semiquick + assert(chunks2[$ - 1 .. $].equal([[4, 4]])); //Semiquick + assert(chunks1[$ .. 5].equal((int[][]).init)); //Semiquick + assert(chunks2[$ .. 5].equal((int[][]).init)); //Semiquick + + assert(chunks1[$ / 2 .. $ - 1].equal([[2, 2], [3, 3]])); //Slow +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filter; + + //ForwardRange + auto r = filter!"true"([1, 2, 3, 4, 5]).chunks(2); + assert(equal!"equal(a, b)"(r, [[1, 2], [3, 4], [5]])); + + //InfiniteRange w/o RA + auto fibsByPairs = recurrence!"a[n-1] + a[n-2]"(1, 1).chunks(2); + assert(equal!`equal(a, b)`(fibsByPairs.take(2), [[ 1, 1], [ 2, 3]])); + + //InfiniteRange w/ RA and slicing + auto odds = sequence!("a[0] + n * a[1]")(1, 2); + auto oddsByPairs = odds.chunks(2); + assert(equal!`equal(a, b)`(oddsByPairs.take(2), [[ 1, 3], [ 5, 7]])); + + //Requires phobos#991 for Sequence to have slice to end + static assert(hasSlicing!(typeof(odds))); + assert(equal!`equal(a, b)`(oddsByPairs[3 .. 5], [[13, 15], [17, 19]])); + assert(equal!`equal(a, b)`(oddsByPairs[3 .. $].take(2), [[13, 15], [17, 19]])); +} + + + +/** +This range splits a $(D source) range into $(D chunkCount) chunks of +approximately equal length. $(D Source) must be a forward range with +known length. + +Unlike $(LREF chunks), $(D evenChunks) takes a chunk count (not size). +The returned range will contain zero or more $(D source.length / +chunkCount + 1) elements followed by $(D source.length / chunkCount) +elements. If $(D source.length < chunkCount), some chunks will be empty. + +$(D chunkCount) must not be zero, unless $(D source) is also empty. +*/ +struct EvenChunks(Source) +if (isForwardRange!Source && hasLength!Source) +{ + /// Standard constructor + this(Source source, size_t chunkCount) + { + assert(chunkCount != 0 || source.empty, "Cannot create EvenChunks with a zero chunkCount"); + _source = source; + _chunkCount = chunkCount; + } + + /// Forward range primitives. Always present. + @property auto front() + { + assert(!empty, "Attempting to fetch the front of an empty evenChunks"); + return _source.save.take(_chunkPos(1)); + } + + /// Ditto + void popFront() + { + assert(!empty, "Attempting to popFront an empty evenChunks"); + _source.popFrontN(_chunkPos(1)); + _chunkCount--; + } + + /// Ditto + @property bool empty() + { + return _source.empty; + } + + /// Ditto + @property typeof(this) save() + { + return typeof(this)(_source.save, _chunkCount); + } + + /// Length + @property size_t length() const + { + return _chunkCount; + } + //Note: No point in defining opDollar here without slicing. + //opDollar is defined below in the hasSlicing!Source section + + static if (hasSlicing!Source) + { + /** + Indexing, slicing and bidirectional operations and range primitives. + Provided only if $(D hasSlicing!Source) is $(D true). + */ + auto opIndex(size_t index) + { + assert(index < _chunkCount, "evenChunks index out of bounds"); + return _source[_chunkPos(index) .. _chunkPos(index+1)]; + } + + /// Ditto + typeof(this) opSlice(size_t lower, size_t upper) + { + assert(lower <= upper && upper <= length, "evenChunks slicing index out of bounds"); + return evenChunks(_source[_chunkPos(lower) .. _chunkPos(upper)], upper - lower); + } + + /// Ditto + @property auto back() + { + assert(!empty, "back called on empty evenChunks"); + return _source[_chunkPos(_chunkCount - 1) .. _source.length]; + } + + /// Ditto + void popBack() + { + assert(!empty, "popBack() called on empty evenChunks"); + _source = _source[0 .. _chunkPos(_chunkCount - 1)]; + _chunkCount--; + } + } + +private: + Source _source; + size_t _chunkCount; + + size_t _chunkPos(size_t i) + { + /* + _chunkCount = 5, _source.length = 13: + + chunk0 + | chunk3 + | | + v v + +-+-+-+-+-+ ^ + |0|3|.| | | | + +-+-+-+-+-+ | div + |1|4|.| | | | + +-+-+-+-+-+ v + |2|5|.| + +-+-+-+ + + <-----> + mod + + <---------> + _chunkCount + + One column is one chunk. + popFront and popBack pop the left-most + and right-most column, respectively. + */ + + auto div = _source.length / _chunkCount; + auto mod = _source.length % _chunkCount; + auto pos = i <= mod + ? i * (div+1) + : mod * (div+1) + (i-mod) * div + ; + //auto len = i < mod + // ? div+1 + // : div + //; + return pos; + } +} + +/// Ditto +EvenChunks!Source evenChunks(Source)(Source source, size_t chunkCount) +if (isForwardRange!Source && hasLength!Source) +{ + return typeof(return)(source, chunkCount); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + auto source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + auto chunks = evenChunks(source, 3); + assert(chunks[0] == [1, 2, 3, 4]); + assert(chunks[1] == [5, 6, 7]); + assert(chunks[2] == [8, 9, 10]); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + auto chunks = evenChunks(source, 3); + assert(chunks.back == chunks[2]); + assert(chunks.front == chunks[0]); + assert(chunks.length == 3); + assert(equal(retro(array(chunks)), array(retro(chunks)))); + + auto chunks2 = chunks.save; + chunks.popFront(); + assert(chunks[0] == [5, 6, 7]); + assert(chunks[1] == [8, 9, 10]); + chunks2.popBack(); + assert(chunks2[1] == [5, 6, 7]); + assert(chunks2.length == 2); + + static assert(isRandomAccessRange!(typeof(chunks))); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + int[] source = []; + auto chunks = source.evenChunks(0); + assert(chunks.length == 0); + chunks = source.evenChunks(3); + assert(equal(chunks, [[], [], []])); + chunks = [1, 2, 3].evenChunks(5); + assert(equal(chunks, [[1], [2], [3], [], []])); +} + +/* +A fixed-sized sliding window iteration +of size `windowSize` over a `source` range by a custom `stepSize`. + +The `Source` range must be at least an `ForwardRange` and +the `windowSize` must be greater than zero. + +For `windowSize = 1` it splits the range into single element groups (aka `unflatten`) +For `windowSize = 2` it is similar to `zip(source, source.save.dropOne)`. + +Params: + f = If `Yes.withFewerElements` slide with fewer + elements than `windowSize`. This can only happen if the initial range + contains less elements than `windowSize`. In this case + if `No.withFewerElements` an empty range will be returned. + source = Range from which the slide will be selected + windowSize = Sliding window size + stepSize = Steps between the windows (by default 1) + +Returns: Range of all sliding windows with propagated bi-directionality, + forwarding, conditional random access, and slicing. + +See_Also: $(LREF chunks) +*/ +// Explicitly set to private to delay the release until 2.076 +private +auto slide(Flag!"withFewerElements" f = Yes.withFewerElements, + Source)(Source source, size_t windowSize, size_t stepSize = 1) + if (isForwardRange!Source) +{ + return Slides!(f, Source)(source, windowSize, stepSize); +} + +private struct Slides(Flag!"withFewerElements" withFewerElements = Yes.withFewerElements, Source) + if (isForwardRange!Source) +{ +private: + Source _source; + size_t _windowSize; + size_t _stepSize; + + static if (hasLength!Source) + { + enum needsEndTracker = false; + } + else + { + // if there's no information about the length, track needs to be kept manually + Source _nextSource; + enum needsEndTracker = true; + } + + bool _empty; + + static if (hasSlicing!Source) + { + enum hasSliceToEnd = hasSlicing!Source && is(typeof(Source.init[0 .. $]) == Source); + } + +public: + /// Standard constructor + this(Source source, size_t windowSize, size_t stepSize) + { + assert(windowSize > 0, "windowSize must be greater than zero"); + assert(stepSize > 0, "stepSize must be greater than zero"); + _source = source; + _windowSize = windowSize; + _stepSize = stepSize; + + static if (needsEndTracker) + { + // _nextSource is used to "look into the future" and check for the end + _nextSource = source.save; + _nextSource.popFrontN(windowSize); + } + + static if (!withFewerElements) + { + // empty source range is needed, s.t. length, slicing etc. works properly + static if (needsEndTracker) + { + if (_nextSource.empty) + _source = _nextSource; + } + else + { + if (_source.length < windowSize) + { + static if (hasSlicing!Source) + { + // if possible use the faster opDollar overload + static if (hasSliceToEnd) + _source = _source[$ .. $]; + else + _source = _source[_source.length .. _source.length]; + } + else + { + _source.popFrontN(_source.length); + } + } + } + } + + _empty = _source.empty; + } + + /// Forward range primitives. Always present. + @property auto front() + { + assert(!empty, "Attempting to access front on an empty slide"); + static if (hasSlicing!Source && hasLength!Source) + { + import std.algorithm.comparison : min; + return _source[0 .. min(_windowSize, _source.length)]; + } + else + { + return _source.save.take(_windowSize); + } + } + + /// Ditto + void popFront() + { + assert(!empty, "Attempting to call popFront() on an empty slide"); + _source.popFrontN(_stepSize); + + // if the range has less elements than its window size, + // we have seen the last full window (i.e. its empty) + static if (needsEndTracker) + { + if (_nextSource.empty) + _empty = true; + else + _nextSource.popFrontN(_stepSize); + } + else + { + if (_source.length < _windowSize) + _empty = true; + } + } + + static if (!isInfinite!Source) + { + /// Ditto + @property bool empty() const + { + return _empty; + } + } + else + { + // undocumented + enum empty = false; + } + + /// Ditto + @property typeof(this) save() + { + return typeof(this)(_source.save, _windowSize, _stepSize); + } + + static if (hasLength!Source) + { + /// Length. Only if $(D hasLength!Source) is $(D true) + @property size_t length() + { + if (_source.length < _windowSize) + { + static if (withFewerElements) + return 1; + else + return 0; + } + else + { + return (_source.length - _windowSize + _stepSize) / _stepSize; + } + } + } + + static if (hasSlicing!Source) + { + /** + Indexing and slicing operations. Provided only if + `hasSlicing!Source` is `true`. + */ + auto opIndex(size_t index) + { + immutable start = index * _stepSize; + + static if (isInfinite!Source) + { + immutable end = start + _windowSize; + } + else + { + import std.algorithm.comparison : min; + + immutable len = _source.length; + assert(start < len, "slide index out of bounds"); + immutable end = min(start + _windowSize, len); + } + + return _source[start .. end]; + } + + static if (!isInfinite!Source) + { + /// ditto + typeof(this) opSlice(size_t lower, size_t upper) + { + import std.algorithm.comparison : min; + assert(lower <= upper && upper <= length, "slide slicing index out of bounds"); + + lower *= _stepSize; + upper *= _stepSize; + + immutable len = _source.length; + + /* + * Notice that we only need to move for windowSize - 1 to the right: + * source = [0, 1, 2, 3] (length: 4) + * - source.slide(2) -> s = [[0, 1], [1, 2], [2, 3]] + * right pos for s[0 .. 3]: 3 (upper) + 2 (windowSize) - 1 = 4 + * + * - source.slide(3) -> s = [[0, 1, 2], [1, 2, 3]] + * right pos for s[0 .. 2]: 2 (upper) + 3 (windowSize) - 1 = 4 + * + * source = [0, 1, 2, 3, 4] (length: 5) + * - source.slide(4) -> s = [[0, 1, 2, 3], [1, 2, 3, 4]] + * right pos for s[0 .. 2]: 2 (upper) + 4 (windowSize) - 1 = 5 + */ + return typeof(this) + (_source[min(lower, len) .. min(upper + _windowSize - 1, len)], + _windowSize, _stepSize); + } + } + else static if (hasSliceToEnd) + { + //For slicing an infinite chunk, we need to slice the source to the infinite end. + auto opSlice(size_t lower, size_t upper) + { + assert(lower <= upper, "slide slicing index out of bounds"); + return typeof(this)(_source[lower * _stepSize .. $], + _windowSize, _stepSize).takeExactly(upper - lower); + } + } + + static if (isInfinite!Source) + { + static if (hasSliceToEnd) + { + private static struct DollarToken{} + DollarToken opDollar() + { + return DollarToken(); + } + //Slice to dollar + typeof(this) opSlice(size_t lower, DollarToken) + { + return typeof(this)(_source[lower * _stepSize .. $], _windowSize, _stepSize); + } + } + } + else + { + //Dollar token carries a static type, with no extra information. + //It can lazily transform into _source.length on algorithmic + //operations such as : slide[$/2, $-1]; + private static struct DollarToken + { + private size_t _length; + alias _length this; + } + + DollarToken opDollar() + { + return DollarToken(this.length); + } + + // Optimized slice overloads optimized for using dollar. + typeof(this) opSlice(DollarToken, DollarToken) + { + static if (hasSliceToEnd) + { + return typeof(this)(_source[$ .. $], _windowSize, _stepSize); + } + else + { + immutable len = _source.length; + return typeof(this)(_source[len .. len], _windowSize, _stepSize); + } + } + + // Optimized slice overloads optimized for using dollar. + typeof(this) opSlice(size_t lower, DollarToken) + { + import std.algorithm.comparison : min; + assert(lower <= length, "slide slicing index out of bounds"); + lower *= _stepSize; + static if (hasSliceToEnd) + { + return typeof(this)(_source[min(lower, _source.length) .. $], _windowSize, _stepSize); + } + else + { + immutable len = _source.length; + return typeof(this)(_source[min(lower, len) .. len], _windowSize, _stepSize); + } + } + + // Optimized slice overloads optimized for using dollar. + typeof(this) opSlice(DollarToken, size_t upper) + { + assert(upper == length, "slide slicing index out of bounds"); + return this[$ .. $]; + } + } + + // Bidirectional range primitives + static if (!isInfinite!Source) + { + /** + Bidirectional range primitives. Provided only if both + `hasSlicing!Source` and `!isInfinite!Source` are `true`. + */ + @property auto back() + { + import std.algorithm.comparison : max; + + assert(!empty, "Attempting to access front on an empty slide"); + + immutable len = _source.length; + /* + * Note: + * - `end` in the following is the exclusive end as used in opSlice + * - For the trivial case with `stepSize = 1` `end` is at `len`: + * + * iota(4).slide(2) = [[0, 1], [1, 2], [2, 3] (end = 4) + * iota(4).slide(3) = [[0, 1, 2], [1, 2, 3]] (end = 4) + * + * - For the non-trivial cases, we need to calculate the gap + * between `len` and `end` - this is the number of missing elements + * from the input range: + * + * iota(7).slide(2, 3) = [[0, 1], [3, 4]] || 6 + * iota(7).slide(2, 4) = [[0, 1], [4, 5]] || 6 + * iota(7).slide(1, 5) = [[0], [5]] || 6 + * + * As it can be seen `gap` can be at most `stepSize - 1` + * More generally the elements of the sliding window with + * `w = windowSize` and `s = stepSize` are: + * + * [0, w], [s, s + w], [2 * s, 2 * s + w], ... [n * s, n * s + w] + * + * We can thus calculate the gap between the `end` and `len` as: + * + * gap = len - (n * s + w) = len - w - (n * s) + * + * As we aren't interested in exact value of `n`, but the best + * minimal `gap` value, we can use modulo to "cut" `len - w` optimally: + * + * gap = len - w - (s - s ... - s) = (len - w) % s + * + * So for example: + * + * iota(7).slide(2, 3) = [[0, 1], [3, 4]] + * gap: (7 - 2) % 3 = 5 % 3 = 2 + * end: 7 - 2 = 5 + * + * iota(7).slide(4, 2) = [[0, 1, 2, 3], [2, 3, 4, 5]] + * gap: (7 - 4) % 2 = 3 % 2 = 1 + * end: 7 - 1 = 6 + */ + size_t gap = (len - _windowSize) % _stepSize; + + // check for underflow + immutable start = (len > _windowSize + gap) ? len - _windowSize - gap : 0; + + return _source[start .. len - gap]; + } + + /// Ditto + void popBack() + { + assert(!empty, "Attempting to call popBack() on an empty slide"); + + immutable end = _source.length > _stepSize ? _source.length - _stepSize : 0; + _source = _source[0 .. end]; + + if (_source.length < _windowSize) + _empty = true; + } + } + } +} + +// +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.array : array; + + assert([0, 1, 2, 3].slide(2).equal!equal( + [[0, 1], [1, 2], [2, 3]] + )); + assert(5.iota.slide(3).equal!equal( + [[0, 1, 2], [1, 2, 3], [2, 3, 4]] + )); + + assert(iota(7).slide(2, 2).equal!equal([[0, 1], [2, 3], [4, 5]])); + assert(iota(12).slide(2, 4).equal!equal([[0, 1], [4, 5], [8, 9]])); + + // set a custom stepsize (default 1) + assert(6.iota.slide(1, 2).equal!equal( + [[0], [2], [4]] + )); + + assert(6.iota.slide(2, 4).equal!equal( + [[0, 1], [4, 5]] + )); + + // allow slide with less elements than the window size + assert(3.iota.slide!(No.withFewerElements)(4).empty); + assert(3.iota.slide!(Yes.withFewerElements)(4).equal!equal( + [[0, 1, 2]] + )); +} + +// count k-mers +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : each; + + int[dstring] d; + "AGAGA"d.slide(2).each!(a => d[a]++); + assert(d == ["AG"d: 2, "GA"d: 2]); +} + +// @nogc +@safe pure nothrow @nogc unittest +{ + import std.algorithm.comparison : equal; + + static immutable res1 = [[0], [1], [2], [3]]; + assert(4.iota.slide(1).equal!equal(res1)); + + static immutable res2 = [[0, 1], [1, 2], [2, 3]]; + assert(4.iota.slide(2).equal!equal(res2)); +} + +// different window sizes +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.array : array; + + assert([0, 1, 2, 3].slide(1).array == [[0], [1], [2], [3]]); + assert([0, 1, 2, 3].slide(2).array == [[0, 1], [1, 2], [2, 3]]); + assert([0, 1, 2, 3].slide(3).array == [[0, 1, 2], [1, 2, 3]]); + assert([0, 1, 2, 3].slide(4).array == [[0, 1, 2, 3]]); + assert([0, 1, 2, 3].slide(5).array == [[0, 1, 2, 3]]); + + + assert(iota(2).slide(2).front.equal([0, 1])); + assert(iota(3).slide(2).equal!equal([[0, 1],[1, 2]])); + assert(iota(3).slide(3).equal!equal([[0, 1, 2]])); + assert(iota(3).slide(4).equal!equal([[0, 1, 2]])); + assert(iota(1, 4).slide(1).equal!equal([[1], [2], [3]])); + assert(iota(1, 4).slide(3).equal!equal([[1, 2, 3]])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + assert(6.iota.slide(1, 1).equal!equal( + [[0], [1], [2], [3], [4], [5]] + )); + assert(6.iota.slide(1, 2).equal!equal( + [[0], [2], [4]] + )); + assert(6.iota.slide(1, 3).equal!equal( + [[0], [3]] + )); + assert(6.iota.slide(1, 4).equal!equal( + [[0], [4]] + )); + assert(6.iota.slide(1, 5).equal!equal( + [[0], [5]] + )); + assert(6.iota.slide(2, 1).equal!equal( + [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5]] + )); + assert(6.iota.slide(2, 2).equal!equal( + [[0, 1], [2, 3], [4, 5]] + )); + assert(6.iota.slide(2, 3).equal!equal( + [[0, 1], [3, 4]] + )); + assert(6.iota.slide(2, 4).equal!equal( + [[0, 1], [4, 5]] + )); + assert(6.iota.slide(2, 5).equal!equal( + [[0, 1]] + )); + assert(6.iota.slide(3, 1).equal!equal( + [[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5]] + )); + assert(6.iota.slide(3, 2).equal!equal( + [[0, 1, 2], [2, 3, 4]] + )); + assert(6.iota.slide(3, 3).equal!equal( + [[0, 1, 2], [3, 4, 5]] + )); + assert(6.iota.slide(3, 4).equal!equal( + [[0, 1, 2]] + )); + assert(6.iota.slide(4, 1).equal!equal( + [[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]] + )); + assert(6.iota.slide(4, 2).equal!equal( + [[0, 1, 2, 3], [2, 3, 4, 5]] + )); + assert(6.iota.slide(4, 3).equal!equal( + [[0, 1, 2, 3]] + )); + assert(6.iota.slide(5, 1).equal!equal( + [[0, 1, 2, 3, 4], [1, 2, 3, 4, 5]] + )); + assert(6.iota.slide(5, 2).equal!equal( + [[0, 1, 2, 3, 4]] + )); + assert(6.iota.slide(5, 3).equal!equal( + [[0, 1, 2, 3, 4]] + )); +} + +// emptyness, copyability, strings +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : each, map; + + // check with empty input + int[] d; + assert(d.slide(2).empty); + assert(d.slide(2, 2).empty); + + // is copyable? + auto e = iota(5).slide(2); + e.popFront; + assert(e.save.equal!equal([[1, 2], [2, 3], [3, 4]])); + assert(e.save.equal!equal([[1, 2], [2, 3], [3, 4]])); + assert(e.map!"a.array".array == [[1, 2], [2, 3], [3, 4]]); + + // test with strings + int[dstring] f; + "AGAGA"d.slide(3).each!(a => f[a]++); + assert(f == ["AGA"d: 2, "GAG"d: 1]); + + int[dstring] g; + "ABCDEFG"d.slide(3, 3).each!(a => g[a]++); + assert(g == ["ABC"d:1, "DEF"d:1]); +} + +// test slicing, length +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.array : array; + + // test index + assert(iota(3).slide(4)[0].equal([0, 1, 2])); + assert(iota(5).slide(4)[1].equal([1, 2, 3, 4])); + assert(iota(3).slide(4, 2)[0].equal([0, 1, 2])); + assert(iota(5).slide(4, 2)[1].equal([2, 3, 4])); + assert(iota(3).slide(4, 3)[0].equal([0, 1, 2])); + assert(iota(5).slide(4, 3)[1].equal([3, 4,])); + + // test slicing + assert(iota(3).slide(4)[0 .. $].equal!equal([[0, 1, 2]])); + assert(iota(3).slide(2)[1 .. $].equal!equal([[1, 2]])); + assert(iota(1, 5).slide(2)[0 .. 1].equal!equal([[1, 2]])); + assert(iota(1, 5).slide(2)[0 .. 2].equal!equal([[1, 2], [2, 3]])); + assert(iota(1, 5).slide(3)[0 .. 1].equal!equal([[1, 2, 3]])); + assert(iota(1, 5).slide(3)[0 .. 2].equal!equal([[1, 2, 3], [2, 3, 4]])); + assert(iota(1, 6).slide(3)[2 .. 3].equal!equal([[3, 4, 5]])); + assert(iota(1, 5).slide(4)[0 .. 1].equal!equal([[1, 2, 3, 4]])); + + // length + assert(iota(3).slide(1).length == 3); + assert(iota(3).slide(1, 2).length == 2); + assert(iota(3).slide(1, 3).length == 1); + assert(iota(3).slide(1, 4).length == 1); + assert(iota(3).slide(2).length == 2); + assert(iota(3).slide(2, 2).length == 1); + assert(iota(3).slide(2, 3).length == 1); + assert(iota(3).slide(3).length == 1); + assert(iota(3).slide(3, 2).length == 1); + + // opDollar + assert(iota(3).slide(4)[$/2 .. $].equal!equal([[0, 1, 2]])); + assert(iota(3).slide(4)[$ .. $].empty); + assert(iota(3).slide(4)[$ .. 1].empty); + + assert(iota(5).slide(3, 1)[$/2 .. $].equal!equal([[1, 2, 3], [2, 3, 4]])); + assert(iota(5).slide(3, 2)[$/2 .. $].equal!equal([[2, 3, 4]])); + assert(iota(5).slide(3, 3)[$/2 .. $].equal!equal([[0, 1, 2]])); + assert(iota(3).slide(4, 3)[$ .. $].empty); + assert(iota(3).slide(4, 3)[$ .. 1].empty); +} + +// test No.withFewerElements +@safe pure nothrow unittest +{ + assert(iota(3).slide(4).length == 1); + assert(iota(3).slide(4, 4).length == 1); + + assert(iota(3).slide!(No.withFewerElements)(4).empty); + assert(iota(3, 3).slide!(No.withFewerElements)(4).empty); + assert(iota(3).slide!(No.withFewerElements)(4).length == 0); + assert(iota(3).slide!(No.withFewerElements)(4, 4).length == 0); + + assert(iota(3).slide!(No.withFewerElements)(400).empty); + assert(iota(3).slide!(No.withFewerElements)(400).length == 0); + assert(iota(3).slide!(No.withFewerElements)(400, 10).length == 0); + + assert(iota(3).slide!(No.withFewerElements)(4)[0 .. $].empty); + assert(iota(3).slide!(No.withFewerElements)(4)[$ .. $].empty); + assert(iota(3).slide!(No.withFewerElements)(4)[$ .. 0].empty); + assert(iota(3).slide!(No.withFewerElements)(4)[$/2 .. $].empty); + + // with different step sizes + assert(iota(3).slide!(No.withFewerElements)(4, 5)[0 .. $].empty); + assert(iota(3).slide!(No.withFewerElements)(4, 6)[$ .. $].empty); + assert(iota(3).slide!(No.withFewerElements)(4, 7)[$ .. 0].empty); + assert(iota(3).slide!(No.withFewerElements)(4, 8)[$/2 .. $].empty); +} + +// test with infinite ranges +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + + // InfiniteRange without RandomAccess + auto fibs = recurrence!"a[n-1] + a[n-2]"(1, 1); + assert(fibs.slide(2).take(2).equal!equal([[1, 1], [1, 2]])); + assert(fibs.slide(2, 3).take(2).equal!equal([[1, 1], [3, 5]])); + + // InfiniteRange with RandomAccess and slicing + auto odds = sequence!("a[0] + n * a[1]")(1, 2); + auto oddsByPairs = odds.slide(2); + assert(oddsByPairs.take(2).equal!equal([[ 1, 3], [ 3, 5]])); + assert(oddsByPairs[1].equal([3, 5])); + assert(oddsByPairs[4].equal([9, 11])); + + static assert(hasSlicing!(typeof(odds))); + assert(oddsByPairs[3 .. 5].equal!equal([[7, 9], [9, 11]])); + assert(oddsByPairs[3 .. $].take(2).equal!equal([[7, 9], [9, 11]])); + + auto oddsWithGaps = odds.slide(2, 4); + assert(oddsWithGaps.take(3).equal!equal([[1, 3], [9, 11], [17, 19]])); + assert(oddsWithGaps[2].equal([17, 19])); + assert(oddsWithGaps[1 .. 3].equal!equal([[9, 11], [17, 19]])); + assert(oddsWithGaps[1 .. $].take(2).equal!equal([[9, 11], [17, 19]])); +} + +// test reverse +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + + auto e = iota(3).slide(2); + assert(e.retro.equal!equal([[1, 2], [0, 1]])); + assert(e.retro.array.equal(e.array.retro)); + + auto e2 = iota(5).slide(3); + assert(e2.retro.equal!equal([[2, 3, 4], [1, 2, 3], [0, 1, 2]])); + assert(e2.retro.array.equal(e2.array.retro)); + + auto e3 = iota(3).slide(4); + assert(e3.retro.equal!equal([[0, 1, 2]])); + assert(e3.retro.array.equal(e3.array.retro)); +} + +// test reverse with different steps +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + + assert(iota(7).slide(2, 1).retro.equal!equal( + [[5, 6], [4, 5], [3, 4], [2, 3], [1, 2], [0, 1]] + )); + assert(iota(7).slide(2, 2).retro.equal!equal( + [[4, 5], [2, 3], [0, 1]] + )); + assert(iota(7).slide(2, 3).retro.equal!equal( + [[3, 4], [0, 1]] + )); + assert(iota(7).slide(2, 4).retro.equal!equal( + [[4, 5], [0, 1]] + )); + assert(iota(7).slide(2, 5).retro.equal!equal( + [[5, 6], [0, 1]] + )); + assert(iota(7).slide(3, 1).retro.equal!equal( + [[4, 5, 6], [3, 4, 5], [2, 3, 4], [1, 2, 3], [0, 1, 2]] + )); + assert(iota(7).slide(3, 2).retro.equal!equal( + [[4, 5, 6], [2, 3, 4], [0, 1, 2]] + )); + assert(iota(7).slide(4, 1).retro.equal!equal( + [[3, 4, 5, 6], [2, 3, 4, 5], [1, 2, 3, 4], [0, 1, 2, 3]] + )); + assert(iota(7).slide(4, 2).retro.equal!equal( + [[2, 3, 4, 5], [0, 1, 2, 3]] + )); + assert(iota(7).slide(4, 3).retro.equal!equal( + [[3, 4, 5, 6], [0, 1, 2, 3]] + )); + assert(iota(7).slide(4, 4).retro.equal!equal( + [[0, 1, 2, 3]] + )); + assert(iota(7).slide(5, 1).retro.equal!equal( + [[2, 3, 4, 5, 6], [1, 2, 3, 4, 5], [0, 1, 2, 3, 4]] + )); + assert(iota(7).slide(5, 2).retro.equal!equal( + [[2, 3, 4, 5, 6], [0, 1, 2, 3, 4]] + )); + assert(iota(7).slide(5, 3).retro.equal!equal( + [[0, 1, 2, 3, 4]] + )); + assert(iota(7).slide(5, 4).retro.equal!equal( + [[0, 1, 2, 3, 4]] + )); +} + +// step size +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + + assert(iota(7).slide(2, 2).equal!equal([[0, 1], [2, 3], [4, 5]])); + assert(iota(8).slide(2, 2).equal!equal([[0, 1], [2, 3], [4, 5], [6, 7]])); + assert(iota(9).slide(2, 2).equal!equal([[0, 1], [2, 3], [4, 5], [6, 7]])); + assert(iota(12).slide(2, 4).equal!equal([[0, 1], [4, 5], [8, 9]])); + assert(iota(13).slide(2, 4).equal!equal([[0, 1], [4, 5], [8, 9]])); +} + +// test with dummy ranges +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : DummyRange, Length, RangeType, ReturnBy, AllDummyRanges; + import std.meta : AliasSeq; + + alias AllForwardDummyRanges = AliasSeq!( + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Forward), + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Bidirectional), + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random), + DummyRange!(ReturnBy.Reference, Length.No, RangeType.Forward), + DummyRange!(ReturnBy.Reference, Length.No, RangeType.Bidirectional), + //DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Input), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Forward), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Bidirectional), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random), + //DummyRange!(ReturnBy.Value, Length.No, RangeType.Input), + DummyRange!(ReturnBy.Value, Length.No, RangeType.Forward), + DummyRange!(ReturnBy.Value, Length.No, RangeType.Bidirectional) + ); + + foreach (Range; AliasSeq!AllForwardDummyRanges) + { + Range r; + assert(r.slide(1).equal!equal( + [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10]] + )); + assert(r.slide(2).equal!equal( + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9], [9, 10]] + )); + assert(r.slide(3).equal!equal( + [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6], + [5, 6, 7], [6, 7, 8], [7, 8, 9], [8, 9, 10]] + )); + assert(r.slide(6).equal!equal( + [[1, 2, 3, 4, 5, 6], [2, 3, 4, 5, 6, 7], [3, 4, 5, 6, 7, 8], + [4, 5, 6, 7, 8, 9], [5, 6, 7, 8, 9, 10]] + )); + assert(r.slide(15).equal!equal( + [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]] + )); + + assert(r.slide!(No.withFewerElements)(15).empty); + } + + alias BackwardsDummyRanges = AliasSeq!( + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random), + ); + + foreach (Range; AliasSeq!BackwardsDummyRanges) + { + Range r; + assert(r.slide(1).retro.equal!equal( + [[10], [9], [8], [7], [6], [5], [4], [3], [2], [1]] + )); + assert(r.slide(2).retro.equal!equal( + [[9, 10], [8, 9], [7, 8], [6, 7], [5, 6], [4, 5], [3, 4], [2, 3], [1, 2]] + )); + assert(r.slide(5).retro.equal!equal( + [[6, 7, 8, 9, 10], [5, 6, 7, 8, 9], [4, 5, 6, 7, 8], + [3, 4, 5, 6, 7], [2, 3, 4, 5, 6], [1, 2, 3, 4, 5]] + )); + assert(r.slide(15).retro.equal!equal( + [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]] + )); + + // different step sizes + assert(r.slide(2, 4)[2].equal([9, 10])); + assert(r.slide(2, 1).equal!equal( + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9], [9, 10]] + )); + assert(r.slide(2, 2).equal!equal( + [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]] + )); + assert(r.slide(2, 3).equal!equal( + [[1, 2], [4, 5], [7, 8]] + )); + assert(r.slide(2, 4).equal!equal( + [[1, 2], [5, 6], [9, 10]] + )); + + // front = back + foreach (windowSize; 1 .. 10) + foreach (stepSize; 1 .. 10) + { + auto slider = r.slide(windowSize, stepSize); + assert(slider.retro.retro.equal!equal(slider)); + } + } + + assert(iota(1, 12).slide(2, 4)[0 .. 3].equal!equal([[1, 2], [5, 6], [9, 10]])); + assert(iota(1, 12).slide(2, 4)[0 .. $].equal!equal([[1, 2], [5, 6], [9, 10]])); + assert(iota(1, 12).slide(2, 4)[$/2 .. $].equal!equal([[5, 6], [9, 10]])); + + // reverse + assert(iota(1, 12).slide(2, 4).retro.equal!equal([[9, 10], [5, 6], [1, 2]])); +} + +// test different sliceable ranges +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : DummyRange, Length, RangeType, ReturnBy; + import std.meta : AliasSeq; + + struct SliceableRange(Range, Flag!"withOpDollar" withOpDollar = No.withOpDollar, + Flag!"withInfiniteness" withInfiniteness = No.withInfiniteness) + { + Range arr = 10.iota.array; // similar to DummyRange + @property auto save() { return typeof(this)(arr); } + @property auto front() { return arr[0]; } + void popFront() { arr.popFront(); } + auto opSlice(size_t i, size_t j) + { + // subslices can't be infinite + return SliceableRange!(Range, withOpDollar, No.withInfiniteness)(arr[i .. j]); + } + + static if (withInfiniteness) + { + enum empty = false; + } + else + { + @property bool empty() { return arr.empty; } + @property auto length() { return arr.length; } + } + + static if (withOpDollar) + { + static if (withInfiniteness) + { + struct Dollar {} + Dollar opDollar() const { return Dollar.init; } + + //Slice to dollar + typeof(this) opSlice(size_t lower, Dollar) + { + return typeof(this)(arr[lower .. $]); + } + + } + else + { + alias opDollar = length; + } + } + } + + alias T = int[]; + + alias SliceableDummyRanges = AliasSeq!( + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random, T), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random, T), + SliceableRange!(T, No.withOpDollar, No.withInfiniteness), + SliceableRange!(T, Yes.withOpDollar, No.withInfiniteness), + SliceableRange!(T, Yes.withOpDollar, Yes.withInfiniteness), + ); + + foreach (Range; AliasSeq!SliceableDummyRanges) + { + Range r; + r.arr = 10.iota.array; // for clarity + + static assert(isForwardRange!Range); + enum hasSliceToEnd = hasSlicing!Range && is(typeof(Range.init[0 .. $]) == Range); + + assert(r.slide(2)[0].equal([0, 1])); + assert(r.slide(2)[1].equal([1, 2])); + + // saveable + auto s = r.slide(2); + assert(s[0 .. 2].equal!equal([[0, 1], [1, 2]])); + s.save.popFront; + assert(s[0 .. 2].equal!equal([[0, 1], [1, 2]])); + + assert(r.slide(3)[1 .. 3].equal!equal([[1, 2, 3], [2, 3, 4]])); + } + + alias SliceableDummyRangesWithoutInfinity = AliasSeq!( + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random, T), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random, T), + SliceableRange!(T, No.withOpDollar, No.withInfiniteness), + SliceableRange!(T, Yes.withOpDollar, No.withInfiniteness), + ); + + foreach (Range; AliasSeq!SliceableDummyRangesWithoutInfinity) + { + static assert(hasSlicing!Range); + static assert(hasLength!Range); + + Range r; + r.arr = 10.iota.array; // for clarity + + assert(r.slide!(No.withFewerElements)(6).equal!equal( + [[0, 1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6], [2, 3, 4, 5, 6, 7], + [3, 4, 5, 6, 7, 8], [4, 5, 6, 7, 8, 9]] + )); + assert(r.slide!(No.withFewerElements)(16).empty); + + assert(r.slide(4)[0 .. $].equal(r.slide(4))); + assert(r.slide(2)[$/2 .. $].equal!equal([[4, 5], [5, 6], [6, 7], [7, 8], [8, 9]])); + assert(r.slide(2)[$ .. $].empty); + + assert(r.slide(3).retro.equal!equal( + [[7, 8, 9], [6, 7, 8], [5, 6, 7], [4, 5, 6], [3, 4, 5], [2, 3, 4], [1, 2, 3], [0, 1, 2]] + )); + } + + // separate checks for infinity + auto infIndex = SliceableRange!(T, No.withOpDollar, Yes.withInfiniteness)([0, 1, 2, 3]); + assert(infIndex.slide(2)[0].equal([0, 1])); + assert(infIndex.slide(2)[1].equal([1, 2])); + + auto infDollar = SliceableRange!(T, Yes.withOpDollar, Yes.withInfiniteness)(); + assert(infDollar.slide(2)[1 .. $].front.equal([1, 2])); + assert(infDollar.slide(4)[0 .. $].front.equal([0, 1, 2, 3])); + assert(infDollar.slide(4)[2 .. $].front.equal([2, 3, 4, 5])); +} + +private struct OnlyResult(T, size_t arity) +{ + private this(Values...)(auto ref Values values) + { + this.data = [values]; + this.backIndex = arity; + } + + bool empty() @property + { + return frontIndex >= backIndex; + } + + T front() @property + { + assert(!empty, "Attempting to fetch the front of an empty Only range"); + return data[frontIndex]; + } + + void popFront() + { + assert(!empty, "Attempting to popFront an empty Only range"); + ++frontIndex; + } + + T back() @property + { + assert(!empty, "Attempting to fetch the back of an empty Only range"); + return data[backIndex - 1]; + } + + void popBack() + { + assert(!empty, "Attempting to popBack an empty Only range"); + --backIndex; + } + + OnlyResult save() @property + { + return this; + } + + size_t length() const @property + { + return backIndex - frontIndex; + } + + alias opDollar = length; + + T opIndex(size_t idx) + { + // when i + idx points to elements popped + // with popBack + assert(idx < length, "Attempting to fetch an out of bounds index from an Only range"); + return data[frontIndex + idx]; + } + + OnlyResult opSlice() + { + return this; + } + + OnlyResult opSlice(size_t from, size_t to) + { + OnlyResult result = this; + result.frontIndex += from; + result.backIndex = this.frontIndex + to; + assert( + from <= to, + "Attempting to slice an Only range with a larger first argument than the second." + ); + assert( + to <= length, + "Attempting to slice using an out of bounds index on an Only range" + ); + return result; + } + + private size_t frontIndex = 0; + private size_t backIndex = 0; + + // @@@BUG@@@ 10643 + version (none) + { + import std.traits : hasElaborateAssign; + static if (hasElaborateAssign!T) + private T[arity] data; + else + private T[arity] data = void; + } + else + private T[arity] data; +} + +// Specialize for single-element results +private struct OnlyResult(T, size_t arity : 1) +{ + @property T front() + { + assert(!empty, "Attempting to fetch the front of an empty Only range"); + return _value; + } + @property T back() + { + assert(!empty, "Attempting to fetch the back of an empty Only range"); + return _value; + } + @property bool empty() const { return _empty; } + @property size_t length() const { return !_empty; } + @property auto save() { return this; } + void popFront() + { + assert(!_empty, "Attempting to popFront an empty Only range"); + _empty = true; + } + void popBack() + { + assert(!_empty, "Attempting to popBack an empty Only range"); + _empty = true; + } + alias opDollar = length; + + private this()(auto ref T value) + { + this._value = value; + this._empty = false; + } + + T opIndex(size_t i) + { + assert(!_empty && i == 0, "Attempting to fetch an out of bounds index from an Only range"); + return _value; + } + + OnlyResult opSlice() + { + return this; + } + + OnlyResult opSlice(size_t from, size_t to) + { + assert( + from <= to, + "Attempting to slice an Only range with a larger first argument than the second." + ); + assert( + to <= length, + "Attempting to slice using an out of bounds index on an Only range" + ); + OnlyResult copy = this; + copy._empty = _empty || from == to; + return copy; + } + + private Unqual!T _value; + private bool _empty = true; +} + +// Specialize for the empty range +private struct OnlyResult(T, size_t arity : 0) +{ + private static struct EmptyElementType {} + + bool empty() @property { return true; } + size_t length() const @property { return 0; } + alias opDollar = length; + EmptyElementType front() @property { assert(false); } + void popFront() { assert(false); } + EmptyElementType back() @property { assert(false); } + void popBack() { assert(false); } + OnlyResult save() @property { return this; } + + EmptyElementType opIndex(size_t i) + { + assert(false); + } + + OnlyResult opSlice() { return this; } + + OnlyResult opSlice(size_t from, size_t to) + { + assert(from == 0 && to == 0); + return this; + } +} + +/** +Assemble $(D values) into a range that carries all its +elements in-situ. + +Useful when a single value or multiple disconnected values +must be passed to an algorithm expecting a range, without +having to perform dynamic memory allocation. + +As copying the range means copying all elements, it can be +safely returned from functions. For the same reason, copying +the returned range may be expensive for a large number of arguments. + +Params: + values = the values to assemble together + +Returns: + A `RandomAccessRange` of the assembled values. + +See_Also: $(LREF chain) to chain ranges + */ +auto only(Values...)(auto ref Values values) +if (!is(CommonType!Values == void) || Values.length == 0) +{ + return OnlyResult!(CommonType!Values, Values.length)(values); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filter, joiner, map; + import std.algorithm.searching : findSplitBefore; + import std.uni : isUpper; + + assert(equal(only('♡'), "♡")); + assert([1, 2, 3, 4].findSplitBefore(only(3))[0] == [1, 2]); + + assert(only("one", "two", "three").joiner(" ").equal("one two three")); + + string title = "The D Programming Language"; + assert(title + .filter!isUpper // take the upper case letters + .map!only // make each letter its own range + .joiner(".") // join the ranges together lazily + .equal("T.D.P.L")); +} + +@safe unittest +{ + // Verify that the same common type and same arity + // results in the same template instantiation + static assert(is(typeof(only(byte.init, int.init)) == + typeof(only(int.init, byte.init)))); + + static assert(is(typeof(only((const(char)[]).init, string.init)) == + typeof(only((const(char)[]).init, (const(char)[]).init)))); +} + +// Tests the zero-element result +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto emptyRange = only(); + + alias EmptyRange = typeof(emptyRange); + static assert(isInputRange!EmptyRange); + static assert(isForwardRange!EmptyRange); + static assert(isBidirectionalRange!EmptyRange); + static assert(isRandomAccessRange!EmptyRange); + static assert(hasLength!EmptyRange); + static assert(hasSlicing!EmptyRange); + + assert(emptyRange.empty); + assert(emptyRange.length == 0); + assert(emptyRange.equal(emptyRange[])); + assert(emptyRange.equal(emptyRange.save)); + assert(emptyRange[0 .. 0].equal(emptyRange)); +} + +// Tests the single-element result +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : tuple; + foreach (x; tuple(1, '1', 1.0, "1", [1])) + { + auto a = only(x); + typeof(x)[] e = []; + assert(a.front == x); + assert(a.back == x); + assert(!a.empty); + assert(a.length == 1); + assert(equal(a, a[])); + assert(equal(a, a[0 .. 1])); + assert(equal(a[0 .. 0], e)); + assert(equal(a[1 .. 1], e)); + assert(a[0] == x); + + auto b = a.save; + assert(equal(a, b)); + a.popFront(); + assert(a.empty && a.length == 0 && a[].empty); + b.popBack(); + assert(b.empty && b.length == 0 && b[].empty); + + alias A = typeof(a); + static assert(isInputRange!A); + static assert(isForwardRange!A); + static assert(isBidirectionalRange!A); + static assert(isRandomAccessRange!A); + static assert(hasLength!A); + static assert(hasSlicing!A); + } + + auto imm = only!(immutable int)(1); + immutable int[] imme = []; + assert(imm.front == 1); + assert(imm.back == 1); + assert(!imm.empty); + assert(imm.init.empty); // Issue 13441 + assert(imm.length == 1); + assert(equal(imm, imm[])); + assert(equal(imm, imm[0 .. 1])); + assert(equal(imm[0 .. 0], imme)); + assert(equal(imm[1 .. 1], imme)); + assert(imm[0] == 1); +} + +// Tests multiple-element results +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : joiner; + import std.meta : AliasSeq; + static assert(!__traits(compiles, only(1, "1"))); + + auto nums = only!(byte, uint, long)(1, 2, 3); + static assert(is(ElementType!(typeof(nums)) == long)); + assert(nums.length == 3); + + foreach (i; 0 .. 3) + assert(nums[i] == i + 1); + + auto saved = nums.save; + + foreach (i; 1 .. 4) + { + assert(nums.front == nums[0]); + assert(nums.front == i); + nums.popFront(); + assert(nums.length == 3 - i); + } + + assert(nums.empty); + + assert(saved.equal(only(1, 2, 3))); + assert(saved.equal(saved[])); + assert(saved[0 .. 1].equal(only(1))); + assert(saved[0 .. 2].equal(only(1, 2))); + assert(saved[0 .. 3].equal(saved)); + assert(saved[1 .. 3].equal(only(2, 3))); + assert(saved[2 .. 3].equal(only(3))); + assert(saved[0 .. 0].empty); + assert(saved[3 .. 3].empty); + + alias data = AliasSeq!("one", "two", "three", "four"); + static joined = + ["one two", "one two three", "one two three four"]; + string[] joinedRange = joined; + + foreach (argCount; AliasSeq!(2, 3, 4)) + { + auto values = only(data[0 .. argCount]); + alias Values = typeof(values); + static assert(is(ElementType!Values == string)); + static assert(isInputRange!Values); + static assert(isForwardRange!Values); + static assert(isBidirectionalRange!Values); + static assert(isRandomAccessRange!Values); + static assert(hasSlicing!Values); + static assert(hasLength!Values); + + assert(values.length == argCount); + assert(values[0 .. $].equal(values[0 .. values.length])); + assert(values.joiner(" ").equal(joinedRange.front)); + joinedRange.popFront(); + } + + assert(saved.retro.equal(only(3, 2, 1))); + assert(saved.length == 3); + + assert(saved.back == 3); + saved.popBack(); + assert(saved.length == 2); + assert(saved.back == 2); + + assert(saved.front == 1); + saved.popFront(); + assert(saved.length == 1); + assert(saved.front == 2); + + saved.popBack(); + assert(saved.empty); + + auto imm = only!(immutable int, immutable int)(42, 24); + alias Imm = typeof(imm); + static assert(is(ElementType!Imm == immutable(int))); + assert(!imm.empty); + assert(imm.init.empty); // Issue 13441 + assert(imm.front == 42); + imm.popFront(); + assert(imm.front == 24); + imm.popFront(); + assert(imm.empty); + + static struct Test { int* a; } + immutable(Test) test; + cast(void) only(test, test); // Works with mutable indirection +} + +/** +Iterate over `range` with an attached index variable. + +Each element is a $(REF Tuple, std,typecons) containing the index +and the element, in that order, where the index member is named $(D index) +and the element member is named `value`. + +The index starts at `start` and is incremented by one on every iteration. + +Overflow: + If `range` has length, then it is an error to pass a value for `start` + so that `start + range.length` is bigger than `Enumerator.max`, thus + it is ensured that overflow cannot happen. + + If `range` does not have length, and `popFront` is called when + `front.index == Enumerator.max`, the index will overflow and + continue from `Enumerator.min`. + +Params: + range = the input range to attach indexes to + start = the number to start the index counter from + +Returns: + At minimum, an input range. All other range primitives are given in the + resulting range if `range` has them. The exceptions are the bidirectional + primitives, which are propagated only if `range` has length. + +Example: +Useful for using $(D foreach) with an index loop variable: +---- + import std.stdio : stdin, stdout; + import std.range : enumerate; + + foreach (lineNum, line; stdin.byLine().enumerate(1)) + stdout.writefln("line #%s: %s", lineNum, line); +---- +*/ +auto enumerate(Enumerator = size_t, Range)(Range range, Enumerator start = 0) +if (isIntegral!Enumerator && isInputRange!Range) +in +{ + static if (hasLength!Range) + { + // TODO: core.checkedint supports mixed signedness yet? + import core.checkedint : adds, addu; + import std.conv : ConvException, to; + import std.traits : isSigned, Largest, Signed; + + alias LengthType = typeof(range.length); + bool overflow; + static if (isSigned!Enumerator && isSigned!LengthType) + auto result = adds(start, range.length, overflow); + else static if (isSigned!Enumerator) + { + Largest!(Enumerator, Signed!LengthType) signedLength; + try signedLength = to!(typeof(signedLength))(range.length); + catch (ConvException) + overflow = true; + catch (Exception) + assert(false); + + auto result = adds(start, signedLength, overflow); + } + else + { + static if (isSigned!LengthType) + assert(range.length >= 0); + auto result = addu(start, range.length, overflow); + } + + assert(!overflow && result <= Enumerator.max); + } +} +body +{ + // TODO: Relax isIntegral!Enumerator to allow user-defined integral types + static struct Result + { + import std.typecons : Tuple; + + private: + alias ElemType = Tuple!(Enumerator, "index", ElementType!Range, "value"); + Range range; + Enumerator index; + + public: + ElemType front() @property + { + assert(!range.empty, "Attempting to fetch the front of an empty enumerate"); + return typeof(return)(index, range.front); + } + + static if (isInfinite!Range) + enum bool empty = false; + else + { + bool empty() @property + { + return range.empty; + } + } + + void popFront() + { + assert(!range.empty, "Attempting to popFront an empty enumerate"); + range.popFront(); + ++index; // When !hasLength!Range, overflow is expected + } + + static if (isForwardRange!Range) + { + Result save() @property + { + return typeof(return)(range.save, index); + } + } + + static if (hasLength!Range) + { + size_t length() @property + { + return range.length; + } + + alias opDollar = length; + + static if (isBidirectionalRange!Range) + { + ElemType back() @property + { + assert(!range.empty, "Attempting to fetch the back of an empty enumerate"); + return typeof(return)(cast(Enumerator)(index + range.length - 1), range.back); + } + + void popBack() + { + assert(!range.empty, "Attempting to popBack an empty enumerate"); + range.popBack(); + } + } + } + + static if (isRandomAccessRange!Range) + { + ElemType opIndex(size_t i) + { + return typeof(return)(cast(Enumerator)(index + i), range[i]); + } + } + + static if (hasSlicing!Range) + { + static if (hasLength!Range) + { + Result opSlice(size_t i, size_t j) + { + return typeof(return)(range[i .. j], cast(Enumerator)(index + i)); + } + } + else + { + static struct DollarToken {} + enum opDollar = DollarToken.init; + + Result opSlice(size_t i, DollarToken) + { + return typeof(return)(range[i .. $], cast(Enumerator)(index + i)); + } + + auto opSlice(size_t i, size_t j) + { + return this[i .. $].takeExactly(j - 1); + } + } + } + } + + return Result(range, start); +} + +/// Can start enumeration from a negative position: +pure @safe nothrow unittest +{ + import std.array : assocArray; + import std.range : enumerate; + + bool[int] aa = true.repeat(3).enumerate(-1).assocArray(); + assert(aa[-1]); + assert(aa[0]); + assert(aa[1]); +} + +pure @safe nothrow unittest +{ + import std.internal.test.dummyrange : AllDummyRanges; + import std.meta : AliasSeq; + import std.typecons : tuple; + + static struct HasSlicing + { + typeof(this) front() @property { return typeof(this).init; } + bool empty() @property { return true; } + void popFront() {} + + typeof(this) opSlice(size_t, size_t) + { + return typeof(this)(); + } + } + + foreach (DummyType; AliasSeq!(AllDummyRanges, HasSlicing)) + { + alias R = typeof(enumerate(DummyType.init)); + static assert(isInputRange!R); + static assert(isForwardRange!R == isForwardRange!DummyType); + static assert(isRandomAccessRange!R == isRandomAccessRange!DummyType); + static assert(!hasAssignableElements!R); + + static if (hasLength!DummyType) + { + static assert(hasLength!R); + static assert(isBidirectionalRange!R == + isBidirectionalRange!DummyType); + } + + static assert(hasSlicing!R == hasSlicing!DummyType); + } + + static immutable values = ["zero", "one", "two", "three"]; + auto enumerated = values[].enumerate(); + assert(!enumerated.empty); + assert(enumerated.front == tuple(0, "zero")); + assert(enumerated.back == tuple(3, "three")); + + typeof(enumerated) saved = enumerated.save; + saved.popFront(); + assert(enumerated.front == tuple(0, "zero")); + assert(saved.front == tuple(1, "one")); + assert(saved.length == enumerated.length - 1); + saved.popBack(); + assert(enumerated.back == tuple(3, "three")); + assert(saved.back == tuple(2, "two")); + saved.popFront(); + assert(saved.front == tuple(2, "two")); + assert(saved.back == tuple(2, "two")); + saved.popFront(); + assert(saved.empty); + + size_t control = 0; + foreach (i, v; enumerated) + { + static assert(is(typeof(i) == size_t)); + static assert(is(typeof(v) == typeof(values[0]))); + assert(i == control); + assert(v == values[i]); + assert(tuple(i, v) == enumerated[i]); + ++control; + } + + assert(enumerated[0 .. $].front == tuple(0, "zero")); + assert(enumerated[$ - 1 .. $].front == tuple(3, "three")); + + foreach (i; 0 .. 10) + { + auto shifted = values[0 .. 2].enumerate(i); + assert(shifted.front == tuple(i, "zero")); + assert(shifted[0] == shifted.front); + + auto next = tuple(i + 1, "one"); + assert(shifted[1] == next); + shifted.popFront(); + assert(shifted.front == next); + shifted.popFront(); + assert(shifted.empty); + } + + foreach (T; AliasSeq!(ubyte, byte, uint, int)) + { + auto inf = 42.repeat().enumerate(T.max); + alias Inf = typeof(inf); + static assert(isInfinite!Inf); + static assert(hasSlicing!Inf); + + // test overflow + assert(inf.front == tuple(T.max, 42)); + inf.popFront(); + assert(inf.front == tuple(T.min, 42)); + + // test slicing + inf = inf[42 .. $]; + assert(inf.front == tuple(T.min + 42, 42)); + auto window = inf[0 .. 2]; + assert(window.length == 1); + assert(window.front == inf.front); + window.popFront(); + assert(window.empty); + } +} + +pure @safe unittest +{ + import std.algorithm.comparison : equal; + import std.meta : AliasSeq; + static immutable int[] values = [0, 1, 2, 3, 4]; + foreach (T; AliasSeq!(ubyte, ushort, uint, ulong)) + { + auto enumerated = values.enumerate!T(); + static assert(is(typeof(enumerated.front.index) == T)); + assert(enumerated.equal(values[].zip(values))); + + foreach (T i; 0 .. 5) + { + auto subset = values[cast(size_t) i .. $]; + auto offsetEnumerated = subset.enumerate(i); + static assert(is(typeof(enumerated.front.index) == T)); + assert(offsetEnumerated.equal(subset.zip(subset))); + } + } +} + +version (none) // @@@BUG@@@ 10939 +{ + // Re-enable (or remove) if 10939 is resolved. + /+pure+/ @safe unittest // Impure because of std.conv.to + { + import core.exception : RangeError; + import std.exception : assertNotThrown, assertThrown; + import std.meta : AliasSeq; + + static immutable values = [42]; + + static struct SignedLengthRange + { + immutable(int)[] _values = values; + + int front() @property { assert(false); } + bool empty() @property { assert(false); } + void popFront() { assert(false); } + + int length() @property + { + return cast(int)_values.length; + } + } + + SignedLengthRange svalues; + foreach (Enumerator; AliasSeq!(ubyte, byte, ushort, short, uint, int, ulong, long)) + { + assertThrown!RangeError(values[].enumerate!Enumerator(Enumerator.max)); + assertNotThrown!RangeError(values[].enumerate!Enumerator(Enumerator.max - values.length)); + assertThrown!RangeError(values[].enumerate!Enumerator(Enumerator.max - values.length + 1)); + + assertThrown!RangeError(svalues.enumerate!Enumerator(Enumerator.max)); + assertNotThrown!RangeError(svalues.enumerate!Enumerator(Enumerator.max - values.length)); + assertThrown!RangeError(svalues.enumerate!Enumerator(Enumerator.max - values.length + 1)); + } + + foreach (Enumerator; AliasSeq!(byte, short, int)) + { + assertThrown!RangeError(repeat(0, uint.max).enumerate!Enumerator()); + } + + assertNotThrown!RangeError(repeat(0, uint.max).enumerate!long()); + } +} + +/** + Returns true if $(D fn) accepts variables of type T1 and T2 in any order. + The following code should compile: + --- + T1 foo(); + T2 bar(); + + fn(foo(), bar()); + fn(bar(), foo()); + --- +*/ +template isTwoWayCompatible(alias fn, T1, T2) +{ + enum isTwoWayCompatible = is(typeof( (){ + T1 foo(); + T2 bar(); + + fn(foo(), bar()); + fn(bar(), foo()); + } + )); +} + + +/** + Policy used with the searching primitives $(D lowerBound), $(D + upperBound), and $(D equalRange) of $(LREF SortedRange) below. + */ +enum SearchPolicy +{ + /** + Searches in a linear fashion. + */ + linear, + + /** + Searches with a step that is grows linearly (1, 2, 3,...) + leading to a quadratic search schedule (indexes tried are 0, 1, + 3, 6, 10, 15, 21, 28,...) Once the search overshoots its target, + the remaining interval is searched using binary search. The + search is completed in $(BIGOH sqrt(n)) time. Use it when you + are reasonably confident that the value is around the beginning + of the range. + */ + trot, + + /** + Performs a $(LINK2 https://en.wikipedia.org/wiki/Exponential_search, + galloping search algorithm), i.e. searches + with a step that doubles every time, (1, 2, 4, 8, ...) leading + to an exponential search schedule (indexes tried are 0, 1, 3, + 7, 15, 31, 63,...) Once the search overshoots its target, the + remaining interval is searched using binary search. A value is + found in $(BIGOH log(n)) time. + */ + gallop, + + /** + Searches using a classic interval halving policy. The search + starts in the middle of the range, and each search step cuts + the range in half. This policy finds a value in $(BIGOH log(n)) + time but is less cache friendly than $(D gallop) for large + ranges. The $(D binarySearch) policy is used as the last step + of $(D trot), $(D gallop), $(D trotBackwards), and $(D + gallopBackwards) strategies. + */ + binarySearch, + + /** + Similar to $(D trot) but starts backwards. Use it when + confident that the value is around the end of the range. + */ + trotBackwards, + + /** + Similar to $(D gallop) but starts backwards. Use it when + confident that the value is around the end of the range. + */ + gallopBackwards + } + +/** +Represents a sorted range. In addition to the regular range +primitives, supports additional operations that take advantage of the +ordering, such as merge and binary search. To obtain a $(D +SortedRange) from an unsorted range $(D r), use +$(REF sort, std,algorithm,sorting) which sorts $(D r) in place and returns the +corresponding $(D SortedRange). To construct a $(D SortedRange) from a range +$(D r) that is known to be already sorted, use $(LREF assumeSorted) described +below. +*/ +struct SortedRange(Range, alias pred = "a < b") +if (isInputRange!Range) +{ + import std.functional : binaryFun; + + private alias predFun = binaryFun!pred; + private bool geq(L, R)(L lhs, R rhs) + { + return !predFun(lhs, rhs); + } + private bool gt(L, R)(L lhs, R rhs) + { + return predFun(rhs, lhs); + } + private Range _input; + + // Undocummented because a clearer way to invoke is by calling + // assumeSorted. + this(Range input) + out + { + // moved out of the body as a workaround for Issue 12661 + dbgVerifySorted(); + } + body + { + this._input = input; + } + + // Assertion only. + private void dbgVerifySorted() + { + if (!__ctfe) + debug + { + static if (isRandomAccessRange!Range && hasLength!Range) + { + import core.bitop : bsr; + import std.algorithm.sorting : isSorted; + + // Check the sortedness of the input + if (this._input.length < 2) return; + + immutable size_t msb = bsr(this._input.length) + 1; + assert(msb > 0 && msb <= this._input.length); + immutable step = this._input.length / msb; + auto st = stride(this._input, step); + + assert(isSorted!pred(st), "Range is not sorted"); + } + } + } + + /// Range primitives. + @property bool empty() //const + { + return this._input.empty; + } + + /// Ditto + static if (isForwardRange!Range) + @property auto save() + { + // Avoid the constructor + typeof(this) result = this; + result._input = _input.save; + return result; + } + + /// Ditto + @property auto ref front() + { + return _input.front; + } + + /// Ditto + void popFront() + { + _input.popFront(); + } + + /// Ditto + static if (isBidirectionalRange!Range) + { + @property auto ref back() + { + return _input.back; + } + + /// Ditto + void popBack() + { + _input.popBack(); + } + } + + /// Ditto + static if (isRandomAccessRange!Range) + auto ref opIndex(size_t i) + { + return _input[i]; + } + + /// Ditto + static if (hasSlicing!Range) + auto opSlice(size_t a, size_t b) + { + assert( + a <= b, + "Attempting to slice a SortedRange with a larger first argument than the second." + ); + typeof(this) result = this; + result._input = _input[a .. b];// skip checking + return result; + } + + /// Ditto + static if (hasLength!Range) + { + @property size_t length() //const + { + return _input.length; + } + alias opDollar = length; + } + +/** + Releases the controlled range and returns it. +*/ + auto release() + { + import std.algorithm.mutation : move; + return move(_input); + } + + // Assuming a predicate "test" that returns 0 for a left portion + // of the range and then 1 for the rest, returns the index at + // which the first 1 appears. Used internally by the search routines. + private size_t getTransitionIndex(SearchPolicy sp, alias test, V)(V v) + if (sp == SearchPolicy.binarySearch && isRandomAccessRange!Range && hasLength!Range) + { + size_t first = 0, count = _input.length; + while (count > 0) + { + immutable step = count / 2, it = first + step; + if (!test(_input[it], v)) + { + first = it + 1; + count -= step + 1; + } + else + { + count = step; + } + } + return first; + } + + // Specialization for trot and gallop + private size_t getTransitionIndex(SearchPolicy sp, alias test, V)(V v) + if ((sp == SearchPolicy.trot || sp == SearchPolicy.gallop) + && isRandomAccessRange!Range) + { + if (empty || test(front, v)) return 0; + immutable count = length; + if (count == 1) return 1; + size_t below = 0, above = 1, step = 2; + while (!test(_input[above], v)) + { + // Still too small, update below and increase gait + below = above; + immutable next = above + step; + if (next >= count) + { + // Overshot - the next step took us beyond the end. So + // now adjust next and simply exit the loop to do the + // binary search thingie. + above = count; + break; + } + // Still in business, increase step and continue + above = next; + static if (sp == SearchPolicy.trot) + ++step; + else + step <<= 1; + } + return below + this[below .. above].getTransitionIndex!( + SearchPolicy.binarySearch, test, V)(v); + } + + // Specialization for trotBackwards and gallopBackwards + private size_t getTransitionIndex(SearchPolicy sp, alias test, V)(V v) + if ((sp == SearchPolicy.trotBackwards || sp == SearchPolicy.gallopBackwards) + && isRandomAccessRange!Range) + { + immutable count = length; + if (empty || !test(back, v)) return count; + if (count == 1) return 0; + size_t below = count - 2, above = count - 1, step = 2; + while (test(_input[below], v)) + { + // Still too large, update above and increase gait + above = below; + if (below < step) + { + // Overshot - the next step took us beyond the end. So + // now adjust next and simply fall through to do the + // binary search thingie. + below = 0; + break; + } + // Still in business, increase step and continue + below -= step; + static if (sp == SearchPolicy.trot) + ++step; + else + step <<= 1; + } + return below + this[below .. above].getTransitionIndex!( + SearchPolicy.binarySearch, test, V)(v); + } + +// lowerBound +/** + This function uses a search with policy $(D sp) to find the + largest left subrange on which $(D pred(x, value)) is $(D true) for + all $(D x) (e.g., if $(D pred) is "less than", returns the portion of + the range with elements strictly smaller than $(D value)). The search + schedule and its complexity are documented in + $(LREF SearchPolicy). See also STL's + $(HTTP sgi.com/tech/stl/lower_bound.html, lower_bound). +*/ + auto lowerBound(SearchPolicy sp = SearchPolicy.binarySearch, V)(V value) + if (isTwoWayCompatible!(predFun, ElementType!Range, V) + && hasSlicing!Range) + { + return this[0 .. getTransitionIndex!(sp, geq)(value)]; + } + + /// + @safe unittest + { + import std.algorithm.comparison : equal; + auto a = assumeSorted([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]); + auto p = a.lowerBound(4); + assert(equal(p, [ 0, 1, 2, 3 ])); + } + +// upperBound +/** +This function searches with policy $(D sp) to find the largest right +subrange on which $(D pred(value, x)) is $(D true) for all $(D x) +(e.g., if $(D pred) is "less than", returns the portion of the range +with elements strictly greater than $(D value)). The search schedule +and its complexity are documented in $(LREF SearchPolicy). + +For ranges that do not offer random access, $(D SearchPolicy.linear) +is the only policy allowed (and it must be specified explicitly lest it exposes +user code to unexpected inefficiencies). For random-access searches, all +policies are allowed, and $(D SearchPolicy.binarySearch) is the default. + +See_Also: STL's $(HTTP sgi.com/tech/stl/lower_bound.html,upper_bound). +*/ + auto upperBound(SearchPolicy sp = SearchPolicy.binarySearch, V)(V value) + if (isTwoWayCompatible!(predFun, ElementType!Range, V)) + { + static assert(hasSlicing!Range || sp == SearchPolicy.linear, + "Specify SearchPolicy.linear explicitly for " + ~ typeof(this).stringof); + static if (sp == SearchPolicy.linear) + { + for (; !_input.empty && !predFun(value, _input.front); + _input.popFront()) + { + } + return this; + } + else + { + return this[getTransitionIndex!(sp, gt)(value) .. length]; + } + } + + /// + @safe unittest + { + import std.algorithm.comparison : equal; + auto a = assumeSorted([ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]); + auto p = a.upperBound(3); + assert(equal(p, [4, 4, 5, 6])); + } + + +// equalRange +/** + Returns the subrange containing all elements $(D e) for which both $(D + pred(e, value)) and $(D pred(value, e)) evaluate to $(D false) (e.g., + if $(D pred) is "less than", returns the portion of the range with + elements equal to $(D value)). Uses a classic binary search with + interval halving until it finds a value that satisfies the condition, + then uses $(D SearchPolicy.gallopBackwards) to find the left boundary + and $(D SearchPolicy.gallop) to find the right boundary. These + policies are justified by the fact that the two boundaries are likely + to be near the first found value (i.e., equal ranges are relatively + small). Completes the entire search in $(BIGOH log(n)) time. See also + STL's $(HTTP sgi.com/tech/stl/equal_range.html, equal_range). +*/ + auto equalRange(V)(V value) + if (isTwoWayCompatible!(predFun, ElementType!Range, V) + && isRandomAccessRange!Range) + { + size_t first = 0, count = _input.length; + while (count > 0) + { + immutable step = count / 2; + auto it = first + step; + if (predFun(_input[it], value)) + { + // Less than value, bump left bound up + first = it + 1; + count -= step + 1; + } + else if (predFun(value, _input[it])) + { + // Greater than value, chop count + count = step; + } + else + { + // Equal to value, do binary searches in the + // leftover portions + // Gallop towards the left end as it's likely nearby + immutable left = first + + this[first .. it] + .lowerBound!(SearchPolicy.gallopBackwards)(value).length; + first += count; + // Gallop towards the right end as it's likely nearby + immutable right = first + - this[it + 1 .. first] + .upperBound!(SearchPolicy.gallop)(value).length; + return this[left .. right]; + } + } + return this.init; + } + + /// + @safe unittest + { + import std.algorithm.comparison : equal; + auto a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]; + auto r = a.assumeSorted.equalRange(3); + assert(equal(r, [ 3, 3, 3 ])); + } + +// trisect +/** +Returns a tuple $(D r) such that $(D r[0]) is the same as the result +of $(D lowerBound(value)), $(D r[1]) is the same as the result of $(D +equalRange(value)), and $(D r[2]) is the same as the result of $(D +upperBound(value)). The call is faster than computing all three +separately. Uses a search schedule similar to $(D +equalRange). Completes the entire search in $(BIGOH log(n)) time. +*/ + auto trisect(V)(V value) + if (isTwoWayCompatible!(predFun, ElementType!Range, V) + && isRandomAccessRange!Range && hasLength!Range) + { + import std.typecons : tuple; + size_t first = 0, count = _input.length; + while (count > 0) + { + immutable step = count / 2; + auto it = first + step; + if (predFun(_input[it], value)) + { + // Less than value, bump left bound up + first = it + 1; + count -= step + 1; + } + else if (predFun(value, _input[it])) + { + // Greater than value, chop count + count = step; + } + else + { + // Equal to value, do binary searches in the + // leftover portions + // Gallop towards the left end as it's likely nearby + immutable left = first + + this[first .. it] + .lowerBound!(SearchPolicy.gallopBackwards)(value).length; + first += count; + // Gallop towards the right end as it's likely nearby + immutable right = first + - this[it + 1 .. first] + .upperBound!(SearchPolicy.gallop)(value).length; + return tuple(this[0 .. left], this[left .. right], + this[right .. length]); + } + } + // No equal element was found + return tuple(this[0 .. first], this.init, this[first .. length]); + } + + /// + @safe unittest + { + import std.algorithm.comparison : equal; + auto a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]; + auto r = assumeSorted(a).trisect(3); + assert(equal(r[0], [ 1, 2 ])); + assert(equal(r[1], [ 3, 3, 3 ])); + assert(equal(r[2], [ 4, 4, 5, 6 ])); + } + +// contains +/** +Returns $(D true) if and only if $(D value) can be found in $(D +range), which is assumed to be sorted. Performs $(BIGOH log(r.length)) +evaluations of $(D pred). See also STL's $(HTTP +sgi.com/tech/stl/binary_search.html, binary_search). + */ + + bool contains(V)(V value) + if (isRandomAccessRange!Range) + { + if (empty) return false; + immutable i = getTransitionIndex!(SearchPolicy.binarySearch, geq)(value); + if (i >= length) return false; + return !predFun(value, _input[i]); + } + +// groupBy +/** +Returns a range of subranges of elements that are equivalent according to the +sorting relation. + */ + auto groupBy()() + { + import std.algorithm.iteration : chunkBy; + return _input.chunkBy!((a, b) => !predFun(a, b) && !predFun(b, a)); + } +} + +/// +@safe unittest +{ + import std.algorithm.sorting : sort; + auto a = [ 1, 2, 3, 42, 52, 64 ]; + auto r = assumeSorted(a); + assert(r.contains(3)); + assert(!r.contains(32)); + auto r1 = sort!"a > b"(a); + assert(r1.contains(3)); + assert(!r1.contains(32)); + assert(r1.release() == [ 64, 52, 42, 3, 2, 1 ]); +} + +/** +$(D SortedRange) could accept ranges weaker than random-access, but it +is unable to provide interesting functionality for them. Therefore, +$(D SortedRange) is currently restricted to random-access ranges. + +No copy of the original range is ever made. If the underlying range is +changed concurrently with its corresponding $(D SortedRange) in ways +that break its sorted-ness, $(D SortedRange) will work erratically. +*/ +@safe unittest +{ + import std.algorithm.mutation : swap; + auto a = [ 1, 2, 3, 42, 52, 64 ]; + auto r = assumeSorted(a); + assert(r.contains(42)); + swap(a[3], a[5]); // illegal to break sortedness of original range + assert(!r.contains(42)); // passes although it shouldn't +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + auto a = [ 10, 20, 30, 30, 30, 40, 40, 50, 60 ]; + auto r = assumeSorted(a).trisect(30); + assert(equal(r[0], [ 10, 20 ])); + assert(equal(r[1], [ 30, 30, 30 ])); + assert(equal(r[2], [ 40, 40, 50, 60 ])); + + r = assumeSorted(a).trisect(35); + assert(equal(r[0], [ 10, 20, 30, 30, 30 ])); + assert(r[1].empty); + assert(equal(r[2], [ 40, 40, 50, 60 ])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + auto a = [ "A", "AG", "B", "E", "F" ]; + auto r = assumeSorted!"cmp(a,b) < 0"(a).trisect("B"w); + assert(equal(r[0], [ "A", "AG" ])); + assert(equal(r[1], [ "B" ])); + assert(equal(r[2], [ "E", "F" ])); + r = assumeSorted!"cmp(a,b) < 0"(a).trisect("A"d); + assert(r[0].empty); + assert(equal(r[1], [ "A" ])); + assert(equal(r[2], [ "AG", "B", "E", "F" ])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + static void test(SearchPolicy pol)() + { + auto a = [ 1, 2, 3, 42, 52, 64 ]; + auto r = assumeSorted(a); + assert(equal(r.lowerBound(42), [1, 2, 3])); + + assert(equal(r.lowerBound!(pol)(42), [1, 2, 3])); + assert(equal(r.lowerBound!(pol)(41), [1, 2, 3])); + assert(equal(r.lowerBound!(pol)(43), [1, 2, 3, 42])); + assert(equal(r.lowerBound!(pol)(51), [1, 2, 3, 42])); + assert(equal(r.lowerBound!(pol)(3), [1, 2])); + assert(equal(r.lowerBound!(pol)(55), [1, 2, 3, 42, 52])); + assert(equal(r.lowerBound!(pol)(420), a)); + assert(equal(r.lowerBound!(pol)(0), a[0 .. 0])); + + assert(equal(r.upperBound!(pol)(42), [52, 64])); + assert(equal(r.upperBound!(pol)(41), [42, 52, 64])); + assert(equal(r.upperBound!(pol)(43), [52, 64])); + assert(equal(r.upperBound!(pol)(51), [52, 64])); + assert(equal(r.upperBound!(pol)(53), [64])); + assert(equal(r.upperBound!(pol)(55), [64])); + assert(equal(r.upperBound!(pol)(420), a[0 .. 0])); + assert(equal(r.upperBound!(pol)(0), a)); + } + + test!(SearchPolicy.trot)(); + test!(SearchPolicy.gallop)(); + test!(SearchPolicy.trotBackwards)(); + test!(SearchPolicy.gallopBackwards)(); + test!(SearchPolicy.binarySearch)(); +} + +@safe unittest +{ + // Check for small arrays + int[] a; + auto r = assumeSorted(a); + a = [ 1 ]; + r = assumeSorted(a); + a = [ 1, 2 ]; + r = assumeSorted(a); + a = [ 1, 2, 3 ]; + r = assumeSorted(a); +} + +@safe unittest +{ + import std.algorithm.mutation : swap; + auto a = [ 1, 2, 3, 42, 52, 64 ]; + auto r = assumeSorted(a); + assert(r.contains(42)); + swap(a[3], a[5]); // illegal to break sortedness of original range + assert(!r.contains(42)); // passes although it shouldn't +} + +@safe unittest +{ + immutable(int)[] arr = [ 1, 2, 3 ]; + auto s = assumeSorted(arr); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + int[] arr = [100, 101, 102, 200, 201, 300]; + auto s = assumeSorted!((a, b) => a / 100 < b / 100)(arr); + assert(s.groupBy.equal!equal([[100, 101, 102], [200, 201], [300]])); +} + +// Test on an input range +@system unittest +{ + import std.conv : text; + import std.file : exists, remove, tempDir; + import std.path : buildPath; + import std.stdio : File; + import std.uuid : randomUUID; + auto name = buildPath(tempDir(), "test.std.range.line-" ~ text(__LINE__) ~ + "." ~ randomUUID().toString()); + auto f = File(name, "w"); + scope(exit) if (exists(name)) remove(name); + // write a sorted range of lines to the file + f.write("abc\ndef\nghi\njkl"); + f.close(); + f.open(name, "r"); + auto r = assumeSorted(f.byLine()); + auto r1 = r.upperBound!(SearchPolicy.linear)("def"); + assert(r1.front == "ghi", r1.front); + f.close(); +} + +/** +Assumes $(D r) is sorted by predicate $(D pred) and returns the +corresponding $(D SortedRange!(pred, R)) having $(D r) as support. To +keep the checking costs low, the cost is $(BIGOH 1) in release mode +(no checks for sorted-ness are performed). In debug mode, a few random +elements of $(D r) are checked for sorted-ness. The size of the sample +is proportional $(BIGOH log(r.length)). That way, checking has no +effect on the complexity of subsequent operations specific to sorted +ranges (such as binary search). The probability of an arbitrary +unsorted range failing the test is very high (however, an +almost-sorted range is likely to pass it). To check for sorted-ness at +cost $(BIGOH n), use $(REF isSorted, std,algorithm,sorting). + */ +auto assumeSorted(alias pred = "a < b", R)(R r) +if (isInputRange!(Unqual!R)) +{ + return SortedRange!(Unqual!R, pred)(r); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + static assert(isRandomAccessRange!(SortedRange!(int[]))); + int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]; + auto p = assumeSorted(a).lowerBound(4); + assert(equal(p, [0, 1, 2, 3])); + p = assumeSorted(a).lowerBound(5); + assert(equal(p, [0, 1, 2, 3, 4])); + p = assumeSorted(a).lowerBound(6); + assert(equal(p, [ 0, 1, 2, 3, 4, 5])); + p = assumeSorted(a).lowerBound(6.9); + assert(equal(p, [ 0, 1, 2, 3, 4, 5, 6])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + int[] a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]; + auto p = assumeSorted(a).upperBound(3); + assert(equal(p, [4, 4, 5, 6 ])); + p = assumeSorted(a).upperBound(4.2); + assert(equal(p, [ 5, 6 ])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.conv : text; + + int[] a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]; + auto p = assumeSorted(a).equalRange(3); + assert(equal(p, [ 3, 3, 3 ]), text(p)); + p = assumeSorted(a).equalRange(4); + assert(equal(p, [ 4, 4 ]), text(p)); + p = assumeSorted(a).equalRange(2); + assert(equal(p, [ 2 ])); + p = assumeSorted(a).equalRange(0); + assert(p.empty); + p = assumeSorted(a).equalRange(7); + assert(p.empty); + p = assumeSorted(a).equalRange(3.0); + assert(equal(p, [ 3, 3, 3])); +} + +@safe unittest +{ + int[] a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]; + if (a.length) + { + auto b = a[a.length / 2]; + //auto r = sort(a); + //assert(r.contains(b)); + } +} + +@safe unittest +{ + auto a = [ 5, 7, 34, 345, 677 ]; + auto r = assumeSorted(a); + a = null; + r = assumeSorted(a); + a = [ 1 ]; + r = assumeSorted(a); +} + +@system unittest +{ + bool ok = true; + try + { + auto r2 = assumeSorted([ 677, 345, 34, 7, 5 ]); + debug ok = false; + } + catch (Throwable) + { + } + assert(ok); +} + +// issue 15003 +@nogc @safe unittest +{ + static immutable a = [1, 2, 3, 4]; + auto r = a.assumeSorted; +} + +/++ + Wrapper which effectively makes it possible to pass a range by reference. + Both the original range and the RefRange will always have the exact same + elements. Any operation done on one will affect the other. So, for instance, + if it's passed to a function which would implicitly copy the original range + if it were passed to it, the original range is $(I not) copied but is + consumed as if it were a reference type. + + Note: + `save` works as normal and operates on a new _range, so if + `save` is ever called on the `RefRange`, then no operations on the + saved _range will affect the original. + + Params: + range = the range to construct the `RefRange` from + + Returns: + A `RefRange`. If the given _range is a class type + (and thus is already a reference type), then the original + range is returned rather than a `RefRange`. + +/ +struct RefRange(R) +if (isInputRange!R) +{ +public: + + /++ +/ + this(R* range) @safe pure nothrow + { + _range = range; + } + + + /++ + This does not assign the pointer of $(D rhs) to this $(D RefRange). + Rather it assigns the range pointed to by $(D rhs) to the range pointed + to by this $(D RefRange). This is because $(I any) operation on a + $(D RefRange) is the same is if it occurred to the original range. The + one exception is when a $(D RefRange) is assigned $(D null) either + directly or because $(D rhs) is $(D null). In that case, $(D RefRange) + no longer refers to the original range but is $(D null). + +/ + auto opAssign(RefRange rhs) + { + if (_range && rhs._range) + *_range = *rhs._range; + else + _range = rhs._range; + + return this; + } + + /++ +/ + void opAssign(typeof(null) rhs) + { + _range = null; + } + + + /++ + A pointer to the wrapped range. + +/ + @property inout(R*) ptr() @safe inout pure nothrow + { + return _range; + } + + + version (StdDdoc) + { + /++ +/ + @property auto front() {assert(0);} + /++ Ditto +/ + @property auto front() const {assert(0);} + /++ Ditto +/ + @property auto front(ElementType!R value) {assert(0);} + } + else + { + @property auto front() + { + return (*_range).front; + } + + static if (is(typeof((*(cast(const R*)_range)).front))) @property auto front() const + { + return (*_range).front; + } + + static if (is(typeof((*_range).front = (*_range).front))) @property auto front(ElementType!R value) + { + return (*_range).front = value; + } + } + + + version (StdDdoc) + { + @property bool empty(); /// + @property bool empty() const; ///Ditto + } + else static if (isInfinite!R) + enum empty = false; + else + { + @property bool empty() + { + return (*_range).empty; + } + + static if (is(typeof((*cast(const R*)_range).empty))) @property bool empty() const + { + return (*_range).empty; + } + } + + + /++ +/ + void popFront() + { + return (*_range).popFront(); + } + + + version (StdDdoc) + { + /++ + Only defined if $(D isForwardRange!R) is $(D true). + +/ + @property auto save() {assert(0);} + /++ Ditto +/ + @property auto save() const {assert(0);} + /++ Ditto +/ + auto opSlice() {assert(0);} + /++ Ditto +/ + auto opSlice() const {assert(0);} + } + else static if (isForwardRange!R) + { + import std.traits : isSafe; + private alias S = typeof((*_range).save); + + static if (is(typeof((*cast(const R*)_range).save))) + private alias CS = typeof((*cast(const R*)_range).save); + + static if (isSafe!((R* r) => (*r).save)) + { + @property RefRange!S save() @trusted + { + mixin(_genSave()); + } + + static if (is(typeof((*cast(const R*)_range).save))) @property RefRange!CS save() @trusted const + { + mixin(_genSave()); + } + } + else + { + @property RefRange!S save() + { + mixin(_genSave()); + } + + static if (is(typeof((*cast(const R*)_range).save))) @property RefRange!CS save() const + { + mixin(_genSave()); + } + } + + auto opSlice()() + { + return save; + } + + auto opSlice()() const + { + return save; + } + + private static string _genSave() @safe pure nothrow + { + return `import std.conv : emplace;` ~ + `alias S = typeof((*_range).save);` ~ + `static assert(isForwardRange!S, S.stringof ~ " is not a forward range.");` ~ + `auto mem = new void[S.sizeof];` ~ + `emplace!S(mem, cast(S)(*_range).save);` ~ + `return RefRange!S(cast(S*) mem.ptr);`; + } + + static assert(isForwardRange!RefRange); + } + + + version (StdDdoc) + { + /++ + Only defined if $(D isBidirectionalRange!R) is $(D true). + +/ + @property auto back() {assert(0);} + /++ Ditto +/ + @property auto back() const {assert(0);} + /++ Ditto +/ + @property auto back(ElementType!R value) {assert(0);} + } + else static if (isBidirectionalRange!R) + { + @property auto back() + { + return (*_range).back; + } + + static if (is(typeof((*(cast(const R*)_range)).back))) @property auto back() const + { + return (*_range).back; + } + + static if (is(typeof((*_range).back = (*_range).back))) @property auto back(ElementType!R value) + { + return (*_range).back = value; + } + } + + + /++ Ditto +/ + static if (isBidirectionalRange!R) void popBack() + { + return (*_range).popBack(); + } + + + version (StdDdoc) + { + /++ + Only defined if $(D isRandomAccesRange!R) is $(D true). + +/ + auto ref opIndex(IndexType)(IndexType index) {assert(0);} + + /++ Ditto +/ + auto ref opIndex(IndexType)(IndexType index) const {assert(0);} + } + else static if (isRandomAccessRange!R) + { + auto ref opIndex(IndexType)(IndexType index) + if (is(typeof((*_range)[index]))) + { + return (*_range)[index]; + } + + auto ref opIndex(IndexType)(IndexType index) const + if (is(typeof((*cast(const R*)_range)[index]))) + { + return (*_range)[index]; + } + } + + + /++ + Only defined if $(D hasMobileElements!R) and $(D isForwardRange!R) are + $(D true). + +/ + static if (hasMobileElements!R && isForwardRange!R) auto moveFront() + { + return (*_range).moveFront(); + } + + + /++ + Only defined if $(D hasMobileElements!R) and $(D isBidirectionalRange!R) + are $(D true). + +/ + static if (hasMobileElements!R && isBidirectionalRange!R) auto moveBack() + { + return (*_range).moveBack(); + } + + + /++ + Only defined if $(D hasMobileElements!R) and $(D isRandomAccessRange!R) + are $(D true). + +/ + static if (hasMobileElements!R && isRandomAccessRange!R) auto moveAt(size_t index) + { + return (*_range).moveAt(index); + } + + + version (StdDdoc) + { + /++ + Only defined if $(D hasLength!R) is $(D true). + +/ + @property auto length() {assert(0);} + + /++ Ditto +/ + @property auto length() const {assert(0);} + + /++ Ditto +/ + alias opDollar = length; + } + else static if (hasLength!R) + { + @property auto length() + { + return (*_range).length; + } + + static if (is(typeof((*cast(const R*)_range).length))) @property auto length() const + { + return (*_range).length; + } + + alias opDollar = length; + } + + + version (StdDdoc) + { + /++ + Only defined if $(D hasSlicing!R) is $(D true). + +/ + auto opSlice(IndexType1, IndexType2) + (IndexType1 begin, IndexType2 end) {assert(0);} + + /++ Ditto +/ + auto opSlice(IndexType1, IndexType2) + (IndexType1 begin, IndexType2 end) const {assert(0);} + } + else static if (hasSlicing!R) + { + private alias T = typeof((*_range)[1 .. 2]); + static if (is(typeof((*cast(const R*)_range)[1 .. 2]))) + { + private alias CT = typeof((*cast(const R*)_range)[1 .. 2]); + } + + RefRange!T opSlice(IndexType1, IndexType2) + (IndexType1 begin, IndexType2 end) + if (is(typeof((*_range)[begin .. end]))) + { + mixin(_genOpSlice()); + } + + RefRange!CT opSlice(IndexType1, IndexType2) + (IndexType1 begin, IndexType2 end) const + if (is(typeof((*cast(const R*)_range)[begin .. end]))) + { + mixin(_genOpSlice()); + } + + private static string _genOpSlice() @safe pure nothrow + { + return `import std.conv : emplace;` ~ + `alias S = typeof((*_range)[begin .. end]);` ~ + `static assert(hasSlicing!S, S.stringof ~ " is not sliceable.");` ~ + `auto mem = new void[S.sizeof];` ~ + `emplace!S(mem, cast(S)(*_range)[begin .. end]);` ~ + `return RefRange!S(cast(S*) mem.ptr);`; + } + } + + +private: + + R* _range; +} + +/// Basic Example +@system unittest +{ + import std.algorithm.searching : find; + ubyte[] buffer = [1, 9, 45, 12, 22]; + auto found1 = find(buffer, 45); + assert(found1 == [45, 12, 22]); + assert(buffer == [1, 9, 45, 12, 22]); + + auto wrapped1 = refRange(&buffer); + auto found2 = find(wrapped1, 45); + assert(*found2.ptr == [45, 12, 22]); + assert(buffer == [45, 12, 22]); + + auto found3 = find(wrapped1.save, 22); + assert(*found3.ptr == [22]); + assert(buffer == [45, 12, 22]); + + string str = "hello world"; + auto wrappedStr = refRange(&str); + assert(str.front == 'h'); + str.popFrontN(5); + assert(str == " world"); + assert(wrappedStr.front == ' '); + assert(*wrappedStr.ptr == " world"); +} + +/// opAssign Example. +@system unittest +{ + ubyte[] buffer1 = [1, 2, 3, 4, 5]; + ubyte[] buffer2 = [6, 7, 8, 9, 10]; + auto wrapped1 = refRange(&buffer1); + auto wrapped2 = refRange(&buffer2); + assert(wrapped1.ptr is &buffer1); + assert(wrapped2.ptr is &buffer2); + assert(wrapped1.ptr !is wrapped2.ptr); + assert(buffer1 != buffer2); + + wrapped1 = wrapped2; + + //Everything points to the same stuff as before. + assert(wrapped1.ptr is &buffer1); + assert(wrapped2.ptr is &buffer2); + assert(wrapped1.ptr !is wrapped2.ptr); + + //But buffer1 has changed due to the assignment. + assert(buffer1 == [6, 7, 8, 9, 10]); + assert(buffer2 == [6, 7, 8, 9, 10]); + + buffer2 = [11, 12, 13, 14, 15]; + + //Everything points to the same stuff as before. + assert(wrapped1.ptr is &buffer1); + assert(wrapped2.ptr is &buffer2); + assert(wrapped1.ptr !is wrapped2.ptr); + + //But buffer2 has changed due to the assignment. + assert(buffer1 == [6, 7, 8, 9, 10]); + assert(buffer2 == [11, 12, 13, 14, 15]); + + wrapped2 = null; + + //The pointer changed for wrapped2 but not wrapped1. + assert(wrapped1.ptr is &buffer1); + assert(wrapped2.ptr is null); + assert(wrapped1.ptr !is wrapped2.ptr); + + //buffer2 is not affected by the assignment. + assert(buffer1 == [6, 7, 8, 9, 10]); + assert(buffer2 == [11, 12, 13, 14, 15]); +} + +@system unittest +{ + import std.algorithm.iteration : filter; + { + ubyte[] buffer = [1, 2, 3, 4, 5]; + auto wrapper = refRange(&buffer); + auto p = wrapper.ptr; + auto f = wrapper.front; + wrapper.front = f; + auto e = wrapper.empty; + wrapper.popFront(); + auto s = wrapper.save; + auto b = wrapper.back; + wrapper.back = b; + wrapper.popBack(); + auto i = wrapper[0]; + wrapper.moveFront(); + wrapper.moveBack(); + wrapper.moveAt(0); + auto l = wrapper.length; + auto sl = wrapper[0 .. 1]; + assert(wrapper[0 .. $].length == buffer[0 .. $].length); + } + + { + ubyte[] buffer = [1, 2, 3, 4, 5]; + const wrapper = refRange(&buffer); + const p = wrapper.ptr; + const f = wrapper.front; + const e = wrapper.empty; + const s = wrapper.save; + const b = wrapper.back; + const i = wrapper[0]; + const l = wrapper.length; + const sl = wrapper[0 .. 1]; + } + + { + ubyte[] buffer = [1, 2, 3, 4, 5]; + auto filtered = filter!"true"(buffer); + auto wrapper = refRange(&filtered); + auto p = wrapper.ptr; + auto f = wrapper.front; + wrapper.front = f; + auto e = wrapper.empty; + wrapper.popFront(); + auto s = wrapper.save; + wrapper.moveFront(); + } + + { + ubyte[] buffer = [1, 2, 3, 4, 5]; + auto filtered = filter!"true"(buffer); + const wrapper = refRange(&filtered); + const p = wrapper.ptr; + + //Cannot currently be const. filter needs to be updated to handle const. + /+ + const f = wrapper.front; + const e = wrapper.empty; + const s = wrapper.save; + +/ + } + + { + string str = "hello world"; + auto wrapper = refRange(&str); + auto p = wrapper.ptr; + auto f = wrapper.front; + auto e = wrapper.empty; + wrapper.popFront(); + auto s = wrapper.save; + auto b = wrapper.back; + wrapper.popBack(); + } + + { + // Issue 16534 - opDollar should be defined if the + // wrapped range defines length. + auto range = 10.iota.takeExactly(5); + auto wrapper = refRange(&range); + assert(wrapper.length == 5); + assert(wrapper[0 .. $ - 1].length == 4); + } +} + +//Test assignment. +@system unittest +{ + ubyte[] buffer1 = [1, 2, 3, 4, 5]; + ubyte[] buffer2 = [6, 7, 8, 9, 10]; + RefRange!(ubyte[]) wrapper1; + RefRange!(ubyte[]) wrapper2 = refRange(&buffer2); + assert(wrapper1.ptr is null); + assert(wrapper2.ptr is &buffer2); + + wrapper1 = refRange(&buffer1); + assert(wrapper1.ptr is &buffer1); + + wrapper1 = wrapper2; + assert(wrapper1.ptr is &buffer1); + assert(buffer1 == buffer2); + + wrapper1 = RefRange!(ubyte[]).init; + assert(wrapper1.ptr is null); + assert(wrapper2.ptr is &buffer2); + assert(buffer1 == buffer2); + assert(buffer1 == [6, 7, 8, 9, 10]); + + wrapper2 = null; + assert(wrapper2.ptr is null); + assert(buffer2 == [6, 7, 8, 9, 10]); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.mutation : bringToFront; + import std.algorithm.searching : commonPrefix, find, until; + import std.algorithm.sorting : sort; + + //Test that ranges are properly consumed. + { + int[] arr = [1, 42, 2, 41, 3, 40, 4, 42, 9]; + auto wrapper = refRange(&arr); + + assert(*find(wrapper, 41).ptr == [41, 3, 40, 4, 42, 9]); + assert(arr == [41, 3, 40, 4, 42, 9]); + + assert(*drop(wrapper, 2).ptr == [40, 4, 42, 9]); + assert(arr == [40, 4, 42, 9]); + + assert(equal(until(wrapper, 42), [40, 4])); + assert(arr == [42, 9]); + + assert(find(wrapper, 12).empty); + assert(arr.empty); + } + + { + string str = "Hello, world-like object."; + auto wrapper = refRange(&str); + + assert(*find(wrapper, "l").ptr == "llo, world-like object."); + assert(str == "llo, world-like object."); + + assert(equal(take(wrapper, 5), "llo, ")); + assert(str == "world-like object."); + } + + //Test that operating on saved ranges does not consume the original. + { + int[] arr = [1, 42, 2, 41, 3, 40, 4, 42, 9]; + auto wrapper = refRange(&arr); + auto saved = wrapper.save; + saved.popFrontN(3); + assert(*saved.ptr == [41, 3, 40, 4, 42, 9]); + assert(arr == [1, 42, 2, 41, 3, 40, 4, 42, 9]); + } + + { + string str = "Hello, world-like object."; + auto wrapper = refRange(&str); + auto saved = wrapper.save; + saved.popFrontN(13); + assert(*saved.ptr == "like object."); + assert(str == "Hello, world-like object."); + } + + //Test that functions which use save work properly. + { + int[] arr = [1, 42]; + auto wrapper = refRange(&arr); + assert(equal(commonPrefix(wrapper, [1, 27]), [1])); + } + + { + int[] arr = [4, 5, 6, 7, 1, 2, 3]; + auto wrapper = refRange(&arr); + assert(bringToFront(wrapper[0 .. 4], wrapper[4 .. arr.length]) == 3); + assert(arr == [1, 2, 3, 4, 5, 6, 7]); + } + + //Test bidirectional functions. + { + int[] arr = [1, 42, 2, 41, 3, 40, 4, 42, 9]; + auto wrapper = refRange(&arr); + + assert(wrapper.back == 9); + assert(arr == [1, 42, 2, 41, 3, 40, 4, 42, 9]); + + wrapper.popBack(); + assert(arr == [1, 42, 2, 41, 3, 40, 4, 42]); + } + + { + string str = "Hello, world-like object."; + auto wrapper = refRange(&str); + + assert(wrapper.back == '.'); + assert(str == "Hello, world-like object."); + + wrapper.popBack(); + assert(str == "Hello, world-like object"); + } + + //Test random access functions. + { + int[] arr = [1, 42, 2, 41, 3, 40, 4, 42, 9]; + auto wrapper = refRange(&arr); + + assert(wrapper[2] == 2); + assert(arr == [1, 42, 2, 41, 3, 40, 4, 42, 9]); + + assert(*wrapper[3 .. 6].ptr != null, [41, 3, 40]); + assert(arr == [1, 42, 2, 41, 3, 40, 4, 42, 9]); + } + + //Test move functions. + { + int[] arr = [1, 42, 2, 41, 3, 40, 4, 42, 9]; + auto wrapper = refRange(&arr); + + auto t1 = wrapper.moveFront(); + auto t2 = wrapper.moveBack(); + wrapper.front = t2; + wrapper.back = t1; + assert(arr == [9, 42, 2, 41, 3, 40, 4, 42, 1]); + + sort(wrapper.save); + assert(arr == [1, 2, 3, 4, 9, 40, 41, 42, 42]); + } +} + +@system unittest +{ + struct S + { + @property int front() @safe const pure nothrow { return 0; } + enum bool empty = false; + void popFront() @safe pure nothrow { } + @property auto save() @safe pure nothrow { return this; } + } + + S s; + auto wrapper = refRange(&s); + static assert(isInfinite!(typeof(wrapper))); +} + +@system unittest +{ + class C + { + @property int front() @safe const pure nothrow { return 0; } + @property bool empty() @safe const pure nothrow { return false; } + void popFront() @safe pure nothrow { } + @property auto save() @safe pure nothrow { return this; } + } + static assert(isForwardRange!C); + + auto c = new C; + auto cWrapper = refRange(&c); + static assert(is(typeof(cWrapper) == C)); + assert(cWrapper is c); +} + +@system unittest // issue 14373 +{ + static struct R + { + @property int front() {return 0;} + void popFront() {empty = true;} + bool empty = false; + } + R r; + refRange(&r).popFront(); + assert(r.empty); +} + +@system unittest // issue 14575 +{ + struct R + { + Object front; + alias back = front; + bool empty = false; + void popFront() {empty = true;} + alias popBack = popFront; + @property R save() {return this;} + } + static assert(isBidirectionalRange!R); + R r; + auto rr = refRange(&r); + + struct R2 + { + @property Object front() {return null;} + @property const(Object) front() const {return null;} + alias back = front; + bool empty = false; + void popFront() {empty = true;} + alias popBack = popFront; + @property R2 save() {return this;} + } + static assert(isBidirectionalRange!R2); + R2 r2; + auto rr2 = refRange(&r2); +} + +/// ditto +auto refRange(R)(R* range) +if (isInputRange!R) +{ + static if (!is(R == class)) + return RefRange!R(range); + else + return *range; +} + +/*****************************************************************************/ + +@safe unittest // bug 9060 +{ + import std.algorithm.iteration : map, joiner, group; + import std.algorithm.searching : until; + // fix for std.algorithm + auto r = map!(x => 0)([1]); + chain(r, r); + zip(r, r); + roundRobin(r, r); + + struct NRAR { + typeof(r) input; + @property empty() { return input.empty; } + @property front() { return input.front; } + void popFront() { input.popFront(); } + @property save() { return NRAR(input.save); } + } + auto n1 = NRAR(r); + cycle(n1); // non random access range version + + assumeSorted(r); + + // fix for std.range + joiner([r], [9]); + + struct NRAR2 { + NRAR input; + @property empty() { return true; } + @property front() { return input; } + void popFront() { } + @property save() { return NRAR2(input.save); } + } + auto n2 = NRAR2(n1); + joiner(n2); + + group(r); + + until(r, 7); + static void foo(R)(R r) { until!(x => x > 7)(r); } + foo(r); +} + +private struct Bitwise(R) +if (isInputRange!R && isIntegral!(ElementType!R)) +{ +private: + alias ElemType = ElementType!R; + alias UnsignedElemType = Unsigned!ElemType; + + R parent; + enum bitsNum = ElemType.sizeof * 8; + size_t maskPos = 1; + + static if (isBidirectionalRange!R) + { + size_t backMaskPos = bitsNum; + } + +public: + this()(auto ref R range) + { + parent = range; + } + + static if (isInfinite!R) + { + enum empty = false; + } + else + { + /** + * Check if the range is empty + * + * Returns: a boolean true or false + */ + bool empty() + { + static if (hasLength!R) + { + return length == 0; + } + else static if (isBidirectionalRange!R) + { + if (parent.empty) + { + return true; + } + else + { + /* + If we have consumed the last element of the range both from + the front and the back, then the masks positions will overlap + */ + return parent.save.dropOne.empty && (maskPos > backMaskPos); + } + } + else + { + /* + If we consumed the last element of the range, but not all the + bits in the last element + */ + return parent.empty; + } + } + } + + bool front() + { + assert(!empty); + return (parent.front & mask(maskPos)) != 0; + } + + void popFront() + { + assert(!empty); + ++maskPos; + if (maskPos > bitsNum) + { + parent.popFront; + maskPos = 1; + } + } + + static if (hasLength!R) + { + size_t length() + { + auto len = parent.length * bitsNum - (maskPos - 1); + static if (isBidirectionalRange!R) + { + len -= bitsNum - backMaskPos; + } + return len; + } + + alias opDollar = length; + } + + static if (isForwardRange!R) + { + typeof(this) save() + { + auto result = this; + result.parent = parent.save; + return result; + } + } + + static if (isBidirectionalRange!R) + { + bool back() + { + assert(!empty); + return (parent.back & mask(backMaskPos)) != 0; + } + + void popBack() + { + assert(!empty); + --backMaskPos; + if (backMaskPos == 0) + { + parent.popBack; + backMaskPos = bitsNum; + } + } + } + + static if (isRandomAccessRange!R) + { + /** + Return the `n`th bit within the range + */ + bool opIndex(size_t n) + in + { + /* + If it does not have the length property, it means that R is + an infinite range + */ + static if (hasLength!R) + { + assert(n < length, "Index out of bounds"); + } + } + body + { + immutable size_t remainingBits = bitsNum - maskPos + 1; + // If n >= maskPos, then the bit sign will be 1, otherwise 0 + immutable sizediff_t sign = (remainingBits - n - 1) >> (sizediff_t.sizeof * 8 - 1); + /* + By truncating n with remainingBits bits we have skipped the + remaining bits in parent[0], so we need to add 1 to elemIndex. + + Because bitsNum is a power of 2, n / bitsNum == n >> bitsNum.bsf + */ + import core.bitop : bsf; + immutable size_t elemIndex = sign * (((n - remainingBits) >> bitsNum.bsf) + 1); + + /* + Since the indexing is from LSB to MSB, we need to index at the + remainder of (n - remainingBits). + + Because bitsNum is a power of 2, n % bitsNum == n & (bitsNum - 1) + */ + immutable size_t elemMaskPos = (sign ^ 1) * (maskPos + n) + + sign * (1 + ((n - remainingBits) & (bitsNum - 1))); + + return (parent[elemIndex] & mask(elemMaskPos)) != 0; + } + + static if (hasAssignableElements!R) + { + /** + Assigns `flag` to the `n`th bit within the range + */ + void opIndexAssign(bool flag, size_t n) + in + { + static if (hasLength!R) + { + assert(n < length, "Index out of bounds"); + } + } + body + { + import core.bitop : bsf; + + immutable size_t remainingBits = bitsNum - maskPos + 1; + immutable sizediff_t sign = (remainingBits - n - 1) >> (sizediff_t.sizeof * 8 - 1); + immutable size_t elemIndex = sign * (((n - remainingBits) >> bitsNum.bsf) + 1); + immutable size_t elemMaskPos = (sign ^ 1) * (maskPos + n) + + sign * (1 + ((n - remainingBits) & (bitsNum - 1))); + + auto elem = parent[elemIndex]; + auto elemMask = mask(elemMaskPos); + parent[elemIndex] = cast(UnsignedElemType)(flag * (elem | elemMask) + + (flag ^ 1) * (elem & ~elemMask)); + } + } + + Bitwise!R opSlice() + { + return this.save; + } + + Bitwise!R opSlice(size_t start, size_t end) + in + { + assert(start < end, "Invalid bounds: end <= start"); + } + body + { + import core.bitop : bsf; + + size_t remainingBits = bitsNum - maskPos + 1; + sizediff_t sign = (remainingBits - start - 1) >> (sizediff_t.sizeof * 8 - 1); + immutable size_t startElemIndex = sign * (((start - remainingBits) >> bitsNum.bsf) + 1); + immutable size_t startElemMaskPos = (sign ^ 1) * (maskPos + start) + + sign * (1 + ((start - remainingBits) & (bitsNum - 1))); + + immutable size_t sliceLen = end - start - 1; + remainingBits = bitsNum - startElemMaskPos + 1; + sign = (remainingBits - sliceLen - 1) >> (sizediff_t.sizeof * 8 - 1); + immutable size_t endElemIndex = startElemIndex + + sign * (((sliceLen - remainingBits) >> bitsNum.bsf) + 1); + immutable size_t endElemMaskPos = (sign ^ 1) * (startElemMaskPos + sliceLen) + + sign * (1 + ((sliceLen - remainingBits) & (bitsNum - 1))); + + typeof(return) result; + // Get the slice to be returned from the parent + result.parent = (parent[startElemIndex .. endElemIndex + 1]).save; + result.maskPos = startElemMaskPos; + static if (isBidirectionalRange!R) + { + result.backMaskPos = endElemMaskPos; + } + return result; + } + } + +private: + auto mask(size_t maskPos) + { + return (1UL << (maskPos - 1UL)); + } +} + +/** +Bitwise adapter over an integral type range. Consumes the range elements bit by +bit, from the least significant bit to the most significant bit. + +Params: + R = an integral input range to iterate over + range = range to consume bit by by + +Returns: + A `Bitwise` input range with propagated forward, bidirectional + and random access capabilities +*/ +auto bitwise(R)(auto ref R range) +if (isInputRange!R && isIntegral!(ElementType!R)) +{ + return Bitwise!R(range); +} + +/// +@safe pure unittest +{ + import std.algorithm.comparison : equal; + import std.format : format; + + // 00000011 00001001 + ubyte[] arr = [3, 9]; + auto r = arr.bitwise; + + // iterate through it as with any other range + assert(format("%(%d%)", r) == "1100000010010000"); + assert(format("%(%d%)", r.retro).equal("1100000010010000".retro)); + + auto r2 = r[5 .. $]; + // set a bit + r[2] = 1; + assert(arr[0] == 7); + assert(r[5] == r2[0]); +} + +/// You can use bitwise to implement an uniform bool generator +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.random : rndGen; + + auto rb = rndGen.bitwise; + static assert(isInfinite!(typeof(rb))); + + auto rb2 = rndGen.bitwise; + // Don't forget that structs are passed by value + assert(rb.take(10).equal(rb2.take(10))); +} + +// Test nogc inference +@safe @nogc unittest +{ + static ubyte[] arr = [3, 9]; + auto bw = arr.bitwise; + auto bw2 = bw[]; + auto bw3 = bw[8 .. $]; + bw3[2] = true; + + assert(arr[1] == 13); + assert(bw[$ - 6]); + assert(bw[$ - 6] == bw2[$ - 6]); + assert(bw[$ - 6] == bw3[$ - 6]); +} + +// Test all range types over all integral types +@safe pure nothrow unittest +{ + import std.internal.test.dummyrange; + + alias IntegralTypes = AliasSeq!(byte, ubyte, short, ushort, int, uint, + long, ulong); + foreach (IntegralType; IntegralTypes) + { + foreach (T; AllDummyRangesType!(IntegralType[])) + { + T a; + auto bw = Bitwise!T(a); + + static if (isForwardRange!T) + { + auto bwFwdSave = bw.save; + } + + static if (isBidirectionalRange!T) + { + auto bwBack = bw.save; + auto bwBackSave = bw.save; + } + + static if (hasLength!T) + { + auto bwLength = bw.length; + assert(bw.length == (IntegralType.sizeof * 8 * a.length)); + static if (isForwardRange!T) + { + assert(bw.length == bwFwdSave.length); + } + } + + // Make sure front and back are not the mechanisms that modify the range + long numCalls = 42; + bool initialFrontValue; + + if (!bw.empty) + { + initialFrontValue = bw.front; + } + + while (!bw.empty && (--numCalls)) + { + bw.front; + assert(bw.front == initialFrontValue); + } + + /* + Check that empty works properly and that popFront does not get called + more times than it should + */ + numCalls = 0; + while (!bw.empty) + { + ++numCalls; + + static if (hasLength!T) + { + assert(bw.length == bwLength); + --bwLength; + } + + static if (isForwardRange!T) + { + assert(bw.front == bwFwdSave.front); + bwFwdSave.popFront(); + } + + static if (isBidirectionalRange!T) + { + assert(bwBack.front == bwBackSave.front); + bwBack.popBack(); + bwBackSave.popBack(); + } + bw.popFront(); + } + + auto rangeLen = numCalls / (IntegralType.sizeof * 8); + assert(numCalls == (IntegralType.sizeof * 8 * rangeLen)); + assert(bw.empty); + static if (isForwardRange!T) + { + assert(bwFwdSave.empty); + } + + static if (isBidirectionalRange!T) + { + assert(bwBack.empty); + } + } + } +} + +// Test opIndex and opSlice +@system unittest +{ + alias IntegralTypes = AliasSeq!(byte, ubyte, short, ushort, int, uint, + long, ulong); + foreach (IntegralType; IntegralTypes) + { + size_t bitsNum = IntegralType.sizeof * 8; + + auto first = cast(IntegralType)(1); + + // 2 ^ (bitsNum - 1) + auto second = cast(IntegralType)(cast(IntegralType)(1) << (bitsNum - 2)); + + IntegralType[] a = [first, second]; + auto bw = Bitwise!(IntegralType[])(a); + + // Check against lsb of a[0] + assert(bw[0] == true); + // Check against msb - 1 of a[1] + assert(bw[2 * bitsNum - 2] == true); + + bw.popFront(); + assert(bw[2 * bitsNum - 3] == true); + + import core.exception : Error; + import std.exception : assertThrown; + + // Check out of bounds error + assertThrown!Error(bw[2 * bitsNum - 1]); + + bw[2] = true; + assert(bw[2] == true); + bw.popFront(); + assert(bw[1] == true); + + auto bw2 = bw[0 .. $ - 5]; + auto bw3 = bw2[]; + assert(bw2.length == (bw.length - 5)); + assert(bw2.length == bw3.length); + bw2.popFront(); + assert(bw2.length != bw3.length); + } +} + +/********************************* + * An OutputRange that discards the data it receives. + */ +struct NullSink +{ + void put(E)(E){} +} + +/// +@safe unittest +{ + import std.algorithm.iteration : map; + import std.algorithm.mutation : copy; + [4, 5, 6].map!(x => x * 2).copy(NullSink()); // data is discarded +} + + +/++ + + Implements a "tee" style pipe, wrapping an input range so that elements of the + range can be passed to a provided function or $(LREF OutputRange) as they are + iterated over. This is useful for printing out intermediate values in a long + chain of range code, performing some operation with side-effects on each call + to $(D front) or $(D popFront), or diverting the elements of a range into an + auxiliary $(LREF OutputRange). + + It is important to note that as the resultant range is evaluated lazily, + in the case of the version of $(D tee) that takes a function, the function + will not actually be executed until the range is "walked" using functions + that evaluate ranges, such as $(REF array, std,array) or + $(REF fold, std,algorithm,iteration). + + Params: + pipeOnPop = If `Yes.pipeOnPop`, simply iterating the range without ever + calling `front` is enough to have `tee` mirror elements to `outputRange` (or, + respectively, `fun`). If `No.pipeOnPop`, only elements for which `front` does + get called will be also sent to `outputRange`/`fun`. + inputRange = The input range being passed through. + outputRange = This range will receive elements of `inputRange` progressively + as iteration proceeds. + fun = This function will be called with elements of `inputRange` + progressively as iteration proceeds. + + Returns: + An input range that offers the elements of `inputRange`. Regardless of + whether `inputRange` is a more powerful range (forward, bidirectional etc), + the result is always an input range. Reading this causes `inputRange` to be + iterated and returns its elements in turn. In addition, the same elements + will be passed to `outputRange` or `fun` as well. + + See_Also: $(REF each, std,algorithm,iteration) ++/ +auto tee(Flag!"pipeOnPop" pipeOnPop = Yes.pipeOnPop, R1, R2)(R1 inputRange, R2 outputRange) +if (isInputRange!R1 && isOutputRange!(R2, ElementType!R1)) +{ + static struct Result + { + private R1 _input; + private R2 _output; + static if (!pipeOnPop) + { + private bool _frontAccessed; + } + + static if (hasLength!R1) + { + @property auto length() + { + return _input.length; + } + } + + static if (isInfinite!R1) + { + enum bool empty = false; + } + else + { + @property bool empty() { return _input.empty; } + } + + void popFront() + { + assert(!_input.empty, "Attempting to popFront an empty tee"); + static if (pipeOnPop) + { + put(_output, _input.front); + } + else + { + _frontAccessed = false; + } + _input.popFront(); + } + + @property auto ref front() + { + assert(!_input.empty, "Attempting to fetch the front of an empty tee"); + static if (!pipeOnPop) + { + if (!_frontAccessed) + { + _frontAccessed = true; + put(_output, _input.front); + } + } + return _input.front; + } + } + + return Result(inputRange, outputRange); +} + +/// Ditto +auto tee(alias fun, Flag!"pipeOnPop" pipeOnPop = Yes.pipeOnPop, R1)(R1 inputRange) +if (is(typeof(fun) == void) || isSomeFunction!fun) +{ + import std.traits : isDelegate, isFunctionPointer; + /* + Distinguish between function literals and template lambdas + when using either as an $(LREF OutputRange). Since a template + has no type, typeof(template) will always return void. + If it's a template lambda, it's first necessary to instantiate + it with $(D ElementType!R1). + */ + static if (is(typeof(fun) == void)) + alias _fun = fun!(ElementType!R1); + else + alias _fun = fun; + + static if (isFunctionPointer!_fun || isDelegate!_fun) + { + return tee!pipeOnPop(inputRange, _fun); + } + else + { + return tee!pipeOnPop(inputRange, &_fun); + } +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filter, map; + + // Sum values while copying + int[] values = [1, 4, 9, 16, 25]; + int sum = 0; + auto newValues = values.tee!(a => sum += a).array; + assert(equal(newValues, values)); + assert(sum == 1 + 4 + 9 + 16 + 25); + + // Count values that pass the first filter + int count = 0; + auto newValues4 = values.filter!(a => a < 10) + .tee!(a => count++) + .map!(a => a + 1) + .filter!(a => a < 10); + + //Fine, equal also evaluates any lazy ranges passed to it. + //count is not 3 until equal evaluates newValues4 + assert(equal(newValues4, [2, 5])); + assert(count == 3); +} + +// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filter, map; + + int[] values = [1, 4, 9, 16, 25]; + + int count = 0; + auto newValues = values.filter!(a => a < 10) + .tee!(a => count++, No.pipeOnPop) + .map!(a => a + 1) + .filter!(a => a < 10); + + auto val = newValues.front; + assert(count == 1); + //front is only evaluated once per element + val = newValues.front; + assert(count == 1); + + //popFront() called, fun will be called + //again on the next access to front + newValues.popFront(); + newValues.front; + assert(count == 2); + + int[] preMap = new int[](3), postMap = []; + auto mappedValues = values.filter!(a => a < 10) + //Note the two different ways of using tee + .tee(preMap) + .map!(a => a + 1) + .tee!(a => postMap ~= a) + .filter!(a => a < 10); + assert(equal(mappedValues, [2, 5])); + assert(equal(preMap, [1, 4, 9])); + assert(equal(postMap, [2, 5, 10])); +} + +// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filter, map; + + char[] txt = "Line one, Line 2".dup; + + bool isVowel(dchar c) + { + import std.string : indexOf; + return "AaEeIiOoUu".indexOf(c) != -1; + } + + int vowelCount = 0; + int shiftedCount = 0; + auto removeVowels = txt.tee!(c => isVowel(c) ? vowelCount++ : 0) + .filter!(c => !isVowel(c)) + .map!(c => (c == ' ') ? c : c + 1) + .tee!(c => isVowel(c) ? shiftedCount++ : 0); + assert(equal(removeVowels, "Mo o- Mo 3")); + assert(vowelCount == 6); + assert(shiftedCount == 3); +} + +@safe unittest +{ + // Manually stride to test different pipe behavior. + void testRange(Range)(Range r) + { + const int strideLen = 3; + int i = 0; + ElementType!Range elem1; + ElementType!Range elem2; + while (!r.empty) + { + if (i % strideLen == 0) + { + //Make sure front is only + //evaluated once per item + elem1 = r.front; + elem2 = r.front; + assert(elem1 == elem2); + } + r.popFront(); + i++; + } + } + + string txt = "abcdefghijklmnopqrstuvwxyz"; + + int popCount = 0; + auto pipeOnPop = txt.tee!(a => popCount++); + testRange(pipeOnPop); + assert(popCount == 26); + + int frontCount = 0; + auto pipeOnFront = txt.tee!(a => frontCount++, No.pipeOnPop); + testRange(pipeOnFront); + assert(frontCount == 9); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.meta : AliasSeq; + + //Test diverting elements to an OutputRange + string txt = "abcdefghijklmnopqrstuvwxyz"; + + dchar[] asink1 = []; + auto fsink = (dchar c) { asink1 ~= c; }; + auto result1 = txt.tee(fsink).array; + assert(equal(txt, result1) && (equal(result1, asink1))); + + dchar[] _asink1 = []; + auto _result1 = txt.tee!((dchar c) { _asink1 ~= c; })().array; + assert(equal(txt, _result1) && (equal(_result1, _asink1))); + + dchar[] asink2 = new dchar[](txt.length); + void fsink2(dchar c) { static int i = 0; asink2[i] = c; i++; } + auto result2 = txt.tee(&fsink2).array; + assert(equal(txt, result2) && equal(result2, asink2)); + + dchar[] asink3 = new dchar[](txt.length); + auto result3 = txt.tee(asink3).array; + assert(equal(txt, result3) && equal(result3, asink3)); + + foreach (CharType; AliasSeq!(char, wchar, dchar)) + { + auto appSink = appender!(CharType[])(); + auto appResult = txt.tee(appSink).array; + assert(equal(txt, appResult) && equal(appResult, appSink.data)); + } + + foreach (StringType; AliasSeq!(string, wstring, dstring)) + { + auto appSink = appender!StringType(); + auto appResult = txt.tee(appSink).array; + assert(equal(txt, appResult) && equal(appResult, appSink.data)); + } +} + +@safe unittest +{ + // Issue 13483 + static void func1(T)(T x) {} + void func2(int x) {} + + auto r = [1, 2, 3, 4].tee!func1.tee!func2; +} + +/** +Extends the length of the input range `r` by padding out the start of the +range with the element `e`. The element `e` must be of a common type with +the element type of the range `r` as defined by $(REF CommonType, std, traits). +If `n` is less than the length of of `r`, then `r` is returned unmodified. + +If `r` is a string with Unicode characters in it, `padLeft` follows D's rules +about length for strings, which is not the number of characters, or +graphemes, but instead the number of encoding units. If you want to treat each +grapheme as only one encoding unit long, then call +$(REF byGrapheme, std, uni) before calling this function. + +If `r` has a length, then this is $(BIGOH 1). Otherwise, it's $(BIGOH r.length). + +Params: + r = an input range with a length, or a forward range + e = element to pad the range with + n = the length to pad to + +Returns: + A range containing the elements of the original range with the extra padding + +See Also: + $(REF leftJustifier, std, string) +*/ +auto padLeft(R, E)(R r, E e, size_t n) +if ( + ((isInputRange!R && hasLength!R) || isForwardRange!R) && + !is(CommonType!(ElementType!R, E) == void) +) +{ + static if (hasLength!R) + auto dataLength = r.length; + else + auto dataLength = r.save.walkLength(n); + + return e.repeat(n > dataLength ? n - dataLength : 0).chain(r); +} + +/// +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + assert([1, 2, 3, 4].padLeft(0, 6).equal([0, 0, 1, 2, 3, 4])); + assert([1, 2, 3, 4].padLeft(0, 3).equal([1, 2, 3, 4])); + + assert("abc".padLeft('_', 6).equal("___abc")); +} + +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : DummyRange, Length, RangeType, ReturnBy; + import std.meta : AliasSeq; + + alias DummyRanges = AliasSeq!( + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Input), + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Forward), + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Bidirectional), + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random), + DummyRange!(ReturnBy.Reference, Length.No, RangeType.Forward), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Input), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Forward), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Bidirectional), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random), + DummyRange!(ReturnBy.Value, Length.No, RangeType.Forward) + ); + + foreach (Range; DummyRanges) + { + Range r; + assert(r + .padLeft(0, 12) + .equal([0, 0, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U]) + ); + } +} + +// Test nogc inference +@safe @nogc pure unittest +{ + import std.algorithm.comparison : equal; + + static immutable r1 = [1, 2, 3, 4]; + static immutable r2 = [0, 0, 1, 2, 3, 4]; + assert(r1.padLeft(0, 6).equal(r2)); +} + +/** +Extend the length of the input range `r` by padding out the end of the range +with the element `e`. The element `e` must be of a common type with the +element type of the range `r` as defined by $(REF CommonType, std, traits). +If `n` is less than the length of of `r`, then the contents of `r` are +returned. + +The range primitives that the resulting range provides depends whether or not `r` +provides them. Except the functions `back` and `popBack`, which also require +the range to have a length as well as `back` and `popBack` + +Params: + r = an input range with a length + e = element to pad the range with + n = the length to pad to + +Returns: + A range containing the elements of the original range with the extra padding + +See Also: + $(REF rightJustifier, std, string) +*/ +auto padRight(R, E)(R r, E e, size_t n) +if ( + isInputRange!R && + !isInfinite!R && + !is(CommonType!(ElementType!R, E) == void)) +{ + static struct Result + { + private: + R data; + E element; + size_t counter; + static if (isBidirectionalRange!R && hasLength!R) size_t backPosition; + size_t maxSize; + + public: + bool empty() @property + { + return data.empty && counter >= maxSize; + } + + auto front() @property + { + assert(!empty, "Attempting to fetch the front of an empty padRight"); + return data.empty ? element : data.front; + } + + void popFront() + { + assert(!empty, "Attempting to popFront an empty padRight"); + ++counter; + + if (!data.empty) + { + data.popFront; + } + } + + static if (hasLength!R) + { + size_t length() @property + { + import std.algorithm.comparison : max; + return max(data.length, maxSize); + } + } + + static if (isForwardRange!R) + { + auto save() @property + { + typeof(this) result = this; + data = data.save; + return result; + } + } + + static if (isBidirectionalRange!R && hasLength!R) + { + auto back() @property + { + assert(!empty, "Attempting to fetch the back of an empty padRight"); + return backPosition > data.length ? element : data.back; + } + + void popBack() + { + assert(!empty, "Attempting to popBack an empty padRight"); + if (backPosition > data.length) + { + --backPosition; + --maxSize; + } + else + { + data.popBack; + } + } + } + + static if (isRandomAccessRange!R && hasLength!R) + { + E opIndex(size_t index) + { + assert(index <= this.length, "Index out of bounds"); + return (index > data.length && index <= maxSize) ? element : + data[index]; + } + } + + static if (hasSlicing!R && hasLength!R) + { + auto opSlice(size_t a, size_t b) + { + assert( + a <= b, + "Attempting to slice a padRight with a larger first argument than the second." + ); + assert( + b <= length, + "Attempting to slice using an out of bounds index on a padRight" + ); + return Result((b <= data.length) ? data[a .. b] : data[a .. data.length], + element, b - a); + } + + alias opDollar = length; + } + + this(R r, E e, size_t max) + { + data = r; + element = e; + maxSize = max; + static if (isBidirectionalRange!R && hasLength!R) + backPosition = max; + } + + @disable this(); + } + + return Result(r, e, n); +} + +/// +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + assert([1, 2, 3, 4].padRight(0, 6).equal([1, 2, 3, 4, 0, 0])); + assert([1, 2, 3, 4].padRight(0, 4).equal([1, 2, 3, 4])); + + assert("abc".padRight('_', 6).equal("abc___")); +} + +pure @safe unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : AllDummyRanges, ReferenceInputRange; + import std.meta : AliasSeq; + + auto string_input_range = new ReferenceInputRange!dchar(['a', 'b', 'c']); + dchar padding = '_'; + assert(string_input_range.padRight(padding, 6).equal("abc___")); + + foreach (RangeType; AllDummyRanges) + { + RangeType r1; + assert(r1 + .padRight(0, 12) + .equal([1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U, 0, 0]) + ); + + // test if Result properly uses random access ranges + static if (isRandomAccessRange!RangeType) + { + RangeType r3; + assert(r3.padRight(0, 12)[0] == 1); + assert(r3.padRight(0, 12)[2] == 3); + assert(r3.padRight(0, 12)[11] == 0); + } + + // test if Result properly uses slicing and opDollar + static if (hasSlicing!RangeType) + { + RangeType r4; + assert(r4 + .padRight(0, 12)[0 .. 3] + .equal([1, 2, 3]) + ); + assert(r4 + .padRight(0, 12)[2 .. $] + .equal([3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U, 0, 0]) + ); + assert(r4 + .padRight(0, 12)[0 .. $] + .equal([1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U, 0, 0]) + ); + } + } +} + +// Test nogc inference +@safe @nogc pure unittest +{ + import std.algorithm.comparison : equal; + + static immutable r1 = [1, 2, 3, 4]; + static immutable r2 = [1, 2, 3, 4, 0, 0]; + assert(r1.padRight(0, 6).equal(r2)); +} diff --git a/libphobos/src/std/range/primitives.d b/libphobos/src/std/range/primitives.d new file mode 100644 index 0000000..d602048 --- /dev/null +++ b/libphobos/src/std/range/primitives.d @@ -0,0 +1,2320 @@ +/** +This module is a submodule of $(MREF std, range). + +It provides basic range functionality by defining several templates for testing +whether a given object is a _range, and what kind of _range it is: + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE , + $(TR $(TD $(LREF isInputRange)) + $(TD Tests if something is an $(I input _range), defined to be + something from which one can sequentially read data using the + primitives $(D front), $(D popFront), and $(D empty). + )) + $(TR $(TD $(LREF isOutputRange)) + $(TD Tests if something is an $(I output _range), defined to be + something to which one can sequentially write data using the + $(LREF put) primitive. + )) + $(TR $(TD $(LREF isForwardRange)) + $(TD Tests if something is a $(I forward _range), defined to be an + input _range with the additional capability that one can save one's + current position with the $(D save) primitive, thus allowing one to + iterate over the same _range multiple times. + )) + $(TR $(TD $(LREF isBidirectionalRange)) + $(TD Tests if something is a $(I bidirectional _range), that is, a + forward _range that allows reverse traversal using the primitives $(D + back) and $(D popBack). + )) + $(TR $(TD $(LREF isRandomAccessRange)) + $(TD Tests if something is a $(I random access _range), which is a + bidirectional _range that also supports the array subscripting + operation via the primitive $(D opIndex). + )) +) + +It also provides number of templates that test for various _range capabilities: + +$(BOOKTABLE , + $(TR $(TD $(LREF hasMobileElements)) + $(TD Tests if a given _range's elements can be moved around using the + primitives $(D moveFront), $(D moveBack), or $(D moveAt). + )) + $(TR $(TD $(LREF ElementType)) + $(TD Returns the element type of a given _range. + )) + $(TR $(TD $(LREF ElementEncodingType)) + $(TD Returns the encoding element type of a given _range. + )) + $(TR $(TD $(LREF hasSwappableElements)) + $(TD Tests if a _range is a forward _range with swappable elements. + )) + $(TR $(TD $(LREF hasAssignableElements)) + $(TD Tests if a _range is a forward _range with mutable elements. + )) + $(TR $(TD $(LREF hasLvalueElements)) + $(TD Tests if a _range is a forward _range with elements that can be + passed by reference and have their address taken. + )) + $(TR $(TD $(LREF hasLength)) + $(TD Tests if a given _range has the $(D length) attribute. + )) + $(TR $(TD $(LREF isInfinite)) + $(TD Tests if a given _range is an $(I infinite _range). + )) + $(TR $(TD $(LREF hasSlicing)) + $(TD Tests if a given _range supports the array slicing operation $(D + R[x .. y]). + )) +) + +Finally, it includes some convenience functions for manipulating ranges: + +$(BOOKTABLE , + $(TR $(TD $(LREF popFrontN)) + $(TD Advances a given _range by up to $(I n) elements. + )) + $(TR $(TD $(LREF popBackN)) + $(TD Advances a given bidirectional _range from the right by up to + $(I n) elements. + )) + $(TR $(TD $(LREF popFrontExactly)) + $(TD Advances a given _range by up exactly $(I n) elements. + )) + $(TR $(TD $(LREF popBackExactly)) + $(TD Advances a given bidirectional _range from the right by exactly + $(I n) elements. + )) + $(TR $(TD $(LREF moveFront)) + $(TD Removes the front element of a _range. + )) + $(TR $(TD $(LREF moveBack)) + $(TD Removes the back element of a bidirectional _range. + )) + $(TR $(TD $(LREF moveAt)) + $(TD Removes the $(I i)'th element of a random-access _range. + )) + $(TR $(TD $(LREF walkLength)) + $(TD Computes the length of any _range in O(n) time. + )) + $(TR $(TD $(LREF put)) + $(TD Outputs element $(D e) to a _range. + )) +) + +Source: $(PHOBOSSRC std/range/_primitives.d) + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(HTTP erdani.com, Andrei Alexandrescu), David Simcha, +and Jonathan M Davis. Credit for some of the ideas in building this module goes +to $(HTTP fantascienza.net/leonardo/so/, Leonardo Maffi). +*/ +module std.range.primitives; + +import std.traits; + +/** +Returns $(D true) if $(D R) is an input range. An input range must +define the primitives $(D empty), $(D popFront), and $(D front). The +following code should compile for any input range. + +---- +R r; // can define a range object +if (r.empty) {} // can test for empty +r.popFront(); // can invoke popFront() +auto h = r.front; // can get the front of the range of non-void type +---- + +The following are rules of input ranges are assumed to hold true in all +Phobos code. These rules are not checkable at compile-time, so not conforming +to these rules when writing ranges or range based code will result in +undefined behavior. + +$(UL + $(LI `r.empty` returns `false` if and only if there is more data + available in the range.) + $(LI `r.empty` evaluated multiple times, without calling + `r.popFront`, or otherwise mutating the range object or the + underlying data, yields the same result for every evaluation.) + $(LI `r.front` returns the current element in the range. + It may return by value or by reference.) + $(LI `r.front` can be legally evaluated if and only if evaluating + `r.empty` has, or would have, equaled `false`.) + $(LI `r.front` evaluated multiple times, without calling + `r.popFront`, or otherwise mutating the range object or the + underlying data, yields the same result for every evaluation.) + $(LI `r.popFront` advances to the next element in the range.) + $(LI `r.popFront` can be called if and only if evaluating `r.empty` + has, or would have, equaled `false`.) +) + +Also, note that Phobos code assumes that the primitives `r.front` and +`r.empty` are $(BIGOH 1) time complexity wise or "cheap" in terms of +running time. $(BIGOH) statements in the documentation of range functions +are made with this assumption. + +Params: + R = type to be tested + +Returns: + true if R is an InputRange, false if not + */ +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(ReturnType!((R r) => r.front) == void) + && is(typeof((R r) => r.popFront)); + +/// +@safe unittest +{ + struct A {} + struct B + { + void popFront(); + @property bool empty(); + @property int front(); + } + static assert(!isInputRange!A); + static assert( isInputRange!B); + static assert( isInputRange!(int[])); + static assert( isInputRange!(char[])); + static assert(!isInputRange!(char[4])); + static assert( isInputRange!(inout(int)[])); + + static struct NotDefaultConstructible + { + @disable this(); + void popFront(); + @property bool empty(); + @property int front(); + } + static assert( isInputRange!NotDefaultConstructible); + + static struct NotDefaultConstructibleOrCopyable + { + @disable this(); + @disable this(this); + void popFront(); + @property bool empty(); + @property int front(); + } + static assert(isInputRange!NotDefaultConstructibleOrCopyable); + + static struct Frontless + { + void popFront(); + @property bool empty(); + } + static assert(!isInputRange!Frontless); + + static struct VoidFront + { + void popFront(); + @property bool empty(); + void front(); + } + static assert(!isInputRange!VoidFront); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + + static struct R + { + static struct Front + { + R* impl; + @property int value() { return impl._front; } + alias value this; + } + + int _front; + + @property bool empty() { return _front >= 3; } + @property auto front() { return Front(&this); } + void popFront() { _front++; } + } + R r; + + static assert(isInputRange!R); + assert(r.equal([ 0, 1, 2 ])); +} + +/+ +puts the whole raw element $(D e) into $(D r). doPut will not attempt to +iterate, slice or transcode $(D e) in any way shape or form. It will $(B only) +call the correct primitive ($(D r.put(e)), $(D r.front = e) or +$(D r(0)) once. + +This can be important when $(D e) needs to be placed in $(D r) unchanged. +Furthermore, it can be useful when working with $(D InputRange)s, as doPut +guarantees that no more than a single element will be placed. ++/ +private void doPut(R, E)(ref R r, auto ref E e) +{ + static if (is(PointerTarget!R == struct)) + enum usingPut = hasMember!(PointerTarget!R, "put"); + else + enum usingPut = hasMember!(R, "put"); + + static if (usingPut) + { + static assert(is(typeof(r.put(e))), + "Cannot put a " ~ E.stringof ~ " into a " ~ R.stringof ~ "."); + r.put(e); + } + else static if (isInputRange!R) + { + static assert(is(typeof(r.front = e)), + "Cannot put a " ~ E.stringof ~ " into a " ~ R.stringof ~ "."); + r.front = e; + r.popFront(); + } + else static if (is(typeof(r(e)))) + { + r(e); + } + else + { + static assert(false, + "Cannot put a " ~ E.stringof ~ " into a " ~ R.stringof ~ "."); + } +} + +@safe unittest +{ + static assert(!isNativeOutputRange!(int, int)); + static assert( isNativeOutputRange!(int[], int)); + static assert(!isNativeOutputRange!(int[][], int)); + + static assert(!isNativeOutputRange!(int, int[])); + static assert(!isNativeOutputRange!(int[], int[])); + static assert( isNativeOutputRange!(int[][], int[])); + + static assert(!isNativeOutputRange!(int, int[][])); + static assert(!isNativeOutputRange!(int[], int[][])); + static assert(!isNativeOutputRange!(int[][], int[][])); + + static assert(!isNativeOutputRange!(int[4], int)); + static assert( isNativeOutputRange!(int[4][], int)); //Scary! + static assert( isNativeOutputRange!(int[4][], int[4])); + + static assert(!isNativeOutputRange!( char[], char)); + static assert(!isNativeOutputRange!( char[], dchar)); + static assert( isNativeOutputRange!(dchar[], char)); + static assert( isNativeOutputRange!(dchar[], dchar)); + +} + +/++ +Outputs $(D e) to $(D r). The exact effect is dependent upon the two +types. Several cases are accepted, as described below. The code snippets +are attempted in order, and the first to compile "wins" and gets +evaluated. + +In this table "doPut" is a method that places $(D e) into $(D r), using the +correct primitive: $(D r.put(e)) if $(D R) defines $(D put), $(D r.front = e) +if $(D r) is an input range (followed by $(D r.popFront())), or $(D r(e)) +otherwise. + +$(BOOKTABLE , + $(TR + $(TH Code Snippet) + $(TH Scenario) + ) + $(TR + $(TD $(D r.doPut(e);)) + $(TD $(D R) specifically accepts an $(D E).) + ) + $(TR + $(TD $(D r.doPut([ e ]);)) + $(TD $(D R) specifically accepts an $(D E[]).) + ) + $(TR + $(TD $(D r.putChar(e);)) + $(TD $(D R) accepts some form of string or character. put will + transcode the character $(D e) accordingly.) + ) + $(TR + $(TD $(D for (; !e.empty; e.popFront()) put(r, e.front);)) + $(TD Copying range $(D E) into $(D R).) + ) +) + +Tip: $(D put) should $(I not) be used "UFCS-style", e.g. $(D r.put(e)). +Doing this may call $(D R.put) directly, by-passing any transformation +feature provided by $(D Range.put). $(D put(r, e)) is prefered. + +/ +void put(R, E)(ref R r, E e) +{ + //First level: simply straight up put. + static if (is(typeof(doPut(r, e)))) + { + doPut(r, e); + } + //Optional optimization block for straight up array to array copy. + else static if (isDynamicArray!R && !isNarrowString!R && isDynamicArray!E && is(typeof(r[] = e[]))) + { + immutable len = e.length; + r[0 .. len] = e[]; + r = r[len .. $]; + } + //Accepts E[] ? + else static if (is(typeof(doPut(r, [e]))) && !isDynamicArray!R) + { + if (__ctfe) + { + E[1] arr = [e]; + doPut(r, arr[]); + } + else + doPut(r, (ref e) @trusted { return (&e)[0 .. 1]; }(e)); + } + //special case for char to string. + else static if (isSomeChar!E && is(typeof(putChar(r, e)))) + { + putChar(r, e); + } + //Extract each element from the range + //We can use "put" here, so we can recursively test a RoR of E. + else static if (isInputRange!E && is(typeof(put(r, e.front)))) + { + //Special optimization: If E is a narrow string, and r accepts characters no-wider than the string's + //Then simply feed the characters 1 by 1. + static if (isNarrowString!E && ( + (is(E : const char[]) && is(typeof(doPut(r, char.max))) && !is(typeof(doPut(r, dchar.max))) && + !is(typeof(doPut(r, wchar.max)))) || + (is(E : const wchar[]) && is(typeof(doPut(r, wchar.max))) && !is(typeof(doPut(r, dchar.max)))) ) ) + { + foreach (c; e) + doPut(r, c); + } + else + { + for (; !e.empty; e.popFront()) + put(r, e.front); + } + } + else + { + static assert(false, "Cannot put a " ~ E.stringof ~ " into a " ~ R.stringof ~ "."); + } +} + +@safe pure nothrow @nogc unittest +{ + static struct R() { void put(in char[]) {} } + R!() r; + put(r, 'a'); +} + +//Helper function to handle chars as quickly and as elegantly as possible +//Assumes r.put(e)/r(e) has already been tested +private void putChar(R, E)(ref R r, E e) +if (isSomeChar!E) +{ + ////@@@9186@@@: Can't use (E[]).init + ref const( char)[] cstringInit(); + ref const(wchar)[] wstringInit(); + ref const(dchar)[] dstringInit(); + + enum csCond = !isDynamicArray!R && is(typeof(doPut(r, cstringInit()))); + enum wsCond = !isDynamicArray!R && is(typeof(doPut(r, wstringInit()))); + enum dsCond = !isDynamicArray!R && is(typeof(doPut(r, dstringInit()))); + + //Use "max" to avoid static type demotion + enum ccCond = is(typeof(doPut(r, char.max))); + enum wcCond = is(typeof(doPut(r, wchar.max))); + //enum dcCond = is(typeof(doPut(r, dchar.max))); + + //Fast transform a narrow char into a wider string + static if ((wsCond && E.sizeof < wchar.sizeof) || (dsCond && E.sizeof < dchar.sizeof)) + { + enum w = wsCond && E.sizeof < wchar.sizeof; + Select!(w, wchar, dchar) c = e; + typeof(c)[1] arr = [c]; + doPut(r, arr[]); + } + //Encode a wide char into a narrower string + else static if (wsCond || csCond) + { + import std.utf : encode; + /+static+/ Select!(wsCond, wchar[2], char[4]) buf; //static prevents purity. + doPut(r, buf[0 .. encode(buf, e)]); + } + //Slowly encode a wide char into a series of narrower chars + else static if (wcCond || ccCond) + { + import std.encoding : encode; + alias C = Select!(wcCond, wchar, char); + encode!(C, R)(e, r); + } + else + { + static assert(false, "Cannot put a " ~ E.stringof ~ " into a " ~ R.stringof ~ "."); + } +} + +pure @safe unittest +{ + auto f = delegate (const(char)[]) {}; + putChar(f, cast(dchar)'a'); +} + + +@safe pure unittest +{ + static struct R() { void put(in char[]) {} } + R!() r; + putChar(r, 'a'); +} + +@safe unittest +{ + struct A {} + static assert(!isInputRange!(A)); + struct B + { + void put(int) {} + } + B b; + put(b, 5); +} + +@safe unittest +{ + int[] a = [1, 2, 3], b = [10, 20]; + auto c = a; + put(a, b); + assert(c == [10, 20, 3]); + assert(a == [3]); +} + +@safe unittest +{ + int[] a = new int[10]; + int b; + static assert(isInputRange!(typeof(a))); + put(a, b); +} + +@safe unittest +{ + void myprint(in char[] s) { } + auto r = &myprint; + put(r, 'a'); +} + +@safe unittest +{ + int[] a = new int[10]; + static assert(!__traits(compiles, put(a, 1.0L))); + put(a, 1); + assert(a.length == 9); + /* + * a[0] = 65; // OK + * a[0] = 'A'; // OK + * a[0] = "ABC"[0]; // OK + * put(a, "ABC"); // OK + */ + put(a, "ABC"); + assert(a.length == 6); +} + +@safe unittest +{ + char[] a = new char[10]; + static assert(!__traits(compiles, put(a, 1.0L))); + static assert(!__traits(compiles, put(a, 1))); + // char[] is NOT output range. + static assert(!__traits(compiles, put(a, 'a'))); + static assert(!__traits(compiles, put(a, "ABC"))); +} + +@safe unittest +{ + int[][] a = new int[][10]; + int[] b = new int[10]; + int c; + put(b, c); + assert(b.length == 9); + put(a, b); + assert(a.length == 9); + static assert(!__traits(compiles, put(a, c))); +} + +@safe unittest +{ + int[][] a = new int[][](3); + int[] b = [1]; + auto aa = a; + put(aa, b); + assert(aa == [[], []]); + assert(a == [[1], [], []]); + int[][3] c = [2]; + aa = a; + put(aa, c[]); + assert(aa.empty); + assert(a == [[2], [2], [2]]); +} + +@safe unittest +{ + // Test fix for bug 7476. + struct LockingTextWriter + { + void put(dchar c){} + } + struct RetroResult + { + bool end = false; + @property bool empty() const { return end; } + @property dchar front(){ return 'a'; } + void popFront(){ end = true; } + } + LockingTextWriter w; + RetroResult r; + put(w, r); +} + +@system unittest +{ + import std.conv : to; + import std.meta : AliasSeq; + import std.typecons : tuple; + + static struct PutC(C) + { + string result; + void put(const(C) c) { result ~= to!string((&c)[0 .. 1]); } + } + static struct PutS(C) + { + string result; + void put(const(C)[] s) { result ~= to!string(s); } + } + static struct PutSS(C) + { + string result; + void put(const(C)[][] ss) + { + foreach (s; ss) + result ~= to!string(s); + } + } + + PutS!char p; + putChar(p, cast(dchar)'a'); + + //Source Char + foreach (SC; AliasSeq!(char, wchar, dchar)) + { + SC ch = 'I'; + dchar dh = '♥'; + immutable(SC)[] s = "日本語!"; + immutable(SC)[][] ss = ["日本語", "が", "好き", "ですか", "?"]; + + //Target Char + foreach (TC; AliasSeq!(char, wchar, dchar)) + { + //Testing PutC and PutS + foreach (Type; AliasSeq!(PutC!TC, PutS!TC)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + Type type; + auto sink = new Type(); + + //Testing put and sink + foreach (value ; tuple(type, sink)) + { + put(value, ch); + assert(value.result == "I"); + put(value, dh); + assert(value.result == "I♥"); + put(value, s); + assert(value.result == "I♥日本語!"); + put(value, ss); + assert(value.result == "I♥日本語!日本語が好きですか?"); + } + }(); + } + } +} + +@safe unittest +{ + static struct CharRange + { + char c; + enum empty = false; + void popFront(){} + ref char front() return @property + { + return c; + } + } + CharRange c; + put(c, cast(dchar)'H'); + put(c, "hello"d); +} + +@system unittest +{ + // issue 9823 + const(char)[] r; + void delegate(const(char)[]) dg = (s) { r = s; }; + put(dg, ["ABC"]); + assert(r == "ABC"); +} + +@safe unittest +{ + // issue 10571 + import std.format; + string buf; + formattedWrite((in char[] s) { buf ~= s; }, "%s", "hello"); + assert(buf == "hello"); +} + +@safe unittest +{ + import std.format; + import std.meta : AliasSeq; + struct PutC(C) + { + void put(C){} + } + struct PutS(C) + { + void put(const(C)[]){} + } + struct CallC(C) + { + void opCall(C){} + } + struct CallS(C) + { + void opCall(const(C)[]){} + } + struct FrontC(C) + { + enum empty = false; + auto front()@property{return C.init;} + void front(C)@property{} + void popFront(){} + } + struct FrontS(C) + { + enum empty = false; + auto front()@property{return C[].init;} + void front(const(C)[])@property{} + void popFront(){} + } + void foo() + { + foreach (C; AliasSeq!(char, wchar, dchar)) + { + formattedWrite((C c){}, "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); + formattedWrite((const(C)[]){}, "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); + formattedWrite(PutC!C(), "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); + formattedWrite(PutS!C(), "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); + CallC!C callC; + CallS!C callS; + formattedWrite(callC, "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); + formattedWrite(callS, "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); + formattedWrite(FrontC!C(), "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); + formattedWrite(FrontS!C(), "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); + } + formattedWrite((dchar[]).init, "", 1, 'a', cast(wchar)'a', cast(dchar)'a', "a"c, "a"w, "a"d); + } +} + +/+ +Returns $(D true) if $(D R) is a native output range for elements of type +$(D E). An output range is defined functionally as a range that +supports the operation $(D doPut(r, e)) as defined above. if $(D doPut(r, e)) +is valid, then $(D put(r,e)) will have the same behavior. + +The two guarantees isNativeOutputRange gives over the larger $(D isOutputRange) +are: +1: $(D e) is $(B exactly) what will be placed (not $(D [e]), for example). +2: if $(D E) is a non $(empty) $(D InputRange), then placing $(D e) is +guaranteed to not overflow the range. + +/ +package(std) enum bool isNativeOutputRange(R, E) = + is(typeof(doPut(lvalueOf!R, lvalueOf!E))); + +@safe unittest +{ + int[] r = new int[](4); + static assert(isInputRange!(int[])); + static assert( isNativeOutputRange!(int[], int)); + static assert(!isNativeOutputRange!(int[], int[])); + static assert( isOutputRange!(int[], int[])); + + if (!r.empty) + put(r, 1); //guaranteed to succeed + if (!r.empty) + put(r, [1, 2]); //May actually error out. +} + +/++ +Returns $(D true) if $(D R) is an output range for elements of type +$(D E). An output range is defined functionally as a range that +supports the operation $(D put(r, e)) as defined above. + +/ +enum bool isOutputRange(R, E) = + is(typeof(put(lvalueOf!R, lvalueOf!E))); + +/// +@safe unittest +{ + void myprint(in char[] s) { } + static assert(isOutputRange!(typeof(&myprint), char)); + + static assert(!isOutputRange!(char[], char)); + static assert( isOutputRange!(dchar[], wchar)); + static assert( isOutputRange!(dchar[], dchar)); +} + +@safe unittest +{ + import std.array; + import std.stdio : writeln; + + auto app = appender!string(); + string s; + static assert( isOutputRange!(Appender!string, string)); + static assert( isOutputRange!(Appender!string*, string)); + static assert(!isOutputRange!(Appender!string, int)); + static assert(!isOutputRange!(wchar[], wchar)); + static assert( isOutputRange!(dchar[], char)); + static assert( isOutputRange!(dchar[], string)); + static assert( isOutputRange!(dchar[], wstring)); + static assert( isOutputRange!(dchar[], dstring)); + + static assert(!isOutputRange!(const(int)[], int)); + static assert(!isOutputRange!(inout(int)[], int)); +} + + +/** +Returns $(D true) if $(D R) is a forward range. A forward range is an +input range $(D r) that can save "checkpoints" by saving $(D r.save) +to another value of type $(D R). Notable examples of input ranges that +are $(I not) forward ranges are file/socket ranges; copying such a +range will not save the position in the stream, and they most likely +reuse an internal buffer as the entire stream does not sit in +memory. Subsequently, advancing either the original or the copy will +advance the stream, so the copies are not independent. + +The following code should compile for any forward range. + +---- +static assert(isInputRange!R); +R r1; +auto s1 = r1.save; +static assert(is(typeof(s1) == R)); +---- + +Saving a range is not duplicating it; in the example above, $(D r1) +and $(D r2) still refer to the same underlying data. They just +navigate that data independently. + +The semantics of a forward range (not checkable during compilation) +are the same as for an input range, with the additional requirement +that backtracking must be possible by saving a copy of the range +object with $(D save) and using it later. + */ +enum bool isForwardRange(R) = isInputRange!R + && is(ReturnType!((R r) => r.save) == R); + +/// +@safe unittest +{ + static assert(!isForwardRange!(int)); + static assert( isForwardRange!(int[])); + static assert( isForwardRange!(inout(int)[])); +} + +@safe unittest +{ + // BUG 14544 + struct R14544 + { + int front() { return 0;} + void popFront() {} + bool empty() { return false; } + R14544 save() {return this;} + } + + static assert( isForwardRange!R14544 ); +} + +/** +Returns $(D true) if $(D R) is a bidirectional range. A bidirectional +range is a forward range that also offers the primitives $(D back) and +$(D popBack). The following code should compile for any bidirectional +range. + +The semantics of a bidirectional range (not checkable during +compilation) are assumed to be the following ($(D r) is an object of +type $(D R)): + +$(UL $(LI $(D r.back) returns (possibly a reference to) the last +element in the range. Calling $(D r.back) is allowed only if calling +$(D r.empty) has, or would have, returned $(D false).)) + */ +enum bool isBidirectionalRange(R) = isForwardRange!R + && is(typeof((R r) => r.popBack)) + && is(ReturnType!((R r) => r.back) == ElementType!R); + +/// +@safe unittest +{ + alias R = int[]; + R r = [0,1]; + static assert(isForwardRange!R); // is forward range + r.popBack(); // can invoke popBack + auto t = r.back; // can get the back of the range + auto w = r.front; + static assert(is(typeof(t) == typeof(w))); // same type for front and back +} + +@safe unittest +{ + struct A {} + struct B + { + void popFront(); + @property bool empty(); + @property int front(); + } + struct C + { + @property bool empty(); + @property C save(); + void popFront(); + @property int front(); + void popBack(); + @property int back(); + } + static assert(!isBidirectionalRange!(A)); + static assert(!isBidirectionalRange!(B)); + static assert( isBidirectionalRange!(C)); + static assert( isBidirectionalRange!(int[])); + static assert( isBidirectionalRange!(char[])); + static assert( isBidirectionalRange!(inout(int)[])); +} + +/** +Returns $(D true) if $(D R) is a random-access range. A random-access +range is a bidirectional range that also offers the primitive $(D +opIndex), OR an infinite forward range that offers $(D opIndex). In +either case, the range must either offer $(D length) or be +infinite. The following code should compile for any random-access +range. + +The semantics of a random-access range (not checkable during +compilation) are assumed to be the following ($(D r) is an object of +type $(D R)): $(UL $(LI $(D r.opIndex(n)) returns a reference to the +$(D n)th element in the range.)) + +Although $(D char[]) and $(D wchar[]) (as well as their qualified +versions including $(D string) and $(D wstring)) are arrays, $(D +isRandomAccessRange) yields $(D false) for them because they use +variable-length encodings (UTF-8 and UTF-16 respectively). These types +are bidirectional ranges only. + */ +enum bool isRandomAccessRange(R) = + is(typeof(lvalueOf!R[1]) == ElementType!R) + && !isNarrowString!R + && isForwardRange!R + && (isBidirectionalRange!R || isInfinite!R) + && (hasLength!R || isInfinite!R) + && (isInfinite!R || !is(typeof(lvalueOf!R[$ - 1])) + || is(typeof(lvalueOf!R[$ - 1]) == ElementType!R)); + +/// +@safe unittest +{ + import std.traits : isNarrowString; + + alias R = int[]; + + // range is finite and bidirectional or infinite and forward. + static assert(isBidirectionalRange!R || + isForwardRange!R && isInfinite!R); + + R r = [0,1]; + auto e = r[1]; // can index + auto f = r.front; + static assert(is(typeof(e) == typeof(f))); // same type for indexed and front + static assert(!isNarrowString!R); // narrow strings cannot be indexed as ranges + static assert(hasLength!R || isInfinite!R); // must have length or be infinite + + // $ must work as it does with arrays if opIndex works with $ + static if (is(typeof(r[$]))) + { + static assert(is(typeof(f) == typeof(r[$]))); + + // $ - 1 doesn't make sense with infinite ranges but needs to work + // with finite ones. + static if (!isInfinite!R) + static assert(is(typeof(f) == typeof(r[$ - 1]))); + } +} + +@safe unittest +{ + struct A {} + struct B + { + void popFront(); + @property bool empty(); + @property int front(); + } + struct C + { + void popFront(); + @property bool empty(); + @property int front(); + void popBack(); + @property int back(); + } + struct D + { + @property bool empty(); + @property D save(); + @property int front(); + void popFront(); + @property int back(); + void popBack(); + ref int opIndex(uint); + @property size_t length(); + alias opDollar = length; + //int opSlice(uint, uint); + } + struct E + { + bool empty(); + E save(); + int front(); + void popFront(); + int back(); + void popBack(); + ref int opIndex(uint); + size_t length(); + alias opDollar = length; + //int opSlice(uint, uint); + } + static assert(!isRandomAccessRange!(A)); + static assert(!isRandomAccessRange!(B)); + static assert(!isRandomAccessRange!(C)); + static assert( isRandomAccessRange!(D)); + static assert( isRandomAccessRange!(E)); + static assert( isRandomAccessRange!(int[])); + static assert( isRandomAccessRange!(inout(int)[])); +} + +@safe unittest +{ + // Test fix for bug 6935. + struct R + { + @disable this(); + + @property bool empty() const { return false; } + @property int front() const { return 0; } + void popFront() {} + + @property R save() { return this; } + + @property int back() const { return 0; } + void popBack(){} + + int opIndex(size_t n) const { return 0; } + @property size_t length() const { return 0; } + alias opDollar = length; + + void put(int e){ } + } + static assert(isInputRange!R); + static assert(isForwardRange!R); + static assert(isBidirectionalRange!R); + static assert(isRandomAccessRange!R); + static assert(isOutputRange!(R, int)); +} + +/** +Returns $(D true) iff $(D R) is an input range that supports the +$(D moveFront) primitive, as well as $(D moveBack) and $(D moveAt) if it's a +bidirectional or random access range. These may be explicitly implemented, or +may work via the default behavior of the module level functions $(D moveFront) +and friends. The following code should compile for any range +with mobile elements. + +---- +alias E = ElementType!R; +R r; +static assert(isInputRange!R); +static assert(is(typeof(moveFront(r)) == E)); +static if (isBidirectionalRange!R) + static assert(is(typeof(moveBack(r)) == E)); +static if (isRandomAccessRange!R) + static assert(is(typeof(moveAt(r, 0)) == E)); +---- + */ +enum bool hasMobileElements(R) = + isInputRange!R + && is(typeof(moveFront(lvalueOf!R)) == ElementType!R) + && (!isBidirectionalRange!R + || is(typeof(moveBack(lvalueOf!R)) == ElementType!R)) + && (!isRandomAccessRange!R + || is(typeof(moveAt(lvalueOf!R, 0)) == ElementType!R)); + +/// +@safe unittest +{ + import std.algorithm.iteration : map; + import std.range : iota, repeat; + + static struct HasPostblit + { + this(this) {} + } + + auto nonMobile = map!"a"(repeat(HasPostblit.init)); + static assert(!hasMobileElements!(typeof(nonMobile))); + static assert( hasMobileElements!(int[])); + static assert( hasMobileElements!(inout(int)[])); + static assert( hasMobileElements!(typeof(iota(1000)))); + + static assert( hasMobileElements!( string)); + static assert( hasMobileElements!(dstring)); + static assert( hasMobileElements!( char[])); + static assert( hasMobileElements!(dchar[])); +} + +/** +The element type of $(D R). $(D R) does not have to be a range. The +element type is determined as the type yielded by $(D r.front) for an +object $(D r) of type $(D R). For example, $(D ElementType!(T[])) is +$(D T) if $(D T[]) isn't a narrow string; if it is, the element type is +$(D dchar). If $(D R) doesn't have $(D front), $(D ElementType!R) is +$(D void). + */ +template ElementType(R) +{ + static if (is(typeof(R.init.front.init) T)) + alias ElementType = T; + else + alias ElementType = void; +} + +/// +@safe unittest +{ + import std.range : iota; + + // Standard arrays: returns the type of the elements of the array + static assert(is(ElementType!(int[]) == int)); + + // Accessing .front retrieves the decoded dchar + static assert(is(ElementType!(char[]) == dchar)); // rvalue + static assert(is(ElementType!(dchar[]) == dchar)); // lvalue + + // Ditto + static assert(is(ElementType!(string) == dchar)); + static assert(is(ElementType!(dstring) == immutable(dchar))); + + // For ranges it gets the type of .front. + auto range = iota(0, 10); + static assert(is(ElementType!(typeof(range)) == int)); +} + +@safe unittest +{ + static assert(is(ElementType!(byte[]) == byte)); + static assert(is(ElementType!(wchar[]) == dchar)); // rvalue + static assert(is(ElementType!(wstring) == dchar)); +} + +@safe unittest +{ + enum XYZ : string { a = "foo" } + auto x = XYZ.a.front; + immutable char[3] a = "abc"; + int[] i; + void[] buf; + static assert(is(ElementType!(XYZ) == dchar)); + static assert(is(ElementType!(typeof(a)) == dchar)); + static assert(is(ElementType!(typeof(i)) == int)); + static assert(is(ElementType!(typeof(buf)) == void)); + static assert(is(ElementType!(inout(int)[]) == inout(int))); + static assert(is(ElementType!(inout(int[])) == inout(int))); +} + +@safe unittest +{ + static assert(is(ElementType!(int[5]) == int)); + static assert(is(ElementType!(int[0]) == int)); + static assert(is(ElementType!(char[5]) == dchar)); + static assert(is(ElementType!(char[0]) == dchar)); +} + +@safe unittest //11336 +{ + static struct S + { + this(this) @disable; + } + static assert(is(ElementType!(S[]) == S)); +} + +@safe unittest // 11401 +{ + // ElementType should also work for non-@propety 'front' + struct E { ushort id; } + struct R + { + E front() { return E.init; } + } + static assert(is(ElementType!R == E)); +} + +/** +The encoding element type of $(D R). For narrow strings ($(D char[]), +$(D wchar[]) and their qualified variants including $(D string) and +$(D wstring)), $(D ElementEncodingType) is the character type of the +string. For all other types, $(D ElementEncodingType) is the same as +$(D ElementType). + */ +template ElementEncodingType(R) +{ + static if (is(StringTypeOf!R) && is(R : E[], E)) + alias ElementEncodingType = E; + else + alias ElementEncodingType = ElementType!R; +} + +/// +@safe unittest +{ + import std.range : iota; + // internally the range stores the encoded type + static assert(is(ElementEncodingType!(char[]) == char)); + + static assert(is(ElementEncodingType!(wstring) == immutable(wchar))); + + static assert(is(ElementEncodingType!(byte[]) == byte)); + + auto range = iota(0, 10); + static assert(is(ElementEncodingType!(typeof(range)) == int)); +} + +@safe unittest +{ + static assert(is(ElementEncodingType!(wchar[]) == wchar)); + static assert(is(ElementEncodingType!(dchar[]) == dchar)); + static assert(is(ElementEncodingType!(string) == immutable(char))); + static assert(is(ElementEncodingType!(dstring) == immutable(dchar))); + static assert(is(ElementEncodingType!(int[]) == int)); +} + +@safe unittest +{ + enum XYZ : string { a = "foo" } + auto x = XYZ.a.front; + immutable char[3] a = "abc"; + int[] i; + void[] buf; + static assert(is(ElementType!(XYZ) : dchar)); + static assert(is(ElementEncodingType!(char[]) == char)); + static assert(is(ElementEncodingType!(string) == immutable char)); + static assert(is(ElementType!(typeof(a)) : dchar)); + static assert(is(ElementType!(typeof(i)) == int)); + static assert(is(ElementEncodingType!(typeof(i)) == int)); + static assert(is(ElementType!(typeof(buf)) : void)); + + static assert(is(ElementEncodingType!(inout char[]) : inout(char))); +} + +@safe unittest +{ + static assert(is(ElementEncodingType!(int[5]) == int)); + static assert(is(ElementEncodingType!(int[0]) == int)); + static assert(is(ElementEncodingType!(char[5]) == char)); + static assert(is(ElementEncodingType!(char[0]) == char)); +} + +/** +Returns $(D true) if $(D R) is an input range and has swappable +elements. The following code should compile for any range +with swappable elements. + +---- +R r; +static assert(isInputRange!R); +swap(r.front, r.front); +static if (isBidirectionalRange!R) swap(r.back, r.front); +static if (isRandomAccessRange!R) swap(r[0], r.front); +---- + */ +template hasSwappableElements(R) +{ + import std.algorithm.mutation : swap; + enum bool hasSwappableElements = isInputRange!R + && is(typeof((ref R r) => swap(r.front, r.front))) + && (!isBidirectionalRange!R + || is(typeof((ref R r) => swap(r.back, r.front)))) + && (!isRandomAccessRange!R + || is(typeof((ref R r) => swap(r[0], r.front)))); +} + +/// +@safe unittest +{ + static assert(!hasSwappableElements!(const int[])); + static assert(!hasSwappableElements!(const(int)[])); + static assert(!hasSwappableElements!(inout(int)[])); + static assert( hasSwappableElements!(int[])); + + static assert(!hasSwappableElements!( string)); + static assert(!hasSwappableElements!(dstring)); + static assert(!hasSwappableElements!( char[])); + static assert( hasSwappableElements!(dchar[])); +} + +/** +Returns $(D true) if $(D R) is an input range and has mutable +elements. The following code should compile for any range +with assignable elements. + +---- +R r; +static assert(isInputRange!R); +r.front = r.front; +static if (isBidirectionalRange!R) r.back = r.front; +static if (isRandomAccessRange!R) r[0] = r.front; +---- + */ +enum bool hasAssignableElements(R) = isInputRange!R + && is(typeof(lvalueOf!R.front = lvalueOf!R.front)) + && (!isBidirectionalRange!R + || is(typeof(lvalueOf!R.back = lvalueOf!R.back))) + && (!isRandomAccessRange!R + || is(typeof(lvalueOf!R[0] = lvalueOf!R.front))); + +/// +@safe unittest +{ + static assert(!hasAssignableElements!(const int[])); + static assert(!hasAssignableElements!(const(int)[])); + static assert( hasAssignableElements!(int[])); + static assert(!hasAssignableElements!(inout(int)[])); + + static assert(!hasAssignableElements!( string)); + static assert(!hasAssignableElements!(dstring)); + static assert(!hasAssignableElements!( char[])); + static assert( hasAssignableElements!(dchar[])); +} + +/** +Tests whether the range $(D R) has lvalue elements. These are defined as +elements that can be passed by reference and have their address taken. +The following code should compile for any range with lvalue elements. +---- +void passByRef(ref ElementType!R stuff); +... +static assert(isInputRange!R); +passByRef(r.front); +static if (isBidirectionalRange!R) passByRef(r.back); +static if (isRandomAccessRange!R) passByRef(r[0]); +---- +*/ +enum bool hasLvalueElements(R) = isInputRange!R + && is(typeof(((ref x) => x)(lvalueOf!R.front))) + && (!isBidirectionalRange!R + || is(typeof(((ref x) => x)(lvalueOf!R.back)))) + && (!isRandomAccessRange!R + || is(typeof(((ref x) => x)(lvalueOf!R[0])))); + +/// +@safe unittest +{ + import std.range : iota, chain; + + static assert( hasLvalueElements!(int[])); + static assert( hasLvalueElements!(const(int)[])); + static assert( hasLvalueElements!(inout(int)[])); + static assert( hasLvalueElements!(immutable(int)[])); + static assert(!hasLvalueElements!(typeof(iota(3)))); + + static assert(!hasLvalueElements!( string)); + static assert( hasLvalueElements!(dstring)); + static assert(!hasLvalueElements!( char[])); + static assert( hasLvalueElements!(dchar[])); + + auto c = chain([1, 2, 3], [4, 5, 6]); + static assert( hasLvalueElements!(typeof(c))); +} + +@safe unittest +{ + // bugfix 6336 + struct S { immutable int value; } + static assert( isInputRange!(S[])); + static assert( hasLvalueElements!(S[])); +} + +/** +Yields `true` if `R` has a `length` member that returns a value of `size_t` +type. `R` does not have to be a range. If `R` is a range, algorithms in the +standard library are only guaranteed to support `length` with type `size_t`. + +Note that `length` is an optional primitive as no range must implement it. Some +ranges do not store their length explicitly, some cannot compute it without +actually exhausting the range (e.g. socket streams), and some other ranges may +be infinite. + +Although narrow string types (`char[]`, `wchar[]`, and their qualified +derivatives) do define a `length` property, `hasLength` yields `false` for them. +This is because a narrow string's length does not reflect the number of +characters, but instead the number of encoding units, and as such is not useful +with range-oriented algorithms. To use strings as random-access ranges with +length, use $(REF representation, std, string) or $(REF byCodeUnit, std, utf). + +Deprecation: Historically `hasLength!R` yielded `true` for types whereby +`R.length` returns other types convertible to `ulong`, such as `int`, `ushort`, +`const(size_t)`, user-defined types using `alias this`, or notably `ulong` on +32-bit systems. This behavior has been deprecated. After December 2017, +`hasLength` will yield `true` only if `R.length` yields the exact type `size_t`. +*/ +template hasLength(R) +{ + static if (is(typeof(((R* r) => r.length)(null)) Length)) + { + static if (is(Length == size_t)) + { + enum bool hasLength = !isNarrowString!R; + } + else static if (is(Length : ulong)) + { + // @@@DEPRECATED_2017-12@@@ + // Uncomment the deprecated(...) message and take the pragma(msg) + // out once https://issues.dlang.org/show_bug.cgi?id=10181 is fixed. + pragma(msg, __FILE__ ~ "(" ~ __LINE__.stringof ~ + "): Note: length must have type size_t on all systems" ~ + ", please update your code by December 2017."); + //deprecated("length must have type size_t on all systems") + enum bool hasLength = true; + } + else + { + enum bool hasLength = false; + } + } + else + { + enum bool hasLength = false; + } +} + +/// +@safe unittest +{ + static assert(!hasLength!(char[])); + static assert( hasLength!(int[])); + static assert( hasLength!(inout(int)[])); + + struct A { ulong length; } + struct B { size_t length() { return 0; } } + struct C { @property size_t length() { return 0; } } + static assert( hasLength!(A)); + static assert( hasLength!(B)); + static assert( hasLength!(C)); +} + +/** +Returns $(D true) if $(D R) is an infinite input range. An +infinite input range is an input range that has a statically-defined +enumerated member called $(D empty) that is always $(D false), +for example: + +---- +struct MyInfiniteRange +{ + enum bool empty = false; + ... +} +---- + */ + +template isInfinite(R) +{ + static if (isInputRange!R && __traits(compiles, { enum e = R.empty; })) + enum bool isInfinite = !R.empty; + else + enum bool isInfinite = false; +} + +/// +@safe unittest +{ + import std.range : Repeat; + static assert(!isInfinite!(int[])); + static assert( isInfinite!(Repeat!(int))); +} + +/** +Returns $(D true) if $(D R) offers a slicing operator with integral boundaries +that returns a forward range type. + +For finite ranges, the result of $(D opSlice) must be of the same type as the +original range type. If the range defines $(D opDollar), then it must support +subtraction. + +For infinite ranges, when $(I not) using $(D opDollar), the result of +$(D opSlice) must be the result of $(LREF take) or $(LREF takeExactly) on the +original range (they both return the same type for infinite ranges). However, +when using $(D opDollar), the result of $(D opSlice) must be that of the +original range type. + +The following expression must be true for `hasSlicing` to be `true`: + +---- + isForwardRange!R + && !isNarrowString!R + && is(ReturnType!((R r) => r[1 .. 1].length) == size_t) + && (is(typeof(lvalueOf!R[1 .. 1]) == R) || isInfinite!R) + && (!is(typeof(lvalueOf!R[0 .. $])) || is(typeof(lvalueOf!R[0 .. $]) == R)) + && (!is(typeof(lvalueOf!R[0 .. $])) || isInfinite!R + || is(typeof(lvalueOf!R[0 .. $ - 1]) == R)) + && is(typeof((ref R r) + { + static assert(isForwardRange!(typeof(r[1 .. 2]))); + })); +---- + */ +enum bool hasSlicing(R) = isForwardRange!R + && !isNarrowString!R + && is(ReturnType!((R r) => r[1 .. 1].length) == size_t) + && (is(typeof(lvalueOf!R[1 .. 1]) == R) || isInfinite!R) + && (!is(typeof(lvalueOf!R[0 .. $])) || is(typeof(lvalueOf!R[0 .. $]) == R)) + && (!is(typeof(lvalueOf!R[0 .. $])) || isInfinite!R + || is(typeof(lvalueOf!R[0 .. $ - 1]) == R)) + && is(typeof((ref R r) + { + static assert(isForwardRange!(typeof(r[1 .. 2]))); + })); + +/// +@safe unittest +{ + import std.range : takeExactly; + static assert( hasSlicing!(int[])); + static assert( hasSlicing!(const(int)[])); + static assert(!hasSlicing!(const int[])); + static assert( hasSlicing!(inout(int)[])); + static assert(!hasSlicing!(inout int [])); + static assert( hasSlicing!(immutable(int)[])); + static assert(!hasSlicing!(immutable int[])); + static assert(!hasSlicing!string); + static assert( hasSlicing!dstring); + + enum rangeFuncs = "@property int front();" ~ + "void popFront();" ~ + "@property bool empty();" ~ + "@property auto save() { return this; }" ~ + "@property size_t length();"; + + struct A { mixin(rangeFuncs); int opSlice(size_t, size_t); } + struct B { mixin(rangeFuncs); B opSlice(size_t, size_t); } + struct C { mixin(rangeFuncs); @disable this(); C opSlice(size_t, size_t); } + struct D { mixin(rangeFuncs); int[] opSlice(size_t, size_t); } + static assert(!hasSlicing!(A)); + static assert( hasSlicing!(B)); + static assert( hasSlicing!(C)); + static assert(!hasSlicing!(D)); + + struct InfOnes + { + enum empty = false; + void popFront() {} + @property int front() { return 1; } + @property InfOnes save() { return this; } + auto opSlice(size_t i, size_t j) { return takeExactly(this, j - i); } + auto opSlice(size_t i, Dollar d) { return this; } + + struct Dollar {} + Dollar opDollar() const { return Dollar.init; } + } + + static assert(hasSlicing!InfOnes); +} + +/** +This is a best-effort implementation of $(D length) for any kind of +range. + +If $(D hasLength!Range), simply returns $(D range.length) without +checking $(D upTo) (when specified). + +Otherwise, walks the range through its length and returns the number +of elements seen. Performes $(BIGOH n) evaluations of $(D range.empty) +and $(D range.popFront()), where $(D n) is the effective length of $(D +range). + +The $(D upTo) parameter is useful to "cut the losses" in case +the interest is in seeing whether the range has at least some number +of elements. If the parameter $(D upTo) is specified, stops if $(D +upTo) steps have been taken and returns $(D upTo). + +Infinite ranges are compatible, provided the parameter $(D upTo) is +specified, in which case the implementation simply returns upTo. + */ +auto walkLength(Range)(Range range) +if (isInputRange!Range && !isInfinite!Range) +{ + static if (hasLength!Range) + return range.length; + else + { + size_t result; + for ( ; !range.empty ; range.popFront() ) + ++result; + return result; + } +} +/// ditto +auto walkLength(Range)(Range range, const size_t upTo) +if (isInputRange!Range) +{ + static if (hasLength!Range) + return range.length; + else static if (isInfinite!Range) + return upTo; + else + { + size_t result; + for ( ; result < upTo && !range.empty ; range.popFront() ) + ++result; + return result; + } +} + +@safe unittest +{ + import std.algorithm.iteration : filter; + import std.range : recurrence, take; + + //hasLength Range + int[] a = [ 1, 2, 3 ]; + assert(walkLength(a) == 3); + assert(walkLength(a, 0) == 3); + assert(walkLength(a, 2) == 3); + assert(walkLength(a, 4) == 3); + + //Forward Range + auto b = filter!"true"([1, 2, 3, 4]); + assert(b.walkLength() == 4); + assert(b.walkLength(0) == 0); + assert(b.walkLength(2) == 2); + assert(b.walkLength(4) == 4); + assert(b.walkLength(6) == 4); + + //Infinite Range + auto fibs = recurrence!"a[n-1] + a[n-2]"(1, 1); + assert(!__traits(compiles, fibs.walkLength())); + assert(fibs.take(10).walkLength() == 10); + assert(fibs.walkLength(55) == 55); +} + +/** + Eagerly advances $(D r) itself (not a copy) up to $(D n) times (by + calling $(D r.popFront)). $(D popFrontN) takes $(D r) by $(D ref), + so it mutates the original range. Completes in $(BIGOH 1) steps for ranges + that support slicing and have length. + Completes in $(BIGOH n) time for all other ranges. + + Returns: + How much $(D r) was actually advanced, which may be less than $(D n) if + $(D r) did not have at least $(D n) elements. + + $(D popBackN) will behave the same but instead removes elements from + the back of the (bidirectional) range instead of the front. + + See_Also: $(REF drop, std, range), $(REF dropBack, std, range) +*/ +size_t popFrontN(Range)(ref Range r, size_t n) +if (isInputRange!Range) +{ + static if (hasLength!Range) + { + n = cast(size_t) (n < r.length ? n : r.length); + } + + static if (hasSlicing!Range && is(typeof(r = r[n .. $]))) + { + r = r[n .. $]; + } + else static if (hasSlicing!Range && hasLength!Range) //TODO: Remove once hasSlicing forces opDollar. + { + r = r[n .. r.length]; + } + else + { + static if (hasLength!Range) + { + foreach (i; 0 .. n) + r.popFront(); + } + else + { + foreach (i; 0 .. n) + { + if (r.empty) return i; + r.popFront(); + } + } + } + return n; +} + +/// ditto +size_t popBackN(Range)(ref Range r, size_t n) +if (isBidirectionalRange!Range) +{ + static if (hasLength!Range) + { + n = cast(size_t) (n < r.length ? n : r.length); + } + + static if (hasSlicing!Range && is(typeof(r = r[0 .. $ - n]))) + { + r = r[0 .. $ - n]; + } + else static if (hasSlicing!Range && hasLength!Range) //TODO: Remove once hasSlicing forces opDollar. + { + r = r[0 .. r.length - n]; + } + else + { + static if (hasLength!Range) + { + foreach (i; 0 .. n) + r.popBack(); + } + else + { + foreach (i; 0 .. n) + { + if (r.empty) return i; + r.popBack(); + } + } + } + return n; +} + +/// +@safe unittest +{ + int[] a = [ 1, 2, 3, 4, 5 ]; + a.popFrontN(2); + assert(a == [ 3, 4, 5 ]); + a.popFrontN(7); + assert(a == [ ]); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : iota; + auto LL = iota(1L, 7L); + auto r = popFrontN(LL, 2); + assert(equal(LL, [3L, 4L, 5L, 6L])); + assert(r == 2); +} + +/// +@safe unittest +{ + int[] a = [ 1, 2, 3, 4, 5 ]; + a.popBackN(2); + assert(a == [ 1, 2, 3 ]); + a.popBackN(7); + assert(a == [ ]); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : iota; + auto LL = iota(1L, 7L); + auto r = popBackN(LL, 2); + assert(equal(LL, [1L, 2L, 3L, 4L])); + assert(r == 2); +} + +/** + Eagerly advances $(D r) itself (not a copy) exactly $(D n) times (by + calling $(D r.popFront)). $(D popFrontExactly) takes $(D r) by $(D ref), + so it mutates the original range. Completes in $(BIGOH 1) steps for ranges + that support slicing, and have either length or are infinite. + Completes in $(BIGOH n) time for all other ranges. + + Note: Unlike $(LREF popFrontN), $(D popFrontExactly) will assume that the + range holds at least $(D n) elements. This makes $(D popFrontExactly) + faster than $(D popFrontN), but it also means that if $(D range) does + not contain at least $(D n) elements, it will attempt to call $(D popFront) + on an empty range, which is undefined behavior. So, only use + $(D popFrontExactly) when it is guaranteed that $(D range) holds at least + $(D n) elements. + + $(D popBackExactly) will behave the same but instead removes elements from + the back of the (bidirectional) range instead of the front. + + See_Also: $(REF dropExcatly, std, range), $(REF dropBackExactly, std, range) +*/ +void popFrontExactly(Range)(ref Range r, size_t n) +if (isInputRange!Range) +{ + static if (hasLength!Range) + assert(n <= r.length, "range is smaller than amount of items to pop"); + + static if (hasSlicing!Range && is(typeof(r = r[n .. $]))) + r = r[n .. $]; + else static if (hasSlicing!Range && hasLength!Range) //TODO: Remove once hasSlicing forces opDollar. + r = r[n .. r.length]; + else + foreach (i; 0 .. n) + r.popFront(); +} + +/// ditto +void popBackExactly(Range)(ref Range r, size_t n) +if (isBidirectionalRange!Range) +{ + static if (hasLength!Range) + assert(n <= r.length, "range is smaller than amount of items to pop"); + + static if (hasSlicing!Range && is(typeof(r = r[0 .. $ - n]))) + r = r[0 .. $ - n]; + else static if (hasSlicing!Range && hasLength!Range) //TODO: Remove once hasSlicing forces opDollar. + r = r[0 .. r.length - n]; + else + foreach (i; 0 .. n) + r.popBack(); +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filterBidirectional; + + auto a = [1, 2, 3]; + a.popFrontExactly(1); + assert(a == [2, 3]); + a.popBackExactly(1); + assert(a == [2]); + + string s = "日本語"; + s.popFrontExactly(1); + assert(s == "本語"); + s.popBackExactly(1); + assert(s == "本"); + + auto bd = filterBidirectional!"true"([1, 2, 3]); + bd.popFrontExactly(1); + assert(bd.equal([2, 3])); + bd.popBackExactly(1); + assert(bd.equal([2])); +} + +/** + Moves the front of $(D r) out and returns it. Leaves $(D r.front) in a + destroyable state that does not allocate any resources (usually equal + to its $(D .init) value). +*/ +ElementType!R moveFront(R)(R r) +{ + static if (is(typeof(&r.moveFront))) + { + return r.moveFront(); + } + else static if (!hasElaborateCopyConstructor!(ElementType!R)) + { + return r.front; + } + else static if (is(typeof(&(r.front())) == ElementType!R*)) + { + import std.algorithm.mutation : move; + return move(r.front); + } + else + { + static assert(0, + "Cannot move front of a range with a postblit and an rvalue front."); + } +} + +/// +@safe unittest +{ + auto a = [ 1, 2, 3 ]; + assert(moveFront(a) == 1); + assert(a.length == 3); + + // define a perfunctory input range + struct InputRange + { + enum bool empty = false; + enum int front = 7; + void popFront() {} + int moveFront() { return 43; } + } + InputRange r; + // calls r.moveFront + assert(moveFront(r) == 43); +} + +@safe unittest +{ + struct R + { + @property ref int front() { static int x = 42; return x; } + this(this){} + } + R r; + assert(moveFront(r) == 42); +} + +/** + Moves the back of $(D r) out and returns it. Leaves $(D r.back) in a + destroyable state that does not allocate any resources (usually equal + to its $(D .init) value). +*/ +ElementType!R moveBack(R)(R r) +{ + static if (is(typeof(&r.moveBack))) + { + return r.moveBack(); + } + else static if (!hasElaborateCopyConstructor!(ElementType!R)) + { + return r.back; + } + else static if (is(typeof(&(r.back())) == ElementType!R*)) + { + import std.algorithm.mutation : move; + return move(r.back); + } + else + { + static assert(0, + "Cannot move back of a range with a postblit and an rvalue back."); + } +} + +/// +@safe unittest +{ + struct TestRange + { + int payload = 5; + @property bool empty() { return false; } + @property TestRange save() { return this; } + @property ref int front() return { return payload; } + @property ref int back() return { return payload; } + void popFront() { } + void popBack() { } + } + static assert(isBidirectionalRange!TestRange); + TestRange r; + auto x = moveBack(r); + assert(x == 5); +} + +/** + Moves element at index $(D i) of $(D r) out and returns it. Leaves $(D + r[i]) in a destroyable state that does not allocate any resources + (usually equal to its $(D .init) value). +*/ +ElementType!R moveAt(R)(R r, size_t i) +{ + static if (is(typeof(&r.moveAt))) + { + return r.moveAt(i); + } + else static if (!hasElaborateCopyConstructor!(ElementType!(R))) + { + return r[i]; + } + else static if (is(typeof(&r[i]) == ElementType!R*)) + { + import std.algorithm.mutation : move; + return move(r[i]); + } + else + { + static assert(0, + "Cannot move element of a range with a postblit and rvalue elements."); + } +} + +/// +@safe unittest +{ + auto a = [1,2,3,4]; + foreach (idx, it; a) + { + assert(it == moveAt(a, idx)); + } +} + +@safe unittest +{ + import std.internal.test.dummyrange; + + foreach (DummyType; AllDummyRanges) + { + auto d = DummyType.init; + assert(moveFront(d) == 1); + + static if (isBidirectionalRange!DummyType) + { + assert(moveBack(d) == 10); + } + + static if (isRandomAccessRange!DummyType) + { + assert(moveAt(d, 2) == 3); + } + } +} + +/** +Implements the range interface primitive $(D empty) for built-in +arrays. Due to the fact that nonmember functions can be called with +the first argument using the dot notation, $(D array.empty) is +equivalent to $(D empty(array)). + */ +@property bool empty(T)(in T[] a) @safe pure nothrow @nogc +{ + return !a.length; +} + +/// +@safe pure nothrow unittest +{ + auto a = [ 1, 2, 3 ]; + assert(!a.empty); + assert(a[3 .. $].empty); +} + +/** +Implements the range interface primitive $(D save) for built-in +arrays. Due to the fact that nonmember functions can be called with +the first argument using the dot notation, $(D array.save) is +equivalent to $(D save(array)). The function does not duplicate the +content of the array, it simply returns its argument. + */ +@property T[] save(T)(T[] a) @safe pure nothrow @nogc +{ + return a; +} + +/// +@safe pure nothrow unittest +{ + auto a = [ 1, 2, 3 ]; + auto b = a.save; + assert(b is a); +} + +/** +Implements the range interface primitive $(D popFront) for built-in +arrays. Due to the fact that nonmember functions can be called with +the first argument using the dot notation, $(D array.popFront) is +equivalent to $(D popFront(array)). For $(GLOSSARY narrow strings), +$(D popFront) automatically advances to the next $(GLOSSARY code +point). +*/ +void popFront(T)(ref T[] a) @safe pure nothrow @nogc +if (!isNarrowString!(T[]) && !is(T[] == void[])) +{ + assert(a.length, "Attempting to popFront() past the end of an array of " ~ T.stringof); + a = a[1 .. $]; +} + +/// +@safe pure nothrow unittest +{ + auto a = [ 1, 2, 3 ]; + a.popFront(); + assert(a == [ 2, 3 ]); +} + +version (unittest) +{ + static assert(!is(typeof({ int[4] a; popFront(a); }))); + static assert(!is(typeof({ immutable int[] a; popFront(a); }))); + static assert(!is(typeof({ void[] a; popFront(a); }))); +} + +/// ditto +void popFront(C)(ref C[] str) @trusted pure nothrow +if (isNarrowString!(C[])) +{ + import std.algorithm.comparison : min; + + assert(str.length, "Attempting to popFront() past the end of an array of " ~ C.stringof); + + static if (is(Unqual!C == char)) + { + static immutable ubyte[] charWidthTab = [ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1 + ]; + + immutable c = str[0]; + if (c < 192) + { + str = str.ptr[1 .. str.length]; + } + else + { + str = str.ptr[min(str.length, charWidthTab.ptr[c - 192]) .. str.length]; + } + + } + else static if (is(Unqual!C == wchar)) + { + immutable u = str[0]; + immutable seqLen = 1 + (u >= 0xD800 && u <= 0xDBFF); + str = str.ptr[min(seqLen, str.length) .. str.length]; + } + else static assert(0, "Bad template constraint."); +} + +@safe pure unittest +{ + import std.meta : AliasSeq; + + foreach (S; AliasSeq!(string, wstring, dstring)) + { + S s = "\xC2\xA9hello"; + s.popFront(); + assert(s == "hello"); + + S str = "hello\U00010143\u0100\U00010143"; + foreach (dchar c; ['h', 'e', 'l', 'l', 'o', '\U00010143', '\u0100', '\U00010143']) + { + assert(str.front == c); + str.popFront(); + } + assert(str.empty); + + static assert(!is(typeof({ immutable S a; popFront(a); }))); + static assert(!is(typeof({ typeof(S.init[0])[4] a; popFront(a); }))); + } + + C[] _eatString(C)(C[] str) + { + while (!str.empty) + str.popFront(); + + return str; + } + enum checkCTFE = _eatString("ウェブサイト@La_Verité.com"); + static assert(checkCTFE.empty); + enum checkCTFEW = _eatString("ウェブサイト@La_Verité.com"w); + static assert(checkCTFEW.empty); +} + +@safe unittest // issue 16090 +{ + string s = "\u00E4"; + assert(s.length == 2); + s = s[0 .. 1]; + assert(s.length == 1); + s.popFront; + assert(s.empty); +} + +@safe unittest +{ + wstring s = "\U00010000"; + assert(s.length == 2); + s = s[0 .. 1]; + assert(s.length == 1); + s.popFront; + assert(s.empty); +} + +/** +Implements the range interface primitive $(D popBack) for built-in +arrays. Due to the fact that nonmember functions can be called with +the first argument using the dot notation, $(D array.popBack) is +equivalent to $(D popBack(array)). For $(GLOSSARY narrow strings), $(D +popFront) automatically eliminates the last $(GLOSSARY code point). +*/ +void popBack(T)(ref T[] a) @safe pure nothrow @nogc +if (!isNarrowString!(T[]) && !is(T[] == void[])) +{ + assert(a.length); + a = a[0 .. $ - 1]; +} + +/// +@safe pure nothrow unittest +{ + auto a = [ 1, 2, 3 ]; + a.popBack(); + assert(a == [ 1, 2 ]); +} + +version (unittest) +{ + static assert(!is(typeof({ immutable int[] a; popBack(a); }))); + static assert(!is(typeof({ int[4] a; popBack(a); }))); + static assert(!is(typeof({ void[] a; popBack(a); }))); +} + +/// ditto +void popBack(T)(ref T[] a) @safe pure +if (isNarrowString!(T[])) +{ + import std.utf : strideBack; + assert(a.length, "Attempting to popBack() past the front of an array of " ~ T.stringof); + a = a[0 .. $ - strideBack(a, $)]; +} + +@safe pure unittest +{ + import std.meta : AliasSeq; + + foreach (S; AliasSeq!(string, wstring, dstring)) + { + S s = "hello\xE2\x89\xA0"; + s.popBack(); + assert(s == "hello"); + S s3 = "\xE2\x89\xA0"; + auto c = s3.back; + assert(c == cast(dchar)'\u2260'); + s3.popBack(); + assert(s3 == ""); + + S str = "\U00010143\u0100\U00010143hello"; + foreach (dchar ch; ['o', 'l', 'l', 'e', 'h', '\U00010143', '\u0100', '\U00010143']) + { + assert(str.back == ch); + str.popBack(); + } + assert(str.empty); + + static assert(!is(typeof({ immutable S a; popBack(a); }))); + static assert(!is(typeof({ typeof(S.init[0])[4] a; popBack(a); }))); + } +} + +/** +Implements the range interface primitive $(D front) for built-in +arrays. Due to the fact that nonmember functions can be called with +the first argument using the dot notation, $(D array.front) is +equivalent to $(D front(array)). For $(GLOSSARY narrow strings), $(D +front) automatically returns the first $(GLOSSARY code point) as _a $(D +dchar). +*/ +@property ref T front(T)(T[] a) @safe pure nothrow @nogc +if (!isNarrowString!(T[]) && !is(T[] == void[])) +{ + assert(a.length, "Attempting to fetch the front of an empty array of " ~ T.stringof); + return a[0]; +} + +/// +@safe pure nothrow unittest +{ + int[] a = [ 1, 2, 3 ]; + assert(a.front == 1); +} + +@safe pure nothrow unittest +{ + auto a = [ 1, 2 ]; + a.front = 4; + assert(a.front == 4); + assert(a == [ 4, 2 ]); + + immutable b = [ 1, 2 ]; + assert(b.front == 1); + + int[2] c = [ 1, 2 ]; + assert(c.front == 1); +} + +/// ditto +@property dchar front(T)(T[] a) @safe pure +if (isNarrowString!(T[])) +{ + import std.utf : decode; + assert(a.length, "Attempting to fetch the front of an empty array of " ~ T.stringof); + size_t i = 0; + return decode(a, i); +} + +/** +Implements the range interface primitive $(D back) for built-in +arrays. Due to the fact that nonmember functions can be called with +the first argument using the dot notation, $(D array.back) is +equivalent to $(D back(array)). For $(GLOSSARY narrow strings), $(D +back) automatically returns the last $(GLOSSARY code point) as _a $(D +dchar). +*/ +@property ref T back(T)(T[] a) @safe pure nothrow @nogc +if (!isNarrowString!(T[]) && !is(T[] == void[])) +{ + assert(a.length, "Attempting to fetch the back of an empty array of " ~ T.stringof); + return a[$ - 1]; +} + +/// +@safe pure nothrow unittest +{ + int[] a = [ 1, 2, 3 ]; + assert(a.back == 3); + a.back += 4; + assert(a.back == 7); +} + +@safe pure nothrow unittest +{ + immutable b = [ 1, 2, 3 ]; + assert(b.back == 3); + + int[3] c = [ 1, 2, 3 ]; + assert(c.back == 3); +} + +/// ditto +// Specialization for strings +@property dchar back(T)(T[] a) @safe pure +if (isNarrowString!(T[])) +{ + import std.utf : decode, strideBack; + assert(a.length, "Attempting to fetch the back of an empty array of " ~ T.stringof); + size_t i = a.length - strideBack(a, a.length); + return decode(a, i); +} diff --git a/libphobos/src/std/regex/internal/backtracking.d b/libphobos/src/std/regex/internal/backtracking.d new file mode 100644 index 0000000..ffc9779 --- /dev/null +++ b/libphobos/src/std/regex/internal/backtracking.d @@ -0,0 +1,1495 @@ +/* + Implementation of backtracking std.regex engine. + Contains both compile-time and run-time versions. +*/ +module std.regex.internal.backtracking; + +package(std.regex): + +import core.stdc.stdlib, std.range.primitives, std.traits, std.typecons; +import std.regex.internal.ir; + +/+ + BacktrackingMatcher implements backtracking scheme of matching + regular expressions. ++/ +template BacktrackingMatcher(bool CTregex) +{ + @trusted struct BacktrackingMatcher(Char, Stream = Input!Char) + if (is(Char : dchar)) + { + alias DataIndex = Stream.DataIndex; + struct State + {//top bit in pc is set if saved along with matches + DataIndex index; + uint pc, counter, infiniteNesting; + } + static assert(State.sizeof % size_t.sizeof == 0); + enum stateSize = State.sizeof / size_t.sizeof; + enum initialStack = 1 << 11; // items in a block of segmented stack + alias String = const(Char)[]; + alias RegEx = Regex!Char; + alias MatchFn = bool function (ref BacktrackingMatcher!(Char, Stream)); + RegEx re; //regex program + static if (CTregex) + MatchFn nativeFn; //native code for that program + //Stream state + Stream s; + DataIndex index; + dchar front; + bool exhausted; + //backtracking machine state + uint pc, counter; + DataIndex lastState = 0; //top of state stack + static if (!CTregex) + uint infiniteNesting; + size_t[] memory; + Trace[] merge; + static struct Trace + { + ulong mask; + size_t offset; + + bool mark(size_t idx) + { + immutable d = idx - offset; + if (d < 64) // including overflow + { + immutable p = mask & (1UL << d); + mask |= 1UL << d; + return p != 0; + } + else + { + offset = idx; + mask = 1; + return false; + } + } + } + //local slice of matches, global for backref + Group!DataIndex[] matches, backrefed; + + static if (__traits(hasMember,Stream, "search")) + { + enum kicked = true; + } + else + enum kicked = false; + + static size_t initialMemory(const ref RegEx re) + { + return stackSize(re)*size_t.sizeof + re.hotspotTableSize*Trace.sizeof; + } + + static size_t stackSize(const ref RegEx re) + { + size_t itemSize = stateSize + + re.ngroup * (Group!DataIndex).sizeof / size_t.sizeof; + return initialStack * itemSize + 2; + } + + @property bool atStart(){ return index == 0; } + + @property bool atEnd(){ return index == s.lastIndex && s.atEnd; } + + void next() + { + if (!s.nextChar(front, index)) + index = s.lastIndex; + } + + void search() + { + static if (kicked) + { + if (!s.search(re.kickstart, front, index)) + { + index = s.lastIndex; + } + } + else + next(); + } + + // + void newStack() + { + auto chunk = mallocArray!(size_t)(stackSize(re)); + chunk[0] = cast(size_t)(memory.ptr); + chunk[1] = lastState; + memory = chunk[2..$]; + lastState = 0; + } + + bool prevStack() + { + // pointer to previous block + size_t* prev = cast(size_t*) memory.ptr[-2]; + if (!prev) + { + // The last segment is freed in RegexMatch + return false; + } + else + { + import core.stdc.stdlib : free; + // memory used in previous block + size_t size = memory.ptr[-1]; + free(memory.ptr-2); + memory = prev[0 .. size]; + lastState = size; + return true; + } + } + + void initExternalMemory(void[] memBlock) + { + merge = arrayInChunk!(Trace)(re.hotspotTableSize, memBlock); + merge[] = Trace.init; + memory = cast(size_t[]) memBlock; + memory[0] = 0; // hidden pointer + memory[1] = 0; // used size + memory = memory[2..$]; + } + + void initialize(ref RegEx program, Stream stream, void[] memBlock) + { + re = program; + s = stream; + exhausted = false; + initExternalMemory(memBlock); + backrefed = null; + } + + auto dupTo(void[] memory) + { + typeof(this) tmp = this; + tmp.initExternalMemory(memory); + return tmp; + } + + this(ref RegEx program, Stream stream, void[] memBlock, dchar ch, DataIndex idx) + { + initialize(program, stream, memBlock); + front = ch; + index = idx; + } + + this(ref RegEx program, Stream stream, void[] memBlock) + { + initialize(program, stream, memBlock); + next(); + } + + auto fwdMatcher(ref BacktrackingMatcher matcher, void[] memBlock) + { + alias BackMatcherTempl = .BacktrackingMatcher!(CTregex); + alias BackMatcher = BackMatcherTempl!(Char, Stream); + auto fwdMatcher = BackMatcher(matcher.re, s, memBlock, front, index); + return fwdMatcher; + } + + auto bwdMatcher(ref BacktrackingMatcher matcher, void[] memBlock) + { + alias BackMatcherTempl = .BacktrackingMatcher!(CTregex); + alias BackMatcher = BackMatcherTempl!(Char, typeof(s.loopBack(index))); + auto fwdMatcher = + BackMatcher(matcher.re, s.loopBack(index), memBlock); + return fwdMatcher; + } + + // + int matchFinalize() + { + immutable start = index; + immutable val = matchImpl(); + if (val) + {//stream is updated here + matches[0].begin = start; + matches[0].end = index; + if (!(re.flags & RegexOption.global) || atEnd) + exhausted = true; + if (start == index)//empty match advances input + next(); + return val; + } + else + return 0; + } + + //lookup next match, fill matches with indices into input + int match(Group!DataIndex[] matches) + { + debug(std_regex_matcher) + { + writeln("------------------------------------------"); + } + if (exhausted) //all matches collected + return false; + this.matches = matches; + if (re.flags & RegexInfo.oneShot) + { + exhausted = true; + const DataIndex start = index; + immutable m = matchImpl(); + if (m) + { + matches[0].begin = start; + matches[0].end = index; + } + return m; + } + static if (kicked) + { + if (!re.kickstart.empty) + { + for (;;) + { + immutable val = matchFinalize(); + if (val) + return val; + else + { + if (atEnd) + break; + search(); + if (atEnd) + { + exhausted = true; + return matchFinalize(); + } + } + } + exhausted = true; + return 0; //early return + } + } + //no search available - skip a char at a time + for (;;) + { + immutable val = matchFinalize(); + if (val) + return val; + else + { + if (atEnd) + break; + next(); + if (atEnd) + { + exhausted = true; + return matchFinalize(); + } + } + } + exhausted = true; + return 0; + } + + /+ + match subexpression against input, + results are stored in matches + +/ + int matchImpl() + { + static if (CTregex && is(typeof(nativeFn(this)))) + { + debug(std_regex_ctr) writeln("using C-T matcher"); + return nativeFn(this); + } + else + { + pc = 0; + counter = 0; + lastState = 0; + matches[] = Group!DataIndex.init; + auto start = s._index; + debug(std_regex_matcher) + writeln("Try match starting at ", s[index .. s.lastIndex]); + for (;;) + { + debug(std_regex_matcher) + writefln("PC: %s\tCNT: %s\t%s \tfront: %s src: %s", + pc, counter, disassemble(re.ir, pc, re.dict), + front, s._index); + switch (re.ir[pc].code) + { + case IR.OrChar://assumes IRL!(OrChar) == 1 + if (atEnd) + goto L_backtrack; + uint len = re.ir[pc].sequence; + uint end = pc + len; + if (re.ir[pc].data != front && re.ir[pc+1].data != front) + { + for (pc = pc+2; pc < end; pc++) + if (re.ir[pc].data == front) + break; + if (pc == end) + goto L_backtrack; + } + pc = end; + next(); + break; + case IR.Char: + if (atEnd || front != re.ir[pc].data) + goto L_backtrack; + pc += IRL!(IR.Char); + next(); + break; + case IR.Any: + if (atEnd) + goto L_backtrack; + pc += IRL!(IR.Any); + next(); + break; + case IR.CodepointSet: + if (atEnd || !re.charsets[re.ir[pc].data].scanFor(front)) + goto L_backtrack; + next(); + pc += IRL!(IR.CodepointSet); + break; + case IR.Trie: + if (atEnd || !re.matchers[re.ir[pc].data][front]) + goto L_backtrack; + next(); + pc += IRL!(IR.Trie); + break; + case IR.Wordboundary: + dchar back; + DataIndex bi; + //at start & end of input + if (atStart && wordMatcher[front]) + { + pc += IRL!(IR.Wordboundary); + break; + } + else if (atEnd && s.loopBack(index).nextChar(back, bi) + && wordMatcher[back]) + { + pc += IRL!(IR.Wordboundary); + break; + } + else if (s.loopBack(index).nextChar(back, bi)) + { + immutable af = wordMatcher[front]; + immutable ab = wordMatcher[back]; + if (af ^ ab) + { + pc += IRL!(IR.Wordboundary); + break; + } + } + goto L_backtrack; + case IR.Notwordboundary: + dchar back; + DataIndex bi; + //at start & end of input + if (atStart && wordMatcher[front]) + goto L_backtrack; + else if (atEnd && s.loopBack(index).nextChar(back, bi) + && wordMatcher[back]) + goto L_backtrack; + else if (s.loopBack(index).nextChar(back, bi)) + { + immutable af = wordMatcher[front]; + immutable ab = wordMatcher[back]; + if (af ^ ab) + goto L_backtrack; + } + pc += IRL!(IR.Wordboundary); + break; + case IR.Bof: + if (atStart) + pc += IRL!(IR.Bol); + else + goto L_backtrack; + break; + case IR.Bol: + dchar back; + DataIndex bi; + if (atStart) + pc += IRL!(IR.Bol); + else if (s.loopBack(index).nextChar(back,bi) + && endOfLine(back, front == '\n')) + { + pc += IRL!(IR.Bol); + } + else + goto L_backtrack; + break; + case IR.Eof: + if (atEnd) + pc += IRL!(IR.Eol); + else + goto L_backtrack; + break; + case IR.Eol: + dchar back; + DataIndex bi; + debug(std_regex_matcher) writefln("EOL (front 0x%x) %s", front, s[index .. s.lastIndex]); + //no matching inside \r\n + if (atEnd || (endOfLine(front, s.loopBack(index).nextChar(back,bi) + && back == '\r'))) + { + pc += IRL!(IR.Eol); + } + else + goto L_backtrack; + break; + case IR.InfiniteStart, IR.InfiniteQStart: + pc += re.ir[pc].data + IRL!(IR.InfiniteStart); + //now pc is at end IR.Infinite(Q)End + uint len = re.ir[pc].data; + if (re.ir[pc].code == IR.InfiniteEnd) + { + pushState(pc+IRL!(IR.InfiniteEnd), counter); + pc -= len; + } + else + { + pushState(pc - len, counter); + pc += IRL!(IR.InfiniteEnd); + } + break; + case IR.InfiniteBloomStart: + pc += re.ir[pc].data + IRL!(IR.InfiniteBloomStart); + //now pc is at end IR.InfiniteBloomEnd + immutable len = re.ir[pc].data; + immutable filterIdx = re.ir[pc+2].raw; + if (re.filters[filterIdx][front]) + pushState(pc+IRL!(IR.InfiniteBloomEnd), counter); + pc -= len; + break; + case IR.RepeatStart, IR.RepeatQStart: + pc += re.ir[pc].data + IRL!(IR.RepeatStart); + break; + case IR.RepeatEnd: + case IR.RepeatQEnd: + if (merge[re.ir[pc + 1].raw+counter].mark(index)) + { + // merged! + goto L_backtrack; + } + //len, step, min, max + immutable len = re.ir[pc].data; + immutable step = re.ir[pc+2].raw; + immutable min = re.ir[pc+3].raw; + immutable max = re.ir[pc+4].raw; + if (counter < min) + { + counter += step; + pc -= len; + } + else if (counter < max) + { + if (re.ir[pc].code == IR.RepeatEnd) + { + pushState(pc + IRL!(IR.RepeatEnd), counter%step); + counter += step; + pc -= len; + } + else + { + pushState(pc - len, counter + step); + counter = counter%step; + pc += IRL!(IR.RepeatEnd); + } + } + else + { + counter = counter%step; + pc += IRL!(IR.RepeatEnd); + } + break; + case IR.InfiniteEnd: + case IR.InfiniteQEnd: + debug(std_regex_matcher) writeln("Infinited nesting:", infiniteNesting); + if (merge[re.ir[pc + 1].raw+counter].mark(index)) + { + // merged! + goto L_backtrack; + } + immutable len = re.ir[pc].data; + if (re.ir[pc].code == IR.InfiniteEnd) + { + pushState(pc + IRL!(IR.InfiniteEnd), counter); + pc -= len; + } + else + { + pushState(pc-len, counter); + pc += IRL!(IR.InfiniteEnd); + } + break; + case IR.InfiniteBloomEnd: + debug(std_regex_matcher) writeln("Infinited nesting:", infiniteNesting); + if (merge[re.ir[pc + 1].raw+counter].mark(index)) + { + // merged! + goto L_backtrack; + } + immutable len = re.ir[pc].data; + immutable filterIdx = re.ir[pc+2].raw; + if (re.filters[filterIdx][front]) + { + infiniteNesting--; + pushState(pc + IRL!(IR.InfiniteBloomEnd), counter); + infiniteNesting++; + } + pc -= len; + break; + case IR.OrEnd: + if (merge[re.ir[pc + 1].raw+counter].mark(index)) + { + // merged! + goto L_backtrack; + } + pc += IRL!(IR.OrEnd); + break; + case IR.OrStart: + pc += IRL!(IR.OrStart); + goto case; + case IR.Option: + immutable len = re.ir[pc].data; + if (re.ir[pc+len].code == IR.GotoEndOr)//not a last one + { + pushState(pc + len + IRL!(IR.Option), counter); //remember 2nd branch + } + pc += IRL!(IR.Option); + break; + case IR.GotoEndOr: + pc = pc + re.ir[pc].data + IRL!(IR.GotoEndOr); + break; + case IR.GroupStart: + immutable n = re.ir[pc].data; + matches[n].begin = index; + debug(std_regex_matcher) writefln("IR group #%u starts at %u", n, index); + pc += IRL!(IR.GroupStart); + break; + case IR.GroupEnd: + immutable n = re.ir[pc].data; + matches[n].end = index; + debug(std_regex_matcher) writefln("IR group #%u ends at %u", n, index); + pc += IRL!(IR.GroupEnd); + break; + case IR.LookaheadStart: + case IR.NeglookaheadStart: + immutable len = re.ir[pc].data; + auto save = index; + immutable ms = re.ir[pc+1].raw, me = re.ir[pc+2].raw; + auto mem = malloc(initialMemory(re))[0 .. initialMemory(re)]; + scope(exit) free(mem.ptr); + static if (Stream.isLoopback) + { + auto matcher = bwdMatcher(this, mem); + } + else + { + auto matcher = fwdMatcher(this, mem); + } + matcher.matches = matches[ms .. me]; + matcher.backrefed = backrefed.empty ? matches : backrefed; + matcher.re.ir = re.ir[ + pc+IRL!(IR.LookaheadStart) .. pc+IRL!(IR.LookaheadStart)+len+IRL!(IR.LookaheadEnd) + ]; + immutable match = (matcher.matchImpl() != 0) ^ (re.ir[pc].code == IR.NeglookaheadStart); + s.reset(save); + next(); + if (!match) + goto L_backtrack; + else + { + pc += IRL!(IR.LookaheadStart)+len+IRL!(IR.LookaheadEnd); + } + break; + case IR.LookbehindStart: + case IR.NeglookbehindStart: + immutable len = re.ir[pc].data; + immutable ms = re.ir[pc+1].raw, me = re.ir[pc+2].raw; + auto mem = malloc(initialMemory(re))[0 .. initialMemory(re)]; + scope(exit) free(mem.ptr); + static if (Stream.isLoopback) + { + alias Matcher = BacktrackingMatcher!(Char, Stream); + auto matcher = Matcher(re, s, mem, front, index); + } + else + { + alias Matcher = BacktrackingMatcher!(Char, typeof(s.loopBack(index))); + auto matcher = Matcher(re, s.loopBack(index), mem); + } + matcher.matches = matches[ms .. me]; + matcher.re.ir = re.ir[ + pc + IRL!(IR.LookbehindStart) .. pc + IRL!(IR.LookbehindStart) + len + IRL!(IR.LookbehindEnd) + ]; + matcher.backrefed = backrefed.empty ? matches : backrefed; + immutable match = (matcher.matchImpl() != 0) ^ (re.ir[pc].code == IR.NeglookbehindStart); + if (!match) + goto L_backtrack; + else + { + pc += IRL!(IR.LookbehindStart)+len+IRL!(IR.LookbehindEnd); + } + break; + case IR.Backref: + immutable n = re.ir[pc].data; + auto referenced = re.ir[pc].localRef + ? s[matches[n].begin .. matches[n].end] + : s[backrefed[n].begin .. backrefed[n].end]; + while (!atEnd && !referenced.empty && front == referenced.front) + { + next(); + referenced.popFront(); + } + if (referenced.empty) + pc++; + else + goto L_backtrack; + break; + case IR.Nop: + pc += IRL!(IR.Nop); + break; + case IR.LookaheadEnd: + case IR.NeglookaheadEnd: + case IR.LookbehindEnd: + case IR.NeglookbehindEnd: + case IR.End: + // cleanup stale stack blocks if any + while (prevStack()) {} + return re.ir[pc].data; + default: + debug printBytecode(re.ir[0..$]); + assert(0); + L_backtrack: + if (!popState()) + { + s.reset(start); + return 0; + } + } + } + } + assert(0); + } + + @property size_t stackAvail() + { + return memory.length - lastState; + } + + void stackPush(T)(T val) + if (!isDynamicArray!T) + { + *cast(T*)&memory[lastState] = val; + enum delta = (T.sizeof+size_t.sizeof/2)/size_t.sizeof; + lastState += delta; + debug(std_regex_matcher) writeln("push element SP= ", lastState); + } + + void stackPush(T)(T[] val) + { + static assert(T.sizeof % size_t.sizeof == 0); + (cast(T*)&memory[lastState])[0 .. val.length] + = val[0..$]; + lastState += val.length*(T.sizeof/size_t.sizeof); + debug(std_regex_matcher) writeln("push array SP= ", lastState); + } + + void stackPop(T)(ref T val) + if (!isDynamicArray!T) + { + enum delta = (T.sizeof+size_t.sizeof/2)/size_t.sizeof; + lastState -= delta; + val = *cast(T*)&memory[lastState]; + debug(std_regex_matcher) writeln("pop element SP= ", lastState); + } + + void stackPop(T)(T[] val) + { + stackPop(val); // call ref version + } + void stackPop(T)(ref T[] val) + { + lastState -= val.length*(T.sizeof/size_t.sizeof); + val[0..$] = (cast(T*)&memory[lastState])[0 .. val.length]; + debug(std_regex_matcher) writeln("pop array SP= ", lastState); + } + + static if (!CTregex) + { + //helper function, saves engine state + void pushState(uint pc, uint counter) + { + if (stateSize + 2 * matches.length > stackAvail) + { + newStack(); + } + *cast(State*)&memory[lastState] = + State(index, pc, counter, infiniteNesting); + lastState += stateSize; + memory[lastState .. lastState + 2 * matches.length] = (cast(size_t[]) matches)[]; + lastState += 2*matches.length; + debug(std_regex_matcher) + writefln("Saved(pc=%s) front: %s src: %s", + pc, front, s[index .. s.lastIndex]); + } + + //helper function, restores engine state + bool popState() + { + if (!lastState && !prevStack()) + return false; + lastState -= 2*matches.length; + auto pm = cast(size_t[]) matches; + pm[] = memory[lastState .. lastState + 2 * matches.length]; + lastState -= stateSize; + State* state = cast(State*)&memory[lastState]; + index = state.index; + pc = state.pc; + counter = state.counter; + infiniteNesting = state.infiniteNesting; + debug(std_regex_matcher) + { + writefln("Restored matches", front, s[index .. s.lastIndex]); + foreach (i, m; matches) + writefln("Sub(%d) : %s..%s", i, m.begin, m.end); + } + s.reset(index); + next(); + debug(std_regex_matcher) + writefln("Backtracked (pc=%s) front: %s src: %s", + pc, front, s[index .. s.lastIndex]); + return true; + } + } + } +} + +//very shitty string formatter, $$ replaced with next argument converted to string +@trusted string ctSub( U...)(string format, U args) +{ + import std.conv : to; + bool seenDollar; + foreach (i, ch; format) + { + if (ch == '$') + { + if (seenDollar) + { + static if (args.length > 0) + { + return format[0 .. i - 1] ~ to!string(args[0]) + ~ ctSub(format[i + 1 .. $], args[1 .. $]); + } + else + assert(0); + } + else + seenDollar = true; + } + else + seenDollar = false; + + } + return format; +} + +alias Sequence(int B, int E) = staticIota!(B, E); + +struct CtContext +{ + import std.conv : to, text; + //dirty flags + bool counter; + //to mark the portion of matches to save + int match, total_matches; + int reserved; + CodepointSet[] charsets; + + + //state of codegenerator + static struct CtState + { + string code; + int addr; + } + + this(Char)(Regex!Char re) + { + match = 1; + reserved = 1; //first match is skipped + total_matches = re.ngroup; + charsets = re.charsets; + } + + CtContext lookaround(uint s, uint e) + { + CtContext ct; + ct.total_matches = e - s; + ct.match = 1; + return ct; + } + + //restore state having current context + string restoreCode() + { + string text; + //stack is checked in L_backtrack + text ~= counter + ? " + stackPop(counter);" + : " + counter = 0;"; + if (match < total_matches) + { + text ~= ctSub(" + stackPop(matches[$$..$$]);", reserved, match); + text ~= ctSub(" + matches[$$..$] = typeof(matches[0]).init;", match); + } + else + text ~= ctSub(" + stackPop(matches[$$..$]);", reserved); + return text; + } + + //save state having current context + string saveCode(uint pc, string count_expr="counter") + { + string text = ctSub(" + if (stackAvail < $$*(Group!(DataIndex)).sizeof/size_t.sizeof + $$) + { + newStack(); + }", match - reserved, cast(int) counter + 2); + if (match < total_matches) + text ~= ctSub(" + stackPush(matches[$$..$$]);", reserved, match); + else + text ~= ctSub(" + stackPush(matches[$$..$]);", reserved); + text ~= counter ? ctSub(" + stackPush($$);", count_expr) : ""; + text ~= ctSub(" + stackPush(index); stackPush($$); \n", pc); + return text; + } + + // + CtState ctGenBlock(Bytecode[] ir, int addr) + { + CtState result; + result.addr = addr; + while (!ir.empty) + { + auto n = ctGenGroup(ir, result.addr); + result.code ~= n.code; + result.addr = n.addr; + } + return result; + } + + // + CtState ctGenGroup(ref Bytecode[] ir, int addr) + { + import std.algorithm.comparison : max; + auto bailOut = "goto L_backtrack;"; + auto nextInstr = ctSub("goto case $$;", addr+1); + CtState r; + assert(!ir.empty); + switch (ir[0].code) + { + case IR.InfiniteStart, IR.InfiniteBloomStart,IR.InfiniteQStart, IR.RepeatStart, IR.RepeatQStart: + immutable infLoop = + ir[0].code == IR.InfiniteStart || ir[0].code == IR.InfiniteQStart || + ir[0].code == IR.InfiniteBloomStart; + + counter = counter || + ir[0].code == IR.RepeatStart || ir[0].code == IR.RepeatQStart; + immutable len = ir[0].data; + auto nir = ir[ir[0].length .. ir[0].length+len]; + r = ctGenBlock(nir, addr+1); + //start/end codegen + //r.addr is at last test+ jump of loop, addr+1 is body of loop + nir = ir[ir[0].length + len .. $]; + r.code = ctGenFixupCode(ir[0 .. ir[0].length], addr, r.addr) ~ r.code; + r.code ~= ctGenFixupCode(nir, r.addr, addr+1); + r.addr += 2; //account end instruction + restore state + ir = nir; + break; + case IR.OrStart: + immutable len = ir[0].data; + auto nir = ir[ir[0].length .. ir[0].length+len]; + r = ctGenAlternation(nir, addr); + ir = ir[ir[0].length + len .. $]; + assert(ir[0].code == IR.OrEnd); + ir = ir[ir[0].length..$]; + break; + case IR.LookaheadStart: + case IR.NeglookaheadStart: + case IR.LookbehindStart: + case IR.NeglookbehindStart: + immutable len = ir[0].data; + immutable behind = ir[0].code == IR.LookbehindStart || ir[0].code == IR.NeglookbehindStart; + immutable negative = ir[0].code == IR.NeglookaheadStart || ir[0].code == IR.NeglookbehindStart; + string fwdType = "typeof(fwdMatcher(matcher, []))"; + string bwdType = "typeof(bwdMatcher(matcher, []))"; + string fwdCreate = "fwdMatcher(matcher, mem)"; + string bwdCreate = "bwdMatcher(matcher, mem)"; + immutable start = IRL!(IR.LookbehindStart); + immutable end = IRL!(IR.LookbehindStart)+len+IRL!(IR.LookaheadEnd); + CtContext context = lookaround(ir[1].raw, ir[2].raw); //split off new context + auto slice = ir[start .. end]; + r.code ~= ctSub(` + case $$: //fake lookaround "atom" + static if (typeof(matcher.s).isLoopback) + alias Lookaround = $$; + else + alias Lookaround = $$; + static bool matcher_$$(ref Lookaround matcher) @trusted + { + //(neg)lookaround piece start + $$ + //(neg)lookaround piece ends + } + auto save = index; + auto mem = malloc(initialMemory(re))[0 .. initialMemory(re)]; + scope(exit) free(mem.ptr); + static if (typeof(matcher.s).isLoopback) + auto lookaround = $$; + else + auto lookaround = $$; + lookaround.matches = matches[$$..$$]; + lookaround.backrefed = backrefed.empty ? matches : backrefed; + lookaround.nativeFn = &matcher_$$; //hookup closure's binary code + int match = $$; + s.reset(save); + next(); + if (match) + $$ + else + $$`, addr, + behind ? fwdType : bwdType, behind ? bwdType : fwdType, + addr, context.ctGenRegEx(slice), + behind ? fwdCreate : bwdCreate, behind ? bwdCreate : fwdCreate, + ir[1].raw, ir[2].raw, //start - end of matches slice + addr, + negative ? "!lookaround.matchImpl()" : "lookaround.matchImpl()", + nextInstr, bailOut); + ir = ir[end .. $]; + r.addr = addr + 1; + break; + case IR.LookaheadEnd: case IR.NeglookaheadEnd: + case IR.LookbehindEnd: case IR.NeglookbehindEnd: + ir = ir[IRL!(IR.LookaheadEnd) .. $]; + r.addr = addr; + break; + default: + assert(ir[0].isAtom, text(ir[0].mnemonic)); + r = ctGenAtom(ir, addr); + } + return r; + } + + //generate source for bytecode contained in OrStart ... OrEnd + CtState ctGenAlternation(Bytecode[] ir, int addr) + { + CtState[] pieces; + CtState r; + enum optL = IRL!(IR.Option); + for (;;) + { + assert(ir[0].code == IR.Option); + auto len = ir[0].data; + if (optL+len < ir.length && ir[optL+len].code == IR.Option)//not a last option + { + auto nir = ir[optL .. optL+len-IRL!(IR.GotoEndOr)]; + r = ctGenBlock(nir, addr+2);//space for Option + restore state + //r.addr+1 to account GotoEndOr at end of branch + r.code = ctGenFixupCode(ir[0 .. ir[0].length], addr, r.addr+1) ~ r.code; + addr = r.addr+1;//leave space for GotoEndOr + pieces ~= r; + ir = ir[optL + len .. $]; + } + else + { + pieces ~= ctGenBlock(ir[optL..$], addr); + addr = pieces[$-1].addr; + break; + } + } + r = pieces[0]; + for (uint i = 1; i < pieces.length; i++) + { + r.code ~= ctSub(` + case $$: + goto case $$; `, pieces[i-1].addr, addr); + r.code ~= pieces[i].code; + } + r.addr = addr; + return r; + } + + // generate fixup code for instruction in ir, + // fixup means it has an alternative way for control flow + string ctGenFixupCode(Bytecode[] ir, int addr, int fixup) + { + return ctGenFixupCode(ir, addr, fixup); // call ref Bytecode[] version + } + string ctGenFixupCode(ref Bytecode[] ir, int addr, int fixup) + { + string r; + string testCode; + r = ctSub(` + case $$: debug(std_regex_matcher) writeln("#$$");`, + addr, addr); + switch (ir[0].code) + { + case IR.InfiniteStart, IR.InfiniteQStart, IR.InfiniteBloomStart: + r ~= ctSub( ` + goto case $$;`, fixup); + ir = ir[ir[0].length..$]; + break; + case IR.InfiniteEnd: + testCode = ctQuickTest(ir[IRL!(IR.InfiniteEnd) .. $],addr + 1); + r ~= ctSub( ` + if (merge[$$+counter].mark(index)) + { + // merged! + goto L_backtrack; + } + + $$ + { + $$ + } + goto case $$; + case $$: //restore state and go out of loop + $$ + goto case;`, ir[1].raw, testCode, saveCode(addr+1), fixup, + addr+1, restoreCode()); + ir = ir[ir[0].length..$]; + break; + case IR.InfiniteBloomEnd: + //TODO: check bloom filter and skip on failure + testCode = ctQuickTest(ir[IRL!(IR.InfiniteBloomEnd) .. $],addr + 1); + r ~= ctSub( ` + if (merge[$$+counter].mark(index)) + { + // merged! + goto L_backtrack; + } + + $$ + { + $$ + } + goto case $$; + case $$: //restore state and go out of loop + $$ + goto case;`, ir[1].raw, testCode, saveCode(addr+1), fixup, + addr+1, restoreCode()); + ir = ir[ir[0].length..$]; + break; + case IR.InfiniteQEnd: + testCode = ctQuickTest(ir[IRL!(IR.InfiniteEnd) .. $],addr + 1); + auto altCode = testCode.length ? ctSub("else goto case $$;", fixup) : ""; + r ~= ctSub( ` + if (merge[$$+counter].mark(index)) + { + // merged! + goto L_backtrack; + } + + $$ + { + $$ + goto case $$; + } + $$ + case $$://restore state and go inside loop + $$ + goto case $$;`, ir[1].raw, + testCode, saveCode(addr+1), addr+2, altCode, + addr+1, restoreCode(), fixup); + ir = ir[ir[0].length..$]; + break; + case IR.RepeatStart, IR.RepeatQStart: + r ~= ctSub( ` + goto case $$;`, fixup); + ir = ir[ir[0].length..$]; + break; + case IR.RepeatEnd, IR.RepeatQEnd: + //len, step, min, max + immutable len = ir[0].data; + immutable step = ir[2].raw; + immutable min = ir[3].raw; + immutable max = ir[4].raw; + r ~= ctSub(` + if (merge[$$+counter].mark(index)) + { + // merged! + goto L_backtrack; + } + if (counter < $$) + { + debug(std_regex_matcher) writeln("RepeatEnd min case pc=", $$); + counter += $$; + goto case $$; + }`, ir[1].raw, min, addr, step, fixup); + if (ir[0].code == IR.RepeatEnd) + { + string counter_expr = ctSub("counter % $$", step); + r ~= ctSub(` + else if (counter < $$) + { + $$ + counter += $$; + goto case $$; + }`, max, saveCode(addr+1, counter_expr), step, fixup); + } + else + { + string counter_expr = ctSub("counter % $$", step); + r ~= ctSub(` + else if (counter < $$) + { + $$ + counter = counter % $$; + goto case $$; + }`, max, saveCode(addr+1,counter_expr), step, addr+2); + } + r ~= ctSub(` + else + { + counter = counter % $$; + goto case $$; + } + case $$: //restore state + $$ + goto case $$;`, step, addr+2, addr+1, restoreCode(), + ir[0].code == IR.RepeatEnd ? addr+2 : fixup ); + ir = ir[ir[0].length..$]; + break; + case IR.Option: + r ~= ctSub( ` + { + $$ + } + goto case $$; + case $$://restore thunk to go to the next group + $$ + goto case $$;`, saveCode(addr+1), addr+2, + addr+1, restoreCode(), fixup); + ir = ir[ir[0].length..$]; + break; + default: + assert(0, text(ir[0].mnemonic)); + } + return r; + } + + + string ctQuickTest(Bytecode[] ir, int id) + { + uint pc = 0; + while (pc < ir.length && ir[pc].isAtom) + { + if (ir[pc].code == IR.GroupStart || ir[pc].code == IR.GroupEnd) + { + pc++; + } + else if (ir[pc].code == IR.Backref) + break; + else + { + auto code = ctAtomCode(ir[pc..$], -1); + return ctSub(` + int test_$$() + { + $$ //$$ + } + if (test_$$() >= 0)`, id, code.ptr ? code : "return 0;", + ir[pc].mnemonic, id); + } + } + return ""; + } + + //process & generate source for simple bytecodes at front of ir using address addr + CtState ctGenAtom(ref Bytecode[] ir, int addr) + { + CtState result; + result.code = ctAtomCode(ir, addr); + ir.popFrontN(ir[0].code == IR.OrChar ? ir[0].sequence : ir[0].length); + result.addr = addr + 1; + return result; + } + + //D code for atom at ir using address addr, addr < 0 means quickTest + string ctAtomCode(Bytecode[] ir, int addr) + { + string code; + string bailOut, nextInstr; + if (addr < 0) + { + bailOut = "return -1;"; + nextInstr = "return 0;"; + } + else + { + bailOut = "goto L_backtrack;"; + nextInstr = ctSub("goto case $$;", addr+1); + code ~= ctSub( ` + case $$: debug(std_regex_matcher) writeln("#$$"); + `, addr, addr); + } + switch (ir[0].code) + { + case IR.OrChar://assumes IRL!(OrChar) == 1 + code ~= ctSub(` + if (atEnd) + $$`, bailOut); + immutable len = ir[0].sequence; + for (uint i = 0; i < len; i++) + { + code ~= ctSub( ` + if (front == $$) + { + $$ + $$ + }`, ir[i].data, addr >= 0 ? "next();" :"", nextInstr); + } + code ~= ctSub( ` + $$`, bailOut); + break; + case IR.Char: + code ~= ctSub( ` + if (atEnd || front != $$) + $$ + $$ + $$`, ir[0].data, bailOut, addr >= 0 ? "next();" :"", nextInstr); + break; + case IR.Any: + code ~= ctSub( ` + if (atEnd || (!(re.flags & RegexOption.singleline) + && (front == '\r' || front == '\n'))) + $$ + $$ + $$`, bailOut, addr >= 0 ? "next();" :"",nextInstr); + break; + case IR.CodepointSet: + if (charsets.length) + { + string name = `func_`~to!string(addr+1); + string funcCode = charsets[ir[0].data].toSourceCode(name); + code ~= ctSub( ` + static $$ + if (atEnd || !$$(front)) + $$ + $$ + $$`, funcCode, name, bailOut, addr >= 0 ? "next();" :"", nextInstr); + } + else + code ~= ctSub( ` + if (atEnd || !re.charsets[$$].scanFor(front)) + $$ + $$ + $$`, ir[0].data, bailOut, addr >= 0 ? "next();" :"", nextInstr); + break; + case IR.Trie: + if (charsets.length && charsets[ir[0].data].byInterval.length <= 8) + goto case IR.CodepointSet; + code ~= ctSub( ` + if (atEnd || !re.matchers[$$][front]) + $$ + $$ + $$`, ir[0].data, bailOut, addr >= 0 ? "next();" :"", nextInstr); + break; + case IR.Wordboundary: + code ~= ctSub( ` + dchar back; + DataIndex bi; + if (atStart && wordMatcher[front]) + { + $$ + } + else if (atEnd && s.loopBack(index).nextChar(back, bi) + && wordMatcher[back]) + { + $$ + } + else if (s.loopBack(index).nextChar(back, bi)) + { + bool af = wordMatcher[front]; + bool ab = wordMatcher[back]; + if (af ^ ab) + { + $$ + } + } + $$`, nextInstr, nextInstr, nextInstr, bailOut); + break; + case IR.Notwordboundary: + code ~= ctSub( ` + dchar back; + DataIndex bi; + //at start & end of input + if (atStart && wordMatcher[front]) + $$ + else if (atEnd && s.loopBack(index).nextChar(back, bi) + && wordMatcher[back]) + $$ + else if (s.loopBack(index).nextChar(back, bi)) + { + bool af = wordMatcher[front]; + bool ab = wordMatcher[back]; + if (af ^ ab) + $$ + } + $$`, bailOut, bailOut, bailOut, nextInstr); + + break; + case IR.Bol: + code ~= ctSub(` + dchar back; + DataIndex bi; + if (atStart || (s.loopBack(index).nextChar(back,bi) + && endOfLine(back, front == '\n'))) + { + debug(std_regex_matcher) writeln("BOL matched"); + $$ + } + else + $$`, nextInstr, bailOut); + + break; + case IR.Bof: + code ~= ctSub(` + if (atStart) + { + debug(std_regex_matcher) writeln("BOF matched"); + $$ + } + else + $$`, nextInstr, bailOut); + break; + case IR.Eol: + code ~= ctSub(` + dchar back; + DataIndex bi; + debug(std_regex_matcher) writefln("EOL (front 0x%x) %s", front, s[index .. s.lastIndex]); + //no matching inside \r\n + if (atEnd || (endOfLine(front, s.loopBack(index).nextChar(back,bi) + && back == '\r'))) + { + debug(std_regex_matcher) writeln("EOL matched"); + $$ + } + else + $$`, nextInstr, bailOut); + break; + case IR.Eof: + code ~= ctSub(` + if (atEnd) + { + debug(std_regex_matcher) writeln("BOF matched"); + $$ + } + else + $$`, nextInstr, bailOut); + break; + case IR.GroupStart: + code ~= ctSub(` + matches[$$].begin = index; + $$`, ir[0].data, nextInstr); + match = ir[0].data+1; + break; + case IR.GroupEnd: + code ~= ctSub(` + matches[$$].end = index; + $$`, ir[0].data, nextInstr); + break; + case IR.Backref: + string mStr = "auto referenced = "; + mStr ~= ir[0].localRef + ? ctSub("s[matches[$$].begin .. matches[$$].end];", + ir[0].data, ir[0].data) + : ctSub("s[backrefed[$$].begin .. backrefed[$$].end];", + ir[0].data, ir[0].data); + code ~= ctSub( ` + $$ + while (!atEnd && !referenced.empty && front == referenced.front) + { + next(); + referenced.popFront(); + } + if (referenced.empty) + $$ + else + $$`, mStr, nextInstr, bailOut); + break; + case IR.Nop: + case IR.End: + break; + default: + assert(0, text(ir[0].mnemonic, " is not supported yet")); + } + return code; + } + + //generate D code for the whole regex + public string ctGenRegEx(Bytecode[] ir) + { + auto bdy = ctGenBlock(ir, 0); + auto r = ` + import core.stdc.stdlib; + with(matcher) + { + pc = 0; + counter = 0; + lastState = 0; + matches[] = Group!DataIndex.init; + auto start = s._index;`; + r ~= ` + goto StartLoop; + debug(std_regex_matcher) writeln("Try CT matching starting at ",s[index .. s.lastIndex]); + L_backtrack: + if (lastState || prevStack()) + { + stackPop(pc); + stackPop(index); + s.reset(index); + next(); + } + else + { + s.reset(start); + return false; + } + StartLoop: + switch (pc) + { + `; + r ~= bdy.code; + r ~= ctSub(` + case $$: break;`,bdy.addr); + r ~= ` + default: + assert(0); + } + // cleanup stale stack blocks + while (prevStack()) {} + return true; + } + `; + return r; + } + +} + +string ctGenRegExCode(Char)(Regex!Char re) +{ + auto context = CtContext(re); + return context.ctGenRegEx(re.ir); +} diff --git a/libphobos/src/std/regex/internal/generator.d b/libphobos/src/std/regex/internal/generator.d new file mode 100644 index 0000000..6831e59 --- /dev/null +++ b/libphobos/src/std/regex/internal/generator.d @@ -0,0 +1,187 @@ +/* + Generators - components that generate strings for a given regex pattern. + + For the moment undocumented, and is subject to change. +*/ +module std.regex.internal.generator; + +/* + Useful utility for self-testing, an infinite range of string samples + that _have_ to match given compiled regex. + Caveats: supports only a simple subset of bytecode. +*/ +@trusted private struct SampleGenerator(Char) +{ + import std.array : appender, Appender; + import std.format : formattedWrite; + import std.random : Xorshift; + import std.regex.internal.ir : Regex, IR, IRL; + import std.utf : isValidDchar, byChar; + Regex!Char re; + Appender!(char[]) app; + uint limit, seed; + Xorshift gen; + //generator for pattern r, with soft maximum of threshold elements + //and a given random seed + this(ref Regex!Char r, uint threshold, uint randomSeed) + { + re = r; + limit = threshold; + seed = randomSeed; + app = appender!(Char[])(); + compose(); + } + + uint rand(uint x) + { + uint r = gen.front % x; + gen.popFront(); + return r; + } + + void compose() + { + uint pc = 0, counter = 0, dataLenOld = uint.max; + for (;;) + { + switch (re.ir[pc].code) + { + case IR.Char: + formattedWrite(app,"%s", cast(dchar) re.ir[pc].data); + pc += IRL!(IR.Char); + break; + case IR.OrChar: + uint len = re.ir[pc].sequence; + formattedWrite(app, "%s", cast(dchar) re.ir[pc + rand(len)].data); + pc += len; + break; + case IR.CodepointSet: + case IR.Trie: + auto set = re.charsets[re.ir[pc].data]; + auto x = rand(cast(uint) set.byInterval.length); + auto y = rand(set.byInterval[x].b - set.byInterval[x].a); + formattedWrite(app, "%s", cast(dchar)(set.byInterval[x].a+y)); + pc += IRL!(IR.CodepointSet); + break; + case IR.Any: + uint x; + do + { + x = rand(0x11_000); + }while (x == '\r' || x == '\n' || !isValidDchar(x)); + formattedWrite(app, "%s", cast(dchar) x); + pc += IRL!(IR.Any); + break; + case IR.GotoEndOr: + pc += IRL!(IR.GotoEndOr)+re.ir[pc].data; + assert(re.ir[pc].code == IR.OrEnd); + goto case; + case IR.OrEnd: + pc += IRL!(IR.OrEnd); + break; + case IR.OrStart: + pc += IRL!(IR.OrStart); + goto case; + case IR.Option: + uint next = pc + re.ir[pc].data + IRL!(IR.Option); + uint nOpt = 0; + //queue next Option + while (re.ir[next].code == IR.Option) + { + nOpt++; + next += re.ir[next].data + IRL!(IR.Option); + } + nOpt++; + nOpt = rand(nOpt); + for (;nOpt; nOpt--) + { + pc += re.ir[pc].data + IRL!(IR.Option); + } + assert(re.ir[pc].code == IR.Option); + pc += IRL!(IR.Option); + break; + case IR.RepeatStart:case IR.RepeatQStart: + pc += IRL!(IR.RepeatStart)+re.ir[pc].data; + goto case IR.RepeatEnd; + case IR.RepeatEnd: + case IR.RepeatQEnd: + uint len = re.ir[pc].data; + uint step = re.ir[pc+2].raw; + uint min = re.ir[pc+3].raw; + if (counter < min) + { + counter += step; + pc -= len; + break; + } + uint max = re.ir[pc+4].raw; + if (counter < max) + { + if (app.data.length < limit && rand(3) > 0) + { + pc -= len; + counter += step; + } + else + { + counter = counter%step; + pc += IRL!(IR.RepeatEnd); + } + } + else + { + counter = counter%step; + pc += IRL!(IR.RepeatEnd); + } + break; + case IR.InfiniteStart, IR.InfiniteBloomStart, IR.InfiniteQStart: + pc += re.ir[pc].data + IRL!(IR.InfiniteStart); + goto case IR.InfiniteEnd; //both Q and non-Q + case IR.InfiniteEnd, IR.InfiniteBloomEnd, IR.InfiniteQEnd: + uint len = re.ir[pc].data; + if (app.data.length == dataLenOld) + { + pc += IRL!(IR.InfiniteEnd); + break; + } + dataLenOld = cast(uint) app.data.length; + if (app.data.length < limit && rand(3) > 0) + pc = pc - len; + else + pc = pc + re.ir[pc].length; + break; + case IR.GroupStart, IR.GroupEnd: + pc += IRL!(IR.GroupStart); + break; + case IR.Bol, IR.Wordboundary, IR.Notwordboundary: + case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart: + default: + return; + } + } + } + + @property Char[] front() + { + return app.data; + } + + enum empty = false; + + void popFront() + { + app.shrinkTo(0); + compose(); + } +} + +@system unittest +{ + import std.range, std.regex; + auto re = regex(`P[a-z]{3,}q`); + auto gen = SampleGenerator!char(re, 20, 3141592); + static assert(isInputRange!(typeof(gen))); + //@@@BUG@@@ somehow gen.take(1_000) doesn't work + foreach (v; take(gen, 1_000)) + assert(v.match(re)); +} diff --git a/libphobos/src/std/regex/internal/ir.d b/libphobos/src/std/regex/internal/ir.d new file mode 100644 index 0000000..28b1998 --- /dev/null +++ b/libphobos/src/std/regex/internal/ir.d @@ -0,0 +1,788 @@ +/* + Implementation of std.regex IR, an intermediate representation + of a regular expression pattern. + + This is a common ground between frontend regex component (parser) + and backend components - generators, matchers and other "filters". +*/ +module std.regex.internal.ir; + +package(std.regex): + +import std.exception, std.meta, std.range.primitives, std.traits, std.uni; + +debug(std_regex_parser) import std.stdio; +// just a common trait, may be moved elsewhere +alias BasicElementOf(Range) = Unqual!(ElementEncodingType!Range); + +enum privateUseStart = '\U000F0000', privateUseEnd ='\U000FFFFD'; + +// heuristic value determines maximum CodepointSet length suitable for linear search +enum maxCharsetUsed = 6; + +// another variable to tweak behavior of caching generated Tries for character classes +enum maxCachedMatchers = 8; + +alias Trie = CodepointSetTrie!(13, 8); +alias makeTrie = codepointSetTrie!(13, 8); + +CharMatcher[CodepointSet] matcherCache; + +//accessor with caching +@trusted CharMatcher getMatcher(CodepointSet set) +{// @@@BUG@@@ 6357 almost all properties of AA are not @safe + if (__ctfe || maxCachedMatchers == 0) + return CharMatcher(set); + else + { + auto p = set in matcherCache; + if (p) + return *p; + if (matcherCache.length == maxCachedMatchers) + { + // flush enmatchers in trieCache + matcherCache = null; + } + return (matcherCache[set] = CharMatcher(set)); + } +} + +@trusted auto memoizeExpr(string expr)() +{ + if (__ctfe) + return mixin(expr); + alias T = typeof(mixin(expr)); + static T slot; + static bool initialized; + if (!initialized) + { + slot = mixin(expr); + initialized = true; + } + return slot; +} + +//property for \w character class +@property CodepointSet wordCharacter() +{ + return memoizeExpr!("unicode.Alphabetic | unicode.Mn | unicode.Mc + | unicode.Me | unicode.Nd | unicode.Pc")(); +} + +@property CharMatcher wordMatcher() +{ + return memoizeExpr!("CharMatcher(wordCharacter)")(); +} + +// some special Unicode white space characters +private enum NEL = '\u0085', LS = '\u2028', PS = '\u2029'; + +// Characters that need escaping in string posed as regular expressions +alias Escapables = AliasSeq!('[', ']', '\\', '^', '$', '.', '|', '?', ',', '-', + ';', ':', '#', '&', '%', '/', '<', '>', '`', '*', '+', '(', ')', '{', '}', '~'); + +//Regular expression engine/parser options: +// global - search all nonoverlapping matches in input +// casefold - case insensitive matching, do casefolding on match in unicode mode +// freeform - ignore whitespace in pattern, to match space use [ ] or \s +// multiline - switch ^, $ detect start and end of linesinstead of just start and end of input +enum RegexOption: uint { + global = 0x1, + casefold = 0x2, + freeform = 0x4, + nonunicode = 0x8, + multiline = 0x10, + singleline = 0x20 +} +//do not reorder this list +alias RegexOptionNames = AliasSeq!('g', 'i', 'x', 'U', 'm', 's'); +static assert( RegexOption.max < 0x80); +// flags that allow guide execution of engine +enum RegexInfo : uint { oneShot = 0x80 } + +// IR bit pattern: 0b1_xxxxx_yy +// where yy indicates class of instruction, xxxxx for actual operation code +// 00: atom, a normal instruction +// 01: open, opening of a group, has length of contained IR in the low bits +// 10: close, closing of a group, has length of contained IR in the low bits +// 11 unused +// +// Loops with Q (non-greedy, with ? mark) must have the same size / other properties as non Q version +// Possible changes: +//* merge group, option, infinite/repeat start (to never copy during parsing of (a|b){1,2}) +//* reorganize groups to make n args easier to find, or simplify the check for groups of similar ops +// (like lookaround), or make it easier to identify hotspots. + +enum IR:uint { + Char = 0b1_00000_00, //a character + Any = 0b1_00001_00, //any character + CodepointSet = 0b1_00010_00, //a most generic CodepointSet [...] + Trie = 0b1_00011_00, //CodepointSet implemented as Trie + //match with any of a consecutive OrChar's in this sequence + //(used for case insensitive match) + //OrChar holds in upper two bits of data total number of OrChars in this _sequence_ + //the drawback of this representation is that it is difficult + // to detect a jump in the middle of it + OrChar = 0b1_00100_00, + Nop = 0b1_00101_00, //no operation (padding) + End = 0b1_00110_00, //end of program + Bol = 0b1_00111_00, //beginning of a line ^ + Eol = 0b1_01000_00, //end of a line $ + Wordboundary = 0b1_01001_00, //boundary of a word + Notwordboundary = 0b1_01010_00, //not a word boundary + Backref = 0b1_01011_00, //backreference to a group (that has to be pinned, i.e. locally unique) (group index) + GroupStart = 0b1_01100_00, //start of a group (x) (groupIndex+groupPinning(1bit)) + GroupEnd = 0b1_01101_00, //end of a group (x) (groupIndex+groupPinning(1bit)) + Option = 0b1_01110_00, //start of an option within an alternation x | y (length) + GotoEndOr = 0b1_01111_00, //end of an option (length of the rest) + Bof = 0b1_10000_00, //begining of "file" (string) ^ + Eof = 0b1_10001_00, //end of "file" (string) $ + //... any additional atoms here + + OrStart = 0b1_00000_01, //start of alternation group (length) + OrEnd = 0b1_00000_10, //end of the or group (length,mergeIndex) + //with this instruction order + //bit mask 0b1_00001_00 could be used to test/set greediness + InfiniteStart = 0b1_00001_01, //start of an infinite repetition x* (length) + InfiniteEnd = 0b1_00001_10, //end of infinite repetition x* (length,mergeIndex) + InfiniteQStart = 0b1_00010_01, //start of a non eager infinite repetition x*? (length) + InfiniteQEnd = 0b1_00010_10, //end of non eager infinite repetition x*? (length,mergeIndex) + InfiniteBloomStart = 0b1_00011_01, //start of an filtered infinite repetition x* (length) + InfiniteBloomEnd = 0b1_00011_10, //end of filtered infinite repetition x* (length,mergeIndex) + RepeatStart = 0b1_00100_01, //start of a {n,m} repetition (length) + RepeatEnd = 0b1_00100_10, //end of x{n,m} repetition (length,step,minRep,maxRep) + RepeatQStart = 0b1_00101_01, //start of a non eager x{n,m}? repetition (length) + RepeatQEnd = 0b1_00101_10, //end of non eager x{n,m}? repetition (length,step,minRep,maxRep) + + // + LookaheadStart = 0b1_00110_01, //begin of the lookahead group (length) + LookaheadEnd = 0b1_00110_10, //end of a lookahead group (length) + NeglookaheadStart = 0b1_00111_01, //start of a negative lookahead (length) + NeglookaheadEnd = 0b1_00111_10, //end of a negative lookahead (length) + LookbehindStart = 0b1_01000_01, //start of a lookbehind (length) + LookbehindEnd = 0b1_01000_10, //end of a lookbehind (length) + NeglookbehindStart = 0b1_01001_01, //start of a negative lookbehind (length) + NeglookbehindEnd = 0b1_01001_10, //end of negative lookbehind (length) +} + +//a shorthand for IR length - full length of specific opcode evaluated at compile time +template IRL(IR code) +{ + enum uint IRL = lengthOfIR(code); +} +static assert(IRL!(IR.LookaheadStart) == 3); + +//how many parameters follow the IR, should be optimized fixing some IR bits +int immediateParamsIR(IR i){ + switch (i) + { + case IR.OrEnd,IR.InfiniteEnd,IR.InfiniteQEnd: + return 1; // merge table index + case IR.InfiniteBloomEnd: + return 2; // bloom filter index + merge table index + case IR.RepeatEnd, IR.RepeatQEnd: + return 4; + case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart: + return 2; // start-end of captures used + default: + return 0; + } +} + +//full length of IR instruction inlcuding all parameters that might follow it +int lengthOfIR(IR i) +{ + return 1 + immediateParamsIR(i); +} + +//full length of the paired IR instruction inlcuding all parameters that might follow it +int lengthOfPairedIR(IR i) +{ + return 1 + immediateParamsIR(pairedIR(i)); +} + +//if the operation has a merge point (this relies on the order of the ops) +bool hasMerge(IR i) +{ + return (i&0b11)==0b10 && i <= IR.RepeatQEnd; +} + +//is an IR that opens a "group" +bool isStartIR(IR i) +{ + return (i&0b11)==0b01; +} + +//is an IR that ends a "group" +bool isEndIR(IR i) +{ + return (i&0b11)==0b10; +} + +//is a standalone IR +bool isAtomIR(IR i) +{ + return (i&0b11)==0b00; +} + +//makes respective pair out of IR i, swapping start/end bits of instruction +IR pairedIR(IR i) +{ + assert(isStartIR(i) || isEndIR(i)); + return cast(IR)(i ^ 0b11); +} + +//encoded IR instruction +struct Bytecode +{ + uint raw; + //natural constraints + enum maxSequence = 2+4; + enum maxData = 1 << 22; + enum maxRaw = 1 << 31; + + this(IR code, uint data) + { + assert(data < (1 << 22) && code < 256); + raw = code << 24 | data; + } + + this(IR code, uint data, uint seq) + { + assert(data < (1 << 22) && code < 256 ); + assert(seq >= 2 && seq < maxSequence); + raw = code << 24 | (seq - 2)<<22 | data; + } + + //store raw data + static Bytecode fromRaw(uint data) + { + Bytecode t; + t.raw = data; + return t; + } + + //bit twiddling helpers + //0-arg template due to @@@BUG@@@ 10985 + @property uint data()() const { return raw & 0x003f_ffff; } + + @property void data()(uint val) + { + raw = (raw & ~0x003f_ffff) | (val & 0x003f_ffff); + } + + //ditto + //0-arg template due to @@@BUG@@@ 10985 + @property uint sequence()() const { return 2 + (raw >> 22 & 0x3); } + + //ditto + //0-arg template due to @@@BUG@@@ 10985 + @property IR code()() const { return cast(IR)(raw >> 24); } + + //ditto + @property bool hotspot() const { return hasMerge(code); } + + //test the class of this instruction + @property bool isAtom() const { return isAtomIR(code); } + + //ditto + @property bool isStart() const { return isStartIR(code); } + + //ditto + @property bool isEnd() const { return isEndIR(code); } + + //number of arguments for this instruction + @property int args() const { return immediateParamsIR(code); } + + //mark this GroupStart or GroupEnd as referenced in backreference + void setBackrefence() + { + assert(code == IR.GroupStart || code == IR.GroupEnd); + raw = raw | 1 << 23; + } + + //is referenced + @property bool backreference() const + { + assert(code == IR.GroupStart || code == IR.GroupEnd); + return cast(bool)(raw & 1 << 23); + } + + //mark as local reference (for backrefs in lookarounds) + void setLocalRef() + { + assert(code == IR.Backref); + raw = raw | 1 << 23; + } + + //is a local ref + @property bool localRef() const + { + assert(code == IR.Backref); + return cast(bool)(raw & 1 << 23); + } + + //human readable name of instruction + @trusted @property string mnemonic()() const + {//@@@BUG@@@ to is @system + import std.conv : to; + return to!string(code); + } + + //full length of instruction + @property uint length() const + { + return lengthOfIR(code); + } + + //full length of respective start/end of this instruction + @property uint pairedLength() const + { + return lengthOfPairedIR(code); + } + + //returns bytecode of paired instruction (assuming this one is start or end) + @property Bytecode paired() const + {//depends on bit and struct layout order + assert(isStart || isEnd); + return Bytecode.fromRaw(raw ^ 0b11 << 24); + } + + //gets an index into IR block of the respective pair + uint indexOfPair(uint pc) const + { + assert(isStart || isEnd); + return isStart ? pc + data + length : pc - data - lengthOfPairedIR(code); + } +} + +static assert(Bytecode.sizeof == 4); + + +//index entry structure for name --> number of submatch +struct NamedGroup +{ + string name; + uint group; +} + +//holds pair of start-end markers for a submatch +struct Group(DataIndex) +{ + DataIndex begin, end; + @trusted string toString()() const + { + import std.array : appender; + import std.format : formattedWrite; + auto a = appender!string(); + formattedWrite(a, "%s..%s", begin, end); + return a.data; + } +} + +//debugging tool, prints out instruction along with opcodes +@trusted string disassemble(in Bytecode[] irb, uint pc, in NamedGroup[] dict=[]) +{ + import std.array : appender; + import std.format : formattedWrite; + auto output = appender!string(); + formattedWrite(output,"%s", irb[pc].mnemonic); + switch (irb[pc].code) + { + case IR.Char: + formattedWrite(output, " %s (0x%x)",cast(dchar) irb[pc].data, irb[pc].data); + break; + case IR.OrChar: + formattedWrite(output, " %s (0x%x) seq=%d", cast(dchar) irb[pc].data, irb[pc].data, irb[pc].sequence); + break; + case IR.RepeatStart, IR.InfiniteStart, IR.InfiniteBloomStart, + IR.Option, IR.GotoEndOr, IR.OrStart: + //forward-jump instructions + uint len = irb[pc].data; + formattedWrite(output, " pc=>%u", pc+len+IRL!(IR.RepeatStart)); + break; + case IR.RepeatEnd, IR.RepeatQEnd: //backward-jump instructions + uint len = irb[pc].data; + formattedWrite(output, " pc=>%u min=%u max=%u step=%u", + pc - len, irb[pc + 3].raw, irb[pc + 4].raw, irb[pc + 2].raw); + break; + case IR.InfiniteEnd, IR.InfiniteQEnd, IR.InfiniteBloomEnd, IR.OrEnd: //ditto + uint len = irb[pc].data; + formattedWrite(output, " pc=>%u", pc-len); + break; + case IR.LookaheadEnd, IR.NeglookaheadEnd: //ditto + uint len = irb[pc].data; + formattedWrite(output, " pc=>%u", pc-len); + break; + case IR.GroupStart, IR.GroupEnd: + uint n = irb[pc].data; + string name; + foreach (v;dict) + if (v.group == n) + { + name = "'"~v.name~"'"; + break; + } + formattedWrite(output, " %s #%u " ~ (irb[pc].backreference ? "referenced" : ""), + name, n); + break; + case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart: + uint len = irb[pc].data; + uint start = irb[pc+1].raw, end = irb[pc+2].raw; + formattedWrite(output, " pc=>%u [%u..%u]", pc + len + IRL!(IR.LookaheadStart), start, end); + break; + case IR.Backref: case IR.CodepointSet: case IR.Trie: + uint n = irb[pc].data; + formattedWrite(output, " %u", n); + if (irb[pc].code == IR.Backref) + formattedWrite(output, " %s", irb[pc].localRef ? "local" : "global"); + break; + default://all data-free instructions + } + if (irb[pc].hotspot) + formattedWrite(output, " Hotspot %u", irb[pc+1].raw); + return output.data; +} + +//disassemble the whole chunk +@trusted void printBytecode()(in Bytecode[] slice, in NamedGroup[] dict=[]) +{ + import std.stdio : writeln; + for (uint pc=0; pc= end; } + @property size_t length() { return end - start; } + alias opDollar = length; + @property NamedGroupRange save() + { + return NamedGroupRange(groups, start, end); + } + void popFront() { assert(!empty); start++; } + void popBack() { assert(!empty); end--; } + string opIndex()(size_t i) + { + assert(start + i < end, + "Requested named group is out of range."); + return groups[start+i].name; + } + NamedGroupRange opSlice(size_t low, size_t high) { + assert(low <= high); + assert(start + high <= end); + return NamedGroupRange(groups, start + low, start + high); + } + NamedGroupRange opSlice() { return this.save; } + } + return NamedGroupRange(dict, 0, dict.length); + } + +package(std.regex): + import std.regex.internal.kickstart : Kickstart; //TODO: get rid of this dependency + NamedGroup[] dict; // maps name -> user group number + uint ngroup; // number of internal groups + uint maxCounterDepth; // max depth of nested {n,m} repetitions + uint hotspotTableSize; // number of entries in merge table + uint threadCount; // upper bound on number of Thompson VM threads + uint flags; // global regex flags + public const(CharMatcher)[] matchers; // tables that represent character sets + public const(BitTable)[] filters; // bloom filters for conditional loops + uint[] backrefed; // bit array of backreferenced submatches + Kickstart!Char kickstart; + + //bit access helper + uint isBackref(uint n) + { + if (n/32 >= backrefed.length) + return 0; + return backrefed[n / 32] & (1 << (n & 31)); + } + + //check if searching is not needed + void checkIfOneShot() + { + L_CheckLoop: + for (uint i = 0; i < ir.length; i += ir[i].length) + { + switch (ir[i].code) + { + case IR.Bof: + flags |= RegexInfo.oneShot; + break L_CheckLoop; + case IR.GroupStart, IR.GroupEnd, IR.Bol, IR.Eol, IR.Eof, + IR.Wordboundary, IR.Notwordboundary: + break; + default: + break L_CheckLoop; + } + } + } + + //print out disassembly a program's IR + @trusted debug(std_regex_parser) void print() const + {//@@@BUG@@@ write is system + for (uint i = 0; i < ir.length; i += ir[i].length) + { + writefln("%d\t%s ", i, disassemble(ir, i, dict)); + } + writeln("Total merge table size: ", hotspotTableSize); + writeln("Max counter nesting depth: ", maxCounterDepth); + } + +} + +//@@@BUG@@@ (unreduced) - public makes it inaccessible in std.regex.package (!) +/*public*/ struct StaticRegex(Char) +{ +package(std.regex): + import std.regex.internal.backtracking : BacktrackingMatcher; + alias Matcher = BacktrackingMatcher!(true); + alias MatchFn = bool function(ref Matcher!Char) @trusted; + MatchFn nativeFn; +public: + Regex!Char _regex; + alias _regex this; + this(Regex!Char re, MatchFn fn) + { + _regex = re; + nativeFn = fn; + + } + +} + +// The stuff below this point is temporarrily part of IR module +// but may need better place in the future (all internals) +package(std.regex): + +//Simple UTF-string abstraction compatible with stream interface +struct Input(Char) +if (is(Char :dchar)) +{ + import std.utf : decode; + alias DataIndex = size_t; + enum bool isLoopback = false; + alias String = const(Char)[]; + String _origin; + size_t _index; + + //constructs Input object out of plain string + this(String input, size_t idx = 0) + { + _origin = input; + _index = idx; + } + + //codepoint at current stream position + pragma(inline, true) bool nextChar(ref dchar res, ref size_t pos) + { + pos = _index; + // DMD's inliner hates multiple return functions + // but can live with single statement if/else bodies + bool n = !(_index == _origin.length); + if (n) + res = decode(_origin, _index); + return n; + } + @property bool atEnd(){ + return _index == _origin.length; + } + bool search(Kickstart)(ref Kickstart kick, ref dchar res, ref size_t pos) + { + size_t idx = kick.search(_origin, _index); + _index = idx; + return nextChar(res, pos); + } + + //index of at End position + @property size_t lastIndex(){ return _origin.length; } + + //support for backtracker engine, might not be present + void reset(size_t index){ _index = index; } + + String opSlice(size_t start, size_t end){ return _origin[start .. end]; } + + auto loopBack(size_t index){ return BackLooper!Input(this, index); } +} + +struct BackLooperImpl(Input) +{ + import std.utf : strideBack; + alias DataIndex = size_t; + alias String = Input.String; + enum bool isLoopback = true; + String _origin; + size_t _index; + this(Input input, size_t index) + { + _origin = input._origin; + _index = index; + } + @trusted bool nextChar(ref dchar res,ref size_t pos) + { + pos = _index; + if (_index == 0) + return false; + + res = _origin[0.._index].back; + _index -= strideBack(_origin, _index); + + return true; + } + @property atEnd(){ return _index == 0 || _index == strideBack(_origin, _index); } + auto loopBack(size_t index){ return Input(_origin, index); } + + //support for backtracker engine, might not be present + //void reset(size_t index){ _index = index ? index-std.utf.strideBack(_origin, index) : 0; } + void reset(size_t index){ _index = index; } + + String opSlice(size_t start, size_t end){ return _origin[end .. start]; } + //index of at End position + @property size_t lastIndex(){ return 0; } +} + +template BackLooper(E) +{ + static if (is(E : BackLooperImpl!U, U)) + { + alias BackLooper = U; + } + else + { + alias BackLooper = BackLooperImpl!E; + } +} + +//both helpers below are internal, on its own are quite "explosive" +//unsafe, no initialization of elements +@system T[] mallocArray(T)(size_t len) +{ + import core.stdc.stdlib : malloc; + return (cast(T*) malloc(len * T.sizeof))[0 .. len]; +} + +//very unsafe, no initialization +@system T[] arrayInChunk(T)(size_t len, ref void[] chunk) +{ + auto ret = (cast(T*) chunk.ptr)[0 .. len]; + chunk = chunk[len * T.sizeof .. $]; + return ret; +} + +// +@trusted uint lookupNamedGroup(String)(NamedGroup[] dict, String name) +{//equal is @system? + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map; + import std.conv : text; + import std.range : assumeSorted; + + auto fnd = assumeSorted!"cmp(a,b) < 0"(map!"a.name"(dict)).lowerBound(name).length; + enforce(fnd < dict.length && equal(dict[fnd].name, name), + text("no submatch named ", name)); + return dict[fnd].group; +} + +//whether ch is one of unicode newline sequences +//0-arg template due to @@@BUG@@@ 10985 +bool endOfLine()(dchar front, bool seenCr) +{ + return ((front == '\n') ^ seenCr) || front == '\r' + || front == NEL || front == LS || front == PS; +} + +// +//0-arg template due to @@@BUG@@@ 10985 +bool startOfLine()(dchar back, bool seenNl) +{ + return ((back == '\r') ^ seenNl) || back == '\n' + || back == NEL || back == LS || back == PS; +} + +///Exception object thrown in case of errors during regex compilation. +public class RegexException : Exception +{ + mixin basicExceptionCtors; +} + +// simple 128-entry bit-table used with a hash function +struct BitTable { + uint[4] filter; + + this(CodepointSet set){ + foreach (iv; set.byInterval) + { + foreach (v; iv.a .. iv.b) + add(v); + } + } + + void add()(dchar ch){ + immutable i = index(ch); + filter[i >> 5] |= 1<<(i & 31); + } + // non-zero -> might be present, 0 -> absent + bool opIndex()(dchar ch) const{ + immutable i = index(ch); + return (filter[i >> 5]>>(i & 31)) & 1; + } + + static uint index()(dchar ch){ + return ((ch >> 7) ^ ch) & 0x7F; + } +} + +struct CharMatcher { + BitTable ascii; // fast path for ASCII + Trie trie; // slow path for Unicode + + this(CodepointSet set) + { + auto asciiSet = set & unicode.ASCII; + ascii = BitTable(asciiSet); + trie = makeTrie(set); + } + + bool opIndex()(dchar ch) const + { + if (ch < 0x80) + return ascii[ch]; + else + return trie[ch]; + } +} diff --git a/libphobos/src/std/regex/internal/kickstart.d b/libphobos/src/std/regex/internal/kickstart.d new file mode 100644 index 0000000..f303b43 --- /dev/null +++ b/libphobos/src/std/regex/internal/kickstart.d @@ -0,0 +1,579 @@ +/* + Kickstart is a coarse-grained "filter" engine that finds likely matches + to be verified by full-blown matcher. +*/ +module std.regex.internal.kickstart; + +package(std.regex): + +import std.range.primitives, std.utf; +import std.regex.internal.ir; + +//utility for shiftOr, returns a minimum number of bytes to test in a Char +uint effectiveSize(Char)() +{ + static if (is(Char == char)) + return 1; + else static if (is(Char == wchar)) + return 2; + else static if (is(Char == dchar)) + return 3; + else + static assert(0); +} + +/* + Kickstart engine using ShiftOr algorithm, + a bit parallel technique for inexact string searching. +*/ +struct ShiftOr(Char) +{ +private: + uint[] table; + uint fChar; + uint n_length; + enum charSize = effectiveSize!Char(); + //maximum number of chars in CodepointSet to process + enum uint charsetThreshold = 32_000; + static struct ShiftThread + { + uint[] tab; + uint mask; + uint idx; + uint pc, counter, hops; + this(uint newPc, uint newCounter, uint[] table) + { + pc = newPc; + counter = newCounter; + mask = 1; + idx = 0; + hops = 0; + tab = table; + } + + void setMask(uint idx, uint mask) + { + tab[idx] |= mask; + } + + void setInvMask(uint idx, uint mask) + { + tab[idx] &= ~mask; + } + + void set(alias setBits = setInvMask)(dchar ch) + { + static if (charSize == 3) + { + uint val = ch, tmask = mask; + setBits(val&0xFF, tmask); + tmask <<= 1; + val >>= 8; + setBits(val&0xFF, tmask); + tmask <<= 1; + val >>= 8; + assert(val <= 0x10); + setBits(val, tmask); + tmask <<= 1; + } + else + { + Char[dchar.sizeof/Char.sizeof] buf; + uint tmask = mask; + size_t total = encode(buf, ch); + for (size_t i = 0; i < total; i++, tmask<<=1) + { + static if (charSize == 1) + setBits(buf[i], tmask); + else static if (charSize == 2) + { + setBits(buf[i]&0xFF, tmask); + tmask <<= 1; + setBits(buf[i]>>8, tmask); + } + } + } + } + void add(dchar ch){ return set!setInvMask(ch); } + void advance(uint s) + { + mask <<= s; + idx += s; + } + @property bool full(){ return !mask; } + } + + static ShiftThread fork(ShiftThread t, uint newPc, uint newCounter) + { + ShiftThread nt = t; + nt.pc = newPc; + nt.counter = newCounter; + return nt; + } + + @trusted static ShiftThread fetch(ref ShiftThread[] worklist) + { + auto t = worklist[$-1]; + worklist.length -= 1; + if (!__ctfe) + cast(void) worklist.assumeSafeAppend(); + return t; + } + + static uint charLen(uint ch) + { + assert(ch <= 0x10FFFF); + return codeLength!Char(cast(dchar) ch)*charSize; + } + +public: + @trusted this(ref Regex!Char re, uint[] memory) + { + static import std.algorithm.comparison; + import std.algorithm.searching : countUntil; + import std.conv : text; + import std.range : assumeSorted; + assert(memory.length == 256); + fChar = uint.max; + // FNV-1a flavored hash (uses 32bits at a time) + ulong hash(uint[] tab) + { + ulong h = 0xcbf29ce484222325; + foreach (v; tab) + { + h ^= v; + h *= 0x100000001b3; + } + return h; + } + L_FindChar: + for (size_t i = 0;;) + { + switch (re.ir[i].code) + { + case IR.Char: + fChar = re.ir[i].data; + static if (charSize != 3) + { + Char[dchar.sizeof/Char.sizeof] buf; + encode(buf, fChar); + fChar = buf[0]; + } + fChar = fChar & 0xFF; + break L_FindChar; + case IR.GroupStart, IR.GroupEnd: + i += IRL!(IR.GroupStart); + break; + case IR.Bof, IR.Bol, IR.Wordboundary, IR.Notwordboundary: + i += IRL!(IR.Bol); + break; + default: + break L_FindChar; + } + } + table = memory; + table[] = uint.max; + alias MergeTab = bool[ulong]; + // use reasonably complex hash to identify equivalent tables + auto merge = new MergeTab[re.hotspotTableSize]; + ShiftThread[] trs; + ShiftThread t = ShiftThread(0, 0, table); + //locate first fixed char if any + n_length = 32; + for (;;) + { + L_Eval_Thread: + for (;;) + { + switch (re.ir[t.pc].code) + { + case IR.Char: + uint s = charLen(re.ir[t.pc].data); + if (t.idx+s > n_length) + goto L_StopThread; + t.add(re.ir[t.pc].data); + t.advance(s); + t.pc += IRL!(IR.Char); + break; + case IR.OrChar://assumes IRL!(OrChar) == 1 + uint len = re.ir[t.pc].sequence; + uint end = t.pc + len; + uint[Bytecode.maxSequence] s; + uint numS; + for (uint i = 0; i < len; i++) + { + auto x = charLen(re.ir[t.pc+i].data); + if (countUntil(s[0 .. numS], x) < 0) + s[numS++] = x; + } + for (uint i = t.pc; i < end; i++) + { + t.add(re.ir[i].data); + } + for (uint i = 0; i < numS; i++) + { + auto tx = fork(t, t.pc + len, t.counter); + if (tx.idx + s[i] <= n_length) + { + tx.advance(s[i]); + trs ~= tx; + } + } + if (!trs.empty) + t = fetch(trs); + else + goto L_StopThread; + break; + case IR.CodepointSet: + case IR.Trie: + auto set = re.charsets[re.ir[t.pc].data]; + uint[4] s; + uint numS; + static if (charSize == 3) + { + s[0] = charSize; + numS = 1; + } + else + { + + static if (charSize == 1) + static immutable codeBounds = [0x0, 0x7F, 0x80, 0x7FF, 0x800, 0xFFFF, 0x10000, 0x10FFFF]; + else //== 2 + static immutable codeBounds = [0x0, 0xFFFF, 0x10000, 0x10FFFF]; + uint[] arr = new uint[set.byInterval.length * 2]; + size_t ofs = 0; + foreach (ival; set.byInterval) + { + arr[ofs++] = ival.a; + arr[ofs++] = ival.b; + } + auto srange = assumeSorted!"a <= b"(arr); + for (uint i = 0; i < codeBounds.length/2; i++) + { + auto start = srange.lowerBound(codeBounds[2*i]).length; + auto end = srange.lowerBound(codeBounds[2*i+1]).length; + if (end > start || (end == start && (end & 1))) + s[numS++] = (i+1)*charSize; + } + } + if (numS == 0 || t.idx + s[numS-1] > n_length) + goto L_StopThread; + auto chars = set.length; + if (chars > charsetThreshold) + goto L_StopThread; + foreach (ch; set.byCodepoint) + { + //avoid surrogate pairs + if (0xD800 <= ch && ch <= 0xDFFF) + continue; + t.add(ch); + } + for (uint i = 0; i < numS; i++) + { + auto tx = fork(t, t.pc + IRL!(IR.CodepointSet), t.counter); + tx.advance(s[i]); + trs ~= tx; + } + if (!trs.empty) + t = fetch(trs); + else + goto L_StopThread; + break; + case IR.Any: + goto L_StopThread; + + case IR.GotoEndOr: + t.pc += IRL!(IR.GotoEndOr)+re.ir[t.pc].data; + assert(re.ir[t.pc].code == IR.OrEnd); + goto case; + case IR.OrEnd: + auto slot = re.ir[t.pc+1].raw+t.counter; + auto val = hash(t.tab); + if (val in merge[slot]) + goto L_StopThread; // merge equivalent + merge[slot][val] = true; + t.pc += IRL!(IR.OrEnd); + break; + case IR.OrStart: + t.pc += IRL!(IR.OrStart); + goto case; + case IR.Option: + uint next = t.pc + re.ir[t.pc].data + IRL!(IR.Option); + //queue next Option + if (re.ir[next].code == IR.Option) + { + trs ~= fork(t, next, t.counter); + } + t.pc += IRL!(IR.Option); + break; + case IR.RepeatStart:case IR.RepeatQStart: + t.pc += IRL!(IR.RepeatStart)+re.ir[t.pc].data; + goto case IR.RepeatEnd; + case IR.RepeatEnd: + case IR.RepeatQEnd: + auto slot = re.ir[t.pc+1].raw+t.counter; + auto val = hash(t.tab); + if (val in merge[slot]) + goto L_StopThread; // merge equivalent + merge[slot][val] = true; + uint len = re.ir[t.pc].data; + uint step = re.ir[t.pc+2].raw; + uint min = re.ir[t.pc+3].raw; + if (t.counter < min) + { + t.counter += step; + t.pc -= len; + break; + } + uint max = re.ir[t.pc+4].raw; + if (t.counter < max) + { + trs ~= fork(t, t.pc - len, t.counter + step); + t.counter = t.counter%step; + t.pc += IRL!(IR.RepeatEnd); + } + else + { + t.counter = t.counter%step; + t.pc += IRL!(IR.RepeatEnd); + } + break; + case IR.InfiniteStart, IR.InfiniteQStart: + t.pc += re.ir[t.pc].data + IRL!(IR.InfiniteStart); + goto case IR.InfiniteEnd; //both Q and non-Q + case IR.InfiniteEnd: + case IR.InfiniteQEnd: + auto slot = re.ir[t.pc+1].raw+t.counter; + auto val = hash(t.tab); + if (val in merge[slot]) + goto L_StopThread; // merge equivalent + merge[slot][val] = true; + uint len = re.ir[t.pc].data; + uint pc1, pc2; //branches to take in priority order + if (++t.hops == 32) + goto L_StopThread; + pc1 = t.pc + IRL!(IR.InfiniteEnd); + pc2 = t.pc - len; + trs ~= fork(t, pc2, t.counter); + t.pc = pc1; + break; + case IR.GroupStart, IR.GroupEnd: + t.pc += IRL!(IR.GroupStart); + break; + case IR.Bof, IR.Bol, IR.Wordboundary, IR.Notwordboundary: + t.pc += IRL!(IR.Bol); + break; + case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart: + t.pc += IRL!(IR.LookaheadStart) + IRL!(IR.LookaheadEnd) + re.ir[t.pc].data; + break; + default: + L_StopThread: + assert(re.ir[t.pc].code >= 0x80, text(re.ir[t.pc].code)); + debug (fred_search) writeln("ShiftOr stumbled on ",re.ir[t.pc].mnemonic); + n_length = std.algorithm.comparison.min(t.idx, n_length); + break L_Eval_Thread; + } + } + if (trs.empty) + break; + t = fetch(trs); + } + debug(std_regex_search) + { + writeln("Min length: ", n_length); + } + } + + @property bool empty() const { return n_length == 0; } + + @property uint length() const{ return n_length/charSize; } + + // lookup compatible bit pattern in haystack, return starting index + // has a useful trait: if supplied with valid UTF indexes, + // returns only valid UTF indexes + // (that given the haystack in question is valid UTF string) + @trusted size_t search(const(Char)[] haystack, size_t idx) + {//@BUG: apparently assumes little endian machines + import core.stdc.string : memchr; + import std.conv : text; + assert(!empty); + auto p = cast(const(ubyte)*)(haystack.ptr+idx); + uint state = uint.max; + uint limit = 1u<<(n_length - 1u); + debug(std_regex_search) writefln("Limit: %32b",limit); + if (fChar != uint.max) + { + const(ubyte)* end = cast(ubyte*)(haystack.ptr + haystack.length); + const orginalAlign = cast(size_t) p & (Char.sizeof-1); + while (p != end) + { + if (!~state) + {//speed up seeking first matching place + for (;;) + { + assert(p <= end, text(p," vs ", end)); + p = cast(ubyte*) memchr(p, fChar, end - p); + if (!p) + return haystack.length; + if ((cast(size_t) p & (Char.sizeof-1)) == orginalAlign) + break; + if (++p == end) + return haystack.length; + } + state = ~1u; + assert((cast(size_t) p & (Char.sizeof-1)) == orginalAlign); + static if (charSize == 3) + { + state = (state << 1) | table[p[1]]; + state = (state << 1) | table[p[2]]; + p += 4; + } + else + p++; + //first char is tested, see if that's all + if (!(state & limit)) + return (p-cast(ubyte*) haystack.ptr)/Char.sizeof + -length; + } + else + {//have some bits/states for possible matches, + //use the usual shift-or cycle + static if (charSize == 3) + { + state = (state << 1) | table[p[0]]; + state = (state << 1) | table[p[1]]; + state = (state << 1) | table[p[2]]; + p += 4; + } + else + { + state = (state << 1) | table[p[0]]; + p++; + } + if (!(state & limit)) + return (p-cast(ubyte*) haystack.ptr)/Char.sizeof + -length; + } + debug(std_regex_search) writefln("State: %32b", state); + } + } + else + { + //normal path, partially unrolled for char/wchar + static if (charSize == 3) + { + const(ubyte)* end = cast(ubyte*)(haystack.ptr + haystack.length); + while (p != end) + { + state = (state << 1) | table[p[0]]; + state = (state << 1) | table[p[1]]; + state = (state << 1) | table[p[2]]; + p += 4; + if (!(state & limit))//division rounds down for dchar + return (p-cast(ubyte*) haystack.ptr)/Char.sizeof + -length; + } + } + else + { + auto len = cast(ubyte*)(haystack.ptr + haystack.length) - p; + size_t i = 0; + if (len & 1) + { + state = (state << 1) | table[p[i++]]; + if (!(state & limit)) + return idx+i/Char.sizeof-length; + } + while (i < len) + { + state = (state << 1) | table[p[i++]]; + if (!(state & limit)) + return idx+i/Char.sizeof + -length; + state = (state << 1) | table[p[i++]]; + if (!(state & limit)) + return idx+i/Char.sizeof + -length; + debug(std_regex_search) writefln("State: %32b", state); + } + } + } + return haystack.length; + } + + @system debug static void dump(uint[] table) + {//@@@BUG@@@ writef(ln) is @system + import std.stdio : writefln; + for (size_t i = 0; i < table.length; i += 4) + { + writefln("%32b %32b %32b %32b",table[i], table[i+1], table[i+2], table[i+3]); + } + } +} + +@system unittest +{ + import std.conv, std.regex; + @trusted void test_fixed(alias Kick)() + { + foreach (i, v; AliasSeq!(char, wchar, dchar)) + { + alias Char = v; + alias String = immutable(v)[]; + auto r = regex(to!String(`abc$`)); + auto kick = Kick!Char(r, new uint[256]); + assert(kick.length == 3, text(Kick.stringof," ",v.stringof, " == ", kick.length)); + auto r2 = regex(to!String(`(abc){2}a+`)); + kick = Kick!Char(r2, new uint[256]); + assert(kick.length == 7, text(Kick.stringof,v.stringof," == ", kick.length)); + auto r3 = regex(to!String(`\b(a{2}b{3}){2,4}`)); + kick = Kick!Char(r3, new uint[256]); + assert(kick.length == 10, text(Kick.stringof,v.stringof," == ", kick.length)); + auto r4 = regex(to!String(`\ba{2}c\bxyz`)); + kick = Kick!Char(r4, new uint[256]); + assert(kick.length == 6, text(Kick.stringof,v.stringof, " == ", kick.length)); + auto r5 = regex(to!String(`\ba{2}c\b`)); + kick = Kick!Char(r5, new uint[256]); + size_t x = kick.search("aabaacaa", 0); + assert(x == 3, text(Kick.stringof,v.stringof," == ", kick.length)); + x = kick.search("aabaacaa", x+1); + assert(x == 8, text(Kick.stringof,v.stringof," == ", kick.length)); + } + } + @trusted void test_flex(alias Kick)() + { + foreach (i, v; AliasSeq!(char, wchar, dchar)) + { + alias Char = v; + alias String = immutable(v)[]; + auto r = regex(to!String(`abc[a-z]`)); + auto kick = Kick!Char(r, new uint[256]); + auto x = kick.search(to!String("abbabca"), 0); + assert(x == 3, text("real x is ", x, " ",v.stringof)); + + auto r2 = regex(to!String(`(ax|bd|cdy)`)); + String s2 = to!String("abdcdyabax"); + kick = Kick!Char(r2, new uint[256]); + x = kick.search(s2, 0); + assert(x == 1, text("real x is ", x)); + x = kick.search(s2, x+1); + assert(x == 3, text("real x is ", x)); + x = kick.search(s2, x+1); + assert(x == 8, text("real x is ", x)); + auto rdot = regex(to!String(`...`)); + kick = Kick!Char(rdot, new uint[256]); + assert(kick.length == 0); + auto rN = regex(to!String(`a(b+|c+)x`)); + kick = Kick!Char(rN, new uint[256]); + assert(kick.length == 3, to!string(kick.length)); + assert(kick.search("ababx",0) == 2); + assert(kick.search("abaacba",0) == 3);//expected inexact + + } + } + test_fixed!(ShiftOr)(); + test_flex!(ShiftOr)(); +} + +alias Kickstart = ShiftOr; diff --git a/libphobos/src/std/regex/internal/parser.d b/libphobos/src/std/regex/internal/parser.d new file mode 100644 index 0000000..8cabae5 --- /dev/null +++ b/libphobos/src/std/regex/internal/parser.d @@ -0,0 +1,1751 @@ +//Written in the D programming language +/* + Regular expression pattern parser. +*/ +module std.regex.internal.parser; + +static import std.ascii; +import std.range.primitives, std.uni, std.meta, + std.traits, std.typecons, std.exception; +import std.regex.internal.ir; + +// package relevant info from parser into a regex object +auto makeRegex(S, CG)(Parser!(S, CG) p) +{ + Regex!(BasicElementOf!S) re; + auto g = p.g; + with(re) + { + ir = g.ir; + dict = g.dict; + ngroup = g.ngroup; + maxCounterDepth = g.counterDepth; + flags = p.re_flags; + charsets = g.charsets; + matchers = g.matchers; + backrefed = g.backrefed; + re.postprocess(); + debug(std_regex_parser) + { + __ctfe || print(); + } + //@@@BUG@@@ (not reduced) + //somehow just using validate _collides_ with std.utf.validate (!) + version (assert) re.validateRe(); + } + return re; +} + +// helper for unittest +auto makeRegex(S)(S arg) +if (isSomeString!S) +{ + return makeRegex(Parser!(S, CodeGen)(arg, "")); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + auto re = makeRegex(`(?P\w+) = (?P\d+)`); + auto nc = re.namedCaptures; + static assert(isRandomAccessRange!(typeof(nc))); + assert(!nc.empty); + assert(nc.length == 2); + assert(nc.equal(["name", "var"])); + assert(nc[0] == "name"); + assert(nc[1..$].equal(["var"])); + + re = makeRegex(`(\w+) (?P\w+) (\w+)`); + nc = re.namedCaptures; + assert(nc.length == 1); + assert(nc[0] == "named"); + assert(nc.front == "named"); + assert(nc.back == "named"); + + re = makeRegex(`(\w+) (\w+)`); + nc = re.namedCaptures; + assert(nc.empty); + + re = makeRegex(`(?P\d{4})/(?P\d{2})/(?P\d{2})/`); + nc = re.namedCaptures; + auto cp = nc.save; + assert(nc.equal(cp)); + nc.popFront(); + assert(nc.equal(cp[1..$])); + nc.popBack(); + assert(nc.equal(cp[1 .. $ - 1])); +} + + +@trusted void reverseBytecode()(Bytecode[] code) +{ + Bytecode[] rev = new Bytecode[code.length]; + uint revPc = cast(uint) rev.length; + Stack!(Tuple!(uint, uint, uint)) stack; + uint start = 0; + uint end = cast(uint) code.length; + for (;;) + { + for (uint pc = start; pc < end; ) + { + immutable len = code[pc].length; + if (code[pc].code == IR.GotoEndOr) + break; //pick next alternation branch + if (code[pc].isAtom) + { + rev[revPc - len .. revPc] = code[pc .. pc + len]; + revPc -= len; + pc += len; + } + else if (code[pc].isStart || code[pc].isEnd) + { + //skip over other embedded lookbehinds they are reversed + if (code[pc].code == IR.LookbehindStart + || code[pc].code == IR.NeglookbehindStart) + { + immutable blockLen = len + code[pc].data + + code[pc].pairedLength; + rev[revPc - blockLen .. revPc] = code[pc .. pc + blockLen]; + pc += blockLen; + revPc -= blockLen; + continue; + } + immutable second = code[pc].indexOfPair(pc); + immutable secLen = code[second].length; + rev[revPc - secLen .. revPc] = code[second .. second + secLen]; + revPc -= secLen; + if (code[pc].code == IR.OrStart) + { + //we pass len bytes forward, but secLen in reverse + immutable revStart = revPc - (second + len - secLen - pc); + uint r = revStart; + uint i = pc + IRL!(IR.OrStart); + while (code[i].code == IR.Option) + { + if (code[i - 1].code != IR.OrStart) + { + assert(code[i - 1].code == IR.GotoEndOr); + rev[r - 1] = code[i - 1]; + } + rev[r] = code[i]; + auto newStart = i + IRL!(IR.Option); + auto newEnd = newStart + code[i].data; + auto newRpc = r + code[i].data + IRL!(IR.Option); + if (code[newEnd].code != IR.OrEnd) + { + newRpc--; + } + stack.push(tuple(newStart, newEnd, newRpc)); + r += code[i].data + IRL!(IR.Option); + i += code[i].data + IRL!(IR.Option); + } + pc = i; + revPc = revStart; + assert(code[pc].code == IR.OrEnd); + } + else + pc += len; + } + } + if (stack.empty) + break; + start = stack.top[0]; + end = stack.top[1]; + revPc = stack.top[2]; + stack.pop(); + } + code[] = rev[]; +} + +//test if a given string starts with hex number of maxDigit that's a valid codepoint +//returns it's value and skips these maxDigit chars on success, throws on failure +dchar parseUniHex(Char)(ref Char[] str, size_t maxDigit) +{ + //std.conv.parse is both @system and bogus + enforce(str.length >= maxDigit,"incomplete escape sequence"); + uint val; + for (int k = 0; k < maxDigit; k++) + { + immutable current = str[k];//accepts ascii only, so it's OK to index directly + if ('0' <= current && current <= '9') + val = val * 16 + current - '0'; + else if ('a' <= current && current <= 'f') + val = val * 16 + current -'a' + 10; + else if ('A' <= current && current <= 'F') + val = val * 16 + current - 'A' + 10; + else + throw new Exception("invalid escape sequence"); + } + enforce(val <= 0x10FFFF, "invalid codepoint"); + str = str[maxDigit..$]; + return val; +} + +@system unittest //BUG canFind is system +{ + import std.algorithm.searching : canFind; + string[] non_hex = [ "000j", "000z", "FffG", "0Z"]; + string[] hex = [ "01", "ff", "00af", "10FFFF" ]; + int[] value = [ 1, 0xFF, 0xAF, 0x10FFFF ]; + foreach (v; non_hex) + assert(collectException(parseUniHex(v, v.length)).msg + .canFind("invalid escape sequence")); + foreach (i, v; hex) + assert(parseUniHex(v, v.length) == value[i]); + string over = "0011FFFF"; + assert(collectException(parseUniHex(over, over.length)).msg + .canFind("invalid codepoint")); +} + +auto caseEnclose(CodepointSet set) +{ + auto cased = set & unicode.LC; + foreach (dchar ch; cased.byCodepoint) + { + foreach (c; simpleCaseFoldings(ch)) + set |= c; + } + return set; +} + +/+ + fetch codepoint set corresponding to a name (InBlock or binary property) ++/ +@trusted CodepointSet getUnicodeSet(in char[] name, bool negated, bool casefold) +{ + CodepointSet s = unicode(name); + //FIXME: caseEnclose for new uni as Set | CaseEnclose(SET && LC) + if (casefold) + s = caseEnclose(s); + if (negated) + s = s.inverted; + return s; +} + +//basic stack, just in case it gets used anywhere else then Parser +@trusted struct Stack(T) +{ + T[] data; + @property bool empty(){ return data.empty; } + + @property size_t length(){ return data.length; } + + void push(T val){ data ~= val; } + + T pop() + { + assert(!empty); + auto val = data[$ - 1]; + data = data[0 .. $ - 1]; + if (!__ctfe) + cast(void) data.assumeSafeAppend(); + return val; + } + + @property ref T top() + { + assert(!empty); + return data[$ - 1]; + } +} + +struct CodeGen +{ + Bytecode[] ir; // resulting bytecode + Stack!(uint) fixupStack; // stack of opened start instructions + NamedGroup[] dict; // maps name -> user group number + Stack!(uint) groupStack; // stack of current number of group + uint nesting = 0; // group nesting level and repetitions step + uint lookaroundNest = 0; // nesting of lookaround + uint counterDepth = 0; // current depth of nested counted repetitions + CodepointSet[] charsets; // sets for char classes + const(CharMatcher)[] matchers; // matchers for char classes + uint[] backrefed; // bitarray for groups refered by backref + uint ngroup; // final number of groups (of all patterns) + + void start(uint length) + { + if (!__ctfe) + ir.reserve((length*5+2)/4); + fixupStack.push(0); + groupStack.push(1);//0 - whole match + } + + //mark referenced groups for latter processing + void markBackref(uint n) + { + if (n/32 >= backrefed.length) + backrefed.length = n/32 + 1; + backrefed[n / 32] |= 1 << (n & 31); + } + + bool isOpenGroup(uint n) + { + import std.algorithm.searching : canFind; + // walk the fixup stack and see if there are groups labeled 'n' + // fixup '0' is reserved for alternations + return fixupStack.data[1..$]. + canFind!(fix => ir[fix].code == IR.GroupStart && ir[fix].data == n)(); + } + + void put(Bytecode code) + { + enforce(ir.length < maxCompiledLength, + "maximum compiled pattern length is exceeded"); + ir ~= code; + } + + void putRaw(uint number) + { + enforce(ir.length < maxCompiledLength, + "maximum compiled pattern length is exceeded"); + ir ~= Bytecode.fromRaw(number); + } + + //try to generate optimal IR code for this CodepointSet + @trusted void charsetToIr(CodepointSet set) + {//@@@BUG@@@ writeln is @system + uint chars = cast(uint) set.length; + if (chars < Bytecode.maxSequence) + { + switch (chars) + { + case 1: + put(Bytecode(IR.Char, set.byCodepoint.front)); + break; + case 0: + throw new RegexException("empty CodepointSet not allowed"); + default: + foreach (ch; set.byCodepoint) + put(Bytecode(IR.OrChar, ch, chars)); + } + } + else + { + import std.algorithm.searching : countUntil; + const ivals = set.byInterval; + immutable n = charsets.countUntil(set); + if (n >= 0) + { + if (ivals.length*2 > maxCharsetUsed) + put(Bytecode(IR.Trie, cast(uint) n)); + else + put(Bytecode(IR.CodepointSet, cast(uint) n)); + return; + } + if (ivals.length*2 > maxCharsetUsed) + { + auto t = getMatcher(set); + put(Bytecode(IR.Trie, cast(uint) matchers.length)); + matchers ~= t; + debug(std_regex_allocation) writeln("Trie generated"); + } + else + { + put(Bytecode(IR.CodepointSet, cast(uint) charsets.length)); + matchers ~= CharMatcher.init; + } + charsets ~= set; + assert(charsets.length == matchers.length); + } + } + + void genLogicGroup() + { + nesting++; + pushFixup(length); + put(Bytecode(IR.Nop, 0)); + } + + void genGroup() + { + nesting++; + pushFixup(length); + immutable nglob = groupStack.top++; + enforce(groupStack.top <= maxGroupNumber, "limit on number of submatches is exceeded"); + put(Bytecode(IR.GroupStart, nglob)); + } + + void genNamedGroup(string name) + { + import std.array : insertInPlace; + import std.range : assumeSorted; + nesting++; + pushFixup(length); + immutable nglob = groupStack.top++; + enforce(groupStack.top <= maxGroupNumber, "limit on submatches is exceeded"); + auto t = NamedGroup(name, nglob); + auto d = assumeSorted!"a.name < b.name"(dict); + immutable ind = d.lowerBound(t).length; + insertInPlace(dict, ind, t); + put(Bytecode(IR.GroupStart, nglob)); + } + + //generate code for start of lookaround: (?= (?! (?<= (? fix && ir[fix].code == IR.Option) + { + ir[fix] = Bytecode(ir[fix].code, cast(uint) ir.length - fix); + put(Bytecode(IR.GotoEndOr, 0)); + fixupStack.top = cast(uint) ir.length; //replace latest fixup for Option + put(Bytecode(IR.Option, 0)); + return; + } + uint len, orStart; + //start a new option + if (fixupStack.length == 1) + {//only root entry, effectively no fixup + len = cast(uint) ir.length + IRL!(IR.GotoEndOr); + orStart = 0; + } + else + {//IR.lookahead, etc. fixups that have length > 1, thus check ir[x].length + len = cast(uint) ir.length - fix - (ir[fix].length - 1); + orStart = fix + ir[fix].length; + } + insertInPlace(ir, orStart, Bytecode(IR.OrStart, 0), Bytecode(IR.Option, len)); + assert(ir[orStart].code == IR.OrStart); + put(Bytecode(IR.GotoEndOr, 0)); + fixupStack.push(orStart); //fixup for StartOR + fixupStack.push(cast(uint) ir.length); //for second Option + put(Bytecode(IR.Option, 0)); + } + + // finalizes IR.Option, fix points to the first option of sequence + void finishAlternation(uint fix) + { + enforce(ir[fix].code == IR.Option, "no matching ')'"); + ir[fix] = Bytecode(ir[fix].code, cast(uint) ir.length - fix - IRL!(IR.OrStart)); + fix = fixupStack.pop(); + enforce(ir[fix].code == IR.OrStart, "no matching ')'"); + ir[fix] = Bytecode(IR.OrStart, cast(uint) ir.length - fix - IRL!(IR.OrStart)); + put(Bytecode(IR.OrEnd, cast(uint) ir.length - fix - IRL!(IR.OrStart))); + uint pc = fix + IRL!(IR.OrStart); + while (ir[pc].code == IR.Option) + { + pc = pc + ir[pc].data; + if (ir[pc].code != IR.GotoEndOr) + break; + ir[pc] = Bytecode(IR.GotoEndOr, cast(uint)(ir.length - pc - IRL!(IR.OrEnd))); + pc += IRL!(IR.GotoEndOr); + } + put(Bytecode.fromRaw(0)); + } + + // returns: (flag - repetition possible?, fixup of the start of this "group") + Tuple!(bool, uint) onClose() + { + nesting--; + uint fix = popFixup(); + switch (ir[fix].code) + { + case IR.GroupStart: + put(Bytecode(IR.GroupEnd, ir[fix].data)); + return tuple(true, fix); + case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart: + assert(lookaroundNest); + fixLookaround(fix); + return tuple(false, 0u); + case IR.Option: //| xxx ) + //two fixups: last option + full OR + finishAlternation(fix); + fix = topFixup; + switch (ir[fix].code) + { + case IR.GroupStart: + popFixup(); + put(Bytecode(IR.GroupEnd, ir[fix].data)); + return tuple(true, fix); + case IR.LookaheadStart, IR.NeglookaheadStart, IR.LookbehindStart, IR.NeglookbehindStart: + assert(lookaroundNest); + fix = popFixup(); + fixLookaround(fix); + return tuple(false, 0u); + default://(?:xxx) + popFixup(); + return tuple(true, fix); + } + default://(?:xxx) + return tuple(true, fix); + } + } + + uint popFixup(){ return fixupStack.pop(); } + + void pushFixup(uint val){ return fixupStack.push(val); } + + @property uint topFixup(){ return fixupStack.top; } + + @property size_t fixupLength(){ return fixupStack.data.length; } + + @property uint length(){ return cast(uint) ir.length; } +} + +// safety limits +enum maxGroupNumber = 2^^19; +enum maxLookaroundDepth = 16; +// *Bytecode.sizeof, i.e. 1Mb of bytecode alone +enum maxCompiledLength = 2^^18; +// amounts to up to 4 Mb of auxilary table for matching +enum maxCumulativeRepetitionLength = 2^^20; +// marker to indicate infinite repetition +enum infinite = ~0u; + +struct Parser(R, Generator) +if (isForwardRange!R && is(ElementType!R : dchar)) +{ + dchar _current; + bool empty; + R pat, origin; //keep full pattern for pretty printing error messages + uint re_flags = 0; //global flags e.g. multiline + internal ones + Generator g; + + @trusted this(S)(R pattern, S flags) + if (isSomeString!S) + { + pat = origin = pattern; + //reserve slightly more then avg as sampled from unittests + parseFlags(flags); + _current = ' ';//a safe default for freeform parsing + next(); + g.start(cast(uint) pat.length); + try + { + parseRegex(); + } + catch (Exception e) + { + error(e.msg);//also adds pattern location + } + g.endPattern(1); + } + + @property dchar current(){ return _current; } + + bool _next() + { + if (pat.empty) + { + empty = true; + return false; + } + _current = pat.front; + pat.popFront(); + return true; + } + + void skipSpace() + { + while (isWhite(current) && _next()){ } + } + + bool next() + { + if (re_flags & RegexOption.freeform) + { + immutable r = _next(); + skipSpace(); + return r; + } + else + return _next(); + } + + //parsing number with basic overflow check + uint parseDecimal() + { + uint r = 0; + while (std.ascii.isDigit(current)) + { + if (r >= (uint.max/10)) + error("Overflow in decimal number"); + r = 10*r + cast(uint)(current-'0'); + if (!next()) + break; + } + return r; + } + + //parse control code of form \cXXX, c assumed to be the current symbol + dchar parseControlCode() + { + enforce(next(), "Unfinished escape sequence"); + enforce(('a' <= current && current <= 'z') || ('A' <= current && current <= 'Z'), + "Only letters are allowed after \\c"); + return current & 0x1f; + } + + // + @trusted void parseFlags(S)(S flags) + {//@@@BUG@@@ text is @system + import std.conv : text; + foreach (ch; flags)//flags are ASCII anyway + { + L_FlagSwitch: + switch (ch) + { + + foreach (i, op; __traits(allMembers, RegexOption)) + { + case RegexOptionNames[i]: + if (re_flags & mixin("RegexOption."~op)) + throw new RegexException(text("redundant flag specified: ",ch)); + re_flags |= mixin("RegexOption."~op); + break L_FlagSwitch; + } + default: + throw new RegexException(text("unknown regex flag '",ch,"'")); + } + } + } + + //parse and store IR for regex pattern + @trusted void parseRegex() + { + uint fix;//fixup pointer + + while (!empty) + { + debug(std_regex_parser) + __ctfe || writeln("*LR*\nSource: ", pat, "\nStack: ",fixupStack.data); + switch (current) + { + case '(': + next(); + if (current == '?') + { + next(); + switch (current) + { + case '#': + for (;;) + { + if (!next()) + error("Unexpected end of pattern"); + if (current == ')') + { + next(); + break; + } + } + break; + case ':': + g.genLogicGroup(); + next(); + break; + case '=': + g.genLookaround(IR.LookaheadStart); + next(); + break; + case '!': + g.genLookaround(IR.NeglookaheadStart); + next(); + break; + case 'P': + next(); + if (current != '<') + error("Expected '<' in named group"); + string name; + if (!next() || !(isAlpha(current) || current == '_')) + error("Expected alpha starting a named group"); + name ~= current; + while (next() && (isAlpha(current) || + current == '_' || std.ascii.isDigit(current))) + { + name ~= current; + } + if (current != '>') + error("Expected '>' closing named group"); + next(); + g.genNamedGroup(name); + break; + case '<': + next(); + if (current == '=') + g.genLookaround(IR.LookbehindStart); + else if (current == '!') + g.genLookaround(IR.NeglookbehindStart); + else + error("'!' or '=' expected after '<'"); + next(); + break; + default: + uint enableFlags, disableFlags; + bool enable = true; + do + { + switch (current) + { + case 's': + if (enable) + enableFlags |= RegexOption.singleline; + else + disableFlags |= RegexOption.singleline; + break; + case 'x': + if (enable) + enableFlags |= RegexOption.freeform; + else + disableFlags |= RegexOption.freeform; + break; + case 'i': + if (enable) + enableFlags |= RegexOption.casefold; + else + disableFlags |= RegexOption.casefold; + break; + case 'm': + if (enable) + enableFlags |= RegexOption.multiline; + else + disableFlags |= RegexOption.multiline; + break; + case '-': + if (!enable) + error(" unexpected second '-' in flags"); + enable = false; + break; + default: + error(" 's', 'x', 'i', 'm' or '-' expected after '(?' "); + } + next(); + }while (current != ')'); + next(); + re_flags |= enableFlags; + re_flags &= ~disableFlags; + } + } + else + { + g.genGroup(); + } + break; + case ')': + enforce(g.nesting, "Unmatched ')'"); + next(); + auto pair = g.onClose(); + if (pair[0]) + parseQuantifier(pair[1]); + break; + case '|': + next(); + g.fixAlternation(); + break; + default://no groups or whatever + immutable start = g.length; + parseAtom(); + parseQuantifier(start); + } + } + + if (g.fixupLength != 1) + { + fix = g.popFixup(); + g.finishAlternation(fix); + enforce(g.fixupLength == 1, "no matching ')'"); + } + } + + + //parse and store IR for atom-quantifier pair + @trusted void parseQuantifier(uint offset) + {//copy is @system + if (empty) + return g.fixRepetition(offset); + uint min, max; + switch (current) + { + case '*': + min = 0; + max = infinite; + break; + case '?': + min = 0; + max = 1; + break; + case '+': + min = 1; + max = infinite; + break; + case '{': + enforce(next(), "Unexpected end of regex pattern"); + enforce(std.ascii.isDigit(current), "First number required in repetition"); + min = parseDecimal(); + if (current == '}') + max = min; + else if (current == ',') + { + next(); + if (std.ascii.isDigit(current)) + max = parseDecimal(); + else if (current == '}') + max = infinite; + else + error("Unexpected symbol in regex pattern"); + skipSpace(); + if (current != '}') + error("Unmatched '{' in regex pattern"); + } + else + error("Unexpected symbol in regex pattern"); + if (min > max) + error("Illegal {n,m} quantifier"); + break; + default: + g.fixRepetition(offset); + return; + } + bool greedy = true; + //check only if we managed to get new symbol + if (next() && current == '?') + { + greedy = false; + next(); + } + g.fixRepetition(offset, min, max, greedy); + } + + //parse and store IR for atom + void parseAtom() + { + if (empty) + return; + switch (current) + { + case '*', '?', '+', '|', '{', '}': + error("'*', '+', '?', '{', '}' not allowed in atom"); + break; + case '.': + if (re_flags & RegexOption.singleline) + g.put(Bytecode(IR.Any, 0)); + else + { + CodepointSet set; + g.charsetToIr(set.add('\n','\n'+1).add('\r', '\r'+1).inverted); + } + next(); + break; + case '[': + parseCharset(); + break; + case '\\': + enforce(_next(), "Unfinished escape sequence"); + parseEscape(); + break; + case '^': + if (re_flags & RegexOption.multiline) + g.put(Bytecode(IR.Bol, 0)); + else + g.put(Bytecode(IR.Bof, 0)); + next(); + break; + case '$': + if (re_flags & RegexOption.multiline) + g.put(Bytecode(IR.Eol, 0)); + else + g.put(Bytecode(IR.Eof, 0)); + next(); + break; + default: + //FIXME: getCommonCasing in new std uni + if (re_flags & RegexOption.casefold) + { + auto range = simpleCaseFoldings(current); + assert(range.length <= 5); + if (range.length == 1) + g.put(Bytecode(IR.Char, range.front)); + else + foreach (v; range) + g.put(Bytecode(IR.OrChar, v, cast(uint) range.length)); + } + else + g.put(Bytecode(IR.Char, current)); + next(); + } + } + + + + //CodepointSet operations relatively in order of priority + enum Operator:uint { + Open = 0, Negate, Difference, SymDifference, Intersection, Union, None + } + + //parse unit of CodepointSet spec, most notably escape sequences and char ranges + //also fetches next set operation + Tuple!(CodepointSet,Operator) parseCharTerm() + { + enum State{ Start, Char, Escape, CharDash, CharDashEscape, + PotentialTwinSymbolOperator } + Operator op = Operator.None; + dchar last; + CodepointSet set; + State state = State.Start; + + static void addWithFlags(ref CodepointSet set, uint ch, uint re_flags) + { + if (re_flags & RegexOption.casefold) + { + auto range = simpleCaseFoldings(ch); + foreach (v; range) + set |= v; + } + else + set |= ch; + } + + static Operator twinSymbolOperator(dchar symbol) + { + switch (symbol) + { + case '|': + return Operator.Union; + case '-': + return Operator.Difference; + case '~': + return Operator.SymDifference; + case '&': + return Operator.Intersection; + default: + assert(false); + } + } + + L_CharTermLoop: + for (;;) + { + final switch (state) + { + case State.Start: + switch (current) + { + case '|': + case '-': + case '~': + case '&': + state = State.PotentialTwinSymbolOperator; + last = current; + break; + case '[': + op = Operator.Union; + goto case; + case ']': + break L_CharTermLoop; + case '\\': + state = State.Escape; + break; + default: + state = State.Char; + last = current; + } + break; + case State.Char: + // xxx last current xxx + switch (current) + { + case '|': + case '~': + case '&': + // then last is treated as normal char and added as implicit union + state = State.PotentialTwinSymbolOperator; + addWithFlags(set, last, re_flags); + last = current; + break; + case '-': // still need more info + state = State.CharDash; + break; + case '\\': + set |= last; + state = State.Escape; + break; + case '[': + op = Operator.Union; + goto case; + case ']': + addWithFlags(set, last, re_flags); + break L_CharTermLoop; + default: + state = State.Char; + addWithFlags(set, last, re_flags); + last = current; + } + break; + case State.PotentialTwinSymbolOperator: + // xxx last current xxxx + // where last = [|-&~] + if (current == last) + { + op = twinSymbolOperator(last); + next();//skip second twin char + break L_CharTermLoop; + } + goto case State.Char; + case State.Escape: + // xxx \ current xxx + switch (current) + { + case 'f': + last = '\f'; + state = State.Char; + break; + case 'n': + last = '\n'; + state = State.Char; + break; + case 'r': + last = '\r'; + state = State.Char; + break; + case 't': + last = '\t'; + state = State.Char; + break; + case 'v': + last = '\v'; + state = State.Char; + break; + case 'c': + last = parseControlCode(); + state = State.Char; + break; + foreach (val; Escapables) + { + case val: + } + last = current; + state = State.Char; + break; + case 'p': + set.add(parseUnicodePropertySpec(false)); + state = State.Start; + continue L_CharTermLoop; //next char already fetched + case 'P': + set.add(parseUnicodePropertySpec(true)); + state = State.Start; + continue L_CharTermLoop; //next char already fetched + case 'x': + last = parseUniHex(pat, 2); + state = State.Char; + break; + case 'u': + last = parseUniHex(pat, 4); + state = State.Char; + break; + case 'U': + last = parseUniHex(pat, 8); + state = State.Char; + break; + case 'd': + set.add(unicode.Nd); + state = State.Start; + break; + case 'D': + set.add(unicode.Nd.inverted); + state = State.Start; + break; + case 's': + set.add(unicode.White_Space); + state = State.Start; + break; + case 'S': + set.add(unicode.White_Space.inverted); + state = State.Start; + break; + case 'w': + set.add(wordCharacter); + state = State.Start; + break; + case 'W': + set.add(wordCharacter.inverted); + state = State.Start; + break; + default: + if (current >= privateUseStart && current <= privateUseEnd) + enforce(false, "no matching ']' found while parsing character class"); + enforce(false, "invalid escape sequence"); + } + break; + case State.CharDash: + // xxx last - current xxx + switch (current) + { + case '[': + op = Operator.Union; + goto case; + case ']': + //means dash is a single char not an interval specifier + addWithFlags(set, last, re_flags); + addWithFlags(set, '-', re_flags); + break L_CharTermLoop; + case '-'://set Difference again + addWithFlags(set, last, re_flags); + op = Operator.Difference; + next();//skip '-' + break L_CharTermLoop; + case '\\': + state = State.CharDashEscape; + break; + default: + enforce(last <= current, "inverted range"); + if (re_flags & RegexOption.casefold) + { + for (uint ch = last; ch <= current; ch++) + addWithFlags(set, ch, re_flags); + } + else + set.add(last, current + 1); + state = State.Start; + } + break; + case State.CharDashEscape: + //xxx last - \ current xxx + uint end; + switch (current) + { + case 'f': + end = '\f'; + break; + case 'n': + end = '\n'; + break; + case 'r': + end = '\r'; + break; + case 't': + end = '\t'; + break; + case 'v': + end = '\v'; + break; + foreach (val; Escapables) + { + case val: + } + end = current; + break; + case 'c': + end = parseControlCode(); + break; + case 'x': + end = parseUniHex(pat, 2); + break; + case 'u': + end = parseUniHex(pat, 4); + break; + case 'U': + end = parseUniHex(pat, 8); + break; + default: + if (current >= privateUseStart && current <= privateUseEnd) + enforce(false, "no matching ']' found while parsing character class"); + error("invalid escape sequence"); + } + // Lookahead to check if it's a \T + // where T is sub-pattern terminator in multi-pattern scheme + if (end == '\\' && !pat.empty) + { + if (pat.front >= privateUseStart && pat.front <= privateUseEnd) + enforce(false, "invalid escape sequence"); + } + enforce(last <= end,"inverted range"); + set.add(last, end + 1); + state = State.Start; + break; + } + enforce(next(), "unexpected end of CodepointSet"); + } + return tuple(set, op); + } + + alias ValStack = Stack!(CodepointSet); + alias OpStack = Stack!(Operator); + + //parse and store IR for CodepointSet + void parseCharset() + { + const save = re_flags; + re_flags &= ~RegexOption.freeform; // stop ignoring whitespace if we did + parseCharsetImpl(); + re_flags = save; + // Last next() in parseCharsetImp is executed w/o freeform flag + if (re_flags & RegexOption.freeform) skipSpace(); + } + + void parseCharsetImpl() + { + ValStack vstack; + OpStack opstack; + import std.functional : unaryFun; + // + static bool apply(Operator op, ref ValStack stack) + { + switch (op) + { + case Operator.Negate: + enforce(!stack.empty, "no operand for '^'"); + stack.top = stack.top.inverted; + break; + case Operator.Union: + auto s = stack.pop();//2nd operand + enforce(!stack.empty, "no operand for '||'"); + stack.top.add(s); + break; + case Operator.Difference: + auto s = stack.pop();//2nd operand + enforce(!stack.empty, "no operand for '--'"); + stack.top.sub(s); + break; + case Operator.SymDifference: + auto s = stack.pop();//2nd operand + enforce(!stack.empty, "no operand for '~~'"); + stack.top ~= s; + break; + case Operator.Intersection: + auto s = stack.pop();//2nd operand + enforce(!stack.empty, "no operand for '&&'"); + stack.top.intersect(s); + break; + default: + return false; + } + return true; + } + static bool unrollWhile(alias cond)(ref ValStack vstack, ref OpStack opstack) + { + while (cond(opstack.top)) + { + if (!apply(opstack.pop(),vstack)) + return false;//syntax error + if (opstack.empty) + return false; + } + return true; + } + + L_CharsetLoop: + do + { + switch (current) + { + case '[': + opstack.push(Operator.Open); + enforce(next(), "unexpected end of character class"); + if (current == '^') + { + opstack.push(Operator.Negate); + enforce(next(), "unexpected end of character class"); + } + else if (current == ']') // []...] is special cased + { + enforce(next(), "wrong character set"); + auto pair = parseCharTerm(); + pair[0].add(']', ']'+1); + if (pair[1] != Operator.None) + { + if (opstack.top == Operator.Union) + unrollWhile!(unaryFun!"a == a.Union")(vstack, opstack); + opstack.push(pair[1]); + } + vstack.push(pair[0]); + } + break; + case ']': + enforce(unrollWhile!(unaryFun!"a != a.Open")(vstack, opstack), + "character class syntax error"); + enforce(!opstack.empty, "unmatched ']'"); + opstack.pop(); + if (!next() || opstack.empty) + break L_CharsetLoop; + auto pair = parseCharTerm(); + if (!pair[0].empty)//not only operator e.g. -- or ~~ + { + vstack.top.add(pair[0]);//apply union + } + if (pair[1] != Operator.None) + { + if (opstack.top == Operator.Union) + unrollWhile!(unaryFun!"a == a.Union")(vstack, opstack); + opstack.push(pair[1]); + } + break; + // + default://yet another pair of term(op)? + auto pair = parseCharTerm(); + if (pair[1] != Operator.None) + { + if (opstack.top == Operator.Union) + unrollWhile!(unaryFun!"a == a.Union")(vstack, opstack); + opstack.push(pair[1]); + } + vstack.push(pair[0]); + } + }while (!empty || !opstack.empty); + while (!opstack.empty) + { + enforce(opstack.top != Operator.Open, + "no matching ']' found while parsing character class"); + apply(opstack.pop(), vstack); + } + assert(vstack.length == 1); + g.charsetToIr(vstack.top); + } + + //parse and generate IR for escape stand alone escape sequence + @trusted void parseEscape() + {//accesses array of appender + import std.algorithm.iteration : sum; + switch (current) + { + case 'f': next(); g.put(Bytecode(IR.Char, '\f')); break; + case 'n': next(); g.put(Bytecode(IR.Char, '\n')); break; + case 'r': next(); g.put(Bytecode(IR.Char, '\r')); break; + case 't': next(); g.put(Bytecode(IR.Char, '\t')); break; + case 'v': next(); g.put(Bytecode(IR.Char, '\v')); break; + + case 'd': + next(); + g.charsetToIr(unicode.Nd); + break; + case 'D': + next(); + g.charsetToIr(unicode.Nd.inverted); + break; + case 'b': next(); g.put(Bytecode(IR.Wordboundary, 0)); break; + case 'B': next(); g.put(Bytecode(IR.Notwordboundary, 0)); break; + case 's': + next(); + g.charsetToIr(unicode.White_Space); + break; + case 'S': + next(); + g.charsetToIr(unicode.White_Space.inverted); + break; + case 'w': + next(); + g.charsetToIr(wordCharacter); + break; + case 'W': + next(); + g.charsetToIr(wordCharacter.inverted); + break; + case 'p': case 'P': + auto CodepointSet = parseUnicodePropertySpec(current == 'P'); + g.charsetToIr(CodepointSet); + break; + case 'x': + immutable code = parseUniHex(pat, 2); + next(); + g.put(Bytecode(IR.Char,code)); + break; + case 'u': case 'U': + immutable code = parseUniHex(pat, current == 'u' ? 4 : 8); + next(); + g.put(Bytecode(IR.Char, code)); + break; + case 'c': //control codes + Bytecode code = Bytecode(IR.Char, parseControlCode()); + next(); + g.put(code); + break; + case '0': + next(); + g.put(Bytecode(IR.Char, 0));//NUL character + break; + case '1': .. case '9': + uint nref = cast(uint) current - '0'; + immutable maxBackref = sum(g.groupStack.data); + enforce(nref < maxBackref, "Backref to unseen group"); + //perl's disambiguation rule i.e. + //get next digit only if there is such group number + while (nref < maxBackref && next() && std.ascii.isDigit(current)) + { + nref = nref * 10 + current - '0'; + } + if (nref >= maxBackref) + nref /= 10; + enforce(!g.isOpenGroup(nref), "Backref to open group"); + uint localLimit = maxBackref - g.groupStack.top; + if (nref >= localLimit) + { + g.put(Bytecode(IR.Backref, nref-localLimit)); + g.ir[$-1].setLocalRef(); + } + else + g.put(Bytecode(IR.Backref, nref)); + g.markBackref(nref); + break; + default: + // Lookahead to check if it's a \T + // where T is sub-pattern terminator in multi-pattern scheme + if (current == '\\' && !pat.empty) + { + if (pat.front >= privateUseStart && current <= privateUseEnd) + enforce(false, "invalid escape sequence"); + } + if (current >= privateUseStart && current <= privateUseEnd) + { + g.endPattern(current - privateUseStart + 1); + break; + } + auto op = Bytecode(IR.Char, current); + next(); + g.put(op); + } + } + + //parse and return a CodepointSet for \p{...Property...} and \P{...Property..}, + //\ - assumed to be processed, p - is current + CodepointSet parseUnicodePropertySpec(bool negated) + { + enum MAX_PROPERTY = 128; + char[MAX_PROPERTY] result; + uint k = 0; + enforce(next(), "eof parsing unicode property spec"); + if (current == '{') + { + while (k < MAX_PROPERTY && next() && current !='}' && current !=':') + if (current != '-' && current != ' ' && current != '_') + result[k++] = cast(char) std.ascii.toLower(current); + enforce(k != MAX_PROPERTY, "invalid property name"); + enforce(current == '}', "} expected "); + } + else + {//single char properties e.g.: \pL, \pN ... + enforce(current < 0x80, "invalid property name"); + result[k++] = cast(char) current; + } + auto s = getUnicodeSet(result[0 .. k], negated, + cast(bool)(re_flags & RegexOption.casefold)); + enforce(!s.empty, "unrecognized unicode property spec"); + next(); + return s; + } + + // + @trusted void error(string msg) + { + import std.array : appender; + import std.format : formattedWrite; + auto app = appender!string(); + formattedWrite(app, "%s\nPattern with error: `%s` <--HERE-- `%s`", + msg, origin[0..$-pat.length], pat); + throw new RegexException(app.data); + } + + alias Char = BasicElementOf!R; + + @property program() + { + return makeRegex(this); + } +} + +/+ + Postproces the IR, then optimize. ++/ +@trusted void postprocess(Char)(ref Regex!Char zis) +{//@@@BUG@@@ write is @system + with(zis) + { + struct FixedStack(T) + { + T[] arr; + uint _top; + //this(T[] storage){ arr = storage; _top = -1; } + @property ref T top(){ assert(!empty); return arr[_top]; } + void push(T x){ arr[++_top] = x; } + T pop() { assert(!empty); return arr[_top--]; } + @property bool empty(){ return _top == -1; } + } + auto counterRange = FixedStack!uint(new uint[maxCounterDepth+1], -1); + counterRange.push(1); + ulong cumRange = 0; + for (uint i = 0; i < ir.length; i += ir[i].length) + { + if (ir[i].hotspot) + { + assert(i + 1 < ir.length, + "unexpected end of IR while looking for hotspot"); + ir[i+1] = Bytecode.fromRaw(hotspotTableSize); + hotspotTableSize += counterRange.top; + } + switch (ir[i].code) + { + case IR.RepeatStart, IR.RepeatQStart: + uint repEnd = cast(uint)(i + ir[i].data + IRL!(IR.RepeatStart)); + assert(ir[repEnd].code == ir[i].paired.code); + immutable max = ir[repEnd + 4].raw; + ir[repEnd+2].raw = counterRange.top; + ir[repEnd+3].raw *= counterRange.top; + ir[repEnd+4].raw *= counterRange.top; + ulong cntRange = cast(ulong)(max)*counterRange.top; + cumRange += cntRange; + enforce(cumRange < maxCumulativeRepetitionLength, + "repetition length limit is exceeded"); + counterRange.push(cast(uint) cntRange + counterRange.top); + threadCount += counterRange.top; + break; + case IR.RepeatEnd, IR.RepeatQEnd: + threadCount += counterRange.top; + counterRange.pop(); + break; + case IR.GroupStart: + if (isBackref(ir[i].data)) + ir[i].setBackrefence(); + threadCount += counterRange.top; + break; + case IR.GroupEnd: + if (isBackref(ir[i].data)) + ir[i].setBackrefence(); + threadCount += counterRange.top; + break; + default: + threadCount += counterRange.top; + } + } + checkIfOneShot(); + if (!(flags & RegexInfo.oneShot)) + kickstart = Kickstart!Char(zis, new uint[](256)); + debug(std_regex_allocation) writefln("IR processed, max threads: %d", threadCount); + optimize(zis); + } +} + +void fixupBytecode()(Bytecode[] ir) +{ + Stack!uint fixups; + + with(IR) for (uint i=0; i\\d+)/(?P\\d+)", "2/3", "y", "${d}/${q}", "3/2"), +//set operations: + TestVectors( "[a-z--d-f]", " dfa", "y", "$&", "a"), + TestVectors( "[abc[pq--acq]]{2}", "bqpaca", "y", "$&", "pa"), + TestVectors( "[a-z9&&abc0-9]{3}", "z90a0abc", "y", "$&", "abc"), + TestVectors( "[0-9a-f~~0-5a-z]{2}", "g0a58x", "y", "$&", "8x"), + TestVectors( "[abc[pq]xyz[rs]]{4}", "cqxr", "y", "$&", "cqxr"), + TestVectors( "[abcdf--[ab&&[bcd]][acd]]", "abcdefgh", "y", "$&", "f"), + TestVectors( "[a-c||d-f]+", "abcdef", "y", "$&", "abcdef"), + TestVectors( "[a-f--a-c]+", "abcdef", "y", "$&", "def"), + TestVectors( "[a-c&&b-f]+", "abcdef", "y", "$&", "bc"), + TestVectors( "[a-c~~b-f]+", "abcdef", "y", "$&", "a"), +//unicode blocks & properties: + TestVectors( `\P{Inlatin1suppl ement}`, "\u00c2!", "y", "$&", "!"), + TestVectors( `\p{InLatin-1 Supplement}\p{in-mathematical-operators}\P{Inlatin1suppl ement}`, + "\u00c2\u2200\u00c3\u2203.", "y", "$&", "\u00c3\u2203."), + TestVectors( `[-+*/\p{in-mathematical-operators}]{2}`, "a+\u2212", "y", "$&", "+\u2212"), + TestVectors( `\p{Ll}+`, "XabcD", "y", "$&", "abc"), + TestVectors( `\p{Lu}+`, "абвГДЕ", "y", "$&", "ГДЕ"), + TestVectors( `^\p{Currency Symbol}\p{Sc}`, "$₤", "y", "$&", "$₤"), + TestVectors( `\p{Common}\p{Thai}`, "!ฆ", "y", "$&", "!ฆ"), + TestVectors( `[\d\s]*\D`, "12 \t3\U00001680\u0F20_2", "y", "$&", "12 \t3\U00001680\u0F20_"), + TestVectors( `[c-wф]фф`, "ффф", "y", "$&", "ффф"), +//case insensitive: + TestVectors( `^abcdEf$`, "AbCdEF", "y", "$&", "AbCdEF", "i"), + TestVectors( `Русский язык`, "рУсскИй ЯзЫк", "y", "$&", "рУсскИй ЯзЫк", "i"), + TestVectors( `ⒶⒷⓒ` , "ⓐⓑⒸ", "y", "$&", "ⓐⓑⒸ", "i"), + TestVectors( "\U00010400{2}", "\U00010428\U00010400 ", "y", "$&", "\U00010428\U00010400", "i"), + TestVectors( `[adzУ-Я]{4}`, "DzюЯ", "y", "$&", "DzюЯ", "i"), + TestVectors( `\p{L}\p{Lu}{10}`, "абвгдеЖЗИКЛ", "y", "$&", "абвгдеЖЗИКЛ", "i"), + TestVectors( `(?:Dåb){3}`, "DåbDÅBdÅb", "y", "$&", "DåbDÅBdÅb", "i"), +//escapes: + TestVectors( `\u0041\u005a\U00000065\u0001`, "AZe\u0001", "y", "$&", "AZe\u0001"), + TestVectors( `\u`, "", "c", "-", "-"), + TestVectors( `\U`, "", "c", "-", "-"), + TestVectors( `\u003`, "", "c", "-", "-"), + TestVectors( `[\x00-\x7f]{4}`, "\x00\x09ab", "y", "$&", "\x00\x09ab"), + TestVectors( `[\cJ\cK\cA-\cD]{3}\cQ`, "\x01\x0B\x0A\x11", "y", "$&", "\x01\x0B\x0A\x11"), + TestVectors( `\r\n\v\t\f\\`, "\r\n\v\t\f\\", "y", "$&", "\r\n\v\t\f\\"), + TestVectors( `[\u0003\u0001]{2}`, "\u0001\u0003", "y", "$&", "\u0001\u0003"), + TestVectors( `^[\u0020-\u0080\u0001\n-\r]{8}`, "abc\u0001\v\f\r\n", "y", "$&", "abc\u0001\v\f\r\n"), + TestVectors( `\w+\S\w+`, "ab7!44c", "y", "$&", "ab7!44c"), + TestVectors( `\b\w+\b`, " abde4 ", "y", "$&", "abde4"), + TestVectors( `\b\w+\b`, " abde4", "y", "$&", "abde4"), + TestVectors( `\b\w+\b`, "abde4 ", "y", "$&", "abde4"), + TestVectors( `\pL\pS`, "a\u02DA", "y", "$&", "a\u02DA"), + TestVectors( `\pX`, "", "c", "-", "-"), +// ^, $, \b, \B, multiline : + TestVectors( `\r.*?$`, "abc\r\nxy", "y", "$&", "\r\nxy", "sm"), + TestVectors( `^a$^b$`, "a\r\nb\n", "n", "$&", "-", "m"), + TestVectors( `^a$\r\n^b$`,"a\r\nb\n", "y", "$&", "a\r\nb", "m"), + TestVectors( `^$`, "\r\n", "y", "$&", "", "m"), + TestVectors( `^a$\nx$`, "a\nx\u2028","y", "$&", "a\nx", "m"), + TestVectors( `^a$\nx$`, "a\nx\u2029","y", "$&", "a\nx", "m"), + TestVectors( `^a$\nx$`, "a\nx\u0085","y", "$&", "a\nx","m"), + TestVectors( `^x$`, "\u2028x", "y", "$&", "x", "m"), + TestVectors( `^x$`, "\u2029x", "y", "$&", "x", "m"), + TestVectors( `^x$`, "\u0085x", "y", "$&", "x", "m"), + TestVectors( `\b^.`, "ab", "y", "$&", "a"), + TestVectors( `\B^.`, "ab", "n", "-", "-"), + TestVectors( `^ab\Bc\B`, "\r\nabcd", "y", "$&", "abc", "m"), + TestVectors( `^.*$`, "12345678", "y", "$&", "12345678"), + +// luckily obtained regression on incremental matching in backtracker + TestVectors( `^(?:(?:([0-9A-F]+)\.\.([0-9A-F]+)|([0-9A-F]+))\s*;\s*([^ ]*)\s*#|# (?:\w|_)+=((?:\w|_)+))`, + "0020 ; White_Space # ", "y", "$1-$2-$3", "--0020"), +//lookahead + TestVectors( "(foo.)(?=(bar))", "foobar foodbar", "y", "$&-$1-$2", "food-food-bar" ), + TestVectors( `\b(\d+)[a-z](?=\1)`, "123a123", "y", "$&-$1", "123a-123" ), + TestVectors( `\$(?!\d{3})\w+`, "$123 $abc", "y", "$&", "$abc"), + TestVectors( `(abc)(?=(ed(f))\3)`, "abcedff", "y", "-", "-"), + TestVectors( `\b[A-Za-z0-9.]+(?=(@(?!gmail)))`, "a@gmail,x@com", "y", "$&-$1", "x-@"), + TestVectors( `x()(abc)(?=(d)(e)(f)\2)`, "xabcdefabc", "y", "$&", "xabc"), + TestVectors( `x()(abc)(?=(d)(e)(f)()\3\4\5)`, "xabcdefdef", "y", "$&", "xabc"), +//lookback + TestVectors( `(?<=(ab))\d`, "12ba3ab4", "y", "$&-$1", "4-ab", "i"), + TestVectors( `\w(?"); + assert(bmatch("texttext", greed).hit + == "text"); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + auto cr8 = ctRegex!("^(a)(b)?(c*)"); + auto m8 = bmatch("abcc",cr8); + assert(m8); + assert(m8.captures[1] == "a"); + assert(m8.captures[2] == "b"); + assert(m8.captures[3] == "cc"); + auto cr9 = ctRegex!("q(a|b)*q"); + auto m9 = match("xxqababqyy",cr9); + assert(m9); + assert(equal(bmatch("xxqababqyy",cr9).captures, ["qababq", "b"])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + auto rtr = regex("a|b|c"); + enum ctr = regex("a|b|c"); + assert(equal(rtr.ir,ctr.ir)); + //CTFE parser BUG is triggered by group + //in the middle of alternation (at least not first and not last) + enum testCT = regex(`abc|(edf)|xyz`); + auto testRT = regex(`abc|(edf)|xyz`); + assert(equal(testCT.ir,testRT.ir)); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map; + enum cx = ctRegex!"(A|B|C)"; + auto mx = match("B",cx); + assert(mx); + assert(equal(mx.captures, [ "B", "B"])); + enum cx2 = ctRegex!"(A|B)*"; + assert(match("BAAA",cx2)); + + enum cx3 = ctRegex!("a{3,4}","i"); + auto mx3 = match("AaA",cx3); + assert(mx3); + assert(mx3.captures[0] == "AaA"); + enum cx4 = ctRegex!(`^a{3,4}?[a-zA-Z0-9~]{1,2}`,"i"); + auto mx4 = match("aaaabc", cx4); + assert(mx4); + assert(mx4.captures[0] == "aaaab"); + auto cr8 = ctRegex!("(a)(b)?(c*)"); + auto m8 = bmatch("abcc",cr8); + assert(m8); + assert(m8.captures[1] == "a"); + assert(m8.captures[2] == "b"); + assert(m8.captures[3] == "cc"); + auto cr9 = ctRegex!(".*$", "gm"); + auto m9 = match("First\rSecond", cr9); + assert(m9); + assert(equal(map!"a.hit"(m9), ["First", "", "Second"])); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map; +//global matching + void test_body(alias matchFn)() + { + string s = "a quick brown fox jumps over a lazy dog"; + auto r1 = regex("\\b[a-z]+\\b","g"); + string[] test; + foreach (m; matchFn(s, r1)) + test ~= m.hit; + assert(equal(test, [ "a", "quick", "brown", "fox", "jumps", "over", "a", "lazy", "dog"])); + auto free_reg = regex(` + + abc + \s+ + " + ( + [^"]+ + | \\ " + )+ + " + z + `, "x"); + auto m = match(`abc "quoted string with \" inside"z`,free_reg); + assert(m); + string mails = " hey@you.com no@spam.net "; + auto rm = regex(`@(?<=\S+@)\S+`,"g"); + assert(equal(map!"a[0]"(matchFn(mails, rm)), ["@you.com", "@spam.net"])); + auto m2 = matchFn("First line\nSecond line",regex(".*$","gm")); + assert(equal(map!"a[0]"(m2), ["First line", "", "Second line"])); + auto m2a = matchFn("First line\nSecond line",regex(".+$","gm")); + assert(equal(map!"a[0]"(m2a), ["First line", "Second line"])); + auto m2b = matchFn("First line\nSecond line",regex(".+?$","gm")); + assert(equal(map!"a[0]"(m2b), ["First line", "Second line"])); + debug(std_regex_test) writeln("!!! FReD FLAGS test done "~matchFn.stringof~" !!!"); + } + test_body!bmatch(); + test_body!match(); +} + +//tests for accumulated std.regex issues and other regressions +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map; + void test_body(alias matchFn)() + { + //issue 5857 + //matching goes out of control if ... in (...){x} has .*/.+ + auto c = matchFn("axxxzayyyyyzd",regex("(a.*z){2}d")).captures; + assert(c[0] == "axxxzayyyyyzd"); + assert(c[1] == "ayyyyyz"); + auto c2 = matchFn("axxxayyyyyd",regex("(a.*){2}d")).captures; + assert(c2[0] == "axxxayyyyyd"); + assert(c2[1] == "ayyyyy"); + //issue 2108 + //greedy vs non-greedy + auto nogreed = regex(""); + assert(matchFn("texttext", nogreed).hit + == "text"); + auto greed = regex(""); + assert(matchFn("texttext", greed).hit + == "texttext"); + //issue 4574 + //empty successful match still advances the input + string[] pres, posts, hits; + foreach (m; matchFn("abcabc", regex("","g"))) + { + pres ~= m.pre; + posts ~= m.post; + assert(m.hit.empty); + + } + auto heads = [ + "abcabc", + "abcab", + "abca", + "abc", + "ab", + "a", + "" + ]; + auto tails = [ + "abcabc", + "bcabc", + "cabc", + "abc", + "bc", + "c", + "" + ]; + assert(pres == array(retro(heads))); + assert(posts == tails); + //issue 6076 + //regression on .* + auto re = regex("c.*|d"); + auto m = matchFn("mm", re); + assert(!m); + debug(std_regex_test) writeln("!!! FReD REGRESSION test done "~matchFn.stringof~" !!!"); + auto rprealloc = regex(`((.){5}.{1,10}){5}`); + auto arr = array(repeat('0',100)); + auto m2 = matchFn(arr, rprealloc); + assert(m2); + assert(collectException( + regex(r"^(import|file|binary|config)\s+([^\(]+)\(?([^\)]*)\)?\s*$") + ) is null); + foreach (ch; [Escapables]) + { + assert(match(to!string(ch),regex(`[\`~ch~`]`))); + assert(!match(to!string(ch),regex(`[^\`~ch~`]`))); + assert(match(to!string(ch),regex(`[\`~ch~`-\`~ch~`]`))); + } + //bugzilla 7718 + string strcmd = "./myApp.rb -os OSX -path \"/GIT/Ruby Apps/sec\" -conf 'notimer'"; + auto reStrCmd = regex (`(".*")|('.*')`, "g"); + assert(equal(map!"a[0]"(matchFn(strcmd, reStrCmd)), + [`"/GIT/Ruby Apps/sec"`, `'notimer'`])); + } + test_body!bmatch(); + test_body!match(); +} + +// tests for replace +@safe unittest +{ + void test(alias matchFn)() + { + import std.uni : toUpper; + + foreach (i, v; AliasSeq!(string, wstring, dstring)) + { + auto baz(Cap)(Cap m) + if (is(Cap == Captures!(Cap.String))) + { + return toUpper(m.hit); + } + alias String = v; + assert(std.regex.replace!(matchFn)(to!String("ark rapacity"), regex(to!String("r")), to!String("c")) + == to!String("ack rapacity")); + assert(std.regex.replace!(matchFn)(to!String("ark rapacity"), regex(to!String("r"), "g"), to!String("c")) + == to!String("ack capacity")); + assert(std.regex.replace!(matchFn)(to!String("noon"), regex(to!String("^n")), to!String("[$&]")) + == to!String("[n]oon")); + assert(std.regex.replace!(matchFn)( + to!String("test1 test2"), regex(to!String(`\w+`),"g"), to!String("$`:$'") + ) == to!String(": test2 test1 :")); + auto s = std.regex.replace!(baz!(Captures!(String)))(to!String("Strap a rocket engine on a chicken."), + regex(to!String("[ar]"), "g")); + assert(s == "StRAp A Rocket engine on A chicken."); + } + debug(std_regex_test) writeln("!!! Replace test done "~matchFn.stringof~" !!!"); + } + test!(bmatch)(); + test!(match)(); +} + +// tests for splitter +@safe unittest +{ + import std.algorithm.comparison : equal; + auto s1 = ", abc, de, fg, hi, "; + auto sp1 = splitter(s1, regex(", *")); + auto w1 = ["", "abc", "de", "fg", "hi", ""]; + assert(equal(sp1, w1)); + + auto s2 = ", abc, de, fg, hi"; + auto sp2 = splitter(s2, regex(", *")); + auto w2 = ["", "abc", "de", "fg", "hi"]; + + uint cnt; + foreach (e; sp2) + { + assert(w2[cnt++] == e); + } + assert(equal(sp2, w2)); +} + +@safe unittest +{ + char[] s1 = ", abc, de, fg, hi, ".dup; + auto sp2 = splitter(s1, regex(", *")); +} + +@safe unittest +{ + import std.algorithm.comparison : equal; + auto s1 = ", abc, de, fg, hi, "; + auto w1 = ["", "abc", "de", "fg", "hi", ""]; + assert(equal(split(s1, regex(", *")), w1[])); +} + +@safe unittest +{ // bugzilla 7141 + string pattern = `[a\--b]`; + assert(match("-", pattern)); + assert(match("b", pattern)); + string pattern2 = `[&-z]`; + assert(match("b", pattern2)); +} +@safe unittest +{//bugzilla 7111 + assert(match("", regex("^"))); +} +@safe unittest +{//bugzilla 7300 + assert(!match("a"d, "aa"d)); +} + +// bugzilla 7551 +@safe unittest +{ + auto r = regex("[]abc]*"); + assert("]ab".matchFirst(r).hit == "]ab"); + assertThrown(regex("[]")); + auto r2 = regex("[]abc--ab]*"); + assert("]ac".matchFirst(r2).hit == "]"); +} + +@safe unittest +{//bugzilla 7674 + assert("1234".replace(regex("^"), "$$") == "$1234"); + assert("hello?".replace(regex(r"\?", "g"), r"\?") == r"hello\?"); + assert("hello?".replace(regex(r"\?", "g"), r"\\?") != r"hello\?"); +} +@safe unittest +{// bugzilla 7679 + import std.algorithm.comparison : equal; + foreach (S; AliasSeq!(string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + enum re = ctRegex!(to!S(r"\.")); + auto str = to!S("a.b"); + assert(equal(std.regex.splitter(str, re), [to!S("a"), to!S("b")])); + assert(split(str, re) == [to!S("a"), to!S("b")]); + }(); +} +@safe unittest +{//bugzilla 8203 + string data = " + NAME = XPAW01_STA:STATION + NAME = XPAW01_STA + "; + auto uniFileOld = data; + auto r = regex( + r"^NAME = (?P[a-zA-Z0-9_]+):*(?P[a-zA-Z0-9_]*)","gm"); + auto uniCapturesNew = match(uniFileOld, r); + for (int i = 0; i < 20; i++) + foreach (matchNew; uniCapturesNew) {} + //a second issue with same symptoms + auto r2 = regex(`([а-яА-Я\-_]+\s*)+(?<=[\s\.,\^])`); + match("аллея Театральная", r2); +} +@safe unittest +{// bugzilla 8637 purity of enforce + auto m = match("hello world", regex("world")); + enforce(m); +} + +// bugzilla 8725 +@safe unittest +{ + static italic = regex( r"\* + (?!\s+) + (.*?) + (?!\s+) + \*", "gx" ); + string input = "this * is* interesting, *very* interesting"; + assert(replace(input, italic, "$1") == + "this * is* interesting, very interesting"); +} + +// bugzilla 8349 +@safe unittest +{ + enum peakRegexStr = r"\>(wgEncode.*Tfbs.*\.(?:narrow)|(?:broad)Peak.gz)"; + enum peakRegex = ctRegex!(peakRegexStr); + //note that the regex pattern itself is probably bogus + assert(match(r"\>wgEncode-blah-Tfbs.narrow", peakRegex)); +} + +// bugzilla 9211 +@safe unittest +{ + import std.algorithm.comparison : equal; + auto rx_1 = regex(r"^(\w)*(\d)"); + auto m = match("1234", rx_1); + assert(equal(m.front, ["1234", "3", "4"])); + auto rx_2 = regex(r"^([0-9])*(\d)"); + auto m2 = match("1234", rx_2); + assert(equal(m2.front, ["1234", "3", "4"])); +} + +// bugzilla 9280 +@safe unittest +{ + string tomatch = "a!b@c"; + static r = regex(r"^(?P.*?)!(?P.*?)@(?P.*?)$"); + auto nm = match(tomatch, r); + assert(nm); + auto c = nm.captures; + assert(c[1] == "a"); + assert(c["nick"] == "a"); +} + + +// bugzilla 9579 +@safe unittest +{ + char[] input = ['a', 'b', 'c']; + string format = "($1)"; + // used to give a compile error: + auto re = regex(`(a)`, "g"); + auto r = replace(input, re, format); + assert(r == "(a)bc"); +} + +// bugzilla 9634 +@safe unittest +{ + auto re = ctRegex!"(?:a+)"; + assert(match("aaaa", re).hit == "aaaa"); +} + +//bugzilla 10798 +@safe unittest +{ + auto cr = ctRegex!("[abcd--c]*"); + auto m = "abc".match(cr); + assert(m); + assert(m.hit == "ab"); +} + +// bugzilla 10913 +@system unittest +{ + @system static string foo(const(char)[] s) + { + return s.dup; + } + @safe static string bar(const(char)[] s) + { + return s.dup; + } + () @system { + replace!((a) => foo(a.hit))("blah", regex(`a`)); + }(); + () @safe { + replace!((a) => bar(a.hit))("blah", regex(`a`)); + }(); +} + +// bugzilla 11262 +@safe unittest +{ + enum reg = ctRegex!(r",", "g"); + auto str = "This,List"; + str = str.replace(reg, "-"); + assert(str == "This-List"); +} + +// bugzilla 11775 +@safe unittest +{ + assert(collectException(regex("a{1,0}"))); +} + +// bugzilla 11839 +@safe unittest +{ + import std.algorithm.comparison : equal; + assert(regex(`(?P\w+)`).namedCaptures.equal(["var1"])); + assert(collectException(regex(`(?P<1>\w+)`))); + assert(regex(`(?P\w+)`).namedCaptures.equal(["v1"])); + assert(regex(`(?P<__>\w+)`).namedCaptures.equal(["__"])); + assert(regex(`(?P<я>\w+)`).namedCaptures.equal(["я"])); +} + +// bugzilla 12076 +@safe unittest +{ + auto RE = ctRegex!(r"(?abc)`); + assert(collectException("abc".matchFirst(r)["b"])); +} + +// bugzilla 12691 +@safe unittest +{ + assert(bmatch("e@", "^([a-z]|)*$").empty); + assert(bmatch("e@", ctRegex!`^([a-z]|)*$`).empty); +} + +//bugzilla 12713 +@safe unittest +{ + assertThrown(regex("[[a-z]([a-z]|(([[a-z])))")); +} + +//bugzilla 12747 +@safe unittest +{ + assertThrown(regex(`^x(\1)`)); + assertThrown(regex(`^(x(\1))`)); + assertThrown(regex(`^((x)(?=\1))`)); +} + +// bugzilla 14504 +@safe unittest +{ + auto p = ctRegex!("a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?" ~ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); +} + +// bugzilla 14529 +@safe unittest +{ + auto ctPat2 = regex(r"^[CDF]$", "i"); + foreach (v; ["C", "c", "D", "d", "F", "f"]) + assert(matchAll(v, ctPat2).front.hit == v); +} + +// bugzilla 14615 +@safe unittest +{ + import std.array : appender; + import std.regex : replaceFirst, replaceFirstInto, regex; + import std.stdio : writeln; + + auto example = "Hello, world!"; + auto pattern = regex("^Hello, (bug)"); // won't find this one + auto result = replaceFirst(example, pattern, "$1 Sponge Bob"); + assert(result == "Hello, world!"); // Ok. + + auto sink = appender!string; + replaceFirstInto(sink, example, pattern, "$1 Sponge Bob"); + assert(sink.data == "Hello, world!"); + replaceAllInto(sink, example, pattern, "$1 Sponge Bob"); + assert(sink.data == "Hello, world!Hello, world!"); +} + +// bugzilla 15573 +@safe unittest +{ + auto rx = regex("[c d]", "x"); + assert("a b".matchFirst(rx)); +} + +// bugzilla 15864 +@safe unittest +{ + regex(`((.+)`; + static titleRegex = ctRegex!titlePattern; + string input = "" ~ "<".repeat(100_000).join; + assert(input.matchFirst(titleRegex).empty); +} + +// bugzilla 17212 +@safe unittest +{ + auto r = regex(" [a] ", "x"); + assert("a".matchFirst(r)); +} + +// bugzilla 17157 +@safe unittest +{ + import std.algorithm.comparison : equal; + auto ctr = ctRegex!"(a)|(b)|(c)|(d)"; + auto r = regex("(a)|(b)|(c)|(d)", "g"); + auto s = "--a--b--c--d--"; + auto outcomes = [ + ["a", "a", "", "", ""], + ["b", "", "b", "", ""], + ["c", "", "", "c", ""], + ["d", "", "", "", "d"] + ]; + assert(equal!equal(s.matchAll(ctr), outcomes)); + assert(equal!equal(s.bmatch(r), outcomes)); +} + +// bugzilla 17667 +@safe unittest +{ + import std.algorithm.searching : canFind; + void willThrow(T, size_t line = __LINE__)(T arg, string msg) + { + auto e = collectException(regex(arg)); + assert(e.msg.canFind(msg), to!string(line) ~ ": " ~ e.msg); + } + willThrow([r".", r"[\(\{[\]\}\)]"], "no matching ']' found while parsing character class"); + willThrow([r"[\", r"123"], "no matching ']' found while parsing character class"); + willThrow([r"[a-", r"123"], "no matching ']' found while parsing character class"); + willThrow([r"[a-\", r"123"], "invalid escape sequence"); + willThrow([r"\", r"123"], "invalid escape sequence"); +} + +// bugzilla 17668 +@safe unittest +{ + import std.algorithm.searching; + auto e = collectException!RegexException(regex(q"<[^]>")); + assert(e.msg.canFind("no operand for '^'")); +} + +// bugzilla 17673 +@safe unittest +{ + string str = `<">`; + string[] regexps = ["abc", "\"|x"]; + auto regexp = regex(regexps); + auto c = matchFirst(str, regexp); + assert(c); + assert(c.whichPattern == 2); +} + diff --git a/libphobos/src/std/regex/internal/thompson.d b/libphobos/src/std/regex/internal/thompson.d new file mode 100644 index 0000000..4d7deaa --- /dev/null +++ b/libphobos/src/std/regex/internal/thompson.d @@ -0,0 +1,1188 @@ +//Written in the D programming language +/* + Implementation of Thompson NFA std.regex engine. + Key point is evaluation of all possible threads (state) at each step + in a breadth-first manner, thereby geting some nice properties: + - looking at each character only once + - merging of equivalent threads, that gives matching process linear time complexity +*/ +module std.regex.internal.thompson; + +package(std.regex): + +import std.range.primitives; +import std.regex.internal.ir; + +//State of VM thread +struct Thread(DataIndex) +{ + Thread* next; //intrusive linked list + uint pc; + uint counter; //loop counter + uint uopCounter; //counts micro operations inside one macro instruction (e.g. BackRef) + Group!DataIndex[1] matches; +} + +//head-tail singly-linked list +struct ThreadList(DataIndex) +{ + Thread!DataIndex* tip = null, toe = null; + //add new thread to the start of list + void insertFront(Thread!DataIndex* t) + { + if (tip) + { + t.next = tip; + tip = t; + } + else + { + t.next = null; + tip = toe = t; + } + } + //add new thread to the end of list + void insertBack(Thread!DataIndex* t) + { + if (toe) + { + toe.next = t; + toe = t; + } + else + tip = toe = t; + toe.next = null; + } + //move head element out of list + Thread!DataIndex* fetch() + { + auto t = tip; + if (tip == toe) + tip = toe = null; + else + tip = tip.next; + return t; + } + //non-destructive iteration of ThreadList + struct ThreadRange + { + const(Thread!DataIndex)* ct; + this(ThreadList tlist){ ct = tlist.tip; } + @property bool empty(){ return ct is null; } + @property const(Thread!DataIndex)* front(){ return ct; } + @property popFront() + { + assert(ct); + ct = ct.next; + } + } + @property bool empty() + { + return tip == null; + } + ThreadRange opSlice() + { + return ThreadRange(this); + } +} + +template ThompsonOps(E, S, bool withInput:true) +{ +@trusted: + static bool op(IR code:IR.End)(E* e, S* state) + { + with(e) with(state) + { + finish(t, matches, re.ir[t.pc].data); + //fix endpoint of the whole match + matches[0].end = index; + recycle(t); + //cut off low priority threads + recycle(clist); + recycle(worklist); + debug(std_regex_matcher) writeln("Finished thread ", matches); + return false; // no more state to eval + } + } + + static bool op(IR code:IR.Wordboundary)(E* e, S* state) + { + with(e) with(state) + { + dchar back; + DataIndex bi; + //at start & end of input + if (atStart && wordMatcher[front]) + { + t.pc += IRL!(IR.Wordboundary); + return true; + } + else if (atEnd && s.loopBack(index).nextChar(back, bi) + && wordMatcher[back]) + { + t.pc += IRL!(IR.Wordboundary); + return true; + } + else if (s.loopBack(index).nextChar(back, bi)) + { + bool af = wordMatcher[front]; + bool ab = wordMatcher[back]; + if (af ^ ab) + { + t.pc += IRL!(IR.Wordboundary); + return true; + } + } + return popState(e); + } + } + + static bool op(IR code:IR.Notwordboundary)(E* e, S* state) + { + with(e) with(state) + { + dchar back; + DataIndex bi; + //at start & end of input + if (atStart && wordMatcher[front]) + { + return popState(e); + } + else if (atEnd && s.loopBack(index).nextChar(back, bi) + && wordMatcher[back]) + { + return popState(e); + } + else if (s.loopBack(index).nextChar(back, bi)) + { + bool af = wordMatcher[front]; + bool ab = wordMatcher[back] != 0; + if (af ^ ab) + { + return popState(e); + } + } + t.pc += IRL!(IR.Notwordboundary); + } + return true; + } + + static bool op(IR code:IR.Bof)(E* e, S* state) + { + with(e) with(state) + { + if (atStart) + { + t.pc += IRL!(IR.Bof); + return true; + } + else + { + return popState(e); + } + } + } + + static bool op(IR code:IR.Bol)(E* e, S* state) + { + with(e) with(state) + { + dchar back; + DataIndex bi; + if (atStart + ||(s.loopBack(index).nextChar(back,bi) + && startOfLine(back, front == '\n'))) + { + t.pc += IRL!(IR.Bol); + return true; + } + else + { + return popState(e); + } + } + } + + static bool op(IR code:IR.Eof)(E* e, S* state) + { + with(e) with(state) + { + if (atEnd) + { + t.pc += IRL!(IR.Eol); + return true; + } + else + { + return popState(e); + } + } + } + + static bool op(IR code:IR.Eol)(E* e, S* state) + { + with(e) with(state) + { + dchar back; + DataIndex bi; + //no matching inside \r\n + if (atEnd || (endOfLine(front, s.loopBack(index).nextChar(back, bi) + && back == '\r'))) + { + t.pc += IRL!(IR.Eol); + return true; + } + else + { + return popState(e); + } + + } + } + + static bool op(IR code:IR.InfiniteStart)(E* e, S* state) + { + with(e) with(state) + t.pc += re.ir[t.pc].data + IRL!(IR.InfiniteStart); + return op!(IR.InfiniteEnd)(e,state); + } + + static bool op(IR code:IR.InfiniteBloomStart)(E* e, S* state) + { + with(e) with(state) + t.pc += re.ir[t.pc].data + IRL!(IR.InfiniteBloomStart); + return op!(IR.InfiniteBloomEnd)(e,state); + } + + static bool op(IR code:IR.InfiniteQStart)(E* e, S* state) + { + with(e) with(state) + t.pc += re.ir[t.pc].data + IRL!(IR.InfiniteQStart); + return op!(IR.InfiniteQEnd)(e,state); + } + + static bool op(IR code:IR.RepeatStart)(E* e, S* state) + { + with(e) with(state) + t.pc += re.ir[t.pc].data + IRL!(IR.RepeatStart); + return op!(IR.RepeatEnd)(e,state); + } + + static bool op(IR code:IR.RepeatQStart)(E* e, S* state) + { + with(e) with(state) + t.pc += re.ir[t.pc].data + IRL!(IR.RepeatQStart); + return op!(IR.RepeatQEnd)(e,state); + } + + static bool op(IR code)(E* e, S* state) + if (code == IR.RepeatEnd || code == IR.RepeatQEnd) + { + with(e) with(state) + { + //len, step, min, max + uint len = re.ir[t.pc].data; + uint step = re.ir[t.pc+2].raw; + uint min = re.ir[t.pc+3].raw; + if (t.counter < min) + { + t.counter += step; + t.pc -= len; + return true; + } + if (merge[re.ir[t.pc + 1].raw+t.counter] < genCounter) + { + debug(std_regex_matcher) writefln("A thread(pc=%s) passed there : %s ; GenCounter=%s mergetab=%s", + t.pc, index, genCounter, merge[re.ir[t.pc + 1].raw+t.counter] ); + merge[re.ir[t.pc + 1].raw+t.counter] = genCounter; + } + else + { + debug(std_regex_matcher) + writefln("A thread(pc=%s) got merged there : %s ; GenCounter=%s mergetab=%s", + t.pc, index, genCounter, merge[re.ir[t.pc + 1].raw+t.counter] ); + return popState(e); + } + uint max = re.ir[t.pc+4].raw; + if (t.counter < max) + { + if (re.ir[t.pc].code == IR.RepeatEnd) + { + //queue out-of-loop thread + worklist.insertFront(fork(t, t.pc + IRL!(IR.RepeatEnd), t.counter % step)); + t.counter += step; + t.pc -= len; + } + else + { + //queue into-loop thread + worklist.insertFront(fork(t, t.pc - len, t.counter + step)); + t.counter %= step; + t.pc += IRL!(IR.RepeatEnd); + } + } + else + { + t.counter %= step; + t.pc += IRL!(IR.RepeatEnd); + } + return true; + } + } + + static bool op(IR code)(E* e, S* state) + if (code == IR.InfiniteEnd || code == IR.InfiniteQEnd) + { + with(e) with(state) + { + if (merge[re.ir[t.pc + 1].raw+t.counter] < genCounter) + { + debug(std_regex_matcher) writefln("A thread(pc=%s) passed there : %s ; GenCounter=%s mergetab=%s", + t.pc, index, genCounter, merge[re.ir[t.pc + 1].raw+t.counter] ); + merge[re.ir[t.pc + 1].raw+t.counter] = genCounter; + } + else + { + debug(std_regex_matcher) writefln("A thread(pc=%s) got merged there : %s ; GenCounter=%s mergetab=%s", + t.pc, index, genCounter, merge[re.ir[t.pc + 1].raw+t.counter] ); + return popState(e); + } + uint len = re.ir[t.pc].data; + uint pc1, pc2; //branches to take in priority order + if (re.ir[t.pc].code == IR.InfiniteEnd) + { + pc1 = t.pc - len; + pc2 = t.pc + IRL!(IR.InfiniteEnd); + } + else + { + pc1 = t.pc + IRL!(IR.InfiniteEnd); + pc2 = t.pc - len; + } + worklist.insertFront(fork(t, pc2, t.counter)); + t.pc = pc1; + return true; + } + } + + static bool op(IR code)(E* e, S* state) + if (code == IR.InfiniteBloomEnd) + { + with(e) with(state) + { + if (merge[re.ir[t.pc + 1].raw+t.counter] < genCounter) + { + debug(std_regex_matcher) writefln("A thread(pc=%s) passed there : %s ; GenCounter=%s mergetab=%s", + t.pc, index, genCounter, merge[re.ir[t.pc + 1].raw+t.counter] ); + merge[re.ir[t.pc + 1].raw+t.counter] = genCounter; + } + else + { + debug(std_regex_matcher) writefln("A thread(pc=%s) got merged there : %s ; GenCounter=%s mergetab=%s", + t.pc, index, genCounter, merge[re.ir[t.pc + 1].raw+t.counter] ); + return popState(e); + } + uint len = re.ir[t.pc].data; + uint pc1, pc2; //branches to take in priority order + pc1 = t.pc - len; + pc2 = t.pc + IRL!(IR.InfiniteBloomEnd); + uint filterIndex = re.ir[t.pc + 2].raw; + if (re.filters[filterIndex][front]) + worklist.insertFront(fork(t, pc2, t.counter)); + t.pc = pc1; + return true; + } + } + + static bool op(IR code:IR.OrEnd)(E* e, S* state) + { + with(e) with(state) + { + if (merge[re.ir[t.pc + 1].raw+t.counter] < genCounter) + { + debug(std_regex_matcher) writefln("A thread(pc=%s) passed there : %s ; GenCounter=%s mergetab=%s", + t.pc, s[index .. s.lastIndex], genCounter, merge[re.ir[t.pc + 1].raw + t.counter] ); + merge[re.ir[t.pc + 1].raw+t.counter] = genCounter; + t.pc += IRL!(IR.OrEnd); + } + else + { + debug(std_regex_matcher) writefln("A thread(pc=%s) got merged there : %s ; GenCounter=%s mergetab=%s", + t.pc, s[index .. s.lastIndex], genCounter, merge[re.ir[t.pc + 1].raw + t.counter] ); + return popState(e); + } + return true; + } + } + + static bool op(IR code:IR.OrStart)(E* e, S* state) + { + with(e) with(state) + { + t.pc += IRL!(IR.OrStart); + return op!(IR.Option)(e,state); + } + } + + static bool op(IR code:IR.Option)(E* e, S* state) + { + with(e) with(state) + { + uint next = t.pc + re.ir[t.pc].data + IRL!(IR.Option); + //queue next Option + if (re.ir[next].code == IR.Option) + { + worklist.insertFront(fork(t, next, t.counter)); + } + t.pc += IRL!(IR.Option); + return true; + } + } + + static bool op(IR code:IR.GotoEndOr)(E* e, S* state) + { + with(e) with(state) + { + t.pc = t.pc + re.ir[t.pc].data + IRL!(IR.GotoEndOr); + return op!(IR.OrEnd)(e, state); + } + } + + static bool op(IR code:IR.GroupStart)(E* e, S* state) + { + with(e) with(state) + { + uint n = re.ir[t.pc].data; + t.matches.ptr[n].begin = index; + t.pc += IRL!(IR.GroupStart); + return true; + } + } + static bool op(IR code:IR.GroupEnd)(E* e, S* state) + { + with(e) with(state) + { + uint n = re.ir[t.pc].data; + t.matches.ptr[n].end = index; + t.pc += IRL!(IR.GroupEnd); + return true; + } + } + + static bool op(IR code:IR.Backref)(E* e, S* state) + { + with(e) with(state) + { + uint n = re.ir[t.pc].data; + Group!DataIndex* source = re.ir[t.pc].localRef ? t.matches.ptr : backrefed.ptr; + assert(source); + if (source[n].begin == source[n].end)//zero-width Backref! + { + t.pc += IRL!(IR.Backref); + return true; + } + else + { + size_t idx = source[n].begin + t.uopCounter; + size_t end = source[n].end; + if (s[idx .. end].front == front) + { + import std.utf : stride; + + t.uopCounter += stride(s[idx .. end], 0); + if (t.uopCounter + source[n].begin == source[n].end) + {//last codepoint + t.pc += IRL!(IR.Backref); + t.uopCounter = 0; + } + nlist.insertBack(t); + } + else + recycle(t); + t = worklist.fetch(); + return t != null; + } + } + } + + + static bool op(IR code)(E* e, S* state) + if (code == IR.LookbehindStart || code == IR.NeglookbehindStart) + { + with(e) with(state) + { + uint len = re.ir[t.pc].data; + uint ms = re.ir[t.pc + 1].raw, me = re.ir[t.pc + 2].raw; + uint end = t.pc + len + IRL!(IR.LookbehindEnd) + IRL!(IR.LookbehindStart); + bool positive = re.ir[t.pc].code == IR.LookbehindStart; + static if (Stream.isLoopback) + auto matcher = fwdMatcher(t.pc, end, subCounters.get(t.pc, 0)); + else + auto matcher = bwdMatcher(t.pc, end, subCounters.get(t.pc, 0)); + matcher.re.ngroup = me - ms; + matcher.backrefed = backrefed.empty ? t.matches : backrefed; + //backMatch + auto mRes = matcher.matchOneShot(t.matches.ptr[ms .. me], IRL!(IR.LookbehindStart)); + freelist = matcher.freelist; + subCounters[t.pc] = matcher.genCounter; + if ((mRes != 0 ) ^ positive) + { + return popState(e); + } + t.pc = end; + return true; + } + } + + static bool op(IR code)(E* e, S* state) + if (code == IR.LookaheadStart || code == IR.NeglookaheadStart) + { + with(e) with(state) + { + auto save = index; + uint len = re.ir[t.pc].data; + uint ms = re.ir[t.pc+1].raw, me = re.ir[t.pc+2].raw; + uint end = t.pc+len+IRL!(IR.LookaheadEnd)+IRL!(IR.LookaheadStart); + bool positive = re.ir[t.pc].code == IR.LookaheadStart; + static if (Stream.isLoopback) + auto matcher = bwdMatcher(t.pc, end, subCounters.get(t.pc, 0)); + else + auto matcher = fwdMatcher(t.pc, end, subCounters.get(t.pc, 0)); + matcher.re.ngroup = me - ms; + matcher.backrefed = backrefed.empty ? t.matches : backrefed; + auto mRes = matcher.matchOneShot(t.matches.ptr[ms .. me], IRL!(IR.LookaheadStart)); + freelist = matcher.freelist; + subCounters[t.pc] = matcher.genCounter; + s.reset(index); + next(); + if ((mRes != 0) ^ positive) + { + return popState(e); + } + t.pc = end; + return true; + } + } + + static bool op(IR code)(E* e, S* state) + if (code == IR.LookaheadEnd || code == IR.NeglookaheadEnd || + code == IR.LookbehindEnd || code == IR.NeglookbehindEnd) + { + with(e) with(state) + { + finish(t, matches.ptr[0 .. re.ngroup], re.ir[t.pc].data); + recycle(t); + //cut off low priority threads + recycle(clist); + recycle(worklist); + return false; // no more state + } + } + + static bool op(IR code:IR.Nop)(E* e, S* state) + { + with(state) t.pc += IRL!(IR.Nop); + return true; + } + + static bool op(IR code:IR.OrChar)(E* e, S* state) + { + with(e) with(state) + { + uint len = re.ir[t.pc].sequence; + uint end = t.pc + len; + static assert(IRL!(IR.OrChar) == 1); + for (; t.pc < end; t.pc++) + if (re.ir[t.pc].data == front) + break; + if (t.pc != end) + { + t.pc = end; + nlist.insertBack(t); + } + else + recycle(t); + t = worklist.fetch(); + return t != null; + } + } + + static bool op(IR code:IR.Char)(E* e, S* state) + { + with(e) with(state) + { + if (front == re.ir[t.pc].data) + { + t.pc += IRL!(IR.Char); + nlist.insertBack(t); + } + else + recycle(t); + t = worklist.fetch(); + return t != null; + } + } + + static bool op(IR code:IR.Any)(E* e, S* state) + { + with(e) with(state) + { + t.pc += IRL!(IR.Any); + nlist.insertBack(t); + t = worklist.fetch(); + return t != null; + } + } + + static bool op(IR code:IR.CodepointSet)(E* e, S* state) + { + with(e) with(state) + { + if (re.charsets[re.ir[t.pc].data].scanFor(front)) + { + t.pc += IRL!(IR.CodepointSet); + nlist.insertBack(t); + } + else + { + recycle(t); + } + t = worklist.fetch(); + return t != null; + } + } + + static bool op(IR code:IR.Trie)(E* e, S* state) + { + with(e) with(state) + { + if (re.matchers[re.ir[t.pc].data][front]) + { + t.pc += IRL!(IR.Trie); + nlist.insertBack(t); + } + else + { + recycle(t); + } + t = worklist.fetch(); + return t != null; + } + } + +} + +template ThompsonOps(E,S, bool withInput:false) +{ +@trusted: + // can't match these without input + static bool op(IR code)(E* e, S* state) + if (code == IR.Char || code == IR.OrChar || code == IR.CodepointSet + || code == IR.Trie || code == IR.Char || code == IR.Any) + { + return state.popState(e); + } + + // special case of zero-width backref + static bool op(IR code:IR.Backref)(E* e, S* state) + { + with(e) with(state) + { + uint n = re.ir[t.pc].data; + Group!DataIndex* source = re.ir[t.pc].localRef ? t.matches.ptr : backrefed.ptr; + assert(source); + if (source[n].begin == source[n].end)//zero-width Backref! + { + t.pc += IRL!(IR.Backref); + return true; + } + else + return popState(e); + } + } + + // forward all control flow to normal versions + static bool op(IR code)(E* e, S* state) + if (code != IR.Char && code != IR.OrChar && code != IR.CodepointSet + && code != IR.Trie && code != IR.Char && code != IR.Any && code != IR.Backref) + { + return ThompsonOps!(E,S,true).op!code(e,state); + } +} + +/+ + Thomspon matcher does all matching in lockstep, + never looking at the same char twice ++/ +@trusted struct ThompsonMatcher(Char, StreamType = Input!Char) +if (is(Char : dchar)) +{ + alias DataIndex = Stream.DataIndex; + alias Stream = StreamType; + alias OpFunc = bool function(ThompsonMatcher*, State*); + alias BackMatcher = ThompsonMatcher!(Char, BackLooper!(Stream)); + alias OpBackFunc = bool function(BackMatcher*, BackMatcher.State*); + Thread!DataIndex* freelist; + ThreadList!DataIndex clist, nlist; + DataIndex[] merge; + Group!DataIndex[] backrefed; + Regex!Char re; //regex program + Stream s; + dchar front; + DataIndex index; + DataIndex genCounter; //merge trace counter, goes up on every dchar + size_t[size_t] subCounters; //a table of gen counter per sub-engine: PC -> counter + OpFunc[] opCacheTrue; // pointers to Op!(IR.xyz) for each bytecode + OpFunc[] opCacheFalse; // ditto + OpBackFunc[] opCacheBackTrue; // ditto + OpBackFunc[] opCacheBackFalse; // ditto + size_t threadSize; + int matched; + bool exhausted; + + static struct State + { + Thread!DataIndex* t; + ThreadList!DataIndex worklist; + Group!DataIndex[] matches; + + bool popState(E)(E* e) + { + with(e) + { + recycle(t); + t = worklist.fetch(); + return t != null; + } + } + + } + + static if (__traits(hasMember,Stream, "search")) + { + enum kicked = true; + } + else + enum kicked = false; + + static size_t getThreadSize(const ref Regex!Char re) + { + return re.ngroup + ? (Thread!DataIndex).sizeof + (re.ngroup-1)*(Group!DataIndex).sizeof + : (Thread!DataIndex).sizeof - (Group!DataIndex).sizeof; + } + + static size_t initialMemory(const ref Regex!Char re) + { + return getThreadSize(re)*re.threadCount + re.hotspotTableSize*size_t.sizeof + +4*OpFunc.sizeof*re.ir.length; + } + + //true if it's start of input + @property bool atStart(){ return index == 0; } + + //true if it's end of input + @property bool atEnd(){ return index == s.lastIndex && s.atEnd; } + + bool next() + { + if (!s.nextChar(front, index)) + { + index = s.lastIndex; + return false; + } + return true; + } + + static if (kicked) + { + bool search() + { + + if (!s.search(re.kickstart, front, index)) + { + index = s.lastIndex; + return false; + } + return true; + } + } + + void initExternalMemory(void[] memory) + { + threadSize = getThreadSize(re); + prepareFreeList(re.threadCount, memory); + if (re.hotspotTableSize) + { + merge = arrayInChunk!(DataIndex)(re.hotspotTableSize, memory); + merge[] = 0; + } + opCacheTrue = arrayInChunk!(OpFunc)(re.ir.length, memory); + opCacheFalse = arrayInChunk!(OpFunc)(re.ir.length, memory); + opCacheBackTrue = arrayInChunk!(OpBackFunc)(re.ir.length, memory); + opCacheBackFalse = arrayInChunk!(OpBackFunc)(re.ir.length, memory); + + for (uint pc = 0; pc<re.ir.length; pc += re.ir[pc].length) + { + L_dispatch: + switch (re.ir[pc].code) + { + foreach (e; __traits(allMembers, IR)) + { + mixin(`case IR.`~e~`: + opCacheTrue[pc] = &Ops!(true).op!(IR.`~e~`); + opCacheBackTrue[pc] = &BackOps!(true).op!(IR.`~e~`); + opCacheFalse[pc] = &Ops!(false).op!(IR.`~e~`); + opCacheBackFalse[pc] = &BackOps!(false).op!(IR.`~e~`); + break L_dispatch; + `); + } + default: + assert(0, "Unrecognized instruction "~re.ir[pc].mnemonic); + } + } + } + + this()(Regex!Char program, Stream stream, void[] memory) + { + re = program; + s = stream; + initExternalMemory(memory); + genCounter = 0; + } + + this(ref ThompsonMatcher matcher, size_t lo, size_t hi, Stream stream) + { + s = stream; + re = matcher.re; + re.ir = re.ir[lo .. hi]; + threadSize = matcher.threadSize; + merge = matcher.merge; + freelist = matcher.freelist; + opCacheTrue = matcher.opCacheTrue[lo .. hi]; + opCacheBackTrue = matcher.opCacheBackTrue[lo .. hi]; + opCacheFalse = matcher.opCacheFalse[lo .. hi]; + opCacheBackFalse = matcher.opCacheBackFalse[lo .. hi]; + front = matcher.front; + index = matcher.index; + } + + this(ref BackMatcher matcher, size_t lo, size_t hi, Stream stream) + { + s = stream; + re = matcher.re; + re.ir = re.ir[lo .. hi]; + threadSize = matcher.threadSize; + merge = matcher.merge; + freelist = matcher.freelist; + opCacheTrue = matcher.opCacheBackTrue[lo .. hi]; + opCacheBackTrue = matcher.opCacheTrue[lo .. hi]; + opCacheFalse = matcher.opCacheBackFalse[lo .. hi]; + opCacheBackFalse = matcher.opCacheFalse[lo .. hi]; + front = matcher.front; + index = matcher.index; + } + + auto fwdMatcher()(size_t lo, size_t hi, size_t counter) + { + auto m = ThompsonMatcher!(Char, Stream)(this, lo, hi, s); + m.genCounter = counter; + return m; + } + + auto bwdMatcher()(size_t lo, size_t hi, size_t counter) + { + alias BackLooper = typeof(s.loopBack(index)); + auto m = ThompsonMatcher!(Char, BackLooper)(this, lo, hi, s.loopBack(index)); + m.genCounter = counter; + m.next(); + return m; + } + + auto dupTo(void[] memory) + { + typeof(this) tmp = this;//bitblit + tmp.initExternalMemory(memory); + tmp.genCounter = 0; + return tmp; + } + + int match(Group!DataIndex[] matches) + { + debug(std_regex_matcher) + writeln("------------------------------------------"); + if (exhausted) + { + return false; + } + if (re.flags & RegexInfo.oneShot) + { + next(); + exhausted = true; + return matchOneShot(matches); + } + static if (kicked) + if (!re.kickstart.empty) + return matchImpl!(true)(matches); + return matchImpl!(false)(matches); + } + + //match the input and fill matches + int matchImpl(bool withSearch)(Group!DataIndex[] matches) + { + if (!matched && clist.empty) + { + static if (withSearch) + search(); + else + next(); + } + else//char in question is fetched in prev call to match + { + matched = 0; + } + State state; + state.matches = matches; + + if (!atEnd)//if no char + for (;;) + { + genCounter++; + debug(std_regex_matcher) + { + writefln("Threaded matching threads at %s", s[index .. s.lastIndex]); + foreach (t; clist[]) + { + assert(t); + writef("pc=%s ",t.pc); + write(t.matches); + writeln(); + } + } + for (state.t = clist.fetch(); state.t; state.t = clist.fetch()) + { + eval!true(&state); + } + //if we already have match no need to push the engine + if (!matched) + { + state.t = createStart(index); + eval!true(&state);//new thread staring at this position + } + else if (nlist.empty) + { + debug(std_regex_matcher) writeln("Stopped matching before consuming full input"); + break;//not a partial match for sure + } + clist = nlist; + nlist = (ThreadList!DataIndex).init; + if (clist.tip is null) + { + static if (withSearch) + { + if (!search()) + break; + } + else + { + if (!next()) + break; + } + } + else if (!next()) + { + if (!atEnd) return false; + exhausted = true; + break; + } + } + + genCounter++; //increment also on each end + debug(std_regex_matcher) writefln("Threaded matching threads at end"); + //try out all zero-width posibilities + for (state.t = clist.fetch(); state.t; state.t = clist.fetch()) + { + eval!false(&state); + } + if (!matched) + { + state.t = createStart(index); + eval!false(&state);//new thread starting at end of input + } + if (matched) + {//in case NFA found match along the way + //and last possible longer alternative ultimately failed + s.reset(matches[0].end);//reset to last successful match + next();//and reload front character + //--- here the exact state of stream was restored --- + exhausted = atEnd || !(re.flags & RegexOption.global); + //+ empty match advances the input + if (!exhausted && matches[0].begin == matches[0].end) + next(); + } + return matched; + } + + /+ + handle succesful threads + +/ + void finish(const(Thread!DataIndex)* t, Group!DataIndex[] matches, int code) + { + matches.ptr[0 .. re.ngroup] = t.matches.ptr[0 .. re.ngroup]; + debug(std_regex_matcher) + { + writef("FOUND pc=%s prog_len=%s", + t.pc, re.ir.length); + if (!matches.empty) + writefln(": %s..%s", matches[0].begin, matches[0].end); + foreach (v; matches) + writefln("%d .. %d", v.begin, v.end); + } + matched = code; + } + + alias Ops(bool withInput) = ThompsonOps!(ThompsonMatcher, State, withInput); + alias BackOps(bool withInput) = ThompsonOps!(BackMatcher, BackMatcher.State, withInput); + + /+ + match thread against codepoint, cutting trough all 0-width instructions + and taking care of control flow, then add it to nlist + +/ + void eval(bool withInput)(State* state) + { + debug(std_regex_matcher) writeln("---- Evaluating thread"); + static if (withInput) + while (opCacheTrue.ptr[state.t.pc](&this, state)){} + else + while (opCacheFalse.ptr[state.t.pc](&this, state)){} + } + enum uint RestartPc = uint.max; + //match the input, evaluating IR without searching + int matchOneShot(Group!DataIndex[] matches, uint startPc = 0) + { + debug(std_regex_matcher) + { + writefln("---------------single shot match ----------------- "); + } + alias evalFn = eval; + assert(clist == (ThreadList!DataIndex).init || startPc == RestartPc); // incorrect after a partial match + assert(nlist == (ThreadList!DataIndex).init || startPc == RestartPc); + State state; + state.matches = matches; + if (!atEnd)//if no char + { + debug(std_regex_matcher) + { + writefln("-- Threaded matching threads at %s", s[index .. s.lastIndex]); + } + if (startPc != RestartPc) + { + state.t = createStart(index, startPc); + genCounter++; + evalFn!true(&state); + } + for (;;) + { + debug(std_regex_matcher) writeln("\n-- Started iteration of main cycle"); + genCounter++; + debug(std_regex_matcher) + { + foreach (t; clist[]) + { + assert(t); + } + } + for (state.t = clist.fetch(); state.t; state.t = clist.fetch()) + { + evalFn!true(&state); + } + if (nlist.empty) + { + debug(std_regex_matcher) writeln("Stopped matching before consuming full input"); + break;//not a partial match for sure + } + clist = nlist; + nlist = (ThreadList!DataIndex).init; + if (!next()) + break; + debug(std_regex_matcher) writeln("-- Ended iteration of main cycle\n"); + } + } + genCounter++; //increment also on each end + debug(std_regex_matcher) writefln("-- Matching threads at end"); + //try out all zero-width posibilities + for (state.t = clist.fetch(); state.t; state.t = clist.fetch()) + { + evalFn!false(&state); + } + if (!matched) + { + state.t = createStart(index, startPc); + evalFn!false(&state); + } + return matched; + } + + //get a dirty recycled Thread + Thread!DataIndex* allocate() + { + assert(freelist, "not enough preallocated memory"); + Thread!DataIndex* t = freelist; + freelist = freelist.next; + return t; + } + + //link memory into a free list of Threads + void prepareFreeList(size_t size, ref void[] memory) + { + void[] mem = memory[0 .. threadSize*size]; + memory = memory[threadSize * size .. $]; + freelist = cast(Thread!DataIndex*)&mem[0]; + size_t i; + for (i = threadSize; i < threadSize*size; i += threadSize) + (cast(Thread!DataIndex*)&mem[i-threadSize]).next = cast(Thread!DataIndex*)&mem[i]; + (cast(Thread!DataIndex*)&mem[i-threadSize]).next = null; + } + + //dispose a thread + void recycle(Thread!DataIndex* t) + { + t.next = freelist; + freelist = t; + } + + //dispose list of threads + void recycle(ref ThreadList!DataIndex list) + { + if (list.tip) + { + // just put this head-tail list in front of freelist + list.toe.next = freelist; + freelist = list.tip; + list = list.init; + } + } + + //creates a copy of master thread with given pc + Thread!DataIndex* fork(Thread!DataIndex* master, uint pc, uint counter) + { + auto t = allocate(); + t.matches.ptr[0 .. re.ngroup] = master.matches.ptr[0 .. re.ngroup]; + t.pc = pc; + t.counter = counter; + t.uopCounter = 0; + return t; + } + + //creates a start thread + Thread!DataIndex* createStart(DataIndex index, uint pc = 0) + { + auto t = allocate(); + t.matches.ptr[0 .. re.ngroup] = (Group!DataIndex).init; + t.matches[0].begin = index; + t.pc = pc; + t.counter = 0; + t.uopCounter = 0; + return t; + } +} diff --git a/libphobos/src/std/regex/package.d b/libphobos/src/std/regex/package.d new file mode 100644 index 0000000..bfc7d7f --- /dev/null +++ b/libphobos/src/std/regex/package.d @@ -0,0 +1,1735 @@ +/++ + $(LINK2 https://en.wikipedia.org/wiki/Regular_expression, Regular expressions) + are a commonly used method of pattern matching + on strings, with $(I regex) being a catchy word for a pattern in this domain + specific language. Typical problems usually solved by regular expressions + include validation of user input and the ubiquitous find $(AMP) replace + in text processing utilities. + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE, +$(TR $(TH Category) $(TH Functions)) +$(TR $(TD Matching) $(TD + $(LREF bmatch) + $(LREF match) + $(LREF matchAll) + $(LREF matchFirst) +)) +$(TR $(TD Building) $(TD + $(LREF ctRegex) + $(LREF escaper) + $(LREF _regex) +)) +$(TR $(TD Replace) $(TD + $(LREF replace) + $(LREF replaceAll) + $(LREF replaceAllInto) + $(LREF replaceFirst) + $(LREF replaceFirstInto) +)) +$(TR $(TD Split) $(TD + $(LREF split) + $(LREF splitter) +)) +$(TR $(TD Objects) $(TD + $(LREF Captures) + $(LREF Regex) + $(LREF RegexException) + $(LREF RegexMatch) + $(LREF Splitter) + $(LREF StaticRegex) +)) +) + + $(SECTION Synopsis) + --- + import std.regex; + import std.stdio; + void main() + { + // Print out all possible dd/mm/yy(yy) dates found in user input. + auto r = regex(r"\b[0-9][0-9]?/[0-9][0-9]?/[0-9][0-9](?:[0-9][0-9])?\b"); + foreach (line; stdin.byLine) + { + // matchAll() returns a range that can be iterated + // to get all subsequent matches. + foreach (c; matchAll(line, r)) + writeln(c.hit); + } + } + ... + + // Create a static regex at compile-time, which contains fast native code. + auto ctr = ctRegex!(`^.*/([^/]+)/?$`); + + // It works just like a normal regex: + auto c2 = matchFirst("foo/bar", ctr); // First match found here, if any + assert(!c2.empty); // Be sure to check if there is a match before examining contents! + assert(c2[1] == "bar"); // Captures is a range of submatches: 0 = full match. + + ... + // multi-pattern regex + auto multi = regex([`\d+,\d+`,`(a-z]+):(\d+)`]); + auto m = "abc:43 12,34".matchAll(multi); + assert(m.front.whichPattern == 2); + assert(m.front[1] == "abc"); + assert(m.front[2] == "43"); + m.popFront(); + assert(m.front.whichPattern == 1); + assert(m.front[1] == "12"); + ... + + // The result of the `matchAll/matchFirst` is directly testable with if/assert/while. + // e.g. test if a string consists of letters: + assert(matchFirst("Letter", `^\p{L}+$`)); + --- + + $(SECTION Syntax and general information) + The general usage guideline is to keep regex complexity on the side of simplicity, + as its capabilities reside in purely character-level manipulation. + As such it's ill-suited for tasks involving higher level invariants + like matching an integer number $(U bounded) in an [a,b] interval. + Checks of this sort of are better addressed by additional post-processing. + + The basic syntax shouldn't surprise experienced users of regular expressions. + For an introduction to $(D std.regex) see a + $(HTTP dlang.org/regular-expression.html, short tour) of the module API + and its abilities. + + There are other web resources on regular expressions to help newcomers, + and a good $(HTTP www.regular-expressions.info, reference with tutorial) + can easily be found. + + This library uses a remarkably common ECMAScript syntax flavor + with the following extensions: + $(UL + $(LI Named subexpressions, with Python syntax. ) + $(LI Unicode properties such as Scripts, Blocks and common binary properties e.g Alphabetic, White_Space, Hex_Digit etc.) + $(LI Arbitrary length and complexity lookbehind, including lookahead in lookbehind and vise-versa.) + ) + + $(REG_START Pattern syntax ) + $(I std.regex operates on codepoint level, + 'character' in this table denotes a single Unicode codepoint.) + $(REG_TABLE + $(REG_TITLE Pattern element, Semantics ) + $(REG_TITLE Atoms, Match single characters ) + $(REG_ROW any character except [{|*+?()^$, Matches the character itself. ) + $(REG_ROW ., In single line mode matches any character. + Otherwise it matches any character except '\n' and '\r'. ) + $(REG_ROW [class], Matches a single character + that belongs to this character class. ) + $(REG_ROW [^class], Matches a single character that + does $(U not) belong to this character class.) + $(REG_ROW \cC, Matches the control character corresponding to letter C) + $(REG_ROW \xXX, Matches a character with hexadecimal value of XX. ) + $(REG_ROW \uXXXX, Matches a character with hexadecimal value of XXXX. ) + $(REG_ROW \U00YYYYYY, Matches a character with hexadecimal value of YYYYYY. ) + $(REG_ROW \f, Matches a formfeed character. ) + $(REG_ROW \n, Matches a linefeed character. ) + $(REG_ROW \r, Matches a carriage return character. ) + $(REG_ROW \t, Matches a tab character. ) + $(REG_ROW \v, Matches a vertical tab character. ) + $(REG_ROW \d, Matches any Unicode digit. ) + $(REG_ROW \D, Matches any character except Unicode digits. ) + $(REG_ROW \w, Matches any word character (note: this includes numbers).) + $(REG_ROW \W, Matches any non-word character.) + $(REG_ROW \s, Matches whitespace, same as \p{White_Space}.) + $(REG_ROW \S, Matches any character except those recognized as $(I \s ). ) + $(REG_ROW \\, Matches \ character. ) + $(REG_ROW \c where c is one of [|*+?(), Matches the character c itself. ) + $(REG_ROW \p{PropertyName}, Matches a character that belongs + to the Unicode PropertyName set. + Single letter abbreviations can be used without surrounding {,}. ) + $(REG_ROW \P{PropertyName}, Matches a character that does not belong + to the Unicode PropertyName set. + Single letter abbreviations can be used without surrounding {,}. ) + $(REG_ROW \p{InBasicLatin}, Matches any character that is part of + the BasicLatin Unicode $(U block).) + $(REG_ROW \P{InBasicLatin}, Matches any character except ones in + the BasicLatin Unicode $(U block).) + $(REG_ROW \p{Cyrillic}, Matches any character that is part of + Cyrillic $(U script).) + $(REG_ROW \P{Cyrillic}, Matches any character except ones in + Cyrillic $(U script).) + $(REG_TITLE Quantifiers, Specify repetition of other elements) + $(REG_ROW *, Matches previous character/subexpression 0 or more times. + Greedy version - tries as many times as possible.) + $(REG_ROW *?, Matches previous character/subexpression 0 or more times. + Lazy version - stops as early as possible.) + $(REG_ROW +, Matches previous character/subexpression 1 or more times. + Greedy version - tries as many times as possible.) + $(REG_ROW +?, Matches previous character/subexpression 1 or more times. + Lazy version - stops as early as possible.) + $(REG_ROW {n}, Matches previous character/subexpression exactly n times. ) + $(REG_ROW {n$(COMMA)}, Matches previous character/subexpression n times or more. + Greedy version - tries as many times as possible. ) + $(REG_ROW {n$(COMMA)}?, Matches previous character/subexpression n times or more. + Lazy version - stops as early as possible.) + $(REG_ROW {n$(COMMA)m}, Matches previous character/subexpression n to m times. + Greedy version - tries as many times as possible, but no more than m times. ) + $(REG_ROW {n$(COMMA)m}?, Matches previous character/subexpression n to m times. + Lazy version - stops as early as possible, but no less then n times.) + $(REG_TITLE Other, Subexpressions $(AMP) alternations ) + $(REG_ROW (regex), Matches subexpression regex, + saving matched portion of text for later retrieval. ) + $(REG_ROW (?#comment), An inline comment that is ignored while matching.) + $(REG_ROW (?:regex), Matches subexpression regex, + $(U not) saving matched portion of text. Useful to speed up matching. ) + $(REG_ROW A|B, Matches subexpression A, or failing that, matches B. ) + $(REG_ROW (?P$(LT)name$(GT)regex), Matches named subexpression + regex labeling it with name 'name'. + When referring to a matched portion of text, + names work like aliases in addition to direct numbers. + ) + $(REG_TITLE Assertions, Match position rather than character ) + $(REG_ROW ^, Matches at the begining of input or line (in multiline mode).) + $(REG_ROW $, Matches at the end of input or line (in multiline mode). ) + $(REG_ROW \b, Matches at word boundary. ) + $(REG_ROW \B, Matches when $(U not) at word boundary. ) + $(REG_ROW (?=regex), Zero-width lookahead assertion. + Matches at a point where the subexpression + regex could be matched starting from the current position. + ) + $(REG_ROW (?!regex), Zero-width negative lookahead assertion. + Matches at a point where the subexpression + regex could $(U not) be matched starting from the current position. + ) + $(REG_ROW (?<=regex), Zero-width lookbehind assertion. Matches at a point + where the subexpression regex could be matched ending + at the current position (matching goes backwards). + ) + $(REG_ROW (?<!regex), Zero-width negative lookbehind assertion. + Matches at a point where the subexpression regex could $(U not) + be matched ending at the current position (matching goes backwards). + ) + ) + + $(REG_START Character classes ) + $(REG_TABLE + $(REG_TITLE Pattern element, Semantics ) + $(REG_ROW Any atom, Has the same meaning as outside of a character class.) + $(REG_ROW a-z, Includes characters a, b, c, ..., z. ) + $(REG_ROW [a||b]$(COMMA) [a--b]$(COMMA) [a~~b]$(COMMA) [a$(AMP)$(AMP)b], + Where a, b are arbitrary classes, means union, set difference, + symmetric set difference, and intersection respectively. + $(I Any sequence of character class elements implicitly forms a union.) ) + ) + + $(REG_START Regex flags ) + $(REG_TABLE + $(REG_TITLE Flag, Semantics ) + $(REG_ROW g, Global regex, repeat over the whole input. ) + $(REG_ROW i, Case insensitive matching. ) + $(REG_ROW m, Multi-line mode, match ^, $ on start and end line separators + as well as start and end of input.) + $(REG_ROW s, Single-line mode, makes . match '\n' and '\r' as well. ) + $(REG_ROW x, Free-form syntax, ignores whitespace in pattern, + useful for formatting complex regular expressions. ) + ) + + $(SECTION Unicode support) + + This library provides full Level 1 support* according to + $(HTTP unicode.org/reports/tr18/, UTS 18). Specifically: + $(UL + $(LI 1.1 Hex notation via any of \uxxxx, \U00YYYYYY, \xZZ.) + $(LI 1.2 Unicode properties.) + $(LI 1.3 Character classes with set operations.) + $(LI 1.4 Word boundaries use the full set of "word" characters.) + $(LI 1.5 Using simple casefolding to match case + insensitively across the full range of codepoints.) + $(LI 1.6 Respecting line breaks as any of + \u000A | \u000B | \u000C | \u000D | \u0085 | \u2028 | \u2029 | \u000D\u000A.) + $(LI 1.7 Operating on codepoint level.) + ) + *With exception of point 1.1.1, as of yet, normalization of input + is expected to be enforced by user. + + $(SECTION Replace format string) + + A set of functions in this module that do the substitution rely + on a simple format to guide the process. In particular the table below + applies to the $(D format) argument of + $(LREF replaceFirst) and $(LREF replaceAll). + + The format string can reference parts of match using the following notation. + $(REG_TABLE + $(REG_TITLE Format specifier, Replaced by ) + $(REG_ROW $$(AMP), the whole match. ) + $(REG_ROW $(DOLLAR)$(BACKTICK), part of input $(I preceding) the match. ) + $(REG_ROW $', part of input $(I following) the match. ) + $(REG_ROW $$, '$' character. ) + $(REG_ROW \c $(COMMA) where c is any character, the character c itself. ) + $(REG_ROW \\, '\' character. ) + $(REG_ROW $(DOLLAR)1 .. $(DOLLAR)99, submatch number 1 to 99 respectively. ) + ) + + $(SECTION Slicing and zero memory allocations orientation) + + All matches returned by pattern matching functionality in this library + are slices of the original input. The notable exception is the $(D replace) + family of functions that generate a new string from the input. + + In cases where producing the replacement is the ultimate goal + $(LREF replaceFirstInto) and $(LREF replaceAllInto) could come in handy + as functions that avoid allocations even for replacement. + + Copyright: Copyright Dmitry Olshansky, 2011- + + License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + + Authors: Dmitry Olshansky, + + API and utility constructs are modeled after the original $(D std.regex) + by Walter Bright and Andrei Alexandrescu. + + Source: $(PHOBOSSRC std/_regex/_package.d) + +Macros: + REG_ROW = $(TR $(TD $(I $1 )) $(TD $+) ) + REG_TITLE = $(TR $(TD $(B $1)) $(TD $(B $2)) ) + REG_TABLE = <table border="1" cellspacing="0" cellpadding="5" > $0 </table> + REG_START = <h3><div align="center"> $0 </div></h3> + SECTION = <h3><a id="$1" href="#$1" class="anchor">$0</a></h3> + S_LINK = <a href="#$1">$+</a> + +/ +module std.regex; + +import std.range.primitives, std.traits; +import std.regex.internal.ir; +import std.regex.internal.thompson; //TODO: get rid of this dependency +import std.typecons; // : Flag, Yes, No; + +/++ + $(D Regex) object holds regular expression pattern in compiled form. + + Instances of this object are constructed via calls to $(D regex). + This is an intended form for caching and storage of frequently + used regular expressions. + + Example: + + Test if this object doesn't contain any compiled pattern. + --- + Regex!char r; + assert(r.empty); + r = regex(""); // Note: "" is a valid regex pattern. + assert(!r.empty); + --- + + Getting a range of all the named captures in the regex. + ---- + import std.range; + import std.algorithm; + + auto re = regex(`(?P<name>\w+) = (?P<var>\d+)`); + auto nc = re.namedCaptures; + static assert(isRandomAccessRange!(typeof(nc))); + assert(!nc.empty); + assert(nc.length == 2); + assert(nc.equal(["name", "var"])); + assert(nc[0] == "name"); + assert(nc[1..$].equal(["var"])); + ---- ++/ +public alias Regex(Char) = std.regex.internal.ir.Regex!(Char); + +/++ + A $(D StaticRegex) is $(D Regex) object that contains D code specially + generated at compile-time to speed up matching. + + Implicitly convertible to normal $(D Regex), + however doing so will result in losing this additional capability. ++/ +public alias StaticRegex(Char) = std.regex.internal.ir.StaticRegex!(Char); + +/++ + Compile regular expression pattern for the later execution. + Returns: $(D Regex) object that works on inputs having + the same character width as $(D pattern). + + Params: + pattern = A single regular expression to match. + patterns = An array of regular expression strings. + The resulting `Regex` object will match any expression; + use $(LREF whichPattern) to know which. + flags = The _attributes (g, i, m and x accepted) + + Throws: $(D RegexException) if there were any errors during compilation. ++/ +@trusted public auto regex(S)(S[] patterns, const(char)[] flags="") +if (isSomeString!(S)) +{ + import std.array : appender; + import std.functional : memoize; + enum cacheSize = 8; //TODO: invent nice interface to control regex caching + S pat; + if (patterns.length > 1) + { + auto app = appender!S(); + foreach (i, p; patterns) + { + if (i != 0) + app.put("|"); + app.put("(?:"); + app.put(patterns[i]); + // terminator for the pattern + // to detect if the pattern unexpectedly ends + app.put("\\"); + app.put(cast(dchar)(privateUseStart+i)); + app.put(")"); + // another one to return correct whichPattern + // for all of potential alternatives in the patterns[i] + app.put("\\"); + app.put(cast(dchar)(privateUseStart+i)); + } + pat = app.data; + } + else + pat = patterns[0]; + + if (__ctfe) + return regexImpl(pat, flags); + return memoize!(regexImpl!S, cacheSize)(pat, flags); +} + +///ditto +@trusted public auto regex(S)(S pattern, const(char)[] flags="") +if (isSomeString!(S)) +{ + return regex([pattern], flags); +} + +/// +@system unittest +{ + // multi-pattern regex example + auto multi = regex([`([a-z]+):(\d+)`, `(\d+),\d+`]); // multi regex + auto m = "abc:43 12,34".matchAll(multi); + assert(m.front.whichPattern == 1); + assert(m.front[1] == "abc"); + assert(m.front[2] == "43"); + m.popFront(); + assert(m.front.whichPattern == 2); + assert(m.front[1] == "12"); +} + +public auto regexImpl(S)(S pattern, const(char)[] flags="") +if (isSomeString!(S)) +{ + import std.regex.internal.parser : Parser, CodeGen; + auto parser = Parser!(Unqual!(typeof(pattern)), CodeGen)(pattern, flags); + auto r = parser.program; + return r; +} + + +template ctRegexImpl(alias pattern, string flags=[]) +{ + import std.regex.internal.backtracking, std.regex.internal.parser; + enum r = regex(pattern, flags); + alias Char = BasicElementOf!(typeof(pattern)); + enum source = ctGenRegExCode(r); + alias Matcher = BacktrackingMatcher!(true); + @trusted bool func(ref Matcher!Char matcher) + { + debug(std_regex_ctr) pragma(msg, source); + mixin(source); + } + enum nr = StaticRegex!Char(r, &func); +} + +/++ + Compile regular expression using CTFE + and generate optimized native machine code for matching it. + + Returns: StaticRegex object for faster matching. + + Params: + pattern = Regular expression + flags = The _attributes (g, i, m and x accepted) ++/ +public enum ctRegex(alias pattern, alias flags=[]) = ctRegexImpl!(pattern, flags).nr; + +enum isRegexFor(RegEx, R) = is(RegEx == Regex!(BasicElementOf!R)) + || is(RegEx == StaticRegex!(BasicElementOf!R)); + + +/++ + $(D Captures) object contains submatches captured during a call + to $(D match) or iteration over $(D RegexMatch) range. + + First element of range is the whole match. ++/ +@trusted public struct Captures(R, DIndex = size_t) +if (isSomeString!R) +{//@trusted because of union inside + alias DataIndex = DIndex; + alias String = R; +private: + import std.conv : text; + R _input; + int _nMatch; + enum smallString = 3; + enum SMALL_MASK = 0x8000_0000, REF_MASK= 0x1FFF_FFFF; + union + { + Group!DataIndex[] big_matches; + Group!DataIndex[smallString] small_matches; + } + uint _f, _b; + uint _refcount; // ref count or SMALL MASK + num groups + NamedGroup[] _names; + + this()(R input, uint n, NamedGroup[] named) + { + _input = input; + _names = named; + newMatches(n); + _b = n; + _f = 0; + } + + this(alias Engine)(ref RegexMatch!(R,Engine) rmatch) + { + _input = rmatch._input; + _names = rmatch._engine.re.dict; + immutable n = rmatch._engine.re.ngroup; + newMatches(n); + _b = n; + _f = 0; + } + + @property inout(Group!DataIndex[]) matches() inout + { + return (_refcount & SMALL_MASK) ? small_matches[0 .. _refcount & 0xFF] : big_matches; + } + + void newMatches(uint n) + { + import core.stdc.stdlib : calloc; + import std.exception : enforce; + if (n > smallString) + { + auto p = cast(Group!DataIndex*) enforce( + calloc(Group!DataIndex.sizeof,n), + "Failed to allocate Captures struct" + ); + big_matches = p[0 .. n]; + _refcount = 1; + } + else + { + _refcount = SMALL_MASK | n; + } + } + + bool unique() + { + return (_refcount & SMALL_MASK) || _refcount == 1; + } + +public: + this(this) + { + if (!(_refcount & SMALL_MASK)) + { + _refcount++; + } + } + ~this() + { + import core.stdc.stdlib : free; + if (!(_refcount & SMALL_MASK)) + { + if (--_refcount == 0) + { + free(big_matches.ptr); + big_matches = null; + } + } + } + ///Slice of input prior to the match. + @property R pre() + { + return _nMatch == 0 ? _input[] : _input[0 .. matches[0].begin]; + } + + ///Slice of input immediately after the match. + @property R post() + { + return _nMatch == 0 ? _input[] : _input[matches[0].end .. $]; + } + + ///Slice of matched portion of input. + @property R hit() + { + assert(_nMatch, "attempted to get hit of an empty match"); + return _input[matches[0].begin .. matches[0].end]; + } + + ///Range interface. + @property R front() + { + assert(_nMatch, "attempted to get front of an empty match"); + return _input[matches[_f].begin .. matches[_f].end]; + } + + ///ditto + @property R back() + { + assert(_nMatch, "attempted to get back of an empty match"); + return _input[matches[_b - 1].begin .. matches[_b - 1].end]; + } + + ///ditto + void popFront() + { + assert(!empty); + ++_f; + } + + ///ditto + void popBack() + { + assert(!empty); + --_b; + } + + ///ditto + @property bool empty() const { return _nMatch == 0 || _f >= _b; } + + ///ditto + inout(R) opIndex()(size_t i) inout + { + assert(_f + i < _b,text("requested submatch number ", i," is out of range")); + assert(matches[_f + i].begin <= matches[_f + i].end, + text("wrong match: ", matches[_f + i].begin, "..", matches[_f + i].end)); + return _input[matches[_f + i].begin .. matches[_f + i].end]; + } + + /++ + Explicit cast to bool. + Useful as a shorthand for !(x.empty) in if and assert statements. + + --- + import std.regex; + + assert(!matchFirst("nothing", "something")); + --- + +/ + + @safe bool opCast(T:bool)() const nothrow { return _nMatch != 0; } + + /++ + Number of pattern matched counting, where 1 - the first pattern. + Returns 0 on no match. + +/ + + @safe @property int whichPattern() const nothrow { return _nMatch; } + + /// + @system unittest + { + import std.regex; + assert(matchFirst("abc", "[0-9]+", "[a-z]+").whichPattern == 2); + } + + /++ + Lookup named submatch. + + --- + import std.regex; + import std.range; + + auto c = matchFirst("a = 42;", regex(`(?P<var>\w+)\s*=\s*(?P<value>\d+);`)); + assert(c["var"] == "a"); + assert(c["value"] == "42"); + popFrontN(c, 2); + //named groups are unaffected by range primitives + assert(c["var"] =="a"); + assert(c.front == "42"); + ---- + +/ + R opIndex(String)(String i) /*const*/ //@@@BUG@@@ + if (isSomeString!String) + { + size_t index = lookupNamedGroup(_names, i); + return _input[matches[index].begin .. matches[index].end]; + } + + ///Number of matches in this object. + @property size_t length() const { return _nMatch == 0 ? 0 : _b - _f; } + + ///A hook for compatibility with original std.regex. + @property ref captures(){ return this; } +} + +/// +@system unittest +{ + import std.range.primitives : popFrontN; + + auto c = matchFirst("@abc#", regex(`(\w)(\w)(\w)`)); + assert(c.pre == "@"); // Part of input preceding match + assert(c.post == "#"); // Immediately after match + assert(c.hit == c[0] && c.hit == "abc"); // The whole match + assert(c[2] == "b"); + assert(c.front == "abc"); + c.popFront(); + assert(c.front == "a"); + assert(c.back == "c"); + c.popBack(); + assert(c.back == "b"); + popFrontN(c, 2); + assert(c.empty); + + assert(!matchFirst("nothing", "something")); +} + +/++ + A regex engine state, as returned by $(D match) family of functions. + + Effectively it's a forward range of Captures!R, produced + by lazily searching for matches in a given input. + + $(D alias Engine) specifies an engine type to use during matching, + and is automatically deduced in a call to $(D match)/$(D bmatch). ++/ +@trusted public struct RegexMatch(R, alias Engine = ThompsonMatcher) +if (isSomeString!R) +{ +private: + import core.stdc.stdlib : malloc, free; + alias Char = BasicElementOf!R; + alias EngineType = Engine!Char; + EngineType _engine; + R _input; + Captures!(R,EngineType.DataIndex) _captures; + void[] _memory;//is ref-counted + + this(RegEx)(R input, RegEx prog) + { + import std.exception : enforce; + _input = input; + immutable size = EngineType.initialMemory(prog)+size_t.sizeof; + _memory = (enforce(malloc(size), "malloc failed")[0 .. size]); + scope(failure) free(_memory.ptr); + *cast(size_t*)_memory.ptr = 1; + _engine = EngineType(prog, Input!Char(input), _memory[size_t.sizeof..$]); + static if (is(RegEx == StaticRegex!(BasicElementOf!R))) + _engine.nativeFn = prog.nativeFn; + _captures = Captures!(R,EngineType.DataIndex)(this); + _captures._nMatch = _engine.match(_captures.matches); + debug(std_regex_allocation) writefln("RefCount (ctor): %x %d", _memory.ptr, counter); + } + + @property ref size_t counter(){ return *cast(size_t*)_memory.ptr; } +public: + this(this) + { + if (_memory.ptr) + { + ++counter; + debug(std_regex_allocation) writefln("RefCount (postblit): %x %d", + _memory.ptr, *cast(size_t*)_memory.ptr); + } + } + + ~this() + { + if (_memory.ptr && --*cast(size_t*)_memory.ptr == 0) + { + debug(std_regex_allocation) writefln("RefCount (dtor): %x %d", + _memory.ptr, *cast(size_t*)_memory.ptr); + free(cast(void*)_memory.ptr); + } + } + + ///Shorthands for front.pre, front.post, front.hit. + @property R pre() + { + return _captures.pre; + } + + ///ditto + @property R post() + { + return _captures.post; + } + + ///ditto + @property R hit() + { + return _captures.hit; + } + + /++ + Functionality for processing subsequent matches of global regexes via range interface: + --- + import std.regex; + auto m = matchAll("Hello, world!", regex(`\w+`)); + assert(m.front.hit == "Hello"); + m.popFront(); + assert(m.front.hit == "world"); + m.popFront(); + assert(m.empty); + --- + +/ + @property auto front() + { + return _captures; + } + + ///ditto + void popFront() + { + import std.exception : enforce; + if (counter != 1) + {//do cow magic first + counter--;//we abandon this reference + immutable size = EngineType.initialMemory(_engine.re)+size_t.sizeof; + _memory = (enforce(malloc(size), "malloc failed")[0 .. size]); + _engine = _engine.dupTo(_memory[size_t.sizeof .. size]); + counter = 1;//points to new chunk + } + + if (!_captures.unique) + { + // has external references - allocate new space + _captures.newMatches(_engine.re.ngroup); + } + _captures._nMatch = _engine.match(_captures.matches); + } + + ///ditto + auto save(){ return this; } + + ///Test if this match object is empty. + @property bool empty() const { return _captures._nMatch == 0; } + + ///Same as !(x.empty), provided for its convenience in conditional statements. + T opCast(T:bool)(){ return !empty; } + + /// Same as .front, provided for compatibility with original std.regex. + @property auto captures() inout { return _captures; } + +} + +private @trusted auto matchOnce(alias Engine, RegEx, R)(R input, RegEx re) +{ + import core.stdc.stdlib : malloc, free; + import std.exception : enforce; + alias Char = BasicElementOf!R; + alias EngineType = Engine!Char; + + size_t size = EngineType.initialMemory(re); + void[] memory = enforce(malloc(size), "malloc failed")[0 .. size]; + scope(exit) free(memory.ptr); + auto captures = Captures!(R, EngineType.DataIndex)(input, re.ngroup, re.dict); + auto engine = EngineType(re, Input!Char(input), memory); + static if (is(RegEx == StaticRegex!(BasicElementOf!R))) + engine.nativeFn = re.nativeFn; + captures._nMatch = engine.match(captures.matches); + return captures; +} + +private auto matchMany(alias Engine, RegEx, R)(R input, RegEx re) +{ + re.flags |= RegexOption.global; + return RegexMatch!(R, Engine)(input, re); +} + +@system unittest +{ + //sanity checks for new API + auto re = regex("abc"); + assert(!"abc".matchOnce!(ThompsonMatcher)(re).empty); + assert("abc".matchOnce!(ThompsonMatcher)(re)[0] == "abc"); +} + + +private enum isReplaceFunctor(alias fun, R) = + __traits(compiles, (Captures!R c) { fun(c); }); + +// the lowest level - just stuff replacements into the sink +private @trusted void replaceCapturesInto(alias output, Sink, R, T) + (ref Sink sink, R input, T captures) +if (isOutputRange!(Sink, dchar) && isSomeString!R) +{ + if (captures.empty) + { + sink.put(input); + return; + } + sink.put(captures.pre); + // a hack to get around bogus errors, should be simply output(captures, sink) + // "is a nested function and cannot be accessed from" + static if (isReplaceFunctor!(output, R)) + sink.put(output(captures)); //"mutator" type of function + else + output(captures, sink); //"output" type of function + sink.put(captures.post); +} + +// ditto for a range of captures +private void replaceMatchesInto(alias output, Sink, R, T) + (ref Sink sink, R input, T matches) +if (isOutputRange!(Sink, dchar) && isSomeString!R) +{ + size_t offset = 0; + foreach (cap; matches) + { + sink.put(cap.pre[offset .. $]); + // same hack, see replaceCapturesInto + static if (isReplaceFunctor!(output, R)) + sink.put(output(cap)); //"mutator" type of function + else + output(cap, sink); //"output" type of function + offset = cap.pre.length + cap.hit.length; + } + sink.put(input[offset .. $]); +} + +// a general skeleton of replaceFirst +private R replaceFirstWith(alias output, R, RegEx)(R input, RegEx re) +if (isSomeString!R && isRegexFor!(RegEx, R)) +{ + import std.array : appender; + auto data = matchFirst(input, re); + if (data.empty) + return input; + auto app = appender!(R)(); + replaceCapturesInto!output(app, input, data); + return app.data; +} + +// ditto for replaceAll +// the method parameter allows old API to ride on the back of the new one +private R replaceAllWith(alias output, + alias method=matchAll, R, RegEx)(R input, RegEx re) +if (isSomeString!R && isRegexFor!(RegEx, R)) +{ + import std.array : appender; + auto matches = method(input, re); //inout(C)[] fails + if (matches.empty) + return input; + auto app = appender!(R)(); + replaceMatchesInto!output(app, input, matches); + return app.data; +} + + +/++ + Start matching $(D input) to regex pattern $(D re), + using Thompson NFA matching scheme. + + The use of this function is $(RED discouraged) - use either of + $(LREF matchAll) or $(LREF matchFirst). + + Delegating the kind of operation + to "g" flag is soon to be phased out along with the + ability to choose the exact matching scheme. The choice of + matching scheme to use depends highly on the pattern kind and + can done automatically on case by case basis. + + Returns: a $(D RegexMatch) object holding engine state after first match. ++/ + +public auto match(R, RegEx)(R input, RegEx re) +if (isSomeString!R && is(RegEx == Regex!(BasicElementOf!R))) +{ + import std.regex.internal.thompson : ThompsonMatcher; + return RegexMatch!(Unqual!(typeof(input)),ThompsonMatcher)(input, re); +} + +///ditto +public auto match(R, String)(R input, String re) +if (isSomeString!R && isSomeString!String) +{ + import std.regex.internal.thompson : ThompsonMatcher; + return RegexMatch!(Unqual!(typeof(input)),ThompsonMatcher)(input, regex(re)); +} + +public auto match(R, RegEx)(R input, RegEx re) +if (isSomeString!R && is(RegEx == StaticRegex!(BasicElementOf!R))) +{ + import std.regex.internal.backtracking : BacktrackingMatcher; + return RegexMatch!(Unqual!(typeof(input)),BacktrackingMatcher!true)(input, re); +} + +/++ + Find the first (leftmost) slice of the $(D input) that + matches the pattern $(D re). This function picks the most suitable + regular expression engine depending on the pattern properties. + + $(D re) parameter can be one of three types: + $(UL + $(LI Plain string(s), in which case it's compiled to bytecode before matching. ) + $(LI Regex!char (wchar/dchar) that contains a pattern in the form of + compiled bytecode. ) + $(LI StaticRegex!char (wchar/dchar) that contains a pattern in the form of + compiled native machine code. ) + ) + + Returns: + $(LREF Captures) containing the extent of a match together with all submatches + if there was a match, otherwise an empty $(LREF Captures) object. ++/ +public auto matchFirst(R, RegEx)(R input, RegEx re) +if (isSomeString!R && is(RegEx == Regex!(BasicElementOf!R))) +{ + import std.regex.internal.thompson : ThompsonMatcher; + return matchOnce!ThompsonMatcher(input, re); +} + +///ditto +public auto matchFirst(R, String)(R input, String re) +if (isSomeString!R && isSomeString!String) +{ + import std.regex.internal.thompson : ThompsonMatcher; + return matchOnce!ThompsonMatcher(input, regex(re)); +} + +///ditto +public auto matchFirst(R, String)(R input, String[] re...) +if (isSomeString!R && isSomeString!String) +{ + import std.regex.internal.thompson : ThompsonMatcher; + return matchOnce!ThompsonMatcher(input, regex(re)); +} + +public auto matchFirst(R, RegEx)(R input, RegEx re) +if (isSomeString!R && is(RegEx == StaticRegex!(BasicElementOf!R))) +{ + import std.regex.internal.backtracking : BacktrackingMatcher; + return matchOnce!(BacktrackingMatcher!true)(input, re); +} + +/++ + Initiate a search for all non-overlapping matches to the pattern $(D re) + in the given $(D input). The result is a lazy range of matches generated + as they are encountered in the input going left to right. + + This function picks the most suitable regular expression engine + depending on the pattern properties. + + $(D re) parameter can be one of three types: + $(UL + $(LI Plain string(s), in which case it's compiled to bytecode before matching. ) + $(LI Regex!char (wchar/dchar) that contains a pattern in the form of + compiled bytecode. ) + $(LI StaticRegex!char (wchar/dchar) that contains a pattern in the form of + compiled native machine code. ) + ) + + Returns: + $(LREF RegexMatch) object that represents matcher state + after the first match was found or an empty one if not present. ++/ +public auto matchAll(R, RegEx)(R input, RegEx re) +if (isSomeString!R && is(RegEx == Regex!(BasicElementOf!R))) +{ + import std.regex.internal.thompson : ThompsonMatcher; + return matchMany!ThompsonMatcher(input, re); +} + +///ditto +public auto matchAll(R, String)(R input, String re) +if (isSomeString!R && isSomeString!String) +{ + import std.regex.internal.thompson : ThompsonMatcher; + return matchMany!ThompsonMatcher(input, regex(re)); +} + +///ditto +public auto matchAll(R, String)(R input, String[] re...) +if (isSomeString!R && isSomeString!String) +{ + import std.regex.internal.thompson : ThompsonMatcher; + return matchMany!ThompsonMatcher(input, regex(re)); +} + +public auto matchAll(R, RegEx)(R input, RegEx re) +if (isSomeString!R && is(RegEx == StaticRegex!(BasicElementOf!R))) +{ + import std.regex.internal.backtracking : BacktrackingMatcher; + return matchMany!(BacktrackingMatcher!true)(input, re); +} + +// another set of tests just to cover the new API +@system unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map; + import std.conv : to; + + foreach (String; AliasSeq!(string, wstring, const(dchar)[])) + { + auto str1 = "blah-bleh".to!String(); + auto pat1 = "bl[ae]h".to!String(); + auto mf = matchFirst(str1, pat1); + assert(mf.equal(["blah".to!String()])); + auto mAll = matchAll(str1, pat1); + assert(mAll.equal!((a,b) => a.equal(b)) + ([["blah".to!String()], ["bleh".to!String()]])); + + auto str2 = "1/03/12 - 3/03/12".to!String(); + auto pat2 = regex([r"(\d+)/(\d+)/(\d+)".to!String(), "abc".to!String]); + auto mf2 = matchFirst(str2, pat2); + assert(mf2.equal(["1/03/12", "1", "03", "12"].map!(to!String)())); + auto mAll2 = matchAll(str2, pat2); + assert(mAll2.front.equal(mf2)); + mAll2.popFront(); + assert(mAll2.front.equal(["3/03/12", "3", "03", "12"].map!(to!String)())); + mf2.popFrontN(3); + assert(mf2.equal(["12".to!String()])); + + auto ctPat = ctRegex!(`(?P<Quot>\d+)/(?P<Denom>\d+)`.to!String()); + auto str = "2 + 34/56 - 6/1".to!String(); + auto cmf = matchFirst(str, ctPat); + assert(cmf.equal(["34/56", "34", "56"].map!(to!String)())); + assert(cmf["Quot"] == "34".to!String()); + assert(cmf["Denom"] == "56".to!String()); + + auto cmAll = matchAll(str, ctPat); + assert(cmAll.front.equal(cmf)); + cmAll.popFront(); + assert(cmAll.front.equal(["6/1", "6", "1"].map!(to!String)())); + } +} + +/++ + Start matching of $(D input) to regex pattern $(D re), + using traditional $(LINK2 https://en.wikipedia.org/wiki/Backtracking, + backtracking) matching scheme. + + The use of this function is $(RED discouraged) - use either of + $(LREF matchAll) or $(LREF matchFirst). + + Delegating the kind of operation + to "g" flag is soon to be phased out along with the + ability to choose the exact matching scheme. The choice of + matching scheme to use depends highly on the pattern kind and + can done automatically on case by case basis. + + Returns: a $(D RegexMatch) object holding engine + state after first match. + ++/ +public auto bmatch(R, RegEx)(R input, RegEx re) +if (isSomeString!R && is(RegEx == Regex!(BasicElementOf!R))) +{ + import std.regex.internal.backtracking : BacktrackingMatcher; + return RegexMatch!(Unqual!(typeof(input)), BacktrackingMatcher!false)(input, re); +} + +///ditto +public auto bmatch(R, String)(R input, String re) +if (isSomeString!R && isSomeString!String) +{ + import std.regex.internal.backtracking : BacktrackingMatcher; + return RegexMatch!(Unqual!(typeof(input)), BacktrackingMatcher!false)(input, regex(re)); +} + +public auto bmatch(R, RegEx)(R input, RegEx re) +if (isSomeString!R && is(RegEx == StaticRegex!(BasicElementOf!R))) +{ + import std.regex.internal.backtracking : BacktrackingMatcher; + return RegexMatch!(Unqual!(typeof(input)),BacktrackingMatcher!true)(input, re); +} + +// produces replacement string from format using captures for substitution +package void replaceFmt(R, Capt, OutR) + (R format, Capt captures, OutR sink, bool ignoreBadSubs = false) +if (isOutputRange!(OutR, ElementEncodingType!R[]) && + isOutputRange!(OutR, ElementEncodingType!(Capt.String)[])) +{ + import std.algorithm.searching : find; + import std.ascii : isDigit, isAlpha; + import std.conv : text, parse; + import std.exception : enforce; + enum State { Normal, Dollar } + auto state = State.Normal; + size_t offset; +L_Replace_Loop: + while (!format.empty) + final switch (state) + { + case State.Normal: + for (offset = 0; offset < format.length; offset++)//no decoding + { + if (format[offset] == '$') + { + state = State.Dollar; + sink.put(format[0 .. offset]); + format = format[offset+1 .. $];//ditto + continue L_Replace_Loop; + } + } + sink.put(format[0 .. offset]); + format = format[offset .. $]; + break; + case State.Dollar: + if (isDigit(format[0])) + { + uint digit = parse!uint(format); + enforce(ignoreBadSubs || digit < captures.length, text("invalid submatch number ", digit)); + if (digit < captures.length) + sink.put(captures[digit]); + } + else if (format[0] == '{') + { + auto x = find!(a => !isAlpha(a))(format[1..$]); + enforce(!x.empty && x[0] == '}', "no matching '}' in replacement format"); + auto name = format[1 .. $ - x.length]; + format = x[1..$]; + enforce(!name.empty, "invalid name in ${...} replacement format"); + sink.put(captures[name]); + } + else if (format[0] == '&') + { + sink.put(captures[0]); + format = format[1 .. $]; + } + else if (format[0] == '`') + { + sink.put(captures.pre); + format = format[1 .. $]; + } + else if (format[0] == '\'') + { + sink.put(captures.post); + format = format[1 .. $]; + } + else if (format[0] == '$') + { + sink.put(format[0 .. 1]); + format = format[1 .. $]; + } + state = State.Normal; + break; + } + enforce(state == State.Normal, "invalid format string in regex replace"); +} + +/++ + Construct a new string from $(D input) by replacing the first match with + a string generated from it according to the $(D format) specifier. + + To replace all matches use $(LREF replaceAll). + + Params: + input = string to search + re = compiled regular expression to use + format = _format string to generate replacements from, + see $(S_LINK Replace _format string, the _format string). + + Returns: + A string of the same type with the first match (if any) replaced. + If no match is found returns the input string itself. ++/ +public R replaceFirst(R, C, RegEx)(R input, RegEx re, const(C)[] format) +if (isSomeString!R && is(C : dchar) && isRegexFor!(RegEx, R)) +{ + return replaceFirstWith!((m, sink) => replaceFmt(format, m, sink))(input, re); +} + +/// +@system unittest +{ + assert(replaceFirst("noon", regex("n"), "[$&]") == "[n]oon"); +} + +/++ + This is a general replacement tool that construct a new string by replacing + matches of pattern $(D re) in the $(D input). Unlike the other overload + there is no format string instead captures are passed to + to a user-defined functor $(D fun) that returns a new string + to use as replacement. + + This version replaces the first match in $(D input), + see $(LREF replaceAll) to replace the all of the matches. + + Returns: + A new string of the same type as $(D input) with all matches + replaced by return values of $(D fun). If no matches found + returns the $(D input) itself. ++/ +public R replaceFirst(alias fun, R, RegEx)(R input, RegEx re) +if (isSomeString!R && isRegexFor!(RegEx, R)) +{ + return replaceFirstWith!((m, sink) => sink.put(fun(m)))(input, re); +} + +/// +@system unittest +{ + import std.conv : to; + string list = "#21 out of 46"; + string newList = replaceFirst!(cap => to!string(to!int(cap.hit)+1)) + (list, regex(`[0-9]+`)); + assert(newList == "#22 out of 46"); +} + +/++ + A variation on $(LREF replaceFirst) that instead of allocating a new string + on each call outputs the result piece-wise to the $(D sink). In particular + this enables efficient construction of a final output incrementally. + + Like in $(LREF replaceFirst) family of functions there is an overload + for the substitution guided by the $(D format) string + and the one with the user defined callback. ++/ +public @trusted void replaceFirstInto(Sink, R, C, RegEx) + (ref Sink sink, R input, RegEx re, const(C)[] format) +if (isOutputRange!(Sink, dchar) && isSomeString!R + && is(C : dchar) && isRegexFor!(RegEx, R)) + { + replaceCapturesInto!((m, sink) => replaceFmt(format, m, sink)) + (sink, input, matchFirst(input, re)); + } + +///ditto +public @trusted void replaceFirstInto(alias fun, Sink, R, RegEx) + (Sink sink, R input, RegEx re) +if (isOutputRange!(Sink, dchar) && isSomeString!R && isRegexFor!(RegEx, R)) +{ + replaceCapturesInto!fun(sink, input, matchFirst(input, re)); +} + +/// +@system unittest +{ + import std.array; + string m1 = "first message\n"; + string m2 = "second message\n"; + auto result = appender!string(); + replaceFirstInto(result, m1, regex(`([a-z]+) message`), "$1"); + //equivalent of the above with user-defined callback + replaceFirstInto!(cap=>cap[1])(result, m2, regex(`([a-z]+) message`)); + assert(result.data == "first\nsecond\n"); +} + +//examples for replaceFirst +@system unittest +{ + import std.conv; + string list = "#21 out of 46"; + string newList = replaceFirst!(cap => to!string(to!int(cap.hit)+1)) + (list, regex(`[0-9]+`)); + assert(newList == "#22 out of 46"); + import std.array; + string m1 = "first message\n"; + string m2 = "second message\n"; + auto result = appender!string(); + replaceFirstInto(result, m1, regex(`([a-z]+) message`), "$1"); + //equivalent of the above with user-defined callback + replaceFirstInto!(cap=>cap[1])(result, m2, regex(`([a-z]+) message`)); + assert(result.data == "first\nsecond\n"); +} + +/++ + Construct a new string from $(D input) by replacing all of the + fragments that match a pattern $(D re) with a string generated + from the match according to the $(D format) specifier. + + To replace only the first match use $(LREF replaceFirst). + + Params: + input = string to search + re = compiled regular expression to use + format = _format string to generate replacements from, + see $(S_LINK Replace _format string, the _format string). + + Returns: + A string of the same type as $(D input) with the all + of the matches (if any) replaced. + If no match is found returns the input string itself. ++/ +public @trusted R replaceAll(R, C, RegEx)(R input, RegEx re, const(C)[] format) +if (isSomeString!R && is(C : dchar) && isRegexFor!(RegEx, R)) +{ + return replaceAllWith!((m, sink) => replaceFmt(format, m, sink))(input, re); +} + +/// +@system unittest +{ + // insert comma as thousands delimiter + auto re = regex(r"(?<=\d)(?=(\d\d\d)+\b)","g"); + assert(replaceAll("12000 + 42100 = 54100", re, ",") == "12,000 + 42,100 = 54,100"); +} + +/++ + This is a general replacement tool that construct a new string by replacing + matches of pattern $(D re) in the $(D input). Unlike the other overload + there is no format string instead captures are passed to + to a user-defined functor $(D fun) that returns a new string + to use as replacement. + + This version replaces all of the matches found in $(D input), + see $(LREF replaceFirst) to replace the first match only. + + Returns: + A new string of the same type as $(D input) with all matches + replaced by return values of $(D fun). If no matches found + returns the $(D input) itself. + + Params: + input = string to search + re = compiled regular expression + fun = delegate to use ++/ +public @trusted R replaceAll(alias fun, R, RegEx)(R input, RegEx re) +if (isSomeString!R && isRegexFor!(RegEx, R)) +{ + return replaceAllWith!((m, sink) => sink.put(fun(m)))(input, re); +} + +/// +@system unittest +{ + string baz(Captures!(string) m) + { + import std.string : toUpper; + return toUpper(m.hit); + } + // Capitalize the letters 'a' and 'r': + auto s = replaceAll!(baz)("Strap a rocket engine on a chicken.", + regex("[ar]")); + assert(s == "StRAp A Rocket engine on A chicken."); +} + +/++ + A variation on $(LREF replaceAll) that instead of allocating a new string + on each call outputs the result piece-wise to the $(D sink). In particular + this enables efficient construction of a final output incrementally. + + As with $(LREF replaceAll) there are 2 overloads - one with a format string, + the other one with a user defined functor. ++/ +public @trusted void replaceAllInto(Sink, R, C, RegEx) + (Sink sink, R input, RegEx re, const(C)[] format) +if (isOutputRange!(Sink, dchar) && isSomeString!R + && is(C : dchar) && isRegexFor!(RegEx, R)) + { + replaceMatchesInto!((m, sink) => replaceFmt(format, m, sink)) + (sink, input, matchAll(input, re)); + } + +///ditto +public @trusted void replaceAllInto(alias fun, Sink, R, RegEx) + (Sink sink, R input, RegEx re) +if (isOutputRange!(Sink, dchar) && isSomeString!R && isRegexFor!(RegEx, R)) +{ + replaceMatchesInto!fun(sink, input, matchAll(input, re)); +} + +/// +@system unittest +{ + // insert comma as thousands delimiter in fifty randomly produced big numbers + import std.array, std.conv, std.random, std.range; + static re = regex(`(?<=\d)(?=(\d\d\d)+\b)`, "g"); + auto sink = appender!(char [])(); + enum ulong min = 10UL ^^ 10, max = 10UL ^^ 19; + foreach (i; 0 .. 50) + { + sink.clear(); + replaceAllInto(sink, text(uniform(min, max)), re, ","); + foreach (pos; iota(sink.data.length - 4, 0, -4)) + assert(sink.data[pos] == ','); + } +} + +// exercise all of the replace APIs +@system unittest +{ + import std.array : appender; + import std.conv; + // try and check first/all simple substitution + foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[])) + { + S s1 = "curt trial".to!S(); + S s2 = "round dome".to!S(); + S t1F = "court trial".to!S(); + S t2F = "hound dome".to!S(); + S t1A = "court trial".to!S(); + S t2A = "hound home".to!S(); + auto re1 = regex("curt".to!S()); + auto re2 = regex("[dr]o".to!S()); + + assert(replaceFirst(s1, re1, "court") == t1F); + assert(replaceFirst(s2, re2, "ho") == t2F); + assert(replaceAll(s1, re1, "court") == t1A); + assert(replaceAll(s2, re2, "ho") == t2A); + + auto rep1 = replaceFirst!(cap => cap[0][0]~"o".to!S()~cap[0][1..$])(s1, re1); + assert(rep1 == t1F); + assert(replaceFirst!(cap => "ho".to!S())(s2, re2) == t2F); + auto rep1A = replaceAll!(cap => cap[0][0]~"o".to!S()~cap[0][1..$])(s1, re1); + assert(rep1A == t1A); + assert(replaceAll!(cap => "ho".to!S())(s2, re2) == t2A); + + auto sink = appender!S(); + replaceFirstInto(sink, s1, re1, "court"); + assert(sink.data == t1F); + replaceFirstInto(sink, s2, re2, "ho"); + assert(sink.data == t1F~t2F); + replaceAllInto(sink, s1, re1, "court"); + assert(sink.data == t1F~t2F~t1A); + replaceAllInto(sink, s2, re2, "ho"); + assert(sink.data == t1F~t2F~t1A~t2A); + } +} + +/++ + Old API for replacement, operation depends on flags of pattern $(D re). + With "g" flag it performs the equivalent of $(LREF replaceAll) otherwise it + works the same as $(LREF replaceFirst). + + The use of this function is $(RED discouraged), please use $(LREF replaceAll) + or $(LREF replaceFirst) explicitly. ++/ +public R replace(alias scheme = match, R, C, RegEx)(R input, RegEx re, const(C)[] format) +if (isSomeString!R && isRegexFor!(RegEx, R)) +{ + return replaceAllWith!((m, sink) => replaceFmt(format, m, sink), match)(input, re); +} + +///ditto +public R replace(alias fun, R, RegEx)(R input, RegEx re) +if (isSomeString!R && isRegexFor!(RegEx, R)) +{ + return replaceAllWith!(fun, match)(input, re); +} + +/** +Splits a string `r` using a regular expression `pat` as a separator. + +Params: + keepSeparators = flag to specify if the matches should be in the resulting range + r = the string to split + pat = the pattern to split on +Returns: + A lazy range of strings +*/ +public struct Splitter(Flag!"keepSeparators" keepSeparators = No.keepSeparators, Range, alias RegEx = Regex) +if (isSomeString!Range && isRegexFor!(RegEx, Range)) +{ +private: + Range _input; + size_t _offset; + alias Rx = typeof(match(Range.init,RegEx.init)); + Rx _match; + + static if (keepSeparators) bool onMatch = false; + + @trusted this(Range input, RegEx separator) + {//@@@BUG@@@ generated opAssign of RegexMatch is not @trusted + _input = input; + separator.flags |= RegexOption.global; + if (_input.empty) + { + //there is nothing to match at all, make _offset > 0 + _offset = 1; + } + else + { + _match = Rx(_input, separator); + + static if (keepSeparators) + if (_match.pre.empty) + popFront(); + } + } + +public: + auto ref opSlice() + { + return this.save; + } + + ///Forward range primitives. + @property Range front() + { + import std.algorithm.comparison : min; + + assert(!empty && _offset <= _match.pre.length + && _match.pre.length <= _input.length); + + static if (keepSeparators) + { + if (!onMatch) + return _input[_offset .. min($, _match.pre.length)]; + else + return _match.hit(); + } + else + { + return _input[_offset .. min($, _match.pre.length)]; + } + } + + ///ditto + @property bool empty() + { + static if (keepSeparators) + return _offset >= _input.length; + else + return _offset > _input.length; + } + + ///ditto + void popFront() + { + assert(!empty); + if (_match.empty) + { + //No more separators, work is done here + _offset = _input.length + 1; + } + else + { + static if (keepSeparators) + { + if (!onMatch) + { + //skip past the separator + _offset = _match.pre.length; + } + else + { + _offset += _match.hit.length; + _match.popFront(); + } + + onMatch = !onMatch; + } + else + { + //skip past the separator + _offset = _match.pre.length + _match.hit.length; + _match.popFront(); + } + } + } + + ///ditto + @property auto save() + { + return this; + } +} + +/// ditto +public Splitter!(keepSeparators, Range, RegEx) splitter( + Flag!"keepSeparators" keepSeparators = No.keepSeparators, Range, RegEx)(Range r, RegEx pat) +if ( + is(BasicElementOf!Range : dchar) && isRegexFor!(RegEx, Range)) +{ + return Splitter!(keepSeparators, Range, RegEx)(r, pat); +} + +/// +@system unittest +{ + import std.algorithm.comparison : equal; + auto s1 = ", abc, de, fg, hi, "; + assert(equal(splitter(s1, regex(", *")), + ["", "abc", "de", "fg", "hi", ""])); +} + +/// Split on a pattern, but keep the matches in the resulting range +@system unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : Yes; + + auto pattern = regex(`([\.,])`); + + assert("2003.04.05" + .splitter!(Yes.keepSeparators)(pattern) + .equal(["2003", ".", "04", ".", "05"])); + + assert(",1,2,3" + .splitter!(Yes.keepSeparators)(pattern) + .equal([",", "1", ",", "2", ",", "3"])); +} + +///An eager version of $(D splitter) that creates an array with splitted slices of $(D input). +public @trusted String[] split(String, RegEx)(String input, RegEx rx) +if (isSomeString!String && isRegexFor!(RegEx, String)) +{ + import std.array : appender; + auto a = appender!(String[])(); + foreach (e; splitter(input, rx)) + a.put(e); + return a.data; +} + +///Exception object thrown in case of errors during regex compilation. +public alias RegexException = std.regex.internal.ir.RegexException; + +/++ + A range that lazily produces a string output escaped + to be used inside of a regular expression. ++/ +auto escaper(Range)(Range r) +{ + import std.algorithm.searching : find; + static immutable escapables = [Escapables]; + static struct Escaper // template to deduce attributes + { + Range r; + bool escaped; + + @property ElementType!Range front(){ + if (escaped) + return '\\'; + else + return r.front; + } + + @property bool empty(){ return r.empty; } + + void popFront(){ + if (escaped) escaped = false; + else + { + r.popFront(); + if (!r.empty && !escapables.find(r.front).empty) + escaped = true; + } + } + + @property auto save(){ return Escaper(r.save, escaped); } + } + + bool escaped = !r.empty && !escapables.find(r.front).empty; + return Escaper(r, escaped); +} + +/// +@system unittest +{ + import std.algorithm.comparison; + import std.regex; + string s = `This is {unfriendly} to *regex*`; + assert(s.escaper.equal(`This is \{unfriendly\} to \*regex\*`)); +} + +@system unittest +{ + import std.algorithm.comparison; + import std.conv; + foreach (S; AliasSeq!(string, wstring, dstring)) + { + auto s = "^".to!S; + assert(s.escaper.equal(`\^`)); + auto s2 = ""; + assert(s2.escaper.equal("")); + } +} diff --git a/libphobos/src/std/signals.d b/libphobos/src/std/signals.d new file mode 100644 index 0000000..071adca --- /dev/null +++ b/libphobos/src/std/signals.d @@ -0,0 +1,708 @@ +// Written in the D programming language. + +/** + * Signals and Slots are an implementation of the Observer Pattern. + * Essentially, when a Signal is emitted, a list of connected Observers + * (called slots) are called. + * + * There have been several D implementations of Signals and Slots. + * This version makes use of several new features in D, which make + * using it simpler and less error prone. In particular, it is no + * longer necessary to instrument the slots. + * + * References: + * $(LUCKY A Deeper Look at Signals and Slots)$(BR) + * $(LINK2 http://en.wikipedia.org/wiki/Observer_pattern, Observer pattern)$(BR) + * $(LINK2 http://en.wikipedia.org/wiki/Signals_and_slots, Wikipedia)$(BR) + * $(LINK2 http://boost.org/doc/html/$(SIGNALS).html, Boost Signals)$(BR) + * $(LINK2 http://qt-project.org/doc/qt-5/signalsandslots.html, Qt)$(BR) + * + * There has been a great deal of discussion in the D newsgroups + * over this, and several implementations: + * + * $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/announce/signal_slots_library_4825.html, signal slots library)$(BR) + * $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/Signals_and_Slots_in_D_42387.html, Signals and Slots in D)$(BR) + * $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/Dynamic_binding_--_Qt_s_Signals_and_Slots_vs_Objective-C_42260.html, Dynamic binding -- Qt's Signals and Slots vs Objective-C)$(BR) + * $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/Dissecting_the_SS_42377.html, Dissecting the SS)$(BR) + * $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/dwt/about_harmonia_454.html, about harmonia)$(BR) + * $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/announce/1502.html, Another event handling module)$(BR) + * $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/41825.html, Suggestion: signal/slot mechanism)$(BR) + * $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/13251.html, Signals and slots?)$(BR) + * $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/10714.html, Signals and slots ready for evaluation)$(BR) + * $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/1393.html, Signals & Slots for Walter)$(BR) + * $(LINK2 http://www.digitalmars.com/d/archives/28456.html, Signal/Slot mechanism?)$(BR) + * $(LINK2 http://www.digitalmars.com/d/archives/19470.html, Modern Features?)$(BR) + * $(LINK2 http://www.digitalmars.com/d/archives/16592.html, Delegates vs interfaces)$(BR) + * $(LINK2 http://www.digitalmars.com/d/archives/16583.html, The importance of component programming (properties$(COMMA) signals and slots$(COMMA) etc))$(BR) + * $(LINK2 http://www.digitalmars.com/d/archives/16368.html, signals and slots)$(BR) + * + * Bugs: + * Slots can only be delegates formed from class objects or + * interfaces to class objects. If a delegate to something else + * is passed to connect(), such as a struct member function, + * a nested function or a COM interface, undefined behavior + * will result. + * + * Not safe for multiple threads operating on the same signals + * or slots. + * Macros: + * SIGNALS=signals + * + * Copyright: Copyright Digital Mars 2000 - 2009. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: $(HTTP digitalmars.com, Walter Bright) + * Source: $(PHOBOSSRC std/_signals.d) + * + * $(SCRIPT inhibitQuickIndex = 1;) + */ +/* Copyright Digital Mars 2000 - 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) + */ +module std.signals; + +import core.exception : onOutOfMemoryError; +import core.stdc.stdlib : calloc, realloc, free; +import std.stdio; + +// Special function for internal use only. +// Use of this is where the slot had better be a delegate +// to an object or an interface that is part of an object. +extern (C) Object _d_toObject(void* p); + +// Used in place of Object.notifyRegister and Object.notifyUnRegister. +alias DisposeEvt = void delegate(Object); +extern (C) void rt_attachDisposeEvent( Object obj, DisposeEvt evt ); +extern (C) void rt_detachDisposeEvent( Object obj, DisposeEvt evt ); +//debug=signal; + +/************************ + * Mixin to create a signal within a class object. + * + * Different signals can be added to a class by naming the mixins. + */ + +mixin template Signal(T1...) +{ + static import core.exception; + static import core.stdc.stdlib; + /*** + * A slot is implemented as a delegate. + * The slot_t is the type of the delegate. + * The delegate must be to an instance of a class or an interface + * to a class instance. + * Delegates to struct instances or nested functions must not be + * used as slots. + */ + alias slot_t = void delegate(T1); + + /*** + * Call each of the connected slots, passing the argument(s) i to them. + * Nested call will be ignored. + */ + final void emit( T1 i ) + { + if (status >= ST.inemitting || !slots.length) + return; // should not nest + + status = ST.inemitting; + scope (exit) + status = ST.idle; + + foreach (slot; slots[0 .. slots_idx]) + { if (slot) + slot(i); + } + + assert(status >= ST.inemitting); + if (status == ST.inemitting_disconnected) + { + for (size_t j = 0; j < slots_idx;) + { + if (slots[j] is null) + { + slots_idx--; + slots[j] = slots[slots_idx]; + } + else + j++; + } + } + } + + /*** + * Add a slot to the list of slots to be called when emit() is called. + */ + final void connect(slot_t slot) + { + /* Do this: + * slots ~= slot; + * but use malloc() and friends instead + */ + auto len = slots.length; + if (slots_idx == len) + { + if (slots.length == 0) + { + len = 4; + auto p = core.stdc.stdlib.calloc(slot_t.sizeof, len); + if (!p) + core.exception.onOutOfMemoryError(); + slots = (cast(slot_t*) p)[0 .. len]; + } + else + { + import core.checkedint : addu, mulu; + bool overflow; + len = addu(mulu(len, 2, overflow), 4, overflow); // len = len * 2 + 4 + const nbytes = mulu(len, slot_t.sizeof, overflow); + if (overflow) assert(0); + + auto p = core.stdc.stdlib.realloc(slots.ptr, nbytes); + if (!p) + core.exception.onOutOfMemoryError(); + slots = (cast(slot_t*) p)[0 .. len]; + slots[slots_idx + 1 .. $] = null; + } + } + slots[slots_idx++] = slot; + + L1: + Object o = _d_toObject(slot.ptr); + rt_attachDisposeEvent(o, &unhook); + } + + /*** + * Remove a slot from the list of slots to be called when emit() is called. + */ + final void disconnect(slot_t slot) + { + debug (signal) writefln("Signal.disconnect(slot)"); + size_t disconnectedSlots = 0; + size_t instancePreviousSlots = 0; + if (status >= ST.inemitting) + { + foreach (i, sloti; slots[0 .. slots_idx]) + { + if (sloti.ptr == slot.ptr && + ++instancePreviousSlots && + sloti == slot) + { + disconnectedSlots++; + slots[i] = null; + status = ST.inemitting_disconnected; + } + } + } + else + { + for (size_t i = 0; i < slots_idx; ) + { + if (slots[i].ptr == slot.ptr && + ++instancePreviousSlots && + slots[i] == slot) + { + slots_idx--; + disconnectedSlots++; + slots[i] = slots[slots_idx]; + slots[slots_idx] = null; // not strictly necessary + } + else + i++; + } + } + + // detach object from dispose event if all its slots have been removed + if (instancePreviousSlots == disconnectedSlots) + { + Object o = _d_toObject(slot.ptr); + rt_detachDisposeEvent(o, &unhook); + } + } + + /* ** + * Special function called when o is destroyed. + * It causes any slots dependent on o to be removed from the list + * of slots to be called by emit(). + */ + final void unhook(Object o) + in { assert( status == ST.idle ); } + body { + debug (signal) writefln("Signal.unhook(o = %s)", cast(void*) o); + for (size_t i = 0; i < slots_idx; ) + { + if (_d_toObject(slots[i].ptr) is o) + { slots_idx--; + slots[i] = slots[slots_idx]; + slots[slots_idx] = null; // not strictly necessary + } + else + i++; + } + } + + /* ** + * There can be multiple destructors inserted by mixins. + */ + ~this() + { + /* ** + * When this object is destroyed, need to let every slot + * know that this object is destroyed so they are not left + * with dangling references to it. + */ + if (slots.length) + { + foreach (slot; slots[0 .. slots_idx]) + { + if (slot) + { Object o = _d_toObject(slot.ptr); + rt_detachDisposeEvent(o, &unhook); + } + } + core.stdc.stdlib.free(slots.ptr); + slots = null; + } + } + + private: + slot_t[] slots; // the slots to call from emit() + size_t slots_idx; // used length of slots[] + + enum ST { idle, inemitting, inemitting_disconnected } + ST status; +} + +/// +@system unittest +{ + import std.signals; + + int observedMessageCounter = 0; + + class Observer + { // our slot + void watch(string msg, int value) + { + switch (observedMessageCounter++) + { + case 0: + assert(msg == "setting new value"); + assert(value == 4); + break; + case 1: + assert(msg == "setting new value"); + assert(value == 6); + break; + default: + assert(0, "Unknown observation"); + } + } + } + + class Observer2 + { // our slot + void watch(string msg, int value) + { + } + } + + class Foo + { + int value() { return _value; } + + int value(int v) + { + if (v != _value) + { _value = v; + // call all the connected slots with the two parameters + emit("setting new value", v); + } + return v; + } + + // Mix in all the code we need to make Foo into a signal + mixin Signal!(string, int); + + private : + int _value; + } + + Foo a = new Foo; + Observer o = new Observer; + auto o2 = new Observer2; + auto o3 = new Observer2; + auto o4 = new Observer2; + auto o5 = new Observer2; + + a.value = 3; // should not call o.watch() + a.connect(&o.watch); // o.watch is the slot + a.connect(&o2.watch); + a.connect(&o3.watch); + a.connect(&o4.watch); + a.connect(&o5.watch); + a.value = 4; // should call o.watch() + a.disconnect(&o.watch); // o.watch is no longer a slot + a.disconnect(&o3.watch); + a.disconnect(&o5.watch); + a.disconnect(&o4.watch); + a.disconnect(&o2.watch); + a.value = 5; // so should not call o.watch() + a.connect(&o2.watch); + a.connect(&o.watch); // connect again + a.value = 6; // should call o.watch() + destroy(o); // destroying o should automatically disconnect it + a.value = 7; // should not call o.watch() + + assert(observedMessageCounter == 2); +} + +// A function whose sole purpose is to get this module linked in +// so the unittest will run. +void linkin() { } + +@system unittest +{ + class Observer + { + void watch(string msg, int i) + { + //writefln("Observed msg '%s' and value %s", msg, i); + captured_value = i; + captured_msg = msg; + } + + int captured_value; + string captured_msg; + } + + class Foo + { + @property int value() { return _value; } + + @property int value(int v) + { + if (v != _value) + { _value = v; + emit("setting new value", v); + } + return v; + } + + mixin Signal!(string, int); + + private: + int _value; + } + + Foo a = new Foo; + Observer o = new Observer; + + // check initial condition + assert(o.captured_value == 0); + assert(o.captured_msg == ""); + + // set a value while no observation is in place + a.value = 3; + assert(o.captured_value == 0); + assert(o.captured_msg == ""); + + // connect the watcher and trigger it + a.connect(&o.watch); + a.value = 4; + assert(o.captured_value == 4); + assert(o.captured_msg == "setting new value"); + + // disconnect the watcher and make sure it doesn't trigger + a.disconnect(&o.watch); + a.value = 5; + assert(o.captured_value == 4); + assert(o.captured_msg == "setting new value"); + + // reconnect the watcher and make sure it triggers + a.connect(&o.watch); + a.value = 6; + assert(o.captured_value == 6); + assert(o.captured_msg == "setting new value"); + + // destroy the underlying object and make sure it doesn't cause + // a crash or other problems + destroy(o); + a.value = 7; +} + +@system unittest +{ + class Observer + { + int i; + long l; + string str; + + void watchInt(string str, int i) + { + this.str = str; + this.i = i; + } + + void watchLong(string str, long l) + { + this.str = str; + this.l = l; + } + } + + class Bar + { + @property void value1(int v) { s1.emit("str1", v); } + @property void value2(int v) { s2.emit("str2", v); } + @property void value3(long v) { s3.emit("str3", v); } + + mixin Signal!(string, int) s1; + mixin Signal!(string, int) s2; + mixin Signal!(string, long) s3; + } + + void test(T)(T a) { + auto o1 = new Observer; + auto o2 = new Observer; + auto o3 = new Observer; + + // connect the watcher and trigger it + a.s1.connect(&o1.watchInt); + a.s2.connect(&o2.watchInt); + a.s3.connect(&o3.watchLong); + + assert(!o1.i && !o1.l && o1.str == null); + assert(!o2.i && !o2.l && o2.str == null); + assert(!o3.i && !o3.l && o3.str == null); + + a.value1 = 11; + assert(o1.i == 11 && !o1.l && o1.str == "str1"); + assert(!o2.i && !o2.l && o2.str == null); + assert(!o3.i && !o3.l && o3.str == null); + o1.i = -11; o1.str = "x1"; + + a.value2 = 12; + assert(o1.i == -11 && !o1.l && o1.str == "x1"); + assert(o2.i == 12 && !o2.l && o2.str == "str2"); + assert(!o3.i && !o3.l && o3.str == null); + o2.i = -12; o2.str = "x2"; + + a.value3 = 13; + assert(o1.i == -11 && !o1.l && o1.str == "x1"); + assert(o2.i == -12 && !o1.l && o2.str == "x2"); + assert(!o3.i && o3.l == 13 && o3.str == "str3"); + o3.l = -13; o3.str = "x3"; + + // disconnect the watchers and make sure it doesn't trigger + a.s1.disconnect(&o1.watchInt); + a.s2.disconnect(&o2.watchInt); + a.s3.disconnect(&o3.watchLong); + + a.value1 = 21; + a.value2 = 22; + a.value3 = 23; + assert(o1.i == -11 && !o1.l && o1.str == "x1"); + assert(o2.i == -12 && !o1.l && o2.str == "x2"); + assert(!o3.i && o3.l == -13 && o3.str == "x3"); + + // reconnect the watcher and make sure it triggers + a.s1.connect(&o1.watchInt); + a.s2.connect(&o2.watchInt); + a.s3.connect(&o3.watchLong); + + a.value1 = 31; + a.value2 = 32; + a.value3 = 33; + assert(o1.i == 31 && !o1.l && o1.str == "str1"); + assert(o2.i == 32 && !o1.l && o2.str == "str2"); + assert(!o3.i && o3.l == 33 && o3.str == "str3"); + + // destroy observers + destroy(o1); + destroy(o2); + destroy(o3); + a.value1 = 41; + a.value2 = 42; + a.value3 = 43; + } + + test(new Bar); + + class BarDerived: Bar + { + @property void value4(int v) { s4.emit("str4", v); } + @property void value5(int v) { s5.emit("str5", v); } + @property void value6(long v) { s6.emit("str6", v); } + + mixin Signal!(string, int) s4; + mixin Signal!(string, int) s5; + mixin Signal!(string, long) s6; + } + + auto a = new BarDerived; + + test!Bar(a); + test!BarDerived(a); + + auto o4 = new Observer; + auto o5 = new Observer; + auto o6 = new Observer; + + // connect the watcher and trigger it + a.s4.connect(&o4.watchInt); + a.s5.connect(&o5.watchInt); + a.s6.connect(&o6.watchLong); + + assert(!o4.i && !o4.l && o4.str == null); + assert(!o5.i && !o5.l && o5.str == null); + assert(!o6.i && !o6.l && o6.str == null); + + a.value4 = 44; + assert(o4.i == 44 && !o4.l && o4.str == "str4"); + assert(!o5.i && !o5.l && o5.str == null); + assert(!o6.i && !o6.l && o6.str == null); + o4.i = -44; o4.str = "x4"; + + a.value5 = 45; + assert(o4.i == -44 && !o4.l && o4.str == "x4"); + assert(o5.i == 45 && !o5.l && o5.str == "str5"); + assert(!o6.i && !o6.l && o6.str == null); + o5.i = -45; o5.str = "x5"; + + a.value6 = 46; + assert(o4.i == -44 && !o4.l && o4.str == "x4"); + assert(o5.i == -45 && !o4.l && o5.str == "x5"); + assert(!o6.i && o6.l == 46 && o6.str == "str6"); + o6.l = -46; o6.str = "x6"; + + // disconnect the watchers and make sure it doesn't trigger + a.s4.disconnect(&o4.watchInt); + a.s5.disconnect(&o5.watchInt); + a.s6.disconnect(&o6.watchLong); + + a.value4 = 54; + a.value5 = 55; + a.value6 = 56; + assert(o4.i == -44 && !o4.l && o4.str == "x4"); + assert(o5.i == -45 && !o4.l && o5.str == "x5"); + assert(!o6.i && o6.l == -46 && o6.str == "x6"); + + // reconnect the watcher and make sure it triggers + a.s4.connect(&o4.watchInt); + a.s5.connect(&o5.watchInt); + a.s6.connect(&o6.watchLong); + + a.value4 = 64; + a.value5 = 65; + a.value6 = 66; + assert(o4.i == 64 && !o4.l && o4.str == "str4"); + assert(o5.i == 65 && !o4.l && o5.str == "str5"); + assert(!o6.i && o6.l == 66 && o6.str == "str6"); + + // destroy observers + destroy(o4); + destroy(o5); + destroy(o6); + a.value4 = 44; + a.value5 = 45; + a.value6 = 46; +} + +// Triggers bug from issue 15341 +@system unittest +{ + class Observer + { + void watch() { } + void watch2() { } + } + + class Bar + { + mixin Signal!(); + } + + auto a = new Bar; + auto o = new Observer; + + //Connect both observer methods for the same instance + a.connect(&o.watch); + a.connect(&o.watch2); // not connecting watch2() or disconnecting it manually fixes the issue + + //Disconnect a single method of the two + a.disconnect(&o.watch); // NOT disconnecting watch() fixes the issue + + destroy(o); // destroying o should automatically call unhook and disconnect the slot for watch2 + a.emit(); // should not raise segfault since &o.watch2 is no longer connected +} + +version (none) // Disabled because of dmd @@@BUG5028@@@ +@system unittest +{ + class A + { + mixin Signal!(string, int) s1; + } + + class B : A + { + mixin Signal!(string, int) s2; + } +} + +// Triggers bug from issue 16249 +@system unittest +{ + class myLINE + { + mixin Signal!( myLINE, int ); + + void value( int v ) + { + if ( v >= 0 ) emit( this, v ); + else emit( new myLINE, v ); + } + } + + class Dot + { + int value; + + myLINE line_; + void line( myLINE line_x ) + { + if ( line_ is line_x ) return; + + if ( line_ !is null ) + { + line_.disconnect( &watch ); + } + line_ = line_x; + line_.connect( &watch ); + } + + void watch( myLINE line_x, int value_x ) + { + line = line_x; + value = value_x; + } + } + + auto dot1 = new Dot; + auto dot2 = new Dot; + auto line = new myLINE; + dot1.line = line; + dot2.line = line; + + line.value = 11; + assert( dot1.value == 11 ); + assert( dot2.value == 11 ); + + line.value = -22; + assert( dot1.value == -22 ); + assert( dot2.value == -22 ); +} + diff --git a/libphobos/src/std/socket.d b/libphobos/src/std/socket.d new file mode 100644 index 0000000..c008d62 --- /dev/null +++ b/libphobos/src/std/socket.d @@ -0,0 +1,3670 @@ +// Written in the D programming language + +/* + Copyright (C) 2004-2011 Christopher E. Miller + + Boost Software License - Version 1.0 - August 17th, 2003 + + Permission is hereby granted, free of charge, to any person or organization + obtaining a copy of the software and accompanying documentation covered by + this license (the "Software") to use, reproduce, display, distribute, + execute, and transmit the Software, and to prepare derivative works of the + Software, and to permit third-parties to whom the Software is furnished to + do so, all subject to the following: + + The copyright notices in the Software and this entire statement, including + the above license grant, this restriction and the following disclaimer, + must be included in all copies of the Software, in whole or in part, and + all derivative works of the Software, unless such copies or derivative + works are solely in the form of machine-executable object code generated by + a source language processor. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + socket.d 1.4 + Jan 2011 + + Thanks to Benjamin Herr for his assistance. + */ + +/** + * Socket primitives. + * Example: See $(SAMPLESRC listener.d) and $(SAMPLESRC htmlget.d) + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Christopher E. Miller, $(HTTP klickverbot.at, David Nadlinger), + * $(HTTP thecybershadow.net, Vladimir Panteleev) + * Source: $(PHOBOSSRC std/_socket.d) + */ + +module std.socket; + +import core.stdc.stdint, core.stdc.stdlib, core.stdc.string, std.conv, std.string; + +import core.stdc.config; +import core.time : dur, Duration; +import std.exception; + +import std.internal.cstring; + + +@safe: + +version (Windows) +{ + version (GNU) {} + else + { + pragma (lib, "ws2_32.lib"); + pragma (lib, "wsock32.lib"); + } + + import core.sys.windows.windows, std.windows.syserror; + public import core.sys.windows.winsock2; + private alias _ctimeval = core.sys.windows.winsock2.timeval; + private alias _clinger = core.sys.windows.winsock2.linger; + + enum socket_t : SOCKET { INVALID_SOCKET } + private const int _SOCKET_ERROR = SOCKET_ERROR; + + + private int _lasterr() nothrow @nogc + { + return WSAGetLastError(); + } +} +else version (Posix) +{ + version (linux) + { + enum : int + { + TCP_KEEPIDLE = 4, + TCP_KEEPINTVL = 5 + } + } + + import core.sys.posix.arpa.inet; + import core.sys.posix.fcntl; + import core.sys.posix.netdb; + import core.sys.posix.netinet.in_; + import core.sys.posix.netinet.tcp; + import core.sys.posix.sys.select; + import core.sys.posix.sys.socket; + import core.sys.posix.sys.time; + import core.sys.posix.sys.un : sockaddr_un; + import core.sys.posix.unistd; + private alias _ctimeval = core.sys.posix.sys.time.timeval; + private alias _clinger = core.sys.posix.sys.socket.linger; + + import core.stdc.errno; + + enum socket_t : int32_t { init = -1 } + private const int _SOCKET_ERROR = -1; + + private enum : int + { + SD_RECEIVE = SHUT_RD, + SD_SEND = SHUT_WR, + SD_BOTH = SHUT_RDWR + } + + private int _lasterr() nothrow @nogc + { + return errno; + } +} +else +{ + static assert(0); // No socket support yet. +} + +version (unittest) +{ + static assert(is(uint32_t == uint)); + static assert(is(uint16_t == ushort)); + + import std.stdio : writefln; + + // Print a message on exception instead of failing the unittest. + private void softUnittest(void delegate() @safe test, int line = __LINE__) @trusted + { + try + test(); + catch (Throwable e) + { + writefln(" --- std.socket(%d) test fails depending on environment ---", line); + writefln(" (%s)", e); + } + } +} + +/// Base exception thrown by $(D std.socket). +class SocketException: Exception +{ + mixin basicExceptionCtors; +} + + +/* + * Needs to be public so that SocketOSException can be thrown outside of + * std.socket (since it uses it as a default argument), but it probably doesn't + * need to actually show up in the docs, since there's not really any public + * need for it outside of being a default argument. + */ +string formatSocketError(int err) @trusted +{ + version (Posix) + { + char[80] buf; + const(char)* cs; + version (CRuntime_Glibc) + { + cs = strerror_r(err, buf.ptr, buf.length); + } + else version (OSX) + { + auto errs = strerror_r(err, buf.ptr, buf.length); + if (errs == 0) + cs = buf.ptr; + else + return "Socket error " ~ to!string(err); + } + else version (FreeBSD) + { + auto errs = strerror_r(err, buf.ptr, buf.length); + if (errs == 0) + cs = buf.ptr; + else + return "Socket error " ~ to!string(err); + } + else version (NetBSD) + { + auto errs = strerror_r(err, buf.ptr, buf.length); + if (errs == 0) + cs = buf.ptr; + else + return "Socket error " ~ to!string(err); + } + else version (Solaris) + { + auto errs = strerror_r(err, buf.ptr, buf.length); + if (errs == 0) + cs = buf.ptr; + else + return "Socket error " ~ to!string(err); + } + else version (CRuntime_Bionic) + { + auto errs = strerror_r(err, buf.ptr, buf.length); + if (errs == 0) + cs = buf.ptr; + else + return "Socket error " ~ to!string(err); + } + else + static assert(0); + + auto len = strlen(cs); + + if (cs[len - 1] == '\n') + len--; + if (cs[len - 1] == '\r') + len--; + return cs[0 .. len].idup; + } + else + version (Windows) + { + return sysErrorString(err); + } + else + return "Socket error " ~ to!string(err); +} + +/// Retrieve the error message for the most recently encountered network error. +@property string lastSocketError() +{ + return formatSocketError(_lasterr()); +} + +/** + * Socket exceptions representing network errors reported by the operating + * system. + */ +class SocketOSException: SocketException +{ + int errorCode; /// Platform-specific error code. + + /// + this(string msg, + string file = __FILE__, + size_t line = __LINE__, + Throwable next = null, + int err = _lasterr(), + string function(int) @trusted errorFormatter = &formatSocketError) + { + errorCode = err; + + if (msg.length) + super(msg ~ ": " ~ errorFormatter(err), file, line, next); + else + super(errorFormatter(err), file, line, next); + } + + /// + this(string msg, + Throwable next, + string file = __FILE__, + size_t line = __LINE__, + int err = _lasterr(), + string function(int) @trusted errorFormatter = &formatSocketError) + { + this(msg, file, line, next, err, errorFormatter); + } + + /// + this(string msg, + int err, + string function(int) @trusted errorFormatter = &formatSocketError, + string file = __FILE__, + size_t line = __LINE__, + Throwable next = null) + { + this(msg, file, line, next, err, errorFormatter); + } +} + +/// Socket exceptions representing invalid parameters specified by user code. +class SocketParameterException: SocketException +{ + mixin basicExceptionCtors; +} + +/** + * Socket exceptions representing attempts to use network capabilities not + * available on the current system. + */ +class SocketFeatureException: SocketException +{ + mixin basicExceptionCtors; +} + + +/** + * Returns: + * $(D true) if the last socket operation failed because the socket + * was in non-blocking mode and the operation would have blocked. + */ +bool wouldHaveBlocked() nothrow @nogc +{ + version (Windows) + return _lasterr() == WSAEWOULDBLOCK; + else version (Posix) + return _lasterr() == EAGAIN; + else + static assert(0); +} + + +private immutable +{ + typeof(&getnameinfo) getnameinfoPointer; + typeof(&getaddrinfo) getaddrinfoPointer; + typeof(&freeaddrinfo) freeaddrinfoPointer; +} + +shared static this() @system +{ + version (Windows) + { + WSADATA wd; + + // Winsock will still load if an older version is present. + // The version is just a request. + int val; + val = WSAStartup(0x2020, &wd); + if (val) // Request Winsock 2.2 for IPv6. + throw new SocketOSException("Unable to initialize socket library", val); + + // These functions may not be present on older Windows versions. + // See the comment in InternetAddress.toHostNameString() for details. + auto ws2Lib = GetModuleHandleA("ws2_32.dll"); + if (ws2Lib) + { + getnameinfoPointer = cast(typeof(getnameinfoPointer)) + GetProcAddress(ws2Lib, "getnameinfo"); + getaddrinfoPointer = cast(typeof(getaddrinfoPointer)) + GetProcAddress(ws2Lib, "getaddrinfo"); + freeaddrinfoPointer = cast(typeof(freeaddrinfoPointer)) + GetProcAddress(ws2Lib, "freeaddrinfo"); + } + } + else version (Posix) + { + getnameinfoPointer = &getnameinfo; + getaddrinfoPointer = &getaddrinfo; + freeaddrinfoPointer = &freeaddrinfo; + } +} + + +shared static ~this() @system nothrow @nogc +{ + version (Windows) + { + WSACleanup(); + } +} + +/** + * The communication domain used to resolve an address. + */ +enum AddressFamily: int +{ + UNSPEC = AF_UNSPEC, /// Unspecified address family + UNIX = AF_UNIX, /// Local communication + INET = AF_INET, /// Internet Protocol version 4 + IPX = AF_IPX, /// Novell IPX + APPLETALK = AF_APPLETALK, /// AppleTalk + INET6 = AF_INET6, /// Internet Protocol version 6 +} + + +/** + * Communication semantics + */ +enum SocketType: int +{ + STREAM = SOCK_STREAM, /// Sequenced, reliable, two-way communication-based byte streams + DGRAM = SOCK_DGRAM, /// Connectionless, unreliable datagrams with a fixed maximum length; data may be lost or arrive out of order + RAW = SOCK_RAW, /// Raw protocol access + RDM = SOCK_RDM, /// Reliably-delivered message datagrams + SEQPACKET = SOCK_SEQPACKET, /// Sequenced, reliable, two-way connection-based datagrams with a fixed maximum length +} + + +/** + * Protocol + */ +enum ProtocolType: int +{ + IP = IPPROTO_IP, /// Internet Protocol version 4 + ICMP = IPPROTO_ICMP, /// Internet Control Message Protocol + IGMP = IPPROTO_IGMP, /// Internet Group Management Protocol + GGP = IPPROTO_GGP, /// Gateway to Gateway Protocol + TCP = IPPROTO_TCP, /// Transmission Control Protocol + PUP = IPPROTO_PUP, /// PARC Universal Packet Protocol + UDP = IPPROTO_UDP, /// User Datagram Protocol + IDP = IPPROTO_IDP, /// Xerox NS protocol + RAW = IPPROTO_RAW, /// Raw IP packets + IPV6 = IPPROTO_IPV6, /// Internet Protocol version 6 +} + + +/** + * $(D Protocol) is a class for retrieving protocol information. + * + * Example: + * --- + * auto proto = new Protocol; + * writeln("About protocol TCP:"); + * if (proto.getProtocolByType(ProtocolType.TCP)) + * { + * writefln(" Name: %s", proto.name); + * foreach (string s; proto.aliases) + * writefln(" Alias: %s", s); + * } + * else + * writeln(" No information found"); + * --- + */ +class Protocol +{ + /// These members are populated when one of the following functions are called successfully: + ProtocolType type; + string name; /// ditto + string[] aliases; /// ditto + + + void populate(protoent* proto) @system pure nothrow + { + type = cast(ProtocolType) proto.p_proto; + name = to!string(proto.p_name); + + int i; + for (i = 0;; i++) + { + if (!proto.p_aliases[i]) + break; + } + + if (i) + { + aliases = new string[i]; + for (i = 0; i != aliases.length; i++) + { + aliases[i] = + to!string(proto.p_aliases[i]); + } + } + else + { + aliases = null; + } + } + + /** Returns: false on failure */ + bool getProtocolByName(in char[] name) @trusted nothrow + { + protoent* proto; + proto = getprotobyname(name.tempCString()); + if (!proto) + return false; + populate(proto); + return true; + } + + + /** Returns: false on failure */ + // Same as getprotobynumber(). + bool getProtocolByType(ProtocolType type) @trusted nothrow + { + protoent* proto; + proto = getprotobynumber(type); + if (!proto) + return false; + populate(proto); + return true; + } +} + + +// Skip this test on Android because getprotobyname/number are +// unimplemented in bionic. +version (CRuntime_Bionic) {} else +@safe unittest +{ + softUnittest({ + Protocol proto = new Protocol; + assert(proto.getProtocolByType(ProtocolType.TCP)); + //writeln("About protocol TCP:"); + //writefln("\tName: %s", proto.name); + // foreach (string s; proto.aliases) + // { + // writefln("\tAlias: %s", s); + // } + assert(proto.name == "tcp"); + assert(proto.aliases.length == 1 && proto.aliases[0] == "TCP"); + }); +} + + +/** + * $(D Service) is a class for retrieving service information. + * + * Example: + * --- + * auto serv = new Service; + * writeln("About service epmap:"); + * if (serv.getServiceByName("epmap", "tcp")) + * { + * writefln(" Service: %s", serv.name); + * writefln(" Port: %d", serv.port); + * writefln(" Protocol: %s", serv.protocolName); + * foreach (string s; serv.aliases) + * writefln(" Alias: %s", s); + * } + * else + * writefln(" No service for epmap."); + * --- + */ +class Service +{ + /// These members are populated when one of the following functions are called successfully: + string name; + string[] aliases; /// ditto + ushort port; /// ditto + string protocolName; /// ditto + + + void populate(servent* serv) @system pure nothrow + { + name = to!string(serv.s_name); + port = ntohs(cast(ushort) serv.s_port); + protocolName = to!string(serv.s_proto); + + int i; + for (i = 0;; i++) + { + if (!serv.s_aliases[i]) + break; + } + + if (i) + { + aliases = new string[i]; + for (i = 0; i != aliases.length; i++) + { + aliases[i] = + to!string(serv.s_aliases[i]); + } + } + else + { + aliases = null; + } + } + + /** + * If a protocol name is omitted, any protocol will be matched. + * Returns: false on failure. + */ + bool getServiceByName(in char[] name, in char[] protocolName = null) @trusted nothrow + { + servent* serv; + serv = getservbyname(name.tempCString(), protocolName.tempCString()); + if (!serv) + return false; + populate(serv); + return true; + } + + + /// ditto + bool getServiceByPort(ushort port, in char[] protocolName = null) @trusted nothrow + { + servent* serv; + serv = getservbyport(port, protocolName.tempCString()); + if (!serv) + return false; + populate(serv); + return true; + } +} + + +@safe unittest +{ + softUnittest({ + Service serv = new Service; + if (serv.getServiceByName("epmap", "tcp")) + { + // writefln("About service epmap:"); + // writefln("\tService: %s", serv.name); + // writefln("\tPort: %d", serv.port); + // writefln("\tProtocol: %s", serv.protocolName); + // foreach (string s; serv.aliases) + // { + // writefln("\tAlias: %s", s); + // } + // For reasons unknown this is loc-srv on Wine and epmap on Windows + assert(serv.name == "loc-srv" || serv.name == "epmap", serv.name); + assert(serv.port == 135); + assert(serv.protocolName == "tcp"); + } + else + { + writefln("No service for epmap."); + } + }); +} + + +private mixin template socketOSExceptionCtors() +{ + /// + this(string msg, string file = __FILE__, size_t line = __LINE__, + Throwable next = null, int err = _lasterr()) + { + super(msg, file, line, next, err); + } + + /// + this(string msg, Throwable next, string file = __FILE__, + size_t line = __LINE__, int err = _lasterr()) + { + super(msg, next, file, line, err); + } + + /// + this(string msg, int err, string file = __FILE__, size_t line = __LINE__, + Throwable next = null) + { + super(msg, next, file, line, err); + } +} + + +/** + * Class for exceptions thrown from an `InternetHost`. + */ +class HostException: SocketOSException +{ + mixin socketOSExceptionCtors; +} + +/** + * `InternetHost` is a class for resolving IPv4 addresses. + * + * Consider using `getAddress`, `parseAddress` and `Address` methods + * instead of using this class directly. + */ +class InternetHost +{ + /// These members are populated when one of the following functions are called successfully: + string name; + string[] aliases; /// ditto + uint[] addrList; /// ditto + + + void validHostent(in hostent* he) + { + if (he.h_addrtype != cast(int) AddressFamily.INET || he.h_length != 4) + throw new HostException("Address family mismatch"); + } + + + void populate(hostent* he) @system pure nothrow + { + int i; + char* p; + + name = to!string(he.h_name); + + for (i = 0;; i++) + { + p = he.h_aliases[i]; + if (!p) + break; + } + + if (i) + { + aliases = new string[i]; + for (i = 0; i != aliases.length; i++) + { + aliases[i] = + to!string(he.h_aliases[i]); + } + } + else + { + aliases = null; + } + + for (i = 0;; i++) + { + p = he.h_addr_list[i]; + if (!p) + break; + } + + if (i) + { + addrList = new uint[i]; + for (i = 0; i != addrList.length; i++) + { + addrList[i] = ntohl(*(cast(uint*) he.h_addr_list[i])); + } + } + else + { + addrList = null; + } + } + + private bool getHostNoSync(string opMixin, T)(T param) @system + { + mixin(opMixin); + if (!he) + return false; + validHostent(he); + populate(he); + return true; + } + + version (Windows) + alias getHost = getHostNoSync; + else + { + // posix systems use global state for return value, so we + // must synchronize across all threads + private bool getHost(string opMixin, T)(T param) @system + { + synchronized(this.classinfo) + return getHostNoSync!(opMixin, T)(param); + } + } + + /** + * Resolve host name. + * Returns: false if unable to resolve. + */ + bool getHostByName(in char[] name) @trusted + { + static if (is(typeof(gethostbyname_r))) + { + return getHostNoSync!q{ + hostent he_v; + hostent* he; + ubyte[256] buffer_v = void; + auto buffer = buffer_v[]; + auto param_zTmp = param.tempCString(); + while (true) + { + he = &he_v; + int errno; + if (gethostbyname_r(param_zTmp, he, buffer.ptr, buffer.length, &he, &errno) == ERANGE) + buffer.length = buffer.length * 2; + else + break; + } + }(name); + } + else + { + return getHost!q{ + auto he = gethostbyname(param.tempCString()); + }(name); + } + } + + /** + * Resolve IPv4 address number. + * + * Params: + * addr = The IPv4 address to resolve, in host byte order. + * Returns: + * false if unable to resolve. + */ + bool getHostByAddr(uint addr) @trusted + { + return getHost!q{ + auto x = htonl(param); + auto he = gethostbyaddr(&x, 4, cast(int) AddressFamily.INET); + }(addr); + } + + /** + * Same as previous, but addr is an IPv4 address string in the + * dotted-decimal form $(I a.b.c.d). + * Returns: false if unable to resolve. + */ + bool getHostByAddr(in char[] addr) @trusted + { + return getHost!q{ + auto x = inet_addr(param.tempCString()); + enforce(x != INADDR_NONE, + new SocketParameterException("Invalid IPv4 address")); + auto he = gethostbyaddr(&x, 4, cast(int) AddressFamily.INET); + }(addr); + } +} + +/// +@safe unittest +{ + InternetHost ih = new InternetHost; + + ih.getHostByAddr(0x7F_00_00_01); + assert(ih.addrList[0] == 0x7F_00_00_01); + ih.getHostByAddr("127.0.0.1"); + assert(ih.addrList[0] == 0x7F_00_00_01); + + if (!ih.getHostByName("www.digitalmars.com")) + return; // don't fail if not connected to internet + + assert(ih.addrList.length); + InternetAddress ia = new InternetAddress(ih.addrList[0], InternetAddress.PORT_ANY); + assert(ih.name == "www.digitalmars.com" || ih.name == "digitalmars.com", + ih.name); + + assert(ih.getHostByAddr(ih.addrList[0])); + string getHostNameFromInt = ih.name.dup; + + assert(ih.getHostByAddr(ia.toAddrString())); + string getHostNameFromStr = ih.name.dup; + + assert(getHostNameFromInt == getHostNameFromStr); +} + + +/// Holds information about a socket _address retrieved by $(D getAddressInfo). +struct AddressInfo +{ + AddressFamily family; /// Address _family + SocketType type; /// Socket _type + ProtocolType protocol; /// Protocol + Address address; /// Socket _address + string canonicalName; /// Canonical name, when $(D AddressInfoFlags.CANONNAME) is used. +} + +/** + * A subset of flags supported on all platforms with getaddrinfo. + * Specifies option flags for $(D getAddressInfo). + */ +enum AddressInfoFlags: int +{ + /// The resulting addresses will be used in a call to $(D Socket.bind). + PASSIVE = AI_PASSIVE, + + /// The canonical name is returned in $(D canonicalName) member in the first $(D AddressInfo). + CANONNAME = AI_CANONNAME, + + /** + * The $(D node) parameter passed to $(D getAddressInfo) must be a numeric string. + * This will suppress any potentially lengthy network host address lookups. + */ + NUMERICHOST = AI_NUMERICHOST, +} + + +/** + * On POSIX, getaddrinfo uses its own error codes, and thus has its own + * formatting function. + */ +private string formatGaiError(int err) @trusted +{ + version (Windows) + { + return sysErrorString(err); + } + else + { + synchronized + return to!string(gai_strerror(err)); + } +} + +/** + * Provides _protocol-independent translation from host names to socket + * addresses. If advanced functionality is not required, consider using + * $(D getAddress) for compatibility with older systems. + * + * Returns: Array with one $(D AddressInfo) per socket address. + * + * Throws: $(D SocketOSException) on failure, or $(D SocketFeatureException) + * if this functionality is not available on the current system. + * + * Params: + * node = string containing host name or numeric address + * options = optional additional parameters, identified by type: + * $(UL $(LI $(D string) - service name or port number) + * $(LI $(D AddressInfoFlags) - option flags) + * $(LI $(D AddressFamily) - address family to filter by) + * $(LI $(D SocketType) - socket type to filter by) + * $(LI $(D ProtocolType) - protocol to filter by)) + * + * Example: + * --- + * // Roundtrip DNS resolution + * auto results = getAddressInfo("www.digitalmars.com"); + * assert(results[0].address.toHostNameString() == + * "digitalmars.com"); + * + * // Canonical name + * results = getAddressInfo("www.digitalmars.com", + * AddressInfoFlags.CANONNAME); + * assert(results[0].canonicalName == "digitalmars.com"); + * + * // IPv6 resolution + * results = getAddressInfo("ipv6.google.com"); + * assert(results[0].family == AddressFamily.INET6); + * + * // Multihomed resolution + * results = getAddressInfo("google.com"); + * assert(results.length > 1); + * + * // Parsing IPv4 + * results = getAddressInfo("127.0.0.1", + * AddressInfoFlags.NUMERICHOST); + * assert(results.length && results[0].family == + * AddressFamily.INET); + * + * // Parsing IPv6 + * results = getAddressInfo("::1", + * AddressInfoFlags.NUMERICHOST); + * assert(results.length && results[0].family == + * AddressFamily.INET6); + * --- + */ +AddressInfo[] getAddressInfo(T...)(in char[] node, T options) +{ + const(char)[] service = null; + addrinfo hints; + hints.ai_family = AF_UNSPEC; + + foreach (option; options) + { + static if (is(typeof(option) : const(char)[])) + service = option; + else + static if (is(typeof(option) == AddressInfoFlags)) + hints.ai_flags |= option; + else + static if (is(typeof(option) == AddressFamily)) + hints.ai_family = option; + else + static if (is(typeof(option) == SocketType)) + hints.ai_socktype = option; + else + static if (is(typeof(option) == ProtocolType)) + hints.ai_protocol = option; + else + static assert(0, "Unknown getAddressInfo option type: " ~ typeof(option).stringof); + } + + return () @trusted { return getAddressInfoImpl(node, service, &hints); }(); +} + +@system unittest +{ + struct Oops + { + const(char[]) breakSafety() + { + *cast(int*) 0xcafebabe = 0xdeadbeef; + return null; + } + alias breakSafety this; + } + assert(!__traits(compiles, () { + getAddressInfo("", Oops.init); + }), "getAddressInfo breaks @safe"); +} + +private AddressInfo[] getAddressInfoImpl(in char[] node, in char[] service, addrinfo* hints) @system +{ + import std.array : appender; + + if (getaddrinfoPointer && freeaddrinfoPointer) + { + addrinfo* ai_res; + + int ret = getaddrinfoPointer( + node.tempCString(), + service.tempCString(), + hints, &ai_res); + enforce(ret == 0, new SocketOSException("getaddrinfo error", ret, &formatGaiError)); + scope(exit) freeaddrinfoPointer(ai_res); + + auto result = appender!(AddressInfo[])(); + + // Use const to force UnknownAddressReference to copy the sockaddr. + for (const(addrinfo)* ai = ai_res; ai; ai = ai.ai_next) + result ~= AddressInfo( + cast(AddressFamily) ai.ai_family, + cast(SocketType ) ai.ai_socktype, + cast(ProtocolType ) ai.ai_protocol, + new UnknownAddressReference(ai.ai_addr, cast(socklen_t) ai.ai_addrlen), + ai.ai_canonname ? to!string(ai.ai_canonname) : null); + + assert(result.data.length > 0); + return result.data; + } + + throw new SocketFeatureException("Address info lookup is not available " ~ + "on this system."); +} + + +@safe unittest +{ + softUnittest({ + if (getaddrinfoPointer) + { + // Roundtrip DNS resolution + auto results = getAddressInfo("www.digitalmars.com"); + assert(results[0].address.toHostNameString() == "digitalmars.com"); + + // Canonical name + results = getAddressInfo("www.digitalmars.com", + AddressInfoFlags.CANONNAME); + assert(results[0].canonicalName == "digitalmars.com"); + + // IPv6 resolution + //results = getAddressInfo("ipv6.google.com"); + //assert(results[0].family == AddressFamily.INET6); + + // Multihomed resolution + //results = getAddressInfo("google.com"); + //assert(results.length > 1); + + // Parsing IPv4 + results = getAddressInfo("127.0.0.1", AddressInfoFlags.NUMERICHOST); + assert(results.length && results[0].family == AddressFamily.INET); + + // Parsing IPv6 + results = getAddressInfo("::1", AddressInfoFlags.NUMERICHOST); + assert(results.length && results[0].family == AddressFamily.INET6); + } + }); + + if (getaddrinfoPointer) + { + auto results = getAddressInfo(null, "1234", AddressInfoFlags.PASSIVE, + SocketType.STREAM, ProtocolType.TCP, AddressFamily.INET); + assert(results.length == 1 && results[0].address.toString() == "0.0.0.0:1234"); + } +} + + +private ushort serviceToPort(in char[] service) +{ + if (service == "") + return InternetAddress.PORT_ANY; + else + if (isNumeric(service)) + return to!ushort(service); + else + { + auto s = new Service(); + s.getServiceByName(service); + return s.port; + } +} + +/** + * Provides _protocol-independent translation from host names to socket + * addresses. Uses $(D getAddressInfo) if the current system supports it, + * and $(D InternetHost) otherwise. + * + * Returns: Array with one $(D Address) instance per socket address. + * + * Throws: $(D SocketOSException) on failure. + * + * Example: + * --- + * writeln("Resolving www.digitalmars.com:"); + * try + * { + * auto addresses = getAddress("www.digitalmars.com"); + * foreach (address; addresses) + * writefln(" IP: %s", address.toAddrString()); + * } + * catch (SocketException e) + * writefln(" Lookup failed: %s", e.msg); + * --- + */ +Address[] getAddress(in char[] hostname, in char[] service = null) +{ + if (getaddrinfoPointer && freeaddrinfoPointer) + { + // use getAddressInfo + auto infos = getAddressInfo(hostname, service); + Address[] results; + results.length = infos.length; + foreach (i, ref result; results) + result = infos[i].address; + return results; + } + else + return getAddress(hostname, serviceToPort(service)); +} + +/// ditto +Address[] getAddress(in char[] hostname, ushort port) +{ + if (getaddrinfoPointer && freeaddrinfoPointer) + return getAddress(hostname, to!string(port)); + else + { + // use getHostByName + auto ih = new InternetHost; + if (!ih.getHostByName(hostname)) + throw new AddressException( + text("Unable to resolve host '", hostname, "'")); + + Address[] results; + foreach (uint addr; ih.addrList) + results ~= new InternetAddress(addr, port); + return results; + } +} + + +@safe unittest +{ + softUnittest({ + auto addresses = getAddress("63.105.9.61"); + assert(addresses.length && addresses[0].toAddrString() == "63.105.9.61"); + + if (getaddrinfoPointer) + { + // test via gethostbyname + auto getaddrinfoPointerBackup = getaddrinfoPointer; + cast() getaddrinfoPointer = null; + scope(exit) cast() getaddrinfoPointer = getaddrinfoPointerBackup; + + addresses = getAddress("63.105.9.61"); + assert(addresses.length && addresses[0].toAddrString() == "63.105.9.61"); + } + }); +} + + +/** + * Provides _protocol-independent parsing of network addresses. Does not + * attempt name resolution. Uses $(D getAddressInfo) with + * $(D AddressInfoFlags.NUMERICHOST) if the current system supports it, and + * $(D InternetAddress) otherwise. + * + * Returns: An $(D Address) instance representing specified address. + * + * Throws: $(D SocketException) on failure. + * + * Example: + * --- + * writeln("Enter IP address:"); + * string ip = readln().chomp(); + * try + * { + * Address address = parseAddress(ip); + * writefln("Looking up reverse of %s:", + * address.toAddrString()); + * try + * { + * string reverse = address.toHostNameString(); + * if (reverse) + * writefln(" Reverse name: %s", reverse); + * else + * writeln(" Reverse hostname not found."); + * } + * catch (SocketException e) + * writefln(" Lookup error: %s", e.msg); + * } + * catch (SocketException e) + * { + * writefln(" %s is not a valid IP address: %s", + * ip, e.msg); + * } + * --- + */ +Address parseAddress(in char[] hostaddr, in char[] service = null) +{ + if (getaddrinfoPointer && freeaddrinfoPointer) + return getAddressInfo(hostaddr, service, AddressInfoFlags.NUMERICHOST)[0].address; + else + return parseAddress(hostaddr, serviceToPort(service)); +} + +/// ditto +Address parseAddress(in char[] hostaddr, ushort port) +{ + if (getaddrinfoPointer && freeaddrinfoPointer) + return parseAddress(hostaddr, to!string(port)); + else + { + auto in4_addr = InternetAddress.parse(hostaddr); + enforce(in4_addr != InternetAddress.ADDR_NONE, + new SocketParameterException("Invalid IP address")); + return new InternetAddress(in4_addr, port); + } +} + + +@safe unittest +{ + softUnittest({ + auto address = parseAddress("63.105.9.61"); + assert(address.toAddrString() == "63.105.9.61"); + + if (getaddrinfoPointer) + { + // test via inet_addr + auto getaddrinfoPointerBackup = getaddrinfoPointer; + cast() getaddrinfoPointer = null; + scope(exit) cast() getaddrinfoPointer = getaddrinfoPointerBackup; + + address = parseAddress("63.105.9.61"); + assert(address.toAddrString() == "63.105.9.61"); + } + + assert(collectException!SocketException(parseAddress("Invalid IP address"))); + }); +} + + +/** + * Class for exceptions thrown from an $(D Address). + */ +class AddressException: SocketOSException +{ + mixin socketOSExceptionCtors; +} + + +/** + * $(D Address) is an abstract class for representing a socket addresses. + * + * Example: + * --- + * writeln("About www.google.com port 80:"); + * try + * { + * Address[] addresses = getAddress("www.google.com", 80); + * writefln(" %d addresses found.", addresses.length); + * foreach (int i, Address a; addresses) + * { + * writefln(" Address %d:", i+1); + * writefln(" IP address: %s", a.toAddrString()); + * writefln(" Hostname: %s", a.toHostNameString()); + * writefln(" Port: %s", a.toPortString()); + * writefln(" Service name: %s", + * a.toServiceNameString()); + * } + * } + * catch (SocketException e) + * writefln(" Lookup error: %s", e.msg); + * --- + */ +abstract class Address +{ + /// Returns pointer to underlying $(D sockaddr) structure. + abstract @property sockaddr* name() pure nothrow @nogc; + abstract @property const(sockaddr)* name() const pure nothrow @nogc; /// ditto + + /// Returns actual size of underlying $(D sockaddr) structure. + abstract @property socklen_t nameLen() const pure nothrow @nogc; + + // Socket.remoteAddress, Socket.localAddress, and Socket.receiveFrom + // use setNameLen to set the actual size of the address as returned by + // getsockname, getpeername, and recvfrom, respectively. + // The following implementation is sufficient for fixed-length addresses, + // and ensures that the length is not changed. + // Must be overridden for variable-length addresses. + protected void setNameLen(socklen_t len) + { + if (len != this.nameLen) + throw new AddressException( + format("%s expects address of length %d, not %d", typeid(this), + this.nameLen, len), 0); + } + + /// Family of this address. + @property AddressFamily addressFamily() const pure nothrow @nogc + { + return cast(AddressFamily) name.sa_family; + } + + // Common code for toAddrString and toHostNameString + private string toHostString(bool numeric) @trusted const + { + // getnameinfo() is the recommended way to perform a reverse (name) + // lookup on both Posix and Windows. However, it is only available + // on Windows XP and above, and not included with the WinSock import + // libraries shipped with DMD. Thus, we check for getnameinfo at + // runtime in the shared module constructor, and use it if it's + // available in the base class method. Classes for specific network + // families (e.g. InternetHost) override this method and use a + // deprecated, albeit commonly-available method when getnameinfo() + // is not available. + // http://technet.microsoft.com/en-us/library/aa450403.aspx + if (getnameinfoPointer) + { + auto buf = new char[NI_MAXHOST]; + auto ret = getnameinfoPointer( + name, nameLen, + buf.ptr, cast(uint) buf.length, + null, 0, + numeric ? NI_NUMERICHOST : NI_NAMEREQD); + + if (!numeric) + { + if (ret == EAI_NONAME) + return null; + version (Windows) + if (ret == WSANO_DATA) + return null; + } + + enforce(ret == 0, new AddressException("Could not get " ~ + (numeric ? "host address" : "host name"))); + return assumeUnique(buf[0 .. strlen(buf.ptr)]); + } + + throw new SocketFeatureException((numeric ? "Host address" : "Host name") ~ + " lookup for this address family is not available on this system."); + } + + // Common code for toPortString and toServiceNameString + private string toServiceString(bool numeric) @trusted const + { + // See toHostNameString() for details about getnameinfo(). + if (getnameinfoPointer) + { + auto buf = new char[NI_MAXSERV]; + enforce(getnameinfoPointer( + name, nameLen, + null, 0, + buf.ptr, cast(uint) buf.length, + numeric ? NI_NUMERICSERV : NI_NAMEREQD + ) == 0, new AddressException("Could not get " ~ + (numeric ? "port number" : "service name"))); + return assumeUnique(buf[0 .. strlen(buf.ptr)]); + } + + throw new SocketFeatureException((numeric ? "Port number" : "Service name") ~ + " lookup for this address family is not available on this system."); + } + + /** + * Attempts to retrieve the host address as a human-readable string. + * + * Throws: $(D AddressException) on failure, or $(D SocketFeatureException) + * if address retrieval for this address family is not available on the + * current system. + */ + string toAddrString() const + { + return toHostString(true); + } + + /** + * Attempts to retrieve the host name as a fully qualified domain name. + * + * Returns: The FQDN corresponding to this $(D Address), or $(D null) if + * the host name did not resolve. + * + * Throws: $(D AddressException) on error, or $(D SocketFeatureException) + * if host name lookup for this address family is not available on the + * current system. + */ + string toHostNameString() const + { + return toHostString(false); + } + + /** + * Attempts to retrieve the numeric port number as a string. + * + * Throws: $(D AddressException) on failure, or $(D SocketFeatureException) + * if port number retrieval for this address family is not available on the + * current system. + */ + string toPortString() const + { + return toServiceString(true); + } + + /** + * Attempts to retrieve the service name as a string. + * + * Throws: $(D AddressException) on failure, or $(D SocketFeatureException) + * if service name lookup for this address family is not available on the + * current system. + */ + string toServiceNameString() const + { + return toServiceString(false); + } + + /// Human readable string representing this address. + override string toString() const + { + try + { + string host = toAddrString(); + string port = toPortString(); + if (host.indexOf(':') >= 0) + return "[" ~ host ~ "]:" ~ port; + else + return host ~ ":" ~ port; + } + catch (SocketException) + return "Unknown"; + } +} + +/** + * $(D UnknownAddress) encapsulates an unknown socket address. + */ +class UnknownAddress: Address +{ +protected: + sockaddr sa; + + +public: + override @property sockaddr* name() + { + return &sa; + } + + override @property const(sockaddr)* name() const + { + return &sa; + } + + + override @property socklen_t nameLen() const + { + return cast(socklen_t) sa.sizeof; + } + +} + + +/** + * $(D UnknownAddressReference) encapsulates a reference to an arbitrary + * socket address. + */ +class UnknownAddressReference: Address +{ +protected: + sockaddr* sa; + socklen_t len; + +public: + /// Constructs an $(D Address) with a reference to the specified $(D sockaddr). + this(sockaddr* sa, socklen_t len) pure nothrow @nogc + { + this.sa = sa; + this.len = len; + } + + /// Constructs an $(D Address) with a copy of the specified $(D sockaddr). + this(const(sockaddr)* sa, socklen_t len) @system pure nothrow + { + this.sa = cast(sockaddr*) (cast(ubyte*) sa)[0 .. len].dup.ptr; + this.len = len; + } + + override @property sockaddr* name() + { + return sa; + } + + override @property const(sockaddr)* name() const + { + return sa; + } + + + override @property socklen_t nameLen() const + { + return cast(socklen_t) len; + } +} + + +/** + * $(D InternetAddress) encapsulates an IPv4 (Internet Protocol version 4) + * socket address. + * + * Consider using $(D getAddress), $(D parseAddress) and $(D Address) methods + * instead of using this class directly. + */ +class InternetAddress: Address +{ +protected: + sockaddr_in sin; + + + this() pure nothrow @nogc + { + } + + +public: + override @property sockaddr* name() + { + return cast(sockaddr*)&sin; + } + + override @property const(sockaddr)* name() const + { + return cast(const(sockaddr)*)&sin; + } + + + override @property socklen_t nameLen() const + { + return cast(socklen_t) sin.sizeof; + } + + + enum uint ADDR_ANY = INADDR_ANY; /// Any IPv4 host address. + enum uint ADDR_NONE = INADDR_NONE; /// An invalid IPv4 host address. + enum ushort PORT_ANY = 0; /// Any IPv4 port number. + + /// Returns the IPv4 _port number (in host byte order). + @property ushort port() const pure nothrow @nogc + { + return ntohs(sin.sin_port); + } + + /// Returns the IPv4 address number (in host byte order). + @property uint addr() const pure nothrow @nogc + { + return ntohl(sin.sin_addr.s_addr); + } + + /** + * Construct a new $(D InternetAddress). + * Params: + * addr = an IPv4 address string in the dotted-decimal form a.b.c.d, + * or a host name which will be resolved using an $(D InternetHost) + * object. + * port = port number, may be $(D PORT_ANY). + */ + this(in char[] addr, ushort port) + { + uint uiaddr = parse(addr); + if (ADDR_NONE == uiaddr) + { + InternetHost ih = new InternetHost; + if (!ih.getHostByName(addr)) + //throw new AddressException("Invalid internet address"); + throw new AddressException( + text("Unable to resolve host '", addr, "'")); + uiaddr = ih.addrList[0]; + } + sin.sin_family = AddressFamily.INET; + sin.sin_addr.s_addr = htonl(uiaddr); + sin.sin_port = htons(port); + } + + /** + * Construct a new $(D InternetAddress). + * Params: + * addr = (optional) an IPv4 address in host byte order, may be $(D ADDR_ANY). + * port = port number, may be $(D PORT_ANY). + */ + this(uint addr, ushort port) pure nothrow @nogc + { + sin.sin_family = AddressFamily.INET; + sin.sin_addr.s_addr = htonl(addr); + sin.sin_port = htons(port); + } + + /// ditto + this(ushort port) pure nothrow @nogc + { + sin.sin_family = AddressFamily.INET; + sin.sin_addr.s_addr = ADDR_ANY; + sin.sin_port = htons(port); + } + + /** + * Construct a new $(D InternetAddress). + * Params: + * addr = A sockaddr_in as obtained from lower-level API calls such as getifaddrs. + */ + this(sockaddr_in addr) pure nothrow @nogc + { + assert(addr.sin_family == AddressFamily.INET); + sin = addr; + } + + /// Human readable string representing the IPv4 address in dotted-decimal form. + override string toAddrString() @trusted const + { + return to!string(inet_ntoa(sin.sin_addr)); + } + + /// Human readable string representing the IPv4 port. + override string toPortString() const + { + return std.conv.to!string(port); + } + + /** + * Attempts to retrieve the host name as a fully qualified domain name. + * + * Returns: The FQDN corresponding to this $(D InternetAddress), or + * $(D null) if the host name did not resolve. + * + * Throws: $(D AddressException) on error. + */ + override string toHostNameString() const + { + // getnameinfo() is the recommended way to perform a reverse (name) + // lookup on both Posix and Windows. However, it is only available + // on Windows XP and above, and not included with the WinSock import + // libraries shipped with DMD. Thus, we check for getnameinfo at + // runtime in the shared module constructor, and fall back to the + // deprecated getHostByAddr() if it could not be found. See also: + // http://technet.microsoft.com/en-us/library/aa450403.aspx + + if (getnameinfoPointer) + return super.toHostNameString(); + else + { + auto host = new InternetHost(); + if (!host.getHostByAddr(ntohl(sin.sin_addr.s_addr))) + return null; + return host.name; + } + } + + /** + * Compares with another InternetAddress of same type for equality + * Returns: true if the InternetAddresses share the same address and + * port number. + */ + override bool opEquals(Object o) const + { + auto other = cast(InternetAddress) o; + return other && this.sin.sin_addr.s_addr == other.sin.sin_addr.s_addr && + this.sin.sin_port == other.sin.sin_port; + } + + /// + @system unittest + { + auto addr1 = new InternetAddress("127.0.0.1", 80); + auto addr2 = new InternetAddress("127.0.0.2", 80); + + assert(addr1 == addr1); + assert(addr1 != addr2); + } + + /** + * Parse an IPv4 address string in the dotted-decimal form $(I a.b.c.d) + * and return the number. + * Returns: If the string is not a legitimate IPv4 address, + * $(D ADDR_NONE) is returned. + */ + static uint parse(in char[] addr) @trusted nothrow + { + return ntohl(inet_addr(addr.tempCString())); + } + + /** + * Convert an IPv4 address number in host byte order to a human readable + * string representing the IPv4 address in dotted-decimal form. + */ + static string addrToString(uint addr) @trusted nothrow + { + in_addr sin_addr; + sin_addr.s_addr = htonl(addr); + return to!string(inet_ntoa(sin_addr)); + } +} + + +@safe unittest +{ + softUnittest({ + const InternetAddress ia = new InternetAddress("63.105.9.61", 80); + assert(ia.toString() == "63.105.9.61:80"); + }); + + softUnittest({ + // test construction from a sockaddr_in + sockaddr_in sin; + + sin.sin_addr.s_addr = htonl(0x7F_00_00_01); // 127.0.0.1 + sin.sin_family = AddressFamily.INET; + sin.sin_port = htons(80); + + const InternetAddress ia = new InternetAddress(sin); + assert(ia.toString() == "127.0.0.1:80"); + }); + + softUnittest({ + // test reverse lookup + auto ih = new InternetHost; + if (ih.getHostByName("digitalmars.com")) + { + const ia = new InternetAddress(ih.addrList[0], 80); + assert(ia.toHostNameString() == "digitalmars.com"); + + if (getnameinfoPointer) + { + // test reverse lookup, via gethostbyaddr + auto getnameinfoPointerBackup = getnameinfoPointer; + cast() getnameinfoPointer = null; + scope(exit) cast() getnameinfoPointer = getnameinfoPointerBackup; + + assert(ia.toHostNameString() == "digitalmars.com"); + } + } + }); + + version (SlowTests) + softUnittest({ + // test failing reverse lookup + const InternetAddress ia = new InternetAddress("127.114.111.120", 80); + assert(ia.toHostNameString() is null); + + if (getnameinfoPointer) + { + // test failing reverse lookup, via gethostbyaddr + auto getnameinfoPointerBackup = getnameinfoPointer; + getnameinfoPointer = null; + scope(exit) getnameinfoPointer = getnameinfoPointerBackup; + + assert(ia.toHostNameString() is null); + } + }); +} + + +/** + * $(D Internet6Address) encapsulates an IPv6 (Internet Protocol version 6) + * socket address. + * + * Consider using $(D getAddress), $(D parseAddress) and $(D Address) methods + * instead of using this class directly. + */ +class Internet6Address: Address +{ +protected: + sockaddr_in6 sin6; + + + this() pure nothrow @nogc + { + } + + +public: + override @property sockaddr* name() + { + return cast(sockaddr*)&sin6; + } + + override @property const(sockaddr)* name() const + { + return cast(const(sockaddr)*)&sin6; + } + + + override @property socklen_t nameLen() const + { + return cast(socklen_t) sin6.sizeof; + } + + + /// Any IPv6 host address. + static @property ref const(ubyte)[16] ADDR_ANY() pure nothrow @nogc + { + const(ubyte)[16]* addr; + static if (is(typeof(IN6ADDR_ANY))) + { + addr = &IN6ADDR_ANY.s6_addr; + return *addr; + } + else static if (is(typeof(in6addr_any))) + { + addr = &in6addr_any.s6_addr; + return *addr; + } + else + static assert(0); + } + + /// Any IPv6 port number. + enum ushort PORT_ANY = 0; + + /// Returns the IPv6 port number. + @property ushort port() const pure nothrow @nogc + { + return ntohs(sin6.sin6_port); + } + + /// Returns the IPv6 address. + @property ubyte[16] addr() const pure nothrow @nogc + { + return sin6.sin6_addr.s6_addr; + } + + /** + * Construct a new $(D Internet6Address). + * Params: + * addr = an IPv6 host address string in the form described in RFC 2373, + * or a host name which will be resolved using $(D getAddressInfo). + * service = (optional) service name. + */ + this(in char[] addr, in char[] service = null) @trusted + { + auto results = getAddressInfo(addr, service, AddressFamily.INET6); + assert(results.length && results[0].family == AddressFamily.INET6); + sin6 = *cast(sockaddr_in6*) results[0].address.name; + } + + /** + * Construct a new $(D Internet6Address). + * Params: + * addr = an IPv6 host address string in the form described in RFC 2373, + * or a host name which will be resolved using $(D getAddressInfo). + * port = port number, may be $(D PORT_ANY). + */ + this(in char[] addr, ushort port) + { + if (port == PORT_ANY) + this(addr); + else + this(addr, to!string(port)); + } + + /** + * Construct a new $(D Internet6Address). + * Params: + * addr = (optional) an IPv6 host address in host byte order, or + * $(D ADDR_ANY). + * port = port number, may be $(D PORT_ANY). + */ + this(ubyte[16] addr, ushort port) pure nothrow @nogc + { + sin6.sin6_family = AddressFamily.INET6; + sin6.sin6_addr.s6_addr = addr; + sin6.sin6_port = htons(port); + } + + /// ditto + this(ushort port) pure nothrow @nogc + { + sin6.sin6_family = AddressFamily.INET6; + sin6.sin6_addr.s6_addr = ADDR_ANY; + sin6.sin6_port = htons(port); + } + + /** + * Construct a new $(D Internet6Address). + * Params: + * addr = A sockaddr_in6 as obtained from lower-level API calls such as getifaddrs. + */ + this(sockaddr_in6 addr) pure nothrow @nogc + { + assert(addr.sin6_family == AddressFamily.INET6); + sin6 = addr; + } + + /** + * Parse an IPv6 host address string as described in RFC 2373, and return the + * address. + * Throws: $(D SocketException) on error. + */ + static ubyte[16] parse(in char[] addr) @trusted + { + // Although we could use inet_pton here, it's only available on Windows + // versions starting with Vista, so use getAddressInfo with NUMERICHOST + // instead. + auto results = getAddressInfo(addr, AddressInfoFlags.NUMERICHOST); + if (results.length && results[0].family == AddressFamily.INET6) + return (cast(sockaddr_in6*) results[0].address.name).sin6_addr.s6_addr; + throw new AddressException("Not an IPv6 address", 0); + } +} + + +@safe unittest +{ + softUnittest({ + const Internet6Address ia = new Internet6Address("::1", 80); + assert(ia.toString() == "[::1]:80"); + }); + + softUnittest({ + // test construction from a sockaddr_in6 + sockaddr_in6 sin; + + sin.sin6_addr.s6_addr = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; // [::1] + sin.sin6_family = AddressFamily.INET6; + sin.sin6_port = htons(80); + + const Internet6Address ia = new Internet6Address(sin); + assert(ia.toString() == "[::1]:80"); + }); +} + + +version (StdDdoc) +{ + static if (!is(sockaddr_un)) + { + // This exists only to allow the constructor taking + // a sockaddr_un to be compilable for documentation + // on platforms that don't supply a sockaddr_un. + struct sockaddr_un + { + } + } + + /** + * $(D UnixAddress) encapsulates an address for a Unix domain socket + * ($(D AF_UNIX)), i.e. a socket bound to a path name in the file system. + * Available only on supported systems. + * + * Linux also supports an abstract address namespace, in which addresses + * are independent of the file system. A socket address is abstract + * iff `path` starts with a _null byte (`'\0'`). Null bytes in other + * positions of an abstract address are allowed and have no special + * meaning. + * + * Example: + * --- + * auto addr = new UnixAddress("/var/run/dbus/system_bus_socket"); + * auto abstractAddr = new UnixAddress("\0/tmp/dbus-OtHLWmCLPR"); + * --- + * + * See_Also: $(HTTP http://man7.org/linux/man-pages/man7/unix.7.html, UNIX(7)) + */ + class UnixAddress: Address + { + private this() pure nothrow @nogc {} + + /// Construct a new $(D UnixAddress) from the specified path. + this(in char[] path) { } + + /** + * Construct a new $(D UnixAddress). + * Params: + * addr = A sockaddr_un as obtained from lower-level API calls. + */ + this(sockaddr_un addr) pure nothrow @nogc { } + + /// Get the underlying _path. + @property string path() const { return null; } + + /// ditto + override string toString() const { return null; } + + override @property sockaddr* name() { return null; } + override @property const(sockaddr)* name() const { return null; } + override @property socklen_t nameLen() const { return 0; } + } +} +else +static if (is(sockaddr_un)) +{ + class UnixAddress: Address + { + protected: + socklen_t _nameLen; + + struct + { + align (1): + sockaddr_un sun; + char unused = '\0'; // placeholder for a terminating '\0' + } + + this() pure nothrow @nogc + { + sun.sun_family = AddressFamily.UNIX; + sun.sun_path = '?'; + _nameLen = sun.sizeof; + } + + override void setNameLen(socklen_t len) @trusted + { + if (len > sun.sizeof) + throw new SocketParameterException("Not enough socket address storage"); + _nameLen = len; + } + + public: + override @property sockaddr* name() + { + return cast(sockaddr*)&sun; + } + + override @property const(sockaddr)* name() const + { + return cast(const(sockaddr)*)&sun; + } + + override @property socklen_t nameLen() @trusted const + { + return _nameLen; + } + + this(in char[] path) @trusted pure + { + enforce(path.length <= sun.sun_path.sizeof, new SocketParameterException("Path too long")); + sun.sun_family = AddressFamily.UNIX; + sun.sun_path.ptr[0 .. path.length] = (cast(byte[]) path)[]; + _nameLen = cast(socklen_t) + { + auto len = sockaddr_un.init.sun_path.offsetof + path.length; + // Pathname socket address must be terminated with '\0' + // which must be included in the address length. + if (sun.sun_path.ptr[0]) + { + sun.sun_path.ptr[path.length] = 0; + ++len; + } + return len; + }(); + } + + this(sockaddr_un addr) pure nothrow @nogc + { + assert(addr.sun_family == AddressFamily.UNIX); + sun = addr; + } + + @property string path() @trusted const pure + { + auto len = _nameLen - sockaddr_un.init.sun_path.offsetof; + // For pathname socket address we need to strip off the terminating '\0' + if (sun.sun_path.ptr[0]) + --len; + return (cast(const(char)*) sun.sun_path.ptr)[0 .. len].idup; + } + + override string toString() const pure + { + return path; + } + } + + @safe unittest + { + import core.stdc.stdio : remove; + import std.file : deleteme; + + immutable ubyte[] data = [1, 2, 3, 4]; + Socket[2] pair; + + auto names = [ deleteme ~ "-unix-socket" ]; + version (linux) + names ~= "\0" ~ deleteme ~ "-abstract\0unix\0socket"; + foreach (name; names) + { + auto address = new UnixAddress(name); + + auto listener = new Socket(AddressFamily.UNIX, SocketType.STREAM); + scope(exit) listener.close(); + listener.bind(address); + scope(exit) () @trusted { if (name[0]) remove(name.tempCString()); } (); + assert(listener.localAddress.toString == name); + + listener.listen(1); + + pair[0] = new Socket(AddressFamily.UNIX, SocketType.STREAM); + scope(exit) listener.close(); + + pair[0].connect(address); + scope(exit) pair[0].close(); + + pair[1] = listener.accept(); + scope(exit) pair[1].close(); + + pair[0].send(data); + + auto buf = new ubyte[data.length]; + pair[1].receive(buf); + assert(buf == data); + } + } +} + + +/** + * Class for exceptions thrown by $(D Socket.accept). + */ +class SocketAcceptException: SocketOSException +{ + mixin socketOSExceptionCtors; +} + +/// How a socket is shutdown: +enum SocketShutdown: int +{ + RECEIVE = SD_RECEIVE, /// socket receives are disallowed + SEND = SD_SEND, /// socket sends are disallowed + BOTH = SD_BOTH, /// both RECEIVE and SEND +} + + +/// Flags may be OR'ed together: +enum SocketFlags: int +{ + NONE = 0, /// no flags specified + + OOB = MSG_OOB, /// out-of-band stream data + PEEK = MSG_PEEK, /// peek at incoming data without removing it from the queue, only for receiving + DONTROUTE = MSG_DONTROUTE, /// data should not be subject to routing; this flag may be ignored. Only for sending +} + + +private mixin template FieldProxy(string target, string field) +{ + mixin(` + @property typeof(`~target~`) `~field~`() const pure nothrow @nogc + { + return `~target~`; + } + + /// ditto + @property typeof(`~target~`) `~field~`(typeof(`~target~`) value) pure nothrow @nogc + { + return `~target~` = value; + } + `); +} + + +/// Duration timeout value. +struct TimeVal +{ + _ctimeval ctimeval; + alias tv_sec_t = typeof(ctimeval.tv_sec); + alias tv_usec_t = typeof(ctimeval.tv_usec); + + version (StdDdoc) // no DDoc for string mixins, can't forward individual fields + { + tv_sec_t seconds; /// Number of _seconds. + tv_usec_t microseconds; /// Number of additional _microseconds. + } + else + { + // D interface + mixin FieldProxy!(`ctimeval.tv_sec`, `seconds`); + mixin FieldProxy!(`ctimeval.tv_usec`, `microseconds`); + } +} + + +/** + * A collection of sockets for use with $(D Socket.select). + * + * $(D SocketSet) wraps the platform $(D fd_set) type. However, unlike + * $(D fd_set), $(D SocketSet) is not statically limited to $(D FD_SETSIZE) + * or any other limit, and grows as needed. + */ +class SocketSet +{ +private: + version (Windows) + { + // On Windows, fd_set is an array of socket handles, + // following a word containing the fd_set instance size. + // We use one dynamic array for everything, and use its first + // element(s) for the count. + + alias fd_set_count_type = typeof(fd_set.init.fd_count); + alias fd_set_type = typeof(fd_set.init.fd_array[0]); + static assert(fd_set_type.sizeof == socket_t.sizeof); + + // Number of fd_set_type elements at the start of our array that are + // used for the socket count and alignment + + enum FD_SET_OFFSET = fd_set.fd_array.offsetof / fd_set_type.sizeof; + static assert(FD_SET_OFFSET); + static assert(fd_set.fd_count.offsetof % fd_set_type.sizeof == 0); + + fd_set_type[] set; + + void resize(size_t size) pure nothrow + { + set.length = FD_SET_OFFSET + size; + } + + ref inout(fd_set_count_type) count() @trusted @property inout pure nothrow @nogc + { + assert(set.length); + return *cast(inout(fd_set_count_type)*)set.ptr; + } + + size_t capacity() @property const pure nothrow @nogc + { + return set.length - FD_SET_OFFSET; + } + + inout(socket_t)[] fds() @trusted inout @property pure nothrow @nogc + { + return cast(inout(socket_t)[])set[FD_SET_OFFSET .. FD_SET_OFFSET+count]; + } + } + else + version (Posix) + { + // On Posix, fd_set is a bit array. We assume that the fd_set + // type (declared in core.sys.posix.sys.select) is a structure + // containing a single field, a static array. + + static assert(fd_set.tupleof.length == 1); + + // This is the type used in the fd_set array. + // Using the type of the correct size is important for big-endian + // architectures. + + alias fd_set_type = typeof(fd_set.init.tupleof[0][0]); + + // Number of file descriptors represented by one fd_set_type + + enum FD_NFDBITS = 8 * fd_set_type.sizeof; + + static fd_set_type mask(uint n) pure nothrow @nogc + { + return (cast(fd_set_type) 1) << (n % FD_NFDBITS); + } + + // Array size to fit that many sockets + + static size_t lengthFor(size_t size) pure nothrow @nogc + { + return (size + (FD_NFDBITS-1)) / FD_NFDBITS; + } + + fd_set_type[] set; + + void resize(size_t size) pure nothrow + { + set.length = lengthFor(size); + } + + // Make sure we can fit that many sockets + + void setMinCapacity(size_t size) pure nothrow + { + auto length = lengthFor(size); + if (set.length < length) + set.length = length; + } + + size_t capacity() @property const pure nothrow @nogc + { + return set.length * FD_NFDBITS; + } + + int maxfd; + } + else + static assert(false, "Unknown platform"); + +public: + + /** + * Create a SocketSet with a specific initial capacity (defaults to + * $(D FD_SETSIZE), the system's default capacity). + */ + this(size_t size = FD_SETSIZE) pure nothrow + { + resize(size); + reset(); + } + + /// Reset the $(D SocketSet) so that there are 0 $(D Socket)s in the collection. + void reset() pure nothrow @nogc + { + version (Windows) + count = 0; + else + { + set[] = 0; + maxfd = -1; + } + } + + + void add(socket_t s) @trusted pure nothrow + { + version (Windows) + { + if (count == capacity) + { + set.length *= 2; + set.length = set.capacity; + } + ++count; + fds[$-1] = s; + } + else + { + auto index = s / FD_NFDBITS; + auto length = set.length; + if (index >= length) + { + while (index >= length) + length *= 2; + set.length = length; + set.length = set.capacity; + } + set[index] |= mask(s); + if (maxfd < s) + maxfd = s; + } + } + + /** + * Add a $(D Socket) to the collection. + * The socket must not already be in the collection. + */ + void add(Socket s) pure nothrow + { + add(s.sock); + } + + void remove(socket_t s) pure nothrow + { + version (Windows) + { + import std.algorithm.searching : countUntil; + auto fds = fds; + auto p = fds.countUntil(s); + if (p >= 0) + fds[p] = fds[--count]; + } + else + { + auto index = s / FD_NFDBITS; + if (index >= set.length) + return; + set[index] &= ~mask(s); + // note: adjusting maxfd would require scanning the set, not worth it + } + } + + + /** + * Remove this $(D Socket) from the collection. + * Does nothing if the socket is not in the collection already. + */ + void remove(Socket s) pure nothrow + { + remove(s.sock); + } + + int isSet(socket_t s) const pure nothrow @nogc + { + version (Windows) + { + import std.algorithm.searching : canFind; + return fds.canFind(s) ? 1 : 0; + } + else + { + if (s > maxfd) + return 0; + auto index = s / FD_NFDBITS; + return (set[index] & mask(s)) ? 1 : 0; + } + } + + + /// Return nonzero if this $(D Socket) is in the collection. + int isSet(Socket s) const pure nothrow @nogc + { + return isSet(s.sock); + } + + + /** + * Returns: + * The current capacity of this $(D SocketSet). The exact + * meaning of the return value varies from platform to platform. + * + * Note: + * Since D 2.065, this value does not indicate a + * restriction, and $(D SocketSet) will grow its capacity as + * needed automatically. + */ + @property uint max() const pure nothrow @nogc + { + return cast(uint) capacity; + } + + + fd_set* toFd_set() @trusted pure nothrow @nogc + { + return cast(fd_set*) set.ptr; + } + + + int selectn() const pure nothrow @nogc + { + version (Windows) + { + return count; + } + else version (Posix) + { + return maxfd + 1; + } + } +} + +@safe unittest +{ + auto fds = cast(socket_t[]) + [cast(socket_t) 1, 2, 0, 1024, 17, 42, 1234, 77, 77+32, 77+64]; + auto set = new SocketSet(); + foreach (fd; fds) assert(!set.isSet(fd)); + foreach (fd; fds) set.add(fd); + foreach (fd; fds) assert(set.isSet(fd)); + + // Make sure SocketSet reimplements fd_set correctly + auto fdset = set.toFd_set(); + foreach (fd; fds[0]..cast(socket_t)(fds[$-1]+1)) + assert(cast(bool) set.isSet(fd) == cast(bool)(() @trusted => FD_ISSET(fd, fdset))()); + + foreach (fd; fds) + { + assert(set.isSet(fd)); + set.remove(fd); + assert(!set.isSet(fd)); + } +} + +@safe unittest +{ + softUnittest({ + enum PAIRS = 768; + version (Posix) + () @trusted + { + enum LIMIT = 2048; + static assert(LIMIT > PAIRS*2); + import core.sys.posix.sys.resource; + rlimit fileLimit; + getrlimit(RLIMIT_NOFILE, &fileLimit); + assert(fileLimit.rlim_max > LIMIT, "Open file hard limit too low"); + fileLimit.rlim_cur = LIMIT; + setrlimit(RLIMIT_NOFILE, &fileLimit); + } (); + + Socket[2][PAIRS] pairs; + foreach (ref pair; pairs) + pair = socketPair(); + scope(exit) + { + foreach (pair; pairs) + { + pair[0].close(); + pair[1].close(); + } + } + + import std.random; + auto rng = Xorshift(42); + pairs[].randomShuffle(rng); + + auto readSet = new SocketSet(); + auto writeSet = new SocketSet(); + auto errorSet = new SocketSet(); + + foreach (testPair; pairs) + { + void fillSets() + { + readSet.reset(); + writeSet.reset(); + errorSet.reset(); + foreach (ref pair; pairs) + foreach (s; pair[]) + { + readSet.add(s); + writeSet.add(s); + errorSet.add(s); + } + } + + fillSets(); + auto n = Socket.select(readSet, writeSet, errorSet); + assert(n == PAIRS*2); // All in writeSet + assert(writeSet.isSet(testPair[0])); + assert(writeSet.isSet(testPair[1])); + assert(!readSet.isSet(testPair[0])); + assert(!readSet.isSet(testPair[1])); + assert(!errorSet.isSet(testPair[0])); + assert(!errorSet.isSet(testPair[1])); + + ubyte[1] b; + testPair[0].send(b[]); + fillSets(); + n = Socket.select(readSet, null, null); + assert(n == 1); // testPair[1] + assert(readSet.isSet(testPair[1])); + assert(!readSet.isSet(testPair[0])); + testPair[1].receive(b[]); + } + }); +} + +@safe unittest // Issue 14012, 14013 +{ + auto set = new SocketSet(1); + assert(set.max >= 0); + + enum LIMIT = 4096; + foreach (n; 0 .. LIMIT) + set.add(cast(socket_t) n); + assert(set.max >= LIMIT); +} + +/// The level at which a socket option is defined: +enum SocketOptionLevel: int +{ + SOCKET = SOL_SOCKET, /// Socket level + IP = ProtocolType.IP, /// Internet Protocol version 4 level + ICMP = ProtocolType.ICMP, /// Internet Control Message Protocol level + IGMP = ProtocolType.IGMP, /// Internet Group Management Protocol level + GGP = ProtocolType.GGP, /// Gateway to Gateway Protocol level + TCP = ProtocolType.TCP, /// Transmission Control Protocol level + PUP = ProtocolType.PUP, /// PARC Universal Packet Protocol level + UDP = ProtocolType.UDP, /// User Datagram Protocol level + IDP = ProtocolType.IDP, /// Xerox NS protocol level + RAW = ProtocolType.RAW, /// Raw IP packet level + IPV6 = ProtocolType.IPV6, /// Internet Protocol version 6 level +} + +/// _Linger information for use with SocketOption.LINGER. +struct Linger +{ + _clinger clinger; + + version (StdDdoc) // no DDoc for string mixins, can't forward individual fields + { + private alias l_onoff_t = typeof(_clinger.init.l_onoff ); + private alias l_linger_t = typeof(_clinger.init.l_linger); + l_onoff_t on; /// Nonzero for _on. + l_linger_t time; /// Linger _time. + } + else + { + // D interface + mixin FieldProxy!(`clinger.l_onoff`, `on`); + mixin FieldProxy!(`clinger.l_linger`, `time`); + } +} + +/// Specifies a socket option: +enum SocketOption: int +{ + DEBUG = SO_DEBUG, /// Record debugging information + BROADCAST = SO_BROADCAST, /// Allow transmission of broadcast messages + REUSEADDR = SO_REUSEADDR, /// Allow local reuse of address + LINGER = SO_LINGER, /// Linger on close if unsent data is present + OOBINLINE = SO_OOBINLINE, /// Receive out-of-band data in band + SNDBUF = SO_SNDBUF, /// Send buffer size + RCVBUF = SO_RCVBUF, /// Receive buffer size + DONTROUTE = SO_DONTROUTE, /// Do not route + SNDTIMEO = SO_SNDTIMEO, /// Send timeout + RCVTIMEO = SO_RCVTIMEO, /// Receive timeout + ERROR = SO_ERROR, /// Retrieve and clear error status + KEEPALIVE = SO_KEEPALIVE, /// Enable keep-alive packets + ACCEPTCONN = SO_ACCEPTCONN, /// Listen + RCVLOWAT = SO_RCVLOWAT, /// Minimum number of input bytes to process + SNDLOWAT = SO_SNDLOWAT, /// Minimum number of output bytes to process + TYPE = SO_TYPE, /// Socket type + + // SocketOptionLevel.TCP: + TCP_NODELAY = .TCP_NODELAY, /// Disable the Nagle algorithm for send coalescing + + // SocketOptionLevel.IPV6: + IPV6_UNICAST_HOPS = .IPV6_UNICAST_HOPS, /// IP unicast hop limit + IPV6_MULTICAST_IF = .IPV6_MULTICAST_IF, /// IP multicast interface + IPV6_MULTICAST_LOOP = .IPV6_MULTICAST_LOOP, /// IP multicast loopback + IPV6_MULTICAST_HOPS = .IPV6_MULTICAST_HOPS, /// IP multicast hops + IPV6_JOIN_GROUP = .IPV6_JOIN_GROUP, /// Add an IP group membership + IPV6_LEAVE_GROUP = .IPV6_LEAVE_GROUP, /// Drop an IP group membership + IPV6_V6ONLY = .IPV6_V6ONLY, /// Treat wildcard bind as AF_INET6-only +} + + +/** + * $(D Socket) is a class that creates a network communication endpoint using + * the Berkeley sockets interface. + */ +class Socket +{ +private: + socket_t sock; + AddressFamily _family; + + version (Windows) + bool _blocking = false; /// Property to get or set whether the socket is blocking or nonblocking. + + // The WinSock timeouts seem to be effectively skewed by a constant + // offset of about half a second (value in milliseconds). This has + // been confirmed on updated (as of Jun 2011) Windows XP, Windows 7 + // and Windows Server 2008 R2 boxes. The unittest below tests this + // behavior. + enum WINSOCK_TIMEOUT_SKEW = 500; + + @safe unittest + { + version (SlowTests) + softUnittest({ + import std.datetime; + import std.typecons; + + enum msecs = 1000; + auto pair = socketPair(); + auto sock = pair[0]; + sock.setOption(SocketOptionLevel.SOCKET, + SocketOption.RCVTIMEO, dur!"msecs"(msecs)); + + auto sw = StopWatch(Yes.autoStart); + ubyte[1] buf; + sock.receive(buf); + sw.stop(); + + Duration readBack = void; + sock.getOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, readBack); + + assert(readBack.total!"msecs" == msecs); + assert(sw.peek().msecs > msecs-100 && sw.peek().msecs < msecs+100); + }); + } + + void setSock(socket_t handle) + { + assert(handle != socket_t.init); + sock = handle; + + // Set the option to disable SIGPIPE on send() if the platform + // has it (e.g. on OS X). + static if (is(typeof(SO_NOSIGPIPE))) + { + setOption(SocketOptionLevel.SOCKET, cast(SocketOption) SO_NOSIGPIPE, true); + } + } + + + // For use with accepting(). + protected this() pure nothrow @nogc + { + } + + +public: + + /** + * Create a blocking socket. If a single protocol type exists to support + * this socket type within the address family, the $(D ProtocolType) may be + * omitted. + */ + this(AddressFamily af, SocketType type, ProtocolType protocol) @trusted + { + _family = af; + auto handle = cast(socket_t) socket(af, type, protocol); + if (handle == socket_t.init) + throw new SocketOSException("Unable to create socket"); + setSock(handle); + } + + /// ditto + this(AddressFamily af, SocketType type) + { + /* A single protocol exists to support this socket type within the + * protocol family, so the ProtocolType is assumed. + */ + this(af, type, cast(ProtocolType) 0); // Pseudo protocol number. + } + + + /// ditto + this(AddressFamily af, SocketType type, in char[] protocolName) @trusted + { + protoent* proto; + proto = getprotobyname(protocolName.tempCString()); + if (!proto) + throw new SocketOSException("Unable to find the protocol"); + this(af, type, cast(ProtocolType) proto.p_proto); + } + + + /** + * Create a blocking socket using the parameters from the specified + * $(D AddressInfo) structure. + */ + this(in AddressInfo info) + { + this(info.family, info.type, info.protocol); + } + + /// Use an existing socket handle. + this(socket_t sock, AddressFamily af) pure nothrow @nogc + { + assert(sock != socket_t.init); + this.sock = sock; + this._family = af; + } + + + ~this() nothrow @nogc + { + close(); + } + + + /// Get underlying socket handle. + @property socket_t handle() const pure nothrow @nogc + { + return sock; + } + + /** + * Get/set socket's blocking flag. + * + * When a socket is blocking, calls to receive(), accept(), and send() + * will block and wait for data/action. + * A non-blocking socket will immediately return instead of blocking. + */ + @property bool blocking() @trusted const nothrow @nogc + { + version (Windows) + { + return _blocking; + } + else version (Posix) + { + return !(fcntl(handle, F_GETFL, 0) & O_NONBLOCK); + } + } + + /// ditto + @property void blocking(bool byes) @trusted + { + version (Windows) + { + uint num = !byes; + if (_SOCKET_ERROR == ioctlsocket(sock, FIONBIO, &num)) + goto err; + _blocking = byes; + } + else version (Posix) + { + int x = fcntl(sock, F_GETFL, 0); + if (-1 == x) + goto err; + if (byes) + x &= ~O_NONBLOCK; + else + x |= O_NONBLOCK; + if (-1 == fcntl(sock, F_SETFL, x)) + goto err; + } + return; // Success. + + err: + throw new SocketOSException("Unable to set socket blocking"); + } + + + /// Get the socket's address family. + @property AddressFamily addressFamily() + { + return _family; + } + + /// Property that indicates if this is a valid, alive socket. + @property bool isAlive() @trusted const + { + int type; + socklen_t typesize = cast(socklen_t) type.sizeof; + return !getsockopt(sock, SOL_SOCKET, SO_TYPE, cast(char*)&type, &typesize); + } + + /// Associate a local address with this socket. + void bind(Address addr) @trusted + { + if (_SOCKET_ERROR == .bind(sock, addr.name, addr.nameLen)) + throw new SocketOSException("Unable to bind socket"); + } + + /** + * Establish a connection. If the socket is blocking, connect waits for + * the connection to be made. If the socket is nonblocking, connect + * returns immediately and the connection attempt is still in progress. + */ + void connect(Address to) @trusted + { + if (_SOCKET_ERROR == .connect(sock, to.name, to.nameLen)) + { + int err; + err = _lasterr(); + + if (!blocking) + { + version (Windows) + { + if (WSAEWOULDBLOCK == err) + return; + } + else version (Posix) + { + if (EINPROGRESS == err) + return; + } + else + { + static assert(0); + } + } + throw new SocketOSException("Unable to connect socket", err); + } + } + + /** + * Listen for an incoming connection. $(D bind) must be called before you + * can $(D listen). The $(D backlog) is a request of how many pending + * incoming connections are queued until $(D accept)ed. + */ + void listen(int backlog) @trusted + { + if (_SOCKET_ERROR == .listen(sock, backlog)) + throw new SocketOSException("Unable to listen on socket"); + } + + /** + * Called by $(D accept) when a new $(D Socket) must be created for a new + * connection. To use a derived class, override this method and return an + * instance of your class. The returned $(D Socket)'s handle must not be + * set; $(D Socket) has a protected constructor $(D this()) to use in this + * situation. + * + * Override to use a derived class. + * The returned socket's handle must not be set. + */ + protected Socket accepting() pure nothrow + { + return new Socket; + } + + /** + * Accept an incoming connection. If the socket is blocking, $(D accept) + * waits for a connection request. Throws $(D SocketAcceptException) if + * unable to _accept. See $(D accepting) for use with derived classes. + */ + Socket accept() @trusted + { + auto newsock = cast(socket_t).accept(sock, null, null); + if (socket_t.init == newsock) + throw new SocketAcceptException("Unable to accept socket connection"); + + Socket newSocket; + try + { + newSocket = accepting(); + assert(newSocket.sock == socket_t.init); + + newSocket.setSock(newsock); + version (Windows) + newSocket._blocking = _blocking; //inherits blocking mode + newSocket._family = _family; //same family + } + catch (Throwable o) + { + _close(newsock); + throw o; + } + + return newSocket; + } + + /// Disables sends and/or receives. + void shutdown(SocketShutdown how) @trusted nothrow @nogc + { + .shutdown(sock, cast(int) how); + } + + + private static void _close(socket_t sock) @system nothrow @nogc + { + version (Windows) + { + .closesocket(sock); + } + else version (Posix) + { + .close(sock); + } + } + + + /** + * Immediately drop any connections and release socket resources. + * Calling $(D shutdown) before $(D close) is recommended for + * connection-oriented sockets. The $(D Socket) object is no longer + * usable after $(D close). + * Calling shutdown() before this is recommended + * for connection-oriented sockets. + */ + void close() @trusted nothrow @nogc + { + _close(sock); + sock = socket_t.init; + } + + + /** + * Returns: the local machine's host name + */ + static @property string hostName() @trusted // getter + { + char[256] result; // Host names are limited to 255 chars. + if (_SOCKET_ERROR == .gethostname(result.ptr, result.length)) + throw new SocketOSException("Unable to obtain host name"); + return to!string(result.ptr); + } + + /// Remote endpoint $(D Address). + @property Address remoteAddress() @trusted + { + Address addr = createAddress(); + socklen_t nameLen = addr.nameLen; + if (_SOCKET_ERROR == .getpeername(sock, addr.name, &nameLen)) + throw new SocketOSException("Unable to obtain remote socket address"); + addr.setNameLen(nameLen); + assert(addr.addressFamily == _family); + return addr; + } + + /// Local endpoint $(D Address). + @property Address localAddress() @trusted + { + Address addr = createAddress(); + socklen_t nameLen = addr.nameLen; + if (_SOCKET_ERROR == .getsockname(sock, addr.name, &nameLen)) + throw new SocketOSException("Unable to obtain local socket address"); + addr.setNameLen(nameLen); + assert(addr.addressFamily == _family); + return addr; + } + + /** + * Send or receive error code. See $(D wouldHaveBlocked), + * $(D lastSocketError) and $(D Socket.getErrorText) for obtaining more + * information about the error. + */ + enum int ERROR = _SOCKET_ERROR; + + private static int capToInt(size_t size) nothrow @nogc + { + // Windows uses int instead of size_t for length arguments. + // Luckily, the send/recv functions make no guarantee that + // all the data is sent, so we use that to send at most + // int.max bytes. + return size > size_t(int.max) ? int.max : cast(int) size; + } + + /** + * Send data on the connection. If the socket is blocking and there is no + * buffer space left, $(D send) waits. + * Returns: The number of bytes actually sent, or $(D Socket.ERROR) on + * failure. + */ + ptrdiff_t send(const(void)[] buf, SocketFlags flags) @trusted + { + static if (is(typeof(MSG_NOSIGNAL))) + { + flags = cast(SocketFlags)(flags | MSG_NOSIGNAL); + } + version (Windows) + auto sent = .send(sock, buf.ptr, capToInt(buf.length), cast(int) flags); + else + auto sent = .send(sock, buf.ptr, buf.length, cast(int) flags); + return sent; + } + + /// ditto + ptrdiff_t send(const(void)[] buf) + { + return send(buf, SocketFlags.NONE); + } + + /** + * Send data to a specific destination Address. If the destination address is + * not specified, a connection must have been made and that address is used. + * If the socket is blocking and there is no buffer space left, $(D sendTo) waits. + * Returns: The number of bytes actually sent, or $(D Socket.ERROR) on + * failure. + */ + ptrdiff_t sendTo(const(void)[] buf, SocketFlags flags, Address to) @trusted + { + static if (is(typeof(MSG_NOSIGNAL))) + { + flags = cast(SocketFlags)(flags | MSG_NOSIGNAL); + } + version (Windows) + return .sendto( + sock, buf.ptr, capToInt(buf.length), + cast(int) flags, to.name, to.nameLen + ); + else + return .sendto(sock, buf.ptr, buf.length, cast(int) flags, to.name, to.nameLen); + } + + /// ditto + ptrdiff_t sendTo(const(void)[] buf, Address to) + { + return sendTo(buf, SocketFlags.NONE, to); + } + + + //assumes you connect()ed + /// ditto + ptrdiff_t sendTo(const(void)[] buf, SocketFlags flags) @trusted + { + static if (is(typeof(MSG_NOSIGNAL))) + { + flags = cast(SocketFlags)(flags | MSG_NOSIGNAL); + } + version (Windows) + return .sendto(sock, buf.ptr, capToInt(buf.length), cast(int) flags, null, 0); + else + return .sendto(sock, buf.ptr, buf.length, cast(int) flags, null, 0); + } + + + //assumes you connect()ed + /// ditto + ptrdiff_t sendTo(const(void)[] buf) + { + return sendTo(buf, SocketFlags.NONE); + } + + + /** + * Receive data on the connection. If the socket is blocking, $(D receive) + * waits until there is data to be received. + * Returns: The number of bytes actually received, $(D 0) if the remote side + * has closed the connection, or $(D Socket.ERROR) on failure. + */ + ptrdiff_t receive(void[] buf, SocketFlags flags) @trusted + { + version (Windows) // Does not use size_t + { + return buf.length + ? .recv(sock, buf.ptr, capToInt(buf.length), cast(int) flags) + : 0; + } + else + { + return buf.length + ? .recv(sock, buf.ptr, buf.length, cast(int) flags) + : 0; + } + } + + /// ditto + ptrdiff_t receive(void[] buf) + { + return receive(buf, SocketFlags.NONE); + } + + /** + * Receive data and get the remote endpoint $(D Address). + * If the socket is blocking, $(D receiveFrom) waits until there is data to + * be received. + * Returns: The number of bytes actually received, $(D 0) if the remote side + * has closed the connection, or $(D Socket.ERROR) on failure. + */ + ptrdiff_t receiveFrom(void[] buf, SocketFlags flags, ref Address from) @trusted + { + if (!buf.length) //return 0 and don't think the connection closed + return 0; + if (from is null || from.addressFamily != _family) + from = createAddress(); + socklen_t nameLen = from.nameLen; + version (Windows) + { + auto read = .recvfrom(sock, buf.ptr, capToInt(buf.length), cast(int) flags, from.name, &nameLen); + from.setNameLen(nameLen); + assert(from.addressFamily == _family); + // if (!read) //connection closed + return read; + } + else + { + auto read = .recvfrom(sock, buf.ptr, buf.length, cast(int) flags, from.name, &nameLen); + from.setNameLen(nameLen); + assert(from.addressFamily == _family); + // if (!read) //connection closed + return read; + } + } + + + /// ditto + ptrdiff_t receiveFrom(void[] buf, ref Address from) + { + return receiveFrom(buf, SocketFlags.NONE, from); + } + + + //assumes you connect()ed + /// ditto + ptrdiff_t receiveFrom(void[] buf, SocketFlags flags) @trusted + { + if (!buf.length) //return 0 and don't think the connection closed + return 0; + version (Windows) + { + auto read = .recvfrom(sock, buf.ptr, capToInt(buf.length), cast(int) flags, null, null); + // if (!read) //connection closed + return read; + } + else + { + auto read = .recvfrom(sock, buf.ptr, buf.length, cast(int) flags, null, null); + // if (!read) //connection closed + return read; + } + } + + + //assumes you connect()ed + /// ditto + ptrdiff_t receiveFrom(void[] buf) + { + return receiveFrom(buf, SocketFlags.NONE); + } + + + /** + * Get a socket option. + * Returns: The number of bytes written to $(D result). + * The length, in bytes, of the actual result - very different from getsockopt() + */ + int getOption(SocketOptionLevel level, SocketOption option, void[] result) @trusted + { + socklen_t len = cast(socklen_t) result.length; + if (_SOCKET_ERROR == .getsockopt(sock, cast(int) level, cast(int) option, result.ptr, &len)) + throw new SocketOSException("Unable to get socket option"); + return len; + } + + + /// Common case of getting integer and boolean options. + int getOption(SocketOptionLevel level, SocketOption option, out int32_t result) @trusted + { + return getOption(level, option, (&result)[0 .. 1]); + } + + + /// Get the linger option. + int getOption(SocketOptionLevel level, SocketOption option, out Linger result) @trusted + { + //return getOption(cast(SocketOptionLevel) SocketOptionLevel.SOCKET, SocketOption.LINGER, (&result)[0 .. 1]); + return getOption(level, option, (&result.clinger)[0 .. 1]); + } + + /// Get a timeout (duration) option. + void getOption(SocketOptionLevel level, SocketOption option, out Duration result) @trusted + { + enforce(option == SocketOption.SNDTIMEO || option == SocketOption.RCVTIMEO, + new SocketParameterException("Not a valid timeout option: " ~ to!string(option))); + // WinSock returns the timeout values as a milliseconds DWORD, + // while Linux and BSD return a timeval struct. + version (Windows) + { + int msecs; + getOption(level, option, (&msecs)[0 .. 1]); + if (option == SocketOption.RCVTIMEO) + msecs += WINSOCK_TIMEOUT_SKEW; + result = dur!"msecs"(msecs); + } + else version (Posix) + { + TimeVal tv; + getOption(level, option, (&tv.ctimeval)[0 .. 1]); + result = dur!"seconds"(tv.seconds) + dur!"usecs"(tv.microseconds); + } + else static assert(false); + } + + /// Set a socket option. + void setOption(SocketOptionLevel level, SocketOption option, void[] value) @trusted + { + if (_SOCKET_ERROR == .setsockopt(sock, cast(int) level, + cast(int) option, value.ptr, cast(uint) value.length)) + throw new SocketOSException("Unable to set socket option"); + } + + + /// Common case for setting integer and boolean options. + void setOption(SocketOptionLevel level, SocketOption option, int32_t value) @trusted + { + setOption(level, option, (&value)[0 .. 1]); + } + + + /// Set the linger option. + void setOption(SocketOptionLevel level, SocketOption option, Linger value) @trusted + { + //setOption(cast(SocketOptionLevel) SocketOptionLevel.SOCKET, SocketOption.LINGER, (&value)[0 .. 1]); + setOption(level, option, (&value.clinger)[0 .. 1]); + } + + /** + * Sets a timeout (duration) option, i.e. $(D SocketOption.SNDTIMEO) or + * $(D RCVTIMEO). Zero indicates no timeout. + * + * In a typical application, you might also want to consider using + * a non-blocking socket instead of setting a timeout on a blocking one. + * + * Note: While the receive timeout setting is generally quite accurate + * on *nix systems even for smaller durations, there are two issues to + * be aware of on Windows: First, although undocumented, the effective + * timeout duration seems to be the one set on the socket plus half + * a second. $(D setOption()) tries to compensate for that, but still, + * timeouts under 500ms are not possible on Windows. Second, be aware + * that the actual amount of time spent until a blocking call returns + * randomly varies on the order of 10ms. + * + * Params: + * level = The level at which a socket option is defined. + * option = Either $(D SocketOption.SNDTIMEO) or $(D SocketOption.RCVTIMEO). + * value = The timeout duration to set. Must not be negative. + * + * Throws: $(D SocketException) if setting the options fails. + * + * Example: + * --- + * import std.datetime; + * import std.typecons; + * auto pair = socketPair(); + * scope(exit) foreach (s; pair) s.close(); + * + * // Set a receive timeout, and then wait at one end of + * // the socket pair, knowing that no data will arrive. + * pair[0].setOption(SocketOptionLevel.SOCKET, + * SocketOption.RCVTIMEO, dur!"seconds"(1)); + * + * auto sw = StopWatch(Yes.autoStart); + * ubyte[1] buffer; + * pair[0].receive(buffer); + * writefln("Waited %s ms until the socket timed out.", + * sw.peek.msecs); + * --- + */ + void setOption(SocketOptionLevel level, SocketOption option, Duration value) @trusted + { + enforce(option == SocketOption.SNDTIMEO || option == SocketOption.RCVTIMEO, + new SocketParameterException("Not a valid timeout option: " ~ to!string(option))); + + enforce(value >= dur!"hnsecs"(0), new SocketParameterException( + "Timeout duration must not be negative.")); + + version (Windows) + { + import std.algorithm.comparison : max; + + auto msecs = to!int(value.total!"msecs"); + if (msecs != 0 && option == SocketOption.RCVTIMEO) + msecs = max(1, msecs - WINSOCK_TIMEOUT_SKEW); + setOption(level, option, msecs); + } + else version (Posix) + { + _ctimeval tv; + value.split!("seconds", "usecs")(tv.tv_sec, tv.tv_usec); + setOption(level, option, (&tv)[0 .. 1]); + } + else static assert(false); + } + + /** + * Get a text description of this socket's error status, and clear the + * socket's error status. + */ + string getErrorText() + { + int32_t error; + getOption(SocketOptionLevel.SOCKET, SocketOption.ERROR, error); + return formatSocketError(error); + } + + /** + * Enables TCP keep-alive with the specified parameters. + * + * Params: + * time = Number of seconds with no activity until the first + * keep-alive packet is sent. + * interval = Number of seconds between when successive keep-alive + * packets are sent if no acknowledgement is received. + * + * Throws: $(D SocketOSException) if setting the options fails, or + * $(D SocketFeatureException) if setting keep-alive parameters is + * unsupported on the current platform. + */ + void setKeepAlive(int time, int interval) @trusted + { + version (Windows) + { + tcp_keepalive options; + options.onoff = 1; + options.keepalivetime = time * 1000; + options.keepaliveinterval = interval * 1000; + uint cbBytesReturned; + enforce(WSAIoctl(sock, SIO_KEEPALIVE_VALS, + &options, options.sizeof, + null, 0, + &cbBytesReturned, null, null) == 0, + new SocketOSException("Error setting keep-alive")); + } + else + static if (is(typeof(TCP_KEEPIDLE)) && is(typeof(TCP_KEEPINTVL))) + { + setOption(SocketOptionLevel.TCP, cast(SocketOption) TCP_KEEPIDLE, time); + setOption(SocketOptionLevel.TCP, cast(SocketOption) TCP_KEEPINTVL, interval); + setOption(SocketOptionLevel.SOCKET, SocketOption.KEEPALIVE, true); + } + else + throw new SocketFeatureException("Setting keep-alive options " ~ + "is not supported on this platform"); + } + + /** + * Wait for a socket to change status. A wait timeout of $(REF Duration, core, time) or + * $(D TimeVal), may be specified; if a timeout is not specified or the + * $(D TimeVal) is $(D null), the maximum timeout is used. The $(D TimeVal) + * timeout has an unspecified value when $(D select) returns. + * Returns: The number of sockets with status changes, $(D 0) on timeout, + * or $(D -1) on interruption. If the return value is greater than $(D 0), + * the $(D SocketSets) are updated to only contain the sockets having status + * changes. For a connecting socket, a write status change means the + * connection is established and it's able to send. For a listening socket, + * a read status change means there is an incoming connection request and + * it's able to accept. + * + * `SocketSet`'s updated to include only those sockets which an event occured. + * For a `connect()`ing socket, writeability means connected. + * For a `listen()`ing socket, readability means listening + * `Winsock`; possibly internally limited to 64 sockets per set. + * + * Returns: + * the number of events, 0 on timeout, or -1 on interruption + */ + static int select(SocketSet checkRead, SocketSet checkWrite, SocketSet checkError, Duration timeout) @trusted + { + auto vals = timeout.split!("seconds", "usecs")(); + TimeVal tv; + tv.seconds = cast(tv.tv_sec_t ) vals.seconds; + tv.microseconds = cast(tv.tv_usec_t) vals.usecs; + return select(checkRead, checkWrite, checkError, &tv); + } + + /// ditto + //maximum timeout + static int select(SocketSet checkRead, SocketSet checkWrite, SocketSet checkError) + { + return select(checkRead, checkWrite, checkError, null); + } + + /// Ditto + static int select(SocketSet checkRead, SocketSet checkWrite, SocketSet checkError, TimeVal* timeout) @trusted + in + { + //make sure none of the SocketSet's are the same object + if (checkRead) + { + assert(checkRead !is checkWrite); + assert(checkRead !is checkError); + } + if (checkWrite) + { + assert(checkWrite !is checkError); + } + } + body + { + fd_set* fr, fw, fe; + int n = 0; + + version (Windows) + { + // Windows has a problem with empty fd_set`s that aren't null. + fr = checkRead && checkRead.count ? checkRead.toFd_set() : null; + fw = checkWrite && checkWrite.count ? checkWrite.toFd_set() : null; + fe = checkError && checkError.count ? checkError.toFd_set() : null; + } + else + { + if (checkRead) + { + fr = checkRead.toFd_set(); + n = checkRead.selectn(); + } + else + { + fr = null; + } + + if (checkWrite) + { + fw = checkWrite.toFd_set(); + int _n; + _n = checkWrite.selectn(); + if (_n > n) + n = _n; + } + else + { + fw = null; + } + + if (checkError) + { + fe = checkError.toFd_set(); + int _n; + _n = checkError.selectn(); + if (_n > n) + n = _n; + } + else + { + fe = null; + } + + // Make sure the sets' capacity matches, to avoid select reading + // out of bounds just because one set was bigger than another + if (checkRead ) checkRead .setMinCapacity(n); + if (checkWrite) checkWrite.setMinCapacity(n); + if (checkError) checkError.setMinCapacity(n); + } + + int result = .select(n, fr, fw, fe, &timeout.ctimeval); + + version (Windows) + { + if (_SOCKET_ERROR == result && WSAGetLastError() == WSAEINTR) + return -1; + } + else version (Posix) + { + if (_SOCKET_ERROR == result && errno == EINTR) + return -1; + } + else + { + static assert(0); + } + + if (_SOCKET_ERROR == result) + throw new SocketOSException("Socket select error"); + + return result; + } + + + /** + * Can be overridden to support other addresses. + * Returns: a new `Address` object for the current address family. + */ + protected Address createAddress() pure nothrow + { + Address result; + switch (_family) + { + static if (is(sockaddr_un)) + { + case AddressFamily.UNIX: + result = new UnixAddress; + break; + } + + case AddressFamily.INET: + result = new InternetAddress; + break; + + case AddressFamily.INET6: + result = new Internet6Address; + break; + + default: + result = new UnknownAddress; + } + return result; + } + +} + + +/// $(D TcpSocket) is a shortcut class for a TCP Socket. +class TcpSocket: Socket +{ + /// Constructs a blocking TCP Socket. + this(AddressFamily family) + { + super(family, SocketType.STREAM, ProtocolType.TCP); + } + + /// Constructs a blocking IPv4 TCP Socket. + this() + { + this(AddressFamily.INET); + } + + + //shortcut + /// Constructs a blocking TCP Socket and connects to an $(D Address). + this(Address connectTo) + { + this(connectTo.addressFamily); + connect(connectTo); + } +} + + +/// $(D UdpSocket) is a shortcut class for a UDP Socket. +class UdpSocket: Socket +{ + /// Constructs a blocking UDP Socket. + this(AddressFamily family) + { + super(family, SocketType.DGRAM, ProtocolType.UDP); + } + + + /// Constructs a blocking IPv4 UDP Socket. + this() + { + this(AddressFamily.INET); + } +} + +// Issue 16514 +@safe unittest +{ + class TestSocket : Socket + { + override + { + const pure nothrow @nogc @property @safe socket_t handle() { assert(0); } + const nothrow @nogc @property @trusted bool blocking() { assert(0); } + @property @trusted void blocking(bool byes) { assert(0); } + @property @safe AddressFamily addressFamily() { assert(0); } + const @property @trusted bool isAlive() { assert(0); } + @trusted void bind(Address addr) { assert(0); } + @trusted void connect(Address to) { assert(0); } + @trusted void listen(int backlog) { assert(0); } + protected pure nothrow @safe Socket accepting() { assert(0); } + @trusted Socket accept() { assert(0); } + nothrow @nogc @trusted void shutdown(SocketShutdown how) { assert(0); } + nothrow @nogc @trusted void close() { assert(0); } + @property @trusted Address remoteAddress() { assert(0); } + @property @trusted Address localAddress() { assert(0); } + @trusted ptrdiff_t send(const(void)[] buf, SocketFlags flags) { assert(0); } + @safe ptrdiff_t send(const(void)[] buf) { assert(0); } + @trusted ptrdiff_t sendTo(const(void)[] buf, SocketFlags flags, Address to) { assert(0); } + @safe ptrdiff_t sendTo(const(void)[] buf, Address to) { assert(0); } + @trusted ptrdiff_t sendTo(const(void)[] buf, SocketFlags flags) { assert(0); } + @safe ptrdiff_t sendTo(const(void)[] buf) { assert(0); } + @trusted ptrdiff_t receive(void[] buf, SocketFlags flags) { assert(0); } + @safe ptrdiff_t receive(void[] buf) { assert(0); } + @trusted ptrdiff_t receiveFrom(void[] buf, SocketFlags flags, ref Address from) { assert(0); } + @safe ptrdiff_t receiveFrom(void[] buf, ref Address from) { assert(0); } + @trusted ptrdiff_t receiveFrom(void[] buf, SocketFlags flags) { assert(0); } + @safe ptrdiff_t receiveFrom(void[] buf) { assert(0); } + @trusted int getOption(SocketOptionLevel level, SocketOption option, void[] result) { assert(0); } + @trusted int getOption(SocketOptionLevel level, SocketOption option, out int32_t result) { assert(0); } + @trusted int getOption(SocketOptionLevel level, SocketOption option, out Linger result) { assert(0); } + @trusted void getOption(SocketOptionLevel level, SocketOption option, out Duration result) { assert(0); } + @trusted void setOption(SocketOptionLevel level, SocketOption option, void[] value) { assert(0); } + @trusted void setOption(SocketOptionLevel level, SocketOption option, int32_t value) { assert(0); } + @trusted void setOption(SocketOptionLevel level, SocketOption option, Linger value) { assert(0); } + @trusted void setOption(SocketOptionLevel level, SocketOption option, Duration value) { assert(0); } + @safe string getErrorText() { assert(0); } + @trusted void setKeepAlive(int time, int interval) { assert(0); } + protected pure nothrow @safe Address createAddress() { assert(0); } + } + } +} + +/** + * Creates a pair of connected sockets. + * + * The two sockets are indistinguishable. + * + * Throws: $(D SocketException) if creation of the sockets fails. + */ +Socket[2] socketPair() @trusted +{ + version (Posix) + { + int[2] socks; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) == -1) + throw new SocketOSException("Unable to create socket pair"); + + Socket toSocket(size_t id) + { + auto s = new Socket; + s.setSock(cast(socket_t) socks[id]); + s._family = AddressFamily.UNIX; + return s; + } + + return [toSocket(0), toSocket(1)]; + } + else version (Windows) + { + // We do not have socketpair() on Windows, just manually create a + // pair of sockets connected over some localhost port. + Socket[2] result; + + auto listener = new TcpSocket(); + listener.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true); + listener.bind(new InternetAddress(INADDR_LOOPBACK, InternetAddress.PORT_ANY)); + auto addr = listener.localAddress; + listener.listen(1); + + result[0] = new TcpSocket(addr); + result[1] = listener.accept(); + + listener.close(); + return result; + } + else + static assert(false); +} + +/// +@safe unittest +{ + immutable ubyte[] data = [1, 2, 3, 4]; + auto pair = socketPair(); + scope(exit) foreach (s; pair) s.close(); + + pair[0].send(data); + + auto buf = new ubyte[data.length]; + pair[1].receive(buf); + assert(buf == data); +} diff --git a/libphobos/src/std/stdint.d b/libphobos/src/std/stdint.d new file mode 100644 index 0000000..b4a5ff9 --- /dev/null +++ b/libphobos/src/std/stdint.d @@ -0,0 +1,131 @@ +// Written in the D programming language. + +/** + * + D constrains integral types to specific sizes. But efficiency + of different sizes varies from machine to machine, + pointer sizes vary, and the maximum integer size varies. + <b>stdint</b> offers a portable way of trading off size + vs efficiency, in a manner compatible with the <tt>stdint.h</tt> + definitions in C. + + In the table below, the $(B exact alias)es are types of exactly the + specified number of bits. + The $(B at least alias)es are at least the specified number of bits + large, and can be larger. + The $(B fast alias)es are the fastest integral type supported by the + processor that is at least as wide as the specified number of bits. + + The aliases are: + + $(ATABLE $(TR + $(TH Exact Alias) + $(TH Description) + $(TH At Least Alias) + $(TH Description) + $(TH Fast Alias) + $(TH Description) + )$(TR + $(TD int8_t) + $(TD exactly 8 bits signed) + $(TD int_least8_t) + $(TD at least 8 bits signed) + $(TD int_fast8_t) + $(TD fast 8 bits signed) + )$(TR + $(TD uint8_t) + $(TD exactly 8 bits unsigned) + $(TD uint_least8_t) + $(TD at least 8 bits unsigned) + $(TD uint_fast8_t) + $(TD fast 8 bits unsigned) + + )$(TR + $(TD int16_t) + $(TD exactly 16 bits signed) + $(TD int_least16_t) + $(TD at least 16 bits signed) + $(TD int_fast16_t) + $(TD fast 16 bits signed) + )$(TR + $(TD uint16_t) + $(TD exactly 16 bits unsigned) + $(TD uint_least16_t) + $(TD at least 16 bits unsigned) + $(TD uint_fast16_t) + $(TD fast 16 bits unsigned) + + )$(TR + $(TD int32_t) + $(TD exactly 32 bits signed) + $(TD int_least32_t) + $(TD at least 32 bits signed) + $(TD int_fast32_t) + $(TD fast 32 bits signed) + )$(TR + $(TD uint32_t) + $(TD exactly 32 bits unsigned) + $(TD uint_least32_t) + $(TD at least 32 bits unsigned) + $(TD uint_fast32_t) + $(TD fast 32 bits unsigned) + + )$(TR + $(TD int64_t) + $(TD exactly 64 bits signed) + $(TD int_least64_t) + $(TD at least 64 bits signed) + $(TD int_fast64_t) + $(TD fast 64 bits signed) + )$(TR + $(TD uint64_t) + $(TD exactly 64 bits unsigned) + $(TD uint_least64_t) + $(TD at least 64 bits unsigned) + $(TD uint_fast64_t) + $(TD fast 64 bits unsigned) + )) + + The ptr aliases are integral types guaranteed to be large enough + to hold a pointer without losing bits: + + $(ATABLE $(TR + $(TH Alias) + $(TH Description) + )$(TR + $(TD intptr_t) + $(TD signed integral type large enough to hold a pointer) + )$(TR + $(TD uintptr_t) + $(TD unsigned integral type large enough to hold a pointer) + )) + + The max aliases are the largest integral types: + + $(ATABLE $(TR + $(TH Alias) + $(TH Description) + )$(TR + $(TD intmax_t) + $(TD the largest signed integral type) + )$(TR + $(TD uintmax_t) + $(TD the largest unsigned integral type) + )) + + * Macros: + * ATABLE=<table border="1" cellspacing="0" cellpadding="5">$0</table> + * + * Copyright: Copyright Digital Mars 2000 - 2009. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: $(HTTP digitalmars.com, Walter Bright) + * Source: $(PHOBOSSRC std/_stdint.d) + */ +/* Copyright Digital Mars 2000 - 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) + */ +module std.stdint; + +public import core.stdc.stdint; diff --git a/libphobos/src/std/stdio.d b/libphobos/src/std/stdio.d new file mode 100644 index 0000000..9683c98 --- /dev/null +++ b/libphobos/src/std/stdio.d @@ -0,0 +1,5159 @@ +// Written in the D programming language. + +/** +Standard I/O functions that extend $(B core.stdc.stdio). $(B core.stdc.stdio) +is $(D_PARAM public)ally imported when importing $(B std.stdio). + +Source: $(PHOBOSSRC std/_stdio.d) +Copyright: Copyright Digital Mars 2007-. +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). +Authors: $(HTTP digitalmars.com, Walter Bright), + $(HTTP erdani.org, Andrei Alexandrescu), + Alex Rønne Petersen + */ +module std.stdio; + +import core.stdc.stddef; // wchar_t +public import core.stdc.stdio; +import std.algorithm.mutation; // copy +import std.meta; // allSatisfy +import std.range.primitives; // ElementEncodingType, empty, front, + // isBidirectionalRange, isInputRange, put +import std.traits; // isSomeChar, isSomeString, Unqual, isPointer +import std.typecons; // Flag + +/++ +If flag $(D KeepTerminator) is set to $(D KeepTerminator.yes), then the delimiter +is included in the strings returned. ++/ +alias KeepTerminator = Flag!"keepTerminator"; + +version (CRuntime_Microsoft) +{ + version = MICROSOFT_STDIO; +} +else version (CRuntime_DigitalMars) +{ + // Specific to the way Digital Mars C does stdio + version = DIGITAL_MARS_STDIO; +} + +version (CRuntime_Glibc) +{ + // Specific to the way Gnu C does stdio + version = GCC_IO; + version = HAS_GETDELIM; +} + +version (OSX) +{ + version = GENERIC_IO; + version = HAS_GETDELIM; +} + +version (FreeBSD) +{ + version = GENERIC_IO; + version = HAS_GETDELIM; +} + +version (NetBSD) +{ + version = GENERIC_IO; + version = HAS_GETDELIM; +} + +version (Solaris) +{ + version = GENERIC_IO; + version = NO_GETDELIM; +} + +version (CRuntime_Bionic) +{ + version = GENERIC_IO; + version = NO_GETDELIM; +} + +// Character type used for operating system filesystem APIs +version (Windows) +{ + private alias FSChar = wchar; +} +else version (Posix) +{ + private alias FSChar = char; +} +else + static assert(0); + +version (Windows) +{ + // core.stdc.stdio.fopen expects file names to be + // encoded in CP_ACP on Windows instead of UTF-8. + /+ Waiting for druntime pull 299 + +/ + extern (C) nothrow @nogc FILE* _wfopen(in wchar* filename, in wchar* mode); + extern (C) nothrow @nogc FILE* _wfreopen(in wchar* filename, in wchar* mode, FILE* fp); + + import core.sys.windows.windows : HANDLE; +} + +version (DIGITAL_MARS_STDIO) +{ + extern (C) + { + /* ** + * Digital Mars under-the-hood C I/O functions. + * Use _iobuf* for the unshared version of FILE*, + * usable when the FILE is locked. + */ + nothrow: + @nogc: + int _fputc_nlock(int, _iobuf*); + int _fputwc_nlock(int, _iobuf*); + int _fgetc_nlock(_iobuf*); + int _fgetwc_nlock(_iobuf*); + int __fp_lock(FILE*); + void __fp_unlock(FILE*); + + int setmode(int, int); + } + alias FPUTC = _fputc_nlock; + alias FPUTWC = _fputwc_nlock; + alias FGETC = _fgetc_nlock; + alias FGETWC = _fgetwc_nlock; + + alias FLOCK = __fp_lock; + alias FUNLOCK = __fp_unlock; + + alias _setmode = setmode; + enum _O_BINARY = 0x8000; + int _fileno(FILE* f) { return f._file; } + alias fileno = _fileno; +} +else version (MICROSOFT_STDIO) +{ + extern (C) + { + /* ** + * Microsoft under-the-hood C I/O functions + */ + nothrow: + @nogc: + int _fputc_nolock(int, _iobuf*); + int _fputwc_nolock(int, _iobuf*); + int _fgetc_nolock(_iobuf*); + int _fgetwc_nolock(_iobuf*); + void _lock_file(FILE*); + void _unlock_file(FILE*); + int _setmode(int, int); + int _fileno(FILE*); + FILE* _fdopen(int, const (char)*); + int _fseeki64(FILE*, long, int); + long _ftelli64(FILE*); + } + alias FPUTC = _fputc_nolock; + alias FPUTWC = _fputwc_nolock; + alias FGETC = _fgetc_nolock; + alias FGETWC = _fgetwc_nolock; + + alias FLOCK = _lock_file; + alias FUNLOCK = _unlock_file; + + alias setmode = _setmode; + alias fileno = _fileno; + + enum + { + _O_RDONLY = 0x0000, + _O_APPEND = 0x0004, + _O_TEXT = 0x4000, + _O_BINARY = 0x8000, + } +} +else version (GCC_IO) +{ + /* ** + * Gnu under-the-hood C I/O functions; see + * http://gnu.org/software/libc/manual/html_node/I_002fO-on-Streams.html + */ + extern (C) + { + nothrow: + @nogc: + int fputc_unlocked(int, _iobuf*); + int fputwc_unlocked(wchar_t, _iobuf*); + int fgetc_unlocked(_iobuf*); + int fgetwc_unlocked(_iobuf*); + void flockfile(FILE*); + void funlockfile(FILE*); + + private size_t fwrite_unlocked(const(void)* ptr, + size_t size, size_t n, _iobuf *stream); + } + + alias FPUTC = fputc_unlocked; + alias FPUTWC = fputwc_unlocked; + alias FGETC = fgetc_unlocked; + alias FGETWC = fgetwc_unlocked; + + alias FLOCK = flockfile; + alias FUNLOCK = funlockfile; +} +else version (GENERIC_IO) +{ + nothrow: + @nogc: + + extern (C) + { + void flockfile(FILE*); + void funlockfile(FILE*); + } + + int fputc_unlocked(int c, _iobuf* fp) { return fputc(c, cast(shared) fp); } + int fputwc_unlocked(wchar_t c, _iobuf* fp) + { + import core.stdc.wchar_ : fputwc; + return fputwc(c, cast(shared) fp); + } + int fgetc_unlocked(_iobuf* fp) { return fgetc(cast(shared) fp); } + int fgetwc_unlocked(_iobuf* fp) + { + import core.stdc.wchar_ : fgetwc; + return fgetwc(cast(shared) fp); + } + + alias FPUTC = fputc_unlocked; + alias FPUTWC = fputwc_unlocked; + alias FGETC = fgetc_unlocked; + alias FGETWC = fgetwc_unlocked; + + alias FLOCK = flockfile; + alias FUNLOCK = funlockfile; +} +else +{ + static assert(0, "unsupported C I/O system"); +} + +version (HAS_GETDELIM) extern(C) nothrow @nogc +{ + ptrdiff_t getdelim(char**, size_t*, int, FILE*); + // getline() always comes together with getdelim() + ptrdiff_t getline(char**, size_t*, FILE*); +} + +//------------------------------------------------------------------------------ +struct ByRecord(Fields...) +{ +private: + import std.typecons : Tuple; + + File file; + char[] line; + Tuple!(Fields) current; + string format; + +public: + this(File f, string format) + { + assert(f.isOpen); + file = f; + this.format = format; + popFront(); // prime the range + } + + /// Range primitive implementations. + @property bool empty() + { + return !file.isOpen; + } + + /// Ditto + @property ref Tuple!(Fields) front() + { + return current; + } + + /// Ditto + void popFront() + { + import std.conv : text; + import std.exception : enforce; + import std.format : formattedRead; + import std.string : chomp; + + enforce(file.isOpen, "ByRecord: File must be open"); + file.readln(line); + if (!line.length) + { + file.detach(); + } + else + { + line = chomp(line); + formattedRead(line, format, ¤t); + enforce(line.empty, text("Leftover characters in record: `", + line, "'")); + } + } +} + +template byRecord(Fields...) +{ + ByRecord!(Fields) byRecord(File f, string format) + { + return typeof(return)(f, format); + } +} + +/** +Encapsulates a $(D FILE*). Generally D does not attempt to provide +thin wrappers over equivalent functions in the C standard library, but +manipulating $(D FILE*) values directly is unsafe and error-prone in +many ways. The $(D File) type ensures safe manipulation, automatic +file closing, and a lot of convenience. + +The underlying $(D FILE*) handle is maintained in a reference-counted +manner, such that as soon as the last $(D File) variable bound to a +given $(D FILE*) goes out of scope, the underlying $(D FILE*) is +automatically closed. + +Example: +---- +// test.d +void main(string[] args) +{ + auto f = File("test.txt", "w"); // open for writing + f.write("Hello"); + if (args.length > 1) + { + auto g = f; // now g and f write to the same file + // internal reference count is 2 + g.write(", ", args[1]); + // g exits scope, reference count decreases to 1 + } + f.writeln("!"); + // f exits scope, reference count falls to zero, + // underlying `FILE*` is closed. +} +---- +$(CONSOLE +% rdmd test.d Jimmy +% cat test.txt +Hello, Jimmy! +% __ +) + */ +struct File +{ + import std.range.primitives : ElementEncodingType; + import std.traits : isScalarType, isArray; + enum Orientation { unknown, narrow, wide } + + private struct Impl + { + FILE * handle = null; // Is null iff this Impl is closed by another File + uint refs = uint.max / 2; + bool isPopened; // true iff the stream has been created by popen() + Orientation orientation; + } + private Impl* _p; + private string _name; + + package this(FILE* handle, string name, uint refs = 1, bool isPopened = false) @trusted + { + import core.stdc.stdlib : malloc; + import std.exception : enforce; + + assert(!_p); + _p = cast(Impl*) enforce(malloc(Impl.sizeof), "Out of memory"); + _p.handle = handle; + _p.refs = refs; + _p.isPopened = isPopened; + _p.orientation = Orientation.unknown; + _name = name; + } + +/** +Constructor taking the name of the file to open and the open mode. + +Copying one $(D File) object to another results in the two $(D File) +objects referring to the same underlying file. + +The destructor automatically closes the file as soon as no $(D File) +object refers to it anymore. + +Params: + name = range or string representing the file _name + stdioOpenmode = range or string represting the open mode + (with the same semantics as in the C standard library + $(HTTP cplusplus.com/reference/clibrary/cstdio/fopen.html, fopen) + function) + +Throws: $(D ErrnoException) if the file could not be opened. + */ + this(string name, in char[] stdioOpenmode = "rb") @safe + { + import std.conv : text; + import std.exception : errnoEnforce; + + this(errnoEnforce(.fopen(name, stdioOpenmode), + text("Cannot open file `", name, "' in mode `", + stdioOpenmode, "'")), + name); + + // MSVCRT workaround (issue 14422) + version (MICROSOFT_STDIO) + { + bool append, update; + foreach (c; stdioOpenmode) + if (c == 'a') + append = true; + else + if (c == '+') + update = true; + if (append && !update) + seek(size); + } + } + + /// ditto + this(R1, R2)(R1 name) + if (isInputRange!R1 && isSomeChar!(ElementEncodingType!R1)) + { + import std.conv : to; + this(name.to!string, "rb"); + } + + /// ditto + this(R1, R2)(R1 name, R2 mode) + if (isInputRange!R1 && isSomeChar!(ElementEncodingType!R1) && + isInputRange!R2 && isSomeChar!(ElementEncodingType!R2)) + { + import std.conv : to; + this(name.to!string, mode.to!string); + } + + @safe unittest + { + static import std.file; + import std.utf : byChar; + auto deleteme = testFilename(); + auto f = File(deleteme.byChar, "w".byChar); + f.close(); + std.file.remove(deleteme); + } + + ~this() @safe + { + detach(); + } + + this(this) @safe nothrow + { + if (!_p) return; + assert(_p.refs); + ++_p.refs; + } + +/** +Assigns a file to another. The target of the assignment gets detached +from whatever file it was attached to, and attaches itself to the new +file. + */ + void opAssign(File rhs) @safe + { + import std.algorithm.mutation : swap; + + swap(this, rhs); + } + +/** +First calls $(D detach) (throwing on failure), and then attempts to +_open file $(D name) with mode $(D stdioOpenmode). The mode has the +same semantics as in the C standard library $(HTTP +cplusplus.com/reference/clibrary/cstdio/fopen.html, fopen) function. + +Throws: $(D ErrnoException) in case of error. + */ + void open(string name, in char[] stdioOpenmode = "rb") @safe + { + detach(); + this = File(name, stdioOpenmode); + } + +/** +Reuses the `File` object to either open a different file, or change +the file mode. If `name` is `null`, the mode of the currently open +file is changed; otherwise, a new file is opened, reusing the C +`FILE*`. The function has the same semantics as in the C standard +library $(HTTP cplusplus.com/reference/cstdio/freopen/, freopen) +function. + +Note: Calling `reopen` with a `null` `name` is not implemented +in all C runtimes. + +Throws: $(D ErrnoException) in case of error. + */ + void reopen(string name, in char[] stdioOpenmode = "rb") @trusted + { + import std.conv : text; + import std.exception : enforce, errnoEnforce; + import std.internal.cstring : tempCString; + + enforce(isOpen, "Attempting to reopen() an unopened file"); + + auto namez = (name == null ? _name : name).tempCString!FSChar(); + auto modez = stdioOpenmode.tempCString!FSChar(); + + FILE* fd = _p.handle; + version (Windows) + fd = _wfreopen(namez, modez, fd); + else + fd = freopen(namez, modez, fd); + + errnoEnforce(fd, name + ? text("Cannot reopen file `", name, "' in mode `", stdioOpenmode, "'") + : text("Cannot reopen file in mode `", stdioOpenmode, "'")); + + if (name !is null) + _name = name; + } + + @system unittest // Test changing filename + { + import std.exception : assertThrown, assertNotThrown; + static import std.file; + + auto deleteme = testFilename(); + std.file.write(deleteme, "foo"); + scope(exit) std.file.remove(deleteme); + auto f = File(deleteme); + assert(f.readln() == "foo"); + + auto deleteme2 = testFilename(); + std.file.write(deleteme2, "bar"); + scope(exit) std.file.remove(deleteme2); + f.reopen(deleteme2); + assert(f.name == deleteme2); + assert(f.readln() == "bar"); + f.close(); + } + + version (CRuntime_DigitalMars) {} else // Not implemented + version (CRuntime_Microsoft) {} else // Not implemented + @system unittest // Test changing mode + { + import std.exception : assertThrown, assertNotThrown; + static import std.file; + + auto deleteme = testFilename(); + std.file.write(deleteme, "foo"); + scope(exit) std.file.remove(deleteme); + auto f = File(deleteme, "r+"); + assert(f.readln() == "foo"); + f.reopen(null, "w"); + f.write("bar"); + f.seek(0); + f.reopen(null, "a"); + f.write("baz"); + assert(f.name == deleteme); + f.close(); + assert(std.file.readText(deleteme) == "barbaz"); + } + +/** +First calls $(D detach) (throwing on failure), and then runs a command +by calling the C standard library function $(HTTP +opengroup.org/onlinepubs/007908799/xsh/_popen.html, _popen). + +Throws: $(D ErrnoException) in case of error. + */ + version (Posix) void popen(string command, in char[] stdioOpenmode = "r") @safe + { + import std.exception : errnoEnforce; + + detach(); + this = File(errnoEnforce(.popen(command, stdioOpenmode), + "Cannot run command `"~command~"'"), + command, 1, true); + } + +/** +First calls $(D detach) (throwing on failure), and then attempts to +associate the given file descriptor with the $(D File). The mode must +be compatible with the mode of the file descriptor. + +Throws: $(D ErrnoException) in case of error. + */ + void fdopen(int fd, in char[] stdioOpenmode = "rb") @safe + { + fdopen(fd, stdioOpenmode, null); + } + + package void fdopen(int fd, in char[] stdioOpenmode, string name) @trusted + { + import std.exception : errnoEnforce; + import std.internal.cstring : tempCString; + + auto modez = stdioOpenmode.tempCString(); + detach(); + + version (DIGITAL_MARS_STDIO) + { + // This is a re-implementation of DMC's fdopen, but without the + // mucking with the file descriptor. POSIX standard requires the + // new fdopen'd file to retain the given file descriptor's + // position. + import core.stdc.stdio : fopen; + auto fp = fopen("NUL", modez); + errnoEnforce(fp, "Cannot open placeholder NUL stream"); + FLOCK(fp); + auto iob = cast(_iobuf*) fp; + .close(iob._file); + iob._file = fd; + iob._flag &= ~_IOTRAN; + FUNLOCK(fp); + } + else + { + version (Windows) // MSVCRT + auto fp = _fdopen(fd, modez); + else version (Posix) + { + import core.sys.posix.stdio : fdopen; + auto fp = fdopen(fd, modez); + } + errnoEnforce(fp); + } + this = File(fp, name); + } + + // Declare a dummy HANDLE to allow generating documentation + // for Windows-only methods. + version (StdDdoc) { version (Windows) {} else alias HANDLE = int; } + +/** +First calls $(D detach) (throwing on failure), and then attempts to +associate the given Windows $(D HANDLE) with the $(D File). The mode must +be compatible with the access attributes of the handle. Windows only. + +Throws: $(D ErrnoException) in case of error. +*/ + version (StdDdoc) + void windowsHandleOpen(HANDLE handle, in char[] stdioOpenmode); + + version (Windows) + void windowsHandleOpen(HANDLE handle, in char[] stdioOpenmode) + { + import core.stdc.stdint : intptr_t; + import std.exception : errnoEnforce; + import std.format : format; + + // Create file descriptors from the handles + version (DIGITAL_MARS_STDIO) + auto fd = _handleToFD(handle, FHND_DEVICE); + else // MSVCRT + { + int mode; + modeLoop: + foreach (c; stdioOpenmode) + switch (c) + { + case 'r': mode |= _O_RDONLY; break; + case '+': mode &=~_O_RDONLY; break; + case 'a': mode |= _O_APPEND; break; + case 'b': mode |= _O_BINARY; break; + case 't': mode |= _O_TEXT; break; + case ',': break modeLoop; + default: break; + } + + auto fd = _open_osfhandle(cast(intptr_t) handle, mode); + } + + errnoEnforce(fd >= 0, "Cannot open Windows HANDLE"); + fdopen(fd, stdioOpenmode, "HANDLE(%s)".format(handle)); + } + + +/** Returns $(D true) if the file is opened. */ + @property bool isOpen() const @safe pure nothrow + { + return _p !is null && _p.handle; + } + +/** +Returns $(D true) if the file is at end (see $(HTTP +cplusplus.com/reference/clibrary/cstdio/feof.html, feof)). + +Throws: $(D Exception) if the file is not opened. + */ + @property bool eof() const @trusted pure + { + import std.exception : enforce; + + enforce(_p && _p.handle, "Calling eof() against an unopened file."); + return .feof(cast(FILE*) _p.handle) != 0; + } + +/** Returns the name of the last opened file, if any. +If a $(D File) was created with $(LREF tmpfile) and $(LREF wrapFile) +it has no name.*/ + @property string name() const @safe pure nothrow + { + return _name; + } + +/** +If the file is not opened, returns $(D true). Otherwise, returns +$(HTTP cplusplus.com/reference/clibrary/cstdio/ferror.html, ferror) for +the file handle. + */ + @property bool error() const @trusted pure nothrow + { + return !isOpen || .ferror(cast(FILE*) _p.handle); + } + + @safe unittest + { + // Issue 12349 + static import std.file; + auto deleteme = testFilename(); + auto f = File(deleteme, "w"); + scope(exit) std.file.remove(deleteme); + + f.close(); + assert(f.error); + } + +/** +Detaches from the underlying file. If the sole owner, calls $(D close). + +Throws: $(D ErrnoException) on failure if closing the file. + */ + void detach() @safe + { + if (!_p) return; + if (_p.refs == 1) + close(); + else + { + assert(_p.refs); + --_p.refs; + _p = null; + } + } + + @safe unittest + { + static import std.file; + + auto deleteme = testFilename(); + scope(exit) std.file.remove(deleteme); + auto f = File(deleteme, "w"); + { + auto f2 = f; + f2.detach(); + } + assert(f._p.refs == 1); + f.close(); + } + +/** +If the file was unopened, succeeds vacuously. Otherwise closes the +file (by calling $(HTTP +cplusplus.com/reference/clibrary/cstdio/fclose.html, fclose)), +throwing on error. Even if an exception is thrown, afterwards the $(D +File) object is empty. This is different from $(D detach) in that it +always closes the file; consequently, all other $(D File) objects +referring to the same handle will see a closed file henceforth. + +Throws: $(D ErrnoException) on error. + */ + void close() @trusted + { + import core.stdc.stdlib : free; + import std.exception : errnoEnforce; + + if (!_p) return; // succeed vacuously + scope(exit) + { + assert(_p.refs); + if (!--_p.refs) + free(_p); + _p = null; // start a new life + } + if (!_p.handle) return; // Impl is closed by another File + + scope(exit) _p.handle = null; // nullify the handle anyway + version (Posix) + { + import core.sys.posix.stdio : pclose; + import std.format : format; + + if (_p.isPopened) + { + auto res = pclose(_p.handle); + errnoEnforce(res != -1, + "Could not close pipe `"~_name~"'"); + errnoEnforce(res == 0, format("Command returned %d", res)); + return; + } + } + errnoEnforce(.fclose(_p.handle) == 0, + "Could not close file `"~_name~"'"); + } + +/** +If the file is not opened, succeeds vacuously. Otherwise, returns +$(HTTP cplusplus.com/reference/clibrary/cstdio/_clearerr.html, +_clearerr) for the file handle. + */ + void clearerr() @safe pure nothrow + { + _p is null || _p.handle is null || + .clearerr(_p.handle); + } + +/** +Flushes the C $(D FILE) buffers. + +Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_fflush.html, _fflush) +for the file handle. + +Throws: $(D Exception) if the file is not opened or if the call to $(D fflush) fails. + */ + void flush() @trusted + { + import std.exception : enforce, errnoEnforce; + + enforce(isOpen, "Attempting to flush() in an unopened file"); + errnoEnforce(.fflush(_p.handle) == 0); + } + + @safe unittest + { + // Issue 12349 + import std.exception : assertThrown; + static import std.file; + + auto deleteme = testFilename(); + auto f = File(deleteme, "w"); + scope(exit) std.file.remove(deleteme); + + f.close(); + assertThrown(f.flush()); + } + +/** +Forces any data buffered by the OS to be written to disk. +Call $(LREF flush) before calling this function to flush the C $(D FILE) buffers first. + +This function calls +$(HTTP msdn.microsoft.com/en-us/library/windows/desktop/aa364439%28v=vs.85%29.aspx, +$(D FlushFileBuffers)) on Windows and +$(HTTP pubs.opengroup.org/onlinepubs/7908799/xsh/fsync.html, +$(D fsync)) on POSIX for the file handle. + +Throws: $(D Exception) if the file is not opened or if the OS call fails. + */ + void sync() @trusted + { + import std.exception : enforce; + + enforce(isOpen, "Attempting to sync() an unopened file"); + + version (Windows) + { + import core.sys.windows.windows : FlushFileBuffers; + wenforce(FlushFileBuffers(windowsHandle), "FlushFileBuffers failed"); + } + else + { + import core.sys.posix.unistd : fsync; + import std.exception : errnoEnforce; + errnoEnforce(fsync(fileno) == 0, "fsync failed"); + } + } + +/** +Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/fread.html, fread) for the +file handle. The number of items to read and the size of +each item is inferred from the size and type of the input array, respectively. + +Returns: The slice of $(D buffer) containing the data that was actually read. +This will be shorter than $(D buffer) if EOF was reached before the buffer +could be filled. + +Throws: $(D Exception) if $(D buffer) is empty. + $(D ErrnoException) if the file is not opened or the call to $(D fread) fails. + +$(D rawRead) always reads in binary mode on Windows. + */ + T[] rawRead(T)(T[] buffer) + { + import std.exception : errnoEnforce; + + if (!buffer.length) + throw new Exception("rawRead must take a non-empty buffer"); + version (Windows) + { + immutable fd = ._fileno(_p.handle); + immutable mode = ._setmode(fd, _O_BINARY); + scope(exit) ._setmode(fd, mode); + version (DIGITAL_MARS_STDIO) + { + import core.atomic : atomicOp; + + // @@@BUG@@@ 4243 + immutable info = __fhnd_info[fd]; + atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT); + scope(exit) __fhnd_info[fd] = info; + } + } + immutable freadResult = trustedFread(_p.handle, buffer); + assert(freadResult <= buffer.length); // fread return guarantee + if (freadResult != buffer.length) // error or eof + { + errnoEnforce(!error); + return buffer[0 .. freadResult]; + } + return buffer; + } + + /// + @system unittest + { + static import std.file; + + auto testFile = testFilename(); + std.file.write(testFile, "\r\n\n\r\n"); + scope(exit) std.file.remove(testFile); + + auto f = File(testFile, "r"); + auto buf = f.rawRead(new char[5]); + f.close(); + assert(buf == "\r\n\n\r\n"); + } + +/** +Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/fwrite.html, fwrite) for the file +handle. The number of items to write and the size of each +item is inferred from the size and type of the input array, respectively. An +error is thrown if the buffer could not be written in its entirety. + +$(D rawWrite) always writes in binary mode on Windows. + +Throws: $(D ErrnoException) if the file is not opened or if the call to $(D fwrite) fails. + */ + void rawWrite(T)(in T[] buffer) + { + import std.conv : text; + import std.exception : errnoEnforce; + + version (Windows) + { + flush(); // before changing translation mode + immutable fd = ._fileno(_p.handle); + immutable mode = ._setmode(fd, _O_BINARY); + scope(exit) ._setmode(fd, mode); + version (DIGITAL_MARS_STDIO) + { + import core.atomic : atomicOp; + + // @@@BUG@@@ 4243 + immutable info = __fhnd_info[fd]; + atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT); + scope(exit) __fhnd_info[fd] = info; + } + scope(exit) flush(); // before restoring translation mode + } + auto result = trustedFwrite(_p.handle, buffer); + if (result == result.max) result = 0; + errnoEnforce(result == buffer.length, + text("Wrote ", result, " instead of ", buffer.length, + " objects of type ", T.stringof, " to file `", + _name, "'")); + } + + /// + @system unittest + { + static import std.file; + + auto testFile = testFilename(); + auto f = File(testFile, "w"); + scope(exit) std.file.remove(testFile); + + f.rawWrite("\r\n\n\r\n"); + f.close(); + assert(std.file.read(testFile) == "\r\n\n\r\n"); + } + +/** +Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/fseek.html, fseek) +for the file handle. + +Throws: $(D Exception) if the file is not opened. + $(D ErrnoException) if the call to $(D fseek) fails. + */ + void seek(long offset, int origin = SEEK_SET) @trusted + { + import std.conv : to, text; + import std.exception : enforce, errnoEnforce; + + enforce(isOpen, "Attempting to seek() in an unopened file"); + version (Windows) + { + version (CRuntime_Microsoft) + { + alias fseekFun = _fseeki64; + alias off_t = long; + } + else + { + alias fseekFun = fseek; + alias off_t = int; + } + } + else version (Posix) + { + import core.sys.posix.stdio : fseeko, off_t; + alias fseekFun = fseeko; + } + errnoEnforce(fseekFun(_p.handle, to!off_t(offset), origin) == 0, + "Could not seek in file `"~_name~"'"); + } + + @system unittest + { + import std.conv : text; + static import std.file; + + auto deleteme = testFilename(); + auto f = File(deleteme, "w+"); + scope(exit) { f.close(); std.file.remove(deleteme); } + f.rawWrite("abcdefghijklmnopqrstuvwxyz"); + f.seek(7); + assert(f.readln() == "hijklmnopqrstuvwxyz"); + + version (CRuntime_DigitalMars) + auto bigOffset = int.max - 100; + else + version (CRuntime_Bionic) + auto bigOffset = int.max - 100; + else + auto bigOffset = cast(ulong) int.max + 100; + f.seek(bigOffset); + assert(f.tell == bigOffset, text(f.tell)); + // Uncomment the tests below only if you want to wait for + // a long time + // f.rawWrite("abcdefghijklmnopqrstuvwxyz"); + // f.seek(-3, SEEK_END); + // assert(f.readln() == "xyz"); + } + +/** +Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/ftell.html, ftell) for the +managed file handle. + +Throws: $(D Exception) if the file is not opened. + $(D ErrnoException) if the call to $(D ftell) fails. + */ + @property ulong tell() const @trusted + { + import std.exception : enforce, errnoEnforce; + + enforce(isOpen, "Attempting to tell() in an unopened file"); + version (Windows) + { + version (CRuntime_Microsoft) + immutable result = _ftelli64(cast(FILE*) _p.handle); + else + immutable result = ftell(cast(FILE*) _p.handle); + } + else version (Posix) + { + import core.sys.posix.stdio : ftello; + immutable result = ftello(cast(FILE*) _p.handle); + } + errnoEnforce(result != -1, + "Query ftell() failed for file `"~_name~"'"); + return result; + } + + /// + @system unittest + { + import std.conv : text; + static import std.file; + + auto testFile = testFilename(); + std.file.write(testFile, "abcdefghijklmnopqrstuvwqxyz"); + scope(exit) { std.file.remove(testFile); } + + auto f = File(testFile); + auto a = new ubyte[4]; + f.rawRead(a); + assert(f.tell == 4, text(f.tell)); + } + +/** +Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_rewind.html, _rewind) +for the file handle. + +Throws: $(D Exception) if the file is not opened. + */ + void rewind() @safe + { + import std.exception : enforce; + + enforce(isOpen, "Attempting to rewind() an unopened file"); + .rewind(_p.handle); + } + +/** +Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_setvbuf.html, _setvbuf) for +the file handle. + +Throws: $(D Exception) if the file is not opened. + $(D ErrnoException) if the call to $(D setvbuf) fails. + */ + void setvbuf(size_t size, int mode = _IOFBF) @trusted + { + import std.exception : enforce, errnoEnforce; + + enforce(isOpen, "Attempting to call setvbuf() on an unopened file"); + errnoEnforce(.setvbuf(_p.handle, null, mode, size) == 0, + "Could not set buffering for file `"~_name~"'"); + } + +/** +Calls $(HTTP cplusplus.com/reference/clibrary/cstdio/_setvbuf.html, +_setvbuf) for the file handle. + +Throws: $(D Exception) if the file is not opened. + $(D ErrnoException) if the call to $(D setvbuf) fails. +*/ + void setvbuf(void[] buf, int mode = _IOFBF) @trusted + { + import std.exception : enforce, errnoEnforce; + + enforce(isOpen, "Attempting to call setvbuf() on an unopened file"); + errnoEnforce(.setvbuf(_p.handle, + cast(char*) buf.ptr, mode, buf.length) == 0, + "Could not set buffering for file `"~_name~"'"); + } + + + version (Windows) + { + import core.sys.windows.windows : ULARGE_INTEGER, OVERLAPPED, BOOL; + + private BOOL lockImpl(alias F, Flags...)(ulong start, ulong length, + Flags flags) + { + if (!start && !length) + length = ulong.max; + ULARGE_INTEGER liStart = void, liLength = void; + liStart.QuadPart = start; + liLength.QuadPart = length; + OVERLAPPED overlapped; + overlapped.Offset = liStart.LowPart; + overlapped.OffsetHigh = liStart.HighPart; + overlapped.hEvent = null; + return F(windowsHandle, flags, 0, liLength.LowPart, + liLength.HighPart, &overlapped); + } + + private static T wenforce(T)(T cond, string str) + { + import core.sys.windows.windows : GetLastError; + import std.windows.syserror : sysErrorString; + + if (cond) return cond; + throw new Exception(str ~ ": " ~ sysErrorString(GetLastError())); + } + } + version (Posix) + { + private int lockImpl(int operation, short l_type, + ulong start, ulong length) + { + import core.sys.posix.fcntl : fcntl, flock, off_t; + import core.sys.posix.unistd : getpid; + import std.conv : to; + + flock fl = void; + fl.l_type = l_type; + fl.l_whence = SEEK_SET; + fl.l_start = to!off_t(start); + fl.l_len = to!off_t(length); + fl.l_pid = getpid(); + return fcntl(fileno, operation, &fl); + } + } + +/** +Locks the specified file segment. If the file segment is already locked +by another process, waits until the existing lock is released. +If both $(D start) and $(D length) are zero, the entire file is locked. + +Locks created using $(D lock) and $(D tryLock) have the following properties: +$(UL + $(LI All locks are automatically released when the process terminates.) + $(LI Locks are not inherited by child processes.) + $(LI Closing a file will release all locks associated with the file. On POSIX, + even locks acquired via a different $(D File) will be released as well.) + $(LI Not all NFS implementations correctly implement file locking.) +) + */ + void lock(LockType lockType = LockType.readWrite, + ulong start = 0, ulong length = 0) + { + import std.exception : enforce; + + enforce(isOpen, "Attempting to call lock() on an unopened file"); + version (Posix) + { + import core.sys.posix.fcntl : F_RDLCK, F_SETLKW, F_WRLCK; + import std.exception : errnoEnforce; + immutable short type = lockType == LockType.readWrite + ? F_WRLCK : F_RDLCK; + errnoEnforce(lockImpl(F_SETLKW, type, start, length) != -1, + "Could not set lock for file `"~_name~"'"); + } + else + version (Windows) + { + import core.sys.windows.windows : LockFileEx, LOCKFILE_EXCLUSIVE_LOCK; + immutable type = lockType == LockType.readWrite ? + LOCKFILE_EXCLUSIVE_LOCK : 0; + wenforce(lockImpl!LockFileEx(start, length, type), + "Could not set lock for file `"~_name~"'"); + } + else + static assert(false); + } + +/** +Attempts to lock the specified file segment. +If both $(D start) and $(D length) are zero, the entire file is locked. +Returns: $(D true) if the lock was successful, and $(D false) if the +specified file segment was already locked. + */ + bool tryLock(LockType lockType = LockType.readWrite, + ulong start = 0, ulong length = 0) + { + import std.exception : enforce; + + enforce(isOpen, "Attempting to call tryLock() on an unopened file"); + version (Posix) + { + import core.stdc.errno : EACCES, EAGAIN, errno; + import core.sys.posix.fcntl : F_RDLCK, F_SETLK, F_WRLCK; + import std.exception : errnoEnforce; + immutable short type = lockType == LockType.readWrite + ? F_WRLCK : F_RDLCK; + immutable res = lockImpl(F_SETLK, type, start, length); + if (res == -1 && (errno == EACCES || errno == EAGAIN)) + return false; + errnoEnforce(res != -1, "Could not set lock for file `"~_name~"'"); + return true; + } + else + version (Windows) + { + import core.sys.windows.windows : GetLastError, LockFileEx, LOCKFILE_EXCLUSIVE_LOCK, + ERROR_IO_PENDING, ERROR_LOCK_VIOLATION, LOCKFILE_FAIL_IMMEDIATELY; + immutable type = lockType == LockType.readWrite + ? LOCKFILE_EXCLUSIVE_LOCK : 0; + immutable res = lockImpl!LockFileEx(start, length, + type | LOCKFILE_FAIL_IMMEDIATELY); + if (!res && (GetLastError() == ERROR_IO_PENDING + || GetLastError() == ERROR_LOCK_VIOLATION)) + return false; + wenforce(res, "Could not set lock for file `"~_name~"'"); + return true; + } + else + static assert(false); + } + +/** +Removes the lock over the specified file segment. + */ + void unlock(ulong start = 0, ulong length = 0) + { + import std.exception : enforce; + + enforce(isOpen, "Attempting to call unlock() on an unopened file"); + version (Posix) + { + import core.sys.posix.fcntl : F_SETLK, F_UNLCK; + import std.exception : errnoEnforce; + errnoEnforce(lockImpl(F_SETLK, F_UNLCK, start, length) != -1, + "Could not remove lock for file `"~_name~"'"); + } + else + version (Windows) + { + import core.sys.windows.windows : UnlockFileEx; + wenforce(lockImpl!UnlockFileEx(start, length), + "Could not remove lock for file `"~_name~"'"); + } + else + static assert(false); + } + + version (Windows) + @system unittest + { + static import std.file; + auto deleteme = testFilename(); + scope(exit) std.file.remove(deleteme); + auto f = File(deleteme, "wb"); + assert(f.tryLock()); + auto g = File(deleteme, "wb"); + assert(!g.tryLock()); + assert(!g.tryLock(LockType.read)); + f.unlock(); + f.lock(LockType.read); + assert(!g.tryLock()); + assert(g.tryLock(LockType.read)); + f.unlock(); + g.unlock(); + } + + version (Posix) + @system unittest + { + static import std.file; + auto deleteme = testFilename(); + scope(exit) std.file.remove(deleteme); + + // Since locks are per-process, we cannot test lock failures within + // the same process. fork() is used to create a second process. + static void runForked(void delegate() code) + { + import core.stdc.stdlib : exit; + import core.sys.posix.sys.wait : wait; + import core.sys.posix.unistd : fork; + int child, status; + if ((child = fork()) == 0) + { + code(); + exit(0); + } + else + { + assert(wait(&status) != -1); + assert(status == 0, "Fork crashed"); + } + } + + auto f = File(deleteme, "w+b"); + + runForked + ({ + auto g = File(deleteme, "a+b"); + assert(g.tryLock()); + g.unlock(); + assert(g.tryLock(LockType.read)); + }); + + assert(f.tryLock()); + runForked + ({ + auto g = File(deleteme, "a+b"); + assert(!g.tryLock()); + assert(!g.tryLock(LockType.read)); + }); + f.unlock(); + + f.lock(LockType.read); + runForked + ({ + auto g = File(deleteme, "a+b"); + assert(!g.tryLock()); + assert(g.tryLock(LockType.read)); + g.unlock(); + }); + f.unlock(); + } + + +/** +Writes its arguments in text format to the file. + +Throws: $(D Exception) if the file is not opened. + $(D ErrnoException) on an error writing to the file. +*/ + void write(S...)(S args) + { + import std.traits : isBoolean, isIntegral, isAggregateType; + auto w = lockingTextWriter(); + foreach (arg; args) + { + alias A = typeof(arg); + static if (isAggregateType!A || is(A == enum)) + { + import std.format : formattedWrite; + + formattedWrite(w, "%s", arg); + } + else static if (isSomeString!A) + { + put(w, arg); + } + else static if (isIntegral!A) + { + import std.conv : toTextRange; + + toTextRange(arg, w); + } + else static if (isBoolean!A) + { + put(w, arg ? "true" : "false"); + } + else static if (isSomeChar!A) + { + put(w, arg); + } + else + { + import std.format : formattedWrite; + + // Most general case + formattedWrite(w, "%s", arg); + } + } + } + +/** +Writes its arguments in text format to the file, followed by a newline. + +Throws: $(D Exception) if the file is not opened. + $(D ErrnoException) on an error writing to the file. +*/ + void writeln(S...)(S args) + { + write(args, '\n'); + } + +/** +Writes its arguments in text format to the file, according to the +format string fmt. + +Params: +fmt = The $(LINK2 std_format.html#format-string, format string). +When passed as a compile-time argument, the string will be statically checked +against the argument types passed. +args = Items to write. + +Throws: $(D Exception) if the file is not opened. + $(D ErrnoException) on an error writing to the file. +*/ + void writef(alias fmt, A...)(A args) + if (isSomeString!(typeof(fmt))) + { + import std.format : checkFormatException; + + alias e = checkFormatException!(fmt, A); + static assert(!e, e.msg); + return this.writef(fmt, args); + } + + /// ditto + void writef(Char, A...)(in Char[] fmt, A args) + { + import std.format : formattedWrite; + + formattedWrite(lockingTextWriter(), fmt, args); + } + + /// Equivalent to `file.writef(fmt, args, '\n')`. + void writefln(alias fmt, A...)(A args) + if (isSomeString!(typeof(fmt))) + { + import std.format : checkFormatException; + + alias e = checkFormatException!(fmt, A); + static assert(!e, e.msg); + return this.writefln(fmt, args); + } + + /// ditto + void writefln(Char, A...)(in Char[] fmt, A args) + { + import std.format : formattedWrite; + + auto w = lockingTextWriter(); + formattedWrite(w, fmt, args); + w.put('\n'); + } + +/** +Read line from the file handle and return it as a specified type. + +This version manages its own read buffer, which means one memory allocation per call. If you are not +retaining a reference to the read data, consider the $(D File.readln(buf)) version, which may offer +better performance as it can reuse its read buffer. + +Params: + S = Template parameter; the type of the allocated buffer, and the type returned. Defaults to $(D string). + terminator = Line terminator (by default, $(D '\n')). + +Note: + String terminators are not supported due to ambiguity with readln(buf) below. + +Returns: + The line that was read, including the line terminator character. + +Throws: + $(D StdioException) on I/O error, or $(D UnicodeException) on Unicode conversion error. + +Example: +--- +// Reads `stdin` and writes it to `stdout`. +import std.stdio; + +void main() +{ + string line; + while ((line = stdin.readln()) !is null) + write(line); +} +--- +*/ + S readln(S = string)(dchar terminator = '\n') + if (isSomeString!S) + { + Unqual!(ElementEncodingType!S)[] buf; + readln(buf, terminator); + return cast(S) buf; + } + + @system unittest + { + import std.algorithm.comparison : equal; + static import std.file; + import std.meta : AliasSeq; + + auto deleteme = testFilename(); + std.file.write(deleteme, "hello\nworld\n"); + scope(exit) std.file.remove(deleteme); + foreach (String; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[])) + { + auto witness = [ "hello\n", "world\n" ]; + auto f = File(deleteme); + uint i = 0; + String buf; + while ((buf = f.readln!String()).length) + { + assert(i < witness.length); + assert(equal(buf, witness[i++])); + } + assert(i == witness.length); + } + } + + @system unittest + { + static import std.file; + import std.typecons : Tuple; + + auto deleteme = testFilename(); + std.file.write(deleteme, "cześć \U0002000D"); + scope(exit) std.file.remove(deleteme); + uint[] lengths = [12,8,7]; + foreach (uint i, C; Tuple!(char, wchar, dchar).Types) + { + immutable(C)[] witness = "cześć \U0002000D"; + auto buf = File(deleteme).readln!(immutable(C)[])(); + assert(buf.length == lengths[i]); + assert(buf == witness); + } + } + +/** +Read line from the file handle and write it to $(D buf[]), including +terminating character. + +This can be faster than $(D line = File.readln()) because you can reuse +the buffer for each call. Note that reusing the buffer means that you +must copy the previous contents if you wish to retain them. + +Params: +buf = Buffer used to store the resulting line data. buf is +resized as necessary. +terminator = Line terminator (by default, $(D '\n')). Use +$(REF newline, std,ascii) for portability (unless the file was opened in +text mode). + +Returns: +0 for end of file, otherwise number of characters read + +Throws: $(D StdioException) on I/O error, or $(D UnicodeException) on Unicode +conversion error. + +Example: +--- +// Read lines from `stdin` into a string +// Ignore lines starting with '#' +// Write the string to `stdout` + +void main() +{ + string output; + char[] buf; + + while (stdin.readln(buf)) + { + if (buf[0] == '#') + continue; + + output ~= buf; + } + + write(output); +} +--- + +This method can be more efficient than the one in the previous example +because $(D stdin.readln(buf)) reuses (if possible) memory allocated +for $(D buf), whereas $(D line = stdin.readln()) makes a new memory allocation +for every line. + +For even better performance you can help $(D readln) by passing in a +large buffer to avoid memory reallocations. This can be done by reusing the +largest buffer returned by $(D readln): + +Example: +--- +// Read lines from `stdin` and count words + +void main() +{ + char[] buf; + size_t words = 0; + + while (!stdin.eof) + { + char[] line = buf; + stdin.readln(line); + if (line.length > buf.length) + buf = line; + + words += line.split.length; + } + + writeln(words); +} +--- +This is actually what $(LREF byLine) does internally, so its usage +is recommended if you want to process a complete file. +*/ + size_t readln(C)(ref C[] buf, dchar terminator = '\n') + if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum)) + { + import std.exception : enforce; + + static if (is(C == char)) + { + enforce(_p && _p.handle, "Attempt to read from an unopened file."); + if (_p.orientation == Orientation.unknown) + { + import core.stdc.wchar_ : fwide; + auto w = fwide(_p.handle, 0); + if (w < 0) _p.orientation = Orientation.narrow; + else if (w > 0) _p.orientation = Orientation.wide; + } + return readlnImpl(_p.handle, buf, terminator, _p.orientation); + } + else + { + // TODO: optimize this + string s = readln(terminator); + buf.length = 0; + if (!s.length) return 0; + foreach (C c; s) + { + buf ~= c; + } + return buf.length; + } + } + + @system unittest + { + // @system due to readln + static import std.file; + auto deleteme = testFilename(); + std.file.write(deleteme, "123\n456789"); + scope(exit) std.file.remove(deleteme); + + auto file = File(deleteme); + char[] buffer = new char[10]; + char[] line = buffer; + file.readln(line); + auto beyond = line.length; + buffer[beyond] = 'a'; + file.readln(line); // should not write buffer beyond line + assert(buffer[beyond] == 'a'); + } + + @system unittest // bugzilla 15293 + { + // @system due to readln + static import std.file; + auto deleteme = testFilename(); + std.file.write(deleteme, "a\n\naa"); + scope(exit) std.file.remove(deleteme); + + auto file = File(deleteme); + char[] buffer; + char[] line; + + file.readln(buffer, '\n'); + + line = buffer; + file.readln(line, '\n'); + + line = buffer; + file.readln(line, '\n'); + + assert(line[0 .. 1].capacity == 0); + } + +/** ditto */ + size_t readln(C, R)(ref C[] buf, R terminator) + if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum) && + isBidirectionalRange!R && is(typeof(terminator.front == dchar.init))) + { + import std.algorithm.mutation : swap; + import std.algorithm.searching : endsWith; + import std.range.primitives : back; + + auto last = terminator.back; + C[] buf2; + swap(buf, buf2); + for (;;) + { + if (!readln(buf2, last) || endsWith(buf2, terminator)) + { + if (buf.empty) + { + buf = buf2; + } + else + { + buf ~= buf2; + } + break; + } + buf ~= buf2; + } + return buf.length; + } + + @system unittest + { + static import std.file; + import std.typecons : Tuple; + + auto deleteme = testFilename(); + std.file.write(deleteme, "hello\n\rworld\nhow\n\rare ya"); + scope(exit) std.file.remove(deleteme); + foreach (C; Tuple!(char, wchar, dchar).Types) + { + immutable(C)[][] witness = [ "hello\n\r", "world\nhow\n\r", "are ya" ]; + auto f = File(deleteme); + uint i = 0; + C[] buf; + while (f.readln(buf, "\n\r")) + { + assert(i < witness.length); + assert(buf == witness[i++]); + } + assert(buf.length == 0); + } + } + + /** + * Reads formatted _data from the file using $(REF formattedRead, std,_format). + * Params: + * format = The $(LINK2 std_format.html#_format-string, _format string). + * When passed as a compile-time argument, the string will be statically checked + * against the argument types passed. + * data = Items to be read. + * Example: +---- +// test.d +void main() +{ + import std.stdio; + auto f = File("input"); + foreach (_; 0 .. 3) + { + int a; + f.readf!" %d"(a); + writeln(++a); + } +} +---- +$(CONSOLE +% echo "1 2 3" > input +% rdmd test.d +2 +3 +4 +) + */ + uint readf(alias format, Data...)(auto ref Data data) + if (isSomeString!(typeof(format))) + { + import std.format : checkFormatException; + + alias e = checkFormatException!(format, Data); + static assert(!e, e.msg); + return this.readf(format, data); + } + + /// ditto + uint readf(Data...)(in char[] format, auto ref Data data) + { + import std.format : formattedRead; + + assert(isOpen); + auto input = LockingTextReader(this); + return formattedRead(input, format, data); + } + + /// + @system unittest + { + static import std.file; + + auto deleteme = testFilename(); + std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); + scope(exit) std.file.remove(deleteme); + string s; + auto f = File(deleteme); + f.readf!"%s\n"(s); + assert(s == "hello", "["~s~"]"); + f.readf("%s\n", s); + assert(s == "world", "["~s~"]"); + + bool b1, b2; + f.readf("%s\n%s\n", b1, b2); + assert(b1 == true && b2 == false); + } + + // backwards compatibility with pointers + @system unittest + { + // @system due to readf + static import std.file; + + auto deleteme = testFilename(); + std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); + scope(exit) std.file.remove(deleteme); + string s; + auto f = File(deleteme); + f.readf("%s\n", &s); + assert(s == "hello", "["~s~"]"); + f.readf("%s\n", &s); + assert(s == "world", "["~s~"]"); + + // Issue 11698 + bool b1, b2; + f.readf("%s\n%s\n", &b1, &b2); + assert(b1 == true && b2 == false); + } + + // backwards compatibility (mixed) + @system unittest + { + // @system due to readf + static import std.file; + + auto deleteme = testFilename(); + std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n"); + scope(exit) std.file.remove(deleteme); + string s1, s2; + auto f = File(deleteme); + f.readf("%s\n%s\n", s1, &s2); + assert(s1 == "hello"); + assert(s2 == "world"); + + // Issue 11698 + bool b1, b2; + f.readf("%s\n%s\n", &b1, b2); + assert(b1 == true && b2 == false); + } + + // Issue 12260 - Nice error of std.stdio.readf with newlines + @system unittest + { + static import std.file; + + auto deleteme = testFilename(); + std.file.write(deleteme, "1\n2"); + scope(exit) std.file.remove(deleteme); + int input; + auto f = File(deleteme); + f.readf("%s", &input); + + import std.conv : ConvException; + import std.exception : collectException; + assert(collectException!ConvException(f.readf("%s", &input)).msg == + "Unexpected '\\n' when converting from type LockingTextReader to type int"); + } + +/** + Returns a temporary file by calling + $(HTTP cplusplus.com/reference/clibrary/cstdio/_tmpfile.html, _tmpfile). + Note that the created file has no $(LREF name).*/ + static File tmpfile() @safe + { + import std.exception : errnoEnforce; + + return File(errnoEnforce(.tmpfile(), + "Could not create temporary file with tmpfile()"), + null); + } + +/** +Unsafe function that wraps an existing $(D FILE*). The resulting $(D +File) never takes the initiative in closing the file. +Note that the created file has no $(LREF name)*/ + /*private*/ static File wrapFile(FILE* f) @safe + { + import std.exception : enforce; + + return File(enforce(f, "Could not wrap null FILE*"), + null, /*uint.max / 2*/ 9999); + } + +/** +Returns the $(D FILE*) corresponding to this object. + */ + FILE* getFP() @safe pure + { + import std.exception : enforce; + + enforce(_p && _p.handle, + "Attempting to call getFP() on an unopened file"); + return _p.handle; + } + + @system unittest + { + static import core.stdc.stdio; + assert(stdout.getFP() == core.stdc.stdio.stdout); + } + +/** +Returns the file number corresponding to this object. + */ + @property int fileno() const @trusted + { + import std.exception : enforce; + + enforce(isOpen, "Attempting to call fileno() on an unopened file"); + return .fileno(cast(FILE*) _p.handle); + } + +/** +Returns the underlying operating system $(D HANDLE) (Windows only). +*/ + version (StdDdoc) + @property HANDLE windowsHandle(); + + version (Windows) + @property HANDLE windowsHandle() + { + version (DIGITAL_MARS_STDIO) + return _fdToHandle(fileno); + else + return cast(HANDLE)_get_osfhandle(fileno); + } + + +// Note: This was documented until 2013/08 +/* +Range that reads one line at a time. Returned by $(LREF byLine). + +Allows to directly use range operations on lines of a file. +*/ + struct ByLine(Char, Terminator) + { + private: + import std.typecons : RefCounted, RefCountedAutoInitialize; + + /* Ref-counting stops the source range's Impl + * from getting out of sync after the range is copied, e.g. + * when accessing range.front, then using std.range.take, + * then accessing range.front again. */ + alias PImpl = RefCounted!(Impl, RefCountedAutoInitialize.no); + PImpl impl; + + static if (isScalarType!Terminator) + enum defTerm = '\n'; + else + enum defTerm = cast(Terminator)"\n"; + + public: + this(File f, KeepTerminator kt = No.keepTerminator, + Terminator terminator = defTerm) + { + impl = PImpl(f, kt, terminator); + } + + @property bool empty() + { + return impl.refCountedPayload.empty; + } + + @property Char[] front() + { + return impl.refCountedPayload.front; + } + + void popFront() + { + impl.refCountedPayload.popFront(); + } + + private: + struct Impl + { + private: + File file; + Char[] line; + Char[] buffer; + Terminator terminator; + KeepTerminator keepTerminator; + + public: + this(File f, KeepTerminator kt, Terminator terminator) + { + file = f; + this.terminator = terminator; + keepTerminator = kt; + popFront(); + } + + // Range primitive implementations. + @property bool empty() + { + return line is null; + } + + @property Char[] front() + { + return line; + } + + void popFront() + { + import std.algorithm.searching : endsWith; + assert(file.isOpen); + line = buffer; + file.readln(line, terminator); + if (line.length > buffer.length) + { + buffer = line; + } + if (line.empty) + { + file.detach(); + line = null; + } + else if (keepTerminator == No.keepTerminator + && endsWith(line, terminator)) + { + static if (isScalarType!Terminator) + enum tlen = 1; + else static if (isArray!Terminator) + { + static assert( + is(Unqual!(ElementEncodingType!Terminator) == Char)); + const tlen = terminator.length; + } + else + static assert(false); + line = line[0 .. line.length - tlen]; + } + } + } + } + +/** +Returns an input range set up to read from the file handle one line +at a time. + +The element type for the range will be $(D Char[]). Range primitives +may throw $(D StdioException) on I/O error. + +Note: +Each $(D front) will not persist after $(D +popFront) is called, so the caller must copy its contents (e.g. by +calling $(D to!string)) when retention is needed. If the caller needs +to retain a copy of every line, use the $(LREF byLineCopy) function +instead. + +Params: +Char = Character type for each line, defaulting to $(D char). +keepTerminator = Use $(D Yes.keepTerminator) to include the +terminator at the end of each line. +terminator = Line separator ($(D '\n') by default). Use +$(REF newline, std,ascii) for portability (unless the file was opened in +text mode). + +Example: +---- +import std.algorithm, std.stdio, std.string; +// Count words in a file using ranges. +void main() +{ + auto file = File("file.txt"); // Open for reading + const wordCount = file.byLine() // Read lines + .map!split // Split into words + .map!(a => a.length) // Count words per line + .sum(); // Total word count + writeln(wordCount); +} +---- + +Example: +---- +import std.range, std.stdio; +// Read lines using foreach. +void main() +{ + auto file = File("file.txt"); // Open for reading + auto range = file.byLine(); + // Print first three lines + foreach (line; range.take(3)) + writeln(line); + // Print remaining lines beginning with '#' + foreach (line; range) + { + if (!line.empty && line[0] == '#') + writeln(line); + } +} +---- +Notice that neither example accesses the line data returned by +$(D front) after the corresponding $(D popFront) call is made (because +the contents may well have changed). +*/ + auto byLine(Terminator = char, Char = char) + (KeepTerminator keepTerminator = No.keepTerminator, + Terminator terminator = '\n') + if (isScalarType!Terminator) + { + return ByLine!(Char, Terminator)(this, keepTerminator, terminator); + } + +/// ditto + auto byLine(Terminator, Char = char) + (KeepTerminator keepTerminator, Terminator terminator) + if (is(Unqual!(ElementEncodingType!Terminator) == Char)) + { + return ByLine!(Char, Terminator)(this, keepTerminator, terminator); + } + + @system unittest + { + static import std.file; + auto deleteme = testFilename(); + std.file.write(deleteme, "hi"); + scope(success) std.file.remove(deleteme); + + import std.meta : AliasSeq; + foreach (T; AliasSeq!(char, wchar, dchar)) + { + auto blc = File(deleteme).byLine!(T, T); + assert(blc.front == "hi"); + // check front is cached + assert(blc.front is blc.front); + } + } + + private struct ByLineCopy(Char, Terminator) + { + private: + import std.typecons : RefCounted, RefCountedAutoInitialize; + + /* Ref-counting stops the source range's ByLineCopyImpl + * from getting out of sync after the range is copied, e.g. + * when accessing range.front, then using std.range.take, + * then accessing range.front again. */ + alias Impl = RefCounted!(ByLineCopyImpl!(Char, Terminator), + RefCountedAutoInitialize.no); + Impl impl; + + public: + this(File f, KeepTerminator kt, Terminator terminator) + { + impl = Impl(f, kt, terminator); + } + + @property bool empty() + { + return impl.refCountedPayload.empty; + } + + @property Char[] front() + { + return impl.refCountedPayload.front; + } + + void popFront() + { + impl.refCountedPayload.popFront(); + } + } + + private struct ByLineCopyImpl(Char, Terminator) + { + ByLine!(Unqual!Char, Terminator).Impl impl; + bool gotFront; + Char[] line; + + public: + this(File f, KeepTerminator kt, Terminator terminator) + { + impl = ByLine!(Unqual!Char, Terminator).Impl(f, kt, terminator); + } + + @property bool empty() + { + return impl.empty; + } + + @property front() + { + if (!gotFront) + { + line = impl.front.dup; + gotFront = true; + } + return line; + } + + void popFront() + { + impl.popFront(); + gotFront = false; + } + } + +/** +Returns an input range set up to read from the file handle one line +at a time. Each line will be newly allocated. $(D front) will cache +its value to allow repeated calls without unnecessary allocations. + +Note: Due to caching byLineCopy can be more memory-efficient than +$(D File.byLine.map!idup). + +The element type for the range will be $(D Char[]). Range +primitives may throw $(D StdioException) on I/O error. + +Params: +Char = Character type for each line, defaulting to $(D immutable char). +keepTerminator = Use $(D Yes.keepTerminator) to include the +terminator at the end of each line. +terminator = Line separator ($(D '\n') by default). Use +$(REF newline, std,ascii) for portability (unless the file was opened in +text mode). + +Example: +---- +import std.algorithm, std.array, std.stdio; +// Print sorted lines of a file. +void main() +{ + auto sortedLines = File("file.txt") // Open for reading + .byLineCopy() // Read persistent lines + .array() // into an array + .sort(); // then sort them + foreach (line; sortedLines) + writeln(line); +} +---- +See_Also: +$(REF readText, std,file) +*/ + auto byLineCopy(Terminator = char, Char = immutable char) + (KeepTerminator keepTerminator = No.keepTerminator, + Terminator terminator = '\n') + if (isScalarType!Terminator) + { + return ByLineCopy!(Char, Terminator)(this, keepTerminator, terminator); + } + +/// ditto + auto byLineCopy(Terminator, Char = immutable char) + (KeepTerminator keepTerminator, Terminator terminator) + if (is(Unqual!(ElementEncodingType!Terminator) == Unqual!Char)) + { + return ByLineCopy!(Char, Terminator)(this, keepTerminator, terminator); + } + + @safe unittest + { + static assert(is(typeof(File("").byLine.front) == char[])); + static assert(is(typeof(File("").byLineCopy.front) == string)); + static assert( + is(typeof(File("").byLineCopy!(char, char).front) == char[])); + } + + @system unittest + { + import std.algorithm.comparison : equal; + static import std.file; + + scope(failure) printf("Failed test at line %d\n", __LINE__); + auto deleteme = testFilename(); + std.file.write(deleteme, ""); + scope(success) std.file.remove(deleteme); + + // Test empty file + auto f = File(deleteme); + foreach (line; f.byLine()) + { + assert(false); + } + f.detach(); + assert(!f.isOpen); + + void test(Terminator)(string txt, in string[] witness, + KeepTerminator kt, Terminator term, bool popFirstLine = false) + { + import std.algorithm.sorting : sort; + import std.array : array; + import std.conv : text; + import std.range.primitives : walkLength; + + uint i; + std.file.write(deleteme, txt); + auto f = File(deleteme); + scope(exit) + { + f.close(); + assert(!f.isOpen); + } + auto lines = f.byLine(kt, term); + if (popFirstLine) + { + lines.popFront(); + i = 1; + } + assert(lines.empty || lines.front is lines.front); + foreach (line; lines) + { + assert(line == witness[i++]); + } + assert(i == witness.length, text(i, " != ", witness.length)); + + // Issue 11830 + auto walkedLength = File(deleteme).byLine(kt, term).walkLength; + assert(walkedLength == witness.length, text(walkedLength, " != ", witness.length)); + + // test persistent lines + assert(File(deleteme).byLineCopy(kt, term).array.sort() == witness.dup.sort()); + } + + KeepTerminator kt = No.keepTerminator; + test("", null, kt, '\n'); + test("\n", [ "" ], kt, '\n'); + test("asd\ndef\nasdf", [ "asd", "def", "asdf" ], kt, '\n'); + test("asd\ndef\nasdf", [ "asd", "def", "asdf" ], kt, '\n', true); + test("asd\ndef\nasdf\n", [ "asd", "def", "asdf" ], kt, '\n'); + test("foo", [ "foo" ], kt, '\n', true); + test("bob\r\nmarge\r\nsteve\r\n", ["bob", "marge", "steve"], + kt, "\r\n"); + test("sue\r", ["sue"], kt, '\r'); + + kt = Yes.keepTerminator; + test("", null, kt, '\n'); + test("\n", [ "\n" ], kt, '\n'); + test("asd\ndef\nasdf", [ "asd\n", "def\n", "asdf" ], kt, '\n'); + test("asd\ndef\nasdf\n", [ "asd\n", "def\n", "asdf\n" ], kt, '\n'); + test("asd\ndef\nasdf\n", [ "asd\n", "def\n", "asdf\n" ], kt, '\n', true); + test("foo", [ "foo" ], kt, '\n'); + test("bob\r\nmarge\r\nsteve\r\n", ["bob\r\n", "marge\r\n", "steve\r\n"], + kt, "\r\n"); + test("sue\r", ["sue\r"], kt, '\r'); + } + + @system unittest + { + import std.algorithm.comparison : equal; + import std.range : drop, take; + + version (Win64) + { + static import std.file; + + /* the C function tmpfile doesn't seem to work, even when called from C */ + auto deleteme = testFilename(); + auto file = File(deleteme, "w+"); + scope(success) std.file.remove(deleteme); + } + else version (CRuntime_Bionic) + { + static import std.file; + + /* the C function tmpfile doesn't work when called from a shared + library apk: + https://code.google.com/p/android/issues/detail?id=66815 */ + auto deleteme = testFilename(); + auto file = File(deleteme, "w+"); + scope(success) std.file.remove(deleteme); + } + else + auto file = File.tmpfile(); + file.write("1\n2\n3\n"); + + // bug 9599 + file.rewind(); + File.ByLine!(char, char) fbl = file.byLine(); + auto fbl2 = fbl; + assert(fbl.front == "1"); + assert(fbl.front is fbl2.front); + assert(fbl.take(1).equal(["1"])); + assert(fbl.equal(["2", "3"])); + assert(fbl.empty); + assert(file.isOpen); // we still have a valid reference + + file.rewind(); + fbl = file.byLine(); + assert(!fbl.drop(2).empty); + assert(fbl.equal(["3"])); + assert(fbl.empty); + assert(file.isOpen); + + file.detach(); + assert(!file.isOpen); + } + + @system unittest + { + static import std.file; + auto deleteme = testFilename(); + std.file.write(deleteme, "hi"); + scope(success) std.file.remove(deleteme); + + auto blc = File(deleteme).byLineCopy; + assert(!blc.empty); + // check front is cached + assert(blc.front is blc.front); + } + + /** + Creates an input range set up to parse one line at a time from the file + into a tuple. + + Range primitives may throw $(D StdioException) on I/O error. + + Params: + format = tuple record $(REF_ALTTEXT _format, formattedRead, std, _format) + + Returns: + The input range set up to parse one line at a time into a record tuple. + + See_Also: + + It is similar to $(LREF byLine) and uses + $(REF_ALTTEXT _format, formattedRead, std, _format) under the hood. + */ + template byRecord(Fields...) + { + ByRecord!(Fields) byRecord(string format) + { + return typeof(return)(this, format); + } + } + + /// + @system unittest + { + static import std.file; + import std.typecons : tuple; + + // prepare test file + auto testFile = testFilename(); + scope(failure) printf("Failed test at line %d\n", __LINE__); + std.file.write(testFile, "1 2\n4 1\n5 100"); + scope(exit) std.file.remove(testFile); + + File f = File(testFile); + scope(exit) f.close(); + + auto expected = [tuple(1, 2), tuple(4, 1), tuple(5, 100)]; + uint i; + foreach (e; f.byRecord!(int, int)("%s %s")) + { + assert(e == expected[i++]); + } + } + + // Note: This was documented until 2013/08 + /* + * Range that reads a chunk at a time. + */ + struct ByChunk + { + private: + File file_; + ubyte[] chunk_; + + void prime() + { + chunk_ = file_.rawRead(chunk_); + if (chunk_.length == 0) + file_.detach(); + } + + public: + this(File file, size_t size) + { + this(file, new ubyte[](size)); + } + + this(File file, ubyte[] buffer) + { + import std.exception : enforce; + enforce(buffer.length, "size must be larger than 0"); + file_ = file; + chunk_ = buffer; + prime(); + } + + // $(D ByChunk)'s input range primitive operations. + @property nothrow + bool empty() const + { + return !file_.isOpen; + } + + /// Ditto + @property nothrow + ubyte[] front() + { + version (assert) + { + import core.exception : RangeError; + if (empty) + throw new RangeError(); + } + return chunk_; + } + + /// Ditto + void popFront() + { + version (assert) + { + import core.exception : RangeError; + if (empty) + throw new RangeError(); + } + prime(); + } + } + +/** +Returns an input range set up to read from the file handle a chunk at a +time. + +The element type for the range will be $(D ubyte[]). Range primitives +may throw $(D StdioException) on I/O error. + +Example: +--------- +void main() +{ + // Read standard input 4KB at a time + foreach (ubyte[] buffer; stdin.byChunk(4096)) + { + ... use buffer ... + } +} +--------- + +The parameter may be a number (as shown in the example above) dictating the +size of each chunk. Alternatively, $(D byChunk) accepts a +user-provided buffer that it uses directly. + +Example: +--------- +void main() +{ + // Read standard input 4KB at a time + foreach (ubyte[] buffer; stdin.byChunk(new ubyte[4096])) + { + ... use buffer ... + } +} +--------- + +In either case, the content of the buffer is reused across calls. That means +$(D front) will not persist after $(D popFront) is called, so if retention is +needed, the caller must copy its contents (e.g. by calling $(D buffer.dup)). + +In the example above, $(D buffer.length) is 4096 for all iterations, except +for the last one, in which case $(D buffer.length) may be less than 4096 (but +always greater than zero). + +With the mentioned limitations, $(D byChunk) works with any algorithm +compatible with input ranges. + +Example: +--- +// Efficient file copy, 1MB at a time. +import std.algorithm, std.stdio; +void main() +{ + stdin.byChunk(1024 * 1024).copy(stdout.lockingTextWriter()); +} +--- + +$(REF joiner, std,algorithm,iteration) can be used to join chunks together into +a single range lazily. +Example: +--- +import std.algorithm, std.stdio; +void main() +{ + //Range of ranges + static assert(is(typeof(stdin.byChunk(4096).front) == ubyte[])); + //Range of elements + static assert(is(typeof(stdin.byChunk(4096).joiner.front) == ubyte)); +} +--- + +Returns: A call to $(D byChunk) returns a range initialized with the $(D File) +object and the appropriate buffer. + +Throws: If the user-provided size is zero or the user-provided buffer +is empty, throws an $(D Exception). In case of an I/O error throws +$(D StdioException). + */ + auto byChunk(size_t chunkSize) + { + return ByChunk(this, chunkSize); + } +/// Ditto + ByChunk byChunk(ubyte[] buffer) + { + return ByChunk(this, buffer); + } + + @system unittest + { + static import std.file; + + scope(failure) printf("Failed test at line %d\n", __LINE__); + + auto deleteme = testFilename(); + std.file.write(deleteme, "asd\ndef\nasdf"); + + auto witness = ["asd\n", "def\n", "asdf" ]; + auto f = File(deleteme); + scope(exit) + { + f.close(); + assert(!f.isOpen); + std.file.remove(deleteme); + } + + uint i; + foreach (chunk; f.byChunk(4)) + assert(chunk == cast(ubyte[]) witness[i++]); + + assert(i == witness.length); + } + + @system unittest + { + static import std.file; + + scope(failure) printf("Failed test at line %d\n", __LINE__); + + auto deleteme = testFilename(); + std.file.write(deleteme, "asd\ndef\nasdf"); + + auto witness = ["asd\n", "def\n", "asdf" ]; + auto f = File(deleteme); + scope(exit) + { + f.close(); + assert(!f.isOpen); + std.file.remove(deleteme); + } + + uint i; + foreach (chunk; f.byChunk(new ubyte[4])) + assert(chunk == cast(ubyte[]) witness[i++]); + + assert(i == witness.length); + } + + // Note: This was documented until 2013/08 +/* +$(D Range) that locks the file and allows fast writing to it. + */ + struct LockingTextWriter + { + private: + import std.range.primitives : ElementType, isInfinite, isInputRange; + // the shared file handle + FILE* fps_; + + // the unshared version of fps + @property _iobuf* handle_() @trusted { return cast(_iobuf*) fps_; } + + // the file's orientation (byte- or wide-oriented) + int orientation_; + public: + + this(ref File f) @trusted + { + import core.stdc.wchar_ : fwide; + import std.exception : enforce; + + enforce(f._p && f._p.handle, "Attempting to write to closed File"); + fps_ = f._p.handle; + orientation_ = fwide(fps_, 0); + FLOCK(fps_); + } + + ~this() @trusted + { + if (fps_) + { + FUNLOCK(fps_); + fps_ = null; + } + } + + this(this) @trusted + { + if (fps_) + { + FLOCK(fps_); + } + } + + /// Range primitive implementations. + void put(A)(A writeme) + if ((isSomeChar!(Unqual!(ElementType!A)) || + is(ElementType!A : const(ubyte))) && + isInputRange!A && + !isInfinite!A) + { + import std.exception : errnoEnforce; + + alias C = ElementEncodingType!A; + static assert(!is(C == void)); + static if (isSomeString!A && C.sizeof == 1 || is(A : const(ubyte)[])) + { + if (orientation_ <= 0) + { + //file.write(writeme); causes infinite recursion!!! + //file.rawWrite(writeme); + auto result = trustedFwrite(fps_, writeme); + if (result != writeme.length) errnoEnforce(0); + return; + } + } + + // put each element in turn. + alias Elem = Unqual!(ElementType!A); + foreach (Elem c; writeme) + { + put(c); + } + } + + /// ditto + void put(C)(C c) @safe if (isSomeChar!C || is(C : const(ubyte))) + { + import std.traits : Parameters; + static auto trustedFPUTC(int ch, _iobuf* h) @trusted + { + return FPUTC(ch, h); + } + static auto trustedFPUTWC(Parameters!FPUTWC[0] ch, _iobuf* h) @trusted + { + return FPUTWC(ch, h); + } + + static if (c.sizeof == 1) + { + // simple char + if (orientation_ <= 0) trustedFPUTC(c, handle_); + else trustedFPUTWC(c, handle_); + } + else static if (c.sizeof == 2) + { + import std.utf : encode, UseReplacementDchar; + + if (orientation_ <= 0) + { + if (c <= 0x7F) + { + trustedFPUTC(c, handle_); + } + else + { + char[4] buf; + immutable size = encode!(UseReplacementDchar.yes)(buf, c); + foreach (i ; 0 .. size) + trustedFPUTC(buf[i], handle_); + } + } + else + { + trustedFPUTWC(c, handle_); + } + } + else // 32-bit characters + { + import std.utf : encode; + + if (orientation_ <= 0) + { + if (c <= 0x7F) + { + trustedFPUTC(c, handle_); + } + else + { + char[4] buf = void; + immutable len = encode(buf, c); + foreach (i ; 0 .. len) + trustedFPUTC(buf[i], handle_); + } + } + else + { + version (Windows) + { + import std.utf : isValidDchar; + + assert(isValidDchar(c)); + if (c <= 0xFFFF) + { + trustedFPUTWC(c, handle_); + } + else + { + trustedFPUTWC(cast(wchar) + ((((c - 0x10000) >> 10) & 0x3FF) + + 0xD800), handle_); + trustedFPUTWC(cast(wchar) + (((c - 0x10000) & 0x3FF) + 0xDC00), + handle_); + } + } + else version (Posix) + { + trustedFPUTWC(c, handle_); + } + else + { + static assert(0); + } + } + } + } + } + +/** Returns an output range that locks the file and allows fast writing to it. + +See $(LREF byChunk) for an example. +*/ + auto lockingTextWriter() @safe + { + return LockingTextWriter(this); + } + + // An output range which optionally locks the file and puts it into + // binary mode (similar to rawWrite). Because it needs to restore + // the file mode on destruction, it is RefCounted on Windows. + struct BinaryWriterImpl(bool locking) + { + import std.traits : hasIndirections; + private: + FILE* fps; + string name; + + version (Windows) + { + int fd, oldMode; + version (DIGITAL_MARS_STDIO) + ubyte oldInfo; + } + + package: + this(ref File f) + { + import std.exception : enforce; + + enforce(f._p && f._p.handle); + name = f._name; + fps = f._p.handle; + static if (locking) + FLOCK(fps); + + version (Windows) + { + .fflush(fps); // before changing translation mode + fd = ._fileno(fps); + oldMode = ._setmode(fd, _O_BINARY); + version (DIGITAL_MARS_STDIO) + { + import core.atomic : atomicOp; + + // @@@BUG@@@ 4243 + oldInfo = __fhnd_info[fd]; + atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT); + } + } + } + + public: + ~this() + { + if (!fps) + return; + + version (Windows) + { + .fflush(fps); // before restoring translation mode + version (DIGITAL_MARS_STDIO) + { + // @@@BUG@@@ 4243 + __fhnd_info[fd] = oldInfo; + } + ._setmode(fd, oldMode); + } + + FUNLOCK(fps); + fps = null; + } + + void rawWrite(T)(in T[] buffer) + { + import std.conv : text; + import std.exception : errnoEnforce; + + auto result = trustedFwrite(fps, buffer); + if (result == result.max) result = 0; + errnoEnforce(result == buffer.length, + text("Wrote ", result, " instead of ", buffer.length, + " objects of type ", T.stringof, " to file `", + name, "'")); + } + + version (Windows) + { + @disable this(this); + } + else + { + this(this) + { + if (fps) + { + FLOCK(fps); + } + } + } + + void put(T)(auto ref in T value) + if (!hasIndirections!T && + !isInputRange!T) + { + rawWrite((&value)[0 .. 1]); + } + + void put(T)(in T[] array) + if (!hasIndirections!T && + !isInputRange!T) + { + rawWrite(array); + } + } + +/** Returns an output range that locks the file and allows fast writing to it. + +Example: +Produce a grayscale image of the $(LINK2 https://en.wikipedia.org/wiki/Mandelbrot_set, Mandelbrot set) +in binary $(LINK2 https://en.wikipedia.org/wiki/Netpbm_format, Netpbm format) to standard output. +--- +import std.algorithm, std.range, std.stdio; + +void main() +{ + enum size = 500; + writef("P5\n%d %d %d\n", size, size, ubyte.max); + + iota(-1, 3, 2.0/size).map!(y => + iota(-1.5, 0.5, 2.0/size).map!(x => + cast(ubyte)(1+ + recurrence!((a, n) => x + y*1i + a[n-1]^^2)(0+0i) + .take(ubyte.max) + .countUntil!(z => z.re^^2 + z.im^^2 > 4)) + ) + ) + .copy(stdout.lockingBinaryWriter); +} +--- +*/ + auto lockingBinaryWriter() + { + alias LockingBinaryWriterImpl = BinaryWriterImpl!true; + + version (Windows) + { + import std.typecons : RefCounted; + alias LockingBinaryWriter = RefCounted!LockingBinaryWriterImpl; + } + else + alias LockingBinaryWriter = LockingBinaryWriterImpl; + + return LockingBinaryWriter(this); + } + + @system unittest + { + import std.algorithm.mutation : reverse; + import std.exception : collectException; + static import std.file; + import std.range : only, retro; + import std.string : format; + + auto deleteme = testFilename(); + scope(exit) collectException(std.file.remove(deleteme)); + auto output = File(deleteme, "wb"); + auto writer = output.lockingBinaryWriter(); + auto input = File(deleteme, "rb"); + + T[] readExact(T)(T[] buf) + { + auto result = input.rawRead(buf); + assert(result.length == buf.length, + "Read %d out of %d bytes" + .format(result.length, buf.length)); + return result; + } + + // test raw values + ubyte byteIn = 42; + byteIn.only.copy(writer); output.flush(); + ubyte byteOut = readExact(new ubyte[1])[0]; + assert(byteIn == byteOut); + + // test arrays + ubyte[] bytesIn = [1, 2, 3, 4, 5]; + bytesIn.copy(writer); output.flush(); + ubyte[] bytesOut = readExact(new ubyte[bytesIn.length]); + scope(failure) .writeln(bytesOut); + assert(bytesIn == bytesOut); + + // test ranges of values + bytesIn.retro.copy(writer); output.flush(); + bytesOut = readExact(bytesOut); + bytesOut.reverse(); + assert(bytesIn == bytesOut); + + // test string + "foobar".copy(writer); output.flush(); + char[] charsOut = readExact(new char[6]); + assert(charsOut == "foobar"); + + // test ranges of arrays + only("foo", "bar").copy(writer); output.flush(); + charsOut = readExact(charsOut); + assert(charsOut == "foobar"); + + // test that we are writing arrays as is, + // without UTF-8 transcoding + "foo"d.copy(writer); output.flush(); + dchar[] dcharsOut = readExact(new dchar[3]); + assert(dcharsOut == "foo"); + } + +/// Get the size of the file, ulong.max if file is not searchable, but still throws if an actual error occurs. + @property ulong size() @safe + { + import std.exception : collectException; + + ulong pos = void; + if (collectException(pos = tell)) return ulong.max; + scope(exit) seek(pos); + seek(0, SEEK_END); + return tell; + } +} + +@system unittest +{ + @system struct SystemToString + { + string toString() + { + return "system"; + } + } + + @trusted struct TrustedToString + { + string toString() + { + return "trusted"; + } + } + + @safe struct SafeToString + { + string toString() + { + return "safe"; + } + } + + @system void systemTests() + { + //system code can write to files/stdout with anything! + if (false) + { + auto f = File(); + + f.write("just a string"); + f.write("string with arg: ", 47); + f.write(SystemToString()); + f.write(TrustedToString()); + f.write(SafeToString()); + + write("just a string"); + write("string with arg: ", 47); + write(SystemToString()); + write(TrustedToString()); + write(SafeToString()); + + f.writeln("just a string"); + f.writeln("string with arg: ", 47); + f.writeln(SystemToString()); + f.writeln(TrustedToString()); + f.writeln(SafeToString()); + + writeln("just a string"); + writeln("string with arg: ", 47); + writeln(SystemToString()); + writeln(TrustedToString()); + writeln(SafeToString()); + + f.writef("string with arg: %s", 47); + f.writef("%s", SystemToString()); + f.writef("%s", TrustedToString()); + f.writef("%s", SafeToString()); + + writef("string with arg: %s", 47); + writef("%s", SystemToString()); + writef("%s", TrustedToString()); + writef("%s", SafeToString()); + + f.writefln("string with arg: %s", 47); + f.writefln("%s", SystemToString()); + f.writefln("%s", TrustedToString()); + f.writefln("%s", SafeToString()); + + writefln("string with arg: %s", 47); + writefln("%s", SystemToString()); + writefln("%s", TrustedToString()); + writefln("%s", SafeToString()); + } + } + + @safe void safeTests() + { + auto f = File(); + + //safe code can write to files only with @safe and @trusted code... + if (false) + { + f.write("just a string"); + f.write("string with arg: ", 47); + f.write(TrustedToString()); + f.write(SafeToString()); + + write("just a string"); + write("string with arg: ", 47); + write(TrustedToString()); + write(SafeToString()); + + f.writeln("just a string"); + f.writeln("string with arg: ", 47); + f.writeln(TrustedToString()); + f.writeln(SafeToString()); + + writeln("just a string"); + writeln("string with arg: ", 47); + writeln(TrustedToString()); + writeln(SafeToString()); + + f.writef("string with arg: %s", 47); + f.writef("%s", TrustedToString()); + f.writef("%s", SafeToString()); + + writef("string with arg: %s", 47); + writef("%s", TrustedToString()); + writef("%s", SafeToString()); + + f.writefln("string with arg: %s", 47); + f.writefln("%s", TrustedToString()); + f.writefln("%s", SafeToString()); + + writefln("string with arg: %s", 47); + writefln("%s", TrustedToString()); + writefln("%s", SafeToString()); + } + + static assert(!__traits(compiles, f.write(SystemToString().toString()))); + static assert(!__traits(compiles, f.writeln(SystemToString()))); + static assert(!__traits(compiles, f.writef("%s", SystemToString()))); + static assert(!__traits(compiles, f.writefln("%s", SystemToString()))); + + static assert(!__traits(compiles, write(SystemToString().toString()))); + static assert(!__traits(compiles, writeln(SystemToString()))); + static assert(!__traits(compiles, writef("%s", SystemToString()))); + static assert(!__traits(compiles, writefln("%s", SystemToString()))); + } + + systemTests(); + safeTests(); +} + +@safe unittest +{ + import std.exception : collectException; + static import std.file; + + auto deleteme = testFilename(); + scope(exit) collectException(std.file.remove(deleteme)); + std.file.write(deleteme, "1 2 3"); + auto f = File(deleteme); + assert(f.size == 5); + assert(f.tell == 0); +} + +@system unittest +{ + // @system due to readln + static import std.file; + import std.range : chain, only, repeat; + import std.range.primitives : isOutputRange; + + auto deleteme = testFilename(); + scope(exit) std.file.remove(deleteme); + + { + File f = File(deleteme, "w"); + auto writer = f.lockingTextWriter(); + static assert(isOutputRange!(typeof(writer), dchar)); + writer.put("日本語"); + writer.put("日本語"w); + writer.put("日本語"d); + writer.put('日'); + writer.put(chain(only('本'), only('語'))); + writer.put(repeat('#', 12)); // BUG 11945 + writer.put(cast(immutable(ubyte)[])"日本語"); // Bug 17229 + } + assert(File(deleteme).readln() == "日本語日本語日本語日本語############日本語"); +} + +@safe unittest +{ + import std.exception : collectException; + auto e = collectException({ File f; f.writeln("Hello!"); }()); + assert(e && e.msg == "Attempting to write to closed File"); +} + +/// Used to specify the lock type for $(D File.lock) and $(D File.tryLock). +enum LockType +{ + /** + * Specifies a _read (shared) lock. A _read lock denies all processes + * write access to the specified region of the file, including the + * process that first locks the region. All processes can _read the + * locked region. Multiple simultaneous _read locks are allowed, as + * long as there are no exclusive locks. + */ + read, + + /** + * Specifies a read/write (exclusive) lock. A read/write lock denies all + * other processes both read and write access to the locked file region. + * If a segment has an exclusive lock, it may not have any shared locks + * or other exclusive locks. + */ + readWrite +} + +struct LockingTextReader +{ + private File _f; + private char _front; + private bool _hasChar; + + this(File f) + { + import std.exception : enforce; + enforce(f.isOpen, "LockingTextReader: File must be open"); + _f = f; + FLOCK(_f._p.handle); + } + + this(this) + { + FLOCK(_f._p.handle); + } + + ~this() + { + if (_hasChar) + ungetc(_front, cast(FILE*)_f._p.handle); + + // File locking has its own reference count + if (_f.isOpen) FUNLOCK(_f._p.handle); + } + + void opAssign(LockingTextReader r) + { + import std.algorithm.mutation : swap; + swap(this, r); + } + + @property bool empty() + { + if (!_hasChar) + { + if (!_f.isOpen || _f.eof) + return true; + immutable int c = FGETC(cast(_iobuf*) _f._p.handle); + if (c == EOF) + { + .destroy(_f); + return true; + } + _front = cast(char) c; + _hasChar = true; + } + return false; + } + + @property char front() + { + if (!_hasChar) + { + version (assert) + { + import core.exception : RangeError; + if (empty) + throw new RangeError(); + } + else + { + empty; + } + } + return _front; + } + + void popFront() + { + if (!_hasChar) + empty; + _hasChar = false; + } +} + +@system unittest +{ + // @system due to readf + static import std.file; + import std.range.primitives : isInputRange; + + static assert(isInputRange!LockingTextReader); + auto deleteme = testFilename(); + std.file.write(deleteme, "1 2 3"); + scope(exit) std.file.remove(deleteme); + int x, y; + auto f = File(deleteme); + f.readf("%s ", &x); + assert(x == 1); + f.readf("%d ", &x); + assert(x == 2); + f.readf("%d ", &x); + assert(x == 3); +} + +@system unittest // bugzilla 13686 +{ + import std.algorithm.comparison : equal; + static import std.file; + import std.utf : byDchar; + + auto deleteme = testFilename(); + std.file.write(deleteme, "Тест"); + scope(exit) std.file.remove(deleteme); + + string s; + File(deleteme).readf("%s", &s); + assert(s == "Тест"); + + auto ltr = LockingTextReader(File(deleteme)).byDchar; + assert(equal(ltr, "Тест".byDchar)); +} + +@system unittest // bugzilla 12320 +{ + static import std.file; + auto deleteme = testFilename(); + std.file.write(deleteme, "ab"); + scope(exit) std.file.remove(deleteme); + auto ltr = LockingTextReader(File(deleteme)); + assert(ltr.front == 'a'); + ltr.popFront(); + assert(ltr.front == 'b'); + ltr.popFront(); + assert(ltr.empty); +} + +@system unittest // bugzilla 14861 +{ + // @system due to readf + static import std.file; + auto deleteme = testFilename(); + File fw = File(deleteme, "w"); + for (int i; i != 5000; i++) + fw.writeln(i, ";", "Иванов;Пётр;Петрович"); + fw.close(); + scope(exit) std.file.remove(deleteme); + // Test read + File fr = File(deleteme, "r"); + scope (exit) fr.close(); + int nom; string fam, nam, ot; + // Error format read + while (!fr.eof) + fr.readf("%s;%s;%s;%s\n", &nom, &fam, &nam, &ot); +} + +/** + * Indicates whether $(D T) is a file handle, i.e. the type + * is implicitly convertable to $(LREF File) or a pointer to a + * $(REF FILE, core,stdc,stdio). + * + * Returns: + * `true` if `T` is a file handle, `false` otherwise. + */ +template isFileHandle(T) +{ + enum isFileHandle = is(T : FILE*) || + is(T : File); +} + +/// +@safe unittest +{ + static assert(isFileHandle!(FILE*)); + static assert(isFileHandle!(File)); +} + +/** + * Property used by writeln/etc. so it can infer @safe since stdout is __gshared + */ +private @property File trustedStdout() @trusted +{ + return stdout; +} + +/*********************************** +For each argument $(D arg) in $(D args), format the argument (using +$(REF to, std,conv)) and write the resulting +string to $(D args[0]). A call without any arguments will fail to +compile. + +Params: + args = the items to write to `stdout` + +Throws: In case of an I/O error, throws an $(D StdioException). + +Example: + Reads `stdin` and writes it to `stdout` with an argument + counter. +--- +import std.stdio; + +void main() +{ + string line; + + for (size_t count = 0; (line = readln) !is null; count++) + { + write("Input ", count, ": ", line, "\n"); + } +} +--- + */ +void write(T...)(T args) +if (!is(T[0] : File)) +{ + trustedStdout.write(args); +} + +@system unittest +{ + static import std.file; + + scope(failure) printf("Failed test at line %d\n", __LINE__); + void[] buf; + if (false) write(buf); + // test write + auto deleteme = testFilename(); + auto f = File(deleteme, "w"); + f.write("Hello, ", "world number ", 42, "!"); + f.close(); + scope(exit) { std.file.remove(deleteme); } + assert(cast(char[]) std.file.read(deleteme) == "Hello, world number 42!"); +} + +/*********************************** + * Equivalent to `write(args, '\n')`. Calling `writeln` without + * arguments is valid and just prints a newline to the standard + * output. + * + * Params: + * args = the items to write to `stdout` + * + * Throws: + * In case of an I/O error, throws an $(LREF StdioException). + * Example: + * Reads $(D stdin) and writes it to $(D stdout) with a argument + * counter. +--- +import std.stdio; + +void main() +{ + string line; + + for (size_t count = 0; (line = readln) !is null; count++) + { + writeln("Input ", count, ": ", line); + } +} +--- + */ +void writeln(T...)(T args) +{ + import std.traits : isAggregateType; + static if (T.length == 0) + { + import std.exception : enforce; + + enforce(fputc('\n', .trustedStdout._p.handle) != EOF, "fputc failed"); + } + else static if (T.length == 1 && + is(typeof(args[0]) : const(char)[]) && + !is(typeof(args[0]) == enum) && + !is(Unqual!(typeof(args[0])) == typeof(null)) && + !isAggregateType!(typeof(args[0]))) + { + import std.traits : isStaticArray; + + // Specialization for strings - a very frequent case + auto w = .trustedStdout.lockingTextWriter(); + + static if (isStaticArray!(typeof(args[0]))) + { + w.put(args[0][]); + } + else + { + w.put(args[0]); + } + w.put('\n'); + } + else + { + // Most general instance + trustedStdout.write(args, '\n'); + } +} + +@safe unittest +{ + // Just make sure the call compiles + if (false) writeln(); + + if (false) writeln("wyda"); + + // bug 8040 + if (false) writeln(null); + if (false) writeln(">", null, "<"); + + // Bugzilla 14041 + if (false) + { + char[8] a; + writeln(a); + } +} + +@system unittest +{ + static import std.file; + + scope(failure) printf("Failed test at line %d\n", __LINE__); + + // test writeln + auto deleteme = testFilename(); + auto f = File(deleteme, "w"); + scope(exit) { std.file.remove(deleteme); } + f.writeln("Hello, ", "world number ", 42, "!"); + f.close(); + version (Windows) + assert(cast(char[]) std.file.read(deleteme) == + "Hello, world number 42!\r\n"); + else + assert(cast(char[]) std.file.read(deleteme) == + "Hello, world number 42!\n"); + + // test writeln on stdout + auto saveStdout = stdout; + scope(exit) stdout = saveStdout; + stdout.open(deleteme, "w"); + writeln("Hello, ", "world number ", 42, "!"); + stdout.close(); + version (Windows) + assert(cast(char[]) std.file.read(deleteme) == + "Hello, world number 42!\r\n"); + else + assert(cast(char[]) std.file.read(deleteme) == + "Hello, world number 42!\n"); + + stdout.open(deleteme, "w"); + writeln("Hello!"c); + writeln("Hello!"w); // bug 8386 + writeln("Hello!"d); // bug 8386 + writeln("embedded\0null"c); // bug 8730 + stdout.close(); + version (Windows) + assert(cast(char[]) std.file.read(deleteme) == + "Hello!\r\nHello!\r\nHello!\r\nembedded\0null\r\n"); + else + assert(cast(char[]) std.file.read(deleteme) == + "Hello!\nHello!\nHello!\nembedded\0null\n"); +} + +@system unittest +{ + static import std.file; + + auto deleteme = testFilename(); + auto f = File(deleteme, "w"); + scope(exit) { std.file.remove(deleteme); } + + enum EI : int { A, B } + enum ED : double { A, B } + enum EC : char { A, B } + enum ES : string { A = "aaa", B = "bbb" } + + f.writeln(EI.A); // false, but A on 2.058 + f.writeln(EI.B); // true, but B on 2.058 + + f.writeln(ED.A); // A + f.writeln(ED.B); // B + + f.writeln(EC.A); // A + f.writeln(EC.B); // B + + f.writeln(ES.A); // A + f.writeln(ES.B); // B + + f.close(); + version (Windows) + assert(cast(char[]) std.file.read(deleteme) == + "A\r\nB\r\nA\r\nB\r\nA\r\nB\r\nA\r\nB\r\n"); + else + assert(cast(char[]) std.file.read(deleteme) == + "A\nB\nA\nB\nA\nB\nA\nB\n"); +} + +@system unittest +{ + static auto useInit(T)(T ltw) + { + T val; + val = ltw; + val = T.init; + return val; + } + useInit(stdout.lockingTextWriter()); +} + + +/*********************************** +Writes formatted data to standard output (without a trailing newline). + +Params: +fmt = The $(LINK2 std_format.html#format-string, format string). +When passed as a compile-time argument, the string will be statically checked +against the argument types passed. +args = Items to write. + +Note: In older versions of Phobos, it used to be possible to write: + +------ +writef(stderr, "%s", "message"); +------ + +to print a message to $(D stderr). This syntax is no longer supported, and has +been superceded by: + +------ +stderr.writef("%s", "message"); +------ + +*/ +void writef(alias fmt, A...)(A args) +if (isSomeString!(typeof(fmt))) +{ + import std.format : checkFormatException; + + alias e = checkFormatException!(fmt, A); + static assert(!e, e.msg); + return .writef(fmt, args); +} + +/// ditto +void writef(Char, A...)(in Char[] fmt, A args) +{ + trustedStdout.writef(fmt, args); +} + +@system unittest +{ + static import std.file; + + scope(failure) printf("Failed test at line %d\n", __LINE__); + + // test writef + auto deleteme = testFilename(); + auto f = File(deleteme, "w"); + scope(exit) { std.file.remove(deleteme); } + f.writef!"Hello, %s world number %s!"("nice", 42); + f.close(); + assert(cast(char[]) std.file.read(deleteme) == "Hello, nice world number 42!"); + // test write on stdout + auto saveStdout = stdout; + scope(exit) stdout = saveStdout; + stdout.open(deleteme, "w"); + writef!"Hello, %s world number %s!"("nice", 42); + stdout.close(); + assert(cast(char[]) std.file.read(deleteme) == "Hello, nice world number 42!"); +} + +/*********************************** + * Equivalent to $(D writef(fmt, args, '\n')). + */ +void writefln(alias fmt, A...)(A args) +if (isSomeString!(typeof(fmt))) +{ + import std.format : checkFormatException; + + alias e = checkFormatException!(fmt, A); + static assert(!e, e.msg); + return .writefln(fmt, args); +} + +/// ditto +void writefln(Char, A...)(in Char[] fmt, A args) +{ + trustedStdout.writefln(fmt, args); +} + +@system unittest +{ + static import std.file; + + scope(failure) printf("Failed test at line %d\n", __LINE__); + + // test File.writefln + auto deleteme = testFilename(); + auto f = File(deleteme, "w"); + scope(exit) { std.file.remove(deleteme); } + f.writefln!"Hello, %s world number %s!"("nice", 42); + f.close(); + version (Windows) + assert(cast(char[]) std.file.read(deleteme) == + "Hello, nice world number 42!\r\n"); + else + assert(cast(char[]) std.file.read(deleteme) == + "Hello, nice world number 42!\n", + cast(char[]) std.file.read(deleteme)); + + // test writefln + auto saveStdout = stdout; + scope(exit) stdout = saveStdout; + stdout.open(deleteme, "w"); + writefln!"Hello, %s world number %s!"("nice", 42); + stdout.close(); + version (Windows) + assert(cast(char[]) std.file.read(deleteme) == + "Hello, nice world number 42!\r\n"); + else + assert(cast(char[]) std.file.read(deleteme) == + "Hello, nice world number 42!\n"); +} + +/** + * Reads formatted data from $(D stdin) using $(REF formattedRead, std,_format). + * Params: + * format = The $(LINK2 std_format.html#_format-string, _format string). + * When passed as a compile-time argument, the string will be statically checked + * against the argument types passed. + * args = Items to be read. + * Example: +---- +// test.d +void main() +{ + import std.stdio; + foreach (_; 0 .. 3) + { + int a; + readf!" %d"(a); + writeln(++a); + } +} +---- +$(CONSOLE +% echo "1 2 3" | rdmd test.d +2 +3 +4 +) + */ +uint readf(alias format, A...)(auto ref A args) +if (isSomeString!(typeof(format))) +{ + import std.format : checkFormatException; + + alias e = checkFormatException!(format, A); + static assert(!e, e.msg); + return .readf(format, args); +} + +/// ditto +uint readf(A...)(in char[] format, auto ref A args) +{ + return stdin.readf(format, args); +} + +@system unittest +{ + float f; + if (false) uint x = readf("%s", &f); + + char a; + wchar b; + dchar c; + if (false) readf("%s %s %s", a, b, c); + // backwards compatibility with pointers + if (false) readf("%s %s %s", a, &b, c); + if (false) readf("%s %s %s", &a, &b, &c); +} + +/********************************** + * Read line from $(D stdin). + * + * This version manages its own read buffer, which means one memory allocation per call. If you are not + * retaining a reference to the read data, consider the $(D readln(buf)) version, which may offer + * better performance as it can reuse its read buffer. + * + * Returns: + * The line that was read, including the line terminator character. + * Params: + * S = Template parameter; the type of the allocated buffer, and the type returned. Defaults to $(D string). + * terminator = Line terminator (by default, $(D '\n')). + * Note: + * String terminators are not supported due to ambiguity with readln(buf) below. + * Throws: + * $(D StdioException) on I/O error, or $(D UnicodeException) on Unicode conversion error. + * Example: + * Reads $(D stdin) and writes it to $(D stdout). +--- +import std.stdio; + +void main() +{ + string line; + while ((line = readln()) !is null) + write(line); +} +--- +*/ +S readln(S = string)(dchar terminator = '\n') +if (isSomeString!S) +{ + return stdin.readln!S(terminator); +} + +/********************************** + * Read line from $(D stdin) and write it to buf[], including terminating character. + * + * This can be faster than $(D line = readln()) because you can reuse + * the buffer for each call. Note that reusing the buffer means that you + * must copy the previous contents if you wish to retain them. + * + * Returns: + * $(D size_t) 0 for end of file, otherwise number of characters read + * Params: + * buf = Buffer used to store the resulting line data. buf is resized as necessary. + * terminator = Line terminator (by default, $(D '\n')). Use $(REF newline, std,ascii) + * for portability (unless the file was opened in text mode). + * Throws: + * $(D StdioException) on I/O error, or $(D UnicodeException) on Unicode conversion error. + * Example: + * Reads $(D stdin) and writes it to $(D stdout). +--- +import std.stdio; + +void main() +{ + char[] buf; + while (readln(buf)) + write(buf); +} +--- +*/ +size_t readln(C)(ref C[] buf, dchar terminator = '\n') +if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum)) +{ + return stdin.readln(buf, terminator); +} + +/** ditto */ +size_t readln(C, R)(ref C[] buf, R terminator) +if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum) && + isBidirectionalRange!R && is(typeof(terminator.front == dchar.init))) +{ + return stdin.readln(buf, terminator); +} + +@safe unittest +{ + import std.meta : AliasSeq; + + //we can't actually test readln, so at the very least, + //we test compilability + void foo() + { + readln(); + readln('\t'); + foreach (String; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[])) + { + readln!String(); + readln!String('\t'); + } + foreach (String; AliasSeq!(char[], wchar[], dchar[])) + { + String buf; + readln(buf); + readln(buf, '\t'); + readln(buf, "<br />"); + } + } +} + +/* + * Convenience function that forwards to $(D core.sys.posix.stdio.fopen) + * (to $(D _wfopen) on Windows) + * with appropriately-constructed C-style strings. + */ +private FILE* fopen(R1, R2)(R1 name, R2 mode = "r") +if ((isInputRange!R1 && isSomeChar!(ElementEncodingType!R1) || isSomeString!R1) && + (isInputRange!R2 && isSomeChar!(ElementEncodingType!R2) || isSomeString!R2)) +{ + import std.internal.cstring : tempCString; + + auto namez = name.tempCString!FSChar(); + auto modez = mode.tempCString!FSChar(); + + static fopenImpl(const(FSChar)* namez, const(FSChar)* modez) @trusted nothrow @nogc + { + version (Windows) + { + return _wfopen(namez, modez); + } + else version (Posix) + { + /* + * The new opengroup large file support API is transparently + * included in the normal C bindings. http://opengroup.org/platform/lfs.html#1.0 + * if _FILE_OFFSET_BITS in druntime is 64, off_t is 64 bit and + * the normal functions work fine. If not, then large file support + * probably isn't available. Do not use the old transitional API + * (the native extern(C) fopen64, http://www.unix.org/version2/whatsnew/lfs20mar.html#3.0) + */ + import core.sys.posix.stdio : fopen; + return fopen(namez, modez); + } + else + { + return .fopen(namez, modez); + } + } + return fopenImpl(namez, modez); +} + +version (Posix) +{ + /*********************************** + * Convenience function that forwards to $(D core.sys.posix.stdio.popen) + * with appropriately-constructed C-style strings. + */ + FILE* popen(R1, R2)(R1 name, R2 mode = "r") @trusted nothrow @nogc + if ((isInputRange!R1 && isSomeChar!(ElementEncodingType!R1) || isSomeString!R1) && + (isInputRange!R2 && isSomeChar!(ElementEncodingType!R2) || isSomeString!R2)) + { + import std.internal.cstring : tempCString; + + auto namez = name.tempCString!FSChar(); + auto modez = mode.tempCString!FSChar(); + + static popenImpl(const(FSChar)* namez, const(FSChar)* modez) @trusted nothrow @nogc + { + import core.sys.posix.stdio : popen; + return popen(namez, modez); + } + return popenImpl(namez, modez); + } +} + +/* + * Convenience function that forwards to $(D core.stdc.stdio.fwrite) + */ +private auto trustedFwrite(T)(FILE* f, const T[] obj) @trusted +{ + return fwrite(obj.ptr, T.sizeof, obj.length, f); +} + +/* + * Convenience function that forwards to $(D core.stdc.stdio.fread) + */ +private auto trustedFread(T)(FILE* f, T[] obj) @trusted +{ + return fread(obj.ptr, T.sizeof, obj.length, f); +} + +/** + * Iterates through the lines of a file by using $(D foreach). + * + * Example: + * +--------- +void main() +{ + foreach (string line; lines(stdin)) + { + ... use line ... + } +} +--------- +The line terminator ($(D '\n') by default) is part of the string read (it +could be missing in the last line of the file). Several types are +supported for $(D line), and the behavior of $(D lines) +changes accordingly: + +$(OL $(LI If $(D line) has type $(D string), $(D +wstring), or $(D dstring), a new string of the respective type +is allocated every read.) $(LI If $(D line) has type $(D +char[]), $(D wchar[]), $(D dchar[]), the line's content +will be reused (overwritten) across reads.) $(LI If $(D line) +has type $(D immutable(ubyte)[]), the behavior is similar to +case (1), except that no UTF checking is attempted upon input.) $(LI +If $(D line) has type $(D ubyte[]), the behavior is +similar to case (2), except that no UTF checking is attempted upon +input.)) + +In all cases, a two-symbols versions is also accepted, in which case +the first symbol (of integral type, e.g. $(D ulong) or $(D +uint)) tracks the zero-based number of the current line. + +Example: +---- + foreach (ulong i, string line; lines(stdin)) + { + ... use line ... + } +---- + + In case of an I/O error, an $(D StdioException) is thrown. + +See_Also: +$(LREF byLine) + */ + +struct lines +{ + private File f; + private dchar terminator = '\n'; + + /** + Constructor. + Params: + f = File to read lines from. + terminator = Line separator ($(D '\n') by default). + */ + this(File f, dchar terminator = '\n') + { + this.f = f; + this.terminator = terminator; + } + + int opApply(D)(scope D dg) + { + import std.traits : Parameters; + alias Parms = Parameters!(dg); + static if (isSomeString!(Parms[$ - 1])) + { + enum bool duplicate = is(Parms[$ - 1] == string) + || is(Parms[$ - 1] == wstring) || is(Parms[$ - 1] == dstring); + int result = 0; + static if (is(Parms[$ - 1] : const(char)[])) + alias C = char; + else static if (is(Parms[$ - 1] : const(wchar)[])) + alias C = wchar; + else static if (is(Parms[$ - 1] : const(dchar)[])) + alias C = dchar; + C[] line; + static if (Parms.length == 2) + Parms[0] i = 0; + for (;;) + { + import std.conv : to; + + if (!f.readln(line, terminator)) break; + auto copy = to!(Parms[$ - 1])(line); + static if (Parms.length == 2) + { + result = dg(i, copy); + ++i; + } + else + { + result = dg(copy); + } + if (result != 0) break; + } + return result; + } + else + { + // raw read + return opApplyRaw(dg); + } + } + // no UTF checking + int opApplyRaw(D)(scope D dg) + { + import std.conv : to; + import std.exception : assumeUnique; + import std.traits : Parameters; + + alias Parms = Parameters!(dg); + enum duplicate = is(Parms[$ - 1] : immutable(ubyte)[]); + int result = 1; + int c = void; + FLOCK(f._p.handle); + scope(exit) FUNLOCK(f._p.handle); + ubyte[] buffer; + static if (Parms.length == 2) + Parms[0] line = 0; + while ((c = FGETC(cast(_iobuf*) f._p.handle)) != -1) + { + buffer ~= to!(ubyte)(c); + if (c == terminator) + { + static if (duplicate) + auto arg = assumeUnique(buffer); + else + alias arg = buffer; + // unlock the file while calling the delegate + FUNLOCK(f._p.handle); + scope(exit) FLOCK(f._p.handle); + static if (Parms.length == 1) + { + result = dg(arg); + } + else + { + result = dg(line, arg); + ++line; + } + if (result) break; + static if (!duplicate) + buffer.length = 0; + } + } + // can only reach when FGETC returned -1 + if (!f.eof) throw new StdioException("Error in reading file"); // error occured + return result; + } +} + +@system unittest +{ + static import std.file; + import std.meta : AliasSeq; + + scope(failure) printf("Failed test at line %d\n", __LINE__); + + auto deleteme = testFilename(); + scope(exit) { std.file.remove(deleteme); } + + alias TestedWith = + AliasSeq!(string, wstring, dstring, + char[], wchar[], dchar[]); + foreach (T; TestedWith) + { + // test looping with an empty file + std.file.write(deleteme, ""); + auto f = File(deleteme, "r"); + foreach (T line; lines(f)) + { + assert(false); + } + f.close(); + + // test looping with a file with three lines + std.file.write(deleteme, "Line one\nline two\nline three\n"); + f.open(deleteme, "r"); + uint i = 0; + foreach (T line; lines(f)) + { + if (i == 0) assert(line == "Line one\n"); + else if (i == 1) assert(line == "line two\n"); + else if (i == 2) assert(line == "line three\n"); + else assert(false); + ++i; + } + f.close(); + + // test looping with a file with three lines, last without a newline + std.file.write(deleteme, "Line one\nline two\nline three"); + f.open(deleteme, "r"); + i = 0; + foreach (T line; lines(f)) + { + if (i == 0) assert(line == "Line one\n"); + else if (i == 1) assert(line == "line two\n"); + else if (i == 2) assert(line == "line three"); + else assert(false); + ++i; + } + f.close(); + } + + // test with ubyte[] inputs + alias TestedWith2 = AliasSeq!(immutable(ubyte)[], ubyte[]); + foreach (T; TestedWith2) + { + // test looping with an empty file + std.file.write(deleteme, ""); + auto f = File(deleteme, "r"); + foreach (T line; lines(f)) + { + assert(false); + } + f.close(); + + // test looping with a file with three lines + std.file.write(deleteme, "Line one\nline two\nline three\n"); + f.open(deleteme, "r"); + uint i = 0; + foreach (T line; lines(f)) + { + if (i == 0) assert(cast(char[]) line == "Line one\n"); + else if (i == 1) assert(cast(char[]) line == "line two\n", + T.stringof ~ " " ~ cast(char[]) line); + else if (i == 2) assert(cast(char[]) line == "line three\n"); + else assert(false); + ++i; + } + f.close(); + + // test looping with a file with three lines, last without a newline + std.file.write(deleteme, "Line one\nline two\nline three"); + f.open(deleteme, "r"); + i = 0; + foreach (T line; lines(f)) + { + if (i == 0) assert(cast(char[]) line == "Line one\n"); + else if (i == 1) assert(cast(char[]) line == "line two\n"); + else if (i == 2) assert(cast(char[]) line == "line three"); + else assert(false); + ++i; + } + f.close(); + + } + + foreach (T; AliasSeq!(ubyte[])) + { + // test looping with a file with three lines, last without a newline + // using a counter too this time + std.file.write(deleteme, "Line one\nline two\nline three"); + auto f = File(deleteme, "r"); + uint i = 0; + foreach (ulong j, T line; lines(f)) + { + if (i == 0) assert(cast(char[]) line == "Line one\n"); + else if (i == 1) assert(cast(char[]) line == "line two\n"); + else if (i == 2) assert(cast(char[]) line == "line three"); + else assert(false); + ++i; + } + f.close(); + } +} + +/** +Iterates through a file a chunk at a time by using $(D foreach). + +Example: + +--------- +void main() +{ + foreach (ubyte[] buffer; chunks(stdin, 4096)) + { + ... use buffer ... + } +} +--------- + +The content of $(D buffer) is reused across calls. In the + example above, $(D buffer.length) is 4096 for all iterations, + except for the last one, in which case $(D buffer.length) may + be less than 4096 (but always greater than zero). + + In case of an I/O error, an $(D StdioException) is thrown. +*/ +auto chunks(File f, size_t size) +{ + return ChunksImpl(f, size); +} +private struct ChunksImpl +{ + private File f; + private size_t size; + // private string fileName; // Currently, no use + + this(File f, size_t size) + in + { + assert(size, "size must be larger than 0"); + } + body + { + this.f = f; + this.size = size; + } + + int opApply(D)(scope D dg) + { + import core.stdc.stdlib : alloca; + enum maxStackSize = 1024 * 16; + ubyte[] buffer = void; + if (size < maxStackSize) + buffer = (cast(ubyte*) alloca(size))[0 .. size]; + else + buffer = new ubyte[size]; + size_t r = void; + int result = 1; + uint tally = 0; + while ((r = trustedFread(f._p.handle, buffer)) > 0) + { + assert(r <= size); + if (r != size) + { + // error occured + if (!f.eof) throw new StdioException(null); + buffer.length = r; + } + static if (is(typeof(dg(tally, buffer)))) + { + if ((result = dg(tally, buffer)) != 0) break; + } + else + { + if ((result = dg(buffer)) != 0) break; + } + ++tally; + } + return result; + } +} + +@system unittest +{ + static import std.file; + + scope(failure) printf("Failed test at line %d\n", __LINE__); + + auto deleteme = testFilename(); + scope(exit) { std.file.remove(deleteme); } + + // test looping with an empty file + std.file.write(deleteme, ""); + auto f = File(deleteme, "r"); + foreach (ubyte[] line; chunks(f, 4)) + { + assert(false); + } + f.close(); + + // test looping with a file with three lines + std.file.write(deleteme, "Line one\nline two\nline three\n"); + f = File(deleteme, "r"); + uint i = 0; + foreach (ubyte[] line; chunks(f, 3)) + { + if (i == 0) assert(cast(char[]) line == "Lin"); + else if (i == 1) assert(cast(char[]) line == "e o"); + else if (i == 2) assert(cast(char[]) line == "ne\n"); + else break; + ++i; + } + f.close(); +} + + +/** +Writes an array or range to a file. +Shorthand for $(D data.copy(File(fileName, "wb").lockingBinaryWriter)). +Similar to $(REF write, std,file), strings are written as-is, +rather than encoded according to the $(D File)'s $(HTTP +en.cppreference.com/w/c/io#Narrow_and_wide_orientation, +orientation). +*/ +void toFile(T)(T data, string fileName) +if (is(typeof(copy(data, stdout.lockingBinaryWriter)))) +{ + copy(data, File(fileName, "wb").lockingBinaryWriter); +} + +@system unittest +{ + static import std.file; + + auto deleteme = testFilename(); + scope(exit) { std.file.remove(deleteme); } + + "Test".toFile(deleteme); + assert(std.file.readText(deleteme) == "Test"); +} + +/********************* + * Thrown if I/O errors happen. + */ +class StdioException : Exception +{ + static import core.stdc.errno; + /// Operating system error code. + uint errno; + +/** +Initialize with a message and an error code. +*/ + this(string message, uint e = core.stdc.errno.errno) @trusted + { + import std.exception : errnoString; + errno = e; + auto sysmsg = errnoString(errno); + // If e is 0, we don't use the system error message. (The message + // is "Success", which is rather pointless for an exception.) + super(e == 0 ? message + : (message ? message ~ " (" ~ sysmsg ~ ")" : sysmsg)); + } + +/** Convenience functions that throw an $(D StdioException). */ + static void opCall(string msg) + { + throw new StdioException(msg); + } + +/// ditto + static void opCall() + { + throw new StdioException(null, core.stdc.errno.errno); + } +} + +// Undocumented but public because the std* handles are aliasing it. +@property ref File makeGlobal(alias handle)() +{ + __gshared File.Impl impl; + __gshared File result; + + // Use an inline spinlock to make sure the initializer is only run once. + // We assume there will be at most uint.max / 2 threads trying to initialize + // `handle` at once and steal the high bit to indicate that the globals have + // been initialized. + static shared uint spinlock; + import core.atomic : atomicLoad, atomicOp, MemoryOrder; + if (atomicLoad!(MemoryOrder.acq)(spinlock) <= uint.max / 2) + { + for (;;) + { + if (atomicLoad!(MemoryOrder.acq)(spinlock) > uint.max / 2) + break; + if (atomicOp!"+="(spinlock, 1) == 1) + { + impl.handle = handle; + result._p = &impl; + atomicOp!"+="(spinlock, uint.max / 2); + break; + } + atomicOp!"-="(spinlock, 1); + } + } + return result; +} + +/** The standard input stream. + Bugs: + Due to $(LINK2 https://issues.dlang.org/show_bug.cgi?id=15768, bug 15768), + it is thread un-safe to reassign `stdin` to a different `File` instance + than the default. +*/ +alias stdin = makeGlobal!(core.stdc.stdio.stdin); + +/// +@safe unittest +{ + // Read stdin, sort lines, write to stdout + import std.algorithm.mutation : copy; + import std.algorithm.sorting : sort; + import std.array : array; + import std.typecons : Yes; + + void main() { + stdin // read from stdin + .byLineCopy(Yes.keepTerminator) // copying each line + .array() // convert to array of lines + .sort() // sort the lines + .copy( // copy output of .sort to an OutputRange + stdout.lockingTextWriter()); // the OutputRange + } +} + +/** + The standard output stream. + Bugs: + Due to $(LINK2 https://issues.dlang.org/show_bug.cgi?id=15768, bug 15768), + it is thread un-safe to reassign `stdout` to a different `File` instance + than the default. +*/ +alias stdout = makeGlobal!(core.stdc.stdio.stdout); + +/** + The standard error stream. + Bugs: + Due to $(LINK2 https://issues.dlang.org/show_bug.cgi?id=15768, bug 15768), + it is thread un-safe to reassign `stderr` to a different `File` instance + than the default. +*/ +alias stderr = makeGlobal!(core.stdc.stdio.stderr); + +@system unittest +{ + static import std.file; + import std.typecons : tuple; + + scope(failure) printf("Failed test at line %d\n", __LINE__); + auto deleteme = testFilename(); + + std.file.write(deleteme, "1 2\n4 1\n5 100"); + scope(exit) std.file.remove(deleteme); + { + File f = File(deleteme); + scope(exit) f.close(); + auto t = [ tuple(1, 2), tuple(4, 1), tuple(5, 100) ]; + uint i; + foreach (e; f.byRecord!(int, int)("%s %s")) + { + //writeln(e); + assert(e == t[i++]); + } + assert(i == 3); + } +} + +@safe unittest +{ + // Retain backwards compatibility + // https://issues.dlang.org/show_bug.cgi?id=17472 + static assert(is(typeof(stdin) == File)); + static assert(is(typeof(stdout) == File)); + static assert(is(typeof(stderr) == File)); +} + +// roll our own appender, but with "safe" arrays +private struct ReadlnAppender +{ + char[] buf; + size_t pos; + bool safeAppend = false; + + void initialize(char[] b) + { + buf = b; + pos = 0; + } + @property char[] data() @trusted + { + if (safeAppend) + assumeSafeAppend(buf.ptr[0 .. pos]); + return buf.ptr[0 .. pos]; + } + + bool reserveWithoutAllocating(size_t n) + { + if (buf.length >= pos + n) // buf is already large enough + return true; + + immutable curCap = buf.capacity; + if (curCap >= pos + n) + { + buf.length = curCap; + /* Any extra capacity we end up not using can safely be claimed + by someone else. */ + safeAppend = true; + return true; + } + + return false; + } + void reserve(size_t n) @trusted + { + import core.stdc.string : memcpy; + if (!reserveWithoutAllocating(n)) + { + size_t ncap = buf.length * 2 + 128 + n; + char[] nbuf = new char[ncap]; + memcpy(nbuf.ptr, buf.ptr, pos); + buf = nbuf; + // Allocated a new buffer. No one else knows about it. + safeAppend = true; + } + } + void putchar(char c) @trusted + { + reserve(1); + buf.ptr[pos++] = c; + } + void putdchar(dchar dc) @trusted + { + import std.utf : encode, UseReplacementDchar; + + char[4] ubuf; + immutable size = encode!(UseReplacementDchar.yes)(ubuf, dc); + reserve(size); + foreach (c; ubuf) + buf.ptr[pos++] = c; + } + void putonly(char[] b) @trusted + { + import core.stdc.string : memcpy; + assert(pos == 0); // assume this is the only put call + if (reserveWithoutAllocating(b.length)) + memcpy(buf.ptr + pos, b.ptr, b.length); + else + buf = b.dup; + pos = b.length; + } +} + +// Private implementation of readln +version (DIGITAL_MARS_STDIO) +private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orientation /*ignored*/) +{ + FLOCK(fps); + scope(exit) FUNLOCK(fps); + + /* Since fps is now locked, we can create an "unshared" version + * of fp. + */ + auto fp = cast(_iobuf*) fps; + + ReadlnAppender app; + app.initialize(buf); + + if (__fhnd_info[fp._file] & FHND_WCHAR) + { /* Stream is in wide characters. + * Read them and convert to chars. + */ + static assert(wchar_t.sizeof == 2); + for (int c = void; (c = FGETWC(fp)) != -1; ) + { + if ((c & ~0x7F) == 0) + { + app.putchar(cast(char) c); + if (c == terminator) + break; + } + else + { + if (c >= 0xD800 && c <= 0xDBFF) + { + int c2 = void; + if ((c2 = FGETWC(fp)) != -1 || + c2 < 0xDC00 && c2 > 0xDFFF) + { + StdioException("unpaired UTF-16 surrogate"); + } + c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00); + } + app.putdchar(cast(dchar) c); + } + } + if (ferror(fps)) + StdioException(); + } + + else if (fp._flag & _IONBF) + { + /* Use this for unbuffered I/O, when running + * across buffer boundaries, or for any but the common + * cases. + */ + L1: + int c; + while ((c = FGETC(fp)) != -1) + { + app.putchar(cast(char) c); + if (c == terminator) + { + buf = app.data; + return buf.length; + } + + } + + if (ferror(fps)) + StdioException(); + } + else + { + int u = fp._cnt; + char* p = fp._ptr; + int i; + if (fp._flag & _IOTRAN) + { /* Translated mode ignores \r and treats ^Z as end-of-file + */ + char c; + while (1) + { + if (i == u) // if end of buffer + goto L1; // give up + c = p[i]; + i++; + if (c != '\r') + { + if (c == terminator) + break; + if (c != 0x1A) + continue; + goto L1; + } + else + { if (i != u && p[i] == terminator) + break; + goto L1; + } + } + app.putonly(p[0 .. i]); + app.buf[i - 1] = cast(char) terminator; + if (terminator == '\n' && c == '\r') + i++; + } + else + { + while (1) + { + if (i == u) // if end of buffer + goto L1; // give up + auto c = p[i]; + i++; + if (c == terminator) + break; + } + app.putonly(p[0 .. i]); + } + fp._cnt -= i; + fp._ptr += i; + } + + buf = app.data; + return buf.length; +} + +version (MICROSOFT_STDIO) +private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orientation /*ignored*/) +{ + FLOCK(fps); + scope(exit) FUNLOCK(fps); + + /* Since fps is now locked, we can create an "unshared" version + * of fp. + */ + auto fp = cast(_iobuf*) fps; + + ReadlnAppender app; + app.initialize(buf); + + int c; + while ((c = FGETC(fp)) != -1) + { + app.putchar(cast(char) c); + if (c == terminator) + { + buf = app.data; + return buf.length; + } + + } + + if (ferror(fps)) + StdioException(); + buf = app.data; + return buf.length; +} + +version (HAS_GETDELIM) +private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orientation orientation) +{ + import core.stdc.stdlib : free; + import core.stdc.wchar_ : fwide; + + if (orientation == File.Orientation.wide) + { + /* Stream is in wide characters. + * Read them and convert to chars. + */ + FLOCK(fps); + scope(exit) FUNLOCK(fps); + auto fp = cast(_iobuf*) fps; + version (Windows) + { + buf.length = 0; + for (int c = void; (c = FGETWC(fp)) != -1; ) + { + if ((c & ~0x7F) == 0) + { buf ~= c; + if (c == terminator) + break; + } + else + { + if (c >= 0xD800 && c <= 0xDBFF) + { + int c2 = void; + if ((c2 = FGETWC(fp)) != -1 || + c2 < 0xDC00 && c2 > 0xDFFF) + { + StdioException("unpaired UTF-16 surrogate"); + } + c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00); + } + import std.utf : encode; + encode(buf, c); + } + } + if (ferror(fp)) + StdioException(); + return buf.length; + } + else version (Posix) + { + buf.length = 0; + for (int c; (c = FGETWC(fp)) != -1; ) + { + import std.utf : encode; + + if ((c & ~0x7F) == 0) + buf ~= cast(char) c; + else + encode(buf, cast(dchar) c); + if (c == terminator) + break; + } + if (ferror(fps)) + StdioException(); + return buf.length; + } + else + { + static assert(0); + } + } + + static char *lineptr = null; + static size_t n = 0; + scope(exit) + { + if (n > 128 * 1024) + { + // Bound memory used by readln + free(lineptr); + lineptr = null; + n = 0; + } + } + + auto s = getdelim(&lineptr, &n, terminator, fps); + if (s < 0) + { + if (ferror(fps)) + StdioException(); + buf.length = 0; // end of file + return 0; + } + + if (s <= buf.length) + { + buf = buf[0 .. s]; + buf[] = lineptr[0 .. s]; + } + else + { + buf = lineptr[0 .. s].dup; + } + return s; +} + +version (NO_GETDELIM) +private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator, File.Orientation orientation) +{ + import core.stdc.wchar_ : fwide; + + FLOCK(fps); + scope(exit) FUNLOCK(fps); + auto fp = cast(_iobuf*) fps; + if (orientation == File.Orientation.wide) + { + /* Stream is in wide characters. + * Read them and convert to chars. + */ + version (Windows) + { + buf.length = 0; + for (int c; (c = FGETWC(fp)) != -1; ) + { + if ((c & ~0x7F) == 0) + { buf ~= c; + if (c == terminator) + break; + } + else + { + if (c >= 0xD800 && c <= 0xDBFF) + { + int c2 = void; + if ((c2 = FGETWC(fp)) != -1 || + c2 < 0xDC00 && c2 > 0xDFFF) + { + StdioException("unpaired UTF-16 surrogate"); + } + c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00); + } + import std.utf : encode; + encode(buf, c); + } + } + if (ferror(fp)) + StdioException(); + return buf.length; + } + else version (Posix) + { + import std.utf : encode; + buf.length = 0; + for (int c; (c = FGETWC(fp)) != -1; ) + { + if ((c & ~0x7F) == 0) + buf ~= cast(char) c; + else + encode(buf, cast(dchar) c); + if (c == terminator) + break; + } + if (ferror(fps)) + StdioException(); + return buf.length; + } + else + { + static assert(0); + } + } + + // Narrow stream + // First, fill the existing buffer + for (size_t bufPos = 0; bufPos < buf.length; ) + { + immutable c = FGETC(fp); + if (c == -1) + { + buf.length = bufPos; + goto endGame; + } + buf[bufPos++] = cast(char) c; + if (c == terminator) + { + // No need to test for errors in file + buf.length = bufPos; + return bufPos; + } + } + // Then, append to it + for (int c; (c = FGETC(fp)) != -1; ) + { + buf ~= cast(char) c; + if (c == terminator) + { + // No need to test for errors in file + return buf.length; + } + } + + endGame: + if (ferror(fps)) + StdioException(); + return buf.length; +} + +@system unittest +{ + static import std.file; + auto deleteme = testFilename(); + scope(exit) std.file.remove(deleteme); + + std.file.write(deleteme, "abcd\n0123456789abcde\n1234\n"); + File f = File(deleteme, "rb"); + + char[] ln = new char[2]; + char* lnptr = ln.ptr; + f.readln(ln); + + assert(ln == "abcd\n"); + char[] t = ln[0 .. 2]; + t ~= 't'; + assert(t == "abt"); + assert(ln == "abcd\n"); // bug 13856: ln stomped to "abtd" + + // it can also stomp the array length + ln = new char[4]; + lnptr = ln.ptr; + f.readln(ln); + assert(ln == "0123456789abcde\n"); + + char[100] buf; + ln = buf[]; + f.readln(ln); + assert(ln == "1234\n"); + assert(ln.ptr == buf.ptr); // avoid allocation, buffer is good enough +} + +/** Experimental network access via the File interface + + Opens a TCP connection to the given host and port, then returns + a File struct with read and write access through the same interface + as any other file (meaning writef and the byLine ranges work!). + + Authors: + Adam D. Ruppe + + Bugs: + Only works on Linux +*/ +version (linux) +{ + File openNetwork(string host, ushort port) + { + import core.stdc.string : memcpy; + import core.sys.posix.arpa.inet : htons; + import core.sys.posix.netdb : gethostbyname; + import core.sys.posix.netinet.in_ : sockaddr_in; + static import core.sys.posix.unistd; + static import sock = core.sys.posix.sys.socket; + import std.conv : to; + import std.exception : enforce; + import std.internal.cstring : tempCString; + + auto h = enforce( gethostbyname(host.tempCString()), + new StdioException("gethostbyname")); + + int s = sock.socket(sock.AF_INET, sock.SOCK_STREAM, 0); + enforce(s != -1, new StdioException("socket")); + + scope(failure) + { + // want to make sure it doesn't dangle if something throws. Upon + // normal exit, the File struct's reference counting takes care of + // closing, so we don't need to worry about success + core.sys.posix.unistd.close(s); + } + + sockaddr_in addr; + + addr.sin_family = sock.AF_INET; + addr.sin_port = htons(port); + memcpy(&addr.sin_addr.s_addr, h.h_addr, h.h_length); + + enforce(sock.connect(s, cast(sock.sockaddr*) &addr, addr.sizeof) != -1, + new StdioException("Connect failed")); + + File f; + f.fdopen(s, "w+", host ~ ":" ~ to!string(port)); + return f; + } +} + +version (unittest) string testFilename(string file = __FILE__, size_t line = __LINE__) @safe +{ + import std.conv : text; + import std.file : deleteme; + import std.path : baseName; + + // filename intentionally contains non-ASCII (Russian) characters for test Issue 7648 + return text(deleteme, "-детка.", baseName(file), ".", line); +} diff --git a/libphobos/src/std/string.d b/libphobos/src/std/string.d new file mode 100644 index 0000000..34a1452 --- /dev/null +++ b/libphobos/src/std/string.d @@ -0,0 +1,6952 @@ +// Written in the D programming language. + +/** +String handling functions. + +$(SCRIPT inhibitQuickIndex = 1;) + +$(DIVC quickindex, +$(BOOKTABLE , +$(TR $(TH Category) $(TH Functions) ) +$(TR $(TDNW Searching) + $(TD + $(MYREF column) + $(MYREF indexOf) + $(MYREF indexOfAny) + $(MYREF indexOfNeither) + $(MYREF lastIndexOf) + $(MYREF lastIndexOfAny) + $(MYREF lastIndexOfNeither) + ) +) +$(TR $(TDNW Comparison) + $(TD + $(MYREF isNumeric) + ) +) +$(TR $(TDNW Mutation) + $(TD + $(MYREF capitalize) + ) +) +$(TR $(TDNW Pruning and Filling) + $(TD + $(MYREF center) + $(MYREF chomp) + $(MYREF chompPrefix) + $(MYREF chop) + $(MYREF detabber) + $(MYREF detab) + $(MYREF entab) + $(MYREF entabber) + $(MYREF leftJustify) + $(MYREF outdent) + $(MYREF rightJustify) + $(MYREF strip) + $(MYREF stripLeft) + $(MYREF stripRight) + $(MYREF wrap) + ) +) +$(TR $(TDNW Substitution) + $(TD + $(MYREF abbrev) + $(MYREF soundex) + $(MYREF soundexer) + $(MYREF succ) + $(MYREF tr) + $(MYREF translate) + ) +) +$(TR $(TDNW Miscellaneous) + $(TD + $(MYREF assumeUTF) + $(MYREF fromStringz) + $(MYREF lineSplitter) + $(MYREF representation) + $(MYREF splitLines) + $(MYREF toStringz) + ) +))) + +Objects of types $(D _string), $(D wstring), and $(D dstring) are value types +and cannot be mutated element-by-element. For using mutation during building +strings, use $(D char[]), $(D wchar[]), or $(D dchar[]). The $(D xxxstring) +types are preferable because they don't exhibit undesired aliasing, thus +making code more robust. + +The following functions are publicly imported: + +$(BOOKTABLE , +$(TR $(TH Module) $(TH Functions) ) +$(LEADINGROW Publicly imported functions) + $(TR $(TD std.algorithm) + $(TD + $(REF_SHORT cmp, std,algorithm,comparison) + $(REF_SHORT count, std,algorithm,searching) + $(REF_SHORT endsWith, std,algorithm,searching) + $(REF_SHORT startsWith, std,algorithm,searching) + )) + $(TR $(TD std.array) + $(TD + $(REF_SHORT join, std,array) + $(REF_SHORT replace, std,array) + $(REF_SHORT replaceInPlace, std,array) + $(REF_SHORT split, std,array) + $(REF_SHORT empty, std,array) + )) + $(TR $(TD std.format) + $(TD + $(REF_SHORT format, std,format) + $(REF_SHORT sformat, std,format) + )) + $(TR $(TD std.uni) + $(TD + $(REF_SHORT icmp, std,uni) + $(REF_SHORT toLower, std,uni) + $(REF_SHORT toLowerInPlace, std,uni) + $(REF_SHORT toUpper, std,uni) + $(REF_SHORT toUpperInPlace, std,uni) + )) +) + +There is a rich set of functions for _string handling defined in other modules. +Functions related to Unicode and ASCII are found in $(MREF std, uni) +and $(MREF std, ascii), respectively. Other functions that have a +wider generality than just strings can be found in $(MREF std, algorithm) +and $(MREF std, range). + +See_Also: + $(LIST + $(MREF std, algorithm) and + $(MREF std, range) + for generic range algorithms + , + $(MREF std, ascii) + for functions that work with ASCII strings + , + $(MREF std, uni) + for functions that work with unicode strings + ) + +Copyright: Copyright Digital Mars 2007-. + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(HTTP digitalmars.com, Walter Bright), + $(HTTP erdani.org, Andrei Alexandrescu), + Jonathan M Davis, + and David L. 'SpottedTiger' Davis + +Source: $(PHOBOSSRC std/_string.d) + +*/ +module std.string; + +version (unittest) +{ +private: + struct TestAliasedString + { + string get() @safe @nogc pure nothrow { return _s; } + alias get this; + @disable this(this); + string _s; + } + + bool testAliasedString(alias func, Args...)(string s, Args args) + { + import std.algorithm.comparison : equal; + auto a = func(TestAliasedString(s), args); + auto b = func(s, args); + static if (is(typeof(equal(a, b)))) + { + // For ranges, compare contents instead of object identity. + return equal(a, b); + } + else + { + return a == b; + } + } +} + +public import std.format : format, sformat; +import std.typecons : Flag, Yes, No; +public import std.uni : icmp, toLower, toLowerInPlace, toUpper, toUpperInPlace; + +import std.meta; // AliasSeq, staticIndexOf +import std.range.primitives; // back, ElementEncodingType, ElementType, front, + // hasLength, hasSlicing, isBidirectionalRange, isForwardRange, isInfinite, + // isInputRange, isOutputRange, isRandomAccessRange, popBack, popFront, put, + // save; +import std.traits; // isConvertibleToString, isNarrowString, isSomeChar, + // isSomeString, StringTypeOf, Unqual + +//public imports for backward compatibility +public import std.algorithm.comparison : cmp; +public import std.algorithm.searching : startsWith, endsWith, count; +public import std.array : join, replace, replaceInPlace, split, empty; + +/* ************* Exceptions *************** */ + +/++ + Exception thrown on errors in std.string functions. + +/ +class StringException : Exception +{ + import std.exception : basicExceptionCtors; + + /// + mixin basicExceptionCtors; +} + + +/++ + Params: + cString = A null-terminated c-style string. + + Returns: A D-style array of $(D char) referencing the same string. The + returned array will retain the same type qualifiers as the input. + + $(RED Important Note:) The returned array is a slice of the original buffer. + The original data is not changed and not copied. ++/ + +inout(char)[] fromStringz(inout(char)* cString) @nogc @system pure nothrow { + import core.stdc.string : strlen; + return cString ? cString[0 .. strlen(cString)] : null; +} + +/// +@system pure unittest +{ + assert(fromStringz(null) == null); + assert(fromStringz("foo") == "foo"); +} + +/++ + Params: + s = A D-style string. + + Returns: A C-style null-terminated string equivalent to $(D s). $(D s) + must not contain embedded $(D '\0')'s as any C function will treat the + first $(D '\0') that it sees as the end of the string. If $(D s.empty) is + $(D true), then a string containing only $(D '\0') is returned. + + $(RED Important Note:) When passing a $(D char*) to a C function, and the C + function keeps it around for any reason, make sure that you keep a + reference to it in your D code. Otherwise, it may become invalid during a + garbage collection cycle and cause a nasty bug when the C code tries to use + it. + +/ +immutable(char)* toStringz(const(char)[] s) @trusted pure nothrow +out (result) +{ + import core.stdc.string : strlen, memcmp; + if (result) + { + auto slen = s.length; + while (slen > 0 && s[slen-1] == 0) --slen; + assert(strlen(result) == slen); + assert(result[0 .. slen] == s[0 .. slen]); + } +} +body +{ + import std.exception : assumeUnique; + /+ Unfortunately, this isn't reliable. + We could make this work if string literals are put + in read-only memory and we test if s[] is pointing into + that. + + /* Peek past end of s[], if it's 0, no conversion necessary. + * Note that the compiler will put a 0 past the end of static + * strings, and the storage allocator will put a 0 past the end + * of newly allocated char[]'s. + */ + char* p = &s[0] + s.length; + if (*p == 0) + return s; + +/ + + // Need to make a copy + auto copy = new char[s.length + 1]; + copy[0 .. s.length] = s[]; + copy[s.length] = 0; + + return &assumeUnique(copy)[0]; +} + +/++ Ditto +/ +immutable(char)* toStringz(in string s) @trusted pure nothrow +{ + if (s.empty) return "".ptr; + /* Peek past end of s[], if it's 0, no conversion necessary. + * Note that the compiler will put a 0 past the end of static + * strings, and the storage allocator will put a 0 past the end + * of newly allocated char[]'s. + */ + immutable p = s.ptr + s.length; + // Is p dereferenceable? A simple test: if the p points to an + // address multiple of 4, then conservatively assume the pointer + // might be pointing to a new block of memory, which might be + // unreadable. Otherwise, it's definitely pointing to valid + // memory. + if ((cast(size_t) p & 3) && *p == 0) + return &s[0]; + return toStringz(cast(const char[]) s); +} + +/// +pure nothrow @system unittest +{ + import core.stdc.string : strlen; + import std.conv : to; + + auto p = toStringz("foo"); + assert(strlen(p) == 3); + const(char)[] foo = "abbzxyzzy"; + p = toStringz(foo[3 .. 5]); + assert(strlen(p) == 2); + + string test = ""; + p = toStringz(test); + assert(*p == 0); + + test = "\0"; + p = toStringz(test); + assert(*p == 0); + + test = "foo\0"; + p = toStringz(test); + assert(p[0] == 'f' && p[1] == 'o' && p[2] == 'o' && p[3] == 0); + + const string test2 = ""; + p = toStringz(test2); + assert(*p == 0); +} + + +/** + Flag indicating whether a search is case-sensitive. +*/ +alias CaseSensitive = Flag!"caseSensitive"; + +/++ + Searches for character in range. + + Params: + s = string or InputRange of characters to search in correct UTF format + c = character to search for + startIdx = starting index to a well-formed code point + cs = $(D Yes.caseSensitive) or $(D No.caseSensitive) + + Returns: + the index of the first occurrence of $(D c) in $(D s) with + respect to the start index $(D startIdx). If $(D c) + is not found, then $(D -1) is returned. + If $(D c) is found the value of the returned index is at least + $(D startIdx). + If the parameters are not valid UTF, the result will still + be in the range [-1 .. s.length], but will not be reliable otherwise. + + Throws: + If the sequence starting at $(D startIdx) does not represent a well + formed codepoint, then a $(REF UTFException, std,utf) may be thrown. + + See_Also: $(REF countUntil, std,algorithm,searching) + +/ +ptrdiff_t indexOf(Range)(Range s, in dchar c, + in CaseSensitive cs = Yes.caseSensitive) +if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) && + !isConvertibleToString!Range) +{ + static import std.ascii; + static import std.uni; + import std.utf : byDchar, byCodeUnit, UTFException, codeLength; + alias Char = Unqual!(ElementEncodingType!Range); + + if (cs == Yes.caseSensitive) + { + static if (Char.sizeof == 1 && isSomeString!Range) + { + if (std.ascii.isASCII(c) && !__ctfe) + { // Plain old ASCII + static ptrdiff_t trustedmemchr(Range s, char c) @trusted + { + import core.stdc.string : memchr; + const p = cast(const(Char)*)memchr(s.ptr, c, s.length); + return p ? p - s.ptr : -1; + } + + return trustedmemchr(s, cast(char) c); + } + } + + static if (Char.sizeof == 1) + { + if (c <= 0x7F) + { + ptrdiff_t i; + foreach (const c2; s) + { + if (c == c2) + return i; + ++i; + } + } + else + { + ptrdiff_t i; + foreach (const c2; s.byDchar()) + { + if (c == c2) + return i; + i += codeLength!Char(c2); + } + } + } + else static if (Char.sizeof == 2) + { + if (c <= 0xFFFF) + { + ptrdiff_t i; + foreach (const c2; s) + { + if (c == c2) + return i; + ++i; + } + } + else if (c <= 0x10FFFF) + { + // Encode UTF-16 surrogate pair + const wchar c1 = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); + const wchar c2 = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00); + ptrdiff_t i; + for (auto r = s.byCodeUnit(); !r.empty; r.popFront()) + { + if (c1 == r.front) + { + r.popFront(); + if (r.empty) // invalid UTF - missing second of pair + break; + if (c2 == r.front) + return i; + ++i; + } + ++i; + } + } + } + else static if (Char.sizeof == 4) + { + ptrdiff_t i; + foreach (const c2; s) + { + if (c == c2) + return i; + ++i; + } + } + else + static assert(0); + return -1; + } + else + { + if (std.ascii.isASCII(c)) + { // Plain old ASCII + immutable c1 = cast(char) std.ascii.toLower(c); + + ptrdiff_t i; + foreach (const c2; s.byCodeUnit()) + { + if (c1 == std.ascii.toLower(c2)) + return i; + ++i; + } + } + else + { // c is a universal character + immutable c1 = std.uni.toLower(c); + + ptrdiff_t i; + foreach (const c2; s.byDchar()) + { + if (c1 == std.uni.toLower(c2)) + return i; + i += codeLength!Char(c2); + } + } + } + return -1; +} + +/// Ditto +ptrdiff_t indexOf(Range)(Range s, in dchar c, in size_t startIdx, + in CaseSensitive cs = Yes.caseSensitive) +if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) && + !isConvertibleToString!Range) +{ + static if (isSomeString!(typeof(s)) || + (hasSlicing!(typeof(s)) && hasLength!(typeof(s)))) + { + if (startIdx < s.length) + { + ptrdiff_t foundIdx = indexOf(s[startIdx .. $], c, cs); + if (foundIdx != -1) + { + return foundIdx + cast(ptrdiff_t) startIdx; + } + } + } + else + { + foreach (i; 0 .. startIdx) + { + if (s.empty) + return -1; + s.popFront(); + } + ptrdiff_t foundIdx = indexOf(s, c, cs); + if (foundIdx != -1) + { + return foundIdx + cast(ptrdiff_t) startIdx; + } + } + return -1; +} + +/// +@safe pure unittest +{ + import std.typecons : No; + + string s = "Hello World"; + assert(indexOf(s, 'W') == 6); + assert(indexOf(s, 'Z') == -1); + assert(indexOf(s, 'w', No.caseSensitive) == 6); +} + +/// +@safe pure unittest +{ + import std.typecons : No; + + string s = "Hello World"; + assert(indexOf(s, 'W', 4) == 6); + assert(indexOf(s, 'Z', 100) == -1); + assert(indexOf(s, 'w', 3, No.caseSensitive) == 6); +} + +ptrdiff_t indexOf(Range)(auto ref Range s, in dchar c, + in CaseSensitive cs = Yes.caseSensitive) +if (isConvertibleToString!Range) +{ + return indexOf!(StringTypeOf!Range)(s, c, cs); +} + +ptrdiff_t indexOf(Range)(auto ref Range s, in dchar c, in size_t startIdx, + in CaseSensitive cs = Yes.caseSensitive) +if (isConvertibleToString!Range) +{ + return indexOf!(StringTypeOf!Range)(s, c, startIdx, cs); +} + +@safe pure unittest +{ + assert(testAliasedString!indexOf("std/string.d", '/')); +} + +@safe pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + import std.traits : EnumMembers; + import std.utf : byChar, byWchar, byDchar; + + assertCTFEable!( + { + foreach (S; AliasSeq!(string, wstring, dstring)) + { + assert(indexOf(cast(S) null, cast(dchar)'a') == -1); + assert(indexOf(to!S("def"), cast(dchar)'a') == -1); + assert(indexOf(to!S("abba"), cast(dchar)'a') == 0); + assert(indexOf(to!S("def"), cast(dchar)'f') == 2); + + assert(indexOf(to!S("def"), cast(dchar)'a', No.caseSensitive) == -1); + assert(indexOf(to!S("def"), cast(dchar)'a', No.caseSensitive) == -1); + assert(indexOf(to!S("Abba"), cast(dchar)'a', No.caseSensitive) == 0); + assert(indexOf(to!S("def"), cast(dchar)'F', No.caseSensitive) == 2); + assert(indexOf(to!S("ödef"), 'ö', No.caseSensitive) == 0); + + S sPlts = "Mars: the fourth Rock (Planet) from the Sun."; + assert(indexOf("def", cast(char)'f', No.caseSensitive) == 2); + assert(indexOf(sPlts, cast(char)'P', No.caseSensitive) == 23); + assert(indexOf(sPlts, cast(char)'R', No.caseSensitive) == 2); + } + + foreach (cs; EnumMembers!CaseSensitive) + { + assert(indexOf("hello\U00010143\u0100\U00010143", '\u0100', cs) == 9); + assert(indexOf("hello\U00010143\u0100\U00010143"w, '\u0100', cs) == 7); + assert(indexOf("hello\U00010143\u0100\U00010143"d, '\u0100', cs) == 6); + + assert(indexOf("hello\U00010143\u0100\U00010143".byChar, '\u0100', cs) == 9); + assert(indexOf("hello\U00010143\u0100\U00010143".byWchar, '\u0100', cs) == 7); + assert(indexOf("hello\U00010143\u0100\U00010143".byDchar, '\u0100', cs) == 6); + + assert(indexOf("hello\U000007FF\u0100\U00010143".byChar, 'l', cs) == 2); + assert(indexOf("hello\U000007FF\u0100\U00010143".byChar, '\u0100', cs) == 7); + assert(indexOf("hello\U0000EFFF\u0100\U00010143".byChar, '\u0100', cs) == 8); + + assert(indexOf("hello\U00010100".byWchar, '\U00010100', cs) == 5); + assert(indexOf("hello\U00010100".byWchar, '\U00010101', cs) == -1); + } + + char[10] fixedSizeArray = "0123456789"; + assert(indexOf(fixedSizeArray, '2') == 2); + }); +} + +@safe pure unittest +{ + assert(testAliasedString!indexOf("std/string.d", '/', 3)); +} + +@safe pure unittest +{ + import std.conv : to; + import std.traits : EnumMembers; + import std.utf : byCodeUnit, byChar, byWchar; + + assert("hello".byCodeUnit.indexOf(cast(dchar)'l', 1) == 2); + assert("hello".byWchar.indexOf(cast(dchar)'l', 1) == 2); + assert("hello".byWchar.indexOf(cast(dchar)'l', 6) == -1); + + foreach (S; AliasSeq!(string, wstring, dstring)) + { + assert(indexOf(cast(S) null, cast(dchar)'a', 1) == -1); + assert(indexOf(to!S("def"), cast(dchar)'a', 1) == -1); + assert(indexOf(to!S("abba"), cast(dchar)'a', 1) == 3); + assert(indexOf(to!S("def"), cast(dchar)'f', 1) == 2); + + assert((to!S("def")).indexOf(cast(dchar)'a', 1, + No.caseSensitive) == -1); + assert(indexOf(to!S("def"), cast(dchar)'a', 1, + No.caseSensitive) == -1); + assert(indexOf(to!S("def"), cast(dchar)'a', 12, + No.caseSensitive) == -1); + assert(indexOf(to!S("AbbA"), cast(dchar)'a', 2, + No.caseSensitive) == 3); + assert(indexOf(to!S("def"), cast(dchar)'F', 2, No.caseSensitive) == 2); + + S sPlts = "Mars: the fourth Rock (Planet) from the Sun."; + assert(indexOf("def", cast(char)'f', cast(uint) 2, + No.caseSensitive) == 2); + assert(indexOf(sPlts, cast(char)'P', 12, No.caseSensitive) == 23); + assert(indexOf(sPlts, cast(char)'R', cast(ulong) 1, + No.caseSensitive) == 2); + } + + foreach (cs; EnumMembers!CaseSensitive) + { + assert(indexOf("hello\U00010143\u0100\U00010143", '\u0100', 2, cs) + == 9); + assert(indexOf("hello\U00010143\u0100\U00010143"w, '\u0100', 3, cs) + == 7); + assert(indexOf("hello\U00010143\u0100\U00010143"d, '\u0100', 6, cs) + == 6); + } +} + +/++ + Searches for substring in $(D s). + + Params: + s = string or ForwardRange of characters to search in correct UTF format + sub = substring to search for + startIdx = the index into s to start searching from + cs = $(D Yes.caseSensitive) or $(D No.caseSensitive) + + Returns: + the index of the first occurrence of $(D sub) in $(D s) with + respect to the start index $(D startIdx). If $(D sub) is not found, + then $(D -1) is returned. + If the arguments are not valid UTF, the result will still + be in the range [-1 .. s.length], but will not be reliable otherwise. + If $(D sub) is found the value of the returned index is at least + $(D startIdx). + + Throws: + If the sequence starting at $(D startIdx) does not represent a well + formed codepoint, then a $(REF UTFException, std,utf) may be thrown. + + Bugs: + Does not work with case insensitive strings where the mapping of + tolower and toupper is not 1:1. + +/ +ptrdiff_t indexOf(Range, Char)(Range s, const(Char)[] sub, + in CaseSensitive cs = Yes.caseSensitive) +if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) && + isSomeChar!Char) +{ + alias Char1 = Unqual!(ElementEncodingType!Range); + + static if (isSomeString!Range) + { + import std.algorithm.searching : find; + + const(Char1)[] balance; + if (cs == Yes.caseSensitive) + { + balance = find(s, sub); + } + else + { + balance = find! + ((a, b) => toLower(a) == toLower(b)) + (s, sub); + } + return () @trusted { return balance.empty ? -1 : balance.ptr - s.ptr; } (); + } + else + { + if (s.empty) + return -1; + if (sub.empty) + return 0; // degenerate case + + import std.utf : byDchar, codeLength; + auto subr = sub.byDchar; // decode sub[] by dchar's + dchar sub0 = subr.front; // cache first character of sub[] + subr.popFront(); + + // Special case for single character search + if (subr.empty) + return indexOf(s, sub0, cs); + + if (cs == No.caseSensitive) + sub0 = toLower(sub0); + + /* Classic double nested loop search algorithm + */ + ptrdiff_t index = 0; // count code unit index into s + for (auto sbydchar = s.byDchar(); !sbydchar.empty; sbydchar.popFront()) + { + dchar c2 = sbydchar.front; + if (cs == No.caseSensitive) + c2 = toLower(c2); + if (c2 == sub0) + { + auto s2 = sbydchar.save; // why s must be a forward range + foreach (c; subr.save) + { + s2.popFront(); + if (s2.empty) + return -1; + if (cs == Yes.caseSensitive ? c != s2.front + : toLower(c) != toLower(s2.front) + ) + goto Lnext; + } + return index; + } + Lnext: + index += codeLength!Char1(c2); + } + return -1; + } +} + +/// Ditto +ptrdiff_t indexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub, + in size_t startIdx, in CaseSensitive cs = Yes.caseSensitive) +@safe +if (isSomeChar!Char1 && isSomeChar!Char2) +{ + if (startIdx < s.length) + { + ptrdiff_t foundIdx = indexOf(s[startIdx .. $], sub, cs); + if (foundIdx != -1) + { + return foundIdx + cast(ptrdiff_t) startIdx; + } + } + return -1; +} + +/// +@safe pure unittest +{ + import std.typecons : No; + + string s = "Hello World"; + assert(indexOf(s, "Wo", 4) == 6); + assert(indexOf(s, "Zo", 100) == -1); + assert(indexOf(s, "wo", 3, No.caseSensitive) == 6); +} + +/// +@safe pure unittest +{ + import std.typecons : No; + + string s = "Hello World"; + assert(indexOf(s, "Wo") == 6); + assert(indexOf(s, "Zo") == -1); + assert(indexOf(s, "wO", No.caseSensitive) == 6); +} + +ptrdiff_t indexOf(Range, Char)(auto ref Range s, const(Char)[] sub, + in CaseSensitive cs = Yes.caseSensitive) +if (!(isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) && + isSomeChar!Char) && + is(StringTypeOf!Range)) +{ + return indexOf!(StringTypeOf!Range)(s, sub, cs); +} + +@safe pure unittest +{ + assert(testAliasedString!indexOf("std/string.d", "string")); +} + +@safe pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + import std.traits : EnumMembers; + + assertCTFEable!( + { + foreach (S; AliasSeq!(string, wstring, dstring)) + { + foreach (T; AliasSeq!(string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + assert(indexOf(cast(S) null, to!T("a")) == -1); + assert(indexOf(to!S("def"), to!T("a")) == -1); + assert(indexOf(to!S("abba"), to!T("a")) == 0); + assert(indexOf(to!S("def"), to!T("f")) == 2); + assert(indexOf(to!S("dfefffg"), to!T("fff")) == 3); + assert(indexOf(to!S("dfeffgfff"), to!T("fff")) == 6); + + assert(indexOf(to!S("dfeffgfff"), to!T("a"), No.caseSensitive) == -1); + assert(indexOf(to!S("def"), to!T("a"), No.caseSensitive) == -1); + assert(indexOf(to!S("abba"), to!T("a"), No.caseSensitive) == 0); + assert(indexOf(to!S("def"), to!T("f"), No.caseSensitive) == 2); + assert(indexOf(to!S("dfefffg"), to!T("fff"), No.caseSensitive) == 3); + assert(indexOf(to!S("dfeffgfff"), to!T("fff"), No.caseSensitive) == 6); + + S sPlts = "Mars: the fourth Rock (Planet) from the Sun."; + S sMars = "Who\'s \'My Favorite Maritian?\'"; + + assert(indexOf(sMars, to!T("MY fAVe"), No.caseSensitive) == -1); + assert(indexOf(sMars, to!T("mY fAVOriTe"), No.caseSensitive) == 7); + assert(indexOf(sPlts, to!T("mArS:"), No.caseSensitive) == 0); + assert(indexOf(sPlts, to!T("rOcK"), No.caseSensitive) == 17); + assert(indexOf(sPlts, to!T("Un."), No.caseSensitive) == 41); + assert(indexOf(sPlts, to!T(sPlts), No.caseSensitive) == 0); + + assert(indexOf("\u0100", to!T("\u0100"), No.caseSensitive) == 0); + + // Thanks to Carlos Santander B. and zwang + assert(indexOf("sus mejores cortesanos. Se embarcaron en el puerto de Dubai y", + to!T("page-break-before"), No.caseSensitive) == -1); + }(); + + foreach (cs; EnumMembers!CaseSensitive) + { + assert(indexOf("hello\U00010143\u0100\U00010143", to!S("\u0100"), cs) == 9); + assert(indexOf("hello\U00010143\u0100\U00010143"w, to!S("\u0100"), cs) == 7); + assert(indexOf("hello\U00010143\u0100\U00010143"d, to!S("\u0100"), cs) == 6); + } + } + }); +} + +@safe pure @nogc nothrow +unittest +{ + import std.traits : EnumMembers; + import std.utf : byWchar; + + foreach (cs; EnumMembers!CaseSensitive) + { + assert(indexOf("".byWchar, "", cs) == -1); + assert(indexOf("hello".byWchar, "", cs) == 0); + assert(indexOf("hello".byWchar, "l", cs) == 2); + assert(indexOf("heLLo".byWchar, "LL", cs) == 2); + assert(indexOf("hello".byWchar, "lox", cs) == -1); + assert(indexOf("hello".byWchar, "betty", cs) == -1); + assert(indexOf("hello\U00010143\u0100*\U00010143".byWchar, "\u0100*", cs) == 7); + } +} + +@safe pure unittest +{ + import std.conv : to; + import std.traits : EnumMembers; + + foreach (S; AliasSeq!(string, wstring, dstring)) + { + foreach (T; AliasSeq!(string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + assert(indexOf(cast(S) null, to!T("a"), 1337) == -1); + assert(indexOf(to!S("def"), to!T("a"), 0) == -1); + assert(indexOf(to!S("abba"), to!T("a"), 2) == 3); + assert(indexOf(to!S("def"), to!T("f"), 1) == 2); + assert(indexOf(to!S("dfefffg"), to!T("fff"), 1) == 3); + assert(indexOf(to!S("dfeffgfff"), to!T("fff"), 5) == 6); + + assert(indexOf(to!S("dfeffgfff"), to!T("a"), 1, No.caseSensitive) == -1); + assert(indexOf(to!S("def"), to!T("a"), 2, No.caseSensitive) == -1); + assert(indexOf(to!S("abba"), to!T("a"), 3, No.caseSensitive) == 3); + assert(indexOf(to!S("def"), to!T("f"), 1, No.caseSensitive) == 2); + assert(indexOf(to!S("dfefffg"), to!T("fff"), 2, No.caseSensitive) == 3); + assert(indexOf(to!S("dfeffgfff"), to!T("fff"), 4, No.caseSensitive) == 6); + assert(indexOf(to!S("dfeffgffföä"), to!T("öä"), 9, No.caseSensitive) == 9, + to!string(indexOf(to!S("dfeffgffföä"), to!T("öä"), 9, No.caseSensitive)) + ~ " " ~ S.stringof ~ " " ~ T.stringof); + + S sPlts = "Mars: the fourth Rock (Planet) from the Sun."; + S sMars = "Who\'s \'My Favorite Maritian?\'"; + + assert(indexOf(sMars, to!T("MY fAVe"), 10, + No.caseSensitive) == -1); + assert(indexOf(sMars, to!T("mY fAVOriTe"), 4, No.caseSensitive) == 7); + assert(indexOf(sPlts, to!T("mArS:"), 0, No.caseSensitive) == 0); + assert(indexOf(sPlts, to!T("rOcK"), 12, No.caseSensitive) == 17); + assert(indexOf(sPlts, to!T("Un."), 32, No.caseSensitive) == 41); + assert(indexOf(sPlts, to!T(sPlts), 0, No.caseSensitive) == 0); + + assert(indexOf("\u0100", to!T("\u0100"), 0, No.caseSensitive) == 0); + + // Thanks to Carlos Santander B. and zwang + assert(indexOf("sus mejores cortesanos. Se embarcaron en el puerto de Dubai y", + to!T("page-break-before"), 10, No.caseSensitive) == -1); + + // In order for indexOf with and without index to be consistent + assert(indexOf(to!S(""), to!T("")) == indexOf(to!S(""), to!T(""), 0)); + }(); + + foreach (cs; EnumMembers!CaseSensitive) + { + assert(indexOf("hello\U00010143\u0100\U00010143", to!S("\u0100"), + 3, cs) == 9); + assert(indexOf("hello\U00010143\u0100\U00010143"w, to!S("\u0100"), + 3, cs) == 7); + assert(indexOf("hello\U00010143\u0100\U00010143"d, to!S("\u0100"), + 3, cs) == 6); + } + } +} + +/++ + Params: + s = string to search + c = character to search for + startIdx = the index into s to start searching from + cs = $(D Yes.caseSensitive) or $(D No.caseSensitive) + + Returns: + The index of the last occurrence of $(D c) in $(D s). If $(D c) is not + found, then $(D -1) is returned. The $(D startIdx) slices $(D s) in + the following way $(D s[0 .. startIdx]). $(D startIdx) represents a + codeunit index in $(D s). + + Throws: + If the sequence ending at $(D startIdx) does not represent a well + formed codepoint, then a $(REF UTFException, std,utf) may be thrown. + + $(D cs) indicates whether the comparisons are case sensitive. + +/ +ptrdiff_t lastIndexOf(Char)(const(Char)[] s, in dchar c, + in CaseSensitive cs = Yes.caseSensitive) @safe pure +if (isSomeChar!Char) +{ + static import std.ascii, std.uni; + import std.utf : canSearchInCodeUnits; + if (cs == Yes.caseSensitive) + { + if (canSearchInCodeUnits!Char(c)) + { + foreach_reverse (i, it; s) + { + if (it == c) + { + return i; + } + } + } + else + { + foreach_reverse (i, dchar it; s) + { + if (it == c) + { + return i; + } + } + } + } + else + { + if (std.ascii.isASCII(c)) + { + immutable c1 = std.ascii.toLower(c); + + foreach_reverse (i, it; s) + { + immutable c2 = std.ascii.toLower(it); + if (c1 == c2) + { + return i; + } + } + } + else + { + immutable c1 = std.uni.toLower(c); + + foreach_reverse (i, dchar it; s) + { + immutable c2 = std.uni.toLower(it); + if (c1 == c2) + { + return i; + } + } + } + } + + return -1; +} + +/// Ditto +ptrdiff_t lastIndexOf(Char)(const(Char)[] s, in dchar c, in size_t startIdx, + in CaseSensitive cs = Yes.caseSensitive) @safe pure +if (isSomeChar!Char) +{ + if (startIdx <= s.length) + { + return lastIndexOf(s[0u .. startIdx], c, cs); + } + + return -1; +} + +/// +@safe pure unittest +{ + import std.typecons : No; + + string s = "Hello World"; + assert(lastIndexOf(s, 'l') == 9); + assert(lastIndexOf(s, 'Z') == -1); + assert(lastIndexOf(s, 'L', No.caseSensitive) == 9); +} + +/// +@safe pure unittest +{ + import std.typecons : No; + + string s = "Hello World"; + assert(lastIndexOf(s, 'l', 4) == 3); + assert(lastIndexOf(s, 'Z', 1337) == -1); + assert(lastIndexOf(s, 'L', 7, No.caseSensitive) == 3); +} + +@safe pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + import std.traits : EnumMembers; + + assertCTFEable!( + { + foreach (S; AliasSeq!(string, wstring, dstring)) + { + assert(lastIndexOf(cast(S) null, 'a') == -1); + assert(lastIndexOf(to!S("def"), 'a') == -1); + assert(lastIndexOf(to!S("abba"), 'a') == 3); + assert(lastIndexOf(to!S("def"), 'f') == 2); + assert(lastIndexOf(to!S("ödef"), 'ö') == 0); + + assert(lastIndexOf(cast(S) null, 'a', No.caseSensitive) == -1); + assert(lastIndexOf(to!S("def"), 'a', No.caseSensitive) == -1); + assert(lastIndexOf(to!S("AbbA"), 'a', No.caseSensitive) == 3); + assert(lastIndexOf(to!S("def"), 'F', No.caseSensitive) == 2); + assert(lastIndexOf(to!S("ödef"), 'ö', No.caseSensitive) == 0); + assert(lastIndexOf(to!S("i\u0100def"), to!dchar("\u0100"), + No.caseSensitive) == 1); + + S sPlts = "Mars: the fourth Rock (Planet) from the Sun."; + + assert(lastIndexOf(to!S("def"), 'f', No.caseSensitive) == 2); + assert(lastIndexOf(sPlts, 'M', No.caseSensitive) == 34); + assert(lastIndexOf(sPlts, 'S', No.caseSensitive) == 40); + } + + foreach (cs; EnumMembers!CaseSensitive) + { + assert(lastIndexOf("\U00010143\u0100\U00010143hello", '\u0100', cs) == 4); + assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, '\u0100', cs) == 2); + assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, '\u0100', cs) == 1); + } + }); +} + +@safe pure unittest +{ + import std.conv : to; + import std.traits : EnumMembers; + + foreach (S; AliasSeq!(string, wstring, dstring)) + { + assert(lastIndexOf(cast(S) null, 'a') == -1); + assert(lastIndexOf(to!S("def"), 'a') == -1); + assert(lastIndexOf(to!S("abba"), 'a', 3) == 0); + assert(lastIndexOf(to!S("deff"), 'f', 3) == 2); + + assert(lastIndexOf(cast(S) null, 'a', No.caseSensitive) == -1); + assert(lastIndexOf(to!S("def"), 'a', No.caseSensitive) == -1); + assert(lastIndexOf(to!S("AbbAa"), 'a', to!ushort(4), No.caseSensitive) == 3, + to!string(lastIndexOf(to!S("AbbAa"), 'a', 4, No.caseSensitive))); + assert(lastIndexOf(to!S("def"), 'F', 3, No.caseSensitive) == 2); + + S sPlts = "Mars: the fourth Rock (Planet) from the Sun."; + + assert(lastIndexOf(to!S("def"), 'f', 4, No.caseSensitive) == -1); + assert(lastIndexOf(sPlts, 'M', sPlts.length -2, No.caseSensitive) == 34); + assert(lastIndexOf(sPlts, 'S', sPlts.length -2, No.caseSensitive) == 40); + } + + foreach (cs; EnumMembers!CaseSensitive) + { + assert(lastIndexOf("\U00010143\u0100\U00010143hello", '\u0100', cs) == 4); + assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, '\u0100', cs) == 2); + assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, '\u0100', cs) == 1); + } +} + +/++ + Params: + s = string to search + sub = substring to search for + startIdx = the index into s to start searching from + cs = $(D Yes.caseSensitive) or $(D No.caseSensitive) + + Returns: + the index of the last occurrence of $(D sub) in $(D s). If $(D sub) is + not found, then $(D -1) is returned. The $(D startIdx) slices $(D s) + in the following way $(D s[0 .. startIdx]). $(D startIdx) represents a + codeunit index in $(D s). + + Throws: + If the sequence ending at $(D startIdx) does not represent a well + formed codepoint, then a $(REF UTFException, std,utf) may be thrown. + + $(D cs) indicates whether the comparisons are case sensitive. + +/ +ptrdiff_t lastIndexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub, + in CaseSensitive cs = Yes.caseSensitive) @safe pure +if (isSomeChar!Char1 && isSomeChar!Char2) +{ + import std.algorithm.searching : endsWith; + import std.conv : to; + import std.range.primitives : walkLength; + static import std.uni; + import std.utf : strideBack; + if (sub.empty) + return -1; + + if (walkLength(sub) == 1) + return lastIndexOf(s, sub.front, cs); + + if (cs == Yes.caseSensitive) + { + static if (is(Unqual!Char1 == Unqual!Char2)) + { + import core.stdc.string : memcmp; + + immutable c = sub[0]; + + for (ptrdiff_t i = s.length - sub.length; i >= 0; --i) + { + if (s[i] == c) + { + if (__ctfe) + { + foreach (j; 1 .. sub.length) + { + if (s[i + j] != sub[j]) + continue; + } + return i; + } + else + { + auto trustedMemcmp(in void* s1, in void* s2, size_t n) @trusted + { + return memcmp(s1, s2, n); + } + if (trustedMemcmp(&s[i + 1], &sub[1], + (sub.length - 1) * Char1.sizeof) == 0) + return i; + } + } + } + } + else + { + for (size_t i = s.length; !s.empty;) + { + if (s.endsWith(sub)) + return cast(ptrdiff_t) i - to!(const(Char1)[])(sub).length; + + i -= strideBack(s, i); + s = s[0 .. i]; + } + } + } + else + { + for (size_t i = s.length; !s.empty;) + { + if (endsWith!((a, b) => std.uni.toLower(a) == std.uni.toLower(b)) + (s, sub)) + { + return cast(ptrdiff_t) i - to!(const(Char1)[])(sub).length; + } + + i -= strideBack(s, i); + s = s[0 .. i]; + } + } + + return -1; +} + +/// Ditto +ptrdiff_t lastIndexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub, + in size_t startIdx, in CaseSensitive cs = Yes.caseSensitive) @safe pure +if (isSomeChar!Char1 && isSomeChar!Char2) +{ + if (startIdx <= s.length) + { + return lastIndexOf(s[0u .. startIdx], sub, cs); + } + + return -1; +} + +/// +@safe pure unittest +{ + import std.typecons : No; + + string s = "Hello World"; + assert(lastIndexOf(s, "ll") == 2); + assert(lastIndexOf(s, "Zo") == -1); + assert(lastIndexOf(s, "lL", No.caseSensitive) == 2); +} + +/// +@safe pure unittest +{ + import std.typecons : No; + + string s = "Hello World"; + assert(lastIndexOf(s, "ll", 4) == 2); + assert(lastIndexOf(s, "Zo", 128) == -1); + assert(lastIndexOf(s, "lL", 3, No.caseSensitive) == -1); +} + +@safe pure unittest +{ + import std.conv : to; + + foreach (S; AliasSeq!(string, wstring, dstring)) + { + auto r = to!S("").lastIndexOf("hello"); + assert(r == -1, to!string(r)); + + r = to!S("hello").lastIndexOf(""); + assert(r == -1, to!string(r)); + + r = to!S("").lastIndexOf(""); + assert(r == -1, to!string(r)); + } +} + +@safe pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + import std.traits : EnumMembers; + + assertCTFEable!( + { + foreach (S; AliasSeq!(string, wstring, dstring)) + { + foreach (T; AliasSeq!(string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + enum typeStr = S.stringof ~ " " ~ T.stringof; + + assert(lastIndexOf(cast(S) null, to!T("a")) == -1, typeStr); + assert(lastIndexOf(to!S("abcdefcdef"), to!T("c")) == 6, typeStr); + assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd")) == 6, typeStr); + assert(lastIndexOf(to!S("abcdefcdef"), to!T("ef")) == 8, typeStr); + assert(lastIndexOf(to!S("abcdefCdef"), to!T("c")) == 2, typeStr); + assert(lastIndexOf(to!S("abcdefCdef"), to!T("cd")) == 2, typeStr); + assert(lastIndexOf(to!S("abcdefcdef"), to!T("x")) == -1, typeStr); + assert(lastIndexOf(to!S("abcdefcdef"), to!T("xy")) == -1, typeStr); + assert(lastIndexOf(to!S("abcdefcdef"), to!T("")) == -1, typeStr); + assert(lastIndexOf(to!S("öabcdefcdef"), to!T("ö")) == 0, typeStr); + + assert(lastIndexOf(cast(S) null, to!T("a"), No.caseSensitive) == -1, typeStr); + assert(lastIndexOf(to!S("abcdefCdef"), to!T("c"), No.caseSensitive) == 6, typeStr); + assert(lastIndexOf(to!S("abcdefCdef"), to!T("cD"), No.caseSensitive) == 6, typeStr); + assert(lastIndexOf(to!S("abcdefcdef"), to!T("x"), No.caseSensitive) == -1, typeStr); + assert(lastIndexOf(to!S("abcdefcdef"), to!T("xy"), No.caseSensitive) == -1, typeStr); + assert(lastIndexOf(to!S("abcdefcdef"), to!T(""), No.caseSensitive) == -1, typeStr); + assert(lastIndexOf(to!S("öabcdefcdef"), to!T("ö"), No.caseSensitive) == 0, typeStr); + + assert(lastIndexOf(to!S("abcdefcdef"), to!T("c"), No.caseSensitive) == 6, typeStr); + assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd"), No.caseSensitive) == 6, typeStr); + assert(lastIndexOf(to!S("abcdefcdef"), to!T("def"), No.caseSensitive) == 7, typeStr); + + assert(lastIndexOf(to!S("ödfeffgfff"), to!T("ö"), Yes.caseSensitive) == 0); + + S sPlts = "Mars: the fourth Rock (Planet) from the Sun."; + S sMars = "Who\'s \'My Favorite Maritian?\'"; + + assert(lastIndexOf(sMars, to!T("RiTE maR"), No.caseSensitive) == 14, typeStr); + assert(lastIndexOf(sPlts, to!T("FOuRTh"), No.caseSensitive) == 10, typeStr); + assert(lastIndexOf(sMars, to!T("whO\'s \'MY"), No.caseSensitive) == 0, typeStr); + assert(lastIndexOf(sMars, to!T(sMars), No.caseSensitive) == 0, typeStr); + }(); + + foreach (cs; EnumMembers!CaseSensitive) + { + enum csString = to!string(cs); + + assert(lastIndexOf("\U00010143\u0100\U00010143hello", to!S("\u0100"), cs) == 4, csString); + assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, to!S("\u0100"), cs) == 2, csString); + assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, to!S("\u0100"), cs) == 1, csString); + } + } + }); +} + +@safe pure unittest // issue13529 +{ + import std.conv : to; + foreach (S; AliasSeq!(string, wstring, dstring)) + { + foreach (T; AliasSeq!(string, wstring, dstring)) + { + enum typeStr = S.stringof ~ " " ~ T.stringof; + auto idx = lastIndexOf(to!T("Hällö Wörldö ö"),to!S("ö ö")); + assert(idx != -1, to!string(idx) ~ " " ~ typeStr); + + idx = lastIndexOf(to!T("Hällö Wörldö ö"),to!S("ö öd")); + assert(idx == -1, to!string(idx) ~ " " ~ typeStr); + } + } +} + +@safe pure unittest +{ + import std.conv : to; + import std.traits : EnumMembers; + + foreach (S; AliasSeq!(string, wstring, dstring)) + { + foreach (T; AliasSeq!(string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + enum typeStr = S.stringof ~ " " ~ T.stringof; + + assert(lastIndexOf(cast(S) null, to!T("a")) == -1, typeStr); + assert(lastIndexOf(to!S("abcdefcdef"), to!T("c"), 5) == 2, typeStr); + assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd"), 3) == -1, typeStr); + assert(lastIndexOf(to!S("abcdefcdef"), to!T("ef"), 6) == 4, typeStr ~ + format(" %u", lastIndexOf(to!S("abcdefcdef"), to!T("ef"), 6))); + assert(lastIndexOf(to!S("abcdefCdef"), to!T("c"), 5) == 2, typeStr); + assert(lastIndexOf(to!S("abcdefCdef"), to!T("cd"), 3) == -1, typeStr); + assert(lastIndexOf(to!S("abcdefcdefx"), to!T("x"), 1) == -1, typeStr); + assert(lastIndexOf(to!S("abcdefcdefxy"), to!T("xy"), 6) == -1, typeStr); + assert(lastIndexOf(to!S("abcdefcdef"), to!T(""), 8) == -1, typeStr); + assert(lastIndexOf(to!S("öafö"), to!T("ö"), 3) == 0, typeStr ~ + to!string(lastIndexOf(to!S("öafö"), to!T("ö"), 3))); //BUG 10472 + + assert(lastIndexOf(cast(S) null, to!T("a"), 1, No.caseSensitive) == -1, typeStr); + assert(lastIndexOf(to!S("abcdefCdef"), to!T("c"), 5, No.caseSensitive) == 2, typeStr); + assert(lastIndexOf(to!S("abcdefCdef"), to!T("cD"), 4, No.caseSensitive) == 2, typeStr ~ + " " ~ to!string(lastIndexOf(to!S("abcdefCdef"), to!T("cD"), 3, No.caseSensitive))); + assert(lastIndexOf(to!S("abcdefcdef"), to!T("x"),3 , No.caseSensitive) == -1, typeStr); + assert(lastIndexOf(to!S("abcdefcdefXY"), to!T("xy"), 4, No.caseSensitive) == -1, typeStr); + assert(lastIndexOf(to!S("abcdefcdef"), to!T(""), 7, No.caseSensitive) == -1, typeStr); + + assert(lastIndexOf(to!S("abcdefcdef"), to!T("c"), 4, No.caseSensitive) == 2, typeStr); + assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd"), 4, No.caseSensitive) == 2, typeStr); + assert(lastIndexOf(to!S("abcdefcdef"), to!T("def"), 6, No.caseSensitive) == 3, typeStr); + assert(lastIndexOf(to!S(""), to!T(""), 0) == lastIndexOf(to!S(""), to!T("")), typeStr); + }(); + + foreach (cs; EnumMembers!CaseSensitive) + { + enum csString = to!string(cs); + + assert(lastIndexOf("\U00010143\u0100\U00010143hello", to!S("\u0100"), 6, cs) == 4, csString); + assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, to!S("\u0100"), 6, cs) == 2, csString); + assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, to!S("\u0100"), 3, cs) == 1, csString); + } + } +} + +private ptrdiff_t indexOfAnyNeitherImpl(bool forward, bool any, Char, Char2)( + const(Char)[] haystack, const(Char2)[] needles, + in CaseSensitive cs = Yes.caseSensitive) @safe pure +if (isSomeChar!Char && isSomeChar!Char2) +{ + import std.algorithm.searching : canFind, findAmong; + if (cs == Yes.caseSensitive) + { + static if (forward) + { + static if (any) + { + size_t n = haystack.findAmong(needles).length; + return n ? haystack.length - n : -1; + } + else + { + foreach (idx, dchar hay; haystack) + { + if (!canFind(needles, hay)) + { + return idx; + } + } + } + } + else + { + static if (any) + { + import std.range : retro; + import std.utf : strideBack; + size_t n = haystack.retro.findAmong(needles).source.length; + if (n) + { + return n - haystack.strideBack(n); + } + } + else + { + foreach_reverse (idx, dchar hay; haystack) + { + if (!canFind(needles, hay)) + { + return idx; + } + } + } + } + } + else + { + import std.range.primitives : walkLength; + if (needles.length <= 16 && needles.walkLength(17)) + { + size_t si = 0; + dchar[16] scratch = void; + foreach ( dchar c; needles) + { + scratch[si++] = toLower(c); + } + + static if (forward) + { + foreach (i, dchar c; haystack) + { + if (canFind(scratch[0 .. si], toLower(c)) == any) + { + return i; + } + } + } + else + { + foreach_reverse (i, dchar c; haystack) + { + if (canFind(scratch[0 .. si], toLower(c)) == any) + { + return i; + } + } + } + } + else + { + static bool f(dchar a, dchar b) + { + return toLower(a) == b; + } + + static if (forward) + { + foreach (i, dchar c; haystack) + { + if (canFind!f(needles, toLower(c)) == any) + { + return i; + } + } + } + else + { + foreach_reverse (i, dchar c; haystack) + { + if (canFind!f(needles, toLower(c)) == any) + { + return i; + } + } + } + } + } + + return -1; +} + +/** + Returns the index of the first occurrence of any of the elements in $(D + needles) in $(D haystack). If no element of $(D needles) is found, + then $(D -1) is returned. The $(D startIdx) slices $(D haystack) in the + following way $(D haystack[startIdx .. $]). $(D startIdx) represents a + codeunit index in $(D haystack). If the sequence ending at $(D startIdx) + does not represent a well formed codepoint, then a $(REF UTFException, std,utf) + may be thrown. + + Params: + haystack = String to search for needles in. + needles = Strings to search for in haystack. + startIdx = slices haystack like this $(D haystack[startIdx .. $]). If + the startIdx is greater equal the length of haystack the functions + returns $(D -1). + cs = Indicates whether the comparisons are case sensitive. +*/ +ptrdiff_t indexOfAny(Char,Char2)(const(Char)[] haystack, const(Char2)[] needles, + in CaseSensitive cs = Yes.caseSensitive) @safe pure +if (isSomeChar!Char && isSomeChar!Char2) +{ + return indexOfAnyNeitherImpl!(true, true)(haystack, needles, cs); +} + +/// Ditto +ptrdiff_t indexOfAny(Char,Char2)(const(Char)[] haystack, const(Char2)[] needles, + in size_t startIdx, in CaseSensitive cs = Yes.caseSensitive) @safe pure +if (isSomeChar!Char && isSomeChar!Char2) +{ + if (startIdx < haystack.length) + { + ptrdiff_t foundIdx = indexOfAny(haystack[startIdx .. $], needles, cs); + if (foundIdx != -1) + { + return foundIdx + cast(ptrdiff_t) startIdx; + } + } + + return -1; +} + +/// +@safe pure unittest +{ + import std.conv : to; + + ptrdiff_t i = "helloWorld".indexOfAny("Wr"); + assert(i == 5); + i = "öällo world".indexOfAny("lo "); + assert(i == 4, to!string(i)); +} + +/// +@safe pure unittest +{ + import std.conv : to; + + ptrdiff_t i = "helloWorld".indexOfAny("Wr", 4); + assert(i == 5); + + i = "Foo öällo world".indexOfAny("lh", 3); + assert(i == 8, to!string(i)); +} + +@safe pure unittest +{ + import std.conv : to; + + foreach (S; AliasSeq!(string, wstring, dstring)) + { + auto r = to!S("").indexOfAny("hello"); + assert(r == -1, to!string(r)); + + r = to!S("hello").indexOfAny(""); + assert(r == -1, to!string(r)); + + r = to!S("").indexOfAny(""); + assert(r == -1, to!string(r)); + } +} + +@safe pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + foreach (S; AliasSeq!(string, wstring, dstring)) + { + foreach (T; AliasSeq!(string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + assert(indexOfAny(cast(S) null, to!T("a")) == -1); + assert(indexOfAny(to!S("def"), to!T("rsa")) == -1); + assert(indexOfAny(to!S("abba"), to!T("a")) == 0); + assert(indexOfAny(to!S("def"), to!T("f")) == 2); + assert(indexOfAny(to!S("dfefffg"), to!T("fgh")) == 1); + assert(indexOfAny(to!S("dfeffgfff"), to!T("feg")) == 1); + + assert(indexOfAny(to!S("zfeffgfff"), to!T("ACDC"), + No.caseSensitive) == -1); + assert(indexOfAny(to!S("def"), to!T("MI6"), + No.caseSensitive) == -1); + assert(indexOfAny(to!S("abba"), to!T("DEA"), + No.caseSensitive) == 0); + assert(indexOfAny(to!S("def"), to!T("FBI"), No.caseSensitive) == 2); + assert(indexOfAny(to!S("dfefffg"), to!T("NSA"), No.caseSensitive) + == -1); + assert(indexOfAny(to!S("dfeffgfff"), to!T("BND"), + No.caseSensitive) == 0); + assert(indexOfAny(to!S("dfeffgfff"), to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"), + No.caseSensitive) == 0); + + assert(indexOfAny("\u0100", to!T("\u0100"), No.caseSensitive) == 0); + }(); + } + } + ); +} + +@safe pure unittest +{ + import std.conv : to; + import std.traits : EnumMembers; + + foreach (S; AliasSeq!(string, wstring, dstring)) + { + foreach (T; AliasSeq!(string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + assert(indexOfAny(cast(S) null, to!T("a"), 1337) == -1); + assert(indexOfAny(to!S("def"), to!T("AaF"), 0) == -1); + assert(indexOfAny(to!S("abba"), to!T("NSa"), 2) == 3); + assert(indexOfAny(to!S("def"), to!T("fbi"), 1) == 2); + assert(indexOfAny(to!S("dfefffg"), to!T("foo"), 2) == 3); + assert(indexOfAny(to!S("dfeffgfff"), to!T("fsb"), 5) == 6); + + assert(indexOfAny(to!S("dfeffgfff"), to!T("NDS"), 1, + No.caseSensitive) == -1); + assert(indexOfAny(to!S("def"), to!T("DRS"), 2, + No.caseSensitive) == -1); + assert(indexOfAny(to!S("abba"), to!T("SI"), 3, + No.caseSensitive) == -1); + assert(indexOfAny(to!S("deO"), to!T("ASIO"), 1, + No.caseSensitive) == 2); + assert(indexOfAny(to!S("dfefffg"), to!T("fbh"), 2, + No.caseSensitive) == 3); + assert(indexOfAny(to!S("dfeffgfff"), to!T("fEe"), 4, + No.caseSensitive) == 4); + assert(indexOfAny(to!S("dfeffgffföä"), to!T("föä"), 9, + No.caseSensitive) == 9); + + assert(indexOfAny("\u0100", to!T("\u0100"), 0, + No.caseSensitive) == 0); + }(); + + foreach (cs; EnumMembers!CaseSensitive) + { + assert(indexOfAny("hello\U00010143\u0100\U00010143", + to!S("e\u0100"), 3, cs) == 9); + assert(indexOfAny("hello\U00010143\u0100\U00010143"w, + to!S("h\u0100"), 3, cs) == 7); + assert(indexOfAny("hello\U00010143\u0100\U00010143"d, + to!S("l\u0100"), 5, cs) == 6); + } + } +} + +/** + Returns the index of the last occurrence of any of the elements in $(D + needles) in $(D haystack). If no element of $(D needles) is found, + then $(D -1) is returned. The $(D stopIdx) slices $(D haystack) in the + following way $(D s[0 .. stopIdx]). $(D stopIdx) represents a codeunit + index in $(D haystack). If the sequence ending at $(D startIdx) does not + represent a well formed codepoint, then a $(REF UTFException, std,utf) may be + thrown. + + Params: + haystack = String to search for needles in. + needles = Strings to search for in haystack. + stopIdx = slices haystack like this $(D haystack[0 .. stopIdx]). If + the stopIdx is greater equal the length of haystack the functions + returns $(D -1). + cs = Indicates whether the comparisons are case sensitive. +*/ +ptrdiff_t lastIndexOfAny(Char,Char2)(const(Char)[] haystack, + const(Char2)[] needles, in CaseSensitive cs = Yes.caseSensitive) + @safe pure +if (isSomeChar!Char && isSomeChar!Char2) +{ + return indexOfAnyNeitherImpl!(false, true)(haystack, needles, cs); +} + +/// Ditto +ptrdiff_t lastIndexOfAny(Char,Char2)(const(Char)[] haystack, + const(Char2)[] needles, in size_t stopIdx, + in CaseSensitive cs = Yes.caseSensitive) @safe pure +if (isSomeChar!Char && isSomeChar!Char2) +{ + if (stopIdx <= haystack.length) + { + return lastIndexOfAny(haystack[0u .. stopIdx], needles, cs); + } + + return -1; +} + +/// +@safe pure unittest +{ + ptrdiff_t i = "helloWorld".lastIndexOfAny("Wlo"); + assert(i == 8); + + i = "Foo öäöllo world".lastIndexOfAny("öF"); + assert(i == 8); +} + +/// +@safe pure unittest +{ + import std.conv : to; + + ptrdiff_t i = "helloWorld".lastIndexOfAny("Wlo", 4); + assert(i == 3); + + i = "Foo öäöllo world".lastIndexOfAny("öF", 3); + assert(i == 0); +} + +@safe pure unittest +{ + import std.conv : to; + + foreach (S; AliasSeq!(string, wstring, dstring)) + { + auto r = to!S("").lastIndexOfAny("hello"); + assert(r == -1, to!string(r)); + + r = to!S("hello").lastIndexOfAny(""); + assert(r == -1, to!string(r)); + + r = to!S("").lastIndexOfAny(""); + assert(r == -1, to!string(r)); + } +} + +@safe pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + foreach (S; AliasSeq!(string, wstring, dstring)) + { + foreach (T; AliasSeq!(string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + assert(lastIndexOfAny(cast(S) null, to!T("a")) == -1); + assert(lastIndexOfAny(to!S("def"), to!T("rsa")) == -1); + assert(lastIndexOfAny(to!S("abba"), to!T("a")) == 3); + assert(lastIndexOfAny(to!S("def"), to!T("f")) == 2); + assert(lastIndexOfAny(to!S("dfefffg"), to!T("fgh")) == 6); + + ptrdiff_t oeIdx = 9; + if (is(S == wstring) || is(S == dstring)) + { + oeIdx = 8; + } + + auto foundOeIdx = lastIndexOfAny(to!S("dfeffgföf"), to!T("feg")); + assert(foundOeIdx == oeIdx, to!string(foundOeIdx)); + + assert(lastIndexOfAny(to!S("zfeffgfff"), to!T("ACDC"), + No.caseSensitive) == -1); + assert(lastIndexOfAny(to!S("def"), to!T("MI6"), + No.caseSensitive) == -1); + assert(lastIndexOfAny(to!S("abba"), to!T("DEA"), + No.caseSensitive) == 3); + assert(lastIndexOfAny(to!S("def"), to!T("FBI"), + No.caseSensitive) == 2); + assert(lastIndexOfAny(to!S("dfefffg"), to!T("NSA"), + No.caseSensitive) == -1); + + oeIdx = 2; + if (is(S == wstring) || is(S == dstring)) + { + oeIdx = 1; + } + assert(lastIndexOfAny(to!S("ödfeffgfff"), to!T("BND"), + No.caseSensitive) == oeIdx); + + assert(lastIndexOfAny("\u0100", to!T("\u0100"), + No.caseSensitive) == 0); + }(); + } + } + ); +} + +@safe pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + foreach (S; AliasSeq!(string, wstring, dstring)) + { + foreach (T; AliasSeq!(string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + enum typeStr = S.stringof ~ " " ~ T.stringof; + + assert(lastIndexOfAny(cast(S) null, to!T("a"), 1337) == -1, + typeStr); + assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("c"), 7) == 6, + typeStr); + assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("cd"), 5) == 3, + typeStr); + assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("ef"), 6) == 5, + typeStr); + assert(lastIndexOfAny(to!S("abcdefCdef"), to!T("c"), 8) == 2, + typeStr); + assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("x"), 7) == -1, + typeStr); + assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("xy"), 4) == -1, + typeStr); + assert(lastIndexOfAny(to!S("öabcdefcdef"), to!T("ö"), 2) == 0, + typeStr); + + assert(lastIndexOfAny(cast(S) null, to!T("a"), 1337, + No.caseSensitive) == -1, typeStr); + assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("C"), 7, + No.caseSensitive) == 6, typeStr); + assert(lastIndexOfAny(to!S("ABCDEFCDEF"), to!T("cd"), 5, + No.caseSensitive) == 3, typeStr); + assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("EF"), 6, + No.caseSensitive) == 5, typeStr); + assert(lastIndexOfAny(to!S("ABCDEFcDEF"), to!T("C"), 8, + No.caseSensitive) == 6, typeStr); + assert(lastIndexOfAny(to!S("ABCDEFCDEF"), to!T("x"), 7, + No.caseSensitive) == -1, typeStr); + assert(lastIndexOfAny(to!S("abCdefcdef"), to!T("XY"), 4, + No.caseSensitive) == -1, typeStr); + assert(lastIndexOfAny(to!S("ÖABCDEFCDEF"), to!T("ö"), 2, + No.caseSensitive) == 0, typeStr); + }(); + } + } + ); +} + +/** + Returns the index of the first occurrence of any character not an elements + in $(D needles) in $(D haystack). If all element of $(D haystack) are + element of $(D needles) $(D -1) is returned. + + Params: + haystack = String to search for needles in. + needles = Strings to search for in haystack. + startIdx = slices haystack like this $(D haystack[startIdx .. $]). If + the startIdx is greater equal the length of haystack the functions + returns $(D -1). + cs = Indicates whether the comparisons are case sensitive. +*/ +ptrdiff_t indexOfNeither(Char,Char2)(const(Char)[] haystack, + const(Char2)[] needles, in CaseSensitive cs = Yes.caseSensitive) + @safe pure +if (isSomeChar!Char && isSomeChar!Char2) +{ + return indexOfAnyNeitherImpl!(true, false)(haystack, needles, cs); +} + +/// Ditto +ptrdiff_t indexOfNeither(Char,Char2)(const(Char)[] haystack, + const(Char2)[] needles, in size_t startIdx, + in CaseSensitive cs = Yes.caseSensitive) + @safe pure +if (isSomeChar!Char && isSomeChar!Char2) +{ + if (startIdx < haystack.length) + { + ptrdiff_t foundIdx = indexOfAnyNeitherImpl!(true, false)( + haystack[startIdx .. $], needles, cs); + if (foundIdx != -1) + { + return foundIdx + cast(ptrdiff_t) startIdx; + } + } + return -1; +} + +/// +@safe pure unittest +{ + assert(indexOfNeither("abba", "a", 2) == 2); + assert(indexOfNeither("def", "de", 1) == 2); + assert(indexOfNeither("dfefffg", "dfe", 4) == 6); +} + +/// +@safe pure unittest +{ + assert(indexOfNeither("def", "a") == 0); + assert(indexOfNeither("def", "de") == 2); + assert(indexOfNeither("dfefffg", "dfe") == 6); +} + +@safe pure unittest +{ + import std.conv : to; + + foreach (S; AliasSeq!(string, wstring, dstring)) + { + auto r = to!S("").indexOfNeither("hello"); + assert(r == -1, to!string(r)); + + r = to!S("hello").indexOfNeither(""); + assert(r == 0, to!string(r)); + + r = to!S("").indexOfNeither(""); + assert(r == -1, to!string(r)); + } +} + +@safe pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + foreach (S; AliasSeq!(string, wstring, dstring)) + { + foreach (T; AliasSeq!(string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + assert(indexOfNeither(cast(S) null, to!T("a")) == -1); + assert(indexOfNeither("abba", "a") == 1); + + assert(indexOfNeither(to!S("dfeffgfff"), to!T("a"), + No.caseSensitive) == 0); + assert(indexOfNeither(to!S("def"), to!T("D"), + No.caseSensitive) == 1); + assert(indexOfNeither(to!S("ABca"), to!T("a"), + No.caseSensitive) == 1); + assert(indexOfNeither(to!S("def"), to!T("f"), + No.caseSensitive) == 0); + assert(indexOfNeither(to!S("DfEfffg"), to!T("dFe"), + No.caseSensitive) == 6); + if (is(S == string)) + { + assert(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"), + No.caseSensitive) == 8, + to!string(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"), + No.caseSensitive))); + } + else + { + assert(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"), + No.caseSensitive) == 7, + to!string(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"), + No.caseSensitive))); + } + }(); + } + } + ); +} + +@safe pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + foreach (S; AliasSeq!(string, wstring, dstring)) + { + foreach (T; AliasSeq!(string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + assert(indexOfNeither(cast(S) null, to!T("a"), 1) == -1); + assert(indexOfNeither(to!S("def"), to!T("a"), 1) == 1, + to!string(indexOfNeither(to!S("def"), to!T("a"), 1))); + + assert(indexOfNeither(to!S("dfeffgfff"), to!T("a"), 4, + No.caseSensitive) == 4); + assert(indexOfNeither(to!S("def"), to!T("D"), 2, + No.caseSensitive) == 2); + assert(indexOfNeither(to!S("ABca"), to!T("a"), 3, + No.caseSensitive) == -1); + assert(indexOfNeither(to!S("def"), to!T("tzf"), 2, + No.caseSensitive) == -1); + assert(indexOfNeither(to!S("DfEfffg"), to!T("dFe"), 5, + No.caseSensitive) == 6); + if (is(S == string)) + { + assert(indexOfNeither(to!S("öDfEfffg"), to!T("äDi"), 2, + No.caseSensitive) == 3, to!string(indexOfNeither( + to!S("öDfEfffg"), to!T("äDi"), 2, No.caseSensitive))); + } + else + { + assert(indexOfNeither(to!S("öDfEfffg"), to!T("äDi"), 2, + No.caseSensitive) == 2, to!string(indexOfNeither( + to!S("öDfEfffg"), to!T("äDi"), 2, No.caseSensitive))); + } + }(); + } + } + ); +} + +/** + Returns the last index of the first occurence of any character that is not + an elements in $(D needles) in $(D haystack). If all element of + $(D haystack) are element of $(D needles) $(D -1) is returned. + + Params: + haystack = String to search for needles in. + needles = Strings to search for in haystack. + stopIdx = slices haystack like this $(D haystack[0 .. stopIdx]) If + the stopIdx is greater equal the length of haystack the functions + returns $(D -1). + cs = Indicates whether the comparisons are case sensitive. +*/ +ptrdiff_t lastIndexOfNeither(Char,Char2)(const(Char)[] haystack, + const(Char2)[] needles, in CaseSensitive cs = Yes.caseSensitive) + @safe pure +if (isSomeChar!Char && isSomeChar!Char2) +{ + return indexOfAnyNeitherImpl!(false, false)(haystack, needles, cs); +} + +/// Ditto +ptrdiff_t lastIndexOfNeither(Char,Char2)(const(Char)[] haystack, + const(Char2)[] needles, in size_t stopIdx, + in CaseSensitive cs = Yes.caseSensitive) + @safe pure +if (isSomeChar!Char && isSomeChar!Char2) +{ + if (stopIdx < haystack.length) + { + return indexOfAnyNeitherImpl!(false, false)(haystack[0 .. stopIdx], + needles, cs); + } + return -1; +} + +/// +@safe pure unittest +{ + assert(lastIndexOfNeither("abba", "a") == 2); + assert(lastIndexOfNeither("def", "f") == 1); +} + +/// +@safe pure unittest +{ + assert(lastIndexOfNeither("def", "rsa", 3) == -1); + assert(lastIndexOfNeither("abba", "a", 2) == 1); +} + +@safe pure unittest +{ + import std.conv : to; + + foreach (S; AliasSeq!(string, wstring, dstring)) + { + auto r = to!S("").lastIndexOfNeither("hello"); + assert(r == -1, to!string(r)); + + r = to!S("hello").lastIndexOfNeither(""); + assert(r == 4, to!string(r)); + + r = to!S("").lastIndexOfNeither(""); + assert(r == -1, to!string(r)); + } +} + +@safe pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + foreach (S; AliasSeq!(string, wstring, dstring)) + { + foreach (T; AliasSeq!(string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + assert(lastIndexOfNeither(cast(S) null, to!T("a")) == -1); + assert(lastIndexOfNeither(to!S("def"), to!T("rsa")) == 2); + assert(lastIndexOfNeither(to!S("dfefffg"), to!T("fgh")) == 2); + + ptrdiff_t oeIdx = 8; + if (is(S == string)) + { + oeIdx = 9; + } + + auto foundOeIdx = lastIndexOfNeither(to!S("ödfefegff"), to!T("zeg")); + assert(foundOeIdx == oeIdx, to!string(foundOeIdx)); + + assert(lastIndexOfNeither(to!S("zfeffgfsb"), to!T("FSB"), + No.caseSensitive) == 5); + assert(lastIndexOfNeither(to!S("def"), to!T("MI6"), + No.caseSensitive) == 2, to!string(lastIndexOfNeither(to!S("def"), + to!T("MI6"), No.caseSensitive))); + assert(lastIndexOfNeither(to!S("abbadeafsb"), to!T("fSb"), + No.caseSensitive) == 6, to!string(lastIndexOfNeither( + to!S("abbadeafsb"), to!T("fSb"), No.caseSensitive))); + assert(lastIndexOfNeither(to!S("defbi"), to!T("FBI"), + No.caseSensitive) == 1); + assert(lastIndexOfNeither(to!S("dfefffg"), to!T("NSA"), + No.caseSensitive) == 6); + assert(lastIndexOfNeither(to!S("dfeffgfffö"), to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"), + No.caseSensitive) == 8, to!string(lastIndexOfNeither(to!S("dfeffgfffö"), + to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"), No.caseSensitive))); + }(); + } + } + ); +} + +@safe pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + foreach (S; AliasSeq!(string, wstring, dstring)) + { + foreach (T; AliasSeq!(string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + assert(lastIndexOfNeither(cast(S) null, to!T("a"), 1337) == -1); + assert(lastIndexOfNeither(to!S("def"), to!T("f")) == 1); + assert(lastIndexOfNeither(to!S("dfefffg"), to!T("fgh")) == 2); + + ptrdiff_t oeIdx = 4; + if (is(S == string)) + { + oeIdx = 5; + } + + auto foundOeIdx = lastIndexOfNeither(to!S("ödfefegff"), to!T("zeg"), + 7); + assert(foundOeIdx == oeIdx, to!string(foundOeIdx)); + + assert(lastIndexOfNeither(to!S("zfeffgfsb"), to!T("FSB"), 6, + No.caseSensitive) == 5); + assert(lastIndexOfNeither(to!S("def"), to!T("MI6"), 2, + No.caseSensitive) == 1, to!string(lastIndexOfNeither(to!S("def"), + to!T("MI6"), 2, No.caseSensitive))); + assert(lastIndexOfNeither(to!S("abbadeafsb"), to!T("fSb"), 6, + No.caseSensitive) == 5, to!string(lastIndexOfNeither( + to!S("abbadeafsb"), to!T("fSb"), 6, No.caseSensitive))); + assert(lastIndexOfNeither(to!S("defbi"), to!T("FBI"), 3, + No.caseSensitive) == 1); + assert(lastIndexOfNeither(to!S("dfefffg"), to!T("NSA"), 2, + No.caseSensitive) == 1, to!string(lastIndexOfNeither( + to!S("dfefffg"), to!T("NSA"), 2, No.caseSensitive))); + }(); + } + } + ); +} + +/** + * Returns the _representation of a string, which has the same type + * as the string except the character type is replaced by $(D ubyte), + * $(D ushort), or $(D uint) depending on the character width. + * + * Params: + * s = The string to return the _representation of. + * + * Returns: + * The _representation of the passed string. + */ +auto representation(Char)(Char[] s) @safe pure nothrow @nogc +if (isSomeChar!Char) +{ + import std.traits : ModifyTypePreservingTQ; + alias ToRepType(T) = AliasSeq!(ubyte, ushort, uint)[T.sizeof / 2]; + return cast(ModifyTypePreservingTQ!(ToRepType, Char)[])s; +} + +/// +@safe pure unittest +{ + string s = "hello"; + static assert(is(typeof(representation(s)) == immutable(ubyte)[])); + assert(representation(s) is cast(immutable(ubyte)[]) s); + assert(representation(s) == [0x68, 0x65, 0x6c, 0x6c, 0x6f]); +} + +@system pure unittest +{ + import std.exception : assertCTFEable; + import std.traits : Fields; + import std.typecons : Tuple; + + assertCTFEable!( + { + void test(Char, T)(Char[] str) + { + static assert(is(typeof(representation(str)) == T[])); + assert(representation(str) is cast(T[]) str); + } + + foreach (Type; AliasSeq!(Tuple!(char , ubyte ), + Tuple!(wchar, ushort), + Tuple!(dchar, uint ))) + { + alias Char = Fields!Type[0]; + alias Int = Fields!Type[1]; + enum immutable(Char)[] hello = "hello"; + + test!( immutable Char, immutable Int)(hello); + test!( const Char, const Int)(hello); + test!( Char, Int)(hello.dup); + test!( shared Char, shared Int)(cast(shared) hello.dup); + test!(const shared Char, const shared Int)(hello); + } + }); +} + + +/** + * Capitalize the first character of $(D s) and convert the rest of $(D s) to + * lowercase. + * + * Params: + * input = The string to _capitalize. + * + * Returns: + * The capitalized string. + * + * See_Also: + * $(REF asCapitalized, std,uni) for a lazy range version that doesn't allocate memory + */ +S capitalize(S)(S input) @trusted pure +if (isSomeString!S) +{ + import std.array : array; + import std.uni : asCapitalized; + import std.utf : byUTF; + + return input.asCapitalized.byUTF!(ElementEncodingType!(S)).array; +} + +/// +pure @safe unittest +{ + assert(capitalize("hello") == "Hello"); + assert(capitalize("World") == "World"); +} + +auto capitalize(S)(auto ref S s) +if (!isSomeString!S && is(StringTypeOf!S)) +{ + return capitalize!(StringTypeOf!S)(s); +} + +@safe pure unittest +{ + assert(testAliasedString!capitalize("hello")); +} + +@safe pure unittest +{ + import std.algorithm.comparison : cmp; + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[])) + { + S s1 = to!S("FoL"); + S s2; + + s2 = capitalize(s1); + assert(cmp(s2, "Fol") == 0); + assert(s2 !is s1); + + s2 = capitalize(s1[0 .. 2]); + assert(cmp(s2, "Fo") == 0); + + s1 = to!S("fOl"); + s2 = capitalize(s1); + assert(cmp(s2, "Fol") == 0); + assert(s2 !is s1); + s1 = to!S("\u0131 \u0130"); + s2 = capitalize(s1); + assert(cmp(s2, "\u0049 i\u0307") == 0); + assert(s2 !is s1); + + s1 = to!S("\u017F \u0049"); + s2 = capitalize(s1); + assert(cmp(s2, "\u0053 \u0069") == 0); + assert(s2 !is s1); + } + }); +} + +/++ + Split $(D s) into an array of lines according to the unicode standard using + $(D '\r'), $(D '\n'), $(D "\r\n"), $(REF lineSep, std,uni), + $(REF paraSep, std,uni), $(D U+0085) (NEL), $(D '\v') and $(D '\f') + as delimiters. If $(D keepTerm) is set to $(D KeepTerminator.yes), then the + delimiter is included in the strings returned. + + Does not throw on invalid UTF; such is simply passed unchanged + to the output. + + Allocates memory; use $(LREF lineSplitter) for an alternative that + does not. + + Adheres to $(HTTP www.unicode.org/versions/Unicode7.0.0/ch05.pdf, Unicode 7.0). + + Params: + s = a string of $(D chars), $(D wchars), or $(D dchars), or any custom + type that casts to a $(D string) type + keepTerm = whether delimiter is included or not in the results + Returns: + array of strings, each element is a line that is a slice of $(D s) + See_Also: + $(LREF lineSplitter) + $(REF splitter, std,algorithm) + $(REF splitter, std,regex) + +/ +alias KeepTerminator = Flag!"keepTerminator"; + +/// ditto +S[] splitLines(S)(S s, in KeepTerminator keepTerm = No.keepTerminator) @safe pure +if (isSomeString!S) +{ + import std.array : appender; + import std.uni : lineSep, paraSep; + + size_t iStart = 0; + auto retval = appender!(S[])(); + + for (size_t i; i < s.length; ++i) + { + switch (s[i]) + { + case '\v', '\f', '\n': + retval.put(s[iStart .. i + (keepTerm == Yes.keepTerminator)]); + iStart = i + 1; + break; + + case '\r': + if (i + 1 < s.length && s[i + 1] == '\n') + { + retval.put(s[iStart .. i + (keepTerm == Yes.keepTerminator) * 2]); + iStart = i + 2; + ++i; + } + else + { + goto case '\n'; + } + break; + + static if (s[i].sizeof == 1) + { + /* Manually decode: + * lineSep is E2 80 A8 + * paraSep is E2 80 A9 + */ + case 0xE2: + if (i + 2 < s.length && + s[i + 1] == 0x80 && + (s[i + 2] == 0xA8 || s[i + 2] == 0xA9) + ) + { + retval.put(s[iStart .. i + (keepTerm == Yes.keepTerminator) * 3]); + iStart = i + 3; + i += 2; + } + else + goto default; + break; + /* Manually decode: + * NEL is C2 85 + */ + case 0xC2: + if (i + 1 < s.length && s[i + 1] == 0x85) + { + retval.put(s[iStart .. i + (keepTerm == Yes.keepTerminator) * 2]); + iStart = i + 2; + i += 1; + } + else + goto default; + break; + } + else + { + case lineSep: + case paraSep: + case '\u0085': + goto case '\n'; + } + + default: + break; + } + } + + if (iStart != s.length) + retval.put(s[iStart .. $]); + + return retval.data; +} + +/// +@safe pure nothrow unittest +{ + string s = "Hello\nmy\rname\nis"; + assert(splitLines(s) == ["Hello", "my", "name", "is"]); +} + +@safe pure nothrow unittest +{ + string s = "a\xC2\x86b"; + assert(splitLines(s) == [s]); +} + +auto splitLines(S)(auto ref S s, in KeepTerminator keepTerm = No.keepTerminator) +if (!isSomeString!S && is(StringTypeOf!S)) +{ + return splitLines!(StringTypeOf!S)(s, keepTerm); +} + +@safe pure nothrow unittest +{ + assert(testAliasedString!splitLines("hello\nworld")); +} + +@safe pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + { + auto s = to!S( + "\rpeter\n\rpaul\r\njerry\u2028ice\u2029cream\n\nsunday\n" ~ + "mon\u2030day\nschadenfreude\vkindergarten\f\vcookies\u0085" + ); + auto lines = splitLines(s); + assert(lines.length == 14); + assert(lines[0] == ""); + assert(lines[1] == "peter"); + assert(lines[2] == ""); + assert(lines[3] == "paul"); + assert(lines[4] == "jerry"); + assert(lines[5] == "ice"); + assert(lines[6] == "cream"); + assert(lines[7] == ""); + assert(lines[8] == "sunday"); + assert(lines[9] == "mon\u2030day"); + assert(lines[10] == "schadenfreude"); + assert(lines[11] == "kindergarten"); + assert(lines[12] == ""); + assert(lines[13] == "cookies"); + + + ubyte[] u = ['a', 0xFF, 0x12, 'b']; // invalid UTF + auto ulines = splitLines(cast(char[]) u); + assert(cast(ubyte[])(ulines[0]) == u); + + lines = splitLines(s, Yes.keepTerminator); + assert(lines.length == 14); + assert(lines[0] == "\r"); + assert(lines[1] == "peter\n"); + assert(lines[2] == "\r"); + assert(lines[3] == "paul\r\n"); + assert(lines[4] == "jerry\u2028"); + assert(lines[5] == "ice\u2029"); + assert(lines[6] == "cream\n"); + assert(lines[7] == "\n"); + assert(lines[8] == "sunday\n"); + assert(lines[9] == "mon\u2030day\n"); + assert(lines[10] == "schadenfreude\v"); + assert(lines[11] == "kindergarten\f"); + assert(lines[12] == "\v"); + assert(lines[13] == "cookies\u0085"); + + s.popBack(); // Lop-off trailing \n + lines = splitLines(s); + assert(lines.length == 14); + assert(lines[9] == "mon\u2030day"); + + lines = splitLines(s, Yes.keepTerminator); + assert(lines.length == 14); + assert(lines[13] == "cookies"); + } + }); +} + +private struct LineSplitter(KeepTerminator keepTerm = No.keepTerminator, Range) +{ + import std.conv : unsigned; + import std.uni : lineSep, paraSep; +private: + Range _input; + + alias IndexType = typeof(unsigned(_input.length)); + enum IndexType _unComputed = IndexType.max; + IndexType iStart = _unComputed; + IndexType iEnd = 0; + IndexType iNext = 0; + +public: + this(Range input) + { + _input = input; + } + + static if (isInfinite!Range) + { + enum bool empty = false; + } + else + { + @property bool empty() + { + return iStart == _unComputed && iNext == _input.length; + } + } + + @property typeof(_input) front() + { + if (iStart == _unComputed) + { + iStart = iNext; + Loop: + for (IndexType i = iNext; ; ++i) + { + if (i == _input.length) + { + iEnd = i; + iNext = i; + break Loop; + } + switch (_input[i]) + { + case '\v', '\f', '\n': + iEnd = i + (keepTerm == Yes.keepTerminator); + iNext = i + 1; + break Loop; + + case '\r': + if (i + 1 < _input.length && _input[i + 1] == '\n') + { + iEnd = i + (keepTerm == Yes.keepTerminator) * 2; + iNext = i + 2; + break Loop; + } + else + { + goto case '\n'; + } + + static if (_input[i].sizeof == 1) + { + /* Manually decode: + * lineSep is E2 80 A8 + * paraSep is E2 80 A9 + */ + case 0xE2: + if (i + 2 < _input.length && + _input[i + 1] == 0x80 && + (_input[i + 2] == 0xA8 || _input[i + 2] == 0xA9) + ) + { + iEnd = i + (keepTerm == Yes.keepTerminator) * 3; + iNext = i + 3; + break Loop; + } + else + goto default; + /* Manually decode: + * NEL is C2 85 + */ + case 0xC2: + if (i + 1 < _input.length && _input[i + 1] == 0x85) + { + iEnd = i + (keepTerm == Yes.keepTerminator) * 2; + iNext = i + 2; + break Loop; + } + else + goto default; + } + else + { + case '\u0085': + case lineSep: + case paraSep: + goto case '\n'; + } + + default: + break; + } + } + } + return _input[iStart .. iEnd]; + } + + void popFront() + { + if (iStart == _unComputed) + { + assert(!empty); + front; + } + iStart = _unComputed; + } + + static if (isForwardRange!Range) + { + @property typeof(this) save() + { + auto ret = this; + ret._input = _input.save; + return ret; + } + } +} + +/*********************************** + * Split an array or slicable range of characters into a range of lines + using $(D '\r'), $(D '\n'), $(D '\v'), $(D '\f'), $(D "\r\n"), + $(REF lineSep, std,uni), $(REF paraSep, std,uni) and $(D '\u0085') (NEL) + as delimiters. If $(D keepTerm) is set to $(D Yes.keepTerminator), then the + delimiter is included in the slices returned. + + Does not throw on invalid UTF; such is simply passed unchanged + to the output. + + Adheres to $(HTTP www.unicode.org/versions/Unicode7.0.0/ch05.pdf, Unicode 7.0). + + Does not allocate memory. + + Params: + r = array of $(D chars), $(D wchars), or $(D dchars) or a slicable range + keepTerm = whether delimiter is included or not in the results + Returns: + range of slices of the input range $(D r) + + See_Also: + $(LREF splitLines) + $(REF splitter, std,algorithm) + $(REF splitter, std,regex) + */ +auto lineSplitter(KeepTerminator keepTerm = No.keepTerminator, Range)(Range r) +if ((hasSlicing!Range && hasLength!Range && isSomeChar!(ElementType!Range) || + isSomeString!Range) && + !isConvertibleToString!Range) +{ + return LineSplitter!(keepTerm, Range)(r); +} + +/// +@safe pure unittest +{ + import std.array : array; + + string s = "Hello\nmy\rname\nis"; + + /* notice the call to 'array' to turn the lazy range created by + lineSplitter comparable to the string[] created by splitLines. + */ + assert(lineSplitter(s).array == splitLines(s)); +} + +auto lineSplitter(KeepTerminator keepTerm = No.keepTerminator, Range)(auto ref Range r) +if (isConvertibleToString!Range) +{ + return LineSplitter!(keepTerm, StringTypeOf!Range)(r); +} + +@safe pure unittest +{ + import std.array : array; + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + { + auto s = to!S( + "\rpeter\n\rpaul\r\njerry\u2028ice\u2029cream\n\n" ~ + "sunday\nmon\u2030day\nschadenfreude\vkindergarten\f\vcookies\u0085" + ); + + auto lines = lineSplitter(s).array; + assert(lines.length == 14); + assert(lines[0] == ""); + assert(lines[1] == "peter"); + assert(lines[2] == ""); + assert(lines[3] == "paul"); + assert(lines[4] == "jerry"); + assert(lines[5] == "ice"); + assert(lines[6] == "cream"); + assert(lines[7] == ""); + assert(lines[8] == "sunday"); + assert(lines[9] == "mon\u2030day"); + assert(lines[10] == "schadenfreude"); + assert(lines[11] == "kindergarten"); + assert(lines[12] == ""); + assert(lines[13] == "cookies"); + + + ubyte[] u = ['a', 0xFF, 0x12, 'b']; // invalid UTF + auto ulines = lineSplitter(cast(char[]) u).array; + assert(cast(ubyte[])(ulines[0]) == u); + + lines = lineSplitter!(Yes.keepTerminator)(s).array; + assert(lines.length == 14); + assert(lines[0] == "\r"); + assert(lines[1] == "peter\n"); + assert(lines[2] == "\r"); + assert(lines[3] == "paul\r\n"); + assert(lines[4] == "jerry\u2028"); + assert(lines[5] == "ice\u2029"); + assert(lines[6] == "cream\n"); + assert(lines[7] == "\n"); + assert(lines[8] == "sunday\n"); + assert(lines[9] == "mon\u2030day\n"); + assert(lines[10] == "schadenfreude\v"); + assert(lines[11] == "kindergarten\f"); + assert(lines[12] == "\v"); + assert(lines[13] == "cookies\u0085"); + + s.popBack(); // Lop-off trailing \n + lines = lineSplitter(s).array; + assert(lines.length == 14); + assert(lines[9] == "mon\u2030day"); + + lines = lineSplitter!(Yes.keepTerminator)(s).array; + assert(lines.length == 14); + assert(lines[13] == "cookies"); + } + }); +} + +/// +@nogc @safe pure unittest +{ + auto s = "\rpeter\n\rpaul\r\njerry\u2028ice\u2029cream\n\nsunday\nmon\u2030day\n"; + auto lines = s.lineSplitter(); + static immutable witness = ["", "peter", "", "paul", "jerry", "ice", "cream", "", "sunday", "mon\u2030day"]; + uint i; + foreach (line; lines) + { + assert(line == witness[i++]); + } + assert(i == witness.length); +} + +@nogc @safe pure unittest +{ + import std.algorithm.comparison : equal; + auto s = "std/string.d"; + auto as = TestAliasedString(s); + assert(equal(s.lineSplitter(), as.lineSplitter())); +} + +@safe pure unittest +{ + auto s = "line1\nline2"; + auto spl0 = s.lineSplitter!(Yes.keepTerminator); + auto spl1 = spl0.save; + spl0.popFront; + assert(spl1.front ~ spl0.front == s); + string r = "a\xC2\x86b"; + assert(r.lineSplitter.front == r); +} + +/++ + Strips leading whitespace (as defined by $(REF isWhite, std,uni)). + + Params: + input = string or $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) + of characters + + Returns: $(D input) stripped of leading whitespace. + + Postconditions: $(D input) and the returned value + will share the same tail (see $(REF sameTail, std,array)). + + See_Also: + Generic stripping on ranges: $(REF _stripLeft, std, algorithm, mutation) + +/ +auto stripLeft(Range)(Range input) +if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) && + !isInfinite!Range && !isConvertibleToString!Range) +{ + static import std.ascii; + static import std.uni; + import std.utf : decodeFront; + + while (!input.empty) + { + auto c = input.front; + if (std.ascii.isASCII(c)) + { + if (!std.ascii.isWhite(c)) + break; + input.popFront(); + } + else + { + auto save = input.save; + auto dc = decodeFront(input); + if (!std.uni.isWhite(dc)) + return save; + } + } + return input; +} + +/// +@safe pure unittest +{ + import std.uni : lineSep, paraSep; + assert(stripLeft(" hello world ") == + "hello world "); + assert(stripLeft("\n\t\v\rhello world\n\t\v\r") == + "hello world\n\t\v\r"); + assert(stripLeft("hello world") == + "hello world"); + assert(stripLeft([lineSep] ~ "hello world" ~ lineSep) == + "hello world" ~ [lineSep]); + assert(stripLeft([paraSep] ~ "hello world" ~ paraSep) == + "hello world" ~ [paraSep]); + + import std.array : array; + import std.utf : byChar; + assert(stripLeft(" hello world "w.byChar).array == + "hello world "); +} + +auto stripLeft(Range)(auto ref Range str) +if (isConvertibleToString!Range) +{ + return stripLeft!(StringTypeOf!Range)(str); +} + +@safe pure unittest +{ + assert(testAliasedString!stripLeft(" hello")); +} + +/++ + Strips trailing whitespace (as defined by $(REF isWhite, std,uni)). + + Params: + str = string or random access range of characters + + Returns: + slice of $(D str) stripped of trailing whitespace. + + See_Also: + Generic stripping on ranges: $(REF _stripRight, std, algorithm, mutation) + +/ +auto stripRight(Range)(Range str) +if (isSomeString!Range || + isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range && + !isConvertibleToString!Range && + isSomeChar!(ElementEncodingType!Range)) +{ + import std.uni : isWhite; + alias C = Unqual!(ElementEncodingType!(typeof(str))); + + static if (isSomeString!(typeof(str))) + { + import std.utf : codeLength; + + foreach_reverse (i, dchar c; str) + { + if (!isWhite(c)) + return str[0 .. i + codeLength!C(c)]; + } + + return str[0 .. 0]; + } + else + { + size_t i = str.length; + while (i--) + { + static if (C.sizeof == 4) + { + if (isWhite(str[i])) + continue; + break; + } + else static if (C.sizeof == 2) + { + auto c2 = str[i]; + if (c2 < 0xD800 || c2 >= 0xE000) + { + if (isWhite(c2)) + continue; + } + else if (c2 >= 0xDC00) + { + if (i) + { + immutable c1 = str[i - 1]; + if (c1 >= 0xD800 && c1 < 0xDC00) + { + immutable dchar c = ((c1 - 0xD7C0) << 10) + (c2 - 0xDC00); + if (isWhite(c)) + { + --i; + continue; + } + } + } + } + break; + } + else static if (C.sizeof == 1) + { + import std.utf : byDchar; + + char cx = str[i]; + if (cx <= 0x7F) + { + if (isWhite(cx)) + continue; + break; + } + else + { + size_t stride = 0; + + while (1) + { + ++stride; + if (!i || (cx & 0xC0) == 0xC0 || stride == 4) + break; + cx = str[i - 1]; + if (!(cx & 0x80)) + break; + --i; + } + + if (!str[i .. i + stride].byDchar.front.isWhite) + return str[0 .. i + stride]; + } + } + else + static assert(0); + } + + return str[0 .. i + 1]; + } +} + +/// +@safe pure +unittest +{ + import std.uni : lineSep, paraSep; + assert(stripRight(" hello world ") == + " hello world"); + assert(stripRight("\n\t\v\rhello world\n\t\v\r") == + "\n\t\v\rhello world"); + assert(stripRight("hello world") == + "hello world"); + assert(stripRight([lineSep] ~ "hello world" ~ lineSep) == + [lineSep] ~ "hello world"); + assert(stripRight([paraSep] ~ "hello world" ~ paraSep) == + [paraSep] ~ "hello world"); +} + +auto stripRight(Range)(auto ref Range str) +if (isConvertibleToString!Range) +{ + return stripRight!(StringTypeOf!Range)(str); +} + +@safe pure unittest +{ + assert(testAliasedString!stripRight("hello ")); +} + +@safe pure unittest +{ + import std.array : array; + import std.uni : lineSep, paraSep; + import std.utf : byChar, byDchar, byUTF, byWchar, invalidUTFstrings; + assert(stripRight(" hello world ".byChar).array == " hello world"); + assert(stripRight("\n\t\v\rhello world\n\t\v\r"w.byWchar).array == "\n\t\v\rhello world"w); + assert(stripRight("hello world"d.byDchar).array == "hello world"d); + assert(stripRight("\u2028hello world\u2020\u2028".byChar).array == "\u2028hello world\u2020"); + assert(stripRight("hello world\U00010001"w.byWchar).array == "hello world\U00010001"w); + + foreach (C; AliasSeq!(char, wchar, dchar)) + { + foreach (s; invalidUTFstrings!C()) + { + cast(void) stripRight(s.byUTF!C).array; + } + } + + cast(void) stripRight("a\x80".byUTF!char).array; + wstring ws = ['a', cast(wchar) 0xDC00]; + cast(void) stripRight(ws.byUTF!wchar).array; +} + + +/++ + Strips both leading and trailing whitespace (as defined by + $(REF isWhite, std,uni)). + + Params: + str = string or random access range of characters + + Returns: + slice of $(D str) stripped of leading and trailing whitespace. + + See_Also: + Generic stripping on ranges: $(REF _strip, std, algorithm, mutation) + +/ +auto strip(Range)(Range str) +if (isSomeString!Range || + isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range && + !isConvertibleToString!Range && + isSomeChar!(ElementEncodingType!Range)) +{ + return stripRight(stripLeft(str)); +} + +/// +@safe pure unittest +{ + import std.uni : lineSep, paraSep; + assert(strip(" hello world ") == + "hello world"); + assert(strip("\n\t\v\rhello world\n\t\v\r") == + "hello world"); + assert(strip("hello world") == + "hello world"); + assert(strip([lineSep] ~ "hello world" ~ [lineSep]) == + "hello world"); + assert(strip([paraSep] ~ "hello world" ~ [paraSep]) == + "hello world"); +} + +auto strip(Range)(auto ref Range str) +if (isConvertibleToString!Range) +{ + return strip!(StringTypeOf!Range)(str); +} + +@safe pure unittest +{ + assert(testAliasedString!strip(" hello world ")); +} + +@safe pure unittest +{ + import std.algorithm.comparison : equal; + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + foreach (S; AliasSeq!( char[], const char[], string, + wchar[], const wchar[], wstring, + dchar[], const dchar[], dstring)) + { + assert(equal(stripLeft(to!S(" foo\t ")), "foo\t ")); + assert(equal(stripLeft(to!S("\u2008 foo\t \u2007")), "foo\t \u2007")); + assert(equal(stripLeft(to!S("\u0085 μ \u0085 \u00BB \r")), "μ \u0085 \u00BB \r")); + assert(equal(stripLeft(to!S("1")), "1")); + assert(equal(stripLeft(to!S("\U0010FFFE")), "\U0010FFFE")); + assert(equal(stripLeft(to!S("")), "")); + + assert(equal(stripRight(to!S(" foo\t ")), " foo")); + assert(equal(stripRight(to!S("\u2008 foo\t \u2007")), "\u2008 foo")); + assert(equal(stripRight(to!S("\u0085 μ \u0085 \u00BB \r")), "\u0085 μ \u0085 \u00BB")); + assert(equal(stripRight(to!S("1")), "1")); + assert(equal(stripRight(to!S("\U0010FFFE")), "\U0010FFFE")); + assert(equal(stripRight(to!S("")), "")); + + assert(equal(strip(to!S(" foo\t ")), "foo")); + assert(equal(strip(to!S("\u2008 foo\t \u2007")), "foo")); + assert(equal(strip(to!S("\u0085 μ \u0085 \u00BB \r")), "μ \u0085 \u00BB")); + assert(equal(strip(to!S("\U0010FFFE")), "\U0010FFFE")); + assert(equal(strip(to!S("")), "")); + } + }); +} + +@safe pure unittest +{ + import std.array : sameHead, sameTail; + import std.exception : assertCTFEable; + assertCTFEable!( + { + wstring s = " "; + assert(s.sameTail(s.stripLeft())); + assert(s.sameHead(s.stripRight())); + }); +} + + +/++ + If $(D str) ends with $(D delimiter), then $(D str) is returned without + $(D delimiter) on its end. If it $(D str) does $(I not) end with + $(D delimiter), then it is returned unchanged. + + If no $(D delimiter) is given, then one trailing $(D '\r'), $(D '\n'), + $(D "\r\n"), $(D '\f'), $(D '\v'), $(REF lineSep, std,uni), $(REF paraSep, std,uni), or $(REF nelSep, std,uni) + is removed from the end of $(D str). If $(D str) does not end with any of those characters, + then it is returned unchanged. + + Params: + str = string or indexable range of characters + delimiter = string of characters to be sliced off end of str[] + + Returns: + slice of str + +/ +Range chomp(Range)(Range str) +if ((isRandomAccessRange!Range && isSomeChar!(ElementEncodingType!Range) || + isNarrowString!Range) && + !isConvertibleToString!Range) +{ + import std.uni : lineSep, paraSep, nelSep; + if (str.empty) + return str; + + alias C = ElementEncodingType!Range; + + switch (str[$ - 1]) + { + case '\n': + { + if (str.length > 1 && str[$ - 2] == '\r') + return str[0 .. $ - 2]; + goto case; + } + case '\r', '\v', '\f': + return str[0 .. $ - 1]; + + // Pop off the last character if lineSep, paraSep, or nelSep + static if (is(C : const char)) + { + /* Manually decode: + * lineSep is E2 80 A8 + * paraSep is E2 80 A9 + */ + case 0xA8: // Last byte of lineSep + case 0xA9: // Last byte of paraSep + if (str.length > 2 && str[$ - 2] == 0x80 && str[$ - 3] == 0xE2) + return str [0 .. $ - 3]; + goto default; + + /* Manually decode: + * NEL is C2 85 + */ + case 0x85: + if (str.length > 1 && str[$ - 2] == 0xC2) + return str [0 .. $ - 2]; + goto default; + } + else + { + case lineSep: + case paraSep: + case nelSep: + return str[0 .. $ - 1]; + } + default: + return str; + } +} + +/// Ditto +Range chomp(Range, C2)(Range str, const(C2)[] delimiter) +if ((isBidirectionalRange!Range && isSomeChar!(ElementEncodingType!Range) || + isNarrowString!Range) && + !isConvertibleToString!Range && + isSomeChar!C2) +{ + if (delimiter.empty) + return chomp(str); + + alias C1 = ElementEncodingType!Range; + + static if (is(Unqual!C1 == Unqual!C2) && (isSomeString!Range || (hasSlicing!Range && C2.sizeof == 4))) + { + import std.algorithm.searching : endsWith; + if (str.endsWith(delimiter)) + return str[0 .. $ - delimiter.length]; + return str; + } + else + { + auto orig = str.save; + + static if (isSomeString!Range) + alias C = dchar; // because strings auto-decode + else + alias C = C1; // and ranges do not + + foreach_reverse (C c; delimiter) + { + if (str.empty || str.back != c) + return orig; + + str.popBack(); + } + + return str; + } +} + +/// +@safe pure +unittest +{ + import std.uni : lineSep, paraSep, nelSep; + import std.utf : decode; + assert(chomp(" hello world \n\r") == " hello world \n"); + assert(chomp(" hello world \r\n") == " hello world "); + assert(chomp(" hello world \f") == " hello world "); + assert(chomp(" hello world \v") == " hello world "); + assert(chomp(" hello world \n\n") == " hello world \n"); + assert(chomp(" hello world \n\n ") == " hello world \n\n "); + assert(chomp(" hello world \n\n" ~ [lineSep]) == " hello world \n\n"); + assert(chomp(" hello world \n\n" ~ [paraSep]) == " hello world \n\n"); + assert(chomp(" hello world \n\n" ~ [ nelSep]) == " hello world \n\n"); + assert(chomp(" hello world") == " hello world"); + assert(chomp("") == ""); + + assert(chomp(" hello world", "orld") == " hello w"); + assert(chomp(" hello world", " he") == " hello world"); + assert(chomp("", "hello") == ""); + + // Don't decode pointlessly + assert(chomp("hello\xFE", "\r") == "hello\xFE"); +} + +StringTypeOf!Range chomp(Range)(auto ref Range str) +if (isConvertibleToString!Range) +{ + return chomp!(StringTypeOf!Range)(str); +} + +StringTypeOf!Range chomp(Range, C2)(auto ref Range str, const(C2)[] delimiter) +if (isConvertibleToString!Range) +{ + return chomp!(StringTypeOf!Range, C2)(str, delimiter); +} + +@safe pure unittest +{ + assert(testAliasedString!chomp(" hello world \n\r")); + assert(testAliasedString!chomp(" hello world", "orld")); +} + +@safe pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + string s; + + assertCTFEable!( + { + foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + { + // @@@ BUG IN COMPILER, MUST INSERT CAST + assert(chomp(cast(S) null) is null); + assert(chomp(to!S("hello")) == "hello"); + assert(chomp(to!S("hello\n")) == "hello"); + assert(chomp(to!S("hello\r")) == "hello"); + assert(chomp(to!S("hello\r\n")) == "hello"); + assert(chomp(to!S("hello\n\r")) == "hello\n"); + assert(chomp(to!S("hello\n\n")) == "hello\n"); + assert(chomp(to!S("hello\r\r")) == "hello\r"); + assert(chomp(to!S("hello\nxxx\n")) == "hello\nxxx"); + assert(chomp(to!S("hello\u2028")) == "hello"); + assert(chomp(to!S("hello\u2029")) == "hello"); + assert(chomp(to!S("hello\u0085")) == "hello"); + assert(chomp(to!S("hello\u2028\u2028")) == "hello\u2028"); + assert(chomp(to!S("hello\u2029\u2029")) == "hello\u2029"); + assert(chomp(to!S("hello\u2029\u2129")) == "hello\u2029\u2129"); + assert(chomp(to!S("hello\u2029\u0185")) == "hello\u2029\u0185"); + + foreach (T; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + // @@@ BUG IN COMPILER, MUST INSERT CAST + assert(chomp(cast(S) null, cast(T) null) is null); + assert(chomp(to!S("hello\n"), cast(T) null) == "hello"); + assert(chomp(to!S("hello"), to!T("o")) == "hell"); + assert(chomp(to!S("hello"), to!T("p")) == "hello"); + // @@@ BUG IN COMPILER, MUST INSERT CAST + assert(chomp(to!S("hello"), cast(T) null) == "hello"); + assert(chomp(to!S("hello"), to!T("llo")) == "he"); + assert(chomp(to!S("\uFF28ello"), to!T("llo")) == "\uFF28e"); + assert(chomp(to!S("\uFF28el\uFF4co"), to!T("l\uFF4co")) == "\uFF28e"); + }(); + } + }); + + // Ranges + import std.array : array; + import std.utf : byChar, byWchar, byDchar; + assert(chomp("hello world\r\n" .byChar ).array == "hello world"); + assert(chomp("hello world\r\n"w.byWchar).array == "hello world"w); + assert(chomp("hello world\r\n"d.byDchar).array == "hello world"d); + + assert(chomp("hello world"d.byDchar, "ld").array == "hello wor"d); + + assert(chomp("hello\u2020" .byChar , "\u2020").array == "hello"); + assert(chomp("hello\u2020"d.byDchar, "\u2020"d).array == "hello"d); +} + + +/++ + If $(D str) starts with $(D delimiter), then the part of $(D str) following + $(D delimiter) is returned. If $(D str) does $(I not) start with + + $(D delimiter), then it is returned unchanged. + + Params: + str = string or $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) + of characters + delimiter = string of characters to be sliced off front of str[] + + Returns: + slice of str + +/ +Range chompPrefix(Range, C2)(Range str, const(C2)[] delimiter) +if ((isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) || + isNarrowString!Range) && + !isConvertibleToString!Range && + isSomeChar!C2) +{ + alias C1 = ElementEncodingType!Range; + + static if (is(Unqual!C1 == Unqual!C2) && (isSomeString!Range || (hasSlicing!Range && C2.sizeof == 4))) + { + import std.algorithm.searching : startsWith; + if (str.startsWith(delimiter)) + return str[delimiter.length .. $]; + return str; + } + else + { + auto orig = str.save; + + static if (isSomeString!Range) + alias C = dchar; // because strings auto-decode + else + alias C = C1; // and ranges do not + + foreach (C c; delimiter) + { + if (str.empty || str.front != c) + return orig; + + str.popFront(); + } + + return str; + } +} + +/// +@safe pure unittest +{ + assert(chompPrefix("hello world", "he") == "llo world"); + assert(chompPrefix("hello world", "hello w") == "orld"); + assert(chompPrefix("hello world", " world") == "hello world"); + assert(chompPrefix("", "hello") == ""); +} + +StringTypeOf!Range chompPrefix(Range, C2)(auto ref Range str, const(C2)[] delimiter) +if (isConvertibleToString!Range) +{ + return chompPrefix!(StringTypeOf!Range, C2)(str, delimiter); +} + +@safe pure +unittest +{ + import std.algorithm.comparison : equal; + import std.conv : to; + import std.exception : assertCTFEable; + assertCTFEable!( + { + foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + { + foreach (T; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + assert(equal(chompPrefix(to!S("abcdefgh"), to!T("abcde")), "fgh")); + assert(equal(chompPrefix(to!S("abcde"), to!T("abcdefgh")), "abcde")); + assert(equal(chompPrefix(to!S("\uFF28el\uFF4co"), to!T("\uFF28el\uFF4co")), "")); + assert(equal(chompPrefix(to!S("\uFF28el\uFF4co"), to!T("\uFF28el")), "\uFF4co")); + assert(equal(chompPrefix(to!S("\uFF28el"), to!T("\uFF28el\uFF4co")), "\uFF28el")); + }(); + } + }); + + // Ranges + import std.array : array; + import std.utf : byChar, byWchar, byDchar; + assert(chompPrefix("hello world" .byChar , "hello"d).array == " world"); + assert(chompPrefix("hello world"w.byWchar, "hello" ).array == " world"w); + assert(chompPrefix("hello world"d.byDchar, "hello"w).array == " world"d); + assert(chompPrefix("hello world"c.byDchar, "hello"w).array == " world"d); + + assert(chompPrefix("hello world"d.byDchar, "lx").array == "hello world"d); + assert(chompPrefix("hello world"d.byDchar, "hello world xx").array == "hello world"d); + + assert(chompPrefix("\u2020world" .byChar , "\u2020").array == "world"); + assert(chompPrefix("\u2020world"d.byDchar, "\u2020"d).array == "world"d); +} + +@safe pure unittest +{ + assert(testAliasedString!chompPrefix("hello world", "hello")); +} + +/++ + Returns $(D str) without its last character, if there is one. If $(D str) + ends with $(D "\r\n"), then both are removed. If $(D str) is empty, then + then it is returned unchanged. + + Params: + str = string (must be valid UTF) + Returns: + slice of str + +/ + +Range chop(Range)(Range str) +if ((isBidirectionalRange!Range && isSomeChar!(ElementEncodingType!Range) || + isNarrowString!Range) && + !isConvertibleToString!Range) +{ + if (str.empty) + return str; + + static if (isSomeString!Range) + { + if (str.length >= 2 && str[$ - 1] == '\n' && str[$ - 2] == '\r') + return str[0 .. $ - 2]; + str.popBack(); + return str; + } + else + { + alias C = Unqual!(ElementEncodingType!Range); + C c = str.back; + str.popBack(); + if (c == '\n') + { + if (!str.empty && str.back == '\r') + str.popBack(); + return str; + } + // Pop back a dchar, not just a code unit + static if (C.sizeof == 1) + { + int cnt = 1; + while ((c & 0xC0) == 0x80) + { + if (str.empty) + break; + c = str.back; + str.popBack(); + if (++cnt > 4) + break; + } + } + else static if (C.sizeof == 2) + { + if (c >= 0xD800 && c <= 0xDBFF) + { + if (!str.empty) + str.popBack(); + } + } + else static if (C.sizeof == 4) + { + } + else + static assert(0); + return str; + } +} + +/// +@safe pure unittest +{ + assert(chop("hello world") == "hello worl"); + assert(chop("hello world\n") == "hello world"); + assert(chop("hello world\r") == "hello world"); + assert(chop("hello world\n\r") == "hello world\n"); + assert(chop("hello world\r\n") == "hello world"); + assert(chop("Walter Bright") == "Walter Brigh"); + assert(chop("") == ""); +} + +StringTypeOf!Range chop(Range)(auto ref Range str) +if (isConvertibleToString!Range) +{ + return chop!(StringTypeOf!Range)(str); +} + +@safe pure unittest +{ + assert(testAliasedString!chop("hello world")); +} + +@safe pure unittest +{ + import std.array : array; + import std.utf : byChar, byWchar, byDchar, byCodeUnit, invalidUTFstrings; + + assert(chop("hello world".byChar).array == "hello worl"); + assert(chop("hello world\n"w.byWchar).array == "hello world"w); + assert(chop("hello world\r"d.byDchar).array == "hello world"d); + assert(chop("hello world\n\r".byChar).array == "hello world\n"); + assert(chop("hello world\r\n"w.byWchar).array == "hello world"w); + assert(chop("Walter Bright"d.byDchar).array == "Walter Brigh"d); + assert(chop("".byChar).array == ""); + + assert(chop(`ミツバチと科学者` .byCodeUnit).array == "ミツバチと科学"); + assert(chop(`ミツバチと科学者`w.byCodeUnit).array == "ミツバチと科学"w); + assert(chop(`ミツバチと科学者`d.byCodeUnit).array == "ミツバチと科学"d); + + auto ca = invalidUTFstrings!char(); + foreach (s; ca) + { + foreach (c; chop(s.byCodeUnit)) + { + } + } + + auto wa = invalidUTFstrings!wchar(); + foreach (s; wa) + { + foreach (c; chop(s.byCodeUnit)) + { + } + } +} + +@safe pure unittest +{ + import std.algorithm.comparison : equal; + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + { + assert(chop(cast(S) null) is null); + assert(equal(chop(to!S("hello")), "hell")); + assert(equal(chop(to!S("hello\r\n")), "hello")); + assert(equal(chop(to!S("hello\n\r")), "hello\n")); + assert(equal(chop(to!S("Verité")), "Verit")); + assert(equal(chop(to!S(`さいごの果実`)), "さいごの果")); + assert(equal(chop(to!S(`ミツバチと科学者`)), "ミツバチと科学")); + } + }); +} + + +/++ + Left justify $(D s) in a field $(D width) characters wide. $(D fillChar) + is the character that will be used to fill up the space in the field that + $(D s) doesn't fill. + + Params: + s = string + width = minimum field width + fillChar = used to pad end up to $(D width) characters + + Returns: + GC allocated string + + See_Also: + $(LREF leftJustifier), which does not allocate + +/ +S leftJustify(S)(S s, size_t width, dchar fillChar = ' ') +if (isSomeString!S) +{ + import std.array : array; + return leftJustifier(s, width, fillChar).array; +} + +/// +@safe pure unittest +{ + assert(leftJustify("hello", 7, 'X') == "helloXX"); + assert(leftJustify("hello", 2, 'X') == "hello"); + assert(leftJustify("hello", 9, 'X') == "helloXXXX"); +} + +/++ + Left justify $(D s) in a field $(D width) characters wide. $(D fillChar) + is the character that will be used to fill up the space in the field that + $(D s) doesn't fill. + + Params: + r = string or range of characters + width = minimum field width + fillChar = used to pad end up to $(D width) characters + + Returns: + a lazy range of the left justified result + + See_Also: + $(LREF rightJustifier) + +/ + +auto leftJustifier(Range)(Range r, size_t width, dchar fillChar = ' ') +if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) && + !isConvertibleToString!Range) +{ + alias C = Unqual!(ElementEncodingType!Range); + + static if (C.sizeof == 1) + { + import std.utf : byDchar, byChar; + return leftJustifier(r.byDchar, width, fillChar).byChar; + } + else static if (C.sizeof == 2) + { + import std.utf : byDchar, byWchar; + return leftJustifier(r.byDchar, width, fillChar).byWchar; + } + else static if (C.sizeof == 4) + { + static struct Result + { + private: + Range _input; + size_t _width; + dchar _fillChar; + size_t len; + + public: + + @property bool empty() + { + return len >= _width && _input.empty; + } + + @property C front() + { + return _input.empty ? _fillChar : _input.front; + } + + void popFront() + { + ++len; + if (!_input.empty) + _input.popFront(); + } + + static if (isForwardRange!Range) + { + @property typeof(this) save() return scope + { + auto ret = this; + ret._input = _input.save; + return ret; + } + } + } + + return Result(r, width, fillChar); + } + else + static assert(0); +} + +/// +@safe pure @nogc nothrow +unittest +{ + import std.algorithm.comparison : equal; + import std.utf : byChar; + assert(leftJustifier("hello", 2).equal("hello".byChar)); + assert(leftJustifier("hello", 7).equal("hello ".byChar)); + assert(leftJustifier("hello", 7, 'x').equal("helloxx".byChar)); +} + +auto leftJustifier(Range)(auto ref Range r, size_t width, dchar fillChar = ' ') +if (isConvertibleToString!Range) +{ + return leftJustifier!(StringTypeOf!Range)(r, width, fillChar); +} + +@safe pure unittest +{ + auto r = "hello".leftJustifier(8); + r.popFront(); + auto save = r.save; + r.popFront(); + assert(r.front == 'l'); + assert(save.front == 'e'); +} + +@safe pure unittest +{ + assert(testAliasedString!leftJustifier("hello", 2)); +} + +/++ + Right justify $(D s) in a field $(D width) characters wide. $(D fillChar) + is the character that will be used to fill up the space in the field that + $(D s) doesn't fill. + + Params: + s = string + width = minimum field width + fillChar = used to pad end up to $(D width) characters + + Returns: + GC allocated string + + See_Also: + $(LREF rightJustifier), which does not allocate + +/ +S rightJustify(S)(S s, size_t width, dchar fillChar = ' ') +if (isSomeString!S) +{ + import std.array : array; + return rightJustifier(s, width, fillChar).array; +} + +/// +@safe pure unittest +{ + assert(rightJustify("hello", 7, 'X') == "XXhello"); + assert(rightJustify("hello", 2, 'X') == "hello"); + assert(rightJustify("hello", 9, 'X') == "XXXXhello"); +} + +/++ + Right justify $(D s) in a field $(D width) characters wide. $(D fillChar) + is the character that will be used to fill up the space in the field that + $(D s) doesn't fill. + + Params: + r = string or $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) + of characters + width = minimum field width + fillChar = used to pad end up to $(D width) characters + + Returns: + a lazy range of the right justified result + + See_Also: + $(LREF leftJustifier) + +/ + +auto rightJustifier(Range)(Range r, size_t width, dchar fillChar = ' ') +if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) && + !isConvertibleToString!Range) +{ + alias C = Unqual!(ElementEncodingType!Range); + + static if (C.sizeof == 1) + { + import std.utf : byDchar, byChar; + return rightJustifier(r.byDchar, width, fillChar).byChar; + } + else static if (C.sizeof == 2) + { + import std.utf : byDchar, byWchar; + return rightJustifier(r.byDchar, width, fillChar).byWchar; + } + else static if (C.sizeof == 4) + { + static struct Result + { + private: + Range _input; + size_t _width; + alias nfill = _width; // number of fill characters to prepend + dchar _fillChar; + bool inited; + + // Lazy initialization so constructor is trivial and cannot fail + void initialize() + { + // Replace _width with nfill + // (use alias instead of union because CTFE cannot deal with unions) + assert(_width); + static if (hasLength!Range) + { + immutable len = _input.length; + nfill = (_width > len) ? _width - len : 0; + } + else + { + // Lookahead to see now many fill characters are needed + import std.range : take; + import std.range.primitives : walkLength; + nfill = _width - walkLength(_input.save.take(_width), _width); + } + inited = true; + } + + public: + this(Range input, size_t width, dchar fillChar) pure nothrow + { + _input = input; + _fillChar = fillChar; + _width = width; + } + + @property bool empty() + { + return !nfill && _input.empty; + } + + @property C front() + { + if (!nfill) + return _input.front; // fast path + if (!inited) + initialize(); + return nfill ? _fillChar : _input.front; + } + + void popFront() + { + if (!nfill) + _input.popFront(); // fast path + else + { + if (!inited) + initialize(); + if (nfill) + --nfill; + else + _input.popFront(); + } + } + + @property typeof(this) save() + { + auto ret = this; + ret._input = _input.save; + return ret; + } + } + + return Result(r, width, fillChar); + } + else + static assert(0); +} + +/// +@safe pure @nogc nothrow +unittest +{ + import std.algorithm.comparison : equal; + import std.utf : byChar; + assert(rightJustifier("hello", 2).equal("hello".byChar)); + assert(rightJustifier("hello", 7).equal(" hello".byChar)); + assert(rightJustifier("hello", 7, 'x').equal("xxhello".byChar)); +} + +auto rightJustifier(Range)(auto ref Range r, size_t width, dchar fillChar = ' ') +if (isConvertibleToString!Range) +{ + return rightJustifier!(StringTypeOf!Range)(r, width, fillChar); +} + +@safe pure unittest +{ + assert(testAliasedString!rightJustifier("hello", 2)); +} + +@safe pure unittest +{ + auto r = "hello"d.rightJustifier(6); + r.popFront(); + auto save = r.save; + r.popFront(); + assert(r.front == 'e'); + assert(save.front == 'h'); + + auto t = "hello".rightJustifier(7); + t.popFront(); + assert(t.front == ' '); + t.popFront(); + assert(t.front == 'h'); + + auto u = "hello"d.rightJustifier(5); + u.popFront(); + u.popFront(); + u.popFront(); +} + +/++ + Center $(D s) in a field $(D width) characters wide. $(D fillChar) + is the character that will be used to fill up the space in the field that + $(D s) doesn't fill. + + Params: + s = The string to center + width = Width of the field to center `s` in + fillChar = The character to use for filling excess space in the field + + Returns: + The resulting _center-justified string. The returned string is + GC-allocated. To avoid GC allocation, use $(LREF centerJustifier) + instead. + +/ +S center(S)(S s, size_t width, dchar fillChar = ' ') +if (isSomeString!S) +{ + import std.array : array; + return centerJustifier(s, width, fillChar).array; +} + +/// +@safe pure unittest +{ + assert(center("hello", 7, 'X') == "XhelloX"); + assert(center("hello", 2, 'X') == "hello"); + assert(center("hello", 9, 'X') == "XXhelloXX"); +} + +@safe pure +unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + { + S s = to!S("hello"); + + assert(leftJustify(s, 2) == "hello"); + assert(rightJustify(s, 2) == "hello"); + assert(center(s, 2) == "hello"); + + assert(leftJustify(s, 7) == "hello "); + assert(rightJustify(s, 7) == " hello"); + assert(center(s, 7) == " hello "); + + assert(leftJustify(s, 8) == "hello "); + assert(rightJustify(s, 8) == " hello"); + assert(center(s, 8) == " hello "); + + assert(leftJustify(s, 8, '\u0100') == "hello\u0100\u0100\u0100"); + assert(rightJustify(s, 8, '\u0100') == "\u0100\u0100\u0100hello"); + assert(center(s, 8, '\u0100') == "\u0100hello\u0100\u0100"); + + assert(leftJustify(s, 8, 'ö') == "helloööö"); + assert(rightJustify(s, 8, 'ö') == "öööhello"); + assert(center(s, 8, 'ö') == "öhelloöö"); + } + }); +} + +/++ + Center justify $(D r) in a field $(D width) characters wide. $(D fillChar) + is the character that will be used to fill up the space in the field that + $(D r) doesn't fill. + + Params: + r = string or $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) + of characters + width = minimum field width + fillChar = used to pad end up to $(D width) characters + + Returns: + a lazy range of the center justified result + + See_Also: + $(LREF leftJustifier) + $(LREF rightJustifier) + +/ + +auto centerJustifier(Range)(Range r, size_t width, dchar fillChar = ' ') +if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) && + !isConvertibleToString!Range) +{ + alias C = Unqual!(ElementEncodingType!Range); + + static if (C.sizeof == 1) + { + import std.utf : byDchar, byChar; + return centerJustifier(r.byDchar, width, fillChar).byChar; + } + else static if (C.sizeof == 2) + { + import std.utf : byDchar, byWchar; + return centerJustifier(r.byDchar, width, fillChar).byWchar; + } + else static if (C.sizeof == 4) + { + import std.range : chain, repeat; + import std.range.primitives : walkLength; + + auto len = walkLength(r.save, width); + if (len > width) + len = width; + const nleft = (width - len) / 2; + const nright = width - len - nleft; + return chain(repeat(fillChar, nleft), r, repeat(fillChar, nright)); + } + else + static assert(0); +} + +/// +@safe pure @nogc nothrow +unittest +{ + import std.algorithm.comparison : equal; + import std.utf : byChar; + assert(centerJustifier("hello", 2).equal("hello".byChar)); + assert(centerJustifier("hello", 8).equal(" hello ".byChar)); + assert(centerJustifier("hello", 7, 'x').equal("xhellox".byChar)); +} + +auto centerJustifier(Range)(auto ref Range r, size_t width, dchar fillChar = ' ') +if (isConvertibleToString!Range) +{ + return centerJustifier!(StringTypeOf!Range)(r, width, fillChar); +} + +@safe pure unittest +{ + assert(testAliasedString!centerJustifier("hello", 8)); +} + +@system unittest +{ + static auto byFwdRange(dstring s) + { + static struct FRange + { + dstring str; + this(dstring s) { str = s; } + @property bool empty() { return str.length == 0; } + @property dchar front() { return str[0]; } + void popFront() { str = str[1 .. $]; } + @property FRange save() { return this; } + } + return FRange(s); + } + + auto r = centerJustifier(byFwdRange("hello"d), 6); + r.popFront(); + auto save = r.save; + r.popFront(); + assert(r.front == 'l'); + assert(save.front == 'e'); + + auto t = "hello".centerJustifier(7); + t.popFront(); + assert(t.front == 'h'); + t.popFront(); + assert(t.front == 'e'); + + auto u = byFwdRange("hello"d).centerJustifier(6); + u.popFront(); + u.popFront(); + u.popFront(); + u.popFront(); + u.popFront(); + u.popFront(); +} + + +/++ + Replace each tab character in $(D s) with the number of spaces necessary + to align the following character at the next tab stop. + + Params: + s = string + tabSize = distance between tab stops + + Returns: + GC allocated string with tabs replaced with spaces + +/ +auto detab(Range)(auto ref Range s, size_t tabSize = 8) pure +if ((isForwardRange!Range && isSomeChar!(ElementEncodingType!Range)) + || __traits(compiles, StringTypeOf!Range)) +{ + import std.array : array; + return detabber(s, tabSize).array; +} + +/// +@system pure unittest +{ + assert(detab(" \n\tx", 9) == " \n x"); +} + +@safe pure unittest +{ + static struct TestStruct + { + string s; + alias s this; + } + + static struct TestStruct2 + { + string s; + alias s this; + @disable this(this); + } + + string s = " \n\tx"; + string cmp = " \n x"; + auto t = TestStruct(s); + assert(detab(t, 9) == cmp); + assert(detab(TestStruct(s), 9) == cmp); + assert(detab(TestStruct(s), 9) == detab(TestStruct(s), 9)); + assert(detab(TestStruct2(s), 9) == detab(TestStruct2(s), 9)); + assert(detab(TestStruct2(s), 9) == cmp); +} + +/++ + Replace each tab character in $(D r) with the number of spaces + necessary to align the following character at the next tab stop. + + Params: + r = string or $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) + tabSize = distance between tab stops + + Returns: + lazy forward range with tabs replaced with spaces + +/ +auto detabber(Range)(Range r, size_t tabSize = 8) +if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) && + !isConvertibleToString!Range) +{ + import std.uni : lineSep, paraSep, nelSep; + import std.utf : codeUnitLimit, decodeFront; + + assert(tabSize > 0); + + alias C = Unqual!(ElementEncodingType!(Range)); + + static struct Result + { + private: + Range _input; + size_t _tabSize; + size_t nspaces; + int column; + size_t index; + + public: + + this(Range input, size_t tabSize) + { + _input = input; + _tabSize = tabSize; + } + + static if (isInfinite!(Range)) + { + enum bool empty = false; + } + else + { + @property bool empty() + { + return _input.empty && nspaces == 0; + } + } + + @property C front() + { + if (nspaces) + return ' '; + static if (isSomeString!(Range)) + C c = _input[0]; + else + C c = _input.front; + if (index) + return c; + dchar dc; + if (c < codeUnitLimit!(immutable(C)[])) + { + dc = c; + index = 1; + } + else + { + auto r = _input.save; + dc = decodeFront(r, index); // lookahead to decode + } + switch (dc) + { + case '\r': + case '\n': + case paraSep: + case lineSep: + case nelSep: + column = 0; + break; + + case '\t': + nspaces = _tabSize - (column % _tabSize); + column += nspaces; + c = ' '; + break; + + default: + ++column; + break; + } + return c; + } + + void popFront() + { + if (!index) + front; + if (nspaces) + --nspaces; + if (!nspaces) + { + static if (isSomeString!(Range)) + _input = _input[1 .. $]; + else + _input.popFront(); + --index; + } + } + + @property typeof(this) save() + { + auto ret = this; + ret._input = _input.save; + return ret; + } + } + + return Result(r, tabSize); +} + +/// +@system pure unittest +{ + import std.array : array; + + assert(detabber(" \n\tx", 9).array == " \n x"); +} + +auto detabber(Range)(auto ref Range r, size_t tabSize = 8) +if (isConvertibleToString!Range) +{ + return detabber!(StringTypeOf!Range)(r, tabSize); +} + +@safe pure unittest +{ + assert(testAliasedString!detabber( " ab\t asdf ", 8)); +} + +@system pure unittest +{ + import std.algorithm.comparison : cmp; + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + { + S s = to!S("This \tis\t a fofof\tof list"); + assert(cmp(detab(s), "This is a fofof of list") == 0); + + assert(detab(cast(S) null) is null); + assert(detab("").empty); + assert(detab("a") == "a"); + assert(detab("\t") == " "); + assert(detab("\t", 3) == " "); + assert(detab("\t", 9) == " "); + assert(detab( " ab\t asdf ") == " ab asdf "); + assert(detab( " \U00010000b\tasdf ") == " \U00010000b asdf "); + assert(detab("\r\t", 9) == "\r "); + assert(detab("\n\t", 9) == "\n "); + assert(detab("\u0085\t", 9) == "\u0085 "); + assert(detab("\u2028\t", 9) == "\u2028 "); + assert(detab(" \u2029\t", 9) == " \u2029 "); + } + }); +} + +/// +@system pure unittest +{ + import std.array : array; + import std.utf : byChar, byWchar; + + assert(detabber(" \u2029\t".byChar, 9).array == " \u2029 "); + auto r = "hel\tx".byWchar.detabber(); + assert(r.front == 'h'); + auto s = r.save; + r.popFront(); + r.popFront(); + assert(r.front == 'l'); + assert(s.front == 'h'); +} + +/++ + Replaces spaces in $(D s) with the optimal number of tabs. + All spaces and tabs at the end of a line are removed. + + Params: + s = String to convert. + tabSize = Tab columns are $(D tabSize) spaces apart. + + Returns: + GC allocated string with spaces replaced with tabs; + use $(LREF entabber) to not allocate. + + See_Also: + $(LREF entabber) + +/ +auto entab(Range)(Range s, size_t tabSize = 8) +if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range)) +{ + import std.array : array; + return entabber(s, tabSize).array; +} + +/// +@safe pure unittest +{ + assert(entab(" x \n") == "\tx\n"); +} + +auto entab(Range)(auto ref Range s, size_t tabSize = 8) +if (!(isForwardRange!Range && isSomeChar!(ElementEncodingType!Range)) && + is(StringTypeOf!Range)) +{ + return entab!(StringTypeOf!Range)(s, tabSize); +} + +@safe pure unittest +{ + assert(testAliasedString!entab(" x \n")); +} + +/++ + Replaces spaces in range $(D r) with the optimal number of tabs. + All spaces and tabs at the end of a line are removed. + + Params: + r = string or $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) + tabSize = distance between tab stops + + Returns: + lazy forward range with spaces replaced with tabs + + See_Also: + $(LREF entab) + +/ +auto entabber(Range)(Range r, size_t tabSize = 8) +if (isForwardRange!Range && !isConvertibleToString!Range) +{ + import std.uni : lineSep, paraSep, nelSep; + import std.utf : codeUnitLimit, decodeFront; + + assert(tabSize > 0); + alias C = Unqual!(ElementEncodingType!Range); + + static struct Result + { + private: + Range _input; + size_t _tabSize; + size_t nspaces; + size_t ntabs; + int column; + size_t index; + + @property C getFront() + { + static if (isSomeString!Range) + return _input[0]; // avoid autodecode + else + return _input.front; + } + + public: + + this(Range input, size_t tabSize) + { + _input = input; + _tabSize = tabSize; + } + + @property bool empty() + { + if (ntabs || nspaces) + return false; + + /* Since trailing spaces are removed, + * look ahead for anything that is not a trailing space + */ + static if (isSomeString!Range) + { + foreach (c; _input) + { + if (c != ' ' && c != '\t') + return false; + } + return true; + } + else + { + if (_input.empty) + return true; + immutable c = _input.front; + if (c != ' ' && c != '\t') + return false; + auto t = _input.save; + t.popFront(); + foreach (c2; t) + { + if (c2 != ' ' && c2 != '\t') + return false; + } + return true; + } + } + + @property C front() + { + //writefln(" front(): ntabs = %s nspaces = %s index = %s front = '%s'", ntabs, nspaces, index, getFront); + if (ntabs) + return '\t'; + if (nspaces) + return ' '; + C c = getFront; + if (index) + return c; + dchar dc; + if (c < codeUnitLimit!(immutable(C)[])) + { + index = 1; + dc = c; + if (c == ' ' || c == '\t') + { + // Consume input until a non-blank is encountered + immutable startcol = column; + C cx; + static if (isSomeString!Range) + { + while (1) + { + assert(_input.length); + cx = _input[0]; + if (cx == ' ') + ++column; + else if (cx == '\t') + column += _tabSize - (column % _tabSize); + else + break; + _input = _input[1 .. $]; + } + } + else + { + while (1) + { + assert(!_input.empty); + cx = _input.front; + if (cx == ' ') + ++column; + else if (cx == '\t') + column += _tabSize - (column % _tabSize); + else + break; + _input.popFront(); + } + } + // Compute ntabs+nspaces to get from startcol to column + immutable n = column - startcol; + if (n == 1) + { + nspaces = 1; + } + else + { + ntabs = column / _tabSize - startcol / _tabSize; + if (ntabs == 0) + nspaces = column - startcol; + else + nspaces = column % _tabSize; + } + //writefln("\tstartcol = %s, column = %s, _tabSize = %s", startcol, column, _tabSize); + //writefln("\tntabs = %s, nspaces = %s", ntabs, nspaces); + if (cx < codeUnitLimit!(immutable(C)[])) + { + dc = cx; + index = 1; + } + else + { + auto r = _input.save; + dc = decodeFront(r, index); // lookahead to decode + } + switch (dc) + { + case '\r': + case '\n': + case paraSep: + case lineSep: + case nelSep: + column = 0; + // Spaces followed by newline are ignored + ntabs = 0; + nspaces = 0; + return cx; + + default: + ++column; + break; + } + return ntabs ? '\t' : ' '; + } + } + else + { + auto r = _input.save; + dc = decodeFront(r, index); // lookahead to decode + } + //writefln("dc = x%x", dc); + switch (dc) + { + case '\r': + case '\n': + case paraSep: + case lineSep: + case nelSep: + column = 0; + break; + + default: + ++column; + break; + } + return c; + } + + void popFront() + { + //writefln("popFront(): ntabs = %s nspaces = %s index = %s front = '%s'", ntabs, nspaces, index, getFront); + if (!index) + front; + if (ntabs) + --ntabs; + else if (nspaces) + --nspaces; + else if (!ntabs && !nspaces) + { + static if (isSomeString!Range) + _input = _input[1 .. $]; + else + _input.popFront(); + --index; + } + } + + @property typeof(this) save() + { + auto ret = this; + ret._input = _input.save; + return ret; + } + } + + return Result(r, tabSize); +} + +/// +@safe pure unittest +{ + import std.array : array; + assert(entabber(" x \n").array == "\tx\n"); +} + +auto entabber(Range)(auto ref Range r, size_t tabSize = 8) +if (isConvertibleToString!Range) +{ + return entabber!(StringTypeOf!Range)(r, tabSize); +} + +@safe pure unittest +{ + assert(testAliasedString!entabber(" ab asdf ", 8)); +} + +@safe pure +unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + assert(entab(cast(string) null) is null); + assert(entab("").empty); + assert(entab("a") == "a"); + assert(entab(" ") == ""); + assert(entab(" x") == "\tx"); + assert(entab(" ab asdf ") == " ab\tasdf"); + assert(entab(" ab asdf ") == " ab\t asdf"); + assert(entab(" ab \t asdf ") == " ab\t asdf"); + assert(entab("1234567 \ta") == "1234567\t\ta"); + assert(entab("1234567 \ta") == "1234567\t\ta"); + assert(entab("1234567 \ta") == "1234567\t\ta"); + assert(entab("1234567 \ta") == "1234567\t\ta"); + assert(entab("1234567 \ta") == "1234567\t\ta"); + assert(entab("1234567 \ta") == "1234567\t\ta"); + assert(entab("1234567 \ta") == "1234567\t\ta"); + assert(entab("1234567 \ta") == "1234567\t\ta"); + assert(entab("1234567 \ta") == "1234567\t\t\ta"); + + assert(entab("a ") == "a"); + assert(entab("a\v") == "a\v"); + assert(entab("a\f") == "a\f"); + assert(entab("a\n") == "a\n"); + assert(entab("a\n\r") == "a\n\r"); + assert(entab("a\r\n") == "a\r\n"); + assert(entab("a\u2028") == "a\u2028"); + assert(entab("a\u2029") == "a\u2029"); + assert(entab("a\u0085") == "a\u0085"); + assert(entab("a ") == "a"); + assert(entab("a\t") == "a"); + assert(entab("\uFF28\uFF45\uFF4C\uFF4C567 \t\uFF4F \t") == + "\uFF28\uFF45\uFF4C\uFF4C567\t\t\uFF4F"); + assert(entab(" \naa") == "\naa"); + assert(entab(" \r aa") == "\r aa"); + assert(entab(" \u2028 aa") == "\u2028 aa"); + assert(entab(" \u2029 aa") == "\u2029 aa"); + assert(entab(" \u0085 aa") == "\u0085 aa"); + }); +} + +@safe pure +unittest +{ + import std.array : array; + import std.utf : byChar; + assert(entabber(" \u0085 aa".byChar).array == "\u0085 aa"); + assert(entabber(" \u2028\t aa \t".byChar).array == "\u2028\t aa"); + + auto r = entabber("1234", 4); + r.popFront(); + auto rsave = r.save; + r.popFront(); + assert(r.front == '3'); + assert(rsave.front == '2'); +} + + +/++ + Replaces the characters in $(D str) which are keys in $(D transTable) with + their corresponding values in $(D transTable). $(D transTable) is an AA + where its keys are $(D dchar) and its values are either $(D dchar) or some + type of string. Also, if $(D toRemove) is given, the characters in it are + removed from $(D str) prior to translation. $(D str) itself is unaltered. + A copy with the changes is returned. + + See_Also: + $(LREF tr) + $(REF replace, std,array) + + Params: + str = The original string. + transTable = The AA indicating which characters to replace and what to + replace them with. + toRemove = The characters to remove from the string. + +/ +C1[] translate(C1, C2 = immutable char)(C1[] str, + in dchar[dchar] transTable, + const(C2)[] toRemove = null) @safe pure +if (isSomeChar!C1 && isSomeChar!C2) +{ + import std.array : appender; + auto buffer = appender!(C1[])(); + translateImpl(str, transTable, toRemove, buffer); + return buffer.data; +} + +/// +@safe pure unittest +{ + dchar[dchar] transTable1 = ['e' : '5', 'o' : '7', '5': 'q']; + assert(translate("hello world", transTable1) == "h5ll7 w7rld"); + + assert(translate("hello world", transTable1, "low") == "h5 rd"); + + string[dchar] transTable2 = ['e' : "5", 'o' : "orange"]; + assert(translate("hello world", transTable2) == "h5llorange worangerld"); +} + +@safe pure unittest // issue 13018 +{ + immutable dchar[dchar] transTable1 = ['e' : '5', 'o' : '7', '5': 'q']; + assert(translate("hello world", transTable1) == "h5ll7 w7rld"); + + assert(translate("hello world", transTable1, "low") == "h5 rd"); + + immutable string[dchar] transTable2 = ['e' : "5", 'o' : "orange"]; + assert(translate("hello world", transTable2) == "h5llorange worangerld"); +} + +@system pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + foreach (S; AliasSeq!( char[], const( char)[], immutable( char)[], + wchar[], const(wchar)[], immutable(wchar)[], + dchar[], const(dchar)[], immutable(dchar)[])) + { + assert(translate(to!S("hello world"), cast(dchar[dchar])['h' : 'q', 'l' : '5']) == + to!S("qe55o wor5d")); + assert(translate(to!S("hello world"), cast(dchar[dchar])['o' : 'l', 'l' : '\U00010143']) == + to!S("he\U00010143\U00010143l wlr\U00010143d")); + assert(translate(to!S("hello \U00010143 world"), cast(dchar[dchar])['h' : 'q', 'l': '5']) == + to!S("qe55o \U00010143 wor5d")); + assert(translate(to!S("hello \U00010143 world"), cast(dchar[dchar])['o' : '0', '\U00010143' : 'o']) == + to!S("hell0 o w0rld")); + assert(translate(to!S("hello world"), cast(dchar[dchar]) null) == to!S("hello world")); + + foreach (T; AliasSeq!( char[], const( char)[], immutable( char)[], + wchar[], const(wchar)[], immutable(wchar)[], + dchar[], const(dchar)[], immutable(dchar)[])) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + foreach (R; AliasSeq!(dchar[dchar], const dchar[dchar], + immutable dchar[dchar])) + { + R tt = ['h' : 'q', 'l' : '5']; + assert(translate(to!S("hello world"), tt, to!T("r")) + == to!S("qe55o wo5d")); + assert(translate(to!S("hello world"), tt, to!T("helo")) + == to!S(" wrd")); + assert(translate(to!S("hello world"), tt, to!T("q5")) + == to!S("qe55o wor5d")); + } + }(); + + auto s = to!S("hello world"); + dchar[dchar] transTable = ['h' : 'q', 'l' : '5']; + static assert(is(typeof(s) == typeof(translate(s, transTable)))); + } + }); +} + +/++ Ditto +/ +C1[] translate(C1, S, C2 = immutable char)(C1[] str, + in S[dchar] transTable, + const(C2)[] toRemove = null) @safe pure +if (isSomeChar!C1 && isSomeString!S && isSomeChar!C2) +{ + import std.array : appender; + auto buffer = appender!(C1[])(); + translateImpl(str, transTable, toRemove, buffer); + return buffer.data; +} + +@system pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + foreach (S; AliasSeq!( char[], const( char)[], immutable( char)[], + wchar[], const(wchar)[], immutable(wchar)[], + dchar[], const(dchar)[], immutable(dchar)[])) + { + assert(translate(to!S("hello world"), ['h' : "yellow", 'l' : "42"]) == + to!S("yellowe4242o wor42d")); + assert(translate(to!S("hello world"), ['o' : "owl", 'l' : "\U00010143\U00010143"]) == + to!S("he\U00010143\U00010143\U00010143\U00010143owl wowlr\U00010143\U00010143d")); + assert(translate(to!S("hello \U00010143 world"), ['h' : "yellow", 'l' : "42"]) == + to!S("yellowe4242o \U00010143 wor42d")); + assert(translate(to!S("hello \U00010143 world"), ['o' : "owl", 'l' : "\U00010143\U00010143"]) == + to!S("he\U00010143\U00010143\U00010143\U00010143owl \U00010143 wowlr\U00010143\U00010143d")); + assert(translate(to!S("hello \U00010143 world"), ['h' : ""]) == + to!S("ello \U00010143 world")); + assert(translate(to!S("hello \U00010143 world"), ['\U00010143' : ""]) == + to!S("hello world")); + assert(translate(to!S("hello world"), cast(string[dchar]) null) == to!S("hello world")); + + foreach (T; AliasSeq!( char[], const( char)[], immutable( char)[], + wchar[], const(wchar)[], immutable(wchar)[], + dchar[], const(dchar)[], immutable(dchar)[])) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + + foreach (R; AliasSeq!(string[dchar], const string[dchar], + immutable string[dchar])) + { + R tt = ['h' : "yellow", 'l' : "42"]; + assert(translate(to!S("hello world"), tt, to!T("r")) == + to!S("yellowe4242o wo42d")); + assert(translate(to!S("hello world"), tt, to!T("helo")) == + to!S(" wrd")); + assert(translate(to!S("hello world"), tt, to!T("y42")) == + to!S("yellowe4242o wor42d")); + assert(translate(to!S("hello world"), tt, to!T("hello world")) == + to!S("")); + assert(translate(to!S("hello world"), tt, to!T("42")) == + to!S("yellowe4242o wor42d")); + } + }(); + + auto s = to!S("hello world"); + string[dchar] transTable = ['h' : "silly", 'l' : "putty"]; + static assert(is(typeof(s) == typeof(translate(s, transTable)))); + } + }); +} + +/++ + This is an overload of $(D translate) which takes an existing buffer to write the contents to. + + Params: + str = The original string. + transTable = The AA indicating which characters to replace and what to + replace them with. + toRemove = The characters to remove from the string. + buffer = An output range to write the contents to. + +/ +void translate(C1, C2 = immutable char, Buffer)(C1[] str, + in dchar[dchar] transTable, + const(C2)[] toRemove, + Buffer buffer) +if (isSomeChar!C1 && isSomeChar!C2 && isOutputRange!(Buffer, C1)) +{ + translateImpl(str, transTable, toRemove, buffer); +} + +/// +@safe pure unittest +{ + import std.array : appender; + dchar[dchar] transTable1 = ['e' : '5', 'o' : '7', '5': 'q']; + auto buffer = appender!(dchar[])(); + translate("hello world", transTable1, null, buffer); + assert(buffer.data == "h5ll7 w7rld"); + + buffer.clear(); + translate("hello world", transTable1, "low", buffer); + assert(buffer.data == "h5 rd"); + + buffer.clear(); + string[dchar] transTable2 = ['e' : "5", 'o' : "orange"]; + translate("hello world", transTable2, null, buffer); + assert(buffer.data == "h5llorange worangerld"); +} + +@safe pure unittest // issue 13018 +{ + import std.array : appender; + immutable dchar[dchar] transTable1 = ['e' : '5', 'o' : '7', '5': 'q']; + auto buffer = appender!(dchar[])(); + translate("hello world", transTable1, null, buffer); + assert(buffer.data == "h5ll7 w7rld"); + + buffer.clear(); + translate("hello world", transTable1, "low", buffer); + assert(buffer.data == "h5 rd"); + + buffer.clear(); + immutable string[dchar] transTable2 = ['e' : "5", 'o' : "orange"]; + translate("hello world", transTable2, null, buffer); + assert(buffer.data == "h5llorange worangerld"); +} + +/++ Ditto +/ +void translate(C1, S, C2 = immutable char, Buffer)(C1[] str, + in S[dchar] transTable, + const(C2)[] toRemove, + Buffer buffer) +if (isSomeChar!C1 && isSomeString!S && isSomeChar!C2 && isOutputRange!(Buffer, S)) +{ + translateImpl(str, transTable, toRemove, buffer); +} + +private void translateImpl(C1, T, C2, Buffer)(C1[] str, + T transTable, + const(C2)[] toRemove, + Buffer buffer) +{ + bool[dchar] removeTable; + + foreach (dchar c; toRemove) + removeTable[c] = true; + + foreach (dchar c; str) + { + if (c in removeTable) + continue; + + auto newC = c in transTable; + + if (newC) + put(buffer, *newC); + else + put(buffer, c); + } +} + +/++ + This is an $(I $(RED ASCII-only)) overload of $(LREF _translate). It + will $(I not) work with Unicode. It exists as an optimization for the + cases where Unicode processing is not necessary. + + Unlike the other overloads of $(LREF _translate), this one does not take + an AA. Rather, it takes a $(D string) generated by $(LREF makeTransTable). + + The array generated by $(D makeTransTable) is $(D 256) elements long such that + the index is equal to the ASCII character being replaced and the value is + equal to the character that it's being replaced with. Note that translate + does not decode any of the characters, so you can actually pass it Extended + ASCII characters if you want to (ASCII only actually uses $(D 128) + characters), but be warned that Extended ASCII characters are not valid + Unicode and therefore will result in a $(D UTFException) being thrown from + most other Phobos functions. + + Also, because no decoding occurs, it is possible to use this overload to + translate ASCII characters within a proper UTF-8 string without altering the + other, non-ASCII characters. It's replacing any code unit greater than + $(D 127) with another code unit or replacing any code unit with another code + unit greater than $(D 127) which will cause UTF validation issues. + + See_Also: + $(LREF tr) + $(REF replace, std,array) + + Params: + str = The original string. + transTable = The string indicating which characters to replace and what + to replace them with. It is generated by $(LREF makeTransTable). + toRemove = The characters to remove from the string. + +/ +C[] translate(C = immutable char)(in char[] str, in char[] transTable, in char[] toRemove = null) @trusted pure nothrow +if (is(Unqual!C == char)) +in +{ + assert(transTable.length == 256); +} +body +{ + bool[256] remTable = false; + + foreach (char c; toRemove) + remTable[c] = true; + + size_t count = 0; + foreach (char c; str) + { + if (!remTable[c]) + ++count; + } + + auto buffer = new char[count]; + + size_t i = 0; + foreach (char c; str) + { + if (!remTable[c]) + buffer[i++] = transTable[c]; + } + + return cast(C[])(buffer); +} + + +/** + * Do same thing as $(LREF makeTransTable) but allocate the translation table + * on the GC heap. + * + * Use $(LREF makeTransTable) instead. + */ +string makeTrans(in char[] from, in char[] to) @trusted pure nothrow +{ + return makeTransTable(from, to)[].idup; +} + +/// +@safe pure nothrow unittest +{ + auto transTable1 = makeTrans("eo5", "57q"); + assert(translate("hello world", transTable1) == "h5ll7 w7rld"); + + assert(translate("hello world", transTable1, "low") == "h5 rd"); +} + +/******* + * Construct 256 character translation table, where characters in from[] are replaced + * by corresponding characters in to[]. + * + * Params: + * from = array of chars, less than or equal to 256 in length + * to = corresponding array of chars to translate to + * Returns: + * translation array + */ + +char[256] makeTransTable(in char[] from, in char[] to) @safe pure nothrow @nogc +in +{ + import std.ascii : isASCII; + assert(from.length == to.length); + assert(from.length <= 256); + foreach (char c; from) + assert(isASCII(c)); + foreach (char c; to) + assert(isASCII(c)); +} +body +{ + char[256] result = void; + + foreach (i; 0 .. result.length) + result[i] = cast(char) i; + foreach (i, c; from) + result[c] = to[i]; + return result; +} + +@safe pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + foreach (C; AliasSeq!(char, const char, immutable char)) + { + assert(translate!C("hello world", makeTransTable("hl", "q5")) == to!(C[])("qe55o wor5d")); + + auto s = to!(C[])("hello world"); + auto transTable = makeTransTable("hl", "q5"); + static assert(is(typeof(s) == typeof(translate!C(s, transTable)))); + } + + foreach (S; AliasSeq!(char[], const(char)[], immutable(char)[])) + { + assert(translate(to!S("hello world"), makeTransTable("hl", "q5")) == to!S("qe55o wor5d")); + assert(translate(to!S("hello \U00010143 world"), makeTransTable("hl", "q5")) == + to!S("qe55o \U00010143 wor5d")); + assert(translate(to!S("hello world"), makeTransTable("ol", "1o")) == to!S("heoo1 w1rod")); + assert(translate(to!S("hello world"), makeTransTable("", "")) == to!S("hello world")); + assert(translate(to!S("hello world"), makeTransTable("12345", "67890")) == to!S("hello world")); + assert(translate(to!S("hello \U00010143 world"), makeTransTable("12345", "67890")) == + to!S("hello \U00010143 world")); + + foreach (T; AliasSeq!(char[], const(char)[], immutable(char)[])) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + assert(translate(to!S("hello world"), makeTransTable("hl", "q5"), to!T("r")) == + to!S("qe55o wo5d")); + assert(translate(to!S("hello \U00010143 world"), makeTransTable("hl", "q5"), to!T("r")) == + to!S("qe55o \U00010143 wo5d")); + assert(translate(to!S("hello world"), makeTransTable("hl", "q5"), to!T("helo")) == + to!S(" wrd")); + assert(translate(to!S("hello world"), makeTransTable("hl", "q5"), to!T("q5")) == + to!S("qe55o wor5d")); + }(); + } + }); +} + +/++ + This is an $(I $(RED ASCII-only)) overload of $(D translate) which takes an existing buffer to write the contents to. + + Params: + str = The original string. + transTable = The string indicating which characters to replace and what + to replace them with. It is generated by $(LREF makeTransTable). + toRemove = The characters to remove from the string. + buffer = An output range to write the contents to. + +/ +void translate(C = immutable char, Buffer)(in char[] str, in char[] transTable, + in char[] toRemove, Buffer buffer) @trusted pure +if (is(Unqual!C == char) && isOutputRange!(Buffer, char)) +in +{ + assert(transTable.length == 256); +} +body +{ + bool[256] remTable = false; + + foreach (char c; toRemove) + remTable[c] = true; + + foreach (char c; str) + { + if (!remTable[c]) + put(buffer, transTable[c]); + } +} + +/// +@safe pure unittest +{ + import std.array : appender; + auto buffer = appender!(char[])(); + auto transTable1 = makeTransTable("eo5", "57q"); + translate("hello world", transTable1, null, buffer); + assert(buffer.data == "h5ll7 w7rld"); + + buffer.clear(); + translate("hello world", transTable1, "low", buffer); + assert(buffer.data == "h5 rd"); +} + +//@@@DEPRECATED_2018-05@@@ +/*********************************************** + * $(RED This function is deprecated and will be removed May 2018.) + * Please use the functions in $(MREF std, regex) and $(MREF std, algorithm) + * instead. If you still need this function, it will be available in + * $(LINK2 https://github.com/dlang/undeaD, undeaD). + * + * See if character c is in the pattern. + * Patterns: + * + * A $(I pattern) is an array of characters much like a $(I character + * class) in regular expressions. A sequence of characters + * can be given, such as "abcde". The '-' can represent a range + * of characters, as "a-e" represents the same pattern as "abcde". + * "a-fA-F0-9" represents all the hex characters. + * If the first character of a pattern is '^', then the pattern + * is negated, i.e. "^0-9" means any character except a digit. + * The functions inPattern, $(B countchars), $(B removeschars), + * and $(B squeeze) use patterns. + * + * Note: In the future, the pattern syntax may be improved + * to be more like regular expression character classes. + */ +deprecated("This function is obsolete and will be removed May 2018. See the docs for more details") +bool inPattern(S)(dchar c, in S pattern) @safe pure @nogc +if (isSomeString!S) +{ + bool result = false; + int range = 0; + dchar lastc; + + foreach (size_t i, dchar p; pattern) + { + if (p == '^' && i == 0) + { + result = true; + if (i + 1 == pattern.length) + return (c == p); // or should this be an error? + } + else if (range) + { + range = 0; + if (lastc <= c && c <= p || c == p) + return !result; + } + else if (p == '-' && i > result && i + 1 < pattern.length) + { + range = 1; + continue; + } + else if (c == p) + return !result; + lastc = p; + } + return result; +} + + +deprecated +@safe pure @nogc unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + assert(inPattern('x', "x") == 1); + assert(inPattern('x', "y") == 0); + assert(inPattern('x', string.init) == 0); + assert(inPattern('x', "^y") == 1); + assert(inPattern('x', "yxxy") == 1); + assert(inPattern('x', "^yxxy") == 0); + assert(inPattern('x', "^abcd") == 1); + assert(inPattern('^', "^^") == 0); + assert(inPattern('^', "^") == 1); + assert(inPattern('^', "a^") == 1); + assert(inPattern('x', "a-z") == 1); + assert(inPattern('x', "A-Z") == 0); + assert(inPattern('x', "^a-z") == 0); + assert(inPattern('x', "^A-Z") == 1); + assert(inPattern('-', "a-") == 1); + assert(inPattern('-', "^A-") == 0); + assert(inPattern('a', "z-a") == 1); + assert(inPattern('z', "z-a") == 1); + assert(inPattern('x', "z-a") == 0); + }); +} + +//@@@DEPRECATED_2018-05@@@ +/*********************************************** + * $(RED This function is deprecated and will be removed May 2018.) + * Please use the functions in $(MREF std, regex) and $(MREF std, algorithm) + * instead. If you still need this function, it will be available in + * $(LINK2 https://github.com/dlang/undeaD, undeaD). + * + * See if character c is in the intersection of the patterns. + */ +deprecated("This function is obsolete and will be removed May 2018. See the docs for more details") +bool inPattern(S)(dchar c, S[] patterns) @safe pure @nogc +if (isSomeString!S) +{ + foreach (string pattern; patterns) + { + if (!inPattern(c, pattern)) + { + return false; + } + } + return true; +} + +//@@@DEPRECATED_2018-05@@@ +/******************************************** + * $(RED This function is deprecated and will be removed May 2018.) + * Please use the functions in $(MREF std, regex) and $(MREF std, algorithm) + * instead. If you still need this function, it will be available in + * $(LINK2 https://github.com/dlang/undeaD, undeaD). + * + * Count characters in s that match pattern. + */ +deprecated("This function is obsolete and will be removed May 2018. See the docs for more details") +size_t countchars(S, S1)(S s, in S1 pattern) @safe pure @nogc +if (isSomeString!S && isSomeString!S1) +{ + size_t count; + foreach (dchar c; s) + { + count += inPattern(c, pattern); + } + return count; +} + +deprecated +@safe pure @nogc unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + assert(countchars("abc", "a-c") == 3); + assert(countchars("hello world", "or") == 3); + }); +} + +//@@@DEPRECATED_2018-05@@@ +/******************************************** + * $(RED This function is deprecated and will be removed May 2018.) + * Please use the functions in $(MREF std, regex) and $(MREF std, algorithm) + * instead. If you still need this function, it will be available in + * $(LINK2 https://github.com/dlang/undeaD, undeaD). + * + * Return string that is s with all characters removed that match pattern. + */ +deprecated("This function is obsolete and will be removed May 2018. See the docs for more details") +S removechars(S)(S s, in S pattern) @safe pure +if (isSomeString!S) +{ + import std.utf : encode; + + Unqual!(typeof(s[0]))[] r; + bool changed = false; + + foreach (size_t i, dchar c; s) + { + if (inPattern(c, pattern)) + { + if (!changed) + { + changed = true; + r = s[0 .. i].dup; + } + continue; + } + if (changed) + { + encode(r, c); + } + } + if (changed) + return r; + else + return s; +} + +deprecated +@safe pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + assert(removechars("abc", "a-c").length == 0); + assert(removechars("hello world", "or") == "hell wld"); + assert(removechars("hello world", "d") == "hello worl"); + assert(removechars("hah", "h") == "a"); + }); +} + +deprecated +@safe pure unittest +{ + assert(removechars("abc", "x") == "abc"); +} + +//@@@DEPRECATED_2018-05@@@ +/*************************************************** + * $(RED This function is deprecated and will be removed May 2018.) + * Please use the functions in $(MREF std, regex) and $(MREF std, algorithm) + * instead. If you still need this function, it will be available in + * $(LINK2 https://github.com/dlang/undeaD, undeaD). + * + * Return string where sequences of a character in s[] from pattern[] + * are replaced with a single instance of that character. + * If pattern is null, it defaults to all characters. + */ +deprecated("This function is obsolete and will be removed May 2018. See the docs for more details") +S squeeze(S)(S s, in S pattern = null) +{ + import std.utf : encode, stride; + + Unqual!(typeof(s[0]))[] r; + dchar lastc; + size_t lasti; + int run; + bool changed; + + foreach (size_t i, dchar c; s) + { + if (run && lastc == c) + { + changed = true; + } + else if (pattern is null || inPattern(c, pattern)) + { + run = 1; + if (changed) + { + if (r is null) + r = s[0 .. lasti].dup; + encode(r, c); + } + else + lasti = i + stride(s, i); + lastc = c; + } + else + { + run = 0; + if (changed) + { + if (r is null) + r = s[0 .. lasti].dup; + encode(r, c); + } + } + } + return changed ? ((r is null) ? s[0 .. lasti] : cast(S) r) : s; +} + +deprecated +@system pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + string s; + + assert(squeeze("hello") == "helo"); + + s = "abcd"; + assert(squeeze(s) is s); + s = "xyzz"; + assert(squeeze(s).ptr == s.ptr); // should just be a slice + + assert(squeeze("hello goodbyee", "oe") == "hello godbye"); + }); +} + +//@@@DEPRECATED_2018-05@@@ +/*************************************************************** + $(RED This function is deprecated and will be removed May 2018.) + Please use the functions in $(MREF std, regex) and $(MREF std, algorithm) + instead. If you still need this function, it will be available in + $(LINK2 https://github.com/dlang/undeaD, undeaD). + + Finds the position $(D_PARAM pos) of the first character in $(D_PARAM + s) that does not match $(D_PARAM pattern) (in the terminology used by + $(REF inPattern, std,string)). Updates $(D_PARAM s = + s[pos..$]). Returns the slice from the beginning of the original + (before update) string up to, and excluding, $(D_PARAM pos). + +The $(D_PARAM munch) function is mostly convenient for skipping +certain category of characters (e.g. whitespace) when parsing +strings. (In such cases, the return value is not used.) + */ +deprecated("This function is obsolete and will be removed May 2018. See the docs for more details") +S1 munch(S1, S2)(ref S1 s, S2 pattern) @safe pure @nogc +{ + size_t j = s.length; + foreach (i, dchar c; s) + { + if (!inPattern(c, pattern)) + { + j = i; + break; + } + } + scope(exit) s = s[j .. $]; + return s[0 .. j]; +} + +/// +deprecated +@safe pure @nogc unittest +{ + string s = "123abc"; + string t = munch(s, "0123456789"); + assert(t == "123" && s == "abc"); + t = munch(s, "0123456789"); + assert(t == "" && s == "abc"); +} + +deprecated +@safe pure @nogc unittest +{ + string s = "123€abc"; + string t = munch(s, "0123456789"); + assert(t == "123" && s == "€abc"); + t = munch(s, "0123456789"); + assert(t == "" && s == "€abc"); + t = munch(s, "£$€¥"); + assert(t == "€" && s == "abc"); +} + + +/********************************************** + * Return string that is the 'successor' to s[]. + * If the rightmost character is a-zA-Z0-9, it is incremented within + * its case or digits. If it generates a carry, the process is + * repeated with the one to its immediate left. + */ + +S succ(S)(S s) @safe pure +if (isSomeString!S) +{ + import std.ascii : isAlphaNum; + + if (s.length && isAlphaNum(s[$ - 1])) + { + auto r = s.dup; + size_t i = r.length - 1; + + while (1) + { + dchar c = s[i]; + dchar carry; + + switch (c) + { + case '9': + c = '0'; + carry = '1'; + goto Lcarry; + case 'z': + case 'Z': + c -= 'Z' - 'A'; + carry = c; + Lcarry: + r[i] = cast(char) c; + if (i == 0) + { + auto t = new typeof(r[0])[r.length + 1]; + t[0] = cast(char) carry; + t[1 .. $] = r[]; + return t; + } + i--; + break; + + default: + if (isAlphaNum(c)) + r[i]++; + return r; + } + } + } + return s; +} + +/// +@safe pure unittest +{ + assert(succ("1") == "2"); + assert(succ("9") == "10"); + assert(succ("999") == "1000"); + assert(succ("zz99") == "aaa00"); +} + +@safe pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + assert(succ(string.init) is null); + assert(succ("!@#$%") == "!@#$%"); + assert(succ("1") == "2"); + assert(succ("9") == "10"); + assert(succ("999") == "1000"); + assert(succ("zz99") == "aaa00"); + }); +} + + +/++ + Replaces the characters in $(D str) which are in $(D from) with the + the corresponding characters in $(D to) and returns the resulting string. + + $(D tr) is based on + $(HTTP pubs.opengroup.org/onlinepubs/9699919799/utilities/_tr.html, Posix's tr), + though it doesn't do everything that the Posix utility does. + + Params: + str = The original string. + from = The characters to replace. + to = The characters to replace with. + modifiers = String containing modifiers. + + Modifiers: + $(BOOKTABLE, + $(TR $(TD Modifier) $(TD Description)) + $(TR $(TD $(D 'c')) $(TD Complement the list of characters in $(D from))) + $(TR $(TD $(D 'd')) $(TD Removes matching characters with no corresponding + replacement in $(D to))) + $(TR $(TD $(D 's')) $(TD Removes adjacent duplicates in the replaced + characters)) + ) + + If the modifier $(D 'd') is present, then the number of characters in + $(D to) may be only $(D 0) or $(D 1). + + If the modifier $(D 'd') is $(I not) present, and $(D to) is empty, then + $(D to) is taken to be the same as $(D from). + + If the modifier $(D 'd') is $(I not) present, and $(D to) is shorter than + $(D from), then $(D to) is extended by replicating the last character in + $(D to). + + Both $(D from) and $(D to) may contain ranges using the $(D '-') character + (e.g. $(D "a-d") is synonymous with $(D "abcd").) Neither accept a leading + $(D '^') as meaning the complement of the string (use the $(D 'c') modifier + for that). + +/ +C1[] tr(C1, C2, C3, C4 = immutable char) + (C1[] str, const(C2)[] from, const(C3)[] to, const(C4)[] modifiers = null) +{ + import std.array : appender; + import std.conv : conv_to = to; + import std.utf : decode; + + bool mod_c; + bool mod_d; + bool mod_s; + + foreach (char c; modifiers) + { + switch (c) + { + case 'c': mod_c = 1; break; // complement + case 'd': mod_d = 1; break; // delete unreplaced chars + case 's': mod_s = 1; break; // squeeze duplicated replaced chars + default: assert(0); + } + } + + if (to.empty && !mod_d) + to = conv_to!(typeof(to))(from); + + auto result = appender!(C1[])(); + bool modified; + dchar lastc; + + foreach (dchar c; str) + { + dchar lastf; + dchar lastt; + dchar newc; + int n = 0; + + for (size_t i = 0; i < from.length; ) + { + immutable f = decode(from, i); + if (f == '-' && lastf != dchar.init && i < from.length) + { + immutable nextf = decode(from, i); + if (lastf <= c && c <= nextf) + { + n += c - lastf - 1; + if (mod_c) + goto Lnotfound; + goto Lfound; + } + n += nextf - lastf; + lastf = lastf.init; + continue; + } + + if (c == f) + { if (mod_c) + goto Lnotfound; + goto Lfound; + } + lastf = f; + n++; + } + if (!mod_c) + goto Lnotfound; + n = 0; // consider it 'found' at position 0 + + Lfound: + + // Find the nth character in to[] + dchar nextt; + for (size_t i = 0; i < to.length; ) + { + immutable t = decode(to, i); + if (t == '-' && lastt != dchar.init && i < to.length) + { + nextt = decode(to, i); + n -= nextt - lastt; + if (n < 0) + { + newc = nextt + n + 1; + goto Lnewc; + } + lastt = dchar.init; + continue; + } + if (n == 0) + { newc = t; + goto Lnewc; + } + lastt = t; + nextt = t; + n--; + } + if (mod_d) + continue; + newc = nextt; + + Lnewc: + if (mod_s && modified && newc == lastc) + continue; + result.put(newc); + assert(newc != dchar.init); + modified = true; + lastc = newc; + continue; + + Lnotfound: + result.put(c); + lastc = c; + modified = false; + } + + return result.data; +} + +@safe pure unittest +{ + import std.algorithm.comparison : equal; + import std.conv : to; + import std.exception : assertCTFEable; + + // Complete list of test types; too slow to test'em all + // alias TestTypes = AliasSeq!( + // char[], const( char)[], immutable( char)[], + // wchar[], const(wchar)[], immutable(wchar)[], + // dchar[], const(dchar)[], immutable(dchar)[]); + + // Reduced list of test types + alias TestTypes = AliasSeq!(char[], const(wchar)[], immutable(dchar)[]); + + assertCTFEable!( + { + foreach (S; TestTypes) + { + foreach (T; TestTypes) + { + foreach (U; TestTypes) + { + assert(equal(tr(to!S("abcdef"), to!T("cd"), to!U("CD")), "abCDef")); + assert(equal(tr(to!S("abcdef"), to!T("b-d"), to!U("B-D")), "aBCDef")); + assert(equal(tr(to!S("abcdefgh"), to!T("b-dh"), to!U("B-Dx")), "aBCDefgx")); + assert(equal(tr(to!S("abcdefgh"), to!T("b-dh"), to!U("B-CDx")), "aBCDefgx")); + assert(equal(tr(to!S("abcdefgh"), to!T("b-dh"), to!U("B-BCDx")), "aBCDefgx")); + assert(equal(tr(to!S("abcdef"), to!T("ef"), to!U("*"), to!S("c")), "****ef")); + assert(equal(tr(to!S("abcdef"), to!T("ef"), to!U(""), to!T("d")), "abcd")); + assert(equal(tr(to!S("hello goodbye"), to!T("lo"), to!U(""), to!U("s")), "helo godbye")); + assert(equal(tr(to!S("hello goodbye"), to!T("lo"), to!U("x"), "s"), "hex gxdbye")); + assert(equal(tr(to!S("14-Jul-87"), to!T("a-zA-Z"), to!U(" "), "cs"), " Jul ")); + assert(equal(tr(to!S("Abc"), to!T("AAA"), to!U("XYZ")), "Xbc")); + } + } + + auto s = to!S("hello world"); + static assert(is(typeof(s) == typeof(tr(s, "he", "if")))); + } + }); +} + +@system pure unittest +{ + import core.exception : AssertError; + import std.exception : assertThrown; + assertThrown!AssertError(tr("abcdef", "cd", "CD", "X")); +} + +/** + * Takes a string $(D s) and determines if it represents a number. This function + * also takes an optional parameter, $(D bAllowSep), which will accept the + * separator characters $(D ',') and $(D '__') within the string. But these + * characters should be stripped from the string before using any + * of the conversion functions like $(D to!int()), $(D to!float()), and etc + * else an error will occur. + * + * Also please note, that no spaces are allowed within the string + * anywhere whether it's a leading, trailing, or embedded space(s), + * thus they too must be stripped from the string before using this + * function, or any of the conversion functions. + * + * Params: + * s = the string or random access range to check + * bAllowSep = accept separator characters or not + * + * Returns: + * $(D bool) + */ +bool isNumeric(S)(S s, bool bAllowSep = false) +if (isSomeString!S || + (isRandomAccessRange!S && + hasSlicing!S && + isSomeChar!(ElementType!S) && + !isInfinite!S)) +{ + import std.algorithm.comparison : among; + import std.ascii : isASCII; + + // ASCII only case insensitive comparison with two ranges + static bool asciiCmp(S1)(S1 a, string b) + { + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map; + import std.ascii : toLower; + import std.utf : byChar; + return a.map!toLower.equal(b.byChar.map!toLower); + } + + // auto-decoding special case, we're only comparing characters + // in the ASCII range so there's no reason to decode + static if (isSomeString!S) + { + import std.utf : byCodeUnit; + auto codeUnits = s.byCodeUnit; + } + else + { + alias codeUnits = s; + } + + if (codeUnits.empty) + return false; + + // Check for NaN (Not a Number) and for Infinity + if (codeUnits.among!((a, b) => asciiCmp(a.save, b)) + ("nan", "nani", "nan+nani", "inf", "-inf")) + return true; + + immutable frontResult = codeUnits.front; + if (frontResult == '-' || frontResult == '+') + codeUnits.popFront; + + immutable iLen = codeUnits.length; + bool bDecimalPoint, bExponent, bComplex, sawDigits; + + for (size_t i = 0; i < iLen; i++) + { + immutable c = codeUnits[i]; + + if (!c.isASCII) + return false; + + // Digits are good, skip to the next character + if (c >= '0' && c <= '9') + { + sawDigits = true; + continue; + } + + // Check for the complex type, and if found + // reset the flags for checking the 2nd number. + if (c == '+') + { + if (!i) + return false; + bDecimalPoint = false; + bExponent = false; + bComplex = true; + sawDigits = false; + continue; + } + + // Allow only one exponent per number + if (c == 'e' || c == 'E') + { + // A 2nd exponent found, return not a number + if (bExponent || i + 1 >= iLen) + return false; + // Look forward for the sign, and if + // missing then this is not a number. + if (codeUnits[i + 1] != '-' && codeUnits[i + 1] != '+') + return false; + bExponent = true; + i++; + continue; + } + // Allow only one decimal point per number to be used + if (c == '.') + { + // A 2nd decimal point found, return not a number + if (bDecimalPoint) + return false; + bDecimalPoint = true; + continue; + } + // Check for ending literal characters: "f,u,l,i,ul,fi,li", + // and whether they're being used with the correct datatype. + if (i == iLen - 2) + { + if (!sawDigits) + return false; + // Integer Whole Number + if (asciiCmp(codeUnits[i .. iLen], "ul") && + (!bDecimalPoint && !bExponent && !bComplex)) + return true; + // Floating-Point Number + if (codeUnits[i .. iLen].among!((a, b) => asciiCmp(a, b))("fi", "li") && + (bDecimalPoint || bExponent || bComplex)) + return true; + if (asciiCmp(codeUnits[i .. iLen], "ul") && + (bDecimalPoint || bExponent || bComplex)) + return false; + // Could be a Integer or a Float, thus + // all these suffixes are valid for both + return codeUnits[i .. iLen].among!((a, b) => asciiCmp(a, b)) + ("ul", "fi", "li") != 0; + } + if (i == iLen - 1) + { + if (!sawDigits) + return false; + // Integer Whole Number + if (c.among!('u', 'l', 'U', 'L')() && + (!bDecimalPoint && !bExponent && !bComplex)) + return true; + // Check to see if the last character in the string + // is the required 'i' character + if (bComplex) + return c.among!('i', 'I')() != 0; + // Floating-Point Number + return c.among!('l', 'L', 'f', 'F', 'i', 'I')() != 0; + } + // Check if separators are allowed to be in the numeric string + if (!bAllowSep || !c.among!('_', ',')()) + return false; + } + + return sawDigits; +} + +/** + * Integer Whole Number: (byte, ubyte, short, ushort, int, uint, long, and ulong) + * ['+'|'-']digit(s)[U|L|UL] + */ +@safe @nogc pure nothrow unittest +{ + assert(isNumeric("123")); + assert(isNumeric("123UL")); + assert(isNumeric("123L")); + assert(isNumeric("+123U")); + assert(isNumeric("-123L")); +} + +/** + * Floating-Point Number: (float, double, real, ifloat, idouble, and ireal) + * ['+'|'-']digit(s)[.][digit(s)][[e-|e+]digit(s)][i|f|L|Li|fi]] + * or [nan|nani|inf|-inf] + */ +@safe @nogc pure nothrow unittest +{ + assert(isNumeric("+123")); + assert(isNumeric("-123.01")); + assert(isNumeric("123.3e-10f")); + assert(isNumeric("123.3e-10fi")); + assert(isNumeric("123.3e-10L")); + + assert(isNumeric("nan")); + assert(isNumeric("nani")); + assert(isNumeric("-inf")); +} + +/** + * Floating-Point Number: (cfloat, cdouble, and creal) + * ['+'|'-']digit(s)[.][digit(s)][[e-|e+]digit(s)][+] + * [digit(s)[.][digit(s)][[e-|e+]digit(s)][i|f|L|Li|fi]] + * or [nan|nani|nan+nani|inf|-inf] + */ +@safe @nogc pure nothrow unittest +{ + assert(isNumeric("-123e-1+456.9e-10Li")); + assert(isNumeric("+123e+10+456i")); + assert(isNumeric("123+456")); +} + +@safe @nogc pure nothrow unittest +{ + assert(!isNumeric("F")); + assert(!isNumeric("L")); + assert(!isNumeric("U")); + assert(!isNumeric("i")); + assert(!isNumeric("fi")); + assert(!isNumeric("ul")); + assert(!isNumeric("li")); + assert(!isNumeric(".")); + assert(!isNumeric("-")); + assert(!isNumeric("+")); + assert(!isNumeric("e-")); + assert(!isNumeric("e+")); + assert(!isNumeric(".f")); + assert(!isNumeric("e+f")); + assert(!isNumeric("++1")); + assert(!isNumeric("")); + assert(!isNumeric("1E+1E+1")); + assert(!isNumeric("1E1")); + assert(!isNumeric("\x81")); +} + +// Test string types +@safe unittest +{ + import std.conv : to; + + foreach (T; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[])) + { + assert("123".to!T.isNumeric()); + assert("123UL".to!T.isNumeric()); + assert("123fi".to!T.isNumeric()); + assert("123li".to!T.isNumeric()); + assert(!"--123L".to!T.isNumeric()); + } +} + +// test ranges +@system pure unittest +{ + import std.range : refRange; + import std.utf : byCodeUnit; + + assert("123".byCodeUnit.isNumeric()); + assert("123UL".byCodeUnit.isNumeric()); + assert("123fi".byCodeUnit.isNumeric()); + assert("123li".byCodeUnit.isNumeric()); + assert(!"--123L".byCodeUnit.isNumeric()); + + dstring z = "0"; + assert(isNumeric(refRange(&z))); + + dstring nani = "nani"; + assert(isNumeric(refRange(&nani))); +} + +/// isNumeric works with CTFE +@safe pure unittest +{ + enum a = isNumeric("123.00E-5+1234.45E-12Li"); + enum b = isNumeric("12345xxxx890"); + + static assert( a); + static assert(!b); +} + +@system unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + // Test the isNumeric(in string) function + assert(isNumeric("1") == true ); + assert(isNumeric("1.0") == true ); + assert(isNumeric("1e-1") == true ); + assert(isNumeric("12345xxxx890") == false ); + assert(isNumeric("567L") == true ); + assert(isNumeric("23UL") == true ); + assert(isNumeric("-123..56f") == false ); + assert(isNumeric("12.3.5.6") == false ); + assert(isNumeric(" 12.356") == false ); + assert(isNumeric("123 5.6") == false ); + assert(isNumeric("1233E-1+1.0e-1i") == true ); + + assert(isNumeric("123.00E-5+1234.45E-12Li") == true); + assert(isNumeric("123.00e-5+1234.45E-12iL") == false); + assert(isNumeric("123.00e-5+1234.45e-12uL") == false); + assert(isNumeric("123.00E-5+1234.45e-12lu") == false); + + assert(isNumeric("123fi") == true); + assert(isNumeric("123li") == true); + assert(isNumeric("--123L") == false); + assert(isNumeric("+123.5UL") == false); + assert(isNumeric("123f") == true); + assert(isNumeric("123.u") == false); + + // @@@BUG@@ to!string(float) is not CTFEable. + // Related: formatValue(T) if (is(FloatingPointTypeOf!T)) + if (!__ctfe) + { + assert(isNumeric(to!string(real.nan)) == true); + assert(isNumeric(to!string(-real.infinity)) == true); + assert(isNumeric(to!string(123e+2+1234.78Li)) == true); + } + + string s = "$250.99-"; + assert(isNumeric(s[1 .. s.length - 2]) == true); + assert(isNumeric(s) == false); + assert(isNumeric(s[0 .. s.length - 1]) == false); + }); + + assert(!isNumeric("-")); + assert(!isNumeric("+")); +} + +/***************************** + * Soundex algorithm. + * + * The Soundex algorithm converts a word into 4 characters + * based on how the word sounds phonetically. The idea is that + * two spellings that sound alike will have the same Soundex + * value, which means that Soundex can be used for fuzzy matching + * of names. + * + * Params: + * str = String or InputRange to convert to Soundex representation. + * + * Returns: + * The four character array with the Soundex result in it. + * The array has zero's in it if there is no Soundex representation for the string. + * + * See_Also: + * $(LINK2 http://en.wikipedia.org/wiki/Soundex, Wikipedia), + * $(LUCKY The Soundex Indexing System) + * $(LREF soundex) + * + * Bugs: + * Only works well with English names. + * There are other arguably better Soundex algorithms, + * but this one is the standard one. + */ +char[4] soundexer(Range)(Range str) +if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) && + !isConvertibleToString!Range) +{ + alias C = Unqual!(ElementEncodingType!Range); + + static immutable dex = + // ABCDEFGHIJKLMNOPQRSTUVWXYZ + "01230120022455012623010202"; + + char[4] result = void; + size_t b = 0; + C lastc; + foreach (C c; str) + { + if (c >= 'a' && c <= 'z') + c -= 'a' - 'A'; + else if (c >= 'A' && c <= 'Z') + { + } + else + { + lastc = lastc.init; + continue; + } + if (b == 0) + { + result[0] = cast(char) c; + b++; + lastc = dex[c - 'A']; + } + else + { + if (c == 'H' || c == 'W') + continue; + if (c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U') + lastc = lastc.init; + c = dex[c - 'A']; + if (c != '0' && c != lastc) + { + result[b] = cast(char) c; + b++; + lastc = c; + } + if (b == 4) + goto Lret; + } + } + if (b == 0) + result[] = 0; + else + result[b .. 4] = '0'; + Lret: + return result; +} + +char[4] soundexer(Range)(auto ref Range str) +if (isConvertibleToString!Range) +{ + return soundexer!(StringTypeOf!Range)(str); +} + +/***************************** + * Like $(LREF soundexer), but with different parameters + * and return value. + * + * Params: + * str = String to convert to Soundex representation. + * buffer = Optional 4 char array to put the resulting Soundex + * characters into. If null, the return value + * buffer will be allocated on the heap. + * Returns: + * The four character array with the Soundex result in it. + * Returns null if there is no Soundex representation for the string. + * See_Also: + * $(LREF soundexer) + */ +char[] soundex(const(char)[] str, char[] buffer = null) + @safe pure nothrow +in +{ + assert(buffer is null || buffer.length >= 4); +} +out (result) +{ + if (result !is null) + { + assert(result.length == 4); + assert(result[0] >= 'A' && result[0] <= 'Z'); + foreach (char c; result[1 .. 4]) + assert(c >= '0' && c <= '6'); + } +} +body +{ + char[4] result = soundexer(str); + if (result[0] == 0) + return null; + if (buffer is null) + buffer = new char[4]; + buffer[] = result[]; + return buffer; +} + + +@safe pure nothrow unittest +{ + import std.exception : assertCTFEable; + assertCTFEable!( + { + char[4] buffer; + + assert(soundex(null) == null); + assert(soundex("") == null); + assert(soundex("0123^&^^**&^") == null); + assert(soundex("Euler") == "E460"); + assert(soundex(" Ellery ") == "E460"); + assert(soundex("Gauss") == "G200"); + assert(soundex("Ghosh") == "G200"); + assert(soundex("Hilbert") == "H416"); + assert(soundex("Heilbronn") == "H416"); + assert(soundex("Knuth") == "K530"); + assert(soundex("Kant", buffer) == "K530"); + assert(soundex("Lloyd") == "L300"); + assert(soundex("Ladd") == "L300"); + assert(soundex("Lukasiewicz", buffer) == "L222"); + assert(soundex("Lissajous") == "L222"); + assert(soundex("Robert") == "R163"); + assert(soundex("Rupert") == "R163"); + assert(soundex("Rubin") == "R150"); + assert(soundex("Washington") == "W252"); + assert(soundex("Lee") == "L000"); + assert(soundex("Gutierrez") == "G362"); + assert(soundex("Pfister") == "P236"); + assert(soundex("Jackson") == "J250"); + assert(soundex("Tymczak") == "T522"); + assert(soundex("Ashcraft") == "A261"); + + assert(soundex("Woo") == "W000"); + assert(soundex("Pilgrim") == "P426"); + assert(soundex("Flingjingwaller") == "F452"); + assert(soundex("PEARSE") == "P620"); + assert(soundex("PIERCE") == "P620"); + assert(soundex("Price") == "P620"); + assert(soundex("CATHY") == "C300"); + assert(soundex("KATHY") == "K300"); + assert(soundex("Jones") == "J520"); + assert(soundex("johnsons") == "J525"); + assert(soundex("Hardin") == "H635"); + assert(soundex("Martinez") == "M635"); + + import std.utf : byChar, byDchar, byWchar; + assert(soundexer("Martinez".byChar ) == "M635"); + assert(soundexer("Martinez".byWchar) == "M635"); + assert(soundexer("Martinez".byDchar) == "M635"); + }); +} + +@safe pure unittest +{ + assert(testAliasedString!soundexer("Martinez")); +} + + +/*************************************************** + * Construct an associative array consisting of all + * abbreviations that uniquely map to the strings in values. + * + * This is useful in cases where the user is expected to type + * in one of a known set of strings, and the program will helpfully + * auto-complete the string once sufficient characters have been + * entered that uniquely identify it. + */ + +string[string] abbrev(string[] values) @safe pure +{ + import std.algorithm.sorting : sort; + + string[string] result; + + // Make a copy when sorting so we follow COW principles. + values = values.dup; + sort(values); + + size_t values_length = values.length; + size_t lasti = values_length; + size_t nexti; + + string nv; + string lv; + + for (size_t i = 0; i < values_length; i = nexti) + { + string value = values[i]; + + // Skip dups + for (nexti = i + 1; nexti < values_length; nexti++) + { + nv = values[nexti]; + if (value != values[nexti]) + break; + } + + import std.utf : stride; + + for (size_t j = 0; j < value.length; j += stride(value, j)) + { + string v = value[0 .. j]; + + if ((nexti == values_length || j > nv.length || v != nv[0 .. j]) && + (lasti == values_length || j > lv.length || v != lv[0 .. j])) + { + result[v] = value; + } + } + result[value] = value; + lasti = i; + lv = value; + } + + return result; +} + +/// +@safe unittest +{ + import std.string; + + static string[] list = [ "food", "foxy" ]; + auto abbrevs = abbrev(list); + assert(abbrevs == ["fox": "foxy", "food": "food", + "foxy": "foxy", "foo": "food"]); +} + + +@system pure unittest +{ + import std.algorithm.sorting : sort; + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + string[] values; + values ~= "hello"; + values ~= "hello"; + values ~= "he"; + + string[string] r; + + r = abbrev(values); + auto keys = r.keys.dup; + sort(keys); + + assert(keys.length == 4); + assert(keys[0] == "he"); + assert(keys[1] == "hel"); + assert(keys[2] == "hell"); + assert(keys[3] == "hello"); + + assert(r[keys[0]] == "he"); + assert(r[keys[1]] == "hello"); + assert(r[keys[2]] == "hello"); + assert(r[keys[3]] == "hello"); + }); +} + + +/****************************************** + * Compute _column number at the end of the printed form of the string, + * assuming the string starts in the leftmost _column, which is numbered + * starting from 0. + * + * Tab characters are expanded into enough spaces to bring the _column number + * to the next multiple of tabsize. + * If there are multiple lines in the string, the _column number of the last + * line is returned. + * + * Params: + * str = string or InputRange to be analyzed + * tabsize = number of columns a tab character represents + * + * Returns: + * column number + */ + +size_t column(Range)(Range str, in size_t tabsize = 8) +if ((isInputRange!Range && isSomeChar!(Unqual!(ElementEncodingType!Range)) || + isNarrowString!Range) && + !isConvertibleToString!Range) +{ + static if (is(Unqual!(ElementEncodingType!Range) == char)) + { + // decoding needed for chars + import std.utf : byDchar; + + return str.byDchar.column(tabsize); + } + else + { + // decoding not needed for wchars and dchars + import std.uni : lineSep, paraSep, nelSep; + + size_t column; + + foreach (const c; str) + { + switch (c) + { + case '\t': + column = (column + tabsize) / tabsize * tabsize; + break; + + case '\r': + case '\n': + case paraSep: + case lineSep: + case nelSep: + column = 0; + break; + + default: + column++; + break; + } + } + return column; + } +} + +/// +@safe pure unittest +{ + import std.utf : byChar, byWchar, byDchar; + + assert(column("1234 ") == 5); + assert(column("1234 "w) == 5); + assert(column("1234 "d) == 5); + + assert(column("1234 ".byChar()) == 5); + assert(column("1234 "w.byWchar()) == 5); + assert(column("1234 "d.byDchar()) == 5); + + // Tab stops are set at 8 spaces by default; tab characters insert enough + // spaces to bring the column position to the next multiple of 8. + assert(column("\t") == 8); + assert(column("1\t") == 8); + assert(column("\t1") == 9); + assert(column("123\t") == 8); + + // Other tab widths are possible by specifying it explicitly: + assert(column("\t", 4) == 4); + assert(column("1\t", 4) == 4); + assert(column("\t1", 4) == 5); + assert(column("123\t", 4) == 4); + + // New lines reset the column number. + assert(column("abc\n") == 0); + assert(column("abc\n1") == 1); + assert(column("abcdefg\r1234") == 4); + assert(column("abc\u20281") == 1); + assert(column("abc\u20291") == 1); + assert(column("abc\u00851") == 1); + assert(column("abc\u00861") == 5); +} + +size_t column(Range)(auto ref Range str, in size_t tabsize = 8) +if (isConvertibleToString!Range) +{ + return column!(StringTypeOf!Range)(str, tabsize); +} + +@safe pure unittest +{ + assert(testAliasedString!column("abc\u00861")); +} + +@safe @nogc unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + assert(column(string.init) == 0); + assert(column("") == 0); + assert(column("\t") == 8); + assert(column("abc\t") == 8); + assert(column("12345678\t") == 16); + }); +} + +/****************************************** + * Wrap text into a paragraph. + * + * The input text string s is formed into a paragraph + * by breaking it up into a sequence of lines, delineated + * by \n, such that the number of columns is not exceeded + * on each line. + * The last line is terminated with a \n. + * Params: + * s = text string to be wrapped + * columns = maximum number of _columns in the paragraph + * firstindent = string used to _indent first line of the paragraph + * indent = string to use to _indent following lines of the paragraph + * tabsize = column spacing of tabs in firstindent[] and indent[] + * Returns: + * resulting paragraph as an allocated string + */ + +S wrap(S)(S s, in size_t columns = 80, S firstindent = null, +S indent = null, in size_t tabsize = 8) +if (isSomeString!S) +{ + import std.uni : isWhite; + typeof(s.dup) result; + bool inword; + bool first = true; + size_t wordstart; + + const indentcol = column(indent, tabsize); + + result.length = firstindent.length + s.length; + result.length = firstindent.length; + result[] = firstindent[]; + auto col = column(firstindent, tabsize); + foreach (size_t i, dchar c; s) + { + if (isWhite(c)) + { + if (inword) + { + if (first) + { + } + else if (col + 1 + (i - wordstart) > columns) + { + result ~= '\n'; + result ~= indent; + col = indentcol; + } + else + { + result ~= ' '; + col += 1; + } + result ~= s[wordstart .. i]; + col += i - wordstart; + inword = false; + first = false; + } + } + else + { + if (!inword) + { + wordstart = i; + inword = true; + } + } + } + + if (inword) + { + if (col + 1 + (s.length - wordstart) >= columns) + { + result ~= '\n'; + result ~= indent; + } + else if (result.length != firstindent.length) + result ~= ' '; + result ~= s[wordstart .. s.length]; + } + result ~= '\n'; + + return result; +} + +/// +@safe pure unittest +{ + assert(wrap("a short string", 7) == "a short\nstring\n"); + + // wrap will not break inside of a word, but at the next space + assert(wrap("a short string", 4) == "a\nshort\nstring\n"); + + assert(wrap("a short string", 7, "\t") == "\ta\nshort\nstring\n"); + assert(wrap("a short string", 7, "\t", " ") == "\ta\n short\n string\n"); +} + +@safe pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + assertCTFEable!( + { + assert(wrap(string.init) == "\n"); + assert(wrap(" a b df ") == "a b df\n"); + assert(wrap(" a b df ", 3) == "a b\ndf\n"); + assert(wrap(" a bc df ", 3) == "a\nbc\ndf\n"); + assert(wrap(" abcd df ", 3) == "abcd\ndf\n"); + assert(wrap("x") == "x\n"); + assert(wrap("u u") == "u u\n"); + assert(wrap("abcd", 3) == "\nabcd\n"); + assert(wrap("a de", 10, "\t", " ", 8) == "\ta\n de\n"); + }); +} + +/****************************************** + * Removes one level of indentation from a multi-line string. + * + * This uniformly outdents the text as much as possible. + * Whitespace-only lines are always converted to blank lines. + * + * Does not allocate memory if it does not throw. + * + * Params: + * str = multi-line string + * + * Returns: + * outdented string + * + * Throws: + * StringException if indentation is done with different sequences + * of whitespace characters. + */ +S outdent(S)(S str) @safe pure +if (isSomeString!S) +{ + return str.splitLines(Yes.keepTerminator).outdent().join(); +} + +/// +@safe pure unittest +{ + enum pretty = q{ + import std.stdio; + void main() { + writeln("Hello"); + } + }.outdent(); + + enum ugly = q{ +import std.stdio; +void main() { + writeln("Hello"); +} +}; + + assert(pretty == ugly); +} + + +/****************************************** + * Removes one level of indentation from an array of single-line strings. + * + * This uniformly outdents the text as much as possible. + * Whitespace-only lines are always converted to blank lines. + * + * Params: + * lines = array of single-line strings + * + * Returns: + * lines[] is rewritten in place with outdented lines + * + * Throws: + * StringException if indentation is done with different sequences + * of whitespace characters. + */ +S[] outdent(S)(S[] lines) @safe pure +if (isSomeString!S) +{ + import std.algorithm.searching : startsWith; + + if (lines.empty) + { + return null; + } + + static S leadingWhiteOf(S str) + { + return str[ 0 .. $ - stripLeft(str).length ]; + } + + S shortestIndent; + foreach (ref line; lines) + { + const stripped = line.stripLeft(); + + if (stripped.empty) + { + line = line[line.chomp().length .. $]; + } + else + { + const indent = leadingWhiteOf(line); + + // Comparing number of code units instead of code points is OK here + // because this function throws upon inconsistent indentation. + if (shortestIndent is null || indent.length < shortestIndent.length) + { + if (indent.empty) + return lines; + shortestIndent = indent; + } + } + } + + foreach (ref line; lines) + { + const stripped = line.stripLeft(); + + if (stripped.empty) + { + // Do nothing + } + else if (line.startsWith(shortestIndent)) + { + line = line[shortestIndent.length .. $]; + } + else + { + throw new StringException("outdent: Inconsistent indentation"); + } + } + + return lines; +} + +@safe pure unittest +{ + import std.conv : to; + import std.exception : assertCTFEable; + + template outdent_testStr(S) + { + enum S outdent_testStr = +" + \t\tX + \t\U00010143X + \t\t + + \t\t\tX +\t "; + } + + template outdent_expected(S) + { + enum S outdent_expected = +" +\tX +\U00010143X + + +\t\tX +"; + } + + assertCTFEable!( + { + + foreach (S; AliasSeq!(string, wstring, dstring)) + { + enum S blank = ""; + assert(blank.outdent() == blank); + static assert(blank.outdent() == blank); + + enum S testStr1 = " \n \t\n "; + enum S expected1 = "\n\n"; + assert(testStr1.outdent() == expected1); + static assert(testStr1.outdent() == expected1); + + assert(testStr1[0..$-1].outdent() == expected1); + static assert(testStr1[0..$-1].outdent() == expected1); + + enum S testStr2 = "a\n \t\nb"; + assert(testStr2.outdent() == testStr2); + static assert(testStr2.outdent() == testStr2); + + enum S testStr3 = +" + \t\tX + \t\U00010143X + \t\t + + \t\t\tX +\t "; + + enum S expected3 = +" +\tX +\U00010143X + + +\t\tX +"; + assert(testStr3.outdent() == expected3); + static assert(testStr3.outdent() == expected3); + + enum testStr4 = " X\r X\n X\r\n X\u2028 X\u2029 X"; + enum expected4 = "X\rX\nX\r\nX\u2028X\u2029X"; + assert(testStr4.outdent() == expected4); + static assert(testStr4.outdent() == expected4); + + enum testStr5 = testStr4[0..$-1]; + enum expected5 = expected4[0..$-1]; + assert(testStr5.outdent() == expected5); + static assert(testStr5.outdent() == expected5); + + enum testStr6 = " \r \n \r\n \u2028 \u2029"; + enum expected6 = "\r\n\r\n\u2028\u2029"; + assert(testStr6.outdent() == expected6); + static assert(testStr6.outdent() == expected6); + + enum testStr7 = " a \n b "; + enum expected7 = "a \nb "; + assert(testStr7.outdent() == expected7); + static assert(testStr7.outdent() == expected7); + } + }); +} + +@safe pure unittest +{ + import std.exception : assertThrown; + auto bad = " a\n\tb\n c"; + assertThrown!StringException(bad.outdent); +} + +/** Assume the given array of integers $(D arr) is a well-formed UTF string and +return it typed as a UTF string. + +$(D ubyte) becomes $(D char), $(D ushort) becomes $(D wchar) and $(D uint) +becomes $(D dchar). Type qualifiers are preserved. + +When compiled with debug mode, this function performs an extra check to make +sure the return value is a valid Unicode string. + +Params: + arr = array of bytes, ubytes, shorts, ushorts, ints, or uints + +Returns: + arr retyped as an array of chars, wchars, or dchars + +See_Also: $(LREF representation) +*/ +auto assumeUTF(T)(T[] arr) pure +if (staticIndexOf!(Unqual!T, ubyte, ushort, uint) != -1) +{ + import std.traits : ModifyTypePreservingTQ; + import std.utf : validate; + alias ToUTFType(U) = AliasSeq!(char, wchar, dchar)[U.sizeof / 2]; + auto asUTF = cast(ModifyTypePreservingTQ!(ToUTFType, T)[])arr; + debug validate(asUTF); + return asUTF; +} + +/// +@safe pure unittest +{ + string a = "Hölo World"; + immutable(ubyte)[] b = a.representation; + string c = b.assumeUTF; + + assert(a == c); +} + +pure @system unittest +{ + import std.algorithm.comparison : equal; + foreach (T; AliasSeq!(char[], wchar[], dchar[])) + { + immutable T jti = "Hello World"; + T jt = jti.dup; + + static if (is(T == char[])) + { + auto gt = cast(ubyte[]) jt; + auto gtc = cast(const(ubyte)[])jt; + auto gti = cast(immutable(ubyte)[])jt; + } + else static if (is(T == wchar[])) + { + auto gt = cast(ushort[]) jt; + auto gtc = cast(const(ushort)[])jt; + auto gti = cast(immutable(ushort)[])jt; + } + else static if (is(T == dchar[])) + { + auto gt = cast(uint[]) jt; + auto gtc = cast(const(uint)[])jt; + auto gti = cast(immutable(uint)[])jt; + } + + auto ht = assumeUTF(gt); + auto htc = assumeUTF(gtc); + auto hti = assumeUTF(gti); + assert(equal(jt, ht)); + assert(equal(jt, htc)); + assert(equal(jt, hti)); + } +} diff --git a/libphobos/src/std/system.d b/libphobos/src/std/system.d new file mode 100644 index 0000000..f8c23b7 --- /dev/null +++ b/libphobos/src/std/system.d @@ -0,0 +1,74 @@ +// Written in the D programming language. + +/** + * Information about the target operating system, environment, and CPU. + * + * Copyright: Copyright Digital Mars 2000 - 2011 + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: $(HTTP digitalmars.com, Walter Bright) and Jonathan M Davis + * Source: $(PHOBOSSRC std/_system.d) + */ +module std.system; + +immutable +{ + /++ + Operating system. + + Note: + This is for cases where you need a value representing the OS at + runtime. If you're doing something which should compile differently + on different OSes, then please use $(D version (Windows)), + $(D version (linux)), etc. + + See_Also: + $(DDSUBLINK spec/version,PredefinedVersions, Predefined Versions) + +/ + enum OS + { + win32 = 1, /// Microsoft 32 bit Windows systems + win64, /// Microsoft 64 bit Windows systems + linux, /// All Linux Systems + osx, /// Mac OS X + freeBSD, /// FreeBSD + netBSD, /// NetBSD + solaris, /// Solaris + android, /// Android + otherPosix /// Other Posix Systems + } + + /// The OS that the program was compiled for. + version (Win32) OS os = OS.win32; + else version (Win64) OS os = OS.win64; + else version (Android) OS os = OS.android; + else version (linux) OS os = OS.linux; + else version (OSX) OS os = OS.osx; + else version (FreeBSD) OS os = OS.freeBSD; + else version (NetBSD) OS os = OS.netBSD; + else version (Posix) OS os = OS.otherPosix; + else static assert(0, "Unknown OS."); + + /++ + Byte order endianness. + + Note: + This is intended for cases where you need to deal with endianness at + runtime. If you're doing something which should compile differently + depending on whether you're compiling on a big endian or little + endian machine, then please use $(D version (BigEndian)) and + $(D version (LittleEndian)). + + See_Also: + $(DDSUBLINK spec/version,PredefinedVersions, Predefined Versions) + +/ + enum Endian + { + bigEndian, /// Big endian byte order + littleEndian /// Little endian byte order + } + + /// The endianness that the program was compiled for. + version (LittleEndian) Endian endian = Endian.littleEndian; + else Endian endian = Endian.bigEndian; +} + diff --git a/libphobos/src/std/traits.d b/libphobos/src/std/traits.d new file mode 100644 index 0000000..4359dfb --- /dev/null +++ b/libphobos/src/std/traits.d @@ -0,0 +1,8058 @@ +// Written in the D programming language. + +/** + * Templates which extract information about types and symbols at compile time. + * + * $(SCRIPT inhibitQuickIndex = 1;) + * + * $(DIVC quickindex, + * $(BOOKTABLE , + * $(TR $(TH Category) $(TH Templates)) + * $(TR $(TD Symbol Name _traits) $(TD + * $(LREF fullyQualifiedName) + * $(LREF moduleName) + * $(LREF packageName) + * )) + * $(TR $(TD Function _traits) $(TD + * $(LREF isFunction) + * $(LREF arity) + * $(LREF functionAttributes) + * $(LREF hasFunctionAttributes) + * $(LREF functionLinkage) + * $(LREF FunctionTypeOf) + * $(LREF isSafe) + * $(LREF isUnsafe) + * $(LREF isFinal) + * $(LREF ParameterDefaults) + * $(LREF ParameterIdentifierTuple) + * $(LREF ParameterStorageClassTuple) + * $(LREF Parameters) + * $(LREF ReturnType) + * $(LREF SetFunctionAttributes) + * $(LREF variadicFunctionStyle) + * )) + * $(TR $(TD Aggregate Type _traits) $(TD + * $(LREF BaseClassesTuple) + * $(LREF BaseTypeTuple) + * $(LREF classInstanceAlignment) + * $(LREF EnumMembers) + * $(LREF FieldNameTuple) + * $(LREF Fields) + * $(LREF hasAliasing) + * $(LREF hasElaborateAssign) + * $(LREF hasElaborateCopyConstructor) + * $(LREF hasElaborateDestructor) + * $(LREF hasIndirections) + * $(LREF hasMember) + * $(LREF hasStaticMember) + * $(LREF hasNested) + * $(LREF hasUnsharedAliasing) + * $(LREF InterfacesTuple) + * $(LREF isInnerClass) + * $(LREF isNested) + * $(LREF MemberFunctionsTuple) + * $(LREF RepresentationTypeTuple) + * $(LREF TemplateArgsOf) + * $(LREF TemplateOf) + * $(LREF TransitiveBaseTypeTuple) + * )) + * $(TR $(TD Type Conversion) $(TD + * $(LREF CommonType) + * $(LREF ImplicitConversionTargets) + * $(LREF CopyTypeQualifiers) + * $(LREF CopyConstness) + * $(LREF isAssignable) + * $(LREF isCovariantWith) + * $(LREF isImplicitlyConvertible) + * )) + * $(TR $(TD SomethingTypeOf) $(TD + * $(LREF rvalueOf) + * $(LREF lvalueOf) + * $(LREF InoutOf) + * $(LREF ConstOf) + * $(LREF SharedOf) + * $(LREF SharedInoutOf) + * $(LREF SharedConstOf) + * $(LREF ImmutableOf) + * $(LREF QualifierOf) + * )) + * $(TR $(TD Categories of types) $(TD + * $(LREF allSameType) + * $(LREF ifTestable) + * $(LREF isType) + * $(LREF isAggregateType) + * $(LREF isArray) + * $(LREF isAssociativeArray) + * $(LREF isAutodecodableString) + * $(LREF isBasicType) + * $(LREF isBoolean) + * $(LREF isBuiltinType) + * $(LREF isCopyable) + * $(LREF isDynamicArray) + * $(LREF isEqualityComparable) + * $(LREF isFloatingPoint) + * $(LREF isIntegral) + * $(LREF isNarrowString) + * $(LREF isConvertibleToString) + * $(LREF isNumeric) + * $(LREF isOrderingComparable) + * $(LREF isPointer) + * $(LREF isScalarType) + * $(LREF isSigned) + * $(LREF isSIMDVector) + * $(LREF isSomeChar) + * $(LREF isSomeString) + * $(LREF isStaticArray) + * $(LREF isUnsigned) + * )) + * $(TR $(TD Type behaviours) $(TD + * $(LREF isAbstractClass) + * $(LREF isAbstractFunction) + * $(LREF isCallable) + * $(LREF isDelegate) + * $(LREF isExpressions) + * $(LREF isFinalClass) + * $(LREF isFinalFunction) + * $(LREF isFunctionPointer) + * $(LREF isInstanceOf) + * $(LREF isIterable) + * $(LREF isMutable) + * $(LREF isSomeFunction) + * $(LREF isTypeTuple) + * )) + * $(TR $(TD General Types) $(TD + * $(LREF ForeachType) + * $(LREF KeyType) + * $(LREF Largest) + * $(LREF mostNegative) + * $(LREF OriginalType) + * $(LREF PointerTarget) + * $(LREF Signed) + * $(LREF Unqual) + * $(LREF Unsigned) + * $(LREF ValueType) + * $(LREF Promoted) + * )) + * $(TR $(TD Misc) $(TD + * $(LREF mangledName) + * $(LREF Select) + * $(LREF select) + * )) + * $(TR $(TD User-Defined Attributes) $(TD + * $(LREF hasUDA) + * $(LREF getUDAs) + * $(LREF getSymbolsByUDA) + * )) + * ) + * ) + * + * Copyright: Copyright Digital Mars 2005 - 2009. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: $(HTTP digitalmars.com, Walter Bright), + * Tomasz Stachowiak ($(D isExpressions)), + * $(HTTP erdani.org, Andrei Alexandrescu), + * Shin Fujishiro, + * $(HTTP octarineparrot.com, Robert Clipsham), + * $(HTTP klickverbot.at, David Nadlinger), + * Kenji Hara, + * Shoichi Kato + * Source: $(PHOBOSSRC std/_traits.d) + */ +/* Copyright Digital Mars 2005 - 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) + */ +module std.traits; + +import std.meta : AliasSeq, allSatisfy; +import std.functional : unaryFun; + +// Legacy inheritance from std.typetuple +// See also: https://github.com/dlang/phobos/pull/5484#discussion_r122602797 +import std.meta : staticMapMeta = staticMap; +// TODO: find a way to trigger deprecation warnings +//deprecated("staticMap is part of std.meta: Please import std.meta") +alias staticMap = staticMapMeta; + +/////////////////////////////////////////////////////////////////////////////// +// Functions +/////////////////////////////////////////////////////////////////////////////// + +// Petit demangler +// (this or similar thing will eventually go to std.demangle if necessary +// ctfe stuffs are available) +private +{ + struct Demangle(T) + { + T value; // extracted information + string rest; + } + + /* Demangles mstr as the storage class part of Argument. */ + Demangle!uint demangleParameterStorageClass(string mstr) + { + uint pstc = 0; // parameter storage class + + // Argument --> Argument2 | M Argument2 + if (mstr.length > 0 && mstr[0] == 'M') + { + pstc |= ParameterStorageClass.scope_; + mstr = mstr[1 .. $]; + } + + // Argument2 --> Type | J Type | K Type | L Type + ParameterStorageClass stc2; + + switch (mstr.length ? mstr[0] : char.init) + { + case 'J': stc2 = ParameterStorageClass.out_; break; + case 'K': stc2 = ParameterStorageClass.ref_; break; + case 'L': stc2 = ParameterStorageClass.lazy_; break; + case 'N': if (mstr.length >= 2 && mstr[1] == 'k') + stc2 = ParameterStorageClass.return_; + break; + default : break; + } + if (stc2 != ParameterStorageClass.init) + { + pstc |= stc2; + mstr = mstr[1 .. $]; + if (stc2 & ParameterStorageClass.return_) + mstr = mstr[1 .. $]; + } + + return Demangle!uint(pstc, mstr); + } + + /* Demangles mstr as FuncAttrs. */ + Demangle!uint demangleFunctionAttributes(string mstr) + { + immutable LOOKUP_ATTRIBUTE = + [ + 'a': FunctionAttribute.pure_, + 'b': FunctionAttribute.nothrow_, + 'c': FunctionAttribute.ref_, + 'd': FunctionAttribute.property, + 'e': FunctionAttribute.trusted, + 'f': FunctionAttribute.safe, + 'i': FunctionAttribute.nogc, + 'j': FunctionAttribute.return_, + 'l': FunctionAttribute.scope_ + ]; + uint atts = 0; + + // FuncAttrs --> FuncAttr | FuncAttr FuncAttrs + // FuncAttr --> empty | Na | Nb | Nc | Nd | Ne | Nf | Ni | Nj + // except 'Ng' == inout, because it is a qualifier of function type + while (mstr.length >= 2 && mstr[0] == 'N' && mstr[1] != 'g' && mstr[1] != 'k') + { + if (FunctionAttribute att = LOOKUP_ATTRIBUTE[ mstr[1] ]) + { + atts |= att; + mstr = mstr[2 .. $]; + } + else assert(0); + } + return Demangle!uint(atts, mstr); + } + + static if (is(ucent)) + { + alias CentTypeList = AliasSeq!(cent, ucent); + alias SignedCentTypeList = AliasSeq!(cent); + alias UnsignedCentTypeList = AliasSeq!(ucent); + } + else + { + alias CentTypeList = AliasSeq!(); + alias SignedCentTypeList = AliasSeq!(); + alias UnsignedCentTypeList = AliasSeq!(); + } + + alias IntegralTypeList = AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, CentTypeList); + alias SignedIntTypeList = AliasSeq!(byte, short, int, long, SignedCentTypeList); + alias UnsignedIntTypeList = AliasSeq!(ubyte, ushort, uint, ulong, UnsignedCentTypeList); + alias FloatingPointTypeList = AliasSeq!(float, double, real); + alias ImaginaryTypeList = AliasSeq!(ifloat, idouble, ireal); + alias ComplexTypeList = AliasSeq!(cfloat, cdouble, creal); + alias NumericTypeList = AliasSeq!(IntegralTypeList, FloatingPointTypeList); + alias CharTypeList = AliasSeq!(char, wchar, dchar); +} + +package +{ + // Add the mutable qualifier to the given type T. + template MutableOf(T) { alias MutableOf = T ; } +} + +/// Add the inout qualifier to the given type T. +template InoutOf(T) { alias InoutOf = inout(T) ; } +/// Add the const qualifier to the given type T. +template ConstOf(T) { alias ConstOf = const(T) ; } +/// Add the shared qualifier to the given type T. +template SharedOf(T) { alias SharedOf = shared(T) ; } +/// Add the shared and inout qualifiers to the given type T. +template SharedInoutOf(T) { alias SharedInoutOf = shared(inout(T)); } +/// Add the shared and const qualifiers to the given type T. +template SharedConstOf(T) { alias SharedConstOf = shared(const(T)); } +/// Add the immutable qualifier to the given type T. +template ImmutableOf(T) { alias ImmutableOf = immutable(T) ; } + +@safe unittest +{ + static assert(is( MutableOf!int == int)); + static assert(is( InoutOf!int == inout int)); + static assert(is( ConstOf!int == const int)); + static assert(is( SharedOf!int == shared int)); + static assert(is(SharedInoutOf!int == shared inout int)); + static assert(is(SharedConstOf!int == shared const int)); + static assert(is( ImmutableOf!int == immutable int)); +} + +/// Get qualifier template from the given type T +template QualifierOf(T) +{ + static if (is(T == shared(const U), U)) alias QualifierOf = SharedConstOf; + else static if (is(T == const U , U)) alias QualifierOf = ConstOf; + else static if (is(T == shared(inout U), U)) alias QualifierOf = SharedInoutOf; + else static if (is(T == inout U , U)) alias QualifierOf = InoutOf; + else static if (is(T == immutable U , U)) alias QualifierOf = ImmutableOf; + else static if (is(T == shared U , U)) alias QualifierOf = SharedOf; + else alias QualifierOf = MutableOf; +} + +@safe unittest +{ + alias Qual1 = QualifierOf!( int); static assert(is(Qual1!long == long)); + alias Qual2 = QualifierOf!( inout int); static assert(is(Qual2!long == inout long)); + alias Qual3 = QualifierOf!( const int); static assert(is(Qual3!long == const long)); + alias Qual4 = QualifierOf!(shared int); static assert(is(Qual4!long == shared long)); + alias Qual5 = QualifierOf!(shared inout int); static assert(is(Qual5!long == shared inout long)); + alias Qual6 = QualifierOf!(shared const int); static assert(is(Qual6!long == shared const long)); + alias Qual7 = QualifierOf!( immutable int); static assert(is(Qual7!long == immutable long)); +} + +version (unittest) +{ + alias TypeQualifierList = AliasSeq!(MutableOf, ConstOf, SharedOf, SharedConstOf, ImmutableOf); + + struct SubTypeOf(T) + { + T val; + alias val this; + } +} + +private alias parentOf(alias sym) = Identity!(__traits(parent, sym)); +private alias parentOf(alias sym : T!Args, alias T, Args...) = Identity!(__traits(parent, T)); + +/** + * Get the full package name for the given symbol. + */ +template packageName(alias T) +{ + import std.algorithm.searching : startsWith; + + static if (__traits(compiles, parentOf!T)) + enum parent = packageName!(parentOf!T); + else + enum string parent = null; + + static if (T.stringof.startsWith("package ")) + enum packageName = (parent.length ? parent ~ '.' : "") ~ T.stringof[8 .. $]; + else static if (parent) + enum packageName = parent; + else + static assert(false, T.stringof ~ " has no parent"); +} + +/// +@safe unittest +{ + import std.traits; + static assert(packageName!packageName == "std"); +} + +@safe unittest +{ + import std.array; + + // Commented out because of dmd @@@BUG8922@@@ + // static assert(packageName!std == "std"); // this package (currently: "std.std") + static assert(packageName!(std.traits) == "std"); // this module + static assert(packageName!packageName == "std"); // symbol in this module + static assert(packageName!(std.array) == "std"); // other module from same package + + import core.sync.barrier; // local import + static assert(packageName!core == "core"); + static assert(packageName!(core.sync) == "core.sync"); + static assert(packageName!Barrier == "core.sync"); + + struct X12287(T) { T i; } + static assert(packageName!(X12287!int.i) == "std"); +} + +version (none) version (unittest) //Please uncomment me when changing packageName to test global imports +{ + import core.sync.barrier; // global import + static assert(packageName!core == "core"); + static assert(packageName!(core.sync) == "core.sync"); + static assert(packageName!Barrier == "core.sync"); +} + +/** + * Get the module name (including package) for the given symbol. + */ +template moduleName(alias T) +{ + import std.algorithm.searching : startsWith; + + static assert(!T.stringof.startsWith("package "), "cannot get the module name for a package"); + + static if (T.stringof.startsWith("module ")) + { + static if (__traits(compiles, packageName!T)) + enum packagePrefix = packageName!T ~ '.'; + else + enum packagePrefix = ""; + + enum moduleName = packagePrefix ~ T.stringof[7..$]; + } + else + alias moduleName = moduleName!(parentOf!T); // If you use enum, it will cause compiler ICE +} + +/// +@safe unittest +{ + import std.traits; + static assert(moduleName!moduleName == "std.traits"); +} + +@safe unittest +{ + import std.array; + + static assert(!__traits(compiles, moduleName!std)); + static assert(moduleName!(std.traits) == "std.traits"); // this module + static assert(moduleName!moduleName == "std.traits"); // symbol in this module + static assert(moduleName!(std.array) == "std.array"); // other module + static assert(moduleName!(std.array.array) == "std.array"); // symbol in other module + + import core.sync.barrier; // local import + static assert(!__traits(compiles, moduleName!(core.sync))); + static assert(moduleName!(core.sync.barrier) == "core.sync.barrier"); + static assert(moduleName!Barrier == "core.sync.barrier"); + + struct X12287(T) { T i; } + static assert(moduleName!(X12287!int.i) == "std.traits"); +} + +version (none) version (unittest) //Please uncomment me when changing moduleName to test global imports +{ + import core.sync.barrier; // global import + static assert(!__traits(compiles, moduleName!(core.sync))); + static assert(moduleName!(core.sync.barrier) == "core.sync.barrier"); + static assert(moduleName!Barrier == "core.sync.barrier"); +} + +/*** + * Get the fully qualified name of a type or a symbol. Can act as an intelligent type/symbol to string converter. + +Example: +----------------- +module myModule; +struct MyStruct {} +static assert(fullyQualifiedName!(const MyStruct[]) == "const(myModule.MyStruct[])"); +----------------- +*/ +template fullyQualifiedName(T...) + if (T.length == 1) +{ + + static if (is(T)) + enum fullyQualifiedName = fqnType!(T[0], false, false, false, false); + else + enum fullyQualifiedName = fqnSym!(T[0]); +} + +/// +@safe unittest +{ + static assert(fullyQualifiedName!fullyQualifiedName == "std.traits.fullyQualifiedName"); +} + +version (unittest) +{ + // Used for both fqnType and fqnSym unittests + private struct QualifiedNameTests + { + struct Inner + { + } + + ref const(Inner[string]) func( ref Inner var1, lazy scope string var2 ); + ref const(Inner[string]) retfunc( return ref Inner var1 ); + Inner inoutFunc(inout Inner) inout; + shared(const(Inner[string])[]) data; + const Inner delegate(double, string) @safe nothrow deleg; + inout(int) delegate(inout int) inout inoutDeleg; + Inner function(out double, string) funcPtr; + extern(C) Inner function(double, string) cFuncPtr; + + extern(C) void cVarArg(int, ...); + void dVarArg(...); + void dVarArg2(int, ...); + void typesafeVarArg(int[] ...); + + Inner[] array; + Inner[16] sarray; + Inner[Inner] aarray; + const(Inner[const(Inner)]) qualAarray; + + shared(immutable(Inner) delegate(ref double, scope string) const shared @trusted nothrow) attrDeleg; + + struct Data(T) { int x; } + void tfunc(T...)(T args) {} + + template Inst(alias A) { int x; } + + class Test12309(T, int x, string s) {} + } + + private enum QualifiedEnum + { + a = 42 + } +} + +private template fqnSym(alias T : X!A, alias X, A...) +{ + template fqnTuple(T...) + { + static if (T.length == 0) + enum fqnTuple = ""; + else static if (T.length == 1) + { + static if (isExpressionTuple!T) + enum fqnTuple = T[0].stringof; + else + enum fqnTuple = fullyQualifiedName!(T[0]); + } + else + enum fqnTuple = fqnTuple!(T[0]) ~ ", " ~ fqnTuple!(T[1 .. $]); + } + + enum fqnSym = + fqnSym!(__traits(parent, X)) ~ + '.' ~ __traits(identifier, X) ~ "!(" ~ fqnTuple!A ~ ")"; +} + +private template fqnSym(alias T) +{ + static if (__traits(compiles, __traits(parent, T)) && !__traits(isSame, T, __traits(parent, T))) + enum parentPrefix = fqnSym!(__traits(parent, T)) ~ "."; + else + enum parentPrefix = null; + + static string adjustIdent(string s) + { + import std.algorithm.searching : findSplit, skipOver; + + if (s.skipOver("package ") || s.skipOver("module ")) + return s; + return s.findSplit("(")[0]; + } + enum fqnSym = parentPrefix ~ adjustIdent(__traits(identifier, T)); +} + +@safe unittest +{ + alias fqn = fullyQualifiedName; + + // Make sure those 2 are the same + static assert(fqnSym!fqn == fqn!fqn); + + static assert(fqn!fqn == "std.traits.fullyQualifiedName"); + + alias qnTests = QualifiedNameTests; + enum prefix = "std.traits.QualifiedNameTests."; + static assert(fqn!(qnTests.Inner) == prefix ~ "Inner"); + static assert(fqn!(qnTests.func) == prefix ~ "func"); + static assert(fqn!(qnTests.Data!int) == prefix ~ "Data!(int)"); + static assert(fqn!(qnTests.Data!int.x) == prefix ~ "Data!(int).x"); + static assert(fqn!(qnTests.tfunc!(int[])) == prefix ~ "tfunc!(int[])"); + static assert(fqn!(qnTests.Inst!(Object)) == prefix ~ "Inst!(object.Object)"); + static assert(fqn!(qnTests.Inst!(Object).x) == prefix ~ "Inst!(object.Object).x"); + + static assert(fqn!(qnTests.Test12309!(int, 10, "str")) + == prefix ~ "Test12309!(int, 10, \"str\")"); + + import core.sync.barrier; + static assert(fqn!Barrier == "core.sync.barrier.Barrier"); +} + +@safe unittest +{ + struct TemplatedStruct() + { + enum foo = 0; + } + alias TemplatedStructAlias = TemplatedStruct; + assert("TemplatedStruct.foo" == fullyQualifiedName!(TemplatedStructAlias!().foo)); +} + +private template fqnType(T, + bool alreadyConst, bool alreadyImmutable, bool alreadyShared, bool alreadyInout) +{ + import std.format : format; + + // Convenience tags + enum { + _const = 0, + _immutable = 1, + _shared = 2, + _inout = 3 + } + + alias qualifiers = AliasSeq!(is(T == const), is(T == immutable), is(T == shared), is(T == inout)); + alias noQualifiers = AliasSeq!(false, false, false, false); + + string storageClassesString(uint psc)() @property + { + alias PSC = ParameterStorageClass; + + return format("%s%s%s%s%s", + psc & PSC.scope_ ? "scope " : "", + psc & PSC.return_ ? "return " : "", + psc & PSC.out_ ? "out " : "", + psc & PSC.ref_ ? "ref " : "", + psc & PSC.lazy_ ? "lazy " : "" + ); + } + + string parametersTypeString(T)() @property + { + alias parameters = Parameters!(T); + alias parameterStC = ParameterStorageClassTuple!(T); + + enum variadic = variadicFunctionStyle!T; + static if (variadic == Variadic.no) + enum variadicStr = ""; + else static if (variadic == Variadic.c) + enum variadicStr = ", ..."; + else static if (variadic == Variadic.d) + enum variadicStr = parameters.length ? ", ..." : "..."; + else static if (variadic == Variadic.typesafe) + enum variadicStr = " ..."; + else + static assert(0, "New variadic style has been added, please update fullyQualifiedName implementation"); + + static if (parameters.length) + { + import std.algorithm.iteration : map; + import std.array : join; + import std.meta : staticMap; + import std.range : zip; + + string result = join( + map!(a => format("%s%s", a[0], a[1]))( + zip([staticMap!(storageClassesString, parameterStC)], + [staticMap!(fullyQualifiedName, parameters)]) + ), + ", " + ); + + return result ~= variadicStr; + } + else + return variadicStr; + } + + string linkageString(T)() @property + { + enum linkage = functionLinkage!T; + + if (linkage != "D") + return format("extern(%s) ", linkage); + else + return ""; + } + + string functionAttributeString(T)() @property + { + alias FA = FunctionAttribute; + enum attrs = functionAttributes!T; + + static if (attrs == FA.none) + return ""; + else + return format("%s%s%s%s%s%s%s%s", + attrs & FA.pure_ ? " pure" : "", + attrs & FA.nothrow_ ? " nothrow" : "", + attrs & FA.ref_ ? " ref" : "", + attrs & FA.property ? " @property" : "", + attrs & FA.trusted ? " @trusted" : "", + attrs & FA.safe ? " @safe" : "", + attrs & FA.nogc ? " @nogc" : "", + attrs & FA.return_ ? " return" : "" + ); + } + + string addQualifiers(string typeString, + bool addConst, bool addImmutable, bool addShared, bool addInout) + { + auto result = typeString; + if (addShared) + { + result = format("shared(%s)", result); + } + if (addConst || addImmutable || addInout) + { + result = format("%s(%s)", + addConst ? "const" : + addImmutable ? "immutable" : "inout", + result + ); + } + return result; + } + + // Convenience template to avoid copy-paste + template chain(string current) + { + enum chain = addQualifiers(current, + qualifiers[_const] && !alreadyConst, + qualifiers[_immutable] && !alreadyImmutable, + qualifiers[_shared] && !alreadyShared, + qualifiers[_inout] && !alreadyInout); + } + + static if (is(T == string)) + { + enum fqnType = "string"; + } + else static if (is(T == wstring)) + { + enum fqnType = "wstring"; + } + else static if (is(T == dstring)) + { + enum fqnType = "dstring"; + } + else static if (isBasicType!T && !is(T == enum)) + { + enum fqnType = chain!((Unqual!T).stringof); + } + else static if (isAggregateType!T || is(T == enum)) + { + enum fqnType = chain!(fqnSym!T); + } + else static if (isStaticArray!T) + { + enum fqnType = chain!( + format("%s[%s]", fqnType!(typeof(T.init[0]), qualifiers), T.length) + ); + } + else static if (isArray!T) + { + enum fqnType = chain!( + format("%s[]", fqnType!(typeof(T.init[0]), qualifiers)) + ); + } + else static if (isAssociativeArray!T) + { + enum fqnType = chain!( + format("%s[%s]", fqnType!(ValueType!T, qualifiers), fqnType!(KeyType!T, noQualifiers)) + ); + } + else static if (isSomeFunction!T) + { + static if (is(T F == delegate)) + { + enum qualifierString = format("%s%s", + is(F == shared) ? " shared" : "", + is(F == inout) ? " inout" : + is(F == immutable) ? " immutable" : + is(F == const) ? " const" : "" + ); + enum formatStr = "%s%s delegate(%s)%s%s"; + enum fqnType = chain!( + format(formatStr, linkageString!T, fqnType!(ReturnType!T, noQualifiers), + parametersTypeString!(T), functionAttributeString!T, qualifierString) + ); + } + else + { + static if (isFunctionPointer!T) + enum formatStr = "%s%s function(%s)%s"; + else + enum formatStr = "%s%s(%s)%s"; + + enum fqnType = chain!( + format(formatStr, linkageString!T, fqnType!(ReturnType!T, noQualifiers), + parametersTypeString!(T), functionAttributeString!T) + ); + } + } + else static if (isPointer!T) + { + enum fqnType = chain!( + format("%s*", fqnType!(PointerTarget!T, qualifiers)) + ); + } + else static if (is(T : __vector(V[N]), V, size_t N)) + { + enum fqnType = chain!( + format("__vector(%s[%s])", fqnType!(V, qualifiers), N) + ); + } + else + // In case something is forgotten + static assert(0, "Unrecognized type " ~ T.stringof ~ ", can't convert to fully qualified string"); +} + +@safe unittest +{ + import std.format : format; + alias fqn = fullyQualifiedName; + + // Verify those 2 are the same for simple case + alias Ambiguous = const(QualifiedNameTests.Inner); + static assert(fqn!Ambiguous == fqnType!(Ambiguous, false, false, false, false)); + + // Main tests + enum inner_name = "std.traits.QualifiedNameTests.Inner"; + with (QualifiedNameTests) + { + // Special cases + static assert(fqn!(string) == "string"); + static assert(fqn!(wstring) == "wstring"); + static assert(fqn!(dstring) == "dstring"); + static assert(fqn!(void) == "void"); + static assert(fqn!(const(void)) == "const(void)"); + static assert(fqn!(shared(void)) == "shared(void)"); + static assert(fqn!(shared const(void)) == "const(shared(void))"); + static assert(fqn!(shared inout(void)) == "inout(shared(void))"); + static assert(fqn!(shared inout const(void)) == "const(shared(void))"); + static assert(fqn!(inout(void)) == "inout(void)"); + static assert(fqn!(inout const(void)) == "const(void)"); + static assert(fqn!(immutable(void)) == "immutable(void)"); + + // Basic qualified name + static assert(fqn!(Inner) == inner_name); + static assert(fqn!(QualifiedEnum) == "std.traits.QualifiedEnum"); // type + static assert(fqn!(QualifiedEnum.a) == "std.traits.QualifiedEnum.a"); // symbol + + // Array types + static assert(fqn!(typeof(array)) == format("%s[]", inner_name)); + static assert(fqn!(typeof(sarray)) == format("%s[16]", inner_name)); + static assert(fqn!(typeof(aarray)) == format("%s[%s]", inner_name, inner_name)); + + // qualified key for AA + static assert(fqn!(typeof(qualAarray)) == format("const(%s[const(%s)])", inner_name, inner_name)); + + // Qualified composed data types + static assert(fqn!(typeof(data)) == format("shared(const(%s[string])[])", inner_name)); + + // Function types + function attributes + static assert(fqn!(typeof(func)) == format("const(%s[string])(ref %s, scope lazy string) ref", + inner_name, inner_name)); + static assert(fqn!(typeof(retfunc)) == format("const(%s[string])(return %s) ref", inner_name, inner_name)); + static assert(fqn!(typeof(inoutFunc)) == format("inout(%s(inout(%s)))", inner_name, inner_name)); + static assert(fqn!(typeof(deleg)) == format("const(%s delegate(double, string) nothrow @safe)", inner_name)); + static assert(fqn!(typeof(inoutDeleg)) == "inout(int) delegate(inout(int)) inout"); + static assert(fqn!(typeof(funcPtr)) == format("%s function(out double, string)", inner_name)); + static assert(fqn!(typeof(cFuncPtr)) == format("extern(C) %s function(double, string)", inner_name)); + + // Delegate type with qualified function type + static assert(fqn!(typeof(attrDeleg)) == format("shared(immutable(%s) "~ + "delegate(ref double, scope string) nothrow @trusted shared const)", inner_name)); + + // Variable argument function types + static assert(fqn!(typeof(cVarArg)) == "extern(C) void(int, ...)"); + static assert(fqn!(typeof(dVarArg)) == "void(...)"); + static assert(fqn!(typeof(dVarArg2)) == "void(int, ...)"); + static assert(fqn!(typeof(typesafeVarArg)) == "void(int[] ...)"); + + // SIMD vector + static if (is(__vector(float[4]))) + { + static assert(fqn!(__vector(float[4])) == "__vector(float[4])"); + } + } +} + +/*** + * Get the type of the return value from a function, + * a pointer to function, a delegate, a struct + * with an opCall, a pointer to a struct with an opCall, + * or a class with an $(D opCall). Please note that $(D_KEYWORD ref) + * is not part of a type, but the attribute of the function + * (see template $(LREF functionAttributes)). + */ +template ReturnType(func...) + if (func.length == 1 && isCallable!func) +{ + static if (is(FunctionTypeOf!func R == return)) + alias ReturnType = R; + else + static assert(0, "argument has no return type"); +} + +/// +@safe unittest +{ + int foo(); + ReturnType!foo x; // x is declared as int +} + +@safe unittest +{ + struct G + { + int opCall (int i) { return 1;} + } + + alias ShouldBeInt = ReturnType!G; + static assert(is(ShouldBeInt == int)); + + G g; + static assert(is(ReturnType!g == int)); + + G* p; + alias pg = ReturnType!p; + static assert(is(pg == int)); + + class C + { + int opCall (int i) { return 1;} + } + + static assert(is(ReturnType!C == int)); + + C c; + static assert(is(ReturnType!c == int)); + + class Test + { + int prop() @property { return 0; } + } + alias R_Test_prop = ReturnType!(Test.prop); + static assert(is(R_Test_prop == int)); + + alias R_dglit = ReturnType!((int a) { return a; }); + static assert(is(R_dglit == int)); +} + +/*** +Get, as a tuple, the types of the parameters to a function, a pointer +to function, a delegate, a struct with an $(D opCall), a pointer to a +struct with an $(D opCall), or a class with an $(D opCall). +*/ +template Parameters(func...) + if (func.length == 1 && isCallable!func) +{ + static if (is(FunctionTypeOf!func P == function)) + alias Parameters = P; + else + static assert(0, "argument has no parameters"); +} + +/// +@safe unittest +{ + int foo(int, long); + void bar(Parameters!foo); // declares void bar(int, long); + void abc(Parameters!foo[1]); // declares void abc(long); +} + +/** + * Alternate name for $(LREF Parameters), kept for legacy compatibility. + */ +alias ParameterTypeTuple = Parameters; + +@safe unittest +{ + int foo(int i, bool b) { return 0; } + static assert(is(ParameterTypeTuple!foo == AliasSeq!(int, bool))); + static assert(is(ParameterTypeTuple!(typeof(&foo)) == AliasSeq!(int, bool))); + + struct S { real opCall(real r, int i) { return 0.0; } } + S s; + static assert(is(ParameterTypeTuple!S == AliasSeq!(real, int))); + static assert(is(ParameterTypeTuple!(S*) == AliasSeq!(real, int))); + static assert(is(ParameterTypeTuple!s == AliasSeq!(real, int))); + + class Test + { + int prop() @property { return 0; } + } + alias P_Test_prop = ParameterTypeTuple!(Test.prop); + static assert(P_Test_prop.length == 0); + + alias P_dglit = ParameterTypeTuple!((int a){}); + static assert(P_dglit.length == 1); + static assert(is(P_dglit[0] == int)); +} + +/** +Returns the number of arguments of function $(D func). +arity is undefined for variadic functions. +*/ +template arity(alias func) + if ( isCallable!func && variadicFunctionStyle!func == Variadic.no ) +{ + enum size_t arity = Parameters!func.length; +} + +/// +@safe unittest +{ + void foo(){} + static assert(arity!foo == 0); + void bar(uint){} + static assert(arity!bar == 1); + void variadicFoo(uint...){} + static assert(!__traits(compiles, arity!variadicFoo)); +} + +/** +Get tuple, one per function parameter, of the storage classes of the parameters. +Params: + func = function symbol or type of function, delegate, or pointer to function +Returns: + A tuple of ParameterStorageClass bits + */ +enum ParameterStorageClass : uint +{ + /** + * These flags can be bitwise OR-ed together to represent complex storage + * class. + */ + none = 0, + scope_ = 1, /// ditto + out_ = 2, /// ditto + ref_ = 4, /// ditto + lazy_ = 8, /// ditto + return_ = 0x10, /// ditto +} + +/// ditto +template ParameterStorageClassTuple(func...) + if (func.length == 1 && isCallable!func) +{ + alias Func = FunctionTypeOf!func; + + static if (is(Func PT == __parameters)) + { + template StorageClass(size_t i) + { + static if (i < PT.length) + { + alias StorageClass = AliasSeq!( + extractParameterStorageClassFlags!(__traits(getParameterStorageClasses, Func, i)), + StorageClass!(i + 1)); + } + else + alias StorageClass = AliasSeq!(); + } + alias ParameterStorageClassTuple = StorageClass!0; + } + else + { + static assert(0, func[0].stringof ~ " is not a function"); + alias ParameterStorageClassTuple = AliasSeq!(); + } +} + +/// +@safe unittest +{ + alias STC = ParameterStorageClass; // shorten the enum name + + void func(ref int ctx, out real result, real param) + { + } + alias pstc = ParameterStorageClassTuple!func; + static assert(pstc.length == 3); // three parameters + static assert(pstc[0] == STC.ref_); + static assert(pstc[1] == STC.out_); + static assert(pstc[2] == STC.none); +} + +/***************** + * Convert string tuple Attribs to ParameterStorageClass bits + * Params: + * Attribs = string tuple + * Returns: + * ParameterStorageClass bits + */ +template extractParameterStorageClassFlags(Attribs...) +{ + enum ParameterStorageClass extractParameterStorageClassFlags = () + { + auto result = ParameterStorageClass.none; + static if (Attribs.length > 0) + { + foreach (attrib; [Attribs]) + { + final switch (attrib) with (ParameterStorageClass) + { + case "scope": result |= scope_; break; + case "out": result |= out_; break; + case "ref": result |= ref_; break; + case "lazy": result |= lazy_; break; + case "return": result |= return_; break; + } + } + /* Mimic behavor of original version of ParameterStorageClassTuple() + * to avoid breaking existing code. + */ + if (result == (ParameterStorageClass.ref_ | ParameterStorageClass.return_)) + result = ParameterStorageClass.return_; + } + return result; + }(); +} + +@safe unittest +{ + alias STC = ParameterStorageClass; + + void noparam() {} + static assert(ParameterStorageClassTuple!noparam.length == 0); + + ref int test(scope int*, ref int, out int, lazy int, int, return ref int i) { return i; } + alias test_pstc = ParameterStorageClassTuple!test; + static assert(test_pstc.length == 6); + static assert(test_pstc[0] == STC.scope_); + static assert(test_pstc[1] == STC.ref_); + static assert(test_pstc[2] == STC.out_); + static assert(test_pstc[3] == STC.lazy_); + static assert(test_pstc[4] == STC.none); + static assert(test_pstc[5] == STC.return_); + + interface Test + { + void test_const(int) const; + void test_sharedconst(int) shared const; + } + Test testi; + + alias test_const_pstc = ParameterStorageClassTuple!(Test.test_const); + static assert(test_const_pstc.length == 1); + static assert(test_const_pstc[0] == STC.none); + + alias test_sharedconst_pstc = ParameterStorageClassTuple!(testi.test_sharedconst); + static assert(test_sharedconst_pstc.length == 1); + static assert(test_sharedconst_pstc[0] == STC.none); + + alias dglit_pstc = ParameterStorageClassTuple!((ref int a) {}); + static assert(dglit_pstc.length == 1); + static assert(dglit_pstc[0] == STC.ref_); + + // Bugzilla 9317 + static inout(int) func(inout int param) { return param; } + static assert(ParameterStorageClassTuple!(typeof(func))[0] == STC.none); +} + +@safe unittest +{ + // Bugzilla 14253 + static struct Foo { + ref Foo opAssign(ref Foo rhs) return { return this; } + } + + alias tup = ParameterStorageClassTuple!(__traits(getOverloads, Foo, "opAssign")[0]); +} + + +/** +Get, as a tuple, the identifiers of the parameters to a function symbol. + */ +template ParameterIdentifierTuple(func...) + if (func.length == 1 && isCallable!func) +{ + static if (is(FunctionTypeOf!func PT == __parameters)) + { + template Get(size_t i) + { + static if (!isFunctionPointer!func && !isDelegate!func + // Unnamed parameters yield CT error. + && is(typeof(__traits(identifier, PT[i .. i+1])))) + { + enum Get = __traits(identifier, PT[i .. i+1]); + } + else + { + enum Get = ""; + } + } + } + else + { + static assert(0, func[0].stringof ~ "is not a function"); + + // Define dummy entities to avoid pointless errors + template Get(size_t i) { enum Get = ""; } + alias PT = AliasSeq!(); + } + + template Impl(size_t i = 0) + { + static if (i == PT.length) + alias Impl = AliasSeq!(); + else + alias Impl = AliasSeq!(Get!i, Impl!(i+1)); + } + + alias ParameterIdentifierTuple = Impl!(); +} + +/// +@safe unittest +{ + int foo(int num, string name, int); + static assert([ParameterIdentifierTuple!foo] == ["num", "name", ""]); +} + +@safe unittest +{ + alias PIT = ParameterIdentifierTuple; + + void bar(int num, string name, int[] array){} + static assert([PIT!bar] == ["num", "name", "array"]); + + // might be changed in the future? + void function(int num, string name) fp; + static assert([PIT!fp] == ["", ""]); + + // might be changed in the future? + void delegate(int num, string name, int[long] aa) dg; + static assert([PIT!dg] == ["", "", ""]); + + interface Test + { + @property string getter(); + @property void setter(int a); + Test method(int a, long b, string c); + } + static assert([PIT!(Test.getter)] == []); + static assert([PIT!(Test.setter)] == ["a"]); + static assert([PIT!(Test.method)] == ["a", "b", "c"]); + +/+ + // depends on internal + void baw(int, string, int[]){} + static assert([PIT!baw] == ["_param_0", "_param_1", "_param_2"]); + + // depends on internal + void baz(AliasSeq!(int, string, int[]) args){} + static assert([PIT!baz] == ["_param_0", "_param_1", "_param_2"]); ++/ +} + + +/** +Get, as a tuple, the default value of the parameters to a function symbol. +If a parameter doesn't have the default value, $(D void) is returned instead. + */ +template ParameterDefaults(func...) + if (func.length == 1 && isCallable!func) +{ + alias param_names = ParameterIdentifierTuple!func; + static if (is(FunctionTypeOf!(func[0]) PT == __parameters)) + { + template Get(size_t i) + { + // `PT[i .. i+1]` declares a parameter with an arbitrary name. + // To avoid a name clash, generate local names that are distinct + // from the parameter name, and mix them in. + enum name = param_names[i]; + enum args = "args" ~ (name == "args" ? "_" : ""); + enum val = "val" ~ (name == "val" ? "_" : ""); + enum ptr = "ptr" ~ (name == "ptr" ? "_" : ""); + mixin(" + // workaround scope escape check, see + // https://issues.dlang.org/show_bug.cgi?id=16582 + // should use return scope once available + enum get = (PT[i .. i+1] " ~ args ~ ") @trusted + { + // If the parameter is lazy, we force it to be evaluated + // like this. + auto " ~ val ~ " = " ~ args ~ "[0]; + auto " ~ ptr ~ " = &" ~ val ~ "; + // workaround Bugzilla 16582 + return *" ~ ptr ~ "; + }; + "); + static if (is(typeof(get()))) + enum Get = get(); + else + alias Get = void; + // If default arg doesn't exist, returns void instead. + } + } + else + { + static assert(0, func[0].stringof ~ "is not a function"); + + // Define dummy entities to avoid pointless errors + template Get(size_t i) { enum Get = ""; } + alias PT = AliasSeq!(); + } + + template Impl(size_t i = 0) + { + static if (i == PT.length) + alias Impl = AliasSeq!(); + else + alias Impl = AliasSeq!(Get!i, Impl!(i+1)); + } + + alias ParameterDefaults = Impl!(); +} + +/// +@safe unittest +{ + int foo(int num, string name = "hello", int[] = [1,2,3], lazy int x = 0); + static assert(is(ParameterDefaults!foo[0] == void)); + static assert( ParameterDefaults!foo[1] == "hello"); + static assert( ParameterDefaults!foo[2] == [1,2,3]); + static assert( ParameterDefaults!foo[3] == 0); +} + +@safe unittest // issue 17192 +{ + static void func(int i, int PT, int __pd_value, int __pd_val, int __args, + int name, int args, int val, int ptr, int args_, int val_, int ptr_) + { + } + alias Voids = ParameterDefaults!func; + static assert(Voids.length == 12); + foreach (V; Voids) static assert(is(V == void)); +} + +/** + * Alternate name for $(LREF ParameterDefaults), kept for legacy compatibility. + */ +alias ParameterDefaultValueTuple = ParameterDefaults; + +@safe unittest +{ + alias PDVT = ParameterDefaultValueTuple; + + void bar(int n = 1, string s = "hello"){} + static assert(PDVT!bar.length == 2); + static assert(PDVT!bar[0] == 1); + static assert(PDVT!bar[1] == "hello"); + static assert(is(typeof(PDVT!bar) == typeof(AliasSeq!(1, "hello")))); + + void baz(int x, int n = 1, string s = "hello"){} + static assert(PDVT!baz.length == 3); + static assert(is(PDVT!baz[0] == void)); + static assert( PDVT!baz[1] == 1); + static assert( PDVT!baz[2] == "hello"); + static assert(is(typeof(PDVT!baz) == typeof(AliasSeq!(void, 1, "hello")))); + + // bug 10800 - property functions return empty string + @property void foo(int x = 3) { } + static assert(PDVT!foo.length == 1); + static assert(PDVT!foo[0] == 3); + static assert(is(typeof(PDVT!foo) == typeof(AliasSeq!(3)))); + + struct Colour + { + ubyte a,r,g,b; + + static immutable Colour white = Colour(255,255,255,255); + } + void bug8106(Colour c = Colour.white) {} + //pragma(msg, PDVT!bug8106); + static assert(PDVT!bug8106[0] == Colour.white); + void bug16582(scope int* val = null) {} + static assert(PDVT!bug16582[0] is null); +} + + +/** +Returns the FunctionAttribute mask for function $(D func). + +See_Also: + $(LREF hasFunctionAttributes) + */ +enum FunctionAttribute : uint +{ + /** + * These flags can be bitwise OR-ed together to represent a complex attribute. + */ + none = 0, + pure_ = 1 << 0, /// ditto + nothrow_ = 1 << 1, /// ditto + ref_ = 1 << 2, /// ditto + property = 1 << 3, /// ditto + trusted = 1 << 4, /// ditto + safe = 1 << 5, /// ditto + nogc = 1 << 6, /// ditto + system = 1 << 7, /// ditto + const_ = 1 << 8, /// ditto + immutable_ = 1 << 9, /// ditto + inout_ = 1 << 10, /// ditto + shared_ = 1 << 11, /// ditto + return_ = 1 << 12, /// ditto + scope_ = 1 << 13, /// ditto +} + +/// ditto +template functionAttributes(func...) + if (func.length == 1 && isCallable!func) +{ + // @bug: workaround for opCall + alias FuncSym = Select!(is(typeof(__traits(getFunctionAttributes, func))), + func, Unqual!(FunctionTypeOf!func)); + + enum FunctionAttribute functionAttributes = + extractAttribFlags!(__traits(getFunctionAttributes, FuncSym))(); +} + +/// +@safe unittest +{ + import std.traits : functionAttributes, FunctionAttribute; + + alias FA = FunctionAttribute; // shorten the enum name + + real func(real x) pure nothrow @safe + { + return x; + } + static assert(functionAttributes!func & FA.pure_); + static assert(functionAttributes!func & FA.safe); + static assert(!(functionAttributes!func & FA.trusted)); // not @trusted +} + +@system unittest +{ + alias FA = FunctionAttribute; + + struct S + { + int noF() { return 0; } + int constF() const { return 0; } + int immutableF() immutable { return 0; } + int inoutF() inout { return 0; } + int sharedF() shared { return 0; } + + int x; + ref int refF() return { return x; } + int propertyF() @property { return 0; } + int nothrowF() nothrow { return 0; } + int nogcF() @nogc { return 0; } + + int systemF() @system { return 0; } + int trustedF() @trusted { return 0; } + int safeF() @safe { return 0; } + + int pureF() pure { return 0; } + } + + static assert(functionAttributes!(S.noF) == FA.system); + static assert(functionAttributes!(typeof(S.noF)) == FA.system); + + static assert(functionAttributes!(S.constF) == (FA.const_ | FA.system)); + static assert(functionAttributes!(typeof(S.constF)) == (FA.const_ | FA.system)); + + static assert(functionAttributes!(S.immutableF) == (FA.immutable_ | FA.system)); + static assert(functionAttributes!(typeof(S.immutableF)) == (FA.immutable_ | FA.system)); + + static assert(functionAttributes!(S.inoutF) == (FA.inout_ | FA.system)); + static assert(functionAttributes!(typeof(S.inoutF)) == (FA.inout_ | FA.system)); + + static assert(functionAttributes!(S.sharedF) == (FA.shared_ | FA.system)); + static assert(functionAttributes!(typeof(S.sharedF)) == (FA.shared_ | FA.system)); + + static assert(functionAttributes!(S.refF) == (FA.ref_ | FA.system | FA.return_)); + static assert(functionAttributes!(typeof(S.refF)) == (FA.ref_ | FA.system | FA.return_)); + + static assert(functionAttributes!(S.propertyF) == (FA.property | FA.system)); + static assert(functionAttributes!(typeof(&S.propertyF)) == (FA.property | FA.system)); + + static assert(functionAttributes!(S.nothrowF) == (FA.nothrow_ | FA.system)); + static assert(functionAttributes!(typeof(S.nothrowF)) == (FA.nothrow_ | FA.system)); + + static assert(functionAttributes!(S.nogcF) == (FA.nogc | FA.system)); + static assert(functionAttributes!(typeof(S.nogcF)) == (FA.nogc | FA.system)); + + static assert(functionAttributes!(S.systemF) == FA.system); + static assert(functionAttributes!(typeof(S.systemF)) == FA.system); + + static assert(functionAttributes!(S.trustedF) == FA.trusted); + static assert(functionAttributes!(typeof(S.trustedF)) == FA.trusted); + + static assert(functionAttributes!(S.safeF) == FA.safe); + static assert(functionAttributes!(typeof(S.safeF)) == FA.safe); + + static assert(functionAttributes!(S.pureF) == (FA.pure_ | FA.system)); + static assert(functionAttributes!(typeof(S.pureF)) == (FA.pure_ | FA.system)); + + int pure_nothrow() nothrow pure; + void safe_nothrow() @safe nothrow; + static ref int static_ref_property() @property; + ref int ref_property() @property; + + static assert(functionAttributes!(pure_nothrow) == (FA.pure_ | FA.nothrow_ | FA.system)); + static assert(functionAttributes!(typeof(pure_nothrow)) == (FA.pure_ | FA.nothrow_ | FA.system)); + + static assert(functionAttributes!(safe_nothrow) == (FA.safe | FA.nothrow_)); + static assert(functionAttributes!(typeof(safe_nothrow)) == (FA.safe | FA.nothrow_)); + + static assert(functionAttributes!(static_ref_property) == (FA.property | FA.ref_ | FA.system)); + static assert(functionAttributes!(typeof(&static_ref_property)) == (FA.property | FA.ref_ | FA.system)); + + static assert(functionAttributes!(ref_property) == (FA.property | FA.ref_ | FA.system)); + static assert(functionAttributes!(typeof(&ref_property)) == (FA.property | FA.ref_ | FA.system)); + + struct S2 + { + int pure_const() const pure { return 0; } + int pure_sharedconst() const shared pure { return 0; } + } + + static assert(functionAttributes!(S2.pure_const) == (FA.const_ | FA.pure_ | FA.system)); + static assert(functionAttributes!(typeof(S2.pure_const)) == (FA.const_ | FA.pure_ | FA.system)); + + static assert(functionAttributes!(S2.pure_sharedconst) == (FA.const_ | FA.shared_ | FA.pure_ | FA.system)); + static assert(functionAttributes!(typeof(S2.pure_sharedconst)) == (FA.const_ | FA.shared_ | FA.pure_ | FA.system)); + + static assert(functionAttributes!((int a) { }) == (FA.pure_ | FA.nothrow_ | FA.nogc | FA.safe)); + static assert(functionAttributes!(typeof((int a) { })) == (FA.pure_ | FA.nothrow_ | FA.nogc | FA.safe)); + + auto safeDel = delegate() @safe { }; + static assert(functionAttributes!(safeDel) == (FA.pure_ | FA.nothrow_ | FA.nogc | FA.safe)); + static assert(functionAttributes!(typeof(safeDel)) == (FA.pure_ | FA.nothrow_ | FA.nogc | FA.safe)); + + auto trustedDel = delegate() @trusted { }; + static assert(functionAttributes!(trustedDel) == (FA.pure_ | FA.nothrow_ | FA.nogc | FA.trusted)); + static assert(functionAttributes!(typeof(trustedDel)) == (FA.pure_ | FA.nothrow_ | FA.nogc | FA.trusted)); + + auto systemDel = delegate() @system { }; + static assert(functionAttributes!(systemDel) == (FA.pure_ | FA.nothrow_ | FA.nogc | FA.system)); + static assert(functionAttributes!(typeof(systemDel)) == (FA.pure_ | FA.nothrow_ | FA.nogc | FA.system)); +} + +private FunctionAttribute extractAttribFlags(Attribs...)() +{ + auto res = FunctionAttribute.none; + + foreach (attrib; Attribs) + { + switch (attrib) with (FunctionAttribute) + { + case "pure": res |= pure_; break; + case "nothrow": res |= nothrow_; break; + case "ref": res |= ref_; break; + case "@property": res |= property; break; + case "@trusted": res |= trusted; break; + case "@safe": res |= safe; break; + case "@nogc": res |= nogc; break; + case "@system": res |= system; break; + case "const": res |= const_; break; + case "immutable": res |= immutable_; break; + case "inout": res |= inout_; break; + case "shared": res |= shared_; break; + case "return": res |= return_; break; + case "scope": res |= scope_; break; + default: assert(0, attrib); + } + } + + return res; +} + +/** +Checks whether a function has the given attributes attached. + +Params: + args = Function to check, followed by a + variadic number of function attributes as strings + +Returns: + `true`, if the function has the list of attributes attached and `false` otherwise. + +See_Also: + $(LREF functionAttributes) +*/ +template hasFunctionAttributes(args...) + if (args.length > 0 && isCallable!(args[0]) + && allSatisfy!(isSomeString, typeof(args[1 .. $]))) +{ + enum bool hasFunctionAttributes = { + import std.algorithm.searching : canFind; + import std.range : only; + enum funcAttribs = only(__traits(getFunctionAttributes, args[0])); + foreach (attribute; args[1 .. $]) + { + if (!funcAttribs.canFind(attribute)) + return false; + } + return true; + }(); +} + +/// +@safe unittest +{ + real func(real x) pure nothrow @safe; + static assert(hasFunctionAttributes!(func, "@safe", "pure")); + static assert(!hasFunctionAttributes!(func, "@trusted")); + + // for templates attributes are automatically inferred + bool myFunc(T)(T b) + { + return !b; + } + static assert(hasFunctionAttributes!(myFunc!bool, "@safe", "pure", "@nogc", "nothrow")); + static assert(!hasFunctionAttributes!(myFunc!bool, "shared")); +} + +@system unittest +{ + struct S + { + int noF(); + int constF() const; + int immutableF() immutable; + int inoutF() inout; + int sharedF() shared; + + ref int refF() return; + int propertyF() @property; + int nothrowF() nothrow; + int nogcF() @nogc; + + int systemF() @system; + int trustedF() @trusted; + int safeF() @safe; + + int pureF() pure; + } + + // true if no args passed + static assert(hasFunctionAttributes!(S.noF)); + + static assert(hasFunctionAttributes!(S.noF, "@system")); + static assert(hasFunctionAttributes!(typeof(S.noF), "@system")); + static assert(!hasFunctionAttributes!(S.noF, "@system", "pure")); + + static assert(hasFunctionAttributes!(S.constF, "const", "@system")); + static assert(hasFunctionAttributes!(typeof(S.constF), "const", "@system")); + static assert(!hasFunctionAttributes!(S.constF, "const", "@system", "@nogc")); + + static assert(hasFunctionAttributes!(S.immutableF, "immutable", "@system")); + static assert(hasFunctionAttributes!(typeof(S.immutableF), "immutable", "@system")); + static assert(!hasFunctionAttributes!(S.immutableF, "immutable", "@system", "pure")); + + static assert(hasFunctionAttributes!(S.inoutF, "inout", "@system")); + static assert(hasFunctionAttributes!(typeof(S.inoutF), "inout", "@system")); + static assert(!hasFunctionAttributes!(S.inoutF, "inout", "@system", "pure")); + + static assert(hasFunctionAttributes!(S.sharedF, "shared", "@system")); + static assert(hasFunctionAttributes!(typeof(S.sharedF), "shared", "@system")); + static assert(!hasFunctionAttributes!(S.sharedF, "shared", "@system", "@trusted")); + + static assert(hasFunctionAttributes!(S.refF, "ref", "@system", "return")); + static assert(hasFunctionAttributes!(typeof(S.refF), "ref", "@system", "return")); + static assert(!hasFunctionAttributes!(S.refF, "ref", "@system", "return", "pure")); + + static assert(hasFunctionAttributes!(S.propertyF, "@property", "@system")); + static assert(hasFunctionAttributes!(typeof(&S.propertyF), "@property", "@system")); + static assert(!hasFunctionAttributes!(S.propertyF, "@property", "@system", "ref")); + + static assert(hasFunctionAttributes!(S.nothrowF, "nothrow", "@system")); + static assert(hasFunctionAttributes!(typeof(S.nothrowF), "nothrow", "@system")); + static assert(!hasFunctionAttributes!(S.nothrowF, "nothrow", "@system", "@trusted")); + + static assert(hasFunctionAttributes!(S.nogcF, "@nogc", "@system")); + static assert(hasFunctionAttributes!(typeof(S.nogcF), "@nogc", "@system")); + static assert(!hasFunctionAttributes!(S.nogcF, "@nogc", "@system", "ref")); + + static assert(hasFunctionAttributes!(S.systemF, "@system")); + static assert(hasFunctionAttributes!(typeof(S.systemF), "@system")); + static assert(!hasFunctionAttributes!(S.systemF, "@system", "ref")); + + static assert(hasFunctionAttributes!(S.trustedF, "@trusted")); + static assert(hasFunctionAttributes!(typeof(S.trustedF), "@trusted")); + static assert(!hasFunctionAttributes!(S.trustedF, "@trusted", "@safe")); + + static assert(hasFunctionAttributes!(S.safeF, "@safe")); + static assert(hasFunctionAttributes!(typeof(S.safeF), "@safe")); + static assert(!hasFunctionAttributes!(S.safeF, "@safe", "nothrow")); + + static assert(hasFunctionAttributes!(S.pureF, "pure", "@system")); + static assert(hasFunctionAttributes!(typeof(S.pureF), "pure", "@system")); + static assert(!hasFunctionAttributes!(S.pureF, "pure", "@system", "ref")); + + int pure_nothrow() nothrow pure { return 0; } + void safe_nothrow() @safe nothrow { } + static ref int static_ref_property() @property { return *(new int); } + ref int ref_property() @property { return *(new int); } + + static assert(hasFunctionAttributes!(pure_nothrow, "pure", "nothrow", "@safe")); + static assert(hasFunctionAttributes!(typeof(pure_nothrow), "pure", "nothrow", "@safe")); + static assert(!hasFunctionAttributes!(pure_nothrow, "pure", "nothrow", "@safe", "@trusted")); + + static assert(hasFunctionAttributes!(safe_nothrow, "@safe", "nothrow")); + static assert(hasFunctionAttributes!(typeof(safe_nothrow), "@safe", "nothrow")); + static assert(hasFunctionAttributes!(safe_nothrow, "@safe", "nothrow", "pure")); + static assert(!hasFunctionAttributes!(safe_nothrow, "@safe", "nothrow", "pure", "@trusted")); + + static assert(hasFunctionAttributes!(static_ref_property, "@property", "ref", "@safe")); + static assert(hasFunctionAttributes!(typeof(&static_ref_property), "@property", "ref", "@safe")); + static assert(hasFunctionAttributes!(static_ref_property, "@property", "ref", "@safe", "nothrow")); + static assert(!hasFunctionAttributes!(static_ref_property, "@property", "ref", "@safe", "nothrow", "@nogc")); + + static assert(hasFunctionAttributes!(ref_property, "@property", "ref", "@safe")); + static assert(hasFunctionAttributes!(typeof(&ref_property), "@property", "ref", "@safe")); + static assert(!hasFunctionAttributes!(ref_property, "@property", "ref", "@safe", "@nogc")); + + struct S2 + { + int pure_const() const pure { return 0; } + int pure_sharedconst() const shared pure { return 0; } + } + + static assert(hasFunctionAttributes!(S2.pure_const, "const", "pure", "@system")); + static assert(hasFunctionAttributes!(typeof(S2.pure_const), "const", "pure", "@system")); + static assert(!hasFunctionAttributes!(S2.pure_const, "const", "pure", "@system", "ref")); + + static assert(hasFunctionAttributes!(S2.pure_sharedconst, "const", "shared", "pure", "@system")); + static assert(hasFunctionAttributes!(typeof(S2.pure_sharedconst), "const", "shared", "pure", "@system")); + static assert(!hasFunctionAttributes!(S2.pure_sharedconst, "const", "shared", "pure", "@system", "@nogc")); + + static assert(hasFunctionAttributes!((int a) { }, "pure", "nothrow", "@nogc", "@safe")); + static assert(hasFunctionAttributes!(typeof((int a) { }), "pure", "nothrow", "@nogc", "@safe")); + static assert(!hasFunctionAttributes!((int a) { }, "pure", "nothrow", "@nogc", "@safe", "ref")); + + auto safeDel = delegate() @safe { }; + static assert(hasFunctionAttributes!(safeDel, "pure", "nothrow", "@nogc", "@safe")); + static assert(hasFunctionAttributes!(typeof(safeDel), "pure", "nothrow", "@nogc", "@safe")); + static assert(!hasFunctionAttributes!(safeDel, "pure", "nothrow", "@nogc", "@safe", "@system")); + + auto trustedDel = delegate() @trusted { }; + static assert(hasFunctionAttributes!(trustedDel, "pure", "nothrow", "@nogc", "@trusted")); + static assert(hasFunctionAttributes!(typeof(trustedDel), "pure", "nothrow", "@nogc", "@trusted")); + static assert(!hasFunctionAttributes!(trustedDel, "pure", "nothrow", "@nogc", "@trusted", "ref")); + + auto systemDel = delegate() @system { }; + static assert(hasFunctionAttributes!(systemDel, "pure", "nothrow", "@nogc", "@system")); + static assert(hasFunctionAttributes!(typeof(systemDel), "pure", "nothrow", "@nogc", "@system")); + static assert(!hasFunctionAttributes!(systemDel, "pure", "nothrow", "@nogc", "@system", "@property")); + + + // call functions to make CodeCov happy + { + assert(pure_nothrow == 0); + safe_nothrow; + assert(static_ref_property == 0); + assert(ref_property == 0); + assert(S2().pure_const == 0); + assert((shared S2()).pure_sharedconst == 0); + cast(void) safeDel; + cast(void) trustedDel; + cast(void) systemDel; + } +} + +/** +$(D true) if $(D func) is $(D @safe) or $(D @trusted). + */ +template isSafe(alias func) + if (isCallable!func) +{ + enum isSafe = (functionAttributes!func & FunctionAttribute.safe) != 0 || + (functionAttributes!func & FunctionAttribute.trusted) != 0; +} + +/// +@safe unittest +{ + @safe int add(int a, int b) {return a+b;} + @trusted int sub(int a, int b) {return a-b;} + @system int mul(int a, int b) {return a*b;} + + static assert( isSafe!add); + static assert( isSafe!sub); + static assert(!isSafe!mul); +} + + +@safe unittest +{ + //Member functions + interface Set + { + int systemF() @system; + int trustedF() @trusted; + int safeF() @safe; + } + static assert( isSafe!(Set.safeF)); + static assert( isSafe!(Set.trustedF)); + static assert(!isSafe!(Set.systemF)); + + //Functions + @safe static safeFunc() {} + @trusted static trustedFunc() {} + @system static systemFunc() {} + + static assert( isSafe!safeFunc); + static assert( isSafe!trustedFunc); + static assert(!isSafe!systemFunc); + + //Delegates + auto safeDel = delegate() @safe {}; + auto trustedDel = delegate() @trusted {}; + auto systemDel = delegate() @system {}; + + static assert( isSafe!safeDel); + static assert( isSafe!trustedDel); + static assert(!isSafe!systemDel); + + //Lambdas + static assert( isSafe!({safeDel();})); + static assert( isSafe!({trustedDel();})); + static assert(!isSafe!({systemDel();})); + + //Static opCall + struct SafeStatic { @safe static SafeStatic opCall() { return SafeStatic.init; } } + struct TrustedStatic { @trusted static TrustedStatic opCall() { return TrustedStatic.init; } } + struct SystemStatic { @system static SystemStatic opCall() { return SystemStatic.init; } } + + static assert( isSafe!(SafeStatic())); + static assert( isSafe!(TrustedStatic())); + static assert(!isSafe!(SystemStatic())); + + //Non-static opCall + struct Safe { @safe Safe opCall() { return Safe.init; } } + struct Trusted { @trusted Trusted opCall() { return Trusted.init; } } + struct System { @system System opCall() { return System.init; } } + + static assert( isSafe!(Safe.init())); + static assert( isSafe!(Trusted.init())); + static assert(!isSafe!(System.init())); +} + + +/** +$(D true) if $(D func) is $(D @system). +*/ +template isUnsafe(alias func) +{ + enum isUnsafe = !isSafe!func; +} + +/// +@safe unittest +{ + @safe int add(int a, int b) {return a+b;} + @trusted int sub(int a, int b) {return a-b;} + @system int mul(int a, int b) {return a*b;} + + static assert(!isUnsafe!add); + static assert(!isUnsafe!sub); + static assert( isUnsafe!mul); +} + +@safe unittest +{ + //Member functions + interface Set + { + int systemF() @system; + int trustedF() @trusted; + int safeF() @safe; + } + static assert(!isUnsafe!(Set.safeF)); + static assert(!isUnsafe!(Set.trustedF)); + static assert( isUnsafe!(Set.systemF)); + + //Functions + @safe static safeFunc() {} + @trusted static trustedFunc() {} + @system static systemFunc() {} + + static assert(!isUnsafe!safeFunc); + static assert(!isUnsafe!trustedFunc); + static assert( isUnsafe!systemFunc); + + //Delegates + auto safeDel = delegate() @safe {}; + auto trustedDel = delegate() @trusted {}; + auto systemDel = delegate() @system {}; + + static assert(!isUnsafe!safeDel); + static assert(!isUnsafe!trustedDel); + static assert( isUnsafe!systemDel); + + //Lambdas + static assert(!isUnsafe!({safeDel();})); + static assert(!isUnsafe!({trustedDel();})); + static assert( isUnsafe!({systemDel();})); + + //Static opCall + struct SafeStatic { @safe static SafeStatic opCall() { return SafeStatic.init; } } + struct TrustedStatic { @trusted static TrustedStatic opCall() { return TrustedStatic.init; } } + struct SystemStatic { @system static SystemStatic opCall() { return SystemStatic.init; } } + + static assert(!isUnsafe!(SafeStatic())); + static assert(!isUnsafe!(TrustedStatic())); + static assert( isUnsafe!(SystemStatic())); + + //Non-static opCall + struct Safe { @safe Safe opCall() { return Safe.init; } } + struct Trusted { @trusted Trusted opCall() { return Trusted.init; } } + struct System { @system System opCall() { return System.init; } } + + static assert(!isUnsafe!(Safe.init())); + static assert(!isUnsafe!(Trusted.init())); + static assert( isUnsafe!(System.init())); +} + + +/** +Determine the linkage attribute of the function. +Params: + func = the function symbol, or the type of a function, delegate, or pointer to function +Returns: + one of the strings "D", "C", "Windows", "Pascal", or "Objective-C" +*/ +template functionLinkage(func...) + if (func.length == 1 && isCallable!func) +{ + enum string functionLinkage = __traits(getLinkage, FunctionTypeOf!func); +} + +/// +@safe unittest +{ + extern(D) void Dfunc() {} + extern(C) void Cfunc() {} + static assert(functionLinkage!Dfunc == "D"); + static assert(functionLinkage!Cfunc == "C"); + + string a = functionLinkage!Dfunc; + assert(a == "D"); + + auto fp = &Cfunc; + string b = functionLinkage!fp; + assert(b == "C"); +} + +@safe unittest +{ + interface Test + { + void const_func() const; + void sharedconst_func() shared const; + } + static assert(functionLinkage!(Test.const_func) == "D"); + static assert(functionLinkage!(Test.sharedconst_func) == "D"); + + static assert(functionLinkage!((int a){}) == "D"); +} + + +/** +Determines what kind of variadic parameters function has. +Params: + func = function symbol or type of function, delegate, or pointer to function +Returns: + enum Variadic + */ +enum Variadic +{ + no, /// Function is not variadic. + c, /// Function is a _C-style variadic function, which uses + /// core.stdc.stdarg + /// Function is a _D-style variadic function, which uses + d, /// __argptr and __arguments. + typesafe, /// Function is a typesafe variadic function. +} + +/// ditto +template variadicFunctionStyle(func...) + if (func.length == 1 && isCallable!func) +{ + enum string varargs = __traits(getFunctionVariadicStyle, FunctionTypeOf!func); + enum Variadic variadicFunctionStyle = + (varargs == "stdarg") ? Variadic.c : + (varargs == "argptr") ? Variadic.d : + (varargs == "typesafe") ? Variadic.typesafe : + (varargs == "none") ? Variadic.no : Variadic.no; +} + +/// +@safe unittest +{ + void func() {} + static assert(variadicFunctionStyle!func == Variadic.no); + + extern(C) int printf(in char*, ...); + static assert(variadicFunctionStyle!printf == Variadic.c); +} + +@safe unittest +{ + import core.vararg; + + extern(D) void novar() {} + extern(C) void cstyle(int, ...) {} + extern(D) void dstyle(...) {} + extern(D) void typesafe(int[]...) {} + + static assert(variadicFunctionStyle!novar == Variadic.no); + static assert(variadicFunctionStyle!cstyle == Variadic.c); + static assert(variadicFunctionStyle!dstyle == Variadic.d); + static assert(variadicFunctionStyle!typesafe == Variadic.typesafe); + + static assert(variadicFunctionStyle!((int[] a...) {}) == Variadic.typesafe); +} + + +/** +Get the function type from a callable object $(D func). + +Using builtin $(D typeof) on a property function yields the types of the +property value, not of the property function itself. Still, +$(D FunctionTypeOf) is able to obtain function types of properties. + +Note: +Do not confuse function types with function pointer types; function types are +usually used for compile-time reflection purposes. + */ +template FunctionTypeOf(func...) + if (func.length == 1 && isCallable!func) +{ + static if (is(typeof(& func[0]) Fsym : Fsym*) && is(Fsym == function) || is(typeof(& func[0]) Fsym == delegate)) + { + alias FunctionTypeOf = Fsym; // HIT: (nested) function symbol + } + else static if (is(typeof(& func[0].opCall) Fobj == delegate)) + { + alias FunctionTypeOf = Fobj; // HIT: callable object + } + else static if (is(typeof(& func[0].opCall) Ftyp : Ftyp*) && is(Ftyp == function)) + { + alias FunctionTypeOf = Ftyp; // HIT: callable type + } + else static if (is(func[0] T) || is(typeof(func[0]) T)) + { + static if (is(T == function)) + alias FunctionTypeOf = T; // HIT: function + else static if (is(T Fptr : Fptr*) && is(Fptr == function)) + alias FunctionTypeOf = Fptr; // HIT: function pointer + else static if (is(T Fdlg == delegate)) + alias FunctionTypeOf = Fdlg; // HIT: delegate + else + static assert(0); + } + else + static assert(0); +} + +/// +@safe unittest +{ + class C + { + int value() @property { return 0; } + } + static assert(is( typeof(C.value) == int )); + static assert(is( FunctionTypeOf!(C.value) == function )); +} + +@system unittest +{ + int test(int a); + int propGet() @property; + int propSet(int a) @property; + int function(int) test_fp; + int delegate(int) test_dg; + static assert(is( typeof(test) == FunctionTypeOf!(typeof(test)) )); + static assert(is( typeof(test) == FunctionTypeOf!test )); + static assert(is( typeof(test) == FunctionTypeOf!test_fp )); + static assert(is( typeof(test) == FunctionTypeOf!test_dg )); + alias int GetterType() @property; + alias int SetterType(int) @property; + static assert(is( FunctionTypeOf!propGet == GetterType )); + static assert(is( FunctionTypeOf!propSet == SetterType )); + + interface Prop { int prop() @property; } + Prop prop; + static assert(is( FunctionTypeOf!(Prop.prop) == GetterType )); + static assert(is( FunctionTypeOf!(prop.prop) == GetterType )); + + class Callable { int opCall(int) { return 0; } } + auto call = new Callable; + static assert(is( FunctionTypeOf!call == typeof(test) )); + + struct StaticCallable { static int opCall(int) { return 0; } } + StaticCallable stcall_val; + StaticCallable* stcall_ptr; + static assert(is( FunctionTypeOf!stcall_val == typeof(test) )); + static assert(is( FunctionTypeOf!stcall_ptr == typeof(test) )); + + interface Overloads + { + void test(string); + real test(real); + int test(int); + int test() @property; + } + alias ov = AliasSeq!(__traits(getVirtualFunctions, Overloads, "test")); + alias F_ov0 = FunctionTypeOf!(ov[0]); + alias F_ov1 = FunctionTypeOf!(ov[1]); + alias F_ov2 = FunctionTypeOf!(ov[2]); + alias F_ov3 = FunctionTypeOf!(ov[3]); + static assert(is(F_ov0* == void function(string))); + static assert(is(F_ov1* == real function(real))); + static assert(is(F_ov2* == int function(int))); + static assert(is(F_ov3* == int function() @property)); + + alias F_dglit = FunctionTypeOf!((int a){ return a; }); + static assert(is(F_dglit* : int function(int))); +} + +/** + * Constructs a new function or delegate type with the same basic signature + * as the given one, but different attributes (including linkage). + * + * This is especially useful for adding/removing attributes to/from types in + * generic code, where the actual type name cannot be spelt out. + * + * Params: + * T = The base type. + * linkage = The desired linkage of the result type. + * attrs = The desired $(LREF FunctionAttribute)s of the result type. + */ +template SetFunctionAttributes(T, string linkage, uint attrs) + if (isFunctionPointer!T || isDelegate!T) +{ + mixin({ + import std.algorithm.searching : canFind; + + static assert(!(attrs & FunctionAttribute.trusted) || + !(attrs & FunctionAttribute.safe), + "Cannot have a function/delegate that is both trusted and safe."); + + static immutable linkages = ["D", "C", "Windows", "Pascal", "C++", "System"]; + static assert(canFind(linkages, linkage), "Invalid linkage '" ~ + linkage ~ "', must be one of " ~ linkages.stringof ~ "."); + + string result = "alias "; + + static if (linkage != "D") + result ~= "extern(" ~ linkage ~ ") "; + + static if (attrs & FunctionAttribute.ref_) + result ~= "ref "; + + result ~= "ReturnType!T"; + + static if (isDelegate!T) + result ~= " delegate"; + else + result ~= " function"; + + result ~= "("; + + static if (Parameters!T.length > 0) + result ~= "Parameters!T"; + + enum varStyle = variadicFunctionStyle!T; + static if (varStyle == Variadic.c) + result ~= ", ..."; + else static if (varStyle == Variadic.d) + result ~= "..."; + else static if (varStyle == Variadic.typesafe) + result ~= "..."; + + result ~= ")"; + + static if (attrs & FunctionAttribute.pure_) + result ~= " pure"; + static if (attrs & FunctionAttribute.nothrow_) + result ~= " nothrow"; + static if (attrs & FunctionAttribute.property) + result ~= " @property"; + static if (attrs & FunctionAttribute.trusted) + result ~= " @trusted"; + static if (attrs & FunctionAttribute.safe) + result ~= " @safe"; + static if (attrs & FunctionAttribute.nogc) + result ~= " @nogc"; + static if (attrs & FunctionAttribute.system) + result ~= " @system"; + static if (attrs & FunctionAttribute.const_) + result ~= " const"; + static if (attrs & FunctionAttribute.immutable_) + result ~= " immutable"; + static if (attrs & FunctionAttribute.inout_) + result ~= " inout"; + static if (attrs & FunctionAttribute.shared_) + result ~= " shared"; + static if (attrs & FunctionAttribute.return_) + result ~= " return"; + + result ~= " SetFunctionAttributes;"; + return result; + }()); +} + +/// Ditto +template SetFunctionAttributes(T, string linkage, uint attrs) + if (is(T == function)) +{ + // To avoid a lot of syntactic headaches, we just use the above version to + // operate on the corresponding function pointer type and then remove the + // indirection again. + alias SetFunctionAttributes = FunctionTypeOf!(SetFunctionAttributes!(T*, linkage, attrs)); +} + +/// +@safe unittest +{ + alias ExternC(T) = SetFunctionAttributes!(T, "C", functionAttributes!T); + + auto assumePure(T)(T t) + if (isFunctionPointer!T || isDelegate!T) + { + enum attrs = functionAttributes!T | FunctionAttribute.pure_; + return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; + } +} + +version (unittest) +{ + // Some function types to test. + int sc(scope int, ref int, out int, lazy int, int); + extern(System) int novar(); + extern(C) int cstyle(int, ...); + extern(D) int dstyle(...); + extern(D) int typesafe(int[]...); +} +@safe unittest +{ + import std.algorithm.iteration : reduce; + + alias FA = FunctionAttribute; + foreach (BaseT; AliasSeq!(typeof(&sc), typeof(&novar), typeof(&cstyle), + typeof(&dstyle), typeof(&typesafe))) + { + foreach (T; AliasSeq!(BaseT, FunctionTypeOf!BaseT)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + enum linkage = functionLinkage!T; + enum attrs = functionAttributes!T; + + static assert(is(SetFunctionAttributes!(T, linkage, attrs) == T), + "Identity check failed for: " ~ T.stringof); + + // Check that all linkage types work (D-style variadics require D linkage). + static if (variadicFunctionStyle!T != Variadic.d) + { + foreach (newLinkage; AliasSeq!("D", "C", "Windows", "Pascal", "C++")) + { + alias New = SetFunctionAttributes!(T, newLinkage, attrs); + static assert(functionLinkage!New == newLinkage, + "Linkage test failed for: " ~ T.stringof ~ ", " ~ newLinkage ~ + " (got " ~ New.stringof ~ ")"); + } + } + + // Add @safe. + alias T1 = SetFunctionAttributes!(T, functionLinkage!T, FA.safe); + static assert(functionAttributes!T1 == FA.safe); + + // Add all known attributes, excluding conflicting ones. + enum allAttrs = reduce!"a | b"([EnumMembers!FA]) + & ~FA.safe & ~FA.property & ~FA.const_ & ~FA.immutable_ & ~FA.inout_ + & ~FA.shared_ & ~FA.system & ~FA.return_ & ~FA.scope_; + + alias T2 = SetFunctionAttributes!(T1, functionLinkage!T, allAttrs); + static assert(functionAttributes!T2 == allAttrs); + + // Strip all attributes again. + alias T3 = SetFunctionAttributes!(T2, functionLinkage!T, FA.none); + static assert(is(T3 == T)); + }(); + } +} + + +//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// +// Aggregate Types +//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// + +/** +Determines whether `T` is a class nested inside another class +and that `T.outer` is the implicit reference to the outer class +(i.e. `outer` has not been used as a field or method name) + +Params: + T = type to test + +Returns: +`true` if `T` is a class nested inside another, with the conditions described above; +`false` otherwise +*/ +template isInnerClass(T) + if (is(T == class)) +{ + import std.meta : staticIndexOf; + + static if (is(typeof(T.outer))) + enum isInnerClass = __traits(isSame, typeof(T.outer), __traits(parent, T)) + && (staticIndexOf!(__traits(allMembers, T), "outer") == -1); + else + enum isInnerClass = false; +} + +/// +@safe unittest +{ + class C + { + int outer; + } + static assert(!isInnerClass!C); + + class Outer1 + { + class Inner1 { } + class Inner2 + { + int outer; + } + } + static assert(isInnerClass!(Outer1.Inner1)); + static assert(!isInnerClass!(Outer1.Inner2)); + + static class Outer2 + { + static class Inner + { + int outer; + } + } + static assert(!isInnerClass!(Outer2.Inner)); +} + +/** +Determines whether $(D T) has its own context pointer. +$(D T) must be either $(D class), $(D struct), or $(D union). +*/ +template isNested(T) + if (is(T == class) || is(T == struct) || is(T == union)) +{ + enum isNested = __traits(isNested, T); +} + +/// +@safe unittest +{ + static struct S { } + static assert(!isNested!S); + + int i; + struct NestedStruct { void f() { ++i; } } + static assert(isNested!NestedStruct); +} + +/** +Determines whether $(D T) or any of its representation types +have a context pointer. +*/ +template hasNested(T) +{ + import std.meta : anySatisfy, Filter; + + static if (isStaticArray!T && T.length) + enum hasNested = hasNested!(typeof(T.init[0])); + else static if (is(T == class) || is(T == struct) || is(T == union)) + { + // prevent infinite recursion for class with member of same type + enum notSame(U) = !is(Unqual!T == Unqual!U); + enum hasNested = isNested!T || + anySatisfy!(.hasNested, Filter!(notSame, Fields!T)); + } + else + enum hasNested = false; +} + +/// +@safe unittest +{ + static struct S { } + + int i; + struct NS { void f() { ++i; } } + + static assert(!hasNested!(S[2])); + static assert(hasNested!(NS[2])); +} + +@safe unittest +{ + static assert(!__traits(compiles, isNested!int)); + static assert(!hasNested!int); + + static struct StaticStruct { } + static assert(!isNested!StaticStruct); + static assert(!hasNested!StaticStruct); + + int i; + struct NestedStruct { void f() { ++i; } } + static assert( isNested!NestedStruct); + static assert( hasNested!NestedStruct); + static assert( isNested!(immutable NestedStruct)); + static assert( hasNested!(immutable NestedStruct)); + + static assert(!__traits(compiles, isNested!(NestedStruct[1]))); + static assert( hasNested!(NestedStruct[1])); + static assert(!hasNested!(NestedStruct[0])); + + struct S1 { NestedStruct nested; } + static assert(!isNested!S1); + static assert( hasNested!S1); + + static struct S2 { NestedStruct nested; } + static assert(!isNested!S2); + static assert( hasNested!S2); + + static struct S3 { NestedStruct[0] nested; } + static assert(!isNested!S3); + static assert(!hasNested!S3); + + static union U { NestedStruct nested; } + static assert(!isNested!U); + static assert( hasNested!U); + + static class StaticClass { } + static assert(!isNested!StaticClass); + static assert(!hasNested!StaticClass); + + class NestedClass { void f() { ++i; } } + static assert( isNested!NestedClass); + static assert( hasNested!NestedClass); + static assert( isNested!(immutable NestedClass)); + static assert( hasNested!(immutable NestedClass)); + + static assert(!__traits(compiles, isNested!(NestedClass[1]))); + static assert( hasNested!(NestedClass[1])); + static assert(!hasNested!(NestedClass[0])); + + static class A + { + A a; + } + static assert(!hasNested!A); +} + + +/*** + * Get as a tuple the types of the fields of a struct, class, or union. + * This consists of the fields that take up memory space, + * excluding the hidden fields like the virtual function + * table pointer or a context pointer for nested types. + * If $(D T) isn't a struct, class, or union returns a tuple + * with one element $(D T). + */ +template Fields(T) +{ + static if (is(T == struct) || is(T == union)) + alias Fields = typeof(T.tupleof[0 .. $ - isNested!T]); + else static if (is(T == class)) + alias Fields = typeof(T.tupleof); + else + alias Fields = AliasSeq!T; +} + +/// +@safe unittest +{ + struct S { int x; float y; } + static assert(is(Fields!S == AliasSeq!(int, float))); +} + +/** + * Alternate name for $(LREF Fields), kept for legacy compatibility. + */ +alias FieldTypeTuple = Fields; + +@safe unittest +{ + static assert(is(FieldTypeTuple!int == AliasSeq!int)); + + static struct StaticStruct1 { } + static assert(is(FieldTypeTuple!StaticStruct1 == AliasSeq!())); + + static struct StaticStruct2 { int a, b; } + static assert(is(FieldTypeTuple!StaticStruct2 == AliasSeq!(int, int))); + + int i; + + struct NestedStruct1 { void f() { ++i; } } + static assert(is(FieldTypeTuple!NestedStruct1 == AliasSeq!())); + + struct NestedStruct2 { int a; void f() { ++i; } } + static assert(is(FieldTypeTuple!NestedStruct2 == AliasSeq!int)); + + class NestedClass { int a; void f() { ++i; } } + static assert(is(FieldTypeTuple!NestedClass == AliasSeq!int)); +} + + +//Required for FieldNameTuple +private enum NameOf(alias T) = T.stringof; + +/** + * Get as an expression tuple the names of the fields of a struct, class, or + * union. This consists of the fields that take up memory space, excluding the + * hidden fields like the virtual function table pointer or a context pointer + * for nested types. If $(D T) isn't a struct, class, or union returns an + * expression tuple with an empty string. + */ +template FieldNameTuple(T) +{ + import std.meta : staticMap; + static if (is(T == struct) || is(T == union)) + alias FieldNameTuple = staticMap!(NameOf, T.tupleof[0 .. $ - isNested!T]); + else static if (is(T == class)) + alias FieldNameTuple = staticMap!(NameOf, T.tupleof); + else + alias FieldNameTuple = AliasSeq!""; +} + +/// +@safe unittest +{ + struct S { int x; float y; } + static assert(FieldNameTuple!S == AliasSeq!("x", "y")); + static assert(FieldNameTuple!int == AliasSeq!""); +} + +@safe unittest +{ + static assert(FieldNameTuple!int == AliasSeq!""); + + static struct StaticStruct1 { } + static assert(is(FieldNameTuple!StaticStruct1 == AliasSeq!())); + + static struct StaticStruct2 { int a, b; } + static assert(FieldNameTuple!StaticStruct2 == AliasSeq!("a", "b")); + + int i; + + struct NestedStruct1 { void f() { ++i; } } + static assert(is(FieldNameTuple!NestedStruct1 == AliasSeq!())); + + struct NestedStruct2 { int a; void f() { ++i; } } + static assert(FieldNameTuple!NestedStruct2 == AliasSeq!"a"); + + class NestedClass { int a; void f() { ++i; } } + static assert(FieldNameTuple!NestedClass == AliasSeq!"a"); +} + + +/*** +Get the primitive types of the fields of a struct or class, in +topological order. +*/ +template RepresentationTypeTuple(T) +{ + template Impl(T...) + { + static if (T.length == 0) + { + alias Impl = AliasSeq!(); + } + else + { + import std.typecons : Rebindable; + + static if (is(T[0] R: Rebindable!R)) + { + alias Impl = Impl!(Impl!R, T[1 .. $]); + } + else static if (is(T[0] == struct) || is(T[0] == union)) + { + // @@@BUG@@@ this should work + //alias .RepresentationTypes!(T[0].tupleof) + // RepresentationTypes; + alias Impl = Impl!(FieldTypeTuple!(T[0]), T[1 .. $]); + } + else + { + alias Impl = AliasSeq!(T[0], Impl!(T[1 .. $])); + } + } + } + + static if (is(T == struct) || is(T == union) || is(T == class)) + { + alias RepresentationTypeTuple = Impl!(FieldTypeTuple!T); + } + else + { + alias RepresentationTypeTuple = Impl!T; + } +} + +/// +@safe unittest +{ + struct S1 { int a; float b; } + struct S2 { char[] a; union { S1 b; S1 * c; } } + alias R = RepresentationTypeTuple!S2; + assert(R.length == 4 + && is(R[0] == char[]) && is(R[1] == int) + && is(R[2] == float) && is(R[3] == S1*)); +} + +@safe unittest +{ + alias S1 = RepresentationTypeTuple!int; + static assert(is(S1 == AliasSeq!int)); + + struct S2 { int a; } + struct S3 { int a; char b; } + struct S4 { S1 a; int b; S3 c; } + static assert(is(RepresentationTypeTuple!S2 == AliasSeq!int)); + static assert(is(RepresentationTypeTuple!S3 == AliasSeq!(int, char))); + static assert(is(RepresentationTypeTuple!S4 == AliasSeq!(int, int, int, char))); + + struct S11 { int a; float b; } + struct S21 { char[] a; union { S11 b; S11 * c; } } + alias R = RepresentationTypeTuple!S21; + assert(R.length == 4 + && is(R[0] == char[]) && is(R[1] == int) + && is(R[2] == float) && is(R[3] == S11*)); + + class C { int a; float b; } + alias R1 = RepresentationTypeTuple!C; + static assert(R1.length == 2 && is(R1[0] == int) && is(R1[1] == float)); + + /* Issue 6642 */ + import std.typecons : Rebindable; + + struct S5 { int a; Rebindable!(immutable Object) b; } + alias R2 = RepresentationTypeTuple!S5; + static assert(R2.length == 2 && is(R2[0] == int) && is(R2[1] == immutable(Object))); +} + +/* +Statically evaluates to $(D true) if and only if $(D T)'s +representation contains at least one field of pointer or array type. +Members of class types are not considered raw pointers. Pointers to +immutable objects are not considered raw aliasing. +*/ +private template hasRawAliasing(T...) +{ + template Impl(T...) + { + static if (T.length == 0) + { + enum Impl = false; + } + else + { + static if (is(T[0] foo : U*, U) && !isFunctionPointer!(T[0])) + enum has = !is(U == immutable); + else static if (is(T[0] foo : U[], U) && !isStaticArray!(T[0])) + enum has = !is(U == immutable); + else static if (isAssociativeArray!(T[0])) + enum has = !is(T[0] == immutable); + else + enum has = false; + + enum Impl = has || Impl!(T[1 .. $]); + } + } + + enum hasRawAliasing = Impl!(RepresentationTypeTuple!T); +} + +/// +@safe unittest +{ + // simple types + static assert(!hasRawAliasing!int); + static assert( hasRawAliasing!(char*)); + // references aren't raw pointers + static assert(!hasRawAliasing!Object); + // built-in arrays do contain raw pointers + static assert( hasRawAliasing!(int[])); + // aggregate of simple types + struct S1 { int a; double b; } + static assert(!hasRawAliasing!S1); + // indirect aggregation + struct S2 { S1 a; double b; } + static assert(!hasRawAliasing!S2); +} + +@safe unittest +{ + // struct with a pointer member + struct S3 { int a; double * b; } + static assert( hasRawAliasing!S3); + // struct with an indirect pointer member + struct S4 { S3 a; double b; } + static assert( hasRawAliasing!S4); + struct S5 { int a; Object z; int c; } + static assert( hasRawAliasing!S3); + static assert( hasRawAliasing!S4); + static assert(!hasRawAliasing!S5); + + union S6 { int a; int b; } + union S7 { int a; int * b; } + static assert(!hasRawAliasing!S6); + static assert( hasRawAliasing!S7); + + static assert(!hasRawAliasing!(void delegate())); + static assert(!hasRawAliasing!(void delegate() const)); + static assert(!hasRawAliasing!(void delegate() immutable)); + static assert(!hasRawAliasing!(void delegate() shared)); + static assert(!hasRawAliasing!(void delegate() shared const)); + static assert(!hasRawAliasing!(const(void delegate()))); + static assert(!hasRawAliasing!(immutable(void delegate()))); + + struct S8 { void delegate() a; int b; Object c; } + class S12 { typeof(S8.tupleof) a; } + class S13 { typeof(S8.tupleof) a; int* b; } + static assert(!hasRawAliasing!S8); + static assert(!hasRawAliasing!S12); + static assert( hasRawAliasing!S13); + + enum S9 { a } + static assert(!hasRawAliasing!S9); + + // indirect members + struct S10 { S7 a; int b; } + struct S11 { S6 a; int b; } + static assert( hasRawAliasing!S10); + static assert(!hasRawAliasing!S11); + + static assert( hasRawAliasing!(int[string])); + static assert(!hasRawAliasing!(immutable(int[string]))); +} + +/* +Statically evaluates to $(D true) if and only if $(D T)'s +representation contains at least one non-shared field of pointer or +array type. Members of class types are not considered raw pointers. +Pointers to immutable objects are not considered raw aliasing. +*/ +private template hasRawUnsharedAliasing(T...) +{ + template Impl(T...) + { + static if (T.length == 0) + { + enum Impl = false; + } + else + { + static if (is(T[0] foo : U*, U) && !isFunctionPointer!(T[0])) + enum has = !is(U == immutable) && !is(U == shared); + else static if (is(T[0] foo : U[], U) && !isStaticArray!(T[0])) + enum has = !is(U == immutable) && !is(U == shared); + else static if (isAssociativeArray!(T[0])) + enum has = !is(T[0] == immutable) && !is(T[0] == shared); + else + enum has = false; + + enum Impl = has || Impl!(T[1 .. $]); + } + } + + enum hasRawUnsharedAliasing = Impl!(RepresentationTypeTuple!T); +} + +/// +@safe unittest +{ + // simple types + static assert(!hasRawUnsharedAliasing!int); + static assert( hasRawUnsharedAliasing!(char*)); + static assert(!hasRawUnsharedAliasing!(shared char*)); + // references aren't raw pointers + static assert(!hasRawUnsharedAliasing!Object); + // built-in arrays do contain raw pointers + static assert( hasRawUnsharedAliasing!(int[])); + static assert(!hasRawUnsharedAliasing!(shared int[])); + // aggregate of simple types + struct S1 { int a; double b; } + static assert(!hasRawUnsharedAliasing!S1); + // indirect aggregation + struct S2 { S1 a; double b; } + static assert(!hasRawUnsharedAliasing!S2); + // struct with a pointer member + struct S3 { int a; double * b; } + static assert( hasRawUnsharedAliasing!S3); + struct S4 { int a; shared double * b; } + static assert(!hasRawUnsharedAliasing!S4); +} + +@safe unittest +{ + // struct with a pointer member + struct S3 { int a; double * b; } + static assert( hasRawUnsharedAliasing!S3); + struct S4 { int a; shared double * b; } + static assert(!hasRawUnsharedAliasing!S4); + // struct with an indirect pointer member + struct S5 { S3 a; double b; } + static assert( hasRawUnsharedAliasing!S5); + struct S6 { S4 a; double b; } + static assert(!hasRawUnsharedAliasing!S6); + struct S7 { int a; Object z; int c; } + static assert( hasRawUnsharedAliasing!S5); + static assert(!hasRawUnsharedAliasing!S6); + static assert(!hasRawUnsharedAliasing!S7); + + union S8 { int a; int b; } + union S9 { int a; int* b; } + union S10 { int a; shared int* b; } + static assert(!hasRawUnsharedAliasing!S8); + static assert( hasRawUnsharedAliasing!S9); + static assert(!hasRawUnsharedAliasing!S10); + + static assert(!hasRawUnsharedAliasing!(void delegate())); + static assert(!hasRawUnsharedAliasing!(void delegate() const)); + static assert(!hasRawUnsharedAliasing!(void delegate() immutable)); + static assert(!hasRawUnsharedAliasing!(void delegate() shared)); + static assert(!hasRawUnsharedAliasing!(void delegate() shared const)); + static assert(!hasRawUnsharedAliasing!(const(void delegate()))); + static assert(!hasRawUnsharedAliasing!(const(void delegate() const))); + static assert(!hasRawUnsharedAliasing!(const(void delegate() immutable))); + static assert(!hasRawUnsharedAliasing!(const(void delegate() shared))); + static assert(!hasRawUnsharedAliasing!(const(void delegate() shared const))); + static assert(!hasRawUnsharedAliasing!(immutable(void delegate()))); + static assert(!hasRawUnsharedAliasing!(immutable(void delegate() const))); + static assert(!hasRawUnsharedAliasing!(immutable(void delegate() immutable))); + static assert(!hasRawUnsharedAliasing!(immutable(void delegate() shared))); + static assert(!hasRawUnsharedAliasing!(immutable(void delegate() shared const))); + static assert(!hasRawUnsharedAliasing!(shared(void delegate()))); + static assert(!hasRawUnsharedAliasing!(shared(void delegate() const))); + static assert(!hasRawUnsharedAliasing!(shared(void delegate() immutable))); + static assert(!hasRawUnsharedAliasing!(shared(void delegate() shared))); + static assert(!hasRawUnsharedAliasing!(shared(void delegate() shared const))); + static assert(!hasRawUnsharedAliasing!(shared(const(void delegate())))); + static assert(!hasRawUnsharedAliasing!(shared(const(void delegate() const)))); + static assert(!hasRawUnsharedAliasing!(shared(const(void delegate() immutable)))); + static assert(!hasRawUnsharedAliasing!(shared(const(void delegate() shared)))); + static assert(!hasRawUnsharedAliasing!(shared(const(void delegate() shared const)))); + static assert(!hasRawUnsharedAliasing!(void function())); + + enum S13 { a } + static assert(!hasRawUnsharedAliasing!S13); + + // indirect members + struct S14 { S9 a; int b; } + struct S15 { S10 a; int b; } + struct S16 { S6 a; int b; } + static assert( hasRawUnsharedAliasing!S14); + static assert(!hasRawUnsharedAliasing!S15); + static assert(!hasRawUnsharedAliasing!S16); + + static assert( hasRawUnsharedAliasing!(int[string])); + static assert(!hasRawUnsharedAliasing!(shared(int[string]))); + static assert(!hasRawUnsharedAliasing!(immutable(int[string]))); + + struct S17 + { + void delegate() shared a; + void delegate() immutable b; + void delegate() shared const c; + shared(void delegate()) d; + shared(void delegate() shared) e; + shared(void delegate() immutable) f; + shared(void delegate() shared const) g; + immutable(void delegate()) h; + immutable(void delegate() shared) i; + immutable(void delegate() immutable) j; + immutable(void delegate() shared const) k; + shared(const(void delegate())) l; + shared(const(void delegate() shared)) m; + shared(const(void delegate() immutable)) n; + shared(const(void delegate() shared const)) o; + } + struct S18 { typeof(S17.tupleof) a; void delegate() p; } + struct S19 { typeof(S17.tupleof) a; Object p; } + struct S20 { typeof(S17.tupleof) a; int* p; } + class S21 { typeof(S17.tupleof) a; } + class S22 { typeof(S17.tupleof) a; void delegate() p; } + class S23 { typeof(S17.tupleof) a; Object p; } + class S24 { typeof(S17.tupleof) a; int* p; } + static assert(!hasRawUnsharedAliasing!S17); + static assert(!hasRawUnsharedAliasing!(immutable(S17))); + static assert(!hasRawUnsharedAliasing!(shared(S17))); + static assert(!hasRawUnsharedAliasing!S18); + static assert(!hasRawUnsharedAliasing!(immutable(S18))); + static assert(!hasRawUnsharedAliasing!(shared(S18))); + static assert(!hasRawUnsharedAliasing!S19); + static assert(!hasRawUnsharedAliasing!(immutable(S19))); + static assert(!hasRawUnsharedAliasing!(shared(S19))); + static assert( hasRawUnsharedAliasing!S20); + static assert(!hasRawUnsharedAliasing!(immutable(S20))); + static assert(!hasRawUnsharedAliasing!(shared(S20))); + static assert(!hasRawUnsharedAliasing!S21); + static assert(!hasRawUnsharedAliasing!(immutable(S21))); + static assert(!hasRawUnsharedAliasing!(shared(S21))); + static assert(!hasRawUnsharedAliasing!S22); + static assert(!hasRawUnsharedAliasing!(immutable(S22))); + static assert(!hasRawUnsharedAliasing!(shared(S22))); + static assert(!hasRawUnsharedAliasing!S23); + static assert(!hasRawUnsharedAliasing!(immutable(S23))); + static assert(!hasRawUnsharedAliasing!(shared(S23))); + static assert( hasRawUnsharedAliasing!S24); + static assert(!hasRawUnsharedAliasing!(immutable(S24))); + static assert(!hasRawUnsharedAliasing!(shared(S24))); + struct S25 {} + class S26 {} + interface S27 {} + union S28 {} + static assert(!hasRawUnsharedAliasing!S25); + static assert(!hasRawUnsharedAliasing!S26); + static assert(!hasRawUnsharedAliasing!S27); + static assert(!hasRawUnsharedAliasing!S28); +} + +/* +Statically evaluates to $(D true) if and only if $(D T)'s +representation includes at least one non-immutable object reference. +*/ + +private template hasObjects(T...) +{ + static if (T.length == 0) + { + enum hasObjects = false; + } + else static if (is(T[0] == struct)) + { + enum hasObjects = hasObjects!( + RepresentationTypeTuple!(T[0]), T[1 .. $]); + } + else + { + enum hasObjects = ((is(T[0] == class) || is(T[0] == interface)) + && !is(T[0] == immutable)) || hasObjects!(T[1 .. $]); + } +} + +/* +Statically evaluates to $(D true) if and only if $(D T)'s +representation includes at least one non-immutable non-shared object +reference. +*/ +private template hasUnsharedObjects(T...) +{ + static if (T.length == 0) + { + enum hasUnsharedObjects = false; + } + else static if (is(T[0] == struct)) + { + enum hasUnsharedObjects = hasUnsharedObjects!( + RepresentationTypeTuple!(T[0]), T[1 .. $]); + } + else + { + enum hasUnsharedObjects = ((is(T[0] == class) || is(T[0] == interface)) && + !is(T[0] == immutable) && !is(T[0] == shared)) || + hasUnsharedObjects!(T[1 .. $]); + } +} + +/** +Returns $(D true) if and only if $(D T)'s representation includes at +least one of the following: $(OL $(LI a raw pointer $(D U*) and $(D U) +is not immutable;) $(LI an array $(D U[]) and $(D U) is not +immutable;) $(LI a reference to a class or interface type $(D C) and $(D C) is +not immutable.) $(LI an associative array that is not immutable.) +$(LI a delegate.)) +*/ +template hasAliasing(T...) +{ + import std.meta : anySatisfy; + import std.typecons : Rebindable; + + static if (T.length && is(T[0] : Rebindable!R, R)) + { + enum hasAliasing = hasAliasing!(R, T[1 .. $]); + } + else + { + template isAliasingDelegate(T) + { + enum isAliasingDelegate = isDelegate!T + && !is(T == immutable) + && !is(FunctionTypeOf!T == immutable); + } + enum hasAliasing = hasRawAliasing!T || hasObjects!T || + anySatisfy!(isAliasingDelegate, T, RepresentationTypeTuple!T); + } +} + +/// +@safe unittest +{ + struct S1 { int a; Object b; } + struct S2 { string a; } + struct S3 { int a; immutable Object b; } + struct S4 { float[3] vals; } + static assert( hasAliasing!S1); + static assert(!hasAliasing!S2); + static assert(!hasAliasing!S3); + static assert(!hasAliasing!S4); +} + +@safe unittest +{ + static assert( hasAliasing!(uint[uint])); + static assert(!hasAliasing!(immutable(uint[uint]))); + static assert( hasAliasing!(void delegate())); + static assert( hasAliasing!(void delegate() const)); + static assert(!hasAliasing!(void delegate() immutable)); + static assert( hasAliasing!(void delegate() shared)); + static assert( hasAliasing!(void delegate() shared const)); + static assert( hasAliasing!(const(void delegate()))); + static assert( hasAliasing!(const(void delegate() const))); + static assert(!hasAliasing!(const(void delegate() immutable))); + static assert( hasAliasing!(const(void delegate() shared))); + static assert( hasAliasing!(const(void delegate() shared const))); + static assert(!hasAliasing!(immutable(void delegate()))); + static assert(!hasAliasing!(immutable(void delegate() const))); + static assert(!hasAliasing!(immutable(void delegate() immutable))); + static assert(!hasAliasing!(immutable(void delegate() shared))); + static assert(!hasAliasing!(immutable(void delegate() shared const))); + static assert( hasAliasing!(shared(const(void delegate())))); + static assert( hasAliasing!(shared(const(void delegate() const)))); + static assert(!hasAliasing!(shared(const(void delegate() immutable)))); + static assert( hasAliasing!(shared(const(void delegate() shared)))); + static assert( hasAliasing!(shared(const(void delegate() shared const)))); + static assert(!hasAliasing!(void function())); + + interface I; + static assert( hasAliasing!I); + + import std.typecons : Rebindable; + static assert( hasAliasing!(Rebindable!(const Object))); + static assert(!hasAliasing!(Rebindable!(immutable Object))); + static assert( hasAliasing!(Rebindable!(shared Object))); + static assert( hasAliasing!(Rebindable!Object)); + + struct S5 + { + void delegate() immutable b; + shared(void delegate() immutable) f; + immutable(void delegate() immutable) j; + shared(const(void delegate() immutable)) n; + } + struct S6 { typeof(S5.tupleof) a; void delegate() p; } + static assert(!hasAliasing!S5); + static assert( hasAliasing!S6); + + struct S7 { void delegate() a; int b; Object c; } + class S8 { int a; int b; } + class S9 { typeof(S8.tupleof) a; } + class S10 { typeof(S8.tupleof) a; int* b; } + static assert( hasAliasing!S7); + static assert( hasAliasing!S8); + static assert( hasAliasing!S9); + static assert( hasAliasing!S10); + struct S11 {} + class S12 {} + interface S13 {} + union S14 {} + static assert(!hasAliasing!S11); + static assert( hasAliasing!S12); + static assert( hasAliasing!S13); + static assert(!hasAliasing!S14); +} +/** +Returns $(D true) if and only if $(D T)'s representation includes at +least one of the following: $(OL $(LI a raw pointer $(D U*);) $(LI an +array $(D U[]);) $(LI a reference to a class type $(D C).) +$(LI an associative array.) $(LI a delegate.)) + */ +template hasIndirections(T) +{ + import std.meta : anySatisfy; + static if (is(T == struct) || is(T == union)) + enum hasIndirections = anySatisfy!(.hasIndirections, FieldTypeTuple!T); + else static if (isStaticArray!T && is(T : E[N], E, size_t N)) + enum hasIndirections = is(E == void) ? true : hasIndirections!E; + else static if (isFunctionPointer!T) + enum hasIndirections = false; + else + enum hasIndirections = isPointer!T || isDelegate!T || isDynamicArray!T || + isAssociativeArray!T || is (T == class) || is(T == interface); +} + +/// +@safe unittest +{ + static assert( hasIndirections!(int[string])); + static assert( hasIndirections!(void delegate())); + static assert( hasIndirections!(void delegate() immutable)); + static assert( hasIndirections!(immutable(void delegate()))); + static assert( hasIndirections!(immutable(void delegate() immutable))); + + static assert(!hasIndirections!(void function())); + static assert( hasIndirections!(void*[1])); + static assert(!hasIndirections!(byte[1])); +} + +@safe unittest +{ + // void static array hides actual type of bits, so "may have indirections". + static assert( hasIndirections!(void[1])); + interface I {} + struct S1 {} + struct S2 { int a; } + struct S3 { int a; int b; } + struct S4 { int a; int* b; } + struct S5 { int a; Object b; } + struct S6 { int a; string b; } + struct S7 { int a; immutable Object b; } + struct S8 { int a; immutable I b; } + struct S9 { int a; void delegate() b; } + struct S10 { int a; immutable(void delegate()) b; } + struct S11 { int a; void delegate() immutable b; } + struct S12 { int a; immutable(void delegate() immutable) b; } + class S13 {} + class S14 { int a; } + class S15 { int a; int b; } + class S16 { int a; Object b; } + class S17 { string a; } + class S18 { int a; immutable Object b; } + class S19 { int a; immutable(void delegate() immutable) b; } + union S20 {} + union S21 { int a; } + union S22 { int a; int b; } + union S23 { int a; Object b; } + union S24 { string a; } + union S25 { int a; immutable Object b; } + union S26 { int a; immutable(void delegate() immutable) b; } + static assert( hasIndirections!I); + static assert(!hasIndirections!S1); + static assert(!hasIndirections!S2); + static assert(!hasIndirections!S3); + static assert( hasIndirections!S4); + static assert( hasIndirections!S5); + static assert( hasIndirections!S6); + static assert( hasIndirections!S7); + static assert( hasIndirections!S8); + static assert( hasIndirections!S9); + static assert( hasIndirections!S10); + static assert( hasIndirections!S12); + static assert( hasIndirections!S13); + static assert( hasIndirections!S14); + static assert( hasIndirections!S15); + static assert( hasIndirections!S16); + static assert( hasIndirections!S17); + static assert( hasIndirections!S18); + static assert( hasIndirections!S19); + static assert(!hasIndirections!S20); + static assert(!hasIndirections!S21); + static assert(!hasIndirections!S22); + static assert( hasIndirections!S23); + static assert( hasIndirections!S24); + static assert( hasIndirections!S25); + static assert( hasIndirections!S26); +} + +@safe unittest //12000 +{ + static struct S(T) + { + static assert(hasIndirections!T); + } + + static class A(T) + { + S!A a; + } + + A!int dummy; +} + +/** +Returns $(D true) if and only if $(D T)'s representation includes at +least one of the following: $(OL $(LI a raw pointer $(D U*) and $(D U) +is not immutable or shared;) $(LI an array $(D U[]) and $(D U) is not +immutable or shared;) $(LI a reference to a class type $(D C) and +$(D C) is not immutable or shared.) $(LI an associative array that is not +immutable or shared.) $(LI a delegate that is not shared.)) +*/ + +template hasUnsharedAliasing(T...) +{ + import std.meta : anySatisfy; + import std.typecons : Rebindable; + + static if (!T.length) + { + enum hasUnsharedAliasing = false; + } + else static if (is(T[0] R: Rebindable!R)) + { + enum hasUnsharedAliasing = hasUnsharedAliasing!R; + } + else + { + template unsharedDelegate(T) + { + enum bool unsharedDelegate = isDelegate!T + && !is(T == shared) + && !is(T == shared) + && !is(T == immutable) + && !is(FunctionTypeOf!T == shared) + && !is(FunctionTypeOf!T == immutable); + } + + enum hasUnsharedAliasing = + hasRawUnsharedAliasing!(T[0]) || + anySatisfy!(unsharedDelegate, RepresentationTypeTuple!(T[0])) || + hasUnsharedObjects!(T[0]) || + hasUnsharedAliasing!(T[1..$]); + } +} + +/// +@safe unittest +{ + struct S1 { int a; Object b; } + struct S2 { string a; } + struct S3 { int a; immutable Object b; } + static assert( hasUnsharedAliasing!S1); + static assert(!hasUnsharedAliasing!S2); + static assert(!hasUnsharedAliasing!S3); + + struct S4 { int a; shared Object b; } + struct S5 { char[] a; } + struct S6 { shared char[] b; } + struct S7 { float[3] vals; } + static assert(!hasUnsharedAliasing!S4); + static assert( hasUnsharedAliasing!S5); + static assert(!hasUnsharedAliasing!S6); + static assert(!hasUnsharedAliasing!S7); +} + +@safe unittest +{ + /* Issue 6642 */ + import std.typecons : Rebindable; + struct S8 { int a; Rebindable!(immutable Object) b; } + static assert(!hasUnsharedAliasing!S8); + + static assert( hasUnsharedAliasing!(uint[uint])); + + static assert( hasUnsharedAliasing!(void delegate())); + static assert( hasUnsharedAliasing!(void delegate() const)); + static assert(!hasUnsharedAliasing!(void delegate() immutable)); + static assert(!hasUnsharedAliasing!(void delegate() shared)); + static assert(!hasUnsharedAliasing!(void delegate() shared const)); +} + +@safe unittest +{ + import std.typecons : Rebindable; + static assert( hasUnsharedAliasing!(const(void delegate()))); + static assert( hasUnsharedAliasing!(const(void delegate() const))); + static assert(!hasUnsharedAliasing!(const(void delegate() immutable))); + static assert(!hasUnsharedAliasing!(const(void delegate() shared))); + static assert(!hasUnsharedAliasing!(const(void delegate() shared const))); + static assert(!hasUnsharedAliasing!(immutable(void delegate()))); + static assert(!hasUnsharedAliasing!(immutable(void delegate() const))); + static assert(!hasUnsharedAliasing!(immutable(void delegate() immutable))); + static assert(!hasUnsharedAliasing!(immutable(void delegate() shared))); + static assert(!hasUnsharedAliasing!(immutable(void delegate() shared const))); + static assert(!hasUnsharedAliasing!(shared(void delegate()))); + static assert(!hasUnsharedAliasing!(shared(void delegate() const))); + static assert(!hasUnsharedAliasing!(shared(void delegate() immutable))); + static assert(!hasUnsharedAliasing!(shared(void delegate() shared))); + static assert(!hasUnsharedAliasing!(shared(void delegate() shared const))); + static assert(!hasUnsharedAliasing!(shared(const(void delegate())))); + static assert(!hasUnsharedAliasing!(shared(const(void delegate() const)))); + static assert(!hasUnsharedAliasing!(shared(const(void delegate() immutable)))); + static assert(!hasUnsharedAliasing!(shared(const(void delegate() shared)))); + static assert(!hasUnsharedAliasing!(shared(const(void delegate() shared const)))); + static assert(!hasUnsharedAliasing!(void function())); + + interface I {} + static assert(hasUnsharedAliasing!I); + + static assert( hasUnsharedAliasing!(Rebindable!(const Object))); + static assert(!hasUnsharedAliasing!(Rebindable!(immutable Object))); + static assert(!hasUnsharedAliasing!(Rebindable!(shared Object))); + static assert( hasUnsharedAliasing!(Rebindable!Object)); + + /* Issue 6979 */ + static assert(!hasUnsharedAliasing!(int, shared(int)*)); + static assert( hasUnsharedAliasing!(int, int*)); + static assert( hasUnsharedAliasing!(int, const(int)[])); + static assert( hasUnsharedAliasing!(int, shared(int)*, Rebindable!Object)); + static assert(!hasUnsharedAliasing!(shared(int)*, Rebindable!(shared Object))); + static assert(!hasUnsharedAliasing!()); + + struct S9 + { + void delegate() shared a; + void delegate() immutable b; + void delegate() shared const c; + shared(void delegate()) d; + shared(void delegate() shared) e; + shared(void delegate() immutable) f; + shared(void delegate() shared const) g; + immutable(void delegate()) h; + immutable(void delegate() shared) i; + immutable(void delegate() immutable) j; + immutable(void delegate() shared const) k; + shared(const(void delegate())) l; + shared(const(void delegate() shared)) m; + shared(const(void delegate() immutable)) n; + shared(const(void delegate() shared const)) o; + } + struct S10 { typeof(S9.tupleof) a; void delegate() p; } + struct S11 { typeof(S9.tupleof) a; Object p; } + struct S12 { typeof(S9.tupleof) a; int* p; } + class S13 { typeof(S9.tupleof) a; } + class S14 { typeof(S9.tupleof) a; void delegate() p; } + class S15 { typeof(S9.tupleof) a; Object p; } + class S16 { typeof(S9.tupleof) a; int* p; } + static assert(!hasUnsharedAliasing!S9); + static assert(!hasUnsharedAliasing!(immutable(S9))); + static assert(!hasUnsharedAliasing!(shared(S9))); + static assert( hasUnsharedAliasing!S10); + static assert(!hasUnsharedAliasing!(immutable(S10))); + static assert(!hasUnsharedAliasing!(shared(S10))); + static assert( hasUnsharedAliasing!S11); + static assert(!hasUnsharedAliasing!(immutable(S11))); + static assert(!hasUnsharedAliasing!(shared(S11))); + static assert( hasUnsharedAliasing!S12); + static assert(!hasUnsharedAliasing!(immutable(S12))); + static assert(!hasUnsharedAliasing!(shared(S12))); + static assert( hasUnsharedAliasing!S13); + static assert(!hasUnsharedAliasing!(immutable(S13))); + static assert(!hasUnsharedAliasing!(shared(S13))); + static assert( hasUnsharedAliasing!S14); + static assert(!hasUnsharedAliasing!(immutable(S14))); + static assert(!hasUnsharedAliasing!(shared(S14))); + static assert( hasUnsharedAliasing!S15); + static assert(!hasUnsharedAliasing!(immutable(S15))); + static assert(!hasUnsharedAliasing!(shared(S15))); + static assert( hasUnsharedAliasing!S16); + static assert(!hasUnsharedAliasing!(immutable(S16))); + static assert(!hasUnsharedAliasing!(shared(S16))); + struct S17 {} + class S18 {} + interface S19 {} + union S20 {} + static assert(!hasUnsharedAliasing!S17); + static assert( hasUnsharedAliasing!S18); + static assert( hasUnsharedAliasing!S19); + static assert(!hasUnsharedAliasing!S20); +} + +/** + True if $(D S) or any type embedded directly in the representation of $(D S) + defines an elaborate copy constructor. Elaborate copy constructors are + introduced by defining $(D this(this)) for a $(D struct). + + Classes and unions never have elaborate copy constructors. + */ +template hasElaborateCopyConstructor(S) +{ + import std.meta : anySatisfy; + static if (isStaticArray!S && S.length) + { + enum bool hasElaborateCopyConstructor = hasElaborateCopyConstructor!(typeof(S.init[0])); + } + else static if (is(S == struct)) + { + enum hasElaborateCopyConstructor = hasMember!(S, "__postblit") + || anySatisfy!(.hasElaborateCopyConstructor, FieldTypeTuple!S); + } + else + { + enum bool hasElaborateCopyConstructor = false; + } +} + +/// +@safe unittest +{ + static assert(!hasElaborateCopyConstructor!int); + + static struct S1 { } + static struct S2 { this(this) {} } + static struct S3 { S2 field; } + static struct S4 { S3[1] field; } + static struct S5 { S3[] field; } + static struct S6 { S3[0] field; } + static struct S7 { @disable this(); S3 field; } + static assert(!hasElaborateCopyConstructor!S1); + static assert( hasElaborateCopyConstructor!S2); + static assert( hasElaborateCopyConstructor!(immutable S2)); + static assert( hasElaborateCopyConstructor!S3); + static assert( hasElaborateCopyConstructor!(S3[1])); + static assert(!hasElaborateCopyConstructor!(S3[0])); + static assert( hasElaborateCopyConstructor!S4); + static assert(!hasElaborateCopyConstructor!S5); + static assert(!hasElaborateCopyConstructor!S6); + static assert( hasElaborateCopyConstructor!S7); +} + +/** + True if $(D S) or any type directly embedded in the representation of $(D S) + defines an elaborate assignment. Elaborate assignments are introduced by + defining $(D opAssign(typeof(this))) or $(D opAssign(ref typeof(this))) + for a $(D struct) or when there is a compiler-generated $(D opAssign). + + A type $(D S) gets compiler-generated $(D opAssign) in case it has + an elaborate copy constructor or elaborate destructor. + + Classes and unions never have elaborate assignments. + + Note: Structs with (possibly nested) postblit operator(s) will have a + hidden yet elaborate compiler generated assignment operator (unless + explicitly disabled). + */ +template hasElaborateAssign(S) +{ + import std.meta : anySatisfy; + static if (isStaticArray!S && S.length) + { + enum bool hasElaborateAssign = hasElaborateAssign!(typeof(S.init[0])); + } + else static if (is(S == struct)) + { + enum hasElaborateAssign = is(typeof(S.init.opAssign(rvalueOf!S))) || + is(typeof(S.init.opAssign(lvalueOf!S))) || + anySatisfy!(.hasElaborateAssign, FieldTypeTuple!S); + } + else + { + enum bool hasElaborateAssign = false; + } +} + +/// +@safe unittest +{ + static assert(!hasElaborateAssign!int); + + static struct S { void opAssign(S) {} } + static assert( hasElaborateAssign!S); + static assert(!hasElaborateAssign!(const(S))); + + static struct S1 { void opAssign(ref S1) {} } + static struct S2 { void opAssign(int) {} } + static struct S3 { S s; } + static assert( hasElaborateAssign!S1); + static assert(!hasElaborateAssign!S2); + static assert( hasElaborateAssign!S3); + static assert( hasElaborateAssign!(S3[1])); + static assert(!hasElaborateAssign!(S3[0])); +} + +@safe unittest +{ + static struct S { void opAssign(S) {} } + static struct S4 + { + void opAssign(U)(U u) {} + @disable void opAssign(U)(ref U u); + } + static assert( hasElaborateAssign!S4); + + static struct S41 + { + void opAssign(U)(ref U u) {} + @disable void opAssign(U)(U u); + } + static assert( hasElaborateAssign!S41); + + static struct S5 { @disable this(); this(int n){ s = S(); } S s; } + static assert( hasElaborateAssign!S5); + + static struct S6 { this(this) {} } + static struct S7 { this(this) {} @disable void opAssign(S7); } + static struct S8 { this(this) {} @disable void opAssign(S8); void opAssign(int) {} } + static struct S9 { this(this) {} void opAssign(int) {} } + static struct S10 { ~this() { } } + static assert( hasElaborateAssign!S6); + static assert(!hasElaborateAssign!S7); + static assert(!hasElaborateAssign!S8); + static assert( hasElaborateAssign!S9); + static assert( hasElaborateAssign!S10); + static struct SS6 { S6 s; } + static struct SS7 { S7 s; } + static struct SS8 { S8 s; } + static struct SS9 { S9 s; } + static assert( hasElaborateAssign!SS6); + static assert(!hasElaborateAssign!SS7); + static assert(!hasElaborateAssign!SS8); + static assert( hasElaborateAssign!SS9); +} + +/** + True if $(D S) or any type directly embedded in the representation + of $(D S) defines an elaborate destructor. Elaborate destructors + are introduced by defining $(D ~this()) for a $(D + struct). + + Classes and unions never have elaborate destructors, even + though classes may define $(D ~this()). + */ +template hasElaborateDestructor(S) +{ + import std.meta : anySatisfy; + static if (isStaticArray!S && S.length) + { + enum bool hasElaborateDestructor = hasElaborateDestructor!(typeof(S.init[0])); + } + else static if (is(S == struct)) + { + enum hasElaborateDestructor = hasMember!(S, "__dtor") + || anySatisfy!(.hasElaborateDestructor, FieldTypeTuple!S); + } + else + { + enum bool hasElaborateDestructor = false; + } +} + +/// +@safe unittest +{ + static assert(!hasElaborateDestructor!int); + + static struct S1 { } + static struct S2 { ~this() {} } + static struct S3 { S2 field; } + static struct S4 { S3[1] field; } + static struct S5 { S3[] field; } + static struct S6 { S3[0] field; } + static struct S7 { @disable this(); S3 field; } + static assert(!hasElaborateDestructor!S1); + static assert( hasElaborateDestructor!S2); + static assert( hasElaborateDestructor!(immutable S2)); + static assert( hasElaborateDestructor!S3); + static assert( hasElaborateDestructor!(S3[1])); + static assert(!hasElaborateDestructor!(S3[0])); + static assert( hasElaborateDestructor!S4); + static assert(!hasElaborateDestructor!S5); + static assert(!hasElaborateDestructor!S6); + static assert( hasElaborateDestructor!S7); +} + +package alias Identity(alias A) = A; + +/** + Yields $(D true) if and only if $(D T) is an aggregate that defines + a symbol called $(D name). + */ +enum hasMember(T, string name) = __traits(hasMember, T, name); + +/// +@safe unittest +{ + static assert(!hasMember!(int, "blah")); + struct S1 { int blah; } + struct S2 { int blah(){ return 0; } } + class C1 { int blah; } + class C2 { int blah(){ return 0; } } + static assert(hasMember!(S1, "blah")); + static assert(hasMember!(S2, "blah")); + static assert(hasMember!(C1, "blah")); + static assert(hasMember!(C2, "blah")); +} + +@safe unittest +{ + // 8321 + struct S { + int x; + void f(){} + void t()(){} + template T(){} + } + struct R1(T) { + T t; + alias t this; + } + struct R2(T) { + T t; + @property ref inout(T) payload() inout { return t; } + alias t this; + } + static assert(hasMember!(S, "x")); + static assert(hasMember!(S, "f")); + static assert(hasMember!(S, "t")); + static assert(hasMember!(S, "T")); + static assert(hasMember!(R1!S, "x")); + static assert(hasMember!(R1!S, "f")); + static assert(hasMember!(R1!S, "t")); + static assert(hasMember!(R1!S, "T")); + static assert(hasMember!(R2!S, "x")); + static assert(hasMember!(R2!S, "f")); + static assert(hasMember!(R2!S, "t")); + static assert(hasMember!(R2!S, "T")); +} + +@safe unittest +{ + static struct S + { + void opDispatch(string n, A)(A dummy) {} + } + static assert(hasMember!(S, "foo")); +} + +/** + * Whether the symbol represented by the string, member, exists and is a static member of T. + * + * Params: + * T = Type containing symbol $(D member). + * member = Name of symbol to test that resides in $(D T). + * + * Returns: + * $(D true) iff $(D member) exists and is static. + */ +template hasStaticMember(T, string member) +{ + static if (__traits(hasMember, T, member)) + { + import std.meta : Alias; + alias sym = Alias!(__traits(getMember, T, member)); + + static if (__traits(getOverloads, T, member).length == 0) + enum bool hasStaticMember = __traits(compiles, &sym); + else + enum bool hasStaticMember = __traits(isStaticFunction, sym); + } + else + { + enum bool hasStaticMember = false; + } +} + +/// +@safe unittest +{ + static struct S + { + static void sf() {} + void f() {} + + static int si; + int i; + } + + static assert( hasStaticMember!(S, "sf")); + static assert(!hasStaticMember!(S, "f")); + + static assert( hasStaticMember!(S, "si")); + static assert(!hasStaticMember!(S, "i")); + + static assert(!hasStaticMember!(S, "hello")); +} + +@safe unittest +{ + static struct S + { + enum X = 10; + enum Y + { + i = 10 + } + struct S {} + class C {} + + static int sx = 0; + __gshared int gx = 0; + + Y y; + static Y sy; + + static void f(); + static void f2() pure nothrow @nogc @safe; + + shared void g(); + + static void function() fp; + __gshared void function() gfp; + void function() fpm; + + void delegate() dm; + static void delegate() sd; + + void m(); + void m2() const pure nothrow @nogc @safe; + + inout(int) iom() inout; + static inout(int) iosf(inout int x); + + @property int p(); + static @property int sp(); + } + + static class C + { + enum X = 10; + enum Y + { + i = 10 + } + struct S {} + class C {} + + static int sx = 0; + __gshared int gx = 0; + + Y y; + static Y sy; + + static void f(); + static void f2() pure nothrow @nogc @safe; + + shared void g() { } + + static void function() fp; + __gshared void function() gfp; + void function() fpm; + + void delegate() dm; + static void delegate() sd; + + void m() {} + final void m2() const pure nothrow @nogc @safe; + + inout(int) iom() inout { return 10; } + static inout(int) iosf(inout int x); + + @property int p() { return 10; } + static @property int sp(); + } + + static assert(!hasStaticMember!(S, "X")); + static assert(!hasStaticMember!(S, "Y")); + static assert(!hasStaticMember!(S, "Y.i")); + static assert(!hasStaticMember!(S, "S")); + static assert(!hasStaticMember!(S, "C")); + static assert( hasStaticMember!(S, "sx")); + static assert( hasStaticMember!(S, "gx")); + static assert(!hasStaticMember!(S, "y")); + static assert( hasStaticMember!(S, "sy")); + static assert( hasStaticMember!(S, "f")); + static assert( hasStaticMember!(S, "f2")); + static assert(!hasStaticMember!(S, "dm")); + static assert( hasStaticMember!(S, "sd")); + static assert(!hasStaticMember!(S, "g")); + static assert( hasStaticMember!(S, "fp")); + static assert( hasStaticMember!(S, "gfp")); + static assert(!hasStaticMember!(S, "fpm")); + static assert(!hasStaticMember!(S, "m")); + static assert(!hasStaticMember!(S, "m2")); + static assert(!hasStaticMember!(S, "iom")); + static assert( hasStaticMember!(S, "iosf")); + static assert(!hasStaticMember!(S, "p")); + static assert( hasStaticMember!(S, "sp")); + + static assert(!hasStaticMember!(C, "X")); + static assert(!hasStaticMember!(C, "Y")); + static assert(!hasStaticMember!(C, "Y.i")); + static assert(!hasStaticMember!(C, "S")); + static assert(!hasStaticMember!(C, "C")); + static assert( hasStaticMember!(C, "sx")); + static assert( hasStaticMember!(C, "gx")); + static assert(!hasStaticMember!(C, "y")); + static assert( hasStaticMember!(C, "sy")); + static assert( hasStaticMember!(C, "f")); + static assert( hasStaticMember!(C, "f2")); + static assert(!hasStaticMember!(S, "dm")); + static assert( hasStaticMember!(S, "sd")); + static assert(!hasStaticMember!(C, "g")); + static assert( hasStaticMember!(C, "fp")); + static assert( hasStaticMember!(C, "gfp")); + static assert(!hasStaticMember!(C, "fpm")); + static assert(!hasStaticMember!(C, "m")); + static assert(!hasStaticMember!(C, "m2")); + static assert(!hasStaticMember!(C, "iom")); + static assert( hasStaticMember!(C, "iosf")); + static assert(!hasStaticMember!(C, "p")); + static assert( hasStaticMember!(C, "sp")); +} + +/** +Retrieves the members of an enumerated type $(D enum E). + +Params: + E = An enumerated type. $(D E) may have duplicated values. + +Returns: + Static tuple composed of the members of the enumerated type $(D E). + The members are arranged in the same order as declared in $(D E). + +Note: + An enum can have multiple members which have the same value. If you want + to use EnumMembers to e.g. generate switch cases at compile-time, + you should use the $(REF NoDuplicates, std,meta) template to avoid + generating duplicate switch cases. + +Note: + Returned values are strictly typed with $(D E). Thus, the following code + does not work without the explicit cast: +-------------------- +enum E : int { a, b, c } +int[] abc = cast(int[]) [ EnumMembers!E ]; +-------------------- + Cast is not necessary if the type of the variable is inferred. See the + example below. + +Example: + Creating an array of enumerated values: +-------------------- +enum Sqrts : real +{ + one = 1, + two = 1.41421, + three = 1.73205, +} +auto sqrts = [ EnumMembers!Sqrts ]; +assert(sqrts == [ Sqrts.one, Sqrts.two, Sqrts.three ]); +-------------------- + + A generic function $(D rank(v)) in the following example uses this + template for finding a member $(D e) in an enumerated type $(D E). +-------------------- +// Returns i if e is the i-th enumerator of E. +size_t rank(E)(E e) + if (is(E == enum)) +{ + foreach (i, member; EnumMembers!E) + { + if (e == member) + return i; + } + assert(0, "Not an enum member"); +} + +enum Mode +{ + read = 1, + write = 2, + map = 4, +} +assert(rank(Mode.read ) == 0); +assert(rank(Mode.write) == 1); +assert(rank(Mode.map ) == 2); +-------------------- + */ +template EnumMembers(E) + if (is(E == enum)) +{ + import std.meta : AliasSeq; + // Supply the specified identifier to an constant value. + template WithIdentifier(string ident) + { + static if (ident == "Symbolize") + { + template Symbolize(alias value) + { + enum Symbolize = value; + } + } + else + { + mixin("template Symbolize(alias "~ ident ~")" + ~"{" + ~"alias Symbolize = "~ ident ~";" + ~"}"); + } + } + + template EnumSpecificMembers(names...) + { + static if (names.length == 1) + { + alias EnumSpecificMembers = AliasSeq!(WithIdentifier!(names[0]) + .Symbolize!(__traits(getMember, E, names[0]))); + } + else static if (names.length > 0) + { + alias EnumSpecificMembers = + AliasSeq!( + WithIdentifier!(names[0]) + .Symbolize!(__traits(getMember, E, names[0])), + EnumSpecificMembers!(names[1 .. $/2]), + EnumSpecificMembers!(names[$/2..$]) + ); + } + else + { + alias EnumSpecificMembers = AliasSeq!(); + } + } + + alias EnumMembers = EnumSpecificMembers!(__traits(allMembers, E)); +} + +@safe unittest +{ + enum A { a } + static assert([ EnumMembers!A ] == [ A.a ]); + enum B { a, b, c, d, e } + static assert([ EnumMembers!B ] == [ B.a, B.b, B.c, B.d, B.e ]); +} + +@safe unittest // typed enums +{ + enum A : string { a = "alpha", b = "beta" } + static assert([ EnumMembers!A ] == [ A.a, A.b ]); + + static struct S + { + int value; + int opCmp(S rhs) const nothrow { return value - rhs.value; } + } + enum B : S { a = S(1), b = S(2), c = S(3) } + static assert([ EnumMembers!B ] == [ B.a, B.b, B.c ]); +} + +@safe unittest // duplicated values +{ + enum A + { + a = 0, b = 0, + c = 1, d = 1, e + } + static assert([ EnumMembers!A ] == [ A.a, A.b, A.c, A.d, A.e ]); +} + +@safe unittest // Bugzilla 14561: huge enums +{ + string genEnum() + { + string result = "enum TLAs {"; + foreach (c0; '0'..'2'+1) + foreach (c1; '0'..'9'+1) + foreach (c2; '0'..'9'+1) + foreach (c3; '0'..'9'+1) + { + result ~= '_'; + result ~= c0; + result ~= c1; + result ~= c2; + result ~= c3; + result ~= ','; + } + result ~= '}'; + return result; + } + mixin(genEnum); + static assert(EnumMembers!TLAs[0] == TLAs._0000); + static assert(EnumMembers!TLAs[$-1] == TLAs._2999); +} + +@safe unittest +{ + enum E { member, a = 0, b = 0 } + static assert(__traits(identifier, EnumMembers!E[0]) == "member"); + static assert(__traits(identifier, EnumMembers!E[1]) == "a"); + static assert(__traits(identifier, EnumMembers!E[2]) == "b"); +} + + +//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// +// Classes and Interfaces +//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// + +/*** + * Get a $(D_PARAM AliasSeq) of the base class and base interfaces of + * this class or interface. $(D_PARAM BaseTypeTuple!Object) returns + * the empty type tuple. + */ +template BaseTypeTuple(A) +{ + static if (is(A P == super)) + alias BaseTypeTuple = P; + else + static assert(0, "argument is not a class or interface"); +} + +/// +@safe unittest +{ + interface I1 { } + interface I2 { } + interface I12 : I1, I2 { } + static assert(is(BaseTypeTuple!I12 == AliasSeq!(I1, I2))); + + interface I3 : I1 { } + interface I123 : I1, I2, I3 { } + static assert(is(BaseTypeTuple!I123 == AliasSeq!(I1, I2, I3))); +} + +@safe unittest +{ + interface I1 { } + interface I2 { } + class A { } + class C : A, I1, I2 { } + + alias TL = BaseTypeTuple!C; + assert(TL.length == 3); + assert(is (TL[0] == A)); + assert(is (TL[1] == I1)); + assert(is (TL[2] == I2)); + + assert(BaseTypeTuple!Object.length == 0); +} + +/** + * Get a $(D_PARAM AliasSeq) of $(I all) base classes of this class, + * in decreasing order. Interfaces are not included. $(D_PARAM + * BaseClassesTuple!Object) yields the empty type tuple. + */ +template BaseClassesTuple(T) + if (is(T == class)) +{ + static if (is(T == Object)) + { + alias BaseClassesTuple = AliasSeq!(); + } + else static if (is(BaseTypeTuple!T[0] == Object)) + { + alias BaseClassesTuple = AliasSeq!Object; + } + else + { + alias BaseClassesTuple = + AliasSeq!(BaseTypeTuple!T[0], + BaseClassesTuple!(BaseTypeTuple!T[0])); + } +} + +/// +@safe unittest +{ + class C1 { } + class C2 : C1 { } + class C3 : C2 { } + static assert(!BaseClassesTuple!Object.length); + static assert(is(BaseClassesTuple!C1 == AliasSeq!(Object))); + static assert(is(BaseClassesTuple!C2 == AliasSeq!(C1, Object))); + static assert(is(BaseClassesTuple!C3 == AliasSeq!(C2, C1, Object))); +} + +@safe unittest +{ + struct S { } + static assert(!__traits(compiles, BaseClassesTuple!S)); + interface I { } + static assert(!__traits(compiles, BaseClassesTuple!I)); + class C4 : I { } + class C5 : C4, I { } + static assert(is(BaseClassesTuple!C5 == AliasSeq!(C4, Object))); +} + +/** + * Get a $(D_PARAM AliasSeq) of $(I all) interfaces directly or + * indirectly inherited by this class or interface. Interfaces do not + * repeat if multiply implemented. $(D_PARAM InterfacesTuple!Object) + * yields the empty type tuple. + */ +template InterfacesTuple(T) +{ + import std.meta : NoDuplicates; + template Flatten(H, T...) + { + static if (T.length) + { + alias Flatten = AliasSeq!(Flatten!H, Flatten!T); + } + else + { + static if (is(H == interface)) + alias Flatten = AliasSeq!(H, InterfacesTuple!H); + else + alias Flatten = InterfacesTuple!H; + } + } + + static if (is(T S == super) && S.length) + alias InterfacesTuple = NoDuplicates!(Flatten!S); + else + alias InterfacesTuple = AliasSeq!(); +} + +@safe unittest +{ + // doc example + interface I1 {} + interface I2 {} + class A : I1, I2 { } + class B : A, I1 { } + class C : B { } + alias TL = InterfacesTuple!C; + static assert(is(TL[0] == I1) && is(TL[1] == I2)); +} + +@safe unittest +{ + interface Iaa {} + interface Iab {} + interface Iba {} + interface Ibb {} + interface Ia : Iaa, Iab {} + interface Ib : Iba, Ibb {} + interface I : Ia, Ib {} + interface J {} + class B2 : J {} + class C2 : B2, Ia, Ib {} + static assert(is(InterfacesTuple!I == + AliasSeq!(Ia, Iaa, Iab, Ib, Iba, Ibb))); + static assert(is(InterfacesTuple!C2 == + AliasSeq!(J, Ia, Iaa, Iab, Ib, Iba, Ibb))); + +} + +/** + * Get a $(D_PARAM AliasSeq) of $(I all) base classes of $(D_PARAM + * T), in decreasing order, followed by $(D_PARAM T)'s + * interfaces. $(D_PARAM TransitiveBaseTypeTuple!Object) yields the + * empty type tuple. + */ +template TransitiveBaseTypeTuple(T) +{ + static if (is(T == Object)) + alias TransitiveBaseTypeTuple = AliasSeq!(); + else + alias TransitiveBaseTypeTuple = + AliasSeq!(BaseClassesTuple!T, InterfacesTuple!T); +} + +/// +@safe unittest +{ + interface J1 {} + interface J2 {} + class B1 {} + class B2 : B1, J1, J2 {} + class B3 : B2, J1 {} + alias TL = TransitiveBaseTypeTuple!B3; + assert(TL.length == 5); + assert(is (TL[0] == B2)); + assert(is (TL[1] == B1)); + assert(is (TL[2] == Object)); + assert(is (TL[3] == J1)); + assert(is (TL[4] == J2)); + + assert(TransitiveBaseTypeTuple!Object.length == 0); +} + + +/** +Returns a tuple of non-static functions with the name $(D name) declared in the +class or interface $(D C). Covariant duplicates are shrunk into the most +derived one. + */ +template MemberFunctionsTuple(C, string name) + if (is(C == class) || is(C == interface)) +{ + static if (__traits(hasMember, C, name)) + { + /* + * First, collect all overloads in the class hierarchy. + */ + template CollectOverloads(Node) + { + static if (__traits(hasMember, Node, name) && __traits(compiles, __traits(getMember, Node, name))) + { + // Get all overloads in sight (not hidden). + alias inSight = AliasSeq!(__traits(getVirtualFunctions, Node, name)); + + // And collect all overloads in ancestor classes to reveal hidden + // methods. The result may contain duplicates. + template walkThru(Parents...) + { + static if (Parents.length > 0) + alias walkThru = AliasSeq!( + CollectOverloads!(Parents[0]), + walkThru!(Parents[1 .. $]) + ); + else + alias walkThru = AliasSeq!(); + } + + static if (is(Node Parents == super)) + alias CollectOverloads = AliasSeq!(inSight, walkThru!Parents); + else + alias CollectOverloads = AliasSeq!inSight; + } + else + alias CollectOverloads = AliasSeq!(); // no overloads in this hierarchy + } + + // duplicates in this tuple will be removed by shrink() + alias overloads = CollectOverloads!C; + + // shrinkOne!args[0] = the most derived one in the covariant siblings of target + // shrinkOne!args[1..$] = non-covariant others + template shrinkOne(/+ alias target, rest... +/ args...) + { + import std.meta : AliasSeq; + alias target = args[0 .. 1]; // prevent property functions from being evaluated + alias rest = args[1 .. $]; + + static if (rest.length > 0) + { + alias Target = FunctionTypeOf!target; + alias Rest0 = FunctionTypeOf!(rest[0]); + + static if (isCovariantWith!(Target, Rest0) && isCovariantWith!(Rest0, Target)) + { + // One of these overrides the other. Choose the one from the most derived parent. + static if (is(AliasSeq!(__traits(parent, target))[0] : AliasSeq!(__traits(parent, rest[0]))[0])) + alias shrinkOne = shrinkOne!(target, rest[1 .. $]); + else + alias shrinkOne = shrinkOne!(rest[0], rest[1 .. $]); + } + else static if (isCovariantWith!(Target, Rest0)) + // target overrides rest[0] -- erase rest[0]. + alias shrinkOne = shrinkOne!(target, rest[1 .. $]); + else static if (isCovariantWith!(Rest0, Target)) + // rest[0] overrides target -- erase target. + alias shrinkOne = shrinkOne!(rest[0], rest[1 .. $]); + else + // target and rest[0] are distinct. + alias shrinkOne = AliasSeq!( + shrinkOne!(target, rest[1 .. $]), + rest[0] // keep + ); + } + else + alias shrinkOne = AliasSeq!target; // done + } + + /* + * Now shrink covariant overloads into one. + */ + template shrink(overloads...) + { + static if (overloads.length > 0) + { + alias temp = shrinkOne!overloads; + alias shrink = AliasSeq!(temp[0], shrink!(temp[1 .. $])); + } + else + alias shrink = AliasSeq!(); // done + } + + // done. + alias MemberFunctionsTuple = shrink!overloads; + } + else + alias MemberFunctionsTuple = AliasSeq!(); +} + +/// +@safe unittest +{ + interface I { I foo(); } + class B + { + real foo(real v) { return v; } + } + class C : B, I + { + override C foo() { return this; } // covariant overriding of I.foo() + } + alias foos = MemberFunctionsTuple!(C, "foo"); + static assert(foos.length == 2); + static assert(__traits(isSame, foos[0], C.foo)); + static assert(__traits(isSame, foos[1], B.foo)); +} + +@safe unittest // Issue 15920 +{ + import std.meta : AliasSeq; + class A + { + void f(){} + void f(int){} + } + class B : A + { + override void f(){} + override void f(int){} + } + alias fs = MemberFunctionsTuple!(B, "f"); + alias bfs = AliasSeq!(__traits(getOverloads, B, "f")); + assert(__traits(isSame, fs[0], bfs[0]) || __traits(isSame, fs[0], bfs[1])); + assert(__traits(isSame, fs[1], bfs[0]) || __traits(isSame, fs[1], bfs[1])); +} + +@safe unittest +{ + interface I { I test(); } + interface J : I { J test(); } + interface K { K test(int); } + class B : I, K + { + K test(int) { return this; } + B test() { return this; } + static void test(string) { } + } + class C : B, J + { + override C test() { return this; } + } + alias test =MemberFunctionsTuple!(C, "test"); + static assert(test.length == 2); + static assert(is(FunctionTypeOf!(test[0]) == FunctionTypeOf!(C.test))); + static assert(is(FunctionTypeOf!(test[1]) == FunctionTypeOf!(K.test))); + alias noexist = MemberFunctionsTuple!(C, "noexist"); + static assert(noexist.length == 0); + + interface L { int prop() @property; } + alias prop = MemberFunctionsTuple!(L, "prop"); + static assert(prop.length == 1); + + interface Test_I + { + void foo(); + void foo(int); + void foo(int, int); + } + interface Test : Test_I {} + alias Test_foo = MemberFunctionsTuple!(Test, "foo"); + static assert(Test_foo.length == 3); + static assert(is(typeof(&Test_foo[0]) == void function())); + static assert(is(typeof(&Test_foo[2]) == void function(int))); + static assert(is(typeof(&Test_foo[1]) == void function(int, int))); +} + + +/** +Returns an alias to the template that $(D T) is an instance of. + */ +template TemplateOf(alias T : Base!Args, alias Base, Args...) +{ + alias TemplateOf = Base; +} + +/// ditto +template TemplateOf(T : Base!Args, alias Base, Args...) +{ + alias TemplateOf = Base; +} + +/// +@safe unittest +{ + struct Foo(T, U) {} + static assert(__traits(isSame, TemplateOf!(Foo!(int, real)), Foo)); +} + +@safe unittest +{ + template Foo1(A) {} + template Foo2(A, B) {} + template Foo3(alias A) {} + template Foo4(string A) {} + struct Foo5(A) {} + struct Foo6(A, B) {} + struct Foo7(alias A) {} + template Foo8(A) { template Foo9(B) {} } + template Foo10() {} + + static assert(__traits(isSame, TemplateOf!(Foo1!(int)), Foo1)); + static assert(__traits(isSame, TemplateOf!(Foo2!(int, int)), Foo2)); + static assert(__traits(isSame, TemplateOf!(Foo3!(123)), Foo3)); + static assert(__traits(isSame, TemplateOf!(Foo4!("123")), Foo4)); + static assert(__traits(isSame, TemplateOf!(Foo5!(int)), Foo5)); + static assert(__traits(isSame, TemplateOf!(Foo6!(int, int)), Foo6)); + static assert(__traits(isSame, TemplateOf!(Foo7!(123)), Foo7)); + static assert(__traits(isSame, TemplateOf!(Foo8!(int).Foo9!(real)), Foo8!(int).Foo9)); + static assert(__traits(isSame, TemplateOf!(Foo10!()), Foo10)); +} + + +/** +Returns a $(D AliasSeq) of the template arguments used to instantiate $(D T). + */ +template TemplateArgsOf(alias T : Base!Args, alias Base, Args...) +{ + alias TemplateArgsOf = Args; +} + +/// ditto +template TemplateArgsOf(T : Base!Args, alias Base, Args...) +{ + alias TemplateArgsOf = Args; +} + +/// +@safe unittest +{ + struct Foo(T, U) {} + static assert(is(TemplateArgsOf!(Foo!(int, real)) == AliasSeq!(int, real))); +} + +@safe unittest +{ + template Foo1(A) {} + template Foo2(A, B) {} + template Foo3(alias A) {} + template Foo4(string A) {} + struct Foo5(A) {} + struct Foo6(A, B) {} + struct Foo7(alias A) {} + template Foo8(A) { template Foo9(B) {} } + template Foo10() {} + + enum x = 123; + enum y = "123"; + static assert(is(TemplateArgsOf!(Foo1!(int)) == AliasSeq!(int))); + static assert(is(TemplateArgsOf!(Foo2!(int, int)) == AliasSeq!(int, int))); + static assert(__traits(isSame, TemplateArgsOf!(Foo3!(x)), AliasSeq!(x))); + static assert(TemplateArgsOf!(Foo4!(y)) == AliasSeq!(y)); + static assert(is(TemplateArgsOf!(Foo5!(int)) == AliasSeq!(int))); + static assert(is(TemplateArgsOf!(Foo6!(int, int)) == AliasSeq!(int, int))); + static assert(__traits(isSame, TemplateArgsOf!(Foo7!(x)), AliasSeq!(x))); + static assert(is(TemplateArgsOf!(Foo8!(int).Foo9!(real)) == AliasSeq!(real))); + static assert(is(TemplateArgsOf!(Foo10!()) == AliasSeq!())); +} + + +private template maxAlignment(U...) if (isTypeTuple!U) +{ + import std.meta : staticMap; + static if (U.length == 0) + static assert(0); + else static if (U.length == 1) + enum maxAlignment = U[0].alignof; + else + { + import std.algorithm.comparison : max; + enum maxAlignment = max(staticMap!(.maxAlignment, U)); + } +} + + +/** +Returns class instance alignment. + */ +template classInstanceAlignment(T) if (is(T == class)) +{ + alias classInstanceAlignment = maxAlignment!(void*, typeof(T.tupleof)); +} + +/// +@safe unittest +{ + class A { byte b; } + class B { long l; } + + // As class instance always has a hidden pointer + static assert(classInstanceAlignment!A == (void*).alignof); + static assert(classInstanceAlignment!B == long.alignof); +} + + +//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// +// Type Conversion +//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// + +/** +Get the type that all types can be implicitly converted to. Useful +e.g. in figuring out an array type from a bunch of initializing +values. Returns $(D_PARAM void) if passed an empty list, or if the +types have no common type. + */ +template CommonType(T...) +{ + static if (!T.length) + { + alias CommonType = void; + } + else static if (T.length == 1) + { + static if (is(typeof(T[0]))) + { + alias CommonType = typeof(T[0]); + } + else + { + alias CommonType = T[0]; + } + } + else static if (is(typeof(true ? T[0].init : T[1].init) U)) + { + alias CommonType = CommonType!(U, T[2 .. $]); + } + else + alias CommonType = void; +} + +/// +@safe unittest +{ + alias X = CommonType!(int, long, short); + assert(is(X == long)); + alias Y = CommonType!(int, char[], short); + assert(is(Y == void)); +} +@safe unittest +{ + static assert(is(CommonType!(3) == int)); + static assert(is(CommonType!(double, 4, float) == double)); + static assert(is(CommonType!(string, char[]) == const(char)[])); + static assert(is(CommonType!(3, 3U) == uint)); +} + + +/** + * Returns a tuple with all possible target types of an implicit + * conversion of a value of type $(D_PARAM T). + * + * Important note: + * + * The possible targets are computed more conservatively than the D + * 2.005 compiler does, eliminating all dangerous conversions. For + * example, $(D_PARAM ImplicitConversionTargets!double) does not + * include $(D_PARAM float). + */ +template ImplicitConversionTargets(T) +{ + static if (is(T == bool)) + alias ImplicitConversionTargets = + AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, CentTypeList, + float, double, real, char, wchar, dchar); + else static if (is(T == byte)) + alias ImplicitConversionTargets = + AliasSeq!(short, ushort, int, uint, long, ulong, CentTypeList, + float, double, real, char, wchar, dchar); + else static if (is(T == ubyte)) + alias ImplicitConversionTargets = + AliasSeq!(short, ushort, int, uint, long, ulong, CentTypeList, + float, double, real, char, wchar, dchar); + else static if (is(T == short)) + alias ImplicitConversionTargets = + AliasSeq!(int, uint, long, ulong, CentTypeList, float, double, real); + else static if (is(T == ushort)) + alias ImplicitConversionTargets = + AliasSeq!(int, uint, long, ulong, CentTypeList, float, double, real); + else static if (is(T == int)) + alias ImplicitConversionTargets = + AliasSeq!(long, ulong, CentTypeList, float, double, real); + else static if (is(T == uint)) + alias ImplicitConversionTargets = + AliasSeq!(long, ulong, CentTypeList, float, double, real); + else static if (is(T == long)) + alias ImplicitConversionTargets = AliasSeq!(float, double, real); + else static if (is(T == ulong)) + alias ImplicitConversionTargets = AliasSeq!(float, double, real); + else static if (is(cent) && is(T == cent)) + alias ImplicitConversionTargets = AliasSeq!(float, double, real); + else static if (is(ucent) && is(T == ucent)) + alias ImplicitConversionTargets = AliasSeq!(float, double, real); + else static if (is(T == float)) + alias ImplicitConversionTargets = AliasSeq!(double, real); + else static if (is(T == double)) + alias ImplicitConversionTargets = AliasSeq!real; + else static if (is(T == char)) + alias ImplicitConversionTargets = + AliasSeq!(wchar, dchar, byte, ubyte, short, ushort, + int, uint, long, ulong, CentTypeList, float, double, real); + else static if (is(T == wchar)) + alias ImplicitConversionTargets = + AliasSeq!(dchar, short, ushort, int, uint, long, ulong, CentTypeList, + float, double, real); + else static if (is(T == dchar)) + alias ImplicitConversionTargets = + AliasSeq!(int, uint, long, ulong, CentTypeList, float, double, real); + else static if (is(T : typeof(null))) + alias ImplicitConversionTargets = AliasSeq!(typeof(null)); + else static if (is(T : Object)) + alias ImplicitConversionTargets = TransitiveBaseTypeTuple!(T); + else static if (isDynamicArray!T && !is(typeof(T.init[0]) == const)) + alias ImplicitConversionTargets = + AliasSeq!(const(Unqual!(typeof(T.init[0])))[]); + else static if (is(T : void*)) + alias ImplicitConversionTargets = AliasSeq!(void*); + else + alias ImplicitConversionTargets = AliasSeq!(); +} + +@safe unittest +{ + static assert(is(ImplicitConversionTargets!(double)[0] == real)); + static assert(is(ImplicitConversionTargets!(string)[0] == const(char)[])); +} + +/** +Is $(D From) implicitly convertible to $(D To)? + */ +template isImplicitlyConvertible(From, To) +{ + enum bool isImplicitlyConvertible = is(typeof({ + void fun(ref From v) + { + void gun(To) {} + gun(v); + } + })); +} + +/// +@safe unittest +{ + static assert( isImplicitlyConvertible!(immutable(char), char)); + static assert( isImplicitlyConvertible!(const(char), char)); + static assert( isImplicitlyConvertible!(char, wchar)); + static assert(!isImplicitlyConvertible!(wchar, char)); + + static assert(!isImplicitlyConvertible!(const(ushort), ubyte)); + static assert(!isImplicitlyConvertible!(const(uint), ubyte)); + static assert(!isImplicitlyConvertible!(const(ulong), ubyte)); + + static assert(!isImplicitlyConvertible!(const(char)[], string)); + static assert( isImplicitlyConvertible!(string, const(char)[])); +} + +/** +Returns $(D true) iff a value of type $(D Rhs) can be assigned to a variable of +type $(D Lhs). + +$(D isAssignable) returns whether both an lvalue and rvalue can be assigned. + +If you omit $(D Rhs), $(D isAssignable) will check identity assignable of $(D Lhs). +*/ +enum isAssignable(Lhs, Rhs = Lhs) = isRvalueAssignable!(Lhs, Rhs) && isLvalueAssignable!(Lhs, Rhs); + +/// +@safe unittest +{ + static assert( isAssignable!(long, int)); + static assert(!isAssignable!(int, long)); + static assert( isAssignable!(const(char)[], string)); + static assert(!isAssignable!(string, char[])); + + // int is assignable to int + static assert( isAssignable!int); + + // immutable int is not assignable to immutable int + static assert(!isAssignable!(immutable int)); +} + +// ditto +private enum isRvalueAssignable(Lhs, Rhs = Lhs) = __traits(compiles, lvalueOf!Lhs = rvalueOf!Rhs); + +// ditto +private enum isLvalueAssignable(Lhs, Rhs = Lhs) = __traits(compiles, lvalueOf!Lhs = lvalueOf!Rhs); + +@safe unittest +{ + static assert(!isAssignable!(immutable int, int)); + static assert( isAssignable!(int, immutable int)); + + static assert(!isAssignable!(inout int, int)); + static assert( isAssignable!(int, inout int)); + static assert(!isAssignable!(inout int)); + + static assert( isAssignable!(shared int, int)); + static assert( isAssignable!(int, shared int)); + static assert( isAssignable!(shared int)); + + static assert( isAssignable!(void[1], void[1])); + + struct S { @disable this(); this(int n){} } + static assert( isAssignable!(S, S)); + + struct S2 { this(int n){} } + static assert( isAssignable!(S2, S2)); + static assert(!isAssignable!(S2, int)); + + struct S3 { @disable void opAssign(); } + static assert( isAssignable!(S3, S3)); + + struct S3X { @disable void opAssign(S3X); } + static assert(!isAssignable!(S3X, S3X)); + + struct S4 { void opAssign(int); } + static assert( isAssignable!(S4, S4)); + static assert( isAssignable!(S4, int)); + static assert( isAssignable!(S4, immutable int)); + + struct S5 { @disable this(); @disable this(this); } + struct S6 { void opAssign(in ref S5); } + static assert(!isAssignable!(S6, S5)); + static assert(!isRvalueAssignable!(S6, S5)); + static assert( isLvalueAssignable!(S6, S5)); + static assert( isLvalueAssignable!(S6, immutable S5)); +} + + +// Equivalent with TypeStruct::isAssignable in compiler code. +package template isBlitAssignable(T) +{ + static if (is(OriginalType!T U) && !is(T == U)) + { + enum isBlitAssignable = isBlitAssignable!U; + } + else static if (isStaticArray!T && is(T == E[n], E, size_t n)) + // Workaround for issue 11499 : isStaticArray!T should not be necessary. + { + enum isBlitAssignable = isBlitAssignable!E; + } + else static if (is(T == struct) || is(T == union)) + { + enum isBlitAssignable = isMutable!T && + { + size_t offset = 0; + bool assignable = true; + foreach (i, F; FieldTypeTuple!T) + { + static if (i == 0) + { + } + else + { + if (T.tupleof[i].offsetof == offset) + { + if (assignable) + continue; + } + else + { + if (!assignable) + return false; + } + } + assignable = isBlitAssignable!(typeof(T.tupleof[i])); + offset = T.tupleof[i].offsetof; + } + return assignable; + }(); + } + else + enum isBlitAssignable = isMutable!T; +} + +@safe unittest +{ + static assert( isBlitAssignable!int); + static assert(!isBlitAssignable!(const int)); + + class C{ const int i; } + static assert( isBlitAssignable!C); + + struct S1{ int i; } + struct S2{ const int i; } + static assert( isBlitAssignable!S1); + static assert(!isBlitAssignable!S2); + + struct S3X { union { int x; int y; } } + struct S3Y { union { int x; const int y; } } + struct S3Z { union { const int x; const int y; } } + static assert( isBlitAssignable!(S3X)); + static assert( isBlitAssignable!(S3Y)); + static assert(!isBlitAssignable!(S3Z)); + static assert(!isBlitAssignable!(const S3X)); + static assert(!isBlitAssignable!(inout S3Y)); + static assert(!isBlitAssignable!(immutable S3Z)); + static assert( isBlitAssignable!(S3X[3])); + static assert( isBlitAssignable!(S3Y[3])); + static assert(!isBlitAssignable!(S3Z[3])); + enum ES3X : S3X { a = S3X() } + enum ES3Y : S3Y { a = S3Y() } + enum ES3Z : S3Z { a = S3Z() } + static assert( isBlitAssignable!(ES3X)); + static assert( isBlitAssignable!(ES3Y)); + static assert(!isBlitAssignable!(ES3Z)); + static assert(!isBlitAssignable!(const ES3X)); + static assert(!isBlitAssignable!(inout ES3Y)); + static assert(!isBlitAssignable!(immutable ES3Z)); + static assert( isBlitAssignable!(ES3X[3])); + static assert( isBlitAssignable!(ES3Y[3])); + static assert(!isBlitAssignable!(ES3Z[3])); + + union U1X { int x; int y; } + union U1Y { int x; const int y; } + union U1Z { const int x; const int y; } + static assert( isBlitAssignable!(U1X)); + static assert( isBlitAssignable!(U1Y)); + static assert(!isBlitAssignable!(U1Z)); + static assert(!isBlitAssignable!(const U1X)); + static assert(!isBlitAssignable!(inout U1Y)); + static assert(!isBlitAssignable!(immutable U1Z)); + static assert( isBlitAssignable!(U1X[3])); + static assert( isBlitAssignable!(U1Y[3])); + static assert(!isBlitAssignable!(U1Z[3])); + enum EU1X : U1X { a = U1X() } + enum EU1Y : U1Y { a = U1Y() } + enum EU1Z : U1Z { a = U1Z() } + static assert( isBlitAssignable!(EU1X)); + static assert( isBlitAssignable!(EU1Y)); + static assert(!isBlitAssignable!(EU1Z)); + static assert(!isBlitAssignable!(const EU1X)); + static assert(!isBlitAssignable!(inout EU1Y)); + static assert(!isBlitAssignable!(immutable EU1Z)); + static assert( isBlitAssignable!(EU1X[3])); + static assert( isBlitAssignable!(EU1Y[3])); + static assert(!isBlitAssignable!(EU1Z[3])); + + struct SA + { + @property int[3] foo() { return [1,2,3]; } + alias foo this; + const int x; // SA is not blit assignable + } + static assert(!isStaticArray!SA); + static assert(!isBlitAssignable!(SA[3])); +} + + +/* +Works like $(D isImplicitlyConvertible), except this cares only about storage +classes of the arguments. + */ +private template isStorageClassImplicitlyConvertible(From, To) +{ + alias Pointify(T) = void*; + + enum isStorageClassImplicitlyConvertible = isImplicitlyConvertible!( + ModifyTypePreservingTQ!(Pointify, From), + ModifyTypePreservingTQ!(Pointify, To) ); +} + +@safe unittest +{ + static assert( isStorageClassImplicitlyConvertible!( int, const int)); + static assert( isStorageClassImplicitlyConvertible!(immutable int, const int)); + + static assert(!isStorageClassImplicitlyConvertible!(const int, int)); + static assert(!isStorageClassImplicitlyConvertible!(const int, immutable int)); + static assert(!isStorageClassImplicitlyConvertible!(int, shared int)); + static assert(!isStorageClassImplicitlyConvertible!(shared int, int)); +} + + +/** +Determines whether the function type $(D F) is covariant with $(D G), i.e., +functions of the type $(D F) can override ones of the type $(D G). + */ +template isCovariantWith(F, G) + if (is(F == function) && is(G == function)) +{ + static if (is(F : G)) + enum isCovariantWith = true; + else + { + alias Upr = F; + alias Lwr = G; + + /* + * Check for calling convention: require exact match. + */ + template checkLinkage() + { + enum ok = functionLinkage!Upr == functionLinkage!Lwr; + } + /* + * Check for variadic parameter: require exact match. + */ + template checkVariadicity() + { + enum ok = variadicFunctionStyle!Upr == variadicFunctionStyle!Lwr; + } + /* + * Check for function storage class: + * - overrider can have narrower storage class than base + */ + template checkSTC() + { + // Note the order of arguments. The convertion order Lwr -> Upr is + // correct since Upr should be semantically 'narrower' than Lwr. + enum ok = isStorageClassImplicitlyConvertible!(Lwr, Upr); + } + /* + * Check for function attributes: + * - require exact match for ref and @property + * - overrider can add pure and nothrow, but can't remove them + * - @safe and @trusted are covariant with each other, unremovable + */ + template checkAttributes() + { + alias FA = FunctionAttribute; + enum uprAtts = functionAttributes!Upr; + enum lwrAtts = functionAttributes!Lwr; + // + enum wantExact = FA.ref_ | FA.property; + enum safety = FA.safe | FA.trusted; + enum ok = + ( (uprAtts & wantExact) == (lwrAtts & wantExact)) && + ( (uprAtts & FA.pure_ ) >= (lwrAtts & FA.pure_ )) && + ( (uprAtts & FA.nothrow_) >= (lwrAtts & FA.nothrow_)) && + (!!(uprAtts & safety ) >= !!(lwrAtts & safety )) ; + } + /* + * Check for return type: usual implicit convertion. + */ + template checkReturnType() + { + enum ok = is(ReturnType!Upr : ReturnType!Lwr); + } + /* + * Check for parameters: + * - require exact match for types (cf. bugzilla 3075) + * - require exact match for in, out, ref and lazy + * - overrider can add scope, but can't remove + */ + template checkParameters() + { + alias STC = ParameterStorageClass; + alias UprParams = Parameters!Upr; + alias LwrParams = Parameters!Lwr; + alias UprPSTCs = ParameterStorageClassTuple!Upr; + alias LwrPSTCs = ParameterStorageClassTuple!Lwr; + // + template checkNext(size_t i) + { + static if (i < UprParams.length) + { + enum uprStc = UprPSTCs[i]; + enum lwrStc = LwrPSTCs[i]; + // + enum wantExact = STC.out_ | STC.ref_ | STC.lazy_ | STC.return_; + enum ok = + ((uprStc & wantExact ) == (lwrStc & wantExact )) && + ((uprStc & STC.scope_) >= (lwrStc & STC.scope_)) && + checkNext!(i + 1).ok; + } + else + enum ok = true; // done + } + static if (UprParams.length == LwrParams.length) + enum ok = is(UprParams == LwrParams) && checkNext!(0).ok; + else + enum ok = false; + } + + /* run all the checks */ + enum isCovariantWith = + checkLinkage !().ok && + checkVariadicity!().ok && + checkSTC !().ok && + checkAttributes !().ok && + checkReturnType !().ok && + checkParameters !().ok ; + } +} + +/// +@safe unittest +{ + interface I { I clone(); } + interface J { J clone(); } + class C : I + { + override C clone() // covariant overriding of I.clone() + { + return new C; + } + } + + // C.clone() can override I.clone(), indeed. + static assert(isCovariantWith!(typeof(C.clone), typeof(I.clone))); + + // C.clone() can't override J.clone(); the return type C is not implicitly + // convertible to J. + static assert(!isCovariantWith!(typeof(C.clone), typeof(J.clone))); +} + +@safe unittest +{ + enum bool isCovariantWith(alias f, alias g) = .isCovariantWith!(typeof(f), typeof(g)); + + // covariant return type + interface I {} + interface J : I {} + interface BaseA { const(I) test(int); } + interface DerivA_1 : BaseA { override const(J) test(int); } + interface DerivA_2 : BaseA { override J test(int); } + static assert( isCovariantWith!(DerivA_1.test, BaseA.test)); + static assert( isCovariantWith!(DerivA_2.test, BaseA.test)); + static assert(!isCovariantWith!(BaseA.test, DerivA_1.test)); + static assert(!isCovariantWith!(BaseA.test, DerivA_2.test)); + static assert( isCovariantWith!(BaseA.test, BaseA.test)); + static assert( isCovariantWith!(DerivA_1.test, DerivA_1.test)); + static assert( isCovariantWith!(DerivA_2.test, DerivA_2.test)); + + // scope parameter + interface BaseB { void test( int*, int*); } + interface DerivB_1 : BaseB { override void test(scope int*, int*); } + interface DerivB_2 : BaseB { override void test( int*, scope int*); } + interface DerivB_3 : BaseB { override void test(scope int*, scope int*); } + static assert( isCovariantWith!(DerivB_1.test, BaseB.test)); + static assert( isCovariantWith!(DerivB_2.test, BaseB.test)); + static assert( isCovariantWith!(DerivB_3.test, BaseB.test)); + static assert(!isCovariantWith!(BaseB.test, DerivB_1.test)); + static assert(!isCovariantWith!(BaseB.test, DerivB_2.test)); + static assert(!isCovariantWith!(BaseB.test, DerivB_3.test)); + + // function storage class + interface BaseC { void test() ; } + interface DerivC_1 : BaseC { override void test() const; } + static assert( isCovariantWith!(DerivC_1.test, BaseC.test)); + static assert(!isCovariantWith!(BaseC.test, DerivC_1.test)); + + // increasing safety + interface BaseE { void test() ; } + interface DerivE_1 : BaseE { override void test() @safe ; } + interface DerivE_2 : BaseE { override void test() @trusted; } + static assert( isCovariantWith!(DerivE_1.test, BaseE.test)); + static assert( isCovariantWith!(DerivE_2.test, BaseE.test)); + static assert(!isCovariantWith!(BaseE.test, DerivE_1.test)); + static assert(!isCovariantWith!(BaseE.test, DerivE_2.test)); + + // @safe and @trusted + interface BaseF + { + void test1() @safe; + void test2() @trusted; + } + interface DerivF : BaseF + { + override void test1() @trusted; + override void test2() @safe; + } + static assert( isCovariantWith!(DerivF.test1, BaseF.test1)); + static assert( isCovariantWith!(DerivF.test2, BaseF.test2)); +} + + +// Needed for rvalueOf/lvalueOf because "inout on return means +// inout must be on a parameter as well" +private struct __InoutWorkaroundStruct{} + +/** +Creates an lvalue or rvalue of type $(D T) for $(D typeof(...)) and +$(D __traits(compiles, ...)) purposes. No actual value is returned. + +Note: Trying to use returned value will result in a +"Symbol Undefined" error at link time. + +Example: +--- +// Note that `f` doesn't have to be implemented +// as is isn't called. +int f(int); +bool f(ref int); +static assert(is(typeof(f(rvalueOf!int)) == int)); +static assert(is(typeof(f(lvalueOf!int)) == bool)); + +int i = rvalueOf!int; // error, no actual value is returned +--- +*/ +@property T rvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); + +/// ditto +@property ref T lvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); + +// Note: unittest can't be used as an example here as function overloads +// aren't allowed inside functions. + +@system unittest +{ + void needLvalue(T)(ref T); + static struct S { } + int i; + struct Nested { void f() { ++i; } } + foreach (T; AliasSeq!(int, immutable int, inout int, string, S, Nested, Object)) + { + static assert(!__traits(compiles, needLvalue(rvalueOf!T))); + static assert( __traits(compiles, needLvalue(lvalueOf!T))); + static assert(is(typeof(rvalueOf!T) == T)); + static assert(is(typeof(lvalueOf!T) == T)); + } + + static assert(!__traits(compiles, rvalueOf!int = 1)); + static assert( __traits(compiles, lvalueOf!byte = 127)); + static assert(!__traits(compiles, lvalueOf!byte = 128)); +} + + +//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// +// SomethingTypeOf +//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// + +private template AliasThisTypeOf(T) if (isAggregateType!T) +{ + alias members = AliasSeq!(__traits(getAliasThis, T)); + + static if (members.length == 1) + { + alias AliasThisTypeOf = typeof(__traits(getMember, T.init, members[0])); + } + else + static assert(0, T.stringof~" does not have alias this type"); +} + +/* + */ +template BooleanTypeOf(T) +{ + static if (is(AliasThisTypeOf!T AT) && !is(AT[] == AT)) + alias X = BooleanTypeOf!AT; + else + alias X = OriginalType!T; + + static if (is(Unqual!X == bool)) + { + alias BooleanTypeOf = X; + } + else + static assert(0, T.stringof~" is not boolean type"); +} + +@safe unittest +{ + // unexpected failure, maybe dmd type-merging bug + foreach (T; AliasSeq!bool) + foreach (Q; TypeQualifierList) + { + static assert( is(Q!T == BooleanTypeOf!( Q!T ))); + static assert( is(Q!T == BooleanTypeOf!( SubTypeOf!(Q!T) ))); + } + + foreach (T; AliasSeq!(void, NumericTypeList, ImaginaryTypeList, ComplexTypeList, CharTypeList)) + foreach (Q; TypeQualifierList) + { + static assert(!is(BooleanTypeOf!( Q!T )), Q!T.stringof); + static assert(!is(BooleanTypeOf!( SubTypeOf!(Q!T) ))); + } +} + +@safe unittest +{ + struct B + { + bool val; + alias val this; + } + struct S + { + B b; + alias b this; + } + static assert(is(BooleanTypeOf!B == bool)); + static assert(is(BooleanTypeOf!S == bool)); +} + +/* + */ +template IntegralTypeOf(T) +{ + import std.meta : staticIndexOf; + static if (is(AliasThisTypeOf!T AT) && !is(AT[] == AT)) + alias X = IntegralTypeOf!AT; + else + alias X = OriginalType!T; + + static if (staticIndexOf!(Unqual!X, IntegralTypeList) >= 0) + { + alias IntegralTypeOf = X; + } + else + static assert(0, T.stringof~" is not an integral type"); +} + +@safe unittest +{ + foreach (T; IntegralTypeList) + foreach (Q; TypeQualifierList) + { + static assert( is(Q!T == IntegralTypeOf!( Q!T ))); + static assert( is(Q!T == IntegralTypeOf!( SubTypeOf!(Q!T) ))); + } + + foreach (T; AliasSeq!(void, bool, FloatingPointTypeList, ImaginaryTypeList, ComplexTypeList, CharTypeList)) + foreach (Q; TypeQualifierList) + { + static assert(!is(IntegralTypeOf!( Q!T ))); + static assert(!is(IntegralTypeOf!( SubTypeOf!(Q!T) ))); + } +} + +/* + */ +template FloatingPointTypeOf(T) +{ + import std.meta : staticIndexOf; + static if (is(AliasThisTypeOf!T AT) && !is(AT[] == AT)) + alias X = FloatingPointTypeOf!AT; + else + alias X = OriginalType!T; + + static if (staticIndexOf!(Unqual!X, FloatingPointTypeList) >= 0) + { + alias FloatingPointTypeOf = X; + } + else + static assert(0, T.stringof~" is not a floating point type"); +} + +@safe unittest +{ + foreach (T; FloatingPointTypeList) + foreach (Q; TypeQualifierList) + { + static assert( is(Q!T == FloatingPointTypeOf!( Q!T ))); + static assert( is(Q!T == FloatingPointTypeOf!( SubTypeOf!(Q!T) ))); + } + + foreach (T; AliasSeq!(void, bool, IntegralTypeList, ImaginaryTypeList, ComplexTypeList, CharTypeList)) + foreach (Q; TypeQualifierList) + { + static assert(!is(FloatingPointTypeOf!( Q!T ))); + static assert(!is(FloatingPointTypeOf!( SubTypeOf!(Q!T) ))); + } +} + +/* + */ +template NumericTypeOf(T) +{ + static if (is(IntegralTypeOf!T X) || is(FloatingPointTypeOf!T X)) + { + alias NumericTypeOf = X; + } + else + static assert(0, T.stringof~" is not a numeric type"); +} + +@safe unittest +{ + foreach (T; NumericTypeList) + foreach (Q; TypeQualifierList) + { + static assert( is(Q!T == NumericTypeOf!( Q!T ))); + static assert( is(Q!T == NumericTypeOf!( SubTypeOf!(Q!T) ))); + } + + foreach (T; AliasSeq!(void, bool, CharTypeList, ImaginaryTypeList, ComplexTypeList)) + foreach (Q; TypeQualifierList) + { + static assert(!is(NumericTypeOf!( Q!T ))); + static assert(!is(NumericTypeOf!( SubTypeOf!(Q!T) ))); + } +} + +/* + */ +template UnsignedTypeOf(T) +{ + import std.meta : staticIndexOf; + static if (is(IntegralTypeOf!T X) && + staticIndexOf!(Unqual!X, UnsignedIntTypeList) >= 0) + alias UnsignedTypeOf = X; + else + static assert(0, T.stringof~" is not an unsigned type."); +} + +/* + */ +template SignedTypeOf(T) +{ + import std.meta : staticIndexOf; + static if (is(IntegralTypeOf!T X) && + staticIndexOf!(Unqual!X, SignedIntTypeList) >= 0) + alias SignedTypeOf = X; + else static if (is(FloatingPointTypeOf!T X)) + alias SignedTypeOf = X; + else + static assert(0, T.stringof~" is not an signed type."); +} + +/* + */ +template CharTypeOf(T) +{ + import std.meta : staticIndexOf; + static if (is(AliasThisTypeOf!T AT) && !is(AT[] == AT)) + alias X = CharTypeOf!AT; + else + alias X = OriginalType!T; + + static if (staticIndexOf!(Unqual!X, CharTypeList) >= 0) + { + alias CharTypeOf = X; + } + else + static assert(0, T.stringof~" is not a character type"); +} + +@safe unittest +{ + foreach (T; CharTypeList) + foreach (Q; TypeQualifierList) + { + static assert( is(CharTypeOf!( Q!T ))); + static assert( is(CharTypeOf!( SubTypeOf!(Q!T) ))); + } + + foreach (T; AliasSeq!(void, bool, NumericTypeList, ImaginaryTypeList, ComplexTypeList)) + foreach (Q; TypeQualifierList) + { + static assert(!is(CharTypeOf!( Q!T ))); + static assert(!is(CharTypeOf!( SubTypeOf!(Q!T) ))); + } + + foreach (T; AliasSeq!(string, wstring, dstring, char[4])) + foreach (Q; TypeQualifierList) + { + static assert(!is(CharTypeOf!( Q!T ))); + static assert(!is(CharTypeOf!( SubTypeOf!(Q!T) ))); + } +} + +/* + */ +template StaticArrayTypeOf(T) +{ + static if (is(AliasThisTypeOf!T AT) && !is(AT[] == AT)) + alias X = StaticArrayTypeOf!AT; + else + alias X = OriginalType!T; + + static if (is(X : E[n], E, size_t n)) + alias StaticArrayTypeOf = X; + else + static assert(0, T.stringof~" is not a static array type"); +} + +@safe unittest +{ + foreach (T; AliasSeq!(bool, NumericTypeList, ImaginaryTypeList, ComplexTypeList)) + foreach (Q; AliasSeq!(TypeQualifierList, InoutOf, SharedInoutOf)) + { + static assert(is( Q!( T[1] ) == StaticArrayTypeOf!( Q!( T[1] ) ) )); + + foreach (P; TypeQualifierList) + { // SubTypeOf cannot have inout type + static assert(is( Q!(P!(T[1])) == StaticArrayTypeOf!( Q!(SubTypeOf!(P!(T[1]))) ) )); + } + } + + foreach (T; AliasSeq!void) + foreach (Q; AliasSeq!TypeQualifierList) + { + static assert(is( StaticArrayTypeOf!( Q!(void[1]) ) == Q!(void[1]) )); + } +} + +/* + */ +template DynamicArrayTypeOf(T) +{ + static if (is(AliasThisTypeOf!T AT) && !is(AT[] == AT)) + alias X = DynamicArrayTypeOf!AT; + else + alias X = OriginalType!T; + + static if (is(Unqual!X : E[], E) && !is(typeof({ enum n = X.length; }))) + { + alias DynamicArrayTypeOf = X; + } + else + static assert(0, T.stringof~" is not a dynamic array"); +} + +@safe unittest +{ + foreach (T; AliasSeq!(/*void, */bool, NumericTypeList, ImaginaryTypeList, ComplexTypeList)) + foreach (Q; AliasSeq!(TypeQualifierList, InoutOf, SharedInoutOf)) + { + static assert(is( Q!T[] == DynamicArrayTypeOf!( Q!T[] ) )); + static assert(is( Q!(T[]) == DynamicArrayTypeOf!( Q!(T[]) ) )); + + foreach (P; AliasSeq!(MutableOf, ConstOf, ImmutableOf)) + { + static assert(is( Q!(P!T[]) == DynamicArrayTypeOf!( Q!(SubTypeOf!(P!T[])) ) )); + static assert(is( Q!(P!(T[])) == DynamicArrayTypeOf!( Q!(SubTypeOf!(P!(T[]))) ) )); + } + } + + static assert(!is(DynamicArrayTypeOf!(int[3]))); + static assert(!is(DynamicArrayTypeOf!(void[3]))); + static assert(!is(DynamicArrayTypeOf!(typeof(null)))); +} + +/* + */ +template ArrayTypeOf(T) +{ + static if (is(StaticArrayTypeOf!T X) || is(DynamicArrayTypeOf!T X)) + { + alias ArrayTypeOf = X; + } + else + static assert(0, T.stringof~" is not an array type"); +} + +/* +Always returns the Dynamic Array version. + */ +template StringTypeOf(T) +{ + static if (is(T == typeof(null))) + { + // It is impossible to determine exact string type from typeof(null) - + // it means that StringTypeOf!(typeof(null)) is undefined. + // Then this behavior is convenient for template constraint. + static assert(0, T.stringof~" is not a string type"); + } + else static if (is(T : const char[]) || is(T : const wchar[]) || is(T : const dchar[])) + { + static if (is(T : U[], U)) + alias StringTypeOf = U[]; + else + static assert(0); + } + else + static assert(0, T.stringof~" is not a string type"); +} + +@safe unittest +{ + foreach (T; CharTypeList) + foreach (Q; AliasSeq!(MutableOf, ConstOf, ImmutableOf, InoutOf)) + { + static assert(is(Q!T[] == StringTypeOf!( Q!T[] ))); + + static if (!__traits(isSame, Q, InoutOf)) + { + static assert(is(Q!T[] == StringTypeOf!( SubTypeOf!(Q!T[]) ))); + + alias Str = Q!T[]; + class C(S) { S val; alias val this; } + static assert(is(StringTypeOf!(C!Str) == Str)); + } + } + + foreach (T; CharTypeList) + foreach (Q; AliasSeq!(SharedOf, SharedConstOf, SharedInoutOf)) + { + static assert(!is(StringTypeOf!( Q!T[] ))); + } +} + +@safe unittest +{ + static assert(is(StringTypeOf!(char[4]) == char[])); +} + +/* + */ +template AssocArrayTypeOf(T) +{ + static if (is(AliasThisTypeOf!T AT) && !is(AT[] == AT)) + alias X = AssocArrayTypeOf!AT; + else + alias X = OriginalType!T; + + static if (is(Unqual!X : V[K], K, V)) + { + alias AssocArrayTypeOf = X; + } + else + static assert(0, T.stringof~" is not an associative array type"); +} + +@safe unittest +{ + foreach (T; AliasSeq!(int/*bool, CharTypeList, NumericTypeList, ImaginaryTypeList, ComplexTypeList*/)) + foreach (P; AliasSeq!(TypeQualifierList, InoutOf, SharedInoutOf)) + foreach (Q; AliasSeq!(TypeQualifierList, InoutOf, SharedInoutOf)) + foreach (R; AliasSeq!(TypeQualifierList, InoutOf, SharedInoutOf)) + { + static assert(is( P!(Q!T[R!T]) == AssocArrayTypeOf!( P!(Q!T[R!T]) ) )); + } + + foreach (T; AliasSeq!(int/*bool, CharTypeList, NumericTypeList, ImaginaryTypeList, ComplexTypeList*/)) + foreach (O; AliasSeq!(TypeQualifierList, InoutOf, SharedInoutOf)) + foreach (P; AliasSeq!TypeQualifierList) + foreach (Q; AliasSeq!TypeQualifierList) + foreach (R; AliasSeq!TypeQualifierList) + { + static assert(is( O!(P!(Q!T[R!T])) == AssocArrayTypeOf!( O!(SubTypeOf!(P!(Q!T[R!T]))) ) )); + } +} + +/* + */ +template BuiltinTypeOf(T) +{ + static if (is(T : void)) alias BuiltinTypeOf = void; + else static if (is(BooleanTypeOf!T X)) alias BuiltinTypeOf = X; + else static if (is(IntegralTypeOf!T X)) alias BuiltinTypeOf = X; + else static if (is(FloatingPointTypeOf!T X))alias BuiltinTypeOf = X; + else static if (is(T : const(ireal))) alias BuiltinTypeOf = ireal; //TODO + else static if (is(T : const(creal))) alias BuiltinTypeOf = creal; //TODO + else static if (is(CharTypeOf!T X)) alias BuiltinTypeOf = X; + else static if (is(ArrayTypeOf!T X)) alias BuiltinTypeOf = X; + else static if (is(AssocArrayTypeOf!T X)) alias BuiltinTypeOf = X; + else static assert(0); +} + +//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// +// isSomething +//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// + +/** + * Detect whether $(D T) is a built-in boolean type. + */ +enum bool isBoolean(T) = is(BooleanTypeOf!T) && !isAggregateType!T; + +/// +@safe unittest +{ + static assert( isBoolean!bool); + enum EB : bool { a = true } + static assert( isBoolean!EB); + static assert(!isBoolean!(SubTypeOf!bool)); +} + +@safe unittest +{ + static struct S(T) + { + T t; + alias t this; + } + static assert(!isIntegral!(S!bool)); +} + +/** + * Detect whether $(D T) is a built-in integral type. Types $(D bool), + * $(D char), $(D wchar), and $(D dchar) are not considered integral. + */ +enum bool isIntegral(T) = is(IntegralTypeOf!T) && !isAggregateType!T; + +/// +@safe unittest +{ + static assert( + isIntegral!byte && + isIntegral!short && + isIntegral!int && + isIntegral!long && + isIntegral!(const(long)) && + isIntegral!(immutable(long)) + ); + + static assert( + !isIntegral!bool && + !isIntegral!char && + !isIntegral!double + ); + + // types which act as integral values do not pass + struct S + { + int val; + alias val this; + } + + static assert(!isIntegral!S); +} + +@safe unittest +{ + foreach (T; IntegralTypeList) + { + foreach (Q; TypeQualifierList) + { + static assert( isIntegral!(Q!T)); + static assert(!isIntegral!(SubTypeOf!(Q!T))); + } + } + + static assert(!isIntegral!float); + + enum EU : uint { a = 0, b = 1, c = 2 } // base type is unsigned + enum EI : int { a = -1, b = 0, c = 1 } // base type is signed (bug 7909) + static assert(isIntegral!EU && isUnsigned!EU && !isSigned!EU); + static assert(isIntegral!EI && !isUnsigned!EI && isSigned!EI); +} + +/** + * Detect whether $(D T) is a built-in floating point type. + */ +enum bool isFloatingPoint(T) = __traits(isFloating, T) && !(is(Unqual!T == cfloat) || + is(Unqual!T == cdouble) || + is(Unqual!T == creal) || + is(Unqual!T == ifloat) || + is(Unqual!T == idouble) || + is(Unqual!T == ireal)); + +/// +@safe unittest +{ + static assert( + isFloatingPoint!float && + isFloatingPoint!double && + isFloatingPoint!real && + isFloatingPoint!(const(real)) && + isFloatingPoint!(immutable(real)) + ); + + static assert(!isFloatingPoint!int); + + // complex and imaginary numbers do not pass + static assert( + !isFloatingPoint!cfloat && + !isFloatingPoint!ifloat + ); + + // types which act as floating point values do not pass + struct S + { + float val; + alias val this; + } + + static assert(!isFloatingPoint!S); +} + +@safe unittest +{ + enum EF : real { a = 1.414, b = 1.732, c = 2.236 } + + foreach (T; AliasSeq!(FloatingPointTypeList, EF)) + { + foreach (Q; TypeQualifierList) + { + static assert( isFloatingPoint!(Q!T)); + static assert(!isFloatingPoint!(SubTypeOf!(Q!T))); + } + } + foreach (T; IntegralTypeList) + { + foreach (Q; TypeQualifierList) + { + static assert(!isFloatingPoint!(Q!T)); + } + } +} + +// https://issues.dlang.org/show_bug.cgi?id=17195 +@safe unittest +{ + static assert(!isFloatingPoint!cfloat); + static assert(!isFloatingPoint!cdouble); + static assert(!isFloatingPoint!creal); + + static assert(!isFloatingPoint!ifloat); + static assert(!isFloatingPoint!idouble); + static assert(!isFloatingPoint!ireal); +} + +/** + * Detect whether $(D T) is a built-in numeric type (integral or floating + * point). + */ +enum bool isNumeric(T) = __traits(isArithmetic, T) && !(is(Unqual!T == bool) || + is(Unqual!T == char) || + is(Unqual!T == wchar) || + is(Unqual!T == dchar)); + +/// +@safe unittest +{ + static assert( + isNumeric!byte && + isNumeric!short && + isNumeric!int && + isNumeric!long && + isNumeric!float && + isNumeric!double && + isNumeric!real && + isNumeric!(const(real)) && + isNumeric!(immutable(real)) + ); + + static assert( + !isNumeric!void && + !isNumeric!bool && + !isNumeric!char && + !isNumeric!wchar && + !isNumeric!dchar + ); + + // types which act as numeric values do not pass + struct S + { + int val; + alias val this; + } + + static assert(!isIntegral!S); +} + +@safe unittest +{ + foreach (T; AliasSeq!(NumericTypeList)) + { + foreach (Q; TypeQualifierList) + { + static assert( isNumeric!(Q!T)); + static assert(!isNumeric!(SubTypeOf!(Q!T))); + } + } + + static struct S(T) + { + T t; + alias t this; + } + static assert(!isNumeric!(S!int)); +} + +/** + * Detect whether $(D T) is a scalar type (a built-in numeric, character or + * boolean type). + */ +enum bool isScalarType(T) = is(T : real) && !isAggregateType!T; + +/// +@safe unittest +{ + static assert(!isScalarType!void); + static assert( isScalarType!(immutable(byte))); + static assert( isScalarType!(immutable(ushort))); + static assert( isScalarType!(immutable(int))); + static assert( isScalarType!(ulong)); + static assert( isScalarType!(shared(float))); + static assert( isScalarType!(shared(const bool))); + static assert( isScalarType!(const(char))); + static assert( isScalarType!(wchar)); + static assert( isScalarType!(const(dchar))); + static assert( isScalarType!(const(double))); + static assert( isScalarType!(const(real))); +} + +@safe unittest +{ + static struct S(T) + { + T t; + alias t this; + } + static assert(!isScalarType!(S!int)); +} + +/** + * Detect whether $(D T) is a basic type (scalar type or void). + */ +enum bool isBasicType(T) = isScalarType!T || is(Unqual!T == void); + +/// +@safe unittest +{ + static assert(isBasicType!void); + static assert(isBasicType!(const(void))); + static assert(isBasicType!(shared(void))); + static assert(isBasicType!(immutable(void))); + static assert(isBasicType!(shared const(void))); + static assert(isBasicType!(shared inout(void))); + static assert(isBasicType!(shared inout const(void))); + static assert(isBasicType!(inout(void))); + static assert(isBasicType!(inout const(void))); + static assert(isBasicType!(immutable(int))); + static assert(isBasicType!(shared(float))); + static assert(isBasicType!(shared(const bool))); + static assert(isBasicType!(const(dchar))); +} + +/** + * Detect whether $(D T) is a built-in unsigned numeric type. + */ +enum bool isUnsigned(T) = __traits(isUnsigned, T) && !(is(Unqual!T == char) || + is(Unqual!T == wchar) || + is(Unqual!T == dchar) || + is(Unqual!T == bool)); + +/// +@safe unittest +{ + static assert( + isUnsigned!uint && + isUnsigned!ulong + ); + + static assert( + !isUnsigned!char && + !isUnsigned!int && + !isUnsigned!long && + !isUnsigned!char && + !isUnsigned!wchar && + !isUnsigned!dchar + ); +} + +@safe unittest +{ + foreach (T; AliasSeq!(UnsignedIntTypeList)) + { + foreach (Q; TypeQualifierList) + { + static assert( isUnsigned!(Q!T)); + static assert(!isUnsigned!(SubTypeOf!(Q!T))); + } + } + + static struct S(T) + { + T t; + alias t this; + } + static assert(!isUnsigned!(S!uint)); +} + +/** + * Detect whether $(D T) is a built-in signed numeric type. + */ +enum bool isSigned(T) = __traits(isArithmetic, T) && !__traits(isUnsigned, T); + +/// +@safe unittest +{ + static assert( + isSigned!int && + isSigned!long + ); + + static assert( + !isSigned!uint && + !isSigned!ulong + ); +} + +@safe unittest +{ + enum E { e1 = 0 } + static assert(isSigned!E); + + enum Eubyte : ubyte { e1 = 0 } + static assert(!isSigned!Eubyte); + + foreach (T; AliasSeq!(SignedIntTypeList)) + { + foreach (Q; TypeQualifierList) + { + static assert( isSigned!(Q!T)); + static assert(!isSigned!(SubTypeOf!(Q!T))); + } + } + + static struct S(T) + { + T t; + alias t this; + } + static assert(!isSigned!(S!uint)); +} + +// https://issues.dlang.org/show_bug.cgi?id=17196 +@safe unittest +{ + static assert(isUnsigned!bool == false); + static assert(isSigned!bool == false); +} + +/** + * Detect whether $(D T) is one of the built-in character types. + * + * The built-in char types are any of $(D char), $(D wchar) or $(D dchar), with + * or without qualifiers. + */ +enum bool isSomeChar(T) = is(CharTypeOf!T) && !isAggregateType!T; + +/// +@safe unittest +{ + //Char types + static assert( isSomeChar!char); + static assert( isSomeChar!wchar); + static assert( isSomeChar!dchar); + static assert( isSomeChar!(typeof('c'))); + static assert( isSomeChar!(immutable char)); + static assert( isSomeChar!(const dchar)); + + //Non char types + static assert(!isSomeChar!int); + static assert(!isSomeChar!byte); + static assert(!isSomeChar!string); + static assert(!isSomeChar!wstring); + static assert(!isSomeChar!dstring); + static assert(!isSomeChar!(char[4])); +} + +@safe unittest +{ + enum EC : char { a = 'x', b = 'y' } + + foreach (T; AliasSeq!(CharTypeList, EC)) + { + foreach (Q; TypeQualifierList) + { + static assert( isSomeChar!( Q!T )); + static assert(!isSomeChar!( SubTypeOf!(Q!T) )); + } + } + + // alias-this types are not allowed + static struct S(T) + { + T t; + alias t this; + } + static assert(!isSomeChar!(S!char)); +} + +/** +Detect whether $(D T) is one of the built-in string types. + +The built-in string types are $(D Char[]), where $(D Char) is any of $(D char), +$(D wchar) or $(D dchar), with or without qualifiers. + +Static arrays of characters (like $(D char[80])) are not considered +built-in string types. + */ +enum bool isSomeString(T) = is(StringTypeOf!T) && !isAggregateType!T && !isStaticArray!T; + +/// +@safe unittest +{ + //String types + static assert( isSomeString!string); + static assert( isSomeString!(wchar[])); + static assert( isSomeString!(dchar[])); + static assert( isSomeString!(typeof("aaa"))); + static assert( isSomeString!(const(char)[])); + + enum ES : string { a = "aaa", b = "bbb" } + static assert( isSomeString!ES); + + //Non string types + static assert(!isSomeString!int); + static assert(!isSomeString!(int[])); + static assert(!isSomeString!(byte[])); + static assert(!isSomeString!(typeof(null))); + static assert(!isSomeString!(char[4])); +} + +@safe unittest +{ + foreach (T; AliasSeq!(char[], dchar[], string, wstring, dstring)) + { + static assert( isSomeString!( T )); + static assert(!isSomeString!(SubTypeOf!(T))); + } +} + +/** + * Detect whether type $(D T) is a narrow string. + * + * All arrays that use char, wchar, and their qualified versions are narrow + * strings. (Those include string and wstring). + */ +enum bool isNarrowString(T) = (is(T : const char[]) || is(T : const wchar[])) && !isAggregateType!T && !isStaticArray!T; + +/// +@safe unittest +{ + static assert(isNarrowString!string); + static assert(isNarrowString!wstring); + static assert(isNarrowString!(char[])); + static assert(isNarrowString!(wchar[])); + + static assert(!isNarrowString!dstring); + static assert(!isNarrowString!(dchar[])); +} + +@safe unittest +{ + foreach (T; AliasSeq!(char[], string, wstring)) + { + foreach (Q; AliasSeq!(MutableOf, ConstOf, ImmutableOf)/*TypeQualifierList*/) + { + static assert( isNarrowString!( Q!T )); + static assert(!isNarrowString!( SubTypeOf!(Q!T) )); + } + } + + foreach (T; AliasSeq!(int, int[], byte[], dchar[], dstring, char[4])) + { + foreach (Q; TypeQualifierList) + { + static assert(!isNarrowString!( Q!T )); + static assert(!isNarrowString!( SubTypeOf!(Q!T) )); + } + } +} + +/** + * Detects whether `T` is a comparable type. Basic types and structs and + * classes that implement opCmp are ordering comparable. + */ +enum bool isOrderingComparable(T) = ifTestable!(T, unaryFun!"a < a"); + +/// +@safe unittest +{ + static assert(isOrderingComparable!int); + static assert(isOrderingComparable!string); + static assert(!isOrderingComparable!creal); + + static struct Foo {} + static assert(!isOrderingComparable!Foo); + + static struct Bar + { + int a; + auto opCmp(Bar b1) const { return a - b1.a; } + } + + Bar b1 = Bar(5); + Bar b2 = Bar(7); + assert(isOrderingComparable!Bar && b2 > b1); +} + +/// ditto +enum bool isEqualityComparable(T) = ifTestable!(T, unaryFun!"a == a"); + +@safe unittest +{ + static assert(isEqualityComparable!int); + static assert(isEqualityComparable!string); + static assert(isEqualityComparable!creal); + static assert(!isEqualityComparable!void); + + struct Foo {} + static assert(isEqualityComparable!Foo); + + struct Bar + { + int a; + auto opEquals(Bar b1) const { return a == b1.a; } + } + + Bar b1 = Bar(5); + Bar b2 = Bar(5); + Bar b3 = Bar(7); + static assert(isEqualityComparable!Bar); + assert(b1 == b2); + assert(b1 != b3); +} + +/** + * Detect whether $(D T) is a struct, static array, or enum that is implicitly + * convertible to a string. + */ +template isConvertibleToString(T) +{ + enum isConvertibleToString = + (isAggregateType!T || isStaticArray!T || is(T == enum)) + && is(StringTypeOf!T); +} + +/// +@safe unittest +{ + static struct AliasedString + { + string s; + alias s this; + } + + enum StringEnum { a = "foo" } + + assert(!isConvertibleToString!string); + assert(isConvertibleToString!AliasedString); + assert(isConvertibleToString!StringEnum); + assert(isConvertibleToString!(char[25])); + assert(!isConvertibleToString!(char[])); +} + +@safe unittest // Bugzilla 16573 +{ + enum I : int { foo = 1 } + enum S : string { foo = "foo" } + assert(!isConvertibleToString!I); + assert(isConvertibleToString!S); +} + +package template convertToString(T) +{ + static if (isConvertibleToString!T) + alias convertToString = StringTypeOf!T; + else + alias convertToString = T; +} + +/** + * Detect whether type $(D T) is a string that will be autodecoded. + * + * All arrays that use char, wchar, and their qualified versions are narrow + * strings. (Those include string and wstring). + * Aggregates that implicitly cast to narrow strings are included. + * + * Params: + * T = type to be tested + * + * Returns: + * true if T represents a string that is subject to autodecoding + * + * See Also: + * $(LREF isNarrowString) + */ +enum bool isAutodecodableString(T) = (is(T : const char[]) || is(T : const wchar[])) && !isStaticArray!T; + +/// +@safe unittest +{ + static struct Stringish + { + string s; + alias s this; + } + assert(isAutodecodableString!wstring); + assert(isAutodecodableString!Stringish); + assert(!isAutodecodableString!dstring); +} + +/** + * Detect whether type $(D T) is a static array. + */ +enum bool isStaticArray(T) = __traits(isStaticArray, T); + +/// +@safe unittest +{ + static assert( isStaticArray!(int[3])); + static assert( isStaticArray!(const(int)[5])); + static assert( isStaticArray!(const(int)[][5])); + + static assert(!isStaticArray!(const(int)[])); + static assert(!isStaticArray!(immutable(int)[])); + static assert(!isStaticArray!(const(int)[4][])); + static assert(!isStaticArray!(int[])); + static assert(!isStaticArray!(int[char])); + static assert(!isStaticArray!(int[1][])); + static assert(!isStaticArray!(int[int])); + static assert(!isStaticArray!int); +} + +@safe unittest +{ + foreach (T; AliasSeq!(int[51], int[][2], + char[][int][11], immutable char[13u], + const(real)[1], const(real)[1][1], void[0])) + { + foreach (Q; TypeQualifierList) + { + static assert( isStaticArray!( Q!T )); + static assert(!isStaticArray!( SubTypeOf!(Q!T) )); + } + } + + //enum ESA : int[1] { a = [1], b = [2] } + //static assert( isStaticArray!ESA); +} + +/** + * Detect whether type $(D T) is a dynamic array. + */ +enum bool isDynamicArray(T) = is(DynamicArrayTypeOf!T) && !isAggregateType!T; + +/// +@safe unittest +{ + static assert( isDynamicArray!(int[])); + static assert( isDynamicArray!(string)); + static assert( isDynamicArray!(long[3][])); + + static assert(!isDynamicArray!(int[5])); + static assert(!isDynamicArray!(typeof(null))); +} + +@safe unittest +{ + import std.meta : AliasSeq; + foreach (T; AliasSeq!(int[], char[], string, long[3][], double[string][])) + { + foreach (Q; TypeQualifierList) + { + static assert( isDynamicArray!( Q!T )); + static assert(!isDynamicArray!( SubTypeOf!(Q!T) )); + } + } +} + +/** + * Detect whether type $(D T) is an array (static or dynamic; for associative + * arrays see $(LREF isAssociativeArray)). + */ +enum bool isArray(T) = isStaticArray!T || isDynamicArray!T; + +/// +@safe unittest +{ + static assert( isArray!(int[])); + static assert( isArray!(int[5])); + static assert( isArray!(string)); + + static assert(!isArray!uint); + static assert(!isArray!(uint[uint])); + static assert(!isArray!(typeof(null))); +} + +@safe unittest +{ + import std.meta : AliasSeq; + foreach (T; AliasSeq!(int[], int[5], void[])) + { + foreach (Q; TypeQualifierList) + { + static assert( isArray!(Q!T)); + static assert(!isArray!(SubTypeOf!(Q!T))); + } + } +} + +/** + * Detect whether $(D T) is an associative array type + */ +enum bool isAssociativeArray(T) = __traits(isAssociativeArray, T); + +@safe unittest +{ + struct Foo + { + @property uint[] keys() { return null; } + @property uint[] values() { return null; } + } + + foreach (T; AliasSeq!(int[int], int[string], immutable(char[5])[int])) + { + foreach (Q; TypeQualifierList) + { + static assert( isAssociativeArray!(Q!T)); + static assert(!isAssociativeArray!(SubTypeOf!(Q!T))); + } + } + + static assert(!isAssociativeArray!Foo); + static assert(!isAssociativeArray!int); + static assert(!isAssociativeArray!(int[])); + static assert(!isAssociativeArray!(typeof(null))); + + //enum EAA : int[int] { a = [1:1], b = [2:2] } + //static assert( isAssociativeArray!EAA); +} + +/** + * Detect whether type $(D T) is a builtin type. + */ +enum bool isBuiltinType(T) = is(BuiltinTypeOf!T) && !isAggregateType!T; + +/// +@safe unittest +{ + class C; + union U; + struct S; + interface I; + + static assert( isBuiltinType!void); + static assert( isBuiltinType!string); + static assert( isBuiltinType!(int[])); + static assert( isBuiltinType!(C[string])); + static assert(!isBuiltinType!C); + static assert(!isBuiltinType!U); + static assert(!isBuiltinType!S); + static assert(!isBuiltinType!I); + static assert(!isBuiltinType!(void delegate(int))); +} + +/** + * Detect whether type $(D T) is a SIMD vector type. + */ +enum bool isSIMDVector(T) = is(T : __vector(V[N]), V, size_t N); + +@safe unittest +{ + static if (is(__vector(float[4]))) + { + alias SimdVec = __vector(float[4]); + static assert(isSIMDVector!(__vector(float[4]))); + static assert(isSIMDVector!SimdVec); + } + static assert(!isSIMDVector!uint); + static assert(!isSIMDVector!(float[4])); +} + +/** + * Detect whether type $(D T) is a pointer. + */ +enum bool isPointer(T) = is(T == U*, U) && !isAggregateType!T; + +@safe unittest +{ + foreach (T; AliasSeq!(int*, void*, char[]*)) + { + foreach (Q; TypeQualifierList) + { + static assert( isPointer!(Q!T)); + static assert(!isPointer!(SubTypeOf!(Q!T))); + } + } + + static assert(!isPointer!uint); + static assert(!isPointer!(uint[uint])); + static assert(!isPointer!(char[])); + static assert(!isPointer!(typeof(null))); +} + +/** +Returns the target type of a pointer. +*/ +alias PointerTarget(T : T*) = T; + +/// +@safe unittest +{ + static assert(is(PointerTarget!(int*) == int)); + static assert(is(PointerTarget!(void*) == void)); +} + +/** + * Detect whether type $(D T) is an aggregate type. + */ +enum bool isAggregateType(T) = is(T == struct) || is(T == union) || + is(T == class) || is(T == interface); + +/// +@safe unittest +{ + class C; + union U; + struct S; + interface I; + + static assert( isAggregateType!C); + static assert( isAggregateType!U); + static assert( isAggregateType!S); + static assert( isAggregateType!I); + static assert(!isAggregateType!void); + static assert(!isAggregateType!string); + static assert(!isAggregateType!(int[])); + static assert(!isAggregateType!(C[string])); + static assert(!isAggregateType!(void delegate(int))); +} + +/** + * Returns $(D true) if T can be iterated over using a $(D foreach) loop with + * a single loop variable of automatically inferred type, regardless of how + * the $(D foreach) loop is implemented. This includes ranges, structs/classes + * that define $(D opApply) with a single loop variable, and builtin dynamic, + * static and associative arrays. + */ +enum bool isIterable(T) = is(typeof({ foreach (elem; T.init) {} })); + +/// +@safe unittest +{ + struct OpApply + { + int opApply(scope int delegate(ref uint) dg) { assert(0); } + } + + struct Range + { + @property uint front() { assert(0); } + void popFront() { assert(0); } + enum bool empty = false; + } + + static assert( isIterable!(uint[])); + static assert( isIterable!OpApply); + static assert( isIterable!(uint[string])); + static assert( isIterable!Range); + + static assert(!isIterable!uint); +} + +/** + * Returns true if T is not const or immutable. Note that isMutable is true for + * string, or immutable(char)[], because the 'head' is mutable. + */ +enum bool isMutable(T) = !is(T == const) && !is(T == immutable) && !is(T == inout); + +/// +@safe unittest +{ + static assert( isMutable!int); + static assert( isMutable!string); + static assert( isMutable!(shared int)); + static assert( isMutable!(shared const(int)[])); + + static assert(!isMutable!(const int)); + static assert(!isMutable!(inout int)); + static assert(!isMutable!(shared(const int))); + static assert(!isMutable!(shared(inout int))); + static assert(!isMutable!(immutable string)); +} + +/** + * Returns true if T is an instance of the template S. + */ +enum bool isInstanceOf(alias S, T) = is(T == S!Args, Args...); +/// ditto +template isInstanceOf(alias S, alias T) +{ + enum impl(alias T : S!Args, Args...) = true; + enum impl(alias T) = false; + enum isInstanceOf = impl!T; +} + +/// +@safe unittest +{ + static struct Foo(T...) { } + static struct Bar(T...) { } + static struct Doo(T) { } + static struct ABC(int x) { } + static void fun(T)() { } + template templ(T) { } + + static assert(isInstanceOf!(Foo, Foo!int)); + static assert(!isInstanceOf!(Foo, Bar!int)); + static assert(!isInstanceOf!(Foo, int)); + static assert(isInstanceOf!(Doo, Doo!int)); + static assert(isInstanceOf!(ABC, ABC!1)); + static assert(!isInstanceOf!(Foo, Foo)); + static assert(isInstanceOf!(fun, fun!int)); + static assert(isInstanceOf!(templ, templ!int)); +} + +@safe unittest +{ + static void fun1(T)() { } + static void fun2(T)() { } + template templ1(T) { } + template templ2(T) { } + + static assert(!isInstanceOf!(fun1, fun2!int)); + static assert(!isInstanceOf!(templ1, templ2!int)); +} + +/** + * Check whether the tuple T is an expression tuple. + * An expression tuple only contains expressions. + * + * See_Also: $(LREF isTypeTuple). + */ +template isExpressions(T ...) +{ + static if (T.length >= 2) + enum bool isExpressions = + isExpressions!(T[0 .. $/2]) && + isExpressions!(T[$/2 .. $]); + else static if (T.length == 1) + enum bool isExpressions = + !is(T[0]) && __traits(compiles, { auto ex = T[0]; }); + else + enum bool isExpressions = true; // default +} + +/// +@safe unittest +{ + static assert(isExpressions!(1, 2.0, "a")); + static assert(!isExpressions!(int, double, string)); + static assert(!isExpressions!(int, 2.0, "a")); +} + +/** + * Alternate name for $(LREF isExpressions), kept for legacy compatibility. + */ + +alias isExpressionTuple = isExpressions; + +@safe unittest +{ + void foo(); + static int bar() { return 42; } + immutable aa = [ 1: -1 ]; + alias myint = int; + + static assert( isExpressionTuple!(42)); + static assert( isExpressionTuple!aa); + static assert( isExpressionTuple!("cattywampus", 2.7, aa)); + static assert( isExpressionTuple!(bar())); + + static assert(!isExpressionTuple!isExpressionTuple); + static assert(!isExpressionTuple!foo); + static assert(!isExpressionTuple!( (a) { } )); + static assert(!isExpressionTuple!int); + static assert(!isExpressionTuple!myint); +} + + +/** + * Check whether the tuple $(D T) is a type tuple. + * A type tuple only contains types. + * + * See_Also: $(LREF isExpressions). + */ +template isTypeTuple(T...) +{ + static if (T.length >= 2) + enum bool isTypeTuple = isTypeTuple!(T[0 .. $/2]) && isTypeTuple!(T[$/2 .. $]); + else static if (T.length == 1) + enum bool isTypeTuple = is(T[0]); + else + enum bool isTypeTuple = true; // default +} + +/// +@safe unittest +{ + static assert(isTypeTuple!(int, float, string)); + static assert(!isTypeTuple!(1, 2.0, "a")); + static assert(!isTypeTuple!(1, double, string)); +} + +@safe unittest +{ + class C {} + void func(int) {} + auto c = new C; + enum CONST = 42; + + static assert( isTypeTuple!int); + static assert( isTypeTuple!string); + static assert( isTypeTuple!C); + static assert( isTypeTuple!(typeof(func))); + static assert( isTypeTuple!(int, char, double)); + + static assert(!isTypeTuple!c); + static assert(!isTypeTuple!isTypeTuple); + static assert(!isTypeTuple!CONST); +} + + +/** +Detect whether symbol or type $(D T) is a function pointer. + */ +template isFunctionPointer(T...) + if (T.length == 1) +{ + static if (is(T[0] U) || is(typeof(T[0]) U)) + { + static if (is(U F : F*) && is(F == function)) + enum bool isFunctionPointer = true; + else + enum bool isFunctionPointer = false; + } + else + enum bool isFunctionPointer = false; +} + +/// +@safe unittest +{ + static void foo() {} + void bar() {} + + auto fpfoo = &foo; + static assert( isFunctionPointer!fpfoo); + static assert( isFunctionPointer!(void function())); + + auto dgbar = &bar; + static assert(!isFunctionPointer!dgbar); + static assert(!isFunctionPointer!(void delegate())); + static assert(!isFunctionPointer!foo); + static assert(!isFunctionPointer!bar); + + static assert( isFunctionPointer!((int a) {})); +} + +/** +Detect whether symbol or type $(D T) is a delegate. +*/ +template isDelegate(T...) + if (T.length == 1) +{ + static if (is(typeof(& T[0]) U : U*) && is(typeof(& T[0]) U == delegate)) + { + // T is a (nested) function symbol. + enum bool isDelegate = true; + } + else static if (is(T[0] W) || is(typeof(T[0]) W)) + { + // T is an expression or a type. Take the type of it and examine. + enum bool isDelegate = is(W == delegate); + } + else + enum bool isDelegate = false; +} + +/// +@safe unittest +{ + static void sfunc() { } + int x; + void func() { x++; } + + int delegate() dg; + assert(isDelegate!dg); + assert(isDelegate!(int delegate())); + assert(isDelegate!(typeof(&func))); + + int function() fp; + assert(!isDelegate!fp); + assert(!isDelegate!(int function())); + assert(!isDelegate!(typeof(&sfunc))); +} + +/** +Detect whether symbol or type $(D T) is a function, a function pointer or a delegate. + */ +template isSomeFunction(T...) + if (T.length == 1) +{ + static if (is(typeof(& T[0]) U : U*) && is(U == function) || is(typeof(& T[0]) U == delegate)) + { + // T is a (nested) function symbol. + enum bool isSomeFunction = true; + } + else static if (is(T[0] W) || is(typeof(T[0]) W)) + { + // T is an expression or a type. Take the type of it and examine. + static if (is(W F : F*) && is(F == function)) + enum bool isSomeFunction = true; // function pointer + else + enum bool isSomeFunction = is(W == function) || is(W == delegate); + } + else + enum bool isSomeFunction = false; +} + +@safe unittest +{ + static real func(ref int) { return 0; } + static void prop() @property { } + void nestedFunc() { } + void nestedProp() @property { } + class C + { + real method(ref int) { return 0; } + real prop() @property { return 0; } + } + auto c = new C; + auto fp = &func; + auto dg = &c.method; + real val; + + static assert( isSomeFunction!func); + static assert( isSomeFunction!prop); + static assert( isSomeFunction!nestedFunc); + static assert( isSomeFunction!nestedProp); + static assert( isSomeFunction!(C.method)); + static assert( isSomeFunction!(C.prop)); + static assert( isSomeFunction!(c.prop)); + static assert( isSomeFunction!(c.prop)); + static assert( isSomeFunction!fp); + static assert( isSomeFunction!dg); + static assert( isSomeFunction!(typeof(func))); + static assert( isSomeFunction!(real function(ref int))); + static assert( isSomeFunction!(real delegate(ref int))); + static assert( isSomeFunction!((int a) { return a; })); + + static assert(!isSomeFunction!int); + static assert(!isSomeFunction!val); + static assert(!isSomeFunction!isSomeFunction); +} + + +/** +Detect whether $(D T) is a callable object, which can be called with the +function call operator $(D $(LPAREN)...$(RPAREN)). + */ +template isCallable(T...) + if (T.length == 1) +{ + static if (is(typeof(& T[0].opCall) == delegate)) + // T is a object which has a member function opCall(). + enum bool isCallable = true; + else static if (is(typeof(& T[0].opCall) V : V*) && is(V == function)) + // T is a type which has a static member function opCall(). + enum bool isCallable = true; + else + enum bool isCallable = isSomeFunction!T; +} + +/// +@safe unittest +{ + interface I { real value() @property; } + struct S { static int opCall(int) { return 0; } } + class C { int opCall(int) { return 0; } } + auto c = new C; + + static assert( isCallable!c); + static assert( isCallable!S); + static assert( isCallable!(c.opCall)); + static assert( isCallable!(I.value)); + static assert( isCallable!((int a) { return a; })); + + static assert(!isCallable!I); +} + + +/** + * Detect whether $(D T) is an abstract function. + */ +template isAbstractFunction(T...) + if (T.length == 1) +{ + enum bool isAbstractFunction = __traits(isAbstractFunction, T[0]); +} + +@safe unittest +{ + struct S { void foo() { } } + class C { void foo() { } } + class AC { abstract void foo(); } + static assert(!isAbstractFunction!(int)); + static assert(!isAbstractFunction!(S.foo)); + static assert(!isAbstractFunction!(C.foo)); + static assert( isAbstractFunction!(AC.foo)); +} + +/** + * Detect whether $(D T) is a final function. + */ +template isFinalFunction(T...) + if (T.length == 1) +{ + enum bool isFinalFunction = __traits(isFinalFunction, T[0]); +} + +/// +@safe unittest +{ + struct S { void bar() { } } + final class FC { void foo(); } + class C + { + void bar() { } + final void foo(); + } + static assert(!isFinalFunction!(int)); + static assert(!isFinalFunction!(S.bar)); + static assert( isFinalFunction!(FC.foo)); + static assert(!isFinalFunction!(C.bar)); + static assert( isFinalFunction!(C.foo)); +} + +/** +Determines whether function $(D f) requires a context pointer. +*/ +template isNestedFunction(alias f) +{ + enum isNestedFunction = __traits(isNested, f); +} + +@safe unittest +{ + static void f() { } + void g() { } + static assert(!isNestedFunction!f); + static assert( isNestedFunction!g); +} + +/** + * Detect whether $(D T) is an abstract class. + */ +template isAbstractClass(T...) + if (T.length == 1) +{ + enum bool isAbstractClass = __traits(isAbstractClass, T[0]); +} + +/// +@safe unittest +{ + struct S { } + class C { } + abstract class AC { } + static assert(!isAbstractClass!S); + static assert(!isAbstractClass!C); + static assert( isAbstractClass!AC); + C c; + static assert(!isAbstractClass!c); + AC ac; + static assert( isAbstractClass!ac); +} + +/** + * Detect whether $(D T) is a final class. + */ +template isFinalClass(T...) + if (T.length == 1) +{ + enum bool isFinalClass = __traits(isFinalClass, T[0]); +} + +/// +@safe unittest +{ + class C { } + abstract class AC { } + final class FC1 : C { } + final class FC2 { } + static assert(!isFinalClass!C); + static assert(!isFinalClass!AC); + static assert( isFinalClass!FC1); + static assert( isFinalClass!FC2); + C c; + static assert(!isFinalClass!c); + FC1 fc1; + static assert( isFinalClass!fc1); +} + +//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// +// General Types +//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// + +/** +Removes all qualifiers, if any, from type $(D T). + */ +template Unqual(T) +{ + version (none) // Error: recursive alias declaration @@@BUG1308@@@ + { + static if (is(T U == const U)) alias Unqual = Unqual!U; + else static if (is(T U == immutable U)) alias Unqual = Unqual!U; + else static if (is(T U == inout U)) alias Unqual = Unqual!U; + else static if (is(T U == shared U)) alias Unqual = Unqual!U; + else alias Unqual = T; + } + else // workaround + { + static if (is(T U == immutable U)) alias Unqual = U; + else static if (is(T U == shared inout const U)) alias Unqual = U; + else static if (is(T U == shared inout U)) alias Unqual = U; + else static if (is(T U == shared const U)) alias Unqual = U; + else static if (is(T U == shared U)) alias Unqual = U; + else static if (is(T U == inout const U)) alias Unqual = U; + else static if (is(T U == inout U)) alias Unqual = U; + else static if (is(T U == const U)) alias Unqual = U; + else alias Unqual = T; + } +} + +/// +@safe unittest +{ + static assert(is(Unqual!int == int)); + static assert(is(Unqual!(const int) == int)); + static assert(is(Unqual!(immutable int) == int)); + static assert(is(Unqual!(shared int) == int)); + static assert(is(Unqual!(shared(const int)) == int)); +} + +@safe unittest +{ + static assert(is(Unqual!( int) == int)); + static assert(is(Unqual!( const int) == int)); + static assert(is(Unqual!( inout int) == int)); + static assert(is(Unqual!( inout const int) == int)); + static assert(is(Unqual!(shared int) == int)); + static assert(is(Unqual!(shared const int) == int)); + static assert(is(Unqual!(shared inout int) == int)); + static assert(is(Unqual!(shared inout const int) == int)); + static assert(is(Unqual!( immutable int) == int)); + + alias ImmIntArr = immutable(int[]); + static assert(is(Unqual!ImmIntArr == immutable(int)[])); +} + +// [For internal use] +package template ModifyTypePreservingTQ(alias Modifier, T) +{ + static if (is(T U == immutable U)) alias ModifyTypePreservingTQ = immutable Modifier!U; + else static if (is(T U == shared inout const U)) alias ModifyTypePreservingTQ = shared inout const Modifier!U; + else static if (is(T U == shared inout U)) alias ModifyTypePreservingTQ = shared inout Modifier!U; + else static if (is(T U == shared const U)) alias ModifyTypePreservingTQ = shared const Modifier!U; + else static if (is(T U == shared U)) alias ModifyTypePreservingTQ = shared Modifier!U; + else static if (is(T U == inout const U)) alias ModifyTypePreservingTQ = inout const Modifier!U; + else static if (is(T U == inout U)) alias ModifyTypePreservingTQ = inout Modifier!U; + else static if (is(T U == const U)) alias ModifyTypePreservingTQ = const Modifier!U; + else alias ModifyTypePreservingTQ = Modifier!T; +} + +@safe unittest +{ + alias Intify(T) = int; + static assert(is(ModifyTypePreservingTQ!(Intify, real) == int)); + static assert(is(ModifyTypePreservingTQ!(Intify, const real) == const int)); + static assert(is(ModifyTypePreservingTQ!(Intify, inout real) == inout int)); + static assert(is(ModifyTypePreservingTQ!(Intify, inout const real) == inout const int)); + static assert(is(ModifyTypePreservingTQ!(Intify, shared real) == shared int)); + static assert(is(ModifyTypePreservingTQ!(Intify, shared const real) == shared const int)); + static assert(is(ModifyTypePreservingTQ!(Intify, shared inout real) == shared inout int)); + static assert(is(ModifyTypePreservingTQ!(Intify, shared inout const real) == shared inout const int)); + static assert(is(ModifyTypePreservingTQ!(Intify, immutable real) == immutable int)); +} + +/** + * Copies type qualifiers from $(D FromType) to $(D ToType). + * + * Supported type qualifiers: + * $(UL + * $(LI $(D const)) + * $(LI $(D inout)) + * $(LI $(D immutable)) + * $(LI $(D shared)) + * ) + */ +template CopyTypeQualifiers(FromType, ToType) +{ + alias T(U) = ToType; + alias CopyTypeQualifiers = ModifyTypePreservingTQ!(T, FromType); +} + +/// +@safe unittest +{ + static assert(is(CopyTypeQualifiers!(inout const real, int) == inout const int)); +} + +@safe unittest +{ + static assert(is(CopyTypeQualifiers!( real, int) == int)); + static assert(is(CopyTypeQualifiers!( const real, int) == const int)); + static assert(is(CopyTypeQualifiers!( inout real, int) == inout int)); + static assert(is(CopyTypeQualifiers!( inout const real, int) == inout const int)); + static assert(is(CopyTypeQualifiers!(shared real, int) == shared int)); + static assert(is(CopyTypeQualifiers!(shared const real, int) == shared const int)); + static assert(is(CopyTypeQualifiers!(shared inout real, int) == shared inout int)); + static assert(is(CopyTypeQualifiers!(shared inout const real, int) == shared inout const int)); + static assert(is(CopyTypeQualifiers!( immutable real, int) == immutable int)); +} + +/** +Returns the type of `Target` with the "constness" of `Source`. A type's $(B constness) +refers to whether it is `const`, `immutable`, or `inout`. If `source` has no constness, the +returned type will be the same as `Target`. +*/ +template CopyConstness(FromType, ToType) +{ + alias Unshared(T) = T; + alias Unshared(T: shared U, U) = U; + + alias CopyConstness = Unshared!(CopyTypeQualifiers!(FromType, ToType)); +} + +/// +@safe unittest +{ + const(int) i; + CopyConstness!(typeof(i), float) f; + assert( is(typeof(f) == const float)); + + CopyConstness!(char, uint) u; + assert( is(typeof(u) == uint)); + + //The 'shared' qualifier will not be copied + assert(!is(CopyConstness!(shared bool, int) == shared int)); + + //But the constness will be + assert( is(CopyConstness!(shared const real, double) == const double)); + + //Careful, const(int)[] is a mutable array of const(int) + alias MutT = CopyConstness!(const(int)[], int); + assert(!is(MutT == const(int))); + + //Okay, const(int[]) applies to array and contained ints + alias CstT = CopyConstness!(const(int[]), int); + assert( is(CstT == const(int))); +} + +@safe unittest +{ + struct Test + { + void method1() {} + void method2() const {} + void method3() immutable {} + } + + assert(is(CopyConstness!(typeof(Test.method1), real) == real)); + + assert(is(CopyConstness!(typeof(Test.method2), byte) == const(byte))); + + assert(is(CopyConstness!(typeof(Test.method3), string) == immutable(string))); +} + +@safe unittest +{ + assert(is(CopyConstness!(inout(int)[], int[]) == int[])); + assert(is(CopyConstness!(inout(int[]), int[]) == inout(int[]))); +} + +@safe unittest +{ + static assert(is(CopyConstness!( int, real) == real)); + static assert(is(CopyConstness!(const int, real) == const real)); + static assert(is(CopyConstness!(inout int, real) == inout real)); + static assert(is(CopyConstness!(inout const int, real) == inout const real)); + static assert(is(CopyConstness!(shared int, real) == real)); + static assert(is(CopyConstness!(shared const int, real) == const real)); + static assert(is(CopyConstness!(shared inout int, real) == inout real)); + static assert(is(CopyConstness!(shared inout const int, real) == inout const real)); + static assert(is(CopyConstness!(immutable int, real) == immutable real)); +} + +/** +Returns the inferred type of the loop variable when a variable of type T +is iterated over using a $(D foreach) loop with a single loop variable and +automatically inferred return type. Note that this may not be the same as +$(D std.range.ElementType!Range) in the case of narrow strings, or if T +has both opApply and a range interface. +*/ +template ForeachType(T) +{ + alias ForeachType = ReturnType!(typeof( + (inout int x = 0) + { + foreach (elem; T.init) + { + return elem; + } + assert(0); + })); +} + +/// +@safe unittest +{ + static assert(is(ForeachType!(uint[]) == uint)); + static assert(is(ForeachType!string == immutable(char))); + static assert(is(ForeachType!(string[string]) == string)); + static assert(is(ForeachType!(inout(int)[]) == inout(int))); +} + + +/** + * Strips off all $(D enum)s from type $(D T). + */ +template OriginalType(T) +{ + template Impl(T) + { + static if (is(T U == enum)) alias Impl = OriginalType!U; + else alias Impl = T; + } + + alias OriginalType = ModifyTypePreservingTQ!(Impl, T); +} + +/// +@safe unittest +{ + enum E : real { a } + enum F : E { a = E.a } + alias G = const(F); + static assert(is(OriginalType!E == real)); + static assert(is(OriginalType!F == real)); + static assert(is(OriginalType!G == const real)); +} + +/** + * Get the Key type of an Associative Array. + */ +alias KeyType(V : V[K], K) = K; + +/// +@safe unittest +{ + import std.traits; + alias Hash = int[string]; + static assert(is(KeyType!Hash == string)); + static assert(is(ValueType!Hash == int)); + KeyType!Hash str = "a"; // str is declared as string + ValueType!Hash num = 1; // num is declared as int +} + +/** + * Get the Value type of an Associative Array. + */ +alias ValueType(V : V[K], K) = V; + +/// +@safe unittest +{ + import std.traits; + alias Hash = int[string]; + static assert(is(KeyType!Hash == string)); + static assert(is(ValueType!Hash == int)); + KeyType!Hash str = "a"; // str is declared as string + ValueType!Hash num = 1; // num is declared as int +} + +/** + * Returns the corresponding unsigned type for T. T must be a numeric + * integral type, otherwise a compile-time error occurs. + */ +template Unsigned(T) +{ + template Impl(T) + { + static if (is(T : __vector(V[N]), V, size_t N)) + alias Impl = __vector(Impl!V[N]); + else static if (isUnsigned!T) + alias Impl = T; + else static if (isSigned!T && !isFloatingPoint!T) + { + static if (is(T == byte )) alias Impl = ubyte; + static if (is(T == short)) alias Impl = ushort; + static if (is(T == int )) alias Impl = uint; + static if (is(T == long )) alias Impl = ulong; + static if (is(ucent) && is(T == cent )) alias Impl = ucent; + } + else + static assert(false, "Type " ~ T.stringof ~ + " does not have an Unsigned counterpart"); + } + + alias Unsigned = ModifyTypePreservingTQ!(Impl, OriginalType!T); +} + +@safe unittest +{ + alias U1 = Unsigned!int; + alias U2 = Unsigned!(const(int)); + alias U3 = Unsigned!(immutable(int)); + static assert(is(U1 == uint)); + static assert(is(U2 == const(uint))); + static assert(is(U3 == immutable(uint))); + static if (is(__vector(int[4])) && is(__vector(uint[4]))) + { + alias UV1 = Unsigned!(__vector(int[4])); + alias UV2 = Unsigned!(const(__vector(int[4]))); + static assert(is(UV1 == __vector(uint[4]))); + static assert(is(UV2 == const(__vector(uint[4])))); + } + //struct S {} + //alias U2 = Unsigned!S; + //alias U3 = Unsigned!double; + static if (is(ucent)) + { + alias U4 = Unsigned!cent; + alias U5 = Unsigned!(const(cent)); + alias U6 = Unsigned!(immutable(cent)); + static assert(is(U4 == ucent)); + static assert(is(U5 == const(ucent))); + static assert(is(U6 == immutable(ucent))); + } +} + +/** +Returns the largest type, i.e. T such that T.sizeof is the largest. If more +than one type is of the same size, the leftmost argument of these in will be +returned. +*/ +template Largest(T...) if (T.length >= 1) +{ + static if (T.length == 1) + { + alias Largest = T[0]; + } + else static if (T.length == 2) + { + static if (T[0].sizeof >= T[1].sizeof) + { + alias Largest = T[0]; + } + else + { + alias Largest = T[1]; + } + } + else + { + alias Largest = Largest!(Largest!(T[0 .. $/2]), Largest!(T[$/2 .. $])); + } +} + +/// +@safe unittest +{ + static assert(is(Largest!(uint, ubyte, ushort, real) == real)); + static assert(is(Largest!(ulong, double) == ulong)); + static assert(is(Largest!(double, ulong) == double)); + static assert(is(Largest!(uint, byte, double, short) == double)); + static if (is(ucent)) + static assert(is(Largest!(uint, ubyte, ucent, ushort) == ucent)); +} + +/** +Returns the corresponding signed type for T. T must be a numeric integral type, +otherwise a compile-time error occurs. + */ +template Signed(T) +{ + template Impl(T) + { + static if (is(T : __vector(V[N]), V, size_t N)) + alias Impl = __vector(Impl!V[N]); + else static if (isSigned!T) + alias Impl = T; + else static if (isUnsigned!T) + { + static if (is(T == ubyte )) alias Impl = byte; + static if (is(T == ushort)) alias Impl = short; + static if (is(T == uint )) alias Impl = int; + static if (is(T == ulong )) alias Impl = long; + static if (is(ucent) && is(T == ucent )) alias Impl = cent; + } + else + static assert(false, "Type " ~ T.stringof ~ + " does not have an Signed counterpart"); + } + + alias Signed = ModifyTypePreservingTQ!(Impl, OriginalType!T); +} + +/// +@safe unittest +{ + alias S1 = Signed!uint; + static assert(is(S1 == int)); + alias S2 = Signed!(const(uint)); + static assert(is(S2 == const(int))); + alias S3 = Signed!(immutable(uint)); + static assert(is(S3 == immutable(int))); + static if (is(ucent)) + { + alias S4 = Signed!ucent; + static assert(is(S4 == cent)); + } +} + +@safe unittest +{ + static assert(is(Signed!float == float)); + static if (is(__vector(int[4])) && is(__vector(uint[4]))) + { + alias SV1 = Signed!(__vector(uint[4])); + alias SV2 = Signed!(const(__vector(uint[4]))); + static assert(is(SV1 == __vector(int[4]))); + static assert(is(SV2 == const(__vector(int[4])))); + } +} + + +/** +Returns the most negative value of the numeric type T. +*/ +template mostNegative(T) + if (isNumeric!T || isSomeChar!T || isBoolean!T) +{ + static if (is(typeof(T.min_normal))) + enum mostNegative = -T.max; + else static if (T.min == 0) + enum byte mostNegative = 0; + else + enum mostNegative = T.min; +} + +/// +@safe unittest +{ + static assert(mostNegative!float == -float.max); + static assert(mostNegative!double == -double.max); + static assert(mostNegative!real == -real.max); + static assert(mostNegative!bool == false); +} + +/// +@safe unittest +{ + foreach (T; AliasSeq!(bool, byte, short, int, long)) + static assert(mostNegative!T == T.min); + + foreach (T; AliasSeq!(ubyte, ushort, uint, ulong, char, wchar, dchar)) + static assert(mostNegative!T == 0); +} + +/** +Get the type that a scalar type `T` will $(LINK2 $(ROOT_DIR)spec/type.html#integer-promotions, promote) +to in multi-term arithmetic expressions. +*/ +template Promoted(T) + if (isScalarType!T) +{ + alias Promoted = CopyTypeQualifiers!(T, typeof(T.init + T.init)); +} + +/// +@safe unittest +{ + ubyte a = 3, b = 5; + static assert(is(typeof(a * b) == Promoted!ubyte)); + static assert(is(Promoted!ubyte == int)); + + static assert(is(Promoted!(shared(bool)) == shared(int))); + static assert(is(Promoted!(const(int)) == const(int))); + static assert(is(Promoted!double == double)); +} + +@safe unittest +{ + // promote to int: + foreach (T; AliasSeq!(bool, byte, ubyte, short, ushort, char, wchar)) + { + static assert(is(Promoted!T == int)); + static assert(is(Promoted!(shared(const T)) == shared(const int))); + } + + // already promoted: + foreach (T; AliasSeq!(int, uint, long, ulong, float, double, real)) + { + static assert(is(Promoted!T == T)); + static assert(is(Promoted!(immutable(T)) == immutable(T))); + } +} + +//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// +// Misc. +//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// + +/** +Returns the mangled name of symbol or type $(D sth). + +$(D mangledName) is the same as builtin $(D .mangleof) property, but +might be more convenient in generic code, e.g. as a template argument +when invoking staticMap. + */ +template mangledName(sth...) + if (sth.length == 1) +{ + enum string mangledName = sth[0].mangleof; +} + +/// +@safe unittest +{ + alias TL = staticMap!(mangledName, int, const int, immutable int); + static assert(TL == AliasSeq!("i", "xi", "yi")); +} + +version (unittest) void freeFunc(string); + +@safe unittest +{ + class C { int value() @property { return 0; } } + static assert(mangledName!int == int.mangleof); + static assert(mangledName!C == C.mangleof); + static assert(mangledName!(C.value) == C.value.mangleof); + static assert(mangledName!(C.value)[$ - 12 .. $] == "5valueMFNdZi"); + static assert(mangledName!mangledName == "3std6traits11mangledName"); + static assert(mangledName!freeFunc == "_D3std6traits8freeFuncFAyaZv"); + int x; + static if (is(typeof({ return x; }) : int delegate() pure)) // issue 9148 + static assert(mangledName!((int a) { return a+x; }) == "DFNaNbNiNfiZi"); // pure nothrow @safe @nogc + else + static assert(mangledName!((int a) { return a+x; }) == "DFNbNiNfiZi"); // nothrow @safe @nnogc +} + +@system unittest +{ + // @system due to demangle + // Test for bug 5718 + import std.demangle : demangle; + int foo; + auto foo_demangled = demangle(mangledName!foo); + assert(foo_demangled[0 .. 4] == "int " && foo_demangled[$-3 .. $] == "foo", + foo_demangled); + + void bar(); + auto bar_demangled = demangle(mangledName!bar); + assert(bar_demangled[0 .. 5] == "void " && bar_demangled[$-5 .. $] == "bar()"); +} + + + +// XXX Select & select should go to another module. (functional or algorithm?) + +/** +Aliases itself to $(D T[0]) if the boolean $(D condition) is $(D true) +and to $(D T[1]) otherwise. + */ +template Select(bool condition, T...) if (T.length == 2) +{ + import std.meta : Alias; + alias Select = Alias!(T[!condition]); +} + +/// +@safe unittest +{ + // can select types + static assert(is(Select!(true, int, long) == int)); + static assert(is(Select!(false, int, long) == long)); + static struct Foo {} + static assert(is(Select!(false, const(int), const(Foo)) == const(Foo))); + + // can select symbols + int a = 1; + int b = 2; + alias selA = Select!(true, a, b); + alias selB = Select!(false, a, b); + assert(selA == 1); + assert(selB == 2); + + // can select (compile-time) expressions + enum val = Select!(false, -4, 9 - 6); + static assert(val == 3); +} + +/** +If $(D cond) is $(D true), returns $(D a) without evaluating $(D +b). Otherwise, returns $(D b) without evaluating $(D a). + */ +A select(bool cond : true, A, B)(A a, lazy B b) { return a; } +/// Ditto +B select(bool cond : false, A, B)(lazy A a, B b) { return b; } + +@safe unittest +{ + real pleasecallme() { return 0; } + int dontcallme() { assert(0); } + auto a = select!true(pleasecallme(), dontcallme()); + auto b = select!false(dontcallme(), pleasecallme()); + static assert(is(typeof(a) == real)); + static assert(is(typeof(b) == real)); +} + +/++ + Determine if a symbol has a given + $(DDSUBLINK spec/attribute, uda, user-defined attribute). + + See_Also: + $(LREF getUDAs) + +/ +template hasUDA(alias symbol, alias attribute) +{ + enum hasUDA = getUDAs!(symbol, attribute).length != 0; +} + +/// +@safe unittest +{ + enum E; + struct S {} + + @("alpha") int a; + static assert(hasUDA!(a, "alpha")); + static assert(!hasUDA!(a, S)); + static assert(!hasUDA!(a, E)); + + @(E) int b; + static assert(!hasUDA!(b, "alpha")); + static assert(!hasUDA!(b, S)); + static assert(hasUDA!(b, E)); + + @E int c; + static assert(!hasUDA!(c, "alpha")); + static assert(!hasUDA!(c, S)); + static assert(hasUDA!(c, E)); + + @(S, E) int d; + static assert(!hasUDA!(d, "alpha")); + static assert(hasUDA!(d, S)); + static assert(hasUDA!(d, E)); + + @S int e; + static assert(!hasUDA!(e, "alpha")); + static assert(hasUDA!(e, S)); + static assert(!hasUDA!(e, S())); + static assert(!hasUDA!(e, E)); + + @S() int f; + static assert(!hasUDA!(f, "alpha")); + static assert(hasUDA!(f, S)); + static assert(hasUDA!(f, S())); + static assert(!hasUDA!(f, E)); + + @(S, E, "alpha") int g; + static assert(hasUDA!(g, "alpha")); + static assert(hasUDA!(g, S)); + static assert(hasUDA!(g, E)); + + @(100) int h; + static assert(hasUDA!(h, 100)); + + struct Named { string name; } + + @Named("abc") int i; + static assert(hasUDA!(i, Named)); + static assert(hasUDA!(i, Named("abc"))); + static assert(!hasUDA!(i, Named("def"))); + + struct AttrT(T) + { + string name; + T value; + } + + @AttrT!int("answer", 42) int j; + static assert(hasUDA!(j, AttrT)); + static assert(hasUDA!(j, AttrT!int)); + static assert(!hasUDA!(j, AttrT!string)); + + @AttrT!string("hello", "world") int k; + static assert(hasUDA!(k, AttrT)); + static assert(!hasUDA!(k, AttrT!int)); + static assert(hasUDA!(k, AttrT!string)); + + struct FuncAttr(alias f) { alias func = f; } + static int fourtyTwo() { return 42; } + static size_t getLen(string s) { return s.length; } + + @FuncAttr!getLen int l; + static assert(hasUDA!(l, FuncAttr)); + static assert(!hasUDA!(l, FuncAttr!fourtyTwo)); + static assert(hasUDA!(l, FuncAttr!getLen)); + static assert(!hasUDA!(l, FuncAttr!fourtyTwo())); + static assert(!hasUDA!(l, FuncAttr!getLen())); + + @FuncAttr!getLen() int m; + static assert(hasUDA!(m, FuncAttr)); + static assert(!hasUDA!(m, FuncAttr!fourtyTwo)); + static assert(hasUDA!(m, FuncAttr!getLen)); + static assert(!hasUDA!(m, FuncAttr!fourtyTwo())); + static assert(hasUDA!(m, FuncAttr!getLen())); +} + +/++ + Gets the matching $(DDSUBLINK spec/attribute, uda, user-defined attributes) + from the given symbol. + + If the UDA is a type, then any UDAs of the same type on the symbol will + match. If the UDA is a template for a type, then any UDA which is an + instantiation of that template will match. And if the UDA is a value, + then any UDAs on the symbol which are equal to that value will match. + + See_Also: + $(LREF hasUDA) + +/ +template getUDAs(alias symbol, alias attribute) +{ + import std.meta : Filter; + + template isDesiredUDA(alias toCheck) + { + static if (is(typeof(attribute)) && !__traits(isTemplate, attribute)) + { + static if (__traits(compiles, toCheck == attribute)) + enum isDesiredUDA = toCheck == attribute; + else + enum isDesiredUDA = false; + } + else static if (is(typeof(toCheck))) + { + static if (__traits(isTemplate, attribute)) + enum isDesiredUDA = isInstanceOf!(attribute, typeof(toCheck)); + else + enum isDesiredUDA = is(typeof(toCheck) == attribute); + } + else static if (__traits(isTemplate, attribute)) + enum isDesiredUDA = isInstanceOf!(attribute, toCheck); + else + enum isDesiredUDA = is(toCheck == attribute); + } + alias getUDAs = Filter!(isDesiredUDA, __traits(getAttributes, symbol)); +} + +/// +@safe unittest +{ + struct Attr + { + string name; + int value; + } + + @Attr("Answer", 42) int a; + static assert(getUDAs!(a, Attr).length == 1); + static assert(getUDAs!(a, Attr)[0].name == "Answer"); + static assert(getUDAs!(a, Attr)[0].value == 42); + + @(Attr("Answer", 42), "string", 9999) int b; + static assert(getUDAs!(b, Attr).length == 1); + static assert(getUDAs!(b, Attr)[0].name == "Answer"); + static assert(getUDAs!(b, Attr)[0].value == 42); + + @Attr("Answer", 42) @Attr("Pi", 3) int c; + static assert(getUDAs!(c, Attr).length == 2); + static assert(getUDAs!(c, Attr)[0].name == "Answer"); + static assert(getUDAs!(c, Attr)[0].value == 42); + static assert(getUDAs!(c, Attr)[1].name == "Pi"); + static assert(getUDAs!(c, Attr)[1].value == 3); + + static assert(getUDAs!(c, Attr("Answer", 42)).length == 1); + static assert(getUDAs!(c, Attr("Answer", 42))[0].name == "Answer"); + static assert(getUDAs!(c, Attr("Answer", 42))[0].value == 42); + + static assert(getUDAs!(c, Attr("Answer", 99)).length == 0); + + struct AttrT(T) + { + string name; + T value; + } + + @AttrT!uint("Answer", 42) @AttrT!int("Pi", 3) @AttrT int d; + static assert(getUDAs!(d, AttrT).length == 2); + static assert(getUDAs!(d, AttrT)[0].name == "Answer"); + static assert(getUDAs!(d, AttrT)[0].value == 42); + static assert(getUDAs!(d, AttrT)[1].name == "Pi"); + static assert(getUDAs!(d, AttrT)[1].value == 3); + + static assert(getUDAs!(d, AttrT!uint).length == 1); + static assert(getUDAs!(d, AttrT!uint)[0].name == "Answer"); + static assert(getUDAs!(d, AttrT!uint)[0].value == 42); + + static assert(getUDAs!(d, AttrT!int).length == 1); + static assert(getUDAs!(d, AttrT!int)[0].name == "Pi"); + static assert(getUDAs!(d, AttrT!int)[0].value == 3); + + struct SimpleAttr {} + + @SimpleAttr int e; + static assert(getUDAs!(e, SimpleAttr).length == 1); + static assert(is(getUDAs!(e, SimpleAttr)[0] == SimpleAttr)); + + @SimpleAttr() int f; + static assert(getUDAs!(f, SimpleAttr).length == 1); + static assert(is(typeof(getUDAs!(f, SimpleAttr)[0]) == SimpleAttr)); + + struct FuncAttr(alias f) { alias func = f; } + static int add42(int v) { return v + 42; } + static string concat(string l, string r) { return l ~ r; } + + @FuncAttr!add42 int g; + static assert(getUDAs!(g, FuncAttr).length == 1); + static assert(getUDAs!(g, FuncAttr)[0].func(5) == 47); + + static assert(getUDAs!(g, FuncAttr!add42).length == 1); + static assert(getUDAs!(g, FuncAttr!add42)[0].func(5) == 47); + + static assert(getUDAs!(g, FuncAttr!add42()).length == 0); + + static assert(getUDAs!(g, FuncAttr!concat).length == 0); + static assert(getUDAs!(g, FuncAttr!concat()).length == 0); + + @FuncAttr!add42() int h; + static assert(getUDAs!(h, FuncAttr).length == 1); + static assert(getUDAs!(h, FuncAttr)[0].func(5) == 47); + + static assert(getUDAs!(h, FuncAttr!add42).length == 1); + static assert(getUDAs!(h, FuncAttr!add42)[0].func(5) == 47); + + static assert(getUDAs!(h, FuncAttr!add42()).length == 1); + static assert(getUDAs!(h, FuncAttr!add42())[0].func(5) == 47); + + static assert(getUDAs!(h, FuncAttr!concat).length == 0); + static assert(getUDAs!(h, FuncAttr!concat()).length == 0); + + @("alpha") @(42) int i; + static assert(getUDAs!(i, "alpha").length == 1); + static assert(getUDAs!(i, "alpha")[0] == "alpha"); + + static assert(getUDAs!(i, 42).length == 1); + static assert(getUDAs!(i, 42)[0] == 42); + + static assert(getUDAs!(i, 'c').length == 0); +} + +/** + * Gets all symbols within `symbol` that have the given user-defined attribute. + * This is not recursive; it will not search for symbols within symbols such as + * nested structs or unions. + */ +template getSymbolsByUDA(alias symbol, alias attribute) +{ + import std.format : format; + import std.meta : AliasSeq, Filter; + + // translate a list of strings into symbols. mixing in the entire alias + // avoids trying to access the symbol, which could cause a privacy violation + template toSymbols(names...) + { + static if (names.length == 0) + alias toSymbols = AliasSeq!(); + else + mixin("alias toSymbols = AliasSeq!(symbol.%s, toSymbols!(names[1..$]));" + .format(names[0])); + } + + // filtering inaccessible members + enum isAccessibleMember(string name) = __traits(compiles, __traits(getMember, symbol, name)); + alias accessibleMembers = Filter!(isAccessibleMember, __traits(allMembers, symbol)); + + // filtering not compiled members such as alias of basic types + enum hasSpecificUDA(string name) = mixin("hasUDA!(symbol." ~ name ~ ", attribute)"); + enum isCorrectMember(string name) = __traits(compiles, hasSpecificUDA!(name)); + + alias correctMembers = Filter!(isCorrectMember, accessibleMembers); + alias membersWithUDA = toSymbols!(Filter!(hasSpecificUDA, correctMembers)); + + // if the symbol itself has the UDA, tack it on to the front of the list + static if (hasUDA!(symbol, attribute)) + alias getSymbolsByUDA = AliasSeq!(symbol, membersWithUDA); + else + alias getSymbolsByUDA = membersWithUDA; +} + +/// +@safe unittest +{ + enum Attr; + + static struct A + { + @Attr int a; + int b; + @Attr void doStuff() {} + void doOtherStuff() {} + static struct Inner + { + // Not found by getSymbolsByUDA + @Attr int c; + } + } + + // Finds both variables and functions with the attribute, but + // doesn't include the variables and functions without it. + static assert(getSymbolsByUDA!(A, Attr).length == 2); + // Can access attributes on the symbols returned by getSymbolsByUDA. + static assert(hasUDA!(getSymbolsByUDA!(A, Attr)[0], Attr)); + static assert(hasUDA!(getSymbolsByUDA!(A, Attr)[1], Attr)); + + static struct UDA { string name; } + + static struct B + { + @UDA("X") + int x; + @UDA("Y") + int y; + @(100) + int z; + } + + // Finds both UDA attributes. + static assert(getSymbolsByUDA!(B, UDA).length == 2); + // Finds one `100` attribute. + static assert(getSymbolsByUDA!(B, 100).length == 1); + // Can get the value of the UDA from the return value + static assert(getUDAs!(getSymbolsByUDA!(B, UDA)[0], UDA)[0].name == "X"); + + @UDA("A") + static struct C + { + @UDA("B") + int d; + } + + // Also checks the symbol itself + static assert(getSymbolsByUDA!(C, UDA).length == 2); + static assert(getSymbolsByUDA!(C, UDA)[0].stringof == "C"); + static assert(getSymbolsByUDA!(C, UDA)[1].stringof == "d"); + + static struct D + { + int x; + } + + //Finds nothing if there is no member with specific UDA + static assert(getSymbolsByUDA!(D,UDA).length == 0); +} + +// #15335: getSymbolsByUDA fails if type has private members +@safe unittest +{ + // HasPrivateMembers has, well, private members, one of which has a UDA. + import std.internal.test.uda : Attr, HasPrivateMembers; + // Trying access to private member from another file therefore we do not have access + // for this otherwise we get deprecation warning - not visible from module + static assert(getSymbolsByUDA!(HasPrivateMembers, Attr).length == 1); + static assert(hasUDA!(getSymbolsByUDA!(HasPrivateMembers, Attr)[0], Attr)); +} + +/// +@safe unittest +{ + enum Attr; + struct A + { + alias int INT; + alias void function(INT) SomeFunction; + @Attr int a; + int b; + @Attr private int c; + private int d; + } + + // Here everything is fine, we have access to private member c + static assert(getSymbolsByUDA!(A, Attr).length == 2); + static assert(hasUDA!(getSymbolsByUDA!(A, Attr)[0], Attr)); + static assert(hasUDA!(getSymbolsByUDA!(A, Attr)[1], Attr)); +} + +// #16387: getSymbolsByUDA works with structs but fails with classes +@safe unittest +{ + enum Attr; + class A + { + @Attr uint a; + } + + alias res = getSymbolsByUDA!(A, Attr); + static assert(res.length == 1); + static assert(res[0].stringof == "a"); +} + +/** + Returns: $(D true) iff all types $(D T) are the same. +*/ +template allSameType(T...) +{ + static if (T.length <= 1) + { + enum bool allSameType = true; + } + else + { + enum bool allSameType = is(T[0] == T[1]) && allSameType!(T[1..$]); + } +} + +/// +@safe unittest +{ + static assert(allSameType!(int, int)); + static assert(allSameType!(int, int, int)); + static assert(allSameType!(float, float, float)); + static assert(!allSameType!(int, double)); + static assert(!allSameType!(int, float, double)); + static assert(!allSameType!(int, float, double, real)); + static assert(!allSameType!(short, int, float, double, real)); +} + +/** + Returns: $(D true) iff the type $(D T) can be tested in an $(D + if)-expression, that is if $(D if (pred(T.init)) {}) is compilable. +*/ +enum ifTestable(T, alias pred = a => a) = __traits(compiles, { if (pred(T.init)) {} }); + +@safe unittest +{ + import std.meta : AliasSeq, allSatisfy; + static assert(allSatisfy!(ifTestable, AliasSeq!(bool, int, float, double, string))); + struct BoolWrapper { bool value; } + static assert(!ifTestable!(bool, a => BoolWrapper(a))); +} + +/** + * Detect whether `X` is a type. Analogous to `is(X)`. This is useful when used + * in conjunction with other templates, e.g. `allSatisfy!(isType, X)`. + * + * Returns: + * `true` if `X` is a type, `false` otherwise + */ +template isType(X...) if (X.length == 1) +{ + enum isType = is(X[0]); +} + +/// +@safe unittest +{ + struct S { + template Test() {} + } + class C {} + interface I {} + union U {} + static assert(isType!int); + static assert(isType!string); + static assert(isType!(int[int])); + static assert(isType!S); + static assert(isType!C); + static assert(isType!I); + static assert(isType!U); + + int n; + void func(){} + static assert(!isType!n); + static assert(!isType!func); + static assert(!isType!(S.Test)); + static assert(!isType!(S.Test!())); +} + +/** + * Detect whether symbol or type `X` is a function. This is different that finding + * if a symbol is callable or satisfying `is(X == function)`, it finds + * specifically if the symbol represents a normal function declaration, i.e. + * not a delegate or a function pointer. + * + * Returns: + * `true` if `X` is a function, `false` otherwise + * + * See_Also: + * Use $(LREF isFunctionPointer) or $(LREF isDelegate) for detecting those types + * respectively. + */ +template isFunction(X...) if (X.length == 1) +{ + static if (is(typeof(&X[0]) U : U*) && is(U == function) || + is(typeof(&X[0]) U == delegate)) + { + // x is a (nested) function symbol. + enum isFunction = true; + } + else static if (is(X[0] T)) + { + // x is a type. Take the type of it and examine. + enum isFunction = is(T == function); + } + else + enum isFunction = false; +} + +/// +@safe unittest +{ + static void func(){} + static assert(isFunction!func); + + struct S + { + void func(){} + } + static assert(isFunction!(S.func)); +} + +/** + * Detect whether `X` is a final method or class. + * + * Returns: + * `true` if `X` is final, `false` otherwise + */ +template isFinal(X...) if (X.length == 1) +{ + static if (is(X[0] == class)) + enum isFinal = __traits(isFinalClass, X[0]); + else static if (isFunction!X) + enum isFinal = __traits(isFinalFunction, X[0]); + else + enum isFinal = false; +} + +/// +@safe unittest +{ + class C + { + void nf() {} + static void sf() {} + final void ff() {} + } + final class FC { } + + static assert(!isFinal!(C)); + static assert( isFinal!(FC)); + + static assert(!isFinal!(C.nf)); + static assert(!isFinal!(C.sf)); + static assert( isFinal!(C.ff)); +} + +/++ + + Determines whether the type `S` can be copied. + + If a type cannot be copied, then code such as `MyStruct x; auto y = x;` will fail to compile. + + Copying for structs can be disabled by using `@disable this(this)`. + + + + Params: + + S = The type to check. + + + + Returns: + + `true` if `S` can be copied. `false` otherwise. + + ++/ +enum isCopyable(S) = is(typeof( + { S foo = S.init; S copy = foo; } +)); + +/// +@safe unittest +{ + struct S1 {} // Fine. Can be copied + struct S2 { this(this) {}} // Fine. Can be copied + struct S3 {@disable this(this) {}} // Not fine. Copying is disabled. + struct S4 {S3 s;} // Not fine. A field has copying disabled. + + class C1 {} + + static assert( isCopyable!S1); + static assert( isCopyable!S2); + static assert(!isCopyable!S3); + static assert(!isCopyable!S4); + + static assert(isCopyable!C1); + static assert(isCopyable!int); + static assert(isCopyable!(int[])); +} diff --git a/libphobos/src/std/typecons.d b/libphobos/src/std/typecons.d new file mode 100644 index 0000000..a63227c --- /dev/null +++ b/libphobos/src/std/typecons.d @@ -0,0 +1,8029 @@ +// Written in the D programming language. + +/** +This module implements a variety of type constructors, i.e., templates +that allow construction of new, useful general-purpose types. + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE, +$(TR $(TH Category) $(TH Functions)) +$(TR $(TD Tuple) $(TD + $(LREF isTuple) + $(LREF Tuple) + $(LREF tuple) + $(LREF reverse) +)) +$(TR $(TD Flags) $(TD + $(LREF BitFlags) + $(LREF isBitFlagEnum) + $(LREF Flag) + $(LREF No) + $(LREF Yes) +)) +$(TR $(TD Memory allocation) $(TD + $(LREF RefCounted) + $(LREF refCounted) + $(LREF RefCountedAutoInitialize) + $(LREF scoped) + $(LREF Unique) +)) +$(TR $(TD Code generation) $(TD + $(LREF AutoImplement) + $(LREF BlackHole) + $(LREF generateAssertTrap) + $(LREF generateEmptyFunction) + $(LREF WhiteHole) +)) +$(TR $(TD Nullable) $(TD + $(LREF Nullable) + $(LREF nullable) + $(LREF NullableRef) + $(LREF nullableRef) +)) +$(TR $(TD Proxies) $(TD + $(LREF Proxy) + $(LREF rebindable) + $(LREF Rebindable) + $(LREF ReplaceType) + $(LREF unwrap) + $(LREF wrap) +)) +$(TR $(TD Types) $(TD + $(LREF alignForSize) + $(LREF Ternary) + $(LREF Typedef) + $(LREF TypedefType) + $(LREF UnqualRef) +)) +) + +Copyright: Copyright the respective authors, 2008- +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). +Source: $(PHOBOSSRC std/_typecons.d) +Authors: $(HTTP erdani.org, Andrei Alexandrescu), + $(HTTP bartoszmilewski.wordpress.com, Bartosz Milewski), + Don Clugston, + Shin Fujishiro, + Kenji Hara + */ +module std.typecons; + +import core.stdc.stdint : uintptr_t; +import std.meta; // : AliasSeq, allSatisfy; +import std.traits; + +/// +@safe unittest +{ + // value tuples + alias Coord = Tuple!(int, "x", int, "y", int, "z"); + Coord c; + c[1] = 1; // access by index + c.z = 1; // access by given name + assert(c == Coord(0, 1, 1)); + + // names can be omitted + alias DicEntry = Tuple!(string, string); + + // tuples can also be constructed on instantiation + assert(tuple(2, 3, 4)[1] == 3); + // construction on instantiation works with names too + assert(tuple!("x", "y", "z")(2, 3, 4).y == 3); + + // Rebindable references to const and immutable objects + { + class Widget { void foo() const @safe {} } + const w1 = new Widget, w2 = new Widget; + w1.foo(); + // w1 = w2 would not work; can't rebind const object + auto r = Rebindable!(const Widget)(w1); + // invoke method as if r were a Widget object + r.foo(); + // rebind r to refer to another object + r = w2; + } +} + +debug(Unique) import std.stdio; + +/** +Encapsulates unique ownership of a resource. + +When a $(D Unique!T) goes out of scope it will call $(D destroy) +on the resource $(D T) that it manages, unless it is transferred. +One important consequence of $(D destroy) is that it will call the +destructor of the resource $(D T). GC-managed references are not +guaranteed to be valid during a destructor call, but other members of +$(D T), such as file handles or pointers to $(D malloc) memory, will +still be valid during the destructor call. This allows the resource +$(D T) to deallocate or clean up any non-GC resources. + +If it is desirable to persist a $(D Unique!T) outside of its original +scope, then it can be transferred. The transfer can be explicit, by +calling $(D release), or implicit, when returning Unique from a +function. The resource $(D T) can be a polymorphic class object or +instance of an interface, in which case Unique behaves polymorphically +too. + +If $(D T) is a value type, then $(D Unique!T) will be implemented +as a reference to a $(D T). +*/ +struct Unique(T) +{ +/** Represents a reference to $(D T). Resolves to $(D T*) if $(D T) is a value type. */ +static if (is(T == class) || is(T == interface)) + alias RefT = T; +else + alias RefT = T*; + +public: + // Deferred in case we get some language support for checking uniqueness. + version (None) + /** + Allows safe construction of $(D Unique). It creates the resource and + guarantees unique ownership of it (unless $(D T) publishes aliases of + $(D this)). + Note: Nested structs/classes cannot be created. + Params: + args = Arguments to pass to $(D T)'s constructor. + --- + static class C {} + auto u = Unique!(C).create(); + --- + */ + static Unique!T create(A...)(auto ref A args) + if (__traits(compiles, new T(args))) + { + debug(Unique) writeln("Unique.create for ", T.stringof); + Unique!T u; + u._p = new T(args); + return u; + } + + /** + Constructor that takes an rvalue. + It will ensure uniqueness, as long as the rvalue + isn't just a view on an lvalue (e.g., a cast). + Typical usage: + ---- + Unique!Foo f = new Foo; + ---- + */ + this(RefT p) + { + debug(Unique) writeln("Unique constructor with rvalue"); + _p = p; + } + /** + Constructor that takes an lvalue. It nulls its source. + The nulling will ensure uniqueness as long as there + are no previous aliases to the source. + */ + this(ref RefT p) + { + _p = p; + debug(Unique) writeln("Unique constructor nulling source"); + p = null; + assert(p is null); + } + /** + Constructor that takes a $(D Unique) of a type that is convertible to our type. + + Typically used to transfer a $(D Unique) rvalue of derived type to + a $(D Unique) of base type. + Example: + --- + class C : Object {} + + Unique!C uc = new C; + Unique!Object uo = uc.release; + --- + */ + this(U)(Unique!U u) + if (is(u.RefT:RefT)) + { + debug(Unique) writeln("Unique constructor converting from ", U.stringof); + _p = u._p; + u._p = null; + } + + /// Transfer ownership from a $(D Unique) of a type that is convertible to our type. + void opAssign(U)(Unique!U u) + if (is(u.RefT:RefT)) + { + debug(Unique) writeln("Unique opAssign converting from ", U.stringof); + // first delete any resource we own + destroy(this); + _p = u._p; + u._p = null; + } + + ~this() + { + debug(Unique) writeln("Unique destructor of ", (_p is null)? null: _p); + if (_p !is null) + { + destroy(_p); + _p = null; + } + } + + /** Returns whether the resource exists. */ + @property bool isEmpty() const + { + return _p is null; + } + /** Transfer ownership to a $(D Unique) rvalue. Nullifies the current contents. + Same as calling std.algorithm.move on it. + */ + Unique release() + { + debug(Unique) writeln("Unique Release"); + import std.algorithm.mutation : move; + return this.move; + } + + /** Forwards member access to contents. */ + mixin Proxy!_p; + + /** + Postblit operator is undefined to prevent the cloning of $(D Unique) objects. + */ + @disable this(this); + +private: + RefT _p; +} + +/// +@system unittest +{ + static struct S + { + int i; + this(int i){this.i = i;} + } + Unique!S produce() + { + // Construct a unique instance of S on the heap + Unique!S ut = new S(5); + // Implicit transfer of ownership + return ut; + } + // Borrow a unique resource by ref + void increment(ref Unique!S ur) + { + ur.i++; + } + void consume(Unique!S u2) + { + assert(u2.i == 6); + // Resource automatically deleted here + } + Unique!S u1; + assert(u1.isEmpty); + u1 = produce(); + increment(u1); + assert(u1.i == 6); + //consume(u1); // Error: u1 is not copyable + // Transfer ownership of the resource + consume(u1.release); + assert(u1.isEmpty); +} + +@system unittest +{ + // test conversion to base ref + int deleted = 0; + class C + { + ~this(){deleted++;} + } + // constructor conversion + Unique!Object u = Unique!C(new C); + static assert(!__traits(compiles, {u = new C;})); + assert(!u.isEmpty); + destroy(u); + assert(deleted == 1); + + Unique!C uc = new C; + static assert(!__traits(compiles, {Unique!Object uo = uc;})); + Unique!Object uo = new C; + // opAssign conversion, deleting uo resource first + uo = uc.release; + assert(uc.isEmpty); + assert(!uo.isEmpty); + assert(deleted == 2); +} + +@system unittest +{ + debug(Unique) writeln("Unique class"); + class Bar + { + ~this() { debug(Unique) writeln(" Bar destructor"); } + int val() const { return 4; } + } + alias UBar = Unique!(Bar); + UBar g(UBar u) + { + debug(Unique) writeln("inside g"); + return u.release; + } + auto ub = UBar(new Bar); + assert(!ub.isEmpty); + assert(ub.val == 4); + static assert(!__traits(compiles, {auto ub3 = g(ub);})); + debug(Unique) writeln("Calling g"); + auto ub2 = g(ub.release); + debug(Unique) writeln("Returned from g"); + assert(ub.isEmpty); + assert(!ub2.isEmpty); +} + +@system unittest +{ + debug(Unique) writeln("Unique interface"); + interface Bar + { + int val() const; + } + class BarImpl : Bar + { + static int count; + this() + { + count++; + } + ~this() + { + count--; + } + int val() const { return 4; } + } + alias UBar = Unique!Bar; + UBar g(UBar u) + { + debug(Unique) writeln("inside g"); + return u.release; + } + void consume(UBar u) + { + assert(u.val() == 4); + // Resource automatically deleted here + } + auto ub = UBar(new BarImpl); + assert(BarImpl.count == 1); + assert(!ub.isEmpty); + assert(ub.val == 4); + static assert(!__traits(compiles, {auto ub3 = g(ub);})); + debug(Unique) writeln("Calling g"); + auto ub2 = g(ub.release); + debug(Unique) writeln("Returned from g"); + assert(ub.isEmpty); + assert(!ub2.isEmpty); + consume(ub2.release); + assert(BarImpl.count == 0); +} + +@system unittest +{ + debug(Unique) writeln("Unique struct"); + struct Foo + { + ~this() { debug(Unique) writeln(" Foo destructor"); } + int val() const { return 3; } + @disable this(this); + } + alias UFoo = Unique!(Foo); + + UFoo f(UFoo u) + { + debug(Unique) writeln("inside f"); + return u.release; + } + + auto uf = UFoo(new Foo); + assert(!uf.isEmpty); + assert(uf.val == 3); + static assert(!__traits(compiles, {auto uf3 = f(uf);})); + debug(Unique) writeln("Unique struct: calling f"); + auto uf2 = f(uf.release); + debug(Unique) writeln("Unique struct: returned from f"); + assert(uf.isEmpty); + assert(!uf2.isEmpty); +} + +// ensure Unique behaves correctly through const access paths +@system unittest +{ + struct Bar {int val;} + struct Foo + { + Unique!Bar bar = new Bar; + } + + Foo foo; + foo.bar.val = 6; + const Foo* ptr = &foo; + static assert(is(typeof(ptr) == const(Foo*))); + static assert(is(typeof(ptr.bar) == const(Unique!Bar))); + static assert(is(typeof(ptr.bar.val) == const(int))); + assert(ptr.bar.val == 6); + foo.bar.val = 7; + assert(ptr.bar.val == 7); +} + +// Used in Tuple.toString +private template sharedToString(alias field) + if (is(typeof(field) == shared)) +{ + static immutable sharedToString = typeof(field).stringof; +} + +private template sharedToString(alias field) + if (!is(typeof(field) == shared)) +{ + alias sharedToString = field; +} + +/** +_Tuple of values, for example $(D Tuple!(int, string)) is a record that +stores an $(D int) and a $(D string). $(D Tuple) can be used to bundle +values together, notably when returning multiple values from a +function. If $(D obj) is a `Tuple`, the individual members are +accessible with the syntax $(D obj[0]) for the first field, $(D obj[1]) +for the second, and so on. + +The choice of zero-based indexing instead of one-base indexing was +motivated by the ability to use value tuples with various compile-time +loop constructs (e.g. $(REF AliasSeq, std,meta) iteration), all of which use +zero-based indexing. + +See_Also: $(LREF tuple). + +Params: + Specs = A list of types (and optionally, member names) that the `Tuple` contains. +*/ +template Tuple(Specs...) +{ + import std.meta : staticMap; + + // Parse (type,name) pairs (FieldSpecs) out of the specified + // arguments. Some fields would have name, others not. + template parseSpecs(Specs...) + { + static if (Specs.length == 0) + { + alias parseSpecs = AliasSeq!(); + } + else static if (is(Specs[0])) + { + static if (is(typeof(Specs[1]) : string)) + { + alias parseSpecs = + AliasSeq!(FieldSpec!(Specs[0 .. 2]), + parseSpecs!(Specs[2 .. $])); + } + else + { + alias parseSpecs = + AliasSeq!(FieldSpec!(Specs[0]), + parseSpecs!(Specs[1 .. $])); + } + } + else + { + static assert(0, "Attempted to instantiate Tuple with an " + ~"invalid argument: "~ Specs[0].stringof); + } + } + + template FieldSpec(T, string s = "") + { + alias Type = T; + alias name = s; + } + + alias fieldSpecs = parseSpecs!Specs; + + // Used with staticMap. + alias extractType(alias spec) = spec.Type; + alias extractName(alias spec) = spec.name; + + // Generates named fields as follows: + // alias name_0 = Identity!(field[0]); + // alias name_1 = Identity!(field[1]); + // : + // NOTE: field[k] is an expression (which yields a symbol of a + // variable) and can't be aliased directly. + string injectNamedFields() + { + string decl = ""; + foreach (i, name; staticMap!(extractName, fieldSpecs)) + { + import std.format : format; + + decl ~= format("alias _%s = Identity!(field[%s]);", i, i); + if (name.length != 0) + { + decl ~= format("alias %s = _%s;", name, i); + } + } + return decl; + } + + // Returns Specs for a subtuple this[from .. to] preserving field + // names if any. + alias sliceSpecs(size_t from, size_t to) = + staticMap!(expandSpec, fieldSpecs[from .. to]); + + template expandSpec(alias spec) + { + static if (spec.name.length == 0) + { + alias expandSpec = AliasSeq!(spec.Type); + } + else + { + alias expandSpec = AliasSeq!(spec.Type, spec.name); + } + } + + enum areCompatibleTuples(Tup1, Tup2, string op) = isTuple!Tup2 && is(typeof( + (ref Tup1 tup1, ref Tup2 tup2) + { + static assert(tup1.field.length == tup2.field.length); + foreach (i, _; Tup1.Types) + { + auto lhs = typeof(tup1.field[i]).init; + auto rhs = typeof(tup2.field[i]).init; + static if (op == "=") + lhs = rhs; + else + auto result = mixin("lhs "~op~" rhs"); + } + })); + + enum areBuildCompatibleTuples(Tup1, Tup2) = isTuple!Tup2 && is(typeof( + { + static assert(Tup1.Types.length == Tup2.Types.length); + foreach (i, _; Tup1.Types) + static assert(isBuildable!(Tup1.Types[i], Tup2.Types[i])); + })); + + /+ Returns $(D true) iff a $(D T) can be initialized from a $(D U). +/ + enum isBuildable(T, U) = is(typeof( + { + U u = U.init; + T t = u; + })); + /+ Helper for partial instanciation +/ + template isBuildableFrom(U) + { + enum isBuildableFrom(T) = isBuildable!(T, U); + } + + struct Tuple + { + /** + * The types of the `Tuple`'s components. + */ + alias Types = staticMap!(extractType, fieldSpecs); + + /// + static if (Specs.length == 0) @safe unittest + { + alias Fields = Tuple!(int, "id", string, float); + static assert(is(Fields.Types == AliasSeq!(int, string, float))); + } + + /** + * The names of the `Tuple`'s components. Unnamed fields have empty names. + */ + alias fieldNames = staticMap!(extractName, fieldSpecs); + + /// + static if (Specs.length == 0) @safe unittest + { + alias Fields = Tuple!(int, "id", string, float); + static assert(Fields.fieldNames == AliasSeq!("id", "", "")); + } + + /** + * Use $(D t.expand) for a `Tuple` $(D t) to expand it into its + * components. The result of $(D expand) acts as if the `Tuple`'s components + * were listed as a list of values. (Ordinarily, a $(D Tuple) acts as a + * single value.) + */ + Types expand; + mixin(injectNamedFields()); + + /// + static if (Specs.length == 0) @safe unittest + { + auto t1 = tuple(1, " hello ", 2.3); + assert(t1.toString() == `Tuple!(int, string, double)(1, " hello ", 2.3)`); + + void takeSeveralTypes(int n, string s, bool b) + { + assert(n == 4 && s == "test" && b == false); + } + + auto t2 = tuple(4, "test", false); + //t.expand acting as a list of values + takeSeveralTypes(t2.expand); + } + + static if (is(Specs)) + { + // This is mostly to make t[n] work. + alias expand this; + } + else + { + @property + ref inout(Tuple!Types) _Tuple_super() inout @trusted + { + foreach (i, _; Types) // Rely on the field layout + { + static assert(typeof(return).init.tupleof[i].offsetof == + expand[i].offsetof); + } + return *cast(typeof(return)*) &(field[0]); + } + // This is mostly to make t[n] work. + alias _Tuple_super this; + } + + // backwards compatibility + alias field = expand; + + /** + * Constructor taking one value for each field. + * + * Params: + * values = A list of values that are either the same + * types as those given by the `Types` field + * of this `Tuple`, or can implicitly convert + * to those types. They must be in the same + * order as they appear in `Types`. + */ + static if (Types.length > 0) + { + this(Types values) + { + field[] = values[]; + } + } + + /// + static if (Specs.length == 0) @safe unittest + { + alias ISD = Tuple!(int, string, double); + auto tup = ISD(1, "test", 3.2); + assert(tup.toString() == `Tuple!(int, string, double)(1, "test", 3.2)`); + } + + /** + * Constructor taking a compatible array. + * + * Params: + * values = A compatible static array to build the `Tuple` from. + * Array slices are not supported. + */ + this(U, size_t n)(U[n] values) + if (n == Types.length && allSatisfy!(isBuildableFrom!U, Types)) + { + foreach (i, _; Types) + { + field[i] = values[i]; + } + } + + /// + static if (Specs.length == 0) @safe unittest + { + int[2] ints; + Tuple!(int, int) t = ints; + } + + /** + * Constructor taking a compatible `Tuple`. Two `Tuple`s are compatible + * $(B iff) they are both of the same length, and, for each type `T` on the + * left-hand side, the corresponding type `U` on the right-hand side can + * implicitly convert to `T`. + * + * Params: + * another = A compatible `Tuple` to build from. Its type must be + * compatible with the target `Tuple`'s type. + */ + this(U)(U another) + if (areBuildCompatibleTuples!(typeof(this), U)) + { + field[] = another.field[]; + } + + /// + static if (Specs.length == 0) @safe unittest + { + alias IntVec = Tuple!(int, int, int); + alias DubVec = Tuple!(double, double, double); + + IntVec iv = tuple(1, 1, 1); + + //Ok, int can implicitly convert to double + DubVec dv = iv; + //Error: double cannot implicitly convert to int + //IntVec iv2 = dv; + } + + /** + * Comparison for equality. Two `Tuple`s are considered equal + * $(B iff) they fulfill the following criteria: + * + * $(UL + * $(LI Each `Tuple` is the same length.) + * $(LI For each type `T` on the left-hand side and each type + * `U` on the right-hand side, values of type `T` can be + * compared with values of type `U`.) + * $(LI For each value `v1` on the left-hand side and each value + * `v2` on the right-hand side, the expression `v1 == v2` is + * true.)) + * + * Params: + * rhs = The `Tuple` to compare against. It must meeting the criteria + * for comparison between `Tuple`s. + * + * Returns: + * true if both `Tuple`s are equal, otherwise false. + */ + bool opEquals(R)(R rhs) + if (areCompatibleTuples!(typeof(this), R, "==")) + { + return field[] == rhs.field[]; + } + + /// ditto + bool opEquals(R)(R rhs) const + if (areCompatibleTuples!(typeof(this), R, "==")) + { + return field[] == rhs.field[]; + } + + /// + static if (Specs.length == 0) @safe unittest + { + Tuple!(int, string) t1 = tuple(1, "test"); + Tuple!(double, string) t2 = tuple(1.0, "test"); + //Ok, int can be compared with double and + //both have a value of 1 + assert(t1 == t2); + } + + /** + * Comparison for ordering. + * + * Params: + * rhs = The `Tuple` to compare against. It must meet the criteria + * for comparison between `Tuple`s. + * + * Returns: + * For any values `v1` on the right-hand side and `v2` on the + * left-hand side: + * + * $(UL + * $(LI A negative integer if the expression `v1 < v2` is true.) + * $(LI A positive integer if the expression `v1 > v2` is true.) + * $(LI 0 if the expression `v1 == v2` is true.)) + */ + int opCmp(R)(R rhs) + if (areCompatibleTuples!(typeof(this), R, "<")) + { + foreach (i, Unused; Types) + { + if (field[i] != rhs.field[i]) + { + return field[i] < rhs.field[i] ? -1 : 1; + } + } + return 0; + } + + /// ditto + int opCmp(R)(R rhs) const + if (areCompatibleTuples!(typeof(this), R, "<")) + { + foreach (i, Unused; Types) + { + if (field[i] != rhs.field[i]) + { + return field[i] < rhs.field[i] ? -1 : 1; + } + } + return 0; + } + + /** + The first `v1` for which `v1 > v2` is true determines + the result. This could lead to unexpected behaviour. + */ + static if (Specs.length == 0) @safe unittest + { + auto tup1 = tuple(1, 1, 1); + auto tup2 = tuple(1, 100, 100); + assert(tup1 < tup2); + + //Only the first result matters for comparison + tup1[0] = 2; + assert(tup1 > tup2); + } + + /** + * Assignment from another `Tuple`. + * + * Params: + * rhs = The source `Tuple` to assign from. Each element of the + * source `Tuple` must be implicitly assignable to each + * respective element of the target `Tuple`. + */ + void opAssign(R)(auto ref R rhs) + if (areCompatibleTuples!(typeof(this), R, "=")) + { + import std.algorithm.mutation : swap; + + static if (is(R : Tuple!Types) && !__traits(isRef, rhs)) + { + if (__ctfe) + { + // Cannot use swap at compile time + field[] = rhs.field[]; + } + else + { + // Use swap-and-destroy to optimize rvalue assignment + swap!(Tuple!Types)(this, rhs); + } + } + else + { + // Do not swap; opAssign should be called on the fields. + field[] = rhs.field[]; + } + } + + /** + * Renames the elements of a $(LREF Tuple). + * + * `rename` uses the passed `names` and returns a new + * $(LREF Tuple) using these names, with the content + * unchanged. + * If fewer names are passed than there are members + * of the $(LREF Tuple) then those trailing members are unchanged. + * An empty string will remove the name for that member. + * It is an compile-time error to pass more names than + * there are members of the $(LREF Tuple). + */ + ref rename(names...)() return + if (names.length == 0 || allSatisfy!(isSomeString, typeof(names))) + { + import std.algorithm.comparison : equal; + // to circumvent bug 16418 + static if (names.length == 0 || equal([names], [fieldNames])) + return this; + else + { + enum nT = Types.length; + enum nN = names.length; + static assert(nN <= nT, "Cannot have more names than tuple members"); + alias allNames = AliasSeq!(names, fieldNames[nN .. $]); + + template GetItem(size_t idx) + { + import std.array : empty; + static if (idx < nT) + alias GetItem = Alias!(Types[idx]); + else static if (allNames[idx - nT].empty) + alias GetItem = AliasSeq!(); + else + alias GetItem = Alias!(allNames[idx - nT]); + } + + import std.range : roundRobin, iota; + alias NewTupleT = Tuple!(staticMap!(GetItem, aliasSeqOf!( + roundRobin(iota(nT), iota(nT, 2*nT))))); + return *(() @trusted => cast(NewTupleT*)&this)(); + } + } + + /// + static if (Specs.length == 0) @safe unittest + { + auto t0 = tuple(4, "hello"); + + auto t0Named = t0.rename!("val", "tag"); + assert(t0Named.val == 4); + assert(t0Named.tag == "hello"); + + Tuple!(float, "dat", size_t[2], "pos") t1; + t1.pos = [2, 1]; + auto t1Named = t1.rename!"height"; + t1Named.height = 3.4f; + assert(t1Named.height == 3.4f); + assert(t1Named.pos == [2, 1]); + t1Named.rename!"altitude".altitude = 5; + assert(t1Named.height == 5); + + Tuple!(int, "a", int, int, "c") t2; + t2 = tuple(3,4,5); + auto t2Named = t2.rename!("", "b"); + // "a" no longer has a name + static assert(!hasMember!(typeof(t2Named), "a")); + assert(t2Named[0] == 3); + assert(t2Named.b == 4); + assert(t2Named.c == 5); + + // not allowed to specify more names than the tuple has members + static assert(!__traits(compiles, t2.rename!("a","b","c","d"))); + + // use it in a range pipeline + import std.range : iota, zip; + import std.algorithm.iteration : map, sum; + auto res = zip(iota(1, 4), iota(10, 13)) + .map!(t => t.rename!("a", "b")) + .map!(t => t.a * t.b) + .sum; + assert(res == 68); + } + + /** + * Overload of $(LREF _rename) that takes an associative array + * `translate` as a template parameter, where the keys are + * either the names or indices of the members to be changed + * and the new names are the corresponding values. + * Every key in `translate` must be the name of a member of the + * $(LREF tuple). + * The same rules for empty strings apply as for the variadic + * template overload of $(LREF _rename). + */ + ref rename(alias translate)() + if (is(typeof(translate) : V[K], V, K) && isSomeString!V && + (isSomeString!K || is(K : size_t))) + { + import std.range : ElementType; + static if (isSomeString!(ElementType!(typeof(translate.keys)))) + { + { + import std.conv : to; + import std.algorithm.iteration : filter; + import std.algorithm.searching : canFind; + enum notFound = translate.keys + .filter!(k => fieldNames.canFind(k) == -1); + static assert(notFound.empty, "Cannot find members " + ~ notFound.to!string ~ " in type " + ~ typeof(this).stringof); + } + return this.rename!(aliasSeqOf!( + { + import std.array : empty; + auto names = [fieldNames]; + foreach (ref n; names) + if (!n.empty) + if (auto p = n in translate) + n = *p; + return names; + }())); + } + else + { + { + import std.algorithm.iteration : filter; + import std.conv : to; + enum invalid = translate.keys. + filter!(k => k < 0 || k >= this.length); + static assert(invalid.empty, "Indices " ~ invalid.to!string + ~ " are out of bounds for tuple with length " + ~ this.length.to!string); + } + return this.rename!(aliasSeqOf!( + { + auto names = [fieldNames]; + foreach (k, v; translate) + names[k] = v; + return names; + }())); + } + } + + /// + static if (Specs.length == 0) @safe unittest + { + //replacing names by their current name + + Tuple!(float, "dat", size_t[2], "pos") t1; + t1.pos = [2, 1]; + auto t1Named = t1.rename!(["dat": "height"]); + t1Named.height = 3.4; + assert(t1Named.pos == [2, 1]); + t1Named.rename!(["height": "altitude"]).altitude = 5; + assert(t1Named.height == 5); + + Tuple!(int, "a", int, "b") t2; + t2 = tuple(3, 4); + auto t2Named = t2.rename!(["a": "b", "b": "c"]); + assert(t2Named.b == 3); + assert(t2Named.c == 4); + } + + /// + static if (Specs.length == 0) @safe unittest + { + //replace names by their position + + Tuple!(float, "dat", size_t[2], "pos") t1; + t1.pos = [2, 1]; + auto t1Named = t1.rename!([0: "height"]); + t1Named.height = 3.4; + assert(t1Named.pos == [2, 1]); + t1Named.rename!([0: "altitude"]).altitude = 5; + assert(t1Named.height == 5); + + Tuple!(int, "a", int, "b", int, "c") t2; + t2 = tuple(3, 4, 5); + auto t2Named = t2.rename!([0: "c", 2: "a"]); + assert(t2Named.a == 5); + assert(t2Named.b == 4); + assert(t2Named.c == 3); + } + + static if (Specs.length == 0) @safe unittest + { + //check that empty translations work fine + enum string[string] a0 = null; + enum string[int] a1 = null; + Tuple!(float, "a", float, "b") t0; + + auto t1 = t0.rename!a0; + + t1.a = 3; + t1.b = 4; + auto t2 = t0.rename!a1; + t2.a = 3; + t2.b = 4; + auto t3 = t0.rename; + t3.a = 3; + t3.b = 4; + } + + /** + * Takes a slice by-reference of this `Tuple`. + * + * Params: + * from = A `size_t` designating the starting position of the slice. + * to = A `size_t` designating the ending position (exclusive) of the slice. + * + * Returns: + * A new `Tuple` that is a slice from `[from, to$(RPAREN)` of the original. + * It has the same types and values as the range `[from, to$(RPAREN)` in + * the original. + */ + @property + ref inout(Tuple!(sliceSpecs!(from, to))) slice(size_t from, size_t to)() inout @trusted + if (from <= to && to <= Types.length) + { + static assert( + (typeof(this).alignof % typeof(return).alignof == 0) && + (expand[from].offsetof % typeof(return).alignof == 0), + "Slicing by reference is impossible because of an alignment mistmatch. (See Phobos issue #15645.)"); + + return *cast(typeof(return)*) &(field[from]); + } + + /// + static if (Specs.length == 0) @safe unittest + { + Tuple!(int, string, float, double) a; + a[1] = "abc"; + a[2] = 4.5; + auto s = a.slice!(1, 3); + static assert(is(typeof(s) == Tuple!(string, float))); + assert(s[0] == "abc" && s[1] == 4.5); + + // Phobos issue #15645 + Tuple!(int, short, bool, double) b; + static assert(!__traits(compiles, b.slice!(2, 4))); + } + + /** + Creates a hash of this `Tuple`. + + Returns: + A `size_t` representing the hash of this `Tuple`. + */ + size_t toHash() const nothrow @trusted + { + size_t h = 0; + foreach (i, T; Types) + h += typeid(T).getHash(cast(const void*)&field[i]); + return h; + } + + /// + template toString() + { + /** + * Converts to string. + * + * Returns: + * The string representation of this `Tuple`. + */ + string toString()() const + { + import std.array : appender; + auto app = appender!string(); + this.toString((const(char)[] chunk) => app ~= chunk); + return app.data; + } + + import std.format : FormatSpec; + + /** + * Formats `Tuple` with either `%s`, `%(inner%)` or `%(inner%|sep%)`. + * + * $(TABLE2 Formats supported by Tuple, + * $(THEAD Format, Description) + * $(TROW $(P `%s`), $(P Format like `Tuple!(types)(elements formatted with %s each)`.)) + * $(TROW $(P `%(inner%)`), $(P The format `inner` is applied the expanded `Tuple`, so + * it may contain as many formats as the `Tuple` has fields.)) + * $(TROW $(P `%(inner%|sep%)`), $(P The format `inner` is one format, that is applied + * on all fields of the `Tuple`. The inner format must be compatible to all + * of them.))) + * --- + * Tuple!(int, double)[3] tupList = [ tuple(1, 1.0), tuple(2, 4.0), tuple(3, 9.0) ]; + * + * // Default format + * assert(format("%s", tuple("a", 1)) == `Tuple!(string, int)("a", 1)`); + * + * // One Format for each individual component + * assert(format("%(%#x v %.4f w %#x%)", tuple(1, 1.0, 10)) == `0x1 v 1.0000 w 0xa`); + * assert(format( "%#x v %.4f w %#x" , tuple(1, 1.0, 10).expand) == `0x1 v 1.0000 w 0xa`); + * + * // One Format for all components + * assert(format("%(>%s<%| & %)", tuple("abc", 1, 2.3, [4, 5])) == `>abc< & >1< & >2.3< & >[4, 5]<`); + * + * // Array of Tuples + * assert(format("%(%(f(%d) = %.1f%); %)", tupList) == `f(1) = 1.0; f(2) = 4.0; f(3) = 9.0`); + * + * + * // Error: %( %) missing. + * assertThrown!FormatException( + * format("%d, %f", tuple(1, 2.0)) == `1, 2.0` + * ); + * + * // Error: %( %| %) missing. + * assertThrown!FormatException( + * format("%d", tuple(1, 2)) == `1, 2` + * ); + * + * // Error: %d inadequate for double. + * assertThrown!FormatException( + * format("%(%d%|, %)", tuple(1, 2.0)) == `1, 2.0` + * ); + * --- + */ + void toString(DG)(scope DG sink) const + { + toString(sink, FormatSpec!char()); + } + + /// ditto + void toString(DG, Char)(scope DG sink, FormatSpec!Char fmt) const + { + import std.format : formatElement, formattedWrite, FormatException; + if (fmt.nested) + { + if (fmt.sep) + { + foreach (i, Type; Types) + { + static if (i > 0) + { + sink(fmt.sep); + } + // TODO: Change this once formattedWrite() works for shared objects. + static if (is(Type == class) && is(Type == shared)) + { + sink(Type.stringof); + } + else + { + formattedWrite(sink, fmt.nested, this.field[i]); + } + } + } + else + { + formattedWrite(sink, fmt.nested, staticMap!(sharedToString, this.expand)); + } + } + else if (fmt.spec == 's') + { + enum header = Unqual!(typeof(this)).stringof ~ "(", + footer = ")", + separator = ", "; + sink(header); + foreach (i, Type; Types) + { + static if (i > 0) + { + sink(separator); + } + // TODO: Change this once formatElement() works for shared objects. + static if (is(Type == class) && is(Type == shared)) + { + sink(Type.stringof); + } + else + { + FormatSpec!Char f; + formatElement(sink, field[i], f); + } + } + sink(footer); + } + else + { + throw new FormatException( + "Expected '%s' or '%(...%)' or '%(...%|...%)' format specifier for type '" ~ + Unqual!(typeof(this)).stringof ~ "', not '%" ~ fmt.spec ~ "'."); + } + } + } + } +} + +/// +@safe unittest +{ + Tuple!(int, int) point; + // assign coordinates + point[0] = 5; + point[1] = 6; + // read coordinates + auto x = point[0]; + auto y = point[1]; +} + +/** + `Tuple` members can be named. It is legal to mix named and unnamed + members. The method above is still applicable to all fields. + */ +@safe unittest +{ + alias Entry = Tuple!(int, "index", string, "value"); + Entry e; + e.index = 4; + e.value = "Hello"; + assert(e[1] == "Hello"); + assert(e[0] == 4); +} + +/** + A `Tuple` with named fields is a distinct type from a `Tuple` with unnamed + fields, i.e. each naming imparts a separate type for the `Tuple`. Two + `Tuple`s differing in naming only are still distinct, even though they + might have the same structure. + */ +@safe unittest +{ + Tuple!(int, "x", int, "y") point1; + Tuple!(int, int) point2; + assert(!is(typeof(point1) == typeof(point2))); +} + +/** + Creates a copy of a $(LREF Tuple) with its fields in _reverse order. + + Params: + t = The `Tuple` to copy. + + Returns: + A new `Tuple`. + */ +auto reverse(T)(T t) + if (isTuple!T) +{ + import std.meta : Reverse; + // @@@BUG@@@ Cannot be an internal function due to forward reference issues. + + // @@@BUG@@@ 9929 Need 'this' when calling template with expanded tuple + // return tuple(Reverse!(t.expand)); + + ReverseTupleType!T result; + auto tup = t.expand; + result.expand = Reverse!tup; + return result; +} + +/// +@safe unittest +{ + auto tup = tuple(1, "2"); + assert(tup.reverse == tuple("2", 1)); +} + +/* Get a Tuple type with the reverse specification of Tuple T. */ +private template ReverseTupleType(T) + if (isTuple!T) +{ + static if (is(T : Tuple!A, A...)) + alias ReverseTupleType = Tuple!(ReverseTupleSpecs!A); +} + +/* Reverse the Specs of a Tuple. */ +private template ReverseTupleSpecs(T...) +{ + static if (T.length > 1) + { + static if (is(typeof(T[$-1]) : string)) + { + alias ReverseTupleSpecs = AliasSeq!(T[$-2], T[$-1], ReverseTupleSpecs!(T[0 .. $-2])); + } + else + { + alias ReverseTupleSpecs = AliasSeq!(T[$-1], ReverseTupleSpecs!(T[0 .. $-1])); + } + } + else + { + alias ReverseTupleSpecs = T; + } +} + +// ensure that internal Tuple unittests are compiled +@safe unittest +{ + Tuple!() t; +} + +@safe unittest +{ + import std.conv; + { + Tuple!(int, "a", int, "b") nosh; + static assert(nosh.length == 2); + nosh.a = 5; + nosh.b = 6; + assert(nosh.a == 5); + assert(nosh.b == 6); + } + { + Tuple!(short, double) b; + static assert(b.length == 2); + b[1] = 5; + auto a = Tuple!(int, real)(b); + assert(a[0] == 0 && a[1] == 5); + a = Tuple!(int, real)(1, 2); + assert(a[0] == 1 && a[1] == 2); + auto c = Tuple!(int, "a", double, "b")(a); + assert(c[0] == 1 && c[1] == 2); + } + { + Tuple!(int, real) nosh; + nosh[0] = 5; + nosh[1] = 0; + assert(nosh[0] == 5 && nosh[1] == 0); + assert(nosh.to!string == "Tuple!(int, real)(5, 0)", nosh.to!string); + Tuple!(int, int) yessh; + nosh = yessh; + } + { + class A {} + Tuple!(int, shared A) nosh; + nosh[0] = 5; + assert(nosh[0] == 5 && nosh[1] is null); + assert(nosh.to!string == "Tuple!(int, shared(A))(5, shared(A))"); + } + { + Tuple!(int, string) t; + t[0] = 10; + t[1] = "str"; + assert(t[0] == 10 && t[1] == "str"); + assert(t.to!string == `Tuple!(int, string)(10, "str")`, t.to!string); + } + { + Tuple!(int, "a", double, "b") x; + static assert(x.a.offsetof == x[0].offsetof); + static assert(x.b.offsetof == x[1].offsetof); + x.b = 4.5; + x.a = 5; + assert(x[0] == 5 && x[1] == 4.5); + assert(x.a == 5 && x.b == 4.5); + } + // indexing + { + Tuple!(int, real) t; + static assert(is(typeof(t[0]) == int)); + static assert(is(typeof(t[1]) == real)); + int* p0 = &t[0]; + real* p1 = &t[1]; + t[0] = 10; + t[1] = -200.0L; + assert(*p0 == t[0]); + assert(*p1 == t[1]); + } + // slicing + { + Tuple!(int, "x", real, "y", double, "z", string) t; + t[0] = 10; + t[1] = 11; + t[2] = 12; + t[3] = "abc"; + auto a = t.slice!(0, 3); + assert(a.length == 3); + assert(a.x == t.x); + assert(a.y == t.y); + assert(a.z == t.z); + auto b = t.slice!(2, 4); + assert(b.length == 2); + assert(b.z == t.z); + assert(b[1] == t[3]); + } + // nesting + { + Tuple!(Tuple!(int, real), Tuple!(string, "s")) t; + static assert(is(typeof(t[0]) == Tuple!(int, real))); + static assert(is(typeof(t[1]) == Tuple!(string, "s"))); + static assert(is(typeof(t[0][0]) == int)); + static assert(is(typeof(t[0][1]) == real)); + static assert(is(typeof(t[1].s) == string)); + t[0] = tuple(10, 20.0L); + t[1].s = "abc"; + assert(t[0][0] == 10); + assert(t[0][1] == 20.0L); + assert(t[1].s == "abc"); + } + // non-POD + { + static struct S + { + int count; + this(this) { ++count; } + ~this() { --count; } + void opAssign(S rhs) { count = rhs.count; } + } + Tuple!(S, S) ss; + Tuple!(S, S) ssCopy = ss; + assert(ssCopy[0].count == 1); + assert(ssCopy[1].count == 1); + ssCopy[1] = ssCopy[0]; + assert(ssCopy[1].count == 2); + } + // bug 2800 + { + static struct R + { + Tuple!(int, int) _front; + @property ref Tuple!(int, int) front() return { return _front; } + @property bool empty() { return _front[0] >= 10; } + void popFront() { ++_front[0]; } + } + foreach (a; R()) + { + static assert(is(typeof(a) == Tuple!(int, int))); + assert(0 <= a[0] && a[0] < 10); + assert(a[1] == 0); + } + } + // Construction with compatible elements + { + auto t1 = Tuple!(int, double)(1, 1); + + // 8702 + auto t8702a = tuple(tuple(1)); + auto t8702b = Tuple!(Tuple!(int))(Tuple!(int)(1)); + } + // Construction with compatible tuple + { + Tuple!(int, int) x; + x[0] = 10; + x[1] = 20; + Tuple!(int, "a", double, "b") y = x; + assert(y.a == 10); + assert(y.b == 20); + // incompatible + static assert(!__traits(compiles, Tuple!(int, int)(y))); + } + // 6275 + { + const int x = 1; + auto t1 = tuple(x); + alias T = Tuple!(const(int)); + auto t2 = T(1); + } + // 9431 + { + alias T = Tuple!(int[1][]); + auto t = T([[10]]); + } + // 7666 + { + auto tup = tuple(1, "2"); + assert(tup.reverse == tuple("2", 1)); + } + { + Tuple!(int, "x", string, "y") tup = tuple(1, "2"); + auto rev = tup.reverse; + assert(rev == tuple("2", 1)); + assert(rev.x == 1 && rev.y == "2"); + } + { + Tuple!(wchar, dchar, int, "x", string, "y", char, byte, float) tup; + tup = tuple('a', 'b', 3, "4", 'c', cast(byte) 0x0D, 0.00); + auto rev = tup.reverse; + assert(rev == tuple(0.00, cast(byte) 0x0D, 'c', "4", 3, 'b', 'a')); + assert(rev.x == 3 && rev.y == "4"); + } +} +@safe unittest +{ + // opEquals + { + struct Equ1 { bool opEquals(Equ1) { return true; } } + auto tm1 = tuple(Equ1.init); + const tc1 = tuple(Equ1.init); + static assert( is(typeof(tm1 == tm1))); + static assert(!is(typeof(tm1 == tc1))); + static assert(!is(typeof(tc1 == tm1))); + static assert(!is(typeof(tc1 == tc1))); + + struct Equ2 { bool opEquals(const Equ2) const { return true; } } + auto tm2 = tuple(Equ2.init); + const tc2 = tuple(Equ2.init); + static assert( is(typeof(tm2 == tm2))); + static assert( is(typeof(tm2 == tc2))); + static assert( is(typeof(tc2 == tm2))); + static assert( is(typeof(tc2 == tc2))); + + struct Equ3 { bool opEquals(T)(T) { return true; } } + auto tm3 = tuple(Equ3.init); // bugzilla 8686 + const tc3 = tuple(Equ3.init); + static assert( is(typeof(tm3 == tm3))); + static assert( is(typeof(tm3 == tc3))); + static assert(!is(typeof(tc3 == tm3))); + static assert(!is(typeof(tc3 == tc3))); + + struct Equ4 { bool opEquals(T)(T) const { return true; } } + auto tm4 = tuple(Equ4.init); + const tc4 = tuple(Equ4.init); + static assert( is(typeof(tm4 == tm4))); + static assert( is(typeof(tm4 == tc4))); + static assert( is(typeof(tc4 == tm4))); + static assert( is(typeof(tc4 == tc4))); + } + // opCmp + { + struct Cmp1 { int opCmp(Cmp1) { return 0; } } + auto tm1 = tuple(Cmp1.init); + const tc1 = tuple(Cmp1.init); + static assert( is(typeof(tm1 < tm1))); + static assert(!is(typeof(tm1 < tc1))); + static assert(!is(typeof(tc1 < tm1))); + static assert(!is(typeof(tc1 < tc1))); + + struct Cmp2 { int opCmp(const Cmp2) const { return 0; } } + auto tm2 = tuple(Cmp2.init); + const tc2 = tuple(Cmp2.init); + static assert( is(typeof(tm2 < tm2))); + static assert( is(typeof(tm2 < tc2))); + static assert( is(typeof(tc2 < tm2))); + static assert( is(typeof(tc2 < tc2))); + + struct Cmp3 { int opCmp(T)(T) { return 0; } } + auto tm3 = tuple(Cmp3.init); + const tc3 = tuple(Cmp3.init); + static assert( is(typeof(tm3 < tm3))); + static assert( is(typeof(tm3 < tc3))); + static assert(!is(typeof(tc3 < tm3))); + static assert(!is(typeof(tc3 < tc3))); + + struct Cmp4 { int opCmp(T)(T) const { return 0; } } + auto tm4 = tuple(Cmp4.init); + const tc4 = tuple(Cmp4.init); + static assert( is(typeof(tm4 < tm4))); + static assert( is(typeof(tm4 < tc4))); + static assert( is(typeof(tc4 < tm4))); + static assert( is(typeof(tc4 < tc4))); + } + // Bugzilla 14890 + static void test14890(inout int[] dummy) + { + alias V = Tuple!(int, int); + + V mv; + const V cv; + immutable V iv; + inout V wv; // OK <- NG + inout const V wcv; // OK <- NG + + foreach (v1; AliasSeq!(mv, cv, iv, wv, wcv)) + foreach (v2; AliasSeq!(mv, cv, iv, wv, wcv)) + { + assert(!(v1 < v2)); + } + } + { + int[2] ints = [ 1, 2 ]; + Tuple!(int, int) t = ints; + assert(t[0] == 1 && t[1] == 2); + Tuple!(long, uint) t2 = ints; + assert(t2[0] == 1 && t2[1] == 2); + } +} +@safe unittest +{ + auto t1 = Tuple!(int, "x", string, "y")(1, "a"); + assert(t1.x == 1); + assert(t1.y == "a"); + void foo(Tuple!(int, string) t2) {} + foo(t1); + + Tuple!(int, int)[] arr; + arr ~= tuple(10, 20); // OK + arr ~= Tuple!(int, "x", int, "y")(10, 20); // NG -> OK + + static assert(is(typeof(Tuple!(int, "x", string, "y").tupleof) == + typeof(Tuple!(int, string ).tupleof))); +} +@safe unittest +{ + // Bugzilla 10686 + immutable Tuple!(int) t1; + auto r1 = t1[0]; // OK + immutable Tuple!(int, "x") t2; + auto r2 = t2[0]; // error +} +@safe unittest +{ + import std.exception : assertCTFEable; + + // Bugzilla 10218 + assertCTFEable!( + { + auto t = tuple(1); + t = tuple(2); // assignment + }); +} +@safe unittest +{ + class Foo{} + Tuple!(immutable(Foo)[]) a; +} + +@safe unittest +{ + //Test non-assignable + static struct S + { + int* p; + } + alias IS = immutable S; + static assert(!isAssignable!IS); + + auto s = IS.init; + + alias TIS = Tuple!IS; + TIS a = tuple(s); + TIS b = a; + + alias TISIS = Tuple!(IS, IS); + TISIS d = tuple(s, s); + IS[2] ss; + TISIS e = TISIS(ss); +} + +// Bugzilla #9819 +@safe unittest +{ + alias T = Tuple!(int, "x", double, "foo"); + static assert(T.fieldNames[0] == "x"); + static assert(T.fieldNames[1] == "foo"); + + alias Fields = Tuple!(int, "id", string, float); + static assert(Fields.fieldNames == AliasSeq!("id", "", "")); +} + +// Bugzilla 13837 +@safe unittest +{ + // New behaviour, named arguments. + static assert(is( + typeof(tuple!("x")(1)) == Tuple!(int, "x"))); + static assert(is( + typeof(tuple!("x")(1.0)) == Tuple!(double, "x"))); + static assert(is( + typeof(tuple!("x")("foo")) == Tuple!(string, "x"))); + static assert(is( + typeof(tuple!("x", "y")(1, 2.0)) == Tuple!(int, "x", double, "y"))); + + auto a = tuple!("a", "b", "c")("1", 2, 3.0f); + static assert(is(typeof(a.a) == string)); + static assert(is(typeof(a.b) == int)); + static assert(is(typeof(a.c) == float)); + + // Old behaviour, but with explicit type parameters. + static assert(is( + typeof(tuple!(int, double)(1, 2.0)) == Tuple!(int, double))); + static assert(is( + typeof(tuple!(const int)(1)) == Tuple!(const int))); + static assert(is( + typeof(tuple()) == Tuple!())); + + // Nonsensical behaviour + static assert(!__traits(compiles, tuple!(1)(2))); + static assert(!__traits(compiles, tuple!("x")(1, 2))); + static assert(!__traits(compiles, tuple!("x", "y")(1))); + static assert(!__traits(compiles, tuple!("x")())); + static assert(!__traits(compiles, tuple!("x", int)(2))); +} + +@safe unittest +{ + class C {} + Tuple!(Rebindable!(const C)) a; + Tuple!(const C) b; + a = b; +} + +@nogc @safe unittest +{ + alias T = Tuple!(string, "s"); + T x; + x = T.init; +} + +@safe unittest +{ + import std.format : format, FormatException; + import std.exception : assertThrown; + + // enum tupStr = tuple(1, 1.0).toString; // toString is *impure*. + //static assert(tupStr == `Tuple!(int, double)(1, 1)`); + + Tuple!(int, double)[3] tupList = [ tuple(1, 1.0), tuple(2, 4.0), tuple(3, 9.0) ]; + + // Default format + assert(format("%s", tuple("a", 1)) == `Tuple!(string, int)("a", 1)`); + + // One Format for each individual component + assert(format("%(%#x v %.4f w %#x%)", tuple(1, 1.0, 10)) == `0x1 v 1.0000 w 0xa`); + assert(format( "%#x v %.4f w %#x" , tuple(1, 1.0, 10).expand) == `0x1 v 1.0000 w 0xa`); + + // One Format for all components + assert(format("%(>%s<%| & %)", tuple("abc", 1, 2.3, [4, 5])) == `>abc< & >1< & >2.3< & >[4, 5]<`); + + // Array of Tuples + assert(format("%(%(f(%d) = %.1f%); %)", tupList) == `f(1) = 1.0; f(2) = 4.0; f(3) = 9.0`); + + + // Error: %( %) missing. + assertThrown!FormatException( + format("%d, %f", tuple(1, 2.0)) == `1, 2.0` + ); + + // Error: %( %| %) missing. + assertThrown!FormatException( + format("%d", tuple(1, 2)) == `1, 2` + ); + + // Error: %d inadequate for double + assertThrown!FormatException( + format("%(%d%|, %)", tuple(1, 2.0)) == `1, 2.0` + ); +} + +/** + Constructs a $(LREF Tuple) object instantiated and initialized according to + the given arguments. + + Params: + Names = An optional list of strings naming each successive field of the `Tuple`. + Each name matches up with the corresponding field given by `Args`. + A name does not have to be provided for every field, but as + the names must proceed in order, it is not possible to skip + one field and name the next after it. +*/ +template tuple(Names...) +{ + /** + Params: + args = Values to initialize the `Tuple` with. The `Tuple`'s type will + be inferred from the types of the values given. + + Returns: + A new `Tuple` with its type inferred from the arguments given. + */ + auto tuple(Args...)(Args args) + { + static if (Names.length == 0) + { + // No specified names, just infer types from Args... + return Tuple!Args(args); + } + else static if (!is(typeof(Names[0]) : string)) + { + // Names[0] isn't a string, must be explicit types. + return Tuple!Names(args); + } + else + { + // Names[0] is a string, so must be specifying names. + static assert(Names.length == Args.length, + "Insufficient number of names given."); + + // Interleave(a, b).and(c, d) == (a, c, b, d) + // This is to get the interleaving of types and names for Tuple + // e.g. Tuple!(int, "x", string, "y") + template Interleave(A...) + { + template and(B...) if (B.length == 1) + { + alias and = AliasSeq!(A[0], B[0]); + } + + template and(B...) if (B.length != 1) + { + alias and = AliasSeq!(A[0], B[0], + Interleave!(A[1..$]).and!(B[1..$])); + } + } + return Tuple!(Interleave!(Args).and!(Names))(args); + } + } +} + +/// +@safe unittest +{ + auto value = tuple(5, 6.7, "hello"); + assert(value[0] == 5); + assert(value[1] == 6.7); + assert(value[2] == "hello"); + + // Field names can be provided. + auto entry = tuple!("index", "value")(4, "Hello"); + assert(entry.index == 4); + assert(entry.value == "Hello"); +} + +/** + Returns $(D true) if and only if $(D T) is an instance of $(D std.typecons.Tuple). + + Params: + T = The type to check. + + Returns: + true if `T` is a `Tuple` type, false otherwise. + */ +enum isTuple(T) = __traits(compiles, + { + void f(Specs...)(Tuple!Specs tup) {} + f(T.init); + } ); + +/// +@safe unittest +{ + static assert(isTuple!(Tuple!())); + static assert(isTuple!(Tuple!(int))); + static assert(isTuple!(Tuple!(int, real, string))); + static assert(isTuple!(Tuple!(int, "x", real, "y"))); + static assert(isTuple!(Tuple!(int, Tuple!(real), string))); +} + +@safe unittest +{ + static assert(isTuple!(const Tuple!(int))); + static assert(isTuple!(immutable Tuple!(int))); + + static assert(!isTuple!(int)); + static assert(!isTuple!(const int)); + + struct S {} + static assert(!isTuple!(S)); +} + +// used by both Rebindable and UnqualRef +private mixin template RebindableCommon(T, U, alias This) + if (is(T == class) || is(T == interface) || isAssociativeArray!T) +{ + private union + { + T original; + U stripped; + } + + @trusted pure nothrow @nogc + { + void opAssign(T another) + { + stripped = cast(U) another; + } + + void opAssign(typeof(this) another) + { + stripped = another.stripped; + } + + static if (is(T == const U) && is(T == const shared U)) + { + // safely assign immutable to const / const shared + void opAssign(This!(immutable U) another) + { + stripped = another.stripped; + } + } + + this(T initializer) + { + opAssign(initializer); + } + + @property inout(T) get() inout + { + return original; + } + } + + alias get this; +} + +/** +$(D Rebindable!(T)) is a simple, efficient wrapper that behaves just +like an object of type $(D T), except that you can reassign it to +refer to another object. For completeness, $(D Rebindable!(T)) aliases +itself away to $(D T) if $(D T) is a non-const object type. + +You may want to use $(D Rebindable) when you want to have mutable +storage referring to $(D const) objects, for example an array of +references that must be sorted in place. $(D Rebindable) does not +break the soundness of D's type system and does not incur any of the +risks usually associated with $(D cast). + +Params: + T = An object, interface, array slice type, or associative array type. + */ +template Rebindable(T) + if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T) +{ + static if (is(T == const U, U) || is(T == immutable U, U)) + { + static if (isDynamicArray!T) + { + import std.range.primitives : ElementEncodingType; + alias Rebindable = const(ElementEncodingType!T)[]; + } + else + { + struct Rebindable + { + mixin RebindableCommon!(T, U, Rebindable); + } + } + } + else + { + alias Rebindable = T; + } +} + +///Regular $(D const) object references cannot be reassigned. +@system unittest +{ + class Widget { int x; int y() const { return x; } } + const a = new Widget; + // Fine + a.y(); + // error! can't modify const a + // a.x = 5; + // error! can't modify const a + // a = new Widget; +} + +/** + However, $(D Rebindable!(Widget)) does allow reassignment, + while otherwise behaving exactly like a $(D const Widget). + */ +@system unittest +{ + class Widget { int x; int y() const { return x; } } + auto a = Rebindable!(const Widget)(new Widget); + // Fine + a.y(); + // error! can't modify const a + // a.x = 5; + // Fine + a = new Widget; +} + +@safe unittest // issue 16054 +{ + Rebindable!(immutable Object) r; + static assert(__traits(compiles, r.get())); + static assert(!__traits(compiles, &r.get())); +} + +/** +Convenience function for creating a $(D Rebindable) using automatic type +inference. + +Params: + obj = A reference to an object, interface, associative array, or an array slice + to initialize the `Rebindable` with. + +Returns: + A newly constructed `Rebindable` initialized with the given reference. +*/ +Rebindable!T rebindable(T)(T obj) + if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T) +{ + typeof(return) ret; + ret = obj; + return ret; +} + +/** +This function simply returns the $(D Rebindable) object passed in. It's useful +in generic programming cases when a given object may be either a regular +$(D class) or a $(D Rebindable). + +Params: + obj = An instance of Rebindable!T. + +Returns: + `obj` without any modification. +*/ +Rebindable!T rebindable(T)(Rebindable!T obj) +{ + return obj; +} + +@system unittest +{ + interface CI { int foo() const; } + class C : CI { + int foo() const { return 42; } + @property int bar() const { return 23; } + } + Rebindable!(C) obj0; + static assert(is(typeof(obj0) == C)); + + Rebindable!(const(C)) obj1; + static assert(is(typeof(obj1.get) == const(C)), typeof(obj1.get).stringof); + static assert(is(typeof(obj1.stripped) == C)); + obj1 = new C; + assert(obj1.get !is null); + obj1 = new const(C); + assert(obj1.get !is null); + + Rebindable!(immutable(C)) obj2; + static assert(is(typeof(obj2.get) == immutable(C))); + static assert(is(typeof(obj2.stripped) == C)); + obj2 = new immutable(C); + assert(obj1.get !is null); + + // test opDot + assert(obj2.foo() == 42); + assert(obj2.bar == 23); + + interface I { final int foo() const { return 42; } } + Rebindable!(I) obj3; + static assert(is(typeof(obj3) == I)); + + Rebindable!(const I) obj4; + static assert(is(typeof(obj4.get) == const I)); + static assert(is(typeof(obj4.stripped) == I)); + static assert(is(typeof(obj4.foo()) == int)); + obj4 = new class I {}; + + Rebindable!(immutable C) obj5i; + Rebindable!(const C) obj5c; + obj5c = obj5c; + obj5c = obj5i; + obj5i = obj5i; + static assert(!__traits(compiles, obj5i = obj5c)); + + // Test the convenience functions. + auto obj5convenience = rebindable(obj5i); + assert(obj5convenience is obj5i); + + auto obj6 = rebindable(new immutable(C)); + static assert(is(typeof(obj6) == Rebindable!(immutable C))); + assert(obj6.foo() == 42); + + auto obj7 = rebindable(new C); + CI interface1 = obj7; + auto interfaceRebind1 = rebindable(interface1); + assert(interfaceRebind1.foo() == 42); + + const interface2 = interface1; + auto interfaceRebind2 = rebindable(interface2); + assert(interfaceRebind2.foo() == 42); + + auto arr = [1,2,3,4,5]; + const arrConst = arr; + assert(rebindable(arr) == arr); + assert(rebindable(arrConst) == arr); + + // Issue 7654 + immutable(char[]) s7654; + Rebindable!(typeof(s7654)) r7654 = s7654; + + foreach (T; AliasSeq!(char, wchar, char, int)) + { + static assert(is(Rebindable!(immutable(T[])) == immutable(T)[])); + static assert(is(Rebindable!(const(T[])) == const(T)[])); + static assert(is(Rebindable!(T[]) == T[])); + } + + // Issue 12046 + static assert(!__traits(compiles, Rebindable!(int[1]))); + static assert(!__traits(compiles, Rebindable!(const int[1]))); + + // Pull request 3341 + Rebindable!(immutable int[int]) pr3341 = [123:345]; + assert(pr3341[123] == 345); + immutable int[int] pr3341_aa = [321:543]; + pr3341 = pr3341_aa; + assert(pr3341[321] == 543); + assert(rebindable(pr3341_aa)[321] == 543); +} + +/** + Similar to $(D Rebindable!(T)) but strips all qualifiers from the reference as + opposed to just constness / immutability. Primary intended use case is with + shared (having thread-local reference to shared class data) + + Params: + T = A class or interface type. + */ +template UnqualRef(T) + if (is(T == class) || is(T == interface)) +{ + static if (is(T == const U, U) + || is(T == immutable U, U) + || is(T == shared U, U) + || is(T == const shared U, U)) + { + struct UnqualRef + { + mixin RebindableCommon!(T, U, UnqualRef); + } + } + else + { + alias UnqualRef = T; + } +} + +/// +@system unittest +{ + class Data {} + + static shared(Data) a; + static UnqualRef!(shared Data) b; + + import core.thread; + + auto thread = new core.thread.Thread({ + a = new shared Data(); + b = new shared Data(); + }); + + thread.start(); + thread.join(); + + assert(a !is null); + assert(b is null); +} + +@safe unittest +{ + class C { } + alias T = UnqualRef!(const shared C); + static assert(is(typeof(T.stripped) == C)); +} + + + +/** + Order the provided members to minimize size while preserving alignment. + Alignment is not always optimal for 80-bit reals, nor for structs declared + as align(1). + + Params: + E = A list of the types to be aligned, representing fields + of an aggregate such as a `struct` or `class`. + + names = The names of the fields that are to be aligned. + + Returns: + A string to be mixed in to an aggregate, such as a `struct` or `class`. +*/ +string alignForSize(E...)(const char[][] names...) +{ + // Sort all of the members by .alignof. + // BUG: Alignment is not always optimal for align(1) structs + // or 80-bit reals or 64-bit primitives on x86. + // TRICK: Use the fact that .alignof is always a power of 2, + // and maximum 16 on extant systems. Thus, we can perform + // a very limited radix sort. + // Contains the members with .alignof = 64,32,16,8,4,2,1 + + assert(E.length == names.length, + "alignForSize: There should be as many member names as the types"); + + string[7] declaration = ["", "", "", "", "", "", ""]; + + foreach (i, T; E) + { + auto a = T.alignof; + auto k = a >= 64? 0 : a >= 32? 1 : a >= 16? 2 : a >= 8? 3 : a >= 4? 4 : a >= 2? 5 : 6; + declaration[k] ~= T.stringof ~ " " ~ names[i] ~ ";\n"; + } + + auto s = ""; + foreach (decl; declaration) + s ~= decl; + return s; +} + +/// +@safe unittest +{ + struct Banner { + mixin(alignForSize!(byte[6], double)(["name", "height"])); + } +} + +@safe unittest +{ + enum x = alignForSize!(int[], char[3], short, double[5])("x", "y","z", "w"); + struct Foo { int x; } + enum y = alignForSize!(ubyte, Foo, cdouble)("x", "y", "z"); + + enum passNormalX = x == "double[5] w;\nint[] x;\nshort z;\nchar[3] y;\n"; + enum passNormalY = y == "cdouble z;\nFoo y;\nubyte x;\n"; + + enum passAbnormalX = x == "int[] x;\ndouble[5] w;\nshort z;\nchar[3] y;\n"; + enum passAbnormalY = y == "Foo y;\ncdouble z;\nubyte x;\n"; + // ^ blame http://d.puremagic.com/issues/show_bug.cgi?id=231 + + static assert(passNormalX || passAbnormalX && double.alignof <= (int[]).alignof); + static assert(passNormalY || passAbnormalY && double.alignof <= int.alignof); +} + +// Issue 12914 +@safe unittest +{ + immutable string[] fieldNames = ["x", "y"]; + struct S + { + mixin(alignForSize!(byte, int)(fieldNames)); + } +} + +/** +Defines a value paired with a distinctive "null" state that denotes +the absence of a value. If default constructed, a $(D +Nullable!T) object starts in the null state. Assigning it renders it +non-null. Calling $(D nullify) can nullify it again. + +Practically $(D Nullable!T) stores a $(D T) and a $(D bool). + */ +struct Nullable(T) +{ + private T _value; + private bool _isNull = true; + +/** +Constructor initializing $(D this) with $(D value). + +Params: + value = The value to initialize this `Nullable` with. + */ + this(inout T value) inout + { + _value = value; + _isNull = false; + } + + /** + If they are both null, then they are equal. If one is null and the other + is not, then they are not equal. If they are both non-null, then they are + equal if their values are equal. + */ + bool opEquals()(auto ref const(typeof(this)) rhs) const + { + if (_isNull) + return rhs._isNull; + if (rhs._isNull) + return false; + return _value == rhs._value; + } + + /// Ditto + bool opEquals(U)(auto ref const(U) rhs) const + if (is(typeof(this.get == rhs))) + { + return _isNull ? false : rhs == _value; + } + + /// + @safe unittest + { + Nullable!int empty; + Nullable!int a = 42; + Nullable!int b = 42; + Nullable!int c = 27; + + assert(empty == empty); + assert(empty == Nullable!int.init); + assert(empty != a); + assert(empty != b); + assert(empty != c); + + assert(a == b); + assert(a != c); + + assert(empty != 42); + assert(a == 42); + assert(c != 42); + } + + @safe unittest + { + // Test constness + immutable Nullable!int a = 42; + Nullable!int b = 42; + immutable Nullable!int c = 29; + Nullable!int d = 29; + immutable e = 42; + int f = 29; + assert(a == a); + assert(a == b); + assert(a != c); + assert(a != d); + assert(a == e); + assert(a != f); + + // Test rvalue + assert(a == const Nullable!int(42)); + assert(a != Nullable!int(29)); + } + + // Issue 17482 + @system unittest + { + import std.variant : Variant; + Nullable!Variant a = Variant(12); + assert(a == 12); + Nullable!Variant e; + assert(e != 12); + } + + template toString() + { + import std.format : FormatSpec, formatValue; + // Needs to be a template because of DMD @@BUG@@ 13737. + void toString()(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) + { + if (isNull) + { + sink.formatValue("Nullable.null", fmt); + } + else + { + sink.formatValue(_value, fmt); + } + } + + // Issue 14940 + void toString()(scope void delegate(const(char)[]) @safe sink, FormatSpec!char fmt) + { + if (isNull) + { + sink.formatValue("Nullable.null", fmt); + } + else + { + sink.formatValue(_value, fmt); + } + } + } + +/** +Check if `this` is in the null state. + +Returns: + true $(B iff) `this` is in the null state, otherwise false. + */ + @property bool isNull() const @safe pure nothrow + { + return _isNull; + } + +/// +@system unittest +{ + Nullable!int ni; + assert(ni.isNull); + + ni = 0; + assert(!ni.isNull); +} + +// Issue 14940 +@safe unittest +{ + import std.array : appender; + import std.format : formattedWrite; + + auto app = appender!string(); + Nullable!int a = 1; + formattedWrite(app, "%s", a); + assert(app.data == "1"); +} + +/** +Forces $(D this) to the null state. + */ + void nullify()() + { + .destroy(_value); + _isNull = true; + } + +/// +@safe unittest +{ + Nullable!int ni = 0; + assert(!ni.isNull); + + ni.nullify(); + assert(ni.isNull); +} + +/** +Assigns $(D value) to the internally-held state. If the assignment +succeeds, $(D this) becomes non-null. + +Params: + value = A value of type `T` to assign to this `Nullable`. + */ + void opAssign()(T value) + { + _value = value; + _isNull = false; + } + +/** + If this `Nullable` wraps a type that already has a null value + (such as a pointer), then assigning the null value to this + `Nullable` is no different than assigning any other value of + type `T`, and the resulting code will look very strange. It + is strongly recommended that this be avoided by instead using + the version of `Nullable` that takes an additional `nullValue` + template argument. + */ +@safe unittest +{ + //Passes + Nullable!(int*) npi; + assert(npi.isNull); + + //Passes?! + npi = null; + assert(!npi.isNull); +} + +/** +Gets the value. $(D this) must not be in the null state. +This function is also called for the implicit conversion to $(D T). + +Returns: + The value held internally by this `Nullable`. + */ + @property ref inout(T) get() inout @safe pure nothrow + { + enum message = "Called `get' on null Nullable!" ~ T.stringof ~ "."; + assert(!isNull, message); + return _value; + } + +/// +@system unittest +{ + import core.exception : AssertError; + import std.exception : assertThrown, assertNotThrown; + + Nullable!int ni; + int i = 42; + //`get` is implicitly called. Will throw + //an AssertError in non-release mode + assertThrown!AssertError(i = ni); + assert(i == 42); + + ni = 5; + assertNotThrown!AssertError(i = ni); + assert(i == 5); +} + +/** +Implicitly converts to $(D T). +$(D this) must not be in the null state. + */ + alias get this; +} + +/// ditto +auto nullable(T)(T t) +{ + return Nullable!T(t); +} + +/// +@safe unittest +{ + struct CustomerRecord + { + string name; + string address; + int customerNum; + } + + Nullable!CustomerRecord getByName(string name) + { + //A bunch of hairy stuff + + return Nullable!CustomerRecord.init; + } + + auto queryResult = getByName("Doe, John"); + if (!queryResult.isNull) + { + //Process Mr. Doe's customer record + auto address = queryResult.address; + auto customerNum = queryResult.customerNum; + + //Do some things with this customer's info + } + else + { + //Add the customer to the database + } +} + +/// +@system unittest +{ + import std.exception : assertThrown; + + auto a = 42.nullable; + assert(!a.isNull); + assert(a.get == 42); + + a.nullify(); + assert(a.isNull); + assertThrown!Throwable(a.get); +} + +@system unittest +{ + import std.exception : assertThrown; + + Nullable!int a; + assert(a.isNull); + assertThrown!Throwable(a.get); + a = 5; + assert(!a.isNull); + assert(a == 5); + assert(a != 3); + assert(a.get != 3); + a.nullify(); + assert(a.isNull); + a = 3; + assert(a == 3); + a *= 6; + assert(a == 18); + a = a; + assert(a == 18); + a.nullify(); + assertThrown!Throwable(a += 2); +} +@safe unittest +{ + auto k = Nullable!int(74); + assert(k == 74); + k.nullify(); + assert(k.isNull); +} +@safe unittest +{ + static int f(in Nullable!int x) { + return x.isNull ? 42 : x.get; + } + Nullable!int a; + assert(f(a) == 42); + a = 8; + assert(f(a) == 8); + a.nullify(); + assert(f(a) == 42); +} +@system unittest +{ + import std.exception : assertThrown; + + static struct S { int x; } + Nullable!S s; + assert(s.isNull); + s = S(6); + assert(s == S(6)); + assert(s != S(0)); + assert(s.get != S(0)); + s.x = 9190; + assert(s.x == 9190); + s.nullify(); + assertThrown!Throwable(s.x = 9441); +} +@safe unittest +{ + // Ensure Nullable can be used in pure/nothrow/@safe environment. + function() @safe pure nothrow + { + Nullable!int n; + assert(n.isNull); + n = 4; + assert(!n.isNull); + assert(n == 4); + n.nullify(); + assert(n.isNull); + }(); +} +@system unittest +{ + // Ensure Nullable can be used when the value is not pure/nothrow/@safe + static struct S + { + int x; + this(this) @system {} + } + + Nullable!S s; + assert(s.isNull); + s = S(5); + assert(!s.isNull); + assert(s.x == 5); + s.nullify(); + assert(s.isNull); +} +@safe unittest +{ + // Bugzilla 9404 + alias N = Nullable!int; + + void foo(N a) + { + N b; + b = a; // `N b = a;` works fine + } + N n; + foo(n); +} +@safe unittest +{ + //Check nullable immutable is constructable + { + auto a1 = Nullable!(immutable int)(); + auto a2 = Nullable!(immutable int)(1); + auto i = a2.get; + } + //Check immutable nullable is constructable + { + auto a1 = immutable (Nullable!int)(); + auto a2 = immutable (Nullable!int)(1); + auto i = a2.get; + } +} +@safe unittest +{ + alias NInt = Nullable!int; + + //Construct tests + { + //from other Nullable null + NInt a1; + NInt b1 = a1; + assert(b1.isNull); + + //from other Nullable non-null + NInt a2 = NInt(1); + NInt b2 = a2; + assert(b2 == 1); + + //Construct from similar nullable + auto a3 = immutable(NInt)(); + NInt b3 = a3; + assert(b3.isNull); + } + + //Assign tests + { + //from other Nullable null + NInt a1; + NInt b1; + b1 = a1; + assert(b1.isNull); + + //from other Nullable non-null + NInt a2 = NInt(1); + NInt b2; + b2 = a2; + assert(b2 == 1); + + //Construct from similar nullable + auto a3 = immutable(NInt)(); + NInt b3 = a3; + b3 = a3; + assert(b3.isNull); + } +} +@safe unittest +{ + //Check nullable is nicelly embedable in a struct + static struct S1 + { + Nullable!int ni; + } + static struct S2 //inspired from 9404 + { + Nullable!int ni; + this(S2 other) + { + ni = other.ni; + } + void opAssign(S2 other) + { + ni = other.ni; + } + } + foreach (S; AliasSeq!(S1, S2)) + { + S a; + S b = a; + S c; + c = a; + } +} +@system unittest +{ + // Bugzilla 10268 + import std.json; + JSONValue value = null; + auto na = Nullable!JSONValue(value); + + struct S1 { int val; } + struct S2 { int* val; } + struct S3 { immutable int* val; } + + { + auto sm = S1(1); + immutable si = immutable S1(1); + auto x1 = Nullable!S1(sm); + auto x2 = immutable Nullable!S1(sm); + auto x3 = Nullable!S1(si); + auto x4 = immutable Nullable!S1(si); + assert(x1.val == 1); + assert(x2.val == 1); + assert(x3.val == 1); + assert(x4.val == 1); + } + + auto nm = 10; + immutable ni = 10; + + { + auto sm = S2(&nm); + immutable si = immutable S2(&ni); + auto x1 = Nullable!S2(sm); + static assert(!__traits(compiles, { auto x2 = immutable Nullable!S2(sm); })); + static assert(!__traits(compiles, { auto x3 = Nullable!S2(si); })); + auto x4 = immutable Nullable!S2(si); + assert(*x1.val == 10); + assert(*x4.val == 10); + } + + { + auto sm = S3(&ni); + immutable si = immutable S3(&ni); + auto x1 = Nullable!S3(sm); + auto x2 = immutable Nullable!S3(sm); + auto x3 = Nullable!S3(si); + auto x4 = immutable Nullable!S3(si); + assert(*x1.val == 10); + assert(*x2.val == 10); + assert(*x3.val == 10); + assert(*x4.val == 10); + } +} +@safe unittest +{ + // Bugzila 10357 + import std.datetime; + Nullable!SysTime time = SysTime(0); +} +@system unittest +{ + import std.conv : to; + import std.array; + + // Bugzilla 10915 + Appender!string buffer; + + Nullable!int ni; + assert(ni.to!string() == "Nullable.null"); + + struct Test { string s; } + alias NullableTest = Nullable!Test; + + NullableTest nt = Test("test"); + assert(nt.to!string() == `Test("test")`); + + NullableTest ntn = Test("null"); + assert(ntn.to!string() == `Test("null")`); + + class TestToString + { + double d; + + this (double d) + { + this.d = d; + } + + override string toString() + { + return d.to!string(); + } + } + Nullable!TestToString ntts = new TestToString(2.5); + assert(ntts.to!string() == "2.5"); +} + +/** +Just like $(D Nullable!T), except that the null state is defined as a +particular value. For example, $(D Nullable!(uint, uint.max)) is an +$(D uint) that sets aside the value $(D uint.max) to denote a null +state. $(D Nullable!(T, nullValue)) is more storage-efficient than $(D +Nullable!T) because it does not need to store an extra $(D bool). + +Params: + T = The wrapped type for which Nullable provides a null value. + + nullValue = The null value which denotes the null state of this + `Nullable`. Must be of type `T`. + */ +struct Nullable(T, T nullValue) +{ + private T _value = nullValue; + +/** +Constructor initializing $(D this) with $(D value). + +Params: + value = The value to initialize this `Nullable` with. + */ + this(T value) + { + _value = value; + } + + template toString() + { + import std.format : FormatSpec, formatValue; + // Needs to be a template because of DMD @@BUG@@ 13737. + void toString()(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) + { + if (isNull) + { + sink.formatValue("Nullable.null", fmt); + } + else + { + sink.formatValue(_value, fmt); + } + } + } + +/** +Check if `this` is in the null state. + +Returns: + true $(B iff) `this` is in the null state, otherwise false. + */ + @property bool isNull() const + { + //Need to use 'is' if T is a nullable type and + //nullValue is null, or it's a compiler error + static if (is(CommonType!(T, typeof(null)) == T) && nullValue is null) + { + return _value is nullValue; + } + //Need to use 'is' if T is a float type + //because NaN != NaN + else static if (isFloatingPoint!T) + { + return _value is nullValue; + } + else + { + return _value == nullValue; + } + } + +/// +@system unittest +{ + Nullable!(int, -1) ni; + //Initialized to "null" state + assert(ni.isNull); + + ni = 0; + assert(!ni.isNull); +} + +// https://issues.dlang.org/show_bug.cgi?id=11135 +// disable test until https://issues.dlang.org/show_bug.cgi?id=15316 gets fixed +version (none) @system unittest +{ + foreach (T; AliasSeq!(float, double, real)) + { + Nullable!(T, T.init) nf; + //Initialized to "null" state + assert(nf.isNull); + assert(nf is typeof(nf).init); + + nf = 0; + assert(!nf.isNull); + + nf.nullify(); + assert(nf.isNull); + } +} + +/** +Forces $(D this) to the null state. + */ + void nullify()() + { + _value = nullValue; + } + +/// +@system unittest +{ + Nullable!(int, -1) ni = 0; + assert(!ni.isNull); + + ni = -1; + assert(ni.isNull); +} + +/** +Assigns $(D value) to the internally-held state. If the assignment +succeeds, $(D this) becomes non-null. No null checks are made. Note +that the assignment may leave $(D this) in the null state. + +Params: + value = A value of type `T` to assign to this `Nullable`. + If it is `nullvalue`, then the internal state of + this `Nullable` will be set to null. + */ + void opAssign()(T value) + { + _value = value; + } + +/** + If this `Nullable` wraps a type that already has a null value + (such as a pointer), and that null value is not given for + `nullValue`, then assigning the null value to this `Nullable` + is no different than assigning any other value of type `T`, + and the resulting code will look very strange. It is strongly + recommended that this be avoided by using `T`'s "built in" + null value for `nullValue`. + */ +@system unittest +{ + //Passes + enum nullVal = cast(int*) 0xCAFEBABE; + Nullable!(int*, nullVal) npi; + assert(npi.isNull); + + //Passes?! + npi = null; + assert(!npi.isNull); +} + +/** +Gets the value. $(D this) must not be in the null state. +This function is also called for the implicit conversion to $(D T). + +Returns: + The value held internally by this `Nullable`. + */ + @property ref inout(T) get() inout + { + //@@@6169@@@: We avoid any call that might evaluate nullValue's %s, + //Because it might messup get's purity and safety inference. + enum message = "Called `get' on null Nullable!(" ~ T.stringof ~ ",nullValue)."; + assert(!isNull, message); + return _value; + } + +/// +@system unittest +{ + import std.exception : assertThrown, assertNotThrown; + + Nullable!(int, -1) ni; + //`get` is implicitly called. Will throw + //an error in non-release mode + assertThrown!Throwable(ni == 0); + + ni = 0; + assertNotThrown!Throwable(ni == 0); +} + +/** +Implicitly converts to $(D T). +$(D this) must not be in the null state. + */ + alias get this; +} + +/// ditto +auto nullable(alias nullValue, T)(T t) + if (is (typeof(nullValue) == T)) +{ + return Nullable!(T, nullValue)(t); +} + +/// +@safe unittest +{ + Nullable!(size_t, size_t.max) indexOf(string[] haystack, string needle) + { + //Find the needle, returning -1 if not found + + return Nullable!(size_t, size_t.max).init; + } + + void sendLunchInvite(string name) + { + } + + //It's safer than C... + auto coworkers = ["Jane", "Jim", "Marry", "Fred"]; + auto pos = indexOf(coworkers, "Bob"); + if (!pos.isNull) + { + //Send Bob an invitation to lunch + sendLunchInvite(coworkers[pos]); + } + else + { + //Bob not found; report the error + } + + //And there's no overhead + static assert(Nullable!(size_t, size_t.max).sizeof == size_t.sizeof); +} + +/// +@system unittest +{ + import std.exception : assertThrown; + + Nullable!(int, int.min) a; + assert(a.isNull); + assertThrown!Throwable(a.get); + a = 5; + assert(!a.isNull); + assert(a == 5); + static assert(a.sizeof == int.sizeof); +} + +/// +@safe unittest +{ + auto a = nullable!(int.min)(8); + assert(a == 8); + a.nullify(); + assert(a.isNull); +} + +@safe unittest +{ + static int f(in Nullable!(int, int.min) x) { + return x.isNull ? 42 : x.get; + } + Nullable!(int, int.min) a; + assert(f(a) == 42); + a = 8; + assert(f(a) == 8); + a.nullify(); + assert(f(a) == 42); +} +@safe unittest +{ + // Ensure Nullable can be used in pure/nothrow/@safe environment. + function() @safe pure nothrow + { + Nullable!(int, int.min) n; + assert(n.isNull); + n = 4; + assert(!n.isNull); + assert(n == 4); + n.nullify(); + assert(n.isNull); + }(); +} +@system unittest +{ + // Ensure Nullable can be used when the value is not pure/nothrow/@system + static struct S + { + int x; + bool opEquals(const S s) const @system { return s.x == x; } + } + + Nullable!(S, S(711)) s; + assert(s.isNull); + s = S(5); + assert(!s.isNull); + assert(s.x == 5); + s.nullify(); + assert(s.isNull); +} +@safe unittest +{ + //Check nullable is nicelly embedable in a struct + static struct S1 + { + Nullable!(int, 0) ni; + } + static struct S2 //inspired from 9404 + { + Nullable!(int, 0) ni; + this(S2 other) + { + ni = other.ni; + } + void opAssign(S2 other) + { + ni = other.ni; + } + } + foreach (S; AliasSeq!(S1, S2)) + { + S a; + S b = a; + S c; + c = a; + } +} +@system unittest +{ + import std.conv : to; + + // Bugzilla 10915 + Nullable!(int, 1) ni = 1; + assert(ni.to!string() == "Nullable.null"); + + struct Test { string s; } + alias NullableTest = Nullable!(Test, Test("null")); + + NullableTest nt = Test("test"); + assert(nt.to!string() == `Test("test")`); + + NullableTest ntn = Test("null"); + assert(ntn.to!string() == "Nullable.null"); + + class TestToString + { + double d; + + this(double d) + { + this.d = d; + } + + override string toString() + { + return d.to!string(); + } + } + alias NullableTestToString = Nullable!(TestToString, null); + + NullableTestToString ntts = new TestToString(2.5); + assert(ntts.to!string() == "2.5"); +} + + +/** +Just like $(D Nullable!T), except that the object refers to a value +sitting elsewhere in memory. This makes assignments overwrite the +initially assigned value. Internally $(D NullableRef!T) only stores a +pointer to $(D T) (i.e., $(D Nullable!T.sizeof == (T*).sizeof)). + */ +struct NullableRef(T) +{ + private T* _value; + +/** +Constructor binding $(D this) to $(D value). + +Params: + value = The value to bind to. + */ + this(T* value) @safe pure nothrow + { + _value = value; + } + + template toString() + { + import std.format : FormatSpec, formatValue; + // Needs to be a template because of DMD @@BUG@@ 13737. + void toString()(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) + { + if (isNull) + { + sink.formatValue("Nullable.null", fmt); + } + else + { + sink.formatValue(*_value, fmt); + } + } + } + +/** +Binds the internal state to $(D value). + +Params: + value = A pointer to a value of type `T` to bind this `NullableRef` to. + */ + void bind(T* value) @safe pure nothrow + { + _value = value; + } + + /// + @safe unittest + { + NullableRef!int nr = new int(42); + assert(nr == 42); + + int* n = new int(1); + nr.bind(n); + assert(nr == 1); + } + +/** +Returns $(D true) if and only if $(D this) is in the null state. + +Returns: + true if `this` is in the null state, otherwise false. + */ + @property bool isNull() const @safe pure nothrow + { + return _value is null; + } + + /// + @safe unittest + { + NullableRef!int nr; + assert(nr.isNull); + + int* n = new int(42); + nr.bind(n); + assert(!nr.isNull && nr == 42); + } + +/** +Forces $(D this) to the null state. + */ + void nullify() @safe pure nothrow + { + _value = null; + } + + /// + @safe unittest + { + NullableRef!int nr = new int(42); + assert(!nr.isNull); + + nr.nullify(); + assert(nr.isNull); + } + +/** +Assigns $(D value) to the internally-held state. + +Params: + value = A value of type `T` to assign to this `NullableRef`. + If the internal state of this `NullableRef` has not + been initialized, an error will be thrown in + non-release mode. + */ + void opAssign()(T value) + if (isAssignable!T) //@@@9416@@@ + { + enum message = "Called `opAssign' on null NullableRef!" ~ T.stringof ~ "."; + assert(!isNull, message); + *_value = value; + } + + /// + @system unittest + { + import std.exception : assertThrown, assertNotThrown; + + NullableRef!int nr; + assert(nr.isNull); + assertThrown!Throwable(nr = 42); + + nr.bind(new int(0)); + assert(!nr.isNull); + assertNotThrown!Throwable(nr = 42); + assert(nr == 42); + } + +/** +Gets the value. $(D this) must not be in the null state. +This function is also called for the implicit conversion to $(D T). + */ + @property ref inout(T) get() inout @safe pure nothrow + { + enum message = "Called `get' on null NullableRef!" ~ T.stringof ~ "."; + assert(!isNull, message); + return *_value; + } + + /// + @system unittest + { + import std.exception : assertThrown, assertNotThrown; + + NullableRef!int nr; + //`get` is implicitly called. Will throw + //an error in non-release mode + assertThrown!Throwable(nr == 0); + + nr.bind(new int(0)); + assertNotThrown!Throwable(nr == 0); + } + +/** +Implicitly converts to $(D T). +$(D this) must not be in the null state. + */ + alias get this; +} + +/// ditto +auto nullableRef(T)(T* t) +{ + return NullableRef!T(t); +} + +/// +@system unittest +{ + import std.exception : assertThrown; + + int x = 5, y = 7; + auto a = nullableRef(&x); + assert(!a.isNull); + assert(a == 5); + assert(x == 5); + a = 42; + assert(x == 42); + assert(!a.isNull); + assert(a == 42); + a.nullify(); + assert(x == 42); + assert(a.isNull); + assertThrown!Throwable(a.get); + assertThrown!Throwable(a = 71); + a.bind(&y); + assert(a == 7); + y = 135; + assert(a == 135); +} +@system unittest +{ + static int f(in NullableRef!int x) { + return x.isNull ? 42 : x.get; + } + int x = 5; + auto a = nullableRef(&x); + assert(f(a) == 5); + a.nullify(); + assert(f(a) == 42); +} +@safe unittest +{ + // Ensure NullableRef can be used in pure/nothrow/@safe environment. + function() @safe pure nothrow + { + auto storage = new int; + *storage = 19902; + NullableRef!int n; + assert(n.isNull); + n.bind(storage); + assert(!n.isNull); + assert(n == 19902); + n = 2294; + assert(n == 2294); + assert(*storage == 2294); + n.nullify(); + assert(n.isNull); + }(); +} +@system unittest +{ + // Ensure NullableRef can be used when the value is not pure/nothrow/@safe + static struct S + { + int x; + this(this) @system {} + bool opEquals(const S s) const @system { return s.x == x; } + } + + auto storage = S(5); + + NullableRef!S s; + assert(s.isNull); + s.bind(&storage); + assert(!s.isNull); + assert(s.x == 5); + s.nullify(); + assert(s.isNull); +} +@safe unittest +{ + //Check nullable is nicelly embedable in a struct + static struct S1 + { + NullableRef!int ni; + } + static struct S2 //inspired from 9404 + { + NullableRef!int ni; + this(S2 other) + { + ni = other.ni; + } + void opAssign(S2 other) + { + ni = other.ni; + } + } + foreach (S; AliasSeq!(S1, S2)) + { + S a; + S b = a; + S c; + c = a; + } +} +@system unittest +{ + import std.conv : to; + + // Bugzilla 10915 + NullableRef!int nri; + assert(nri.to!string() == "Nullable.null"); + + struct Test + { + string s; + } + NullableRef!Test nt = new Test("test"); + assert(nt.to!string() == `Test("test")`); + + class TestToString + { + double d; + + this(double d) + { + this.d = d; + } + + override string toString() + { + return d.to!string(); + } + } + TestToString tts = new TestToString(2.5); + NullableRef!TestToString ntts = &tts; + assert(ntts.to!string() == "2.5"); +} + + +/** +$(D BlackHole!Base) is a subclass of $(D Base) which automatically implements +all abstract member functions in $(D Base) as do-nothing functions. Each +auto-implemented function just returns the default value of the return type +without doing anything. + +The name came from +$(HTTP search.cpan.org/~sburke/Class-_BlackHole-0.04/lib/Class/_BlackHole.pm, Class::_BlackHole) +Perl module by Sean M. Burke. + +Params: + Base = A non-final class for `BlackHole` to inherit from. + +See_Also: + $(LREF AutoImplement), $(LREF generateEmptyFunction) + */ +alias BlackHole(Base) = AutoImplement!(Base, generateEmptyFunction, isAbstractFunction); + +/// +@system unittest +{ + import std.math : isNaN; + + static abstract class C + { + int m_value; + this(int v) { m_value = v; } + int value() @property { return m_value; } + + abstract real realValue() @property; + abstract void doSomething(); + } + + auto c = new BlackHole!C(42); + assert(c.value == 42); + + // Returns real.init which is NaN + assert(c.realValue.isNaN); + // Abstract functions are implemented as do-nothing + c.doSomething(); +} + +@system unittest +{ + import std.math : isNaN; + + // return default + { + interface I_1 { real test(); } + auto o = new BlackHole!I_1; + assert(o.test().isNaN()); // NaN + } + // doc example + { + static class C + { + int m_value; + this(int v) { m_value = v; } + int value() @property { return m_value; } + + abstract real realValue() @property; + abstract void doSomething(); + } + + auto c = new BlackHole!C(42); + assert(c.value == 42); + + assert(c.realValue.isNaN); // NaN + c.doSomething(); + } + + // Bugzilla 12058 + interface Foo + { + inout(Object) foo() inout; + } + BlackHole!Foo o; +} + + +/** +$(D WhiteHole!Base) is a subclass of $(D Base) which automatically implements +all abstract member functions as functions that always fail. These functions +simply throw an $(D Error) and never return. `Whitehole` is useful for +trapping the use of class member functions that haven't been implemented. + +The name came from +$(HTTP search.cpan.org/~mschwern/Class-_WhiteHole-0.04/lib/Class/_WhiteHole.pm, Class::_WhiteHole) +Perl module by Michael G Schwern. + +Params: + Base = A non-final class for `WhiteHole` to inherit from. + +See_Also: + $(LREF AutoImplement), $(LREF generateAssertTrap) + */ +alias WhiteHole(Base) = AutoImplement!(Base, generateAssertTrap, isAbstractFunction); + +/// +@system unittest +{ + import std.exception : assertThrown; + + static class C + { + abstract void notYetImplemented(); + } + + auto c = new WhiteHole!C; + assertThrown!NotImplementedError(c.notYetImplemented()); // throws an Error +} + +// / ditto +class NotImplementedError : Error +{ + this(string method) + { + super(method ~ " is not implemented"); + } +} + +@system unittest +{ + import std.exception : assertThrown; + // nothrow + { + interface I_1 + { + void foo(); + void bar() nothrow; + } + auto o = new WhiteHole!I_1; + assertThrown!NotImplementedError(o.foo()); + assertThrown!NotImplementedError(o.bar()); + } + // doc example + { + static class C + { + abstract void notYetImplemented(); + } + + auto c = new WhiteHole!C; + try + { + c.notYetImplemented(); + assert(0); + } + catch (Error e) {} + } +} + + +/** +$(D AutoImplement) automatically implements (by default) all abstract member +functions in the class or interface $(D Base) in specified way. + +The second version of $(D AutoImplement) automatically implements +$(D Interface), while deriving from $(D BaseClass). + +Params: + how = template which specifies _how functions will be implemented/overridden. + + Two arguments are passed to $(D how): the type $(D Base) and an alias + to an implemented function. Then $(D how) must return an implemented + function body as a string. + + The generated function body can use these keywords: + $(UL + $(LI $(D a0), $(D a1), …: arguments passed to the function;) + $(LI $(D args): a tuple of the arguments;) + $(LI $(D self): an alias to the function itself;) + $(LI $(D parent): an alias to the overridden function (if any).) + ) + + You may want to use templated property functions (instead of Implicit + Template Properties) to generate complex functions: +-------------------- +// Prints log messages for each call to overridden functions. +string generateLogger(C, alias fun)() @property +{ + import std.traits; + enum qname = C.stringof ~ "." ~ __traits(identifier, fun); + string stmt; + + stmt ~= q{ struct Importer { import std.stdio; } }; + stmt ~= `Importer.writeln("Log: ` ~ qname ~ `(", args, ")");`; + static if (!__traits(isAbstractFunction, fun)) + { + static if (is(ReturnType!fun == void)) + stmt ~= q{ parent(args); }; + else + stmt ~= q{ + auto r = parent(args); + Importer.writeln("--> ", r); + return r; + }; + } + return stmt; +} +-------------------- + + what = template which determines _what functions should be + implemented/overridden. + + An argument is passed to $(D what): an alias to a non-final member + function in $(D Base). Then $(D what) must return a boolean value. + Return $(D true) to indicate that the passed function should be + implemented/overridden. + +-------------------- +// Sees if fun returns something. +enum bool hasValue(alias fun) = !is(ReturnType!(fun) == void); +-------------------- + + +Note: + +Generated code is inserted in the scope of $(D std.typecons) module. Thus, +any useful functions outside $(D std.typecons) cannot be used in the generated +code. To workaround this problem, you may $(D import) necessary things in a +local struct, as done in the $(D generateLogger()) template in the above +example. + + +BUGS: + +$(UL + $(LI Variadic arguments to constructors are not forwarded to super.) + $(LI Deep interface inheritance causes compile error with messages like + "Error: function std.typecons._AutoImplement!(Foo)._AutoImplement.bar + does not override any function". [$(BUGZILLA 2525), $(BUGZILLA 3525)] ) + $(LI The $(D parent) keyword is actually a delegate to the super class' + corresponding member function. [$(BUGZILLA 2540)] ) + $(LI Using alias template parameter in $(D how) and/or $(D what) may cause + strange compile error. Use template tuple parameter instead to workaround + this problem. [$(BUGZILLA 4217)] ) +) + */ +class AutoImplement(Base, alias how, alias what = isAbstractFunction) : Base + if (!is(how == class)) +{ + private alias autoImplement_helper_ = + AutoImplement_Helper!("autoImplement_helper_", "Base", Base, typeof(this), how, what); + mixin(autoImplement_helper_.code); +} + +/// ditto +class AutoImplement( + Interface, BaseClass, alias how, + alias what = isAbstractFunction) : BaseClass, Interface + if (is(Interface == interface) && is(BaseClass == class)) +{ + private alias autoImplement_helper_ = AutoImplement_Helper!( + "autoImplement_helper_", "Interface", Interface, typeof(this), how, what); + mixin(autoImplement_helper_.code); +} + +/* + * Code-generating stuffs are encupsulated in this helper template so that + * namespace pollution, which can cause name confliction with Base's public + * members, should be minimized. + */ +private template AutoImplement_Helper(string myName, string baseName, + Base, Self, alias generateMethodBody, alias cherrypickMethod) +{ +private static: + //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// + // Internal stuffs + //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// + + // Returns function overload sets in the class C, filtered with pred. + template enumerateOverloads(C, alias pred) + { + template Impl(names...) + { + import std.meta : Filter; + static if (names.length > 0) + { + alias methods = Filter!(pred, MemberFunctionsTuple!(C, names[0])); + alias next = Impl!(names[1 .. $]); + + static if (methods.length > 0) + alias Impl = AliasSeq!(OverloadSet!(names[0], methods), next); + else + alias Impl = next; + } + else + alias Impl = AliasSeq!(); + } + + alias enumerateOverloads = Impl!(__traits(allMembers, C)); + } + + //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// + // Target functions + //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// + + // Add a non-final check to the cherrypickMethod. + enum bool canonicalPicker(fun.../+[BUG 4217]+/) = + !__traits(isFinalFunction, fun[0]) && cherrypickMethod!(fun); + + /* + * A tuple of overload sets, each item of which consists of functions to be + * implemented by the generated code. + */ + alias targetOverloadSets = enumerateOverloads!(Base, canonicalPicker); + + /* + * Super class of this AutoImplement instance + */ + alias Super = BaseTypeTuple!(Self)[0]; + static assert(is(Super == class)); + static assert(is(Base == interface) || is(Super == Base)); + + /* + * A tuple of the super class' constructors. Used for forwarding + * constructor calls. + */ + static if (__traits(hasMember, Super, "__ctor")) + alias ctorOverloadSet = OverloadSet!("__ctor", __traits(getOverloads, Super, "__ctor")); + else + alias ctorOverloadSet = OverloadSet!("__ctor"); // empty + + + //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// + // Type information + //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// + + /* + * The generated code will be mixed into AutoImplement, which will be + * instantiated in this module's scope. Thus, any user-defined types are + * out of scope and cannot be used directly (i.e. by their names). + * + * We will use FuncInfo instances for accessing return types and parameter + * types of the implemented functions. The instances will be populated to + * the AutoImplement's scope in a certain way; see the populate() below. + */ + + // Returns the preferred identifier for the FuncInfo instance for the i-th + // overloaded function with the name. + template INTERNAL_FUNCINFO_ID(string name, size_t i) + { + import std.format : format; + + enum string INTERNAL_FUNCINFO_ID = format("F_%s_%s", name, i); + } + + /* + * Insert FuncInfo instances about all the target functions here. This + * enables the generated code to access type information via, for example, + * "autoImplement_helper_.F_foo_1". + */ + template populate(overloads...) + { + static if (overloads.length > 0) + { + mixin populate!(overloads[0].name, overloads[0].contents); + mixin populate!(overloads[1 .. $]); + } + } + template populate(string name, methods...) + { + static if (methods.length > 0) + { + mixin populate!(name, methods[0 .. $ - 1]); + // + alias target = methods[$ - 1]; + enum ith = methods.length - 1; + mixin("alias " ~ INTERNAL_FUNCINFO_ID!(name, ith) ~ " = FuncInfo!target;"); + } + } + + public mixin populate!(targetOverloadSets); + public mixin populate!( ctorOverloadSet ); + + + //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// + // Code-generating policies + //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// + + /* Common policy configurations for generating constructors and methods. */ + template CommonGeneratingPolicy() + { + // base class identifier which generated code should use + enum string BASE_CLASS_ID = baseName; + + // FuncInfo instance identifier which generated code should use + template FUNCINFO_ID(string name, size_t i) + { + enum string FUNCINFO_ID = + myName ~ "." ~ INTERNAL_FUNCINFO_ID!(name, i); + } + } + + /* Policy configurations for generating constructors. */ + template ConstructorGeneratingPolicy() + { + mixin CommonGeneratingPolicy; + + /* Generates constructor body. Just forward to the base class' one. */ + string generateFunctionBody(ctor.../+[BUG 4217]+/)() @property + { + enum varstyle = variadicFunctionStyle!(typeof(&ctor[0])); + + static if (varstyle & (Variadic.c | Variadic.d)) + { + // the argptr-forwarding problem + //pragma(msg, "Warning: AutoImplement!(", Base, ") ", + // "ignored variadic arguments to the constructor ", + // FunctionTypeOf!(typeof(&ctor[0])) ); + } + return "super(args);"; + } + } + + /* Policy configurations for genearting target methods. */ + template MethodGeneratingPolicy() + { + mixin CommonGeneratingPolicy; + + /* Geneartes method body. */ + string generateFunctionBody(func.../+[BUG 4217]+/)() @property + { + return generateMethodBody!(Base, func); // given + } + } + + + //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// + // Generated code + //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// + + alias ConstructorGenerator = MemberFunctionGenerator!(ConstructorGeneratingPolicy!()); + alias MethodGenerator = MemberFunctionGenerator!(MethodGeneratingPolicy!()); + + public enum string code = + ConstructorGenerator.generateCode!( ctorOverloadSet ) ~ "\n" ~ + MethodGenerator.generateCode!(targetOverloadSets); + + debug (SHOW_GENERATED_CODE) + { + pragma(msg, "-------------------- < ", Base, " >"); + pragma(msg, code); + pragma(msg, "--------------------"); + } +} + +//debug = SHOW_GENERATED_CODE; +@system unittest +{ + import core.vararg; + // no function to implement + { + interface I_1 {} + auto o = new BlackHole!I_1; + } + // parameters + { + interface I_3 { void test(int, in int, out int, ref int, lazy int); } + auto o = new BlackHole!I_3; + } + // use of user-defined type + { + struct S {} + interface I_4 { S test(); } + auto o = new BlackHole!I_4; + } + // overloads + { + interface I_5 + { + void test(string); + real test(real); + int test(); + } + auto o = new BlackHole!I_5; + } + // constructor forwarding + { + static class C_6 + { + this(int n) { assert(n == 42); } + this(string s) { assert(s == "Deeee"); } + this(...) {} + } + auto o1 = new BlackHole!C_6(42); + auto o2 = new BlackHole!C_6("Deeee"); + auto o3 = new BlackHole!C_6(1, 2, 3, 4); + } + // attributes + { + interface I_7 + { + ref int test_ref(); + int test_pure() pure; + int test_nothrow() nothrow; + int test_property() @property; + int test_safe() @safe; + int test_trusted() @trusted; + int test_system() @system; + int test_pure_nothrow() pure nothrow; + } + auto o = new BlackHole!I_7; + } + // storage classes + { + interface I_8 + { + void test_const() const; + void test_immutable() immutable; + void test_shared() shared; + void test_shared_const() shared const; + } + auto o = new BlackHole!I_8; + } + // use baseclass + { + static class C_9 + { + private string foo_; + + this(string s) { + foo_ = s; + } + + protected string boilerplate() @property + { + return "Boilerplate stuff."; + } + + public string foo() @property + { + return foo_; + } + } + + interface I_10 + { + string testMethod(size_t); + } + + static string generateTestMethod(C, alias fun)() @property + { + return "return this.boilerplate[0 .. a0];"; + } + + auto o = new AutoImplement!(I_10, C_9, generateTestMethod)("Testing"); + assert(o.testMethod(11) == "Boilerplate"); + assert(o.foo == "Testing"); + } + /+ // deep inheritance + { + // XXX [BUG 2525,3525] + // NOTE: [r494] func.c(504-571) FuncDeclaration::semantic() + interface I { void foo(); } + interface J : I {} + interface K : J {} + static abstract class C_9 : K {} + auto o = new BlackHole!C_9; + }+/ +} + +// Issue 17177 - AutoImplement fails on function overload sets with "cannot infer type from overloaded function symbol" +@system unittest +{ + static class Issue17177 + { + private string n_; + + public { + Issue17177 overloaded(string n) + { + this.n_ = n; + + return this; + } + + string overloaded() + { + return this.n_; + } + } + } + + static string how(C, alias fun)() + { + static if (!is(ReturnType!fun == void)) + { + return q{ + return parent(args); + }; + } + else + { + return q{ + parent(args); + }; + } + } + + alias Implementation = AutoImplement!(Issue17177, how, templateNot!isFinalFunction); +} + +version (unittest) +{ + // Issue 10647 + // Add prefix "issue10647_" as a workaround for issue 1238 + private string issue10647_generateDoNothing(C, alias fun)() @property + { + string stmt; + + static if (is(ReturnType!fun == void)) + stmt ~= ""; + else + { + string returnType = ReturnType!fun.stringof; + stmt ~= "return "~returnType~".init;"; + } + return stmt; + } + + private template issue10647_isAlwaysTrue(alias fun) + { + enum issue10647_isAlwaysTrue = true; + } + + // Do nothing template + private template issue10647_DoNothing(Base) + { + alias issue10647_DoNothing = AutoImplement!(Base, issue10647_generateDoNothing, issue10647_isAlwaysTrue); + } + + // A class to be overridden + private class issue10647_Foo{ + void bar(int a) { } + } +} +@system unittest +{ + auto foo = new issue10647_DoNothing!issue10647_Foo(); + foo.bar(13); +} + +/* +Used by MemberFunctionGenerator. + */ +package template OverloadSet(string nam, T...) +{ + enum string name = nam; + alias contents = T; +} + +/* +Used by MemberFunctionGenerator. + */ +package template FuncInfo(alias func, /+[BUG 4217 ?]+/ T = typeof(&func)) +{ + alias RT = ReturnType!T; + alias PT = Parameters!T; +} +package template FuncInfo(Func) +{ + alias RT = ReturnType!Func; + alias PT = Parameters!Func; +} + +/* +General-purpose member function generator. +-------------------- +template GeneratingPolicy() +{ + // [optional] the name of the class where functions are derived + enum string BASE_CLASS_ID; + + // [optional] define this if you have only function types + enum bool WITHOUT_SYMBOL; + + // [optional] Returns preferred identifier for i-th parameter. + template PARAMETER_VARIABLE_ID(size_t i); + + // Returns the identifier of the FuncInfo instance for the i-th overload + // of the specified name. The identifier must be accessible in the scope + // where generated code is mixed. + template FUNCINFO_ID(string name, size_t i); + + // Returns implemented function body as a string. When WITHOUT_SYMBOL is + // defined, the latter is used. + template generateFunctionBody(alias func); + template generateFunctionBody(string name, FuncType); +} +-------------------- + */ +package template MemberFunctionGenerator(alias Policy) +{ +private static: + //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// + // Internal stuffs + //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// + import std.format; + + enum CONSTRUCTOR_NAME = "__ctor"; + + // true if functions are derived from a base class + enum WITH_BASE_CLASS = __traits(hasMember, Policy, "BASE_CLASS_ID"); + + // true if functions are specified as types, not symbols + enum WITHOUT_SYMBOL = __traits(hasMember, Policy, "WITHOUT_SYMBOL"); + + // preferred identifier for i-th parameter variable + static if (__traits(hasMember, Policy, "PARAMETER_VARIABLE_ID")) + { + alias PARAMETER_VARIABLE_ID = Policy.PARAMETER_VARIABLE_ID; + } + else + { + enum string PARAMETER_VARIABLE_ID(size_t i) = format("a%s", i); + // default: a0, a1, ... + } + + // Returns a tuple consisting of 0,1,2,...,n-1. For static foreach. + template CountUp(size_t n) + { + static if (n > 0) + alias CountUp = AliasSeq!(CountUp!(n - 1), n - 1); + else + alias CountUp = AliasSeq!(); + } + + + //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// + // Code generator + //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// + + /* + * Runs through all the target overload sets and generates D code which + * implements all the functions in the overload sets. + */ + public string generateCode(overloads...)() @property + { + string code = ""; + + // run through all the overload sets + foreach (i_; CountUp!(0 + overloads.length)) // workaround + { + enum i = 0 + i_; // workaround + alias oset = overloads[i]; + + code ~= generateCodeForOverloadSet!(oset); + + static if (WITH_BASE_CLASS && oset.name != CONSTRUCTOR_NAME) + { + // The generated function declarations may hide existing ones + // in the base class (cf. HiddenFuncError), so we put an alias + // declaration here to reveal possible hidden functions. + code ~= format("alias %s = %s.%s;\n", + oset.name, + Policy.BASE_CLASS_ID, // [BUG 2540] super. + oset.name); + } + } + return code; + } + + // handle each overload set + private string generateCodeForOverloadSet(alias oset)() @property + { + string code = ""; + + foreach (i_; CountUp!(0 + oset.contents.length)) // workaround + { + enum i = 0 + i_; // workaround + code ~= generateFunction!( + Policy.FUNCINFO_ID!(oset.name, i), oset.name, + oset.contents[i]) ~ "\n"; + } + return code; + } + + /* + * Returns D code which implements the function func. This function + * actually generates only the declarator part; the function body part is + * generated by the functionGenerator() policy. + */ + public string generateFunction( + string myFuncInfo, string name, func... )() @property + { + import std.format : format; + + enum isCtor = (name == CONSTRUCTOR_NAME); + + string code; // the result + + auto paramsRes = generateParameters!(myFuncInfo, func)(); + code ~= paramsRes.imports; + + /*** Function Declarator ***/ + { + alias Func = FunctionTypeOf!(func); + alias FA = FunctionAttribute; + enum atts = functionAttributes!(func); + enum realName = isCtor ? "this" : name; + + // FIXME?? Make it so that these aren't CTFE funcs any more, since + // Format is deprecated, and format works at compile time? + /* Made them CTFE funcs just for the sake of Format!(...) */ + + // return type with optional "ref" + static string make_returnType() + { + string rtype = ""; + + if (!isCtor) + { + if (atts & FA.ref_) rtype ~= "ref "; + rtype ~= myFuncInfo ~ ".RT"; + } + return rtype; + } + enum returnType = make_returnType(); + + // function attributes attached after declaration + static string make_postAtts() + { + string poatts = ""; + if (atts & FA.pure_ ) poatts ~= " pure"; + if (atts & FA.nothrow_) poatts ~= " nothrow"; + if (atts & FA.property) poatts ~= " @property"; + if (atts & FA.safe ) poatts ~= " @safe"; + if (atts & FA.trusted ) poatts ~= " @trusted"; + return poatts; + } + enum postAtts = make_postAtts(); + + // function storage class + static string make_storageClass() + { + string postc = ""; + if (is(Func == shared)) postc ~= " shared"; + if (is(Func == const)) postc ~= " const"; + if (is(Func == inout)) postc ~= " inout"; + if (is(Func == immutable)) postc ~= " immutable"; + return postc; + } + enum storageClass = make_storageClass(); + + // + if (__traits(isVirtualMethod, func)) + code ~= "override "; + code ~= format("extern(%s) %s %s(%s) %s %s\n", + functionLinkage!(func), + returnType, + realName, + paramsRes.params, + postAtts, storageClass ); + } + + /*** Function Body ***/ + code ~= "{\n"; + { + enum nparams = Parameters!(func).length; + + /* Declare keywords: args, self and parent. */ + string preamble; + + preamble ~= "alias args = AliasSeq!(" ~ enumerateParameters!(nparams) ~ ");\n"; + if (!isCtor) + { + preamble ~= "alias self = " ~ name ~ ";\n"; + if (WITH_BASE_CLASS && !__traits(isAbstractFunction, func)) + preamble ~= "alias parent = AliasSeq!(__traits(getMember, super, \"" ~ name ~ "\"))[0];"; + } + + // Function body + static if (WITHOUT_SYMBOL) + enum fbody = Policy.generateFunctionBody!(name, func); + else + enum fbody = Policy.generateFunctionBody!(func); + + code ~= preamble; + code ~= fbody; + } + code ~= "}"; + + return code; + } + + /* + * Returns D code which declares function parameters, + * and optionally any imports (e.g. core.vararg) + * "ref int a0, real a1, ..." + */ + static struct GenParams { string imports, params; } + private GenParams generateParameters(string myFuncInfo, func...)() + { + alias STC = ParameterStorageClass; + alias stcs = ParameterStorageClassTuple!(func); + enum nparams = stcs.length; + + string imports = ""; // any imports required + string params = ""; // parameters + + foreach (i, stc; stcs) + { + if (i > 0) params ~= ", "; + + // Parameter storage classes. + if (stc & STC.scope_) params ~= "scope "; + if (stc & STC.out_ ) params ~= "out "; + if (stc & STC.ref_ ) params ~= "ref "; + if (stc & STC.lazy_ ) params ~= "lazy "; + + // Take parameter type from the FuncInfo. + params ~= format("%s.PT[%s]", myFuncInfo, i); + + // Declare a parameter variable. + params ~= " " ~ PARAMETER_VARIABLE_ID!(i); + } + + // Add some ellipsis part if needed. + auto style = variadicFunctionStyle!(func); + final switch (style) + { + case Variadic.no: + break; + + case Variadic.c, Variadic.d: + imports ~= "import core.vararg;\n"; + // (...) or (a, b, ...) + params ~= (nparams == 0) ? "..." : ", ..."; + break; + + case Variadic.typesafe: + params ~= " ..."; + break; + } + + return typeof(return)(imports, params); + } + + // Returns D code which enumerates n parameter variables using comma as the + // separator. "a0, a1, a2, a3" + private string enumerateParameters(size_t n)() @property + { + string params = ""; + + foreach (i_; CountUp!(n)) + { + enum i = 0 + i_; // workaround + if (i > 0) params ~= ", "; + params ~= PARAMETER_VARIABLE_ID!(i); + } + return params; + } +} + + +/** +Predefined how-policies for $(D AutoImplement). These templates are also used by +$(D BlackHole) and $(D WhiteHole), respectively. + */ +template generateEmptyFunction(C, func.../+[BUG 4217]+/) +{ + static if (is(ReturnType!(func) == void)) + enum string generateEmptyFunction = q{ + }; + else static if (functionAttributes!(func) & FunctionAttribute.ref_) + enum string generateEmptyFunction = q{ + static typeof(return) dummy; + return dummy; + }; + else + enum string generateEmptyFunction = q{ + return typeof(return).init; + }; +} + +/// ditto +template generateAssertTrap(C, func...) +{ + enum string generateAssertTrap = + `throw new NotImplementedError("` ~ C.stringof ~ "." + ~ __traits(identifier, func) ~ `");`; +} + +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 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 opCast(T)() {} Object instance(); } + interface J { @disable 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()); +} + +/** + * Supports structural based typesafe conversion. + * + * If $(D Source) has structural conformance with the $(D interface) $(D Targets), + * wrap creates internal wrapper class which inherits $(D Targets) and + * wrap $(D src) object, then return it. + */ +template wrap(Targets...) +if (Targets.length >= 1 && allSatisfy!(isMutable, Targets)) +{ + import std.meta : staticMap; + + // strict upcast + auto wrap(Source)(inout Source src) @trusted pure nothrow + 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!(Bind!(isImplicitlyConvertible, Source), Targets)) + { + auto wrap(inout Source src) + { + static assert(hasRequireMethods!(), + "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); + } + + template FuncInfo(string s, F) + { + enum name = s; + alias type = F; + } + + // Concat all Targets function members into one tuple + template Concat(size_t i = 0) + { + static if (i >= Targets.length) + alias Concat = AliasSeq!(); + else + { + alias Concat = AliasSeq!(GetOverloadedMethods!(Targets[i]), Concat!(i + 1)); + } + } + // Remove duplicated functions based on the identifier name and function type covariance + template Uniq(members...) + { + static if (members.length == 0) + alias Uniq = 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 + { + enum ptrdiff_t check = + __traits(identifier, func) == __traits(identifier, mem[i]) && + !is(DerivedFunctionType!(type, FunctionTypeOf!(mem[i])) == void) + ? i : 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 = Uniq!(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 Uniq = AliasSeq!(FuncInfo!(name, F), remain[1 .. $]); + } + else + alias Uniq = AliasSeq!(FuncInfo!(name, typex), remain); + } + else + { + alias Uniq = AliasSeq!(FuncInfo!(name, type), Uniq!(members[1 .. $])); + } + } + } + alias TargetMembers = Uniq!(Concat!()); // list of FuncInfo + alias SourceMembers = GetOverloadedMethods!Source; // list of function symbols + + // Check whether all of SourceMembers satisfy covariance target in TargetMembers + template hasRequireMethods(size_t i = 0) + { + static if (i >= TargetMembers.length) + enum hasRequireMethods = true; + else + { + enum hasRequireMethods = + findCovariantFunction!(TargetMembers[i], Source, SourceMembers) != -1 && + hasRequireMethods!(i + 1); + } + } + + // Internal wrapper class + final class Impl : Structural, 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; } + + // BUG: making private should work with NVI. + protected final inout(Object) _wrap_getSource() inout @trusted + { + return dynamicCast!(inout Object)(_wrap_source); + } + + import std.conv : to; + import std.functional : forward; + template generateFun(size_t i) + { + enum name = TargetMembers[i].name; + enum fa = functionAttributes!(TargetMembers[i].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!(TargetMembers[i].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; + } + enum n = to!string(i); + static if (fa & FunctionAttribute.property) + { + static if (Parameters!(TargetMembers[i].type).length == 0) + enum fbody = "_wrap_source."~name; + else + enum fbody = "_wrap_source."~name~" = forward!args"; + } + else + { + enum fbody = "_wrap_source."~name~"(forward!args)"; + } + enum generateFun = + "override "~stc~"ReturnType!(TargetMembers["~n~"].type) " + ~ name~"(Parameters!(TargetMembers["~n~"].type) args) "~mod~ + "{ return "~fbody~"; }"; + } + + public: + mixin mixinAll!( + staticMap!(generateFun, staticIota!(0, TargetMembers.length))); + } + } +} +/// ditto +template wrap(Targets...) +if (Targets.length >= 1 && !allSatisfy!(isMutable, Targets)) +{ + import std.meta : staticMap; + + alias wrap = .wrap!(staticMap!(Unqual, Targets)); +} + +// Internal class to support dynamic cross-casting +private interface Structural +{ + inout(Object) _wrap_getSource() inout @safe pure nothrow; +} + +/** + * Extract object which wrapped by $(D wrap). + */ +template unwrap(Target) +if (isMutable!Target) +{ + // strict downcast + auto unwrap(Source)(inout Source src) @trusted pure nothrow + if (is(Target : Source)) + { + alias T = Select!(is(Source == shared), shared Target, Target); + return dynamicCast!(inout T)(src); + } + // structural downcast + auto unwrap(Source)(inout Source src) @trusted pure nothrow + if (!is(Target : Source)) + { + alias T = Select!(is(Source == shared), shared Target, Target); + Object o = dynamicCast!(Object)(src); // remove qualifier + do + { + if (auto a = dynamicCast!(Structural)(o)) + { + if (auto d = dynamicCast!(inout T)(o = a._wrap_getSource())) + return d; + } + else if (auto d = dynamicCast!(inout T)(o)) + return d; + else + break; + } while (o); + return null; + } +} +/// ditto +template unwrap(Target) +if (!isMutable!Target) +{ + alias unwrap = .unwrap!(Unqual!Target); +} + +/// +@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; } + } + + Duck d1 = new Duck(); + Human h1 = new Human(); + + interface Refleshable + { + int reflesh(); + } + // does not have structural conformance + static assert(!__traits(compiles, d1.wrap!Refleshable)); + static assert(!__traits(compiles, h1.wrap!Refleshable)); + + // 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; + assert(qh.quack() == 2); // calls Human.quack + // structural downcast + Human h2 = qh.unwrap!Human; + assert(h2 is h1); + + // structural upcast (two steps) + Quack qx = h1.wrap!Quack; // Human -> Quack + Flyer fx = qx.wrap!Flyer; // Quack -> Flyer + assert(fx.height == 20); // calls Human.height + // strucural downcast (two steps) + Quack qy = fx.unwrap!Quack; // Flyer -> Quack + Human hy = qy.unwrap!Human; // Quack -> Human + assert(hy is h1); + // strucural downcast (one step) + Human hz = fx.unwrap!Human; // Flyer -> Human + assert(hz is h1); +} +/// +@system unittest +{ + import std.traits : FunctionAttribute, functionAttributes; + 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); +} +@system unittest +{ + 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); + } +} +@system unittest +{ + // Bugzilla 10377 + import std.range, std.algorithm; + + 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])); +} +@system unittest +{ + // Bugzilla 10536 + 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); +} +@system unittest +{ + // Enhancement 10538 + 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); +} + +// Make a tuple of non-static function symbols +package template GetOverloadedMethods(T) +{ + import std.meta : Filter; + + alias allMembers = AliasSeq!(__traits(allMembers, T)); + template follows(size_t i = 0) + { + static if (i >= allMembers.length) + { + alias follows = AliasSeq!(); + } + else static if (!__traits(compiles, mixin("T."~allMembers[i]))) + { + alias follows = follows!(i + 1); + } + else + { + enum name = allMembers[i]; + + template isMethod(alias f) + { + static if (is(typeof(&f) F == F*) && is(F == function)) + enum isMethod = !__traits(isStaticFunction, f); + else + enum isMethod = false; + } + alias follows = AliasSeq!( + std.meta.Filter!(isMethod, __traits(getOverloads, T, name)), + follows!(i + 1)); + } + } + alias GetOverloadedMethods = follows!(); +} +// 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; +} + +private enum TypeModifier +{ + mutable = 0, // type is mutable + const_ = 1, // type is const + immutable_ = 2, // type is immutable + shared_ = 4, // type is shared + inout_ = 8, // type is wild +} +private template TypeMod(T) +{ + static if (is(T == immutable)) + { + enum mod1 = TypeModifier.immutable_; + enum mod2 = 0; + } + else + { + enum mod1 = is(T == shared) ? TypeModifier.shared_ : 0; + static if (is(T == const)) + enum mod2 = TypeModifier.const_; + else static if (is(T == inout)) + enum mod2 = TypeModifier.inout_; + else + enum mod2 = TypeModifier.mutable; + } + enum TypeMod = cast(TypeModifier)(mod1 | mod2); +} + +version (unittest) +{ + private template UnittestFuncInfo(alias f) + { + enum name = __traits(identifier, f); + alias type = FunctionTypeOf!f; + } +} +@system unittest +{ + class A + { + int draw() { return 1; } + @property int value() { return 2; } + final int run() { return 3; } + } + alias methods = GetOverloadedMethods!A; + + alias int F1(); + alias @property int F2(); + alias string F3(); + alias nothrow @trusted uint F4(); + alias int F5(Object); + alias bool F6(Object); + static assert(methods.length == 3 + 4); + static assert(__traits(identifier, methods[0]) == "draw" && is(typeof(&methods[0]) == F1*)); + static assert(__traits(identifier, methods[1]) == "value" && is(typeof(&methods[1]) == F2*)); + static assert(__traits(identifier, methods[2]) == "run" && is(typeof(&methods[2]) == F1*)); + + int draw(); + @property int value(); + void opEquals(); + int nomatch(); + static assert(findCovariantFunction!(UnittestFuncInfo!draw, A, methods) == 0); + static assert(findCovariantFunction!(UnittestFuncInfo!value, A, methods) == 1); + static assert(findCovariantFunction!(UnittestFuncInfo!opEquals, A, methods) == -1); + static assert(findCovariantFunction!(UnittestFuncInfo!nomatch, A, methods) == -1); + + // considering opDispatch + class B + { + void opDispatch(string name, A...)(A) {} + } + alias methodsB = GetOverloadedMethods!B; + static assert(findCovariantFunction!(UnittestFuncInfo!draw, B, methodsB) == ptrdiff_t.max); + static assert(findCovariantFunction!(UnittestFuncInfo!value, B, methodsB) == ptrdiff_t.max); + static assert(findCovariantFunction!(UnittestFuncInfo!opEquals, B, methodsB) == ptrdiff_t.max); + static assert(findCovariantFunction!(UnittestFuncInfo!nomatch, B, methodsB) == ptrdiff_t.max); +} + +package template DerivedFunctionType(T...) +{ + static if (!T.length) + { + alias DerivedFunctionType = void; + } + else static if (T.length == 1) + { + static if (is(T[0] == function)) + { + alias DerivedFunctionType = T[0]; + } + else + { + alias DerivedFunctionType = void; + } + } + else static if (is(T[0] P0 == function) && is(T[1] P1 == function)) + { + alias FA = FunctionAttribute; + + alias F0 = T[0], R0 = ReturnType!F0, PSTC0 = ParameterStorageClassTuple!F0; + alias F1 = T[1], R1 = ReturnType!F1, PSTC1 = ParameterStorageClassTuple!F1; + enum FA0 = functionAttributes!F0; + enum FA1 = functionAttributes!F1; + + template CheckParams(size_t i = 0) + { + static if (i >= P0.length) + enum CheckParams = true; + else + { + enum CheckParams = (is(P0[i] == P1[i]) && PSTC0[i] == PSTC1[i]) && + CheckParams!(i + 1); + } + } + static if (R0.sizeof == R1.sizeof && !is(CommonType!(R0, R1) == void) && + P0.length == P1.length && CheckParams!() && TypeMod!F0 == TypeMod!F1 && + variadicFunctionStyle!F0 == variadicFunctionStyle!F1 && + functionLinkage!F0 == functionLinkage!F1 && + ((FA0 ^ FA1) & (FA.ref_ | FA.property)) == 0) + { + alias R = Select!(is(R0 : R1), R0, R1); + alias FX = FunctionTypeOf!(R function(P0)); + // @system is default + alias FY = SetFunctionAttributes!(FX, functionLinkage!F0, (FA0 | FA1) & ~FA.system); + alias DerivedFunctionType = DerivedFunctionType!(FY, T[2 .. $]); + } + else + alias DerivedFunctionType = void; + } + else + alias DerivedFunctionType = void; +} +@safe unittest +{ + // attribute covariance + alias int F1(); + static assert(is(DerivedFunctionType!(F1, F1) == F1)); + alias int F2() pure nothrow; + static assert(is(DerivedFunctionType!(F1, F2) == F2)); + alias int F3() @safe; + alias int F23() @safe pure nothrow; + static assert(is(DerivedFunctionType!(F2, F3) == F23)); + + // return type covariance + alias long F4(); + static assert(is(DerivedFunctionType!(F1, F4) == void)); + class C {} + class D : C {} + alias C F5(); + alias D F6(); + static assert(is(DerivedFunctionType!(F5, F6) == F6)); + alias typeof(null) F7(); + alias int[] F8(); + alias int* F9(); + static assert(is(DerivedFunctionType!(F5, F7) == F7)); + static assert(is(DerivedFunctionType!(F7, F8) == void)); + static assert(is(DerivedFunctionType!(F7, F9) == F7)); + + // variadic type equality + alias int F10(int); + alias int F11(int...); + alias int F12(int, ...); + static assert(is(DerivedFunctionType!(F10, F11) == void)); + static assert(is(DerivedFunctionType!(F10, F12) == void)); + static assert(is(DerivedFunctionType!(F11, F12) == void)); + + // linkage equality + alias extern(C) int F13(int); + alias extern(D) int F14(int); + alias extern(Windows) int F15(int); + static assert(is(DerivedFunctionType!(F13, F14) == void)); + static assert(is(DerivedFunctionType!(F13, F15) == void)); + static assert(is(DerivedFunctionType!(F14, F15) == void)); + + // ref & @property equality + alias int F16(int); + alias ref int F17(int); + alias @property int F18(int); + static assert(is(DerivedFunctionType!(F16, F17) == void)); + static assert(is(DerivedFunctionType!(F16, F18) == void)); + static assert(is(DerivedFunctionType!(F17, F18) == void)); +} + +package template staticIota(int beg, int end) +{ + static if (beg + 1 >= end) + { + static if (beg >= end) + { + alias staticIota = AliasSeq!(); + } + else + { + alias staticIota = AliasSeq!(+beg); + } + } + else + { + enum mid = beg + (end - beg) / 2; + alias staticIota = AliasSeq!(staticIota!(beg, mid), staticIota!(mid, end)); + } +} + +package template mixinAll(mixins...) +{ + static if (mixins.length == 1) + { + static if (is(typeof(mixins[0]) == string)) + { + mixin(mixins[0]); + } + else + { + alias it = mixins[0]; + mixin it; + } + } + else static if (mixins.length >= 2) + { + mixin mixinAll!(mixins[ 0 .. $/2]); + mixin mixinAll!(mixins[$/2 .. $ ]); + } +} + +package template Bind(alias Template, args1...) +{ + alias Bind(args2...) = Template!(args1, args2); +} + + +/** +Options regarding auto-initialization of a $(D RefCounted) object (see +the definition of $(D RefCounted) below). + */ +enum RefCountedAutoInitialize +{ + /// Do not auto-initialize the object + no, + /// Auto-initialize the object + yes, +} + +/** +Defines a reference-counted object containing a $(D T) value as +payload. + +An instance of $(D RefCounted) 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 $(D T) payload. $(D RefCounted) uses $(D malloc) to allocate +the store. As instances of $(D RefCounted) are copied or go out of +scope, they will automatically increment or decrement the reference +count. When the reference count goes down to zero, $(D RefCounted) +will call $(D destroy) against the payload and call $(D free) to +deallocate the store. If the $(D T) payload contains any references +to GC-allocated memory, then `RefCounted` will add it to the GC memory +that is scanned for pointers, and remove it from GC scanning before +$(D free) is called on the store. + +One important consequence of $(D destroy) is that it will call the +destructor of the $(D T) payload. GC-managed references are not +guaranteed to be valid during a destructor call, but other members of +$(D T), such as file handles or pointers to $(D malloc) memory, will +still be valid during the destructor call. This allows the $(D T) to +deallocate or clean up any non-GC resources immediately after the +reference count has reached zero. + +$(D RefCounted) is unsafe and should be used with care. No references +to the payload should be escaped outside the $(D RefCounted) object. + +The $(D autoInit) option makes the object ensure the store is +automatically initialized. Leaving $(D autoInit == +RefCountedAutoInitialize.yes) (the default option) is convenient but +has the cost of a test whenever the payload is accessed. If $(D +autoInit == RefCountedAutoInitialize.no), user code must call either +$(D refCountedStore.isInitialized) or $(D refCountedStore.ensureInitialized) +before attempting to access the payload. Not doing so results in null +pointer dereference. + */ +struct RefCounted(T, RefCountedAutoInitialize autoInit = + RefCountedAutoInitialize.yes) +if (!is(T == class) && !(is(T == interface))) +{ + extern(C) private pure nothrow @nogc static // TODO remove pure when https://issues.dlang.org/show_bug.cgi?id=15862 has been fixed + { + pragma(mangle, "free") void pureFree( void *ptr ); + 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 ); + } + + /// $(D RefCounted) storage implementation. + struct RefCountedStore + { + import core.memory : pureMalloc; + private struct Impl + { + T _payload; + size_t _count; + } + + private Impl* _store; + + private void initialize(A...)(auto ref A args) + { + import core.exception : onOutOfMemoryError; + import std.conv : emplace; + + _store = cast(Impl*) pureMalloc(Impl.sizeof); + if (_store is null) + onOutOfMemoryError(); + static if (hasIndirections!T) + pureGcAddRange(&_store._payload, T.sizeof); + emplace(&_store._payload, args); + _store._count = 1; + } + + private void move(ref T source) + { + import core.exception : onOutOfMemoryError; + import core.stdc.string : memcpy, memset; + + _store = cast(Impl*) pureMalloc(Impl.sizeof); + if (_store is null) + onOutOfMemoryError(); + static if (hasIndirections!T) + pureGcAddRange(&_store._payload, T.sizeof); + + // Can't use std.algorithm.move(source, _store._payload) + // here because it requires the target to be initialized. + // Might be worth to add this as `moveEmplace` + + // Can avoid destructing result. + static if (hasElaborateAssign!T || !isAssignable!T) + memcpy(&_store._payload, &source, T.sizeof); + else + _store._payload = source; + + // If the source defines a destructor or a postblit hook, we must obliterate the + // object in order to avoid double freeing and undue aliasing + static if (hasElaborateDestructor!T || hasElaborateCopyConstructor!T) + { + // If T is nested struct, keep original context pointer + static if (__traits(isNested, T)) + enum sz = T.sizeof - (void*).sizeof; + else + enum sz = T.sizeof; + + auto init = typeid(T).initializer(); + if (init.ptr is null) // null ptr means initialize to 0s + memset(&source, 0, sz); + else + memcpy(&source, init.ptr, sz); + } + + _store._count = 1; + } + + /** + Returns $(D true) if and only if the underlying store has been + allocated and initialized. + */ + @property nothrow @safe pure @nogc + bool isInitialized() const + { + return _store !is null; + } + + /** + Returns underlying reference count if it is allocated and initialized + (a positive integer), and $(D 0) otherwise. + */ + @property nothrow @safe pure @nogc + size_t refCount() const + { + return isInitialized ? _store._count : 0; + } + + /** + Makes sure the payload was properly initialized. Such a + call is typically inserted before using the payload. + */ + void ensureInitialized() + { + if (!isInitialized) initialize(); + } + + } + RefCountedStore _refCounted; + + /// Returns storage implementation struct. + @property nothrow @safe + ref inout(RefCountedStore) refCountedStore() inout + { + return _refCounted; + } + +/** +Constructor that initializes the payload. + +Postcondition: $(D refCountedStore.isInitialized) + */ + this(A...)(auto ref A args) if (A.length > 0) + { + _refCounted.initialize(args); + } + + /// Ditto + this(T val) + { + _refCounted.move(val); + } + +/** +Constructor that tracks the reference count appropriately. If $(D +!refCountedStore.isInitialized), does nothing. + */ + this(this) @safe pure nothrow @nogc + { + if (!_refCounted.isInitialized) return; + ++_refCounted._store._count; + } + +/** +Destructor that tracks the reference count appropriately. If $(D +!refCountedStore.isInitialized), does nothing. When the reference count goes +down to zero, calls $(D destroy) agaist the payload and calls $(D free) +to deallocate the corresponding resource. + */ + ~this() + { + if (!_refCounted.isInitialized) return; + assert(_refCounted._store._count > 0); + if (--_refCounted._store._count) + return; + // Done, deallocate + .destroy(_refCounted._store._payload); + static if (hasIndirections!T) + { + pureGcRemoveRange(&_refCounted._store._payload); + } + + pureFree(_refCounted._store); + _refCounted._store = null; + } + +/** +Assignment operators + */ + void opAssign(typeof(this) rhs) + { + import std.algorithm.mutation : swap; + + swap(_refCounted._store, rhs._refCounted._store); + } + +/// Ditto + 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); + } + + //version to have a single properly ddoc'ed function (w/ correct sig) + version (StdDdoc) + { + /** + Returns a reference to the payload. If (autoInit == + 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 $(D RefCounted) + object as a $(D 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 + $(D refCountedPayload) will also be qualified as safe and nothrow + (but will still assert if not initialized). + */ + @property + ref T refCountedPayload() return; + + /// ditto + @property nothrow @safe pure @nogc + ref inout(T) refCountedPayload() inout return; + } + else + { + 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; + } + } + +/** +Returns a reference to the payload. If (autoInit == +RefCountedAutoInitialize.yes), calls $(D +refCountedStore.ensureInitialized). Otherwise, just issues $(D +assert(refCountedStore.isInitialized)). + */ + alias refCountedPayload this; +} + +/// +pure @system nothrow @nogc unittest +{ + // A pair of an `int` and a `size_t` - the latter being the + // reference count - will be dynamically allocated + auto rc1 = RefCounted!int(5); + assert(rc1 == 5); + // No more allocation, add just one extra reference count + auto rc2 = rc1; + // Reference semantics + rc2 = 42; + assert(rc1 == 42); + // the pair will be freed when rc1 and rc2 go out of scope +} + +pure @system 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); + + // RefCounted as a member + struct A + { + RefCounted!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, "BUG 4356 still unfixed"); +} + +pure @system nothrow @nogc unittest +{ + import std.algorithm.mutation : swap; + + RefCounted!int p1, p2; + swap(p1, p2); +} + +// 6606 +@safe pure nothrow @nogc unittest +{ + union U { + size_t i; + void* p; + } + + struct S { + U u; + } + + alias SRC = RefCounted!S; +} + +// 6436 +@system pure unittest +{ + struct S { this(ref int val) { assert(val == 3); ++val; } } + + int val = 3; + auto s = RefCounted!S(val); + assert(val == 4); +} + +// gc_addRange coverage +@system pure unittest +{ + struct S { int* p; } + + auto s = RefCounted!S(null); +} + +@system pure nothrow @nogc unittest +{ + RefCounted!int a; + a = 5; //This should not assert + assert(a == 5); + + RefCounted!int b; + b = a; //This should not assert either + assert(b == 5); + + RefCounted!(int*) c; +} + +/** + * Initializes a `RefCounted` with `val`. The template parameter + * `T` of `RefCounted` 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`. + * + * Params: + * val = The value to be reference counted + * Returns: + * An initialized $(D RefCounted) containing $(D val). + * See_Also: + * $(HTTP en.cppreference.com/w/cpp/memory/shared_ptr/make_shared, C++'s make_shared) + */ +RefCounted!(T, RefCountedAutoInitialize.no) refCounted(T)(T val) +{ + typeof(return) res; + res._refCounted.move(val); + return res; +} + +/// +@system unittest +{ + static struct File + { + string name; + @disable this(this); // not copyable + ~this() { name = null; } + } + + auto file = File("name"); + assert(file.name == "name"); + // file cannot be copied and has unique ownership + static assert(!__traits(compiles, {auto file2 = file;})); + + // make the file refcounted to share ownership + import std.algorithm.mutation : move; + auto rcFile = refCounted(move(file)); + assert(rcFile.name == "name"); + assert(file.name == null); + auto rcFile2 = rcFile; + assert(rcFile.refCountedStore.refCount == 2); + // file gets properly closed when last reference is dropped +} + +/** + Creates a proxy for the value `a` that will forward all operations + while disabling implicit conversions. The aliased item `a` must be + an $(B lvalue). This is useful for creating a new type from the + "base" type (though this is $(B not) a subtype-supertype + relationship; the new type is not related to the old type in any way, + by design). + + The new type supports all operations that the underlying type does, + including all operators such as `+`, `--`, `<`, `[]`, etc. + + Params: + a = The value to act as a proxy for all operations. It must + be an lvalue. + */ +mixin template Proxy(alias a) +{ + private alias ValueType = typeof({ return a; }()); + + /* Determine if 'T.a' can referenced via a const(T). + * Use T* as the parameter because 'scope' inference needs a fully + * analyzed T, which doesn't work when accessibleFrom() is used in a + * 'static if' in the definition of Proxy or T. + */ + private enum bool accessibleFrom(T) = + is(typeof((T* self){ cast(void) mixin("(*self)."~__traits(identifier, a)); })); + + static if (is(typeof(this) == class)) + { + override bool opEquals(Object o) + { + if (auto b = cast(typeof(this))o) + { + return a == mixin("b."~__traits(identifier, a)); + } + return false; + } + + bool opEquals(T)(T b) + if (is(ValueType : T) || is(typeof(a.opEquals(b))) || is(typeof(b.opEquals(a)))) + { + static if (is(typeof(a.opEquals(b)))) + return a.opEquals(b); + else static if (is(typeof(b.opEquals(a)))) + return b.opEquals(a); + else + return a == b; + } + + override int opCmp(Object o) + { + if (auto b = cast(typeof(this))o) + { + return a < mixin("b."~__traits(identifier, a)) ? -1 + : a > mixin("b."~__traits(identifier, a)) ? +1 : 0; + } + static if (is(ValueType == class)) + return a.opCmp(o); + else + throw new Exception("Attempt to compare a "~typeid(this).toString~" and a "~typeid(o).toString); + } + + int opCmp(T)(auto ref const T b) + if (is(ValueType : T) || is(typeof(a.opCmp(b))) || is(typeof(b.opCmp(a)))) + { + static if (is(typeof(a.opCmp(b)))) + return a.opCmp(b); + else static if (is(typeof(b.opCmp(a)))) + return -b.opCmp(b); + else + return a < b ? -1 : a > b ? +1 : 0; + } + + static if (accessibleFrom!(const typeof(this))) + { + override hash_t toHash() const nothrow @trusted + { + static if (is(typeof(&a) == ValueType*)) + alias v = a; + else + auto v = a; // if a is (property) function + return typeid(ValueType).getHash(cast(const void*)&v); + } + } + } + else + { + auto ref opEquals(this X, B)(auto ref B b) + { + static if (is(immutable B == immutable typeof(this))) + { + return a == mixin("b."~__traits(identifier, a)); + } + else + return a == b; + } + + auto ref opCmp(this X, B)(auto ref B b) + if (!is(typeof(a.opCmp(b))) || !is(typeof(b.opCmp(a)))) + { + static if (is(typeof(a.opCmp(b)))) + return a.opCmp(b); + else static if (is(typeof(b.opCmp(a)))) + return -b.opCmp(a); + else static if (isFloatingPoint!ValueType || isFloatingPoint!B) + return a < b ? -1 : a > b ? +1 : a == b ? 0 : float.nan; + else + return a < b ? -1 : (a > b); + } + + static if (accessibleFrom!(const typeof(this))) + { + hash_t toHash() const nothrow @trusted + { + static if (is(typeof(&a) == ValueType*)) + alias v = a; + else + auto v = a; // if a is (property) function + return typeid(ValueType).getHash(cast(const void*)&v); + } + } + } + + auto ref opCall(this X, Args...)(auto ref Args args) { return a(args); } + + auto ref opCast(T, this X)() { return cast(T) a; } + + auto ref opIndex(this X, D...)(auto ref D i) { return a[i]; } + auto ref opSlice(this X )() { return a[]; } + auto ref opSlice(this X, B, E)(auto ref B b, auto ref E e) { return a[b .. e]; } + + auto ref opUnary (string op, this X )() { return mixin(op~"a"); } + auto ref opIndexUnary(string op, this X, D...)(auto ref D i) { return mixin(op~"a[i]"); } + auto ref opSliceUnary(string op, this X )() { return mixin(op~"a[]"); } + auto ref opSliceUnary(string op, this X, B, E)(auto ref B b, auto ref E e) { return mixin(op~"a[b .. e]"); } + + auto ref opBinary(string op, this X, B)(auto ref B b) + if (op == "in" && is(typeof(a in b)) || op != "in") + { + return mixin("a "~op~" b"); + } + auto ref opBinaryRight(string op, this X, B)(auto ref B b) { return mixin("b "~op~" a"); } + + static if (!is(typeof(this) == class)) + { + import std.traits; + static if (isAssignable!ValueType) + { + auto ref opAssign(this X)(auto ref typeof(this) v) + { + a = mixin("v."~__traits(identifier, a)); + return this; + } + } + else + { + @disable void opAssign(this X)(auto ref typeof(this) v); + } + } + + auto ref opAssign (this X, V )(auto ref V v) if (!is(V == typeof(this))) { return a = v; } + auto ref opIndexAssign(this X, V, D...)(auto ref V v, auto ref D i) { return a[i] = v; } + auto ref opSliceAssign(this X, V )(auto ref V v) { return a[] = v; } + auto ref opSliceAssign(this X, V, B, E)(auto ref V v, auto ref B b, auto ref E e) { return a[b .. e] = v; } + + auto ref opOpAssign (string op, this X, V )(auto ref V v) + { + return mixin("a " ~op~"= v"); + } + auto ref opIndexOpAssign(string op, this X, V, D...)(auto ref V v, auto ref D i) + { + return mixin("a[i] " ~op~"= v"); + } + auto ref opSliceOpAssign(string op, this X, V )(auto ref V v) + { + return mixin("a[] " ~op~"= v"); + } + auto ref opSliceOpAssign(string op, this X, V, B, E)(auto ref V v, auto ref B b, auto ref E e) + { + return mixin("a[b .. e] "~op~"= v"); + } + + template opDispatch(string name) + { + static if (is(typeof(__traits(getMember, a, name)) == function)) + { + // non template function + auto ref opDispatch(this X, Args...)(auto ref Args args) { return mixin("a."~name~"(args)"); } + } + else static if (is(typeof({ enum x = mixin("a."~name); }))) + { + // built-in type field, manifest constant, and static non-mutable field + enum opDispatch = mixin("a."~name); + } + else static if (is(typeof(mixin("a."~name))) || __traits(getOverloads, a, name).length != 0) + { + // field or property function + @property auto ref opDispatch(this X)() { return mixin("a."~name); } + @property auto ref opDispatch(this X, V)(auto ref V v) { return mixin("a."~name~" = v"); } + } + else + { + // member template + template opDispatch(T...) + { + enum targs = T.length ? "!T" : ""; + auto ref opDispatch(this X, Args...)(auto ref Args args){ return mixin("a."~name~targs~"(args)"); } + } + } + } + + import std.traits : isArray; + + static if (isArray!ValueType) + { + auto opDollar() const { return a.length; } + } + else static if (is(typeof(a.opDollar!0))) + { + auto ref opDollar(size_t pos)() { return a.opDollar!pos(); } + } + else static if (is(typeof(a.opDollar) == function)) + { + auto ref opDollar() { return a.opDollar(); } + } + else static if (is(typeof(a.opDollar))) + { + alias opDollar = a.opDollar; + } +} + +/// +@safe unittest +{ + struct MyInt + { + private int value; + mixin Proxy!value; + + this(int n){ value = n; } + } + + MyInt n = 10; + + // Enable operations that original type has. + ++n; + assert(n == 11); + assert(n * 2 == 22); + + void func(int n) { } + + // Disable implicit conversions to original type. + //int x = n; + //func(n); +} + +///The proxied value must be an $(B lvalue). +@safe unittest +{ + struct NewIntType + { + //Won't work; the literal '1' + //is an rvalue, not an lvalue + //mixin Proxy!1; + + //Okay, n is an lvalue + int n; + mixin Proxy!n; + + this(int n) { this.n = n; } + } + + NewIntType nit = 0; + nit++; + assert(nit == 1); + + + struct NewObjectType + { + Object obj; + //Ok, obj is an lvalue + mixin Proxy!obj; + + this (Object o) { obj = o; } + } + + NewObjectType not = new Object(); + assert(__traits(compiles, not.toHash())); +} + +/** + There is one exception to the fact that the new type is not related to the + old type. $(DDSUBLINK spec/function,pseudo-member, Pseudo-member) + functions are usable with the new type; they will be forwarded on to the + proxied value. + */ +@safe unittest +{ + import std.math; + + float f = 1.0; + assert(!f.isInfinity); + + struct NewFloat + { + float _; + mixin Proxy!_; + + this(float f) { _ = f; } + } + + NewFloat nf = 1.0f; + assert(!nf.isInfinity); +} + +@safe unittest +{ + static struct MyInt + { + private int value; + mixin Proxy!value; + this(int n) inout { value = n; } + + enum str = "str"; + static immutable arr = [1,2,3]; + } + + foreach (T; AliasSeq!(MyInt, const MyInt, immutable MyInt)) + { + T m = 10; + static assert(!__traits(compiles, { int x = m; })); + static assert(!__traits(compiles, { void func(int n){} func(m); })); + assert(m == 10); + assert(m != 20); + assert(m < 20); + assert(+m == 10); + assert(-m == -10); + assert(cast(double) m == 10.0); + assert(m + 10 == 20); + assert(m - 5 == 5); + assert(m * 20 == 200); + assert(m / 2 == 5); + assert(10 + m == 20); + assert(15 - m == 5); + assert(20 * m == 200); + assert(50 / m == 5); + static if (is(T == MyInt)) // mutable + { + assert(++m == 11); + assert(m++ == 11); assert(m == 12); + assert(--m == 11); + assert(m-- == 11); assert(m == 10); + m = m; + m = 20; assert(m == 20); + } + static assert(T.max == int.max); + static assert(T.min == int.min); + static assert(T.init == int.init); + static assert(T.str == "str"); + static assert(T.arr == [1,2,3]); + } +} +@system unittest +{ + static struct MyArray + { + private int[] value; + mixin Proxy!value; + this(int[] arr) { value = arr; } + this(immutable int[] arr) immutable { value = arr; } + } + + foreach (T; AliasSeq!(MyArray, const MyArray, immutable MyArray)) + { + static if (is(T == immutable) && !is(typeof({ T a = [1,2,3,4]; }))) + T a = [1,2,3,4].idup; // workaround until qualified ctor is properly supported + else + T a = [1,2,3,4]; + assert(a == [1,2,3,4]); + assert(a != [5,6,7,8]); + assert(+a[0] == 1); + version (LittleEndian) + assert(cast(ulong[]) a == [0x0000_0002_0000_0001, 0x0000_0004_0000_0003]); + else + assert(cast(ulong[]) a == [0x0000_0001_0000_0002, 0x0000_0003_0000_0004]); + assert(a ~ [10,11] == [1,2,3,4,10,11]); + assert(a[0] == 1); + assert(a[] == [1,2,3,4]); + assert(a[2 .. 4] == [3,4]); + static if (is(T == MyArray)) // mutable + { + a = a; + a = [5,6,7,8]; assert(a == [5,6,7,8]); + a[0] = 0; assert(a == [0,6,7,8]); + a[] = 1; assert(a == [1,1,1,1]); + a[0 .. 3] = 2; assert(a == [2,2,2,1]); + a[0] += 2; assert(a == [4,2,2,1]); + a[] *= 2; assert(a == [8,4,4,2]); + a[0 .. 2] /= 2; assert(a == [4,2,4,2]); + } + } +} +@system unittest +{ + class Foo + { + int field; + + @property int val1() const { return field; } + @property void val1(int n) { field = n; } + + @property ref int val2() { return field; } + + int func(int x, int y) const { return x; } + void func1(ref int a) { a = 9; } + + T ifti1(T)(T t) { return t; } + void ifti2(Args...)(Args args) { } + void ifti3(T, Args...)(Args args) { } + + T opCast(T)(){ return T.init; } + + T tempfunc(T)() { return T.init; } + } + class Hoge + { + Foo foo; + mixin Proxy!foo; + this(Foo f) { foo = f; } + } + + auto h = new Hoge(new Foo()); + int n; + + static assert(!__traits(compiles, { Foo f = h; })); + + // field + h.field = 1; // lhs of assign + n = h.field; // rhs of assign + assert(h.field == 1); // lhs of BinExp + assert(1 == h.field); // rhs of BinExp + assert(n == 1); + + // getter/setter property function + h.val1 = 4; + n = h.val1; + assert(h.val1 == 4); + assert(4 == h.val1); + assert(n == 4); + + // ref getter property function + h.val2 = 8; + n = h.val2; + assert(h.val2 == 8); + assert(8 == h.val2); + assert(n == 8); + + // member function + assert(h.func(2,4) == 2); + h.func1(n); + assert(n == 9); + + // IFTI + assert(h.ifti1(4) == 4); + h.ifti2(4); + h.ifti3!int(4, 3); + + // bug5896 test + assert(h.opCast!int() == 0); + assert(cast(int) h == 0); + const ih = new const Hoge(new Foo()); + static assert(!__traits(compiles, ih.opCast!int())); + static assert(!__traits(compiles, cast(int) ih)); + + // template member function + assert(h.tempfunc!int() == 0); +} + +@system unittest // about Proxy inside a class +{ + class MyClass + { + int payload; + mixin Proxy!payload; + this(int i){ payload = i; } + string opCall(string msg){ return msg; } + int pow(int i){ return payload ^^ i; } + } + + class MyClass2 + { + MyClass payload; + mixin Proxy!payload; + this(int i){ payload = new MyClass(i); } + } + + class MyClass3 + { + int payload; + mixin Proxy!payload; + this(int i){ payload = i; } + } + + // opEquals + Object a = new MyClass(5); + Object b = new MyClass(5); + Object c = new MyClass2(5); + Object d = new MyClass3(5); + assert(a == b); + assert((cast(MyClass) a) == 5); + assert(5 == (cast(MyClass) b)); + assert(5 == cast(MyClass2) c); + assert(a != d); + + assert(c != a); + // oops! above line is unexpected, isn't it? + // the reason is below. + // MyClass2.opEquals knows MyClass but, + // MyClass.opEquals doesn't know MyClass2. + // so, c.opEquals(a) is true, but a.opEquals(c) is false. + // furthermore, opEquals(T) couldn't be invoked. + assert((cast(MyClass2) c) != (cast(MyClass) a)); + + // opCmp + Object e = new MyClass2(7); + assert(a < cast(MyClass2) e); // OK. and + assert(e > a); // OK, but... + // assert(a < e); // RUNTIME ERROR! + // assert((cast(MyClass) a) < e); // RUNTIME ERROR! + assert(3 < cast(MyClass) a); + assert((cast(MyClass2) e) < 11); + + // opCall + assert((cast(MyClass2) e)("hello") == "hello"); + + // opCast + assert((cast(MyClass)(cast(MyClass2) c)) == a); + assert((cast(int)(cast(MyClass2) c)) == 5); + + // opIndex + class MyClass4 + { + string payload; + mixin Proxy!payload; + this(string s){ payload = s; } + } + class MyClass5 + { + MyClass4 payload; + mixin Proxy!payload; + this(string s){ payload = new MyClass4(s); } + } + auto f = new MyClass4("hello"); + assert(f[1] == 'e'); + auto g = new MyClass5("hello"); + assert(f[1] == 'e'); + + // opSlice + assert(f[2 .. 4] == "ll"); + + // opUnary + assert(-(cast(MyClass2) c) == -5); + + // opBinary + assert((cast(MyClass) a) + (cast(MyClass2) c) == 10); + assert(5 + cast(MyClass) a == 10); + + // opAssign + (cast(MyClass2) c) = 11; + assert((cast(MyClass2) c) == 11); + (cast(MyClass2) c) = new MyClass(13); + assert((cast(MyClass2) c) == 13); + + // opOpAssign + assert((cast(MyClass2) c) += 4); + assert((cast(MyClass2) c) == 17); + + // opDispatch + assert((cast(MyClass2) c).pow(2) == 289); + + // opDollar + assert(f[2..$-1] == "ll"); + + // toHash + int[Object] hash; + hash[a] = 19; + hash[c] = 21; + assert(hash[b] == 19); + assert(hash[c] == 21); +} + +@safe unittest +{ + struct MyInt + { + int payload; + + mixin Proxy!payload; + } + + MyInt v; + v = v; + + struct Foo + { + @disable void opAssign(typeof(this)); + } + struct MyFoo + { + Foo payload; + + mixin Proxy!payload; + } + MyFoo f; + static assert(!__traits(compiles, f = f)); + + struct MyFoo2 + { + Foo payload; + + mixin Proxy!payload; + + // override default Proxy behavior + void opAssign(typeof(this) rhs){} + } + MyFoo2 f2; + f2 = f2; +} +@safe unittest +{ + // bug8613 + static struct Name + { + mixin Proxy!val; + private string val; + this(string s) { val = s; } + } + + bool[Name] names; + names[Name("a")] = true; + bool* b = Name("a") in names; +} + +@system unittest +{ + // bug14213, using function for the payload + static struct S + { + int foo() { return 12; } + mixin Proxy!foo; + } + static class C + { + int foo() { return 12; } + mixin Proxy!foo; + } + S s; + assert(s + 1 == 13); + C c = new C(); + assert(s * 2 == 24); +} + +// Check all floating point comparisons for both Proxy and Typedef, +// also against int and a Typedef!int, to be as regression-proof +// as possible. bug 15561 +@safe unittest +{ + static struct MyFloatImpl + { + float value; + mixin Proxy!value; + } + static void allFail(T0, T1)(T0 a, T1 b) + { + assert(!(a == b)); + assert(!(a<b)); + assert(!(a <= b)); + assert(!(a>b)); + assert(!(a >= b)); + } + foreach (T1; AliasSeq!(MyFloatImpl, Typedef!float, Typedef!double, + float, real, Typedef!int, int)) + { + foreach (T2; AliasSeq!(MyFloatImpl, Typedef!float)) + { + T1 a; + T2 b; + + static if (isFloatingPoint!T1 || isFloatingPoint!(TypedefType!T1)) + allFail(a, b); + a = 3; + allFail(a, b); + + b = 4; + assert(a != b); + assert(a<b); + assert(a <= b); + assert(!(a>b)); + assert(!(a >= b)); + + a = 4; + assert(a == b); + assert(!(a<b)); + assert(a <= b); + assert(!(a>b)); + assert(a >= b); + } + } +} + +/** +$(B Typedef) allows the creation of a unique type which is +based on an existing type. Unlike the $(D alias) feature, +$(B Typedef) ensures the two types are not considered as equals. + +Example: +---- +alias MyInt = Typedef!int; +static void takeInt(int) { } +static void takeMyInt(MyInt) { } + +int i; +takeInt(i); // ok +takeMyInt(i); // fails + +MyInt myInt; +takeInt(myInt); // fails +takeMyInt(myInt); // ok +---- + +Params: + +init = Optional initial value for the new type. For example: + +---- +alias MyInt = Typedef!(int, 10); +MyInt myInt; +assert(myInt == 10); // default-initialized to 10 +---- + +cookie = Optional, used to create multiple unique types which are +based on the same origin type $(D T). For example: + +---- +alias TypeInt1 = Typedef!int; +alias TypeInt2 = Typedef!int; + +// The two Typedefs are the same type. +static assert(is(TypeInt1 == TypeInt2)); + +alias MoneyEuros = Typedef!(float, float.init, "euros"); +alias MoneyDollars = Typedef!(float, float.init, "dollars"); + +// The two Typedefs are _not_ the same type. +static assert(!is(MoneyEuros == MoneyDollars)); +---- + +Note: If a library routine cannot handle the Typedef type, +you can use the $(D TypedefType) template to extract the +type which the Typedef wraps. + */ +struct Typedef(T, T init = T.init, string cookie=null) +{ + private T Typedef_payload = init; + + this(T init) + { + Typedef_payload = init; + } + + this(Typedef tdef) + { + this(tdef.Typedef_payload); + } + + // We need to add special overload for cast(Typedef!X) exp, + // thus we can't simply inherit Proxy!Typedef_payload + T2 opCast(T2 : Typedef!(T, Unused), this X, T, Unused...)() + { + return T2(cast(T) Typedef_payload); + } + + auto ref opCast(T2, this X)() + { + return cast(T2) Typedef_payload; + } + + mixin Proxy!Typedef_payload; + + pure nothrow @nogc @safe @property + { + alias TD = typeof(this); + static if (isIntegral!T) + { + static TD min() {return TD(T.min);} + static TD max() {return TD(T.max);} + } + else static if (isFloatingPoint!T) + { + static TD infinity() {return TD(T.infinity);} + static TD nan() {return TD(T.nan);} + static TD dig() {return TD(T.dig);} + static TD epsilon() {return TD(T.epsilon);} + static TD mant_dig() {return TD(T.mant_dig);} + static TD max_10_exp() {return TD(T.max_10_exp);} + static TD max_exp() {return TD(T.max_exp);} + static TD min_10_exp() {return TD(T.min_10_exp);} + static TD min_exp() {return TD(T.min_exp);} + static TD max() {return TD(T.max);} + static TD min_normal() {return TD(T.min_normal);} + TD re() {return TD(Typedef_payload.re);} + TD im() {return TD(Typedef_payload.im);} + } + } +} + +/** +Get the underlying type which a $(D Typedef) wraps. +If $(D T) is not a $(D Typedef) it will alias itself to $(D T). +*/ +template TypedefType(T) +{ + static if (is(T : Typedef!Arg, Arg)) + alias TypedefType = Arg; + else + alias TypedefType = T; +} + +/// +@safe unittest +{ + import std.typecons : Typedef, TypedefType; + import std.conv : to; + + alias MyInt = Typedef!int; + static assert(is(TypedefType!MyInt == int)); + + /// Instantiating with a non-Typedef will return that type + static assert(is(TypedefType!int == int)); + + string num = "5"; + + // extract the needed type + MyInt myInt = MyInt( num.to!(TypedefType!MyInt) ); + assert(myInt == 5); + + // cast to the underlying type to get the value that's being wrapped + int x = cast(TypedefType!MyInt) myInt; + + alias MyIntInit = Typedef!(int, 42); + static assert(is(TypedefType!MyIntInit == int)); + static assert(MyIntInit() == 42); +} + +@safe unittest +{ + Typedef!int x = 10; + static assert(!__traits(compiles, { int y = x; })); + static assert(!__traits(compiles, { long z = x; })); + + Typedef!int y = 10; + assert(x == y); + + static assert(Typedef!int.init == int.init); + + Typedef!(float, 1.0) z; // specifies the init + assert(z == 1.0); + + static assert(typeof(z).init == 1.0); + + alias Dollar = Typedef!(int, 0, "dollar"); + alias Yen = Typedef!(int, 0, "yen"); + static assert(!is(Dollar == Yen)); + + Typedef!(int[3]) sa; + static assert(sa.length == 3); + static assert(typeof(sa).length == 3); + + Typedef!(int[3]) dollar1; + assert(dollar1[0..$] is dollar1[0 .. 3]); + + Typedef!(int[]) dollar2; + dollar2.length = 3; + assert(dollar2[0..$] is dollar2[0 .. 3]); + + static struct Dollar1 + { + static struct DollarToken {} + enum opDollar = DollarToken.init; + auto opSlice(size_t, DollarToken) { return 1; } + auto opSlice(size_t, size_t) { return 2; } + } + + Typedef!Dollar1 drange1; + assert(drange1[0..$] == 1); + assert(drange1[0 .. 1] == 2); + + static struct Dollar2 + { + size_t opDollar(size_t pos)() { return pos == 0 ? 1 : 100; } + size_t opIndex(size_t i, size_t j) { return i + j; } + } + + Typedef!Dollar2 drange2; + assert(drange2[$, $] == 101); + + static struct Dollar3 + { + size_t opDollar() { return 123; } + size_t opIndex(size_t i) { return i; } + } + + Typedef!Dollar3 drange3; + assert(drange3[$] == 123); +} + +@safe @nogc pure nothrow unittest // Bugzilla 11703 +{ + alias I = Typedef!int; + static assert(is(typeof(I.min) == I)); + static assert(is(typeof(I.max) == I)); + + alias F = Typedef!double; + static assert(is(typeof(F.infinity) == F)); + static assert(is(typeof(F.epsilon) == F)); + + F f; + assert(!is(typeof(F.re).stringof == double)); + assert(!is(typeof(F.im).stringof == double)); +} + +@safe unittest +{ + // bug8655 + import std.typecons; + import std.bitmanip; + static import core.stdc.config; + + alias c_ulong = Typedef!(core.stdc.config.c_ulong); + + static struct Foo + { + mixin(bitfields!( + c_ulong, "NameOffset", 31, + c_ulong, "NameIsString", 1 + )); + } +} + +@safe unittest // Issue 12596 +{ + import std.typecons; + alias TD = Typedef!int; + TD x = TD(1); + TD y = TD(x); + assert(x == y); +} + +@safe unittest // about toHash +{ + import std.typecons; + { + alias TD = Typedef!int; + int[TD] td; + td[TD(1)] = 1; + assert(td[TD(1)] == 1); + } + + { + alias TD = Typedef!(int[]); + int[TD] td; + td[TD([1,2,3,4])] = 2; + assert(td[TD([1,2,3,4])] == 2); + } + + { + alias TD = Typedef!(int[][]); + int[TD] td; + td[TD([[1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,0,1]])] = 3; + assert(td[TD([[1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,0,1]])] == 3); + } + + { + struct MyStruct{ int x; } + alias TD = Typedef!MyStruct; + int[TD] td; + td[TD(MyStruct(10))] = 4; + assert(TD(MyStruct(20)) !in td); + assert(td[TD(MyStruct(10))] == 4); + } + + { + static struct MyStruct2 + { + int x; + size_t toHash() const nothrow @safe { return x; } + bool opEquals(ref const MyStruct2 r) const { return r.x == x; } + } + + alias TD = Typedef!MyStruct2; + int[TD] td; + td[TD(MyStruct2(50))] = 5; + assert(td[TD(MyStruct2(50))] == 5); + } + + { + class MyClass{} + alias TD = Typedef!MyClass; + int[TD] td; + auto c = new MyClass; + td[TD(c)] = 6; + assert(TD(new MyClass) !in td); + assert(td[TD(c)] == 6); + } +} + +@system unittest +{ + alias String = Typedef!(char[]); + alias CString = Typedef!(const(char)[]); + CString cs = "fubar"; + String s = cast(String) cs; + assert(cs == s); + char[] s2 = cast(char[]) cs; + const(char)[] cs2 = cast(const(char)[])s; + assert(s2 == cs2); +} + +/** +Allocates a $(D class) object right inside the current scope, +therefore avoiding the overhead of $(D new). This facility is unsafe; +it is the responsibility of the user to not escape a reference to the +object outside the scope. + +The class destructor will be called when the result of `scoped()` is +itself destroyed. + +Scoped class instances can be embedded in a parent `class` or `struct`, +just like a child struct instance. Scoped member variables must have +type `typeof(scoped!Class(args))`, and be initialized with a call to +scoped. See below for an example. + +Note: +It's illegal to move a class instance even if you are sure there +are no pointers to it. As such, it is illegal to move a scoped object. + */ +template scoped(T) + if (is(T == class)) +{ + // _d_newclass now use default GC alignment (looks like (void*).sizeof * 2 for + // small objects). We will just use the maximum of filed alignments. + alias alignment = classInstanceAlignment!T; + alias aligned = _alignUp!alignment; + + static struct Scoped + { + // Addition of `alignment` is required as `Scoped_store` can be misaligned in memory. + private void[aligned(__traits(classInstanceSize, T) + size_t.sizeof) + alignment] Scoped_store = void; + + @property inout(T) Scoped_payload() inout + { + void* alignedStore = cast(void*) aligned(cast(uintptr_t) Scoped_store.ptr); + // As `Scoped` can be unaligned moved in memory class instance should be moved accordingly. + immutable size_t d = alignedStore - Scoped_store.ptr; + size_t* currD = cast(size_t*) &Scoped_store[$ - size_t.sizeof]; + if (d != *currD) + { + import core.stdc.string : memmove; + memmove(alignedStore, Scoped_store.ptr + *currD, __traits(classInstanceSize, T)); + *currD = d; + } + return cast(inout(T)) alignedStore; + } + alias Scoped_payload this; + + @disable this(); + @disable this(this); + + ~this() + { + // `destroy` will also write .init but we have no functions in druntime + // for deterministic finalization and memory releasing for now. + .destroy(Scoped_payload); + } + } + + /** Returns the _scoped object. + Params: args = Arguments to pass to $(D T)'s constructor. + */ + @system auto scoped(Args...)(auto ref Args args) + { + import std.conv : emplace; + + Scoped result = void; + void* alignedStore = cast(void*) aligned(cast(uintptr_t) result.Scoped_store.ptr); + immutable size_t d = alignedStore - result.Scoped_store.ptr; + *cast(size_t*) &result.Scoped_store[$ - size_t.sizeof] = d; + emplace!(Unqual!T)(result.Scoped_store[d .. $ - size_t.sizeof], args); + return result; + } +} + +/// +@system unittest +{ + class A + { + int x; + this() {x = 0;} + this(int i){x = i;} + ~this() {} + } + + // Standard usage, constructing A on the stack + auto a1 = scoped!A(); + a1.x = 42; + + // Result of `scoped` call implicitly converts to a class reference + A aRef = a1; + assert(aRef.x == 42); + + // Scoped destruction + { + auto a2 = scoped!A(1); + assert(a2.x == 1); + aRef = a2; + // a2 is destroyed here, calling A's destructor + } + // aRef is now an invalid reference + + // Here the temporary scoped A is immediately destroyed. + // This means the reference is then invalid. + version (Bug) + { + // Wrong, should use `auto` + A invalid = scoped!A(); + } + + // Restrictions + version (Bug) + { + import std.algorithm.mutation : move; + auto invalid = a1.move; // illegal, scoped objects can't be moved + } + static assert(!is(typeof({ + auto e1 = a1; // illegal, scoped objects can't be copied + assert([a1][0].x == 42); // ditto + }))); + static assert(!is(typeof({ + alias ScopedObject = typeof(a1); + auto e2 = ScopedObject(); // illegal, must be built via scoped!A + auto e3 = ScopedObject(1); // ditto + }))); + + // Use with alias + alias makeScopedA = scoped!A; + auto a3 = makeScopedA(); + auto a4 = makeScopedA(1); + + // Use as member variable + struct B + { + typeof(scoped!A()) a; // note the trailing parentheses + + this(int i) + { + // construct member + a = scoped!A(i); + } + } + + // Stack-allocate + auto b1 = B(5); + aRef = b1.a; + assert(aRef.x == 5); + destroy(b1); // calls A's destructor for b1.a + // aRef is now an invalid reference + + // Heap-allocate + auto b2 = new B(6); + assert(b2.a.x == 6); + destroy(*b2); // calls A's destructor for b2.a +} + +private uintptr_t _alignUp(uintptr_t alignment)(uintptr_t n) + if (alignment > 0 && !((alignment - 1) & alignment)) +{ + enum badEnd = alignment - 1; // 0b11, 0b111, ... + return (n + badEnd) & ~badEnd; +} + +@system unittest // Issue 6580 testcase +{ + enum alignment = (void*).alignof; + + static class C0 { } + static class C1 { byte b; } + static class C2 { byte[2] b; } + static class C3 { byte[3] b; } + static class C7 { byte[7] b; } + static assert(scoped!C0().sizeof % alignment == 0); + static assert(scoped!C1().sizeof % alignment == 0); + static assert(scoped!C2().sizeof % alignment == 0); + static assert(scoped!C3().sizeof % alignment == 0); + static assert(scoped!C7().sizeof % alignment == 0); + + enum longAlignment = long.alignof; + static class C1long + { + long long_; byte byte_ = 4; + this() { } + this(long _long, ref int i) { long_ = _long; ++i; } + } + static class C2long { byte[2] byte_ = [5, 6]; long long_ = 7; } + static assert(scoped!C1long().sizeof % longAlignment == 0); + static assert(scoped!C2long().sizeof % longAlignment == 0); + + void alignmentTest() + { + int var = 5; + auto c1long = scoped!C1long(3, var); + assert(var == 6); + auto c2long = scoped!C2long(); + assert(cast(uint)&c1long.long_ % longAlignment == 0); + assert(cast(uint)&c2long.long_ % longAlignment == 0); + assert(c1long.long_ == 3 && c1long.byte_ == 4); + assert(c2long.byte_ == [5, 6] && c2long.long_ == 7); + } + + alignmentTest(); + + version (DigitalMars) + { + void test(size_t size) + { + import core.stdc.stdlib; + alloca(size); + alignmentTest(); + } + foreach (i; 0 .. 10) + test(i); + } + else + { + void test(size_t size)() + { + byte[size] arr; + alignmentTest(); + } + foreach (i; AliasSeq!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) + test!i(); + } +} + +@system unittest // Original Issue 6580 testcase +{ + class C { int i; byte b; } + + auto sa = [scoped!C(), scoped!C()]; + assert(cast(uint)&sa[0].i % int.alignof == 0); + assert(cast(uint)&sa[1].i % int.alignof == 0); // fails +} + +@system unittest +{ + class A { int x = 1; } + auto a1 = scoped!A(); + assert(a1.x == 1); + auto a2 = scoped!A(); + a1.x = 42; + a2.x = 53; + assert(a1.x == 42); +} + +@system unittest +{ + class A { int x = 1; this() { x = 2; } } + auto a1 = scoped!A(); + assert(a1.x == 2); + auto a2 = scoped!A(); + a1.x = 42; + a2.x = 53; + assert(a1.x == 42); +} + +@system unittest +{ + class A { int x = 1; this(int y) { x = y; } ~this() {} } + auto a1 = scoped!A(5); + assert(a1.x == 5); + auto a2 = scoped!A(42); + a1.x = 42; + a2.x = 53; + assert(a1.x == 42); +} + +@system unittest +{ + class A { static bool dead; ~this() { dead = true; } } + class B : A { static bool dead; ~this() { dead = true; } } + { + auto b = scoped!B(); + } + assert(B.dead, "asdasd"); + assert(A.dead, "asdasd"); +} + +@system unittest // Issue 8039 testcase +{ + static int dels; + static struct S { ~this(){ ++dels; } } + + static class A { S s; } + dels = 0; { scoped!A(); } + assert(dels == 1); + + static class B { S[2] s; } + dels = 0; { scoped!B(); } + assert(dels == 2); + + static struct S2 { S[3] s; } + static class C { S2[2] s; } + dels = 0; { scoped!C(); } + assert(dels == 6); + + static class D: A { S2[2] s; } + dels = 0; { scoped!D(); } + assert(dels == 1+6); +} + +@system unittest +{ + // bug4500 + class A + { + this() { a = this; } + this(int i) { a = this; } + A a; + bool check() { return this is a; } + } + + auto a1 = scoped!A(); + assert(a1.check()); + + auto a2 = scoped!A(1); + assert(a2.check()); + + a1.a = a1; + assert(a1.check()); +} + +@system unittest +{ + static class A + { + static int sdtor; + + this() { ++sdtor; assert(sdtor == 1); } + ~this() { assert(sdtor == 1); --sdtor; } + } + + interface Bob {} + + static class ABob : A, Bob + { + this() { ++sdtor; assert(sdtor == 2); } + ~this() { assert(sdtor == 2); --sdtor; } + } + + A.sdtor = 0; + scope(exit) assert(A.sdtor == 0); + auto abob = scoped!ABob(); +} + +@safe unittest +{ + static class A { this(int) {} } + static assert(!__traits(compiles, scoped!A())); +} + +@system unittest +{ + static class A { @property inout(int) foo() inout { return 1; } } + + auto a1 = scoped!A(); + assert(a1.foo == 1); + static assert(is(typeof(a1.foo) == int)); + + auto a2 = scoped!(const(A))(); + assert(a2.foo == 1); + static assert(is(typeof(a2.foo) == const(int))); + + auto a3 = scoped!(immutable(A))(); + assert(a3.foo == 1); + static assert(is(typeof(a3.foo) == immutable(int))); + + const c1 = scoped!A(); + assert(c1.foo == 1); + static assert(is(typeof(c1.foo) == const(int))); + + const c2 = scoped!(const(A))(); + assert(c2.foo == 1); + static assert(is(typeof(c2.foo) == const(int))); + + const c3 = scoped!(immutable(A))(); + assert(c3.foo == 1); + static assert(is(typeof(c3.foo) == immutable(int))); +} + +@system unittest +{ + class C { this(ref int val) { assert(val == 3); ++val; } } + + int val = 3; + auto s = scoped!C(val); + assert(val == 4); +} + +@system unittest +{ + class C + { + this(){} + this(int){} + this(int, int){} + } + alias makeScopedC = scoped!C; + + auto a = makeScopedC(); + auto b = makeScopedC(1); + auto c = makeScopedC(1, 1); + + static assert(is(typeof(a) == typeof(b))); + static assert(is(typeof(b) == typeof(c))); +} + +/** +Defines a simple, self-documenting yes/no flag. This makes it easy for +APIs to define functions accepting flags without resorting to $(D +bool), which is opaque in calls, and without needing to define an +enumerated type separately. Using $(D Flag!"Name") instead of $(D +bool) makes the flag's meaning visible in calls. Each yes/no flag has +its own type, which makes confusions and mix-ups impossible. + +Example: + +Code calling $(D getLine) (usually far away from its definition) can't be +understood without looking at the documentation, even by users familiar with +the API: +---- +string getLine(bool keepTerminator) +{ + ... + if (keepTerminator) ... + ... +} +... +auto line = getLine(false); +---- + +Assuming the reverse meaning (i.e. "ignoreTerminator") and inserting the wrong +code compiles and runs with erroneous results. + +After replacing the boolean parameter with an instantiation of $(D Flag), code +calling $(D getLine) can be easily read and understood even by people not +fluent with the API: + +---- +string getLine(Flag!"keepTerminator" keepTerminator) +{ + ... + if (keepTerminator) ... + ... +} +... +auto line = getLine(Yes.keepTerminator); +---- + +The structs $(D Yes) and $(D No) are provided as shorthand for +$(D Flag!"Name".yes) and $(D Flag!"Name".no) and are preferred for brevity and +readability. These convenience structs mean it is usually unnecessary and +counterproductive to create an alias of a $(D Flag) as a way of avoiding typing +out the full type while specifying the affirmative or negative options. + +Passing categorical data by means of unstructured $(D bool) +parameters is classified under "simple-data coupling" by Steve +McConnell in the $(LUCKY Code Complete) book, along with three other +kinds of coupling. The author argues citing several studies that +coupling has a negative effect on code quality. $(D Flag) offers a +simple structuring method for passing yes/no flags to APIs. + */ +template Flag(string name) { + /// + enum Flag : bool + { + /** + When creating a value of type $(D Flag!"Name"), use $(D + Flag!"Name".no) for the negative option. When using a value + of type $(D Flag!"Name"), compare it against $(D + Flag!"Name".no) or just $(D false) or $(D 0). */ + no = false, + + /** When creating a value of type $(D Flag!"Name"), use $(D + Flag!"Name".yes) for the affirmative option. When using a + value of type $(D Flag!"Name"), compare it against $(D + Flag!"Name".yes). + */ + yes = true + } +} + +/** +Convenience names that allow using e.g. $(D Yes.encryption) instead of +$(D Flag!"encryption".yes) and $(D No.encryption) instead of $(D +Flag!"encryption".no). +*/ +struct Yes +{ + template opDispatch(string name) + { + enum opDispatch = Flag!name.yes; + } +} +//template yes(string name) { enum Flag!name yes = Flag!name.yes; } + +/// Ditto +struct No +{ + template opDispatch(string name) + { + enum opDispatch = Flag!name.no; + } +} + +/// +@safe unittest +{ + Flag!"abc" flag1; + assert(flag1 == Flag!"abc".no); + assert(flag1 == No.abc); + assert(!flag1); + if (flag1) assert(false); + flag1 = Yes.abc; + assert(flag1); + if (!flag1) assert(false); + if (flag1) {} else assert(false); + assert(flag1 == Yes.abc); +} + +/** +Detect whether an enum is of integral type and has only "flag" values +(i.e. values with a bit count of exactly 1). +Additionally, a zero value is allowed for compatibility with enums including +a "None" value. +*/ +template isBitFlagEnum(E) +{ + static if (is(E Base == enum) && isIntegral!Base) + { + enum isBitFlagEnum = (E.min >= 0) && + { + foreach (immutable flag; EnumMembers!E) + { + Base value = flag; + value &= value - 1; + if (value != 0) return false; + } + return true; + }(); + } + else + { + enum isBitFlagEnum = false; + } +} + +/// +@safe pure nothrow unittest +{ + enum A + { + None, + A = 1 << 0, + B = 1 << 1, + C = 1 << 2, + D = 1 << 3, + } + + static assert(isBitFlagEnum!A); + + enum B + { + A, + B, + C, + D // D == 3 + } + + static assert(!isBitFlagEnum!B); + + enum C: double + { + A = 1 << 0, + B = 1 << 1 + } + + static assert(!isBitFlagEnum!C); +} + +/** +A typesafe structure for storing combinations of enum values. + +This template defines a simple struct to represent bitwise OR combinations of +enum values. It can be used if all the enum values are integral constants with +a bit count of at most 1, or if the $(D unsafe) parameter is explicitly set to +Yes. +This is much safer than using the enum itself to store +the OR combination, which can produce surprising effects like this: +---- +enum E +{ + A = 1 << 0, + B = 1 << 1 +} +E e = E.A | E.B; +// will throw SwitchError +final switch (e) +{ + case E.A: + return; + case E.B: + return; +} +---- +*/ +struct BitFlags(E, Flag!"unsafe" unsafe = No.unsafe) if (unsafe || isBitFlagEnum!(E)) +{ +@safe @nogc pure nothrow: +private: + enum isBaseEnumType(T) = is(E == T); + alias Base = OriginalType!E; + Base mValue; + static struct Negation + { + @safe @nogc pure nothrow: + private: + Base mValue; + + // Prevent non-copy construction outside the module. + @disable this(); + this(Base value) + { + mValue = value; + } + } + +public: + this(E flag) + { + this = flag; + } + + this(T...)(T flags) + if (allSatisfy!(isBaseEnumType, T)) + { + this = flags; + } + + bool opCast(B: bool)() const + { + return mValue != 0; + } + + Base opCast(B)() const + if (isImplicitlyConvertible!(Base, B)) + { + return mValue; + } + + Negation opUnary(string op)() const + if (op == "~") + { + return Negation(~mValue); + } + + auto ref opAssign(T...)(T flags) + if (allSatisfy!(isBaseEnumType, T)) + { + mValue = 0; + foreach (E flag; flags) + { + mValue |= flag; + } + return this; + } + + auto ref opAssign(E flag) + { + mValue = flag; + return this; + } + + auto ref opOpAssign(string op: "|")(BitFlags flags) + { + mValue |= flags.mValue; + return this; + } + + auto ref opOpAssign(string op: "&")(BitFlags flags) + { + mValue &= flags.mValue; + return this; + } + + auto ref opOpAssign(string op: "|")(E flag) + { + mValue |= flag; + return this; + } + + auto ref opOpAssign(string op: "&")(E flag) + { + mValue &= flag; + return this; + } + + auto ref opOpAssign(string op: "&")(Negation negatedFlags) + { + mValue &= negatedFlags.mValue; + return this; + } + + auto opBinary(string op)(BitFlags flags) const + if (op == "|" || op == "&") + { + BitFlags result = this; + result.opOpAssign!op(flags); + return result; + } + + auto opBinary(string op)(E flag) const + if (op == "|" || op == "&") + { + BitFlags result = this; + result.opOpAssign!op(flag); + return result; + } + + auto opBinary(string op: "&")(Negation negatedFlags) const + { + BitFlags result = this; + result.opOpAssign!op(negatedFlags); + return result; + } + + auto opBinaryRight(string op)(E flag) const + if (op == "|" || op == "&") + { + return opBinary!op(flag); + } +} + +/// BitFlags can be manipulated with the usual operators +@safe @nogc pure nothrow unittest +{ + import std.traits : EnumMembers; + + // You can use such an enum with BitFlags straight away + enum Enum + { + None, + A = 1 << 0, + B = 1 << 1, + C = 1 << 2 + } + BitFlags!Enum flags1; + assert(!(flags1 & (Enum.A | Enum.B | Enum.C))); + + // You need to specify the `unsafe` parameter for enum with custom values + enum UnsafeEnum + { + A, + B, + C, + D = B|C + } + static assert(!__traits(compiles, { BitFlags!UnsafeEnum flags2; })); + BitFlags!(UnsafeEnum, Yes.unsafe) flags3; + + immutable BitFlags!Enum flags_empty; + // A default constructed BitFlags has no value set + assert(!(flags_empty & Enum.A) && !(flags_empty & Enum.B) && !(flags_empty & Enum.C)); + + // Value can be set with the | operator + immutable BitFlags!Enum flags_A = flags_empty | Enum.A; + + // And tested with the & operator + assert(flags_A & Enum.A); + + // Which commutes + assert(Enum.A & flags_A); + + // BitFlags can be variadically initialized + immutable BitFlags!Enum flags_AB = BitFlags!Enum(Enum.A, Enum.B); + assert((flags_AB & Enum.A) && (flags_AB & Enum.B) && !(flags_AB & Enum.C)); + + // Use the ~ operator for subtracting flags + immutable BitFlags!Enum flags_B = flags_AB & ~BitFlags!Enum(Enum.A); + assert(!(flags_B & Enum.A) && (flags_B & Enum.B) && !(flags_B & Enum.C)); + + // You can use the EnumMembers template to set all flags + immutable BitFlags!Enum flags_all = EnumMembers!Enum; + + // use & between BitFlags for intersection + immutable BitFlags!Enum flags_BC = BitFlags!Enum(Enum.B, Enum.C); + assert(flags_B == (flags_BC & flags_AB)); + + // All the binary operators work in their assignment version + BitFlags!Enum temp = flags_empty; + temp |= flags_AB; + assert(temp == (flags_empty | flags_AB)); + temp = flags_empty; + temp |= Enum.B; + assert(temp == (flags_empty | Enum.B)); + temp = flags_empty; + temp &= flags_AB; + assert(temp == (flags_empty & flags_AB)); + temp = flags_empty; + temp &= Enum.A; + assert(temp == (flags_empty & Enum.A)); + + // BitFlags with no value set evaluate to false + assert(!flags_empty); + + // BitFlags with at least one value set evaluate to true + assert(flags_A); + + // This can be useful to check intersection between BitFlags + assert(flags_A & flags_AB); + assert(flags_AB & Enum.A); + + // Finally, you can of course get you raw value out of flags + auto value = cast(int) flags_A; + assert(value == Enum.A); +} + +// ReplaceType +/** +Replaces all occurrences of `From` into `To`, in one or more types `T`. For +example, $(D ReplaceType!(int, uint, Tuple!(int, float)[string])) yields +$(D Tuple!(uint, float)[string]). The types in which replacement is performed +may be arbitrarily complex, including qualifiers, built-in type constructors +(pointers, arrays, associative arrays, functions, and delegates), and template +instantiations; replacement proceeds transitively through the type definition. +However, member types in `struct`s or `class`es are not replaced because there +are no ways to express the types resulting after replacement. + +This is an advanced type manipulation necessary e.g. for replacing the +placeholder type `This` in $(REF Algebraic, std,variant). + +Returns: `ReplaceType` aliases itself to the type(s) that result after +replacement. +*/ +template ReplaceType(From, To, T...) +{ + static if (T.length == 1) + { + static if (is(T[0] == From)) + alias ReplaceType = To; + else static if (is(T[0] == const(U), U)) + alias ReplaceType = const(ReplaceType!(From, To, U)); + else static if (is(T[0] == immutable(U), U)) + alias ReplaceType = immutable(ReplaceType!(From, To, U)); + else static if (is(T[0] == shared(U), U)) + alias ReplaceType = shared(ReplaceType!(From, To, U)); + else static if (is(T[0] == U*, U)) + { + static if (is(U == function)) + alias ReplaceType = replaceTypeInFunctionType!(From, To, T[0]); + else + alias ReplaceType = ReplaceType!(From, To, U)*; + } + else static if (is(T[0] == delegate)) + { + alias ReplaceType = replaceTypeInFunctionType!(From, To, T[0]); + } + else static if (is(T[0] == function)) + { + static assert(0, "Function types not supported," ~ + " use a function pointer type instead of " ~ T[0].stringof); + } + else static if (is(T[0] : U!V, alias U, V...)) + { + template replaceTemplateArgs(T...) + { + static if (is(typeof(T[0]))) // template argument is value or symbol + enum replaceTemplateArgs = T[0]; + else + alias replaceTemplateArgs = ReplaceType!(From, To, T[0]); + } + alias ReplaceType = U!(staticMap!(replaceTemplateArgs, V)); + } + else static if (is(T[0] == struct)) + // don't match with alias this struct below (Issue 15168) + alias ReplaceType = T[0]; + else static if (is(T[0] == U[], U)) + alias ReplaceType = ReplaceType!(From, To, U)[]; + else static if (is(T[0] == U[n], U, size_t n)) + alias ReplaceType = ReplaceType!(From, To, U)[n]; + else static if (is(T[0] == U[V], U, V)) + alias ReplaceType = + ReplaceType!(From, To, U)[ReplaceType!(From, To, V)]; + else + alias ReplaceType = T[0]; + } + else static if (T.length > 1) + { + alias ReplaceType = AliasSeq!(ReplaceType!(From, To, T[0]), + ReplaceType!(From, To, T[1 .. $])); + } + else + { + alias ReplaceType = AliasSeq!(); + } +} + +/// +@safe unittest +{ + static assert( + is(ReplaceType!(int, string, int[]) == string[]) && + is(ReplaceType!(int, string, int[int]) == string[string]) && + is(ReplaceType!(int, string, const(int)[]) == const(string)[]) && + is(ReplaceType!(int, string, Tuple!(int[], float)) + == Tuple!(string[], float)) + ); +} + +private template replaceTypeInFunctionType(From, To, fun) +{ + alias RX = ReplaceType!(From, To, ReturnType!fun); + alias PX = AliasSeq!(ReplaceType!(From, To, Parameters!fun)); + // Wrapping with AliasSeq is neccesary because ReplaceType doesn't return + // tuple if Parameters!fun.length == 1 + + string gen() + { + enum linkage = functionLinkage!fun; + alias attributes = functionAttributes!fun; + enum variadicStyle = variadicFunctionStyle!fun; + alias storageClasses = ParameterStorageClassTuple!fun; + + string result; + + result ~= "extern(" ~ linkage ~ ") "; + static if (attributes & FunctionAttribute.ref_) + { + result ~= "ref "; + } + + result ~= "RX"; + static if (is(fun == delegate)) + result ~= " delegate"; + else + result ~= " function"; + + result ~= "("; + foreach (i, _; PX) + { + if (i) + result ~= ", "; + if (storageClasses[i] & ParameterStorageClass.scope_) + result ~= "scope "; + if (storageClasses[i] & ParameterStorageClass.out_) + result ~= "out "; + if (storageClasses[i] & ParameterStorageClass.ref_) + result ~= "ref "; + if (storageClasses[i] & ParameterStorageClass.lazy_) + result ~= "lazy "; + if (storageClasses[i] & ParameterStorageClass.return_) + result ~= "return "; + + result ~= "PX[" ~ i.stringof ~ "]"; + } + static if (variadicStyle == Variadic.typesafe) + result ~= " ..."; + else static if (variadicStyle != Variadic.no) + result ~= ", ..."; + result ~= ")"; + + static if (attributes & FunctionAttribute.pure_) + result ~= " pure"; + static if (attributes & FunctionAttribute.nothrow_) + result ~= " nothrow"; + static if (attributes & FunctionAttribute.property) + result ~= " @property"; + static if (attributes & FunctionAttribute.trusted) + result ~= " @trusted"; + static if (attributes & FunctionAttribute.safe) + result ~= " @safe"; + static if (attributes & FunctionAttribute.nogc) + result ~= " @nogc"; + static if (attributes & FunctionAttribute.system) + result ~= " @system"; + static if (attributes & FunctionAttribute.const_) + result ~= " const"; + static if (attributes & FunctionAttribute.immutable_) + result ~= " immutable"; + static if (attributes & FunctionAttribute.inout_) + result ~= " inout"; + static if (attributes & FunctionAttribute.shared_) + result ~= " shared"; + static if (attributes & FunctionAttribute.return_) + result ~= " return"; + + return result; + } + //pragma(msg, "gen ==> ", gen()); + + mixin("alias replaceTypeInFunctionType = " ~ gen() ~ ";"); +} + +@safe unittest +{ + template Test(Ts...) + { + static if (Ts.length) + { + //pragma(msg, "Testing: ReplaceType!("~Ts[0].stringof~", " + // ~Ts[1].stringof~", "~Ts[2].stringof~")"); + static assert(is(ReplaceType!(Ts[0], Ts[1], Ts[2]) == Ts[3]), + "ReplaceType!("~Ts[0].stringof~", "~Ts[1].stringof~", " + ~Ts[2].stringof~") == " + ~ReplaceType!(Ts[0], Ts[1], Ts[2]).stringof); + alias Test = Test!(Ts[4 .. $]); + } + else alias Test = void; + } + + //import core.stdc.stdio; + alias RefFun1 = ref int function(float, long); + alias RefFun2 = ref float function(float, long); + extern(C) int printf(const char*, ...) nothrow @nogc @system; + extern(C) float floatPrintf(const char*, ...) nothrow @nogc @system; + int func(float); + + int x; + struct S1 { void foo() { x = 1; } } + struct S2 { void bar() { x = 2; } } + + alias Pass = Test!( + int, float, typeof(&func), float delegate(float), + int, float, typeof(&printf), typeof(&floatPrintf), + int, float, int function(out long, ...), + float function(out long, ...), + int, float, int function(ref float, long), + float function(ref float, long), + int, float, int function(ref int, long), + float function(ref float, long), + int, float, int function(out int, long), + float function(out float, long), + int, float, int function(lazy int, long), + float function(lazy float, long), + int, float, int function(out long, ref const int), + float function(out long, ref const float), + int, int, int, int, + int, float, int, float, + int, float, const int, const float, + int, float, immutable int, immutable float, + int, float, shared int, shared float, + int, float, int*, float*, + int, float, const(int)*, const(float)*, + int, float, const(int*), const(float*), + const(int)*, float, const(int*), const(float), + int*, float, const(int)*, const(int)*, + int, float, int[], float[], + int, float, int[42], float[42], + int, float, const(int)[42], const(float)[42], + int, float, const(int[42]), const(float[42]), + int, float, int[int], float[float], + int, float, int[double], float[double], + int, float, double[int], double[float], + int, float, int function(float, long), float function(float, long), + int, float, int function(float), float function(float), + int, float, int function(float, int), float function(float, float), + int, float, int delegate(float, long), float delegate(float, long), + int, float, int delegate(float), float delegate(float), + int, float, int delegate(float, int), float delegate(float, float), + int, float, Unique!int, Unique!float, + int, float, Tuple!(float, int), Tuple!(float, float), + int, float, RefFun1, RefFun2, + S1, S2, + S1[1][][S1]* function(), + S2[1][][S2]* function(), + int, string, + int[3] function( int[] arr, int[2] ...) pure @trusted, + string[3] function(string[] arr, string[2] ...) pure @trusted, + ); + + // Bugzilla 15168 + static struct T1 { string s; alias s this; } + static struct T2 { char[10] s; alias s this; } + static struct T3 { string[string] s; alias s this; } + alias Pass2 = Test!( + ubyte, ubyte, T1, T1, + ubyte, ubyte, T2, T2, + ubyte, ubyte, T3, T3, + ); +} + +@safe unittest // Bugzilla 17116 +{ + alias ConstDg = void delegate(float) const; + alias B = void delegate(int) const; + alias A = ReplaceType!(float, int, ConstDg); + static assert(is(B == A)); +} + +/** +Ternary type with three truth values: + +$(UL + $(LI `Ternary.yes` for `true`) + $(LI `Ternary.no` for `false`) + $(LI `Ternary.unknown` as an unknown state) +) + +Also known as trinary, trivalent, or trilean. + +See_Also: + $(HTTP en.wikipedia.org/wiki/Three-valued_logic, + Three Valued Logic on Wikipedia) +*/ +struct Ternary +{ + @safe @nogc nothrow pure: + + private ubyte value = 6; + private static Ternary make(ubyte b) + { + Ternary r = void; + r.value = b; + return r; + } + + /** + The possible states of the `Ternary` + */ + enum no = make(0); + /// ditto + enum yes = make(2); + /// ditto + enum unknown = make(6); + + /** + Construct and assign from a `bool`, receiving `no` for `false` and `yes` + for `true`. + */ + this(bool b) { value = b << 1; } + + /// ditto + void opAssign(bool b) { value = b << 1; } + + /** + Construct a ternary value from another ternary value + */ + this(const Ternary b) { value = b.value; } + + /** + $(TABLE Truth table for logical operations, + $(TR $(TH `a`) $(TH `b`) $(TH `$(TILDE)a`) $(TH `a | b`) $(TH `a & b`) $(TH `a ^ b`)) + $(TR $(TD `no`) $(TD `no`) $(TD `yes`) $(TD `no`) $(TD `no`) $(TD `no`)) + $(TR $(TD `no`) $(TD `yes`) $(TD) $(TD `yes`) $(TD `no`) $(TD `yes`)) + $(TR $(TD `no`) $(TD `unknown`) $(TD) $(TD `unknown`) $(TD `no`) $(TD `unknown`)) + $(TR $(TD `yes`) $(TD `no`) $(TD `no`) $(TD `yes`) $(TD `no`) $(TD `yes`)) + $(TR $(TD `yes`) $(TD `yes`) $(TD) $(TD `yes`) $(TD `yes`) $(TD `no`)) + $(TR $(TD `yes`) $(TD `unknown`) $(TD) $(TD `yes`) $(TD `unknown`) $(TD `unknown`)) + $(TR $(TD `unknown`) $(TD `no`) $(TD `unknown`) $(TD `unknown`) $(TD `no`) $(TD `unknown`)) + $(TR $(TD `unknown`) $(TD `yes`) $(TD) $(TD `yes`) $(TD `unknown`) $(TD `unknown`)) + $(TR $(TD `unknown`) $(TD `unknown`) $(TD) $(TD `unknown`) $(TD `unknown`) $(TD `unknown`)) + ) + */ + Ternary opUnary(string s)() if (s == "~") + { + return make((386 >> value) & 6); + } + + /// ditto + Ternary opBinary(string s)(Ternary rhs) if (s == "|") + { + return make((25_512 >> (value + rhs.value)) & 6); + } + + /// ditto + Ternary opBinary(string s)(Ternary rhs) if (s == "&") + { + return make((26_144 >> (value + rhs.value)) & 6); + } + + /// ditto + Ternary opBinary(string s)(Ternary rhs) if (s == "^") + { + return make((26_504 >> (value + rhs.value)) & 6); + } +} + +/// +@safe @nogc nothrow pure +unittest +{ + Ternary a; + assert(a == Ternary.unknown); + + assert(~Ternary.yes == Ternary.no); + assert(~Ternary.no == Ternary.yes); + assert(~Ternary.unknown == Ternary.unknown); +} + +@safe @nogc nothrow pure +unittest +{ + alias f = Ternary.no, t = Ternary.yes, u = Ternary.unknown; + Ternary[27] truthTableAnd = + [ + t, t, t, + t, u, u, + t, f, f, + u, t, u, + u, u, u, + u, f, f, + f, t, f, + f, u, f, + f, f, f, + ]; + + Ternary[27] truthTableOr = + [ + t, t, t, + t, u, t, + t, f, t, + u, t, t, + u, u, u, + u, f, u, + f, t, t, + f, u, u, + f, f, f, + ]; + + Ternary[27] truthTableXor = + [ + t, t, f, + t, u, u, + t, f, t, + u, t, u, + u, u, u, + u, f, u, + f, t, t, + f, u, u, + f, f, f, + ]; + + for (auto i = 0; i != truthTableAnd.length; i += 3) + { + assert((truthTableAnd[i] & truthTableAnd[i + 1]) + == truthTableAnd[i + 2]); + assert((truthTableOr[i] | truthTableOr[i + 1]) + == truthTableOr[i + 2]); + assert((truthTableXor[i] ^ truthTableXor[i + 1]) + == truthTableXor[i + 2]); + } + + Ternary a; + assert(a == Ternary.unknown); + static assert(!is(typeof({ if (a) {} }))); + assert(!is(typeof({ auto b = Ternary(3); }))); + a = true; + assert(a == Ternary.yes); + a = false; + assert(a == Ternary.no); + a = Ternary.unknown; + assert(a == Ternary.unknown); + Ternary b; + b = a; + assert(b == a); + assert(~Ternary.yes == Ternary.no); + assert(~Ternary.no == Ternary.yes); + assert(~Ternary.unknown == Ternary.unknown); +} diff --git a/libphobos/src/std/typetuple.d b/libphobos/src/std/typetuple.d new file mode 100644 index 0000000..dedbdc2 --- /dev/null +++ b/libphobos/src/std/typetuple.d @@ -0,0 +1,40 @@ +/** + * This module was renamed to disambiguate the term tuple, use + * $(MREF std, meta) instead. + * + * Copyright: Copyright Digital Mars 2005 - 2015. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: + * Source: $(PHOBOSSRC std/_typetuple.d) + * + * $(SCRIPT inhibitQuickIndex = 1;) + */ +module std.typetuple; + +public import std.meta; + +/** + * Alternate name for $(REF AliasSeq, std,meta) for legacy compatibility. + */ +alias TypeTuple = AliasSeq; + +/// +@safe unittest +{ + import std.typetuple; + alias TL = TypeTuple!(int, double); + + int foo(TL td) // same as int foo(int, double); + { + return td[0] + cast(int) td[1]; + } +} + +/// +@safe unittest +{ + alias TL = TypeTuple!(int, double); + + alias Types = TypeTuple!(TL, char); + static assert(is(Types == TypeTuple!(int, double, char))); +} diff --git a/libphobos/src/std/uni.d b/libphobos/src/std/uni.d new file mode 100644 index 0000000..5f24ad1 --- /dev/null +++ b/libphobos/src/std/uni.d @@ -0,0 +1,9756 @@ +// Written in the D programming language. + +/++ + $(P The $(D std.uni) module provides an implementation + of fundamental Unicode algorithms and data structures. + This doesn't include UTF encoding and decoding primitives, + see $(REF decode, std,_utf) and $(REF encode, std,_utf) in $(MREF std, utf) + for this functionality. ) + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE, +$(TR $(TH Category) $(TH Functions)) +$(TR $(TD Decode) $(TD + $(LREF byCodePoint) + $(LREF byGrapheme) + $(LREF decodeGrapheme) + $(LREF graphemeStride) +)) +$(TR $(TD Comparison) $(TD + $(LREF icmp) + $(LREF sicmp) +)) +$(TR $(TD Classification) $(TD + $(LREF isAlpha) + $(LREF isAlphaNum) + $(LREF isCodepointSet) + $(LREF isControl) + $(LREF isFormat) + $(LREF isGraphical) + $(LREF isIntegralPair) + $(LREF isMark) + $(LREF isNonCharacter) + $(LREF isNumber) + $(LREF isPrivateUse) + $(LREF isPunctuation) + $(LREF isSpace) + $(LREF isSurrogate) + $(LREF isSurrogateHi) + $(LREF isSurrogateLo) + $(LREF isSymbol) + $(LREF isWhite) +)) +$(TR $(TD Normalization) $(TD + $(LREF NFC) + $(LREF NFD) + $(LREF NFKD) + $(LREF NormalizationForm) + $(LREF normalize) +)) +$(TR $(TD Decompose) $(TD + $(LREF decompose) + $(LREF decomposeHangul) + $(LREF UnicodeDecomposition) +)) +$(TR $(TD Compose) $(TD + $(LREF compose) + $(LREF composeJamo) +)) +$(TR $(TD Sets) $(TD + $(LREF CodepointInterval) + $(LREF CodepointSet) + $(LREF InversionList) + $(LREF unicode) +)) +$(TR $(TD Trie) $(TD + $(LREF codepointSetTrie) + $(LREF CodepointSetTrie) + $(LREF codepointTrie) + $(LREF CodepointTrie) + $(LREF toTrie) + $(LREF toDelegate) +)) +$(TR $(TD Casing) $(TD + $(LREF asCapitalized) + $(LREF asLowerCase) + $(LREF asUpperCase) + $(LREF isLower) + $(LREF isUpper) + $(LREF toLower) + $(LREF toLowerInPlace) + $(LREF toUpper) + $(LREF toUpperInPlace) +)) +$(TR $(TD Utf8Matcher) $(TD + $(LREF isUtfMatcher) + $(LREF MatcherConcept) + $(LREF utfMatcher) +)) +$(TR $(TD Separators) $(TD + $(LREF lineSep) + $(LREF nelSep) + $(LREF paraSep) +)) +$(TR $(TD Building blocks) $(TD + $(LREF allowedIn) + $(LREF combiningClass) + $(LREF Grapheme) +)) +) + + $(P All primitives listed operate on Unicode characters and + sets of characters. For functions which operate on ASCII characters + and ignore Unicode $(CHARACTERS), see $(MREF std, ascii). + For definitions of Unicode $(CHARACTER), $(CODEPOINT) and other terms + used throughout this module see the $(S_LINK Terminology, terminology) section + below. + ) + $(P The focus of this module is the core needs of developing Unicode-aware + applications. To that effect it provides the following optimized primitives: + ) + $(UL + $(LI Character classification by category and common properties: + $(LREF isAlpha), $(LREF isWhite) and others. + ) + $(LI + Case-insensitive string comparison ($(LREF sicmp), $(LREF icmp)). + ) + $(LI + Converting text to any of the four normalization forms via $(LREF normalize). + ) + $(LI + Decoding ($(LREF decodeGrapheme)) and iteration ($(LREF byGrapheme), $(LREF graphemeStride)) + by user-perceived characters, that is by $(LREF Grapheme) clusters. + ) + $(LI + Decomposing and composing of individual character(s) according to canonical + or compatibility rules, see $(LREF compose) and $(LREF decompose), + including the specific version for Hangul syllables $(LREF composeJamo) + and $(LREF decomposeHangul). + ) + ) + $(P It's recognized that an application may need further enhancements + and extensions, such as less commonly known algorithms, + or tailoring existing ones for region specific needs. To help users + with building any extra functionality beyond the core primitives, + the module provides: + ) + $(UL + $(LI + $(LREF CodepointSet), a type for easy manipulation of sets of characters. + Besides the typical set algebra it provides an unusual feature: + a D source code generator for detection of $(CODEPOINTS) in this set. + This is a boon for meta-programming parser frameworks, + and is used internally to power classification in small + sets like $(LREF isWhite). + ) + $(LI + A way to construct optimal packed multi-stage tables also known as a + special case of $(LINK2 https://en.wikipedia.org/wiki/Trie, Trie). + The functions $(LREF codepointTrie), $(LREF codepointSetTrie) + construct custom tries that map dchar to value. + The end result is a fast and predictable $(BIGOH 1) lookup that powers + functions like $(LREF isAlpha) and $(LREF combiningClass), + but for user-defined data sets. + ) + $(LI + A useful technique for Unicode-aware parsers that perform + character classification of encoded $(CODEPOINTS) + is to avoid unnecassary decoding at all costs. + $(LREF utfMatcher) provides an improvement over the usual workflow + of decode-classify-process, combining the decoding and classification + steps. By extracting necessary bits directly from encoded + $(S_LINK Code unit, code units) matchers achieve + significant performance improvements. See $(LREF MatcherConcept) for + the common interface of UTF matchers. + ) + $(LI + Generally useful building blocks for customized normalization: + $(LREF combiningClass) for querying combining class + and $(LREF allowedIn) for testing the Quick_Check + property of a given normalization form. + ) + $(LI + Access to a large selection of commonly used sets of $(CODEPOINTS). + $(S_LINK Unicode properties, Supported sets) include Script, + Block and General Category. The exact contents of a set can be + observed in the CLDR utility, on the + $(HTTP www.unicode.org/cldr/utility/properties.jsp, property index) page + of the Unicode website. + See $(LREF unicode) for easy and (optionally) compile-time checked set + queries. + ) + ) + $(SECTION Synopsis) + --- + import std.uni; + void main() + { + // initialize code point sets using script/block or property name + // now 'set' contains code points from both scripts. + auto set = unicode("Cyrillic") | unicode("Armenian"); + // same thing but simpler and checked at compile-time + auto ascii = unicode.ASCII; + auto currency = unicode.Currency_Symbol; + + // easy set ops + auto a = set & ascii; + assert(a.empty); // as it has no intersection with ascii + a = set | ascii; + auto b = currency - a; // subtract all ASCII, Cyrillic and Armenian + + // some properties of code point sets + assert(b.length > 45); // 46 items in Unicode 6.1, even more in 6.2 + // testing presence of a code point in a set + // is just fine, it is O(logN) + assert(!b['$']); + assert(!b['\u058F']); // Armenian dram sign + assert(b['¥']); + + // building fast lookup tables, these guarantee O(1) complexity + // 1-level Trie lookup table essentially a huge bit-set ~262Kb + auto oneTrie = toTrie!1(b); + // 2-level far more compact but typically slightly slower + auto twoTrie = toTrie!2(b); + // 3-level even smaller, and a bit slower yet + auto threeTrie = toTrie!3(b); + assert(oneTrie['£']); + assert(twoTrie['£']); + assert(threeTrie['£']); + + // build the trie with the most sensible trie level + // and bind it as a functor + auto cyrillicOrArmenian = toDelegate(set); + auto balance = find!(cyrillicOrArmenian)("Hello ընկեր!"); + assert(balance == "ընկեր!"); + // compatible with bool delegate(dchar) + bool delegate(dchar) bindIt = cyrillicOrArmenian; + + // Normalization + string s = "Plain ascii (and not only), is always normalized!"; + assert(s is normalize(s));// is the same string + + string nonS = "A\u0308ffin"; // A ligature + auto nS = normalize(nonS); // to NFC, the W3C endorsed standard + assert(nS == "Äffin"); + assert(nS != nonS); + string composed = "Äffin"; + + assert(normalize!NFD(composed) == "A\u0308ffin"); + // to NFKD, compatibility decomposition useful for fuzzy matching/searching + assert(normalize!NFKD("2¹⁰") == "210"); + } + --- + $(SECTION Terminology + ) + $(P The following is a list of important Unicode notions + and definitions. Any conventions used specifically in this + module alone are marked as such. The descriptions are based on the formal + definition as found in $(HTTP www.unicode.org/versions/Unicode6.2.0/ch03.pdf, + chapter three of The Unicode Standard Core Specification.) + ) + $(P $(DEF Abstract character) A unit of information used for the organization, + control, or representation of textual data. + Note that: + $(UL + $(LI When representing data, the nature of that data + is generally symbolic as opposed to some other + kind of data (for example, visual). + ) + $(LI An abstract character has no concrete form + and should not be confused with a $(S_LINK Glyph, glyph). + ) + $(LI An abstract character does not necessarily + correspond to what a user thinks of as a “character” + and should not be confused with a $(LREF Grapheme). + ) + $(LI The abstract characters encoded (see Encoded character) + are known as Unicode abstract characters. + ) + $(LI Abstract characters not directly + encoded by the Unicode Standard can often be + represented by the use of combining character sequences. + ) + ) + ) + $(P $(DEF Canonical decomposition) + The decomposition of a character or character sequence + that results from recursively applying the canonical + mappings found in the Unicode Character Database + and these described in Conjoining Jamo Behavior + (section 12 of + $(HTTP www.unicode.org/uni2book/ch03.pdf, Unicode Conformance)). + ) + $(P $(DEF Canonical composition) + The precise definition of the Canonical composition + is the algorithm as specified in $(HTTP www.unicode.org/uni2book/ch03.pdf, + Unicode Conformance) section 11. + Informally it's the process that does the reverse of the canonical + decomposition with the addition of certain rules + that e.g. prevent legacy characters from appearing in the composed result. + ) + $(P $(DEF Canonical equivalent) + Two character sequences are said to be canonical equivalents if + their full canonical decompositions are identical. + ) + $(P $(DEF Character) Typically differs by context. + For the purpose of this documentation the term $(I character) + implies $(I encoded character), that is, a code point having + an assigned abstract character (a symbolic meaning). + ) + $(P $(DEF Code point) Any value in the Unicode codespace; + that is, the range of integers from 0 to 10FFFF (hex). + Not all code points are assigned to encoded characters. + ) + $(P $(DEF Code unit) The minimal bit combination that can represent + a unit of encoded text for processing or interchange. + Depending on the encoding this could be: + 8-bit code units in the UTF-8 ($(D char)), + 16-bit code units in the UTF-16 ($(D wchar)), + and 32-bit code units in the UTF-32 ($(D dchar)). + $(I Note that in UTF-32, a code unit is a code point + and is represented by the D $(D dchar) type.) + ) + $(P $(DEF Combining character) A character with the General Category + of Combining Mark(M). + $(UL + $(LI All characters with non-zero canonical combining class + are combining characters, but the reverse is not the case: + there are combining characters with a zero combining class. + ) + $(LI These characters are not normally used in isolation + unless they are being described. They include such characters + as accents, diacritics, Hebrew points, Arabic vowel signs, + and Indic matras. + ) + ) + ) + $(P $(DEF Combining class) + A numerical value used by the Unicode Canonical Ordering Algorithm + to determine which sequences of combining marks are to be + considered canonically equivalent and which are not. + ) + $(P $(DEF Compatibility decomposition) + The decomposition of a character or character sequence that results + from recursively applying both the compatibility mappings and + the canonical mappings found in the Unicode Character Database, and those + described in Conjoining Jamo Behavior no characters + can be further decomposed. + ) + $(P $(DEF Compatibility equivalent) + Two character sequences are said to be compatibility + equivalents if their full compatibility decompositions are identical. + ) + $(P $(DEF Encoded character) An association (or mapping) + between an abstract character and a code point. + ) + $(P $(DEF Glyph) The actual, concrete image of a glyph representation + having been rasterized or otherwise imaged onto some display surface. + ) + $(P $(DEF Grapheme base) A character with the property + Grapheme_Base, or any standard Korean syllable block. + ) + $(P $(DEF Grapheme cluster) Defined as the text between + grapheme boundaries as specified by Unicode Standard Annex #29, + $(HTTP www.unicode.org/reports/tr29/, Unicode text segmentation). + Important general properties of a grapheme: + $(UL + $(LI The grapheme cluster represents a horizontally segmentable + unit of text, consisting of some grapheme base (which may + consist of a Korean syllable) together with any number of + nonspacing marks applied to it. + ) + $(LI A grapheme cluster typically starts with a grapheme base + and then extends across any subsequent sequence of nonspacing marks. + A grapheme cluster is most directly relevant to text rendering and + processes such as cursor placement and text selection in editing, + but may also be relevant to comparison and searching. + ) + $(LI For many processes, a grapheme cluster behaves as if it was a + single character with the same properties as its grapheme base. + Effectively, nonspacing marks apply $(I graphically) to the base, + but do not change its properties. + ) + ) + $(P This module defines a number of primitives that work with graphemes: + $(LREF Grapheme), $(LREF decodeGrapheme) and $(LREF graphemeStride). + All of them are using $(I extended grapheme) boundaries + as defined in the aforementioned standard annex. + ) + ) + $(P $(DEF Nonspacing mark) A combining character with the + General Category of Nonspacing Mark (Mn) or Enclosing Mark (Me). + ) + $(P $(DEF Spacing mark) A combining character that is not a nonspacing mark. + ) + $(SECTION Normalization + ) + $(P The concepts of $(S_LINK Canonical equivalent, canonical equivalent) + or $(S_LINK Compatibility equivalent, compatibility equivalent) + characters in the Unicode Standard make it necessary to have a full, formal + definition of equivalence for Unicode strings. + String equivalence is determined by a process called normalization, + whereby strings are converted into forms which are compared + directly for identity. This is the primary goal of the normalization process, + see the function $(LREF normalize) to convert into any of + the four defined forms. + ) + $(P A very important attribute of the Unicode Normalization Forms + is that they must remain stable between versions of the Unicode Standard. + A Unicode string normalized to a particular Unicode Normalization Form + in one version of the standard is guaranteed to remain in that Normalization + Form for implementations of future versions of the standard. + ) + $(P The Unicode Standard specifies four normalization forms. + Informally, two of these forms are defined by maximal decomposition + of equivalent sequences, and two of these forms are defined + by maximal $(I composition) of equivalent sequences. + $(UL + $(LI Normalization Form D (NFD): The $(S_LINK Canonical decomposition, + canonical decomposition) of a character sequence.) + $(LI Normalization Form KD (NFKD): The $(S_LINK Compatibility decomposition, + compatibility decomposition) of a character sequence.) + $(LI Normalization Form C (NFC): The canonical composition of the + $(S_LINK Canonical decomposition, canonical decomposition) + of a coded character sequence.) + $(LI Normalization Form KC (NFKC): The canonical composition + of the $(S_LINK Compatibility decomposition, + compatibility decomposition) of a character sequence) + ) + ) + $(P The choice of the normalization form depends on the particular use case. + NFC is the best form for general text, since it's more compatible with + strings converted from legacy encodings. NFKC is the preferred form for + identifiers, especially where there are security concerns. NFD and NFKD + are the most useful for internal processing. + ) + $(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), + it is not a trivial task to provide a space-efficient solution for + the multitude of properties. + ) + $(P Common approaches such as hash-tables or binary search over + sorted code point intervals (as in $(LREF InversionList)) are insufficient. + Hash-tables have enormous memory footprint and binary search + over intervals is not fast enough for some heavy-duty algorithms. + ) + $(P The recommended solution (see Unicode Implementation Guidelines) + is using multi-stage tables that are an implementation of the + $(HTTP en.wikipedia.org/wiki/Trie, Trie) data structure with integer + keys and a fixed number of stages. For the remainder of the section + this will be called a fixed trie. The following describes a particular + implementation that is aimed for the speed of access at the expense + of ideal size savings. + ) + $(P Taking a 2-level Trie as an example the principle of operation is as follows. + Split the number of bits in a key (code point, 21 bits) into 2 components + (e.g. 15 and 8). The first is the number of bits in the index of the trie + and the other is number of bits in each page of the trie. + The layout of the trie is then an array of size 2^^bits-of-index followed + an array of memory chunks of size 2^^bits-of-page/bits-per-element. + ) + $(P The number of pages is variable (but not less then 1) + unlike the number of entries in the index. The slots of the index + all have to contain a number of a page that is present. The lookup is then + just a couple of operations - slice the upper bits, + lookup an index for these, take a page at this index and use + the lower bits as an offset within this page. + + Assuming that pages are laid out consequently + in one array at $(D pages), the pseudo-code is: + ) + --- + auto elemsPerPage = (2 ^^ bits_per_page) / Value.sizeOfInBits; + pages[index[n >> bits_per_page]][n & (elemsPerPage - 1)]; + --- + $(P Where if $(D elemsPerPage) is a power of 2 the whole process is + a handful of simple instructions and 2 array reads. Subsequent levels + of the trie are introduced by recursing on this notion - the index array + is treated as values. The number of bits in index is then again + split into 2 parts, with pages over 'current-index' and the new 'upper-index'. + ) + + $(P For completeness a level 1 trie is simply an array. + The current implementation takes advantage of bit-packing values + when the range is known to be limited in advance (such as $(D bool)). + See also $(LREF BitPacked) for enforcing it manually. + The major size advantage however comes from the fact + that multiple $(B identical pages on every level are merged) by construction. + ) + $(P The process of constructing a trie is more involved and is hidden from + the user in a form of the convenience functions $(LREF codepointTrie), + $(LREF codepointSetTrie) and the even more convenient $(LREF toTrie). + In general a set or built-in AA with $(D dchar) type + can be turned into a trie. The trie object in this module + is read-only (immutable); it's effectively frozen after construction. + ) + $(SECTION Unicode properties + ) + $(P This is a full list of Unicode properties accessible through $(LREF unicode) + with specific helpers per category nested within. Consult the + $(HTTP www.unicode.org/cldr/utility/properties.jsp, CLDR utility) + when in doubt about the contents of a particular set. + ) + $(P General category sets listed below are only accessible with the + $(LREF unicode) shorthand accessor.) + $(BOOKTABLE $(B General category ), + $(TR $(TH Abb.) $(TH Long form) + $(TH Abb.) $(TH Long form)$(TH Abb.) $(TH Long form)) + $(TR $(TD L) $(TD Letter) + $(TD Cn) $(TD Unassigned) $(TD Po) $(TD Other_Punctuation)) + $(TR $(TD Ll) $(TD Lowercase_Letter) + $(TD Co) $(TD Private_Use) $(TD Ps) $(TD Open_Punctuation)) + $(TR $(TD Lm) $(TD Modifier_Letter) + $(TD Cs) $(TD Surrogate) $(TD S) $(TD Symbol)) + $(TR $(TD Lo) $(TD Other_Letter) + $(TD N) $(TD Number) $(TD Sc) $(TD Currency_Symbol)) + $(TR $(TD Lt) $(TD Titlecase_Letter) + $(TD Nd) $(TD Decimal_Number) $(TD Sk) $(TD Modifier_Symbol)) + $(TR $(TD Lu) $(TD Uppercase_Letter) + $(TD Nl) $(TD Letter_Number) $(TD Sm) $(TD Math_Symbol)) + $(TR $(TD M) $(TD Mark) + $(TD No) $(TD Other_Number) $(TD So) $(TD Other_Symbol)) + $(TR $(TD Mc) $(TD Spacing_Mark) + $(TD P) $(TD Punctuation) $(TD Z) $(TD Separator)) + $(TR $(TD Me) $(TD Enclosing_Mark) + $(TD Pc) $(TD Connector_Punctuation) $(TD Zl) $(TD Line_Separator)) + $(TR $(TD Mn) $(TD Nonspacing_Mark) + $(TD Pd) $(TD Dash_Punctuation) $(TD Zp) $(TD Paragraph_Separator)) + $(TR $(TD C) $(TD Other) + $(TD Pe) $(TD Close_Punctuation) $(TD Zs) $(TD Space_Separator)) + $(TR $(TD Cc) $(TD Control) $(TD Pf) + $(TD Final_Punctuation) $(TD -) $(TD Any)) + $(TR $(TD Cf) $(TD Format) + $(TD Pi) $(TD Initial_Punctuation) $(TD -) $(TD ASCII)) + ) + $(P Sets for other commonly useful properties that are + accessible with $(LREF unicode):) + $(BOOKTABLE $(B Common binary properties), + $(TR $(TH Name) $(TH Name) $(TH Name)) + $(TR $(TD Alphabetic) $(TD Ideographic) $(TD Other_Uppercase)) + $(TR $(TD ASCII_Hex_Digit) $(TD IDS_Binary_Operator) $(TD Pattern_Syntax)) + $(TR $(TD Bidi_Control) $(TD ID_Start) $(TD Pattern_White_Space)) + $(TR $(TD Cased) $(TD IDS_Trinary_Operator) $(TD Quotation_Mark)) + $(TR $(TD Case_Ignorable) $(TD Join_Control) $(TD Radical)) + $(TR $(TD Dash) $(TD Logical_Order_Exception) $(TD Soft_Dotted)) + $(TR $(TD Default_Ignorable_Code_Point) $(TD Lowercase) $(TD STerm)) + $(TR $(TD Deprecated) $(TD Math) $(TD Terminal_Punctuation)) + $(TR $(TD Diacritic) $(TD Noncharacter_Code_Point) $(TD Unified_Ideograph)) + $(TR $(TD Extender) $(TD Other_Alphabetic) $(TD Uppercase)) + $(TR $(TD Grapheme_Base) $(TD Other_Default_Ignorable_Code_Point) $(TD Variation_Selector)) + $(TR $(TD Grapheme_Extend) $(TD Other_Grapheme_Extend) $(TD White_Space)) + $(TR $(TD Grapheme_Link) $(TD Other_ID_Continue) $(TD XID_Continue)) + $(TR $(TD Hex_Digit) $(TD Other_ID_Start) $(TD XID_Start)) + $(TR $(TD Hyphen) $(TD Other_Lowercase) ) + $(TR $(TD ID_Continue) $(TD Other_Math) ) + ) + $(P Below is the table with block names accepted by $(LREF unicode.block). + Note that the shorthand version $(LREF unicode) requires "In" + to be prepended to the names of blocks so as to disambiguate + scripts and blocks. + ) + $(BOOKTABLE $(B Blocks), + $(TR $(TD Aegean Numbers) $(TD Ethiopic Extended) $(TD Mongolian)) + $(TR $(TD Alchemical Symbols) $(TD Ethiopic Extended-A) $(TD Musical Symbols)) + $(TR $(TD Alphabetic Presentation Forms) $(TD Ethiopic Supplement) $(TD Myanmar)) + $(TR $(TD Ancient Greek Musical Notation) $(TD General Punctuation) $(TD Myanmar Extended-A)) + $(TR $(TD Ancient Greek Numbers) $(TD Geometric Shapes) $(TD New Tai Lue)) + $(TR $(TD Ancient Symbols) $(TD Georgian) $(TD NKo)) + $(TR $(TD Arabic) $(TD Georgian Supplement) $(TD Number Forms)) + $(TR $(TD Arabic Extended-A) $(TD Glagolitic) $(TD Ogham)) + $(TR $(TD Arabic Mathematical Alphabetic Symbols) $(TD Gothic) $(TD Ol Chiki)) + $(TR $(TD Arabic Presentation Forms-A) $(TD Greek and Coptic) $(TD Old Italic)) + $(TR $(TD Arabic Presentation Forms-B) $(TD Greek Extended) $(TD Old Persian)) + $(TR $(TD Arabic Supplement) $(TD Gujarati) $(TD Old South Arabian)) + $(TR $(TD Armenian) $(TD Gurmukhi) $(TD Old Turkic)) + $(TR $(TD Arrows) $(TD Halfwidth and Fullwidth Forms) $(TD Optical Character Recognition)) + $(TR $(TD Avestan) $(TD Hangul Compatibility Jamo) $(TD Oriya)) + $(TR $(TD Balinese) $(TD Hangul Jamo) $(TD Osmanya)) + $(TR $(TD Bamum) $(TD Hangul Jamo Extended-A) $(TD Phags-pa)) + $(TR $(TD Bamum Supplement) $(TD Hangul Jamo Extended-B) $(TD Phaistos Disc)) + $(TR $(TD Basic Latin) $(TD Hangul Syllables) $(TD Phoenician)) + $(TR $(TD Batak) $(TD Hanunoo) $(TD Phonetic Extensions)) + $(TR $(TD Bengali) $(TD Hebrew) $(TD Phonetic Extensions Supplement)) + $(TR $(TD Block Elements) $(TD High Private Use Surrogates) $(TD Playing Cards)) + $(TR $(TD Bopomofo) $(TD High Surrogates) $(TD Private Use Area)) + $(TR $(TD Bopomofo Extended) $(TD Hiragana) $(TD Rejang)) + $(TR $(TD Box Drawing) $(TD Ideographic Description Characters) $(TD Rumi Numeral Symbols)) + $(TR $(TD Brahmi) $(TD Imperial Aramaic) $(TD Runic)) + $(TR $(TD Braille Patterns) $(TD Inscriptional Pahlavi) $(TD Samaritan)) + $(TR $(TD Buginese) $(TD Inscriptional Parthian) $(TD Saurashtra)) + $(TR $(TD Buhid) $(TD IPA Extensions) $(TD Sharada)) + $(TR $(TD Byzantine Musical Symbols) $(TD Javanese) $(TD Shavian)) + $(TR $(TD Carian) $(TD Kaithi) $(TD Sinhala)) + $(TR $(TD Chakma) $(TD Kana Supplement) $(TD Small Form Variants)) + $(TR $(TD Cham) $(TD Kanbun) $(TD Sora Sompeng)) + $(TR $(TD Cherokee) $(TD Kangxi Radicals) $(TD Spacing Modifier Letters)) + $(TR $(TD CJK Compatibility) $(TD Kannada) $(TD Specials)) + $(TR $(TD CJK Compatibility Forms) $(TD Katakana) $(TD Sundanese)) + $(TR $(TD CJK Compatibility Ideographs) $(TD Katakana Phonetic Extensions) $(TD Sundanese Supplement)) + $(TR $(TD CJK Compatibility Ideographs Supplement) $(TD Kayah Li) $(TD Superscripts and Subscripts)) + $(TR $(TD CJK Radicals Supplement) $(TD Kharoshthi) $(TD Supplemental Arrows-A)) + $(TR $(TD CJK Strokes) $(TD Khmer) $(TD Supplemental Arrows-B)) + $(TR $(TD CJK Symbols and Punctuation) $(TD Khmer Symbols) $(TD Supplemental Mathematical Operators)) + $(TR $(TD CJK Unified Ideographs) $(TD Lao) $(TD Supplemental Punctuation)) + $(TR $(TD CJK Unified Ideographs Extension A) $(TD Latin-1 Supplement) $(TD Supplementary Private Use Area-A)) + $(TR $(TD CJK Unified Ideographs Extension B) $(TD Latin Extended-A) $(TD Supplementary Private Use Area-B)) + $(TR $(TD CJK Unified Ideographs Extension C) $(TD Latin Extended Additional) $(TD Syloti Nagri)) + $(TR $(TD CJK Unified Ideographs Extension D) $(TD Latin Extended-B) $(TD Syriac)) + $(TR $(TD Combining Diacritical Marks) $(TD Latin Extended-C) $(TD Tagalog)) + $(TR $(TD Combining Diacritical Marks for Symbols) $(TD Latin Extended-D) $(TD Tagbanwa)) + $(TR $(TD Combining Diacritical Marks Supplement) $(TD Lepcha) $(TD Tags)) + $(TR $(TD Combining Half Marks) $(TD Letterlike Symbols) $(TD Tai Le)) + $(TR $(TD Common Indic Number Forms) $(TD Limbu) $(TD Tai Tham)) + $(TR $(TD Control Pictures) $(TD Linear B Ideograms) $(TD Tai Viet)) + $(TR $(TD Coptic) $(TD Linear B Syllabary) $(TD Tai Xuan Jing Symbols)) + $(TR $(TD Counting Rod Numerals) $(TD Lisu) $(TD Takri)) + $(TR $(TD Cuneiform) $(TD Low Surrogates) $(TD Tamil)) + $(TR $(TD Cuneiform Numbers and Punctuation) $(TD Lycian) $(TD Telugu)) + $(TR $(TD Currency Symbols) $(TD Lydian) $(TD Thaana)) + $(TR $(TD Cypriot Syllabary) $(TD Mahjong Tiles) $(TD Thai)) + $(TR $(TD Cyrillic) $(TD Malayalam) $(TD Tibetan)) + $(TR $(TD Cyrillic Extended-A) $(TD Mandaic) $(TD Tifinagh)) + $(TR $(TD Cyrillic Extended-B) $(TD Mathematical Alphanumeric Symbols) $(TD Transport And Map Symbols)) + $(TR $(TD Cyrillic Supplement) $(TD Mathematical Operators) $(TD Ugaritic)) + $(TR $(TD Deseret) $(TD Meetei Mayek) $(TD Unified Canadian Aboriginal Syllabics)) + $(TR $(TD Devanagari) $(TD Meetei Mayek Extensions) $(TD Unified Canadian Aboriginal Syllabics Extended)) + $(TR $(TD Devanagari Extended) $(TD Meroitic Cursive) $(TD Vai)) + $(TR $(TD Dingbats) $(TD Meroitic Hieroglyphs) $(TD Variation Selectors)) + $(TR $(TD Domino Tiles) $(TD Miao) $(TD Variation Selectors Supplement)) + $(TR $(TD Egyptian Hieroglyphs) $(TD Miscellaneous Mathematical Symbols-A) $(TD Vedic Extensions)) + $(TR $(TD Emoticons) $(TD Miscellaneous Mathematical Symbols-B) $(TD Vertical Forms)) + $(TR $(TD Enclosed Alphanumerics) $(TD Miscellaneous Symbols) $(TD Yijing Hexagram Symbols)) + $(TR $(TD Enclosed Alphanumeric Supplement) $(TD Miscellaneous Symbols and Arrows) $(TD Yi Radicals)) + $(TR $(TD Enclosed CJK Letters and Months) $(TD Miscellaneous Symbols And Pictographs) $(TD Yi Syllables)) + $(TR $(TD Enclosed Ideographic Supplement) $(TD Miscellaneous Technical) ) + $(TR $(TD Ethiopic) $(TD Modifier Tone Letters) ) + ) + $(P Below is the table with script names accepted by $(LREF unicode.script) + and by the shorthand version $(LREF unicode):) + $(BOOKTABLE $(B Scripts), + $(TR $(TD Arabic) $(TD Hanunoo) $(TD Old_Italic)) + $(TR $(TD Armenian) $(TD Hebrew) $(TD Old_Persian)) + $(TR $(TD Avestan) $(TD Hiragana) $(TD Old_South_Arabian)) + $(TR $(TD Balinese) $(TD Imperial_Aramaic) $(TD Old_Turkic)) + $(TR $(TD Bamum) $(TD Inherited) $(TD Oriya)) + $(TR $(TD Batak) $(TD Inscriptional_Pahlavi) $(TD Osmanya)) + $(TR $(TD Bengali) $(TD Inscriptional_Parthian) $(TD Phags_Pa)) + $(TR $(TD Bopomofo) $(TD Javanese) $(TD Phoenician)) + $(TR $(TD Brahmi) $(TD Kaithi) $(TD Rejang)) + $(TR $(TD Braille) $(TD Kannada) $(TD Runic)) + $(TR $(TD Buginese) $(TD Katakana) $(TD Samaritan)) + $(TR $(TD Buhid) $(TD Kayah_Li) $(TD Saurashtra)) + $(TR $(TD Canadian_Aboriginal) $(TD Kharoshthi) $(TD Sharada)) + $(TR $(TD Carian) $(TD Khmer) $(TD Shavian)) + $(TR $(TD Chakma) $(TD Lao) $(TD Sinhala)) + $(TR $(TD Cham) $(TD Latin) $(TD Sora_Sompeng)) + $(TR $(TD Cherokee) $(TD Lepcha) $(TD Sundanese)) + $(TR $(TD Common) $(TD Limbu) $(TD Syloti_Nagri)) + $(TR $(TD Coptic) $(TD Linear_B) $(TD Syriac)) + $(TR $(TD Cuneiform) $(TD Lisu) $(TD Tagalog)) + $(TR $(TD Cypriot) $(TD Lycian) $(TD Tagbanwa)) + $(TR $(TD Cyrillic) $(TD Lydian) $(TD Tai_Le)) + $(TR $(TD Deseret) $(TD Malayalam) $(TD Tai_Tham)) + $(TR $(TD Devanagari) $(TD Mandaic) $(TD Tai_Viet)) + $(TR $(TD Egyptian_Hieroglyphs) $(TD Meetei_Mayek) $(TD Takri)) + $(TR $(TD Ethiopic) $(TD Meroitic_Cursive) $(TD Tamil)) + $(TR $(TD Georgian) $(TD Meroitic_Hieroglyphs) $(TD Telugu)) + $(TR $(TD Glagolitic) $(TD Miao) $(TD Thaana)) + $(TR $(TD Gothic) $(TD Mongolian) $(TD Thai)) + $(TR $(TD Greek) $(TD Myanmar) $(TD Tibetan)) + $(TR $(TD Gujarati) $(TD New_Tai_Lue) $(TD Tifinagh)) + $(TR $(TD Gurmukhi) $(TD Nko) $(TD Ugaritic)) + $(TR $(TD Han) $(TD Ogham) $(TD Vai)) + $(TR $(TD Hangul) $(TD Ol_Chiki) $(TD Yi)) + ) + $(P Below is the table of names accepted by $(LREF unicode.hangulSyllableType).) + $(BOOKTABLE $(B Hangul syllable type), + $(TR $(TH Abb.) $(TH Long form)) + $(TR $(TD L) $(TD Leading_Jamo)) + $(TR $(TD LV) $(TD LV_Syllable)) + $(TR $(TD LVT) $(TD LVT_Syllable) ) + $(TR $(TD T) $(TD Trailing_Jamo)) + $(TR $(TD V) $(TD Vowel_Jamo)) + ) + References: + $(HTTP www.digitalmars.com/d/ascii-table.html, ASCII Table), + $(HTTP en.wikipedia.org/wiki/Unicode, Wikipedia), + $(HTTP www.unicode.org, The Unicode Consortium), + $(HTTP www.unicode.org/reports/tr15/, Unicode normalization forms), + $(HTTP www.unicode.org/reports/tr29/, Unicode text segmentation) + $(HTTP www.unicode.org/uni2book/ch05.pdf, + Unicode Implementation Guidelines) + $(HTTP www.unicode.org/uni2book/ch03.pdf, + Unicode Conformance) + Trademarks: + Unicode(tm) is a trademark of Unicode, Inc. + + Copyright: Copyright 2013 - + License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + Authors: Dmitry Olshansky + Source: $(PHOBOSSRC std/_uni.d) + Standards: $(HTTP www.unicode.org/versions/Unicode6.2.0/, Unicode v6.2) + +Macros: + +SECTION = <h3><a id="$1">$0</a></h3> +DEF = <div><a id="$1"><i>$0</i></a></div> +S_LINK = <a href="#$1">$+</a> +CODEPOINT = $(S_LINK Code point, code point) +CODEPOINTS = $(S_LINK Code point, code points) +CHARACTER = $(S_LINK Character, character) +CHARACTERS = $(S_LINK Character, characters) +CLUSTER = $(S_LINK Grapheme cluster, grapheme cluster) ++/ +module std.uni; + +import std.meta; // AliasSeq +import std.range.primitives; // back, ElementEncodingType, ElementType, empty, + // front, isForwardRange, isInputRange, isRandomAccessRange, popFront, put, + // save +import std.traits; // isConvertibleToString, isIntegral, isSomeChar, + // isSomeString, Unqual +import std.exception;// : enforce; +import core.memory; //: pureMalloc, pureRealloc, pureFree; +import core.exception; // : onOutOfMemoryError; +static import std.ascii; +// debug = std_uni; + +debug(std_uni) import std.stdio; // writefln, writeln + +private: + +version (unittest) +{ +private: + struct TestAliasedString + { + string get() @safe @nogc pure nothrow { return _s; } + alias get this; + @disable this(this); + string _s; + } + + bool testAliasedString(alias func, Args...)(string s, Args args) + { + import std.algorithm.comparison : equal; + auto a = func(TestAliasedString(s), args); + auto b = func(s, args); + static if (is(typeof(equal(a, b)))) + { + // For ranges, compare contents instead of object identity. + return equal(a, b); + } + else + { + return a == b; + } + } +} + +void copyBackwards(T,U)(T[] src, U[] dest) +{ + assert(src.length == dest.length); + for (size_t i=src.length; i-- > 0; ) + dest[i] = src[i]; +} + +void copyForward(T,U)(T[] src, U[] dest) +{ + assert(src.length == dest.length); + for (size_t i=0; i<src.length; i++) + dest[i] = src[i]; +} + +// TODO: update to reflect all major CPUs supporting unaligned reads +version (X86) + enum hasUnalignedReads = true; +else version (X86_64) + enum hasUnalignedReads = true; +else + enum hasUnalignedReads = false; // better be safe then sorry + +public enum dchar lineSep = '\u2028'; /// Constant $(CODEPOINT) (0x2028) - line separator. +public enum dchar paraSep = '\u2029'; /// Constant $(CODEPOINT) (0x2029) - paragraph separator. +public enum dchar nelSep = '\u0085'; /// Constant $(CODEPOINT) (0x0085) - next line. + +// test the intro example +@safe unittest +{ + import std.algorithm.searching : find; + // initialize code point sets using script/block or property name + // set contains code points from both scripts. + auto set = unicode("Cyrillic") | unicode("Armenian"); + // or simpler and statically-checked look + auto ascii = unicode.ASCII; + auto currency = unicode.Currency_Symbol; + + // easy set ops + auto a = set & ascii; + assert(a.empty); // as it has no intersection with ascii + a = set | ascii; + auto b = currency - a; // subtract all ASCII, Cyrillic and Armenian + + // some properties of code point sets + assert(b.length > 45); // 46 items in Unicode 6.1, even more in 6.2 + // testing presence of a code point in a set + // is just fine, it is O(logN) + assert(!b['$']); + assert(!b['\u058F']); // Armenian dram sign + assert(b['¥']); + + // building fast lookup tables, these guarantee O(1) complexity + // 1-level Trie lookup table essentially a huge bit-set ~262Kb + auto oneTrie = toTrie!1(b); + // 2-level far more compact but typically slightly slower + auto twoTrie = toTrie!2(b); + // 3-level even smaller, and a bit slower yet + auto threeTrie = toTrie!3(b); + assert(oneTrie['£']); + assert(twoTrie['£']); + assert(threeTrie['£']); + + // build the trie with the most sensible trie level + // and bind it as a functor + auto cyrillicOrArmenian = toDelegate(set); + auto balance = find!(cyrillicOrArmenian)("Hello ընկեր!"); + assert(balance == "ընկեր!"); + // compatible with bool delegate(dchar) + bool delegate(dchar) bindIt = cyrillicOrArmenian; + + // Normalization + string s = "Plain ascii (and not only), is always normalized!"; + assert(s is normalize(s));// is the same string + + string nonS = "A\u0308ffin"; // A ligature + auto nS = normalize(nonS); // to NFC, the W3C endorsed standard + assert(nS == "Äffin"); + assert(nS != nonS); + string composed = "Äffin"; + + assert(normalize!NFD(composed) == "A\u0308ffin"); + // to NFKD, compatibility decomposition useful for fuzzy matching/searching + assert(normalize!NFKD("2¹⁰") == "210"); +} + +enum lastDchar = 0x10FFFF; + +auto force(T, F)(F from) +if (isIntegral!T && !is(T == F)) +{ + assert(from <= T.max && from >= T.min); + return cast(T) from; +} + +auto force(T, F)(F from) +if (isBitPacked!T && !is(T == F)) +{ + assert(from <= 2^^bitSizeOf!T-1); + return T(cast(TypeOfBitPacked!T) from); +} + +auto force(T, F)(F from) +if (is(T == F)) +{ + return from; +} + +// repeat X times the bit-pattern in val assuming it's length is 'bits' +size_t replicateBits(size_t times, size_t bits)(size_t val) @safe pure nothrow @nogc +{ + static if (times == 1) + return val; + else static if (bits == 1) + { + static if (times == size_t.sizeof*8) + return val ? size_t.max : 0; + else + return val ? (1 << times)-1 : 0; + } + else static if (times % 2) + return (replicateBits!(times-1, bits)(val)<<bits) | val; + else + return replicateBits!(times/2, bits*2)((val << bits) | val); +} + +@safe pure nothrow @nogc unittest // for replicate +{ + import std.algorithm.iteration : sum, map; + import std.range : iota; + size_t m = 0b111; + size_t m2 = 0b01; + foreach (i; AliasSeq!(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) + { + assert(replicateBits!(i, 3)(m)+1 == (1<<(3*i))); + assert(replicateBits!(i, 2)(m2) == iota(0, i).map!"2^^(2*a)"().sum()); + } +} + +// multiple arrays squashed into one memory block +struct MultiArray(Types...) +{ + import std.range.primitives : isOutputRange; + this(size_t[] sizes...) @safe pure nothrow + { + assert(dim == sizes.length); + size_t full_size; + foreach (i, v; Types) + { + full_size += spaceFor!(bitSizeOf!v)(sizes[i]); + sz[i] = sizes[i]; + static if (i >= 1) + offsets[i] = offsets[i-1] + + spaceFor!(bitSizeOf!(Types[i-1]))(sizes[i-1]); + } + + storage = new size_t[full_size]; + } + + this(const(size_t)[] raw_offsets, + const(size_t)[] raw_sizes, const(size_t)[] data)const @safe pure nothrow @nogc + { + offsets[] = raw_offsets[]; + sz[] = raw_sizes[]; + storage = data; + } + + @property auto slice(size_t n)()inout pure nothrow @nogc + { + auto ptr = raw_ptr!n; + return packedArrayView!(Types[n])(ptr, sz[n]); + } + + @property auto ptr(size_t n)()inout pure nothrow @nogc + { + auto ptr = raw_ptr!n; + return inout(PackedPtr!(Types[n]))(ptr); + } + + template length(size_t n) + { + @property size_t length()const @safe pure nothrow @nogc{ return sz[n]; } + + @property void length(size_t new_size) + { + if (new_size > sz[n]) + {// extend + size_t delta = (new_size - sz[n]); + sz[n] += delta; + delta = spaceFor!(bitSizeOf!(Types[n]))(delta); + storage.length += delta;// extend space at end + // raw_slice!x must follow resize as it could be moved! + // next stmts move all data past this array, last-one-goes-first + static if (n != dim-1) + { + auto start = raw_ptr!(n+1); + // len includes delta + size_t len = (storage.ptr+storage.length-start); + + copyBackwards(start[0 .. len-delta], start[delta .. len]); + + start[0 .. delta] = 0; + // offsets are used for raw_slice, ptr etc. + foreach (i; n+1 .. dim) + offsets[i] += delta; + } + } + else if (new_size < sz[n]) + {// shrink + size_t delta = (sz[n] - new_size); + sz[n] -= delta; + delta = spaceFor!(bitSizeOf!(Types[n]))(delta); + // move all data past this array, forward direction + static if (n != dim-1) + { + auto start = raw_ptr!(n+1); + size_t len = (storage.ptr+storage.length-start); + copyForward(start[0 .. len-delta], start[delta .. len]); + + // adjust offsets last, they affect raw_slice + foreach (i; n+1 .. dim) + offsets[i] -= delta; + } + storage.length -= delta; + } + // else - NOP + } + } + + @property size_t bytes(size_t n=size_t.max)() const @safe + { + static if (n == size_t.max) + return storage.length*size_t.sizeof; + else static if (n != Types.length-1) + return (raw_ptr!(n+1)-raw_ptr!n)*size_t.sizeof; + else + return (storage.ptr+storage.length - raw_ptr!n)*size_t.sizeof; + } + + void store(OutRange)(scope OutRange sink) const + if (isOutputRange!(OutRange, char)) + { + import std.format : formattedWrite; + formattedWrite(sink, "[%( 0x%x, %)]", offsets[]); + formattedWrite(sink, ", [%( 0x%x, %)]", sz[]); + formattedWrite(sink, ", [%( 0x%x, %)]", storage); + } + +private: + import std.meta : staticMap; + @property auto raw_ptr(size_t n)()inout pure nothrow @nogc + { + static if (n == 0) + return storage.ptr; + else + { + return storage.ptr+offsets[n]; + } + } + enum dim = Types.length; + size_t[dim] offsets;// offset for level x + size_t[dim] sz;// size of level x + alias bitWidth = staticMap!(bitSizeOf, Types); + size_t[] storage; +} + +@system unittest +{ + import std.conv : text; + enum dg = (){ + // sizes are: + // lvl0: 3, lvl1 : 2, lvl2: 1 + auto m = MultiArray!(int, ubyte, int)(3,2,1); + + static void check(size_t k, T)(ref T m, int n) + { + foreach (i; 0 .. n) + assert(m.slice!(k)[i] == i+1, text("level:",i," : ",m.slice!(k)[0 .. n])); + } + + static void checkB(size_t k, T)(ref T m, int n) + { + foreach (i; 0 .. n) + assert(m.slice!(k)[i] == n-i, text("level:",i," : ",m.slice!(k)[0 .. n])); + } + + static void fill(size_t k, T)(ref T m, int n) + { + foreach (i; 0 .. n) + m.slice!(k)[i] = force!ubyte(i+1); + } + + static void fillB(size_t k, T)(ref T m, int n) + { + foreach (i; 0 .. n) + m.slice!(k)[i] = force!ubyte(n-i); + } + + m.length!1 = 100; + fill!1(m, 100); + check!1(m, 100); + + m.length!0 = 220; + fill!0(m, 220); + check!1(m, 100); + check!0(m, 220); + + m.length!2 = 17; + fillB!2(m, 17); + checkB!2(m, 17); + check!0(m, 220); + check!1(m, 100); + + m.length!2 = 33; + checkB!2(m, 17); + fillB!2(m, 33); + checkB!2(m, 33); + check!0(m, 220); + check!1(m, 100); + + m.length!1 = 195; + fillB!1(m, 195); + checkB!1(m, 195); + checkB!2(m, 33); + check!0(m, 220); + + auto marr = MultiArray!(BitPacked!(uint, 4), BitPacked!(uint, 6))(20, 10); + marr.length!0 = 15; + marr.length!1 = 30; + fill!1(marr, 30); + fill!0(marr, 15); + check!1(marr, 30); + check!0(marr, 15); + return 0; + }; + enum ct = dg(); + auto rt = dg(); +} + +@system unittest +{// more bitpacking tests + import std.conv : text; + + alias Bitty = + MultiArray!(BitPacked!(size_t, 3) + , BitPacked!(size_t, 4) + , BitPacked!(size_t, 3) + , BitPacked!(size_t, 6) + , bool); + alias fn1 = sliceBits!(13, 16); + alias fn2 = sliceBits!( 9, 13); + alias fn3 = sliceBits!( 6, 9); + alias fn4 = sliceBits!( 0, 6); + static void check(size_t lvl, MA)(ref MA arr){ + for (size_t i = 0; i< arr.length!lvl; i++) + assert(arr.slice!(lvl)[i] == i, text("Mismatch on lvl ", lvl, " idx ", i, " value: ", arr.slice!(lvl)[i])); + } + + static void fillIdx(size_t lvl, MA)(ref MA arr){ + for (size_t i = 0; i< arr.length!lvl; i++) + arr.slice!(lvl)[i] = i; + } + Bitty m1; + + m1.length!4 = 10; + m1.length!3 = 2^^6; + m1.length!2 = 2^^3; + m1.length!1 = 2^^4; + m1.length!0 = 2^^3; + + m1.length!4 = 2^^16; + + for (size_t i = 0; i< m1.length!4; i++) + m1.slice!(4)[i] = i % 2; + + fillIdx!1(m1); + check!1(m1); + fillIdx!2(m1); + check!2(m1); + fillIdx!3(m1); + check!3(m1); + fillIdx!0(m1); + check!0(m1); + check!3(m1); + check!2(m1); + check!1(m1); + for (size_t i=0; i < 2^^16; i++) + { + m1.slice!(4)[i] = i % 2; + m1.slice!(0)[fn1(i)] = fn1(i); + m1.slice!(1)[fn2(i)] = fn2(i); + m1.slice!(2)[fn3(i)] = fn3(i); + m1.slice!(3)[fn4(i)] = fn4(i); + } + for (size_t i=0; i < 2^^16; i++) + { + assert(m1.slice!(4)[i] == i % 2); + assert(m1.slice!(0)[fn1(i)] == fn1(i)); + assert(m1.slice!(1)[fn2(i)] == fn2(i)); + assert(m1.slice!(2)[fn3(i)] == fn3(i)); + assert(m1.slice!(3)[fn4(i)] == fn4(i)); + } +} + +size_t spaceFor(size_t _bits)(size_t new_len) @safe pure nothrow @nogc +{ + import std.math : nextPow2; + enum bits = _bits == 1 ? 1 : nextPow2(_bits - 1);// see PackedArrayView + static if (bits > 8*size_t.sizeof) + { + static assert(bits % (size_t.sizeof*8) == 0); + return new_len * bits/(8*size_t.sizeof); + } + else + { + enum factor = size_t.sizeof*8/bits; + return (new_len+factor-1)/factor; // rounded up + } +} + +template isBitPackableType(T) +{ + enum isBitPackableType = isBitPacked!T + || isIntegral!T || is(T == bool) || isSomeChar!T; +} + +//============================================================================ +template PackedArrayView(T) +if ((is(T dummy == BitPacked!(U, sz), U, size_t sz) + && isBitPackableType!U) || isBitPackableType!T) +{ + import std.math : nextPow2; + private enum bits = bitSizeOf!T; + alias PackedArrayView = PackedArrayViewImpl!(T, bits > 1 ? nextPow2(bits - 1) : 1); +} + +//unsafe and fast access to a chunk of RAM as if it contains packed values +template PackedPtr(T) +if ((is(T dummy == BitPacked!(U, sz), U, size_t sz) + && isBitPackableType!U) || isBitPackableType!T) +{ + import std.math : nextPow2; + private enum bits = bitSizeOf!T; + alias PackedPtr = PackedPtrImpl!(T, bits > 1 ? nextPow2(bits - 1) : 1); +} + +struct PackedPtrImpl(T, size_t bits) +{ +pure nothrow: + static assert(isPow2OrZero(bits)); + + this(inout(size_t)* ptr)inout @safe @nogc + { + origin = ptr; + } + + private T simpleIndex(size_t n) inout + { + immutable q = n / factor; + immutable r = n % factor; + return cast(T)((origin[q] >> bits*r) & mask); + } + + private void simpleWrite(TypeOfBitPacked!T val, size_t n) + in + { + static if (isIntegral!T) + assert(val <= mask); + } + body + { + immutable q = n / factor; + immutable r = n % factor; + immutable tgt_shift = bits*r; + immutable word = origin[q]; + origin[q] = (word & ~(mask << tgt_shift)) + | (cast(size_t) val << tgt_shift); + } + + static if (factor == bytesPerWord// can safely pack by byte + || factor == 1 // a whole word at a time + || ((factor == bytesPerWord/2 || factor == bytesPerWord/4) + && hasUnalignedReads)) // this needs unaligned reads + { + static if (factor == bytesPerWord) + alias U = ubyte; + else static if (factor == bytesPerWord/2) + alias U = ushort; + else static if (factor == bytesPerWord/4) + alias U = uint; + else static if (size_t.sizeof == 8 && factor == bytesPerWord/8) + alias U = ulong; + + T opIndex(size_t idx) inout + { + return __ctfe ? simpleIndex(idx) : + cast(inout(T))(cast(U*) origin)[idx]; + } + + static if (isBitPacked!T) // lack of user-defined implicit conversion + { + void opIndexAssign(T val, size_t idx) + { + return opIndexAssign(cast(TypeOfBitPacked!T) val, idx); + } + } + + void opIndexAssign(TypeOfBitPacked!T val, size_t idx) + { + if (__ctfe) + simpleWrite(val, idx); + else + (cast(U*) origin)[idx] = cast(U) val; + } + } + else + { + T opIndex(size_t n) inout + { + return simpleIndex(n); + } + + static if (isBitPacked!T) // lack of user-defined implicit conversion + { + void opIndexAssign(T val, size_t idx) + { + return opIndexAssign(cast(TypeOfBitPacked!T) val, idx); + } + } + + void opIndexAssign(TypeOfBitPacked!T val, size_t n) + { + return simpleWrite(val, n); + } + } + +private: + // factor - number of elements in one machine word + enum factor = size_t.sizeof*8/bits, mask = 2^^bits-1; + enum bytesPerWord = size_t.sizeof; + size_t* origin; +} + +// data is packed only by power of two sized packs per word, +// thus avoiding mul/div overhead at the cost of ultimate packing +// this construct doesn't own memory, only provides access, see MultiArray for usage +struct PackedArrayViewImpl(T, size_t bits) +{ +pure nothrow: + + this(inout(size_t)* origin, size_t offset, size_t items) inout @safe + { + ptr = inout(PackedPtr!(T))(origin); + ofs = offset; + limit = items; + } + + bool zeros(size_t s, size_t e) + in + { + assert(s <= e); + } + body + { + s += ofs; + e += ofs; + immutable pad_s = roundUp(s); + if ( s >= e) + { + foreach (i; s .. e) + if (ptr[i]) + return false; + return true; + } + immutable pad_e = roundDown(e); + size_t i; + for (i=s; i<pad_s; i++) + if (ptr[i]) + return false; + // all in between is x*factor elements + for (size_t j=i/factor; i<pad_e; i+=factor, j++) + if (ptr.origin[j]) + return false; + for (; i<e; i++) + if (ptr[i]) + return false; + return true; + } + + T opIndex(size_t idx) inout + in + { + assert(idx < limit); + } + body + { + return ptr[ofs + idx]; + } + + static if (isBitPacked!T) // lack of user-defined implicit conversion + { + void opIndexAssign(T val, size_t idx) + { + return opIndexAssign(cast(TypeOfBitPacked!T) val, idx); + } + } + + void opIndexAssign(TypeOfBitPacked!T val, size_t idx) + in + { + assert(idx < limit); + } + body + { + ptr[ofs + idx] = val; + } + + static if (isBitPacked!T) // lack of user-defined implicit conversions + { + void opSliceAssign(T val, size_t start, size_t end) + { + opSliceAssign(cast(TypeOfBitPacked!T) val, start, end); + } + } + + void opSliceAssign(TypeOfBitPacked!T val, size_t start, size_t end) + in + { + assert(start <= end); + assert(end <= limit); + } + body + { + // account for ofsetted view + start += ofs; + end += ofs; + // rounded to factor granularity + immutable pad_start = roundUp(start);// rounded up + if (pad_start >= end) //rounded up >= then end of slice + { + //nothing to gain, use per element assignment + foreach (i; start .. end) + ptr[i] = val; + return; + } + immutable pad_end = roundDown(end); // rounded down + size_t i; + for (i=start; i<pad_start; i++) + ptr[i] = val; + // all in between is x*factor elements + if (pad_start != pad_end) + { + immutable repval = replicateBits!(factor, bits)(val); + for (size_t j=i/factor; i<pad_end; i+=factor, j++) + ptr.origin[j] = repval;// so speed it up by factor + } + for (; i<end; i++) + ptr[i] = val; + } + + auto opSlice(size_t from, size_t to)inout + in + { + assert(from <= to); + assert(ofs + to <= limit); + } + body + { + return typeof(this)(ptr.origin, ofs + from, to - from); + } + + auto opSlice(){ return opSlice(0, length); } + + bool opEquals(T)(auto ref T arr) const + { + if (limit != arr.limit) + return false; + size_t s1 = ofs, s2 = arr.ofs; + size_t e1 = s1 + limit, e2 = s2 + limit; + if (s1 % factor == 0 && s2 % factor == 0 && length % factor == 0) + { + return ptr.origin[s1/factor .. e1/factor] + == arr.ptr.origin[s2/factor .. e2/factor]; + } + for (size_t i=0;i<limit; i++) + if (this[i] != arr[i]) + return false; + return true; + } + + @property size_t length()const{ return limit; } + +private: + auto roundUp()(size_t val){ return (val+factor-1)/factor*factor; } + auto roundDown()(size_t val){ return val/factor*factor; } + // factor - number of elements in one machine word + enum factor = size_t.sizeof*8/bits; + PackedPtr!(T) ptr; + size_t ofs, limit; +} + + +private struct SliceOverIndexed(T) +{ + enum assignableIndex = is(typeof((){ T.init[0] = Item.init; })); + enum assignableSlice = is(typeof((){ T.init[0 .. 0] = Item.init; })); + auto opIndex(size_t idx)const + in + { + assert(idx < to - from); + } + body + { + return (*arr)[from+idx]; + } + + static if (assignableIndex) + void opIndexAssign(Item val, size_t idx) + in + { + assert(idx < to - from); + } + body + { + (*arr)[from+idx] = val; + } + + auto opSlice(size_t a, size_t b) + { + return typeof(this)(from+a, from+b, arr); + } + + // static if (assignableSlice) + void opSliceAssign(T)(T val, size_t start, size_t end) + { + (*arr)[start+from .. end+from] = val; + } + + auto opSlice() + { + return typeof(this)(from, to, arr); + } + + @property size_t length()const { return to-from;} + + auto opDollar()const { return length; } + + @property bool empty()const { return from == to; } + + @property auto front()const { return (*arr)[from]; } + + static if (assignableIndex) + @property void front(Item val) { (*arr)[from] = val; } + + @property auto back()const { return (*arr)[to-1]; } + + static if (assignableIndex) + @property void back(Item val) { (*arr)[to-1] = val; } + + @property auto save() inout { return this; } + + void popFront() { from++; } + + void popBack() { to--; } + + bool opEquals(T)(auto ref T arr) const + { + if (arr.length != length) + return false; + for (size_t i=0; i <length; i++) + if (this[i] != arr[i]) + return false; + return true; + } +private: + alias Item = typeof(T.init[0]); + size_t from, to; + T* arr; +} + +static assert(isRandomAccessRange!(SliceOverIndexed!(int[]))); + +SliceOverIndexed!(const(T)) sliceOverIndexed(T)(size_t a, size_t b, const(T)* x) +if (is(Unqual!T == T)) +{ + return SliceOverIndexed!(const(T))(a, b, x); +} + +// BUG? inout is out of reach +//...SliceOverIndexed.arr only parameters or stack based variables can be inout +SliceOverIndexed!T sliceOverIndexed(T)(size_t a, size_t b, T* x) +if (is(Unqual!T == T)) +{ + return SliceOverIndexed!T(a, b, x); +} + +@system unittest +{ + int[] idxArray = [2, 3, 5, 8, 13]; + auto sliced = sliceOverIndexed(0, idxArray.length, &idxArray); + + assert(!sliced.empty); + assert(sliced.front == 2); + sliced.front = 1; + assert(sliced.front == 1); + assert(sliced.back == 13); + sliced.popFront(); + assert(sliced.front == 3); + assert(sliced.back == 13); + sliced.back = 11; + assert(sliced.back == 11); + sliced.popBack(); + + assert(sliced.front == 3); + assert(sliced[$-1] == 8); + sliced = sliced[]; + assert(sliced[0] == 3); + assert(sliced.back == 8); + sliced = sliced[1..$]; + assert(sliced.front == 5); + sliced = sliced[0..$-1]; + assert(sliced[$-1] == 5); + + int[] other = [2, 5]; + assert(sliced[] == sliceOverIndexed(1, 2, &other)); + sliceOverIndexed(0, 2, &idxArray)[0 .. 2] = -1; + assert(idxArray[0 .. 2] == [-1, -1]); + uint[] nullArr = null; + auto nullSlice = sliceOverIndexed(0, 0, &idxArray); + assert(nullSlice.empty); +} + +private auto packedArrayView(T)(inout(size_t)* ptr, size_t items) @trusted pure nothrow +{ + return inout(PackedArrayView!T)(ptr, 0, items); +} + + +//============================================================================ +// Partially unrolled binary search using Shar's method +//============================================================================ + +string genUnrolledSwitchSearch(size_t size) @safe pure nothrow +{ + import core.bitop : bsr; + import std.array : replace; + import std.conv : to; + assert(isPow2OrZero(size)); + string code = ` + import core.bitop : bsr; + auto power = bsr(m)+1; + switch (power){`; + size_t i = bsr(size); + foreach_reverse (val; 0 .. bsr(size)) + { + auto v = 2^^val; + code ~= ` + case pow: + if (pred(range[idx+m], needle)) + idx += m; + goto case; + `.replace("m", to!string(v)) + .replace("pow", to!string(i)); + i--; + } + code ~= ` + case 0: + if (pred(range[idx], needle)) + idx += 1; + goto default; + `; + code ~= ` + default: + }`; + return code; +} + +bool isPow2OrZero(size_t sz) @safe pure nothrow @nogc +{ + // See also: std.math.isPowerOf2() + return (sz & (sz-1)) == 0; +} + +size_t uniformLowerBound(alias pred, Range, T)(Range range, T needle) +if (is(T : ElementType!Range)) +{ + assert(isPow2OrZero(range.length)); + size_t idx = 0, m = range.length/2; + while (m != 0) + { + if (pred(range[idx+m], needle)) + idx += m; + m /= 2; + } + if (pred(range[idx], needle)) + idx += 1; + return idx; +} + +size_t switchUniformLowerBound(alias pred, Range, T)(Range range, T needle) +if (is(T : ElementType!Range)) +{ + assert(isPow2OrZero(range.length)); + size_t idx = 0, m = range.length/2; + enum max = 1 << 10; + while (m >= max) + { + if (pred(range[idx+m], needle)) + idx += m; + m /= 2; + } + mixin(genUnrolledSwitchSearch(max)); + return idx; +} + +template sharMethod(alias uniLowerBound) +{ + size_t sharMethod(alias _pred="a<b", Range, T)(Range range, T needle) + if (is(T : ElementType!Range)) + { + import std.functional : binaryFun; + import std.math : nextPow2, truncPow2; + alias pred = binaryFun!_pred; + if (range.length == 0) + return 0; + if (isPow2OrZero(range.length)) + return uniLowerBound!pred(range, needle); + size_t n = truncPow2(range.length); + if (pred(range[n-1], needle)) + {// search in another 2^^k area that fully covers the tail of range + size_t k = nextPow2(range.length - n + 1); + return range.length - k + uniLowerBound!pred(range[$-k..$], needle); + } + else + return uniLowerBound!pred(range[0 .. n], needle); + } +} + +alias sharLowerBound = sharMethod!uniformLowerBound; +alias sharSwitchLowerBound = sharMethod!switchUniformLowerBound; + +@safe unittest +{ + import std.array : array; + import std.range : assumeSorted, iota; + + auto stdLowerBound(T)(T[] range, T needle) + { + return assumeSorted(range).lowerBound(needle).length; + } + immutable MAX = 5*1173; + auto arr = array(iota(5, MAX, 5)); + assert(arr.length == MAX/5-1); + foreach (i; 0 .. MAX+5) + { + auto st = stdLowerBound(arr, i); + assert(st == sharLowerBound(arr, i)); + assert(st == sharSwitchLowerBound(arr, i)); + } + arr = []; + auto st = stdLowerBound(arr, 33); + assert(st == sharLowerBound(arr, 33)); + assert(st == sharSwitchLowerBound(arr, 33)); +} +//============================================================================ + +@safe +{ +// hope to see simillar stuff in public interface... once Allocators are out +//@@@BUG moveFront and friends? dunno, for now it's POD-only + +@trusted size_t genericReplace(Policy=void, T, Range) + (ref T dest, size_t from, size_t to, Range stuff) +{ + import std.algorithm.mutation : copy; + size_t delta = to - from; + size_t stuff_end = from+stuff.length; + if (stuff.length > delta) + {// replace increases length + delta = stuff.length - delta;// now, new is > old by delta + static if (is(Policy == void)) + dest.length = dest.length+delta;//@@@BUG lame @property + else + dest = Policy.realloc(dest, dest.length+delta); + copyBackwards(dest[to .. dest.length-delta], + dest[to+delta .. dest.length]); + copyForward(stuff, dest[from .. stuff_end]); + } + else if (stuff.length == delta) + { + copy(stuff, dest[from .. to]); + } + else + {// replace decreases length by delta + delta = delta - stuff.length; + copy(stuff, dest[from .. stuff_end]); + copyForward(dest[to .. dest.length], + dest[stuff_end .. dest.length-delta]); + static if (is(Policy == void)) + dest.length = dest.length - delta;//@@@BUG lame @property + else + dest = Policy.realloc(dest, dest.length-delta); + } + return stuff_end; +} + + +// Simple storage manipulation policy +@trusted private struct GcPolicy +{ + import std.traits : isDynamicArray; + + static T[] dup(T)(const T[] arr) + { + return arr.dup; + } + + static T[] alloc(T)(size_t size) + { + return new T[size]; + } + + static T[] realloc(T)(T[] arr, size_t sz) + { + arr.length = sz; + return arr; + } + + static void replaceImpl(T, Range)(ref T[] dest, size_t from, size_t to, Range stuff) + { + replaceInPlace(dest, from, to, stuff); + } + + static void append(T, V)(ref T[] arr, V value) + if (!isInputRange!V) + { + arr ~= force!T(value); + } + + static void append(T, V)(ref T[] arr, V value) + if (isInputRange!V) + { + insertInPlace(arr, arr.length, value); + } + + static void destroy(T)(ref T arr) + if (isDynamicArray!T && is(Unqual!T == T)) + { + debug + { + arr[] = cast(typeof(T.init[0]))(0xdead_beef); + } + arr = null; + } + + static void destroy(T)(ref T arr) + if (isDynamicArray!T && !is(Unqual!T == T)) + { + arr = null; + } +} + +// ditto +@trusted struct ReallocPolicy +{ + import std.range.primitives : hasLength; + + static T[] dup(T)(const T[] arr) + { + auto result = alloc!T(arr.length); + result[] = arr[]; + return result; + } + + static T[] alloc(T)(size_t size) + { + import core.stdc.stdlib : malloc; + import std.exception : enforce; + + import core.checkedint : mulu; + bool overflow; + size_t nbytes = mulu(size, T.sizeof, overflow); + if (overflow) assert(0); + + auto ptr = cast(T*) enforce(malloc(nbytes), "out of memory on C heap"); + return ptr[0 .. size]; + } + + static T[] realloc(T)(T[] arr, size_t size) + { + import core.stdc.stdlib : realloc; + import std.exception : enforce; + if (!size) + { + destroy(arr); + return null; + } + + import core.checkedint : mulu; + bool overflow; + size_t nbytes = mulu(size, T.sizeof, overflow); + if (overflow) assert(0); + + auto ptr = cast(T*) enforce(realloc(arr.ptr, nbytes), "out of memory on C heap"); + return ptr[0 .. size]; + } + + static void replaceImpl(T, Range)(ref T[] dest, size_t from, size_t to, Range stuff) + { + genericReplace!(ReallocPolicy)(dest, from, to, stuff); + } + + static void append(T, V)(ref T[] arr, V value) + if (!isInputRange!V) + { + if (arr.length == size_t.max) assert(0); + arr = realloc(arr, arr.length+1); + arr[$-1] = force!T(value); + } + + @safe unittest + { + int[] arr; + ReallocPolicy.append(arr, 3); + + import std.algorithm.comparison : equal; + assert(equal(arr, [3])); + } + + static void append(T, V)(ref T[] arr, V value) + if (isInputRange!V && hasLength!V) + { + import core.checkedint : addu; + bool overflow; + size_t nelems = addu(arr.length, value.length, overflow); + if (overflow) assert(0); + + arr = realloc(arr, nelems); + + import std.algorithm.mutation : copy; + copy(value, arr[$-value.length..$]); + } + + @safe unittest + { + int[] arr; + ReallocPolicy.append(arr, [1,2,3]); + + import std.algorithm.comparison : equal; + assert(equal(arr, [1,2,3])); + } + + static void destroy(T)(ref T[] arr) + { + import core.stdc.stdlib : free; + if (arr.ptr) + free(arr.ptr); + arr = null; + } +} + +//build hack +alias _RealArray = CowArray!ReallocPolicy; + +@safe unittest +{ + import std.algorithm.comparison : equal; + + with(ReallocPolicy) + { + bool test(T, U, V)(T orig, size_t from, size_t to, U toReplace, V result, + string file = __FILE__, size_t line = __LINE__) + { + { + replaceImpl(orig, from, to, toReplace); + scope(exit) destroy(orig); + if (!equal(orig, result)) + return false; + } + return true; + } + static T[] arr(T)(T[] args... ) + { + return dup(args); + } + + assert(test(arr([1, 2, 3, 4]), 0, 0, [5, 6, 7], [5, 6, 7, 1, 2, 3, 4])); + assert(test(arr([1, 2, 3, 4]), 0, 2, cast(int[])[], [3, 4])); + assert(test(arr([1, 2, 3, 4]), 0, 4, [5, 6, 7], [5, 6, 7])); + assert(test(arr([1, 2, 3, 4]), 0, 2, [5, 6, 7], [5, 6, 7, 3, 4])); + assert(test(arr([1, 2, 3, 4]), 2, 3, [5, 6, 7], [1, 2, 5, 6, 7, 4])); + } +} + +/** + Tests if T is some kind a set of code points. Intended for template constraints. +*/ +public template isCodepointSet(T) +{ + static if (is(T dummy == InversionList!(Args), Args...)) + enum isCodepointSet = true; + else + enum isCodepointSet = false; +} + +/** + Tests if $(D T) is a pair of integers that implicitly convert to $(D V). + The following code must compile for any pair $(D T): + --- + (T x){ V a = x[0]; V b = x[1];} + --- + The following must not compile: + --- + (T x){ V c = x[2];} + --- +*/ +public template isIntegralPair(T, V=uint) +{ + enum isIntegralPair = is(typeof((T x){ V a = x[0]; V b = x[1];})) + && !is(typeof((T x){ V c = x[2]; })); +} + + +/** + The recommended default type for set of $(CODEPOINTS). + For details, see the current implementation: $(LREF InversionList). +*/ +public alias CodepointSet = InversionList!GcPolicy; + + +//@@@BUG: std.typecons tuples depend on std.format to produce fields mixin +// which relies on std.uni.isGraphical and this chain blows up with Forward reference error +// hence below doesn't seem to work +// public alias CodepointInterval = Tuple!(uint, "a", uint, "b"); + +/** + The recommended type of $(REF Tuple, std,_typecons) + to represent [a, b$(RPAREN) intervals of $(CODEPOINTS). As used in $(LREF InversionList). + Any interval type should pass $(LREF isIntegralPair) trait. +*/ +public struct CodepointInterval +{ +pure: + uint[2] _tuple; + alias _tuple this; + +@safe pure nothrow @nogc: + + this(uint low, uint high) + { + _tuple[0] = low; + _tuple[1] = high; + } + bool opEquals(T)(T val) const + { + return this[0] == val[0] && this[1] == val[1]; + } + @property ref inout(uint) a() inout { return _tuple[0]; } + @property ref inout(uint) b() inout { return _tuple[1]; } +} + +/** + $(P + $(D InversionList) is a set of $(CODEPOINTS) + represented as an array of open-right [a, b$(RPAREN) + intervals (see $(LREF CodepointInterval) above). + The name comes from the way the representation reads left to right. + For instance a set of all values [10, 50$(RPAREN), [80, 90$(RPAREN), + plus a singular value 60 looks like this: + ) + --- + 10, 50, 60, 61, 80, 90 + --- + $(P + The way to read this is: start with negative meaning that all numbers + smaller then the next one are not present in this set (and positive + - the contrary). Then switch positive/negative after each + number passed from left to right. + ) + $(P This way negative spans until 10, then positive until 50, + then negative until 60, then positive until 61, and so on. + As seen this provides a space-efficient storage of highly redundant data + that comes in long runs. A description which Unicode $(CHARACTER) + properties fit nicely. The technique itself could be seen as a variation + on $(LINK2 https://en.wikipedia.org/wiki/Run-length_encoding, RLE encoding). + ) + + $(P Sets are value types (just like $(D int) is) thus they + are never aliased. + ) + Example: + --- + auto a = CodepointSet('a', 'z'+1); + auto b = CodepointSet('A', 'Z'+1); + auto c = a; + a = a | b; + assert(a == CodepointSet('A', 'Z'+1, 'a', 'z'+1)); + assert(a != c); + --- + $(P See also $(LREF unicode) for simpler construction of sets + from predefined ones. + ) + + $(P Memory usage is 8 bytes per each contiguous interval in a set. + The value semantics are achieved by using the + $(HTTP en.wikipedia.org/wiki/Copy-on-write, COW) technique + and thus it's $(RED not) safe to cast this type to $(D_KEYWORD shared). + ) + + Note: + $(P It's not recommended to rely on the template parameters + or the exact type of a current $(CODEPOINT) set in $(D std.uni). + The type and parameters may change when the standard + allocators design is finalized. + Use $(LREF isCodepointSet) with templates or just stick with the default + alias $(LREF CodepointSet) throughout the whole code base. + ) +*/ +@trusted public struct InversionList(SP=GcPolicy) +{ + import std.range : assumeSorted; + + /** + Construct from another code point set of any type. + */ + this(Set)(Set set) pure + if (isCodepointSet!Set) + { + uint[] arr; + foreach (v; set.byInterval) + { + arr ~= v.a; + arr ~= v.b; + } + data = CowArray!(SP).reuse(arr); + } + + /** + Construct a set from a forward range of code point intervals. + */ + this(Range)(Range intervals) pure + if (isForwardRange!Range && isIntegralPair!(ElementType!Range)) + { + uint[] arr; + foreach (v; intervals) + { + SP.append(arr, v.a); + SP.append(arr, v.b); + } + data = CowArray!(SP).reuse(arr); + sanitize(); //enforce invariant: sort intervals etc. + } + + //helper function that avoids sanity check to be CTFE-friendly + private static fromIntervals(Range)(Range intervals) pure + { + import std.algorithm.iteration : map; + import std.range : roundRobin; + auto flattened = roundRobin(intervals.save.map!"a[0]"(), + intervals.save.map!"a[1]"()); + InversionList set; + set.data = CowArray!(SP)(flattened); + return set; + } + //ditto untill sort is CTFE-able + private static fromIntervals()(uint[] intervals...) pure + in + { + import std.conv : text; + assert(intervals.length % 2 == 0, "Odd number of interval bounds [a, b)!"); + for (uint i = 0; i < intervals.length; i += 2) + { + auto a = intervals[i], b = intervals[i+1]; + assert(a < b, text("illegal interval [a, b): ", a, " > ", b)); + } + } + body + { + InversionList set; + set.data = CowArray!(SP)(intervals); + return set; + } + + /** + Construct a set from plain values of code point intervals. + */ + this()(uint[] intervals...) + in + { + import std.conv : text; + assert(intervals.length % 2 == 0, "Odd number of interval bounds [a, b)!"); + for (uint i = 0; i < intervals.length; i += 2) + { + auto a = intervals[i], b = intervals[i+1]; + assert(a < b, text("illegal interval [a, b): ", a, " > ", b)); + } + } + body + { + data = CowArray!(SP)(intervals); + sanitize(); //enforce invariant: sort intervals etc. + } + + /// + @safe unittest + { + import std.algorithm.comparison : equal; + + auto set = CodepointSet('a', 'z'+1, 'а', 'я'+1); + foreach (v; 'a'..'z'+1) + assert(set[v]); + // Cyrillic lowercase interval + foreach (v; 'а'..'я'+1) + assert(set[v]); + //specific order is not required, intervals may interesect + auto set2 = CodepointSet('а', 'я'+1, 'a', 'd', 'b', 'z'+1); + //the same end result + assert(set2.byInterval.equal(set.byInterval)); + } + + /** + Get range that spans all of the $(CODEPOINT) intervals in this $(LREF InversionList). + + Example: + ----------- + import std.algorithm.comparison : equal; + import std.typecons : tuple; + + auto set = CodepointSet('A', 'D'+1, 'a', 'd'+1); + + assert(set.byInterval.equal([tuple('A','E'), tuple('a','e')])); + ----------- + */ + @property auto byInterval() + { + return Intervals!(typeof(data))(data); + } + + /** + Tests the presence of code point $(D val) in this set. + */ + bool opIndex(uint val) const + { + // the <= ensures that searching in interval of [a, b) for 'a' you get .length == 1 + // return assumeSorted!((a,b) => a <= b)(data[]).lowerBound(val).length & 1; + return sharSwitchLowerBound!"a <= b"(data[], val) & 1; + } + + /// + @safe unittest + { + auto gothic = unicode.Gothic; + // Gothic letter ahsa + assert(gothic['\U00010330']); + // no ascii in Gothic obviously + assert(!gothic['$']); + } + + + // Linear scan for $(D ch). Useful only for small sets. + // TODO: + // used internally in std.regex + // should be properly exposed in a public API ? + package auto scanFor()(dchar ch) const + { + immutable len = data.length; + for (size_t i = 0; i < len; i++) + if (ch < data[i]) + return i & 1; + return 0; + } + + /// Number of $(CODEPOINTS) in this set + @property size_t length() + { + size_t sum = 0; + foreach (iv; byInterval) + { + sum += iv.b - iv.a; + } + return sum; + } + +// bootstrap full set operations from 4 primitives (suitable as a template mixin): +// addInterval, skipUpTo, dropUpTo & byInterval iteration +//============================================================================ +public: + /** + $(P Sets support natural syntax for set algebra, namely: ) + $(BOOKTABLE , + $(TR $(TH Operator) $(TH Math notation) $(TH Description) ) + $(TR $(TD &) $(TD a ∩ b) $(TD intersection) ) + $(TR $(TD |) $(TD a ∪ b) $(TD union) ) + $(TR $(TD -) $(TD a ∖ b) $(TD subtraction) ) + $(TR $(TD ~) $(TD a ~ b) $(TD symmetric set difference i.e. (a ∪ b) \ (a ∩ b)) ) + ) + */ + This opBinary(string op, U)(U rhs) + if (isCodepointSet!U || is(U:dchar)) + { + static if (op == "&" || op == "|" || op == "~") + {// symmetric ops thus can swap arguments to reuse r-value + static if (is(U:dchar)) + { + auto tmp = this; + mixin("tmp "~op~"= rhs; "); + return tmp; + } + else + { + static if (is(Unqual!U == U)) + { + // try hard to reuse r-value + mixin("rhs "~op~"= this;"); + return rhs; + } + else + { + auto tmp = this; + mixin("tmp "~op~"= rhs;"); + return tmp; + } + } + } + else static if (op == "-") // anti-symmetric + { + auto tmp = this; + tmp -= rhs; + return tmp; + } + else + static assert(0, "no operator "~op~" defined for Set"); + } + + /// + @safe unittest + { + import std.algorithm.comparison : equal; + import std.range : iota; + + auto lower = unicode.LowerCase; + auto upper = unicode.UpperCase; + auto ascii = unicode.ASCII; + + assert((lower & upper).empty); // no intersection + auto lowerASCII = lower & ascii; + assert(lowerASCII.byCodepoint.equal(iota('a', 'z'+1))); + // throw away all of the lowercase ASCII + assert((ascii - lower).length == 128 - 26); + + auto onlyOneOf = lower ~ ascii; + assert(!onlyOneOf['Δ']); // not ASCII and not lowercase + assert(onlyOneOf['$']); // ASCII and not lowercase + assert(!onlyOneOf['a']); // ASCII and lowercase + assert(onlyOneOf['я']); // not ASCII but lowercase + + // throw away all cased letters from ASCII + auto noLetters = ascii - (lower | upper); + assert(noLetters.length == 128 - 26*2); + } + + /// The 'op=' versions of the above overloaded operators. + ref This opOpAssign(string op, U)(U rhs) + if (isCodepointSet!U || is(U:dchar)) + { + static if (op == "|") // union + { + static if (is(U:dchar)) + { + this.addInterval(rhs, rhs+1); + return this; + } + else + return this.add(rhs); + } + else static if (op == "&") // intersection + return this.intersect(rhs);// overloaded + else static if (op == "-") // set difference + return this.sub(rhs);// overloaded + else static if (op == "~") // symmetric set difference + { + auto copy = this & rhs; + this |= rhs; + this -= copy; + return this; + } + else + static assert(0, "no operator "~op~" defined for Set"); + } + + /** + Tests the presence of codepoint $(D ch) in this set, + the same as $(LREF opIndex). + */ + bool opBinaryRight(string op: "in", U)(U ch) const + if (is(U : dchar)) + { + return this[ch]; + } + + /// + @safe unittest + { + assert('я' in unicode.Cyrillic); + assert(!('z' in unicode.Cyrillic)); + } + + + + /** + * Obtains a set that is the inversion of this set. + * + * See_Also: $(LREF inverted) + */ + auto opUnary(string op: "!")() + { + return this.inverted; + } + + /** + A range that spans each $(CODEPOINT) in this set. + */ + @property auto byCodepoint() + { + @trusted static struct CodepointRange + { + this(This set) + { + r = set.byInterval; + if (!r.empty) + cur = r.front.a; + } + + @property dchar front() const + { + return cast(dchar) cur; + } + + @property bool empty() const + { + return r.empty; + } + + void popFront() + { + cur++; + while (cur >= r.front.b) + { + r.popFront(); + if (r.empty) + break; + cur = r.front.a; + } + } + private: + uint cur; + typeof(This.init.byInterval) r; + } + + return CodepointRange(this); + } + + /// + @safe unittest + { + import std.algorithm.comparison : equal; + import std.range : iota; + + auto set = unicode.ASCII; + set.byCodepoint.equal(iota(0, 0x80)); + } + + /** + $(P Obtain textual representation of this set in from of + open-right intervals and feed it to $(D sink). + ) + $(P Used by various standard formatting facilities such as + $(REF formattedWrite, std,_format), $(REF write, std,_stdio), + $(REF writef, std,_stdio), $(REF to, std,_conv) and others. + ) + Example: + --- + import std.conv; + assert(unicode.ASCII.to!string == "[0..128$(RPAREN)"); + --- + */ + + private import std.format : FormatSpec; + + /*************************************** + * Obtain a textual representation of this InversionList + * in form of open-right intervals. + * + * The formatting flag is applied individually to each value, for example: + * $(LI $(B %s) and $(B %d) format the intervals as a [low .. high$(RPAREN) range of integrals) + * $(LI $(B %x) formats the intervals as a [low .. high$(RPAREN) range of lowercase hex characters) + * $(LI $(B %X) formats the intervals as a [low .. high$(RPAREN) range of uppercase hex characters) + */ + void toString(Writer)(scope Writer sink, + FormatSpec!char fmt) /* const */ + { + import std.format : formatValue; + auto range = byInterval; + if (range.empty) + return; + + while (1) + { + auto i = range.front; + range.popFront(); + + put(sink, "["); + formatValue(sink, i.a, fmt); + put(sink, ".."); + formatValue(sink, i.b, fmt); + put(sink, ")"); + if (range.empty) return; + put(sink, " "); + } + } + + /// + @safe unittest + { + import std.conv : to; + import std.format : format; + import std.uni : unicode; + + assert(unicode.Cyrillic.to!string == + "[1024..1157) [1159..1320) [7467..7468) [7544..7545) [11744..11776) [42560..42648) [42655..42656)"); + + // The specs '%s' and '%d' are equivalent to the to!string call above. + assert(format("%d", unicode.Cyrillic) == unicode.Cyrillic.to!string); + + assert(format("%#x", unicode.Cyrillic) == + "[0x400..0x485) [0x487..0x528) [0x1d2b..0x1d2c) [0x1d78..0x1d79) [0x2de0..0x2e00) " + ~"[0xa640..0xa698) [0xa69f..0xa6a0)"); + + assert(format("%#X", unicode.Cyrillic) == + "[0X400..0X485) [0X487..0X528) [0X1D2B..0X1D2C) [0X1D78..0X1D79) [0X2DE0..0X2E00) " + ~"[0XA640..0XA698) [0XA69F..0XA6A0)"); + } + + @safe unittest + { + import std.exception : assertThrown; + import std.format : format, FormatException; + assertThrown!FormatException(format("%a", unicode.ASCII)); + } + + + /** + Add an interval [a, b$(RPAREN) to this set. + */ + ref add()(uint a, uint b) + { + addInterval(a, b); + return this; + } + + /// + @safe unittest + { + CodepointSet someSet; + someSet.add('0', '5').add('A','Z'+1); + someSet.add('5', '9'+1); + assert(someSet['0']); + assert(someSet['5']); + assert(someSet['9']); + assert(someSet['Z']); + } + +private: + + package(std) // used from: std.regex.internal.parser + ref intersect(U)(U rhs) + if (isCodepointSet!U) + { + Marker mark; + foreach ( i; rhs.byInterval) + { + mark = this.dropUpTo(i.a, mark); + mark = this.skipUpTo(i.b, mark); + } + this.dropUpTo(uint.max, mark); + return this; + } + + ref intersect()(dchar ch) + { + foreach (i; byInterval) + if (i.a <= ch && ch < i.b) + return this = This.init.add(ch, ch+1); + this = This.init; + return this; + } + + @safe unittest + { + assert(unicode.Cyrillic.intersect('-').byInterval.empty); + } + + ref sub()(dchar ch) + { + return subChar(ch); + } + + // same as the above except that skip & drop parts are swapped + package(std) // used from: std.regex.internal.parser + ref sub(U)(U rhs) + if (isCodepointSet!U) + { + Marker mark; + foreach (i; rhs.byInterval) + { + mark = this.skipUpTo(i.a, mark); + mark = this.dropUpTo(i.b, mark); + } + return this; + } + + package(std) // used from: std.regex.internal.parse + ref add(U)(U rhs) + if (isCodepointSet!U) + { + Marker start; + foreach (i; rhs.byInterval) + { + start = addInterval(i.a, i.b, start); + } + return this; + } + +// end of mixin-able part +//============================================================================ +public: + /** + Obtains a set that is the inversion of this set. + + See the '!' $(LREF opUnary) for the same but using operators. + */ + @property auto inverted() + { + InversionList inversion = this; + if (inversion.data.length == 0) + { + inversion.addInterval(0, lastDchar+1); + return inversion; + } + if (inversion.data[0] != 0) + genericReplace(inversion.data, 0, 0, [0]); + else + genericReplace(inversion.data, 0, 1, cast(uint[]) null); + if (data[data.length-1] != lastDchar+1) + genericReplace(inversion.data, + inversion.data.length, inversion.data.length, [lastDchar+1]); + else + genericReplace(inversion.data, + inversion.data.length-1, inversion.data.length, cast(uint[]) null); + + return inversion; + } + + /// + @safe unittest + { + auto set = unicode.ASCII; + // union with the inverse gets all of the code points in the Unicode + assert((set | set.inverted).length == 0x110000); + // no intersection with the inverse + assert((set & set.inverted).empty); + } + + /** + Generates string with D source code of unary function with name of + $(D funcName) taking a single $(D dchar) argument. If $(D funcName) is empty + the code is adjusted to be a lambda function. + + The function generated tests if the $(CODEPOINT) passed + belongs to this set or not. The result is to be used with string mixin. + The intended usage area is aggressive optimization via meta programming + in parser generators and the like. + + Note: Use with care for relatively small or regular sets. It + could end up being slower then just using multi-staged tables. + + Example: + --- + import std.stdio; + + // construct set directly from [a, b$RPAREN intervals + auto set = CodepointSet(10, 12, 45, 65, 100, 200); + writeln(set); + writeln(set.toSourceCode("func")); + --- + + The above outputs something along the lines of: + --- + bool func(dchar ch) @safe pure nothrow @nogc + { + if (ch < 45) + { + if (ch == 10 || ch == 11) return true; + return false; + } + else if (ch < 65) return true; + else + { + if (ch < 100) return false; + if (ch < 200) return true; + return false; + } + } + --- + */ + string toSourceCode(string funcName="") + { + import std.algorithm.searching : countUntil; + import std.array : array; + import std.format : format; + enum maxBinary = 3; + static string linearScope(R)(R ivals, string indent) + { + string result = indent~"{\n"; + string deeper = indent~" "; + foreach (ival; ivals) + { + immutable span = ival[1] - ival[0]; + assert(span != 0); + if (span == 1) + { + result ~= format("%sif (ch == %s) return true;\n", deeper, ival[0]); + } + else if (span == 2) + { + result ~= format("%sif (ch == %s || ch == %s) return true;\n", + deeper, ival[0], ival[0]+1); + } + else + { + if (ival[0] != 0) // dchar is unsigned and < 0 is useless + result ~= format("%sif (ch < %s) return false;\n", deeper, ival[0]); + result ~= format("%sif (ch < %s) return true;\n", deeper, ival[1]); + } + } + result ~= format("%sreturn false;\n%s}\n", deeper, indent); // including empty range of intervals + return result; + } + + static string binaryScope(R)(R ivals, string indent) + { + // time to do unrolled comparisons? + if (ivals.length < maxBinary) + return linearScope(ivals, indent); + else + return bisect(ivals, ivals.length/2, indent); + } + + // not used yet if/elsebinary search is far better with DMD as of 2.061 + // and GDC is doing fine job either way + static string switchScope(R)(R ivals, string indent) + { + string result = indent~"switch (ch){\n"; + string deeper = indent~" "; + foreach (ival; ivals) + { + if (ival[0]+1 == ival[1]) + { + result ~= format("%scase %s: return true;\n", + deeper, ival[0]); + } + else + { + result ~= format("%scase %s: .. case %s: return true;\n", + deeper, ival[0], ival[1]-1); + } + } + result ~= deeper~"default: return false;\n"~indent~"}\n"; + return result; + } + + static string bisect(R)(R range, size_t idx, string indent) + { + string deeper = indent ~ " "; + // bisect on one [a, b) interval at idx + string result = indent~"{\n"; + // less branch, < a + result ~= format("%sif (ch < %s)\n%s", + deeper, range[idx][0], binaryScope(range[0 .. idx], deeper)); + // middle point, >= a && < b + result ~= format("%selse if (ch < %s) return true;\n", + deeper, range[idx][1]); + // greater or equal branch, >= b + result ~= format("%selse\n%s", + deeper, binaryScope(range[idx+1..$], deeper)); + return result~indent~"}\n"; + } + + string code = format("bool %s(dchar ch) @safe pure nothrow @nogc\n", + funcName.empty ? "function" : funcName); + auto range = byInterval.array(); + // special case first bisection to be on ASCII vs beyond + auto tillAscii = countUntil!"a[0] > 0x80"(range); + if (tillAscii <= 0) // everything is ASCII or nothing is ascii (-1 & 0) + code ~= binaryScope(range, ""); + else + code ~= bisect(range, tillAscii, ""); + return code; + } + + /** + True if this set doesn't contain any $(CODEPOINTS). + */ + @property bool empty() const + { + return data.length == 0; + } + + /// + @safe unittest + { + CodepointSet emptySet; + assert(emptySet.length == 0); + assert(emptySet.empty); + } + +private: + alias This = typeof(this); + alias Marker = size_t; + + // a random-access range of integral pairs + static struct Intervals(Range) + { + this(Range sp) + { + slice = sp; + start = 0; + end = sp.length; + } + + this(Range sp, size_t s, size_t e) + { + slice = sp; + start = s; + end = e; + } + + @property auto front()const + { + immutable a = slice[start]; + immutable b = slice[start+1]; + return CodepointInterval(a, b); + } + + //may break sorted property - but we need std.sort to access it + //hence package protection attribute + package @property void front(CodepointInterval val) + { + slice[start] = val.a; + slice[start+1] = val.b; + } + + @property auto back()const + { + immutable a = slice[end-2]; + immutable b = slice[end-1]; + return CodepointInterval(a, b); + } + + //ditto about package + package @property void back(CodepointInterval val) + { + slice[end-2] = val.a; + slice[end-1] = val.b; + } + + void popFront() + { + start += 2; + } + + void popBack() + { + end -= 2; + } + + auto opIndex(size_t idx) const + { + immutable a = slice[start+idx*2]; + immutable b = slice[start+idx*2+1]; + return CodepointInterval(a, b); + } + + //ditto about package + package void opIndexAssign(CodepointInterval val, size_t idx) + { + slice[start+idx*2] = val.a; + slice[start+idx*2+1] = val.b; + } + + auto opSlice(size_t s, size_t e) + { + return Intervals(slice, s*2+start, e*2+start); + } + + @property size_t length()const { return slice.length/2; } + + @property bool empty()const { return start == end; } + + @property auto save(){ return this; } + private: + size_t start, end; + Range slice; + } + + // called after construction from intervals + // to make sure invariants hold + void sanitize() + { + import std.algorithm.comparison : max; + import std.algorithm.mutation : SwapStrategy; + import std.algorithm.sorting : sort; + if (data.length == 0) + return; + alias Ival = CodepointInterval; + //intervals wrapper for a _range_ over packed array + auto ivals = Intervals!(typeof(data[]))(data[]); + //@@@BUG@@@ can't use "a.a < b.a" see issue 12265 + sort!((a,b) => a.a < b.a, SwapStrategy.stable)(ivals); + // what follows is a variation on stable remove + // differences: + // - predicate is binary, and is tested against + // the last kept element (at 'i'). + // - predicate mutates lhs (merges rhs into lhs) + size_t len = ivals.length; + size_t i = 0; + size_t j = 1; + while (j < len) + { + if (ivals[i].b >= ivals[j].a) + { + ivals[i] = Ival(ivals[i].a, max(ivals[i].b, ivals[j].b)); + j++; + } + else //unmergable + { + // check if there is a hole after merges + // (in the best case we do 0 writes to ivals) + if (j != i+1) + ivals[i+1] = ivals[j]; //copy over + i++; + j++; + } + } + len = i + 1; + for (size_t k=0; k + 1 < len; k++) + { + assert(ivals[k].a < ivals[k].b); + assert(ivals[k].b < ivals[k+1].a); + } + data.length = len * 2; + } + + // special case for normal InversionList + ref subChar(dchar ch) + { + auto mark = skipUpTo(ch); + if (mark != data.length + && data[mark] == ch && data[mark-1] == ch) + { + // it has split, meaning that ch happens to be in one of intervals + data[mark] = data[mark]+1; + } + return this; + } + + // + Marker addInterval(int a, int b, Marker hint=Marker.init) + in + { + assert(a <= b); + } + body + { + import std.range : assumeSorted, SearchPolicy; + auto range = assumeSorted(data[]); + size_t pos; + size_t a_idx = hint + range[hint..$].lowerBound!(SearchPolicy.gallop)(a).length; + if (a_idx == range.length) + { + // [---+++----++++----++++++] + // [ a b] + data.append(a, b); + return data.length-1; + } + size_t b_idx = range[a_idx .. range.length].lowerBound!(SearchPolicy.gallop)(b).length+a_idx; + uint[3] buf = void; + uint to_insert; + debug(std_uni) + { + writefln("a_idx=%d; b_idx=%d;", a_idx, b_idx); + } + if (b_idx == range.length) + { + // [-------++++++++----++++++-] + // [ s a b] + if (a_idx & 1)// a in positive + { + buf[0] = b; + to_insert = 1; + } + else// a in negative + { + buf[0] = a; + buf[1] = b; + to_insert = 2; + } + pos = genericReplace(data, a_idx, b_idx, buf[0 .. to_insert]); + return pos - 1; + } + + uint top = data[b_idx]; + + debug(std_uni) + { + writefln("a_idx=%d; b_idx=%d;", a_idx, b_idx); + writefln("a=%s; b=%s; top=%s;", a, b, top); + } + if (a_idx & 1) + {// a in positive + if (b_idx & 1)// b in positive + { + // [-------++++++++----++++++-] + // [ s a b ] + buf[0] = top; + to_insert = 1; + } + else // b in negative + { + // [-------++++++++----++++++-] + // [ s a b ] + if (top == b) + { + assert(b_idx+1 < data.length); + buf[0] = data[b_idx+1]; + pos = genericReplace(data, a_idx, b_idx+2, buf[0 .. 1]); + return pos - 1; + } + buf[0] = b; + buf[1] = top; + to_insert = 2; + } + } + else + { // a in negative + if (b_idx & 1) // b in positive + { + // [----------+++++----++++++-] + // [ a b ] + buf[0] = a; + buf[1] = top; + to_insert = 2; + } + else// b in negative + { + // [----------+++++----++++++-] + // [ a s b ] + if (top == b) + { + assert(b_idx+1 < data.length); + buf[0] = a; + buf[1] = data[b_idx+1]; + pos = genericReplace(data, a_idx, b_idx+2, buf[0 .. 2]); + return pos - 1; + } + buf[0] = a; + buf[1] = b; + buf[2] = top; + to_insert = 3; + } + } + pos = genericReplace(data, a_idx, b_idx+1, buf[0 .. to_insert]); + debug(std_uni) + { + writefln("marker idx: %d; length=%d", pos, data[pos], data.length); + writeln("inserting ", buf[0 .. to_insert]); + } + return pos - 1; + } + + // + Marker dropUpTo(uint a, Marker pos=Marker.init) + in + { + assert(pos % 2 == 0); // at start of interval + } + body + { + auto range = assumeSorted!"a <= b"(data[pos .. data.length]); + if (range.empty) + return pos; + size_t idx = pos; + idx += range.lowerBound(a).length; + + debug(std_uni) + { + writeln("dropUpTo full length=", data.length); + writeln(pos,"~~~", idx); + } + if (idx == data.length) + return genericReplace(data, pos, idx, cast(uint[])[]); + if (idx & 1) + { // a in positive + //[--+++----++++++----+++++++------...] + // |<---si s a t + genericReplace(data, pos, idx, [a]); + } + else + { // a in negative + //[--+++----++++++----+++++++-------+++...] + // |<---si s a t + genericReplace(data, pos, idx, cast(uint[])[]); + } + return pos; + } + + // + Marker skipUpTo(uint a, Marker pos=Marker.init) + out(result) + { + assert(result % 2 == 0);// always start of interval + //(may be 0-width after-split) + } + body + { + assert(data.length % 2 == 0); + auto range = assumeSorted!"a <= b"(data[pos .. data.length]); + size_t idx = pos+range.lowerBound(a).length; + + if (idx >= data.length) // could have Marker point to recently removed stuff + return data.length; + + if (idx & 1)// inside of interval, check for split + { + + immutable top = data[idx]; + if (top == a)// no need to split, it's end + return idx+1; + immutable start = data[idx-1]; + if (a == start) + return idx-1; + // split it up + genericReplace(data, idx, idx+1, [a, a, top]); + return idx+1; // avoid odd index + } + return idx; + } + + CowArray!SP data; +} + +@system unittest +{ + import std.conv : to; + assert(unicode.ASCII.to!string() == "[0..128)"); +} + +// pedantic version for ctfe, and aligned-access only architectures +@system private uint safeRead24(scope const ubyte* ptr, size_t idx) pure nothrow @nogc +{ + idx *= 3; + version (LittleEndian) + return ptr[idx] + (cast(uint) ptr[idx+1]<<8) + + (cast(uint) ptr[idx+2]<<16); + else + return (cast(uint) ptr[idx]<<16) + (cast(uint) ptr[idx+1]<<8) + + ptr[idx+2]; +} + +// ditto +@system private void safeWrite24(scope ubyte* ptr, uint val, size_t idx) pure nothrow @nogc +{ + idx *= 3; + version (LittleEndian) + { + ptr[idx] = val & 0xFF; + ptr[idx+1] = (val >> 8) & 0xFF; + ptr[idx+2] = (val >> 16) & 0xFF; + } + else + { + ptr[idx] = (val >> 16) & 0xFF; + ptr[idx+1] = (val >> 8) & 0xFF; + ptr[idx+2] = val & 0xFF; + } +} + +// unaligned x86-like read/write functions +@system private uint unalignedRead24(scope const ubyte* ptr, size_t idx) pure nothrow @nogc +{ + uint* src = cast(uint*)(ptr+3*idx); + version (LittleEndian) + return *src & 0xFF_FFFF; + else + return *src >> 8; +} + +// ditto +@system private void unalignedWrite24(scope ubyte* ptr, uint val, size_t idx) pure nothrow @nogc +{ + uint* dest = cast(uint*)(cast(ubyte*) ptr + 3*idx); + version (LittleEndian) + *dest = val | (*dest & 0xFF00_0000); + else + *dest = (val << 8) | (*dest & 0xFF); +} + +@system private uint read24(scope const ubyte* ptr, size_t idx) pure nothrow @nogc +{ + static if (hasUnalignedReads) + return __ctfe ? safeRead24(ptr, idx) : unalignedRead24(ptr, idx); + else + return safeRead24(ptr, idx); +} + +@system private void write24(scope ubyte* ptr, uint val, size_t idx) pure nothrow @nogc +{ + static if (hasUnalignedReads) + return __ctfe ? safeWrite24(ptr, val, idx) : unalignedWrite24(ptr, val, idx); + else + return safeWrite24(ptr, val, idx); +} + +struct CowArray(SP=GcPolicy) +{ + import std.range.primitives : hasLength; + + @safe: + static auto reuse(uint[] arr) + { + CowArray cow; + cow.data = arr; + SP.append(cow.data, 1); + assert(cow.refCount == 1); + assert(cow.length == arr.length); + return cow; + } + + this(Range)(Range range) + if (isInputRange!Range && hasLength!Range) + { + import std.algorithm.mutation : copy; + length = range.length; + copy(range, data[0..$-1]); + } + + this(Range)(Range range) + if (isForwardRange!Range && !hasLength!Range) + { + import std.algorithm.mutation : copy; + import std.range.primitives : walkLength; + immutable len = walkLength(range.save); + length = len; + copy(range, data[0..$-1]); + } + + this(this) + { + if (!empty) + { + refCount = refCount + 1; + } + } + + ~this() + { + if (!empty) + { + immutable cnt = refCount; + if (cnt == 1) + SP.destroy(data); + else + refCount = cnt - 1; + } + } + + // no ref-count for empty U24 array + @property bool empty() const { return data.length == 0; } + + // report one less then actual size + @property size_t length() const + { + return data.length ? data.length - 1 : 0; + } + + //+ an extra slot for ref-count + @property void length(size_t len) + { + import std.algorithm.comparison : min; + import std.algorithm.mutation : copy; + if (len == 0) + { + if (!empty) + freeThisReference(); + return; + } + immutable total = len + 1; // including ref-count + if (empty) + { + data = SP.alloc!uint(total); + refCount = 1; + return; + } + immutable cur_cnt = refCount; + if (cur_cnt != 1) // have more references to this memory + { + refCount = cur_cnt - 1; + auto new_data = SP.alloc!uint(total); + // take shrinking into account + auto to_copy = min(total, data.length) - 1; + copy(data[0 .. to_copy], new_data[0 .. to_copy]); + data = new_data; // before setting refCount! + refCount = 1; + } + else // 'this' is the only reference + { + // use the realloc (hopefully in-place operation) + data = SP.realloc(data, total); + refCount = 1; // setup a ref-count in the new end of the array + } + } + + alias opDollar = length; + + uint opIndex()(size_t idx)const + { + return data[idx]; + } + + void opIndexAssign(uint val, size_t idx) + { + auto cnt = refCount; + if (cnt != 1) + dupThisReference(cnt); + data[idx] = val; + } + + // + auto opSlice(size_t from, size_t to) + { + if (!empty) + { + auto cnt = refCount; + if (cnt != 1) + dupThisReference(cnt); + } + return data[from .. to]; + + } + + // + auto opSlice(size_t from, size_t to) const + { + return data[from .. to]; + } + + // length slices before the ref count + auto opSlice() + { + return opSlice(0, length); + } + + // ditto + auto opSlice() const + { + return opSlice(0, length); + } + + void append(Range)(Range range) + if (isInputRange!Range && hasLength!Range && is(ElementType!Range : uint)) + { + size_t nl = length + range.length; + length = nl; + copy(range, this[nl-range.length .. nl]); + } + + void append()(uint[] val...) + { + length = length + val.length; + data[$-val.length-1 .. $-1] = val[]; + } + + bool opEquals()(auto const ref CowArray rhs)const + { + if (empty ^ rhs.empty) + return false; // one is empty and the other isn't + return empty || data[0..$-1] == rhs.data[0..$-1]; + } + +private: + // ref-count is right after the data + @property uint refCount() const + { + return data[$-1]; + } + + @property void refCount(uint cnt) + { + data[$-1] = cnt; + } + + void freeThisReference() + { + immutable count = refCount; + if (count != 1) // have more references to this memory + { + // dec shared ref-count + refCount = count - 1; + data = []; + } + else + SP.destroy(data); + assert(!data.ptr); + } + + void dupThisReference(uint count) + in + { + assert(!empty && count != 1 && count == refCount); + } + body + { + import std.algorithm.mutation : copy; + // dec shared ref-count + refCount = count - 1; + // copy to the new chunk of RAM + auto new_data = SP.alloc!uint(data.length); + // bit-blit old stuff except the counter + copy(data[0..$-1], new_data[0..$-1]); + data = new_data; // before setting refCount! + refCount = 1; // so that this updates the right one + } + + uint[] data; +} + +@safe unittest// Uint24 tests +{ + import std.algorithm.comparison : equal; + import std.algorithm.mutation : copy; + import std.conv : text; + import std.range : iota, chain; + import std.range.primitives : isBidirectionalRange, isOutputRange; + void funcRef(T)(ref T u24) + { + u24.length = 2; + u24[1] = 1024; + T u24_c = u24; + assert(u24[1] == 1024); + u24.length = 0; + assert(u24.empty); + u24.append([1, 2]); + assert(equal(u24[], [1, 2])); + u24.append(111); + assert(equal(u24[], [1, 2, 111])); + assert(!u24_c.empty && u24_c[1] == 1024); + u24.length = 3; + copy(iota(0, 3), u24[]); + assert(equal(u24[], iota(0, 3))); + assert(u24_c[1] == 1024); + } + + void func2(T)(T u24) + { + T u24_2 = u24; + T u24_3; + u24_3 = u24_2; + assert(u24_2 == u24_3); + assert(equal(u24[], u24_2[])); + assert(equal(u24_2[], u24_3[])); + funcRef(u24_3); + + assert(equal(u24_3[], iota(0, 3))); + assert(!equal(u24_2[], u24_3[])); + assert(equal(u24_2[], u24[])); + u24_2 = u24_3; + assert(equal(u24_2[], iota(0, 3))); + // to test that passed arg is intact outside + // plus try out opEquals + u24 = u24_3; + u24 = T.init; + u24_3 = T.init; + assert(u24.empty); + assert(u24 == u24_3); + assert(u24 != u24_2); + } + + foreach (Policy; AliasSeq!(GcPolicy, ReallocPolicy)) + { + alias Range = typeof(CowArray!Policy.init[]); + alias U24A = CowArray!Policy; + static assert(isForwardRange!Range); + static assert(isBidirectionalRange!Range); + static assert(isOutputRange!(Range, uint)); + static assert(isRandomAccessRange!(Range)); + + auto arr = U24A([42u, 36, 100]); + assert(arr[0] == 42); + assert(arr[1] == 36); + arr[0] = 72; + arr[1] = 0xFE_FEFE; + assert(arr[0] == 72); + assert(arr[1] == 0xFE_FEFE); + assert(arr[2] == 100); + U24A arr2 = arr; + assert(arr2[0] == 72); + arr2[0] = 11; + // test COW-ness + assert(arr[0] == 72); + assert(arr2[0] == 11); + // set this to about 100M to stress-test COW memory management + foreach (v; 0 .. 10_000) + func2(arr); + assert(equal(arr[], [72, 0xFE_FEFE, 100])); + + auto r2 = U24A(iota(0, 100)); + assert(equal(r2[], iota(0, 100)), text(r2[])); + copy(iota(10, 170, 2), r2[10 .. 90]); + assert(equal(r2[], chain(iota(0, 10), iota(10, 170, 2), iota(90, 100))) + , text(r2[])); + } +} + +version (unittest) +{ + private alias AllSets = AliasSeq!(InversionList!GcPolicy, InversionList!ReallocPolicy); +} + +@safe unittest// core set primitives test +{ + import std.conv : text; + foreach (CodeList; AllSets) + { + CodeList a; + //"plug a hole" test + a.add(10, 20).add(25, 30).add(15, 27); + assert(a == CodeList(10, 30), text(a)); + + auto x = CodeList.init; + x.add(10, 20).add(30, 40).add(50, 60); + + a = x; + a.add(20, 49);//[10, 49) [50, 60) + assert(a == CodeList(10, 49, 50 ,60)); + + a = x; + a.add(20, 50); + assert(a == CodeList(10, 60), text(a)); + + // simple unions, mostly edge effects + x = CodeList.init; + x.add(10, 20).add(40, 60); + + a = x; + a.add(10, 25); //[10, 25) [40, 60) + assert(a == CodeList(10, 25, 40, 60)); + + a = x; + a.add(5, 15); //[5, 20) [40, 60) + assert(a == CodeList(5, 20, 40, 60)); + + a = x; + a.add(0, 10); // [0, 20) [40, 60) + assert(a == CodeList(0, 20, 40, 60)); + + a = x; + a.add(0, 5); // prepand + assert(a == CodeList(0, 5, 10, 20, 40, 60), text(a)); + + a = x; + a.add(5, 20); + assert(a == CodeList(5, 20, 40, 60)); + + a = x; + a.add(3, 37); + assert(a == CodeList(3, 37, 40, 60)); + + a = x; + a.add(37, 65); + assert(a == CodeList(10, 20, 37, 65)); + + // some tests on helpers for set intersection + x = CodeList.init.add(10, 20).add(40, 60).add(100, 120); + a = x; + + auto m = a.skipUpTo(60); + a.dropUpTo(110, m); + assert(a == CodeList(10, 20, 40, 60, 110, 120), text(a.data[])); + + a = x; + a.dropUpTo(100); + assert(a == CodeList(100, 120), text(a.data[])); + + a = x; + m = a.skipUpTo(50); + a.dropUpTo(140, m); + assert(a == CodeList(10, 20, 40, 50), text(a.data[])); + a = x; + a.dropUpTo(60); + assert(a == CodeList(100, 120), text(a.data[])); + } +} + + +//test constructor to work with any order of intervals +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.conv : text, to; + import std.range : chain, iota; + import std.typecons : tuple; + //ensure constructor handles bad ordering and overlap + auto c1 = CodepointSet('а', 'я'+1, 'А','Я'+1); + foreach (ch; chain(iota('а', 'я'+1), iota('А','Я'+1))) + assert(ch in c1, to!string(ch)); + + //contiguos + assert(CodepointSet(1000, 1006, 1006, 1009) + .byInterval.equal([tuple(1000, 1009)])); + //contains + assert(CodepointSet(900, 1200, 1000, 1100) + .byInterval.equal([tuple(900, 1200)])); + //intersect left + assert(CodepointSet(900, 1100, 1000, 1200) + .byInterval.equal([tuple(900, 1200)])); + //intersect right + assert(CodepointSet(1000, 1200, 900, 1100) + .byInterval.equal([tuple(900, 1200)])); + + //ditto with extra items at end + assert(CodepointSet(1000, 1200, 900, 1100, 800, 850) + .byInterval.equal([tuple(800, 850), tuple(900, 1200)])); + assert(CodepointSet(900, 1100, 1000, 1200, 800, 850) + .byInterval.equal([tuple(800, 850), tuple(900, 1200)])); + + //"plug a hole" test + auto c2 = CodepointSet(20, 40, + 60, 80, 100, 140, 150, 200, + 40, 60, 80, 100, 140, 150 + ); + assert(c2.byInterval.equal([tuple(20, 200)])); + + auto c3 = CodepointSet( + 20, 40, 60, 80, 100, 140, 150, 200, + 0, 10, 15, 100, 10, 20, 200, 220); + assert(c3.byInterval.equal([tuple(0, 140), tuple(150, 220)])); +} + + +@safe unittest +{ // full set operations + import std.conv : text; + foreach (CodeList; AllSets) + { + CodeList a, b, c, d; + + //"plug a hole" + a.add(20, 40).add(60, 80).add(100, 140).add(150, 200); + b.add(40, 60).add(80, 100).add(140, 150); + c = a | b; + d = b | a; + assert(c == CodeList(20, 200), text(CodeList.stringof," ", c)); + assert(c == d, text(c," vs ", d)); + + b = CodeList.init.add(25, 45).add(65, 85).add(95,110).add(150, 210); + c = a | b; //[20,45) [60, 85) [95, 140) [150, 210) + d = b | a; + assert(c == CodeList(20, 45, 60, 85, 95, 140, 150, 210), text(c)); + assert(c == d, text(c," vs ", d)); + + b = CodeList.init.add(10, 20).add(30,100).add(145,200); + c = a | b;//[10, 140) [145, 200) + d = b | a; + assert(c == CodeList(10, 140, 145, 200)); + assert(c == d, text(c," vs ", d)); + + b = CodeList.init.add(0, 10).add(15, 100).add(10, 20).add(200, 220); + c = a | b;//[0, 140) [150, 220) + d = b | a; + assert(c == CodeList(0, 140, 150, 220)); + assert(c == d, text(c," vs ", d)); + + + a = CodeList.init.add(20, 40).add(60, 80); + b = CodeList.init.add(25, 35).add(65, 75); + c = a & b; + d = b & a; + assert(c == CodeList(25, 35, 65, 75), text(c)); + assert(c == d, text(c," vs ", d)); + + a = CodeList.init.add(20, 40).add(60, 80).add(100, 140).add(150, 200); + b = CodeList.init.add(25, 35).add(65, 75).add(110, 130).add(160, 180); + c = a & b; + d = b & a; + assert(c == CodeList(25, 35, 65, 75, 110, 130, 160, 180), text(c)); + assert(c == d, text(c," vs ", d)); + + a = CodeList.init.add(20, 40).add(60, 80).add(100, 140).add(150, 200); + b = CodeList.init.add(10, 30).add(60, 120).add(135, 160); + c = a & b;//[20, 30)[60, 80) [100, 120) [135, 140) [150, 160) + d = b & a; + + assert(c == CodeList(20, 30, 60, 80, 100, 120, 135, 140, 150, 160),text(c)); + assert(c == d, text(c, " vs ",d)); + assert((c & a) == c); + assert((d & b) == d); + assert((c & d) == d); + + b = CodeList.init.add(40, 60).add(80, 100).add(140, 200); + c = a & b; + d = b & a; + assert(c == CodeList(150, 200), text(c)); + assert(c == d, text(c, " vs ",d)); + assert((c & a) == c); + assert((d & b) == d); + assert((c & d) == d); + + assert((a & a) == a); + assert((b & b) == b); + + a = CodeList.init.add(20, 40).add(60, 80).add(100, 140).add(150, 200); + b = CodeList.init.add(30, 60).add(75, 120).add(190, 300); + c = a - b;// [30, 40) [60, 75) [120, 140) [150, 190) + d = b - a;// [40, 60) [80, 100) [200, 300) + assert(c == CodeList(20, 30, 60, 75, 120, 140, 150, 190), text(c)); + assert(d == CodeList(40, 60, 80, 100, 200, 300), text(d)); + assert(c - d == c, text(c-d, " vs ", c)); + assert(d - c == d, text(d-c, " vs ", d)); + assert(c - c == CodeList.init); + assert(d - d == CodeList.init); + + a = CodeList.init.add(20, 40).add( 60, 80).add(100, 140).add(150, 200); + b = CodeList.init.add(10, 50).add(60, 160).add(190, 300); + c = a - b;// [160, 190) + d = b - a;// [10, 20) [40, 50) [80, 100) [140, 150) [200, 300) + assert(c == CodeList(160, 190), text(c)); + assert(d == CodeList(10, 20, 40, 50, 80, 100, 140, 150, 200, 300), text(d)); + assert(c - d == c, text(c-d, " vs ", c)); + assert(d - c == d, text(d-c, " vs ", d)); + assert(c - c == CodeList.init); + assert(d - d == CodeList.init); + + a = CodeList.init.add(20, 40).add(60, 80).add(100, 140).add(150, 200); + b = CodeList.init.add(10, 30).add(45, 100).add(130, 190); + c = a ~ b; // [10, 20) [30, 40) [45, 60) [80, 130) [140, 150) [190, 200) + d = b ~ a; + assert(c == CodeList(10, 20, 30, 40, 45, 60, 80, 130, 140, 150, 190, 200), + text(c)); + assert(c == d, text(c, " vs ", d)); + } +} + +} + +@safe unittest// vs single dchar +{ + import std.conv : text; + CodepointSet a = CodepointSet(10, 100, 120, 200); + assert(a - 'A' == CodepointSet(10, 65, 66, 100, 120, 200), text(a - 'A')); + assert((a & 'B') == CodepointSet(66, 67)); +} + +@safe unittest// iteration & opIndex +{ + import std.algorithm.comparison : equal; + import std.conv : text; + import std.typecons : tuple, Tuple; + + foreach (CodeList; AliasSeq!(InversionList!(ReallocPolicy))) + { + auto arr = "ABCDEFGHIJKLMabcdefghijklm"d; + auto a = CodeList('A','N','a', 'n'); + assert(equal(a.byInterval, + [tuple(cast(uint)'A', cast(uint)'N'), tuple(cast(uint)'a', cast(uint)'n')] + ), text(a.byInterval)); + + // same @@@BUG as in issue 8949 ? + version (bug8949) + { + import std.range : retro; + assert(equal(retro(a.byInterval), + [tuple(cast(uint)'a', cast(uint)'n'), tuple(cast(uint)'A', cast(uint)'N')] + ), text(retro(a.byInterval))); + } + auto achr = a.byCodepoint; + assert(equal(achr, arr), text(a.byCodepoint)); + foreach (ch; a.byCodepoint) + assert(a[ch]); + auto x = CodeList(100, 500, 600, 900, 1200, 1500); + assert(equal(x.byInterval, [ tuple(100, 500), tuple(600, 900), tuple(1200, 1500)]), text(x.byInterval)); + foreach (ch; x.byCodepoint) + assert(x[ch]); + static if (is(CodeList == CodepointSet)) + { + auto y = CodeList(x.byInterval); + assert(equal(x.byInterval, y.byInterval)); + } + assert(equal(CodepointSet.init.byInterval, cast(Tuple!(uint, uint)[])[])); + assert(equal(CodepointSet.init.byCodepoint, cast(dchar[])[])); + } +} + +//============================================================================ +// Generic Trie template and various ways to build it +//============================================================================ + +// debug helper to get a shortened array dump +auto arrayRepr(T)(T x) +{ + import std.conv : text; + if (x.length > 32) + { + return text(x[0 .. 16],"~...~", x[x.length-16 .. x.length]); + } + else + return text(x); +} + +/** + Maps $(D Key) to a suitable integer index within the range of $(D size_t). + The mapping is constructed by applying predicates from $(D Prefix) left to right + and concatenating the resulting bits. + + The first (leftmost) predicate defines the most significant bits of + the resulting index. + */ +template mapTrieIndex(Prefix...) +{ + size_t mapTrieIndex(Key)(Key key) + if (isValidPrefixForTrie!(Key, Prefix)) + { + alias p = Prefix; + size_t idx; + foreach (i, v; p[0..$-1]) + { + idx |= p[i](key); + idx <<= p[i+1].bitSize; + } + idx |= p[$-1](key); + return idx; + } +} + +/* + $(D TrieBuilder) is a type used for incremental construction + of $(LREF Trie)s. + + See $(LREF buildTrie) for generic helpers built on top of it. +*/ +@trusted private struct TrieBuilder(Value, Key, Args...) +if (isBitPackableType!Value && isValidArgsForTrie!(Key, Args)) +{ + import std.exception : enforce; + +private: + // last index is not stored in table, it is used as an offset to values in a block. + static if (is(Value == bool))// always pack bool + alias V = BitPacked!(Value, 1); + else + alias V = Value; + static auto deduceMaxIndex(Preds...)() + { + size_t idx = 1; + foreach (v; Preds) + idx *= 2^^v.bitSize; + return idx; + } + + static if (is(typeof(Args[0]) : Key)) // Args start with upper bound on Key + { + alias Prefix = Args[1..$]; + enum lastPageSize = 2^^Prefix[$-1].bitSize; + enum translatedMaxIndex = mapTrieIndex!(Prefix)(Args[0]); + enum roughedMaxIndex = + (translatedMaxIndex + lastPageSize-1)/lastPageSize*lastPageSize; + // check warp around - if wrapped, use the default deduction rule + enum maxIndex = roughedMaxIndex < translatedMaxIndex ? + deduceMaxIndex!(Prefix)() : roughedMaxIndex; + } + else + { + alias Prefix = Args; + enum maxIndex = deduceMaxIndex!(Prefix)(); + } + + alias getIndex = mapTrieIndex!(Prefix); + + enum lastLevel = Prefix.length-1; + struct ConstructState + { + size_t idx_zeros, idx_ones; + } + // iteration over levels of Trie, each indexes its own level and thus a shortened domain + size_t[Prefix.length] indices; + // default filler value to use + Value defValue; + // this is a full-width index of next item + size_t curIndex; + // all-zeros page index, all-ones page index (+ indicator if there is such a page) + ConstructState[Prefix.length] state; + // the table being constructed + MultiArray!(idxTypes!(Key, fullBitSize!(Prefix), Prefix[0..$]), V) table; + + @disable this(); + + //shortcut for index variable at level 'level' + @property ref idx(size_t level)(){ return indices[level]; } + + // this function assumes no holes in the input so + // indices are going one by one + void addValue(size_t level, T)(T val, size_t numVals) + { + alias j = idx!level; + enum pageSize = 1 << Prefix[level].bitSize; + if (numVals == 0) + return; + auto ptr = table.slice!(level); + if (numVals == 1) + { + static if (level == Prefix.length-1) + ptr[j] = val; + else + {// can incur narrowing conversion + assert(j < ptr.length); + ptr[j] = force!(typeof(ptr[j]))(val); + } + j++; + if (j % pageSize == 0) + spillToNextPage!level(ptr); + return; + } + // longer row of values + // get to the next page boundary + immutable nextPB = (j + pageSize) & ~(pageSize-1); + immutable n = nextPB - j;// can fill right in this page + if (numVals < n) //fits in current page + { + ptr[j .. j+numVals] = val; + j += numVals; + return; + } + static if (level != 0)//on the first level it always fits + { + numVals -= n; + //write till the end of current page + ptr[j .. j+n] = val; + j += n; + //spill to the next page + spillToNextPage!level(ptr); + // page at once loop + if (state[level].idx_zeros != size_t.max && val == T.init) + { + alias NextIdx = typeof(table.slice!(level-1)[0]); + addValue!(level-1)(force!NextIdx(state[level].idx_zeros), + numVals/pageSize); + ptr = table.slice!level; //table structure might have changed + numVals %= pageSize; + } + else + { + while (numVals >= pageSize) + { + numVals -= pageSize; + ptr[j .. j+pageSize] = val; + j += pageSize; + spillToNextPage!level(ptr); + } + } + if (numVals) + { + // the leftovers, an incomplete page + ptr[j .. j+numVals] = val; + j += numVals; + } + } + } + + void spillToNextPage(size_t level, Slice)(ref Slice ptr) + { + // last level (i.e. topmost) has 1 "page" + // thus it need not to add a new page on upper level + static if (level != 0) + spillToNextPageImpl!(level)(ptr); + } + + // this can re-use the current page if duplicate or allocate a new one + // it also makes sure that previous levels point to the correct page in this level + void spillToNextPageImpl(size_t level, Slice)(ref Slice ptr) + { + alias NextIdx = typeof(table.slice!(level-1)[0]); + NextIdx next_lvl_index; + enum pageSize = 1 << Prefix[level].bitSize; + assert(idx!level % pageSize == 0); + immutable last = idx!level-pageSize; + const slice = ptr[idx!level - pageSize .. idx!level]; + size_t j; + for (j=0; j<last; j+=pageSize) + { + if (ptr[j .. j+pageSize] == slice) + { + // get index to it, reuse ptr space for the next block + next_lvl_index = force!NextIdx(j/pageSize); + version (none) + { + import std.stdio : writefln, writeln; + writefln("LEVEL(%s) page mapped idx: %s: 0..%s ---> [%s..%s]" + ,level + ,indices[level-1], pageSize, j, j+pageSize); + writeln("LEVEL(", level + , ") mapped page is: ", slice, ": ", arrayRepr(ptr[j .. j+pageSize])); + writeln("LEVEL(", level + , ") src page is :", ptr, ": ", arrayRepr(slice[0 .. pageSize])); + } + idx!level -= pageSize; // reuse this page, it is duplicate + break; + } + } + if (j == last) + { + L_allocate_page: + next_lvl_index = force!NextIdx(idx!level/pageSize - 1); + if (state[level].idx_zeros == size_t.max && ptr.zeros(j, j+pageSize)) + { + state[level].idx_zeros = next_lvl_index; + } + // allocate next page + version (none) + { + import std.stdio : writefln; + writefln("LEVEL(%s) page allocated: %s" + , level, arrayRepr(slice[0 .. pageSize])); + writefln("LEVEL(%s) index: %s ; page at this index %s" + , level + , next_lvl_index + , arrayRepr( + table.slice!(level) + [pageSize*next_lvl_index..(next_lvl_index+1)*pageSize] + )); + } + table.length!level = table.length!level + pageSize; + } + L_know_index: + // for the previous level, values are indices to the pages in the current level + addValue!(level-1)(next_lvl_index, 1); + ptr = table.slice!level; //re-load the slice after moves + } + + // idx - full-width index to fill with v (full-width index != key) + // fills everything in the range of [curIndex, idx) with filler + void putAt(size_t idx, Value v) + { + assert(idx >= curIndex); + immutable numFillers = idx - curIndex; + addValue!lastLevel(defValue, numFillers); + addValue!lastLevel(v, 1); + curIndex = idx + 1; + } + + // ditto, but sets the range of [idxA, idxB) to v + void putRangeAt(size_t idxA, size_t idxB, Value v) + { + assert(idxA >= curIndex); + assert(idxB >= idxA); + size_t numFillers = idxA - curIndex; + addValue!lastLevel(defValue, numFillers); + addValue!lastLevel(v, idxB - idxA); + curIndex = idxB; // open-right + } + + enum errMsg = "non-monotonic prefix function(s), an unsorted range or "~ + "duplicate key->value mapping"; + +public: + /** + Construct a builder, where $(D filler) is a value + to indicate empty slots (or "not found" condition). + */ + this(Value filler) + { + curIndex = 0; + defValue = filler; + // zeros-page index, ones-page index + foreach (ref v; state) + v = ConstructState(size_t.max, size_t.max); + table = typeof(table)(indices); + // one page per level is a bootstrap minimum + foreach (i, Pred; Prefix) + table.length!i = (1 << Pred.bitSize); + } + + /** + Put a value $(D v) into interval as + mapped by keys from $(D a) to $(D b). + All slots prior to $(D a) are filled with + the default filler. + */ + void putRange(Key a, Key b, Value v) + { + auto idxA = getIndex(a), idxB = getIndex(b); + // indexes of key should always grow + enforce(idxB >= idxA && idxA >= curIndex, errMsg); + putRangeAt(idxA, idxB, v); + } + + /** + Put a value $(D v) into slot mapped by $(D key). + All slots prior to $(D key) are filled with the + default filler. + */ + void putValue(Key key, Value v) + { + import std.conv : text; + auto idx = getIndex(key); + enforce(idx >= curIndex, text(errMsg, " ", idx)); + putAt(idx, v); + } + + /// Finishes construction of Trie, yielding an immutable Trie instance. + auto build() + { + static if (maxIndex != 0) // doesn't cover full range of size_t + { + assert(curIndex <= maxIndex); + addValue!lastLevel(defValue, maxIndex - curIndex); + } + else + { + if (curIndex != 0 // couldn't wrap around + || (Prefix.length != 1 && indices[lastLevel] == 0)) // can be just empty + { + addValue!lastLevel(defValue, size_t.max - curIndex); + addValue!lastLevel(defValue, 1); + } + // else curIndex already completed the full range of size_t by wrapping around + } + return Trie!(V, Key, maxIndex, Prefix)(table); + } +} + +/** + $(P A generic Trie data-structure for a fixed number of stages. + The design goal is optimal speed with smallest footprint size. + ) + $(P It's intentionally read-only and doesn't provide constructors. + To construct one use a special builder, + see $(LREF TrieBuilder) and $(LREF buildTrie). + ) + +*/ +@trusted private struct Trie(Value, Key, Args...) +if (isValidPrefixForTrie!(Key, Args) + || (isValidPrefixForTrie!(Key, Args[1..$]) + && is(typeof(Args[0]) : size_t))) +{ + import std.range.primitives : isOutputRange; + static if (is(typeof(Args[0]) : size_t)) + { + private enum maxIndex = Args[0]; + private enum hasBoundsCheck = true; + private alias Prefix = Args[1..$]; + } + else + { + private enum hasBoundsCheck = false; + private alias Prefix = Args; + } + + private this()(typeof(_table) table) + { + _table = table; + } + + // only for constant Tries constructed from precompiled tables + private this()(const(size_t)[] offsets, const(size_t)[] sizes, + const(size_t)[] data) const + { + _table = typeof(_table)(offsets, sizes, data); + } + + /** + $(P Lookup the $(D key) in this $(D Trie). ) + + $(P The lookup always succeeds if key fits the domain + provided during construction. The whole domain defined + is covered so instead of not found condition + the sentinel (filler) value could be used. ) + + $(P See $(LREF buildTrie), $(LREF TrieBuilder) for how to + define a domain of $(D Trie) keys and the sentinel value. ) + + Note: + Domain range-checking is only enabled in debug builds + and results in assertion failure. + */ + TypeOfBitPacked!Value opIndex()(Key key) const + { + static if (hasBoundsCheck) + assert(mapTrieIndex!Prefix(key) < maxIndex); + size_t idx; + alias p = Prefix; + idx = cast(size_t) p[0](key); + foreach (i, v; p[0..$-1]) + idx = cast(size_t)((_table.ptr!i[idx]<<p[i+1].bitSize) + p[i+1](key)); + return _table.ptr!(p.length-1)[idx]; + } + + /// + @property size_t bytes(size_t n=size_t.max)() const + { + return _table.bytes!n; + } + + /// + @property size_t pages(size_t n)() const + { + return (bytes!n+2^^(Prefix[n].bitSize-1)) + /2^^Prefix[n].bitSize; + } + + /// + void store(OutRange)(scope OutRange sink) const + if (isOutputRange!(OutRange, char)) + { + _table.store(sink); + } + +private: + MultiArray!(idxTypes!(Key, fullBitSize!(Prefix), Prefix[0..$]), Value) _table; +} + +// create a tuple of 'sliceBits' that slice the 'top' of bits into pieces of sizes 'sizes' +// left-to-right, the most significant bits first +template GetBitSlicing(size_t top, sizes...) +{ + static if (sizes.length > 0) + alias GetBitSlicing = + AliasSeq!(sliceBits!(top - sizes[0], top), + GetBitSlicing!(top - sizes[0], sizes[1..$])); + else + alias GetBitSlicing = AliasSeq!(); +} + +template callableWith(T) +{ + template callableWith(alias Pred) + { + static if (!is(typeof(Pred(T.init)))) + enum callableWith = false; + else + { + alias Result = typeof(Pred(T.init)); + enum callableWith = isBitPackableType!(TypeOfBitPacked!(Result)); + } + } +} + +/* + Check if $(D Prefix) is a valid set of predicates + for $(D Trie) template having $(D Key) as the type of keys. + This requires all predicates to be callable, take + single argument of type $(D Key) and return unsigned value. +*/ +template isValidPrefixForTrie(Key, Prefix...) +{ + import std.meta : allSatisfy; + enum isValidPrefixForTrie = allSatisfy!(callableWith!Key, Prefix); // TODO: tighten the screws +} + +/* + Check if $(D Args) is a set of maximum key value followed by valid predicates + for $(D Trie) template having $(D Key) as the type of keys. +*/ +template isValidArgsForTrie(Key, Args...) +{ + static if (Args.length > 1) + { + enum isValidArgsForTrie = isValidPrefixForTrie!(Key, Args) + || (isValidPrefixForTrie!(Key, Args[1..$]) && is(typeof(Args[0]) : Key)); + } + else + enum isValidArgsForTrie = isValidPrefixForTrie!Args; +} + +@property size_t sumOfIntegerTuple(ints...)() +{ + size_t count=0; + foreach (v; ints) + count += v; + return count; +} + +/** + A shorthand for creating a custom multi-level fixed Trie + from a $(D CodepointSet). $(D sizes) are numbers of bits per level, + with the most significant bits used first. + + Note: The sum of $(D sizes) must be equal 21. + + See_Also: $(LREF toTrie), which is even simpler. + + Example: + --- + { + import std.stdio; + auto set = unicode("Number"); + auto trie = codepointSetTrie!(8, 5, 8)(set); + writeln("Input code points to test:"); + foreach (line; stdin.byLine) + { + int count=0; + foreach (dchar ch; line) + if (trie[ch])// is number + count++; + writefln("Contains %d number code points.", count); + } + } + --- +*/ +public template codepointSetTrie(sizes...) +if (sumOfIntegerTuple!sizes == 21) +{ + auto codepointSetTrie(Set)(Set set) + if (isCodepointSet!Set) + { + auto builder = TrieBuilder!(bool, dchar, lastDchar+1, GetBitSlicing!(21, sizes))(false); + foreach (ival; set.byInterval) + builder.putRange(ival[0], ival[1], true); + return builder.build(); + } +} + +/// Type of Trie generated by codepointSetTrie function. +public template CodepointSetTrie(sizes...) +if (sumOfIntegerTuple!sizes == 21) +{ + alias Prefix = GetBitSlicing!(21, sizes); + alias CodepointSetTrie = typeof(TrieBuilder!(bool, dchar, lastDchar+1, Prefix)(false).build()); +} + +/** + A slightly more general tool for building fixed $(D Trie) + for the Unicode data. + + Specifically unlike $(D codepointSetTrie) it's allows creating mappings + of $(D dchar) to an arbitrary type $(D T). + + Note: Overload taking $(D CodepointSet)s will naturally convert + only to bool mapping $(D Trie)s. +*/ +public template codepointTrie(T, sizes...) +if (sumOfIntegerTuple!sizes == 21) +{ + alias Prefix = GetBitSlicing!(21, sizes); + + static if (is(TypeOfBitPacked!T == bool)) + { + auto codepointTrie(Set)(in Set set) + if (isCodepointSet!Set) + { + return codepointSetTrie(set); + } + } + + auto codepointTrie()(T[dchar] map, T defValue=T.init) + { + return buildTrie!(T, dchar, Prefix)(map, defValue); + } + + // unsorted range of pairs + auto codepointTrie(R)(R range, T defValue=T.init) + if (isInputRange!R + && is(typeof(ElementType!R.init[0]) : T) + && is(typeof(ElementType!R.init[1]) : dchar)) + { + // build from unsorted array of pairs + // TODO: expose index sorting functions for Trie + return buildTrie!(T, dchar, Prefix)(range, defValue, true); + } +} + +@system pure unittest +{ + import std.algorithm.comparison : max; + import std.algorithm.searching : count; + + // pick characters from the Greek script + auto set = unicode.Greek; + + // a user-defined property (or an expensive function) + // that we want to look up + static uint luckFactor(dchar ch) + { + // here we consider a character lucky + // if its code point has a lot of identical hex-digits + // e.g. arabic letter DDAL (\u0688) has a "luck factor" of 2 + ubyte[6] nibbles; // 6 4-bit chunks of code point + uint value = ch; + foreach (i; 0 .. 6) + { + nibbles[i] = value & 0xF; + value >>= 4; + } + uint luck; + foreach (n; nibbles) + luck = cast(uint) max(luck, count(nibbles[], n)); + return luck; + } + + // only unsigned built-ins are supported at the moment + alias LuckFactor = BitPacked!(uint, 3); + + // create a temporary associative array (AA) + LuckFactor[dchar] map; + foreach (ch; set.byCodepoint) + map[ch] = LuckFactor(luckFactor(ch)); + + // bits per stage are chosen randomly, fell free to optimize + auto trie = codepointTrie!(LuckFactor, 8, 5, 8)(map); + + // from now on the AA is not needed + foreach (ch; set.byCodepoint) + assert(trie[ch] == luckFactor(ch)); // verify + // CJK is not Greek, thus it has the default value + assert(trie['\u4444'] == 0); + // and here is a couple of quite lucky Greek characters: + // Greek small letter epsilon with dasia + assert(trie['\u1F11'] == 3); + // Ancient Greek metretes sign + assert(trie['\U00010181'] == 3); + +} + +/// Type of Trie as generated by codepointTrie function. +public template CodepointTrie(T, sizes...) +if (sumOfIntegerTuple!sizes == 21) +{ + alias Prefix = GetBitSlicing!(21, sizes); + alias CodepointTrie = typeof(TrieBuilder!(T, dchar, lastDchar+1, Prefix)(T.init).build()); +} + +package template cmpK0(alias Pred) +{ + import std.typecons : Tuple; + static bool cmpK0(Value, Key) + (Tuple!(Value, Key) a, Tuple!(Value, Key) b) + { + return Pred(a[1]) < Pred(b[1]); + } +} + +/** + The most general utility for construction of $(D Trie)s + short of using $(D TrieBuilder) directly. + + Provides a number of convenience overloads. + $(D Args) is tuple of maximum key value followed by + predicates to construct index from key. + + Alternatively if the first argument is not a value convertible to $(D Key) + then the whole tuple of $(D Args) is treated as predicates + and the maximum Key is deduced from predicates. +*/ +private template buildTrie(Value, Key, Args...) +if (isValidArgsForTrie!(Key, Args)) +{ + static if (is(typeof(Args[0]) : Key)) // prefix starts with upper bound on Key + { + alias Prefix = Args[1..$]; + } + else + alias Prefix = Args; + + alias getIndex = mapTrieIndex!(Prefix); + + // for multi-sort + template GetComparators(size_t n) + { + static if (n > 0) + alias GetComparators = + AliasSeq!(GetComparators!(n-1), cmpK0!(Prefix[n-1])); + else + alias GetComparators = AliasSeq!(); + } + + /* + Build $(D Trie) from a range of a Key-Value pairs, + assuming it is sorted by Key as defined by the following lambda: + ------ + (a, b) => mapTrieIndex!(Prefix)(a) < mapTrieIndex!(Prefix)(b) + ------ + Exception is thrown if it's detected that the above order doesn't hold. + + In other words $(LREF mapTrieIndex) should be a + monotonically increasing function that maps $(D Key) to an integer. + + See_Also: $(REF sort, std,_algorithm), + $(REF SortedRange, std,_range), + $(REF setUnion, std,_algorithm). + */ + auto buildTrie(Range)(Range range, Value filler=Value.init) + if (isInputRange!Range && is(typeof(Range.init.front[0]) : Value) + && is(typeof(Range.init.front[1]) : Key)) + { + auto builder = TrieBuilder!(Value, Key, Prefix)(filler); + foreach (v; range) + builder.putValue(v[1], v[0]); + return builder.build(); + } + + /* + If $(D Value) is bool (or BitPacked!(bool, x)) then it's possible + to build $(D Trie) from a range of open-right intervals of $(D Key)s. + The requirement on the ordering of keys (and the behavior on the + violation of it) is the same as for Key-Value range overload. + + Intervals denote ranges of !$(D filler) i.e. the opposite of filler. + If no filler provided keys inside of the intervals map to true, + and $(D filler) is false. + */ + auto buildTrie(Range)(Range range, Value filler=Value.init) + if (is(TypeOfBitPacked!Value == bool) + && isInputRange!Range && is(typeof(Range.init.front[0]) : Key) + && is(typeof(Range.init.front[1]) : Key)) + { + auto builder = TrieBuilder!(Value, Key, Prefix)(filler); + foreach (ival; range) + builder.putRange(ival[0], ival[1], !filler); + return builder.build(); + } + + auto buildTrie(Range)(Range range, Value filler, bool unsorted) + if (isInputRange!Range + && is(typeof(Range.init.front[0]) : Value) + && is(typeof(Range.init.front[1]) : Key)) + { + import std.algorithm.sorting : multiSort; + alias Comps = GetComparators!(Prefix.length); + if (unsorted) + multiSort!(Comps)(range); + return buildTrie(range, filler); + } + + /* + If $(D Value) is bool (or BitPacked!(bool, x)) then it's possible + to build $(D Trie) simply from an input range of $(D Key)s. + The requirement on the ordering of keys (and the behavior on the + violation of it) is the same as for Key-Value range overload. + + Keys found in range denote !$(D filler) i.e. the opposite of filler. + If no filler provided keys map to true, and $(D filler) is false. + */ + auto buildTrie(Range)(Range range, Value filler=Value.init) + if (is(TypeOfBitPacked!Value == bool) + && isInputRange!Range && is(typeof(Range.init.front) : Key)) + { + auto builder = TrieBuilder!(Value, Key, Prefix)(filler); + foreach (v; range) + builder.putValue(v, !filler); + return builder.build(); + } + + /* + If $(D Key) is unsigned integer $(D Trie) could be constructed from array + of values where array index serves as key. + */ + auto buildTrie()(Value[] array, Value filler=Value.init) + if (isUnsigned!Key) + { + auto builder = TrieBuilder!(Value, Key, Prefix)(filler); + foreach (idx, v; array) + builder.putValue(idx, v); + return builder.build(); + } + + /* + Builds $(D Trie) from associative array. + */ + auto buildTrie(Key, Value)(Value[Key] map, Value filler=Value.init) + { + import std.array : array; + import std.range : zip; + auto range = array(zip(map.values, map.keys)); + return buildTrie(range, filler, true); // sort it + } +} + +// helper in place of assumeSize to +//reduce mangled name & help DMD inline Trie functors +struct clamp(size_t bits) +{ + static size_t opCall(T)(T arg){ return arg; } + enum bitSize = bits; +} + +struct clampIdx(size_t idx, size_t bits) +{ + static size_t opCall(T)(T arg){ return arg[idx]; } + enum bitSize = bits; +} + +/** + Conceptual type that outlines the common properties of all UTF Matchers. + + Note: For illustration purposes only, every method + call results in assertion failure. + Use $(LREF utfMatcher) to obtain a concrete matcher + for UTF-8 or UTF-16 encodings. +*/ +public struct MatcherConcept +{ + /** + $(P Perform a semantic equivalent 2 operations: + decoding a $(CODEPOINT) at front of $(D inp) and testing if + it belongs to the set of $(CODEPOINTS) of this matcher. ) + + $(P The effect on $(D inp) depends on the kind of function called:) + + $(P Match. If the codepoint is found in the set then range $(D inp) + is advanced by its size in $(S_LINK Code unit, code units), + otherwise the range is not modifed.) + + $(P Skip. The range is always advanced by the size + of the tested $(CODEPOINT) regardless of the result of test.) + + $(P Test. The range is left unaffected regardless + of the result of test.) + */ + public bool match(Range)(ref Range inp) + if (isRandomAccessRange!Range && is(ElementType!Range : char)) + { + assert(false); + } + + ///ditto + public bool skip(Range)(ref Range inp) + if (isRandomAccessRange!Range && is(ElementType!Range : char)) + { + assert(false); + } + + ///ditto + public bool test(Range)(ref Range inp) + if (isRandomAccessRange!Range && is(ElementType!Range : char)) + { + assert(false); + } + /// + @safe unittest + { + string truth = "2² = 4"; + auto m = utfMatcher!char(unicode.Number); + assert(m.match(truth)); // '2' is a number all right + assert(truth == "² = 4"); // skips on match + assert(m.match(truth)); // so is the superscript '2' + assert(!m.match(truth)); // space is not a number + assert(truth == " = 4"); // unaffected on no match + assert(!m.skip(truth)); // same test ... + assert(truth == "= 4"); // but skips a codepoint regardless + assert(!m.test(truth)); // '=' is not a number + assert(truth == "= 4"); // test never affects argument + } + + /** + Advanced feature - provide direct access to a subset of matcher based a + set of known encoding lengths. Lengths are provided in + $(S_LINK Code unit, code units). The sub-matcher then may do less + operations per any $(D test)/$(D match). + + Use with care as the sub-matcher won't match + any $(CODEPOINTS) that have encoded length that doesn't belong + to the selected set of lengths. Also the sub-matcher object references + the parent matcher and must not be used past the liftetime + of the latter. + + Another caveat of using sub-matcher is that skip is not available + preciesly because sub-matcher doesn't detect all lengths. + */ + @property auto subMatcher(Lengths...)() + { + assert(0); + return this; + } + + @safe unittest + { + auto m = utfMatcher!char(unicode.Number); + string square = "2²"; + // about sub-matchers + assert(!m.subMatcher!(2,3,4).test(square)); // ASCII no covered + assert(m.subMatcher!1.match(square)); // ASCII-only, works + assert(!m.subMatcher!1.test(square)); // unicode '²' + assert(m.subMatcher!(2,3,4).match(square)); // + assert(square == ""); + wstring wsquare = "2²"; + auto m16 = utfMatcher!wchar(unicode.Number); + // may keep ref, but the orignal (m16) must be kept alive + auto bmp = m16.subMatcher!1; + assert(bmp.match(wsquare)); // Okay, in basic multilingual plan + assert(bmp.match(wsquare)); // And '²' too + } +} + +/** + Test if $(D M) is an UTF Matcher for ranges of $(D Char). +*/ +public enum isUtfMatcher(M, C) = __traits(compiles, (){ + C[] s; + auto d = s.decoder; + M m; + assert(is(typeof(m.match(d)) == bool)); + assert(is(typeof(m.test(d)) == bool)); + static if (is(typeof(m.skip(d)))) + { + assert(is(typeof(m.skip(d)) == bool)); + assert(is(typeof(m.skip(s)) == bool)); + } + assert(is(typeof(m.match(s)) == bool)); + assert(is(typeof(m.test(s)) == bool)); +}); + +@safe unittest +{ + alias CharMatcher = typeof(utfMatcher!char(CodepointSet.init)); + alias WcharMatcher = typeof(utfMatcher!wchar(CodepointSet.init)); + static assert(isUtfMatcher!(CharMatcher, char)); + static assert(isUtfMatcher!(CharMatcher, immutable(char))); + static assert(isUtfMatcher!(WcharMatcher, wchar)); + static assert(isUtfMatcher!(WcharMatcher, immutable(wchar))); +} + +enum Mode { + alwaysSkip, + neverSkip, + skipOnMatch +} + +mixin template ForwardStrings() +{ + private bool fwdStr(string fn, C)(ref C[] str) const pure + { + import std.utf : byCodeUnit; + alias type = typeof(byCodeUnit(str)); + return mixin(fn~"(*cast(type*)&str)"); + } +} + +template Utf8Matcher() +{ + enum validSize(int sz) = sz >= 1 && sz <= 4; + + void badEncoding() pure @safe + { + import std.utf : UTFException; + throw new UTFException("Invalid UTF-8 sequence"); + } + + //for 1-stage ASCII + alias AsciiSpec = AliasSeq!(bool, char, clamp!7); + //for 2-stage lookup of 2 byte UTF-8 sequences + alias Utf8Spec2 = AliasSeq!(bool, char[2], + clampIdx!(0, 5), clampIdx!(1, 6)); + //ditto for 3 byte + alias Utf8Spec3 = AliasSeq!(bool, char[3], + clampIdx!(0, 4), + clampIdx!(1, 6), + clampIdx!(2, 6) + ); + //ditto for 4 byte + alias Utf8Spec4 = AliasSeq!(bool, char[4], + clampIdx!(0, 3), clampIdx!(1, 6), + clampIdx!(2, 6), clampIdx!(3, 6) + ); + alias Tables = AliasSeq!( + typeof(TrieBuilder!(AsciiSpec)(false).build()), + typeof(TrieBuilder!(Utf8Spec2)(false).build()), + typeof(TrieBuilder!(Utf8Spec3)(false).build()), + typeof(TrieBuilder!(Utf8Spec4)(false).build()) + ); + alias Table(int size) = Tables[size-1]; + + enum leadMask(size_t size) = (cast(size_t) 1<<(7 - size))-1; + enum encMask(size_t size) = ((1 << size)-1)<<(8-size); + + char truncate()(char ch) pure @safe + { + ch -= 0x80; + if (ch < 0x40) + { + return ch; + } + else + { + badEncoding(); + return cast(char) 0; + } + } + + static auto encode(size_t sz)(dchar ch) + if (sz > 1) + { + import std.utf : encodeUTF = encode; + char[4] buf; + encodeUTF(buf, ch); + char[sz] ret; + buf[0] &= leadMask!sz; + foreach (n; 1 .. sz) + buf[n] = buf[n] & 0x3f; //keep 6 lower bits + ret[] = buf[0 .. sz]; + return ret; + } + + auto build(Set)(Set set) + { + import std.algorithm.iteration : map; + auto ascii = set & unicode.ASCII; + auto utf8_2 = set & CodepointSet(0x80, 0x800); + auto utf8_3 = set & CodepointSet(0x800, 0x1_0000); + auto utf8_4 = set & CodepointSet(0x1_0000, lastDchar+1); + auto asciiT = ascii.byCodepoint.map!(x=>cast(char) x).buildTrie!(AsciiSpec); + auto utf8_2T = utf8_2.byCodepoint.map!(x=>encode!2(x)).buildTrie!(Utf8Spec2); + auto utf8_3T = utf8_3.byCodepoint.map!(x=>encode!3(x)).buildTrie!(Utf8Spec3); + auto utf8_4T = utf8_4.byCodepoint.map!(x=>encode!4(x)).buildTrie!(Utf8Spec4); + alias Ret = Impl!(1,2,3,4); + return Ret(asciiT, utf8_2T, utf8_3T, utf8_4T); + } + + // Bootstrap UTF-8 static matcher interface + // from 3 primitives: tab!(size), lookup and Sizes + mixin template DefMatcher() + { + import std.format : format; + import std.meta : Erase, staticIndexOf; + enum hasASCII = staticIndexOf!(1, Sizes) >= 0; + alias UniSizes = Erase!(1, Sizes); + + //generate dispatch code sequence for unicode parts + static auto genDispatch() + { + string code; + foreach (size; UniSizes) + code ~= format(q{ + if ((ch & ~leadMask!%d) == encMask!(%d)) + return lookup!(%d, mode)(inp); + else + }, size, size, size); + static if (Sizes.length == 4) //covers all code unit cases + code ~= "{ badEncoding(); return false; }"; + else + code ~= "return false;"; //may be just fine but not covered + return code; + } + enum dispatch = genDispatch(); + + public bool match(Range)(ref Range inp) const pure + if (isRandomAccessRange!Range && is(ElementType!Range : char)) + { + enum mode = Mode.skipOnMatch; + assert(!inp.empty); + immutable ch = inp[0]; + static if (hasASCII) + { + if (ch < 0x80) + { + immutable r = tab!1[ch]; + if (r) + inp.popFront(); + return r; + } + else + mixin(dispatch); + } + else + mixin(dispatch); + } + + static if (Sizes.length == 4) // can skip iff can detect all encodings + { + public bool skip(Range)(ref Range inp) const pure @trusted + if (isRandomAccessRange!Range && is(ElementType!Range : char)) + { + enum mode = Mode.alwaysSkip; + assert(!inp.empty); + auto ch = inp[0]; + static if (hasASCII) + { + if (ch < 0x80) + { + inp.popFront(); + return tab!1[ch]; + } + else + mixin(dispatch); + } + else + mixin(dispatch); + } + } + + public bool test(Range)(ref Range inp) const pure @trusted + if (isRandomAccessRange!Range && is(ElementType!Range : char)) + { + enum mode = Mode.neverSkip; + assert(!inp.empty); + auto ch = inp[0]; + static if (hasASCII) + { + if (ch < 0x80) + return tab!1[ch]; + else + mixin(dispatch); + } + else + mixin(dispatch); + } + + bool match(C)(ref C[] str) const pure @trusted + if (isSomeChar!C) + { + return fwdStr!"match"(str); + } + + bool skip(C)(ref C[] str) const pure @trusted + if (isSomeChar!C) + { + return fwdStr!"skip"(str); + } + + bool test(C)(ref C[] str) const pure @trusted + if (isSomeChar!C) + { + return fwdStr!"test"(str); + } + + mixin ForwardStrings; + } + + struct Impl(Sizes...) + { + import std.meta : allSatisfy, staticMap; + static assert(allSatisfy!(validSize, Sizes), + "Only lengths of 1, 2, 3 and 4 code unit are possible for UTF-8"); + private: + //pick tables for chosen sizes + alias OurTabs = staticMap!(Table, Sizes); + OurTabs tables; + mixin DefMatcher; + //static disptach helper UTF size ==> table + alias tab(int i) = tables[i - 1]; + + package @property auto subMatcher(SizesToPick...)() @trusted + { + return CherryPick!(Impl, SizesToPick)(&this); + } + + bool lookup(int size, Mode mode, Range)(ref Range inp) const pure @trusted + { + import std.typecons : staticIota; + if (inp.length < size) + { + badEncoding(); + return false; + } + char[size] needle = void; + needle[0] = leadMask!size & inp[0]; + foreach (i; staticIota!(1, size)) + { + needle[i] = truncate(inp[i]); + } + //overlong encoding checks + static if (size == 2) + { + //0x80-0x7FF + //got 6 bits in needle[1], must use at least 8 bits + //must use at least 2 bits in needle[1] + if (needle[0] < 2) badEncoding(); + } + else static if (size == 3) + { + //0x800-0xFFFF + //got 6 bits in needle[2], must use at least 12bits + //must use 6 bits in needle[1] or anything in needle[0] + if (needle[0] == 0 && needle[1] < 0x20) badEncoding(); + } + else static if (size == 4) + { + //0x800-0xFFFF + //got 2x6=12 bits in needle[2 .. 3] must use at least 17bits + //must use 5 bits (or above) in needle[1] or anything in needle[0] + if (needle[0] == 0 && needle[1] < 0x10) badEncoding(); + } + static if (mode == Mode.alwaysSkip) + { + inp.popFrontN(size); + return tab!size[needle]; + } + else static if (mode == Mode.neverSkip) + { + return tab!size[needle]; + } + else + { + static assert(mode == Mode.skipOnMatch); + if (tab!size[needle]) + { + inp.popFrontN(size); + return true; + } + else + return false; + } + } + } + + struct CherryPick(I, Sizes...) + { + import std.meta : allSatisfy; + static assert(allSatisfy!(validSize, Sizes), + "Only lengths of 1, 2, 3 and 4 code unit are possible for UTF-8"); + private: + I* m; + @property ref tab(int i)() const pure { return m.tables[i - 1]; } + bool lookup(int size, Mode mode, Range)(ref Range inp) const pure + { + return m.lookup!(size, mode)(inp); + } + mixin DefMatcher; + } +} + +template Utf16Matcher() +{ + enum validSize(int sz) = sz >= 1 && sz <= 2; + + void badEncoding() pure + { + import std.utf : UTFException; + throw new UTFException("Invalid UTF-16 sequence"); + } + + // 1-stage ASCII + alias AsciiSpec = AliasSeq!(bool, wchar, clamp!7); + //2-stage BMP + alias BmpSpec = AliasSeq!(bool, wchar, sliceBits!(7, 16), sliceBits!(0, 7)); + //4-stage - full Unicode + //assume that 0xD800 & 0xDC00 bits are cleared + //thus leaving 10 bit per wchar to worry about + alias UniSpec = AliasSeq!(bool, wchar[2], + assumeSize!(x=>x[0]>>4, 6), assumeSize!(x=>x[0]&0xf, 4), + assumeSize!(x=>x[1]>>6, 4), assumeSize!(x=>x[1]&0x3f, 6), + ); + alias Ascii = typeof(TrieBuilder!(AsciiSpec)(false).build()); + alias Bmp = typeof(TrieBuilder!(BmpSpec)(false).build()); + alias Uni = typeof(TrieBuilder!(UniSpec)(false).build()); + + auto encode2(dchar ch) + { + ch -= 0x1_0000; + assert(ch <= 0xF_FFFF); + wchar[2] ret; + //do not put surrogate bits, they are sliced off + ret[0] = cast(wchar)(ch >> 10); + ret[1] = (ch & 0xFFF); + return ret; + } + + auto build(Set)(Set set) + { + import std.algorithm.iteration : map; + auto ascii = set & unicode.ASCII; + auto bmp = (set & CodepointSet.fromIntervals(0x80, 0xFFFF+1)) + - CodepointSet.fromIntervals(0xD800, 0xDFFF+1); + auto other = set - (bmp | ascii); + auto asciiT = ascii.byCodepoint.map!(x=>cast(char) x).buildTrie!(AsciiSpec); + auto bmpT = bmp.byCodepoint.map!(x=>cast(wchar) x).buildTrie!(BmpSpec); + auto otherT = other.byCodepoint.map!(x=>encode2(x)).buildTrie!(UniSpec); + alias Ret = Impl!(1,2); + return Ret(asciiT, bmpT, otherT); + } + + //bootstrap full UTF-16 matcher interace from + //sizeFlags, lookupUni and ascii + mixin template DefMatcher() + { + public bool match(Range)(ref Range inp) const pure @trusted + if (isRandomAccessRange!Range && is(ElementType!Range : wchar)) + { + enum mode = Mode.skipOnMatch; + assert(!inp.empty); + immutable ch = inp[0]; + static if (sizeFlags & 1) + { + if (ch < 0x80) + { + if (ascii[ch]) + { + inp.popFront(); + return true; + } + else + return false; + } + return lookupUni!mode(inp); + } + else + return lookupUni!mode(inp); + } + + static if (Sizes.length == 2) + { + public bool skip(Range)(ref Range inp) const pure @trusted + if (isRandomAccessRange!Range && is(ElementType!Range : wchar)) + { + enum mode = Mode.alwaysSkip; + assert(!inp.empty); + immutable ch = inp[0]; + static if (sizeFlags & 1) + { + if (ch < 0x80) + { + inp.popFront(); + return ascii[ch]; + } + else + return lookupUni!mode(inp); + } + else + return lookupUni!mode(inp); + } + } + + public bool test(Range)(ref Range inp) const pure @trusted + if (isRandomAccessRange!Range && is(ElementType!Range : wchar)) + { + enum mode = Mode.neverSkip; + assert(!inp.empty); + auto ch = inp[0]; + static if (sizeFlags & 1) + return ch < 0x80 ? ascii[ch] : lookupUni!mode(inp); + else + return lookupUni!mode(inp); + } + + bool match(C)(ref C[] str) const pure @trusted + if (isSomeChar!C) + { + return fwdStr!"match"(str); + } + + bool skip(C)(ref C[] str) const pure @trusted + if (isSomeChar!C) + { + return fwdStr!"skip"(str); + } + + bool test(C)(ref C[] str) const pure @trusted + if (isSomeChar!C) + { + return fwdStr!"test"(str); + } + + mixin ForwardStrings; //dispatch strings to range versions + } + + struct Impl(Sizes...) + if (Sizes.length >= 1 && Sizes.length <= 2) + { + private: + import std.meta : allSatisfy; + static assert(allSatisfy!(validSize, Sizes), + "Only lengths of 1 and 2 code units are possible in UTF-16"); + static if (Sizes.length > 1) + enum sizeFlags = Sizes[0] | Sizes[1]; + else + enum sizeFlags = Sizes[0]; + + static if (sizeFlags & 1) + { + Ascii ascii; + Bmp bmp; + } + static if (sizeFlags & 2) + { + Uni uni; + } + mixin DefMatcher; + + package @property auto subMatcher(SizesToPick...)() @trusted + { + return CherryPick!(Impl, SizesToPick)(&this); + } + + bool lookupUni(Mode mode, Range)(ref Range inp) const pure + { + wchar x = cast(wchar)(inp[0] - 0xD800); + //not a high surrogate + if (x > 0x3FF) + { + //low surrogate + if (x <= 0x7FF) badEncoding(); + static if (sizeFlags & 1) + { + auto ch = inp[0]; + static if (mode == Mode.alwaysSkip) + inp.popFront(); + static if (mode == Mode.skipOnMatch) + { + if (bmp[ch]) + { + inp.popFront(); + return true; + } + else + return false; + } + else + return bmp[ch]; + } + else //skip is not available for sub-matchers, so just false + return false; + } + else + { + static if (sizeFlags & 2) + { + if (inp.length < 2) + badEncoding(); + wchar y = cast(wchar)(inp[1] - 0xDC00); + //not a low surrogate + if (y > 0x3FF) + badEncoding(); + wchar[2] needle = [inp[0] & 0x3ff, inp[1] & 0x3ff]; + static if (mode == Mode.alwaysSkip) + inp.popFrontN(2); + static if (mode == Mode.skipOnMatch) + { + if (uni[needle]) + { + inp.popFrontN(2); + return true; + } + else + return false; + } + else + return uni[needle]; + } + else //ditto + return false; + } + } + } + + struct CherryPick(I, Sizes...) + if (Sizes.length >= 1 && Sizes.length <= 2) + { + private: + import std.meta : allSatisfy; + I* m; + enum sizeFlags = I.sizeFlags; + + static if (sizeFlags & 1) + { + @property ref ascii()() const pure{ return m.ascii; } + } + + bool lookupUni(Mode mode, Range)(ref Range inp) const pure + { + return m.lookupUni!mode(inp); + } + mixin DefMatcher; + static assert(allSatisfy!(validSize, Sizes), + "Only lengths of 1 and 2 code units are possible in UTF-16"); + } +} + +private auto utf8Matcher(Set)(Set set) @trusted +{ + return Utf8Matcher!().build(set); +} + +private auto utf16Matcher(Set)(Set set) @trusted +{ + return Utf16Matcher!().build(set); +} + +/** + Constructs a matcher object + to classify $(CODEPOINTS) from the $(D set) for encoding + that has $(D Char) as code unit. + + See $(LREF MatcherConcept) for API outline. +*/ +public auto utfMatcher(Char, Set)(Set set) @trusted +if (isCodepointSet!Set) +{ + static if (is(Char : char)) + return utf8Matcher(set); + else static if (is(Char : wchar)) + return utf16Matcher(set); + else static if (is(Char : dchar)) + static assert(false, "UTF-32 needs no decoding, + and thus not supported by utfMatcher"); + else + static assert(false, "Only character types 'char' and 'wchar' are allowed"); +} + + +//a range of code units, packed with index to speed up forward iteration +package auto decoder(C)(C[] s, size_t offset=0) @safe pure nothrow @nogc +if (is(C : wchar) || is(C : char)) +{ + static struct Decoder + { + pure nothrow: + C[] str; + size_t idx; + @property C front(){ return str[idx]; } + @property C back(){ return str[$-1]; } + void popFront(){ idx++; } + void popBack(){ str = str[0..$-1]; } + void popFrontN(size_t n){ idx += n; } + @property bool empty(){ return idx == str.length; } + @property auto save(){ return this; } + auto opIndex(size_t i){ return str[idx+i]; } + @property size_t length(){ return str.length - idx; } + alias opDollar = length; + auto opSlice(size_t a, size_t b){ return Decoder(str[0 .. idx+b], idx+a); } + } + static assert(isRandomAccessRange!Decoder); + static assert(is(ElementType!Decoder : C)); + return Decoder(s, offset); +} + +@safe unittest +{ + string rs = "hi! ネемног砀 текста"; + auto codec = rs.decoder; + auto utf8 = utf8Matcher(unicode.Letter); + auto asc = utf8.subMatcher!(1); + auto uni = utf8.subMatcher!(2,3,4); + assert(asc.test(codec)); + assert(!uni.match(codec)); + assert(utf8.skip(codec)); + assert(codec.idx == 1); + + assert(!uni.match(codec)); + assert(asc.test(codec)); + assert(utf8.skip(codec)); + assert(codec.idx == 2); + assert(!asc.match(codec)); + + assert(!utf8.test(codec)); + assert(!utf8.skip(codec)); + + assert(!asc.test(codec)); + assert(!utf8.test(codec)); + assert(!utf8.skip(codec)); + assert(utf8.test(codec)); + foreach (i; 0 .. 7) + { + assert(!asc.test(codec)); + assert(uni.test(codec)); + assert(utf8.skip(codec)); + } + assert(!utf8.test(codec)); + assert(!utf8.skip(codec)); + //the same with match where applicable + codec = rs.decoder; + assert(utf8.match(codec)); + assert(codec.idx == 1); + assert(utf8.match(codec)); + assert(codec.idx == 2); + assert(!utf8.match(codec)); + assert(codec.idx == 2); + assert(!utf8.skip(codec)); + assert(!utf8.skip(codec)); + + foreach (i; 0 .. 7) + { + assert(!asc.test(codec)); + assert(utf8.test(codec)); + assert(utf8.match(codec)); + } + auto i = codec.idx; + assert(!utf8.match(codec)); + assert(codec.idx == i); +} + +@safe unittest +{ + import std.range : stride; + static bool testAll(Matcher, Range)(ref Matcher m, ref Range r) + { + bool t = m.test(r); + auto save = r.idx; + assert(t == m.match(r)); + assert(r.idx == save || t); //ether no change or was match + r.idx = save; + static if (is(typeof(m.skip(r)))) + { + assert(t == m.skip(r)); + assert(r.idx != save); //always changed + r.idx = save; + } + return t; + } + auto utf16 = utfMatcher!wchar(unicode.L); + auto bmp = utf16.subMatcher!1; + auto nonBmp = utf16.subMatcher!1; + auto utf8 = utfMatcher!char(unicode.L); + auto ascii = utf8.subMatcher!1; + auto uni2 = utf8.subMatcher!2; + auto uni3 = utf8.subMatcher!3; + auto uni24 = utf8.subMatcher!(2,4); + foreach (ch; unicode.L.byCodepoint.stride(3)) + { + import std.utf : encode; + char[4] buf; + wchar[2] buf16; + auto len = encode(buf, ch); + auto len16 = encode(buf16, ch); + auto c8 = buf[0 .. len].decoder; + auto c16 = buf16[0 .. len16].decoder; + assert(testAll(utf16, c16)); + assert(testAll(bmp, c16) || len16 != 1); + assert(testAll(nonBmp, c16) || len16 != 2); + + assert(testAll(utf8, c8)); + + //submatchers return false on out of their domain + assert(testAll(ascii, c8) || len != 1); + assert(testAll(uni2, c8) || len != 2); + assert(testAll(uni3, c8) || len != 3); + assert(testAll(uni24, c8) || (len != 2 && len != 4)); + } +} + +// cover decode fail cases of Matcher +@system unittest +{ + import std.algorithm.iteration : map; + import std.exception : collectException; + import std.format : format; + auto utf16 = utfMatcher!wchar(unicode.L); + auto utf8 = utfMatcher!char(unicode.L); + //decode failure cases UTF-8 + alias fails8 = AliasSeq!("\xC1", "\x80\x00","\xC0\x00", "\xCF\x79", + "\xFF\x00\0x00\0x00\x00", "\xC0\0x80\0x80\x80", "\x80\0x00\0x00\x00", + "\xCF\x00\0x00\0x00\x00"); + foreach (msg; fails8) + { + assert(collectException((){ + auto s = msg; + size_t idx = 0; + utf8.test(s); + }()), format("%( %2x %)", cast(ubyte[]) msg)); + } + //decode failure cases UTF-16 + alias fails16 = AliasSeq!([0xD811], [0xDC02]); + foreach (msg; fails16) + { + assert(collectException((){ + auto s = msg.map!(x => cast(wchar) x); + utf16.test(s); + }())); + } +} + +/++ + Convenience function to construct optimal configurations for + packed Trie from any $(D set) of $(CODEPOINTS). + + The parameter $(D level) indicates the number of trie levels to use, + allowed values are: 1, 2, 3 or 4. Levels represent different trade-offs + speed-size wise. + + $(P Level 1 is fastest and the most memory hungry (a bit array). ) + $(P Level 4 is the slowest and has the smallest footprint. ) + + See the $(S_LINK Synopsis, Synopsis) section for example. + + Note: + Level 4 stays very practical (being faster and more predictable) + compared to using direct lookup on the $(D set) itself. + + ++/ +public auto toTrie(size_t level, Set)(Set set) +if (isCodepointSet!Set) +{ + static if (level == 1) + return codepointSetTrie!(21)(set); + else static if (level == 2) + return codepointSetTrie!(10, 11)(set); + else static if (level == 3) + return codepointSetTrie!(8, 5, 8)(set); + else static if (level == 4) + return codepointSetTrie!(6, 4, 4, 7)(set); + else + static assert(false, + "Sorry, toTrie doesn't support levels > 4, use codepointSetTrie directly"); +} + +/** + $(P Builds a $(D Trie) with typically optimal speed-size trade-off + and wraps it into a delegate of the following type: + $(D bool delegate(dchar ch)). ) + + $(P Effectively this creates a 'tester' lambda suitable + for algorithms like std.algorithm.find that take unary predicates. ) + + See the $(S_LINK Synopsis, Synopsis) section for example. +*/ +public auto toDelegate(Set)(Set set) +if (isCodepointSet!Set) +{ + // 3 is very small and is almost as fast as 2-level (due to CPU caches?) + auto t = toTrie!3(set); + return (dchar ch) => t[ch]; +} + +/** + $(P Opaque wrapper around unsigned built-in integers and + code unit (char/wchar/dchar) types. + Parameter $(D sz) indicates that the value is confined + to the range of [0, 2^^sz$(RPAREN). With this knowledge it can be + packed more tightly when stored in certain + data-structures like trie. ) + + Note: + $(P The $(D BitPacked!(T, sz)) is implicitly convertible to $(D T) + but not vise-versa. Users have to ensure the value fits in + the range required and use the $(D cast) + operator to perform the conversion.) +*/ +struct BitPacked(T, size_t sz) +if (isIntegral!T || is(T:dchar)) +{ + enum bitSize = sz; + T _value; + alias _value this; +} + +/* + Depending on the form of the passed argument $(D bitSizeOf) returns + the amount of bits required to represent a given type + or a return type of a given functor. +*/ +template bitSizeOf(Args...) +if (Args.length == 1) +{ + import std.traits : ReturnType; + alias T = Args[0]; + static if (__traits(compiles, { size_t val = T.bitSize; })) //(is(typeof(T.bitSize) : size_t)) + { + enum bitSizeOf = T.bitSize; + } + else static if (is(ReturnType!T dummy == BitPacked!(U, bits), U, size_t bits)) + { + enum bitSizeOf = bitSizeOf!(ReturnType!T); + } + else + { + enum bitSizeOf = T.sizeof*8; + } +} + +/** + Tests if $(D T) is some instantiation of $(LREF BitPacked)!(U, x) + and thus suitable for packing. +*/ +template isBitPacked(T) +{ + static if (is(T dummy == BitPacked!(U, bits), U, size_t bits)) + enum isBitPacked = true; + else + enum isBitPacked = false; +} + +/** + Gives the type $(D U) from $(LREF BitPacked)!(U, x) + or $(D T) itself for every other type. +*/ +template TypeOfBitPacked(T) +{ + static if (is(T dummy == BitPacked!(U, bits), U, size_t bits)) + alias TypeOfBitPacked = U; + else + alias TypeOfBitPacked = T; +} + +/* + Wrapper, used in definition of custom data structures from $(D Trie) template. + Applying it to a unary lambda function indicates that the returned value always + fits within $(D bits) of bits. +*/ +struct assumeSize(alias Fn, size_t bits) +{ + enum bitSize = bits; + static auto ref opCall(T)(auto ref T arg) + { + return Fn(arg); + } +} + +/* + A helper for defining lambda function that yields a slice + of certain bits from an unsigned integral value. + The resulting lambda is wrapped in assumeSize and can be used directly + with $(D Trie) template. +*/ +struct sliceBits(size_t from, size_t to) +{ + //for now bypass assumeSize, DMD has trouble inlining it + enum bitSize = to-from; + static auto opCall(T)(T x) + out(result) + { + assert(result < (1 << to-from)); + } + body + { + static assert(from < to); + static if (from == 0) + return x & ((1 << to)-1); + else + return (x >> from) & ((1<<(to-from))-1); + } +} + +@safe pure nothrow @nogc uint low_8(uint x) { return x&0xFF; } +@safe pure nothrow @nogc uint midlow_8(uint x){ return (x&0xFF00)>>8; } +alias lo8 = assumeSize!(low_8, 8); +alias mlo8 = assumeSize!(midlow_8, 8); + +static assert(bitSizeOf!lo8 == 8); +static assert(bitSizeOf!(sliceBits!(4, 7)) == 3); +static assert(bitSizeOf!(BitPacked!(uint, 2)) == 2); + +template Sequence(size_t start, size_t end) +{ + static if (start < end) + alias Sequence = AliasSeq!(start, Sequence!(start+1, end)); + else + alias Sequence = AliasSeq!(); +} + +//---- TRIE TESTS ---- +@system unittest +{ + import std.algorithm.iteration : map; + import std.algorithm.sorting : sort; + import std.array : array; + import std.conv : text, to; + import std.range : iota; + static trieStats(TRIE)(TRIE t) + { + version (std_uni_stats) + { + import std.stdio : writefln, writeln; + writeln("---TRIE FOOTPRINT STATS---"); + foreach (i; staticIota!(0, t.table.dim) ) + { + writefln("lvl%s = %s bytes; %s pages" + , i, t.bytes!i, t.pages!i); + } + writefln("TOTAL: %s bytes", t.bytes); + version (none) + { + writeln("INDEX (excluding value level):"); + foreach (i; staticIota!(0, t.table.dim-1) ) + writeln(t.table.slice!(i)[0 .. t.table.length!i]); + } + writeln("---------------------------"); + } + } + //@@@BUG link failure, lambdas not found by linker somehow (in case of trie2) + // alias lo8 = assumeSize!(8, function (uint x) { return x&0xFF; }); + // alias next8 = assumeSize!(7, function (uint x) { return (x&0x7F00)>>8; }); + alias Set = CodepointSet; + auto set = Set('A','Z','a','z'); + auto trie = buildTrie!(bool, uint, 256, lo8)(set.byInterval);// simple bool array + for (int a='a'; a<'z';a++) + assert(trie[a]); + for (int a='A'; a<'Z';a++) + assert(trie[a]); + for (int a=0; a<'A'; a++) + assert(!trie[a]); + for (int a ='Z'; a<'a'; a++) + assert(!trie[a]); + trieStats(trie); + + auto redundant2 = Set( + 1, 18, 256+2, 256+111, 512+1, 512+18, 768+2, 768+111); + auto trie2 = buildTrie!(bool, uint, 1024, mlo8, lo8)(redundant2.byInterval); + trieStats(trie2); + foreach (e; redundant2.byCodepoint) + assert(trie2[e], text(cast(uint) e, " - ", trie2[e])); + foreach (i; 0 .. 1024) + { + assert(trie2[i] == (i in redundant2)); + } + + + auto redundant3 = Set( + 2, 4, 6, 8, 16, + 2+16, 4+16, 16+6, 16+8, 16+16, + 2+32, 4+32, 32+6, 32+8, + ); + + enum max3 = 256; + // sliceBits + auto trie3 = buildTrie!(bool, uint, max3, + sliceBits!(6,8), sliceBits!(4,6), sliceBits!(0,4) + )(redundant3.byInterval); + trieStats(trie3); + foreach (i; 0 .. max3) + assert(trie3[i] == (i in redundant3), text(cast(uint) i)); + + auto redundant4 = Set( + 10, 64, 64+10, 128, 128+10, 256, 256+10, 512, + 1000, 2000, 3000, 4000, 5000, 6000 + ); + enum max4 = 2^^16; + auto trie4 = buildTrie!(bool, size_t, max4, + sliceBits!(13, 16), sliceBits!(9, 13), sliceBits!(6, 9) , sliceBits!(0, 6) + )(redundant4.byInterval); + foreach (i; 0 .. max4) + { + if (i in redundant4) + assert(trie4[i], text(cast(uint) i)); + } + trieStats(trie4); + + alias mapToS = mapTrieIndex!(useItemAt!(0, char)); + string[] redundantS = ["tea", "start", "orange"]; + redundantS.sort!((a,b) => mapToS(a) < mapToS(b))(); + auto strie = buildTrie!(bool, string, useItemAt!(0, char))(redundantS); + // using first char only + assert(redundantS == ["orange", "start", "tea"]); + assert(strie["test"], text(strie["test"])); + assert(!strie["aea"]); + assert(strie["s"]); + + // a bit size test + auto a = array(map!(x => to!ubyte(x))(iota(0, 256))); + auto bt = buildTrie!(bool, ubyte, sliceBits!(7, 8), sliceBits!(5, 7), sliceBits!(0, 5))(a); + trieStats(bt); + foreach (i; 0 .. 256) + assert(bt[cast(ubyte) i]); +} + +template useItemAt(size_t idx, T) +if (isIntegral!T || is(T: dchar)) +{ + size_t impl(in T[] arr){ return arr[idx]; } + alias useItemAt = assumeSize!(impl, 8*T.sizeof); +} + +template useLastItem(T) +{ + size_t impl(in T[] arr){ return arr[$-1]; } + alias useLastItem = assumeSize!(impl, 8*T.sizeof); +} + +template fullBitSize(Prefix...) +{ + static if (Prefix.length > 0) + enum fullBitSize = bitSizeOf!(Prefix[0])+fullBitSize!(Prefix[1..$]); + else + enum fullBitSize = 0; +} + +template idxTypes(Key, size_t fullBits, Prefix...) +{ + static if (Prefix.length == 1) + {// the last level is value level, so no index once reduced to 1-level + alias idxTypes = AliasSeq!(); + } + else + { + // Important note on bit packing + // Each level has to hold enough of bits to address the next one + // The bottom level is known to hold full bit width + // thus it's size in pages is full_bit_width - size_of_last_prefix + // Recourse on this notion + alias idxTypes = + AliasSeq!( + idxTypes!(Key, fullBits - bitSizeOf!(Prefix[$-1]), Prefix[0..$-1]), + BitPacked!(typeof(Prefix[$-2](Key.init)), fullBits - bitSizeOf!(Prefix[$-1])) + ); + } +} + +//============================================================================ + +@safe pure int comparePropertyName(Char1, Char2)(const(Char1)[] a, const(Char2)[] b) +if (is(Char1 : dchar) && is(Char2 : dchar)) +{ + import std.algorithm.comparison : cmp; + import std.algorithm.iteration : map, filter; + import std.ascii : toLower; + static bool pred(dchar c) {return !c.isWhite && c != '-' && c != '_';} + return cmp( + a.map!toLower.filter!pred, + b.map!toLower.filter!pred); +} + +@safe pure unittest +{ + assert(!comparePropertyName("foo-bar", "fooBar")); +} + +bool propertyNameLess(Char1, Char2)(const(Char1)[] a, const(Char2)[] b) @safe pure +if (is(Char1 : dchar) && is(Char2 : dchar)) +{ + return comparePropertyName(a, b) < 0; +} + +//============================================================================ +// Utilities for compression of Unicode code point sets +//============================================================================ + +@safe void compressTo(uint val, ref ubyte[] arr) pure nothrow +{ + // not optimized as usually done 1 time (and not public interface) + if (val < 128) + arr ~= cast(ubyte) val; + else if (val < (1 << 13)) + { + arr ~= (0b1_00 << 5) | cast(ubyte)(val >> 8); + arr ~= val & 0xFF; + } + else + { + assert(val < (1 << 21)); + arr ~= (0b1_01 << 5) | cast(ubyte)(val >> 16); + arr ~= (val >> 8) & 0xFF; + arr ~= val & 0xFF; + } +} + +@safe uint decompressFrom(const(ubyte)[] arr, ref size_t idx) pure +{ + import std.exception : enforce; + immutable first = arr[idx++]; + if (!(first & 0x80)) // no top bit -> [0 .. 127] + return first; + immutable extra = ((first >> 5) & 1) + 1; // [1, 2] + uint val = (first & 0x1F); + enforce(idx + extra <= arr.length, "bad code point interval encoding"); + foreach (j; 0 .. extra) + val = (val << 8) | arr[idx+j]; + idx += extra; + return val; +} + + +package ubyte[] compressIntervals(Range)(Range intervals) +if (isInputRange!Range && isIntegralPair!(ElementType!Range)) +{ + ubyte[] storage; + uint base = 0; + // RLE encode + foreach (val; intervals) + { + compressTo(val[0]-base, storage); + base = val[0]; + if (val[1] != lastDchar+1) // till the end of the domain so don't store it + { + compressTo(val[1]-base, storage); + base = val[1]; + } + } + return storage; +} + +@safe pure unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : tuple; + + auto run = [tuple(80, 127), tuple(128, (1 << 10)+128)]; + ubyte[] enc = [cast(ubyte) 80, 47, 1, (0b1_00 << 5) | (1 << 2), 0]; + assert(compressIntervals(run) == enc); + auto run2 = [tuple(0, (1 << 20)+512+1), tuple((1 << 20)+512+4, lastDchar+1)]; + ubyte[] enc2 = [cast(ubyte) 0, (0b1_01 << 5) | (1 << 4), 2, 1, 3]; // odd length-ed + assert(compressIntervals(run2) == enc2); + size_t idx = 0; + assert(decompressFrom(enc, idx) == 80); + assert(decompressFrom(enc, idx) == 47); + assert(decompressFrom(enc, idx) == 1); + assert(decompressFrom(enc, idx) == (1 << 10)); + idx = 0; + assert(decompressFrom(enc2, idx) == 0); + assert(decompressFrom(enc2, idx) == (1 << 20)+512+1); + assert(equal(decompressIntervals(compressIntervals(run)), run)); + assert(equal(decompressIntervals(compressIntervals(run2)), run2)); +} + +// Creates a range of $(D CodepointInterval) that lazily decodes compressed data. +@safe package auto decompressIntervals(const(ubyte)[] data) pure +{ + return DecompressedIntervals(data); +} + +@safe struct DecompressedIntervals +{ +pure: + const(ubyte)[] _stream; + size_t _idx; + CodepointInterval _front; + + this(const(ubyte)[] stream) + { + _stream = stream; + popFront(); + } + + @property CodepointInterval front() + { + assert(!empty); + return _front; + } + + void popFront() + { + if (_idx == _stream.length) + { + _idx = size_t.max; + return; + } + uint base = _front[1]; + _front[0] = base + decompressFrom(_stream, _idx); + if (_idx == _stream.length)// odd length ---> till the end + _front[1] = lastDchar+1; + else + { + base = _front[0]; + _front[1] = base + decompressFrom(_stream, _idx); + } + } + + @property bool empty() const + { + return _idx == size_t.max; + } + + @property DecompressedIntervals save() { return this; } +} + +static assert(isInputRange!DecompressedIntervals); +static assert(isForwardRange!DecompressedIntervals); +//============================================================================ + +version (std_uni_bootstrap){} +else +{ + +// helper for looking up code point sets +@trusted ptrdiff_t findUnicodeSet(alias table, C)(in C[] name) pure +{ + import std.algorithm.iteration : map; + import std.range : assumeSorted; + auto range = assumeSorted!((a,b) => propertyNameLess(a,b)) + (table.map!"a.name"()); + size_t idx = range.lowerBound(name).length; + if (idx < range.length && comparePropertyName(range[idx], name) == 0) + return idx; + return -1; +} + +// another one that loads it +@trusted bool loadUnicodeSet(alias table, Set, C)(in C[] name, ref Set dest) pure +{ + auto idx = findUnicodeSet!table(name); + if (idx >= 0) + { + dest = Set(asSet(table[idx].compressed)); + return true; + } + return false; +} + +@trusted bool loadProperty(Set=CodepointSet, C) + (in C[] name, ref Set target) pure +{ + import std.internal.unicode_tables : uniProps; // generated file + alias ucmp = comparePropertyName; + // conjure cumulative properties by hand + if (ucmp(name, "L") == 0 || ucmp(name, "Letter") == 0) + { + target = asSet(uniProps.Lu); + target |= asSet(uniProps.Ll); + target |= asSet(uniProps.Lt); + target |= asSet(uniProps.Lo); + target |= asSet(uniProps.Lm); + } + else if (ucmp(name,"LC") == 0 || ucmp(name,"Cased Letter")==0) + { + target = asSet(uniProps.Ll); + target |= asSet(uniProps.Lu); + target |= asSet(uniProps.Lt);// Title case + } + else if (ucmp(name, "M") == 0 || ucmp(name, "Mark") == 0) + { + target = asSet(uniProps.Mn); + target |= asSet(uniProps.Mc); + target |= asSet(uniProps.Me); + } + else if (ucmp(name, "N") == 0 || ucmp(name, "Number") == 0) + { + target = asSet(uniProps.Nd); + target |= asSet(uniProps.Nl); + target |= asSet(uniProps.No); + } + else if (ucmp(name, "P") == 0 || ucmp(name, "Punctuation") == 0) + { + target = asSet(uniProps.Pc); + target |= asSet(uniProps.Pd); + target |= asSet(uniProps.Ps); + target |= asSet(uniProps.Pe); + target |= asSet(uniProps.Pi); + target |= asSet(uniProps.Pf); + target |= asSet(uniProps.Po); + } + else if (ucmp(name, "S") == 0 || ucmp(name, "Symbol") == 0) + { + target = asSet(uniProps.Sm); + target |= asSet(uniProps.Sc); + target |= asSet(uniProps.Sk); + target |= asSet(uniProps.So); + } + else if (ucmp(name, "Z") == 0 || ucmp(name, "Separator") == 0) + { + target = asSet(uniProps.Zs); + target |= asSet(uniProps.Zl); + target |= asSet(uniProps.Zp); + } + else if (ucmp(name, "C") == 0 || ucmp(name, "Other") == 0) + { + target = asSet(uniProps.Co); + target |= asSet(uniProps.Lo); + target |= asSet(uniProps.No); + target |= asSet(uniProps.So); + target |= asSet(uniProps.Po); + } + else if (ucmp(name, "graphical") == 0) + { + target = asSet(uniProps.Alphabetic); + + target |= asSet(uniProps.Mn); + target |= asSet(uniProps.Mc); + target |= asSet(uniProps.Me); + + target |= asSet(uniProps.Nd); + target |= asSet(uniProps.Nl); + target |= asSet(uniProps.No); + + target |= asSet(uniProps.Pc); + target |= asSet(uniProps.Pd); + target |= asSet(uniProps.Ps); + target |= asSet(uniProps.Pe); + target |= asSet(uniProps.Pi); + target |= asSet(uniProps.Pf); + target |= asSet(uniProps.Po); + + target |= asSet(uniProps.Zs); + + target |= asSet(uniProps.Sm); + target |= asSet(uniProps.Sc); + target |= asSet(uniProps.Sk); + target |= asSet(uniProps.So); + } + else if (ucmp(name, "any") == 0) + target = Set.fromIntervals(0, 0x110000); + else if (ucmp(name, "ascii") == 0) + target = Set.fromIntervals(0, 0x80); + else + return loadUnicodeSet!(uniProps.tab)(name, target); + return true; +} + +// CTFE-only helper for checking property names at compile-time +@safe bool isPrettyPropertyName(C)(in C[] name) +{ + import std.algorithm.searching : find; + auto names = [ + "L", "Letter", + "LC", "Cased Letter", + "M", "Mark", + "N", "Number", + "P", "Punctuation", + "S", "Symbol", + "Z", "Separator", + "Graphical", + "any", + "ascii" + ]; + auto x = find!(x => comparePropertyName(x, name) == 0)(names); + return !x.empty; +} + +// ditto, CTFE-only, not optimized +@safe private static bool findSetName(alias table, C)(in C[] name) +{ + return findUnicodeSet!table(name) >= 0; +} + +template SetSearcher(alias table, string kind) +{ + /// Run-time checked search. + static auto opCall(C)(in C[] name) + if (is(C : dchar)) + { + import std.conv : to; + CodepointSet set; + if (loadUnicodeSet!table(name, set)) + return set; + throw new Exception("No unicode set for "~kind~" by name " + ~name.to!string()~" was found."); + } + /// Compile-time checked search. + static @property auto opDispatch(string name)() + { + static if (findSetName!table(name)) + { + CodepointSet set; + loadUnicodeSet!table(name, set); + return set; + } + else + static assert(false, "No unicode set for "~kind~" by name " + ~name~" was found."); + } +} + +/** + A single entry point to lookup Unicode $(CODEPOINT) sets by name or alias of + a block, script or general category. + + It uses well defined standard rules of property name lookup. + This includes fuzzy matching of names, so that + 'White_Space', 'white-SpAce' and 'whitespace' are all considered equal + and yield the same set of white space $(CHARACTERS). +*/ +@safe public struct unicode +{ + /** + Performs the lookup of set of $(CODEPOINTS) + with compile-time correctness checking. + This short-cut version combines 3 searches: + across blocks, scripts, and common binary properties. + + Note that since scripts and blocks overlap the + usual trick to disambiguate is used - to get a block use + $(D unicode.InBlockName), to search a script + use $(D unicode.ScriptName). + + See_Also: $(LREF block), $(LREF script) + and (not included in this search) $(LREF hangulSyllableType). + */ + + static @property auto opDispatch(string name)() pure + { + static if (findAny(name)) + return loadAny(name); + else + static assert(false, "No unicode set by name "~name~" was found."); + } + + /// + @safe unittest + { + import std.exception : collectException; + auto ascii = unicode.ASCII; + assert(ascii['A']); + assert(ascii['~']); + assert(!ascii['\u00e0']); + // matching is case-insensitive + assert(ascii == unicode.ascII); + assert(!ascii['à']); + // underscores, '-' and whitespace in names are ignored too + auto latin = unicode.in_latin1_Supplement; + assert(latin['à']); + assert(!latin['$']); + // BTW Latin 1 Supplement is a block, hence "In" prefix + assert(latin == unicode("In Latin 1 Supplement")); + // run-time look up throws if no such set is found + assert(collectException(unicode("InCyrilliac"))); + } + + /** + The same lookup across blocks, scripts, or binary properties, + but performed at run-time. + This version is provided for cases where $(D name) + is not known beforehand; otherwise compile-time + checked $(LREF opDispatch) is typically a better choice. + + See the $(S_LINK Unicode properties, table of properties) for available + sets. + */ + static auto opCall(C)(in C[] name) + if (is(C : dchar)) + { + return loadAny(name); + } + + /** + Narrows down the search for sets of $(CODEPOINTS) to all Unicode blocks. + + Note: + Here block names are unambiguous as no scripts are searched + and thus to search use simply $(D unicode.block.BlockName) notation. + + See $(S_LINK Unicode properties, table of properties) for available sets. + See_Also: $(S_LINK Unicode properties, table of properties). + */ + struct block + { + import std.internal.unicode_tables : blocks; // generated file + mixin SetSearcher!(blocks.tab, "block"); + } + + /// + @safe unittest + { + // use .block for explicitness + assert(unicode.block.Greek_and_Coptic == unicode.InGreek_and_Coptic); + } + + /** + Narrows down the search for sets of $(CODEPOINTS) to all Unicode scripts. + + See the $(S_LINK Unicode properties, table of properties) for available + sets. + */ + struct script + { + import std.internal.unicode_tables : scripts; // generated file + mixin SetSearcher!(scripts.tab, "script"); + } + + /// + @safe unittest + { + auto arabicScript = unicode.script.arabic; + auto arabicBlock = unicode.block.arabic; + // there is an intersection between script and block + assert(arabicBlock['؁']); + assert(arabicScript['؁']); + // but they are different + assert(arabicBlock != arabicScript); + assert(arabicBlock == unicode.inArabic); + assert(arabicScript == unicode.arabic); + } + + /** + Fetch a set of $(CODEPOINTS) that have the given hangul syllable type. + + Other non-binary properties (once supported) follow the same + notation - $(D unicode.propertyName.propertyValue) for compile-time + checked access and $(D unicode.propertyName(propertyValue)) + for run-time checked one. + + See the $(S_LINK Unicode properties, table of properties) for available + sets. + */ + struct hangulSyllableType + { + import std.internal.unicode_tables : hangul; // generated file + mixin SetSearcher!(hangul.tab, "hangul syllable type"); + } + + /// + @safe unittest + { + // L here is syllable type not Letter as in unicode.L short-cut + auto leadingVowel = unicode.hangulSyllableType("L"); + // check that some leading vowels are present + foreach (vowel; '\u1110'..'\u115F') + assert(leadingVowel[vowel]); + assert(leadingVowel == unicode.hangulSyllableType.L); + } + +private: + alias ucmp = comparePropertyName; + + static bool findAny(string name) + { + import std.internal.unicode_tables : blocks, scripts, uniProps; // generated file + return isPrettyPropertyName(name) + || findSetName!(uniProps.tab)(name) || findSetName!(scripts.tab)(name) + || (ucmp(name[0 .. 2],"In") == 0 && findSetName!(blocks.tab)(name[2..$])); + } + + static auto loadAny(Set=CodepointSet, C)(in C[] name) pure + { + import std.conv : to; + import std.internal.unicode_tables : blocks, scripts; // generated file + Set set; + immutable loaded = loadProperty(name, set) || loadUnicodeSet!(scripts.tab)(name, set) + || (name.length > 2 && ucmp(name[0 .. 2],"In") == 0 + && loadUnicodeSet!(blocks.tab)(name[2..$], set)); + if (loaded) + return set; + throw new Exception("No unicode set by name "~name.to!string()~" was found."); + } + + // FIXME: re-disable once the compiler is fixed + // Disabled to prevent the mistake of creating instances of this pseudo-struct. + //@disable ~this(); +} + +@safe unittest +{ + import std.internal.unicode_tables : blocks, uniProps; // generated file + assert(unicode("InHebrew") == asSet(blocks.Hebrew)); + assert(unicode("separator") == (asSet(uniProps.Zs) | asSet(uniProps.Zl) | asSet(uniProps.Zp))); + assert(unicode("In-Kharoshthi") == asSet(blocks.Kharoshthi)); +} + +enum EMPTY_CASE_TRIE = ushort.max;// from what gen_uni uses internally + +// control - '\r' +enum controlSwitch = ` + case '\u0000':..case '\u0008':case '\u000E':..case '\u001F':case '\u007F':.. + case '\u0084':case '\u0086':..case '\u009F': case '\u0009':..case '\u000C': case '\u0085': +`; +// TODO: redo the most of hangul stuff algorithmically in case of Graphemes too +// kill unrolled switches + +private static bool isRegionalIndicator(dchar ch) @safe pure @nogc nothrow +{ + return ch >= '\U0001F1E6' && ch <= '\U0001F1FF'; +} + +template genericDecodeGrapheme(bool getValue) +{ + alias graphemeExtend = graphemeExtendTrie; + alias spacingMark = mcTrie; + static if (getValue) + alias Value = Grapheme; + else + alias Value = void; + + Value genericDecodeGrapheme(Input)(ref Input range) + { + import std.internal.unicode_tables : isHangL, isHangT, isHangV; // generated file + enum GraphemeState { + Start, + CR, + RI, + L, + V, + LVT + } + static if (getValue) + Grapheme grapheme; + auto state = GraphemeState.Start; + enum eat = q{ + static if (getValue) + grapheme ~= ch; + range.popFront(); + }; + + dchar ch; + assert(!range.empty, "Attempting to decode grapheme from an empty " ~ Input.stringof); + while (!range.empty) + { + ch = range.front; + final switch (state) with(GraphemeState) + { + case Start: + mixin(eat); + if (ch == '\r') + state = CR; + else if (isRegionalIndicator(ch)) + state = RI; + else if (isHangL(ch)) + state = L; + else if (hangLV[ch] || isHangV(ch)) + state = V; + else if (hangLVT[ch]) + state = LVT; + else if (isHangT(ch)) + state = LVT; + else + { + switch (ch) + { + mixin(controlSwitch); + goto L_End; + default: + goto L_End_Extend; + } + } + break; + case CR: + if (ch == '\n') + mixin(eat); + goto L_End_Extend; + case RI: + if (isRegionalIndicator(ch)) + mixin(eat); + else + goto L_End_Extend; + break; + case L: + if (isHangL(ch)) + mixin(eat); + else if (isHangV(ch) || hangLV[ch]) + { + state = V; + mixin(eat); + } + else if (hangLVT[ch]) + { + state = LVT; + mixin(eat); + } + else + goto L_End_Extend; + break; + case V: + if (isHangV(ch)) + mixin(eat); + else if (isHangT(ch)) + { + state = LVT; + mixin(eat); + } + else + goto L_End_Extend; + break; + case LVT: + if (isHangT(ch)) + { + mixin(eat); + } + else + goto L_End_Extend; + break; + } + } + L_End_Extend: + while (!range.empty) + { + ch = range.front; + // extend & spacing marks + if (!graphemeExtend[ch] && !spacingMark[ch]) + break; + mixin(eat); + } + L_End: + static if (getValue) + return grapheme; + } + +} + +public: // Public API continues + +/++ + Computes the length of grapheme cluster starting at $(D index). + Both the resulting length and the $(D index) are measured + in $(S_LINK Code unit, code units). + + Params: + C = type that is implicitly convertible to $(D dchars) + input = array of grapheme clusters + index = starting index into $(D input[]) + + Returns: + length of grapheme cluster ++/ +size_t graphemeStride(C)(in C[] input, size_t index) +if (is(C : dchar)) +{ + auto src = input[index..$]; + auto n = src.length; + genericDecodeGrapheme!(false)(src); + return n - src.length; +} + +/// +@safe unittest +{ + assert(graphemeStride(" ", 1) == 1); + // A + combing ring above + string city = "A\u030Arhus"; + size_t first = graphemeStride(city, 0); + assert(first == 3); //\u030A has 2 UTF-8 code units + assert(city[0 .. first] == "A\u030A"); + assert(city[first..$] == "rhus"); +} + +/++ + Reads one full grapheme cluster from an input range of dchar $(D inp). + + For examples see the $(LREF Grapheme) below. + + Note: + This function modifies $(D inp) and thus $(D inp) + must be an L-value. ++/ +Grapheme decodeGrapheme(Input)(ref Input inp) +if (isInputRange!Input && is(Unqual!(ElementType!Input) == dchar)) +{ + return genericDecodeGrapheme!true(inp); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + + Grapheme gr; + string s = " \u0020\u0308 "; + gr = decodeGrapheme(s); + assert(gr.length == 1 && gr[0] == ' '); + gr = decodeGrapheme(s); + assert(gr.length == 2 && equal(gr[0 .. 2], " \u0308")); + s = "\u0300\u0308\u1100"; + assert(equal(decodeGrapheme(s)[], "\u0300\u0308")); + assert(equal(decodeGrapheme(s)[], "\u1100")); + s = "\u11A8\u0308\uAC01"; + assert(equal(decodeGrapheme(s)[], "\u11A8\u0308")); + assert(equal(decodeGrapheme(s)[], "\uAC01")); +} + +/++ + $(P Iterate a string by grapheme.) + + $(P Useful for doing string manipulation that needs to be aware + of graphemes.) + + See_Also: + $(LREF byCodePoint) ++/ +auto byGrapheme(Range)(Range range) +if (isInputRange!Range && is(Unqual!(ElementType!Range) == dchar)) +{ + // TODO: Bidirectional access + static struct Result(R) + { + private R _range; + private Grapheme _front; + + bool empty() @property + { + return _front.length == 0; + } + + Grapheme front() @property + { + return _front; + } + + void popFront() + { + _front = _range.empty ? Grapheme.init : _range.decodeGrapheme(); + } + + static if (isForwardRange!R) + { + Result save() @property + { + return Result(_range.save, _front); + } + } + } + + auto result = Result!(Range)(range); + result.popFront(); + return result; +} + +/// +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range.primitives : walkLength; + import std.range : take, drop; + auto text = "noe\u0308l"; // noël using e + combining diaeresis + assert(text.walkLength == 5); // 5 code points + + auto gText = text.byGrapheme; + assert(gText.walkLength == 4); // 4 graphemes + + assert(gText.take(3).equal("noe\u0308".byGrapheme)); + assert(gText.drop(3).equal("l".byGrapheme)); +} + +// For testing non-forward-range input ranges +version (unittest) +private static struct InputRangeString +{ + private string s; + + bool empty() @property { return s.empty; } + dchar front() @property { return s.front; } + void popFront() { s.popFront(); } +} + +@system unittest +{ + import std.algorithm.comparison : equal; + import std.array : array; + import std.range : retro; + import std.range.primitives : walkLength; + assert("".byGrapheme.walkLength == 0); + + auto reverse = "le\u0308on"; + assert(reverse.walkLength == 5); + + auto gReverse = reverse.byGrapheme; + assert(gReverse.walkLength == 4); + + foreach (text; AliasSeq!("noe\u0308l"c, "noe\u0308l"w, "noe\u0308l"d)) + { + assert(text.walkLength == 5); + static assert(isForwardRange!(typeof(text))); + + auto gText = text.byGrapheme; + static assert(isForwardRange!(typeof(gText))); + assert(gText.walkLength == 4); + assert(gText.array.retro.equal(gReverse)); + } + + auto nonForwardRange = InputRangeString("noe\u0308l").byGrapheme; + static assert(!isForwardRange!(typeof(nonForwardRange))); + assert(nonForwardRange.walkLength == 4); +} + +/++ + $(P Lazily transform a range of $(LREF Grapheme)s to a range of code points.) + + $(P Useful for converting the result to a string after doing operations + on graphemes.) + + $(P Acts as the identity function when given a range of code points.) ++/ +auto byCodePoint(Range)(Range range) +if (isInputRange!Range && is(Unqual!(ElementType!Range) == Grapheme)) +{ + // TODO: Propagate bidirectional access + static struct Result + { + private Range _range; + private size_t i = 0; + + bool empty() @property + { + return _range.empty; + } + + dchar front() @property + { + return _range.front[i]; + } + + void popFront() + { + ++i; + + if (i >= _range.front.length) + { + _range.popFront(); + i = 0; + } + } + + static if (isForwardRange!Range) + { + Result save() @property + { + return Result(_range.save, i); + } + } + } + + return Result(range); +} + +/// Ditto +Range byCodePoint(Range)(Range range) +if (isInputRange!Range && is(Unqual!(ElementType!Range) == dchar)) +{ + return range; +} + +/// +@safe unittest +{ + import std.array : array; + import std.conv : text; + import std.range : retro; + + string s = "noe\u0308l"; // noël + + // reverse it and convert the result to a string + string reverse = s.byGrapheme + .array + .retro + .byCodePoint + .text; + + assert(reverse == "le\u0308on"); // lëon +} + +@system unittest +{ + import std.algorithm.comparison : equal; + import std.range.primitives : walkLength; + assert("".byGrapheme.byCodePoint.equal("")); + + string text = "noe\u0308l"; + static assert(is(typeof(text.byCodePoint) == string)); + + auto gText = InputRangeString(text).byGrapheme; + static assert(!isForwardRange!(typeof(gText))); + + auto cpText = gText.byCodePoint; + static assert(!isForwardRange!(typeof(cpText))); + + assert(cpText.walkLength == text.walkLength); +} + +@trusted: + +/++ + $(P A structure designed to effectively pack $(CHARACTERS) + of a $(CLUSTER). + ) + + $(P $(D Grapheme) has value semantics so 2 copies of a $(D Grapheme) + always refer to distinct objects. In most actual scenarios a $(D Grapheme) + fits on the stack and avoids memory allocation overhead for all but quite + long clusters. + ) + + See_Also: $(LREF decodeGrapheme), $(LREF graphemeStride) ++/ +@trusted struct Grapheme +{ + import std.traits : isDynamicArray; + +public: + /// Ctor + this(C)(in C[] chars...) + if (is(C : dchar)) + { + this ~= chars; + } + + ///ditto + this(Input)(Input seq) + if (!isDynamicArray!Input + && isInputRange!Input && is(ElementType!Input : dchar)) + { + this ~= seq; + } + + /// Gets a $(CODEPOINT) at the given index in this cluster. + dchar opIndex(size_t index) const pure nothrow @nogc + { + assert(index < length); + return read24(isBig ? ptr_ : small_.ptr, index); + } + + /++ + Writes a $(CODEPOINT) $(D ch) at given index in this cluster. + + Warning: + Use of this facility may invalidate grapheme cluster, + see also $(LREF Grapheme.valid). + +/ + void opIndexAssign(dchar ch, size_t index) pure nothrow @nogc + { + assert(index < length); + write24(isBig ? ptr_ : small_.ptr, ch, index); + } + + /// + @safe unittest + { + auto g = Grapheme("A\u0302"); + assert(g[0] == 'A'); + assert(g.valid); + g[1] = '~'; // ASCII tilda is not a combining mark + assert(g[1] == '~'); + assert(!g.valid); + } + + /++ + Random-access range over Grapheme's $(CHARACTERS). + + Warning: Invalidates when this Grapheme leaves the scope, + attempts to use it then would lead to memory corruption. + +/ + @system SliceOverIndexed!Grapheme opSlice(size_t a, size_t b) pure nothrow @nogc + { + return sliceOverIndexed(a, b, &this); + } + + /// ditto + @system SliceOverIndexed!Grapheme opSlice() pure nothrow @nogc + { + return sliceOverIndexed(0, length, &this); + } + + /// Grapheme cluster length in $(CODEPOINTS). + @property size_t length() const pure nothrow @nogc + { + return isBig ? len_ : slen_ & 0x7F; + } + + /++ + Append $(CHARACTER) $(D ch) to this grapheme. + Warning: + Use of this facility may invalidate grapheme cluster, + see also $(D valid). + + See_Also: $(LREF Grapheme.valid) + +/ + ref opOpAssign(string op)(dchar ch) + { + static if (op == "~") + { + if (!isBig) + { + if (slen_ == small_cap) + convertToBig();// & fallthrough to "big" branch + else + { + write24(small_.ptr, ch, smallLength); + slen_++; + return this; + } + } + + assert(isBig); + if (len_ == cap_) + { + import core.checkedint : addu, mulu; + bool overflow; + cap_ = addu(cap_, grow, overflow); + auto nelems = mulu(3, addu(cap_, 1, overflow), overflow); + if (overflow) assert(0); + ptr_ = cast(ubyte*) pureRealloc(ptr_, nelems); + if (ptr_ is null) onOutOfMemoryError(); + } + write24(ptr_, ch, len_++); + return this; + } + else + static assert(false, "No operation "~op~" defined for Grapheme"); + } + + /// + @system unittest + { + import std.algorithm.comparison : equal; + auto g = Grapheme("A"); + assert(g.valid); + g ~= '\u0301'; + assert(g[].equal("A\u0301")); + assert(g.valid); + g ~= "B"; + // not a valid grapheme cluster anymore + assert(!g.valid); + // still could be useful though + assert(g[].equal("A\u0301B")); + } + + /// Append all $(CHARACTERS) from the input range $(D inp) to this Grapheme. + ref opOpAssign(string op, Input)(Input inp) + if (isInputRange!Input && is(ElementType!Input : dchar)) + { + static if (op == "~") + { + foreach (dchar ch; inp) + this ~= ch; + return this; + } + else + static assert(false, "No operation "~op~" defined for Grapheme"); + } + + /++ + True if this object contains valid extended grapheme cluster. + Decoding primitives of this module always return a valid $(D Grapheme). + + Appending to and direct manipulation of grapheme's $(CHARACTERS) may + render it no longer valid. Certain applications may chose to use + Grapheme as a "small string" of any $(CODEPOINTS) and ignore this property + entirely. + +/ + @property bool valid()() /*const*/ + { + auto r = this[]; + genericDecodeGrapheme!false(r); + return r.length == 0; + } + + this(this) pure @nogc nothrow + { + if (isBig) + {// dup it + import core.checkedint : addu, mulu; + bool overflow; + auto raw_cap = mulu(3, addu(cap_, 1, overflow), overflow); + if (overflow) assert(0); + + auto p = cast(ubyte*) pureMalloc(raw_cap); + if (p is null) onOutOfMemoryError(); + p[0 .. raw_cap] = ptr_[0 .. raw_cap]; + ptr_ = p; + } + } + + ~this() pure @nogc nothrow + { + if (isBig) + { + pureFree(ptr_); + } + } + + +private: + enum small_bytes = ((ubyte*).sizeof+3*size_t.sizeof-1); + // "out of the blue" grow rate, needs testing + // (though graphemes are typically small < 9) + enum grow = 20; + enum small_cap = small_bytes/3; + enum small_flag = 0x80, small_mask = 0x7F; + // 16 bytes in 32bits, should be enough for the majority of cases + union + { + struct + { + ubyte* ptr_; + size_t cap_; + size_t len_; + size_t padding_; + } + struct + { + ubyte[small_bytes] small_; + ubyte slen_; + } + } + + void convertToBig() pure @nogc nothrow + { + static assert(grow.max / 3 - 1 >= grow); + enum nbytes = 3 * (grow + 1); + size_t k = smallLength; + ubyte* p = cast(ubyte*) pureMalloc(nbytes); + if (p is null) onOutOfMemoryError(); + for (int i=0; i<k; i++) + write24(p, read24(small_.ptr, i), i); + // now we can overwrite small array data + ptr_ = p; + len_ = slen_; + assert(grow > len_); + cap_ = grow; + setBig(); + } + + void setBig() pure nothrow @nogc { slen_ |= small_flag; } + + @property size_t smallLength() const pure nothrow @nogc + { + return slen_ & small_mask; + } + @property ubyte isBig() const pure nothrow @nogc + { + return slen_ & small_flag; + } +} + +static assert(Grapheme.sizeof == size_t.sizeof*4); + + +@system pure /*nothrow @nogc*/ unittest // TODO: string .front is GC and throw +{ + import std.algorithm.comparison : equal; + Grapheme[3] data = [Grapheme("Ю"), Grapheme("У"), Grapheme("З")]; + assert(byGrapheme("ЮУЗ").equal(data[])); +} + +/// +@system unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : filter; + import std.range : isRandomAccessRange; + + string bold = "ku\u0308hn"; + + // note that decodeGrapheme takes parameter by ref + auto first = decodeGrapheme(bold); + + assert(first.length == 1); + assert(first[0] == 'k'); + + // the next grapheme is 2 characters long + auto wideOne = decodeGrapheme(bold); + // slicing a grapheme yields a random-access range of dchar + assert(wideOne[].equal("u\u0308")); + assert(wideOne.length == 2); + static assert(isRandomAccessRange!(typeof(wideOne[]))); + + // all of the usual range manipulation is possible + assert(wideOne[].filter!isMark().equal("\u0308")); + + auto g = Grapheme("A"); + assert(g.valid); + g ~= '\u0301'; + assert(g[].equal("A\u0301")); + assert(g.valid); + g ~= "B"; + // not a valid grapheme cluster anymore + assert(!g.valid); + // still could be useful though + assert(g[].equal("A\u0301B")); +} + +@safe unittest +{ + auto g = Grapheme("A\u0302"); + assert(g[0] == 'A'); + assert(g.valid); + g[1] = '~'; // ASCII tilda is not a combining mark + assert(g[1] == '~'); + assert(!g.valid); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.iteration : map; + import std.conv : text; + import std.range : iota; + + // not valid clusters (but it just a test) + auto g = Grapheme('a', 'b', 'c', 'd', 'e'); + assert(g[0] == 'a'); + assert(g[1] == 'b'); + assert(g[2] == 'c'); + assert(g[3] == 'd'); + assert(g[4] == 'e'); + g[3] = 'Й'; + assert(g[2] == 'c'); + assert(g[3] == 'Й', text(g[3], " vs ", 'Й')); + assert(g[4] == 'e'); + assert(!g.valid); + + g ~= 'ц'; + g ~= '~'; + assert(g[0] == 'a'); + assert(g[1] == 'b'); + assert(g[2] == 'c'); + assert(g[3] == 'Й'); + assert(g[4] == 'e'); + assert(g[5] == 'ц'); + assert(g[6] == '~'); + assert(!g.valid); + + Grapheme copy = g; + copy[0] = 'X'; + copy[1] = '-'; + assert(g[0] == 'a' && copy[0] == 'X'); + assert(g[1] == 'b' && copy[1] == '-'); + assert(equal(g[2 .. g.length], copy[2 .. copy.length])); + copy = Grapheme("АБВГДЕЁЖЗИКЛМ"); + assert(equal(copy[0 .. 8], "АБВГДЕЁЖ"), text(copy[0 .. 8])); + copy ~= "xyz"; + assert(equal(copy[13 .. 15], "xy"), text(copy[13 .. 15])); + assert(!copy.valid); + + Grapheme h; + foreach (dchar v; iota(cast(int)'A', cast(int)'Z'+1).map!"cast(dchar)a"()) + h ~= v; + assert(equal(h[], iota(cast(int)'A', cast(int)'Z'+1))); +} + +/++ + $(P Does basic case-insensitive comparison of $(D r1) and $(D r2). + This function uses simpler comparison rule thus achieving better performance + than $(LREF icmp). However keep in mind the warning below.) + + Params: + r1 = an input range of characters + r2 = an input range of characters + + Returns: + An $(D int) that is 0 if the strings match, + <0 if $(D r1) is lexicographically "less" than $(D r2), + >0 if $(D r1) is lexicographically "greater" than $(D r2) + + Warning: + This function only handles 1:1 $(CODEPOINT) mapping + and thus is not sufficient for certain alphabets + like German, Greek and few others. + + See_Also: + $(LREF icmp) + $(REF cmp, std,algorithm,comparison) ++/ +int sicmp(S1, S2)(S1 r1, S2 r2) +if (isInputRange!S1 && isSomeChar!(ElementEncodingType!S1) + && isInputRange!S2 && isSomeChar!(ElementEncodingType!S2)) +{ + import std.internal.unicode_tables : sTable = simpleCaseTable; // generated file + import std.utf : byDchar; + + auto str1 = r1.byDchar; + auto str2 = r2.byDchar; + + foreach (immutable lhs; str1) + { + if (str2.empty) + return 1; + immutable rhs = str2.front; + str2.popFront(); + int diff = lhs - rhs; + if (!diff) + continue; + size_t idx = simpleCaseTrie[lhs]; + size_t idx2 = simpleCaseTrie[rhs]; + // simpleCaseTrie is packed index table + if (idx != EMPTY_CASE_TRIE) + { + if (idx2 != EMPTY_CASE_TRIE) + {// both cased chars + // adjust idx --> start of bucket + idx = idx - sTable[idx].n; + idx2 = idx2 - sTable[idx2].n; + if (idx == idx2)// one bucket, equivalent chars + continue; + else// not the same bucket + diff = sTable[idx].ch - sTable[idx2].ch; + } + else + diff = sTable[idx - sTable[idx].n].ch - rhs; + } + else if (idx2 != EMPTY_CASE_TRIE) + { + diff = lhs - sTable[idx2 - sTable[idx2].n].ch; + } + // one of chars is not cased at all + return diff; + } + return str2.empty ? 0 : -1; +} + +/// +@safe @nogc pure nothrow unittest +{ + assert(sicmp("Август", "авгусТ") == 0); + // Greek also works as long as there is no 1:M mapping in sight + assert(sicmp("ΌΎ", "όύ") == 0); + // things like the following won't get matched as equal + // Greek small letter iota with dialytika and tonos + assert(sicmp("ΐ", "\u03B9\u0308\u0301") != 0); + + // while icmp has no problem with that + assert(icmp("ΐ", "\u03B9\u0308\u0301") == 0); + assert(icmp("ΌΎ", "όύ") == 0); +} + +// overloads for the most common cases to reduce compile time +@safe @nogc pure nothrow +{ + int sicmp(const(char)[] str1, const(char)[] str2) + { return sicmp!(const(char)[], const(char)[])(str1, str2); } + int sicmp(const(wchar)[] str1, const(wchar)[] str2) + { return sicmp!(const(wchar)[], const(wchar)[])(str1, str2); } + int sicmp(const(dchar)[] str1, const(dchar)[] str2) + { return sicmp!(const(dchar)[], const(dchar)[])(str1, str2); } +} + +private int fullCasedCmp(Range)(dchar lhs, dchar rhs, ref Range rtail) +{ + import std.algorithm.searching : skipOver; + import std.internal.unicode_tables : fullCaseTable; // generated file + alias fTable = fullCaseTable; + size_t idx = fullCaseTrie[lhs]; + // fullCaseTrie is packed index table + if (idx == EMPTY_CASE_TRIE) + return lhs; + immutable start = idx - fTable[idx].n; + immutable end = fTable[idx].size + start; + assert(fTable[start].entry_len == 1); + for (idx=start; idx<end; idx++) + { + auto entryLen = fTable[idx].entry_len; + if (entryLen == 1) + { + if (fTable[idx].seq[0] == rhs) + { + return 0; + } + } + else + {// OK it's a long chunk, like 'ss' for German + dstring seq = fTable[idx].seq[0 .. entryLen]; + if (rhs == seq[0] + && rtail.skipOver(seq[1..$])) + { + // note that this path modifies rtail + // iff we managed to get there + return 0; + } + } + } + return fTable[start].seq[0]; // new remapped character for accurate diffs +} + +/++ + Does case insensitive comparison of `r1` and `r2`. + Follows the rules of full case-folding mapping. + This includes matching as equal german ß with "ss" and + other 1:M $(CODEPOINT) mappings unlike $(LREF sicmp). + The cost of `icmp` being pedantically correct is + slightly worse performance. + + Params: + r1 = a forward range of characters + r2 = a forward range of characters + + Returns: + An $(D int) that is 0 if the strings match, + <0 if $(D str1) is lexicographically "less" than $(D str2), + >0 if $(D str1) is lexicographically "greater" than $(D str2) + + See_Also: + $(LREF sicmp) + $(REF cmp, std,algorithm,comparison) ++/ +int icmp(S1, S2)(S1 r1, S2 r2) +if (isForwardRange!S1 && isSomeChar!(ElementEncodingType!S1) + && isForwardRange!S2 && isSomeChar!(ElementEncodingType!S2)) +{ + import std.utf : byDchar; + + auto str1 = r1.byDchar; + auto str2 = r2.byDchar; + + for (;;) + { + if (str1.empty) + return str2.empty ? 0 : -1; + immutable lhs = str1.front; + if (str2.empty) + return 1; + immutable rhs = str2.front; + str1.popFront(); + str2.popFront(); + if (!(lhs - rhs)) + continue; + // first try to match lhs to <rhs,right-tail> sequence + immutable cmpLR = fullCasedCmp(lhs, rhs, str2); + if (!cmpLR) + continue; + // then rhs to <lhs,left-tail> sequence + immutable cmpRL = fullCasedCmp(rhs, lhs, str1); + if (!cmpRL) + continue; + // cmpXX contain remapped codepoints + // to obtain stable ordering of icmp + return cmpLR - cmpRL; + } +} + +/// +@safe @nogc pure nothrow unittest +{ + assert(icmp("Rußland", "Russland") == 0); + assert(icmp("ᾩ -> \u1F70\u03B9", "\u1F61\u03B9 -> ᾲ") == 0); +} + +/** + * By using $(REF byUTF, std,utf) and its aliases, GC allocations via auto-decoding + * and thrown exceptions can be avoided, making `icmp` `@safe @nogc nothrow pure`. + */ +@safe @nogc nothrow pure unittest +{ + import std.utf : byDchar; + + assert(icmp("Rußland".byDchar, "Russland".byDchar) == 0); + assert(icmp("ᾩ -> \u1F70\u03B9".byDchar, "\u1F61\u03B9 -> ᾲ".byDchar) == 0); +} + +// test different character types +@safe unittest +{ + assert(icmp("Rußland", "Russland") == 0); + assert(icmp("Rußland"w, "Russland") == 0); + assert(icmp("Rußland", "Russland"w) == 0); + assert(icmp("Rußland"w, "Russland"w) == 0); + assert(icmp("Rußland"d, "Russland"w) == 0); + assert(icmp("Rußland"w, "Russland"d) == 0); +} + +// overloads for the most common cases to reduce compile time +@safe @nogc pure nothrow +{ + int icmp(const(char)[] str1, const(char)[] str2) + { return icmp!(const(char)[], const(char)[])(str1, str2); } + int icmp(const(wchar)[] str1, const(wchar)[] str2) + { return icmp!(const(wchar)[], const(wchar)[])(str1, str2); } + int icmp(const(dchar)[] str1, const(dchar)[] str2) + { return icmp!(const(dchar)[], const(dchar)[])(str1, str2); } +} + +@safe unittest +{ + import std.algorithm.sorting : sort; + import std.conv : to; + import std.exception : assertCTFEable; + assertCTFEable!( + { + foreach (cfunc; AliasSeq!(icmp, sicmp)) + { + foreach (S1; AliasSeq!(string, wstring, dstring)) + foreach (S2; AliasSeq!(string, wstring, dstring)) + (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + assert(cfunc("".to!S1(), "".to!S2()) == 0); + assert(cfunc("A".to!S1(), "".to!S2()) > 0); + assert(cfunc("".to!S1(), "0".to!S2()) < 0); + assert(cfunc("abc".to!S1(), "abc".to!S2()) == 0); + assert(cfunc("abcd".to!S1(), "abc".to!S2()) > 0); + assert(cfunc("abc".to!S1(), "abcd".to!S2()) < 0); + assert(cfunc("Abc".to!S1(), "aBc".to!S2()) == 0); + assert(cfunc("авГуст".to!S1(), "АВгУСТ".to!S2()) == 0); + // Check example: + assert(cfunc("Август".to!S1(), "авгусТ".to!S2()) == 0); + assert(cfunc("ΌΎ".to!S1(), "όύ".to!S2()) == 0); + }(); + // check that the order is properly agnostic to the case + auto strs = [ "Apple", "ORANGE", "orAcle", "amp", "banana"]; + sort!((a,b) => cfunc(a,b) < 0)(strs); + assert(strs == ["amp", "Apple", "banana", "orAcle", "ORANGE"]); + } + assert(icmp("ßb", "ssa") > 0); + // Check example: + assert(icmp("Russland", "Rußland") == 0); + assert(icmp("ᾩ -> \u1F70\u03B9", "\u1F61\u03B9 -> ᾲ") == 0); + assert(icmp("ΐ"w, "\u03B9\u0308\u0301") == 0); + assert(sicmp("ΐ", "\u03B9\u0308\u0301") != 0); + //bugzilla 11057 + assert( icmp("K", "L") < 0 ); + }); +} + +// issue 17372 +@safe pure unittest +{ + import std.algorithm.iteration : joiner, map; + import std.algorithm.sorting : sort; + import std.array : array; + auto a = [["foo", "bar"], ["baz"]].map!(line => line.joiner(" ")).array.sort!((a, b) => icmp(a, b) < 0); +} + +// This is package for the moment to be used as a support tool for std.regex +// It needs a better API +/* + Return a range of all $(CODEPOINTS) that casefold to + and from this $(D ch). +*/ +package auto simpleCaseFoldings(dchar ch) @safe +{ + import std.internal.unicode_tables : simpleCaseTable; // generated file + alias sTable = simpleCaseTable; + static struct Range + { + @safe pure nothrow: + uint idx; //if == uint.max, then read c. + union + { + dchar c; // == 0 - empty range + uint len; + } + @property bool isSmall() const { return idx == uint.max; } + + this(dchar ch) + { + idx = uint.max; + c = ch; + } + + this(uint start, uint size) + { + idx = start; + len = size; + } + + @property dchar front() const + { + assert(!empty); + if (isSmall) + { + return c; + } + auto ch = sTable[idx].ch; + return ch; + } + + @property bool empty() const + { + if (isSmall) + { + return c == 0; + } + return len == 0; + } + + @property size_t length() const + { + if (isSmall) + { + return c == 0 ? 0 : 1; + } + return len; + } + + void popFront() + { + if (isSmall) + c = 0; + else + { + idx++; + len--; + } + } + } + immutable idx = simpleCaseTrie[ch]; + if (idx == EMPTY_CASE_TRIE) + return Range(ch); + auto entry = sTable[idx]; + immutable start = idx - entry.n; + return Range(start, entry.size); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + import std.algorithm.searching : canFind; + import std.array : array; + import std.exception : assertCTFEable; + assertCTFEable!((){ + auto r = simpleCaseFoldings('Э').array; + assert(r.length == 2); + assert(r.canFind('э') && r.canFind('Э')); + auto sr = simpleCaseFoldings('~'); + assert(sr.equal("~")); + //A with ring above - casefolds to the same bucket as Angstrom sign + sr = simpleCaseFoldings('Å'); + assert(sr.length == 3); + assert(sr.canFind('å') && sr.canFind('Å') && sr.canFind('\u212B')); + }); +} + +/++ + $(P Returns the $(S_LINK Combining class, combining class) of $(D ch).) ++/ +ubyte combiningClass(dchar ch) @safe pure nothrow @nogc +{ + return combiningClassTrie[ch]; +} + +/// +@safe unittest +{ + // shorten the code + alias CC = combiningClass; + + // combining tilda + assert(CC('\u0303') == 230); + // combining ring below + assert(CC('\u0325') == 220); + // the simple consequence is that "tilda" should be + // placed after a "ring below" in a sequence +} + +@safe pure nothrow @nogc unittest +{ + foreach (ch; 0 .. 0x80) + assert(combiningClass(ch) == 0); + assert(combiningClass('\u05BD') == 22); + assert(combiningClass('\u0300') == 230); + assert(combiningClass('\u0317') == 220); + assert(combiningClass('\u1939') == 222); +} + +/// Unicode character decomposition type. +enum UnicodeDecomposition { + /// Canonical decomposition. The result is canonically equivalent sequence. + Canonical, + /** + Compatibility decomposition. The result is compatibility equivalent sequence. + Note: Compatibility decomposition is a $(B lossy) conversion, + typically suitable only for fuzzy matching and internal processing. + */ + Compatibility +} + +/** + Shorthand aliases for character decomposition type, passed as a + template parameter to $(LREF decompose). +*/ +enum { + Canonical = UnicodeDecomposition.Canonical, + Compatibility = UnicodeDecomposition.Compatibility +} + +/++ + Try to canonically compose 2 $(CHARACTERS). + Returns the composed $(CHARACTER) if they do compose and dchar.init otherwise. + + The assumption is that $(D first) comes before $(D second) in the original text, + usually meaning that the first is a starter. + + Note: Hangul syllables are not covered by this function. + See $(D composeJamo) below. ++/ +public dchar compose(dchar first, dchar second) pure nothrow @safe +{ + import std.algorithm.iteration : map; + import std.internal.unicode_comp : compositionTable, composeCntShift, composeIdxMask; + import std.range : assumeSorted; + immutable packed = compositionJumpTrie[first]; + if (packed == ushort.max) + return dchar.init; + // unpack offset and length + immutable idx = packed & composeIdxMask, cnt = packed >> composeCntShift; + // TODO: optimize this micro binary search (no more then 4-5 steps) + auto r = compositionTable[idx .. idx+cnt].map!"a.rhs"().assumeSorted(); + immutable target = r.lowerBound(second).length; + if (target == cnt) + return dchar.init; + immutable entry = compositionTable[idx+target]; + if (entry.rhs != second) + return dchar.init; + return entry.composed; +} + +/// +@safe unittest +{ + assert(compose('A','\u0308') == '\u00C4'); + assert(compose('A', 'B') == dchar.init); + assert(compose('C', '\u0301') == '\u0106'); + // note that the starter is the first one + // thus the following doesn't compose + assert(compose('\u0308', 'A') == dchar.init); +} + +/++ + Returns a full $(S_LINK Canonical decomposition, Canonical) + (by default) or $(S_LINK Compatibility decomposition, Compatibility) + decomposition of $(CHARACTER) $(D ch). + If no decomposition is available returns a $(LREF Grapheme) + with the $(D ch) itself. + + Note: + This function also decomposes hangul syllables + as prescribed by the standard. + + See_Also: $(LREF decomposeHangul) for a restricted version + that takes into account only hangul syllables but + no other decompositions. ++/ +public Grapheme decompose(UnicodeDecomposition decompType=Canonical)(dchar ch) @safe +{ + import std.algorithm.searching : until; + import std.internal.unicode_decomp : decompCompatTable, decompCanonTable; + static if (decompType == Canonical) + { + alias table = decompCanonTable; + alias mapping = canonMappingTrie; + } + else static if (decompType == Compatibility) + { + alias table = decompCompatTable; + alias mapping = compatMappingTrie; + } + immutable idx = mapping[ch]; + if (!idx) // not found, check hangul arithmetic decomposition + return decomposeHangul(ch); + auto decomp = table[idx..$].until(0); + return Grapheme(decomp); +} + +/// +@system unittest +{ + import std.algorithm.comparison : equal; + + assert(compose('A','\u0308') == '\u00C4'); + assert(compose('A', 'B') == dchar.init); + assert(compose('C', '\u0301') == '\u0106'); + // note that the starter is the first one + // thus the following doesn't compose + assert(compose('\u0308', 'A') == dchar.init); + + assert(decompose('Ĉ')[].equal("C\u0302")); + assert(decompose('D')[].equal("D")); + assert(decompose('\uD4DC')[].equal("\u1111\u1171\u11B7")); + assert(decompose!Compatibility('¹')[].equal("1")); +} + +//---------------------------------------------------------------------------- +// Hangul specific composition/decomposition +enum jamoSBase = 0xAC00; +enum jamoLBase = 0x1100; +enum jamoVBase = 0x1161; +enum jamoTBase = 0x11A7; +enum jamoLCount = 19, jamoVCount = 21, jamoTCount = 28; +enum jamoNCount = jamoVCount * jamoTCount; +enum jamoSCount = jamoLCount * jamoNCount; + +// Tests if $(D ch) is a Hangul leading consonant jamo. +bool isJamoL(dchar ch) pure nothrow @nogc @safe +{ + // first cmp rejects ~ 1M code points above leading jamo range + return ch < jamoLBase+jamoLCount && ch >= jamoLBase; +} + +// Tests if $(D ch) is a Hangul vowel jamo. +bool isJamoT(dchar ch) pure nothrow @nogc @safe +{ + // first cmp rejects ~ 1M code points above trailing jamo range + // Note: ch == jamoTBase doesn't indicate trailing jamo (TIndex must be > 0) + return ch < jamoTBase+jamoTCount && ch > jamoTBase; +} + +// Tests if $(D ch) is a Hangul trailnig consonant jamo. +bool isJamoV(dchar ch) pure nothrow @nogc @safe +{ + // first cmp rejects ~ 1M code points above vowel range + return ch < jamoVBase+jamoVCount && ch >= jamoVBase; +} + +int hangulSyllableIndex(dchar ch) pure nothrow @nogc @safe +{ + int idxS = cast(int) ch - jamoSBase; + return idxS >= 0 && idxS < jamoSCount ? idxS : -1; +} + +// internal helper: compose hangul syllables leaving dchar.init in holes +void hangulRecompose(dchar[] seq) pure nothrow @nogc @safe +{ + for (size_t idx = 0; idx + 1 < seq.length; ) + { + if (isJamoL(seq[idx]) && isJamoV(seq[idx+1])) + { + immutable int indexL = seq[idx] - jamoLBase; + immutable int indexV = seq[idx+1] - jamoVBase; + immutable int indexLV = indexL * jamoNCount + indexV * jamoTCount; + if (idx + 2 < seq.length && isJamoT(seq[idx+2])) + { + seq[idx] = jamoSBase + indexLV + seq[idx+2] - jamoTBase; + seq[idx+1] = dchar.init; + seq[idx+2] = dchar.init; + idx += 3; + } + else + { + seq[idx] = jamoSBase + indexLV; + seq[idx+1] = dchar.init; + idx += 2; + } + } + else + idx++; + } +} + +//---------------------------------------------------------------------------- +public: + +/** + Decomposes a Hangul syllable. If $(D ch) is not a composed syllable + then this function returns $(LREF Grapheme) containing only $(D ch) as is. +*/ +Grapheme decomposeHangul(dchar ch) @safe +{ + immutable idxS = cast(int) ch - jamoSBase; + if (idxS < 0 || idxS >= jamoSCount) return Grapheme(ch); + immutable idxL = idxS / jamoNCount; + immutable idxV = (idxS % jamoNCount) / jamoTCount; + immutable idxT = idxS % jamoTCount; + + immutable partL = jamoLBase + idxL; + immutable partV = jamoVBase + idxV; + if (idxT > 0) // there is a trailling consonant (T); <L,V,T> decomposition + return Grapheme(partL, partV, jamoTBase + idxT); + else // <L, V> decomposition + return Grapheme(partL, partV); +} + +/// +@system unittest +{ + import std.algorithm.comparison : equal; + assert(decomposeHangul('\uD4DB')[].equal("\u1111\u1171\u11B6")); +} + +/++ + Try to compose hangul syllable out of a leading consonant ($(D lead)), + a $(D vowel) and optional $(D trailing) consonant jamos. + + On success returns the composed LV or LVT hangul syllable. + + If any of $(D lead) and $(D vowel) are not a valid hangul jamo + of the respective $(CHARACTER) class returns dchar.init. ++/ +dchar composeJamo(dchar lead, dchar vowel, dchar trailing=dchar.init) pure nothrow @nogc @safe +{ + if (!isJamoL(lead)) + return dchar.init; + immutable indexL = lead - jamoLBase; + if (!isJamoV(vowel)) + return dchar.init; + immutable indexV = vowel - jamoVBase; + immutable indexLV = indexL * jamoNCount + indexV * jamoTCount; + immutable dchar syllable = jamoSBase + indexLV; + return isJamoT(trailing) ? syllable + (trailing - jamoTBase) : syllable; +} + +/// +@safe unittest +{ + assert(composeJamo('\u1111', '\u1171', '\u11B6') == '\uD4DB'); + // leaving out T-vowel, or passing any codepoint + // that is not trailing consonant composes an LV-syllable + assert(composeJamo('\u1111', '\u1171') == '\uD4CC'); + assert(composeJamo('\u1111', '\u1171', ' ') == '\uD4CC'); + assert(composeJamo('\u1111', 'A') == dchar.init); + assert(composeJamo('A', '\u1171') == dchar.init); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + import std.conv : text; + + static void testDecomp(UnicodeDecomposition T)(dchar ch, string r) + { + Grapheme g = decompose!T(ch); + assert(equal(g[], r), text(g[], " vs ", r)); + } + testDecomp!Canonical('\u1FF4', "\u03C9\u0301\u0345"); + testDecomp!Canonical('\uF907', "\u9F9C"); + testDecomp!Compatibility('\u33FF', "\u0067\u0061\u006C"); + testDecomp!Compatibility('\uA7F9', "\u0153"); + + // check examples + assert(decomposeHangul('\uD4DB')[].equal("\u1111\u1171\u11B6")); + assert(composeJamo('\u1111', '\u1171', '\u11B6') == '\uD4DB'); + assert(composeJamo('\u1111', '\u1171') == '\uD4CC'); // leave out T-vowel + assert(composeJamo('\u1111', '\u1171', ' ') == '\uD4CC'); + assert(composeJamo('\u1111', 'A') == dchar.init); + assert(composeJamo('A', '\u1171') == dchar.init); +} + +/** + Enumeration type for normalization forms, + passed as template parameter for functions like $(LREF normalize). +*/ +enum NormalizationForm { + NFC, + NFD, + NFKC, + NFKD +} + + +enum { + /** + Shorthand aliases from values indicating normalization forms. + */ + NFC = NormalizationForm.NFC, + ///ditto + NFD = NormalizationForm.NFD, + ///ditto + NFKC = NormalizationForm.NFKC, + ///ditto + NFKD = NormalizationForm.NFKD +} + +/++ + Returns $(D input) string normalized to the chosen form. + Form C is used by default. + + For more information on normalization forms see + the $(S_LINK Normalization, normalization section). + + Note: + In cases where the string in question is already normalized, + it is returned unmodified and no memory allocation happens. ++/ +inout(C)[] normalize(NormalizationForm norm=NFC, C)(inout(C)[] input) +{ + import std.algorithm.mutation : SwapStrategy; + import std.algorithm.sorting : sort; + import std.array : appender; + import std.range : zip; + + auto anchors = splitNormalized!norm(input); + if (anchors[0] == input.length && anchors[1] == input.length) + return input; + dchar[] decomposed; + decomposed.reserve(31); + ubyte[] ccc; + ccc.reserve(31); + auto app = appender!(C[])(); + do + { + app.put(input[0 .. anchors[0]]); + foreach (dchar ch; input[anchors[0]..anchors[1]]) + static if (norm == NFD || norm == NFC) + { + foreach (dchar c; decompose!Canonical(ch)[]) + decomposed ~= c; + } + else // NFKD & NFKC + { + foreach (dchar c; decompose!Compatibility(ch)[]) + decomposed ~= c; + } + ccc.length = decomposed.length; + size_t firstNonStable = 0; + ubyte lastClazz = 0; + + foreach (idx, dchar ch; decomposed) + { + immutable clazz = combiningClass(ch); + ccc[idx] = clazz; + if (clazz == 0 && lastClazz != 0) + { + // found a stable code point after unstable ones + sort!("a[0] < b[0]", SwapStrategy.stable) + (zip(ccc[firstNonStable .. idx], decomposed[firstNonStable .. idx])); + firstNonStable = decomposed.length; + } + else if (clazz != 0 && lastClazz == 0) + { + // found first unstable code point after stable ones + firstNonStable = idx; + } + lastClazz = clazz; + } + sort!("a[0] < b[0]", SwapStrategy.stable) + (zip(ccc[firstNonStable..$], decomposed[firstNonStable..$])); + static if (norm == NFC || norm == NFKC) + { + import std.algorithm.searching : countUntil; + auto first = countUntil(ccc, 0); + if (first >= 0) // no starters?? no recomposition + { + for (;;) + { + immutable second = recompose(first, decomposed, ccc); + if (second == decomposed.length) + break; + first = second; + } + // 2nd pass for hangul syllables + hangulRecompose(decomposed); + } + } + static if (norm == NFD || norm == NFKD) + app.put(decomposed); + else + { + import std.algorithm.mutation : remove; + auto clean = remove!("a == dchar.init", SwapStrategy.stable)(decomposed); + app.put(decomposed[0 .. clean.length]); + } + // reset variables + decomposed.length = 0; + decomposed.assumeSafeAppend(); + ccc.length = 0; + ccc.assumeSafeAppend(); + input = input[anchors[1]..$]; + // and move on + anchors = splitNormalized!norm(input); + }while (anchors[0] != input.length); + app.put(input[0 .. anchors[0]]); + return cast(inout(C)[])app.data; +} + +/// +@safe unittest +{ + // any encoding works + wstring greet = "Hello world"; + assert(normalize(greet) is greet); // the same exact slice + + // An example of a character with all 4 forms being different: + // Greek upsilon with acute and hook symbol (code point 0x03D3) + assert(normalize!NFC("ϓ") == "\u03D3"); + assert(normalize!NFD("ϓ") == "\u03D2\u0301"); + assert(normalize!NFKC("ϓ") == "\u038E"); + assert(normalize!NFKD("ϓ") == "\u03A5\u0301"); +} + +@safe unittest +{ + import std.conv : text; + + assert(normalize!NFD("abc\uF904def") == "abc\u6ED1def", text(normalize!NFD("abc\uF904def"))); + assert(normalize!NFKD("2¹⁰") == "210", normalize!NFKD("2¹⁰")); + assert(normalize!NFD("Äffin") == "A\u0308ffin"); + + // check example + + // any encoding works + wstring greet = "Hello world"; + assert(normalize(greet) is greet); // the same exact slice + + // An example of a character with all 4 forms being different: + // Greek upsilon with acute and hook symbol (code point 0x03D3) + assert(normalize!NFC("ϓ") == "\u03D3"); + assert(normalize!NFD("ϓ") == "\u03D2\u0301"); + assert(normalize!NFKC("ϓ") == "\u038E"); + assert(normalize!NFKD("ϓ") == "\u03A5\u0301"); +} + +// canonically recompose given slice of code points, works in-place and mutates data +private size_t recompose(size_t start, dchar[] input, ubyte[] ccc) pure nothrow @safe +{ + assert(input.length == ccc.length); + int accumCC = -1;// so that it's out of 0 .. 255 range + // writefln("recomposing %( %04x %)", input); + // first one is always a starter thus we start at i == 1 + size_t i = start+1; + for (; ; ) + { + if (i == input.length) + break; + immutable curCC = ccc[i]; + // In any character sequence beginning with a starter S + // a character C is blocked from S if and only if there + // is some character B between S and C, and either B + // is a starter or it has the same or higher combining class as C. + //------------------------ + // Applying to our case: + // S is input[0] + // accumCC is the maximum CCC of characters between C and S, + // as ccc are sorted + // C is input[i] + + if (curCC > accumCC) + { + immutable comp = compose(input[start], input[i]); + if (comp != dchar.init) + { + input[start] = comp; + input[i] = dchar.init;// put a sentinel + // current was merged so its CCC shouldn't affect + // composing with the next one + } + else + { + // if it was a starter then accumCC is now 0, end of loop + accumCC = curCC; + if (accumCC == 0) + break; + } + } + else + { + // ditto here + accumCC = curCC; + if (accumCC == 0) + break; + } + i++; + } + return i; +} + +// returns tuple of 2 indexes that delimit: +// normalized text, piece that needs normalization and +// the rest of input starting with stable code point +private auto splitNormalized(NormalizationForm norm, C)(const(C)[] input) +{ + import std.typecons : tuple; + ubyte lastCC = 0; + + foreach (idx, dchar ch; input) + { + static if (norm == NFC) + if (ch < 0x0300) + { + lastCC = 0; + continue; + } + immutable ubyte CC = combiningClass(ch); + if (lastCC > CC && CC != 0) + { + return seekStable!norm(idx, input); + } + + if (notAllowedIn!norm(ch)) + { + return seekStable!norm(idx, input); + } + lastCC = CC; + } + return tuple(input.length, input.length); +} + +private auto seekStable(NormalizationForm norm, C)(size_t idx, in C[] input) +{ + import std.typecons : tuple; + import std.utf : codeLength; + + auto br = input[0 .. idx]; + size_t region_start = 0;// default + for (;;) + { + if (br.empty)// start is 0 + break; + dchar ch = br.back; + if (combiningClass(ch) == 0 && allowedIn!norm(ch)) + { + region_start = br.length - codeLength!C(ch); + break; + } + br.popFront(); + } + ///@@@BUG@@@ can't use find: " find is a nested function and can't be used..." + size_t region_end=input.length;// end is $ by default + foreach (i, dchar ch; input[idx..$]) + { + if (combiningClass(ch) == 0 && allowedIn!norm(ch)) + { + region_end = i+idx; + break; + } + } + // writeln("Region to normalize: ", input[region_start .. region_end]); + return tuple(region_start, region_end); +} + +/** + Tests if dchar $(D ch) is always allowed (Quick_Check=YES) in normalization + form $(D norm). +*/ +public bool allowedIn(NormalizationForm norm)(dchar ch) +{ + return !notAllowedIn!norm(ch); +} + +/// +@safe unittest +{ + // e.g. Cyrillic is always allowed, so is ASCII + assert(allowedIn!NFC('я')); + assert(allowedIn!NFD('я')); + assert(allowedIn!NFKC('я')); + assert(allowedIn!NFKD('я')); + assert(allowedIn!NFC('Z')); +} + +// not user friendly name but more direct +private bool notAllowedIn(NormalizationForm norm)(dchar ch) +{ + static if (norm == NFC) + alias qcTrie = nfcQCTrie; + else static if (norm == NFD) + alias qcTrie = nfdQCTrie; + else static if (norm == NFKC) + alias qcTrie = nfkcQCTrie; + else static if (norm == NFKD) + alias qcTrie = nfkdQCTrie; + else + static assert("Unknown normalization form "~norm); + return qcTrie[ch]; +} + +@safe unittest +{ + assert(allowedIn!NFC('я')); + assert(allowedIn!NFD('я')); + assert(allowedIn!NFKC('я')); + assert(allowedIn!NFKD('я')); + assert(allowedIn!NFC('Z')); +} + +} + +version (std_uni_bootstrap) +{ + // old version used for bootstrapping of gen_uni.d that generates + // up to date optimal versions of all of isXXX functions + @safe pure nothrow @nogc public bool isWhite(dchar c) + { + import std.ascii : isWhite; + return isWhite(c) || + c == lineSep || c == paraSep || + c == '\u0085' || c == '\u00A0' || c == '\u1680' || c == '\u180E' || + (c >= '\u2000' && c <= '\u200A') || + c == '\u202F' || c == '\u205F' || c == '\u3000'; + } +} +else +{ + +// trusted -> avoid bounds check +@trusted pure nothrow @nogc private +{ + import std.internal.unicode_tables; // : toLowerTable, toTitleTable, toUpperTable; // generated file + + // hide template instances behind functions (Bugzilla 13232) + ushort toLowerIndex(dchar c) { return toLowerIndexTrie[c]; } + ushort toLowerSimpleIndex(dchar c) { return toLowerSimpleIndexTrie[c]; } + dchar toLowerTab(size_t idx) { return toLowerTable[idx]; } + + ushort toTitleIndex(dchar c) { return toTitleIndexTrie[c]; } + ushort toTitleSimpleIndex(dchar c) { return toTitleSimpleIndexTrie[c]; } + dchar toTitleTab(size_t idx) { return toTitleTable[idx]; } + + ushort toUpperIndex(dchar c) { return toUpperIndexTrie[c]; } + ushort toUpperSimpleIndex(dchar c) { return toUpperSimpleIndexTrie[c]; } + dchar toUpperTab(size_t idx) { return toUpperTable[idx]; } +} + +public: + +/++ + Whether or not $(D c) is a Unicode whitespace $(CHARACTER). + (general Unicode category: Part of C0(tab, vertical tab, form feed, + carriage return, and linefeed characters), Zs, Zl, Zp, and NEL(U+0085)) ++/ +@safe pure nothrow @nogc +public bool isWhite(dchar c) +{ + import std.internal.unicode_tables : isWhiteGen; // generated file + return isWhiteGen(c); // call pregenerated binary search +} + +/++ + Return whether $(D c) is a Unicode lowercase $(CHARACTER). ++/ +@safe pure nothrow @nogc +bool isLower(dchar c) +{ + import std.ascii : isLower, isASCII; + if (isASCII(c)) + return isLower(c); + return lowerCaseTrie[c]; +} + +@safe unittest +{ + import std.ascii : isLower; + foreach (v; 0 .. 0x80) + assert(isLower(v) == .isLower(v)); + assert(.isLower('я')); + assert(.isLower('й')); + assert(!.isLower('Ж')); + // Greek HETA + assert(!.isLower('\u0370')); + assert(.isLower('\u0371')); + assert(!.isLower('\u039C')); // capital MU + assert(.isLower('\u03B2')); // beta + // from extended Greek + assert(!.isLower('\u1F18')); + assert(.isLower('\u1F00')); + foreach (v; unicode.lowerCase.byCodepoint) + assert(.isLower(v) && !isUpper(v)); +} + + +/++ + Return whether $(D c) is a Unicode uppercase $(CHARACTER). ++/ +@safe pure nothrow @nogc +bool isUpper(dchar c) +{ + import std.ascii : isUpper, isASCII; + if (isASCII(c)) + return isUpper(c); + return upperCaseTrie[c]; +} + +@safe unittest +{ + import std.ascii : isLower; + foreach (v; 0 .. 0x80) + assert(isLower(v) == .isLower(v)); + assert(!isUpper('й')); + assert(isUpper('Ж')); + // Greek HETA + assert(isUpper('\u0370')); + assert(!isUpper('\u0371')); + assert(isUpper('\u039C')); // capital MU + assert(!isUpper('\u03B2')); // beta + // from extended Greek + assert(!isUpper('\u1F00')); + assert(isUpper('\u1F18')); + foreach (v; unicode.upperCase.byCodepoint) + assert(isUpper(v) && !.isLower(v)); +} + + +//TODO: Hidden for now, needs better API. +//Other transforms could use better API as well, but this one is a new primitive. +@safe pure nothrow @nogc +private dchar toTitlecase(dchar c) +{ + // optimize ASCII case + if (c < 0xAA) + { + if (c < 'a') + return c; + if (c <= 'z') + return c - 32; + return c; + } + size_t idx = toTitleSimpleIndex(c); + if (idx != ushort.max) + { + return toTitleTab(idx); + } + return c; +} + +private alias UpperTriple = AliasSeq!(toUpperIndex, MAX_SIMPLE_UPPER, toUpperTab); +private alias LowerTriple = AliasSeq!(toLowerIndex, MAX_SIMPLE_LOWER, toLowerTab); + +// generic toUpper/toLower on whole string, creates new or returns as is +private S toCase(alias indexFn, uint maxIdx, alias tableFn, alias asciiConvert, S)(S s) @trusted pure +if (isSomeString!S) +{ + import std.array : appender; + import std.ascii : isASCII; + + foreach (i, dchar cOuter; s) + { + ushort idx = indexFn(cOuter); + if (idx == ushort.max) + continue; + auto result = appender!S(s[0 .. i]); + result.reserve(s.length); + foreach (dchar c; s[i .. $]) + { + if (c.isASCII) + { + result.put(asciiConvert(c)); + } + else + { + idx = indexFn(c); + if (idx == ushort.max) + result.put(c); + else if (idx < maxIdx) + { + c = tableFn(idx); + result.put(c); + } + else + { + auto val = tableFn(idx); + // unpack length + codepoint + immutable uint len = val >> 24; + result.put(cast(dchar)(val & 0xFF_FFFF)); + foreach (j; idx+1 .. idx+len) + result.put(tableFn(j)); + } + } + } + return result.data; + } + return s; +} + +@safe unittest //12428 +{ + import std.array : replicate; + auto s = "abcdefghij".replicate(300); + s = s[0 .. 10]; + + toUpper(s); + + assert(s == "abcdefghij"); +} + + +// generic toUpper/toLower on whole range, returns range +private auto toCaser(alias indexFn, uint maxIdx, alias tableFn, alias asciiConvert, Range)(Range str) + // Accept range of dchar's +if (isInputRange!Range && + isSomeChar!(ElementEncodingType!Range) && + ElementEncodingType!Range.sizeof == dchar.sizeof) +{ + static struct ToCaserImpl + { + @property bool empty() + { + return !nLeft && r.empty; + } + + @property auto front() + { + import std.ascii : isASCII; + + if (!nLeft) + { + dchar c = r.front; + if (c.isASCII) + { + buf[0] = asciiConvert(c); + nLeft = 1; + } + else + { + const idx = indexFn(c); + if (idx == ushort.max) + { + buf[0] = c; + nLeft = 1; + } + else if (idx < maxIdx) + { + buf[0] = tableFn(idx); + nLeft = 1; + } + else + { + immutable val = tableFn(idx); + // unpack length + codepoint + nLeft = val >> 24; + if (nLeft == 0) + nLeft = 1; + assert(nLeft <= buf.length); + buf[nLeft - 1] = cast(dchar)(val & 0xFF_FFFF); + foreach (j; 1 .. nLeft) + buf[nLeft - j - 1] = tableFn(idx + j); + } + } + } + return buf[nLeft - 1]; + } + + void popFront() + { + if (!nLeft) + front; + assert(nLeft); + --nLeft; + if (!nLeft) + r.popFront(); + } + + static if (isForwardRange!Range) + { + @property auto save() + { + auto ret = this; + ret.r = r.save; + return ret; + } + } + + private: + Range r; + uint nLeft; + dchar[3] buf = void; + } + + return ToCaserImpl(str); +} + +/********************* + * Convert input range or string to upper or lower case. + * + * Does not allocate memory. + * Characters in UTF-8 or UTF-16 format that cannot be decoded + * are treated as $(REF replacementDchar, std,utf). + * + * Params: + * str = string or range of characters + * + * Returns: + * an InputRange of dchars + * + * See_Also: + * $(LREF toUpper), $(LREF toLower) + */ + +auto asLowerCase(Range)(Range str) +if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) && + !isConvertibleToString!Range) +{ + static if (ElementEncodingType!Range.sizeof < dchar.sizeof) + { + import std.utf : byDchar; + + // Decode first + return asLowerCase(str.byDchar); + } + else + { + static import std.ascii; + return toCaser!(LowerTriple, std.ascii.toLower)(str); + } +} + +/// ditto +auto asUpperCase(Range)(Range str) +if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) && + !isConvertibleToString!Range) +{ + static if (ElementEncodingType!Range.sizeof < dchar.sizeof) + { + import std.utf : byDchar; + + // Decode first + return asUpperCase(str.byDchar); + } + else + { + static import std.ascii; + return toCaser!(UpperTriple, std.ascii.toUpper)(str); + } +} + +/// +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + assert("hEllo".asUpperCase.equal("HELLO")); +} + +// explicitly undocumented +auto asLowerCase(Range)(auto ref Range str) +if (isConvertibleToString!Range) +{ + import std.traits : StringTypeOf; + return asLowerCase!(StringTypeOf!Range)(str); +} + +// explicitly undocumented +auto asUpperCase(Range)(auto ref Range str) +if (isConvertibleToString!Range) +{ + import std.traits : StringTypeOf; + return asUpperCase!(StringTypeOf!Range)(str); +} + +@safe unittest +{ + assert(testAliasedString!asLowerCase("hEllo")); + assert(testAliasedString!asUpperCase("hEllo")); +} + +@safe unittest +{ + import std.array : array; + + auto a = "HELLo".asLowerCase; + auto savea = a.save; + auto s = a.array; + assert(s == "hello"); + s = savea.array; + assert(s == "hello"); + + string[] lower = ["123", "abcфеж", "\u0131\u023f\u03c9", "i\u0307\u1Fe2"]; + string[] upper = ["123", "ABCФЕЖ", "I\u2c7e\u2126", "\u0130\u03A5\u0308\u0300"]; + + foreach (i, slwr; lower) + { + import std.utf : byChar; + + auto sx = slwr.asUpperCase.byChar.array; + assert(sx == toUpper(slwr)); + auto sy = upper[i].asLowerCase.byChar.array; + assert(sy == toLower(upper[i])); + } + + // Not necessary to call r.front + for (auto r = lower[3].asUpperCase; !r.empty; r.popFront()) + { + } + + import std.algorithm.comparison : equal; + + "HELLo"w.asLowerCase.equal("hello"d); + "HELLo"w.asUpperCase.equal("HELLO"d); + "HELLo"d.asLowerCase.equal("hello"d); + "HELLo"d.asUpperCase.equal("HELLO"d); + + import std.utf : byChar; + assert(toLower("\u1Fe2") == asLowerCase("\u1Fe2").byChar.array); +} + +// generic capitalizer on whole range, returns range +private auto toCapitalizer(alias indexFnUpper, uint maxIdxUpper, alias tableFnUpper, + Range)(Range str) + // Accept range of dchar's +if (isInputRange!Range && + isSomeChar!(ElementEncodingType!Range) && + ElementEncodingType!Range.sizeof == dchar.sizeof) +{ + static struct ToCapitalizerImpl + { + @property bool empty() + { + return lower ? lwr.empty : !nLeft && r.empty; + } + + @property auto front() + { + if (lower) + return lwr.front; + + if (!nLeft) + { + immutable dchar c = r.front; + const idx = indexFnUpper(c); + if (idx == ushort.max) + { + buf[0] = c; + nLeft = 1; + } + else if (idx < maxIdxUpper) + { + buf[0] = tableFnUpper(idx); + nLeft = 1; + } + else + { + immutable val = tableFnUpper(idx); + // unpack length + codepoint + nLeft = val >> 24; + if (nLeft == 0) + nLeft = 1; + assert(nLeft <= buf.length); + buf[nLeft - 1] = cast(dchar)(val & 0xFF_FFFF); + foreach (j; 1 .. nLeft) + buf[nLeft - j - 1] = tableFnUpper(idx + j); + } + } + return buf[nLeft - 1]; + } + + void popFront() + { + if (lower) + lwr.popFront(); + else + { + if (!nLeft) + front; + assert(nLeft); + --nLeft; + if (!nLeft) + { + r.popFront(); + lwr = r.asLowerCase(); + lower = true; + } + } + } + + static if (isForwardRange!Range) + { + @property auto save() + { + auto ret = this; + ret.r = r.save; + ret.lwr = lwr.save; + return ret; + } + } + + private: + Range r; + typeof(r.asLowerCase) lwr; // range representing the lower case rest of string + bool lower = false; // false for first character, true for rest of string + dchar[3] buf = void; + uint nLeft = 0; + } + + return ToCapitalizerImpl(str); +} + +/********************* + * Capitalize input range or string, meaning convert the first + * character to upper case and subsequent characters to lower case. + * + * Does not allocate memory. + * Characters in UTF-8 or UTF-16 format that cannot be decoded + * are treated as $(REF replacementDchar, std,utf). + * + * Params: + * str = string or range of characters + * + * Returns: + * an InputRange of dchars + * + * See_Also: + * $(LREF toUpper), $(LREF toLower) + * $(LREF asUpperCase), $(LREF asLowerCase) + */ + +auto asCapitalized(Range)(Range str) +if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) && + !isConvertibleToString!Range) +{ + static if (ElementEncodingType!Range.sizeof < dchar.sizeof) + { + import std.utf : byDchar; + + // Decode first + return toCapitalizer!UpperTriple(str.byDchar); + } + else + { + return toCapitalizer!UpperTriple(str); + } +} + +/// +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + assert("hEllo".asCapitalized.equal("Hello")); +} + +auto asCapitalized(Range)(auto ref Range str) +if (isConvertibleToString!Range) +{ + import std.traits : StringTypeOf; + return asCapitalized!(StringTypeOf!Range)(str); +} + +@safe unittest +{ + assert(testAliasedString!asCapitalized("hEllo")); +} + +@safe pure nothrow @nogc unittest +{ + auto r = "hEllo".asCapitalized(); + assert(r.front == 'H'); +} + +@safe unittest +{ + import std.array : array; + + auto a = "hELLo".asCapitalized; + auto savea = a.save; + auto s = a.array; + assert(s == "Hello"); + s = savea.array; + assert(s == "Hello"); + + string[2][] cases = + [ + ["", ""], + ["h", "H"], + ["H", "H"], + ["3", "3"], + ["123", "123"], + ["h123A", "H123a"], + ["феж", "Феж"], + ["\u1Fe2", "\u03a5\u0308\u0300"], + ]; + + foreach (i; 0 .. cases.length) + { + import std.utf : byChar; + + auto r = cases[i][0].asCapitalized.byChar.array; + auto result = cases[i][1]; + assert(r == result); + } + + // Don't call r.front + for (auto r = "\u1Fe2".asCapitalized; !r.empty; r.popFront()) + { + } + + import std.algorithm.comparison : equal; + + "HELLo"w.asCapitalized.equal("Hello"d); + "hElLO"w.asCapitalized.equal("Hello"d); + "hello"d.asCapitalized.equal("Hello"d); + "HELLO"d.asCapitalized.equal("Hello"d); + + import std.utf : byChar; + assert(asCapitalized("\u0130").byChar.array == asUpperCase("\u0130").byChar.array); +} + +// TODO: helper, I wish std.utf was more flexible (and stright) +private size_t encodeTo(scope char[] buf, size_t idx, dchar c) @trusted pure nothrow @nogc +{ + if (c <= 0x7F) + { + buf[idx] = cast(char) c; + idx++; + } + else if (c <= 0x7FF) + { + buf[idx] = cast(char)(0xC0 | (c >> 6)); + buf[idx+1] = cast(char)(0x80 | (c & 0x3F)); + idx += 2; + } + else if (c <= 0xFFFF) + { + buf[idx] = cast(char)(0xE0 | (c >> 12)); + buf[idx+1] = cast(char)(0x80 | ((c >> 6) & 0x3F)); + buf[idx+2] = cast(char)(0x80 | (c & 0x3F)); + idx += 3; + } + else if (c <= 0x10FFFF) + { + buf[idx] = cast(char)(0xF0 | (c >> 18)); + buf[idx+1] = cast(char)(0x80 | ((c >> 12) & 0x3F)); + buf[idx+2] = cast(char)(0x80 | ((c >> 6) & 0x3F)); + buf[idx+3] = cast(char)(0x80 | (c & 0x3F)); + idx += 4; + } + else + assert(0); + return idx; +} + +@safe unittest +{ + char[] s = "abcd".dup; + size_t i = 0; + i = encodeTo(s, i, 'X'); + assert(s == "Xbcd"); + + i = encodeTo(s, i, cast(dchar)'\u00A9'); + assert(s == "X\xC2\xA9d"); +} + +// TODO: helper, I wish std.utf was more flexible (and stright) +private size_t encodeTo(scope wchar[] buf, size_t idx, dchar c) @trusted pure +{ + import std.utf : UTFException; + if (c <= 0xFFFF) + { + if (0xD800 <= c && c <= 0xDFFF) + throw (new UTFException("Encoding an isolated surrogate code point in UTF-16")).setSequence(c); + buf[idx] = cast(wchar) c; + idx++; + } + else if (c <= 0x10FFFF) + { + buf[idx] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); + buf[idx+1] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00); + idx += 2; + } + else + assert(0); + return idx; +} + +private size_t encodeTo(scope dchar[] buf, size_t idx, dchar c) @trusted pure nothrow @nogc +{ + buf[idx] = c; + idx++; + return idx; +} + +private void toCaseInPlace(alias indexFn, uint maxIdx, alias tableFn, C)(ref C[] s) @trusted pure +if (is(C == char) || is(C == wchar) || is(C == dchar)) +{ + import std.utf : decode, codeLength; + size_t curIdx = 0; + size_t destIdx = 0; + alias slowToCase = toCaseInPlaceAlloc!(indexFn, maxIdx, tableFn); + size_t lastUnchanged = 0; + // in-buffer move of bytes to a new start index + // the trick is that it may not need to copy at all + static size_t moveTo(C[] str, size_t dest, size_t from, size_t to) + { + // Interestingly we may just bump pointer for a while + // then have to copy if a re-cased char was smaller the original + // later we may regain pace with char that got bigger + // In the end it sometimes flip-flops between the 2 cases below + if (dest == from) + return to; + // got to copy + foreach (C c; str[from .. to]) + str[dest++] = c; + return dest; + } + while (curIdx != s.length) + { + size_t startIdx = curIdx; + immutable ch = decode(s, curIdx); + // TODO: special case for ASCII + immutable caseIndex = indexFn(ch); + if (caseIndex == ushort.max) // unchanged, skip over + { + continue; + } + else if (caseIndex < maxIdx) // 1:1 codepoint mapping + { + // previous cased chars had the same length as uncased ones + // thus can just adjust pointer + destIdx = moveTo(s, destIdx, lastUnchanged, startIdx); + lastUnchanged = curIdx; + immutable cased = tableFn(caseIndex); + immutable casedLen = codeLength!C(cased); + if (casedLen + destIdx > curIdx) // no place to fit cased char + { + // switch to slow codepath, where we allocate + return slowToCase(s, startIdx, destIdx); + } + else + { + destIdx = encodeTo(s, destIdx, cased); + } + } + else // 1:m codepoint mapping, slow codepath + { + destIdx = moveTo(s, destIdx, lastUnchanged, startIdx); + lastUnchanged = curIdx; + return slowToCase(s, startIdx, destIdx); + } + assert(destIdx <= curIdx); + } + if (lastUnchanged != s.length) + { + destIdx = moveTo(s, destIdx, lastUnchanged, s.length); + } + s = s[0 .. destIdx]; +} + +// helper to precalculate size of case-converted string +private template toCaseLength(alias indexFn, uint maxIdx, alias tableFn) +{ + size_t toCaseLength(C)(in C[] str) + { + import std.utf : decode, codeLength; + size_t codeLen = 0; + size_t lastNonTrivial = 0; + size_t curIdx = 0; + while (curIdx != str.length) + { + immutable startIdx = curIdx; + immutable ch = decode(str, curIdx); + immutable ushort caseIndex = indexFn(ch); + if (caseIndex == ushort.max) + continue; + else if (caseIndex < maxIdx) + { + codeLen += startIdx - lastNonTrivial; + lastNonTrivial = curIdx; + immutable cased = tableFn(caseIndex); + codeLen += codeLength!C(cased); + } + else + { + codeLen += startIdx - lastNonTrivial; + lastNonTrivial = curIdx; + immutable val = tableFn(caseIndex); + immutable len = val >> 24; + immutable dchar cased = val & 0xFF_FFFF; + codeLen += codeLength!C(cased); + foreach (j; caseIndex+1 .. caseIndex+len) + codeLen += codeLength!C(tableFn(j)); + } + } + if (lastNonTrivial != str.length) + codeLen += str.length - lastNonTrivial; + return codeLen; + } +} + +@safe unittest +{ + alias toLowerLength = toCaseLength!(LowerTriple); + assert(toLowerLength("abcd") == 4); + assert(toLowerLength("аБВгд456") == 10+3); +} + +// slower code path that preallocates and then copies +// case-converted stuf to the new string +private template toCaseInPlaceAlloc(alias indexFn, uint maxIdx, alias tableFn) +{ + void toCaseInPlaceAlloc(C)(ref C[] s, size_t curIdx, + size_t destIdx) @trusted pure + if (is(C == char) || is(C == wchar) || is(C == dchar)) + { + import std.utf : decode; + alias caseLength = toCaseLength!(indexFn, maxIdx, tableFn); + auto trueLength = destIdx + caseLength(s[curIdx..$]); + C[] ns = new C[trueLength]; + ns[0 .. destIdx] = s[0 .. destIdx]; + size_t lastUnchanged = curIdx; + while (curIdx != s.length) + { + immutable startIdx = curIdx; // start of current codepoint + immutable ch = decode(s, curIdx); + immutable caseIndex = indexFn(ch); + if (caseIndex == ushort.max) // skip over + { + continue; + } + else if (caseIndex < maxIdx) // 1:1 codepoint mapping + { + immutable cased = tableFn(caseIndex); + auto toCopy = startIdx - lastUnchanged; + ns[destIdx .. destIdx+toCopy] = s[lastUnchanged .. startIdx]; + lastUnchanged = curIdx; + destIdx += toCopy; + destIdx = encodeTo(ns, destIdx, cased); + } + else // 1:m codepoint mapping, slow codepath + { + auto toCopy = startIdx - lastUnchanged; + ns[destIdx .. destIdx+toCopy] = s[lastUnchanged .. startIdx]; + lastUnchanged = curIdx; + destIdx += toCopy; + auto val = tableFn(caseIndex); + // unpack length + codepoint + immutable uint len = val >> 24; + destIdx = encodeTo(ns, destIdx, cast(dchar)(val & 0xFF_FFFF)); + foreach (j; caseIndex+1 .. caseIndex+len) + destIdx = encodeTo(ns, destIdx, tableFn(j)); + } + } + if (lastUnchanged != s.length) + { + auto toCopy = s.length - lastUnchanged; + ns[destIdx .. destIdx+toCopy] = s[lastUnchanged..$]; + destIdx += toCopy; + } + assert(ns.length == destIdx); + s = ns; + } +} + +/++ + Converts $(D s) to lowercase (by performing Unicode lowercase mapping) in place. + For a few characters string length may increase after the transformation, + in such a case the function reallocates exactly once. + If $(D s) does not have any uppercase characters, then $(D s) is unaltered. ++/ +void toLowerInPlace(C)(ref C[] s) @trusted pure +if (is(C == char) || is(C == wchar) || is(C == dchar)) +{ + toCaseInPlace!(LowerTriple)(s); +} +// overloads for the most common cases to reduce compile time +@safe pure /*TODO nothrow*/ +{ + void toLowerInPlace(ref char[] s) + { toLowerInPlace!char(s); } + void toLowerInPlace(ref wchar[] s) + { toLowerInPlace!wchar(s); } + void toLowerInPlace(ref dchar[] s) + { toLowerInPlace!dchar(s); } +} + +/++ + Converts $(D s) to uppercase (by performing Unicode uppercase mapping) in place. + For a few characters string length may increase after the transformation, + in such a case the function reallocates exactly once. + If $(D s) does not have any lowercase characters, then $(D s) is unaltered. ++/ +void toUpperInPlace(C)(ref C[] s) @trusted pure +if (is(C == char) || is(C == wchar) || is(C == dchar)) +{ + toCaseInPlace!(UpperTriple)(s); +} +// overloads for the most common cases to reduce compile time/code size +@safe pure /*TODO nothrow*/ +{ + void toUpperInPlace(ref char[] s) + { toUpperInPlace!char(s); } + void toUpperInPlace(ref wchar[] s) + { toUpperInPlace!wchar(s); } + void toUpperInPlace(ref dchar[] s) + { toUpperInPlace!dchar(s); } +} + +/++ + If $(D c) is a Unicode uppercase $(CHARACTER), then its lowercase equivalent + is returned. Otherwise $(D c) is returned. + + Warning: certain alphabets like German and Greek have no 1:1 + upper-lower mapping. Use overload of toLower which takes full string instead. ++/ +@safe pure nothrow @nogc +dchar toLower(dchar c) +{ + // optimize ASCII case + if (c < 0xAA) + { + if (c < 'A') + return c; + if (c <= 'Z') + return c + 32; + return c; + } + size_t idx = toLowerSimpleIndex(c); + if (idx != ushort.max) + { + return toLowerTab(idx); + } + return c; +} + +/++ + Returns a string which is identical to $(D s) except that all of its + characters are converted to lowercase (by preforming Unicode lowercase mapping). + If none of $(D s) characters were affected, then $(D s) itself is returned. ++/ +S toLower(S)(S s) @trusted pure +if (isSomeString!S) +{ + static import std.ascii; + return toCase!(LowerTriple, std.ascii.toLower)(s); +} +// overloads for the most common cases to reduce compile time +@safe pure /*TODO nothrow*/ +{ + string toLower(string s) + { return toLower!string(s); } + wstring toLower(wstring s) + { return toLower!wstring(s); } + dstring toLower(dstring s) + { return toLower!dstring(s); } + + @safe unittest + { + // https://issues.dlang.org/show_bug.cgi?id=16663 + + static struct String + { + string data; + alias data this; + } + + void foo() + { + auto u = toLower(String("")); + } + } +} + + +@system unittest //@@@BUG std.format is not @safe +{ + static import std.ascii; + import std.format : format; + foreach (ch; 0 .. 0x80) + assert(std.ascii.toLower(ch) == toLower(ch)); + assert(toLower('Я') == 'я'); + assert(toLower('Δ') == 'δ'); + foreach (ch; unicode.upperCase.byCodepoint) + { + dchar low = ch.toLower(); + assert(low == ch || isLower(low), format("%s -> %s", ch, low)); + } + assert(toLower("АЯ") == "ая"); + + assert("\u1E9E".toLower == "\u00df"); + assert("\u00df".toUpper == "SS"); +} + +//bugzilla 9629 +@safe unittest +{ + wchar[] test = "hello þ world"w.dup; + auto piece = test[6 .. 7]; + toUpperInPlace(piece); + assert(test == "hello Þ world"); +} + + +@safe unittest +{ + import std.algorithm.comparison : cmp; + string s1 = "FoL"; + string s2 = toLower(s1); + assert(cmp(s2, "fol") == 0, s2); + assert(s2 != s1); + + char[] s3 = s1.dup; + toLowerInPlace(s3); + assert(s3 == s2); + + s1 = "A\u0100B\u0101d"; + s2 = toLower(s1); + s3 = s1.dup; + assert(cmp(s2, "a\u0101b\u0101d") == 0); + assert(s2 !is s1); + toLowerInPlace(s3); + assert(s3 == s2); + + s1 = "A\u0460B\u0461d"; + s2 = toLower(s1); + s3 = s1.dup; + assert(cmp(s2, "a\u0461b\u0461d") == 0); + assert(s2 !is s1); + toLowerInPlace(s3); + assert(s3 == s2); + + s1 = "\u0130"; + s2 = toLower(s1); + s3 = s1.dup; + assert(s2 == "i\u0307"); + assert(s2 !is s1); + toLowerInPlace(s3); + assert(s3 == s2); + + // Test on wchar and dchar strings. + assert(toLower("Some String"w) == "some string"w); + assert(toLower("Some String"d) == "some string"d); + + // bugzilla 12455 + dchar c = 'İ'; // '\U0130' LATIN CAPITAL LETTER I WITH DOT ABOVE + assert(isUpper(c)); + assert(toLower(c) == 'i'); + // extend on 12455 reprot - check simple-case toUpper too + c = '\u1f87'; + assert(isLower(c)); + assert(toUpper(c) == '\u1F8F'); +} + + +/++ + If $(D c) is a Unicode lowercase $(CHARACTER), then its uppercase equivalent + is returned. Otherwise $(D c) is returned. + + Warning: + Certain alphabets like German and Greek have no 1:1 + upper-lower mapping. Use overload of toUpper which takes full string instead. + + toUpper can be used as an argument to $(REF map, std,algorithm,iteration) + to produce an algorithm that can convert a range of characters to upper case + without allocating memory. + A string can then be produced by using $(REF copy, std,algorithm,mutation) + to send it to an $(REF appender, std,array). ++/ +@safe pure nothrow @nogc +dchar toUpper(dchar c) +{ + // optimize ASCII case + if (c < 0xAA) + { + if (c < 'a') + return c; + if (c <= 'z') + return c - 32; + return c; + } + size_t idx = toUpperSimpleIndex(c); + if (idx != ushort.max) + { + return toUpperTab(idx); + } + return c; +} + +/// +@system unittest +{ + import std.algorithm.iteration : map; + import std.algorithm.mutation : copy; + import std.array : appender; + + auto abuf = appender!(char[])(); + "hello".map!toUpper.copy(&abuf); + assert(abuf.data == "HELLO"); +} + +@safe unittest +{ + static import std.ascii; + import std.format : format; + foreach (ch; 0 .. 0x80) + assert(std.ascii.toUpper(ch) == toUpper(ch)); + assert(toUpper('я') == 'Я'); + assert(toUpper('δ') == 'Δ'); + auto title = unicode.Titlecase_Letter; + foreach (ch; unicode.lowerCase.byCodepoint) + { + dchar up = ch.toUpper(); + assert(up == ch || isUpper(up) || title[up], + format("%x -> %x", ch, up)); + } +} + +/++ + Returns a string which is identical to $(D s) except that all of its + characters are converted to uppercase (by preforming Unicode uppercase mapping). + If none of $(D s) characters were affected, then $(D s) itself is returned. ++/ +S toUpper(S)(S s) @trusted pure +if (isSomeString!S) +{ + static import std.ascii; + return toCase!(UpperTriple, std.ascii.toUpper)(s); +} +// overloads for the most common cases to reduce compile time +@safe pure /*TODO nothrow*/ +{ + string toUpper(string s) + { return toUpper!string(s); } + wstring toUpper(wstring s) + { return toUpper!wstring(s); } + dstring toUpper(dstring s) + { return toUpper!dstring(s); } + + @safe unittest + { + // https://issues.dlang.org/show_bug.cgi?id=16663 + + static struct String + { + string data; + alias data this; + } + + void foo() + { + auto u = toUpper(String("")); + } + } +} + +@safe unittest +{ + import std.algorithm.comparison : cmp; + + string s1 = "FoL"; + string s2; + char[] s3; + + s2 = toUpper(s1); + s3 = s1.dup; toUpperInPlace(s3); + assert(s3 == s2, s3); + assert(cmp(s2, "FOL") == 0); + assert(s2 !is s1); + + s1 = "a\u0100B\u0101d"; + s2 = toUpper(s1); + s3 = s1.dup; toUpperInPlace(s3); + assert(s3 == s2); + assert(cmp(s2, "A\u0100B\u0100D") == 0); + assert(s2 !is s1); + + s1 = "a\u0460B\u0461d"; + s2 = toUpper(s1); + s3 = s1.dup; toUpperInPlace(s3); + assert(s3 == s2); + assert(cmp(s2, "A\u0460B\u0460D") == 0); + assert(s2 !is s1); +} + +@system unittest +{ + static void doTest(C)(const(C)[] s, const(C)[] trueUp, const(C)[] trueLow) + { + import std.format : format; + string diff = "src: %( %x %)\nres: %( %x %)\ntru: %( %x %)"; + auto low = s.toLower() , up = s.toUpper(); + auto lowInp = s.dup, upInp = s.dup; + lowInp.toLowerInPlace(); + upInp.toUpperInPlace(); + assert(low == trueLow, format(diff, low, trueLow)); + assert(up == trueUp, format(diff, up, trueUp)); + assert(lowInp == trueLow, + format(diff, cast(ubyte[]) s, cast(ubyte[]) lowInp, cast(ubyte[]) trueLow)); + assert(upInp == trueUp, + format(diff, cast(ubyte[]) s, cast(ubyte[]) upInp, cast(ubyte[]) trueUp)); + } + foreach (S; AliasSeq!(dstring, wstring, string)) + { + + S easy = "123"; + S good = "abCФеж"; + S awful = "\u0131\u023f\u2126"; + S wicked = "\u0130\u1FE2"; + auto options = [easy, good, awful, wicked]; + S[] lower = ["123", "abcфеж", "\u0131\u023f\u03c9", "i\u0307\u1Fe2"]; + S[] upper = ["123", "ABCФЕЖ", "I\u2c7e\u2126", "\u0130\u03A5\u0308\u0300"]; + + foreach (val; AliasSeq!(easy, good)) + { + auto e = val.dup; + auto g = e; + e.toUpperInPlace(); + assert(e is g); + e.toLowerInPlace(); + assert(e is g); + } + foreach (i, v; options) + { + doTest(v, upper[i], lower[i]); + } + + // a few combinatorial runs + foreach (i; 0 .. options.length) + foreach (j; i .. options.length) + foreach (k; j .. options.length) + { + auto sample = options[i] ~ options[j] ~ options[k]; + auto sample2 = options[k] ~ options[j] ~ options[i]; + doTest(sample, upper[i] ~ upper[j] ~ upper[k], + lower[i] ~ lower[j] ~ lower[k]); + doTest(sample2, upper[k] ~ upper[j] ~ upper[i], + lower[k] ~ lower[j] ~ lower[i]); + } + } +} + + +/++ + Returns whether $(D c) is a Unicode alphabetic $(CHARACTER) + (general Unicode category: Alphabetic). ++/ +@safe pure nothrow @nogc +bool isAlpha(dchar c) +{ + // optimization + if (c < 0xAA) + { + size_t x = c - 'A'; + if (x <= 'Z' - 'A') + return true; + else + { + x = c - 'a'; + if (x <= 'z'-'a') + return true; + } + return false; + } + + return alphaTrie[c]; +} + +@safe unittest +{ + auto alpha = unicode("Alphabetic"); + foreach (ch; alpha.byCodepoint) + assert(isAlpha(ch)); + foreach (ch; 0 .. 0x4000) + assert((ch in alpha) == isAlpha(ch)); +} + + +/++ + Returns whether $(D c) is a Unicode mark + (general Unicode category: Mn, Me, Mc). ++/ +@safe pure nothrow @nogc +bool isMark(dchar c) +{ + return markTrie[c]; +} + +@safe unittest +{ + auto mark = unicode("Mark"); + foreach (ch; mark.byCodepoint) + assert(isMark(ch)); + foreach (ch; 0 .. 0x4000) + assert((ch in mark) == isMark(ch)); +} + +/++ + Returns whether $(D c) is a Unicode numerical $(CHARACTER) + (general Unicode category: Nd, Nl, No). ++/ +@safe pure nothrow @nogc +bool isNumber(dchar c) +{ + // optimization for ascii case + if (c <= 0x7F) + { + return c >= '0' && c <= '9'; + } + else + { + return numberTrie[c]; + } +} + +@safe unittest +{ + auto n = unicode("N"); + foreach (ch; n.byCodepoint) + assert(isNumber(ch)); + foreach (ch; 0 .. 0x4000) + assert((ch in n) == isNumber(ch)); +} + +/++ + Returns whether $(D c) is a Unicode alphabetic $(CHARACTER) or number. + (general Unicode category: Alphabetic, Nd, Nl, No). + + Params: + c = any Unicode character + Returns: + `true` if the character is in the Alphabetic, Nd, Nl, or No Unicode + categories ++/ +@safe pure nothrow @nogc +bool isAlphaNum(dchar c) +{ + static import std.ascii; + + // optimization for ascii case + if (std.ascii.isASCII(c)) + { + return std.ascii.isAlphaNum(c); + } + else + { + return isAlpha(c) || isNumber(c); + } +} + +@safe unittest +{ + auto n = unicode("N"); + auto alpha = unicode("Alphabetic"); + + foreach (ch; n.byCodepoint) + assert(isAlphaNum(ch)); + + foreach (ch; alpha.byCodepoint) + assert(isAlphaNum(ch)); + + foreach (ch; 0 .. 0x4000) + { + assert(((ch in n) || (ch in alpha)) == isAlphaNum(ch)); + } +} + +/++ + Returns whether $(D c) is a Unicode punctuation $(CHARACTER) + (general Unicode category: Pd, Ps, Pe, Pc, Po, Pi, Pf). ++/ +@safe pure nothrow @nogc +bool isPunctuation(dchar c) +{ + static import std.ascii; + + // optimization for ascii case + if (c <= 0x7F) + { + return std.ascii.isPunctuation(c); + } + else + { + return punctuationTrie[c]; + } +} + +@safe unittest +{ + assert(isPunctuation('\u0021')); + assert(isPunctuation('\u0028')); + assert(isPunctuation('\u0029')); + assert(isPunctuation('\u002D')); + assert(isPunctuation('\u005F')); + assert(isPunctuation('\u00AB')); + assert(isPunctuation('\u00BB')); + foreach (ch; unicode("P").byCodepoint) + assert(isPunctuation(ch)); +} + +/++ + Returns whether $(D c) is a Unicode symbol $(CHARACTER) + (general Unicode category: Sm, Sc, Sk, So). ++/ +@safe pure nothrow @nogc +bool isSymbol(dchar c) +{ + return symbolTrie[c]; +} + +@safe unittest +{ + import std.format : format; + assert(isSymbol('\u0024')); + assert(isSymbol('\u002B')); + assert(isSymbol('\u005E')); + assert(isSymbol('\u00A6')); + foreach (ch; unicode("S").byCodepoint) + assert(isSymbol(ch), format("%04x", ch)); +} + +/++ + Returns whether $(D c) is a Unicode space $(CHARACTER) + (general Unicode category: Zs) + Note: This doesn't include '\n', '\r', \t' and other non-space $(CHARACTER). + For commonly used less strict semantics see $(LREF isWhite). ++/ +@safe pure nothrow @nogc +bool isSpace(dchar c) +{ + import std.internal.unicode_tables : isSpaceGen; // generated file + return isSpaceGen(c); +} + +@safe unittest +{ + assert(isSpace('\u0020')); + auto space = unicode.Zs; + foreach (ch; space.byCodepoint) + assert(isSpace(ch)); + foreach (ch; 0 .. 0x1000) + assert(isSpace(ch) == space[ch]); +} + + +/++ + Returns whether $(D c) is a Unicode graphical $(CHARACTER) + (general Unicode category: L, M, N, P, S, Zs). + ++/ +@safe pure nothrow @nogc +bool isGraphical(dchar c) +{ + return graphicalTrie[c]; +} + + +@safe unittest +{ + auto set = unicode("Graphical"); + import std.format : format; + foreach (ch; set.byCodepoint) + assert(isGraphical(ch), format("%4x", ch)); + foreach (ch; 0 .. 0x4000) + assert((ch in set) == isGraphical(ch)); +} + + +/++ + Returns whether $(D c) is a Unicode control $(CHARACTER) + (general Unicode category: Cc). ++/ +@safe pure nothrow @nogc +bool isControl(dchar c) +{ + import std.internal.unicode_tables : isControlGen; // generated file + return isControlGen(c); +} + +@safe unittest +{ + assert(isControl('\u0000')); + assert(isControl('\u0081')); + assert(!isControl('\u0100')); + auto cc = unicode.Cc; + foreach (ch; cc.byCodepoint) + assert(isControl(ch)); + foreach (ch; 0 .. 0x1000) + assert(isControl(ch) == cc[ch]); +} + + +/++ + Returns whether $(D c) is a Unicode formatting $(CHARACTER) + (general Unicode category: Cf). ++/ +@safe pure nothrow @nogc +bool isFormat(dchar c) +{ + import std.internal.unicode_tables : isFormatGen; // generated file + return isFormatGen(c); +} + + +@safe unittest +{ + assert(isFormat('\u00AD')); + foreach (ch; unicode("Format").byCodepoint) + assert(isFormat(ch)); +} + +// code points for private use, surrogates are not likely to change in near feature +// if need be they can be generated from unicode data as well + +/++ + Returns whether $(D c) is a Unicode Private Use $(CODEPOINT) + (general Unicode category: Co). ++/ +@safe pure nothrow @nogc +bool isPrivateUse(dchar c) +{ + return (0x00_E000 <= c && c <= 0x00_F8FF) + || (0x0F_0000 <= c && c <= 0x0F_FFFD) + || (0x10_0000 <= c && c <= 0x10_FFFD); +} + +/++ + Returns whether $(D c) is a Unicode surrogate $(CODEPOINT) + (general Unicode category: Cs). ++/ +@safe pure nothrow @nogc +bool isSurrogate(dchar c) +{ + return (0xD800 <= c && c <= 0xDFFF); +} + +/++ + Returns whether $(D c) is a Unicode high surrogate (lead surrogate). ++/ +@safe pure nothrow @nogc +bool isSurrogateHi(dchar c) +{ + return (0xD800 <= c && c <= 0xDBFF); +} + +/++ + Returns whether $(D c) is a Unicode low surrogate (trail surrogate). ++/ +@safe pure nothrow @nogc +bool isSurrogateLo(dchar c) +{ + return (0xDC00 <= c && c <= 0xDFFF); +} + +/++ + Returns whether $(D c) is a Unicode non-character i.e. + a $(CODEPOINT) with no assigned abstract character. + (general Unicode category: Cn) ++/ +@safe pure nothrow @nogc +bool isNonCharacter(dchar c) +{ + return nonCharacterTrie[c]; +} + +@safe unittest +{ + auto set = unicode("Cn"); + foreach (ch; set.byCodepoint) + assert(isNonCharacter(ch)); +} + +private: +// load static data from pre-generated tables into usable datastructures + + +@safe auto asSet(const (ubyte)[] compressed) pure +{ + return CodepointSet.fromIntervals(decompressIntervals(compressed)); +} + +@safe pure nothrow auto asTrie(T...)(in TrieEntry!T e) +{ + return const(CodepointTrie!T)(e.offsets, e.sizes, e.data); +} + +@safe pure nothrow @nogc @property +{ + import std.internal.unicode_tables; // generated file + + // It's important to use auto return here, so that the compiler + // only runs semantic on the return type if the function gets + // used. Also these are functions rather than templates to not + // increase the object size of the caller. + auto lowerCaseTrie() { static immutable res = asTrie(lowerCaseTrieEntries); return res; } + auto upperCaseTrie() { static immutable res = asTrie(upperCaseTrieEntries); return res; } + auto simpleCaseTrie() { static immutable res = asTrie(simpleCaseTrieEntries); return res; } + auto fullCaseTrie() { static immutable res = asTrie(fullCaseTrieEntries); return res; } + auto alphaTrie() { static immutable res = asTrie(alphaTrieEntries); return res; } + auto markTrie() { static immutable res = asTrie(markTrieEntries); return res; } + auto numberTrie() { static immutable res = asTrie(numberTrieEntries); return res; } + auto punctuationTrie() { static immutable res = asTrie(punctuationTrieEntries); return res; } + auto symbolTrie() { static immutable res = asTrie(symbolTrieEntries); return res; } + auto graphicalTrie() { static immutable res = asTrie(graphicalTrieEntries); return res; } + auto nonCharacterTrie() { static immutable res = asTrie(nonCharacterTrieEntries); return res; } + + //normalization quick-check tables + auto nfcQCTrie() + { + import std.internal.unicode_norm : nfcQCTrieEntries; + static immutable res = asTrie(nfcQCTrieEntries); + return res; + } + + auto nfdQCTrie() + { + import std.internal.unicode_norm : nfdQCTrieEntries; + static immutable res = asTrie(nfdQCTrieEntries); + return res; + } + + auto nfkcQCTrie() + { + import std.internal.unicode_norm : nfkcQCTrieEntries; + static immutable res = asTrie(nfkcQCTrieEntries); + return res; + } + + auto nfkdQCTrie() + { + import std.internal.unicode_norm : nfkdQCTrieEntries; + static immutable res = asTrie(nfkdQCTrieEntries); + return res; + } + + //grapheme breaking algorithm tables + auto mcTrie() + { + import std.internal.unicode_grapheme : mcTrieEntries; + static immutable res = asTrie(mcTrieEntries); + return res; + } + + auto graphemeExtendTrie() + { + import std.internal.unicode_grapheme : graphemeExtendTrieEntries; + static immutable res = asTrie(graphemeExtendTrieEntries); + return res; + } + + auto hangLV() + { + import std.internal.unicode_grapheme : hangulLVTrieEntries; + static immutable res = asTrie(hangulLVTrieEntries); + return res; + } + + auto hangLVT() + { + import std.internal.unicode_grapheme : hangulLVTTrieEntries; + static immutable res = asTrie(hangulLVTTrieEntries); + return res; + } + + // tables below are used for composition/decomposition + auto combiningClassTrie() + { + import std.internal.unicode_comp : combiningClassTrieEntries; + static immutable res = asTrie(combiningClassTrieEntries); + return res; + } + + auto compatMappingTrie() + { + import std.internal.unicode_decomp : compatMappingTrieEntries; + static immutable res = asTrie(compatMappingTrieEntries); + return res; + } + + auto canonMappingTrie() + { + import std.internal.unicode_decomp : canonMappingTrieEntries; + static immutable res = asTrie(canonMappingTrieEntries); + return res; + } + + auto compositionJumpTrie() + { + import std.internal.unicode_comp : compositionJumpTrieEntries; + static immutable res = asTrie(compositionJumpTrieEntries); + return res; + } + + //case conversion tables + auto toUpperIndexTrie() { static immutable res = asTrie(toUpperIndexTrieEntries); return res; } + auto toLowerIndexTrie() { static immutable res = asTrie(toLowerIndexTrieEntries); return res; } + auto toTitleIndexTrie() { static immutable res = asTrie(toTitleIndexTrieEntries); return res; } + //simple case conversion tables + auto toUpperSimpleIndexTrie() { static immutable res = asTrie(toUpperSimpleIndexTrieEntries); return res; } + auto toLowerSimpleIndexTrie() { static immutable res = asTrie(toLowerSimpleIndexTrieEntries); return res; } + auto toTitleSimpleIndexTrie() { static immutable res = asTrie(toTitleSimpleIndexTrieEntries); return res; } + +} + +}// version (!std_uni_bootstrap) diff --git a/libphobos/src/std/uri.d b/libphobos/src/std/uri.d new file mode 100644 index 0000000..0852955 --- /dev/null +++ b/libphobos/src/std/uri.d @@ -0,0 +1,592 @@ +// Written in the D programming language. + +/** + * Encode and decode Uniform Resource Identifiers (URIs). + * URIs are used in internet transfer protocols. + * Valid URI characters consist of letters, digits, + * and the characters $(B ;/?:@&=+$,-_.!~*'()) + * Reserved URI characters are $(B ;/?:@&=+$,) + * Escape sequences consist of $(B %) followed by two hex digits. + * + * See_Also: + * $(LINK2 http://www.ietf.org/rfc/rfc3986.txt, RFC 3986)<br> + * $(LINK2 http://en.wikipedia.org/wiki/Uniform_resource_identifier, Wikipedia) + * Copyright: Copyright Digital Mars 2000 - 2009. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: $(HTTP digitalmars.com, Walter Bright) + * Source: $(PHOBOSSRC std/_uri.d) + */ +/* Copyright Digital Mars 2000 - 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) + */ +module std.uri; + +//debug=uri; // uncomment to turn on debugging writefln's +debug(uri) import std.stdio; +import std.traits : isSomeChar; + +/** This Exception is thrown if something goes wrong when encoding or +decoding a URI. +*/ +class URIException : Exception +{ + import std.exception : basicExceptionCtors; + mixin basicExceptionCtors; +} + +private enum +{ + URI_Alpha = 1, + URI_Reserved = 2, + URI_Mark = 4, + URI_Digit = 8, + URI_Hash = 0x10, // '#' +} + +private immutable char[16] hex2ascii = "0123456789ABCDEF"; + +private immutable ubyte[128] uri_flags = // indexed by character + ({ + ubyte[128] uflags; + + // Compile time initialize + uflags['#'] |= URI_Hash; + + foreach (c; 'A' .. 'Z' + 1) + { + uflags[c] |= URI_Alpha; + uflags[c + 0x20] |= URI_Alpha; // lowercase letters + } + foreach (c; '0' .. '9' + 1) uflags[c] |= URI_Digit; + foreach (c; ";/?:@&=+$,") uflags[c] |= URI_Reserved; + foreach (c; "-_.!~*'()") uflags[c] |= URI_Mark; + return uflags; + })(); + +private string URI_Encode(dstring str, uint unescapedSet) +{ + import core.exception : OutOfMemoryError; + import core.stdc.stdlib : alloca; + + uint j; + uint k; + dchar V; + dchar C; + + // result buffer + char[50] buffer = void; + char* R; + uint Rlen; + uint Rsize; // alloc'd size + + immutable len = str.length; + + R = buffer.ptr; + Rsize = buffer.length; + Rlen = 0; + + for (k = 0; k != len; k++) + { + C = str[k]; + // if (C in unescapedSet) + if (C < uri_flags.length && uri_flags[C] & unescapedSet) + { + if (Rlen == Rsize) + { + char* R2; + + Rsize *= 2; + if (Rsize > 1024) + { + R2 = (new char[Rsize]).ptr; + } + else + { + R2 = cast(char *) alloca(Rsize * char.sizeof); + if (!R2) + throw new OutOfMemoryError("Alloca failure"); + } + R2[0 .. Rlen] = R[0 .. Rlen]; + R = R2; + } + R[Rlen] = cast(char) C; + Rlen++; + } + else + { + char[6] Octet; + uint L; + + V = C; + + // Transform V into octets + if (V <= 0x7F) + { + Octet[0] = cast(char) V; + L = 1; + } + else if (V <= 0x7FF) + { + Octet[0] = cast(char)(0xC0 | (V >> 6)); + Octet[1] = cast(char)(0x80 | (V & 0x3F)); + L = 2; + } + else if (V <= 0xFFFF) + { + Octet[0] = cast(char)(0xE0 | (V >> 12)); + Octet[1] = cast(char)(0x80 | ((V >> 6) & 0x3F)); + Octet[2] = cast(char)(0x80 | (V & 0x3F)); + L = 3; + } + else if (V <= 0x1FFFFF) + { + Octet[0] = cast(char)(0xF0 | (V >> 18)); + Octet[1] = cast(char)(0x80 | ((V >> 12) & 0x3F)); + Octet[2] = cast(char)(0x80 | ((V >> 6) & 0x3F)); + Octet[3] = cast(char)(0x80 | (V & 0x3F)); + L = 4; + } + else + { + throw new URIException("Undefined UTF-32 code point"); + } + + if (Rlen + L * 3 > Rsize) + { + char *R2; + + Rsize = 2 * (Rlen + L * 3); + if (Rsize > 1024) + { + R2 = (new char[Rsize]).ptr; + } + else + { + R2 = cast(char *) alloca(Rsize * char.sizeof); + if (!R2) + throw new OutOfMemoryError("Alloca failure"); + } + R2[0 .. Rlen] = R[0 .. Rlen]; + R = R2; + } + + for (j = 0; j < L; j++) + { + R[Rlen] = '%'; + R[Rlen + 1] = hex2ascii[Octet[j] >> 4]; + R[Rlen + 2] = hex2ascii[Octet[j] & 15]; + + Rlen += 3; + } + } + } + + return R[0 .. Rlen].idup; +} + +private uint ascii2hex(dchar c) @nogc @safe pure nothrow +{ + return (c <= '9') ? c - '0' : + (c <= 'F') ? c - 'A' + 10 : + c - 'a' + 10; +} + +private dstring URI_Decode(Char)(in Char[] uri, uint reservedSet) +if (isSomeChar!Char) +{ + import core.exception : OutOfMemoryError; + import core.stdc.stdlib : alloca; + import std.ascii : isHexDigit; + + uint j; + uint k; + uint V; + dchar C; + + // Result array, allocated on stack + dchar* R; + uint Rlen; + + immutable len = uri.length; + auto s = uri.ptr; + + // Preallocate result buffer R guaranteed to be large enough for result + auto Rsize = len; + if (Rsize > 1024 / dchar.sizeof) + { + R = (new dchar[Rsize]).ptr; + } + else + { + R = cast(dchar *) alloca(Rsize * dchar.sizeof); + if (!R) + throw new OutOfMemoryError("Alloca failure"); + } + Rlen = 0; + + for (k = 0; k != len; k++) + { + char B; + uint start; + + C = s[k]; + if (C != '%') + { + R[Rlen] = C; + Rlen++; + continue; + } + start = k; + if (k + 2 >= len) + throw new URIException("Unexpected end of URI"); + if (!isHexDigit(s[k + 1]) || !isHexDigit(s[k + 2])) + throw new URIException("Expected two hexadecimal digits after '%'"); + B = cast(char)((ascii2hex(s[k + 1]) << 4) + ascii2hex(s[k + 2])); + k += 2; + if ((B & 0x80) == 0) + { + C = B; + } + else + { + uint n; + + for (n = 1; ; n++) + { + if (n > 4) + throw new URIException("UTF-32 code point size too large"); + if (((B << n) & 0x80) == 0) + { + if (n == 1) + throw new URIException("UTF-32 code point size too small"); + break; + } + } + + // Pick off (7 - n) significant bits of B from first byte of octet + V = B & ((1 << (7 - n)) - 1); // (!!!) + + if (k + (3 * (n - 1)) >= len) + throw new URIException("UTF-32 unaligned String"); + for (j = 1; j != n; j++) + { + k++; + if (s[k] != '%') + throw new URIException("Expected: '%'"); + if (!isHexDigit(s[k + 1]) || !isHexDigit(s[k + 2])) + throw new URIException("Expected two hexadecimal digits after '%'"); + B = cast(char)((ascii2hex(s[k + 1]) << 4) + ascii2hex(s[k + 2])); + if ((B & 0xC0) != 0x80) + throw new URIException("Incorrect UTF-32 multi-byte sequence"); + k += 2; + V = (V << 6) | (B & 0x3F); + } + if (V > 0x10FFFF) + throw new URIException("Unknown UTF-32 code point"); + C = V; + } + if (C < uri_flags.length && uri_flags[C] & reservedSet) + { + // R ~= s[start .. k + 1]; + immutable width = (k + 1) - start; + for (int ii = 0; ii < width; ii++) + R[Rlen + ii] = s[start + ii]; + Rlen += width; + } + else + { + R[Rlen] = C; + Rlen++; + } + } + assert(Rlen <= Rsize); // enforce our preallocation size guarantee + + // Copy array on stack to array in memory + return R[0 .. Rlen].idup; +} + +/************************************* + * Decodes the URI string encodedURI into a UTF-8 string and returns it. + * Escape sequences that resolve to reserved URI characters are not replaced. + * Escape sequences that resolve to the '#' character are not replaced. + */ + +string decode(Char)(in Char[] encodedURI) +if (isSomeChar!Char) +{ + import std.algorithm.iteration : each; + import std.utf : encode; + auto s = URI_Decode(encodedURI, URI_Reserved | URI_Hash); + char[] r; + s.each!(c => encode(r, c)); + return r; +} + +/******************************* + * Decodes the URI string encodedURI into a UTF-8 string and returns it. All + * escape sequences are decoded. + */ + +string decodeComponent(Char)(in Char[] encodedURIComponent) +if (isSomeChar!Char) +{ + import std.algorithm.iteration : each; + import std.utf : encode; + auto s = URI_Decode(encodedURIComponent, 0); + char[] r; + s.each!(c => encode(r, c)); + return r; +} + +/***************************** + * Encodes the UTF-8 string uri into a URI and returns that URI. Any character + * not a valid URI character is escaped. The '#' character is not escaped. + */ + +string encode(Char)(in Char[] uri) +if (isSomeChar!Char) +{ + import std.utf : toUTF32; + auto s = toUTF32(uri); + return URI_Encode(s, URI_Reserved | URI_Hash | URI_Alpha | URI_Digit | URI_Mark); +} + +/******************************** + * Encodes the UTF-8 string uriComponent into a URI and returns that URI. + * Any character not a letter, digit, or one of -_.!~*'() is escaped. + */ + +string encodeComponent(Char)(in Char[] uriComponent) +if (isSomeChar!Char) +{ + import std.utf : toUTF32; + auto s = toUTF32(uriComponent); + return URI_Encode(s, URI_Alpha | URI_Digit | URI_Mark); +} + +/* Encode associative array using www-form-urlencoding + * + * Params: + * values = an associative array containing the values to be encoded. + * + * Returns: + * A string encoded using www-form-urlencoding. + */ +package string urlEncode(in string[string] values) +{ + if (values.length == 0) + return ""; + + import std.array : Appender; + import std.format : formattedWrite; + + Appender!string enc; + enc.reserve(values.length * 128); + + bool first = true; + foreach (k, v; values) + { + if (!first) + enc.put('&'); + formattedWrite(enc, "%s=%s", encodeComponent(k), encodeComponent(v)); + first = false; + } + return enc.data; +} + +@system unittest +{ + // @system because urlEncode -> encodeComponent -> URI_Encode + // URI_Encode uses alloca and pointer slicing + string[string] a; + assert(urlEncode(a) == ""); + assert(urlEncode(["name1" : "value1"]) == "name1=value1"); + assert(urlEncode(["name1" : "value1", "name2" : "value2"]) == "name1=value1&name2=value2"); +} + +/*************************** + * Does string s[] start with a URL? + * Returns: + * -1 it does not + * len it does, and s[0 .. len] is the slice of s[] that is that URL + */ + +ptrdiff_t uriLength(Char)(in Char[] s) +if (isSomeChar!Char) +{ + /* Must start with one of: + * http:// + * https:// + * www. + */ + import std.ascii : isAlphaNum; + import std.uni : icmp; + + ptrdiff_t i; + + if (s.length <= 4) + return -1; + + if (s.length > 7 && icmp(s[0 .. 7], "http://") == 0) + { + i = 7; + } + else + { + if (s.length > 8 && icmp(s[0 .. 8], "https://") == 0) + i = 8; + else + return -1; + } + + ptrdiff_t lastdot; + for (; i < s.length; i++) + { + auto c = s[i]; + if (isAlphaNum(c)) + continue; + if (c == '-' || c == '_' || c == '?' || + c == '=' || c == '%' || c == '&' || + c == '/' || c == '+' || c == '#' || + c == '~' || c == '$') + continue; + if (c == '.') + { + lastdot = i; + continue; + } + break; + } + if (!lastdot) + return -1; + + return i; +} + +/// +@safe unittest +{ + string s1 = "http://www.digitalmars.com/~fred/fredsRX.html#foo end!"; + assert(uriLength(s1) == 49); + string s2 = "no uri here"; + assert(uriLength(s2) == -1); + assert(uriLength("issue 14924") < 0); +} + + +/*************************** + * Does string s[] start with an email address? + * Returns: + * -1 it does not + * len it does, and s[0 .. i] is the slice of s[] that is that email address + * References: + * RFC2822 + */ +ptrdiff_t emailLength(Char)(in Char[] s) +if (isSomeChar!Char) +{ + import std.ascii : isAlpha, isAlphaNum; + + ptrdiff_t i; + + if (!isAlpha(s[0])) + return -1; + + for (i = 1; 1; i++) + { + if (i == s.length) + return -1; + auto c = s[i]; + if (isAlphaNum(c)) + continue; + if (c == '-' || c == '_' || c == '.') + continue; + if (c != '@') + return -1; + i++; + break; + } + + /* Now do the part past the '@' + */ + ptrdiff_t lastdot; + for (; i < s.length; i++) + { + auto c = s[i]; + if (isAlphaNum(c)) + continue; + if (c == '-' || c == '_') + continue; + if (c == '.') + { + lastdot = i; + continue; + } + break; + } + if (!lastdot || (i - lastdot != 3 && i - lastdot != 4)) + return -1; + + return i; +} + +/// +@safe unittest +{ + string s1 = "my.e-mail@www.example-domain.com with garbage added"; + assert(emailLength(s1) == 32); + string s2 = "no email address here"; + assert(emailLength(s2) == -1); + assert(emailLength("issue 14924") < 0); +} + + +@system unittest +{ + //@system because of encode -> URI_Encode + debug(uri) writeln("uri.encodeURI.unittest"); + + string source = "http://www.digitalmars.com/~fred/fred's RX.html#foo"; + string target = "http://www.digitalmars.com/~fred/fred's%20RX.html#foo"; + + auto result = encode(source); + debug(uri) writefln("result = '%s'", result); + assert(result == target); + result = decode(target); + debug(uri) writefln("result = '%s'", result); + assert(result == source); + + result = encode(decode("%E3%81%82%E3%81%82")); + assert(result == "%E3%81%82%E3%81%82"); + + result = encodeComponent("c++"); + assert(result == "c%2B%2B"); + + auto str = new char[10_000_000]; + str[] = 'A'; + result = encodeComponent(str); + foreach (char c; result) + assert(c == 'A'); + + result = decode("%41%42%43"); + debug(uri) writeln(result); + + import std.meta : AliasSeq; + foreach (StringType; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + { + import std.conv : to; + StringType decoded1 = source.to!StringType; + string encoded1 = encode(decoded1); + assert(decoded1 == source.to!StringType); // check that `decoded1` wasn't changed + assert(encoded1 == target); + assert(decoded1 == decode(encoded1).to!StringType); + + StringType encoded2 = target.to!StringType; + string decoded2 = decode(encoded2); + assert(encoded2 == target.to!StringType); // check that `encoded2` wasn't changed + assert(decoded2 == source); + assert(encoded2 == encode(decoded2).to!StringType); + } +} diff --git a/libphobos/src/std/utf.d b/libphobos/src/std/utf.d new file mode 100644 index 0000000..63ae736 --- /dev/null +++ b/libphobos/src/std/utf.d @@ -0,0 +1,4058 @@ +// Written in the D programming language. + +/++ + Encode and decode UTF-8, UTF-16 and UTF-32 strings. + + UTF character support is restricted to + $(D '\u0000' <= character <= '\U0010FFFF'). + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE, +$(TR $(TH Category) $(TH Functions)) +$(TR $(TD Decode) $(TD + $(LREF decode) + $(LREF decodeFront) +)) +$(TR $(TD Lazy decode) $(TD + $(LREF byCodeUnit) + $(LREF byChar) + $(LREF byWchar) + $(LREF byDchar) + $(LREF byUTF) +)) +$(TR $(TD Encode) $(TD + $(LREF encode) + $(LREF toUTF8) + $(LREF toUTF16) + $(LREF toUTF32) + $(LREF toUTFz) + $(LREF toUTF16z) +)) +$(TR $(TD Length) $(TD + $(LREF codeLength) + $(LREF count) + $(LREF stride) + $(LREF strideBack) +)) +$(TR $(TD Index) $(TD + $(LREF toUCSindex) + $(LREF toUTFindex) +)) +$(TR $(TD Validation) $(TD + $(LREF isValidDchar) + $(LREF validate) +)) +$(TR $(TD Miscellaneous) $(TD + $(LREF replacementDchar) + $(LREF UseReplacementDchar) + $(LREF UTFException) +)) +) + See_Also: + $(LINK2 http://en.wikipedia.org/wiki/Unicode, Wikipedia)<br> + $(LINK http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8)<br> + $(LINK http://anubis.dkuug.dk/JTC1/SC2/WG2/docs/n1335) + Copyright: Copyright Digital Mars 2000 - 2012. + License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + Authors: $(HTTP digitalmars.com, Walter Bright) and Jonathan M Davis + Source: $(PHOBOSSRC std/_utf.d) + +/ +module std.utf; + +import std.exception; // basicExceptionCtors +import std.meta; // AliasSeq +import std.range.primitives; +import std.traits; // isSomeChar, isSomeString +import std.typecons; // Flag, Yes, No + + +/++ + Exception thrown on errors in std.utf functions. + +/ +class UTFException : Exception +{ + import core.internal.string : unsignedToTempString, UnsignedStringBuf; + + uint[4] sequence; + size_t len; + + @safe pure nothrow @nogc + UTFException setSequence(scope uint[] data...) + { + assert(data.length <= 4); + + len = data.length < 4 ? data.length : 4; + sequence[0 .. len] = data[0 .. len]; + + return this; + } + + // FIXME: Use std.exception.basicExceptionCtors here once bug #11500 is fixed + + this(string msg, string file = __FILE__, size_t line = __LINE__, + Throwable next = null) @nogc @safe pure nothrow + { + super(msg, file, line, next); + } + + this(string msg, size_t index, string file = __FILE__, + size_t line = __LINE__, Throwable next = null) @safe pure nothrow + { + UnsignedStringBuf buf = void; + msg ~= " (at index " ~ unsignedToTempString(index, buf, 10) ~ ")"; + super(msg, file, line, next); + } + + + override string toString() const + { + if (len == 0) + { + /* Exception.toString() is not marked as const, although + * it is const-compatible. + */ + //return super.toString(); + auto e = () @trusted { return cast(Exception) super; } (); + return e.toString(); + } + + string result = "Invalid UTF sequence:"; + + foreach (i; sequence[0 .. len]) + { + UnsignedStringBuf buf = void; + result ~= ' '; + auto h = unsignedToTempString(i, buf, 16); + if (h.length == 1) + result ~= '0'; + result ~= h; + result ~= 'x'; + } + + if (super.msg.length > 0) + { + result ~= " - "; + result ~= super.msg; + } + + return result; + } +} + +/* + Provide array of invalidly encoded UTF strings. Useful for testing. + + Params: + Char = char, wchar, or dchar + + Returns: + an array of invalidly encoded UTF strings + */ + +package auto invalidUTFstrings(Char)() @safe pure @nogc nothrow +if (isSomeChar!Char) +{ + static if (is(Char == char)) + { + enum x = 0xDC00; // invalid surrogate value + enum y = 0x110000; // out of range + + static immutable string[8] result = + [ + "\x80", // not a start byte + "\xC0", // truncated + "\xC0\xC0", // invalid continuation + "\xF0\x82\x82\xAC", // overlong + [ + 0xE0 | (x >> 12), + 0x80 | ((x >> 6) & 0x3F), + 0x80 | (x & 0x3F) + ], + [ + cast(char)(0xF0 | (y >> 18)), + cast(char)(0x80 | ((y >> 12) & 0x3F)), + cast(char)(0x80 | ((y >> 6) & 0x3F)), + cast(char)(0x80 | (y & 0x3F)) + ], + [ + cast(char)(0xF8 | 3), // 5 byte encoding + cast(char)(0x80 | 3), + cast(char)(0x80 | 3), + cast(char)(0x80 | 3), + cast(char)(0x80 | 3), + ], + [ + cast(char)(0xFC | 3), // 6 byte encoding + cast(char)(0x80 | 3), + cast(char)(0x80 | 3), + cast(char)(0x80 | 3), + cast(char)(0x80 | 3), + cast(char)(0x80 | 3), + ], + ]; + + return result[]; + } + else static if (is(Char == wchar)) + { + static immutable wstring[5] result = + [ + [ + cast(wchar) 0xDC00, + ], + [ + cast(wchar) 0xDFFF, + ], + [ + cast(wchar) 0xDBFF, + cast(wchar) 0xDBFF, + ], + [ + cast(wchar) 0xDBFF, + cast(wchar) 0xE000, + ], + [ + cast(wchar) 0xD800, + ], + ]; + + return result[]; + } + else static if (is(Char == dchar)) + { + static immutable dstring[3] result = + [ + [ cast(dchar) 0x110000 ], + [ cast(dchar) 0x00D800 ], + [ cast(dchar) 0x00DFFF ], + ]; + + return result; + } + else + static assert(0); +} + +/++ + Check whether the given Unicode code point is valid. + + Params: + c = code point to check + + Returns: + $(D true) iff $(D c) is a valid Unicode code point + + Note: + $(D '\uFFFE') and $(D '\uFFFF') are considered valid by $(D isValidDchar), + as they are permitted for internal use by an application, but they are + not allowed for interchange by the Unicode standard. + +/ +bool isValidDchar(dchar c) pure nothrow @safe @nogc +{ + return c < 0xD800 || (c > 0xDFFF && c <= 0x10FFFF); +} + +pure nothrow @safe @nogc unittest +{ + import std.exception; + + assertCTFEable!( + { + assert( isValidDchar(cast(dchar)'a') == true); + assert( isValidDchar(cast(dchar) 0x1FFFFF) == false); + + assert(!isValidDchar(cast(dchar) 0x00D800)); + assert(!isValidDchar(cast(dchar) 0x00DBFF)); + assert(!isValidDchar(cast(dchar) 0x00DC00)); + assert(!isValidDchar(cast(dchar) 0x00DFFF)); + assert( isValidDchar(cast(dchar) 0x00FFFE)); + assert( isValidDchar(cast(dchar) 0x00FFFF)); + assert( isValidDchar(cast(dchar) 0x01FFFF)); + assert( isValidDchar(cast(dchar) 0x10FFFF)); + assert(!isValidDchar(cast(dchar) 0x110000)); + }); +} + + +/++ + Calculate the length of the UTF sequence starting at $(D index) + in $(D str). + + Params: + str = input range of UTF code units. Must be random access if + $(D index) is passed + index = starting index of UTF sequence (default: $(D 0)) + + Returns: + The number of code units in the UTF sequence. For UTF-8, this is a + value between 1 and 4 (as per $(HTTP tools.ietf.org/html/rfc3629#section-3, RFC 3629$(COMMA) section 3)). + For UTF-16, it is either 1 or 2. For UTF-32, it is always 1. + + Throws: + May throw a $(D UTFException) if $(D str[index]) is not the start of a + valid UTF sequence. + + Note: + $(D stride) will only analyze the first $(D str[index]) element. It + will not fully verify the validity of the UTF sequence, nor even verify + the presence of the sequence: it will not actually guarantee that + $(D index + stride(str, index) <= str.length). + +/ +uint stride(S)(auto ref S str, size_t index) +if (is(S : const char[]) || + (isRandomAccessRange!S && is(Unqual!(ElementType!S) == char))) +{ + static if (is(typeof(str.length) : ulong)) + assert(index < str.length, "Past the end of the UTF-8 sequence"); + immutable c = str[index]; + + if (c < 0x80) + return 1; + else + return strideImpl(c, index); +} + +/// Ditto +uint stride(S)(auto ref S str) +if (is(S : const char[]) || + (isInputRange!S && is(Unqual!(ElementType!S) == char))) +{ + static if (is(S : const char[])) + immutable c = str[0]; + else + immutable c = str.front; + + if (c < 0x80) + return 1; + else + return strideImpl(c, 0); +} + +private uint strideImpl(char c, size_t index) @trusted pure +in { assert(c & 0x80); } +body +{ + import core.bitop : bsr; + immutable msbs = 7 - bsr((~uint(c)) & 0xFF); + if (c == 0xFF || msbs < 2 || msbs > 4) + throw new UTFException("Invalid UTF-8 sequence", index); + return msbs; +} + +@system unittest +{ + import core.exception : AssertError; + import std.conv : to; + import std.exception; + import std.string : format; + static void test(string s, dchar c, size_t i = 0, size_t line = __LINE__) + { + enforce(stride(s, i) == codeLength!char(c), + new AssertError(format("Unit test failure string: %s", s), __FILE__, line)); + + enforce(stride(RandomCU!char(s), i) == codeLength!char(c), + new AssertError(format("Unit test failure range: %s", s), __FILE__, line)); + + auto refRandom = new RefRandomCU!char(s); + immutable randLen = refRandom.length; + enforce(stride(refRandom, i) == codeLength!char(c), + new AssertError(format("Unit test failure rand ref range: %s", s), __FILE__, line)); + enforce(refRandom.length == randLen, + new AssertError(format("Unit test failure rand ref range length: %s", s), __FILE__, line)); + + if (i == 0) + { + enforce(stride(s) == codeLength!char(c), + new AssertError(format("Unit test failure string 0: %s", s), __FILE__, line)); + + enforce(stride(InputCU!char(s)) == codeLength!char(c), + new AssertError(format("Unit test failure range 0: %s", s), __FILE__, line)); + + auto refBidir = new RefBidirCU!char(s); + immutable bidirLen = refBidir.length; + enforce(stride(refBidir) == codeLength!char(c), + new AssertError(format("Unit test failure bidir ref range code length: %s", s), __FILE__, line)); + enforce(refBidir.length == bidirLen, + new AssertError(format("Unit test failure bidir ref range length: %s", s), __FILE__, line)); + } + } + + assertCTFEable!( + { + test("a", 'a'); + test(" ", ' '); + test("\u2029", '\u2029'); //paraSep + test("\u0100", '\u0100'); + test("\u0430", '\u0430'); + test("\U00010143", '\U00010143'); + test("abcdefcdef", 'a'); + test("hello\U00010143\u0100\U00010143", 'h', 0); + test("hello\U00010143\u0100\U00010143", 'e', 1); + test("hello\U00010143\u0100\U00010143", 'l', 2); + test("hello\U00010143\u0100\U00010143", 'l', 3); + test("hello\U00010143\u0100\U00010143", 'o', 4); + test("hello\U00010143\u0100\U00010143", '\U00010143', 5); + test("hello\U00010143\u0100\U00010143", '\u0100', 9); + test("hello\U00010143\u0100\U00010143", '\U00010143', 11); + + foreach (S; AliasSeq!(char[], const char[], string)) + { + enum str = to!S("hello world"); + static assert(isSafe!({ stride(str, 0); })); + static assert(isSafe!({ stride(str); })); + static assert((functionAttributes!({ stride(str, 0); }) & FunctionAttribute.pure_) != 0); + static assert((functionAttributes!({ stride(str); }) & FunctionAttribute.pure_) != 0); + } + }); +} + +@safe unittest // invalid start bytes +{ + import std.exception : assertThrown; + immutable char[] invalidStartBytes = [ + 0b1111_1000, // indicating a sequence length of 5 + 0b1111_1100, // 6 + 0b1111_1110, // 7 + 0b1111_1111, // 8 + 0b1000_0000, // continuation byte + ]; + foreach (c; invalidStartBytes) + assertThrown!UTFException(stride([c])); +} + +/// Ditto +uint stride(S)(auto ref S str, size_t index) +if (is(S : const wchar[]) || + (isRandomAccessRange!S && is(Unqual!(ElementType!S) == wchar))) +{ + static if (is(typeof(str.length) : ulong)) + assert(index < str.length, "Past the end of the UTF-16 sequence"); + immutable uint u = str[index]; + return 1 + (u >= 0xD800 && u <= 0xDBFF); +} + +/// Ditto +uint stride(S)(auto ref S str) @safe pure +if (is(S : const wchar[])) +{ + return stride(str, 0); +} + +/// Ditto +uint stride(S)(auto ref S str) +if (isInputRange!S && is(Unqual!(ElementType!S) == wchar)) +{ + assert(!str.empty, "UTF-16 sequence is empty"); + immutable uint u = str.front; + return 1 + (u >= 0xD800 && u <= 0xDBFF); +} + +@system unittest +{ + import core.exception : AssertError; + import std.conv : to; + import std.exception; + import std.string : format; + static void test(wstring s, dchar c, size_t i = 0, size_t line = __LINE__) + { + enforce(stride(s, i) == codeLength!wchar(c), + new AssertError(format("Unit test failure string: %s", s), __FILE__, line)); + + enforce(stride(RandomCU!wchar(s), i) == codeLength!wchar(c), + new AssertError(format("Unit test failure range: %s", s), __FILE__, line)); + + auto refRandom = new RefRandomCU!wchar(s); + immutable randLen = refRandom.length; + enforce(stride(refRandom, i) == codeLength!wchar(c), + new AssertError(format("Unit test failure rand ref range: %s", s), __FILE__, line)); + enforce(refRandom.length == randLen, + new AssertError(format("Unit test failure rand ref range length: %s", s), __FILE__, line)); + + if (i == 0) + { + enforce(stride(s) == codeLength!wchar(c), + new AssertError(format("Unit test failure string 0: %s", s), __FILE__, line)); + + enforce(stride(InputCU!wchar(s)) == codeLength!wchar(c), + new AssertError(format("Unit test failure range 0: %s", s), __FILE__, line)); + + auto refBidir = new RefBidirCU!wchar(s); + immutable bidirLen = refBidir.length; + enforce(stride(refBidir) == codeLength!wchar(c), + new AssertError(format("Unit test failure bidir ref range code length: %s", s), __FILE__, line)); + enforce(refBidir.length == bidirLen, + new AssertError(format("Unit test failure bidir ref range length: %s", s), __FILE__, line)); + } + } + + assertCTFEable!( + { + test("a", 'a'); + test(" ", ' '); + test("\u2029", '\u2029'); //paraSep + test("\u0100", '\u0100'); + test("\u0430", '\u0430'); + test("\U00010143", '\U00010143'); + test("abcdefcdef", 'a'); + test("hello\U00010143\u0100\U00010143", 'h', 0); + test("hello\U00010143\u0100\U00010143", 'e', 1); + test("hello\U00010143\u0100\U00010143", 'l', 2); + test("hello\U00010143\u0100\U00010143", 'l', 3); + test("hello\U00010143\u0100\U00010143", 'o', 4); + test("hello\U00010143\u0100\U00010143", '\U00010143', 5); + test("hello\U00010143\u0100\U00010143", '\u0100', 7); + test("hello\U00010143\u0100\U00010143", '\U00010143', 8); + + foreach (S; AliasSeq!(wchar[], const wchar[], wstring)) + { + enum str = to!S("hello world"); + static assert(isSafe!(() => stride(str, 0))); + static assert(isSafe!(() => stride(str) )); + static assert((functionAttributes!(() => stride(str, 0)) & FunctionAttribute.pure_) != 0); + static assert((functionAttributes!(() => stride(str) ) & FunctionAttribute.pure_) != 0); + } + }); +} + +/// Ditto +uint stride(S)(auto ref S str, size_t index = 0) +if (is(S : const dchar[]) || + (isInputRange!S && is(Unqual!(ElementEncodingType!S) == dchar))) +{ + static if (is(typeof(str.length) : ulong)) + assert(index < str.length, "Past the end of the UTF-32 sequence"); + else + assert(!str.empty, "UTF-32 sequence is empty."); + return 1; +} + +@system unittest +{ + import core.exception : AssertError; + import std.conv : to; + import std.exception; + import std.string : format; + static void test(dstring s, dchar c, size_t i = 0, size_t line = __LINE__) + { + enforce(stride(s, i) == codeLength!dchar(c), + new AssertError(format("Unit test failure string: %s", s), __FILE__, line)); + + enforce(stride(RandomCU!dchar(s), i) == codeLength!dchar(c), + new AssertError(format("Unit test failure range: %s", s), __FILE__, line)); + + auto refRandom = new RefRandomCU!dchar(s); + immutable randLen = refRandom.length; + enforce(stride(refRandom, i) == codeLength!dchar(c), + new AssertError(format("Unit test failure rand ref range: %s", s), __FILE__, line)); + enforce(refRandom.length == randLen, + new AssertError(format("Unit test failure rand ref range length: %s", s), __FILE__, line)); + + if (i == 0) + { + enforce(stride(s) == codeLength!dchar(c), + new AssertError(format("Unit test failure string 0: %s", s), __FILE__, line)); + + enforce(stride(InputCU!dchar(s)) == codeLength!dchar(c), + new AssertError(format("Unit test failure range 0: %s", s), __FILE__, line)); + + auto refBidir = new RefBidirCU!dchar(s); + immutable bidirLen = refBidir.length; + enforce(stride(refBidir) == codeLength!dchar(c), + new AssertError(format("Unit test failure bidir ref range code length: %s", s), __FILE__, line)); + enforce(refBidir.length == bidirLen, + new AssertError(format("Unit test failure bidir ref range length: %s", s), __FILE__, line)); + } + } + + assertCTFEable!( + { + test("a", 'a'); + test(" ", ' '); + test("\u2029", '\u2029'); //paraSep + test("\u0100", '\u0100'); + test("\u0430", '\u0430'); + test("\U00010143", '\U00010143'); + test("abcdefcdef", 'a'); + test("hello\U00010143\u0100\U00010143", 'h', 0); + test("hello\U00010143\u0100\U00010143", 'e', 1); + test("hello\U00010143\u0100\U00010143", 'l', 2); + test("hello\U00010143\u0100\U00010143", 'l', 3); + test("hello\U00010143\u0100\U00010143", 'o', 4); + test("hello\U00010143\u0100\U00010143", '\U00010143', 5); + test("hello\U00010143\u0100\U00010143", '\u0100', 6); + test("hello\U00010143\u0100\U00010143", '\U00010143', 7); + + foreach (S; AliasSeq!(dchar[], const dchar[], dstring)) + { + enum str = to!S("hello world"); + static assert(isSafe!(() => stride(str, 0))); + static assert(isSafe!(() => stride(str) )); + static assert((functionAttributes!(() => stride(str, 0)) & FunctionAttribute.pure_) != 0); + static assert((functionAttributes!(() => stride(str) ) & FunctionAttribute.pure_) != 0); + } + }); +} + +/++ + Calculate the length of the UTF sequence ending one code unit before + $(D index) in $(D str). + + Params: + str = bidirectional range of UTF code units. Must be random access if + $(D index) is passed + index = index one past end of UTF sequence (default: $(D str.length)) + + Returns: + The number of code units in the UTF sequence. For UTF-8, this is a + value between 1 and 4 (as per $(HTTP tools.ietf.org/html/rfc3629#section-3, RFC 3629$(COMMA) section 3)). + For UTF-16, it is either 1 or 2. For UTF-32, it is always 1. + + Throws: + May throw a $(D UTFException) if $(D str[index]) is not one past the + end of a valid UTF sequence. + + Note: + $(D strideBack) will only analyze the element at $(D str[index - 1]) + element. It will not fully verify the validity of the UTF sequence, nor + even verify the presence of the sequence: it will not actually + guarantee that $(D strideBack(str, index) <= index). + +/ +uint strideBack(S)(auto ref S str, size_t index) +if (is(S : const char[]) || + (isRandomAccessRange!S && is(Unqual!(ElementType!S) == char))) +{ + static if (is(typeof(str.length) : ulong)) + assert(index <= str.length, "Past the end of the UTF-8 sequence"); + assert(index > 0, "Not the end of the UTF-8 sequence"); + + if ((str[index-1] & 0b1100_0000) != 0b1000_0000) + return 1; + + if (index >= 4) //single verification for most common case + { + foreach (i; AliasSeq!(2, 3, 4)) + { + if ((str[index-i] & 0b1100_0000) != 0b1000_0000) + return i; + } + } + else + { + foreach (i; AliasSeq!(2, 3)) + { + if (index >= i && (str[index-i] & 0b1100_0000) != 0b1000_0000) + return i; + } + } + throw new UTFException("Not the end of the UTF sequence", index); +} + +/// Ditto +uint strideBack(S)(auto ref S str) +if (is(S : const char[]) || + (isRandomAccessRange!S && hasLength!S && is(Unqual!(ElementType!S) == char))) +{ + return strideBack(str, str.length); +} + +/// Ditto +uint strideBack(S)(auto ref S str) +if (isBidirectionalRange!S && is(Unqual!(ElementType!S) == char) && !isRandomAccessRange!S) +{ + assert(!str.empty, "Past the end of the UTF-8 sequence"); + auto temp = str.save; + foreach (i; AliasSeq!(1, 2, 3, 4)) + { + if ((temp.back & 0b1100_0000) != 0b1000_0000) + return i; + temp.popBack(); + if (temp.empty) + break; + } + throw new UTFException("The last code unit is not the end of the UTF-8 sequence"); +} + +@system unittest +{ + import core.exception : AssertError; + import std.conv : to; + import std.exception; + import std.string : format; + static void test(string s, dchar c, size_t i = size_t.max, size_t line = __LINE__) + { + enforce(strideBack(s, i == size_t.max ? s.length : i) == codeLength!char(c), + new AssertError(format("Unit test failure string: %s", s), __FILE__, line)); + + enforce(strideBack(RandomCU!char(s), i == size_t.max ? s.length : i) == codeLength!char(c), + new AssertError(format("Unit test failure range: %s", s), __FILE__, line)); + + auto refRandom = new RefRandomCU!char(s); + immutable randLen = refRandom.length; + enforce(strideBack(refRandom, i == size_t.max ? s.length : i) == codeLength!char(c), + new AssertError(format("Unit test failure rand ref range: %s", s), __FILE__, line)); + enforce(refRandom.length == randLen, + new AssertError(format("Unit test failure rand ref range length: %s", s), __FILE__, line)); + + if (i == size_t.max) + { + enforce(strideBack(s) == codeLength!char(c), + new AssertError(format("Unit test failure string code length: %s", s), __FILE__, line)); + + enforce(strideBack(BidirCU!char(s)) == codeLength!char(c), + new AssertError(format("Unit test failure range code length: %s", s), __FILE__, line)); + + auto refBidir = new RefBidirCU!char(s); + immutable bidirLen = refBidir.length; + enforce(strideBack(refBidir) == codeLength!char(c), + new AssertError(format("Unit test failure bidir ref range code length: %s", s), __FILE__, line)); + enforce(refBidir.length == bidirLen, + new AssertError(format("Unit test failure bidir ref range length: %s", s), __FILE__, line)); + } + } + + assertCTFEable!( + { + test("a", 'a'); + test(" ", ' '); + test("\u2029", '\u2029'); //paraSep + test("\u0100", '\u0100'); + test("\u0430", '\u0430'); + test("\U00010143", '\U00010143'); + test("abcdefcdef", 'f'); + test("\U00010143\u0100\U00010143hello", 'o', 15); + test("\U00010143\u0100\U00010143hello", 'l', 14); + test("\U00010143\u0100\U00010143hello", 'l', 13); + test("\U00010143\u0100\U00010143hello", 'e', 12); + test("\U00010143\u0100\U00010143hello", 'h', 11); + test("\U00010143\u0100\U00010143hello", '\U00010143', 10); + test("\U00010143\u0100\U00010143hello", '\u0100', 6); + test("\U00010143\u0100\U00010143hello", '\U00010143', 4); + + foreach (S; AliasSeq!(char[], const char[], string)) + { + enum str = to!S("hello world"); + static assert(isSafe!({ strideBack(str, 0); })); + static assert(isSafe!({ strideBack(str); })); + static assert((functionAttributes!({ strideBack(str, 0); }) & FunctionAttribute.pure_) != 0); + static assert((functionAttributes!({ strideBack(str); }) & FunctionAttribute.pure_) != 0); + } + }); +} + +//UTF-16 is self synchronizing: The length of strideBack can be found from +//the value of a single wchar +/// Ditto +uint strideBack(S)(auto ref S str, size_t index) +if (is(S : const wchar[]) || + (isRandomAccessRange!S && is(Unqual!(ElementType!S) == wchar))) +{ + static if (is(typeof(str.length) : ulong)) + assert(index <= str.length, "Past the end of the UTF-16 sequence"); + assert(index > 0, "Not the end of a UTF-16 sequence"); + + immutable c2 = str[index-1]; + return 1 + (0xDC00 <= c2 && c2 < 0xE000); +} + +/// Ditto +uint strideBack(S)(auto ref S str) +if (is(S : const wchar[]) || + (isBidirectionalRange!S && is(Unqual!(ElementType!S) == wchar))) +{ + assert(!str.empty, "UTF-16 sequence is empty"); + + static if (is(S : const(wchar)[])) + immutable c2 = str[$ - 1]; + else + immutable c2 = str.back; + + return 1 + (0xDC00 <= c2 && c2 <= 0xE000); +} + +@system unittest +{ + import core.exception : AssertError; + import std.conv : to; + import std.exception; + import std.string : format; + static void test(wstring s, dchar c, size_t i = size_t.max, size_t line = __LINE__) + { + enforce(strideBack(s, i == size_t.max ? s.length : i) == codeLength!wchar(c), + new AssertError(format("Unit test failure string: %s", s), __FILE__, line)); + + enforce(strideBack(RandomCU!wchar(s), i == size_t.max ? s.length : i) == codeLength!wchar(c), + new AssertError(format("Unit test failure range: %s", s), __FILE__, line)); + + auto refRandom = new RefRandomCU!wchar(s); + immutable randLen = refRandom.length; + enforce(strideBack(refRandom, i == size_t.max ? s.length : i) == codeLength!wchar(c), + new AssertError(format("Unit test failure rand ref range: %s", s), __FILE__, line)); + enforce(refRandom.length == randLen, + new AssertError(format("Unit test failure rand ref range length: %s", s), __FILE__, line)); + + if (i == size_t.max) + { + enforce(strideBack(s) == codeLength!wchar(c), + new AssertError(format("Unit test failure string code length: %s", s), __FILE__, line)); + + enforce(strideBack(BidirCU!wchar(s)) == codeLength!wchar(c), + new AssertError(format("Unit test failure range code length: %s", s), __FILE__, line)); + + auto refBidir = new RefBidirCU!wchar(s); + immutable bidirLen = refBidir.length; + enforce(strideBack(refBidir) == codeLength!wchar(c), + new AssertError(format("Unit test failure bidir ref range code length: %s", s), __FILE__, line)); + enforce(refBidir.length == bidirLen, + new AssertError(format("Unit test failure bidir ref range length: %s", s), __FILE__, line)); + } + } + + assertCTFEable!( + { + test("a", 'a'); + test(" ", ' '); + test("\u2029", '\u2029'); //paraSep + test("\u0100", '\u0100'); + test("\u0430", '\u0430'); + test("\U00010143", '\U00010143'); + test("abcdefcdef", 'f'); + test("\U00010143\u0100\U00010143hello", 'o', 10); + test("\U00010143\u0100\U00010143hello", 'l', 9); + test("\U00010143\u0100\U00010143hello", 'l', 8); + test("\U00010143\u0100\U00010143hello", 'e', 7); + test("\U00010143\u0100\U00010143hello", 'h', 6); + test("\U00010143\u0100\U00010143hello", '\U00010143', 5); + test("\U00010143\u0100\U00010143hello", '\u0100', 3); + test("\U00010143\u0100\U00010143hello", '\U00010143', 2); + + foreach (S; AliasSeq!(wchar[], const wchar[], wstring)) + { + enum str = to!S("hello world"); + static assert(isSafe!(() => strideBack(str, 0))); + static assert(isSafe!(() => strideBack(str) )); + static assert((functionAttributes!(() => strideBack(str, 0)) & FunctionAttribute.pure_) != 0); + static assert((functionAttributes!(() => strideBack(str) ) & FunctionAttribute.pure_) != 0); + } + }); +} + +/// Ditto +uint strideBack(S)(auto ref S str, size_t index) +if (isRandomAccessRange!S && is(Unqual!(ElementEncodingType!S) == dchar)) +{ + static if (is(typeof(str.length) : ulong)) + assert(index <= str.length, "Past the end of the UTF-32 sequence"); + assert(index > 0, "Not the end of the UTF-32 sequence"); + return 1; +} + +/// Ditto +uint strideBack(S)(auto ref S str) +if (isBidirectionalRange!S && is(Unqual!(ElementEncodingType!S) == dchar)) +{ + assert(!str.empty, "Empty UTF-32 sequence"); + return 1; +} + +@system unittest +{ + import core.exception : AssertError; + import std.conv : to; + import std.exception; + import std.string : format; + static void test(dstring s, dchar c, size_t i = size_t.max, size_t line = __LINE__) + { + enforce(strideBack(s, i == size_t.max ? s.length : i) == codeLength!dchar(c), + new AssertError(format("Unit test failure string: %s", s), __FILE__, line)); + + enforce(strideBack(RandomCU!dchar(s), i == size_t.max ? s.length : i) == codeLength!dchar(c), + new AssertError(format("Unit test failure range: %s", s), __FILE__, line)); + + auto refRandom = new RefRandomCU!dchar(s); + immutable randLen = refRandom.length; + enforce(strideBack(refRandom, i == size_t.max ? s.length : i) == codeLength!dchar(c), + new AssertError(format("Unit test failure rand ref range: %s", s), __FILE__, line)); + enforce(refRandom.length == randLen, + new AssertError(format("Unit test failure rand ref range length: %s", s), __FILE__, line)); + + if (i == size_t.max) + { + enforce(strideBack(s) == codeLength!dchar(c), + new AssertError(format("Unit test failure string code length: %s", s), __FILE__, line)); + + enforce(strideBack(BidirCU!dchar(s)) == codeLength!dchar(c), + new AssertError(format("Unit test failure range code length: %s", s), __FILE__, line)); + + auto refBidir = new RefBidirCU!dchar(s); + immutable bidirLen = refBidir.length; + enforce(strideBack(refBidir) == codeLength!dchar(c), + new AssertError(format("Unit test failure bidir ref range code length: %s", s), __FILE__, line)); + enforce(refBidir.length == bidirLen, + new AssertError(format("Unit test failure bidir ref range length: %s", s), __FILE__, line)); + } + } + + assertCTFEable!( + { + test("a", 'a'); + test(" ", ' '); + test("\u2029", '\u2029'); //paraSep + test("\u0100", '\u0100'); + test("\u0430", '\u0430'); + test("\U00010143", '\U00010143'); + test("abcdefcdef", 'f'); + test("\U00010143\u0100\U00010143hello", 'o', 8); + test("\U00010143\u0100\U00010143hello", 'l', 7); + test("\U00010143\u0100\U00010143hello", 'l', 6); + test("\U00010143\u0100\U00010143hello", 'e', 5); + test("\U00010143\u0100\U00010143hello", 'h', 4); + test("\U00010143\u0100\U00010143hello", '\U00010143', 3); + test("\U00010143\u0100\U00010143hello", '\u0100', 2); + test("\U00010143\u0100\U00010143hello", '\U00010143', 1); + + foreach (S; AliasSeq!(dchar[], const dchar[], dstring)) + { + enum str = to!S("hello world"); + static assert(isSafe!(() => strideBack(str, 0))); + static assert(isSafe!(() => strideBack(str) )); + static assert((functionAttributes!(() => strideBack(str, 0)) & FunctionAttribute.pure_) != 0); + static assert((functionAttributes!(() => strideBack(str) ) & FunctionAttribute.pure_) != 0); + } + }); +} + + +/++ + Given $(D index) into $(D str) and assuming that $(D index) is at the start + of a UTF sequence, $(D toUCSindex) determines the number of UCS characters + up to $(D index). So, $(D index) is the index of a code unit at the + beginning of a code point, and the return value is how many code points into + the string that that code point is. + +/ +size_t toUCSindex(C)(const(C)[] str, size_t index) @safe pure +if (isSomeChar!C) +{ + static if (is(Unqual!C == dchar)) + return index; + else + { + size_t n = 0; + size_t j = 0; + + for (; j < index; ++n) + j += stride(str, j); + + if (j > index) + { + static if (is(Unqual!C == char)) + throw new UTFException("Invalid UTF-8 sequence", index); + else + throw new UTFException("Invalid UTF-16 sequence", index); + } + + return n; + } +} + +/// +@safe unittest +{ + assert(toUCSindex(`hello world`, 7) == 7); + assert(toUCSindex(`hello world`w, 7) == 7); + assert(toUCSindex(`hello world`d, 7) == 7); + + assert(toUCSindex(`Ma Chérie`, 7) == 6); + assert(toUCSindex(`Ma Chérie`w, 7) == 7); + assert(toUCSindex(`Ma Chérie`d, 7) == 7); + + assert(toUCSindex(`さいごの果実 / ミツバチと科学者`, 9) == 3); + assert(toUCSindex(`さいごの果実 / ミツバチと科学者`w, 9) == 9); + assert(toUCSindex(`さいごの果実 / ミツバチと科学者`d, 9) == 9); +} + + +/++ + Given a UCS index $(D n) into $(D str), returns the UTF index. + So, $(D n) is how many code points into the string the code point is, and + the array index of the code unit is returned. + +/ +size_t toUTFindex(C)(const(C)[] str, size_t n) @safe pure +if (isSomeChar!C) +{ + static if (is(Unqual!C == dchar)) + { + return n; + } + else + { + size_t i; + while (n--) + { + i += stride(str, i); + } + return i; + } +} + +/// +@safe unittest +{ + assert(toUTFindex(`hello world`, 7) == 7); + assert(toUTFindex(`hello world`w, 7) == 7); + assert(toUTFindex(`hello world`d, 7) == 7); + + assert(toUTFindex(`Ma Chérie`, 6) == 7); + assert(toUTFindex(`Ma Chérie`w, 7) == 7); + assert(toUTFindex(`Ma Chérie`d, 7) == 7); + + assert(toUTFindex(`さいごの果実 / ミツバチと科学者`, 3) == 9); + assert(toUTFindex(`さいごの果実 / ミツバチと科学者`w, 9) == 9); + assert(toUTFindex(`さいごの果実 / ミツバチと科学者`d, 9) == 9); +} + + +/* =================== Decode ======================= */ + +/// Whether or not to replace invalid UTF with $(LREF replacementDchar) +alias UseReplacementDchar = Flag!"useReplacementDchar"; + +/++ + Decodes and returns the code point starting at $(D str[index]). $(D index) + is advanced to one past the decoded code point. If the code point is not + well-formed, then a $(D UTFException) is thrown and $(D index) remains + unchanged. + + decode will only work with strings and random access ranges of code units + with length and slicing, whereas $(LREF decodeFront) will work with any + input range of code units. + + Params: + useReplacementDchar = if invalid UTF, return replacementDchar rather than throwing + str = input string or indexable Range + index = starting index into s[]; incremented by number of code units processed + + Returns: + decoded character + + Throws: + $(LREF UTFException) if $(D str[index]) is not the start of a valid UTF + sequence and useReplacementDchar is $(D No.useReplacementDchar) + +/ +dchar decode(UseReplacementDchar useReplacementDchar = No.useReplacementDchar, S)(auto ref S str, ref size_t index) +if (!isSomeString!S && + isRandomAccessRange!S && hasSlicing!S && hasLength!S && isSomeChar!(ElementType!S)) +in +{ + assert(index < str.length, "Attempted to decode past the end of a string"); +} +out (result) +{ + assert(isValidDchar(result)); +} +body +{ + if (str[index] < codeUnitLimit!S) + return str[index++]; + else + return decodeImpl!(true, useReplacementDchar)(str, index); +} + +dchar decode(UseReplacementDchar useReplacementDchar = No.useReplacementDchar, S)( +auto ref S str, ref size_t index) @trusted pure +if (isSomeString!S) +in +{ + assert(index < str.length, "Attempted to decode past the end of a string"); +} +out (result) +{ + assert(isValidDchar(result)); +} +body +{ + if (str[index] < codeUnitLimit!S) + return str[index++]; + else + return decodeImpl!(true, useReplacementDchar)(str, index); +} + +/++ + $(D decodeFront) is a variant of $(LREF decode) which specifically decodes + the first code point. Unlike $(LREF decode), $(D decodeFront) accepts any + input range of code units (rather than just a string or random access + range). It also takes the range by $(D ref) and pops off the elements as it + decodes them. If $(D numCodeUnits) is passed in, it gets set to the number + of code units which were in the code point which was decoded. + + Params: + useReplacementDchar = if invalid UTF, return replacementDchar rather than throwing + str = input string or indexable Range + numCodeUnits = set to number of code units processed + + Returns: + decoded character + + Throws: + $(LREF UTFException) if $(D str.front) is not the start of a valid UTF + sequence. If an exception is thrown, then there is no guarantee as to + the number of code units which were popped off, as it depends on the + type of range being used and how many code units had to be popped off + before the code point was determined to be invalid. + +/ +dchar decodeFront(UseReplacementDchar useReplacementDchar = No.useReplacementDchar, S)( +ref S str, out size_t numCodeUnits) +if (!isSomeString!S && isInputRange!S && isSomeChar!(ElementType!S)) +in +{ + assert(!str.empty); +} +out (result) +{ + assert(isValidDchar(result)); +} +body +{ + immutable fst = str.front; + + if (fst < codeUnitLimit!S) + { + str.popFront(); + numCodeUnits = 1; + return fst; + } + else + { + //@@@BUG@@@ 14447 forces canIndex to be done outside of decodeImpl, which + //is undesirable, since not all overloads of decodeImpl need it. So, it + //should be moved back into decodeImpl once bug# 8521 has been fixed. + enum canIndex = isRandomAccessRange!S && hasSlicing!S && hasLength!S; + immutable retval = decodeImpl!(canIndex, useReplacementDchar)(str, numCodeUnits); + + // The other range types were already popped by decodeImpl. + static if (isRandomAccessRange!S && hasSlicing!S && hasLength!S) + str = str[numCodeUnits .. str.length]; + + return retval; + } +} + +dchar decodeFront(UseReplacementDchar useReplacementDchar = No.useReplacementDchar, S)( +ref S str, out size_t numCodeUnits) @trusted pure +if (isSomeString!S) +in +{ + assert(!str.empty); +} +out (result) +{ + assert(isValidDchar(result)); +} +body +{ + if (str[0] < codeUnitLimit!S) + { + numCodeUnits = 1; + immutable retval = str[0]; + str = str[1 .. $]; + return retval; + } + else + { + immutable retval = decodeImpl!(true, useReplacementDchar)(str, numCodeUnits); + str = str[numCodeUnits .. $]; + return retval; + } +} + +/++ Ditto +/ +dchar decodeFront(UseReplacementDchar useReplacementDchar = No.useReplacementDchar, S)(ref S str) +if (isInputRange!S && isSomeChar!(ElementType!S)) +{ + size_t numCodeUnits; + return decodeFront!useReplacementDchar(str, numCodeUnits); +} + +/++ + $(D decodeBack) is a variant of $(LREF decode) which specifically decodes + the last code point. Unlike $(LREF decode), $(D decodeBack) accepts any + bidirectional range of code units (rather than just a string or random access + range). It also takes the range by $(D ref) and pops off the elements as it + decodes them. If $(D numCodeUnits) is passed in, it gets set to the number + of code units which were in the code point which was decoded. + + Params: + useReplacementDchar = if invalid UTF, return `replacementDchar` rather than throwing + str = input string or bidirectional Range + numCodeUnits = gives the number of code units processed + + Returns: + A decoded UTF character. + + Throws: + $(LREF UTFException) if $(D str.back) is not the end of a valid UTF + sequence. If an exception is thrown, the $(D str) itself remains unchanged, + but there is no guarantee as to the value of $(D numCodeUnits) (when passed). + +/ +dchar decodeBack(UseReplacementDchar useReplacementDchar = No.useReplacementDchar, S)( + ref S str, out size_t numCodeUnits) +if (isSomeString!S) +in +{ + assert(!str.empty); +} +out (result) +{ + assert(isValidDchar(result)); +} +body +{ + if (str[$ - 1] < codeUnitLimit!S) + { + numCodeUnits = 1; + immutable retval = str[$ - 1]; + str = str[0 .. $ - 1]; + return retval; + } + else + { + numCodeUnits = strideBack(str); + immutable newLength = str.length - numCodeUnits; + size_t index = newLength; + immutable retval = decodeImpl!(true, useReplacementDchar)(str, index); + str = str[0 .. newLength]; + return retval; + } +} + +/++ Ditto +/ +dchar decodeBack(UseReplacementDchar useReplacementDchar = No.useReplacementDchar, S)( + ref S str, out size_t numCodeUnits) +if (!isSomeString!S && isSomeChar!(ElementType!S) && isBidirectionalRange!S + && ((isRandomAccessRange!S && hasLength!S) || !isRandomAccessRange!S)) +in +{ + assert(!str.empty); +} +out (result) +{ + assert(isValidDchar(result)); +} +body +{ + if (str.back < codeUnitLimit!S) + { + numCodeUnits = 1; + immutable retval = str.back; + str.popBack(); + return retval; + } + else + { + numCodeUnits = strideBack(str); + static if (isRandomAccessRange!S) + { + size_t index = str.length - numCodeUnits; + immutable retval = decodeImpl!(true, useReplacementDchar)(str, index); + str.popBackExactly(numCodeUnits); + return retval; + } + else + { + alias Char = Unqual!(ElementType!S); + Char[4] codeUnits; + S tmp = str.save; + for (size_t i = numCodeUnits; i > 0; ) + { + codeUnits[--i] = tmp.back; + tmp.popBack(); + } + const Char[] codePoint = codeUnits[0 .. numCodeUnits]; + size_t index = 0; + immutable retval = decodeImpl!(true, useReplacementDchar)(codePoint, index); + str = tmp; + return retval; + } + } +} + +/++ Ditto +/ +dchar decodeBack(UseReplacementDchar useReplacementDchar = No.useReplacementDchar, S)(ref S str) +if (isSomeString!S + || (isRandomAccessRange!S && hasLength!S && isSomeChar!(ElementType!S)) + || (!isRandomAccessRange!S && isBidirectionalRange!S && isSomeChar!(ElementType!S))) +in +{ + assert(!str.empty); +} +out (result) +{ + assert(isValidDchar(result)); +} +body +{ + size_t numCodeUnits; + return decodeBack!useReplacementDchar(str, numCodeUnits); +} + +// Gives the maximum value that a code unit for the given range type can hold. +package template codeUnitLimit(S) +if (isSomeChar!(ElementEncodingType!S)) +{ + static if (is(Unqual!(ElementEncodingType!S) == char)) + enum char codeUnitLimit = 0x80; + else static if (is(Unqual!(ElementEncodingType!S) == wchar)) + enum wchar codeUnitLimit = 0xD800; + else + enum dchar codeUnitLimit = 0xD800; +} + +/* + * For strings, this function does its own bounds checking to give a + * more useful error message when attempting to decode past the end of a string. + * Subsequently it uses a pointer instead of an array to avoid + * redundant bounds checking. + * + * The three overloads of this operate on chars, wchars, and dchars. + * + * Params: + * canIndex = if S is indexable + * useReplacementDchar = if invalid UTF, return replacementDchar rather than throwing + * str = input string or Range + * index = starting index into s[]; incremented by number of code units processed + * + * Returns: + * decoded character + */ +private dchar decodeImpl(bool canIndex, UseReplacementDchar useReplacementDchar = No.useReplacementDchar, S)( + auto ref S str, ref size_t index) +if ( + is(S : const char[]) || (isInputRange!S && is(Unqual!(ElementEncodingType!S) == char))) +{ + /* The following encodings are valid, except for the 5 and 6 byte + * combinations: + * 0xxxxxxx + * 110xxxxx 10xxxxxx + * 1110xxxx 10xxxxxx 10xxxxxx + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + * 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + * 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + + /* Dchar bitmask for different numbers of UTF-8 code units. + */ + alias bitMask = AliasSeq!((1 << 7) - 1, (1 << 11) - 1, (1 << 16) - 1, (1 << 21) - 1); + + static if (is(S : const char[])) + auto pstr = str.ptr + index; // this is what makes decodeImpl() @system code + else static if (isRandomAccessRange!S && hasSlicing!S && hasLength!S) + auto pstr = str[index .. str.length]; + else + alias pstr = str; + + //@@@BUG@@@ 14447 forces this to be done outside of decodeImpl + //enum canIndex = is(S : const char[]) || (isRandomAccessRange!S && hasSlicing!S && hasLength!S); + + static if (canIndex) + { + immutable length = str.length - index; + ubyte fst = pstr[0]; + } + else + { + ubyte fst = pstr.front; + pstr.popFront(); + } + + static if (!useReplacementDchar) + { + static if (canIndex) + { + static UTFException exception(S)(S str, string msg) + { + uint[4] sequence = void; + size_t i; + + do + { + sequence[i] = str[i]; + } while (++i < str.length && i < 4 && (str[i] & 0xC0) == 0x80); + + return new UTFException(msg, i).setSequence(sequence[0 .. i]); + } + } + + UTFException invalidUTF() + { + static if (canIndex) + return exception(pstr[0 .. length], "Invalid UTF-8 sequence"); + else + { + //We can't include the invalid sequence with input strings without + //saving each of the code units along the way, and we can't do it with + //forward ranges without saving the entire range. Both would incur a + //cost for the decoding of every character just to provide a better + //error message for the (hopefully) rare case when an invalid UTF-8 + //sequence is encountered, so we don't bother trying to include the + //invalid sequence here, unlike with strings and sliceable ranges. + return new UTFException("Invalid UTF-8 sequence"); + } + } + + UTFException outOfBounds() + { + static if (canIndex) + return exception(pstr[0 .. length], "Attempted to decode past the end of a string"); + else + return new UTFException("Attempted to decode past the end of a string"); + } + } + + if ((fst & 0b1100_0000) != 0b1100_0000) + { + static if (useReplacementDchar) + { + ++index; // always consume bad input to avoid infinite loops + return replacementDchar; + } + else + throw invalidUTF(); // starter must have at least 2 first bits set + } + ubyte tmp = void; + dchar d = fst; // upper control bits are masked out later + fst <<= 1; + + foreach (i; AliasSeq!(1, 2, 3)) + { + + static if (canIndex) + { + if (i == length) + { + static if (useReplacementDchar) + { + index += i; + return replacementDchar; + } + else + throw outOfBounds(); + } + } + else + { + if (pstr.empty) + { + static if (useReplacementDchar) + { + index += i; + return replacementDchar; + } + else + throw outOfBounds(); + } + } + + static if (canIndex) + tmp = pstr[i]; + else + { + tmp = pstr.front; + pstr.popFront(); + } + + if ((tmp & 0xC0) != 0x80) + { + static if (useReplacementDchar) + { + index += i + 1; + return replacementDchar; + } + else + throw invalidUTF(); + } + + d = (d << 6) | (tmp & 0x3F); + fst <<= 1; + + if (!(fst & 0x80)) // no more bytes + { + d &= bitMask[i]; // mask out control bits + + // overlong, could have been encoded with i bytes + if ((d & ~bitMask[i - 1]) == 0) + { + static if (useReplacementDchar) + { + index += i + 1; + return replacementDchar; + } + else + throw invalidUTF(); + } + + // check for surrogates only needed for 3 bytes + static if (i == 2) + { + if (!isValidDchar(d)) + { + static if (useReplacementDchar) + { + index += i + 1; + return replacementDchar; + } + else + throw invalidUTF(); + } + } + + index += i + 1; + static if (i == 3) + { + if (d > dchar.max) + { + static if (useReplacementDchar) + d = replacementDchar; + else + throw invalidUTF(); + } + } + return d; + } + } + + static if (useReplacementDchar) + { + index += 4; // read 4 chars by now + return replacementDchar; + } + else + throw invalidUTF(); +} + +@safe pure @nogc nothrow +unittest +{ + // Add tests for useReplacemendDchar == yes path + + static struct R + { + @safe pure @nogc nothrow: + this(string s) { this.s = s; } + @property bool empty() { return idx == s.length; } + @property char front() { return s[idx]; } + void popFront() { ++idx; } + size_t idx; + string s; + } + + foreach (s; invalidUTFstrings!char()) + { + auto r = R(s); + size_t index; + dchar dc = decodeImpl!(false, Yes.useReplacementDchar)(r, index); + assert(dc == replacementDchar); + assert(1 <= index && index <= s.length); + } +} + +private dchar decodeImpl(bool canIndex, UseReplacementDchar useReplacementDchar = No.useReplacementDchar, S) +(auto ref S str, ref size_t index) +if (is(S : const wchar[]) || (isInputRange!S && is(Unqual!(ElementEncodingType!S) == wchar))) +{ + static if (is(S : const wchar[])) + auto pstr = str.ptr + index; + else static if (isRandomAccessRange!S && hasSlicing!S && hasLength!S) + auto pstr = str[index .. str.length]; + else + alias pstr = str; + + //@@@BUG@@@ 14447 forces this to be done outside of decodeImpl + //enum canIndex = is(S : const wchar[]) || (isRandomAccessRange!S && hasSlicing!S && hasLength!S); + + static if (canIndex) + { + immutable length = str.length - index; + uint u = pstr[0]; + } + else + { + uint u = pstr.front; + pstr.popFront(); + } + + static if (!useReplacementDchar) + { + UTFException exception(string msg) + { + static if (canIndex) + return new UTFException(msg).setSequence(pstr[0]); + else + return new UTFException(msg); + } + } + + // The < case must be taken care of before decodeImpl is called. + assert(u >= 0xD800); + + if (u <= 0xDBFF) + { + static if (canIndex) + immutable onlyOneCodeUnit = length == 1; + else + immutable onlyOneCodeUnit = pstr.empty; + + if (onlyOneCodeUnit) + { + static if (useReplacementDchar) + { + ++index; + return replacementDchar; + } + else + throw exception("surrogate UTF-16 high value past end of string"); + } + + static if (canIndex) + immutable uint u2 = pstr[1]; + else + { + immutable uint u2 = pstr.front; + pstr.popFront(); + } + + if (u2 < 0xDC00 || u2 > 0xDFFF) + { + static if (useReplacementDchar) + u = replacementDchar; + else + throw exception("surrogate UTF-16 low value out of range"); + } + else + u = ((u - 0xD7C0) << 10) + (u2 - 0xDC00); + ++index; + } + else if (u >= 0xDC00 && u <= 0xDFFF) + { + static if (useReplacementDchar) + u = replacementDchar; + else + throw exception("unpaired surrogate UTF-16 value"); + } + ++index; + + // Note: u+FFFE and u+FFFF are specifically permitted by the + // Unicode standard for application internal use (see isValidDchar) + + return cast(dchar) u; +} + +@safe pure @nogc nothrow +unittest +{ + // Add tests for useReplacemendDchar == true path + + static struct R + { + @safe pure @nogc nothrow: + this(wstring s) { this.s = s; } + @property bool empty() { return idx == s.length; } + @property wchar front() { return s[idx]; } + void popFront() { ++idx; } + size_t idx; + wstring s; + } + + foreach (s; invalidUTFstrings!wchar()) + { + auto r = R(s); + size_t index; + dchar dc = decodeImpl!(false, Yes.useReplacementDchar)(r, index); + assert(dc == replacementDchar); + assert(1 <= index && index <= s.length); + } +} + +private dchar decodeImpl(bool canIndex, UseReplacementDchar useReplacementDchar = No.useReplacementDchar, S)( + auto ref S str, ref size_t index) +if (is(S : const dchar[]) || (isInputRange!S && is(Unqual!(ElementEncodingType!S) == dchar))) +{ + static if (is(S : const dchar[])) + auto pstr = str.ptr; + else + alias pstr = str; + + static if (is(S : const dchar[]) || isRandomAccessRange!S) + { + dchar dc = pstr[index]; + if (!isValidDchar(dc)) + { + static if (useReplacementDchar) + dc = replacementDchar; + else + throw new UTFException("Invalid UTF-32 value").setSequence(dc); + } + ++index; + return dc; + } + else + { + dchar dc = pstr.front; + if (!isValidDchar(dc)) + { + static if (useReplacementDchar) + dc = replacementDchar; + else + throw new UTFException("Invalid UTF-32 value").setSequence(dc); + } + ++index; + pstr.popFront(); + return dc; + } +} + +@safe pure @nogc nothrow +unittest +{ + // Add tests for useReplacemendDchar == true path + + static struct R + { + @safe pure @nogc nothrow: + this(dstring s) { this.s = s; } + @property bool empty() { return idx == s.length; } + @property dchar front() { return s[idx]; } + void popFront() { ++idx; } + size_t idx; + dstring s; + } + + foreach (s; invalidUTFstrings!dchar()) + { + auto r = R(s); + size_t index; + dchar dc = decodeImpl!(false, Yes.useReplacementDchar)(r, index); + assert(dc == replacementDchar); + assert(1 <= index && index <= s.length); + } +} + + +version (unittest) private void testDecode(R)(R range, + size_t index, + dchar expectedChar, + size_t expectedIndex, + size_t line = __LINE__) +{ + import core.exception : AssertError; + import std.string : format; + + static if (hasLength!R) + immutable lenBefore = range.length; + + static if (isRandomAccessRange!R) + { + { + immutable result = decode(range, index); + enforce(result == expectedChar, + new AssertError(format("decode: Wrong character: %s", result), __FILE__, line)); + enforce(index == expectedIndex, + new AssertError(format("decode: Wrong index: %s", index), __FILE__, line)); + static if (hasLength!R) + { + enforce(range.length == lenBefore, + new AssertError(format("decode: length changed: %s", range.length), __FILE__, line)); + } + } + } +} + +version (unittest) private void testDecodeFront(R)(ref R range, + dchar expectedChar, + size_t expectedNumCodeUnits, + size_t line = __LINE__) +{ + import core.exception : AssertError; + import std.string : format; + + static if (hasLength!R) + immutable lenBefore = range.length; + + size_t numCodeUnits; + immutable result = decodeFront(range, numCodeUnits); + enforce(result == expectedChar, + new AssertError(format("decodeFront: Wrong character: %s", result), __FILE__, line)); + enforce(numCodeUnits == expectedNumCodeUnits, + new AssertError(format("decodeFront: Wrong numCodeUnits: %s", numCodeUnits), __FILE__, line)); + + static if (hasLength!R) + { + enforce(range.length == lenBefore - numCodeUnits, + new AssertError(format("decodeFront: wrong length: %s", range.length), __FILE__, line)); + } +} + +version (unittest) private void testDecodeBack(R)(ref R range, + dchar expectedChar, + size_t expectedNumCodeUnits, + size_t line = __LINE__) +{ + // This condition is to allow unit testing all `decode` functions together + static if (!isBidirectionalRange!R) + return; + else + { + import core.exception : AssertError; + import std.string : format; + + static if (hasLength!R) + immutable lenBefore = range.length; + + size_t numCodeUnits; + immutable result = decodeBack(range, numCodeUnits); + enforce(result == expectedChar, + new AssertError(format("decodeBack: Wrong character: %s", result), __FILE__, line)); + enforce(numCodeUnits == expectedNumCodeUnits, + new AssertError(format("decodeBack: Wrong numCodeUnits: %s", numCodeUnits), __FILE__, line)); + + static if (hasLength!R) + { + enforce(range.length == lenBefore - numCodeUnits, + new AssertError(format("decodeBack: wrong length: %s", range.length), __FILE__, line)); + } + } +} + +version (unittest) private void testAllDecode(R)(R range, + dchar expectedChar, + size_t expectedIndex, + size_t line = __LINE__) +{ + testDecode(range, 0, expectedChar, expectedIndex, line); + static if (isBidirectionalRange!R) + { + auto rangeCopy = range.save; + testDecodeBack(rangeCopy, expectedChar, expectedIndex, line); + } + testDecodeFront(range, expectedChar, expectedIndex, line); +} + +version (unittest) private void testBadDecode(R)(R range, size_t index, size_t line = __LINE__) +{ + import core.exception : AssertError; + import std.string : format; + + immutable initialIndex = index; + + static if (hasLength!R) + immutable lenBefore = range.length; + + static if (isRandomAccessRange!R) + { + assertThrown!UTFException(decode(range, index), null, __FILE__, line); + enforce(index == initialIndex, + new AssertError(format("decode: Wrong index: %s", index), __FILE__, line)); + static if (hasLength!R) + { + enforce(range.length == lenBefore, + new AssertError(format("decode: length changed:", range.length), __FILE__, line)); + } + } + + if (initialIndex == 0) + assertThrown!UTFException(decodeFront(range, index), null, __FILE__, line); +} + +version (unittest) private void testBadDecodeBack(R)(R range, size_t line = __LINE__) +{ + // This condition is to allow unit testing all `decode` functions together + static if (!isBidirectionalRange!R) + return; + else + { + import core.exception : AssertError; + import std.string : format; + + static if (hasLength!R) + immutable lenBefore = range.length; + + static if (isRandomAccessRange!R) + { + assertThrown!UTFException(decodeBack(range), null, __FILE__, line); + static if (hasLength!R) + { + enforce(range.length == lenBefore, + new AssertError(format("decodeBack: length changed:", range.length), __FILE__, line)); + } + } + } +} + +@system unittest +{ + import std.conv : to; + import std.exception; + + assertCTFEable!( + { + foreach (S; AliasSeq!(to!string, InputCU!char, RandomCU!char, + (string s) => new RefBidirCU!char(s), + (string s) => new RefRandomCU!char(s))) + { + enum sHasLength = hasLength!(typeof(S("abcd"))); + + { + auto range = S("abcd"); + testDecode(range, 0, 'a', 1); + testDecode(range, 1, 'b', 2); + testDecodeFront(range, 'a', 1); + testDecodeFront(range, 'b', 1); + assert(decodeFront(range) == 'c'); + assert(decodeFront(range) == 'd'); + } + + { + auto range = S("ウェブサイト"); + testDecode(range, 0, 'ウ', 3); + testDecode(range, 3, 'ェ', 6); + testDecodeFront(range, 'ウ', 3); + testDecodeFront(range, 'ェ', 3); + assert(decodeFront(range) == 'ブ'); + assert(decodeFront(range) == 'サ'); + } + + { + auto range = S("abcd"); + testDecodeBack(range, 'd', 1); + testDecodeBack(range, 'c', 1); + testDecodeBack(range, 'b', 1); + testDecodeBack(range, 'a', 1); + } + + { + auto range = S("ウェブサイト"); + testDecodeBack(range, 'ト', 3); + testDecodeBack(range, 'イ', 3); + testDecodeBack(range, 'サ', 3); + testDecodeBack(range, 'ブ', 3); + } + + testAllDecode(S("\xC2\xA9"), '\u00A9', 2); + testAllDecode(S("\xE2\x89\xA0"), '\u2260', 3); + + foreach (str; ["\xE2\x89", // too short + "\xC0\x8A", + "\xE0\x80\x8A", + "\xF0\x80\x80\x8A", + "\xF8\x80\x80\x80\x8A", + "\xFC\x80\x80\x80\x80\x8A"]) + { + testBadDecode(S(str), 0); + testBadDecode(S(str), 1); + testBadDecodeBack(S(str)); + } + + //Invalid UTF-8 sequence where the first code unit is valid. + testAllDecode(S("\xEF\xBF\xBE"), cast(dchar) 0xFFFE, 3); + testAllDecode(S("\xEF\xBF\xBF"), cast(dchar) 0xFFFF, 3); + + //Invalid UTF-8 sequence where the first code unit isn't valid. + foreach (str; ["\xED\xA0\x80", + "\xED\xAD\xBF", + "\xED\xAE\x80", + "\xED\xAF\xBF", + "\xED\xB0\x80", + "\xED\xBE\x80", + "\xED\xBF\xBF"]) + { + testBadDecode(S(str), 0); + testBadDecodeBack(S(str)); + } + } + }); +} + +@system unittest +{ + import std.conv : to; + import std.exception; + assertCTFEable!( + { + foreach (S; AliasSeq!(to!wstring, InputCU!wchar, RandomCU!wchar, + (wstring s) => new RefBidirCU!wchar(s), + (wstring s) => new RefRandomCU!wchar(s))) + { + testAllDecode(S([cast(wchar) 0x1111]), cast(dchar) 0x1111, 1); + testAllDecode(S([cast(wchar) 0xD800, cast(wchar) 0xDC00]), cast(dchar) 0x10000, 2); + testAllDecode(S([cast(wchar) 0xDBFF, cast(wchar) 0xDFFF]), cast(dchar) 0x10FFFF, 2); + testAllDecode(S([cast(wchar) 0xFFFE]), cast(dchar) 0xFFFE, 1); + testAllDecode(S([cast(wchar) 0xFFFF]), cast(dchar) 0xFFFF, 1); + + testBadDecode(S([ cast(wchar) 0xD801 ]), 0); + testBadDecode(S([ cast(wchar) 0xD800, cast(wchar) 0x1200 ]), 0); + + testBadDecodeBack(S([ cast(wchar) 0xD801 ])); + testBadDecodeBack(S([ cast(wchar) 0x0010, cast(wchar) 0xD800 ])); + + { + auto range = S("ウェブサイト"); + testDecode(range, 0, 'ウ', 1); + testDecode(range, 1, 'ェ', 2); + testDecodeFront(range, 'ウ', 1); + testDecodeFront(range, 'ェ', 1); + assert(decodeFront(range) == 'ブ'); + assert(decodeFront(range) == 'サ'); + } + + { + auto range = S("ウェブサイト"); + testDecodeBack(range, 'ト', 1); + testDecodeBack(range, 'イ', 1); + testDecodeBack(range, 'サ', 1); + testDecodeBack(range, 'ブ', 1); + } + } + + foreach (S; AliasSeq!(to!wstring, RandomCU!wchar, (wstring s) => new RefRandomCU!wchar(s))) + { + auto str = S([cast(wchar) 0xD800, cast(wchar) 0xDC00, + cast(wchar) 0x1400, + cast(wchar) 0xDAA7, cast(wchar) 0xDDDE]); + testDecode(str, 0, cast(dchar) 0x10000, 2); + testDecode(str, 2, cast(dchar) 0x1400, 3); + testDecode(str, 3, cast(dchar) 0xB9DDE, 5); + testDecodeBack(str, cast(dchar) 0xB9DDE, 2); + testDecodeBack(str, cast(dchar) 0x1400, 1); + testDecodeBack(str, cast(dchar) 0x10000, 2); + } + }); +} + +@system unittest +{ + import std.conv : to; + import std.exception; + assertCTFEable!( + { + foreach (S; AliasSeq!(to!dstring, RandomCU!dchar, InputCU!dchar, + (dstring s) => new RefBidirCU!dchar(s), + (dstring s) => new RefRandomCU!dchar(s))) + { + testAllDecode(S([cast(dchar) 0x1111]), cast(dchar) 0x1111, 1); + testAllDecode(S([cast(dchar) 0x10000]), cast(dchar) 0x10000, 1); + testAllDecode(S([cast(dchar) 0x10FFFF]), cast(dchar) 0x10FFFF, 1); + testAllDecode(S([cast(dchar) 0xFFFE]), cast(dchar) 0xFFFE, 1); + testAllDecode(S([cast(dchar) 0xFFFF]), cast(dchar) 0xFFFF, 1); + + testBadDecode(S([cast(dchar) 0xD800]), 0); + testBadDecode(S([cast(dchar) 0xDFFE]), 0); + testBadDecode(S([cast(dchar) 0x110000]), 0); + + testBadDecodeBack(S([cast(dchar) 0xD800])); + testBadDecodeBack(S([cast(dchar) 0xDFFE])); + testBadDecodeBack(S([cast(dchar) 0x110000])); + + { + auto range = S("ウェブサイト"); + testDecode(range, 0, 'ウ', 1); + testDecode(range, 1, 'ェ', 2); + testDecodeFront(range, 'ウ', 1); + testDecodeFront(range, 'ェ', 1); + assert(decodeFront(range) == 'ブ'); + assert(decodeFront(range) == 'サ'); + } + + { + auto range = S("ウェブサイト"); + testDecodeBack(range, 'ト', 1); + testDecodeBack(range, 'イ', 1); + testDecodeBack(range, 'サ', 1); + testDecodeBack(range, 'ブ', 1); + } + } + + foreach (S; AliasSeq!(to!dstring, RandomCU!dchar, (dstring s) => new RefRandomCU!dchar(s))) + { + auto str = S([cast(dchar) 0x10000, cast(dchar) 0x1400, cast(dchar) 0xB9DDE]); + testDecode(str, 0, 0x10000, 1); + testDecode(str, 1, 0x1400, 2); + testDecode(str, 2, 0xB9DDE, 3); + testDecodeBack(str, cast(dchar) 0xB9DDE, 1); + testDecodeBack(str, cast(dchar) 0x1400, 1); + testDecodeBack(str, cast(dchar) 0x10000, 1); + } + }); +} + +@safe unittest +{ + import std.exception; + assertCTFEable!( + { + foreach (S; AliasSeq!( char[], const( char)[], string, + wchar[], const(wchar)[], wstring, + dchar[], const(dchar)[], dstring)) + { + static assert(isSafe!({ S str; size_t i = 0; decode(str, i); })); + static assert(isSafe!({ S str; size_t i = 0; decodeFront(str, i); })); + static assert(isSafe!({ S str; decodeFront(str); })); + static assert((functionAttributes!({ S str; size_t i = 0; decode(str, i); }) & FunctionAttribute.pure_) != 0); + static assert((functionAttributes!({ + S str; size_t i = 0; decodeFront(str, i); + }) & FunctionAttribute.pure_) != 0); + static assert((functionAttributes!({ S str; decodeFront(str); }) & FunctionAttribute.pure_) != 0); + static assert((functionAttributes!({ + S str; size_t i = 0; decodeBack(str, i); + }) & FunctionAttribute.pure_) != 0); + static assert((functionAttributes!({ S str; decodeBack(str); }) & FunctionAttribute.pure_) != 0); + } + }); +} + +@safe unittest +{ + import std.exception; + char[4] val; + val[0] = 0b1111_0111; + val[1] = 0b1011_1111; + val[2] = 0b1011_1111; + val[3] = 0b1011_1111; + size_t i = 0; + assertThrown!UTFException((){ dchar ch = decode(val[], i); }()); +} +/* =================== Encode ======================= */ + +private dchar _utfException(UseReplacementDchar useReplacementDchar)(string msg, dchar c) +{ + static if (useReplacementDchar) + return replacementDchar; + else + throw new UTFException(msg).setSequence(c); +} + +/++ + Encodes $(D c) into the static array, $(D buf), and returns the actual + length of the encoded character (a number between $(D 1) and $(D 4) for + $(D char[4]) buffers and a number between $(D 1) and $(D 2) for + $(D wchar[2]) buffers). + + Throws: + $(D UTFException) if $(D c) is not a valid UTF code point. + +/ +size_t encode(UseReplacementDchar useReplacementDchar = No.useReplacementDchar)( + out char[4] buf, dchar c) @safe pure +{ + if (c <= 0x7F) + { + assert(isValidDchar(c)); + buf[0] = cast(char) c; + return 1; + } + if (c <= 0x7FF) + { + assert(isValidDchar(c)); + buf[0] = cast(char)(0xC0 | (c >> 6)); + buf[1] = cast(char)(0x80 | (c & 0x3F)); + return 2; + } + if (c <= 0xFFFF) + { + if (0xD800 <= c && c <= 0xDFFF) + c = _utfException!useReplacementDchar("Encoding a surrogate code point in UTF-8", c); + + assert(isValidDchar(c)); + L3: + buf[0] = cast(char)(0xE0 | (c >> 12)); + buf[1] = cast(char)(0x80 | ((c >> 6) & 0x3F)); + buf[2] = cast(char)(0x80 | (c & 0x3F)); + return 3; + } + if (c <= 0x10FFFF) + { + assert(isValidDchar(c)); + buf[0] = cast(char)(0xF0 | (c >> 18)); + buf[1] = cast(char)(0x80 | ((c >> 12) & 0x3F)); + buf[2] = cast(char)(0x80 | ((c >> 6) & 0x3F)); + buf[3] = cast(char)(0x80 | (c & 0x3F)); + return 4; + } + + assert(!isValidDchar(c)); + c = _utfException!useReplacementDchar("Encoding an invalid code point in UTF-8", c); + goto L3; +} + +@safe unittest +{ + import std.exception; + assertCTFEable!( + { + char[4] buf; + + assert(encode(buf, '\u0000') == 1 && buf[0 .. 1] == "\u0000"); + assert(encode(buf, '\u007F') == 1 && buf[0 .. 1] == "\u007F"); + assert(encode(buf, '\u0080') == 2 && buf[0 .. 2] == "\u0080"); + assert(encode(buf, '\u07FF') == 2 && buf[0 .. 2] == "\u07FF"); + assert(encode(buf, '\u0800') == 3 && buf[0 .. 3] == "\u0800"); + assert(encode(buf, '\uD7FF') == 3 && buf[0 .. 3] == "\uD7FF"); + assert(encode(buf, '\uE000') == 3 && buf[0 .. 3] == "\uE000"); + assert(encode(buf, 0xFFFE) == 3 && buf[0 .. 3] == "\xEF\xBF\xBE"); + assert(encode(buf, 0xFFFF) == 3 && buf[0 .. 3] == "\xEF\xBF\xBF"); + assert(encode(buf, '\U00010000') == 4 && buf[0 .. 4] == "\U00010000"); + assert(encode(buf, '\U0010FFFF') == 4 && buf[0 .. 4] == "\U0010FFFF"); + + assertThrown!UTFException(encode(buf, cast(dchar) 0xD800)); + assertThrown!UTFException(encode(buf, cast(dchar) 0xDBFF)); + assertThrown!UTFException(encode(buf, cast(dchar) 0xDC00)); + assertThrown!UTFException(encode(buf, cast(dchar) 0xDFFF)); + assertThrown!UTFException(encode(buf, cast(dchar) 0x110000)); + + assert(encode!(Yes.useReplacementDchar)(buf, cast(dchar) 0x110000) == buf.stride); + assert(buf.front == replacementDchar); + }); +} + + +/// Ditto +size_t encode(UseReplacementDchar useReplacementDchar = No.useReplacementDchar)( + out wchar[2] buf, dchar c) @safe pure +{ + if (c <= 0xFFFF) + { + if (0xD800 <= c && c <= 0xDFFF) + c = _utfException!useReplacementDchar("Encoding an isolated surrogate code point in UTF-16", c); + + assert(isValidDchar(c)); + L1: + buf[0] = cast(wchar) c; + return 1; + } + if (c <= 0x10FFFF) + { + assert(isValidDchar(c)); + buf[0] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); + buf[1] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00); + return 2; + } + + c = _utfException!useReplacementDchar("Encoding an invalid code point in UTF-16", c); + goto L1; +} + +@safe unittest +{ + import std.exception; + assertCTFEable!( + { + wchar[2] buf; + + assert(encode(buf, '\u0000') == 1 && buf[0 .. 1] == "\u0000"); + assert(encode(buf, '\uD7FF') == 1 && buf[0 .. 1] == "\uD7FF"); + assert(encode(buf, '\uE000') == 1 && buf[0 .. 1] == "\uE000"); + assert(encode(buf, 0xFFFE) == 1 && buf[0] == 0xFFFE); + assert(encode(buf, 0xFFFF) == 1 && buf[0] == 0xFFFF); + assert(encode(buf, '\U00010000') == 2 && buf[0 .. 2] == "\U00010000"); + assert(encode(buf, '\U0010FFFF') == 2 && buf[0 .. 2] == "\U0010FFFF"); + + assertThrown!UTFException(encode(buf, cast(dchar) 0xD800)); + assertThrown!UTFException(encode(buf, cast(dchar) 0xDBFF)); + assertThrown!UTFException(encode(buf, cast(dchar) 0xDC00)); + assertThrown!UTFException(encode(buf, cast(dchar) 0xDFFF)); + assertThrown!UTFException(encode(buf, cast(dchar) 0x110000)); + + assert(encode!(Yes.useReplacementDchar)(buf, cast(dchar) 0x110000) == buf.stride); + assert(buf.front == replacementDchar); + }); +} + + +/// Ditto +size_t encode(UseReplacementDchar useReplacementDchar = No.useReplacementDchar)( + out dchar[1] buf, dchar c) @safe pure +{ + if ((0xD800 <= c && c <= 0xDFFF) || 0x10FFFF < c) + c = _utfException!useReplacementDchar("Encoding an invalid code point in UTF-32", c); + else + assert(isValidDchar(c)); + buf[0] = c; + return 1; +} + +@safe unittest +{ + import std.exception; + assertCTFEable!( + { + dchar[1] buf; + + encode(buf, '\u0000'); assert(buf[0] == '\u0000'); + encode(buf, '\uD7FF'); assert(buf[0] == '\uD7FF'); + encode(buf, '\uE000'); assert(buf[0] == '\uE000'); + encode(buf, 0xFFFE ); assert(buf[0] == 0xFFFE); + encode(buf, 0xFFFF ); assert(buf[0] == 0xFFFF); + encode(buf, '\U0010FFFF'); assert(buf[0] == '\U0010FFFF'); + + assertThrown!UTFException(encode(buf, cast(dchar) 0xD800)); + assertThrown!UTFException(encode(buf, cast(dchar) 0xDBFF)); + assertThrown!UTFException(encode(buf, cast(dchar) 0xDC00)); + assertThrown!UTFException(encode(buf, cast(dchar) 0xDFFF)); + assertThrown!UTFException(encode(buf, cast(dchar) 0x110000)); + + assert(encode!(Yes.useReplacementDchar)(buf, cast(dchar) 0x110000) == buf.stride); + assert(buf.front == replacementDchar); + }); +} + + +/++ + Encodes $(D c) in $(D str)'s encoding and appends it to $(D str). + + Throws: + $(D UTFException) if $(D c) is not a valid UTF code point. + +/ +void encode(UseReplacementDchar useReplacementDchar = No.useReplacementDchar)( + ref char[] str, dchar c) @safe pure +{ + char[] r = str; + + if (c <= 0x7F) + { + assert(isValidDchar(c)); + r ~= cast(char) c; + } + else + { + char[4] buf; + uint L; + + if (c <= 0x7FF) + { + assert(isValidDchar(c)); + buf[0] = cast(char)(0xC0 | (c >> 6)); + buf[1] = cast(char)(0x80 | (c & 0x3F)); + L = 2; + } + else if (c <= 0xFFFF) + { + if (0xD800 <= c && c <= 0xDFFF) + c = _utfException!useReplacementDchar("Encoding a surrogate code point in UTF-8", c); + + assert(isValidDchar(c)); + L3: + buf[0] = cast(char)(0xE0 | (c >> 12)); + buf[1] = cast(char)(0x80 | ((c >> 6) & 0x3F)); + buf[2] = cast(char)(0x80 | (c & 0x3F)); + L = 3; + } + else if (c <= 0x10FFFF) + { + assert(isValidDchar(c)); + buf[0] = cast(char)(0xF0 | (c >> 18)); + buf[1] = cast(char)(0x80 | ((c >> 12) & 0x3F)); + buf[2] = cast(char)(0x80 | ((c >> 6) & 0x3F)); + buf[3] = cast(char)(0x80 | (c & 0x3F)); + L = 4; + } + else + { + assert(!isValidDchar(c)); + c = _utfException!useReplacementDchar("Encoding an invalid code point in UTF-8", c); + goto L3; + } + r ~= buf[0 .. L]; + } + str = r; +} + +@safe unittest +{ + import std.exception; + + assertCTFEable!( + { + char[] s = "abcd".dup; + encode(s, cast(dchar)'a'); + assert(s.length == 5); + assert(s == "abcda"); + + encode(s, cast(dchar)'\u00A9'); + assert(s.length == 7); + assert(s == "abcda\xC2\xA9"); + //assert(s == "abcda\u00A9"); // BUG: fix compiler + + encode(s, cast(dchar)'\u2260'); + assert(s.length == 10); + assert(s == "abcda\xC2\xA9\xE2\x89\xA0"); + }); +} + +@safe unittest +{ + import std.exception; + assertCTFEable!( + { + char[] buf; + + encode(buf, '\u0000'); assert(buf[0 .. $] == "\u0000"); + encode(buf, '\u007F'); assert(buf[1 .. $] == "\u007F"); + encode(buf, '\u0080'); assert(buf[2 .. $] == "\u0080"); + encode(buf, '\u07FF'); assert(buf[4 .. $] == "\u07FF"); + encode(buf, '\u0800'); assert(buf[6 .. $] == "\u0800"); + encode(buf, '\uD7FF'); assert(buf[9 .. $] == "\uD7FF"); + encode(buf, '\uE000'); assert(buf[12 .. $] == "\uE000"); + encode(buf, 0xFFFE); assert(buf[15 .. $] == "\xEF\xBF\xBE"); + encode(buf, 0xFFFF); assert(buf[18 .. $] == "\xEF\xBF\xBF"); + encode(buf, '\U00010000'); assert(buf[21 .. $] == "\U00010000"); + encode(buf, '\U0010FFFF'); assert(buf[25 .. $] == "\U0010FFFF"); + + assertThrown!UTFException(encode(buf, cast(dchar) 0xD800)); + assertThrown!UTFException(encode(buf, cast(dchar) 0xDBFF)); + assertThrown!UTFException(encode(buf, cast(dchar) 0xDC00)); + assertThrown!UTFException(encode(buf, cast(dchar) 0xDFFF)); + assertThrown!UTFException(encode(buf, cast(dchar) 0x110000)); + + assert(buf.back != replacementDchar); + encode!(Yes.useReplacementDchar)(buf, cast(dchar) 0x110000); + assert(buf.back == replacementDchar); + }); +} + +/// ditto +void encode(UseReplacementDchar useReplacementDchar = No.useReplacementDchar)( + ref wchar[] str, dchar c) @safe pure +{ + wchar[] r = str; + + if (c <= 0xFFFF) + { + if (0xD800 <= c && c <= 0xDFFF) + c = _utfException!useReplacementDchar("Encoding an isolated surrogate code point in UTF-16", c); + + assert(isValidDchar(c)); + L1: + r ~= cast(wchar) c; + } + else if (c <= 0x10FFFF) + { + wchar[2] buf; + + assert(isValidDchar(c)); + buf[0] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); + buf[1] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00); + r ~= buf; + } + else + { + assert(!isValidDchar(c)); + c = _utfException!useReplacementDchar("Encoding an invalid code point in UTF-16", c); + goto L1; + } + + str = r; +} + +@safe unittest +{ + import std.exception; + assertCTFEable!( + { + wchar[] buf; + + encode(buf, '\u0000'); assert(buf[0] == '\u0000'); + encode(buf, '\uD7FF'); assert(buf[1] == '\uD7FF'); + encode(buf, '\uE000'); assert(buf[2] == '\uE000'); + encode(buf, 0xFFFE); assert(buf[3] == 0xFFFE); + encode(buf, 0xFFFF); assert(buf[4] == 0xFFFF); + encode(buf, '\U00010000'); assert(buf[5 .. $] == "\U00010000"); + encode(buf, '\U0010FFFF'); assert(buf[7 .. $] == "\U0010FFFF"); + + assertThrown!UTFException(encode(buf, cast(dchar) 0xD800)); + assertThrown!UTFException(encode(buf, cast(dchar) 0xDBFF)); + assertThrown!UTFException(encode(buf, cast(dchar) 0xDC00)); + assertThrown!UTFException(encode(buf, cast(dchar) 0xDFFF)); + assertThrown!UTFException(encode(buf, cast(dchar) 0x110000)); + + assert(buf.back != replacementDchar); + encode!(Yes.useReplacementDchar)(buf, cast(dchar) 0x110000); + assert(buf.back == replacementDchar); + }); +} + +/// ditto +void encode(UseReplacementDchar useReplacementDchar = No.useReplacementDchar)( + ref dchar[] str, dchar c) @safe pure +{ + if ((0xD800 <= c && c <= 0xDFFF) || 0x10FFFF < c) + c = _utfException!useReplacementDchar("Encoding an invalid code point in UTF-32", c); + else + assert(isValidDchar(c)); + str ~= c; +} + +@safe unittest +{ + import std.exception; + assertCTFEable!( + { + dchar[] buf; + + encode(buf, '\u0000'); assert(buf[0] == '\u0000'); + encode(buf, '\uD7FF'); assert(buf[1] == '\uD7FF'); + encode(buf, '\uE000'); assert(buf[2] == '\uE000'); + encode(buf, 0xFFFE ); assert(buf[3] == 0xFFFE); + encode(buf, 0xFFFF ); assert(buf[4] == 0xFFFF); + encode(buf, '\U0010FFFF'); assert(buf[5] == '\U0010FFFF'); + + assertThrown!UTFException(encode(buf, cast(dchar) 0xD800)); + assertThrown!UTFException(encode(buf, cast(dchar) 0xDBFF)); + assertThrown!UTFException(encode(buf, cast(dchar) 0xDC00)); + assertThrown!UTFException(encode(buf, cast(dchar) 0xDFFF)); + assertThrown!UTFException(encode(buf, cast(dchar) 0x110000)); + + assert(buf.back != replacementDchar); + encode!(Yes.useReplacementDchar)(buf, cast(dchar) 0x110000); + assert(buf.back == replacementDchar); + }); +} + + +/++ + Returns the number of code units that are required to encode the code point + $(D c) when $(D C) is the character type used to encode it. + +/ +ubyte codeLength(C)(dchar c) @safe pure nothrow @nogc +if (isSomeChar!C) +{ + static if (C.sizeof == 1) + { + if (c <= 0x7F) return 1; + if (c <= 0x7FF) return 2; + if (c <= 0xFFFF) return 3; + if (c <= 0x10FFFF) return 4; + assert(false); + } + else static if (C.sizeof == 2) + { + return c <= 0xFFFF ? 1 : 2; + } + else + { + static assert(C.sizeof == 4); + return 1; + } +} + +/// +@safe pure nothrow @nogc unittest +{ + assert(codeLength!char('a') == 1); + assert(codeLength!wchar('a') == 1); + assert(codeLength!dchar('a') == 1); + + assert(codeLength!char('\U0010FFFF') == 4); + assert(codeLength!wchar('\U0010FFFF') == 2); + assert(codeLength!dchar('\U0010FFFF') == 1); +} + + +/++ + Returns the number of code units that are required to encode $(D str) + in a string whose character type is $(D C). This is particularly useful + when slicing one string with the length of another and the two string + types use different character types. + + Params: + C = the character type to get the encoding length for + input = the input range to calculate the encoding length from + Returns: + The number of code units in `input` when encoded to `C` + +/ +size_t codeLength(C, InputRange)(InputRange input) +if (isInputRange!InputRange && !isInfinite!InputRange && is(ElementType!InputRange : dchar)) +{ + alias EncType = Unqual!(ElementEncodingType!InputRange); + static if (isSomeString!InputRange && is(EncType == C) && is(typeof(input.length))) + return input.length; + else + { + size_t total = 0; + + foreach (dchar c; input) + total += codeLength!C(c); + + return total; + } +} + +/// +@safe unittest +{ + import std.conv : to; + assert(codeLength!char("hello world") == + to!string("hello world").length); + assert(codeLength!wchar("hello world") == + to!wstring("hello world").length); + assert(codeLength!dchar("hello world") == + to!dstring("hello world").length); + + assert(codeLength!char(`プログラミング`) == + to!string(`プログラミング`).length); + assert(codeLength!wchar(`プログラミング`) == + to!wstring(`プログラミング`).length); + assert(codeLength!dchar(`プログラミング`) == + to!dstring(`プログラミング`).length); + + string haystack = `Être sans la verité, ça, ce ne serait pas bien.`; + wstring needle = `Être sans la verité`; + assert(haystack[codeLength!char(needle) .. $] == + `, ça, ce ne serait pas bien.`); +} + +@safe unittest +{ + import std.algorithm.iteration : filter; + import std.conv : to; + import std.exception; + + assertCTFEable!( + { + foreach (S; AliasSeq!( char[], const char[], string, + wchar[], const wchar[], wstring, + dchar[], const dchar[], dstring)) + { + foreach (C; AliasSeq!(char, wchar, dchar)) + { + assert(codeLength!C(to!S("Walter Bright")) == to!(C[])("Walter Bright").length); + assert(codeLength!C(to!S(`言語`)) == to!(C[])(`言語`).length); + assert(codeLength!C(to!S(`ウェブサイト@La_Verité.com`)) == + to!(C[])(`ウェブサイト@La_Verité.com`).length); + assert(codeLength!C(to!S(`ウェブサイト@La_Verité.com`).filter!(x => true)()) == + to!(C[])(`ウェブサイト@La_Verité.com`).length); + } + } + }); +} + +/+ +Internal helper function: + +Returns true if it is safe to search for the Codepoint $(D c) inside +code units, without decoding. + +This is a runtime check that is used an optimization in various functions, +particularly, in $(D std.string). + +/ +package bool canSearchInCodeUnits(C)(dchar c) +if (isSomeChar!C) +{ + static if (C.sizeof == 1) + return c <= 0x7F; + else static if (C.sizeof == 2) + return c <= 0xD7FF || (0xE000 <= c && c <= 0xFFFF); + else static if (C.sizeof == 4) + return true; + else + static assert(0); +} +@safe unittest +{ + assert( canSearchInCodeUnits! char('a')); + assert( canSearchInCodeUnits!wchar('a')); + assert( canSearchInCodeUnits!dchar('a')); + assert(!canSearchInCodeUnits! char('ö')); //Important test: ö <= 0xFF + assert(!canSearchInCodeUnits! char(cast(char)'ö')); //Important test: ö <= 0xFF + assert( canSearchInCodeUnits!wchar('ö')); + assert( canSearchInCodeUnits!dchar('ö')); + assert(!canSearchInCodeUnits! char('日')); + assert( canSearchInCodeUnits!wchar('日')); + assert( canSearchInCodeUnits!dchar('日')); + assert(!canSearchInCodeUnits!wchar(cast(wchar) 0xDA00)); + assert( canSearchInCodeUnits!dchar(cast(dchar) 0xDA00)); + assert(!canSearchInCodeUnits! char('\U00010001')); + assert(!canSearchInCodeUnits!wchar('\U00010001')); + assert( canSearchInCodeUnits!dchar('\U00010001')); +} + +/* =================== Validation ======================= */ + +/++ + Checks to see if $(D str) is well-formed unicode or not. + + Throws: + $(D UTFException) if $(D str) is not well-formed. + +/ +void validate(S)(in S str) @safe pure +if (isSomeString!S) +{ + immutable len = str.length; + for (size_t i = 0; i < len; ) + { + decode(str, i); + } +} + + +@safe unittest // bugzilla 12923 +{ + import std.exception; + assertThrown((){ + char[3]a=[167, 133, 175]; + validate(a[]); + }()); +} + +//@@@DEPRECATED_2017-10@@@ +deprecated("To be removed November 2017. Please use std.utf.encode instead.") +char[] toUTF8(return out char[4] buf, dchar c) nothrow @nogc @safe pure +{ + const sz = encode!(Yes.useReplacementDchar)(buf, c); + return buf[0 .. sz]; +} + +/** + * Encodes the elements of `s` to UTF-8 and returns a newly allocated + * string of the elements. + * + * Params: + * s = the string to encode + * Returns: + * A UTF-8 string + * See_Also: + * For a lazy, non-allocating version of these functions, see $(LREF byUTF). + */ +string toUTF8(S)(S s) +if (isInputRange!S && !isInfinite!S && isSomeChar!(ElementEncodingType!S)) +{ + return toUTFImpl!string(s); +} + +/// +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + // The ö is represented by two UTF-8 code units + assert("Hellø"w.toUTF8.equal(['H', 'e', 'l', 'l', 0xC3, 0xB8])); + + // 𐐷 is four code units in UTF-8 + assert("𐐷"d.toUTF8.equal([0xF0, 0x90, 0x90, 0xB7])); +} + +@system pure unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : ReferenceInputRange; + + auto r1 = new ReferenceInputRange!dchar("Hellø"); + auto r2 = new ReferenceInputRange!dchar("𐐷"); + + assert(r1.toUTF8.equal(['H', 'e', 'l', 'l', 0xC3, 0xB8])); + assert(r2.toUTF8.equal([0xF0, 0x90, 0x90, 0xB7])); +} + +//@@@DEPRECATED_2017-10@@@ +deprecated("To be removed November 2017. Please use std.utf.encode instead.") +wchar[] toUTF16(return ref wchar[2] buf, dchar c) nothrow @nogc @safe pure +{ + const sz = encode!(Yes.useReplacementDchar)(buf, c); + return buf[0 .. sz]; +} + +/** + * Encodes the elements of `s` to UTF-16 and returns a newly GC allocated + * `wstring` of the elements. + * + * Params: + * s = the range to encode + * Returns: + * A UTF-16 string + * See_Also: + * For a lazy, non-allocating version of these functions, see $(LREF byUTF). + */ +wstring toUTF16(S)(S s) +if (isInputRange!S && !isInfinite!S && isSomeChar!(ElementEncodingType!S)) +{ + return toUTFImpl!wstring(s); +} + +/// +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + // these graphemes are two code units in UTF-16 and one in UTF-32 + assert("𤭢"d.length == 1); + assert("𐐷"d.length == 1); + + assert("𤭢"d.toUTF16.equal([0xD852, 0xDF62])); + assert("𐐷"d.toUTF16.equal([0xD801, 0xDC37])); +} + +@system pure unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : ReferenceInputRange; + + auto r1 = new ReferenceInputRange!dchar("𤭢"); + auto r2 = new ReferenceInputRange!dchar("𐐷"); + + assert(r1.toUTF16.equal([0xD852, 0xDF62])); + assert(r2.toUTF16.equal([0xD801, 0xDC37])); +} + + +/** + * Encodes the elements of `s` to UTF-32 and returns a newly GC allocated + * `dstring` of the elements. + * + * Params: + * s = the range to encode + * Returns: + * A UTF-32 string + * See_Also: + * For a lazy, non-allocating version of these functions, see $(LREF byUTF). + */ +dstring toUTF32(S)(S s) +if (isInputRange!S && !isInfinite!S && isSomeChar!(ElementEncodingType!S)) +{ + return toUTFImpl!dstring(s); +} + +private T toUTFImpl(T, S)(S s) +{ + static if (is(S : T)) + { + return s.idup; + } + else + { + import std.array : appender; + auto app = appender!T(); + + static if (hasLength!S || isSomeString!S) + app.reserve(s.length); + + foreach (c; s.byUTF!(Unqual!(ElementEncodingType!T))) + app.put(c); + + return app.data; + } +} + +/* =================== toUTFz ======================= */ + +/++ + Returns a C-style zero-terminated string equivalent to $(D str). $(D str) + must not contain embedded $(D '\0')'s as any C function will treat the first + $(D '\0') that it sees as the end of the string. If $(D str.empty) is + $(D true), then a string containing only $(D '\0') is returned. + + $(D toUTFz) accepts any type of string and is templated on the type of + character pointer that you wish to convert to. It will avoid allocating a + new string if it can, but there's a decent chance that it will end up having + to allocate a new string - particularly when dealing with character types + other than $(D char). + + $(RED Warning 1:) If the result of $(D toUTFz) equals $(D str.ptr), then if + anything alters the character one past the end of $(D str) (which is the + $(D '\0') character terminating the string), then the string won't be + zero-terminated anymore. The most likely scenarios for that are if you + append to $(D str) and no reallocation takes place or when $(D str) is a + slice of a larger array, and you alter the character in the larger array + which is one character past the end of $(D str). Another case where it could + occur would be if you had a mutable character array immediately after + $(D str) in memory (for example, if they're member variables in a + user-defined type with one declared right after the other) and that + character array happened to start with $(D '\0'). Such scenarios will never + occur if you immediately use the zero-terminated string after calling + $(D toUTFz) and the C function using it doesn't keep a reference to it. + Also, they are unlikely to occur even if you save the zero-terminated string + (the cases above would be among the few examples of where it could happen). + However, if you save the zero-terminate string and want to be absolutely + certain that the string stays zero-terminated, then simply append a + $(D '\0') to the string and use its $(D ptr) property rather than calling + $(D toUTFz). + + $(RED Warning 2:) When passing a character pointer to a C function, and the + C function keeps it around for any reason, make sure that you keep a + reference to it in your D code. Otherwise, it may go away during a garbage + collection cycle and cause a nasty bug when the C code tries to use it. + +/ +template toUTFz(P) +{ + P toUTFz(S)(S str) @safe pure + { + return toUTFzImpl!(P, S)(str); + } +} + +/// +@safe pure unittest +{ + auto p1 = toUTFz!(char*)("hello world"); + auto p2 = toUTFz!(const(char)*)("hello world"); + auto p3 = toUTFz!(immutable(char)*)("hello world"); + auto p4 = toUTFz!(char*)("hello world"d); + auto p5 = toUTFz!(const(wchar)*)("hello world"); + auto p6 = toUTFz!(immutable(dchar)*)("hello world"w); +} + +private P toUTFzImpl(P, S)(S str) @safe pure +if (isSomeString!S && isPointer!P && isSomeChar!(typeof(*P.init)) && + is(Unqual!(typeof(*P.init)) == Unqual!(ElementEncodingType!S)) && + is(immutable(Unqual!(ElementEncodingType!S)) == ElementEncodingType!S)) +//immutable(C)[] -> C*, const(C)*, or immutable(C)* +{ + if (str.empty) + { + typeof(*P.init)[] retval = ['\0']; + + auto trustedPtr() @trusted { return retval.ptr; } + return trustedPtr(); + } + + alias C = Unqual!(ElementEncodingType!S); + + //If the P is mutable, then we have to make a copy. + static if (is(Unqual!(typeof(*P.init)) == typeof(*P.init))) + { + return toUTFzImpl!(P, const(C)[])(cast(const(C)[])str); + } + else + { + if (!__ctfe) + { + auto trustedPtrAdd(S s) @trusted { return s.ptr + s.length; } + immutable p = trustedPtrAdd(str); + + // Peek past end of str, if it's 0, no conversion necessary. + // Note that the compiler will put a 0 past the end of static + // strings, and the storage allocator will put a 0 past the end + // of newly allocated char[]'s. + // Is p dereferenceable? A simple test: if the p points to an + // address multiple of 4, then conservatively assume the pointer + // might be pointing to a new block of memory, which might be + // unreadable. Otherwise, it's definitely pointing to valid + // memory. + if ((cast(size_t) p & 3) && *p == '\0') + return &str[0]; + } + + return toUTFzImpl!(P, const(C)[])(cast(const(C)[])str); + } +} + +private P toUTFzImpl(P, S)(S str) @safe pure +if (isSomeString!S && isPointer!P && isSomeChar!(typeof(*P.init)) && + is(Unqual!(typeof(*P.init)) == Unqual!(ElementEncodingType!S)) && + !is(immutable(Unqual!(ElementEncodingType!S)) == ElementEncodingType!S)) +//C[] or const(C)[] -> C*, const(C)*, or immutable(C)* +{ + alias InChar = ElementEncodingType!S; + alias OutChar = typeof(*P.init); + + //const(C)[] -> const(C)* or + //C[] -> C* or const(C)* + static if (( is(const(Unqual!InChar) == InChar) && is(const(Unqual!OutChar) == OutChar)) || + (!is(const(Unqual!InChar) == InChar) && !is(immutable(Unqual!OutChar) == OutChar))) + { + if (!__ctfe) + { + auto trustedPtrAdd(S s) @trusted { return s.ptr + s.length; } + auto p = trustedPtrAdd(str); + + if ((cast(size_t) p & 3) && *p == '\0') + return &str[0]; + } + + str ~= '\0'; + return &str[0]; + } + //const(C)[] -> C* or immutable(C)* or + //C[] -> immutable(C)* + else + { + import std.array : uninitializedArray; + auto copy = uninitializedArray!(Unqual!OutChar[])(str.length + 1); + copy[0 .. $ - 1] = str[]; + copy[$ - 1] = '\0'; + + auto trustedCast(typeof(copy) c) @trusted { return cast(P) c.ptr; } + return trustedCast(copy); + } +} + +private P toUTFzImpl(P, S)(S str) @safe pure +if (isSomeString!S && isPointer!P && isSomeChar!(typeof(*P.init)) && + !is(Unqual!(typeof(*P.init)) == Unqual!(ElementEncodingType!S))) +//C1[], const(C1)[], or immutable(C1)[] -> C2*, const(C2)*, or immutable(C2)* +{ + import std.array : appender; + auto retval = appender!(typeof(*P.init)[])(); + + foreach (dchar c; str) + retval.put(c); + retval.put('\0'); + + return () @trusted { return cast(P) retval.data.ptr; } (); +} + +@safe pure unittest +{ + import core.exception : AssertError; + import std.algorithm; + import std.conv : to; + import std.exception; + import std.string : format; + + assertCTFEable!( + { + foreach (S; AliasSeq!(string, wstring, dstring)) + { + alias C = Unqual!(ElementEncodingType!S); + + auto s1 = to!S("hello\U00010143\u0100\U00010143"); + auto temp = new C[](s1.length + 1); + temp[0 .. $ - 1] = s1[0 .. $]; + temp[$ - 1] = '\n'; + --temp.length; + auto trustedAssumeUnique(T)(T t) @trusted { return assumeUnique(t); } + auto s2 = trustedAssumeUnique(temp); + assert(s1 == s2); + + void trustedCStringAssert(P, S)(S s) @trusted + { + auto p = toUTFz!P(s); + assert(p[0 .. s.length] == s); + assert(p[s.length] == '\0'); + } + + foreach (P; AliasSeq!(C*, const(C)*, immutable(C)*)) + { + trustedCStringAssert!P(s1); + trustedCStringAssert!P(s2); + } + } + }); + + static void test(P, S)(S s, size_t line = __LINE__) @trusted + { + static size_t zeroLen(C)(const(C)* ptr) @trusted + { + size_t len = 0; + while (*ptr != '\0') { ++ptr; ++len; } + return len; + } + + auto p = toUTFz!P(s); + immutable len = zeroLen(p); + enforce(cmp(s, p[0 .. len]) == 0, + new AssertError(format("Unit test failed: %s %s", P.stringof, S.stringof), + __FILE__, line)); + } + + assertCTFEable!( + { + foreach (P; AliasSeq!(wchar*, const(wchar)*, immutable(wchar)*, + dchar*, const(dchar)*, immutable(dchar)*)) + { + test!P("hello\U00010143\u0100\U00010143"); + } + foreach (P; AliasSeq!( char*, const( char)*, immutable( char)*, + dchar*, const(dchar)*, immutable(dchar)*)) + { + test!P("hello\U00010143\u0100\U00010143"w); + } + foreach (P; AliasSeq!( char*, const( char)*, immutable( char)*, + wchar*, const(wchar)*, immutable(wchar)*)) + { + test!P("hello\U00010143\u0100\U00010143"d); + } + foreach (S; AliasSeq!( char[], const( char)[], + wchar[], const(wchar)[], + dchar[], const(dchar)[])) + { + auto s = to!S("hello\U00010143\u0100\U00010143"); + + foreach (P; AliasSeq!( char*, const( char)*, immutable( char)*, + wchar*, const(wchar)*, immutable(wchar)*, + dchar*, const(dchar)*, immutable(dchar)*)) + { + test!P(s); + } + } + }); +} + + +/++ + $(D toUTF16z) is a convenience function for $(D toUTFz!(const(wchar)*)). + + Encodes string $(D s) into UTF-16 and returns the encoded string. + $(D toUTF16z) is suitable for calling the 'W' functions in the Win32 API + that take an $(D LPWSTR) or $(D LPCWSTR) argument. + +/ +const(wchar)* toUTF16z(C)(const(C)[] str) @safe pure +if (isSomeChar!C) +{ + return toUTFz!(const(wchar)*)(str); +} + +@safe pure unittest +{ + import std.conv : to; + //toUTFz is already thoroughly tested, so this will just verify that + //toUTF16z compiles properly for the various string types. + foreach (S; AliasSeq!(string, wstring, dstring)) + assert(toUTF16z(to!S("hello world")) !is null); +} + + +/* ================================ tests ================================== */ + +@safe pure unittest +{ + import std.exception; + + assertCTFEable!( + { + assert(toUTF16("hello"c) == "hello"); + assert(toUTF32("hello"c) == "hello"); + assert(toUTF8 ("hello"w) == "hello"); + assert(toUTF32("hello"w) == "hello"); + assert(toUTF8 ("hello"d) == "hello"); + assert(toUTF16("hello"d) == "hello"); + + assert(toUTF16("hel\u1234o"c) == "hel\u1234o"); + assert(toUTF32("hel\u1234o"c) == "hel\u1234o"); + assert(toUTF8 ("hel\u1234o"w) == "hel\u1234o"); + assert(toUTF32("hel\u1234o"w) == "hel\u1234o"); + assert(toUTF8 ("hel\u1234o"d) == "hel\u1234o"); + assert(toUTF16("hel\u1234o"d) == "hel\u1234o"); + + assert(toUTF16("he\U0010AAAAllo"c) == "he\U0010AAAAllo"); + assert(toUTF32("he\U0010AAAAllo"c) == "he\U0010AAAAllo"); + assert(toUTF8 ("he\U0010AAAAllo"w) == "he\U0010AAAAllo"); + assert(toUTF32("he\U0010AAAAllo"w) == "he\U0010AAAAllo"); + assert(toUTF8 ("he\U0010AAAAllo"d) == "he\U0010AAAAllo"); + assert(toUTF16("he\U0010AAAAllo"d) == "he\U0010AAAAllo"); + }); +} + + +/++ + Returns the total number of code points encoded in $(D str). + + Supercedes: This function supercedes $(LREF toUCSindex). + + Standards: Unicode 5.0, ASCII, ISO-8859-1, WINDOWS-1252 + + Throws: + $(D UTFException) if $(D str) is not well-formed. + +/ +size_t count(C)(const(C)[] str) @trusted pure nothrow @nogc +if (isSomeChar!C) +{ + return walkLength(str); +} + +@safe pure nothrow @nogc unittest +{ + import std.exception; + assertCTFEable!( + { + assert(count("") == 0); + assert(count("a") == 1); + assert(count("abc") == 3); + assert(count("\u20AC100") == 4); + }); +} + + +// Ranges of code units for testing. +version (unittest) +{ + struct InputCU(C) + { + import std.conv : to; + @property bool empty() { return _str.empty; } + @property C front() { return _str[0]; } + void popFront() { _str = _str[1 .. $]; } + + this(inout(C)[] str) + { + _str = to!(C[])(str); + } + + C[] _str; + } + + struct BidirCU(C) + { + import std.conv : to; + @property bool empty() { return _str.empty; } + @property C front() { return _str[0]; } + void popFront() { _str = _str[1 .. $]; } + @property C back() { return _str[$ - 1]; } + void popBack() { _str = _str[0 .. $ - 1]; } + @property auto save() { return BidirCU(_str); } + @property size_t length() { return _str.length; } + + this(inout(C)[] str) + { + _str = to!(C[])(str); + } + + C[] _str; + } + + struct RandomCU(C) + { + import std.conv : to; + @property bool empty() { return _str.empty; } + @property C front() { return _str[0]; } + void popFront() { _str = _str[1 .. $]; } + @property C back() { return _str[$ - 1]; } + void popBack() { _str = _str[0 .. $ - 1]; } + @property auto save() { return RandomCU(_str); } + @property size_t length() { return _str.length; } + C opIndex(size_t i) { return _str[i]; } + auto opSlice(size_t i, size_t j) { return RandomCU(_str[i .. j]); } + + this(inout(C)[] str) + { + _str = to!(C[])(str); + } + + C[] _str; + } + + class RefBidirCU(C) + { + import std.conv : to; + @property bool empty() { return _str.empty; } + @property C front() { return _str[0]; } + void popFront() { _str = _str[1 .. $]; } + @property C back() { return _str[$ - 1]; } + void popBack() { _str = _str[0 .. $ - 1]; } + @property auto save() { return new RefBidirCU(_str); } + @property size_t length() { return _str.length; } + + this(inout(C)[] str) + { + _str = to!(C[])(str); + } + + C[] _str; + } + + class RefRandomCU(C) + { + import std.conv : to; + @property bool empty() { return _str.empty; } + @property C front() { return _str[0]; } + void popFront() { _str = _str[1 .. $]; } + @property C back() { return _str[$ - 1]; } + void popBack() { _str = _str[0 .. $ - 1]; } + @property auto save() { return new RefRandomCU(_str); } + @property size_t length() { return _str.length; } + C opIndex(size_t i) { return _str[i]; } + auto opSlice(size_t i, size_t j) { return new RefRandomCU(_str[i .. j]); } + + this(inout(C)[] str) + { + _str = to!(C[])(str); + } + + C[] _str; + } +} + + +/** + * Inserted in place of invalid UTF sequences. + * + * References: + * $(LINK http://en.wikipedia.org/wiki/Replacement_character#Replacement_character) + */ +enum dchar replacementDchar = '\uFFFD'; + +/******************************************** + * Iterate a range of char, wchar, or dchars by code unit. + * + * The purpose is to bypass the special case decoding that + * $(REF front, std,range,primitives) does to character arrays. As a result, + * using ranges with `byCodeUnit` can be `nothrow` while + * $(REF front, std,range,primitives) throws when it encounters invalid Unicode + * sequences. + * + * A code unit is a building block of the UTF encodings. Generally, an + * individual code unit does not represent what's perceived as a full + * character (a.k.a. a grapheme cluster in Unicode terminology). Many characters + * are encoded with multiple code units. For example, the UTF-8 code units for + * `ø` are `0xC3 0xB8`. That means, an individual element of `byCodeUnit` + * often does not form a character on its own. Attempting to treat it as + * one while iterating over the resulting range will give nonsensical results. + * + * Params: + * r = an input range 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` + * is returned. + * + * Otherwise, `r` is converted to its corresponding string type (if it's + * not already a string) and wrapped in a random-access range where the + * element encoding type of the string (its code unit) is the element type + * of the range, and that range returned. The range has slicing. + * + * If `r` is quirky enough to be a struct or class which is an input range + * of characters on its own (i.e. it has the input range API as member + * functions), $(I and) it's implicitly convertible to a string type, then + * `r` is returned, and no implicit conversion takes place. + * See_Also: + * Refer to the $(MREF std, uni) docs for a reference on Unicode + * terminology. + * + * For a range that iterates by grapheme cluster (written character) see + * $(REF byGrapheme, std,uni). + */ +auto byCodeUnit(R)(R r) +if (isAutodecodableString!R || + isInputRange!R && isSomeChar!(ElementEncodingType!R) || + (is(R : const dchar[]) && !isStaticArray!R)) +{ + static if (isNarrowString!R || + // This would be cleaner if we had a way to check whether a type + // was a range without any implicit conversions. + (isAutodecodableString!R && !__traits(hasMember, R, "empty") && + !__traits(hasMember, R, "front") && !__traits(hasMember, R, "popFront"))) + { + static struct ByCodeUnitImpl + { + @safe pure nothrow @nogc: + + @property bool empty() const { return str.length == 0; } + @property auto ref front() inout { return str[0]; } + void popFront() { str = str[1 .. $]; } + + @property auto save() { return ByCodeUnitImpl(str.save); } + + @property auto ref back() inout { return str[$ - 1]; } + void popBack() { str = str[0 .. $-1]; } + + auto ref opIndex(size_t index) inout { return str[index]; } + auto opSlice(size_t lower, size_t upper) { return ByCodeUnitImpl(str[lower .. upper]); } + + @property size_t length() const { return str.length; } + alias opDollar = length; + + private: + StringTypeOf!R str; + } + + static assert(isRandomAccessRange!ByCodeUnitImpl); + + return ByCodeUnitImpl(r); + } + else static if (is(R : const dchar[]) && !__traits(hasMember, R, "empty") && + !__traits(hasMember, R, "front") && !__traits(hasMember, R, "popFront")) + { + return cast(StringTypeOf!R) r; + } + else + { + // byCodeUnit for ranges and dchar[] is a no-op + return r; + } +} + +/// +@safe unittest +{ + import std.range.primitives; + + auto r = "Hello, World!".byCodeUnit(); + static assert(hasLength!(typeof(r))); + static assert(hasSlicing!(typeof(r))); + static assert(isRandomAccessRange!(typeof(r))); + static assert(is(ElementType!(typeof(r)) == immutable char)); + + // contrast with the range capabilities of standard strings + auto s = "Hello, World!"; + static assert(isBidirectionalRange!(typeof(r))); + static assert(is(ElementType!(typeof(s)) == dchar)); + + static assert(!isRandomAccessRange!(typeof(s))); + static assert(!hasSlicing!(typeof(s))); + static assert(!hasLength!(typeof(s))); +} + +/// `byCodeUnit` does no Unicode decoding +@safe unittest +{ + string noel1 = "noe\u0308l"; // noël using e + combining diaeresis + assert(noel1.byCodeUnit[2] != 'ë'); + assert(noel1.byCodeUnit[2] == 'e'); + + string noel2 = "no\u00EBl"; // noël using a precomposed ë character + // Because string is UTF-8, the code unit at index 2 is just + // the first of a sequence that encodes 'ë' + assert(noel2.byCodeUnit[2] != 'ë'); +} + +@safe pure nothrow @nogc unittest +{ + import std.range; + { + enum testStr = "𐁄𐂌𐃯 hello ディラン"; + char[testStr.length] s; + int i; + foreach (c; testStr.byCodeUnit().byCodeUnit()) + { + s[i++] = c; + } + assert(s == testStr); + } + { + enum testStr = "𐁄𐂌𐃯 hello ディラン"w; + wchar[testStr.length] s; + int i; + foreach (c; testStr.byCodeUnit().byCodeUnit()) + { + s[i++] = c; + } + assert(s == testStr); + } + { + enum testStr = "𐁄𐂌𐃯 hello ディラン"d; + dchar[testStr.length] s; + int i; + foreach (c; testStr.byCodeUnit().byCodeUnit()) + { + s[i++] = c; + } + assert(s == testStr); + } + { + auto bcu = "hello".byCodeUnit(); + assert(bcu.length == 5); + assert(bcu[3] == 'l'); + assert(bcu[2 .. 4][1] == 'l'); + } + { + char[5] orig = "hello"; + auto bcu = orig[].byCodeUnit(); + bcu.front = 'H'; + assert(bcu.front == 'H'); + bcu[1] = 'E'; + assert(bcu[1] == 'E'); + } + { + auto bcu = "hello".byCodeUnit().byCodeUnit(); + static assert(isForwardRange!(typeof(bcu))); + static assert(is(typeof(bcu) == struct)); + auto s = bcu.save; + bcu.popFront(); + assert(s.front == 'h'); + } + { + auto bcu = "hello".byCodeUnit(); + static assert(hasSlicing!(typeof(bcu))); + static assert(isBidirectionalRange!(typeof(bcu))); + static assert(is(typeof(bcu) == struct)); + static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); + auto ret = bcu.retro; + assert(ret.front == 'o'); + ret.popFront(); + assert(ret.front == 'l'); + } + { + auto bcu = "κόσμε"w.byCodeUnit(); + static assert(hasSlicing!(typeof(bcu))); + static assert(isBidirectionalRange!(typeof(bcu))); + static assert(is(typeof(bcu) == struct)); + static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); + auto ret = bcu.retro; + assert(ret.front == 'ε'); + ret.popFront(); + assert(ret.front == 'μ'); + } + { + static struct Stringish + { + string s; + alias s this; + } + + auto orig = Stringish("\U0010fff8 𐁊 foo 𐂓"); + auto bcu = orig.byCodeUnit(); + static assert(is(typeof(bcu) == struct)); + static assert(!is(typeof(bcu) == Stringish)); + static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); + static assert(is(ElementType!(typeof(bcu)) == immutable char)); + assert(bcu.front == cast(char) 244); + } + { + static struct WStringish + { + wstring s; + alias s this; + } + + auto orig = WStringish("\U0010fff8 𐁊 foo 𐂓"w); + auto bcu = orig.byCodeUnit(); + static assert(is(typeof(bcu) == struct)); + static assert(!is(typeof(bcu) == WStringish)); + static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); + static assert(is(ElementType!(typeof(bcu)) == immutable wchar)); + assert(bcu.front == cast(wchar) 56319); + } + { + static struct DStringish + { + dstring s; + alias s this; + } + + auto orig = DStringish("\U0010fff8 𐁊 foo 𐂓"d); + auto bcu = orig.byCodeUnit(); + static assert(is(typeof(bcu) == dstring)); + static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); + static assert(is(ElementType!(typeof(bcu)) == immutable dchar)); + assert(bcu.front == cast(dchar) 1114104); + } + { + static struct FuncStringish + { + string str; + string s() pure nothrow @nogc { return str; } + alias s this; + } + + auto orig = FuncStringish("\U0010fff8 𐁊 foo 𐂓"); + auto bcu = orig.byCodeUnit(); + static assert(is(typeof(bcu) == struct)); + static assert(!is(typeof(bcu) == FuncStringish)); + static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); + static assert(is(ElementType!(typeof(bcu)) == immutable char)); + assert(bcu.front == cast(char) 244); + } + { + static struct Range + { + string data; + bool empty() pure nothrow @nogc { return data.empty; } + char front() pure nothrow @nogc { return data[0]; } + void popFront() pure nothrow @nogc { data = data[1 .. $]; } + } + + auto orig = Range("\U0010fff8 𐁊 foo 𐂓"); + auto bcu = orig.byCodeUnit(); + static assert(is(typeof(bcu) == Range)); + static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); + static assert(is(ElementType!(typeof(bcu)) == char)); + assert(bcu.front == cast(char) 244); + } + { + static struct WRange + { + wstring data; + bool empty() pure nothrow @nogc { return data.empty; } + wchar front() pure nothrow @nogc { return data[0]; } + void popFront() pure nothrow @nogc { data = data[1 .. $]; } + } + + auto orig = WRange("\U0010fff8 𐁊 foo 𐂓"w); + auto bcu = orig.byCodeUnit(); + static assert(is(typeof(bcu) == WRange)); + static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); + static assert(is(ElementType!(typeof(bcu)) == wchar)); + assert(bcu.front == 56319); + } + { + static struct DRange + { + dstring data; + bool empty() pure nothrow @nogc { return data.empty; } + dchar front() pure nothrow @nogc { return data[0]; } + void popFront() pure nothrow @nogc { data = data[1 .. $]; } + } + + auto orig = DRange("\U0010fff8 𐁊 foo 𐂓"d); + auto bcu = orig.byCodeUnit(); + static assert(is(typeof(bcu) == DRange)); + static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); + static assert(is(ElementType!(typeof(bcu)) == dchar)); + assert(bcu.front == 1114104); + } + { + static struct RangeAndStringish + { + bool empty() pure nothrow @nogc { return data.empty; } + char front() pure nothrow @nogc { return data[0]; } + void popFront() pure nothrow @nogc { data = data[1 .. $]; } + + string data; + string s; + alias s this; + } + + auto orig = RangeAndStringish("test.d", "other"); + auto bcu = orig.byCodeUnit(); + static assert(is(typeof(bcu) == RangeAndStringish)); + static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); + static assert(is(ElementType!(typeof(bcu)) == char)); + assert(bcu.front == 't'); + } + { + static struct WRangeAndStringish + { + bool empty() pure nothrow @nogc { return data.empty; } + wchar front() pure nothrow @nogc { return data[0]; } + void popFront() pure nothrow @nogc { data = data[1 .. $]; } + + wstring data; + wstring s; + alias s this; + } + + auto orig = WRangeAndStringish("test.d"w, "other"w); + auto bcu = orig.byCodeUnit(); + static assert(is(typeof(bcu) == WRangeAndStringish)); + static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); + static assert(is(ElementType!(typeof(bcu)) == wchar)); + assert(bcu.front == 't'); + } + { + static struct DRangeAndStringish + { + bool empty() pure nothrow @nogc { return data.empty; } + dchar front() pure nothrow @nogc { return data[0]; } + void popFront() pure nothrow @nogc { data = data[1 .. $]; } + + dstring data; + dstring s; + alias s this; + } + + auto orig = DRangeAndStringish("test.d"d, "other"d); + auto bcu = orig.byCodeUnit(); + static assert(is(typeof(bcu) == DRangeAndStringish)); + static assert(is(typeof(bcu) == typeof(bcu.byCodeUnit()))); + static assert(is(ElementType!(typeof(bcu)) == dchar)); + assert(bcu.front == 't'); + } + { + enum Enum : string { a = "test.d" } + + auto orig = Enum.a; + auto bcu = orig.byCodeUnit(); + static assert(!is(typeof(bcu) == Enum)); + static assert(is(typeof(bcu) == struct)); + static assert(is(ElementType!(typeof(bcu)) == immutable char)); + assert(bcu.front == 't'); + } + { + enum WEnum : wstring { a = "test.d"w } + + auto orig = WEnum.a; + auto bcu = orig.byCodeUnit(); + static assert(!is(typeof(bcu) == WEnum)); + static assert(is(typeof(bcu) == struct)); + static assert(is(ElementType!(typeof(bcu)) == immutable wchar)); + assert(bcu.front == 't'); + } + { + enum DEnum : dstring { a = "test.d"d } + + auto orig = DEnum.a; + auto bcu = orig.byCodeUnit(); + static assert(is(typeof(bcu) == dstring)); + static assert(is(ElementType!(typeof(bcu)) == immutable dchar)); + assert(bcu.front == 't'); + } + + static assert(!is(typeof(byCodeUnit("hello")) == string)); + static assert(!is(typeof(byCodeUnit("hello"w)) == wstring)); + static assert(is(typeof(byCodeUnit("hello"d)) == dstring)); + + static assert(!__traits(compiles, byCodeUnit((char[5]).init))); + static assert(!__traits(compiles, byCodeUnit((wchar[5]).init))); + static assert(!__traits(compiles, byCodeUnit((dchar[5]).init))); + + enum SEnum : char[5] { a = "hello" } + enum WSEnum : wchar[5] { a = "hello"w } + enum DSEnum : dchar[5] { a = "hello"d } + + static assert(!__traits(compiles, byCodeUnit(SEnum.a))); + static assert(!__traits(compiles, byCodeUnit(WSEnum.a))); + static assert(!__traits(compiles, byCodeUnit(DSEnum.a))); +} + +/**************************** + * Iterate an input range of characters by char, wchar, or dchar. + * These aliases simply forward to $(LREF byUTF) with the + * corresponding C argument. + * + * Params: + * r = input range of characters, or array of characters + */ +alias byChar = byUTF!char; + +/// Ditto +alias byWchar = byUTF!wchar; + +/// Ditto +alias byDchar = byUTF!dchar; + +@safe pure nothrow @nogc unittest +{ + { + char[5] s; + int i; + foreach (c; "hello".byChar.byChar()) + { + //writefln("[%d] '%c'", i, c); + s[i++] = c; + } + assert(s == "hello"); + } + { + char[5+2+3+4+3+3] s; + int i; + dchar[10] a; + a[0 .. 8] = "hello\u07FF\uD7FF\U0010FFFF"d; + a[8] = 0xD800; // invalid + a[9] = cast(dchar) 0x110000; // invalid + foreach (c; a[].byChar()) + { + //writefln("[%d] '%c'", i, c); + s[i++] = c; + } + assert(s == "hello\u07FF\uD7FF\U0010FFFF\uFFFD\uFFFD"); + } + { + auto r = "hello"w.byChar(); + r.popFront(); + r.popFront(); + assert(r.front == 'l'); + } + { + auto r = "hello"d.byChar(); + r.popFront(); + r.popFront(); + assert(r.front == 'l'); + } + { + auto r = "hello"d.byChar(); + assert(isForwardRange!(typeof(r))); + auto s = r.save; + r.popFront(); + assert(s.front == 'h'); + } +} + +@safe pure nothrow @nogc unittest +{ + { + wchar[11] s; + int i; + dchar[10] a; + a[0 .. 8] = "hello\u07FF\uD7FF\U0010FFFF"d; + a[8] = 0xD800; // invalid + a[9] = cast(dchar) 0x110000; // invalid + foreach (c; a[].byWchar()) + { + //writefln("[%d] '%c' x%x", i, c, c); + s[i++] = c; + } + foreach (j, wchar c; "hello\u07FF\uD7FF\U0010FFFF\uFFFD\uFFFD"w) + { + //writefln("[%d] '%c' x%x", j, c, c); + } + assert(s == "hello\u07FF\uD7FF\U0010FFFF\uFFFD\uFFFD"w); + } + + { + auto r = "hello".byWchar(); + r.popFront(); + r.popFront(); + assert(r.front == 'l'); + } + { + auto r = "hello"d.byWchar(); + r.popFront(); + r.popFront(); + assert(r.front == 'l'); + } + { + auto r = "hello"d.byWchar(); + assert(isForwardRange!(typeof(r))); + auto s = r.save; + r.popFront(); + assert(s.front == 'h'); + } +} + +@safe pure nothrow @nogc unittest +{ + { + dchar[9] s; + int i; + string a = "hello\u07FF\uD7FF\U00010000\U0010FFFF"; // 1,2,3,4 byte sequences + foreach (c; a.byDchar()) + { + s[i++] = c; + } + assert(s == "hello\u07FF\uD7FF\U00010000\U0010FFFF"d); + } + { + foreach (s; invalidUTFstrings!char()) + { + auto r = s.byDchar(); + assert(!r.empty); + assert(r.front == r.front); + dchar c = r.front; + assert(c == replacementDchar); + } + } + { + auto r = "hello".byDchar(); + r.popFront(); + r.popFront(); + assert(r.front == 'l'); + } + + { + dchar[8] s; + int i; + wstring a = "hello\u07FF\uD7FF\U0010FFFF"w; + foreach (c; a.byDchar()) + { + //writefln("[%d] '%c' x%x", i, c, c); + s[i++] = c; + } + assert(s == "hello\u07FF\uD7FF\U0010FFFF"d); + } + { + foreach (s; invalidUTFstrings!wchar()) + { + auto r = s.byDchar(); + assert(!r.empty); + assert(r.front == r.front); + dchar c = r.front; + assert(c == replacementDchar); + } + } + { + wchar[2] ws; + ws[0] = 0xD800; + ws[1] = 0xDD00; // correct surrogate pair + auto r = ws[].byDchar(); + assert(!r.empty); + assert(r.front == r.front); + dchar c = r.front; + assert(c == '\U00010100'); + } + { + auto r = "hello"w.byDchar(); + r.popFront(); + r.popFront(); + assert(r.front == 'l'); + } + + { + dchar[5] s; + int i; + dstring a = "hello"d; + foreach (c; a.byDchar.byDchar()) + { + //writefln("[%d] '%c' x%x", i, c, c); + s[i++] = c; + } + assert(s == "hello"d); + } + { + auto r = "hello".byDchar(); + assert(isForwardRange!(typeof(r))); + auto s = r.save; + r.popFront(); + assert(s.front == 'h'); + } + { + auto r = "hello"w.byDchar(); + assert(isForwardRange!(typeof(r))); + auto s = r.save; + r.popFront(); + assert(s.front == 'h'); + } +} + +// test pure, @safe, nothrow, @nogc correctness of byChar/byWchar/byDchar, +// which needs to support ranges with and without those attributes + +pure @safe nothrow @nogc unittest +{ + dchar[5] s = "hello"d; + foreach (c; s[].byChar()) { } + foreach (c; s[].byWchar()) { } + foreach (c; s[].byDchar()) { } +} + +version (unittest) +int impureVariable; + +@system unittest +{ + static struct ImpureThrowingSystemRange(Char) + { + @property bool empty() const { return true; } + @property Char front() const { return Char.init; } + void popFront() + { + impureVariable++; + throw new Exception("only for testing nothrow"); + } + } + + foreach (Char; AliasSeq!(char, wchar, dchar)) + { + ImpureThrowingSystemRange!Char range; + foreach (c; range.byChar()) { } + foreach (c; range.byWchar()) { } + foreach (c; range.byDchar()) { } + } +} + +/**************************** + * Iterate an input range of characters by char type `C` by + * encoding the elements of the range. + * + * UTF sequences that cannot be converted to the specified encoding are + * replaced by U+FFFD per "5.22 Best Practice for U+FFFD Substitution" + * of the Unicode Standard 6.2. Hence byUTF is not symmetric. + * This algorithm is lazy, and does not allocate memory. + * `@nogc`, `pure`-ity, `nothrow`, and `@safe`-ty are inferred from the + * `r` parameter. + * + * Params: + * C = `char`, `wchar`, or `dchar` + * + * Returns: + * A forward range if `R` is a range and not auto-decodable, as defined by + * $(REF isAutodecodableString, std, traits), and if the base range is + * also a forward range. + * + * Or, if `R` is a range and it is auto-decodable and + * `is(ElementEncodingType!typeof(r) == C)`, then the range is passed + * to $(LREF byCodeUnit). + * + * Otherwise, an input range of characters. + */ +template byUTF(C) +if (isSomeChar!C) +{ + static if (!is(Unqual!C == C)) + alias byUTF = byUTF!(Unqual!C); + else: + + auto ref byUTF(R)(R r) + if (isAutodecodableString!R && isInputRange!R && isSomeChar!(ElementEncodingType!R)) + { + return byUTF(r.byCodeUnit()); + } + + auto ref byUTF(R)(R r) + if (!isAutodecodableString!R && isInputRange!R && isSomeChar!(ElementEncodingType!R)) + { + alias RC = Unqual!(ElementEncodingType!R); + + static if (is(RC == C)) + { + return r.byCodeUnit(); + } + else + { + static struct Result + { + @property bool empty() + { + return pos == fill && r.empty; + } + + @property auto front() scope // 'scope' required by call to decodeFront() below + { + if (pos == fill) + { + pos = 0; + auto c = r.front; + + if (c <= 0x7F) + { + fill = 1; + r.popFront; + buf[pos] = cast(C) c; + } + else + { + static if (is(RC == dchar)) + { + r.popFront; + dchar dc = c; + } + else + dchar dc = () @trusted { return decodeFront!(Yes.useReplacementDchar)(r); }(); + fill = cast(ushort) encode!(Yes.useReplacementDchar)(buf, dc); + } + } + return buf[pos]; + } + + void popFront() + { + if (pos == fill) + front; + ++pos; + } + + static if (isForwardRange!R) + { + @property auto save() return scope + /* `return scope` cannot be inferred because compiler does not + * track it backwards from assignment to local `ret` + */ + { + auto ret = this; + ret.r = r.save; + return ret; + } + } + + private: + + R r; + C[4 / C.sizeof] buf = void; + ushort pos, fill; + } + + return Result(r); + } + } +} + +/// +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + + // hellö as a range of `char`s, which are UTF-8 + "hell\u00F6".byUTF!char().equal(['h', 'e', 'l', 'l', 0xC3, 0xB6]); + + // `wchar`s are able to hold the ö in a single element (UTF-16 code unit) + "hell\u00F6".byUTF!wchar().equal(['h', 'e', 'l', 'l', 'ö']); + + // 𐐷 is four code units in UTF-8, two in UTF-16, and one in UTF-32 + "𐐷".byUTF!char().equal([0xF0, 0x90, 0x90, 0xB7]); + "𐐷".byUTF!wchar().equal([0xD801, 0xDC37]); + "𐐷".byUTF!dchar().equal([0x00010437]); +} diff --git a/libphobos/src/std/uuid.d b/libphobos/src/std/uuid.d new file mode 100644 index 0000000..c804e8e --- /dev/null +++ b/libphobos/src/std/uuid.d @@ -0,0 +1,1731 @@ +/** + * A $(LINK2 http://en.wikipedia.org/wiki/Universally_unique_identifier, UUID), or + * $(LINK2 http://en.wikipedia.org/wiki/Universally_unique_identifier, Universally unique identifier), + * is intended to uniquely identify information in a distributed environment + * without significant central coordination. It can be + * used to tag objects with very short lifetimes, or to reliably identify very + * persistent objects across a network. + * +$(SCRIPT inhibitQuickIndex = 1;) + +$(DIVC quickindex, +$(BOOKTABLE , +$(TR $(TH Category) $(TH Functions) +) +$(TR $(TDNW Parsing UUIDs) + $(TD $(MYREF parseUUID) + $(MYREF UUID) + $(MYREF UUIDParsingException) + $(MYREF uuidRegex) + ) + ) +$(TR $(TDNW Generating UUIDs) + $(TD $(MYREF sha1UUID) + $(MYREF randomUUID) + $(MYREF md5UUID) + ) + ) +$(TR $(TDNW Using UUIDs) + $(TD $(MYREF2 UUID.uuidVersion, uuidVersion) + $(MYREF2 UUID.variant, variant) + $(MYREF2 UUID.toString, toString) + $(MYREF2 UUID.data, data) + $(MYREF2 UUID.swap, swap) + $(MYREF2 UUID.opEquals, opEquals) + $(MYREF2 UUID.opCmp, opCmp) + $(MYREF2 UUID.toHash, toHash) + ) + ) +$(TR $(TDNW UUID namespaces) + $(TD $(MYREF dnsNamespace) + $(MYREF urlNamespace) + $(MYREF oidNamespace) + $(MYREF x500Namespace) + ) + ) +) +) + + * UUIDs have many applications. Some examples follow: Databases may use UUIDs to identify + * rows or records in order to ensure that they are unique across different + * databases, or for publication/subscription services. Network messages may be + * identified with a UUID to ensure that different parts of a message are put back together + * again. Distributed computing may use UUIDs to identify a remote procedure call. + * Transactions and classes involved in serialization may be identified by UUIDs. + * Microsoft's component object model (COM) uses UUIDs to distinguish different software + * component interfaces. UUIDs are inserted into documents from Microsoft Office programs. + * UUIDs identify audio or video streams in the Advanced Systems Format (ASF). UUIDs are + * also a basis for OIDs (object identifiers), and URNs (uniform resource name). + * + * An attractive feature of UUIDs when compared to alternatives is their relative small size, + * of 128 bits, or 16 bytes. Another is that the creation of UUIDs does not require + * a centralized authority. + * + * When UUIDs are generated by one of the defined mechanisms, they are either guaranteed + * to be unique, different from all other generated UUIDs (that is, it has never been + * generated before and it will never be generated again), or it is extremely likely + * to be unique (depending on the mechanism). + * + * For efficiency, UUID is implemented as a struct. UUIDs are therefore empty if not explicitly + * initialized. An UUID is empty if $(MYREF3 UUID.empty, empty) is true. Empty UUIDs are equal to + * $(D UUID.init), which is a UUID with all 16 bytes set to 0. + * Use UUID's constructors or the UUID generator functions to get an initialized UUID. + * + * This is a port of $(LINK2 http://www.boost.org/doc/libs/1_42_0/libs/uuid/uuid.html, + * boost._uuid) from the Boost project with some minor additions and API + * changes for a more D-like API. + * + * Standards: + * $(LINK2 http://www.ietf.org/rfc/rfc4122.txt, RFC 4122) + * + * See_Also: + * $(LINK http://en.wikipedia.org/wiki/Universally_unique_identifier) + * + * Copyright: Copyright Johannes Pfau 2011 - . + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Johannes Pfau + * Source: $(PHOBOSSRC std/_uuid.d) + * + * Macros: + * MYREF2 = <a href="#$2">$(TT $1)</a>  + * MYREF3 = <a href="#$2">$(D $1)</a> + */ +/* Copyright Johannes Pfau 2011 - 2012. + * 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) + */ +module std.uuid; + +/// +@safe unittest +{ + import std.uuid; + + UUID[] ids; + ids ~= randomUUID(); + ids ~= md5UUID("test.name.123"); + ids ~= sha1UUID("test.name.123"); + + foreach (entry; ids) + { + assert(entry.variant == UUID.Variant.rfc4122); + } + assert(ids[0].uuidVersion == UUID.Version.randomNumberBased); + assert(ids[1].toString() == "22390768-cced-325f-8f0f-cfeaa19d0ccd"); + assert(ids[1].data == [34, 57, 7, 104, 204, 237, 50, 95, 143, 15, 207, + 234, 161, 157, 12, 205]); + UUID id; + assert(id.empty); +} + +import std.range.primitives; +import std.traits; + +/** + * + */ +public struct UUID +{ + import std.meta : AliasSeq, allSatisfy; + + private: + alias skipSeq = AliasSeq!(8, 13, 18, 23); + alias byteSeq = AliasSeq!(0,2,4,6,9,11,14,16,19,21,24,26,28,30,32,34); + + @safe pure nothrow @nogc Char toChar(Char)(size_t i) const + { + if (i <= 9) + return cast(Char)('0' + i); + else + return cast(Char)('a' + (i-10)); + } + + @safe pure nothrow unittest + { + assert(UUID(cast(ubyte[16])[138, 179, 6, 14, 44, 186, 79, 35, 183, 76, 181, 45, + 179, 189, 251, 70]).toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46"); + } + + // Reinterpret the UUID as an array of some other primitive. + @trusted ref T[16 / T.sizeof] asArrayOf(T)() return + if (isIntegral!T) + { + return *cast(typeof(return)*)&data; + } + + public: + /** + * RFC 4122 defines different internal data layouts for UUIDs. These are + * the UUID formats supported by this module. It's + * possible to read, compare and use all these Variants, but + * UUIDs generated by this module will always be in rfc4122 format. + * + * Note: Do not confuse this with $(REF _Variant, std,_variant). + */ + enum Variant + { + ncs, /// NCS backward compatibility + rfc4122, /// Defined in RFC 4122 document + microsoft, /// Microsoft Corporation backward compatibility + future ///Reserved for future use + } + + /** + * RFC 4122 defines different UUID versions. The version shows + * how a UUID was generated, e.g. a version 4 UUID was generated + * from a random number, a version 3 UUID from an MD5 hash of a name. + * + * Note: + * All of these UUID versions can be read and processed by + * $(D std.uuid), but only version 3, 4 and 5 UUIDs can be generated. + */ + enum Version + { + ///Unknown version + unknown = -1, + ///Version 1 + timeBased = 1, + ///Version 2 + dceSecurity = 2, + ///Version 3 (Name based + MD5) + nameBasedMD5 = 3, + ///Version 4 (Random) + randomNumberBased = 4, + ///Version 5 (Name based + SHA-1) + nameBasedSHA1 = 5 + } + + union + { + /** + * It is sometimes useful to get or set the 16 bytes of a UUID + * directly. + * + * Note: + * UUID uses a 16-ubyte representation for the UUID data. + * RFC 4122 defines a UUID as a special structure in big-endian + * format. These 16-ubytes always equal the big-endian structure + * defined in RFC 4122. + * + * Example: + * ----------------------------------------------- + * auto rawData = uuid.data; //get data + * rawData[0] = 1; //modify + * uuid.data = rawData; //set data + * uuid.data[1] = 2; //modify directly + * ----------------------------------------------- + */ + ubyte[16] data; + private ulong[2] ulongs; + static if (size_t.sizeof == 4) + private uint[4] uints; + } + + /* + * We could use a union here to also provide access to the + * fields specified in RFC 4122, but as we never have to access + * those (only necessary for version 1 (and maybe 2) UUIDs), + * that is not needed right now. + */ + + @safe pure unittest + { + UUID tmp; + tmp.data = cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11,12, + 13,14,15]; + assert(tmp.data == cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11, + 12,13,14,15]); + tmp.data[2] = 3; + assert(tmp.data == cast(ubyte[16])[0,1,3,3,4,5,6,7,8,9,10,11, + 12,13,14,15]); + + auto tmp2 = cast(immutable UUID) tmp; + assert(tmp2.data == cast(ubyte[16])[0,1,3,3,4,5,6,7,8,9,10,11, + 12,13,14,15]); + } + + /** + * Construct a UUID struct from the 16 byte representation + * of a UUID. + */ + @safe pure nothrow @nogc this(ref in ubyte[16] uuidData) + { + data = uuidData; + } + /// ditto + @safe pure nothrow @nogc this(in ubyte[16] uuidData) + { + data = uuidData; + } + + /// + @safe pure unittest + { + enum ubyte[16] data = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]; + auto uuid = UUID(data); + enum ctfe = UUID(data); + assert(uuid.data == data); + assert(ctfe.data == data); + } + + /** + * Construct a UUID struct from the 16 byte representation + * of a UUID. Variadic constructor to allow a simpler syntax, see examples. + * You need to pass exactly 16 ubytes. + */ + @safe pure this(T...)(T uuidData) + if (uuidData.length == 16 && allSatisfy!(isIntegral, T)) + { + import std.conv : to; + + foreach (idx, it; uuidData) + { + this.data[idx] = to!ubyte(it); + } + } + + /// + @safe unittest + { + auto tmp = UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15); + assert(tmp.data == cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11, + 12,13,14,15]); + } + + @safe unittest + { + UUID tmp = UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15); + assert(tmp.data == cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11, + 12,13,14,15]); + + enum UUID ctfeID = UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15); + assert(ctfeID == tmp); + + //Too few arguments + assert(!__traits(compiles, typeof(UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14)))); + + //Too many arguments + assert(!__traits(compiles, typeof(UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,1)))); + } + + /** + * <a name="UUID(string)"></a> + * Parse a UUID from its canonical string form. An UUID in its + * canonical form looks like this: 8ab3060e-2cba-4f23-b74c-b52db3bdfb46 + * + * Throws: + * $(LREF UUIDParsingException) if the input is invalid + * + * CTFE: + * This function is supported in CTFE code. Note that error messages + * caused by a malformed UUID parsed at compile time can be cryptic, + * but errors are detected and reported at + * compile time. + * + * Note: + * This is a strict parser. It only accepts the pattern above. + * It doesn't support any leading or trailing characters. It only + * accepts characters used for hex numbers and the string must have + * hyphens exactly like above. + * + * For a less strict parser, see $(LREF parseUUID) + */ + this(T)(in T[] uuid) if (isSomeChar!(Unqual!T)) + { + import std.conv : to, parse; + if (uuid.length < 36) + { + throw new UUIDParsingException(to!string(uuid), 0, + UUIDParsingException.Reason.tooLittle, "Insufficient Input"); + } + if (uuid.length > 36) + { + throw new UUIDParsingException(to!string(uuid), 35, UUIDParsingException.Reason.tooMuch, + "Input is too long, need exactly 36 characters"); + } + static immutable skipInd = [skipSeq]; + foreach (pos; skipInd) + if (uuid[pos] != '-') + throw new UUIDParsingException(to!string(uuid), pos, + UUIDParsingException.Reason.invalidChar, "Expected '-'"); + + ubyte[16] data2; //ctfe bug + uint pos = void; + + foreach (i, p; byteSeq) + { + enum uint s = 'a'-10-'0'; + uint h = uuid[p]; + uint l = uuid[p+1]; + pos = p; + if (h < '0') goto Lerr; + if (l < '0') goto Lerr; + if (h > '9') + { + h |= 0x20; //poorman's tolower + if (h < 'a') goto Lerr; + if (h > 'f') goto Lerr; + h -= s; + } + if (l > '9') + { + l |= 0x20; //poorman's tolower + if (l < 'a') goto Lerr; + if (l > 'f') goto Lerr; + l -= s; + } + h -= '0'; + l -= '0'; + + data2[i] = cast(ubyte)((h << 4) ^ l); + } + this.data = data2; + return; + + Lerr: throw new UUIDParsingException(to!string(uuid), pos, + UUIDParsingException.Reason.invalidChar, "Couldn't parse ubyte"); + } + + /// + @safe pure unittest + { + auto id = UUID("8AB3060E-2cba-4f23-b74c-b52db3bdfb46"); + assert(id.data == [138, 179, 6, 14, 44, 186, 79, 35, 183, 76, + 181, 45, 179, 189, 251, 70]); + assert(id.toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46"); + + //Can also be used in CTFE, for example as UUID literals: + enum ctfeID = UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"); + //here parsing is done at compile time, no runtime overhead! + } + + @safe pure unittest + { + import std.conv : to; + import std.exception; + import std.meta; + + foreach (S; AliasSeq!(char[], const(char)[], immutable(char)[], + wchar[], const(wchar)[], immutable(wchar)[], + dchar[], const(dchar)[], immutable(dchar)[], + immutable(char[]), immutable(wchar[]), immutable(dchar[]))) + { + //Test valid, working cases + assert(UUID(to!S("00000000-0000-0000-0000-000000000000")).empty); + + auto id = UUID(to!S("8AB3060E-2cba-4f23-b74c-b52db3bdfb46")); + assert(id.data == [138, 179, 6, 14, 44, 186, 79, 35, 183, 76, + 181, 45, 179, 189, 251, 70]); + assert(id.toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46"); + + enum UUID ctfe = UUID(to!S("8ab3060e-2cba-4f23-b74c-b52db3bdfb46")); + assert(ctfe == id); + + assert(UUID(to!S("5668122d-9df0-49a4-ad0b-b9b0a57f886a")).data + == [86, 104, 18, 45, 157, 240, 73, 164, 173, 11, 185, 176, 165, 127, 136, 106]); + + //Test too short UUIDS + auto except = collectException!UUIDParsingException( + UUID(to!S("5668122d-9df0-49a4-ad0b-b9b0a57f886"))); + assert(except && except.reason == UUIDParsingException.Reason.tooLittle); + + //Test too long UUIDS + except = collectException!UUIDParsingException( + UUID(to!S("5668122d-9df0-49a4-ad0b-b9b0a57f886aa"))); + assert(except && except.reason == UUIDParsingException.Reason.tooMuch); + + //Test dashes + except = collectException!UUIDParsingException( + UUID(to!S("8ab3060e2cba-4f23-b74c-b52db3bdfb-46"))); + assert(except && except.reason == UUIDParsingException.Reason.invalidChar); + + //Test dashes 2 + except = collectException!UUIDParsingException( + UUID(to!S("8ab3-060e2cba-4f23-b74c-b52db3bdfb46"))); + assert(except && except.reason == UUIDParsingException.Reason.invalidChar); + + //Test invalid characters + //make sure 36 characters in total or we'll get a 'tooMuch' reason + except = collectException!UUIDParsingException( + UUID(to!S("{8ab3060e-2cba-4f23-b74c-b52db3bdf6}"))); + assert(except && except.reason == UUIDParsingException.Reason.invalidChar); + + //Boost test + assert(UUID(to!S("01234567-89ab-cdef-0123-456789ABCDEF")) + == UUID(cast(ubyte[16])[0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,0x01, + 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef])); + } + } + + /** + * Returns true if and only if the UUID is equal + * to {00000000-0000-0000-0000-000000000000} + */ + @trusted pure nothrow @nogc @property bool empty() const + { + if (__ctfe) + return data == (ubyte[16]).init; + + auto p = cast(const(size_t*))data.ptr; + static if (size_t.sizeof == 4) + return p[0] == 0 && p[1] == 0 && p[2] == 0 && p[3] == 0; + else static if (size_t.sizeof == 8) + return p[0] == 0 && p[1] == 0; + else + static assert(false, "nonsense, it's not 32 or 64 bit"); + } + + /// + @safe pure unittest + { + UUID id; + assert(id.empty); + id = UUID("00000000-0000-0000-0000-000000000001"); + assert(!id.empty); + } + + @safe pure unittest + { + ubyte[16] getData(size_t i) + { + ubyte[16] data; + data[i] = 1; + return data; + } + + for (size_t i = 0; i < 16; i++) + { + assert(!UUID(getData(i)).empty); + } + + enum ctfeEmpty = UUID.init.empty; + assert(ctfeEmpty); + + bool ctfeTest() + { + for (size_t i = 0; i < 16; i++) + { + auto ctfeEmpty2 = UUID(getData(i)).empty; + assert(!ctfeEmpty2); + } + return true; + } + enum res = ctfeTest(); + } + + /** + * RFC 4122 defines different internal data layouts for UUIDs. + * Returns the format used by this UUID. + * + * Note: Do not confuse this with $(REF _Variant, std,_variant). + * The type of this property is $(MYREF3 std.uuid.UUID.Variant, _Variant). + * + * See_Also: + * $(MYREF3 UUID.Variant, Variant) + */ + @safe pure nothrow @nogc @property Variant variant() const + { + //variant is stored in octet 7 + //which is index 8, since indexes count backwards + immutable octet7 = data[8]; //octet 7 is array index 8 + + if ((octet7 & 0x80) == 0x00) //0b0xxxxxxx + return Variant.ncs; + else if ((octet7 & 0xC0) == 0x80) //0b10xxxxxx + return Variant.rfc4122; + else if ((octet7 & 0xE0) == 0xC0) //0b110xxxxx + return Variant.microsoft; + else + { + //assert((octet7 & 0xE0) == 0xE0, "Unknown UUID variant!") //0b111xxxx + return Variant.future; + } + } + + /// + @safe pure unittest + { + assert(UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46").variant + == UUID.Variant.rfc4122); + } + @system pure unittest + { + // @system due to Variant + Variant[ubyte] tests = cast(Variant[ubyte])[0x00 : Variant.ncs, + 0x10 : Variant.ncs, + 0x20 : Variant.ncs, + 0x30 : Variant.ncs, + 0x40 : Variant.ncs, + 0x50 : Variant.ncs, + 0x60 : Variant.ncs, + 0x70 : Variant.ncs, + 0x80 : Variant.rfc4122, + 0x90 : Variant.rfc4122, + 0xa0 : Variant.rfc4122, + 0xb0 : Variant.rfc4122, + 0xc0 : Variant.microsoft, + 0xd0 : Variant.microsoft, + 0xe0 : Variant.future, + 0xf0 : Variant.future]; + foreach (key, value; tests) + { + UUID u; + u.data[8] = key; + assert(u.variant == value); + } + } + + /** + * RFC 4122 defines different UUID versions. The version shows + * how a UUID was generated, e.g. a version 4 UUID was generated + * from a random number, a version 3 UUID from an MD5 hash of a name. + * Returns the version used by this UUID. + * + * See_Also: + * $(MYREF3 UUID.Version, Version) + */ + @safe pure nothrow @nogc @property Version uuidVersion() const + { + //version is stored in octet 9 + //which is index 6, since indexes count backwards + immutable octet9 = data[6]; + if ((octet9 & 0xF0) == 0x10) + return Version.timeBased; + else if ((octet9 & 0xF0) == 0x20) + return Version.dceSecurity; + else if ((octet9 & 0xF0) == 0x30) + return Version.nameBasedMD5; + else if ((octet9 & 0xF0) == 0x40) + return Version.randomNumberBased; + else if ((octet9 & 0xF0) == 0x50) + return Version.nameBasedSHA1; + else + return Version.unknown; + } + + /// + @safe unittest + { + assert(UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46").uuidVersion + == UUID.Version.randomNumberBased); + } + @system unittest + { + // @system due to cast + Version[ubyte] tests = cast(Version[ubyte]) [ + 0x00 : UUID.Version.unknown, + 0x10 : UUID.Version.timeBased, + 0x20 : UUID.Version.dceSecurity, + 0x30 : UUID.Version.nameBasedMD5, + 0x40 : UUID.Version.randomNumberBased, + 0x50 : UUID.Version.nameBasedSHA1, + 0x60 : UUID.Version.unknown, + 0x70 : UUID.Version.unknown, + 0x80 : UUID.Version.unknown, + 0x90 : UUID.Version.unknown, + 0xa0 : UUID.Version.unknown, + 0xb0 : UUID.Version.unknown, + 0xc0 : UUID.Version.unknown, + 0xd0 : UUID.Version.unknown, + 0xe0 : UUID.Version.unknown, + 0xf0 : UUID.Version.unknown]; + foreach (key, value; tests) + { + UUID u; + u.data[6] = key; + assert(u.uuidVersion == value); + } + } + + /** + * Swap the data of this UUID with the data of rhs. + */ + @safe pure nothrow @nogc void swap(ref UUID rhs) + { + immutable bck = data; + data = rhs.data; + rhs.data = bck; + } + + /// + @safe unittest + { + immutable ubyte[16] data = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]; + UUID u1; + UUID u2 = UUID(data); + u1.swap(u2); + + assert(u1 == UUID(data)); + assert(u2 == UUID.init); + } + + /** + * All of the standard numeric operators are defined for + * the UUID struct. + */ + @safe pure nothrow @nogc bool opEquals(in UUID s) const + { + return ulongs[0] == s.ulongs[0] && ulongs[1] == s.ulongs[1]; + } + + /// + @safe pure unittest + { + //compare UUIDs + assert(UUID("00000000-0000-0000-0000-000000000000") == UUID.init); + + //UUIDs in associative arrays: + int[UUID] test = [UUID("8a94f585-d180-44f7-8929-6fca0189c7d0") : 1, + UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a") : 2, + UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1") : 3]; + + assert(test[UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")] == 3); + + //UUIDS can be sorted: + import std.algorithm; + UUID[] ids = [UUID("8a94f585-d180-44f7-8929-6fca0189c7d0"), + UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a"), + UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")]; + sort(ids); + } + + /** + * ditto + */ + @safe pure nothrow @nogc bool opEquals(ref in UUID s) const + { + return ulongs[0] == s.ulongs[0] && ulongs[1] == s.ulongs[1]; + } + + /** + * ditto + */ + @safe pure nothrow @nogc int opCmp(in UUID s) const + { + import std.algorithm.comparison : cmp; + return cmp(this.data[], s.data[]); + } + + /** + * ditto + */ + @safe pure nothrow @nogc int opCmp(ref in UUID s) const + { + import std.algorithm.comparison : cmp; + return cmp(this.data[], s.data[]); + } + + /** + * ditto + */ + @safe pure nothrow @nogc UUID opAssign(in UUID s) + { + ulongs[0] = s.ulongs[0]; + ulongs[1] = s.ulongs[1]; + return this; + } + + /** + * ditto + */ + @safe pure nothrow @nogc UUID opAssign(ref in UUID s) + { + ulongs[0] = s.ulongs[0]; + ulongs[1] = s.ulongs[1]; + return this; + } + + /** + * ditto + */ + //MurmurHash2 + @safe pure nothrow @nogc size_t toHash() const + { + static if (size_t.sizeof == 4) + { + enum uint m = 0x5bd1e995; + enum uint n = 16; + enum uint r = 24; + + uint h = n; + + uint k = uints[0]; + k *= m; + k ^= k >> r; + k *= m; + + h ^= k; + h *= m; + + k = uints[1]; + k *= m; + k ^= k >> r; + k *= m; + + h ^= k; + h *= m; + + k = uints[2]; + k *= m; + k ^= k >> r; + k *= m; + + h ^= k; + h *= m; + + k = uints[3]; + k *= m; + k ^= k >> r; + k *= m; + + h ^= k; + h *= m; + } + else + { + enum ulong m = 0xc6a4a7935bd1e995UL; + enum ulong n = m * 16; + enum uint r = 47; + + ulong h = n; + + ulong k = ulongs[0]; + k *= m; + k ^= k >> r; + k *= m; + + h ^= k; + h *= m; + + k = ulongs[1]; + k *= m; + k ^= k >> r; + k *= m; + + h ^= k; + h *= m; + } + return h; + } + @safe unittest + { + assert(UUID("00000000-0000-0000-0000-000000000000") == UUID.init); + int[UUID] test = [UUID("8a94f585-d180-44f7-8929-6fca0189c7d0") : 1, + UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a") : 2, + UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1") : 3]; + + assert(test[UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")] == 3); + + import std.algorithm; + UUID[] ids = [UUID("8a94f585-d180-44f7-8929-6fca0189c7d0"), + UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a"), + UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")]; + sort(ids); + auto id2 = ids.dup; + + ids = [UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a"), + UUID("8a94f585-d180-44f7-8929-6fca0189c7d0"), + UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")]; + sort(ids); + assert(ids == id2); + + //test comparsion + UUID u1; + UUID u2 = UUID(cast(ubyte[16])[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); + UUID u3 = UUID(cast(ubyte[16])[255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255]); + + assert(u1 == u1); + + assert(u1 != u2); + + assert(u1 < u2); + assert(u2 < u3); + + assert(u1 <= u1); + assert(u1 <= u2); + assert(u2 <= u3); + + assert(u2 >= u2); + assert(u3 >= u2); + + assert(u3 >= u3); + assert(u2 >= u1); + assert(u3 >= u1); + + // test hash + assert(u1.toHash() != u2.toHash()); + assert(u2.toHash() != u3.toHash()); + assert(u3.toHash() != u1.toHash()); + } + + + /** + * Write the UUID into `sink` as an ASCII string in the canonical form, + * which is 36 characters in the form "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + * Params: + * sink = OutputRange or writeable array at least 36 entries long + */ + void toString(Writer)(scope Writer sink) const + { + char[36] result = void; + foreach (pos; skipSeq) + result[pos] = '-'; + foreach (i, pos; byteSeq) + { + const uint entry = this.data[i]; + const uint hi = entry >> 4; + result[pos ] = toChar!char(hi); + const uint lo = (entry) & 0x0F; + result[pos+1] = toChar!char(lo); + } + foreach (i, c; result) + { + static if (__traits(compiles, put(sink, c))) + put(sink, c); + else + sink[i] = cast(typeof(sink[i]))c; + } + } + + /** + * Return the UUID as a string in the canonical form. + */ + @trusted pure nothrow string toString() const + { + import std.exception : assumeUnique; + auto result = new char[36]; + toString(result); + return result.assumeUnique; + } + + /// + @safe pure unittest + { + immutable str = "8ab3060e-2cba-4f23-b74c-b52db3bdfb46"; + auto id = UUID(str); + assert(id.toString() == str); + } + + @safe pure nothrow @nogc unittest + { + import std.meta : AliasSeq; + foreach (Char; AliasSeq!(char, wchar, dchar)) + { + alias String = immutable(Char)[]; + //CTFE + enum String s = "8ab3060e-2cba-4f23-b74c-b52db3bdfb46"; + enum id = UUID(s); + static if (is(Char == char)) + { + enum p = id.toString(); + static assert(s == p); + } + //nogc + Char[36] str; + id.toString(str[]); + assert(str == s); + } + } + + @system pure nothrow @nogc unittest + { + // @system due to cast + import std.encoding : Char = AsciiChar; + enum utfstr = "8ab3060e-2cba-4f23-b74c-b52db3bdfb46"; + alias String = immutable(Char)[]; + enum String s = cast(String) utfstr; + enum id = UUID(utfstr); + //nogc + Char[36] str; + id.toString(str[]); + assert(str == s); + } + + @safe unittest + { + auto u1 = UUID(cast(ubyte[16])[138, 179, 6, 14, 44, 186, 79, + 35, 183, 76, 181, 45, 179, 189, 251, 70]); + assert(u1.toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46"); + u1 = UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"); + assert(u1.toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46"); + + char[] buf; + void sink(const(char)[] data) + { + buf ~= data; + } + u1.toString(&sink); + assert(buf == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46"); + } +} + + +/** + * This function generates a name based (Version 3) UUID from a namespace UUID and a name. + * If no namespace UUID was passed, the empty UUID $(D UUID.init) is used. + * + * Note: + * The default namespaces ($(LREF dnsNamespace), ...) defined by + * this module should be used when appropriate. + * + * RFC 4122 recommends to use Version 5 UUIDs (SHA-1) instead of Version 3 + * UUIDs (MD5) for new applications. + * + * CTFE: + * CTFE is not supported. + * + * Note: + * RFC 4122 isn't very clear on how UUIDs should be generated from names. + * It is possible that different implementations return different UUIDs + * for the same input, so be warned. The implementation for UTF-8 strings + * and byte arrays used by $(D std.uuid) is compatible with Boost's implementation. + * $(D std.uuid) guarantees that the same input to this function will generate + * the same output at any time, on any system (this especially means endianness + * doesn't matter). + * + * Note: + * This function does not provide overloads for wstring and dstring, as + * there's no clear answer on how that should be implemented. It could be + * argued, that string, wstring and dstring input should have the same output, + * but that wouldn't be compatible with Boost, which generates different output + * for strings and wstrings. It's always possible to pass wstrings and dstrings + * by using the ubyte[] function overload (but be aware of endianness issues!). + */ +@safe pure nothrow @nogc UUID md5UUID(const(char[]) name, const UUID namespace = UUID.init) +{ + return md5UUID(cast(const(ubyte[]))name, namespace); +} + +/// ditto +@safe pure nothrow @nogc UUID md5UUID(const(ubyte[]) data, const UUID namespace = UUID.init) +{ + import std.digest.md : MD5; + + MD5 hash; + hash.start(); + + /* + * NOTE: RFC 4122 says namespace should be converted to big-endian. + * We always keep the UUID data in big-endian representation, so + * that's fine + */ + hash.put(namespace.data[]); + hash.put(data[]); + + UUID u; + u.data = hash.finish(); + + //set variant + //must be 0b10xxxxxx + u.data[8] &= 0b10111111; + u.data[8] |= 0b10000000; + + //set version + //must be 0b0011xxxx + u.data[6] &= 0b00111111; + u.data[6] |= 0b00110000; + + return u; +} + +/// +@safe unittest +{ + //Use default UUID.init namespace + auto simpleID = md5UUID("test.uuid.any.string"); + + //use a name-based id as namespace + auto namespace = md5UUID("my.app"); + auto id = md5UUID("some-description", namespace); +} + +@safe pure unittest +{ + auto simpleID = md5UUID("test.uuid.any.string"); + assert(simpleID.data == cast(ubyte[16])[126, 206, 86, 72, 29, 233, 62, 213, 178, 139, 198, 136, + 188, 135, 153, 123]); + auto namespace = md5UUID("my.app"); + auto id = md5UUID("some-description", namespace); + assert(id.data == cast(ubyte[16])[166, 138, 167, 79, 48, 219, 55, 166, 170, 103, 39, 73, 216, + 150, 144, 164]); + + auto constTest = md5UUID(cast(const(char)[])"test"); + constTest = md5UUID(cast(const(char[]))"test"); + + char[] mutable = "test".dup; + id = md5UUID(mutable, namespace); + + const(ubyte)[] data = cast(ubyte[])[0,1,2,244,165,222]; + id = md5UUID(data); + assert(id.data == cast(ubyte[16])[16, 50, 29, 247, 243, 185, 61, 178, 157, 100, 253, 236, 73, + 76, 51, 47]); + + assert(id.variant == UUID.Variant.rfc4122); + assert(id.uuidVersion == UUID.Version.nameBasedMD5); + + auto correct = UUID("3d813cbb-47fb-32ba-91df-831e1593ac29"); + + auto u = md5UUID("www.widgets.com", dnsNamespace); + //enum ctfeId = md5UUID("www.widgets.com", dnsNamespace); + //assert(ctfeId == u); + assert(u == correct); + assert(u.variant == UUID.Variant.rfc4122); + assert(u.uuidVersion == UUID.Version.nameBasedMD5); +} + + /** + * This function generates a name based (Version 5) UUID from a namespace + * UUID and a name. + * If no namespace UUID was passed, the empty UUID $(D UUID.init) is used. + * + * Note: + * The default namespaces ($(LREF dnsNamespace), ...) defined by + * this module should be used when appropriate. + * + * CTFE: + * CTFE is not supported. + * + * Note: + * RFC 4122 isn't very clear on how UUIDs should be generated from names. + * It is possible that different implementations return different UUIDs + * for the same input, so be warned. The implementation for UTF-8 strings + * and byte arrays used by $(D std.uuid) is compatible with Boost's implementation. + * $(D std.uuid) guarantees that the same input to this function will generate + * the same output at any time, on any system (this especially means endianness + * doesn't matter). + * + * Note: + * This function does not provide overloads for wstring and dstring, as + * there's no clear answer on how that should be implemented. It could be + * argued, that string, wstring and dstring input should have the same output, + * but that wouldn't be compatible with Boost, which generates different output + * for strings and wstrings. It's always possible to pass wstrings and dstrings + * by using the ubyte[] function overload (but be aware of endianness issues!). + */ +@safe pure nothrow @nogc UUID sha1UUID(in char[] name, const UUID namespace = UUID.init) +{ + return sha1UUID(cast(const(ubyte[]))name, namespace); +} + +/// ditto +@safe pure nothrow @nogc UUID sha1UUID(in ubyte[] data, const UUID namespace = UUID.init) +{ + import std.digest.sha : SHA1; + + SHA1 sha; + sha.start(); + + /* + * NOTE: RFC 4122 says namespace should be converted to big-endian. + * We always keep the UUID data in big-endian representation, so + * that's fine + */ + sha.put(namespace.data[]); + sha.put(data[]); + + auto hash = sha.finish(); + auto u = UUID(); + u.data[] = hash[0 .. 16]; + + //set variant + //must be 0b10xxxxxx + u.data[8] &= 0b10111111; + u.data[8] |= 0b10000000; + + //set version + //must be 0b0101xxxx + u.data[6] &= 0b01011111; + u.data[6] |= 0b01010000; + + return u; +} + +/// +@safe unittest +{ + //Use default UUID.init namespace + auto simpleID = sha1UUID("test.uuid.any.string"); + + //use a name-based id as namespace + auto namespace = sha1UUID("my.app"); + auto id = sha1UUID("some-description", namespace); +} + +@safe pure unittest +{ + auto simpleID = sha1UUID("test.uuid.any.string"); + assert(simpleID.data == cast(ubyte[16])[16, 209, 239, 61, 99, 12, 94, 70, 159, 79, 255, 250, + 131, 79, 14, 147]); + auto namespace = sha1UUID("my.app"); + auto id = sha1UUID("some-description", namespace); + assert(id.data == cast(ubyte[16])[225, 94, 195, 219, 126, 75, 83, 71, 157, 52, 247, 43, 238, 248, + 148, 46]); + + auto constTest = sha1UUID(cast(const(char)[])"test"); + constTest = sha1UUID(cast(const(char[]))"test"); + + char[] mutable = "test".dup; + id = sha1UUID(mutable, namespace); + + const(ubyte)[] data = cast(ubyte[])[0,1,2,244,165,222]; + id = sha1UUID(data); + assert(id.data == cast(ubyte[16])[60, 65, 92, 240, 96, 46, 95, 238, 149, 100, 12, 64, 199, 194, + 243, 12]); + + auto correct = UUID("21f7f8de-8051-5b89-8680-0195ef798b6a"); + + auto u = sha1UUID("www.widgets.com", dnsNamespace); + assert(u == correct); + assert(u.variant == UUID.Variant.rfc4122); + assert(u.uuidVersion == UUID.Version.nameBasedSHA1); +} + +/** + * This function generates a random number based UUID from a random + * number generator. + * + * This function is not supported at compile time. + * + * Params: + * randomGen = uniform RNG + * See_Also: $(REF isUniformRNG, std,random) + */ +@safe UUID randomUUID() +{ + import std.random : rndGen; + return randomUUID(rndGen); +} + +/// ditto +UUID randomUUID(RNG)(ref RNG randomGen) +if (isInputRange!RNG && isIntegral!(ElementType!RNG)) +{ + import std.random : isUniformRNG; + static assert(isUniformRNG!RNG, "randomGen must be a uniform RNG"); + + alias E = ElementEncodingType!RNG; + enum size_t elemSize = E.sizeof; + static assert(elemSize <= 16); + static assert(16 % elemSize == 0); + + UUID u; + foreach (ref E e ; u.asArrayOf!E()) + { + e = randomGen.front; + randomGen.popFront(); + } + + //set variant + //must be 0b10xxxxxx + u.data[8] &= 0b10111111; + u.data[8] |= 0b10000000; + + //set version + //must be 0b0100xxxx + u.data[6] &= 0b01001111; + u.data[6] |= 0b01000000; + + return u; +} + +/// +@safe unittest +{ + import std.random : Xorshift192, unpredictableSeed; + + //simple call + auto uuid = randomUUID(); + + //provide a custom RNG. Must be seeded manually. + Xorshift192 gen; + + gen.seed(unpredictableSeed); + auto uuid3 = randomUUID(gen); +} + +/* + * Original boost.uuid used Mt19937, we don't want + * to use anything worse than that. If Random is changed + * to something else, this assert and the randomUUID function + * have to be updated. + */ +@safe unittest +{ + import std.random : rndGen, Mt19937; + static assert(is(typeof(rndGen) == Mt19937)); +} + +@safe unittest +{ + import std.random : Xorshift192, unpredictableSeed; + //simple call + auto uuid = randomUUID(); + + //provide a custom RNG. Must be seeded manually. + Xorshift192 gen; + gen.seed(unpredictableSeed); + auto uuid3 = randomUUID(gen); + + auto u1 = randomUUID(); + auto u2 = randomUUID(); + assert(u1 != u2); + assert(u1.variant == UUID.Variant.rfc4122); + assert(u1.uuidVersion == UUID.Version.randomNumberBased); +} + +/** + * This is a less strict parser compared to the parser used in the + * UUID constructor. It enforces the following rules: + * + * $(UL + * $(LI hex numbers are always two hexdigits([0-9a-fA-F])) + * $(LI there must be exactly 16 such pairs in the input, not less, not more) + * $(LI there can be exactly one dash between two hex-pairs, but not more) + * $(LI there can be multiple characters enclosing the 16 hex pairs, + * as long as these characters do not contain [0-9a-fA-F]) + * ) + * + * Note: + * Like most parsers, it consumes its argument. This means: + * ------------------------- + * string s = "8AB3060E-2CBA-4F23-b74c-B52Db3BDFB46"; + * parseUUID(s); + * assert(s == ""); + * ------------------------- + * + * Throws: + * $(LREF UUIDParsingException) if the input is invalid + * + * CTFE: + * This function is supported in CTFE code. Note that error messages + * caused by a malformed UUID parsed at compile time can be cryptic, + * but errors are detected and reported at compile time. + */ +UUID parseUUID(T)(T uuidString) +if (isSomeString!T) +{ + return parseUUID(uuidString); +} + +///ditto +UUID parseUUID(Range)(ref Range uuidRange) +if (isInputRange!Range + && is(Unqual!(ElementType!Range) == dchar)) +{ + import std.ascii : isHexDigit; + import std.conv : ConvException, parse; + + static if (isForwardRange!Range) + auto errorCopy = uuidRange.save; + + void parserError()(size_t pos, UUIDParsingException.Reason reason, string message, Throwable next = null, + string file = __FILE__, size_t line = __LINE__) + { + static if (isForwardRange!Range) + { + import std.conv : to; + static if (isInfinite!Range) + { + throw new UUIDParsingException(to!string(take(errorCopy, pos)), pos, reason, message, + next, file, line); + } + else + { + throw new UUIDParsingException(to!string(errorCopy), pos, reason, message, next, file, + line); + } + } + else + { + throw new UUIDParsingException("", pos, reason, message, next, file, line); + } + } + + static if (hasLength!Range) + { + import std.conv : to; + if (uuidRange.length < 32) + { + throw new UUIDParsingException(to!string(uuidRange), 0, UUIDParsingException.Reason.tooLittle, + "Insufficient Input"); + } + } + + UUID result; + size_t consumed; + size_t element = 0; + + //skip garbage + size_t skip()() + { + size_t skipped; + while (!uuidRange.empty && !isHexDigit(uuidRange.front)) + { + skipped++; + uuidRange.popFront(); + } + return skipped; + } + + consumed += skip(); + + if (uuidRange.empty) + parserError(consumed, UUIDParsingException.Reason.tooLittle, "Insufficient Input"); + + bool dashAllowed = false; + + parseLoop: while (!uuidRange.empty) + { + immutable character = uuidRange.front; + + if (character == '-') + { + if (!dashAllowed) + parserError(consumed, UUIDParsingException.Reason.invalidChar, "Unexpected '-'"); + else + dashAllowed = false; + + consumed++; + } + else if (!isHexDigit(character)) + { + parserError(consumed, UUIDParsingException.Reason.invalidChar, + "Unexpected character (wanted a hexDigit)"); + } + else + { + try + { + consumed += 2; + static if (isSomeString!Range) + { + if (uuidRange.length < 2) + { + parserError(consumed, UUIDParsingException.Reason.tooLittle, + "Insufficient Input"); + } + auto part = uuidRange[0 .. 2]; + result.data[element++] = parse!ubyte(part, 16); + uuidRange.popFront(); + } + else + { + dchar[2] copyBuf; + copyBuf[0] = character; + uuidRange.popFront(); + if (uuidRange.empty) + { + parserError(consumed, UUIDParsingException.Reason.tooLittle, + "Insufficient Input"); + } + copyBuf[1] = uuidRange.front; + auto part = copyBuf[]; + result.data[element++] = parse!ubyte(part, 16); + } + + if (element == 16) + { + uuidRange.popFront(); + break parseLoop; + } + + dashAllowed = true; + } + catch (ConvException e) + { + parserError(consumed, UUIDParsingException.Reason.invalidChar, + "Couldn't parse ubyte", e); + } + } + uuidRange.popFront(); + } + assert(element <= 16); + + if (element < 16) + parserError(consumed, UUIDParsingException.Reason.tooLittle, "Insufficient Input"); + + consumed += skip(); + if (!uuidRange.empty) + parserError(consumed, UUIDParsingException.Reason.invalidChar, "Unexpected character"); + + return result; +} + +/// +@safe unittest +{ + auto id = parseUUID("8AB3060E-2CBA-4F23-b74c-B52Db3BDFB46"); + //no dashes + id = parseUUID("8ab3060e2cba4f23b74cb52db3bdfb46"); + //dashes at different positions + id = parseUUID("8a-b3-06-0e2cba4f23b74c-b52db3bdfb-46"); + //leading / trailing characters + id = parseUUID("{8ab3060e-2cba-4f23-b74c-b52db3bdfb46}"); + //unicode + id = parseUUID("ü8ab3060e2cba4f23b74cb52db3bdfb46ü"); + //multiple trailing/leading characters + id = parseUUID("///8ab3060e2cba4f23b74cb52db3bdfb46||"); + + //Can also be used in CTFE, for example as UUID literals: + enum ctfeID = parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"); + //here parsing is done at compile time, no runtime overhead! +} + +@safe pure unittest +{ + import std.conv : to; + import std.exception; + import std.meta; + + struct TestRange(bool forward) + { + dstring input; + + @property dchar front() + { + return input.front; + } + + void popFront() + { + input.popFront(); + } + + @property bool empty() + { + return input.empty; + } + + static if (forward) + { + @property TestRange!true save() + { + return this; + } + } + } + alias TestInputRange = TestRange!false; + alias TestForwardRange = TestRange!true; + + assert(isInputRange!TestInputRange); + assert(is(ElementType!TestInputRange == dchar)); + assert(isInputRange!TestForwardRange); + assert(isForwardRange!TestForwardRange); + assert(is(ElementType!TestForwardRange == dchar)); + + //Helper function for unittests - Need to pass ranges by ref + UUID parseHelper(T)(string input) + { + static if (is(T == TestInputRange) || is(T == TestForwardRange)) + { + T range = T(to!dstring(input)); + return parseUUID(range); + } + else + return parseUUID(to!T(input)); + } + + foreach (S; AliasSeq!(char[], const(char)[], immutable(char)[], + wchar[], const(wchar)[], immutable(wchar)[], + dchar[], const(dchar)[], immutable(dchar)[], + immutable(char[]), immutable(wchar[]), immutable(dchar[]), + TestForwardRange, TestInputRange)) + { + //Verify examples. + auto id = parseHelper!S("8AB3060E-2CBA-4F23-b74c-B52Db3BDFB46"); + //no dashes + id = parseHelper!S("8ab3060e2cba4f23b74cb52db3bdfb46"); + //dashes at different positions + id = parseHelper!S("8a-b3-06-0e2cba4f23b74c-b52db3bdfb-46"); + //leading / trailing characters + id = parseHelper!S("{8ab3060e-2cba-4f23-b74c-b52db3bdfb46}"); + //unicode + id = parseHelper!S("ü8ab3060e2cba4f23b74cb52db3bdfb46ü"); + //multiple trailing/leading characters + id = parseHelper!S("///8ab3060e2cba4f23b74cb52db3bdfb46||"); + enum ctfeId = parseHelper!S("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"); + assert(parseHelper!S("8AB3060E-2cba-4f23-b74c-b52db3bdfb46") == ctfeId); + + //Test valid, working cases + assert(parseHelper!S("00000000-0000-0000-0000-000000000000").empty); + assert(parseHelper!S("8AB3060E-2CBA-4F23-b74c-B52Db3BDFB46").data + == [138, 179, 6, 14, 44, 186, 79, 35, 183, 76, 181, 45, 179, 189, 251, 70]); + + assert(parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886a").data + == [86, 104, 18, 45, 157, 240, 73, 164, 173, 11, 185, 176, 165, 127, 136, 106]); + + //wstring / dstring + assert(parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886a").data + == [86, 104, 18, 45, 157, 240, 73, 164, 173, 11, 185, 176, 165, 127, 136, 106]); + assert(parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886a").data + == [86, 104, 18, 45, 157, 240, 73, 164, 173, 11, 185, 176, 165, 127, 136, 106]); + + //Test too short UUIDS + auto except = collectException!UUIDParsingException( + parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886")); + assert(except && except.reason == UUIDParsingException.Reason.tooLittle); + + //Test too long UUIDS + except = collectException!UUIDParsingException( + parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886aa")); + assert(except && except.reason == UUIDParsingException.Reason.invalidChar); + + //Test too long UUIDS 2 + except = collectException!UUIDParsingException( + parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886a-aa")); + assert(except && except.reason == UUIDParsingException.Reason.invalidChar); + + //Test dashes + assert(parseHelper!S("8ab3060e2cba-4f23-b74c-b52db3bdfb46") + == parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46")); + assert(parseHelper!S("8ab3-060e2cba-4f23-b74c-b52db3bdfb46") + == parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46")); + assert(parseHelper!S("8ab3060e2cba4f23b74cb52db3bdfb46") + == parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46")); + + except = collectException!UUIDParsingException( + parseHelper!S("8-ab3060e2cba-4f23-b74c-b52db3bdfb46")); + assert(except && except.reason == UUIDParsingException.Reason.invalidChar); + + //Test leading/trailing characters + assert(parseHelper!S("{8ab3060e-2cba-4f23-b74c-b52db3bdfb46}") + == parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46")); + assert(parseHelper!S("{8ab3060e2cba4f23b74cb52db3bdfb46}") + == parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46")); + + //Boost test + auto u_increasing = UUID(cast(ubyte[16])[0x01, 0x23, 0x45, 0x67, 0x89, 0xab, + 0xcd, 0xef,0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]); + assert(parseHelper!S("0123456789abcdef0123456789ABCDEF") == UUID(cast(ubyte[16])[0x01, + 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef])); + + //unicode + assert(parseHelper!S("ü8ab3060e2cba4f23b74cb52db3bdfb46ü") + == parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46")); + + //multiple trailing/leading characters + assert(parseHelper!S("///8ab3060e2cba4f23b74cb52db3bdfb46||") + == parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46")); + } +} + +/** + * Default namespace from RFC 4122 + * + * Name string is a fully-qualified domain name + */ +enum dnsNamespace = UUID("6ba7b810-9dad-11d1-80b4-00c04fd430c8"); + +/** + * Default namespace from RFC 4122 + * + * Name string is a URL + */ +enum urlNamespace = UUID("6ba7b811-9dad-11d1-80b4-00c04fd430c8"); + +/** + * Default namespace from RFC 4122 + * + * Name string is an ISO OID + */ +enum oidNamespace = UUID("6ba7b812-9dad-11d1-80b4-00c04fd430c8"); + +/** + * Default namespace from RFC 4122 + * + * Name string is an X.500 DN (in DER or a text output format) + */ +enum x500Namespace = UUID("6ba7b814-9dad-11d1-80b4-00c04fd430c8"); + +/** + * Regex string to extract UUIDs from text. + */ +enum uuidRegex = "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}"~ + "-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"; + +/// +@safe unittest +{ + import std.algorithm; + import std.regex; + + string test = "Lorem ipsum dolor sit amet, consetetur "~ + "6ba7b814-9dad-11d1-80b4-00c04fd430c8 sadipscing \n"~ + "elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore \r\n"~ + "magna aliquyam erat, sed diam voluptua. "~ + "8ab3060e-2cba-4f23-b74c-b52db3bdfb46 At vero eos et accusam et "~ + "justo duo dolores et ea rebum."; + + auto r = regex(uuidRegex, "g"); + UUID[] found; + foreach (c; match(test, r)) + { + found ~= UUID(c.hit); + } + assert(found == [ + UUID("6ba7b814-9dad-11d1-80b4-00c04fd430c8"), + UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"), + ]); +} + +/** + * This exception is thrown if an error occurs when parsing a UUID + * from a string. + */ +public class UUIDParsingException : Exception +{ + /** + * The reason why parsing the UUID string failed (if known) + */ + enum Reason + { + unknown, /// + tooLittle, ///The passed in input was correct, but more input was expected. + tooMuch, ///The input data is too long (There's no guarantee the first part of the data is valid) + invalidChar, ///Encountered an invalid character + + } + ///ditto + Reason reason; + ///The original input string which should have been parsed. + string input; + ///The position in the input string where the error occurred. + size_t position; + + private this(string input, size_t pos, Reason why = Reason.unknown, string msg = "", + Throwable next = null, string file = __FILE__, size_t line = __LINE__) pure @trusted + { + import std.array : replace; + import std.format : format; + this.input = input; + this.position = pos; + this.reason = why; + string message = format("An error occured in the UUID parser: %s\n" ~ + " * Input:\t'%s'\n * Position:\t%s", msg, replace(replace(input, + "\r", "\\r"), "\n", "\\n"), pos); + super(message, file, line, next); + } +} + +/// +@safe unittest +{ + import std.exception : collectException; + + const inputUUID = "this-is-an-invalid-uuid"; + auto ex = collectException!UUIDParsingException(UUID(inputUUID)); + assert(ex !is null); // check that exception was thrown + assert(ex.input == inputUUID); + assert(ex.position == 0); + assert(ex.reason == UUIDParsingException.Reason.tooLittle); +} + +@safe unittest +{ + auto ex = new UUIDParsingException("foo", 10, UUIDParsingException.Reason.tooMuch); + assert(ex.input == "foo"); + assert(ex.position == 10); + assert(ex.reason == UUIDParsingException.Reason.tooMuch); +} diff --git a/libphobos/src/std/variant.d b/libphobos/src/std/variant.d new file mode 100644 index 0000000..574e2c5 --- /dev/null +++ b/libphobos/src/std/variant.d @@ -0,0 +1,2771 @@ +// Written in the D programming language. + +/** +This module implements a +$(HTTP erdani.org/publications/cuj-04-2002.html,discriminated union) +type (a.k.a. +$(HTTP en.wikipedia.org/wiki/Tagged_union,tagged union), +$(HTTP en.wikipedia.org/wiki/Algebraic_data_type,algebraic type)). +Such types are useful +for type-uniform binary interfaces, interfacing with scripting +languages, and comfortable exploratory programming. + +A $(LREF Variant) object can hold a value of any type, with very few +restrictions (such as `shared` types and noncopyable types). Setting the value +is as immediate as assigning to the `Variant` object. To read back the value of +the appropriate type `T`, use the $(LREF get!T) call. To query whether a +`Variant` currently holds a value of type `T`, use $(LREF peek!T). To fetch the +exact type currently held, call $(LREF type), which returns the `TypeInfo` of +the current value. + +In addition to $(LREF Variant), this module also defines the $(LREF Algebraic) +type constructor. Unlike `Variant`, `Algebraic` only allows a finite set of +types, which are specified in the instantiation (e.g. $(D Algebraic!(int, +string)) may only hold an `int` or a `string`). + +Credits: Reviewed by Brad Roberts. Daniel Keep provided a detailed code review +prompting the following improvements: (1) better support for arrays; (2) support +for associative arrays; (3) friendlier behavior towards the garbage collector. +Copyright: Copyright Andrei Alexandrescu 2007 - 2015. +License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). +Authors: $(HTTP erdani.org, Andrei Alexandrescu) +Source: $(PHOBOSSRC std/_variant.d) +*/ +module std.variant; + +import std.meta, std.traits, std.typecons; + +/// +@system unittest +{ + Variant a; // Must assign before use, otherwise exception ensues + // Initialize with an integer; make the type int + Variant b = 42; + assert(b.type == typeid(int)); + // Peek at the value + assert(b.peek!(int) !is null && *b.peek!(int) == 42); + // Automatically convert per language rules + auto x = b.get!(real); + + // Assign any other type, including other variants + a = b; + a = 3.14; + assert(a.type == typeid(double)); + // Implicit conversions work just as with built-in types + assert(a < b); + // Check for convertibility + assert(!a.convertsTo!(int)); // double not convertible to int + // Strings and all other arrays are supported + a = "now I'm a string"; + assert(a == "now I'm a string"); + + // can also assign arrays + a = new int[42]; + assert(a.length == 42); + a[5] = 7; + assert(a[5] == 7); + + // Can also assign class values + class Foo {} + auto foo = new Foo; + a = foo; + assert(*a.peek!(Foo) == foo); // and full type information is preserved +} + +/++ + Gives the $(D sizeof) the largest type given. + +/ +template maxSize(T...) +{ + static if (T.length == 1) + { + enum size_t maxSize = T[0].sizeof; + } + else + { + import std.algorithm.comparison : max; + enum size_t maxSize = max(T[0].sizeof, maxSize!(T[1 .. $])); + } +} + +/// +@safe unittest +{ + static assert(maxSize!(int, long) == 8); + static assert(maxSize!(bool, byte) == 1); + + struct Cat { int a, b, c; } + static assert(maxSize!(bool, Cat) == 12); +} + +struct This; + +private alias This2Variant(V, T...) = AliasSeq!(ReplaceType!(This, V, T)); + +/** + * Back-end type seldom used directly by user + * code. Two commonly-used types using $(D VariantN) are: + * + * $(OL $(LI $(LREF Algebraic): A closed discriminated union with a + * limited type universe (e.g., $(D Algebraic!(int, double, + * string)) only accepts these three types and rejects anything + * else).) $(LI $(LREF Variant): An open discriminated union allowing an + * unbounded set of types. If any of the types in the $(D Variant) + * are larger than the largest built-in type, they will automatically + * be boxed. This means that even large types will only be the size + * of a pointer within the $(D Variant), but this also implies some + * overhead. $(D Variant) can accommodate all primitive types and + * all user-defined types.)) + * + * Both $(D Algebraic) and $(D Variant) share $(D + * VariantN)'s interface. (See their respective documentations below.) + * + * $(D VariantN) is a discriminated union type parameterized + * with the largest size of the types stored ($(D maxDataSize)) + * and with the list of allowed types ($(D AllowedTypes)). If + * the list is empty, then any type up of size up to $(D + * maxDataSize) (rounded up for alignment) can be stored in a + * $(D VariantN) object without being boxed (types larger + * than this will be boxed). + * + */ +struct VariantN(size_t maxDataSize, AllowedTypesParam...) +{ + /** + The list of allowed types. If empty, any type is allowed. + */ + alias AllowedTypes = This2Variant!(VariantN, AllowedTypesParam); + +private: + // Compute the largest practical size from maxDataSize + struct SizeChecker + { + int function() fptr; + ubyte[maxDataSize] data; + } + enum size = SizeChecker.sizeof - (int function()).sizeof; + + /** Tells whether a type $(D T) is statically _allowed for + * storage inside a $(D VariantN) object by looking + * $(D T) up in $(D AllowedTypes). + */ + public template allowed(T) + { + enum bool allowed + = is(T == VariantN) + || + //T.sizeof <= size && + (AllowedTypes.length == 0 || staticIndexOf!(T, AllowedTypes) >= 0); + } + + // Each internal operation is encoded with an identifier. See + // the "handler" function below. + enum OpID { getTypeInfo, get, compare, equals, testConversion, toString, + index, indexAssign, catAssign, copyOut, length, + apply, postblit, destruct } + + // state + ptrdiff_t function(OpID selector, ubyte[size]* store, void* data) fptr + = &handler!(void); + union + { + ubyte[size] store; + // conservatively mark the region as pointers + static if (size >= (void*).sizeof) + void*[size / (void*).sizeof] p; + } + + // internals + // Handler for an uninitialized value + static ptrdiff_t handler(A : void)(OpID selector, ubyte[size]*, void* parm) + { + switch (selector) + { + case OpID.getTypeInfo: + *cast(TypeInfo *) parm = typeid(A); + break; + case OpID.copyOut: + auto target = cast(VariantN *) parm; + target.fptr = &handler!(A); + // no need to copy the data (it's garbage) + break; + case OpID.compare: + case OpID.equals: + auto rhs = cast(const VariantN *) parm; + return rhs.peek!(A) + ? 0 // all uninitialized are equal + : ptrdiff_t.min; // uninitialized variant is not comparable otherwise + case OpID.toString: + string * target = cast(string*) parm; + *target = "<Uninitialized VariantN>"; + break; + case OpID.postblit: + case OpID.destruct: + break; + case OpID.get: + case OpID.testConversion: + case OpID.index: + case OpID.indexAssign: + case OpID.catAssign: + case OpID.length: + throw new VariantException( + "Attempt to use an uninitialized VariantN"); + default: assert(false, "Invalid OpID"); + } + return 0; + } + + // Handler for all of a type's operations + static ptrdiff_t handler(A)(OpID selector, ubyte[size]* pStore, void* parm) + { + import std.conv : to; + static A* getPtr(void* untyped) + { + if (untyped) + { + static if (A.sizeof <= size) + return cast(A*) untyped; + else + return *cast(A**) untyped; + } + return null; + } + + static ptrdiff_t compare(A* rhsPA, A* zis, OpID selector) + { + static if (is(typeof(*rhsPA == *zis))) + { + if (*rhsPA == *zis) + { + return 0; + } + static if (is(typeof(*zis < *rhsPA))) + { + // Many types (such as any using the default Object opCmp) + // will throw on an invalid opCmp, so do it only + // if the caller requests it. + if (selector == OpID.compare) + return *zis < *rhsPA ? -1 : 1; + else + return ptrdiff_t.min; + } + else + { + // Not equal, and type does not support ordering + // comparisons. + return ptrdiff_t.min; + } + } + else + { + // Type does not support comparisons at all. + return ptrdiff_t.min; + } + } + + auto zis = getPtr(pStore); + // Input: TypeInfo object + // Output: target points to a copy of *me, if me was not null + // Returns: true iff the A can be converted to the type represented + // by the incoming TypeInfo + static bool tryPutting(A* src, TypeInfo targetType, void* target) + { + alias UA = Unqual!A; + alias MutaTypes = AliasSeq!(UA, ImplicitConversionTargets!UA); + alias ConstTypes = staticMap!(ConstOf, MutaTypes); + alias SharedTypes = staticMap!(SharedOf, MutaTypes); + alias SharedConstTypes = staticMap!(SharedConstOf, MutaTypes); + alias ImmuTypes = staticMap!(ImmutableOf, MutaTypes); + + static if (is(A == immutable)) + alias AllTypes = AliasSeq!(ImmuTypes, ConstTypes, SharedConstTypes); + else static if (is(A == shared)) + { + static if (is(A == const)) + alias AllTypes = SharedConstTypes; + else + alias AllTypes = AliasSeq!(SharedTypes, SharedConstTypes); + } + else + { + static if (is(A == const)) + alias AllTypes = ConstTypes; + else + alias AllTypes = AliasSeq!(MutaTypes, ConstTypes); + } + + foreach (T ; AllTypes) + { + if (targetType != typeid(T)) + continue; + + static if (is(typeof(*cast(T*) target = *src)) || + is(T == const(U), U) || + is(T == shared(U), U) || + is(T == shared const(U), U) || + is(T == immutable(U), U)) + { + import std.conv : emplaceRef; + + auto zat = cast(T*) target; + if (src) + { + static if (T.sizeof > 0) + assert(target, "target must be non-null"); + + emplaceRef(*cast(Unqual!T*) zat, *cast(UA*) src); + } + } + else + { + // type T is not constructible from A + if (src) + assert(false, A.stringof); + } + return true; + } + return false; + } + + switch (selector) + { + case OpID.getTypeInfo: + *cast(TypeInfo *) parm = typeid(A); + break; + case OpID.copyOut: + auto target = cast(VariantN *) parm; + assert(target); + + static if (target.size < A.sizeof) + { + if (target.type.tsize < A.sizeof) + *cast(A**)&target.store = new A; + } + tryPutting(zis, typeid(A), cast(void*) getPtr(&target.store)) + || assert(false); + target.fptr = &handler!(A); + break; + case OpID.get: + auto t = * cast(Tuple!(TypeInfo, void*)*) parm; + return !tryPutting(zis, t[0], t[1]); + case OpID.testConversion: + return !tryPutting(null, *cast(TypeInfo*) parm, null); + case OpID.compare: + case OpID.equals: + auto rhsP = cast(VariantN *) parm; + auto rhsType = rhsP.type; + // Are we the same? + if (rhsType == typeid(A)) + { + // cool! Same type! + auto rhsPA = getPtr(&rhsP.store); + return compare(rhsPA, zis, selector); + } + else if (rhsType == typeid(void)) + { + // No support for ordering comparisons with + // uninitialized vars + return ptrdiff_t.min; + } + VariantN temp; + // Do I convert to rhs? + if (tryPutting(zis, rhsType, &temp.store)) + { + // cool, I do; temp's store contains my data in rhs's type! + // also fix up its fptr + temp.fptr = rhsP.fptr; + // now lhsWithRhsType is a full-blown VariantN of rhs's type + if (selector == OpID.compare) + return temp.opCmp(*rhsP); + else + return temp.opEquals(*rhsP) ? 0 : 1; + } + // Does rhs convert to zis? + auto t = tuple(typeid(A), &temp.store); + if (rhsP.fptr(OpID.get, &rhsP.store, &t) == 0) + { + // cool! Now temp has rhs in my type! + auto rhsPA = getPtr(&temp.store); + return compare(rhsPA, zis, selector); + } + return ptrdiff_t.min; // dunno + case OpID.toString: + auto target = cast(string*) parm; + static if (is(typeof(to!(string)(*zis)))) + { + *target = to!(string)(*zis); + break; + } + // TODO: The following test evaluates to true for shared objects. + // Use __traits for now until this is sorted out. + // else static if (is(typeof((*zis).toString))) + else static if (__traits(compiles, {(*zis).toString();})) + { + *target = (*zis).toString(); + break; + } + else + { + throw new VariantException(typeid(A), typeid(string)); + } + + case OpID.index: + auto result = cast(Variant*) parm; + static if (isArray!(A) && !is(Unqual!(typeof(A.init[0])) == void)) + { + // array type; input and output are the same VariantN + size_t index = result.convertsTo!(int) + ? result.get!(int) : result.get!(size_t); + *result = (*zis)[index]; + break; + } + else static if (isAssociativeArray!(A)) + { + *result = (*zis)[result.get!(typeof(A.init.keys[0]))]; + break; + } + else + { + throw new VariantException(typeid(A), result[0].type); + } + + case OpID.indexAssign: + // array type; result comes first, index comes second + auto args = cast(Variant*) parm; + static if (isArray!(A) && is(typeof((*zis)[0] = (*zis)[0]))) + { + size_t index = args[1].convertsTo!(int) + ? args[1].get!(int) : args[1].get!(size_t); + (*zis)[index] = args[0].get!(typeof((*zis)[0])); + break; + } + else static if (isAssociativeArray!(A)) + { + (*zis)[args[1].get!(typeof(A.init.keys[0]))] + = args[0].get!(typeof(A.init.values[0])); + break; + } + else + { + throw new VariantException(typeid(A), args[0].type); + } + + case OpID.catAssign: + static if (!is(Unqual!(typeof((*zis)[0])) == void) && is(typeof((*zis)[0])) && is(typeof((*zis) ~= *zis))) + { + // array type; parm is the element to append + auto arg = cast(Variant*) parm; + alias E = typeof((*zis)[0]); + if (arg[0].convertsTo!(E)) + { + // append one element to the array + (*zis) ~= [ arg[0].get!(E) ]; + } + else + { + // append a whole array to the array + (*zis) ~= arg[0].get!(A); + } + break; + } + else + { + throw new VariantException(typeid(A), typeid(void[])); + } + + case OpID.length: + static if (isArray!(A) || isAssociativeArray!(A)) + { + return zis.length; + } + else + { + throw new VariantException(typeid(A), typeid(void[])); + } + + case OpID.apply: + static if (!isFunctionPointer!A && !isDelegate!A) + { + import std.conv : text; + import std.exception : enforce; + enforce(0, text("Cannot apply `()' to a value of type `", + A.stringof, "'.")); + } + else + { + import std.conv : text; + import std.exception : enforce; + alias ParamTypes = Parameters!A; + auto p = cast(Variant*) parm; + auto argCount = p.get!size_t; + // To assign the tuple we need to use the unqualified version, + // otherwise we run into issues such as with const values. + // We still get the actual type from the Variant though + // to ensure that we retain const correctness. + Tuple!(staticMap!(Unqual, ParamTypes)) t; + enforce(t.length == argCount, + text("Argument count mismatch: ", + A.stringof, " expects ", t.length, + " argument(s), not ", argCount, ".")); + auto variantArgs = p[1 .. argCount + 1]; + foreach (i, T; ParamTypes) + { + t[i] = cast() variantArgs[i].get!T; + } + + auto args = cast(Tuple!(ParamTypes))t; + static if (is(ReturnType!A == void)) + { + (*zis)(args.expand); + *p = Variant.init; // void returns uninitialized Variant. + } + else + { + *p = (*zis)(args.expand); + } + } + break; + + case OpID.postblit: + static if (hasElaborateCopyConstructor!A) + { + typeid(A).postblit(zis); + } + break; + + case OpID.destruct: + static if (hasElaborateDestructor!A) + { + typeid(A).destroy(zis); + } + break; + + default: assert(false); + } + return 0; + } + + enum doUnittest = is(VariantN == Variant); + +public: + /** Constructs a $(D VariantN) value given an argument of a + * generic type. Statically rejects disallowed types. + */ + + this(T)(T value) + { + static assert(allowed!(T), "Cannot store a " ~ T.stringof + ~ " in a " ~ VariantN.stringof); + opAssign(value); + } + + /// Allows assignment from a subset algebraic type + this(T : VariantN!(tsize, Types), size_t tsize, Types...)(T value) + if (!is(T : VariantN) && Types.length > 0 && allSatisfy!(allowed, Types)) + { + opAssign(value); + } + + static if (!AllowedTypes.length || anySatisfy!(hasElaborateCopyConstructor, AllowedTypes)) + { + this(this) + { + fptr(OpID.postblit, &store, null); + } + } + + static if (!AllowedTypes.length || anySatisfy!(hasElaborateDestructor, AllowedTypes)) + { + ~this() + { + fptr(OpID.destruct, &store, null); + } + } + + /** Assigns a $(D VariantN) from a generic + * argument. Statically rejects disallowed types. */ + + VariantN opAssign(T)(T rhs) + { + //writeln(typeid(rhs)); + static assert(allowed!(T), "Cannot store a " ~ T.stringof + ~ " in a " ~ VariantN.stringof ~ ". Valid types are " + ~ AllowedTypes.stringof); + + static if (is(T : VariantN)) + { + rhs.fptr(OpID.copyOut, &rhs.store, &this); + } + else static if (is(T : const(VariantN))) + { + static assert(false, + "Assigning Variant objects from const Variant"~ + " objects is currently not supported."); + } + else + { + static if (!AllowedTypes.length || anySatisfy!(hasElaborateDestructor, AllowedTypes)) + { + // Assignment should destruct previous value + fptr(OpID.destruct, &store, null); + } + + static if (T.sizeof <= size) + { + import core.stdc.string : memcpy; + // If T is a class we're only copying the reference, so it + // should be safe to cast away shared so the memcpy will work. + // + // TODO: If a shared class has an atomic reference then using + // an atomic load may be more correct. Just make sure + // to use the fastest approach for the load op. + static if (is(T == class) && is(T == shared)) + memcpy(&store, cast(const(void*)) &rhs, rhs.sizeof); + else + memcpy(&store, &rhs, rhs.sizeof); + static if (hasElaborateCopyConstructor!T) + { + typeid(T).postblit(&store); + } + } + else + { + import core.stdc.string : memcpy; + static if (__traits(compiles, {new T(T.init);})) + { + auto p = new T(rhs); + } + else + { + auto p = new T; + *p = rhs; + } + memcpy(&store, &p, p.sizeof); + } + fptr = &handler!(T); + } + return this; + } + + // Allow assignment from another variant which is a subset of this one + VariantN opAssign(T : VariantN!(tsize, Types), size_t tsize, Types...)(T rhs) + if (!is(T : VariantN) && Types.length > 0 && allSatisfy!(allowed, Types)) + { + // discover which type rhs is actually storing + foreach (V; T.AllowedTypes) + if (rhs.type == typeid(V)) + return this = rhs.get!V; + assert(0, T.AllowedTypes.stringof); + } + + + Variant opCall(P...)(auto ref P params) + { + Variant[P.length + 1] pack; + pack[0] = P.length; + foreach (i, _; params) + { + pack[i + 1] = params[i]; + } + fptr(OpID.apply, &store, &pack); + return pack[0]; + } + + /** Returns true if and only if the $(D VariantN) object + * holds a valid value (has been initialized with, or assigned + * from, a valid value). + */ + @property bool hasValue() const pure nothrow + { + // @@@BUG@@@ in compiler, the cast shouldn't be needed + return cast(typeof(&handler!(void))) fptr != &handler!(void); + } + + /// + static if (doUnittest) + @system unittest + { + Variant a; + assert(!a.hasValue); + Variant b; + a = b; + assert(!a.hasValue); // still no value + a = 5; + assert(a.hasValue); + } + + /** + * If the $(D VariantN) object holds a value of the + * $(I exact) type $(D T), returns a pointer to that + * value. Otherwise, returns $(D null). In cases + * where $(D T) is statically disallowed, $(D + * peek) will not compile. + */ + @property inout(T)* peek(T)() inout + { + static if (!is(T == void)) + static assert(allowed!(T), "Cannot store a " ~ T.stringof + ~ " in a " ~ VariantN.stringof); + if (type != typeid(T)) + return null; + static if (T.sizeof <= size) + return cast(inout T*)&store; + else + return *cast(inout T**)&store; + } + + /// + static if (doUnittest) + @system unittest + { + Variant a = 5; + auto b = a.peek!(int); + assert(b !is null); + *b = 6; + assert(a == 6); + } + + /** + * Returns the $(D typeid) of the currently held value. + */ + + @property TypeInfo type() const nothrow @trusted + { + scope(failure) assert(0); + + TypeInfo result; + fptr(OpID.getTypeInfo, null, &result); + return result; + } + + /** + * Returns $(D true) if and only if the $(D VariantN) + * object holds an object implicitly convertible to type `T`. + * Implicit convertibility is defined as per + * $(REF_ALTTEXT ImplicitConversionTargets, ImplicitConversionTargets, std,traits). + */ + + @property bool convertsTo(T)() const + { + TypeInfo info = typeid(T); + return fptr(OpID.testConversion, null, &info) == 0; + } + + /** + Returns the value stored in the `VariantN` object, either by specifying the + needed type or the index in the list of allowed types. The latter overload + only applies to bounded variants (e.g. $(LREF Algebraic)). + + Params: + T = The requested type. The currently stored value must implicitly convert + to the requested type, in fact `DecayStaticToDynamicArray!T`. If an + implicit conversion is not possible, throws a `VariantException`. + index = The index of the type among `AllowedTypesParam`, zero-based. + */ + @property inout(T) get(T)() inout + { + inout(T) result = void; + static if (is(T == shared)) + alias R = shared Unqual!T; + else + alias R = Unqual!T; + auto buf = tuple(typeid(T), cast(R*)&result); + + if (fptr(OpID.get, cast(ubyte[size]*) &store, &buf)) + { + throw new VariantException(type, typeid(T)); + } + return result; + } + + /// Ditto + @property auto get(uint index)() inout + if (index < AllowedTypes.length) + { + foreach (i, T; AllowedTypes) + { + static if (index == i) return get!T; + } + assert(0); + } + + /** + * Returns the value stored in the $(D VariantN) object, + * explicitly converted (coerced) to the requested type $(D + * T). If $(D T) is a string type, the value is formatted as + * a string. If the $(D VariantN) object is a string, a + * parse of the string to type $(D T) is attempted. If a + * conversion is not possible, throws a $(D + * VariantException). + */ + + @property T coerce(T)() + { + import std.conv : to, text; + static if (isNumeric!T || isBoolean!T) + { + if (convertsTo!real) + { + // maybe optimize this fella; handle ints separately + return to!T(get!real); + } + else if (convertsTo!(const(char)[])) + { + return to!T(get!(const(char)[])); + } + // I'm not sure why this doesn't convert to const(char), + // but apparently it doesn't (probably a deeper bug). + // + // Until that is fixed, this quick addition keeps a common + // function working. "10".coerce!int ought to work. + else if (convertsTo!(immutable(char)[])) + { + return to!T(get!(immutable(char)[])); + } + else + { + import std.exception : enforce; + enforce(false, text("Type ", type, " does not convert to ", + typeid(T))); + assert(0); + } + } + else static if (is(T : Object)) + { + return to!(T)(get!(Object)); + } + else static if (isSomeString!(T)) + { + return to!(T)(toString()); + } + else + { + // Fix for bug 1649 + static assert(false, "unsupported type for coercion"); + } + } + + /** + * Formats the stored value as a string. + */ + + string toString() + { + string result; + fptr(OpID.toString, &store, &result) == 0 || assert(false); + return result; + } + + /** + * Comparison for equality used by the "==" and "!=" operators. + */ + + // returns 1 if the two are equal + bool opEquals(T)(auto ref T rhs) const + if (allowed!T || is(Unqual!T == VariantN)) + { + static if (is(Unqual!T == VariantN)) + alias temp = rhs; + else + auto temp = VariantN(rhs); + return !fptr(OpID.equals, cast(ubyte[size]*) &store, + cast(void*) &temp); + } + + // workaround for bug 10567 fix + int opCmp(ref const VariantN rhs) const + { + return (cast() this).opCmp!(VariantN)(cast() rhs); + } + + /** + * Ordering comparison used by the "<", "<=", ">", and ">=" + * operators. In case comparison is not sensible between the held + * value and $(D rhs), an exception is thrown. + */ + + int opCmp(T)(T rhs) + if (allowed!T) // includes T == VariantN + { + static if (is(T == VariantN)) + alias temp = rhs; + else + auto temp = VariantN(rhs); + auto result = fptr(OpID.compare, &store, &temp); + if (result == ptrdiff_t.min) + { + throw new VariantException(type, temp.type); + } + + assert(result >= -1 && result <= 1); // Should be true for opCmp. + return cast(int) result; + } + + /** + * Computes the hash of the held value. + */ + + size_t toHash() const nothrow @safe + { + return type.getHash(&store); + } + + private VariantN opArithmetic(T, string op)(T other) + { + static if (isInstanceOf!(.VariantN, T)) + { + string tryUseType(string tp) + { + import std.format : format; + return q{ + static if (allowed!%1$s && T.allowed!%1$s) + if (convertsTo!%1$s && other.convertsTo!%1$s) + return VariantN(get!%1$s %2$s other.get!%1$s); + }.format(tp, op); + } + + mixin(tryUseType("uint")); + mixin(tryUseType("int")); + mixin(tryUseType("ulong")); + mixin(tryUseType("long")); + mixin(tryUseType("float")); + mixin(tryUseType("double")); + mixin(tryUseType("real")); + } + else + { + static if (allowed!T) + if (auto pv = peek!T) return VariantN(mixin("*pv " ~ op ~ " other")); + static if (allowed!uint && is(typeof(T.max) : uint) && isUnsigned!T) + if (convertsTo!uint) return VariantN(mixin("get!(uint) " ~ op ~ " other")); + static if (allowed!int && is(typeof(T.max) : int) && !isUnsigned!T) + if (convertsTo!int) return VariantN(mixin("get!(int) " ~ op ~ " other")); + static if (allowed!ulong && is(typeof(T.max) : ulong) && isUnsigned!T) + if (convertsTo!ulong) return VariantN(mixin("get!(ulong) " ~ op ~ " other")); + static if (allowed!long && is(typeof(T.max) : long) && !isUnsigned!T) + if (convertsTo!long) return VariantN(mixin("get!(long) " ~ op ~ " other")); + static if (allowed!float && is(T : float)) + if (convertsTo!float) return VariantN(mixin("get!(float) " ~ op ~ " other")); + static if (allowed!double && is(T : double)) + if (convertsTo!double) return VariantN(mixin("get!(double) " ~ op ~ " other")); + static if (allowed!real && is (T : real)) + if (convertsTo!real) return VariantN(mixin("get!(real) " ~ op ~ " other")); + } + + throw new VariantException("No possible match found for VariantN "~op~" "~T.stringof); + } + + private VariantN opLogic(T, string op)(T other) + { + VariantN result; + static if (is(T == VariantN)) + { + if (convertsTo!(uint) && other.convertsTo!(uint)) + result = mixin("get!(uint) " ~ op ~ " other.get!(uint)"); + else if (convertsTo!(int) && other.convertsTo!(int)) + result = mixin("get!(int) " ~ op ~ " other.get!(int)"); + else if (convertsTo!(ulong) && other.convertsTo!(ulong)) + result = mixin("get!(ulong) " ~ op ~ " other.get!(ulong)"); + else + result = mixin("get!(long) " ~ op ~ " other.get!(long)"); + } + else + { + if (is(typeof(T.max) : uint) && T.min == 0 && convertsTo!(uint)) + result = mixin("get!(uint) " ~ op ~ " other"); + else if (is(typeof(T.max) : int) && T.min < 0 && convertsTo!(int)) + result = mixin("get!(int) " ~ op ~ " other"); + else if (is(typeof(T.max) : ulong) && T.min == 0 + && convertsTo!(ulong)) + result = mixin("get!(ulong) " ~ op ~ " other"); + else + result = mixin("get!(long) " ~ op ~ " other"); + } + return result; + } + + /** + * Arithmetic between $(D VariantN) objects and numeric + * values. All arithmetic operations return a $(D VariantN) + * object typed depending on the types of both values + * involved. The conversion rules mimic D's built-in rules for + * arithmetic conversions. + */ + + // Adapted from http://www.prowiki.org/wiki4d/wiki.cgi?DanielKeep/Variant + // arithmetic + VariantN opAdd(T)(T rhs) { return opArithmetic!(T, "+")(rhs); } + ///ditto + VariantN opSub(T)(T rhs) { return opArithmetic!(T, "-")(rhs); } + + // Commenteed all _r versions for now because of ambiguities + // arising when two Variants are used + + // ///ditto + // VariantN opSub_r(T)(T lhs) + // { + // return VariantN(lhs).opArithmetic!(VariantN, "-")(this); + // } + ///ditto + VariantN opMul(T)(T rhs) { return opArithmetic!(T, "*")(rhs); } + ///ditto + VariantN opDiv(T)(T rhs) { return opArithmetic!(T, "/")(rhs); } + // ///ditto + // VariantN opDiv_r(T)(T lhs) + // { + // return VariantN(lhs).opArithmetic!(VariantN, "/")(this); + // } + ///ditto + VariantN opMod(T)(T rhs) { return opArithmetic!(T, "%")(rhs); } + // ///ditto + // VariantN opMod_r(T)(T lhs) + // { + // return VariantN(lhs).opArithmetic!(VariantN, "%")(this); + // } + ///ditto + VariantN opAnd(T)(T rhs) { return opLogic!(T, "&")(rhs); } + ///ditto + VariantN opOr(T)(T rhs) { return opLogic!(T, "|")(rhs); } + ///ditto + VariantN opXor(T)(T rhs) { return opLogic!(T, "^")(rhs); } + ///ditto + VariantN opShl(T)(T rhs) { return opLogic!(T, "<<")(rhs); } + // ///ditto + // VariantN opShl_r(T)(T lhs) + // { + // return VariantN(lhs).opLogic!(VariantN, "<<")(this); + // } + ///ditto + VariantN opShr(T)(T rhs) { return opLogic!(T, ">>")(rhs); } + // ///ditto + // VariantN opShr_r(T)(T lhs) + // { + // return VariantN(lhs).opLogic!(VariantN, ">>")(this); + // } + ///ditto + VariantN opUShr(T)(T rhs) { return opLogic!(T, ">>>")(rhs); } + // ///ditto + // VariantN opUShr_r(T)(T lhs) + // { + // return VariantN(lhs).opLogic!(VariantN, ">>>")(this); + // } + ///ditto + VariantN opCat(T)(T rhs) + { + auto temp = this; + temp ~= rhs; + return temp; + } + // ///ditto + // VariantN opCat_r(T)(T rhs) + // { + // VariantN temp = rhs; + // temp ~= this; + // return temp; + // } + + ///ditto + VariantN opAddAssign(T)(T rhs) { return this = this + rhs; } + ///ditto + VariantN opSubAssign(T)(T rhs) { return this = this - rhs; } + ///ditto + VariantN opMulAssign(T)(T rhs) { return this = this * rhs; } + ///ditto + VariantN opDivAssign(T)(T rhs) { return this = this / rhs; } + ///ditto + VariantN opModAssign(T)(T rhs) { return this = this % rhs; } + ///ditto + VariantN opAndAssign(T)(T rhs) { return this = this & rhs; } + ///ditto + VariantN opOrAssign(T)(T rhs) { return this = this | rhs; } + ///ditto + VariantN opXorAssign(T)(T rhs) { return this = this ^ rhs; } + ///ditto + VariantN opShlAssign(T)(T rhs) { return this = this << rhs; } + ///ditto + VariantN opShrAssign(T)(T rhs) { return this = this >> rhs; } + ///ditto + VariantN opUShrAssign(T)(T rhs) { return this = this >>> rhs; } + ///ditto + VariantN opCatAssign(T)(T rhs) + { + auto toAppend = Variant(rhs); + fptr(OpID.catAssign, &store, &toAppend) == 0 || assert(false); + return this; + } + + /** + * Array and associative array operations. If a $(D + * VariantN) contains an (associative) array, it can be indexed + * into. Otherwise, an exception is thrown. + */ + inout(Variant) opIndex(K)(K i) inout + { + auto result = Variant(i); + fptr(OpID.index, cast(ubyte[size]*) &store, &result) == 0 || assert(false); + return result; + } + + /// + static if (doUnittest) + @system unittest + { + Variant a = new int[10]; + a[5] = 42; + assert(a[5] == 42); + a[5] += 8; + assert(a[5] == 50); + + int[int] hash = [ 42:24 ]; + a = hash; + assert(a[42] == 24); + a[42] /= 2; + assert(a[42] == 12); + } + + /// ditto + Variant opIndexAssign(T, N)(T value, N i) + { + static if (AllowedTypes.length && !isInstanceOf!(.VariantN, T)) + { + enum canAssign(U) = __traits(compiles, (U u){ u[i] = value; }); + static assert(anySatisfy!(canAssign, AllowedTypes), + "Cannot assign " ~ T.stringof ~ " to " ~ VariantN.stringof ~ + " indexed with " ~ N.stringof); + } + Variant[2] args = [ Variant(value), Variant(i) ]; + fptr(OpID.indexAssign, &store, &args) == 0 || assert(false); + return args[0]; + } + + /// ditto + Variant opIndexOpAssign(string op, T, N)(T value, N i) + { + return opIndexAssign(mixin(`opIndex(i)` ~ op ~ `value`), i); + } + + /** If the $(D VariantN) contains an (associative) array, + * returns the _length of that array. Otherwise, throws an + * exception. + */ + @property size_t length() + { + return cast(size_t) fptr(OpID.length, &store, null); + } + + /** + If the $(D VariantN) contains an array, applies $(D dg) to each + element of the array in turn. Otherwise, throws an exception. + */ + int opApply(Delegate)(scope Delegate dg) if (is(Delegate == delegate)) + { + alias A = Parameters!(Delegate)[0]; + if (type == typeid(A[])) + { + auto arr = get!(A[]); + foreach (ref e; arr) + { + if (dg(e)) return 1; + } + } + else static if (is(A == VariantN)) + { + foreach (i; 0 .. length) + { + // @@@TODO@@@: find a better way to not confuse + // clients who think they change values stored in the + // Variant when in fact they are only changing tmp. + auto tmp = this[i]; + debug scope(exit) assert(tmp == this[i]); + if (dg(tmp)) return 1; + } + } + else + { + import std.conv : text; + import std.exception : enforce; + enforce(false, text("Variant type ", type, + " not iterable with values of type ", + A.stringof)); + } + return 0; + } +} + +@system unittest +{ + import std.conv : to; + Variant v; + int foo() { return 42; } + v = &foo; + assert(v() == 42); + + static int bar(string s) { return to!int(s); } + v = &bar; + assert(v("43") == 43); +} + +@system unittest +{ + int[int] hash = [ 42:24 ]; + Variant v = hash; + assert(v[42] == 24); + v[42] = 5; + assert(v[42] == 5); +} + +// opIndex with static arrays, issue 12771 +@system unittest +{ + int[4] elements = [0, 1, 2, 3]; + Variant v = elements; + assert(v == elements); + assert(v[2] == 2); + assert(v[3] == 3); + v[2] = 6; + assert(v[2] == 6); + assert(v != elements); +} + +@system unittest +{ + import std.exception : assertThrown; + Algebraic!(int[]) v = [2, 2]; + + assert(v == [2, 2]); + v[0] = 1; + assert(v[0] == 1); + assert(v != [2, 2]); + + // opIndexAssign from Variant + v[1] = v[0]; + assert(v[1] == 1); + + static assert(!__traits(compiles, (v[1] = null))); + assertThrown!VariantException(v[1] = Variant(null)); +} + +//Issue# 8195 +@system unittest +{ + struct S + { + int a; + long b; + string c; + real d = 0.0; + bool e; + } + + static assert(S.sizeof >= Variant.sizeof); + alias Types = AliasSeq!(string, int, S); + alias MyVariant = VariantN!(maxSize!Types, Types); + + auto v = MyVariant(S.init); + assert(v == S.init); +} + +// Issue #10961 +@system unittest +{ + // Primarily test that we can assign a void[] to a Variant. + void[] elements = cast(void[])[1, 2, 3]; + Variant v = elements; + void[] returned = v.get!(void[]); + assert(returned == elements); +} + +// Issue #13352 +@system unittest +{ + alias TP = Algebraic!(long); + auto a = TP(1L); + auto b = TP(2L); + assert(!TP.allowed!ulong); + assert(a + b == 3L); + assert(a + 2 == 3L); + assert(1 + b == 3L); + + alias TP2 = Algebraic!(long, string); + auto c = TP2(3L); + assert(a + c == 4L); +} + +// Issue #13354 +@system unittest +{ + alias A = Algebraic!(string[]); + A a = ["a", "b"]; + assert(a[0] == "a"); + assert(a[1] == "b"); + a[1] = "c"; + assert(a[1] == "c"); + + alias AA = Algebraic!(int[string]); + AA aa = ["a": 1, "b": 2]; + assert(aa["a"] == 1); + assert(aa["b"] == 2); + aa["b"] = 3; + assert(aa["b"] == 3); +} + +// Issue #14198 +@system unittest +{ + Variant a = true; + assert(a.type == typeid(bool)); +} + +// Issue #14233 +@system unittest +{ + alias Atom = Algebraic!(string, This[]); + + Atom[] values = []; + auto a = Atom(values); +} + +pure nothrow @nogc +@system unittest +{ + Algebraic!(int, double) a; + a = 100; + a = 1.0; +} + +// Issue 14457 +@system unittest +{ + alias A = Algebraic!(int, float, double); + alias B = Algebraic!(int, float); + + A a = 1; + B b = 6f; + a = b; + + assert(a.type == typeid(float)); + assert(a.get!float == 6f); +} + +// Issue 14585 +@system unittest +{ + static struct S + { + int x = 42; + ~this() {assert(x == 42);} + } + Variant(S()).get!S; +} + +// Issue 14586 +@system unittest +{ + const Variant v = new immutable Object; + v.get!(immutable Object); +} + +@system unittest +{ + static struct S + { + T opCast(T)() {assert(false);} + } + Variant v = S(); + v.get!S; +} + + +/** +_Algebraic data type restricted to a closed set of possible +types. It's an alias for $(LREF VariantN) with an +appropriately-constructed maximum size. `Algebraic` is +useful when it is desirable to restrict what a discriminated type +could hold to the end of defining simpler and more efficient +manipulation. + +*/ +template Algebraic(T...) +{ + alias Algebraic = VariantN!(maxSize!T, T); +} + +/// +@system unittest +{ + auto v = Algebraic!(int, double, string)(5); + assert(v.peek!(int)); + v = 3.14; + assert(v.peek!(double)); + // auto x = v.peek!(long); // won't compile, type long not allowed + // v = '1'; // won't compile, type char not allowed +} + +/** +$(H4 Self-Referential Types) + +A useful and popular use of algebraic data structures is for defining $(LUCKY +self-referential data structures), i.e. structures that embed references to +values of their own type within. + +This is achieved with `Algebraic` by using `This` as a placeholder whenever a +reference to the type being defined is needed. The `Algebraic` instantiation +will perform $(LINK2 https://en.wikipedia.org/wiki/Name_resolution_(programming_languages)#Alpha_renaming_to_make_name_resolution_trivial, +alpha renaming) on its constituent types, replacing `This` +with the self-referenced type. The structure of the type involving `This` may +be arbitrarily complex. +*/ +@system unittest +{ + import std.typecons : Tuple, tuple; + + // A tree is either a leaf or a branch of two other trees + alias Tree(Leaf) = Algebraic!(Leaf, Tuple!(This*, This*)); + Tree!int tree = tuple(new Tree!int(42), new Tree!int(43)); + Tree!int* right = tree.get!1[1]; + assert(*right == 43); + + // An object is a double, a string, or a hash of objects + alias Obj = Algebraic!(double, string, This[string]); + Obj obj = "hello"; + assert(obj.get!1 == "hello"); + obj = 42.0; + assert(obj.get!0 == 42); + obj = ["customer": Obj("John"), "paid": Obj(23.95)]; + assert(obj.get!2["customer"] == "John"); +} + +/** +Alias for $(LREF VariantN) instantiated with the largest size of `creal`, +`char[]`, and `void delegate()`. This ensures that `Variant` is large enough +to hold all of D's predefined types unboxed, including all numeric types, +pointers, delegates, and class references. You may want to use +$(D VariantN) directly with a different maximum size either for +storing larger types unboxed, or for saving memory. + */ +alias Variant = VariantN!(maxSize!(creal, char[], void delegate())); + +/** + * Returns an array of variants constructed from $(D args). + * + * This is by design. During construction the $(D Variant) needs + * static type information about the type being held, so as to store a + * pointer to function for fast retrieval. + */ +Variant[] variantArray(T...)(T args) +{ + Variant[] result; + foreach (arg; args) + { + result ~= Variant(arg); + } + return result; +} + +/// +@system unittest +{ + auto a = variantArray(1, 3.14, "Hi!"); + assert(a[1] == 3.14); + auto b = Variant(a); // variant array as variant + assert(b[1] == 3.14); +} + +/** + * Thrown in three cases: + * + * $(OL $(LI An uninitialized `Variant` is used in any way except + * assignment and $(D hasValue);) $(LI A $(D get) or + * $(D coerce) is attempted with an incompatible target type;) + * $(LI A comparison between $(D Variant) objects of + * incompatible types is attempted.)) + * + */ + +// @@@ BUG IN COMPILER. THE 'STATIC' BELOW SHOULD NOT COMPILE +static class VariantException : Exception +{ + /// The source type in the conversion or comparison + TypeInfo source; + /// The target type in the conversion or comparison + TypeInfo target; + this(string s) + { + super(s); + } + this(TypeInfo source, TypeInfo target) + { + super("Variant: attempting to use incompatible types " + ~ source.toString() + ~ " and " ~ target.toString()); + this.source = source; + this.target = target; + } +} + +@system unittest +{ + alias W1 = This2Variant!(char, int, This[int]); + alias W2 = AliasSeq!(int, char[int]); + static assert(is(W1 == W2)); + + alias var_t = Algebraic!(void, string); + var_t foo = "quux"; +} + +@system unittest +{ + alias A = Algebraic!(real, This[], This[int], This[This]); + A v1, v2, v3; + v2 = 5.0L; + v3 = 42.0L; + //v1 = [ v2 ][]; + auto v = v1.peek!(A[]); + //writeln(v[0]); + v1 = [ 9 : v3 ]; + //writeln(v1); + v1 = [ v3 : v3 ]; + //writeln(v1); +} + +@system unittest +{ + import std.conv : ConvException; + import std.exception : assertThrown, collectException; + // try it with an oddly small size + VariantN!(1) test; + assert(test.size > 1); + + // variantArray tests + auto heterogeneous = variantArray(1, 4.5, "hi"); + assert(heterogeneous.length == 3); + auto variantArrayAsVariant = Variant(heterogeneous); + assert(variantArrayAsVariant[0] == 1); + assert(variantArrayAsVariant.length == 3); + + // array tests + auto arr = Variant([1.2].dup); + auto e = arr[0]; + assert(e == 1.2); + arr[0] = 2.0; + assert(arr[0] == 2); + arr ~= 4.5; + assert(arr[1] == 4.5); + + // general tests + Variant a; + auto b = Variant(5); + assert(!b.peek!(real) && b.peek!(int)); + // assign + a = *b.peek!(int); + // comparison + assert(a == b, a.type.toString() ~ " " ~ b.type.toString()); + auto c = Variant("this is a string"); + assert(a != c); + // comparison via implicit conversions + a = 42; b = 42.0; assert(a == b); + + // try failing conversions + bool failed = false; + try + { + auto d = c.get!(int); + } + catch (Exception e) + { + //writeln(stderr, e.toString); + failed = true; + } + assert(failed); // :o) + + // toString tests + a = Variant(42); assert(a.toString() == "42"); + a = Variant(42.22); assert(a.toString() == "42.22"); + + // coerce tests + a = Variant(42.22); assert(a.coerce!(int) == 42); + a = cast(short) 5; assert(a.coerce!(double) == 5); + a = Variant("10"); assert(a.coerce!int == 10); + + a = Variant(1); + assert(a.coerce!bool); + a = Variant(0); + assert(!a.coerce!bool); + + a = Variant(1.0); + assert(a.coerce!bool); + a = Variant(0.0); + assert(!a.coerce!bool); + a = Variant(float.init); + assertThrown!ConvException(a.coerce!bool); + + a = Variant("true"); + assert(a.coerce!bool); + a = Variant("false"); + assert(!a.coerce!bool); + a = Variant(""); + assertThrown!ConvException(a.coerce!bool); + + // Object tests + class B1 {} + class B2 : B1 {} + a = new B2; + assert(a.coerce!(B1) !is null); + a = new B1; + assert(collectException(a.coerce!(B2) is null)); + a = cast(Object) new B2; // lose static type info; should still work + assert(a.coerce!(B2) !is null); + +// struct Big { int a[45]; } +// a = Big.init; + + // hash + assert(a.toHash() != 0); +} + +// tests adapted from +// http://www.dsource.org/projects/tango/browser/trunk/tango/core/Variant.d?rev=2601 +@system unittest +{ + Variant v; + + assert(!v.hasValue); + v = 42; + assert( v.peek!(int) ); + assert( v.convertsTo!(long) ); + assert( v.get!(int) == 42 ); + assert( v.get!(long) == 42L ); + assert( v.get!(ulong) == 42uL ); + + v = "Hello, World!"; + assert( v.peek!(string) ); + + assert( v.get!(string) == "Hello, World!" ); + assert(!is(char[] : wchar[])); + assert( !v.convertsTo!(wchar[]) ); + assert( v.get!(string) == "Hello, World!" ); + + // Literal arrays are dynamically-typed + v = cast(int[4]) [1,2,3,4]; + assert( v.peek!(int[4]) ); + assert( v.get!(int[4]) == [1,2,3,4] ); + + { + v = [1,2,3,4,5]; + assert( v.peek!(int[]) ); + assert( v.get!(int[]) == [1,2,3,4,5] ); + } + + v = 3.1413; + assert( v.peek!(double) ); + assert( v.convertsTo!(real) ); + //@@@ BUG IN COMPILER: DOUBLE SHOULD NOT IMPLICITLY CONVERT TO FLOAT + assert( !v.convertsTo!(float) ); + assert( *v.peek!(double) == 3.1413 ); + + auto u = Variant(v); + assert( u.peek!(double) ); + assert( *u.peek!(double) == 3.1413 ); + + // operators + v = 38; + assert( v + 4 == 42 ); + assert( 4 + v == 42 ); + assert( v - 4 == 34 ); + assert( Variant(4) - v == -34 ); + assert( v * 2 == 76 ); + assert( 2 * v == 76 ); + assert( v / 2 == 19 ); + assert( Variant(2) / v == 0 ); + assert( v % 2 == 0 ); + assert( Variant(2) % v == 2 ); + assert( (v & 6) == 6 ); + assert( (6 & v) == 6 ); + assert( (v | 9) == 47 ); + assert( (9 | v) == 47 ); + assert( (v ^ 5) == 35 ); + assert( (5 ^ v) == 35 ); + assert( v << 1 == 76 ); + assert( Variant(1) << Variant(2) == 4 ); + assert( v >> 1 == 19 ); + assert( Variant(4) >> Variant(2) == 1 ); + assert( Variant("abc") ~ "def" == "abcdef" ); + assert( Variant("abc") ~ Variant("def") == "abcdef" ); + + v = 38; + v += 4; + assert( v == 42 ); + v = 38; v -= 4; assert( v == 34 ); + v = 38; v *= 2; assert( v == 76 ); + v = 38; v /= 2; assert( v == 19 ); + v = 38; v %= 2; assert( v == 0 ); + v = 38; v &= 6; assert( v == 6 ); + v = 38; v |= 9; assert( v == 47 ); + v = 38; v ^= 5; assert( v == 35 ); + v = 38; v <<= 1; assert( v == 76 ); + v = 38; v >>= 1; assert( v == 19 ); + v = 38; v += 1; assert( v < 40 ); + + v = "abc"; + v ~= "def"; + assert( v == "abcdef", *v.peek!(char[]) ); + assert( Variant(0) < Variant(42) ); + assert( Variant(42) > Variant(0) ); + assert( Variant(42) > Variant(0.1) ); + assert( Variant(42.1) > Variant(1) ); + assert( Variant(21) == Variant(21) ); + assert( Variant(0) != Variant(42) ); + assert( Variant("bar") == Variant("bar") ); + assert( Variant("foo") != Variant("bar") ); + + { + auto v1 = Variant(42); + auto v2 = Variant("foo"); + auto v3 = Variant(1+2.0i); + + int[Variant] hash; + hash[v1] = 0; + hash[v2] = 1; + hash[v3] = 2; + + assert( hash[v1] == 0 ); + assert( hash[v2] == 1 ); + assert( hash[v3] == 2 ); + } + + { + int[char[]] hash; + hash["a"] = 1; + hash["b"] = 2; + hash["c"] = 3; + Variant vhash = hash; + + assert( vhash.get!(int[char[]])["a"] == 1 ); + assert( vhash.get!(int[char[]])["b"] == 2 ); + assert( vhash.get!(int[char[]])["c"] == 3 ); + } +} + +@system unittest +{ + // check comparisons incompatible with AllowedTypes + Algebraic!int v = 2; + + assert(v == 2); + assert(v < 3); + static assert(!__traits(compiles, {v == long.max;})); + static assert(!__traits(compiles, {v == null;})); + static assert(!__traits(compiles, {v < long.max;})); + static assert(!__traits(compiles, {v > null;})); +} + +@system unittest +{ + // bug 1558 + Variant va=1; + Variant vb=-2; + assert((va+vb).get!(int) == -1); + assert((va-vb).get!(int) == 3); +} + +@system unittest +{ + Variant a; + a=5; + Variant b; + b=a; + Variant[] c; + c = variantArray(1, 2, 3.0, "hello", 4); + assert(c[3] == "hello"); +} + +@system unittest +{ + Variant v = 5; + assert(!__traits(compiles, v.coerce!(bool delegate()))); +} + + +@system unittest +{ + struct Huge { + real a, b, c, d, e, f, g; + } + + Huge huge; + huge.e = 42; + Variant v; + v = huge; // Compile time error. + assert(v.get!(Huge).e == 42); +} + +@system unittest +{ + const x = Variant(42); + auto y1 = x.get!(const int); + // @@@BUG@@@ + //auto y2 = x.get!(immutable int)(); +} + +// test iteration +@system unittest +{ + auto v = Variant([ 1, 2, 3, 4 ][]); + auto j = 0; + foreach (int i; v) + { + assert(i == ++j); + } + assert(j == 4); +} + +// test convertibility +@system unittest +{ + auto v = Variant("abc".dup); + assert(v.convertsTo!(char[])); +} + +// http://d.puremagic.com/issues/show_bug.cgi?id=5424 +@system unittest +{ + interface A { + void func1(); + } + static class AC: A { + void func1() { + } + } + + A a = new AC(); + a.func1(); + Variant b = Variant(a); +} + +@system unittest +{ + // bug 7070 + Variant v; + v = null; +} + +// Class and interface opEquals, issue 12157 +@system unittest +{ + class Foo { } + + class DerivedFoo : Foo { } + + Foo f1 = new Foo(); + Foo f2 = new DerivedFoo(); + + Variant v1 = f1, v2 = f2; + assert(v1 == f1); + assert(v1 != new Foo()); + assert(v1 != f2); + assert(v2 != v1); + assert(v2 == f2); +} + +// Const parameters with opCall, issue 11361. +@system unittest +{ + static string t1(string c) { + return c ~ "a"; + } + + static const(char)[] t2(const(char)[] p) { + return p ~ "b"; + } + + static char[] t3(int p) { + import std.conv : text; + return p.text.dup; + } + + Variant v1 = &t1; + Variant v2 = &t2; + Variant v3 = &t3; + + assert(v1("abc") == "abca"); + assert(v1("abc").type == typeid(string)); + assert(v2("abc") == "abcb"); + + assert(v2(cast(char[])("abc".dup)) == "abcb"); + assert(v2("abc").type == typeid(const(char)[])); + + assert(v3(4) == ['4']); + assert(v3(4).type == typeid(char[])); +} + +// issue 12071 +@system unittest +{ + static struct Structure { int data; } + alias VariantTest = Algebraic!(Structure delegate() pure nothrow @nogc @safe); + + bool called = false; + Structure example() pure nothrow @nogc @safe + { + called = true; + return Structure.init; + } + auto m = VariantTest(&example); + m(); + assert(called); +} + +// Ordering comparisons of incompatible types, e.g. issue 7990. +@system unittest +{ + import std.exception : assertThrown; + assertThrown!VariantException(Variant(3) < "a"); + assertThrown!VariantException("a" < Variant(3)); + assertThrown!VariantException(Variant(3) < Variant("a")); + + assertThrown!VariantException(Variant.init < Variant(3)); + assertThrown!VariantException(Variant(3) < Variant.init); +} + +// Handling of unordered types, e.g. issue 9043. +@system unittest +{ + import std.exception : assertThrown; + static struct A { int a; } + + assert(Variant(A(3)) != A(4)); + + assertThrown!VariantException(Variant(A(3)) < A(4)); + assertThrown!VariantException(A(3) < Variant(A(4))); + assertThrown!VariantException(Variant(A(3)) < Variant(A(4))); +} + +// Handling of empty types and arrays, e.g. issue 10958 +@system unittest +{ + class EmptyClass { } + struct EmptyStruct { } + alias EmptyArray = void[0]; + alias Alg = Algebraic!(EmptyClass, EmptyStruct, EmptyArray); + + Variant testEmpty(T)() + { + T inst; + Variant v = inst; + assert(v.get!T == inst); + assert(v.peek!T !is null); + assert(*v.peek!T == inst); + Alg alg = inst; + assert(alg.get!T == inst); + return v; + } + + testEmpty!EmptyClass(); + testEmpty!EmptyStruct(); + testEmpty!EmptyArray(); + + // EmptyClass/EmptyStruct sizeof is 1, so we have this to test just size 0. + EmptyArray arr = EmptyArray.init; + Algebraic!(EmptyArray) a = arr; + assert(a.length == 0); + assert(a.get!EmptyArray == arr); +} + +// Handling of void function pointers / delegates, e.g. issue 11360 +@system unittest +{ + static void t1() { } + Variant v = &t1; + assert(v() == Variant.init); + + static int t2() { return 3; } + Variant v2 = &t2; + assert(v2() == 3); +} + +// Using peek for large structs, issue 8580 +@system unittest +{ + struct TestStruct(bool pad) + { + int val1; + static if (pad) + ubyte[Variant.size] padding; + int val2; + } + + void testPeekWith(T)() + { + T inst; + inst.val1 = 3; + inst.val2 = 4; + Variant v = inst; + T* original = v.peek!T; + assert(original.val1 == 3); + assert(original.val2 == 4); + original.val1 = 6; + original.val2 = 8; + T modified = v.get!T; + assert(modified.val1 == 6); + assert(modified.val2 == 8); + } + + testPeekWith!(TestStruct!false)(); + testPeekWith!(TestStruct!true)(); +} + +/** + * Applies a delegate or function to the given $(LREF Algebraic) depending on the held type, + * ensuring that all types are handled by the visiting functions. + * + * The delegate or function having the currently held value as parameter is called + * with $(D variant)'s current value. Visiting handlers are passed + * in the template parameter list. + * It is statically ensured that all held types of + * $(D variant) are handled across all handlers. + * $(D visit) allows delegates and static functions to be passed + * as parameters. + * + * If a function with an untyped parameter is specified, this function is called + * when the variant contains a type that does not match any other function. + * This can be used to apply the same function across multiple possible types. + * Exactly one generic function is allowed. + * + * If a function without parameters is specified, this function is called + * when `variant` doesn't hold a value. Exactly one parameter-less function + * is allowed. + * + * Duplicate overloads matching the same type in one of the visitors are disallowed. + * + * Returns: The return type of visit is deduced from the visiting functions and must be + * the same across all overloads. + * Throws: $(LREF VariantException) if `variant` doesn't hold a value and no + * parameter-less fallback function is specified. + */ +template visit(Handlers...) +if (Handlers.length > 0) +{ + /// + auto visit(VariantType)(VariantType variant) + if (isAlgebraic!VariantType) + { + return visitImpl!(true, VariantType, Handlers)(variant); + } +} + +/// +@system unittest +{ + Algebraic!(int, string) variant; + + variant = 10; + assert(variant.visit!((string s) => cast(int) s.length, + (int i) => i)() + == 10); + variant = "string"; + assert(variant.visit!((int i) => i, + (string s) => cast(int) s.length)() + == 6); + + // Error function usage + Algebraic!(int, string) emptyVar; + auto rslt = emptyVar.visit!((string s) => cast(int) s.length, + (int i) => i, + () => -1)(); + assert(rslt == -1); + + // Generic function usage + Algebraic!(int, float, real) number = 2; + assert(number.visit!(x => x += 1) == 3); + + // Generic function for int/float with separate behavior for string + Algebraic!(int, float, string) something = 2; + assert(something.visit!((string s) => s.length, x => x) == 2); // generic + something = "asdf"; + assert(something.visit!((string s) => s.length, x => x) == 4); // string + + // Generic handler and empty handler + Algebraic!(int, float, real) empty2; + assert(empty2.visit!(x => x + 1, () => -1) == -1); +} + +@system unittest +{ + Algebraic!(size_t, string) variant; + + // not all handled check + static assert(!__traits(compiles, variant.visit!((size_t i){ })() )); + + variant = cast(size_t) 10; + auto which = 0; + variant.visit!( (string s) => which = 1, + (size_t i) => which = 0 + )(); + + // integer overload was called + assert(which == 0); + + // mustn't compile as generic Variant not supported + Variant v; + static assert(!__traits(compiles, v.visit!((string s) => which = 1, + (size_t i) => which = 0 + )() + )); + + static size_t func(string s) { + return s.length; + } + + variant = "test"; + assert( 4 == variant.visit!(func, + (size_t i) => i + )()); + + Algebraic!(int, float, string) variant2 = 5.0f; + // Shouldn' t compile as float not handled by visitor. + static assert(!__traits(compiles, variant2.visit!( + (int _) {}, + (string _) {})())); + + Algebraic!(size_t, string, float) variant3; + variant3 = 10.0f; + auto floatVisited = false; + + assert(variant3.visit!( + (float f) { floatVisited = true; return cast(size_t) f; }, + func, + (size_t i) { return i; } + )() == 10); + assert(floatVisited == true); + + Algebraic!(float, string) variant4; + + assert(variant4.visit!(func, (float f) => cast(size_t) f, () => size_t.max)() == size_t.max); + + // double error func check + static assert(!__traits(compiles, + visit!(() => size_t.max, func, (float f) => cast(size_t) f, () => size_t.max)(variant4)) + ); +} + +// disallow providing multiple generic handlers to visit +// disallow a generic handler that does not apply to all types +@system unittest +{ + Algebraic!(int, float) number = 2; + // ok, x + 1 valid for int and float + static assert( __traits(compiles, number.visit!(x => x + 1))); + // bad, two generic handlers + static assert(!__traits(compiles, number.visit!(x => x + 1, x => x + 2))); + // bad, x ~ "a" does not apply to int or float + static assert(!__traits(compiles, number.visit!(x => x ~ "a"))); + // bad, x ~ "a" does not apply to int or float + static assert(!__traits(compiles, number.visit!(x => x + 1, x => x ~ "a"))); + + Algebraic!(int, string) maybenumber = 2; + // ok, x ~ "a" valid for string, x + 1 valid for int, only 1 generic + static assert( __traits(compiles, number.visit!((string x) => x ~ "a", x => x + 1))); + // bad, x ~ "a" valid for string but not int + static assert(!__traits(compiles, number.visit!(x => x ~ "a"))); + // bad, two generics, each only applies in one case + static assert(!__traits(compiles, number.visit!(x => x + 1, x => x ~ "a"))); +} + +/** + * Behaves as $(LREF visit) but doesn't enforce that all types are handled + * by the visiting functions. + * + * If a parameter-less function is specified it is called when + * either $(D variant) doesn't hold a value or holds a type + * which isn't handled by the visiting functions. + * + * Returns: The return type of tryVisit is deduced from the visiting functions and must be + * the same across all overloads. + * Throws: $(LREF VariantException) if `variant` doesn't hold a value or + * `variant` holds a value which isn't handled by the visiting functions, + * when no parameter-less fallback function is specified. + */ +template tryVisit(Handlers...) +if (Handlers.length > 0) +{ + /// + auto tryVisit(VariantType)(VariantType variant) + if (isAlgebraic!VariantType) + { + return visitImpl!(false, VariantType, Handlers)(variant); + } +} + +/// +@system unittest +{ + Algebraic!(int, string) variant; + + variant = 10; + auto which = -1; + variant.tryVisit!((int i) { which = 0; })(); + assert(which == 0); + + // Error function usage + variant = "test"; + variant.tryVisit!((int i) { which = 0; }, + () { which = -100; })(); + assert(which == -100); +} + +@system unittest +{ + import std.exception : assertThrown; + Algebraic!(int, string) variant; + + variant = 10; + auto which = -1; + variant.tryVisit!((int i){ which = 0; })(); + + assert(which == 0); + + variant = "test"; + + assertThrown!VariantException(variant.tryVisit!((int i) { which = 0; })()); + + void errorfunc() + { + which = -1; + } + + variant.tryVisit!((int i) { which = 0; }, errorfunc)(); + + assert(which == -1); +} + +private template isAlgebraic(Type) +{ + static if (is(Type _ == VariantN!T, T...)) + enum isAlgebraic = T.length >= 2; // T[0] == maxDataSize, T[1..$] == AllowedTypesParam + else + enum isAlgebraic = false; +} + +@system unittest +{ + static assert(!isAlgebraic!(Variant)); + static assert( isAlgebraic!(Algebraic!(string))); + static assert( isAlgebraic!(Algebraic!(int, int[]))); +} + +private auto visitImpl(bool Strict, VariantType, Handler...)(VariantType variant) +if (isAlgebraic!VariantType && Handler.length > 0) +{ + alias AllowedTypes = VariantType.AllowedTypes; + + + /** + * Returns: Struct where $(D indices) is an array which + * contains at the n-th position the index in Handler which takes the + * n-th type of AllowedTypes. If an Handler doesn't match an + * AllowedType, -1 is set. If a function in the delegates doesn't + * have parameters, the field $(D exceptionFuncIdx) is set; + * otherwise it's -1. + */ + auto visitGetOverloadMap() + { + struct Result { + int[AllowedTypes.length] indices; + int exceptionFuncIdx = -1; + int generalFuncIdx = -1; + } + + Result result; + + foreach (tidx, T; AllowedTypes) + { + bool added = false; + foreach (dgidx, dg; Handler) + { + // Handle normal function objects + static if (isSomeFunction!dg) + { + alias Params = Parameters!dg; + static if (Params.length == 0) + { + // Just check exception functions in the first + // inner iteration (over delegates) + if (tidx > 0) + continue; + else + { + if (result.exceptionFuncIdx != -1) + assert(false, "duplicate parameter-less (error-)function specified"); + result.exceptionFuncIdx = dgidx; + } + } + else static if (is(Params[0] == T) || is(Unqual!(Params[0]) == T)) + { + if (added) + assert(false, "duplicate overload specified for type '" ~ T.stringof ~ "'"); + + added = true; + result.indices[tidx] = dgidx; + } + } + else static if (isSomeFunction!(dg!T)) + { + assert(result.generalFuncIdx == -1 || + result.generalFuncIdx == dgidx, + "Only one generic visitor function is allowed"); + result.generalFuncIdx = dgidx; + } + // Handle composite visitors with opCall overloads + else + { + static assert(false, dg.stringof ~ " is not a function or delegate"); + } + } + + if (!added) + result.indices[tidx] = -1; + } + + return result; + } + + enum HandlerOverloadMap = visitGetOverloadMap(); + + if (!variant.hasValue) + { + // Call the exception function. The HandlerOverloadMap + // will have its exceptionFuncIdx field set to value != -1 if an + // exception function has been specified; otherwise we just through an exception. + static if (HandlerOverloadMap.exceptionFuncIdx != -1) + return Handler[ HandlerOverloadMap.exceptionFuncIdx ](); + else + throw new VariantException("variant must hold a value before being visited."); + } + + foreach (idx, T; AllowedTypes) + { + if (auto ptr = variant.peek!T) + { + enum dgIdx = HandlerOverloadMap.indices[idx]; + + static if (dgIdx == -1) + { + static if (HandlerOverloadMap.generalFuncIdx >= 0) + return Handler[HandlerOverloadMap.generalFuncIdx](*ptr); + else static if (Strict) + static assert(false, "overload for type '" ~ T.stringof ~ "' hasn't been specified"); + else static if (HandlerOverloadMap.exceptionFuncIdx != -1) + return Handler[HandlerOverloadMap.exceptionFuncIdx](); + else + throw new VariantException( + "variant holds value of type '" + ~ T.stringof ~ + "' but no visitor has been provided" + ); + } + else + { + return Handler[ dgIdx ](*ptr); + } + } + } + + assert(false); +} + +@system unittest +{ + // validate that visit can be called with a const type + struct Foo { int depth; } + struct Bar { int depth; } + alias FooBar = Algebraic!(Foo, Bar); + + int depth(in FooBar fb) { + return fb.visit!((Foo foo) => foo.depth, + (Bar bar) => bar.depth); + } + + FooBar fb = Foo(3); + assert(depth(fb) == 3); +} + +@system unittest +{ + // https://issues.dlang.org/show_bug.cgi?id=16383 + class Foo {this() immutable {}} + alias V = Algebraic!(immutable Foo); + + auto x = V(new immutable Foo).visit!( + (immutable(Foo) _) => 3 + ); + assert(x == 3); +} + +@system unittest +{ + // http://d.puremagic.com/issues/show_bug.cgi?id=5310 + const Variant a; + assert(a == a); + Variant b; + assert(a == b); + assert(b == a); +} + +@system unittest +{ + const Variant a = [2]; + assert(a[0] == 2); +} + +@system unittest +{ + // http://d.puremagic.com/issues/show_bug.cgi?id=10017 + static struct S + { + ubyte[Variant.size + 1] s; + } + + Variant v1, v2; + v1 = S(); // the payload is allocated on the heap + v2 = v1; // AssertError: target must be non-null + assert(v1 == v2); +} +@system unittest +{ + import std.exception : assertThrown; + // http://d.puremagic.com/issues/show_bug.cgi?id=7069 + Variant v; + + int i = 10; + v = i; + foreach (qual; AliasSeq!(MutableOf, ConstOf)) + { + assert(v.get!(qual!int) == 10); + assert(v.get!(qual!float) == 10.0f); + } + foreach (qual; AliasSeq!(ImmutableOf, SharedOf, SharedConstOf)) + { + assertThrown!VariantException(v.get!(qual!int)); + } + + const(int) ci = 20; + v = ci; + foreach (qual; AliasSeq!(ConstOf)) + { + assert(v.get!(qual!int) == 20); + assert(v.get!(qual!float) == 20.0f); + } + foreach (qual; AliasSeq!(MutableOf, ImmutableOf, SharedOf, SharedConstOf)) + { + assertThrown!VariantException(v.get!(qual!int)); + assertThrown!VariantException(v.get!(qual!float)); + } + + immutable(int) ii = ci; + v = ii; + foreach (qual; AliasSeq!(ImmutableOf, ConstOf, SharedConstOf)) + { + assert(v.get!(qual!int) == 20); + assert(v.get!(qual!float) == 20.0f); + } + foreach (qual; AliasSeq!(MutableOf, SharedOf)) + { + assertThrown!VariantException(v.get!(qual!int)); + assertThrown!VariantException(v.get!(qual!float)); + } + + int[] ai = [1,2,3]; + v = ai; + foreach (qual; AliasSeq!(MutableOf, ConstOf)) + { + assert(v.get!(qual!(int[])) == [1,2,3]); + assert(v.get!(qual!(int)[]) == [1,2,3]); + } + foreach (qual; AliasSeq!(ImmutableOf, SharedOf, SharedConstOf)) + { + assertThrown!VariantException(v.get!(qual!(int[]))); + assertThrown!VariantException(v.get!(qual!(int)[])); + } + + const(int[]) cai = [4,5,6]; + v = cai; + foreach (qual; AliasSeq!(ConstOf)) + { + assert(v.get!(qual!(int[])) == [4,5,6]); + assert(v.get!(qual!(int)[]) == [4,5,6]); + } + foreach (qual; AliasSeq!(MutableOf, ImmutableOf, SharedOf, SharedConstOf)) + { + assertThrown!VariantException(v.get!(qual!(int[]))); + assertThrown!VariantException(v.get!(qual!(int)[])); + } + + immutable(int[]) iai = [7,8,9]; + v = iai; + //assert(v.get!(immutable(int[])) == [7,8,9]); // Bug ??? runtime error + assert(v.get!(immutable(int)[]) == [7,8,9]); + assert(v.get!(const(int[])) == [7,8,9]); + assert(v.get!(const(int)[]) == [7,8,9]); + //assert(v.get!(shared(const(int[]))) == cast(shared const)[7,8,9]); // Bug ??? runtime error + //assert(v.get!(shared(const(int))[]) == cast(shared const)[7,8,9]); // Bug ??? runtime error + foreach (qual; AliasSeq!(MutableOf)) + { + assertThrown!VariantException(v.get!(qual!(int[]))); + assertThrown!VariantException(v.get!(qual!(int)[])); + } + + class A {} + class B : A {} + B b = new B(); + v = b; + foreach (qual; AliasSeq!(MutableOf, ConstOf)) + { + assert(v.get!(qual!B) is b); + assert(v.get!(qual!A) is b); + assert(v.get!(qual!Object) is b); + } + foreach (qual; AliasSeq!(ImmutableOf, SharedOf, SharedConstOf)) + { + assertThrown!VariantException(v.get!(qual!B)); + assertThrown!VariantException(v.get!(qual!A)); + assertThrown!VariantException(v.get!(qual!Object)); + } + + const(B) cb = new B(); + v = cb; + foreach (qual; AliasSeq!(ConstOf)) + { + assert(v.get!(qual!B) is cb); + assert(v.get!(qual!A) is cb); + assert(v.get!(qual!Object) is cb); + } + foreach (qual; AliasSeq!(MutableOf, ImmutableOf, SharedOf, SharedConstOf)) + { + assertThrown!VariantException(v.get!(qual!B)); + assertThrown!VariantException(v.get!(qual!A)); + assertThrown!VariantException(v.get!(qual!Object)); + } + + immutable(B) ib = new immutable(B)(); + v = ib; + foreach (qual; AliasSeq!(ImmutableOf, ConstOf, SharedConstOf)) + { + assert(v.get!(qual!B) is ib); + assert(v.get!(qual!A) is ib); + assert(v.get!(qual!Object) is ib); + } + foreach (qual; AliasSeq!(MutableOf, SharedOf)) + { + assertThrown!VariantException(v.get!(qual!B)); + assertThrown!VariantException(v.get!(qual!A)); + assertThrown!VariantException(v.get!(qual!Object)); + } + + shared(B) sb = new shared B(); + v = sb; + foreach (qual; AliasSeq!(SharedOf, SharedConstOf)) + { + assert(v.get!(qual!B) is sb); + assert(v.get!(qual!A) is sb); + assert(v.get!(qual!Object) is sb); + } + foreach (qual; AliasSeq!(MutableOf, ImmutableOf, ConstOf)) + { + assertThrown!VariantException(v.get!(qual!B)); + assertThrown!VariantException(v.get!(qual!A)); + assertThrown!VariantException(v.get!(qual!Object)); + } + + shared(const(B)) scb = new shared const B(); + v = scb; + foreach (qual; AliasSeq!(SharedConstOf)) + { + assert(v.get!(qual!B) is scb); + assert(v.get!(qual!A) is scb); + assert(v.get!(qual!Object) is scb); + } + foreach (qual; AliasSeq!(MutableOf, ConstOf, ImmutableOf, SharedOf)) + { + assertThrown!VariantException(v.get!(qual!B)); + assertThrown!VariantException(v.get!(qual!A)); + assertThrown!VariantException(v.get!(qual!Object)); + } +} + +@system unittest +{ + static struct DummyScope + { + // https://d.puremagic.com/issues/show_bug.cgi?id=12540 + alias Alias12540 = Algebraic!Class12540; + + static class Class12540 + { + Alias12540 entity; + } + } +} + +@system unittest +{ + // https://issues.dlang.org/show_bug.cgi?id=10194 + // Also test for elaborate copying + static struct S + { + @disable this(); + this(int dummy) + { + ++cnt; + } + + this(this) + { + ++cnt; + } + + @disable S opAssign(); + + ~this() + { + --cnt; + assert(cnt >= 0); + } + static int cnt = 0; + } + + { + Variant v; + { + v = S(0); + assert(S.cnt == 1); + } + assert(S.cnt == 1); + + // assigning a new value should destroy the existing one + v = 0; + assert(S.cnt == 0); + + // destroying the variant should destroy it's current value + v = S(0); + assert(S.cnt == 1); + } + assert(S.cnt == 0); +} + +@system unittest +{ + // Bugzilla 13300 + static struct S + { + this(this) {} + ~this() {} + } + + static assert( hasElaborateCopyConstructor!(Variant)); + static assert(!hasElaborateCopyConstructor!(Algebraic!bool)); + static assert( hasElaborateCopyConstructor!(Algebraic!S)); + static assert( hasElaborateCopyConstructor!(Algebraic!(bool, S))); + + static assert( hasElaborateDestructor!(Variant)); + static assert(!hasElaborateDestructor!(Algebraic!bool)); + static assert( hasElaborateDestructor!(Algebraic!S)); + static assert( hasElaborateDestructor!(Algebraic!(bool, S))); + + import std.array; + alias Value = Algebraic!bool; + + static struct T + { + Value value; + @disable this(); + } + auto a = appender!(T[]); +} + +@system unittest +{ + // Bugzilla 13871 + alias A = Algebraic!(int, typeof(null)); + static struct B { A value; } + alias C = std.variant.Algebraic!B; + + C var; + var = C(B()); +} + +@system unittest +{ + import std.exception : assertThrown, assertNotThrown; + // Make sure Variant can handle types with opDispatch but no length field. + struct SWithNoLength + { + void opDispatch(string s)() { } + } + + struct SWithLength + { + @property int opDispatch(string s)() + { + // Assume that s == "length" + return 5; // Any value is OK for test. + } + } + + SWithNoLength sWithNoLength; + Variant v = sWithNoLength; + assertThrown!VariantException(v.length); + + SWithLength sWithLength; + v = sWithLength; + assertNotThrown!VariantException(v.get!SWithLength.length); + assertThrown!VariantException(v.length); +} + +@system unittest +{ + // Bugzilla 13534 + static assert(!__traits(compiles, () @safe { + auto foo() @system { return 3; } + auto v = Variant(&foo); + v(); // foo is called in safe code!? + })); +} + +@system unittest +{ + // Bugzilla 15039 + import std.typecons; + import std.variant; + + alias IntTypedef = Typedef!int; + alias Obj = Algebraic!(int, IntTypedef, This[]); + + Obj obj = 1; + + obj.visit!( + (int x) {}, + (IntTypedef x) {}, + (Obj[] x) {}, + ); +} + +@system unittest +{ + // Bugzilla 15791 + int n = 3; + struct NS1 { int foo() { return n + 10; } } + struct NS2 { int foo() { return n * 10; } } + + Variant v; + v = NS1(); + assert(v.get!NS1.foo() == 13); + v = NS2(); + assert(v.get!NS2.foo() == 30); +} + +@system unittest +{ + // Bugzilla 15827 + static struct Foo15827 { Variant v; this(Foo15827 v) {} } + Variant v = Foo15827.init; +} diff --git a/libphobos/src/std/windows/charset.d b/libphobos/src/std/windows/charset.d new file mode 100644 index 0000000..ee7211d --- /dev/null +++ b/libphobos/src/std/windows/charset.d @@ -0,0 +1,122 @@ +// Written in the D programming language. + +/** + * Support UTF-8 on Windows 95, 98 and ME systems. + * + * Copyright: Copyright Digital Mars 2005 - 2009. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: $(HTTP digitalmars.com, Walter Bright) + */ +/* Copyright Digital Mars 2005 - 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) + */ +module std.windows.charset; + +version (StdDdoc) +{ + /****************************************** + * Converts the UTF-8 string s into a null-terminated string in a Windows + * 8-bit character set. + * + * Params: + * s = UTF-8 string to convert. + * codePage = is the number of the target codepage, or + * 0 - ANSI, + * 1 - OEM, + * 2 - Mac + * + * Authors: + * yaneurao, Walter Bright, Stewart Gordon + */ + const(char)* toMBSz(in char[] s, uint codePage = 0); + + /********************************************** + * Converts the null-terminated string s from a Windows 8-bit character set + * into a UTF-8 char array. + * + * Params: + * s = UTF-8 string to convert. + * codePage = is the number of the source codepage, or + * 0 - ANSI, + * 1 - OEM, + * 2 - Mac + * Authors: Stewart Gordon, Walter Bright + */ + string fromMBSz(immutable(char)* s, int codePage = 0); +} +else: + +version (Windows): + +import core.sys.windows.windows; +import std.conv; +import std.string; +import std.windows.syserror; + +import std.internal.cstring; + +const(char)* toMBSz(in char[] s, uint codePage = 0) +{ + // Only need to do this if any chars have the high bit set + foreach (char c; s) + { + if (c >= 0x80) + { + char[] result; + int readLen; + auto wsTmp = s.tempCStringW(); + result.length = WideCharToMultiByte(codePage, 0, wsTmp, -1, null, 0, + null, null); + + if (result.length) + { + readLen = WideCharToMultiByte(codePage, 0, wsTmp, -1, result.ptr, + to!int(result.length), null, null); + } + + if (!readLen || readLen != result.length) + { + throw new Exception("Couldn't convert string: " ~ + sysErrorString(GetLastError())); + } + + return result.ptr; + } + } + return std.string.toStringz(s); +} + +string fromMBSz(immutable(char)* s, int codePage = 0) +{ + const(char)* c; + + for (c = s; *c != 0; c++) + { + if (*c >= 0x80) + { + wchar[] result; + int readLen; + + result.length = MultiByteToWideChar(codePage, 0, s, -1, null, 0); + + if (result.length) + { + readLen = MultiByteToWideChar(codePage, 0, s, -1, result.ptr, + to!int(result.length)); + } + + if (!readLen || readLen != result.length) + { + throw new Exception("Couldn't convert string: " ~ + sysErrorString(GetLastError())); + } + + return result[0 .. result.length-1].to!string; // omit trailing null + } + } + return s[0 .. c-s]; // string is ASCII, no conversion necessary +} + + diff --git a/libphobos/src/std/windows/registry.d b/libphobos/src/std/windows/registry.d new file mode 100644 index 0000000..7293d2d --- /dev/null +++ b/libphobos/src/std/windows/registry.d @@ -0,0 +1,1868 @@ +/** + This library provides Win32 Registry facilities. + + Copyright: Copyright 2003-2004 by Matthew Wilson and Synesis Software + Written by Matthew Wilson + + License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + + Author: Matthew Wilson, Kenji Hara + + History: + Created 15th March 2003, + Updated 25th April 2004, + + Source: $(PHOBOSSRC std/windows/_registry.d) +*/ +/* ///////////////////////////////////////////////////////////////////////////// + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, in both source and binary form, subject to the following + * restrictions: + * + * - The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * - Altered source versions must be plainly marked as such, and must not + * be misrepresented as being the original software. + * - This notice may not be removed or altered from any source + * distribution. + * + * ////////////////////////////////////////////////////////////////////////// */ +module std.windows.registry; +version (Windows): + +import core.sys.windows.windows; +import std.array; +import std.conv; +import std.exception; +import std.internal.cstring; +import std.internal.windows.advapi32; +import std.system : Endian, endian; +import std.windows.syserror; + +//debug = winreg; +debug(winreg) import std.stdio; + +private +{ + import core.sys.windows.winbase : lstrlenW; + + void enforceSucc(LONG res, lazy string message, string fn = __FILE__, size_t ln = __LINE__) + { + if (res != ERROR_SUCCESS) + throw new RegistryException(message, res, fn, ln); + } +} + +/* ************* Exceptions *************** */ + +// Do not use. Left for compatibility. +class Win32Exception : WindowsException +{ + @safe + this(string message, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null) + { + super(0, message, fn, ln); + } + + @safe + this(string message, int errnum, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null) + { + super(errnum, message, fn, ln); + } + + @property int error() { return super.code; } +} + +version (unittest) import std.string : startsWith, endsWith; + +@safe unittest +{ + // Test that we can throw and catch one by its own type + string message = "Test W1"; + + auto e = collectException!Win32Exception( + enforce(false, new Win32Exception(message))); + assert(e.msg.startsWith(message)); +} + +@system unittest +{ + // ditto + string message = "Test W2"; + int code = 5; + + auto e = collectException!Win32Exception( + enforce(false, new Win32Exception(message, code))); + assert(e.error == code); + assert(e.msg.startsWith(message)); +} + +/** + Exception class thrown by the std.windows.registry classes. + */ +class RegistryException + : Win32Exception +{ +public: + /** + Creates an instance of the exception. + + Params: + message = The message associated with the exception. + */ + @safe + this(string message, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null) + { + super(message, fn, ln, next); + } + + /** + Creates an instance of the exception, with the given. + + Params: + message = The message associated with the exception. + error = The Win32 error number associated with the exception. + */ + @safe + this(string message, int error, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null) + { + super(message, error, fn, ln, next); + } +} + +@system unittest +{ + // (i) Test that we can throw and catch one by its own type + string message = "Test 1"; + int code = 3; + + auto e = collectException!RegistryException( + enforce(false, new RegistryException(message, code))); + assert(e.error == code); + assert(e.msg.startsWith(message)); +} + +@safe unittest +{ + // ditto + string message = "Test 2"; + + auto e = collectException!RegistryException( + enforce(false, new RegistryException(message))); + assert(e.msg.startsWith(message)); +} + +/* ************* public enumerations *************** */ + +/** + Enumeration of the recognised registry access modes. + */ +enum REGSAM +{ + KEY_QUERY_VALUE = 0x0001, /// Permission to query subkey data + KEY_SET_VALUE = 0x0002, /// Permission to set subkey data + KEY_CREATE_SUB_KEY = 0x0004, /// Permission to create subkeys + KEY_ENUMERATE_SUB_KEYS = 0x0008, /// Permission to enumerate subkeys + KEY_NOTIFY = 0x0010, /// Permission for change notification + KEY_CREATE_LINK = 0x0020, /// Permission to create a symbolic link + KEY_WOW64_32KEY = 0x0200, /// Enables a 64- or 32-bit application to open a 32-bit key + KEY_WOW64_64KEY = 0x0100, /// Enables a 64- or 32-bit application to open a 64-bit key + KEY_WOW64_RES = 0x0300, /// + KEY_READ = (STANDARD_RIGHTS_READ + | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY) + & ~(SYNCHRONIZE), + /// Combines the STANDARD_RIGHTS_READ, KEY_QUERY_VALUE, + /// KEY_ENUMERATE_SUB_KEYS, and KEY_NOTIFY access rights + KEY_WRITE = (STANDARD_RIGHTS_WRITE + | KEY_SET_VALUE | KEY_CREATE_SUB_KEY) + & ~(SYNCHRONIZE), + /// Combines the STANDARD_RIGHTS_WRITE, KEY_SET_VALUE, + /// and KEY_CREATE_SUB_KEY access rights + KEY_EXECUTE = KEY_READ & ~(SYNCHRONIZE), + /// Permission for read access + KEY_ALL_ACCESS = (STANDARD_RIGHTS_ALL + | KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY + | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY | KEY_CREATE_LINK) + & ~(SYNCHRONIZE), + /// Combines the KEY_QUERY_VALUE, KEY_ENUMERATE_SUB_KEYS, + /// KEY_NOTIFY, KEY_CREATE_SUB_KEY, KEY_CREATE_LINK, and + /// KEY_SET_VALUE access rights, plus all the standard + /// access rights except SYNCHRONIZE +} + +/** + Enumeration of the recognised registry value types. + */ +enum REG_VALUE_TYPE : DWORD +{ + REG_UNKNOWN = -1, /// + REG_NONE = 0, /// The null value type. (In practise this is treated as a zero-length binary array by the Win32 registry) + REG_SZ = 1, /// A zero-terminated string + REG_EXPAND_SZ = 2, /// A zero-terminated string containing expandable environment variable references + REG_BINARY = 3, /// A binary blob + REG_DWORD = 4, /// A 32-bit unsigned integer + REG_DWORD_LITTLE_ENDIAN = 4, /// A 32-bit unsigned integer, stored in little-endian byte order + REG_DWORD_BIG_ENDIAN = 5, /// A 32-bit unsigned integer, stored in big-endian byte order + REG_LINK = 6, /// A registry link + REG_MULTI_SZ = 7, /// A set of zero-terminated strings + REG_RESOURCE_LIST = 8, /// A hardware resource list + REG_FULL_RESOURCE_DESCRIPTOR = 9, /// A hardware resource descriptor + REG_RESOURCE_REQUIREMENTS_LIST = 10, /// A hardware resource requirements list + REG_QWORD = 11, /// A 64-bit unsigned integer + REG_QWORD_LITTLE_ENDIAN = 11, /// A 64-bit unsigned integer, stored in little-endian byte order +} + + +/* ************* private *************** */ + +import core.sys.windows.winnt : + DELETE , + READ_CONTROL , + WRITE_DAC , + WRITE_OWNER , + SYNCHRONIZE , + + STANDARD_RIGHTS_REQUIRED, + + STANDARD_RIGHTS_READ , + STANDARD_RIGHTS_WRITE , + STANDARD_RIGHTS_EXECUTE , + + STANDARD_RIGHTS_ALL , + + SPECIFIC_RIGHTS_ALL ; + +import core.sys.windows.winreg : + REG_CREATED_NEW_KEY , + REG_OPENED_EXISTING_KEY ; + +// Returns samDesired but without WoW64 flags if not in WoW64 mode +// for compatibility with Windows 2000 +private REGSAM compatibleRegsam(in REGSAM samDesired) +{ + return isWow64 ? samDesired : cast(REGSAM)(samDesired & ~REGSAM.KEY_WOW64_RES); +} + +///Returns true, if we are in WoW64 mode and have WoW64 flags +private bool haveWoW64Job(in REGSAM samDesired) +{ + return isWow64 && (samDesired & REGSAM.KEY_WOW64_RES); +} + +private REG_VALUE_TYPE _RVT_from_Endian(Endian endian) +{ + final switch (endian) + { + case Endian.bigEndian: + return REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN; + + case Endian.littleEndian: + return REG_VALUE_TYPE.REG_DWORD_LITTLE_ENDIAN; + } +} + +private LONG regCloseKey(in HKEY hkey) +in +{ + assert(hkey !is null); +} +body +{ + /* No need to attempt to close any of the standard hive keys. + * Although it's documented that calling RegCloseKey() on any of + * these hive keys is ignored, we'd rather not trust the Win32 + * API. + */ + if (cast(uint) hkey & 0x80000000) + { + switch (cast(uint) hkey) + { + case HKEY_CLASSES_ROOT: + case HKEY_CURRENT_USER: + case HKEY_LOCAL_MACHINE: + case HKEY_USERS: + case HKEY_PERFORMANCE_DATA: + case HKEY_PERFORMANCE_TEXT: + case HKEY_PERFORMANCE_NLSTEXT: + case HKEY_CURRENT_CONFIG: + case HKEY_DYN_DATA: + return ERROR_SUCCESS; + default: + /* Do nothing */ + break; + } + } + + return RegCloseKey(hkey); +} + +private void regFlushKey(in HKEY hkey) +in +{ + assert(hkey !is null); +} +body +{ + immutable res = RegFlushKey(hkey); + enforceSucc(res, "Key cannot be flushed"); +} + +private HKEY regCreateKey(in HKEY hkey, in string subKey, in DWORD dwOptions, in REGSAM samDesired, + in LPSECURITY_ATTRIBUTES lpsa, out DWORD disposition) +in +{ + assert(hkey !is null); + assert(subKey !is null); +} +body +{ + HKEY hkeyResult; + enforceSucc(RegCreateKeyExW( + hkey, subKey.tempCStringW(), 0, null, dwOptions, + compatibleRegsam(samDesired), cast(LPSECURITY_ATTRIBUTES) lpsa, + &hkeyResult, &disposition), + "Failed to create requested key: \"" ~ subKey ~ "\""); + + return hkeyResult; +} + +private void regDeleteKey(in HKEY hkey, in string subKey, in REGSAM samDesired) +in +{ + assert(hkey !is null); + assert(subKey !is null); +} +body +{ + LONG res; + if (haveWoW64Job(samDesired)) + { + loadAdvapi32(); + res = pRegDeleteKeyExW(hkey, subKey.tempCStringW(), samDesired, 0); + } + else + { + res = RegDeleteKeyW(hkey, subKey.tempCStringW()); + } + enforceSucc(res, "Key cannot be deleted: \"" ~ subKey ~ "\""); +} + +private void regDeleteValue(in HKEY hkey, in string valueName) +in +{ + assert(hkey !is null); + assert(valueName !is null); +} +body +{ + enforceSucc(RegDeleteValueW(hkey, valueName.tempCStringW()), + "Value cannot be deleted: \"" ~ valueName ~ "\""); +} + +private HKEY regDup(HKEY hkey) +in +{ + assert(hkey !is null); +} +body +{ + /* Can't duplicate standard keys, but don't need to, so can just return */ + if (cast(uint) hkey & 0x80000000) + { + switch (cast(uint) hkey) + { + case HKEY_CLASSES_ROOT: + case HKEY_CURRENT_USER: + case HKEY_LOCAL_MACHINE: + case HKEY_USERS: + case HKEY_PERFORMANCE_DATA: + case HKEY_PERFORMANCE_TEXT: + case HKEY_PERFORMANCE_NLSTEXT: + case HKEY_CURRENT_CONFIG: + case HKEY_DYN_DATA: + return hkey; + default: + /* Do nothing */ + break; + } + } + + HKEY hkeyDup; + immutable res = RegOpenKeyW(hkey, null, &hkeyDup); + + debug(winreg) + { + if (res != ERROR_SUCCESS) + { + writefln("regDup() failed: 0x%08x 0x%08x %d", hkey, hkeyDup, res); + } + + assert(res == ERROR_SUCCESS); + } + + return (res == ERROR_SUCCESS) ? hkeyDup : null; +} + +private LONG regEnumKeyName(in HKEY hkey, in DWORD index, ref wchar[] name, out DWORD cchName) +in +{ + assert(hkey !is null); + assert(name !is null); + assert(name.length > 0); +} +out(res) +{ + assert(res != ERROR_MORE_DATA); +} +body +{ + // The Registry API lies about the lengths of a very few sub-key lengths + // so we have to test to see if it whinges about more data, and provide + // more if it does. + for (;;) + { + cchName = to!DWORD(name.length); + immutable res = RegEnumKeyExW(hkey, index, name.ptr, &cchName, null, null, null, null); + if (res != ERROR_MORE_DATA) + return res; + + // Now need to increase the size of the buffer and try again + name.length *= 2; + } + + assert(0); +} + + +private LONG regEnumValueName(in HKEY hkey, in DWORD dwIndex, ref wchar[] name, out DWORD cchName) +in +{ + assert(hkey !is null); +} +body +{ + for (;;) + { + cchName = to!DWORD(name.length); + immutable res = RegEnumValueW(hkey, dwIndex, name.ptr, &cchName, null, null, null, null); + if (res != ERROR_MORE_DATA) + return res; + + name.length *= 2; + } + + assert(0); +} + +private LONG regGetNumSubKeys(in HKEY hkey, out DWORD cSubKeys, out DWORD cchSubKeyMaxLen) +in +{ + assert(hkey !is null); +} +body +{ + return RegQueryInfoKeyW(hkey, null, null, null, &cSubKeys, + &cchSubKeyMaxLen, null, null, null, null, null, null); +} + +private LONG regGetNumValues(in HKEY hkey, out DWORD cValues, out DWORD cchValueMaxLen) +in +{ + assert(hkey !is null); +} +body +{ + return RegQueryInfoKeyW(hkey, null, null, null, null, null, null, + &cValues, &cchValueMaxLen, null, null, null); +} + +private REG_VALUE_TYPE regGetValueType(in HKEY hkey, in string name) +in +{ + assert(hkey !is null); +} +body +{ + REG_VALUE_TYPE type; + enforceSucc(RegQueryValueExW(hkey, name.tempCStringW(), null, cast(LPDWORD) &type, null, null), + "Value cannot be opened: \"" ~ name ~ "\""); + + return type; +} + +private HKEY regOpenKey(in HKEY hkey, in string subKey, in REGSAM samDesired) +in +{ + assert(hkey !is null); + assert(subKey !is null); +} +body +{ + HKEY hkeyResult; + enforceSucc(RegOpenKeyExW(hkey, subKey.tempCStringW(), 0, compatibleRegsam(samDesired), &hkeyResult), + "Failed to open requested key: \"" ~ subKey ~ "\""); + + return hkeyResult; +} + +private void regQueryValue(in HKEY hkey, string name, out string value, REG_VALUE_TYPE reqType) +in +{ + assert(hkey !is null); +} +body +{ + import core.bitop : bswap; + + REG_VALUE_TYPE type; + + // See bugzilla 961 on this + union U + { + uint dw; + ulong qw; + } + U u; + void* data = &u.qw; + DWORD cbData = u.qw.sizeof; + + auto keynameTmp = name.tempCStringW(); + LONG res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data, &cbData); + if (res == ERROR_MORE_DATA) + { + data = (new ubyte[cbData]).ptr; + res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data, &cbData); + } + + enforceSucc(res, + "Cannot read the requested value"); + enforce(type == reqType, + new RegistryException("Value type has been changed since the value was acquired")); + + switch (type) + { + case REG_VALUE_TYPE.REG_SZ: + case REG_VALUE_TYPE.REG_EXPAND_SZ: + auto wstr = (cast(immutable(wchar)*)data)[0 .. cbData / wchar.sizeof]; + assert(wstr.length > 0 && wstr[$-1] == '\0'); + if (wstr.length && wstr[$-1] == '\0') + wstr.length = wstr.length - 1; + assert(wstr.length == 0 || wstr[$-1] != '\0'); + value = wstr.to!string; + break; + + case REG_VALUE_TYPE.REG_DWORD_LITTLE_ENDIAN: + version (LittleEndian) + value = to!string(u.dw); + else + value = to!string(bswap(u.dw)); + break; + + case REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN: + version (LittleEndian) + value = to!string(bswap(u.dw)); + else + value = to!string(u.dw); + break; + + case REG_VALUE_TYPE.REG_QWORD_LITTLE_ENDIAN: + value = to!string(u.qw); + break; + + case REG_VALUE_TYPE.REG_BINARY: + case REG_VALUE_TYPE.REG_MULTI_SZ: + default: + throw new RegistryException("Cannot read the given value as a string"); + } +} + +private void regQueryValue(in HKEY hkey, in string name, out string[] value, REG_VALUE_TYPE reqType) +in +{ + assert(hkey !is null); +} +body +{ + REG_VALUE_TYPE type; + + auto keynameTmp = name.tempCStringW(); + wchar[] data = new wchar[256]; + DWORD cbData = to!DWORD(data.length * wchar.sizeof); + LONG res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data.ptr, &cbData); + if (res == ERROR_MORE_DATA) + { + data.length = cbData / wchar.sizeof; + res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data.ptr, &cbData); + } + else if (res == ERROR_SUCCESS) + { + data.length = cbData / wchar.sizeof; + } + enforceSucc(res, "Cannot read the requested value"); + enforce(type == REG_VALUE_TYPE.REG_MULTI_SZ, + new RegistryException("Cannot read the given value as a string")); + enforce(type == reqType, + new RegistryException("Value type has been changed since the value was acquired")); + + // Remove last two (or one) null terminator + assert(data.length > 0 && data[$-1] == '\0'); + data.length = data.length - 1; + if (data.length > 0 && data[$-1] == '\0') + data.length = data.length - 1; + + auto list = std.array.split(data[], "\0"); + value.length = list.length; + foreach (i, ref v; value) + { + v = list[i].to!string; + } +} + +private void regQueryValue(in HKEY hkey, in string name, out uint value, REG_VALUE_TYPE reqType) +in +{ + assert(hkey !is null); +} +body +{ + import core.bitop : bswap; + + REG_VALUE_TYPE type; + + DWORD cbData = value.sizeof; + enforceSucc(RegQueryValueExW(hkey, name.tempCStringW(), null, cast(LPDWORD) &type, &value, &cbData), + "Cannot read the requested value"); + enforce(type == reqType, + new RegistryException("Value type has been changed since the value was acquired")); + + switch (type) + { + case REG_VALUE_TYPE.REG_DWORD_LITTLE_ENDIAN: + version (LittleEndian) + static assert(REG_VALUE_TYPE.REG_DWORD == REG_VALUE_TYPE.REG_DWORD_LITTLE_ENDIAN); + else + value = bswap(value); + break; + + case REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN: + version (LittleEndian) + value = bswap(value); + else + static assert(REG_VALUE_TYPE.REG_DWORD == REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN); + break; + + default: + throw new RegistryException("Cannot read the given value as a 32-bit integer"); + } +} + +private void regQueryValue(in HKEY hkey, in string name, out ulong value, REG_VALUE_TYPE reqType) +in +{ + assert(hkey !is null); +} +body +{ + REG_VALUE_TYPE type; + + DWORD cbData = value.sizeof; + enforceSucc(RegQueryValueExW(hkey, name.tempCStringW(), null, cast(LPDWORD) &type, &value, &cbData), + "Cannot read the requested value"); + enforce(type == reqType, + new RegistryException("Value type has been changed since the value was acquired")); + + switch (type) + { + case REG_VALUE_TYPE.REG_QWORD_LITTLE_ENDIAN: + break; + + default: + throw new RegistryException("Cannot read the given value as a 64-bit integer"); + } +} + +private void regQueryValue(in HKEY hkey, in string name, out byte[] value, REG_VALUE_TYPE reqType) +in +{ + assert(hkey !is null); +} +body +{ + REG_VALUE_TYPE type; + + byte[] data = new byte[100]; + DWORD cbData = to!DWORD(data.length); + LONG res; + auto keynameTmp = name.tempCStringW(); + res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data.ptr, &cbData); + if (res == ERROR_MORE_DATA) + { + data.length = cbData; + res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data.ptr, &cbData); + } + enforceSucc(res, "Cannot read the requested value"); + enforce(type == reqType, + new RegistryException("Value type has been changed since the value was acquired")); + + switch (type) + { + case REG_VALUE_TYPE.REG_BINARY: + data.length = cbData; + value = data; + break; + + default: + throw new RegistryException("Cannot read the given value as a string"); + } +} + +private void regSetValue(in HKEY hkey, in string subKey, in REG_VALUE_TYPE type, in LPCVOID lpData, in DWORD cbData) +in +{ + assert(hkey !is null); +} +body +{ + enforceSucc(RegSetValueExW(hkey, subKey.tempCStringW(), 0, type, cast(BYTE*) lpData, cbData), + "Value cannot be set: \"" ~ subKey ~ "\""); +} + +private void regProcessNthKey(Key key, scope void delegate(scope LONG delegate(DWORD, out string)) dg) +{ + DWORD cSubKeys; + DWORD cchSubKeyMaxLen; + + immutable res = regGetNumSubKeys(key.m_hkey, cSubKeys, cchSubKeyMaxLen); + assert(res == ERROR_SUCCESS); + + wchar[] sName = new wchar[cchSubKeyMaxLen + 1]; + + // Capture `key` in the lambda to keep the object alive (and so its HKEY handle open). + dg((DWORD index, out string name) + { + DWORD cchName; + immutable res = regEnumKeyName(key.m_hkey, index, sName, cchName); + if (res == ERROR_SUCCESS) + { + name = sName[0 .. cchName].to!string; + } + return res; + }); +} + +private void regProcessNthValue(Key key, scope void delegate(scope LONG delegate(DWORD, out string)) dg) +{ + DWORD cValues; + DWORD cchValueMaxLen; + + immutable res = regGetNumValues(key.m_hkey, cValues, cchValueMaxLen); + assert(res == ERROR_SUCCESS); + + wchar[] sName = new wchar[cchValueMaxLen + 1]; + + // Capture `key` in the lambda to keep the object alive (and so its HKEY handle open). + dg((DWORD index, out string name) + { + DWORD cchName; + immutable res = regEnumValueName(key.m_hkey, index, sName, cchName); + if (res == ERROR_SUCCESS) + { + name = sName[0 .. cchName].to!string; + } + return res; + }); +} + +/* ************* public classes *************** */ + +/** + This class represents a registry key. + */ +class Key +{ + @safe pure nothrow + invariant() + { + assert(m_hkey !is null); + } + +private: + @safe pure nothrow + this(HKEY hkey, string name, bool created) + in + { + assert(hkey !is null); + } + body + { + m_hkey = hkey; + m_name = name; + } + + ~this() + { + regCloseKey(m_hkey); + + // Even though this is horried waste-of-cycles programming + // we're doing it here so that the + m_hkey = null; + } + +public: + /// The name of the key + @property string name() @safe pure nothrow const + { + return m_name; + } + + /** + The number of sub keys. + */ + @property size_t keyCount() const + { + uint cSubKeys; + uint cchSubKeyMaxLen; + enforceSucc(regGetNumSubKeys(m_hkey, cSubKeys, cchSubKeyMaxLen), + "Number of sub-keys cannot be determined"); + + return cSubKeys; + } + + /** + An enumerable sequence of all the sub-keys of this key. + */ + @property KeySequence keys() @safe pure + { + return new KeySequence(this); + } + + /** + An enumerable sequence of the names of all the sub-keys of this key. + */ + @property KeyNameSequence keyNames() @safe pure + { + return new KeyNameSequence(this); + } + + /** + The number of values. + */ + @property size_t valueCount() const + { + uint cValues; + uint cchValueMaxLen; + enforceSucc(regGetNumValues(m_hkey, cValues, cchValueMaxLen), + "Number of values cannot be determined"); + + return cValues; + } + + /** + An enumerable sequence of all the values of this key. + */ + @property ValueSequence values() @safe pure + { + return new ValueSequence(this); + } + + /** + An enumerable sequence of the names of all the values of this key. + */ + @property ValueNameSequence valueNames() @safe pure + { + return new ValueNameSequence(this); + } + +public: + /** + Returns the named sub-key of this key. + + Params: + name = The name of the subkey to create. May not be $(D null). + Returns: + The created key. + Throws: + $(D RegistryException) is thrown if the key cannot be created. + */ + Key createKey(string name, REGSAM access = REGSAM.KEY_ALL_ACCESS) + { + enforce(!name.empty, new RegistryException("Key name is invalid")); + + DWORD disposition; + HKEY hkey = regCreateKey(m_hkey, name, 0, access, null, disposition); + assert(hkey !is null); + + // Potential resource leak here!! + // + // If the allocation of the memory for Key fails, the HKEY could be + // lost. Hence, we catch such a failure by the finally, and release + // the HKEY there. If the creation of + try + { + Key key = new Key(hkey, name, disposition == REG_CREATED_NEW_KEY); + hkey = null; + return key; + } + finally + { + if (hkey !is null) + { + regCloseKey(hkey); + } + } + } + + /** + Returns the named sub-key of this key. + + Params: + name = The name of the subkey to aquire. If name is the empty + string, then the called key is duplicated. + access = The desired access; one of the $(D REGSAM) enumeration. + Returns: + The aquired key. + Throws: + This function never returns $(D null). If a key corresponding to + the requested name is not found, $(D RegistryException) is thrown. + */ + Key getKey(string name, REGSAM access = REGSAM.KEY_READ) + { + if (name.empty) + return new Key(regDup(m_hkey), m_name, false); + + HKEY hkey = regOpenKey(m_hkey, name, access); + assert(hkey !is null); + + // Potential resource leak here!! + // + // If the allocation of the memory for Key fails, the HKEY could be + // lost. Hence, we catch such a failure by the finally, and release + // the HKEY there. If the creation of + try + { + Key key = new Key(hkey, name, false); + hkey = null; + return key; + } + finally + { + if (hkey != null) + { + regCloseKey(hkey); + } + } + } + + /** + Deletes the named key. + + Params: + name = The name of the key to delete. May not be $(D null). + */ + void deleteKey(string name, REGSAM access = cast(REGSAM) 0) + { + enforce(!name.empty, new RegistryException("Key name is invalid")); + + regDeleteKey(m_hkey, name, access); + } + + /** + Returns the named value. + If $(D name) is the empty string, then the default value is returned. + + Returns: + This function never returns $(D null). If a value corresponding + to the requested name is not found, $(D RegistryException) is thrown. + */ + Value getValue(string name) + { + return new Value(this, name, regGetValueType(m_hkey, name)); + } + + /** + Sets the named value with the given 32-bit unsigned integer value. + + Params: + name = The name of the value to set. If it is the empty string, + sets the default value. + value = The 32-bit unsigned value to set. + Throws: + If a value corresponding to the requested name is not found, + $(D RegistryException) is thrown. + */ + void setValue(string name, uint value) + { + setValue(name, value, endian); + } + + /** + Sets the named value with the given 32-bit unsigned integer value, + according to the desired byte-ordering. + + Params: + name = The name of the value to set. If it is the empty string, + sets the default value. + value = The 32-bit unsigned value to set. + endian = Can be $(D Endian.BigEndian) or $(D Endian.LittleEndian). + Throws: + If a value corresponding to the requested name is not found, + $(D RegistryException) is thrown. + */ + void setValue(string name, uint value, Endian endian) + { + REG_VALUE_TYPE type = _RVT_from_Endian(endian); + + assert(type == REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN || + type == REG_VALUE_TYPE.REG_DWORD_LITTLE_ENDIAN); + + regSetValue(m_hkey, name, type, &value, value.sizeof); + } + + /** + Sets the named value with the given 64-bit unsigned integer value. + + Params: + name = The name of the value to set. If it is the empty string, + sets the default value. + value = The 64-bit unsigned value to set. + Throws: + If a value corresponding to the requested name is not found, + $(D RegistryException) is thrown. + */ + void setValue(string name, ulong value) + { + regSetValue(m_hkey, name, REG_VALUE_TYPE.REG_QWORD, &value, value.sizeof); + } + + /** + Sets the named value with the given string value. + + Params: + name = The name of the value to set. If it is the empty string, + sets the default value. + value = The string value to set. + Throws: + If a value corresponding to the requested name is not found, + $(D RegistryException) is thrown. + */ + void setValue(string name, string value) + { + setValue(name, value, false); + } + + /** + Sets the named value with the given string value. + + Params: + name = The name of the value to set. If it is the empty string, + sets the default value. + value = The string value to set. + asEXPAND_SZ = If $(D true), the value will be stored as an + expandable environment string, otherwise as a normal string. + Throws: + If a value corresponding to the requested name is not found, + $(D RegistryException) is thrown. + */ + void setValue(string name, string value, bool asEXPAND_SZ) + { + auto pszTmp = value.tempCStringW(); + const(void)* data = pszTmp; + DWORD len = to!DWORD(lstrlenW(pszTmp) * wchar.sizeof); + + regSetValue(m_hkey, name, + asEXPAND_SZ ? REG_VALUE_TYPE.REG_EXPAND_SZ + : REG_VALUE_TYPE.REG_SZ, + data, len); + } + + /** + Sets the named value with the given multiple-strings value. + + Params: + name = The name of the value to set. If it is the empty string, + sets the default value. + value = The multiple-strings value to set. + Throws: + If a value corresponding to the requested name is not found, + $(D RegistryException) is thrown. + */ + void setValue(string name, string[] value) + { + wstring[] data = new wstring[value.length+1]; + foreach (i, ref s; data[0..$-1]) + { + s = value[i].to!wstring; + } + data[$-1] = "\0"; + auto ws = std.array.join(data, "\0"w); + + regSetValue(m_hkey, name, REG_VALUE_TYPE.REG_MULTI_SZ, ws.ptr, to!uint(ws.length * wchar.sizeof)); + } + + /** + Sets the named value with the given binary value. + + Params: + name = The name of the value to set. If it is the empty string, + sets the default value. + value = The binary value to set. + Throws: + If a value corresponding to the requested name is not found, + $(D RegistryException) is thrown. + */ + void setValue(string name, byte[] value) + { + regSetValue(m_hkey, name, REG_VALUE_TYPE.REG_BINARY, value.ptr, to!DWORD(value.length)); + } + + /** + Deletes the named value. + + Params: + name = The name of the value to delete. May not be $(D null). + Throws: + If a value of the requested name is not found, + $(D RegistryException) is thrown. + */ + void deleteValue(string name) + { + regDeleteValue(m_hkey, name); + } + + /** + Flushes any changes to the key to disk. + */ + void flush() + { + regFlushKey(m_hkey); + } + +private: + HKEY m_hkey; + string m_name; +} + +/** + This class represents a value of a registry key. + */ +class Value +{ + @safe pure nothrow + invariant() + { + assert(m_key !is null); + } + +private: + @safe pure nothrow + this(Key key, string name, REG_VALUE_TYPE type) + in + { + assert(null !is key); + } + body + { + m_key = key; + m_type = type; + m_name = name; + } + +public: + /** + The name of the value. + If the value represents a default value of a key, which has no name, + the returned string will be of zero length. + */ + @property string name() @safe pure nothrow const + { + return m_name; + } + + /** + The type of value. + */ + @property REG_VALUE_TYPE type() @safe pure nothrow const + { + return m_type; + } + + /** + Obtains the current value of the value as a string. + If the value's type is REG_EXPAND_SZ the returned value is <b>not</b> + expanded; $(D value_EXPAND_SZ) should be called + + Returns: + The contents of the value. + Throws: + $(D RegistryException) if the type of the value is not REG_SZ, + REG_EXPAND_SZ, REG_DWORD, or REG_QWORD. + */ + @property string value_SZ() const + { + string value; + + regQueryValue(m_key.m_hkey, m_name, value, m_type); + + return value; + } + + /** + Obtains the current value as a string, within which any environment + variables have undergone expansion. + This function works with the same value-types as $(D value_SZ). + + Returns: + The contents of the value. + */ + @property string value_EXPAND_SZ() const + { + string value = value_SZ; + + // ExpandEnvironemntStrings(): + // http://msdn2.microsoft.com/en-us/library/ms724265.aspx + const srcTmp = value.tempCStringW(); + DWORD cchRequired = ExpandEnvironmentStringsW(srcTmp, null, 0); + wchar[] newValue = new wchar[cchRequired]; + + immutable DWORD count = enforce!Win32Exception( + ExpandEnvironmentStringsW(srcTmp, newValue.ptr, to!DWORD(newValue.length)), + "Failed to expand environment variables"); + + return newValue[0 .. count-1].to!string; // remove trailing 0 + } + + /** + Obtains the current value as an array of strings. + + Returns: + The contents of the value. + Throws: + $(D RegistryException) if the type of the value is not REG_MULTI_SZ. + */ + @property string[] value_MULTI_SZ() const + { + string[] value; + + regQueryValue(m_key.m_hkey, m_name, value, m_type); + + return value; + } + + /** + Obtains the current value as a 32-bit unsigned integer, ordered + correctly according to the current architecture. + + Returns: + The contents of the value. + Throws: + $(D RegistryException) is thrown for all types other than + REG_DWORD, REG_DWORD_LITTLE_ENDIAN and REG_DWORD_BIG_ENDIAN. + */ + @property uint value_DWORD() const + { + uint value; + + regQueryValue(m_key.m_hkey, m_name, value, m_type); + + return value; + } + + /** + Obtains the value as a 64-bit unsigned integer, ordered correctly + according to the current architecture. + + Returns: + The contents of the value. + Throws: + $(D RegistryException) if the type of the value is not REG_QWORD. + */ + @property ulong value_QWORD() const + { + ulong value; + + regQueryValue(m_key.m_hkey, m_name, value, m_type); + + return value; + } + + /** + Obtains the value as a binary blob. + + Returns: + The contents of the value. + Throws: + $(D RegistryException) if the type of the value is not REG_BINARY. + */ + @property byte[] value_BINARY() const + { + byte[] value; + + regQueryValue(m_key.m_hkey, m_name, value, m_type); + + return value; + } + +private: + Key m_key; + REG_VALUE_TYPE m_type; + string m_name; +} + +/** + Represents the local system registry. + */ +final class Registry +{ +private: + @disable this() { } + +public: + /// Returns the root key for the HKEY_CLASSES_ROOT hive + static @property Key classesRoot() { return new Key(HKEY_CLASSES_ROOT, "HKEY_CLASSES_ROOT", false); } + /// Returns the root key for the HKEY_CURRENT_USER hive + static @property Key currentUser() { return new Key(HKEY_CURRENT_USER, "HKEY_CURRENT_USER", false); } + /// Returns the root key for the HKEY_LOCAL_MACHINE hive + static @property Key localMachine() { return new Key(HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE", false); } + /// Returns the root key for the HKEY_USERS hive + static @property Key users() { return new Key(HKEY_USERS, "HKEY_USERS", false); } + /// Returns the root key for the HKEY_PERFORMANCE_DATA hive + static @property Key performanceData() { return new Key(HKEY_PERFORMANCE_DATA, "HKEY_PERFORMANCE_DATA", false); } + /// Returns the root key for the HKEY_CURRENT_CONFIG hive + static @property Key currentConfig() { return new Key(HKEY_CURRENT_CONFIG, "HKEY_CURRENT_CONFIG", false); } + /// Returns the root key for the HKEY_DYN_DATA hive + static @property Key dynData() { return new Key(HKEY_DYN_DATA, "HKEY_DYN_DATA", false); } +} + +/** + An enumerable sequence representing the names of the sub-keys of a registry Key. + +Example: +---- +Key key = ... +foreach (string subkeyName; key.keyNames) +{ + // using subkeyName +} +---- + */ +class KeyNameSequence +{ + @safe pure nothrow + invariant() + { + assert(m_key !is null); + } + +private: + @safe pure nothrow + this(Key key) + { + m_key = key; + } + +public: + /** + The number of keys. + */ + @property size_t count() const + { + return m_key.keyCount; + } + + /** + The name of the key at the given index. + + Params: + index = The 0-based index of the key to retrieve. + Returns: + The name of the key corresponding to the given index. + Throws: + RegistryException if no corresponding key is retrieved. + */ + string getKeyName(size_t index) + { + string name; + regProcessNthKey(m_key, (scope LONG delegate(DWORD, out string) getName) + { + enforceSucc(getName(to!DWORD(index), name), "Invalid key"); + }); + return name; + } + + /** + The name of the key at the given index. + + Params: + index = The 0-based index of the key to retrieve. + Returns: + The name of the key corresponding to the given index. + Throws: + $(D RegistryException) if no corresponding key is retrieved. + */ + string opIndex(size_t index) + { + return getKeyName(index); + } + +public: + /// + int opApply(scope int delegate(ref string name) dg) + { + int result; + regProcessNthKey(m_key, (scope LONG delegate(DWORD, out string) getName) + { + for (DWORD index = 0; !result; ++index) + { + string name; + immutable res = getName(index, name); + if (res == ERROR_NO_MORE_ITEMS) // Enumeration complete + break; + enforceSucc(res, "Key name enumeration incomplete"); + + result = dg(name); + } + }); + return result; + } + +private: + Key m_key; +} + + +/** + An enumerable sequence representing the sub-keys of a registry Key. + +Example: +---- +Key key = ... +foreach (Key subkey; key.keys) +{ + // using subkey +} +---- + */ +class KeySequence +{ + @safe pure nothrow + invariant() + { + assert(m_key !is null); + } + +private: + @safe pure nothrow + this(Key key) + { + m_key = key; + } + +public: + /** + The number of keys. + */ + @property size_t count() const + { + return m_key.keyCount; + } + + /** + The key at the given index. + + Params: + index = The 0-based index of the key to retrieve. + Returns: + The key corresponding to the given index. + Throws: + $(D RegistryException) if no corresponding key is retrieved. + */ + Key getKey(size_t index) + { + string name; + regProcessNthKey(m_key, (scope LONG delegate(DWORD, out string) getName) + { + enforceSucc(getName(to!DWORD(index), name), "Invalid key"); + }); + return m_key.getKey(name); + } + + /** + The key at the given index. + + Params: + index = The 0-based index of the key to retrieve. + Returns: + The key corresponding to the given index. + Throws: + $(D RegistryException) if no corresponding key is retrieved. + */ + Key opIndex(size_t index) + { + return getKey(index); + } + +public: + /// + int opApply(scope int delegate(ref Key key) dg) + { + int result = 0; + regProcessNthKey(m_key, (scope LONG delegate(DWORD, out string) getName) + { + for (DWORD index = 0; !result; ++index) + { + string name; + immutable res = getName(index, name); + if (res == ERROR_NO_MORE_ITEMS) // Enumeration complete + break; + enforceSucc(res, "Key enumeration incomplete"); + + try + { + Key key = m_key.getKey(name); + result = dg(key); + } + catch (RegistryException e) + { + // Skip inaccessible keys; they are + // accessible via the KeyNameSequence + if (e.error == ERROR_ACCESS_DENIED) + continue; + + throw e; + } + } + }); + return result; + } + +private: + Key m_key; +} + +/** + An enumerable sequence representing the names of the values of a registry Key. + +Example: +---- +Key key = ... +foreach (string valueName; key.valueNames) +{ + // using valueName +} +---- + */ +class ValueNameSequence +{ + @safe pure nothrow + invariant() + { + assert(m_key !is null); + } + +private: + @safe pure nothrow + this(Key key) + { + m_key = key; + } + +public: + /** + The number of values. + */ + @property size_t count() const + { + return m_key.valueCount; + } + + /** + The name of the value at the given index. + + Params: + index = The 0-based index of the value to retrieve. + Returns: + The name of the value corresponding to the given index. + Throws: + $(D RegistryException) if no corresponding value is retrieved. + */ + string getValueName(size_t index) + { + string name; + regProcessNthValue(m_key, (scope LONG delegate(DWORD, out string) getName) + { + enforceSucc(getName(to!DWORD(index), name), "Invalid value"); + }); + return name; + } + + /** + The name of the value at the given index. + + Params: + index = The 0-based index of the value to retrieve. + Returns: + The name of the value corresponding to the given index. + Throws: + $(D RegistryException) if no corresponding value is retrieved. + */ + string opIndex(size_t index) + { + return getValueName(index); + } + +public: + /// + int opApply(scope int delegate(ref string name) dg) + { + int result = 0; + regProcessNthValue(m_key, (scope LONG delegate(DWORD, out string) getName) + { + for (DWORD index = 0; !result; ++index) + { + string name; + immutable res = getName(index, name); + if (res == ERROR_NO_MORE_ITEMS) // Enumeration complete + break; + enforceSucc(res, "Value name enumeration incomplete"); + + result = dg(name); + } + }); + return result; + } + +private: + Key m_key; +} + +/** + An enumerable sequence representing the values of a registry Key. + +Example: +---- +Key key = ... +foreach (Value value; key.values) +{ + // using value +} +---- + */ +class ValueSequence +{ + @safe pure nothrow + invariant() + { + assert(m_key !is null); + } + +private: + @safe pure nothrow + this(Key key) + { + m_key = key; + } + +public: + /// The number of values + @property size_t count() const + { + return m_key.valueCount; + } + + /** + The value at the given $(D index). + + Params: + index = The 0-based index of the value to retrieve + Returns: + The value corresponding to the given index. + Throws: + $(D RegistryException) if no corresponding value is retrieved + */ + Value getValue(size_t index) + { + string name; + regProcessNthValue(m_key, (scope LONG delegate(DWORD, out string) getName) + { + enforceSucc(getName(to!DWORD(index), name), "Invalid value"); + }); + return m_key.getValue(name); + } + + /** + The value at the given $(D index). + + Params: + index = The 0-based index of the value to retrieve. + Returns: + The value corresponding to the given index. + Throws: + $(D RegistryException) if no corresponding value is retrieved. + */ + Value opIndex(size_t index) + { + return getValue(index); + } + +public: + /// + int opApply(scope int delegate(ref Value value) dg) + { + int result = 0; + regProcessNthValue(m_key, (scope LONG delegate(DWORD, out string) getName) + { + for (DWORD index = 0; !result; ++index) + { + string name; + immutable res = getName(index, name); + if (res == ERROR_NO_MORE_ITEMS) // Enumeration complete + break; + enforceSucc(res, "Value enumeration incomplete"); + + Value value = m_key.getValue(name); + result = dg(value); + } + }); + return result; + } + +private: + Key m_key; +} + + +@system unittest +{ + debug(winreg) scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " succeeded."); + debug(winreg) writefln("std.windows.registry.unittest read"); + +/+ + // Mask for test speed up + + Key HKCR = Registry.classesRoot; + Key CLSID = HKCR.getKey("CLSID"); + + foreach (Key key; CLSID.keys) + { + foreach (Value val; key.values) + { + } + } ++/ + Key HKCU = Registry.currentUser; + assert(HKCU); + + // Enumerate all subkeys of key Software + Key softwareKey = HKCU.getKey("Software"); + assert(softwareKey); + foreach (Key key; softwareKey.keys) + { + //writefln("Key %s", key.name); + foreach (Value val; key.values) + { + } + } +} + +@system unittest +{ + debug(winreg) scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " succeeded."); + debug(winreg) writefln("std.windows.registry.unittest write"); + + // Warning: This unit test writes to the registry. + // The test can fail if you don't have sufficient rights + + Key HKCU = Registry.currentUser; + assert(HKCU); + + // Create a new key + string unittestKeyName = "Temporary key for a D UnitTest which can be deleted afterwards"; + Key unittestKey = HKCU.createKey(unittestKeyName); + assert(unittestKey); + Key cityKey = unittestKey.createKey( + "CityCollection using foreign names with umlauts and accents: " + ~"\u00f6\u00e4\u00fc\u00d6\u00c4\u00dc\u00e0\u00e1\u00e2\u00df" + ); + cityKey.setValue("K\u00f6ln", "Germany"); // Cologne + cityKey.setValue("\u041c\u0438\u043d\u0441\u043a", "Belarus"); // Minsk + cityKey.setValue("\u5317\u4eac", "China"); // Bejing + bool foundCologne, foundMinsk, foundBeijing; + foreach (Value v; cityKey.values) + { + auto vname = v.name; + auto vvalue_SZ = v.value_SZ; + if (v.name == "K\u00f6ln") + { + foundCologne = true; + assert(v.value_SZ == "Germany"); + } + if (v.name == "\u041c\u0438\u043d\u0441\u043a") + { + foundMinsk = true; + assert(v.value_SZ == "Belarus"); + } + if (v.name == "\u5317\u4eac") + { + foundBeijing = true; + assert(v.value_SZ == "China"); + } + } + assert(foundCologne); + assert(foundMinsk); + assert(foundBeijing); + + Key stateKey = unittestKey.createKey("StateCollection"); + stateKey.setValue("Germany", ["D\u00fcsseldorf", "K\u00f6ln", "Hamburg"]); + Value v = stateKey.getValue("Germany"); + string[] actual = v.value_MULTI_SZ; + assert(actual.length == 3); + assert(actual[0] == "D\u00fcsseldorf"); + assert(actual[1] == "K\u00f6ln"); + assert(actual[2] == "Hamburg"); + + Key numberKey = unittestKey.createKey("Number"); + numberKey.setValue("One", 1); + Value one = numberKey.getValue("One"); + assert(one.value_SZ == "1"); + assert(one.value_DWORD == 1); + + unittestKey.deleteKey(numberKey.name); + unittestKey.deleteKey(stateKey.name); + unittestKey.deleteKey(cityKey.name); + HKCU.deleteKey(unittestKeyName); + + auto e = collectException!RegistryException(HKCU.getKey("cDhmxsX9K23a8Uf869uB")); + assert(e.msg.endsWith(" (error 2)")); +} + +@system unittest +{ + Key HKCU = Registry.currentUser; + assert(HKCU); + + Key key = HKCU.getKey("Control Panel"); + assert(key); + assert(key.keyCount >= 2); + + // Make sure `key` isn't garbage-collected while iterating over it. + // Trigger a collection in the first iteration and check whether we + // make it successfully to the second iteration. + int i = 0; + foreach (name; key.keyNames) + { + if (i++ > 0) + break; + + import core.memory; + GC.collect(); + } + assert(i == 2); +} diff --git a/libphobos/src/std/windows/syserror.d b/libphobos/src/std/windows/syserror.d new file mode 100644 index 0000000..7386360 --- /dev/null +++ b/libphobos/src/std/windows/syserror.d @@ -0,0 +1,201 @@ +// Written in the D programming language. + +/** + * Convert Win32 error code to string. + * + * Copyright: Copyright Digital Mars 2006 - 2013. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: $(HTTP digitalmars.com, Walter Bright) + * Credits: Based on code written by Regan Heath + */ +/* Copyright Digital Mars 2006 - 2013. + * 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) + */ +module std.windows.syserror; +import std.traits : isSomeString; + +version (StdDdoc) +{ + private + { + alias DWORD = uint; + enum LANG_NEUTRAL = 0, SUBLANG_DEFAULT = 1; + } + + /** Query the text for a Windows error code, as returned by + $(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/ms679360.aspx, + $(D GetLastError)), as a D string. + */ + string sysErrorString( + DWORD errCode, + // MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) is the user's default language + int langId = LANG_NEUTRAL, + int subLangId = SUBLANG_DEFAULT) @trusted; + + /********************* + Thrown if errors that set + $(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/ms679360.aspx, + $(D GetLastError)) occur. + */ + class WindowsException : Exception + { + private alias DWORD = int; + final @property DWORD code(); /// $(D GetLastError)'s return value. + this(DWORD code, string str=null, string file = null, size_t line = 0) @trusted; + } + + /++ + If $(D !!value) is true, $(D value) is returned. Otherwise, + $(D new WindowsException(GetLastError(), msg)) is thrown. + $(D WindowsException) assumes that the last operation set + $(D GetLastError()) appropriately. + + Example: + -------------------- + wenforce(DeleteFileA("junk.tmp"), "DeleteFile failed"); + -------------------- + +/ + T wenforce(T, S)(T value, lazy S msg = null, + string file = __FILE__, size_t line = __LINE__) @safe + if (isSomeString!S); +} +else: + +version (Windows): + +import core.sys.windows.windows; +import std.array : appender; +import std.conv : to; +import std.format : formattedWrite; +import std.windows.charset; + +string sysErrorString( + DWORD errCode, + // MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) is the user's default language + int langId = LANG_NEUTRAL, + int subLangId = SUBLANG_DEFAULT) @trusted +{ + auto buf = appender!string(); + + if (!putSysError(errCode, buf, MAKELANGID(langId, subLangId))) + { + throw new Exception( + "failed getting error string for WinAPI error code: " ~ + sysErrorString(GetLastError())); + } + + return buf.data; +} + +bool putSysError(Writer)(DWORD code, Writer w, /*WORD*/int langId = 0) +{ + wchar *lpMsgBuf = null; + auto res = FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + null, + code, + langId, + cast(LPWSTR)&lpMsgBuf, + 0, + null); + scope(exit) if (lpMsgBuf) LocalFree(lpMsgBuf); + + if (lpMsgBuf) + { + import std.string : strip; + w.put(lpMsgBuf[0 .. res].strip()); + return true; + } + else + return false; +} + + +class WindowsException : Exception +{ + import core.sys.windows.windows : DWORD; + + final @property DWORD code() { return _code; } /// $(D GetLastError)'s return value. + private DWORD _code; + + this(DWORD code, string str=null, string file = null, size_t line = 0) @trusted + { + _code = code; + + auto buf = appender!string(); + + if (str != null) + { + buf.put(str); + if (code) + buf.put(": "); + } + + if (code) + { + auto success = putSysError(code, buf); + formattedWrite(buf, success ? " (error %d)" : "Error %d", code); + } + + super(buf.data, file, line); + } +} + + +T wenforce(T, S)(T value, lazy S msg = null, +string file = __FILE__, size_t line = __LINE__) +if (isSomeString!S) +{ + if (!value) + throw new WindowsException(GetLastError(), to!string(msg), file, line); + return value; +} + +T wenforce(T)(T condition, const(char)[] name, const(wchar)* namez, string file = __FILE__, size_t line = __LINE__) +{ + if (condition) + return condition; + string names; + if (!name) + { + static string trustedToString(const(wchar)* stringz) @trusted + { + import core.stdc.wchar_ : wcslen; + import std.conv : to; + auto len = wcslen(stringz); + return to!string(stringz[0 .. len]); + } + + names = trustedToString(namez); + } + else + names = to!string(name); + throw new WindowsException(GetLastError(), names, file, line); +} + +version (Windows) +@system unittest +{ + import std.algorithm.searching : startsWith, endsWith; + import std.exception; + import std.string; + + auto e = collectException!WindowsException( + DeleteFileA("unexisting.txt").wenforce("DeleteFile") + ); + assert(e.code == ERROR_FILE_NOT_FOUND); + assert(e.msg.startsWith("DeleteFile: ")); + // can't test the entire message, as it depends on Windows locale + assert(e.msg.endsWith(" (error 2)")); + + // Test code zero + e = new WindowsException(0); + assert(e.msg == ""); + + e = new WindowsException(0, "Test"); + assert(e.msg == "Test"); +} diff --git a/libphobos/src/std/xml.d b/libphobos/src/std/xml.d new file mode 100644 index 0000000..770c56f --- /dev/null +++ b/libphobos/src/std/xml.d @@ -0,0 +1,3103 @@ +// Written in the D programming language. + +/** +$(RED Warning: This module is considered out-dated and not up to Phobos' + current standards. It will remain until we have a suitable replacement, + but be aware that it will not remain long term.) + +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 <i>may</i> 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) +*/ +module std.xml; + +enum cdata = "<![CDATA["; + +/** + * Returns true if the character is a 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 isChar(dchar c) @safe @nogc pure nothrow // rule 2 +{ + if (c <= 0xD7FF) + { + if (c >= 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 = "<?xml version=\"1.0\"?>"; + /** + * 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); + } + body + { + 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 = `<?xml version="1.0" encoding="UTF-8"?><foo></foo>`; + + 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 <title>Serenity + * ------------------------------------------------------- + */ + this(string name, string interior=null) @safe pure + { + this(new Tag(name)); + if (interior.length != 0) opCatAssign(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 opCatAssign(Text item) @safe pure + { + 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 opCatAssign(CData item) @safe pure + { + 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 opCatAssign(Comment item) @safe pure + { + 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 opCatAssign(ProcessingInstruction item) @safe pure + { + 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 opCatAssign(Element item) @safe pure + { + 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) { opCatAssign(new Text(s)); }; + xml.onCData = (string s) { opCatAssign(new CData(s)); }; + xml.onComment = (string s) { opCatAssign(new Comment(s)); }; + xml.onPI = (string s) { opCatAssign(new ProcessingInstruction(s)); }; + + xml.onStartTag[null] = (ElementParser xml) + { + auto e = new Element(xml.tag); + e.parse(xml); + opCatAssign(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 + *
    + *
  • that $(B type) is a valid enum TagType value
  • + *
  • that $(B name) consists of valid characters
  • + *
  • that each attribute name consists of valid characters
  • + *
+ */ +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 atrribute 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 typeid(name).getHash(&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 +} + +@safe unittest // issue 16241 +{ + 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()); + } + } + body + { + 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, for bug 2979 + // http://d.puremagic.com/issues/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 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; + } + + size_t hash(string s,size_t h=0) @trusted nothrow + { + return typeid(s).getHash(&s) + h; + } + + // 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/libphobos/src/std/zip.d b/libphobos/src/std/zip.d new file mode 100644 index 0000000..db47dde --- /dev/null +++ b/libphobos/src/std/zip.d @@ -0,0 +1,990 @@ +// Written in the D programming language. + +/** + * Read/write data in the $(LINK2 http://www.info-zip.org, _zip archive) format. + * Makes use of the etc.c.zlib compression library. + * + * Bugs: + * $(UL + * $(LI Multi-disk zips not supported.) + * $(LI Only Zip version 20 formats are supported.) + * $(LI Only supports compression modes 0 (no compression) and 8 (deflate).) + * $(LI Does not support encryption.) + * $(LI $(BUGZILLA 592)) + * $(LI $(BUGZILLA 2137)) + * ) + * + * Example: + * --- +// Read existing zip file. +import std.digest.crc, std.file, std.stdio, std.zip; + +void main(string[] args) +{ + // read a zip file into memory + auto zip = new ZipArchive(read(args[1])); + writeln("Archive: ", args[1]); + writefln("%-10s %-8s Name", "Length", "CRC-32"); + // iterate over all zip members + foreach (name, am; zip.directory) + { + // print some data about each member + writefln("%10s %08x %s", am.expandedSize, am.crc32, name); + assert(am.expandedData.length == 0); + // decompress the archive member + zip.expand(am); + assert(am.expandedData.length == am.expandedSize); + } +} + +// Create and write new zip file. +import std.file : write; +import std.string : representation; + +void main() +{ + char[] data = "Test data.\n".dup; + // Create an ArchiveMember for the test file. + ArchiveMember am = new ArchiveMember(); + am.name = "test.txt"; + am.expandedData(data.representation); + // Create an archive and add the member. + ZipArchive zip = new ZipArchive(); + zip.addMember(am); + // Build the archive + void[] compressed_data = zip.build(); + // Write to a file + write("test.zip", compressed_data); +} + * --- + * + * Copyright: Copyright Digital Mars 2000 - 2009. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: $(HTTP digitalmars.com, Walter Bright) + * Source: $(PHOBOSSRC std/_zip.d) + */ + +/* Copyright Digital Mars 2000 - 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) + */ +module std.zip; + +//debug=print; + +/** Thrown on error. + */ +class ZipException : Exception +{ + this(string msg) @safe + { + super("ZipException: " ~ msg); + } +} + +/** + * Compression method used by ArchiveMember + */ +enum CompressionMethod : ushort +{ + none = 0, /// No compression, just archiving + deflate = 8 /// Deflate algorithm. Use zlib library to compress +} + +/** + * A member of the ZipArchive. + */ +final class ArchiveMember +{ + import std.conv : to, octal; + import std.datetime.systime : DosFileTime, SysTime, SysTimeToDosFileTime; + + /** + * Read/Write: Usually the file name of the archive member; it is used to + * index the archive directory for the member. Each member must have a unique + * name[]. Do not change without removing member from the directory first. + */ + string name; + + ubyte[] extra; /// Read/Write: extra data for this member. + string comment; /// Read/Write: comment associated with this member. + + private ubyte[] _compressedData; + private ubyte[] _expandedData; + private uint offset; + private uint _crc32; + private uint _compressedSize; + private uint _expandedSize; + private CompressionMethod _compressionMethod; + private ushort _madeVersion = 20; + private ushort _extractVersion = 20; + private ushort _diskNumber; + private uint _externalAttributes; + private DosFileTime _time; + // by default, no explicit order goes after explicit order + private uint _index = uint.max; + + ushort flags; /// Read/Write: normally set to 0 + ushort internalAttributes; /// Read/Write + + @property ushort extractVersion() { return _extractVersion; } /// Read Only + @property uint crc32() { return _crc32; } /// Read Only: cyclic redundancy check (CRC) value + + /// Read Only: size of data of member in compressed form. + @property uint compressedSize() { return _compressedSize; } + + /// Read Only: size of data of member in expanded form. + @property uint expandedSize() { return _expandedSize; } + @property ushort diskNumber() { return _diskNumber; } /// Read Only: should be 0. + + /// Read Only: data of member in compressed form. + @property ubyte[] compressedData() { return _compressedData; } + + /// Read data of member in uncompressed form. + @property ubyte[] expandedData() { return _expandedData; } + + /// Write data of member in uncompressed form. + @property @safe void expandedData(ubyte[] ed) + { + _expandedData = ed; + _expandedSize = to!uint(_expandedData.length); + + // Clean old compressed data, if any + _compressedData.length = 0; + _compressedSize = 0; + } + + /** + * Set the OS specific file attributes, as obtained by + * $(REF getAttributes, std,file) or $(REF DirEntry.attributes, std,file), for this archive member. + */ + @property @safe void fileAttributes(uint attr) + { + version (Posix) + { + _externalAttributes = (attr & 0xFFFF) << 16; + _madeVersion &= 0x00FF; + _madeVersion |= 0x0300; // attributes are in UNIX format + } + else version (Windows) + { + _externalAttributes = attr; + _madeVersion &= 0x00FF; // attributes are in MS-DOS and OS/2 format + } + else + { + static assert(0, "Unimplemented platform"); + } + } + + version (Posix) @safe unittest + { + auto am = new ArchiveMember(); + am.fileAttributes = octal!100644; + assert(am._externalAttributes == octal!100644 << 16); + assert((am._madeVersion & 0xFF00) == 0x0300); + } + + /** + * Get the OS specific file attributes for the archive member. + * + * Returns: The file attributes or 0 if the file attributes were + * encoded for an incompatible OS (Windows vs. Posix). + * + */ + @property uint fileAttributes() const + { + version (Posix) + { + if ((_madeVersion & 0xFF00) == 0x0300) + return _externalAttributes >> 16; + return 0; + } + else version (Windows) + { + if ((_madeVersion & 0xFF00) == 0x0000) + return _externalAttributes; + return 0; + } + else + { + static assert(0, "Unimplemented platform"); + } + } + + /// Set the last modification time for this member. + @property void time(SysTime time) + { + _time = SysTimeToDosFileTime(time); + } + + /// ditto + @property void time(DosFileTime time) + { + _time = time; + } + + /// Get the last modification time for this member. + @property DosFileTime time() const + { + return _time; + } + + /** + * Read compression method used for this member + * See_Also: + * CompressionMethod + **/ + @property @safe CompressionMethod compressionMethod() { return _compressionMethod; } + + /** + * Write compression method used for this member + * See_Also: + * CompressionMethod + **/ + @property void compressionMethod(CompressionMethod cm) + { + if (cm == _compressionMethod) return; + + if (_compressedSize > 0) + throw new ZipException("Can't change compression method for a compressed element"); + + _compressionMethod = cm; + } + + /** + * The index of this archive member within the archive. + */ + @property uint index() const pure nothrow @nogc { return _index; } + @property uint index(uint value) pure nothrow @nogc { return _index = value; } + + debug(print) + { + void print() + { + printf("name = '%.*s'\n", name.length, name.ptr); + printf("\tcomment = '%.*s'\n", comment.length, comment.ptr); + printf("\tmadeVersion = x%04x\n", _madeVersion); + printf("\textractVersion = x%04x\n", extractVersion); + printf("\tflags = x%04x\n", flags); + printf("\tcompressionMethod = %d\n", compressionMethod); + printf("\ttime = %d\n", time); + printf("\tcrc32 = x%08x\n", crc32); + printf("\texpandedSize = %d\n", expandedSize); + printf("\tcompressedSize = %d\n", compressedSize); + printf("\tinternalAttributes = x%04x\n", internalAttributes); + printf("\texternalAttributes = x%08x\n", externalAttributes); + printf("\tindex = x%08x\n", index); + } + } +} + +/** + * Object representing the entire archive. + * ZipArchives are collections of ArchiveMembers. + */ +final class ZipArchive +{ + import std.algorithm.comparison : max; + import std.bitmanip : littleEndianToNative, nativeToLittleEndian; + import std.conv : to; + import std.datetime.systime : DosFileTime; + + string comment; /// Read/Write: the archive comment. Must be less than 65536 bytes in length. + + private ubyte[] _data; + private uint endrecOffset; + + private uint _diskNumber; + private uint _diskStartDir; + private uint _numEntries; + private uint _totalEntries; + private bool _isZip64; + static const ushort zip64ExtractVersion = 45; + static const int digiSignLength = 6; + static const int eocd64LocLength = 20; + static const int eocd64Length = 56; + + /// Read Only: array representing the entire contents of the archive. + @property @safe ubyte[] data() { return _data; } + + /// Read Only: 0 since multi-disk zip archives are not supported. + @property @safe uint diskNumber() { return _diskNumber; } + + /// Read Only: 0 since multi-disk zip archives are not supported + @property @safe uint diskStartDir() { return _diskStartDir; } + + /// Read Only: number of ArchiveMembers in the directory. + @property @safe uint numEntries() { return _numEntries; } + @property @safe uint totalEntries() { return _totalEntries; } /// ditto + + /// True when the archive is in Zip64 format. + @property @safe bool isZip64() { return _isZip64; } + + /// Set this to true to force building a Zip64 archive. + @property @safe void isZip64(bool value) { _isZip64 = value; } + /** + * Read Only: array indexed by the name of each member of the archive. + * All the members of the archive can be accessed with a foreach loop: + * Example: + * -------------------- + * ZipArchive archive = new ZipArchive(data); + * foreach (ArchiveMember am; archive.directory) + * { + * writefln("member name is '%s'", am.name); + * } + * -------------------- + */ + @property @safe ArchiveMember[string] directory() { return _directory; } + + private ArchiveMember[string] _directory; + + debug (print) + { + @safe void print() + { + printf("\tdiskNumber = %u\n", diskNumber); + printf("\tdiskStartDir = %u\n", diskStartDir); + printf("\tnumEntries = %u\n", numEntries); + printf("\ttotalEntries = %u\n", totalEntries); + printf("\tcomment = '%.*s'\n", comment.length, comment.ptr); + } + } + + /* ============ Creating a new archive =================== */ + + /** Constructor to use when creating a new archive. + */ + this() @safe + { + } + + /** Add de to the archive. The file is compressed on the fly. + */ + @safe void addMember(ArchiveMember de) + { + _directory[de.name] = de; + if (!de._compressedData.length) + { + switch (de.compressionMethod) + { + case CompressionMethod.none: + de._compressedData = de._expandedData; + break; + + case CompressionMethod.deflate: + import std.zlib : compress; + () @trusted + { + de._compressedData = cast(ubyte[]) compress(cast(void[]) de._expandedData); + }(); + de._compressedData = de._compressedData[2 .. de._compressedData.length - 4]; + break; + + default: + throw new ZipException("unsupported compression method"); + } + + de._compressedSize = to!uint(de._compressedData.length); + import std.zlib : crc32; + () @trusted { de._crc32 = crc32(0, cast(void[]) de._expandedData); }(); + } + assert(de._compressedData.length == de._compressedSize); + } + + /** Delete de from the archive. + */ + @safe void deleteMember(ArchiveMember de) + { + _directory.remove(de.name); + } + + /** + * Construct an archive out of the current members of the archive. + * + * Fills in the properties data[], diskNumber, diskStartDir, numEntries, + * totalEntries, and directory[]. + * For each ArchiveMember, fills in properties crc32, compressedSize, + * compressedData[]. + * + * Returns: array representing the entire archive. + */ + void[] build() + { + import std.algorithm.sorting : sort; + uint i; + uint directoryOffset; + + if (comment.length > 0xFFFF) + throw new ZipException("archive comment longer than 65535"); + + // Compress each member; compute size + uint archiveSize = 0; + uint directorySize = 0; + auto directory = _directory.values().sort!((x, y) => x.index < y.index).release; + foreach (ArchiveMember de; directory) + { + if (to!ulong(archiveSize) + 30 + de.name.length + de.extra.length + de.compressedSize + + directorySize + 46 + de.name.length + de.extra.length + de.comment.length + + 22 + comment.length + eocd64LocLength + eocd64Length > uint.max) + throw new ZipException("zip files bigger than 4 GB are unsupported"); + + archiveSize += 30 + de.name.length + + de.extra.length + + de.compressedSize; + directorySize += 46 + de.name.length + + de.extra.length + + de.comment.length; + } + + if (!isZip64 && _directory.length > ushort.max) + _isZip64 = true; + uint dataSize = archiveSize + directorySize + 22 + cast(uint) comment.length; + if (isZip64) + dataSize += eocd64LocLength + eocd64Length; + + _data = new ubyte[dataSize]; + + // Populate the data[] + + // Store each archive member + i = 0; + foreach (ArchiveMember de; directory) + { + de.offset = i; + _data[i .. i + 4] = cast(ubyte[])"PK\x03\x04"; + putUshort(i + 4, de.extractVersion); + putUshort(i + 6, de.flags); + putUshort(i + 8, de._compressionMethod); + putUint (i + 10, cast(uint) de.time); + putUint (i + 14, de.crc32); + putUint (i + 18, de.compressedSize); + putUint (i + 22, to!uint(de.expandedSize)); + putUshort(i + 26, cast(ushort) de.name.length); + putUshort(i + 28, cast(ushort) de.extra.length); + i += 30; + + _data[i .. i + de.name.length] = (cast(ubyte[]) de.name)[]; + i += de.name.length; + _data[i .. i + de.extra.length] = (cast(ubyte[]) de.extra)[]; + i += de.extra.length; + _data[i .. i + de.compressedSize] = de.compressedData[]; + i += de.compressedSize; + } + + // Write directory + directoryOffset = i; + _numEntries = 0; + foreach (ArchiveMember de; directory) + { + _data[i .. i + 4] = cast(ubyte[])"PK\x01\x02"; + putUshort(i + 4, de._madeVersion); + putUshort(i + 6, de.extractVersion); + putUshort(i + 8, de.flags); + putUshort(i + 10, de._compressionMethod); + putUint (i + 12, cast(uint) de.time); + putUint (i + 16, de.crc32); + putUint (i + 20, de.compressedSize); + putUint (i + 24, de.expandedSize); + putUshort(i + 28, cast(ushort) de.name.length); + putUshort(i + 30, cast(ushort) de.extra.length); + putUshort(i + 32, cast(ushort) de.comment.length); + putUshort(i + 34, de.diskNumber); + putUshort(i + 36, de.internalAttributes); + putUint (i + 38, de._externalAttributes); + putUint (i + 42, de.offset); + i += 46; + + _data[i .. i + de.name.length] = (cast(ubyte[]) de.name)[]; + i += de.name.length; + _data[i .. i + de.extra.length] = (cast(ubyte[]) de.extra)[]; + i += de.extra.length; + _data[i .. i + de.comment.length] = (cast(ubyte[]) de.comment)[]; + i += de.comment.length; + _numEntries++; + } + _totalEntries = numEntries; + + if (isZip64) + { + // Write zip64 end of central directory record + uint eocd64Offset = i; + _data[i .. i + 4] = cast(ubyte[])"PK\x06\x06"; + putUlong (i + 4, eocd64Length - 12); + putUshort(i + 12, zip64ExtractVersion); + putUshort(i + 14, zip64ExtractVersion); + putUint (i + 16, diskNumber); + putUint (i + 20, diskStartDir); + putUlong (i + 24, numEntries); + putUlong (i + 32, totalEntries); + putUlong (i + 40, directorySize); + putUlong (i + 48, directoryOffset); + i += eocd64Length; + + // Write zip64 end of central directory record locator + _data[i .. i + 4] = cast(ubyte[])"PK\x06\x07"; + putUint (i + 4, diskNumber); + putUlong (i + 8, eocd64Offset); + putUint (i + 16, 1); + i += eocd64LocLength; + } + + // Write end record + endrecOffset = i; + _data[i .. i + 4] = cast(ubyte[])"PK\x05\x06"; + putUshort(i + 4, cast(ushort) diskNumber); + putUshort(i + 6, cast(ushort) diskStartDir); + putUshort(i + 8, (numEntries > ushort.max ? ushort.max : cast(ushort) numEntries)); + putUshort(i + 10, (totalEntries > ushort.max ? ushort.max : cast(ushort) totalEntries)); + putUint (i + 12, directorySize); + putUint (i + 16, directoryOffset); + putUshort(i + 20, cast(ushort) comment.length); + i += 22; + + // Write archive comment + assert(i + comment.length == data.length); + _data[i .. data.length] = (cast(ubyte[]) comment)[]; + + return cast(void[]) data; + } + + /* ============ Reading an existing archive =================== */ + + /** + * Constructor to use when reading an existing archive. + * + * Fills in the properties data[], diskNumber, diskStartDir, numEntries, + * totalEntries, comment[], and directory[]. + * For each ArchiveMember, fills in + * properties madeVersion, extractVersion, flags, compressionMethod, time, + * crc32, compressedSize, expandedSize, compressedData[], diskNumber, + * internalAttributes, externalAttributes, name[], extra[], comment[]. + * Use expand() to get the expanded data for each ArchiveMember. + * + * Params: + * buffer = the entire contents of the archive. + */ + + this(void[] buffer) + { uint iend; + uint i; + int endcommentlength; + uint directorySize; + uint directoryOffset; + + this._data = cast(ubyte[]) buffer; + + if (data.length > uint.max - 2) + throw new ZipException("zip files bigger than 4 GB are unsupported"); + + // Find 'end record index' by searching backwards for signature + iend = (data.length > 66_000 ? to!uint(data.length - 66_000) : 0); + for (i = to!uint(data.length) - 22; 1; i--) + { + if (i < iend || i >= data.length) + throw new ZipException("no end record"); + + if (_data[i .. i + 4] == cast(ubyte[])"PK\x05\x06") + { + endcommentlength = getUshort(i + 20); + if (i + 22 + endcommentlength > data.length + || i + 22 + endcommentlength < i) + continue; + comment = cast(string)(_data[i + 22 .. i + 22 + endcommentlength]); + endrecOffset = i; + + uint k = i - eocd64LocLength; + if (k < i && _data[k .. k + 4] == cast(ubyte[])"PK\x06\x07") + { + _isZip64 = true; + i = k; + } + + break; + } + } + + if (isZip64) + { + // Read Zip64 record data + ulong eocdOffset = getUlong(i + 8); + if (eocdOffset + eocd64Length > _data.length) + throw new ZipException("corrupted directory"); + + i = to!uint(eocdOffset); + if (_data[i .. i + 4] != cast(ubyte[])"PK\x06\x06") + throw new ZipException("invalid Zip EOCD64 signature"); + + ulong eocd64Size = getUlong(i + 4); + if (eocd64Size + i - 12 > data.length) + throw new ZipException("invalid Zip EOCD64 size"); + + _diskNumber = getUint(i + 16); + _diskStartDir = getUint(i + 20); + + ulong numEntriesUlong = getUlong(i + 24); + ulong totalEntriesUlong = getUlong(i + 32); + ulong directorySizeUlong = getUlong(i + 40); + ulong directoryOffsetUlong = getUlong(i + 48); + + if (numEntriesUlong > uint.max) + throw new ZipException("supposedly more than 4294967296 files in archive"); + + if (numEntriesUlong != totalEntriesUlong) + throw new ZipException("multiple disk zips not supported"); + + if (directorySizeUlong > i || directoryOffsetUlong > i + || directorySizeUlong + directoryOffsetUlong > i) + throw new ZipException("corrupted directory"); + + _numEntries = to!uint(numEntriesUlong); + _totalEntries = to!uint(totalEntriesUlong); + directorySize = to!uint(directorySizeUlong); + directoryOffset = to!uint(directoryOffsetUlong); + } + else + { + // Read end record data + _diskNumber = getUshort(i + 4); + _diskStartDir = getUshort(i + 6); + + _numEntries = getUshort(i + 8); + _totalEntries = getUshort(i + 10); + + if (numEntries != totalEntries) + throw new ZipException("multiple disk zips not supported"); + + directorySize = getUint(i + 12); + directoryOffset = getUint(i + 16); + + if (directoryOffset + directorySize > i) + throw new ZipException("corrupted directory"); + } + + i = directoryOffset; + for (int n = 0; n < numEntries; n++) + { + /* The format of an entry is: + * 'PK' 1, 2 + * directory info + * path + * extra data + * comment + */ + + uint namelen; + uint extralen; + uint commentlen; + + if (_data[i .. i + 4] != cast(ubyte[])"PK\x01\x02") + throw new ZipException("invalid directory entry 1"); + ArchiveMember de = new ArchiveMember(); + de._index = n; + de._madeVersion = getUshort(i + 4); + de._extractVersion = getUshort(i + 6); + de.flags = getUshort(i + 8); + de._compressionMethod = cast(CompressionMethod) getUshort(i + 10); + de.time = cast(DosFileTime) getUint(i + 12); + de._crc32 = getUint(i + 16); + de._compressedSize = getUint(i + 20); + de._expandedSize = getUint(i + 24); + namelen = getUshort(i + 28); + extralen = getUshort(i + 30); + commentlen = getUshort(i + 32); + de._diskNumber = getUshort(i + 34); + de.internalAttributes = getUshort(i + 36); + de._externalAttributes = getUint(i + 38); + de.offset = getUint(i + 42); + i += 46; + + if (i + namelen + extralen + commentlen > directoryOffset + directorySize) + throw new ZipException("invalid directory entry 2"); + + de.name = cast(string)(_data[i .. i + namelen]); + i += namelen; + de.extra = _data[i .. i + extralen]; + i += extralen; + de.comment = cast(string)(_data[i .. i + commentlen]); + i += commentlen; + + immutable uint dataOffset = de.offset + 30 + namelen + extralen; + if (dataOffset + de.compressedSize > endrecOffset) + throw new ZipException("Invalid directory entry offset or size."); + de._compressedData = _data[dataOffset .. dataOffset + de.compressedSize]; + + _directory[de.name] = de; + + } + if (i != directoryOffset + directorySize) + throw new ZipException("invalid directory entry 3"); + } + + /***** + * Decompress the contents of archive member de and return the expanded + * data. + * + * Fills in properties extractVersion, flags, compressionMethod, time, + * crc32, compressedSize, expandedSize, expandedData[], name[], extra[]. + */ + ubyte[] expand(ArchiveMember de) + { uint namelen; + uint extralen; + + if (_data[de.offset .. de.offset + 4] != cast(ubyte[])"PK\x03\x04") + throw new ZipException("invalid directory entry 4"); + + // These values should match what is in the main zip archive directory + de._extractVersion = getUshort(de.offset + 4); + de.flags = getUshort(de.offset + 6); + de._compressionMethod = cast(CompressionMethod) getUshort(de.offset + 8); + de.time = cast(DosFileTime) getUint(de.offset + 10); + de._crc32 = getUint(de.offset + 14); + de._compressedSize = max(getUint(de.offset + 18), de.compressedSize); + de._expandedSize = max(getUint(de.offset + 22), de.expandedSize); + namelen = getUshort(de.offset + 26); + extralen = getUshort(de.offset + 28); + + debug(print) + { + printf("\t\texpandedSize = %d\n", de.expandedSize); + printf("\t\tcompressedSize = %d\n", de.compressedSize); + printf("\t\tnamelen = %d\n", namelen); + printf("\t\textralen = %d\n", extralen); + } + + if (de.flags & 1) + throw new ZipException("encryption not supported"); + + int i; + i = de.offset + 30 + namelen + extralen; + if (i + de.compressedSize > endrecOffset) + throw new ZipException("invalid directory entry 5"); + + de._compressedData = _data[i .. i + de.compressedSize]; + debug(print) arrayPrint(de.compressedData); + + switch (de.compressionMethod) + { + case CompressionMethod.none: + de._expandedData = de.compressedData; + return de.expandedData; + + case CompressionMethod.deflate: + // -15 is a magic value used to decompress zip files. + // It has the effect of not requiring the 2 byte header + // and 4 byte trailer. + import std.zlib : uncompress; + de._expandedData = cast(ubyte[]) uncompress(cast(void[]) de.compressedData, de.expandedSize, -15); + return de.expandedData; + + default: + throw new ZipException("unsupported compression method"); + } + } + + /* ============ Utility =================== */ + + @safe ushort getUshort(int i) + { + ubyte[2] result = data[i .. i + 2]; + return littleEndianToNative!ushort(result); + } + + @safe uint getUint(int i) + { + ubyte[4] result = data[i .. i + 4]; + return littleEndianToNative!uint(result); + } + + @safe ulong getUlong(int i) + { + ubyte[8] result = data[i .. i + 8]; + return littleEndianToNative!ulong(result); + } + + @safe void putUshort(int i, ushort us) + { + data[i .. i + 2] = nativeToLittleEndian(us); + } + + @safe void putUint(int i, uint ui) + { + data[i .. i + 4] = nativeToLittleEndian(ui); + } + + @safe void putUlong(int i, ulong ul) + { + data[i .. i + 8] = nativeToLittleEndian(ul); + } +} + +debug(print) +{ + @safe void arrayPrint(ubyte[] array) + { + printf("array %p,%d\n", cast(void*) array, array.length); + for (int i = 0; i < array.length; i++) + { + printf("%02x ", array[i]); + if (((i + 1) & 15) == 0) + printf("\n"); + } + printf("\n"); + } +} + +@system unittest +{ + // @system due to (at least) ZipArchive.build + auto zip1 = new ZipArchive(); + auto zip2 = new ZipArchive(); + auto am1 = new ArchiveMember(); + am1.name = "foo"; + am1.expandedData = new ubyte[](1024); + zip1.addMember(am1); + auto data1 = zip1.build(); + zip2.addMember(zip1.directory["foo"]); + zip2.build(); + auto am2 = zip2.directory["foo"]; + zip2.expand(am2); + assert(am1.expandedData == am2.expandedData); + auto zip3 = new ZipArchive(data1); + zip3.build(); + assert(zip3.directory["foo"].compressedSize == am1.compressedSize); + + // Test if packing and unpacking produces the original data + import std.conv, std.stdio; + import std.random : uniform, MinstdRand0; + MinstdRand0 gen; + const uint itemCount = 20, minSize = 10, maxSize = 500; + foreach (variant; 0 .. 2) + { + bool useZip64 = !!variant; + zip1 = new ZipArchive(); + zip1.isZip64 = useZip64; + ArchiveMember[itemCount] ams; + foreach (i; 0 .. itemCount) + { + ams[i] = new ArchiveMember(); + ams[i].name = to!string(i); + ams[i].expandedData = new ubyte[](uniform(minSize, maxSize)); + foreach (ref ubyte c; ams[i].expandedData) + c = cast(ubyte)(uniform(0, 256)); + ams[i].compressionMethod = CompressionMethod.deflate; + zip1.addMember(ams[i]); + } + auto zippedData = zip1.build(); + zip2 = new ZipArchive(zippedData); + assert(zip2.isZip64 == useZip64); + foreach (am; ams) + { + am2 = zip2.directory[am.name]; + zip2.expand(am2); + assert(am.crc32 == am2.crc32); + assert(am.expandedData == am2.expandedData); + } + } +} + +@system unittest +{ + import std.conv : to; + import std.random : Mt19937, randomShuffle; + // Test if packing and unpacking preserves order. + auto rand = Mt19937(15966); + string[] names; + int value = 0; + // Generate a series of unique numbers as filenames. + foreach (i; 0 .. 20) + { + value += 1 + rand.front & 0xFFFF; + rand.popFront; + names ~= value.to!string; + } + // Insert them in a random order. + names.randomShuffle(rand); + auto zip1 = new ZipArchive(); + foreach (i, name; names) + { + auto member = new ArchiveMember(); + member.name = name; + member.expandedData = cast(ubyte[]) name; + member.index = cast(int) i; + zip1.addMember(member); + } + auto data = zip1.build(); + + // Ensure that they appear in the same order. + auto zip2 = new ZipArchive(data); + foreach (i, name; names) + { + const member = zip2.directory[name]; + assert(member.index == i, "member " ~ name ~ " had index " ~ + member.index.to!string ~ " but we expected index " ~ i.to!string ~ + ". The input array was " ~ names.to!string); + } +} + +@system unittest +{ + import std.zlib; + + ubyte[] src = cast(ubyte[]) +"the quick brown fox jumps over the lazy dog\r +the quick brown fox jumps over the lazy dog\r +"; + auto dst = cast(ubyte[]) compress(cast(void[]) src); + auto after = cast(ubyte[]) uncompress(cast(void[]) dst); + assert(src == after); +} + +@system unittest +{ + // @system due to ZipArchive.build + import std.datetime; + ubyte[] buf = [1, 2, 3, 4, 5, 0, 7, 8, 9]; + + auto ar = new ZipArchive; + auto am = new ArchiveMember; // 10 + am.name = "buf"; + am.expandedData = buf; + am.compressionMethod = CompressionMethod.deflate; + am.time = SysTimeToDosFileTime(Clock.currTime()); + ar.addMember(am); // 15 + + auto zip1 = ar.build(); + auto arAfter = new ZipArchive(zip1); + assert(arAfter.directory.length == 1); + auto amAfter = arAfter.directory["buf"]; + arAfter.expand(amAfter); + assert(amAfter.name == am.name); + assert(amAfter.expandedData == am.expandedData); + assert(amAfter.time == am.time); +} + +// Non-Android Posix-only, because we can't rely on the unzip command being +// available on Android or Windows +version (Android) {} else +version (Posix) @system unittest +{ + import std.datetime, std.file, std.format, std.path, std.process, std.stdio; + + auto zr = new ZipArchive(); + auto am = new ArchiveMember(); + am.compressionMethod = CompressionMethod.deflate; + am.name = "foo.bar"; + am.time = SysTimeToDosFileTime(Clock.currTime()); + am.expandedData = cast(ubyte[])"We all live in a yellow submarine, a yellow submarine"; + zr.addMember(am); + auto data2 = zr.build(); + + mkdirRecurse(deleteme); + scope(exit) rmdirRecurse(deleteme); + string zipFile = buildPath(deleteme, "foo.zip"); + std.file.write(zipFile, cast(byte[]) data2); + + auto result = executeShell(format("unzip -l %s", zipFile)); + scope(failure) writeln(result.output); + assert(result.status == 0); +} diff --git a/libphobos/src/std/zlib.d b/libphobos/src/std/zlib.d new file mode 100644 index 0000000..e6cce24 --- /dev/null +++ b/libphobos/src/std/zlib.d @@ -0,0 +1,760 @@ +// Written in the D programming language. + +/** + * Compress/decompress data using the $(HTTP www._zlib.net, _zlib library). + * + * Examples: + * + * If you have a small buffer you can use $(LREF compress) and + * $(LREF uncompress) directly. + * + * ------- + * import std.zlib; + * + * auto src = + * "the quick brown fox jumps over the lazy dog\r + * the quick brown fox jumps over the lazy dog\r"; + * + * ubyte[] dst; + * ubyte[] result; + * + * dst = compress(src); + * result = cast(ubyte[]) uncompress(dst); + * assert(result == src); + * ------- + * + * When the data to be compressed doesn't fit in one buffer, use + * $(LREF Compress) and $(LREF UnCompress). + * + * ------- + * import std.zlib; + * import std.stdio; + * import std.conv : to; + * import std.algorithm.iteration : map; + * + * UnCompress decmp = new UnCompress; + * foreach (chunk; stdin.byChunk(4096).map!(x => decmp.uncompress(x))) + * { + * chunk.to!string.write; + * } + + * ------- + * + * References: + * $(HTTP en.wikipedia.org/wiki/Zlib, Wikipedia) + * + * Copyright: Copyright Digital Mars 2000 - 2011. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: $(HTTP digitalmars.com, Walter Bright) + * Source: $(PHOBOSSRC std/_zlib.d) + */ +/* Copyright Digital Mars 2000 - 2011. + * 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) + */ +module std.zlib; + +//debug=zlib; // uncomment to turn on debugging printf's + +import etc.c.zlib; + +// Values for 'mode' + +enum +{ + Z_NO_FLUSH = 0, + Z_SYNC_FLUSH = 2, + Z_FULL_FLUSH = 3, + Z_FINISH = 4, +} + +/************************************* + * Errors throw a ZlibException. + */ + +class ZlibException : Exception +{ + this(int errnum) + { string msg; + + switch (errnum) + { + case Z_STREAM_END: msg = "stream end"; break; + case Z_NEED_DICT: msg = "need dict"; break; + case Z_ERRNO: msg = "errno"; break; + case Z_STREAM_ERROR: msg = "stream error"; break; + case Z_DATA_ERROR: msg = "data error"; break; + case Z_MEM_ERROR: msg = "mem error"; break; + case Z_BUF_ERROR: msg = "buf error"; break; + case Z_VERSION_ERROR: msg = "version error"; break; + default: msg = "unknown error"; break; + } + super(msg); + } +} + +/** + * $(P Compute the Adler-32 checksum of a buffer's worth of data.) + * + * Params: + * adler = the starting checksum for the computation. Use 1 + * for a new checksum. Use the output of this function + * for a cumulative checksum. + * buf = buffer containing input data + * + * Returns: + * A $(D uint) checksum for the provided input data and starting checksum + * + * See_Also: + * $(LINK http://en.wikipedia.org/wiki/Adler-32) + */ + +uint adler32(uint adler, const(void)[] buf) +{ + import std.range : chunks; + foreach (chunk; (cast(ubyte[]) buf).chunks(0xFFFF0000)) + { + adler = etc.c.zlib.adler32(adler, chunk.ptr, cast(uint) chunk.length); + } + return adler; +} + +/// +@system unittest +{ + static ubyte[] data = [1,2,3,4,5,6,7,8,9,10]; + + uint adler = adler32(0u, data); + assert(adler == 0xdc0037); +} + +@system unittest +{ + static string data = "test"; + + uint adler = adler32(1, data); + assert(adler == 0x045d01c1); +} + +/** + * $(P Compute the CRC32 checksum of a buffer's worth of data.) + * + * Params: + * crc = the starting checksum for the computation. Use 0 + * for a new checksum. Use the output of this function + * for a cumulative checksum. + * buf = buffer containing input data + * + * Returns: + * A $(D uint) checksum for the provided input data and starting checksum + * + * See_Also: + * $(LINK http://en.wikipedia.org/wiki/Cyclic_redundancy_check) + */ + +uint crc32(uint crc, const(void)[] buf) +{ + import std.range : chunks; + foreach (chunk; (cast(ubyte[]) buf).chunks(0xFFFF0000)) + { + crc = etc.c.zlib.crc32(crc, chunk.ptr, cast(uint) chunk.length); + } + return crc; +} + +@system unittest +{ + static ubyte[] data = [1,2,3,4,5,6,7,8,9,10]; + + uint crc; + + debug(zlib) printf("D.zlib.crc32.unittest\n"); + crc = crc32(0u, cast(void[]) data); + debug(zlib) printf("crc = %x\n", crc); + assert(crc == 0x2520577b); +} + +/** + * $(P Compress data) + * + * Params: + * srcbuf = buffer containing the data to compress + * level = compression level. Legal values are -1 .. 9, with -1 indicating + * the default level (6), 0 indicating no compression, 1 being the + * least compression and 9 being the most. + * + * Returns: + * the compressed data + */ + +ubyte[] compress(const(void)[] srcbuf, int level) +in +{ + assert(-1 <= level && level <= 9); +} +body +{ + import core.memory : GC; + auto destlen = srcbuf.length + ((srcbuf.length + 1023) / 1024) + 12; + auto destbuf = new ubyte[destlen]; + auto err = etc.c.zlib.compress2(destbuf.ptr, &destlen, cast(ubyte *) srcbuf.ptr, srcbuf.length, level); + if (err) + { + GC.free(destbuf.ptr); + throw new ZlibException(err); + } + + destbuf.length = destlen; + return destbuf; +} + +/********************************************* + * ditto + */ + +ubyte[] compress(const(void)[] srcbuf) +{ + return compress(srcbuf, Z_DEFAULT_COMPRESSION); +} + +/********************************************* + * Decompresses the data in srcbuf[]. + * Params: + * srcbuf = buffer containing the compressed data. + * destlen = size of the uncompressed data. + * It need not be accurate, but the decompression will be faster + * if the exact size is supplied. + * winbits = the base two logarithm of the maximum window size. + * Returns: the decompressed data. + */ + +void[] uncompress(const(void)[] srcbuf, size_t destlen = 0u, int winbits = 15) +{ + import std.conv : to; + int err; + ubyte[] destbuf; + + if (!destlen) + destlen = srcbuf.length * 2 + 1; + + etc.c.zlib.z_stream zs; + zs.next_in = cast(typeof(zs.next_in)) srcbuf.ptr; + zs.avail_in = to!uint(srcbuf.length); + err = etc.c.zlib.inflateInit2(&zs, winbits); + if (err) + { + throw new ZlibException(err); + } + + size_t olddestlen = 0u; + + loop: + while (true) + { + destbuf.length = destlen; + zs.next_out = cast(typeof(zs.next_out)) &destbuf[olddestlen]; + zs.avail_out = to!uint(destlen - olddestlen); + olddestlen = destlen; + + err = etc.c.zlib.inflate(&zs, Z_NO_FLUSH); + switch (err) + { + case Z_OK: + destlen = destbuf.length * 2; + continue loop; + + case Z_STREAM_END: + destbuf.length = zs.total_out; + err = etc.c.zlib.inflateEnd(&zs); + if (err != Z_OK) + throw new ZlibException(err); + return destbuf; + + default: + etc.c.zlib.inflateEnd(&zs); + throw new ZlibException(err); + } + } + assert(0); +} + +@system unittest +{ + auto src = +"the quick brown fox jumps over the lazy dog\r +the quick brown fox jumps over the lazy dog\r +"; + ubyte[] dst; + ubyte[] result; + + //arrayPrint(src); + dst = compress(src); + //arrayPrint(dst); + result = cast(ubyte[]) uncompress(dst); + //arrayPrint(result); + assert(result == src); +} + +@system unittest +{ + ubyte[] src = new ubyte[1000000]; + ubyte[] dst; + ubyte[] result; + + src[] = 0x80; + dst = compress(src); + assert(dst.length*2 + 1 < src.length); + result = cast(ubyte[]) uncompress(dst); + assert(result == src); +} + +/+ +void arrayPrint(ubyte[] array) +{ + //printf("array %p,%d\n", cast(void*) array, array.length); + for (size_t i = 0; i < array.length; i++) + { + printf("%02x ", array[i]); + if (((i + 1) & 15) == 0) + printf("\n"); + } + printf("\n\n"); +} ++/ + +/// the header format the compressed stream is wrapped in +enum HeaderFormat { + deflate, /// a standard zlib header + gzip, /// a gzip file format header + determineFromData /// used when decompressing. Try to automatically detect the stream format by looking at the data +} + +/********************************************* + * Used when the data to be compressed is not all in one buffer. + */ + +class Compress +{ + import std.conv : to; + + private: + z_stream zs; + int level = Z_DEFAULT_COMPRESSION; + int inited; + immutable bool gzip; + + void error(int err) + { + if (inited) + { deflateEnd(&zs); + inited = 0; + } + throw new ZlibException(err); + } + + public: + + /** + * Constructor. + * + * Params: + * level = compression level. Legal values are 1 .. 9, with 1 being the least + * compression and 9 being the most. The default value is 6. + * header = sets the compression type to one of the options available + * in $(LREF HeaderFormat). Defaults to HeaderFormat.deflate. + * + * See_Also: + * $(LREF compress), $(LREF HeaderFormat) + */ + this(int level, HeaderFormat header = HeaderFormat.deflate) + in + { + assert(1 <= level && level <= 9); + } + body + { + this.level = level; + this.gzip = header == HeaderFormat.gzip; + } + + /// ditto + this(HeaderFormat header = HeaderFormat.deflate) + { + this.gzip = header == HeaderFormat.gzip; + } + + ~this() + { int err; + + if (inited) + { + inited = 0; + deflateEnd(&zs); + } + } + + /** + * Compress the data in buf and return the compressed data. + * Params: + * buf = data to compress + * + * Returns: + * the compressed data. The buffers returned from successive calls to this should be concatenated together. + * + */ + const(void)[] compress(const(void)[] buf) + { + import core.memory : GC; + int err; + ubyte[] destbuf; + + if (buf.length == 0) + return null; + + if (!inited) + { + err = deflateInit2(&zs, level, Z_DEFLATED, 15 + (gzip ? 16 : 0), 8, Z_DEFAULT_STRATEGY); + if (err) + error(err); + inited = 1; + } + + destbuf = new ubyte[zs.avail_in + buf.length]; + zs.next_out = destbuf.ptr; + zs.avail_out = to!uint(destbuf.length); + + if (zs.avail_in) + buf = zs.next_in[0 .. zs.avail_in] ~ cast(ubyte[]) buf; + + zs.next_in = cast(typeof(zs.next_in)) buf.ptr; + zs.avail_in = to!uint(buf.length); + + err = deflate(&zs, Z_NO_FLUSH); + if (err != Z_STREAM_END && err != Z_OK) + { + GC.free(destbuf.ptr); + error(err); + } + destbuf.length = destbuf.length - zs.avail_out; + return destbuf; + } + + /*** + * Compress and return any remaining data. + * The returned data should be appended to that returned by compress(). + * Params: + * mode = one of the following: + * $(DL + $(DT Z_SYNC_FLUSH ) + $(DD Syncs up flushing to the next byte boundary. + Used when more data is to be compressed later on.) + $(DT Z_FULL_FLUSH ) + $(DD Syncs up flushing to the next byte boundary. + Used when more data is to be compressed later on, + and the decompressor needs to be restartable at this + point.) + $(DT Z_FINISH) + $(DD (default) Used when finished compressing the data. ) + ) + */ + void[] flush(int mode = Z_FINISH) + in + { + assert(mode == Z_FINISH || mode == Z_SYNC_FLUSH || mode == Z_FULL_FLUSH); + } + body + { + import core.memory : GC; + ubyte[] destbuf; + ubyte[512] tmpbuf = void; + int err; + + if (!inited) + return null; + + /* may be zs.avail_out+ + * zs.avail_out is set nonzero by deflate in previous compress() + */ + //tmpbuf = new void[zs.avail_out]; + zs.next_out = tmpbuf.ptr; + zs.avail_out = tmpbuf.length; + + while ( (err = deflate(&zs, mode)) != Z_STREAM_END) + { + if (err == Z_OK) + { + if (zs.avail_out != 0 && mode != Z_FINISH) + break; + else if (zs.avail_out == 0) + { + destbuf ~= tmpbuf; + zs.next_out = tmpbuf.ptr; + zs.avail_out = tmpbuf.length; + continue; + } + err = Z_BUF_ERROR; + } + GC.free(destbuf.ptr); + error(err); + } + destbuf ~= tmpbuf[0 .. (tmpbuf.length - zs.avail_out)]; + + if (mode == Z_FINISH) + { + err = deflateEnd(&zs); + inited = 0; + if (err) + error(err); + } + return destbuf; + } +} + +/****** + * Used when the data to be decompressed is not all in one buffer. + */ + +class UnCompress +{ + import std.conv : to; + + private: + z_stream zs; + int inited; + int done; + size_t destbufsize; + + HeaderFormat format; + + void error(int err) + { + if (inited) + { inflateEnd(&zs); + inited = 0; + } + throw new ZlibException(err); + } + + public: + + /** + * Construct. destbufsize is the same as for D.zlib.uncompress(). + */ + this(uint destbufsize) + { + this.destbufsize = destbufsize; + } + + /** ditto */ + this(HeaderFormat format = HeaderFormat.determineFromData) + { + this.format = format; + } + + ~this() + { int err; + + if (inited) + { + inited = 0; + inflateEnd(&zs); + } + done = 1; + } + + /** + * Decompress the data in buf and return the decompressed data. + * The buffers returned from successive calls to this should be concatenated + * together. + */ + const(void)[] uncompress(const(void)[] buf) + in + { + assert(!done); + } + body + { + import core.memory : GC; + int err; + ubyte[] destbuf; + + if (buf.length == 0) + return null; + + if (!inited) + { + int windowBits = 15; + if (format == HeaderFormat.gzip) + windowBits += 16; + else if (format == HeaderFormat.determineFromData) + windowBits += 32; + + err = inflateInit2(&zs, windowBits); + if (err) + error(err); + inited = 1; + } + + if (!destbufsize) + destbufsize = to!uint(buf.length) * 2; + destbuf = new ubyte[zs.avail_in * 2 + destbufsize]; + zs.next_out = destbuf.ptr; + zs.avail_out = to!uint(destbuf.length); + + if (zs.avail_in) + buf = zs.next_in[0 .. zs.avail_in] ~ cast(ubyte[]) buf; + + zs.next_in = cast(ubyte*) buf.ptr; + zs.avail_in = to!uint(buf.length); + + err = inflate(&zs, Z_NO_FLUSH); + if (err != Z_STREAM_END && err != Z_OK) + { + GC.free(destbuf.ptr); + error(err); + } + destbuf.length = destbuf.length - zs.avail_out; + return destbuf; + } + + /** + * Decompress and return any remaining data. + * The returned data should be appended to that returned by uncompress(). + * The UnCompress object cannot be used further. + */ + void[] flush() + in + { + assert(!done); + } + out + { + assert(done); + } + body + { + import core.memory : GC; + ubyte[] extra; + ubyte[] destbuf; + int err; + + done = 1; + if (!inited) + return null; + + L1: + destbuf = new ubyte[zs.avail_in * 2 + 100]; + zs.next_out = destbuf.ptr; + zs.avail_out = to!uint(destbuf.length); + + err = etc.c.zlib.inflate(&zs, Z_NO_FLUSH); + if (err == Z_OK && zs.avail_out == 0) + { + extra ~= destbuf; + goto L1; + } + if (err != Z_STREAM_END) + { + GC.free(destbuf.ptr); + if (err == Z_OK) + err = Z_BUF_ERROR; + error(err); + } + destbuf = destbuf.ptr[0 .. zs.next_out - destbuf.ptr]; + err = etc.c.zlib.inflateEnd(&zs); + inited = 0; + if (err) + error(err); + if (extra.length) + destbuf = extra ~ destbuf; + return destbuf; + } +} + +/* ========================== unittest ========================= */ + +import std.random; +import std.stdio; + +@system unittest // by Dave +{ + debug(zlib) writeln("std.zlib.unittest"); + + bool CompressThenUncompress (void[] src) + { + ubyte[] dst = std.zlib.compress(src); + double ratio = (dst.length / cast(double) src.length); + debug(zlib) writef("src.length: %1$d, dst: %2$d, Ratio = %3$f", src.length, dst.length, ratio); + ubyte[] uncompressedBuf; + uncompressedBuf = cast(ubyte[]) std.zlib.uncompress(dst); + assert(src.length == uncompressedBuf.length); + assert(src == uncompressedBuf); + + return true; + } + + + // smallish buffers + for (int idx = 0; idx < 25; idx++) + { + char[] buf = new char[uniform(0, 100)]; + + // Alternate between more & less compressible + foreach (ref char c; buf) + c = cast(char) (' ' + (uniform(0, idx % 2 ? 91 : 2))); + + if (CompressThenUncompress(buf)) + { + debug(zlib) writeln("; Success."); + } + else + { + return; + } + } + + // larger buffers + for (int idx = 0; idx < 25; idx++) + { + char[] buf = new char[uniform(0, 1000/*0000*/)]; + + // Alternate between more & less compressible + foreach (ref char c; buf) + c = cast(char) (' ' + (uniform(0, idx % 2 ? 91 : 10))); + + if (CompressThenUncompress(buf)) + { + debug(zlib) writefln("; Success."); + } + else + { + return; + } + } + + debug(zlib) writefln("PASSED std.zlib.unittest"); +} + + +@system unittest // by Artem Rebrov +{ + Compress cmp = new Compress; + UnCompress decmp = new UnCompress; + + const(void)[] input; + input = "tesatdffadf"; + + const(void)[] buf = cmp.compress(input); + buf ~= cmp.flush(); + const(void)[] output = decmp.uncompress(buf); + + //writefln("input = '%s'", cast(char[]) input); + //writefln("output = '%s'", cast(char[]) output); + assert( output[] == input[] ); +} + +@system unittest +{ + static assert(__traits(compiles, etc.c.zlib.gzclose(null))); // bugzilla 15457 +} -- cgit v1.1